mirror of
https://github.com/bartvdbraak/omnidash.git
synced 2025-05-03 18:21:20 +00:00
feat: add dashboard and settings pages and components
This commit is contained in:
parent
cb51a4507d
commit
39a36462bb
109 changed files with 3770 additions and 116 deletions
|
@ -29,19 +29,24 @@
|
|||
"prettier-plugin-tailwindcss": "^0.5.9",
|
||||
"svelte": "^4.2.7",
|
||||
"svelte-check": "^3.6.0",
|
||||
"sveltekit-superforms": "^1.13.4",
|
||||
"tailwindcss": "^3.3.6",
|
||||
"tslib": "^2.4.1",
|
||||
"typescript": "^5.0.0",
|
||||
"vite": "^5.0.3"
|
||||
"vite": "^5.0.3",
|
||||
"zod": "^3.22.4"
|
||||
},
|
||||
"type": "module",
|
||||
"dependencies": {
|
||||
"bits-ui": "^0.15.1",
|
||||
"clsx": "^2.1.0",
|
||||
"cmdk-sv": "^0.0.13",
|
||||
"formsnap": "^0.4.3",
|
||||
"lucide-svelte": "^0.316.0",
|
||||
"mode-watcher": "^0.1.2",
|
||||
"pocketbase": "^0.21.0",
|
||||
"radix-icons-svelte": "^1.2.1",
|
||||
"svelte-headless-table": "^0.18.1",
|
||||
"tailwind-merge": "^2.2.1",
|
||||
"tailwind-variants": "^0.1.20"
|
||||
}
|
||||
|
|
|
@ -11,6 +11,12 @@ dependencies:
|
|||
clsx:
|
||||
specifier: ^2.1.0
|
||||
version: 2.1.0
|
||||
cmdk-sv:
|
||||
specifier: ^0.0.13
|
||||
version: 0.0.13(svelte@4.2.9)
|
||||
formsnap:
|
||||
specifier: ^0.4.3
|
||||
version: 0.4.3(svelte@4.2.9)(sveltekit-superforms@1.13.4)(zod@3.22.4)
|
||||
lucide-svelte:
|
||||
specifier: ^0.316.0
|
||||
version: 0.316.0(svelte@4.2.9)
|
||||
|
@ -23,6 +29,9 @@ dependencies:
|
|||
radix-icons-svelte:
|
||||
specifier: ^1.2.1
|
||||
version: 1.2.1
|
||||
svelte-headless-table:
|
||||
specifier: ^0.18.1
|
||||
version: 0.18.1(svelte@4.2.9)
|
||||
tailwind-merge:
|
||||
specifier: ^2.2.1
|
||||
version: 2.2.1
|
||||
|
@ -82,6 +91,9 @@ devDependencies:
|
|||
svelte-check:
|
||||
specifier: ^3.6.0
|
||||
version: 3.6.3(postcss-load-config@5.0.2)(postcss@8.4.33)(svelte@4.2.9)
|
||||
sveltekit-superforms:
|
||||
specifier: ^1.13.4
|
||||
version: 1.13.4(@sveltejs/kit@2.5.0)(svelte@4.2.9)(zod@3.22.4)
|
||||
tailwindcss:
|
||||
specifier: ^3.3.6
|
||||
version: 3.4.1
|
||||
|
@ -94,6 +106,9 @@ devDependencies:
|
|||
vite:
|
||||
specifier: ^5.0.3
|
||||
version: 5.0.12
|
||||
zod:
|
||||
specifier: ^3.22.4
|
||||
version: 3.22.4
|
||||
|
||||
packages:
|
||||
|
||||
|
@ -126,7 +141,6 @@ packages:
|
|||
cpu: [ppc64]
|
||||
os: [aix]
|
||||
requiresBuild: true
|
||||
dev: true
|
||||
optional: true
|
||||
|
||||
/@esbuild/android-arm64@0.19.12:
|
||||
|
@ -135,7 +149,6 @@ packages:
|
|||
cpu: [arm64]
|
||||
os: [android]
|
||||
requiresBuild: true
|
||||
dev: true
|
||||
optional: true
|
||||
|
||||
/@esbuild/android-arm@0.19.12:
|
||||
|
@ -144,7 +157,6 @@ packages:
|
|||
cpu: [arm]
|
||||
os: [android]
|
||||
requiresBuild: true
|
||||
dev: true
|
||||
optional: true
|
||||
|
||||
/@esbuild/android-x64@0.19.12:
|
||||
|
@ -153,7 +165,6 @@ packages:
|
|||
cpu: [x64]
|
||||
os: [android]
|
||||
requiresBuild: true
|
||||
dev: true
|
||||
optional: true
|
||||
|
||||
/@esbuild/darwin-arm64@0.19.12:
|
||||
|
@ -162,7 +173,6 @@ packages:
|
|||
cpu: [arm64]
|
||||
os: [darwin]
|
||||
requiresBuild: true
|
||||
dev: true
|
||||
optional: true
|
||||
|
||||
/@esbuild/darwin-x64@0.19.12:
|
||||
|
@ -171,7 +181,6 @@ packages:
|
|||
cpu: [x64]
|
||||
os: [darwin]
|
||||
requiresBuild: true
|
||||
dev: true
|
||||
optional: true
|
||||
|
||||
/@esbuild/freebsd-arm64@0.19.12:
|
||||
|
@ -180,7 +189,6 @@ packages:
|
|||
cpu: [arm64]
|
||||
os: [freebsd]
|
||||
requiresBuild: true
|
||||
dev: true
|
||||
optional: true
|
||||
|
||||
/@esbuild/freebsd-x64@0.19.12:
|
||||
|
@ -189,7 +197,6 @@ packages:
|
|||
cpu: [x64]
|
||||
os: [freebsd]
|
||||
requiresBuild: true
|
||||
dev: true
|
||||
optional: true
|
||||
|
||||
/@esbuild/linux-arm64@0.19.12:
|
||||
|
@ -198,7 +205,6 @@ packages:
|
|||
cpu: [arm64]
|
||||
os: [linux]
|
||||
requiresBuild: true
|
||||
dev: true
|
||||
optional: true
|
||||
|
||||
/@esbuild/linux-arm@0.19.12:
|
||||
|
@ -207,7 +213,6 @@ packages:
|
|||
cpu: [arm]
|
||||
os: [linux]
|
||||
requiresBuild: true
|
||||
dev: true
|
||||
optional: true
|
||||
|
||||
/@esbuild/linux-ia32@0.19.12:
|
||||
|
@ -216,7 +221,6 @@ packages:
|
|||
cpu: [ia32]
|
||||
os: [linux]
|
||||
requiresBuild: true
|
||||
dev: true
|
||||
optional: true
|
||||
|
||||
/@esbuild/linux-loong64@0.19.12:
|
||||
|
@ -225,7 +229,6 @@ packages:
|
|||
cpu: [loong64]
|
||||
os: [linux]
|
||||
requiresBuild: true
|
||||
dev: true
|
||||
optional: true
|
||||
|
||||
/@esbuild/linux-mips64el@0.19.12:
|
||||
|
@ -234,7 +237,6 @@ packages:
|
|||
cpu: [mips64el]
|
||||
os: [linux]
|
||||
requiresBuild: true
|
||||
dev: true
|
||||
optional: true
|
||||
|
||||
/@esbuild/linux-ppc64@0.19.12:
|
||||
|
@ -243,7 +245,6 @@ packages:
|
|||
cpu: [ppc64]
|
||||
os: [linux]
|
||||
requiresBuild: true
|
||||
dev: true
|
||||
optional: true
|
||||
|
||||
/@esbuild/linux-riscv64@0.19.12:
|
||||
|
@ -252,7 +253,6 @@ packages:
|
|||
cpu: [riscv64]
|
||||
os: [linux]
|
||||
requiresBuild: true
|
||||
dev: true
|
||||
optional: true
|
||||
|
||||
/@esbuild/linux-s390x@0.19.12:
|
||||
|
@ -261,7 +261,6 @@ packages:
|
|||
cpu: [s390x]
|
||||
os: [linux]
|
||||
requiresBuild: true
|
||||
dev: true
|
||||
optional: true
|
||||
|
||||
/@esbuild/linux-x64@0.19.12:
|
||||
|
@ -270,7 +269,6 @@ packages:
|
|||
cpu: [x64]
|
||||
os: [linux]
|
||||
requiresBuild: true
|
||||
dev: true
|
||||
optional: true
|
||||
|
||||
/@esbuild/netbsd-x64@0.19.12:
|
||||
|
@ -279,7 +277,6 @@ packages:
|
|||
cpu: [x64]
|
||||
os: [netbsd]
|
||||
requiresBuild: true
|
||||
dev: true
|
||||
optional: true
|
||||
|
||||
/@esbuild/openbsd-x64@0.19.12:
|
||||
|
@ -288,7 +285,6 @@ packages:
|
|||
cpu: [x64]
|
||||
os: [openbsd]
|
||||
requiresBuild: true
|
||||
dev: true
|
||||
optional: true
|
||||
|
||||
/@esbuild/sunos-x64@0.19.12:
|
||||
|
@ -297,7 +293,6 @@ packages:
|
|||
cpu: [x64]
|
||||
os: [sunos]
|
||||
requiresBuild: true
|
||||
dev: true
|
||||
optional: true
|
||||
|
||||
/@esbuild/win32-arm64@0.19.12:
|
||||
|
@ -306,7 +301,6 @@ packages:
|
|||
cpu: [arm64]
|
||||
os: [win32]
|
||||
requiresBuild: true
|
||||
dev: true
|
||||
optional: true
|
||||
|
||||
/@esbuild/win32-ia32@0.19.12:
|
||||
|
@ -315,7 +309,6 @@ packages:
|
|||
cpu: [ia32]
|
||||
os: [win32]
|
||||
requiresBuild: true
|
||||
dev: true
|
||||
optional: true
|
||||
|
||||
/@esbuild/win32-x64@0.19.12:
|
||||
|
@ -324,7 +317,6 @@ packages:
|
|||
cpu: [x64]
|
||||
os: [win32]
|
||||
requiresBuild: true
|
||||
dev: true
|
||||
optional: true
|
||||
|
||||
/@eslint-community/eslint-utils@4.4.0(eslint@8.56.0):
|
||||
|
@ -443,6 +435,20 @@ packages:
|
|||
'@jridgewell/resolve-uri': 3.1.1
|
||||
'@jridgewell/sourcemap-codec': 1.4.15
|
||||
|
||||
/@melt-ui/svelte@0.61.2(svelte@4.2.9):
|
||||
resolution: {integrity: sha512-BHkD9G31zQBToA4euDRBgTQRvWxT9scufOVCXgDO6HKTvyxFspbWT2bgiSFqAK4BbAGDn9Ao36Q8F9O71KN4OQ==}
|
||||
peerDependencies:
|
||||
svelte: '>=3 <5'
|
||||
dependencies:
|
||||
'@floating-ui/core': 1.6.0
|
||||
'@floating-ui/dom': 1.6.1
|
||||
'@internationalized/date': 3.5.1
|
||||
dequal: 2.0.3
|
||||
focus-trap: 7.5.4
|
||||
nanoid: 4.0.2
|
||||
svelte: 4.2.9
|
||||
dev: false
|
||||
|
||||
/@melt-ui/svelte@0.68.0(svelte@4.2.9):
|
||||
resolution: {integrity: sha512-/QvA98hnYEodZtHJ71+ocum/WWp30hVNt3F8uiZKnNYwZDaiQYjlyR9AaGKYcZLCe6R68op1mfCzc0kTzJilyA==}
|
||||
peerDependencies:
|
||||
|
@ -483,14 +489,12 @@ packages:
|
|||
|
||||
/@polka/url@1.0.0-next.24:
|
||||
resolution: {integrity: sha512-2LuNTFBIO0m7kKIQvvPHN6UE63VjpmL9rnEEaOOaiSPbZK+zUOYIzBAWcED+3XYzhYsd/0mD57VdxAEqqV52CQ==}
|
||||
dev: true
|
||||
|
||||
/@rollup/rollup-android-arm-eabi@4.9.6:
|
||||
resolution: {integrity: sha512-MVNXSSYN6QXOulbHpLMKYi60ppyO13W9my1qogeiAqtjb2yR4LSmfU2+POvDkLzhjYLXz9Rf9+9a3zFHW1Lecg==}
|
||||
cpu: [arm]
|
||||
os: [android]
|
||||
requiresBuild: true
|
||||
dev: true
|
||||
optional: true
|
||||
|
||||
/@rollup/rollup-android-arm64@4.9.6:
|
||||
|
@ -498,7 +502,6 @@ packages:
|
|||
cpu: [arm64]
|
||||
os: [android]
|
||||
requiresBuild: true
|
||||
dev: true
|
||||
optional: true
|
||||
|
||||
/@rollup/rollup-darwin-arm64@4.9.6:
|
||||
|
@ -506,7 +509,6 @@ packages:
|
|||
cpu: [arm64]
|
||||
os: [darwin]
|
||||
requiresBuild: true
|
||||
dev: true
|
||||
optional: true
|
||||
|
||||
/@rollup/rollup-darwin-x64@4.9.6:
|
||||
|
@ -514,7 +516,6 @@ packages:
|
|||
cpu: [x64]
|
||||
os: [darwin]
|
||||
requiresBuild: true
|
||||
dev: true
|
||||
optional: true
|
||||
|
||||
/@rollup/rollup-linux-arm-gnueabihf@4.9.6:
|
||||
|
@ -522,7 +523,6 @@ packages:
|
|||
cpu: [arm]
|
||||
os: [linux]
|
||||
requiresBuild: true
|
||||
dev: true
|
||||
optional: true
|
||||
|
||||
/@rollup/rollup-linux-arm64-gnu@4.9.6:
|
||||
|
@ -530,7 +530,6 @@ packages:
|
|||
cpu: [arm64]
|
||||
os: [linux]
|
||||
requiresBuild: true
|
||||
dev: true
|
||||
optional: true
|
||||
|
||||
/@rollup/rollup-linux-arm64-musl@4.9.6:
|
||||
|
@ -538,7 +537,6 @@ packages:
|
|||
cpu: [arm64]
|
||||
os: [linux]
|
||||
requiresBuild: true
|
||||
dev: true
|
||||
optional: true
|
||||
|
||||
/@rollup/rollup-linux-riscv64-gnu@4.9.6:
|
||||
|
@ -546,7 +544,6 @@ packages:
|
|||
cpu: [riscv64]
|
||||
os: [linux]
|
||||
requiresBuild: true
|
||||
dev: true
|
||||
optional: true
|
||||
|
||||
/@rollup/rollup-linux-x64-gnu@4.9.6:
|
||||
|
@ -554,7 +551,6 @@ packages:
|
|||
cpu: [x64]
|
||||
os: [linux]
|
||||
requiresBuild: true
|
||||
dev: true
|
||||
optional: true
|
||||
|
||||
/@rollup/rollup-linux-x64-musl@4.9.6:
|
||||
|
@ -562,7 +558,6 @@ packages:
|
|||
cpu: [x64]
|
||||
os: [linux]
|
||||
requiresBuild: true
|
||||
dev: true
|
||||
optional: true
|
||||
|
||||
/@rollup/rollup-win32-arm64-msvc@4.9.6:
|
||||
|
@ -570,7 +565,6 @@ packages:
|
|||
cpu: [arm64]
|
||||
os: [win32]
|
||||
requiresBuild: true
|
||||
dev: true
|
||||
optional: true
|
||||
|
||||
/@rollup/rollup-win32-ia32-msvc@4.9.6:
|
||||
|
@ -578,7 +572,6 @@ packages:
|
|||
cpu: [ia32]
|
||||
os: [win32]
|
||||
requiresBuild: true
|
||||
dev: true
|
||||
optional: true
|
||||
|
||||
/@rollup/rollup-win32-x64-msvc@4.9.6:
|
||||
|
@ -586,7 +579,6 @@ packages:
|
|||
cpu: [x64]
|
||||
os: [win32]
|
||||
requiresBuild: true
|
||||
dev: true
|
||||
optional: true
|
||||
|
||||
/@sveltejs/adapter-auto@3.1.1(@sveltejs/kit@2.5.0):
|
||||
|
@ -623,7 +615,6 @@ packages:
|
|||
svelte: 4.2.9
|
||||
tiny-glob: 0.2.9
|
||||
vite: 5.0.12
|
||||
dev: true
|
||||
|
||||
/@sveltejs/vite-plugin-svelte-inspector@2.0.0(@sveltejs/vite-plugin-svelte@3.0.2)(svelte@4.2.9)(vite@5.0.12):
|
||||
resolution: {integrity: sha512-gjr9ZFg1BSlIpfZ4PRewigrvYmHWbDrq2uvvPB1AmTWKuM+dI1JXQSUu2pIrYLb/QncyiIGkFDFKTwJ0XqQZZg==}
|
||||
|
@ -639,7 +630,6 @@ packages:
|
|||
vite: 5.0.12
|
||||
transitivePeerDependencies:
|
||||
- supports-color
|
||||
dev: true
|
||||
|
||||
/@sveltejs/vite-plugin-svelte@3.0.2(svelte@4.2.9)(vite@5.0.12):
|
||||
resolution: {integrity: sha512-MpmF/cju2HqUls50WyTHQBZUV3ovV/Uk8k66AN2gwHogNAG8wnW8xtZDhzNBsFJJuvmq1qnzA5kE7YfMJNFv2Q==}
|
||||
|
@ -659,7 +649,6 @@ packages:
|
|||
vitefu: 0.2.5(vite@5.0.12)
|
||||
transitivePeerDependencies:
|
||||
- supports-color
|
||||
dev: true
|
||||
|
||||
/@swc/helpers@0.5.3:
|
||||
resolution: {integrity: sha512-FaruWX6KdudYloq1AHD/4nU+UsMTdNE8CKyrseXWEcgjDAbvkwJg2QGPAnfIJLIWsjZOSPLOAykK6fuYp4vp4A==}
|
||||
|
@ -669,7 +658,6 @@ packages:
|
|||
|
||||
/@types/cookie@0.6.0:
|
||||
resolution: {integrity: sha512-4Kh9a6B2bQciAhf7FSuMRRkUWecJgJu9nPnx3yzpsfXX/c50REIqpHY4C82bXP90qrLtXtkDxTZosYO3UpOwlA==}
|
||||
dev: true
|
||||
|
||||
/@types/eslint@8.56.0:
|
||||
resolution: {integrity: sha512-FlsN0p4FhuYRjIxpbdXovvHQhtlG05O1GG/RNWvdAxTboR438IOTwmrY/vLA+Xfgg06BTkP045M3vpFwTMv1dg==}
|
||||
|
@ -935,6 +923,16 @@ packages:
|
|||
svelte: 4.2.9
|
||||
dev: false
|
||||
|
||||
/bits-ui@0.9.9(svelte@4.2.9):
|
||||
resolution: {integrity: sha512-LkdkyTtpXdkjBzPZJVJgpcre4fut6DONoprMfadHFo82HNUhph+02CxDjYEcZcThb5z4YjSxMlCYvQPZm+YtfQ==}
|
||||
peerDependencies:
|
||||
svelte: ^4.0.0
|
||||
dependencies:
|
||||
'@melt-ui/svelte': 0.61.2(svelte@4.2.9)
|
||||
nanoid: 5.0.4
|
||||
svelte: 4.2.9
|
||||
dev: false
|
||||
|
||||
/brace-expansion@1.1.11:
|
||||
resolution: {integrity: sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==}
|
||||
dependencies:
|
||||
|
@ -1008,6 +1006,16 @@ packages:
|
|||
engines: {node: '>=6'}
|
||||
dev: false
|
||||
|
||||
/cmdk-sv@0.0.13(svelte@4.2.9):
|
||||
resolution: {integrity: sha512-WrYn0MMdVyzJx+KuOQy028/7mv+uMwO1cxVBM0uJ4KA+50PX792epsj8Yw3It8WfWR8Rae7siBCg54mIAlKsiw==}
|
||||
peerDependencies:
|
||||
svelte: ^4.0.0
|
||||
dependencies:
|
||||
bits-ui: 0.9.9(svelte@4.2.9)
|
||||
nanoid: 5.0.4
|
||||
svelte: 4.2.9
|
||||
dev: false
|
||||
|
||||
/code-red@1.0.4:
|
||||
resolution: {integrity: sha512-7qJWqItLA8/VPVlKJlFXU+NBlo/qyfs39aJcuMT/2ere32ZqvF5OSxgdM5xOfJJ7O429gg2HM47y8v9P+9wrNw==}
|
||||
dependencies:
|
||||
|
@ -1037,7 +1045,6 @@ packages:
|
|||
/cookie@0.6.0:
|
||||
resolution: {integrity: sha512-U71cyTamuh1CRNCfpGY6to28lxvNwPG4Guz/EVjgf3Jmzv0vlDp1atT9eS5dDjMYHucpHbWns6Lwf3BKz6svdw==}
|
||||
engines: {node: '>= 0.6'}
|
||||
dev: true
|
||||
|
||||
/cross-spawn@7.0.3:
|
||||
resolution: {integrity: sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==}
|
||||
|
@ -1069,7 +1076,6 @@ packages:
|
|||
optional: true
|
||||
dependencies:
|
||||
ms: 2.1.2
|
||||
dev: true
|
||||
|
||||
/deep-is@0.1.4:
|
||||
resolution: {integrity: sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==}
|
||||
|
@ -1078,7 +1084,6 @@ packages:
|
|||
/deepmerge@4.3.1:
|
||||
resolution: {integrity: sha512-3sUqbMEc77XqpdNO7FRyRog+eW3ph+GYCbj+rK+uYyRMuwsVy0rMiVtPn+QJlKFvWP/1PYpapqYn0Me2knFn+A==}
|
||||
engines: {node: '>=0.10.0'}
|
||||
dev: true
|
||||
|
||||
/dequal@2.0.3:
|
||||
resolution: {integrity: sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA==}
|
||||
|
@ -1091,7 +1096,6 @@ packages:
|
|||
|
||||
/devalue@4.3.2:
|
||||
resolution: {integrity: sha512-KqFl6pOgOW+Y6wJgu80rHpo2/3H07vr8ntR9rkkFIRETewbf5GaYYcakYfiKz89K+sLsuPkQIZaXDMjUObZwWg==}
|
||||
dev: true
|
||||
|
||||
/didyoumean@1.2.2:
|
||||
resolution: {integrity: sha512-gxtyfqMg7GKyhQmb056K7M3xszy/myH8w+B4RT+QXBQsvAOdc3XymqDDPHx1BgPgsdAA5SIifona89YtRATDzw==}
|
||||
|
@ -1159,7 +1163,6 @@ packages:
|
|||
'@esbuild/win32-arm64': 0.19.12
|
||||
'@esbuild/win32-ia32': 0.19.12
|
||||
'@esbuild/win32-x64': 0.19.12
|
||||
dev: true
|
||||
|
||||
/escalade@3.1.1:
|
||||
resolution: {integrity: sha512-k0er2gUkLf8O0zKJiAhmkTnJlTvINGv7ygDNPbeIsX/TJjGJZHuh9B2UxbsaEkmlEo9MfhrSzmhIlhRlI2GXnw==}
|
||||
|
@ -1280,7 +1283,6 @@ packages:
|
|||
|
||||
/esm-env@1.0.0:
|
||||
resolution: {integrity: sha512-Cf6VksWPsTuW01vU9Mk/3vRue91Zevka5SjyNf3nEpokFRuqt/KjUQoGAwq9qMmhpLTHmXzSIrFRw8zxWzmFBA==}
|
||||
dev: true
|
||||
|
||||
/espree@9.6.1:
|
||||
resolution: {integrity: sha512-oruZaFkjorTpF32kDSI5/75ViwGeZginGGy2NoOSg3Q9bnwlnmDm4HLnkl0RE3n+njDXR037aY1+x58Z/zFdwQ==}
|
||||
|
@ -1394,6 +1396,18 @@ packages:
|
|||
cross-spawn: 7.0.3
|
||||
signal-exit: 4.1.0
|
||||
|
||||
/formsnap@0.4.3(svelte@4.2.9)(sveltekit-superforms@1.13.4)(zod@3.22.4):
|
||||
resolution: {integrity: sha512-PWVq78XVUHhAU1tcVGKeGamk6B4Opkk1uVNRW2YofiQpnA5Bry1c3TQjB9cVDw5u4oAwmDvIoAzVHlrAIgc+tw==}
|
||||
peerDependencies:
|
||||
svelte: ^4.0.0
|
||||
sveltekit-superforms: ^1.7.1
|
||||
zod: ^3.22.2
|
||||
dependencies:
|
||||
svelte: 4.2.9
|
||||
sveltekit-superforms: 1.13.4(@sveltejs/kit@2.5.0)(svelte@4.2.9)(zod@3.22.4)
|
||||
zod: 3.22.4
|
||||
dev: false
|
||||
|
||||
/fraction.js@4.3.7:
|
||||
resolution: {integrity: sha512-ZsDfxO51wGAXREY55a7la9LScWpwv9RxIrYABrlvOFBlH/ShPnrtsXeuUIfXKKOVicNxQ+o8JTbJvjS4M89yew==}
|
||||
dev: true
|
||||
|
@ -1455,7 +1469,6 @@ packages:
|
|||
|
||||
/globalyzer@0.1.0:
|
||||
resolution: {integrity: sha512-40oNTM9UfG6aBmuKxk/giHn5nQ8RVz/SS4Ir6zgzOv9/qC3kKZ9v4etGTcJbEl/NyVQH7FGU7d+X1egr57Md2Q==}
|
||||
dev: true
|
||||
|
||||
/globby@11.1.0:
|
||||
resolution: {integrity: sha512-jhIXaOzy1sb8IyocaruWSn1TjmnBVs8Ayhcy83rmxNJ8q2uWKCAj3CnJY+KpGSXCueAPc0i05kVvVKtP1t9S3g==}
|
||||
|
@ -1471,7 +1484,6 @@ packages:
|
|||
|
||||
/globrex@0.1.2:
|
||||
resolution: {integrity: sha512-uHJgbwAMwNFf5mLst7IWLNg14x1CkeqglJb/K3doi4dw6q2IvAAmM/Y81kevy83wP+Sst+nutFTYOGg3d1lsxg==}
|
||||
dev: true
|
||||
|
||||
/graceful-fs@4.2.11:
|
||||
resolution: {integrity: sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==}
|
||||
|
@ -1507,7 +1519,6 @@ packages:
|
|||
|
||||
/import-meta-resolve@4.0.0:
|
||||
resolution: {integrity: sha512-okYUR7ZQPH+efeuMJGlq4f8ubUgO50kByRPyt/Cy1Io4PSRsPjxME+YlVaCOx+NIToW7hCsZNFJyTPFFKepRSA==}
|
||||
dev: true
|
||||
|
||||
/imurmurhash@0.1.4:
|
||||
resolution: {integrity: sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==}
|
||||
|
@ -1607,7 +1618,10 @@ packages:
|
|||
/kleur@4.1.5:
|
||||
resolution: {integrity: sha512-o+NO+8WrRiQEE4/7nwRJhN1HWpVmJm511pBHUxPLtp0BUISzlBplORYSmTclCnJvQq2tKu/sgl3xVpkc7ZWuQQ==}
|
||||
engines: {node: '>=6'}
|
||||
dev: true
|
||||
|
||||
/klona@2.0.6:
|
||||
resolution: {integrity: sha512-dhG34DXATL5hSxJbIexCft8FChFXtmskoZYnoPWjXQuebWYCNkVeV3KkGegCK9CP1oswI/vQibS2GY7Em/sJJA==}
|
||||
engines: {node: '>= 8'}
|
||||
|
||||
/known-css-properties@0.29.0:
|
||||
resolution: {integrity: sha512-Ne7wqW7/9Cz54PDt4I3tcV+hAyat8ypyOGzYRJQfdxnnjeWsTxt1cy8pjvvKeI5kfXuyvULyeeAvwvvtAX3ayQ==}
|
||||
|
@ -1728,16 +1742,13 @@ packages:
|
|||
/mri@1.2.0:
|
||||
resolution: {integrity: sha512-tzzskb3bG8LvYGFF/mDTpq3jpI6Q9wc3LEmBaghu+DdCssd1FakN7Bc0hVNmEyGq1bq3RgfkCb3cmQLpNPOroA==}
|
||||
engines: {node: '>=4'}
|
||||
dev: true
|
||||
|
||||
/mrmime@2.0.0:
|
||||
resolution: {integrity: sha512-eu38+hdgojoyq63s+yTpN4XMBdt5l8HhMhc4VKLO9KM5caLIBvUm4thi7fFaxyTmCKeNnXZ5pAlBwCUnhA09uw==}
|
||||
engines: {node: '>=10'}
|
||||
dev: true
|
||||
|
||||
/ms@2.1.2:
|
||||
resolution: {integrity: sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==}
|
||||
dev: true
|
||||
|
||||
/mz@2.7.0:
|
||||
resolution: {integrity: sha512-z81GNO7nnYMEhrGh9LeymoE4+Yr0Wn5McHIZMK5cfQCl+NDX08sCZgUc9/6MHni9IWuFLm1Z3HTCXu2z9fN62Q==}
|
||||
|
@ -1751,6 +1762,12 @@ packages:
|
|||
engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1}
|
||||
hasBin: true
|
||||
|
||||
/nanoid@4.0.2:
|
||||
resolution: {integrity: sha512-7ZtY5KTCNheRGfEFxnedV5zFiORN1+Y1N6zvPTnHQd8ENUvfaDBeuJDZb2bN/oXwXxu3qkTXDzy57W5vAmDTBw==}
|
||||
engines: {node: ^14 || ^16 || >=18}
|
||||
hasBin: true
|
||||
dev: false
|
||||
|
||||
/nanoid@5.0.4:
|
||||
resolution: {integrity: sha512-vAjmBf13gsmhXSgBrtIclinISzFFy22WwCYoyilZlsrRXNIHSwgFQ1bEdjRwMT3aoadeIF6HMuDRlOxzfXV8ig==}
|
||||
engines: {node: ^18 || >=20}
|
||||
|
@ -2144,7 +2161,6 @@ packages:
|
|||
'@rollup/rollup-win32-ia32-msvc': 4.9.6
|
||||
'@rollup/rollup-win32-x64-msvc': 4.9.6
|
||||
fsevents: 2.3.3
|
||||
dev: true
|
||||
|
||||
/run-parallel@1.2.0:
|
||||
resolution: {integrity: sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==}
|
||||
|
@ -2156,7 +2172,6 @@ packages:
|
|||
engines: {node: '>=6'}
|
||||
dependencies:
|
||||
mri: 1.2.0
|
||||
dev: true
|
||||
|
||||
/sander@0.5.1:
|
||||
resolution: {integrity: sha512-3lVqBir7WuKDHGrKRDn/1Ye3kwpXaDOMsiRP1wd6wpZW56gJhsbp5RqQpA6JG/P+pkXizygnr1dKR8vzWaVsfA==}
|
||||
|
@ -2177,7 +2192,6 @@ packages:
|
|||
|
||||
/set-cookie-parser@2.6.0:
|
||||
resolution: {integrity: sha512-RVnVQxTXuerk653XfuliOxBP81Sf0+qfQE73LIYKcyMYHG94AuH0kgrQpRDuTZnSmjpysHmzxJXKNfa6PjFhyQ==}
|
||||
dev: true
|
||||
|
||||
/shebang-command@2.0.0:
|
||||
resolution: {integrity: sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==}
|
||||
|
@ -2200,7 +2214,6 @@ packages:
|
|||
'@polka/url': 1.0.0-next.24
|
||||
mrmime: 2.0.0
|
||||
totalist: 3.0.1
|
||||
dev: true
|
||||
|
||||
/slash@3.0.0:
|
||||
resolution: {integrity: sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==}
|
||||
|
@ -2329,6 +2342,17 @@ packages:
|
|||
svelte: 4.2.9
|
||||
dev: true
|
||||
|
||||
/svelte-headless-table@0.18.1(svelte@4.2.9):
|
||||
resolution: {integrity: sha512-pcZIi36u8RV9B3m0oG6000az7tvT6CCK+c8qBUuNUQSChsfJySC5ryGdJSTFlAYBVRGUgZlqYOKGUgSbOfIrnA==}
|
||||
peerDependencies:
|
||||
svelte: ^4.0.0
|
||||
dependencies:
|
||||
svelte: 4.2.9
|
||||
svelte-keyed: 2.0.0(svelte@4.2.9)
|
||||
svelte-render: 2.0.0(svelte@4.2.9)
|
||||
svelte-subscribe: 2.0.1(svelte@4.2.9)
|
||||
dev: false
|
||||
|
||||
/svelte-hmr@0.15.3(svelte@4.2.9):
|
||||
resolution: {integrity: sha512-41snaPswvSf8TJUhlkoJBekRrABDXDMdpNpT2tfHIv4JuhgvHqLMhEPGtaQn0BmbNSTkuz2Ed20DF2eHw0SmBQ==}
|
||||
engines: {node: ^12.20 || ^14.13.1 || >= 16}
|
||||
|
@ -2336,7 +2360,14 @@ packages:
|
|||
svelte: ^3.19.0 || ^4.0.0
|
||||
dependencies:
|
||||
svelte: 4.2.9
|
||||
dev: true
|
||||
|
||||
/svelte-keyed@2.0.0(svelte@4.2.9):
|
||||
resolution: {integrity: sha512-7TeEn+QbJC2OJrHiuM0T8vMBkms3DNpTE+Ir+NtnVBnBMA78aL4f1ft9t0Hn/pBbD/TnIXi4YfjFRAgtN+DZ5g==}
|
||||
peerDependencies:
|
||||
svelte: ^4.0.0
|
||||
dependencies:
|
||||
svelte: 4.2.9
|
||||
dev: false
|
||||
|
||||
/svelte-preprocess@5.1.3(postcss-load-config@5.0.2)(postcss@8.4.33)(svelte@4.2.9)(typescript@5.3.3):
|
||||
resolution: {integrity: sha512-xxAkmxGHT+J/GourS5mVJeOXZzne1FR5ljeOUAMXUkfEhkLEllRreXpbl3dIYJlcJRfL1LO1uIAPpBpBfiqGPw==}
|
||||
|
@ -2387,6 +2418,23 @@ packages:
|
|||
typescript: 5.3.3
|
||||
dev: true
|
||||
|
||||
/svelte-render@2.0.0(svelte@4.2.9):
|
||||
resolution: {integrity: sha512-YWMwXGlUlnlb8QhEd138Kmay2KfU6sE7jj3epoYZuHe3M2voGdXfgqYJY7gGizW55N0zDWtuRaY3Rh6PGcGQyQ==}
|
||||
peerDependencies:
|
||||
svelte: ^4.0.0
|
||||
dependencies:
|
||||
svelte: 4.2.9
|
||||
svelte-subscribe: 2.0.1(svelte@4.2.9)
|
||||
dev: false
|
||||
|
||||
/svelte-subscribe@2.0.1(svelte@4.2.9):
|
||||
resolution: {integrity: sha512-eKXIjLxB4C7eQWPqKEdxcGfNXm2g/qJ67zmEZK/GigCZMfrTR3m7DPY93R6MX+5uoqM1FRYxl8LZ1oy4URWi2A==}
|
||||
peerDependencies:
|
||||
svelte: ^4.0.0
|
||||
dependencies:
|
||||
svelte: 4.2.9
|
||||
dev: false
|
||||
|
||||
/svelte@4.2.9:
|
||||
resolution: {integrity: sha512-hsoB/WZGEPFXeRRLPhPrbRz67PhP6sqYgvwcAs+gWdSQSvNDw+/lTeUJSWe5h2xC97Fz/8QxAOqItwBzNJPU8w==}
|
||||
engines: {node: '>=16'}
|
||||
|
@ -2406,6 +2454,19 @@ packages:
|
|||
magic-string: 0.30.5
|
||||
periscopic: 3.1.0
|
||||
|
||||
/sveltekit-superforms@1.13.4(@sveltejs/kit@2.5.0)(svelte@4.2.9)(zod@3.22.4):
|
||||
resolution: {integrity: sha512-rM2+Ictaw7OAIorCLmvg82orci/mtO9ZouI4emtx8SyYngx9aED+eNZlHPLufgB6D7geL2a+hMSFtM3zmMQixQ==}
|
||||
peerDependencies:
|
||||
'@sveltejs/kit': 1.x || 2.x
|
||||
svelte: 3.x || 4.x
|
||||
zod: 3.x
|
||||
dependencies:
|
||||
'@sveltejs/kit': 2.5.0(@sveltejs/vite-plugin-svelte@3.0.2)(svelte@4.2.9)(vite@5.0.12)
|
||||
devalue: 4.3.2
|
||||
klona: 2.0.6
|
||||
svelte: 4.2.9
|
||||
zod: 3.22.4
|
||||
|
||||
/tabbable@6.2.0:
|
||||
resolution: {integrity: sha512-Cat63mxsVJlzYvN51JmVXIgNoUokrIaT2zLclCXjRd8boZ0004U4KCs/sToJ75C6sdlByWxpYnb5Boif1VSFew==}
|
||||
dev: false
|
||||
|
@ -2480,7 +2541,6 @@ packages:
|
|||
dependencies:
|
||||
globalyzer: 0.1.0
|
||||
globrex: 0.1.2
|
||||
dev: true
|
||||
|
||||
/to-regex-range@5.0.1:
|
||||
resolution: {integrity: sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==}
|
||||
|
@ -2491,7 +2551,6 @@ packages:
|
|||
/totalist@3.0.1:
|
||||
resolution: {integrity: sha512-sf4i37nQ2LBx4m3wB74y+ubopq6W/dIzXg0FDGjsYnZHVa1Da8FH853wlL2gtUhg+xJXjfk3kUZS3BRoQeoQBQ==}
|
||||
engines: {node: '>=6'}
|
||||
dev: true
|
||||
|
||||
/ts-api-utils@1.0.3(typescript@5.3.3):
|
||||
resolution: {integrity: sha512-wNMeqtMz5NtwpT/UZGY5alT+VoKdSsOOP/kqHFcUW1P/VRhH2wJ48+DN2WwUliNbQ976ETwDL0Ifd2VVvgonvg==}
|
||||
|
@ -2579,7 +2638,6 @@ packages:
|
|||
rollup: 4.9.6
|
||||
optionalDependencies:
|
||||
fsevents: 2.3.3
|
||||
dev: true
|
||||
|
||||
/vitefu@0.2.5(vite@5.0.12):
|
||||
resolution: {integrity: sha512-SgHtMLoqaeeGnd2evZ849ZbACbnwQCIwRH57t18FxcXoZop0uQu0uzlIhJBlF/eWVzuce0sHeqPcDo+evVcg8Q==}
|
||||
|
@ -2590,7 +2648,6 @@ packages:
|
|||
optional: true
|
||||
dependencies:
|
||||
vite: 5.0.12
|
||||
dev: true
|
||||
|
||||
/which@2.0.2:
|
||||
resolution: {integrity: sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==}
|
||||
|
@ -2636,3 +2693,6 @@ packages:
|
|||
resolution: {integrity: sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==}
|
||||
engines: {node: '>=10'}
|
||||
dev: true
|
||||
|
||||
/zod@3.22.4:
|
||||
resolution: {integrity: sha512-iC+8Io04lddc+mVqQ9AZ7OQ2MrUKGN+oIQyq1vemgt46jwCwLfhq7/pwnBnNXXXZb8VTVLKwp9EDkx+ryxIWmg==}
|
||||
|
|
|
@ -23,7 +23,7 @@
|
|||
--border: 0 0% 89.8%;
|
||||
--input: 0 0% 89.8%;
|
||||
--ring: 0 72.2% 50.6%;
|
||||
--radius: 0rem;
|
||||
--radius: 0.5rem;
|
||||
}
|
||||
.dark {
|
||||
--background: 0 0% 3.9%;
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
import type { Icon as LucideIcon } from 'lucide-svelte';
|
||||
import { Loader2 } from 'lucide-svelte';
|
||||
import { ArrowRight, Loader2 } from 'lucide-svelte';
|
||||
import { GithubLogo, VercelLogo, LinkedinLogo } from 'radix-icons-svelte';
|
||||
import Logo from './logo.svelte';
|
||||
import Svelte from './svelte.svelte';
|
||||
|
@ -14,5 +14,6 @@ export const Icons = {
|
|||
svelte: Svelte,
|
||||
vercel: VercelLogo,
|
||||
linkedIn: LinkedinLogo,
|
||||
spinner: Loader2
|
||||
spinner: Loader2,
|
||||
arrowRight: ArrowRight,
|
||||
};
|
||||
|
|
16
apps/web/src/lib/components/ui/avatar/avatar-fallback.svelte
Normal file
16
apps/web/src/lib/components/ui/avatar/avatar-fallback.svelte
Normal file
|
@ -0,0 +1,16 @@
|
|||
<script lang="ts">
|
||||
import { Avatar as AvatarPrimitive } from "bits-ui";
|
||||
import { cn } from "$lib/utils";
|
||||
|
||||
type $$Props = AvatarPrimitive.FallbackProps;
|
||||
|
||||
let className: $$Props["class"] = undefined;
|
||||
export { className as class };
|
||||
</script>
|
||||
|
||||
<AvatarPrimitive.Fallback
|
||||
class={cn("flex h-full w-full items-center justify-center rounded-full bg-muted", className)}
|
||||
{...$$restProps}
|
||||
>
|
||||
<slot />
|
||||
</AvatarPrimitive.Fallback>
|
18
apps/web/src/lib/components/ui/avatar/avatar-image.svelte
Normal file
18
apps/web/src/lib/components/ui/avatar/avatar-image.svelte
Normal file
|
@ -0,0 +1,18 @@
|
|||
<script lang="ts">
|
||||
import { Avatar as AvatarPrimitive } from "bits-ui";
|
||||
import { cn } from "$lib/utils";
|
||||
|
||||
type $$Props = AvatarPrimitive.ImageProps;
|
||||
|
||||
let className: $$Props["class"] = undefined;
|
||||
export let src: $$Props["src"] = undefined;
|
||||
export let alt: $$Props["alt"] = undefined;
|
||||
export { className as class };
|
||||
</script>
|
||||
|
||||
<AvatarPrimitive.Image
|
||||
{src}
|
||||
{alt}
|
||||
class={cn("aspect-square h-full w-full", className)}
|
||||
{...$$restProps}
|
||||
/>
|
18
apps/web/src/lib/components/ui/avatar/avatar.svelte
Normal file
18
apps/web/src/lib/components/ui/avatar/avatar.svelte
Normal file
|
@ -0,0 +1,18 @@
|
|||
<script lang="ts">
|
||||
import { Avatar as AvatarPrimitive } from "bits-ui";
|
||||
import { cn } from "$lib/utils";
|
||||
|
||||
type $$Props = AvatarPrimitive.Props;
|
||||
|
||||
let className: $$Props["class"] = undefined;
|
||||
export let delayMs: $$Props["delayMs"] = undefined;
|
||||
export { className as class };
|
||||
</script>
|
||||
|
||||
<AvatarPrimitive.Root
|
||||
{delayMs}
|
||||
class={cn("relative flex h-10 w-10 shrink-0 overflow-hidden rounded-full", className)}
|
||||
{...$$restProps}
|
||||
>
|
||||
<slot />
|
||||
</AvatarPrimitive.Root>
|
13
apps/web/src/lib/components/ui/avatar/index.ts
Normal file
13
apps/web/src/lib/components/ui/avatar/index.ts
Normal file
|
@ -0,0 +1,13 @@
|
|||
import Root from "./avatar.svelte";
|
||||
import Image from "./avatar-image.svelte";
|
||||
import Fallback from "./avatar-fallback.svelte";
|
||||
|
||||
export {
|
||||
Root,
|
||||
Image,
|
||||
Fallback,
|
||||
//
|
||||
Root as Avatar,
|
||||
Image as AvatarImage,
|
||||
Fallback as AvatarFallback
|
||||
};
|
18
apps/web/src/lib/components/ui/badge/badge.svelte
Normal file
18
apps/web/src/lib/components/ui/badge/badge.svelte
Normal file
|
@ -0,0 +1,18 @@
|
|||
<script lang="ts">
|
||||
import { cn } from "$lib/utils";
|
||||
import { badgeVariants, type Variant } from ".";
|
||||
|
||||
let className: string | undefined | null = undefined;
|
||||
export let href: string | undefined = undefined;
|
||||
export let variant: Variant = "default";
|
||||
export { className as class };
|
||||
</script>
|
||||
|
||||
<svelte:element
|
||||
this={href ? "a" : "span"}
|
||||
{href}
|
||||
class={cn(badgeVariants({ variant, className }))}
|
||||
{...$$restProps}
|
||||
>
|
||||
<slot />
|
||||
</svelte:element>
|
22
apps/web/src/lib/components/ui/badge/index.ts
Normal file
22
apps/web/src/lib/components/ui/badge/index.ts
Normal file
|
@ -0,0 +1,22 @@
|
|||
import { tv, type VariantProps } from "tailwind-variants";
|
||||
|
||||
export { default as Badge } from "./badge.svelte";
|
||||
export const badgeVariants = tv({
|
||||
base: "inline-flex items-center rounded-md 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 select-none",
|
||||
variants: {
|
||||
variant: {
|
||||
default:
|
||||
"border-transparent bg-primary text-primary-foreground shadow hover:bg-primary/80",
|
||||
secondary:
|
||||
"border-transparent bg-secondary text-secondary-foreground hover:bg-secondary/80",
|
||||
destructive:
|
||||
"border-transparent bg-destructive text-destructive-foreground shadow hover:bg-destructive/80",
|
||||
outline: "text-foreground"
|
||||
}
|
||||
},
|
||||
defaultVariants: {
|
||||
variant: "default"
|
||||
}
|
||||
});
|
||||
|
||||
export type Variant = VariantProps<typeof badgeVariants>["variant"];
|
34
apps/web/src/lib/components/ui/checkbox/checkbox.svelte
Normal file
34
apps/web/src/lib/components/ui/checkbox/checkbox.svelte
Normal file
|
@ -0,0 +1,34 @@
|
|||
<script lang="ts">
|
||||
import { Checkbox as CheckboxPrimitive } from "bits-ui";
|
||||
import { Check, Minus } from "radix-icons-svelte";
|
||||
import { cn } from "$lib/utils";
|
||||
|
||||
type $$Props = CheckboxPrimitive.Props;
|
||||
type $$Events = CheckboxPrimitive.Events;
|
||||
|
||||
let className: $$Props["class"] = undefined;
|
||||
export let checked: $$Props["checked"] = false;
|
||||
export { className as class };
|
||||
</script>
|
||||
|
||||
<CheckboxPrimitive.Root
|
||||
class={cn(
|
||||
"box-content peer h-4 w-4 shrink-0 rounded-sm border border-primary shadow focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring disabled:cursor-not-allowed disabled:opacity-50 data-[state=checked]:bg-primary data-[state=checked]:text-primary-foreground data-[disabled=true]:cursor-not-allowed data-[disabled=true]:opacity-50",
|
||||
className
|
||||
)}
|
||||
bind:checked
|
||||
on:click
|
||||
{...$$restProps}
|
||||
>
|
||||
<CheckboxPrimitive.Indicator
|
||||
class={cn("flex items-center justify-center text-current h-4 w-4")}
|
||||
let:isChecked
|
||||
let:isIndeterminate
|
||||
>
|
||||
{#if isIndeterminate}
|
||||
<Minus class="h-3.5 w-3.5" />
|
||||
{:else}
|
||||
<Check class={cn("h-3.5 w-3.5", !isChecked && "text-transparent")} />
|
||||
{/if}
|
||||
</CheckboxPrimitive.Indicator>
|
||||
</CheckboxPrimitive.Root>
|
6
apps/web/src/lib/components/ui/checkbox/index.ts
Normal file
6
apps/web/src/lib/components/ui/checkbox/index.ts
Normal file
|
@ -0,0 +1,6 @@
|
|||
import Root from "./checkbox.svelte";
|
||||
export {
|
||||
Root,
|
||||
//
|
||||
Root as Checkbox
|
||||
};
|
23
apps/web/src/lib/components/ui/command/command-dialog.svelte
Normal file
23
apps/web/src/lib/components/ui/command/command-dialog.svelte
Normal file
|
@ -0,0 +1,23 @@
|
|||
<script lang="ts">
|
||||
import Command from "./command.svelte";
|
||||
import * as Dialog from "$lib/components/ui/dialog";
|
||||
import type { Dialog as DialogPrimitive } from "bits-ui";
|
||||
import type { Command as CommandPrimitive } from "cmdk-sv";
|
||||
|
||||
type $$Props = DialogPrimitive.Props & CommandPrimitive.CommandProps;
|
||||
|
||||
export let open: $$Props["open"] = false;
|
||||
export let value: $$Props["value"] = undefined;
|
||||
</script>
|
||||
|
||||
<Dialog.Root bind:open {...$$restProps}>
|
||||
<Dialog.Content class="overflow-hidden p-0">
|
||||
<Command
|
||||
class="[&_[data-cmdk-group-heading]]:px-2 [&_[data-cmdk-group-heading]]:font-medium [&_[data-cmdk-group-heading]]:text-muted-foreground [&_[data-cmdk-group]:not([hidden])_~[data-cmdk-group]]:pt-0 [&_[data-cmdk-group]]:px-2 [&_[data-cmdk-input-wrapper]_svg]:h-5 [&_[data-cmdk-input-wrapper]_svg]:w-5 [&_[data-cmdk-input]]:h-12 [&_[data-cmdk-item]]:px-2 [&_[data-cmdk-item]]:py-3 [&_[data-cmdk-item]_svg]:h-5 [&_[data-cmdk-item]_svg]:w-5"
|
||||
{...$$restProps}
|
||||
bind:value
|
||||
>
|
||||
<slot />
|
||||
</Command>
|
||||
</Dialog.Content>
|
||||
</Dialog.Root>
|
12
apps/web/src/lib/components/ui/command/command-empty.svelte
Normal file
12
apps/web/src/lib/components/ui/command/command-empty.svelte
Normal file
|
@ -0,0 +1,12 @@
|
|||
<script lang="ts">
|
||||
import { Command as CommandPrimitive } from "cmdk-sv";
|
||||
import { cn } from "$lib/utils";
|
||||
|
||||
type $$Props = CommandPrimitive.EmptyProps;
|
||||
let className: string | undefined | null = undefined;
|
||||
export { className as class };
|
||||
</script>
|
||||
|
||||
<CommandPrimitive.Empty class={cn("py-6 text-center text-sm", className)} {...$$restProps}>
|
||||
<slot />
|
||||
</CommandPrimitive.Empty>
|
18
apps/web/src/lib/components/ui/command/command-group.svelte
Normal file
18
apps/web/src/lib/components/ui/command/command-group.svelte
Normal file
|
@ -0,0 +1,18 @@
|
|||
<script lang="ts">
|
||||
import { Command as CommandPrimitive } from "cmdk-sv";
|
||||
import { cn } from "$lib/utils";
|
||||
type $$Props = CommandPrimitive.GroupProps;
|
||||
|
||||
let className: string | undefined | null = undefined;
|
||||
export { className as class };
|
||||
</script>
|
||||
|
||||
<CommandPrimitive.Group
|
||||
class={cn(
|
||||
"overflow-hidden p-1 text-foreground [&_[data-cmdk-group-heading]]:px-2 [&_[data-cmdk-group-heading]]:py-1.5 [&_[data-cmdk-group-heading]]:text-xs [&_[data-cmdk-group-heading]]:font-medium [&_[data-cmdk-group-heading]]:text-muted-foreground",
|
||||
className
|
||||
)}
|
||||
{...$$restProps}
|
||||
>
|
||||
<slot />
|
||||
</CommandPrimitive.Group>
|
23
apps/web/src/lib/components/ui/command/command-input.svelte
Normal file
23
apps/web/src/lib/components/ui/command/command-input.svelte
Normal file
|
@ -0,0 +1,23 @@
|
|||
<script lang="ts">
|
||||
import { Command as CommandPrimitive } from "cmdk-sv";
|
||||
import { MagnifyingGlass } from "radix-icons-svelte";
|
||||
import { cn } from "$lib/utils";
|
||||
|
||||
type $$Props = CommandPrimitive.InputProps;
|
||||
|
||||
let className: string | undefined | null = undefined;
|
||||
export { className as class };
|
||||
export let value: string = "";
|
||||
</script>
|
||||
|
||||
<div class="flex items-center border-b px-3" data-cmdk-input-wrapper="">
|
||||
<MagnifyingGlass class="mr-2 h-4 w-4 shrink-0 opacity-50" />
|
||||
<CommandPrimitive.Input
|
||||
class={cn(
|
||||
"flex h-10 w-full rounded-md bg-transparent py-3 text-sm outline-none placeholder:text-muted-foreground disabled:cursor-not-allowed disabled:opacity-50",
|
||||
className
|
||||
)}
|
||||
{...$$restProps}
|
||||
bind:value
|
||||
/>
|
||||
</div>
|
19
apps/web/src/lib/components/ui/command/command-item.svelte
Normal file
19
apps/web/src/lib/components/ui/command/command-item.svelte
Normal file
|
@ -0,0 +1,19 @@
|
|||
<script lang="ts">
|
||||
import { Command as CommandPrimitive } from "cmdk-sv";
|
||||
import { cn } from "$lib/utils";
|
||||
|
||||
type $$Props = CommandPrimitive.ItemProps;
|
||||
|
||||
let className: string | undefined | null = undefined;
|
||||
export { className as class };
|
||||
</script>
|
||||
|
||||
<CommandPrimitive.Item
|
||||
class={cn(
|
||||
"relative flex cursor-default select-none items-center rounded-sm px-2 py-1.5 text-sm outline-none aria-selected:bg-accent aria-selected:text-accent-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50",
|
||||
className
|
||||
)}
|
||||
{...$$restProps}
|
||||
>
|
||||
<slot />
|
||||
</CommandPrimitive.Item>
|
15
apps/web/src/lib/components/ui/command/command-list.svelte
Normal file
15
apps/web/src/lib/components/ui/command/command-list.svelte
Normal file
|
@ -0,0 +1,15 @@
|
|||
<script lang="ts">
|
||||
import { Command as CommandPrimitive } from "cmdk-sv";
|
||||
import { cn } from "$lib/utils";
|
||||
|
||||
type $$Props = CommandPrimitive.ListProps;
|
||||
let className: string | undefined | null = undefined;
|
||||
export { className as class };
|
||||
</script>
|
||||
|
||||
<CommandPrimitive.List
|
||||
class={cn("max-h-[300px] overflow-y-auto overflow-x-hidden", className)}
|
||||
{...$$restProps}
|
||||
>
|
||||
<slot />
|
||||
</CommandPrimitive.List>
|
|
@ -0,0 +1,10 @@
|
|||
<script lang="ts">
|
||||
import { Command as CommandPrimitive } from "cmdk-sv";
|
||||
import { cn } from "$lib/utils";
|
||||
|
||||
type $$Props = CommandPrimitive.SeparatorProps;
|
||||
let className: string | undefined | null = undefined;
|
||||
export { className as class };
|
||||
</script>
|
||||
|
||||
<CommandPrimitive.Separator class={cn("-mx-1 h-px bg-border", className)} {...$$restProps} />
|
|
@ -0,0 +1,16 @@
|
|||
<script lang="ts">
|
||||
import { cn } from "$lib/utils";
|
||||
import type { HTMLAttributes } from "svelte/elements";
|
||||
|
||||
type $$Props = HTMLAttributes<HTMLSpanElement>;
|
||||
|
||||
let className: string | undefined | null = undefined;
|
||||
export { className as class };
|
||||
</script>
|
||||
|
||||
<span
|
||||
class={cn("ml-auto text-xs tracking-widest text-muted-foreground", className)}
|
||||
{...$$restProps}
|
||||
>
|
||||
<slot />
|
||||
</span>
|
22
apps/web/src/lib/components/ui/command/command.svelte
Normal file
22
apps/web/src/lib/components/ui/command/command.svelte
Normal file
|
@ -0,0 +1,22 @@
|
|||
<script lang="ts">
|
||||
import { Command as CommandPrimitive } from "cmdk-sv";
|
||||
import { cn } from "$lib/utils";
|
||||
|
||||
type $$Props = CommandPrimitive.CommandProps;
|
||||
|
||||
export let value: $$Props["value"] = undefined;
|
||||
|
||||
let className: string | undefined | null = undefined;
|
||||
export { className as class };
|
||||
</script>
|
||||
|
||||
<CommandPrimitive.Root
|
||||
class={cn(
|
||||
"flex h-full w-full flex-col overflow-hidden rounded-md bg-popover text-popover-foreground",
|
||||
className
|
||||
)}
|
||||
bind:value
|
||||
{...$$restProps}
|
||||
>
|
||||
<slot />
|
||||
</CommandPrimitive.Root>
|
37
apps/web/src/lib/components/ui/command/index.ts
Normal file
37
apps/web/src/lib/components/ui/command/index.ts
Normal file
|
@ -0,0 +1,37 @@
|
|||
import { Command as CommandPrimitive } from "cmdk-sv";
|
||||
|
||||
import Root from "./command.svelte";
|
||||
import Dialog from "./command-dialog.svelte";
|
||||
import Empty from "./command-empty.svelte";
|
||||
import Group from "./command-group.svelte";
|
||||
import Item from "./command-item.svelte";
|
||||
import Input from "./command-input.svelte";
|
||||
import List from "./command-list.svelte";
|
||||
import Separator from "./command-separator.svelte";
|
||||
import Shortcut from "./command-shortcut.svelte";
|
||||
|
||||
const Loading = CommandPrimitive.Loading;
|
||||
|
||||
export {
|
||||
Root,
|
||||
Dialog,
|
||||
Empty,
|
||||
Group,
|
||||
Item,
|
||||
Input,
|
||||
List,
|
||||
Separator,
|
||||
Shortcut,
|
||||
Loading,
|
||||
//
|
||||
Root as Command,
|
||||
Dialog as CommandDialog,
|
||||
Empty as CommandEmpty,
|
||||
Group as CommandGroup,
|
||||
Item as CommandItem,
|
||||
Input as CommandInput,
|
||||
List as CommandList,
|
||||
Separator as CommandSeparator,
|
||||
Shortcut as CommandShortcut,
|
||||
Loading as CommandLoading
|
||||
};
|
36
apps/web/src/lib/components/ui/dialog/dialog-content.svelte
Normal file
36
apps/web/src/lib/components/ui/dialog/dialog-content.svelte
Normal file
|
@ -0,0 +1,36 @@
|
|||
<script lang="ts">
|
||||
import { Dialog as DialogPrimitive } from "bits-ui";
|
||||
import * as Dialog from ".";
|
||||
import { cn, flyAndScale } from "$lib/utils";
|
||||
import { Cross2 } from "radix-icons-svelte";
|
||||
|
||||
type $$Props = DialogPrimitive.ContentProps;
|
||||
|
||||
let className: $$Props["class"] = undefined;
|
||||
export let transition: $$Props["transition"] = flyAndScale;
|
||||
export let transitionConfig: $$Props["transitionConfig"] = {
|
||||
duration: 200
|
||||
};
|
||||
export { className as class };
|
||||
</script>
|
||||
|
||||
<Dialog.Portal>
|
||||
<Dialog.Overlay />
|
||||
<DialogPrimitive.Content
|
||||
{transition}
|
||||
{transitionConfig}
|
||||
class={cn(
|
||||
"fixed left-[50%] top-[50%] z-50 grid w-full max-w-lg translate-x-[-50%] translate-y-[-50%] gap-4 border bg-background p-6 shadow-lg sm:rounded-lg md:w-full",
|
||||
className
|
||||
)}
|
||||
{...$$restProps}
|
||||
>
|
||||
<slot />
|
||||
<DialogPrimitive.Close
|
||||
class="absolute right-4 top-4 rounded-sm opacity-70 ring-offset-background transition-opacity hover:opacity-100 focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2 disabled:pointer-events-none data-[state=open]:bg-accent data-[state=open]:text-muted-foreground"
|
||||
>
|
||||
<Cross2 class="h-4 w-4" />
|
||||
<span class="sr-only">Close</span>
|
||||
</DialogPrimitive.Close>
|
||||
</DialogPrimitive.Content>
|
||||
</Dialog.Portal>
|
|
@ -0,0 +1,16 @@
|
|||
<script lang="ts">
|
||||
import { Dialog as DialogPrimitive } from "bits-ui";
|
||||
import { cn } from "$lib/utils";
|
||||
|
||||
type $$Props = DialogPrimitive.DescriptionProps;
|
||||
|
||||
let className: $$Props["class"] = undefined;
|
||||
export { className as class };
|
||||
</script>
|
||||
|
||||
<DialogPrimitive.Description
|
||||
class={cn("text-sm text-muted-foreground", className)}
|
||||
{...$$restProps}
|
||||
>
|
||||
<slot />
|
||||
</DialogPrimitive.Description>
|
16
apps/web/src/lib/components/ui/dialog/dialog-footer.svelte
Normal file
16
apps/web/src/lib/components/ui/dialog/dialog-footer.svelte
Normal file
|
@ -0,0 +1,16 @@
|
|||
<script lang="ts">
|
||||
import { cn } from "$lib/utils";
|
||||
import type { HTMLAttributes } from "svelte/elements";
|
||||
|
||||
type $$Props = HTMLAttributes<HTMLDivElement>;
|
||||
|
||||
let className: $$Props["class"] = undefined;
|
||||
export { className as class };
|
||||
</script>
|
||||
|
||||
<div
|
||||
class={cn("flex flex-col-reverse sm:flex-row sm:justify-end sm:space-x-2", className)}
|
||||
{...$$restProps}
|
||||
>
|
||||
<slot />
|
||||
</div>
|
13
apps/web/src/lib/components/ui/dialog/dialog-header.svelte
Normal file
13
apps/web/src/lib/components/ui/dialog/dialog-header.svelte
Normal file
|
@ -0,0 +1,13 @@
|
|||
<script lang="ts">
|
||||
import { cn } from "$lib/utils";
|
||||
import type { HTMLAttributes } from "svelte/elements";
|
||||
|
||||
type $$Props = HTMLAttributes<HTMLDivElement>;
|
||||
|
||||
let className: $$Props["class"] = undefined;
|
||||
export { className as class };
|
||||
</script>
|
||||
|
||||
<div class={cn("flex flex-col space-y-1.5 text-center sm:text-left", className)} {...$$restProps}>
|
||||
<slot />
|
||||
</div>
|
21
apps/web/src/lib/components/ui/dialog/dialog-overlay.svelte
Normal file
21
apps/web/src/lib/components/ui/dialog/dialog-overlay.svelte
Normal file
|
@ -0,0 +1,21 @@
|
|||
<script lang="ts">
|
||||
import { Dialog as DialogPrimitive } from "bits-ui";
|
||||
import { cn } from "$lib/utils";
|
||||
import { fade } from "svelte/transition";
|
||||
|
||||
type $$Props = DialogPrimitive.OverlayProps;
|
||||
|
||||
let className: $$Props["class"] = undefined;
|
||||
export let transition: $$Props["transition"] = fade;
|
||||
export let transitionConfig: $$Props["transitionConfig"] = {
|
||||
duration: 150
|
||||
};
|
||||
export { className as class };
|
||||
</script>
|
||||
|
||||
<DialogPrimitive.Overlay
|
||||
{transition}
|
||||
{transitionConfig}
|
||||
class={cn("fixed inset-0 z-50 bg-background/80 backdrop-blur-sm ", className)}
|
||||
{...$$restProps}
|
||||
/>
|
|
@ -0,0 +1,9 @@
|
|||
<script lang="ts">
|
||||
import { Dialog as DialogPrimitive } from "bits-ui";
|
||||
|
||||
type $$Props = DialogPrimitive.PortalProps;
|
||||
</script>
|
||||
|
||||
<DialogPrimitive.Portal {...$$restProps}>
|
||||
<slot />
|
||||
</DialogPrimitive.Portal>
|
16
apps/web/src/lib/components/ui/dialog/dialog-title.svelte
Normal file
16
apps/web/src/lib/components/ui/dialog/dialog-title.svelte
Normal file
|
@ -0,0 +1,16 @@
|
|||
<script lang="ts">
|
||||
import { Dialog as DialogPrimitive } from "bits-ui";
|
||||
import { cn } from "$lib/utils";
|
||||
|
||||
type $$Props = DialogPrimitive.TitleProps;
|
||||
|
||||
let className: $$Props["class"] = undefined;
|
||||
export { className as class };
|
||||
</script>
|
||||
|
||||
<DialogPrimitive.Title
|
||||
class={cn("text-lg font-semibold leading-none tracking-tight", className)}
|
||||
{...$$restProps}
|
||||
>
|
||||
<slot />
|
||||
</DialogPrimitive.Title>
|
34
apps/web/src/lib/components/ui/dialog/index.ts
Normal file
34
apps/web/src/lib/components/ui/dialog/index.ts
Normal file
|
@ -0,0 +1,34 @@
|
|||
import { Dialog as DialogPrimitive } from "bits-ui";
|
||||
|
||||
const Root = DialogPrimitive.Root;
|
||||
const Trigger = DialogPrimitive.Trigger;
|
||||
|
||||
import Title from "./dialog-title.svelte";
|
||||
import Portal from "./dialog-portal.svelte";
|
||||
import Footer from "./dialog-footer.svelte";
|
||||
import Header from "./dialog-header.svelte";
|
||||
import Overlay from "./dialog-overlay.svelte";
|
||||
import Content from "./dialog-content.svelte";
|
||||
import Description from "./dialog-description.svelte";
|
||||
|
||||
export {
|
||||
Root,
|
||||
Title,
|
||||
Portal,
|
||||
Footer,
|
||||
Header,
|
||||
Trigger,
|
||||
Overlay,
|
||||
Content,
|
||||
Description,
|
||||
//
|
||||
Root as Dialog,
|
||||
Title as DialogTitle,
|
||||
Portal as DialogPortal,
|
||||
Footer as DialogFooter,
|
||||
Header as DialogHeader,
|
||||
Trigger as DialogTrigger,
|
||||
Overlay as DialogOverlay,
|
||||
Content as DialogContent,
|
||||
Description as DialogDescription
|
||||
};
|
9
apps/web/src/lib/components/ui/form/form-button.svelte
Normal file
9
apps/web/src/lib/components/ui/form/form-button.svelte
Normal file
|
@ -0,0 +1,9 @@
|
|||
<script lang="ts">
|
||||
import * as Button from "$lib/components/ui/button";
|
||||
type $$Props = Button.Props;
|
||||
type $$Events = Button.Events;
|
||||
</script>
|
||||
|
||||
<Button.Root type="submit" {...$$restProps} on:click on:keydown>
|
||||
<slot />
|
||||
</Button.Root>
|
26
apps/web/src/lib/components/ui/form/form-checkbox.svelte
Normal file
26
apps/web/src/lib/components/ui/form/form-checkbox.svelte
Normal file
|
@ -0,0 +1,26 @@
|
|||
<script lang="ts">
|
||||
import { getFormField } from "formsnap";
|
||||
import type { Checkbox as CheckboxPrimitive } from "bits-ui";
|
||||
import { Checkbox } from "$lib/components/ui/checkbox";
|
||||
type $$Props = CheckboxPrimitive.Props;
|
||||
type $$Events = CheckboxPrimitive.Events;
|
||||
|
||||
export let onCheckedChange: $$Props["onCheckedChange"] = undefined;
|
||||
|
||||
const { name, setValue, attrStore, value } = getFormField();
|
||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||
const { name: nameAttr, value: valueAttr, ...rest } = $attrStore;
|
||||
</script>
|
||||
|
||||
<Checkbox
|
||||
{...rest}
|
||||
checked={typeof $value === "boolean" ? $value : false}
|
||||
onCheckedChange={(v) => {
|
||||
onCheckedChange?.(v);
|
||||
setValue(v);
|
||||
}}
|
||||
{...$$restProps}
|
||||
on:click
|
||||
on:keydown
|
||||
/>
|
||||
<input hidden {name} value={$value} />
|
16
apps/web/src/lib/components/ui/form/form-description.svelte
Normal file
16
apps/web/src/lib/components/ui/form/form-description.svelte
Normal file
|
@ -0,0 +1,16 @@
|
|||
<script lang="ts">
|
||||
import { Form as FormPrimitive } from "formsnap";
|
||||
import { cn } from "$lib/utils";
|
||||
import type { HTMLAttributes } from "svelte/elements";
|
||||
|
||||
type $$Props = HTMLAttributes<HTMLSpanElement>;
|
||||
let className: string | undefined | null = undefined;
|
||||
export { className as class };
|
||||
</script>
|
||||
|
||||
<FormPrimitive.Description
|
||||
class={cn("text-[0.8rem] text-muted-foreground", className)}
|
||||
{...$$restProps}
|
||||
>
|
||||
<slot />
|
||||
</FormPrimitive.Description>
|
28
apps/web/src/lib/components/ui/form/form-input.svelte
Normal file
28
apps/web/src/lib/components/ui/form/form-input.svelte
Normal file
|
@ -0,0 +1,28 @@
|
|||
<script lang="ts">
|
||||
import { getFormField } from "formsnap";
|
||||
import type { HTMLInputAttributes } from "svelte/elements";
|
||||
import { Input, type InputEvents } from "$lib/components/ui/input";
|
||||
|
||||
type $$Props = HTMLInputAttributes;
|
||||
type $$Events = InputEvents;
|
||||
|
||||
const { attrStore, value } = getFormField();
|
||||
</script>
|
||||
|
||||
<Input
|
||||
{...$attrStore}
|
||||
bind:value={$value}
|
||||
{...$$restProps}
|
||||
on:blur
|
||||
on:change
|
||||
on:click
|
||||
on:focus
|
||||
on:keydown
|
||||
on:keypress
|
||||
on:keyup
|
||||
on:mouseover
|
||||
on:mouseenter
|
||||
on:mouseleave
|
||||
on:paste
|
||||
on:input
|
||||
/>
|
12
apps/web/src/lib/components/ui/form/form-item.svelte
Normal file
12
apps/web/src/lib/components/ui/form/form-item.svelte
Normal file
|
@ -0,0 +1,12 @@
|
|||
<script lang="ts">
|
||||
import { cn } from "$lib/utils";
|
||||
import type { HTMLAttributes } from "svelte/elements";
|
||||
|
||||
type $$Props = HTMLAttributes<HTMLDivElement>;
|
||||
let className: string | undefined | null = undefined;
|
||||
export { className as class };
|
||||
</script>
|
||||
|
||||
<div class={cn("space-y-2", className)} {...$$restProps}>
|
||||
<slot />
|
||||
</div>
|
17
apps/web/src/lib/components/ui/form/form-label.svelte
Normal file
17
apps/web/src/lib/components/ui/form/form-label.svelte
Normal file
|
@ -0,0 +1,17 @@
|
|||
<script lang="ts">
|
||||
import type { Label as LabelPrimitive } from "bits-ui";
|
||||
import { getFormField } from "formsnap";
|
||||
import { cn } from "$lib/utils";
|
||||
import { Label } from "$lib/components/ui/label";
|
||||
|
||||
type $$Props = LabelPrimitive.Props;
|
||||
|
||||
let className: $$Props["class"] = undefined;
|
||||
export { className as class };
|
||||
|
||||
const { errors, ids } = getFormField();
|
||||
</script>
|
||||
|
||||
<Label for={$ids.input} class={cn($errors && "text-destructive", className)} {...$$restProps}>
|
||||
<slot />
|
||||
</Label>
|
|
@ -0,0 +1,26 @@
|
|||
<script lang="ts">
|
||||
import { Form as FormPrimitive } from "formsnap";
|
||||
import { buttonVariants } from "$lib/components/ui/button";
|
||||
import { cn } from "$lib/utils";
|
||||
import { CaretSort } from "radix-icons-svelte";
|
||||
import type { HTMLSelectAttributes } from "svelte/elements";
|
||||
|
||||
type $$Props = HTMLSelectAttributes;
|
||||
|
||||
let className: string | undefined | null = undefined;
|
||||
export { className as class };
|
||||
</script>
|
||||
|
||||
<div class="relative">
|
||||
<FormPrimitive.Select
|
||||
class={cn(
|
||||
buttonVariants({ variant: "outline" }),
|
||||
"appearance-none bg-transparent font-normal",
|
||||
className
|
||||
)}
|
||||
{...$$restProps}
|
||||
>
|
||||
<slot />
|
||||
</FormPrimitive.Select>
|
||||
<CaretSort class="absolute right-3 top-2.5 h-4 w-4 opacity-50" />
|
||||
</div>
|
22
apps/web/src/lib/components/ui/form/form-radio-group.svelte
Normal file
22
apps/web/src/lib/components/ui/form/form-radio-group.svelte
Normal file
|
@ -0,0 +1,22 @@
|
|||
<script lang="ts">
|
||||
import { getFormField } from "formsnap";
|
||||
import type { RadioGroup as RadioGroupPrimitive } from "bits-ui";
|
||||
import * as RadioGroup from "$lib/components/ui/radio-group";
|
||||
|
||||
type $$Props = RadioGroupPrimitive.Props;
|
||||
const { attrStore, setValue, name, value } = getFormField();
|
||||
|
||||
export let onValueChange: $$Props["onValueChange"] = undefined;
|
||||
</script>
|
||||
|
||||
<RadioGroup.Root
|
||||
{...$attrStore}
|
||||
onValueChange={(v) => {
|
||||
onValueChange?.(v);
|
||||
setValue(v);
|
||||
}}
|
||||
{...$$restProps}
|
||||
>
|
||||
<slot />
|
||||
<input hidden {name} value={$value} />
|
||||
</RadioGroup.Root>
|
|
@ -0,0 +1,18 @@
|
|||
<script lang="ts">
|
||||
import * as Select from "$lib/components/ui/select";
|
||||
import type { Select as SelectPrimitive } from "bits-ui";
|
||||
import { getFormField } from "formsnap";
|
||||
|
||||
type $$Props = SelectPrimitive.TriggerProps & {
|
||||
placeholder?: string;
|
||||
};
|
||||
type $$Events = SelectPrimitive.TriggerEvents;
|
||||
const { attrStore, value } = getFormField();
|
||||
export let placeholder = "";
|
||||
</script>
|
||||
|
||||
<Select.Trigger {...$$restProps} {...$attrStore} on:click on:keydown>
|
||||
<slot value={$value}>
|
||||
<Select.Value {placeholder} />
|
||||
</slot>
|
||||
</Select.Trigger>
|
20
apps/web/src/lib/components/ui/form/form-select.svelte
Normal file
20
apps/web/src/lib/components/ui/form/form-select.svelte
Normal file
|
@ -0,0 +1,20 @@
|
|||
<script lang="ts">
|
||||
import * as Select from "$lib/components/ui/select";
|
||||
import { getFormField } from "formsnap";
|
||||
import type { Select as SelectPrimitive } from "bits-ui";
|
||||
|
||||
type $$Props = SelectPrimitive.Props<unknown>;
|
||||
const { setValue, name, value } = getFormField();
|
||||
export let onSelectedChange: $$Props["onSelectedChange"] = undefined;
|
||||
</script>
|
||||
|
||||
<Select.Root
|
||||
onSelectedChange={(v) => {
|
||||
onSelectedChange?.(v);
|
||||
setValue(v ? v.value : undefined);
|
||||
}}
|
||||
{...$$restProps}
|
||||
>
|
||||
<slot />
|
||||
<input hidden {name} value={$value} />
|
||||
</Select.Root>
|
24
apps/web/src/lib/components/ui/form/form-switch.svelte
Normal file
24
apps/web/src/lib/components/ui/form/form-switch.svelte
Normal file
|
@ -0,0 +1,24 @@
|
|||
<script lang="ts">
|
||||
import { getFormField } from "formsnap";
|
||||
import type { Switch as SwitchPrimitive } from "bits-ui";
|
||||
import { Switch } from "$lib/components/ui/switch";
|
||||
type $$Props = SwitchPrimitive.Props;
|
||||
type $$Events = SwitchPrimitive.Events;
|
||||
|
||||
export let onCheckedChange: $$Props["onCheckedChange"] = undefined;
|
||||
|
||||
const { name, setValue, attrStore, value } = getFormField();
|
||||
</script>
|
||||
|
||||
<Switch
|
||||
{...$attrStore}
|
||||
checked={typeof $value === "boolean" ? $value : false}
|
||||
onCheckedChange={(v) => {
|
||||
onCheckedChange?.(v);
|
||||
setValue(v);
|
||||
}}
|
||||
{...$$restProps}
|
||||
on:click
|
||||
on:keydown
|
||||
/>
|
||||
<input hidden {name} value={$value} />
|
29
apps/web/src/lib/components/ui/form/form-textarea.svelte
Normal file
29
apps/web/src/lib/components/ui/form/form-textarea.svelte
Normal file
|
@ -0,0 +1,29 @@
|
|||
<script lang="ts">
|
||||
import { getFormField } from "formsnap";
|
||||
import type { HTMLTextareaAttributes } from "svelte/elements";
|
||||
import type { TextareaGetFormField } from ".";
|
||||
import { Textarea, type TextareaEvents } from "$lib/components/ui/textarea";
|
||||
|
||||
type $$Props = HTMLTextareaAttributes;
|
||||
type $$Events = TextareaEvents;
|
||||
|
||||
const { attrStore, value } = getFormField() as TextareaGetFormField;
|
||||
</script>
|
||||
|
||||
<Textarea
|
||||
{...$attrStore}
|
||||
bind:value={$value}
|
||||
{...$$restProps}
|
||||
on:blur
|
||||
on:change
|
||||
on:click
|
||||
on:focus
|
||||
on:keydown
|
||||
on:keypress
|
||||
on:keyup
|
||||
on:mouseover
|
||||
on:mouseenter
|
||||
on:mouseleave
|
||||
on:paste
|
||||
on:input
|
||||
/>
|
14
apps/web/src/lib/components/ui/form/form-validation.svelte
Normal file
14
apps/web/src/lib/components/ui/form/form-validation.svelte
Normal file
|
@ -0,0 +1,14 @@
|
|||
<script lang="ts">
|
||||
import { Form as FormPrimitive } from "formsnap";
|
||||
import { cn } from "$lib/utils";
|
||||
import type { HTMLAttributes } from "svelte/elements";
|
||||
|
||||
type $$Props = HTMLAttributes<HTMLParagraphElement>;
|
||||
let className: string | undefined | null = undefined;
|
||||
export { className as class };
|
||||
</script>
|
||||
|
||||
<FormPrimitive.Validation
|
||||
class={cn("text-[0.8rem] font-medium text-destructive", className)}
|
||||
{...$$restProps}
|
||||
/>
|
82
apps/web/src/lib/components/ui/form/index.ts
Normal file
82
apps/web/src/lib/components/ui/form/index.ts
Normal file
|
@ -0,0 +1,82 @@
|
|||
import { Form as FormPrimitive, getFormField } from "formsnap";
|
||||
import type { Writable } from "svelte/store";
|
||||
import * as RadioGroupComp from "$lib/components/ui/radio-group";
|
||||
import * as SelectComp from "$lib/components/ui/select";
|
||||
import Item from "./form-item.svelte";
|
||||
import Input from "./form-input.svelte";
|
||||
import Textarea from "./form-textarea.svelte";
|
||||
import Description from "./form-description.svelte";
|
||||
import Label from "./form-label.svelte";
|
||||
import Validation from "./form-validation.svelte";
|
||||
import Checkbox from "./form-checkbox.svelte";
|
||||
import Switch from "./form-switch.svelte";
|
||||
import NativeSelect from "./form-native-select.svelte";
|
||||
import RadioGroup from "./form-radio-group.svelte";
|
||||
import Select from "./form-select.svelte";
|
||||
import SelectTrigger from "./form-select-trigger.svelte";
|
||||
import Button from "./form-button.svelte";
|
||||
|
||||
const Root = FormPrimitive.Root;
|
||||
const Field = FormPrimitive.Field;
|
||||
const Control = FormPrimitive.Control;
|
||||
const RadioItem = RadioGroupComp.Item;
|
||||
const NativeRadio = FormPrimitive.Radio;
|
||||
const SelectContent = SelectComp.Content;
|
||||
const SelectLabel = SelectComp.Label;
|
||||
const SelectGroup = SelectComp.Group;
|
||||
const SelectItem = SelectComp.Item;
|
||||
const SelectSeparator = SelectComp.Separator;
|
||||
|
||||
export type TextareaGetFormField = Omit<ReturnType<typeof getFormField>, "value"> & {
|
||||
value: Writable<string>;
|
||||
};
|
||||
|
||||
export {
|
||||
Root,
|
||||
Field,
|
||||
Control,
|
||||
Item,
|
||||
Input,
|
||||
Label,
|
||||
Button,
|
||||
Switch,
|
||||
Select,
|
||||
Checkbox,
|
||||
Textarea,
|
||||
Validation,
|
||||
RadioGroup,
|
||||
RadioItem,
|
||||
Description,
|
||||
SelectContent,
|
||||
SelectLabel,
|
||||
SelectGroup,
|
||||
SelectItem,
|
||||
SelectSeparator,
|
||||
SelectTrigger,
|
||||
NativeSelect,
|
||||
NativeRadio,
|
||||
//
|
||||
Root as Form,
|
||||
Field as FormField,
|
||||
Control as FormControl,
|
||||
Item as FormItem,
|
||||
Input as FormInput,
|
||||
Textarea as FormTextarea,
|
||||
Description as FormDescription,
|
||||
Label as FormLabel,
|
||||
Validation as FormValidation,
|
||||
NativeSelect as FormNativeSelect,
|
||||
NativeRadio as FormNativeRadio,
|
||||
Checkbox as FormCheckbox,
|
||||
Switch as FormSwitch,
|
||||
RadioGroup as FormRadioGroup,
|
||||
RadioItem as FormRadioItem,
|
||||
Select as FormSelect,
|
||||
SelectContent as FormSelectContent,
|
||||
SelectLabel as FormSelectLabel,
|
||||
SelectGroup as FormSelectGroup,
|
||||
SelectItem as FormSelectItem,
|
||||
SelectSeparator as FormSelectSeparator,
|
||||
SelectTrigger as FormSelectTrigger,
|
||||
Button as FormButton
|
||||
};
|
14
apps/web/src/lib/components/ui/popover/index.ts
Normal file
14
apps/web/src/lib/components/ui/popover/index.ts
Normal file
|
@ -0,0 +1,14 @@
|
|||
import { Popover as PopoverPrimitive } from "bits-ui";
|
||||
import Content from "./popover-content.svelte";
|
||||
const Root = PopoverPrimitive.Root;
|
||||
const Trigger = PopoverPrimitive.Trigger;
|
||||
|
||||
export {
|
||||
Root,
|
||||
Content,
|
||||
Trigger,
|
||||
//
|
||||
Root as Popover,
|
||||
Content as PopoverContent,
|
||||
Trigger as PopoverTrigger
|
||||
};
|
|
@ -0,0 +1,27 @@
|
|||
<script lang="ts">
|
||||
import { Popover as PopoverPrimitive } from "bits-ui";
|
||||
import { cn, flyAndScale } from "$lib/utils";
|
||||
|
||||
type $$Props = PopoverPrimitive.ContentProps;
|
||||
|
||||
let className: $$Props["class"] = undefined;
|
||||
export let transition: $$Props["transition"] = flyAndScale;
|
||||
export let transitionConfig: $$Props["transitionConfig"] = undefined;
|
||||
export let align: $$Props["align"] = "center";
|
||||
export let sideOffset: $$Props["sideOffset"] = 4;
|
||||
export { className as class };
|
||||
</script>
|
||||
|
||||
<PopoverPrimitive.Content
|
||||
{transition}
|
||||
{transitionConfig}
|
||||
{align}
|
||||
{sideOffset}
|
||||
{...$$restProps}
|
||||
class={cn(
|
||||
"z-50 w-72 rounded-md border bg-popover p-4 text-popover-foreground shadow-md outline-none",
|
||||
className
|
||||
)}
|
||||
>
|
||||
<slot />
|
||||
</PopoverPrimitive.Content>
|
15
apps/web/src/lib/components/ui/radio-group/index.ts
Normal file
15
apps/web/src/lib/components/ui/radio-group/index.ts
Normal file
|
@ -0,0 +1,15 @@
|
|||
import { RadioGroup as RadioGroupPrimitive } from "bits-ui";
|
||||
|
||||
import Root from "./radio-group.svelte";
|
||||
import Item from "./radio-group-item.svelte";
|
||||
const Input = RadioGroupPrimitive.Input;
|
||||
|
||||
export {
|
||||
Root,
|
||||
Input,
|
||||
Item,
|
||||
//
|
||||
Root as RadioGroup,
|
||||
Input as RadioGroupInput,
|
||||
Item as RadioGroupItem
|
||||
};
|
|
@ -0,0 +1,30 @@
|
|||
<script lang="ts">
|
||||
import { RadioGroup as RadioGroupPrimitive } from "bits-ui";
|
||||
import { Check } from "radix-icons-svelte";
|
||||
import { cn } from "$lib/utils";
|
||||
|
||||
type $$Props = RadioGroupPrimitive.ItemProps & {
|
||||
value: string;
|
||||
};
|
||||
type $$Events = RadioGroupPrimitive.ItemEvents;
|
||||
|
||||
let className: $$Props["class"] = undefined;
|
||||
export let value: $$Props["value"];
|
||||
export { className as class };
|
||||
</script>
|
||||
|
||||
<RadioGroupPrimitive.Item
|
||||
{value}
|
||||
class={cn(
|
||||
"aspect-square h-4 w-4 rounded-full border border-primary text-primary shadow focus:outline-none focus-visible:ring-1 focus-visible:ring-ring disabled:cursor-not-allowed disabled:opacity-50",
|
||||
className
|
||||
)}
|
||||
{...$$restProps}
|
||||
on:click
|
||||
>
|
||||
<div class="flex items-center justify-center">
|
||||
<RadioGroupPrimitive.ItemIndicator>
|
||||
<Check class="h-3.5 w-3.5 fill-primary" />
|
||||
</RadioGroupPrimitive.ItemIndicator>
|
||||
</div>
|
||||
</RadioGroupPrimitive.Item>
|
|
@ -0,0 +1,14 @@
|
|||
<script lang="ts">
|
||||
import { RadioGroup as RadioGroupPrimitive } from "bits-ui";
|
||||
import { cn } from "$lib/utils";
|
||||
|
||||
type $$Props = RadioGroupPrimitive.Props;
|
||||
|
||||
let className: $$Props["class"] = undefined;
|
||||
export let value: $$Props["value"] = undefined;
|
||||
export { className as class };
|
||||
</script>
|
||||
|
||||
<RadioGroupPrimitive.Root bind:value class={cn("grid gap-2", className)} {...$$restProps}>
|
||||
<slot />
|
||||
</RadioGroupPrimitive.Root>
|
34
apps/web/src/lib/components/ui/select/index.ts
Normal file
34
apps/web/src/lib/components/ui/select/index.ts
Normal file
|
@ -0,0 +1,34 @@
|
|||
import { Select as SelectPrimitive } from "bits-ui";
|
||||
|
||||
import Label from "./select-label.svelte";
|
||||
import Item from "./select-item.svelte";
|
||||
import Content from "./select-content.svelte";
|
||||
import Trigger from "./select-trigger.svelte";
|
||||
import Separator from "./select-separator.svelte";
|
||||
|
||||
const Root = SelectPrimitive.Root;
|
||||
const Group = SelectPrimitive.Group;
|
||||
const Input = SelectPrimitive.Input;
|
||||
const Value = SelectPrimitive.Value;
|
||||
|
||||
export {
|
||||
Root,
|
||||
Item,
|
||||
Group,
|
||||
Input,
|
||||
Label,
|
||||
Value,
|
||||
Content,
|
||||
Trigger,
|
||||
Separator,
|
||||
//
|
||||
Root as Select,
|
||||
Item as SelectItem,
|
||||
Group as SelectGroup,
|
||||
Input as SelectInput,
|
||||
Label as SelectLabel,
|
||||
Value as SelectValue,
|
||||
Content as SelectContent,
|
||||
Trigger as SelectTrigger,
|
||||
Separator as SelectSeparator
|
||||
};
|
36
apps/web/src/lib/components/ui/select/select-content.svelte
Normal file
36
apps/web/src/lib/components/ui/select/select-content.svelte
Normal file
|
@ -0,0 +1,36 @@
|
|||
<script lang="ts">
|
||||
import { Select as SelectPrimitive } from "bits-ui";
|
||||
import { cn, flyAndScale } from "$lib/utils";
|
||||
import { scale } from "svelte/transition";
|
||||
|
||||
type $$Props = SelectPrimitive.ContentProps;
|
||||
|
||||
let className: $$Props["class"] = undefined;
|
||||
export let sideOffset: $$Props["sideOffset"] = 4;
|
||||
export let inTransition: $$Props["inTransition"] = flyAndScale;
|
||||
export let inTransitionConfig: $$Props["inTransitionConfig"] = undefined;
|
||||
export let outTransition: $$Props["outTransition"] = scale;
|
||||
export let outTransitionConfig: $$Props["outTransitionConfig"] = {
|
||||
start: 0.95,
|
||||
opacity: 0,
|
||||
duration: 50
|
||||
};
|
||||
export { className as class };
|
||||
</script>
|
||||
|
||||
<SelectPrimitive.Content
|
||||
{inTransition}
|
||||
{inTransitionConfig}
|
||||
{outTransition}
|
||||
{outTransitionConfig}
|
||||
{sideOffset}
|
||||
class={cn(
|
||||
"relative z-50 min-w-[8rem] overflow-hidden rounded-md border bg-popover text-popover-foreground shadow-md focus:outline-none",
|
||||
className
|
||||
)}
|
||||
{...$$restProps}
|
||||
>
|
||||
<div class="w-full p-1">
|
||||
<slot />
|
||||
</div>
|
||||
</SelectPrimitive.Content>
|
35
apps/web/src/lib/components/ui/select/select-item.svelte
Normal file
35
apps/web/src/lib/components/ui/select/select-item.svelte
Normal file
|
@ -0,0 +1,35 @@
|
|||
<script lang="ts">
|
||||
import { cn } from "$lib/utils";
|
||||
import { Select as SelectPrimitive } from "bits-ui";
|
||||
import { Check } from "radix-icons-svelte";
|
||||
|
||||
type $$Props = SelectPrimitive.ItemProps;
|
||||
type $$Events = Required<SelectPrimitive.ItemEvents>;
|
||||
|
||||
let className: $$Props["class"] = undefined;
|
||||
export let value: $$Props["value"];
|
||||
export let label: $$Props["label"] = undefined;
|
||||
export let disabled: $$Props["disabled"] = undefined;
|
||||
export { className as class };
|
||||
</script>
|
||||
|
||||
<SelectPrimitive.Item
|
||||
{value}
|
||||
{disabled}
|
||||
{label}
|
||||
class={cn(
|
||||
"relative flex w-full cursor-default select-none items-center rounded-sm py-1.5 pl-2 pr-8 text-sm outline-none data-[highlighted]:bg-accent data-[highlighted]:text-accent-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50",
|
||||
className
|
||||
)}
|
||||
{...$$restProps}
|
||||
on:click
|
||||
on:pointermove
|
||||
on:focusin
|
||||
>
|
||||
<span class="absolute right-2 flex h-3.5 w-3.5 items-center justify-center">
|
||||
<SelectPrimitive.ItemIndicator>
|
||||
<Check class="h-4 w-4" />
|
||||
</SelectPrimitive.ItemIndicator>
|
||||
</span>
|
||||
<slot />
|
||||
</SelectPrimitive.Item>
|
13
apps/web/src/lib/components/ui/select/select-label.svelte
Normal file
13
apps/web/src/lib/components/ui/select/select-label.svelte
Normal file
|
@ -0,0 +1,13 @@
|
|||
<script lang="ts">
|
||||
import { Select as SelectPrimitive } from "bits-ui";
|
||||
import { cn } from "$lib/utils";
|
||||
|
||||
type $$Props = SelectPrimitive.LabelProps;
|
||||
|
||||
let className: $$Props["class"] = undefined;
|
||||
export { className as class };
|
||||
</script>
|
||||
|
||||
<SelectPrimitive.Label class={cn("px-2 py-1.5 text-sm font-semibold", className)} {...$$restProps}>
|
||||
<slot />
|
||||
</SelectPrimitive.Label>
|
|
@ -0,0 +1,11 @@
|
|||
<script lang="ts">
|
||||
import { Select as SelectPrimitive } from "bits-ui";
|
||||
import { cn } from "$lib/utils";
|
||||
|
||||
type $$Props = SelectPrimitive.SeparatorProps;
|
||||
|
||||
let className: $$Props["class"] = undefined;
|
||||
export { className as class };
|
||||
</script>
|
||||
|
||||
<SelectPrimitive.Separator class={cn("-mx-1 my-1 h-px bg-muted", className)} {...$$restProps} />
|
22
apps/web/src/lib/components/ui/select/select-trigger.svelte
Normal file
22
apps/web/src/lib/components/ui/select/select-trigger.svelte
Normal file
|
@ -0,0 +1,22 @@
|
|||
<script lang="ts">
|
||||
import { Select as SelectPrimitive } from 'bits-ui';
|
||||
import { CaretSort } from 'radix-icons-svelte';
|
||||
import { cn } from '$lib/utils';
|
||||
|
||||
type $$Props = SelectPrimitive.TriggerProps;
|
||||
type $$Events = SelectPrimitive.TriggerEvents;
|
||||
|
||||
let className: $$Props['class'] = undefined;
|
||||
export { className as class };
|
||||
</script>
|
||||
|
||||
<SelectPrimitive.Trigger
|
||||
class={cn(
|
||||
'border-input ring-offset-background placeholder:text-muted-foreground focus:ring-ring line-clamp-1 flex h-9 w-full items-center justify-between truncate rounded-md border bg-transparent px-3 py-2 text-sm shadow-sm focus:outline-none focus:ring-1 disabled:cursor-not-allowed disabled:opacity-50 [&>span]:line-clamp-1',
|
||||
className
|
||||
)}
|
||||
{...$$restProps}
|
||||
>
|
||||
<slot />
|
||||
<CaretSort class="h-4 w-4 opacity-50" />
|
||||
</SelectPrimitive.Trigger>
|
7
apps/web/src/lib/components/ui/separator/index.ts
Normal file
7
apps/web/src/lib/components/ui/separator/index.ts
Normal file
|
@ -0,0 +1,7 @@
|
|||
import Root from "./separator.svelte";
|
||||
|
||||
export {
|
||||
Root,
|
||||
//
|
||||
Root as Separator
|
||||
};
|
22
apps/web/src/lib/components/ui/separator/separator.svelte
Normal file
22
apps/web/src/lib/components/ui/separator/separator.svelte
Normal file
|
@ -0,0 +1,22 @@
|
|||
<script lang="ts">
|
||||
import { Separator as SeparatorPrimitive } from "bits-ui";
|
||||
import { cn } from "$lib/utils";
|
||||
|
||||
type $$Props = SeparatorPrimitive.Props;
|
||||
|
||||
let className: $$Props["class"] = undefined;
|
||||
export let orientation: $$Props["orientation"] = "horizontal";
|
||||
export let decorative: $$Props["decorative"] = undefined;
|
||||
export { className as class };
|
||||
</script>
|
||||
|
||||
<SeparatorPrimitive.Root
|
||||
class={cn(
|
||||
"shrink-0 bg-border",
|
||||
orientation === "horizontal" ? "h-[1px] w-full" : "h-full w-[1px]",
|
||||
className
|
||||
)}
|
||||
{orientation}
|
||||
{decorative}
|
||||
{...$$restProps}
|
||||
/>
|
7
apps/web/src/lib/components/ui/switch/index.ts
Normal file
7
apps/web/src/lib/components/ui/switch/index.ts
Normal file
|
@ -0,0 +1,7 @@
|
|||
import Root from "./switch.svelte";
|
||||
|
||||
export {
|
||||
Root,
|
||||
//
|
||||
Root as Switch
|
||||
};
|
28
apps/web/src/lib/components/ui/switch/switch.svelte
Normal file
28
apps/web/src/lib/components/ui/switch/switch.svelte
Normal file
|
@ -0,0 +1,28 @@
|
|||
<script lang="ts">
|
||||
import { Switch as SwitchPrimitive } from "bits-ui";
|
||||
import { cn } from "$lib/utils";
|
||||
|
||||
type $$Props = SwitchPrimitive.Props;
|
||||
type $$Events = SwitchPrimitive.Events;
|
||||
|
||||
let className: $$Props["class"] = undefined;
|
||||
export let checked: $$Props["checked"] = undefined;
|
||||
export { className as class };
|
||||
</script>
|
||||
|
||||
<SwitchPrimitive.Root
|
||||
bind:checked
|
||||
class={cn(
|
||||
"peer inline-flex h-[20px] w-[36px] shrink-0 cursor-pointer items-center rounded-full border-2 border-transparent shadow-sm transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 focus-visible:ring-offset-background disabled:cursor-not-allowed disabled:opacity-50 data-[state=checked]:bg-primary data-[state=unchecked]:bg-input",
|
||||
className
|
||||
)}
|
||||
{...$$restProps}
|
||||
on:click
|
||||
on:keydown
|
||||
>
|
||||
<SwitchPrimitive.Thumb
|
||||
class={cn(
|
||||
"pointer-events-none block h-4 w-4 rounded-full bg-background shadow-lg ring-0 transition-transform data-[state=checked]:translate-x-4 data-[state=unchecked]:translate-x-0"
|
||||
)}
|
||||
/>
|
||||
</SwitchPrimitive.Root>
|
28
apps/web/src/lib/components/ui/table/index.ts
Normal file
28
apps/web/src/lib/components/ui/table/index.ts
Normal file
|
@ -0,0 +1,28 @@
|
|||
import Root from "./table.svelte";
|
||||
import Body from "./table-body.svelte";
|
||||
import Caption from "./table-caption.svelte";
|
||||
import Cell from "./table-cell.svelte";
|
||||
import Footer from "./table-footer.svelte";
|
||||
import Head from "./table-head.svelte";
|
||||
import Header from "./table-header.svelte";
|
||||
import Row from "./table-row.svelte";
|
||||
|
||||
export {
|
||||
Root,
|
||||
Body,
|
||||
Caption,
|
||||
Cell,
|
||||
Footer,
|
||||
Head,
|
||||
Header,
|
||||
Row,
|
||||
//
|
||||
Root as Table,
|
||||
Body as TableBody,
|
||||
Caption as TableCaption,
|
||||
Cell as TableCell,
|
||||
Footer as TableFooter,
|
||||
Head as TableHead,
|
||||
Header as TableHeader,
|
||||
Row as TableRow
|
||||
};
|
13
apps/web/src/lib/components/ui/table/table-body.svelte
Normal file
13
apps/web/src/lib/components/ui/table/table-body.svelte
Normal file
|
@ -0,0 +1,13 @@
|
|||
<script lang="ts">
|
||||
import { cn } from "$lib/utils";
|
||||
import type { HTMLAttributes } from "svelte/elements";
|
||||
|
||||
type $$Props = HTMLAttributes<HTMLTableSectionElement>;
|
||||
|
||||
let className: $$Props["class"] = undefined;
|
||||
export { className as class };
|
||||
</script>
|
||||
|
||||
<tbody class={cn("[&_tr:last-child]:border-0", className)} {...$$restProps}>
|
||||
<slot />
|
||||
</tbody>
|
13
apps/web/src/lib/components/ui/table/table-caption.svelte
Normal file
13
apps/web/src/lib/components/ui/table/table-caption.svelte
Normal file
|
@ -0,0 +1,13 @@
|
|||
<script lang="ts">
|
||||
import { cn } from "$lib/utils";
|
||||
import type { HTMLAttributes } from "svelte/elements";
|
||||
|
||||
type $$Props = HTMLAttributes<HTMLTableCaptionElement>;
|
||||
|
||||
let className: $$Props["class"] = undefined;
|
||||
export { className as class };
|
||||
</script>
|
||||
|
||||
<caption class={cn("mt-4 text-sm text-muted-foreground", className)} {...$$restProps}>
|
||||
<slot />
|
||||
</caption>
|
21
apps/web/src/lib/components/ui/table/table-cell.svelte
Normal file
21
apps/web/src/lib/components/ui/table/table-cell.svelte
Normal file
|
@ -0,0 +1,21 @@
|
|||
<script lang="ts">
|
||||
import { cn } from "$lib/utils";
|
||||
import type { HTMLTdAttributes } from "svelte/elements";
|
||||
|
||||
type $$Props = HTMLTdAttributes;
|
||||
|
||||
let className: $$Props["class"] = undefined;
|
||||
export { className as class };
|
||||
</script>
|
||||
|
||||
<td
|
||||
class={cn(
|
||||
"p-2 align-middle [&:has([role=checkbox])]:pr-0 [&>[role=checkbox]]:translate-y-[2px]",
|
||||
className
|
||||
)}
|
||||
{...$$restProps}
|
||||
on:click
|
||||
on:keydown
|
||||
>
|
||||
<slot />
|
||||
</td>
|
13
apps/web/src/lib/components/ui/table/table-footer.svelte
Normal file
13
apps/web/src/lib/components/ui/table/table-footer.svelte
Normal file
|
@ -0,0 +1,13 @@
|
|||
<script lang="ts">
|
||||
import { cn } from "$lib/utils";
|
||||
import type { HTMLAttributes } from "svelte/elements";
|
||||
|
||||
type $$Props = HTMLAttributes<HTMLTableSectionElement>;
|
||||
|
||||
let className: $$Props["class"] = undefined;
|
||||
export { className as class };
|
||||
</script>
|
||||
|
||||
<tfoot class={cn("bg-primary font-medium text-primary-foreground", className)} {...$$restProps}>
|
||||
<slot />
|
||||
</tfoot>
|
19
apps/web/src/lib/components/ui/table/table-head.svelte
Normal file
19
apps/web/src/lib/components/ui/table/table-head.svelte
Normal file
|
@ -0,0 +1,19 @@
|
|||
<script lang="ts">
|
||||
import { cn } from "$lib/utils";
|
||||
import type { HTMLThAttributes } from "svelte/elements";
|
||||
|
||||
type $$Props = HTMLThAttributes;
|
||||
|
||||
let className: $$Props["class"] = undefined;
|
||||
export { className as class };
|
||||
</script>
|
||||
|
||||
<th
|
||||
class={cn(
|
||||
"h-10 px-2 text-left align-middle font-medium text-muted-foreground [&:has([role=checkbox])]:pr-0 [&>[role=checkbox]]:translate-y-[2px]",
|
||||
className
|
||||
)}
|
||||
{...$$restProps}
|
||||
>
|
||||
<slot />
|
||||
</th>
|
14
apps/web/src/lib/components/ui/table/table-header.svelte
Normal file
14
apps/web/src/lib/components/ui/table/table-header.svelte
Normal file
|
@ -0,0 +1,14 @@
|
|||
<script lang="ts">
|
||||
import { cn } from "$lib/utils";
|
||||
import type { HTMLAttributes } from "svelte/elements";
|
||||
|
||||
type $$Props = HTMLAttributes<HTMLTableSectionElement>;
|
||||
|
||||
let className: $$Props["class"] = undefined;
|
||||
export { className as class };
|
||||
</script>
|
||||
|
||||
<!-- svelte-ignore a11y-no-noninteractive-element-interactions -->
|
||||
<thead class={cn("[&_tr]:border-b", className)} {...$$restProps} on:click on:keydown>
|
||||
<slot />
|
||||
</thead>
|
23
apps/web/src/lib/components/ui/table/table-row.svelte
Normal file
23
apps/web/src/lib/components/ui/table/table-row.svelte
Normal file
|
@ -0,0 +1,23 @@
|
|||
<script lang="ts">
|
||||
import { cn } from "$lib/utils";
|
||||
import type { HTMLAttributes } from "svelte/elements";
|
||||
|
||||
type $$Props = HTMLAttributes<HTMLTableRowElement> & {
|
||||
"data-state"?: unknown;
|
||||
};
|
||||
|
||||
let className: $$Props["class"] = undefined;
|
||||
export { className as class };
|
||||
</script>
|
||||
|
||||
<tr
|
||||
class={cn(
|
||||
"border-b transition-colors hover:bg-muted/50 data-[state=selected]:bg-muted",
|
||||
className
|
||||
)}
|
||||
{...$$restProps}
|
||||
on:click
|
||||
on:keydown
|
||||
>
|
||||
<slot />
|
||||
</tr>
|
15
apps/web/src/lib/components/ui/table/table.svelte
Normal file
15
apps/web/src/lib/components/ui/table/table.svelte
Normal file
|
@ -0,0 +1,15 @@
|
|||
<script lang="ts">
|
||||
import { cn } from "$lib/utils";
|
||||
import type { HTMLTableAttributes } from "svelte/elements";
|
||||
|
||||
type $$Props = HTMLTableAttributes;
|
||||
|
||||
let className: $$Props["class"] = undefined;
|
||||
export { className as class };
|
||||
</script>
|
||||
|
||||
<div class="w-full overflow-auto">
|
||||
<table class={cn("w-full caption-bottom text-sm", className)} {...$$restProps}>
|
||||
<slot />
|
||||
</table>
|
||||
</div>
|
28
apps/web/src/lib/components/ui/textarea/index.ts
Normal file
28
apps/web/src/lib/components/ui/textarea/index.ts
Normal file
|
@ -0,0 +1,28 @@
|
|||
import Root from "./textarea.svelte";
|
||||
|
||||
type FormTextareaEvent<T extends Event = Event> = T & {
|
||||
currentTarget: EventTarget & HTMLTextAreaElement;
|
||||
};
|
||||
|
||||
type TextareaEvents = {
|
||||
blur: FormTextareaEvent<FocusEvent>;
|
||||
change: FormTextareaEvent<Event>;
|
||||
click: FormTextareaEvent<MouseEvent>;
|
||||
focus: FormTextareaEvent<FocusEvent>;
|
||||
keydown: FormTextareaEvent<KeyboardEvent>;
|
||||
keypress: FormTextareaEvent<KeyboardEvent>;
|
||||
keyup: FormTextareaEvent<KeyboardEvent>;
|
||||
mouseover: FormTextareaEvent<MouseEvent>;
|
||||
mouseenter: FormTextareaEvent<MouseEvent>;
|
||||
mouseleave: FormTextareaEvent<MouseEvent>;
|
||||
paste: FormTextareaEvent<ClipboardEvent>;
|
||||
input: FormTextareaEvent<InputEvent>;
|
||||
};
|
||||
|
||||
export {
|
||||
Root,
|
||||
//
|
||||
Root as Textarea,
|
||||
type TextareaEvents,
|
||||
type FormTextareaEvent
|
||||
};
|
33
apps/web/src/lib/components/ui/textarea/textarea.svelte
Normal file
33
apps/web/src/lib/components/ui/textarea/textarea.svelte
Normal file
|
@ -0,0 +1,33 @@
|
|||
<script lang="ts">
|
||||
import type { HTMLTextareaAttributes } from "svelte/elements";
|
||||
import { cn } from "$lib/utils";
|
||||
import type { TextareaEvents } from ".";
|
||||
|
||||
type $$Props = HTMLTextareaAttributes;
|
||||
type $$Events = TextareaEvents;
|
||||
|
||||
let className: $$Props["class"] = undefined;
|
||||
export let value: $$Props["value"] = undefined;
|
||||
export { className as class };
|
||||
</script>
|
||||
|
||||
<textarea
|
||||
class={cn(
|
||||
"flex min-h-[60px] w-full rounded-md border border-input bg-transparent px-3 py-2 text-sm shadow-sm placeholder:text-muted-foreground focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring disabled:cursor-not-allowed disabled:opacity-50",
|
||||
className
|
||||
)}
|
||||
bind:value
|
||||
on:blur
|
||||
on:change
|
||||
on:click
|
||||
on:focus
|
||||
on:keydown
|
||||
on:keypress
|
||||
on:keyup
|
||||
on:mouseover
|
||||
on:mouseenter
|
||||
on:mouseleave
|
||||
on:paste
|
||||
on:input
|
||||
{...$$restProps}
|
||||
/>
|
|
@ -1,4 +1,5 @@
|
|||
import { error, redirect } from '@sveltejs/kit';
|
||||
import type { Actions } from './$types';
|
||||
|
||||
export const actions = {
|
||||
default: async ({ request, locals }: { request: Request; locals: App.Locals }) => {
|
||||
|
@ -20,5 +21,15 @@ export const actions = {
|
|||
}
|
||||
|
||||
throw redirect(303, '/');
|
||||
}
|
||||
};
|
||||
},
|
||||
// TODO: Implement MS Auth
|
||||
// msauth: async ({ request, cookies }) => {
|
||||
// const form = await request.formData();
|
||||
// const token = form.get('token');
|
||||
// if (!token || typeof token !== 'string') {
|
||||
// throw redirect(303, '/login');
|
||||
// }
|
||||
// cookies.set('pb_auth', JSON.stringify({ token: token }), { path: '/' });
|
||||
// throw redirect(303, '/');
|
||||
// }
|
||||
} satisfies Actions;
|
||||
|
|
|
@ -1,9 +1,10 @@
|
|||
<script lang="ts">
|
||||
import { enhance } from '$app/forms';
|
||||
import { Icons } from '$lib/components/site/icons';
|
||||
import { Button } from '$lib/components/ui/button';
|
||||
import { Input } from '$lib/components/ui/input';
|
||||
import { Label } from '$lib/components/ui/label';
|
||||
import * as Alert from "$lib/components/ui/alert";
|
||||
import * as Alert from '$lib/components/ui/alert';
|
||||
import { cn } from '$lib/utils';
|
||||
|
||||
export let form;
|
||||
|
@ -19,7 +20,7 @@
|
|||
</p>
|
||||
</div>
|
||||
<div class={cn('grid gap-6')} {...$$restProps}>
|
||||
<form method="POST">
|
||||
<form method="POST" use:enhance={() => { isLoading = true; }}>
|
||||
<div class="grid gap-2">
|
||||
<div class="grid gap-1">
|
||||
<Label class="sr-only" for="email">Email</Label>
|
||||
|
@ -36,7 +37,13 @@
|
|||
</div>
|
||||
<div class="grid gap-1">
|
||||
<Label class="sr-only" for="password">Password</Label>
|
||||
<Input id="password" name="password" type="password" disabled={isLoading} placeholder="Password" />
|
||||
<Input
|
||||
id="password"
|
||||
name="password"
|
||||
type="password"
|
||||
disabled={isLoading}
|
||||
placeholder="Password"
|
||||
/>
|
||||
</div>
|
||||
<Button type="submit" disabled={isLoading}>
|
||||
{#if isLoading}
|
||||
|
@ -48,9 +55,7 @@
|
|||
{#if form?.notVerified}
|
||||
<Alert.Root>
|
||||
<Alert.Title></Alert.Title>
|
||||
<Alert.Description>
|
||||
You must verify your email before you can login.
|
||||
</Alert.Description>
|
||||
<Alert.Description>You must verify your email before you can login.</Alert.Description>
|
||||
</Alert.Root>
|
||||
{/if}
|
||||
</form>
|
||||
|
@ -62,15 +67,17 @@
|
|||
<span class="bg-background text-muted-foreground px-2"> Or continue with </span>
|
||||
</div>
|
||||
</div>
|
||||
<Button variant="outline" type="button" disabled={isLoading}>
|
||||
{#if isLoading}
|
||||
<Icons.spinner class="mr-2 h-4 w-4 animate-spin" />
|
||||
{:else}
|
||||
<Icons.microsoft class="mr-2 h-4 w-4" />
|
||||
{/if}
|
||||
{' '}
|
||||
Microsoft
|
||||
</Button>
|
||||
<form action="/?msauth" method="POST">
|
||||
<Button type="submit" variant="outline" disabled={true} class="w-full">
|
||||
{#if isLoading}
|
||||
<Icons.spinner class="mr-2 h-4 w-4 animate-spin" />
|
||||
{:else}
|
||||
<Icons.microsoft class="mr-2 h-4 w-4" />
|
||||
{/if}
|
||||
{' '}
|
||||
Microsoft
|
||||
</Button>
|
||||
</form>
|
||||
</div>
|
||||
<p class="text-muted-foreground px-8 text-center text-sm">
|
||||
Don't have an account? <a class="text-primary underline" href="/register">Sign up.</a> <br />
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
<script lang="ts">
|
||||
import { enhance } from '$app/forms';
|
||||
import { Icons } from '$lib/components/site/icons';
|
||||
import { Button } from '$lib/components/ui/button';
|
||||
import { Input } from '$lib/components/ui/input';
|
||||
|
@ -17,7 +18,7 @@
|
|||
</p>
|
||||
</div>
|
||||
<div class={cn('grid gap-6')} {...$$restProps}>
|
||||
<form method="POST">
|
||||
<form method="POST" use:enhance={() => { isLoading = true; }}>
|
||||
<div class="grid gap-2">
|
||||
<div class="grid gap-1">
|
||||
<Label class="sr-only" for="email">Name</Label>
|
||||
|
@ -50,7 +51,7 @@
|
|||
<Label class="sr-only" for="password">Confirm Password</Label>
|
||||
<Input id="password" name="passwordConfirm" type="password" disabled={isLoading} placeholder="Confirm password" />
|
||||
</div>
|
||||
<Button type="submit" disabled={isLoading}>
|
||||
<Button type="submit" disabled={isLoading} on:click={() => isLoading = true}>
|
||||
{#if isLoading}
|
||||
<Icons.spinner class="mr-2 h-4 w-4 animate-spin" />
|
||||
{/if}
|
||||
|
@ -66,7 +67,7 @@
|
|||
<span class="bg-background text-muted-foreground px-2"> Or continue with </span>
|
||||
</div>
|
||||
</div>
|
||||
<Button variant="outline" type="button" disabled={isLoading}>
|
||||
<Button variant="outline" type="button" disabled={true}>
|
||||
{#if isLoading}
|
||||
<Icons.spinner class="mr-2 h-4 w-4 animate-spin" />
|
||||
{:else}
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
<script lang="ts">
|
||||
import { enhance } from '$app/forms';
|
||||
import { Icons } from '$lib/components/site/icons';
|
||||
import { Button } from '$lib/components/ui/button';
|
||||
import { Input } from '$lib/components/ui/input';
|
||||
|
@ -20,7 +21,11 @@
|
|||
</p>
|
||||
</div>
|
||||
<div class={cn('grid gap-6')} {...$$restProps}>
|
||||
<form method="POST">
|
||||
<form method="POST" use:enhance={() => { isLoading = true;
|
||||
return async ({ update }) => {
|
||||
isLoading = false;
|
||||
update();
|
||||
}; }}>
|
||||
<div class="grid gap-2">
|
||||
<div class="grid gap-1">
|
||||
<Label class="sr-only" for="email">Email</Label>
|
||||
|
|
|
@ -0,0 +1,12 @@
|
|||
<script lang="ts">
|
||||
import { Checkbox } from "$lib/components/ui/checkbox";
|
||||
import type { HTMLButtonAttributes } from "svelte/elements";
|
||||
import type { Writable } from "svelte/store";
|
||||
|
||||
type $$Props = HTMLButtonAttributes & {
|
||||
checked: Writable<boolean>;
|
||||
};
|
||||
export let checked: Writable<boolean>;
|
||||
</script>
|
||||
|
||||
<Checkbox bind:checked={$checked} {...$$restProps} />
|
|
@ -0,0 +1,62 @@
|
|||
<script lang="ts">
|
||||
import { ArrowDown, ArrowUp, CaretSort } from "radix-icons-svelte";
|
||||
import { cn } from "$lib/utils";
|
||||
import { Button } from "$lib/components/ui/button";
|
||||
import * as DropdownMenu from "$lib/components/ui/dropdown-menu";
|
||||
|
||||
let className: string | undefined | null = undefined;
|
||||
export { className as class };
|
||||
export let props: {
|
||||
select: never;
|
||||
sort: {
|
||||
order: "desc" | "asc" | undefined;
|
||||
toggle: (event: Event) => void;
|
||||
clear: () => void;
|
||||
disabled: boolean;
|
||||
};
|
||||
filter: never;
|
||||
};
|
||||
|
||||
function handleAscSort(e: Event) {
|
||||
if (props.sort.order === "asc") {
|
||||
return;
|
||||
}
|
||||
props.sort.toggle(e);
|
||||
}
|
||||
|
||||
function handleDescSort(e: Event) {
|
||||
if (props.sort.order === "desc") {
|
||||
return;
|
||||
}
|
||||
props.sort.toggle(e);
|
||||
}
|
||||
</script>
|
||||
|
||||
{#if !props.sort.disabled}
|
||||
<div class={cn("flex items-center", className)}>
|
||||
<DropdownMenu.Root>
|
||||
<DropdownMenu.Trigger asChild let:builder>
|
||||
<Button
|
||||
variant="ghost"
|
||||
builders={[builder]}
|
||||
class="-ml-3 h-8 data-[state=open]:bg-accent"
|
||||
>
|
||||
<slot />
|
||||
{#if props.sort.order === "desc"}
|
||||
<ArrowDown class="ml-2 h-4 w-4" />
|
||||
{:else if props.sort.order === "asc"}
|
||||
<ArrowUp class="ml-2 h-4 w-4" />
|
||||
{:else}
|
||||
<CaretSort class="ml-2 h-4 w-4" />
|
||||
{/if}
|
||||
</Button>
|
||||
</DropdownMenu.Trigger>
|
||||
<DropdownMenu.Content align="start">
|
||||
<DropdownMenu.Item on:click={handleAscSort}>Asc</DropdownMenu.Item>
|
||||
<DropdownMenu.Item on:click={handleDescSort}>Desc</DropdownMenu.Item>
|
||||
</DropdownMenu.Content>
|
||||
</DropdownMenu.Root>
|
||||
</div>
|
||||
{:else}
|
||||
<slot />
|
||||
{/if}
|
|
@ -0,0 +1,96 @@
|
|||
<script lang="ts">
|
||||
import { PlusCircled, Check } from "radix-icons-svelte";
|
||||
import * as Command from "$lib/components/ui/command";
|
||||
import * as Popover from "$lib/components/ui/popover";
|
||||
import { Button } from "$lib/components/ui/button";
|
||||
import { cn } from "$lib/utils";
|
||||
import Separator from "$lib/components/ui/separator/separator.svelte";
|
||||
import Badge from "$lib/components/ui/badge/badge.svelte";
|
||||
import type { statuses } from "../(data)/data";
|
||||
|
||||
export let filterValues: string[] = [];
|
||||
export let title: string;
|
||||
export let options = [] as typeof statuses;
|
||||
|
||||
let open = false;
|
||||
|
||||
const handleSelect = (currentValue: string) => {
|
||||
if (Array.isArray(filterValues) && filterValues.includes(currentValue)) {
|
||||
filterValues = filterValues.filter((v) => v !== currentValue);
|
||||
} else {
|
||||
filterValues = [...(Array.isArray(filterValues) ? filterValues : []), currentValue];
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
<Popover.Root bind:open>
|
||||
<Popover.Trigger asChild let:builder>
|
||||
<Button builders={[builder]} variant="outline" size="sm" class="h-8 border-dashed">
|
||||
<PlusCircled class="mr-2 h-4 w-4" />
|
||||
{title}
|
||||
|
||||
{#if filterValues.length > 0}
|
||||
<Separator orientation="vertical" class="mx-2 h-4" />
|
||||
<Badge variant="secondary" class="rounded-sm px-1 font-normal lg:hidden">
|
||||
{filterValues.length}
|
||||
</Badge>
|
||||
<div class="hidden space-x-1 lg:flex">
|
||||
{#if filterValues.length > 2}
|
||||
<Badge variant="secondary" class="rounded-sm px-1 font-normal">
|
||||
{filterValues.length} Selected
|
||||
</Badge>
|
||||
{:else}
|
||||
{#each filterValues as option}
|
||||
<Badge variant="secondary" class="rounded-sm px-1 font-normal">
|
||||
{option}
|
||||
</Badge>
|
||||
{/each}
|
||||
{/if}
|
||||
</div>
|
||||
{/if}
|
||||
</Button>
|
||||
</Popover.Trigger>
|
||||
<Popover.Content class="w-[200px] p-0" align="start" side="bottom">
|
||||
<Command.Root>
|
||||
<Command.Input placeholder={title} />
|
||||
<Command.List>
|
||||
<Command.Empty>No results found.</Command.Empty>
|
||||
<Command.Group>
|
||||
{#each options as option}
|
||||
<Command.Item
|
||||
value={option.value}
|
||||
onSelect={(currentValue) => {
|
||||
handleSelect(currentValue);
|
||||
}}
|
||||
>
|
||||
<div
|
||||
class={cn(
|
||||
"mr-2 flex h-4 w-4 items-center justify-center rounded-sm border border-primary",
|
||||
filterValues.includes(option.value)
|
||||
? "bg-primary text-primary-foreground"
|
||||
: "opacity-50 [&_svg]:invisible"
|
||||
)}
|
||||
>
|
||||
<Check className={cn("h-4 w-4")} />
|
||||
</div>
|
||||
<span>
|
||||
{option.label}
|
||||
</span>
|
||||
</Command.Item>
|
||||
{/each}
|
||||
</Command.Group>
|
||||
{#if filterValues.length > 0}
|
||||
<Command.Separator />
|
||||
<Command.Item
|
||||
class="justify-center text-center"
|
||||
onSelect={() => {
|
||||
filterValues = [];
|
||||
}}
|
||||
>
|
||||
Clear filters
|
||||
</Command.Item>
|
||||
{/if}
|
||||
</Command.List>
|
||||
</Command.Root>
|
||||
</Popover.Content>
|
||||
</Popover.Root>
|
|
@ -0,0 +1,89 @@
|
|||
<script lang="ts">
|
||||
import { Button } from "$lib/components/ui/button";
|
||||
import {
|
||||
ChevronRight,
|
||||
ChevronLeft,
|
||||
DoubleArrowRight,
|
||||
DoubleArrowLeft
|
||||
} from "radix-icons-svelte";
|
||||
import * as Select from "$lib/components/ui/select";
|
||||
import type { Task } from "../(data)/schemas";
|
||||
import type { AnyPlugins } from "svelte-headless-table/plugins";
|
||||
import type { TableViewModel } from "svelte-headless-table";
|
||||
|
||||
export let tableModel: TableViewModel<Task, AnyPlugins>;
|
||||
|
||||
const { pageRows, pluginStates, rows } = tableModel;
|
||||
|
||||
const { hasNextPage, hasPreviousPage, pageIndex, pageCount, pageSize } = pluginStates.page;
|
||||
|
||||
const { selectedDataIds } = pluginStates.select;
|
||||
</script>
|
||||
|
||||
<div class="flex items-center justify-between px-2">
|
||||
<div class="flex-1 text-sm text-muted-foreground">
|
||||
{Object.keys($selectedDataIds).length} of{" "}
|
||||
{$rows.length} row(s) selected.
|
||||
</div>
|
||||
<div class="flex items-center space-x-6 lg:space-x-8">
|
||||
<div class="flex items-center space-x-2">
|
||||
<p class="text-sm font-medium">Rows per page</p>
|
||||
<Select.Root
|
||||
onSelectedChange={(selected) => pageSize.set(Number(selected?.value))}
|
||||
selected={{ value: 10, label: "10" }}
|
||||
>
|
||||
<Select.Trigger class="w-[180px]">
|
||||
<Select.Value placeholder="Select page size" />
|
||||
</Select.Trigger>
|
||||
<Select.Content>
|
||||
<Select.Item value="10">10</Select.Item>
|
||||
<Select.Item value="20">20</Select.Item>
|
||||
<Select.Item value="30">30</Select.Item>
|
||||
<Select.Item value="40">40</Select.Item>
|
||||
<Select.Item value="50">50</Select.Item>
|
||||
</Select.Content>
|
||||
</Select.Root>
|
||||
</div>
|
||||
<div class="flex w-[100px] items-center justify-center text-sm font-medium">
|
||||
Page {$pageIndex + 1} of {$pageCount}
|
||||
</div>
|
||||
<div class="flex items-center space-x-2">
|
||||
<Button
|
||||
variant="outline"
|
||||
class="hidden h-8 w-8 p-0 lg:flex"
|
||||
on:click={() => ($pageIndex = 0)}
|
||||
disabled={!$hasPreviousPage}
|
||||
>
|
||||
<span class="sr-only">Go to first page</span>
|
||||
<DoubleArrowLeft size={15} />
|
||||
</Button>
|
||||
<Button
|
||||
variant="outline"
|
||||
class="p-0 w-8 h-8"
|
||||
on:click={() => ($pageIndex = $pageIndex - 1)}
|
||||
disabled={!$hasPreviousPage}
|
||||
>
|
||||
<span class="sr-only">Go to previous page</span>
|
||||
<ChevronLeft size={15} />
|
||||
</Button>
|
||||
<Button
|
||||
variant="outline"
|
||||
class="p-0 w-8 h-8"
|
||||
disabled={!$hasNextPage}
|
||||
on:click={() => ($pageIndex = $pageIndex + 1)}
|
||||
>
|
||||
<span class="sr-only">Go to next page</span>
|
||||
<ChevronRight size={15} />
|
||||
</Button>
|
||||
<Button
|
||||
variant="outline"
|
||||
class="hidden h-8 w-8 p-0 lg:flex"
|
||||
disabled={!$hasNextPage}
|
||||
on:click={() => ($pageIndex = Math.ceil($rows.length / $pageRows.length) - 1)}
|
||||
>
|
||||
<span class="sr-only">Go to last page</span>
|
||||
<DoubleArrowRight size={15} />
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
|
@ -0,0 +1,15 @@
|
|||
<script lang="ts">
|
||||
import { priorities } from "../(data)/data";
|
||||
export let value: string;
|
||||
const priority = priorities.find((priority) => priority.value === value);
|
||||
const Icon = priority?.icon;
|
||||
</script>
|
||||
|
||||
{#if priority}
|
||||
<div class="flex items-center">
|
||||
{#if Icon}
|
||||
<Icon class="mr-2 h-4 w-4 text-muted-foreground" />
|
||||
{/if}
|
||||
<span>{priority.label}</span>
|
||||
</div>
|
||||
{/if}
|
|
@ -0,0 +1,46 @@
|
|||
<script lang="ts">
|
||||
import { DotsHorizontal } from "radix-icons-svelte";
|
||||
import { Button } from "$lib/components/ui/button";
|
||||
import * as DropdownMenu from "$lib/components/ui/dropdown-menu";
|
||||
import { labels } from "../(data)/data";
|
||||
import { taskSchema, type Task } from "../(data)/schemas";
|
||||
|
||||
export let row: Task;
|
||||
const task = taskSchema.parse(row);
|
||||
</script>
|
||||
|
||||
<DropdownMenu.Root>
|
||||
<DropdownMenu.Trigger asChild let:builder>
|
||||
<Button
|
||||
variant="ghost"
|
||||
builders={[builder]}
|
||||
class="flex h-8 w-8 p-0 data-[state=open]:bg-muted"
|
||||
>
|
||||
<DotsHorizontal class="h-4 w-4" />
|
||||
<span class="sr-only">Open menu</span>
|
||||
</Button>
|
||||
</DropdownMenu.Trigger>
|
||||
<DropdownMenu.Content class="w-[160px]" align="end">
|
||||
<DropdownMenu.Item>Edit</DropdownMenu.Item>
|
||||
<DropdownMenu.Item>Make a copy</DropdownMenu.Item>
|
||||
<DropdownMenu.Item>Favorite</DropdownMenu.Item>
|
||||
<DropdownMenu.Separator />
|
||||
<DropdownMenu.Sub>
|
||||
<DropdownMenu.SubTrigger>Labels</DropdownMenu.SubTrigger>
|
||||
<DropdownMenu.SubContent>
|
||||
<DropdownMenu.RadioGroup value={task.label}>
|
||||
{#each labels as label}
|
||||
<DropdownMenu.RadioItem value={label.value}>
|
||||
{label.label}
|
||||
</DropdownMenu.RadioItem>
|
||||
{/each}
|
||||
</DropdownMenu.RadioGroup>
|
||||
</DropdownMenu.SubContent>
|
||||
</DropdownMenu.Sub>
|
||||
<DropdownMenu.Separator />
|
||||
<DropdownMenu.Item>
|
||||
Delete
|
||||
<DropdownMenu.Shortcut>⌘⌫</DropdownMenu.Shortcut>
|
||||
</DropdownMenu.Item>
|
||||
</DropdownMenu.Content>
|
||||
</DropdownMenu.Root>
|
|
@ -0,0 +1,16 @@
|
|||
<script lang="ts">
|
||||
import { statuses } from "../(data)/data";
|
||||
|
||||
export let value: string;
|
||||
const status = statuses.find((status) => status.value === value);
|
||||
const Icon = status?.icon;
|
||||
</script>
|
||||
|
||||
{#if status}
|
||||
<div class="flex w-[100px] items-center">
|
||||
{#if Icon}
|
||||
<Icon class="mr-2 h-4 w-4 text-muted-foreground" />
|
||||
{/if}
|
||||
<span>{status.label}</span>
|
||||
</div>
|
||||
{/if}
|
|
@ -0,0 +1,17 @@
|
|||
<script lang="ts">
|
||||
import { Badge } from "$lib/components/ui/badge";
|
||||
import { labels } from "../(data)/data";
|
||||
|
||||
export let value: string;
|
||||
export let labelValue: string;
|
||||
const label = labels.find((label) => label.value === labelValue);
|
||||
</script>
|
||||
|
||||
<div class="flex space-x-2">
|
||||
{#if label}
|
||||
<Badge variant="outline">{label.label}</Badge>
|
||||
{/if}
|
||||
<span class="max-w-[500px] truncate font-medium">
|
||||
{value}
|
||||
</span>
|
||||
</div>
|
|
@ -0,0 +1,69 @@
|
|||
<script lang="ts">
|
||||
import { Input } from "$lib/components/ui/input";
|
||||
import { DataTableFacetedFilter, DataTableViewOptions } from ".";
|
||||
import type { AnyPlugins } from "svelte-headless-table/lib/types/TablePlugin";
|
||||
import type { Task } from "../(data)/schemas";
|
||||
import type { TableViewModel } from "svelte-headless-table/lib/createViewModel";
|
||||
import Button from "$lib/components/ui/button/button.svelte";
|
||||
import { Cross2 } from "radix-icons-svelte";
|
||||
import { statuses, priorities } from "../(data)/data";
|
||||
import type { Writable } from "svelte/store";
|
||||
|
||||
export let tableModel: TableViewModel<Task, AnyPlugins>;
|
||||
|
||||
const { pluginStates } = tableModel;
|
||||
const {
|
||||
filterValue
|
||||
}: {
|
||||
filterValue: Writable<string>;
|
||||
} = pluginStates.filter;
|
||||
|
||||
const {
|
||||
filterValues
|
||||
}: {
|
||||
filterValues: Writable<{
|
||||
status: string[];
|
||||
priority: string[];
|
||||
}>;
|
||||
} = pluginStates.colFilter;
|
||||
|
||||
$: showReset = Object.values({ ...$filterValues, $filterValue }).some((v) => v.length > 0);
|
||||
</script>
|
||||
|
||||
<div class="flex items-center justify-between">
|
||||
<div class="flex flex-1 items-center space-x-2">
|
||||
<Input
|
||||
placeholder="Filter tasks..."
|
||||
class="h-8 w-[150px] lg:w-[250px]"
|
||||
type="search"
|
||||
bind:value={$filterValue}
|
||||
/>
|
||||
|
||||
<DataTableFacetedFilter
|
||||
bind:filterValues={$filterValues.status}
|
||||
title="Status"
|
||||
options={statuses}
|
||||
/>
|
||||
<DataTableFacetedFilter
|
||||
bind:filterValues={$filterValues.priority}
|
||||
title="Priority"
|
||||
options={priorities}
|
||||
/>
|
||||
{#if showReset}
|
||||
<Button
|
||||
on:click={() => {
|
||||
$filterValue = "";
|
||||
$filterValues.status = [];
|
||||
$filterValues.priority = [];
|
||||
}}
|
||||
variant="ghost"
|
||||
class="h-8 px-2 lg:px-3"
|
||||
>
|
||||
Reset
|
||||
<Cross2 class="ml-2 h-4 w-4" />
|
||||
</Button>
|
||||
{/if}
|
||||
</div>
|
||||
|
||||
<DataTableViewOptions {tableModel} />
|
||||
</div>
|
|
@ -0,0 +1,42 @@
|
|||
<script lang="ts">
|
||||
import { MixerHorizontal } from "radix-icons-svelte";
|
||||
import { Button } from "$lib/components/ui/button";
|
||||
import * as DropdownMenu from "$lib/components/ui/dropdown-menu";
|
||||
import type { AnyPlugins } from "svelte-headless-table/lib/types/TablePlugin";
|
||||
import type { Task } from "../(data)/schemas";
|
||||
import type { TableViewModel } from "svelte-headless-table/lib/createViewModel";
|
||||
|
||||
export let tableModel: TableViewModel<Task, AnyPlugins>;
|
||||
const { pluginStates, flatColumns } = tableModel;
|
||||
const { hiddenColumnIds } = pluginStates.hide;
|
||||
|
||||
const ids = flatColumns.map((col: { id: string }) => col.id);
|
||||
|
||||
let hideForId = Object.fromEntries(ids.map((id: string) => [id, true]));
|
||||
|
||||
$: $hiddenColumnIds = Object.entries(hideForId)
|
||||
.filter(([, hide]) => !hide)
|
||||
.map(([id]) => id);
|
||||
|
||||
const hidableCols = ["title", "status", "priority"];
|
||||
</script>
|
||||
|
||||
<DropdownMenu.Root>
|
||||
<DropdownMenu.Trigger asChild let:builder>
|
||||
<Button variant="outline" size="sm" class="ml-auto hidden h-8 lg:flex" builders={[builder]}>
|
||||
<MixerHorizontal class="mr-2 h-4 w-4" />
|
||||
View
|
||||
</Button>
|
||||
</DropdownMenu.Trigger>
|
||||
<DropdownMenu.Content>
|
||||
<DropdownMenu.Label>Toggle columns</DropdownMenu.Label>
|
||||
<DropdownMenu.Separator />
|
||||
{#each flatColumns as col}
|
||||
{#if hidableCols.includes(col.id)}
|
||||
<DropdownMenu.CheckboxItem bind:checked={hideForId[col.id]}>
|
||||
{col.header}
|
||||
</DropdownMenu.CheckboxItem>
|
||||
{/if}
|
||||
{/each}
|
||||
</DropdownMenu.Content>
|
||||
</DropdownMenu.Root>
|
|
@ -0,0 +1,222 @@
|
|||
<script lang="ts">
|
||||
import { get, readable } from "svelte/store";
|
||||
import { Render, Subscribe, createRender, createTable } from "svelte-headless-table";
|
||||
import * as Table from "$lib/components/ui/table";
|
||||
import {
|
||||
addColumnFilters,
|
||||
addHiddenColumns,
|
||||
addPagination,
|
||||
addSelectedRows,
|
||||
addSortBy,
|
||||
addTableFilter
|
||||
} from "svelte-headless-table/plugins";
|
||||
import {
|
||||
DataTableCheckbox,
|
||||
DataTableTitleCell,
|
||||
DataTableStatusCell,
|
||||
DataTableRowActions,
|
||||
DataTablePriorityCell,
|
||||
DataTableColumnHeader,
|
||||
DataTableToolbar,
|
||||
DataTablePagination
|
||||
} from ".";
|
||||
|
||||
import type { Task } from "../(data)/schemas";
|
||||
|
||||
export let data: Task[];
|
||||
|
||||
const table = createTable(readable(data), {
|
||||
select: addSelectedRows(),
|
||||
sort: addSortBy({
|
||||
toggleOrder: ["asc", "desc"]
|
||||
}),
|
||||
page: addPagination(),
|
||||
filter: addTableFilter({
|
||||
fn: ({ filterValue, value }) => {
|
||||
return value.toLowerCase().includes(filterValue.toLowerCase());
|
||||
}
|
||||
}),
|
||||
colFilter: addColumnFilters(),
|
||||
hide: addHiddenColumns()
|
||||
});
|
||||
|
||||
const columns = table.createColumns([
|
||||
table.display({
|
||||
id: "select",
|
||||
header: (_, { pluginStates }) => {
|
||||
const { allPageRowsSelected } = pluginStates.select;
|
||||
return createRender(DataTableCheckbox, {
|
||||
checked: allPageRowsSelected,
|
||||
"aria-label": "Select all"
|
||||
});
|
||||
},
|
||||
cell: ({ row }, { pluginStates }) => {
|
||||
const { getRowState } = pluginStates.select;
|
||||
const { isSelected } = getRowState(row);
|
||||
return createRender(DataTableCheckbox, {
|
||||
checked: isSelected,
|
||||
"aria-label": "Select row",
|
||||
class: "translate-y-[2px]"
|
||||
});
|
||||
},
|
||||
plugins: {
|
||||
sort: {
|
||||
disable: true
|
||||
}
|
||||
}
|
||||
}),
|
||||
table.column({
|
||||
accessor: "id",
|
||||
header: () => {
|
||||
return "Task";
|
||||
},
|
||||
id: "task",
|
||||
plugins: {
|
||||
sort: {
|
||||
disable: true
|
||||
}
|
||||
}
|
||||
}),
|
||||
table.column({
|
||||
accessor: "title",
|
||||
header: "Title",
|
||||
id: "title",
|
||||
cell: ({ value, row }) => {
|
||||
if (row.isData()) {
|
||||
return createRender(DataTableTitleCell, {
|
||||
value,
|
||||
labelValue: row.original.label
|
||||
});
|
||||
}
|
||||
return value;
|
||||
}
|
||||
}),
|
||||
table.column({
|
||||
accessor: "status",
|
||||
header: "Status",
|
||||
id: "status",
|
||||
cell: ({ value }) => {
|
||||
return createRender(DataTableStatusCell, {
|
||||
value
|
||||
});
|
||||
},
|
||||
plugins: {
|
||||
colFilter: {
|
||||
fn: ({ filterValue, value }) => {
|
||||
if (filterValue.length === 0) return true;
|
||||
if (!Array.isArray(filterValue) || typeof value !== "string") return true;
|
||||
return filterValue.some((filter) => {
|
||||
return value.includes(filter);
|
||||
});
|
||||
},
|
||||
initialFilterValue: [],
|
||||
render: ({ filterValue }) => {
|
||||
return get(filterValue);
|
||||
}
|
||||
}
|
||||
}
|
||||
}),
|
||||
table.column({
|
||||
accessor: "priority",
|
||||
id: "priority",
|
||||
header: "Priority",
|
||||
cell: ({ value }) => {
|
||||
return createRender(DataTablePriorityCell, {
|
||||
value
|
||||
});
|
||||
},
|
||||
plugins: {
|
||||
colFilter: {
|
||||
fn: ({ filterValue, value }) => {
|
||||
if (filterValue.length === 0) return true;
|
||||
if (!Array.isArray(filterValue) || typeof value !== "string") return true;
|
||||
|
||||
return filterValue.some((filter) => {
|
||||
return value.includes(filter);
|
||||
});
|
||||
},
|
||||
initialFilterValue: [],
|
||||
render: ({ filterValue }) => {
|
||||
return get(filterValue);
|
||||
}
|
||||
}
|
||||
}
|
||||
}),
|
||||
table.display({
|
||||
id: "actions",
|
||||
header: () => {
|
||||
return "";
|
||||
},
|
||||
cell: ({ row }) => {
|
||||
if (row.isData() && row.original) {
|
||||
return createRender(DataTableRowActions, {
|
||||
row: row.original
|
||||
});
|
||||
}
|
||||
return "";
|
||||
}
|
||||
})
|
||||
]);
|
||||
|
||||
const tableModel = table.createViewModel(columns);
|
||||
|
||||
const { headerRows, pageRows, tableAttrs, tableBodyAttrs } = tableModel;
|
||||
</script>
|
||||
|
||||
<div class="space-y-4">
|
||||
<DataTableToolbar {tableModel} />
|
||||
<div class="rounded-md border">
|
||||
<Table.Root {...$tableAttrs}>
|
||||
<Table.Header>
|
||||
{#each $headerRows as headerRow}
|
||||
<Subscribe rowAttrs={headerRow.attrs()}>
|
||||
<Table.Row>
|
||||
{#each headerRow.cells as cell (cell.id)}
|
||||
<Subscribe
|
||||
attrs={cell.attrs()}
|
||||
let:attrs
|
||||
props={cell.props()}
|
||||
let:props
|
||||
>
|
||||
<Table.Head {...attrs}>
|
||||
{#if cell.id !== "select" && cell.id !== "actions"}
|
||||
<DataTableColumnHeader {props}
|
||||
><Render
|
||||
of={cell.render()}
|
||||
/></DataTableColumnHeader
|
||||
>
|
||||
{:else}
|
||||
<Render of={cell.render()} />
|
||||
{/if}
|
||||
</Table.Head>
|
||||
</Subscribe>
|
||||
{/each}
|
||||
</Table.Row>
|
||||
</Subscribe>
|
||||
{/each}
|
||||
</Table.Header>
|
||||
<Table.Body {...$tableBodyAttrs}>
|
||||
{#each $pageRows as row (row.id)}
|
||||
<Subscribe rowAttrs={row.attrs()} let:rowAttrs>
|
||||
<Table.Row {...rowAttrs}>
|
||||
{#each row.cells as cell (cell.id)}
|
||||
<Subscribe attrs={cell.attrs()} let:attrs>
|
||||
<Table.Cell {...attrs}>
|
||||
{#if cell.id === "task"}
|
||||
<div class="w-[80px]">
|
||||
<Render of={cell.render()} />
|
||||
</div>
|
||||
{:else}
|
||||
<Render of={cell.render()} />
|
||||
{/if}
|
||||
</Table.Cell>
|
||||
</Subscribe>
|
||||
{/each}
|
||||
</Table.Row>
|
||||
</Subscribe>
|
||||
{/each}
|
||||
</Table.Body>
|
||||
</Table.Root>
|
||||
</div>
|
||||
<DataTablePagination {tableModel} />
|
||||
</div>
|
|
@ -0,0 +1,10 @@
|
|||
export { default as DataTableCheckbox } from "./data-table-checkbox.svelte";
|
||||
export { default as DataTableTitleCell } from "./data-table-title-cell.svelte";
|
||||
export { default as DataTableStatusCell } from "./data-table-status-cell.svelte";
|
||||
export { default as DataTableRowActions } from "./data-table-row-actions.svelte";
|
||||
export { default as DataTablePriorityCell } from "./data-table-priority-cell.svelte";
|
||||
export { default as DataTableColumnHeader } from "./data-table-column-header.svelte";
|
||||
export { default as DataTableToolbar } from "./data-table-toolbar.svelte";
|
||||
export { default as DataTablePagination } from "./data-table-pagination.svelte";
|
||||
export { default as DataTableViewOptions } from "./data-table-view-options.svelte";
|
||||
export { default as DataTableFacetedFilter } from "./data-table-faceted-filter.svelte";
|
|
@ -0,0 +1,45 @@
|
|||
<script lang="ts">
|
||||
import * as Avatar from "$lib/components/ui/avatar";
|
||||
import { Button } from "$lib/components/ui/button";
|
||||
import * as DropdownMenu from "$lib/components/ui/dropdown-menu";
|
||||
</script>
|
||||
|
||||
<DropdownMenu.Root>
|
||||
<DropdownMenu.Trigger asChild let:builder>
|
||||
<Button variant="ghost" builders={[builder]} class="relative h-8 w-8 rounded-full">
|
||||
<Avatar.Root class="h-9 w-9">
|
||||
<Avatar.Image src="/avatars/03.png" alt="@shadcn" />
|
||||
<Avatar.Fallback>SC</Avatar.Fallback>
|
||||
</Avatar.Root>
|
||||
</Button>
|
||||
</DropdownMenu.Trigger>
|
||||
<DropdownMenu.Content class="w-56" align="end">
|
||||
<DropdownMenu.Label class="font-normal">
|
||||
<div class="flex flex-col space-y-1">
|
||||
<p class="text-sm font-medium leading-none">shadcn</p>
|
||||
<p class="text-xs leading-none text-muted-foreground">m@example.com</p>
|
||||
</div>
|
||||
</DropdownMenu.Label>
|
||||
<DropdownMenu.Separator />
|
||||
<DropdownMenu.Group>
|
||||
<DropdownMenu.Item>
|
||||
Profile
|
||||
<DropdownMenu.Shortcut>⇧⌘P</DropdownMenu.Shortcut>
|
||||
</DropdownMenu.Item>
|
||||
<DropdownMenu.Item>
|
||||
Billing
|
||||
<DropdownMenu.Shortcut>⌘B</DropdownMenu.Shortcut>
|
||||
</DropdownMenu.Item>
|
||||
<DropdownMenu.Item>
|
||||
Settings
|
||||
<DropdownMenu.Shortcut>⌘S</DropdownMenu.Shortcut>
|
||||
</DropdownMenu.Item>
|
||||
<DropdownMenu.Item>New Team</DropdownMenu.Item>
|
||||
</DropdownMenu.Group>
|
||||
<DropdownMenu.Separator />
|
||||
<DropdownMenu.Item>
|
||||
Log out
|
||||
<DropdownMenu.Shortcut>⇧⌘Q</DropdownMenu.Shortcut>
|
||||
</DropdownMenu.Item>
|
||||
</DropdownMenu.Content>
|
||||
</DropdownMenu.Root>
|
71
apps/web/src/routes/(dashboard)/dashboard/(data)/data.ts
Normal file
71
apps/web/src/routes/(dashboard)/dashboard/(data)/data.ts
Normal file
|
@ -0,0 +1,71 @@
|
|||
import {
|
||||
ArrowDown,
|
||||
ArrowRight,
|
||||
ArrowUp,
|
||||
CheckCircled,
|
||||
Circle,
|
||||
CrossCircled,
|
||||
QuestionMarkCircled,
|
||||
Stopwatch
|
||||
} from "radix-icons-svelte";
|
||||
|
||||
export const labels = [
|
||||
{
|
||||
value: "bug",
|
||||
label: "Bug"
|
||||
},
|
||||
{
|
||||
value: "feature",
|
||||
label: "Feature"
|
||||
},
|
||||
{
|
||||
value: "documentation",
|
||||
label: "Documentation"
|
||||
}
|
||||
];
|
||||
|
||||
export const statuses = [
|
||||
{
|
||||
value: "backlog",
|
||||
label: "Backlog",
|
||||
icon: QuestionMarkCircled
|
||||
},
|
||||
{
|
||||
value: "todo",
|
||||
label: "Todo",
|
||||
icon: Circle
|
||||
},
|
||||
{
|
||||
value: "in progress",
|
||||
label: "In Progress",
|
||||
icon: Stopwatch
|
||||
},
|
||||
{
|
||||
value: "done",
|
||||
label: "Done",
|
||||
icon: CheckCircled
|
||||
},
|
||||
{
|
||||
value: "canceled",
|
||||
label: "Canceled",
|
||||
icon: CrossCircled
|
||||
}
|
||||
];
|
||||
|
||||
export const priorities = [
|
||||
{
|
||||
label: "Low",
|
||||
value: "low",
|
||||
icon: ArrowDown
|
||||
},
|
||||
{
|
||||
label: "Medium",
|
||||
value: "medium",
|
||||
icon: ArrowRight
|
||||
},
|
||||
{
|
||||
label: "High",
|
||||
value: "high",
|
||||
icon: ArrowUp
|
||||
}
|
||||
];
|
13
apps/web/src/routes/(dashboard)/dashboard/(data)/schemas.ts
Normal file
13
apps/web/src/routes/(dashboard)/dashboard/(data)/schemas.ts
Normal file
|
@ -0,0 +1,13 @@
|
|||
import { z } from "zod";
|
||||
|
||||
// We're keeping a simple non-relational schema here.
|
||||
// IRL, you will have a schema for your data models.
|
||||
export const taskSchema = z.object({
|
||||
id: z.string(),
|
||||
title: z.string(),
|
||||
status: z.string(),
|
||||
label: z.string(),
|
||||
priority: z.string()
|
||||
});
|
||||
|
||||
export type Task = z.infer<typeof taskSchema>;
|
702
apps/web/src/routes/(dashboard)/dashboard/(data)/tasks.json
Normal file
702
apps/web/src/routes/(dashboard)/dashboard/(data)/tasks.json
Normal file
|
@ -0,0 +1,702 @@
|
|||
[
|
||||
{
|
||||
"id": "TASK-8782",
|
||||
"title": "You can't compress the program without quantifying the open-source SSD pixel!",
|
||||
"status": "in progress",
|
||||
"label": "documentation",
|
||||
"priority": "medium"
|
||||
},
|
||||
{
|
||||
"id": "TASK-7878",
|
||||
"title": "Try to calculate the EXE feed, maybe it will index the multi-byte pixel!",
|
||||
"status": "backlog",
|
||||
"label": "documentation",
|
||||
"priority": "medium"
|
||||
},
|
||||
{
|
||||
"id": "TASK-7839",
|
||||
"title": "We need to bypass the neural TCP card!",
|
||||
"status": "todo",
|
||||
"label": "bug",
|
||||
"priority": "high"
|
||||
},
|
||||
{
|
||||
"id": "TASK-5562",
|
||||
"title": "The SAS interface is down, bypass the open-source pixel so we can back up the PNG bandwidth!",
|
||||
"status": "backlog",
|
||||
"label": "feature",
|
||||
"priority": "medium"
|
||||
},
|
||||
{
|
||||
"id": "TASK-8686",
|
||||
"title": "I'll parse the wireless SSL protocol, that should driver the API panel!",
|
||||
"status": "canceled",
|
||||
"label": "feature",
|
||||
"priority": "medium"
|
||||
},
|
||||
{
|
||||
"id": "TASK-1280",
|
||||
"title": "Use the digital TLS panel, then you can transmit the haptic system!",
|
||||
"status": "done",
|
||||
"label": "bug",
|
||||
"priority": "high"
|
||||
},
|
||||
{
|
||||
"id": "TASK-7262",
|
||||
"title": "The UTF8 application is down, parse the neural bandwidth so we can back up the PNG firewall!",
|
||||
"status": "done",
|
||||
"label": "feature",
|
||||
"priority": "high"
|
||||
},
|
||||
{
|
||||
"id": "TASK-1138",
|
||||
"title": "Generating the driver won't do anything, we need to quantify the 1080p SMTP bandwidth!",
|
||||
"status": "in progress",
|
||||
"label": "feature",
|
||||
"priority": "medium"
|
||||
},
|
||||
{
|
||||
"id": "TASK-7184",
|
||||
"title": "We need to program the back-end THX pixel!",
|
||||
"status": "todo",
|
||||
"label": "feature",
|
||||
"priority": "low"
|
||||
},
|
||||
{
|
||||
"id": "TASK-5160",
|
||||
"title": "Calculating the bus won't do anything, we need to navigate the back-end JSON protocol!",
|
||||
"status": "in progress",
|
||||
"label": "documentation",
|
||||
"priority": "high"
|
||||
},
|
||||
{
|
||||
"id": "TASK-5618",
|
||||
"title": "Generating the driver won't do anything, we need to index the online SSL application!",
|
||||
"status": "done",
|
||||
"label": "documentation",
|
||||
"priority": "medium"
|
||||
},
|
||||
{
|
||||
"id": "TASK-6699",
|
||||
"title": "I'll transmit the wireless JBOD capacitor, that should hard drive the SSD feed!",
|
||||
"status": "backlog",
|
||||
"label": "documentation",
|
||||
"priority": "medium"
|
||||
},
|
||||
{
|
||||
"id": "TASK-2858",
|
||||
"title": "We need to override the online UDP bus!",
|
||||
"status": "backlog",
|
||||
"label": "bug",
|
||||
"priority": "medium"
|
||||
},
|
||||
{
|
||||
"id": "TASK-9864",
|
||||
"title": "I'll reboot the 1080p FTP panel, that should matrix the HEX hard drive!",
|
||||
"status": "done",
|
||||
"label": "bug",
|
||||
"priority": "high"
|
||||
},
|
||||
{
|
||||
"id": "TASK-8404",
|
||||
"title": "We need to generate the virtual HEX alarm!",
|
||||
"status": "in progress",
|
||||
"label": "bug",
|
||||
"priority": "low"
|
||||
},
|
||||
{
|
||||
"id": "TASK-5365",
|
||||
"title": "Backing up the pixel won't do anything, we need to transmit the primary IB array!",
|
||||
"status": "in progress",
|
||||
"label": "documentation",
|
||||
"priority": "low"
|
||||
},
|
||||
{
|
||||
"id": "TASK-1780",
|
||||
"title": "The CSS feed is down, index the bluetooth transmitter so we can compress the CLI protocol!",
|
||||
"status": "todo",
|
||||
"label": "documentation",
|
||||
"priority": "high"
|
||||
},
|
||||
{
|
||||
"id": "TASK-6938",
|
||||
"title": "Use the redundant SCSI application, then you can hack the optical alarm!",
|
||||
"status": "todo",
|
||||
"label": "documentation",
|
||||
"priority": "high"
|
||||
},
|
||||
{
|
||||
"id": "TASK-9885",
|
||||
"title": "We need to compress the auxiliary VGA driver!",
|
||||
"status": "backlog",
|
||||
"label": "bug",
|
||||
"priority": "high"
|
||||
},
|
||||
{
|
||||
"id": "TASK-3216",
|
||||
"title": "Transmitting the transmitter won't do anything, we need to compress the virtual HDD sensor!",
|
||||
"status": "backlog",
|
||||
"label": "documentation",
|
||||
"priority": "medium"
|
||||
},
|
||||
{
|
||||
"id": "TASK-9285",
|
||||
"title": "The IP monitor is down, copy the haptic alarm so we can generate the HTTP transmitter!",
|
||||
"status": "todo",
|
||||
"label": "bug",
|
||||
"priority": "high"
|
||||
},
|
||||
{
|
||||
"id": "TASK-1024",
|
||||
"title": "Overriding the microchip won't do anything, we need to transmit the digital OCR transmitter!",
|
||||
"status": "in progress",
|
||||
"label": "documentation",
|
||||
"priority": "low"
|
||||
},
|
||||
{
|
||||
"id": "TASK-7068",
|
||||
"title": "You can't generate the capacitor without indexing the wireless HEX pixel!",
|
||||
"status": "canceled",
|
||||
"label": "bug",
|
||||
"priority": "low"
|
||||
},
|
||||
{
|
||||
"id": "TASK-6502",
|
||||
"title": "Navigating the microchip won't do anything, we need to bypass the back-end SQL bus!",
|
||||
"status": "todo",
|
||||
"label": "bug",
|
||||
"priority": "high"
|
||||
},
|
||||
{
|
||||
"id": "TASK-5326",
|
||||
"title": "We need to hack the redundant UTF8 transmitter!",
|
||||
"status": "todo",
|
||||
"label": "bug",
|
||||
"priority": "low"
|
||||
},
|
||||
{
|
||||
"id": "TASK-6274",
|
||||
"title": "Use the virtual PCI circuit, then you can parse the bluetooth alarm!",
|
||||
"status": "canceled",
|
||||
"label": "documentation",
|
||||
"priority": "low"
|
||||
},
|
||||
{
|
||||
"id": "TASK-1571",
|
||||
"title": "I'll input the neural DRAM circuit, that should protocol the SMTP interface!",
|
||||
"status": "in progress",
|
||||
"label": "feature",
|
||||
"priority": "medium"
|
||||
},
|
||||
{
|
||||
"id": "TASK-9518",
|
||||
"title": "Compressing the interface won't do anything, we need to compress the online SDD matrix!",
|
||||
"status": "canceled",
|
||||
"label": "documentation",
|
||||
"priority": "medium"
|
||||
},
|
||||
{
|
||||
"id": "TASK-5581",
|
||||
"title": "I'll synthesize the digital COM pixel, that should transmitter the UTF8 protocol!",
|
||||
"status": "backlog",
|
||||
"label": "documentation",
|
||||
"priority": "high"
|
||||
},
|
||||
{
|
||||
"id": "TASK-2197",
|
||||
"title": "Parsing the feed won't do anything, we need to copy the bluetooth DRAM bus!",
|
||||
"status": "todo",
|
||||
"label": "documentation",
|
||||
"priority": "low"
|
||||
},
|
||||
{
|
||||
"id": "TASK-8484",
|
||||
"title": "We need to parse the solid state UDP firewall!",
|
||||
"status": "in progress",
|
||||
"label": "bug",
|
||||
"priority": "low"
|
||||
},
|
||||
{
|
||||
"id": "TASK-9892",
|
||||
"title": "If we back up the application, we can get to the UDP application through the multi-byte THX capacitor!",
|
||||
"status": "done",
|
||||
"label": "documentation",
|
||||
"priority": "high"
|
||||
},
|
||||
{
|
||||
"id": "TASK-9616",
|
||||
"title": "We need to synthesize the cross-platform ASCII pixel!",
|
||||
"status": "in progress",
|
||||
"label": "feature",
|
||||
"priority": "medium"
|
||||
},
|
||||
{
|
||||
"id": "TASK-9744",
|
||||
"title": "Use the back-end IP card, then you can input the solid state hard drive!",
|
||||
"status": "done",
|
||||
"label": "documentation",
|
||||
"priority": "low"
|
||||
},
|
||||
{
|
||||
"id": "TASK-1376",
|
||||
"title": "Generating the alarm won't do anything, we need to generate the mobile IP capacitor!",
|
||||
"status": "backlog",
|
||||
"label": "documentation",
|
||||
"priority": "low"
|
||||
},
|
||||
{
|
||||
"id": "TASK-7382",
|
||||
"title": "If we back up the firewall, we can get to the RAM alarm through the primary UTF8 pixel!",
|
||||
"status": "todo",
|
||||
"label": "feature",
|
||||
"priority": "low"
|
||||
},
|
||||
{
|
||||
"id": "TASK-2290",
|
||||
"title": "I'll compress the virtual JSON panel, that should application the UTF8 bus!",
|
||||
"status": "canceled",
|
||||
"label": "documentation",
|
||||
"priority": "high"
|
||||
},
|
||||
{
|
||||
"id": "TASK-1533",
|
||||
"title": "You can't input the firewall without overriding the wireless TCP firewall!",
|
||||
"status": "done",
|
||||
"label": "bug",
|
||||
"priority": "high"
|
||||
},
|
||||
{
|
||||
"id": "TASK-4920",
|
||||
"title": "Bypassing the hard drive won't do anything, we need to input the bluetooth JSON program!",
|
||||
"status": "in progress",
|
||||
"label": "bug",
|
||||
"priority": "high"
|
||||
},
|
||||
{
|
||||
"id": "TASK-5168",
|
||||
"title": "If we synthesize the bus, we can get to the IP panel through the virtual TLS array!",
|
||||
"status": "in progress",
|
||||
"label": "feature",
|
||||
"priority": "low"
|
||||
},
|
||||
{
|
||||
"id": "TASK-7103",
|
||||
"title": "We need to parse the multi-byte EXE bandwidth!",
|
||||
"status": "canceled",
|
||||
"label": "feature",
|
||||
"priority": "low"
|
||||
},
|
||||
{
|
||||
"id": "TASK-4314",
|
||||
"title": "If we compress the program, we can get to the XML alarm through the multi-byte COM matrix!",
|
||||
"status": "in progress",
|
||||
"label": "bug",
|
||||
"priority": "high"
|
||||
},
|
||||
{
|
||||
"id": "TASK-3415",
|
||||
"title": "Use the cross-platform XML application, then you can quantify the solid state feed!",
|
||||
"status": "todo",
|
||||
"label": "feature",
|
||||
"priority": "high"
|
||||
},
|
||||
{
|
||||
"id": "TASK-8339",
|
||||
"title": "Try to calculate the DNS interface, maybe it will input the bluetooth capacitor!",
|
||||
"status": "in progress",
|
||||
"label": "feature",
|
||||
"priority": "low"
|
||||
},
|
||||
{
|
||||
"id": "TASK-6995",
|
||||
"title": "Try to hack the XSS bandwidth, maybe it will override the bluetooth matrix!",
|
||||
"status": "todo",
|
||||
"label": "feature",
|
||||
"priority": "high"
|
||||
},
|
||||
{
|
||||
"id": "TASK-8053",
|
||||
"title": "If we connect the program, we can get to the UTF8 matrix through the digital UDP protocol!",
|
||||
"status": "todo",
|
||||
"label": "feature",
|
||||
"priority": "medium"
|
||||
},
|
||||
{
|
||||
"id": "TASK-4336",
|
||||
"title": "If we synthesize the microchip, we can get to the SAS sensor through the optical UDP program!",
|
||||
"status": "todo",
|
||||
"label": "documentation",
|
||||
"priority": "low"
|
||||
},
|
||||
{
|
||||
"id": "TASK-8790",
|
||||
"title": "I'll back up the optical COM alarm, that should alarm the RSS capacitor!",
|
||||
"status": "done",
|
||||
"label": "bug",
|
||||
"priority": "medium"
|
||||
},
|
||||
{
|
||||
"id": "TASK-8980",
|
||||
"title": "Try to navigate the SQL transmitter, maybe it will back up the virtual firewall!",
|
||||
"status": "canceled",
|
||||
"label": "bug",
|
||||
"priority": "low"
|
||||
},
|
||||
{
|
||||
"id": "TASK-7342",
|
||||
"title": "Use the neural CLI card, then you can parse the online port!",
|
||||
"status": "backlog",
|
||||
"label": "documentation",
|
||||
"priority": "low"
|
||||
},
|
||||
{
|
||||
"id": "TASK-5608",
|
||||
"title": "I'll hack the haptic SSL program, that should bus the UDP transmitter!",
|
||||
"status": "canceled",
|
||||
"label": "documentation",
|
||||
"priority": "low"
|
||||
},
|
||||
{
|
||||
"id": "TASK-1606",
|
||||
"title": "I'll generate the bluetooth PNG firewall, that should pixel the SSL driver!",
|
||||
"status": "done",
|
||||
"label": "feature",
|
||||
"priority": "medium"
|
||||
},
|
||||
{
|
||||
"id": "TASK-7872",
|
||||
"title": "Transmitting the circuit won't do anything, we need to reboot the 1080p RSS monitor!",
|
||||
"status": "canceled",
|
||||
"label": "feature",
|
||||
"priority": "medium"
|
||||
},
|
||||
{
|
||||
"id": "TASK-4167",
|
||||
"title": "Use the cross-platform SMS circuit, then you can synthesize the optical feed!",
|
||||
"status": "canceled",
|
||||
"label": "bug",
|
||||
"priority": "medium"
|
||||
},
|
||||
{
|
||||
"id": "TASK-9581",
|
||||
"title": "You can't index the port without hacking the cross-platform XSS monitor!",
|
||||
"status": "backlog",
|
||||
"label": "documentation",
|
||||
"priority": "low"
|
||||
},
|
||||
{
|
||||
"id": "TASK-8806",
|
||||
"title": "We need to bypass the back-end SSL panel!",
|
||||
"status": "done",
|
||||
"label": "bug",
|
||||
"priority": "medium"
|
||||
},
|
||||
{
|
||||
"id": "TASK-6542",
|
||||
"title": "Try to quantify the RSS firewall, maybe it will quantify the open-source system!",
|
||||
"status": "done",
|
||||
"label": "feature",
|
||||
"priority": "low"
|
||||
},
|
||||
{
|
||||
"id": "TASK-6806",
|
||||
"title": "The VGA protocol is down, reboot the back-end matrix so we can parse the CSS panel!",
|
||||
"status": "canceled",
|
||||
"label": "documentation",
|
||||
"priority": "low"
|
||||
},
|
||||
{
|
||||
"id": "TASK-9549",
|
||||
"title": "You can't bypass the bus without connecting the neural JBOD bus!",
|
||||
"status": "todo",
|
||||
"label": "feature",
|
||||
"priority": "high"
|
||||
},
|
||||
{
|
||||
"id": "TASK-1075",
|
||||
"title": "Backing up the driver won't do anything, we need to parse the redundant RAM pixel!",
|
||||
"status": "done",
|
||||
"label": "feature",
|
||||
"priority": "medium"
|
||||
},
|
||||
{
|
||||
"id": "TASK-1427",
|
||||
"title": "Use the auxiliary PCI circuit, then you can calculate the cross-platform interface!",
|
||||
"status": "done",
|
||||
"label": "documentation",
|
||||
"priority": "high"
|
||||
},
|
||||
{
|
||||
"id": "TASK-1907",
|
||||
"title": "Hacking the circuit won't do anything, we need to back up the online DRAM system!",
|
||||
"status": "todo",
|
||||
"label": "documentation",
|
||||
"priority": "high"
|
||||
},
|
||||
{
|
||||
"id": "TASK-4309",
|
||||
"title": "If we generate the system, we can get to the TCP sensor through the optical GB pixel!",
|
||||
"status": "backlog",
|
||||
"label": "bug",
|
||||
"priority": "medium"
|
||||
},
|
||||
{
|
||||
"id": "TASK-3973",
|
||||
"title": "I'll parse the back-end ADP array, that should bandwidth the RSS bandwidth!",
|
||||
"status": "todo",
|
||||
"label": "feature",
|
||||
"priority": "medium"
|
||||
},
|
||||
{
|
||||
"id": "TASK-7962",
|
||||
"title": "Use the wireless RAM program, then you can hack the cross-platform feed!",
|
||||
"status": "canceled",
|
||||
"label": "bug",
|
||||
"priority": "low"
|
||||
},
|
||||
{
|
||||
"id": "TASK-3360",
|
||||
"title": "You can't quantify the program without synthesizing the neural OCR interface!",
|
||||
"status": "done",
|
||||
"label": "feature",
|
||||
"priority": "medium"
|
||||
},
|
||||
{
|
||||
"id": "TASK-9887",
|
||||
"title": "Use the auxiliary ASCII sensor, then you can connect the solid state port!",
|
||||
"status": "backlog",
|
||||
"label": "bug",
|
||||
"priority": "medium"
|
||||
},
|
||||
{
|
||||
"id": "TASK-3649",
|
||||
"title": "I'll input the virtual USB system, that should circuit the DNS monitor!",
|
||||
"status": "in progress",
|
||||
"label": "feature",
|
||||
"priority": "medium"
|
||||
},
|
||||
{
|
||||
"id": "TASK-3586",
|
||||
"title": "If we quantify the circuit, we can get to the CLI feed through the mobile SMS hard drive!",
|
||||
"status": "in progress",
|
||||
"label": "bug",
|
||||
"priority": "low"
|
||||
},
|
||||
{
|
||||
"id": "TASK-5150",
|
||||
"title": "I'll hack the wireless XSS port, that should transmitter the IP interface!",
|
||||
"status": "canceled",
|
||||
"label": "feature",
|
||||
"priority": "medium"
|
||||
},
|
||||
{
|
||||
"id": "TASK-3652",
|
||||
"title": "The SQL interface is down, override the optical bus so we can program the ASCII interface!",
|
||||
"status": "backlog",
|
||||
"label": "feature",
|
||||
"priority": "low"
|
||||
},
|
||||
{
|
||||
"id": "TASK-6884",
|
||||
"title": "Use the digital PCI circuit, then you can synthesize the multi-byte microchip!",
|
||||
"status": "canceled",
|
||||
"label": "feature",
|
||||
"priority": "high"
|
||||
},
|
||||
{
|
||||
"id": "TASK-1591",
|
||||
"title": "We need to connect the mobile XSS driver!",
|
||||
"status": "in progress",
|
||||
"label": "feature",
|
||||
"priority": "high"
|
||||
},
|
||||
{
|
||||
"id": "TASK-3802",
|
||||
"title": "Try to override the ASCII protocol, maybe it will parse the virtual matrix!",
|
||||
"status": "in progress",
|
||||
"label": "feature",
|
||||
"priority": "low"
|
||||
},
|
||||
{
|
||||
"id": "TASK-7253",
|
||||
"title": "Programming the capacitor won't do anything, we need to bypass the neural IB hard drive!",
|
||||
"status": "backlog",
|
||||
"label": "bug",
|
||||
"priority": "high"
|
||||
},
|
||||
{
|
||||
"id": "TASK-9739",
|
||||
"title": "We need to hack the multi-byte HDD bus!",
|
||||
"status": "done",
|
||||
"label": "documentation",
|
||||
"priority": "medium"
|
||||
},
|
||||
{
|
||||
"id": "TASK-4424",
|
||||
"title": "Try to hack the HEX alarm, maybe it will connect the optical pixel!",
|
||||
"status": "in progress",
|
||||
"label": "documentation",
|
||||
"priority": "medium"
|
||||
},
|
||||
{
|
||||
"id": "TASK-3922",
|
||||
"title": "You can't back up the capacitor without generating the wireless PCI program!",
|
||||
"status": "backlog",
|
||||
"label": "bug",
|
||||
"priority": "low"
|
||||
},
|
||||
{
|
||||
"id": "TASK-4921",
|
||||
"title": "I'll index the open-source IP feed, that should system the GB application!",
|
||||
"status": "canceled",
|
||||
"label": "bug",
|
||||
"priority": "low"
|
||||
},
|
||||
{
|
||||
"id": "TASK-5814",
|
||||
"title": "We need to calculate the 1080p AGP feed!",
|
||||
"status": "backlog",
|
||||
"label": "bug",
|
||||
"priority": "high"
|
||||
},
|
||||
{
|
||||
"id": "TASK-2645",
|
||||
"title": "Synthesizing the system won't do anything, we need to navigate the multi-byte HDD firewall!",
|
||||
"status": "todo",
|
||||
"label": "documentation",
|
||||
"priority": "medium"
|
||||
},
|
||||
{
|
||||
"id": "TASK-4535",
|
||||
"title": "Try to copy the JSON circuit, maybe it will connect the wireless feed!",
|
||||
"status": "in progress",
|
||||
"label": "feature",
|
||||
"priority": "low"
|
||||
},
|
||||
{
|
||||
"id": "TASK-4463",
|
||||
"title": "We need to copy the solid state AGP monitor!",
|
||||
"status": "done",
|
||||
"label": "documentation",
|
||||
"priority": "low"
|
||||
},
|
||||
{
|
||||
"id": "TASK-9745",
|
||||
"title": "If we connect the protocol, we can get to the GB system through the bluetooth PCI microchip!",
|
||||
"status": "canceled",
|
||||
"label": "feature",
|
||||
"priority": "high"
|
||||
},
|
||||
{
|
||||
"id": "TASK-2080",
|
||||
"title": "If we input the bus, we can get to the RAM matrix through the auxiliary RAM card!",
|
||||
"status": "todo",
|
||||
"label": "bug",
|
||||
"priority": "medium"
|
||||
},
|
||||
{
|
||||
"id": "TASK-3838",
|
||||
"title": "I'll bypass the online TCP application, that should panel the AGP system!",
|
||||
"status": "backlog",
|
||||
"label": "bug",
|
||||
"priority": "high"
|
||||
},
|
||||
{
|
||||
"id": "TASK-1340",
|
||||
"title": "We need to navigate the virtual PNG circuit!",
|
||||
"status": "todo",
|
||||
"label": "bug",
|
||||
"priority": "medium"
|
||||
},
|
||||
{
|
||||
"id": "TASK-6665",
|
||||
"title": "If we parse the monitor, we can get to the SSD hard drive through the cross-platform AGP alarm!",
|
||||
"status": "canceled",
|
||||
"label": "feature",
|
||||
"priority": "low"
|
||||
},
|
||||
{
|
||||
"id": "TASK-7585",
|
||||
"title": "If we calculate the hard drive, we can get to the SSL program through the multi-byte CSS microchip!",
|
||||
"status": "backlog",
|
||||
"label": "feature",
|
||||
"priority": "low"
|
||||
},
|
||||
{
|
||||
"id": "TASK-6319",
|
||||
"title": "We need to copy the multi-byte SCSI program!",
|
||||
"status": "backlog",
|
||||
"label": "bug",
|
||||
"priority": "high"
|
||||
},
|
||||
{
|
||||
"id": "TASK-4369",
|
||||
"title": "Try to input the SCSI bus, maybe it will generate the 1080p pixel!",
|
||||
"status": "backlog",
|
||||
"label": "bug",
|
||||
"priority": "high"
|
||||
},
|
||||
{
|
||||
"id": "TASK-9035",
|
||||
"title": "We need to override the solid state PNG array!",
|
||||
"status": "canceled",
|
||||
"label": "documentation",
|
||||
"priority": "low"
|
||||
},
|
||||
{
|
||||
"id": "TASK-3970",
|
||||
"title": "You can't index the transmitter without quantifying the haptic ASCII card!",
|
||||
"status": "todo",
|
||||
"label": "documentation",
|
||||
"priority": "medium"
|
||||
},
|
||||
{
|
||||
"id": "TASK-4473",
|
||||
"title": "You can't bypass the protocol without overriding the neural RSS program!",
|
||||
"status": "todo",
|
||||
"label": "documentation",
|
||||
"priority": "low"
|
||||
},
|
||||
{
|
||||
"id": "TASK-4136",
|
||||
"title": "You can't hack the hard drive without hacking the primary JSON program!",
|
||||
"status": "canceled",
|
||||
"label": "bug",
|
||||
"priority": "medium"
|
||||
},
|
||||
{
|
||||
"id": "TASK-3939",
|
||||
"title": "Use the back-end SQL firewall, then you can connect the neural hard drive!",
|
||||
"status": "done",
|
||||
"label": "feature",
|
||||
"priority": "low"
|
||||
},
|
||||
{
|
||||
"id": "TASK-2007",
|
||||
"title": "I'll input the back-end USB protocol, that should bandwidth the PCI system!",
|
||||
"status": "backlog",
|
||||
"label": "bug",
|
||||
"priority": "high"
|
||||
},
|
||||
{
|
||||
"id": "TASK-7516",
|
||||
"title": "Use the primary SQL program, then you can generate the auxiliary transmitter!",
|
||||
"status": "done",
|
||||
"label": "documentation",
|
||||
"priority": "medium"
|
||||
},
|
||||
{
|
||||
"id": "TASK-6906",
|
||||
"title": "Try to back up the DRAM system, maybe it will reboot the online transmitter!",
|
||||
"status": "done",
|
||||
"label": "feature",
|
||||
"priority": "high"
|
||||
},
|
||||
{
|
||||
"id": "TASK-5207",
|
||||
"title": "The SMS interface is down, copy the bluetooth bus so we can quantify the VGA card!",
|
||||
"status": "in progress",
|
||||
"label": "bug",
|
||||
"priority": "low"
|
||||
}
|
||||
]
|
|
@ -1,8 +1,18 @@
|
|||
<script lang="ts">
|
||||
import DataTable from "./(components)/data-table.svelte";
|
||||
import UserNav from "./(components)/user-nav.svelte";
|
||||
import data from "./(data)/tasks.json";
|
||||
</script>
|
||||
|
||||
<h1>Create a new dashboard</h1>
|
||||
|
||||
<form method="POST">
|
||||
<button type="submit">Create dashboard</button>
|
||||
</form>
|
||||
<div class="hidden h-full flex-1 flex-col space-y-8 p-8 md:flex">
|
||||
<div class="flex items-center justify-between space-y-2">
|
||||
<div>
|
||||
<h2 class="text-2xl font-bold tracking-tight">Welcome back!</h2>
|
||||
<p class="text-muted-foreground">Here's a list of your tasks for this month!</p>
|
||||
</div>
|
||||
<div class="flex items-center space-x-2">
|
||||
<UserNav />
|
||||
</div>
|
||||
</div>
|
||||
<DataTable {data} />
|
||||
</div>
|
||||
|
|
|
@ -0,0 +1,43 @@
|
|||
<script lang="ts">
|
||||
import { cn } from "$lib/utils";
|
||||
import { page } from "$app/stores";
|
||||
import { Button } from "$lib/components/ui/button";
|
||||
import { cubicInOut } from "svelte/easing";
|
||||
import { crossfade } from "svelte/transition";
|
||||
|
||||
let className: string | undefined | null = undefined;
|
||||
export let items: { href: string; title: string }[];
|
||||
export { className as class };
|
||||
|
||||
const [send, receive] = crossfade({
|
||||
duration: 250,
|
||||
easing: cubicInOut
|
||||
});
|
||||
</script>
|
||||
|
||||
<nav class={cn("flex space-x-2 lg:flex-col lg:space-x-0 lg:space-y-1", className)}>
|
||||
{#each items as item}
|
||||
{@const isActive = $page.url.pathname === item.href}
|
||||
|
||||
<Button
|
||||
href={item.href}
|
||||
variant="ghost"
|
||||
class={cn(
|
||||
!isActive && "hover:underline",
|
||||
"relative justify-start hover:bg-transparent"
|
||||
)}
|
||||
data-sveltekit-noscroll
|
||||
>
|
||||
{#if isActive}
|
||||
<div
|
||||
class="absolute inset-0 rounded-md bg-muted"
|
||||
in:send={{ key: "active-sidebar-tab" }}
|
||||
out:receive={{ key: "active-sidebar-tab" }}
|
||||
/>
|
||||
{/if}
|
||||
<div class="relative">
|
||||
{item.title}
|
||||
</div>
|
||||
</Button>
|
||||
{/each}
|
||||
</nav>
|
41
apps/web/src/routes/(dashboard)/settings/+layout.svelte
Normal file
41
apps/web/src/routes/(dashboard)/settings/+layout.svelte
Normal file
|
@ -0,0 +1,41 @@
|
|||
<script lang="ts">
|
||||
import { Separator } from "$lib/components/ui/separator";
|
||||
import SidebarNav from "./(components)/sidebar-nav.svelte";
|
||||
|
||||
const sidebarNavItems = [
|
||||
{
|
||||
title: "Profile",
|
||||
href: "/settings"
|
||||
},
|
||||
{
|
||||
title: "Account",
|
||||
href: "/settings/account"
|
||||
},
|
||||
{
|
||||
title: "Appearance",
|
||||
href: "/settings/appearance"
|
||||
},
|
||||
{
|
||||
title: "Notifications",
|
||||
href: "/settings/notifications"
|
||||
}
|
||||
];
|
||||
</script>
|
||||
|
||||
<div class="hidden space-y-6 p-10 pb-16 md:block">
|
||||
<div class="space-y-0.5">
|
||||
<h2 class="text-2xl font-bold tracking-tight">Settings</h2>
|
||||
<p class="text-muted-foreground">
|
||||
Manage your account settings and set e-mail preferences.
|
||||
</p>
|
||||
</div>
|
||||
<Separator class="my-6" />
|
||||
<div class="flex flex-col space-y-8 lg:flex-row lg:space-x-12 lg:space-y-0">
|
||||
<aside class="-mx-4 lg:w-1/5">
|
||||
<SidebarNav items={sidebarNavItems} />
|
||||
</aside>
|
||||
<div class="flex-1 lg:max-w-2xl">
|
||||
<slot />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
24
apps/web/src/routes/(dashboard)/settings/+page.server.ts
Normal file
24
apps/web/src/routes/(dashboard)/settings/+page.server.ts
Normal file
|
@ -0,0 +1,24 @@
|
|||
import type { PageServerLoad } from "./$types";
|
||||
import { superValidate } from "sveltekit-superforms/server";
|
||||
import { profileFormSchema } from "./profile-form.svelte";
|
||||
import { fail, type Actions } from "@sveltejs/kit";
|
||||
|
||||
export const load: PageServerLoad = async () => {
|
||||
return {
|
||||
form: await superValidate(profileFormSchema)
|
||||
};
|
||||
};
|
||||
|
||||
export const actions: Actions = {
|
||||
default: async (event) => {
|
||||
const form = await superValidate(event, profileFormSchema);
|
||||
if (!form.valid) {
|
||||
return fail(400, {
|
||||
form
|
||||
});
|
||||
}
|
||||
return {
|
||||
form
|
||||
};
|
||||
}
|
||||
};
|
15
apps/web/src/routes/(dashboard)/settings/+page.svelte
Normal file
15
apps/web/src/routes/(dashboard)/settings/+page.svelte
Normal file
|
@ -0,0 +1,15 @@
|
|||
<script lang="ts">
|
||||
import type { PageData } from "./$types";
|
||||
import ProfileForm from "./profile-form.svelte";
|
||||
import { Separator } from "$lib/components/ui/separator";
|
||||
export let data: PageData;
|
||||
</script>
|
||||
|
||||
<div class="space-y-6">
|
||||
<div>
|
||||
<h3 class="text-lg font-medium">Profile</h3>
|
||||
<p class="text-sm text-muted-foreground">This is how others will see you on the site.</p>
|
||||
</div>
|
||||
<Separator />
|
||||
<ProfileForm data={data.form} />
|
||||
</div>
|
|
@ -0,0 +1,24 @@
|
|||
import { superValidate } from "sveltekit-superforms/server";
|
||||
import type { PageServerLoad } from "./$types";
|
||||
import { accountFormSchema } from "./account-form.svelte";
|
||||
import { fail, type Actions } from "@sveltejs/kit";
|
||||
|
||||
export const load: PageServerLoad = async () => {
|
||||
return {
|
||||
form: await superValidate(accountFormSchema)
|
||||
};
|
||||
};
|
||||
|
||||
export const actions: Actions = {
|
||||
default: async (event) => {
|
||||
const form = await superValidate(event, accountFormSchema);
|
||||
if (!form.valid) {
|
||||
return fail(400, {
|
||||
form
|
||||
});
|
||||
}
|
||||
return {
|
||||
form
|
||||
};
|
||||
}
|
||||
};
|
|
@ -0,0 +1,18 @@
|
|||
<script lang="ts">
|
||||
import { Separator } from "$lib/components/ui/separator";
|
||||
import AccountForm from "./account-form.svelte";
|
||||
import type { PageData } from "./$types";
|
||||
|
||||
export let data: PageData;
|
||||
</script>
|
||||
|
||||
<div class="space-y-6">
|
||||
<div>
|
||||
<h3 class="text-lg font-medium">Account</h3>
|
||||
<p class="text-sm text-muted-foreground">
|
||||
Update your account settings. Set your preferred language and timezone.
|
||||
</p>
|
||||
</div>
|
||||
<Separator />
|
||||
<AccountForm data={data.form} />
|
||||
</div>
|
|
@ -0,0 +1,85 @@
|
|||
<script lang="ts" context="module">
|
||||
import { z } from "zod";
|
||||
|
||||
const languages = {
|
||||
en: "English",
|
||||
fr: "French",
|
||||
de: "German",
|
||||
es: "Spanish",
|
||||
pt: "Portuguese",
|
||||
ru: "Russian",
|
||||
ja: "Japanese",
|
||||
ko: "Korean",
|
||||
zh: "Chinese"
|
||||
} as const;
|
||||
|
||||
type Language = keyof typeof languages;
|
||||
|
||||
export const accountFormSchema = z.object({
|
||||
name: z
|
||||
.string({
|
||||
required_error: "Required."
|
||||
})
|
||||
.min(2, "Name must be at least 2 characters.")
|
||||
.max(30, "Name must not be longer than 30 characters"),
|
||||
// Hack: https://github.com/colinhacks/zod/issues/2280
|
||||
language: z.enum(Object.keys(languages) as [Language, ...Language[]])
|
||||
});
|
||||
|
||||
export type AccountFormSchema = typeof accountFormSchema;
|
||||
</script>
|
||||
|
||||
<script lang="ts">
|
||||
import * as Form from "$lib/components/ui/form";
|
||||
import type { SuperValidated } from "sveltekit-superforms";
|
||||
import { cn } from "$lib/utils";
|
||||
|
||||
export let data: SuperValidated<AccountFormSchema>;
|
||||
</script>
|
||||
|
||||
<Form.Root
|
||||
method="POST"
|
||||
class="space-y-8"
|
||||
let:config
|
||||
schema={accountFormSchema}
|
||||
form={data}
|
||||
debug={true}
|
||||
>
|
||||
<Form.Item>
|
||||
<Form.Field name="name" {config}>
|
||||
<Form.Label>Name</Form.Label>
|
||||
<Form.Input placeholder="Your name" />
|
||||
<Form.Description>
|
||||
This is the name that will be displayed on your profile and in emails.
|
||||
</Form.Description>
|
||||
<Form.Validation />
|
||||
</Form.Field>
|
||||
</Form.Item>
|
||||
<Form.Item>
|
||||
<Form.Field {config} name="language" let:attrs>
|
||||
{@const { value } = attrs.input}
|
||||
<Form.Label>Language</Form.Label>
|
||||
<Form.Select selected={{ value, label: languages[value] }}>
|
||||
<Form.SelectTrigger
|
||||
placeholder="Select language"
|
||||
class={cn(
|
||||
"w-[200px] justify-between",
|
||||
!attrs.input.value && "text-muted-foreground"
|
||||
)}
|
||||
/>
|
||||
<Form.SelectContent class="h-52 overflow-y-auto">
|
||||
{#each Object.entries(languages) as [value, lang]}
|
||||
<Form.SelectItem {value}>
|
||||
{lang}
|
||||
</Form.SelectItem>
|
||||
{/each}
|
||||
</Form.SelectContent>
|
||||
</Form.Select>
|
||||
<Form.Description>
|
||||
This is the language that will be used in the dashboard.
|
||||
</Form.Description>
|
||||
<Form.Validation />
|
||||
</Form.Field>
|
||||
</Form.Item>
|
||||
<Form.Button>Update account</Form.Button>
|
||||
</Form.Root>
|
|
@ -0,0 +1,24 @@
|
|||
import { superValidate } from "sveltekit-superforms/server";
|
||||
import type { PageServerLoad } from "../$types";
|
||||
import { appearanceFormSchema } from "./appearance-form.svelte";
|
||||
import { fail, type Actions } from "@sveltejs/kit";
|
||||
|
||||
export const load: PageServerLoad = async () => {
|
||||
return {
|
||||
form: await superValidate(appearanceFormSchema)
|
||||
};
|
||||
};
|
||||
|
||||
export const actions: Actions = {
|
||||
default: async (event) => {
|
||||
const form = await superValidate(event, appearanceFormSchema);
|
||||
if (!form.valid) {
|
||||
return fail(400, {
|
||||
form
|
||||
});
|
||||
}
|
||||
return {
|
||||
form
|
||||
};
|
||||
}
|
||||
};
|
|
@ -0,0 +1,18 @@
|
|||
<script lang="ts">
|
||||
import { Separator } from "$lib/components/ui/separator";
|
||||
import type { PageData } from "./$types";
|
||||
import AppearanceForm from "./appearance-form.svelte";
|
||||
|
||||
export let data: PageData;
|
||||
</script>
|
||||
|
||||
<div class="space-y-6">
|
||||
<div>
|
||||
<h3 class="text-lg font-medium">Appearance</h3>
|
||||
<p class="text-sm text-muted-foreground">
|
||||
Customize the appearance of the app. Automatically switch between day and night themes.
|
||||
</p>
|
||||
</div>
|
||||
<Separator />
|
||||
<AppearanceForm data={data.form} />
|
||||
</div>
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue