radix-vue / shadcn-vue

Vue port of shadcn-ui
https://www.shadcn-vue.com/
MIT License
4.27k stars 249 forks source link

[Bug]: Select not compatible with opacity transition #440

Open iyume opened 5 months ago

iyume commented 5 months ago

Reproduction

<script setup lang="ts">
import {
  Select,
  SelectContent,
  SelectGroup,
  SelectItem,
  SelectLabel,
  SelectTrigger,
  SelectValue,
} from "@/components/ui/select";

const fold = ref(false);

const selected = ref();
</script>

<template>
  <button @click="fold = !fold">Action</button>
  <Transition
    enter-from-class="opacity-0"
    enter-active-class="transition-opacity duration-1000"
    enter-to-class="opacity-100"
    leave-from-class="opacity-100"
    leave-active-class="transition-opacity duration-1000"
    leave-to-class="opacity-0"
  >
    <div v-if="!fold">
      <Select v-model="selected">
        <SelectTrigger class="w-[180px]">
          <SelectValue placeholder="Select a fruit" />
        </SelectTrigger>
        <SelectContent>
          <SelectGroup>
            <SelectLabel>Fruits</SelectLabel>
            <SelectItem value="apple"> Apple </SelectItem>
            <SelectItem value="banana"> Banana </SelectItem>
            <SelectItem value="blueberry"> Blueberry </SelectItem>
            <SelectItem value="grapes"> Grapes </SelectItem>
            <SelectItem value="pineapple"> Pineapple </SelectItem>
          </SelectGroup>
        </SelectContent>
      </Select>
    </div>
  </Transition>
</template>

Describe the bug

Select is not compatible with opacity transition component. The text disappear (not transparent) on entering opacity transition.

动画

System Info

System:
    OS: Linux 5.15 Ubuntu 22.04.3 LTS 22.04.3 LTS (Jammy Jellyfish)
    CPU: (24) x64 AMD Ryzen 9 7900X 12-Core Processor
    Memory: 3.10 GB / 7.37 GB
    Container: Yes
    Shell: 5.1.16 - /bin/bash
  Binaries:
    Node: 20.11.1 - ~/.nvm/versions/node/v20.11.1/bin/node
    npm: 10.2.4 - ~/.nvm/versions/node/v20.11.1/bin/npm
    pnpm: 8.15.4 - ~/.nvm/versions/node/v20.11.1/bin/pnpm
  npmPackages:
    nuxt: ^3.11.1 => 3.11.1 
    radix-vue: ^1.5.3 => 1.5.3 
    shadcn-nuxt: ^0.10.2 => 0.10.2 
    vue: ^3.4.21 => 3.4.21 

Chrome: 123.0.6312.60
Firefox: 124.0.1

Contributes

stripedpurple commented 5 months ago

@iyume That happens because the of the v-if, which causes the component to be unmounted. if you use a v-show instead the text will fade as well . If you want the component to unmount after the transition you can do something like this.

<script setup lang="ts">
import {
  Select,
  SelectContent,
  SelectGroup,
  SelectItem,
  SelectLabel,
  SelectTrigger,
  SelectValue,
} from "@/shadcn/components/ui/select";

import { ref } from 'vue';

const isMounted = ref(false);
const fold = ref(false);

const selected = ref();
</script>

<template>
  <button @click="fold = !fold">Action</button>
  <Transition enter-from-class="opacity-0" enter-active-class="transition-opacity duration-1000"
    enter-to-class="opacity-100" leave-from-class="opacity-100" leave-active-class="transition-opacity duration-1000"
    leave-to-class="opacity-0" @enter="isMounted = true" @leave="isMounted = false">
    <div v-show="!fold" v-if='!isMounted'>
      <Select v-model="selected">
        <SelectTrigger class="w-[180px]">
          <SelectValue placeholder="Select a fruit" />
        </SelectTrigger>
        <SelectContent>
          <SelectGroup>
            <SelectLabel>Fruits</SelectLabel>
            <SelectItem value="apple"> Apple </SelectItem>
            <SelectItem value="banana"> Banana </SelectItem>
            <SelectItem value="blueberry"> Blueberry </SelectItem>
            <SelectItem value="grapes"> Grapes </SelectItem>
            <SelectItem value="pineapple"> Pineapple </SelectItem>
          </SelectGroup>
        </SelectContent>
      </Select>
    </div>
  </Transition>
</template>
iyume commented 5 months ago

@stripedpurple I tried your code snippet, but it looks strange.

动画

