diff --git a/test-frontend/package.json b/test-frontend/package.json
index 375a91e..dbb8259 100644
--- a/test-frontend/package.json
+++ b/test-frontend/package.json
@@ -10,6 +10,8 @@
},
"dependencies": {
"@tanstack/vue-table": "^8.16.0",
+ "@vee-validate/zod": "^4.12.7",
+ "@vueuse/core": "^10.9.0",
"axios": "^1.6.8",
"class-variance-authority": "^0.7.0",
"clsx": "^2.1.1",
@@ -17,7 +19,9 @@
"radix-vue": "^1.7.4",
"tailwind-merge": "^2.3.0",
"tailwindcss-animate": "^1.0.7",
- "vue": "^3.4.21"
+ "vee-validate": "^4.12.7",
+ "vue": "^3.4.21",
+ "zod": "^3.23.7"
},
"devDependencies": {
"@types/node": "^20.12.11",
diff --git a/test-frontend/pnpm-lock.yaml b/test-frontend/pnpm-lock.yaml
index fee62b3..31e048c 100644
--- a/test-frontend/pnpm-lock.yaml
+++ b/test-frontend/pnpm-lock.yaml
@@ -11,6 +11,12 @@ importers:
'@tanstack/vue-table':
specifier: ^8.16.0
version: 8.16.0(vue@3.4.27(typescript@5.4.5))
+ '@vee-validate/zod':
+ specifier: ^4.12.7
+ version: 4.12.7(vue@3.4.27(typescript@5.4.5))
+ '@vueuse/core':
+ specifier: ^10.9.0
+ version: 10.9.0(vue@3.4.27(typescript@5.4.5))
axios:
specifier: ^1.6.8
version: 1.6.8
@@ -32,9 +38,15 @@ importers:
tailwindcss-animate:
specifier: ^1.0.7
version: 1.0.7(tailwindcss@3.4.3)
+ vee-validate:
+ specifier: ^4.12.7
+ version: 4.12.7(vue@3.4.27(typescript@5.4.5))
vue:
specifier: ^3.4.21
version: 3.4.27(typescript@5.4.5)
+ zod:
+ specifier: ^3.23.7
+ version: 3.23.7
devDependencies:
'@types/node':
specifier: ^20.12.11
@@ -386,6 +398,9 @@ packages:
'@types/web-bluetooth@0.0.20':
resolution: {integrity: sha512-g9gZnnXVq7gM7v3tJCWV/qw7w+KeOlSHAhgF9RytFyifW6AF61hdT2ucrYhPq9hLs5JIryeupHV3qGk95dH9ow==}
+ '@vee-validate/zod@4.12.7':
+ resolution: {integrity: sha512-4VPly2tu9xpPP1onmDBOQPKAyivJ6j+1JF5YAXAf2ofoxfPAW2y8mBwe0zsKE1TAI8xyT9nkb2oWNzX1HrlPVw==}
+
'@vitejs/plugin-vue@5.0.4':
resolution: {integrity: sha512-WS3hevEszI6CEVEx28F8RjTX97k3KsrcY6kvTg7+Whm5y3oYvcqzVeGCU3hxSAn4uY2CLCkeokkGKpoctccilQ==}
engines: {node: ^18.0.0 || >=20.0.0}
@@ -414,6 +429,9 @@ packages:
'@vue/compiler-ssr@3.4.27':
resolution: {integrity: sha512-CVRzSJIltzMG5FcidsW0jKNQnNRYC8bT21VegyMMtHmhW3UOI7knmUehzswXLrExDLE6lQCZdrhD4ogI7c+vuw==}
+ '@vue/devtools-api@6.6.1':
+ resolution: {integrity: sha512-LgPscpE3Vs0x96PzSSB4IGVSZXZBZHpfxs+ZA1d+VEPwHdOXowy/Y2CsvCAIFrf+ssVU1pD1jidj505EpUnfbA==}
+
'@vue/language-core@2.0.16':
resolution: {integrity: sha512-Bc2sexRH99pznOph8mLw2BlRZ9edm7tW51kcBXgx8adAoOcZUWJj3UNSsdQ6H9Y8meGz7BoazVrVo/jUukIsPw==}
peerDependencies:
@@ -972,6 +990,10 @@ packages:
tslib@2.6.2:
resolution: {integrity: sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q==}
+ type-fest@4.18.2:
+ resolution: {integrity: sha512-+suCYpfJLAe4OXS6+PPXjW3urOS4IoP9waSiLuXfLgqZODKw/aWwASvzqE886wA0kQgGy0mIWyhd87VpqIy6Xg==}
+ engines: {node: '>=16'}
+
typescript@5.4.5:
resolution: {integrity: sha512-vcI4UpRgg81oIRUFwR0WSIHKt11nJ7SAVlYNIu+QpqeyXP+gpQJy/Z4+F0aGxSE4MqwjyXvW/TzgkLAx2AGHwQ==}
engines: {node: '>=14.17'}
@@ -989,6 +1011,11 @@ packages:
util-deprecate@1.0.2:
resolution: {integrity: sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==}
+ vee-validate@4.12.7:
+ resolution: {integrity: sha512-1BGql4XNu/3TqHFjBLV6OrZ4fRteHXxRc9tLF5Q40IgIo1cwYEyaccC1AL3tdFUr2E3JsYhXbiEExoUSy4C9nA==}
+ peerDependencies:
+ vue: ^3.4.26
+
vite@5.2.11:
resolution: {integrity: sha512-HndV31LWW05i1BLPMUCE1B9E9GFbOu1MbenhS58FuK6owSO5qHm7GiCotrNY1YE5rMeQSFBGmT5ZaLEjFizgiQ==}
engines: {node: ^18.0.0 || >=20.0.0}
@@ -1063,6 +1090,9 @@ packages:
engines: {node: '>= 14'}
hasBin: true
+ zod@3.23.7:
+ resolution: {integrity: sha512-NBeIoqbtOiUMomACV/y+V3Qfs9+Okr18vR5c/5pHClPpufWOrsx8TENboDPe265lFdfewX2yBtNTLPvnmCxwog==}
+
snapshots:
'@alloc/quick-lru@5.2.0': {}
@@ -1293,6 +1323,14 @@ snapshots:
'@types/web-bluetooth@0.0.20': {}
+ '@vee-validate/zod@4.12.7(vue@3.4.27(typescript@5.4.5))':
+ dependencies:
+ type-fest: 4.18.2
+ vee-validate: 4.12.7(vue@3.4.27(typescript@5.4.5))
+ zod: 3.23.7
+ transitivePeerDependencies:
+ - vue
+
'@vitejs/plugin-vue@5.0.4(vite@5.2.11(@types/node@20.12.11))(vue@3.4.27(typescript@5.4.5))':
dependencies:
vite: 5.2.11(@types/node@20.12.11)
@@ -1341,6 +1379,8 @@ snapshots:
'@vue/compiler-dom': 3.4.27
'@vue/shared': 3.4.27
+ '@vue/devtools-api@6.6.1': {}
+
'@vue/language-core@2.0.16(typescript@5.4.5)':
dependencies:
'@volar/language-core': 2.2.1
@@ -1917,6 +1957,8 @@ snapshots:
tslib@2.6.2: {}
+ type-fest@4.18.2: {}
+
typescript@5.4.5: {}
undici-types@5.26.5: {}
@@ -1929,6 +1971,12 @@ snapshots:
util-deprecate@1.0.2: {}
+ vee-validate@4.12.7(vue@3.4.27(typescript@5.4.5)):
+ dependencies:
+ '@vue/devtools-api': 6.6.1
+ type-fest: 4.18.2
+ vue: 3.4.27(typescript@5.4.5)
+
vite@5.2.11(@types/node@20.12.11):
dependencies:
esbuild: 0.20.2
@@ -1981,3 +2029,5 @@ snapshots:
strip-ansi: 7.1.0
yaml@2.4.2: {}
+
+ zod@3.23.7: {}
diff --git a/test-frontend/src/components/ui/form/FormControl.vue b/test-frontend/src/components/ui/form/FormControl.vue
new file mode 100644
index 0000000..8459cab
--- /dev/null
+++ b/test-frontend/src/components/ui/form/FormControl.vue
@@ -0,0 +1,16 @@
+
+
+
+
+
+
+
diff --git a/test-frontend/src/components/ui/form/FormDescription.vue b/test-frontend/src/components/ui/form/FormDescription.vue
new file mode 100644
index 0000000..6085f76
--- /dev/null
+++ b/test-frontend/src/components/ui/form/FormDescription.vue
@@ -0,0 +1,20 @@
+
+
+
+
+
+
+
diff --git a/test-frontend/src/components/ui/form/FormItem.vue b/test-frontend/src/components/ui/form/FormItem.vue
new file mode 100644
index 0000000..ad120be
--- /dev/null
+++ b/test-frontend/src/components/ui/form/FormItem.vue
@@ -0,0 +1,25 @@
+
+
+
+
+
+
+
+
+
diff --git a/test-frontend/src/components/ui/form/FormLabel.vue b/test-frontend/src/components/ui/form/FormLabel.vue
new file mode 100644
index 0000000..73cf45b
--- /dev/null
+++ b/test-frontend/src/components/ui/form/FormLabel.vue
@@ -0,0 +1,23 @@
+
+
+
+
+
diff --git a/test-frontend/src/components/ui/form/FormMessage.vue b/test-frontend/src/components/ui/form/FormMessage.vue
new file mode 100644
index 0000000..308755e
--- /dev/null
+++ b/test-frontend/src/components/ui/form/FormMessage.vue
@@ -0,0 +1,16 @@
+
+
+
+
+
diff --git a/test-frontend/src/components/ui/form/index.ts b/test-frontend/src/components/ui/form/index.ts
new file mode 100644
index 0000000..30a30a6
--- /dev/null
+++ b/test-frontend/src/components/ui/form/index.ts
@@ -0,0 +1,6 @@
+export { Form, Field as FormField } from 'vee-validate'
+export { default as FormItem } from './FormItem.vue'
+export { default as FormLabel } from './FormLabel.vue'
+export { default as FormControl } from './FormControl.vue'
+export { default as FormMessage } from './FormMessage.vue'
+export { default as FormDescription } from './FormDescription.vue'
diff --git a/test-frontend/src/components/ui/form/useFormField.ts b/test-frontend/src/components/ui/form/useFormField.ts
new file mode 100644
index 0000000..73eeee3
--- /dev/null
+++ b/test-frontend/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 './FormItem.vue'
+
+export function useFormField() {
+ const fieldContext = inject(FieldContextKey)
+ const fieldItemContext = inject(FORM_ITEM_INJECTION_KEY)
+
+ const fieldState = {
+ valid: useIsFieldValid(),
+ isDirty: useIsFieldDirty(),
+ isTouched: useIsFieldTouched(),
+ error: useFieldError(),
+ }
+
+ if (!fieldContext)
+ throw new Error('useFormField should be used within ')
+
+ const { name } = fieldContext
+ const id = fieldItemContext
+
+ return {
+ id,
+ name,
+ formItemId: `${id}-form-item`,
+ formDescriptionId: `${id}-form-item-description`,
+ formMessageId: `${id}-form-item-message`,
+ ...fieldState,
+ }
+}
diff --git a/test-frontend/src/components/ui/input/Input.vue b/test-frontend/src/components/ui/input/Input.vue
new file mode 100644
index 0000000..39c9cee
--- /dev/null
+++ b/test-frontend/src/components/ui/input/Input.vue
@@ -0,0 +1,24 @@
+
+
+
+
+
diff --git a/test-frontend/src/components/ui/input/index.ts b/test-frontend/src/components/ui/input/index.ts
new file mode 100644
index 0000000..a691dd6
--- /dev/null
+++ b/test-frontend/src/components/ui/input/index.ts
@@ -0,0 +1 @@
+export { default as Input } from './Input.vue'
diff --git a/test-frontend/src/components/ui/label/Label.vue b/test-frontend/src/components/ui/label/Label.vue
new file mode 100644
index 0000000..8fba8db
--- /dev/null
+++ b/test-frontend/src/components/ui/label/Label.vue
@@ -0,0 +1,27 @@
+
+
+
+
+
diff --git a/test-frontend/src/components/ui/label/index.ts b/test-frontend/src/components/ui/label/index.ts
new file mode 100644
index 0000000..572c2f0
--- /dev/null
+++ b/test-frontend/src/components/ui/label/index.ts
@@ -0,0 +1 @@
+export { default as Label } from './Label.vue'
diff --git a/test-frontend/src/components/ui/select/Select.vue b/test-frontend/src/components/ui/select/Select.vue
new file mode 100644
index 0000000..adc42fd
--- /dev/null
+++ b/test-frontend/src/components/ui/select/Select.vue
@@ -0,0 +1,15 @@
+
+
+
+
+
+
+
diff --git a/test-frontend/src/components/ui/select/SelectContent.vue b/test-frontend/src/components/ui/select/SelectContent.vue
new file mode 100644
index 0000000..4fe234b
--- /dev/null
+++ b/test-frontend/src/components/ui/select/SelectContent.vue
@@ -0,0 +1,53 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/test-frontend/src/components/ui/select/SelectGroup.vue b/test-frontend/src/components/ui/select/SelectGroup.vue
new file mode 100644
index 0000000..407d8ad
--- /dev/null
+++ b/test-frontend/src/components/ui/select/SelectGroup.vue
@@ -0,0 +1,19 @@
+
+
+
+
+
+
+
diff --git a/test-frontend/src/components/ui/select/SelectItem.vue b/test-frontend/src/components/ui/select/SelectItem.vue
new file mode 100644
index 0000000..b102a81
--- /dev/null
+++ b/test-frontend/src/components/ui/select/SelectItem.vue
@@ -0,0 +1,44 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/test-frontend/src/components/ui/select/SelectItemText.vue b/test-frontend/src/components/ui/select/SelectItemText.vue
new file mode 100644
index 0000000..a0bb5c2
--- /dev/null
+++ b/test-frontend/src/components/ui/select/SelectItemText.vue
@@ -0,0 +1,11 @@
+
+
+
+
+
+
+
diff --git a/test-frontend/src/components/ui/select/SelectLabel.vue b/test-frontend/src/components/ui/select/SelectLabel.vue
new file mode 100644
index 0000000..3d45cdb
--- /dev/null
+++ b/test-frontend/src/components/ui/select/SelectLabel.vue
@@ -0,0 +1,13 @@
+
+
+
+
+
+
+
diff --git a/test-frontend/src/components/ui/select/SelectScrollDownButton.vue b/test-frontend/src/components/ui/select/SelectScrollDownButton.vue
new file mode 100644
index 0000000..54b6c6a
--- /dev/null
+++ b/test-frontend/src/components/ui/select/SelectScrollDownButton.vue
@@ -0,0 +1,24 @@
+
+
+
+
+
+
+
+
+
diff --git a/test-frontend/src/components/ui/select/SelectScrollUpButton.vue b/test-frontend/src/components/ui/select/SelectScrollUpButton.vue
new file mode 100644
index 0000000..5535f1c
--- /dev/null
+++ b/test-frontend/src/components/ui/select/SelectScrollUpButton.vue
@@ -0,0 +1,24 @@
+
+
+
+
+
+
+
+
+
diff --git a/test-frontend/src/components/ui/select/SelectSeparator.vue b/test-frontend/src/components/ui/select/SelectSeparator.vue
new file mode 100644
index 0000000..5ae593d
--- /dev/null
+++ b/test-frontend/src/components/ui/select/SelectSeparator.vue
@@ -0,0 +1,17 @@
+
+
+
+
+
diff --git a/test-frontend/src/components/ui/select/SelectTrigger.vue b/test-frontend/src/components/ui/select/SelectTrigger.vue
new file mode 100644
index 0000000..cfac8eb
--- /dev/null
+++ b/test-frontend/src/components/ui/select/SelectTrigger.vue
@@ -0,0 +1,31 @@
+
+
+
+
+
+
+
+
+
+
diff --git a/test-frontend/src/components/ui/select/SelectValue.vue b/test-frontend/src/components/ui/select/SelectValue.vue
new file mode 100644
index 0000000..4bc37dd
--- /dev/null
+++ b/test-frontend/src/components/ui/select/SelectValue.vue
@@ -0,0 +1,11 @@
+
+
+
+
+
+
+
diff --git a/test-frontend/src/components/ui/select/index.ts b/test-frontend/src/components/ui/select/index.ts
new file mode 100644
index 0000000..b1d89ee
--- /dev/null
+++ b/test-frontend/src/components/ui/select/index.ts
@@ -0,0 +1,11 @@
+export { default as Select } from './Select.vue'
+export { default as SelectValue } from './SelectValue.vue'
+export { default as SelectTrigger } from './SelectTrigger.vue'
+export { default as SelectContent } from './SelectContent.vue'
+export { default as SelectGroup } from './SelectGroup.vue'
+export { default as SelectItem } from './SelectItem.vue'
+export { default as SelectItemText } from './SelectItemText.vue'
+export { default as SelectLabel } from './SelectLabel.vue'
+export { default as SelectSeparator } from './SelectSeparator.vue'
+export { default as SelectScrollUpButton } from './SelectScrollUpButton.vue'
+export { default as SelectScrollDownButton } from './SelectScrollDownButton.vue'