From cd1073d93876c6f15f71bcd8d5c4c4bc76492330 Mon Sep 17 00:00:00 2001 From: Benjamin Canac Date: Fri, 22 Mar 2024 15:57:12 +0100 Subject: [PATCH] feat(Switch): new component --- playground/app.vue | 1 + playground/pages/switch.vue | 30 ++++++ src/runtime/components/Switch.vue | 49 ++++++++++ src/theme/index.ts | 1 + src/theme/switch.ts | 62 +++++++++++++ test/components/Switch.spec.ts | 29 ++++++ .../__snapshots__/Switch.spec.ts.snap | 91 +++++++++++++++++++ 7 files changed, 263 insertions(+) create mode 100644 playground/pages/switch.vue create mode 100644 src/runtime/components/Switch.vue create mode 100644 src/theme/switch.ts create mode 100644 test/components/Switch.spec.ts create mode 100644 test/components/__snapshots__/Switch.spec.ts.snap diff --git a/playground/app.vue b/playground/app.vue index 7d8433ea..0542cd01 100644 --- a/playground/app.vue +++ b/playground/app.vue @@ -25,6 +25,7 @@ const components = [ 'popover', 'skeleton', 'slideover', + 'switch', 'tabs', 'tooltip' ] diff --git a/playground/pages/switch.vue b/playground/pages/switch.vue new file mode 100644 index 00000000..fe2ff039 --- /dev/null +++ b/playground/pages/switch.vue @@ -0,0 +1,30 @@ + + + diff --git a/src/runtime/components/Switch.vue b/src/runtime/components/Switch.vue new file mode 100644 index 00000000..b2064386 --- /dev/null +++ b/src/runtime/components/Switch.vue @@ -0,0 +1,49 @@ + + + + + diff --git a/src/theme/index.ts b/src/theme/index.ts index 4423ed29..54f796db 100644 --- a/src/theme/index.ts +++ b/src/theme/index.ts @@ -17,5 +17,6 @@ export { default as navigationMenu } from './navigationMenu' export { default as popover } from './popover' export { default as skeleton } from './skeleton' export { default as slideover } from './slideover' +export { default as switch } from './switch' export { default as tabs } from './tabs' export { default as tooltip } from './tooltip' diff --git a/src/theme/switch.ts b/src/theme/switch.ts new file mode 100644 index 00000000..072b496a --- /dev/null +++ b/src/theme/switch.ts @@ -0,0 +1,62 @@ +export default (config: { colors: string[] }) => ({ + slots: { + root: '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', + thumb: 'group pointer-events-none block 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', + 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' + }, + variants: { + color: Object.fromEntries(config.colors.map((color: string) => [color, { + root: `data-[state=checked]:bg-${color}-500 dark:data-[state=checked]:bg-${color}-400 focus-visible:ring-${color}-500 dark:focus-visible:ring-${color}-400`, + icon: `group-data-[state=checked]:text-${color}-500 dark:group-data-[state=checked]:text-${color}-400` + }])), + size: { + '2xs': { + root: 'h-3 w-5', + thumb: 'size-2 data-[state=checked]:translate-x-2', + icon: 'size-1.5' + }, + xs: { + root: 'h-3.5 w-6', + thumb: 'size-2.5 data-[state=checked]:translate-x-2.5', + icon: 'size-2' + }, + sm: { + root: 'h-4 w-7', + thumb: 'size-3 data-[state=checked]:translate-x-3', + icon: 'size-2.5' + }, + md: { + root: 'h-5 w-9', + thumb: 'size-4 data-[state=checked]:translate-x-4', + icon: 'size-3' + }, + lg: { + root: 'h-6 w-11', + thumb: 'size-5 data-[state=checked]:translate-x-5', + icon: 'size-4' + }, + xl: { + root: 'h-7 w-[3.25rem]', + thumb: 'size-6 data-[state=checked]:translate-x-6', + icon: 'size-5' + }, + '2xl': { + root: 'h-8 w-[3.75rem]', + thumb: 'size-7 data-[state=checked]:translate-x-7', + icon: 'size-6' + } + }, + checked: { + true: { + icon: 'group-data-[state=checked]:opacity-100' + }, + false: { + icon: 'group-data-[state=unchecked]:opacity-100' + } + } + }, + defaultVariants: { + color: 'primary', + size: 'md' + } +}) diff --git a/test/components/Switch.spec.ts b/test/components/Switch.spec.ts new file mode 100644 index 00000000..7f069b5a --- /dev/null +++ b/test/components/Switch.spec.ts @@ -0,0 +1,29 @@ +import { describe, it, expect } from 'vitest' +import Switch, { type SwitchProps } from '../../src/runtime/components/Switch.vue' +import ComponentRender from '../component-render' + +describe('Switch', () => { + it.each([ + ['basic case', {}], + ['with class', { props: { class: '' } }], + ['with ui', { props: { ui: {} } }], + ['with as', { props: { as: 'section' } }], + ['with checked', { props: { checked: true } }], + ['with defaultChecked', { props: { defaultChecked: true } }], + ['with disabled', { props: { disabled: true } }], + ['with id', { props: { id: 'test' } }], + ['with name', { props: { name: 'test' } }], + ['with required', { props: { required: true } }], + ['with value', { props: { value: 'switch' } }], + ['with size 2xs', { props: { size: '2xs' as const } }], + ['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 } }] + ])('renders %s correctly', async (nameOrHtml: string, options: { props?: SwitchProps, slots?: any }) => { + const html = await ComponentRender(nameOrHtml, options, Switch) + expect(html).toMatchSnapshot() + }) +}) diff --git a/test/components/__snapshots__/Switch.spec.ts.snap b/test/components/__snapshots__/Switch.spec.ts.snap new file mode 100644 index 00000000..3ca2ad70 --- /dev/null +++ b/test/components/__snapshots__/Switch.spec.ts.snap @@ -0,0 +1,91 @@ +// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html + +exports[`Switch > renders basic case correctly 1`] = ` +" +" +`; + +exports[`Switch > renders with as correctly 1`] = ` +"
+" +`; + +exports[`Switch > renders with checked correctly 1`] = ` +" +" +`; + +exports[`Switch > renders with class correctly 1`] = ` +" +" +`; + +exports[`Switch > renders with defaultChecked correctly 1`] = ` +" +" +`; + +exports[`Switch > renders with disabled correctly 1`] = ` +" +" +`; + +exports[`Switch > renders with id correctly 1`] = ` +" +" +`; + +exports[`Switch > renders with name correctly 1`] = ` +" +" +`; + +exports[`Switch > renders with required correctly 1`] = ` +" +" +`; + +exports[`Switch > renders with size 2xl correctly 1`] = ` +" +" +`; + +exports[`Switch > renders with size 2xs correctly 1`] = ` +" +" +`; + +exports[`Switch > renders with size lg correctly 1`] = ` +" +" +`; + +exports[`Switch > renders with size md correctly 1`] = ` +" +" +`; + +exports[`Switch > renders with size sm correctly 1`] = ` +" +" +`; + +exports[`Switch > renders with size xl correctly 1`] = ` +" +" +`; + +exports[`Switch > renders with size xs correctly 1`] = ` +" +" +`; + +exports[`Switch > renders with ui correctly 1`] = ` +" +" +`; + +exports[`Switch > renders with value correctly 1`] = ` +" +" +`;