Promise.then(异步)
queueFlush @ chunk-SV7XBIS2.js?v=84e1f9c6:1775
queueJob @ chunk-SV7XBIS2.js?v=84e1f9c6:1769
(匿名) @ chunk-SV7XBIS2.js?v=84e1f9c6:7580
resetScheduling @ chunk-SV7XBIS2.js?v=84e1f9c6:513
triggerEffects @ chunk-SV7XBIS2.js?v=84e1f9c6:557
triggerRefValue @ chunk-SV7XBIS2.js?v=84e1f9c6:1317
set value @ chunk-SV7XBIS2.js?v=84e1f9c6:1362
set @ chunk-SV7XBIS2.js?v=84e1f9c6:1380
_createElementVNode.onClick._cache.<computed>._cache.<computed> @ app.vue:21
callWithErrorHandling @ chunk-SV7XBIS2.js?v=84e1f9c6:1660
callWithAsyncErrorHandling @ chunk-SV7XBIS2.js?v=84e1f9c6:1667
invoker @ chunk-SV7XBIS2.js?v=84e1f9c6:10287

Uncaught (in promise) Maximum recursive updates exceeded in component <BaseTransition>. This means you have a reactive effect that is mutating its own dependencies and thus recursively triggering itself. Possible sources include component template, render function, updated hook or watcher source function.
lilijh commented 5 months ago

@iyume ,perhaps this issus is caused by radix-vue,I tried to combine your code with the code for the Select component in the radix-vue official doc,but I made a lot of simplifications,then encountered the same problem as you. here is my code:

<script setup lang="ts">
import { ref } from 'vue'
import {
  SelectContent,
  SelectItem,
  SelectItemText,
  SelectRoot,
  SelectTrigger,
  SelectValue,
} from 'radix-vue'
const selected = ref()
const fold = ref(false)
</script>

<template>
  <button @click="fold = !fold">Action</button>
  <Transition>
    <div v-if="!fold">
      <SelectRoot v-model="selected">
        <SelectTrigger>
          <SelectValue placeholder="Select a fruit..." />
        </SelectTrigger>
        <SelectContent>
          <SelectItem value="Apple">
            <SelectItemText> Apple </SelectItemText>
          </SelectItem>
        </SelectContent>
      </SelectRoot>
    </div>
  </Transition>
</template>

<style>
.v-enter-active,
.v-leave-active {
  transition: opacity 1s ease;
}
.v-enter-from,
.v-leave-to {
  opacity: 0;
}
</style>

https://github.com/radix-vue/shadcn-vue/assets/55320213/ee703a8a-fd85-4d30-b809-008cde71468b

by the way, it can work normally with only v-show

iyume commented 5 months ago

Thanks to your solutions! Yes, it can only work with v-show. I'm curious about why Transition cannot "snapshot" the Select's DOM. 🤔

lilijh commented 5 months ago

Thanks to your solutions! Yes, it can only work with v-show. I'm curious about why Transition cannot "snapshot" the Select's DOM. 🤔

“I’m not sure either, but I guess it might be due to the way the source code is written in radix-vue? This is part of the code for SelectItemText.vue:

<template>
  <Primitive :id="itemContext.textId" :ref="forwardRef" v-bind="{ ...props, ...$attrs }">
    <slot />
  </Primitive>

  <!-- Portal the select item text into the trigger value node -->
  <Teleport
    v-if="itemContext.isSelected.value && rootContext.valueElement.value && !rootContext.valueElementHasChildren.value" :to="rootContext.valueElement.value"
  >
    <slot />
  </Teleport>
</template>

and i've written a piece of code here to mimic it:

<script setup lang="ts">
import { ref } from 'vue'

const isDisplay = ref(true)
const root = ref(null)
</script>

<template>
  <div>
    <button @click="isDisplay = !isDisplay">Show</button>
    <Transition>
      <div ref="root" v-if="isDisplay">
        <p>hello</p>
      </div>
    </Transition>
    <Teleport v-if="root" :to="root"> if </Teleport>
  </div>
</template>

<style>
.v-enter-active,
.v-leave-active {
  transition: opacity 2s ease;
}

.v-enter-from,
.v-leave-to {
  opacity: 0;
}
</style>

here is the running effect:

https://github.com/radix-vue/shadcn-vue/assets/55320213/43a96006-7609-4328-bc5a-0292c0c51343

” you can also replace <div ref="root" v-if="isDisplay"> to <div ref="root" v-show="isDisplay"> and then get such an effect:

https://github.com/radix-vue/shadcn-vue/assets/55320213/fe8f6917-8454-45f4-a87a-12db5b25bcc3

here are some potentially relevant issues

iyume commented 5 months ago

Thanks to your work! I point out the related issues here: https://github.com/vuejs/core/issues/5836 https://github.com/vuejs/core/pull/6548