added application form UI
This commit is contained in:
295
ui/package-lock.json
generated
295
ui/package-lock.json
generated
@@ -9,15 +9,20 @@
|
||||
"version": "0.0.0",
|
||||
"dependencies": {
|
||||
"@tailwindcss/vite": "^4.1.11",
|
||||
"@vee-validate/zod": "^4.15.1",
|
||||
"@vueuse/core": "^13.6.0",
|
||||
"class-variance-authority": "^0.7.1",
|
||||
"clsx": "^2.1.1",
|
||||
"lucide-vue-next": "^0.539.0",
|
||||
"pinia": "^3.0.3",
|
||||
"reka-ui": "^2.4.1",
|
||||
"tailwind-merge": "^3.3.1",
|
||||
"tailwindcss": "^4.1.11",
|
||||
"tw-animate-css": "^1.3.6",
|
||||
"vee-validate": "^4.15.1",
|
||||
"vue": "^3.5.18",
|
||||
"vue-router": "^4.5.1"
|
||||
"vue-router": "^4.5.1",
|
||||
"zod": "^3.25.76"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/node": "^24.2.1",
|
||||
@@ -913,6 +918,86 @@
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/@floating-ui/core": {
|
||||
"version": "1.7.3",
|
||||
"resolved": "https://registry.npmjs.org/@floating-ui/core/-/core-1.7.3.tgz",
|
||||
"integrity": "sha512-sGnvb5dmrJaKEZ+LDIpguvdX3bDlEllmv4/ClQ9awcmCZrlx5jQyyMWFM5kBI+EyNOCDDiKk8il0zeuX3Zlg/w==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@floating-ui/utils": "^0.2.10"
|
||||
}
|
||||
},
|
||||
"node_modules/@floating-ui/dom": {
|
||||
"version": "1.7.3",
|
||||
"resolved": "https://registry.npmjs.org/@floating-ui/dom/-/dom-1.7.3.tgz",
|
||||
"integrity": "sha512-uZA413QEpNuhtb3/iIKoYMSK07keHPYeXF02Zhd6e213j+d1NamLix/mCLxBUDW/Gx52sPH2m+chlUsyaBs/Ag==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@floating-ui/core": "^1.7.3",
|
||||
"@floating-ui/utils": "^0.2.10"
|
||||
}
|
||||
},
|
||||
"node_modules/@floating-ui/utils": {
|
||||
"version": "0.2.10",
|
||||
"resolved": "https://registry.npmjs.org/@floating-ui/utils/-/utils-0.2.10.tgz",
|
||||
"integrity": "sha512-aGTxbpbg8/b5JfU1HXSrbH3wXZuLPJcNEcZQFMxLs3oSzgtVu6nFPkbbGGUvBcUjKV2YyB9Wxxabo+HEH9tcRQ==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/@floating-ui/vue": {
|
||||
"version": "1.1.8",
|
||||
"resolved": "https://registry.npmjs.org/@floating-ui/vue/-/vue-1.1.8.tgz",
|
||||
"integrity": "sha512-SNJAa1jbT8Gh1LvWw2uIIViLL0saV2bCY59ISCvJzhbut5DSb2H3LKUK49Xkd7SixTNHKX4LFu59nbwIXt9jjQ==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@floating-ui/dom": "^1.7.3",
|
||||
"@floating-ui/utils": "^0.2.10",
|
||||
"vue-demi": ">=0.13.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@floating-ui/vue/node_modules/vue-demi": {
|
||||
"version": "0.14.10",
|
||||
"resolved": "https://registry.npmjs.org/vue-demi/-/vue-demi-0.14.10.tgz",
|
||||
"integrity": "sha512-nMZBOwuzabUO0nLgIcc6rycZEebF6eeUfaiQx9+WSk8e29IbLvPU9feI6tqW4kTo3hvoYAJkMh8n8D0fuISphg==",
|
||||
"hasInstallScript": true,
|
||||
"license": "MIT",
|
||||
"bin": {
|
||||
"vue-demi-fix": "bin/vue-demi-fix.js",
|
||||
"vue-demi-switch": "bin/vue-demi-switch.js"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/antfu"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@vue/composition-api": "^1.0.0-rc.1",
|
||||
"vue": "^3.0.0-0 || ^2.6.0"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"@vue/composition-api": {
|
||||
"optional": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/@internationalized/date": {
|
||||
"version": "3.8.2",
|
||||
"resolved": "https://registry.npmjs.org/@internationalized/date/-/date-3.8.2.tgz",
|
||||
"integrity": "sha512-/wENk7CbvLbkUvX1tu0mwq49CVkkWpkXubGel6birjRPyo6uQ4nQpnq5xZu823zRCwwn82zgHrvgF1vZyvmVgA==",
|
||||
"license": "Apache-2.0",
|
||||
"dependencies": {
|
||||
"@swc/helpers": "^0.5.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@internationalized/number": {
|
||||
"version": "3.6.4",
|
||||
"resolved": "https://registry.npmjs.org/@internationalized/number/-/number-3.6.4.tgz",
|
||||
"integrity": "sha512-P+/h+RDaiX8EGt3shB9AYM1+QgkvHmJ5rKi4/59k4sg9g58k9rqsRW0WxRO7jCoHyvVbFRRFKmVTdFYdehrxHg==",
|
||||
"license": "Apache-2.0",
|
||||
"dependencies": {
|
||||
"@swc/helpers": "^0.5.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@isaacs/fs-minipass": {
|
||||
"version": "4.0.1",
|
||||
"resolved": "https://registry.npmjs.org/@isaacs/fs-minipass/-/fs-minipass-4.0.1.tgz",
|
||||
@@ -1254,6 +1339,15 @@
|
||||
"url": "https://github.com/sponsors/sindresorhus"
|
||||
}
|
||||
},
|
||||
"node_modules/@swc/helpers": {
|
||||
"version": "0.5.17",
|
||||
"resolved": "https://registry.npmjs.org/@swc/helpers/-/helpers-0.5.17.tgz",
|
||||
"integrity": "sha512-5IKx/Y13RsYd+sauPb2x+U/xZikHjolzfuDgTAl/Tdf3Q8rslRvC19NKDLgAJQ6wsqADk10ntlv08nPFw/gO/A==",
|
||||
"license": "Apache-2.0",
|
||||
"dependencies": {
|
||||
"tslib": "^2.8.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@tailwindcss/node": {
|
||||
"version": "4.1.11",
|
||||
"resolved": "https://registry.npmjs.org/@tailwindcss/node/-/node-4.1.11.tgz",
|
||||
@@ -1516,6 +1610,32 @@
|
||||
"vite": "^5.2.0 || ^6 || ^7"
|
||||
}
|
||||
},
|
||||
"node_modules/@tanstack/virtual-core": {
|
||||
"version": "3.13.12",
|
||||
"resolved": "https://registry.npmjs.org/@tanstack/virtual-core/-/virtual-core-3.13.12.tgz",
|
||||
"integrity": "sha512-1YBOJfRHV4sXUmWsFSf5rQor4Ss82G8dQWLRbnk3GA4jeP8hQt1hxXh0tmflpC0dz3VgEv/1+qwPyLeWkQuPFA==",
|
||||
"license": "MIT",
|
||||
"funding": {
|
||||
"type": "github",
|
||||
"url": "https://github.com/sponsors/tannerlinsley"
|
||||
}
|
||||
},
|
||||
"node_modules/@tanstack/vue-virtual": {
|
||||
"version": "3.13.12",
|
||||
"resolved": "https://registry.npmjs.org/@tanstack/vue-virtual/-/vue-virtual-3.13.12.tgz",
|
||||
"integrity": "sha512-vhF7kEU9EXWXh+HdAwKJ2m3xaOnTTmgcdXcF2pim8g4GvI7eRrk2YRuV5nUlZnd/NbCIX4/Ja2OZu5EjJL06Ww==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@tanstack/virtual-core": "3.13.12"
|
||||
},
|
||||
"funding": {
|
||||
"type": "github",
|
||||
"url": "https://github.com/sponsors/tannerlinsley"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"vue": "^2.7.0 || ^3.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@types/estree": {
|
||||
"version": "1.0.8",
|
||||
"resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.8.tgz",
|
||||
@@ -1532,6 +1652,25 @@
|
||||
"undici-types": "~7.10.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@types/web-bluetooth": {
|
||||
"version": "0.0.21",
|
||||
"resolved": "https://registry.npmjs.org/@types/web-bluetooth/-/web-bluetooth-0.0.21.tgz",
|
||||
"integrity": "sha512-oIQLCGWtcFZy2JW77j9k8nHzAOpqMHLQejDA48XXMWH6tjCQHz5RCFz1bzsmROyL6PUm+LLnUiI4BCn221inxA==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/@vee-validate/zod": {
|
||||
"version": "4.15.1",
|
||||
"resolved": "https://registry.npmjs.org/@vee-validate/zod/-/zod-4.15.1.tgz",
|
||||
"integrity": "sha512-329Z4TDBE5Vx0FdbA8S4eR9iGCFFUNGbxjpQ20ff5b5wGueScjocUIx9JHPa79LTG06RnlUR4XogQsjN4tecKA==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"type-fest": "^4.8.3",
|
||||
"vee-validate": "4.15.1"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"zod": "^3.24.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@vitejs/plugin-vue": {
|
||||
"version": "6.0.1",
|
||||
"resolved": "https://registry.npmjs.org/@vitejs/plugin-vue/-/plugin-vue-6.0.1.tgz",
|
||||
@@ -1798,6 +1937,44 @@
|
||||
"integrity": "sha512-cZy8Dq+uuIXbxCZpuLd2GJdeSO/lIzIspC2WtkqIpje5QyFbvLaI5wZtdUjLHjGZrlVX6GilejatWwVYYRc8tA==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/@vueuse/core": {
|
||||
"version": "13.6.0",
|
||||
"resolved": "https://registry.npmjs.org/@vueuse/core/-/core-13.6.0.tgz",
|
||||
"integrity": "sha512-DJbD5fV86muVmBgS9QQPddVX7d9hWYswzlf4bIyUD2dj8GC46R1uNClZhVAmsdVts4xb2jwp1PbpuiA50Qee1A==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@types/web-bluetooth": "^0.0.21",
|
||||
"@vueuse/metadata": "13.6.0",
|
||||
"@vueuse/shared": "13.6.0"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/antfu"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"vue": "^3.5.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@vueuse/metadata": {
|
||||
"version": "13.6.0",
|
||||
"resolved": "https://registry.npmjs.org/@vueuse/metadata/-/metadata-13.6.0.tgz",
|
||||
"integrity": "sha512-rnIH7JvU7NjrpexTsl2Iwv0V0yAx9cw7+clymjKuLSXG0QMcLD0LDgdNmXic+qL0SGvgSVPEpM9IDO/wqo1vkQ==",
|
||||
"license": "MIT",
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/antfu"
|
||||
}
|
||||
},
|
||||
"node_modules/@vueuse/shared": {
|
||||
"version": "13.6.0",
|
||||
"resolved": "https://registry.npmjs.org/@vueuse/shared/-/shared-13.6.0.tgz",
|
||||
"integrity": "sha512-pDykCSoS2T3fsQrYqf9SyF0QXWHmcGPQ+qiOVjlYSzlWd9dgppB2bFSM1GgKKkt7uzn0BBMV3IbJsUfHG2+BCg==",
|
||||
"license": "MIT",
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/antfu"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"vue": "^3.5.0"
|
||||
}
|
||||
},
|
||||
"node_modules/ansis": {
|
||||
"version": "4.1.0",
|
||||
"resolved": "https://registry.npmjs.org/ansis/-/ansis-4.1.0.tgz",
|
||||
@@ -1808,6 +1985,18 @@
|
||||
"node": ">=14"
|
||||
}
|
||||
},
|
||||
"node_modules/aria-hidden": {
|
||||
"version": "1.2.6",
|
||||
"resolved": "https://registry.npmjs.org/aria-hidden/-/aria-hidden-1.2.6.tgz",
|
||||
"integrity": "sha512-ik3ZgC9dY/lYVVM++OISsaYDeg1tb0VtP5uL3ouh1koGOaUMDPpbFIei4JkFimWUFPn90sbMNMXQAIVOlnYKJA==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"tslib": "^2.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=10"
|
||||
}
|
||||
},
|
||||
"node_modules/birpc": {
|
||||
"version": "2.5.0",
|
||||
"resolved": "https://registry.npmjs.org/birpc/-/birpc-2.5.0.tgz",
|
||||
@@ -2021,6 +2210,12 @@
|
||||
"url": "https://github.com/sponsors/sindresorhus"
|
||||
}
|
||||
},
|
||||
"node_modules/defu": {
|
||||
"version": "6.1.4",
|
||||
"resolved": "https://registry.npmjs.org/defu/-/defu-6.1.4.tgz",
|
||||
"integrity": "sha512-mEQCMmwJu317oSz8CwdIOdwf3xMif1ttiM8LTufzc3g6kR+9Pe236twL8j3IYT1F7GfRgGcW6MWxzZjLIkuHIg==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/detect-libc": {
|
||||
"version": "2.0.4",
|
||||
"resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.0.4.tgz",
|
||||
@@ -2781,7 +2976,6 @@
|
||||
"version": "2.0.11",
|
||||
"resolved": "https://registry.npmjs.org/ohash/-/ohash-2.0.11.tgz",
|
||||
"integrity": "sha512-RdR9FQrFwNBNXAr4GixM8YaRZRJ5PUWbKYbE5eOsrwAjJW0q2REGcf79oYPsLyskQCZG1PLN+S/K1V00joZAoQ==",
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/open": {
|
||||
@@ -2922,6 +3116,63 @@
|
||||
"url": "https://github.com/sponsors/sindresorhus"
|
||||
}
|
||||
},
|
||||
"node_modules/reka-ui": {
|
||||
"version": "2.4.1",
|
||||
"resolved": "https://registry.npmjs.org/reka-ui/-/reka-ui-2.4.1.tgz",
|
||||
"integrity": "sha512-NB7DrCsODN8MH02BWtgiExygfFcuuZ5/PTn6fMgjppmFHqePvNhmSn1LEuF35nel6PFbA4v+gdj0IoGN1yZ+vw==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@floating-ui/dom": "^1.6.13",
|
||||
"@floating-ui/vue": "^1.1.6",
|
||||
"@internationalized/date": "^3.5.0",
|
||||
"@internationalized/number": "^3.5.0",
|
||||
"@tanstack/vue-virtual": "^3.12.0",
|
||||
"@vueuse/core": "^12.5.0",
|
||||
"@vueuse/shared": "^12.5.0",
|
||||
"aria-hidden": "^1.2.4",
|
||||
"defu": "^6.1.4",
|
||||
"ohash": "^2.0.11"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"vue": ">= 3.2.0"
|
||||
}
|
||||
},
|
||||
"node_modules/reka-ui/node_modules/@vueuse/core": {
|
||||
"version": "12.8.2",
|
||||
"resolved": "https://registry.npmjs.org/@vueuse/core/-/core-12.8.2.tgz",
|
||||
"integrity": "sha512-HbvCmZdzAu3VGi/pWYm5Ut+Kd9mn1ZHnn4L5G8kOQTPs/IwIAmJoBrmYk2ckLArgMXZj0AW3n5CAejLUO+PhdQ==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@types/web-bluetooth": "^0.0.21",
|
||||
"@vueuse/metadata": "12.8.2",
|
||||
"@vueuse/shared": "12.8.2",
|
||||
"vue": "^3.5.13"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/antfu"
|
||||
}
|
||||
},
|
||||
"node_modules/reka-ui/node_modules/@vueuse/metadata": {
|
||||
"version": "12.8.2",
|
||||
"resolved": "https://registry.npmjs.org/@vueuse/metadata/-/metadata-12.8.2.tgz",
|
||||
"integrity": "sha512-rAyLGEuoBJ/Il5AmFHiziCPdQzRt88VxR+Y/A/QhJ1EWtWqPBBAxTAFaSkviwEuOEZNtW8pvkPgoCZQ+HxqW1A==",
|
||||
"license": "MIT",
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/antfu"
|
||||
}
|
||||
},
|
||||
"node_modules/reka-ui/node_modules/@vueuse/shared": {
|
||||
"version": "12.8.2",
|
||||
"resolved": "https://registry.npmjs.org/@vueuse/shared/-/shared-12.8.2.tgz",
|
||||
"integrity": "sha512-dznP38YzxZoNloI0qpEfpkms8knDtaoQ6Y/sfS0L7Yki4zh40LFHEhur0odJC6xTHG5dxWVPiUWBXn+wCG2s5w==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"vue": "^3.5.13"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/antfu"
|
||||
}
|
||||
},
|
||||
"node_modules/rfdc": {
|
||||
"version": "1.4.1",
|
||||
"resolved": "https://registry.npmjs.org/rfdc/-/rfdc-1.4.1.tgz",
|
||||
@@ -3161,6 +3412,12 @@
|
||||
"node": ">=6"
|
||||
}
|
||||
},
|
||||
"node_modules/tslib": {
|
||||
"version": "2.8.1",
|
||||
"resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz",
|
||||
"integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==",
|
||||
"license": "0BSD"
|
||||
},
|
||||
"node_modules/tw-animate-css": {
|
||||
"version": "1.3.6",
|
||||
"resolved": "https://registry.npmjs.org/tw-animate-css/-/tw-animate-css-1.3.6.tgz",
|
||||
@@ -3170,6 +3427,18 @@
|
||||
"url": "https://github.com/sponsors/Wombosvideo"
|
||||
}
|
||||
},
|
||||
"node_modules/type-fest": {
|
||||
"version": "4.41.0",
|
||||
"resolved": "https://registry.npmjs.org/type-fest/-/type-fest-4.41.0.tgz",
|
||||
"integrity": "sha512-TeTSQ6H5YHvpqVwBRcnLDCBnDOHWYu7IvGbHT6N8AOymcr9PJGjc1GTtiWZTYg0NCgYwvnYWEkVChQAr9bjfwA==",
|
||||
"license": "(MIT OR CC0-1.0)",
|
||||
"engines": {
|
||||
"node": ">=16"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/sindresorhus"
|
||||
}
|
||||
},
|
||||
"node_modules/undici-types": {
|
||||
"version": "7.10.0",
|
||||
"resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.10.0.tgz",
|
||||
@@ -3238,6 +3507,19 @@
|
||||
"browserslist": ">= 4.21.0"
|
||||
}
|
||||
},
|
||||
"node_modules/vee-validate": {
|
||||
"version": "4.15.1",
|
||||
"resolved": "https://registry.npmjs.org/vee-validate/-/vee-validate-4.15.1.tgz",
|
||||
"integrity": "sha512-DkFsiTwEKau8VIxyZBGdO6tOudD+QoUBPuHj3e6QFqmbfCRj1ArmYWue9lEp6jLSWBIw4XPlDLjFIZNLdRAMSg==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@vue/devtools-api": "^7.5.2",
|
||||
"type-fest": "^4.8.3"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"vue": "^3.4.26"
|
||||
}
|
||||
},
|
||||
"node_modules/vite": {
|
||||
"version": "7.0.6",
|
||||
"resolved": "https://registry.npmjs.org/vite/-/vite-7.0.6.tgz",
|
||||
@@ -3536,6 +3818,15 @@
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/sindresorhus"
|
||||
}
|
||||
},
|
||||
"node_modules/zod": {
|
||||
"version": "3.25.76",
|
||||
"resolved": "https://registry.npmjs.org/zod/-/zod-3.25.76.tgz",
|
||||
"integrity": "sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ==",
|
||||
"license": "MIT",
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/colinhacks"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -13,15 +13,20 @@
|
||||
},
|
||||
"dependencies": {
|
||||
"@tailwindcss/vite": "^4.1.11",
|
||||
"@vee-validate/zod": "^4.15.1",
|
||||
"@vueuse/core": "^13.6.0",
|
||||
"class-variance-authority": "^0.7.1",
|
||||
"clsx": "^2.1.1",
|
||||
"lucide-vue-next": "^0.539.0",
|
||||
"pinia": "^3.0.3",
|
||||
"reka-ui": "^2.4.1",
|
||||
"tailwind-merge": "^3.3.1",
|
||||
"tailwindcss": "^4.1.11",
|
||||
"tw-animate-css": "^1.3.6",
|
||||
"vee-validate": "^4.15.1",
|
||||
"vue": "^3.5.18",
|
||||
"vue-router": "^4.5.1"
|
||||
"vue-router": "^4.5.1",
|
||||
"zod": "^3.25.76"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/node": "^24.2.1",
|
||||
|
||||
@@ -1,11 +1,21 @@
|
||||
<script setup></script>
|
||||
<script setup>
|
||||
import { RouterLink, RouterView } from 'vue-router';
|
||||
import Separator from './components/ui/separator/Separator.vue';
|
||||
import Application from './pages/ApplicationForm.vue';
|
||||
import Button from './components/ui/button/Button.vue';
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<h1>You did it!</h1>
|
||||
<p>
|
||||
Visit <a href="https://vuejs.org/" target="_blank" rel="noopener">vuejs.org</a> to read the
|
||||
documentation
|
||||
</p>
|
||||
<div>
|
||||
<div class="h-15 flex items-center justify-center gap-20">
|
||||
<Button variant="link">Link</Button>
|
||||
<Button variant="link">Link</Button>
|
||||
<Button variant="link">Link</Button>
|
||||
<Button variant="link">Link</Button>
|
||||
</div>
|
||||
<Separator></Separator>
|
||||
<Application></Application>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped></style>
|
||||
|
||||
@@ -1,102 +1,103 @@
|
||||
@import "tailwindcss";
|
||||
@import "tw-animate-css";
|
||||
|
||||
|
||||
@custom-variant dark (&:is(.dark *));
|
||||
|
||||
:root {
|
||||
--background: oklch(0.9911 0 0);
|
||||
--foreground: oklch(0.2046 0 0);
|
||||
--card: oklch(0.9911 0 0);
|
||||
--card-foreground: oklch(0.2046 0 0);
|
||||
--popover: oklch(0.9911 0 0);
|
||||
--popover-foreground: oklch(0.4386 0 0);
|
||||
--primary: oklch(1.0000 0 0);
|
||||
--primary-foreground: oklch(0.3709 0.0313 95.1202);
|
||||
--secondary: oklch(0.9940 0 0);
|
||||
--secondary-foreground: oklch(0.2046 0 0);
|
||||
--muted: oklch(0.9461 0 0);
|
||||
--muted-foreground: oklch(0.2435 0 0);
|
||||
--accent: oklch(0.9461 0 0);
|
||||
--accent-foreground: oklch(0.2435 0 0);
|
||||
--destructive: oklch(0.6704 0.2070 300.0793);
|
||||
--background: oklch(0.2046 0 0);
|
||||
--foreground: oklch(0.9219 0 0);
|
||||
--card: oklch(0.2686 0 0);
|
||||
--card-foreground: oklch(0.9219 0 0);
|
||||
--popover: oklch(0.2686 0 0);
|
||||
--popover-foreground: oklch(0.9219 0 0);
|
||||
--primary: oklch(0.7686 0.1647 70.0804);
|
||||
--primary-foreground: oklch(0 0 0);
|
||||
--secondary: oklch(0.2686 0 0);
|
||||
--secondary-foreground: oklch(0.9219 0 0);
|
||||
--muted: oklch(0.2686 0 0);
|
||||
--muted-foreground: oklch(0.7155 0 0);
|
||||
--accent: oklch(0.4732 0.1247 46.2007);
|
||||
--accent-foreground: oklch(0.9243 0.1151 95.7459);
|
||||
--destructive: oklch(0.6368 0.2078 25.3313);
|
||||
--destructive-foreground: oklch(1.0000 0 0);
|
||||
--border: oklch(0.9037 0 0);
|
||||
--input: oklch(0.9731 0 0);
|
||||
--ring: oklch(1.0000 0 0);
|
||||
--chart-1: oklch(1.0000 0 0);
|
||||
--chart-2: oklch(0.9936 0.0111 141.2643);
|
||||
--chart-3: oklch(1.0000 0 0);
|
||||
--chart-4: oklch(0.8317 0.1444 322.3270);
|
||||
--chart-5: oklch(0.9397 0.1746 103.9958);
|
||||
--sidebar: oklch(0.9911 0 0);
|
||||
--sidebar-foreground: oklch(0.5452 0 0);
|
||||
--sidebar-primary: oklch(1.0000 0 0);
|
||||
--sidebar-primary-foreground: oklch(0.3709 0.0313 95.1202);
|
||||
--sidebar-accent: oklch(0.9461 0 0);
|
||||
--sidebar-accent-foreground: oklch(0.2435 0 0);
|
||||
--sidebar-border: oklch(0.9037 0 0);
|
||||
--sidebar-ring: oklch(1.0000 0 0);
|
||||
--font-sans: Outfit, sans-serif;
|
||||
--font-serif: ui-serif, Georgia, Cambria, "Times New Roman", Times, serif;
|
||||
--font-mono: monospace;
|
||||
--radius: 0.5rem;
|
||||
--shadow-2xs: 0px 1px 3px 0px hsl(0 0% 0% / 0.09);
|
||||
--shadow-xs: 0px 1px 3px 0px hsl(0 0% 0% / 0.09);
|
||||
--shadow-sm: 0px 1px 3px 0px hsl(0 0% 0% / 0.17), 0px 1px 2px -1px hsl(0 0% 0% / 0.17);
|
||||
--shadow: 0px 1px 3px 0px hsl(0 0% 0% / 0.17), 0px 1px 2px -1px hsl(0 0% 0% / 0.17);
|
||||
--shadow-md: 0px 1px 3px 0px hsl(0 0% 0% / 0.17), 0px 2px 4px -1px hsl(0 0% 0% / 0.17);
|
||||
--shadow-lg: 0px 1px 3px 0px hsl(0 0% 0% / 0.17), 0px 4px 6px -1px hsl(0 0% 0% / 0.17);
|
||||
--shadow-xl: 0px 1px 3px 0px hsl(0 0% 0% / 0.17), 0px 8px 10px -1px hsl(0 0% 0% / 0.17);
|
||||
--shadow-2xl: 0px 1px 3px 0px hsl(0 0% 0% / 0.43);
|
||||
--tracking-normal: 0.025em;
|
||||
--spacing: 0.25rem;
|
||||
--border: oklch(0.3715 0 0);
|
||||
--input: oklch(0.3715 0 0);
|
||||
--ring: oklch(0.7686 0.1647 70.0804);
|
||||
--chart-1: oklch(0.8369 0.1644 84.4286);
|
||||
--chart-2: oklch(0.6658 0.1574 58.3183);
|
||||
--chart-3: oklch(0.4732 0.1247 46.2007);
|
||||
--chart-4: oklch(0.5553 0.1455 48.9975);
|
||||
--chart-5: oklch(0.4732 0.1247 46.2007);
|
||||
--sidebar: oklch(0.1684 0 0);
|
||||
--sidebar-foreground: oklch(0.9219 0 0);
|
||||
--sidebar-primary: oklch(0.7686 0.1647 70.0804);
|
||||
--sidebar-primary-foreground: oklch(1.0000 0 0);
|
||||
--sidebar-accent: oklch(0.4732 0.1247 46.2007);
|
||||
--sidebar-accent-foreground: oklch(0.9243 0.1151 95.7459);
|
||||
--sidebar-border: oklch(0.3715 0 0);
|
||||
--sidebar-ring: oklch(0.7686 0.1647 70.0804);
|
||||
--font-sans: Inter, sans-serif;
|
||||
--font-serif: Source Serif 4, serif;
|
||||
--font-mono: JetBrains Mono, monospace;
|
||||
--radius: 0.375rem;
|
||||
--shadow-2xs: 0px 4px 8px -1px hsl(0 0% 0% / 0.07);
|
||||
--shadow-xs: 0px 4px 8px -1px hsl(0 0% 0% / 0.07);
|
||||
--shadow-sm: 0px 4px 8px -1px hsl(0 0% 0% / 0.14), 0px 1px 2px -2px hsl(0 0% 0% / 0.14);
|
||||
--shadow: 0px 4px 8px -1px hsl(0 0% 0% / 0.14), 0px 1px 2px -2px hsl(0 0% 0% / 0.14);
|
||||
--shadow-md: 0px 4px 8px -1px hsl(0 0% 0% / 0.14), 0px 2px 4px -2px hsl(0 0% 0% / 0.14);
|
||||
--shadow-lg: 0px 4px 8px -1px hsl(0 0% 0% / 0.14), 0px 4px 6px -2px hsl(0 0% 0% / 0.14);
|
||||
--shadow-xl: 0px 4px 8px -1px hsl(0 0% 0% / 0.14), 0px 8px 10px -2px hsl(0 0% 0% / 0.14);
|
||||
--shadow-2xl: 0px 4px 8px -1px hsl(0 0% 0% / 0.35);
|
||||
}
|
||||
|
||||
.dark {
|
||||
--background: oklch(0.1822 0 0);
|
||||
--foreground: oklch(1.0000 0 0);
|
||||
--card: oklch(0.2046 0 0);
|
||||
--card-foreground: oklch(1.0000 0 0);
|
||||
--popover: oklch(0.2603 0 0);
|
||||
--popover-foreground: oklch(0.7348 0 0);
|
||||
--primary: oklch(0.6277 0.1287 94.1115);
|
||||
--primary-foreground: oklch(1.0000 0 0);
|
||||
--secondary: oklch(0.2603 0 0);
|
||||
--secondary-foreground: oklch(0.9851 0 0);
|
||||
--muted: oklch(0.2393 0 0);
|
||||
--muted-foreground: oklch(0.7122 0 0);
|
||||
--accent: oklch(0.3132 0 0);
|
||||
--accent-foreground: oklch(0.9851 0 0);
|
||||
--destructive: oklch(0.3712 0.2143 284.7713);
|
||||
--background: oklch(0.2046 0 0);
|
||||
--foreground: oklch(0.9219 0 0);
|
||||
--card: oklch(0.2686 0 0);
|
||||
--card-foreground: oklch(0.9219 0 0);
|
||||
--popover: oklch(0.2686 0 0);
|
||||
--popover-foreground: oklch(0.9219 0 0);
|
||||
--primary: oklch(0.7686 0.1647 70.0804);
|
||||
--primary-foreground: oklch(0 0 0);
|
||||
--secondary: oklch(0.2686 0 0);
|
||||
--secondary-foreground: oklch(0.9219 0 0);
|
||||
--muted: oklch(0.2686 0 0);
|
||||
--muted-foreground: oklch(0.7155 0 0);
|
||||
--accent: oklch(0.4732 0.1247 46.2007);
|
||||
--accent-foreground: oklch(0.9243 0.1151 95.7459);
|
||||
--destructive: oklch(0.6368 0.2078 25.3313);
|
||||
--destructive-foreground: oklch(1.0000 0 0);
|
||||
--border: oklch(0.2809 0 0);
|
||||
--input: oklch(0.2603 0 0);
|
||||
--ring: oklch(0.9786 0.0203 81.7829);
|
||||
--chart-1: oklch(0.9786 0.0203 81.7829);
|
||||
--chart-2: oklch(1.0000 0 0);
|
||||
--chart-3: oklch(1.0000 0 0);
|
||||
--chart-4: oklch(0.9312 0.0608 325.1964);
|
||||
--chart-5: oklch(0.9724 0.1085 114.3821);
|
||||
--sidebar: oklch(0.1822 0 0);
|
||||
--sidebar-foreground: oklch(0.6301 0 0);
|
||||
--sidebar-primary: oklch(0.6277 0.1287 94.1115);
|
||||
--border: oklch(0.3715 0 0);
|
||||
--input: oklch(0.3715 0 0);
|
||||
--ring: oklch(0.7686 0.1647 70.0804);
|
||||
--chart-1: oklch(0.8369 0.1644 84.4286);
|
||||
--chart-2: oklch(0.6658 0.1574 58.3183);
|
||||
--chart-3: oklch(0.4732 0.1247 46.2007);
|
||||
--chart-4: oklch(0.5553 0.1455 48.9975);
|
||||
--chart-5: oklch(0.4732 0.1247 46.2007);
|
||||
--sidebar: oklch(0.1684 0 0);
|
||||
--sidebar-foreground: oklch(0.9219 0 0);
|
||||
--sidebar-primary: oklch(0.7686 0.1647 70.0804);
|
||||
--sidebar-primary-foreground: oklch(1.0000 0 0);
|
||||
--sidebar-accent: oklch(0.3132 0 0);
|
||||
--sidebar-accent-foreground: oklch(0.9851 0 0);
|
||||
--sidebar-border: oklch(0.2809 0 0);
|
||||
--sidebar-ring: oklch(0.9786 0.0203 81.7829);
|
||||
--font-sans: Outfit, sans-serif;
|
||||
--font-serif: ui-serif, Georgia, Cambria, "Times New Roman", Times, serif;
|
||||
--font-mono: monospace;
|
||||
--radius: 0.5rem;
|
||||
--shadow-2xs: 0px 1px 3px 0px hsl(0 0% 0% / 0.09);
|
||||
--shadow-xs: 0px 1px 3px 0px hsl(0 0% 0% / 0.09);
|
||||
--shadow-sm: 0px 1px 3px 0px hsl(0 0% 0% / 0.17), 0px 1px 2px -1px hsl(0 0% 0% / 0.17);
|
||||
--shadow: 0px 1px 3px 0px hsl(0 0% 0% / 0.17), 0px 1px 2px -1px hsl(0 0% 0% / 0.17);
|
||||
--shadow-md: 0px 1px 3px 0px hsl(0 0% 0% / 0.17), 0px 2px 4px -1px hsl(0 0% 0% / 0.17);
|
||||
--shadow-lg: 0px 1px 3px 0px hsl(0 0% 0% / 0.17), 0px 4px 6px -1px hsl(0 0% 0% / 0.17);
|
||||
--shadow-xl: 0px 1px 3px 0px hsl(0 0% 0% / 0.17), 0px 8px 10px -1px hsl(0 0% 0% / 0.17);
|
||||
--shadow-2xl: 0px 1px 3px 0px hsl(0 0% 0% / 0.43);
|
||||
--sidebar-accent: oklch(0.4732 0.1247 46.2007);
|
||||
--sidebar-accent-foreground: oklch(0.9243 0.1151 95.7459);
|
||||
--sidebar-border: oklch(0.3715 0 0);
|
||||
--sidebar-ring: oklch(0.7686 0.1647 70.0804);
|
||||
--font-sans: Inter, sans-serif;
|
||||
--font-serif: Source Serif 4, serif;
|
||||
--font-mono: JetBrains Mono, monospace;
|
||||
--radius: 0.375rem;
|
||||
--shadow-2xs: 0px 4px 8px -1px hsl(0 0% 0% / 0.07);
|
||||
--shadow-xs: 0px 4px 8px -1px hsl(0 0% 0% / 0.07);
|
||||
--shadow-sm: 0px 4px 8px -1px hsl(0 0% 0% / 0.14), 0px 1px 2px -2px hsl(0 0% 0% / 0.14);
|
||||
--shadow: 0px 4px 8px -1px hsl(0 0% 0% / 0.14), 0px 1px 2px -2px hsl(0 0% 0% / 0.14);
|
||||
--shadow-md: 0px 4px 8px -1px hsl(0 0% 0% / 0.14), 0px 2px 4px -2px hsl(0 0% 0% / 0.14);
|
||||
--shadow-lg: 0px 4px 8px -1px hsl(0 0% 0% / 0.14), 0px 4px 6px -2px hsl(0 0% 0% / 0.14);
|
||||
--shadow-xl: 0px 4px 8px -1px hsl(0 0% 0% / 0.14), 0px 8px 10px -2px hsl(0 0% 0% / 0.14);
|
||||
--shadow-2xl: 0px 4px 8px -1px hsl(0 0% 0% / 0.35);
|
||||
}
|
||||
|
||||
|
||||
@theme inline {
|
||||
--color-background: var(--background);
|
||||
--color-foreground: var(--foreground);
|
||||
@@ -148,15 +149,13 @@
|
||||
--shadow-lg: var(--shadow-lg);
|
||||
--shadow-xl: var(--shadow-xl);
|
||||
--shadow-2xl: var(--shadow-2xl);
|
||||
|
||||
--tracking-tighter: calc(var(--tracking-normal) - 0.05em);
|
||||
--tracking-tight: calc(var(--tracking-normal) - 0.025em);
|
||||
--tracking-normal: var(--tracking-normal);
|
||||
--tracking-wide: calc(var(--tracking-normal) + 0.025em);
|
||||
--tracking-wider: calc(var(--tracking-normal) + 0.05em);
|
||||
--tracking-widest: calc(var(--tracking-normal) + 0.1em);
|
||||
}
|
||||
|
||||
@layer base {
|
||||
* {
|
||||
@apply border-border outline-ring/50;
|
||||
}
|
||||
body {
|
||||
letter-spacing: var(--tracking-normal);
|
||||
@apply bg-background text-foreground;
|
||||
}
|
||||
}
|
||||
24
ui/src/components/ui/button/Button.vue
Normal file
24
ui/src/components/ui/button/Button.vue
Normal file
@@ -0,0 +1,24 @@
|
||||
<script setup>
|
||||
import { Primitive } from "reka-ui";
|
||||
import { cn } from "@/lib/utils";
|
||||
import { buttonVariants } from ".";
|
||||
|
||||
const props = defineProps({
|
||||
variant: { type: null, required: false },
|
||||
size: { type: null, required: false },
|
||||
class: { type: null, required: false },
|
||||
asChild: { type: Boolean, required: false },
|
||||
as: { type: [String, Object, Function], required: false, default: "button" },
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<Primitive
|
||||
data-slot="button"
|
||||
:as="as"
|
||||
:as-child="asChild"
|
||||
:class="cn(buttonVariants({ variant, size }), props.class)"
|
||||
>
|
||||
<slot />
|
||||
</Primitive>
|
||||
</template>
|
||||
34
ui/src/components/ui/button/index.js
Normal file
34
ui/src/components/ui/button/index.js
Normal file
@@ -0,0 +1,34 @@
|
||||
import { cva } from "class-variance-authority";
|
||||
|
||||
export { default as Button } from "./Button.vue";
|
||||
|
||||
export const buttonVariants = cva(
|
||||
"inline-flex items-center justify-center gap-2 whitespace-nowrap rounded-md text-sm font-medium transition-all disabled:pointer-events-none disabled:opacity-50 [&_svg]:pointer-events-none [&_svg:not([class*='size-'])]:size-4 shrink-0 [&_svg]:shrink-0 outline-none focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:ring-[3px] aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive",
|
||||
{
|
||||
variants: {
|
||||
variant: {
|
||||
default:
|
||||
"bg-primary text-primary-foreground shadow-xs hover:bg-primary/90",
|
||||
destructive:
|
||||
"bg-destructive text-white shadow-xs hover:bg-destructive/90 focus-visible:ring-destructive/20 dark:focus-visible:ring-destructive/40 dark:bg-destructive/60",
|
||||
outline:
|
||||
"border bg-background shadow-xs hover:bg-accent hover:text-accent-foreground dark:bg-input/30 dark:border-input dark:hover:bg-input/50",
|
||||
secondary:
|
||||
"bg-secondary text-secondary-foreground shadow-xs hover:bg-secondary/80",
|
||||
ghost:
|
||||
"hover:bg-accent hover:text-accent-foreground dark:hover:bg-accent/50",
|
||||
link: "text-primary underline-offset-4 hover:underline",
|
||||
},
|
||||
size: {
|
||||
default: "h-9 px-4 py-2 has-[>svg]:px-3",
|
||||
sm: "h-8 rounded-md gap-1.5 px-3 has-[>svg]:px-2.5",
|
||||
lg: "h-10 rounded-md px-6 has-[>svg]:px-4",
|
||||
icon: "size-9",
|
||||
},
|
||||
},
|
||||
defaultVariants: {
|
||||
variant: "default",
|
||||
size: "default",
|
||||
},
|
||||
},
|
||||
);
|
||||
46
ui/src/components/ui/checkbox/Checkbox.vue
Normal file
46
ui/src/components/ui/checkbox/Checkbox.vue
Normal file
@@ -0,0 +1,46 @@
|
||||
<script setup>
|
||||
import { reactiveOmit } from "@vueuse/core";
|
||||
import { Check } from "lucide-vue-next";
|
||||
import { CheckboxIndicator, CheckboxRoot, useForwardPropsEmits } from "reka-ui";
|
||||
import { cn } from "@/lib/utils";
|
||||
|
||||
const props = defineProps({
|
||||
defaultValue: { type: [Boolean, String], required: false },
|
||||
modelValue: { type: [Boolean, String, null], required: false },
|
||||
disabled: { type: Boolean, required: false },
|
||||
value: { type: null, required: false },
|
||||
id: { type: String, required: false },
|
||||
asChild: { type: Boolean, required: false },
|
||||
as: { type: [String, Object, Function], required: false },
|
||||
name: { type: String, required: false },
|
||||
required: { type: Boolean, required: false },
|
||||
class: { type: null, required: false },
|
||||
});
|
||||
const emits = defineEmits(["update:modelValue"]);
|
||||
|
||||
const delegatedProps = reactiveOmit(props, "class");
|
||||
|
||||
const forwarded = useForwardPropsEmits(delegatedProps, emits);
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<CheckboxRoot
|
||||
data-slot="checkbox"
|
||||
v-bind="forwarded"
|
||||
:class="
|
||||
cn(
|
||||
'peer border-input data-[state=checked]:bg-primary data-[state=checked]:text-primary-foreground data-[state=checked]:border-primary focus-visible:border-ring focus-visible:ring-ring/50 aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive size-4 shrink-0 rounded-[4px] border shadow-xs transition-shadow outline-none focus-visible:ring-[3px] disabled:cursor-not-allowed disabled:opacity-50',
|
||||
props.class,
|
||||
)
|
||||
"
|
||||
>
|
||||
<CheckboxIndicator
|
||||
data-slot="checkbox-indicator"
|
||||
class="flex items-center justify-center text-current transition-none"
|
||||
>
|
||||
<slot>
|
||||
<Check class="size-3.5" />
|
||||
</slot>
|
||||
</CheckboxIndicator>
|
||||
</CheckboxRoot>
|
||||
</template>
|
||||
1
ui/src/components/ui/checkbox/index.js
Normal file
1
ui/src/components/ui/checkbox/index.js
Normal file
@@ -0,0 +1 @@
|
||||
export { default as Checkbox } from "./Checkbox.vue";
|
||||
19
ui/src/components/ui/form/FormControl.vue
Normal file
19
ui/src/components/ui/form/FormControl.vue
Normal file
@@ -0,0 +1,19 @@
|
||||
<script setup>
|
||||
import { Slot } from "reka-ui";
|
||||
import { useFormField } from "./useFormField";
|
||||
|
||||
const { error, formItemId, formDescriptionId, formMessageId } = useFormField();
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<Slot
|
||||
:id="formItemId"
|
||||
data-slot="form-control"
|
||||
:aria-describedby="
|
||||
!error ? `${formDescriptionId}` : `${formDescriptionId} ${formMessageId}`
|
||||
"
|
||||
:aria-invalid="!!error"
|
||||
>
|
||||
<slot />
|
||||
</Slot>
|
||||
</template>
|
||||
20
ui/src/components/ui/form/FormDescription.vue
Normal file
20
ui/src/components/ui/form/FormDescription.vue
Normal file
@@ -0,0 +1,20 @@
|
||||
<script setup>
|
||||
import { cn } from "@/lib/utils";
|
||||
import { useFormField } from "./useFormField";
|
||||
|
||||
const props = defineProps({
|
||||
class: { type: null, required: false },
|
||||
});
|
||||
|
||||
const { formDescriptionId } = useFormField();
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<p
|
||||
:id="formDescriptionId"
|
||||
data-slot="form-description"
|
||||
:class="cn('text-muted-foreground text-sm', props.class)"
|
||||
>
|
||||
<slot />
|
||||
</p>
|
||||
</template>
|
||||
19
ui/src/components/ui/form/FormItem.vue
Normal file
19
ui/src/components/ui/form/FormItem.vue
Normal file
@@ -0,0 +1,19 @@
|
||||
<script setup>
|
||||
import { useId } from "reka-ui";
|
||||
import { provide } from "vue";
|
||||
import { cn } from "@/lib/utils";
|
||||
import { FORM_ITEM_INJECTION_KEY } from "./injectionKeys";
|
||||
|
||||
const props = defineProps({
|
||||
class: { type: null, required: false },
|
||||
});
|
||||
|
||||
const id = useId();
|
||||
provide(FORM_ITEM_INJECTION_KEY, id);
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div data-slot="form-item" :class="cn('grid gap-2', props.class)">
|
||||
<slot />
|
||||
</div>
|
||||
</template>
|
||||
25
ui/src/components/ui/form/FormLabel.vue
Normal file
25
ui/src/components/ui/form/FormLabel.vue
Normal file
@@ -0,0 +1,25 @@
|
||||
<script setup>
|
||||
import { cn } from "@/lib/utils";
|
||||
import { Label } from '@/components/ui/label';
|
||||
import { useFormField } from "./useFormField";
|
||||
|
||||
const props = defineProps({
|
||||
for: { type: String, required: false },
|
||||
asChild: { type: Boolean, required: false },
|
||||
as: { type: [String, Object, Function], required: false },
|
||||
class: { type: null, required: false },
|
||||
});
|
||||
|
||||
const { error, formItemId } = useFormField();
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<Label
|
||||
data-slot="form-label"
|
||||
:data-error="!!error"
|
||||
:class="cn('data-[error=true]:text-destructive-foreground', props.class)"
|
||||
:for="formItemId"
|
||||
>
|
||||
<slot />
|
||||
</Label>
|
||||
</template>
|
||||
22
ui/src/components/ui/form/FormMessage.vue
Normal file
22
ui/src/components/ui/form/FormMessage.vue
Normal file
@@ -0,0 +1,22 @@
|
||||
<script setup>
|
||||
import { ErrorMessage } from "vee-validate";
|
||||
import { toValue } from "vue";
|
||||
import { cn } from "@/lib/utils";
|
||||
import { useFormField } from "./useFormField";
|
||||
|
||||
const props = defineProps({
|
||||
class: { type: null, required: false },
|
||||
});
|
||||
|
||||
const { name, formMessageId } = useFormField();
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<ErrorMessage
|
||||
:id="formMessageId"
|
||||
data-slot="form-message"
|
||||
as="p"
|
||||
:name="toValue(name)"
|
||||
:class="cn('text-destructive-foreground text-sm', props.class)"
|
||||
/>
|
||||
</template>
|
||||
11
ui/src/components/ui/form/index.js
Normal file
11
ui/src/components/ui/form/index.js
Normal file
@@ -0,0 +1,11 @@
|
||||
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,
|
||||
FieldArray as FormFieldArray,
|
||||
} from "vee-validate";
|
||||
1
ui/src/components/ui/form/injectionKeys.js
Normal file
1
ui/src/components/ui/form/injectionKeys.js
Normal file
@@ -0,0 +1 @@
|
||||
export const FORM_ITEM_INJECTION_KEY = Symbol();
|
||||
36
ui/src/components/ui/form/useFormField.js
Normal file
36
ui/src/components/ui/form/useFormField.js
Normal file
@@ -0,0 +1,36 @@
|
||||
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 <FormField>");
|
||||
|
||||
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,
|
||||
};
|
||||
}
|
||||
32
ui/src/components/ui/input/Input.vue
Normal file
32
ui/src/components/ui/input/Input.vue
Normal file
@@ -0,0 +1,32 @@
|
||||
<script setup>
|
||||
import { useVModel } from "@vueuse/core";
|
||||
import { cn } from "@/lib/utils";
|
||||
|
||||
const props = defineProps({
|
||||
defaultValue: { type: [String, Number], required: false },
|
||||
modelValue: { type: [String, Number], required: false },
|
||||
class: { type: null, required: false },
|
||||
});
|
||||
|
||||
const emits = defineEmits(["update:modelValue"]);
|
||||
|
||||
const modelValue = useVModel(props, "modelValue", emits, {
|
||||
passive: true,
|
||||
defaultValue: props.defaultValue,
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<input
|
||||
v-model="modelValue"
|
||||
data-slot="input"
|
||||
:class="
|
||||
cn(
|
||||
'file:text-foreground placeholder:text-muted-foreground selection:bg-primary selection:text-primary-foreground dark:bg-input/30 border-input flex h-9 w-full min-w-0 rounded-md border bg-transparent px-3 py-1 text-base shadow-xs transition-[color,box-shadow] outline-none file:inline-flex file:h-7 file:border-0 file:bg-transparent file:text-sm file:font-medium disabled:pointer-events-none disabled:cursor-not-allowed disabled:opacity-50 md:text-sm',
|
||||
'focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:ring-[3px]',
|
||||
'aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive',
|
||||
props.class,
|
||||
)
|
||||
"
|
||||
/>
|
||||
</template>
|
||||
1
ui/src/components/ui/input/index.js
Normal file
1
ui/src/components/ui/input/index.js
Normal file
@@ -0,0 +1 @@
|
||||
export { default as Input } from "./Input.vue";
|
||||
29
ui/src/components/ui/label/Label.vue
Normal file
29
ui/src/components/ui/label/Label.vue
Normal file
@@ -0,0 +1,29 @@
|
||||
<script setup>
|
||||
import { reactiveOmit } from "@vueuse/core";
|
||||
import { Label } from "reka-ui";
|
||||
import { cn } from "@/lib/utils";
|
||||
|
||||
const props = defineProps({
|
||||
for: { type: String, required: false },
|
||||
asChild: { type: Boolean, required: false },
|
||||
as: { type: [String, Object, Function], required: false },
|
||||
class: { type: null, required: false },
|
||||
});
|
||||
|
||||
const delegatedProps = reactiveOmit(props, "class");
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<Label
|
||||
data-slot="label"
|
||||
v-bind="delegatedProps"
|
||||
:class="
|
||||
cn(
|
||||
'flex items-center gap-2 text-sm leading-none font-medium select-none group-data-[disabled=true]:pointer-events-none group-data-[disabled=true]:opacity-50 peer-disabled:cursor-not-allowed peer-disabled:opacity-50',
|
||||
props.class,
|
||||
)
|
||||
"
|
||||
>
|
||||
<slot />
|
||||
</Label>
|
||||
</template>
|
||||
1
ui/src/components/ui/label/index.js
Normal file
1
ui/src/components/ui/label/index.js
Normal file
@@ -0,0 +1 @@
|
||||
export { default as Label } from "./Label.vue";
|
||||
28
ui/src/components/ui/separator/Separator.vue
Normal file
28
ui/src/components/ui/separator/Separator.vue
Normal file
@@ -0,0 +1,28 @@
|
||||
<script setup>
|
||||
import { reactiveOmit } from "@vueuse/core";
|
||||
import { Separator } from "reka-ui";
|
||||
import { cn } from "@/lib/utils";
|
||||
|
||||
const props = defineProps({
|
||||
orientation: { type: String, required: false, default: "horizontal" },
|
||||
decorative: { type: Boolean, required: false, default: true },
|
||||
asChild: { type: Boolean, required: false },
|
||||
as: { type: [String, Object, Function], required: false },
|
||||
class: { type: null, required: false },
|
||||
});
|
||||
|
||||
const delegatedProps = reactiveOmit(props, "class");
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<Separator
|
||||
data-slot="separator-root"
|
||||
v-bind="delegatedProps"
|
||||
:class="
|
||||
cn(
|
||||
`bg-border shrink-0 data-[orientation=horizontal]:h-px data-[orientation=horizontal]:w-full data-[orientation=vertical]:h-full data-[orientation=vertical]:w-px`,
|
||||
props.class,
|
||||
)
|
||||
"
|
||||
/>
|
||||
</template>
|
||||
1
ui/src/components/ui/separator/index.js
Normal file
1
ui/src/components/ui/separator/index.js
Normal file
@@ -0,0 +1 @@
|
||||
export { default as Separator } from "./Separator.vue";
|
||||
30
ui/src/components/ui/textarea/Textarea.vue
Normal file
30
ui/src/components/ui/textarea/Textarea.vue
Normal file
@@ -0,0 +1,30 @@
|
||||
<script setup>
|
||||
import { useVModel } from "@vueuse/core";
|
||||
import { cn } from "@/lib/utils";
|
||||
|
||||
const props = defineProps({
|
||||
class: { type: null, required: false },
|
||||
defaultValue: { type: [String, Number], required: false },
|
||||
modelValue: { type: [String, Number], required: false },
|
||||
});
|
||||
|
||||
const emits = defineEmits(["update:modelValue"]);
|
||||
|
||||
const modelValue = useVModel(props, "modelValue", emits, {
|
||||
passive: true,
|
||||
defaultValue: props.defaultValue,
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<textarea
|
||||
v-model="modelValue"
|
||||
data-slot="textarea"
|
||||
:class="
|
||||
cn(
|
||||
'border-input placeholder:text-muted-foreground focus-visible:border-ring focus-visible:ring-ring/50 aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive dark:bg-input/30 flex field-sizing-content min-h-16 w-full rounded-md border bg-transparent px-3 py-2 text-base shadow-xs transition-[color,box-shadow] outline-none focus-visible:ring-[3px] disabled:cursor-not-allowed disabled:opacity-50 md:text-sm',
|
||||
props.class,
|
||||
)
|
||||
"
|
||||
/>
|
||||
</template>
|
||||
1
ui/src/components/ui/textarea/index.js
Normal file
1
ui/src/components/ui/textarea/index.js
Normal file
@@ -0,0 +1 @@
|
||||
export { default as Textarea } from "./Textarea.vue";
|
||||
216
ui/src/pages/ApplicationForm.vue
Normal file
216
ui/src/pages/ApplicationForm.vue
Normal file
@@ -0,0 +1,216 @@
|
||||
<script setup lang="ts">
|
||||
import Button from '@/components/ui/button/Button.vue';
|
||||
import Checkbox from '@/components/ui/checkbox/Checkbox.vue';
|
||||
import {
|
||||
FormControl,
|
||||
FormDescription,
|
||||
FormField,
|
||||
FormItem,
|
||||
FormLabel,
|
||||
FormMessage,
|
||||
} from '@/components/ui/form'
|
||||
import Input from '@/components/ui/input/Input.vue';
|
||||
import Label from '@/components/ui/label/Label.vue';
|
||||
import Textarea from '@/components/ui/textarea/Textarea.vue';
|
||||
import { toTypedSchema } from '@vee-validate/zod';
|
||||
import { Form } from 'vee-validate';
|
||||
import * as z from 'zod';
|
||||
|
||||
const formSchema = toTypedSchema(z.object({
|
||||
age: z.coerce.number().min(0),
|
||||
name: z.string(),
|
||||
playtime: z.coerce.number().min(0),
|
||||
hobbies: z.string(),
|
||||
military: z.boolean(),
|
||||
communities: z.string(),
|
||||
joinReason: z.string(),
|
||||
milsimAttraction: z.string(),
|
||||
referral: z.string(),
|
||||
steamProfile: z.string(),
|
||||
timezone: z.string(),
|
||||
canAttendSaturday: z.boolean(),
|
||||
interests: z.string(),
|
||||
aknowledgeRules: z.boolean(),
|
||||
}))
|
||||
|
||||
function onSubmit(val) {
|
||||
console.log(val)
|
||||
}
|
||||
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<Form :validation-schema="formSchema" @submit="onSubmit" class="space-y-6 max-w-3xl mx-auto my-20">
|
||||
<!-- Age -->
|
||||
<FormField name="age" v-slot="{ componentField }">
|
||||
<FormItem>
|
||||
<FormLabel>What is your age?</FormLabel>
|
||||
<FormControl>
|
||||
<Input type="number" v-bind="componentField" />
|
||||
</FormControl>
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
</FormField>
|
||||
|
||||
<!-- Name -->
|
||||
<FormField name="name" v-slot="{ componentField }">
|
||||
<FormItem>
|
||||
<FormLabel>What name will you be going by within the community?</FormLabel>
|
||||
<FormDescription>This name must be consistent across platforms.</FormDescription>
|
||||
<FormControl>
|
||||
<Input v-bind="componentField" />
|
||||
</FormControl>
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
</FormField>
|
||||
|
||||
<!-- Playtime -->
|
||||
<FormField name="playtime" v-slot="{ componentField }">
|
||||
<FormItem>
|
||||
<FormLabel>How long have you played Arma 3 for (in hours)?</FormLabel>
|
||||
<FormControl>
|
||||
<Input type="number" v-bind="componentField" />
|
||||
</FormControl>
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
</FormField>
|
||||
|
||||
<!-- Hobbies -->
|
||||
<FormField name="hobbies" v-slot="{ componentField }">
|
||||
<FormItem>
|
||||
<FormLabel>What hobbies do you like to participate in outside of gaming?</FormLabel>
|
||||
<FormControl>
|
||||
<Textarea rows="4" class="resize-none" v-bind="componentField" />
|
||||
</FormControl>
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
</FormField>
|
||||
|
||||
<!-- Military (boolean) -->
|
||||
<FormField name="military" v-slot="{ value, handleChange }">
|
||||
<FormItem>
|
||||
<FormLabel>Have you ever served in the military?</FormLabel>
|
||||
<FormControl>
|
||||
<div class="flex items-center gap-2">
|
||||
<Checkbox :checked="value" @update:checked="handleChange" />
|
||||
<span>Yes (checked) / No (unchecked)</span>
|
||||
</div>
|
||||
</FormControl>
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
</FormField>
|
||||
|
||||
<!-- Other communities (freeform) -->
|
||||
<FormField name="communities" v-slot="{ componentField }">
|
||||
<FormItem>
|
||||
<FormLabel>Are you a part of any other communities? If so, which ones? If none, type "No"</FormLabel>
|
||||
<FormControl>
|
||||
<Input v-bind="componentField" />
|
||||
</FormControl>
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
</FormField>
|
||||
|
||||
<!-- Why join -->
|
||||
<FormField name="joinReason" v-slot="{ componentField }">
|
||||
<FormItem>
|
||||
<FormLabel>Why do you want to join our community?</FormLabel>
|
||||
<FormControl>
|
||||
<Textarea rows="4" class="resize-none" v-bind="componentField" />
|
||||
</FormControl>
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
</FormField>
|
||||
|
||||
<!-- Attraction to milsim -->
|
||||
<FormField name="milsimAttraction" v-slot="{ componentField }">
|
||||
<FormItem>
|
||||
<FormLabel>What attracts you to the Arma 3 milsim playstyle?</FormLabel>
|
||||
<FormControl>
|
||||
<Textarea rows="4" class="resize-none" v-bind="componentField" />
|
||||
</FormControl>
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
</FormField>
|
||||
|
||||
<!-- Referral (freeform) -->
|
||||
<FormField name="referral" v-slot="{ componentField }">
|
||||
<FormItem>
|
||||
<FormLabel>Where did you hear about us? (If another member, who?)</FormLabel>
|
||||
<FormControl>
|
||||
<Input placeholder="e.g., Reddit / Member: Alice" v-bind="componentField" />
|
||||
</FormControl>
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
</FormField>
|
||||
|
||||
<!-- Steam profile -->
|
||||
<FormField name="steamProfile" v-slot="{ componentField }">
|
||||
<FormItem>
|
||||
<FormLabel>Steam profile link</FormLabel>
|
||||
<FormDescription>
|
||||
Format: <code>https://steamcommunity.com/id/USER/</code> or
|
||||
<code>https://steamcommunity.com/profiles/STEAMID64/</code>
|
||||
</FormDescription>
|
||||
<FormControl>
|
||||
<Input type="url" placeholder="https://steamcommunity.com/profiles/7656119..." v-bind="componentField" />
|
||||
</FormControl>
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
</FormField>
|
||||
|
||||
<!-- Timezone -->
|
||||
<FormField name="timezone" v-slot="{ componentField }">
|
||||
<FormItem>
|
||||
<FormLabel>What time zone are you in?</FormLabel>
|
||||
<FormControl>
|
||||
<Input placeholder="e.g., AEST, EST, UTC+10" v-bind="componentField" />
|
||||
</FormControl>
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
</FormField>
|
||||
|
||||
<!-- Attendance (boolean) -->
|
||||
<FormField name="canAttendSaturday" v-slot="{ value, handleChange }">
|
||||
<FormItem>
|
||||
<FormLabel>Are you able to attend weekly operations Saturdays @ 7pm CST?</FormLabel>
|
||||
<FormControl>
|
||||
<div class="flex items-center gap-2">
|
||||
<Checkbox :checked="value" @update:checked="handleChange" />
|
||||
<span>Yes (checked) / No (unchecked)</span>
|
||||
</div>
|
||||
</FormControl>
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
</FormField>
|
||||
|
||||
<!-- Interests / Playstyle (freeform) -->
|
||||
<FormField name="interests" v-slot="{ componentField }">
|
||||
<FormItem>
|
||||
<FormLabel>Which playstyles interest you?</FormLabel>
|
||||
<FormControl>
|
||||
<Input placeholder="e.g., Rifleman; Medic; Pilot" v-bind="componentField" />
|
||||
</FormControl>
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
</FormField>
|
||||
|
||||
<!-- Code of Conduct (boolean, field name kept as-is) -->
|
||||
<FormField name="aknowledgeRules" v-slot="{ value, handleChange }">
|
||||
<FormItem>
|
||||
<FormLabel>Community Code of Conduct</FormLabel>
|
||||
<FormControl>
|
||||
<div class="flex items-center gap-2">
|
||||
<Checkbox :checked="value" @update:checked="handleChange" />
|
||||
<span>By checking this box, you accept the <Button variant="link" class="p-0">Code of Conduct</Button>.</span>
|
||||
</div>
|
||||
</FormControl>
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
</FormField>
|
||||
|
||||
<div class="pt-2">
|
||||
<Button type="submit" class="w-full">Submit Application</Button>
|
||||
</div>
|
||||
</Form>
|
||||
</template>
|
||||
@@ -2,7 +2,8 @@ import { createRouter, createWebHistory } from 'vue-router'
|
||||
|
||||
const router = createRouter({
|
||||
history: createWebHistory(import.meta.env.BASE_URL),
|
||||
routes: [],
|
||||
routes: [
|
||||
],
|
||||
})
|
||||
|
||||
export default router
|
||||
|
||||
Reference in New Issue
Block a user