From 07ab4ec15706f02e1c25cee8affdbb64871e2dc9 Mon Sep 17 00:00:00 2001 From: SquidSpirit Date: Mon, 13 Oct 2025 16:52:13 +0800 Subject: [PATCH] feat: import shadcn --- frontend/components.json | 16 +++ frontend/package.json | 5 + frontend/pnpm-lock.yaml | 50 ++++++++ frontend/src/app.css | 119 ++++++++++++++++++ .../lib/common/framework/components/utils.ts | 13 ++ 5 files changed, 203 insertions(+) create mode 100644 frontend/components.json create mode 100644 frontend/src/lib/common/framework/components/utils.ts diff --git a/frontend/components.json b/frontend/components.json new file mode 100644 index 0000000..97440fd --- /dev/null +++ b/frontend/components.json @@ -0,0 +1,16 @@ +{ + "$schema": "https://shadcn-svelte.com/schema.json", + "tailwind": { + "css": "src/app.css", + "baseColor": "gray" + }, + "aliases": { + "components": "$lib/common/framework/components", + "utils": "$lib/common/framework/components/utils", + "ui": "$lib/common/framework/components/ui", + "hooks": "$lib/common/framework/components/hooks", + "lib": "$lib" + }, + "typescript": true, + "registry": "https://shadcn-svelte.com/registry" +} diff --git a/frontend/package.json b/frontend/package.json index c1c0fff..5fe0ff5 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -20,6 +20,7 @@ "@eslint/compat": "^1.2.5", "@eslint/js": "^9.18.0", "@fortawesome/fontawesome-free": "^7.0.0", + "@lucide/svelte": "^0.545.0", "@sveltejs/adapter-auto": "^6.0.0", "@sveltejs/adapter-node": "^5.2.13", "@sveltejs/kit": "^2.22.0", @@ -28,6 +29,7 @@ "@tailwindcss/vite": "^4.0.0", "@types/markdown-it": "^14.1.2", "@types/sanitize-html": "^2.16.0", + "clsx": "^2.1.1", "eslint": "^9.18.0", "eslint-config-prettier": "^10.0.1", "eslint-plugin-svelte": "^3.0.0", @@ -39,7 +41,10 @@ "sanitize-html": "^2.17.0", "svelte": "^5.0.0", "svelte-check": "^4.0.0", + "tailwind-merge": "^3.3.1", + "tailwind-variants": "^3.1.1", "tailwindcss": "^4.0.0", + "tw-animate-css": "^1.4.0", "typescript": "^5.0.0", "typescript-eslint": "^8.20.0", "vite": "^7.0.4", diff --git a/frontend/pnpm-lock.yaml b/frontend/pnpm-lock.yaml index 65c56df..ecb9990 100644 --- a/frontend/pnpm-lock.yaml +++ b/frontend/pnpm-lock.yaml @@ -21,6 +21,9 @@ importers: '@fortawesome/fontawesome-free': specifier: ^7.0.0 version: 7.0.0 + '@lucide/svelte': + specifier: ^0.545.0 + version: 0.545.0(svelte@5.36.13) '@sveltejs/adapter-auto': specifier: ^6.0.0 version: 6.0.1(@sveltejs/kit@2.25.1(@sveltejs/vite-plugin-svelte@6.1.0(svelte@5.36.13)(vite@7.0.5(@types/node@24.2.0)(jiti@2.4.2)(lightningcss@1.30.1)))(svelte@5.36.13)(vite@7.0.5(@types/node@24.2.0)(jiti@2.4.2)(lightningcss@1.30.1))) @@ -45,6 +48,9 @@ importers: '@types/sanitize-html': specifier: ^2.16.0 version: 2.16.0 + clsx: + specifier: ^2.1.1 + version: 2.1.1 eslint: specifier: ^9.18.0 version: 9.31.0(jiti@2.4.2) @@ -78,9 +84,18 @@ importers: svelte-check: specifier: ^4.0.0 version: 4.3.0(picomatch@4.0.3)(svelte@5.36.13)(typescript@5.8.3) + tailwind-merge: + specifier: ^3.3.1 + version: 3.3.1 + tailwind-variants: + specifier: ^3.1.1 + version: 3.1.1(tailwind-merge@3.3.1)(tailwindcss@4.1.11) tailwindcss: specifier: ^4.0.0 version: 4.1.11 + tw-animate-css: + specifier: ^1.4.0 + version: 1.4.0 typescript: specifier: ^5.0.0 version: 5.8.3 @@ -416,6 +431,11 @@ packages: '@jridgewell/trace-mapping@0.3.29': resolution: {integrity: sha512-uw6guiW/gcAGPDhLmd77/6lW8QLeiV5RUTsAX46Db6oLhGaVj4lhnPwb184s1bkc8kdVg/+h988dro8GRDpmYQ==} + '@lucide/svelte@0.545.0': + resolution: {integrity: sha512-RlAtWefx9MdpXaOMbx3Qv3/NqpeZKOIPxN2D0RBN2+op0opKly8VgYEEWZTT6Ow/zf7UwyTg6/0ExJlsVLK+8g==} + peerDependencies: + svelte: ^5 + '@nodelib/fs.scandir@2.1.5': resolution: {integrity: sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==} engines: {node: '>= 8'} @@ -2118,6 +2138,19 @@ packages: resolution: {integrity: sha512-LnSywHHQM/nJekC65d84T1Yo85IeCYN4AryWYPhTokSvcEAFdYFCfbMhX1mc0zHizT736QQj0nalUk+SXaWrEQ==} engines: {node: '>=18'} + tailwind-merge@3.3.1: + resolution: {integrity: sha512-gBXpgUm/3rp1lMZZrM/w7D8GKqshif0zAymAhbCyIt8KMe+0v9DQ7cdYLR4FHH/cKpdTXb+A/tKKU3eolfsI+g==} + + tailwind-variants@3.1.1: + resolution: {integrity: sha512-ftLXe3krnqkMHsuBTEmaVUXYovXtPyTK7ckEfDRXS8PBZx0bAUas+A0jYxuKA5b8qg++wvQ3d2MQ7l/xeZxbZQ==} + engines: {node: '>=16.x', pnpm: '>=7.x'} + peerDependencies: + tailwind-merge: '>=3.0.0' + tailwindcss: '*' + peerDependenciesMeta: + tailwind-merge: + optional: true + tailwindcss@4.1.11: resolution: {integrity: sha512-2E9TBm6MDD/xKYe+dvJZAmg3yxIEDNRc0jwlNyDg/4Fil2QcSLjFKGVff0lAf1jjeaArlG/M75Ey/EYr/OJtBA==} @@ -2159,6 +2192,9 @@ packages: tslib@2.8.1: resolution: {integrity: sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==} + tw-animate-css@1.4.0: + resolution: {integrity: sha512-7bziOlRqH0hJx80h/3mbicLW7o8qLsH5+RaLR2t+OHM3D0JlWGODQKQ4cxbK7WlvmUxpcj6Kgu6EKqjrGFe3QQ==} + type-check@0.4.0: resolution: {integrity: sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==} engines: {node: '>= 0.8.0'} @@ -2561,6 +2597,10 @@ snapshots: '@jridgewell/resolve-uri': 3.1.2 '@jridgewell/sourcemap-codec': 1.5.4 + '@lucide/svelte@0.545.0(svelte@5.36.13)': + dependencies: + svelte: 5.36.13 + '@nodelib/fs.scandir@2.1.5': dependencies: '@nodelib/fs.stat': 2.0.5 @@ -4284,6 +4324,14 @@ snapshots: magic-string: 0.30.17 zimmerframe: 1.1.2 + tailwind-merge@3.3.1: {} + + tailwind-variants@3.1.1(tailwind-merge@3.3.1)(tailwindcss@4.1.11): + dependencies: + tailwindcss: 4.1.11 + optionalDependencies: + tailwind-merge: 3.3.1 + tailwindcss@4.1.11: {} tapable@2.2.2: {} @@ -4323,6 +4371,8 @@ snapshots: tslib@2.8.1: {} + tw-animate-css@1.4.0: {} + type-check@0.4.0: dependencies: prelude-ls: 1.2.1 diff --git a/frontend/src/app.css b/frontend/src/app.css index bc37462..6709bec 100644 --- a/frontend/src/app.css +++ b/frontend/src/app.css @@ -1,7 +1,126 @@ @import 'tailwindcss'; +@import 'tw-animate-css'; @plugin '@tailwindcss/typography'; @config "../tailwind.config.js"; +@custom-variant dark (&:is(.dark *)); + +:root { + --radius: 0.625rem; + --background: oklch(1 0 0); + --foreground: oklch(0.13 0.028 261.692); + --card: oklch(1 0 0); + --card-foreground: oklch(0.13 0.028 261.692); + --popover: oklch(1 0 0); + --popover-foreground: oklch(0.13 0.028 261.692); + --primary: oklch(0.21 0.034 264.665); + --primary-foreground: oklch(0.985 0.002 247.839); + --secondary: oklch(0.967 0.003 264.542); + --secondary-foreground: oklch(0.21 0.034 264.665); + --muted: oklch(0.967 0.003 264.542); + --muted-foreground: oklch(0.551 0.027 264.364); + --accent: oklch(0.967 0.003 264.542); + --accent-foreground: oklch(0.21 0.034 264.665); + --destructive: oklch(0.577 0.245 27.325); + --border: oklch(0.928 0.006 264.531); + --input: oklch(0.928 0.006 264.531); + --ring: oklch(0.707 0.022 261.325); + --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.985 0.002 247.839); + --sidebar-foreground: oklch(0.13 0.028 261.692); + --sidebar-primary: oklch(0.21 0.034 264.665); + --sidebar-primary-foreground: oklch(0.985 0.002 247.839); + --sidebar-accent: oklch(0.967 0.003 264.542); + --sidebar-accent-foreground: oklch(0.21 0.034 264.665); + --sidebar-border: oklch(0.928 0.006 264.531); + --sidebar-ring: oklch(0.707 0.022 261.325); +} + +.dark { + --background: oklch(0.13 0.028 261.692); + --foreground: oklch(0.985 0.002 247.839); + --card: oklch(0.21 0.034 264.665); + --card-foreground: oklch(0.985 0.002 247.839); + --popover: oklch(0.21 0.034 264.665); + --popover-foreground: oklch(0.985 0.002 247.839); + --primary: oklch(0.928 0.006 264.531); + --primary-foreground: oklch(0.21 0.034 264.665); + --secondary: oklch(0.278 0.033 256.848); + --secondary-foreground: oklch(0.985 0.002 247.839); + --muted: oklch(0.278 0.033 256.848); + --muted-foreground: oklch(0.707 0.022 261.325); + --accent: oklch(0.278 0.033 256.848); + --accent-foreground: oklch(0.985 0.002 247.839); + --destructive: oklch(0.704 0.191 22.216); + --border: oklch(1 0 0 / 10%); + --input: oklch(1 0 0 / 15%); + --ring: oklch(0.551 0.027 264.364); + --chart-1: oklch(0.488 0.243 264.376); + --chart-2: oklch(0.696 0.17 162.48); + --chart-3: oklch(0.769 0.188 70.08); + --chart-4: oklch(0.627 0.265 303.9); + --chart-5: oklch(0.645 0.246 16.439); + --sidebar: oklch(0.21 0.034 264.665); + --sidebar-foreground: oklch(0.985 0.002 247.839); + --sidebar-primary: oklch(0.488 0.243 264.376); + --sidebar-primary-foreground: oklch(0.985 0.002 247.839); + --sidebar-accent: oklch(0.278 0.033 256.848); + --sidebar-accent-foreground: oklch(0.985 0.002 247.839); + --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; + } +} + @font-face { font-family: 'HackNerdMono'; src: url('/font/HackNerdMono.woff2') format('woff2'); diff --git a/frontend/src/lib/common/framework/components/utils.ts b/frontend/src/lib/common/framework/components/utils.ts new file mode 100644 index 0000000..55b3a91 --- /dev/null +++ b/frontend/src/lib/common/framework/components/utils.ts @@ -0,0 +1,13 @@ +import { clsx, type ClassValue } from "clsx"; +import { twMerge } from "tailwind-merge"; + +export function cn(...inputs: ClassValue[]) { + return twMerge(clsx(inputs)); +} + +// eslint-disable-next-line @typescript-eslint/no-explicit-any +export type WithoutChild = T extends { child?: any } ? Omit : T; +// eslint-disable-next-line @typescript-eslint/no-explicit-any +export type WithoutChildren = T extends { children?: any } ? Omit : T; +export type WithoutChildrenOrChild = WithoutChildren>; +export type WithElementRef = T & { ref?: U | null };