feat(ui): integrate official shadcn/ui + Radix primitives + cmdk + cva in src/components/ui
This commit is contained in:
1 parent
cbc47336d6
commit
07d2050c5f
21 files changed
+1606
-471
No files matched your search
Generated
+711
-1
@@ -8,7 +8,15 @@
|
|||||||
"name": "homelab-console",
|
"name": "homelab-console",
|
||||||
"version": "3.0.0",
|
"version": "3.0.0",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
"@radix-ui/react-dialog": "^1.3.1",
|
||||||
|
"@radix-ui/react-label": "^2.1.17",
|
||||||
|
"@radix-ui/react-separator": "^1.2.0",
|
||||||
|
"@radix-ui/react-slot": "^1.5.0",
|
||||||
|
"@radix-ui/react-switch": "^1.4.1",
|
||||||
|
"@radix-ui/react-tabs": "^1.2.1",
|
||||||
|
"class-variance-authority": "^0.7.1",
|
||||||
"clsx": "^2.1.1",
|
"clsx": "^2.1.1",
|
||||||
|
"cmdk": "^1.1.1",
|
||||||
"lucide-react": "^0.475.0",
|
"lucide-react": "^0.475.0",
|
||||||
"react": "^19.0.0",
|
"react": "^19.0.0",
|
||||||
"react-dom": "^19.0.0",
|
"react-dom": "^19.0.0",
|
||||||
@@ -799,6 +807,535 @@
|
|||||||
"@jridgewell/sourcemap-codec": "^1.4.14"
|
"@jridgewell/sourcemap-codec": "^1.4.14"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/@radix-ui/primitive": {
|
||||||
|
"version": "1.2.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/@radix-ui/primitive/-/primitive-1.2.0.tgz",
|
||||||
|
"integrity": "sha512-KABY+VMIRPJL+Vyzv/335FEhXXD/xDv8BMb1kl48/aXJZMH0LtHCTx5ga5RnxaEP3nZHtmTLf1P9vanmFX46bw==",
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
|
"node_modules/@radix-ui/react-collection": {
|
||||||
|
"version": "1.2.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/@radix-ui/react-collection/-/react-collection-1.2.0.tgz",
|
||||||
|
"integrity": "sha512-+yiQzqcc7Sy7/5s0jrMCjpPRFB3538VNrXPWdTLZhE/SCfgEws4aI6lHWN/vvcnyhHndROWBzqmqB4QBrITZ9Q==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"@radix-ui/react-compose-refs": "1.1.5",
|
||||||
|
"@radix-ui/react-context": "1.3.0",
|
||||||
|
"@radix-ui/react-primitive": "2.2.0",
|
||||||
|
"@radix-ui/react-slot": "1.5.0",
|
||||||
|
"@radix-ui/react-use-layout-effect": "1.1.4"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"@types/react": "*",
|
||||||
|
"@types/react-dom": "*",
|
||||||
|
"react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc",
|
||||||
|
"react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
|
||||||
|
},
|
||||||
|
"peerDependenciesMeta": {
|
||||||
|
"@types/react": {
|
||||||
|
"optional": true
|
||||||
|
},
|
||||||
|
"@types/react-dom": {
|
||||||
|
"optional": true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@radix-ui/react-compose-refs": {
|
||||||
|
"version": "1.1.5",
|
||||||
|
"resolved": "https://registry.npmjs.org/@radix-ui/react-compose-refs/-/react-compose-refs-1.1.5.tgz",
|
||||||
|
"integrity": "sha512-+48PbAAbq3didjJxa+OaWY2ZwgAKsNiRGyeHKszblZMQ+kcpd9pAaT11cMkGEie0vsOi3QdeTE6d5Fe3Gn61kA==",
|
||||||
|
"license": "MIT",
|
||||||
|
"peerDependencies": {
|
||||||
|
"@types/react": "*",
|
||||||
|
"react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
|
||||||
|
},
|
||||||
|
"peerDependenciesMeta": {
|
||||||
|
"@types/react": {
|
||||||
|
"optional": true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@radix-ui/react-context": {
|
||||||
|
"version": "1.3.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/@radix-ui/react-context/-/react-context-1.3.0.tgz",
|
||||||
|
"integrity": "sha512-v5AYEocMISkbJ+1fTXzZpiFv7NRr3D2WiYbLIKf+FkmJ8VkP6d503eV2n34MFussHXY1pcoq3yfJiQH77dc4Mg==",
|
||||||
|
"license": "MIT",
|
||||||
|
"peerDependencies": {
|
||||||
|
"@types/react": "*",
|
||||||
|
"react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
|
||||||
|
},
|
||||||
|
"peerDependenciesMeta": {
|
||||||
|
"@types/react": {
|
||||||
|
"optional": true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@radix-ui/react-dialog": {
|
||||||
|
"version": "1.3.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/@radix-ui/react-dialog/-/react-dialog-1.3.1.tgz",
|
||||||
|
"integrity": "sha512-+0E4qvVTwjAWra9sEhJ+9BTsvHE00zkUuNw1VGh7S+Gc13dfYk+S1PVQdchWDj+XYHhAai7Z7IpzXVDCGsDplg==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"@radix-ui/primitive": "1.2.0",
|
||||||
|
"@radix-ui/react-compose-refs": "1.1.5",
|
||||||
|
"@radix-ui/react-context": "1.3.0",
|
||||||
|
"@radix-ui/react-dismissable-layer": "1.2.1",
|
||||||
|
"@radix-ui/react-focus-guards": "1.2.0",
|
||||||
|
"@radix-ui/react-focus-scope": "1.3.1",
|
||||||
|
"@radix-ui/react-id": "1.1.4",
|
||||||
|
"@radix-ui/react-portal": "1.2.0",
|
||||||
|
"@radix-ui/react-presence": "1.1.11",
|
||||||
|
"@radix-ui/react-primitive": "2.2.0",
|
||||||
|
"@radix-ui/react-slot": "1.5.0",
|
||||||
|
"@radix-ui/react-use-controllable-state": "1.3.0",
|
||||||
|
"@radix-ui/react-use-layout-effect": "1.1.4",
|
||||||
|
"aria-hidden": "^1.2.4",
|
||||||
|
"react-remove-scroll": "^2.7.2"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"@types/react": "*",
|
||||||
|
"@types/react-dom": "*",
|
||||||
|
"react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc",
|
||||||
|
"react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
|
||||||
|
},
|
||||||
|
"peerDependenciesMeta": {
|
||||||
|
"@types/react": {
|
||||||
|
"optional": true
|
||||||
|
},
|
||||||
|
"@types/react-dom": {
|
||||||
|
"optional": true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@radix-ui/react-direction": {
|
||||||
|
"version": "1.2.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/@radix-ui/react-direction/-/react-direction-1.2.0.tgz",
|
||||||
|
"integrity": "sha512-FtRA52a/r+LKStFT+neLUm+8vSe7wbzkbQGGeU8ixFiwC8NU14Evoq9ATECqL3eGbcUzJR4fyfA4KkK/q6dfqg==",
|
||||||
|
"license": "MIT",
|
||||||
|
"peerDependencies": {
|
||||||
|
"@types/react": "*",
|
||||||
|
"react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
|
||||||
|
},
|
||||||
|
"peerDependenciesMeta": {
|
||||||
|
"@types/react": {
|
||||||
|
"optional": true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@radix-ui/react-dismissable-layer": {
|
||||||
|
"version": "1.2.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/@radix-ui/react-dismissable-layer/-/react-dismissable-layer-1.2.1.tgz",
|
||||||
|
"integrity": "sha512-KIfDcAsx+oVylFZ4xryVKusJUrLLhTQmfnJW4kwk4Km5myvhHCrj04hnWWUC166+xD4DHdH8tQGylwYX1h6+Pg==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"@radix-ui/primitive": "1.2.0",
|
||||||
|
"@radix-ui/react-compose-refs": "1.1.5",
|
||||||
|
"@radix-ui/react-primitive": "2.2.0",
|
||||||
|
"@radix-ui/react-use-callback-ref": "1.1.5",
|
||||||
|
"@radix-ui/react-use-effect-event": "0.0.5"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"@types/react": "*",
|
||||||
|
"@types/react-dom": "*",
|
||||||
|
"react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc",
|
||||||
|
"react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
|
||||||
|
},
|
||||||
|
"peerDependenciesMeta": {
|
||||||
|
"@types/react": {
|
||||||
|
"optional": true
|
||||||
|
},
|
||||||
|
"@types/react-dom": {
|
||||||
|
"optional": true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@radix-ui/react-focus-guards": {
|
||||||
|
"version": "1.2.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/@radix-ui/react-focus-guards/-/react-focus-guards-1.2.0.tgz",
|
||||||
|
"integrity": "sha512-MPm5R0iRGUDrDnPNxmLBkv+X7pN4XKzt+MbWJEuJQsH3NFH4omrbpXpucyYCCKTSt+na/ZxGhEWQrWTZ0285cA==",
|
||||||
|
"license": "MIT",
|
||||||
|
"peerDependencies": {
|
||||||
|
"@types/react": "*",
|
||||||
|
"react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
|
||||||
|
},
|
||||||
|
"peerDependenciesMeta": {
|
||||||
|
"@types/react": {
|
||||||
|
"optional": true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@radix-ui/react-focus-scope": {
|
||||||
|
"version": "1.3.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/@radix-ui/react-focus-scope/-/react-focus-scope-1.3.1.tgz",
|
||||||
|
"integrity": "sha512-ugeMhbXt4y5S2T1GnIqbWDKEN962PDwnX1fN0eKOOgf5f63Dh2mn8F1FUySlgW0iF6GiVsRF32DzuDIWTUyQJg==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"@radix-ui/react-compose-refs": "1.1.5",
|
||||||
|
"@radix-ui/react-primitive": "2.2.0",
|
||||||
|
"@radix-ui/react-use-callback-ref": "1.1.5"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"@types/react": "*",
|
||||||
|
"@types/react-dom": "*",
|
||||||
|
"react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc",
|
||||||
|
"react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
|
||||||
|
},
|
||||||
|
"peerDependenciesMeta": {
|
||||||
|
"@types/react": {
|
||||||
|
"optional": true
|
||||||
|
},
|
||||||
|
"@types/react-dom": {
|
||||||
|
"optional": true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@radix-ui/react-id": {
|
||||||
|
"version": "1.1.4",
|
||||||
|
"resolved": "https://registry.npmjs.org/@radix-ui/react-id/-/react-id-1.1.4.tgz",
|
||||||
|
"integrity": "sha512-TMQp2llA+RYn7JcjnrMnz7wN4pcVttPZnRZo52PLQsoLVKzNlVwUeHmfePgTgRluXFvlD3GD5g5MOVVTJCO0qA==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"@radix-ui/react-use-layout-effect": "1.1.4"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"@types/react": "*",
|
||||||
|
"react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
|
||||||
|
},
|
||||||
|
"peerDependenciesMeta": {
|
||||||
|
"@types/react": {
|
||||||
|
"optional": true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@radix-ui/react-label": {
|
||||||
|
"version": "2.1.17",
|
||||||
|
"resolved": "https://registry.npmjs.org/@radix-ui/react-label/-/react-label-2.1.17.tgz",
|
||||||
|
"integrity": "sha512-xmj7MN/drx1oUAXkfiFIvyDmGNUIN/omGobvA5oAoNqu9zO2MkISYGZOkBcqSzs/UZXXwV+/3ACRE7q7mGcmkA==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"@radix-ui/react-primitive": "2.2.0"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"@types/react": "*",
|
||||||
|
"@types/react-dom": "*",
|
||||||
|
"react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc",
|
||||||
|
"react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
|
||||||
|
},
|
||||||
|
"peerDependenciesMeta": {
|
||||||
|
"@types/react": {
|
||||||
|
"optional": true
|
||||||
|
},
|
||||||
|
"@types/react-dom": {
|
||||||
|
"optional": true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@radix-ui/react-portal": {
|
||||||
|
"version": "1.2.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/@radix-ui/react-portal/-/react-portal-1.2.0.tgz",
|
||||||
|
"integrity": "sha512-StX7xXnaRU41VvFtMm9zlhVGz8XF6GmWWTXftkQaY+tqVkbs/R9UmAp5mLzHHapk5hM9RMZQvO/3usyIP/g42g==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"@radix-ui/react-primitive": "2.2.0",
|
||||||
|
"@radix-ui/react-use-layout-effect": "1.1.4"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"@types/react": "*",
|
||||||
|
"@types/react-dom": "*",
|
||||||
|
"react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc",
|
||||||
|
"react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
|
||||||
|
},
|
||||||
|
"peerDependenciesMeta": {
|
||||||
|
"@types/react": {
|
||||||
|
"optional": true
|
||||||
|
},
|
||||||
|
"@types/react-dom": {
|
||||||
|
"optional": true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@radix-ui/react-presence": {
|
||||||
|
"version": "1.1.11",
|
||||||
|
"resolved": "https://registry.npmjs.org/@radix-ui/react-presence/-/react-presence-1.1.11.tgz",
|
||||||
|
"integrity": "sha512-Z7/atlkab36Z8hm+V8zYc79r0W+tiOB1o0tun1By94JyadkekLQmtEAnBSP9XKXBNQsStoStS4+gX10JpS5akA==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"@radix-ui/react-use-layout-effect": "1.1.4"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"@types/react": "*",
|
||||||
|
"@types/react-dom": "*",
|
||||||
|
"react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc",
|
||||||
|
"react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
|
||||||
|
},
|
||||||
|
"peerDependenciesMeta": {
|
||||||
|
"@types/react": {
|
||||||
|
"optional": true
|
||||||
|
},
|
||||||
|
"@types/react-dom": {
|
||||||
|
"optional": true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@radix-ui/react-primitive": {
|
||||||
|
"version": "2.2.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/@radix-ui/react-primitive/-/react-primitive-2.2.0.tgz",
|
||||||
|
"integrity": "sha512-HCTSPMNqv55Qk0IX4geJbpyXazmqulcimwuyjxTkKi29v+QZbG2Zho9BnbBq4YS2PF+KlNb2CXoLz66uUmblkg==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"@radix-ui/react-slot": "1.5.0"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"@types/react": "*",
|
||||||
|
"@types/react-dom": "*",
|
||||||
|
"react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc",
|
||||||
|
"react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
|
||||||
|
},
|
||||||
|
"peerDependenciesMeta": {
|
||||||
|
"@types/react": {
|
||||||
|
"optional": true
|
||||||
|
},
|
||||||
|
"@types/react-dom": {
|
||||||
|
"optional": true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@radix-ui/react-roving-focus": {
|
||||||
|
"version": "1.2.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/@radix-ui/react-roving-focus/-/react-roving-focus-1.2.1.tgz",
|
||||||
|
"integrity": "sha512-UwraORzVUeEPMclhO1p0vz0qI+TbdDLO9i+FiOqhJyHKwdpCRCck9yVEscUnAMIzCiuNoQa8D4A9bIpOPXGt6Q==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"@radix-ui/primitive": "1.2.0",
|
||||||
|
"@radix-ui/react-collection": "1.2.0",
|
||||||
|
"@radix-ui/react-compose-refs": "1.1.5",
|
||||||
|
"@radix-ui/react-context": "1.3.0",
|
||||||
|
"@radix-ui/react-direction": "1.2.0",
|
||||||
|
"@radix-ui/react-id": "1.1.4",
|
||||||
|
"@radix-ui/react-primitive": "2.2.0",
|
||||||
|
"@radix-ui/react-use-callback-ref": "1.1.5",
|
||||||
|
"@radix-ui/react-use-controllable-state": "1.3.0",
|
||||||
|
"@radix-ui/react-use-is-hydrated": "0.1.3",
|
||||||
|
"@radix-ui/react-use-layout-effect": "1.1.4"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"@types/react": "*",
|
||||||
|
"@types/react-dom": "*",
|
||||||
|
"react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc",
|
||||||
|
"react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
|
||||||
|
},
|
||||||
|
"peerDependenciesMeta": {
|
||||||
|
"@types/react": {
|
||||||
|
"optional": true
|
||||||
|
},
|
||||||
|
"@types/react-dom": {
|
||||||
|
"optional": true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@radix-ui/react-separator": {
|
||||||
|
"version": "1.2.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/@radix-ui/react-separator/-/react-separator-1.2.0.tgz",
|
||||||
|
"integrity": "sha512-1jUAap0wZEqVP05Ou7kJgRxe/W+Qftng+rUNWGa0qkKbv9r5pWTG3E7a3ELKo2eJoHKMECnzuYrZpSyO1YgcYQ==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"@radix-ui/react-primitive": "2.2.0"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"@types/react": "*",
|
||||||
|
"@types/react-dom": "*",
|
||||||
|
"react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc",
|
||||||
|
"react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
|
||||||
|
},
|
||||||
|
"peerDependenciesMeta": {
|
||||||
|
"@types/react": {
|
||||||
|
"optional": true
|
||||||
|
},
|
||||||
|
"@types/react-dom": {
|
||||||
|
"optional": true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@radix-ui/react-slot": {
|
||||||
|
"version": "1.5.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/@radix-ui/react-slot/-/react-slot-1.5.0.tgz",
|
||||||
|
"integrity": "sha512-tyYd9ddPXAysAbdbD9eYxgwpCSv6f3PncqVSNKQFSR5lcJVLz3cb/pANDoYrLiHBpXv7skortyUgGUWsYuPPFQ==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"@radix-ui/react-compose-refs": "1.1.5"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"@types/react": "*",
|
||||||
|
"react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
|
||||||
|
},
|
||||||
|
"peerDependenciesMeta": {
|
||||||
|
"@types/react": {
|
||||||
|
"optional": true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@radix-ui/react-switch": {
|
||||||
|
"version": "1.4.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/@radix-ui/react-switch/-/react-switch-1.4.1.tgz",
|
||||||
|
"integrity": "sha512-NpBGwc40rY02ZmSvuyeKk0Bvc8ZsNeTMi4qHvJ+ZH4EiYCv0zIXEgdCW4bVdzgIUeP9xxbLJSxKOUfqITYNmkA==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"@radix-ui/primitive": "1.2.0",
|
||||||
|
"@radix-ui/react-compose-refs": "1.1.5",
|
||||||
|
"@radix-ui/react-context": "1.3.0",
|
||||||
|
"@radix-ui/react-primitive": "2.2.0",
|
||||||
|
"@radix-ui/react-use-controllable-state": "1.3.0",
|
||||||
|
"@radix-ui/react-use-size": "1.1.5"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"@types/react": "*",
|
||||||
|
"@types/react-dom": "*",
|
||||||
|
"react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc",
|
||||||
|
"react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
|
||||||
|
},
|
||||||
|
"peerDependenciesMeta": {
|
||||||
|
"@types/react": {
|
||||||
|
"optional": true
|
||||||
|
},
|
||||||
|
"@types/react-dom": {
|
||||||
|
"optional": true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@radix-ui/react-tabs": {
|
||||||
|
"version": "1.2.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/@radix-ui/react-tabs/-/react-tabs-1.2.1.tgz",
|
||||||
|
"integrity": "sha512-hRoJlode+hg5ZNlR+3tkfkUliIybdTpD5lkAvHQ9yaXseGf7XVO9Ib2GPEIOuaJJbo7YWYbNpDDRfo8Dhf6/xA==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"@radix-ui/primitive": "1.2.0",
|
||||||
|
"@radix-ui/react-context": "1.3.0",
|
||||||
|
"@radix-ui/react-direction": "1.2.0",
|
||||||
|
"@radix-ui/react-id": "1.1.4",
|
||||||
|
"@radix-ui/react-presence": "1.1.11",
|
||||||
|
"@radix-ui/react-primitive": "2.2.0",
|
||||||
|
"@radix-ui/react-roving-focus": "1.2.1",
|
||||||
|
"@radix-ui/react-use-controllable-state": "1.3.0"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"@types/react": "*",
|
||||||
|
"@types/react-dom": "*",
|
||||||
|
"react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc",
|
||||||
|
"react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
|
||||||
|
},
|
||||||
|
"peerDependenciesMeta": {
|
||||||
|
"@types/react": {
|
||||||
|
"optional": true
|
||||||
|
},
|
||||||
|
"@types/react-dom": {
|
||||||
|
"optional": true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@radix-ui/react-use-callback-ref": {
|
||||||
|
"version": "1.1.5",
|
||||||
|
"resolved": "https://registry.npmjs.org/@radix-ui/react-use-callback-ref/-/react-use-callback-ref-1.1.5.tgz",
|
||||||
|
"integrity": "sha512-Y5GBf4NKeADoXI1geiMlMuMoURT3k9MizzzKhGb/m55H94aGigZ01PIpkvsO6lML13BA6XYAXSJ6gza7vTn5nw==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"@radix-ui/react-use-layout-effect": "1.1.4"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"@types/react": "*",
|
||||||
|
"react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
|
||||||
|
},
|
||||||
|
"peerDependenciesMeta": {
|
||||||
|
"@types/react": {
|
||||||
|
"optional": true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@radix-ui/react-use-controllable-state": {
|
||||||
|
"version": "1.3.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/@radix-ui/react-use-controllable-state/-/react-use-controllable-state-1.3.0.tgz",
|
||||||
|
"integrity": "sha512-TAcBI7bw2ljYCbv4+zuTSd6zICu8pRt6SDN2vJ/Z06K2MXh6I9WYFe/A9ovlFDtR5HVHGm5iDxDoABm/++dbKg==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"@radix-ui/primitive": "1.2.0",
|
||||||
|
"@radix-ui/react-use-effect-event": "0.0.5",
|
||||||
|
"@radix-ui/react-use-layout-effect": "1.1.4"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"@types/react": "*",
|
||||||
|
"react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
|
||||||
|
},
|
||||||
|
"peerDependenciesMeta": {
|
||||||
|
"@types/react": {
|
||||||
|
"optional": true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@radix-ui/react-use-effect-event": {
|
||||||
|
"version": "0.0.5",
|
||||||
|
"resolved": "https://registry.npmjs.org/@radix-ui/react-use-effect-event/-/react-use-effect-event-0.0.5.tgz",
|
||||||
|
"integrity": "sha512-7cshFL8HGS/7HEiHH+9kL9HBwp2sa9yX18Knwek6KYWmXwM7pegMgta2AXMQKI+rq3JnfSj9x8wYqFMTdG1Jgg==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"@radix-ui/react-use-layout-effect": "1.1.4"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"@types/react": "*",
|
||||||
|
"react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
|
||||||
|
},
|
||||||
|
"peerDependenciesMeta": {
|
||||||
|
"@types/react": {
|
||||||
|
"optional": true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@radix-ui/react-use-is-hydrated": {
|
||||||
|
"version": "0.1.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/@radix-ui/react-use-is-hydrated/-/react-use-is-hydrated-0.1.3.tgz",
|
||||||
|
"integrity": "sha512-umO/aJ+82CpOnhDZUTbILCQf7kU/g0iv+oGs/Q8jw7IkhWBzaEP4sA268PhFAJTFetbwp3ICc6ktpI4TqtxcIw==",
|
||||||
|
"license": "MIT",
|
||||||
|
"peerDependencies": {
|
||||||
|
"@types/react": "*",
|
||||||
|
"react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
|
||||||
|
},
|
||||||
|
"peerDependenciesMeta": {
|
||||||
|
"@types/react": {
|
||||||
|
"optional": true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@radix-ui/react-use-layout-effect": {
|
||||||
|
"version": "1.1.4",
|
||||||
|
"resolved": "https://registry.npmjs.org/@radix-ui/react-use-layout-effect/-/react-use-layout-effect-1.1.4.tgz",
|
||||||
|
"integrity": "sha512-K20DkRkUwDnxEYMBPcg3Y6voLkEy5p5QQmszZgLngKKiC7dzBR/aEuK3w1qlx2JWDUNH6FluahYdgR3BP+QbYw==",
|
||||||
|
"license": "MIT",
|
||||||
|
"peerDependencies": {
|
||||||
|
"@types/react": "*",
|
||||||
|
"react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
|
||||||
|
},
|
||||||
|
"peerDependenciesMeta": {
|
||||||
|
"@types/react": {
|
||||||
|
"optional": true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@radix-ui/react-use-size": {
|
||||||
|
"version": "1.1.5",
|
||||||
|
"resolved": "https://registry.npmjs.org/@radix-ui/react-use-size/-/react-use-size-1.1.5.tgz",
|
||||||
|
"integrity": "sha512-V1McDQkMGzgivwqdu7u96APs7oi0Jvj9LTzgq3WWrDZqxWdAqU9yZnZQqk5OizGFR8badUQwZsrlao7MOlWYOA==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"@radix-ui/react-use-layout-effect": "1.1.4"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"@types/react": "*",
|
||||||
|
"react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
|
||||||
|
},
|
||||||
|
"peerDependenciesMeta": {
|
||||||
|
"@types/react": {
|
||||||
|
"optional": true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/@rolldown/pluginutils": {
|
"node_modules/@rolldown/pluginutils": {
|
||||||
"version": "1.0.0-beta.27",
|
"version": "1.0.0-beta.27",
|
||||||
"resolved": "https://registry.npmjs.org/@rolldown/pluginutils/-/pluginutils-1.0.0-beta.27.tgz",
|
"resolved": "https://registry.npmjs.org/@rolldown/pluginutils/-/pluginutils-1.0.0-beta.27.tgz",
|
||||||
@@ -1625,7 +2162,7 @@
|
|||||||
"version": "19.3.0",
|
"version": "19.3.0",
|
||||||
"resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-19.3.0.tgz",
|
"resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-19.3.0.tgz",
|
||||||
"integrity": "sha512-ZI7bU42mZXXKHn/qNLEw2IrbiINU7X5+vfgdixBHkCNpYWXjKgfQ/P+uyGb5CjOLB9UcnTeg3rylQtV2hym44Q==",
|
"integrity": "sha512-ZI7bU42mZXXKHn/qNLEw2IrbiINU7X5+vfgdixBHkCNpYWXjKgfQ/P+uyGb5CjOLB9UcnTeg3rylQtV2hym44Q==",
|
||||||
"dev": true,
|
"devOptional": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"peerDependencies": {
|
"peerDependencies": {
|
||||||
"@types/react": "^19.3.0"
|
"@types/react": "^19.3.0"
|
||||||
@@ -1652,6 +2189,18 @@
|
|||||||
"vite": "^4.2.0 || ^5.0.0 || ^6.0.0 || ^7.0.0"
|
"vite": "^4.2.0 || ^5.0.0 || ^6.0.0 || ^7.0.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/aria-hidden": {
|
||||||
|
"version": "1.2.6",
|
||||||
|
"resolved": "https://registry.npmjs.org/aria-hidden/-/aria-hidden-1.2.6.tgz",
|
||||||
|
"integrity": "sha512-ik3ZgC9dY/lYVVM++OISsaYDeg1tb0VtP5uL3ouh1koGOaUMDPpbFIei4JkFimWUFPn90sbMNMXQAIVOlnYKJA==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"tslib": "^2.0.0"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=10"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/baseline-browser-mapping": {
|
"node_modules/baseline-browser-mapping": {
|
||||||
"version": "2.11.35",
|
"version": "2.11.35",
|
||||||
"resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.11.35.tgz",
|
"resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.11.35.tgz",
|
||||||
@@ -1720,6 +2269,18 @@
|
|||||||
],
|
],
|
||||||
"license": "CC-BY-4.0"
|
"license": "CC-BY-4.0"
|
||||||
},
|
},
|
||||||
|
"node_modules/class-variance-authority": {
|
||||||
|
"version": "0.7.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/class-variance-authority/-/class-variance-authority-0.7.1.tgz",
|
||||||
|
"integrity": "sha512-Ka+9Trutv7G8M6WT6SeiRWz792K5qEqIGEGzXKhAE6xOWAY6pPH8U+9IY3oCMv6kqTmLsv7Xh/2w2RigkePMsg==",
|
||||||
|
"license": "Apache-2.0",
|
||||||
|
"dependencies": {
|
||||||
|
"clsx": "^2.1.1"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"url": "https://polar.sh/cva"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/clsx": {
|
"node_modules/clsx": {
|
||||||
"version": "2.1.1",
|
"version": "2.1.1",
|
||||||
"resolved": "https://registry.npmjs.org/clsx/-/clsx-2.1.1.tgz",
|
"resolved": "https://registry.npmjs.org/clsx/-/clsx-2.1.1.tgz",
|
||||||
@@ -1729,6 +2290,22 @@
|
|||||||
"node": ">=6"
|
"node": ">=6"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/cmdk": {
|
||||||
|
"version": "1.1.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/cmdk/-/cmdk-1.1.1.tgz",
|
||||||
|
"integrity": "sha512-Vsv7kFaXm+ptHDMZ7izaRsP70GgrW9NBNGswt9OZaVBLlE0SNpDq8eu/VGXyF9r7M0azK3Wy7OlYXsuyYLFzHg==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"@radix-ui/react-compose-refs": "^1.1.1",
|
||||||
|
"@radix-ui/react-dialog": "^1.1.6",
|
||||||
|
"@radix-ui/react-id": "^1.1.0",
|
||||||
|
"@radix-ui/react-primitive": "^2.0.2"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"react": "^18 || ^19 || ^19.0.0-rc",
|
||||||
|
"react-dom": "^18 || ^19 || ^19.0.0-rc"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/convert-source-map": {
|
"node_modules/convert-source-map": {
|
||||||
"version": "2.0.0",
|
"version": "2.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz",
|
||||||
@@ -1771,6 +2348,12 @@
|
|||||||
"node": ">=8"
|
"node": ">=8"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/detect-node-es": {
|
||||||
|
"version": "1.1.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/detect-node-es/-/detect-node-es-1.1.0.tgz",
|
||||||
|
"integrity": "sha512-ypdmJU/TbBby2Dxibuv7ZLW3Bs1QEmM7nHjEANfohJLvE0XVujisn1qPJcZxg+qDucsr+bP6fLD1rPS3AhJ7EQ==",
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
"node_modules/electron-to-chromium": {
|
"node_modules/electron-to-chromium": {
|
||||||
"version": "1.5.478",
|
"version": "1.5.478",
|
||||||
"resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.478.tgz",
|
"resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.478.tgz",
|
||||||
@@ -1887,6 +2470,15 @@
|
|||||||
"node": ">=6.9.0"
|
"node": ">=6.9.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/get-nonce": {
|
||||||
|
"version": "1.0.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/get-nonce/-/get-nonce-1.0.1.tgz",
|
||||||
|
"integrity": "sha512-FJhYRoDaiatfEkUK8HKlicmu/3SGFD51q3itKDGoSTysQJBnfOcxU5GxnhE1E6soB76MbT0MBtnKJuXyAx+96Q==",
|
||||||
|
"license": "MIT",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=6"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/graceful-fs": {
|
"node_modules/graceful-fs": {
|
||||||
"version": "4.2.11",
|
"version": "4.2.11",
|
||||||
"resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz",
|
"resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz",
|
||||||
@@ -2355,6 +2947,75 @@
|
|||||||
"node": ">=0.10.0"
|
"node": ">=0.10.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/react-remove-scroll": {
|
||||||
|
"version": "2.7.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/react-remove-scroll/-/react-remove-scroll-2.7.2.tgz",
|
||||||
|
"integrity": "sha512-Iqb9NjCCTt6Hf+vOdNIZGdTiH1QSqr27H/Ek9sv/a97gfueI/5h1s3yRi1nngzMUaOOToin5dI1dXKdXiF+u0Q==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"react-remove-scroll-bar": "^2.3.7",
|
||||||
|
"react-style-singleton": "^2.2.3",
|
||||||
|
"tslib": "^2.1.0",
|
||||||
|
"use-callback-ref": "^1.3.3",
|
||||||
|
"use-sidecar": "^1.1.3"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=10"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"@types/react": "*",
|
||||||
|
"react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc"
|
||||||
|
},
|
||||||
|
"peerDependenciesMeta": {
|
||||||
|
"@types/react": {
|
||||||
|
"optional": true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/react-remove-scroll-bar": {
|
||||||
|
"version": "2.3.8",
|
||||||
|
"resolved": "https://registry.npmjs.org/react-remove-scroll-bar/-/react-remove-scroll-bar-2.3.8.tgz",
|
||||||
|
"integrity": "sha512-9r+yi9+mgU33AKcj6IbT9oRCO78WriSj6t/cF8DWBZJ9aOGPOTEDvdUDz1FwKim7QXWwmHqtdHnRJfhAxEG46Q==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"react-style-singleton": "^2.2.2",
|
||||||
|
"tslib": "^2.0.0"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=10"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"@types/react": "*",
|
||||||
|
"react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0"
|
||||||
|
},
|
||||||
|
"peerDependenciesMeta": {
|
||||||
|
"@types/react": {
|
||||||
|
"optional": true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/react-style-singleton": {
|
||||||
|
"version": "2.2.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/react-style-singleton/-/react-style-singleton-2.2.3.tgz",
|
||||||
|
"integrity": "sha512-b6jSvxvVnyptAiLjbkWLE/lOnR4lfTtDAl+eUC7RZy+QQWc6wRzIV2CE6xBuMmDxc2qIihtDCZD5NPOFl7fRBQ==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"get-nonce": "^1.0.0",
|
||||||
|
"tslib": "^2.0.0"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=10"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"@types/react": "*",
|
||||||
|
"react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc"
|
||||||
|
},
|
||||||
|
"peerDependenciesMeta": {
|
||||||
|
"@types/react": {
|
||||||
|
"optional": true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/rollup": {
|
"node_modules/rollup": {
|
||||||
"version": "4.67.5",
|
"version": "4.67.5",
|
||||||
"resolved": "https://registry.npmjs.org/rollup/-/rollup-4.67.5.tgz",
|
"resolved": "https://registry.npmjs.org/rollup/-/rollup-4.67.5.tgz",
|
||||||
@@ -2475,6 +3136,12 @@
|
|||||||
"url": "https://github.com/sponsors/SuperchupuDev"
|
"url": "https://github.com/sponsors/SuperchupuDev"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/tslib": {
|
||||||
|
"version": "2.9.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/tslib/-/tslib-2.9.1.tgz",
|
||||||
|
"integrity": "sha512-a4Ux9VKuIMHsOS7ENbEq6hK2ZGYIT27Rtl0E4eD9KqwxhKiQeMZE/9xb3IdYwWaSOyIBQLExhQgXDTYoiCxnWA==",
|
||||||
|
"license": "0BSD"
|
||||||
|
},
|
||||||
"node_modules/typescript": {
|
"node_modules/typescript": {
|
||||||
"version": "5.9.3",
|
"version": "5.9.3",
|
||||||
"resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz",
|
"resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz",
|
||||||
@@ -2520,6 +3187,49 @@
|
|||||||
"browserslist": ">= 4.21.0"
|
"browserslist": ">= 4.21.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/use-callback-ref": {
|
||||||
|
"version": "1.3.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/use-callback-ref/-/use-callback-ref-1.3.3.tgz",
|
||||||
|
"integrity": "sha512-jQL3lRnocaFtu3V00JToYz/4QkNWswxijDaCVNZRiRTO3HQDLsdu1ZtmIUvV4yPp+rvWm5j0y0TG/S61cuijTg==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"tslib": "^2.0.0"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=10"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"@types/react": "*",
|
||||||
|
"react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc"
|
||||||
|
},
|
||||||
|
"peerDependenciesMeta": {
|
||||||
|
"@types/react": {
|
||||||
|
"optional": true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/use-sidecar": {
|
||||||
|
"version": "1.1.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/use-sidecar/-/use-sidecar-1.1.3.tgz",
|
||||||
|
"integrity": "sha512-Fedw0aZvkhynoPYlA5WXrMCAMm+nSWdZt6lzJQ7Ok8S6Q+VsHmHpRWndVRJ8Be0ZbkfPc5LRYH+5XrzXcEeLRQ==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"detect-node-es": "^1.1.0",
|
||||||
|
"tslib": "^2.0.0"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=10"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"@types/react": "*",
|
||||||
|
"react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc"
|
||||||
|
},
|
||||||
|
"peerDependenciesMeta": {
|
||||||
|
"@types/react": {
|
||||||
|
"optional": true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/vite": {
|
"node_modules/vite": {
|
||||||
"version": "6.4.5",
|
"version": "6.4.5",
|
||||||
"resolved": "https://registry.npmjs.org/vite/-/vite-6.4.5.tgz",
|
"resolved": "https://registry.npmjs.org/vite/-/vite-6.4.5.tgz",
|
||||||
|
|||||||
@@ -9,7 +9,15 @@
|
|||||||
"preview": "vite preview"
|
"preview": "vite preview"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
"@radix-ui/react-dialog": "^1.3.1",
|
||||||
|
"@radix-ui/react-label": "^2.1.17",
|
||||||
|
"@radix-ui/react-separator": "^1.2.0",
|
||||||
|
"@radix-ui/react-slot": "^1.5.0",
|
||||||
|
"@radix-ui/react-switch": "^1.4.1",
|
||||||
|
"@radix-ui/react-tabs": "^1.2.1",
|
||||||
|
"class-variance-authority": "^0.7.1",
|
||||||
"clsx": "^2.1.1",
|
"clsx": "^2.1.1",
|
||||||
|
"cmdk": "^1.1.1",
|
||||||
"lucide-react": "^0.475.0",
|
"lucide-react": "^0.475.0",
|
||||||
"react": "^19.0.0",
|
"react": "^19.0.0",
|
||||||
"react-dom": "^19.0.0",
|
"react-dom": "^19.0.0",
|
||||||
@@ -20,9 +28,9 @@
|
|||||||
"@tailwindcss/vite": "^4.0.6",
|
"@tailwindcss/vite": "^4.0.6",
|
||||||
"@types/react": "^19.0.8",
|
"@types/react": "^19.0.8",
|
||||||
"@types/react-dom": "^19.0.3",
|
"@types/react-dom": "^19.0.3",
|
||||||
|
"@vitejs/plugin-react": "^4.3.4",
|
||||||
"tailwindcss": "^4.0.6",
|
"tailwindcss": "^4.0.6",
|
||||||
"typescript": "^5.7.3",
|
"typescript": "^5.7.3",
|
||||||
"vite": "^6.1.0",
|
"vite": "^6.1.0"
|
||||||
"@vitejs/plugin-react": "^4.3.4"
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1,6 +1,8 @@
|
|||||||
import React, { useEffect, useState } from 'react';
|
import React, { useEffect, useState } from 'react';
|
||||||
import { usePortalStore } from '../store/usePortalStore';
|
import { usePortalStore } from '../store/usePortalStore';
|
||||||
import { AuthGateCard } from './AuthGateModal';
|
import { AuthGateCard } from './AuthGateModal';
|
||||||
|
import { Button } from './ui/button';
|
||||||
|
import { Dialog, DialogContent, DialogDescription, DialogTitle } from './ui/dialog';
|
||||||
|
|
||||||
export const AgyTerminalModal: React.FC = () => {
|
export const AgyTerminalModal: React.FC = () => {
|
||||||
const { showAgyModal, setShowAgyModal, getEffectiveMode } = usePortalStore();
|
const { showAgyModal, setShowAgyModal, getEffectiveMode } = usePortalStore();
|
||||||
@@ -35,12 +37,15 @@ export const AgyTerminalModal: React.FC = () => {
|
|||||||
});
|
});
|
||||||
}, [showAgyModal, mode]);
|
}, [showAgyModal, mode]);
|
||||||
|
|
||||||
if (!showAgyModal) return null;
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="fixed inset-0 z-50 bg-black/75 backdrop-blur-sm flex items-center justify-center p-3 sm:p-6">
|
<Dialog open={showAgyModal} onOpenChange={setShowAgyModal}>
|
||||||
<div className="w-full h-full max-w-6xl hl-card flex flex-col overflow-hidden shadow-2xl">
|
<DialogContent className="w-[95vw] max-w-6xl h-[88vh] flex flex-col overflow-hidden p-0">
|
||||||
<div className="px-4 py-2.5 border-b hl-border flex items-center justify-between hl-muted">
|
<DialogTitle className="sr-only">AGY 云端工作台</DialogTitle>
|
||||||
|
<DialogDescription className="sr-only">
|
||||||
|
M6 Mac mini 上的 24 小时 tmux 常驻 AI 编程终端
|
||||||
|
</DialogDescription>
|
||||||
|
|
||||||
|
<div className="px-4 py-2.5 border-b hl-border flex items-center justify-between hl-muted shrink-0">
|
||||||
<div className="flex items-center gap-2.5">
|
<div className="flex items-center gap-2.5">
|
||||||
<img
|
<img
|
||||||
src="https://cdn.jsdelivr.net/gh/walkxcode/dashboard-icons/png/google-gemini.png"
|
src="https://cdn.jsdelivr.net/gh/walkxcode/dashboard-icons/png/google-gemini.png"
|
||||||
@@ -52,29 +57,34 @@ export const AgyTerminalModal: React.FC = () => {
|
|||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
<div className="flex items-center gap-2">
|
<div className="flex items-center gap-2">
|
||||||
<a
|
<Button variant="outline" size="sm" asChild>
|
||||||
href={url}
|
<a
|
||||||
target="_blank"
|
href={url}
|
||||||
rel="noopener noreferrer"
|
target="_blank"
|
||||||
className="hl-btn-outline px-2.5 py-1 text-xs font-mono-num"
|
rel="noopener noreferrer"
|
||||||
>
|
className="font-mono-num"
|
||||||
新标签页全屏 ↗
|
>
|
||||||
</a>
|
新标签页全屏 ↗
|
||||||
<button
|
</a>
|
||||||
onClick={() => setShowAgyModal(false)}
|
</Button>
|
||||||
className="hl-btn-primary px-3 py-1 text-xs cursor-pointer"
|
<Button size="sm" onClick={() => setShowAgyModal(false)}>
|
||||||
>
|
|
||||||
收起终端
|
收起终端
|
||||||
</button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{!authChecked ? (
|
{!authChecked ? (
|
||||||
<div className="flex-1 flex items-center justify-center text-xs font-mono-num" style={{ color: 'var(--text-muted)' }}>
|
<div
|
||||||
|
className="flex-1 flex items-center justify-center text-xs font-mono-num"
|
||||||
|
style={{ color: 'var(--text-muted)' }}
|
||||||
|
>
|
||||||
正在校验 HomeLab SSO 通行证...
|
正在校验 HomeLab SSO 通行证...
|
||||||
</div>
|
</div>
|
||||||
) : !authenticated ? (
|
) : !authenticated ? (
|
||||||
<div className="flex-1 flex items-center justify-center p-4" style={{ backgroundColor: 'var(--bg-page)' }}>
|
<div
|
||||||
|
className="flex-1 flex items-center justify-center p-4"
|
||||||
|
style={{ backgroundColor: 'var(--bg-page)' }}
|
||||||
|
>
|
||||||
<AuthGateCard
|
<AuthGateCard
|
||||||
targetUrl={url}
|
targetUrl={url}
|
||||||
onSuccess={() => setAuthenticated(true)}
|
onSuccess={() => setAuthenticated(true)}
|
||||||
@@ -82,9 +92,13 @@ export const AgyTerminalModal: React.FC = () => {
|
|||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
) : (
|
) : (
|
||||||
<iframe src={url} title="AGY Cloud Workbench" className="w-full flex-1 bg-black border-0" />
|
<iframe
|
||||||
|
src={url}
|
||||||
|
title="AGY Cloud Workbench"
|
||||||
|
className="w-full flex-1 bg-black border-0"
|
||||||
|
/>
|
||||||
)}
|
)}
|
||||||
</div>
|
</DialogContent>
|
||||||
</div>
|
</Dialog>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
@@ -1,5 +1,10 @@
|
|||||||
import React, { useState } from 'react';
|
import React, { useState } from 'react';
|
||||||
import { Check, Eye, EyeOff, Lock, ShieldCheck, Terminal, User } from 'lucide-react';
|
import { Eye, EyeOff, Lock, ShieldCheck, Terminal, User } from 'lucide-react';
|
||||||
|
import { Badge } from './ui/badge';
|
||||||
|
import { Button } from './ui/button';
|
||||||
|
import { Card } from './ui/card';
|
||||||
|
import { InputGroup } from './ui/input';
|
||||||
|
import { Switch } from './ui/switch';
|
||||||
|
|
||||||
interface AuthGateCardProps {
|
interface AuthGateCardProps {
|
||||||
targetUrl?: string;
|
targetUrl?: string;
|
||||||
@@ -50,11 +55,7 @@ export const AuthGateCard: React.FC<AuthGateCardProps> = ({
|
|||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div
|
<Card className={`w-full max-w-md p-6 space-y-5 ${embedded ? '' : 'shadow-2xl'}`}>
|
||||||
className={`w-full max-w-md hl-card p-6 space-y-5 ${
|
|
||||||
embedded ? '' : 'shadow-2xl'
|
|
||||||
}`}
|
|
||||||
>
|
|
||||||
{/* Header */}
|
{/* Header */}
|
||||||
<div className="flex items-start justify-between gap-3 border-b hl-border pb-4">
|
<div className="flex items-start justify-between gap-3 border-b hl-border pb-4">
|
||||||
<div className="flex items-center gap-3">
|
<div className="flex items-center gap-3">
|
||||||
@@ -67,14 +68,12 @@ export const AuthGateCard: React.FC<AuthGateCardProps> = ({
|
|||||||
<div>
|
<div>
|
||||||
<div className="flex items-center gap-2">
|
<div className="flex items-center gap-2">
|
||||||
<h3 className="text-sm font-bold tracking-tight">HomeLab 统一安全通行证</h3>
|
<h3 className="text-sm font-bold tracking-tight">HomeLab 统一安全通行证</h3>
|
||||||
<span
|
<Badge variant="success">SSO 2.0</Badge>
|
||||||
className="text-[10px] font-mono-num px-1.5 py-0.5 rounded font-semibold"
|
|
||||||
style={{ backgroundColor: 'var(--success-soft)', color: 'var(--success)' }}
|
|
||||||
>
|
|
||||||
SSO 2.0
|
|
||||||
</span>
|
|
||||||
</div>
|
</div>
|
||||||
<p className="text-xs font-mono-num mt-0.5 truncate max-w-[260px]" style={{ color: 'var(--text-muted)' }}>
|
<p
|
||||||
|
className="text-xs font-mono-num mt-0.5 truncate max-w-[260px]"
|
||||||
|
style={{ color: 'var(--text-muted)' }}
|
||||||
|
>
|
||||||
{targetUrl}
|
{targetUrl}
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
@@ -87,7 +86,7 @@ export const AuthGateCard: React.FC<AuthGateCardProps> = ({
|
|||||||
<label className="block text-xs font-semibold" style={{ color: 'var(--text-muted)' }}>
|
<label className="block text-xs font-semibold" style={{ color: 'var(--text-muted)' }}>
|
||||||
管理员账号 (Username)
|
管理员账号 (Username)
|
||||||
</label>
|
</label>
|
||||||
<div className="hl-input-group">
|
<InputGroup>
|
||||||
<User className="w-4 h-4 shrink-0" style={{ color: 'var(--text-subtle)' }} />
|
<User className="w-4 h-4 shrink-0" style={{ color: 'var(--text-subtle)' }} />
|
||||||
<input
|
<input
|
||||||
type="text"
|
type="text"
|
||||||
@@ -97,7 +96,7 @@ export const AuthGateCard: React.FC<AuthGateCardProps> = ({
|
|||||||
placeholder="输入管理员账号 (如 ixvii0iv)"
|
placeholder="输入管理员账号 (如 ixvii0iv)"
|
||||||
className="font-mono-num"
|
className="font-mono-num"
|
||||||
/>
|
/>
|
||||||
</div>
|
</InputGroup>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="space-y-1.5">
|
<div className="space-y-1.5">
|
||||||
@@ -109,7 +108,7 @@ export const AuthGateCard: React.FC<AuthGateCardProps> = ({
|
|||||||
与 Mac mini 密码一致
|
与 Mac mini 密码一致
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
<div className="hl-input-group">
|
<InputGroup>
|
||||||
<Lock className="w-4 h-4 shrink-0" style={{ color: 'var(--text-subtle)' }} />
|
<Lock className="w-4 h-4 shrink-0" style={{ color: 'var(--text-subtle)' }} />
|
||||||
<input
|
<input
|
||||||
type={showPass ? 'text' : 'password'}
|
type={showPass ? 'text' : 'password'}
|
||||||
@@ -129,36 +128,15 @@ export const AuthGateCard: React.FC<AuthGateCardProps> = ({
|
|||||||
>
|
>
|
||||||
{showPass ? <EyeOff className="w-3.5 h-3.5" /> : <Eye className="w-3.5 h-3.5" />}
|
{showPass ? <EyeOff className="w-3.5 h-3.5" /> : <Eye className="w-3.5 h-3.5" />}
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</InputGroup>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Custom Checkbox */}
|
{/* Official @radix-ui/react-switch Toggle */}
|
||||||
<div className="flex items-center justify-between pt-0.5">
|
<div className="flex items-center justify-between pt-1">
|
||||||
<button
|
<span className="text-xs select-none" style={{ color: 'var(--text-muted)' }}>
|
||||||
type="button"
|
在此设备保持 30 天免密通行 (*.961504.xyz)
|
||||||
onClick={() => setRememberMe(!rememberMe)}
|
</span>
|
||||||
className="flex items-center gap-2 text-xs cursor-pointer select-none"
|
<Switch checked={rememberMe} onCheckedChange={setRememberMe} />
|
||||||
style={{ color: 'var(--text-muted)' }}
|
|
||||||
>
|
|
||||||
<span
|
|
||||||
className="w-4 h-4 rounded flex items-center justify-center border transition-colors"
|
|
||||||
style={
|
|
||||||
rememberMe
|
|
||||||
? {
|
|
||||||
backgroundColor: 'var(--accent)',
|
|
||||||
borderColor: 'var(--accent)',
|
|
||||||
color: '#ffffff',
|
|
||||||
}
|
|
||||||
: {
|
|
||||||
backgroundColor: 'var(--bg-muted)',
|
|
||||||
borderColor: 'var(--border-color)',
|
|
||||||
}
|
|
||||||
}
|
|
||||||
>
|
|
||||||
{rememberMe && <Check className="w-3 h-3" />}
|
|
||||||
</span>
|
|
||||||
<span>在此设备保持 30 天免密通行 (*.961504.xyz)</span>
|
|
||||||
</button>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{errorMsg && (
|
{errorMsg && (
|
||||||
@@ -177,24 +155,16 @@ export const AuthGateCard: React.FC<AuthGateCardProps> = ({
|
|||||||
{/* Actions */}
|
{/* Actions */}
|
||||||
<div className="flex items-center justify-end gap-2.5 pt-2">
|
<div className="flex items-center justify-end gap-2.5 pt-2">
|
||||||
{onCancel && (
|
{onCancel && (
|
||||||
<button
|
<Button type="button" variant="outline" onClick={onCancel}>
|
||||||
type="button"
|
|
||||||
onClick={onCancel}
|
|
||||||
className="hl-btn-outline px-4 py-2 text-xs font-medium cursor-pointer"
|
|
||||||
>
|
|
||||||
取消
|
取消
|
||||||
</button>
|
</Button>
|
||||||
)}
|
)}
|
||||||
<button
|
<Button type="submit" disabled={loading} className="px-4">
|
||||||
type="submit"
|
|
||||||
disabled={loading}
|
|
||||||
className="hl-btn-primary px-5 py-2 text-xs font-semibold flex items-center gap-2 cursor-pointer"
|
|
||||||
>
|
|
||||||
<Terminal className="w-3.5 h-3.5" />
|
<Terminal className="w-3.5 h-3.5" />
|
||||||
<span>{loading ? '正在验证签名...' : '解锁并进入工作台'}</span>
|
<span>{loading ? '正在验证签名...' : '解锁并进入工作台'}</span>
|
||||||
</button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
</form>
|
</form>
|
||||||
</div>
|
</Card>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
@@ -1,6 +1,14 @@
|
|||||||
import React, { useEffect, useMemo, useRef, useState } from 'react';
|
import React, { useMemo } from 'react';
|
||||||
import { Search } from 'lucide-react';
|
|
||||||
import { usePortalStore } from '../store/usePortalStore';
|
import { usePortalStore } from '../store/usePortalStore';
|
||||||
|
import { Badge } from './ui/badge';
|
||||||
|
import {
|
||||||
|
CommandDialog,
|
||||||
|
CommandEmpty,
|
||||||
|
CommandGroup,
|
||||||
|
CommandInput,
|
||||||
|
CommandItem,
|
||||||
|
CommandList,
|
||||||
|
} from './ui/command';
|
||||||
|
|
||||||
interface CmdItem {
|
interface CmdItem {
|
||||||
id: string;
|
id: string;
|
||||||
@@ -21,16 +29,6 @@ export const CommandPalette: React.FC = () => {
|
|||||||
getEffectiveMode,
|
getEffectiveMode,
|
||||||
} = usePortalStore();
|
} = usePortalStore();
|
||||||
|
|
||||||
const [query, setQuery] = useState('');
|
|
||||||
const inputRef = useRef<HTMLInputElement>(null);
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
if (showCmdPalette) {
|
|
||||||
setQuery('');
|
|
||||||
setTimeout(() => inputRef.current?.focus(), 20);
|
|
||||||
}
|
|
||||||
}, [showCmdPalette]);
|
|
||||||
|
|
||||||
const commands = useMemo<CmdItem[]>(() => {
|
const commands = useMemo<CmdItem[]>(() => {
|
||||||
const mode = getEffectiveMode();
|
const mode = getEffectiveMode();
|
||||||
const resolve = (edge: string, home: string, lan: string) =>
|
const resolve = (edge: string, home: string, lan: string) =>
|
||||||
@@ -156,86 +154,32 @@ export const CommandPalette: React.FC = () => {
|
|||||||
];
|
];
|
||||||
}, [getEffectiveMode, setShowAgyModal, setTheme, triggerAction, copyCmd]);
|
}, [getEffectiveMode, setShowAgyModal, setTheme, triggerAction, copyCmd]);
|
||||||
|
|
||||||
const filtered = useMemo(() => {
|
|
||||||
const q = query.trim().toLowerCase();
|
|
||||||
if (!q) return commands;
|
|
||||||
return commands.filter(
|
|
||||||
(c) => c.title.toLowerCase().includes(q) || c.hint.toLowerCase().includes(q)
|
|
||||||
);
|
|
||||||
}, [query, commands]);
|
|
||||||
|
|
||||||
if (!showCmdPalette) return null;
|
|
||||||
|
|
||||||
const execute = (cmd: CmdItem) => {
|
|
||||||
setShowCmdPalette(false);
|
|
||||||
cmd.run();
|
|
||||||
};
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div
|
<CommandDialog open={showCmdPalette} onOpenChange={setShowCmdPalette}>
|
||||||
onClick={(e) => {
|
<CommandInput placeholder="输入服务名称、SSH 指令、路由模式或主题..." />
|
||||||
if (e.target === e.currentTarget) setShowCmdPalette(false);
|
<CommandList>
|
||||||
}}
|
<CommandEmpty>未找到匹配的指令或服务</CommandEmpty>
|
||||||
className="fixed inset-0 z-50 bg-black/60 backdrop-blur-sm flex items-start justify-center pt-20 px-4"
|
<CommandGroup heading="快速操作与服务直达 (cmdk + Radix Dialog)">
|
||||||
>
|
{commands.map((cmd) => (
|
||||||
<div className="w-full max-w-xl hl-card overflow-hidden shadow-2xl">
|
<CommandItem
|
||||||
<div className="px-4 py-3 border-b hl-border flex items-center gap-3">
|
|
||||||
<Search className="w-4 h-4" style={{ color: 'var(--text-muted)' }} />
|
|
||||||
<input
|
|
||||||
ref={inputRef}
|
|
||||||
value={query}
|
|
||||||
onChange={(e) => setQuery(e.target.value)}
|
|
||||||
type="text"
|
|
||||||
placeholder="输入服务名称、SSH 指令、路由模式或主题..."
|
|
||||||
className="w-full bg-transparent text-sm outline-none"
|
|
||||||
onKeyDown={(e) => {
|
|
||||||
if (e.key === 'Escape') setShowCmdPalette(false);
|
|
||||||
if (e.key === 'Enter' && filtered.length > 0) execute(filtered[0]);
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
<kbd
|
|
||||||
className="px-1.5 py-0.5 text-[10px] font-mono-num rounded hl-muted"
|
|
||||||
style={{ color: 'var(--text-muted)' }}
|
|
||||||
>
|
|
||||||
ESC
|
|
||||||
</kbd>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className="max-h-80 overflow-y-auto p-2 text-xs">
|
|
||||||
<div
|
|
||||||
className="px-2.5 py-1 text-[10px] font-semibold uppercase tracking-wider"
|
|
||||||
style={{ color: 'var(--text-subtle)' }}
|
|
||||||
>
|
|
||||||
快速操作与服务直达
|
|
||||||
</div>
|
|
||||||
{filtered.map((cmd) => (
|
|
||||||
<button
|
|
||||||
key={cmd.id}
|
key={cmd.id}
|
||||||
onClick={() => execute(cmd)}
|
value={`${cmd.title} ${cmd.hint} ${cmd.type}`}
|
||||||
className="w-full px-3 py-2 rounded-lg text-left flex items-center justify-between transition-colors cursor-pointer"
|
onSelect={() => {
|
||||||
onMouseEnter={(e) => {
|
setShowCmdPalette(false);
|
||||||
e.currentTarget.style.backgroundColor = 'var(--bg-muted)';
|
cmd.run();
|
||||||
}}
|
|
||||||
onMouseLeave={(e) => {
|
|
||||||
e.currentTarget.style.backgroundColor = 'transparent';
|
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<div className="flex items-center gap-2.5">
|
<div className="flex items-center gap-2.5">
|
||||||
<span className="font-medium">{cmd.title}</span>
|
<span className="font-medium">{cmd.title}</span>
|
||||||
<span className="font-mono-num text-[11px]" style={{ color: 'var(--text-muted)' }}>
|
<span className="font-mono-num text-[11px] text-[var(--text-muted)]">
|
||||||
{cmd.hint}
|
{cmd.hint}
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
<span
|
<Badge variant="secondary">{cmd.type}</Badge>
|
||||||
className="text-[10px] font-mono-num px-1.5 py-0.5 rounded hl-muted"
|
</CommandItem>
|
||||||
style={{ color: 'var(--text-muted)' }}
|
|
||||||
>
|
|
||||||
{cmd.type}
|
|
||||||
</span>
|
|
||||||
</button>
|
|
||||||
))}
|
))}
|
||||||
</div>
|
</CommandGroup>
|
||||||
</div>
|
</CommandList>
|
||||||
</div>
|
</CommandDialog>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
@@ -15,6 +15,12 @@ import { usePortalStore } from '../store/usePortalStore';
|
|||||||
import { AuthGateCard } from './AuthGateModal';
|
import { AuthGateCard } from './AuthGateModal';
|
||||||
import { ServiceCard } from './ServiceCard';
|
import { ServiceCard } from './ServiceCard';
|
||||||
import { SshShelf } from './SshShelf';
|
import { SshShelf } from './SshShelf';
|
||||||
|
import { Badge } from './ui/badge';
|
||||||
|
import { Button } from './ui/button';
|
||||||
|
import { Card } from './ui/card';
|
||||||
|
import { Input, InputGroup } from './ui/input';
|
||||||
|
import { Switch } from './ui/switch';
|
||||||
|
import { Tabs, TabsList, TabsTrigger } from './ui/tabs';
|
||||||
|
|
||||||
type StoryId =
|
type StoryId =
|
||||||
| 'layout-architecture'
|
| 'layout-architecture'
|
||||||
@@ -32,6 +38,7 @@ export const StorybookWorkbench: React.FC = () => {
|
|||||||
setTheme,
|
setTheme,
|
||||||
setLayoutPreset,
|
setLayoutPreset,
|
||||||
setViewMode,
|
setViewMode,
|
||||||
|
setShowCmdPalette,
|
||||||
telemetry,
|
telemetry,
|
||||||
showToast,
|
showToast,
|
||||||
} = usePortalStore();
|
} = usePortalStore();
|
||||||
@@ -43,6 +50,7 @@ export const StorybookWorkbench: React.FC = () => {
|
|||||||
// Interactive Playground Props for Form Inputs story
|
// Interactive Playground Props for Form Inputs story
|
||||||
const [demoSearch, setDemoSearch] = useState('');
|
const [demoSearch, setDemoSearch] = useState('');
|
||||||
const [demoHost, setDemoHost] = useState('192.168.31.164');
|
const [demoHost, setDemoHost] = useState('192.168.31.164');
|
||||||
|
const [demoSwitch, setDemoSwitch] = useState(true);
|
||||||
|
|
||||||
const stories: Array<{ id: StoryId; group: string; title: string; icon: React.ReactNode }> = [
|
const stories: Array<{ id: StoryId; group: string; title: string; icon: React.ReactNode }> = [
|
||||||
{
|
{
|
||||||
@@ -59,19 +67,19 @@ export const StorybookWorkbench: React.FC = () => {
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: 'form-inputs',
|
id: 'form-inputs',
|
||||||
group: 'COMPONENTS / 核心组件',
|
group: 'SHADCN / RADIX UI 组件',
|
||||||
title: '03. Input & AuthGate 输入框与登录弹窗',
|
title: '03. shadcn/ui + Radix 原语与认证弹窗',
|
||||||
icon: <KeyRound className="w-3.5 h-3.5" />,
|
icon: <KeyRound className="w-3.5 h-3.5" />,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: 'service-card',
|
id: 'service-card',
|
||||||
group: 'COMPONENTS / 核心组件',
|
group: 'SHADCN / RADIX UI 组件',
|
||||||
title: '04. ServiceCard 内联遥测卡片',
|
title: '04. ServiceCard 内联遥测卡片',
|
||||||
icon: <Layers className="w-3.5 h-3.5" />,
|
icon: <Layers className="w-3.5 h-3.5" />,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: 'ssh-shelf',
|
id: 'ssh-shelf',
|
||||||
group: 'COMPONENTS / 核心组件',
|
group: 'SHADCN / RADIX UI 组件',
|
||||||
title: '05. SshShelf 快捷指令栏',
|
title: '05. SshShelf 快捷指令栏',
|
||||||
icon: <Terminal className="w-3.5 h-3.5" />,
|
icon: <Terminal className="w-3.5 h-3.5" />,
|
||||||
},
|
},
|
||||||
@@ -81,13 +89,13 @@ export const StorybookWorkbench: React.FC = () => {
|
|||||||
<div className="flex-1 max-w-7xl w-full mx-auto px-4 sm:px-6 lg:px-8 py-6">
|
<div className="flex-1 max-w-7xl w-full mx-auto px-4 sm:px-6 lg:px-8 py-6">
|
||||||
<div className="grid grid-cols-1 lg:grid-cols-4 gap-6 items-start">
|
<div className="grid grid-cols-1 lg:grid-cols-4 gap-6 items-start">
|
||||||
{/* Left Sidebar: Storybook Navigation Tree */}
|
{/* Left Sidebar: Storybook Navigation Tree */}
|
||||||
<aside className="hl-card p-4 space-y-4 lg:sticky lg:top-20">
|
<Card className="p-4 space-y-4 lg:sticky lg:top-20">
|
||||||
<div className="flex items-center justify-between border-b hl-border pb-3">
|
<div className="flex items-center justify-between border-b hl-border pb-3">
|
||||||
<div className="flex items-center gap-2">
|
<div className="flex items-center gap-2">
|
||||||
<BookOpen className="w-4 h-4" style={{ color: 'var(--accent)' }} />
|
<BookOpen className="w-4 h-4" style={{ color: 'var(--accent)' }} />
|
||||||
<span className="text-xs font-bold uppercase tracking-wider">Storybook 组件库</span>
|
<span className="text-xs font-bold uppercase tracking-wider">shadcn/ui 组件库</span>
|
||||||
</div>
|
</div>
|
||||||
<span className="text-[10px] font-mono-num px-1.5 py-0.5 rounded hl-muted">v3.2</span>
|
<Badge variant="accent">Radix + cmdk</Badge>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="space-y-1">
|
<div className="space-y-1">
|
||||||
@@ -113,39 +121,27 @@ export const StorybookWorkbench: React.FC = () => {
|
|||||||
|
|
||||||
<div className="pt-3 border-t hl-border space-y-2 text-xs">
|
<div className="pt-3 border-t hl-border space-y-2 text-xs">
|
||||||
<div className="text-[11px] font-semibold" style={{ color: 'var(--text-muted)' }}>
|
<div className="text-[11px] font-semibold" style={{ color: 'var(--text-muted)' }}>
|
||||||
全局外观状态预览
|
全局外观状态预览 (@radix-ui/react-tabs)
|
||||||
</div>
|
</div>
|
||||||
<div className="grid grid-cols-3 gap-1 p-1 rounded-lg hl-muted border hl-border">
|
<Tabs value={theme} onValueChange={(v) => setTheme(v as 'system' | 'light' | 'dark')}>
|
||||||
{(['system', 'light', 'dark'] as const).map((t) => (
|
<TabsList className="w-full grid grid-cols-3">
|
||||||
<button
|
<TabsTrigger value="system">自动</TabsTrigger>
|
||||||
key={t}
|
<TabsTrigger value="light">浅色</TabsTrigger>
|
||||||
onClick={() => setTheme(t)}
|
<TabsTrigger value="dark">深色</TabsTrigger>
|
||||||
className="py-1 rounded text-[11px] font-medium cursor-pointer"
|
</TabsList>
|
||||||
style={
|
</Tabs>
|
||||||
theme === t
|
<Button onClick={() => setViewMode('dashboard')} className="w-full mt-2">
|
||||||
? { backgroundColor: 'var(--bg-card)', color: 'var(--text-main)' }
|
|
||||||
: { color: 'var(--text-muted)' }
|
|
||||||
}
|
|
||||||
>
|
|
||||||
{t === 'system' ? '自动' : t === 'light' ? '浅色' : '深色'}
|
|
||||||
</button>
|
|
||||||
))}
|
|
||||||
</div>
|
|
||||||
<button
|
|
||||||
onClick={() => setViewMode('dashboard')}
|
|
||||||
className="w-full hl-btn-primary py-2 text-xs text-center mt-2 cursor-pointer"
|
|
||||||
>
|
|
||||||
返回中控台主页
|
返回中控台主页
|
||||||
</button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
</aside>
|
</Card>
|
||||||
|
|
||||||
{/* Right Canvas: Interactive Story Preview & Controls */}
|
{/* Right Canvas: Interactive Story Preview & Controls */}
|
||||||
<div className="lg:col-span-3 space-y-6">
|
<div className="lg:col-span-3 space-y-6">
|
||||||
{/* STORY 1: Layout Architecture */}
|
{/* STORY 1: Layout Architecture */}
|
||||||
{activeStory === 'layout-architecture' && (
|
{activeStory === 'layout-architecture' && (
|
||||||
<div className="space-y-6">
|
<div className="space-y-6">
|
||||||
<div className="hl-card p-6 space-y-4">
|
<Card className="p-6 space-y-4">
|
||||||
<div className="flex items-center justify-between border-b hl-border pb-4">
|
<div className="flex items-center justify-between border-b hl-border pb-4">
|
||||||
<div>
|
<div>
|
||||||
<span
|
<span
|
||||||
@@ -168,7 +164,7 @@ export const StorybookWorkbench: React.FC = () => {
|
|||||||
{/* Visual Blueprint Diagram of Two-Tier Header */}
|
{/* Visual Blueprint Diagram of Two-Tier Header */}
|
||||||
<div className="p-4 rounded-xl hl-muted border hl-border space-y-2.5 font-mono-num text-xs">
|
<div className="p-4 rounded-xl hl-muted border hl-border space-y-2.5 font-mono-num text-xs">
|
||||||
<div className="p-3 rounded-lg hl-card flex items-center justify-between">
|
<div className="p-3 rounded-lg hl-card flex items-center justify-between">
|
||||||
<span>[左] 🍎 HomeLab · 导航切换 (中控台 / Storybook)</span>
|
<span>[左] 🍎 HomeLab · Radix Tabs 切换 (中控台 / Storybook)</span>
|
||||||
<span style={{ color: 'var(--accent)' }}>
|
<span style={{ color: 'var(--accent)' }}>
|
||||||
[最右固定] 🔍 搜索 ⌘K · >_ AGY 终端 · ☀️/🌙 主题切换
|
[最右固定] 🔍 搜索 ⌘K · >_ AGY 终端 · ☀️/🌙 主题切换
|
||||||
</span>
|
</span>
|
||||||
@@ -177,7 +173,7 @@ export const StorybookWorkbench: React.FC = () => {
|
|||||||
<span style={{ color: 'var(--success)' }}>
|
<span style={{ color: 'var(--success)' }}>
|
||||||
[左] 实时硬件遥测条 (M6 CPU · RAM · Z4 20TB · SG 专线)
|
[左] 实时硬件遥测条 (M6 CPU · RAM · Z4 20TB · SG 专线)
|
||||||
</span>
|
</span>
|
||||||
<span>[右对齐] 路由模式切换器 (智能路由 / 443 / 10443 / LAN)</span>
|
<span>[右对齐] Radix Tabs 路由切换器 (智能路由 / 443 / 10443 / LAN)</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -225,13 +221,13 @@ export const StorybookWorkbench: React.FC = () => {
|
|||||||
})}
|
})}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</Card>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{/* STORY 2: Theme Tokens */}
|
{/* STORY 2: Theme Tokens */}
|
||||||
{activeStory === 'theme-tokens' && (
|
{activeStory === 'theme-tokens' && (
|
||||||
<div className="hl-card p-6 space-y-6">
|
<Card className="p-6 space-y-6">
|
||||||
<div className="border-b hl-border pb-4 flex items-center justify-between">
|
<div className="border-b hl-border pb-4 flex items-center justify-between">
|
||||||
<div>
|
<div>
|
||||||
<span
|
<span
|
||||||
@@ -244,9 +240,9 @@ export const StorybookWorkbench: React.FC = () => {
|
|||||||
主流双主题色板规范(当前生效:{resolvedTheme === 'dark' ? '深色极客' : '浅色纯净'})
|
主流双主题色板规范(当前生效:{resolvedTheme === 'dark' ? '深色极客' : '浅色纯净'})
|
||||||
</h2>
|
</h2>
|
||||||
</div>
|
</div>
|
||||||
<span className="text-xs font-mono-num px-2.5 py-1 rounded hl-muted">
|
<Badge variant="secondary">
|
||||||
模式: {theme === 'system' ? '跟随设备自动切换' : '手动锁定'}
|
模式: {theme === 'system' ? '跟随设备自动切换' : '手动锁定'}
|
||||||
</span>
|
</Badge>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="grid grid-cols-1 md:grid-cols-2 gap-5">
|
<div className="grid grid-cols-1 md:grid-cols-2 gap-5">
|
||||||
@@ -319,39 +315,75 @@ export const StorybookWorkbench: React.FC = () => {
|
|||||||
);
|
);
|
||||||
})}
|
})}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</Card>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{/* STORY 3: Custom Form Inputs & AuthGate Card */}
|
{/* STORY 3: Official shadcn/ui + Radix UI Primitives & AuthGate Card */}
|
||||||
{activeStory === 'form-inputs' && (
|
{activeStory === 'form-inputs' && (
|
||||||
<div className="hl-card p-6 space-y-6">
|
<Card className="p-6 space-y-6">
|
||||||
<div className="border-b hl-border pb-4 flex items-center justify-between">
|
<div className="border-b hl-border pb-4 flex items-center justify-between">
|
||||||
<div>
|
<div>
|
||||||
<span
|
<span
|
||||||
className="text-[11px] font-mono-num uppercase tracking-wider"
|
className="text-[11px] font-mono-num uppercase tracking-wider"
|
||||||
style={{ color: 'var(--accent)' }}
|
style={{ color: 'var(--accent)' }}
|
||||||
>
|
>
|
||||||
Components / Form Controls & SSO Auth Gate
|
src/components/ui/* · Official shadcn/ui + Radix Primitives + cmdk
|
||||||
</span>
|
</span>
|
||||||
<h2 className="text-lg font-bold mt-0.5">
|
<h2 className="text-lg font-bold mt-0.5">
|
||||||
统一自定义输入框 (`.hl-input`) 与 SSO 认证卡片(彻底禁用浏览器原生弹窗)
|
开源 `shadcn/ui` 组件库(`Input` / `Button` / `Switch` / `Badge` / `CommandDialog` / `AuthGateCard`)
|
||||||
</h2>
|
</h2>
|
||||||
</div>
|
</div>
|
||||||
<KeyRound className="w-4 h-4" style={{ color: 'var(--accent)' }} />
|
<KeyRound className="w-4 h-4" style={{ color: 'var(--accent)' }} />
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="grid grid-cols-1 lg:grid-cols-2 gap-6 items-start">
|
<div className="grid grid-cols-1 lg:grid-cols-2 gap-6 items-start">
|
||||||
{/* Left: Input Primitives Showcase */}
|
{/* Left: shadcn/ui Primitives Showcase */}
|
||||||
<div className="p-5 rounded-xl hl-muted border hl-border space-y-4">
|
<div className="p-5 rounded-xl hl-muted border hl-border space-y-4">
|
||||||
<div className="text-xs font-bold uppercase tracking-wider" style={{ color: 'var(--text-muted)' }}>
|
<div
|
||||||
1. 基础表单输入组件规范 (`.hl-input` / `.hl-input-group`)
|
className="text-xs font-bold uppercase tracking-wider"
|
||||||
|
style={{ color: 'var(--text-muted)' }}
|
||||||
|
>
|
||||||
|
1. `src/components/ui/*` 标准原语展示
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
{/* Button Variants */}
|
||||||
<div className="space-y-1.5">
|
<div className="space-y-1.5">
|
||||||
<label className="block text-xs font-medium" style={{ color: 'var(--text-muted)' }}>
|
<label className="block text-xs font-medium" style={{ color: 'var(--text-muted)' }}>
|
||||||
带前缀图标与快捷键提示的搜索框
|
Button 变体 (`cva` + `@radix-ui/react-slot`)
|
||||||
</label>
|
</label>
|
||||||
<div className="hl-input-group">
|
<div className="flex flex-wrap items-center gap-2">
|
||||||
|
<Button variant="default">Primary 主按钮</Button>
|
||||||
|
<Button variant="outline">Outline 描边</Button>
|
||||||
|
<Button variant="secondary">Secondary 次级</Button>
|
||||||
|
<Button variant="accent">Accent 高亮</Button>
|
||||||
|
<Button variant="ghost" onClick={() => setShowCmdPalette(true)}>
|
||||||
|
唤起 cmdk 面板 ⌘K
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Badge Variants & Radix Switch */}
|
||||||
|
<div className="space-y-1.5">
|
||||||
|
<label className="block text-xs font-medium" style={{ color: 'var(--text-muted)' }}>
|
||||||
|
Badge 标签与 `@radix-ui/react-switch` 开关
|
||||||
|
</label>
|
||||||
|
<div className="flex flex-wrap items-center justify-between gap-2 p-2.5 rounded-lg hl-card">
|
||||||
|
<div className="flex items-center gap-1.5">
|
||||||
|
<Badge variant="default">Default</Badge>
|
||||||
|
<Badge variant="accent">Radix UI</Badge>
|
||||||
|
<Badge variant="success">Online</Badge>
|
||||||
|
<Badge variant="outline">v3.2</Badge>
|
||||||
|
</div>
|
||||||
|
<Switch checked={demoSwitch} onCheckedChange={setDemoSwitch} />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Input & InputGroup */}
|
||||||
|
<div className="space-y-1.5">
|
||||||
|
<label className="block text-xs font-medium" style={{ color: 'var(--text-muted)' }}>
|
||||||
|
`InputGroup` 组合输入框
|
||||||
|
</label>
|
||||||
|
<InputGroup>
|
||||||
<Search className="w-4 h-4 shrink-0" style={{ color: 'var(--text-subtle)' }} />
|
<Search className="w-4 h-4 shrink-0" style={{ color: 'var(--text-subtle)' }} />
|
||||||
<input
|
<input
|
||||||
type="text"
|
type="text"
|
||||||
@@ -365,35 +397,21 @@ export const StorybookWorkbench: React.FC = () => {
|
|||||||
>
|
>
|
||||||
⌘K
|
⌘K
|
||||||
</kbd>
|
</kbd>
|
||||||
</div>
|
</InputGroup>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="space-y-1.5">
|
<div className="space-y-1.5">
|
||||||
<label className="block text-xs font-medium" style={{ color: 'var(--text-muted)' }}>
|
<label className="block text-xs font-medium" style={{ color: 'var(--text-muted)' }}>
|
||||||
等宽字体 IP / 域名输入框 (`.hl-input .font-mono-num`)
|
`Input` 等宽字体输入框
|
||||||
</label>
|
</label>
|
||||||
<input
|
<Input
|
||||||
type="text"
|
type="text"
|
||||||
value={demoHost}
|
value={demoHost}
|
||||||
onChange={(e) => setDemoHost(e.target.value)}
|
onChange={(e) => setDemoHost(e.target.value)}
|
||||||
className="hl-input font-mono-num"
|
className="font-mono-num"
|
||||||
placeholder="192.168.31.164"
|
placeholder="192.168.31.164"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="p-3 rounded-lg hl-card text-[11px] leading-relaxed space-y-1" style={{ color: 'var(--text-muted)' }}>
|
|
||||||
<div className="font-semibold" style={{ color: 'var(--text-main)' }}>
|
|
||||||
为什么不再出现浏览器默认输入框和弹窗?
|
|
||||||
</div>
|
|
||||||
<p>
|
|
||||||
1. 全局 CSS 已设置 <code>appearance: none</code> 与自定义聚焦光环{' '}
|
|
||||||
<code>box-shadow: 0 0 0 3px var(--accent-soft)</code>,并覆盖了 Chrome 自动填充的黄色背景。
|
|
||||||
</p>
|
|
||||||
<p>
|
|
||||||
2. Caddy 网关已移除 <code>basicauth</code>,改用 <code>forward_auth</code> 对接右侧的{' '}
|
|
||||||
<strong>HomeLab SSO 2.0 统一认证卡片</strong>,一次登录全域名 30 天免密通行。
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Right: Live AuthGateCard Preview */}
|
{/* Right: Live AuthGateCard Preview */}
|
||||||
@@ -405,12 +423,12 @@ export const StorybookWorkbench: React.FC = () => {
|
|||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</Card>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{/* STORY 4: ServiceCard Interactive Playground */}
|
{/* STORY 4: ServiceCard Interactive Playground */}
|
||||||
{activeStory === 'service-card' && (
|
{activeStory === 'service-card' && (
|
||||||
<div className="hl-card p-6 space-y-6">
|
<Card className="p-6 space-y-6">
|
||||||
<div className="border-b hl-border pb-4 flex items-center justify-between">
|
<div className="border-b hl-border pb-4 flex items-center justify-between">
|
||||||
<div>
|
<div>
|
||||||
<span
|
<span
|
||||||
@@ -428,27 +446,24 @@ export const StorybookWorkbench: React.FC = () => {
|
|||||||
|
|
||||||
{/* Interactive Controls */}
|
{/* Interactive Controls */}
|
||||||
<div className="p-4 rounded-xl hl-muted border hl-border flex flex-wrap items-center gap-6 text-xs">
|
<div className="p-4 rounded-xl hl-muted border hl-border flex flex-wrap items-center gap-6 text-xs">
|
||||||
<div className="flex items-center gap-2">
|
<div className="flex items-center gap-2.5">
|
||||||
<span className="font-semibold">在线状态 (online):</span>
|
<span className="font-semibold">在线状态 (online):</span>
|
||||||
<button
|
<Switch checked={demoOnline} onCheckedChange={setDemoOnline} />
|
||||||
onClick={() => setDemoOnline(!demoOnline)}
|
<span className="font-mono-num">{demoOnline ? 'true' : 'false'}</span>
|
||||||
className="px-2.5 py-1 rounded hl-card font-mono-num cursor-pointer"
|
|
||||||
>
|
|
||||||
{demoOnline ? 'true (绿灯)' : 'false (离线红灯)'}
|
|
||||||
</button>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="flex items-center gap-2">
|
<div className="flex items-center gap-2">
|
||||||
<span className="font-semibold">右上角动作按钮 (actionType):</span>
|
<span className="font-semibold">右上角动作按钮 (actionType):</span>
|
||||||
{(['drawer', 'backup', undefined] as const).map((act, i) => (
|
{(['drawer', 'backup', undefined] as const).map((act, i) => (
|
||||||
<button
|
<Button
|
||||||
key={i}
|
key={i}
|
||||||
|
variant={demoAction === act ? 'accent' : 'outline'}
|
||||||
|
size="sm"
|
||||||
onClick={() => setDemoAction(act)}
|
onClick={() => setDemoAction(act)}
|
||||||
className="px-2.5 py-1 rounded hl-card font-mono-num cursor-pointer"
|
className="font-mono-num"
|
||||||
style={demoAction === act ? { borderColor: 'var(--accent)' } : {}}
|
|
||||||
>
|
>
|
||||||
{act || 'route-badge'}
|
{act || 'route-badge'}
|
||||||
</button>
|
</Button>
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -476,12 +491,12 @@ export const StorybookWorkbench: React.FC = () => {
|
|||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</Card>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{/* STORY 5: SshShelf */}
|
{/* STORY 5: SshShelf */}
|
||||||
{activeStory === 'ssh-shelf' && (
|
{activeStory === 'ssh-shelf' && (
|
||||||
<div className="hl-card p-6 space-y-6">
|
<Card className="p-6 space-y-6">
|
||||||
<div className="border-b hl-border pb-4">
|
<div className="border-b hl-border pb-4">
|
||||||
<span
|
<span
|
||||||
className="text-[11px] font-mono-num uppercase tracking-wider"
|
className="text-[11px] font-mono-num uppercase tracking-wider"
|
||||||
@@ -492,7 +507,7 @@ export const StorybookWorkbench: React.FC = () => {
|
|||||||
<h2 className="text-lg font-bold mt-0.5">底部 SSH 快捷复制与运维操作栏</h2>
|
<h2 className="text-lg font-bold mt-0.5">底部 SSH 快捷复制与运维操作栏</h2>
|
||||||
</div>
|
</div>
|
||||||
<SshShelf />
|
<SshShelf />
|
||||||
</div>
|
</Card>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -2,6 +2,8 @@ import React from 'react';
|
|||||||
import { BookOpen, LayoutGrid, Moon, Search, Sun, Terminal } from 'lucide-react';
|
import { BookOpen, LayoutGrid, Moon, Search, Sun, Terminal } from 'lucide-react';
|
||||||
import { usePortalStore } from '../store/usePortalStore';
|
import { usePortalStore } from '../store/usePortalStore';
|
||||||
import type { RouteModeId } from '../types/telemetry';
|
import type { RouteModeId } from '../types/telemetry';
|
||||||
|
import { Button } from './ui/button';
|
||||||
|
import { Tabs, TabsList, TabsTrigger } from './ui/tabs';
|
||||||
|
|
||||||
const MODES: Array<{ id: RouteModeId; label: string }> = [
|
const MODES: Array<{ id: RouteModeId; label: string }> = [
|
||||||
{ id: 'auto', label: '智能路由' },
|
{ id: 'auto', label: '智能路由' },
|
||||||
@@ -29,15 +31,15 @@ export const TopRibbon: React.FC = () => {
|
|||||||
<>
|
<>
|
||||||
{/* =====================================================================
|
{/* =====================================================================
|
||||||
Tier 1: Standard AppShell Global Top Navbar (h-14, NEVER wraps!)
|
Tier 1: Standard AppShell Global Top Navbar (h-14, NEVER wraps!)
|
||||||
Left: Brand + View Tabs (中控台 / Storybook)
|
Left: Brand + Radix Tabs (中控台 / Storybook)
|
||||||
Far Right (Red Box): Search (Cmd+K) + AGY Terminal + Theme Switcher
|
Far Right: Search (Cmd+K) + AGY Terminal + Theme Switcher
|
||||||
===================================================================== */}
|
===================================================================== */}
|
||||||
<header
|
<header
|
||||||
className="border-b hl-border sticky top-0 z-30 backdrop-blur-md"
|
className="border-b hl-border sticky top-0 z-30 backdrop-blur-md"
|
||||||
style={{ backgroundColor: 'color-mix(in srgb, var(--bg-card) 88%, transparent)' }}
|
style={{ backgroundColor: 'color-mix(in srgb, var(--bg-card) 88%, transparent)' }}
|
||||||
>
|
>
|
||||||
<div className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 h-14 flex items-center justify-between gap-4">
|
<div className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 h-14 flex items-center justify-between gap-4">
|
||||||
{/* Left Group: Brand Logo + Navigation Tabs */}
|
{/* Left Group: Brand Logo + Official Radix Navigation Tabs */}
|
||||||
<div className="flex items-center gap-4 shrink-0">
|
<div className="flex items-center gap-4 shrink-0">
|
||||||
<div
|
<div
|
||||||
onClick={() => setViewMode('dashboard')}
|
onClick={() => setViewMode('dashboard')}
|
||||||
@@ -58,47 +60,30 @@ export const TopRibbon: React.FC = () => {
|
|||||||
|
|
||||||
<div className="h-4 w-[1px] hl-muted" />
|
<div className="h-4 w-[1px] hl-muted" />
|
||||||
|
|
||||||
{/* View Switcher Tabs: Dashboard vs Storybook */}
|
{/* View Switcher Tabs via @radix-ui/react-tabs */}
|
||||||
<nav className="flex items-center gap-1 text-xs">
|
<Tabs
|
||||||
<button
|
value={viewMode}
|
||||||
onClick={() => setViewMode('dashboard')}
|
onValueChange={(v) => setViewMode(v as 'dashboard' | 'storybook')}
|
||||||
className="px-2.5 py-1.5 rounded-md font-medium flex items-center gap-1.5 transition-all cursor-pointer"
|
>
|
||||||
style={
|
<TabsList>
|
||||||
viewMode === 'dashboard'
|
<TabsTrigger value="dashboard">
|
||||||
? {
|
<LayoutGrid className="w-3.5 h-3.5" />
|
||||||
backgroundColor: 'var(--bg-muted)',
|
<span>中控台</span>
|
||||||
color: 'var(--text-main)',
|
</TabsTrigger>
|
||||||
}
|
<TabsTrigger value="storybook">
|
||||||
: { color: 'var(--text-muted)' }
|
<BookOpen className="w-3.5 h-3.5" />
|
||||||
}
|
<span>Storybook 设计规范</span>
|
||||||
>
|
</TabsTrigger>
|
||||||
<LayoutGrid className="w-3.5 h-3.5" />
|
</TabsList>
|
||||||
<span>中控台</span>
|
</Tabs>
|
||||||
</button>
|
|
||||||
|
|
||||||
<button
|
|
||||||
onClick={() => setViewMode('storybook')}
|
|
||||||
className="px-2.5 py-1.5 rounded-md font-medium flex items-center gap-1.5 transition-all cursor-pointer"
|
|
||||||
style={
|
|
||||||
viewMode === 'storybook'
|
|
||||||
? {
|
|
||||||
backgroundColor: 'var(--bg-muted)',
|
|
||||||
color: 'var(--text-main)',
|
|
||||||
}
|
|
||||||
: { color: 'var(--text-muted)' }
|
|
||||||
}
|
|
||||||
>
|
|
||||||
<BookOpen className="w-3.5 h-3.5" />
|
|
||||||
<span>Storybook 设计规范</span>
|
|
||||||
</button>
|
|
||||||
</nav>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Far Right Group: Pinned to Right Edge (Search + AGY + Theme Switcher) */}
|
{/* Far Right Group: Pinned to Right Edge (Search + AGY + Theme Switcher) */}
|
||||||
<div className="flex items-center gap-2.5 shrink-0 ml-auto">
|
<div className="flex items-center gap-2.5 shrink-0 ml-auto">
|
||||||
<button
|
<Button
|
||||||
|
variant="outline"
|
||||||
onClick={() => setShowCmdPalette(true)}
|
onClick={() => setShowCmdPalette(true)}
|
||||||
className="hl-btn-outline px-3 py-1.5 text-xs flex items-center gap-3 cursor-pointer"
|
className="gap-3"
|
||||||
title="按 Cmd+K 唤起全局命令面板"
|
title="按 Cmd+K 唤起全局命令面板"
|
||||||
>
|
>
|
||||||
<span className="flex items-center gap-1.5" style={{ color: 'var(--text-muted)' }}>
|
<span className="flex items-center gap-1.5" style={{ color: 'var(--text-muted)' }}>
|
||||||
@@ -111,15 +96,12 @@ export const TopRibbon: React.FC = () => {
|
|||||||
>
|
>
|
||||||
⌘K
|
⌘K
|
||||||
</kbd>
|
</kbd>
|
||||||
</button>
|
</Button>
|
||||||
|
|
||||||
<button
|
<Button onClick={() => setShowAgyModal(true)}>
|
||||||
onClick={() => setShowAgyModal(true)}
|
|
||||||
className="hl-btn-primary px-3 py-1.5 text-xs flex items-center gap-1.5 cursor-pointer"
|
|
||||||
>
|
|
||||||
<Terminal className="w-3.5 h-3.5" />
|
<Terminal className="w-3.5 h-3.5" />
|
||||||
<span>AGY 终端</span>
|
<span>AGY 终端</span>
|
||||||
</button>
|
</Button>
|
||||||
|
|
||||||
{/* Top-Right Theme Toggle (Pinned at Far Right Corner!) */}
|
{/* Top-Right Theme Toggle (Pinned at Far Right Corner!) */}
|
||||||
<div className="flex items-center p-0.5 rounded-lg hl-muted border hl-border text-xs">
|
<div className="flex items-center p-0.5 rounded-lg hl-muted border hl-border text-xs">
|
||||||
@@ -163,7 +145,7 @@ export const TopRibbon: React.FC = () => {
|
|||||||
{/* =====================================================================
|
{/* =====================================================================
|
||||||
Tier 2: Subheader Telemetry & Route Mode Bar (Aligned with Grid)
|
Tier 2: Subheader Telemetry & Route Mode Bar (Aligned with Grid)
|
||||||
Left: 4 Hardware & Network Gauges
|
Left: 4 Hardware & Network Gauges
|
||||||
Right: Route Mode Segmented Switcher (智能路由 / 443 / 10443 / LAN)
|
Right: Route Mode Segmented Switcher via @radix-ui/react-tabs
|
||||||
===================================================================== */}
|
===================================================================== */}
|
||||||
{viewMode === 'dashboard' && (
|
{viewMode === 'dashboard' && (
|
||||||
<div className="border-b hl-border" style={{ backgroundColor: 'var(--bg-card)' }}>
|
<div className="border-b hl-border" style={{ backgroundColor: 'var(--bg-card)' }}>
|
||||||
@@ -217,27 +199,20 @@ export const TopRibbon: React.FC = () => {
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Right: Route Mode Switcher (Pinned to Right Edge of Subheader) */}
|
{/* Right: Route Mode Switcher via @radix-ui/react-tabs */}
|
||||||
<div className="flex items-center p-0.5 rounded-lg hl-muted border hl-border text-xs ml-auto">
|
<Tabs
|
||||||
{MODES.map((m) => (
|
value={routeMode}
|
||||||
<button
|
onValueChange={(v) => setRouteMode(v as RouteModeId)}
|
||||||
key={m.id}
|
className="ml-auto"
|
||||||
onClick={() => setRouteMode(m.id)}
|
>
|
||||||
className="px-2.5 py-1 rounded-md font-medium transition-all cursor-pointer"
|
<TabsList>
|
||||||
style={
|
{MODES.map((m) => (
|
||||||
routeMode === m.id
|
<TabsTrigger key={m.id} value={m.id}>
|
||||||
? {
|
{m.label}
|
||||||
backgroundColor: 'var(--bg-card)',
|
</TabsTrigger>
|
||||||
color: 'var(--text-main)',
|
))}
|
||||||
boxShadow: '0 1px 2px rgba(0,0,0,0.08)',
|
</TabsList>
|
||||||
}
|
</Tabs>
|
||||||
: { color: 'var(--text-muted)' }
|
|
||||||
}
|
|
||||||
>
|
|
||||||
{m.label}
|
|
||||||
</button>
|
|
||||||
))}
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|||||||
@@ -0,0 +1,31 @@
|
|||||||
|
import * as React from 'react';
|
||||||
|
import { cva, type VariantProps } from 'class-variance-authority';
|
||||||
|
import { cn } from '../../lib/utils';
|
||||||
|
|
||||||
|
const badgeVariants = cva(
|
||||||
|
'inline-flex items-center gap-1 rounded-md px-2 py-0.5 text-[11px] font-mono-num font-medium transition-colors shrink-0',
|
||||||
|
{
|
||||||
|
variants: {
|
||||||
|
variant: {
|
||||||
|
default: 'bg-[var(--primary)] text-[var(--primary-fg)]',
|
||||||
|
secondary: 'hl-muted text-[var(--text-muted)] border hl-border',
|
||||||
|
outline: 'border hl-border text-[var(--text-muted)] bg-[var(--bg-card)]',
|
||||||
|
accent: 'bg-[var(--accent-soft)] text-[var(--accent)] font-semibold',
|
||||||
|
success: 'bg-[var(--success-soft)] text-[var(--success)] font-semibold',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
defaultVariants: {
|
||||||
|
variant: 'secondary',
|
||||||
|
},
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
export interface BadgeProps
|
||||||
|
extends React.HTMLAttributes<HTMLDivElement>,
|
||||||
|
VariantProps<typeof badgeVariants> {}
|
||||||
|
|
||||||
|
function Badge({ className, variant, ...props }: BadgeProps) {
|
||||||
|
return <div className={cn(badgeVariants({ variant }), className)} {...props} />;
|
||||||
|
}
|
||||||
|
|
||||||
|
export { Badge, badgeVariants };
|
||||||
@@ -0,0 +1,52 @@
|
|||||||
|
import * as React from 'react';
|
||||||
|
import { Slot } from '@radix-ui/react-slot';
|
||||||
|
import { cva, type VariantProps } from 'class-variance-authority';
|
||||||
|
import { cn } from '../../lib/utils';
|
||||||
|
|
||||||
|
const buttonVariants = cva(
|
||||||
|
'inline-flex items-center justify-center gap-1.5 whitespace-nowrap rounded-lg text-xs font-medium transition-all cursor-pointer select-none disabled:pointer-events-none disabled:opacity-50 shrink-0',
|
||||||
|
{
|
||||||
|
variants: {
|
||||||
|
variant: {
|
||||||
|
default: 'hl-btn-primary shadow-xs',
|
||||||
|
outline: 'hl-btn-outline',
|
||||||
|
secondary: 'hl-muted border hl-border hover:opacity-90',
|
||||||
|
ghost: 'hover:bg-[var(--bg-muted)]',
|
||||||
|
accent:
|
||||||
|
'border border-[var(--accent)] bg-[var(--accent-soft)] text-[var(--accent)] font-semibold hover:opacity-90',
|
||||||
|
},
|
||||||
|
size: {
|
||||||
|
default: 'h-8 px-3 py-1.5',
|
||||||
|
sm: 'h-7 px-2.5 text-[11px] rounded-md',
|
||||||
|
lg: 'h-9 px-4 text-xs',
|
||||||
|
icon: 'h-8 w-8 p-0',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
defaultVariants: {
|
||||||
|
variant: 'default',
|
||||||
|
size: 'default',
|
||||||
|
},
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
export interface ButtonProps
|
||||||
|
extends React.ButtonHTMLAttributes<HTMLButtonElement>,
|
||||||
|
VariantProps<typeof buttonVariants> {
|
||||||
|
asChild?: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
const Button = React.forwardRef<HTMLButtonElement, ButtonProps>(
|
||||||
|
({ className, variant, size, asChild = false, ...props }, ref) => {
|
||||||
|
const Comp = asChild ? Slot : 'button';
|
||||||
|
return (
|
||||||
|
<Comp
|
||||||
|
className={cn(buttonVariants({ variant, size, className }))}
|
||||||
|
ref={ref}
|
||||||
|
{...props}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
);
|
||||||
|
Button.displayName = 'Button';
|
||||||
|
|
||||||
|
export { Button, buttonVariants };
|
||||||
@@ -0,0 +1,55 @@
|
|||||||
|
import * as React from 'react';
|
||||||
|
import { cn } from '../../lib/utils';
|
||||||
|
|
||||||
|
const Card = React.forwardRef<HTMLDivElement, React.HTMLAttributes<HTMLDivElement>>(
|
||||||
|
({ className, ...props }, ref) => (
|
||||||
|
<div ref={ref} className={cn('hl-card', className)} {...props} />
|
||||||
|
)
|
||||||
|
);
|
||||||
|
Card.displayName = 'Card';
|
||||||
|
|
||||||
|
const CardHeader = React.forwardRef<HTMLDivElement, React.HTMLAttributes<HTMLDivElement>>(
|
||||||
|
({ className, ...props }, ref) => (
|
||||||
|
<div ref={ref} className={cn('flex flex-col space-y-1.5 p-5', className)} {...props} />
|
||||||
|
)
|
||||||
|
);
|
||||||
|
CardHeader.displayName = 'CardHeader';
|
||||||
|
|
||||||
|
const CardTitle = React.forwardRef<HTMLHeadingElement, React.HTMLAttributes<HTMLHeadingElement>>(
|
||||||
|
({ className, ...props }, ref) => (
|
||||||
|
<h3
|
||||||
|
ref={ref}
|
||||||
|
className={cn('text-sm font-bold leading-none tracking-tight', className)}
|
||||||
|
{...props}
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
);
|
||||||
|
CardTitle.displayName = 'CardTitle';
|
||||||
|
|
||||||
|
const CardDescription = React.forwardRef<
|
||||||
|
HTMLParagraphElement,
|
||||||
|
React.HTMLAttributes<HTMLParagraphElement>
|
||||||
|
>(({ className, ...props }, ref) => (
|
||||||
|
<p
|
||||||
|
ref={ref}
|
||||||
|
className={cn('text-xs text-[var(--text-muted)]', className)}
|
||||||
|
{...props}
|
||||||
|
/>
|
||||||
|
));
|
||||||
|
CardDescription.displayName = 'CardDescription';
|
||||||
|
|
||||||
|
const CardContent = React.forwardRef<HTMLDivElement, React.HTMLAttributes<HTMLDivElement>>(
|
||||||
|
({ className, ...props }, ref) => (
|
||||||
|
<div ref={ref} className={cn('p-5 pt-0', className)} {...props} />
|
||||||
|
)
|
||||||
|
);
|
||||||
|
CardContent.displayName = 'CardContent';
|
||||||
|
|
||||||
|
const CardFooter = React.forwardRef<HTMLDivElement, React.HTMLAttributes<HTMLDivElement>>(
|
||||||
|
({ className, ...props }, ref) => (
|
||||||
|
<div ref={ref} className={cn('flex items-center p-5 pt-0', className)} {...props} />
|
||||||
|
)
|
||||||
|
);
|
||||||
|
CardFooter.displayName = 'CardFooter';
|
||||||
|
|
||||||
|
export { Card, CardHeader, CardFooter, CardTitle, CardDescription, CardContent };
|
||||||
@@ -0,0 +1,121 @@
|
|||||||
|
import * as React from 'react';
|
||||||
|
import { Command as CommandPrimitive } from 'cmdk';
|
||||||
|
import { Search } from 'lucide-react';
|
||||||
|
import { cn } from '../../lib/utils';
|
||||||
|
import { Dialog, DialogContent, DialogDescription, DialogTitle } from './dialog';
|
||||||
|
|
||||||
|
const Command = React.forwardRef<
|
||||||
|
React.ComponentRef<typeof CommandPrimitive>,
|
||||||
|
React.ComponentPropsWithoutRef<typeof CommandPrimitive>
|
||||||
|
>(({ className, ...props }, ref) => (
|
||||||
|
<CommandPrimitive
|
||||||
|
ref={ref}
|
||||||
|
className={cn(
|
||||||
|
'flex h-full w-full flex-col overflow-hidden rounded-xl bg-[var(--bg-card)] text-[var(--text-main)]',
|
||||||
|
className
|
||||||
|
)}
|
||||||
|
{...props}
|
||||||
|
/>
|
||||||
|
));
|
||||||
|
Command.displayName = CommandPrimitive.displayName;
|
||||||
|
|
||||||
|
interface CommandDialogProps extends React.ComponentPropsWithoutRef<typeof Dialog> {}
|
||||||
|
|
||||||
|
const CommandDialog: React.FC<CommandDialogProps> = ({ children, ...props }) => {
|
||||||
|
return (
|
||||||
|
<Dialog {...props}>
|
||||||
|
<DialogContent className="max-w-xl overflow-hidden p-0 shadow-2xl top-[22%] translate-y-0">
|
||||||
|
<DialogTitle className="sr-only">全局命令与服务搜索面板</DialogTitle>
|
||||||
|
<DialogDescription className="sr-only">
|
||||||
|
使用键盘上下方向键选择服务或操作,回车立即执行
|
||||||
|
</DialogDescription>
|
||||||
|
<Command>{children}</Command>
|
||||||
|
</DialogContent>
|
||||||
|
</Dialog>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
const CommandInput = React.forwardRef<
|
||||||
|
React.ComponentRef<typeof CommandPrimitive.Input>,
|
||||||
|
React.ComponentPropsWithoutRef<typeof CommandPrimitive.Input>
|
||||||
|
>(({ className, ...props }, ref) => (
|
||||||
|
<div className="flex items-center border-b hl-border px-4 py-3 gap-3" cmdk-input-wrapper="">
|
||||||
|
<Search className="h-4 w-4 shrink-0 text-[var(--text-muted)]" />
|
||||||
|
<CommandPrimitive.Input
|
||||||
|
ref={ref}
|
||||||
|
className={cn(
|
||||||
|
'flex w-full bg-transparent text-sm outline-none placeholder:text-[var(--text-subtle)] disabled:cursor-not-allowed disabled:opacity-50',
|
||||||
|
className
|
||||||
|
)}
|
||||||
|
{...props}
|
||||||
|
/>
|
||||||
|
<kbd className="px-1.5 py-0.5 text-[10px] font-mono-num rounded hl-muted text-[var(--text-muted)] shrink-0">
|
||||||
|
ESC
|
||||||
|
</kbd>
|
||||||
|
</div>
|
||||||
|
));
|
||||||
|
CommandInput.displayName = CommandPrimitive.Input.displayName;
|
||||||
|
|
||||||
|
const CommandList = React.forwardRef<
|
||||||
|
React.ComponentRef<typeof CommandPrimitive.List>,
|
||||||
|
React.ComponentPropsWithoutRef<typeof CommandPrimitive.List>
|
||||||
|
>(({ className, ...props }, ref) => (
|
||||||
|
<CommandPrimitive.List
|
||||||
|
ref={ref}
|
||||||
|
className={cn('max-h-80 overflow-y-auto overflow-x-hidden p-2', className)}
|
||||||
|
{...props}
|
||||||
|
/>
|
||||||
|
));
|
||||||
|
CommandList.displayName = CommandPrimitive.List.displayName;
|
||||||
|
|
||||||
|
const CommandEmpty = React.forwardRef<
|
||||||
|
React.ComponentRef<typeof CommandPrimitive.Empty>,
|
||||||
|
React.ComponentPropsWithoutRef<typeof CommandPrimitive.Empty>
|
||||||
|
>((props, ref) => (
|
||||||
|
<CommandPrimitive.Empty
|
||||||
|
ref={ref}
|
||||||
|
className="py-6 text-center text-xs text-[var(--text-muted)]"
|
||||||
|
{...props}
|
||||||
|
/>
|
||||||
|
));
|
||||||
|
CommandEmpty.displayName = CommandPrimitive.Empty.displayName;
|
||||||
|
|
||||||
|
const CommandGroup = React.forwardRef<
|
||||||
|
React.ComponentRef<typeof CommandPrimitive.Group>,
|
||||||
|
React.ComponentPropsWithoutRef<typeof CommandPrimitive.Group>
|
||||||
|
>(({ className, ...props }, ref) => (
|
||||||
|
<CommandPrimitive.Group
|
||||||
|
ref={ref}
|
||||||
|
className={cn(
|
||||||
|
'overflow-hidden p-1 text-[var(--text-main)] [&_[cmdk-group-heading]]:px-2.5 [&_[cmdk-group-heading]]:py-1.5 [&_[cmdk-group-heading]]:text-[10px] [&_[cmdk-group-heading]]:font-semibold [&_[cmdk-group-heading]]:uppercase [&_[cmdk-group-heading]]:tracking-wider [&_[cmdk-group-heading]]:text-[var(--text-subtle)]',
|
||||||
|
className
|
||||||
|
)}
|
||||||
|
{...props}
|
||||||
|
/>
|
||||||
|
));
|
||||||
|
CommandGroup.displayName = CommandPrimitive.Group.displayName;
|
||||||
|
|
||||||
|
const CommandItem = React.forwardRef<
|
||||||
|
React.ComponentRef<typeof CommandPrimitive.Item>,
|
||||||
|
React.ComponentPropsWithoutRef<typeof CommandPrimitive.Item>
|
||||||
|
>(({ className, ...props }, ref) => (
|
||||||
|
<CommandPrimitive.Item
|
||||||
|
ref={ref}
|
||||||
|
className={cn(
|
||||||
|
'relative flex cursor-pointer select-none items-center justify-between rounded-lg px-3 py-2 text-xs outline-none transition-colors data-[selected=true]:bg-[var(--bg-muted)] data-[selected=true]:text-[var(--text-main)] data-[disabled=true]:pointer-events-none data-[disabled=true]:opacity-50',
|
||||||
|
className
|
||||||
|
)}
|
||||||
|
{...props}
|
||||||
|
/>
|
||||||
|
));
|
||||||
|
CommandItem.displayName = CommandPrimitive.Item.displayName;
|
||||||
|
|
||||||
|
export {
|
||||||
|
Command,
|
||||||
|
CommandDialog,
|
||||||
|
CommandInput,
|
||||||
|
CommandList,
|
||||||
|
CommandEmpty,
|
||||||
|
CommandGroup,
|
||||||
|
CommandItem,
|
||||||
|
};
|
||||||
@@ -0,0 +1,90 @@
|
|||||||
|
import * as React from 'react';
|
||||||
|
import * as DialogPrimitive from '@radix-ui/react-dialog';
|
||||||
|
import { cn } from '../../lib/utils';
|
||||||
|
|
||||||
|
const Dialog = DialogPrimitive.Root;
|
||||||
|
const DialogTrigger = DialogPrimitive.Trigger;
|
||||||
|
const DialogPortal = DialogPrimitive.Portal;
|
||||||
|
const DialogClose = DialogPrimitive.Close;
|
||||||
|
|
||||||
|
const DialogOverlay = React.forwardRef<
|
||||||
|
React.ComponentRef<typeof DialogPrimitive.Overlay>,
|
||||||
|
React.ComponentPropsWithoutRef<typeof DialogPrimitive.Overlay>
|
||||||
|
>(({ className, ...props }, ref) => (
|
||||||
|
<DialogPrimitive.Overlay
|
||||||
|
ref={ref}
|
||||||
|
className={cn(
|
||||||
|
'fixed inset-0 z-50 bg-black/70 backdrop-blur-sm transition-opacity',
|
||||||
|
className
|
||||||
|
)}
|
||||||
|
{...props}
|
||||||
|
/>
|
||||||
|
));
|
||||||
|
DialogOverlay.displayName = DialogPrimitive.Overlay.displayName;
|
||||||
|
|
||||||
|
const DialogContent = React.forwardRef<
|
||||||
|
React.ComponentRef<typeof DialogPrimitive.Content>,
|
||||||
|
React.ComponentPropsWithoutRef<typeof DialogPrimitive.Content>
|
||||||
|
>(({ className, children, ...props }, ref) => (
|
||||||
|
<DialogPortal>
|
||||||
|
<DialogOverlay />
|
||||||
|
<DialogPrimitive.Content
|
||||||
|
ref={ref}
|
||||||
|
className={cn(
|
||||||
|
'fixed left-[50%] top-[50%] z-50 w-full max-w-lg translate-x-[-50%] translate-y-[-50%] hl-card shadow-2xl duration-200 outline-none',
|
||||||
|
className
|
||||||
|
)}
|
||||||
|
{...props}
|
||||||
|
>
|
||||||
|
{children}
|
||||||
|
</DialogPrimitive.Content>
|
||||||
|
</DialogPortal>
|
||||||
|
));
|
||||||
|
DialogContent.displayName = DialogPrimitive.Content.displayName;
|
||||||
|
|
||||||
|
const DialogHeader = ({
|
||||||
|
className,
|
||||||
|
...props
|
||||||
|
}: React.HTMLAttributes<HTMLDivElement>) => (
|
||||||
|
<div
|
||||||
|
className={cn('flex flex-col space-y-1.5 text-left', className)}
|
||||||
|
{...props}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
DialogHeader.displayName = 'DialogHeader';
|
||||||
|
|
||||||
|
const DialogTitle = React.forwardRef<
|
||||||
|
React.ComponentRef<typeof DialogPrimitive.Title>,
|
||||||
|
React.ComponentPropsWithoutRef<typeof DialogPrimitive.Title>
|
||||||
|
>(({ className, ...props }, ref) => (
|
||||||
|
<DialogPrimitive.Title
|
||||||
|
ref={ref}
|
||||||
|
className={cn('text-sm font-bold leading-none tracking-tight', className)}
|
||||||
|
{...props}
|
||||||
|
/>
|
||||||
|
));
|
||||||
|
DialogTitle.displayName = DialogPrimitive.Title.displayName;
|
||||||
|
|
||||||
|
const DialogDescription = React.forwardRef<
|
||||||
|
React.ComponentRef<typeof DialogPrimitive.Description>,
|
||||||
|
React.ComponentPropsWithoutRef<typeof DialogPrimitive.Description>
|
||||||
|
>(({ className, ...props }, ref) => (
|
||||||
|
<DialogPrimitive.Description
|
||||||
|
ref={ref}
|
||||||
|
className={cn('text-xs text-[var(--text-muted)]', className)}
|
||||||
|
{...props}
|
||||||
|
/>
|
||||||
|
));
|
||||||
|
DialogDescription.displayName = DialogPrimitive.Description.displayName;
|
||||||
|
|
||||||
|
export {
|
||||||
|
Dialog,
|
||||||
|
DialogPortal,
|
||||||
|
DialogOverlay,
|
||||||
|
DialogClose,
|
||||||
|
DialogTrigger,
|
||||||
|
DialogContent,
|
||||||
|
DialogHeader,
|
||||||
|
DialogTitle,
|
||||||
|
DialogDescription,
|
||||||
|
};
|
||||||
@@ -0,0 +1,29 @@
|
|||||||
|
import * as React from 'react';
|
||||||
|
import { cn } from '../../lib/utils';
|
||||||
|
|
||||||
|
export interface InputProps extends React.InputHTMLAttributes<HTMLInputElement> {}
|
||||||
|
|
||||||
|
const Input = React.forwardRef<HTMLInputElement, InputProps>(
|
||||||
|
({ className, type, ...props }, ref) => {
|
||||||
|
return (
|
||||||
|
<input
|
||||||
|
type={type}
|
||||||
|
className={cn('hl-input', className)}
|
||||||
|
ref={ref}
|
||||||
|
{...props}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
);
|
||||||
|
Input.displayName = 'Input';
|
||||||
|
|
||||||
|
export interface InputGroupProps extends React.HTMLAttributes<HTMLDivElement> {}
|
||||||
|
|
||||||
|
const InputGroup = React.forwardRef<HTMLDivElement, InputGroupProps>(
|
||||||
|
({ className, ...props }, ref) => {
|
||||||
|
return <div ref={ref} className={cn('hl-input-group', className)} {...props} />;
|
||||||
|
}
|
||||||
|
);
|
||||||
|
InputGroup.displayName = 'InputGroup';
|
||||||
|
|
||||||
|
export { Input, InputGroup };
|
||||||
@@ -0,0 +1,26 @@
|
|||||||
|
import * as React from 'react';
|
||||||
|
import * as SwitchPrimitives from '@radix-ui/react-switch';
|
||||||
|
import { cn } from '../../lib/utils';
|
||||||
|
|
||||||
|
const Switch = React.forwardRef<
|
||||||
|
React.ComponentRef<typeof SwitchPrimitives.Root>,
|
||||||
|
React.ComponentPropsWithoutRef<typeof SwitchPrimitives.Root>
|
||||||
|
>(({ className, ...props }, ref) => (
|
||||||
|
<SwitchPrimitives.Root
|
||||||
|
className={cn(
|
||||||
|
'peer inline-flex h-5 w-9 shrink-0 cursor-pointer items-center rounded-full border transition-colors disabled:cursor-not-allowed disabled:opacity-50 data-[state=checked]:bg-[var(--accent)] data-[state=checked]:border-[var(--accent)] data-[state=unchecked]:bg-[var(--bg-muted)] data-[state=unchecked]:border-[var(--border-color)]',
|
||||||
|
className
|
||||||
|
)}
|
||||||
|
{...props}
|
||||||
|
ref={ref}
|
||||||
|
>
|
||||||
|
<SwitchPrimitives.Thumb
|
||||||
|
className={cn(
|
||||||
|
'pointer-events-none block h-3.5 w-3.5 rounded-full bg-white shadow-sm transition-transform data-[state=checked]:translate-x-4 data-[state=unchecked]:translate-x-0.5'
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
</SwitchPrimitives.Root>
|
||||||
|
));
|
||||||
|
Switch.displayName = SwitchPrimitives.Root.displayName;
|
||||||
|
|
||||||
|
export { Switch };
|
||||||
@@ -0,0 +1,49 @@
|
|||||||
|
import * as React from 'react';
|
||||||
|
import * as TabsPrimitive from '@radix-ui/react-tabs';
|
||||||
|
import { cn } from '../../lib/utils';
|
||||||
|
|
||||||
|
const Tabs = TabsPrimitive.Root;
|
||||||
|
|
||||||
|
const TabsList = React.forwardRef<
|
||||||
|
React.ComponentRef<typeof TabsPrimitive.List>,
|
||||||
|
React.ComponentPropsWithoutRef<typeof TabsPrimitive.List>
|
||||||
|
>(({ className, ...props }, ref) => (
|
||||||
|
<TabsPrimitive.List
|
||||||
|
ref={ref}
|
||||||
|
className={cn(
|
||||||
|
'inline-flex items-center justify-center rounded-lg hl-muted p-1 border hl-border text-[var(--text-muted)]',
|
||||||
|
className
|
||||||
|
)}
|
||||||
|
{...props}
|
||||||
|
/>
|
||||||
|
));
|
||||||
|
TabsList.displayName = TabsPrimitive.List.displayName;
|
||||||
|
|
||||||
|
const TabsTrigger = React.forwardRef<
|
||||||
|
React.ComponentRef<typeof TabsPrimitive.Trigger>,
|
||||||
|
React.ComponentPropsWithoutRef<typeof TabsPrimitive.Trigger>
|
||||||
|
>(({ className, ...props }, ref) => (
|
||||||
|
<TabsPrimitive.Trigger
|
||||||
|
ref={ref}
|
||||||
|
className={cn(
|
||||||
|
'inline-flex items-center justify-center gap-1.5 whitespace-nowrap rounded-md px-2.5 py-1 text-xs font-medium transition-all cursor-pointer select-none disabled:pointer-events-none disabled:opacity-50 data-[state=active]:bg-[var(--bg-card)] data-[state=active]:text-[var(--text-main)] data-[state=active]:shadow-xs',
|
||||||
|
className
|
||||||
|
)}
|
||||||
|
{...props}
|
||||||
|
/>
|
||||||
|
));
|
||||||
|
TabsTrigger.displayName = TabsPrimitive.Trigger.displayName;
|
||||||
|
|
||||||
|
const TabsContent = React.forwardRef<
|
||||||
|
React.ComponentRef<typeof TabsPrimitive.Content>,
|
||||||
|
React.ComponentPropsWithoutRef<typeof TabsPrimitive.Content>
|
||||||
|
>(({ className, ...props }, ref) => (
|
||||||
|
<TabsPrimitive.Content
|
||||||
|
ref={ref}
|
||||||
|
className={cn('mt-2 outline-none', className)}
|
||||||
|
{...props}
|
||||||
|
/>
|
||||||
|
));
|
||||||
|
TabsContent.displayName = TabsPrimitive.Content.displayName;
|
||||||
|
|
||||||
|
export { Tabs, TabsList, TabsTrigger, TabsContent };
|
||||||
@@ -0,0 +1,6 @@
|
|||||||
|
import { clsx, type ClassValue } from 'clsx';
|
||||||
|
import { twMerge } from 'tailwind-merge';
|
||||||
|
|
||||||
|
export function cn(...inputs: ClassValue[]) {
|
||||||
|
return twMerge(clsx(inputs));
|
||||||
|
}
|
||||||
Reference in new issue
Block a user