From 5e6275fcff151f3607939b1503ff60f970375564 Mon Sep 17 00:00:00 2001 From: Benjamin Canac Date: Thu, 11 Apr 2024 14:50:38 +0200 Subject: [PATCH] feat(Drawer): implement with `vaul-vue` Resolves #53 --- package.json | 3 +- playground/app.vue | 3 +- playground/pages/drawer.vue | 28 ++ playground/pages/slideover.vue | 2 +- pnpm-lock.yaml | 14 + src/runtime/components/Drawer.vue | 100 +++++++ src/runtime/types/index.d.ts | 1 + src/theme/drawer.ts | 13 + src/theme/index.ts | 1 + test/components/Drawer.spec.ts | 24 ++ .../__snapshots__/Drawer.spec.ts.snap | 258 ++++++++++++++++++ 11 files changed, 444 insertions(+), 3 deletions(-) create mode 100644 playground/pages/drawer.vue create mode 100644 src/runtime/components/Drawer.vue create mode 100644 src/theme/drawer.ts create mode 100644 test/components/Drawer.spec.ts create mode 100644 test/components/__snapshots__/Drawer.spec.ts.snap diff --git a/package.json b/package.json index d29c3f85..2cca61a8 100644 --- a/package.json +++ b/package.json @@ -51,7 +51,8 @@ "radix-vue": "^1.6.2", "scule": "^1.3.0", "tailwind-variants": "^0.2.1", - "tailwindcss": "4.0.0-alpha.14" + "tailwindcss": "4.0.0-alpha.14", + "vaul-vue": "^0.1.0" }, "devDependencies": { "@nuxt/eslint-config": "^0.3.5", diff --git a/playground/app.vue b/playground/app.vue index ee281ff7..700e3bed 100644 --- a/playground/app.vue +++ b/playground/app.vue @@ -18,6 +18,7 @@ const components = [ 'checkbox', 'chip', 'collapsible', + 'drawer', 'dropdown-menu', 'form', 'form-field', @@ -45,7 +46,7 @@ function upperName (name: string) { - + diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index e52e0774..36e1ef96 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -51,6 +51,9 @@ importers: tailwindcss: specifier: 4.0.0-alpha.14 version: 4.0.0-alpha.14 + vaul-vue: + specifier: ^0.1.0 + version: 0.1.0(typescript@5.4.4) devDependencies: '@nuxt/eslint-config': specifier: ^0.3.5 @@ -12857,6 +12860,17 @@ packages: dependencies: builtins: 5.0.1 + /vaul-vue@0.1.0(typescript@5.4.4): + resolution: {integrity: sha512-3PYWMbN3cSdsciv3fzewskxZFnX61PYq1uNsbvizXDo/8sN4SMrWkYDqWaPdTD3GTEm6wpx7j5flRLg7A5ZXbQ==} + dependencies: + '@vueuse/core': 10.9.0(vue@3.4.21) + radix-vue: 1.6.2(vue@3.4.21) + vue: 3.4.21(typescript@5.4.4) + transitivePeerDependencies: + - '@vue/composition-api' + - typescript + dev: false + /vfile-location@5.0.2: resolution: {integrity: sha512-NXPYyxyBSH7zB5U6+3uDdd6Nybz6o6/od9rk8bp9H8GR3L+cm/fC0uUTbqBmUTnMCUDslAGBOIKNfvvb+gGlDg==} dependencies: diff --git a/src/runtime/components/Drawer.vue b/src/runtime/components/Drawer.vue new file mode 100644 index 00000000..a8aa650c --- /dev/null +++ b/src/runtime/components/Drawer.vue @@ -0,0 +1,100 @@ + + + + + diff --git a/src/runtime/types/index.d.ts b/src/runtime/types/index.d.ts index 48e93655..d1aad59e 100644 --- a/src/runtime/types/index.d.ts +++ b/src/runtime/types/index.d.ts @@ -8,6 +8,7 @@ export * from '../components/Checkbox.vue' export * from '../components/Chip.vue' export * from '../components/Collapsible.vue' export * from '../components/Container.vue' +export * from '../components/Drawer.vue' export * from '../components/DropdownMenu.vue' export * from '../components/Form.vue' export * from '../components/FormField.vue' diff --git a/src/theme/drawer.ts b/src/theme/drawer.ts new file mode 100644 index 00000000..45884eef --- /dev/null +++ b/src/theme/drawer.ts @@ -0,0 +1,13 @@ +export default { + slots: { + overlay: 'fixed inset-0 z-30 bg-gray-200/75 dark:bg-gray-800/75', + content: 'fixed inset-x-0 bottom-0 z-50 mt-24 bg-white dark:bg-gray-900 ring ring-gray-200 dark:ring-gray-800 rounded-t-lg h-auto max-h-[96%] flex flex-col focus:outline-none', + handle: 'mx-auto w-12 my-4 h-1.5 shrink-0 rounded-full bg-gray-200 dark:bg-gray-700', + container: 'mx-auto w-full max-w-md flex flex-col gap-4 px-4 pb-8 overflow-y-auto', + header: '', + title: 'text-gray-900 dark:text-white font-semibold', + description: 'mt-1 text-gray-500 dark:text-gray-400 text-sm', + body: 'flex-1', + footer: 'flex flex-col gap-1.5' + } +} diff --git a/src/theme/index.ts b/src/theme/index.ts index 3f9fba7a..918be8bf 100644 --- a/src/theme/index.ts +++ b/src/theme/index.ts @@ -7,6 +7,7 @@ export { default as checkbox } from './checkbox' export { default as chip } from './chip' export { default as collapsible } from './collapsible' export { default as container } from './container' +export { default as drawer } from './drawer' export { default as dropdownMenu } from './dropdown-menu' export { default as form } from './form' export { default as formField } from './form-field' diff --git a/test/components/Drawer.spec.ts b/test/components/Drawer.spec.ts new file mode 100644 index 00000000..f76fddc1 --- /dev/null +++ b/test/components/Drawer.spec.ts @@ -0,0 +1,24 @@ +import { describe, it, expect } from 'vitest' +import Drawer, { type DrawerProps } from '../../src/runtime/components/Drawer.vue' +import ComponentRender from '../component-render' + +describe('Drawer', () => { + it.each([ + ['basic case', { props: { open: true, portal: false } }], + ['with title', { props: { open: true, portal: false, title: 'Title' } }], + ['with description', { props: { open: true, portal: false, title: 'Title', description: 'Description' } }], + ['without overlay', { props: { open: true, portal: false, overlay: false, title: 'Title', description: 'Description' } }], + ['with class', { props: { open: true, portal: false, class: 'bg-gray-50 dark:bg-gray-800' } }], + ['with ui', { props: { open: true, portal: false, ui: { handle: 'w-20' } } }], + ['with default slot', { props: { open: true, portal: false }, slots: { default: () => 'Default slot' } }], + ['with content slot', { props: { open: true, portal: false }, slots: { content: () => 'Content slot' } }], + ['with header slot', { props: { open: true, portal: false }, slots: { header: () => 'Header slot' } }], + ['with title slot', { props: { open: true, portal: false }, slots: { title: () => 'Title slot' } }], + ['with description slot', { props: { open: true, portal: false }, slots: { description: () => 'Description slot' } }], + ['with body slot', { props: { open: true, portal: false }, slots: { body: () => 'Body slot' } }], + ['with footer slot', { props: { open: true, portal: false }, slots: { footer: () => 'Footer slot' } }] + ])('renders %s correctly', async (nameOrHtml: string, options: { props?: DrawerProps, slots?: any }) => { + const html = await ComponentRender(nameOrHtml, options, Drawer) + expect(html).toMatchSnapshot() + }) +}) diff --git a/test/components/__snapshots__/Drawer.spec.ts.snap b/test/components/__snapshots__/Drawer.spec.ts.snap new file mode 100644 index 00000000..fc43face --- /dev/null +++ b/test/components/__snapshots__/Drawer.spec.ts.snap @@ -0,0 +1,258 @@ +// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html + +exports[`Drawer > renders basic case correctly 1`] = ` +" + + + +
+ + + +" +`; + +exports[`Drawer > renders with body slot correctly 1`] = ` +" + + + +
+ + + +" +`; + +exports[`Drawer > renders with class correctly 1`] = ` +" + + + +
+ + + +" +`; + +exports[`Drawer > renders with content slot correctly 1`] = ` +" + + + +
+ + + +" +`; + +exports[`Drawer > renders with default slot correctly 1`] = ` +"Default slot + + + +
+ + + +" +`; + +exports[`Drawer > renders with description correctly 1`] = ` +" + + + +
+ + + +" +`; + +exports[`Drawer > renders with description slot correctly 1`] = ` +" + + + +
+ + + +" +`; + +exports[`Drawer > renders with footer slot correctly 1`] = ` +" + + + +
+ + + +" +`; + +exports[`Drawer > renders with header slot correctly 1`] = ` +" + + + +
+ + + +" +`; + +exports[`Drawer > renders with title correctly 1`] = ` +" + + + +
+ + + +" +`; + +exports[`Drawer > renders with title slot correctly 1`] = ` +" + + + +
+ + + +" +`; + +exports[`Drawer > renders with ui correctly 1`] = ` +" + + + +
+ + + +" +`; + +exports[`Drawer > renders without overlay correctly 1`] = ` +" + + + + + + + +" +`;