added shadcn and started PROJECT.md for tracking
This commit is contained in:
37
PROJECT.md
Normal file
37
PROJECT.md
Normal file
@@ -0,0 +1,37 @@
|
||||
## Status
|
||||
### Last Completed
|
||||
Worked on the (Feature List)[#feature-list]
|
||||
|
||||
### Next Up
|
||||
Start building the database structure or the app layout and structure
|
||||
|
||||
## Feature List
|
||||
### Must Haves
|
||||
- Events
|
||||
- Event guests (related to Guests)
|
||||
- Related guest (for +1s)
|
||||
- RSVP status
|
||||
- To Do list
|
||||
- Event Vendor (related to Vendors)
|
||||
- Event Venue (related to Venues)
|
||||
- General Notes
|
||||
- Guests (address book)
|
||||
- Vendors
|
||||
- Contact info
|
||||
- Contracts
|
||||
- Venues
|
||||
- Contact info
|
||||
- Contracts
|
||||
### Eventual Features
|
||||
- Registry (related to Events)
|
||||
- Ability to upload any url
|
||||
- Redirects
|
||||
- Convert url to `BASE_URL.com/[EVENT_SLUG]/registry/[item123]` which redirects to actually link
|
||||
|
||||
## Decisions Log
|
||||
At the top level the guest book will contain "Households". Every guest will belong to a household even if they are alone. This will allow for Guests to RSVP for everyone in their household.
|
||||
|
||||
For guests that are given a +1, there will be a placeholder name added as a Guest `"Guest of [NAME]"`. And a flag will be added to that guest entry. When responding to the RSVP, there will be an option to fill in the +1's name.
|
||||
|
||||
|
||||
## Open Questions
|
||||
211
bun.lock
211
bun.lock
@@ -5,32 +5,47 @@
|
||||
"": {
|
||||
"name": "planner",
|
||||
"devDependencies": {
|
||||
"@dnd-kit-svelte/svelte": "^0.1.5",
|
||||
"@dnd-kit/abstract": "^0.1.21",
|
||||
"@dnd-kit/helpers": "^0.1.21",
|
||||
"@eslint/js": "^10.0.1",
|
||||
"@fontsource-variable/inter": "^5.3.0",
|
||||
"@internationalized/date": "^3.12.0",
|
||||
"@lucide/svelte": "^1.52.0",
|
||||
"@sveltejs/adapter-auto": "^8.0.0",
|
||||
"@sveltejs/kit": "^3.0.0",
|
||||
"@sveltejs/vite-plugin-svelte": "^7.2.0",
|
||||
"@tabler/icons-svelte": "^3.40.0",
|
||||
"@tailwindcss/typography": "^0.5.19",
|
||||
"@tailwindcss/vite": "^4.3.0",
|
||||
"@tanstack/svelte-table": "^9.0.0",
|
||||
"@types/d3-scale": "^4.0.9",
|
||||
"@types/d3-shape": "^3.1.8",
|
||||
"@types/node": "^26",
|
||||
"bits-ui": "^2.18.0",
|
||||
"cn": "^0.3.0",
|
||||
"d3-scale": "^4.0.2",
|
||||
"d3-shape": "^3.2.0",
|
||||
"eslint": "^10.4.1",
|
||||
"eslint-config-prettier": "^10.1.8",
|
||||
"eslint-plugin-svelte": "^3.19.0",
|
||||
"globals": "^17.6.0",
|
||||
"layerchart": "^2.0.0",
|
||||
"prettier": "^3.8.3",
|
||||
"prettier-plugin-svelte": "^4.1.0",
|
||||
"prettier-plugin-tailwindcss": "^0.8.0",
|
||||
"shadcn-svelte": "^1.7.0",
|
||||
"svelte": "^5.57.1",
|
||||
"svelte-check": "^4.6.0",
|
||||
"svelte-sonner": "^1.2.0",
|
||||
"tailwind-variants": "^3.3.1",
|
||||
"tailwindcss": "^4.3.0",
|
||||
"tw-animate-css": "^1.4.0",
|
||||
"typescript": "^6.0.3",
|
||||
"typescript-eslint": "^8.60.1",
|
||||
"vaul-svelte": "^1.0.0-next.7",
|
||||
"vite": "^8.3.0",
|
||||
"zod": "^4.4.3",
|
||||
},
|
||||
},
|
||||
},
|
||||
@@ -39,6 +54,24 @@
|
||||
|
||||
"@cacheable/utils": ["@cacheable/utils@2.5.0", "", { "dependencies": { "hashery": "^1.5.1", "keyv": "^5.6.0" } }, "sha512-buipgOVDkkPXNR5+xBpDw7Zk2n1EvU7qBJCNUcL7rhQ//kfpOXPAvQ511Os0vpLYJ1pZnvudNytkQt2hst3wqA=="],
|
||||
|
||||
"@dagrejs/dagre": ["@dagrejs/dagre@2.0.4", "", { "dependencies": { "@dagrejs/graphlib": "3.0.4" } }, "sha512-J6vCWTNpicHF4zFlZG1cS5DkGzMr9941gddYkakjrg3ZNev4bbqEgLHFTWiFrcJm7UCRu7olO3K6IRDd9gSGhA=="],
|
||||
|
||||
"@dagrejs/graphlib": ["@dagrejs/graphlib@3.0.4", "", {}, "sha512-HxZ7fCvAwTLCWCO0WjDkzAFQze8LdC6iOpKbetDKHIuDfIgMlIzYzqZ4nxwLlclQX+3ZVeZ1K2OuaOE2WWcyOg=="],
|
||||
|
||||
"@dnd-kit-svelte/svelte": ["@dnd-kit-svelte/svelte@0.1.6", "", { "dependencies": { "@dnd-kit/abstract": "^0.2.3", "@dnd-kit/collision": "^0.2.3", "@dnd-kit/dom": "^0.2.3", "@dnd-kit/geometry": "^0.1.21", "@dnd-kit/state": "^0.2.3" }, "peerDependencies": { "svelte": "^5.0.0" } }, "sha512-kpmNWeQoPFrJ4SNB0thRkdWtN3kiYMQI/SvEj6MTKTQ8W5VyHBrbXDRm+mdiTj9tzmG8jgCPg+Fgp3aYIU+Hug=="],
|
||||
|
||||
"@dnd-kit/abstract": ["@dnd-kit/abstract@0.1.21", "", { "dependencies": { "@dnd-kit/geometry": "^0.1.21", "@dnd-kit/state": "^0.1.21", "tslib": "^2.6.2" } }, "sha512-6sJut6/D21xPIK8EFMu+JJeF+fBCOmQKN1BRpeUYFi5m9P1CJpTYbBwfI107h7PHObI6a5bsckiKkRpF2orHpw=="],
|
||||
|
||||
"@dnd-kit/collision": ["@dnd-kit/collision@0.2.4", "", { "dependencies": { "@dnd-kit/abstract": "^0.2.4", "@dnd-kit/geometry": "^0.2.4", "tslib": "^2.6.2" } }, "sha512-w8Vzq8MKu8MYvrwtasqQhw2l27v9eRMzL+jOdj8Qp0jnPQ1FICGgUwjAStZ87wO1Pc2WIt0rQWDDPIEyRd6S8A=="],
|
||||
|
||||
"@dnd-kit/dom": ["@dnd-kit/dom@0.2.4", "", { "dependencies": { "@dnd-kit/abstract": "^0.2.4", "@dnd-kit/collision": "^0.2.4", "@dnd-kit/geometry": "^0.2.4", "@dnd-kit/state": "^0.2.4", "tslib": "^2.6.2" } }, "sha512-ZYIJgx/uyZvmGOHPBDbIeJfqa2xlEUHnkHEHcKlR/VMQz9NoSkPGcjPdcAjpwB+T4dleRQ4tZ8h9X5I3UdTQPw=="],
|
||||
|
||||
"@dnd-kit/geometry": ["@dnd-kit/geometry@0.1.21", "", { "dependencies": { "@dnd-kit/state": "^0.1.21", "tslib": "^2.6.2" } }, "sha512-Tir97wNJbopN2HgkD7AjAcoB3vvrVuUHvwdPALmNDUH0fWR637c4MKQ66YjjZAbUEAR8KL6mlDiHH4MzTLd7CQ=="],
|
||||
|
||||
"@dnd-kit/helpers": ["@dnd-kit/helpers@0.1.21", "", { "dependencies": { "@dnd-kit/abstract": "^0.1.21", "tslib": "^2.6.2" } }, "sha512-yAFOcGaef8OqGJW/sHWnJn77b1Qe00f9ByPm19gNSEDH3/sgDszug7OwvqjoFAEnsDiKwdTgGYsPoyLSnrlprA=="],
|
||||
|
||||
"@dnd-kit/state": ["@dnd-kit/state@0.2.4", "", { "dependencies": { "@preact/signals-core": "^1.10.0", "tslib": "^2.6.2" } }, "sha512-mPtQfmqBZBcVKa+Fi8AV2ocQWa+phvZa9Muq3PxFM3WIyjrFiEosGGPiPcx5hC3K+i2gkmr6Msc7iPrrpYkB5g=="],
|
||||
|
||||
"@eslint-community/eslint-utils": ["@eslint-community/eslint-utils@4.10.1", "", { "dependencies": { "eslint-visitor-keys": "^3.4.3" }, "peerDependencies": { "eslint": "^6.0.0 || ^7.0.0 || >=8.0.0" } }, "sha512-cuadcxVFE8sDK6iWJbs8Sn0av2Nrh2QSGQhVlBW9AaAHqHwjWsZHT8LJ4hFGPh7ASBV2deFdM7H/DPjulmh8rg=="],
|
||||
|
||||
"@eslint-community/regexpp": ["@eslint-community/regexpp@4.12.2", "", {}, "sha512-EriSTlt5OC9/7SXkRSCAhfSxxoSUgBm33OH+IkwbdpgoqsSsUg7y3uh+IICI/Qg4BBWr3U2i39RpmycbxMq4ew=="],
|
||||
@@ -55,6 +88,12 @@
|
||||
|
||||
"@eslint/plugin-kit": ["@eslint/plugin-kit@0.7.3", "", { "dependencies": { "@eslint/core": "^1.2.1", "levn": "^0.4.1" } }, "sha512-IkO+/KEUvwbVpiURZg+P7zF74z5Jxe0UgJxVni+RtoHQ6IZieXaO02kmadomap/q+l6bc/jdPGGqTjhuZnuz1Q=="],
|
||||
|
||||
"@floating-ui/core": ["@floating-ui/core@1.8.0", "", { "dependencies": { "@floating-ui/utils": "^0.2.12" } }, "sha512-0CIZ5itps/8x7BG8dEIhs53BvCUH2PCoogtakwRTut+Arm58sJooJ0AuZhLw2HJYIR5cMLNPBSS728sPho2khQ=="],
|
||||
|
||||
"@floating-ui/dom": ["@floating-ui/dom@1.8.0", "", { "dependencies": { "@floating-ui/core": "^1.8.0", "@floating-ui/utils": "^0.2.12" } }, "sha512-yXSrzeHZBTZadLOlfyhCkJHNeLJnHRnRInwdZ40L7ZiaAtrBwoYlsDrX3v5zB1Utk7CLfzcOVnVVWoXEky7Ceg=="],
|
||||
|
||||
"@floating-ui/utils": ["@floating-ui/utils@0.2.12", "", {}, "sha512-HpCo8tmWzLVad5s2d19EhAz5zqrrQ6s69qd6moPMQvkOuSwDT1YgRfWSVuc4ennqrgv3OHppiOGMQ7oC13yIww=="],
|
||||
|
||||
"@fontsource-variable/inter": ["@fontsource-variable/inter@5.3.0", "", {}, "sha512-OupL48va4JNofb97w6NYeF9S7W/kHNKM0Er8Dem5nqi4jeOLrVJDoE8tZEpnMJmtkvNbB1EIPPwHcdkF6b1oUA=="],
|
||||
|
||||
"@humanfs/core": ["@humanfs/core@0.19.2", "", { "dependencies": { "@humanfs/types": "^0.15.0" } }, "sha512-UhXNm+CFMWcbChXywFwkmhqjs3PRCmcSa/hfBgLIb7oQ5HNb1wS0icWsGtSAUNgefHeI+eBrA8I1fxmbHsGdvA=="],
|
||||
@@ -67,6 +106,8 @@
|
||||
|
||||
"@humanwhocodes/retry": ["@humanwhocodes/retry@0.4.3", "", {}, "sha512-bV0Tgo9K4hfPCek+aMAn81RppFKv2ySDQeMoSZuvTASywNTnVJCArCZE2FWqpvIatKu7VMRLWlR1EazvVhDyhQ=="],
|
||||
|
||||
"@internationalized/date": ["@internationalized/date@3.12.4", "", { "dependencies": { "@swc/helpers": "^0.5.0" } }, "sha512-M1dEn4c1U1HsSlaVR8upZtSqvXrTkHDfv18H01uCSJyjVLDxnBR38v/fMxecmlwXKR4i9HeZcmgQAPE6A+aGJQ=="],
|
||||
|
||||
"@jridgewell/gen-mapping": ["@jridgewell/gen-mapping@0.3.13", "", { "dependencies": { "@jridgewell/sourcemap-codec": "^1.5.0", "@jridgewell/trace-mapping": "^0.3.24" } }, "sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA=="],
|
||||
|
||||
"@jridgewell/remapping": ["@jridgewell/remapping@2.3.5", "", { "dependencies": { "@jridgewell/gen-mapping": "^0.3.5", "@jridgewell/trace-mapping": "^0.3.24" } }, "sha512-LI9u/+laYG4Ds1TDKSJW2YPrIlcVYOwi2fUC6xB43lueCjgxV4lffOCZCtYFiH6TNOX+tQKXx97T4IKHbhyHEQ=="],
|
||||
@@ -81,12 +122,22 @@
|
||||
|
||||
"@keyv/serialize": ["@keyv/serialize@1.1.1", "", {}, "sha512-dXn3FZhPv0US+7dtJsIi2R+c7qWYiReoEh5zUntWCf4oSpMNib8FDhSoed6m3QyZdx5hK7iLFkYk3rNxwt8vTA=="],
|
||||
|
||||
"@layerstack/svelte-actions": ["@layerstack/svelte-actions@1.0.1-next.20", "", { "dependencies": { "@floating-ui/dom": "^1.7.0", "@layerstack/utils": "2.0.0-next.19", "d3-scale": "^4.0.2" } }, "sha512-OtFteDyvTUR6F7dSKPkpD/S77K7vbCHStApSlx8djJBxDKJHU3zxA4gYP6Z71Nc6HB/wLCfjZopkvxnMblbRRw=="],
|
||||
|
||||
"@layerstack/svelte-state": ["@layerstack/svelte-state@0.1.0-next.25", "", { "dependencies": { "@layerstack/utils": "2.0.0-next.19" } }, "sha512-K+SO4dYd2D9OSCnj92Ugo6i5OfVKZoHHA1DA7gs2ynigylRKInicniGI/OmRpp+002n3mhE/i2r3Kfctj8eAdw=="],
|
||||
|
||||
"@layerstack/tailwind": ["@layerstack/tailwind@2.0.0-next.21", "", { "dependencies": { "@layerstack/utils": "^2.0.0-next.18", "clsx": "^2.1.1", "d3-array": "^3.2.4", "tailwind-merge": "^3.2.0" } }, "sha512-Qgp2EpmEHmjtura8MQzWicR6ztBRSsRvddakFtx9ShrLMz6jWzd6bCMVVRu44Q3ZOrtXmSu4QxjCZWu1ytvuPg=="],
|
||||
|
||||
"@layerstack/utils": ["@layerstack/utils@2.0.0-next.19", "", { "dependencies": { "d3-array": "^3.2.4", "d3-time": "^3.1.0", "d3-time-format": "^4.1.0" } }, "sha512-HyXCEz9pge4rFWklrSgFr3YIwdE1gP8WM6GPetmnKPDfYLF1/oetRFKI9FqiE+dw4brnULGPKJsVhjM3vQfD4A=="],
|
||||
|
||||
"@lucide/svelte": ["@lucide/svelte@1.52.0", "", { "peerDependencies": { "svelte": "^5" } }, "sha512-bQ8t3IcZxWqAdmdMZPyZEv8Qf+MDgBAf7n43eRQoXyBeLWr8HWToiMIO6vKlHxC/lnMiCDdTzm09hjy92ch46A=="],
|
||||
|
||||
"@oxc-project/types": ["@oxc-project/types@0.152.0", "", {}, "sha512-oM/5rLBm2tPkg0iBgkH/FOeR3PCDpY19GTgAZjMFM8h9WI9VW7cLgzp6nwtarYKmovavIQZ+Fe/RKX/8C8O/Rw=="],
|
||||
|
||||
"@polka/url": ["@polka/url@1.0.0-next.29", "", {}, "sha512-wwQAWhWSuHaag8c4q/KN/vCoeOJYshAIvMQwD4GpSb3OiZklFfvAgmj0VCBBImRpuF/aFgIRzllXlVX93Jevww=="],
|
||||
|
||||
"@preact/signals-core": ["@preact/signals-core@1.14.4", "", {}, "sha512-HNB6HYeYKhQbJ1aKl+YRjrS4+QWHLKX6qKoUsfS/m0vqzsVaEBiZiaKbG/e+NKk2ch5ALQr/ihWaMHxiCuuWHA=="],
|
||||
|
||||
"@rolldown/binding-android-arm-eabi": ["@rolldown/binding-android-arm-eabi@1.2.12", "", { "os": "android", "cpu": "arm" }, "sha512-dB/a1214qKfHMXCpgqR4OZT+jS4kTyEXbQGJPqzobt5EwH5rX080pxE37alt3RzvR1bf1Yz/yGqRfrYAxuPw0A=="],
|
||||
|
||||
"@rolldown/binding-android-arm64": ["@rolldown/binding-android-arm64@1.2.12", "", { "os": "android", "cpu": "arm64" }, "sha512-7KHFgQ5VJxIHcLlrwrc3Xbds7oTNQT7Pgi9gQCJKrd2VGab/UksIOYp6VD8MzCstGxOKMgNamPwUCfxPdP1OHg=="],
|
||||
@@ -131,6 +182,12 @@
|
||||
|
||||
"@sveltejs/vite-plugin-svelte": ["@sveltejs/vite-plugin-svelte@7.3.1", "", { "dependencies": { "deepmerge": "^4.3.1", "magic-string": "^1.0.0", "obug": "^2.1.0", "vitefu": "^1.1.2" }, "peerDependencies": { "svelte": "^5.46.4", "vite": "^8.0.0-beta.7 || ^8.0.0" } }, "sha512-ZPsLN8B1e/En+Ak5s4V7srFDT532oS0qieLsQwu63NGKsS+iAjoO2Js1BochlHlglcU+Pt7WAO3C5Ee+4f6gVA=="],
|
||||
|
||||
"@swc/helpers": ["@swc/helpers@0.5.23", "", { "dependencies": { "tslib": "^2.8.0" } }, "sha512-5lSsMOTXURePglDfvuAQUqkGek9Hg2kksOYay2m0+XR++b2NWYL/4sWyuvVBIs8oKnJaxkdi9whaL/sqN13afw=="],
|
||||
|
||||
"@tabler/icons": ["@tabler/icons@3.49.0", "", {}, "sha512-l+GnSkFETx0U26icxv1VUFnxxiTqXfCcVRiRAnyMwibupM2S2L/hSaZ9gmSgz01rkrsH/93NvLsxykqdanc6lw=="],
|
||||
|
||||
"@tabler/icons-svelte": ["@tabler/icons-svelte@3.49.0", "", { "dependencies": { "@tabler/icons": "3.49.0" }, "peerDependencies": { "svelte": ">=3 <6 || >=5.0.0-next.0" } }, "sha512-+omJWDCEHHfloOoMELXkdlrIpoQCN2XBx2xnb2NxqDjxsAGGTii0UQF05UoKiUO97skqe1eEIbeGjRZ9xUye0w=="],
|
||||
|
||||
"@tailwindcss/node": ["@tailwindcss/node@4.3.3", "", { "dependencies": { "@jridgewell/remapping": "^2.3.5", "enhanced-resolve": "^5.24.1", "jiti": "^2.7.0", "lightningcss": "1.32.0", "magic-string": "^0.30.21", "source-map-js": "^1.2.1", "tailwindcss": "4.3.3" } }, "sha512-/T8IKEsf9VTU6tLjgC7+sv2mOPtQxzE2jMw7u4Tt40Tx+QSZxpzh95/H6cMKoja9XuW7iMdLJYBB0o9G1CaAgg=="],
|
||||
|
||||
"@tailwindcss/oxide": ["@tailwindcss/oxide@4.3.3", "", { "optionalDependencies": { "@tailwindcss/oxide-android-arm64": "4.3.3", "@tailwindcss/oxide-darwin-arm64": "4.3.3", "@tailwindcss/oxide-darwin-x64": "4.3.3", "@tailwindcss/oxide-freebsd-x64": "4.3.3", "@tailwindcss/oxide-linux-arm-gnueabihf": "4.3.3", "@tailwindcss/oxide-linux-arm64-gnu": "4.3.3", "@tailwindcss/oxide-linux-arm64-musl": "4.3.3", "@tailwindcss/oxide-linux-x64-gnu": "4.3.3", "@tailwindcss/oxide-linux-x64-musl": "4.3.3", "@tailwindcss/oxide-wasm32-wasi": "4.3.3", "@tailwindcss/oxide-win32-arm64-msvc": "4.3.3", "@tailwindcss/oxide-win32-x64-msvc": "4.3.3" } }, "sha512-krXjAikiaFSPaK/FkAQT5UTx3VormQaiZ5hBFlJZ9UFQGB/rwg1MZIhHAG9smMQRTdyJxP6Qt5MwMtdyU5FWrA=="],
|
||||
@@ -163,10 +220,32 @@
|
||||
|
||||
"@tailwindcss/vite": ["@tailwindcss/vite@4.3.3", "", { "dependencies": { "@tailwindcss/node": "4.3.3", "@tailwindcss/oxide": "4.3.3", "tailwindcss": "4.3.3" }, "peerDependencies": { "vite": "^5.2.0 || ^6 || ^7 || ^8" } }, "sha512-yYU8cogLeSh/ms2jh8Fj7jaba/EWa7Ja6GoUqYZaraEuCI5YS6ms6ObZgjjedm+jm6XZjdNRWBpPP6Z86oOxcw=="],
|
||||
|
||||
"@tanstack/store": ["@tanstack/store@0.11.2", "", {}, "sha512-sJ4mjol8uQsHV0gOJzzjwXfh2Fwm+Sz0+8deqiTm4jGbMdjzNSW+xZCFm0kUa870uhd8yi+DpKnZb5Kc4apa0Q=="],
|
||||
|
||||
"@tanstack/svelte-store": ["@tanstack/svelte-store@0.12.2", "", { "dependencies": { "@tanstack/store": "0.11.2" }, "peerDependencies": { "svelte": "^5.0.0" } }, "sha512-zI+LjpxnbfKm5vDogx07RN12oOuI6LR+GysrQ4aVCuw76pY8pwcPT2DU9JGIX+L+r1dzsxBGac8XZETzLGQxiA=="],
|
||||
|
||||
"@tanstack/svelte-table": ["@tanstack/svelte-table@9.2.6", "", { "dependencies": { "@tanstack/svelte-store": "^0.12.2", "@tanstack/table-core": "9.2.6" }, "peerDependencies": { "svelte": "^5.0.0" } }, "sha512-+enojkMPHDwX9W1D9R2ZeBQt2K+6vWp6mhHdsapGtLgmXb7sDLQmYPvq/rE4K9o+MBUVqmaq++UV/1h2/Iw83Q=="],
|
||||
|
||||
"@tanstack/table-core": ["@tanstack/table-core@9.2.6", "", { "dependencies": { "@tanstack/store": "^0.11.2" } }, "sha512-t5LaLx8gDCgzjvwtZo5xtLklJP11D9zaKOW8q6jOCyuQYhkqpTK1heqMfgTQwDlhl0dU8f1X66Xurbj3jpCUSw=="],
|
||||
|
||||
"@types/d3-array": ["@types/d3-array@3.2.2", "", {}, "sha512-hOLWVbm7uRza0BYXpIIW5pxfrKe0W+D5lrFiAEYR+pb6w3N2SwSMaJbXdUfSEv+dT4MfHBLtn5js0LAWaO6otw=="],
|
||||
|
||||
"@types/d3-contour": ["@types/d3-contour@3.0.6", "", { "dependencies": { "@types/d3-array": "*", "@types/geojson": "*" } }, "sha512-BjzLgXGnCWjUSYGfH1cpdo41/hgdWETu4YxpezoztawmqsvCeep+8QGfiY6YbDvfgHz/DkjeIkkZVJavB4a3rg=="],
|
||||
|
||||
"@types/d3-path": ["@types/d3-path@3.1.1", "", {}, "sha512-VMZBYyQvbGmWyWVea0EHs/BwLgxc+MKi1zLDCONksozI4YJMcTt8ZEuIR4Sb1MMTE8MMW49v0IwI5+b7RmfWlg=="],
|
||||
|
||||
"@types/d3-scale": ["@types/d3-scale@4.0.9", "", { "dependencies": { "@types/d3-time": "*" } }, "sha512-dLmtwB8zkAeO/juAMfnV+sItKjlsw2lKdZVVy6LRr0cBmegxSABiLEpGVmSJJ8O08i4+sGR6qQtb6WtuwJdvVw=="],
|
||||
|
||||
"@types/d3-shape": ["@types/d3-shape@3.2.0", "", { "dependencies": { "@types/d3-path": "*" } }, "sha512-kVd74ta9eof3eJOvbNd1vGKS/XERRyQbT26Og63hIsvDO84cjD5gEOhsXf26w3FSoNlPVz84DOFcKv/oou+fMw=="],
|
||||
|
||||
"@types/d3-time": ["@types/d3-time@3.0.4", "", {}, "sha512-yuzZug1nkAAaBlBBikKZTgzCeA+k1uy4ZFwWANOfKw5z5LRhV0gNA7gNkKm7HoK+HRN0wX3EkxGk0fpbWhmB7g=="],
|
||||
|
||||
"@types/esrecurse": ["@types/esrecurse@4.3.1", "", {}, "sha512-xJBAbDifo5hpffDBuHl0Y8ywswbiAp/Wi7Y/GtAgSlZyIABppyurxVueOPE8LUQOxdlgi6Zqce7uoEpqNTeiUw=="],
|
||||
|
||||
"@types/estree": ["@types/estree@1.0.9", "", {}, "sha512-GhdPgy1el4/ImP05X05Uw4cw2/M93BCUmnEvWZNStlCzEKME4Fkk+YpoA5OiHNQmoS7Cafb8Xa3Pya8m1Qrzeg=="],
|
||||
|
||||
"@types/geojson": ["@types/geojson@7946.0.16", "", {}, "sha512-6C8nqWur3j98U6+lXDfTUWIfgvZU+EumvpHKcYjujKH7woYyLj2sUmff0tRhrqM7BohUw7Pz3ZB1jj2gW9Fvmg=="],
|
||||
|
||||
"@types/json-schema": ["@types/json-schema@7.0.15", "", {}, "sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA=="],
|
||||
|
||||
"@types/node": ["@types/node@26.6.4", "", { "dependencies": { "undici-types": "~8.9.0" } }, "sha512-ldVPDCzj7fsaGZrLB0NuHuTvJcsNasysBAqMolr/cgxrLd1xbqxIr3XJiPnHHJUCxj5sNF1vnRj9aWnrVh5Jcg=="],
|
||||
@@ -203,6 +282,8 @@
|
||||
|
||||
"balanced-match": ["balanced-match@4.0.4", "", {}, "sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA=="],
|
||||
|
||||
"bits-ui": ["bits-ui@2.19.5", "", { "dependencies": { "@floating-ui/core": "^1.7.1", "@floating-ui/dom": "^1.7.1", "esm-env": "^1.1.2", "runed": "^0.35.1", "svelte-toolbelt": "^0.10.6", "tabbable": "^6.2.0" }, "peerDependencies": { "@internationalized/date": "^3.8.1", "svelte": "^5.33.0" } }, "sha512-TEwkLSYElE5SE0fIdcItiFPfyg9vrR67N1Mt6VvZCqZo24dwTA1MpGVV3WeyIg7rfVAaZ0So8a0/dcnPngCoew=="],
|
||||
|
||||
"brace-expansion": ["brace-expansion@5.0.12", "", { "dependencies": { "balanced-match": "^4.0.2" } }, "sha512-YovQ3rzhaLMIrDjNDMkNS01tea93qhEhG5xy8f6+R0l+dw3Ki+5sCoIoI942iuLZTHWogWktgwVDhU09iNEimQ=="],
|
||||
|
||||
"cacheable": ["cacheable@2.5.0", "", { "dependencies": { "@cacheable/memory": "^2.2.0", "@cacheable/utils": "^2.5.0", "hookified": "^1.15.0", "keyv": "^5.6.0", "qified": "^0.10.1" } }, "sha512-60cyAOytib/OzBw1JNSoSV/boK1AtHryDIjvVBk7XbN4ugfkM3+Sry7fEjNgPMGgOjuaZPAp8ruZ0Cxafwyq9g=="],
|
||||
@@ -221,12 +302,70 @@
|
||||
|
||||
"cssesc": ["cssesc@3.0.0", "", { "bin": { "cssesc": "bin/cssesc" } }, "sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg=="],
|
||||
|
||||
"d3-array": ["d3-array@3.2.4", "", { "dependencies": { "internmap": "1 - 2" } }, "sha512-tdQAmyA18i4J7wprpYq8ClcxZy3SC31QMeByyCFyRt7BVHdREQZ5lpzoe5mFEYZUWe+oq8HBvk9JjpibyEV4Jg=="],
|
||||
|
||||
"d3-chord": ["d3-chord@3.0.1", "", { "dependencies": { "d3-path": "1 - 3" } }, "sha512-VE5S6TNa+j8msksl7HwjxMHDM2yNK3XCkusIlpX5kwauBfXuyLAtNg9jCp/iHH61tgI4sb6R/EIMWCqEIdjT/g=="],
|
||||
|
||||
"d3-color": ["d3-color@3.1.0", "", {}, "sha512-zg/chbXyeBtMQ1LbD/WSoW2DpC3I0mpmPdW+ynRTj/x2DAWYrIY7qeZIHidozwV24m4iavr15lNwIwLxRmOxhA=="],
|
||||
|
||||
"d3-contour": ["d3-contour@4.0.2", "", { "dependencies": { "d3-array": "^3.2.0" } }, "sha512-4EzFTRIikzs47RGmdxbeUvLWtGedDUNkTcmzoeyg4sP/dvCexO47AaQL7VKy/gul85TOxw+IBgA8US2xwbToNA=="],
|
||||
|
||||
"d3-delaunay": ["d3-delaunay@6.0.4", "", { "dependencies": { "delaunator": "5" } }, "sha512-mdjtIZ1XLAM8bm/hx3WwjfHt6Sggek7qH043O8KEjDXN40xi3vx/6pYSVTwLjEgiXQTbvaouWKynLBiUZ6SK6A=="],
|
||||
|
||||
"d3-dispatch": ["d3-dispatch@3.0.1", "", {}, "sha512-rzUyPU/S7rwUflMyLc1ETDeBj0NRuHKKAcvukozwhshr6g6c5d8zh4c2gQjY2bZ0dXeGLWc1PF174P2tVvKhfg=="],
|
||||
|
||||
"d3-dsv": ["d3-dsv@3.0.1", "", { "dependencies": { "commander": "7", "iconv-lite": "0.6", "rw": "1" }, "bin": { "csv2json": "bin/dsv2json.js", "csv2tsv": "bin/dsv2dsv.js", "dsv2dsv": "bin/dsv2dsv.js", "dsv2json": "bin/dsv2json.js", "json2csv": "bin/json2dsv.js", "json2dsv": "bin/json2dsv.js", "json2tsv": "bin/json2dsv.js", "tsv2csv": "bin/dsv2dsv.js", "tsv2json": "bin/dsv2json.js" } }, "sha512-UG6OvdI5afDIFP9w4G0mNq50dSOsXHJaRE8arAS5o9ApWnIElp8GZw1Dun8vP8OyHOZ/QJUKUJwxiiCCnUwm+Q=="],
|
||||
|
||||
"d3-force": ["d3-force@3.0.0", "", { "dependencies": { "d3-dispatch": "1 - 3", "d3-quadtree": "1 - 3", "d3-timer": "1 - 3" } }, "sha512-zxV/SsA+U4yte8051P4ECydjD/S+qeYtnaIyAs9tgHCqfguma/aAQDjo85A9Z6EKhBirHRJHXIgJUlffT4wdLg=="],
|
||||
|
||||
"d3-format": ["d3-format@3.1.2", "", {}, "sha512-AJDdYOdnyRDV5b6ArilzCPPwc1ejkHcoyFarqlPqT7zRYjhavcT3uSrqcMvsgh2CgoPbK3RCwyHaVyxYcP2Arg=="],
|
||||
|
||||
"d3-geo": ["d3-geo@3.1.1", "", { "dependencies": { "d3-array": "2.5.0 - 3" } }, "sha512-637ln3gXKXOwhalDzinUgY83KzNWZRKbYubaG+fGVuc/dxO64RRljtCTnf5ecMyE1RIdtqpkVcq0IbtU2S8j2Q=="],
|
||||
|
||||
"d3-geo-voronoi": ["d3-geo-voronoi@2.1.0", "", { "dependencies": { "d3-array": "3", "d3-delaunay": "6", "d3-geo": "3", "d3-tricontour": "1" } }, "sha512-kqE4yYuOjPbKdBXG0xztCacPwkVSK2REF1opSNrnqqtXJmNcM++UbwQ8SxvwP6IQTj9RvIjjK4qeiVsEfj0Z2Q=="],
|
||||
|
||||
"d3-hierarchy": ["d3-hierarchy@3.1.2", "", {}, "sha512-FX/9frcub54beBdugHjDCdikxThEqjnR93Qt7PvQTOHxyiNCAlvMrHhclk3cD5VeAaq9fxmfRp+CnWw9rEMBuA=="],
|
||||
|
||||
"d3-interpolate": ["d3-interpolate@3.0.1", "", { "dependencies": { "d3-color": "1 - 3" } }, "sha512-3bYs1rOD33uo8aqJfKP3JWPAibgw8Zm2+L9vBKEHJ2Rg+viTR7o5Mmv5mZcieN+FRYaAOWX5SJATX6k1PWz72g=="],
|
||||
|
||||
"d3-interpolate-path": ["d3-interpolate-path@2.3.0", "", {}, "sha512-tZYtGXxBmbgHsIc9Wms6LS5u4w6KbP8C09a4/ZYc4KLMYYqub57rRBUgpUr2CIarIrJEpdAWWxWQvofgaMpbKQ=="],
|
||||
|
||||
"d3-path": ["d3-path@3.1.0", "", {}, "sha512-p3KP5HCf/bvjBSSKuXid6Zqijx7wIfNW+J/maPs+iwR35at5JCbLUT0LzF1cnjbCHWhqzQTIN2Jpe8pRebIEFQ=="],
|
||||
|
||||
"d3-polygon": ["d3-polygon@3.0.1", "", {}, "sha512-3vbA7vXYwfe1SYhED++fPUQlWSYTTGmFmQiany/gdbiWgU/iEyQzyymwL9SkJjFFuCS4902BSzewVGsHHmHtXg=="],
|
||||
|
||||
"d3-quadtree": ["d3-quadtree@3.0.1", "", {}, "sha512-04xDrxQTDTCFwP5H6hRhsRcb9xxv2RzkcsygFzmkSIOJy3PeRJP7sNk3VRIbKXcog561P9oU0/rVH6vDROAgUw=="],
|
||||
|
||||
"d3-random": ["d3-random@3.0.1", "", {}, "sha512-FXMe9GfxTxqd5D6jFsQ+DJ8BJS4E/fT5mqqdjovykEB2oFbTMDVdg1MGFxfQW+FBOGoB++k8swBrgwSHT1cUXQ=="],
|
||||
|
||||
"d3-sankey": ["d3-sankey@0.12.3", "", { "dependencies": { "d3-array": "1 - 2", "d3-shape": "^1.2.0" } }, "sha512-nQhsBRmM19Ax5xEIPLMY9ZmJ/cDvd1BG3UVvt5h3WRxKg5zGRbvnteTyWAbzeSvlh3tW7ZEmq4VwR5mB3tutmQ=="],
|
||||
|
||||
"d3-scale": ["d3-scale@4.0.2", "", { "dependencies": { "d3-array": "2.10.0 - 3", "d3-format": "1 - 3", "d3-interpolate": "1.2.0 - 3", "d3-time": "2.1.1 - 3", "d3-time-format": "2 - 4" } }, "sha512-GZW464g1SH7ag3Y7hXjf8RoUuAFIqklOAq3MRl4OaWabTFJY9PN/E1YklhXLh+OQ3fM9yS2nOkCoS+WLZ6kvxQ=="],
|
||||
|
||||
"d3-scale-chromatic": ["d3-scale-chromatic@3.1.0", "", { "dependencies": { "d3-color": "1 - 3", "d3-interpolate": "1 - 3" } }, "sha512-A3s5PWiZ9YCXFye1o246KoscMWqf8BsD9eRiJ3He7C9OBaxKhAd5TFCdEx/7VbKtxxTsu//1mMJFrEt572cEyQ=="],
|
||||
|
||||
"d3-shape": ["d3-shape@3.2.0", "", { "dependencies": { "d3-path": "^3.1.0" } }, "sha512-SaLBuwGm3MOViRq2ABk3eLoxwZELpH6zhl3FbAoJ7Vm1gofKx6El1Ib5z23NUEhF9AsGl7y+dzLe5Cw2AArGTA=="],
|
||||
|
||||
"d3-tile": ["d3-tile@1.0.0", "", {}, "sha512-79fnTKpPMPDS5xQ0xuS9ir0165NEwwkFpe/DSOmc2Gl9ldYzKKRDWogmTTE8wAJ8NA7PMapNfEcyKhI9Lxdu5Q=="],
|
||||
|
||||
"d3-time": ["d3-time@3.1.0", "", { "dependencies": { "d3-array": "2 - 3" } }, "sha512-VqKjzBLejbSMT4IgbmVgDjpkYrNWUYJnbCGo874u7MMKIWsILRX+OpX/gTk8MqjpT1A/c6HY2dCA77ZN0lkQ2Q=="],
|
||||
|
||||
"d3-time-format": ["d3-time-format@4.1.0", "", { "dependencies": { "d3-time": "1 - 3" } }, "sha512-dJxPBlzC7NugB2PDLwo9Q8JiTR3M3e4/XANkreKSUxF8vvXKqm1Yfq4Q5dl8budlunRVlUUaDUgFt7eA8D6NLg=="],
|
||||
|
||||
"d3-timer": ["d3-timer@3.0.1", "", {}, "sha512-ndfJ/JxxMd3nw31uyKoY2naivF+r29V+Lc0svZxe1JvvIRmi8hUsrMvdOwgS1o6uBHmiz91geQ0ylPP0aj1VUA=="],
|
||||
|
||||
"d3-tricontour": ["d3-tricontour@1.1.0", "", { "dependencies": { "d3-delaunay": "6", "d3-scale": "4" } }, "sha512-G7gHKj89n2owmkGb6WX6ixcnQ0Kf/0wpa9VIh9DGdbHu8wdrlaHU4ir3/bFNERl8N8nn4G7e7qbtBG8N9caihQ=="],
|
||||
|
||||
"debug": ["debug@4.4.3", "", { "dependencies": { "ms": "^2.1.3" }, "peerDependencies": { "supports-color": "*" }, "optionalPeers": ["supports-color"] }, "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA=="],
|
||||
|
||||
"deep-is": ["deep-is@0.1.4", "", {}, "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ=="],
|
||||
|
||||
"deepmerge": ["deepmerge@4.3.1", "", {}, "sha512-3sUqbMEc77XqpdNO7FRyRog+eW3ph+GYCbj+rK+uYyRMuwsVy0rMiVtPn+QJlKFvWP/1PYpapqYn0Me2knFn+A=="],
|
||||
|
||||
"delaunator": ["delaunator@5.1.0", "", { "dependencies": { "robust-predicates": "^3.0.2" } }, "sha512-AGrQ4QSgssa1NGmWmLPqN5NY2KajF5MqxetNEO+o0n3ZwZZeTmt7bBnvzHWrmkZFxGgr4HdyFgelzgi06otLuQ=="],
|
||||
|
||||
"dequal": ["dequal@2.0.3", "", {}, "sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA=="],
|
||||
|
||||
"detect-libc": ["detect-libc@2.1.2", "", {}, "sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ=="],
|
||||
|
||||
"devalue": ["devalue@5.9.4", "", {}, "sha512-sPAT4pztbu6586/hrhOnMKS17IJrvg12mXiSPSS3W5qDeN2RGgvZ0diZCm31dBbnevfVmujNO3IM2wrS4Y2Rhg=="],
|
||||
@@ -287,10 +426,16 @@
|
||||
|
||||
"hookified": ["hookified@1.15.1", "", {}, "sha512-MvG/clsADq1GPM2KGo2nyfaWVyn9naPiXrqIe4jYjXNZQt238kWyOGrsyc/DmRAQ+Re6yeo6yX/yoNCG5KAEVg=="],
|
||||
|
||||
"iconv-lite": ["iconv-lite@0.6.3", "", { "dependencies": { "safer-buffer": ">= 2.1.2 < 3.0.0" } }, "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw=="],
|
||||
|
||||
"ignore": ["ignore@5.3.2", "", {}, "sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g=="],
|
||||
|
||||
"imurmurhash": ["imurmurhash@0.1.4", "", {}, "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA=="],
|
||||
|
||||
"inline-style-parser": ["inline-style-parser@0.2.7", "", {}, "sha512-Nb2ctOyNR8DqQoR0OwRG95uNWIC0C1lCgf5Naz5H6Ji72KZ8OcFZLz2P5sNgwlyoJ8Yif11oMuYs5pBQa86csA=="],
|
||||
|
||||
"internmap": ["internmap@2.0.3", "", {}, "sha512-5Hh7Y1wQbvY5ooGgPbDaL5iYLAPzMTUrjMulskHLH6wnv/A+1q5rgEaiuqEjB+oxGXIVZs1FF+R/KPN3ZSQYYg=="],
|
||||
|
||||
"is-extglob": ["is-extglob@2.1.1", "", {}, "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ=="],
|
||||
|
||||
"is-glob": ["is-glob@4.0.3", "", { "dependencies": { "is-extglob": "^2.1.1" } }, "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg=="],
|
||||
@@ -309,6 +454,8 @@
|
||||
|
||||
"known-css-properties": ["known-css-properties@0.37.0", "", {}, "sha512-JCDrsP4Z1Sb9JwG0aJ8Eo2r7k4Ou5MwmThS/6lcIe1ICyb7UBJKGRIUUdqc2ASdE/42lgz6zFUnzAIhtXnBVrQ=="],
|
||||
|
||||
"layerchart": ["layerchart@2.5.1", "", { "dependencies": { "@dagrejs/dagre": "^2.0.4", "@layerstack/svelte-actions": "1.0.1-next.20", "@layerstack/svelte-state": "0.1.0-next.25", "@layerstack/tailwind": "2.0.0-next.21", "@layerstack/utils": "2.0.0-next.19", "@types/d3-contour": "^3.0.6", "d3-array": "^3.2.4", "d3-chord": "^3.0.1", "d3-color": "^3.1.0", "d3-contour": "^4.0.2", "d3-delaunay": "^6.0.4", "d3-dsv": "^3.0.1", "d3-force": "^3.0.0", "d3-geo": "^3.1.1", "d3-geo-voronoi": "^2.1.0", "d3-hierarchy": "^3.1.2", "d3-interpolate": "^3.0.1", "d3-interpolate-path": "^2.3.0", "d3-path": "^3.1.0", "d3-polygon": "^3.0.1", "d3-quadtree": "^3.0.1", "d3-random": "^3.0.1", "d3-sankey": "^0.12.3", "d3-scale": "^4.0.2", "d3-scale-chromatic": "^3.1.0", "d3-shape": "^3.2.0", "d3-tile": "^1.0.0", "d3-time": "^3.1.0", "memoize": "^10.2.0", "runed": "^0.37.1" }, "peerDependencies": { "svelte": "^5.0.0" } }, "sha512-OMrjH4yHTIJ0pQ5Qs/o7M9S5IoF15c/1/CEBKcCaZ3xqKxh4Tm5hzqoIjt0uud65t7BxgseavUh1Ah/bWUmjaA=="],
|
||||
|
||||
"levn": ["levn@0.4.1", "", { "dependencies": { "prelude-ls": "^1.2.1", "type-check": "~0.4.0" } }, "sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ=="],
|
||||
|
||||
"lightningcss": ["lightningcss@1.33.0", "", { "dependencies": { "detect-libc": "^2.0.3" }, "optionalDependencies": { "lightningcss-android-arm64": "1.33.0", "lightningcss-darwin-arm64": "1.33.0", "lightningcss-darwin-x64": "1.33.0", "lightningcss-freebsd-x64": "1.33.0", "lightningcss-linux-arm-gnueabihf": "1.33.0", "lightningcss-linux-arm64-gnu": "1.33.0", "lightningcss-linux-arm64-musl": "1.33.0", "lightningcss-linux-x64-gnu": "1.33.0", "lightningcss-linux-x64-musl": "1.33.0", "lightningcss-win32-arm64-msvc": "1.33.0", "lightningcss-win32-x64-msvc": "1.33.0" } }, "sha512-WkUDrojuJs0xkgGf2udWxa3yGBRxPtxUkB79i6aCZLRgc7PM8fZe9TosfPDcvEpQZbuFASnHYmRLBLUbmLOIIA=="],
|
||||
@@ -341,8 +488,14 @@
|
||||
|
||||
"locate-path": ["locate-path@6.0.0", "", { "dependencies": { "p-locate": "^5.0.0" } }, "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw=="],
|
||||
|
||||
"lz-string": ["lz-string@1.5.0", "", { "bin": { "lz-string": "bin/bin.js" } }, "sha512-h5bgJWpxJNswbU7qCrV0tIKQCaS3blPDrqKWx+QxzuzL1zGUzij9XCWLrSLsJPu5t+eWA/ycetzYAO5IOMcWAQ=="],
|
||||
|
||||
"magic-string": ["magic-string@1.4.3", "", { "dependencies": { "@jridgewell/sourcemap-codec": "^1.6.0" } }, "sha512-z12OxmaPGE0F4xlpGdjuAUckipLpQlTAuAmyGhNoD7ODpYUzvDfvtxUbjM/jwznyP178oju/KDdyi220wNJ0RQ=="],
|
||||
|
||||
"memoize": ["memoize@10.2.0", "", { "dependencies": { "mimic-function": "^5.0.1" } }, "sha512-DeC6b7QBrZsRs3Y02A6A7lQyzFbsQbqgjI6UW0GigGWV+u1s25TycMr0XHZE4cJce7rY/vyw2ctMQqfDkIhUEA=="],
|
||||
|
||||
"mimic-function": ["mimic-function@5.0.1", "", {}, "sha512-VP79XUPxV2CigYP3jWwAUFSku2aKqBH7uTAapFWCBqutsbmDo96KY5o8uh6U+/YSIn5OxJnXp73beVkpqMIGhA=="],
|
||||
|
||||
"minimatch": ["minimatch@10.2.6", "", { "dependencies": { "brace-expansion": "^5.0.8" } }, "sha512-vpLQEs+VLCr1nU0BXS07maYoFwlDAH0gngQuuttxIwutDFEMHq2blX+8vpgxDdK3J1PwjCJiep77OitTZ4Ll1A=="],
|
||||
|
||||
"mri": ["mri@1.2.0", "", {}, "sha512-tzzskb3bG8LvYGFF/mDTpq3jpI6Q9wc3LEmBaghu+DdCssd1FakN7Bc0hVNmEyGq1bq3RgfkCb3cmQLpNPOroA=="],
|
||||
@@ -397,10 +550,18 @@
|
||||
|
||||
"readdirp": ["readdirp@4.1.2", "", {}, "sha512-GDhwkLfywWL2s6vEjyhri+eXmfH6j1L7JE27WhqLeYzoh/A3DBaYGEj2H/HFZCn/kMfim73FXxEJTw06WtxQwg=="],
|
||||
|
||||
"robust-predicates": ["robust-predicates@3.0.3", "", {}, "sha512-NS3levdsRIUOmiJ8FZWCP7LG3QpJyrs/TE0Zpf1yvZu8cAJJ6QMW92H1c7kWpdIHo8RvmLxN/o2JXTKHp74lUA=="],
|
||||
|
||||
"rolldown": ["rolldown@1.2.12", "", { "dependencies": { "@oxc-project/types": "=0.152.0", "@rolldown/pluginutils": "^1.0.0" }, "optionalDependencies": { "@rolldown/binding-android-arm-eabi": "1.2.12", "@rolldown/binding-android-arm64": "1.2.12", "@rolldown/binding-darwin-arm64": "1.2.12", "@rolldown/binding-darwin-x64": "1.2.12", "@rolldown/binding-freebsd-x64": "1.2.12", "@rolldown/binding-linux-arm-gnueabihf": "1.2.12", "@rolldown/binding-linux-arm64-gnu": "1.2.12", "@rolldown/binding-linux-arm64-musl": "1.2.12", "@rolldown/binding-linux-ppc64-gnu": "1.2.12", "@rolldown/binding-linux-s390x-gnu": "1.2.12", "@rolldown/binding-linux-x64-gnu": "1.2.12", "@rolldown/binding-linux-x64-musl": "1.2.12", "@rolldown/binding-openharmony-arm64": "1.2.12", "@rolldown/binding-win32-arm64-msvc": "1.2.12", "@rolldown/binding-win32-x64-msvc": "1.2.12" }, "bin": { "rolldown": "./bin/cli.mjs" } }, "sha512-8wafseiaG80xmXSfqidUNqZcylTlhmPZZt+za2m+js2sFZ8dTNlhIOV2WcbIPx2hgwPBJpEUGFAMZ9bgBBLTSQ=="],
|
||||
|
||||
"runed": ["runed@0.35.1", "", { "dependencies": { "dequal": "^2.0.3", "esm-env": "^1.0.0", "lz-string": "^1.5.0" }, "peerDependencies": { "@sveltejs/kit": "^2.21.0", "svelte": "^5.7.0" }, "optionalPeers": ["@sveltejs/kit"] }, "sha512-2F4Q/FZzbeJTFdIS/PuOoPRSm92sA2LhzTnv6FXhCoENb3huf5+fDuNOg1LNvGOouy3u/225qxmuJvcV3IZK5Q=="],
|
||||
|
||||
"rw": ["rw@1.3.3", "", {}, "sha512-PdhdWy89SiZogBLaw42zdeqtRJ//zFd2PgQavcICDUgJT5oW10QCRKbJ6bg4r0/UY2M6BWd5tkxuGFRvCkgfHQ=="],
|
||||
|
||||
"sade": ["sade@1.8.1", "", { "dependencies": { "mri": "^1.1.0" } }, "sha512-xal3CZX1Xlo/k4ApwCFrHVACi9fBqJ7V+mwhBsuf/1IOKbBy098Fex+Wa/5QMubw09pSZ/u8EY8PWgevJsXp1A=="],
|
||||
|
||||
"safer-buffer": ["safer-buffer@2.1.2", "", {}, "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg=="],
|
||||
|
||||
"semver": ["semver@7.8.5", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-Y7/KDsb8LjooZpwaqGyulO6DQlksgCncchHGk+sZIY4SBvUocMBEFH5Ur1fI4dV+Jvl0w6cjvucaIi40puRioA=="],
|
||||
|
||||
"shadcn-svelte": ["shadcn-svelte@1.7.0", "", { "dependencies": { "cn": "^0.3.0", "commander": "^14.0.0", "node-fetch-native": "^1.6.4", "postcss": "^8.5.10" }, "peerDependencies": { "svelte": "^5.0.0" }, "bin": { "shadcn-svelte": "dist/index.mjs" } }, "sha512-AK3ZsYOL8PN/RxcEBCHGvQj2TrLfaaBsbw0QohyEb3nn/yPNDRC/+9iSjnTp/uKEe+tAi0tBxB/Yhzno0N7EEg=="],
|
||||
@@ -413,12 +574,22 @@
|
||||
|
||||
"source-map-js": ["source-map-js@1.2.2", "", {}, "sha512-KGj/8Y43x35aZVDtt+J4mK1hoLGHULMYfSkODJNQjNDC3oW1PqPoxMwo0pLUsWM/UEGzON/NxeHywEfNXNP3Vw=="],
|
||||
|
||||
"style-to-object": ["style-to-object@1.0.14", "", { "dependencies": { "inline-style-parser": "0.2.7" } }, "sha512-LIN7rULI0jBscWQYaSswptyderlarFkjQ+t79nzty8tcIAceVomEVlLzH5VP4Cmsv6MtKhs7qaAiwlcp+Mgaxw=="],
|
||||
|
||||
"svelte": ["svelte@5.57.1", "", { "dependencies": { "@jridgewell/remapping": "^2.3.5", "@jridgewell/sourcemap-codec": "^1.6.0", "@sveltejs/acorn-typescript": "^1.0.13", "@types/estree": "^1.0.9", "acorn": "^8.18.0", "aria-query": "5.3.1", "axobject-query": "^4.1.0", "clsx": "^2.1.1", "devalue": "^5.9.2", "esm-env": "^1.2.1", "esrap": "^2.3.6", "is-reference": "^3.0.3", "locate-character": "^3.0.0", "magic-string": "^0.30.11", "zimmerframe": "^1.1.2" } }, "sha512-Uqj49lWKB+iSSnneuwiYYJ7MZgkB+eXr0LXBhv4uDuAkXqnWmq65Sxflfvp0Lc6MdKjMUxGaeOKWJqz5SNiVIA=="],
|
||||
|
||||
"svelte-check": ["svelte-check@4.7.6", "", { "dependencies": { "@jridgewell/trace-mapping": "^0.3.25", "@sveltejs/load-config": "^0.2.3", "chokidar": "^4.0.1", "fdir": "^6.2.0", "picocolors": "^1.0.0", "sade": "^1.7.4" }, "peerDependencies": { "svelte": "^4.0.0 || ^5.0.0-next.0", "typescript": "^5.0.0 || ^6.0.0" }, "bin": { "svelte-check": "bin/svelte-check" } }, "sha512-t2scM//ZuVbSY/T2w6FSBw1v9s2NEmh/g+sy1lqtosW5ylBV5AF4wFb1Ts9Kf3MbfPDUDJDZ9L436YT0SPTdvw=="],
|
||||
|
||||
"svelte-eslint-parser": ["svelte-eslint-parser@1.8.1", "", { "dependencies": { "eslint-scope": "^8.2.0", "eslint-visitor-keys": "^4.0.0", "espree": "^10.0.0", "postcss": "^8.4.49", "postcss-scss": "^4.0.9", "postcss-selector-parser": "^7.0.0", "semver": "^7.7.2" }, "peerDependencies": { "svelte": "^3.37.0 || ^4.0.0 || ^5.0.0" }, "optionalPeers": ["svelte"] }, "sha512-5zgKBqAf6V8Jyrmr1jViksyG4NKT8NheYwkdxRaMRDAqpGWi9wR8ktcGrAZbfMn/PHUp1BWzAp0cYQECkWuAMA=="],
|
||||
|
||||
"svelte-sonner": ["svelte-sonner@1.2.1", "", { "dependencies": { "runed": "^0.28.0" }, "peerDependencies": { "svelte": "^5.0.0" } }, "sha512-N00LOWb60fjKXULbpngku+7E9Krb16KpNq4/Eax5Vu7o1Dpbca55QpaLPsB+yp82JIEdFhfJuv5cVXLoe0ZtVA=="],
|
||||
|
||||
"svelte-toolbelt": ["svelte-toolbelt@0.10.6", "", { "dependencies": { "clsx": "^2.1.1", "runed": "^0.35.1", "style-to-object": "^1.0.8" }, "peerDependencies": { "svelte": "^5.30.2" } }, "sha512-YWuX+RE+CnWYx09yseAe4ZVMM7e7GRFZM6OYWpBKOb++s+SQ8RBIMMe+Bs/CznBMc0QPLjr+vDBxTAkozXsFXQ=="],
|
||||
|
||||
"tabbable": ["tabbable@6.5.0", "", {}, "sha512-wieBHXygIm7OyQOu5hQlkk62/WyCFYGlWg7L6/ZCUZwx0o398Zkn4pVmMyfYhfMG8kGrj/Krt8eIk6UKC6VzwA=="],
|
||||
|
||||
"tailwind-merge": ["tailwind-merge@3.7.0", "", {}, "sha512-XPPUyAc+cvspz3lHTcR/QgPfW2A0lv/xQNIjX3HGhLR+Nq2lHaLq5MtTesHn8GUr3W3DguT2KT5x3NVgRtYwmA=="],
|
||||
|
||||
"tailwind-variants": ["tailwind-variants@3.3.1", "", { "peerDependencies": { "tailwind-merge": ">=3.0.0", "tailwindcss": "*" }, "optionalPeers": ["tailwind-merge", "tailwindcss"] }, "sha512-4pAvwUtM4HKBiRZftncAbpn6V9Hhwoa5Fl7O2u5zbp7Z5Cvu+/o/6+176WY3WCEES209543quG8zFIcXCsc5Jw=="],
|
||||
|
||||
"tailwindcss": ["tailwindcss@4.3.3", "", {}, "sha512-gOhV3P7ufE62QDGg1zVaTgCR+EtPv92k2nIhVcVKcLmxT1sUBsQGhnZj175j+MqRt4zLF7ic+sCYjfhxMxj7YQ=="],
|
||||
@@ -431,6 +602,8 @@
|
||||
|
||||
"ts-api-utils": ["ts-api-utils@2.5.0", "", { "peerDependencies": { "typescript": ">=4.8.4" } }, "sha512-OJ/ibxhPlqrMM0UiNHJ/0CKQkoKF243/AEmplt3qpRgkW8VG7IfOS41h7V8TjITqdByHzrjcS/2si+y4lIh8NA=="],
|
||||
|
||||
"tslib": ["tslib@2.8.1", "", {}, "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w=="],
|
||||
|
||||
"tw-animate-css": ["tw-animate-css@1.4.0", "", {}, "sha512-7bziOlRqH0hJx80h/3mbicLW7o8qLsH5+RaLR2t+OHM3D0JlWGODQKQ4cxbK7WlvmUxpcj6Kgu6EKqjrGFe3QQ=="],
|
||||
|
||||
"type-check": ["type-check@0.4.0", "", { "dependencies": { "prelude-ls": "^1.2.1" } }, "sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew=="],
|
||||
@@ -445,6 +618,8 @@
|
||||
|
||||
"util-deprecate": ["util-deprecate@1.0.2", "", {}, "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw=="],
|
||||
|
||||
"vaul-svelte": ["vaul-svelte@1.0.0-next.7", "", { "dependencies": { "runed": "^0.23.2", "svelte-toolbelt": "^0.7.1" }, "peerDependencies": { "svelte": "^5.0.0" } }, "sha512-7zN7Bi3dFQixvvbUJY9uGDe7Ws/dGZeBQR2pXdXmzQiakjrxBvWo0QrmsX3HK+VH+SZOltz378cmgmCS9f9rSg=="],
|
||||
|
||||
"vite": ["vite@8.3.2", "", { "dependencies": { "lightningcss": "^1.33.0", "picomatch": "^4.0.7", "postcss": "^8.5.28", "rolldown": "~1.2.11", "tinyglobby": "^0.2.17" }, "optionalDependencies": { "fsevents": "~2.3.3" }, "peerDependencies": { "@types/node": "^20.19.0 || >=22.12.0", "@vitejs/devtools": "^0.7.1", "esbuild": "^0.27.0 || ^0.28.0", "jiti": ">=1.21.0", "less": "^4.0.0", "sass": "^1.70.0", "sass-embedded": "^1.70.0", "stylus": ">=0.54.8", "sugarss": "^5.0.0", "terser": "^5.16.0", "tsx": "^4.8.1", "yaml": "^2.4.2" }, "optionalPeers": ["@types/node", "@vitejs/devtools", "esbuild", "jiti", "less", "sass", "sass-embedded", "stylus", "sugarss", "terser", "tsx", "yaml"], "bin": { "vite": "bin/vite.js" } }, "sha512-SQr1x6W5vVSbROg7vsyXIaxK9b0G7zsT68acdWWRmnBUsgDieLCRG+Rep9WdZgcposvv/GSnr4GUUBqB3vXq6w=="],
|
||||
|
||||
"vitefu": ["vitefu@1.1.3", "", { "peerDependencies": { "vite": "^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0" }, "optionalPeers": ["vite"] }, "sha512-ub4okH7Z5KLjb6hDyjqrGXqWtWvoYdU3IGm/NorpgHncKoLTCfRIbvlhBm7r0YstIaQRYlp4yEbFqDcKSzXSSg=="],
|
||||
@@ -459,6 +634,22 @@
|
||||
|
||||
"zimmerframe": ["zimmerframe@1.1.5", "", {}, "sha512-msJxIvYDYcoNL+PJsu+7qmpDWsYmAxTY+2TNYXXF0hzBzBk0BMecOqDOG/EckUoKCuKwObfbugIl8QpqHDXeFA=="],
|
||||
|
||||
"zod": ["zod@4.6.5", "", {}, "sha512-v5l/aFXZQeai4awLbOpSoHecE9UiMrnfx75tEXLjNonXVARxQ5mOeipTjROUchszUNCqnE+hqAMujRsRHsut2Q=="],
|
||||
|
||||
"@dnd-kit-svelte/svelte/@dnd-kit/abstract": ["@dnd-kit/abstract@0.2.4", "", { "dependencies": { "@dnd-kit/geometry": "^0.2.4", "@dnd-kit/state": "^0.2.4", "tslib": "^2.6.2" } }, "sha512-1F3JQaD8upvTNpfDrhvec57cldTEaUggmVrVJZIpRTIdZR7trKqS7vBYZR1mW3qNjWsxC91eEQAXYWv0vxXXuw=="],
|
||||
|
||||
"@dnd-kit/abstract/@dnd-kit/state": ["@dnd-kit/state@0.1.21", "", { "dependencies": { "@preact/signals-core": "^1.10.0", "tslib": "^2.6.2" } }, "sha512-pdhntEPvn/QttcF295bOJpWiLsRqA/Iczh1ODOJUxGiR+E4GkYVz9VapNNm9gDq6ST0tr/e1Q2xBztUHlJqQgA=="],
|
||||
|
||||
"@dnd-kit/collision/@dnd-kit/abstract": ["@dnd-kit/abstract@0.2.4", "", { "dependencies": { "@dnd-kit/geometry": "^0.2.4", "@dnd-kit/state": "^0.2.4", "tslib": "^2.6.2" } }, "sha512-1F3JQaD8upvTNpfDrhvec57cldTEaUggmVrVJZIpRTIdZR7trKqS7vBYZR1mW3qNjWsxC91eEQAXYWv0vxXXuw=="],
|
||||
|
||||
"@dnd-kit/collision/@dnd-kit/geometry": ["@dnd-kit/geometry@0.2.4", "", { "dependencies": { "@dnd-kit/state": "^0.2.4", "tslib": "^2.6.2" } }, "sha512-S8wVj4rQIErimDQoRT0n1+lgxPvlO/gvzT9/1vzyyw1U4bB/S8avtrI+REVw/jH84c/dii+GwrrzVkDDoIluNw=="],
|
||||
|
||||
"@dnd-kit/dom/@dnd-kit/abstract": ["@dnd-kit/abstract@0.2.4", "", { "dependencies": { "@dnd-kit/geometry": "^0.2.4", "@dnd-kit/state": "^0.2.4", "tslib": "^2.6.2" } }, "sha512-1F3JQaD8upvTNpfDrhvec57cldTEaUggmVrVJZIpRTIdZR7trKqS7vBYZR1mW3qNjWsxC91eEQAXYWv0vxXXuw=="],
|
||||
|
||||
"@dnd-kit/dom/@dnd-kit/geometry": ["@dnd-kit/geometry@0.2.4", "", { "dependencies": { "@dnd-kit/state": "^0.2.4", "tslib": "^2.6.2" } }, "sha512-S8wVj4rQIErimDQoRT0n1+lgxPvlO/gvzT9/1vzyyw1U4bB/S8avtrI+REVw/jH84c/dii+GwrrzVkDDoIluNw=="],
|
||||
|
||||
"@dnd-kit/geometry/@dnd-kit/state": ["@dnd-kit/state@0.1.21", "", { "dependencies": { "@preact/signals-core": "^1.10.0", "tslib": "^2.6.2" } }, "sha512-pdhntEPvn/QttcF295bOJpWiLsRqA/Iczh1ODOJUxGiR+E4GkYVz9VapNNm9gDq6ST0tr/e1Q2xBztUHlJqQgA=="],
|
||||
|
||||
"@eslint-community/eslint-utils/eslint-visitor-keys": ["eslint-visitor-keys@3.4.3", "", {}, "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag=="],
|
||||
|
||||
"@tailwindcss/node/lightningcss": ["lightningcss@1.32.0", "", { "dependencies": { "detect-libc": "^2.0.3" }, "optionalDependencies": { "lightningcss-android-arm64": "1.32.0", "lightningcss-darwin-arm64": "1.32.0", "lightningcss-darwin-x64": "1.32.0", "lightningcss-freebsd-x64": "1.32.0", "lightningcss-linux-arm-gnueabihf": "1.32.0", "lightningcss-linux-arm64-gnu": "1.32.0", "lightningcss-linux-arm64-musl": "1.32.0", "lightningcss-linux-x64-gnu": "1.32.0", "lightningcss-linux-x64-musl": "1.32.0", "lightningcss-win32-arm64-msvc": "1.32.0", "lightningcss-win32-x64-msvc": "1.32.0" } }, "sha512-NXYBzinNrblfraPGyrbPoD19C1h9lfI/1mzgWYvXUTe414Gz/X1FD2XBZSZM7rRTrMA8JL3OtAaGifrIKhQ5yQ=="],
|
||||
@@ -479,8 +670,16 @@
|
||||
|
||||
"@typescript-eslint/eslint-plugin/ignore": ["ignore@7.0.12", "", {}, "sha512-/8UvqAPU9DGTI9k4mxtf49U37Isfwr8Uts96+SBHIkFxPJnHS0Ew4f00sM4Scd8V8EjM0jUNtVDdv1kPdt35lg=="],
|
||||
|
||||
"d3-dsv/commander": ["commander@7.2.0", "", {}, "sha512-QrWXB+ZQSVPmIWIhtEO9H+gwHaMGYiF5ChvoJ+K9ZGHG/sVsa6yiesAD1GC/x46sET00Xlwo1u49RVVVzvcSkw=="],
|
||||
|
||||
"d3-sankey/d3-array": ["d3-array@2.12.1", "", { "dependencies": { "internmap": "^1.0.0" } }, "sha512-B0ErZK/66mHtEsR1TkPEEkwdy+WDesimkM5gpZr5Dsg54BiTA5RXtYW5qTLIAcekaS9xfZrzBLF/OAkB3Qn1YQ=="],
|
||||
|
||||
"d3-sankey/d3-shape": ["d3-shape@1.3.7", "", { "dependencies": { "d3-path": "1" } }, "sha512-EUkvKjqPFUAZyOlhY5gzCxCeI0Aep04LwIRpsZ/mLFelJiUfnK56jo5JMDSE7yyP2kLSb6LtF+S5chMk7uqPqw=="],
|
||||
|
||||
"eslint-plugin-svelte/globals": ["globals@16.5.0", "", {}, "sha512-c/c15i26VrJ4IRt5Z89DnIzCGDn9EcebibhAOjw5ibqEHsE1wLUgkPn9RDmNcUKyU87GeaL633nyJ+pplFR2ZQ=="],
|
||||
|
||||
"layerchart/runed": ["runed@0.37.1", "", { "dependencies": { "dequal": "^2.0.3", "esm-env": "^1.0.0", "lz-string": "^1.5.0" }, "peerDependencies": { "@sveltejs/kit": "^2.21.0", "svelte": "^5.7.0", "zod": "^4.1.0" }, "optionalPeers": ["@sveltejs/kit", "zod"] }, "sha512-MeFY73xBW8IueWBm012nNFIGy19WUGPLtknavyUPMpnyt350M47PhGSGrGoSLbidwn+Zlt/O0cp8/OZE3LASWA=="],
|
||||
|
||||
"qified/hookified": ["hookified@2.2.0", "", {}, "sha512-p/LgFzRN5FeoD3DLS6bkUapeye6E4SI6yJs6KetENd18S+FBthqYq2amJUWpt5z0EQwwHemidjY5OqJGEKm5uA=="],
|
||||
|
||||
"svelte/magic-string": ["magic-string@0.30.21", "", { "dependencies": { "@jridgewell/sourcemap-codec": "^1.5.5" } }, "sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ=="],
|
||||
@@ -493,6 +692,14 @@
|
||||
|
||||
"svelte-eslint-parser/postcss-selector-parser": ["postcss-selector-parser@7.1.6", "", { "dependencies": { "cssesc": "^3.0.0", "util-deprecate": "^1.0.2" } }, "sha512-7qASPzhKF2l2KLboRZux8CCTRMdGiV08vWmyKzPz22qZ7ZjQBOeY7rNzNoCLSUiftJ7HUq0GERHmxw/t0dCdMw=="],
|
||||
|
||||
"svelte-sonner/runed": ["runed@0.28.0", "", { "dependencies": { "esm-env": "^1.0.0" }, "peerDependencies": { "svelte": "^5.7.0" } }, "sha512-k2xx7RuO9hWcdd9f+8JoBeqWtYrm5CALfgpkg2YDB80ds/QE4w0qqu34A7fqiAwiBBSBQOid7TLxwxVC27ymWQ=="],
|
||||
|
||||
"vaul-svelte/runed": ["runed@0.23.4", "", { "dependencies": { "esm-env": "^1.0.0" }, "peerDependencies": { "svelte": "^5.7.0" } }, "sha512-9q8oUiBYeXIDLWNK5DfCWlkL0EW3oGbk845VdKlPeia28l751VpfesaB/+7pI6rnbx1I6rqoZ2fZxptOJLxILA=="],
|
||||
|
||||
"vaul-svelte/svelte-toolbelt": ["svelte-toolbelt@0.7.1", "", { "dependencies": { "clsx": "^2.1.1", "runed": "^0.23.2", "style-to-object": "^1.0.8" }, "peerDependencies": { "svelte": "^5.0.0" } }, "sha512-HcBOcR17Vx9bjaOceUvxkY3nGmbBmCBBbuWLLEWO6jtmWH8f/QoWmbyUfQZrpDINH39en1b8mptfPQT9VKQ1xQ=="],
|
||||
|
||||
"@dnd-kit-svelte/svelte/@dnd-kit/abstract/@dnd-kit/geometry": ["@dnd-kit/geometry@0.2.4", "", { "dependencies": { "@dnd-kit/state": "^0.2.4", "tslib": "^2.6.2" } }, "sha512-S8wVj4rQIErimDQoRT0n1+lgxPvlO/gvzT9/1vzyyw1U4bB/S8avtrI+REVw/jH84c/dii+GwrrzVkDDoIluNw=="],
|
||||
|
||||
"@tailwindcss/node/lightningcss/lightningcss-android-arm64": ["lightningcss-android-arm64@1.32.0", "", { "os": "android", "cpu": "arm64" }, "sha512-YK7/ClTt4kAK0vo6w3X+Pnm0D2cf2vPHbhOXdoNti1Ga0al1P4TBZhwjATvjNwLEBCnKvjJc2jQgHXH0NEwlAg=="],
|
||||
|
||||
"@tailwindcss/node/lightningcss/lightningcss-darwin-arm64": ["lightningcss-darwin-arm64@1.32.0", "", { "os": "darwin", "cpu": "arm64" }, "sha512-RzeG9Ju5bag2Bv1/lwlVJvBE3q6TtXskdZLLCyfg5pt+HLz9BqlICO7LZM7VHNTTn/5PRhHFBSjk5lc4cmscPQ=="],
|
||||
@@ -514,5 +721,9 @@
|
||||
"@tailwindcss/node/lightningcss/lightningcss-win32-arm64-msvc": ["lightningcss-win32-arm64-msvc@1.32.0", "", { "os": "win32", "cpu": "arm64" }, "sha512-8SbC8BR40pS6baCM8sbtYDSwEVQd4JlFTOlaD3gWGHfThTcABnNDBda6eTZeqbofalIJhFx0qKzgHJmcPTnGdw=="],
|
||||
|
||||
"@tailwindcss/node/lightningcss/lightningcss-win32-x64-msvc": ["lightningcss-win32-x64-msvc@1.32.0", "", { "os": "win32", "cpu": "x64" }, "sha512-Amq9B/SoZYdDi1kFrojnoqPLxYhQ4Wo5XiL8EVJrVsB8ARoC1PWW6VGtT0WKCemjy8aC+louJnjS7U18x3b06Q=="],
|
||||
|
||||
"d3-sankey/d3-array/internmap": ["internmap@1.0.1", "", {}, "sha512-lDB5YccMydFBtasVtxnZ3MRBHuaoE8GKsppq+EchKL2U4nK/DmEpPHNH8MZe5HkMtpSiTSOZwfN0tzYjO/lJEw=="],
|
||||
|
||||
"d3-sankey/d3-shape/d3-path": ["d3-path@1.0.9", "", {}, "sha512-VLaYcn81dtHVTjEHd8B+pbe9yHWpXKZUC87PzoFmsFrJqgFwDe/qxfp5MlfsfM1V5E/iVt0MmEbWQ7FVIXh/bg=="],
|
||||
}
|
||||
}
|
||||
|
||||
17
package.json
17
package.json
@@ -14,32 +14,47 @@
|
||||
"format": "prettier --write ."
|
||||
},
|
||||
"devDependencies": {
|
||||
"@dnd-kit-svelte/svelte": "^0.1.5",
|
||||
"@dnd-kit/abstract": "^0.1.21",
|
||||
"@dnd-kit/helpers": "^0.1.21",
|
||||
"@eslint/js": "^10.0.1",
|
||||
"@fontsource-variable/inter": "^5.3.0",
|
||||
"@internationalized/date": "^3.12.0",
|
||||
"@lucide/svelte": "^1.52.0",
|
||||
"@sveltejs/adapter-auto": "^8.0.0",
|
||||
"@sveltejs/kit": "^3.0.0",
|
||||
"@sveltejs/vite-plugin-svelte": "^7.2.0",
|
||||
"@tabler/icons-svelte": "^3.40.0",
|
||||
"@tailwindcss/typography": "^0.5.19",
|
||||
"@tailwindcss/vite": "^4.3.0",
|
||||
"@tanstack/svelte-table": "^9.0.0",
|
||||
"@types/d3-scale": "^4.0.9",
|
||||
"@types/d3-shape": "^3.1.8",
|
||||
"@types/node": "^26",
|
||||
"bits-ui": "^2.18.0",
|
||||
"cn": "^0.3.0",
|
||||
"d3-scale": "^4.0.2",
|
||||
"d3-shape": "^3.2.0",
|
||||
"eslint": "^10.4.1",
|
||||
"eslint-config-prettier": "^10.1.8",
|
||||
"eslint-plugin-svelte": "^3.19.0",
|
||||
"globals": "^17.6.0",
|
||||
"layerchart": "^2.0.0",
|
||||
"prettier": "^3.8.3",
|
||||
"prettier-plugin-svelte": "^4.1.0",
|
||||
"prettier-plugin-tailwindcss": "^0.8.0",
|
||||
"shadcn-svelte": "^1.7.0",
|
||||
"svelte": "^5.57.1",
|
||||
"svelte-check": "^4.6.0",
|
||||
"svelte-sonner": "^1.2.0",
|
||||
"tailwind-variants": "^3.3.1",
|
||||
"tailwindcss": "^4.3.0",
|
||||
"tw-animate-css": "^1.4.0",
|
||||
"typescript": "^6.0.3",
|
||||
"typescript-eslint": "^8.60.1",
|
||||
"vite": "^8.3.0"
|
||||
"vaul-svelte": "^1.0.0-next.7",
|
||||
"vite": "^8.3.0",
|
||||
"zod": "^4.4.3"
|
||||
},
|
||||
"imports": {
|
||||
"#lib": "./src/lib/index.js",
|
||||
|
||||
167
src/lib/components/app-sidebar.svelte
Normal file
167
src/lib/components/app-sidebar.svelte
Normal file
@@ -0,0 +1,167 @@
|
||||
<script lang="ts">
|
||||
import CameraIcon from "@tabler/icons-svelte/icons/camera";
|
||||
import ChartBarIcon from "@tabler/icons-svelte/icons/chart-bar";
|
||||
import DashboardIcon from "@tabler/icons-svelte/icons/dashboard";
|
||||
import DatabaseIcon from "@tabler/icons-svelte/icons/database";
|
||||
import FileAiIcon from "@tabler/icons-svelte/icons/file-ai";
|
||||
import FileDescriptionIcon from "@tabler/icons-svelte/icons/file-description";
|
||||
import FileWordIcon from "@tabler/icons-svelte/icons/file-word";
|
||||
import FolderIcon from "@tabler/icons-svelte/icons/folder";
|
||||
import HelpIcon from "@tabler/icons-svelte/icons/help";
|
||||
import InnerShadowTopIcon from "@tabler/icons-svelte/icons/inner-shadow-top";
|
||||
import ListDetailsIcon from "@tabler/icons-svelte/icons/list-details";
|
||||
import ReportIcon from "@tabler/icons-svelte/icons/report";
|
||||
import SearchIcon from "@tabler/icons-svelte/icons/search";
|
||||
import SettingsIcon from "@tabler/icons-svelte/icons/settings";
|
||||
import UsersIcon from "@tabler/icons-svelte/icons/users";
|
||||
import * as Sidebar from "#lib/components/ui/sidebar/index.js";
|
||||
import NavDocuments from "./nav-documents.svelte";
|
||||
import NavMain from "./nav-main.svelte";
|
||||
import NavSecondary from "./nav-secondary.svelte";
|
||||
import NavUser from "./nav-user.svelte";
|
||||
import type { ComponentProps } from "svelte";
|
||||
|
||||
const data = {
|
||||
user: {
|
||||
name: "shadcn",
|
||||
email: "m@example.com",
|
||||
avatar: "/avatars/shadcn.jpg",
|
||||
},
|
||||
navMain: [
|
||||
{
|
||||
title: "Dashboard",
|
||||
url: "#",
|
||||
icon: DashboardIcon,
|
||||
},
|
||||
{
|
||||
title: "Lifecycle",
|
||||
url: "#",
|
||||
icon: ListDetailsIcon,
|
||||
},
|
||||
{
|
||||
title: "Analytics",
|
||||
url: "#",
|
||||
icon: ChartBarIcon,
|
||||
},
|
||||
{
|
||||
title: "Projects",
|
||||
url: "#",
|
||||
icon: FolderIcon,
|
||||
},
|
||||
{
|
||||
title: "Team",
|
||||
url: "#",
|
||||
icon: UsersIcon,
|
||||
},
|
||||
],
|
||||
navClouds: [
|
||||
{
|
||||
title: "Capture",
|
||||
icon: CameraIcon,
|
||||
isActive: true,
|
||||
url: "#",
|
||||
items: [
|
||||
{
|
||||
title: "Active Proposals",
|
||||
url: "#",
|
||||
},
|
||||
{
|
||||
title: "Archived",
|
||||
url: "#",
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
title: "Proposal",
|
||||
icon: FileDescriptionIcon,
|
||||
url: "#",
|
||||
items: [
|
||||
{
|
||||
title: "Active Proposals",
|
||||
url: "#",
|
||||
},
|
||||
{
|
||||
title: "Archived",
|
||||
url: "#",
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
title: "Prompts",
|
||||
icon: FileAiIcon,
|
||||
url: "#",
|
||||
items: [
|
||||
{
|
||||
title: "Active Proposals",
|
||||
url: "#",
|
||||
},
|
||||
{
|
||||
title: "Archived",
|
||||
url: "#",
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
navSecondary: [
|
||||
{
|
||||
title: "Settings",
|
||||
url: "#",
|
||||
icon: SettingsIcon,
|
||||
},
|
||||
{
|
||||
title: "Get Help",
|
||||
url: "#",
|
||||
icon: HelpIcon,
|
||||
},
|
||||
{
|
||||
title: "Search",
|
||||
url: "#",
|
||||
icon: SearchIcon,
|
||||
},
|
||||
],
|
||||
documents: [
|
||||
{
|
||||
name: "Data Library",
|
||||
url: "#",
|
||||
icon: DatabaseIcon,
|
||||
},
|
||||
{
|
||||
name: "Reports",
|
||||
url: "#",
|
||||
icon: ReportIcon,
|
||||
},
|
||||
{
|
||||
name: "Word Assistant",
|
||||
url: "#",
|
||||
icon: FileWordIcon,
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
let { ...restProps }: ComponentProps<typeof Sidebar.Root> = $props();
|
||||
</script>
|
||||
|
||||
<Sidebar.Root collapsible="offcanvas" {...restProps}>
|
||||
<Sidebar.Header>
|
||||
<Sidebar.Menu>
|
||||
<Sidebar.MenuItem>
|
||||
<Sidebar.MenuButton class="data-[slot=sidebar-menu-button]:!p-1.5">
|
||||
{#snippet child({ props })}
|
||||
<a href="##" {...props}>
|
||||
<InnerShadowTopIcon class="!size-5" />
|
||||
<span class="text-base font-semibold">Acme Inc.</span>
|
||||
</a>
|
||||
{/snippet}
|
||||
</Sidebar.MenuButton>
|
||||
</Sidebar.MenuItem>
|
||||
</Sidebar.Menu>
|
||||
</Sidebar.Header>
|
||||
<Sidebar.Content>
|
||||
<NavMain items={data.navMain} />
|
||||
<NavDocuments items={data.documents} />
|
||||
<NavSecondary items={data.navSecondary} class="mt-auto" />
|
||||
</Sidebar.Content>
|
||||
<Sidebar.Footer>
|
||||
<NavUser user={data.user} />
|
||||
</Sidebar.Footer>
|
||||
</Sidebar.Root>
|
||||
247
src/lib/components/chart-area-interactive.svelte
Normal file
247
src/lib/components/chart-area-interactive.svelte
Normal file
@@ -0,0 +1,247 @@
|
||||
<script lang="ts">
|
||||
import { scaleUtc } from "d3-scale";
|
||||
import { curveNatural } from "d3-shape";
|
||||
import { Area, AreaChart } from "layerchart";
|
||||
import * as Card from "#lib/components/ui/card/index.js";
|
||||
import * as Chart from "#lib/components/ui/chart/index.js";
|
||||
import * as Select from "#lib/components/ui/select/index.js";
|
||||
import * as ToggleGroup from "#lib/components/ui/toggle-group/index.js";
|
||||
|
||||
const chartData = [
|
||||
{ date: new Date("2024-04-01"), desktop: 222, mobile: 150 },
|
||||
{ date: new Date("2024-04-02"), desktop: 97, mobile: 180 },
|
||||
{ date: new Date("2024-04-03"), desktop: 167, mobile: 120 },
|
||||
{ date: new Date("2024-04-04"), desktop: 242, mobile: 260 },
|
||||
{ date: new Date("2024-04-05"), desktop: 373, mobile: 290 },
|
||||
{ date: new Date("2024-04-06"), desktop: 301, mobile: 340 },
|
||||
{ date: new Date("2024-04-07"), desktop: 245, mobile: 180 },
|
||||
{ date: new Date("2024-04-08"), desktop: 409, mobile: 320 },
|
||||
{ date: new Date("2024-04-09"), desktop: 59, mobile: 110 },
|
||||
{ date: new Date("2024-04-10"), desktop: 261, mobile: 190 },
|
||||
{ date: new Date("2024-04-11"), desktop: 327, mobile: 350 },
|
||||
{ date: new Date("2024-04-12"), desktop: 292, mobile: 210 },
|
||||
{ date: new Date("2024-04-13"), desktop: 342, mobile: 380 },
|
||||
{ date: new Date("2024-04-14"), desktop: 137, mobile: 220 },
|
||||
{ date: new Date("2024-04-15"), desktop: 120, mobile: 170 },
|
||||
{ date: new Date("2024-04-16"), desktop: 138, mobile: 190 },
|
||||
{ date: new Date("2024-04-17"), desktop: 446, mobile: 360 },
|
||||
{ date: new Date("2024-04-18"), desktop: 364, mobile: 410 },
|
||||
{ date: new Date("2024-04-19"), desktop: 243, mobile: 180 },
|
||||
{ date: new Date("2024-04-20"), desktop: 89, mobile: 150 },
|
||||
{ date: new Date("2024-04-21"), desktop: 137, mobile: 200 },
|
||||
{ date: new Date("2024-04-22"), desktop: 224, mobile: 170 },
|
||||
{ date: new Date("2024-04-23"), desktop: 138, mobile: 230 },
|
||||
{ date: new Date("2024-04-24"), desktop: 387, mobile: 290 },
|
||||
{ date: new Date("2024-04-25"), desktop: 215, mobile: 250 },
|
||||
{ date: new Date("2024-04-26"), desktop: 75, mobile: 130 },
|
||||
{ date: new Date("2024-04-27"), desktop: 383, mobile: 420 },
|
||||
{ date: new Date("2024-04-28"), desktop: 122, mobile: 180 },
|
||||
{ date: new Date("2024-04-29"), desktop: 315, mobile: 240 },
|
||||
{ date: new Date("2024-04-30"), desktop: 454, mobile: 380 },
|
||||
{ date: new Date("2024-05-01"), desktop: 165, mobile: 220 },
|
||||
{ date: new Date("2024-05-02"), desktop: 293, mobile: 310 },
|
||||
{ date: new Date("2024-05-03"), desktop: 247, mobile: 190 },
|
||||
{ date: new Date("2024-05-04"), desktop: 385, mobile: 420 },
|
||||
{ date: new Date("2024-05-05"), desktop: 481, mobile: 390 },
|
||||
{ date: new Date("2024-05-06"), desktop: 498, mobile: 520 },
|
||||
{ date: new Date("2024-05-07"), desktop: 388, mobile: 300 },
|
||||
{ date: new Date("2024-05-08"), desktop: 149, mobile: 210 },
|
||||
{ date: new Date("2024-05-09"), desktop: 227, mobile: 180 },
|
||||
{ date: new Date("2024-05-10"), desktop: 293, mobile: 330 },
|
||||
{ date: new Date("2024-05-11"), desktop: 335, mobile: 270 },
|
||||
{ date: new Date("2024-05-12"), desktop: 197, mobile: 240 },
|
||||
{ date: new Date("2024-05-13"), desktop: 197, mobile: 160 },
|
||||
{ date: new Date("2024-05-14"), desktop: 448, mobile: 490 },
|
||||
{ date: new Date("2024-05-15"), desktop: 473, mobile: 380 },
|
||||
{ date: new Date("2024-05-16"), desktop: 338, mobile: 400 },
|
||||
{ date: new Date("2024-05-17"), desktop: 499, mobile: 420 },
|
||||
{ date: new Date("2024-05-18"), desktop: 315, mobile: 350 },
|
||||
{ date: new Date("2024-05-19"), desktop: 235, mobile: 180 },
|
||||
{ date: new Date("2024-05-20"), desktop: 177, mobile: 230 },
|
||||
{ date: new Date("2024-05-21"), desktop: 82, mobile: 140 },
|
||||
{ date: new Date("2024-05-22"), desktop: 81, mobile: 120 },
|
||||
{ date: new Date("2024-05-23"), desktop: 252, mobile: 290 },
|
||||
{ date: new Date("2024-05-24"), desktop: 294, mobile: 220 },
|
||||
{ date: new Date("2024-05-25"), desktop: 201, mobile: 250 },
|
||||
{ date: new Date("2024-05-26"), desktop: 213, mobile: 170 },
|
||||
{ date: new Date("2024-05-27"), desktop: 420, mobile: 460 },
|
||||
{ date: new Date("2024-05-28"), desktop: 233, mobile: 190 },
|
||||
{ date: new Date("2024-05-29"), desktop: 78, mobile: 130 },
|
||||
{ date: new Date("2024-05-30"), desktop: 340, mobile: 280 },
|
||||
{ date: new Date("2024-05-31"), desktop: 178, mobile: 230 },
|
||||
{ date: new Date("2024-06-01"), desktop: 178, mobile: 200 },
|
||||
{ date: new Date("2024-06-02"), desktop: 470, mobile: 410 },
|
||||
{ date: new Date("2024-06-03"), desktop: 103, mobile: 160 },
|
||||
{ date: new Date("2024-06-04"), desktop: 439, mobile: 380 },
|
||||
{ date: new Date("2024-06-05"), desktop: 88, mobile: 140 },
|
||||
{ date: new Date("2024-06-06"), desktop: 294, mobile: 250 },
|
||||
{ date: new Date("2024-06-07"), desktop: 323, mobile: 370 },
|
||||
{ date: new Date("2024-06-08"), desktop: 385, mobile: 320 },
|
||||
{ date: new Date("2024-06-09"), desktop: 438, mobile: 480 },
|
||||
{ date: new Date("2024-06-10"), desktop: 155, mobile: 200 },
|
||||
{ date: new Date("2024-06-11"), desktop: 92, mobile: 150 },
|
||||
{ date: new Date("2024-06-12"), desktop: 492, mobile: 420 },
|
||||
{ date: new Date("2024-06-13"), desktop: 81, mobile: 130 },
|
||||
{ date: new Date("2024-06-14"), desktop: 426, mobile: 380 },
|
||||
{ date: new Date("2024-06-15"), desktop: 307, mobile: 350 },
|
||||
{ date: new Date("2024-06-16"), desktop: 371, mobile: 310 },
|
||||
{ date: new Date("2024-06-17"), desktop: 475, mobile: 520 },
|
||||
{ date: new Date("2024-06-18"), desktop: 107, mobile: 170 },
|
||||
{ date: new Date("2024-06-19"), desktop: 341, mobile: 290 },
|
||||
{ date: new Date("2024-06-20"), desktop: 408, mobile: 450 },
|
||||
{ date: new Date("2024-06-21"), desktop: 169, mobile: 210 },
|
||||
{ date: new Date("2024-06-22"), desktop: 317, mobile: 270 },
|
||||
{ date: new Date("2024-06-23"), desktop: 480, mobile: 530 },
|
||||
{ date: new Date("2024-06-24"), desktop: 132, mobile: 180 },
|
||||
{ date: new Date("2024-06-25"), desktop: 141, mobile: 190 },
|
||||
{ date: new Date("2024-06-26"), desktop: 434, mobile: 380 },
|
||||
{ date: new Date("2024-06-27"), desktop: 448, mobile: 490 },
|
||||
{ date: new Date("2024-06-28"), desktop: 149, mobile: 200 },
|
||||
{ date: new Date("2024-06-29"), desktop: 103, mobile: 160 },
|
||||
{ date: new Date("2024-06-30"), desktop: 446, mobile: 400 },
|
||||
];
|
||||
|
||||
let timeRange = $state("90d");
|
||||
|
||||
const selectedLabel = $derived.by(() => {
|
||||
switch (timeRange) {
|
||||
case "90d":
|
||||
return "Last 3 months";
|
||||
case "30d":
|
||||
return "Last 30 days";
|
||||
case "7d":
|
||||
return "Last 7 days";
|
||||
default:
|
||||
return "Last 3 months";
|
||||
}
|
||||
});
|
||||
|
||||
const filteredData = $derived(
|
||||
chartData.filter((item) => {
|
||||
// eslint-disable-next-line svelte/prefer-svelte-reactivity
|
||||
const referenceDate = new Date("2024-06-30");
|
||||
let daysToSubtract = 90;
|
||||
if (timeRange === "30d") {
|
||||
daysToSubtract = 30;
|
||||
} else if (timeRange === "7d") {
|
||||
daysToSubtract = 7;
|
||||
}
|
||||
|
||||
referenceDate.setDate(referenceDate.getDate() - daysToSubtract);
|
||||
return item.date >= referenceDate;
|
||||
})
|
||||
);
|
||||
|
||||
const chartConfig = {
|
||||
desktop: { label: "Desktop", color: "var(--primary)" },
|
||||
mobile: { label: "Mobile", color: "var(--primary)" },
|
||||
} satisfies Chart.ChartConfig;
|
||||
</script>
|
||||
|
||||
<Card.Root class="@container/card">
|
||||
<Card.Header>
|
||||
<Card.Title>Total Visitors</Card.Title>
|
||||
<Card.Description>
|
||||
<span class="hidden @[540px]/card:block"> Total for the last 3 months </span>
|
||||
<span class="@[540px]/card:hidden">Last 3 months</span>
|
||||
</Card.Description>
|
||||
<Card.Action>
|
||||
<ToggleGroup.Root
|
||||
type="single"
|
||||
bind:value={timeRange}
|
||||
variant="outline"
|
||||
class="hidden *:data-[slot=toggle-group-item]:!px-4 @[767px]/card:flex"
|
||||
>
|
||||
<ToggleGroup.Item value="90d">Last 3 months</ToggleGroup.Item>
|
||||
<ToggleGroup.Item value="30d">Last 30 days</ToggleGroup.Item>
|
||||
<ToggleGroup.Item value="7d">Last 7 days</ToggleGroup.Item>
|
||||
</ToggleGroup.Root>
|
||||
<Select.Root type="single" bind:value={timeRange}>
|
||||
<Select.Trigger
|
||||
size="sm"
|
||||
class="flex w-40 **:data-[slot=select-value]:block **:data-[slot=select-value]:truncate @[767px]/card:hidden"
|
||||
aria-label="Select a value"
|
||||
>
|
||||
<span data-slot="select-value">
|
||||
{selectedLabel}
|
||||
</span>
|
||||
</Select.Trigger>
|
||||
<Select.Content class="rounded-xl">
|
||||
<Select.Item value="90d" class="rounded-lg">Last 3 months</Select.Item>
|
||||
<Select.Item value="30d" class="rounded-lg">Last 30 days</Select.Item>
|
||||
<Select.Item value="7d" class="rounded-lg">Last 7 days</Select.Item>
|
||||
</Select.Content>
|
||||
</Select.Root>
|
||||
</Card.Action>
|
||||
</Card.Header>
|
||||
<Card.Content class="px-2 pt-4 sm:px-6 sm:pt-6">
|
||||
<Chart.Container config={chartConfig} class="aspect-auto h-[250px] w-full">
|
||||
<AreaChart
|
||||
legend
|
||||
data={filteredData}
|
||||
x="date"
|
||||
xScale={scaleUtc()}
|
||||
series={[
|
||||
{
|
||||
key: "mobile",
|
||||
label: "Mobile",
|
||||
color: chartConfig.mobile.color,
|
||||
},
|
||||
{
|
||||
key: "desktop",
|
||||
label: "Desktop",
|
||||
color: chartConfig.desktop.color,
|
||||
},
|
||||
]}
|
||||
seriesLayout="stack"
|
||||
props={{
|
||||
xAxis: {
|
||||
ticks: timeRange === "7d" ? 7 : undefined,
|
||||
format: (v) => {
|
||||
return v.toLocaleDateString("en-US", {
|
||||
month: "short",
|
||||
day: "numeric",
|
||||
});
|
||||
},
|
||||
},
|
||||
yAxis: { format: () => "" },
|
||||
}}
|
||||
>
|
||||
{#snippet marks({ context })}
|
||||
<defs>
|
||||
<linearGradient id="fillDesktop" x1="0" y1="0" x2="0" y2="1">
|
||||
<stop offset="5%" stop-color="var(--color-desktop)" stop-opacity={1.0} />
|
||||
<stop offset="95%" stop-color="var(--color-desktop)" stop-opacity={0.1} />
|
||||
</linearGradient>
|
||||
<linearGradient id="fillMobile" x1="0" y1="0" x2="0" y2="1">
|
||||
<stop offset="5%" stop-color="var(--color-mobile)" stop-opacity={0.8} />
|
||||
<stop offset="95%" stop-color="var(--color-mobile)" stop-opacity={0.1} />
|
||||
</linearGradient>
|
||||
</defs>
|
||||
{#each context.series.visibleSeries as s (s.key)}
|
||||
<Area
|
||||
seriesKey={s.key}
|
||||
curve={curveNatural}
|
||||
fillOpacity={0.4}
|
||||
line={{ class: "stroke-1" }}
|
||||
motion="tween"
|
||||
{...s.props}
|
||||
fill={s.key === "desktop" ? "url(#fillDesktop)" : "url(#fillMobile)"}
|
||||
/>
|
||||
{/each}
|
||||
{/snippet}
|
||||
{#snippet tooltip()}
|
||||
<Chart.Tooltip
|
||||
labelFormatter={(v: Date) => {
|
||||
return v.toLocaleDateString("en-US", {
|
||||
month: "short",
|
||||
day: "numeric",
|
||||
});
|
||||
}}
|
||||
indicator="line"
|
||||
/>
|
||||
{/snippet}
|
||||
</AreaChart>
|
||||
</Chart.Container>
|
||||
</Card.Content>
|
||||
</Card.Root>
|
||||
23
src/lib/components/data-table-actions.svelte
Normal file
23
src/lib/components/data-table-actions.svelte
Normal file
@@ -0,0 +1,23 @@
|
||||
<script lang="ts">
|
||||
import DotsVerticalIcon from "@tabler/icons-svelte/icons/dots-vertical";
|
||||
import * as DropdownMenu from "#lib/components/ui/dropdown-menu/index.js";
|
||||
import { Button } from "#lib/components/ui/button/index.js";
|
||||
</script>
|
||||
|
||||
<DropdownMenu.Root>
|
||||
<DropdownMenu.Trigger class="flex size-8 text-muted-foreground data-[state=open]:bg-muted">
|
||||
{#snippet child({ props })}
|
||||
<Button variant="ghost" size="icon" {...props}>
|
||||
<DotsVerticalIcon />
|
||||
<span class="sr-only">Open menu</span>
|
||||
</Button>
|
||||
{/snippet}
|
||||
</DropdownMenu.Trigger>
|
||||
<DropdownMenu.Content align="end" class="w-32">
|
||||
<DropdownMenu.Item>Edit</DropdownMenu.Item>
|
||||
<DropdownMenu.Item>Make a copy</DropdownMenu.Item>
|
||||
<DropdownMenu.Item>Favorite</DropdownMenu.Item>
|
||||
<DropdownMenu.Separator />
|
||||
<DropdownMenu.Item variant="destructive">Delete</DropdownMenu.Item>
|
||||
</DropdownMenu.Content>
|
||||
</DropdownMenu.Root>
|
||||
189
src/lib/components/data-table-cell-viewer.svelte
Normal file
189
src/lib/components/data-table-cell-viewer.svelte
Normal file
@@ -0,0 +1,189 @@
|
||||
<script lang="ts">
|
||||
import TrendingUpIcon from "@tabler/icons-svelte/icons/trending-up";
|
||||
import { scaleUtc } from "d3-scale";
|
||||
import { curveNatural } from "d3-shape";
|
||||
import { AreaChart } from "layerchart";
|
||||
import * as Chart from "#lib/components/ui/chart/index.js";
|
||||
import * as Drawer from "#lib/components/ui/drawer/index.js";
|
||||
import * as Select from "#lib/components/ui/select/index.js";
|
||||
import { IsMobile } from "#lib/hooks/is-mobile.svelte.js";
|
||||
import { Button } from "#lib/components/ui/button/index.js";
|
||||
import { Input } from "#lib/components/ui/input/index.js";
|
||||
import { Label } from "#lib/components/ui/label/index.js";
|
||||
import { Separator } from "#lib/components/ui/separator/index.js";
|
||||
import type { Schema } from "./schemas.js";
|
||||
|
||||
const chartData = [
|
||||
{ date: new Date("2024-01-01"), desktop: 186, mobile: 80 },
|
||||
{ date: new Date("2024-02-01"), desktop: 305, mobile: 200 },
|
||||
{ date: new Date("2024-03-01"), desktop: 237, mobile: 120 },
|
||||
{ date: new Date("2024-04-01"), desktop: 73, mobile: 190 },
|
||||
{ date: new Date("2024-05-01"), desktop: 209, mobile: 130 },
|
||||
{ date: new Date("2024-06-01"), desktop: 214, mobile: 140 },
|
||||
];
|
||||
|
||||
const chartConfig = {
|
||||
desktop: {
|
||||
label: "Desktop",
|
||||
color: "var(--primary)",
|
||||
},
|
||||
mobile: {
|
||||
label: "Mobile",
|
||||
color: "var(--primary)",
|
||||
},
|
||||
} satisfies Chart.ChartConfig;
|
||||
|
||||
const isMobile = new IsMobile();
|
||||
|
||||
let { item }: { item: Schema } = $props();
|
||||
|
||||
let type = $derived(item.type);
|
||||
let status = $derived(item.status);
|
||||
let reviewer = $derived(item.reviewer);
|
||||
</script>
|
||||
|
||||
<Drawer.Root direction={isMobile.current ? "bottom" : "right"}>
|
||||
<Drawer.Trigger>
|
||||
{#snippet child({ props })}
|
||||
<Button variant="link" class="w-fit px-0 text-start text-foreground" {...props}>
|
||||
{item.header}
|
||||
</Button>
|
||||
{/snippet}
|
||||
</Drawer.Trigger>
|
||||
<Drawer.Content>
|
||||
<Drawer.Header class="gap-1">
|
||||
<Drawer.Title>{item.header}</Drawer.Title>
|
||||
<Drawer.Description>Showing total visitors for the last 6 months</Drawer.Description>
|
||||
</Drawer.Header>
|
||||
<div class="flex flex-col gap-4 overflow-y-auto px-4 text-sm">
|
||||
{#if !isMobile.current}
|
||||
<Chart.Container config={chartConfig}>
|
||||
<AreaChart
|
||||
data={chartData}
|
||||
x="date"
|
||||
xScale={scaleUtc()}
|
||||
yDomain={[0, 600]}
|
||||
series={[
|
||||
{
|
||||
key: "mobile",
|
||||
label: "Mobile",
|
||||
color: chartConfig.mobile.color,
|
||||
},
|
||||
{
|
||||
key: "desktop",
|
||||
label: "Desktop",
|
||||
color: chartConfig.desktop.color,
|
||||
},
|
||||
]}
|
||||
seriesLayout="stack"
|
||||
props={{
|
||||
area: {
|
||||
curve: curveNatural,
|
||||
fillOpacity: 0.4,
|
||||
line: { class: "stroke-1" },
|
||||
motion: "tween",
|
||||
},
|
||||
xAxis: {
|
||||
format: (v) => v.toLocaleDateString("en-US", { month: "short" }),
|
||||
},
|
||||
yAxis: { ticks: [0, 300, 600] },
|
||||
}}
|
||||
>
|
||||
{#snippet tooltip()}
|
||||
<Chart.Tooltip
|
||||
labelFormatter={(v: Date) => {
|
||||
return v.toLocaleDateString("en-US", {
|
||||
month: "long",
|
||||
});
|
||||
}}
|
||||
indicator="dot"
|
||||
/>
|
||||
{/snippet}
|
||||
</AreaChart>
|
||||
</Chart.Container>
|
||||
<Separator />
|
||||
<div class="grid gap-2">
|
||||
<div class="flex gap-2 leading-none font-medium">
|
||||
Trending up by 5.2% this month
|
||||
<TrendingUpIcon class="size-4" />
|
||||
</div>
|
||||
<div class="text-muted-foreground">
|
||||
Showing total visitors for the last 6 months. This is just some random text to test the
|
||||
layout. It spans multiple lines and should wrap around.
|
||||
</div>
|
||||
</div>
|
||||
<Separator />
|
||||
{/if}
|
||||
<form class="flex flex-col gap-4">
|
||||
<div class="flex flex-col gap-3">
|
||||
<Label for="header">Header</Label>
|
||||
<Input id="header" value={item.header} />
|
||||
</div>
|
||||
<div class="grid grid-cols-2 gap-4">
|
||||
<div class="flex flex-col gap-3">
|
||||
<Label for="type">Type</Label>
|
||||
<Select.Root type="single" bind:value={type}>
|
||||
<Select.Trigger id="type" class="w-full">
|
||||
{type ?? "Select a type"}
|
||||
</Select.Trigger>
|
||||
<Select.Content>
|
||||
<Select.Item value="Table of Contents">Table of Contents</Select.Item>
|
||||
<Select.Item value="Executive Summary">Executive Summary</Select.Item>
|
||||
<Select.Item value="Technical Approach">Technical Approach</Select.Item>
|
||||
<Select.Item value="Design">Design</Select.Item>
|
||||
<Select.Item value="Capabilities">Capabilities</Select.Item>
|
||||
<Select.Item value="Focus Documents">Focus Documents</Select.Item>
|
||||
<Select.Item value="Narrative">Narrative</Select.Item>
|
||||
<Select.Item value="Cover Page">Cover Page</Select.Item>
|
||||
</Select.Content>
|
||||
</Select.Root>
|
||||
</div>
|
||||
<div class="flex flex-col gap-3">
|
||||
<Label for="status">Status</Label>
|
||||
<Select.Root type="single" bind:value={status}>
|
||||
<Select.Trigger id="status" class="w-full">
|
||||
{status ?? "Select a status"}
|
||||
</Select.Trigger>
|
||||
<Select.Content>
|
||||
<Select.Item value="Done">Done</Select.Item>
|
||||
<Select.Item value="In Progress">In Progress</Select.Item>
|
||||
<Select.Item value="Not Started">Not Started</Select.Item>
|
||||
</Select.Content>
|
||||
</Select.Root>
|
||||
</div>
|
||||
</div>
|
||||
<div class="grid grid-cols-2 gap-4">
|
||||
<div class="flex flex-col gap-3">
|
||||
<Label for="target">Target</Label>
|
||||
<Input id="target" value={item.target} />
|
||||
</div>
|
||||
<div class="flex flex-col gap-3">
|
||||
<Label for="limit">Limit</Label>
|
||||
<Input id="limit" value={item.limit} />
|
||||
</div>
|
||||
</div>
|
||||
<div class="flex flex-col gap-3">
|
||||
<Label for="reviewer">Reviewer</Label>
|
||||
<Select.Root type="single" bind:value={reviewer}>
|
||||
<Select.Trigger id="reviewer" class="w-full">
|
||||
{reviewer ?? "Select a reviewer"}
|
||||
</Select.Trigger>
|
||||
<Select.Content>
|
||||
<Select.Item value="Eddie Lake">Eddie Lake</Select.Item>
|
||||
<Select.Item value="Jamik Tashpulatov">Jamik Tashpulatov</Select.Item>
|
||||
<Select.Item value="Emily Whalen">Emily Whalen</Select.Item>
|
||||
</Select.Content>
|
||||
</Select.Root>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
<Drawer.Footer>
|
||||
<Button>Submit</Button>
|
||||
<Drawer.Close>
|
||||
{#snippet child({ props })}
|
||||
<Button variant="outline" {...props}>Done</Button>
|
||||
{/snippet}
|
||||
</Drawer.Close>
|
||||
</Drawer.Footer>
|
||||
</Drawer.Content>
|
||||
</Drawer.Root>
|
||||
14
src/lib/components/data-table-checkbox.svelte
Normal file
14
src/lib/components/data-table-checkbox.svelte
Normal file
@@ -0,0 +1,14 @@
|
||||
<script lang="ts">
|
||||
import { Checkbox } from "#lib/components/ui/checkbox/index.js";
|
||||
import type { ComponentProps } from "svelte";
|
||||
|
||||
let {
|
||||
checked = false,
|
||||
onCheckedChange = (v) => (checked = v),
|
||||
...restProps
|
||||
}: ComponentProps<typeof Checkbox> = $props();
|
||||
</script>
|
||||
|
||||
<div class="flex items-center justify-center">
|
||||
<Checkbox bind:checked={() => checked, onCheckedChange} {...restProps} />
|
||||
</div>
|
||||
17
src/lib/components/data-table-drag-handle.svelte
Normal file
17
src/lib/components/data-table-drag-handle.svelte
Normal file
@@ -0,0 +1,17 @@
|
||||
<script lang="ts">
|
||||
import GripVerticalIcon from "@tabler/icons-svelte/icons/grip-vertical";
|
||||
import { Button } from "#lib/components/ui/button/index.js";
|
||||
import type { Attachment } from "svelte/attachments";
|
||||
|
||||
let { attach }: { attach?: Attachment } = $props();
|
||||
</script>
|
||||
|
||||
<Button
|
||||
{@attach attach}
|
||||
variant="ghost"
|
||||
size="icon"
|
||||
class="size-7 text-muted-foreground hover:bg-transparent"
|
||||
>
|
||||
<GripVerticalIcon class="size-3 text-muted-foreground" />
|
||||
<span class="sr-only">Drag to reorder</span>
|
||||
</Button>
|
||||
26
src/lib/components/data-table-features.ts
Normal file
26
src/lib/components/data-table-features.ts
Normal file
@@ -0,0 +1,26 @@
|
||||
import {
|
||||
columnFilteringFeature,
|
||||
columnVisibilityFeature,
|
||||
createFilteredRowModel,
|
||||
createPaginatedRowModel,
|
||||
createSortedRowModel,
|
||||
rowPaginationFeature,
|
||||
rowSelectionFeature,
|
||||
rowSortingFeature,
|
||||
tableFeatures,
|
||||
} from "@tanstack/svelte-table";
|
||||
|
||||
// New in v9: declare the features this table uses — anything you don't
|
||||
// register is tree-shaken out of the bundle.
|
||||
export const features = tableFeatures({
|
||||
columnFilteringFeature,
|
||||
columnVisibilityFeature,
|
||||
rowPaginationFeature,
|
||||
rowSelectionFeature,
|
||||
rowSortingFeature,
|
||||
filteredRowModel: createFilteredRowModel(),
|
||||
paginatedRowModel: createPaginatedRowModel(),
|
||||
sortedRowModel: createSortedRowModel(),
|
||||
});
|
||||
|
||||
export type DashboardTableFeatures = typeof features;
|
||||
1
src/lib/components/data-table-header-limit.svelte
Normal file
1
src/lib/components/data-table-header-limit.svelte
Normal file
@@ -0,0 +1 @@
|
||||
<div class="w-full text-end">Limit</div>
|
||||
1
src/lib/components/data-table-header-target.svelte
Normal file
1
src/lib/components/data-table-header-target.svelte
Normal file
@@ -0,0 +1 @@
|
||||
<div class="w-full text-end">Target</div>
|
||||
28
src/lib/components/data-table-limit.svelte
Normal file
28
src/lib/components/data-table-limit.svelte
Normal file
@@ -0,0 +1,28 @@
|
||||
<script lang="ts">
|
||||
import { toast } from "svelte-sonner";
|
||||
import { Input } from "#lib/components/ui/input/index.js";
|
||||
import { Label } from "#lib/components/ui/label/index.js";
|
||||
import type { DashboardTableFeatures } from "./data-table-features.js";
|
||||
import type { Schema } from "./schemas.js";
|
||||
import type { Row } from "@tanstack/svelte-table";
|
||||
|
||||
let { row }: { row: Row<DashboardTableFeatures, Schema> } = $props();
|
||||
</script>
|
||||
|
||||
<form
|
||||
onsubmit={(e) => {
|
||||
e.preventDefault();
|
||||
toast.promise(new Promise((resolve) => setTimeout(resolve, 1000)), {
|
||||
loading: `Saving ${row.original.header}`,
|
||||
success: "Done",
|
||||
error: "Error",
|
||||
});
|
||||
}}
|
||||
>
|
||||
<Label for="{row.original.id}-limit" class="sr-only">Limit</Label>
|
||||
<Input
|
||||
class="h-8 w-16 border-transparent bg-transparent text-end shadow-none hover:bg-input/30 focus-visible:border focus-visible:bg-background dark:bg-transparent dark:hover:bg-input/30 dark:focus-visible:bg-input/30"
|
||||
value={row.original.limit}
|
||||
id="{row.original.id}-limit"
|
||||
/>
|
||||
</form>
|
||||
33
src/lib/components/data-table-reviewer.svelte
Normal file
33
src/lib/components/data-table-reviewer.svelte
Normal file
@@ -0,0 +1,33 @@
|
||||
<script lang="ts">
|
||||
import * as Select from "#lib/components/ui/select/index.js";
|
||||
import { Label } from "#lib/components/ui/label/index.js";
|
||||
import type { DashboardTableFeatures } from "./data-table-features.js";
|
||||
import type { Schema } from "./schemas.js";
|
||||
import type { Row } from "@tanstack/svelte-table";
|
||||
|
||||
let { row }: { row: Row<DashboardTableFeatures, Schema> } = $props();
|
||||
|
||||
const isAssigned = $derived(row.original.reviewer !== "Assign reviewer");
|
||||
let reviewer = $state("");
|
||||
</script>
|
||||
|
||||
{#if isAssigned}
|
||||
{row.original.reviewer}
|
||||
{:else}
|
||||
<Label for="{row.original.id}-reviewer" class="sr-only">Reviewer</Label>
|
||||
<Select.Root type="single" bind:value={reviewer}>
|
||||
<Select.Trigger
|
||||
class="w-38 **:data-[slot=select-value]:block **:data-[slot=select-value]:truncate"
|
||||
size="sm"
|
||||
id="{row.original.id}-reviewer"
|
||||
>
|
||||
<span data-slot="select-value">
|
||||
{reviewer !== "" ? reviewer : "Assign reviewer"}
|
||||
</span>
|
||||
</Select.Trigger>
|
||||
<Select.Content align="end">
|
||||
<Select.Item value="Eddie Lake">Eddie Lake</Select.Item>
|
||||
<Select.Item value="Jamik Tashpulatov">Jamik Tashpulatov</Select.Item>
|
||||
</Select.Content>
|
||||
</Select.Root>
|
||||
{/if}
|
||||
19
src/lib/components/data-table-status.svelte
Normal file
19
src/lib/components/data-table-status.svelte
Normal file
@@ -0,0 +1,19 @@
|
||||
<script lang="ts">
|
||||
import CircleCheckFilledIcon from "@tabler/icons-svelte/icons/circle-check-filled";
|
||||
import LoaderIcon from "@tabler/icons-svelte/icons/loader";
|
||||
import { Badge } from "#lib/components/ui/badge/index.js";
|
||||
import type { DashboardTableFeatures } from "./data-table-features.js";
|
||||
import type { Schema } from "./schemas.js";
|
||||
import type { Row } from "@tanstack/svelte-table";
|
||||
|
||||
let { row }: { row: Row<DashboardTableFeatures, Schema> } = $props();
|
||||
</script>
|
||||
|
||||
<Badge variant="outline" class="px-1.5 text-muted-foreground">
|
||||
{#if row.original.status === "Done"}
|
||||
<CircleCheckFilledIcon class="fill-green-500 dark:fill-green-400" />
|
||||
{:else}
|
||||
<LoaderIcon />
|
||||
{/if}
|
||||
{row.original.status}
|
||||
</Badge>
|
||||
28
src/lib/components/data-table-target.svelte
Normal file
28
src/lib/components/data-table-target.svelte
Normal file
@@ -0,0 +1,28 @@
|
||||
<script lang="ts">
|
||||
import { toast } from "svelte-sonner";
|
||||
import { Input } from "#lib/components/ui/input/index.js";
|
||||
import { Label } from "#lib/components/ui/label/index.js";
|
||||
import type { DashboardTableFeatures } from "./data-table-features.js";
|
||||
import type { Schema } from "./schemas.js";
|
||||
import type { Row } from "@tanstack/svelte-table";
|
||||
|
||||
let { row }: { row: Row<DashboardTableFeatures, Schema> } = $props();
|
||||
</script>
|
||||
|
||||
<form
|
||||
onsubmit={(e) => {
|
||||
e.preventDefault();
|
||||
toast.promise(new Promise((resolve) => setTimeout(resolve, 1000)), {
|
||||
loading: `Saving ${row.original.header}`,
|
||||
success: "Done",
|
||||
error: "Error",
|
||||
});
|
||||
}}
|
||||
>
|
||||
<Label for="{row.original.id}-target" class="sr-only">Target</Label>
|
||||
<Input
|
||||
class="h-8 w-16 border-transparent bg-transparent text-end shadow-none hover:bg-input/30 focus-visible:border focus-visible:bg-background dark:bg-transparent dark:hover:bg-input/30 dark:focus-visible:bg-input/30"
|
||||
value={row.original.target}
|
||||
id="{row.original.id}-target"
|
||||
/>
|
||||
</form>
|
||||
14
src/lib/components/data-table-type.svelte
Normal file
14
src/lib/components/data-table-type.svelte
Normal file
@@ -0,0 +1,14 @@
|
||||
<script lang="ts">
|
||||
import { Badge } from "#lib/components/ui/badge/index.js";
|
||||
import type { DashboardTableFeatures } from "./data-table-features.js";
|
||||
import type { Schema } from "./schemas.js";
|
||||
import type { Row } from "@tanstack/svelte-table";
|
||||
|
||||
let { row }: { row: Row<DashboardTableFeatures, Schema> } = $props();
|
||||
</script>
|
||||
|
||||
<div class="w-32">
|
||||
<Badge variant="outline" class="px-1.5 text-muted-foreground">
|
||||
{row.original.type}
|
||||
</Badge>
|
||||
</div>
|
||||
355
src/lib/components/data-table.svelte
Normal file
355
src/lib/components/data-table.svelte
Normal file
@@ -0,0 +1,355 @@
|
||||
<script lang="ts">
|
||||
import ChevronDownIcon from "@tabler/icons-svelte/icons/chevron-down";
|
||||
import ChevronLeftIcon from "@tabler/icons-svelte/icons/chevron-left";
|
||||
import ChevronRightIcon from "@tabler/icons-svelte/icons/chevron-right";
|
||||
import ChevronsLeftIcon from "@tabler/icons-svelte/icons/chevrons-left";
|
||||
import ChevronsRightIcon from "@tabler/icons-svelte/icons/chevrons-right";
|
||||
import LayoutColumnsIcon from "@tabler/icons-svelte/icons/layout-columns";
|
||||
import PlusIcon from "@tabler/icons-svelte/icons/plus";
|
||||
import { DragDropProvider } from "@dnd-kit-svelte/svelte";
|
||||
import { useSortable } from "@dnd-kit-svelte/svelte/sortable";
|
||||
import { RestrictToVerticalAxis } from "@dnd-kit/abstract/modifiers";
|
||||
import { move } from "@dnd-kit/helpers";
|
||||
import {
|
||||
FlexRender,
|
||||
type RowSelectionState,
|
||||
createColumnHelper,
|
||||
createTable,
|
||||
createTableState,
|
||||
renderComponent,
|
||||
type Row,
|
||||
} from "@tanstack/svelte-table";
|
||||
import * as DropdownMenu from "#lib/components/ui/dropdown-menu/index.js";
|
||||
import * as Select from "#lib/components/ui/select/index.js";
|
||||
import * as Table from "#lib/components/ui/table/index.js";
|
||||
import * as Tabs from "#lib/components/ui/tabs/index.js";
|
||||
import { Badge } from "#lib/components/ui/badge/index.js";
|
||||
import { Button } from "#lib/components/ui/button/index.js";
|
||||
import { Label } from "#lib/components/ui/label/index.js";
|
||||
import DataTableActions from "./data-table-actions.svelte";
|
||||
import DataTableCellViewer from "./data-table-cell-viewer.svelte";
|
||||
import DataTableCheckbox from "./data-table-checkbox.svelte";
|
||||
import DataTableDragHandle from "./data-table-drag-handle.svelte";
|
||||
import DataTableHeaderLimit from "./data-table-header-limit.svelte";
|
||||
import DataTableHeaderTarget from "./data-table-header-target.svelte";
|
||||
import DataTableLimit from "./data-table-limit.svelte";
|
||||
import DataTableReviewer from "./data-table-reviewer.svelte";
|
||||
import DataTableStatus from "./data-table-status.svelte";
|
||||
import DataTableTarget from "./data-table-target.svelte";
|
||||
import DataTableType from "./data-table-type.svelte";
|
||||
import { features, type DashboardTableFeatures } from "./data-table-features.js";
|
||||
import type { Schema } from "./schemas.js";
|
||||
|
||||
let { data }: { data: Schema[] } = $props();
|
||||
|
||||
const columnHelper = createColumnHelper<DashboardTableFeatures, Schema>();
|
||||
|
||||
const columns = columnHelper.columns([
|
||||
columnHelper.display({
|
||||
id: "drag",
|
||||
header: () => null,
|
||||
}),
|
||||
columnHelper.display({
|
||||
id: "select",
|
||||
header: ({ table }) =>
|
||||
renderComponent(DataTableCheckbox, {
|
||||
checked: table.getIsAllPageRowsSelected(),
|
||||
indeterminate: table.getIsSomePageRowsSelected() && !table.getIsAllPageRowsSelected(),
|
||||
onCheckedChange: (value: boolean) => table.toggleAllPageRowsSelected(!!value),
|
||||
"aria-label": "Select all",
|
||||
}),
|
||||
cell: ({ row }) =>
|
||||
renderComponent(DataTableCheckbox, {
|
||||
checked: row.getIsSelected(),
|
||||
onCheckedChange: (value: boolean) => row.toggleSelected(!!value),
|
||||
"aria-label": "Select row",
|
||||
}),
|
||||
enableSorting: false,
|
||||
enableHiding: false,
|
||||
}),
|
||||
columnHelper.accessor("header", {
|
||||
header: "Header",
|
||||
cell: ({ row }) => renderComponent(DataTableCellViewer, { item: row.original }),
|
||||
enableHiding: false,
|
||||
}),
|
||||
columnHelper.accessor("type", {
|
||||
header: "Section Type",
|
||||
cell: ({ row }) => renderComponent(DataTableType, { row }),
|
||||
}),
|
||||
columnHelper.accessor("status", {
|
||||
header: "Status",
|
||||
cell: ({ row }) => renderComponent(DataTableStatus, { row }),
|
||||
}),
|
||||
columnHelper.accessor("target", {
|
||||
header: () => renderComponent(DataTableHeaderTarget, {}),
|
||||
cell: ({ row }) => renderComponent(DataTableTarget, { row }),
|
||||
}),
|
||||
columnHelper.accessor("limit", {
|
||||
header: () => renderComponent(DataTableHeaderLimit, {}),
|
||||
cell: ({ row }) => renderComponent(DataTableLimit, { row }),
|
||||
}),
|
||||
columnHelper.accessor("reviewer", {
|
||||
header: "Reviewer",
|
||||
cell: ({ row }) => renderComponent(DataTableReviewer, { row }),
|
||||
}),
|
||||
columnHelper.display({
|
||||
id: "actions",
|
||||
cell: () => renderComponent(DataTableActions, {}),
|
||||
}),
|
||||
]);
|
||||
|
||||
// Keep row selection outside the table so the rest of the app can read or update it.
|
||||
const [rowSelection, setRowSelection] = createTableState<RowSelectionState>({});
|
||||
|
||||
// v9 manages the rest of its state internally — reads like
|
||||
// `table.getRowModel()` are rune-reactive, so no `$state` mirrors are needed.
|
||||
const table = createTable({
|
||||
features,
|
||||
get data() {
|
||||
return data;
|
||||
},
|
||||
columns,
|
||||
getRowId: (row) => row.id.toString(),
|
||||
enableRowSelection: true,
|
||||
autoResetPageIndex: false,
|
||||
state: {
|
||||
get rowSelection() {
|
||||
return rowSelection();
|
||||
},
|
||||
},
|
||||
onRowSelectionChange: setRowSelection,
|
||||
});
|
||||
|
||||
const pagination = $derived(table.atoms.pagination.get());
|
||||
|
||||
let views = [
|
||||
{
|
||||
id: "outline",
|
||||
label: "Outline",
|
||||
badge: 0,
|
||||
},
|
||||
{
|
||||
id: "past-performance",
|
||||
label: "Past Performance",
|
||||
badge: 3,
|
||||
},
|
||||
{
|
||||
id: "key-personnel",
|
||||
label: "Key Personnel",
|
||||
badge: 2,
|
||||
},
|
||||
{
|
||||
id: "focus-documents",
|
||||
label: "Focus Documents",
|
||||
badge: 0,
|
||||
},
|
||||
];
|
||||
|
||||
let view = $state("outline");
|
||||
let viewLabel = $derived(views.find((v) => view === v.id)?.label ?? "Select a view");
|
||||
</script>
|
||||
|
||||
<Tabs.Root value="outline" class="w-full flex-col justify-start gap-6">
|
||||
<div class="flex items-center justify-between px-4 lg:px-6">
|
||||
<Label for="view-selector" class="sr-only">View</Label>
|
||||
<Select.Root type="single" bind:value={view}>
|
||||
<Select.Trigger class="flex w-fit @4xl/main:hidden" size="sm" id="view-selector">
|
||||
{viewLabel}
|
||||
</Select.Trigger>
|
||||
<Select.Content>
|
||||
{#each views as view (view.id)}
|
||||
<Select.Item value={view.id}>{view.label}</Select.Item>
|
||||
{/each}
|
||||
</Select.Content>
|
||||
</Select.Root>
|
||||
<Tabs.List
|
||||
class="hidden **:data-[slot=badge]:size-5 **:data-[slot=badge]:rounded-full **:data-[slot=badge]:bg-muted-foreground/30 **:data-[slot=badge]:px-1 @4xl/main:flex"
|
||||
>
|
||||
{#each views as view (view.id)}
|
||||
<Tabs.Trigger value={view.id}>
|
||||
{view.label}
|
||||
{#if view.badge > 0}
|
||||
<Badge variant="secondary">{view.badge}</Badge>
|
||||
{/if}
|
||||
</Tabs.Trigger>
|
||||
{/each}
|
||||
</Tabs.List>
|
||||
<div class="flex items-center gap-2">
|
||||
<DropdownMenu.Root>
|
||||
<DropdownMenu.Trigger>
|
||||
{#snippet child({ props })}
|
||||
<Button variant="outline" size="sm" {...props}>
|
||||
<LayoutColumnsIcon />
|
||||
<span class="hidden lg:inline">Customize Columns</span>
|
||||
<span class="lg:hidden">Columns</span>
|
||||
<ChevronDownIcon />
|
||||
</Button>
|
||||
{/snippet}
|
||||
</DropdownMenu.Trigger>
|
||||
<DropdownMenu.Content align="end" class="w-56">
|
||||
{#each table
|
||||
.getAllColumns()
|
||||
.filter((col) => typeof col.accessorFn !== "undefined" && col.getCanHide()) as column (column.id)}
|
||||
<DropdownMenu.CheckboxItem
|
||||
class="capitalize"
|
||||
checked={column.getIsVisible()}
|
||||
onCheckedChange={(value) => column.toggleVisibility(!!value)}
|
||||
>
|
||||
{column.id}
|
||||
</DropdownMenu.CheckboxItem>
|
||||
{/each}
|
||||
</DropdownMenu.Content>
|
||||
</DropdownMenu.Root>
|
||||
<Button variant="outline" size="sm">
|
||||
<PlusIcon />
|
||||
<span class="hidden lg:inline">Add Section</span>
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
<Tabs.Content value="outline" class="relative flex flex-col gap-4 overflow-auto px-4 lg:px-6">
|
||||
<div class="overflow-hidden rounded-lg border">
|
||||
<DragDropProvider
|
||||
modifiers={[
|
||||
// @ts-expect-error @dnd-kit/abstract types are botched atm
|
||||
RestrictToVerticalAxis,
|
||||
]}
|
||||
onDragEnd={(e) => (data = move(data, e))}
|
||||
>
|
||||
<Table.Root>
|
||||
<Table.Header class="sticky top-0 z-10 bg-muted">
|
||||
{#each table.getHeaderGroups() as headerGroup (headerGroup.id)}
|
||||
<Table.Row>
|
||||
{#each headerGroup.headers as header (header.id)}
|
||||
<Table.Head colspan={header.colSpan}>
|
||||
{#if !header.isPlaceholder}
|
||||
<FlexRender {header} />
|
||||
{/if}
|
||||
</Table.Head>
|
||||
{/each}
|
||||
</Table.Row>
|
||||
{/each}
|
||||
</Table.Header>
|
||||
<Table.Body class="**:data-[slot=table-cell]:first:w-8">
|
||||
{#if table.getRowModel().rows?.length}
|
||||
{#each table.getRowModel().rows as row (row.id)}
|
||||
{@render DraggableRow({ row })}
|
||||
{/each}
|
||||
{:else}
|
||||
<Table.Row>
|
||||
<Table.Cell colspan={columns.length} class="h-24 text-center">
|
||||
No results.
|
||||
</Table.Cell>
|
||||
</Table.Row>
|
||||
{/if}
|
||||
</Table.Body>
|
||||
</Table.Root>
|
||||
</DragDropProvider>
|
||||
</div>
|
||||
<div class="flex items-center justify-between px-4">
|
||||
<div class="hidden flex-1 text-sm text-muted-foreground lg:flex">
|
||||
{table.getFilteredSelectedRowModel().rows.length} of
|
||||
{table.getFilteredRowModel().rows.length} row(s) selected.
|
||||
</div>
|
||||
<div class="flex w-full items-center gap-8 lg:w-fit">
|
||||
<div class="hidden items-center gap-2 lg:flex">
|
||||
<Label for="rows-per-page" class="text-sm font-medium">Rows per page</Label>
|
||||
<Select.Root
|
||||
type="single"
|
||||
bind:value={() => `${pagination.pageSize}`, (v) => table.setPageSize(Number(v))}
|
||||
>
|
||||
<Select.Trigger size="sm" class="w-20" id="rows-per-page">
|
||||
{pagination.pageSize}
|
||||
</Select.Trigger>
|
||||
<Select.Content side="top">
|
||||
{#each [10, 20, 30, 40, 50] as pageSize (pageSize)}
|
||||
<Select.Item value={pageSize.toString()}>
|
||||
{pageSize}
|
||||
</Select.Item>
|
||||
{/each}
|
||||
</Select.Content>
|
||||
</Select.Root>
|
||||
</div>
|
||||
<div class="flex w-fit items-center justify-center text-sm font-medium">
|
||||
Page {pagination.pageIndex + 1} of
|
||||
{table.getPageCount()}
|
||||
</div>
|
||||
<div class="ms-auto flex items-center gap-2 lg:ms-0">
|
||||
<Button
|
||||
variant="outline"
|
||||
class="hidden h-8 w-8 p-0 lg:flex"
|
||||
onclick={() => table.setPageIndex(0)}
|
||||
disabled={!table.getCanPreviousPage()}
|
||||
>
|
||||
<span class="sr-only">Go to first page</span>
|
||||
<ChevronsLeftIcon />
|
||||
</Button>
|
||||
<Button
|
||||
variant="outline"
|
||||
class="size-8"
|
||||
size="icon"
|
||||
onclick={() => table.previousPage()}
|
||||
disabled={!table.getCanPreviousPage()}
|
||||
>
|
||||
<span class="sr-only">Go to previous page</span>
|
||||
<ChevronLeftIcon />
|
||||
</Button>
|
||||
<Button
|
||||
variant="outline"
|
||||
class="size-8"
|
||||
size="icon"
|
||||
onclick={() => table.nextPage()}
|
||||
disabled={!table.getCanNextPage()}
|
||||
>
|
||||
<span class="sr-only">Go to next page</span>
|
||||
<ChevronRightIcon />
|
||||
</Button>
|
||||
<Button
|
||||
variant="outline"
|
||||
class="hidden size-8 lg:flex"
|
||||
size="icon"
|
||||
onclick={() => table.setPageIndex(table.getPageCount() - 1)}
|
||||
disabled={!table.getCanNextPage()}
|
||||
>
|
||||
<span class="sr-only">Go to last page</span>
|
||||
<ChevronsRightIcon />
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</Tabs.Content>
|
||||
<Tabs.Content value="past-performance" class="flex flex-col px-4 lg:px-6">
|
||||
<div class="aspect-video w-full flex-1 rounded-lg border border-dashed"></div>
|
||||
</Tabs.Content>
|
||||
<Tabs.Content value="key-personnel" class="flex flex-col px-4 lg:px-6">
|
||||
<div class="aspect-video w-full flex-1 rounded-lg border border-dashed"></div>
|
||||
</Tabs.Content>
|
||||
<Tabs.Content value="focus-documents" class="flex flex-col px-4 lg:px-6">
|
||||
<div class="aspect-video w-full flex-1 rounded-lg border border-dashed"></div>
|
||||
</Tabs.Content>
|
||||
</Tabs.Root>
|
||||
|
||||
{#snippet DraggableRow({ row }: { row: Row<DashboardTableFeatures, Schema> })}
|
||||
{@const { ref, isDragging, handleRef } = useSortable({
|
||||
id: row.original.id,
|
||||
index: () => row.index,
|
||||
})}
|
||||
|
||||
<Table.Row
|
||||
data-state={row.getIsSelected() && "selected"}
|
||||
data-dragging={isDragging.current}
|
||||
class="relative z-0 data-[dragging=true]:z-10 data-[dragging=true]:opacity-80"
|
||||
{@attach ref}
|
||||
>
|
||||
{#each row.getVisibleCells() as cell (cell.id)}
|
||||
<Table.Cell>
|
||||
{#if cell.column.id === "drag"}
|
||||
<!-- The drag handle needs this row's sortable handleRef, so it renders
|
||||
here instead of through the column def. -->
|
||||
<DataTableDragHandle attach={handleRef} />
|
||||
{:else}
|
||||
<FlexRender {cell} />
|
||||
{/if}
|
||||
</Table.Cell>
|
||||
{/each}
|
||||
</Table.Row>
|
||||
{/snippet}
|
||||
70
src/lib/components/nav-documents.svelte
Normal file
70
src/lib/components/nav-documents.svelte
Normal file
@@ -0,0 +1,70 @@
|
||||
<script lang="ts">
|
||||
import DotsIcon from "@tabler/icons-svelte/icons/dots";
|
||||
import FolderIcon from "@tabler/icons-svelte/icons/folder";
|
||||
import Share3Icon from "@tabler/icons-svelte/icons/share-3";
|
||||
import TrashIcon from "@tabler/icons-svelte/icons/trash";
|
||||
import * as DropdownMenu from "#lib/components/ui/dropdown-menu/index.js";
|
||||
import * as Sidebar from "#lib/components/ui/sidebar/index.js";
|
||||
import type { Icon } from "@tabler/icons-svelte";
|
||||
|
||||
let { items }: { items: { name: string; url: string; icon: Icon }[] } = $props();
|
||||
|
||||
const sidebar = Sidebar.useSidebar();
|
||||
</script>
|
||||
|
||||
<Sidebar.Group class="group-data-[collapsible=icon]:hidden">
|
||||
<Sidebar.GroupLabel>Documents</Sidebar.GroupLabel>
|
||||
<Sidebar.Menu>
|
||||
{#each items as item (item.name)}
|
||||
<Sidebar.MenuItem>
|
||||
<Sidebar.MenuButton>
|
||||
{#snippet child({ props })}
|
||||
<a {...props} href={item.url}>
|
||||
<item.icon />
|
||||
<span>{item.name}</span>
|
||||
</a>
|
||||
{/snippet}
|
||||
</Sidebar.MenuButton>
|
||||
<DropdownMenu.Root>
|
||||
<DropdownMenu.Trigger>
|
||||
{#snippet child({ props })}
|
||||
<Sidebar.MenuAction
|
||||
{...props}
|
||||
showOnHover
|
||||
class="rounded-sm data-[state=open]:bg-accent"
|
||||
>
|
||||
<DotsIcon />
|
||||
<span class="sr-only">More</span>
|
||||
</Sidebar.MenuAction>
|
||||
{/snippet}
|
||||
</DropdownMenu.Trigger>
|
||||
<DropdownMenu.Content
|
||||
class="w-24 rounded-lg"
|
||||
side={sidebar.isMobile ? "bottom" : "right"}
|
||||
align={sidebar.isMobile ? "end" : "start"}
|
||||
>
|
||||
<DropdownMenu.Item>
|
||||
<FolderIcon />
|
||||
<span>Open</span>
|
||||
</DropdownMenu.Item>
|
||||
<DropdownMenu.Item>
|
||||
<Share3Icon />
|
||||
<span>Share</span>
|
||||
</DropdownMenu.Item>
|
||||
<DropdownMenu.Separator />
|
||||
<DropdownMenu.Item variant="destructive">
|
||||
<TrashIcon />
|
||||
<span>Delete</span>
|
||||
</DropdownMenu.Item>
|
||||
</DropdownMenu.Content>
|
||||
</DropdownMenu.Root>
|
||||
</Sidebar.MenuItem>
|
||||
{/each}
|
||||
<Sidebar.MenuItem>
|
||||
<Sidebar.MenuButton class="text-sidebar-foreground/70">
|
||||
<DotsIcon class="text-sidebar-foreground/70" />
|
||||
<span>More</span>
|
||||
</Sidebar.MenuButton>
|
||||
</Sidebar.MenuItem>
|
||||
</Sidebar.Menu>
|
||||
</Sidebar.Group>
|
||||
45
src/lib/components/nav-main.svelte
Normal file
45
src/lib/components/nav-main.svelte
Normal file
@@ -0,0 +1,45 @@
|
||||
<script lang="ts">
|
||||
import CirclePlusFilledIcon from "@tabler/icons-svelte/icons/circle-plus-filled";
|
||||
import MailIcon from "@tabler/icons-svelte/icons/mail";
|
||||
import * as Sidebar from "#lib/components/ui/sidebar/index.js";
|
||||
import { Button } from "#lib/components/ui/button/index.js";
|
||||
import type { Icon } from "@tabler/icons-svelte";
|
||||
|
||||
let { items }: { items: { title: string; url: string; icon?: Icon }[] } = $props();
|
||||
</script>
|
||||
|
||||
<Sidebar.Group>
|
||||
<Sidebar.GroupContent class="flex flex-col gap-2">
|
||||
<Sidebar.Menu>
|
||||
<Sidebar.MenuItem class="flex items-center gap-2">
|
||||
<Sidebar.MenuButton
|
||||
class="min-w-8 bg-primary text-primary-foreground duration-200 ease-linear hover:bg-primary/90 hover:text-primary-foreground active:bg-primary/90 active:text-primary-foreground"
|
||||
tooltipContent="Quick create"
|
||||
>
|
||||
<CirclePlusFilledIcon />
|
||||
<span>Quick Create</span>
|
||||
</Sidebar.MenuButton>
|
||||
<Button
|
||||
size="icon"
|
||||
class="size-8 group-data-[collapsible=icon]:opacity-0"
|
||||
variant="outline"
|
||||
>
|
||||
<MailIcon />
|
||||
<span class="sr-only">Inbox</span>
|
||||
</Button>
|
||||
</Sidebar.MenuItem>
|
||||
</Sidebar.Menu>
|
||||
<Sidebar.Menu>
|
||||
{#each items as item (item.title)}
|
||||
<Sidebar.MenuItem>
|
||||
<Sidebar.MenuButton tooltipContent={item.title}>
|
||||
{#if item.icon}
|
||||
<item.icon />
|
||||
{/if}
|
||||
<span>{item.title}</span>
|
||||
</Sidebar.MenuButton>
|
||||
</Sidebar.MenuItem>
|
||||
{/each}
|
||||
</Sidebar.Menu>
|
||||
</Sidebar.GroupContent>
|
||||
</Sidebar.Group>
|
||||
32
src/lib/components/nav-secondary.svelte
Normal file
32
src/lib/components/nav-secondary.svelte
Normal file
@@ -0,0 +1,32 @@
|
||||
<script lang="ts">
|
||||
import * as Sidebar from "#lib/components/ui/sidebar/index.js";
|
||||
import type { WithoutChildren } from "#lib/utils.js";
|
||||
import type { Icon } from "@tabler/icons-svelte";
|
||||
import type { ComponentProps } from "svelte";
|
||||
|
||||
let {
|
||||
items,
|
||||
...restProps
|
||||
}: { items: { title: string; url: string; icon: Icon }[] } & WithoutChildren<
|
||||
ComponentProps<typeof Sidebar.Group>
|
||||
> = $props();
|
||||
</script>
|
||||
|
||||
<Sidebar.Group {...restProps}>
|
||||
<Sidebar.GroupContent>
|
||||
<Sidebar.Menu>
|
||||
{#each items as item (item.title)}
|
||||
<Sidebar.MenuItem>
|
||||
<Sidebar.MenuButton>
|
||||
{#snippet child({ props })}
|
||||
<a href={item.url} {...props}>
|
||||
<item.icon />
|
||||
<span>{item.title}</span>
|
||||
</a>
|
||||
{/snippet}
|
||||
</Sidebar.MenuButton>
|
||||
</Sidebar.MenuItem>
|
||||
{/each}
|
||||
</Sidebar.Menu>
|
||||
</Sidebar.GroupContent>
|
||||
</Sidebar.Group>
|
||||
83
src/lib/components/nav-user.svelte
Normal file
83
src/lib/components/nav-user.svelte
Normal file
@@ -0,0 +1,83 @@
|
||||
<script lang="ts">
|
||||
import CreditCardIcon from "@tabler/icons-svelte/icons/credit-card";
|
||||
import DotsVerticalIcon from "@tabler/icons-svelte/icons/dots-vertical";
|
||||
import LogoutIcon from "@tabler/icons-svelte/icons/logout";
|
||||
import NotificationIcon from "@tabler/icons-svelte/icons/notification";
|
||||
import UserCircleIcon from "@tabler/icons-svelte/icons/user-circle";
|
||||
import * as Avatar from "#lib/components/ui/avatar/index.js";
|
||||
import * as DropdownMenu from "#lib/components/ui/dropdown-menu/index.js";
|
||||
import * as Sidebar from "#lib/components/ui/sidebar/index.js";
|
||||
|
||||
let { user }: { user: { name: string; email: string; avatar: string } } = $props();
|
||||
|
||||
const sidebar = Sidebar.useSidebar();
|
||||
</script>
|
||||
|
||||
<Sidebar.Menu>
|
||||
<Sidebar.MenuItem>
|
||||
<DropdownMenu.Root>
|
||||
<DropdownMenu.Trigger>
|
||||
{#snippet child({ props })}
|
||||
<Sidebar.MenuButton
|
||||
{...props}
|
||||
size="lg"
|
||||
class="data-[state=open]:bg-sidebar-accent data-[state=open]:text-sidebar-accent-foreground"
|
||||
>
|
||||
<Avatar.Root class="size-8 rounded-lg grayscale">
|
||||
<Avatar.Image src={user.avatar} alt={user.name} />
|
||||
<Avatar.Fallback class="rounded-lg">CN</Avatar.Fallback>
|
||||
</Avatar.Root>
|
||||
<div class="grid flex-1 text-start text-sm leading-tight">
|
||||
<span class="truncate font-medium">{user.name}</span>
|
||||
<span class="truncate text-xs text-muted-foreground">
|
||||
{user.email}
|
||||
</span>
|
||||
</div>
|
||||
<DotsVerticalIcon class="ms-auto size-4" />
|
||||
</Sidebar.MenuButton>
|
||||
{/snippet}
|
||||
</DropdownMenu.Trigger>
|
||||
<DropdownMenu.Content
|
||||
class="w-(--bits-dropdown-menu-anchor-width) min-w-56 rounded-lg"
|
||||
side={sidebar.isMobile ? "bottom" : "right"}
|
||||
align="end"
|
||||
sideOffset={4}
|
||||
>
|
||||
<DropdownMenu.Label class="p-0 font-normal">
|
||||
<div class="flex items-center gap-2 px-1 py-1.5 text-start text-sm">
|
||||
<Avatar.Root class="size-8 rounded-lg">
|
||||
<Avatar.Image src={user.avatar} alt={user.name} />
|
||||
<Avatar.Fallback class="rounded-lg">CN</Avatar.Fallback>
|
||||
</Avatar.Root>
|
||||
<div class="grid flex-1 text-start text-sm leading-tight">
|
||||
<span class="truncate font-medium">{user.name}</span>
|
||||
<span class="truncate text-xs text-muted-foreground">
|
||||
{user.email}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</DropdownMenu.Label>
|
||||
<DropdownMenu.Separator />
|
||||
<DropdownMenu.Group>
|
||||
<DropdownMenu.Item>
|
||||
<UserCircleIcon />
|
||||
Account
|
||||
</DropdownMenu.Item>
|
||||
<DropdownMenu.Item>
|
||||
<CreditCardIcon />
|
||||
Billing
|
||||
</DropdownMenu.Item>
|
||||
<DropdownMenu.Item>
|
||||
<NotificationIcon />
|
||||
Notifications
|
||||
</DropdownMenu.Item>
|
||||
</DropdownMenu.Group>
|
||||
<DropdownMenu.Separator />
|
||||
<DropdownMenu.Item>
|
||||
<LogoutIcon />
|
||||
Log out
|
||||
</DropdownMenu.Item>
|
||||
</DropdownMenu.Content>
|
||||
</DropdownMenu.Root>
|
||||
</Sidebar.MenuItem>
|
||||
</Sidebar.Menu>
|
||||
13
src/lib/components/schemas.ts
Normal file
13
src/lib/components/schemas.ts
Normal file
@@ -0,0 +1,13 @@
|
||||
import { z } from "zod";
|
||||
|
||||
export const schema = z.object({
|
||||
id: z.number(),
|
||||
header: z.string(),
|
||||
type: z.string(),
|
||||
status: z.string(),
|
||||
target: z.string(),
|
||||
limit: z.string(),
|
||||
reviewer: z.string(),
|
||||
});
|
||||
|
||||
export type Schema = z.infer<typeof schema>;
|
||||
91
src/lib/components/section-cards.svelte
Normal file
91
src/lib/components/section-cards.svelte
Normal file
@@ -0,0 +1,91 @@
|
||||
<script lang="ts">
|
||||
import TrendingDownIcon from "@tabler/icons-svelte/icons/trending-down";
|
||||
import TrendingUpIcon from "@tabler/icons-svelte/icons/trending-up";
|
||||
import * as Card from "#lib/components/ui/card/index.js";
|
||||
import { Badge } from "#lib/components/ui/badge/index.js";
|
||||
</script>
|
||||
|
||||
<div
|
||||
class="grid grid-cols-1 gap-4 px-4 *:data-[slot=card]:bg-gradient-to-t *:data-[slot=card]:from-primary/5 *:data-[slot=card]:to-card *:data-[slot=card]:shadow-xs lg:px-6 @xl/main:grid-cols-2 @5xl/main:grid-cols-4 dark:*:data-[slot=card]:bg-card"
|
||||
>
|
||||
<Card.Root class="@container/card">
|
||||
<Card.Header>
|
||||
<Card.Description>Total Revenue</Card.Description>
|
||||
<Card.Title class="text-2xl font-semibold tabular-nums @[250px]/card:text-3xl">
|
||||
$1,250.00
|
||||
</Card.Title>
|
||||
<Card.Action>
|
||||
<Badge variant="outline">
|
||||
<TrendingUpIcon />
|
||||
+12.5%
|
||||
</Badge>
|
||||
</Card.Action>
|
||||
</Card.Header>
|
||||
<Card.Footer class="flex-col items-start gap-1.5 text-sm">
|
||||
<div class="line-clamp-1 flex gap-2 font-medium">
|
||||
Trending up this month <TrendingUpIcon class="size-4" />
|
||||
</div>
|
||||
<div class="text-muted-foreground">Visitors for the last 6 months</div>
|
||||
</Card.Footer>
|
||||
</Card.Root>
|
||||
<Card.Root class="@container/card">
|
||||
<Card.Header>
|
||||
<Card.Description>New Customers</Card.Description>
|
||||
<Card.Title class="text-2xl font-semibold tabular-nums @[250px]/card:text-3xl">
|
||||
1,234
|
||||
</Card.Title>
|
||||
<Card.Action>
|
||||
<Badge variant="outline">
|
||||
<TrendingDownIcon />
|
||||
-20%
|
||||
</Badge>
|
||||
</Card.Action>
|
||||
</Card.Header>
|
||||
<Card.Footer class="flex-col items-start gap-1.5 text-sm">
|
||||
<div class="line-clamp-1 flex gap-2 font-medium">
|
||||
Down 20% this period <TrendingDownIcon class="size-4" />
|
||||
</div>
|
||||
<div class="text-muted-foreground">Acquisition needs attention</div>
|
||||
</Card.Footer>
|
||||
</Card.Root>
|
||||
<Card.Root class="@container/card">
|
||||
<Card.Header>
|
||||
<Card.Description>Active Accounts</Card.Description>
|
||||
<Card.Title class="text-2xl font-semibold tabular-nums @[250px]/card:text-3xl">
|
||||
45,678
|
||||
</Card.Title>
|
||||
<Card.Action>
|
||||
<Badge variant="outline">
|
||||
<TrendingUpIcon />
|
||||
+12.5%
|
||||
</Badge>
|
||||
</Card.Action>
|
||||
</Card.Header>
|
||||
<Card.Footer class="flex-col items-start gap-1.5 text-sm">
|
||||
<div class="line-clamp-1 flex gap-2 font-medium">
|
||||
Strong user retention <TrendingUpIcon class="size-4" />
|
||||
</div>
|
||||
<div class="text-muted-foreground">Engagement exceed targets</div>
|
||||
</Card.Footer>
|
||||
</Card.Root>
|
||||
<Card.Root class="@container/card">
|
||||
<Card.Header>
|
||||
<Card.Description>Growth Rate</Card.Description>
|
||||
<Card.Title class="text-2xl font-semibold tabular-nums @[250px]/card:text-3xl">
|
||||
4.5%
|
||||
</Card.Title>
|
||||
<Card.Action>
|
||||
<Badge variant="outline">
|
||||
<TrendingUpIcon />
|
||||
+4.5%
|
||||
</Badge>
|
||||
</Card.Action>
|
||||
</Card.Header>
|
||||
<Card.Footer class="flex-col items-start gap-1.5 text-sm">
|
||||
<div class="line-clamp-1 flex gap-2 font-medium">
|
||||
Steady performance increase <TrendingUpIcon class="size-4" />
|
||||
</div>
|
||||
<div class="text-muted-foreground">Meets growth projections</div>
|
||||
</Card.Footer>
|
||||
</Card.Root>
|
||||
</div>
|
||||
27
src/lib/components/site-header.svelte
Normal file
27
src/lib/components/site-header.svelte
Normal file
@@ -0,0 +1,27 @@
|
||||
<script lang="ts">
|
||||
import * as Sidebar from "#lib/components/ui/sidebar/index.js";
|
||||
import { Button } from "#lib/components/ui/button/index.js";
|
||||
import { Separator } from "#lib/components/ui/separator/index.js";
|
||||
</script>
|
||||
|
||||
<header
|
||||
class="flex h-(--header-height) shrink-0 items-center gap-2 border-b transition-[width,height] ease-linear group-has-data-[collapsible=icon]/sidebar-wrapper:h-(--header-height)"
|
||||
>
|
||||
<div class="flex w-full items-center gap-1 px-4 lg:gap-2 lg:px-6">
|
||||
<Sidebar.Trigger class="-ms-1" />
|
||||
<Separator orientation="vertical" class="mx-2 data-vertical:h-4 data-vertical:self-auto" />
|
||||
<h1 class="text-base font-medium">Documents</h1>
|
||||
<div class="ms-auto flex items-center gap-2">
|
||||
<Button
|
||||
href="https://github.com/shadcn-ui/ui/tree/main/apps/v4/app/(examples)/dashboard"
|
||||
variant="ghost"
|
||||
size="sm"
|
||||
class="hidden sm:flex dark:text-foreground"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
>
|
||||
GitHub
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
</header>
|
||||
26
src/lib/components/ui/avatar/avatar-badge.svelte
Normal file
26
src/lib/components/ui/avatar/avatar-badge.svelte
Normal file
@@ -0,0 +1,26 @@
|
||||
<script lang="ts">
|
||||
import { cn, type WithElementRef } from "#lib/utils.js";
|
||||
import type { HTMLAttributes } from "svelte/elements";
|
||||
|
||||
let {
|
||||
ref = $bindable(null),
|
||||
class: className,
|
||||
children,
|
||||
...restProps
|
||||
}: WithElementRef<HTMLAttributes<HTMLSpanElement>> = $props();
|
||||
</script>
|
||||
|
||||
<span
|
||||
bind:this={ref}
|
||||
data-slot="avatar-badge"
|
||||
class={cn(
|
||||
"bg-primary text-primary-foreground ring-background absolute right-0 bottom-0 z-10 inline-flex items-center justify-center rounded-full bg-blend-color ring-2 select-none",
|
||||
"group-data-[size=sm]/avatar:size-2 group-data-[size=sm]/avatar:[&>svg]:hidden",
|
||||
"group-data-[size=default]/avatar:size-2.5 group-data-[size=default]/avatar:[&>svg]:size-2",
|
||||
"group-data-[size=lg]/avatar:size-3 group-data-[size=lg]/avatar:[&>svg]:size-2",
|
||||
className
|
||||
)}
|
||||
{...restProps}
|
||||
>
|
||||
{@render children?.()}
|
||||
</span>
|
||||
20
src/lib/components/ui/avatar/avatar-fallback.svelte
Normal file
20
src/lib/components/ui/avatar/avatar-fallback.svelte
Normal file
@@ -0,0 +1,20 @@
|
||||
<script lang="ts">
|
||||
import { Avatar as AvatarPrimitive } from "bits-ui";
|
||||
import { cn } from "#lib/utils.js";
|
||||
|
||||
let {
|
||||
ref = $bindable(null),
|
||||
class: className,
|
||||
...restProps
|
||||
}: AvatarPrimitive.FallbackProps = $props();
|
||||
</script>
|
||||
|
||||
<AvatarPrimitive.Fallback
|
||||
bind:ref
|
||||
data-slot="avatar-fallback"
|
||||
class={cn(
|
||||
"bg-muted text-muted-foreground rounded-full flex size-full items-center justify-center text-sm group-data-[size=sm]/avatar:text-xs",
|
||||
className
|
||||
)}
|
||||
{...restProps}
|
||||
/>
|
||||
23
src/lib/components/ui/avatar/avatar-group-count.svelte
Normal file
23
src/lib/components/ui/avatar/avatar-group-count.svelte
Normal file
@@ -0,0 +1,23 @@
|
||||
<script lang="ts">
|
||||
import { cn, type WithElementRef } from "#lib/utils.js";
|
||||
import type { HTMLAttributes } from "svelte/elements";
|
||||
|
||||
let {
|
||||
ref = $bindable(null),
|
||||
class: className,
|
||||
children,
|
||||
...restProps
|
||||
}: WithElementRef<HTMLAttributes<HTMLDivElement>> = $props();
|
||||
</script>
|
||||
|
||||
<div
|
||||
bind:this={ref}
|
||||
data-slot="avatar-group-count"
|
||||
class={cn(
|
||||
"bg-muted text-muted-foreground size-8 rounded-full text-sm group-has-data-[size=lg]/avatar-group:size-10 group-has-data-[size=sm]/avatar-group:size-6 [&>svg]:size-4 group-has-data-[size=lg]/avatar-group:[&>svg]:size-5 group-has-data-[size=sm]/avatar-group:[&>svg]:size-3 relative flex shrink-0 items-center justify-center ring-2 ring-background",
|
||||
className
|
||||
)}
|
||||
{...restProps}
|
||||
>
|
||||
{@render children?.()}
|
||||
</div>
|
||||
23
src/lib/components/ui/avatar/avatar-group.svelte
Normal file
23
src/lib/components/ui/avatar/avatar-group.svelte
Normal file
@@ -0,0 +1,23 @@
|
||||
<script lang="ts">
|
||||
import { cn, type WithElementRef } from "#lib/utils.js";
|
||||
import type { HTMLAttributes } from "svelte/elements";
|
||||
|
||||
let {
|
||||
ref = $bindable(null),
|
||||
class: className,
|
||||
children,
|
||||
...restProps
|
||||
}: WithElementRef<HTMLAttributes<HTMLDivElement>> = $props();
|
||||
</script>
|
||||
|
||||
<div
|
||||
bind:this={ref}
|
||||
data-slot="avatar-group"
|
||||
class={cn(
|
||||
"group/avatar-group flex -space-x-2 *:data-[slot=avatar]:ring-2 *:data-[slot=avatar]:ring-background",
|
||||
className
|
||||
)}
|
||||
{...restProps}
|
||||
>
|
||||
{@render children?.()}
|
||||
</div>
|
||||
17
src/lib/components/ui/avatar/avatar-image.svelte
Normal file
17
src/lib/components/ui/avatar/avatar-image.svelte
Normal file
@@ -0,0 +1,17 @@
|
||||
<script lang="ts">
|
||||
import { Avatar as AvatarPrimitive } from "bits-ui";
|
||||
import { cn } from "#lib/utils.js";
|
||||
|
||||
let {
|
||||
ref = $bindable(null),
|
||||
class: className,
|
||||
...restProps
|
||||
}: AvatarPrimitive.ImageProps = $props();
|
||||
</script>
|
||||
|
||||
<AvatarPrimitive.Image
|
||||
bind:ref
|
||||
data-slot="avatar-image"
|
||||
class={cn("rounded-full aspect-square size-full object-cover", className)}
|
||||
{...restProps}
|
||||
/>
|
||||
26
src/lib/components/ui/avatar/avatar.svelte
Normal file
26
src/lib/components/ui/avatar/avatar.svelte
Normal file
@@ -0,0 +1,26 @@
|
||||
<script lang="ts">
|
||||
import { Avatar as AvatarPrimitive } from "bits-ui";
|
||||
import { cn } from "#lib/utils.js";
|
||||
|
||||
let {
|
||||
ref = $bindable(null),
|
||||
loadingStatus = $bindable("loading"),
|
||||
size = "default",
|
||||
class: className,
|
||||
...restProps
|
||||
}: AvatarPrimitive.RootProps & {
|
||||
size?: "default" | "sm" | "lg";
|
||||
} = $props();
|
||||
</script>
|
||||
|
||||
<AvatarPrimitive.Root
|
||||
bind:ref
|
||||
bind:loadingStatus
|
||||
data-slot="avatar"
|
||||
data-size={size}
|
||||
class={cn(
|
||||
"size-8 rounded-full after:rounded-full data-[size=lg]:size-10 data-[size=sm]:size-6 group/avatar relative flex shrink-0 select-none after:absolute after:inset-0 after:border after:border-border after:mix-blend-darken dark:after:mix-blend-lighten",
|
||||
className
|
||||
)}
|
||||
{...restProps}
|
||||
/>
|
||||
22
src/lib/components/ui/avatar/index.ts
Normal file
22
src/lib/components/ui/avatar/index.ts
Normal file
@@ -0,0 +1,22 @@
|
||||
import Badge from "./avatar-badge.svelte";
|
||||
import Fallback from "./avatar-fallback.svelte";
|
||||
import GroupCount from "./avatar-group-count.svelte";
|
||||
import Group from "./avatar-group.svelte";
|
||||
import Image from "./avatar-image.svelte";
|
||||
import Root from "./avatar.svelte";
|
||||
|
||||
export {
|
||||
Root,
|
||||
Image,
|
||||
Fallback,
|
||||
Badge,
|
||||
Group,
|
||||
GroupCount,
|
||||
//
|
||||
Root as Avatar,
|
||||
Image as AvatarImage,
|
||||
Fallback as AvatarFallback,
|
||||
Badge as AvatarBadge,
|
||||
Group as AvatarGroup,
|
||||
GroupCount as AvatarGroupCount,
|
||||
};
|
||||
49
src/lib/components/ui/badge/badge.svelte
Normal file
49
src/lib/components/ui/badge/badge.svelte
Normal file
@@ -0,0 +1,49 @@
|
||||
<script lang="ts" module>
|
||||
import { type VariantProps, tv } from "tailwind-variants";
|
||||
|
||||
export const badgeVariants = tv({
|
||||
base: "h-5 gap-1 rounded-4xl border border-transparent px-2 py-0.5 text-xs font-medium transition-all has-data-[icon=inline-end]:pr-1.5 has-data-[icon=inline-start]:pl-1.5 [&>svg]:size-3! group/badge inline-flex w-fit shrink-0 items-center justify-center overflow-hidden whitespace-nowrap focus-visible:border-ring focus-visible:ring-[3px] focus-visible:ring-ring/50 aria-invalid:border-destructive aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 [&>svg]:pointer-events-none",
|
||||
variants: {
|
||||
variant: {
|
||||
default: "bg-primary text-primary-foreground [a]:hover:bg-primary/80",
|
||||
secondary: "bg-secondary text-secondary-foreground [a]:hover:bg-secondary/80",
|
||||
destructive: "bg-destructive/10 [a]:hover:bg-destructive/20 focus-visible:ring-destructive/20 dark:focus-visible:ring-destructive/40 text-destructive dark:bg-destructive/20",
|
||||
outline: "border-border text-foreground [a]:hover:bg-muted [a]:hover:text-muted-foreground",
|
||||
ghost: "hover:bg-muted hover:text-muted-foreground dark:hover:bg-muted/50",
|
||||
link: "text-primary underline-offset-4 hover:underline",
|
||||
},
|
||||
},
|
||||
defaultVariants: {
|
||||
variant: "default",
|
||||
},
|
||||
});
|
||||
|
||||
export type BadgeVariant = VariantProps<typeof badgeVariants>["variant"];
|
||||
</script>
|
||||
|
||||
<script lang="ts">
|
||||
import { cn, type WithElementRef } from "#lib/utils.js";
|
||||
import type { HTMLAnchorAttributes } from "svelte/elements";
|
||||
|
||||
let {
|
||||
ref = $bindable(null),
|
||||
href,
|
||||
class: className,
|
||||
variant = "default",
|
||||
children,
|
||||
...restProps
|
||||
}: WithElementRef<HTMLAnchorAttributes> & {
|
||||
variant?: BadgeVariant;
|
||||
} = $props();
|
||||
</script>
|
||||
|
||||
<svelte:element
|
||||
this={href ? "a" : "span"}
|
||||
bind:this={ref}
|
||||
data-slot="badge"
|
||||
{href}
|
||||
class={cn(badgeVariants({ variant }), className)}
|
||||
{...restProps}
|
||||
>
|
||||
{@render children?.()}
|
||||
</svelte:element>
|
||||
2
src/lib/components/ui/badge/index.ts
Normal file
2
src/lib/components/ui/badge/index.ts
Normal file
@@ -0,0 +1,2 @@
|
||||
export { default as Badge } from "./badge.svelte";
|
||||
export { badgeVariants, type BadgeVariant } from "./badge.svelte";
|
||||
23
src/lib/components/ui/card/card-action.svelte
Normal file
23
src/lib/components/ui/card/card-action.svelte
Normal file
@@ -0,0 +1,23 @@
|
||||
<script lang="ts">
|
||||
import { cn, type WithElementRef } from "#lib/utils.js";
|
||||
import type { HTMLAttributes } from "svelte/elements";
|
||||
|
||||
let {
|
||||
ref = $bindable(null),
|
||||
class: className,
|
||||
children,
|
||||
...restProps
|
||||
}: WithElementRef<HTMLAttributes<HTMLDivElement>> = $props();
|
||||
</script>
|
||||
|
||||
<div
|
||||
bind:this={ref}
|
||||
data-slot="card-action"
|
||||
class={cn(
|
||||
"col-start-2 row-span-2 row-start-1 self-start justify-self-end",
|
||||
className
|
||||
)}
|
||||
{...restProps}
|
||||
>
|
||||
{@render children?.()}
|
||||
</div>
|
||||
20
src/lib/components/ui/card/card-content.svelte
Normal file
20
src/lib/components/ui/card/card-content.svelte
Normal file
@@ -0,0 +1,20 @@
|
||||
<script lang="ts">
|
||||
import { cn, type WithElementRef } from "#lib/utils.js";
|
||||
import type { HTMLAttributes } from "svelte/elements";
|
||||
|
||||
let {
|
||||
ref = $bindable(null),
|
||||
class: className,
|
||||
children,
|
||||
...restProps
|
||||
}: WithElementRef<HTMLAttributes<HTMLDivElement>> = $props();
|
||||
</script>
|
||||
|
||||
<div
|
||||
bind:this={ref}
|
||||
data-slot="card-content"
|
||||
class={cn("flex flex-col gap-3 px-(--card-spacing)", className)}
|
||||
{...restProps}
|
||||
>
|
||||
{@render children?.()}
|
||||
</div>
|
||||
20
src/lib/components/ui/card/card-description.svelte
Normal file
20
src/lib/components/ui/card/card-description.svelte
Normal file
@@ -0,0 +1,20 @@
|
||||
<script lang="ts">
|
||||
import { cn, type WithElementRef } from "#lib/utils.js";
|
||||
import type { HTMLAttributes } from "svelte/elements";
|
||||
|
||||
let {
|
||||
ref = $bindable(null),
|
||||
class: className,
|
||||
children,
|
||||
...restProps
|
||||
}: WithElementRef<HTMLAttributes<HTMLParagraphElement>> = $props();
|
||||
</script>
|
||||
|
||||
<p
|
||||
bind:this={ref}
|
||||
data-slot="card-description"
|
||||
class={cn("text-muted-foreground text-sm", className)}
|
||||
{...restProps}
|
||||
>
|
||||
{@render children?.()}
|
||||
</p>
|
||||
20
src/lib/components/ui/card/card-footer.svelte
Normal file
20
src/lib/components/ui/card/card-footer.svelte
Normal file
@@ -0,0 +1,20 @@
|
||||
<script lang="ts">
|
||||
import { cn, type WithElementRef } from "#lib/utils.js";
|
||||
import type { HTMLAttributes } from "svelte/elements";
|
||||
|
||||
let {
|
||||
ref = $bindable(null),
|
||||
class: className,
|
||||
children,
|
||||
...restProps
|
||||
}: WithElementRef<HTMLAttributes<HTMLDivElement>> = $props();
|
||||
</script>
|
||||
|
||||
<div
|
||||
bind:this={ref}
|
||||
data-slot="card-footer"
|
||||
class={cn("rounded-b-xl px-(--card-spacing) [.border-t]:pt-(--card-spacing) flex items-center", className)}
|
||||
{...restProps}
|
||||
>
|
||||
{@render children?.()}
|
||||
</div>
|
||||
23
src/lib/components/ui/card/card-header.svelte
Normal file
23
src/lib/components/ui/card/card-header.svelte
Normal file
@@ -0,0 +1,23 @@
|
||||
<script lang="ts">
|
||||
import { cn, type WithElementRef } from "#lib/utils.js";
|
||||
import type { HTMLAttributes } from "svelte/elements";
|
||||
|
||||
let {
|
||||
ref = $bindable(null),
|
||||
class: className,
|
||||
children,
|
||||
...restProps
|
||||
}: WithElementRef<HTMLAttributes<HTMLDivElement>> = $props();
|
||||
</script>
|
||||
|
||||
<div
|
||||
bind:this={ref}
|
||||
data-slot="card-header"
|
||||
class={cn(
|
||||
"gap-1 rounded-t-xl px-(--card-spacing) [.border-b]:pb-(--card-spacing) group/card-header @container/card-header grid auto-rows-min items-start has-data-[slot=card-action]:grid-cols-[1fr_auto] has-data-[slot=card-description]:grid-rows-[auto_auto]",
|
||||
className
|
||||
)}
|
||||
{...restProps}
|
||||
>
|
||||
{@render children?.()}
|
||||
</div>
|
||||
20
src/lib/components/ui/card/card-title.svelte
Normal file
20
src/lib/components/ui/card/card-title.svelte
Normal file
@@ -0,0 +1,20 @@
|
||||
<script lang="ts">
|
||||
import { cn, type WithElementRef } from "#lib/utils.js";
|
||||
import type { HTMLAttributes } from "svelte/elements";
|
||||
|
||||
let {
|
||||
ref = $bindable(null),
|
||||
class: className,
|
||||
children,
|
||||
...restProps
|
||||
}: WithElementRef<HTMLAttributes<HTMLDivElement>> = $props();
|
||||
</script>
|
||||
|
||||
<div
|
||||
bind:this={ref}
|
||||
data-slot="card-title"
|
||||
class={cn("text-base leading-normal font-medium group-data-[size=sm]/card:text-sm", className)}
|
||||
{...restProps}
|
||||
>
|
||||
{@render children?.()}
|
||||
</div>
|
||||
22
src/lib/components/ui/card/card.svelte
Normal file
22
src/lib/components/ui/card/card.svelte
Normal file
@@ -0,0 +1,22 @@
|
||||
<script lang="ts">
|
||||
import { cn, type WithElementRef } from "#lib/utils.js";
|
||||
import type { HTMLAttributes } from "svelte/elements";
|
||||
|
||||
let {
|
||||
ref = $bindable(null),
|
||||
class: className,
|
||||
children,
|
||||
size = "default",
|
||||
...restProps
|
||||
}: WithElementRef<HTMLAttributes<HTMLDivElement>> & { size?: "default" | "sm" } = $props();
|
||||
</script>
|
||||
|
||||
<div
|
||||
bind:this={ref}
|
||||
data-slot="card"
|
||||
data-size={size}
|
||||
class={cn("ring-foreground/10 bg-card text-card-foreground gap-(--card-spacing) overflow-hidden rounded-xl py-(--card-spacing) text-sm shadow-xs ring-1 [--card-spacing:--spacing(6)] has-[>img:first-child]:pt-0 data-[size=sm]:[--card-spacing:--spacing(4)] *:[img:first-child]:rounded-t-xl *:[img:last-child]:rounded-b-xl group/card flex flex-col", className)}
|
||||
{...restProps}
|
||||
>
|
||||
{@render children?.()}
|
||||
</div>
|
||||
25
src/lib/components/ui/card/index.ts
Normal file
25
src/lib/components/ui/card/index.ts
Normal file
@@ -0,0 +1,25 @@
|
||||
import Action from "./card-action.svelte";
|
||||
import Content from "./card-content.svelte";
|
||||
import Description from "./card-description.svelte";
|
||||
import Footer from "./card-footer.svelte";
|
||||
import Header from "./card-header.svelte";
|
||||
import Title from "./card-title.svelte";
|
||||
import Root from "./card.svelte";
|
||||
|
||||
export {
|
||||
Root,
|
||||
Content,
|
||||
Description,
|
||||
Footer,
|
||||
Header,
|
||||
Title,
|
||||
Action,
|
||||
//
|
||||
Root as Card,
|
||||
Content as CardContent,
|
||||
Description as CardDescription,
|
||||
Footer as CardFooter,
|
||||
Header as CardHeader,
|
||||
Title as CardTitle,
|
||||
Action as CardAction,
|
||||
};
|
||||
80
src/lib/components/ui/chart/chart-container.svelte
Normal file
80
src/lib/components/ui/chart/chart-container.svelte
Normal file
@@ -0,0 +1,80 @@
|
||||
<script lang="ts">
|
||||
import { cn, type WithElementRef } from "#lib/utils.js";
|
||||
import ChartStyle from "./chart-style.svelte";
|
||||
import { setChartContext, type ChartConfig } from "./chart-utils.js";
|
||||
import type { HTMLAttributes } from "svelte/elements";
|
||||
|
||||
const uid = $props.id();
|
||||
|
||||
let {
|
||||
ref = $bindable(null),
|
||||
id = uid,
|
||||
class: className,
|
||||
children,
|
||||
config,
|
||||
...restProps
|
||||
}: WithElementRef<HTMLAttributes<HTMLElement>> & {
|
||||
config: ChartConfig;
|
||||
} = $props();
|
||||
|
||||
const chartId = $derived(`chart-${id || uid.replace(/:/g, "")}`);
|
||||
|
||||
setChartContext({
|
||||
get config() {
|
||||
return config;
|
||||
},
|
||||
});
|
||||
</script>
|
||||
|
||||
<div
|
||||
bind:this={ref}
|
||||
data-chart={chartId}
|
||||
data-slot="chart"
|
||||
class={cn(
|
||||
"flex aspect-video justify-center overflow-visible text-xs",
|
||||
// Overrides
|
||||
//
|
||||
// Stroke around dots/marks when hovering
|
||||
"[&_.lc-highlight-point]:stroke-transparent",
|
||||
// override the default stroke color of lines
|
||||
"[&_.lc-line]:stroke-border/50",
|
||||
|
||||
// by default, layerchart shows a line intersecting the point when hovering, this hides that
|
||||
"[&_.lc-highlight-line]:stroke-0",
|
||||
|
||||
// by default, when you hover a point on a stacked series chart, it will drop the opacity
|
||||
// of the other series, this overrides that
|
||||
"[&_.lc-area-path]:opacity-100 [&_.lc-highlight-line]:opacity-100 [&_.lc-highlight-point]:opacity-100 [&_.lc-spline-path]:opacity-100 [&_.lc-text]:text-xs [&_.lc-text-svg]:overflow-visible",
|
||||
|
||||
// We don't want the little tick lines between the axis labels and the chart, so we remove
|
||||
// the stroke. The alternative is to manually disable `tickMarks` on the x/y axis of every
|
||||
// chart.
|
||||
"[&_.lc-axis-tick]:stroke-0",
|
||||
|
||||
// We don't want to display the rule on the x/y axis, as there is already going to be
|
||||
// a grid line there and rule ends up overlapping the marks because it is rendered after
|
||||
// the marks
|
||||
"[&_.lc-rule-x-line:not(.lc-grid-x-rule)]:stroke-0 [&_.lc-rule-y-line:not(.lc-grid-y-rule)]:stroke-0",
|
||||
"[&_.lc-grid-x-radial-circle]:stroke-border [&_.lc-grid-x-radial-line]:stroke-border",
|
||||
"[&_.lc-grid-y-radial-circle]:stroke-border [&_.lc-grid-y-radial-line]:stroke-border",
|
||||
|
||||
// Legend adjustments
|
||||
"[&_.lc-legend-swatch-button]:items-center [&_.lc-legend-swatch-button]:gap-1.5",
|
||||
"[&_.lc-legend-swatch-group]:items-center [&_.lc-legend-swatch-group]:gap-4",
|
||||
"[&_.lc-legend-swatch]:size-2.5 [&_.lc-legend-swatch]:rounded-[2px]",
|
||||
|
||||
// Labels
|
||||
"[&_.lc-labels-text:not([fill])]:fill-foreground [&_text]:stroke-transparent",
|
||||
|
||||
// Tick labels on th x/y axes
|
||||
"[&_.lc-axis-tick-label]:fill-muted-foreground [&_.lc-axis-tick-label]:font-normal",
|
||||
"[&_.lc-tooltip-rects-g]:fill-transparent",
|
||||
"[&_.lc-layout-svg-g]:fill-transparent",
|
||||
"[&_.lc-root-container]:w-full",
|
||||
className
|
||||
)}
|
||||
{...restProps}
|
||||
>
|
||||
<ChartStyle id={chartId} {config} />
|
||||
{@render children?.()}
|
||||
</div>
|
||||
37
src/lib/components/ui/chart/chart-style.svelte
Normal file
37
src/lib/components/ui/chart/chart-style.svelte
Normal file
@@ -0,0 +1,37 @@
|
||||
<script lang="ts">
|
||||
import { THEMES, type ChartConfig } from "./chart-utils.js";
|
||||
|
||||
let { id, config }: { id: string; config: ChartConfig } = $props();
|
||||
|
||||
const colorConfig = $derived(
|
||||
config ? Object.entries(config).filter(([, config]) => config.theme || config.color) : null
|
||||
);
|
||||
|
||||
const themeContents = $derived.by(() => {
|
||||
if (!colorConfig || !colorConfig.length) return;
|
||||
|
||||
const themeContents = [];
|
||||
for (const [_theme, prefix] of Object.entries(THEMES)) {
|
||||
let content = `${prefix} [data-chart=${id}] {\n`;
|
||||
const color = colorConfig.map(([key, itemConfig]) => {
|
||||
const theme = _theme as keyof typeof itemConfig.theme;
|
||||
const color = itemConfig.theme?.[theme] || itemConfig.color;
|
||||
return color ? `\t--color-${key}: ${color};` : null;
|
||||
});
|
||||
|
||||
content += color.join("\n") +"\n}";
|
||||
|
||||
themeContents.push(content);
|
||||
}
|
||||
|
||||
return themeContents.join("\n");
|
||||
});
|
||||
</script>
|
||||
|
||||
{#if themeContents}
|
||||
{#key id}
|
||||
<svelte:element this={"style"}>
|
||||
{themeContents}
|
||||
</svelte:element>
|
||||
{/key}
|
||||
{/if}
|
||||
180
src/lib/components/ui/chart/chart-tooltip.svelte
Normal file
180
src/lib/components/ui/chart/chart-tooltip.svelte
Normal file
@@ -0,0 +1,180 @@
|
||||
<script lang="ts">
|
||||
import { getChartContext, Tooltip as TooltipPrimitive } from "layerchart";
|
||||
import { cn, type WithElementRef, type WithoutChildren } from "#lib/utils.js";
|
||||
import { getPayloadConfigFromPayload, useChart, type TooltipPayload } from "./chart-utils.js";
|
||||
import type { Snippet } from "svelte";
|
||||
import type { HTMLAttributes } from "svelte/elements";
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
function defaultFormatter(value: any, _payload: TooltipPayload[]) {
|
||||
return `${value}`;
|
||||
}
|
||||
|
||||
let {
|
||||
ref = $bindable(null),
|
||||
class: className,
|
||||
hideLabel = false,
|
||||
indicator = "dot",
|
||||
hideIndicator = false,
|
||||
labelKey,
|
||||
label,
|
||||
labelFormatter = defaultFormatter,
|
||||
labelClassName,
|
||||
formatter,
|
||||
nameKey,
|
||||
color,
|
||||
...restProps
|
||||
}: WithoutChildren<WithElementRef<HTMLAttributes<HTMLDivElement>>> & {
|
||||
hideLabel?: boolean;
|
||||
label?: string;
|
||||
indicator?: "line" | "dot" | "dashed";
|
||||
nameKey?: string;
|
||||
labelKey?: string;
|
||||
hideIndicator?: boolean;
|
||||
labelClassName?: string;
|
||||
labelFormatter?: // eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
((value: any, payload: TooltipPayload[]) => string | number | Snippet) | null;
|
||||
formatter?: Snippet<
|
||||
[
|
||||
{
|
||||
value: unknown;
|
||||
name: string;
|
||||
item: TooltipPayload;
|
||||
index: number;
|
||||
payload: TooltipPayload[];
|
||||
},
|
||||
]
|
||||
>;
|
||||
} = $props();
|
||||
|
||||
const chart = useChart();
|
||||
const chartCtx = getChartContext();
|
||||
|
||||
// Filter to series with defined values (important for item-based charts like Pie/Arc
|
||||
// where only the hovered item has a value)
|
||||
const visibleSeries = $derived(
|
||||
chartCtx.tooltip.series.filter((s: TooltipPayload) => s.value !== undefined)
|
||||
);
|
||||
|
||||
const formattedLabel = $derived.by(() => {
|
||||
if (hideLabel || !visibleSeries?.length) return null;
|
||||
|
||||
const [item] = visibleSeries;
|
||||
const tooltipData = chartCtx.tooltip.data;
|
||||
|
||||
// Get the x-axis label value from the raw tooltip data (e.g. a Date or month string)
|
||||
const dataLabel = tooltipData != null ? chartCtx.x(tooltipData) : undefined;
|
||||
|
||||
const key = labelKey ?? item?.label ?? item?.key ?? "value";
|
||||
const itemConfig = getPayloadConfigFromPayload(
|
||||
chart.config,
|
||||
item,
|
||||
key,
|
||||
tooltipData as Record<string, unknown> | null
|
||||
);
|
||||
|
||||
let value: unknown;
|
||||
if (!labelKey && typeof label === "string") {
|
||||
value = chart.config[label as keyof typeof chart.config]?.label ?? label;
|
||||
} else if (labelKey) {
|
||||
value = itemConfig?.label ?? dataLabel;
|
||||
} else {
|
||||
value = dataLabel;
|
||||
}
|
||||
|
||||
if (value === undefined) return null;
|
||||
if (!labelFormatter) return value;
|
||||
return labelFormatter(value, visibleSeries);
|
||||
});
|
||||
|
||||
const nestLabel = $derived(visibleSeries.length === 1 && indicator !== "dot");
|
||||
</script>
|
||||
|
||||
{#snippet TooltipLabel()}
|
||||
{#if formattedLabel}
|
||||
<div class={cn("font-medium", labelClassName)}>
|
||||
{#if typeof formattedLabel === "function"}
|
||||
{@render formattedLabel()}
|
||||
{:else}
|
||||
{formattedLabel}
|
||||
{/if}
|
||||
</div>
|
||||
{/if}
|
||||
{/snippet}
|
||||
|
||||
<TooltipPrimitive.Root variant="none">
|
||||
<div
|
||||
bind:this={ref}
|
||||
class={cn(
|
||||
"grid min-w-32 items-start gap-1.5 rounded-lg border border-border/50 bg-background px-2.5 py-1.5 text-xs shadow-xl",
|
||||
className
|
||||
)}
|
||||
{...restProps}
|
||||
>
|
||||
{#if !nestLabel}
|
||||
{@render TooltipLabel()}
|
||||
{/if}
|
||||
<div class="grid gap-1.5">
|
||||
{#each visibleSeries as item, i (item.key + i)}
|
||||
{@const key = `${nameKey || item.key || item.label || "value"}`}
|
||||
{@const itemConfig = getPayloadConfigFromPayload(
|
||||
chart.config,
|
||||
item,
|
||||
key,
|
||||
chartCtx.tooltip.data
|
||||
)}
|
||||
{@const indicatorColor = color || item.config?.color || item.color}
|
||||
<div
|
||||
class={cn(
|
||||
"flex w-full flex-wrap items-stretch gap-2 [&>svg]:h-2.5 [&>svg]:w-2.5 [&>svg]:text-muted-foreground",
|
||||
indicator === "dot" && "items-center"
|
||||
)}
|
||||
>
|
||||
{#if formatter && item.value !== undefined && item.label}
|
||||
{@render formatter({
|
||||
value: item.value,
|
||||
name: item.label,
|
||||
item,
|
||||
index: i,
|
||||
payload: visibleSeries,
|
||||
})}
|
||||
{:else}
|
||||
{#if itemConfig?.icon}
|
||||
<itemConfig.icon />
|
||||
{:else if !hideIndicator}
|
||||
<div
|
||||
style="--color-bg: {indicatorColor}; --color-border: {indicatorColor};"
|
||||
class={cn("shrink-0 rounded-[2px] border-(--color-border) bg-(--color-bg)", {
|
||||
"h-2.5 w-2.5": indicator === "dot",
|
||||
"h-full w-1": indicator === "line",
|
||||
"w-0 border-[1.5px] border-dashed bg-transparent": indicator === "dashed",
|
||||
"my-0.5": nestLabel && indicator === "dashed",
|
||||
})}
|
||||
></div>
|
||||
{/if}
|
||||
<div
|
||||
class={cn(
|
||||
"flex flex-1 shrink-0 justify-between leading-none",
|
||||
nestLabel ? "items-end" : "items-center"
|
||||
)}
|
||||
>
|
||||
<div class="grid gap-1.5">
|
||||
{#if nestLabel}
|
||||
{@render TooltipLabel()}
|
||||
{/if}
|
||||
<span class="text-muted-foreground">
|
||||
{itemConfig?.label || item.label}
|
||||
</span>
|
||||
</div>
|
||||
{#if item.value !== undefined}
|
||||
<span class="font-mono font-medium text-foreground tabular-nums">
|
||||
{item.value.toLocaleString()}
|
||||
</span>
|
||||
{/if}
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
{/each}
|
||||
</div>
|
||||
</div>
|
||||
</TooltipPrimitive.Root>
|
||||
68
src/lib/components/ui/chart/chart-utils.ts
Normal file
68
src/lib/components/ui/chart/chart-utils.ts
Normal file
@@ -0,0 +1,68 @@
|
||||
import { getContext, setContext, type Component, type Snippet } from "svelte";
|
||||
import type { Tooltip } from "layerchart";
|
||||
|
||||
export const THEMES = { light: "", dark: ".dark" } as const;
|
||||
|
||||
export type ChartConfig = {
|
||||
[k in string]: {
|
||||
label?: string;
|
||||
icon?: Component;
|
||||
} & (
|
||||
| { color?: string; theme?: never }
|
||||
| { color?: never; theme: Record<keyof typeof THEMES, string> }
|
||||
);
|
||||
};
|
||||
|
||||
export type ExtractSnippetParams<T> = T extends Snippet<[infer P]> ? P : never;
|
||||
|
||||
export type TooltipPayload = Tooltip.TooltipSeries;
|
||||
|
||||
// Helper to extract item config from a payload.
|
||||
export function getPayloadConfigFromPayload(
|
||||
config: ChartConfig,
|
||||
payload: TooltipPayload,
|
||||
key: string,
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
data?: Record<string, any> | null
|
||||
) {
|
||||
if (typeof payload !== "object" || payload === null) return undefined;
|
||||
|
||||
const payloadConfig =
|
||||
"config" in payload && typeof payload.config === "object" && payload.config !== null
|
||||
? payload.config
|
||||
: undefined;
|
||||
|
||||
let configLabelKey: string = key;
|
||||
|
||||
if (payload.key === key) {
|
||||
configLabelKey = payload.key;
|
||||
} else if (payload.label === key) {
|
||||
configLabelKey = payload.label;
|
||||
} else if (key in payload && typeof payload[key as keyof typeof payload] === "string") {
|
||||
configLabelKey = payload[key as keyof typeof payload] as string;
|
||||
} else if (
|
||||
payloadConfig !== undefined &&
|
||||
key in payloadConfig &&
|
||||
typeof payloadConfig[key as keyof typeof payloadConfig] === "string"
|
||||
) {
|
||||
configLabelKey = payloadConfig[key as keyof typeof payloadConfig] as string;
|
||||
} else if (data != null && key in data && typeof data[key] === "string") {
|
||||
configLabelKey = data[key] as string;
|
||||
}
|
||||
|
||||
return configLabelKey in config ? config[configLabelKey] : config[key as keyof typeof config];
|
||||
}
|
||||
|
||||
type ChartContextValue = {
|
||||
config: ChartConfig;
|
||||
};
|
||||
|
||||
const chartContextKey = Symbol("chart-context");
|
||||
|
||||
export function setChartContext(value: ChartContextValue) {
|
||||
return setContext(chartContextKey, value);
|
||||
}
|
||||
|
||||
export function useChart() {
|
||||
return getContext<ChartContextValue>(chartContextKey);
|
||||
}
|
||||
6
src/lib/components/ui/chart/index.ts
Normal file
6
src/lib/components/ui/chart/index.ts
Normal file
@@ -0,0 +1,6 @@
|
||||
import ChartContainer from "./chart-container.svelte";
|
||||
import ChartTooltip from "./chart-tooltip.svelte";
|
||||
|
||||
export { getPayloadConfigFromPayload, type ChartConfig } from "./chart-utils.js";
|
||||
|
||||
export { ChartContainer, ChartTooltip, ChartContainer as Container, ChartTooltip as Tooltip };
|
||||
39
src/lib/components/ui/checkbox/checkbox.svelte
Normal file
39
src/lib/components/ui/checkbox/checkbox.svelte
Normal file
@@ -0,0 +1,39 @@
|
||||
<script lang="ts">
|
||||
import { Checkbox as CheckboxPrimitive } from "bits-ui";
|
||||
import CheckIcon from '@lucide/svelte/icons/check';
|
||||
import MinusIcon from '@lucide/svelte/icons/minus';
|
||||
import { cn, type WithoutChildrenOrChild } from "#lib/utils.js";
|
||||
|
||||
let {
|
||||
ref = $bindable(null),
|
||||
checked = $bindable(false),
|
||||
indeterminate = $bindable(false),
|
||||
class: className,
|
||||
...restProps
|
||||
}: WithoutChildrenOrChild<CheckboxPrimitive.RootProps> = $props();
|
||||
</script>
|
||||
|
||||
<CheckboxPrimitive.Root
|
||||
bind:ref
|
||||
data-slot="checkbox"
|
||||
class={cn(
|
||||
"border-input dark:bg-input/30 data-checked:bg-primary data-checked:text-primary-foreground dark:data-checked:bg-primary data-checked:border-primary aria-invalid:aria-checked:border-primary aria-invalid:border-destructive dark:aria-invalid:border-destructive/50 focus-visible:border-ring focus-visible:ring-ring/50 aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 flex size-4 items-center justify-center rounded-[4px] border shadow-xs transition-shadow group-has-disabled/field:opacity-50 focus-visible:ring-3 aria-invalid:ring-3 group-has-[:focus-visible]/field-label:ring-0 group-has-[:focus-visible]/field-label:not-data-checked:border-input group-has-[:focus-visible]/field-label:data-checked:border-primary peer relative shrink-0 outline-none after:absolute after:-inset-x-3 after:-inset-y-2 disabled:cursor-not-allowed disabled:opacity-50",
|
||||
className
|
||||
)}
|
||||
bind:checked
|
||||
bind:indeterminate
|
||||
{...restProps}
|
||||
>
|
||||
{#snippet children({ checked, indeterminate })}
|
||||
<div
|
||||
data-slot="checkbox-indicator"
|
||||
class="[&>svg]:size-3.5 grid place-content-center text-current transition-none"
|
||||
>
|
||||
{#if checked}
|
||||
<CheckIcon />
|
||||
{:else if indeterminate}
|
||||
<MinusIcon />
|
||||
{/if}
|
||||
</div>
|
||||
{/snippet}
|
||||
</CheckboxPrimitive.Root>
|
||||
6
src/lib/components/ui/checkbox/index.ts
Normal file
6
src/lib/components/ui/checkbox/index.ts
Normal file
@@ -0,0 +1,6 @@
|
||||
import Root from "./checkbox.svelte";
|
||||
export {
|
||||
Root,
|
||||
//
|
||||
Root as Checkbox,
|
||||
};
|
||||
7
src/lib/components/ui/drawer/drawer-close.svelte
Normal file
7
src/lib/components/ui/drawer/drawer-close.svelte
Normal file
@@ -0,0 +1,7 @@
|
||||
<script lang="ts">
|
||||
import { Drawer as DrawerPrimitive } from "vaul-svelte";
|
||||
|
||||
let { ref = $bindable(null), ...restProps }: DrawerPrimitive.CloseProps = $props();
|
||||
</script>
|
||||
|
||||
<DrawerPrimitive.Close bind:ref data-slot="drawer-close" {...restProps} />
|
||||
36
src/lib/components/ui/drawer/drawer-content.svelte
Normal file
36
src/lib/components/ui/drawer/drawer-content.svelte
Normal file
@@ -0,0 +1,36 @@
|
||||
<script lang="ts">
|
||||
import { Drawer as DrawerPrimitive } from "vaul-svelte";
|
||||
import { cn } from "#lib/utils.js";
|
||||
import type { WithoutChildrenOrChild } from "#lib/utils.js";
|
||||
import DrawerOverlay from "./drawer-overlay.svelte";
|
||||
import DrawerPortal from "./drawer-portal.svelte";
|
||||
import type { ComponentProps } from "svelte";
|
||||
|
||||
let {
|
||||
ref = $bindable(null),
|
||||
class: className,
|
||||
portalProps,
|
||||
children,
|
||||
...restProps
|
||||
}: DrawerPrimitive.ContentProps & {
|
||||
portalProps?: WithoutChildrenOrChild<ComponentProps<typeof DrawerPortal>>;
|
||||
} = $props();
|
||||
</script>
|
||||
|
||||
<DrawerPortal {...portalProps}>
|
||||
<DrawerOverlay />
|
||||
<DrawerPrimitive.Content
|
||||
bind:ref
|
||||
data-slot="drawer-content"
|
||||
class={cn(
|
||||
"bg-popover text-popover-foreground flex h-auto flex-col text-sm data-[vaul-drawer-direction=bottom]:rounded-t-xl data-[vaul-drawer-direction=bottom]:border-t data-[vaul-drawer-direction=left]:rounded-r-xl data-[vaul-drawer-direction=left]:border-r data-[vaul-drawer-direction=right]:rounded-l-xl data-[vaul-drawer-direction=right]:border-l data-[vaul-drawer-direction=top]:rounded-b-xl data-[vaul-drawer-direction=top]:border-b group/drawer-content fixed z-50 data-[vaul-drawer-direction=bottom]:inset-x-0 data-[vaul-drawer-direction=bottom]:bottom-0 data-[vaul-drawer-direction=bottom]:mt-24 data-[vaul-drawer-direction=bottom]:max-h-[80vh] data-[vaul-drawer-direction=left]:inset-y-0 data-[vaul-drawer-direction=left]:start-0 data-[vaul-drawer-direction=left]:w-3/4 data-[vaul-drawer-direction=right]:inset-y-0 data-[vaul-drawer-direction=right]:end-0 data-[vaul-drawer-direction=right]:w-3/4 data-[vaul-drawer-direction=top]:inset-x-0 data-[vaul-drawer-direction=top]:top-0 data-[vaul-drawer-direction=top]:mb-24 data-[vaul-drawer-direction=top]:max-h-[80vh] data-[vaul-drawer-direction=left]:sm:max-w-sm data-[vaul-drawer-direction=right]:sm:max-w-sm",
|
||||
className
|
||||
)}
|
||||
{...restProps}
|
||||
>
|
||||
<div
|
||||
class="bg-muted mx-auto mt-4 hidden h-1.5 w-[100px] shrink-0 rounded-full mx-auto hidden shrink-0 group-data-[vaul-drawer-direction=bottom]/drawer-content:block"
|
||||
></div>
|
||||
{@render children?.()}
|
||||
</DrawerPrimitive.Content>
|
||||
</DrawerPortal>
|
||||
17
src/lib/components/ui/drawer/drawer-description.svelte
Normal file
17
src/lib/components/ui/drawer/drawer-description.svelte
Normal file
@@ -0,0 +1,17 @@
|
||||
<script lang="ts">
|
||||
import { Drawer as DrawerPrimitive } from "vaul-svelte";
|
||||
import { cn } from "#lib/utils.js";
|
||||
|
||||
let {
|
||||
ref = $bindable(null),
|
||||
class: className,
|
||||
...restProps
|
||||
}: DrawerPrimitive.DescriptionProps = $props();
|
||||
</script>
|
||||
|
||||
<DrawerPrimitive.Description
|
||||
bind:ref
|
||||
data-slot="drawer-description"
|
||||
class={cn("text-muted-foreground text-sm", className)}
|
||||
{...restProps}
|
||||
/>
|
||||
20
src/lib/components/ui/drawer/drawer-footer.svelte
Normal file
20
src/lib/components/ui/drawer/drawer-footer.svelte
Normal file
@@ -0,0 +1,20 @@
|
||||
<script lang="ts">
|
||||
import { cn, type WithElementRef } from "#lib/utils.js";
|
||||
import type { HTMLAttributes } from "svelte/elements";
|
||||
|
||||
let {
|
||||
ref = $bindable(null),
|
||||
class: className,
|
||||
children,
|
||||
...restProps
|
||||
}: WithElementRef<HTMLAttributes<HTMLDivElement>> = $props();
|
||||
</script>
|
||||
|
||||
<div
|
||||
bind:this={ref}
|
||||
data-slot="drawer-footer"
|
||||
class={cn("gap-2 p-4 mt-auto flex flex-col", className)}
|
||||
{...restProps}
|
||||
>
|
||||
{@render children?.()}
|
||||
</div>
|
||||
23
src/lib/components/ui/drawer/drawer-header.svelte
Normal file
23
src/lib/components/ui/drawer/drawer-header.svelte
Normal file
@@ -0,0 +1,23 @@
|
||||
<script lang="ts">
|
||||
import { cn, type WithElementRef } from "#lib/utils.js";
|
||||
import type { HTMLAttributes } from "svelte/elements";
|
||||
|
||||
let {
|
||||
ref = $bindable(null),
|
||||
class: className,
|
||||
children,
|
||||
...restProps
|
||||
}: WithElementRef<HTMLAttributes<HTMLDivElement>> = $props();
|
||||
</script>
|
||||
|
||||
<div
|
||||
bind:this={ref}
|
||||
data-slot="drawer-header"
|
||||
class={cn(
|
||||
"gap-0.5 p-4 md:gap-1.5 md:text-left flex flex-col group-data-[vaul-drawer-direction=bottom]/drawer-content:text-center group-data-[vaul-drawer-direction=top]/drawer-content:text-center",
|
||||
className
|
||||
)}
|
||||
{...restProps}
|
||||
>
|
||||
{@render children?.()}
|
||||
</div>
|
||||
12
src/lib/components/ui/drawer/drawer-nested.svelte
Normal file
12
src/lib/components/ui/drawer/drawer-nested.svelte
Normal file
@@ -0,0 +1,12 @@
|
||||
<script lang="ts">
|
||||
import { Drawer as DrawerPrimitive } from "vaul-svelte";
|
||||
|
||||
let {
|
||||
shouldScaleBackground = true,
|
||||
open = $bindable(false),
|
||||
activeSnapPoint = $bindable(null),
|
||||
...restProps
|
||||
}: DrawerPrimitive.RootProps = $props();
|
||||
</script>
|
||||
|
||||
<DrawerPrimitive.NestedRoot {shouldScaleBackground} bind:open bind:activeSnapPoint {...restProps} />
|
||||
20
src/lib/components/ui/drawer/drawer-overlay.svelte
Normal file
20
src/lib/components/ui/drawer/drawer-overlay.svelte
Normal file
@@ -0,0 +1,20 @@
|
||||
<script lang="ts">
|
||||
import { Drawer as DrawerPrimitive } from "vaul-svelte";
|
||||
import { cn } from "#lib/utils.js";
|
||||
|
||||
let {
|
||||
ref = $bindable(null),
|
||||
class: className,
|
||||
...restProps
|
||||
}: DrawerPrimitive.OverlayProps = $props();
|
||||
</script>
|
||||
|
||||
<DrawerPrimitive.Overlay
|
||||
bind:ref
|
||||
data-slot="drawer-overlay"
|
||||
class={cn(
|
||||
"bg-black/10 supports-backdrop-filter:backdrop-blur-xs fixed inset-0 z-50 data-open:animate-in data-open:fade-in-0 data-closed:animate-out data-closed:fade-out-0",
|
||||
className
|
||||
)}
|
||||
{...restProps}
|
||||
/>
|
||||
7
src/lib/components/ui/drawer/drawer-portal.svelte
Normal file
7
src/lib/components/ui/drawer/drawer-portal.svelte
Normal file
@@ -0,0 +1,7 @@
|
||||
<script lang="ts">
|
||||
import { Drawer as DrawerPrimitive } from "vaul-svelte";
|
||||
|
||||
let { ...restProps }: DrawerPrimitive.PortalProps = $props();
|
||||
</script>
|
||||
|
||||
<DrawerPrimitive.Portal {...restProps} />
|
||||
17
src/lib/components/ui/drawer/drawer-title.svelte
Normal file
17
src/lib/components/ui/drawer/drawer-title.svelte
Normal file
@@ -0,0 +1,17 @@
|
||||
<script lang="ts">
|
||||
import { Drawer as DrawerPrimitive } from "vaul-svelte";
|
||||
import { cn } from "#lib/utils.js";
|
||||
|
||||
let {
|
||||
ref = $bindable(null),
|
||||
class: className,
|
||||
...restProps
|
||||
}: DrawerPrimitive.TitleProps = $props();
|
||||
</script>
|
||||
|
||||
<DrawerPrimitive.Title
|
||||
bind:ref
|
||||
data-slot="drawer-title"
|
||||
class={cn("text-foreground font-medium", className)}
|
||||
{...restProps}
|
||||
/>
|
||||
7
src/lib/components/ui/drawer/drawer-trigger.svelte
Normal file
7
src/lib/components/ui/drawer/drawer-trigger.svelte
Normal file
@@ -0,0 +1,7 @@
|
||||
<script lang="ts">
|
||||
import { Drawer as DrawerPrimitive } from "vaul-svelte";
|
||||
|
||||
let { ref = $bindable(null), ...restProps }: DrawerPrimitive.TriggerProps = $props();
|
||||
</script>
|
||||
|
||||
<DrawerPrimitive.Trigger bind:ref data-slot="drawer-trigger" {...restProps} />
|
||||
12
src/lib/components/ui/drawer/drawer.svelte
Normal file
12
src/lib/components/ui/drawer/drawer.svelte
Normal file
@@ -0,0 +1,12 @@
|
||||
<script lang="ts">
|
||||
import { Drawer as DrawerPrimitive } from "vaul-svelte";
|
||||
|
||||
let {
|
||||
shouldScaleBackground = true,
|
||||
open = $bindable(false),
|
||||
activeSnapPoint = $bindable(null),
|
||||
...restProps
|
||||
}: DrawerPrimitive.RootProps = $props();
|
||||
</script>
|
||||
|
||||
<DrawerPrimitive.Root {shouldScaleBackground} bind:open bind:activeSnapPoint {...restProps} />
|
||||
38
src/lib/components/ui/drawer/index.ts
Normal file
38
src/lib/components/ui/drawer/index.ts
Normal file
@@ -0,0 +1,38 @@
|
||||
import Close from "./drawer-close.svelte";
|
||||
import Content from "./drawer-content.svelte";
|
||||
import Description from "./drawer-description.svelte";
|
||||
import Footer from "./drawer-footer.svelte";
|
||||
import Header from "./drawer-header.svelte";
|
||||
import NestedRoot from "./drawer-nested.svelte";
|
||||
import Overlay from "./drawer-overlay.svelte";
|
||||
import Portal from "./drawer-portal.svelte";
|
||||
import Title from "./drawer-title.svelte";
|
||||
import Trigger from "./drawer-trigger.svelte";
|
||||
import Root from "./drawer.svelte";
|
||||
|
||||
export {
|
||||
Root,
|
||||
NestedRoot,
|
||||
Content,
|
||||
Description,
|
||||
Overlay,
|
||||
Footer,
|
||||
Header,
|
||||
Title,
|
||||
Trigger,
|
||||
Portal,
|
||||
Close,
|
||||
|
||||
//
|
||||
Root as Drawer,
|
||||
NestedRoot as DrawerNestedRoot,
|
||||
Content as DrawerContent,
|
||||
Description as DrawerDescription,
|
||||
Overlay as DrawerOverlay,
|
||||
Footer as DrawerFooter,
|
||||
Header as DrawerHeader,
|
||||
Title as DrawerTitle,
|
||||
Trigger as DrawerTrigger,
|
||||
Portal as DrawerPortal,
|
||||
Close as DrawerClose,
|
||||
};
|
||||
@@ -0,0 +1,16 @@
|
||||
<script lang="ts">
|
||||
import { DropdownMenu as DropdownMenuPrimitive } from "bits-ui";
|
||||
|
||||
let {
|
||||
ref = $bindable(null),
|
||||
value = $bindable([]),
|
||||
...restProps
|
||||
}: DropdownMenuPrimitive.CheckboxGroupProps = $props();
|
||||
</script>
|
||||
|
||||
<DropdownMenuPrimitive.CheckboxGroup
|
||||
bind:ref
|
||||
bind:value
|
||||
data-slot="dropdown-menu-checkbox-group"
|
||||
{...restProps}
|
||||
/>
|
||||
@@ -0,0 +1,46 @@
|
||||
<script lang="ts">
|
||||
import { DropdownMenu as DropdownMenuPrimitive } from "bits-ui";
|
||||
import MinusIcon from '@lucide/svelte/icons/minus';
|
||||
import CheckIcon from '@lucide/svelte/icons/check';
|
||||
import { cn, type WithoutChildrenOrChild } from "#lib/utils.js";
|
||||
import type { Snippet } from "svelte";
|
||||
|
||||
let {
|
||||
ref = $bindable(null),
|
||||
checked = $bindable(false),
|
||||
indeterminate = $bindable(false),
|
||||
class: className,
|
||||
children: childrenProp,
|
||||
closeOnSelect = false,
|
||||
...restProps
|
||||
}: WithoutChildrenOrChild<DropdownMenuPrimitive.CheckboxItemProps> & {
|
||||
children?: Snippet;
|
||||
} = $props();
|
||||
</script>
|
||||
|
||||
<DropdownMenuPrimitive.CheckboxItem
|
||||
bind:ref
|
||||
{closeOnSelect}
|
||||
bind:checked
|
||||
bind:indeterminate
|
||||
data-slot="dropdown-menu-checkbox-item"
|
||||
class={cn(
|
||||
"focus:bg-accent focus:text-accent-foreground focus:**:text-accent-foreground gap-2 rounded-sm py-1.5 pr-8 pl-2 text-sm data-inset:pl-8 [&_svg:not([class*='size-'])]:size-4 relative flex cursor-default items-center outline-hidden select-none data-disabled:pointer-events-none data-disabled:opacity-50 [&_svg]:pointer-events-none [&_svg]:shrink-0",
|
||||
className
|
||||
)}
|
||||
{...restProps}
|
||||
>
|
||||
{#snippet children({ checked, indeterminate })}
|
||||
<span
|
||||
class="absolute right-2 flex items-center justify-center pointer-events-none"
|
||||
data-slot="dropdown-menu-checkbox-item-indicator"
|
||||
>
|
||||
{#if indeterminate}
|
||||
<MinusIcon />
|
||||
{:else if checked}
|
||||
<CheckIcon />
|
||||
{/if}
|
||||
</span>
|
||||
{@render childrenProp?.()}
|
||||
{/snippet}
|
||||
</DropdownMenuPrimitive.CheckboxItem>
|
||||
@@ -0,0 +1,31 @@
|
||||
<script lang="ts">
|
||||
import { DropdownMenu as DropdownMenuPrimitive } from "bits-ui";
|
||||
import { cn, type WithoutChildrenOrChild } from "#lib/utils.js";
|
||||
import DropdownMenuPortal from "./dropdown-menu-portal.svelte";
|
||||
import type { ComponentProps } from "svelte";
|
||||
|
||||
let {
|
||||
ref = $bindable(null),
|
||||
sideOffset = 4,
|
||||
align = "start",
|
||||
portalProps,
|
||||
class: className,
|
||||
...restProps
|
||||
}: DropdownMenuPrimitive.ContentProps & {
|
||||
portalProps?: WithoutChildrenOrChild<ComponentProps<typeof DropdownMenuPortal>>;
|
||||
} = $props();
|
||||
</script>
|
||||
|
||||
<DropdownMenuPortal {...portalProps}>
|
||||
<DropdownMenuPrimitive.Content
|
||||
bind:ref
|
||||
data-slot="dropdown-menu-content"
|
||||
{sideOffset}
|
||||
{align}
|
||||
class={cn(
|
||||
"data-open:animate-in data-closed:animate-out data-closed:fade-out-0 data-open:fade-in-0 data-closed:zoom-out-95 data-open:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 ring-foreground/10 bg-popover text-popover-foreground min-w-32 rounded-md p-1 shadow-md ring-1 duration-100 z-50 max-h-(--bits-dropdown-menu-content-available-height) w-(--bits-dropdown-menu-anchor-width) origin-(--bits-dropdown-menu-content-transform-origin) overflow-x-hidden overflow-y-auto data-closed:overflow-hidden",
|
||||
className
|
||||
)}
|
||||
{...restProps}
|
||||
/>
|
||||
</DropdownMenuPortal>
|
||||
@@ -0,0 +1,22 @@
|
||||
<script lang="ts">
|
||||
import { DropdownMenu as DropdownMenuPrimitive } from "bits-ui";
|
||||
import { cn } from "#lib/utils.js";
|
||||
import type { ComponentProps } from "svelte";
|
||||
|
||||
let {
|
||||
ref = $bindable(null),
|
||||
class: className,
|
||||
inset,
|
||||
...restProps
|
||||
}: ComponentProps<typeof DropdownMenuPrimitive.GroupHeading> & {
|
||||
inset?: boolean;
|
||||
} = $props();
|
||||
</script>
|
||||
|
||||
<DropdownMenuPrimitive.GroupHeading
|
||||
bind:ref
|
||||
data-slot="dropdown-menu-group-heading"
|
||||
data-inset={inset}
|
||||
class={cn("text-muted-foreground px-2 py-1.5 text-xs font-medium data-inset:pl-8", className)}
|
||||
{...restProps}
|
||||
/>
|
||||
@@ -0,0 +1,7 @@
|
||||
<script lang="ts">
|
||||
import { DropdownMenu as DropdownMenuPrimitive } from "bits-ui";
|
||||
|
||||
let { ref = $bindable(null), ...restProps }: DropdownMenuPrimitive.GroupProps = $props();
|
||||
</script>
|
||||
|
||||
<DropdownMenuPrimitive.Group bind:ref data-slot="dropdown-menu-group" {...restProps} />
|
||||
@@ -0,0 +1,27 @@
|
||||
<script lang="ts">
|
||||
import { DropdownMenu as DropdownMenuPrimitive } from "bits-ui";
|
||||
import { cn } from "#lib/utils.js";
|
||||
|
||||
let {
|
||||
ref = $bindable(null),
|
||||
class: className,
|
||||
inset,
|
||||
variant = "default",
|
||||
...restProps
|
||||
}: DropdownMenuPrimitive.ItemProps & {
|
||||
inset?: boolean;
|
||||
variant?: "default" | "destructive";
|
||||
} = $props();
|
||||
</script>
|
||||
|
||||
<DropdownMenuPrimitive.Item
|
||||
bind:ref
|
||||
data-slot="dropdown-menu-item"
|
||||
data-inset={inset}
|
||||
data-variant={variant}
|
||||
class={cn(
|
||||
"focus:bg-accent focus:text-accent-foreground data-[variant=destructive]:text-destructive data-[variant=destructive]:focus:bg-destructive/10 dark:data-[variant=destructive]:focus:bg-destructive/20 data-[variant=destructive]:focus:text-destructive data-[variant=destructive]:*:[svg]:text-destructive not-data-[variant=destructive]:focus:**:text-accent-foreground gap-2 rounded-sm px-2 py-1.5 text-sm data-inset:pl-8 [&_svg:not([class*='size-'])]:size-4 group/dropdown-menu-item relative flex cursor-default items-center outline-hidden select-none data-disabled:pointer-events-none data-disabled:opacity-50 [&_svg]:pointer-events-none [&_svg]:shrink-0",
|
||||
className
|
||||
)}
|
||||
{...restProps}
|
||||
/>
|
||||
@@ -0,0 +1,24 @@
|
||||
<script lang="ts">
|
||||
import { cn, type WithElementRef } from "#lib/utils.js";
|
||||
import type { HTMLAttributes } from "svelte/elements";
|
||||
|
||||
let {
|
||||
ref = $bindable(null),
|
||||
class: className,
|
||||
inset,
|
||||
children,
|
||||
...restProps
|
||||
}: WithElementRef<HTMLAttributes<HTMLDivElement>> & {
|
||||
inset?: boolean;
|
||||
} = $props();
|
||||
</script>
|
||||
|
||||
<div
|
||||
bind:this={ref}
|
||||
data-slot="dropdown-menu-label"
|
||||
data-inset={inset}
|
||||
class={cn("text-muted-foreground px-2 py-1.5 text-xs font-medium data-inset:pl-8", className)}
|
||||
{...restProps}
|
||||
>
|
||||
{@render children?.()}
|
||||
</div>
|
||||
@@ -0,0 +1,7 @@
|
||||
<script lang="ts">
|
||||
import { DropdownMenu as DropdownMenuPrimitive } from "bits-ui";
|
||||
|
||||
let { ...restProps }: DropdownMenuPrimitive.PortalProps = $props();
|
||||
</script>
|
||||
|
||||
<DropdownMenuPrimitive.Portal {...restProps} />
|
||||
@@ -0,0 +1,16 @@
|
||||
<script lang="ts">
|
||||
import { DropdownMenu as DropdownMenuPrimitive } from "bits-ui";
|
||||
|
||||
let {
|
||||
ref = $bindable(null),
|
||||
value = $bindable(),
|
||||
...restProps
|
||||
}: DropdownMenuPrimitive.RadioGroupProps = $props();
|
||||
</script>
|
||||
|
||||
<DropdownMenuPrimitive.RadioGroup
|
||||
bind:ref
|
||||
bind:value
|
||||
data-slot="dropdown-menu-radio-group"
|
||||
{...restProps}
|
||||
/>
|
||||
@@ -0,0 +1,36 @@
|
||||
<script lang="ts">
|
||||
import { DropdownMenu as DropdownMenuPrimitive } from "bits-ui";
|
||||
import CheckIcon from '@lucide/svelte/icons/check';
|
||||
import { cn, type WithoutChild } from "#lib/utils.js";
|
||||
|
||||
let {
|
||||
ref = $bindable(null),
|
||||
class: className,
|
||||
children: childrenProp,
|
||||
closeOnSelect = false,
|
||||
...restProps
|
||||
}: WithoutChild<DropdownMenuPrimitive.RadioItemProps> = $props();
|
||||
</script>
|
||||
|
||||
<DropdownMenuPrimitive.RadioItem
|
||||
bind:ref
|
||||
{closeOnSelect}
|
||||
data-slot="dropdown-menu-radio-item"
|
||||
class={cn(
|
||||
"focus:bg-accent focus:text-accent-foreground focus:**:text-accent-foreground gap-2 rounded-sm py-1.5 pr-8 pl-2 text-sm data-inset:pl-8 [&_svg:not([class*='size-'])]:size-4 relative flex cursor-default items-center outline-hidden select-none data-disabled:pointer-events-none data-disabled:opacity-50 [&_svg]:pointer-events-none [&_svg]:shrink-0",
|
||||
className
|
||||
)}
|
||||
{...restProps}
|
||||
>
|
||||
{#snippet children({ checked })}
|
||||
<span
|
||||
class="absolute right-2 flex items-center justify-center pointer-events-none"
|
||||
data-slot="dropdown-menu-radio-item-indicator"
|
||||
>
|
||||
{#if checked}
|
||||
<CheckIcon />
|
||||
{/if}
|
||||
</span>
|
||||
{@render childrenProp?.({ checked })}
|
||||
{/snippet}
|
||||
</DropdownMenuPrimitive.RadioItem>
|
||||
@@ -0,0 +1,17 @@
|
||||
<script lang="ts">
|
||||
import { DropdownMenu as DropdownMenuPrimitive } from "bits-ui";
|
||||
import { cn } from "#lib/utils.js";
|
||||
|
||||
let {
|
||||
ref = $bindable(null),
|
||||
class: className,
|
||||
...restProps
|
||||
}: DropdownMenuPrimitive.SeparatorProps = $props();
|
||||
</script>
|
||||
|
||||
<DropdownMenuPrimitive.Separator
|
||||
bind:ref
|
||||
data-slot="dropdown-menu-separator"
|
||||
class={cn("bg-border -mx-1 my-1 h-px", className)}
|
||||
{...restProps}
|
||||
/>
|
||||
@@ -0,0 +1,20 @@
|
||||
<script lang="ts">
|
||||
import { cn, type WithElementRef } from "#lib/utils.js";
|
||||
import type { HTMLAttributes } from "svelte/elements";
|
||||
|
||||
let {
|
||||
ref = $bindable(null),
|
||||
class: className,
|
||||
children,
|
||||
...restProps
|
||||
}: WithElementRef<HTMLAttributes<HTMLSpanElement>> = $props();
|
||||
</script>
|
||||
|
||||
<span
|
||||
bind:this={ref}
|
||||
data-slot="dropdown-menu-shortcut"
|
||||
class={cn("text-muted-foreground group-focus/dropdown-menu-item:text-accent-foreground ml-auto text-xs tracking-widest", className)}
|
||||
{...restProps}
|
||||
>
|
||||
{@render children?.()}
|
||||
</span>
|
||||
@@ -0,0 +1,31 @@
|
||||
<script lang="ts">
|
||||
import { DropdownMenu as DropdownMenuPrimitive } from "bits-ui";
|
||||
import { cn, type WithoutChildrenOrChild } from "#lib/utils.js";
|
||||
import DropdownMenuPortal from "./dropdown-menu-portal.svelte";
|
||||
import type { ComponentProps } from "svelte";
|
||||
|
||||
let {
|
||||
ref = $bindable(null),
|
||||
class: className,
|
||||
align = "start",
|
||||
alignOffset = -3,
|
||||
portalProps,
|
||||
...restProps
|
||||
}: DropdownMenuPrimitive.SubContentProps & {
|
||||
portalProps?: WithoutChildrenOrChild<ComponentProps<typeof DropdownMenuPortal>>;
|
||||
} = $props();
|
||||
</script>
|
||||
|
||||
<DropdownMenuPortal {...portalProps}>
|
||||
<DropdownMenuPrimitive.SubContent
|
||||
bind:ref
|
||||
data-slot="dropdown-menu-sub-content"
|
||||
{align}
|
||||
{alignOffset}
|
||||
class={cn(
|
||||
"data-open:animate-in data-closed:animate-out data-closed:fade-out-0 data-open:fade-in-0 data-closed:zoom-out-95 data-open:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 ring-foreground/10 bg-popover text-popover-foreground min-w-[96px] rounded-md p-1 shadow-lg ring-1 duration-100 z-50 origin-(--bits-dropdown-menu-content-transform-origin) overflow-hidden",
|
||||
className
|
||||
)}
|
||||
{...restProps}
|
||||
/>
|
||||
</DropdownMenuPortal>
|
||||
@@ -0,0 +1,29 @@
|
||||
<script lang="ts">
|
||||
import { DropdownMenu as DropdownMenuPrimitive } from "bits-ui";
|
||||
import ChevronRightIcon from '@lucide/svelte/icons/chevron-right';
|
||||
import { cn } from "#lib/utils.js";
|
||||
|
||||
let {
|
||||
ref = $bindable(null),
|
||||
class: className,
|
||||
inset,
|
||||
children,
|
||||
...restProps
|
||||
}: DropdownMenuPrimitive.SubTriggerProps & {
|
||||
inset?: boolean;
|
||||
} = $props();
|
||||
</script>
|
||||
|
||||
<DropdownMenuPrimitive.SubTrigger
|
||||
bind:ref
|
||||
data-slot="dropdown-menu-sub-trigger"
|
||||
data-inset={inset}
|
||||
class={cn(
|
||||
"focus:bg-accent focus:text-accent-foreground data-open:bg-accent data-open:text-accent-foreground not-data-[variant=destructive]:focus:**:text-accent-foreground gap-2 rounded-sm px-2 py-1.5 text-sm data-inset:pl-8 [&_svg:not([class*='size-'])]:size-4 flex cursor-default items-center outline-hidden select-none [&_svg]:pointer-events-none [&_svg]:shrink-0",
|
||||
className
|
||||
)}
|
||||
{...restProps}
|
||||
>
|
||||
{@render children?.()}
|
||||
<ChevronRightIcon class="cn-rtl-flip ml-auto" />
|
||||
</DropdownMenuPrimitive.SubTrigger>
|
||||
@@ -0,0 +1,7 @@
|
||||
<script lang="ts">
|
||||
import { DropdownMenu as DropdownMenuPrimitive } from "bits-ui";
|
||||
|
||||
let { open = $bindable(false), ...restProps }: DropdownMenuPrimitive.SubProps = $props();
|
||||
</script>
|
||||
|
||||
<DropdownMenuPrimitive.Sub bind:open {...restProps} />
|
||||
@@ -0,0 +1,7 @@
|
||||
<script lang="ts">
|
||||
import { DropdownMenu as DropdownMenuPrimitive } from "bits-ui";
|
||||
|
||||
let { ref = $bindable(null), ...restProps }: DropdownMenuPrimitive.TriggerProps = $props();
|
||||
</script>
|
||||
|
||||
<DropdownMenuPrimitive.Trigger bind:ref data-slot="dropdown-menu-trigger" {...restProps} />
|
||||
7
src/lib/components/ui/dropdown-menu/dropdown-menu.svelte
Normal file
7
src/lib/components/ui/dropdown-menu/dropdown-menu.svelte
Normal file
@@ -0,0 +1,7 @@
|
||||
<script lang="ts">
|
||||
import { DropdownMenu as DropdownMenuPrimitive } from "bits-ui";
|
||||
|
||||
let { open = $bindable(false), ...restProps }: DropdownMenuPrimitive.RootProps = $props();
|
||||
</script>
|
||||
|
||||
<DropdownMenuPrimitive.Root bind:open {...restProps} />
|
||||
54
src/lib/components/ui/dropdown-menu/index.ts
Normal file
54
src/lib/components/ui/dropdown-menu/index.ts
Normal file
@@ -0,0 +1,54 @@
|
||||
import CheckboxGroup from "./dropdown-menu-checkbox-group.svelte";
|
||||
import CheckboxItem from "./dropdown-menu-checkbox-item.svelte";
|
||||
import Content from "./dropdown-menu-content.svelte";
|
||||
import GroupHeading from "./dropdown-menu-group-heading.svelte";
|
||||
import Group from "./dropdown-menu-group.svelte";
|
||||
import Item from "./dropdown-menu-item.svelte";
|
||||
import Label from "./dropdown-menu-label.svelte";
|
||||
import Portal from "./dropdown-menu-portal.svelte";
|
||||
import RadioGroup from "./dropdown-menu-radio-group.svelte";
|
||||
import RadioItem from "./dropdown-menu-radio-item.svelte";
|
||||
import Separator from "./dropdown-menu-separator.svelte";
|
||||
import Shortcut from "./dropdown-menu-shortcut.svelte";
|
||||
import SubContent from "./dropdown-menu-sub-content.svelte";
|
||||
import SubTrigger from "./dropdown-menu-sub-trigger.svelte";
|
||||
import Sub from "./dropdown-menu-sub.svelte";
|
||||
import Trigger from "./dropdown-menu-trigger.svelte";
|
||||
import Root from "./dropdown-menu.svelte";
|
||||
|
||||
export {
|
||||
CheckboxGroup,
|
||||
CheckboxItem,
|
||||
Content,
|
||||
Portal,
|
||||
Root as DropdownMenu,
|
||||
CheckboxGroup as DropdownMenuCheckboxGroup,
|
||||
CheckboxItem as DropdownMenuCheckboxItem,
|
||||
Content as DropdownMenuContent,
|
||||
Portal as DropdownMenuPortal,
|
||||
Group as DropdownMenuGroup,
|
||||
Item as DropdownMenuItem,
|
||||
Label as DropdownMenuLabel,
|
||||
RadioGroup as DropdownMenuRadioGroup,
|
||||
RadioItem as DropdownMenuRadioItem,
|
||||
Separator as DropdownMenuSeparator,
|
||||
Shortcut as DropdownMenuShortcut,
|
||||
Sub as DropdownMenuSub,
|
||||
SubContent as DropdownMenuSubContent,
|
||||
SubTrigger as DropdownMenuSubTrigger,
|
||||
Trigger as DropdownMenuTrigger,
|
||||
GroupHeading as DropdownMenuGroupHeading,
|
||||
Group,
|
||||
GroupHeading,
|
||||
Item,
|
||||
Label,
|
||||
RadioGroup,
|
||||
RadioItem,
|
||||
Root,
|
||||
Separator,
|
||||
Shortcut,
|
||||
Sub,
|
||||
SubContent,
|
||||
SubTrigger,
|
||||
Trigger,
|
||||
};
|
||||
7
src/lib/components/ui/input/index.ts
Normal file
7
src/lib/components/ui/input/index.ts
Normal file
@@ -0,0 +1,7 @@
|
||||
import Root from "./input.svelte";
|
||||
|
||||
export {
|
||||
Root,
|
||||
//
|
||||
Root as Input,
|
||||
};
|
||||
48
src/lib/components/ui/input/input.svelte
Normal file
48
src/lib/components/ui/input/input.svelte
Normal file
@@ -0,0 +1,48 @@
|
||||
<script lang="ts">
|
||||
import { cn, type WithElementRef } from "#lib/utils.js";
|
||||
import type { HTMLInputAttributes, HTMLInputTypeAttribute } from "svelte/elements";
|
||||
|
||||
type InputType = Exclude<HTMLInputTypeAttribute, "file">;
|
||||
|
||||
type Props = WithElementRef<
|
||||
Omit<HTMLInputAttributes, "type"> &
|
||||
({ type: "file"; files?: FileList } | { type?: InputType; files?: undefined })
|
||||
>;
|
||||
|
||||
let {
|
||||
ref = $bindable(null),
|
||||
value = $bindable(),
|
||||
type,
|
||||
files = $bindable(),
|
||||
class: className,
|
||||
"data-slot": dataSlot = "input",
|
||||
...restProps
|
||||
}: Props = $props();
|
||||
</script>
|
||||
|
||||
{#if type === "file"}
|
||||
<input
|
||||
bind:this={ref}
|
||||
data-slot={dataSlot}
|
||||
class={cn(
|
||||
"dark:bg-input/30 border-input focus-visible:border-ring focus-visible:ring-ring/50 aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive dark:aria-invalid:border-destructive/50 h-9 rounded-md border bg-transparent px-2.5 py-1 text-base shadow-xs transition-[color,box-shadow] file:h-7 file:text-sm file:font-medium focus-visible:ring-3 aria-invalid:ring-3 md:text-sm w-full min-w-0 outline-none file:inline-flex file:border-0 file:bg-transparent file:text-foreground placeholder:text-muted-foreground disabled:pointer-events-none disabled:cursor-not-allowed disabled:opacity-50",
|
||||
className
|
||||
)}
|
||||
type="file"
|
||||
bind:files
|
||||
bind:value
|
||||
{...restProps}
|
||||
/>
|
||||
{:else}
|
||||
<input
|
||||
bind:this={ref}
|
||||
data-slot={dataSlot}
|
||||
class={cn(
|
||||
"dark:bg-input/30 border-input focus-visible:border-ring focus-visible:ring-ring/50 aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive dark:aria-invalid:border-destructive/50 h-9 rounded-md border bg-transparent px-2.5 py-1 text-base shadow-xs transition-[color,box-shadow] file:h-7 file:text-sm file:font-medium focus-visible:ring-3 aria-invalid:ring-3 md:text-sm w-full min-w-0 outline-none file:inline-flex file:border-0 file:bg-transparent file:text-foreground placeholder:text-muted-foreground disabled:pointer-events-none disabled:cursor-not-allowed disabled:opacity-50",
|
||||
className
|
||||
)}
|
||||
{type}
|
||||
bind:value
|
||||
{...restProps}
|
||||
/>
|
||||
{/if}
|
||||
7
src/lib/components/ui/label/index.ts
Normal file
7
src/lib/components/ui/label/index.ts
Normal file
@@ -0,0 +1,7 @@
|
||||
import Root from "./label.svelte";
|
||||
|
||||
export {
|
||||
Root,
|
||||
//
|
||||
Root as Label,
|
||||
};
|
||||
20
src/lib/components/ui/label/label.svelte
Normal file
20
src/lib/components/ui/label/label.svelte
Normal file
@@ -0,0 +1,20 @@
|
||||
<script lang="ts">
|
||||
import { Label as LabelPrimitive } from "bits-ui";
|
||||
import { cn } from "#lib/utils.js";
|
||||
|
||||
let {
|
||||
ref = $bindable(null),
|
||||
class: className,
|
||||
...restProps
|
||||
}: LabelPrimitive.RootProps = $props();
|
||||
</script>
|
||||
|
||||
<LabelPrimitive.Root
|
||||
bind:ref
|
||||
data-slot="label"
|
||||
class={cn(
|
||||
"gap-2 text-sm leading-none font-medium group-data-[disabled=true]:opacity-50 peer-disabled:opacity-50 flex items-center select-none group-data-[disabled=true]:pointer-events-none peer-disabled:cursor-not-allowed",
|
||||
className
|
||||
)}
|
||||
{...restProps}
|
||||
/>
|
||||
40
src/lib/components/ui/select/index.ts
Normal file
40
src/lib/components/ui/select/index.ts
Normal file
@@ -0,0 +1,40 @@
|
||||
import Content from "./select-content.svelte";
|
||||
import GroupHeading from "./select-group-heading.svelte";
|
||||
import Group from "./select-group.svelte";
|
||||
import Item from "./select-item.svelte";
|
||||
import Label from "./select-label.svelte";
|
||||
import Portal from "./select-portal.svelte";
|
||||
import ScrollDownButton from "./select-scroll-down-button.svelte";
|
||||
import ScrollUpButton from "./select-scroll-up-button.svelte";
|
||||
import Separator from "./select-separator.svelte";
|
||||
import Trigger from "./select-trigger.svelte";
|
||||
import Value from "./select-value.svelte";
|
||||
import Root from "./select.svelte";
|
||||
|
||||
export {
|
||||
Root,
|
||||
Group,
|
||||
Label,
|
||||
Item,
|
||||
Content,
|
||||
Trigger,
|
||||
Separator,
|
||||
ScrollDownButton,
|
||||
ScrollUpButton,
|
||||
GroupHeading,
|
||||
Portal,
|
||||
Value,
|
||||
//
|
||||
Root as Select,
|
||||
Group as SelectGroup,
|
||||
Label as SelectLabel,
|
||||
Item as SelectItem,
|
||||
Content as SelectContent,
|
||||
Trigger as SelectTrigger,
|
||||
Separator as SelectSeparator,
|
||||
ScrollDownButton as SelectScrollDownButton,
|
||||
ScrollUpButton as SelectScrollUpButton,
|
||||
GroupHeading as SelectGroupHeading,
|
||||
Portal as SelectPortal,
|
||||
Value as SelectValue,
|
||||
};
|
||||
45
src/lib/components/ui/select/select-content.svelte
Normal file
45
src/lib/components/ui/select/select-content.svelte
Normal file
@@ -0,0 +1,45 @@
|
||||
<script lang="ts">
|
||||
import { Select as SelectPrimitive } from "bits-ui";
|
||||
import { cn, type WithoutChild } from "#lib/utils.js";
|
||||
import type { WithoutChildrenOrChild } from "#lib/utils.js";
|
||||
import SelectPortal from "./select-portal.svelte";
|
||||
import SelectScrollDownButton from "./select-scroll-down-button.svelte";
|
||||
import SelectScrollUpButton from "./select-scroll-up-button.svelte";
|
||||
import type { ComponentProps } from "svelte";
|
||||
|
||||
let {
|
||||
ref = $bindable(null),
|
||||
class: className,
|
||||
sideOffset = 4,
|
||||
portalProps,
|
||||
children,
|
||||
preventScroll = true,
|
||||
...restProps
|
||||
}: WithoutChild<SelectPrimitive.ContentProps> & {
|
||||
portalProps?: WithoutChildrenOrChild<ComponentProps<typeof SelectPortal>>;
|
||||
} = $props();
|
||||
</script>
|
||||
|
||||
<SelectPortal {...portalProps}>
|
||||
<SelectPrimitive.Content
|
||||
bind:ref
|
||||
{sideOffset}
|
||||
{preventScroll}
|
||||
data-slot="select-content"
|
||||
class={cn(
|
||||
"bg-popover text-popover-foreground data-open:animate-in data-closed:animate-out data-closed:fade-out-0 data-open:fade-in-0 data-closed:zoom-out-95 data-open:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 ring-foreground/10 min-w-36 rounded-md shadow-md ring-1 duration-100 relative z-50 max-h-(--bits-select-content-available-height) origin-(--bits-select-content-transform-origin) overflow-x-hidden overflow-y-auto",
|
||||
className
|
||||
)}
|
||||
{...restProps}
|
||||
>
|
||||
<SelectScrollUpButton />
|
||||
<SelectPrimitive.Viewport
|
||||
class={cn(
|
||||
"h-(--bits-select-anchor-height) w-full min-w-(--bits-select-anchor-width) scroll-my-1"
|
||||
)}
|
||||
>
|
||||
{@render children?.()}
|
||||
</SelectPrimitive.Viewport>
|
||||
<SelectScrollDownButton />
|
||||
</SelectPrimitive.Content>
|
||||
</SelectPortal>
|
||||
21
src/lib/components/ui/select/select-group-heading.svelte
Normal file
21
src/lib/components/ui/select/select-group-heading.svelte
Normal file
@@ -0,0 +1,21 @@
|
||||
<script lang="ts">
|
||||
import { Select as SelectPrimitive } from "bits-ui";
|
||||
import { cn } from "#lib/utils.js";
|
||||
import type { ComponentProps } from "svelte";
|
||||
|
||||
let {
|
||||
ref = $bindable(null),
|
||||
class: className,
|
||||
children,
|
||||
...restProps
|
||||
}: ComponentProps<typeof SelectPrimitive.GroupHeading> = $props();
|
||||
</script>
|
||||
|
||||
<SelectPrimitive.GroupHeading
|
||||
bind:ref
|
||||
data-slot="select-group-heading"
|
||||
class={cn("text-muted-foreground px-2 py-1.5 text-xs", className)}
|
||||
{...restProps}
|
||||
>
|
||||
{@render children?.()}
|
||||
</SelectPrimitive.GroupHeading>
|
||||
17
src/lib/components/ui/select/select-group.svelte
Normal file
17
src/lib/components/ui/select/select-group.svelte
Normal file
@@ -0,0 +1,17 @@
|
||||
<script lang="ts">
|
||||
import { Select as SelectPrimitive } from "bits-ui";
|
||||
import { cn } from "#lib/utils.js";
|
||||
|
||||
let {
|
||||
ref = $bindable(null),
|
||||
class: className,
|
||||
...restProps
|
||||
}: SelectPrimitive.GroupProps = $props();
|
||||
</script>
|
||||
|
||||
<SelectPrimitive.Group
|
||||
bind:ref
|
||||
data-slot="select-group"
|
||||
class={cn("scroll-my-1 p-1", className)}
|
||||
{...restProps}
|
||||
/>
|
||||
42
src/lib/components/ui/select/select-item.svelte
Normal file
42
src/lib/components/ui/select/select-item.svelte
Normal file
@@ -0,0 +1,42 @@
|
||||
<script lang="ts">
|
||||
import { Select as SelectPrimitive } from "bits-ui";
|
||||
import CheckIcon from '@lucide/svelte/icons/check';
|
||||
import { cn, type WithoutChild } from "#lib/utils.js";
|
||||
|
||||
let {
|
||||
ref = $bindable(null),
|
||||
class: className,
|
||||
value,
|
||||
label,
|
||||
children: childrenProp,
|
||||
...restProps
|
||||
}: WithoutChild<SelectPrimitive.ItemProps> = $props();
|
||||
</script>
|
||||
|
||||
<SelectPrimitive.Item
|
||||
bind:ref
|
||||
{value}
|
||||
{label}
|
||||
data-slot="select-item"
|
||||
class={cn(
|
||||
"focus:bg-accent focus:text-accent-foreground not-data-[variant=destructive]:focus:**:text-accent-foreground gap-2 rounded-sm py-1.5 pr-8 pl-2 text-sm [&_svg:not([class*='size-'])]:size-4 *:[span]:last:flex *:[span]:last:items-center *:[span]:last:gap-2 relative flex w-full cursor-default items-center outline-hidden select-none data-disabled:pointer-events-none data-disabled:opacity-50 [&_svg]:pointer-events-none [&_svg]:shrink-0",
|
||||
"data-highlighted:bg-accent data-highlighted:text-accent-foreground",
|
||||
className
|
||||
)}
|
||||
{...restProps}
|
||||
>
|
||||
{#snippet children({ selected, highlighted })}
|
||||
<span class="pointer-events-none absolute right-2 flex size-4 items-center justify-center">
|
||||
{#if selected}
|
||||
<CheckIcon class="pointer-events-none" />
|
||||
{/if}
|
||||
</span>
|
||||
<span class="flex flex-1 gap-2 shrink-0 whitespace-nowrap">
|
||||
{#if childrenProp}
|
||||
{@render childrenProp({ selected, highlighted })}
|
||||
{:else}
|
||||
{label || value}
|
||||
{/if}
|
||||
</span>
|
||||
{/snippet}
|
||||
</SelectPrimitive.Item>
|
||||
20
src/lib/components/ui/select/select-label.svelte
Normal file
20
src/lib/components/ui/select/select-label.svelte
Normal file
@@ -0,0 +1,20 @@
|
||||
<script lang="ts">
|
||||
import { cn, type WithElementRef } from "#lib/utils.js";
|
||||
import type { HTMLAttributes } from "svelte/elements";
|
||||
|
||||
let {
|
||||
ref = $bindable(null),
|
||||
class: className,
|
||||
children,
|
||||
...restProps
|
||||
}: WithElementRef<HTMLAttributes<HTMLDivElement>> & {} = $props();
|
||||
</script>
|
||||
|
||||
<div
|
||||
bind:this={ref}
|
||||
data-slot="select-label"
|
||||
class={cn("text-muted-foreground px-2 py-1.5 text-xs", className)}
|
||||
{...restProps}
|
||||
>
|
||||
{@render children?.()}
|
||||
</div>
|
||||
7
src/lib/components/ui/select/select-portal.svelte
Normal file
7
src/lib/components/ui/select/select-portal.svelte
Normal file
@@ -0,0 +1,7 @@
|
||||
<script lang="ts">
|
||||
import { Select as SelectPrimitive } from "bits-ui";
|
||||
|
||||
let { ...restProps }: SelectPrimitive.PortalProps = $props();
|
||||
</script>
|
||||
|
||||
<SelectPrimitive.Portal {...restProps} />
|
||||
@@ -0,0 +1,20 @@
|
||||
<script lang="ts">
|
||||
import { Select as SelectPrimitive } from "bits-ui";
|
||||
import ChevronDownIcon from '@lucide/svelte/icons/chevron-down';
|
||||
import { cn, type WithoutChildrenOrChild } from "#lib/utils.js";
|
||||
|
||||
let {
|
||||
ref = $bindable(null),
|
||||
class: className,
|
||||
...restProps
|
||||
}: WithoutChildrenOrChild<SelectPrimitive.ScrollDownButtonProps> = $props();
|
||||
</script>
|
||||
|
||||
<SelectPrimitive.ScrollDownButton
|
||||
bind:ref
|
||||
data-slot="select-scroll-down-button"
|
||||
class={cn("bg-popover z-10 flex cursor-default items-center justify-center py-1 [&_svg:not([class*='size-'])]:size-4", className)}
|
||||
{...restProps}
|
||||
>
|
||||
<ChevronDownIcon />
|
||||
</SelectPrimitive.ScrollDownButton>
|
||||
20
src/lib/components/ui/select/select-scroll-up-button.svelte
Normal file
20
src/lib/components/ui/select/select-scroll-up-button.svelte
Normal file
@@ -0,0 +1,20 @@
|
||||
<script lang="ts">
|
||||
import { Select as SelectPrimitive } from "bits-ui";
|
||||
import ChevronUpIcon from '@lucide/svelte/icons/chevron-up';
|
||||
import { cn, type WithoutChildrenOrChild } from "#lib/utils.js";
|
||||
|
||||
let {
|
||||
ref = $bindable(null),
|
||||
class: className,
|
||||
...restProps
|
||||
}: WithoutChildrenOrChild<SelectPrimitive.ScrollUpButtonProps> = $props();
|
||||
</script>
|
||||
|
||||
<SelectPrimitive.ScrollUpButton
|
||||
bind:ref
|
||||
data-slot="select-scroll-up-button"
|
||||
class={cn("bg-popover z-10 flex cursor-default items-center justify-center py-1 [&_svg:not([class*='size-'])]:size-4", className)}
|
||||
{...restProps}
|
||||
>
|
||||
<ChevronUpIcon />
|
||||
</SelectPrimitive.ScrollUpButton>
|
||||
18
src/lib/components/ui/select/select-separator.svelte
Normal file
18
src/lib/components/ui/select/select-separator.svelte
Normal file
@@ -0,0 +1,18 @@
|
||||
<script lang="ts">
|
||||
import { Separator } from "#lib/components/ui/separator/index.js";
|
||||
import { cn } from "#lib/utils.js";
|
||||
import type { Separator as SeparatorPrimitive } from "bits-ui";
|
||||
|
||||
let {
|
||||
ref = $bindable(null),
|
||||
class: className,
|
||||
...restProps
|
||||
}: SeparatorPrimitive.RootProps = $props();
|
||||
</script>
|
||||
|
||||
<Separator
|
||||
bind:ref
|
||||
data-slot="select-separator"
|
||||
class={cn("bg-border -mx-1 my-1 h-px pointer-events-none", className)}
|
||||
{...restProps}
|
||||
/>
|
||||
29
src/lib/components/ui/select/select-trigger.svelte
Normal file
29
src/lib/components/ui/select/select-trigger.svelte
Normal file
@@ -0,0 +1,29 @@
|
||||
<script lang="ts">
|
||||
import { Select as SelectPrimitive } from "bits-ui";
|
||||
import ChevronDownIcon from '@lucide/svelte/icons/chevron-down';
|
||||
import { cn, type WithoutChild } from "#lib/utils.js";
|
||||
|
||||
let {
|
||||
ref = $bindable(null),
|
||||
class: className,
|
||||
children,
|
||||
size = "default",
|
||||
...restProps
|
||||
}: WithoutChild<SelectPrimitive.TriggerProps> & {
|
||||
size?: "sm" | "default";
|
||||
} = $props();
|
||||
</script>
|
||||
|
||||
<SelectPrimitive.Trigger
|
||||
bind:ref
|
||||
data-slot="select-trigger"
|
||||
data-size={size}
|
||||
class={cn(
|
||||
"border-input data-placeholder:text-muted-foreground dark:bg-input/30 dark:hover:bg-input/50 focus-visible:border-ring focus-visible:ring-ring/50 aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive dark:aria-invalid:border-destructive/50 gap-1.5 rounded-md border bg-transparent py-2 pr-2 pl-2.5 text-sm shadow-xs transition-[color,box-shadow] focus-visible:ring-3 aria-invalid:ring-3 data-[size=default]:h-9 data-[size=sm]:h-8 *:data-[slot=select-value]:flex *:data-[slot=select-value]:gap-1.5 [&_svg:not([class*='size-'])]:size-4 flex w-fit items-center justify-between whitespace-nowrap outline-none disabled:cursor-not-allowed disabled:opacity-50 *:data-[slot=select-value]:line-clamp-1 *:data-[slot=select-value]:flex *:data-[slot=select-value]:items-center [&_svg]:pointer-events-none [&_svg]:shrink-0",
|
||||
className
|
||||
)}
|
||||
{...restProps}
|
||||
>
|
||||
{@render children?.()}
|
||||
<ChevronDownIcon class="text-muted-foreground size-4 pointer-events-none" />
|
||||
</SelectPrimitive.Trigger>
|
||||
7
src/lib/components/ui/select/select-value.svelte
Normal file
7
src/lib/components/ui/select/select-value.svelte
Normal file
@@ -0,0 +1,7 @@
|
||||
<script lang="ts">
|
||||
import { Select as SelectPrimitive } from "bits-ui";
|
||||
|
||||
let { ref = $bindable(null), ...restProps }: SelectPrimitive.ValueProps = $props();
|
||||
</script>
|
||||
|
||||
<SelectPrimitive.Value bind:ref data-slot="select-value" {...restProps} />
|
||||
11
src/lib/components/ui/select/select.svelte
Normal file
11
src/lib/components/ui/select/select.svelte
Normal file
@@ -0,0 +1,11 @@
|
||||
<script lang="ts">
|
||||
import { Select as SelectPrimitive } from "bits-ui";
|
||||
|
||||
let {
|
||||
open = $bindable(false),
|
||||
value = $bindable(),
|
||||
...restProps
|
||||
}: SelectPrimitive.RootProps = $props();
|
||||
</script>
|
||||
|
||||
<SelectPrimitive.Root bind:open bind:value={value as never} {...restProps} />
|
||||
7
src/lib/components/ui/separator/index.ts
Normal file
7
src/lib/components/ui/separator/index.ts
Normal file
@@ -0,0 +1,7 @@
|
||||
import Root from "./separator.svelte";
|
||||
|
||||
export {
|
||||
Root,
|
||||
//
|
||||
Root as Separator,
|
||||
};
|
||||
21
src/lib/components/ui/separator/separator.svelte
Normal file
21
src/lib/components/ui/separator/separator.svelte
Normal file
@@ -0,0 +1,21 @@
|
||||
<script lang="ts">
|
||||
import { Separator as SeparatorPrimitive } from "bits-ui";
|
||||
import { cn } from "#lib/utils.js";
|
||||
|
||||
let {
|
||||
ref = $bindable(null),
|
||||
class: className,
|
||||
"data-slot": dataSlot = "separator",
|
||||
...restProps
|
||||
}: SeparatorPrimitive.RootProps = $props();
|
||||
</script>
|
||||
|
||||
<SeparatorPrimitive.Root
|
||||
bind:ref
|
||||
data-slot={dataSlot}
|
||||
class={cn(
|
||||
"shrink-0 bg-border data-horizontal:h-px data-horizontal:w-full data-vertical:w-px data-vertical:self-stretch",
|
||||
className
|
||||
)}
|
||||
{...restProps}
|
||||
/>
|
||||
34
src/lib/components/ui/sheet/index.ts
Normal file
34
src/lib/components/ui/sheet/index.ts
Normal file
@@ -0,0 +1,34 @@
|
||||
import Close from "./sheet-close.svelte";
|
||||
import Content from "./sheet-content.svelte";
|
||||
import Description from "./sheet-description.svelte";
|
||||
import Footer from "./sheet-footer.svelte";
|
||||
import Header from "./sheet-header.svelte";
|
||||
import Overlay from "./sheet-overlay.svelte";
|
||||
import Portal from "./sheet-portal.svelte";
|
||||
import Title from "./sheet-title.svelte";
|
||||
import Trigger from "./sheet-trigger.svelte";
|
||||
import Root from "./sheet.svelte";
|
||||
|
||||
export {
|
||||
Root,
|
||||
Close,
|
||||
Trigger,
|
||||
Portal,
|
||||
Overlay,
|
||||
Content,
|
||||
Header,
|
||||
Footer,
|
||||
Title,
|
||||
Description,
|
||||
//
|
||||
Root as Sheet,
|
||||
Close as SheetClose,
|
||||
Trigger as SheetTrigger,
|
||||
Portal as SheetPortal,
|
||||
Overlay as SheetOverlay,
|
||||
Content as SheetContent,
|
||||
Header as SheetHeader,
|
||||
Footer as SheetFooter,
|
||||
Title as SheetTitle,
|
||||
Description as SheetDescription,
|
||||
};
|
||||
7
src/lib/components/ui/sheet/sheet-close.svelte
Normal file
7
src/lib/components/ui/sheet/sheet-close.svelte
Normal file
@@ -0,0 +1,7 @@
|
||||
<script lang="ts">
|
||||
import { Dialog as SheetPrimitive } from "bits-ui";
|
||||
|
||||
let { ref = $bindable(null), ...restProps }: SheetPrimitive.CloseProps = $props();
|
||||
</script>
|
||||
|
||||
<SheetPrimitive.Close bind:ref data-slot="sheet-close" {...restProps} />
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user