chore: init
This commit is contained in:
34
.gitignore
vendored
Normal file
34
.gitignore
vendored
Normal file
@ -0,0 +1,34 @@
|
|||||||
|
# dependencies (bun install)
|
||||||
|
node_modules
|
||||||
|
|
||||||
|
# output
|
||||||
|
out
|
||||||
|
dist
|
||||||
|
*.tgz
|
||||||
|
|
||||||
|
# code coverage
|
||||||
|
coverage
|
||||||
|
*.lcov
|
||||||
|
|
||||||
|
# logs
|
||||||
|
logs
|
||||||
|
_.log
|
||||||
|
report.[0-9]_.[0-9]_.[0-9]_.[0-9]_.json
|
||||||
|
|
||||||
|
# dotenv environment variable files
|
||||||
|
.env
|
||||||
|
.env.development.local
|
||||||
|
.env.test.local
|
||||||
|
.env.production.local
|
||||||
|
.env.local
|
||||||
|
|
||||||
|
# caches
|
||||||
|
.eslintcache
|
||||||
|
.cache
|
||||||
|
*.tsbuildinfo
|
||||||
|
|
||||||
|
# IntelliJ based IDEs
|
||||||
|
.idea
|
||||||
|
|
||||||
|
# Finder (MacOS) folder config
|
||||||
|
.DS_Store
|
||||||
38
Dockerfile
Normal file
38
Dockerfile
Normal file
@ -0,0 +1,38 @@
|
|||||||
|
# use the official Bun image
|
||||||
|
# see all versions at https://hub.docker.com/r/oven/bun/tags
|
||||||
|
FROM oven/bun:1 AS base
|
||||||
|
WORKDIR /usr/src/app
|
||||||
|
|
||||||
|
# install dependencies into temp directory
|
||||||
|
# this will cache them and speed up future builds
|
||||||
|
FROM base AS install
|
||||||
|
RUN mkdir -p /temp/dev
|
||||||
|
COPY package.json bun.lock /temp/dev/
|
||||||
|
RUN cd /temp/dev && bun install --frozen-lockfile
|
||||||
|
|
||||||
|
# install with --production (exclude devDependencies)
|
||||||
|
RUN mkdir -p /temp/prod
|
||||||
|
COPY package.json bun.lock /temp/prod/
|
||||||
|
RUN cd /temp/prod && bun install --frozen-lockfile --production
|
||||||
|
|
||||||
|
# copy node_modules from temp directory
|
||||||
|
# then copy all (non-ignored) project files into the image
|
||||||
|
FROM base AS prerelease
|
||||||
|
COPY --from=install /temp/dev/node_modules node_modules
|
||||||
|
COPY . .
|
||||||
|
|
||||||
|
# [optional] tests & build
|
||||||
|
ENV NODE_ENV=production
|
||||||
|
RUN bun test
|
||||||
|
RUN bun run build
|
||||||
|
|
||||||
|
# copy production dependencies and source code into final image
|
||||||
|
FROM base AS release
|
||||||
|
COPY --from=install /temp/prod/node_modules node_modules
|
||||||
|
COPY --from=prerelease /usr/src/app/*.ts ./
|
||||||
|
COPY --from=prerelease /usr/src/app/package.json .
|
||||||
|
|
||||||
|
# run the app
|
||||||
|
USER bun
|
||||||
|
EXPOSE 3000/tcp
|
||||||
|
ENTRYPOINT [ "bun", "run", "index.ts" ]
|
||||||
15
README.md
Normal file
15
README.md
Normal file
@ -0,0 +1,15 @@
|
|||||||
|
# nextup
|
||||||
|
|
||||||
|
To install dependencies:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
bun install
|
||||||
|
```
|
||||||
|
|
||||||
|
To run:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
bun run index.ts
|
||||||
|
```
|
||||||
|
|
||||||
|
This project was created using `bun init` in bun v1.2.10. [Bun](https://bun.sh) is a fast all-in-one JavaScript runtime.
|
||||||
46
bun.lock
Normal file
46
bun.lock
Normal file
@ -0,0 +1,46 @@
|
|||||||
|
{
|
||||||
|
"lockfileVersion": 1,
|
||||||
|
"workspaces": {
|
||||||
|
"": {
|
||||||
|
"name": "nextup",
|
||||||
|
"dependencies": {
|
||||||
|
"redis": "^5.8.2",
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"@types/bun": "latest",
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"typescript": "^5",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"packages": {
|
||||||
|
"@redis/bloom": ["@redis/bloom@5.8.2", "", { "peerDependencies": { "@redis/client": "^5.8.2" } }, "sha512-855DR0ChetZLarblio5eM0yLwxA9Dqq50t8StXKp5bAtLT0G+rZ+eRzzqxl37sPqQKjUudSYypz55o6nNhbz0A=="],
|
||||||
|
|
||||||
|
"@redis/client": ["@redis/client@5.8.2", "", { "dependencies": { "cluster-key-slot": "1.1.2" } }, "sha512-WtMScno3+eBpTac1Uav2zugXEoXqaU23YznwvFgkPwBQVwEHTDgOG7uEAObtZ/Nyn8SmAMbqkEubJaMOvnqdsQ=="],
|
||||||
|
|
||||||
|
"@redis/json": ["@redis/json@5.8.2", "", { "peerDependencies": { "@redis/client": "^5.8.2" } }, "sha512-uxpVfas3I0LccBX9rIfDgJ0dBrUa3+0Gc8sEwmQQH0vHi7C1Rx1Qn8Nv1QWz5bohoeIXMICFZRcyDONvum2l/w=="],
|
||||||
|
|
||||||
|
"@redis/search": ["@redis/search@5.8.2", "", { "peerDependencies": { "@redis/client": "^5.8.2" } }, "sha512-cNv7HlgayavCBXqPXgaS97DRPVWFznuzsAmmuemi2TMCx5scwLiP50TeZvUS06h/MG96YNPe6A0Zt57yayfxwA=="],
|
||||||
|
|
||||||
|
"@redis/time-series": ["@redis/time-series@5.8.2", "", { "peerDependencies": { "@redis/client": "^5.8.2" } }, "sha512-g2NlHM07fK8H4k+613NBsk3y70R2JIM2dPMSkhIjl2Z17SYvaYKdusz85d7VYOrZBWtDrHV/WD2E3vGu+zni8A=="],
|
||||||
|
|
||||||
|
"@types/bun": ["@types/bun@1.2.22", "", { "dependencies": { "bun-types": "1.2.22" } }, "sha512-5A/KrKos2ZcN0c6ljRSOa1fYIyCKhZfIVYeuyb4snnvomnpFqC0tTsEkdqNxbAgExV384OETQ//WAjl3XbYqQA=="],
|
||||||
|
|
||||||
|
"@types/node": ["@types/node@24.5.2", "", { "dependencies": { "undici-types": "~7.12.0" } }, "sha512-FYxk1I7wPv3K2XBaoyH2cTnocQEu8AOZ60hPbsyukMPLv5/5qr7V1i8PLHdl6Zf87I+xZXFvPCXYjiTFq+YSDQ=="],
|
||||||
|
|
||||||
|
"@types/react": ["@types/react@19.1.13", "", { "dependencies": { "csstype": "^3.0.2" } }, "sha512-hHkbU/eoO3EG5/MZkuFSKmYqPbSVk5byPFa3e7y/8TybHiLMACgI8seVYlicwk7H5K/rI2px9xrQp/C+AUDTiQ=="],
|
||||||
|
|
||||||
|
"bun-types": ["bun-types@1.2.22", "", { "dependencies": { "@types/node": "*" }, "peerDependencies": { "@types/react": "^19" } }, "sha512-hwaAu8tct/Zn6Zft4U9BsZcXkYomzpHJX28ofvx7k0Zz2HNz54n1n+tDgxoWFGB4PcFvJXJQloPhaV2eP3Q6EA=="],
|
||||||
|
|
||||||
|
"cluster-key-slot": ["cluster-key-slot@1.1.2", "", {}, "sha512-RMr0FhtfXemyinomL4hrWcYJxmX6deFdCxpJzhDttxgO1+bcCnkk+9drydLVDmAMG7NE6aN/fl4F7ucU/90gAA=="],
|
||||||
|
|
||||||
|
"csstype": ["csstype@3.1.3", "", {}, "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw=="],
|
||||||
|
|
||||||
|
"redis": ["redis@5.8.2", "", { "dependencies": { "@redis/bloom": "5.8.2", "@redis/client": "5.8.2", "@redis/json": "5.8.2", "@redis/search": "5.8.2", "@redis/time-series": "5.8.2" } }, "sha512-31vunZj07++Y1vcFGcnNWEf5jPoTkGARgfWI4+Tk55vdwHxhAvug8VEtW7Cx+/h47NuJTEg/JL77zAwC6E0OeA=="],
|
||||||
|
|
||||||
|
"typescript": ["typescript@5.9.2", "", { "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" } }, "sha512-CWBzXQrc/qOkhidw1OzBTQuYRbfyxDXJMVJ1XNwUHGROVmuaeiEm3OslpZ1RV96d7SKKjZKrSJu3+t/xlw3R9A=="],
|
||||||
|
|
||||||
|
"undici-types": ["undici-types@7.12.0", "", {}, "sha512-goOacqME2GYyOZZfb5Lgtu+1IDmAlAEu5xnD3+xTzS10hT0vzpf0SPjkXwAw9Jm+4n/mQGDP3LO8CPbYROeBfQ=="],
|
||||||
|
}
|
||||||
|
}
|
||||||
129
index.ts
Normal file
129
index.ts
Normal file
@ -0,0 +1,129 @@
|
|||||||
|
import type { ServerWebSocket } from "bun";
|
||||||
|
import { callTicket, completeTicket, getCurrentTicket, getDisplayTickets, getLogEntries, getTicket, noShowTicket } from "./redis";
|
||||||
|
|
||||||
|
let IP_TO_ROOMS: Record<string, string> = {};
|
||||||
|
|
||||||
|
const iprooms = await Bun.file("./iprooms.csv").text();
|
||||||
|
const lines = iprooms.split("\n");
|
||||||
|
for (const line of lines.slice(1)) {
|
||||||
|
const [ip, room] = line.split(",");
|
||||||
|
if (ip && room) {
|
||||||
|
IP_TO_ROOMS[ip.trim()] = room.trim();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export let displaySockets: ServerWebSocket<unknown>[] = [];
|
||||||
|
|
||||||
|
export async function broadcastDisplayUpdate() {
|
||||||
|
for (const ws of displaySockets) {
|
||||||
|
if (ws.readyState === WebSocket.OPEN) {
|
||||||
|
ws.send(JSON.stringify({ type: "display", tickets: await getDisplayTickets() }));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Bun.serve({
|
||||||
|
fetch(req, server) {
|
||||||
|
// upgrade the request to a WebSocket
|
||||||
|
if (server.upgrade(req)) {
|
||||||
|
return; // do not return a Response
|
||||||
|
}
|
||||||
|
return new Response("Upgrade failed", { status: 500 });
|
||||||
|
},
|
||||||
|
websocket: {
|
||||||
|
async message(ws, message) {
|
||||||
|
console.log("Received message:", message);
|
||||||
|
const data = JSON.parse(message.toString());
|
||||||
|
if (data.type === "hello") {}
|
||||||
|
else if (data.type === "my-room") {
|
||||||
|
console.log("Client requested room for IP:", ws.remoteAddress);
|
||||||
|
const room = IP_TO_ROOMS[ws.remoteAddress];
|
||||||
|
if (!room) {
|
||||||
|
return void ws.send(JSON.stringify({ type: "error", code: -1, message: "No room" }));
|
||||||
|
}
|
||||||
|
ws.send(JSON.stringify({ type: "my-room", room }) );
|
||||||
|
// } else if (data.type === "create-ticket") {
|
||||||
|
// const ticket = await getTicket(data.ticket);
|
||||||
|
// if(!ticket || ticket.status == "completed" || ticket.status == "no-show") {
|
||||||
|
// await createTicket(data.ticket);
|
||||||
|
// ws.send(JSON.stringify({ type: "create-ticket", status: "created", ticket, num: data.ticket }) );
|
||||||
|
// return;
|
||||||
|
// }
|
||||||
|
// ws.send(JSON.stringify({ type: "error", code: -2, message: "Ticket already exists and is not completed or no-show", ticket, num: data.ticket }));
|
||||||
|
} else if (data.type === "call-ticket") {
|
||||||
|
// const ticket = await getTicket(data.ticket);
|
||||||
|
// if(!ticket || ticket.status == "completed" || ticket.status == "no-show") {
|
||||||
|
// return void ws.send(JSON.stringify({ type: "error", code: -3, message: "Ticket does not exist or is completed or no-show", ticket, num: data.ticket, room: data.room }) );
|
||||||
|
// }
|
||||||
|
await callTicket(data.ticket, data.room);
|
||||||
|
ws.send(JSON.stringify({ type: "call-ticket", status: "called", ticket: await getTicket(data.ticket), num: data.ticket, room: data.room }) );
|
||||||
|
await broadcastDisplayUpdate();
|
||||||
|
} else if (data.type === "complete-ticket") {
|
||||||
|
const ticket = await getTicket(data.ticket);
|
||||||
|
if(ticket?.status !== "called") {
|
||||||
|
await completeTicket(data.ticket);
|
||||||
|
await broadcastDisplayUpdate();
|
||||||
|
return void ws.send(JSON.stringify({ type: "complete-ticket", status: "skipped", ticket, num: data.ticket }) );
|
||||||
|
}
|
||||||
|
// // Check how long since called
|
||||||
|
// const calledDate = new Date(ticket.calledDate || "1970-01-01T00:00:00.000Z");
|
||||||
|
// const now = new Date();
|
||||||
|
// const diff = now.getTime() - calledDate.getTime();
|
||||||
|
// if(diff < 5 * 60 * 1000) { // 5 minutes
|
||||||
|
// // Mark as no-show
|
||||||
|
// await noShowTicket(data.ticket);
|
||||||
|
// await broadcastDisplayUpdate();
|
||||||
|
// return void ws.send(JSON.stringify({ type: "complete-ticket", status: "no-show", ticket, num: data.ticket }));
|
||||||
|
// }
|
||||||
|
|
||||||
|
await completeTicket(data.ticket);
|
||||||
|
ws.send(JSON.stringify({ type: "complete-ticket", status: "completed", ticket, num: data.ticket }));
|
||||||
|
await broadcastDisplayUpdate();
|
||||||
|
} else if (data.type === "no-show") {
|
||||||
|
const ticket = await getTicket(data.ticket);
|
||||||
|
if(!ticket) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
// // Check how long since called
|
||||||
|
// const calledDate = new Date(ticket.calledDate || "1970-01-01T00:00:00.000Z");
|
||||||
|
// const now = new Date();
|
||||||
|
// const diff = now.getTime() - calledDate.getTime();
|
||||||
|
// if(diff < 5 * 60 * 1000) { // 5 minutes
|
||||||
|
// // Mark as no-show
|
||||||
|
// await noShowTicket(data.ticket);
|
||||||
|
// await broadcastDisplayUpdate();
|
||||||
|
// return void ws.send(JSON.stringify({ type: "complete-ticket", status: "no-show", ticket, num: data.ticket }));
|
||||||
|
// }
|
||||||
|
|
||||||
|
await noShowTicket(data.ticket);
|
||||||
|
ws.send(JSON.stringify({ type: "complete-ticket", status: "no-show", ticket, num: data.ticket }));
|
||||||
|
await broadcastDisplayUpdate();
|
||||||
|
} else if (data.type === "get-log") {
|
||||||
|
const log = await getLogEntries();
|
||||||
|
ws.send(JSON.stringify({ type: "log", log }));
|
||||||
|
} else if (data.type === "get-ticket") {
|
||||||
|
const ticket = await getTicket(data.ticket);
|
||||||
|
ws.send(JSON.stringify({ type: "ticket", ticket, num: data.ticket }));
|
||||||
|
} else if (data.type === "get-current-ticket") {
|
||||||
|
const ticket = await getCurrentTicket(data.room);
|
||||||
|
ws.send(JSON.stringify({ type: "current-ticket", ...ticket, room: data.room }));
|
||||||
|
} else if (data.type == "display") {
|
||||||
|
if(!displaySockets.includes(ws)) {
|
||||||
|
displaySockets.push(ws);
|
||||||
|
console.log("Added display socket. Total:", displaySockets.length);
|
||||||
|
ws.send(JSON.stringify({ type: "display", tickets: await getDisplayTickets() }));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}, // a message is received
|
||||||
|
open(ws) {
|
||||||
|
console.log("WebSocket opened");
|
||||||
|
ws.send(JSON.stringify({ type: "hello" }));
|
||||||
|
}, // a socket is opened
|
||||||
|
close(ws, code, message) {
|
||||||
|
console.log("WebSocket closed");
|
||||||
|
}, // a socket is closed
|
||||||
|
drain(ws) {
|
||||||
|
|
||||||
|
}, // the socket is ready to receive more data
|
||||||
|
}
|
||||||
|
});
|
||||||
2
iprooms.csv
Normal file
2
iprooms.csv
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
IP,Rooms
|
||||||
|
::ffff:127.0.0.1,1
|
||||||
|
15
package.json
Normal file
15
package.json
Normal file
@ -0,0 +1,15 @@
|
|||||||
|
{
|
||||||
|
"name": "nextup",
|
||||||
|
"module": "index.ts",
|
||||||
|
"type": "module",
|
||||||
|
"private": true,
|
||||||
|
"devDependencies": {
|
||||||
|
"@types/bun": "latest"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"typescript": "^5"
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"redis": "^5.8.2"
|
||||||
|
}
|
||||||
|
}
|
||||||
102
redis.ts
Normal file
102
redis.ts
Normal file
@ -0,0 +1,102 @@
|
|||||||
|
import { createClient } from "redis";
|
||||||
|
|
||||||
|
type LogAction = "called" | "completed" | "no-show";
|
||||||
|
type TicketStatus = "called" | "completed" | "no-show";
|
||||||
|
|
||||||
|
interface LogEntry {
|
||||||
|
ticket: string;
|
||||||
|
time: string;
|
||||||
|
action: LogAction;
|
||||||
|
room: string | null;
|
||||||
|
};
|
||||||
|
|
||||||
|
interface Ticket {
|
||||||
|
status: TicketStatus;
|
||||||
|
created: string;
|
||||||
|
room: string | null;
|
||||||
|
calledDate: string | null;
|
||||||
|
}
|
||||||
|
|
||||||
|
const redis = await createClient({
|
||||||
|
url: process.env.DATABASE_URL || "redis://localhost:6379"
|
||||||
|
}).connect();
|
||||||
|
|
||||||
|
export function logAction(entry: LogEntry) {
|
||||||
|
redis.lPush("log", JSON.stringify(entry));
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function getLogEntries(): Promise<LogEntry[]> {
|
||||||
|
const entries = await redis.lRange("log", 0, -1);
|
||||||
|
return entries.map(entry => JSON.parse(entry));
|
||||||
|
}
|
||||||
|
|
||||||
|
// export async function createTicket(ticket: string) {
|
||||||
|
// // redisjson ticket:{ticket} = { status: "waiting", created: now, room: null, calledDate: null }
|
||||||
|
// await redis.json.set(`ticket:${ticket}`, "$", {
|
||||||
|
// status: "waiting" as TicketStatus,
|
||||||
|
// created: new Date().toISOString(),
|
||||||
|
// room: null,
|
||||||
|
// calledDate: null
|
||||||
|
// });
|
||||||
|
// logAction({ ticket, time: new Date().toISOString(), action: "entered", room: null });
|
||||||
|
// }
|
||||||
|
|
||||||
|
export async function callTicket(ticket: string, room: string) {
|
||||||
|
// await redis.json.set(`ticket:${ticket}`, "$.status", "called");
|
||||||
|
// await redis.json.set(`ticket:${ticket}`, "$.room", room);
|
||||||
|
// await redis.json.set(`ticket:${ticket}`, "$.calledDate", new Date().toISOString());
|
||||||
|
await redis.json.set(`ticket:${ticket}`, "$", {
|
||||||
|
status: "called" as TicketStatus,
|
||||||
|
created: new Date().toISOString(),
|
||||||
|
room,
|
||||||
|
calledDate: new Date().toISOString()
|
||||||
|
});
|
||||||
|
logAction({ ticket, time: new Date().toISOString(), action: "called", room });
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function completeTicket(ticket: string) {
|
||||||
|
await redis.json.set(`ticket:${ticket}`, "$.status", "completed");
|
||||||
|
const room = await redis.json.get(`ticket:${ticket}`, { path: "$.room" }) as string | null;
|
||||||
|
logAction({ ticket, time: new Date().toISOString(), action: "completed", room });
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function noShowTicket(ticket: string) {
|
||||||
|
await redis.json.set(`ticket:${ticket}`, "$.status", "no-show");
|
||||||
|
const room = await redis.json.get(`ticket:${ticket}`, { path: "$.room" }) as string | null;
|
||||||
|
logAction({ ticket, time: new Date().toISOString(), action: "no-show", room });
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function getCurrentTicket(room: string): Promise<{num: string, ticket: Ticket} | null> {
|
||||||
|
const tickets = await redis.keys("ticket:*");
|
||||||
|
for (const key of tickets) {
|
||||||
|
const ticket = key.split(":")[1]!;
|
||||||
|
const ticketData = await getTicket(ticket);
|
||||||
|
if (ticketData?.status === "called" && ticketData.room === room) {
|
||||||
|
return { num: ticket, ticket: ticketData };
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function getDisplayTickets(): Promise<{num: string, ticket: Ticket}[]> {
|
||||||
|
const result: {num: string, ticket: Ticket}[] = [];
|
||||||
|
const tickets = await redis.keys("ticket:*");
|
||||||
|
for (const key of tickets) {
|
||||||
|
const ticket = key.split(":")[1]!;
|
||||||
|
const ticketData = await getTicket(ticket);
|
||||||
|
if (ticketData?.status === "called" || ticketData?.status === "no-show") {
|
||||||
|
result.push({ num: ticket, ticket: ticketData });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Sort by calledDate
|
||||||
|
result.sort((a, b) => {
|
||||||
|
const dateA = new Date(a.ticket.calledDate || "1970-01-01T00:00:00.000Z");
|
||||||
|
const dateB = new Date(b.ticket.calledDate || "1970-01-01T00:00:00.000Z");
|
||||||
|
return dateA.getTime() - dateB.getTime();
|
||||||
|
});
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function getTicket(ticket: string): Promise<Ticket | null> {
|
||||||
|
return await redis.json.get(`ticket:${ticket}`) as Ticket | null;
|
||||||
|
}
|
||||||
28
tsconfig.json
Normal file
28
tsconfig.json
Normal file
@ -0,0 +1,28 @@
|
|||||||
|
{
|
||||||
|
"compilerOptions": {
|
||||||
|
// Environment setup & latest features
|
||||||
|
"lib": ["ESNext"],
|
||||||
|
"target": "ESNext",
|
||||||
|
"module": "ESNext",
|
||||||
|
"moduleDetection": "force",
|
||||||
|
"jsx": "react-jsx",
|
||||||
|
"allowJs": true,
|
||||||
|
|
||||||
|
// Bundler mode
|
||||||
|
"moduleResolution": "bundler",
|
||||||
|
"allowImportingTsExtensions": true,
|
||||||
|
"verbatimModuleSyntax": true,
|
||||||
|
"noEmit": true,
|
||||||
|
|
||||||
|
// Best practices
|
||||||
|
"strict": true,
|
||||||
|
"skipLibCheck": true,
|
||||||
|
"noFallthroughCasesInSwitch": true,
|
||||||
|
"noUncheckedIndexedAccess": true,
|
||||||
|
|
||||||
|
// Some stricter flags (disabled by default)
|
||||||
|
"noUnusedLocals": false,
|
||||||
|
"noUnusedParameters": false,
|
||||||
|
"noPropertyAccessFromIndexSignature": false
|
||||||
|
}
|
||||||
|
}
|
||||||
23
web/.gitignore
vendored
Normal file
23
web/.gitignore
vendored
Normal file
@ -0,0 +1,23 @@
|
|||||||
|
node_modules
|
||||||
|
|
||||||
|
# Output
|
||||||
|
.output
|
||||||
|
.vercel
|
||||||
|
.netlify
|
||||||
|
.wrangler
|
||||||
|
/.svelte-kit
|
||||||
|
/build
|
||||||
|
|
||||||
|
# OS
|
||||||
|
.DS_Store
|
||||||
|
Thumbs.db
|
||||||
|
|
||||||
|
# Env
|
||||||
|
.env
|
||||||
|
.env.*
|
||||||
|
!.env.example
|
||||||
|
!.env.test
|
||||||
|
|
||||||
|
# Vite
|
||||||
|
vite.config.js.timestamp-*
|
||||||
|
vite.config.ts.timestamp-*
|
||||||
1
web/.npmrc
Normal file
1
web/.npmrc
Normal file
@ -0,0 +1 @@
|
|||||||
|
engine-strict=true
|
||||||
16
web/Dockerfile
Normal file
16
web/Dockerfile
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
FROM node:22-alpine AS builder
|
||||||
|
WORKDIR /app
|
||||||
|
COPY package*.json ./
|
||||||
|
RUN npm ci
|
||||||
|
COPY . .
|
||||||
|
RUN npm run build
|
||||||
|
RUN npm prune --production
|
||||||
|
|
||||||
|
FROM node:22-alpine
|
||||||
|
WORKDIR /app
|
||||||
|
COPY --from=builder /app/build build/
|
||||||
|
COPY --from=builder /app/node_modules node_modules/
|
||||||
|
COPY package.json .
|
||||||
|
EXPOSE 3000
|
||||||
|
ENV NODE_ENV=production
|
||||||
|
CMD [ "node", "build" ]
|
||||||
38
web/README.md
Normal file
38
web/README.md
Normal file
@ -0,0 +1,38 @@
|
|||||||
|
# sv
|
||||||
|
|
||||||
|
Everything you need to build a Svelte project, powered by [`sv`](https://github.com/sveltejs/cli).
|
||||||
|
|
||||||
|
## Creating a project
|
||||||
|
|
||||||
|
If you're seeing this, you've probably already done this step. Congrats!
|
||||||
|
|
||||||
|
```sh
|
||||||
|
# create a new project in the current directory
|
||||||
|
npx sv create
|
||||||
|
|
||||||
|
# create a new project in my-app
|
||||||
|
npx sv create my-app
|
||||||
|
```
|
||||||
|
|
||||||
|
## Developing
|
||||||
|
|
||||||
|
Once you've created a project and installed dependencies with `npm install` (or `pnpm install` or `yarn`), start a development server:
|
||||||
|
|
||||||
|
```sh
|
||||||
|
npm run dev
|
||||||
|
|
||||||
|
# or start the server and open the app in a new browser tab
|
||||||
|
npm run dev -- --open
|
||||||
|
```
|
||||||
|
|
||||||
|
## Building
|
||||||
|
|
||||||
|
To create a production version of your app:
|
||||||
|
|
||||||
|
```sh
|
||||||
|
npm run build
|
||||||
|
```
|
||||||
|
|
||||||
|
You can preview the production build with `npm run preview`.
|
||||||
|
|
||||||
|
> To deploy your app, you may need to install an [adapter](https://svelte.dev/docs/kit/adapters) for your target environment.
|
||||||
398
web/bun.lock
Normal file
398
web/bun.lock
Normal file
@ -0,0 +1,398 @@
|
|||||||
|
{
|
||||||
|
"lockfileVersion": 1,
|
||||||
|
"workspaces": {
|
||||||
|
"": {
|
||||||
|
"name": "web",
|
||||||
|
"devDependencies": {
|
||||||
|
"@internationalized/date": "^3.8.1",
|
||||||
|
"@lucide/svelte": "^0.544.0",
|
||||||
|
"@sveltejs/adapter-auto": "^6.0.0",
|
||||||
|
"@sveltejs/adapter-node": "^5.3.2",
|
||||||
|
"@sveltejs/kit": "^2.22.0",
|
||||||
|
"@sveltejs/vite-plugin-svelte": "^6.0.0",
|
||||||
|
"@tailwindcss/vite": "^4.0.0",
|
||||||
|
"bits-ui": "^2.11.0",
|
||||||
|
"clsx": "^2.1.1",
|
||||||
|
"mode-watcher": "^1.1.0",
|
||||||
|
"svelte": "^5.0.0",
|
||||||
|
"svelte-check": "^4.0.0",
|
||||||
|
"svelte-sonner": "^1.0.5",
|
||||||
|
"tailwind-merge": "^3.3.1",
|
||||||
|
"tailwind-variants": "^3.1.1",
|
||||||
|
"tailwindcss": "^4.0.0",
|
||||||
|
"tw-animate-css": "^1.4.0",
|
||||||
|
"typescript": "^5.0.0",
|
||||||
|
"vite": "^7.0.4",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"packages": {
|
||||||
|
"@esbuild/aix-ppc64": ["@esbuild/aix-ppc64@0.25.10", "", { "os": "aix", "cpu": "ppc64" }, "sha512-0NFWnA+7l41irNuaSVlLfgNT12caWJVLzp5eAVhZ0z1qpxbockccEt3s+149rE64VUI3Ml2zt8Nv5JVc4QXTsw=="],
|
||||||
|
|
||||||
|
"@esbuild/android-arm": ["@esbuild/android-arm@0.25.10", "", { "os": "android", "cpu": "arm" }, "sha512-dQAxF1dW1C3zpeCDc5KqIYuZ1tgAdRXNoZP7vkBIRtKZPYe2xVr/d3SkirklCHudW1B45tGiUlz2pUWDfbDD4w=="],
|
||||||
|
|
||||||
|
"@esbuild/android-arm64": ["@esbuild/android-arm64@0.25.10", "", { "os": "android", "cpu": "arm64" }, "sha512-LSQa7eDahypv/VO6WKohZGPSJDq5OVOo3UoFR1E4t4Gj1W7zEQMUhI+lo81H+DtB+kP+tDgBp+M4oNCwp6kffg=="],
|
||||||
|
|
||||||
|
"@esbuild/android-x64": ["@esbuild/android-x64@0.25.10", "", { "os": "android", "cpu": "x64" }, "sha512-MiC9CWdPrfhibcXwr39p9ha1x0lZJ9KaVfvzA0Wxwz9ETX4v5CHfF09bx935nHlhi+MxhA63dKRRQLiVgSUtEg=="],
|
||||||
|
|
||||||
|
"@esbuild/darwin-arm64": ["@esbuild/darwin-arm64@0.25.10", "", { "os": "darwin", "cpu": "arm64" }, "sha512-JC74bdXcQEpW9KkV326WpZZjLguSZ3DfS8wrrvPMHgQOIEIG/sPXEN/V8IssoJhbefLRcRqw6RQH2NnpdprtMA=="],
|
||||||
|
|
||||||
|
"@esbuild/darwin-x64": ["@esbuild/darwin-x64@0.25.10", "", { "os": "darwin", "cpu": "x64" }, "sha512-tguWg1olF6DGqzws97pKZ8G2L7Ig1vjDmGTwcTuYHbuU6TTjJe5FXbgs5C1BBzHbJ2bo1m3WkQDbWO2PvamRcg=="],
|
||||||
|
|
||||||
|
"@esbuild/freebsd-arm64": ["@esbuild/freebsd-arm64@0.25.10", "", { "os": "freebsd", "cpu": "arm64" }, "sha512-3ZioSQSg1HT2N05YxeJWYR+Libe3bREVSdWhEEgExWaDtyFbbXWb49QgPvFH8u03vUPX10JhJPcz7s9t9+boWg=="],
|
||||||
|
|
||||||
|
"@esbuild/freebsd-x64": ["@esbuild/freebsd-x64@0.25.10", "", { "os": "freebsd", "cpu": "x64" }, "sha512-LLgJfHJk014Aa4anGDbh8bmI5Lk+QidDmGzuC2D+vP7mv/GeSN+H39zOf7pN5N8p059FcOfs2bVlrRr4SK9WxA=="],
|
||||||
|
|
||||||
|
"@esbuild/linux-arm": ["@esbuild/linux-arm@0.25.10", "", { "os": "linux", "cpu": "arm" }, "sha512-oR31GtBTFYCqEBALI9r6WxoU/ZofZl962pouZRTEYECvNF/dtXKku8YXcJkhgK/beU+zedXfIzHijSRapJY3vg=="],
|
||||||
|
|
||||||
|
"@esbuild/linux-arm64": ["@esbuild/linux-arm64@0.25.10", "", { "os": "linux", "cpu": "arm64" }, "sha512-5luJWN6YKBsawd5f9i4+c+geYiVEw20FVW5x0v1kEMWNq8UctFjDiMATBxLvmmHA4bf7F6hTRaJgtghFr9iziQ=="],
|
||||||
|
|
||||||
|
"@esbuild/linux-ia32": ["@esbuild/linux-ia32@0.25.10", "", { "os": "linux", "cpu": "ia32" }, "sha512-NrSCx2Kim3EnnWgS4Txn0QGt0Xipoumb6z6sUtl5bOEZIVKhzfyp/Lyw4C1DIYvzeW/5mWYPBFJU3a/8Yr75DQ=="],
|
||||||
|
|
||||||
|
"@esbuild/linux-loong64": ["@esbuild/linux-loong64@0.25.10", "", { "os": "linux", "cpu": "none" }, "sha512-xoSphrd4AZda8+rUDDfD9J6FUMjrkTz8itpTITM4/xgerAZZcFW7Dv+sun7333IfKxGG8gAq+3NbfEMJfiY+Eg=="],
|
||||||
|
|
||||||
|
"@esbuild/linux-mips64el": ["@esbuild/linux-mips64el@0.25.10", "", { "os": "linux", "cpu": "none" }, "sha512-ab6eiuCwoMmYDyTnyptoKkVS3k8fy/1Uvq7Dj5czXI6DF2GqD2ToInBI0SHOp5/X1BdZ26RKc5+qjQNGRBelRA=="],
|
||||||
|
|
||||||
|
"@esbuild/linux-ppc64": ["@esbuild/linux-ppc64@0.25.10", "", { "os": "linux", "cpu": "ppc64" }, "sha512-NLinzzOgZQsGpsTkEbdJTCanwA5/wozN9dSgEl12haXJBzMTpssebuXR42bthOF3z7zXFWH1AmvWunUCkBE4EA=="],
|
||||||
|
|
||||||
|
"@esbuild/linux-riscv64": ["@esbuild/linux-riscv64@0.25.10", "", { "os": "linux", "cpu": "none" }, "sha512-FE557XdZDrtX8NMIeA8LBJX3dC2M8VGXwfrQWU7LB5SLOajfJIxmSdyL/gU1m64Zs9CBKvm4UAuBp5aJ8OgnrA=="],
|
||||||
|
|
||||||
|
"@esbuild/linux-s390x": ["@esbuild/linux-s390x@0.25.10", "", { "os": "linux", "cpu": "s390x" }, "sha512-3BBSbgzuB9ajLoVZk0mGu+EHlBwkusRmeNYdqmznmMc9zGASFjSsxgkNsqmXugpPk00gJ0JNKh/97nxmjctdew=="],
|
||||||
|
|
||||||
|
"@esbuild/linux-x64": ["@esbuild/linux-x64@0.25.10", "", { "os": "linux", "cpu": "x64" }, "sha512-QSX81KhFoZGwenVyPoberggdW1nrQZSvfVDAIUXr3WqLRZGZqWk/P4T8p2SP+de2Sr5HPcvjhcJzEiulKgnxtA=="],
|
||||||
|
|
||||||
|
"@esbuild/netbsd-arm64": ["@esbuild/netbsd-arm64@0.25.10", "", { "os": "none", "cpu": "arm64" }, "sha512-AKQM3gfYfSW8XRk8DdMCzaLUFB15dTrZfnX8WXQoOUpUBQ+NaAFCP1kPS/ykbbGYz7rxn0WS48/81l9hFl3u4A=="],
|
||||||
|
|
||||||
|
"@esbuild/netbsd-x64": ["@esbuild/netbsd-x64@0.25.10", "", { "os": "none", "cpu": "x64" }, "sha512-7RTytDPGU6fek/hWuN9qQpeGPBZFfB4zZgcz2VK2Z5VpdUxEI8JKYsg3JfO0n/Z1E/6l05n0unDCNc4HnhQGig=="],
|
||||||
|
|
||||||
|
"@esbuild/openbsd-arm64": ["@esbuild/openbsd-arm64@0.25.10", "", { "os": "openbsd", "cpu": "arm64" }, "sha512-5Se0VM9Wtq797YFn+dLimf2Zx6McttsH2olUBsDml+lm0GOCRVebRWUvDtkY4BWYv/3NgzS8b/UM3jQNh5hYyw=="],
|
||||||
|
|
||||||
|
"@esbuild/openbsd-x64": ["@esbuild/openbsd-x64@0.25.10", "", { "os": "openbsd", "cpu": "x64" }, "sha512-XkA4frq1TLj4bEMB+2HnI0+4RnjbuGZfet2gs/LNs5Hc7D89ZQBHQ0gL2ND6Lzu1+QVkjp3x1gIcPKzRNP8bXw=="],
|
||||||
|
|
||||||
|
"@esbuild/openharmony-arm64": ["@esbuild/openharmony-arm64@0.25.10", "", { "os": "none", "cpu": "arm64" }, "sha512-AVTSBhTX8Y/Fz6OmIVBip9tJzZEUcY8WLh7I59+upa5/GPhh2/aM6bvOMQySspnCCHvFi79kMtdJS1w0DXAeag=="],
|
||||||
|
|
||||||
|
"@esbuild/sunos-x64": ["@esbuild/sunos-x64@0.25.10", "", { "os": "sunos", "cpu": "x64" }, "sha512-fswk3XT0Uf2pGJmOpDB7yknqhVkJQkAQOcW/ccVOtfx05LkbWOaRAtn5SaqXypeKQra1QaEa841PgrSL9ubSPQ=="],
|
||||||
|
|
||||||
|
"@esbuild/win32-arm64": ["@esbuild/win32-arm64@0.25.10", "", { "os": "win32", "cpu": "arm64" }, "sha512-ah+9b59KDTSfpaCg6VdJoOQvKjI33nTaQr4UluQwW7aEwZQsbMCfTmfEO4VyewOxx4RaDT/xCy9ra2GPWmO7Kw=="],
|
||||||
|
|
||||||
|
"@esbuild/win32-ia32": ["@esbuild/win32-ia32@0.25.10", "", { "os": "win32", "cpu": "ia32" }, "sha512-QHPDbKkrGO8/cz9LKVnJU22HOi4pxZnZhhA2HYHez5Pz4JeffhDjf85E57Oyco163GnzNCVkZK0b/n4Y0UHcSw=="],
|
||||||
|
|
||||||
|
"@esbuild/win32-x64": ["@esbuild/win32-x64@0.25.10", "", { "os": "win32", "cpu": "x64" }, "sha512-9KpxSVFCu0iK1owoez6aC/s/EdUQLDN3adTxGCqxMVhrPDj6bt5dbrHDXUuq+Bs2vATFBBrQS5vdQ/Ed2P+nbw=="],
|
||||||
|
|
||||||
|
"@floating-ui/core": ["@floating-ui/core@1.7.3", "", { "dependencies": { "@floating-ui/utils": "^0.2.10" } }, "sha512-sGnvb5dmrJaKEZ+LDIpguvdX3bDlEllmv4/ClQ9awcmCZrlx5jQyyMWFM5kBI+EyNOCDDiKk8il0zeuX3Zlg/w=="],
|
||||||
|
|
||||||
|
"@floating-ui/dom": ["@floating-ui/dom@1.7.4", "", { "dependencies": { "@floating-ui/core": "^1.7.3", "@floating-ui/utils": "^0.2.10" } }, "sha512-OOchDgh4F2CchOX94cRVqhvy7b3AFb+/rQXyswmzmGakRfkMgoWVjfnLWkRirfLEfuD4ysVW16eXzwt3jHIzKA=="],
|
||||||
|
|
||||||
|
"@floating-ui/utils": ["@floating-ui/utils@0.2.10", "", {}, "sha512-aGTxbpbg8/b5JfU1HXSrbH3wXZuLPJcNEcZQFMxLs3oSzgtVu6nFPkbbGGUvBcUjKV2YyB9Wxxabo+HEH9tcRQ=="],
|
||||||
|
|
||||||
|
"@internationalized/date": ["@internationalized/date@3.9.0", "", { "dependencies": { "@swc/helpers": "^0.5.0" } }, "sha512-yaN3brAnHRD+4KyyOsJyk49XUvj2wtbNACSqg0bz3u8t2VuzhC8Q5dfRnrSxjnnbDb+ienBnkn1TzQfE154vyg=="],
|
||||||
|
|
||||||
|
"@isaacs/fs-minipass": ["@isaacs/fs-minipass@4.0.1", "", { "dependencies": { "minipass": "^7.0.4" } }, "sha512-wgm9Ehl2jpeqP3zw/7mo3kRHFp5MEDhqAdwy1fTGkHAwnkGOVsgpvQhL8B5n1qlb01jV3n/bI0ZfZp5lWA1k4w=="],
|
||||||
|
|
||||||
|
"@jridgewell/gen-mapping": ["@jridgewell/gen-mapping@0.3.13", "", { "dependencies": { "@jridgewell/sourcemap-codec": "^1.5.0", "@jridgewell/trace-mapping": "^0.3.24" } }, "sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA=="],
|
||||||
|
|
||||||
|
"@jridgewell/remapping": ["@jridgewell/remapping@2.3.5", "", { "dependencies": { "@jridgewell/gen-mapping": "^0.3.5", "@jridgewell/trace-mapping": "^0.3.24" } }, "sha512-LI9u/+laYG4Ds1TDKSJW2YPrIlcVYOwi2fUC6xB43lueCjgxV4lffOCZCtYFiH6TNOX+tQKXx97T4IKHbhyHEQ=="],
|
||||||
|
|
||||||
|
"@jridgewell/resolve-uri": ["@jridgewell/resolve-uri@3.1.2", "", {}, "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw=="],
|
||||||
|
|
||||||
|
"@jridgewell/sourcemap-codec": ["@jridgewell/sourcemap-codec@1.5.5", "", {}, "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og=="],
|
||||||
|
|
||||||
|
"@jridgewell/trace-mapping": ["@jridgewell/trace-mapping@0.3.31", "", { "dependencies": { "@jridgewell/resolve-uri": "^3.1.0", "@jridgewell/sourcemap-codec": "^1.4.14" } }, "sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw=="],
|
||||||
|
|
||||||
|
"@lucide/svelte": ["@lucide/svelte@0.544.0", "", { "peerDependencies": { "svelte": "^5" } }, "sha512-9f9O6uxng2pLB01sxNySHduJN3HTl5p0HDu4H26VR51vhZfiMzyOMe9Mhof3XAk4l813eTtl+/DYRvGyoRR+yw=="],
|
||||||
|
|
||||||
|
"@polka/url": ["@polka/url@1.0.0-next.29", "", {}, "sha512-wwQAWhWSuHaag8c4q/KN/vCoeOJYshAIvMQwD4GpSb3OiZklFfvAgmj0VCBBImRpuF/aFgIRzllXlVX93Jevww=="],
|
||||||
|
|
||||||
|
"@rollup/plugin-commonjs": ["@rollup/plugin-commonjs@28.0.6", "", { "dependencies": { "@rollup/pluginutils": "^5.0.1", "commondir": "^1.0.1", "estree-walker": "^2.0.2", "fdir": "^6.2.0", "is-reference": "1.2.1", "magic-string": "^0.30.3", "picomatch": "^4.0.2" }, "peerDependencies": { "rollup": "^2.68.0||^3.0.0||^4.0.0" }, "optionalPeers": ["rollup"] }, "sha512-XSQB1K7FUU5QP+3lOQmVCE3I0FcbbNvmNT4VJSj93iUjayaARrTQeoRdiYQoftAJBLrR9t2agwAd3ekaTgHNlw=="],
|
||||||
|
|
||||||
|
"@rollup/plugin-json": ["@rollup/plugin-json@6.1.0", "", { "dependencies": { "@rollup/pluginutils": "^5.1.0" }, "peerDependencies": { "rollup": "^1.20.0||^2.0.0||^3.0.0||^4.0.0" }, "optionalPeers": ["rollup"] }, "sha512-EGI2te5ENk1coGeADSIwZ7G2Q8CJS2sF120T7jLw4xFw9n7wIOXHo+kIYRAoVpJAN+kmqZSoO3Fp4JtoNF4ReA=="],
|
||||||
|
|
||||||
|
"@rollup/plugin-node-resolve": ["@rollup/plugin-node-resolve@16.0.1", "", { "dependencies": { "@rollup/pluginutils": "^5.0.1", "@types/resolve": "1.20.2", "deepmerge": "^4.2.2", "is-module": "^1.0.0", "resolve": "^1.22.1" }, "peerDependencies": { "rollup": "^2.78.0||^3.0.0||^4.0.0" }, "optionalPeers": ["rollup"] }, "sha512-tk5YCxJWIG81umIvNkSod2qK5KyQW19qcBF/B78n1bjtOON6gzKoVeSzAE8yHCZEDmqkHKkxplExA8KzdJLJpA=="],
|
||||||
|
|
||||||
|
"@rollup/pluginutils": ["@rollup/pluginutils@5.3.0", "", { "dependencies": { "@types/estree": "^1.0.0", "estree-walker": "^2.0.2", "picomatch": "^4.0.2" }, "peerDependencies": { "rollup": "^1.20.0||^2.0.0||^3.0.0||^4.0.0" }, "optionalPeers": ["rollup"] }, "sha512-5EdhGZtnu3V88ces7s53hhfK5KSASnJZv8Lulpc04cWO3REESroJXg73DFsOmgbU2BhwV0E20bu2IDZb3VKW4Q=="],
|
||||||
|
|
||||||
|
"@rollup/rollup-android-arm-eabi": ["@rollup/rollup-android-arm-eabi@4.52.2", "", { "os": "android", "cpu": "arm" }, "sha512-o3pcKzJgSGt4d74lSZ+OCnHwkKBeAbFDmbEm5gg70eA8VkyCuC/zV9TwBnmw6VjDlRdF4Pshfb+WE9E6XY1PoQ=="],
|
||||||
|
|
||||||
|
"@rollup/rollup-android-arm64": ["@rollup/rollup-android-arm64@4.52.2", "", { "os": "android", "cpu": "arm64" }, "sha512-cqFSWO5tX2vhC9hJTK8WAiPIm4Q8q/cU8j2HQA0L3E1uXvBYbOZMhE2oFL8n2pKB5sOCHY6bBuHaRwG7TkfJyw=="],
|
||||||
|
|
||||||
|
"@rollup/rollup-darwin-arm64": ["@rollup/rollup-darwin-arm64@4.52.2", "", { "os": "darwin", "cpu": "arm64" }, "sha512-vngduywkkv8Fkh3wIZf5nFPXzWsNsVu1kvtLETWxTFf/5opZmflgVSeLgdHR56RQh71xhPhWoOkEBvbehwTlVA=="],
|
||||||
|
|
||||||
|
"@rollup/rollup-darwin-x64": ["@rollup/rollup-darwin-x64@4.52.2", "", { "os": "darwin", "cpu": "x64" }, "sha512-h11KikYrUCYTrDj6h939hhMNlqU2fo/X4NB0OZcys3fya49o1hmFaczAiJWVAFgrM1NCP6RrO7lQKeVYSKBPSQ=="],
|
||||||
|
|
||||||
|
"@rollup/rollup-freebsd-arm64": ["@rollup/rollup-freebsd-arm64@4.52.2", "", { "os": "freebsd", "cpu": "arm64" }, "sha512-/eg4CI61ZUkLXxMHyVlmlGrSQZ34xqWlZNW43IAU4RmdzWEx0mQJ2mN/Cx4IHLVZFL6UBGAh+/GXhgvGb+nVxw=="],
|
||||||
|
|
||||||
|
"@rollup/rollup-freebsd-x64": ["@rollup/rollup-freebsd-x64@4.52.2", "", { "os": "freebsd", "cpu": "x64" }, "sha512-QOWgFH5X9+p+S1NAfOqc0z8qEpJIoUHf7OWjNUGOeW18Mx22lAUOiA9b6r2/vpzLdfxi/f+VWsYjUOMCcYh0Ng=="],
|
||||||
|
|
||||||
|
"@rollup/rollup-linux-arm-gnueabihf": ["@rollup/rollup-linux-arm-gnueabihf@4.52.2", "", { "os": "linux", "cpu": "arm" }, "sha512-kDWSPafToDd8LcBYd1t5jw7bD5Ojcu12S3uT372e5HKPzQt532vW+rGFFOaiR0opxePyUkHrwz8iWYEyH1IIQA=="],
|
||||||
|
|
||||||
|
"@rollup/rollup-linux-arm-musleabihf": ["@rollup/rollup-linux-arm-musleabihf@4.52.2", "", { "os": "linux", "cpu": "arm" }, "sha512-gKm7Mk9wCv6/rkzwCiUC4KnevYhlf8ztBrDRT9g/u//1fZLapSRc+eDZj2Eu2wpJ+0RzUKgtNijnVIB4ZxyL+w=="],
|
||||||
|
|
||||||
|
"@rollup/rollup-linux-arm64-gnu": ["@rollup/rollup-linux-arm64-gnu@4.52.2", "", { "os": "linux", "cpu": "arm64" }, "sha512-66lA8vnj5mB/rtDNwPgrrKUOtCLVQypkyDa2gMfOefXK6rcZAxKLO9Fy3GkW8VkPnENv9hBkNOFfGLf6rNKGUg=="],
|
||||||
|
|
||||||
|
"@rollup/rollup-linux-arm64-musl": ["@rollup/rollup-linux-arm64-musl@4.52.2", "", { "os": "linux", "cpu": "arm64" }, "sha512-s+OPucLNdJHvuZHuIz2WwncJ+SfWHFEmlC5nKMUgAelUeBUnlB4wt7rXWiyG4Zn07uY2Dd+SGyVa9oyLkVGOjA=="],
|
||||||
|
|
||||||
|
"@rollup/rollup-linux-loong64-gnu": ["@rollup/rollup-linux-loong64-gnu@4.52.2", "", { "os": "linux", "cpu": "none" }, "sha512-8wTRM3+gVMDLLDdaT6tKmOE3lJyRy9NpJUS/ZRWmLCmOPIJhVyXwjBo+XbrrwtV33Em1/eCTd5TuGJm4+DmYjw=="],
|
||||||
|
|
||||||
|
"@rollup/rollup-linux-ppc64-gnu": ["@rollup/rollup-linux-ppc64-gnu@4.52.2", "", { "os": "linux", "cpu": "ppc64" }, "sha512-6yqEfgJ1anIeuP2P/zhtfBlDpXUb80t8DpbYwXQ3bQd95JMvUaqiX+fKqYqUwZXqdJDd8xdilNtsHM2N0cFm6A=="],
|
||||||
|
|
||||||
|
"@rollup/rollup-linux-riscv64-gnu": ["@rollup/rollup-linux-riscv64-gnu@4.52.2", "", { "os": "linux", "cpu": "none" }, "sha512-sshYUiYVSEI2B6dp4jMncwxbrUqRdNApF2c3bhtLAU0qA8Lrri0p0NauOsTWh3yCCCDyBOjESHMExonp7Nzc0w=="],
|
||||||
|
|
||||||
|
"@rollup/rollup-linux-riscv64-musl": ["@rollup/rollup-linux-riscv64-musl@4.52.2", "", { "os": "linux", "cpu": "none" }, "sha512-duBLgd+3pqC4MMwBrKkFxaZerUxZcYApQVC5SdbF5/e/589GwVvlRUnyqMFbM8iUSb1BaoX/3fRL7hB9m2Pj8Q=="],
|
||||||
|
|
||||||
|
"@rollup/rollup-linux-s390x-gnu": ["@rollup/rollup-linux-s390x-gnu@4.52.2", "", { "os": "linux", "cpu": "s390x" }, "sha512-tzhYJJidDUVGMgVyE+PmxENPHlvvqm1KILjjZhB8/xHYqAGeizh3GBGf9u6WdJpZrz1aCpIIHG0LgJgH9rVjHQ=="],
|
||||||
|
|
||||||
|
"@rollup/rollup-linux-x64-gnu": ["@rollup/rollup-linux-x64-gnu@4.52.2", "", { "os": "linux", "cpu": "x64" }, "sha512-opH8GSUuVcCSSyHHcl5hELrmnk4waZoVpgn/4FDao9iyE4WpQhyWJ5ryl5M3ocp4qkRuHfyXnGqg8M9oKCEKRA=="],
|
||||||
|
|
||||||
|
"@rollup/rollup-linux-x64-musl": ["@rollup/rollup-linux-x64-musl@4.52.2", "", { "os": "linux", "cpu": "x64" }, "sha512-LSeBHnGli1pPKVJ79ZVJgeZWWZXkEe/5o8kcn23M8eMKCUANejchJbF/JqzM4RRjOJfNRhKJk8FuqL1GKjF5oQ=="],
|
||||||
|
|
||||||
|
"@rollup/rollup-openharmony-arm64": ["@rollup/rollup-openharmony-arm64@4.52.2", "", { "os": "none", "cpu": "arm64" }, "sha512-uPj7MQ6/s+/GOpolavm6BPo+6CbhbKYyZHUDvZ/SmJM7pfDBgdGisFX3bY/CBDMg2ZO4utfhlApkSfZ92yXw7Q=="],
|
||||||
|
|
||||||
|
"@rollup/rollup-win32-arm64-msvc": ["@rollup/rollup-win32-arm64-msvc@4.52.2", "", { "os": "win32", "cpu": "arm64" }, "sha512-Z9MUCrSgIaUeeHAiNkm3cQyst2UhzjPraR3gYYfOjAuZI7tcFRTOD+4cHLPoS/3qinchth+V56vtqz1Tv+6KPA=="],
|
||||||
|
|
||||||
|
"@rollup/rollup-win32-ia32-msvc": ["@rollup/rollup-win32-ia32-msvc@4.52.2", "", { "os": "win32", "cpu": "ia32" }, "sha512-+GnYBmpjldD3XQd+HMejo+0gJGwYIOfFeoBQv32xF/RUIvccUz20/V6Otdv+57NE70D5pa8W/jVGDoGq0oON4A=="],
|
||||||
|
|
||||||
|
"@rollup/rollup-win32-x64-gnu": ["@rollup/rollup-win32-x64-gnu@4.52.2", "", { "os": "win32", "cpu": "x64" }, "sha512-ApXFKluSB6kDQkAqZOKXBjiaqdF1BlKi+/eqnYe9Ee7U2K3pUDKsIyr8EYm/QDHTJIM+4X+lI0gJc3TTRhd+dA=="],
|
||||||
|
|
||||||
|
"@rollup/rollup-win32-x64-msvc": ["@rollup/rollup-win32-x64-msvc@4.52.2", "", { "os": "win32", "cpu": "x64" }, "sha512-ARz+Bs8kY6FtitYM96PqPEVvPXqEZmPZsSkXvyX19YzDqkCaIlhCieLLMI5hxO9SRZ2XtCtm8wxhy0iJ2jxNfw=="],
|
||||||
|
|
||||||
|
"@standard-schema/spec": ["@standard-schema/spec@1.0.0", "", {}, "sha512-m2bOd0f2RT9k8QJx1JN85cZYyH1RqFBdlwtkSlf4tBDYLCiiZnv1fIIwacK6cqwXavOydf0NPToMQgpKq+dVlA=="],
|
||||||
|
|
||||||
|
"@sveltejs/acorn-typescript": ["@sveltejs/acorn-typescript@1.0.5", "", { "peerDependencies": { "acorn": "^8.9.0" } }, "sha512-IwQk4yfwLdibDlrXVE04jTZYlLnwsTT2PIOQQGNLWfjavGifnk1JD1LcZjZaBTRcxZu2FfPfNLOE04DSu9lqtQ=="],
|
||||||
|
|
||||||
|
"@sveltejs/adapter-auto": ["@sveltejs/adapter-auto@6.1.0", "", { "peerDependencies": { "@sveltejs/kit": "^2.0.0" } }, "sha512-shOuLI5D2s+0zTv2ab5M5PqfknXqWbKi+0UwB9yLTRIdzsK1R93JOO8jNhIYSHdW+IYXIYnLniu+JZqXs7h9Wg=="],
|
||||||
|
|
||||||
|
"@sveltejs/adapter-node": ["@sveltejs/adapter-node@5.3.2", "", { "dependencies": { "@rollup/plugin-commonjs": "^28.0.1", "@rollup/plugin-json": "^6.1.0", "@rollup/plugin-node-resolve": "^16.0.0", "rollup": "^4.9.5" }, "peerDependencies": { "@sveltejs/kit": "^2.4.0" } }, "sha512-nBJSipMb1KLjnAM7uzb+YpnA1VWKb+WdR+0mXEnXI6K1A3XYWbjkcjnW20ubg07sicK8XaGY/FAX3PItw39qBQ=="],
|
||||||
|
|
||||||
|
"@sveltejs/kit": ["@sveltejs/kit@2.43.4", "", { "dependencies": { "@standard-schema/spec": "^1.0.0", "@sveltejs/acorn-typescript": "^1.0.5", "@types/cookie": "^0.6.0", "acorn": "^8.14.1", "cookie": "^0.6.0", "devalue": "^5.3.2", "esm-env": "^1.2.2", "kleur": "^4.1.5", "magic-string": "^0.30.5", "mrmime": "^2.0.0", "sade": "^1.8.1", "set-cookie-parser": "^2.6.0", "sirv": "^3.0.0" }, "peerDependencies": { "@opentelemetry/api": "^1.0.0", "@sveltejs/vite-plugin-svelte": "^3.0.0 || ^4.0.0-next.1 || ^5.0.0 || ^6.0.0-next.0", "svelte": "^4.0.0 || ^5.0.0-next.0", "vite": "^5.0.3 || ^6.0.0 || ^7.0.0-beta.0" }, "optionalPeers": ["@opentelemetry/api"], "bin": { "svelte-kit": "svelte-kit.js" } }, "sha512-GfvOq3A/qMRhj2L9eKjxaI8FLqZDh5SY74YzhRKT//u2AvQw96ksEfjuHviC4jg9U08mBVB0Y47EwEJHO4BB4Q=="],
|
||||||
|
|
||||||
|
"@sveltejs/vite-plugin-svelte": ["@sveltejs/vite-plugin-svelte@6.2.1", "", { "dependencies": { "@sveltejs/vite-plugin-svelte-inspector": "^5.0.0", "debug": "^4.4.1", "deepmerge": "^4.3.1", "magic-string": "^0.30.17", "vitefu": "^1.1.1" }, "peerDependencies": { "svelte": "^5.0.0", "vite": "^6.3.0 || ^7.0.0" } }, "sha512-YZs/OSKOQAQCnJvM/P+F1URotNnYNeU3P2s4oIpzm1uFaqUEqRxUB0g5ejMjEb5Gjb9/PiBI5Ktrq4rUUF8UVQ=="],
|
||||||
|
|
||||||
|
"@sveltejs/vite-plugin-svelte-inspector": ["@sveltejs/vite-plugin-svelte-inspector@5.0.1", "", { "dependencies": { "debug": "^4.4.1" }, "peerDependencies": { "@sveltejs/vite-plugin-svelte": "^6.0.0-next.0", "svelte": "^5.0.0", "vite": "^6.3.0 || ^7.0.0" } }, "sha512-ubWshlMk4bc8mkwWbg6vNvCeT7lGQojE3ijDh3QTR6Zr/R+GXxsGbyH4PExEPpiFmqPhYiVSVmHBjUcVc1JIrA=="],
|
||||||
|
|
||||||
|
"@swc/helpers": ["@swc/helpers@0.5.17", "", { "dependencies": { "tslib": "^2.8.0" } }, "sha512-5IKx/Y13RsYd+sauPb2x+U/xZikHjolzfuDgTAl/Tdf3Q8rslRvC19NKDLgAJQ6wsqADk10ntlv08nPFw/gO/A=="],
|
||||||
|
|
||||||
|
"@tailwindcss/node": ["@tailwindcss/node@4.1.13", "", { "dependencies": { "@jridgewell/remapping": "^2.3.4", "enhanced-resolve": "^5.18.3", "jiti": "^2.5.1", "lightningcss": "1.30.1", "magic-string": "^0.30.18", "source-map-js": "^1.2.1", "tailwindcss": "4.1.13" } }, "sha512-eq3ouolC1oEFOAvOMOBAmfCIqZBJuvWvvYWh5h5iOYfe1HFC6+GZ6EIL0JdM3/niGRJmnrOc+8gl9/HGUaaptw=="],
|
||||||
|
|
||||||
|
"@tailwindcss/oxide": ["@tailwindcss/oxide@4.1.13", "", { "dependencies": { "detect-libc": "^2.0.4", "tar": "^7.4.3" }, "optionalDependencies": { "@tailwindcss/oxide-android-arm64": "4.1.13", "@tailwindcss/oxide-darwin-arm64": "4.1.13", "@tailwindcss/oxide-darwin-x64": "4.1.13", "@tailwindcss/oxide-freebsd-x64": "4.1.13", "@tailwindcss/oxide-linux-arm-gnueabihf": "4.1.13", "@tailwindcss/oxide-linux-arm64-gnu": "4.1.13", "@tailwindcss/oxide-linux-arm64-musl": "4.1.13", "@tailwindcss/oxide-linux-x64-gnu": "4.1.13", "@tailwindcss/oxide-linux-x64-musl": "4.1.13", "@tailwindcss/oxide-wasm32-wasi": "4.1.13", "@tailwindcss/oxide-win32-arm64-msvc": "4.1.13", "@tailwindcss/oxide-win32-x64-msvc": "4.1.13" } }, "sha512-CPgsM1IpGRa880sMbYmG1s4xhAy3xEt1QULgTJGQmZUeNgXFR7s1YxYygmJyBGtou4SyEosGAGEeYqY7R53bIA=="],
|
||||||
|
|
||||||
|
"@tailwindcss/oxide-android-arm64": ["@tailwindcss/oxide-android-arm64@4.1.13", "", { "os": "android", "cpu": "arm64" }, "sha512-BrpTrVYyejbgGo57yc8ieE+D6VT9GOgnNdmh5Sac6+t0m+v+sKQevpFVpwX3pBrM2qKrQwJ0c5eDbtjouY/+ew=="],
|
||||||
|
|
||||||
|
"@tailwindcss/oxide-darwin-arm64": ["@tailwindcss/oxide-darwin-arm64@4.1.13", "", { "os": "darwin", "cpu": "arm64" }, "sha512-YP+Jksc4U0KHcu76UhRDHq9bx4qtBftp9ShK/7UGfq0wpaP96YVnnjFnj3ZFrUAjc5iECzODl/Ts0AN7ZPOANQ=="],
|
||||||
|
|
||||||
|
"@tailwindcss/oxide-darwin-x64": ["@tailwindcss/oxide-darwin-x64@4.1.13", "", { "os": "darwin", "cpu": "x64" }, "sha512-aAJ3bbwrn/PQHDxCto9sxwQfT30PzyYJFG0u/BWZGeVXi5Hx6uuUOQEI2Fa43qvmUjTRQNZnGqe9t0Zntexeuw=="],
|
||||||
|
|
||||||
|
"@tailwindcss/oxide-freebsd-x64": ["@tailwindcss/oxide-freebsd-x64@4.1.13", "", { "os": "freebsd", "cpu": "x64" }, "sha512-Wt8KvASHwSXhKE/dJLCCWcTSVmBj3xhVhp/aF3RpAhGeZ3sVo7+NTfgiN8Vey/Fi8prRClDs6/f0KXPDTZE6nQ=="],
|
||||||
|
|
||||||
|
"@tailwindcss/oxide-linux-arm-gnueabihf": ["@tailwindcss/oxide-linux-arm-gnueabihf@4.1.13", "", { "os": "linux", "cpu": "arm" }, "sha512-mbVbcAsW3Gkm2MGwA93eLtWrwajz91aXZCNSkGTx/R5eb6KpKD5q8Ueckkh9YNboU8RH7jiv+ol/I7ZyQ9H7Bw=="],
|
||||||
|
|
||||||
|
"@tailwindcss/oxide-linux-arm64-gnu": ["@tailwindcss/oxide-linux-arm64-gnu@4.1.13", "", { "os": "linux", "cpu": "arm64" }, "sha512-wdtfkmpXiwej/yoAkrCP2DNzRXCALq9NVLgLELgLim1QpSfhQM5+ZxQQF8fkOiEpuNoKLp4nKZ6RC4kmeFH0HQ=="],
|
||||||
|
|
||||||
|
"@tailwindcss/oxide-linux-arm64-musl": ["@tailwindcss/oxide-linux-arm64-musl@4.1.13", "", { "os": "linux", "cpu": "arm64" }, "sha512-hZQrmtLdhyqzXHB7mkXfq0IYbxegaqTmfa1p9MBj72WPoDD3oNOh1Lnxf6xZLY9C3OV6qiCYkO1i/LrzEdW2mg=="],
|
||||||
|
|
||||||
|
"@tailwindcss/oxide-linux-x64-gnu": ["@tailwindcss/oxide-linux-x64-gnu@4.1.13", "", { "os": "linux", "cpu": "x64" }, "sha512-uaZTYWxSXyMWDJZNY1Ul7XkJTCBRFZ5Fo6wtjrgBKzZLoJNrG+WderJwAjPzuNZOnmdrVg260DKwXCFtJ/hWRQ=="],
|
||||||
|
|
||||||
|
"@tailwindcss/oxide-linux-x64-musl": ["@tailwindcss/oxide-linux-x64-musl@4.1.13", "", { "os": "linux", "cpu": "x64" }, "sha512-oXiPj5mi4Hdn50v5RdnuuIms0PVPI/EG4fxAfFiIKQh5TgQgX7oSuDWntHW7WNIi/yVLAiS+CRGW4RkoGSSgVQ=="],
|
||||||
|
|
||||||
|
"@tailwindcss/oxide-wasm32-wasi": ["@tailwindcss/oxide-wasm32-wasi@4.1.13", "", { "dependencies": { "@emnapi/core": "^1.4.5", "@emnapi/runtime": "^1.4.5", "@emnapi/wasi-threads": "^1.0.4", "@napi-rs/wasm-runtime": "^0.2.12", "@tybys/wasm-util": "^0.10.0", "tslib": "^2.8.0" }, "cpu": "none" }, "sha512-+LC2nNtPovtrDwBc/nqnIKYh/W2+R69FA0hgoeOn64BdCX522u19ryLh3Vf3F8W49XBcMIxSe665kwy21FkhvA=="],
|
||||||
|
|
||||||
|
"@tailwindcss/oxide-win32-arm64-msvc": ["@tailwindcss/oxide-win32-arm64-msvc@4.1.13", "", { "os": "win32", "cpu": "arm64" }, "sha512-dziTNeQXtoQ2KBXmrjCxsuPk3F3CQ/yb7ZNZNA+UkNTeiTGgfeh+gH5Pi7mRncVgcPD2xgHvkFCh/MhZWSgyQg=="],
|
||||||
|
|
||||||
|
"@tailwindcss/oxide-win32-x64-msvc": ["@tailwindcss/oxide-win32-x64-msvc@4.1.13", "", { "os": "win32", "cpu": "x64" }, "sha512-3+LKesjXydTkHk5zXX01b5KMzLV1xl2mcktBJkje7rhFUpUlYJy7IMOLqjIRQncLTa1WZZiFY/foAeB5nmaiTw=="],
|
||||||
|
|
||||||
|
"@tailwindcss/vite": ["@tailwindcss/vite@4.1.13", "", { "dependencies": { "@tailwindcss/node": "4.1.13", "@tailwindcss/oxide": "4.1.13", "tailwindcss": "4.1.13" }, "peerDependencies": { "vite": "^5.2.0 || ^6 || ^7" } }, "sha512-0PmqLQ010N58SbMTJ7BVJ4I2xopiQn/5i6nlb4JmxzQf8zcS5+m2Cv6tqh+sfDwtIdjoEnOvwsGQ1hkUi8QEHQ=="],
|
||||||
|
|
||||||
|
"@types/cookie": ["@types/cookie@0.6.0", "", {}, "sha512-4Kh9a6B2bQciAhf7FSuMRRkUWecJgJu9nPnx3yzpsfXX/c50REIqpHY4C82bXP90qrLtXtkDxTZosYO3UpOwlA=="],
|
||||||
|
|
||||||
|
"@types/estree": ["@types/estree@1.0.8", "", {}, "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w=="],
|
||||||
|
|
||||||
|
"@types/resolve": ["@types/resolve@1.20.2", "", {}, "sha512-60BCwRFOZCQhDncwQdxxeOEEkbc5dIMccYLwbxsS4TUNeVECQ/pBJ0j09mrHOl/JJvpRPGwO9SvE4nR2Nb/a4Q=="],
|
||||||
|
|
||||||
|
"acorn": ["acorn@8.15.0", "", { "bin": { "acorn": "bin/acorn" } }, "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg=="],
|
||||||
|
|
||||||
|
"aria-query": ["aria-query@5.3.2", "", {}, "sha512-COROpnaoap1E2F000S62r6A60uHZnmlvomhfyT2DlTcrY1OrBKn2UhH7qn5wTC9zMvD0AY7csdPSNwKP+7WiQw=="],
|
||||||
|
|
||||||
|
"axobject-query": ["axobject-query@4.1.0", "", {}, "sha512-qIj0G9wZbMGNLjLmg1PT6v2mE9AH2zlnADJD/2tC6E00hgmhUOfEB6greHPAfLRSufHqROIUTkw6E+M3lH0PTQ=="],
|
||||||
|
|
||||||
|
"bits-ui": ["bits-ui@2.11.3", "", { "dependencies": { "@floating-ui/core": "^1.7.1", "@floating-ui/dom": "^1.7.1", "esm-env": "^1.1.2", "runed": "^0.31.1", "svelte-toolbelt": "^0.10.4", "tabbable": "^6.2.0" }, "peerDependencies": { "@internationalized/date": "^3.8.1", "svelte": "^5.33.0" } }, "sha512-XhTuOgcgFatw2qxd+hIlu3EqfYu9R2FFMeJg8srZo3UVKBdOVWHbT0L3GBH46XsaESkTz3aEjU+BiGVoI/2rnQ=="],
|
||||||
|
|
||||||
|
"chokidar": ["chokidar@4.0.3", "", { "dependencies": { "readdirp": "^4.0.1" } }, "sha512-Qgzu8kfBvo+cA4962jnP1KkS6Dop5NS6g7R5LFYJr4b8Ub94PPQXUksCw9PvXoeXPRRddRNC5C1JQUR2SMGtnA=="],
|
||||||
|
|
||||||
|
"chownr": ["chownr@3.0.0", "", {}, "sha512-+IxzY9BZOQd/XuYPRmrvEVjF/nqj5kgT4kEq7VofrDoM1MxoRjEWkrCC3EtLi59TVawxTAn+orJwFQcrqEN1+g=="],
|
||||||
|
|
||||||
|
"clsx": ["clsx@2.1.1", "", {}, "sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA=="],
|
||||||
|
|
||||||
|
"commondir": ["commondir@1.0.1", "", {}, "sha512-W9pAhw0ja1Edb5GVdIF1mjZw/ASI0AlShXM83UUGe2DVr5TdAPEA1OA8m/g8zWp9x6On7gqufY+FatDbC3MDQg=="],
|
||||||
|
|
||||||
|
"cookie": ["cookie@0.6.0", "", {}, "sha512-U71cyTamuh1CRNCfpGY6to28lxvNwPG4Guz/EVjgf3Jmzv0vlDp1atT9eS5dDjMYHucpHbWns6Lwf3BKz6svdw=="],
|
||||||
|
|
||||||
|
"debug": ["debug@4.4.3", "", { "dependencies": { "ms": "^2.1.3" } }, "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA=="],
|
||||||
|
|
||||||
|
"deepmerge": ["deepmerge@4.3.1", "", {}, "sha512-3sUqbMEc77XqpdNO7FRyRog+eW3ph+GYCbj+rK+uYyRMuwsVy0rMiVtPn+QJlKFvWP/1PYpapqYn0Me2knFn+A=="],
|
||||||
|
|
||||||
|
"detect-libc": ["detect-libc@2.1.1", "", {}, "sha512-ecqj/sy1jcK1uWrwpR67UhYrIFQ+5WlGxth34WquCbamhFA6hkkwiu37o6J5xCHdo1oixJRfVRw+ywV+Hq/0Aw=="],
|
||||||
|
|
||||||
|
"devalue": ["devalue@5.3.2", "", {}, "sha512-UDsjUbpQn9kvm68slnrs+mfxwFkIflOhkanmyabZ8zOYk8SMEIbJ3TK+88g70hSIeytu4y18f0z/hYHMTrXIWw=="],
|
||||||
|
|
||||||
|
"enhanced-resolve": ["enhanced-resolve@5.18.3", "", { "dependencies": { "graceful-fs": "^4.2.4", "tapable": "^2.2.0" } }, "sha512-d4lC8xfavMeBjzGr2vECC3fsGXziXZQyJxD868h2M/mBI3PwAuODxAkLkq5HYuvrPYcUtiLzsTo8U3PgX3Ocww=="],
|
||||||
|
|
||||||
|
"esbuild": ["esbuild@0.25.10", "", { "optionalDependencies": { "@esbuild/aix-ppc64": "0.25.10", "@esbuild/android-arm": "0.25.10", "@esbuild/android-arm64": "0.25.10", "@esbuild/android-x64": "0.25.10", "@esbuild/darwin-arm64": "0.25.10", "@esbuild/darwin-x64": "0.25.10", "@esbuild/freebsd-arm64": "0.25.10", "@esbuild/freebsd-x64": "0.25.10", "@esbuild/linux-arm": "0.25.10", "@esbuild/linux-arm64": "0.25.10", "@esbuild/linux-ia32": "0.25.10", "@esbuild/linux-loong64": "0.25.10", "@esbuild/linux-mips64el": "0.25.10", "@esbuild/linux-ppc64": "0.25.10", "@esbuild/linux-riscv64": "0.25.10", "@esbuild/linux-s390x": "0.25.10", "@esbuild/linux-x64": "0.25.10", "@esbuild/netbsd-arm64": "0.25.10", "@esbuild/netbsd-x64": "0.25.10", "@esbuild/openbsd-arm64": "0.25.10", "@esbuild/openbsd-x64": "0.25.10", "@esbuild/openharmony-arm64": "0.25.10", "@esbuild/sunos-x64": "0.25.10", "@esbuild/win32-arm64": "0.25.10", "@esbuild/win32-ia32": "0.25.10", "@esbuild/win32-x64": "0.25.10" }, "bin": { "esbuild": "bin/esbuild" } }, "sha512-9RiGKvCwaqxO2owP61uQ4BgNborAQskMR6QusfWzQqv7AZOg5oGehdY2pRJMTKuwxd1IDBP4rSbI5lHzU7SMsQ=="],
|
||||||
|
|
||||||
|
"esm-env": ["esm-env@1.2.2", "", {}, "sha512-Epxrv+Nr/CaL4ZcFGPJIYLWFom+YeV1DqMLHJoEd9SYRxNbaFruBwfEX/kkHUJf55j2+TUbmDcmuilbP1TmXHA=="],
|
||||||
|
|
||||||
|
"esrap": ["esrap@2.1.0", "", { "dependencies": { "@jridgewell/sourcemap-codec": "^1.4.15" } }, "sha512-yzmPNpl7TBbMRC5Lj2JlJZNPml0tzqoqP5B1JXycNUwtqma9AKCO0M2wHrdgsHcy1WRW7S9rJknAMtByg3usgA=="],
|
||||||
|
|
||||||
|
"estree-walker": ["estree-walker@2.0.2", "", {}, "sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w=="],
|
||||||
|
|
||||||
|
"fdir": ["fdir@6.5.0", "", { "peerDependencies": { "picomatch": "^3 || ^4" }, "optionalPeers": ["picomatch"] }, "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg=="],
|
||||||
|
|
||||||
|
"fsevents": ["fsevents@2.3.3", "", { "os": "darwin" }, "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw=="],
|
||||||
|
|
||||||
|
"function-bind": ["function-bind@1.1.2", "", {}, "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA=="],
|
||||||
|
|
||||||
|
"graceful-fs": ["graceful-fs@4.2.11", "", {}, "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ=="],
|
||||||
|
|
||||||
|
"hasown": ["hasown@2.0.2", "", { "dependencies": { "function-bind": "^1.1.2" } }, "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ=="],
|
||||||
|
|
||||||
|
"inline-style-parser": ["inline-style-parser@0.2.4", "", {}, "sha512-0aO8FkhNZlj/ZIbNi7Lxxr12obT7cL1moPfE4tg1LkX7LlLfC6DeX4l2ZEud1ukP9jNQyNnfzQVqwbwmAATY4Q=="],
|
||||||
|
|
||||||
|
"is-core-module": ["is-core-module@2.16.1", "", { "dependencies": { "hasown": "^2.0.2" } }, "sha512-UfoeMA6fIJ8wTYFEUjelnaGI67v6+N7qXJEvQuIGa99l4xsCruSYOVSQ0uPANn4dAzm8lkYPaKLrrijLq7x23w=="],
|
||||||
|
|
||||||
|
"is-module": ["is-module@1.0.0", "", {}, "sha512-51ypPSPCoTEIN9dy5Oy+h4pShgJmPCygKfyRCISBI+JoWT/2oJvK8QPxmwv7b/p239jXrm9M1mlQbyKJ5A152g=="],
|
||||||
|
|
||||||
|
"is-reference": ["is-reference@3.0.3", "", { "dependencies": { "@types/estree": "^1.0.6" } }, "sha512-ixkJoqQvAP88E6wLydLGGqCJsrFUnqoH6HnaczB8XmDH1oaWU+xxdptvikTgaEhtZ53Ky6YXiBuUI2WXLMCwjw=="],
|
||||||
|
|
||||||
|
"jiti": ["jiti@2.6.0", "", { "bin": { "jiti": "lib/jiti-cli.mjs" } }, "sha512-VXe6RjJkBPj0ohtqaO8vSWP3ZhAKo66fKrFNCll4BTcwljPLz03pCbaNKfzGP5MbrCYcbJ7v0nOYYwUzTEIdXQ=="],
|
||||||
|
|
||||||
|
"kleur": ["kleur@4.1.5", "", {}, "sha512-o+NO+8WrRiQEE4/7nwRJhN1HWpVmJm511pBHUxPLtp0BUISzlBplORYSmTclCnJvQq2tKu/sgl3xVpkc7ZWuQQ=="],
|
||||||
|
|
||||||
|
"lightningcss": ["lightningcss@1.30.1", "", { "dependencies": { "detect-libc": "^2.0.3" }, "optionalDependencies": { "lightningcss-darwin-arm64": "1.30.1", "lightningcss-darwin-x64": "1.30.1", "lightningcss-freebsd-x64": "1.30.1", "lightningcss-linux-arm-gnueabihf": "1.30.1", "lightningcss-linux-arm64-gnu": "1.30.1", "lightningcss-linux-arm64-musl": "1.30.1", "lightningcss-linux-x64-gnu": "1.30.1", "lightningcss-linux-x64-musl": "1.30.1", "lightningcss-win32-arm64-msvc": "1.30.1", "lightningcss-win32-x64-msvc": "1.30.1" } }, "sha512-xi6IyHML+c9+Q3W0S4fCQJOym42pyurFiJUHEcEyHS0CeKzia4yZDEsLlqOFykxOdHpNy0NmvVO31vcSqAxJCg=="],
|
||||||
|
|
||||||
|
"lightningcss-darwin-arm64": ["lightningcss-darwin-arm64@1.30.1", "", { "os": "darwin", "cpu": "arm64" }, "sha512-c8JK7hyE65X1MHMN+Viq9n11RRC7hgin3HhYKhrMyaXflk5GVplZ60IxyoVtzILeKr+xAJwg6zK6sjTBJ0FKYQ=="],
|
||||||
|
|
||||||
|
"lightningcss-darwin-x64": ["lightningcss-darwin-x64@1.30.1", "", { "os": "darwin", "cpu": "x64" }, "sha512-k1EvjakfumAQoTfcXUcHQZhSpLlkAuEkdMBsI/ivWw9hL+7FtilQc0Cy3hrx0AAQrVtQAbMI7YjCgYgvn37PzA=="],
|
||||||
|
|
||||||
|
"lightningcss-freebsd-x64": ["lightningcss-freebsd-x64@1.30.1", "", { "os": "freebsd", "cpu": "x64" }, "sha512-kmW6UGCGg2PcyUE59K5r0kWfKPAVy4SltVeut+umLCFoJ53RdCUWxcRDzO1eTaxf/7Q2H7LTquFHPL5R+Gjyig=="],
|
||||||
|
|
||||||
|
"lightningcss-linux-arm-gnueabihf": ["lightningcss-linux-arm-gnueabihf@1.30.1", "", { "os": "linux", "cpu": "arm" }, "sha512-MjxUShl1v8pit+6D/zSPq9S9dQ2NPFSQwGvxBCYaBYLPlCWuPh9/t1MRS8iUaR8i+a6w7aps+B4N0S1TYP/R+Q=="],
|
||||||
|
|
||||||
|
"lightningcss-linux-arm64-gnu": ["lightningcss-linux-arm64-gnu@1.30.1", "", { "os": "linux", "cpu": "arm64" }, "sha512-gB72maP8rmrKsnKYy8XUuXi/4OctJiuQjcuqWNlJQ6jZiWqtPvqFziskH3hnajfvKB27ynbVCucKSm2rkQp4Bw=="],
|
||||||
|
|
||||||
|
"lightningcss-linux-arm64-musl": ["lightningcss-linux-arm64-musl@1.30.1", "", { "os": "linux", "cpu": "arm64" }, "sha512-jmUQVx4331m6LIX+0wUhBbmMX7TCfjF5FoOH6SD1CttzuYlGNVpA7QnrmLxrsub43ClTINfGSYyHe2HWeLl5CQ=="],
|
||||||
|
|
||||||
|
"lightningcss-linux-x64-gnu": ["lightningcss-linux-x64-gnu@1.30.1", "", { "os": "linux", "cpu": "x64" }, "sha512-piWx3z4wN8J8z3+O5kO74+yr6ze/dKmPnI7vLqfSqI8bccaTGY5xiSGVIJBDd5K5BHlvVLpUB3S2YCfelyJ1bw=="],
|
||||||
|
|
||||||
|
"lightningcss-linux-x64-musl": ["lightningcss-linux-x64-musl@1.30.1", "", { "os": "linux", "cpu": "x64" }, "sha512-rRomAK7eIkL+tHY0YPxbc5Dra2gXlI63HL+v1Pdi1a3sC+tJTcFrHX+E86sulgAXeI7rSzDYhPSeHHjqFhqfeQ=="],
|
||||||
|
|
||||||
|
"lightningcss-win32-arm64-msvc": ["lightningcss-win32-arm64-msvc@1.30.1", "", { "os": "win32", "cpu": "arm64" }, "sha512-mSL4rqPi4iXq5YVqzSsJgMVFENoa4nGTT/GjO2c0Yl9OuQfPsIfncvLrEW6RbbB24WtZ3xP/2CCmI3tNkNV4oA=="],
|
||||||
|
|
||||||
|
"lightningcss-win32-x64-msvc": ["lightningcss-win32-x64-msvc@1.30.1", "", { "os": "win32", "cpu": "x64" }, "sha512-PVqXh48wh4T53F/1CCu8PIPCxLzWyCnn/9T5W1Jpmdy5h9Cwd+0YQS6/LwhHXSafuc61/xg9Lv5OrCby6a++jg=="],
|
||||||
|
|
||||||
|
"locate-character": ["locate-character@3.0.0", "", {}, "sha512-SW13ws7BjaeJ6p7Q6CO2nchbYEc3X3J6WrmTTDto7yMPqVSZTUyY5Tjbid+Ab8gLnATtygYtiDIJGQRRn2ZOiA=="],
|
||||||
|
|
||||||
|
"magic-string": ["magic-string@0.30.19", "", { "dependencies": { "@jridgewell/sourcemap-codec": "^1.5.5" } }, "sha512-2N21sPY9Ws53PZvsEpVtNuSW+ScYbQdp4b9qUaL+9QkHUrGFKo56Lg9Emg5s9V/qrtNBmiR01sYhUOwu3H+VOw=="],
|
||||||
|
|
||||||
|
"minipass": ["minipass@7.1.2", "", {}, "sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw=="],
|
||||||
|
|
||||||
|
"minizlib": ["minizlib@3.1.0", "", { "dependencies": { "minipass": "^7.1.2" } }, "sha512-KZxYo1BUkWD2TVFLr0MQoM8vUUigWD3LlD83a/75BqC+4qE0Hb1Vo5v1FgcfaNXvfXzr+5EhQ6ing/CaBijTlw=="],
|
||||||
|
|
||||||
|
"mode-watcher": ["mode-watcher@1.1.0", "", { "dependencies": { "runed": "^0.25.0", "svelte-toolbelt": "^0.7.1" }, "peerDependencies": { "svelte": "^5.27.0" } }, "sha512-mUT9RRGPDYenk59qJauN1rhsIMKBmWA3xMF+uRwE8MW/tjhaDSCCARqkSuDTq8vr4/2KcAxIGVjACxTjdk5C3g=="],
|
||||||
|
|
||||||
|
"mri": ["mri@1.2.0", "", {}, "sha512-tzzskb3bG8LvYGFF/mDTpq3jpI6Q9wc3LEmBaghu+DdCssd1FakN7Bc0hVNmEyGq1bq3RgfkCb3cmQLpNPOroA=="],
|
||||||
|
|
||||||
|
"mrmime": ["mrmime@2.0.1", "", {}, "sha512-Y3wQdFg2Va6etvQ5I82yUhGdsKrcYox6p7FfL1LbK2J4V01F9TGlepTIhnK24t7koZibmg82KGglhA1XK5IsLQ=="],
|
||||||
|
|
||||||
|
"ms": ["ms@2.1.3", "", {}, "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA=="],
|
||||||
|
|
||||||
|
"nanoid": ["nanoid@3.3.11", "", { "bin": { "nanoid": "bin/nanoid.cjs" } }, "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w=="],
|
||||||
|
|
||||||
|
"path-parse": ["path-parse@1.0.7", "", {}, "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw=="],
|
||||||
|
|
||||||
|
"picocolors": ["picocolors@1.1.1", "", {}, "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA=="],
|
||||||
|
|
||||||
|
"picomatch": ["picomatch@4.0.3", "", {}, "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q=="],
|
||||||
|
|
||||||
|
"postcss": ["postcss@8.5.6", "", { "dependencies": { "nanoid": "^3.3.11", "picocolors": "^1.1.1", "source-map-js": "^1.2.1" } }, "sha512-3Ybi1tAuwAP9s0r1UQ2J4n5Y0G05bJkpUIO0/bI9MhwmD70S5aTWbXGBwxHrelT+XM1k6dM0pk+SwNkpTRN7Pg=="],
|
||||||
|
|
||||||
|
"readdirp": ["readdirp@4.1.2", "", {}, "sha512-GDhwkLfywWL2s6vEjyhri+eXmfH6j1L7JE27WhqLeYzoh/A3DBaYGEj2H/HFZCn/kMfim73FXxEJTw06WtxQwg=="],
|
||||||
|
|
||||||
|
"resolve": ["resolve@1.22.10", "", { "dependencies": { "is-core-module": "^2.16.0", "path-parse": "^1.0.7", "supports-preserve-symlinks-flag": "^1.0.0" }, "bin": { "resolve": "bin/resolve" } }, "sha512-NPRy+/ncIMeDlTAsuqwKIiferiawhefFJtkNSW0qZJEqMEb+qBt/77B/jGeeek+F0uOeN05CDa6HXbbIgtVX4w=="],
|
||||||
|
|
||||||
|
"rollup": ["rollup@4.52.2", "", { "dependencies": { "@types/estree": "1.0.8" }, "optionalDependencies": { "@rollup/rollup-android-arm-eabi": "4.52.2", "@rollup/rollup-android-arm64": "4.52.2", "@rollup/rollup-darwin-arm64": "4.52.2", "@rollup/rollup-darwin-x64": "4.52.2", "@rollup/rollup-freebsd-arm64": "4.52.2", "@rollup/rollup-freebsd-x64": "4.52.2", "@rollup/rollup-linux-arm-gnueabihf": "4.52.2", "@rollup/rollup-linux-arm-musleabihf": "4.52.2", "@rollup/rollup-linux-arm64-gnu": "4.52.2", "@rollup/rollup-linux-arm64-musl": "4.52.2", "@rollup/rollup-linux-loong64-gnu": "4.52.2", "@rollup/rollup-linux-ppc64-gnu": "4.52.2", "@rollup/rollup-linux-riscv64-gnu": "4.52.2", "@rollup/rollup-linux-riscv64-musl": "4.52.2", "@rollup/rollup-linux-s390x-gnu": "4.52.2", "@rollup/rollup-linux-x64-gnu": "4.52.2", "@rollup/rollup-linux-x64-musl": "4.52.2", "@rollup/rollup-openharmony-arm64": "4.52.2", "@rollup/rollup-win32-arm64-msvc": "4.52.2", "@rollup/rollup-win32-ia32-msvc": "4.52.2", "@rollup/rollup-win32-x64-gnu": "4.52.2", "@rollup/rollup-win32-x64-msvc": "4.52.2", "fsevents": "~2.3.2" }, "bin": { "rollup": "dist/bin/rollup" } }, "sha512-I25/2QgoROE1vYV+NQ1En9T9UFB9Cmfm2CJ83zZOlaDpvz29wGQSZXWKw7MiNXau7wYgB/T9fVIdIuEQ+KbiiA=="],
|
||||||
|
|
||||||
|
"runed": ["runed@0.31.1", "", { "dependencies": { "esm-env": "^1.0.0" }, "peerDependencies": { "svelte": "^5.7.0" } }, "sha512-v3czcTnO+EJjiPvD4dwIqfTdHLZ8oH0zJheKqAHh9QMViY7Qb29UlAMRpX7ZtHh7AFqV60KmfxaJ9QMy+L1igQ=="],
|
||||||
|
|
||||||
|
"sade": ["sade@1.8.1", "", { "dependencies": { "mri": "^1.1.0" } }, "sha512-xal3CZX1Xlo/k4ApwCFrHVACi9fBqJ7V+mwhBsuf/1IOKbBy098Fex+Wa/5QMubw09pSZ/u8EY8PWgevJsXp1A=="],
|
||||||
|
|
||||||
|
"set-cookie-parser": ["set-cookie-parser@2.7.1", "", {}, "sha512-IOc8uWeOZgnb3ptbCURJWNjWUPcO3ZnTTdzsurqERrP6nPyv+paC55vJM0LpOlT2ne+Ix+9+CRG1MNLlyZ4GjQ=="],
|
||||||
|
|
||||||
|
"sirv": ["sirv@3.0.2", "", { "dependencies": { "@polka/url": "^1.0.0-next.24", "mrmime": "^2.0.0", "totalist": "^3.0.0" } }, "sha512-2wcC/oGxHis/BoHkkPwldgiPSYcpZK3JU28WoMVv55yHJgcZ8rlXvuG9iZggz+sU1d4bRgIGASwyWqjxu3FM0g=="],
|
||||||
|
|
||||||
|
"source-map-js": ["source-map-js@1.2.1", "", {}, "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA=="],
|
||||||
|
|
||||||
|
"style-to-object": ["style-to-object@1.0.9", "", { "dependencies": { "inline-style-parser": "0.2.4" } }, "sha512-G4qppLgKu/k6FwRpHiGiKPaPTFcG3g4wNVX/Qsfu+RqQM30E7Tyu/TEgxcL9PNLF5pdRLwQdE3YKKf+KF2Dzlw=="],
|
||||||
|
|
||||||
|
"supports-preserve-symlinks-flag": ["supports-preserve-symlinks-flag@1.0.0", "", {}, "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w=="],
|
||||||
|
|
||||||
|
"svelte": ["svelte@5.39.5", "", { "dependencies": { "@jridgewell/remapping": "^2.3.4", "@jridgewell/sourcemap-codec": "^1.5.0", "@sveltejs/acorn-typescript": "^1.0.5", "@types/estree": "^1.0.5", "acorn": "^8.12.1", "aria-query": "^5.3.1", "axobject-query": "^4.1.0", "clsx": "^2.1.1", "esm-env": "^1.2.1", "esrap": "^2.1.0", "is-reference": "^3.0.3", "locate-character": "^3.0.0", "magic-string": "^0.30.11", "zimmerframe": "^1.1.2" } }, "sha512-YrTmQAgJNB5He5t14g+BH76xTjskcx0Dg3p6qKqfPcgha+9Rzdgkoazdk18ahzNfkFYgykZIjfnBvlPcp3NpYg=="],
|
||||||
|
|
||||||
|
"svelte-check": ["svelte-check@4.3.2", "", { "dependencies": { "@jridgewell/trace-mapping": "^0.3.25", "chokidar": "^4.0.1", "fdir": "^6.2.0", "picocolors": "^1.0.0", "sade": "^1.7.4" }, "peerDependencies": { "svelte": "^4.0.0 || ^5.0.0-next.0", "typescript": ">=5.0.0" }, "bin": { "svelte-check": "bin/svelte-check" } }, "sha512-71udP5w2kaSTcX8iV0hn3o2FWlabQHhJTJLIQrCqMsrcOeDUO2VhCQKKCA8AMVHSPwdxLEWkUWh9OKxns5PD9w=="],
|
||||||
|
|
||||||
|
"svelte-sonner": ["svelte-sonner@1.0.5", "", { "dependencies": { "runed": "^0.28.0" }, "peerDependencies": { "svelte": "^5.0.0" } }, "sha512-9dpGPFqKb/QWudYqGnEz93vuY+NgCEvyNvxoCLMVGw6sDN/3oVeKV1xiEirW2E1N3vJEyj5imSBNOGltQHA7mg=="],
|
||||||
|
|
||||||
|
"svelte-toolbelt": ["svelte-toolbelt@0.10.5", "", { "dependencies": { "clsx": "^2.1.1", "runed": "^0.29.0", "style-to-object": "^1.0.8" }, "peerDependencies": { "svelte": "^5.30.2" } }, "sha512-8e+eWTgxw1aiLxhDE8Rb1X6AoLitqpJz+WhAul2W7W58C8KoLoJQf1TgQdFPBiCPJ0Jg5y0Zi1uyua9em4VS0w=="],
|
||||||
|
|
||||||
|
"tabbable": ["tabbable@6.2.0", "", {}, "sha512-Cat63mxsVJlzYvN51JmVXIgNoUokrIaT2zLclCXjRd8boZ0004U4KCs/sToJ75C6sdlByWxpYnb5Boif1VSFew=="],
|
||||||
|
|
||||||
|
"tailwind-merge": ["tailwind-merge@3.3.1", "", {}, "sha512-gBXpgUm/3rp1lMZZrM/w7D8GKqshif0zAymAhbCyIt8KMe+0v9DQ7cdYLR4FHH/cKpdTXb+A/tKKU3eolfsI+g=="],
|
||||||
|
|
||||||
|
"tailwind-variants": ["tailwind-variants@3.1.1", "", { "peerDependencies": { "tailwind-merge": ">=3.0.0", "tailwindcss": "*" }, "optionalPeers": ["tailwind-merge"] }, "sha512-ftLXe3krnqkMHsuBTEmaVUXYovXtPyTK7ckEfDRXS8PBZx0bAUas+A0jYxuKA5b8qg++wvQ3d2MQ7l/xeZxbZQ=="],
|
||||||
|
|
||||||
|
"tailwindcss": ["tailwindcss@4.1.13", "", {}, "sha512-i+zidfmTqtwquj4hMEwdjshYYgMbOrPzb9a0M3ZgNa0JMoZeFC6bxZvO8yr8ozS6ix2SDz0+mvryPeBs2TFE+w=="],
|
||||||
|
|
||||||
|
"tapable": ["tapable@2.2.3", "", {}, "sha512-ZL6DDuAlRlLGghwcfmSn9sK3Hr6ArtyudlSAiCqQ6IfE+b+HHbydbYDIG15IfS5do+7XQQBdBiubF/cV2dnDzg=="],
|
||||||
|
|
||||||
|
"tar": ["tar@7.5.1", "", { "dependencies": { "@isaacs/fs-minipass": "^4.0.0", "chownr": "^3.0.0", "minipass": "^7.1.2", "minizlib": "^3.1.0", "yallist": "^5.0.0" } }, "sha512-nlGpxf+hv0v7GkWBK2V9spgactGOp0qvfWRxUMjqHyzrt3SgwE48DIv/FhqPHJYLHpgW1opq3nERbz5Anq7n1g=="],
|
||||||
|
|
||||||
|
"tinyglobby": ["tinyglobby@0.2.15", "", { "dependencies": { "fdir": "^6.5.0", "picomatch": "^4.0.3" } }, "sha512-j2Zq4NyQYG5XMST4cbs02Ak8iJUdxRM0XI5QyxXuZOzKOINmWurp3smXu3y5wDcJrptwpSjgXHzIQxR0omXljQ=="],
|
||||||
|
|
||||||
|
"totalist": ["totalist@3.0.1", "", {}, "sha512-sf4i37nQ2LBx4m3wB74y+ubopq6W/dIzXg0FDGjsYnZHVa1Da8FH853wlL2gtUhg+xJXjfk3kUZS3BRoQeoQBQ=="],
|
||||||
|
|
||||||
|
"tslib": ["tslib@2.8.1", "", {}, "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w=="],
|
||||||
|
|
||||||
|
"tw-animate-css": ["tw-animate-css@1.4.0", "", {}, "sha512-7bziOlRqH0hJx80h/3mbicLW7o8qLsH5+RaLR2t+OHM3D0JlWGODQKQ4cxbK7WlvmUxpcj6Kgu6EKqjrGFe3QQ=="],
|
||||||
|
|
||||||
|
"typescript": ["typescript@5.9.2", "", { "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" } }, "sha512-CWBzXQrc/qOkhidw1OzBTQuYRbfyxDXJMVJ1XNwUHGROVmuaeiEm3OslpZ1RV96d7SKKjZKrSJu3+t/xlw3R9A=="],
|
||||||
|
|
||||||
|
"vite": ["vite@7.1.7", "", { "dependencies": { "esbuild": "^0.25.0", "fdir": "^6.5.0", "picomatch": "^4.0.3", "postcss": "^8.5.6", "rollup": "^4.43.0", "tinyglobby": "^0.2.15" }, "optionalDependencies": { "fsevents": "~2.3.3" }, "peerDependencies": { "@types/node": "^20.19.0 || >=22.12.0", "jiti": ">=1.21.0", "less": "^4.0.0", "lightningcss": "^1.21.0", "sass": "^1.70.0", "sass-embedded": "^1.70.0", "stylus": ">=0.54.8", "sugarss": "^5.0.0", "terser": "^5.16.0", "tsx": "^4.8.1", "yaml": "^2.4.2" }, "optionalPeers": ["@types/node", "jiti", "less", "lightningcss", "sass", "sass-embedded", "stylus", "sugarss", "terser", "tsx", "yaml"], "bin": { "vite": "bin/vite.js" } }, "sha512-VbA8ScMvAISJNJVbRDTJdCwqQoAareR/wutevKanhR2/1EkoXVZVkkORaYm/tNVCjP/UDTKtcw3bAkwOUdedmA=="],
|
||||||
|
|
||||||
|
"vitefu": ["vitefu@1.1.1", "", { "peerDependencies": { "vite": "^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0-beta.0" }, "optionalPeers": ["vite"] }, "sha512-B/Fegf3i8zh0yFbpzZ21amWzHmuNlLlmJT6n7bu5e+pCHUKQIfXSYokrqOBGEMMe9UG2sostKQF9mml/vYaWJQ=="],
|
||||||
|
|
||||||
|
"yallist": ["yallist@5.0.0", "", {}, "sha512-YgvUTfwqyc7UXVMrB+SImsVYSmTS8X/tSrtdNZMImM+n7+QTriRXyXim0mBrTXNeqzVF0KWGgHPeiyViFFrNDw=="],
|
||||||
|
|
||||||
|
"zimmerframe": ["zimmerframe@1.1.4", "", {}, "sha512-B58NGBEoc8Y9MWWCQGl/gq9xBCe4IiKM0a2x7GZdQKOW5Exr8S1W24J6OgM1njK8xCRGvAJIL/MxXHf6SkmQKQ=="],
|
||||||
|
|
||||||
|
"@rollup/plugin-commonjs/is-reference": ["is-reference@1.2.1", "", { "dependencies": { "@types/estree": "*" } }, "sha512-U82MsXXiFIrjCK4otLT+o2NA2Cd2g5MLoOVXUZjIOhLurrRxpEXzI8O0KZHr3IjLvlAH1kTPYSuqer5T9ZVBKQ=="],
|
||||||
|
|
||||||
|
"@tailwindcss/oxide-wasm32-wasi/@emnapi/core": ["@emnapi/core@1.5.0", "", { "dependencies": { "@emnapi/wasi-threads": "1.1.0", "tslib": "^2.4.0" }, "bundled": true }, "sha512-sbP8GzB1WDzacS8fgNPpHlp6C9VZe+SJP3F90W9rLemaQj2PzIuTEl1qDOYQf58YIpyjViI24y9aPWCjEzY2cg=="],
|
||||||
|
|
||||||
|
"@tailwindcss/oxide-wasm32-wasi/@emnapi/runtime": ["@emnapi/runtime@1.5.0", "", { "dependencies": { "tslib": "^2.4.0" }, "bundled": true }, "sha512-97/BJ3iXHww3djw6hYIfErCZFee7qCtrneuLa20UXFCOTCfBM2cvQHjWJ2EG0s0MtdNwInarqCTz35i4wWXHsQ=="],
|
||||||
|
|
||||||
|
"@tailwindcss/oxide-wasm32-wasi/@emnapi/wasi-threads": ["@emnapi/wasi-threads@1.1.0", "", { "dependencies": { "tslib": "^2.4.0" }, "bundled": true }, "sha512-WI0DdZ8xFSbgMjR1sFsKABJ/C5OnRrjT06JXbZKexJGrDuPTzZdDYfFlsgcCXCyf+suG5QU2e/y1Wo2V/OapLQ=="],
|
||||||
|
|
||||||
|
"@tailwindcss/oxide-wasm32-wasi/@napi-rs/wasm-runtime": ["@napi-rs/wasm-runtime@0.2.12", "", { "dependencies": { "@emnapi/core": "^1.4.3", "@emnapi/runtime": "^1.4.3", "@tybys/wasm-util": "^0.10.0" }, "bundled": true }, "sha512-ZVWUcfwY4E/yPitQJl481FjFo3K22D6qF0DuFH6Y/nbnE11GY5uguDxZMGXPQ8WQ0128MXQD7TnfHyK4oWoIJQ=="],
|
||||||
|
|
||||||
|
"@tailwindcss/oxide-wasm32-wasi/@tybys/wasm-util": ["@tybys/wasm-util@0.10.1", "", { "dependencies": { "tslib": "^2.4.0" }, "bundled": true }, "sha512-9tTaPJLSiejZKx+Bmog4uSubteqTvFrVrURwkmHixBo0G4seD0zUxp98E1DzUBJxLQ3NPwXrGKDiVjwx/DpPsg=="],
|
||||||
|
|
||||||
|
"@tailwindcss/oxide-wasm32-wasi/tslib": ["tslib@2.8.1", "", { "bundled": true }, "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w=="],
|
||||||
|
|
||||||
|
"mode-watcher/runed": ["runed@0.25.0", "", { "dependencies": { "esm-env": "^1.0.0" }, "peerDependencies": { "svelte": "^5.7.0" } }, "sha512-7+ma4AG9FT2sWQEA0Egf6mb7PBT2vHyuHail1ie8ropfSjvZGtEAx8YTmUjv/APCsdRRxEVvArNjALk9zFSOrg=="],
|
||||||
|
|
||||||
|
"mode-watcher/svelte-toolbelt": ["svelte-toolbelt@0.7.1", "", { "dependencies": { "clsx": "^2.1.1", "runed": "^0.23.2", "style-to-object": "^1.0.8" }, "peerDependencies": { "svelte": "^5.0.0" } }, "sha512-HcBOcR17Vx9bjaOceUvxkY3nGmbBmCBBbuWLLEWO6jtmWH8f/QoWmbyUfQZrpDINH39en1b8mptfPQT9VKQ1xQ=="],
|
||||||
|
|
||||||
|
"svelte-sonner/runed": ["runed@0.28.0", "", { "dependencies": { "esm-env": "^1.0.0" }, "peerDependencies": { "svelte": "^5.7.0" } }, "sha512-k2xx7RuO9hWcdd9f+8JoBeqWtYrm5CALfgpkg2YDB80ds/QE4w0qqu34A7fqiAwiBBSBQOid7TLxwxVC27ymWQ=="],
|
||||||
|
|
||||||
|
"svelte-toolbelt/runed": ["runed@0.29.2", "", { "dependencies": { "esm-env": "^1.0.0" }, "peerDependencies": { "svelte": "^5.7.0" } }, "sha512-0cq6cA6sYGZwl/FvVqjx9YN+1xEBu9sDDyuWdDW1yWX7JF2wmvmVKfH+hVCZs+csW+P3ARH92MjI3H9QTagOQA=="],
|
||||||
|
|
||||||
|
"mode-watcher/svelte-toolbelt/runed": ["runed@0.23.4", "", { "dependencies": { "esm-env": "^1.0.0" }, "peerDependencies": { "svelte": "^5.7.0" } }, "sha512-9q8oUiBYeXIDLWNK5DfCWlkL0EW3oGbk845VdKlPeia28l751VpfesaB/+7pI6rnbx1I6rqoZ2fZxptOJLxILA=="],
|
||||||
|
}
|
||||||
|
}
|
||||||
16
web/components.json
Normal file
16
web/components.json
Normal file
@ -0,0 +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"
|
||||||
|
}
|
||||||
35
web/package.json
Normal file
35
web/package.json
Normal file
@ -0,0 +1,35 @@
|
|||||||
|
{
|
||||||
|
"name": "web",
|
||||||
|
"private": true,
|
||||||
|
"version": "0.0.1",
|
||||||
|
"type": "module",
|
||||||
|
"scripts": {
|
||||||
|
"dev": "vite dev",
|
||||||
|
"build": "vite build",
|
||||||
|
"preview": "vite preview",
|
||||||
|
"prepare": "svelte-kit sync || echo ''",
|
||||||
|
"check": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json",
|
||||||
|
"check:watch": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json --watch"
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"@internationalized/date": "^3.8.1",
|
||||||
|
"@lucide/svelte": "^0.544.0",
|
||||||
|
"@sveltejs/adapter-auto": "^6.0.0",
|
||||||
|
"@sveltejs/adapter-node": "^5.3.2",
|
||||||
|
"@sveltejs/kit": "^2.22.0",
|
||||||
|
"@sveltejs/vite-plugin-svelte": "^6.0.0",
|
||||||
|
"@tailwindcss/vite": "^4.0.0",
|
||||||
|
"bits-ui": "^2.11.0",
|
||||||
|
"clsx": "^2.1.1",
|
||||||
|
"mode-watcher": "^1.1.0",
|
||||||
|
"svelte": "^5.0.0",
|
||||||
|
"svelte-check": "^4.0.0",
|
||||||
|
"svelte-sonner": "^1.0.5",
|
||||||
|
"tailwind-merge": "^3.3.1",
|
||||||
|
"tailwind-variants": "^3.1.1",
|
||||||
|
"tailwindcss": "^4.0.0",
|
||||||
|
"tw-animate-css": "^1.4.0",
|
||||||
|
"typescript": "^5.0.0",
|
||||||
|
"vite": "^7.0.4"
|
||||||
|
}
|
||||||
|
}
|
||||||
121
web/src/app.css
Normal file
121
web/src/app.css
Normal file
@ -0,0 +1,121 @@
|
|||||||
|
@import "tailwindcss";
|
||||||
|
|
||||||
|
@import "tw-animate-css";
|
||||||
|
|
||||||
|
@custom-variant dark (&:is(.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);
|
||||||
|
}
|
||||||
|
|
||||||
|
.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(53.943% 0.20646 265.678);
|
||||||
|
--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);
|
||||||
|
}
|
||||||
|
|
||||||
|
@theme inline {
|
||||||
|
--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);
|
||||||
|
}
|
||||||
|
|
||||||
|
@layer base {
|
||||||
|
* {
|
||||||
|
@apply border-border outline-ring/50;
|
||||||
|
}
|
||||||
|
body {
|
||||||
|
@apply bg-background text-foreground;
|
||||||
|
}
|
||||||
|
}
|
||||||
13
web/src/app.d.ts
vendored
Normal file
13
web/src/app.d.ts
vendored
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
// See https://svelte.dev/docs/kit/types#app.d.ts
|
||||||
|
// for information about these interfaces
|
||||||
|
declare global {
|
||||||
|
namespace App {
|
||||||
|
// interface Error {}
|
||||||
|
// interface Locals {}
|
||||||
|
// interface PageData {}
|
||||||
|
// interface PageState {}
|
||||||
|
// interface Platform {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export {};
|
||||||
11
web/src/app.html
Normal file
11
web/src/app.html
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
<!doctype html>
|
||||||
|
<html lang="en" class="dark">
|
||||||
|
<head>
|
||||||
|
<meta charset="utf-8" />
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||||
|
%sveltekit.head%
|
||||||
|
</head>
|
||||||
|
<body data-sveltekit-preload-data="hover">
|
||||||
|
<div style="display: contents">%sveltekit.body%</div>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
171
web/src/lib/AppSidebar.svelte
Normal file
171
web/src/lib/AppSidebar.svelte
Normal file
@ -0,0 +1,171 @@
|
|||||||
|
<script lang="ts">
|
||||||
|
import SearchForm from "./search-form.svelte";
|
||||||
|
import VersionSwitcher from "./switcher.svelte";
|
||||||
|
import * as Sidebar from "$lib/components/ui/sidebar/index.js";
|
||||||
|
import type { ComponentProps } from "svelte";
|
||||||
|
import { page } from "$app/state";
|
||||||
|
let { ref = $bindable(null), ...restProps }: ComponentProps<typeof Sidebar.Root> = $props();
|
||||||
|
|
||||||
|
type NavData = {
|
||||||
|
navMain: {
|
||||||
|
title: string;
|
||||||
|
url: string;
|
||||||
|
items: {
|
||||||
|
title: string;
|
||||||
|
url: string;
|
||||||
|
isActive?: boolean;
|
||||||
|
}[];
|
||||||
|
}[];
|
||||||
|
};
|
||||||
|
|
||||||
|
const isAdmin = $derived(page.url.pathname.startsWith("/admin"));
|
||||||
|
const regularData: NavData = $state({
|
||||||
|
navMain: [
|
||||||
|
// {
|
||||||
|
// title: "Empfang",
|
||||||
|
// url: "/entry",
|
||||||
|
// items: [
|
||||||
|
// {
|
||||||
|
// title: "Empfang",
|
||||||
|
// url: "/entry"
|
||||||
|
// },
|
||||||
|
// ],
|
||||||
|
// },
|
||||||
|
{
|
||||||
|
title: "Räume",
|
||||||
|
url: "#",
|
||||||
|
items: [
|
||||||
|
{
|
||||||
|
title: "1",
|
||||||
|
url: "/room/1",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: "2",
|
||||||
|
url: "/room/2",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: "4",
|
||||||
|
url: "/room/4",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: "5",
|
||||||
|
url: "/room/5",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: "6",
|
||||||
|
url: "/room/6",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: "7",
|
||||||
|
url: "/room/7",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: "8",
|
||||||
|
url: "/room/8",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: "Roe",
|
||||||
|
url: "/room/Roe",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
// {
|
||||||
|
// title: "Tickets",
|
||||||
|
// url: "#",
|
||||||
|
// items: [
|
||||||
|
// {
|
||||||
|
// title: "001",
|
||||||
|
// url: "/ticket/001",
|
||||||
|
// },
|
||||||
|
// {
|
||||||
|
// title: "002",
|
||||||
|
// url: "/ticket/002",
|
||||||
|
// },
|
||||||
|
// ],
|
||||||
|
// },
|
||||||
|
{
|
||||||
|
title: "Mehr",
|
||||||
|
url: "#",
|
||||||
|
items: [
|
||||||
|
{
|
||||||
|
title: "Display",
|
||||||
|
url: "/display",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: "Logs",
|
||||||
|
url: "/log",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: "Admin",
|
||||||
|
url: "/admin",
|
||||||
|
},
|
||||||
|
]
|
||||||
|
}
|
||||||
|
],
|
||||||
|
});
|
||||||
|
const adminData: NavData = {
|
||||||
|
navMain: [
|
||||||
|
{
|
||||||
|
title: "Admin",
|
||||||
|
url: "/admin",
|
||||||
|
items: [
|
||||||
|
{
|
||||||
|
title: "Räume",
|
||||||
|
url: "#",
|
||||||
|
isActive: true
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: "Mehr",
|
||||||
|
url: "#",
|
||||||
|
items: [
|
||||||
|
{
|
||||||
|
title: "Display",
|
||||||
|
url: "/display"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: "Logs",
|
||||||
|
url: "/log",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: "Admin",
|
||||||
|
url: "/",
|
||||||
|
isActive: true
|
||||||
|
},
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
};
|
||||||
|
|
||||||
|
const data = $derived(isAdmin ? adminData : regularData);
|
||||||
|
</script>
|
||||||
|
<Sidebar.Root {...restProps} bind:ref variant="floating">
|
||||||
|
<Sidebar.Header>
|
||||||
|
<!-- <VersionSwitcher versions={data.versions} defaultVersion={data.versions[0]} /> -->
|
||||||
|
<h2 style="text-align: center; font-weight: bold; font-size: 1.8em;">⏭️ NextUp</h2>
|
||||||
|
<SearchForm />
|
||||||
|
</Sidebar.Header>
|
||||||
|
<Sidebar.Content>
|
||||||
|
<!-- We create a Sidebar.Group for each parent. -->
|
||||||
|
{#each data.navMain as group (group.title)}
|
||||||
|
<Sidebar.Group style={group.title === "Mehr" ? "margin-top: auto;" : ""}>
|
||||||
|
<Sidebar.GroupLabel>{group.title}</Sidebar.GroupLabel>
|
||||||
|
<Sidebar.GroupContent>
|
||||||
|
<Sidebar.Menu>
|
||||||
|
{#each group.items as item (item.title)}
|
||||||
|
<Sidebar.MenuItem>
|
||||||
|
<Sidebar.MenuButton isActive={item.isActive == undefined ? page.url.pathname === item.url : item.isActive!}>
|
||||||
|
{#snippet child({ props })}
|
||||||
|
<a href={item.url} {...props}>{item.title}</a>
|
||||||
|
{/snippet}
|
||||||
|
</Sidebar.MenuButton>
|
||||||
|
</Sidebar.MenuItem>
|
||||||
|
{/each}
|
||||||
|
</Sidebar.Menu>
|
||||||
|
</Sidebar.GroupContent>
|
||||||
|
</Sidebar.Group>
|
||||||
|
{/each}
|
||||||
|
</Sidebar.Content>
|
||||||
|
<Sidebar.Rail />
|
||||||
|
</Sidebar.Root>
|
||||||
24
web/src/lib/ConnectionLostDialog.svelte
Normal file
24
web/src/lib/ConnectionLostDialog.svelte
Normal file
@ -0,0 +1,24 @@
|
|||||||
|
<script lang="ts">
|
||||||
|
import { browser } from "$app/environment";
|
||||||
|
import * as AlertDialog from "$lib/components/ui/alert-dialog/index.js";
|
||||||
|
import { onMount } from "svelte";
|
||||||
|
|
||||||
|
onMount(() => {
|
||||||
|
if(!browser) return;
|
||||||
|
|
||||||
|
setTimeout(() => {
|
||||||
|
location.reload();
|
||||||
|
}, 10000);
|
||||||
|
})
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<AlertDialog.Root open>
|
||||||
|
<AlertDialog.Content>
|
||||||
|
<AlertDialog.Header>
|
||||||
|
<AlertDialog.Title>Verbindung verloren</AlertDialog.Title>
|
||||||
|
<AlertDialog.Description>
|
||||||
|
Die Verbindung zum Server wurde unterbrochen.<br>Die Verbindung wird automatisch wiederhergestellt.
|
||||||
|
</AlertDialog.Description>
|
||||||
|
</AlertDialog.Header>
|
||||||
|
</AlertDialog.Content>
|
||||||
|
</AlertDialog.Root>
|
||||||
110
web/src/lib/Spinner.svelte
Normal file
110
web/src/lib/Spinner.svelte
Normal file
@ -0,0 +1,110 @@
|
|||||||
|
<div class="progress-ring spinner-indeter">
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16" class="spinner-ring">
|
||||||
|
<circle class="static" cx="50%" cy="50%" r="43.75%"></circle>
|
||||||
|
<circle class="dynamic" cx="50%" cy="50%" r="43.75%"></circle>
|
||||||
|
</svg>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
@keyframes progressring {
|
||||||
|
0% {
|
||||||
|
stroke-dasharray: 0% 275%;
|
||||||
|
transform: rotate(-90deg);
|
||||||
|
}
|
||||||
|
|
||||||
|
50% {
|
||||||
|
transform: rotate(360deg);
|
||||||
|
stroke-dasharray: 137.5% 137.5%;
|
||||||
|
}
|
||||||
|
|
||||||
|
100% {
|
||||||
|
stroke-dasharray: 0% 275%;
|
||||||
|
transform: rotate(990deg);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes progressring-width {
|
||||||
|
0% {
|
||||||
|
stroke-dasharray: 0% 275%;
|
||||||
|
transform: rotate(-90deg);
|
||||||
|
stroke-width: 12.5%;
|
||||||
|
}
|
||||||
|
|
||||||
|
50% {
|
||||||
|
transform: rotate(360deg);
|
||||||
|
stroke-dasharray: 137.5% 137.5%;
|
||||||
|
stroke-width: 5%;
|
||||||
|
}
|
||||||
|
|
||||||
|
100% {
|
||||||
|
stroke-dasharray: 0% 275%;
|
||||||
|
transform: rotate(990deg);
|
||||||
|
stroke-width: 12.5%;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.progress-ring {
|
||||||
|
width: 64px;
|
||||||
|
height: 64px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.progress-ring > .spinner-ring {
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.progress-ring > .spinner-ring > .static {
|
||||||
|
fill: none;
|
||||||
|
-webkit-transform: rotate(-90deg);
|
||||||
|
-moz-transform: rotate(-90deg);
|
||||||
|
-o-transform: rotate(-90deg);
|
||||||
|
transform: rotate(-90deg);
|
||||||
|
-webkit-transition: all 0.2s ease-in-out;
|
||||||
|
-moz-transition: all 0.2s ease-in-out;
|
||||||
|
-o-transition: all 0.2s ease-in-out;
|
||||||
|
transition: all 0.2s ease-in-out;
|
||||||
|
stroke-width: 12.5%;
|
||||||
|
stroke-linecap: round;
|
||||||
|
-webkit-transform-origin: 50% 50%;
|
||||||
|
-moz-transform-origin: 50% 50%;
|
||||||
|
-o-transform-origin: 50% 50%;
|
||||||
|
transform-origin: 50% 50%;
|
||||||
|
width: 100%;
|
||||||
|
display: flex;
|
||||||
|
position: relative;
|
||||||
|
overflow: hidden;
|
||||||
|
align-items: center;
|
||||||
|
border-radius: 50%;
|
||||||
|
height: 100%;
|
||||||
|
/* stroke: #ccccccff; */
|
||||||
|
stroke: #333333ff;
|
||||||
|
}
|
||||||
|
|
||||||
|
.progress-ring.spinner-indeter > .spinner-ring > .dynamic {
|
||||||
|
-webkit-animation: progressring 2s linear infinite;
|
||||||
|
-moz-animation: progressring 2s linear infinite;
|
||||||
|
-o-animation: progressring 2s linear infinite;
|
||||||
|
animation: progressring 2s linear infinite;
|
||||||
|
}
|
||||||
|
|
||||||
|
.progress-ring > .spinner-ring > .dynamic {
|
||||||
|
fill: none;
|
||||||
|
-webkit-transform: rotate(-90deg);
|
||||||
|
-moz-transform: rotate(-90deg);
|
||||||
|
-o-transform: rotate(-90deg);
|
||||||
|
transform: rotate(-90deg);
|
||||||
|
-webkit-transition: all 0.2s ease-in-out;
|
||||||
|
-moz-transition: all 0.2s ease-in-out;
|
||||||
|
-o-transition: all 0.2s ease-in-out;
|
||||||
|
transition: all 0.2s ease-in-out;
|
||||||
|
stroke-width: 12.5%;
|
||||||
|
stroke-linecap: round;
|
||||||
|
-webkit-transform-origin: 50% 50%;
|
||||||
|
-moz-transform-origin: 50% 50%;
|
||||||
|
-o-transform-origin: 50% 50%;
|
||||||
|
transform-origin: 50% 50%;
|
||||||
|
height: 100%;
|
||||||
|
border-radius: 50%;
|
||||||
|
stroke: #0064ffff;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
1
web/src/lib/assets/favicon.svg
Normal file
1
web/src/lib/assets/favicon.svg
Normal file
@ -0,0 +1 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" width="107" height="128" viewBox="0 0 107 128"><title>svelte-logo</title><path d="M94.157 22.819c-10.4-14.885-30.94-19.297-45.792-9.835L22.282 29.608A29.92 29.92 0 0 0 8.764 49.65a31.5 31.5 0 0 0 3.108 20.231 30 30 0 0 0-4.477 11.183 31.9 31.9 0 0 0 5.448 24.116c10.402 14.887 30.942 19.297 45.791 9.835l26.083-16.624A29.92 29.92 0 0 0 98.235 78.35a31.53 31.53 0 0 0-3.105-20.232 30 30 0 0 0 4.474-11.182 31.88 31.88 0 0 0-5.447-24.116" style="fill:#ff3e00"/><path d="M45.817 106.582a20.72 20.72 0 0 1-22.237-8.243 19.17 19.17 0 0 1-3.277-14.503 18 18 0 0 1 .624-2.435l.49-1.498 1.337.981a33.6 33.6 0 0 0 10.203 5.098l.97.294-.09.968a5.85 5.85 0 0 0 1.052 3.878 6.24 6.24 0 0 0 6.695 2.485 5.8 5.8 0 0 0 1.603-.704L69.27 76.28a5.43 5.43 0 0 0 2.45-3.631 5.8 5.8 0 0 0-.987-4.371 6.24 6.24 0 0 0-6.698-2.487 5.7 5.7 0 0 0-1.6.704l-9.953 6.345a19 19 0 0 1-5.296 2.326 20.72 20.72 0 0 1-22.237-8.243 19.17 19.17 0 0 1-3.277-14.502 17.99 17.99 0 0 1 8.13-12.052l26.081-16.623a19 19 0 0 1 5.3-2.329 20.72 20.72 0 0 1 22.237 8.243 19.17 19.17 0 0 1 3.277 14.503 18 18 0 0 1-.624 2.435l-.49 1.498-1.337-.98a33.6 33.6 0 0 0-10.203-5.1l-.97-.294.09-.968a5.86 5.86 0 0 0-1.052-3.878 6.24 6.24 0 0 0-6.696-2.485 5.8 5.8 0 0 0-1.602.704L37.73 51.72a5.42 5.42 0 0 0-2.449 3.63 5.79 5.79 0 0 0 .986 4.372 6.24 6.24 0 0 0 6.698 2.486 5.8 5.8 0 0 0 1.602-.704l9.952-6.342a19 19 0 0 1 5.295-2.328 20.72 20.72 0 0 1 22.237 8.242 19.17 19.17 0 0 1 3.277 14.503 18 18 0 0 1-8.13 12.053l-26.081 16.622a19 19 0 0 1-5.3 2.328" style="fill:#fff"/></svg>
|
||||||
|
After Width: | Height: | Size: 1.5 KiB |
@ -0,0 +1,18 @@
|
|||||||
|
<script lang="ts">
|
||||||
|
import { AlertDialog as AlertDialogPrimitive } from "bits-ui";
|
||||||
|
import { buttonVariants } from "$lib/components/ui/button/index.js";
|
||||||
|
import { cn } from "$lib/utils.js";
|
||||||
|
|
||||||
|
let {
|
||||||
|
ref = $bindable(null),
|
||||||
|
class: className,
|
||||||
|
...restProps
|
||||||
|
}: AlertDialogPrimitive.ActionProps = $props();
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<AlertDialogPrimitive.Action
|
||||||
|
bind:ref
|
||||||
|
data-slot="alert-dialog-action"
|
||||||
|
class={cn(buttonVariants(), className)}
|
||||||
|
{...restProps}
|
||||||
|
/>
|
||||||
@ -0,0 +1,18 @@
|
|||||||
|
<script lang="ts">
|
||||||
|
import { AlertDialog as AlertDialogPrimitive } from "bits-ui";
|
||||||
|
import { buttonVariants } from "$lib/components/ui/button/index.js";
|
||||||
|
import { cn } from "$lib/utils.js";
|
||||||
|
|
||||||
|
let {
|
||||||
|
ref = $bindable(null),
|
||||||
|
class: className,
|
||||||
|
...restProps
|
||||||
|
}: AlertDialogPrimitive.CancelProps = $props();
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<AlertDialogPrimitive.Cancel
|
||||||
|
bind:ref
|
||||||
|
data-slot="alert-dialog-cancel"
|
||||||
|
class={cn(buttonVariants({ variant: "outline" }), className)}
|
||||||
|
{...restProps}
|
||||||
|
/>
|
||||||
@ -0,0 +1,27 @@
|
|||||||
|
<script lang="ts">
|
||||||
|
import { AlertDialog as AlertDialogPrimitive } from "bits-ui";
|
||||||
|
import AlertDialogOverlay from "./alert-dialog-overlay.svelte";
|
||||||
|
import { cn, type WithoutChild, type WithoutChildrenOrChild } from "$lib/utils.js";
|
||||||
|
|
||||||
|
let {
|
||||||
|
ref = $bindable(null),
|
||||||
|
class: className,
|
||||||
|
portalProps,
|
||||||
|
...restProps
|
||||||
|
}: WithoutChild<AlertDialogPrimitive.ContentProps> & {
|
||||||
|
portalProps?: WithoutChildrenOrChild<AlertDialogPrimitive.PortalProps>;
|
||||||
|
} = $props();
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<AlertDialogPrimitive.Portal {...portalProps}>
|
||||||
|
<AlertDialogOverlay />
|
||||||
|
<AlertDialogPrimitive.Content
|
||||||
|
bind:ref
|
||||||
|
data-slot="alert-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 left-[50%] top-[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}
|
||||||
|
/>
|
||||||
|
</AlertDialogPrimitive.Portal>
|
||||||
@ -0,0 +1,17 @@
|
|||||||
|
<script lang="ts">
|
||||||
|
import { AlertDialog as AlertDialogPrimitive } from "bits-ui";
|
||||||
|
import { cn } from "$lib/utils.js";
|
||||||
|
|
||||||
|
let {
|
||||||
|
ref = $bindable(null),
|
||||||
|
class: className,
|
||||||
|
...restProps
|
||||||
|
}: AlertDialogPrimitive.DescriptionProps = $props();
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<AlertDialogPrimitive.Description
|
||||||
|
bind:ref
|
||||||
|
data-slot="alert-dialog-description"
|
||||||
|
class={cn("text-muted-foreground text-sm", className)}
|
||||||
|
{...restProps}
|
||||||
|
/>
|
||||||
@ -0,0 +1,20 @@
|
|||||||
|
<script lang="ts">
|
||||||
|
import { cn, type WithElementRef } from "$lib/utils.js";
|
||||||
|
import type { HTMLAttributes } from "svelte/elements";
|
||||||
|
|
||||||
|
let {
|
||||||
|
ref = $bindable(null),
|
||||||
|
class: className,
|
||||||
|
children,
|
||||||
|
...restProps
|
||||||
|
}: WithElementRef<HTMLAttributes<HTMLDivElement>> = $props();
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<div
|
||||||
|
bind:this={ref}
|
||||||
|
data-slot="alert-dialog-footer"
|
||||||
|
class={cn("flex flex-col-reverse gap-2 sm:flex-row sm:justify-end", className)}
|
||||||
|
{...restProps}
|
||||||
|
>
|
||||||
|
{@render children?.()}
|
||||||
|
</div>
|
||||||
@ -0,0 +1,20 @@
|
|||||||
|
<script lang="ts">
|
||||||
|
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();
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<div
|
||||||
|
bind:this={ref}
|
||||||
|
data-slot="alert-dialog-header"
|
||||||
|
class={cn("flex flex-col gap-2 text-center sm:text-left", className)}
|
||||||
|
{...restProps}
|
||||||
|
>
|
||||||
|
{@render children?.()}
|
||||||
|
</div>
|
||||||
@ -0,0 +1,20 @@
|
|||||||
|
<script lang="ts">
|
||||||
|
import { AlertDialog as AlertDialogPrimitive } from "bits-ui";
|
||||||
|
import { cn } from "$lib/utils.js";
|
||||||
|
|
||||||
|
let {
|
||||||
|
ref = $bindable(null),
|
||||||
|
class: className,
|
||||||
|
...restProps
|
||||||
|
}: AlertDialogPrimitive.OverlayProps = $props();
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<AlertDialogPrimitive.Overlay
|
||||||
|
bind:ref
|
||||||
|
data-slot="alert-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}
|
||||||
|
/>
|
||||||
@ -0,0 +1,17 @@
|
|||||||
|
<script lang="ts">
|
||||||
|
import { AlertDialog as AlertDialogPrimitive } from "bits-ui";
|
||||||
|
import { cn } from "$lib/utils.js";
|
||||||
|
|
||||||
|
let {
|
||||||
|
ref = $bindable(null),
|
||||||
|
class: className,
|
||||||
|
...restProps
|
||||||
|
}: AlertDialogPrimitive.TitleProps = $props();
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<AlertDialogPrimitive.Title
|
||||||
|
bind:ref
|
||||||
|
data-slot="alert-dialog-title"
|
||||||
|
class={cn("text-lg font-semibold", className)}
|
||||||
|
{...restProps}
|
||||||
|
/>
|
||||||
@ -0,0 +1,7 @@
|
|||||||
|
<script lang="ts">
|
||||||
|
import { AlertDialog as AlertDialogPrimitive } from "bits-ui";
|
||||||
|
|
||||||
|
let { ref = $bindable(null), ...restProps }: AlertDialogPrimitive.TriggerProps = $props();
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<AlertDialogPrimitive.Trigger bind:ref data-slot="alert-dialog-trigger" {...restProps} />
|
||||||
39
web/src/lib/components/ui/alert-dialog/index.ts
Normal file
39
web/src/lib/components/ui/alert-dialog/index.ts
Normal file
@ -0,0 +1,39 @@
|
|||||||
|
import { AlertDialog as AlertDialogPrimitive } from "bits-ui";
|
||||||
|
import Trigger from "./alert-dialog-trigger.svelte";
|
||||||
|
import Title from "./alert-dialog-title.svelte";
|
||||||
|
import Action from "./alert-dialog-action.svelte";
|
||||||
|
import Cancel from "./alert-dialog-cancel.svelte";
|
||||||
|
import Footer from "./alert-dialog-footer.svelte";
|
||||||
|
import Header from "./alert-dialog-header.svelte";
|
||||||
|
import Overlay from "./alert-dialog-overlay.svelte";
|
||||||
|
import Content from "./alert-dialog-content.svelte";
|
||||||
|
import Description from "./alert-dialog-description.svelte";
|
||||||
|
|
||||||
|
const Root = AlertDialogPrimitive.Root;
|
||||||
|
const Portal = AlertDialogPrimitive.Portal;
|
||||||
|
|
||||||
|
export {
|
||||||
|
Root,
|
||||||
|
Title,
|
||||||
|
Action,
|
||||||
|
Cancel,
|
||||||
|
Portal,
|
||||||
|
Footer,
|
||||||
|
Header,
|
||||||
|
Trigger,
|
||||||
|
Overlay,
|
||||||
|
Content,
|
||||||
|
Description,
|
||||||
|
//
|
||||||
|
Root as AlertDialog,
|
||||||
|
Title as AlertDialogTitle,
|
||||||
|
Action as AlertDialogAction,
|
||||||
|
Cancel as AlertDialogCancel,
|
||||||
|
Portal as AlertDialogPortal,
|
||||||
|
Footer as AlertDialogFooter,
|
||||||
|
Header as AlertDialogHeader,
|
||||||
|
Trigger as AlertDialogTrigger,
|
||||||
|
Overlay as AlertDialogOverlay,
|
||||||
|
Content as AlertDialogContent,
|
||||||
|
Description as AlertDialogDescription,
|
||||||
|
};
|
||||||
50
web/src/lib/components/ui/badge/badge.svelte
Normal file
50
web/src/lib/components/ui/badge/badge.svelte
Normal file
@ -0,0 +1,50 @@
|
|||||||
|
<script lang="ts" module>
|
||||||
|
import { type VariantProps, tv } from "tailwind-variants";
|
||||||
|
|
||||||
|
export const badgeVariants = 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 w-fit shrink-0 items-center justify-center gap-1 overflow-hidden whitespace-nowrap rounded-md border px-2 py-0.5 text-xs font-medium transition-[color,box-shadow] focus-visible:ring-[3px] [&>svg]:pointer-events-none [&>svg]:size-3",
|
||||||
|
variants: {
|
||||||
|
variant: {
|
||||||
|
default:
|
||||||
|
"bg-primary text-primary-foreground [a&]:hover:bg-primary/90 border-transparent",
|
||||||
|
secondary:
|
||||||
|
"bg-secondary text-secondary-foreground [a&]:hover:bg-secondary/90 border-transparent",
|
||||||
|
destructive:
|
||||||
|
"bg-destructive [a&]:hover:bg-destructive/90 focus-visible:ring-destructive/20 dark:focus-visible:ring-destructive/40 dark:bg-destructive/70 border-transparent text-white",
|
||||||
|
outline: "text-foreground [a&]:hover:bg-accent [a&]:hover:text-accent-foreground",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
defaultVariants: {
|
||||||
|
variant: "default",
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
export type BadgeVariant = VariantProps<typeof badgeVariants>["variant"];
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<script lang="ts">
|
||||||
|
import type { HTMLAnchorAttributes } from "svelte/elements";
|
||||||
|
import { cn, type WithElementRef } from "$lib/utils.js";
|
||||||
|
|
||||||
|
let {
|
||||||
|
ref = $bindable(null),
|
||||||
|
href,
|
||||||
|
class: className,
|
||||||
|
variant = "default",
|
||||||
|
children,
|
||||||
|
...restProps
|
||||||
|
}: WithElementRef<HTMLAnchorAttributes> & {
|
||||||
|
variant?: BadgeVariant;
|
||||||
|
} = $props();
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<svelte:element
|
||||||
|
this={href ? "a" : "span"}
|
||||||
|
bind:this={ref}
|
||||||
|
data-slot="badge"
|
||||||
|
{href}
|
||||||
|
class={cn(badgeVariants({ variant }), className)}
|
||||||
|
{...restProps}
|
||||||
|
>
|
||||||
|
{@render children?.()}
|
||||||
|
</svelte:element>
|
||||||
2
web/src/lib/components/ui/badge/index.ts
Normal file
2
web/src/lib/components/ui/badge/index.ts
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
export { default as Badge } from "./badge.svelte";
|
||||||
|
export { badgeVariants, type BadgeVariant } from "./badge.svelte";
|
||||||
@ -0,0 +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";
|
||||||
|
|
||||||
|
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}
|
||||||
|
>
|
||||||
|
<EllipsisIcon class="size-4" />
|
||||||
|
<span class="sr-only">More</span>
|
||||||
|
</span>
|
||||||
20
web/src/lib/components/ui/breadcrumb/breadcrumb-item.svelte
Normal file
20
web/src/lib/components/ui/breadcrumb/breadcrumb-item.svelte
Normal file
@ -0,0 +1,20 @@
|
|||||||
|
<script lang="ts">
|
||||||
|
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();
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<li
|
||||||
|
bind:this={ref}
|
||||||
|
data-slot="breadcrumb-item"
|
||||||
|
class={cn("inline-flex items-center gap-1.5", className)}
|
||||||
|
{...restProps}
|
||||||
|
>
|
||||||
|
{@render children?.()}
|
||||||
|
</li>
|
||||||
31
web/src/lib/components/ui/breadcrumb/breadcrumb-link.svelte
Normal file
31
web/src/lib/components/ui/breadcrumb/breadcrumb-link.svelte
Normal file
@ -0,0 +1,31 @@
|
|||||||
|
<script lang="ts">
|
||||||
|
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();
|
||||||
|
|
||||||
|
const attrs = $derived({
|
||||||
|
"data-slot": "breadcrumb-link",
|
||||||
|
class: cn("hover:text-foreground transition-colors", className),
|
||||||
|
href,
|
||||||
|
...restProps,
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
{#if child}
|
||||||
|
{@render child({ props: attrs })}
|
||||||
|
{:else}
|
||||||
|
<a bind:this={ref} {...attrs}>
|
||||||
|
{@render children?.()}
|
||||||
|
</a>
|
||||||
|
{/if}
|
||||||
23
web/src/lib/components/ui/breadcrumb/breadcrumb-list.svelte
Normal file
23
web/src/lib/components/ui/breadcrumb/breadcrumb-list.svelte
Normal file
@ -0,0 +1,23 @@
|
|||||||
|
<script lang="ts">
|
||||||
|
import type { HTMLOlAttributes } from "svelte/elements";
|
||||||
|
import { cn, type WithElementRef } from "$lib/utils.js";
|
||||||
|
|
||||||
|
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 break-words text-sm sm:gap-2.5",
|
||||||
|
className
|
||||||
|
)}
|
||||||
|
{...restProps}
|
||||||
|
>
|
||||||
|
{@render children?.()}
|
||||||
|
</ol>
|
||||||
23
web/src/lib/components/ui/breadcrumb/breadcrumb-page.svelte
Normal file
23
web/src/lib/components/ui/breadcrumb/breadcrumb-page.svelte
Normal file
@ -0,0 +1,23 @@
|
|||||||
|
<script lang="ts">
|
||||||
|
import type { HTMLAttributes } from "svelte/elements";
|
||||||
|
import { cn, type WithElementRef } from "$lib/utils.js";
|
||||||
|
|
||||||
|
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}
|
||||||
|
>
|
||||||
|
{@render children?.()}
|
||||||
|
</span>
|
||||||
@ -0,0 +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";
|
||||||
|
|
||||||
|
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}
|
||||||
|
>
|
||||||
|
{#if children}
|
||||||
|
{@render children?.()}
|
||||||
|
{:else}
|
||||||
|
<ChevronRightIcon />
|
||||||
|
{/if}
|
||||||
|
</li>
|
||||||
21
web/src/lib/components/ui/breadcrumb/breadcrumb.svelte
Normal file
21
web/src/lib/components/ui/breadcrumb/breadcrumb.svelte
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
<script lang="ts">
|
||||||
|
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();
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<nav
|
||||||
|
bind:this={ref}
|
||||||
|
data-slot="breadcrumb"
|
||||||
|
class={className}
|
||||||
|
aria-label="breadcrumb"
|
||||||
|
{...restProps}
|
||||||
|
>
|
||||||
|
{@render children?.()}
|
||||||
|
</nav>
|
||||||
25
web/src/lib/components/ui/breadcrumb/index.ts
Normal file
25
web/src/lib/components/ui/breadcrumb/index.ts
Normal file
@ -0,0 +1,25 @@
|
|||||||
|
import Root from "./breadcrumb.svelte";
|
||||||
|
import Ellipsis from "./breadcrumb-ellipsis.svelte";
|
||||||
|
import Item from "./breadcrumb-item.svelte";
|
||||||
|
import Separator from "./breadcrumb-separator.svelte";
|
||||||
|
import Link from "./breadcrumb-link.svelte";
|
||||||
|
import List from "./breadcrumb-list.svelte";
|
||||||
|
import Page from "./breadcrumb-page.svelte";
|
||||||
|
|
||||||
|
export {
|
||||||
|
Root,
|
||||||
|
Ellipsis,
|
||||||
|
Item,
|
||||||
|
Separator,
|
||||||
|
Link,
|
||||||
|
List,
|
||||||
|
Page,
|
||||||
|
//
|
||||||
|
Root as Breadcrumb,
|
||||||
|
Ellipsis as BreadcrumbEllipsis,
|
||||||
|
Item as BreadcrumbItem,
|
||||||
|
Separator as BreadcrumbSeparator,
|
||||||
|
Link as BreadcrumbLink,
|
||||||
|
List as BreadcrumbList,
|
||||||
|
Page as BreadcrumbPage,
|
||||||
|
};
|
||||||
80
web/src/lib/components/ui/button/button.svelte
Normal file
80
web/src/lib/components/ui/button/button.svelte
Normal file
@ -0,0 +1,80 @@
|
|||||||
|
<script lang="ts" module>
|
||||||
|
import { cn, type WithElementRef } from "$lib/utils.js";
|
||||||
|
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 text-sm 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",
|
||||||
|
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-4 hover:underline",
|
||||||
|
},
|
||||||
|
size: {
|
||||||
|
default: "h-9 px-4 py-2 has-[>svg]:px-3",
|
||||||
|
sm: "h-8 gap-1.5 rounded-md px-3 has-[>svg]:px-2.5",
|
||||||
|
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 ButtonProps = WithElementRef<HTMLButtonAttributes> &
|
||||||
|
WithElementRef<HTMLAnchorAttributes> & {
|
||||||
|
variant?: ButtonVariant;
|
||||||
|
size?: ButtonSize;
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<script lang="ts">
|
||||||
|
let {
|
||||||
|
class: className,
|
||||||
|
variant = "default",
|
||||||
|
size = "default",
|
||||||
|
ref = $bindable(null),
|
||||||
|
href = undefined,
|
||||||
|
type = "button",
|
||||||
|
disabled,
|
||||||
|
children,
|
||||||
|
...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>
|
||||||
|
{:else}
|
||||||
|
<button
|
||||||
|
bind:this={ref}
|
||||||
|
data-slot="button"
|
||||||
|
class={cn(buttonVariants({ variant, size }), className)}
|
||||||
|
{type}
|
||||||
|
{disabled}
|
||||||
|
{...restProps}
|
||||||
|
>
|
||||||
|
{@render children?.()}
|
||||||
|
</button>
|
||||||
|
{/if}
|
||||||
17
web/src/lib/components/ui/button/index.ts
Normal file
17
web/src/lib/components/ui/button/index.ts
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
import Root, {
|
||||||
|
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,
|
||||||
|
};
|
||||||
20
web/src/lib/components/ui/card/card-action.svelte
Normal file
20
web/src/lib/components/ui/card/card-action.svelte
Normal file
@ -0,0 +1,20 @@
|
|||||||
|
<script lang="ts">
|
||||||
|
import { cn, type WithElementRef } from "$lib/utils.js";
|
||||||
|
import type { HTMLAttributes } from "svelte/elements";
|
||||||
|
|
||||||
|
let {
|
||||||
|
ref = $bindable(null),
|
||||||
|
class: className,
|
||||||
|
children,
|
||||||
|
...restProps
|
||||||
|
}: WithElementRef<HTMLAttributes<HTMLDivElement>> = $props();
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<div
|
||||||
|
bind:this={ref}
|
||||||
|
data-slot="card-action"
|
||||||
|
class={cn("col-start-2 row-span-2 row-start-1 self-start justify-self-end", className)}
|
||||||
|
{...restProps}
|
||||||
|
>
|
||||||
|
{@render children?.()}
|
||||||
|
</div>
|
||||||
15
web/src/lib/components/ui/card/card-content.svelte
Normal file
15
web/src/lib/components/ui/card/card-content.svelte
Normal file
@ -0,0 +1,15 @@
|
|||||||
|
<script lang="ts">
|
||||||
|
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();
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<div bind:this={ref} data-slot="card-content" class={cn("px-6", className)} {...restProps}>
|
||||||
|
{@render children?.()}
|
||||||
|
</div>
|
||||||
20
web/src/lib/components/ui/card/card-description.svelte
Normal file
20
web/src/lib/components/ui/card/card-description.svelte
Normal file
@ -0,0 +1,20 @@
|
|||||||
|
<script lang="ts">
|
||||||
|
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();
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<p
|
||||||
|
bind:this={ref}
|
||||||
|
data-slot="card-description"
|
||||||
|
class={cn("text-muted-foreground text-sm", className)}
|
||||||
|
{...restProps}
|
||||||
|
>
|
||||||
|
{@render children?.()}
|
||||||
|
</p>
|
||||||
20
web/src/lib/components/ui/card/card-footer.svelte
Normal file
20
web/src/lib/components/ui/card/card-footer.svelte
Normal file
@ -0,0 +1,20 @@
|
|||||||
|
<script lang="ts">
|
||||||
|
import { cn, type WithElementRef } from "$lib/utils.js";
|
||||||
|
import type { HTMLAttributes } from "svelte/elements";
|
||||||
|
|
||||||
|
let {
|
||||||
|
ref = $bindable(null),
|
||||||
|
class: className,
|
||||||
|
children,
|
||||||
|
...restProps
|
||||||
|
}: WithElementRef<HTMLAttributes<HTMLDivElement>> = $props();
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<div
|
||||||
|
bind:this={ref}
|
||||||
|
data-slot="card-footer"
|
||||||
|
class={cn("[.border-t]:pt-6 flex items-center px-6", className)}
|
||||||
|
{...restProps}
|
||||||
|
>
|
||||||
|
{@render children?.()}
|
||||||
|
</div>
|
||||||
23
web/src/lib/components/ui/card/card-header.svelte
Normal file
23
web/src/lib/components/ui/card/card-header.svelte
Normal file
@ -0,0 +1,23 @@
|
|||||||
|
<script lang="ts">
|
||||||
|
import { cn, type WithElementRef } from "$lib/utils.js";
|
||||||
|
import type { HTMLAttributes } from "svelte/elements";
|
||||||
|
|
||||||
|
let {
|
||||||
|
ref = $bindable(null),
|
||||||
|
class: className,
|
||||||
|
children,
|
||||||
|
...restProps
|
||||||
|
}: WithElementRef<HTMLAttributes<HTMLDivElement>> = $props();
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<div
|
||||||
|
bind:this={ref}
|
||||||
|
data-slot="card-header"
|
||||||
|
class={cn(
|
||||||
|
"@container/card-header has-data-[slot=card-action]:grid-cols-[1fr_auto] [.border-b]:pb-6 grid auto-rows-min grid-rows-[auto_auto] items-start gap-1.5 px-6",
|
||||||
|
className
|
||||||
|
)}
|
||||||
|
{...restProps}
|
||||||
|
>
|
||||||
|
{@render children?.()}
|
||||||
|
</div>
|
||||||
20
web/src/lib/components/ui/card/card-title.svelte
Normal file
20
web/src/lib/components/ui/card/card-title.svelte
Normal file
@ -0,0 +1,20 @@
|
|||||||
|
<script lang="ts">
|
||||||
|
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();
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<div
|
||||||
|
bind:this={ref}
|
||||||
|
data-slot="card-title"
|
||||||
|
class={cn("font-semibold leading-none", className)}
|
||||||
|
{...restProps}
|
||||||
|
>
|
||||||
|
{@render children?.()}
|
||||||
|
</div>
|
||||||
23
web/src/lib/components/ui/card/card.svelte
Normal file
23
web/src/lib/components/ui/card/card.svelte
Normal file
@ -0,0 +1,23 @@
|
|||||||
|
<script lang="ts">
|
||||||
|
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();
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<div
|
||||||
|
bind:this={ref}
|
||||||
|
data-slot="card"
|
||||||
|
class={cn(
|
||||||
|
"bg-card text-card-foreground flex flex-col gap-6 rounded-xl border py-6 shadow-sm",
|
||||||
|
className
|
||||||
|
)}
|
||||||
|
{...restProps}
|
||||||
|
>
|
||||||
|
{@render children?.()}
|
||||||
|
</div>
|
||||||
25
web/src/lib/components/ui/card/index.ts
Normal file
25
web/src/lib/components/ui/card/index.ts
Normal file
@ -0,0 +1,25 @@
|
|||||||
|
import Root from "./card.svelte";
|
||||||
|
import Content from "./card-content.svelte";
|
||||||
|
import Description from "./card-description.svelte";
|
||||||
|
import Footer from "./card-footer.svelte";
|
||||||
|
import Header from "./card-header.svelte";
|
||||||
|
import Title from "./card-title.svelte";
|
||||||
|
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,
|
||||||
|
};
|
||||||
7
web/src/lib/components/ui/dialog/dialog-close.svelte
Normal file
7
web/src/lib/components/ui/dialog/dialog-close.svelte
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
<script lang="ts">
|
||||||
|
import { Dialog as DialogPrimitive } from "bits-ui";
|
||||||
|
|
||||||
|
let { ref = $bindable(null), ...restProps }: DialogPrimitive.CloseProps = $props();
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<DialogPrimitive.Close bind:ref data-slot="dialog-close" {...restProps} />
|
||||||
43
web/src/lib/components/ui/dialog/dialog-content.svelte
Normal file
43
web/src/lib/components/ui/dialog/dialog-content.svelte
Normal file
@ -0,0 +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";
|
||||||
|
|
||||||
|
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 left-[50%] top-[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 rounded-xs focus:outline-hidden absolute end-4 top-4 opacity-70 transition-opacity hover:opacity-100 focus:ring-2 focus:ring-offset-2 disabled:pointer-events-none [&_svg:not([class*='size-'])]:size-4 [&_svg]:pointer-events-none [&_svg]:shrink-0"
|
||||||
|
>
|
||||||
|
<XIcon />
|
||||||
|
<span class="sr-only">Close</span>
|
||||||
|
</DialogPrimitive.Close>
|
||||||
|
{/if}
|
||||||
|
</DialogPrimitive.Content>
|
||||||
|
</Dialog.Portal>
|
||||||
17
web/src/lib/components/ui/dialog/dialog-description.svelte
Normal file
17
web/src/lib/components/ui/dialog/dialog-description.svelte
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
<script lang="ts">
|
||||||
|
import { Dialog as DialogPrimitive } from "bits-ui";
|
||||||
|
import { cn } from "$lib/utils.js";
|
||||||
|
|
||||||
|
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}
|
||||||
|
/>
|
||||||
20
web/src/lib/components/ui/dialog/dialog-footer.svelte
Normal file
20
web/src/lib/components/ui/dialog/dialog-footer.svelte
Normal file
@ -0,0 +1,20 @@
|
|||||||
|
<script lang="ts">
|
||||||
|
import { cn, type WithElementRef } from "$lib/utils.js";
|
||||||
|
import type { HTMLAttributes } from "svelte/elements";
|
||||||
|
|
||||||
|
let {
|
||||||
|
ref = $bindable(null),
|
||||||
|
class: className,
|
||||||
|
children,
|
||||||
|
...restProps
|
||||||
|
}: WithElementRef<HTMLAttributes<HTMLDivElement>> = $props();
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<div
|
||||||
|
bind:this={ref}
|
||||||
|
data-slot="dialog-footer"
|
||||||
|
class={cn("flex flex-col-reverse gap-2 sm:flex-row sm:justify-end", className)}
|
||||||
|
{...restProps}
|
||||||
|
>
|
||||||
|
{@render children?.()}
|
||||||
|
</div>
|
||||||
20
web/src/lib/components/ui/dialog/dialog-header.svelte
Normal file
20
web/src/lib/components/ui/dialog/dialog-header.svelte
Normal file
@ -0,0 +1,20 @@
|
|||||||
|
<script lang="ts">
|
||||||
|
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();
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<div
|
||||||
|
bind:this={ref}
|
||||||
|
data-slot="dialog-header"
|
||||||
|
class={cn("flex flex-col gap-2 text-center sm:text-left", className)}
|
||||||
|
{...restProps}
|
||||||
|
>
|
||||||
|
{@render children?.()}
|
||||||
|
</div>
|
||||||
20
web/src/lib/components/ui/dialog/dialog-overlay.svelte
Normal file
20
web/src/lib/components/ui/dialog/dialog-overlay.svelte
Normal file
@ -0,0 +1,20 @@
|
|||||||
|
<script lang="ts">
|
||||||
|
import { Dialog as DialogPrimitive } from "bits-ui";
|
||||||
|
import { cn } from "$lib/utils.js";
|
||||||
|
|
||||||
|
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}
|
||||||
|
/>
|
||||||
17
web/src/lib/components/ui/dialog/dialog-title.svelte
Normal file
17
web/src/lib/components/ui/dialog/dialog-title.svelte
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
<script lang="ts">
|
||||||
|
import { Dialog as DialogPrimitive } from "bits-ui";
|
||||||
|
import { cn } from "$lib/utils.js";
|
||||||
|
|
||||||
|
let {
|
||||||
|
ref = $bindable(null),
|
||||||
|
class: className,
|
||||||
|
...restProps
|
||||||
|
}: DialogPrimitive.TitleProps = $props();
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<DialogPrimitive.Title
|
||||||
|
bind:ref
|
||||||
|
data-slot="dialog-title"
|
||||||
|
class={cn("text-lg font-semibold leading-none", className)}
|
||||||
|
{...restProps}
|
||||||
|
/>
|
||||||
7
web/src/lib/components/ui/dialog/dialog-trigger.svelte
Normal file
7
web/src/lib/components/ui/dialog/dialog-trigger.svelte
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
<script lang="ts">
|
||||||
|
import { Dialog as DialogPrimitive } from "bits-ui";
|
||||||
|
|
||||||
|
let { ref = $bindable(null), ...restProps }: DialogPrimitive.TriggerProps = $props();
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<DialogPrimitive.Trigger bind:ref data-slot="dialog-trigger" {...restProps} />
|
||||||
37
web/src/lib/components/ui/dialog/index.ts
Normal file
37
web/src/lib/components/ui/dialog/index.ts
Normal file
@ -0,0 +1,37 @@
|
|||||||
|
import { Dialog as DialogPrimitive } from "bits-ui";
|
||||||
|
|
||||||
|
import Title from "./dialog-title.svelte";
|
||||||
|
import Footer from "./dialog-footer.svelte";
|
||||||
|
import Header from "./dialog-header.svelte";
|
||||||
|
import Overlay from "./dialog-overlay.svelte";
|
||||||
|
import Content from "./dialog-content.svelte";
|
||||||
|
import Description from "./dialog-description.svelte";
|
||||||
|
import Trigger from "./dialog-trigger.svelte";
|
||||||
|
import Close from "./dialog-close.svelte";
|
||||||
|
|
||||||
|
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,
|
||||||
|
};
|
||||||
@ -0,0 +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";
|
||||||
|
|
||||||
|
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 outline-hidden relative flex cursor-default select-none items-center gap-2 rounded-sm py-1.5 pl-8 pr-2 text-sm data-[disabled]:pointer-events-none data-[disabled]:opacity-50 [&_svg:not([class*='size-'])]:size-4 [&_svg]:pointer-events-none [&_svg]:shrink-0",
|
||||||
|
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}
|
||||||
|
</DropdownMenuPrimitive.CheckboxItem>
|
||||||
@ -0,0 +1,27 @@
|
|||||||
|
<script lang="ts">
|
||||||
|
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();
|
||||||
|
</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 max-h-(--bits-dropdown-menu-content-available-height) origin-(--bits-dropdown-menu-content-transform-origin) z-50 min-w-[8rem] overflow-y-auto overflow-x-hidden rounded-md border p-1 shadow-md outline-none",
|
||||||
|
className
|
||||||
|
)}
|
||||||
|
{...restProps}
|
||||||
|
/>
|
||||||
|
</DropdownMenuPrimitive.Portal>
|
||||||
@ -0,0 +1,22 @@
|
|||||||
|
<script lang="ts">
|
||||||
|
import { DropdownMenu as DropdownMenuPrimitive } from "bits-ui";
|
||||||
|
import { cn } from "$lib/utils.js";
|
||||||
|
import type { ComponentProps } from "svelte";
|
||||||
|
|
||||||
|
let {
|
||||||
|
ref = $bindable(null),
|
||||||
|
class: className,
|
||||||
|
inset,
|
||||||
|
...restProps
|
||||||
|
}: ComponentProps<typeof DropdownMenuPrimitive.GroupHeading> & {
|
||||||
|
inset?: boolean;
|
||||||
|
} = $props();
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<DropdownMenuPrimitive.GroupHeading
|
||||||
|
bind:ref
|
||||||
|
data-slot="dropdown-menu-group-heading"
|
||||||
|
data-inset={inset}
|
||||||
|
class={cn("px-2 py-1.5 text-sm font-semibold data-[inset]:pl-8", className)}
|
||||||
|
{...restProps}
|
||||||
|
/>
|
||||||
@ -0,0 +1,7 @@
|
|||||||
|
<script lang="ts">
|
||||||
|
import { DropdownMenu as DropdownMenuPrimitive } from "bits-ui";
|
||||||
|
|
||||||
|
let { ref = $bindable(null), ...restProps }: DropdownMenuPrimitive.GroupProps = $props();
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<DropdownMenuPrimitive.Group bind:ref data-slot="dropdown-menu-group" {...restProps} />
|
||||||
@ -0,0 +1,27 @@
|
|||||||
|
<script lang="ts">
|
||||||
|
import { 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();
|
||||||
|
</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 outline-hidden relative flex cursor-default select-none items-center gap-2 rounded-sm px-2 py-1.5 text-sm data-[disabled]:pointer-events-none data-[inset]:pl-8 data-[disabled]:opacity-50 [&_svg:not([class*='size-'])]:size-4 [&_svg]:pointer-events-none [&_svg]:shrink-0",
|
||||||
|
className
|
||||||
|
)}
|
||||||
|
{...restProps}
|
||||||
|
/>
|
||||||
@ -0,0 +1,24 @@
|
|||||||
|
<script lang="ts">
|
||||||
|
import { cn, type WithElementRef } from "$lib/utils.js";
|
||||||
|
import type { HTMLAttributes } from "svelte/elements";
|
||||||
|
|
||||||
|
let {
|
||||||
|
ref = $bindable(null),
|
||||||
|
class: className,
|
||||||
|
inset,
|
||||||
|
children,
|
||||||
|
...restProps
|
||||||
|
}: WithElementRef<HTMLAttributes<HTMLDivElement>> & {
|
||||||
|
inset?: boolean;
|
||||||
|
} = $props();
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<div
|
||||||
|
bind:this={ref}
|
||||||
|
data-slot="dropdown-menu-label"
|
||||||
|
data-inset={inset}
|
||||||
|
class={cn("px-2 py-1.5 text-sm font-semibold data-[inset]:pl-8", className)}
|
||||||
|
{...restProps}
|
||||||
|
>
|
||||||
|
{@render children?.()}
|
||||||
|
</div>
|
||||||
@ -0,0 +1,16 @@
|
|||||||
|
<script lang="ts">
|
||||||
|
import { DropdownMenu as DropdownMenuPrimitive } from "bits-ui";
|
||||||
|
|
||||||
|
let {
|
||||||
|
ref = $bindable(null),
|
||||||
|
value = $bindable(),
|
||||||
|
...restProps
|
||||||
|
}: DropdownMenuPrimitive.RadioGroupProps = $props();
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<DropdownMenuPrimitive.RadioGroup
|
||||||
|
bind:ref
|
||||||
|
bind:value
|
||||||
|
data-slot="dropdown-menu-radio-group"
|
||||||
|
{...restProps}
|
||||||
|
/>
|
||||||
@ -0,0 +1,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";
|
||||||
|
|
||||||
|
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 outline-hidden relative flex cursor-default select-none items-center gap-2 rounded-sm py-1.5 pl-8 pr-2 text-sm data-[disabled]:pointer-events-none data-[disabled]:opacity-50 [&_svg:not([class*='size-'])]:size-4 [&_svg]:pointer-events-none [&_svg]:shrink-0",
|
||||||
|
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}
|
||||||
|
</DropdownMenuPrimitive.RadioItem>
|
||||||
@ -0,0 +1,17 @@
|
|||||||
|
<script lang="ts">
|
||||||
|
import { DropdownMenu as DropdownMenuPrimitive } from "bits-ui";
|
||||||
|
import { cn } from "$lib/utils.js";
|
||||||
|
|
||||||
|
let {
|
||||||
|
ref = $bindable(null),
|
||||||
|
class: className,
|
||||||
|
...restProps
|
||||||
|
}: DropdownMenuPrimitive.SeparatorProps = $props();
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<DropdownMenuPrimitive.Separator
|
||||||
|
bind:ref
|
||||||
|
data-slot="dropdown-menu-separator"
|
||||||
|
class={cn("bg-border -mx-1 my-1 h-px", className)}
|
||||||
|
{...restProps}
|
||||||
|
/>
|
||||||
@ -0,0 +1,20 @@
|
|||||||
|
<script lang="ts">
|
||||||
|
import 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();
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<span
|
||||||
|
bind:this={ref}
|
||||||
|
data-slot="dropdown-menu-shortcut"
|
||||||
|
class={cn("text-muted-foreground ml-auto text-xs tracking-widest", className)}
|
||||||
|
{...restProps}
|
||||||
|
>
|
||||||
|
{@render children?.()}
|
||||||
|
</span>
|
||||||
@ -0,0 +1,20 @@
|
|||||||
|
<script lang="ts">
|
||||||
|
import { DropdownMenu as DropdownMenuPrimitive } from "bits-ui";
|
||||||
|
import { cn } from "$lib/utils.js";
|
||||||
|
|
||||||
|
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 origin-(--bits-dropdown-menu-content-transform-origin) z-50 min-w-[8rem] overflow-hidden rounded-md border p-1 shadow-lg",
|
||||||
|
className
|
||||||
|
)}
|
||||||
|
{...restProps}
|
||||||
|
/>
|
||||||
@ -0,0 +1,29 @@
|
|||||||
|
<script lang="ts">
|
||||||
|
import { DropdownMenu as DropdownMenuPrimitive } from "bits-ui";
|
||||||
|
import ChevronRightIcon from "@lucide/svelte/icons/chevron-right";
|
||||||
|
import { cn } from "$lib/utils.js";
|
||||||
|
|
||||||
|
let {
|
||||||
|
ref = $bindable(null),
|
||||||
|
class: className,
|
||||||
|
inset,
|
||||||
|
children,
|
||||||
|
...restProps
|
||||||
|
}: DropdownMenuPrimitive.SubTriggerProps & {
|
||||||
|
inset?: boolean;
|
||||||
|
} = $props();
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<DropdownMenuPrimitive.SubTrigger
|
||||||
|
bind:ref
|
||||||
|
data-slot="dropdown-menu-sub-trigger"
|
||||||
|
data-inset={inset}
|
||||||
|
class={cn(
|
||||||
|
"data-highlighted:bg-accent data-highlighted:text-accent-foreground data-[state=open]:bg-accent data-[state=open]:text-accent-foreground outline-hidden [&_svg:not([class*='text-'])]:text-muted-foreground flex cursor-default select-none items-center gap-2 rounded-sm px-2 py-1.5 text-sm data-[disabled]:pointer-events-none data-[inset]:pl-8 data-[disabled]:opacity-50 [&_svg:not([class*='size-'])]:size-4 [&_svg]:pointer-events-none [&_svg]:shrink-0",
|
||||||
|
className
|
||||||
|
)}
|
||||||
|
{...restProps}
|
||||||
|
>
|
||||||
|
{@render children?.()}
|
||||||
|
<ChevronRightIcon class="ml-auto size-4" />
|
||||||
|
</DropdownMenuPrimitive.SubTrigger>
|
||||||
@ -0,0 +1,7 @@
|
|||||||
|
<script lang="ts">
|
||||||
|
import { DropdownMenu as DropdownMenuPrimitive } from "bits-ui";
|
||||||
|
|
||||||
|
let { ref = $bindable(null), ...restProps }: DropdownMenuPrimitive.TriggerProps = $props();
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<DropdownMenuPrimitive.Trigger bind:ref data-slot="dropdown-menu-trigger" {...restProps} />
|
||||||
49
web/src/lib/components/ui/dropdown-menu/index.ts
Normal file
49
web/src/lib/components/ui/dropdown-menu/index.ts
Normal file
@ -0,0 +1,49 @@
|
|||||||
|
import { DropdownMenu as DropdownMenuPrimitive } from "bits-ui";
|
||||||
|
import CheckboxItem from "./dropdown-menu-checkbox-item.svelte";
|
||||||
|
import Content from "./dropdown-menu-content.svelte";
|
||||||
|
import Group from "./dropdown-menu-group.svelte";
|
||||||
|
import Item from "./dropdown-menu-item.svelte";
|
||||||
|
import Label from "./dropdown-menu-label.svelte";
|
||||||
|
import RadioGroup from "./dropdown-menu-radio-group.svelte";
|
||||||
|
import RadioItem from "./dropdown-menu-radio-item.svelte";
|
||||||
|
import Separator from "./dropdown-menu-separator.svelte";
|
||||||
|
import Shortcut from "./dropdown-menu-shortcut.svelte";
|
||||||
|
import Trigger from "./dropdown-menu-trigger.svelte";
|
||||||
|
import SubContent from "./dropdown-menu-sub-content.svelte";
|
||||||
|
import SubTrigger from "./dropdown-menu-sub-trigger.svelte";
|
||||||
|
import GroupHeading from "./dropdown-menu-group-heading.svelte";
|
||||||
|
const Sub = DropdownMenuPrimitive.Sub;
|
||||||
|
const Root = DropdownMenuPrimitive.Root;
|
||||||
|
|
||||||
|
export {
|
||||||
|
CheckboxItem,
|
||||||
|
Content,
|
||||||
|
Root as DropdownMenu,
|
||||||
|
CheckboxItem as DropdownMenuCheckboxItem,
|
||||||
|
Content as DropdownMenuContent,
|
||||||
|
Group as DropdownMenuGroup,
|
||||||
|
Item as DropdownMenuItem,
|
||||||
|
Label as DropdownMenuLabel,
|
||||||
|
RadioGroup as DropdownMenuRadioGroup,
|
||||||
|
RadioItem as DropdownMenuRadioItem,
|
||||||
|
Separator as DropdownMenuSeparator,
|
||||||
|
Shortcut as DropdownMenuShortcut,
|
||||||
|
Sub as DropdownMenuSub,
|
||||||
|
SubContent as DropdownMenuSubContent,
|
||||||
|
SubTrigger as DropdownMenuSubTrigger,
|
||||||
|
Trigger as DropdownMenuTrigger,
|
||||||
|
GroupHeading as DropdownMenuGroupHeading,
|
||||||
|
Group,
|
||||||
|
GroupHeading,
|
||||||
|
Item,
|
||||||
|
Label,
|
||||||
|
RadioGroup,
|
||||||
|
RadioItem,
|
||||||
|
Root,
|
||||||
|
Separator,
|
||||||
|
Shortcut,
|
||||||
|
Sub,
|
||||||
|
SubContent,
|
||||||
|
SubTrigger,
|
||||||
|
Trigger,
|
||||||
|
};
|
||||||
15
web/src/lib/components/ui/input-otp/index.ts
Normal file
15
web/src/lib/components/ui/input-otp/index.ts
Normal file
@ -0,0 +1,15 @@
|
|||||||
|
import Root from "./input-otp.svelte";
|
||||||
|
import Group from "./input-otp-group.svelte";
|
||||||
|
import Slot from "./input-otp-slot.svelte";
|
||||||
|
import Separator from "./input-otp-separator.svelte";
|
||||||
|
|
||||||
|
export {
|
||||||
|
Root,
|
||||||
|
Group,
|
||||||
|
Slot,
|
||||||
|
Separator,
|
||||||
|
Root as InputOTP,
|
||||||
|
Group as InputOTPGroup,
|
||||||
|
Slot as InputOTPSlot,
|
||||||
|
Separator as InputOTPSeparator,
|
||||||
|
};
|
||||||
20
web/src/lib/components/ui/input-otp/input-otp-group.svelte
Normal file
20
web/src/lib/components/ui/input-otp/input-otp-group.svelte
Normal file
@ -0,0 +1,20 @@
|
|||||||
|
<script lang="ts">
|
||||||
|
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();
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<div
|
||||||
|
bind:this={ref}
|
||||||
|
data-slot="input-otp-group"
|
||||||
|
class={cn("flex items-center", className)}
|
||||||
|
{...restProps}
|
||||||
|
>
|
||||||
|
{@render children?.()}
|
||||||
|
</div>
|
||||||
@ -0,0 +1,19 @@
|
|||||||
|
<script lang="ts">
|
||||||
|
import type { HTMLAttributes } from "svelte/elements";
|
||||||
|
import type { WithElementRef } from "$lib/utils.js";
|
||||||
|
import DotIcon from "@lucide/svelte/icons/dot";
|
||||||
|
|
||||||
|
let {
|
||||||
|
ref = $bindable(null),
|
||||||
|
children,
|
||||||
|
...restProps
|
||||||
|
}: WithElementRef<HTMLAttributes<HTMLDivElement>> = $props();
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<div bind:this={ref} data-slot="input-otp-separator" role="separator" {...restProps}>
|
||||||
|
{#if children}
|
||||||
|
{@render children?.()}
|
||||||
|
{:else}
|
||||||
|
<DotIcon />
|
||||||
|
{/if}
|
||||||
|
</div>
|
||||||
31
web/src/lib/components/ui/input-otp/input-otp-slot.svelte
Normal file
31
web/src/lib/components/ui/input-otp/input-otp-slot.svelte
Normal file
@ -0,0 +1,31 @@
|
|||||||
|
<script lang="ts">
|
||||||
|
import { PinInput as InputOTPPrimitive } from "bits-ui";
|
||||||
|
import { cn } from "$lib/utils.js";
|
||||||
|
|
||||||
|
let {
|
||||||
|
ref = $bindable(null),
|
||||||
|
cell,
|
||||||
|
class: className,
|
||||||
|
...restProps
|
||||||
|
}: InputOTPPrimitive.CellProps = $props();
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<InputOTPPrimitive.Cell
|
||||||
|
{cell}
|
||||||
|
bind:ref
|
||||||
|
data-slot="input-otp-slot"
|
||||||
|
class={cn(
|
||||||
|
"border-input aria-invalid:border-destructive dark:bg-input/30 relative flex size-10 items-center justify-center border-y border-r text-sm outline-none transition-all first:rounded-l-md first:border-l last:rounded-r-md",
|
||||||
|
cell.isActive &&
|
||||||
|
"border-ring ring-ring/50 aria-invalid:border-destructive dark:aria-invalid:ring-destructive/40 aria-invalid:ring-destructive/20 ring-offset-background z-10 ring-[3px]",
|
||||||
|
className
|
||||||
|
)}
|
||||||
|
{...restProps}
|
||||||
|
>
|
||||||
|
{cell.char}
|
||||||
|
{#if cell.hasFakeCaret}
|
||||||
|
<div class="pointer-events-none absolute inset-0 flex items-center justify-center">
|
||||||
|
<div class="animate-caret-blink bg-foreground h-4 w-px duration-1000"></div>
|
||||||
|
</div>
|
||||||
|
{/if}
|
||||||
|
</InputOTPPrimitive.Cell>
|
||||||
22
web/src/lib/components/ui/input-otp/input-otp.svelte
Normal file
22
web/src/lib/components/ui/input-otp/input-otp.svelte
Normal file
@ -0,0 +1,22 @@
|
|||||||
|
<script lang="ts">
|
||||||
|
import { PinInput as InputOTPPrimitive } from "bits-ui";
|
||||||
|
import { cn } from "$lib/utils.js";
|
||||||
|
|
||||||
|
let {
|
||||||
|
ref = $bindable(null),
|
||||||
|
class: className,
|
||||||
|
value = $bindable(""),
|
||||||
|
...restProps
|
||||||
|
}: InputOTPPrimitive.RootProps = $props();
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<InputOTPPrimitive.Root
|
||||||
|
bind:ref
|
||||||
|
bind:value
|
||||||
|
data-slot="input-otp"
|
||||||
|
class={cn(
|
||||||
|
"has-disabled:opacity-50 flex items-center gap-2 [&_input]:disabled:cursor-not-allowed",
|
||||||
|
className
|
||||||
|
)}
|
||||||
|
{...restProps}
|
||||||
|
/>
|
||||||
7
web/src/lib/components/ui/input/index.ts
Normal file
7
web/src/lib/components/ui/input/index.ts
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
import Root from "./input.svelte";
|
||||||
|
|
||||||
|
export {
|
||||||
|
Root,
|
||||||
|
//
|
||||||
|
Root as Input,
|
||||||
|
};
|
||||||
51
web/src/lib/components/ui/input/input.svelte
Normal file
51
web/src/lib/components/ui/input/input.svelte
Normal file
@ -0,0 +1,51 @@
|
|||||||
|
<script lang="ts">
|
||||||
|
import type { HTMLInputAttributes, HTMLInputTypeAttribute } from "svelte/elements";
|
||||||
|
import { cn, type WithElementRef } from "$lib/utils.js";
|
||||||
|
|
||||||
|
type InputType = Exclude<HTMLInputTypeAttribute, "file">;
|
||||||
|
|
||||||
|
type Props = WithElementRef<
|
||||||
|
Omit<HTMLInputAttributes, "type"> &
|
||||||
|
({ type: "file"; files?: FileList } | { type?: InputType; files?: undefined })
|
||||||
|
>;
|
||||||
|
|
||||||
|
let {
|
||||||
|
ref = $bindable(null),
|
||||||
|
value = $bindable(),
|
||||||
|
type,
|
||||||
|
files = $bindable(),
|
||||||
|
class: className,
|
||||||
|
...restProps
|
||||||
|
}: Props = $props();
|
||||||
|
</script>
|
||||||
|
|
||||||
|
{#if type === "file"}
|
||||||
|
<input
|
||||||
|
bind:this={ref}
|
||||||
|
data-slot="input"
|
||||||
|
class={cn(
|
||||||
|
"selection:bg-primary dark:bg-input/30 selection:text-primary-foreground border-input ring-offset-background placeholder:text-muted-foreground shadow-xs flex h-9 w-full min-w-0 rounded-md border bg-transparent px-3 pt-1.5 text-sm font-medium outline-none transition-[color,box-shadow] disabled:cursor-not-allowed disabled:opacity-50 md:text-sm",
|
||||||
|
"focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:ring-[3px]",
|
||||||
|
"aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive",
|
||||||
|
className
|
||||||
|
)}
|
||||||
|
type="file"
|
||||||
|
bind:files
|
||||||
|
bind:value
|
||||||
|
{...restProps}
|
||||||
|
/>
|
||||||
|
{:else}
|
||||||
|
<input
|
||||||
|
bind:this={ref}
|
||||||
|
data-slot="input"
|
||||||
|
class={cn(
|
||||||
|
"border-input bg-background selection:bg-primary dark:bg-input/30 selection:text-primary-foreground ring-offset-background placeholder:text-muted-foreground shadow-xs flex h-9 w-full min-w-0 rounded-md border px-3 py-1 text-base outline-none transition-[color,box-shadow] disabled:cursor-not-allowed disabled:opacity-50 md:text-sm",
|
||||||
|
"focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:ring-[3px]",
|
||||||
|
"aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive",
|
||||||
|
className
|
||||||
|
)}
|
||||||
|
{type}
|
||||||
|
bind:value
|
||||||
|
{...restProps}
|
||||||
|
/>
|
||||||
|
{/if}
|
||||||
7
web/src/lib/components/ui/label/index.ts
Normal file
7
web/src/lib/components/ui/label/index.ts
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
import Root from "./label.svelte";
|
||||||
|
|
||||||
|
export {
|
||||||
|
Root,
|
||||||
|
//
|
||||||
|
Root as Label,
|
||||||
|
};
|
||||||
20
web/src/lib/components/ui/label/label.svelte
Normal file
20
web/src/lib/components/ui/label/label.svelte
Normal file
@ -0,0 +1,20 @@
|
|||||||
|
<script lang="ts">
|
||||||
|
import { Label as LabelPrimitive } from "bits-ui";
|
||||||
|
import { cn } from "$lib/utils.js";
|
||||||
|
|
||||||
|
let {
|
||||||
|
ref = $bindable(null),
|
||||||
|
class: className,
|
||||||
|
...restProps
|
||||||
|
}: LabelPrimitive.RootProps = $props();
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<LabelPrimitive.Root
|
||||||
|
bind:ref
|
||||||
|
data-slot="label"
|
||||||
|
class={cn(
|
||||||
|
"flex select-none items-center gap-2 text-sm font-medium leading-none peer-disabled:cursor-not-allowed peer-disabled:opacity-50 group-data-[disabled=true]:pointer-events-none group-data-[disabled=true]:opacity-50",
|
||||||
|
className
|
||||||
|
)}
|
||||||
|
{...restProps}
|
||||||
|
/>
|
||||||
7
web/src/lib/components/ui/separator/index.ts
Normal file
7
web/src/lib/components/ui/separator/index.ts
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
import Root from "./separator.svelte";
|
||||||
|
|
||||||
|
export {
|
||||||
|
Root,
|
||||||
|
//
|
||||||
|
Root as Separator,
|
||||||
|
};
|
||||||
20
web/src/lib/components/ui/separator/separator.svelte
Normal file
20
web/src/lib/components/ui/separator/separator.svelte
Normal file
@ -0,0 +1,20 @@
|
|||||||
|
<script lang="ts">
|
||||||
|
import { Separator as SeparatorPrimitive } from "bits-ui";
|
||||||
|
import { cn } from "$lib/utils.js";
|
||||||
|
|
||||||
|
let {
|
||||||
|
ref = $bindable(null),
|
||||||
|
class: className,
|
||||||
|
...restProps
|
||||||
|
}: SeparatorPrimitive.RootProps = $props();
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<SeparatorPrimitive.Root
|
||||||
|
bind:ref
|
||||||
|
data-slot="separator"
|
||||||
|
class={cn(
|
||||||
|
"bg-border shrink-0 data-[orientation=horizontal]:h-px data-[orientation=vertical]:h-full data-[orientation=horizontal]:w-full data-[orientation=vertical]:w-px",
|
||||||
|
className
|
||||||
|
)}
|
||||||
|
{...restProps}
|
||||||
|
/>
|
||||||
36
web/src/lib/components/ui/sheet/index.ts
Normal file
36
web/src/lib/components/ui/sheet/index.ts
Normal file
@ -0,0 +1,36 @@
|
|||||||
|
import { Dialog as SheetPrimitive } from "bits-ui";
|
||||||
|
import Trigger from "./sheet-trigger.svelte";
|
||||||
|
import Close from "./sheet-close.svelte";
|
||||||
|
import Overlay from "./sheet-overlay.svelte";
|
||||||
|
import Content from "./sheet-content.svelte";
|
||||||
|
import Header from "./sheet-header.svelte";
|
||||||
|
import Footer from "./sheet-footer.svelte";
|
||||||
|
import Title from "./sheet-title.svelte";
|
||||||
|
import Description from "./sheet-description.svelte";
|
||||||
|
|
||||||
|
const Root = SheetPrimitive.Root;
|
||||||
|
const Portal = SheetPrimitive.Portal;
|
||||||
|
|
||||||
|
export {
|
||||||
|
Root,
|
||||||
|
Close,
|
||||||
|
Trigger,
|
||||||
|
Portal,
|
||||||
|
Overlay,
|
||||||
|
Content,
|
||||||
|
Header,
|
||||||
|
Footer,
|
||||||
|
Title,
|
||||||
|
Description,
|
||||||
|
//
|
||||||
|
Root as Sheet,
|
||||||
|
Close as SheetClose,
|
||||||
|
Trigger as SheetTrigger,
|
||||||
|
Portal as SheetPortal,
|
||||||
|
Overlay as SheetOverlay,
|
||||||
|
Content as SheetContent,
|
||||||
|
Header as SheetHeader,
|
||||||
|
Footer as SheetFooter,
|
||||||
|
Title as SheetTitle,
|
||||||
|
Description as SheetDescription,
|
||||||
|
};
|
||||||
7
web/src/lib/components/ui/sheet/sheet-close.svelte
Normal file
7
web/src/lib/components/ui/sheet/sheet-close.svelte
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
<script lang="ts">
|
||||||
|
import { Dialog as SheetPrimitive } from "bits-ui";
|
||||||
|
|
||||||
|
let { ref = $bindable(null), ...restProps }: SheetPrimitive.CloseProps = $props();
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<SheetPrimitive.Close bind:ref data-slot="sheet-close" {...restProps} />
|
||||||
58
web/src/lib/components/ui/sheet/sheet-content.svelte
Normal file
58
web/src/lib/components/ui/sheet/sheet-content.svelte
Normal file
@ -0,0 +1,58 @@
|
|||||||
|
<script lang="ts" module>
|
||||||
|
import { tv, type VariantProps } from "tailwind-variants";
|
||||||
|
export const sheetVariants = tv({
|
||||||
|
base: "bg-background data-[state=open]:animate-in data-[state=closed]:animate-out fixed z-50 flex flex-col gap-4 shadow-lg transition ease-in-out data-[state=closed]:duration-300 data-[state=open]:duration-500",
|
||||||
|
variants: {
|
||||||
|
side: {
|
||||||
|
top: "data-[state=closed]:slide-out-to-top data-[state=open]:slide-in-from-top inset-x-0 top-0 h-auto border-b",
|
||||||
|
bottom: "data-[state=closed]:slide-out-to-bottom data-[state=open]:slide-in-from-bottom inset-x-0 bottom-0 h-auto border-t",
|
||||||
|
left: "data-[state=closed]:slide-out-to-left data-[state=open]:slide-in-from-left inset-y-0 left-0 h-full w-3/4 border-r sm:max-w-sm",
|
||||||
|
right: "data-[state=closed]:slide-out-to-right data-[state=open]:slide-in-from-right inset-y-0 right-0 h-full w-3/4 border-l sm:max-w-sm",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
defaultVariants: {
|
||||||
|
side: "right",
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
export type Side = VariantProps<typeof sheetVariants>["side"];
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<script lang="ts">
|
||||||
|
import { Dialog as SheetPrimitive } from "bits-ui";
|
||||||
|
import XIcon from "@lucide/svelte/icons/x";
|
||||||
|
import type { Snippet } from "svelte";
|
||||||
|
import SheetOverlay from "./sheet-overlay.svelte";
|
||||||
|
import { cn, type WithoutChildrenOrChild } from "$lib/utils.js";
|
||||||
|
|
||||||
|
let {
|
||||||
|
ref = $bindable(null),
|
||||||
|
class: className,
|
||||||
|
side = "right",
|
||||||
|
portalProps,
|
||||||
|
children,
|
||||||
|
...restProps
|
||||||
|
}: WithoutChildrenOrChild<SheetPrimitive.ContentProps> & {
|
||||||
|
portalProps?: SheetPrimitive.PortalProps;
|
||||||
|
side?: Side;
|
||||||
|
children: Snippet;
|
||||||
|
} = $props();
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<SheetPrimitive.Portal {...portalProps}>
|
||||||
|
<SheetOverlay />
|
||||||
|
<SheetPrimitive.Content
|
||||||
|
bind:ref
|
||||||
|
data-slot="sheet-content"
|
||||||
|
class={cn(sheetVariants({ side }), className)}
|
||||||
|
{...restProps}
|
||||||
|
>
|
||||||
|
{@render children?.()}
|
||||||
|
<SheetPrimitive.Close
|
||||||
|
class="ring-offset-background focus-visible:ring-ring rounded-xs focus-visible:outline-hidden absolute right-4 top-4 opacity-70 transition-opacity hover:opacity-100 focus-visible:ring-2 focus-visible:ring-offset-2 disabled:pointer-events-none"
|
||||||
|
>
|
||||||
|
<XIcon class="size-4" />
|
||||||
|
<span class="sr-only">Close</span>
|
||||||
|
</SheetPrimitive.Close>
|
||||||
|
</SheetPrimitive.Content>
|
||||||
|
</SheetPrimitive.Portal>
|
||||||
17
web/src/lib/components/ui/sheet/sheet-description.svelte
Normal file
17
web/src/lib/components/ui/sheet/sheet-description.svelte
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
<script lang="ts">
|
||||||
|
import { Dialog as SheetPrimitive } from "bits-ui";
|
||||||
|
import { cn } from "$lib/utils.js";
|
||||||
|
|
||||||
|
let {
|
||||||
|
ref = $bindable(null),
|
||||||
|
class: className,
|
||||||
|
...restProps
|
||||||
|
}: SheetPrimitive.DescriptionProps = $props();
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<SheetPrimitive.Description
|
||||||
|
bind:ref
|
||||||
|
data-slot="sheet-description"
|
||||||
|
class={cn("text-muted-foreground text-sm", className)}
|
||||||
|
{...restProps}
|
||||||
|
/>
|
||||||
20
web/src/lib/components/ui/sheet/sheet-footer.svelte
Normal file
20
web/src/lib/components/ui/sheet/sheet-footer.svelte
Normal file
@ -0,0 +1,20 @@
|
|||||||
|
<script lang="ts">
|
||||||
|
import { cn, type WithElementRef } from "$lib/utils.js";
|
||||||
|
import type { HTMLAttributes } from "svelte/elements";
|
||||||
|
|
||||||
|
let {
|
||||||
|
ref = $bindable(null),
|
||||||
|
class: className,
|
||||||
|
children,
|
||||||
|
...restProps
|
||||||
|
}: WithElementRef<HTMLAttributes<HTMLDivElement>> = $props();
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<div
|
||||||
|
bind:this={ref}
|
||||||
|
data-slot="sheet-footer"
|
||||||
|
class={cn("mt-auto flex flex-col gap-2 p-4", className)}
|
||||||
|
{...restProps}
|
||||||
|
>
|
||||||
|
{@render children?.()}
|
||||||
|
</div>
|
||||||
20
web/src/lib/components/ui/sheet/sheet-header.svelte
Normal file
20
web/src/lib/components/ui/sheet/sheet-header.svelte
Normal file
@ -0,0 +1,20 @@
|
|||||||
|
<script lang="ts">
|
||||||
|
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();
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<div
|
||||||
|
bind:this={ref}
|
||||||
|
data-slot="sheet-header"
|
||||||
|
class={cn("flex flex-col gap-1.5 p-4", className)}
|
||||||
|
{...restProps}
|
||||||
|
>
|
||||||
|
{@render children?.()}
|
||||||
|
</div>
|
||||||
20
web/src/lib/components/ui/sheet/sheet-overlay.svelte
Normal file
20
web/src/lib/components/ui/sheet/sheet-overlay.svelte
Normal file
@ -0,0 +1,20 @@
|
|||||||
|
<script lang="ts">
|
||||||
|
import { Dialog as SheetPrimitive } from "bits-ui";
|
||||||
|
import { cn } from "$lib/utils.js";
|
||||||
|
|
||||||
|
let {
|
||||||
|
ref = $bindable(null),
|
||||||
|
class: className,
|
||||||
|
...restProps
|
||||||
|
}: SheetPrimitive.OverlayProps = $props();
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<SheetPrimitive.Overlay
|
||||||
|
bind:ref
|
||||||
|
data-slot="sheet-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}
|
||||||
|
/>
|
||||||
17
web/src/lib/components/ui/sheet/sheet-title.svelte
Normal file
17
web/src/lib/components/ui/sheet/sheet-title.svelte
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
<script lang="ts">
|
||||||
|
import { Dialog as SheetPrimitive } from "bits-ui";
|
||||||
|
import { cn } from "$lib/utils.js";
|
||||||
|
|
||||||
|
let {
|
||||||
|
ref = $bindable(null),
|
||||||
|
class: className,
|
||||||
|
...restProps
|
||||||
|
}: SheetPrimitive.TitleProps = $props();
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<SheetPrimitive.Title
|
||||||
|
bind:ref
|
||||||
|
data-slot="sheet-title"
|
||||||
|
class={cn("text-foreground font-semibold", className)}
|
||||||
|
{...restProps}
|
||||||
|
/>
|
||||||
7
web/src/lib/components/ui/sheet/sheet-trigger.svelte
Normal file
7
web/src/lib/components/ui/sheet/sheet-trigger.svelte
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
<script lang="ts">
|
||||||
|
import { Dialog as SheetPrimitive } from "bits-ui";
|
||||||
|
|
||||||
|
let { ref = $bindable(null), ...restProps }: SheetPrimitive.TriggerProps = $props();
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<SheetPrimitive.Trigger bind:ref data-slot="sheet-trigger" {...restProps} />
|
||||||
6
web/src/lib/components/ui/sidebar/constants.ts
Normal file
6
web/src/lib/components/ui/sidebar/constants.ts
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
export const SIDEBAR_COOKIE_NAME = "sidebar:state";
|
||||||
|
export const SIDEBAR_COOKIE_MAX_AGE = 60 * 60 * 24 * 7;
|
||||||
|
export const SIDEBAR_WIDTH = "16rem";
|
||||||
|
export const SIDEBAR_WIDTH_MOBILE = "18rem";
|
||||||
|
export const SIDEBAR_WIDTH_ICON = "3rem";
|
||||||
|
export const SIDEBAR_KEYBOARD_SHORTCUT = "b";
|
||||||
81
web/src/lib/components/ui/sidebar/context.svelte.ts
Normal file
81
web/src/lib/components/ui/sidebar/context.svelte.ts
Normal file
@ -0,0 +1,81 @@
|
|||||||
|
import { IsMobile } from "$lib/hooks/is-mobile.svelte.js";
|
||||||
|
import { getContext, setContext } from "svelte";
|
||||||
|
import { SIDEBAR_KEYBOARD_SHORTCUT } from "./constants.js";
|
||||||
|
|
||||||
|
type Getter<T> = () => T;
|
||||||
|
|
||||||
|
export type SidebarStateProps = {
|
||||||
|
/**
|
||||||
|
* A getter function that returns the current open state of the sidebar.
|
||||||
|
* We use a getter function here to support `bind:open` on the `Sidebar.Provider`
|
||||||
|
* component.
|
||||||
|
*/
|
||||||
|
open: Getter<boolean>;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A function that sets the open state of the sidebar. To support `bind:open`, we need
|
||||||
|
* a source of truth for changing the open state to ensure it will be synced throughout
|
||||||
|
* the sub-components and any `bind:` references.
|
||||||
|
*/
|
||||||
|
setOpen: (open: boolean) => void;
|
||||||
|
};
|
||||||
|
|
||||||
|
class SidebarState {
|
||||||
|
readonly props: SidebarStateProps;
|
||||||
|
open = $derived.by(() => this.props.open());
|
||||||
|
openMobile = $state(false);
|
||||||
|
setOpen: SidebarStateProps["setOpen"];
|
||||||
|
#isMobile: IsMobile;
|
||||||
|
state = $derived.by(() => (this.open ? "expanded" : "collapsed"));
|
||||||
|
|
||||||
|
constructor(props: SidebarStateProps) {
|
||||||
|
this.setOpen = props.setOpen;
|
||||||
|
this.#isMobile = new IsMobile();
|
||||||
|
this.props = props;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Convenience getter for checking if the sidebar is mobile
|
||||||
|
// without this, we would need to use `sidebar.isMobile.current` everywhere
|
||||||
|
get isMobile() {
|
||||||
|
return this.#isMobile.current;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Event handler to apply to the `<svelte:window>`
|
||||||
|
handleShortcutKeydown = (e: KeyboardEvent) => {
|
||||||
|
if (e.key === SIDEBAR_KEYBOARD_SHORTCUT && (e.metaKey || e.ctrlKey)) {
|
||||||
|
e.preventDefault();
|
||||||
|
this.toggle();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
setOpenMobile = (value: boolean) => {
|
||||||
|
this.openMobile = value;
|
||||||
|
};
|
||||||
|
|
||||||
|
toggle = () => {
|
||||||
|
return this.#isMobile.current
|
||||||
|
? (this.openMobile = !this.openMobile)
|
||||||
|
: this.setOpen(!this.open);
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
const SYMBOL_KEY = "scn-sidebar";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Instantiates a new `SidebarState` instance and sets it in the context.
|
||||||
|
*
|
||||||
|
* @param props The constructor props for the `SidebarState` class.
|
||||||
|
* @returns The `SidebarState` instance.
|
||||||
|
*/
|
||||||
|
export function setSidebar(props: SidebarStateProps): SidebarState {
|
||||||
|
return setContext(Symbol.for(SYMBOL_KEY), new SidebarState(props));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retrieves the `SidebarState` instance from the context. This is a class instance,
|
||||||
|
* so you cannot destructure it.
|
||||||
|
* @returns The `SidebarState` instance.
|
||||||
|
*/
|
||||||
|
export function useSidebar(): SidebarState {
|
||||||
|
return getContext(Symbol.for(SYMBOL_KEY));
|
||||||
|
}
|
||||||
75
web/src/lib/components/ui/sidebar/index.ts
Normal file
75
web/src/lib/components/ui/sidebar/index.ts
Normal file
@ -0,0 +1,75 @@
|
|||||||
|
import { useSidebar } from "./context.svelte.js";
|
||||||
|
import Content from "./sidebar-content.svelte";
|
||||||
|
import Footer from "./sidebar-footer.svelte";
|
||||||
|
import GroupAction from "./sidebar-group-action.svelte";
|
||||||
|
import GroupContent from "./sidebar-group-content.svelte";
|
||||||
|
import GroupLabel from "./sidebar-group-label.svelte";
|
||||||
|
import Group from "./sidebar-group.svelte";
|
||||||
|
import Header from "./sidebar-header.svelte";
|
||||||
|
import Input from "./sidebar-input.svelte";
|
||||||
|
import Inset from "./sidebar-inset.svelte";
|
||||||
|
import MenuAction from "./sidebar-menu-action.svelte";
|
||||||
|
import MenuBadge from "./sidebar-menu-badge.svelte";
|
||||||
|
import MenuButton from "./sidebar-menu-button.svelte";
|
||||||
|
import MenuItem from "./sidebar-menu-item.svelte";
|
||||||
|
import MenuSkeleton from "./sidebar-menu-skeleton.svelte";
|
||||||
|
import MenuSubButton from "./sidebar-menu-sub-button.svelte";
|
||||||
|
import MenuSubItem from "./sidebar-menu-sub-item.svelte";
|
||||||
|
import MenuSub from "./sidebar-menu-sub.svelte";
|
||||||
|
import Menu from "./sidebar-menu.svelte";
|
||||||
|
import Provider from "./sidebar-provider.svelte";
|
||||||
|
import Rail from "./sidebar-rail.svelte";
|
||||||
|
import Separator from "./sidebar-separator.svelte";
|
||||||
|
import Trigger from "./sidebar-trigger.svelte";
|
||||||
|
import Root from "./sidebar.svelte";
|
||||||
|
|
||||||
|
export {
|
||||||
|
Content,
|
||||||
|
Footer,
|
||||||
|
Group,
|
||||||
|
GroupAction,
|
||||||
|
GroupContent,
|
||||||
|
GroupLabel,
|
||||||
|
Header,
|
||||||
|
Input,
|
||||||
|
Inset,
|
||||||
|
Menu,
|
||||||
|
MenuAction,
|
||||||
|
MenuBadge,
|
||||||
|
MenuButton,
|
||||||
|
MenuItem,
|
||||||
|
MenuSkeleton,
|
||||||
|
MenuSub,
|
||||||
|
MenuSubButton,
|
||||||
|
MenuSubItem,
|
||||||
|
Provider,
|
||||||
|
Rail,
|
||||||
|
Root,
|
||||||
|
Separator,
|
||||||
|
//
|
||||||
|
Root as Sidebar,
|
||||||
|
Content as SidebarContent,
|
||||||
|
Footer as SidebarFooter,
|
||||||
|
Group as SidebarGroup,
|
||||||
|
GroupAction as SidebarGroupAction,
|
||||||
|
GroupContent as SidebarGroupContent,
|
||||||
|
GroupLabel as SidebarGroupLabel,
|
||||||
|
Header as SidebarHeader,
|
||||||
|
Input as SidebarInput,
|
||||||
|
Inset as SidebarInset,
|
||||||
|
Menu as SidebarMenu,
|
||||||
|
MenuAction as SidebarMenuAction,
|
||||||
|
MenuBadge as SidebarMenuBadge,
|
||||||
|
MenuButton as SidebarMenuButton,
|
||||||
|
MenuItem as SidebarMenuItem,
|
||||||
|
MenuSkeleton as SidebarMenuSkeleton,
|
||||||
|
MenuSub as SidebarMenuSub,
|
||||||
|
MenuSubButton as SidebarMenuSubButton,
|
||||||
|
MenuSubItem as SidebarMenuSubItem,
|
||||||
|
Provider as SidebarProvider,
|
||||||
|
Rail as SidebarRail,
|
||||||
|
Separator as SidebarSeparator,
|
||||||
|
Trigger as SidebarTrigger,
|
||||||
|
Trigger,
|
||||||
|
useSidebar,
|
||||||
|
};
|
||||||
24
web/src/lib/components/ui/sidebar/sidebar-content.svelte
Normal file
24
web/src/lib/components/ui/sidebar/sidebar-content.svelte
Normal file
@ -0,0 +1,24 @@
|
|||||||
|
<script lang="ts">
|
||||||
|
import type { HTMLAttributes } from "svelte/elements";
|
||||||
|
import { cn, type WithElementRef } from "$lib/utils.js";
|
||||||
|
|
||||||
|
let {
|
||||||
|
ref = $bindable(null),
|
||||||
|
class: className,
|
||||||
|
children,
|
||||||
|
...restProps
|
||||||
|
}: WithElementRef<HTMLAttributes<HTMLElement>> = $props();
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<div
|
||||||
|
bind:this={ref}
|
||||||
|
data-slot="sidebar-content"
|
||||||
|
data-sidebar="content"
|
||||||
|
class={cn(
|
||||||
|
"flex min-h-0 flex-1 flex-col gap-2 overflow-auto group-data-[collapsible=icon]:overflow-hidden",
|
||||||
|
className
|
||||||
|
)}
|
||||||
|
{...restProps}
|
||||||
|
>
|
||||||
|
{@render children?.()}
|
||||||
|
</div>
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user