feat: smoother hover/select feedback on Checkbox, RadioGroup, Select
- RadioGroupItem and SelectItem had no transition class at all, so their checked/highlighted background changes snapped instantly. Added transition-colors to both. - Added hover:border-ring to Checkbox and RadioGroupItem so unchecked items get visible mouse-hover feedback, not just focus-visible. - Wrapped the Checkbox checkmark and RadioGroup dot in a small Transition (fade-in/zoom-in on check, fade-out/zoom-out on uncheck) instead of popping in/out instantly, using the animation-duration-* utility (not duration-*) to avoid the transition:all bug fixed earlier in this session. Verified via headless Chromium: no console errors, hover border colors resolve to the ring token, Select items show a real transition-colors (not "all") with the expected 150ms duration.
This commit is contained in:
parent
a49f216c43
commit
da4fd17934
3 changed files with 26 additions and 16 deletions
|
|
@ -34,17 +34,22 @@ function toggle() {
|
|||
:aria-required="required"
|
||||
:data-state="checked ? 'checked' : 'unchecked'"
|
||||
:disabled="disabled"
|
||||
:class="cn('border-input dark:bg-input/30 data-checked:bg-primary data-checked:text-primary-foreground dark:data-checked:bg-primary data-checked:border-primary aria-invalid:aria-checked:border-primary aria-invalid:border-destructive dark:aria-invalid:border-destructive/50 focus-visible:border-ring focus-visible:ring-ring/50 aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 flex size-4 items-center justify-center rounded-[4px] border transition-colors group-has-disabled/field:opacity-50 focus-visible:ring-3 aria-invalid:ring-3 peer relative shrink-0 outline-none after:absolute after:-inset-x-3 after:-inset-y-2 disabled:cursor-not-allowed disabled:opacity-50', props.class)"
|
||||
:class="cn('border-input dark:bg-input/30 data-checked:bg-primary data-checked:text-primary-foreground dark:data-checked:bg-primary data-checked:border-primary aria-invalid:aria-checked:border-primary aria-invalid:border-destructive dark:aria-invalid:border-destructive/50 focus-visible:border-ring focus-visible:ring-ring/50 aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 flex size-4 items-center justify-center rounded-[4px] border transition-colors group-has-disabled/field:opacity-50 focus-visible:ring-3 aria-invalid:ring-3 peer relative shrink-0 outline-none after:absolute after:-inset-x-3 after:-inset-y-2 disabled:cursor-not-allowed disabled:opacity-50 hover:border-ring', props.class)"
|
||||
@click="toggle"
|
||||
>
|
||||
<span
|
||||
v-if="checked"
|
||||
data-slot="checkbox-indicator"
|
||||
class="[&>svg]:size-3.5 grid place-content-center text-current transition-none"
|
||||
<Transition
|
||||
enter-active-class="animate-in fade-in-0 zoom-in-50 animation-duration-100"
|
||||
leave-active-class="animate-out fade-out-0 zoom-out-50 animation-duration-100"
|
||||
>
|
||||
<slot>
|
||||
<CheckIcon />
|
||||
</slot>
|
||||
</span>
|
||||
<span
|
||||
v-if="checked"
|
||||
data-slot="checkbox-indicator"
|
||||
class="[&>svg]:size-3.5 grid place-content-center text-current"
|
||||
>
|
||||
<slot>
|
||||
<CheckIcon />
|
||||
</slot>
|
||||
</span>
|
||||
</Transition>
|
||||
</button>
|
||||
</template>
|
||||
|
|
|
|||
|
|
@ -46,16 +46,21 @@ onBeforeUnmount(() => {
|
|||
:disabled="disabled || context.disabled.value"
|
||||
:class="
|
||||
cn(
|
||||
'border-input dark:bg-input/30 data-checked:bg-primary data-checked:text-primary-foreground dark:data-checked:bg-primary data-checked:border-primary aria-invalid:aria-checked:border-primary aria-invalid:border-destructive focus-visible:border-ring focus-visible:ring-ring/50 aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 dark:aria-invalid:border-destructive/50 flex size-4 rounded-full focus-visible:ring-3 aria-invalid:ring-3 group/radio-group-item peer relative aspect-square shrink-0 border outline-none after:absolute after:-inset-x-3 after:-inset-y-2 disabled:cursor-not-allowed disabled:opacity-50',
|
||||
'border-input dark:bg-input/30 data-checked:bg-primary data-checked:text-primary-foreground dark:data-checked:bg-primary data-checked:border-primary aria-invalid:aria-checked:border-primary aria-invalid:border-destructive focus-visible:border-ring focus-visible:ring-ring/50 aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 dark:aria-invalid:border-destructive/50 flex size-4 rounded-full focus-visible:ring-3 aria-invalid:ring-3 group/radio-group-item peer relative aspect-square shrink-0 border outline-none after:absolute after:-inset-x-3 after:-inset-y-2 disabled:cursor-not-allowed disabled:opacity-50 transition-colors hover:border-ring',
|
||||
props.class,
|
||||
)
|
||||
"
|
||||
@click="onClick"
|
||||
>
|
||||
<span v-if="isChecked" data-slot="radio-group-indicator" class="flex size-4 items-center justify-center">
|
||||
<slot>
|
||||
<CircleIcon class="fill-primary-foreground text-primary-foreground absolute top-1/2 left-1/2 size-1.5 -translate-x-1/2 -translate-y-1/2" />
|
||||
</slot>
|
||||
</span>
|
||||
<Transition
|
||||
enter-active-class="animate-in fade-in-0 zoom-in-50 animation-duration-100"
|
||||
leave-active-class="animate-out fade-out-0 zoom-out-50 animation-duration-100"
|
||||
>
|
||||
<span v-if="isChecked" data-slot="radio-group-indicator" class="flex size-4 items-center justify-center">
|
||||
<slot>
|
||||
<CircleIcon class="fill-primary-foreground text-primary-foreground absolute top-1/2 left-1/2 size-1.5 -translate-x-1/2 -translate-y-1/2" />
|
||||
</slot>
|
||||
</span>
|
||||
</Transition>
|
||||
</button>
|
||||
</template>
|
||||
|
|
|
|||
|
|
@ -44,7 +44,7 @@ onMounted(() => {
|
|||
tabindex="-1"
|
||||
:class="
|
||||
cn(
|
||||
'focus:bg-accent focus:text-accent-foreground not-data-[variant=destructive]:focus:**:text-accent-foreground gap-1.5 rounded-md py-1 pr-8 pl-1.5 text-sm [&_svg:not([class*=size-])]:size-4 *:[span]:last:flex *:[span]:last:items-center *:[span]:last:gap-2 relative flex w-full cursor-default items-center outline-hidden select-none data-[disabled]:pointer-events-none data-[disabled]:opacity-50 [&_svg]:pointer-events-none [&_svg]:shrink-0',
|
||||
'focus:bg-accent focus:text-accent-foreground not-data-[variant=destructive]:focus:**:text-accent-foreground gap-1.5 rounded-md py-1 pr-8 pl-1.5 text-sm [&_svg:not([class*=size-])]:size-4 *:[span]:last:flex *:[span]:last:items-center *:[span]:last:gap-2 relative flex w-full cursor-default items-center outline-hidden select-none data-[disabled]:pointer-events-none data-[disabled]:opacity-50 [&_svg]:pointer-events-none [&_svg]:shrink-0 transition-colors',
|
||||
props.class,
|
||||
)
|
||||
"
|
||||
|
|
|
|||
Loading…
Reference in a new issue