Change prettier settings (#65)

Co-authored-by: Karl Ludwig Weise <ludwig@ludwigweise.de>
This commit is contained in:
Karl Ludwig Weise
2025-09-01 17:47:46 +02:00
committed by GitHub
co-authored by Karl Ludwig Weise
parent 5d73b02ac2
commit bf610cac2b
323 changed files with 23351 additions and 23343 deletions
+2 -1
View File
@@ -9,4 +9,5 @@ bun.lock
bun.lockb
# Build Artifacts
src/i18n/**
src/i18n/**
project.inlang/**
+13 -13
View File
@@ -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
View File
@@ -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"
}
+1 -1
View File
@@ -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
+1 -1
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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,
});
+8 -8
View File
@@ -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
View File
@@ -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
View File
@@ -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,
},
},
},
);
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
+18 -18
View File
@@ -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
View File
@@ -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"
}
+5 -5
View File
@@ -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
View File
@@ -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);
}
}
+12 -12
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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}
/>
+13 -13
View File
@@ -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}
/>
+7 -7
View File
@@ -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>
+15 -15
View File
@@ -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,
};
+75 -75
View File
@@ -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}
+12 -12
View File
@@ -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,
};
+13 -13
View File
@@ -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>
+13 -13
View File
@@ -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>
+16 -16
View File
@@ -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>
+13 -13
View File
@@ -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>
+16 -16
View File
@@ -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>
+15 -15
View File
@@ -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} />
+7 -7
View File
@@ -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,
};
+80 -80
View File
@@ -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>
+16 -16
View File
@@ -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}
/>
+23 -23
View File
@@ -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} />
+21 -21
View File
@@ -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