feat(Switch): handle loading and loadingIcon

Resolves #65
This commit is contained in:
Benjamin Canac
2024-03-26 15:48:16 +01:00
parent c9f09992b7
commit 8fd369372b
5 changed files with 58 additions and 26 deletions

View File

@@ -26,5 +26,16 @@ const checked = ref(false)
checked-icon="i-heroicons-check-20-solid"
/>
</div>
<div class="flex items-center gap-2 ml-[-36px]">
<USwitch
v-for="size in sizes"
:key="size"
v-model:checked="checked"
:size="(size as any)"
unchecked-icon="i-heroicons-x-mark-20-solid"
checked-icon="i-heroicons-check-20-solid"
loading
/>
</div>
</div>
</template>

View File

@@ -14,6 +14,8 @@ type SwitchVariants = VariantProps<typeof switchTv>
export interface SwitchProps extends Omit<SwitchRootProps, 'asChild'> {
color?: SwitchVariants['color']
size?: SwitchVariants['size']
loading?: boolean
loadingIcon?: string
checkedIcon?: string
uncheckedIcon?: string
class?: any
@@ -27,23 +29,29 @@ export interface SwitchEmits extends SwitchRootEmits {}
import { computed } from 'vue'
import { SwitchRoot, SwitchThumb, useForwardPropsEmits } from 'radix-vue'
import { reactivePick } from '@vueuse/core'
import { useAppConfig } from '#app'
const props = defineProps<SwitchProps>()
const emits = defineEmits<SwitchEmits>()
const rootProps = useForwardPropsEmits(reactivePick(props, 'as', 'defaultChecked', 'checked', 'disabled', 'required', 'name', 'id', 'value'), emits)
const appConfig = useAppConfig()
const rootProps = useForwardPropsEmits(reactivePick(props, 'as', 'defaultChecked', 'checked', 'required', 'name', 'id', 'value'), emits)
const ui = computed(() => tv({ extend: switchTv, slots: props.ui })({
color: props.color,
size: props.size
size: props.size,
loading: props.loading
}))
</script>
<template>
<SwitchRoot v-bind="rootProps" :class="ui.root({ class: props.class })">
<SwitchRoot :disabled="disabled || loading" v-bind="rootProps" :class="ui.root({ class: props.class })">
<SwitchThumb :class="ui.thumb()">
<UIcon v-if="checkedIcon" :name="checkedIcon" :class="ui.icon({ checked: true })" />
<UIcon v-if="uncheckedIcon" :name="uncheckedIcon" :class="ui.icon({ checked: false })" />
<UIcon v-if="loading" :name="loadingIcon || appConfig.ui.icons.loading" :class="ui.icon({ checked: true, unchecked: true })" />
<template v-else>
<UIcon v-if="checkedIcon" :name="checkedIcon" :class="ui.icon({ checked: true })" />
<UIcon v-if="uncheckedIcon" :name="uncheckedIcon" :class="ui.icon({ unchecked: true })" />
</template>
</SwitchThumb>
</SwitchRoot>
</template>

View File

@@ -34,10 +34,17 @@ export default (config: { colors: string[] }) => ({
checked: {
true: {
icon: 'group-data-[state=checked]:opacity-100'
},
false: {
}
},
unchecked: {
true: {
icon: 'group-data-[state=unchecked]:opacity-100'
}
},
loading: {
true: {
icon: 'animate-spin'
}
}
},
defaultVariants: {

View File

@@ -15,13 +15,14 @@ describe('Switch', () => {
['with name', { props: { name: 'test' } }],
['with required', { props: { required: true } }],
['with value', { props: { value: 'switch' } }],
['with size 2xs', { props: { size: '2xs' as const } }],
['with checkedIcon', { props: { checkedIcon: 'i-heroicons-check-20-solid' } }],
['with uncheckedIcon', { props: { checkedIcon: 'i-heroicons-x-mark-20-solid' } }],
['with loading', { props: { loading: true } }],
['with loadingIcon', { props: { loading: true, loadingIcon: 'i-heroicons-sparkles' } }],
['with size xs', { props: { size: 'xs' as const } }],
['with size sm', { props: { size: 'sm' as const } }],
['with size md', { props: { size: 'md' as const } }],
['with size lg', { props: { size: 'lg' as const } }],
['with size xl', { props: { size: 'xl' as const } }],
['with size 2xl', { props: { size: '2xl' as const } }]
['with size lg', { props: { size: 'lg' as const } }]
])('renders %s correctly', async (nameOrHtml: string, options: { props?: SwitchProps, slots?: any }) => {
const html = await ComponentRender(nameOrHtml, options, Switch)
expect(html).toMatchSnapshot()

View File

@@ -15,6 +15,11 @@ exports[`Switch > renders with checked correctly 1`] = `
<!---->"
`;
exports[`Switch > renders with checkedIcon correctly 1`] = `
"<button class="peer inline-flex shrink-0 items-center rounded-full border-2 border-transparent transition-colors duration-200 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-offset-2 focus-visible:ring-offset-white dark:focus-visible:ring-offset-gray-900 disabled:cursor-not-allowed disabled:opacity-75 data-[state=unchecked]:bg-gray-200 dark:data-[state=unchecked]:bg-gray-700 data-[state=checked]:bg-primary-500 dark:data-[state=checked]:bg-primary-400 focus-visible:ring-primary-500 dark:focus-visible:ring-primary-400 h-5 w-9" role="switch" type="button" aria-checked="false" aria-required="false" data-state="unchecked" value="on"><span data-state="unchecked" class="group pointer-events-none rounded-full bg-white dark:bg-gray-900 shadow-lg ring-0 transition-transform duration-200 data-[state=unchecked]:translate-x-0 flex items-center justify-center size-4 data-[state=checked]:translate-x-4"><svg data-v-afd689a2="" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" aria-hidden="true" role="img" class="icon absolute shrink-0 group-data-[state=unchecked]:text-gray-400 dark:group-data-[state=unchecked]:text-gray-500 transition-[color,opacity] duration-200 opacity-0 group-data-[state=checked]:text-primary-500 dark:group-data-[state=checked]:text-primary-400 size-3 group-data-[state=checked]:opacity-100" width="1em" height="1em" viewBox="0 0 20 20"><path fill="currentColor" fill-rule="evenodd" d="M16.705 4.153a.75.75 0 0 1 .142 1.052l-8 10.5a.75.75 0 0 1-1.127.075l-4.5-4.5a.75.75 0 0 1 1.06-1.06l3.894 3.893l7.48-9.817a.75.75 0 0 1 1.05-.143" clip-rule="evenodd"></path></svg><!--v-if--></span></button>
<!---->"
`;
exports[`Switch > renders with class correctly 1`] = `
"<button class="peer inline-flex shrink-0 items-center rounded-full border-2 border-transparent transition-colors duration-200 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-offset-2 focus-visible:ring-offset-white dark:focus-visible:ring-offset-gray-900 disabled:cursor-not-allowed disabled:opacity-75 data-[state=unchecked]:bg-gray-200 dark:data-[state=unchecked]:bg-gray-700 data-[state=checked]:bg-primary-500 dark:data-[state=checked]:bg-primary-400 focus-visible:ring-primary-500 dark:focus-visible:ring-primary-400 h-5 w-9" role="switch" type="button" aria-checked="false" aria-required="false" data-state="unchecked" value="on"><span data-state="unchecked" class="group pointer-events-none rounded-full bg-white dark:bg-gray-900 shadow-lg ring-0 transition-transform duration-200 data-[state=unchecked]:translate-x-0 flex items-center justify-center size-4 data-[state=checked]:translate-x-4"></span></button>
<!---->"
@@ -35,6 +40,16 @@ exports[`Switch > renders with id correctly 1`] = `
<!---->"
`;
exports[`Switch > renders with loading correctly 1`] = `
"<button class="peer inline-flex shrink-0 items-center rounded-full border-2 border-transparent transition-colors duration-200 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-offset-2 focus-visible:ring-offset-white dark:focus-visible:ring-offset-gray-900 disabled:cursor-not-allowed disabled:opacity-75 data-[state=unchecked]:bg-gray-200 dark:data-[state=unchecked]:bg-gray-700 data-[state=checked]:bg-primary-500 dark:data-[state=checked]:bg-primary-400 focus-visible:ring-primary-500 dark:focus-visible:ring-primary-400 h-5 w-9" role="switch" type="button" aria-checked="false" aria-required="false" data-state="unchecked" data-disabled="" disabled="" value="on"><span data-state="unchecked" data-disabled="" class="group pointer-events-none rounded-full bg-white dark:bg-gray-900 shadow-lg ring-0 transition-transform duration-200 data-[state=unchecked]:translate-x-0 flex items-center justify-center size-4 data-[state=checked]:translate-x-4"><svg data-v-afd689a2="" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" aria-hidden="true" role="img" class="icon absolute shrink-0 group-data-[state=unchecked]:text-gray-400 dark:group-data-[state=unchecked]:text-gray-500 transition-[color,opacity] duration-200 opacity-0 group-data-[state=checked]:text-primary-500 dark:group-data-[state=checked]:text-primary-400 size-3 group-data-[state=checked]:opacity-100 group-data-[state=unchecked]:opacity-100 animate-spin" width="1em" height="1em" viewBox="0 0 20 20"><path fill="currentColor" fill-rule="evenodd" d="M15.312 11.424a5.5 5.5 0 0 1-9.201 2.466l-.312-.311h2.433a.75.75 0 0 0 0-1.5H3.989a.75.75 0 0 0-.75.75v4.242a.75.75 0 0 0 1.5 0v-2.43l.31.31a7 7 0 0 0 11.712-3.138a.75.75 0 0 0-1.449-.39m1.23-3.723a.75.75 0 0 0 .219-.53V2.929a.75.75 0 0 0-1.5 0V5.36l-.31-.31A7 7 0 0 0 3.239 8.188a.75.75 0 1 0 1.448.389A5.5 5.5 0 0 1 13.89 6.11l.311.31h-2.432a.75.75 0 0 0 0 1.5h4.243a.75.75 0 0 0 .53-.219" clip-rule="evenodd"></path></svg></span></button>
<!---->"
`;
exports[`Switch > renders with loadingIcon correctly 1`] = `
"<button class="peer inline-flex shrink-0 items-center rounded-full border-2 border-transparent transition-colors duration-200 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-offset-2 focus-visible:ring-offset-white dark:focus-visible:ring-offset-gray-900 disabled:cursor-not-allowed disabled:opacity-75 data-[state=unchecked]:bg-gray-200 dark:data-[state=unchecked]:bg-gray-700 data-[state=checked]:bg-primary-500 dark:data-[state=checked]:bg-primary-400 focus-visible:ring-primary-500 dark:focus-visible:ring-primary-400 h-5 w-9" role="switch" type="button" aria-checked="false" aria-required="false" data-state="unchecked" data-disabled="" disabled="" value="on"><span data-state="unchecked" data-disabled="" class="group pointer-events-none rounded-full bg-white dark:bg-gray-900 shadow-lg ring-0 transition-transform duration-200 data-[state=unchecked]:translate-x-0 flex items-center justify-center size-4 data-[state=checked]:translate-x-4"><svg data-v-afd689a2="" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" aria-hidden="true" role="img" class="icon absolute shrink-0 group-data-[state=unchecked]:text-gray-400 dark:group-data-[state=unchecked]:text-gray-500 transition-[color,opacity] duration-200 opacity-0 group-data-[state=checked]:text-primary-500 dark:group-data-[state=checked]:text-primary-400 size-3 group-data-[state=checked]:opacity-100 group-data-[state=unchecked]:opacity-100 animate-spin" width="1em" height="1em" viewBox="0 0 24 24"><path fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="1.5" d="M9.813 15.904L9 18.75l-.813-2.846a4.5 4.5 0 0 0-3.09-3.09L2.25 12l2.846-.813a4.5 4.5 0 0 0 3.09-3.09L9 5.25l.813 2.846a4.5 4.5 0 0 0 3.09 3.09L15.75 12l-2.846.813a4.5 4.5 0 0 0-3.09 3.09m8.445-7.188L18 9.75l-.259-1.035a3.375 3.375 0 0 0-2.455-2.456L14.25 6l1.036-.259a3.375 3.375 0 0 0 2.455-2.456L18 2.25l.259 1.035a3.375 3.375 0 0 0 2.456 2.456L21.75 6l-1.035.259a3.375 3.375 0 0 0-2.456 2.456m-1.365 11.852L16.5 21.75l-.394-1.183a2.25 2.25 0 0 0-1.423-1.423L13.5 18.75l1.183-.394a2.25 2.25 0 0 0 1.423-1.423l.394-1.183l.394 1.183a2.25 2.25 0 0 0 1.423 1.423l1.183.394l-1.183.394a2.25 2.25 0 0 0-1.423 1.423"></path></svg></span></button>
<!---->"
`;
exports[`Switch > renders with name correctly 1`] = `
"<button class="peer inline-flex shrink-0 items-center rounded-full border-2 border-transparent transition-colors duration-200 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-offset-2 focus-visible:ring-offset-white dark:focus-visible:ring-offset-gray-900 disabled:cursor-not-allowed disabled:opacity-75 data-[state=unchecked]:bg-gray-200 dark:data-[state=unchecked]:bg-gray-700 data-[state=checked]:bg-primary-500 dark:data-[state=checked]:bg-primary-400 focus-visible:ring-primary-500 dark:focus-visible:ring-primary-400 h-5 w-9" role="switch" type="button" aria-checked="false" aria-required="false" data-state="unchecked" value="on"><span data-state="unchecked" class="group pointer-events-none rounded-full bg-white dark:bg-gray-900 shadow-lg ring-0 transition-transform duration-200 data-[state=unchecked]:translate-x-0 flex items-center justify-center size-4 data-[state=checked]:translate-x-4"></span></button>
<!---->"
@@ -45,16 +60,6 @@ exports[`Switch > renders with required correctly 1`] = `
<!---->"
`;
exports[`Switch > renders with size 2xl correctly 1`] = `
"<button class="peer inline-flex shrink-0 items-center rounded-full border-2 border-transparent transition-colors duration-200 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-offset-2 focus-visible:ring-offset-white dark:focus-visible:ring-offset-gray-900 disabled:cursor-not-allowed disabled:opacity-75 data-[state=unchecked]:bg-gray-200 dark:data-[state=unchecked]:bg-gray-700 data-[state=checked]:bg-primary-500 dark:data-[state=checked]:bg-primary-400 focus-visible:ring-primary-500 dark:focus-visible:ring-primary-400" role="switch" type="button" aria-checked="false" aria-required="false" data-state="unchecked" value="on"><span data-state="unchecked" class="group pointer-events-none rounded-full bg-white dark:bg-gray-900 shadow-lg ring-0 transition-transform duration-200 data-[state=unchecked]:translate-x-0 flex items-center justify-center"></span></button>
<!---->"
`;
exports[`Switch > renders with size 2xs correctly 1`] = `
"<button class="peer inline-flex shrink-0 items-center rounded-full border-2 border-transparent transition-colors duration-200 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-offset-2 focus-visible:ring-offset-white dark:focus-visible:ring-offset-gray-900 disabled:cursor-not-allowed disabled:opacity-75 data-[state=unchecked]:bg-gray-200 dark:data-[state=unchecked]:bg-gray-700 data-[state=checked]:bg-primary-500 dark:data-[state=checked]:bg-primary-400 focus-visible:ring-primary-500 dark:focus-visible:ring-primary-400" role="switch" type="button" aria-checked="false" aria-required="false" data-state="unchecked" value="on"><span data-state="unchecked" class="group pointer-events-none rounded-full bg-white dark:bg-gray-900 shadow-lg ring-0 transition-transform duration-200 data-[state=unchecked]:translate-x-0 flex items-center justify-center"></span></button>
<!---->"
`;
exports[`Switch > renders with size lg correctly 1`] = `
"<button class="peer inline-flex shrink-0 items-center rounded-full border-2 border-transparent transition-colors duration-200 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-offset-2 focus-visible:ring-offset-white dark:focus-visible:ring-offset-gray-900 disabled:cursor-not-allowed disabled:opacity-75 data-[state=unchecked]:bg-gray-200 dark:data-[state=unchecked]:bg-gray-700 data-[state=checked]:bg-primary-500 dark:data-[state=checked]:bg-primary-400 focus-visible:ring-primary-500 dark:focus-visible:ring-primary-400 h-7 w-[3.25rem]" role="switch" type="button" aria-checked="false" aria-required="false" data-state="unchecked" value="on"><span data-state="unchecked" class="group pointer-events-none rounded-full bg-white dark:bg-gray-900 shadow-lg ring-0 transition-transform duration-200 data-[state=unchecked]:translate-x-0 flex items-center justify-center size-6 data-[state=checked]:translate-x-6"></span></button>
<!---->"
@@ -70,11 +75,6 @@ exports[`Switch > renders with size sm correctly 1`] = `
<!---->"
`;
exports[`Switch > renders with size xl correctly 1`] = `
"<button class="peer inline-flex shrink-0 items-center rounded-full border-2 border-transparent transition-colors duration-200 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-offset-2 focus-visible:ring-offset-white dark:focus-visible:ring-offset-gray-900 disabled:cursor-not-allowed disabled:opacity-75 data-[state=unchecked]:bg-gray-200 dark:data-[state=unchecked]:bg-gray-700 data-[state=checked]:bg-primary-500 dark:data-[state=checked]:bg-primary-400 focus-visible:ring-primary-500 dark:focus-visible:ring-primary-400" role="switch" type="button" aria-checked="false" aria-required="false" data-state="unchecked" value="on"><span data-state="unchecked" class="group pointer-events-none rounded-full bg-white dark:bg-gray-900 shadow-lg ring-0 transition-transform duration-200 data-[state=unchecked]:translate-x-0 flex items-center justify-center"></span></button>
<!---->"
`;
exports[`Switch > renders with size xs correctly 1`] = `
"<button class="peer inline-flex shrink-0 items-center rounded-full border-2 border-transparent transition-colors duration-200 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-offset-2 focus-visible:ring-offset-white dark:focus-visible:ring-offset-gray-900 disabled:cursor-not-allowed disabled:opacity-75 data-[state=unchecked]:bg-gray-200 dark:data-[state=unchecked]:bg-gray-700 data-[state=checked]:bg-primary-500 dark:data-[state=checked]:bg-primary-400 focus-visible:ring-primary-500 dark:focus-visible:ring-primary-400 h-4 w-7" role="switch" type="button" aria-checked="false" aria-required="false" data-state="unchecked" value="on"><span data-state="unchecked" class="group pointer-events-none rounded-full bg-white dark:bg-gray-900 shadow-lg ring-0 transition-transform duration-200 data-[state=unchecked]:translate-x-0 flex items-center justify-center size-3 data-[state=checked]:translate-x-3"></span></button>
<!---->"
@@ -85,6 +85,11 @@ exports[`Switch > renders with ui correctly 1`] = `
<!---->"
`;
exports[`Switch > renders with uncheckedIcon correctly 1`] = `
"<button class="peer inline-flex shrink-0 items-center rounded-full border-2 border-transparent transition-colors duration-200 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-offset-2 focus-visible:ring-offset-white dark:focus-visible:ring-offset-gray-900 disabled:cursor-not-allowed disabled:opacity-75 data-[state=unchecked]:bg-gray-200 dark:data-[state=unchecked]:bg-gray-700 data-[state=checked]:bg-primary-500 dark:data-[state=checked]:bg-primary-400 focus-visible:ring-primary-500 dark:focus-visible:ring-primary-400 h-5 w-9" role="switch" type="button" aria-checked="false" aria-required="false" data-state="unchecked" value="on"><span data-state="unchecked" class="group pointer-events-none rounded-full bg-white dark:bg-gray-900 shadow-lg ring-0 transition-transform duration-200 data-[state=unchecked]:translate-x-0 flex items-center justify-center size-4 data-[state=checked]:translate-x-4"><svg data-v-afd689a2="" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" aria-hidden="true" role="img" class="icon absolute shrink-0 group-data-[state=unchecked]:text-gray-400 dark:group-data-[state=unchecked]:text-gray-500 transition-[color,opacity] duration-200 opacity-0 group-data-[state=checked]:text-primary-500 dark:group-data-[state=checked]:text-primary-400 size-3 group-data-[state=checked]:opacity-100" width="1em" height="1em" viewBox="0 0 20 20"><path fill="currentColor" d="M6.28 5.22a.75.75 0 0 0-1.06 1.06L8.94 10l-3.72 3.72a.75.75 0 1 0 1.06 1.06L10 11.06l3.72 3.72a.75.75 0 1 0 1.06-1.06L11.06 10l3.72-3.72a.75.75 0 0 0-1.06-1.06L10 8.94z"></path></svg><!--v-if--></span></button>
<!---->"
`;
exports[`Switch > renders with value correctly 1`] = `
"<button class="peer inline-flex shrink-0 items-center rounded-full border-2 border-transparent transition-colors duration-200 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-offset-2 focus-visible:ring-offset-white dark:focus-visible:ring-offset-gray-900 disabled:cursor-not-allowed disabled:opacity-75 data-[state=unchecked]:bg-gray-200 dark:data-[state=unchecked]:bg-gray-700 data-[state=checked]:bg-primary-500 dark:data-[state=checked]:bg-primary-400 focus-visible:ring-primary-500 dark:focus-visible:ring-primary-400 h-5 w-9" role="switch" type="button" aria-checked="false" aria-required="false" data-state="unchecked" value="switch"><span data-state="unchecked" class="group pointer-events-none rounded-full bg-white dark:bg-gray-900 shadow-lg ring-0 transition-transform duration-200 data-[state=unchecked]:translate-x-0 flex items-center justify-center size-4 data-[state=checked]:translate-x-4"></span></button>
<!---->"