feat: add dashboard and settings pages and components

This commit is contained in:
Bart van der Braak 2024-01-31 01:24:57 +01:00
parent cb51a4507d
commit 39a36462bb
109 changed files with 3770 additions and 116 deletions

View file

@ -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"
}

View file

@ -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==}

View file

@ -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%;

View file

@ -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,
};

View 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>

View 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}
/>

View 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>

View 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
};

View 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>

View 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"];

View 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>

View file

@ -0,0 +1,6 @@
import Root from "./checkbox.svelte";
export {
Root,
//
Root as Checkbox
};

View 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>

View 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>

View 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>

View 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>

View 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>

View 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>

View file

@ -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} />

View file

@ -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>

View 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>

View 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
};

View 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>

View file

@ -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>

View 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>

View 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>

View 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}
/>

View file

@ -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>

View 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>

View 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
};

View 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>

View 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} />

View 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>

View 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
/>

View 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>

View 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>

View file

@ -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>

View 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>

View file

@ -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>

View 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>

View 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} />

View 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
/>

View 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}
/>

View 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
};

View 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
};

View file

@ -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>

View 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
};

View file

@ -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>

View file

@ -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>

View 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
};

View 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>

View 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>

View 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>

View file

@ -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} />

View 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>

View file

@ -0,0 +1,7 @@
import Root from "./separator.svelte";
export {
Root,
//
Root as Separator
};

View 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}
/>

View file

@ -0,0 +1,7 @@
import Root from "./switch.svelte";
export {
Root,
//
Root as Switch
};

View 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>

View 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
};

View 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>

View 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>

View 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>

View 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>

View 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>

View 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>

View 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>

View 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>

View 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
};

View 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}
/>

View file

@ -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;

View file

@ -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 />

View file

@ -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}

View file

@ -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>

View file

@ -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} />

View file

@ -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}

View file

@ -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>

View file

@ -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>

View file

@ -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}

View file

@ -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>

View file

@ -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}

View file

@ -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>

View file

@ -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>

View file

@ -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>

View file

@ -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>

View file

@ -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";

View file

@ -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>

View 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
}
];

View 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>;

View 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"
}
]

View file

@ -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>

View file

@ -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>

View 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>

View 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
};
}
};

View 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>

View file

@ -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
};
}
};

View file

@ -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>

View file

@ -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>

View file

@ -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
};
}
};

View file

@ -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