From 522cf7dfb9d2f8764b3fe250a6878b9e6138d4ea Mon Sep 17 00:00:00 2001 From: Dustella Date: Thu, 9 Jan 2025 01:53:07 +0800 Subject: [PATCH] feat: new ui --- components.d.ts | 43 +++++ package.json | 5 +- pnpm-lock.yaml | 46 +++++ src/App.vue | 4 +- src/components/Image.vue | 14 +- src/components/ImageContainer.vue | 20 ++ src/components/ParamsCard.vue | 43 +++++ src/components/ui/accordion/Accordion.vue | 19 ++ .../ui/accordion/AccordionContent.vue | 24 +++ src/components/ui/accordion/AccordionItem.vue | 24 +++ .../ui/accordion/AccordionTrigger.vue | 39 ++++ src/components/ui/accordion/index.ts | 4 + src/components/ui/auto-form/AutoForm.vue | 105 +++++++++++ src/components/ui/auto-form/AutoFormField.vue | 45 +++++ .../ui/auto-form/AutoFormFieldArray.vue | 110 +++++++++++ .../ui/auto-form/AutoFormFieldBoolean.vue | 41 +++++ .../ui/auto-form/AutoFormFieldDate.vue | 57 ++++++ .../ui/auto-form/AutoFormFieldEnum.vue | 49 +++++ .../ui/auto-form/AutoFormFieldFile.vue | 74 ++++++++ .../ui/auto-form/AutoFormFieldInput.vue | 36 ++++ .../ui/auto-form/AutoFormFieldNumber.vue | 32 ++++ .../ui/auto-form/AutoFormFieldObject.vue | 78 ++++++++ src/components/ui/auto-form/AutoFormLabel.vue | 14 ++ src/components/ui/auto-form/constant.ts | 40 ++++ src/components/ui/auto-form/dependencies.ts | 92 ++++++++++ src/components/ui/auto-form/index.ts | 15 ++ src/components/ui/auto-form/interface.ts | 95 ++++++++++ src/components/ui/auto-form/utils.ts | 171 ++++++++++++++++++ src/components/ui/badge/Badge.vue | 16 ++ src/components/ui/badge/index.ts | 25 +++ src/components/ui/calendar/Calendar.vue | 60 ++++++ src/components/ui/calendar/CalendarCell.vue | 24 +++ .../ui/calendar/CalendarCellTrigger.vue | 38 ++++ src/components/ui/calendar/CalendarGrid.vue | 24 +++ .../ui/calendar/CalendarGridBody.vue | 11 ++ .../ui/calendar/CalendarGridHead.vue | 11 ++ .../ui/calendar/CalendarGridRow.vue | 21 +++ .../ui/calendar/CalendarHeadCell.vue | 21 +++ src/components/ui/calendar/CalendarHeader.vue | 21 +++ .../ui/calendar/CalendarHeading.vue | 27 +++ .../ui/calendar/CalendarNextButton.vue | 32 ++++ .../ui/calendar/CalendarPrevButton.vue | 32 ++++ src/components/ui/calendar/index.ts | 12 ++ src/components/ui/checkbox/Checkbox.vue | 33 ++++ src/components/ui/checkbox/index.ts | 1 + src/components/ui/form/FormControl.vue | 16 ++ src/components/ui/form/FormDescription.vue | 20 ++ src/components/ui/form/FormItem.vue | 19 ++ src/components/ui/form/FormLabel.vue | 23 +++ src/components/ui/form/FormMessage.vue | 16 ++ src/components/ui/form/index.ts | 7 + src/components/ui/form/injectionKeys.ts | 5 + src/components/ui/form/useFormField.ts | 30 +++ src/components/ui/popover/Popover.vue | 15 ++ src/components/ui/popover/PopoverContent.vue | 48 +++++ src/components/ui/popover/PopoverTrigger.vue | 11 ++ src/components/ui/popover/index.ts | 3 + src/components/ui/radio-group/RadioGroup.vue | 25 +++ .../ui/radio-group/RadioGroupItem.vue | 39 ++++ src/components/ui/radio-group/index.ts | 2 + src/components/ui/switch/Switch.vue | 39 ++++ src/components/ui/switch/index.ts | 1 + src/components/ui/textarea/Textarea.vue | 24 +++ src/components/ui/textarea/index.ts | 1 + src/pages/balloon/single.vue | 85 ++++----- 65 files changed, 2114 insertions(+), 63 deletions(-) create mode 100644 src/components/ImageContainer.vue create mode 100644 src/components/ParamsCard.vue create mode 100644 src/components/ui/accordion/Accordion.vue create mode 100644 src/components/ui/accordion/AccordionContent.vue create mode 100644 src/components/ui/accordion/AccordionItem.vue create mode 100644 src/components/ui/accordion/AccordionTrigger.vue create mode 100644 src/components/ui/accordion/index.ts create mode 100644 src/components/ui/auto-form/AutoForm.vue create mode 100644 src/components/ui/auto-form/AutoFormField.vue create mode 100644 src/components/ui/auto-form/AutoFormFieldArray.vue create mode 100644 src/components/ui/auto-form/AutoFormFieldBoolean.vue create mode 100644 src/components/ui/auto-form/AutoFormFieldDate.vue create mode 100644 src/components/ui/auto-form/AutoFormFieldEnum.vue create mode 100644 src/components/ui/auto-form/AutoFormFieldFile.vue create mode 100644 src/components/ui/auto-form/AutoFormFieldInput.vue create mode 100644 src/components/ui/auto-form/AutoFormFieldNumber.vue create mode 100644 src/components/ui/auto-form/AutoFormFieldObject.vue create mode 100644 src/components/ui/auto-form/AutoFormLabel.vue create mode 100644 src/components/ui/auto-form/constant.ts create mode 100644 src/components/ui/auto-form/dependencies.ts create mode 100644 src/components/ui/auto-form/index.ts create mode 100644 src/components/ui/auto-form/interface.ts create mode 100644 src/components/ui/auto-form/utils.ts create mode 100644 src/components/ui/badge/Badge.vue create mode 100644 src/components/ui/badge/index.ts create mode 100644 src/components/ui/calendar/Calendar.vue create mode 100644 src/components/ui/calendar/CalendarCell.vue create mode 100644 src/components/ui/calendar/CalendarCellTrigger.vue create mode 100644 src/components/ui/calendar/CalendarGrid.vue create mode 100644 src/components/ui/calendar/CalendarGridBody.vue create mode 100644 src/components/ui/calendar/CalendarGridHead.vue create mode 100644 src/components/ui/calendar/CalendarGridRow.vue create mode 100644 src/components/ui/calendar/CalendarHeadCell.vue create mode 100644 src/components/ui/calendar/CalendarHeader.vue create mode 100644 src/components/ui/calendar/CalendarHeading.vue create mode 100644 src/components/ui/calendar/CalendarNextButton.vue create mode 100644 src/components/ui/calendar/CalendarPrevButton.vue create mode 100644 src/components/ui/calendar/index.ts create mode 100644 src/components/ui/checkbox/Checkbox.vue create mode 100644 src/components/ui/checkbox/index.ts create mode 100644 src/components/ui/form/FormControl.vue create mode 100644 src/components/ui/form/FormDescription.vue create mode 100644 src/components/ui/form/FormItem.vue create mode 100644 src/components/ui/form/FormLabel.vue create mode 100644 src/components/ui/form/FormMessage.vue create mode 100644 src/components/ui/form/index.ts create mode 100644 src/components/ui/form/injectionKeys.ts create mode 100644 src/components/ui/form/useFormField.ts create mode 100644 src/components/ui/popover/Popover.vue create mode 100644 src/components/ui/popover/PopoverContent.vue create mode 100644 src/components/ui/popover/PopoverTrigger.vue create mode 100644 src/components/ui/popover/index.ts create mode 100644 src/components/ui/radio-group/RadioGroup.vue create mode 100644 src/components/ui/radio-group/RadioGroupItem.vue create mode 100644 src/components/ui/radio-group/index.ts create mode 100644 src/components/ui/switch/Switch.vue create mode 100644 src/components/ui/switch/index.ts create mode 100644 src/components/ui/textarea/Textarea.vue create mode 100644 src/components/ui/textarea/index.ts diff --git a/components.d.ts b/components.d.ts index 1b7106b..e4d0739 100644 --- a/components.d.ts +++ b/components.d.ts @@ -7,9 +7,25 @@ export {} /* prettier-ignore */ declare module 'vue' { export interface GlobalComponents { + Accordion: typeof import('./src/components/ui/accordion/Accordion.vue')['default'] + AccordionContent: typeof import('./src/components/ui/accordion/AccordionContent.vue')['default'] + AccordionItem: typeof import('./src/components/ui/accordion/AccordionItem.vue')['default'] + AccordionTrigger: typeof import('./src/components/ui/accordion/AccordionTrigger.vue')['default'] + AutoForm: typeof import('./src/components/ui/auto-form/AutoForm.vue')['default'] + AutoFormField: typeof import('./src/components/ui/auto-form/AutoFormField.vue')['default'] + AutoFormFieldArray: typeof import('./src/components/ui/auto-form/AutoFormFieldArray.vue')['default'] + AutoFormFieldBoolean: typeof import('./src/components/ui/auto-form/AutoFormFieldBoolean.vue')['default'] + AutoFormFieldDate: typeof import('./src/components/ui/auto-form/AutoFormFieldDate.vue')['default'] + AutoFormFieldEnum: typeof import('./src/components/ui/auto-form/AutoFormFieldEnum.vue')['default'] + AutoFormFieldFile: typeof import('./src/components/ui/auto-form/AutoFormFieldFile.vue')['default'] + AutoFormFieldInput: typeof import('./src/components/ui/auto-form/AutoFormFieldInput.vue')['default'] + AutoFormFieldNumber: typeof import('./src/components/ui/auto-form/AutoFormFieldNumber.vue')['default'] + AutoFormFieldObject: typeof import('./src/components/ui/auto-form/AutoFormFieldObject.vue')['default'] + AutoFormLabel: typeof import('./src/components/ui/auto-form/AutoFormLabel.vue')['default'] Avatar: typeof import('./src/components/ui/avatar/Avatar.vue')['default'] AvatarFallback: typeof import('./src/components/ui/avatar/AvatarFallback.vue')['default'] AvatarImage: typeof import('./src/components/ui/avatar/AvatarImage.vue')['default'] + Badge: typeof import('./src/components/ui/badge/Badge.vue')['default'] Breadcrumb: typeof import('./src/components/ui/breadcrumb/Breadcrumb.vue')['default'] BreadcrumbEllipsis: typeof import('./src/components/ui/breadcrumb/BreadcrumbEllipsis.vue')['default'] BreadcrumbItem: typeof import('./src/components/ui/breadcrumb/BreadcrumbItem.vue')['default'] @@ -18,6 +34,19 @@ declare module 'vue' { BreadcrumbPage: typeof import('./src/components/ui/breadcrumb/BreadcrumbPage.vue')['default'] BreadcrumbSeparator: typeof import('./src/components/ui/breadcrumb/BreadcrumbSeparator.vue')['default'] Button: typeof import('./src/components/ui/button/Button.vue')['default'] + Calendar: typeof import('./src/components/ui/calendar/Calendar.vue')['default'] + CalendarCell: typeof import('./src/components/ui/calendar/CalendarCell.vue')['default'] + CalendarCellTrigger: typeof import('./src/components/ui/calendar/CalendarCellTrigger.vue')['default'] + CalendarGrid: typeof import('./src/components/ui/calendar/CalendarGrid.vue')['default'] + CalendarGridBody: typeof import('./src/components/ui/calendar/CalendarGridBody.vue')['default'] + CalendarGridHead: typeof import('./src/components/ui/calendar/CalendarGridHead.vue')['default'] + CalendarGridRow: typeof import('./src/components/ui/calendar/CalendarGridRow.vue')['default'] + CalendarHeadCell: typeof import('./src/components/ui/calendar/CalendarHeadCell.vue')['default'] + CalendarHeader: typeof import('./src/components/ui/calendar/CalendarHeader.vue')['default'] + CalendarHeading: typeof import('./src/components/ui/calendar/CalendarHeading.vue')['default'] + CalendarNextButton: typeof import('./src/components/ui/calendar/CalendarNextButton.vue')['default'] + CalendarPrevButton: typeof import('./src/components/ui/calendar/CalendarPrevButton.vue')['default'] + Checkbox: typeof import('./src/components/ui/checkbox/Checkbox.vue')['default'] Collapsible: typeof import('./src/components/ui/collapsible/Collapsible.vue')['default'] CollapsibleContent: typeof import('./src/components/ui/collapsible/CollapsibleContent.vue')['default'] CollapsibleTrigger: typeof import('./src/components/ui/collapsible/CollapsibleTrigger.vue')['default'] @@ -35,7 +64,13 @@ declare module 'vue' { DropdownMenuSubContent: typeof import('./src/components/ui/dropdown-menu/DropdownMenuSubContent.vue')['default'] DropdownMenuSubTrigger: typeof import('./src/components/ui/dropdown-menu/DropdownMenuSubTrigger.vue')['default'] DropdownMenuTrigger: typeof import('./src/components/ui/dropdown-menu/DropdownMenuTrigger.vue')['default'] + FormControl: typeof import('./src/components/ui/form/FormControl.vue')['default'] + FormDescription: typeof import('./src/components/ui/form/FormDescription.vue')['default'] + FormItem: typeof import('./src/components/ui/form/FormItem.vue')['default'] + FormLabel: typeof import('./src/components/ui/form/FormLabel.vue')['default'] + FormMessage: typeof import('./src/components/ui/form/FormMessage.vue')['default'] Image: typeof import('./src/components/Image.vue')['default'] + ImageContainer: typeof import('./src/components/ImageContainer.vue')['default'] Input: typeof import('./src/components/ui/input/Input.vue')['default'] Label: typeof import('./src/components/ui/label/Label.vue')['default'] Loading: typeof import('./src/components/Loading.vue')['default'] @@ -53,6 +88,12 @@ declare module 'vue' { NumberFieldDecrement: typeof import('./src/components/ui/number-field/NumberFieldDecrement.vue')['default'] NumberFieldIncrement: typeof import('./src/components/ui/number-field/NumberFieldIncrement.vue')['default'] NumberFieldInput: typeof import('./src/components/ui/number-field/NumberFieldInput.vue')['default'] + ParamsCard: typeof import('./src/components/ParamsCard.vue')['default'] + Popover: typeof import('./src/components/ui/popover/Popover.vue')['default'] + PopoverContent: typeof import('./src/components/ui/popover/PopoverContent.vue')['default'] + PopoverTrigger: typeof import('./src/components/ui/popover/PopoverTrigger.vue')['default'] + RadioGroup: typeof import('./src/components/ui/radio-group/RadioGroup.vue')['default'] + RadioGroupItem: typeof import('./src/components/ui/radio-group/RadioGroupItem.vue')['default'] RouterLink: typeof import('vue-router')['RouterLink'] RouterView: typeof import('vue-router')['RouterView'] Select: typeof import('./src/components/ui/select/Select.vue')['default'] @@ -100,7 +141,9 @@ declare module 'vue' { SidebarSeparator: typeof import('./src/components/ui/sidebar/SidebarSeparator.vue')['default'] SidebarTrigger: typeof import('./src/components/ui/sidebar/SidebarTrigger.vue')['default'] Skeleton: typeof import('./src/components/ui/skeleton/Skeleton.vue')['default'] + Switch: typeof import('./src/components/ui/switch/Switch.vue')['default'] TestHeader: typeof import('./src/components/TestHeader.vue')['default'] + Textarea: typeof import('./src/components/ui/textarea/Textarea.vue')['default'] TheFooter: typeof import('./src/components/TheFooter.vue')['default'] TheHeader: typeof import('./src/components/TheHeader.vue')['default'] Tooltip: typeof import('./src/components/ui/tooltip/Tooltip.vue')['default'] diff --git a/package.json b/package.json index 5dca048..ca24d01 100644 --- a/package.json +++ b/package.json @@ -14,6 +14,7 @@ }, "dependencies": { "@unocss/preset-icons": "^0.65.4", + "@vee-validate/zod": "^4.15.0", "@vueuse/core": "^12.0.0", "class-variance-authority": "^0.7.1", "clsx": "^2.1.1", @@ -22,8 +23,10 @@ "shadcn-vue": "^0.11.3", "tailwind-merge": "^2.5.5", "tailwindcss-animate": "^1.0.7", + "vee-validate": "^4.15.0", "vue": "^3.5.13", - "vue-router": "^4.5.0" + "vue-router": "^4.5.0", + "zod": "^3.24.1" }, "devDependencies": { "@antfu/eslint-config": "^3.12.0", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index e81acb5..183b0b9 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -15,6 +15,9 @@ importers: '@unocss/preset-icons': specifier: ^0.65.4 version: 0.65.4 + '@vee-validate/zod': + specifier: ^4.15.0 + version: 4.15.0(vue@3.5.13(typescript@5.6.3))(zod@3.24.1) '@vueuse/core': specifier: ^12.0.0 version: 12.0.0(typescript@5.6.3) @@ -39,12 +42,18 @@ importers: tailwindcss-animate: specifier: ^1.0.7 version: 1.0.7 + vee-validate: + specifier: ^4.15.0 + version: 4.15.0(vue@3.5.13(typescript@5.6.3)) vue: specifier: ^3.5.13 version: 3.5.13(typescript@5.6.3) vue-router: specifier: ^4.5.0 version: 4.5.0(vue@3.5.13(typescript@5.6.3)) + zod: + specifier: ^3.24.1 + version: 3.24.1 devDependencies: '@antfu/eslint-config': specifier: ^3.12.0 @@ -1194,6 +1203,11 @@ packages: engines: {node: '>=18'} hasBin: true + '@vee-validate/zod@4.15.0': + resolution: {integrity: sha512-MpvIKiyg9X5yD8bJW0no2AU7wtR2T5mrvD9tuPRiie951sU2n6QKgMV38qKKOiqFBCxsMSjIuLLLV3V5kVE4nQ==} + peerDependencies: + zod: ^3.24.0 + '@vitejs/plugin-vue@5.2.1': resolution: {integrity: sha512-cxh314tzaWwOLqVes2gnnCtvBDcM1UMdn+iFR+UjAn411dPT3tOmqrJjbMd7koZpMAmBM/GqeV4n9ge7JSiJJQ==} engines: {node: ^18.0.0 || >=20.0.0} @@ -1460,6 +1474,9 @@ packages: '@vue/devtools-api@6.6.4': resolution: {integrity: sha512-sGhTPMuXqZ1rVOk32RylztWkfXTRhuS7vgAKv0zjqk8gbsHkJ7xfFf+jbySxt7tWObEJwyKaHMikV/WGDiQm8g==} + '@vue/devtools-api@7.7.0': + resolution: {integrity: sha512-bHEv6kT85BHtyGgDhE07bAUMAy7zpv6nnR004nSTd0wWMrAOtcrYoXO5iyr20Hkf5jR8obQOfS3byW+I3l2CCA==} + '@vue/devtools-core@7.7.0': resolution: {integrity: sha512-tSO3pghV5RZGSonZ87S2fOGru3X93epmar5IjZOWjHxH6XSwnK5UbR2aW5puZV+LgLoVYrcNou3krSo5k1F31g==} peerDependencies: @@ -3635,6 +3652,10 @@ packages: resolution: {integrity: sha512-4dbzIzqvjtgiM5rw1k5rEHtBANKmdudhGyBEajN01fEyhaAIhsoKNy6y7+IN93IfpFtwY9iqi7kD+xwKhQsNJA==} engines: {node: '>=8'} + type-fest@4.31.0: + resolution: {integrity: sha512-yCxltHW07Nkhv/1F6wWBr8kz+5BGMfP+RbRSYFnegVb0qV/UMT0G0ElBloPVerqn4M2ZV80Ir1FtCcYv1cT6vQ==} + engines: {node: '>=16'} + typescript@5.6.3: resolution: {integrity: sha512-hjcS1mhfuyi4WW8IWtjP7brDrG2cuDZukyrYrSauoXGNgx0S7zceP07adYkJycEr56BOUTNPzbInooiN3fn1qw==} engines: {node: '>=14.17'} @@ -3785,6 +3806,11 @@ packages: validate-npm-package-license@3.0.4: resolution: {integrity: sha512-DpKm2Ui/xN7/HQKCtpZxoRWBhZ9Z0kqtygG8XCgNQ8ZlDnxuQmWhj566j8fN4Cu3/JmbhsDo7fcAJq4s9h27Ew==} + vee-validate@4.15.0: + resolution: {integrity: sha512-PGJh1QCFwCBjbHu5aN6vB8macYVWrajbDvgo1Y/8fz9n/RVIkLmZCJDpUgu7+mUmCOPMxeyq7vXUOhbwAqdXcA==} + peerDependencies: + vue: ^3.4.26 + vite-hot-client@0.2.4: resolution: {integrity: sha512-a1nzURqO7DDmnXqabFOliz908FRmIppkBKsJthS8rbe8hBEXwEwe4C3Pp33Z1JoFCYfVL4kTOMLKk0ZZxREIeA==} peerDependencies: @@ -5140,6 +5166,14 @@ snapshots: transitivePeerDependencies: - supports-color + '@vee-validate/zod@4.15.0(vue@3.5.13(typescript@5.6.3))(zod@3.24.1)': + dependencies: + type-fest: 4.31.0 + vee-validate: 4.15.0(vue@3.5.13(typescript@5.6.3)) + zod: 3.24.1 + transitivePeerDependencies: + - vue + '@vitejs/plugin-vue@5.2.1(vite@6.0.3(@types/node@22.10.2)(jiti@2.4.0)(stylus@0.57.0)(tsx@4.19.2)(yaml@2.6.1))(vue@3.5.13(typescript@5.6.3))': dependencies: vite: 6.0.3(@types/node@22.10.2)(jiti@2.4.0)(stylus@0.57.0)(tsx@4.19.2)(yaml@2.6.1) @@ -5563,6 +5597,10 @@ snapshots: '@vue/devtools-api@6.6.4': {} + '@vue/devtools-api@7.7.0': + dependencies: + '@vue/devtools-kit': 7.7.0 + '@vue/devtools-core@7.7.0(vite@6.0.3(@types/node@22.10.2)(jiti@2.4.0)(stylus@0.57.0)(tsx@4.19.2)(yaml@2.6.1))(vue@3.5.13(typescript@5.6.3))': dependencies: '@vue/devtools-kit': 7.7.0 @@ -8085,6 +8123,8 @@ snapshots: type-fest@0.8.1: {} + type-fest@4.31.0: {} + typescript@5.6.3: {} ufo@1.5.4: {} @@ -8328,6 +8368,12 @@ snapshots: spdx-correct: 3.2.0 spdx-expression-parse: 3.0.1 + vee-validate@4.15.0(vue@3.5.13(typescript@5.6.3)): + dependencies: + '@vue/devtools-api': 7.7.0 + type-fest: 4.31.0 + vue: 3.5.13(typescript@5.6.3) + vite-hot-client@0.2.4(vite@6.0.3(@types/node@22.10.2)(jiti@2.4.0)(stylus@0.57.0)(tsx@4.19.2)(yaml@2.6.1)): dependencies: vite: 6.0.3(@types/node@22.10.2)(jiti@2.4.0)(stylus@0.57.0)(tsx@4.19.2)(yaml@2.6.1) diff --git a/src/App.vue b/src/App.vue index aadcd44..c586d38 100644 --- a/src/App.vue +++ b/src/App.vue @@ -184,9 +184,7 @@ const data = { -
- -
+ diff --git a/src/components/Image.vue b/src/components/Image.vue index 7f51fdc..4bd2e4a 100644 --- a/src/components/Image.vue +++ b/src/components/Image.vue @@ -1,24 +1,12 @@ diff --git a/src/components/ImageContainer.vue b/src/components/ImageContainer.vue new file mode 100644 index 0000000..95581dd --- /dev/null +++ b/src/components/ImageContainer.vue @@ -0,0 +1,20 @@ + + + diff --git a/src/components/ParamsCard.vue b/src/components/ParamsCard.vue new file mode 100644 index 0000000..6351ddd --- /dev/null +++ b/src/components/ParamsCard.vue @@ -0,0 +1,43 @@ + + + diff --git a/src/components/ui/accordion/Accordion.vue b/src/components/ui/accordion/Accordion.vue new file mode 100644 index 0000000..8ce8571 --- /dev/null +++ b/src/components/ui/accordion/Accordion.vue @@ -0,0 +1,19 @@ + + + diff --git a/src/components/ui/accordion/AccordionContent.vue b/src/components/ui/accordion/AccordionContent.vue new file mode 100644 index 0000000..8c97701 --- /dev/null +++ b/src/components/ui/accordion/AccordionContent.vue @@ -0,0 +1,24 @@ + + + diff --git a/src/components/ui/accordion/AccordionItem.vue b/src/components/ui/accordion/AccordionItem.vue new file mode 100644 index 0000000..6c693ab --- /dev/null +++ b/src/components/ui/accordion/AccordionItem.vue @@ -0,0 +1,24 @@ + + + diff --git a/src/components/ui/accordion/AccordionTrigger.vue b/src/components/ui/accordion/AccordionTrigger.vue new file mode 100644 index 0000000..af5cc23 --- /dev/null +++ b/src/components/ui/accordion/AccordionTrigger.vue @@ -0,0 +1,39 @@ + + + diff --git a/src/components/ui/accordion/index.ts b/src/components/ui/accordion/index.ts new file mode 100644 index 0000000..9340ac0 --- /dev/null +++ b/src/components/ui/accordion/index.ts @@ -0,0 +1,4 @@ +export { default as Accordion } from './Accordion.vue' +export { default as AccordionContent } from './AccordionContent.vue' +export { default as AccordionItem } from './AccordionItem.vue' +export { default as AccordionTrigger } from './AccordionTrigger.vue' diff --git a/src/components/ui/auto-form/AutoForm.vue b/src/components/ui/auto-form/AutoForm.vue new file mode 100644 index 0000000..986fe40 --- /dev/null +++ b/src/components/ui/auto-form/AutoForm.vue @@ -0,0 +1,105 @@ + + + diff --git a/src/components/ui/auto-form/AutoFormField.vue b/src/components/ui/auto-form/AutoFormField.vue new file mode 100644 index 0000000..5c371a8 --- /dev/null +++ b/src/components/ui/auto-form/AutoFormField.vue @@ -0,0 +1,45 @@ + + + diff --git a/src/components/ui/auto-form/AutoFormFieldArray.vue b/src/components/ui/auto-form/AutoFormFieldArray.vue new file mode 100644 index 0000000..700c56d --- /dev/null +++ b/src/components/ui/auto-form/AutoFormFieldArray.vue @@ -0,0 +1,110 @@ + + + diff --git a/src/components/ui/auto-form/AutoFormFieldBoolean.vue b/src/components/ui/auto-form/AutoFormFieldBoolean.vue new file mode 100644 index 0000000..a298568 --- /dev/null +++ b/src/components/ui/auto-form/AutoFormFieldBoolean.vue @@ -0,0 +1,41 @@ + + + diff --git a/src/components/ui/auto-form/AutoFormFieldDate.vue b/src/components/ui/auto-form/AutoFormFieldDate.vue new file mode 100644 index 0000000..066c8cd --- /dev/null +++ b/src/components/ui/auto-form/AutoFormFieldDate.vue @@ -0,0 +1,57 @@ + + + diff --git a/src/components/ui/auto-form/AutoFormFieldEnum.vue b/src/components/ui/auto-form/AutoFormFieldEnum.vue new file mode 100644 index 0000000..94dde82 --- /dev/null +++ b/src/components/ui/auto-form/AutoFormFieldEnum.vue @@ -0,0 +1,49 @@ + + + diff --git a/src/components/ui/auto-form/AutoFormFieldFile.vue b/src/components/ui/auto-form/AutoFormFieldFile.vue new file mode 100644 index 0000000..92611d6 --- /dev/null +++ b/src/components/ui/auto-form/AutoFormFieldFile.vue @@ -0,0 +1,74 @@ + + + diff --git a/src/components/ui/auto-form/AutoFormFieldInput.vue b/src/components/ui/auto-form/AutoFormFieldInput.vue new file mode 100644 index 0000000..ad82e94 --- /dev/null +++ b/src/components/ui/auto-form/AutoFormFieldInput.vue @@ -0,0 +1,36 @@ + + + diff --git a/src/components/ui/auto-form/AutoFormFieldNumber.vue b/src/components/ui/auto-form/AutoFormFieldNumber.vue new file mode 100644 index 0000000..df3f8d7 --- /dev/null +++ b/src/components/ui/auto-form/AutoFormFieldNumber.vue @@ -0,0 +1,32 @@ + + + diff --git a/src/components/ui/auto-form/AutoFormFieldObject.vue b/src/components/ui/auto-form/AutoFormFieldObject.vue new file mode 100644 index 0000000..36bee6c --- /dev/null +++ b/src/components/ui/auto-form/AutoFormFieldObject.vue @@ -0,0 +1,78 @@ + + + diff --git a/src/components/ui/auto-form/AutoFormLabel.vue b/src/components/ui/auto-form/AutoFormLabel.vue new file mode 100644 index 0000000..6ea80cc --- /dev/null +++ b/src/components/ui/auto-form/AutoFormLabel.vue @@ -0,0 +1,14 @@ + + + diff --git a/src/components/ui/auto-form/constant.ts b/src/components/ui/auto-form/constant.ts new file mode 100644 index 0000000..5ab7067 --- /dev/null +++ b/src/components/ui/auto-form/constant.ts @@ -0,0 +1,40 @@ +import type { InputComponents } from './interface' +import AutoFormFieldArray from './AutoFormFieldArray.vue' +import AutoFormFieldBoolean from './AutoFormFieldBoolean.vue' +import AutoFormFieldDate from './AutoFormFieldDate.vue' +import AutoFormFieldEnum from './AutoFormFieldEnum.vue' +import AutoFormFieldFile from './AutoFormFieldFile.vue' +import AutoFormFieldInput from './AutoFormFieldInput.vue' +import AutoFormFieldNumber from './AutoFormFieldNumber.vue' +import AutoFormFieldObject from './AutoFormFieldObject.vue' + +export const INPUT_COMPONENTS: InputComponents = { + date: AutoFormFieldDate, + select: AutoFormFieldEnum, + radio: AutoFormFieldEnum, + checkbox: AutoFormFieldBoolean, + switch: AutoFormFieldBoolean, + textarea: AutoFormFieldInput, + number: AutoFormFieldNumber, + string: AutoFormFieldInput, + file: AutoFormFieldFile, + array: AutoFormFieldArray, + object: AutoFormFieldObject, +} + +/** + * Define handlers for specific Zod types. + * You can expand this object to support more types. + */ +export const DEFAULT_ZOD_HANDLERS: { + [key: string]: keyof typeof INPUT_COMPONENTS +} = { + ZodString: 'string', + ZodBoolean: 'checkbox', + ZodDate: 'date', + ZodEnum: 'select', + ZodNativeEnum: 'select', + ZodNumber: 'number', + ZodArray: 'array', + ZodObject: 'object', +} diff --git a/src/components/ui/auto-form/dependencies.ts b/src/components/ui/auto-form/dependencies.ts new file mode 100644 index 0000000..362cdb0 --- /dev/null +++ b/src/components/ui/auto-form/dependencies.ts @@ -0,0 +1,92 @@ +import type { Ref } from 'vue' +import type * as z from 'zod' +import { createContext } from 'radix-vue' +import { useFieldValue, useFormValues } from 'vee-validate' +import { computed, ref, watch } from 'vue' +import { type Dependency, DependencyType, type EnumValues } from './interface' +import { getFromPath, getIndexIfArray } from './utils' + +export const [injectDependencies, provideDependencies] = createContext>>[] | undefined>>('AutoFormDependencies') + +export default function useDependencies( + fieldName: string, +) { + const form = useFormValues() + // parsed test[0].age => test.age + const currentFieldName = fieldName.replace(/\[\d+\]/g, '') + const currentFieldValue = useFieldValue(fieldName) + + if (!form) + throw new Error('useDependencies should be used within ') + + const dependencies = injectDependencies() + const isDisabled = ref(false) + const isHidden = ref(false) + const isRequired = ref(false) + const overrideOptions = ref() + + const currentFieldDependencies = computed(() => dependencies.value?.filter( + dependency => dependency.targetField === currentFieldName, + )) + + function getSourceValue(dep: Dependency) { + const source = dep.sourceField as string + const index = getIndexIfArray(fieldName) ?? -1 + const [sourceLast, ...sourceInitial] = source.split('.').toReversed() + const [_targetLast, ...targetInitial] = (dep.targetField as string).split('.').toReversed() + + if (index >= 0 && sourceInitial.join(',') === targetInitial.join(',')) { + const [_currentLast, ...currentInitial] = fieldName.split('.').toReversed() + return getFromPath(form.value, currentInitial.join('.') + sourceLast) + } + + return getFromPath(form.value, source) + } + + const sourceFieldValues = computed(() => currentFieldDependencies.value?.map(dep => getSourceValue(dep))) + + const resetConditionState = () => { + isDisabled.value = false + isHidden.value = false + isRequired.value = false + overrideOptions.value = undefined + } + + watch([sourceFieldValues, dependencies], () => { + resetConditionState() + currentFieldDependencies.value?.forEach((dep) => { + const sourceValue = getSourceValue(dep) + const conditionMet = dep.when(sourceValue, currentFieldValue.value) + + switch (dep.type) { + case DependencyType.DISABLES: + if (conditionMet) + isDisabled.value = true + + break + case DependencyType.REQUIRES: + if (conditionMet) + isRequired.value = true + + break + case DependencyType.HIDES: + if (conditionMet) + isHidden.value = true + + break + case DependencyType.SETS_OPTIONS: + if (conditionMet) + overrideOptions.value = dep.options + + break + } + }) + }, { immediate: true, deep: true }) + + return { + isDisabled, + isHidden, + isRequired, + overrideOptions, + } +} diff --git a/src/components/ui/auto-form/index.ts b/src/components/ui/auto-form/index.ts new file mode 100644 index 0000000..1874fd1 --- /dev/null +++ b/src/components/ui/auto-form/index.ts @@ -0,0 +1,15 @@ +export { default as AutoForm } from './AutoForm.vue' +export { default as AutoFormField } from './AutoFormField.vue' + +export { default as AutoFormFieldArray } from './AutoFormFieldArray.vue' +export { default as AutoFormFieldBoolean } from './AutoFormFieldBoolean.vue' +export { default as AutoFormFieldDate } from './AutoFormFieldDate.vue' + +export { default as AutoFormFieldEnum } from './AutoFormFieldEnum.vue' +export { default as AutoFormFieldFile } from './AutoFormFieldFile.vue' +export { default as AutoFormFieldInput } from './AutoFormFieldInput.vue' +export { default as AutoFormFieldNumber } from './AutoFormFieldNumber.vue' +export { default as AutoFormFieldObject } from './AutoFormFieldObject.vue' +export { default as AutoFormLabel } from './AutoFormLabel.vue' +export type { Config, ConfigItem, FieldProps } from './interface' +export { getBaseSchema, getBaseType, getObjectFormSchema } from './utils' diff --git a/src/components/ui/auto-form/interface.ts b/src/components/ui/auto-form/interface.ts new file mode 100644 index 0000000..2f622e0 --- /dev/null +++ b/src/components/ui/auto-form/interface.ts @@ -0,0 +1,95 @@ +import type { Component, InputHTMLAttributes } from 'vue' +import type { z, ZodAny } from 'zod' +import type { INPUT_COMPONENTS } from './constant' + +export interface FieldProps { + fieldName: string + label?: string + required?: boolean + config?: ConfigItem + disabled?: boolean +} + +export interface Shape { + type: string + default?: any + required?: boolean + options?: string[] + schema?: ZodAny +} + +export interface InputComponents { + date: Component + select: Component + radio: Component + checkbox: Component + switch: Component + textarea: Component + number: Component + string: Component + file: Component + array: Component + object: Component +} + +export interface ConfigItem { + /** Value for the `FormLabel` */ + label?: string + /** Value for the `FormDescription` */ + description?: string + /** Pick which component to be rendered. */ + component?: keyof typeof INPUT_COMPONENTS | Component + /** Hide `FormLabel`. */ + hideLabel?: boolean + inputProps?: InputHTMLAttributes +} + +// Define a type to unwrap an array +type UnwrapArray = T extends (infer U)[] ? U : never + +export type Config = { + // If SchemaType.key is an object, create a nested Config, otherwise ConfigItem + [Key in keyof SchemaType]?: + SchemaType[Key] extends any[] + ? UnwrapArray> + : SchemaType[Key] extends object + ? Config + : ConfigItem; +} + +export enum DependencyType { + DISABLES, + REQUIRES, + HIDES, + SETS_OPTIONS, +} + +interface BaseDependency>> { + sourceField: keyof SchemaType + type: DependencyType + targetField: keyof SchemaType + when: (sourceFieldValue: any, targetFieldValue: any) => boolean +} + +export type ValueDependency>> = + BaseDependency & { + type: + | DependencyType.DISABLES + | DependencyType.REQUIRES + | DependencyType.HIDES + } + +export type EnumValues = readonly [string, ...string[]] + +export type OptionsDependency< + SchemaType extends z.infer>, +> = BaseDependency & { + type: DependencyType.SETS_OPTIONS + + // Partial array of values from sourceField that will trigger the dependency + options: EnumValues +} + +export type Dependency>> = + | ValueDependency + | OptionsDependency diff --git a/src/components/ui/auto-form/utils.ts b/src/components/ui/auto-form/utils.ts new file mode 100644 index 0000000..1fa3a29 --- /dev/null +++ b/src/components/ui/auto-form/utils.ts @@ -0,0 +1,171 @@ +import type { z } from 'zod' + +// TODO: This should support recursive ZodEffects but TypeScript doesn't allow circular type definitions. +export type ZodObjectOrWrapped = + | z.ZodObject + | z.ZodEffects> + +/** + * Beautify a camelCase string. + * e.g. "myString" -> "My String" + */ +export function beautifyObjectName(string: string) { + // Remove bracketed indices + // if numbers only return the string + let output = string.replace(/\[\d+\]/g, '').replace(/([A-Z])/g, ' $1') + output = output.charAt(0).toUpperCase() + output.slice(1) + return output +} + +/** + * Parse string and extract the index + * @param string + * @returns index or undefined + */ +export function getIndexIfArray(string: string) { + const indexRegex = /\[(\d+)\]/ + // Match the index + const match = string.match(indexRegex) + // Extract the index (number) + const index = match ? Number.parseInt(match[1]) : undefined + return index +} + +/** + * Get the lowest level Zod type. + * This will unpack optionals, refinements, etc. + */ +export function getBaseSchema< + ChildType extends z.ZodAny | z.AnyZodObject = z.ZodAny, +>(schema: ChildType | z.ZodEffects): ChildType | null { + if (!schema) + return null + if ('innerType' in schema._def) + return getBaseSchema(schema._def.innerType as ChildType) + + if ('schema' in schema._def) + return getBaseSchema(schema._def.schema as ChildType) + + return schema as ChildType +} + +/** + * Get the type name of the lowest level Zod type. + * This will unpack optionals, refinements, etc. + */ +export function getBaseType(schema: z.ZodAny) { + const baseSchema = getBaseSchema(schema) + return baseSchema ? baseSchema._def.typeName : '' +} + +/** + * Search for a "ZodDefault" in the Zod stack and return its value. + */ +export function getDefaultValueInZodStack(schema: z.ZodAny): any { + const typedSchema = schema as unknown as z.ZodDefault< + z.ZodNumber | z.ZodString + > + + if (typedSchema._def.typeName === 'ZodDefault') + return typedSchema._def.defaultValue() + + if ('innerType' in typedSchema._def) { + return getDefaultValueInZodStack( + typedSchema._def.innerType as unknown as z.ZodAny, + ) + } + if ('schema' in typedSchema._def) { + return getDefaultValueInZodStack( + (typedSchema._def as any).schema as z.ZodAny, + ) + } + + return undefined +} + +export function getObjectFormSchema( + schema: ZodObjectOrWrapped, +): z.ZodObject { + if (schema?._def.typeName === 'ZodEffects') { + const typedSchema = schema as z.ZodEffects> + return getObjectFormSchema(typedSchema._def.schema) + } + return schema as z.ZodObject +} + +function isIndex(value: unknown): value is number { + return Number(value) >= 0 +} +/** + * Constructs a path with dot paths for arrays to use brackets to be compatible with vee-validate path syntax + */ +export function normalizeFormPath(path: string): string { + const pathArr = path.split('.') + if (!pathArr.length) + return '' + + let fullPath = String(pathArr[0]) + for (let i = 1; i < pathArr.length; i++) { + if (isIndex(pathArr[i])) { + fullPath += `[${pathArr[i]}]` + continue + } + + fullPath += `.${pathArr[i]}` + } + + return fullPath +} + +type NestedRecord = Record | { [k: string]: NestedRecord } +/** + * Checks if the path opted out of nested fields using `[fieldName]` syntax + */ +export function isNotNestedPath(path: string) { + return /^\[.+\]$/.test(path) +} +function isObject(obj: unknown): obj is Record { + return obj !== null && !!obj && typeof obj === 'object' && !Array.isArray(obj) +} +function isContainerValue(value: unknown): value is Record { + return isObject(value) || Array.isArray(value) +} +function cleanupNonNestedPath(path: string) { + if (isNotNestedPath(path)) + return path.replace(/\[|\]/g, '') + + return path +} + +/** + * Gets a nested property value from an object + */ +export function getFromPath(object: NestedRecord | undefined, path: string): TValue | undefined +export function getFromPath( + object: NestedRecord | undefined, + path: string, + fallback?: TFallback, +): TValue | TFallback +export function getFromPath( + object: NestedRecord | undefined, + path: string, + fallback?: TFallback, +): TValue | TFallback | undefined { + if (!object) + return fallback + + if (isNotNestedPath(path)) + return object[cleanupNonNestedPath(path)] as TValue | undefined + + const resolvedValue = (path || '') + .split(/\.|\[(\d+)\]/) + .filter(Boolean) + .reduce((acc, propKey) => { + if (isContainerValue(acc) && propKey in acc) + return acc[propKey] + + return fallback + }, object as unknown) + + return resolvedValue as TValue | undefined +} diff --git a/src/components/ui/badge/Badge.vue b/src/components/ui/badge/Badge.vue new file mode 100644 index 0000000..5085cf0 --- /dev/null +++ b/src/components/ui/badge/Badge.vue @@ -0,0 +1,16 @@ + + + diff --git a/src/components/ui/badge/index.ts b/src/components/ui/badge/index.ts new file mode 100644 index 0000000..35e7a1a --- /dev/null +++ b/src/components/ui/badge/index.ts @@ -0,0 +1,25 @@ +import { cva, type VariantProps } from 'class-variance-authority' + +export { default as Badge } from './Badge.vue' + +export const badgeVariants = cva( + 'inline-flex items-center rounded-full border px-2.5 py-0.5 text-xs font-semibold transition-colors focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2', + { + variants: { + variant: { + default: + 'border-transparent bg-primary text-primary-foreground hover:bg-primary/80', + secondary: + 'border-transparent bg-secondary text-secondary-foreground hover:bg-secondary/80', + destructive: + 'border-transparent bg-destructive text-destructive-foreground hover:bg-destructive/80', + outline: 'text-foreground', + }, + }, + defaultVariants: { + variant: 'default', + }, + }, +) + +export type BadgeVariants = VariantProps diff --git a/src/components/ui/calendar/Calendar.vue b/src/components/ui/calendar/Calendar.vue new file mode 100644 index 0000000..61f1bb2 --- /dev/null +++ b/src/components/ui/calendar/Calendar.vue @@ -0,0 +1,60 @@ + + + diff --git a/src/components/ui/calendar/CalendarCell.vue b/src/components/ui/calendar/CalendarCell.vue new file mode 100644 index 0000000..9e7f206 --- /dev/null +++ b/src/components/ui/calendar/CalendarCell.vue @@ -0,0 +1,24 @@ + + + diff --git a/src/components/ui/calendar/CalendarCellTrigger.vue b/src/components/ui/calendar/CalendarCellTrigger.vue new file mode 100644 index 0000000..bfae9eb --- /dev/null +++ b/src/components/ui/calendar/CalendarCellTrigger.vue @@ -0,0 +1,38 @@ + + + diff --git a/src/components/ui/calendar/CalendarGrid.vue b/src/components/ui/calendar/CalendarGrid.vue new file mode 100644 index 0000000..e6d3945 --- /dev/null +++ b/src/components/ui/calendar/CalendarGrid.vue @@ -0,0 +1,24 @@ + + + diff --git a/src/components/ui/calendar/CalendarGridBody.vue b/src/components/ui/calendar/CalendarGridBody.vue new file mode 100644 index 0000000..23d71ce --- /dev/null +++ b/src/components/ui/calendar/CalendarGridBody.vue @@ -0,0 +1,11 @@ + + + diff --git a/src/components/ui/calendar/CalendarGridHead.vue b/src/components/ui/calendar/CalendarGridHead.vue new file mode 100644 index 0000000..f8101a3 --- /dev/null +++ b/src/components/ui/calendar/CalendarGridHead.vue @@ -0,0 +1,11 @@ + + + diff --git a/src/components/ui/calendar/CalendarGridRow.vue b/src/components/ui/calendar/CalendarGridRow.vue new file mode 100644 index 0000000..be17a18 --- /dev/null +++ b/src/components/ui/calendar/CalendarGridRow.vue @@ -0,0 +1,21 @@ + + + diff --git a/src/components/ui/calendar/CalendarHeadCell.vue b/src/components/ui/calendar/CalendarHeadCell.vue new file mode 100644 index 0000000..2175b27 --- /dev/null +++ b/src/components/ui/calendar/CalendarHeadCell.vue @@ -0,0 +1,21 @@ + + + diff --git a/src/components/ui/calendar/CalendarHeader.vue b/src/components/ui/calendar/CalendarHeader.vue new file mode 100644 index 0000000..11a40c1 --- /dev/null +++ b/src/components/ui/calendar/CalendarHeader.vue @@ -0,0 +1,21 @@ + + + diff --git a/src/components/ui/calendar/CalendarHeading.vue b/src/components/ui/calendar/CalendarHeading.vue new file mode 100644 index 0000000..a17a933 --- /dev/null +++ b/src/components/ui/calendar/CalendarHeading.vue @@ -0,0 +1,27 @@ + + + diff --git a/src/components/ui/calendar/CalendarNextButton.vue b/src/components/ui/calendar/CalendarNextButton.vue new file mode 100644 index 0000000..09c94bb --- /dev/null +++ b/src/components/ui/calendar/CalendarNextButton.vue @@ -0,0 +1,32 @@ + + + diff --git a/src/components/ui/calendar/CalendarPrevButton.vue b/src/components/ui/calendar/CalendarPrevButton.vue new file mode 100644 index 0000000..bf3b864 --- /dev/null +++ b/src/components/ui/calendar/CalendarPrevButton.vue @@ -0,0 +1,32 @@ + + + diff --git a/src/components/ui/calendar/index.ts b/src/components/ui/calendar/index.ts new file mode 100644 index 0000000..5239a1b --- /dev/null +++ b/src/components/ui/calendar/index.ts @@ -0,0 +1,12 @@ +export { default as Calendar } from './Calendar.vue' +export { default as CalendarCell } from './CalendarCell.vue' +export { default as CalendarCellTrigger } from './CalendarCellTrigger.vue' +export { default as CalendarGrid } from './CalendarGrid.vue' +export { default as CalendarGridBody } from './CalendarGridBody.vue' +export { default as CalendarGridHead } from './CalendarGridHead.vue' +export { default as CalendarGridRow } from './CalendarGridRow.vue' +export { default as CalendarHeadCell } from './CalendarHeadCell.vue' +export { default as CalendarHeader } from './CalendarHeader.vue' +export { default as CalendarHeading } from './CalendarHeading.vue' +export { default as CalendarNextButton } from './CalendarNextButton.vue' +export { default as CalendarPrevButton } from './CalendarPrevButton.vue' diff --git a/src/components/ui/checkbox/Checkbox.vue b/src/components/ui/checkbox/Checkbox.vue new file mode 100644 index 0000000..71f1977 --- /dev/null +++ b/src/components/ui/checkbox/Checkbox.vue @@ -0,0 +1,33 @@ + + + diff --git a/src/components/ui/checkbox/index.ts b/src/components/ui/checkbox/index.ts new file mode 100644 index 0000000..8c28c28 --- /dev/null +++ b/src/components/ui/checkbox/index.ts @@ -0,0 +1 @@ +export { default as Checkbox } from './Checkbox.vue' diff --git a/src/components/ui/form/FormControl.vue b/src/components/ui/form/FormControl.vue new file mode 100644 index 0000000..8459cab --- /dev/null +++ b/src/components/ui/form/FormControl.vue @@ -0,0 +1,16 @@ + + + diff --git a/src/components/ui/form/FormDescription.vue b/src/components/ui/form/FormDescription.vue new file mode 100644 index 0000000..2dccb0e --- /dev/null +++ b/src/components/ui/form/FormDescription.vue @@ -0,0 +1,20 @@ + + + diff --git a/src/components/ui/form/FormItem.vue b/src/components/ui/form/FormItem.vue new file mode 100644 index 0000000..3882356 --- /dev/null +++ b/src/components/ui/form/FormItem.vue @@ -0,0 +1,19 @@ + + + diff --git a/src/components/ui/form/FormLabel.vue b/src/components/ui/form/FormLabel.vue new file mode 100644 index 0000000..72913cc --- /dev/null +++ b/src/components/ui/form/FormLabel.vue @@ -0,0 +1,23 @@ + + + diff --git a/src/components/ui/form/FormMessage.vue b/src/components/ui/form/FormMessage.vue new file mode 100644 index 0000000..37ce26b --- /dev/null +++ b/src/components/ui/form/FormMessage.vue @@ -0,0 +1,16 @@ + + + diff --git a/src/components/ui/form/index.ts b/src/components/ui/form/index.ts new file mode 100644 index 0000000..4241e97 --- /dev/null +++ b/src/components/ui/form/index.ts @@ -0,0 +1,7 @@ +export { default as FormControl } from './FormControl.vue' +export { default as FormDescription } from './FormDescription.vue' +export { default as FormItem } from './FormItem.vue' +export { default as FormLabel } from './FormLabel.vue' +export { default as FormMessage } from './FormMessage.vue' +export { FORM_ITEM_INJECTION_KEY } from './injectionKeys' +export { Form, Field as FormField } from 'vee-validate' diff --git a/src/components/ui/form/injectionKeys.ts b/src/components/ui/form/injectionKeys.ts new file mode 100644 index 0000000..0f65611 --- /dev/null +++ b/src/components/ui/form/injectionKeys.ts @@ -0,0 +1,5 @@ +import type { InjectionKey } from 'vue' + +export const FORM_ITEM_INJECTION_KEY + // eslint-disable-next-line symbol-description + = Symbol() as InjectionKey diff --git a/src/components/ui/form/useFormField.ts b/src/components/ui/form/useFormField.ts new file mode 100644 index 0000000..ed30a8a --- /dev/null +++ b/src/components/ui/form/useFormField.ts @@ -0,0 +1,30 @@ +import { FieldContextKey, useFieldError, useIsFieldDirty, useIsFieldTouched, useIsFieldValid } from 'vee-validate' +import { inject } from 'vue' +import { FORM_ITEM_INJECTION_KEY } from './injectionKeys' + +export function useFormField() { + const fieldContext = inject(FieldContextKey) + const fieldItemContext = inject(FORM_ITEM_INJECTION_KEY) + + if (!fieldContext) + throw new Error('useFormField should be used within ') + + const { name } = fieldContext + const id = fieldItemContext + + const fieldState = { + valid: useIsFieldValid(name), + isDirty: useIsFieldDirty(name), + isTouched: useIsFieldTouched(name), + error: useFieldError(name), + } + + return { + id, + name, + formItemId: `${id}-form-item`, + formDescriptionId: `${id}-form-item-description`, + formMessageId: `${id}-form-item-message`, + ...fieldState, + } +} diff --git a/src/components/ui/popover/Popover.vue b/src/components/ui/popover/Popover.vue new file mode 100644 index 0000000..da5f709 --- /dev/null +++ b/src/components/ui/popover/Popover.vue @@ -0,0 +1,15 @@ + + + diff --git a/src/components/ui/popover/PopoverContent.vue b/src/components/ui/popover/PopoverContent.vue new file mode 100644 index 0000000..2800cfb --- /dev/null +++ b/src/components/ui/popover/PopoverContent.vue @@ -0,0 +1,48 @@ + + + diff --git a/src/components/ui/popover/PopoverTrigger.vue b/src/components/ui/popover/PopoverTrigger.vue new file mode 100644 index 0000000..22f4772 --- /dev/null +++ b/src/components/ui/popover/PopoverTrigger.vue @@ -0,0 +1,11 @@ + + + diff --git a/src/components/ui/popover/index.ts b/src/components/ui/popover/index.ts new file mode 100644 index 0000000..c621f9b --- /dev/null +++ b/src/components/ui/popover/index.ts @@ -0,0 +1,3 @@ +export { default as Popover } from './Popover.vue' +export { default as PopoverContent } from './PopoverContent.vue' +export { default as PopoverTrigger } from './PopoverTrigger.vue' diff --git a/src/components/ui/radio-group/RadioGroup.vue b/src/components/ui/radio-group/RadioGroup.vue new file mode 100644 index 0000000..d97d4f6 --- /dev/null +++ b/src/components/ui/radio-group/RadioGroup.vue @@ -0,0 +1,25 @@ + + + diff --git a/src/components/ui/radio-group/RadioGroupItem.vue b/src/components/ui/radio-group/RadioGroupItem.vue new file mode 100644 index 0000000..a36a5b6 --- /dev/null +++ b/src/components/ui/radio-group/RadioGroupItem.vue @@ -0,0 +1,39 @@ + + + diff --git a/src/components/ui/radio-group/index.ts b/src/components/ui/radio-group/index.ts new file mode 100644 index 0000000..fa1da9c --- /dev/null +++ b/src/components/ui/radio-group/index.ts @@ -0,0 +1,2 @@ +export { default as RadioGroup } from './RadioGroup.vue' +export { default as RadioGroupItem } from './RadioGroupItem.vue' diff --git a/src/components/ui/switch/Switch.vue b/src/components/ui/switch/Switch.vue new file mode 100644 index 0000000..35a412f --- /dev/null +++ b/src/components/ui/switch/Switch.vue @@ -0,0 +1,39 @@ + + + diff --git a/src/components/ui/switch/index.ts b/src/components/ui/switch/index.ts new file mode 100644 index 0000000..87b4b17 --- /dev/null +++ b/src/components/ui/switch/index.ts @@ -0,0 +1 @@ +export { default as Switch } from './Switch.vue' diff --git a/src/components/ui/textarea/Textarea.vue b/src/components/ui/textarea/Textarea.vue new file mode 100644 index 0000000..a6b182a --- /dev/null +++ b/src/components/ui/textarea/Textarea.vue @@ -0,0 +1,24 @@ + + +