mirror of
https://github.com/BradNut/AdelieStack
synced 2025-09-08 17:40:20 +00:00
Initial commit
This commit is contained in:
commit
2c2f33ea68
336 changed files with 16988 additions and 0 deletions
15
.env.example
Normal file
15
.env.example
Normal file
|
|
@ -0,0 +1,15 @@
|
||||||
|
# API
|
||||||
|
ORIGIN=http://localhost:5173
|
||||||
|
|
||||||
|
# Database
|
||||||
|
DATABASE_URL=postgresql://postgres:postgres@localhost:5432/postgres
|
||||||
|
|
||||||
|
# Redis
|
||||||
|
REDIS_URL=redis://localhost:6379
|
||||||
|
|
||||||
|
# Storage
|
||||||
|
PUBLIC_IMAGE_URI=http://localhost:9000/dev
|
||||||
|
STORAGE_BUCKET=dev
|
||||||
|
STORAGE_URL=http://localhost:9000
|
||||||
|
STORAGE_ACCESS_KEY=user
|
||||||
|
STORAGE_SECRET_KEY=password
|
||||||
13
.eslintignore
Normal file
13
.eslintignore
Normal file
|
|
@ -0,0 +1,13 @@
|
||||||
|
.DS_Store
|
||||||
|
node_modules
|
||||||
|
/build
|
||||||
|
/.svelte-kit
|
||||||
|
/package
|
||||||
|
.env
|
||||||
|
.env.*
|
||||||
|
!.env.example
|
||||||
|
|
||||||
|
# Ignore files for PNPM, NPM and YARN
|
||||||
|
pnpm-lock.yaml
|
||||||
|
package-lock.json
|
||||||
|
yarn.lock
|
||||||
10
.gitignore
vendored
Normal file
10
.gitignore
vendored
Normal file
|
|
@ -0,0 +1,10 @@
|
||||||
|
.DS_Store
|
||||||
|
node_modules
|
||||||
|
/build
|
||||||
|
/.svelte-kit
|
||||||
|
/package
|
||||||
|
.env
|
||||||
|
.env.*
|
||||||
|
!.env.example
|
||||||
|
vite.config.js.timestamp-*
|
||||||
|
vite.config.ts.timestamp-*
|
||||||
1
.node-version
Normal file
1
.node-version
Normal file
|
|
@ -0,0 +1 @@
|
||||||
|
22.1.0
|
||||||
1
.npmrc
Normal file
1
.npmrc
Normal file
|
|
@ -0,0 +1 @@
|
||||||
|
engine-strict=true
|
||||||
4
.prettierignore
Normal file
4
.prettierignore
Normal file
|
|
@ -0,0 +1,4 @@
|
||||||
|
# Ignore files for PNPM, NPM and YARN
|
||||||
|
pnpm-lock.yaml
|
||||||
|
package-lock.json
|
||||||
|
yarn.lock
|
||||||
8
.prettierrc
Normal file
8
.prettierrc
Normal file
|
|
@ -0,0 +1,8 @@
|
||||||
|
{
|
||||||
|
"useTabs": true,
|
||||||
|
"singleQuote": true,
|
||||||
|
"trailingComma": "none",
|
||||||
|
"printWidth": 100,
|
||||||
|
"plugins": ["prettier-plugin-svelte", "prettier-plugin-tailwindcss"],
|
||||||
|
"overrides": [{ "files": "*.svelte", "options": { "parser": "svelte" } }]
|
||||||
|
}
|
||||||
3
.vscode/settings.json
vendored
Normal file
3
.vscode/settings.json
vendored
Normal file
|
|
@ -0,0 +1,3 @@
|
||||||
|
{
|
||||||
|
"vitest.disableWorkspaceWarning": true
|
||||||
|
}
|
||||||
3
Dockerfile.minio
Normal file
3
Dockerfile.minio
Normal file
|
|
@ -0,0 +1,3 @@
|
||||||
|
FROM nginx:stable-alpine-slim
|
||||||
|
COPY minio-console.conf.template /etc/nginx/templates/
|
||||||
|
RUN rm /etc/nginx/conf.d/default.conf /docker-entrypoint.d/10-listen-on-ipv6-by-default.sh
|
||||||
111
README.md
Normal file
111
README.md
Normal file
|
|
@ -0,0 +1,111 @@
|
||||||
|
# Sveltekit - Taro Stack
|
||||||
|
|
||||||
|
## ❗ Important
|
||||||
|
|
||||||
|
If you forked this repository before May 27th, you'll want to view commit `653e2c2`. There was an issue with the Hono context not correctly parsing routes.
|
||||||
|
|
||||||
|
## ❔ What
|
||||||
|
|
||||||
|
A scalable, testable, extensible, template for Sveltekit.
|
||||||
|
|
||||||
|
Sveltekit is awesome, but sometimes you need a bit more capability in the backend than what frameworks like
|
||||||
|
NextJS & Sveltekit can deliver.
|
||||||
|
|
||||||
|
To remedy this, I've attatched a fully fletched backend to run on the Sveltekit process and forward all API requests to it and paired it with some architectural patterns.
|
||||||
|
|
||||||
|
`/api/[...slugs]`
|
||||||
|
|
||||||
|
```ts
|
||||||
|
import app from '$lib/api';
|
||||||
|
import type { RequestHandler } from '@sveltejs/kit';
|
||||||
|
|
||||||
|
export const GET: RequestHandler = ({ request }) => app.fetch(request);
|
||||||
|
export const PUT: RequestHandler = ({ request }) => app.fetch(request);
|
||||||
|
export const DELETE: RequestHandler = ({ request }) => app.fetch(request);
|
||||||
|
export const POST: RequestHandler = ({ request }) => app.fetch(request);
|
||||||
|
```
|
||||||
|
|
||||||
|
## Local Setup
|
||||||
|
|
||||||
|
1. Make sure Docker is running
|
||||||
|
2. Copy the `.env.example` file and rename to `.env`
|
||||||
|
3. `pnpm install`
|
||||||
|
4. `pnpm initialize`(this will start the docker-compose and run the initial database migration.)
|
||||||
|
5. `pnpm dev`
|
||||||
|
|
||||||
|
No additional setup is required, zero api keys, zero services to signup for, zero cost.
|
||||||
|
|
||||||
|
## How to Use
|
||||||
|
|
||||||
|
This is **not** supposed to serve as an all batteries included ["production boilerplate"](https://github.com/ixartz/Next-js-Boilerplate) with 200 useless sponsored features that get in your way. Templates that do this are ANYTHING but "production" and "quick start".
|
||||||
|
|
||||||
|
This is stack is designed to be library agnostic. The philosophy here is to boostrap the concrete, repetitive, and time consuming tasks that every application will need reguardless of what you're building.
|
||||||
|
|
||||||
|
**So - fork this repo, add your favorite libraries, and build out your own "more opinionated" personal template tailored to you**!
|
||||||
|
|
||||||
|
## Features
|
||||||
|
|
||||||
|
- 🟢 Full E2E typesafety
|
||||||
|
- 🟢 RPC Client for API Requests
|
||||||
|
- 🟢 Custom Fetch Wrapper
|
||||||
|
- 🟢 Deployment Template
|
||||||
|
- 🟠 Authentication
|
||||||
|
- 🟢 Email/Passkey
|
||||||
|
- 🔴 OAuth
|
||||||
|
- 🟢 Email Update/Verifiaction
|
||||||
|
- 🟢 Rate limiter
|
||||||
|
|
||||||
|
## Technologies
|
||||||
|
|
||||||
|
I'm mostly unopinionated or what technology or libaries someone uses. Wanna [uwu'ify](https://www.npmjs.com/package/owoifyx) your entire fucking app? Go for it.
|
||||||
|
|
||||||
|
That being said, there are some libaries that embody my philosophies of building software more than others,
|
||||||
|
|
||||||
|
- [Lucia](https://lucia-auth.com): Hits the right level of abstraction for me. Hand me the tools to build a secure authentication system and let me implement it to suite my needs
|
||||||
|
- [Drizzle](https://orm.drizzle.team/) - Drizzle advertises itself as an ORM but I think its deceptive. Its a query builder with a migration client. Everytime I've used an ORM, I find myself fighting it for sometimes the simplist of use cases. Drizzle just gives you type-safety while querying SQL in a native fashion. Learn SQL, not ORMs.
|
||||||
|
- [Hono](https://hono.dev/): Fast, lightweight, and built on **web standards**; meaning it can run anywhere you're Sveltekit app can. It's essentially a better, newer, and ironically more stable Express.JS. This provides us an extreemely good foundation to cleanly build ontop of without having to teardown first. It has a zod adapter for validating DTO's which can be shared with the frontend too.
|
||||||
|
- [Sveltekit](https://kit.svelte.dev/): After trying Vue, React, Next, and pretty much every frontend framework in the JS ecosystem, its safe to say I vastly prefer Svelte and its priority of building on **web standards**.
|
||||||
|
|
||||||
|
## Architecture
|
||||||
|
|
||||||
|
There are a few popular architectures for structuring backends. Technical, Onion, DDD, VSA, and the list goes on. I almost always choose
|
||||||
|
to start with Technical and let the project naturally evolve into one of the others as complexity grows.
|
||||||
|
|
||||||
|
### Backend Folder Structure
|
||||||
|
|
||||||
|
- **controllers** - Responsible for routing requests
|
||||||
|
|
||||||
|
- **services** - Responsible for handling business logic.
|
||||||
|
|
||||||
|
- **repositories** - Responsible for retrieving and
|
||||||
|
storing data.
|
||||||
|
|
||||||
|
- **infrastructure** - Handles the implementation of external services or backend operations.
|
||||||
|
|
||||||
|
- **middleware** - Middlware our request router is responsible for handling.
|
||||||
|
|
||||||
|
- **providers** - Injectable services
|
||||||
|
|
||||||
|
- **dtos** - Data Transfer Objects (DTOs) are used to define the shape of data that is passed.
|
||||||
|
|
||||||
|
- **common** - Anything commonly shared throughout the backend
|
||||||
|
|
||||||
|
### File Naming
|
||||||
|
|
||||||
|
You might notice how each file in the backend is postfixed with its architectural type(e.g. `iam.service.ts`). This allows
|
||||||
|
us to easily reorganize the folder structure to suite a different architecture pattern if the domain becomes more complex.
|
||||||
|
|
||||||
|
For example, if you want to group folders by domain(DDD), you simply drag and drop all related files to that folder.
|
||||||
|
|
||||||
|
```
|
||||||
|
└── events/
|
||||||
|
├── events.controller.ts
|
||||||
|
├── events.service.ts
|
||||||
|
└── events.repository.ts
|
||||||
|
```
|
||||||
|
|
||||||
|
## Testing
|
||||||
|
|
||||||
|
Testing probably isn't first and foremost when creating an app. Thats fine. You should not be spending time writing tests if your app is mutable.
|
||||||
|
|
||||||
|
BUT, a good stack should be **testable** when the time to solidify a codebase arrives. I created this stack with that pinciple in mind. I've provided a examples of how to write these tests under `authentication.service.test.ts` or `users.service.test.ts`
|
||||||
14
components.json
Normal file
14
components.json
Normal file
|
|
@ -0,0 +1,14 @@
|
||||||
|
{
|
||||||
|
"$schema": "https://shadcn-svelte.com/schema.json",
|
||||||
|
"style": "default",
|
||||||
|
"tailwind": {
|
||||||
|
"config": "tailwind.config.ts",
|
||||||
|
"css": "src/app.css",
|
||||||
|
"baseColor": "slate"
|
||||||
|
},
|
||||||
|
"aliases": {
|
||||||
|
"components": "$lib/components",
|
||||||
|
"utils": "$lib/utils/ui"
|
||||||
|
},
|
||||||
|
"typescript": true
|
||||||
|
}
|
||||||
58
docker-compose.yaml
Normal file
58
docker-compose.yaml
Normal file
|
|
@ -0,0 +1,58 @@
|
||||||
|
version: '3.8'
|
||||||
|
services:
|
||||||
|
postgres:
|
||||||
|
image: postgres:latest
|
||||||
|
container_name: tofu_postgres
|
||||||
|
environment:
|
||||||
|
POSTGRES_USER: postgres
|
||||||
|
POSTGRES_PASSWORD: postgres
|
||||||
|
POSTGRES_DB: postgres
|
||||||
|
ports:
|
||||||
|
- '5432:5432'
|
||||||
|
volumes:
|
||||||
|
- postgres_data:/data
|
||||||
|
redis:
|
||||||
|
image: redis:latest
|
||||||
|
container_name: tofu_redis
|
||||||
|
ports:
|
||||||
|
- '6379:6379'
|
||||||
|
volumes:
|
||||||
|
- redis_data:/data
|
||||||
|
minio:
|
||||||
|
image: docker.io/bitnami/minio
|
||||||
|
container_name: tofu_minio
|
||||||
|
ports:
|
||||||
|
- '9000:9000'
|
||||||
|
- '9001:9001'
|
||||||
|
networks:
|
||||||
|
- minionetwork
|
||||||
|
volumes:
|
||||||
|
- 'minio_data:/data'
|
||||||
|
environment:
|
||||||
|
- MINIO_ROOT_USER=user
|
||||||
|
- MINIO_ROOT_PASSWORD=password
|
||||||
|
- MINIO_DEFAULT_BUCKETS=dev
|
||||||
|
mailpit:
|
||||||
|
image: axllent/mailpit
|
||||||
|
container_name: tofu_mailpit
|
||||||
|
volumes:
|
||||||
|
- mailpit_data:/data
|
||||||
|
ports:
|
||||||
|
- 8025:8025
|
||||||
|
- 1025:1025
|
||||||
|
environment:
|
||||||
|
MP_MAX_MESSAGES: 5000
|
||||||
|
MP_DATABASE: /data/mailpit.db
|
||||||
|
MP_SMTP_AUTH_ACCEPT_ANY: 1
|
||||||
|
MP_SMTP_AUTH_ALLOW_INSECURE: 1
|
||||||
|
|
||||||
|
volumes:
|
||||||
|
postgres_data:
|
||||||
|
redis_data:
|
||||||
|
mailpit_data:
|
||||||
|
minio_data:
|
||||||
|
driver: local
|
||||||
|
|
||||||
|
networks:
|
||||||
|
minionetwork:
|
||||||
|
driver: bridge
|
||||||
16
drizzle.config.ts
Normal file
16
drizzle.config.ts
Normal file
|
|
@ -0,0 +1,16 @@
|
||||||
|
import type { Config } from 'drizzle-kit';
|
||||||
|
|
||||||
|
export default {
|
||||||
|
out: './src/lib/server/api/databases/postgres/migrations',
|
||||||
|
schema: './src/lib/server/api/databases/postgres/tables/*.table.ts',
|
||||||
|
breakpoints: false,
|
||||||
|
strict: true,
|
||||||
|
dialect: 'postgresql',
|
||||||
|
dbCredentials: {
|
||||||
|
url: process.env.DATABASE_URL!
|
||||||
|
},
|
||||||
|
migrations: {
|
||||||
|
table: 'migrations',
|
||||||
|
schema: 'public'
|
||||||
|
}
|
||||||
|
} satisfies Config;
|
||||||
33
eslint.config.js
Normal file
33
eslint.config.js
Normal file
|
|
@ -0,0 +1,33 @@
|
||||||
|
import js from '@eslint/js';
|
||||||
|
import ts from 'typescript-eslint';
|
||||||
|
import svelte from 'eslint-plugin-svelte';
|
||||||
|
import prettier from 'eslint-config-prettier';
|
||||||
|
import globals from 'globals';
|
||||||
|
|
||||||
|
/** @type {import('eslint').Linter.FlatConfig[]} */
|
||||||
|
export default [
|
||||||
|
js.configs.recommended,
|
||||||
|
...ts.configs.recommended,
|
||||||
|
...svelte.configs['flat/recommended'],
|
||||||
|
prettier,
|
||||||
|
...svelte.configs['flat/prettier'],
|
||||||
|
{
|
||||||
|
languageOptions: {
|
||||||
|
globals: {
|
||||||
|
...globals.browser,
|
||||||
|
...globals.node
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
files: ['**/*.svelte'],
|
||||||
|
languageOptions: {
|
||||||
|
parserOptions: {
|
||||||
|
parser: ts.parser
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
ignores: ['build/', '.svelte-kit/', 'dist/']
|
||||||
|
}
|
||||||
|
];
|
||||||
41
minio-console.conf.template
Normal file
41
minio-console.conf.template
Normal file
|
|
@ -0,0 +1,41 @@
|
||||||
|
upstream minio_console {
|
||||||
|
server ${MINIO_HOST}:${MINIO_CONSOLE_PORT};
|
||||||
|
}
|
||||||
|
|
||||||
|
server {
|
||||||
|
listen ${PORT};
|
||||||
|
|
||||||
|
# Allow special characters in headers
|
||||||
|
ignore_invalid_headers off;
|
||||||
|
# Allow any size file to be uploaded.
|
||||||
|
# Set to a value such as 1000m; to restrict file size to a specific value
|
||||||
|
client_max_body_size 0;
|
||||||
|
# Disable buffering
|
||||||
|
proxy_buffering off;
|
||||||
|
proxy_request_buffering off;
|
||||||
|
|
||||||
|
location / {
|
||||||
|
proxy_set_header Host $http_host;
|
||||||
|
proxy_set_header X-Real-IP $remote_addr;
|
||||||
|
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||||
|
proxy_set_header X-Forwarded-Proto $scheme;
|
||||||
|
proxy_set_header X-NginX-Proxy true;
|
||||||
|
|
||||||
|
# This is necessary to pass the correct IP to be hashed
|
||||||
|
real_ip_header X-Real-IP;
|
||||||
|
|
||||||
|
proxy_connect_timeout 300;
|
||||||
|
|
||||||
|
# To support websockets in MinIO versions released after January 2023
|
||||||
|
proxy_http_version 1.1;
|
||||||
|
proxy_set_header Upgrade $http_upgrade;
|
||||||
|
proxy_set_header Connection "upgrade";
|
||||||
|
# Some environments may encounter CORS errors (Kubernetes + Nginx Ingress)
|
||||||
|
# Uncomment the following line to set the Origin request to an empty string
|
||||||
|
# proxy_set_header Origin '';
|
||||||
|
|
||||||
|
chunked_transfer_encoding off;
|
||||||
|
|
||||||
|
proxy_pass http://minio_console; # This uses the upstream directive definition to load balance
|
||||||
|
}
|
||||||
|
}
|
||||||
90
package.json
Normal file
90
package.json
Normal file
|
|
@ -0,0 +1,90 @@
|
||||||
|
{
|
||||||
|
"name": "taro-stack",
|
||||||
|
"version": "0.0.1",
|
||||||
|
"private": true,
|
||||||
|
"scripts": {
|
||||||
|
"db:push": "drizzle-kit push",
|
||||||
|
"db:generate": "drizzle-kit generate",
|
||||||
|
"db:migrate": "drizzle-kit migrate",
|
||||||
|
"db:studio": "drizzle-kit studio --verbose",
|
||||||
|
"dev": "vite dev",
|
||||||
|
"build": "vite build",
|
||||||
|
"preview": "vite preview",
|
||||||
|
"test": "npm run test:integration && npm run test:unit",
|
||||||
|
"check": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json",
|
||||||
|
"check:watch": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json --watch",
|
||||||
|
"initialize": "pnpm install && docker-compose up --no-recreate -d && pnpm db:migrate",
|
||||||
|
"lint": "prettier --check . && eslint .",
|
||||||
|
"format": "prettier --write .",
|
||||||
|
"test:integration": "playwright test",
|
||||||
|
"test:unit": "vitest"
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"@aws-sdk/client-s3": "^3.651.0",
|
||||||
|
"@hono/zod-validator": "^0.2.2",
|
||||||
|
"@lucia-auth/adapter-drizzle": "^1.1.0",
|
||||||
|
"@paralleldrive/cuid2": "^2.2.2",
|
||||||
|
"@playwright/test": "^1.47.0",
|
||||||
|
"@sveltejs/adapter-node": "^5.2.2",
|
||||||
|
"@sveltejs/kit": "^2.5.26",
|
||||||
|
"@sveltejs/vite-plugin-svelte": "^4.0.0-next.7",
|
||||||
|
"@tailwindcss/typography": "^0.5.15",
|
||||||
|
"@types/eslint": "^9.6.1",
|
||||||
|
"@types/node": "^22.5.4",
|
||||||
|
"@typescript-eslint/eslint-plugin": "^8.5.0",
|
||||||
|
"@typescript-eslint/parser": "^8.5.0",
|
||||||
|
"arctic": "^1.9.2",
|
||||||
|
"autoprefixer": "^10.4.20",
|
||||||
|
"bullmq": "^5.13.0",
|
||||||
|
"chalk": "^5.3.0",
|
||||||
|
"dotenv-cli": "^7.4.2",
|
||||||
|
"drizzle-kit": "^0.26.2",
|
||||||
|
"drizzle-orm": "^0.35.1",
|
||||||
|
"eslint": "^9.10.0",
|
||||||
|
"eslint-config-prettier": "^9.1.0",
|
||||||
|
"eslint-plugin-svelte": "^2.43.0",
|
||||||
|
"hono": "^4.6.1",
|
||||||
|
"ioredis": "^5.4.1",
|
||||||
|
"lucia": "^3.2.0",
|
||||||
|
"lucide-svelte": "^0.441.0",
|
||||||
|
"oslo": "^1.2.1",
|
||||||
|
"pg": "^8.12.0",
|
||||||
|
"postcss": "^8.4.45",
|
||||||
|
"postgres": "^3.4.4",
|
||||||
|
"prettier": "^3.3.3",
|
||||||
|
"prettier-plugin-svelte": "^3.2.6",
|
||||||
|
"prettier-plugin-tailwindcss": "^0.6.6",
|
||||||
|
"reflect-metadata": "^0.2.2",
|
||||||
|
"svelte": "5.0.0-next.164",
|
||||||
|
"svelte-check": "^4.0.2",
|
||||||
|
"svelte-dnd-action": "^0.9.50",
|
||||||
|
"svelte-eslint-parser": "^0.41.0",
|
||||||
|
"sveltekit-search-params": "^3.0.0",
|
||||||
|
"sveltekit-superforms": "^2.17.0",
|
||||||
|
"tailwindcss": "^3.4.11",
|
||||||
|
"tsyringe": "^4.8.0",
|
||||||
|
"typescript": "^5.6.2",
|
||||||
|
"vite": "^5.4.4",
|
||||||
|
"vitest": "^2.1.0",
|
||||||
|
"zod": "^3.23.8"
|
||||||
|
},
|
||||||
|
"type": "module",
|
||||||
|
"dependencies": {
|
||||||
|
"@internationalized/date": "^3.5.5",
|
||||||
|
"bits-ui": "^0.21.13",
|
||||||
|
"clsx": "^2.1.1",
|
||||||
|
"cmdk-sv": "^0.0.18",
|
||||||
|
"embla-carousel-svelte": "^8.2.1",
|
||||||
|
"formsnap": "^1.0.1",
|
||||||
|
"hono-rate-limiter": "^0.4.0",
|
||||||
|
"mode-watcher": "^0.4.1",
|
||||||
|
"paneforge": "^0.0.5",
|
||||||
|
"rate-limit-redis": "^4.2.0",
|
||||||
|
"redis": "^4.7.0",
|
||||||
|
"redis-om": "^0.4.6",
|
||||||
|
"svelte-sonner": "^0.3.28",
|
||||||
|
"tailwind-merge": "^2.5.2",
|
||||||
|
"tailwind-variants": "^0.2.1",
|
||||||
|
"vaul-svelte": "^0.3.2"
|
||||||
|
}
|
||||||
|
}
|
||||||
12
playwright.config.ts
Normal file
12
playwright.config.ts
Normal file
|
|
@ -0,0 +1,12 @@
|
||||||
|
import type { PlaywrightTestConfig } from '@playwright/test';
|
||||||
|
|
||||||
|
const config: PlaywrightTestConfig = {
|
||||||
|
webServer: {
|
||||||
|
command: 'npm run build && npm run preview',
|
||||||
|
port: 4173
|
||||||
|
},
|
||||||
|
testDir: 'tests',
|
||||||
|
testMatch: /(.+\.)?(test|spec)\.[jt]s/
|
||||||
|
};
|
||||||
|
|
||||||
|
export default config;
|
||||||
7388
pnpm-lock.yaml
Normal file
7388
pnpm-lock.yaml
Normal file
File diff suppressed because it is too large
Load diff
6
postcss.config.js
Normal file
6
postcss.config.js
Normal file
|
|
@ -0,0 +1,6 @@
|
||||||
|
export default {
|
||||||
|
plugins: {
|
||||||
|
tailwindcss: {},
|
||||||
|
autoprefixer: {}
|
||||||
|
}
|
||||||
|
};
|
||||||
101
render.yaml
Normal file
101
render.yaml
Normal file
|
|
@ -0,0 +1,101 @@
|
||||||
|
services:
|
||||||
|
# Web
|
||||||
|
- type: web
|
||||||
|
name: web
|
||||||
|
runtime: node
|
||||||
|
region: oregon # optional (defaults to oregon)
|
||||||
|
plan: starter # optional (defaults to starter instance type)
|
||||||
|
branch: main # optional (defaults to master)
|
||||||
|
buildCommand: npm install --force && npm run build
|
||||||
|
startCommand: npm run db:migrate && node build/index.js
|
||||||
|
healthCheckPath: /
|
||||||
|
envVars:
|
||||||
|
- key: STORAGE_API_SECRET_KEY
|
||||||
|
fromService:
|
||||||
|
name: minio-server
|
||||||
|
type: web
|
||||||
|
property: MINIO_ROOT_PASSWORD
|
||||||
|
- key: STORAGE_ACCESS_KEY
|
||||||
|
fromService:
|
||||||
|
name: minio-server
|
||||||
|
type: web
|
||||||
|
property: MINIO_ROOT_USER
|
||||||
|
- key: STORAGE_API_URL
|
||||||
|
fromService:
|
||||||
|
name: minio-server
|
||||||
|
type: web
|
||||||
|
property: connectionString
|
||||||
|
- key: DATABASE_URL
|
||||||
|
fromDatabase:
|
||||||
|
name: tofustack
|
||||||
|
property: connectionString
|
||||||
|
- key: REDIS_URL
|
||||||
|
fromService:
|
||||||
|
name: redis
|
||||||
|
type: redis
|
||||||
|
property: connectionString
|
||||||
|
- key: PUBLIC_ORIGIN
|
||||||
|
fromService:
|
||||||
|
name: web
|
||||||
|
type: web
|
||||||
|
property: host
|
||||||
|
# Redis
|
||||||
|
- type: redis
|
||||||
|
name: redis
|
||||||
|
ipAllowList: [] # Only allow internal connections
|
||||||
|
|
||||||
|
# MinIO Server
|
||||||
|
- type: web
|
||||||
|
name: minio-server
|
||||||
|
healthCheckPath: /minio/health/liveweb
|
||||||
|
runtime: image
|
||||||
|
image:
|
||||||
|
url: docker.io/minio/minio:latest
|
||||||
|
dockerCommand: minio server /data --address $HOST:$PORT --console-address $HOST:$CONSOLE_PORT
|
||||||
|
# Use the following 'dockerCommand' if removing the 'minio-console'
|
||||||
|
# web service
|
||||||
|
# dockerCommand: minio server /data --address $HOST:$PORT
|
||||||
|
autoDeploy: false
|
||||||
|
disk:
|
||||||
|
name: data
|
||||||
|
mountPath: /data
|
||||||
|
envVars:
|
||||||
|
- key: MINIO_ROOT_USER
|
||||||
|
generateValue: true
|
||||||
|
- key: MINIO_ROOT_PASSWORD
|
||||||
|
generateValue: true
|
||||||
|
- key: HOST
|
||||||
|
value: "0.0.0.0"
|
||||||
|
- key: PORT
|
||||||
|
value: 9000
|
||||||
|
- key: CONSOLE_PORT
|
||||||
|
value: 9090
|
||||||
|
# Uncomment the following key/value pair if you are removing the
|
||||||
|
# 'minio-console' web service
|
||||||
|
# - key: MINIO_BROWSER
|
||||||
|
# value: "off"
|
||||||
|
# MinIO Console
|
||||||
|
- type: web
|
||||||
|
name: minio-console
|
||||||
|
runtime: docker
|
||||||
|
dockerContext: /
|
||||||
|
dockerfilePath: ./Dockerfile.minio
|
||||||
|
autoDeploy: false
|
||||||
|
envVars:
|
||||||
|
- key: PORT
|
||||||
|
value: 10000
|
||||||
|
- key: MINIO_HOST
|
||||||
|
fromService:
|
||||||
|
name: minio-server
|
||||||
|
type: web
|
||||||
|
property: host
|
||||||
|
- key: MINIO_CONSOLE_PORT
|
||||||
|
fromService:
|
||||||
|
name: minio-server
|
||||||
|
type: web
|
||||||
|
envVarKey: CONSOLE_PORT
|
||||||
|
|
||||||
|
databases:
|
||||||
|
- name: db
|
||||||
|
databaseName: tofustack
|
||||||
|
ipAllowList: []
|
||||||
78
src/app.css
Normal file
78
src/app.css
Normal file
|
|
@ -0,0 +1,78 @@
|
||||||
|
@tailwind base;
|
||||||
|
@tailwind components;
|
||||||
|
@tailwind utilities;
|
||||||
|
|
||||||
|
@layer base {
|
||||||
|
:root {
|
||||||
|
--background: 0 0% 100%;
|
||||||
|
--foreground: 222.2 84% 4.9%;
|
||||||
|
|
||||||
|
--muted: 210 40% 96.1%;
|
||||||
|
--muted-foreground: 215.4 16.3% 46.9%;
|
||||||
|
|
||||||
|
--popover: 0 0% 100%;
|
||||||
|
--popover-foreground: 222.2 84% 4.9%;
|
||||||
|
|
||||||
|
--card: 0 0% 100%;
|
||||||
|
--card-foreground: 222.2 84% 4.9%;
|
||||||
|
|
||||||
|
--border: 214.3 31.8% 91.4%;
|
||||||
|
--input: 214.3 31.8% 91.4%;
|
||||||
|
|
||||||
|
--primary: 222.2 47.4% 11.2%;
|
||||||
|
--primary-foreground: 210 40% 98%;
|
||||||
|
|
||||||
|
--secondary: 210 40% 96.1%;
|
||||||
|
--secondary-foreground: 222.2 47.4% 11.2%;
|
||||||
|
|
||||||
|
--accent: 210 40% 96.1%;
|
||||||
|
--accent-foreground: 222.2 47.4% 11.2%;
|
||||||
|
|
||||||
|
--destructive: 0 72.2% 50.6%;
|
||||||
|
--destructive-foreground: 210 40% 98%;
|
||||||
|
|
||||||
|
--ring: 222.2 84% 4.9%;
|
||||||
|
|
||||||
|
--radius: 0.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dark {
|
||||||
|
--background: 222.2 84% 4.9%;
|
||||||
|
--foreground: 210 40% 98%;
|
||||||
|
|
||||||
|
--muted: 217.2 32.6% 17.5%;
|
||||||
|
--muted-foreground: 215 20.2% 65.1%;
|
||||||
|
|
||||||
|
--popover: 222.2 84% 4.9%;
|
||||||
|
--popover-foreground: 210 40% 98%;
|
||||||
|
|
||||||
|
--card: 222.2 84% 4.9%;
|
||||||
|
--card-foreground: 210 40% 98%;
|
||||||
|
|
||||||
|
--border: 217.2 32.6% 17.5%;
|
||||||
|
--input: 217.2 32.6% 17.5%;
|
||||||
|
|
||||||
|
--primary: 210 40% 98%;
|
||||||
|
--primary-foreground: 222.2 47.4% 11.2%;
|
||||||
|
|
||||||
|
--secondary: 217.2 32.6% 17.5%;
|
||||||
|
--secondary-foreground: 210 40% 98%;
|
||||||
|
|
||||||
|
--accent: 217.2 32.6% 17.5%;
|
||||||
|
--accent-foreground: 210 40% 98%;
|
||||||
|
|
||||||
|
--destructive: 0 62.8% 30.6%;
|
||||||
|
--destructive-foreground: 210 40% 98%;
|
||||||
|
|
||||||
|
--ring: hsl(212.7,26.8%,83.9);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@layer base {
|
||||||
|
* {
|
||||||
|
@apply border-border;
|
||||||
|
}
|
||||||
|
body {
|
||||||
|
@apply bg-background text-foreground;
|
||||||
|
}
|
||||||
|
}
|
||||||
30
src/app.d.ts
vendored
Normal file
30
src/app.d.ts
vendored
Normal file
|
|
@ -0,0 +1,30 @@
|
||||||
|
import { ApiClient } from '$lib/server/api';
|
||||||
|
import type { User } from 'lucia';
|
||||||
|
import { parseApiResponse } from '$lib/utils/api'
|
||||||
|
import type { Security } from '$lib/utils/security';
|
||||||
|
|
||||||
|
// See https://kit.svelte.dev/docs/types#app
|
||||||
|
// for information about these interfaces
|
||||||
|
declare global {
|
||||||
|
namespace App {
|
||||||
|
// interface Error {}
|
||||||
|
interface Locals {
|
||||||
|
api: ApiClient['api'];
|
||||||
|
parseApiResponse: typeof parseApiResponse;
|
||||||
|
getAuthedUser: () => Promise<Returned<User> | null>;
|
||||||
|
getAuthedUserOrThrow: (redirectTo: string) => Promise<Returned<User>>;
|
||||||
|
}
|
||||||
|
|
||||||
|
// interface PageData {}
|
||||||
|
// interface PageState {}
|
||||||
|
// interface Platform {}
|
||||||
|
namespace Superforms {
|
||||||
|
type Message = {
|
||||||
|
type: 'error' | 'success',
|
||||||
|
text: string
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export { };
|
||||||
12
src/app.html
Normal file
12
src/app.html
Normal file
|
|
@ -0,0 +1,12 @@
|
||||||
|
<!doctype html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="utf-8" />
|
||||||
|
<link rel="icon" href="%sveltekit.assets%/favicon.png" />
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||||
|
%sveltekit.head%
|
||||||
|
</head>
|
||||||
|
<body data-sveltekit-preload-data="hover">
|
||||||
|
<div style="display: contents">%sveltekit.body%</div>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
42
src/hooks.server.ts
Normal file
42
src/hooks.server.ts
Normal file
|
|
@ -0,0 +1,42 @@
|
||||||
|
import 'reflect-metadata'
|
||||||
|
import { hc } from 'hono/client';
|
||||||
|
import { redirect, type Handle } from '@sveltejs/kit';
|
||||||
|
import { sequence } from '@sveltejs/kit/hooks';
|
||||||
|
import type { ApiRoutes } from '$lib/server/api';
|
||||||
|
import { parseApiResponse } from '$lib/utils/api';
|
||||||
|
import { StatusCodes } from '$lib/constants/status-codes';
|
||||||
|
|
||||||
|
const apiClient: Handle = async ({ event, resolve }) => {
|
||||||
|
/* ------------------------------ Register api ------------------------------ */
|
||||||
|
const { api } = hc<ApiRoutes>('/', {
|
||||||
|
fetch: event.fetch,
|
||||||
|
headers: {
|
||||||
|
'x-forwarded-for': event.getClientAddress(),
|
||||||
|
host: event.request.headers.get('host') || ''
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
/* ----------------------------- Auth functions ----------------------------- */
|
||||||
|
async function getAuthedUser() {
|
||||||
|
const { data } = await api.iam.me.$get().then(parseApiResponse)
|
||||||
|
return data && data.user;
|
||||||
|
}
|
||||||
|
|
||||||
|
async function getAuthedUserOrThrow(redirectTo = '/') {
|
||||||
|
const { data } = await api.iam.me.$get().then(parseApiResponse);
|
||||||
|
if (!data || !data.user) throw redirect(StatusCodes.TEMPORARY_REDIRECT, redirectTo);
|
||||||
|
return data?.user;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ------------------------------ Set contexts ------------------------------ */
|
||||||
|
event.locals.api = api;
|
||||||
|
event.locals.parseApiResponse = parseApiResponse;
|
||||||
|
event.locals.getAuthedUser = getAuthedUser;
|
||||||
|
event.locals.getAuthedUserOrThrow = getAuthedUserOrThrow;
|
||||||
|
|
||||||
|
/* ----------------------------- Return response ---------------------------- */
|
||||||
|
const response = await resolve(event);
|
||||||
|
return response;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const handle = sequence(apiClient);
|
||||||
7
src/index.test.ts
Normal file
7
src/index.test.ts
Normal file
|
|
@ -0,0 +1,7 @@
|
||||||
|
import { describe, it, expect } from 'vitest';
|
||||||
|
|
||||||
|
describe('sum test', () => {
|
||||||
|
it('adds 1 + 2 to equal 3', () => {
|
||||||
|
expect(1 + 2).toBe(3);
|
||||||
|
});
|
||||||
|
});
|
||||||
15
src/lib/components/container.svelte
Normal file
15
src/lib/components/container.svelte
Normal file
|
|
@ -0,0 +1,15 @@
|
||||||
|
<script lang="ts">
|
||||||
|
import type { HTMLAttributes } from 'svelte/elements';
|
||||||
|
import { cn } from '$lib/utils/ui';
|
||||||
|
import type { Snippet } from 'svelte';
|
||||||
|
|
||||||
|
interface Props extends HTMLAttributes<HTMLDivElement> {
|
||||||
|
children: Snippet;
|
||||||
|
}
|
||||||
|
|
||||||
|
let { class: classNames, children, ...restProps }: Props = $props();
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<div {...restProps} class={cn('mx-auto w-full max-w-6xl px-0 md:px-6', classNames)}>
|
||||||
|
{@render children()}
|
||||||
|
</div>
|
||||||
32
src/lib/components/pin-input.svelte
Normal file
32
src/lib/components/pin-input.svelte
Normal file
|
|
@ -0,0 +1,32 @@
|
||||||
|
<script lang="ts">
|
||||||
|
import { cn } from '$lib/utils/ui';
|
||||||
|
import { PinInput, type PinInputProps } from 'bits-ui';
|
||||||
|
|
||||||
|
interface Props extends Omit<PinInputProps, 'value'> {
|
||||||
|
value: string;
|
||||||
|
inputCount?: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
let { value = $bindable(), inputCount = 6, ...rest }: Props = $props();
|
||||||
|
let pin = $state<string[] | undefined>(value?.split('') ?? []);
|
||||||
|
let inputs = $derived(Array(inputCount).fill(null));
|
||||||
|
|
||||||
|
$effect(() => {
|
||||||
|
value = pin?.join('') ?? '';
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<PinInput.Root
|
||||||
|
{...rest}
|
||||||
|
bind:value={pin}
|
||||||
|
class={cn('flex items-center gap-2', rest.class)}
|
||||||
|
type="text"
|
||||||
|
placeholder=""
|
||||||
|
>
|
||||||
|
{#each inputs as _}
|
||||||
|
<PinInput.Input
|
||||||
|
class="flex h-14 w-10 rounded-md border border-input bg-background px-3 py-2 text-center text-sm ring-offset-background file:border-0 file:bg-transparent file:text-sm file:font-medium placeholder:text-muted-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50"
|
||||||
|
/>
|
||||||
|
{/each}
|
||||||
|
<PinInput.HiddenInput />
|
||||||
|
</PinInput.Root>
|
||||||
25
src/lib/components/ui/accordion/accordion-content.svelte
Normal file
25
src/lib/components/ui/accordion/accordion-content.svelte
Normal file
|
|
@ -0,0 +1,25 @@
|
||||||
|
<script lang="ts">
|
||||||
|
import { Accordion as AccordionPrimitive } from "bits-ui";
|
||||||
|
import { slide } from "svelte/transition";
|
||||||
|
import { cn } from "$lib/utils/ui.js";
|
||||||
|
|
||||||
|
type $$Props = AccordionPrimitive.ContentProps;
|
||||||
|
|
||||||
|
let className: $$Props["class"] = undefined;
|
||||||
|
export let transition: $$Props["transition"] = slide;
|
||||||
|
export let transitionConfig: $$Props["transitionConfig"] = {
|
||||||
|
duration: 200,
|
||||||
|
};
|
||||||
|
export { className as class };
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<AccordionPrimitive.Content
|
||||||
|
class={cn("overflow-hidden text-sm transition-all", className)}
|
||||||
|
{transition}
|
||||||
|
{transitionConfig}
|
||||||
|
{...$$restProps}
|
||||||
|
>
|
||||||
|
<div class="pb-4 pt-0">
|
||||||
|
<slot />
|
||||||
|
</div>
|
||||||
|
</AccordionPrimitive.Content>
|
||||||
14
src/lib/components/ui/accordion/accordion-item.svelte
Normal file
14
src/lib/components/ui/accordion/accordion-item.svelte
Normal file
|
|
@ -0,0 +1,14 @@
|
||||||
|
<script lang="ts">
|
||||||
|
import { Accordion as AccordionPrimitive } from "bits-ui";
|
||||||
|
import { cn } from "$lib/utils/ui.js";
|
||||||
|
|
||||||
|
type $$Props = AccordionPrimitive.ItemProps;
|
||||||
|
|
||||||
|
let className: $$Props["class"] = undefined;
|
||||||
|
export let value: $$Props["value"];
|
||||||
|
export { className as class };
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<AccordionPrimitive.Item {value} class={cn("border-b", className)} {...$$restProps}>
|
||||||
|
<slot />
|
||||||
|
</AccordionPrimitive.Item>
|
||||||
26
src/lib/components/ui/accordion/accordion-trigger.svelte
Normal file
26
src/lib/components/ui/accordion/accordion-trigger.svelte
Normal file
|
|
@ -0,0 +1,26 @@
|
||||||
|
<script lang="ts">
|
||||||
|
import { Accordion as AccordionPrimitive } from "bits-ui";
|
||||||
|
import ChevronDown from "lucide-svelte/icons/chevron-down";
|
||||||
|
import { cn } from "$lib/utils/ui.js";
|
||||||
|
|
||||||
|
type $$Props = AccordionPrimitive.TriggerProps;
|
||||||
|
type $$Events = AccordionPrimitive.TriggerEvents;
|
||||||
|
|
||||||
|
let className: $$Props["class"] = undefined;
|
||||||
|
export let level: AccordionPrimitive.HeaderProps["level"] = 3;
|
||||||
|
export { className as class };
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<AccordionPrimitive.Header {level} class="flex">
|
||||||
|
<AccordionPrimitive.Trigger
|
||||||
|
class={cn(
|
||||||
|
"flex flex-1 items-center justify-between py-4 font-medium transition-all hover:underline [&[data-state=open]>svg]:rotate-180",
|
||||||
|
className
|
||||||
|
)}
|
||||||
|
{...$$restProps}
|
||||||
|
on:click
|
||||||
|
>
|
||||||
|
<slot />
|
||||||
|
<ChevronDown class="h-4 w-4 transition-transform duration-200" />
|
||||||
|
</AccordionPrimitive.Trigger>
|
||||||
|
</AccordionPrimitive.Header>
|
||||||
17
src/lib/components/ui/accordion/index.ts
Normal file
17
src/lib/components/ui/accordion/index.ts
Normal file
|
|
@ -0,0 +1,17 @@
|
||||||
|
import { Accordion as AccordionPrimitive } from "bits-ui";
|
||||||
|
import Content from "./accordion-content.svelte";
|
||||||
|
import Item from "./accordion-item.svelte";
|
||||||
|
import Trigger from "./accordion-trigger.svelte";
|
||||||
|
const Root = AccordionPrimitive.Root;
|
||||||
|
|
||||||
|
export {
|
||||||
|
Root,
|
||||||
|
Content,
|
||||||
|
Item,
|
||||||
|
Trigger,
|
||||||
|
//
|
||||||
|
Root as Accordion,
|
||||||
|
Content as AccordionContent,
|
||||||
|
Item as AccordionItem,
|
||||||
|
Trigger as AccordionTrigger,
|
||||||
|
};
|
||||||
|
|
@ -0,0 +1,21 @@
|
||||||
|
<script lang="ts">
|
||||||
|
import { AlertDialog as AlertDialogPrimitive } from "bits-ui";
|
||||||
|
import { buttonVariants } from "$lib/components/ui/button/index.js";
|
||||||
|
import { cn } from "$lib/utils/ui.js";
|
||||||
|
|
||||||
|
type $$Props = AlertDialogPrimitive.ActionProps;
|
||||||
|
type $$Events = AlertDialogPrimitive.ActionEvents;
|
||||||
|
|
||||||
|
let className: $$Props["class"] = undefined;
|
||||||
|
export { className as class };
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<AlertDialogPrimitive.Action
|
||||||
|
class={cn(buttonVariants(), className)}
|
||||||
|
{...$$restProps}
|
||||||
|
on:click
|
||||||
|
on:keydown
|
||||||
|
let:builder
|
||||||
|
>
|
||||||
|
<slot {builder} />
|
||||||
|
</AlertDialogPrimitive.Action>
|
||||||
|
|
@ -0,0 +1,21 @@
|
||||||
|
<script lang="ts">
|
||||||
|
import { AlertDialog as AlertDialogPrimitive } from "bits-ui";
|
||||||
|
import { buttonVariants } from "$lib/components/ui/button/index.js";
|
||||||
|
import { cn } from "$lib/utils/ui.js";
|
||||||
|
|
||||||
|
type $$Props = AlertDialogPrimitive.CancelProps;
|
||||||
|
type $$Events = AlertDialogPrimitive.CancelEvents;
|
||||||
|
|
||||||
|
let className: $$Props["class"] = undefined;
|
||||||
|
export { className as class };
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<AlertDialogPrimitive.Cancel
|
||||||
|
class={cn(buttonVariants({ variant: "outline" }), "mt-2 sm:mt-0", className)}
|
||||||
|
{...$$restProps}
|
||||||
|
on:click
|
||||||
|
on:keydown
|
||||||
|
let:builder
|
||||||
|
>
|
||||||
|
<slot {builder} />
|
||||||
|
</AlertDialogPrimitive.Cancel>
|
||||||
|
|
@ -0,0 +1,28 @@
|
||||||
|
<script lang="ts">
|
||||||
|
import { AlertDialog as AlertDialogPrimitive } from "bits-ui";
|
||||||
|
import * as AlertDialog from "./index.js";
|
||||||
|
import { cn, flyAndScale } from "$lib/utils/ui.js";
|
||||||
|
|
||||||
|
type $$Props = AlertDialogPrimitive.ContentProps;
|
||||||
|
|
||||||
|
export let transition: $$Props["transition"] = flyAndScale;
|
||||||
|
export let transitionConfig: $$Props["transitionConfig"] = undefined;
|
||||||
|
|
||||||
|
let className: $$Props["class"] = undefined;
|
||||||
|
export { className as class };
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<AlertDialog.Portal>
|
||||||
|
<AlertDialog.Overlay />
|
||||||
|
<AlertDialogPrimitive.Content
|
||||||
|
{transition}
|
||||||
|
{transitionConfig}
|
||||||
|
class={cn(
|
||||||
|
"fixed left-[50%] top-[50%] z-50 grid w-full max-w-lg translate-x-[-50%] translate-y-[-50%] gap-4 border bg-background p-6 shadow-lg sm:rounded-lg md:w-full",
|
||||||
|
className
|
||||||
|
)}
|
||||||
|
{...$$restProps}
|
||||||
|
>
|
||||||
|
<slot />
|
||||||
|
</AlertDialogPrimitive.Content>
|
||||||
|
</AlertDialog.Portal>
|
||||||
|
|
@ -0,0 +1,16 @@
|
||||||
|
<script lang="ts">
|
||||||
|
import { AlertDialog as AlertDialogPrimitive } from "bits-ui";
|
||||||
|
import { cn } from "$lib/utils/ui.js";
|
||||||
|
|
||||||
|
type $$Props = AlertDialogPrimitive.DescriptionProps;
|
||||||
|
|
||||||
|
let className: $$Props["class"] = undefined;
|
||||||
|
export { className as class };
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<AlertDialogPrimitive.Description
|
||||||
|
class={cn("text-sm text-muted-foreground", className)}
|
||||||
|
{...$$restProps}
|
||||||
|
>
|
||||||
|
<slot />
|
||||||
|
</AlertDialogPrimitive.Description>
|
||||||
|
|
@ -0,0 +1,16 @@
|
||||||
|
<script lang="ts">
|
||||||
|
import type { HTMLAttributes } from "svelte/elements";
|
||||||
|
import { cn } from "$lib/utils/ui.js";
|
||||||
|
|
||||||
|
type $$Props = HTMLAttributes<HTMLDivElement>;
|
||||||
|
|
||||||
|
let className: $$Props["class"] = undefined;
|
||||||
|
export { className as class };
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<div
|
||||||
|
class={cn("flex flex-col-reverse sm:flex-row sm:justify-end sm:space-x-2", className)}
|
||||||
|
{...$$restProps}
|
||||||
|
>
|
||||||
|
<slot />
|
||||||
|
</div>
|
||||||
|
|
@ -0,0 +1,13 @@
|
||||||
|
<script lang="ts">
|
||||||
|
import type { HTMLAttributes } from "svelte/elements";
|
||||||
|
import { cn } from "$lib/utils/ui.js";
|
||||||
|
|
||||||
|
type $$Props = HTMLAttributes<HTMLDivElement>;
|
||||||
|
|
||||||
|
let className: $$Props["class"] = undefined;
|
||||||
|
export { className as class };
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<div class={cn("flex flex-col space-y-2 text-center sm:text-left", className)} {...$$restProps}>
|
||||||
|
<slot />
|
||||||
|
</div>
|
||||||
|
|
@ -0,0 +1,21 @@
|
||||||
|
<script lang="ts">
|
||||||
|
import { AlertDialog as AlertDialogPrimitive } from "bits-ui";
|
||||||
|
import { fade } from "svelte/transition";
|
||||||
|
import { cn } from "$lib/utils/ui.js";
|
||||||
|
|
||||||
|
type $$Props = AlertDialogPrimitive.OverlayProps;
|
||||||
|
|
||||||
|
let className: $$Props["class"] = undefined;
|
||||||
|
export let transition: $$Props["transition"] = fade;
|
||||||
|
export let transitionConfig: $$Props["transitionConfig"] = {
|
||||||
|
duration: 150,
|
||||||
|
};
|
||||||
|
export { className as class };
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<AlertDialogPrimitive.Overlay
|
||||||
|
{transition}
|
||||||
|
{transitionConfig}
|
||||||
|
class={cn("fixed inset-0 z-50 bg-background/80 backdrop-blur-sm ", className)}
|
||||||
|
{...$$restProps}
|
||||||
|
/>
|
||||||
|
|
@ -0,0 +1,9 @@
|
||||||
|
<script lang="ts">
|
||||||
|
import { AlertDialog as AlertDialogPrimitive } from "bits-ui";
|
||||||
|
|
||||||
|
type $$Props = AlertDialogPrimitive.PortalProps;
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<AlertDialogPrimitive.Portal {...$$restProps}>
|
||||||
|
<slot />
|
||||||
|
</AlertDialogPrimitive.Portal>
|
||||||
14
src/lib/components/ui/alert-dialog/alert-dialog-title.svelte
Normal file
14
src/lib/components/ui/alert-dialog/alert-dialog-title.svelte
Normal file
|
|
@ -0,0 +1,14 @@
|
||||||
|
<script lang="ts">
|
||||||
|
import { AlertDialog as AlertDialogPrimitive } from "bits-ui";
|
||||||
|
import { cn } from "$lib/utils/ui.js";
|
||||||
|
|
||||||
|
type $$Props = AlertDialogPrimitive.TitleProps;
|
||||||
|
|
||||||
|
let className: $$Props["class"] = undefined;
|
||||||
|
export let level: $$Props["level"] = "h3";
|
||||||
|
export { className as class };
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<AlertDialogPrimitive.Title class={cn("text-lg font-semibold", className)} {level} {...$$restProps}>
|
||||||
|
<slot />
|
||||||
|
</AlertDialogPrimitive.Title>
|
||||||
40
src/lib/components/ui/alert-dialog/index.ts
Normal file
40
src/lib/components/ui/alert-dialog/index.ts
Normal file
|
|
@ -0,0 +1,40 @@
|
||||||
|
import { AlertDialog as AlertDialogPrimitive } from "bits-ui";
|
||||||
|
|
||||||
|
import Title from "./alert-dialog-title.svelte";
|
||||||
|
import Action from "./alert-dialog-action.svelte";
|
||||||
|
import Cancel from "./alert-dialog-cancel.svelte";
|
||||||
|
import Portal from "./alert-dialog-portal.svelte";
|
||||||
|
import Footer from "./alert-dialog-footer.svelte";
|
||||||
|
import Header from "./alert-dialog-header.svelte";
|
||||||
|
import Overlay from "./alert-dialog-overlay.svelte";
|
||||||
|
import Content from "./alert-dialog-content.svelte";
|
||||||
|
import Description from "./alert-dialog-description.svelte";
|
||||||
|
|
||||||
|
const Root = AlertDialogPrimitive.Root;
|
||||||
|
const Trigger = AlertDialogPrimitive.Trigger;
|
||||||
|
|
||||||
|
export {
|
||||||
|
Root,
|
||||||
|
Title,
|
||||||
|
Action,
|
||||||
|
Cancel,
|
||||||
|
Portal,
|
||||||
|
Footer,
|
||||||
|
Header,
|
||||||
|
Trigger,
|
||||||
|
Overlay,
|
||||||
|
Content,
|
||||||
|
Description,
|
||||||
|
//
|
||||||
|
Root as AlertDialog,
|
||||||
|
Title as AlertDialogTitle,
|
||||||
|
Action as AlertDialogAction,
|
||||||
|
Cancel as AlertDialogCancel,
|
||||||
|
Portal as AlertDialogPortal,
|
||||||
|
Footer as AlertDialogFooter,
|
||||||
|
Header as AlertDialogHeader,
|
||||||
|
Trigger as AlertDialogTrigger,
|
||||||
|
Overlay as AlertDialogOverlay,
|
||||||
|
Content as AlertDialogContent,
|
||||||
|
Description as AlertDialogDescription,
|
||||||
|
};
|
||||||
13
src/lib/components/ui/alert/alert-description.svelte
Normal file
13
src/lib/components/ui/alert/alert-description.svelte
Normal file
|
|
@ -0,0 +1,13 @@
|
||||||
|
<script lang="ts">
|
||||||
|
import type { HTMLAttributes } from "svelte/elements";
|
||||||
|
import { cn } from "$lib/utils/ui.js";
|
||||||
|
|
||||||
|
type $$Props = HTMLAttributes<HTMLDivElement>;
|
||||||
|
|
||||||
|
let className: $$Props["class"] = undefined;
|
||||||
|
export { className as class };
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<div class={cn("text-sm [&_p]:leading-relaxed", className)} {...$$restProps}>
|
||||||
|
<slot />
|
||||||
|
</div>
|
||||||
21
src/lib/components/ui/alert/alert-title.svelte
Normal file
21
src/lib/components/ui/alert/alert-title.svelte
Normal file
|
|
@ -0,0 +1,21 @@
|
||||||
|
<script lang="ts">
|
||||||
|
import type { HTMLAttributes } from "svelte/elements";
|
||||||
|
import type { HeadingLevel } from "./index.js";
|
||||||
|
import { cn } from "$lib/utils/ui.js";
|
||||||
|
|
||||||
|
type $$Props = HTMLAttributes<HTMLHeadingElement> & {
|
||||||
|
level?: HeadingLevel;
|
||||||
|
};
|
||||||
|
|
||||||
|
let className: $$Props["class"] = undefined;
|
||||||
|
export let level: $$Props["level"] = "h5";
|
||||||
|
export { className as class };
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<svelte:element
|
||||||
|
this={level}
|
||||||
|
class={cn("mb-1 font-medium leading-none tracking-tight", className)}
|
||||||
|
{...$$restProps}
|
||||||
|
>
|
||||||
|
<slot />
|
||||||
|
</svelte:element>
|
||||||
17
src/lib/components/ui/alert/alert.svelte
Normal file
17
src/lib/components/ui/alert/alert.svelte
Normal file
|
|
@ -0,0 +1,17 @@
|
||||||
|
<script lang="ts">
|
||||||
|
import type { HTMLAttributes } from "svelte/elements";
|
||||||
|
import { type Variant, alertVariants } from "./index.js";
|
||||||
|
import { cn } from "$lib/utils/ui.js";
|
||||||
|
|
||||||
|
type $$Props = HTMLAttributes<HTMLDivElement> & {
|
||||||
|
variant?: Variant;
|
||||||
|
};
|
||||||
|
|
||||||
|
let className: $$Props["class"] = undefined;
|
||||||
|
export let variant: $$Props["variant"] = "default";
|
||||||
|
export { className as class };
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<div class={cn(alertVariants({ variant }), className)} {...$$restProps} role="alert">
|
||||||
|
<slot />
|
||||||
|
</div>
|
||||||
33
src/lib/components/ui/alert/index.ts
Normal file
33
src/lib/components/ui/alert/index.ts
Normal file
|
|
@ -0,0 +1,33 @@
|
||||||
|
import { type VariantProps, tv } from "tailwind-variants";
|
||||||
|
|
||||||
|
import Root from "./alert.svelte";
|
||||||
|
import Description from "./alert-description.svelte";
|
||||||
|
import Title from "./alert-title.svelte";
|
||||||
|
|
||||||
|
export const alertVariants = tv({
|
||||||
|
base: "relative w-full rounded-lg border p-4 [&:has(svg)]:pl-11 [&>svg+div]:translate-y-[-3px] [&>svg]:absolute [&>svg]:left-4 [&>svg]:top-4 [&>svg]:text-foreground",
|
||||||
|
|
||||||
|
variants: {
|
||||||
|
variant: {
|
||||||
|
default: "bg-background text-foreground",
|
||||||
|
destructive:
|
||||||
|
"border-destructive/50 text-destructive text-destructive dark:border-destructive [&>svg]:text-destructive",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
defaultVariants: {
|
||||||
|
variant: "default",
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
export type Variant = VariantProps<typeof alertVariants>["variant"];
|
||||||
|
export type HeadingLevel = "h1" | "h2" | "h3" | "h4" | "h5" | "h6";
|
||||||
|
|
||||||
|
export {
|
||||||
|
Root,
|
||||||
|
Description,
|
||||||
|
Title,
|
||||||
|
//
|
||||||
|
Root as Alert,
|
||||||
|
Description as AlertDescription,
|
||||||
|
Title as AlertTitle,
|
||||||
|
};
|
||||||
11
src/lib/components/ui/aspect-ratio/aspect-ratio.svelte
Normal file
11
src/lib/components/ui/aspect-ratio/aspect-ratio.svelte
Normal file
|
|
@ -0,0 +1,11 @@
|
||||||
|
<script lang="ts">
|
||||||
|
import { AspectRatio as AspectRatioPrimitive } from "bits-ui";
|
||||||
|
|
||||||
|
type $$Props = AspectRatioPrimitive.Props;
|
||||||
|
|
||||||
|
export let ratio: $$Props["ratio"] = 4 / 3;
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<AspectRatioPrimitive.Root {ratio} {...$$restProps}>
|
||||||
|
<slot />
|
||||||
|
</AspectRatioPrimitive.Root>
|
||||||
3
src/lib/components/ui/aspect-ratio/index.ts
Normal file
3
src/lib/components/ui/aspect-ratio/index.ts
Normal file
|
|
@ -0,0 +1,3 @@
|
||||||
|
import Root from "./aspect-ratio.svelte";
|
||||||
|
|
||||||
|
export { Root, Root as AspectRatio };
|
||||||
16
src/lib/components/ui/avatar/avatar-fallback.svelte
Normal file
16
src/lib/components/ui/avatar/avatar-fallback.svelte
Normal file
|
|
@ -0,0 +1,16 @@
|
||||||
|
<script lang="ts">
|
||||||
|
import { Avatar as AvatarPrimitive } from "bits-ui";
|
||||||
|
import { cn } from "$lib/utils/ui.js";
|
||||||
|
|
||||||
|
type $$Props = AvatarPrimitive.FallbackProps;
|
||||||
|
|
||||||
|
let className: $$Props["class"] = undefined;
|
||||||
|
export { className as class };
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<AvatarPrimitive.Fallback
|
||||||
|
class={cn("flex h-full w-full items-center justify-center rounded-full bg-muted", className)}
|
||||||
|
{...$$restProps}
|
||||||
|
>
|
||||||
|
<slot />
|
||||||
|
</AvatarPrimitive.Fallback>
|
||||||
18
src/lib/components/ui/avatar/avatar-image.svelte
Normal file
18
src/lib/components/ui/avatar/avatar-image.svelte
Normal file
|
|
@ -0,0 +1,18 @@
|
||||||
|
<script lang="ts">
|
||||||
|
import { Avatar as AvatarPrimitive } from "bits-ui";
|
||||||
|
import { cn } from "$lib/utils/ui.js";
|
||||||
|
|
||||||
|
type $$Props = AvatarPrimitive.ImageProps;
|
||||||
|
|
||||||
|
let className: $$Props["class"] = undefined;
|
||||||
|
export let src: $$Props["src"] = undefined;
|
||||||
|
export let alt: $$Props["alt"] = undefined;
|
||||||
|
export { className as class };
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<AvatarPrimitive.Image
|
||||||
|
{src}
|
||||||
|
{alt}
|
||||||
|
class={cn("aspect-square h-full w-full", className)}
|
||||||
|
{...$$restProps}
|
||||||
|
/>
|
||||||
18
src/lib/components/ui/avatar/avatar.svelte
Normal file
18
src/lib/components/ui/avatar/avatar.svelte
Normal file
|
|
@ -0,0 +1,18 @@
|
||||||
|
<script lang="ts">
|
||||||
|
import { Avatar as AvatarPrimitive } from "bits-ui";
|
||||||
|
import { cn } from "$lib/utils/ui.js";
|
||||||
|
|
||||||
|
type $$Props = AvatarPrimitive.Props;
|
||||||
|
|
||||||
|
let className: $$Props["class"] = undefined;
|
||||||
|
export let delayMs: $$Props["delayMs"] = undefined;
|
||||||
|
export { className as class };
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<AvatarPrimitive.Root
|
||||||
|
{delayMs}
|
||||||
|
class={cn("relative flex h-10 w-10 shrink-0 overflow-hidden rounded-full", className)}
|
||||||
|
{...$$restProps}
|
||||||
|
>
|
||||||
|
<slot />
|
||||||
|
</AvatarPrimitive.Root>
|
||||||
13
src/lib/components/ui/avatar/index.ts
Normal file
13
src/lib/components/ui/avatar/index.ts
Normal file
|
|
@ -0,0 +1,13 @@
|
||||||
|
import Root from "./avatar.svelte";
|
||||||
|
import Image from "./avatar-image.svelte";
|
||||||
|
import Fallback from "./avatar-fallback.svelte";
|
||||||
|
|
||||||
|
export {
|
||||||
|
Root,
|
||||||
|
Image,
|
||||||
|
Fallback,
|
||||||
|
//
|
||||||
|
Root as Avatar,
|
||||||
|
Image as AvatarImage,
|
||||||
|
Fallback as AvatarFallback,
|
||||||
|
};
|
||||||
18
src/lib/components/ui/badge/badge.svelte
Normal file
18
src/lib/components/ui/badge/badge.svelte
Normal file
|
|
@ -0,0 +1,18 @@
|
||||||
|
<script lang="ts">
|
||||||
|
import { type Variant, badgeVariants } from "./index.js";
|
||||||
|
import { cn } from "$lib/utils/ui.js";
|
||||||
|
|
||||||
|
let className: string | undefined | null = undefined;
|
||||||
|
export let href: string | undefined = undefined;
|
||||||
|
export let variant: Variant = "default";
|
||||||
|
export { className as class };
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<svelte:element
|
||||||
|
this={href ? "a" : "span"}
|
||||||
|
{href}
|
||||||
|
class={cn(badgeVariants({ variant, className }))}
|
||||||
|
{...$$restProps}
|
||||||
|
>
|
||||||
|
<slot />
|
||||||
|
</svelte:element>
|
||||||
21
src/lib/components/ui/badge/index.ts
Normal file
21
src/lib/components/ui/badge/index.ts
Normal file
|
|
@ -0,0 +1,21 @@
|
||||||
|
import { type VariantProps, tv } from "tailwind-variants";
|
||||||
|
export { default as Badge } from "./badge.svelte";
|
||||||
|
|
||||||
|
export const badgeVariants = tv({
|
||||||
|
base: "inline-flex select-none items-center rounded-full border px-2.5 py-0.5 text-xs font-semibold transition-colors focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2",
|
||||||
|
variants: {
|
||||||
|
variant: {
|
||||||
|
default: "border-transparent bg-primary text-primary-foreground hover:bg-primary/80",
|
||||||
|
secondary:
|
||||||
|
"border-transparent bg-secondary text-secondary-foreground hover:bg-secondary/80",
|
||||||
|
destructive:
|
||||||
|
"border-transparent bg-destructive text-destructive-foreground hover:bg-destructive/80",
|
||||||
|
outline: "text-foreground",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
defaultVariants: {
|
||||||
|
variant: "default",
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
export type Variant = VariantProps<typeof badgeVariants>["variant"];
|
||||||
24
src/lib/components/ui/breadcrumb/breadcrumb-ellipsis.svelte
Normal file
24
src/lib/components/ui/breadcrumb/breadcrumb-ellipsis.svelte
Normal file
|
|
@ -0,0 +1,24 @@
|
||||||
|
<script lang="ts">
|
||||||
|
import Ellipsis from "lucide-svelte/icons/ellipsis";
|
||||||
|
import type { HTMLAttributes } from "svelte/elements";
|
||||||
|
import { cn } from "$lib/utils/ui.js";
|
||||||
|
|
||||||
|
type $$Props = HTMLAttributes<HTMLSpanElement> & {
|
||||||
|
el?: HTMLSpanElement;
|
||||||
|
};
|
||||||
|
|
||||||
|
export let el: $$Props["el"] = undefined;
|
||||||
|
let className: $$Props["class"] = undefined;
|
||||||
|
export { className as class };
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<span
|
||||||
|
bind:this={el}
|
||||||
|
role="presentation"
|
||||||
|
aria-hidden="true"
|
||||||
|
class={cn("flex h-9 w-9 items-center justify-center", className)}
|
||||||
|
{...$$restProps}
|
||||||
|
>
|
||||||
|
<Ellipsis class="h-4 w-4" />
|
||||||
|
<span class="sr-only">More</span>
|
||||||
|
</span>
|
||||||
16
src/lib/components/ui/breadcrumb/breadcrumb-item.svelte
Normal file
16
src/lib/components/ui/breadcrumb/breadcrumb-item.svelte
Normal file
|
|
@ -0,0 +1,16 @@
|
||||||
|
<script lang="ts">
|
||||||
|
import type { HTMLLiAttributes } from "svelte/elements";
|
||||||
|
import { cn } from "$lib/utils/ui.js";
|
||||||
|
|
||||||
|
type $$Props = HTMLLiAttributes & {
|
||||||
|
el?: HTMLLIElement;
|
||||||
|
};
|
||||||
|
|
||||||
|
export let el: $$Props["el"] = undefined;
|
||||||
|
let className: $$Props["class"] = undefined;
|
||||||
|
export { className as class };
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<li bind:this={el} class={cn("inline-flex items-center gap-1.5", className)}>
|
||||||
|
<slot />
|
||||||
|
</li>
|
||||||
31
src/lib/components/ui/breadcrumb/breadcrumb-link.svelte
Normal file
31
src/lib/components/ui/breadcrumb/breadcrumb-link.svelte
Normal file
|
|
@ -0,0 +1,31 @@
|
||||||
|
<script lang="ts">
|
||||||
|
import type { HTMLAnchorAttributes } from "svelte/elements";
|
||||||
|
import { cn } from "$lib/utils/ui.js";
|
||||||
|
|
||||||
|
type $$Props = HTMLAnchorAttributes & {
|
||||||
|
el?: HTMLAnchorElement;
|
||||||
|
asChild?: boolean;
|
||||||
|
};
|
||||||
|
|
||||||
|
export let href: $$Props["href"] = undefined;
|
||||||
|
export let el: $$Props["el"] = undefined;
|
||||||
|
export let asChild: $$Props["asChild"] = false;
|
||||||
|
let className: $$Props["class"] = undefined;
|
||||||
|
export { className as class };
|
||||||
|
|
||||||
|
let attrs: Record<string, unknown>;
|
||||||
|
|
||||||
|
$: attrs = {
|
||||||
|
class: cn("transition-colors hover:text-foreground", className),
|
||||||
|
href,
|
||||||
|
...$$restProps,
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
|
||||||
|
{#if asChild}
|
||||||
|
<slot {attrs} />
|
||||||
|
{:else}
|
||||||
|
<a bind:this={el} {...attrs} {href}>
|
||||||
|
<slot {attrs} />
|
||||||
|
</a>
|
||||||
|
{/if}
|
||||||
23
src/lib/components/ui/breadcrumb/breadcrumb-list.svelte
Normal file
23
src/lib/components/ui/breadcrumb/breadcrumb-list.svelte
Normal file
|
|
@ -0,0 +1,23 @@
|
||||||
|
<script lang="ts">
|
||||||
|
import type { HTMLOlAttributes } from "svelte/elements";
|
||||||
|
import { cn } from "$lib/utils/ui.js";
|
||||||
|
|
||||||
|
type $$Props = HTMLOlAttributes & {
|
||||||
|
el?: HTMLOListElement;
|
||||||
|
};
|
||||||
|
|
||||||
|
export let el: $$Props["el"] = undefined;
|
||||||
|
let className: $$Props["class"] = undefined;
|
||||||
|
export { className as class };
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<ol
|
||||||
|
bind:this={el}
|
||||||
|
class={cn(
|
||||||
|
"flex flex-wrap items-center gap-1.5 break-words text-sm text-muted-foreground sm:gap-2.5",
|
||||||
|
className
|
||||||
|
)}
|
||||||
|
{...$$restProps}
|
||||||
|
>
|
||||||
|
<slot />
|
||||||
|
</ol>
|
||||||
23
src/lib/components/ui/breadcrumb/breadcrumb-page.svelte
Normal file
23
src/lib/components/ui/breadcrumb/breadcrumb-page.svelte
Normal file
|
|
@ -0,0 +1,23 @@
|
||||||
|
<script lang="ts">
|
||||||
|
import type { HTMLAttributes } from "svelte/elements";
|
||||||
|
import { cn } from "$lib/utils/ui.js";
|
||||||
|
|
||||||
|
type $$Props = HTMLAttributes<HTMLSpanElement> & {
|
||||||
|
el?: HTMLSpanElement;
|
||||||
|
};
|
||||||
|
|
||||||
|
export let el: $$Props["el"] = undefined;
|
||||||
|
export let className: $$Props["class"] = undefined;
|
||||||
|
export { className as class };
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<span
|
||||||
|
bind:this={el}
|
||||||
|
role="link"
|
||||||
|
aria-disabled="true"
|
||||||
|
aria-current="page"
|
||||||
|
class={cn("font-normal text-foreground", className)}
|
||||||
|
{...$$restProps}
|
||||||
|
>
|
||||||
|
<slot />
|
||||||
|
</span>
|
||||||
25
src/lib/components/ui/breadcrumb/breadcrumb-separator.svelte
Normal file
25
src/lib/components/ui/breadcrumb/breadcrumb-separator.svelte
Normal file
|
|
@ -0,0 +1,25 @@
|
||||||
|
<script lang="ts">
|
||||||
|
import type { HTMLLiAttributes } from "svelte/elements";
|
||||||
|
import ChevronRight from "lucide-svelte/icons/chevron-right";
|
||||||
|
import { cn } from "$lib/utils/ui.js";
|
||||||
|
|
||||||
|
type $$Props = HTMLLiAttributes & {
|
||||||
|
el?: HTMLLIElement;
|
||||||
|
};
|
||||||
|
|
||||||
|
export let el: $$Props["el"] = undefined;
|
||||||
|
let className: $$Props["class"] = undefined;
|
||||||
|
export { className as class };
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<li
|
||||||
|
role="presentation"
|
||||||
|
aria-hidden="true"
|
||||||
|
class={cn("[&>svg]:size-3.5", className)}
|
||||||
|
bind:this={el}
|
||||||
|
{...$$restProps}
|
||||||
|
>
|
||||||
|
<slot>
|
||||||
|
<ChevronRight />
|
||||||
|
</slot>
|
||||||
|
</li>
|
||||||
15
src/lib/components/ui/breadcrumb/breadcrumb.svelte
Normal file
15
src/lib/components/ui/breadcrumb/breadcrumb.svelte
Normal file
|
|
@ -0,0 +1,15 @@
|
||||||
|
<script lang="ts">
|
||||||
|
import type { HTMLAttributes } from "svelte/elements";
|
||||||
|
|
||||||
|
type $$Props = HTMLAttributes<HTMLElement> & {
|
||||||
|
el?: HTMLElement;
|
||||||
|
};
|
||||||
|
|
||||||
|
export let el: $$Props["el"] = undefined;
|
||||||
|
let className: $$Props["class"] = undefined;
|
||||||
|
export { className as class };
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<nav class={className} bind:this={el} aria-label="breadcrumb" {...$$restProps}>
|
||||||
|
<slot />
|
||||||
|
</nav>
|
||||||
25
src/lib/components/ui/breadcrumb/index.ts
Normal file
25
src/lib/components/ui/breadcrumb/index.ts
Normal file
|
|
@ -0,0 +1,25 @@
|
||||||
|
import Root from "./breadcrumb.svelte";
|
||||||
|
import Ellipsis from "./breadcrumb-ellipsis.svelte";
|
||||||
|
import Item from "./breadcrumb-item.svelte";
|
||||||
|
import Separator from "./breadcrumb-separator.svelte";
|
||||||
|
import Link from "./breadcrumb-link.svelte";
|
||||||
|
import List from "./breadcrumb-list.svelte";
|
||||||
|
import Page from "./breadcrumb-page.svelte";
|
||||||
|
|
||||||
|
export {
|
||||||
|
Root,
|
||||||
|
Ellipsis,
|
||||||
|
Item,
|
||||||
|
Separator,
|
||||||
|
Link,
|
||||||
|
List,
|
||||||
|
Page,
|
||||||
|
//
|
||||||
|
Root as Breadcrumb,
|
||||||
|
Ellipsis as BreadcrumbEllipsis,
|
||||||
|
Item as BreadcrumbItem,
|
||||||
|
Separator as BreadcrumbSeparator,
|
||||||
|
Link as BreadcrumbLink,
|
||||||
|
List as BreadcrumbList,
|
||||||
|
Page as BreadcrumbPage,
|
||||||
|
};
|
||||||
25
src/lib/components/ui/button/button.svelte
Normal file
25
src/lib/components/ui/button/button.svelte
Normal file
|
|
@ -0,0 +1,25 @@
|
||||||
|
<script lang="ts">
|
||||||
|
import { Button as ButtonPrimitive } from "bits-ui";
|
||||||
|
import { type Events, type Props, buttonVariants } from "./index.js";
|
||||||
|
import { cn } from "$lib/utils/ui.js";
|
||||||
|
|
||||||
|
type $$Props = Props;
|
||||||
|
type $$Events = Events;
|
||||||
|
|
||||||
|
let className: $$Props["class"] = undefined;
|
||||||
|
export let variant: $$Props["variant"] = "default";
|
||||||
|
export let size: $$Props["size"] = "default";
|
||||||
|
export let builders: $$Props["builders"] = [];
|
||||||
|
export { className as class };
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<ButtonPrimitive.Root
|
||||||
|
{builders}
|
||||||
|
class={cn(buttonVariants({ variant, size, className }))}
|
||||||
|
type="button"
|
||||||
|
{...$$restProps}
|
||||||
|
on:click
|
||||||
|
on:keydown
|
||||||
|
>
|
||||||
|
<slot />
|
||||||
|
</ButtonPrimitive.Root>
|
||||||
49
src/lib/components/ui/button/index.ts
Normal file
49
src/lib/components/ui/button/index.ts
Normal file
|
|
@ -0,0 +1,49 @@
|
||||||
|
import { type VariantProps, tv } from "tailwind-variants";
|
||||||
|
import type { Button as ButtonPrimitive } from "bits-ui";
|
||||||
|
import Root from "./button.svelte";
|
||||||
|
|
||||||
|
const buttonVariants = tv({
|
||||||
|
base: "inline-flex items-center justify-center whitespace-nowrap rounded-md text-sm font-medium ring-offset-background transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50",
|
||||||
|
variants: {
|
||||||
|
variant: {
|
||||||
|
default: "bg-primary text-primary-foreground hover:bg-primary/90",
|
||||||
|
destructive: "bg-destructive text-destructive-foreground hover:bg-destructive/90",
|
||||||
|
outline:
|
||||||
|
"border border-input bg-background hover:bg-accent hover:text-accent-foreground",
|
||||||
|
secondary: "bg-secondary text-secondary-foreground hover:bg-secondary/80",
|
||||||
|
ghost: "hover:bg-accent hover:text-accent-foreground",
|
||||||
|
link: "text-primary underline-offset-4 hover:underline",
|
||||||
|
},
|
||||||
|
size: {
|
||||||
|
default: "h-10 px-4 py-2",
|
||||||
|
sm: "h-9 rounded-md px-3",
|
||||||
|
lg: "h-11 rounded-md px-8",
|
||||||
|
icon: "h-10 w-10",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
defaultVariants: {
|
||||||
|
variant: "default",
|
||||||
|
size: "default",
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
type Variant = VariantProps<typeof buttonVariants>["variant"];
|
||||||
|
type Size = VariantProps<typeof buttonVariants>["size"];
|
||||||
|
|
||||||
|
type Props = ButtonPrimitive.Props & {
|
||||||
|
variant?: Variant;
|
||||||
|
size?: Size;
|
||||||
|
};
|
||||||
|
|
||||||
|
type Events = ButtonPrimitive.Events;
|
||||||
|
|
||||||
|
export {
|
||||||
|
Root,
|
||||||
|
type Props,
|
||||||
|
type Events,
|
||||||
|
//
|
||||||
|
Root as Button,
|
||||||
|
type Props as ButtonProps,
|
||||||
|
type Events as ButtonEvents,
|
||||||
|
buttonVariants,
|
||||||
|
};
|
||||||
21
src/lib/components/ui/calendar/calendar-cell.svelte
Normal file
21
src/lib/components/ui/calendar/calendar-cell.svelte
Normal file
|
|
@ -0,0 +1,21 @@
|
||||||
|
<script lang="ts">
|
||||||
|
import { Calendar as CalendarPrimitive } from "bits-ui";
|
||||||
|
import { cn } from "$lib/utils/ui.js";
|
||||||
|
|
||||||
|
type $$Props = CalendarPrimitive.CellProps;
|
||||||
|
|
||||||
|
export let date: $$Props["date"];
|
||||||
|
let className: $$Props["class"] = undefined;
|
||||||
|
export { className as class };
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<CalendarPrimitive.Cell
|
||||||
|
{date}
|
||||||
|
class={cn(
|
||||||
|
"relative h-9 w-9 p-0 text-center text-sm focus-within:relative focus-within:z-20 [&:has([data-selected])]:rounded-md [&:has([data-selected])]:bg-accent [&:has([data-selected][data-outside-month])]:bg-accent/50",
|
||||||
|
className
|
||||||
|
)}
|
||||||
|
{...$$restProps}
|
||||||
|
>
|
||||||
|
<slot />
|
||||||
|
</CalendarPrimitive.Cell>
|
||||||
42
src/lib/components/ui/calendar/calendar-day.svelte
Normal file
42
src/lib/components/ui/calendar/calendar-day.svelte
Normal file
|
|
@ -0,0 +1,42 @@
|
||||||
|
<script lang="ts">
|
||||||
|
import { Calendar as CalendarPrimitive } from "bits-ui";
|
||||||
|
import { buttonVariants } from "$lib/components/ui/button/index.js";
|
||||||
|
import { cn } from "$lib/utils/ui.js";
|
||||||
|
|
||||||
|
type $$Props = CalendarPrimitive.DayProps;
|
||||||
|
type $$Events = CalendarPrimitive.DayEvents;
|
||||||
|
|
||||||
|
export let date: $$Props["date"];
|
||||||
|
export let month: $$Props["month"];
|
||||||
|
let className: $$Props["class"] = undefined;
|
||||||
|
export { className as class };
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<CalendarPrimitive.Day
|
||||||
|
on:click
|
||||||
|
{date}
|
||||||
|
{month}
|
||||||
|
class={cn(
|
||||||
|
buttonVariants({ variant: "ghost" }),
|
||||||
|
"h-9 w-9 p-0 font-normal ",
|
||||||
|
"[&[data-today]:not([data-selected])]:bg-accent [&[data-today]:not([data-selected])]:text-accent-foreground",
|
||||||
|
// Selected
|
||||||
|
"data-[selected]:bg-primary data-[selected]:text-primary-foreground data-[selected]:opacity-100 data-[selected]:hover:bg-primary data-[selected]:hover:text-primary-foreground data-[selected]:focus:bg-primary data-[selected]:focus:text-primary-foreground",
|
||||||
|
// Disabled
|
||||||
|
"data-[disabled]:text-muted-foreground data-[disabled]:opacity-50",
|
||||||
|
// Unavailable
|
||||||
|
"data-[unavailable]:text-destructive-foreground data-[unavailable]:line-through",
|
||||||
|
// Outside months
|
||||||
|
"data-[outside-month]:pointer-events-none data-[outside-month]:text-muted-foreground data-[outside-month]:opacity-50 [&[data-outside-month][data-selected]]:bg-accent/50 [&[data-outside-month][data-selected]]:text-muted-foreground [&[data-outside-month][data-selected]]:opacity-30",
|
||||||
|
className
|
||||||
|
)}
|
||||||
|
{...$$restProps}
|
||||||
|
let:selected
|
||||||
|
let:disabled
|
||||||
|
let:unavailable
|
||||||
|
let:builder
|
||||||
|
>
|
||||||
|
<slot {selected} {disabled} {unavailable} {builder}>
|
||||||
|
{date.day}
|
||||||
|
</slot>
|
||||||
|
</CalendarPrimitive.Day>
|
||||||
13
src/lib/components/ui/calendar/calendar-grid-body.svelte
Normal file
13
src/lib/components/ui/calendar/calendar-grid-body.svelte
Normal file
|
|
@ -0,0 +1,13 @@
|
||||||
|
<script lang="ts">
|
||||||
|
import { Calendar as CalendarPrimitive } from "bits-ui";
|
||||||
|
import { cn } from "$lib/utils/ui.js";
|
||||||
|
|
||||||
|
type $$Props = CalendarPrimitive.GridBodyProps;
|
||||||
|
|
||||||
|
let className: $$Props["class"] = undefined;
|
||||||
|
export { className as class };
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<CalendarPrimitive.GridBody class={cn(className)} {...$$restProps}>
|
||||||
|
<slot />
|
||||||
|
</CalendarPrimitive.GridBody>
|
||||||
13
src/lib/components/ui/calendar/calendar-grid-head.svelte
Normal file
13
src/lib/components/ui/calendar/calendar-grid-head.svelte
Normal file
|
|
@ -0,0 +1,13 @@
|
||||||
|
<script lang="ts">
|
||||||
|
import { Calendar as CalendarPrimitive } from "bits-ui";
|
||||||
|
import { cn } from "$lib/utils/ui.js";
|
||||||
|
|
||||||
|
type $$Props = CalendarPrimitive.GridHeadProps;
|
||||||
|
|
||||||
|
let className: $$Props["class"] = undefined;
|
||||||
|
export { className as class };
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<CalendarPrimitive.GridHead class={cn(className)} {...$$restProps}>
|
||||||
|
<slot />
|
||||||
|
</CalendarPrimitive.GridHead>
|
||||||
13
src/lib/components/ui/calendar/calendar-grid-row.svelte
Normal file
13
src/lib/components/ui/calendar/calendar-grid-row.svelte
Normal file
|
|
@ -0,0 +1,13 @@
|
||||||
|
<script lang="ts">
|
||||||
|
import { Calendar as CalendarPrimitive } from "bits-ui";
|
||||||
|
import { cn } from "$lib/utils/ui.js";
|
||||||
|
|
||||||
|
type $$Props = CalendarPrimitive.GridRowProps;
|
||||||
|
|
||||||
|
let className: $$Props["class"] = undefined;
|
||||||
|
export { className as class };
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<CalendarPrimitive.GridRow class={cn("flex", className)} {...$$restProps}>
|
||||||
|
<slot />
|
||||||
|
</CalendarPrimitive.GridRow>
|
||||||
13
src/lib/components/ui/calendar/calendar-grid.svelte
Normal file
13
src/lib/components/ui/calendar/calendar-grid.svelte
Normal file
|
|
@ -0,0 +1,13 @@
|
||||||
|
<script lang="ts">
|
||||||
|
import { Calendar as CalendarPrimitive } from "bits-ui";
|
||||||
|
import { cn } from "$lib/utils/ui.js";
|
||||||
|
|
||||||
|
type $$Props = CalendarPrimitive.GridProps;
|
||||||
|
|
||||||
|
let className: $$Props["class"] = undefined;
|
||||||
|
export { className as class };
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<CalendarPrimitive.Grid class={cn("w-full border-collapse space-y-1", className)} {...$$restProps}>
|
||||||
|
<slot />
|
||||||
|
</CalendarPrimitive.Grid>
|
||||||
16
src/lib/components/ui/calendar/calendar-head-cell.svelte
Normal file
16
src/lib/components/ui/calendar/calendar-head-cell.svelte
Normal file
|
|
@ -0,0 +1,16 @@
|
||||||
|
<script lang="ts">
|
||||||
|
import { Calendar as CalendarPrimitive } from "bits-ui";
|
||||||
|
import { cn } from "$lib/utils/ui.js";
|
||||||
|
|
||||||
|
type $$Props = CalendarPrimitive.HeadCellProps;
|
||||||
|
|
||||||
|
let className: $$Props["class"] = undefined;
|
||||||
|
export { className as class };
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<CalendarPrimitive.HeadCell
|
||||||
|
class={cn("w-9 rounded-md text-[0.8rem] font-normal text-muted-foreground", className)}
|
||||||
|
{...$$restProps}
|
||||||
|
>
|
||||||
|
<slot />
|
||||||
|
</CalendarPrimitive.HeadCell>
|
||||||
16
src/lib/components/ui/calendar/calendar-header.svelte
Normal file
16
src/lib/components/ui/calendar/calendar-header.svelte
Normal file
|
|
@ -0,0 +1,16 @@
|
||||||
|
<script lang="ts">
|
||||||
|
import { Calendar as CalendarPrimitive } from "bits-ui";
|
||||||
|
import { cn } from "$lib/utils/ui.js";
|
||||||
|
|
||||||
|
type $$Props = CalendarPrimitive.HeaderProps;
|
||||||
|
|
||||||
|
let className: $$Props["class"] = undefined;
|
||||||
|
export { className as class };
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<CalendarPrimitive.Header
|
||||||
|
class={cn("relative flex w-full items-center justify-between pt-1", className)}
|
||||||
|
{...$$restProps}
|
||||||
|
>
|
||||||
|
<slot />
|
||||||
|
</CalendarPrimitive.Header>
|
||||||
19
src/lib/components/ui/calendar/calendar-heading.svelte
Normal file
19
src/lib/components/ui/calendar/calendar-heading.svelte
Normal file
|
|
@ -0,0 +1,19 @@
|
||||||
|
<script lang="ts">
|
||||||
|
import { Calendar as CalendarPrimitive } from "bits-ui";
|
||||||
|
import { cn } from "$lib/utils/ui.js";
|
||||||
|
|
||||||
|
type $$Props = CalendarPrimitive.HeadingProps;
|
||||||
|
|
||||||
|
let className: $$Props["class"] = undefined;
|
||||||
|
export { className as class };
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<CalendarPrimitive.Heading
|
||||||
|
let:headingValue
|
||||||
|
class={cn("text-sm font-medium", className)}
|
||||||
|
{...$$restProps}
|
||||||
|
>
|
||||||
|
<slot {headingValue}>
|
||||||
|
{headingValue}
|
||||||
|
</slot>
|
||||||
|
</CalendarPrimitive.Heading>
|
||||||
16
src/lib/components/ui/calendar/calendar-months.svelte
Normal file
16
src/lib/components/ui/calendar/calendar-months.svelte
Normal file
|
|
@ -0,0 +1,16 @@
|
||||||
|
<script lang="ts">
|
||||||
|
import type { HTMLAttributes } from "svelte/elements";
|
||||||
|
import { cn } from "$lib/utils/ui.js";
|
||||||
|
|
||||||
|
type $$Props = HTMLAttributes<HTMLDivElement>;
|
||||||
|
|
||||||
|
let className: $$Props["class"] = undefined;
|
||||||
|
export { className as class };
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<div
|
||||||
|
class={cn("mt-4 flex flex-col space-y-4 sm:flex-row sm:space-x-4 sm:space-y-0", className)}
|
||||||
|
{...$$restProps}
|
||||||
|
>
|
||||||
|
<slot />
|
||||||
|
</div>
|
||||||
27
src/lib/components/ui/calendar/calendar-next-button.svelte
Normal file
27
src/lib/components/ui/calendar/calendar-next-button.svelte
Normal file
|
|
@ -0,0 +1,27 @@
|
||||||
|
<script lang="ts">
|
||||||
|
import { Calendar as CalendarPrimitive } from "bits-ui";
|
||||||
|
import ChevronRight from "lucide-svelte/icons/chevron-right";
|
||||||
|
import { buttonVariants } from "$lib/components/ui/button/index.js";
|
||||||
|
import { cn } from "$lib/utils/ui.js";
|
||||||
|
|
||||||
|
type $$Props = CalendarPrimitive.NextButtonProps;
|
||||||
|
type $$Events = CalendarPrimitive.NextButtonEvents;
|
||||||
|
|
||||||
|
let className: $$Props["class"] = undefined;
|
||||||
|
export { className as class };
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<CalendarPrimitive.NextButton
|
||||||
|
on:click
|
||||||
|
class={cn(
|
||||||
|
buttonVariants({ variant: "outline" }),
|
||||||
|
"h-7 w-7 bg-transparent p-0 opacity-50 hover:opacity-100",
|
||||||
|
className
|
||||||
|
)}
|
||||||
|
{...$$restProps}
|
||||||
|
let:builder
|
||||||
|
>
|
||||||
|
<slot {builder}>
|
||||||
|
<ChevronRight class="h-4 w-4" />
|
||||||
|
</slot>
|
||||||
|
</CalendarPrimitive.NextButton>
|
||||||
27
src/lib/components/ui/calendar/calendar-prev-button.svelte
Normal file
27
src/lib/components/ui/calendar/calendar-prev-button.svelte
Normal file
|
|
@ -0,0 +1,27 @@
|
||||||
|
<script lang="ts">
|
||||||
|
import { Calendar as CalendarPrimitive } from "bits-ui";
|
||||||
|
import ChevronLeft from "lucide-svelte/icons/chevron-left";
|
||||||
|
import { buttonVariants } from "$lib/components/ui/button/index.js";
|
||||||
|
import { cn } from "$lib/utils/ui.js";
|
||||||
|
|
||||||
|
type $$Props = CalendarPrimitive.PrevButtonProps;
|
||||||
|
type $$Events = CalendarPrimitive.PrevButtonEvents;
|
||||||
|
|
||||||
|
let className: $$Props["class"] = undefined;
|
||||||
|
export { className as class };
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<CalendarPrimitive.PrevButton
|
||||||
|
on:click
|
||||||
|
class={cn(
|
||||||
|
buttonVariants({ variant: "outline" }),
|
||||||
|
"h-7 w-7 bg-transparent p-0 opacity-50 hover:opacity-100",
|
||||||
|
className
|
||||||
|
)}
|
||||||
|
{...$$restProps}
|
||||||
|
let:builder
|
||||||
|
>
|
||||||
|
<slot {builder}>
|
||||||
|
<ChevronLeft class="h-4 w-4" />
|
||||||
|
</slot>
|
||||||
|
</CalendarPrimitive.PrevButton>
|
||||||
59
src/lib/components/ui/calendar/calendar.svelte
Normal file
59
src/lib/components/ui/calendar/calendar.svelte
Normal file
|
|
@ -0,0 +1,59 @@
|
||||||
|
<script lang="ts">
|
||||||
|
import { Calendar as CalendarPrimitive } from "bits-ui";
|
||||||
|
import * as Calendar from "./index.js";
|
||||||
|
import { cn } from "$lib/utils/ui.js";
|
||||||
|
|
||||||
|
type $$Props = CalendarPrimitive.Props;
|
||||||
|
|
||||||
|
type $$Events = CalendarPrimitive.Events;
|
||||||
|
|
||||||
|
export let value: $$Props["value"] = undefined;
|
||||||
|
export let placeholder: $$Props["placeholder"] = undefined;
|
||||||
|
export let weekdayFormat: $$Props["weekdayFormat"] = "short";
|
||||||
|
|
||||||
|
let className: $$Props["class"] = undefined;
|
||||||
|
export { className as class };
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<CalendarPrimitive.Root
|
||||||
|
bind:value
|
||||||
|
bind:placeholder
|
||||||
|
{weekdayFormat}
|
||||||
|
class={cn("p-3", className)}
|
||||||
|
{...$$restProps}
|
||||||
|
on:keydown
|
||||||
|
let:months
|
||||||
|
let:weekdays
|
||||||
|
>
|
||||||
|
<Calendar.Header>
|
||||||
|
<Calendar.PrevButton />
|
||||||
|
<Calendar.Heading />
|
||||||
|
<Calendar.NextButton />
|
||||||
|
</Calendar.Header>
|
||||||
|
<Calendar.Months>
|
||||||
|
{#each months as month}
|
||||||
|
<Calendar.Grid>
|
||||||
|
<Calendar.GridHead>
|
||||||
|
<Calendar.GridRow class="flex">
|
||||||
|
{#each weekdays as weekday}
|
||||||
|
<Calendar.HeadCell>
|
||||||
|
{weekday.slice(0, 2)}
|
||||||
|
</Calendar.HeadCell>
|
||||||
|
{/each}
|
||||||
|
</Calendar.GridRow>
|
||||||
|
</Calendar.GridHead>
|
||||||
|
<Calendar.GridBody>
|
||||||
|
{#each month.weeks as weekDates}
|
||||||
|
<Calendar.GridRow class="mt-2 w-full">
|
||||||
|
{#each weekDates as date}
|
||||||
|
<Calendar.Cell {date}>
|
||||||
|
<Calendar.Day {date} month={month.value} />
|
||||||
|
</Calendar.Cell>
|
||||||
|
{/each}
|
||||||
|
</Calendar.GridRow>
|
||||||
|
{/each}
|
||||||
|
</Calendar.GridBody>
|
||||||
|
</Calendar.Grid>
|
||||||
|
{/each}
|
||||||
|
</Calendar.Months>
|
||||||
|
</CalendarPrimitive.Root>
|
||||||
30
src/lib/components/ui/calendar/index.ts
Normal file
30
src/lib/components/ui/calendar/index.ts
Normal file
|
|
@ -0,0 +1,30 @@
|
||||||
|
import Root from "./calendar.svelte";
|
||||||
|
import Cell from "./calendar-cell.svelte";
|
||||||
|
import Day from "./calendar-day.svelte";
|
||||||
|
import Grid from "./calendar-grid.svelte";
|
||||||
|
import Header from "./calendar-header.svelte";
|
||||||
|
import Months from "./calendar-months.svelte";
|
||||||
|
import GridRow from "./calendar-grid-row.svelte";
|
||||||
|
import Heading from "./calendar-heading.svelte";
|
||||||
|
import GridBody from "./calendar-grid-body.svelte";
|
||||||
|
import GridHead from "./calendar-grid-head.svelte";
|
||||||
|
import HeadCell from "./calendar-head-cell.svelte";
|
||||||
|
import NextButton from "./calendar-next-button.svelte";
|
||||||
|
import PrevButton from "./calendar-prev-button.svelte";
|
||||||
|
|
||||||
|
export {
|
||||||
|
Day,
|
||||||
|
Cell,
|
||||||
|
Grid,
|
||||||
|
Header,
|
||||||
|
Months,
|
||||||
|
GridRow,
|
||||||
|
Heading,
|
||||||
|
GridBody,
|
||||||
|
GridHead,
|
||||||
|
HeadCell,
|
||||||
|
NextButton,
|
||||||
|
PrevButton,
|
||||||
|
//
|
||||||
|
Root as Calendar,
|
||||||
|
};
|
||||||
13
src/lib/components/ui/card/card-content.svelte
Normal file
13
src/lib/components/ui/card/card-content.svelte
Normal file
|
|
@ -0,0 +1,13 @@
|
||||||
|
<script lang="ts">
|
||||||
|
import type { HTMLAttributes } from "svelte/elements";
|
||||||
|
import { cn } from "$lib/utils/ui.js";
|
||||||
|
|
||||||
|
type $$Props = HTMLAttributes<HTMLDivElement>;
|
||||||
|
|
||||||
|
let className: $$Props["class"] = undefined;
|
||||||
|
export { className as class };
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<div class={cn("p-6 pt-0", className)} {...$$restProps}>
|
||||||
|
<slot />
|
||||||
|
</div>
|
||||||
13
src/lib/components/ui/card/card-description.svelte
Normal file
13
src/lib/components/ui/card/card-description.svelte
Normal file
|
|
@ -0,0 +1,13 @@
|
||||||
|
<script lang="ts">
|
||||||
|
import type { HTMLAttributes } from "svelte/elements";
|
||||||
|
import { cn } from "$lib/utils/ui.js";
|
||||||
|
|
||||||
|
type $$Props = HTMLAttributes<HTMLParagraphElement>;
|
||||||
|
|
||||||
|
let className: $$Props["class"] = undefined;
|
||||||
|
export { className as class };
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<p class={cn("text-sm text-muted-foreground", className)} {...$$restProps}>
|
||||||
|
<slot />
|
||||||
|
</p>
|
||||||
13
src/lib/components/ui/card/card-footer.svelte
Normal file
13
src/lib/components/ui/card/card-footer.svelte
Normal file
|
|
@ -0,0 +1,13 @@
|
||||||
|
<script lang="ts">
|
||||||
|
import type { HTMLAttributes } from "svelte/elements";
|
||||||
|
import { cn } from "$lib/utils/ui.js";
|
||||||
|
|
||||||
|
type $$Props = HTMLAttributes<HTMLDivElement>;
|
||||||
|
|
||||||
|
let className: $$Props["class"] = undefined;
|
||||||
|
export { className as class };
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<div class={cn("flex items-center p-6 pt-0", className)} {...$$restProps}>
|
||||||
|
<slot />
|
||||||
|
</div>
|
||||||
13
src/lib/components/ui/card/card-header.svelte
Normal file
13
src/lib/components/ui/card/card-header.svelte
Normal file
|
|
@ -0,0 +1,13 @@
|
||||||
|
<script lang="ts">
|
||||||
|
import type { HTMLAttributes } from "svelte/elements";
|
||||||
|
import { cn } from "$lib/utils/ui.js";
|
||||||
|
|
||||||
|
type $$Props = HTMLAttributes<HTMLDivElement>;
|
||||||
|
|
||||||
|
let className: $$Props["class"] = undefined;
|
||||||
|
export { className as class };
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<div class={cn("flex flex-col space-y-1.5 p-6", className)} {...$$restProps}>
|
||||||
|
<slot />
|
||||||
|
</div>
|
||||||
21
src/lib/components/ui/card/card-title.svelte
Normal file
21
src/lib/components/ui/card/card-title.svelte
Normal file
|
|
@ -0,0 +1,21 @@
|
||||||
|
<script lang="ts">
|
||||||
|
import type { HTMLAttributes } from "svelte/elements";
|
||||||
|
import type { HeadingLevel } from "./index.js";
|
||||||
|
import { cn } from "$lib/utils/ui.js";
|
||||||
|
|
||||||
|
type $$Props = HTMLAttributes<HTMLHeadingElement> & {
|
||||||
|
tag?: HeadingLevel;
|
||||||
|
};
|
||||||
|
|
||||||
|
let className: $$Props["class"] = undefined;
|
||||||
|
export let tag: $$Props["tag"] = "h3";
|
||||||
|
export { className as class };
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<svelte:element
|
||||||
|
this={tag}
|
||||||
|
class={cn("text-lg font-semibold leading-none tracking-tight", className)}
|
||||||
|
{...$$restProps}
|
||||||
|
>
|
||||||
|
<slot />
|
||||||
|
</svelte:element>
|
||||||
16
src/lib/components/ui/card/card.svelte
Normal file
16
src/lib/components/ui/card/card.svelte
Normal file
|
|
@ -0,0 +1,16 @@
|
||||||
|
<script lang="ts">
|
||||||
|
import type { HTMLAttributes } from "svelte/elements";
|
||||||
|
import { cn } from "$lib/utils/ui.js";
|
||||||
|
|
||||||
|
type $$Props = HTMLAttributes<HTMLDivElement>;
|
||||||
|
|
||||||
|
let className: $$Props["class"] = undefined;
|
||||||
|
export { className as class };
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<div
|
||||||
|
class={cn("rounded-lg border bg-card text-card-foreground shadow-sm", className)}
|
||||||
|
{...$$restProps}
|
||||||
|
>
|
||||||
|
<slot />
|
||||||
|
</div>
|
||||||
24
src/lib/components/ui/card/index.ts
Normal file
24
src/lib/components/ui/card/index.ts
Normal file
|
|
@ -0,0 +1,24 @@
|
||||||
|
import Root from "./card.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";
|
||||||
|
|
||||||
|
export {
|
||||||
|
Root,
|
||||||
|
Content,
|
||||||
|
Description,
|
||||||
|
Footer,
|
||||||
|
Header,
|
||||||
|
Title,
|
||||||
|
//
|
||||||
|
Root as Card,
|
||||||
|
Content as CardContent,
|
||||||
|
Description as CardDescription,
|
||||||
|
Footer as CardFooter,
|
||||||
|
Header as CardHeader,
|
||||||
|
Title as CardTitle,
|
||||||
|
};
|
||||||
|
|
||||||
|
export type HeadingLevel = "h1" | "h2" | "h3" | "h4" | "h5" | "h6";
|
||||||
35
src/lib/components/ui/carousel/carousel-content.svelte
Normal file
35
src/lib/components/ui/carousel/carousel-content.svelte
Normal file
|
|
@ -0,0 +1,35 @@
|
||||||
|
<script lang="ts">
|
||||||
|
import type { HTMLAttributes } from "svelte/elements";
|
||||||
|
import emblaCarouselSvelte from "embla-carousel-svelte";
|
||||||
|
import { getEmblaContext } from "./context.js";
|
||||||
|
import { cn } from "$lib/utils/ui.js";
|
||||||
|
|
||||||
|
type $$Props = HTMLAttributes<HTMLDivElement>;
|
||||||
|
|
||||||
|
let className: string | undefined | null = undefined;
|
||||||
|
export { className as class };
|
||||||
|
|
||||||
|
const { orientation, options, plugins, onInit } = getEmblaContext("<Carousel.Content/>");
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<div
|
||||||
|
class="overflow-hidden"
|
||||||
|
use:emblaCarouselSvelte={{
|
||||||
|
options: {
|
||||||
|
container: "[data-embla-container]",
|
||||||
|
slides: "[data-embla-slide]",
|
||||||
|
...$options,
|
||||||
|
axis: $orientation === "horizontal" ? "x" : "y",
|
||||||
|
},
|
||||||
|
plugins: $plugins,
|
||||||
|
}}
|
||||||
|
on:emblaInit={onInit}
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
class={cn("flex", $orientation === "horizontal" ? "-ml-4" : "-mt-4 flex-col", className)}
|
||||||
|
data-embla-container=""
|
||||||
|
{...$$restProps}
|
||||||
|
>
|
||||||
|
<slot />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
25
src/lib/components/ui/carousel/carousel-item.svelte
Normal file
25
src/lib/components/ui/carousel/carousel-item.svelte
Normal file
|
|
@ -0,0 +1,25 @@
|
||||||
|
<script lang="ts">
|
||||||
|
import type { HTMLAttributes } from "svelte/elements";
|
||||||
|
import { getEmblaContext } from "./context.js";
|
||||||
|
import { cn } from "$lib/utils/ui.js";
|
||||||
|
|
||||||
|
type $$Props = HTMLAttributes<HTMLDivElement>;
|
||||||
|
let className: string | undefined | null = undefined;
|
||||||
|
export { className as class };
|
||||||
|
|
||||||
|
const { orientation } = getEmblaContext("<Carousel.Item/>");
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<div
|
||||||
|
role="group"
|
||||||
|
aria-roledescription="slide"
|
||||||
|
class={cn(
|
||||||
|
"min-w-0 shrink-0 grow-0 basis-full",
|
||||||
|
$orientation === "horizontal" ? "pl-4" : "pt-4",
|
||||||
|
className
|
||||||
|
)}
|
||||||
|
data-embla-slide=""
|
||||||
|
{...$$restProps}
|
||||||
|
>
|
||||||
|
<slot />
|
||||||
|
</div>
|
||||||
39
src/lib/components/ui/carousel/carousel-next.svelte
Normal file
39
src/lib/components/ui/carousel/carousel-next.svelte
Normal file
|
|
@ -0,0 +1,39 @@
|
||||||
|
<script lang="ts">
|
||||||
|
import ArrowRight from "lucide-svelte/icons/arrow-right";
|
||||||
|
import type { VariantProps } from "tailwind-variants";
|
||||||
|
import { getEmblaContext } from "./context.js";
|
||||||
|
import { cn } from "$lib/utils/ui.js";
|
||||||
|
import {
|
||||||
|
Button,
|
||||||
|
type Props,
|
||||||
|
type buttonVariants,
|
||||||
|
} from "$lib/components/ui/button/index.js";
|
||||||
|
|
||||||
|
type $$Props = Props;
|
||||||
|
|
||||||
|
let className: $$Props["class"] = undefined;
|
||||||
|
export { className as class };
|
||||||
|
export let variant: VariantProps<typeof buttonVariants>["variant"] = "outline";
|
||||||
|
export let size: VariantProps<typeof buttonVariants>["size"] = "icon";
|
||||||
|
const { orientation, canScrollNext, scrollNext, handleKeyDown } =
|
||||||
|
getEmblaContext("<Carousel.Next/>");
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<Button
|
||||||
|
{variant}
|
||||||
|
{size}
|
||||||
|
class={cn(
|
||||||
|
"absolute h-8 w-8 touch-manipulation rounded-full",
|
||||||
|
$orientation === "horizontal"
|
||||||
|
? "-right-12 top-1/2 -translate-y-1/2"
|
||||||
|
: "-bottom-12 left-1/2 -translate-x-1/2 rotate-90",
|
||||||
|
className
|
||||||
|
)}
|
||||||
|
disabled={!$canScrollNext}
|
||||||
|
on:click={scrollNext}
|
||||||
|
on:keydown={handleKeyDown}
|
||||||
|
{...$$restProps}
|
||||||
|
>
|
||||||
|
<ArrowRight class="h-4 w-4" />
|
||||||
|
<span class="sr-only">Next slide</span>
|
||||||
|
</Button>
|
||||||
40
src/lib/components/ui/carousel/carousel-previous.svelte
Normal file
40
src/lib/components/ui/carousel/carousel-previous.svelte
Normal file
|
|
@ -0,0 +1,40 @@
|
||||||
|
<script lang="ts">
|
||||||
|
import ArrowLeft from "lucide-svelte/icons/arrow-left";
|
||||||
|
import type { VariantProps } from "tailwind-variants";
|
||||||
|
import { getEmblaContext } from "./context.js";
|
||||||
|
import { cn } from "$lib/utils/ui.js";
|
||||||
|
import {
|
||||||
|
Button,
|
||||||
|
type Props,
|
||||||
|
type buttonVariants,
|
||||||
|
} from "$lib/components/ui/button/index.js";
|
||||||
|
|
||||||
|
type $$Props = Props;
|
||||||
|
|
||||||
|
let className: $$Props["class"] = undefined;
|
||||||
|
export { className as class };
|
||||||
|
export let variant: VariantProps<typeof buttonVariants>["variant"] = "outline";
|
||||||
|
export let size: VariantProps<typeof buttonVariants>["size"] = "icon";
|
||||||
|
|
||||||
|
const { orientation, canScrollPrev, scrollPrev, handleKeyDown } =
|
||||||
|
getEmblaContext("<Carousel.Previous/>");
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<Button
|
||||||
|
{variant}
|
||||||
|
{size}
|
||||||
|
class={cn(
|
||||||
|
"absolute h-8 w-8 touch-manipulation rounded-full",
|
||||||
|
$orientation === "horizontal"
|
||||||
|
? "-left-12 top-1/2 -translate-y-1/2"
|
||||||
|
: "-top-12 left-1/2 -translate-x-1/2 rotate-90",
|
||||||
|
className
|
||||||
|
)}
|
||||||
|
disabled={!$canScrollPrev}
|
||||||
|
on:click={scrollPrev}
|
||||||
|
on:keydown={handleKeyDown}
|
||||||
|
{...$$restProps}
|
||||||
|
>
|
||||||
|
<ArrowLeft class="h-4 w-4" />
|
||||||
|
<span class="sr-only">Previous slide</span>
|
||||||
|
</Button>
|
||||||
98
src/lib/components/ui/carousel/carousel.svelte
Normal file
98
src/lib/components/ui/carousel/carousel.svelte
Normal file
|
|
@ -0,0 +1,98 @@
|
||||||
|
<script lang="ts">
|
||||||
|
import { writable } from "svelte/store";
|
||||||
|
import { onDestroy } from "svelte";
|
||||||
|
import { type CarouselAPI, type CarouselProps, setEmblaContext } from "./context.js";
|
||||||
|
import { cn } from "$lib/utils/ui.js";
|
||||||
|
|
||||||
|
type $$Props = CarouselProps;
|
||||||
|
|
||||||
|
export let opts = {};
|
||||||
|
export let plugins: NonNullable<$$Props["plugins"]> = [];
|
||||||
|
export let api: $$Props["api"] = undefined;
|
||||||
|
export let orientation: NonNullable<$$Props["orientation"]> = "horizontal";
|
||||||
|
|
||||||
|
let className: $$Props["class"] = undefined;
|
||||||
|
export { className as class };
|
||||||
|
|
||||||
|
const apiStore = writable<CarouselAPI | undefined>(undefined);
|
||||||
|
const orientationStore = writable(orientation);
|
||||||
|
const canScrollPrev = writable(false);
|
||||||
|
const canScrollNext = writable(false);
|
||||||
|
const optionsStore = writable(opts);
|
||||||
|
const pluginStore = writable(plugins);
|
||||||
|
const scrollSnapsStore = writable<number[]>([]);
|
||||||
|
const selectedIndexStore = writable(0);
|
||||||
|
|
||||||
|
$: orientationStore.set(orientation);
|
||||||
|
$: pluginStore.set(plugins);
|
||||||
|
$: optionsStore.set(opts);
|
||||||
|
|
||||||
|
function scrollPrev() {
|
||||||
|
api?.scrollPrev();
|
||||||
|
}
|
||||||
|
function scrollNext() {
|
||||||
|
api?.scrollNext();
|
||||||
|
}
|
||||||
|
function scrollTo(index: number, jump?: boolean) {
|
||||||
|
api?.scrollTo(index, jump);
|
||||||
|
}
|
||||||
|
|
||||||
|
function onSelect(api: CarouselAPI) {
|
||||||
|
if (!api) return;
|
||||||
|
canScrollPrev.set(api.canScrollPrev());
|
||||||
|
canScrollNext.set(api.canScrollNext());
|
||||||
|
}
|
||||||
|
|
||||||
|
$: if (api) {
|
||||||
|
onSelect(api);
|
||||||
|
api.on("select", onSelect);
|
||||||
|
api.on("reInit", onSelect);
|
||||||
|
}
|
||||||
|
|
||||||
|
function handleKeyDown(e: KeyboardEvent) {
|
||||||
|
if (e.key === "ArrowLeft") {
|
||||||
|
e.preventDefault();
|
||||||
|
scrollPrev();
|
||||||
|
} else if (e.key === "ArrowRight") {
|
||||||
|
e.preventDefault();
|
||||||
|
scrollNext();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
setEmblaContext({
|
||||||
|
api: apiStore,
|
||||||
|
scrollPrev,
|
||||||
|
scrollNext,
|
||||||
|
orientation: orientationStore,
|
||||||
|
canScrollNext,
|
||||||
|
canScrollPrev,
|
||||||
|
handleKeyDown,
|
||||||
|
options: optionsStore,
|
||||||
|
plugins: pluginStore,
|
||||||
|
onInit,
|
||||||
|
scrollSnaps: scrollSnapsStore,
|
||||||
|
selectedIndex: selectedIndexStore,
|
||||||
|
scrollTo,
|
||||||
|
});
|
||||||
|
|
||||||
|
function onInit(event: CustomEvent<CarouselAPI>) {
|
||||||
|
api = event.detail;
|
||||||
|
apiStore.set(api);
|
||||||
|
scrollSnapsStore.set(api.scrollSnapList());
|
||||||
|
}
|
||||||
|
|
||||||
|
onDestroy(() => {
|
||||||
|
api?.off("select", onSelect);
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<div
|
||||||
|
class={cn("relative", className)}
|
||||||
|
on:mouseenter
|
||||||
|
on:mouseleave
|
||||||
|
role="region"
|
||||||
|
aria-roledescription="carousel"
|
||||||
|
{...$$restProps}
|
||||||
|
>
|
||||||
|
<slot />
|
||||||
|
</div>
|
||||||
56
src/lib/components/ui/carousel/context.ts
Normal file
56
src/lib/components/ui/carousel/context.ts
Normal file
|
|
@ -0,0 +1,56 @@
|
||||||
|
import type { EmblaCarouselSvelteType } from "embla-carousel-svelte";
|
||||||
|
import type emblaCarouselSvelte from "embla-carousel-svelte";
|
||||||
|
import { getContext, hasContext, setContext } from "svelte";
|
||||||
|
import type { HTMLAttributes } from "svelte/elements";
|
||||||
|
import type { Readable, Writable } from "svelte/store";
|
||||||
|
|
||||||
|
export type CarouselAPI =
|
||||||
|
NonNullable<NonNullable<EmblaCarouselSvelteType["$$_attributes"]>["on:emblaInit"]> extends (
|
||||||
|
evt: CustomEvent<infer CarouselAPI>
|
||||||
|
) => void
|
||||||
|
? CarouselAPI
|
||||||
|
: never;
|
||||||
|
|
||||||
|
type EmblaCarouselConfig = NonNullable<Parameters<typeof emblaCarouselSvelte>[1]>;
|
||||||
|
|
||||||
|
export type CarouselOptions = EmblaCarouselConfig["options"];
|
||||||
|
export type CarouselPlugins = EmblaCarouselConfig["plugins"];
|
||||||
|
|
||||||
|
////
|
||||||
|
|
||||||
|
export type CarouselProps = {
|
||||||
|
opts?: CarouselOptions;
|
||||||
|
plugins?: CarouselPlugins;
|
||||||
|
api?: CarouselAPI;
|
||||||
|
orientation?: "horizontal" | "vertical";
|
||||||
|
} & HTMLAttributes<HTMLDivElement>;
|
||||||
|
|
||||||
|
const EMBLA_CAROUSEL_CONTEXT = Symbol("EMBLA_CAROUSEL_CONTEXT");
|
||||||
|
|
||||||
|
type EmblaContext = {
|
||||||
|
api: Writable<CarouselAPI | undefined>;
|
||||||
|
orientation: Writable<"horizontal" | "vertical">;
|
||||||
|
scrollNext: () => void;
|
||||||
|
scrollPrev: () => void;
|
||||||
|
canScrollNext: Readable<boolean>;
|
||||||
|
canScrollPrev: Readable<boolean>;
|
||||||
|
handleKeyDown: (e: KeyboardEvent) => void;
|
||||||
|
options: Writable<CarouselOptions>;
|
||||||
|
plugins: Writable<CarouselPlugins>;
|
||||||
|
onInit: (e: CustomEvent<CarouselAPI>) => void;
|
||||||
|
scrollTo: (index: number, jump?: boolean) => void;
|
||||||
|
scrollSnaps: Readable<number[]>;
|
||||||
|
selectedIndex: Readable<number>;
|
||||||
|
};
|
||||||
|
|
||||||
|
export function setEmblaContext(config: EmblaContext): EmblaContext {
|
||||||
|
setContext(EMBLA_CAROUSEL_CONTEXT, config);
|
||||||
|
return config;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function getEmblaContext(name = "This component") {
|
||||||
|
if (!hasContext(EMBLA_CAROUSEL_CONTEXT)) {
|
||||||
|
throw new Error(`${name} must be used within a <Carousel.Root> component`);
|
||||||
|
}
|
||||||
|
return getContext<ReturnType<typeof setEmblaContext>>(EMBLA_CAROUSEL_CONTEXT);
|
||||||
|
}
|
||||||
5
src/lib/components/ui/carousel/index.ts
Normal file
5
src/lib/components/ui/carousel/index.ts
Normal file
|
|
@ -0,0 +1,5 @@
|
||||||
|
export { default as Root } from "./carousel.svelte";
|
||||||
|
export { default as Content } from "./carousel-content.svelte";
|
||||||
|
export { default as Item } from "./carousel-item.svelte";
|
||||||
|
export { default as Previous } from "./carousel-previous.svelte";
|
||||||
|
export { default as Next } from "./carousel-next.svelte";
|
||||||
35
src/lib/components/ui/checkbox/checkbox.svelte
Normal file
35
src/lib/components/ui/checkbox/checkbox.svelte
Normal file
|
|
@ -0,0 +1,35 @@
|
||||||
|
<script lang="ts">
|
||||||
|
import { Checkbox as CheckboxPrimitive } from "bits-ui";
|
||||||
|
import Check from "lucide-svelte/icons/check";
|
||||||
|
import Minus from "lucide-svelte/icons/minus";
|
||||||
|
import { cn } from "$lib/utils/ui.js";
|
||||||
|
|
||||||
|
type $$Props = CheckboxPrimitive.Props;
|
||||||
|
type $$Events = CheckboxPrimitive.Events;
|
||||||
|
|
||||||
|
let className: $$Props["class"] = undefined;
|
||||||
|
export let checked: $$Props["checked"] = false;
|
||||||
|
export { className as class };
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<CheckboxPrimitive.Root
|
||||||
|
class={cn(
|
||||||
|
"peer box-content h-4 w-4 shrink-0 rounded-sm border border-primary ring-offset-background focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50 data-[disabled=true]:cursor-not-allowed data-[state=checked]:bg-primary data-[state=checked]:text-primary-foreground data-[disabled=true]:opacity-50",
|
||||||
|
className
|
||||||
|
)}
|
||||||
|
bind:checked
|
||||||
|
{...$$restProps}
|
||||||
|
on:click
|
||||||
|
>
|
||||||
|
<CheckboxPrimitive.Indicator
|
||||||
|
class={cn("flex h-4 w-4 items-center justify-center text-current")}
|
||||||
|
let:isChecked
|
||||||
|
let:isIndeterminate
|
||||||
|
>
|
||||||
|
{#if isChecked}
|
||||||
|
<Check class="h-3.5 w-3.5" />
|
||||||
|
{:else if isIndeterminate}
|
||||||
|
<Minus class="h-3.5 w-3.5" />
|
||||||
|
{/if}
|
||||||
|
</CheckboxPrimitive.Indicator>
|
||||||
|
</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,
|
||||||
|
};
|
||||||
15
src/lib/components/ui/collapsible/collapsible-content.svelte
Normal file
15
src/lib/components/ui/collapsible/collapsible-content.svelte
Normal file
|
|
@ -0,0 +1,15 @@
|
||||||
|
<script lang="ts">
|
||||||
|
import { Collapsible as CollapsiblePrimitive } from "bits-ui";
|
||||||
|
import { slide } from "svelte/transition";
|
||||||
|
|
||||||
|
type $$Props = CollapsiblePrimitive.ContentProps;
|
||||||
|
|
||||||
|
export let transition: $$Props["transition"] = slide;
|
||||||
|
export let transitionConfig: $$Props["transitionConfig"] = {
|
||||||
|
duration: 150,
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<CollapsiblePrimitive.Content {transition} {transitionConfig} {...$$restProps}>
|
||||||
|
<slot />
|
||||||
|
</CollapsiblePrimitive.Content>
|
||||||
15
src/lib/components/ui/collapsible/index.ts
Normal file
15
src/lib/components/ui/collapsible/index.ts
Normal file
|
|
@ -0,0 +1,15 @@
|
||||||
|
import { Collapsible as CollapsiblePrimitive } from "bits-ui";
|
||||||
|
import Content from "./collapsible-content.svelte";
|
||||||
|
|
||||||
|
const Root = CollapsiblePrimitive.Root;
|
||||||
|
const Trigger = CollapsiblePrimitive.Trigger;
|
||||||
|
|
||||||
|
export {
|
||||||
|
Root,
|
||||||
|
Content,
|
||||||
|
Trigger,
|
||||||
|
//
|
||||||
|
Root as Collapsible,
|
||||||
|
Content as CollapsibleContent,
|
||||||
|
Trigger as CollapsibleTrigger,
|
||||||
|
};
|
||||||
23
src/lib/components/ui/command/command-dialog.svelte
Normal file
23
src/lib/components/ui/command/command-dialog.svelte
Normal file
|
|
@ -0,0 +1,23 @@
|
||||||
|
<script lang="ts">
|
||||||
|
import type { Dialog as DialogPrimitive } from "bits-ui";
|
||||||
|
import type { Command as CommandPrimitive } from "cmdk-sv";
|
||||||
|
import Command from "./command.svelte";
|
||||||
|
import * as Dialog from "$lib/components/ui/dialog/index.js";
|
||||||
|
|
||||||
|
type $$Props = DialogPrimitive.Props & CommandPrimitive.CommandProps;
|
||||||
|
|
||||||
|
export let open: $$Props["open"] = false;
|
||||||
|
export let value: $$Props["value"] = undefined;
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<Dialog.Root bind:open {...$$restProps}>
|
||||||
|
<Dialog.Content class="overflow-hidden p-0 shadow-lg">
|
||||||
|
<Command
|
||||||
|
class="[&_[data-cmdk-group-heading]]:px-2 [&_[data-cmdk-group-heading]]:font-medium [&_[data-cmdk-group-heading]]:text-muted-foreground [&_[data-cmdk-group]:not([hidden])_~[data-cmdk-group]]:pt-0 [&_[data-cmdk-group]]:px-2 [&_[data-cmdk-input-wrapper]_svg]:h-5 [&_[data-cmdk-input-wrapper]_svg]:w-5 [&_[data-cmdk-input]]:h-12 [&_[data-cmdk-item]]:px-2 [&_[data-cmdk-item]]:py-3 [&_[data-cmdk-item]_svg]:h-5 [&_[data-cmdk-item]_svg]:w-5"
|
||||||
|
{...$$restProps}
|
||||||
|
bind:value
|
||||||
|
>
|
||||||
|
<slot />
|
||||||
|
</Command>
|
||||||
|
</Dialog.Content>
|
||||||
|
</Dialog.Root>
|
||||||
12
src/lib/components/ui/command/command-empty.svelte
Normal file
12
src/lib/components/ui/command/command-empty.svelte
Normal file
|
|
@ -0,0 +1,12 @@
|
||||||
|
<script lang="ts">
|
||||||
|
import { Command as CommandPrimitive } from "cmdk-sv";
|
||||||
|
import { cn } from "$lib/utils/ui.js";
|
||||||
|
|
||||||
|
type $$Props = CommandPrimitive.EmptyProps;
|
||||||
|
let className: string | undefined | null = undefined;
|
||||||
|
export { className as class };
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<CommandPrimitive.Empty class={cn("py-6 text-center text-sm", className)} {...$$restProps}>
|
||||||
|
<slot />
|
||||||
|
</CommandPrimitive.Empty>
|
||||||
18
src/lib/components/ui/command/command-group.svelte
Normal file
18
src/lib/components/ui/command/command-group.svelte
Normal file
|
|
@ -0,0 +1,18 @@
|
||||||
|
<script lang="ts">
|
||||||
|
import { Command as CommandPrimitive } from "cmdk-sv";
|
||||||
|
import { cn } from "$lib/utils/ui.js";
|
||||||
|
type $$Props = CommandPrimitive.GroupProps;
|
||||||
|
|
||||||
|
let className: string | undefined | null = undefined;
|
||||||
|
export { className as class };
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<CommandPrimitive.Group
|
||||||
|
class={cn(
|
||||||
|
"overflow-hidden p-1 text-foreground [&_[data-cmdk-group-heading]]:px-2 [&_[data-cmdk-group-heading]]:py-1.5 [&_[data-cmdk-group-heading]]:text-xs [&_[data-cmdk-group-heading]]:font-medium [&_[data-cmdk-group-heading]]:text-muted-foreground",
|
||||||
|
className
|
||||||
|
)}
|
||||||
|
{...$$restProps}
|
||||||
|
>
|
||||||
|
<slot />
|
||||||
|
</CommandPrimitive.Group>
|
||||||
23
src/lib/components/ui/command/command-input.svelte
Normal file
23
src/lib/components/ui/command/command-input.svelte
Normal file
|
|
@ -0,0 +1,23 @@
|
||||||
|
<script lang="ts">
|
||||||
|
import { Command as CommandPrimitive } from "cmdk-sv";
|
||||||
|
import Search from "lucide-svelte/icons/search";
|
||||||
|
import { cn } from "$lib/utils/ui.js";
|
||||||
|
|
||||||
|
type $$Props = CommandPrimitive.InputProps;
|
||||||
|
|
||||||
|
let className: string | undefined | null = undefined;
|
||||||
|
export { className as class };
|
||||||
|
export let value: string = "";
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<div class="flex items-center border-b px-2" data-cmdk-input-wrapper="">
|
||||||
|
<Search class="mr-2 h-4 w-4 shrink-0 opacity-50" />
|
||||||
|
<CommandPrimitive.Input
|
||||||
|
class={cn(
|
||||||
|
"flex h-11 w-full rounded-md bg-transparent py-3 text-sm outline-none placeholder:text-muted-foreground disabled:cursor-not-allowed disabled:opacity-50",
|
||||||
|
className
|
||||||
|
)}
|
||||||
|
{...$$restProps}
|
||||||
|
bind:value
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
24
src/lib/components/ui/command/command-item.svelte
Normal file
24
src/lib/components/ui/command/command-item.svelte
Normal file
|
|
@ -0,0 +1,24 @@
|
||||||
|
<script lang="ts">
|
||||||
|
import { Command as CommandPrimitive } from "cmdk-sv";
|
||||||
|
import { cn } from "$lib/utils/ui.js";
|
||||||
|
|
||||||
|
type $$Props = CommandPrimitive.ItemProps;
|
||||||
|
|
||||||
|
export let asChild = false;
|
||||||
|
|
||||||
|
let className: string | undefined | null = undefined;
|
||||||
|
export { className as class };
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<CommandPrimitive.Item
|
||||||
|
{asChild}
|
||||||
|
class={cn(
|
||||||
|
"relative flex cursor-default select-none items-center rounded-sm px-2 py-1.5 text-sm outline-none aria-selected:bg-accent aria-selected:text-accent-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50",
|
||||||
|
className
|
||||||
|
)}
|
||||||
|
{...$$restProps}
|
||||||
|
let:action
|
||||||
|
let:attrs
|
||||||
|
>
|
||||||
|
<slot {action} {attrs} />
|
||||||
|
</CommandPrimitive.Item>
|
||||||
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue