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 @@
+
+
+
+
+
+ Output
+
+
+
+
+
+
+
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 @@
+
+
+
+
+
+
+
+
+
+
+ {{ schema?.description || beautifyObjectName(fieldName) }}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
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 @@
+
+
+
+
+
+
+
+
+
+
+
+
+ {{ config?.label || beautifyObjectName(label ?? fieldName) }}
+
+
+
+
+ {{ config.description }}
+
+
+
+
+
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 @@
+
+
+
+
+
+
+ {{ config?.label || beautifyObjectName(label ?? fieldName) }}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {{ config.description }}
+
+
+
+
+
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 @@
+
+
+
+
+
+
+ {{ config?.label || beautifyObjectName(label ?? fieldName) }}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {{ config.description }}
+
+
+
+
+
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 @@
+
+
+
+
+
+
+ {{ config?.label || beautifyObjectName(label ?? fieldName) }}
+
+
+
+ {
+ const file = (ev.target as HTMLInputElement).files?.[0]
+ inputFile = file
+ const parsed = await parseFileAsString(file)
+ slotProps.componentField.onInput(parsed)
+ }"
+ />
+
+
{{ inputFile?.name }}
+
+
+
+
+
+ {{ config.description }}
+
+
+
+
+
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 @@
+
+
+
+
+
+
+ {{ config?.label || beautifyObjectName(label ?? fieldName) }}
+
+
+
+
+
+
+
+ {{ config.description }}
+
+
+
+
+
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 @@
+
+
+
+
+
+
+ {{ config?.label || beautifyObjectName(label ?? fieldName) }}
+
+
+
+
+
+
+
+ {{ config.description }}
+
+
+
+
+
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 @@
+
+
+
+
+
+
+
+
+
+
+ {{ schema?.description || beautifyObjectName(fieldName) }}
+
+
+
+
+
+
+
+
+
+
+
+
+
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 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {{ day }}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
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 @@
+
+
+
+
+
+ {{ headingValue }}
+
+
+
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 @@
+
+
+
+
+
diff --git a/src/components/ui/textarea/index.ts b/src/components/ui/textarea/index.ts
new file mode 100644
index 0000000..6a7ab2a
--- /dev/null
+++ b/src/components/ui/textarea/index.ts
@@ -0,0 +1 @@
+export { default as Textarea } from './Textarea.vue'
diff --git a/src/pages/balloon/single.vue b/src/pages/balloon/single.vue
index bfa76ef..92704fd 100644
--- a/src/pages/balloon/single.vue
+++ b/src/pages/balloon/single.vue
@@ -7,6 +7,7 @@ meta:
-
-
-
-
-
-
-
-
-
-
-
-
- {{ selectedDate.match(/_(\d{8}T\d{6})/)?.[1] }}
- 这个时刻没有数据
-
-
+