mirror of
https://github.com/open-reception/appointment-booking-software.git
synced 2026-08-17 21:25:52 +02:00
Change prettier settings (#65)
Co-authored-by: Karl Ludwig Weise <ludwig@ludwigweise.de>
This commit is contained in:
co-authored by
Karl Ludwig Weise
parent
5d73b02ac2
commit
bf610cac2b
+2
-1
@@ -9,4 +9,5 @@ bun.lock
|
||||
bun.lockb
|
||||
|
||||
# Build Artifacts
|
||||
src/i18n/**
|
||||
src/i18n/**
|
||||
project.inlang/**
|
||||
+13
-13
@@ -1,15 +1,15 @@
|
||||
{
|
||||
"useTabs": true,
|
||||
"singleQuote": false,
|
||||
"trailingComma": "none",
|
||||
"printWidth": 100,
|
||||
"plugins": ["prettier-plugin-svelte", "prettier-plugin-tailwindcss"],
|
||||
"overrides": [
|
||||
{
|
||||
"files": "*.svelte",
|
||||
"options": {
|
||||
"parser": "svelte"
|
||||
}
|
||||
}
|
||||
]
|
||||
"useTabs": false,
|
||||
"singleQuote": false,
|
||||
"trailingComma": "all",
|
||||
"printWidth": 100,
|
||||
"plugins": ["prettier-plugin-svelte", "prettier-plugin-tailwindcss"],
|
||||
"overrides": [
|
||||
{
|
||||
"files": "*.svelte",
|
||||
"options": {
|
||||
"parser": "svelte"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
+14
-14
@@ -1,16 +1,16 @@
|
||||
{
|
||||
"$schema": "https://shadcn-svelte.com/schema.json",
|
||||
"tailwind": {
|
||||
"css": "src/app.css",
|
||||
"baseColor": "slate"
|
||||
},
|
||||
"aliases": {
|
||||
"components": "$lib/components",
|
||||
"utils": "$lib/utils",
|
||||
"ui": "$lib/components/ui",
|
||||
"hooks": "$lib/hooks",
|
||||
"lib": "$lib"
|
||||
},
|
||||
"typescript": true,
|
||||
"registry": "https://shadcn-svelte.com/registry"
|
||||
"$schema": "https://shadcn-svelte.com/schema.json",
|
||||
"tailwind": {
|
||||
"css": "src/app.css",
|
||||
"baseColor": "slate"
|
||||
},
|
||||
"aliases": {
|
||||
"components": "$lib/components",
|
||||
"utils": "$lib/utils",
|
||||
"ui": "$lib/components/ui",
|
||||
"hooks": "$lib/hooks",
|
||||
"lib": "$lib"
|
||||
},
|
||||
"typescript": true,
|
||||
"registry": "https://shadcn-svelte.com/registry"
|
||||
}
|
||||
|
||||
@@ -20,7 +20,7 @@ services:
|
||||
test:
|
||||
[
|
||||
"CMD-SHELL",
|
||||
"pg_isready -U ${POSTGRES_USER:-postgres} -d ${POSTGRES_DB:-appointment_booking}"
|
||||
"pg_isready -U ${POSTGRES_USER:-postgres} -d ${POSTGRES_DB:-appointment_booking}",
|
||||
]
|
||||
interval: 10s
|
||||
timeout: 5s
|
||||
|
||||
@@ -22,7 +22,7 @@ services:
|
||||
test:
|
||||
[
|
||||
"CMD-SHELL",
|
||||
"pg_isready -U $$(cat /run/secrets/postgres_user) -d $$(cat /run/secrets/postgres_db)"
|
||||
"pg_isready -U $$(cat /run/secrets/postgres_user) -d $$(cat /run/secrets/postgres_db)",
|
||||
]
|
||||
interval: 10s
|
||||
timeout: 5s
|
||||
|
||||
+16
-16
@@ -159,9 +159,9 @@ Tenants can customize three color values that are automatically applied to email
|
||||
|
||||
```typescript
|
||||
interface TenantBranding {
|
||||
primaryColor: string; // Main brand color (buttons, links, logos)
|
||||
secondaryColor: string; // Accent color (success messages, highlights)
|
||||
backgroundColor: string; // Email background color
|
||||
primaryColor: string; // Main brand color (buttons, links, logos)
|
||||
secondaryColor: string; // Accent color (success messages, highlights)
|
||||
backgroundColor: string; // Email background color
|
||||
}
|
||||
```
|
||||
|
||||
@@ -192,9 +192,9 @@ Tenant logos are stored as binary data in the database and automatically convert
|
||||
```html
|
||||
{{#if tenant.logo}}
|
||||
<img
|
||||
src="data:image/png;base64,{{tenant.logo}}"
|
||||
alt="{{tenant.longName}}"
|
||||
style="max-height: 60px; margin-bottom: 10px;"
|
||||
src="data:image/png;base64,{{tenant.logo}}"
|
||||
alt="{{tenant.longName}}"
|
||||
style="max-height: 60px; margin-bottom: 10px;"
|
||||
/>
|
||||
{{/if}}
|
||||
<div class="logo">{{tenant.longName}}</div>
|
||||
@@ -255,16 +255,16 @@ The email system is designed with privacy in mind:
|
||||
```typescript
|
||||
// Client email creation (privacy-focused)
|
||||
const clientRecipient = {
|
||||
email: user.email || "", // May be empty
|
||||
name: undefined, // Never stored for clients
|
||||
language: user.language || "de"
|
||||
email: user.email || "", // May be empty
|
||||
name: undefined, // Never stored for clients
|
||||
language: user.language || "de",
|
||||
};
|
||||
|
||||
// Staff email creation
|
||||
const staffRecipient = {
|
||||
email: user.email, // Always required
|
||||
name: user.name || undefined, // Optional display name
|
||||
language: user.language || "de"
|
||||
email: user.email, // Always required
|
||||
name: user.name || undefined, // Optional display name
|
||||
language: user.language || "de",
|
||||
};
|
||||
```
|
||||
|
||||
@@ -303,10 +303,10 @@ The system automatically uses test mode in development:
|
||||
```typescript
|
||||
// Add test case in email-system.test.ts
|
||||
const result = await templateEngine.renderTemplate("user-created", {
|
||||
recipient: { email: "test@example.com", name: "Test User" },
|
||||
subject: "Test Subject",
|
||||
language: "de",
|
||||
tenant: mockTenant
|
||||
recipient: { email: "test@example.com", name: "Test User" },
|
||||
subject: "Test Subject",
|
||||
language: "de",
|
||||
tenant: mockTenant,
|
||||
});
|
||||
```
|
||||
|
||||
|
||||
+33
-33
@@ -170,29 +170,29 @@ import { readFileSync } from "fs";
|
||||
import pg from "pg";
|
||||
|
||||
function getDatabaseConfig() {
|
||||
if (process.env.NODE_ENV === "production") {
|
||||
// Read from Docker secrets
|
||||
const user = readFileSync("/run/secrets/postgres_user", "utf8").trim();
|
||||
const password = readFileSync("/run/secrets/postgres_password", "utf8").trim();
|
||||
const database = readFileSync("/run/secrets/postgres_db", "utf8").trim();
|
||||
if (process.env.NODE_ENV === "production") {
|
||||
// Read from Docker secrets
|
||||
const user = readFileSync("/run/secrets/postgres_user", "utf8").trim();
|
||||
const password = readFileSync("/run/secrets/postgres_password", "utf8").trim();
|
||||
const database = readFileSync("/run/secrets/postgres_db", "utf8").trim();
|
||||
|
||||
return {
|
||||
host: "postgres",
|
||||
port: 5432,
|
||||
user,
|
||||
password,
|
||||
database
|
||||
};
|
||||
} else {
|
||||
// Development configuration
|
||||
return {
|
||||
host: "localhost",
|
||||
port: process.env.POSTGRES_PORT || 5432,
|
||||
user: process.env.POSTGRES_USER || "postgres",
|
||||
password: process.env.POSTGRES_PASSWORD,
|
||||
database: process.env.POSTGRES_DB || "appointment_booking"
|
||||
};
|
||||
}
|
||||
return {
|
||||
host: "postgres",
|
||||
port: 5432,
|
||||
user,
|
||||
password,
|
||||
database,
|
||||
};
|
||||
} else {
|
||||
// Development configuration
|
||||
return {
|
||||
host: "localhost",
|
||||
port: process.env.POSTGRES_PORT || 5432,
|
||||
user: process.env.POSTGRES_USER || "postgres",
|
||||
password: process.env.POSTGRES_PASSWORD,
|
||||
database: process.env.POSTGRES_DB || "appointment_booking",
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
export const pool = new pg.Pool(getDatabaseConfig());
|
||||
@@ -207,12 +207,12 @@ import { json } from "@sveltejs/kit";
|
||||
import { pool } from "$lib/database.js";
|
||||
|
||||
export async function GET() {
|
||||
try {
|
||||
await pool.query("SELECT 1");
|
||||
return json({ status: "healthy", timestamp: new Date().toISOString() });
|
||||
} catch (error) {
|
||||
return json({ status: "unhealthy", error: error.message }, { status: 500 });
|
||||
}
|
||||
try {
|
||||
await pool.query("SELECT 1");
|
||||
return json({ status: "healthy", timestamp: new Date().toISOString() });
|
||||
} catch (error) {
|
||||
return json({ status: "unhealthy", error: error.message }, { status: 500 });
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
@@ -225,11 +225,11 @@ import { sveltekit } from "@sveltejs/kit/vite";
|
||||
import { defineConfig } from "vite";
|
||||
|
||||
export default defineConfig({
|
||||
plugins: [sveltekit()],
|
||||
server: {
|
||||
host: "0.0.0.0",
|
||||
port: 5173
|
||||
}
|
||||
plugins: [sveltekit()],
|
||||
server: {
|
||||
host: "0.0.0.0",
|
||||
port: 5173,
|
||||
},
|
||||
});
|
||||
```
|
||||
|
||||
|
||||
+14
-14
@@ -138,23 +138,23 @@ The server-side Winston logger is configured with:
|
||||
import { logger } from "$lib/logger";
|
||||
|
||||
export const handle = async ({ event, resolve }) => {
|
||||
const start = Date.now();
|
||||
const requestLogger = logger.setContext("REQUEST");
|
||||
const start = Date.now();
|
||||
const requestLogger = logger.setContext("REQUEST");
|
||||
|
||||
try {
|
||||
const response = await resolve(event);
|
||||
const duration = Date.now() - start;
|
||||
try {
|
||||
const response = await resolve(event);
|
||||
const duration = Date.now() - start;
|
||||
|
||||
requestLogger.info(`${event.request.method} ${event.url.pathname}`, {
|
||||
status: response.status,
|
||||
duration: `${duration}ms`
|
||||
});
|
||||
requestLogger.info(`${event.request.method} ${event.url.pathname}`, {
|
||||
status: response.status,
|
||||
duration: `${duration}ms`,
|
||||
});
|
||||
|
||||
return response;
|
||||
} catch (error) {
|
||||
requestLogger.error("Request failed", { error });
|
||||
throw error;
|
||||
}
|
||||
return response;
|
||||
} catch (error) {
|
||||
requestLogger.error("Request failed", { error });
|
||||
throw error;
|
||||
}
|
||||
};
|
||||
```
|
||||
|
||||
|
||||
+6
-6
@@ -11,10 +11,10 @@ if (!POSTGRES_PASSWORD) throw new Error("POSTGRES_PASSWORD is not set");
|
||||
const DATABASE_URL = `postgres://${POSTGRES_USER}:${POSTGRES_PASSWORD}@localhost:${POSTGRES_PORT}/${POSTGRES_DB}`;
|
||||
|
||||
export default defineConfig({
|
||||
schema: "./src/lib/server/db/central-schema.ts",
|
||||
dialect: "postgresql",
|
||||
dbCredentials: { url: DATABASE_URL },
|
||||
out: "./migrations",
|
||||
verbose: true,
|
||||
strict: true
|
||||
schema: "./src/lib/server/db/central-schema.ts",
|
||||
dialect: "postgresql",
|
||||
dbCredentials: { url: DATABASE_URL },
|
||||
out: "./migrations",
|
||||
verbose: true,
|
||||
strict: true,
|
||||
});
|
||||
|
||||
@@ -3,12 +3,12 @@ import { defineConfig } from "drizzle-kit";
|
||||
// This config is used to generate migrations for tenant schemas
|
||||
// It uses a placeholder database URL that will be replaced at runtime
|
||||
export default defineConfig({
|
||||
schema: "./src/lib/server/db/tenant-schema.ts",
|
||||
dialect: "postgresql",
|
||||
dbCredentials: {
|
||||
url: "postgresql://placeholder:placeholder@localhost:5432/placeholder"
|
||||
},
|
||||
out: "./tenant-migrations",
|
||||
verbose: true,
|
||||
strict: true
|
||||
schema: "./src/lib/server/db/tenant-schema.ts",
|
||||
dialect: "postgresql",
|
||||
dbCredentials: {
|
||||
url: "postgresql://placeholder:placeholder@localhost:5432/placeholder",
|
||||
},
|
||||
out: "./tenant-migrations",
|
||||
verbose: true,
|
||||
strict: true,
|
||||
});
|
||||
|
||||
+2
-2
@@ -1,6 +1,6 @@
|
||||
import { expect, test } from "@playwright/test";
|
||||
|
||||
test("home page has expected h1", async ({ page }) => {
|
||||
await page.goto("/");
|
||||
await expect(page.locator("h1")).toBeVisible();
|
||||
await page.goto("/");
|
||||
await expect(page.locator("h1")).toBeVisible();
|
||||
});
|
||||
|
||||
+26
-26
@@ -10,30 +10,30 @@ import svelteConfig from "./svelte.config.js";
|
||||
const gitignorePath = fileURLToPath(new URL("./.gitignore", import.meta.url));
|
||||
|
||||
export default ts.config(
|
||||
includeIgnoreFile(gitignorePath),
|
||||
js.configs.recommended,
|
||||
...ts.configs.recommended,
|
||||
...svelte.configs.recommended,
|
||||
prettier,
|
||||
...svelte.configs.prettier,
|
||||
{
|
||||
ignores: ["static/**"]
|
||||
},
|
||||
{
|
||||
languageOptions: {
|
||||
globals: { ...globals.browser, ...globals.node }
|
||||
},
|
||||
rules: { "no-undef": "off" }
|
||||
},
|
||||
{
|
||||
files: ["**/*.svelte", "**/*.svelte.ts", "**/*.svelte.js"],
|
||||
languageOptions: {
|
||||
parserOptions: {
|
||||
projectService: true,
|
||||
extraFileExtensions: [".svelte"],
|
||||
parser: ts.parser,
|
||||
svelteConfig
|
||||
}
|
||||
}
|
||||
}
|
||||
includeIgnoreFile(gitignorePath),
|
||||
js.configs.recommended,
|
||||
...ts.configs.recommended,
|
||||
...svelte.configs.recommended,
|
||||
prettier,
|
||||
...svelte.configs.prettier,
|
||||
{
|
||||
ignores: ["static/**"],
|
||||
},
|
||||
{
|
||||
languageOptions: {
|
||||
globals: { ...globals.browser, ...globals.node },
|
||||
},
|
||||
rules: { "no-undef": "off" },
|
||||
},
|
||||
{
|
||||
files: ["**/*.svelte", "**/*.svelte.ts", "**/*.svelte.js"],
|
||||
languageOptions: {
|
||||
parserOptions: {
|
||||
projectService: true,
|
||||
extraFileExtensions: [".svelte"],
|
||||
parser: ts.parser,
|
||||
svelteConfig,
|
||||
},
|
||||
},
|
||||
},
|
||||
);
|
||||
|
||||
+549
-549
File diff suppressed because it is too large
Load Diff
+561
-561
File diff suppressed because it is too large
Load Diff
@@ -1,20 +1,20 @@
|
||||
{
|
||||
"version": "7",
|
||||
"dialect": "postgresql",
|
||||
"entries": [
|
||||
{
|
||||
"idx": 0,
|
||||
"version": "7",
|
||||
"when": 1752661377819,
|
||||
"tag": "0000_big_ultimatum",
|
||||
"breakpoints": true
|
||||
},
|
||||
{
|
||||
"idx": 1,
|
||||
"version": "7",
|
||||
"when": 1752668982273,
|
||||
"tag": "0001_equal_wonder_man",
|
||||
"breakpoints": true
|
||||
}
|
||||
]
|
||||
"version": "7",
|
||||
"dialect": "postgresql",
|
||||
"entries": [
|
||||
{
|
||||
"idx": 0,
|
||||
"version": "7",
|
||||
"when": 1752661377819,
|
||||
"tag": "0000_big_ultimatum",
|
||||
"breakpoints": true
|
||||
},
|
||||
{
|
||||
"idx": 1,
|
||||
"version": "7",
|
||||
"when": 1752668982273,
|
||||
"tag": "0001_equal_wonder_man",
|
||||
"breakpoints": true
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
+98
-98
@@ -1,100 +1,100 @@
|
||||
{
|
||||
"name": "open-reception",
|
||||
"private": true,
|
||||
"version": "0.0.1",
|
||||
"type": "module",
|
||||
"description": "End-to-end encrypted appointment booking platform",
|
||||
"scripts": {
|
||||
"dev": "vite dev",
|
||||
"build": "vite build",
|
||||
"preview": "vite preview",
|
||||
"prepare": "svelte-kit sync && npm run i18n:compile || echo ''",
|
||||
"check": "npm run prepare && svelte-check --tsconfig ./tsconfig.json",
|
||||
"check:watch": "npm run prepare && svelte-check --tsconfig ./tsconfig.json --watch",
|
||||
"format": "prettier --write .",
|
||||
"lint": "prettier --check . && eslint .",
|
||||
"test:unit": "vitest",
|
||||
"test": "npm run test:unit -- --run && npm run test:e2e",
|
||||
"test:e2e": "playwright test",
|
||||
"db:push": "drizzle-kit push",
|
||||
"db:migrate": "drizzle-kit migrate",
|
||||
"db:studio": "drizzle-kit studio",
|
||||
"db:generate": "drizzle-kit generate",
|
||||
"db:tenant:generate": "drizzle-kit generate --config=drizzle.tenant.config.ts",
|
||||
"docker:dev:up": "docker compose -f docker-compose.dev.yml up -d",
|
||||
"docker:dev:down": "docker compose -f docker-compose.dev.yml down",
|
||||
"docker:dev:logs": "docker compose -f docker-compose.dev.yml logs -f",
|
||||
"docker:dev:clean": "docker compose -f docker-compose.dev.yml down -v --remove-orphans",
|
||||
"docker:build": "docker build -t openreception/open-reception:latest .",
|
||||
"docker:build:tag": "docker tag openreception/open-reception:latest openreception/open-reception:$npm_package_version",
|
||||
"docker:push": "docker push openreception/open-reception:$npm_package_version && docker push openreception/open-reception:latest",
|
||||
"docker:build-and-push": "npm run docker:build && npm run docker:build:tag && npm run docker:push",
|
||||
"docker:prod:up": "docker compose -f docker-compose.prod.yml up -d",
|
||||
"docker:prod:down": "docker compose -f docker-compose.prod.yml down",
|
||||
"docker:prod:logs": "docker compose -f docker-compose.prod.yml logs -f",
|
||||
"docker:prod:clean": "docker compose -f docker-compose.prod.yml down -v --remove-orphans",
|
||||
"i18n:compile": "npx @inlang/paraglide-js compile --project ./project.inlang --outdir ./src/i18n"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@eslint/compat": "^1.3.0",
|
||||
"@eslint/js": "^9.29.0",
|
||||
"@inlang/paraglide-js": "2.2.0",
|
||||
"@internationalized/date": "^3.8.2",
|
||||
"@lucide/svelte": "^0.542.0",
|
||||
"@playwright/test": "^1.53.1",
|
||||
"@sveltejs/adapter-auto": "^6.0.1",
|
||||
"@sveltejs/kit": "^2.22.0",
|
||||
"@sveltejs/vite-plugin-svelte": "^5.1.0",
|
||||
"@tailwindcss/typography": "^0.5.16",
|
||||
"@tailwindcss/vite": "^4.1.10",
|
||||
"@testing-library/jest-dom": "^6.6.3",
|
||||
"@testing-library/svelte": "^5.2.8",
|
||||
"@types/dotenv": "^6.1.1",
|
||||
"@types/node": "^24",
|
||||
"@types/nodemailer": "^6.4.17",
|
||||
"bits-ui": "^2.8.13",
|
||||
"clsx": "^2.1.1",
|
||||
"drizzle-kit": "^0.31.1",
|
||||
"eslint": "^9.29.0",
|
||||
"eslint-config-prettier": "^10.1.5",
|
||||
"eslint-plugin-svelte": "^3.9.3",
|
||||
"formsnap": "^2.0.1",
|
||||
"globals": "^16.2.0",
|
||||
"jsdom": "^26.1.0",
|
||||
"prettier": "^3.5.3",
|
||||
"prettier-plugin-svelte": "^3.4.0",
|
||||
"prettier-plugin-tailwindcss": "^0.6.13",
|
||||
"svelte": "^5.34.7",
|
||||
"svelte-check": "^4.2.2",
|
||||
"svelte-sonner": "^1.0.5",
|
||||
"sveltekit-superforms": "^2.27.1",
|
||||
"tailwind-merge": "^3.3.1",
|
||||
"tailwind-variants": "^1.0.0",
|
||||
"tailwindcss": "^4.1.10",
|
||||
"tw-animate-css": "^1.3.4",
|
||||
"typescript": "^5.8.3",
|
||||
"typescript-eslint": "^8.34.1",
|
||||
"vite": "^6.3.5",
|
||||
"vitest": "^3.2.4"
|
||||
},
|
||||
"dependencies": {
|
||||
"@noble/hashes": "^1.8.0",
|
||||
"@noble/post-quantum": "^0.4.1",
|
||||
"@sveltejs/adapter-node": "^5.2.12",
|
||||
"argon2": "^0.43.0",
|
||||
"argon2-browser": "^1.18.0",
|
||||
"date-fns": "^4.1.0",
|
||||
"dotenv": "^16.5.0",
|
||||
"dotenv-expand": "^12.0.2",
|
||||
"drizzle-orm": "^0.44.2",
|
||||
"jose": "^6.0.11",
|
||||
"mode-watcher": "^1.0.8",
|
||||
"nodemailer": "^7.0.3",
|
||||
"postgres": "^3.4.7",
|
||||
"secrets.js-34r7h": "^2.0.2",
|
||||
"uuidv7": "^1.0.2",
|
||||
"winston": "^3.17.0",
|
||||
"zod": "^3.25.76"
|
||||
},
|
||||
"license": "AGPL-3.0"
|
||||
"name": "open-reception",
|
||||
"private": true,
|
||||
"version": "0.0.1",
|
||||
"type": "module",
|
||||
"description": "End-to-end encrypted appointment booking platform",
|
||||
"scripts": {
|
||||
"dev": "vite dev",
|
||||
"build": "vite build",
|
||||
"preview": "vite preview",
|
||||
"prepare": "svelte-kit sync && npm run i18n:compile || echo ''",
|
||||
"check": "npm run prepare && svelte-check --tsconfig ./tsconfig.json",
|
||||
"check:watch": "npm run prepare && svelte-check --tsconfig ./tsconfig.json --watch",
|
||||
"format": "prettier --write .",
|
||||
"lint": "prettier --check . && eslint .",
|
||||
"test:unit": "vitest",
|
||||
"test": "npm run test:unit -- --run && npm run test:e2e",
|
||||
"test:e2e": "playwright test",
|
||||
"db:push": "drizzle-kit push",
|
||||
"db:migrate": "drizzle-kit migrate",
|
||||
"db:studio": "drizzle-kit studio",
|
||||
"db:generate": "drizzle-kit generate",
|
||||
"db:tenant:generate": "drizzle-kit generate --config=drizzle.tenant.config.ts",
|
||||
"docker:dev:up": "docker compose -f docker-compose.dev.yml up -d",
|
||||
"docker:dev:down": "docker compose -f docker-compose.dev.yml down",
|
||||
"docker:dev:logs": "docker compose -f docker-compose.dev.yml logs -f",
|
||||
"docker:dev:clean": "docker compose -f docker-compose.dev.yml down -v --remove-orphans",
|
||||
"docker:build": "docker build -t openreception/open-reception:latest .",
|
||||
"docker:build:tag": "docker tag openreception/open-reception:latest openreception/open-reception:$npm_package_version",
|
||||
"docker:push": "docker push openreception/open-reception:$npm_package_version && docker push openreception/open-reception:latest",
|
||||
"docker:build-and-push": "npm run docker:build && npm run docker:build:tag && npm run docker:push",
|
||||
"docker:prod:up": "docker compose -f docker-compose.prod.yml up -d",
|
||||
"docker:prod:down": "docker compose -f docker-compose.prod.yml down",
|
||||
"docker:prod:logs": "docker compose -f docker-compose.prod.yml logs -f",
|
||||
"docker:prod:clean": "docker compose -f docker-compose.prod.yml down -v --remove-orphans",
|
||||
"i18n:compile": "npx @inlang/paraglide-js compile --project ./project.inlang --outdir ./src/i18n"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@eslint/compat": "^1.3.0",
|
||||
"@eslint/js": "^9.29.0",
|
||||
"@inlang/paraglide-js": "2.2.0",
|
||||
"@internationalized/date": "^3.8.2",
|
||||
"@lucide/svelte": "^0.542.0",
|
||||
"@playwright/test": "^1.53.1",
|
||||
"@sveltejs/adapter-auto": "^6.0.1",
|
||||
"@sveltejs/kit": "^2.22.0",
|
||||
"@sveltejs/vite-plugin-svelte": "^5.1.0",
|
||||
"@tailwindcss/typography": "^0.5.16",
|
||||
"@tailwindcss/vite": "^4.1.10",
|
||||
"@testing-library/jest-dom": "^6.6.3",
|
||||
"@testing-library/svelte": "^5.2.8",
|
||||
"@types/dotenv": "^6.1.1",
|
||||
"@types/node": "^24",
|
||||
"@types/nodemailer": "^6.4.17",
|
||||
"bits-ui": "^2.8.13",
|
||||
"clsx": "^2.1.1",
|
||||
"drizzle-kit": "^0.31.1",
|
||||
"eslint": "^9.29.0",
|
||||
"eslint-config-prettier": "^10.1.5",
|
||||
"eslint-plugin-svelte": "^3.9.3",
|
||||
"formsnap": "^2.0.1",
|
||||
"globals": "^16.2.0",
|
||||
"jsdom": "^26.1.0",
|
||||
"prettier": "^3.5.3",
|
||||
"prettier-plugin-svelte": "^3.4.0",
|
||||
"prettier-plugin-tailwindcss": "^0.6.13",
|
||||
"svelte": "^5.34.7",
|
||||
"svelte-check": "^4.2.2",
|
||||
"svelte-sonner": "^1.0.5",
|
||||
"sveltekit-superforms": "^2.27.1",
|
||||
"tailwind-merge": "^3.3.1",
|
||||
"tailwind-variants": "^1.0.0",
|
||||
"tailwindcss": "^4.1.10",
|
||||
"tw-animate-css": "^1.3.4",
|
||||
"typescript": "^5.8.3",
|
||||
"typescript-eslint": "^8.34.1",
|
||||
"vite": "^6.3.5",
|
||||
"vitest": "^3.2.4"
|
||||
},
|
||||
"dependencies": {
|
||||
"@noble/hashes": "^1.8.0",
|
||||
"@noble/post-quantum": "^0.4.1",
|
||||
"@sveltejs/adapter-node": "^5.2.12",
|
||||
"argon2": "^0.43.0",
|
||||
"argon2-browser": "^1.18.0",
|
||||
"date-fns": "^4.1.0",
|
||||
"dotenv": "^16.5.0",
|
||||
"dotenv-expand": "^12.0.2",
|
||||
"drizzle-orm": "^0.44.2",
|
||||
"jose": "^6.0.11",
|
||||
"mode-watcher": "^1.0.8",
|
||||
"nodemailer": "^7.0.3",
|
||||
"postgres": "^3.4.7",
|
||||
"secrets.js-34r7h": "^2.0.2",
|
||||
"uuidv7": "^1.0.2",
|
||||
"winston": "^3.17.0",
|
||||
"zod": "^3.25.76"
|
||||
},
|
||||
"license": "AGPL-3.0"
|
||||
}
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
import { defineConfig } from "@playwright/test";
|
||||
|
||||
export default defineConfig({
|
||||
webServer: {
|
||||
command: "npm run build && npm run preview",
|
||||
port: 4173
|
||||
},
|
||||
testDir: "e2e"
|
||||
webServer: {
|
||||
command: "npm run build && npm run preview",
|
||||
port: 4173,
|
||||
},
|
||||
testDir: "e2e",
|
||||
});
|
||||
|
||||
+125
-125
@@ -2,143 +2,143 @@
|
||||
@custom-variant dark (&:where(.dark, .dark *));
|
||||
|
||||
:root {
|
||||
--radius: 0.625rem;
|
||||
--background: oklch(1 0 0);
|
||||
--foreground: oklch(0.129 0.042 264.695);
|
||||
--card: oklch(1 0 0);
|
||||
--card-foreground: oklch(0.129 0.042 264.695);
|
||||
--popover: oklch(1 0 0);
|
||||
--popover-foreground: oklch(0.129 0.042 264.695);
|
||||
--primary: oklch(0.208 0.042 265.755);
|
||||
--primary-foreground: oklch(0.984 0.003 247.858);
|
||||
--secondary: oklch(0.968 0.007 247.896);
|
||||
--secondary-foreground: oklch(0.208 0.042 265.755);
|
||||
--muted: oklch(0.968 0.007 247.896);
|
||||
--muted-foreground: oklch(0.554 0.046 257.417);
|
||||
--accent: oklch(0.968 0.007 247.896);
|
||||
--accent-foreground: oklch(0.208 0.042 265.755);
|
||||
--destructive: oklch(0.577 0.245 27.325);
|
||||
--border: oklch(0.929 0.013 255.508);
|
||||
--input: oklch(0.929 0.013 255.508);
|
||||
--ring: oklch(0.704 0.04 256.788);
|
||||
--chart-1: oklch(0.646 0.222 41.116);
|
||||
--chart-2: oklch(0.6 0.118 184.704);
|
||||
--chart-3: oklch(0.398 0.07 227.392);
|
||||
--chart-4: oklch(0.828 0.189 84.429);
|
||||
--chart-5: oklch(0.769 0.188 70.08);
|
||||
--sidebar: oklch(0.984 0.003 247.858);
|
||||
--sidebar-foreground: oklch(0.129 0.042 264.695);
|
||||
--sidebar-primary: oklch(0.208 0.042 265.755);
|
||||
--sidebar-primary-foreground: oklch(0.984 0.003 247.858);
|
||||
--sidebar-accent: oklch(0.968 0.007 247.896);
|
||||
--sidebar-accent-foreground: oklch(0.208 0.042 265.755);
|
||||
--sidebar-border: oklch(0.929 0.013 255.508);
|
||||
--sidebar-ring: oklch(0.704 0.04 256.788);
|
||||
--radius: 0.625rem;
|
||||
--background: oklch(1 0 0);
|
||||
--foreground: oklch(0.129 0.042 264.695);
|
||||
--card: oklch(1 0 0);
|
||||
--card-foreground: oklch(0.129 0.042 264.695);
|
||||
--popover: oklch(1 0 0);
|
||||
--popover-foreground: oklch(0.129 0.042 264.695);
|
||||
--primary: oklch(0.208 0.042 265.755);
|
||||
--primary-foreground: oklch(0.984 0.003 247.858);
|
||||
--secondary: oklch(0.968 0.007 247.896);
|
||||
--secondary-foreground: oklch(0.208 0.042 265.755);
|
||||
--muted: oklch(0.968 0.007 247.896);
|
||||
--muted-foreground: oklch(0.554 0.046 257.417);
|
||||
--accent: oklch(0.968 0.007 247.896);
|
||||
--accent-foreground: oklch(0.208 0.042 265.755);
|
||||
--destructive: oklch(0.577 0.245 27.325);
|
||||
--border: oklch(0.929 0.013 255.508);
|
||||
--input: oklch(0.929 0.013 255.508);
|
||||
--ring: oklch(0.704 0.04 256.788);
|
||||
--chart-1: oklch(0.646 0.222 41.116);
|
||||
--chart-2: oklch(0.6 0.118 184.704);
|
||||
--chart-3: oklch(0.398 0.07 227.392);
|
||||
--chart-4: oklch(0.828 0.189 84.429);
|
||||
--chart-5: oklch(0.769 0.188 70.08);
|
||||
--sidebar: oklch(0.984 0.003 247.858);
|
||||
--sidebar-foreground: oklch(0.129 0.042 264.695);
|
||||
--sidebar-primary: oklch(0.208 0.042 265.755);
|
||||
--sidebar-primary-foreground: oklch(0.984 0.003 247.858);
|
||||
--sidebar-accent: oklch(0.968 0.007 247.896);
|
||||
--sidebar-accent-foreground: oklch(0.208 0.042 265.755);
|
||||
--sidebar-border: oklch(0.929 0.013 255.508);
|
||||
--sidebar-ring: oklch(0.704 0.04 256.788);
|
||||
|
||||
/* custom colors */
|
||||
--lighter: oklch(77.983% 0.03673 254.95);
|
||||
--light: oklch(69.121% 0.03859 257.443);
|
||||
--medium: oklch(62.379% 0.01913 256.381);
|
||||
--dark: oklch(43.309% 0.00977 254.027);
|
||||
--darker: oklch(27.232% 0.00797 264.468);
|
||||
/* custom colors */
|
||||
--lighter: oklch(77.983% 0.03673 254.95);
|
||||
--light: oklch(69.121% 0.03859 257.443);
|
||||
--medium: oklch(62.379% 0.01913 256.381);
|
||||
--dark: oklch(43.309% 0.00977 254.027);
|
||||
--darker: oklch(27.232% 0.00797 264.468);
|
||||
}
|
||||
|
||||
.dark {
|
||||
--background: oklch(0.129 0.042 264.695);
|
||||
--foreground: oklch(0.984 0.003 247.858);
|
||||
--card: oklch(0.208 0.042 265.755);
|
||||
--card-foreground: oklch(0.984 0.003 247.858);
|
||||
--popover: oklch(0.208 0.042 265.755);
|
||||
--popover-foreground: oklch(0.984 0.003 247.858);
|
||||
--primary: oklch(0.929 0.013 255.508);
|
||||
--primary-foreground: oklch(0.208 0.042 265.755);
|
||||
--secondary: oklch(0.279 0.041 260.031);
|
||||
--secondary-foreground: oklch(0.984 0.003 247.858);
|
||||
--muted: oklch(0.279 0.041 260.031);
|
||||
--muted-foreground: oklch(0.704 0.04 256.788);
|
||||
--accent: oklch(0.279 0.041 260.031);
|
||||
--accent-foreground: oklch(0.984 0.003 247.858);
|
||||
--destructive: oklch(0.704 0.191 22.216);
|
||||
--border: oklch(1 0 0 / 10%);
|
||||
--input: oklch(1 0 0 / 15%);
|
||||
--ring: oklch(0.551 0.027 264.364);
|
||||
--chart-1: oklch(0.488 0.243 264.376);
|
||||
--chart-2: oklch(0.696 0.17 162.48);
|
||||
--chart-3: oklch(0.769 0.188 70.08);
|
||||
--chart-4: oklch(0.627 0.265 303.9);
|
||||
--chart-5: oklch(0.645 0.246 16.439);
|
||||
--sidebar: oklch(0.208 0.042 265.755);
|
||||
--sidebar-foreground: oklch(0.984 0.003 247.858);
|
||||
--sidebar-primary: oklch(0.488 0.243 264.376);
|
||||
--sidebar-primary-foreground: oklch(0.984 0.003 247.858);
|
||||
--sidebar-accent: oklch(0.279 0.041 260.031);
|
||||
--sidebar-accent-foreground: oklch(0.984 0.003 247.858);
|
||||
--sidebar-border: oklch(1 0 0 / 10%);
|
||||
--sidebar-ring: oklch(0.551 0.027 264.364);
|
||||
--background: oklch(0.129 0.042 264.695);
|
||||
--foreground: oklch(0.984 0.003 247.858);
|
||||
--card: oklch(0.208 0.042 265.755);
|
||||
--card-foreground: oklch(0.984 0.003 247.858);
|
||||
--popover: oklch(0.208 0.042 265.755);
|
||||
--popover-foreground: oklch(0.984 0.003 247.858);
|
||||
--primary: oklch(0.929 0.013 255.508);
|
||||
--primary-foreground: oklch(0.208 0.042 265.755);
|
||||
--secondary: oklch(0.279 0.041 260.031);
|
||||
--secondary-foreground: oklch(0.984 0.003 247.858);
|
||||
--muted: oklch(0.279 0.041 260.031);
|
||||
--muted-foreground: oklch(0.704 0.04 256.788);
|
||||
--accent: oklch(0.279 0.041 260.031);
|
||||
--accent-foreground: oklch(0.984 0.003 247.858);
|
||||
--destructive: oklch(0.704 0.191 22.216);
|
||||
--border: oklch(1 0 0 / 10%);
|
||||
--input: oklch(1 0 0 / 15%);
|
||||
--ring: oklch(0.551 0.027 264.364);
|
||||
--chart-1: oklch(0.488 0.243 264.376);
|
||||
--chart-2: oklch(0.696 0.17 162.48);
|
||||
--chart-3: oklch(0.769 0.188 70.08);
|
||||
--chart-4: oklch(0.627 0.265 303.9);
|
||||
--chart-5: oklch(0.645 0.246 16.439);
|
||||
--sidebar: oklch(0.208 0.042 265.755);
|
||||
--sidebar-foreground: oklch(0.984 0.003 247.858);
|
||||
--sidebar-primary: oklch(0.488 0.243 264.376);
|
||||
--sidebar-primary-foreground: oklch(0.984 0.003 247.858);
|
||||
--sidebar-accent: oklch(0.279 0.041 260.031);
|
||||
--sidebar-accent-foreground: oklch(0.984 0.003 247.858);
|
||||
--sidebar-border: oklch(1 0 0 / 10%);
|
||||
--sidebar-ring: oklch(0.551 0.027 264.364);
|
||||
|
||||
/* custom colors */
|
||||
--lighter: oklch(27.232% 0.00797 264.468);
|
||||
--light: oklch(43.309% 0.00977 254.027);
|
||||
--medium: oklch(62.379% 0.01913 256.381);
|
||||
--dark: oklch(69.121% 0.03859 257.443);
|
||||
--darker: oklch(77.983% 0.03673 254.95);
|
||||
/* custom colors */
|
||||
--lighter: oklch(27.232% 0.00797 264.468);
|
||||
--light: oklch(43.309% 0.00977 254.027);
|
||||
--medium: oklch(62.379% 0.01913 256.381);
|
||||
--dark: oklch(69.121% 0.03859 257.443);
|
||||
--darker: oklch(77.983% 0.03673 254.95);
|
||||
}
|
||||
|
||||
@theme {
|
||||
--radius-sm: calc(var(--radius) - 4px);
|
||||
--radius-md: calc(var(--radius) - 2px);
|
||||
--radius-lg: var(--radius);
|
||||
--radius-xl: calc(var(--radius) + 4px);
|
||||
--color-background: var(--background);
|
||||
--color-foreground: var(--foreground);
|
||||
--color-card: var(--card);
|
||||
--color-card-foreground: var(--card-foreground);
|
||||
--color-popover: var(--popover);
|
||||
--color-popover-foreground: var(--popover-foreground);
|
||||
--color-primary: var(--primary);
|
||||
--color-primary-foreground: var(--primary-foreground);
|
||||
--color-secondary: var(--secondary);
|
||||
--color-secondary-foreground: var(--secondary-foreground);
|
||||
--color-muted: var(--muted);
|
||||
--color-muted-foreground: var(--muted-foreground);
|
||||
--color-accent: var(--accent);
|
||||
--color-accent-foreground: var(--accent-foreground);
|
||||
--color-destructive: var(--destructive);
|
||||
--color-border: var(--border);
|
||||
--color-input: var(--input);
|
||||
--color-ring: var(--ring);
|
||||
--color-chart-1: var(--chart-1);
|
||||
--color-chart-2: var(--chart-2);
|
||||
--color-chart-3: var(--chart-3);
|
||||
--color-chart-4: var(--chart-4);
|
||||
--color-chart-5: var(--chart-5);
|
||||
--color-sidebar: var(--sidebar);
|
||||
--color-sidebar-foreground: var(--sidebar-foreground);
|
||||
--color-sidebar-primary: var(--sidebar-primary);
|
||||
--color-sidebar-primary-foreground: var(--sidebar-primary-foreground);
|
||||
--color-sidebar-accent: var(--sidebar-accent);
|
||||
--color-sidebar-accent-foreground: var(--sidebar-accent-foreground);
|
||||
--color-sidebar-border: var(--sidebar-border);
|
||||
--color-sidebar-ring: var(--sidebar-ring);
|
||||
--radius-sm: calc(var(--radius) - 4px);
|
||||
--radius-md: calc(var(--radius) - 2px);
|
||||
--radius-lg: var(--radius);
|
||||
--radius-xl: calc(var(--radius) + 4px);
|
||||
--color-background: var(--background);
|
||||
--color-foreground: var(--foreground);
|
||||
--color-card: var(--card);
|
||||
--color-card-foreground: var(--card-foreground);
|
||||
--color-popover: var(--popover);
|
||||
--color-popover-foreground: var(--popover-foreground);
|
||||
--color-primary: var(--primary);
|
||||
--color-primary-foreground: var(--primary-foreground);
|
||||
--color-secondary: var(--secondary);
|
||||
--color-secondary-foreground: var(--secondary-foreground);
|
||||
--color-muted: var(--muted);
|
||||
--color-muted-foreground: var(--muted-foreground);
|
||||
--color-accent: var(--accent);
|
||||
--color-accent-foreground: var(--accent-foreground);
|
||||
--color-destructive: var(--destructive);
|
||||
--color-border: var(--border);
|
||||
--color-input: var(--input);
|
||||
--color-ring: var(--ring);
|
||||
--color-chart-1: var(--chart-1);
|
||||
--color-chart-2: var(--chart-2);
|
||||
--color-chart-3: var(--chart-3);
|
||||
--color-chart-4: var(--chart-4);
|
||||
--color-chart-5: var(--chart-5);
|
||||
--color-sidebar: var(--sidebar);
|
||||
--color-sidebar-foreground: var(--sidebar-foreground);
|
||||
--color-sidebar-primary: var(--sidebar-primary);
|
||||
--color-sidebar-primary-foreground: var(--sidebar-primary-foreground);
|
||||
--color-sidebar-accent: var(--sidebar-accent);
|
||||
--color-sidebar-accent-foreground: var(--sidebar-accent-foreground);
|
||||
--color-sidebar-border: var(--sidebar-border);
|
||||
--color-sidebar-ring: var(--sidebar-ring);
|
||||
|
||||
--color-lighter: var(--lighter);
|
||||
--color-light: var(--light);
|
||||
--color-medium: var(--medium);
|
||||
--color-dark: var(--dark);
|
||||
--color-darker: var(--darker);
|
||||
--color-lighter: var(--lighter);
|
||||
--color-light: var(--light);
|
||||
--color-medium: var(--medium);
|
||||
--color-dark: var(--dark);
|
||||
--color-darker: var(--darker);
|
||||
|
||||
--max-w-sm: 400px;
|
||||
--max-w-md: 640px;
|
||||
--max-w-sm: 400px;
|
||||
--max-w-md: 640px;
|
||||
}
|
||||
|
||||
@layer base {
|
||||
* {
|
||||
border-color: var(--border);
|
||||
outline-color: color-mix(in oklab, var(--ring) 50%, transparent);
|
||||
}
|
||||
* {
|
||||
border-color: var(--border);
|
||||
outline-color: color-mix(in oklab, var(--ring) 50%, transparent);
|
||||
}
|
||||
|
||||
body {
|
||||
background-color: var(--background);
|
||||
color: var(--foreground);
|
||||
}
|
||||
body {
|
||||
background-color: var(--background);
|
||||
color: var(--foreground);
|
||||
}
|
||||
}
|
||||
|
||||
Vendored
+12
-12
@@ -4,18 +4,18 @@ import type { JWTPayload } from "jose";
|
||||
|
||||
// for information about these interfaces
|
||||
declare global {
|
||||
namespace App {
|
||||
// interface Error {}
|
||||
interface Locals {
|
||||
user?: JWTPayload & { userId: string; sessionId: string };
|
||||
}
|
||||
// interface PageData {}
|
||||
// interface PageState {}
|
||||
// interface Platform {}
|
||||
}
|
||||
interface PageState {
|
||||
email?: string;
|
||||
}
|
||||
namespace App {
|
||||
// interface Error {}
|
||||
interface Locals {
|
||||
user?: JWTPayload & { userId: string; sessionId: string };
|
||||
}
|
||||
// interface PageData {}
|
||||
// interface PageState {}
|
||||
// interface Platform {}
|
||||
}
|
||||
interface PageState {
|
||||
email?: string;
|
||||
}
|
||||
}
|
||||
|
||||
export {};
|
||||
|
||||
+15
-15
@@ -1,19 +1,19 @@
|
||||
<!doctype html>
|
||||
<html lang="%lang%">
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||
<meta name="robots" content="index, follow" />
|
||||
<link rel="icon" type="image/svg+xml" href="%sveltekit.assets%/favicon.svg" />
|
||||
<link rel="apple-touch-icon" sizes="180x180" href="%sveltekit.assets%/favicon-192.png" />
|
||||
<link rel="icon" type="image/png" sizes="192x192" href="%sveltekit.assets%/favicon-192.png" />
|
||||
<link rel="icon" type="image/png" sizes="512x512" href="%sveltekit.assets%/favicon-512.png" />
|
||||
<link rel="mask-icon" href="%sveltekit.assets%/safari-pinned-tab.svg" color="#1A1B4D" />
|
||||
<meta name="msapplication-TileColor" color="#1A1B4D" />
|
||||
%sveltekit.head%
|
||||
</head>
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||
<meta name="robots" content="index, follow" />
|
||||
<link rel="icon" type="image/svg+xml" href="%sveltekit.assets%/favicon.svg" />
|
||||
<link rel="apple-touch-icon" sizes="180x180" href="%sveltekit.assets%/favicon-192.png" />
|
||||
<link rel="icon" type="image/png" sizes="192x192" href="%sveltekit.assets%/favicon-192.png" />
|
||||
<link rel="icon" type="image/png" sizes="512x512" href="%sveltekit.assets%/favicon-512.png" />
|
||||
<link rel="mask-icon" href="%sveltekit.assets%/safari-pinned-tab.svg" color="#1A1B4D" />
|
||||
<meta name="msapplication-TileColor" color="#1A1B4D" />
|
||||
%sveltekit.head%
|
||||
</head>
|
||||
|
||||
<body data-sveltekit-preload-data="hover">
|
||||
<div style="display: contents">%sveltekit.body%</div>
|
||||
</body>
|
||||
<body data-sveltekit-preload-data="hover">
|
||||
<div style="display: contents">%sveltekit.body%</div>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
+3
-3
@@ -1,7 +1,7 @@
|
||||
import { describe, it, expect } from "vitest";
|
||||
|
||||
describe("sum test", () => {
|
||||
it("adds 1 + 2 to equal 3", () => {
|
||||
expect(1 + 2).toBe(3);
|
||||
});
|
||||
it("adds 1 + 2 to equal 3", () => {
|
||||
expect(1 + 2).toBe(3);
|
||||
});
|
||||
});
|
||||
|
||||
+165
-165
@@ -4,224 +4,224 @@ import { handle } from "./hooks.server";
|
||||
|
||||
// Mock the startup service
|
||||
vi.mock("$lib/server/services/startup-service", () => ({
|
||||
StartupService: {
|
||||
initialize: vi.fn(() => Promise.resolve())
|
||||
}
|
||||
StartupService: {
|
||||
initialize: vi.fn(() => Promise.resolve()),
|
||||
},
|
||||
}));
|
||||
|
||||
// Mock auth services
|
||||
vi.mock("$lib/server/auth/session-service", () => ({
|
||||
SessionService: {
|
||||
validateSession: vi.fn()
|
||||
}
|
||||
SessionService: {
|
||||
validateSession: vi.fn(),
|
||||
},
|
||||
}));
|
||||
|
||||
vi.mock("$lib/server/auth/jwt-utils", () => ({
|
||||
verifyAccessToken: vi.fn()
|
||||
verifyAccessToken: vi.fn(),
|
||||
}));
|
||||
|
||||
vi.mock("$lib/server/auth/authorization-service", () => ({
|
||||
AuthorizationService: {
|
||||
hasRole: vi.fn(),
|
||||
hasAnyRole: vi.fn()
|
||||
}
|
||||
AuthorizationService: {
|
||||
hasRole: vi.fn(),
|
||||
hasAnyRole: vi.fn(),
|
||||
},
|
||||
}));
|
||||
|
||||
// Mock the Date.now function for rate limiting tests
|
||||
const mockDateNow = vi.fn();
|
||||
const OriginalDate = Date;
|
||||
vi.stubGlobal(
|
||||
"Date",
|
||||
class extends OriginalDate {
|
||||
static now = mockDateNow;
|
||||
}
|
||||
"Date",
|
||||
class extends OriginalDate {
|
||||
static now = mockDateNow;
|
||||
},
|
||||
);
|
||||
|
||||
describe("hooks.server", () => {
|
||||
beforeEach(() => {
|
||||
vi.clearAllMocks();
|
||||
mockDateNow.mockReturnValue(1000000); // Fixed timestamp for consistent testing
|
||||
});
|
||||
beforeEach(() => {
|
||||
vi.clearAllMocks();
|
||||
mockDateNow.mockReturnValue(1000000); // Fixed timestamp for consistent testing
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
// Reset any global state
|
||||
vi.clearAllTimers();
|
||||
});
|
||||
afterEach(() => {
|
||||
// Reset any global state
|
||||
vi.clearAllTimers();
|
||||
});
|
||||
|
||||
describe("rate limiting", () => {
|
||||
const mockResolve = vi.fn();
|
||||
const createEvent = (ip: string = "192.168.1.1", method: string = "GET") => {
|
||||
const request = new Request("http://localhost/api/health", {
|
||||
method,
|
||||
headers: {
|
||||
"x-forwarded-for": ip
|
||||
}
|
||||
});
|
||||
describe("rate limiting", () => {
|
||||
const mockResolve = vi.fn();
|
||||
const createEvent = (ip: string = "192.168.1.1", method: string = "GET") => {
|
||||
const request = new Request("http://localhost/api/health", {
|
||||
method,
|
||||
headers: {
|
||||
"x-forwarded-for": ip,
|
||||
},
|
||||
});
|
||||
|
||||
return {
|
||||
url: new URL("http://localhost/api/health"),
|
||||
request,
|
||||
cookies: {} as any,
|
||||
fetch: {} as any,
|
||||
getClientAddress: () => ip,
|
||||
locals: {},
|
||||
params: {},
|
||||
route: { id: null },
|
||||
setHeaders: vi.fn(),
|
||||
isDataRequest: false,
|
||||
isSubRequest: false,
|
||||
platform: {} as any
|
||||
};
|
||||
};
|
||||
return {
|
||||
url: new URL("http://localhost/api/health"),
|
||||
request,
|
||||
cookies: {} as any,
|
||||
fetch: {} as any,
|
||||
getClientAddress: () => ip,
|
||||
locals: {},
|
||||
params: {},
|
||||
route: { id: null },
|
||||
setHeaders: vi.fn(),
|
||||
isDataRequest: false,
|
||||
isSubRequest: false,
|
||||
platform: {} as any,
|
||||
};
|
||||
};
|
||||
|
||||
beforeEach(() => {
|
||||
mockResolve.mockResolvedValue(new Response("OK"));
|
||||
});
|
||||
beforeEach(() => {
|
||||
mockResolve.mockResolvedValue(new Response("OK"));
|
||||
});
|
||||
|
||||
it("should allow requests within rate limit", async () => {
|
||||
const event = createEvent("192.168.1.100"); // Unique IP for this test
|
||||
it("should allow requests within rate limit", async () => {
|
||||
const event = createEvent("192.168.1.100"); // Unique IP for this test
|
||||
|
||||
const response = await handle({ event, resolve: mockResolve });
|
||||
const response = await handle({ event, resolve: mockResolve });
|
||||
|
||||
expect(response.status).not.toBe(429);
|
||||
expect(mockResolve).toHaveBeenCalled();
|
||||
expect(mockResolve.mock.calls[0][0]).toEqual(event);
|
||||
});
|
||||
expect(response.status).not.toBe(429);
|
||||
expect(mockResolve).toHaveBeenCalled();
|
||||
expect(mockResolve.mock.calls[0][0]).toEqual(event);
|
||||
});
|
||||
|
||||
it("should block requests when rate limit exceeded", async () => {
|
||||
const event = createEvent("192.168.1.101"); // Unique IP for this test
|
||||
it("should block requests when rate limit exceeded", async () => {
|
||||
const event = createEvent("192.168.1.101"); // Unique IP for this test
|
||||
|
||||
// Make multiple requests to exceed rate limit
|
||||
for (let i = 0; i < 10; i++) {
|
||||
await handle({ event, resolve: mockResolve });
|
||||
}
|
||||
// Make multiple requests to exceed rate limit
|
||||
for (let i = 0; i < 10; i++) {
|
||||
await handle({ event, resolve: mockResolve });
|
||||
}
|
||||
|
||||
// This request should be rate limited
|
||||
const response = await handle({ event, resolve: mockResolve });
|
||||
// This request should be rate limited
|
||||
const response = await handle({ event, resolve: mockResolve });
|
||||
|
||||
expect(response.status).toBe(429);
|
||||
expect(await response.text()).toBe("Too Many Requests");
|
||||
});
|
||||
expect(response.status).toBe(429);
|
||||
expect(await response.text()).toBe("Too Many Requests");
|
||||
});
|
||||
|
||||
it("should reset rate limit after window expires", async () => {
|
||||
const event = createEvent("192.168.1.102"); // Unique IP for this test
|
||||
it("should reset rate limit after window expires", async () => {
|
||||
const event = createEvent("192.168.1.102"); // Unique IP for this test
|
||||
|
||||
// Exceed rate limit
|
||||
for (let i = 0; i < 11; i++) {
|
||||
await handle({ event, resolve: mockResolve });
|
||||
}
|
||||
// Exceed rate limit
|
||||
for (let i = 0; i < 11; i++) {
|
||||
await handle({ event, resolve: mockResolve });
|
||||
}
|
||||
|
||||
// Mock time passing (would need to mock Date.now in real implementation)
|
||||
// For now, we'll test that different IPs are treated separately
|
||||
const differentIPEvent = createEvent("192.168.1.2");
|
||||
const response = await handle({ event: differentIPEvent, resolve: mockResolve });
|
||||
// Mock time passing (would need to mock Date.now in real implementation)
|
||||
// For now, we'll test that different IPs are treated separately
|
||||
const differentIPEvent = createEvent("192.168.1.2");
|
||||
const response = await handle({ event: differentIPEvent, resolve: mockResolve });
|
||||
|
||||
expect(response.status).not.toBe(429);
|
||||
});
|
||||
});
|
||||
expect(response.status).not.toBe(429);
|
||||
});
|
||||
});
|
||||
|
||||
describe("CORS handling", () => {
|
||||
const mockResolve = vi.fn();
|
||||
describe("CORS handling", () => {
|
||||
const mockResolve = vi.fn();
|
||||
|
||||
const createCORSEvent = (method: string = "GET", path: string = "/api/health") => ({
|
||||
url: new URL(`http://localhost${path}`),
|
||||
request: new Request(`http://localhost${path}`, {
|
||||
method,
|
||||
headers: {
|
||||
"x-forwarded-for": "192.168.2.1" // Different IP for CORS tests
|
||||
}
|
||||
}),
|
||||
cookies: {} as any,
|
||||
fetch: {} as any,
|
||||
getClientAddress: () => "192.168.2.1",
|
||||
locals: {},
|
||||
params: {},
|
||||
route: { id: null },
|
||||
setHeaders: vi.fn(),
|
||||
isDataRequest: false,
|
||||
isSubRequest: false,
|
||||
platform: {} as any
|
||||
});
|
||||
const createCORSEvent = (method: string = "GET", path: string = "/api/health") => ({
|
||||
url: new URL(`http://localhost${path}`),
|
||||
request: new Request(`http://localhost${path}`, {
|
||||
method,
|
||||
headers: {
|
||||
"x-forwarded-for": "192.168.2.1", // Different IP for CORS tests
|
||||
},
|
||||
}),
|
||||
cookies: {} as any,
|
||||
fetch: {} as any,
|
||||
getClientAddress: () => "192.168.2.1",
|
||||
locals: {},
|
||||
params: {},
|
||||
route: { id: null },
|
||||
setHeaders: vi.fn(),
|
||||
isDataRequest: false,
|
||||
isSubRequest: false,
|
||||
platform: {} as any,
|
||||
});
|
||||
|
||||
beforeEach(() => {
|
||||
mockResolve.mockResolvedValue(new Response("OK"));
|
||||
});
|
||||
beforeEach(() => {
|
||||
mockResolve.mockResolvedValue(new Response("OK"));
|
||||
});
|
||||
|
||||
it("should handle OPTIONS preflight requests", async () => {
|
||||
const event = createCORSEvent("OPTIONS");
|
||||
it("should handle OPTIONS preflight requests", async () => {
|
||||
const event = createCORSEvent("OPTIONS");
|
||||
|
||||
const response = await handle({ event, resolve: mockResolve });
|
||||
const response = await handle({ event, resolve: mockResolve });
|
||||
|
||||
expect(response.status).toBe(200);
|
||||
expect(response.headers.get("Access-Control-Allow-Origin")).toBe("*");
|
||||
expect(response.headers.get("Access-Control-Allow-Methods")).toContain("GET");
|
||||
expect(mockResolve).not.toHaveBeenCalled();
|
||||
});
|
||||
expect(response.status).toBe(200);
|
||||
expect(response.headers.get("Access-Control-Allow-Origin")).toBe("*");
|
||||
expect(response.headers.get("Access-Control-Allow-Methods")).toContain("GET");
|
||||
expect(mockResolve).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("should add CORS headers to API routes", async () => {
|
||||
const event = createCORSEvent();
|
||||
it("should add CORS headers to API routes", async () => {
|
||||
const event = createCORSEvent();
|
||||
|
||||
const response = await handle({ event, resolve: mockResolve });
|
||||
const response = await handle({ event, resolve: mockResolve });
|
||||
|
||||
expect(response.headers.get("Access-Control-Allow-Origin")).toBe("*");
|
||||
expect(response.headers.get("Access-Control-Allow-Methods")).toContain("GET");
|
||||
});
|
||||
});
|
||||
expect(response.headers.get("Access-Control-Allow-Origin")).toBe("*");
|
||||
expect(response.headers.get("Access-Control-Allow-Methods")).toContain("GET");
|
||||
});
|
||||
});
|
||||
|
||||
describe("security headers", () => {
|
||||
const mockResolve = vi.fn();
|
||||
describe("security headers", () => {
|
||||
const mockResolve = vi.fn();
|
||||
|
||||
const createSecurityEvent = (protocol: string = "http", path: string = "/test") => ({
|
||||
url: new URL(`${protocol}://localhost${path}`),
|
||||
request: new Request(`${protocol}://localhost${path}`, {
|
||||
headers: {
|
||||
"x-forwarded-for": "192.168.3.1" // Different IP for security tests
|
||||
}
|
||||
}),
|
||||
cookies: {} as any,
|
||||
fetch: {} as any,
|
||||
getClientAddress: () => "192.168.3.1",
|
||||
locals: {},
|
||||
params: {},
|
||||
route: { id: null },
|
||||
setHeaders: vi.fn(),
|
||||
isDataRequest: false,
|
||||
isSubRequest: false,
|
||||
platform: {} as any
|
||||
});
|
||||
const createSecurityEvent = (protocol: string = "http", path: string = "/test") => ({
|
||||
url: new URL(`${protocol}://localhost${path}`),
|
||||
request: new Request(`${protocol}://localhost${path}`, {
|
||||
headers: {
|
||||
"x-forwarded-for": "192.168.3.1", // Different IP for security tests
|
||||
},
|
||||
}),
|
||||
cookies: {} as any,
|
||||
fetch: {} as any,
|
||||
getClientAddress: () => "192.168.3.1",
|
||||
locals: {},
|
||||
params: {},
|
||||
route: { id: null },
|
||||
setHeaders: vi.fn(),
|
||||
isDataRequest: false,
|
||||
isSubRequest: false,
|
||||
platform: {} as any,
|
||||
});
|
||||
|
||||
beforeEach(() => {
|
||||
mockResolve.mockResolvedValue(new Response("OK"));
|
||||
});
|
||||
beforeEach(() => {
|
||||
mockResolve.mockResolvedValue(new Response("OK"));
|
||||
});
|
||||
|
||||
it("should add security headers to all responses", async () => {
|
||||
const event = createSecurityEvent();
|
||||
it("should add security headers to all responses", async () => {
|
||||
const event = createSecurityEvent();
|
||||
|
||||
const response = await handle({ event, resolve: mockResolve });
|
||||
const response = await handle({ event, resolve: mockResolve });
|
||||
|
||||
expect(response.headers.get("X-Frame-Options")).toBe("DENY");
|
||||
expect(response.headers.get("X-Content-Type-Options")).toBe("nosniff");
|
||||
expect(response.headers.get("X-XSS-Protection")).toBe("1; mode=block");
|
||||
expect(response.headers.get("Referrer-Policy")).toBe("strict-origin-when-cross-origin");
|
||||
expect(response.headers.get("Content-Security-Policy")).toContain("default-src 'self'");
|
||||
});
|
||||
expect(response.headers.get("X-Frame-Options")).toBe("DENY");
|
||||
expect(response.headers.get("X-Content-Type-Options")).toBe("nosniff");
|
||||
expect(response.headers.get("X-XSS-Protection")).toBe("1; mode=block");
|
||||
expect(response.headers.get("Referrer-Policy")).toBe("strict-origin-when-cross-origin");
|
||||
expect(response.headers.get("Content-Security-Policy")).toContain("default-src 'self'");
|
||||
});
|
||||
|
||||
it("should add HSTS header for HTTPS requests", async () => {
|
||||
const event = createSecurityEvent("https");
|
||||
it("should add HSTS header for HTTPS requests", async () => {
|
||||
const event = createSecurityEvent("https");
|
||||
|
||||
const response = await handle({ event, resolve: mockResolve });
|
||||
const response = await handle({ event, resolve: mockResolve });
|
||||
|
||||
expect(response.headers.get("Strict-Transport-Security")).toBe(
|
||||
"max-age=31536000; includeSubDomains; preload"
|
||||
);
|
||||
});
|
||||
expect(response.headers.get("Strict-Transport-Security")).toBe(
|
||||
"max-age=31536000; includeSubDomains; preload",
|
||||
);
|
||||
});
|
||||
|
||||
it("should not add HSTS header for HTTP requests", async () => {
|
||||
const event = createSecurityEvent("http");
|
||||
it("should not add HSTS header for HTTP requests", async () => {
|
||||
const event = createSecurityEvent("http");
|
||||
|
||||
const response = await handle({ event, resolve: mockResolve });
|
||||
const response = await handle({ event, resolve: mockResolve });
|
||||
|
||||
expect(response.headers.get("Strict-Transport-Security")).toBeNull();
|
||||
});
|
||||
});
|
||||
expect(response.headers.get("Strict-Transport-Security")).toBeNull();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
+24
-24
@@ -11,36 +11,36 @@ import { i18nHandle } from "./server-hooks/i18nHandle";
|
||||
import { building } from "$app/environment";
|
||||
|
||||
type Error = {
|
||||
message?: string;
|
||||
stack?: string;
|
||||
message?: string;
|
||||
stack?: string;
|
||||
};
|
||||
|
||||
export async function handleError({ error, event, status, message }) {
|
||||
const errorLogger = logger.setContext("ERROR_HANDLER");
|
||||
const errorLogger = logger.setContext("ERROR_HANDLER");
|
||||
|
||||
errorLogger.error("Unhandled error occurred", {
|
||||
error: (error as Error).message,
|
||||
stack: (error as Error).stack,
|
||||
status,
|
||||
message,
|
||||
url: event.url.pathname,
|
||||
method: event.request.method,
|
||||
userAgent: event.request.headers.get("user-agent"),
|
||||
ip: !building ? event.getClientAddress() : "server"
|
||||
});
|
||||
errorLogger.error("Unhandled error occurred", {
|
||||
error: (error as Error).message,
|
||||
stack: (error as Error).stack,
|
||||
status,
|
||||
message,
|
||||
url: event.url.pathname,
|
||||
method: event.request.method,
|
||||
userAgent: event.request.headers.get("user-agent"),
|
||||
ip: !building ? event.getClientAddress() : "server",
|
||||
});
|
||||
|
||||
return {
|
||||
message: "Internal server error occurred"
|
||||
};
|
||||
return {
|
||||
message: "Internal server error occurred",
|
||||
};
|
||||
}
|
||||
|
||||
export const handle = sequence(
|
||||
startupHandle,
|
||||
loggingHandle,
|
||||
i18nHandle,
|
||||
rateLimitHandle,
|
||||
corsHandle,
|
||||
secHeaderHandle,
|
||||
apiAuthHandle,
|
||||
authGuard
|
||||
startupHandle,
|
||||
loggingHandle,
|
||||
i18nHandle,
|
||||
rateLimitHandle,
|
||||
corsHandle,
|
||||
secHeaderHandle,
|
||||
apiAuthHandle,
|
||||
authGuard,
|
||||
);
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
<script lang="ts">
|
||||
import { Text } from "$lib/components/ui/typography";
|
||||
import type { HTMLAttributes } from "svelte/elements";
|
||||
import { Text } from "$lib/components/ui/typography";
|
||||
import type { HTMLAttributes } from "svelte/elements";
|
||||
|
||||
let { children }: HTMLAttributes<HTMLDivElement> = $props();
|
||||
let { children }: HTMLAttributes<HTMLDivElement> = $props();
|
||||
</script>
|
||||
|
||||
<Text style="xs" class="text-medium px-5 text-center">
|
||||
{@render children?.()}
|
||||
{@render children?.()}
|
||||
</Text>
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
<script lang="ts">
|
||||
import type { HTMLAttributes } from "svelte/elements";
|
||||
import type { HTMLAttributes } from "svelte/elements";
|
||||
|
||||
let { children }: HTMLAttributes<HTMLDivElement> = $props();
|
||||
let { children }: HTMLAttributes<HTMLDivElement> = $props();
|
||||
</script>
|
||||
|
||||
<div class="flex flex-col gap-2">
|
||||
{@render children?.()}
|
||||
{@render children?.()}
|
||||
</div>
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
<script lang="ts">
|
||||
import { Text } from "$lib/components/ui/typography";
|
||||
import { cn } from "$lib/utils";
|
||||
import type { HTMLAttributes } from "svelte/elements";
|
||||
import { Text } from "$lib/components/ui/typography";
|
||||
import { cn } from "$lib/utils";
|
||||
import type { HTMLAttributes } from "svelte/elements";
|
||||
|
||||
let { class: className, children }: HTMLAttributes<HTMLDivElement> = $props();
|
||||
let { class: className, children }: HTMLAttributes<HTMLDivElement> = $props();
|
||||
</script>
|
||||
|
||||
<Text style="md" color="medium" class={cn(className)}>
|
||||
{@render children?.()}
|
||||
{@render children?.()}
|
||||
</Text>
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
<script lang="ts">
|
||||
import type { HTMLAttributes } from "svelte/elements";
|
||||
import type { HTMLAttributes } from "svelte/elements";
|
||||
|
||||
let { children }: HTMLAttributes<HTMLDivElement> = $props();
|
||||
let { children }: HTMLAttributes<HTMLDivElement> = $props();
|
||||
</script>
|
||||
|
||||
<div class="flex flex-col justify-between gap-2">
|
||||
{@render children?.()}
|
||||
{@render children?.()}
|
||||
</div>
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
<script lang="ts">
|
||||
import { cn } from "$lib/utils";
|
||||
import type { HTMLAttributes } from "svelte/elements";
|
||||
import { cn } from "$lib/utils";
|
||||
import type { HTMLAttributes } from "svelte/elements";
|
||||
|
||||
let { class: className, children, ...restProps }: HTMLAttributes<HTMLDivElement> = $props();
|
||||
let { class: className, children, ...restProps }: HTMLAttributes<HTMLDivElement> = $props();
|
||||
</script>
|
||||
|
||||
<div class={cn("flex grow flex-col gap-2", className)} {...restProps}>
|
||||
{@render children?.()}
|
||||
{@render children?.()}
|
||||
</div>
|
||||
|
||||
@@ -1,21 +1,21 @@
|
||||
<script lang="ts">
|
||||
import { cn } from "$lib/utils";
|
||||
import type { HTMLAttributes } from "svelte/elements";
|
||||
import { HorizontalPagePadding } from "../../ui/page";
|
||||
import { Card } from "$lib/components/ui/card";
|
||||
import { cn } from "$lib/utils";
|
||||
import type { HTMLAttributes } from "svelte/elements";
|
||||
import { HorizontalPagePadding } from "../../ui/page";
|
||||
import { Card } from "$lib/components/ui/card";
|
||||
|
||||
let { class: className, children, ...restProps }: HTMLAttributes<HTMLDivElement> = $props();
|
||||
let { class: className, children, ...restProps }: HTMLAttributes<HTMLDivElement> = $props();
|
||||
</script>
|
||||
|
||||
<HorizontalPagePadding
|
||||
as="main"
|
||||
class={cn(
|
||||
"mx-auto flex w-full max-w-(--max-w-md) grow flex-col items-start pt-3 pb-4 sm:max-w-(--max-w-sm) sm:justify-center",
|
||||
className
|
||||
)}
|
||||
{...restProps}
|
||||
as="main"
|
||||
class={cn(
|
||||
"mx-auto flex w-full max-w-(--max-w-md) grow flex-col items-start pt-3 pb-4 sm:max-w-(--max-w-sm) sm:justify-center",
|
||||
className,
|
||||
)}
|
||||
{...restProps}
|
||||
>
|
||||
<Card class="w-full grow sm:grow-0">
|
||||
{@render children?.()}
|
||||
</Card>
|
||||
<Card class="w-full grow sm:grow-0">
|
||||
{@render children?.()}
|
||||
</Card>
|
||||
</HorizontalPagePadding>
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
<script lang="ts">
|
||||
import { Headline } from "$lib/components/ui/typography";
|
||||
import { cn } from "$lib/utils";
|
||||
import type { HTMLAttributes } from "svelte/elements";
|
||||
import { Headline } from "$lib/components/ui/typography";
|
||||
import { cn } from "$lib/utils";
|
||||
import type { HTMLAttributes } from "svelte/elements";
|
||||
|
||||
let { class: className, children }: HTMLAttributes<HTMLHeadingElement> = $props();
|
||||
let { class: className, children }: HTMLAttributes<HTMLHeadingElement> = $props();
|
||||
</script>
|
||||
|
||||
<Headline level="h1" style="h4" class={cn(className)}>
|
||||
{@render children?.()}
|
||||
{@render children?.()}
|
||||
</Headline>
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
<script lang="ts">
|
||||
import type { HTMLAttributes } from "svelte/elements";
|
||||
import type { HTMLAttributes } from "svelte/elements";
|
||||
|
||||
let { children }: HTMLAttributes<HTMLDivElement> = $props();
|
||||
let { children }: HTMLAttributes<HTMLDivElement> = $props();
|
||||
</script>
|
||||
|
||||
{@render children?.()}
|
||||
|
||||
@@ -1,16 +1,16 @@
|
||||
<script lang="ts">
|
||||
import { cn } from "$lib/utils";
|
||||
import type { HTMLAttributes } from "svelte/elements";
|
||||
import { cn } from "$lib/utils";
|
||||
import type { HTMLAttributes } from "svelte/elements";
|
||||
|
||||
let { class: className, children, ...restProps }: HTMLAttributes<HTMLDivElement> = $props();
|
||||
let { class: className, children, ...restProps }: HTMLAttributes<HTMLDivElement> = $props();
|
||||
</script>
|
||||
|
||||
<div
|
||||
class={cn(
|
||||
"flex grow flex-col items-center justify-center gap-5 text-center md:grow-0",
|
||||
className
|
||||
)}
|
||||
{...restProps}
|
||||
class={cn(
|
||||
"flex grow flex-col items-center justify-center gap-5 text-center md:grow-0",
|
||||
className,
|
||||
)}
|
||||
{...restProps}
|
||||
>
|
||||
{@render children?.()}
|
||||
{@render children?.()}
|
||||
</div>
|
||||
|
||||
@@ -1,18 +1,18 @@
|
||||
<script lang="ts">
|
||||
import { cn } from "$lib/utils";
|
||||
import type { HTMLAttributes } from "svelte/elements";
|
||||
import { HorizontalPagePadding } from "../../ui/page";
|
||||
import { cn } from "$lib/utils";
|
||||
import type { HTMLAttributes } from "svelte/elements";
|
||||
import { HorizontalPagePadding } from "../../ui/page";
|
||||
|
||||
let { class: className, children, ...restProps }: HTMLAttributes<HTMLDivElement> = $props();
|
||||
let { class: className, children, ...restProps }: HTMLAttributes<HTMLDivElement> = $props();
|
||||
</script>
|
||||
|
||||
<HorizontalPagePadding
|
||||
as="main"
|
||||
data-slot="card-action"
|
||||
class={cn("mx-auto flex max-w-(--max-w-sm) grow flex-col items-start py-10", className)}
|
||||
{...restProps}
|
||||
as="main"
|
||||
data-slot="card-action"
|
||||
class={cn("mx-auto flex max-w-(--max-w-sm) grow flex-col items-start py-10", className)}
|
||||
{...restProps}
|
||||
>
|
||||
<div class="flex grow flex-col items-center justify-between gap-10 md:justify-center">
|
||||
{@render children?.()}
|
||||
</div>
|
||||
<div class="flex grow flex-col items-center justify-between gap-10 md:justify-center">
|
||||
{@render children?.()}
|
||||
</div>
|
||||
</HorizontalPagePadding>
|
||||
|
||||
@@ -1,27 +1,27 @@
|
||||
<script lang="ts">
|
||||
import * as Sidebar from "$lib/components/ui/sidebar/index.js";
|
||||
import type { ComponentProps } from "svelte";
|
||||
import NavPrimary from "./nav-primary.svelte";
|
||||
import NavSecondary from "./nav-secondary.svelte";
|
||||
import NavUser from "./nav-user.svelte";
|
||||
import TenantSwitcher from "./tenant-switcher.svelte";
|
||||
let {
|
||||
ref = $bindable(null),
|
||||
collapsible = "icon",
|
||||
...restProps
|
||||
}: ComponentProps<typeof Sidebar.Root> = $props();
|
||||
import * as Sidebar from "$lib/components/ui/sidebar/index.js";
|
||||
import type { ComponentProps } from "svelte";
|
||||
import NavPrimary from "./nav-primary.svelte";
|
||||
import NavSecondary from "./nav-secondary.svelte";
|
||||
import NavUser from "./nav-user.svelte";
|
||||
import TenantSwitcher from "./tenant-switcher.svelte";
|
||||
let {
|
||||
ref = $bindable(null),
|
||||
collapsible = "icon",
|
||||
...restProps
|
||||
}: ComponentProps<typeof Sidebar.Root> = $props();
|
||||
</script>
|
||||
|
||||
<Sidebar.Root {collapsible} {...restProps}>
|
||||
<Sidebar.Header>
|
||||
<TenantSwitcher />
|
||||
</Sidebar.Header>
|
||||
<Sidebar.Content class="flex flex-col justify-between gap-3">
|
||||
<NavPrimary />
|
||||
<NavSecondary />
|
||||
</Sidebar.Content>
|
||||
<Sidebar.Footer>
|
||||
<NavUser />
|
||||
</Sidebar.Footer>
|
||||
<Sidebar.Rail />
|
||||
<Sidebar.Header>
|
||||
<TenantSwitcher />
|
||||
</Sidebar.Header>
|
||||
<Sidebar.Content class="flex flex-col justify-between gap-3">
|
||||
<NavPrimary />
|
||||
<NavSecondary />
|
||||
</Sidebar.Content>
|
||||
<Sidebar.Footer>
|
||||
<NavUser />
|
||||
</Sidebar.Footer>
|
||||
<Sidebar.Rail />
|
||||
</Sidebar.Root>
|
||||
|
||||
@@ -1,70 +1,70 @@
|
||||
<script lang="ts">
|
||||
import * as Sidebar from "$lib/components/ui/sidebar/index.js";
|
||||
import { Text } from "$lib/components/ui/typography";
|
||||
import { ROUTES } from "$lib/const/routes";
|
||||
import { auth } from "$lib/stores/auth";
|
||||
import { isCurrentSection } from "$lib/utils/routes";
|
||||
import TenantsIcon from "@lucide/svelte/icons/tickets";
|
||||
import CalendarIcon from "@lucide/svelte/icons/calendar";
|
||||
import HomeIcon from "@lucide/svelte/icons/house";
|
||||
import ChannelsIcon from "@lucide/svelte/icons/split";
|
||||
import AbsencesIcon from "@lucide/svelte/icons/tree-palm";
|
||||
import AgentsIcon from "@lucide/svelte/icons/user-star";
|
||||
import { m } from "$i18n/messages";
|
||||
import * as Sidebar from "$lib/components/ui/sidebar/index.js";
|
||||
import { Text } from "$lib/components/ui/typography";
|
||||
import { ROUTES } from "$lib/const/routes";
|
||||
import { auth } from "$lib/stores/auth";
|
||||
import { isCurrentSection } from "$lib/utils/routes";
|
||||
import TenantsIcon from "@lucide/svelte/icons/tickets";
|
||||
import CalendarIcon from "@lucide/svelte/icons/calendar";
|
||||
import HomeIcon from "@lucide/svelte/icons/house";
|
||||
import ChannelsIcon from "@lucide/svelte/icons/split";
|
||||
import AbsencesIcon from "@lucide/svelte/icons/tree-palm";
|
||||
import AgentsIcon from "@lucide/svelte/icons/user-star";
|
||||
import { m } from "$i18n/messages";
|
||||
|
||||
const items = [
|
||||
{
|
||||
title: m["nav.home"](),
|
||||
url: ROUTES.DASHBOARD.MAIN,
|
||||
isTenatOnly: false,
|
||||
icon: HomeIcon
|
||||
},
|
||||
{
|
||||
title: m["nav.tenants"](),
|
||||
url: ROUTES.DASHBOARD.TENANTS,
|
||||
isTenatOnly: false,
|
||||
icon: TenantsIcon
|
||||
},
|
||||
{
|
||||
title: m["nav.calendar"](),
|
||||
url: ROUTES.DASHBOARD.CALENDAR,
|
||||
isTenatOnly: true,
|
||||
icon: CalendarIcon
|
||||
},
|
||||
{
|
||||
title: m["nav.agents"](),
|
||||
url: ROUTES.DASHBOARD.AGENTS,
|
||||
isTenatOnly: true,
|
||||
icon: AgentsIcon
|
||||
},
|
||||
{
|
||||
title: m["nav.channels"](),
|
||||
url: ROUTES.DASHBOARD.CHANNELS,
|
||||
isTenatOnly: true,
|
||||
icon: ChannelsIcon
|
||||
},
|
||||
{
|
||||
title: m["nav.absences"](),
|
||||
url: ROUTES.DASHBOARD.ABSENCES,
|
||||
isTenatOnly: true,
|
||||
icon: AbsencesIcon
|
||||
}
|
||||
];
|
||||
const items = [
|
||||
{
|
||||
title: m["nav.home"](),
|
||||
url: ROUTES.DASHBOARD.MAIN,
|
||||
isTenatOnly: false,
|
||||
icon: HomeIcon,
|
||||
},
|
||||
{
|
||||
title: m["nav.tenants"](),
|
||||
url: ROUTES.DASHBOARD.TENANTS,
|
||||
isTenatOnly: false,
|
||||
icon: TenantsIcon,
|
||||
},
|
||||
{
|
||||
title: m["nav.calendar"](),
|
||||
url: ROUTES.DASHBOARD.CALENDAR,
|
||||
isTenatOnly: true,
|
||||
icon: CalendarIcon,
|
||||
},
|
||||
{
|
||||
title: m["nav.agents"](),
|
||||
url: ROUTES.DASHBOARD.AGENTS,
|
||||
isTenatOnly: true,
|
||||
icon: AgentsIcon,
|
||||
},
|
||||
{
|
||||
title: m["nav.channels"](),
|
||||
url: ROUTES.DASHBOARD.CHANNELS,
|
||||
isTenatOnly: true,
|
||||
icon: ChannelsIcon,
|
||||
},
|
||||
{
|
||||
title: m["nav.absences"](),
|
||||
url: ROUTES.DASHBOARD.ABSENCES,
|
||||
isTenatOnly: true,
|
||||
icon: AbsencesIcon,
|
||||
},
|
||||
];
|
||||
</script>
|
||||
|
||||
<Sidebar.Group class="gap-1">
|
||||
{#each items as item (item.title)}
|
||||
{#if item.isTenatOnly === false || $auth.user?.tenantId}
|
||||
<Sidebar.MenuItem>
|
||||
<Sidebar.MenuButton isActive={isCurrentSection(item.url)} tooltipContent={item.title}>
|
||||
{#snippet child({ props })}
|
||||
<a href={item.url} {...props}>
|
||||
<item.icon />
|
||||
<Text style="md" class="ml-2">{item.title}</Text>
|
||||
</a>
|
||||
{/snippet}
|
||||
</Sidebar.MenuButton>
|
||||
</Sidebar.MenuItem>
|
||||
{/if}
|
||||
{/each}
|
||||
{#each items as item (item.title)}
|
||||
{#if item.isTenatOnly === false || $auth.user?.tenantId}
|
||||
<Sidebar.MenuItem>
|
||||
<Sidebar.MenuButton isActive={isCurrentSection(item.url)} tooltipContent={item.title}>
|
||||
{#snippet child({ props })}
|
||||
<a href={item.url} {...props}>
|
||||
<item.icon />
|
||||
<Text style="md" class="ml-2">{item.title}</Text>
|
||||
</a>
|
||||
{/snippet}
|
||||
</Sidebar.MenuButton>
|
||||
</Sidebar.MenuItem>
|
||||
{/if}
|
||||
{/each}
|
||||
</Sidebar.Group>
|
||||
|
||||
@@ -1,57 +1,57 @@
|
||||
<script lang="ts">
|
||||
import * as Sidebar from "$lib/components/ui/sidebar/index.js";
|
||||
import { Text } from "$lib/components/ui/typography";
|
||||
import { ROUTES } from "$lib/const/routes";
|
||||
import { isCurrentSection } from "$lib/utils/routes";
|
||||
import DocsIcon from "@lucide/svelte/icons/book-open-text";
|
||||
import ExternalLinkIcon from "@lucide/svelte/icons/external-link";
|
||||
import SettingsIcon from "@lucide/svelte/icons/settings-2";
|
||||
import StaffIcon from "@lucide/svelte/icons/users";
|
||||
import { auth } from "$lib/stores/auth";
|
||||
import { m } from "$i18n/messages";
|
||||
import * as Sidebar from "$lib/components/ui/sidebar/index.js";
|
||||
import { Text } from "$lib/components/ui/typography";
|
||||
import { ROUTES } from "$lib/const/routes";
|
||||
import { isCurrentSection } from "$lib/utils/routes";
|
||||
import DocsIcon from "@lucide/svelte/icons/book-open-text";
|
||||
import ExternalLinkIcon from "@lucide/svelte/icons/external-link";
|
||||
import SettingsIcon from "@lucide/svelte/icons/settings-2";
|
||||
import StaffIcon from "@lucide/svelte/icons/users";
|
||||
import { auth } from "$lib/stores/auth";
|
||||
import { m } from "$i18n/messages";
|
||||
|
||||
const items = [
|
||||
{
|
||||
title: m["nav.staff"](),
|
||||
url: ROUTES.DASHBOARD.STAFF,
|
||||
isTenatOnly: true,
|
||||
icon: StaffIcon
|
||||
},
|
||||
{
|
||||
title: m["nav.settings"](),
|
||||
url: ROUTES.DASHBOARD.SETTINGS,
|
||||
isTenatOnly: true,
|
||||
icon: SettingsIcon
|
||||
},
|
||||
{
|
||||
title: m["nav.documentation"](),
|
||||
url: "https://open-reception.org",
|
||||
isTenatOnly: false,
|
||||
icon: DocsIcon
|
||||
}
|
||||
];
|
||||
const items = [
|
||||
{
|
||||
title: m["nav.staff"](),
|
||||
url: ROUTES.DASHBOARD.STAFF,
|
||||
isTenatOnly: true,
|
||||
icon: StaffIcon,
|
||||
},
|
||||
{
|
||||
title: m["nav.settings"](),
|
||||
url: ROUTES.DASHBOARD.SETTINGS,
|
||||
isTenatOnly: true,
|
||||
icon: SettingsIcon,
|
||||
},
|
||||
{
|
||||
title: m["nav.documentation"](),
|
||||
url: "https://open-reception.org",
|
||||
isTenatOnly: false,
|
||||
icon: DocsIcon,
|
||||
},
|
||||
];
|
||||
</script>
|
||||
|
||||
<Sidebar.Group>
|
||||
{#each items as item (item.title)}
|
||||
{#if item.isTenatOnly === false || $auth.user?.tenantId}
|
||||
<Sidebar.MenuItem>
|
||||
<Sidebar.MenuButton isActive={isCurrentSection(item.url)} tooltipContent={item.title}>
|
||||
{#snippet child({ props })}
|
||||
<a
|
||||
href={item.url}
|
||||
{...props}
|
||||
target={item.url.startsWith("http") ? "_blank" : undefined}
|
||||
>
|
||||
<item.icon />
|
||||
<Text style="xs">{item.title}</Text>
|
||||
{#if item.url.startsWith("http")}
|
||||
<ExternalLinkIcon class="text-light ml-auto !size-3" />
|
||||
{/if}
|
||||
</a>
|
||||
{/snippet}
|
||||
</Sidebar.MenuButton>
|
||||
</Sidebar.MenuItem>
|
||||
{/if}
|
||||
{/each}
|
||||
{#each items as item (item.title)}
|
||||
{#if item.isTenatOnly === false || $auth.user?.tenantId}
|
||||
<Sidebar.MenuItem>
|
||||
<Sidebar.MenuButton isActive={isCurrentSection(item.url)} tooltipContent={item.title}>
|
||||
{#snippet child({ props })}
|
||||
<a
|
||||
href={item.url}
|
||||
{...props}
|
||||
target={item.url.startsWith("http") ? "_blank" : undefined}
|
||||
>
|
||||
<item.icon />
|
||||
<Text style="xs">{item.title}</Text>
|
||||
{#if item.url.startsWith("http")}
|
||||
<ExternalLinkIcon class="text-light ml-auto !size-3" />
|
||||
{/if}
|
||||
</a>
|
||||
{/snippet}
|
||||
</Sidebar.MenuButton>
|
||||
</Sidebar.MenuItem>
|
||||
{/if}
|
||||
{/each}
|
||||
</Sidebar.Group>
|
||||
|
||||
@@ -1,77 +1,77 @@
|
||||
<script lang="ts">
|
||||
import { goto } from "$app/navigation";
|
||||
import * as Avatar from "$lib/components/ui/avatar";
|
||||
import * as DropdownMenu from "$lib/components/ui/dropdown-menu";
|
||||
import * as Sidebar from "$lib/components/ui/sidebar";
|
||||
import { useSidebar } from "$lib/components/ui/sidebar";
|
||||
import { ROUTES } from "$lib/const/routes";
|
||||
import ChevronsUpDownIcon from "@lucide/svelte/icons/chevrons-up-down";
|
||||
import LogOutIcon from "@lucide/svelte/icons/log-out";
|
||||
import AccountIcon from "@lucide/svelte/icons/shield-user";
|
||||
import { auth } from "$lib/stores/auth";
|
||||
import { nameToAvatarFallback } from "$lib/utils/name";
|
||||
import { LanguageSwitch } from "$lib/components/templates/language-switch";
|
||||
import { m } from "$i18n/messages";
|
||||
import { goto } from "$app/navigation";
|
||||
import * as Avatar from "$lib/components/ui/avatar";
|
||||
import * as DropdownMenu from "$lib/components/ui/dropdown-menu";
|
||||
import * as Sidebar from "$lib/components/ui/sidebar";
|
||||
import { useSidebar } from "$lib/components/ui/sidebar";
|
||||
import { ROUTES } from "$lib/const/routes";
|
||||
import ChevronsUpDownIcon from "@lucide/svelte/icons/chevrons-up-down";
|
||||
import LogOutIcon from "@lucide/svelte/icons/log-out";
|
||||
import AccountIcon from "@lucide/svelte/icons/shield-user";
|
||||
import { auth } from "$lib/stores/auth";
|
||||
import { nameToAvatarFallback } from "$lib/utils/name";
|
||||
import { LanguageSwitch } from "$lib/components/templates/language-switch";
|
||||
import { m } from "$i18n/messages";
|
||||
|
||||
const sidebar = useSidebar();
|
||||
const sidebar = useSidebar();
|
||||
</script>
|
||||
|
||||
<Sidebar.Menu>
|
||||
<Sidebar.MenuItem>
|
||||
<DropdownMenu.Root>
|
||||
<DropdownMenu.Trigger>
|
||||
{#snippet child({ props })}
|
||||
<Sidebar.MenuButton
|
||||
size="lg"
|
||||
class="data-[state=open]:bg-sidebar-accent data-[state=open]:text-sidebar-accent-foreground"
|
||||
{...props}
|
||||
>
|
||||
<Avatar.Root class="size-8 rounded-lg">
|
||||
<Avatar.Fallback class="rounded-lg">
|
||||
{nameToAvatarFallback($auth.user?.name)}
|
||||
</Avatar.Fallback>
|
||||
</Avatar.Root>
|
||||
<div class="grid flex-1 text-left text-sm leading-tight">
|
||||
<span class="truncate font-medium">{$auth.user?.name}</span>
|
||||
<span class="truncate text-xs">{$auth.user?.email}</span>
|
||||
</div>
|
||||
<ChevronsUpDownIcon class="ml-auto size-4" />
|
||||
</Sidebar.MenuButton>
|
||||
{/snippet}
|
||||
</DropdownMenu.Trigger>
|
||||
<DropdownMenu.Content
|
||||
class="w-(--bits-dropdown-menu-anchor-width) min-w-56 rounded-lg"
|
||||
side={sidebar.isMobile ? "bottom" : "right"}
|
||||
align="end"
|
||||
sideOffset={4}
|
||||
>
|
||||
<DropdownMenu.Label class="p-0 font-normal">
|
||||
<div class="flex items-center gap-2 px-1 py-1.5 text-left text-sm">
|
||||
<Avatar.Root class="size-8 rounded-lg">
|
||||
<Avatar.Fallback class="rounded-lg">
|
||||
{nameToAvatarFallback($auth.user?.name)}
|
||||
</Avatar.Fallback>
|
||||
</Avatar.Root>
|
||||
<div class="grid flex-1 text-left text-sm leading-tight">
|
||||
<span class="truncate font-medium">{$auth.user?.name}</span>
|
||||
<span class="truncate text-xs">{$auth.user?.email}</span>
|
||||
</div>
|
||||
</div>
|
||||
</DropdownMenu.Label>
|
||||
<DropdownMenu.Separator />
|
||||
<DropdownMenu.Group>
|
||||
<LanguageSwitch class="w-full" triggerClass="w-[100%] [&>svg]:ml-auto" />
|
||||
<DropdownMenu.Item onclick={() => goto(ROUTES.DASHBOARD.ACCOUNT)}>
|
||||
<AccountIcon />
|
||||
{m["nav.account"]()}
|
||||
</DropdownMenu.Item>
|
||||
</DropdownMenu.Group>
|
||||
<DropdownMenu.Separator />
|
||||
<DropdownMenu.Item onclick={() => goto(ROUTES.LOGOUT)}>
|
||||
<LogOutIcon />
|
||||
{m["nav.logout"]()}
|
||||
</DropdownMenu.Item>
|
||||
</DropdownMenu.Content>
|
||||
</DropdownMenu.Root>
|
||||
</Sidebar.MenuItem>
|
||||
<Sidebar.MenuItem>
|
||||
<DropdownMenu.Root>
|
||||
<DropdownMenu.Trigger>
|
||||
{#snippet child({ props })}
|
||||
<Sidebar.MenuButton
|
||||
size="lg"
|
||||
class="data-[state=open]:bg-sidebar-accent data-[state=open]:text-sidebar-accent-foreground"
|
||||
{...props}
|
||||
>
|
||||
<Avatar.Root class="size-8 rounded-lg">
|
||||
<Avatar.Fallback class="rounded-lg">
|
||||
{nameToAvatarFallback($auth.user?.name)}
|
||||
</Avatar.Fallback>
|
||||
</Avatar.Root>
|
||||
<div class="grid flex-1 text-left text-sm leading-tight">
|
||||
<span class="truncate font-medium">{$auth.user?.name}</span>
|
||||
<span class="truncate text-xs">{$auth.user?.email}</span>
|
||||
</div>
|
||||
<ChevronsUpDownIcon class="ml-auto size-4" />
|
||||
</Sidebar.MenuButton>
|
||||
{/snippet}
|
||||
</DropdownMenu.Trigger>
|
||||
<DropdownMenu.Content
|
||||
class="w-(--bits-dropdown-menu-anchor-width) min-w-56 rounded-lg"
|
||||
side={sidebar.isMobile ? "bottom" : "right"}
|
||||
align="end"
|
||||
sideOffset={4}
|
||||
>
|
||||
<DropdownMenu.Label class="p-0 font-normal">
|
||||
<div class="flex items-center gap-2 px-1 py-1.5 text-left text-sm">
|
||||
<Avatar.Root class="size-8 rounded-lg">
|
||||
<Avatar.Fallback class="rounded-lg">
|
||||
{nameToAvatarFallback($auth.user?.name)}
|
||||
</Avatar.Fallback>
|
||||
</Avatar.Root>
|
||||
<div class="grid flex-1 text-left text-sm leading-tight">
|
||||
<span class="truncate font-medium">{$auth.user?.name}</span>
|
||||
<span class="truncate text-xs">{$auth.user?.email}</span>
|
||||
</div>
|
||||
</div>
|
||||
</DropdownMenu.Label>
|
||||
<DropdownMenu.Separator />
|
||||
<DropdownMenu.Group>
|
||||
<LanguageSwitch class="w-full" triggerClass="w-[100%] [&>svg]:ml-auto" />
|
||||
<DropdownMenu.Item onclick={() => goto(ROUTES.DASHBOARD.ACCOUNT)}>
|
||||
<AccountIcon />
|
||||
{m["nav.account"]()}
|
||||
</DropdownMenu.Item>
|
||||
</DropdownMenu.Group>
|
||||
<DropdownMenu.Separator />
|
||||
<DropdownMenu.Item onclick={() => goto(ROUTES.LOGOUT)}>
|
||||
<LogOutIcon />
|
||||
{m["nav.logout"]()}
|
||||
</DropdownMenu.Item>
|
||||
</DropdownMenu.Content>
|
||||
</DropdownMenu.Root>
|
||||
</Sidebar.MenuItem>
|
||||
</Sidebar.Menu>
|
||||
|
||||
@@ -1,85 +1,85 @@
|
||||
<script lang="ts">
|
||||
import { m } from "$i18n/messages";
|
||||
import * as DropdownMenu from "$lib/components/ui/dropdown-menu";
|
||||
import * as Sidebar from "$lib/components/ui/sidebar";
|
||||
import { useSidebar } from "$lib/components/ui/sidebar";
|
||||
import { Text } from "$lib/components/ui/typography";
|
||||
import { auth } from "$lib/stores/auth";
|
||||
import type { TTenant } from "$lib/types/tenant";
|
||||
import ChevronsUpDownIcon from "@lucide/svelte/icons/chevrons-up-down";
|
||||
import UnknownTenantIcon from "@lucide/svelte/icons/ticket-x";
|
||||
import PlusIcon from "@lucide/svelte/icons/plus";
|
||||
import { m } from "$i18n/messages";
|
||||
import * as DropdownMenu from "$lib/components/ui/dropdown-menu";
|
||||
import * as Sidebar from "$lib/components/ui/sidebar";
|
||||
import { useSidebar } from "$lib/components/ui/sidebar";
|
||||
import { Text } from "$lib/components/ui/typography";
|
||||
import { auth } from "$lib/stores/auth";
|
||||
import type { TTenant } from "$lib/types/tenant";
|
||||
import ChevronsUpDownIcon from "@lucide/svelte/icons/chevrons-up-down";
|
||||
import UnknownTenantIcon from "@lucide/svelte/icons/ticket-x";
|
||||
import PlusIcon from "@lucide/svelte/icons/plus";
|
||||
|
||||
const sidebar = useSidebar();
|
||||
const sidebar = useSidebar();
|
||||
|
||||
const tenants: TTenant[] = [
|
||||
// {
|
||||
// id: "praxis-1",
|
||||
// name: "Praxis 1",
|
||||
// logo: ChevronsUpDownIcon
|
||||
// },
|
||||
// {
|
||||
// id: "praxis-2",
|
||||
// name: "Praxis 2",
|
||||
// logo: ChevronsUpDownIcon
|
||||
// }
|
||||
];
|
||||
let activeTenantId = $auth.user?.tenantId;
|
||||
let activeTenant = $derived(tenants.find((t) => t.id === activeTenantId));
|
||||
const tenants: TTenant[] = [
|
||||
// {
|
||||
// id: "praxis-1",
|
||||
// name: "Praxis 1",
|
||||
// logo: ChevronsUpDownIcon
|
||||
// },
|
||||
// {
|
||||
// id: "praxis-2",
|
||||
// name: "Praxis 2",
|
||||
// logo: ChevronsUpDownIcon
|
||||
// }
|
||||
];
|
||||
let activeTenantId = $auth.user?.tenantId;
|
||||
let activeTenant = $derived(tenants.find((t) => t.id === activeTenantId));
|
||||
</script>
|
||||
|
||||
<Sidebar.Menu>
|
||||
<Sidebar.MenuItem>
|
||||
<DropdownMenu.Root>
|
||||
<DropdownMenu.Trigger>
|
||||
{#snippet child({ props })}
|
||||
<Sidebar.MenuButton
|
||||
{...props}
|
||||
size="lg"
|
||||
class="data-[state=open]:bg-sidebar-accent data-[state=open]:text-sidebar-accent-foreground"
|
||||
>
|
||||
<div
|
||||
class="bg-sidebar-primary text-sidebar-primary-foreground flex aspect-square size-8 items-center justify-center rounded-lg"
|
||||
>
|
||||
<UnknownTenantIcon class="size-4" />
|
||||
</div>
|
||||
<div class="grid flex-1 text-left text-sm leading-tight">
|
||||
<Text style="md" class="truncate font-medium">
|
||||
{activeTenant?.name ?? m["nav.noTenantSelected.title"]()}
|
||||
</Text>
|
||||
<Text style="xs" class="truncate">
|
||||
{activeTenant?.url ?? m["nav.noTenantSelected.description"]()}
|
||||
</Text>
|
||||
</div>
|
||||
<ChevronsUpDownIcon class="ml-auto" />
|
||||
</Sidebar.MenuButton>
|
||||
{/snippet}
|
||||
</DropdownMenu.Trigger>
|
||||
<DropdownMenu.Content
|
||||
class="w-(--bits-dropdown-menu-anchor-width) min-w-56 rounded-lg"
|
||||
align="start"
|
||||
side={sidebar.isMobile ? "bottom" : "right"}
|
||||
sideOffset={4}
|
||||
>
|
||||
<DropdownMenu.Label class="text-muted-foreground text-xs"
|
||||
>{m["nav.tenants"]()}</DropdownMenu.Label
|
||||
>
|
||||
{#each tenants as tenant (tenant.id)}
|
||||
<DropdownMenu.Item onSelect={() => (activeTenantId = tenant.id)} class="gap-2 p-2">
|
||||
<div class="flex size-6 items-center justify-center rounded-md border">
|
||||
<UnknownTenantIcon class="size-3.5 shrink-0" />
|
||||
</div>
|
||||
{tenant.name}
|
||||
</DropdownMenu.Item>
|
||||
{/each}
|
||||
<DropdownMenu.Separator />
|
||||
<DropdownMenu.Item class="gap-2 p-2">
|
||||
<div class="flex size-6 items-center justify-center rounded-md border bg-transparent">
|
||||
<PlusIcon class="size-4" />
|
||||
</div>
|
||||
<div class="text-muted-foreground font-medium">{m["nav.addTenant"]()}</div>
|
||||
</DropdownMenu.Item>
|
||||
</DropdownMenu.Content>
|
||||
</DropdownMenu.Root>
|
||||
</Sidebar.MenuItem>
|
||||
<Sidebar.MenuItem>
|
||||
<DropdownMenu.Root>
|
||||
<DropdownMenu.Trigger>
|
||||
{#snippet child({ props })}
|
||||
<Sidebar.MenuButton
|
||||
{...props}
|
||||
size="lg"
|
||||
class="data-[state=open]:bg-sidebar-accent data-[state=open]:text-sidebar-accent-foreground"
|
||||
>
|
||||
<div
|
||||
class="bg-sidebar-primary text-sidebar-primary-foreground flex aspect-square size-8 items-center justify-center rounded-lg"
|
||||
>
|
||||
<UnknownTenantIcon class="size-4" />
|
||||
</div>
|
||||
<div class="grid flex-1 text-left text-sm leading-tight">
|
||||
<Text style="md" class="truncate font-medium">
|
||||
{activeTenant?.name ?? m["nav.noTenantSelected.title"]()}
|
||||
</Text>
|
||||
<Text style="xs" class="truncate">
|
||||
{activeTenant?.url ?? m["nav.noTenantSelected.description"]()}
|
||||
</Text>
|
||||
</div>
|
||||
<ChevronsUpDownIcon class="ml-auto" />
|
||||
</Sidebar.MenuButton>
|
||||
{/snippet}
|
||||
</DropdownMenu.Trigger>
|
||||
<DropdownMenu.Content
|
||||
class="w-(--bits-dropdown-menu-anchor-width) min-w-56 rounded-lg"
|
||||
align="start"
|
||||
side={sidebar.isMobile ? "bottom" : "right"}
|
||||
sideOffset={4}
|
||||
>
|
||||
<DropdownMenu.Label class="text-muted-foreground text-xs"
|
||||
>{m["nav.tenants"]()}</DropdownMenu.Label
|
||||
>
|
||||
{#each tenants as tenant (tenant.id)}
|
||||
<DropdownMenu.Item onSelect={() => (activeTenantId = tenant.id)} class="gap-2 p-2">
|
||||
<div class="flex size-6 items-center justify-center rounded-md border">
|
||||
<UnknownTenantIcon class="size-3.5 shrink-0" />
|
||||
</div>
|
||||
{tenant.name}
|
||||
</DropdownMenu.Item>
|
||||
{/each}
|
||||
<DropdownMenu.Separator />
|
||||
<DropdownMenu.Item class="gap-2 p-2">
|
||||
<div class="flex size-6 items-center justify-center rounded-md border bg-transparent">
|
||||
<PlusIcon class="size-4" />
|
||||
</div>
|
||||
<div class="text-muted-foreground font-medium">{m["nav.addTenant"]()}</div>
|
||||
</DropdownMenu.Item>
|
||||
</DropdownMenu.Content>
|
||||
</DropdownMenu.Root>
|
||||
</Sidebar.MenuItem>
|
||||
</Sidebar.Menu>
|
||||
|
||||
@@ -1,56 +1,56 @@
|
||||
<script lang="ts">
|
||||
import AppSidebar from "$lib/components/layouts/sidebar-layout/components/app-sidebar.svelte";
|
||||
import * as Breadcrumb from "$lib/components/ui/breadcrumb";
|
||||
import { HorizontalPagePadding, PageWithClaim } from "$lib/components/ui/page";
|
||||
import { Separator } from "$lib/components/ui/separator";
|
||||
import * as Sidebar from "$lib/components/ui/sidebar";
|
||||
import type { HTMLAttributes } from "svelte/elements";
|
||||
import { sidebar } from "$lib/stores/sidebar";
|
||||
import AppSidebar from "$lib/components/layouts/sidebar-layout/components/app-sidebar.svelte";
|
||||
import * as Breadcrumb from "$lib/components/ui/breadcrumb";
|
||||
import { HorizontalPagePadding, PageWithClaim } from "$lib/components/ui/page";
|
||||
import { Separator } from "$lib/components/ui/separator";
|
||||
import * as Sidebar from "$lib/components/ui/sidebar";
|
||||
import type { HTMLAttributes } from "svelte/elements";
|
||||
import { sidebar } from "$lib/stores/sidebar";
|
||||
|
||||
let {
|
||||
children,
|
||||
breakcrumbs
|
||||
}: HTMLAttributes<HTMLDivElement> & { breakcrumbs?: Array<{ label: string; href: string }> } =
|
||||
$props();
|
||||
let {
|
||||
children,
|
||||
breakcrumbs,
|
||||
}: HTMLAttributes<HTMLDivElement> & { breakcrumbs?: Array<{ label: string; href: string }> } =
|
||||
$props();
|
||||
</script>
|
||||
|
||||
<Sidebar.Provider bind:open={$sidebar.isOpen} onOpenChange={(open) => sidebar.setOpen(open)}>
|
||||
<AppSidebar />
|
||||
<Sidebar.Inset>
|
||||
<PageWithClaim>
|
||||
<HorizontalPagePadding>
|
||||
<header
|
||||
class="flex h-16 shrink-0 items-center gap-2 transition-[width,height] ease-linear group-has-data-[collapsible=icon]/sidebar-wrapper:h-12"
|
||||
>
|
||||
<div class="flex items-center gap-2">
|
||||
<Sidebar.Trigger class="-ml-1" />
|
||||
{#if breakcrumbs && breakcrumbs.length > 0}
|
||||
<Separator orientation="vertical" class="mr-2 data-[orientation=vertical]:h-4" />
|
||||
<Breadcrumb.Root>
|
||||
<Breadcrumb.List>
|
||||
{#each breakcrumbs as crumb, index (`${crumb.href}-${index}`)}
|
||||
{#if index === breakcrumbs.length - 1}
|
||||
<Breadcrumb.Item>
|
||||
<Breadcrumb.Page>
|
||||
{crumb.label}
|
||||
</Breadcrumb.Page>
|
||||
</Breadcrumb.Item>
|
||||
{:else}
|
||||
<Breadcrumb.Item class="hidden md:block">
|
||||
<Breadcrumb.Link href={crumb.href}>
|
||||
{crumb.label}
|
||||
</Breadcrumb.Link>
|
||||
</Breadcrumb.Item>
|
||||
<Breadcrumb.Separator class="hidden md:block" />
|
||||
{/if}
|
||||
{/each}
|
||||
</Breadcrumb.List>
|
||||
</Breadcrumb.Root>
|
||||
{/if}
|
||||
</div>
|
||||
</header>
|
||||
{@render children?.()}
|
||||
</HorizontalPagePadding>
|
||||
</PageWithClaim>
|
||||
</Sidebar.Inset>
|
||||
<AppSidebar />
|
||||
<Sidebar.Inset>
|
||||
<PageWithClaim>
|
||||
<HorizontalPagePadding>
|
||||
<header
|
||||
class="flex h-16 shrink-0 items-center gap-2 transition-[width,height] ease-linear group-has-data-[collapsible=icon]/sidebar-wrapper:h-12"
|
||||
>
|
||||
<div class="flex items-center gap-2">
|
||||
<Sidebar.Trigger class="-ml-1" />
|
||||
{#if breakcrumbs && breakcrumbs.length > 0}
|
||||
<Separator orientation="vertical" class="mr-2 data-[orientation=vertical]:h-4" />
|
||||
<Breadcrumb.Root>
|
||||
<Breadcrumb.List>
|
||||
{#each breakcrumbs as crumb, index (`${crumb.href}-${index}`)}
|
||||
{#if index === breakcrumbs.length - 1}
|
||||
<Breadcrumb.Item>
|
||||
<Breadcrumb.Page>
|
||||
{crumb.label}
|
||||
</Breadcrumb.Page>
|
||||
</Breadcrumb.Item>
|
||||
{:else}
|
||||
<Breadcrumb.Item class="hidden md:block">
|
||||
<Breadcrumb.Link href={crumb.href}>
|
||||
{crumb.label}
|
||||
</Breadcrumb.Link>
|
||||
</Breadcrumb.Item>
|
||||
<Breadcrumb.Separator class="hidden md:block" />
|
||||
{/if}
|
||||
{/each}
|
||||
</Breadcrumb.List>
|
||||
</Breadcrumb.Root>
|
||||
{/if}
|
||||
</div>
|
||||
</header>
|
||||
{@render children?.()}
|
||||
</HorizontalPagePadding>
|
||||
</PageWithClaim>
|
||||
</Sidebar.Inset>
|
||||
</Sidebar.Provider>
|
||||
|
||||
@@ -1,14 +1,14 @@
|
||||
<script lang="ts">
|
||||
import { Skeleton } from "$lib/components/ui/skeleton";
|
||||
import { cn } from "$lib/utils";
|
||||
import Loader from "@lucide/svelte/icons/loader-2";
|
||||
import { Skeleton } from "$lib/components/ui/skeleton";
|
||||
import { cn } from "$lib/utils";
|
||||
import Loader from "@lucide/svelte/icons/loader-2";
|
||||
|
||||
let { class: className = "" }: { class?: string } = $props();
|
||||
let { class: className = "" }: { class?: string } = $props();
|
||||
</script>
|
||||
|
||||
<div class={cn("my-10 flex grow flex-col justify-center gap-2 text-center", className)}>
|
||||
<Loader class="mx-auto mb-3 size-20 animate-spin" strokeWidth={1} />
|
||||
<Skeleton class="mx-auto h-10 w-45" />
|
||||
<Skeleton class="mx-auto h-5 w-60" />
|
||||
<Skeleton class="mx-auto h-5 w-35" />
|
||||
<Loader class="mx-auto mb-3 size-20 animate-spin" strokeWidth={1} />
|
||||
<Skeleton class="mx-auto h-10 w-45" />
|
||||
<Skeleton class="mx-auto h-5 w-60" />
|
||||
<Skeleton class="mx-auto h-5 w-35" />
|
||||
</div>
|
||||
|
||||
@@ -1,18 +1,18 @@
|
||||
<script lang="ts">
|
||||
import { Headline, Text } from "$lib/components/ui/typography";
|
||||
import { cn } from "$lib/utils";
|
||||
import type { Component } from "svelte";
|
||||
import { Headline, Text } from "$lib/components/ui/typography";
|
||||
import { cn } from "$lib/utils";
|
||||
import type { Component } from "svelte";
|
||||
|
||||
let {
|
||||
class: className = "",
|
||||
Icon,
|
||||
headline,
|
||||
description
|
||||
}: { class?: string; Icon: Component; headline: string; description: string } = $props();
|
||||
let {
|
||||
class: className = "",
|
||||
Icon,
|
||||
headline,
|
||||
description,
|
||||
}: { class?: string; Icon: Component; headline: string; description: string } = $props();
|
||||
</script>
|
||||
|
||||
<div class={cn("my-10 flex grow flex-col justify-center gap-2 text-center", className)}>
|
||||
<Icon class="mx-auto mb-3 size-20" strokeWidth={1} />
|
||||
<Headline level="h1" style="h2">{headline}</Headline>
|
||||
<Text style="lg" class="text-medium">{description}</Text>
|
||||
<Icon class="mx-auto mb-3 size-20" strokeWidth={1} />
|
||||
<Headline level="h1" style="h2">{headline}</Headline>
|
||||
<Text style="lg" class="text-medium">{description}</Text>
|
||||
</div>
|
||||
|
||||
@@ -1,41 +1,41 @@
|
||||
<script lang="ts">
|
||||
import { m } from "$i18n/messages.js";
|
||||
import { getLocale, setLocale } from "$i18n/runtime.js";
|
||||
import type { ButtonSize, ButtonVariant } from "$lib/components/ui/button";
|
||||
import { ComboBox } from "$lib/components/ui/combobox";
|
||||
import { cn } from "$lib/utils";
|
||||
import { m } from "$i18n/messages.js";
|
||||
import { getLocale, setLocale } from "$i18n/runtime.js";
|
||||
import type { ButtonSize, ButtonVariant } from "$lib/components/ui/button";
|
||||
import { ComboBox } from "$lib/components/ui/combobox";
|
||||
import { cn } from "$lib/utils";
|
||||
|
||||
let {
|
||||
class: className = "",
|
||||
triggerClass = "",
|
||||
triggerSize = "default",
|
||||
triggerVariant = "ghost"
|
||||
}: {
|
||||
triggerVariant?: ButtonVariant;
|
||||
triggerSize?: ButtonSize;
|
||||
class?: string;
|
||||
triggerClass?: string;
|
||||
} = $props();
|
||||
let {
|
||||
class: className = "",
|
||||
triggerClass = "",
|
||||
triggerSize = "default",
|
||||
triggerVariant = "ghost",
|
||||
}: {
|
||||
triggerVariant?: ButtonVariant;
|
||||
triggerSize?: ButtonSize;
|
||||
class?: string;
|
||||
triggerClass?: string;
|
||||
} = $props();
|
||||
</script>
|
||||
|
||||
<div class={className}>
|
||||
<ComboBox
|
||||
options={[
|
||||
{ label: "Deutsch", value: "de", keywords: ["german", "deutsch"] },
|
||||
{ label: "English", value: "en", keywords: ["english"] }
|
||||
]}
|
||||
value={getLocale()}
|
||||
onChange={(value) => {
|
||||
setLocale(value as "de" | "en");
|
||||
}}
|
||||
labels={{
|
||||
placeholder: m["i18n.label"](),
|
||||
search: m["i18n.search"](),
|
||||
notFound: m["i18n.notFound"]()
|
||||
}}
|
||||
{triggerVariant}
|
||||
{triggerSize}
|
||||
{triggerClass}
|
||||
class={cn("w-auto")}
|
||||
/>
|
||||
<ComboBox
|
||||
options={[
|
||||
{ label: "Deutsch", value: "de", keywords: ["german", "deutsch"] },
|
||||
{ label: "English", value: "en", keywords: ["english"] },
|
||||
]}
|
||||
value={getLocale()}
|
||||
onChange={(value) => {
|
||||
setLocale(value as "de" | "en");
|
||||
}}
|
||||
labels={{
|
||||
placeholder: m["i18n.label"](),
|
||||
search: m["i18n.search"](),
|
||||
notFound: m["i18n.notFound"](),
|
||||
}}
|
||||
{triggerVariant}
|
||||
{triggerSize}
|
||||
{triggerClass}
|
||||
class={cn("w-auto")}
|
||||
/>
|
||||
</div>
|
||||
|
||||
@@ -1,17 +1,17 @@
|
||||
<script lang="ts">
|
||||
import { Avatar as AvatarPrimitive } from "bits-ui";
|
||||
import { cn } from "$lib/utils.js";
|
||||
import { Avatar as AvatarPrimitive } from "bits-ui";
|
||||
import { cn } from "$lib/utils.js";
|
||||
|
||||
let {
|
||||
ref = $bindable(null),
|
||||
class: className,
|
||||
...restProps
|
||||
}: AvatarPrimitive.FallbackProps = $props();
|
||||
let {
|
||||
ref = $bindable(null),
|
||||
class: className,
|
||||
...restProps
|
||||
}: AvatarPrimitive.FallbackProps = $props();
|
||||
</script>
|
||||
|
||||
<AvatarPrimitive.Fallback
|
||||
bind:ref
|
||||
data-slot="avatar-fallback"
|
||||
class={cn("bg-muted flex size-full items-center justify-center rounded-full", className)}
|
||||
{...restProps}
|
||||
bind:ref
|
||||
data-slot="avatar-fallback"
|
||||
class={cn("bg-muted flex size-full items-center justify-center rounded-full", className)}
|
||||
{...restProps}
|
||||
/>
|
||||
|
||||
@@ -1,17 +1,17 @@
|
||||
<script lang="ts">
|
||||
import { Avatar as AvatarPrimitive } from "bits-ui";
|
||||
import { cn } from "$lib/utils.js";
|
||||
import { Avatar as AvatarPrimitive } from "bits-ui";
|
||||
import { cn } from "$lib/utils.js";
|
||||
|
||||
let {
|
||||
ref = $bindable(null),
|
||||
class: className,
|
||||
...restProps
|
||||
}: AvatarPrimitive.ImageProps = $props();
|
||||
let {
|
||||
ref = $bindable(null),
|
||||
class: className,
|
||||
...restProps
|
||||
}: AvatarPrimitive.ImageProps = $props();
|
||||
</script>
|
||||
|
||||
<AvatarPrimitive.Image
|
||||
bind:ref
|
||||
data-slot="avatar-image"
|
||||
class={cn("aspect-square size-full", className)}
|
||||
{...restProps}
|
||||
bind:ref
|
||||
data-slot="avatar-image"
|
||||
class={cn("aspect-square size-full", className)}
|
||||
{...restProps}
|
||||
/>
|
||||
|
||||
@@ -1,19 +1,19 @@
|
||||
<script lang="ts">
|
||||
import { Avatar as AvatarPrimitive } from "bits-ui";
|
||||
import { cn } from "$lib/utils.js";
|
||||
import { Avatar as AvatarPrimitive } from "bits-ui";
|
||||
import { cn } from "$lib/utils.js";
|
||||
|
||||
let {
|
||||
ref = $bindable(null),
|
||||
loadingStatus = $bindable("loading"),
|
||||
class: className,
|
||||
...restProps
|
||||
}: AvatarPrimitive.RootProps = $props();
|
||||
let {
|
||||
ref = $bindable(null),
|
||||
loadingStatus = $bindable("loading"),
|
||||
class: className,
|
||||
...restProps
|
||||
}: AvatarPrimitive.RootProps = $props();
|
||||
</script>
|
||||
|
||||
<AvatarPrimitive.Root
|
||||
bind:ref
|
||||
bind:loadingStatus
|
||||
data-slot="avatar"
|
||||
class={cn("relative flex size-8 shrink-0 overflow-hidden rounded-full", className)}
|
||||
{...restProps}
|
||||
bind:ref
|
||||
bind:loadingStatus
|
||||
data-slot="avatar"
|
||||
class={cn("relative flex size-8 shrink-0 overflow-hidden rounded-full", className)}
|
||||
{...restProps}
|
||||
/>
|
||||
|
||||
@@ -3,11 +3,11 @@ 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
|
||||
Root,
|
||||
Image,
|
||||
Fallback,
|
||||
//
|
||||
Root as Avatar,
|
||||
Image as AvatarImage,
|
||||
Fallback as AvatarFallback,
|
||||
};
|
||||
|
||||
@@ -1,23 +1,23 @@
|
||||
<script lang="ts">
|
||||
import EllipsisIcon from "@lucide/svelte/icons/ellipsis";
|
||||
import type { HTMLAttributes } from "svelte/elements";
|
||||
import { cn, type WithElementRef, type WithoutChildren } from "$lib/utils.js";
|
||||
import EllipsisIcon from "@lucide/svelte/icons/ellipsis";
|
||||
import type { HTMLAttributes } from "svelte/elements";
|
||||
import { cn, type WithElementRef, type WithoutChildren } from "$lib/utils.js";
|
||||
|
||||
let {
|
||||
ref = $bindable(null),
|
||||
class: className,
|
||||
...restProps
|
||||
}: WithoutChildren<WithElementRef<HTMLAttributes<HTMLSpanElement>>> = $props();
|
||||
let {
|
||||
ref = $bindable(null),
|
||||
class: className,
|
||||
...restProps
|
||||
}: WithoutChildren<WithElementRef<HTMLAttributes<HTMLSpanElement>>> = $props();
|
||||
</script>
|
||||
|
||||
<span
|
||||
bind:this={ref}
|
||||
data-slot="breadcrumb-ellipsis"
|
||||
role="presentation"
|
||||
aria-hidden="true"
|
||||
class={cn("flex size-9 items-center justify-center", className)}
|
||||
{...restProps}
|
||||
bind:this={ref}
|
||||
data-slot="breadcrumb-ellipsis"
|
||||
role="presentation"
|
||||
aria-hidden="true"
|
||||
class={cn("flex size-9 items-center justify-center", className)}
|
||||
{...restProps}
|
||||
>
|
||||
<EllipsisIcon class="size-4" />
|
||||
<span class="sr-only">More</span>
|
||||
<EllipsisIcon class="size-4" />
|
||||
<span class="sr-only">More</span>
|
||||
</span>
|
||||
|
||||
@@ -1,20 +1,20 @@
|
||||
<script lang="ts">
|
||||
import type { HTMLLiAttributes } from "svelte/elements";
|
||||
import { cn, type WithElementRef } from "$lib/utils.js";
|
||||
import type { HTMLLiAttributes } from "svelte/elements";
|
||||
import { cn, type WithElementRef } from "$lib/utils.js";
|
||||
|
||||
let {
|
||||
ref = $bindable(null),
|
||||
class: className,
|
||||
children,
|
||||
...restProps
|
||||
}: WithElementRef<HTMLLiAttributes> = $props();
|
||||
let {
|
||||
ref = $bindable(null),
|
||||
class: className,
|
||||
children,
|
||||
...restProps
|
||||
}: WithElementRef<HTMLLiAttributes> = $props();
|
||||
</script>
|
||||
|
||||
<li
|
||||
bind:this={ref}
|
||||
data-slot="breadcrumb-item"
|
||||
class={cn("inline-flex items-center gap-1.5", className)}
|
||||
{...restProps}
|
||||
bind:this={ref}
|
||||
data-slot="breadcrumb-item"
|
||||
class={cn("inline-flex items-center gap-1.5", className)}
|
||||
{...restProps}
|
||||
>
|
||||
{@render children?.()}
|
||||
{@render children?.()}
|
||||
</li>
|
||||
|
||||
@@ -1,31 +1,31 @@
|
||||
<script lang="ts">
|
||||
import type { HTMLAnchorAttributes } from "svelte/elements";
|
||||
import type { Snippet } from "svelte";
|
||||
import { cn, type WithElementRef } from "$lib/utils.js";
|
||||
import type { HTMLAnchorAttributes } from "svelte/elements";
|
||||
import type { Snippet } from "svelte";
|
||||
import { cn, type WithElementRef } from "$lib/utils.js";
|
||||
|
||||
let {
|
||||
ref = $bindable(null),
|
||||
class: className,
|
||||
href = undefined,
|
||||
child,
|
||||
children,
|
||||
...restProps
|
||||
}: WithElementRef<HTMLAnchorAttributes> & {
|
||||
child?: Snippet<[{ props: HTMLAnchorAttributes }]>;
|
||||
} = $props();
|
||||
let {
|
||||
ref = $bindable(null),
|
||||
class: className,
|
||||
href = undefined,
|
||||
child,
|
||||
children,
|
||||
...restProps
|
||||
}: WithElementRef<HTMLAnchorAttributes> & {
|
||||
child?: Snippet<[{ props: HTMLAnchorAttributes }]>;
|
||||
} = $props();
|
||||
|
||||
const attrs = $derived({
|
||||
"data-slot": "breadcrumb-link",
|
||||
class: cn("hover:text-foreground transition-colors", className),
|
||||
href,
|
||||
...restProps
|
||||
});
|
||||
const attrs = $derived({
|
||||
"data-slot": "breadcrumb-link",
|
||||
class: cn("hover:text-foreground transition-colors", className),
|
||||
href,
|
||||
...restProps,
|
||||
});
|
||||
</script>
|
||||
|
||||
{#if child}
|
||||
{@render child({ props: attrs })}
|
||||
{@render child({ props: attrs })}
|
||||
{:else}
|
||||
<a bind:this={ref} {...attrs}>
|
||||
{@render children?.()}
|
||||
</a>
|
||||
<a bind:this={ref} {...attrs}>
|
||||
{@render children?.()}
|
||||
</a>
|
||||
{/if}
|
||||
|
||||
@@ -1,23 +1,23 @@
|
||||
<script lang="ts">
|
||||
import type { HTMLOlAttributes } from "svelte/elements";
|
||||
import { cn, type WithElementRef } from "$lib/utils.js";
|
||||
import type { HTMLOlAttributes } from "svelte/elements";
|
||||
import { cn, type WithElementRef } from "$lib/utils.js";
|
||||
|
||||
let {
|
||||
ref = $bindable(null),
|
||||
class: className,
|
||||
children,
|
||||
...restProps
|
||||
}: WithElementRef<HTMLOlAttributes> = $props();
|
||||
let {
|
||||
ref = $bindable(null),
|
||||
class: className,
|
||||
children,
|
||||
...restProps
|
||||
}: WithElementRef<HTMLOlAttributes> = $props();
|
||||
</script>
|
||||
|
||||
<ol
|
||||
bind:this={ref}
|
||||
data-slot="breadcrumb-list"
|
||||
class={cn(
|
||||
"text-muted-foreground flex flex-wrap items-center gap-1.5 text-sm break-words sm:gap-2.5",
|
||||
className
|
||||
)}
|
||||
{...restProps}
|
||||
bind:this={ref}
|
||||
data-slot="breadcrumb-list"
|
||||
class={cn(
|
||||
"text-muted-foreground flex flex-wrap items-center gap-1.5 text-sm break-words sm:gap-2.5",
|
||||
className,
|
||||
)}
|
||||
{...restProps}
|
||||
>
|
||||
{@render children?.()}
|
||||
{@render children?.()}
|
||||
</ol>
|
||||
|
||||
@@ -1,23 +1,23 @@
|
||||
<script lang="ts">
|
||||
import type { HTMLAttributes } from "svelte/elements";
|
||||
import { cn, type WithElementRef } from "$lib/utils.js";
|
||||
import type { HTMLAttributes } from "svelte/elements";
|
||||
import { cn, type WithElementRef } from "$lib/utils.js";
|
||||
|
||||
let {
|
||||
ref = $bindable(null),
|
||||
class: className,
|
||||
children,
|
||||
...restProps
|
||||
}: WithElementRef<HTMLAttributes<HTMLSpanElement>> = $props();
|
||||
let {
|
||||
ref = $bindable(null),
|
||||
class: className,
|
||||
children,
|
||||
...restProps
|
||||
}: WithElementRef<HTMLAttributes<HTMLSpanElement>> = $props();
|
||||
</script>
|
||||
|
||||
<span
|
||||
bind:this={ref}
|
||||
data-slot="breadcrumb-page"
|
||||
role="link"
|
||||
aria-disabled="true"
|
||||
aria-current="page"
|
||||
class={cn("text-foreground font-normal", className)}
|
||||
{...restProps}
|
||||
bind:this={ref}
|
||||
data-slot="breadcrumb-page"
|
||||
role="link"
|
||||
aria-disabled="true"
|
||||
aria-current="page"
|
||||
class={cn("text-foreground font-normal", className)}
|
||||
{...restProps}
|
||||
>
|
||||
{@render children?.()}
|
||||
{@render children?.()}
|
||||
</span>
|
||||
|
||||
@@ -1,27 +1,27 @@
|
||||
<script lang="ts">
|
||||
import ChevronRightIcon from "@lucide/svelte/icons/chevron-right";
|
||||
import { cn, type WithElementRef } from "$lib/utils.js";
|
||||
import type { HTMLLiAttributes } from "svelte/elements";
|
||||
import ChevronRightIcon from "@lucide/svelte/icons/chevron-right";
|
||||
import { cn, type WithElementRef } from "$lib/utils.js";
|
||||
import type { HTMLLiAttributes } from "svelte/elements";
|
||||
|
||||
let {
|
||||
ref = $bindable(null),
|
||||
class: className,
|
||||
children,
|
||||
...restProps
|
||||
}: WithElementRef<HTMLLiAttributes> = $props();
|
||||
let {
|
||||
ref = $bindable(null),
|
||||
class: className,
|
||||
children,
|
||||
...restProps
|
||||
}: WithElementRef<HTMLLiAttributes> = $props();
|
||||
</script>
|
||||
|
||||
<li
|
||||
bind:this={ref}
|
||||
data-slot="breadcrumb-separator"
|
||||
role="presentation"
|
||||
aria-hidden="true"
|
||||
class={cn("[&>svg]:size-3.5", className)}
|
||||
{...restProps}
|
||||
bind:this={ref}
|
||||
data-slot="breadcrumb-separator"
|
||||
role="presentation"
|
||||
aria-hidden="true"
|
||||
class={cn("[&>svg]:size-3.5", className)}
|
||||
{...restProps}
|
||||
>
|
||||
{#if children}
|
||||
{@render children?.()}
|
||||
{:else}
|
||||
<ChevronRightIcon />
|
||||
{/if}
|
||||
{#if children}
|
||||
{@render children?.()}
|
||||
{:else}
|
||||
<ChevronRightIcon />
|
||||
{/if}
|
||||
</li>
|
||||
|
||||
@@ -1,21 +1,21 @@
|
||||
<script lang="ts">
|
||||
import type { WithElementRef } from "$lib/utils.js";
|
||||
import type { HTMLAttributes } from "svelte/elements";
|
||||
import type { WithElementRef } from "$lib/utils.js";
|
||||
import type { HTMLAttributes } from "svelte/elements";
|
||||
|
||||
let {
|
||||
ref = $bindable(null),
|
||||
class: className,
|
||||
children,
|
||||
...restProps
|
||||
}: WithElementRef<HTMLAttributes<HTMLElement>> = $props();
|
||||
let {
|
||||
ref = $bindable(null),
|
||||
class: className,
|
||||
children,
|
||||
...restProps
|
||||
}: WithElementRef<HTMLAttributes<HTMLElement>> = $props();
|
||||
</script>
|
||||
|
||||
<nav
|
||||
bind:this={ref}
|
||||
data-slot="breadcrumb"
|
||||
class={className}
|
||||
aria-label="breadcrumb"
|
||||
{...restProps}
|
||||
bind:this={ref}
|
||||
data-slot="breadcrumb"
|
||||
class={className}
|
||||
aria-label="breadcrumb"
|
||||
{...restProps}
|
||||
>
|
||||
{@render children?.()}
|
||||
{@render children?.()}
|
||||
</nav>
|
||||
|
||||
@@ -7,19 +7,19 @@ 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
|
||||
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,
|
||||
};
|
||||
|
||||
@@ -1,87 +1,87 @@
|
||||
<script lang="ts" module>
|
||||
import { cn, type WithElementRef } from "$lib/utils.js";
|
||||
import Loader2Icon from "@lucide/svelte/icons/loader-2";
|
||||
import type { HTMLAnchorAttributes, HTMLButtonAttributes } from "svelte/elements";
|
||||
import { type VariantProps, tv } from "tailwind-variants";
|
||||
import { cn, type WithElementRef } from "$lib/utils.js";
|
||||
import Loader2Icon from "@lucide/svelte/icons/loader-2";
|
||||
import type { HTMLAnchorAttributes, HTMLButtonAttributes } from "svelte/elements";
|
||||
import { type VariantProps, tv } from "tailwind-variants";
|
||||
|
||||
export const buttonVariants = tv({
|
||||
base: "focus-visible:border-ring focus-visible:ring-ring/50 aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive inline-flex shrink-0 items-center justify-center gap-2 whitespace-nowrap rounded-md font-medium outline-none transition-all focus-visible:ring-[3px] disabled:pointer-events-none disabled:opacity-50 aria-disabled:pointer-events-none aria-disabled:opacity-50 [&_svg:not([class*='size-'])]:size-4 [&_svg]:pointer-events-none [&_svg]:shrink-0 select-none",
|
||||
variants: {
|
||||
variant: {
|
||||
default: "bg-primary text-primary-foreground shadow-xs hover:bg-primary/90",
|
||||
destructive:
|
||||
"bg-destructive shadow-xs hover:bg-destructive/90 focus-visible:ring-destructive/20 dark:focus-visible:ring-destructive/40 dark:bg-destructive/60 text-white",
|
||||
outline:
|
||||
"bg-background shadow-xs hover:bg-accent hover:text-accent-foreground dark:bg-input/30 dark:border-input dark:hover:bg-input/50 border",
|
||||
secondary: "bg-secondary text-secondary-foreground shadow-xs hover:bg-secondary/80",
|
||||
ghost: "hover:bg-accent hover:text-accent-foreground dark:hover:bg-accent/50",
|
||||
link: "text-primary underline-offset-1 !p-0 !ml-0.5 !mr-0 underline cursor-pointer"
|
||||
},
|
||||
size: {
|
||||
default: "h-9 px-4 py-2 has-[>svg]:px-3 text-sm",
|
||||
xs: "gap-1.5 rounded-xs px-1 has-[>svg]:px-2.5 text-xs",
|
||||
sm: "h-8 gap-1.5 rounded-md px-3 has-[>svg]:px-2.5 text-sm",
|
||||
lg: "h-10 rounded-md px-6 has-[>svg]:px-4",
|
||||
icon: "size-9"
|
||||
}
|
||||
},
|
||||
defaultVariants: {
|
||||
variant: "default",
|
||||
size: "default"
|
||||
}
|
||||
});
|
||||
export const buttonVariants = tv({
|
||||
base: "focus-visible:border-ring focus-visible:ring-ring/50 aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive inline-flex shrink-0 items-center justify-center gap-2 whitespace-nowrap rounded-md font-medium outline-none transition-all focus-visible:ring-[3px] disabled:pointer-events-none disabled:opacity-50 aria-disabled:pointer-events-none aria-disabled:opacity-50 [&_svg:not([class*='size-'])]:size-4 [&_svg]:pointer-events-none [&_svg]:shrink-0 select-none",
|
||||
variants: {
|
||||
variant: {
|
||||
default: "bg-primary text-primary-foreground shadow-xs hover:bg-primary/90",
|
||||
destructive:
|
||||
"bg-destructive shadow-xs hover:bg-destructive/90 focus-visible:ring-destructive/20 dark:focus-visible:ring-destructive/40 dark:bg-destructive/60 text-white",
|
||||
outline:
|
||||
"bg-background shadow-xs hover:bg-accent hover:text-accent-foreground dark:bg-input/30 dark:border-input dark:hover:bg-input/50 border",
|
||||
secondary: "bg-secondary text-secondary-foreground shadow-xs hover:bg-secondary/80",
|
||||
ghost: "hover:bg-accent hover:text-accent-foreground dark:hover:bg-accent/50",
|
||||
link: "text-primary underline-offset-1 !p-0 !ml-0.5 !mr-0 underline cursor-pointer",
|
||||
},
|
||||
size: {
|
||||
default: "h-9 px-4 py-2 has-[>svg]:px-3 text-sm",
|
||||
xs: "gap-1.5 rounded-xs px-1 has-[>svg]:px-2.5 text-xs",
|
||||
sm: "h-8 gap-1.5 rounded-md px-3 has-[>svg]:px-2.5 text-sm",
|
||||
lg: "h-10 rounded-md px-6 has-[>svg]:px-4",
|
||||
icon: "size-9",
|
||||
},
|
||||
},
|
||||
defaultVariants: {
|
||||
variant: "default",
|
||||
size: "default",
|
||||
},
|
||||
});
|
||||
|
||||
export type ButtonVariant = VariantProps<typeof buttonVariants>["variant"];
|
||||
export type ButtonSize = VariantProps<typeof buttonVariants>["size"];
|
||||
export type ButtonVariant = VariantProps<typeof buttonVariants>["variant"];
|
||||
export type ButtonSize = VariantProps<typeof buttonVariants>["size"];
|
||||
|
||||
export type ButtonProps = WithElementRef<HTMLButtonAttributes> &
|
||||
WithElementRef<HTMLAnchorAttributes> & {
|
||||
variant?: ButtonVariant;
|
||||
size?: ButtonSize;
|
||||
isLoading?: boolean;
|
||||
};
|
||||
export type ButtonProps = WithElementRef<HTMLButtonAttributes> &
|
||||
WithElementRef<HTMLAnchorAttributes> & {
|
||||
variant?: ButtonVariant;
|
||||
size?: ButtonSize;
|
||||
isLoading?: boolean;
|
||||
};
|
||||
</script>
|
||||
|
||||
<script lang="ts">
|
||||
let {
|
||||
class: className,
|
||||
variant = "default",
|
||||
size = "default",
|
||||
ref = $bindable(null),
|
||||
href = undefined,
|
||||
type = "button",
|
||||
disabled,
|
||||
children,
|
||||
isLoading,
|
||||
...restProps
|
||||
}: ButtonProps = $props();
|
||||
let {
|
||||
class: className,
|
||||
variant = "default",
|
||||
size = "default",
|
||||
ref = $bindable(null),
|
||||
href = undefined,
|
||||
type = "button",
|
||||
disabled,
|
||||
children,
|
||||
isLoading,
|
||||
...restProps
|
||||
}: ButtonProps = $props();
|
||||
</script>
|
||||
|
||||
{#if href}
|
||||
<a
|
||||
bind:this={ref}
|
||||
data-slot="button"
|
||||
class={cn(buttonVariants({ variant, size }), className)}
|
||||
href={disabled ? undefined : href}
|
||||
aria-disabled={disabled}
|
||||
role={disabled ? "link" : undefined}
|
||||
tabindex={disabled ? -1 : undefined}
|
||||
{...restProps}
|
||||
>
|
||||
{@render children?.()}
|
||||
</a>
|
||||
<a
|
||||
bind:this={ref}
|
||||
data-slot="button"
|
||||
class={cn(buttonVariants({ variant, size }), className)}
|
||||
href={disabled ? undefined : href}
|
||||
aria-disabled={disabled}
|
||||
role={disabled ? "link" : undefined}
|
||||
tabindex={disabled ? -1 : undefined}
|
||||
{...restProps}
|
||||
>
|
||||
{@render children?.()}
|
||||
</a>
|
||||
{:else}
|
||||
<button
|
||||
bind:this={ref}
|
||||
data-slot="button"
|
||||
class={cn(buttonVariants({ variant, size }), className)}
|
||||
{type}
|
||||
{disabled}
|
||||
{...restProps}
|
||||
>
|
||||
{#if isLoading}
|
||||
<Loader2Icon class="animate-spin" />
|
||||
{/if}
|
||||
{@render children?.()}
|
||||
</button>
|
||||
<button
|
||||
bind:this={ref}
|
||||
data-slot="button"
|
||||
class={cn(buttonVariants({ variant, size }), className)}
|
||||
{type}
|
||||
{disabled}
|
||||
{...restProps}
|
||||
>
|
||||
{#if isLoading}
|
||||
<Loader2Icon class="animate-spin" />
|
||||
{/if}
|
||||
{@render children?.()}
|
||||
</button>
|
||||
{/if}
|
||||
|
||||
@@ -1,17 +1,17 @@
|
||||
import Root, {
|
||||
type ButtonProps,
|
||||
type ButtonSize,
|
||||
type ButtonVariant,
|
||||
buttonVariants
|
||||
type ButtonProps,
|
||||
type ButtonSize,
|
||||
type ButtonVariant,
|
||||
buttonVariants,
|
||||
} from "./button.svelte";
|
||||
|
||||
export {
|
||||
Root,
|
||||
type ButtonProps as Props,
|
||||
//
|
||||
Root as Button,
|
||||
buttonVariants,
|
||||
type ButtonProps,
|
||||
type ButtonSize,
|
||||
type ButtonVariant
|
||||
Root,
|
||||
type ButtonProps as Props,
|
||||
//
|
||||
Root as Button,
|
||||
buttonVariants,
|
||||
type ButtonProps,
|
||||
type ButtonSize,
|
||||
type ButtonVariant,
|
||||
};
|
||||
|
||||
@@ -1,20 +1,20 @@
|
||||
<script lang="ts">
|
||||
import { cn, type WithElementRef } from "$lib/utils.js";
|
||||
import type { HTMLAttributes } from "svelte/elements";
|
||||
import { cn, type WithElementRef } from "$lib/utils.js";
|
||||
import type { HTMLAttributes } from "svelte/elements";
|
||||
|
||||
let {
|
||||
ref = $bindable(null),
|
||||
class: className,
|
||||
children,
|
||||
...restProps
|
||||
}: WithElementRef<HTMLAttributes<HTMLDivElement>> = $props();
|
||||
let {
|
||||
ref = $bindable(null),
|
||||
class: className,
|
||||
children,
|
||||
...restProps
|
||||
}: WithElementRef<HTMLAttributes<HTMLDivElement>> = $props();
|
||||
</script>
|
||||
|
||||
<div
|
||||
bind:this={ref}
|
||||
data-slot="card-action"
|
||||
class={cn("col-start-2 row-span-2 row-start-1 self-start justify-self-end", className)}
|
||||
{...restProps}
|
||||
bind:this={ref}
|
||||
data-slot="card-action"
|
||||
class={cn("col-start-2 row-span-2 row-start-1 self-start justify-self-end", className)}
|
||||
{...restProps}
|
||||
>
|
||||
{@render children?.()}
|
||||
{@render children?.()}
|
||||
</div>
|
||||
|
||||
@@ -1,15 +1,15 @@
|
||||
<script lang="ts">
|
||||
import type { HTMLAttributes } from "svelte/elements";
|
||||
import { cn, type WithElementRef } from "$lib/utils.js";
|
||||
import type { HTMLAttributes } from "svelte/elements";
|
||||
import { cn, type WithElementRef } from "$lib/utils.js";
|
||||
|
||||
let {
|
||||
ref = $bindable(null),
|
||||
class: className,
|
||||
children,
|
||||
...restProps
|
||||
}: WithElementRef<HTMLAttributes<HTMLDivElement>> = $props();
|
||||
let {
|
||||
ref = $bindable(null),
|
||||
class: className,
|
||||
children,
|
||||
...restProps
|
||||
}: WithElementRef<HTMLAttributes<HTMLDivElement>> = $props();
|
||||
</script>
|
||||
|
||||
<div bind:this={ref} data-slot="card-content" class={cn("px-6", className)} {...restProps}>
|
||||
{@render children?.()}
|
||||
{@render children?.()}
|
||||
</div>
|
||||
|
||||
@@ -1,20 +1,20 @@
|
||||
<script lang="ts">
|
||||
import type { HTMLAttributes } from "svelte/elements";
|
||||
import { cn, type WithElementRef } from "$lib/utils.js";
|
||||
import type { HTMLAttributes } from "svelte/elements";
|
||||
import { cn, type WithElementRef } from "$lib/utils.js";
|
||||
|
||||
let {
|
||||
ref = $bindable(null),
|
||||
class: className,
|
||||
children,
|
||||
...restProps
|
||||
}: WithElementRef<HTMLAttributes<HTMLParagraphElement>> = $props();
|
||||
let {
|
||||
ref = $bindable(null),
|
||||
class: className,
|
||||
children,
|
||||
...restProps
|
||||
}: WithElementRef<HTMLAttributes<HTMLParagraphElement>> = $props();
|
||||
</script>
|
||||
|
||||
<p
|
||||
bind:this={ref}
|
||||
data-slot="card-description"
|
||||
class={cn("text-muted-foreground text-sm", className)}
|
||||
{...restProps}
|
||||
bind:this={ref}
|
||||
data-slot="card-description"
|
||||
class={cn("text-muted-foreground text-sm", className)}
|
||||
{...restProps}
|
||||
>
|
||||
{@render children?.()}
|
||||
{@render children?.()}
|
||||
</p>
|
||||
|
||||
@@ -1,20 +1,20 @@
|
||||
<script lang="ts">
|
||||
import { cn, type WithElementRef } from "$lib/utils.js";
|
||||
import type { HTMLAttributes } from "svelte/elements";
|
||||
import { cn, type WithElementRef } from "$lib/utils.js";
|
||||
import type { HTMLAttributes } from "svelte/elements";
|
||||
|
||||
let {
|
||||
ref = $bindable(null),
|
||||
class: className,
|
||||
children,
|
||||
...restProps
|
||||
}: WithElementRef<HTMLAttributes<HTMLDivElement>> = $props();
|
||||
let {
|
||||
ref = $bindable(null),
|
||||
class: className,
|
||||
children,
|
||||
...restProps
|
||||
}: WithElementRef<HTMLAttributes<HTMLDivElement>> = $props();
|
||||
</script>
|
||||
|
||||
<div
|
||||
bind:this={ref}
|
||||
data-slot="card-footer"
|
||||
class={cn("flex items-center px-6 [.border-t]:pt-6", className)}
|
||||
{...restProps}
|
||||
bind:this={ref}
|
||||
data-slot="card-footer"
|
||||
class={cn("flex items-center px-6 [.border-t]:pt-6", className)}
|
||||
{...restProps}
|
||||
>
|
||||
{@render children?.()}
|
||||
{@render children?.()}
|
||||
</div>
|
||||
|
||||
@@ -1,23 +1,23 @@
|
||||
<script lang="ts">
|
||||
import { cn, type WithElementRef } from "$lib/utils.js";
|
||||
import type { HTMLAttributes } from "svelte/elements";
|
||||
import { cn, type WithElementRef } from "$lib/utils.js";
|
||||
import type { HTMLAttributes } from "svelte/elements";
|
||||
|
||||
let {
|
||||
ref = $bindable(null),
|
||||
class: className,
|
||||
children,
|
||||
...restProps
|
||||
}: WithElementRef<HTMLAttributes<HTMLDivElement>> = $props();
|
||||
let {
|
||||
ref = $bindable(null),
|
||||
class: className,
|
||||
children,
|
||||
...restProps
|
||||
}: WithElementRef<HTMLAttributes<HTMLDivElement>> = $props();
|
||||
</script>
|
||||
|
||||
<div
|
||||
bind:this={ref}
|
||||
data-slot="card-header"
|
||||
class={cn(
|
||||
"@container/card-header grid auto-rows-min grid-rows-[auto_auto] items-start gap-1.5 px-6 has-data-[slot=card-action]:grid-cols-[1fr_auto] [.border-b]:pb-6",
|
||||
className
|
||||
)}
|
||||
{...restProps}
|
||||
bind:this={ref}
|
||||
data-slot="card-header"
|
||||
class={cn(
|
||||
"@container/card-header grid auto-rows-min grid-rows-[auto_auto] items-start gap-1.5 px-6 has-data-[slot=card-action]:grid-cols-[1fr_auto] [.border-b]:pb-6",
|
||||
className,
|
||||
)}
|
||||
{...restProps}
|
||||
>
|
||||
{@render children?.()}
|
||||
{@render children?.()}
|
||||
</div>
|
||||
|
||||
@@ -1,20 +1,20 @@
|
||||
<script lang="ts">
|
||||
import type { HTMLAttributes } from "svelte/elements";
|
||||
import { cn, type WithElementRef } from "$lib/utils.js";
|
||||
import type { HTMLAttributes } from "svelte/elements";
|
||||
import { cn, type WithElementRef } from "$lib/utils.js";
|
||||
|
||||
let {
|
||||
ref = $bindable(null),
|
||||
class: className,
|
||||
children,
|
||||
...restProps
|
||||
}: WithElementRef<HTMLAttributes<HTMLDivElement>> = $props();
|
||||
let {
|
||||
ref = $bindable(null),
|
||||
class: className,
|
||||
children,
|
||||
...restProps
|
||||
}: WithElementRef<HTMLAttributes<HTMLDivElement>> = $props();
|
||||
</script>
|
||||
|
||||
<div
|
||||
bind:this={ref}
|
||||
data-slot="card-title"
|
||||
class={cn("leading-none font-semibold", className)}
|
||||
{...restProps}
|
||||
bind:this={ref}
|
||||
data-slot="card-title"
|
||||
class={cn("leading-none font-semibold", className)}
|
||||
{...restProps}
|
||||
>
|
||||
{@render children?.()}
|
||||
{@render children?.()}
|
||||
</div>
|
||||
|
||||
@@ -1,23 +1,23 @@
|
||||
<script lang="ts">
|
||||
import type { HTMLAttributes } from "svelte/elements";
|
||||
import { cn, type WithElementRef } from "$lib/utils.js";
|
||||
import type { HTMLAttributes } from "svelte/elements";
|
||||
import { cn, type WithElementRef } from "$lib/utils.js";
|
||||
|
||||
let {
|
||||
ref = $bindable(null),
|
||||
class: className,
|
||||
children,
|
||||
...restProps
|
||||
}: WithElementRef<HTMLAttributes<HTMLDivElement>> = $props();
|
||||
let {
|
||||
ref = $bindable(null),
|
||||
class: className,
|
||||
children,
|
||||
...restProps
|
||||
}: WithElementRef<HTMLAttributes<HTMLDivElement>> = $props();
|
||||
</script>
|
||||
|
||||
<div
|
||||
bind:this={ref}
|
||||
data-slot="card"
|
||||
class={cn(
|
||||
"bg-card text-card-foreground flex flex-col gap-6 rounded-xl border p-4 shadow-sm",
|
||||
className
|
||||
)}
|
||||
{...restProps}
|
||||
bind:this={ref}
|
||||
data-slot="card"
|
||||
class={cn(
|
||||
"bg-card text-card-foreground flex flex-col gap-6 rounded-xl border p-4 shadow-sm",
|
||||
className,
|
||||
)}
|
||||
{...restProps}
|
||||
>
|
||||
{@render children?.()}
|
||||
{@render children?.()}
|
||||
</div>
|
||||
|
||||
@@ -7,19 +7,19 @@ import Title from "./card-title.svelte";
|
||||
import Action from "./card-action.svelte";
|
||||
|
||||
export {
|
||||
Root,
|
||||
Content,
|
||||
Description,
|
||||
Footer,
|
||||
Header,
|
||||
Title,
|
||||
Action,
|
||||
//
|
||||
Root as Card,
|
||||
Content as CardContent,
|
||||
Description as CardDescription,
|
||||
Footer as CardFooter,
|
||||
Header as CardHeader,
|
||||
Title as CardTitle,
|
||||
Action as CardAction
|
||||
Root,
|
||||
Content,
|
||||
Description,
|
||||
Footer,
|
||||
Header,
|
||||
Title,
|
||||
Action,
|
||||
//
|
||||
Root as Card,
|
||||
Content as CardContent,
|
||||
Description as CardDescription,
|
||||
Footer as CardFooter,
|
||||
Header as CardHeader,
|
||||
Title as CardTitle,
|
||||
Action as CardAction,
|
||||
};
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
<script lang="ts">
|
||||
import { Collapsible as CollapsiblePrimitive } from "bits-ui";
|
||||
import { Collapsible as CollapsiblePrimitive } from "bits-ui";
|
||||
|
||||
let { ref = $bindable(null), ...restProps }: CollapsiblePrimitive.ContentProps = $props();
|
||||
let { ref = $bindable(null), ...restProps }: CollapsiblePrimitive.ContentProps = $props();
|
||||
</script>
|
||||
|
||||
<CollapsiblePrimitive.Content bind:ref data-slot="collapsible-content" {...restProps} />
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
<script lang="ts">
|
||||
import { Collapsible as CollapsiblePrimitive } from "bits-ui";
|
||||
import { Collapsible as CollapsiblePrimitive } from "bits-ui";
|
||||
|
||||
let { ref = $bindable(null), ...restProps }: CollapsiblePrimitive.TriggerProps = $props();
|
||||
let { ref = $bindable(null), ...restProps }: CollapsiblePrimitive.TriggerProps = $props();
|
||||
</script>
|
||||
|
||||
<CollapsiblePrimitive.Trigger bind:ref data-slot="collapsible-trigger" {...restProps} />
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
<script lang="ts">
|
||||
import { Collapsible as CollapsiblePrimitive } from "bits-ui";
|
||||
import { Collapsible as CollapsiblePrimitive } from "bits-ui";
|
||||
|
||||
let {
|
||||
ref = $bindable(null),
|
||||
open = $bindable(false),
|
||||
...restProps
|
||||
}: CollapsiblePrimitive.RootProps = $props();
|
||||
let {
|
||||
ref = $bindable(null),
|
||||
open = $bindable(false),
|
||||
...restProps
|
||||
}: CollapsiblePrimitive.RootProps = $props();
|
||||
</script>
|
||||
|
||||
<CollapsiblePrimitive.Root bind:ref bind:open data-slot="collapsible" {...restProps} />
|
||||
|
||||
@@ -3,11 +3,11 @@ import Trigger from "./collapsible-trigger.svelte";
|
||||
import Content from "./collapsible-content.svelte";
|
||||
|
||||
export {
|
||||
Root,
|
||||
Content,
|
||||
Trigger,
|
||||
//
|
||||
Root as Collapsible,
|
||||
Content as CollapsibleContent,
|
||||
Trigger as CollapsibleTrigger
|
||||
Root,
|
||||
Content,
|
||||
Trigger,
|
||||
//
|
||||
Root as Collapsible,
|
||||
Content as CollapsibleContent,
|
||||
Trigger as CollapsibleTrigger,
|
||||
};
|
||||
|
||||
@@ -1,89 +1,89 @@
|
||||
<script lang="ts">
|
||||
import CheckIcon from "@lucide/svelte/icons/check";
|
||||
import ChevronsUpDownIcon from "@lucide/svelte/icons/chevrons-up-down";
|
||||
import { tick } from "svelte";
|
||||
import * as Command from "$lib/components/ui/command/index.js";
|
||||
import * as Popover from "$lib/components/ui/popover/index.js";
|
||||
import { Button, type ButtonSize, type ButtonVariant } from "$lib/components/ui/button/index.js";
|
||||
import { cn } from "$lib/utils.js";
|
||||
import CheckIcon from "@lucide/svelte/icons/check";
|
||||
import ChevronsUpDownIcon from "@lucide/svelte/icons/chevrons-up-down";
|
||||
import { tick } from "svelte";
|
||||
import * as Command from "$lib/components/ui/command/index.js";
|
||||
import * as Popover from "$lib/components/ui/popover/index.js";
|
||||
import { Button, type ButtonSize, type ButtonVariant } from "$lib/components/ui/button/index.js";
|
||||
import { cn } from "$lib/utils.js";
|
||||
|
||||
const {
|
||||
labels,
|
||||
value,
|
||||
options = [],
|
||||
onChange,
|
||||
class: className = "",
|
||||
triggerVariant = "outline",
|
||||
triggerSize = "default",
|
||||
triggerClass = ""
|
||||
}: {
|
||||
labels: {
|
||||
placeholder: string;
|
||||
search: string;
|
||||
notFound: string;
|
||||
};
|
||||
value: string;
|
||||
onChange: (value: string) => void;
|
||||
triggerVariant?: ButtonVariant;
|
||||
triggerSize?: ButtonSize;
|
||||
triggerClass?: string;
|
||||
class?: string;
|
||||
options?: { label: string; value: string; keywords?: string[] }[];
|
||||
} = $props();
|
||||
const {
|
||||
labels,
|
||||
value,
|
||||
options = [],
|
||||
onChange,
|
||||
class: className = "",
|
||||
triggerVariant = "outline",
|
||||
triggerSize = "default",
|
||||
triggerClass = "",
|
||||
}: {
|
||||
labels: {
|
||||
placeholder: string;
|
||||
search: string;
|
||||
notFound: string;
|
||||
};
|
||||
value: string;
|
||||
onChange: (value: string) => void;
|
||||
triggerVariant?: ButtonVariant;
|
||||
triggerSize?: ButtonSize;
|
||||
triggerClass?: string;
|
||||
class?: string;
|
||||
options?: { label: string; value: string; keywords?: string[] }[];
|
||||
} = $props();
|
||||
|
||||
let open = $state(false);
|
||||
let triggerRef = $state<HTMLButtonElement>(null!);
|
||||
let open = $state(false);
|
||||
let triggerRef = $state<HTMLButtonElement>(null!);
|
||||
|
||||
const selectedValue = $derived(options.find((f) => f.value === value)?.label);
|
||||
const selectedValue = $derived(options.find((f) => f.value === value)?.label);
|
||||
|
||||
// We want to refocus the trigger button when the user selects
|
||||
// an item from the list so users can continue navigating the
|
||||
// rest of the form with the keyboard.
|
||||
function closeAndFocusTrigger() {
|
||||
open = false;
|
||||
tick().then(() => {
|
||||
triggerRef.focus();
|
||||
});
|
||||
}
|
||||
// We want to refocus the trigger button when the user selects
|
||||
// an item from the list so users can continue navigating the
|
||||
// rest of the form with the keyboard.
|
||||
function closeAndFocusTrigger() {
|
||||
open = false;
|
||||
tick().then(() => {
|
||||
triggerRef.focus();
|
||||
});
|
||||
}
|
||||
</script>
|
||||
|
||||
<Popover.Root bind:open>
|
||||
<Popover.Trigger bind:ref={triggerRef} class={triggerClass}>
|
||||
{#snippet child({ props })}
|
||||
<Button
|
||||
variant={triggerVariant}
|
||||
size={triggerSize}
|
||||
class={cn("w-[200px] justify-between", className)}
|
||||
{...props}
|
||||
role="combobox"
|
||||
aria-expanded={open}
|
||||
>
|
||||
{selectedValue || labels.placeholder}
|
||||
<ChevronsUpDownIcon class="ml-2 size-4 shrink-0 opacity-50" />
|
||||
</Button>
|
||||
{/snippet}
|
||||
</Popover.Trigger>
|
||||
<Popover.Content class="w-[200px] p-0">
|
||||
<Command.Root>
|
||||
<Command.Input autofocus placeholder={labels.search} />
|
||||
<Command.List>
|
||||
<Command.Empty>{labels.notFound}</Command.Empty>
|
||||
<Command.Group>
|
||||
{#each options as option (option.value)}
|
||||
<Command.Item
|
||||
value={option.value}
|
||||
keywords={option.keywords}
|
||||
onSelect={() => {
|
||||
onChange(option.value);
|
||||
closeAndFocusTrigger();
|
||||
}}
|
||||
>
|
||||
<CheckIcon class={cn(value !== option.value && "text-transparent")} />
|
||||
{option.label}
|
||||
</Command.Item>
|
||||
{/each}
|
||||
</Command.Group>
|
||||
</Command.List>
|
||||
</Command.Root>
|
||||
</Popover.Content>
|
||||
<Popover.Trigger bind:ref={triggerRef} class={triggerClass}>
|
||||
{#snippet child({ props })}
|
||||
<Button
|
||||
variant={triggerVariant}
|
||||
size={triggerSize}
|
||||
class={cn("w-[200px] justify-between", className)}
|
||||
{...props}
|
||||
role="combobox"
|
||||
aria-expanded={open}
|
||||
>
|
||||
{selectedValue || labels.placeholder}
|
||||
<ChevronsUpDownIcon class="ml-2 size-4 shrink-0 opacity-50" />
|
||||
</Button>
|
||||
{/snippet}
|
||||
</Popover.Trigger>
|
||||
<Popover.Content class="w-[200px] p-0">
|
||||
<Command.Root>
|
||||
<Command.Input autofocus placeholder={labels.search} />
|
||||
<Command.List>
|
||||
<Command.Empty>{labels.notFound}</Command.Empty>
|
||||
<Command.Group>
|
||||
{#each options as option (option.value)}
|
||||
<Command.Item
|
||||
value={option.value}
|
||||
keywords={option.keywords}
|
||||
onSelect={() => {
|
||||
onChange(option.value);
|
||||
closeAndFocusTrigger();
|
||||
}}
|
||||
>
|
||||
<CheckIcon class={cn(value !== option.value && "text-transparent")} />
|
||||
{option.label}
|
||||
</Command.Item>
|
||||
{/each}
|
||||
</Command.Group>
|
||||
</Command.List>
|
||||
</Command.Root>
|
||||
</Popover.Content>
|
||||
</Popover.Root>
|
||||
|
||||
@@ -1,40 +1,40 @@
|
||||
<script lang="ts">
|
||||
import type { Command as CommandPrimitive, Dialog as DialogPrimitive } from "bits-ui";
|
||||
import type { Snippet } from "svelte";
|
||||
import Command from "./command.svelte";
|
||||
import * as Dialog from "$lib/components/ui/dialog/index.js";
|
||||
import type { WithoutChildrenOrChild } from "$lib/utils.js";
|
||||
import type { Command as CommandPrimitive, Dialog as DialogPrimitive } from "bits-ui";
|
||||
import type { Snippet } from "svelte";
|
||||
import Command from "./command.svelte";
|
||||
import * as Dialog from "$lib/components/ui/dialog/index.js";
|
||||
import type { WithoutChildrenOrChild } from "$lib/utils.js";
|
||||
|
||||
let {
|
||||
open = $bindable(false),
|
||||
ref = $bindable(null),
|
||||
value = $bindable(""),
|
||||
title = "Command Palette",
|
||||
description = "Search for a command to run",
|
||||
portalProps,
|
||||
children,
|
||||
...restProps
|
||||
}: WithoutChildrenOrChild<DialogPrimitive.RootProps> &
|
||||
WithoutChildrenOrChild<CommandPrimitive.RootProps> & {
|
||||
portalProps?: DialogPrimitive.PortalProps;
|
||||
children: Snippet;
|
||||
title?: string;
|
||||
description?: string;
|
||||
} = $props();
|
||||
let {
|
||||
open = $bindable(false),
|
||||
ref = $bindable(null),
|
||||
value = $bindable(""),
|
||||
title = "Command Palette",
|
||||
description = "Search for a command to run",
|
||||
portalProps,
|
||||
children,
|
||||
...restProps
|
||||
}: WithoutChildrenOrChild<DialogPrimitive.RootProps> &
|
||||
WithoutChildrenOrChild<CommandPrimitive.RootProps> & {
|
||||
portalProps?: DialogPrimitive.PortalProps;
|
||||
children: Snippet;
|
||||
title?: string;
|
||||
description?: string;
|
||||
} = $props();
|
||||
</script>
|
||||
|
||||
<Dialog.Root bind:open {...restProps}>
|
||||
<Dialog.Header class="sr-only">
|
||||
<Dialog.Title>{title}</Dialog.Title>
|
||||
<Dialog.Description>{description}</Dialog.Description>
|
||||
</Dialog.Header>
|
||||
<Dialog.Content class="overflow-hidden p-0" {portalProps}>
|
||||
<Command
|
||||
class="**:data-[slot=command-input-wrapper]:h-12 [&_[data-command-group]]:px-2 [&_[data-command-group]:not([hidden])_~[data-command-group]]:pt-0 [&_[data-command-input-wrapper]_svg]:h-5 [&_[data-command-input-wrapper]_svg]:w-5 [&_[data-command-input]]:h-12 [&_[data-command-item]]:px-2 [&_[data-command-item]]:py-3 [&_[data-command-item]_svg]:h-5 [&_[data-command-item]_svg]:w-5"
|
||||
{...restProps}
|
||||
bind:value
|
||||
bind:ref
|
||||
{children}
|
||||
/>
|
||||
</Dialog.Content>
|
||||
<Dialog.Header class="sr-only">
|
||||
<Dialog.Title>{title}</Dialog.Title>
|
||||
<Dialog.Description>{description}</Dialog.Description>
|
||||
</Dialog.Header>
|
||||
<Dialog.Content class="overflow-hidden p-0" {portalProps}>
|
||||
<Command
|
||||
class="**:data-[slot=command-input-wrapper]:h-12 [&_[data-command-group]]:px-2 [&_[data-command-group]:not([hidden])_~[data-command-group]]:pt-0 [&_[data-command-input-wrapper]_svg]:h-5 [&_[data-command-input-wrapper]_svg]:w-5 [&_[data-command-input]]:h-12 [&_[data-command-item]]:px-2 [&_[data-command-item]]:py-3 [&_[data-command-item]_svg]:h-5 [&_[data-command-item]_svg]:w-5"
|
||||
{...restProps}
|
||||
bind:value
|
||||
bind:ref
|
||||
{children}
|
||||
/>
|
||||
</Dialog.Content>
|
||||
</Dialog.Root>
|
||||
|
||||
@@ -1,17 +1,17 @@
|
||||
<script lang="ts">
|
||||
import { Command as CommandPrimitive } from "bits-ui";
|
||||
import { cn } from "$lib/utils.js";
|
||||
import { Command as CommandPrimitive } from "bits-ui";
|
||||
import { cn } from "$lib/utils.js";
|
||||
|
||||
let {
|
||||
ref = $bindable(null),
|
||||
class: className,
|
||||
...restProps
|
||||
}: CommandPrimitive.EmptyProps = $props();
|
||||
let {
|
||||
ref = $bindable(null),
|
||||
class: className,
|
||||
...restProps
|
||||
}: CommandPrimitive.EmptyProps = $props();
|
||||
</script>
|
||||
|
||||
<CommandPrimitive.Empty
|
||||
bind:ref
|
||||
data-slot="command-empty"
|
||||
class={cn("py-6 text-center text-sm", className)}
|
||||
{...restProps}
|
||||
bind:ref
|
||||
data-slot="command-empty"
|
||||
class={cn("py-6 text-center text-sm", className)}
|
||||
{...restProps}
|
||||
/>
|
||||
|
||||
@@ -1,30 +1,30 @@
|
||||
<script lang="ts">
|
||||
import { Command as CommandPrimitive, useId } from "bits-ui";
|
||||
import { cn } from "$lib/utils.js";
|
||||
import { Command as CommandPrimitive, useId } from "bits-ui";
|
||||
import { cn } from "$lib/utils.js";
|
||||
|
||||
let {
|
||||
ref = $bindable(null),
|
||||
class: className,
|
||||
children,
|
||||
heading,
|
||||
value,
|
||||
...restProps
|
||||
}: CommandPrimitive.GroupProps & {
|
||||
heading?: string;
|
||||
} = $props();
|
||||
let {
|
||||
ref = $bindable(null),
|
||||
class: className,
|
||||
children,
|
||||
heading,
|
||||
value,
|
||||
...restProps
|
||||
}: CommandPrimitive.GroupProps & {
|
||||
heading?: string;
|
||||
} = $props();
|
||||
</script>
|
||||
|
||||
<CommandPrimitive.Group
|
||||
bind:ref
|
||||
data-slot="command-group"
|
||||
class={cn("text-foreground overflow-hidden p-1", className)}
|
||||
value={value ?? heading ?? `----${useId()}`}
|
||||
{...restProps}
|
||||
bind:ref
|
||||
data-slot="command-group"
|
||||
class={cn("text-foreground overflow-hidden p-1", className)}
|
||||
value={value ?? heading ?? `----${useId()}`}
|
||||
{...restProps}
|
||||
>
|
||||
{#if heading}
|
||||
<CommandPrimitive.GroupHeading class="text-muted-foreground px-2 py-1.5 text-xs font-medium">
|
||||
{heading}
|
||||
</CommandPrimitive.GroupHeading>
|
||||
{/if}
|
||||
<CommandPrimitive.GroupItems {children} />
|
||||
{#if heading}
|
||||
<CommandPrimitive.GroupHeading class="text-muted-foreground px-2 py-1.5 text-xs font-medium">
|
||||
{heading}
|
||||
</CommandPrimitive.GroupHeading>
|
||||
{/if}
|
||||
<CommandPrimitive.GroupItems {children} />
|
||||
</CommandPrimitive.Group>
|
||||
|
||||
@@ -1,26 +1,26 @@
|
||||
<script lang="ts">
|
||||
import { Command as CommandPrimitive } from "bits-ui";
|
||||
import SearchIcon from "@lucide/svelte/icons/search";
|
||||
import { cn } from "$lib/utils.js";
|
||||
import { Command as CommandPrimitive } from "bits-ui";
|
||||
import SearchIcon from "@lucide/svelte/icons/search";
|
||||
import { cn } from "$lib/utils.js";
|
||||
|
||||
let {
|
||||
ref = $bindable(null),
|
||||
class: className,
|
||||
value = $bindable(""),
|
||||
...restProps
|
||||
}: CommandPrimitive.InputProps = $props();
|
||||
let {
|
||||
ref = $bindable(null),
|
||||
class: className,
|
||||
value = $bindable(""),
|
||||
...restProps
|
||||
}: CommandPrimitive.InputProps = $props();
|
||||
</script>
|
||||
|
||||
<div class="flex h-9 items-center gap-2 border-b px-3" data-slot="command-input-wrapper">
|
||||
<SearchIcon class="size-4 shrink-0 opacity-50" />
|
||||
<CommandPrimitive.Input
|
||||
data-slot="command-input"
|
||||
class={cn(
|
||||
"placeholder:text-muted-foreground flex h-10 w-full rounded-md bg-transparent py-3 text-sm outline-hidden disabled:cursor-not-allowed disabled:opacity-50",
|
||||
className
|
||||
)}
|
||||
bind:ref
|
||||
{...restProps}
|
||||
bind:value
|
||||
/>
|
||||
<SearchIcon class="size-4 shrink-0 opacity-50" />
|
||||
<CommandPrimitive.Input
|
||||
data-slot="command-input"
|
||||
class={cn(
|
||||
"placeholder:text-muted-foreground flex h-10 w-full rounded-md bg-transparent py-3 text-sm outline-hidden disabled:cursor-not-allowed disabled:opacity-50",
|
||||
className,
|
||||
)}
|
||||
bind:ref
|
||||
{...restProps}
|
||||
bind:value
|
||||
/>
|
||||
</div>
|
||||
|
||||
@@ -1,20 +1,20 @@
|
||||
<script lang="ts">
|
||||
import { Command as CommandPrimitive } from "bits-ui";
|
||||
import { cn } from "$lib/utils.js";
|
||||
import { Command as CommandPrimitive } from "bits-ui";
|
||||
import { cn } from "$lib/utils.js";
|
||||
|
||||
let {
|
||||
ref = $bindable(null),
|
||||
class: className,
|
||||
...restProps
|
||||
}: CommandPrimitive.ItemProps = $props();
|
||||
let {
|
||||
ref = $bindable(null),
|
||||
class: className,
|
||||
...restProps
|
||||
}: CommandPrimitive.ItemProps = $props();
|
||||
</script>
|
||||
|
||||
<CommandPrimitive.Item
|
||||
bind:ref
|
||||
data-slot="command-item"
|
||||
class={cn(
|
||||
"aria-selected:bg-accent aria-selected:text-accent-foreground [&_svg:not([class*='text-'])]:text-muted-foreground relative flex cursor-default items-center gap-2 rounded-sm px-2 py-1.5 text-sm outline-hidden select-none data-[disabled]:pointer-events-none data-[disabled]:opacity-50 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4",
|
||||
className
|
||||
)}
|
||||
{...restProps}
|
||||
bind:ref
|
||||
data-slot="command-item"
|
||||
class={cn(
|
||||
"aria-selected:bg-accent aria-selected:text-accent-foreground [&_svg:not([class*='text-'])]:text-muted-foreground relative flex cursor-default items-center gap-2 rounded-sm px-2 py-1.5 text-sm outline-hidden select-none data-[disabled]:pointer-events-none data-[disabled]:opacity-50 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4",
|
||||
className,
|
||||
)}
|
||||
{...restProps}
|
||||
/>
|
||||
|
||||
@@ -1,20 +1,20 @@
|
||||
<script lang="ts">
|
||||
import { Command as CommandPrimitive } from "bits-ui";
|
||||
import { cn } from "$lib/utils.js";
|
||||
import { Command as CommandPrimitive } from "bits-ui";
|
||||
import { cn } from "$lib/utils.js";
|
||||
|
||||
let {
|
||||
ref = $bindable(null),
|
||||
class: className,
|
||||
...restProps
|
||||
}: CommandPrimitive.LinkItemProps = $props();
|
||||
let {
|
||||
ref = $bindable(null),
|
||||
class: className,
|
||||
...restProps
|
||||
}: CommandPrimitive.LinkItemProps = $props();
|
||||
</script>
|
||||
|
||||
<CommandPrimitive.LinkItem
|
||||
bind:ref
|
||||
data-slot="command-item"
|
||||
class={cn(
|
||||
"aria-selected:bg-accent aria-selected:text-accent-foreground [&_svg:not([class*='text-'])]:text-muted-foreground relative flex cursor-default items-center gap-2 rounded-sm px-2 py-1.5 text-sm outline-hidden select-none data-[disabled=true]:pointer-events-none data-[disabled=true]:opacity-50 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4",
|
||||
className
|
||||
)}
|
||||
{...restProps}
|
||||
bind:ref
|
||||
data-slot="command-item"
|
||||
class={cn(
|
||||
"aria-selected:bg-accent aria-selected:text-accent-foreground [&_svg:not([class*='text-'])]:text-muted-foreground relative flex cursor-default items-center gap-2 rounded-sm px-2 py-1.5 text-sm outline-hidden select-none data-[disabled=true]:pointer-events-none data-[disabled=true]:opacity-50 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4",
|
||||
className,
|
||||
)}
|
||||
{...restProps}
|
||||
/>
|
||||
|
||||
@@ -1,17 +1,17 @@
|
||||
<script lang="ts">
|
||||
import { Command as CommandPrimitive } from "bits-ui";
|
||||
import { cn } from "$lib/utils.js";
|
||||
import { Command as CommandPrimitive } from "bits-ui";
|
||||
import { cn } from "$lib/utils.js";
|
||||
|
||||
let {
|
||||
ref = $bindable(null),
|
||||
class: className,
|
||||
...restProps
|
||||
}: CommandPrimitive.ListProps = $props();
|
||||
let {
|
||||
ref = $bindable(null),
|
||||
class: className,
|
||||
...restProps
|
||||
}: CommandPrimitive.ListProps = $props();
|
||||
</script>
|
||||
|
||||
<CommandPrimitive.List
|
||||
bind:ref
|
||||
data-slot="command-list"
|
||||
class={cn("max-h-[300px] scroll-py-1 overflow-x-hidden overflow-y-auto", className)}
|
||||
{...restProps}
|
||||
bind:ref
|
||||
data-slot="command-list"
|
||||
class={cn("max-h-[300px] scroll-py-1 overflow-x-hidden overflow-y-auto", className)}
|
||||
{...restProps}
|
||||
/>
|
||||
|
||||
@@ -1,17 +1,17 @@
|
||||
<script lang="ts">
|
||||
import { Command as CommandPrimitive } from "bits-ui";
|
||||
import { cn } from "$lib/utils.js";
|
||||
import { Command as CommandPrimitive } from "bits-ui";
|
||||
import { cn } from "$lib/utils.js";
|
||||
|
||||
let {
|
||||
ref = $bindable(null),
|
||||
class: className,
|
||||
...restProps
|
||||
}: CommandPrimitive.SeparatorProps = $props();
|
||||
let {
|
||||
ref = $bindable(null),
|
||||
class: className,
|
||||
...restProps
|
||||
}: CommandPrimitive.SeparatorProps = $props();
|
||||
</script>
|
||||
|
||||
<CommandPrimitive.Separator
|
||||
bind:ref
|
||||
data-slot="command-separator"
|
||||
class={cn("bg-border -mx-1 h-px", className)}
|
||||
{...restProps}
|
||||
bind:ref
|
||||
data-slot="command-separator"
|
||||
class={cn("bg-border -mx-1 h-px", className)}
|
||||
{...restProps}
|
||||
/>
|
||||
|
||||
@@ -1,20 +1,20 @@
|
||||
<script lang="ts">
|
||||
import { cn, type WithElementRef } from "$lib/utils.js";
|
||||
import type { HTMLAttributes } from "svelte/elements";
|
||||
import { cn, type WithElementRef } from "$lib/utils.js";
|
||||
import type { HTMLAttributes } from "svelte/elements";
|
||||
|
||||
let {
|
||||
ref = $bindable(null),
|
||||
class: className,
|
||||
children,
|
||||
...restProps
|
||||
}: WithElementRef<HTMLAttributes<HTMLSpanElement>> = $props();
|
||||
let {
|
||||
ref = $bindable(null),
|
||||
class: className,
|
||||
children,
|
||||
...restProps
|
||||
}: WithElementRef<HTMLAttributes<HTMLSpanElement>> = $props();
|
||||
</script>
|
||||
|
||||
<span
|
||||
bind:this={ref}
|
||||
data-slot="command-shortcut"
|
||||
class={cn("text-muted-foreground ml-auto text-xs tracking-widest", className)}
|
||||
{...restProps}
|
||||
bind:this={ref}
|
||||
data-slot="command-shortcut"
|
||||
class={cn("text-muted-foreground ml-auto text-xs tracking-widest", className)}
|
||||
{...restProps}
|
||||
>
|
||||
{@render children?.()}
|
||||
{@render children?.()}
|
||||
</span>
|
||||
|
||||
@@ -1,22 +1,22 @@
|
||||
<script lang="ts">
|
||||
import { Command as CommandPrimitive } from "bits-ui";
|
||||
import { cn } from "$lib/utils.js";
|
||||
import { Command as CommandPrimitive } from "bits-ui";
|
||||
import { cn } from "$lib/utils.js";
|
||||
|
||||
let {
|
||||
ref = $bindable(null),
|
||||
value = $bindable(""),
|
||||
class: className,
|
||||
...restProps
|
||||
}: CommandPrimitive.RootProps = $props();
|
||||
let {
|
||||
ref = $bindable(null),
|
||||
value = $bindable(""),
|
||||
class: className,
|
||||
...restProps
|
||||
}: CommandPrimitive.RootProps = $props();
|
||||
</script>
|
||||
|
||||
<CommandPrimitive.Root
|
||||
bind:value
|
||||
bind:ref
|
||||
data-slot="command"
|
||||
class={cn(
|
||||
"bg-popover text-popover-foreground flex h-full w-full flex-col overflow-hidden rounded-md",
|
||||
className
|
||||
)}
|
||||
{...restProps}
|
||||
bind:value
|
||||
bind:ref
|
||||
data-slot="command"
|
||||
class={cn(
|
||||
"bg-popover text-popover-foreground flex h-full w-full flex-col overflow-hidden rounded-md",
|
||||
className,
|
||||
)}
|
||||
{...restProps}
|
||||
/>
|
||||
|
||||
@@ -14,27 +14,27 @@ import LinkItem from "./command-link-item.svelte";
|
||||
const Loading = CommandPrimitive.Loading;
|
||||
|
||||
export {
|
||||
Root,
|
||||
Dialog,
|
||||
Empty,
|
||||
Group,
|
||||
Item,
|
||||
LinkItem,
|
||||
Input,
|
||||
List,
|
||||
Separator,
|
||||
Shortcut,
|
||||
Loading,
|
||||
//
|
||||
Root as Command,
|
||||
Dialog as CommandDialog,
|
||||
Empty as CommandEmpty,
|
||||
Group as CommandGroup,
|
||||
Item as CommandItem,
|
||||
LinkItem as CommandLinkItem,
|
||||
Input as CommandInput,
|
||||
List as CommandList,
|
||||
Separator as CommandSeparator,
|
||||
Shortcut as CommandShortcut,
|
||||
Loading as CommandLoading
|
||||
Root,
|
||||
Dialog,
|
||||
Empty,
|
||||
Group,
|
||||
Item,
|
||||
LinkItem,
|
||||
Input,
|
||||
List,
|
||||
Separator,
|
||||
Shortcut,
|
||||
Loading,
|
||||
//
|
||||
Root as Command,
|
||||
Dialog as CommandDialog,
|
||||
Empty as CommandEmpty,
|
||||
Group as CommandGroup,
|
||||
Item as CommandItem,
|
||||
LinkItem as CommandLinkItem,
|
||||
Input as CommandInput,
|
||||
List as CommandList,
|
||||
Separator as CommandSeparator,
|
||||
Shortcut as CommandShortcut,
|
||||
Loading as CommandLoading,
|
||||
};
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
<script lang="ts">
|
||||
import { Dialog as DialogPrimitive } from "bits-ui";
|
||||
import { Dialog as DialogPrimitive } from "bits-ui";
|
||||
|
||||
let { ref = $bindable(null), ...restProps }: DialogPrimitive.CloseProps = $props();
|
||||
let { ref = $bindable(null), ...restProps }: DialogPrimitive.CloseProps = $props();
|
||||
</script>
|
||||
|
||||
<DialogPrimitive.Close bind:ref data-slot="dialog-close" {...restProps} />
|
||||
|
||||
@@ -1,43 +1,43 @@
|
||||
<script lang="ts">
|
||||
import { Dialog as DialogPrimitive } from "bits-ui";
|
||||
import XIcon from "@lucide/svelte/icons/x";
|
||||
import type { Snippet } from "svelte";
|
||||
import * as Dialog from "./index.js";
|
||||
import { cn, type WithoutChildrenOrChild } from "$lib/utils.js";
|
||||
import { Dialog as DialogPrimitive } from "bits-ui";
|
||||
import XIcon from "@lucide/svelte/icons/x";
|
||||
import type { Snippet } from "svelte";
|
||||
import * as Dialog from "./index.js";
|
||||
import { cn, type WithoutChildrenOrChild } from "$lib/utils.js";
|
||||
|
||||
let {
|
||||
ref = $bindable(null),
|
||||
class: className,
|
||||
portalProps,
|
||||
children,
|
||||
showCloseButton = true,
|
||||
...restProps
|
||||
}: WithoutChildrenOrChild<DialogPrimitive.ContentProps> & {
|
||||
portalProps?: DialogPrimitive.PortalProps;
|
||||
children: Snippet;
|
||||
showCloseButton?: boolean;
|
||||
} = $props();
|
||||
let {
|
||||
ref = $bindable(null),
|
||||
class: className,
|
||||
portalProps,
|
||||
children,
|
||||
showCloseButton = true,
|
||||
...restProps
|
||||
}: WithoutChildrenOrChild<DialogPrimitive.ContentProps> & {
|
||||
portalProps?: DialogPrimitive.PortalProps;
|
||||
children: Snippet;
|
||||
showCloseButton?: boolean;
|
||||
} = $props();
|
||||
</script>
|
||||
|
||||
<Dialog.Portal {...portalProps}>
|
||||
<Dialog.Overlay />
|
||||
<DialogPrimitive.Content
|
||||
bind:ref
|
||||
data-slot="dialog-content"
|
||||
class={cn(
|
||||
"bg-background data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 fixed top-[50%] left-[50%] z-50 grid w-full max-w-[calc(100%-2rem)] translate-x-[-50%] translate-y-[-50%] gap-4 rounded-lg border p-6 shadow-lg duration-200 sm:max-w-lg",
|
||||
className
|
||||
)}
|
||||
{...restProps}
|
||||
>
|
||||
{@render children?.()}
|
||||
{#if showCloseButton}
|
||||
<DialogPrimitive.Close
|
||||
class="ring-offset-background focus:ring-ring absolute top-4 right-4 rounded-xs opacity-70 transition-opacity hover:opacity-100 focus:ring-2 focus:ring-offset-2 focus:outline-hidden disabled:pointer-events-none [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4"
|
||||
>
|
||||
<XIcon />
|
||||
<span class="sr-only">Close</span>
|
||||
</DialogPrimitive.Close>
|
||||
{/if}
|
||||
</DialogPrimitive.Content>
|
||||
<Dialog.Overlay />
|
||||
<DialogPrimitive.Content
|
||||
bind:ref
|
||||
data-slot="dialog-content"
|
||||
class={cn(
|
||||
"bg-background data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 fixed top-[50%] left-[50%] z-50 grid w-full max-w-[calc(100%-2rem)] translate-x-[-50%] translate-y-[-50%] gap-4 rounded-lg border p-6 shadow-lg duration-200 sm:max-w-lg",
|
||||
className,
|
||||
)}
|
||||
{...restProps}
|
||||
>
|
||||
{@render children?.()}
|
||||
{#if showCloseButton}
|
||||
<DialogPrimitive.Close
|
||||
class="ring-offset-background focus:ring-ring absolute top-4 right-4 rounded-xs opacity-70 transition-opacity hover:opacity-100 focus:ring-2 focus:ring-offset-2 focus:outline-hidden disabled:pointer-events-none [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4"
|
||||
>
|
||||
<XIcon />
|
||||
<span class="sr-only">Close</span>
|
||||
</DialogPrimitive.Close>
|
||||
{/if}
|
||||
</DialogPrimitive.Content>
|
||||
</Dialog.Portal>
|
||||
|
||||
@@ -1,17 +1,17 @@
|
||||
<script lang="ts">
|
||||
import { Dialog as DialogPrimitive } from "bits-ui";
|
||||
import { cn } from "$lib/utils.js";
|
||||
import { Dialog as DialogPrimitive } from "bits-ui";
|
||||
import { cn } from "$lib/utils.js";
|
||||
|
||||
let {
|
||||
ref = $bindable(null),
|
||||
class: className,
|
||||
...restProps
|
||||
}: DialogPrimitive.DescriptionProps = $props();
|
||||
let {
|
||||
ref = $bindable(null),
|
||||
class: className,
|
||||
...restProps
|
||||
}: DialogPrimitive.DescriptionProps = $props();
|
||||
</script>
|
||||
|
||||
<DialogPrimitive.Description
|
||||
bind:ref
|
||||
data-slot="dialog-description"
|
||||
class={cn("text-muted-foreground text-sm", className)}
|
||||
{...restProps}
|
||||
bind:ref
|
||||
data-slot="dialog-description"
|
||||
class={cn("text-muted-foreground text-sm", className)}
|
||||
{...restProps}
|
||||
/>
|
||||
|
||||
@@ -1,20 +1,20 @@
|
||||
<script lang="ts">
|
||||
import { cn, type WithElementRef } from "$lib/utils.js";
|
||||
import type { HTMLAttributes } from "svelte/elements";
|
||||
import { cn, type WithElementRef } from "$lib/utils.js";
|
||||
import type { HTMLAttributes } from "svelte/elements";
|
||||
|
||||
let {
|
||||
ref = $bindable(null),
|
||||
class: className,
|
||||
children,
|
||||
...restProps
|
||||
}: WithElementRef<HTMLAttributes<HTMLDivElement>> = $props();
|
||||
let {
|
||||
ref = $bindable(null),
|
||||
class: className,
|
||||
children,
|
||||
...restProps
|
||||
}: WithElementRef<HTMLAttributes<HTMLDivElement>> = $props();
|
||||
</script>
|
||||
|
||||
<div
|
||||
bind:this={ref}
|
||||
data-slot="dialog-footer"
|
||||
class={cn("flex flex-col-reverse gap-2 sm:flex-row sm:justify-end", className)}
|
||||
{...restProps}
|
||||
bind:this={ref}
|
||||
data-slot="dialog-footer"
|
||||
class={cn("flex flex-col-reverse gap-2 sm:flex-row sm:justify-end", className)}
|
||||
{...restProps}
|
||||
>
|
||||
{@render children?.()}
|
||||
{@render children?.()}
|
||||
</div>
|
||||
|
||||
@@ -1,20 +1,20 @@
|
||||
<script lang="ts">
|
||||
import type { HTMLAttributes } from "svelte/elements";
|
||||
import { cn, type WithElementRef } from "$lib/utils.js";
|
||||
import type { HTMLAttributes } from "svelte/elements";
|
||||
import { cn, type WithElementRef } from "$lib/utils.js";
|
||||
|
||||
let {
|
||||
ref = $bindable(null),
|
||||
class: className,
|
||||
children,
|
||||
...restProps
|
||||
}: WithElementRef<HTMLAttributes<HTMLDivElement>> = $props();
|
||||
let {
|
||||
ref = $bindable(null),
|
||||
class: className,
|
||||
children,
|
||||
...restProps
|
||||
}: WithElementRef<HTMLAttributes<HTMLDivElement>> = $props();
|
||||
</script>
|
||||
|
||||
<div
|
||||
bind:this={ref}
|
||||
data-slot="dialog-header"
|
||||
class={cn("flex flex-col gap-2 text-center sm:text-left", className)}
|
||||
{...restProps}
|
||||
bind:this={ref}
|
||||
data-slot="dialog-header"
|
||||
class={cn("flex flex-col gap-2 text-center sm:text-left", className)}
|
||||
{...restProps}
|
||||
>
|
||||
{@render children?.()}
|
||||
{@render children?.()}
|
||||
</div>
|
||||
|
||||
@@ -1,20 +1,20 @@
|
||||
<script lang="ts">
|
||||
import { Dialog as DialogPrimitive } from "bits-ui";
|
||||
import { cn } from "$lib/utils.js";
|
||||
import { Dialog as DialogPrimitive } from "bits-ui";
|
||||
import { cn } from "$lib/utils.js";
|
||||
|
||||
let {
|
||||
ref = $bindable(null),
|
||||
class: className,
|
||||
...restProps
|
||||
}: DialogPrimitive.OverlayProps = $props();
|
||||
let {
|
||||
ref = $bindable(null),
|
||||
class: className,
|
||||
...restProps
|
||||
}: DialogPrimitive.OverlayProps = $props();
|
||||
</script>
|
||||
|
||||
<DialogPrimitive.Overlay
|
||||
bind:ref
|
||||
data-slot="dialog-overlay"
|
||||
class={cn(
|
||||
"data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 fixed inset-0 z-50 bg-black/50",
|
||||
className
|
||||
)}
|
||||
{...restProps}
|
||||
bind:ref
|
||||
data-slot="dialog-overlay"
|
||||
class={cn(
|
||||
"data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 fixed inset-0 z-50 bg-black/50",
|
||||
className,
|
||||
)}
|
||||
{...restProps}
|
||||
/>
|
||||
|
||||
@@ -1,17 +1,17 @@
|
||||
<script lang="ts">
|
||||
import { Dialog as DialogPrimitive } from "bits-ui";
|
||||
import { cn } from "$lib/utils.js";
|
||||
import { Dialog as DialogPrimitive } from "bits-ui";
|
||||
import { cn } from "$lib/utils.js";
|
||||
|
||||
let {
|
||||
ref = $bindable(null),
|
||||
class: className,
|
||||
...restProps
|
||||
}: DialogPrimitive.TitleProps = $props();
|
||||
let {
|
||||
ref = $bindable(null),
|
||||
class: className,
|
||||
...restProps
|
||||
}: DialogPrimitive.TitleProps = $props();
|
||||
</script>
|
||||
|
||||
<DialogPrimitive.Title
|
||||
bind:ref
|
||||
data-slot="dialog-title"
|
||||
class={cn("text-lg leading-none font-semibold", className)}
|
||||
{...restProps}
|
||||
bind:ref
|
||||
data-slot="dialog-title"
|
||||
class={cn("text-lg leading-none font-semibold", className)}
|
||||
{...restProps}
|
||||
/>
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
<script lang="ts">
|
||||
import { Dialog as DialogPrimitive } from "bits-ui";
|
||||
import { Dialog as DialogPrimitive } from "bits-ui";
|
||||
|
||||
let { ref = $bindable(null), ...restProps }: DialogPrimitive.TriggerProps = $props();
|
||||
let { ref = $bindable(null), ...restProps }: DialogPrimitive.TriggerProps = $props();
|
||||
</script>
|
||||
|
||||
<DialogPrimitive.Trigger bind:ref data-slot="dialog-trigger" {...restProps} />
|
||||
|
||||
@@ -13,25 +13,25 @@ const Root = DialogPrimitive.Root;
|
||||
const Portal = DialogPrimitive.Portal;
|
||||
|
||||
export {
|
||||
Root,
|
||||
Title,
|
||||
Portal,
|
||||
Footer,
|
||||
Header,
|
||||
Trigger,
|
||||
Overlay,
|
||||
Content,
|
||||
Description,
|
||||
Close,
|
||||
//
|
||||
Root as Dialog,
|
||||
Title as DialogTitle,
|
||||
Portal as DialogPortal,
|
||||
Footer as DialogFooter,
|
||||
Header as DialogHeader,
|
||||
Trigger as DialogTrigger,
|
||||
Overlay as DialogOverlay,
|
||||
Content as DialogContent,
|
||||
Description as DialogDescription,
|
||||
Close as DialogClose
|
||||
Root,
|
||||
Title,
|
||||
Portal,
|
||||
Footer,
|
||||
Header,
|
||||
Trigger,
|
||||
Overlay,
|
||||
Content,
|
||||
Description,
|
||||
Close,
|
||||
//
|
||||
Root as Dialog,
|
||||
Title as DialogTitle,
|
||||
Portal as DialogPortal,
|
||||
Footer as DialogFooter,
|
||||
Header as DialogHeader,
|
||||
Trigger as DialogTrigger,
|
||||
Overlay as DialogOverlay,
|
||||
Content as DialogContent,
|
||||
Description as DialogDescription,
|
||||
Close as DialogClose,
|
||||
};
|
||||
|
||||
@@ -1,41 +1,41 @@
|
||||
<script lang="ts">
|
||||
import { DropdownMenu as DropdownMenuPrimitive } from "bits-ui";
|
||||
import CheckIcon from "@lucide/svelte/icons/check";
|
||||
import MinusIcon from "@lucide/svelte/icons/minus";
|
||||
import { cn, type WithoutChildrenOrChild } from "$lib/utils.js";
|
||||
import type { Snippet } from "svelte";
|
||||
import { DropdownMenu as DropdownMenuPrimitive } from "bits-ui";
|
||||
import CheckIcon from "@lucide/svelte/icons/check";
|
||||
import MinusIcon from "@lucide/svelte/icons/minus";
|
||||
import { cn, type WithoutChildrenOrChild } from "$lib/utils.js";
|
||||
import type { Snippet } from "svelte";
|
||||
|
||||
let {
|
||||
ref = $bindable(null),
|
||||
checked = $bindable(false),
|
||||
indeterminate = $bindable(false),
|
||||
class: className,
|
||||
children: childrenProp,
|
||||
...restProps
|
||||
}: WithoutChildrenOrChild<DropdownMenuPrimitive.CheckboxItemProps> & {
|
||||
children?: Snippet;
|
||||
} = $props();
|
||||
let {
|
||||
ref = $bindable(null),
|
||||
checked = $bindable(false),
|
||||
indeterminate = $bindable(false),
|
||||
class: className,
|
||||
children: childrenProp,
|
||||
...restProps
|
||||
}: WithoutChildrenOrChild<DropdownMenuPrimitive.CheckboxItemProps> & {
|
||||
children?: Snippet;
|
||||
} = $props();
|
||||
</script>
|
||||
|
||||
<DropdownMenuPrimitive.CheckboxItem
|
||||
bind:ref
|
||||
bind:checked
|
||||
bind:indeterminate
|
||||
data-slot="dropdown-menu-checkbox-item"
|
||||
class={cn(
|
||||
"focus:bg-accent focus:text-accent-foreground relative flex cursor-default items-center gap-2 rounded-sm py-1.5 pr-2 pl-8 text-sm outline-hidden select-none data-[disabled]:pointer-events-none data-[disabled]:opacity-50 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4",
|
||||
className
|
||||
)}
|
||||
{...restProps}
|
||||
bind:ref
|
||||
bind:checked
|
||||
bind:indeterminate
|
||||
data-slot="dropdown-menu-checkbox-item"
|
||||
class={cn(
|
||||
"focus:bg-accent focus:text-accent-foreground relative flex cursor-default items-center gap-2 rounded-sm py-1.5 pr-2 pl-8 text-sm outline-hidden select-none data-[disabled]:pointer-events-none data-[disabled]:opacity-50 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4",
|
||||
className,
|
||||
)}
|
||||
{...restProps}
|
||||
>
|
||||
{#snippet children({ checked, indeterminate })}
|
||||
<span class="pointer-events-none absolute left-2 flex size-3.5 items-center justify-center">
|
||||
{#if indeterminate}
|
||||
<MinusIcon class="size-4" />
|
||||
{:else}
|
||||
<CheckIcon class={cn("size-4", !checked && "text-transparent")} />
|
||||
{/if}
|
||||
</span>
|
||||
{@render childrenProp?.()}
|
||||
{/snippet}
|
||||
{#snippet children({ checked, indeterminate })}
|
||||
<span class="pointer-events-none absolute left-2 flex size-3.5 items-center justify-center">
|
||||
{#if indeterminate}
|
||||
<MinusIcon class="size-4" />
|
||||
{:else}
|
||||
<CheckIcon class={cn("size-4", !checked && "text-transparent")} />
|
||||
{/if}
|
||||
</span>
|
||||
{@render childrenProp?.()}
|
||||
{/snippet}
|
||||
</DropdownMenuPrimitive.CheckboxItem>
|
||||
|
||||
@@ -1,27 +1,27 @@
|
||||
<script lang="ts">
|
||||
import { cn } from "$lib/utils.js";
|
||||
import { DropdownMenu as DropdownMenuPrimitive } from "bits-ui";
|
||||
import { cn } from "$lib/utils.js";
|
||||
import { DropdownMenu as DropdownMenuPrimitive } from "bits-ui";
|
||||
|
||||
let {
|
||||
ref = $bindable(null),
|
||||
sideOffset = 4,
|
||||
portalProps,
|
||||
class: className,
|
||||
...restProps
|
||||
}: DropdownMenuPrimitive.ContentProps & {
|
||||
portalProps?: DropdownMenuPrimitive.PortalProps;
|
||||
} = $props();
|
||||
let {
|
||||
ref = $bindable(null),
|
||||
sideOffset = 4,
|
||||
portalProps,
|
||||
class: className,
|
||||
...restProps
|
||||
}: DropdownMenuPrimitive.ContentProps & {
|
||||
portalProps?: DropdownMenuPrimitive.PortalProps;
|
||||
} = $props();
|
||||
</script>
|
||||
|
||||
<DropdownMenuPrimitive.Portal {...portalProps}>
|
||||
<DropdownMenuPrimitive.Content
|
||||
bind:ref
|
||||
data-slot="dropdown-menu-content"
|
||||
{sideOffset}
|
||||
class={cn(
|
||||
"bg-popover text-popover-foreground data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 z-50 max-h-(--bits-dropdown-menu-content-available-height) min-w-[8rem] origin-(--bits-dropdown-menu-content-transform-origin) overflow-x-hidden overflow-y-auto rounded-md border p-1 shadow-md outline-none",
|
||||
className
|
||||
)}
|
||||
{...restProps}
|
||||
/>
|
||||
<DropdownMenuPrimitive.Content
|
||||
bind:ref
|
||||
data-slot="dropdown-menu-content"
|
||||
{sideOffset}
|
||||
class={cn(
|
||||
"bg-popover text-popover-foreground data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 z-50 max-h-(--bits-dropdown-menu-content-available-height) min-w-[8rem] origin-(--bits-dropdown-menu-content-transform-origin) overflow-x-hidden overflow-y-auto rounded-md border p-1 shadow-md outline-none",
|
||||
className,
|
||||
)}
|
||||
{...restProps}
|
||||
/>
|
||||
</DropdownMenuPrimitive.Portal>
|
||||
|
||||
@@ -1,22 +1,22 @@
|
||||
<script lang="ts">
|
||||
import { DropdownMenu as DropdownMenuPrimitive } from "bits-ui";
|
||||
import { cn } from "$lib/utils.js";
|
||||
import type { ComponentProps } from "svelte";
|
||||
import { DropdownMenu as DropdownMenuPrimitive } from "bits-ui";
|
||||
import { cn } from "$lib/utils.js";
|
||||
import type { ComponentProps } from "svelte";
|
||||
|
||||
let {
|
||||
ref = $bindable(null),
|
||||
class: className,
|
||||
inset,
|
||||
...restProps
|
||||
}: ComponentProps<typeof DropdownMenuPrimitive.GroupHeading> & {
|
||||
inset?: boolean;
|
||||
} = $props();
|
||||
let {
|
||||
ref = $bindable(null),
|
||||
class: className,
|
||||
inset,
|
||||
...restProps
|
||||
}: ComponentProps<typeof DropdownMenuPrimitive.GroupHeading> & {
|
||||
inset?: boolean;
|
||||
} = $props();
|
||||
</script>
|
||||
|
||||
<DropdownMenuPrimitive.GroupHeading
|
||||
bind:ref
|
||||
data-slot="dropdown-menu-group-heading"
|
||||
data-inset={inset}
|
||||
class={cn("px-2 py-1.5 text-sm font-semibold data-[inset]:pl-8", className)}
|
||||
{...restProps}
|
||||
bind:ref
|
||||
data-slot="dropdown-menu-group-heading"
|
||||
data-inset={inset}
|
||||
class={cn("px-2 py-1.5 text-sm font-semibold data-[inset]:pl-8", className)}
|
||||
{...restProps}
|
||||
/>
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
<script lang="ts">
|
||||
import { DropdownMenu as DropdownMenuPrimitive } from "bits-ui";
|
||||
import { DropdownMenu as DropdownMenuPrimitive } from "bits-ui";
|
||||
|
||||
let { ref = $bindable(null), ...restProps }: DropdownMenuPrimitive.GroupProps = $props();
|
||||
let { ref = $bindable(null), ...restProps }: DropdownMenuPrimitive.GroupProps = $props();
|
||||
</script>
|
||||
|
||||
<DropdownMenuPrimitive.Group bind:ref data-slot="dropdown-menu-group" {...restProps} />
|
||||
|
||||
@@ -1,27 +1,27 @@
|
||||
<script lang="ts">
|
||||
import { cn } from "$lib/utils.js";
|
||||
import { DropdownMenu as DropdownMenuPrimitive } from "bits-ui";
|
||||
import { cn } from "$lib/utils.js";
|
||||
import { DropdownMenu as DropdownMenuPrimitive } from "bits-ui";
|
||||
|
||||
let {
|
||||
ref = $bindable(null),
|
||||
class: className,
|
||||
inset,
|
||||
variant = "default",
|
||||
...restProps
|
||||
}: DropdownMenuPrimitive.ItemProps & {
|
||||
inset?: boolean;
|
||||
variant?: "default" | "destructive";
|
||||
} = $props();
|
||||
let {
|
||||
ref = $bindable(null),
|
||||
class: className,
|
||||
inset,
|
||||
variant = "default",
|
||||
...restProps
|
||||
}: DropdownMenuPrimitive.ItemProps & {
|
||||
inset?: boolean;
|
||||
variant?: "default" | "destructive";
|
||||
} = $props();
|
||||
</script>
|
||||
|
||||
<DropdownMenuPrimitive.Item
|
||||
bind:ref
|
||||
data-slot="dropdown-menu-item"
|
||||
data-inset={inset}
|
||||
data-variant={variant}
|
||||
class={cn(
|
||||
"data-highlighted:bg-accent data-highlighted:text-accent-foreground data-[variant=destructive]:text-destructive data-[variant=destructive]:data-highlighted:bg-destructive/10 dark:data-[variant=destructive]:data-highlighted:bg-destructive/20 data-[variant=destructive]:data-highlighted:text-destructive data-[variant=destructive]:*:[svg]:!text-destructive [&_svg:not([class*='text-'])]:text-muted-foreground relative flex cursor-default items-center gap-2 rounded-sm px-2 py-1.5 text-sm outline-hidden select-none data-[disabled]:pointer-events-none data-[disabled]:opacity-50 data-[inset]:pl-8 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4",
|
||||
className
|
||||
)}
|
||||
{...restProps}
|
||||
bind:ref
|
||||
data-slot="dropdown-menu-item"
|
||||
data-inset={inset}
|
||||
data-variant={variant}
|
||||
class={cn(
|
||||
"data-highlighted:bg-accent data-highlighted:text-accent-foreground data-[variant=destructive]:text-destructive data-[variant=destructive]:data-highlighted:bg-destructive/10 dark:data-[variant=destructive]:data-highlighted:bg-destructive/20 data-[variant=destructive]:data-highlighted:text-destructive data-[variant=destructive]:*:[svg]:!text-destructive [&_svg:not([class*='text-'])]:text-muted-foreground relative flex cursor-default items-center gap-2 rounded-sm px-2 py-1.5 text-sm outline-hidden select-none data-[disabled]:pointer-events-none data-[disabled]:opacity-50 data-[inset]:pl-8 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4",
|
||||
className,
|
||||
)}
|
||||
{...restProps}
|
||||
/>
|
||||
|
||||
@@ -1,24 +1,24 @@
|
||||
<script lang="ts">
|
||||
import { cn, type WithElementRef } from "$lib/utils.js";
|
||||
import type { HTMLAttributes } from "svelte/elements";
|
||||
import { cn, type WithElementRef } from "$lib/utils.js";
|
||||
import type { HTMLAttributes } from "svelte/elements";
|
||||
|
||||
let {
|
||||
ref = $bindable(null),
|
||||
class: className,
|
||||
inset,
|
||||
children,
|
||||
...restProps
|
||||
}: WithElementRef<HTMLAttributes<HTMLDivElement>> & {
|
||||
inset?: boolean;
|
||||
} = $props();
|
||||
let {
|
||||
ref = $bindable(null),
|
||||
class: className,
|
||||
inset,
|
||||
children,
|
||||
...restProps
|
||||
}: WithElementRef<HTMLAttributes<HTMLDivElement>> & {
|
||||
inset?: boolean;
|
||||
} = $props();
|
||||
</script>
|
||||
|
||||
<div
|
||||
bind:this={ref}
|
||||
data-slot="dropdown-menu-label"
|
||||
data-inset={inset}
|
||||
class={cn("px-2 py-1.5 text-sm font-semibold data-[inset]:pl-8", className)}
|
||||
{...restProps}
|
||||
bind:this={ref}
|
||||
data-slot="dropdown-menu-label"
|
||||
data-inset={inset}
|
||||
class={cn("px-2 py-1.5 text-sm font-semibold data-[inset]:pl-8", className)}
|
||||
{...restProps}
|
||||
>
|
||||
{@render children?.()}
|
||||
{@render children?.()}
|
||||
</div>
|
||||
|
||||
@@ -1,16 +1,16 @@
|
||||
<script lang="ts">
|
||||
import { DropdownMenu as DropdownMenuPrimitive } from "bits-ui";
|
||||
import { DropdownMenu as DropdownMenuPrimitive } from "bits-ui";
|
||||
|
||||
let {
|
||||
ref = $bindable(null),
|
||||
value = $bindable(),
|
||||
...restProps
|
||||
}: DropdownMenuPrimitive.RadioGroupProps = $props();
|
||||
let {
|
||||
ref = $bindable(null),
|
||||
value = $bindable(),
|
||||
...restProps
|
||||
}: DropdownMenuPrimitive.RadioGroupProps = $props();
|
||||
</script>
|
||||
|
||||
<DropdownMenuPrimitive.RadioGroup
|
||||
bind:ref
|
||||
bind:value
|
||||
data-slot="dropdown-menu-radio-group"
|
||||
{...restProps}
|
||||
bind:ref
|
||||
bind:value
|
||||
data-slot="dropdown-menu-radio-group"
|
||||
{...restProps}
|
||||
/>
|
||||
|
||||
@@ -1,31 +1,31 @@
|
||||
<script lang="ts">
|
||||
import { DropdownMenu as DropdownMenuPrimitive } from "bits-ui";
|
||||
import CircleIcon from "@lucide/svelte/icons/circle";
|
||||
import { cn, type WithoutChild } from "$lib/utils.js";
|
||||
import { DropdownMenu as DropdownMenuPrimitive } from "bits-ui";
|
||||
import CircleIcon from "@lucide/svelte/icons/circle";
|
||||
import { cn, type WithoutChild } from "$lib/utils.js";
|
||||
|
||||
let {
|
||||
ref = $bindable(null),
|
||||
class: className,
|
||||
children: childrenProp,
|
||||
...restProps
|
||||
}: WithoutChild<DropdownMenuPrimitive.RadioItemProps> = $props();
|
||||
let {
|
||||
ref = $bindable(null),
|
||||
class: className,
|
||||
children: childrenProp,
|
||||
...restProps
|
||||
}: WithoutChild<DropdownMenuPrimitive.RadioItemProps> = $props();
|
||||
</script>
|
||||
|
||||
<DropdownMenuPrimitive.RadioItem
|
||||
bind:ref
|
||||
data-slot="dropdown-menu-radio-item"
|
||||
class={cn(
|
||||
"focus:bg-accent focus:text-accent-foreground relative flex cursor-default items-center gap-2 rounded-sm py-1.5 pr-2 pl-8 text-sm outline-hidden select-none data-[disabled]:pointer-events-none data-[disabled]:opacity-50 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4",
|
||||
className
|
||||
)}
|
||||
{...restProps}
|
||||
bind:ref
|
||||
data-slot="dropdown-menu-radio-item"
|
||||
class={cn(
|
||||
"focus:bg-accent focus:text-accent-foreground relative flex cursor-default items-center gap-2 rounded-sm py-1.5 pr-2 pl-8 text-sm outline-hidden select-none data-[disabled]:pointer-events-none data-[disabled]:opacity-50 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4",
|
||||
className,
|
||||
)}
|
||||
{...restProps}
|
||||
>
|
||||
{#snippet children({ checked })}
|
||||
<span class="pointer-events-none absolute left-2 flex size-3.5 items-center justify-center">
|
||||
{#if checked}
|
||||
<CircleIcon class="size-2 fill-current" />
|
||||
{/if}
|
||||
</span>
|
||||
{@render childrenProp?.({ checked })}
|
||||
{/snippet}
|
||||
{#snippet children({ checked })}
|
||||
<span class="pointer-events-none absolute left-2 flex size-3.5 items-center justify-center">
|
||||
{#if checked}
|
||||
<CircleIcon class="size-2 fill-current" />
|
||||
{/if}
|
||||
</span>
|
||||
{@render childrenProp?.({ checked })}
|
||||
{/snippet}
|
||||
</DropdownMenuPrimitive.RadioItem>
|
||||
|
||||
@@ -1,17 +1,17 @@
|
||||
<script lang="ts">
|
||||
import { DropdownMenu as DropdownMenuPrimitive } from "bits-ui";
|
||||
import { cn } from "$lib/utils.js";
|
||||
import { DropdownMenu as DropdownMenuPrimitive } from "bits-ui";
|
||||
import { cn } from "$lib/utils.js";
|
||||
|
||||
let {
|
||||
ref = $bindable(null),
|
||||
class: className,
|
||||
...restProps
|
||||
}: DropdownMenuPrimitive.SeparatorProps = $props();
|
||||
let {
|
||||
ref = $bindable(null),
|
||||
class: className,
|
||||
...restProps
|
||||
}: DropdownMenuPrimitive.SeparatorProps = $props();
|
||||
</script>
|
||||
|
||||
<DropdownMenuPrimitive.Separator
|
||||
bind:ref
|
||||
data-slot="dropdown-menu-separator"
|
||||
class={cn("bg-border -mx-1 my-1 h-px", className)}
|
||||
{...restProps}
|
||||
bind:ref
|
||||
data-slot="dropdown-menu-separator"
|
||||
class={cn("bg-border -mx-1 my-1 h-px", className)}
|
||||
{...restProps}
|
||||
/>
|
||||
|
||||
@@ -1,20 +1,20 @@
|
||||
<script lang="ts">
|
||||
import type { HTMLAttributes } from "svelte/elements";
|
||||
import { cn, type WithElementRef } from "$lib/utils.js";
|
||||
import type { HTMLAttributes } from "svelte/elements";
|
||||
import { cn, type WithElementRef } from "$lib/utils.js";
|
||||
|
||||
let {
|
||||
ref = $bindable(null),
|
||||
class: className,
|
||||
children,
|
||||
...restProps
|
||||
}: WithElementRef<HTMLAttributes<HTMLSpanElement>> = $props();
|
||||
let {
|
||||
ref = $bindable(null),
|
||||
class: className,
|
||||
children,
|
||||
...restProps
|
||||
}: WithElementRef<HTMLAttributes<HTMLSpanElement>> = $props();
|
||||
</script>
|
||||
|
||||
<span
|
||||
bind:this={ref}
|
||||
data-slot="dropdown-menu-shortcut"
|
||||
class={cn("text-muted-foreground ml-auto text-xs tracking-widest", className)}
|
||||
{...restProps}
|
||||
bind:this={ref}
|
||||
data-slot="dropdown-menu-shortcut"
|
||||
class={cn("text-muted-foreground ml-auto text-xs tracking-widest", className)}
|
||||
{...restProps}
|
||||
>
|
||||
{@render children?.()}
|
||||
{@render children?.()}
|
||||
</span>
|
||||
|
||||
@@ -1,20 +1,20 @@
|
||||
<script lang="ts">
|
||||
import { DropdownMenu as DropdownMenuPrimitive } from "bits-ui";
|
||||
import { cn } from "$lib/utils.js";
|
||||
import { DropdownMenu as DropdownMenuPrimitive } from "bits-ui";
|
||||
import { cn } from "$lib/utils.js";
|
||||
|
||||
let {
|
||||
ref = $bindable(null),
|
||||
class: className,
|
||||
...restProps
|
||||
}: DropdownMenuPrimitive.SubContentProps = $props();
|
||||
let {
|
||||
ref = $bindable(null),
|
||||
class: className,
|
||||
...restProps
|
||||
}: DropdownMenuPrimitive.SubContentProps = $props();
|
||||
</script>
|
||||
|
||||
<DropdownMenuPrimitive.SubContent
|
||||
bind:ref
|
||||
data-slot="dropdown-menu-sub-content"
|
||||
class={cn(
|
||||
"bg-popover text-popover-foreground data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 z-50 min-w-[8rem] origin-(--bits-dropdown-menu-content-transform-origin) overflow-hidden rounded-md border p-1 shadow-lg",
|
||||
className
|
||||
)}
|
||||
{...restProps}
|
||||
bind:ref
|
||||
data-slot="dropdown-menu-sub-content"
|
||||
class={cn(
|
||||
"bg-popover text-popover-foreground data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 z-50 min-w-[8rem] origin-(--bits-dropdown-menu-content-transform-origin) overflow-hidden rounded-md border p-1 shadow-lg",
|
||||
className,
|
||||
)}
|
||||
{...restProps}
|
||||
/>
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user