diff --git a/.storybook/babel-plugin-story-source.js b/.storybook/babel-plugin-story-source.js new file mode 100644 index 0000000..55fad25 --- /dev/null +++ b/.storybook/babel-plugin-story-source.js @@ -0,0 +1,132 @@ +/** + * Babel plugin that extracts wrapper/demo function source code and injects it + * as `parameters.docs.source.code` into Storybook story exports. + * + * Problem: Stories that use `render: () => ` cause Storybook's + * "Show code" panel to display `` instead of the actual + * component usage inside the wrapper. + * + * Solution: At compile time, this plugin: + * 1. Collects all top-level non-exported function declarations (the wrappers). + * 2. For each story export whose render body is ``, it copies + * the wrapper function's original source into + * `parameters.docs.source.code` so Storybook displays it verbatim. + */ +module.exports = function storySourcePlugin({ types: t }) { + return { + name: 'story-source-injector', + visitor: { + Program: { + enter(programPath, state) { + const fileSource = state.file.code; + if (!fileSource) return; + + // ── Step 1: collect wrapper functions ──────────────────────── + const wrapperFunctions = new Map(); + + for (const stmt of programPath.get('body')) { + // Skip any exported declaration + if ( + stmt.isExportNamedDeclaration() || + stmt.isExportDefaultDeclaration() + ) { + continue; + } + + if (stmt.isFunctionDeclaration()) { + const name = stmt.node.id?.name; + if (!name) continue; + + const { start, end } = stmt.node; + if (start == null || end == null) continue; + + wrapperFunctions.set(name, fileSource.slice(start, end)); + } + } + + if (wrapperFunctions.size === 0) return; + + // ── Step 2: inject source into matching story exports ──────── + for (const stmt of programPath.get('body')) { + if (!stmt.isExportNamedDeclaration()) continue; + + const decl = stmt.get('declaration'); + if (!decl.isVariableDeclaration()) continue; + + for (const declarator of decl.get('declarations')) { + let init = declarator.get('init'); + + // Unwrap `{ ... } satisfies Type` or `{ ... } as Type` + if ( + init.isTSSatisfiesExpression?.() || + init.isTSAsExpression?.() + ) { + init = init.get('expression'); + } + + if (!init.isObjectExpression()) continue; + + const properties = init.get('properties'); + + // Find `render` property + const renderProp = properties.find( + (p) => + p.isObjectProperty() && + p.get('key').isIdentifier({ name: 'render' }) + ); + if (!renderProp) continue; + + const renderVal = renderProp.get('value'); + if (!renderVal.isArrowFunctionExpression()) continue; + + const renderBody = renderVal.get('body'); + if (!renderBody.isJSXElement()) continue; + + // Must be a self-closing element: + const opening = renderBody.get('openingElement'); + const nameNode = opening.get('name'); + if (!nameNode.isJSXIdentifier()) continue; + + const wrapperName = nameNode.node.name; + if (!wrapperFunctions.has(wrapperName)) continue; + + // Skip stories that already define their own `parameters` + const hasParams = properties.some( + (p) => + p.isObjectProperty() && + p.get('key').isIdentifier({ name: 'parameters' }) + ); + if (hasParams) continue; + + // Build: parameters: { docs: { source: { code, language } } } + const sourceCode = wrapperFunctions.get(wrapperName); + + const sourceObj = t.objectExpression([ + t.objectProperty( + t.identifier('code'), + t.stringLiteral(sourceCode) + ), + t.objectProperty( + t.identifier('language'), + t.stringLiteral('tsx') + ), + ]); + + const docsObj = t.objectExpression([ + t.objectProperty(t.identifier('source'), sourceObj), + ]); + + const paramsObj = t.objectExpression([ + t.objectProperty(t.identifier('docs'), docsObj), + ]); + + init.node.properties.push( + t.objectProperty(t.identifier('parameters'), paramsObj) + ); + } + } + }, + }, + }, + }; +}; diff --git a/.storybook/main.ts b/.storybook/main.ts index 6728e7e..f3cd9b2 100644 --- a/.storybook/main.ts +++ b/.storybook/main.ts @@ -91,6 +91,9 @@ const config: StorybookConfig = { const babelLoaderForStories = { loader: require.resolve("babel-loader"), options: { + plugins: [ + require.resolve("./babel-plugin-story-source"), + ], presets: [ [require.resolve("@babel/preset-react"), { runtime: "automatic" }], [require.resolve("@babel/preset-typescript"), { allowDeclareFields: true }], diff --git a/.storybook/preview.js b/.storybook/preview.js index 2533481..7d7ffac 100644 --- a/.storybook/preview.js +++ b/.storybook/preview.js @@ -63,6 +63,17 @@ export const globalTypes = { { value: 'cyberpunk', title: 'Cyberpunk' }, { value: 'twitter', title: 'Twitter' }, { value: 'slate', title: 'Slate' }, + { value: 'claude', title: 'Claude' }, + { value: 'claymorphism', title: 'Claymorphism' }, + { value: 'clean-slate', title: 'Clean Slate' }, + { value: 'modern-minimal', title: 'Modern Minimal' }, + { value: 'nature', title: 'Nature' }, + { value: 'neo-brutalism', title: 'Neo Brutalism' }, + { value: 'notebook', title: 'Notebook' }, + { value: 'ocean-breeze', title: 'Ocean Breeze' }, + { value: 'supabase', title: 'Supabase' }, + { value: 'terminal', title: 'Terminal' }, + { value: 'whatsapp', title: 'WhatsApp' }, ], showName: true, }, @@ -91,6 +102,17 @@ export const decorators = [ 'cyberpunk': { tokens: Themes.cyberpunkTheme, darkTokens: Themes.cyberpunkDarkTheme }, 'twitter': { tokens: Themes.twitterTheme, darkTokens: Themes.twitterDarkTheme }, slate: { tokens: Themes.slateTheme, darkTokens: Themes.slateDarkTheme }, + claude: { tokens: Themes.claudeTheme, darkTokens: Themes.claudeDarkTheme }, + claymorphism: { tokens: Themes.claymorphismTheme, darkTokens: Themes.claymorphismDarkTheme }, + 'clean-slate': { tokens: Themes.cleanSlateTheme, darkTokens: Themes.cleanSlateDarkTheme }, + 'modern-minimal': { tokens: Themes.modernMinimalTheme, darkTokens: Themes.modernMinimalDarkTheme }, + nature: { tokens: Themes.natureTheme, darkTokens: Themes.natureDarkTheme }, + 'neo-brutalism': { tokens: Themes.neoBrutalismTheme, darkTokens: Themes.neoBrutalismDarkTheme }, + notebook: { tokens: Themes.notebookTheme, darkTokens: Themes.notebookDarkTheme }, + 'ocean-breeze': { tokens: Themes.oceanBreezeTheme, darkTokens: Themes.oceanBreezeDarkTheme }, + supabase: { tokens: Themes.supabaseTheme, darkTokens: Themes.supabaseDarkTheme }, + terminal: { tokens: Themes.terminalTheme, darkTokens: Themes.terminalDarkTheme }, + whatsapp: { tokens: Themes.whatsappTheme, darkTokens: Themes.whatsappDarkTheme }, }; const activeBrand = themeMap[brand] || themeMap.default; diff --git a/package-lock.json b/package-lock.json index 9742b22..22485d0 100644 --- a/package-lock.json +++ b/package-lock.json @@ -20,6 +20,7 @@ "@wordpress/hooks": "^4.39.0", "class-variance-authority": "^0.7.1", "clsx": "^2.1.1", + "cmdk": "^1.1.1", "input-otp": "^1.4.2", "lucide-react": "^0.563.0", "recharts": "^2.15.4", @@ -159,7 +160,6 @@ "integrity": "sha512-CGOfOJqWjg2qW/Mb6zNsDm+u5vFQ8DxXfbM09z69p5Z6+mE1ikP2jUXw+j42Pf1XTYED2Rni5f95npYeuwMDQA==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@babel/code-frame": "^7.29.0", "@babel/generator": "^7.29.0", @@ -2189,7 +2189,6 @@ "integrity": "sha512-CYDD3SOtsHtyXeEORYRx2qBtpDJFjRTGXUtmNEMGyzYOKj1TE3tycdlho7kA1Ufx9OYWZzg52QFBGALTirzDSw==", "devOptional": true, "license": "MIT", - "peer": true, "dependencies": { "@keyv/serialize": "^1.1.1" } @@ -2252,7 +2251,6 @@ } ], "license": "MIT", - "peer": true, "engines": { "node": ">=18" }, @@ -2293,7 +2291,6 @@ } ], "license": "MIT", - "peer": true, "engines": { "node": ">=18" } @@ -2383,7 +2380,6 @@ "resolved": "https://registry.npmjs.org/@dnd-kit/core/-/core-6.3.1.tgz", "integrity": "sha512-xkGBRQQab4RLwgXxoqETICr6S5JlogafbhNsidmrkVv2YRs5MLwpjoF2qpiGjQt8S9AoxtIV603s0GIUpY5eYQ==", "license": "MIT", - "peer": true, "dependencies": { "@dnd-kit/accessibility": "^3.1.1", "@dnd-kit/utilities": "^3.2.2", @@ -2605,7 +2601,6 @@ "resolved": "https://registry.npmjs.org/@emotion/react/-/react-11.14.0.tgz", "integrity": "sha512-O000MLDBDdk/EohJPFUqvnp4qnHeYkVP5B0xEG0D/L7cOKP9kefu2DXn8dj74cQfsEzUqh+sr1RzFqiL1o+PpA==", "license": "MIT", - "peer": true, "dependencies": { "@babel/runtime": "^7.18.3", "@emotion/babel-plugin": "^11.13.5", @@ -4049,7 +4044,6 @@ "integrity": "sha512-3giAOQvZiH5F9bMlMiv8+GSPMeqg0dbaeo58/0SlA9sxSqZhnUtxzX9/2FzyhS9sWQf5S0GJE0AKBrFqjpeYcg==", "dev": true, "license": "Apache-2.0", - "peer": true, "engines": { "node": ">=8.0.0" } @@ -4073,7 +4067,6 @@ "integrity": "sha512-s5vvxXPVdjqS3kTLKMeBMvop9hbWkwzBpu+mUO2M7sZtlkyDJGwFe33wRKnbaYDo8ExRVBIIdwIGrqpxHuKttA==", "dev": true, "license": "Apache-2.0", - "peer": true, "engines": { "node": ">=14" }, @@ -4087,7 +4080,6 @@ "integrity": "sha512-OOCM2C/QIURhJMuKaekP3TRBxBKxG/TWWA0TL2J6nXUtDnuCtccy49LUJF8xPFXMX+0LMcxFpCo8M9cGY1W6rQ==", "dev": true, "license": "Apache-2.0", - "peer": true, "dependencies": { "@opentelemetry/semantic-conventions": "1.28.0" }, @@ -4570,7 +4562,6 @@ "integrity": "sha512-5UxZqiAgLYGFjS4s9qm5mBVo433u+dSPUFWVWXmLAD4wB65oMCoXaJP1KJa9DIYYMeHu3z4BZcStG3LC593cWA==", "dev": true, "license": "Apache-2.0", - "peer": true, "dependencies": { "@opentelemetry/core": "1.30.1", "@opentelemetry/semantic-conventions": "1.28.0" @@ -4598,7 +4589,6 @@ "integrity": "sha512-jVPgBbH1gCy2Lb7X0AVQ8XAfgg0pJ4nvl8/IiQA6nxOsPvS+0zMJaFSs2ltXe0J6C8dqjcnpyqINDJmU30+uOg==", "dev": true, "license": "Apache-2.0", - "peer": true, "dependencies": { "@opentelemetry/core": "1.30.1", "@opentelemetry/resources": "1.30.1", @@ -4627,7 +4617,6 @@ "integrity": "sha512-R5R9tb2AXs2IRLNKLBJDynhkfmx7mX0vi8NkhZb3gUkPWHn6HXk5J8iQ/dql0U3ApfWym4kXXmBDRGO+oeOfjg==", "dev": true, "license": "Apache-2.0", - "peer": true, "engines": { "node": ">=14" } @@ -5138,6 +5127,447 @@ "node": ">=10" } }, + "node_modules/@radix-ui/primitive": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/@radix-ui/primitive/-/primitive-1.1.3.tgz", + "integrity": "sha512-JTF99U/6XIjCBo0wqkU5sK10glYe27MRRsfwoiq5zzOEZLHU3A3KCMa5X/azekYRCJ0HlwI0crAXS/5dEHTzDg==", + "license": "MIT" + }, + "node_modules/@radix-ui/react-compose-refs": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@radix-ui/react-compose-refs/-/react-compose-refs-1.1.2.tgz", + "integrity": "sha512-z4eqJvfiNnFMHIIvXP3CY57y2WJs5g2v3X0zm9mEJkrkNv4rDxu+sg9Jh8EkXyeqBkB7SOcboo9dMVqhyrACIg==", + "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.1.2", + "resolved": "https://registry.npmjs.org/@radix-ui/react-context/-/react-context-1.1.2.tgz", + "integrity": "sha512-jCi/QKUM2r1Ju5a3J64TH2A5SpKAgh0LpknyqdQ4m6DCV0xJ2HG1xARRwNGPQfi1SLdLWZ1OJz6F4OMBBNiGJA==", + "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.1.15", + "resolved": "https://registry.npmjs.org/@radix-ui/react-dialog/-/react-dialog-1.1.15.tgz", + "integrity": "sha512-TCglVRtzlffRNxRMEyR36DGBLJpeusFcgMVD9PZEzAKnUs1lKCgX5u9BmC2Yg+LL9MgZDugFFs1Vl+Jp4t/PGw==", + "license": "MIT", + "dependencies": { + "@radix-ui/primitive": "1.1.3", + "@radix-ui/react-compose-refs": "1.1.2", + "@radix-ui/react-context": "1.1.2", + "@radix-ui/react-dismissable-layer": "1.1.11", + "@radix-ui/react-focus-guards": "1.1.3", + "@radix-ui/react-focus-scope": "1.1.7", + "@radix-ui/react-id": "1.1.1", + "@radix-ui/react-portal": "1.1.9", + "@radix-ui/react-presence": "1.1.5", + "@radix-ui/react-primitive": "2.1.3", + "@radix-ui/react-slot": "1.2.3", + "@radix-ui/react-use-controllable-state": "1.2.2", + "aria-hidden": "^1.2.4", + "react-remove-scroll": "^2.6.3" + }, + "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-dialog/node_modules/@radix-ui/react-primitive": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/@radix-ui/react-primitive/-/react-primitive-2.1.3.tgz", + "integrity": "sha512-m9gTwRkhy2lvCPe6QJp4d3G1TYEUHn/FzJUtq9MjH46an1wJU+GdoGC5VLof8RX8Ft/DlpshApkhswDLZzHIcQ==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-slot": "1.2.3" + }, + "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-dismissable-layer": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/@radix-ui/react-dismissable-layer/-/react-dismissable-layer-1.1.11.tgz", + "integrity": "sha512-Nqcp+t5cTB8BinFkZgXiMJniQH0PsUt2k51FUhbdfeKvc4ACcG2uQniY/8+h1Yv6Kza4Q7lD7PQV0z0oicE0Mg==", + "license": "MIT", + "dependencies": { + "@radix-ui/primitive": "1.1.3", + "@radix-ui/react-compose-refs": "1.1.2", + "@radix-ui/react-primitive": "2.1.3", + "@radix-ui/react-use-callback-ref": "1.1.1", + "@radix-ui/react-use-escape-keydown": "1.1.1" + }, + "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-dismissable-layer/node_modules/@radix-ui/react-primitive": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/@radix-ui/react-primitive/-/react-primitive-2.1.3.tgz", + "integrity": "sha512-m9gTwRkhy2lvCPe6QJp4d3G1TYEUHn/FzJUtq9MjH46an1wJU+GdoGC5VLof8RX8Ft/DlpshApkhswDLZzHIcQ==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-slot": "1.2.3" + }, + "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.1.3", + "resolved": "https://registry.npmjs.org/@radix-ui/react-focus-guards/-/react-focus-guards-1.1.3.tgz", + "integrity": "sha512-0rFg/Rj2Q62NCm62jZw0QX7a3sz6QCQU0LpZdNrJX8byRGaGVTqbrW9jAoIAHyMQqsNpeZ81YgSizOt5WXq0Pw==", + "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.1.7", + "resolved": "https://registry.npmjs.org/@radix-ui/react-focus-scope/-/react-focus-scope-1.1.7.tgz", + "integrity": "sha512-t2ODlkXBQyn7jkl6TNaw/MtVEVvIGelJDCG41Okq/KwUsJBwQ4XVZsHAVUkK4mBv3ewiAS3PGuUWuY2BoK4ZUw==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-compose-refs": "1.1.2", + "@radix-ui/react-primitive": "2.1.3", + "@radix-ui/react-use-callback-ref": "1.1.1" + }, + "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-scope/node_modules/@radix-ui/react-primitive": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/@radix-ui/react-primitive/-/react-primitive-2.1.3.tgz", + "integrity": "sha512-m9gTwRkhy2lvCPe6QJp4d3G1TYEUHn/FzJUtq9MjH46an1wJU+GdoGC5VLof8RX8Ft/DlpshApkhswDLZzHIcQ==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-slot": "1.2.3" + }, + "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.1", + "resolved": "https://registry.npmjs.org/@radix-ui/react-id/-/react-id-1.1.1.tgz", + "integrity": "sha512-kGkGegYIdQsOb4XjsfM97rXsiHaBwco+hFI66oO4s9LU+PLAC5oJ7khdOVFxkhsmlbpUqDAvXw11CluXP+jkHg==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-use-layout-effect": "1.1.1" + }, + "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-portal": { + "version": "1.1.9", + "resolved": "https://registry.npmjs.org/@radix-ui/react-portal/-/react-portal-1.1.9.tgz", + "integrity": "sha512-bpIxvq03if6UNwXZ+HTK71JLh4APvnXntDc6XOX8UVq4XQOVl7lwok0AvIl+b8zgCw3fSaVTZMpAPPagXbKmHQ==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-primitive": "2.1.3", + "@radix-ui/react-use-layout-effect": "1.1.1" + }, + "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/node_modules/@radix-ui/react-primitive": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/@radix-ui/react-primitive/-/react-primitive-2.1.3.tgz", + "integrity": "sha512-m9gTwRkhy2lvCPe6QJp4d3G1TYEUHn/FzJUtq9MjH46an1wJU+GdoGC5VLof8RX8Ft/DlpshApkhswDLZzHIcQ==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-slot": "1.2.3" + }, + "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.5", + "resolved": "https://registry.npmjs.org/@radix-ui/react-presence/-/react-presence-1.1.5.tgz", + "integrity": "sha512-/jfEwNDdQVBCNvjkGit4h6pMOzq8bHkopq458dPt2lMjx+eBQUohZNG9A7DtO/O5ukSbxuaNGXMjHicgwy6rQQ==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-compose-refs": "1.1.2", + "@radix-ui/react-use-layout-effect": "1.1.1" + }, + "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.1.4", + "resolved": "https://registry.npmjs.org/@radix-ui/react-primitive/-/react-primitive-2.1.4.tgz", + "integrity": "sha512-9hQc4+GNVtJAIEPEqlYqW5RiYdrr8ea5XQ0ZOnD6fgru+83kqT15mq2OCcbe8KnjRZl5vF3ks69AKz3kh1jrhg==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-slot": "1.2.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/node_modules/@radix-ui/react-slot": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/@radix-ui/react-slot/-/react-slot-1.2.4.tgz", + "integrity": "sha512-Jl+bCv8HxKnlTLVrcDE8zTMJ09R9/ukw4qBs/oZClOfoQk/cOTbDn+NceXfV7j09YPVQUryJPHurafcSg6EVKA==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-compose-refs": "1.1.2" + }, + "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-slot": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/@radix-ui/react-slot/-/react-slot-1.2.3.tgz", + "integrity": "sha512-aeNmHnBxbi2St0au6VBVC7JXFlhLlOnvIIlePNniyUNAClzmtAUEY8/pBiK3iHjufOlwA+c20/8jngo7xcrg8A==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-compose-refs": "1.1.2" + }, + "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-callback-ref": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@radix-ui/react-use-callback-ref/-/react-use-callback-ref-1.1.1.tgz", + "integrity": "sha512-FkBMwD+qbGQeMu1cOHnuGB6x4yzPjho8ap5WtbEJ26umhgqVXbhekKUQO+hZEL1vU92a3wHwdp0HAcqAUF5iDg==", + "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-controllable-state": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/@radix-ui/react-use-controllable-state/-/react-use-controllable-state-1.2.2.tgz", + "integrity": "sha512-BjasUjixPFdS+NKkypcyyN5Pmg83Olst0+c6vGov0diwTEo6mgdqVR6hxcEgFuh4QrAs7Rc+9KuGJ9TVCj0Zzg==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-use-effect-event": "0.0.2", + "@radix-ui/react-use-layout-effect": "1.1.1" + }, + "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.2", + "resolved": "https://registry.npmjs.org/@radix-ui/react-use-effect-event/-/react-use-effect-event-0.0.2.tgz", + "integrity": "sha512-Qp8WbZOBe+blgpuUT+lw2xheLP8q0oatc9UpmiemEICxGvFLYmHm9QowVZGHtJlGbS6A6yJ3iViad/2cVjnOiA==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-use-layout-effect": "1.1.1" + }, + "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-escape-keydown": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@radix-ui/react-use-escape-keydown/-/react-use-escape-keydown-1.1.1.tgz", + "integrity": "sha512-Il0+boE7w/XebUHyBjroE+DbByORGR9KKmITzbR7MyQ4akpORYP/ZmbhAr0DG7RmmBqoOnZdy2QlvajJ2QA59g==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-use-callback-ref": "1.1.1" + }, + "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.1", + "resolved": "https://registry.npmjs.org/@radix-ui/react-use-layout-effect/-/react-use-layout-effect-1.1.1.tgz", + "integrity": "sha512-RbJRS4UWQFkzHTTwVymMTUv8EqYhOp8dOOviLj2ugtTiXRaRQS7GLGxZTLL1jWhMeoSCf5zmcZkqTl9IiYfXcQ==", + "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/@rtsao/scc": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/@rtsao/scc/-/scc-1.1.0.tgz", @@ -5793,7 +6223,6 @@ "integrity": "sha512-8QqtOQT5ACVlmsvKOJNEaWmRPmcojMOzCz4Hs2BGG/toAp/K38LcsMRyLp349glq5AzJbCEeimEoxaX6v/fLrA==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@babel/core": "^7.21.3", "@svgr/babel-preset": "8.1.0", @@ -6210,6 +6639,7 @@ "integrity": "sha512-o4PXJQidqJl82ckFaXUeoAW+XysPLauYI43Abki5hABd853iMhitooc6znOnczgbTYmEP6U6/y1ZyKAIsvMKGg==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@babel/code-frame": "^7.10.4", "@babel/runtime": "^7.12.5", @@ -6230,6 +6660,7 @@ "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", "dev": true, "license": "MIT", + "peer": true, "engines": { "node": ">=8" } @@ -6240,6 +6671,7 @@ "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", "dev": true, "license": "MIT", + "peer": true, "engines": { "node": ">=10" }, @@ -6253,6 +6685,7 @@ "integrity": "sha512-b0P0sZPKtyu8HkeRAfCq0IfURZK+SuwMjY1UXGBU27wpAiTwQAIlq56IbIO+ytk/JjS1fMR14ee5WBBfKi5J6A==", "dev": true, "license": "Apache-2.0", + "peer": true, "dependencies": { "dequal": "^2.0.3" } @@ -6263,6 +6696,7 @@ "integrity": "sha512-Qb1gy5OrP5+zDf2Bvnzdl3jsTf1qXVMazbvCoKhtKqVs4/YK4ozX4gKQJJVyNe+cajNPn0KoC0MC3FUmaHWEmQ==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "ansi-regex": "^5.0.1", "ansi-styles": "^5.0.0", @@ -6277,7 +6711,8 @@ "resolved": "https://registry.npmjs.org/react-is/-/react-is-17.0.2.tgz", "integrity": "sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w==", "dev": true, - "license": "MIT" + "license": "MIT", + "peer": true }, "node_modules/@testing-library/jest-dom": { "version": "6.9.1", @@ -6363,7 +6798,8 @@ "resolved": "https://registry.npmjs.org/@types/aria-query/-/aria-query-5.0.4.tgz", "integrity": "sha512-rfT93uj5s0PRL7EzccGMs3brplhcrghnDoV26NqKhCAS1hVo+WdNsPvE/yb6ilfr5hi2MEk6d5EWJTKdxg8jVw==", "dev": true, - "license": "MIT" + "license": "MIT", + "peer": true }, "node_modules/@types/babel__core": { "version": "7.20.5", @@ -6546,7 +6982,6 @@ "integrity": "sha512-FXx2pKgId/WyYo2jXw63kk7/+TY7u7AziEJxJAnSFzHlqTAS3Ync6SvgYAN/k4/PQpnnVuzoMuVnByKK2qp0ag==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@types/estree": "*", "@types/json-schema": "*" @@ -6751,7 +7186,6 @@ "integrity": "sha512-Rs1bVAIdBs5gbTIKza/tgpMuG1k3U/UMJLWecIMxNdJFDMzcM5LOiLVRYh3PilWEYDIeUDv7bpiHPLPsbydGcw==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "undici-types": "~6.21.0" } @@ -6826,7 +7260,6 @@ "resolved": "https://registry.npmjs.org/@types/react/-/react-18.3.28.tgz", "integrity": "sha512-z9VXpC7MWrhfWipitjNdgCauoMLRdIILQsAEV+ZesIzBq/oUlxk0m3ApZuMFCXdnS4U7KrI+l3WRUEGQ8K1QKw==", "license": "MIT", - "peer": true, "dependencies": { "@types/prop-types": "*", "csstype": "^3.2.2" @@ -7039,7 +7472,6 @@ "integrity": "sha512-tbsV1jPne5CkFQCgPBcDOt30ItF7aJoZL997JSF7MhGQqOeT3svWRYxiqlfA5RUdlHN6Fi+EI9bxqbdyAUZjYQ==", "dev": true, "license": "BSD-2-Clause", - "peer": true, "dependencies": { "@typescript-eslint/scope-manager": "6.21.0", "@typescript-eslint/types": "6.21.0", @@ -7839,7 +8271,6 @@ "integrity": "sha512-yJ474Zv3cwiSOO9nXJuqzvwEeM+chDuQ8GJirw+pZ91sCGCyOZ3dJkVE09fTV0VEVzXyLWhh3G/AolYTPX7Mow==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@ampproject/remapping": "^2.2.0", "@babel/code-frame": "^7.25.7", @@ -9075,7 +9506,6 @@ "integrity": "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==", "dev": true, "license": "MIT", - "peer": true, "bin": { "acorn": "bin/acorn" }, @@ -9169,7 +9599,6 @@ "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "fast-deep-equal": "^3.1.1", "fast-json-stable-stringify": "^2.0.0", @@ -9371,6 +9800,18 @@ "sprintf-js": "~1.0.2" } }, + "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/aria-query": { "version": "5.3.2", "resolved": "https://registry.npmjs.org/aria-query/-/aria-query-5.3.2.tgz", @@ -10342,7 +10783,6 @@ } ], "license": "MIT", - "peer": true, "dependencies": { "baseline-browser-mapping": "^2.9.0", "caniuse-lite": "^1.0.30001759", @@ -10975,6 +11415,22 @@ "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/co": { "version": "4.6.0", "resolved": "https://registry.npmjs.org/co/-/co-4.6.0.tgz", @@ -12218,6 +12674,7 @@ "integrity": "sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA==", "dev": true, "license": "MIT", + "peer": true, "engines": { "node": ">=6" } @@ -12260,13 +12717,18 @@ "dev": true, "license": "MIT" }, + "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/devtools-protocol": { "version": "0.0.1507524", "resolved": "https://registry.npmjs.org/devtools-protocol/-/devtools-protocol-0.0.1507524.tgz", "integrity": "sha512-OjaNE7qpk6GRTXtqQjAE5bGx6+c4F1zZH0YXtpZQLM92HNXx4zMAaqlKhP4T52DosG6hDW8gPMNhGOF8xbwk/w==", "dev": true, - "license": "BSD-3-Clause", - "peer": true + "license": "BSD-3-Clause" }, "node_modules/diff-sequences": { "version": "29.6.3", @@ -12322,7 +12784,8 @@ "resolved": "https://registry.npmjs.org/dom-accessibility-api/-/dom-accessibility-api-0.5.16.tgz", "integrity": "sha512-X7BJ2yElsnOJ30pZF4uIIDfBEVgF4XEBxL9Bxhy6dnrm5hkzqmsWHGTiHqRiITNhMyFLyAiWndIJP7Z1NTteDg==", "dev": true, - "license": "MIT" + "license": "MIT", + "peer": true }, "node_modules/dom-converter": { "version": "0.2.0", @@ -12876,7 +13339,6 @@ "dev": true, "hasInstallScript": true, "license": "MIT", - "peer": true, "bin": { "esbuild": "bin/esbuild" }, @@ -12981,7 +13443,6 @@ "deprecated": "This version is no longer supported. Please see https://eslint.org/version-support for other options.", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@eslint-community/eslint-utils": "^4.2.0", "@eslint-community/regexpp": "^4.6.1", @@ -13038,7 +13499,6 @@ "integrity": "sha512-/IGJ6+Dka158JnP5n5YFMOszjDWrXggGz1LaK/guZq9vZTmniaKlHcsscvkAhn9y4U+BU3JuUdYvtAMcv30y4A==", "dev": true, "license": "MIT", - "peer": true, "bin": { "eslint-config-prettier": "bin/cli.js" }, @@ -13162,7 +13622,6 @@ "integrity": "sha512-whOE1HFo/qJDyX4SnXzP4N6zOWn79WhnCUY/iDR0mPfQZO8wcYE4JClzI2oZrhBnnMUCBCHZhO6VQyoBU95mZA==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@rtsao/scc": "^1.1.0", "array-includes": "^3.1.9", @@ -14912,6 +15371,15 @@ "url": "https://github.com/sponsors/ljharb" } }, + "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/get-package-type": { "version": "0.1.0", "resolved": "https://registry.npmjs.org/get-package-type/-/get-package-type-0.1.0.tgz", @@ -16871,7 +17339,6 @@ "integrity": "sha512-NIy3oAFp9shda19hy4HK0HRTWKtPJmGdnvywu01nOqNC2vZg+Z+fvJDxpMQA88eb2I9EcafcdjYgsDthnYTvGw==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@jest/core": "^29.7.0", "@jest/types": "^29.6.3", @@ -17972,8 +18439,7 @@ "resolved": "https://registry.npmjs.org/devtools-protocol/-/devtools-protocol-0.0.1566079.tgz", "integrity": "sha512-MJfAEA1UfVhSs7fbSQOG4czavUp1ajfg6prlAN0+cmfa2zNjaIbvq8VneP7do1WAQQIvgNJWSMeP6UyI90gIlQ==", "dev": true, - "license": "BSD-3-Clause", - "peer": true + "license": "BSD-3-Clause" }, "node_modules/lighthouse/node_modules/puppeteer-core/node_modules/ws": { "version": "8.19.0", @@ -18513,6 +18979,7 @@ "integrity": "sha512-h5bgJWpxJNswbU7qCrV0tIKQCaS3blPDrqKWx+QxzuzL1zGUzij9XCWLrSLsJPu5t+eWA/ycetzYAO5IOMcWAQ==", "dev": true, "license": "MIT", + "peer": true, "bin": { "lz-string": "bin/bin.js" } @@ -19345,7 +19812,6 @@ "integrity": "sha512-cuXAJJB1Rdqz0UO6w524matlBqDBjcNt7Ru+RDIu4y6RI1gVqiWBnylrK8sPRk81gGBA0X8hJbDXolVOoTc+sA==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "ajv": "^6.12.6", "ajv-errors": "^1.0.1", @@ -20137,6 +20603,7 @@ "integrity": "sha512-vA30H8Nvkq/cPBnNw4Q8TWz1EJyqgpuinBcHET0YVJVFldr8JDNiU9LaWAE1KqSkRYazuaBhTpB5ZzShOezQ6A==", "dev": true, "license": "Apache-2.0", + "peer": true, "dependencies": { "playwright-core": "1.58.2" }, @@ -20156,6 +20623,7 @@ "integrity": "sha512-yZkEtftgwS8CsfYo7nm0KE8jsvm6i/PTgVtB8DL726wNf6H2IMsDuxCpJj59KDaxCtSnrWan2AeDqM7JBaultg==", "dev": true, "license": "Apache-2.0", + "peer": true, "bin": { "playwright-core": "cli.js" }, @@ -20174,6 +20642,7 @@ "os": [ "darwin" ], + "peer": true, "engines": { "node": "^8.16.0 || ^10.6.0 || >=11.0.0" } @@ -20224,7 +20693,6 @@ } ], "license": "MIT", - "peer": true, "dependencies": { "nanoid": "^3.3.11", "picocolors": "^1.1.1", @@ -21193,7 +21661,6 @@ "integrity": "sha512-X4UlrxDTH8oom9qXlcjnydsjAOD2BmB6yFmvS4Z2zdTzqqpRWb+fbqrH412+l+OUXmbzJlSXjlMFYPgYG12IAA==", "dev": true, "license": "MIT", - "peer": true, "bin": { "prettier": "bin/prettier.cjs" }, @@ -21621,7 +22088,6 @@ "resolved": "https://registry.npmjs.org/react/-/react-18.3.1.tgz", "integrity": "sha512-wS+hAgJShR0KhEvPJArfuPVN1+Hz1t0Y6n5jLrGQbkb4urgPE/0Rve+1kMB1v/oWgHgm4WIcV+i7F2pTVj+2iQ==", "license": "MIT", - "peer": true, "dependencies": { "loose-envify": "^1.1.0" }, @@ -21697,7 +22163,6 @@ "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-18.3.1.tgz", "integrity": "sha512-5m4nQKp+rZRb09LNH59GM4BxTh9251/ylbKIbpe7TpGxfJ+9kv6BLkLBXIjjspbgbnIBNqlI23tRnTWT0snUIw==", "license": "MIT", - "peer": true, "dependencies": { "loose-envify": "^1.1.0", "scheduler": "^0.23.2" @@ -21718,11 +22183,57 @@ "integrity": "sha512-jCvmsr+1IUSMUyzOkRcvnVbX3ZYC6g9TDrDbFuFmRDq7PD4yaGbLKNQL6k2jnArV8hjYxh7hVhAZB6s9HDGpZA==", "dev": true, "license": "MIT", - "peer": true, "engines": { "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-smooth": { "version": "4.0.4", "resolved": "https://registry.npmjs.org/react-smooth/-/react-smooth-4.0.4.tgz", @@ -21738,6 +22249,28 @@ "react-dom": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" } }, + "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/react-transition-group": { "version": "4.4.5", "resolved": "https://registry.npmjs.org/react-transition-group/-/react-transition-group-4.4.5.tgz", @@ -21999,8 +22532,7 @@ "version": "5.0.1", "resolved": "https://registry.npmjs.org/redux/-/redux-5.0.1.tgz", "integrity": "sha512-M9/ELqF6fy8FwmkpnF0S3YKOqMyoWJ4+CS5Efg2ct3oY9daQvd/Pc71FpGZsVsbl3Cpb+IIcjBDUnnyBdQbq4w==", - "license": "MIT", - "peer": true + "license": "MIT" }, "node_modules/reflect.getprototypeof": { "version": "1.0.10", @@ -22547,7 +23079,6 @@ "integrity": "sha512-fDz1zJpd5GycprAbu4Q2PV/RprsRtKC/0z82z0JLgdytmcq0+ujJbJ/09bPGDxCLkKY3Np5cRAOcWiVkLXJURg==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "chokidar": "^4.0.0", "immutable": "^5.0.2", @@ -22652,7 +23183,6 @@ "integrity": "sha512-B/gBuNg5SiMTrPkC+A2+cW0RszwxYmn6VYxB/inlBStS5nx6xHIt/ehKRhIMhqusl7a8LjQoZnjCs5vhwxOQ1g==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "fast-deep-equal": "^3.1.3", "fast-uri": "^3.0.1", @@ -23514,7 +24044,6 @@ "integrity": "sha512-885uSIn8NQw2ZG7vy84K45lHCOSyz1DVsDV8pHiHQj3J0riCuWLNeO50lK9z98zE8kjhgTtxAAkMTy5nkmNRKQ==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@storybook/global": "^5.0.0", "@storybook/icons": "^2.0.1", @@ -23966,7 +24495,6 @@ } ], "license": "MIT", - "peer": true, "dependencies": { "@csstools/css-parser-algorithms": "^3.0.5", "@csstools/css-syntax-patches-for-csstree": "^1.0.19", @@ -24296,7 +24824,6 @@ "integrity": "sha512-orRsuYpJVw8LdAwqqLykBj9ecS5/cRHlI5+nvTo8LcCKmzDmqVORXtOIYEEQuL9D4BxtA1lm5isAqzQZCoQ6Eg==", "devOptional": true, "license": "MIT", - "peer": true, "dependencies": { "cssesc": "^3.0.0", "util-deprecate": "^1.0.2" @@ -24966,7 +25493,6 @@ "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==", "dev": true, "license": "MIT", - "peer": true, "engines": { "node": ">=12" }, @@ -25248,7 +25774,6 @@ "integrity": "sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==", "dev": true, "license": "(MIT OR CC0-1.0)", - "peer": true, "engines": { "node": ">=10" }, @@ -25361,7 +25886,6 @@ "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==", "dev": true, "license": "Apache-2.0", - "peer": true, "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" @@ -25514,7 +26038,6 @@ "dev": true, "hasInstallScript": true, "license": "MIT", - "peer": true, "dependencies": { "napi-postinstall": "^0.3.0" }, @@ -25660,6 +26183,27 @@ "requires-port": "^1.0.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-memo-one": { "version": "1.1.3", "resolved": "https://registry.npmjs.org/use-memo-one/-/use-memo-one-1.1.3.tgz", @@ -25669,6 +26213,28 @@ "react": "^16.8.0 || ^17.0.0 || ^18.0.0" } }, + "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/use-sync-external-store": { "version": "1.6.0", "resolved": "https://registry.npmjs.org/use-sync-external-store/-/use-sync-external-store-1.6.0.tgz", @@ -25891,7 +26457,6 @@ "integrity": "sha512-gX/dMkRQc7QOMzgTe6KsYFM7DxeIONQSui1s0n/0xht36HvrgbxtM1xBlgx596NbpHuQU8P7QpKwrZYwUX48nw==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@types/eslint-scope": "^3.7.7", "@types/estree": "^1.0.8", @@ -26000,7 +26565,6 @@ "integrity": "sha512-pIDJHIEI9LR0yxHXQ+Qh95k2EvXpWzZ5l+d+jIo+RdSm9MiHfzazIxwwni/p7+x4eJZuvG1AJwgC4TNQ7NRgsg==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@discoveryjs/json-ext": "^0.5.0", "@webpack-cli/configtest": "^2.1.1", @@ -26086,7 +26650,6 @@ "integrity": "sha512-0XavAZbNJ5sDrCbkpWL8mia0o5WPOd2YGtxrEiZkBK9FjLppIUK2TgxK6qGD2P3hUXTJNNPVibrerKcx5WkR1g==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@types/bonjour": "^3.5.9", "@types/connect-history-api-fallback": "^1.3.5", diff --git a/package.json b/package.json index df149b7..56aa851 100644 --- a/package.json +++ b/package.json @@ -72,6 +72,7 @@ "@wordpress/hooks": "^4.39.0", "class-variance-authority": "^0.7.1", "clsx": "^2.1.1", + "cmdk": "^1.1.1", "input-otp": "^1.4.2", "lucide-react": "^0.563.0", "recharts": "^2.15.4", diff --git a/src/components/ui/Combobox.stories.tsx b/src/components/ui/Combobox.stories.tsx index ea880cb..6e49db9 100644 --- a/src/components/ui/Combobox.stories.tsx +++ b/src/components/ui/Combobox.stories.tsx @@ -1,5 +1,5 @@ import type { Meta, StoryObj } from "@storybook/react"; -import React, { useState } from "react"; +import { useState } from "react"; import { Combobox, ComboboxInput, @@ -13,11 +13,53 @@ import { ComboboxChips, ComboboxChip, ComboboxChipsInput, + ComboboxTrigger, + ComboboxValue, useComboboxAnchor, -} from "./combobox"; -import { CheckIcon, ChevronDownIcon } from "lucide-react"; + Button, + InputGroupAddon, + Command, + CommandEmpty, + CommandGroup, + CommandInput, + CommandItem, + CommandList, + Popover, + PopoverContent, + PopoverTrigger, + DropdownMenu, + DropdownMenuContent, + DropdownMenuGroup, + DropdownMenuItem, + DropdownMenuLabel, + DropdownMenuSeparator, + DropdownMenuTrigger, + CommandDialog, +} from "./index"; +import { + CheckIcon, + ChevronsUpDownIcon, + GlobeIcon, + MoreHorizontalIcon, + TagIcon, +} from "lucide-react"; +import { cn } from "@/lib/utils"; + +const frameworks = [ + "Next.js", + "SvelteKit", + "Nuxt.js", + "Remix", + "Astro", +] as const; -const frameworks = ["Next.js", "SvelteKit", "Nuxt.js", "Remix", "Astro"] as const; +const frameworkOptions = [ + { value: "next.js", label: "Next.js" }, + { value: "sveltekit", label: "SvelteKit" }, + { value: "nuxt.js", label: "Nuxt.js" }, + { value: "remix", label: "Remix" }, + { value: "astro", label: "Astro" }, +]; const meta = { title: "UI/Combobox", @@ -30,94 +72,107 @@ export default meta; type Story = StoryObj; -/** - * Basic searchable combobox with a list of items. - * Users can type to filter and select a single item. - */ +// ─── Base UI Combobox (Default) ──────────────────────── + +function ComboboxDefaultDemo() { + const [value, setValue] = useState(null); + + return ( + + + + + {frameworks.map((item) => ( + + {item} + + ))} + + No results found. + + + ); +} + export const Default: Story = { - render: function DefaultStory() { - const [value, setValue] = useState(null); - - return ( - - - - - {frameworks.map((item) => ( - {item} - ))} - - No results found. - - - ); - }, + render: () => , }; -/** - * Combobox with clear button to reset selection. - * Useful when users need to easily clear their selection. - */ -export const WithClearButton: Story = { - render: function WithClearButtonStory() { - const [value, setValue] = useState(null); +// ─── With Clear Button ──────────────────────── - return ( - - - - - {frameworks.map((item) => ( - {item} - ))} - - No results found. - - - ); - }, +function ComboboxWithClearDemo() { + const [value, setValue] = useState(null); + + return ( + + + + + {frameworks.map((item) => ( + + {item} + + ))} + + No results found. + + + ); +} + +export const WithClearButton: Story = { + render: () => , }; -const groupedItems = ["apple", "banana", "orange", "carrot", "broccoli", "spinach"]; +// ─── With Groups ──────────────────────── -/** - * Combobox with items organized into labeled groups with separators. - * Useful for categorizing options in larger lists. - */ -export const WithGroups: Story = { - render: function WithGroupsStory() { - const [value, setValue] = useState(null); +const groupedItems = [ + "apple", + "banana", + "orange", + "carrot", + "broccoli", + "spinach", +]; - return ( - - - - - - Fruits - Apple - Banana - Orange - - - - Vegetables - Carrot - Broccoli - Spinach - - - No results found. - - - ); - }, +function ComboboxWithGroupsDemo() { + const [value, setValue] = useState(null); + + return ( + + + + + + Fruits + Apple + Banana + Orange + + + + Vegetables + Carrot + Broccoli + Spinach + + + No results found. + + + ); +} + +export const WithGroups: Story = { + render: () => , }; +// ─── Multi Select ──────────────────────── + type MultiSelectItem = { value: string; label: string }; const multiSelectItems: MultiSelectItem[] = [ @@ -129,151 +184,496 @@ const multiSelectItems: MultiSelectItem[] = [ { value: "preact", label: "Preact" }, ]; -/** - * Multi-select combobox with tag chips and optional search. - * Selected items are displayed as removable tags. Supports controlled - * and uncontrolled modes, max tag count, and searchable filtering. - */ -export const MultiSelect: Story = { - render: function MultiSelectStory() { - const [selectedValues, setSelectedValues] = useState(["react", "vue"]); - const anchorRef = useComboboxAnchor(); - - const selectedItems = multiSelectItems.filter((item) => - selectedValues.includes(item.value) - ); - - const handleValueChange = ( - value: MultiSelectItem[] | MultiSelectItem | null - ) => { - const next = Array.isArray(value) ? value : value ? [value] : []; - setSelectedValues(next.map((i) => i.value)); - }; - - return ( - item.label} - itemToStringValue={(item) => item.value} - > - - {selectedItems.map((item) => ( - +function ComboboxMultiSelectDemo() { + const [selectedValues, setSelectedValues] = useState([ + "react", + "vue", + ]); + const anchorRef = useComboboxAnchor(); + + const selectedItems = multiSelectItems.filter((item) => + selectedValues.includes(item.value) + ); + + const handleValueChange = ( + value: MultiSelectItem[] | MultiSelectItem | null + ) => { + const next = Array.isArray(value) ? value : value ? [value] : []; + setSelectedValues(next.map((i) => i.value)); + }; + + return ( + item.label} + itemToStringValue={(item) => item.value} + > + + {selectedItems.map((item) => ( + {item.label} + ))} + + + + + {multiSelectItems.map((item) => ( + {item.label} - + ))} - - - - - {multiSelectItems.map((item) => ( - - {item.label} - - ))} - - No frameworks found. - - - ); - }, + + No frameworks found. + + + ); +} + +export const MultiSelect: Story = { + render: () => , }; -/** - * Multi-select with trigger button showing selected tags as pills. - * Alternative presentation for multi-select where selections are - * displayed in a compact trigger button with overflow indicator. - */ -export const MultiSelectWithTrigger: Story = { - render: function MultiSelectTriggerStory() { - const [selectedValues, setSelectedValues] = useState(["react"]); - - const selectedItems = multiSelectItems.filter((item) => - selectedValues.includes(item.value) - ); - - const handleValueChange = ( - value: MultiSelectItem[] | MultiSelectItem | null - ) => { - const next = Array.isArray(value) ? value : value ? [value] : []; - setSelectedValues(next.map((i) => i.value)); - }; - - const maxTagCount = 2; - const visibleTags = selectedItems.slice(0, maxTagCount); - const remainingCount = Math.max(selectedItems.length - maxTagCount, 0); - - return ( - item.label} - itemToStringValue={(item) => item.value} +// ─── Command + Popover Pattern (ShadCN Combobox Demo) ──────────────────────── + +function ComboboxCommandDemo() { + const [open, setOpen] = useState(false); + const [value, setValue] = useState(""); + + return ( + + + } > - - - - - {multiSelectItems.map((item) => ( - - - {selectedValues.includes(item.value) && ( - - )} + + + + + ); +} + +export const DropdownMenuWithCommand: Story = { + render: () => , +}; + +// ─── Custom Items ──────────────────────── + +type Framework = { + value: string; + label: string; + description: string; +}; + +const customFrameworks: Framework[] = [ + { value: "next.js", label: "Next.js", description: "The React Framework" }, + { value: "sveltekit", label: "SvelteKit", description: "Web development, streamlined" }, + { value: "nuxt.js", label: "Nuxt.js", description: "The Intuitive Vue Framework" }, + { value: "remix", label: "Remix", description: "Build better websites" }, + { value: "astro", label: "Astro", description: "The web framework for content" }, +]; + +function ComboboxCustomItemsDemo() { + const [value, setValue] = useState(null); + + return ( + item.label} + itemToStringValue={(item) => item.value} + > + + + + {customFrameworks.map((item) => ( + +
+ {item.label} + + {item.description} - {item.label} - - ))} - - No frameworks found. - - - ); - }, +
+
+ ))} +
+ No frameworks found. +
+
+ ); +} + +export const CustomItems: Story = { + render: () => , +}; + +// ─── Invalid ──────────────────────── + +function ComboboxInvalidDemo() { + const [value, setValue] = useState(null); + + return ( + + + + + {frameworks.map((item) => ( + + {item} + + ))} + + No results found. + + + ); +} + +export const Invalid: Story = { + render: () => , }; -/** - * Disabled combobox state. Input is not interactive. - */ +// ─── Disabled ──────────────────────── + export const Disabled: Story = { render: () => ( - + {frameworks.map((item) => ( - {item} + + {item} + ))} ), }; + +// ─── Auto Highlight ──────────────────────── + +function ComboboxAutoHighlightDemo() { + const [value, setValue] = useState(null); + + return ( + + + + + {frameworks.map((item) => ( + + {item} + + ))} + + No results found. + + + ); +} + +export const AutoHighlight: Story = { + render: () => , +}; + +// ─── Popup (Trigger Button) ──────────────────────── + +function ComboboxPopupDemo() { + const [value, setValue] = useState(null); + + return ( + + + } + > + + + + + + + {frameworks.map((item) => ( + + {item} + + ))} + + No results found. + + + ); +} + +export const Popup: Story = { + render: () => , +}; + +// ─── Input Group (with addon) ──────────────────────── + +function ComboboxInputGroupDemo() { + const [value, setValue] = useState(null); + + return ( + + + + + + + + + {frameworks.map((item) => ( + + {item} + + ))} + + No results found. + + + ); +} + +export const InputGroup: Story = { + render: () => , +}; + +// ─── RTL ──────────────────────── + +const arabicItems = [ + "Next.js - إطار عمل ريأكت", + "SvelteKit - إطار عمل سفيلت", + "Nuxt.js - إطار عمل فيو", + "Remix - إطار عمل ريأكت", + "Astro - إطار عمل المحتوى", +]; + +function ComboboxRtlDemo() { + const [value, setValue] = useState(null); + + return ( +
+ + + + + {arabicItems.map((item) => ( + + {item} + + ))} + + لا توجد نتائج. + + +
+ ); +} + +export const RTL: Story = { + render: () => , +}; diff --git a/src/components/ui/Command.stories.tsx b/src/components/ui/Command.stories.tsx new file mode 100644 index 0000000..d8e0010 --- /dev/null +++ b/src/components/ui/Command.stories.tsx @@ -0,0 +1,286 @@ +import { useState, useEffect } from "react"; +import type { Meta, StoryObj } from "@storybook/react"; +import { + Command, + CommandDialog, + CommandEmpty, + CommandGroup, + CommandInput, + CommandItem, + CommandList, + CommandSeparator, + CommandShortcut, + Button, +} from "./index"; +import { + CalendarIcon, + SmileIcon, + CalculatorIcon, + UserIcon, + CreditCardIcon, + SettingsIcon, +} from "lucide-react"; + +function CommandDemo() { + return ( + + + + No results found. + + + + Calendar + + + + Search Emoji + + + + Calculator + + + + + + + Profile + ⌘P + + + + Billing + ⌘B + + + + Settings + ⌘S + + + + + ); +} + +function CommandDialogDemo() { + const [open, setOpen] = useState(false); + + useEffect(() => { + const down = (e: KeyboardEvent) => { + if (e.key === "j" && (e.metaKey || e.ctrlKey)) { + e.preventDefault(); + setOpen((prev) => !prev); + } + }; + document.addEventListener("keydown", down); + return () => document.removeEventListener("keydown", down); + }, []); + + return ( + <> +

+ Press{" "} + + J + {" "} + or click the button below. +

+ + + + + No results found. + + + + Calendar + + + + Search Emoji + + + + Calculator + + + + + + + Profile + ⌘P + + + + Billing + ⌘B + + + + Settings + ⌘S + + + + + + ); +} + +function CommandShortcutsDemo() { + return ( + + + + No results found. + + + New File + ⌘N + + + Open File + ⌘O + + + Save + ⌘S + + + Save As... + ⇧⌘S + + + + + + Undo + ⌘Z + + + Redo + ⇧⌘Z + + + Find + ⌘F + + + Replace + ⌘H + + + + + ); +} + +function CommandGroupsDemo() { + return ( + + + + No results found. + + + + John Doe + + + + Jane Smith + + + + Bob Wilson + + + + + + + Schedule Meeting + + + + Make Payment + + + + Open Settings + + + + + + + Calculator + + + + Emoji Picker + + + + + ); +} + +function CommandScrollableDemo() { + const items = Array.from({ length: 30 }, (_, i) => ({ + label: `Item ${i + 1}`, + value: `item-${i + 1}`, + })); + + return ( + + + + No results found. + + {items.map((item) => ( + + {item.label} + + ))} + + + + ); +} + +const meta = { + title: "UI/Command", + component: Command, + parameters: { layout: "centered" }, + tags: ["autodocs"], +} satisfies Meta; + +export default meta; + +type Story = StoryObj; + +export const Default: Story = { + render: () => , +}; + +export const DialogVariant: Story = { + render: () => , +}; + +export const Shortcuts: Story = { + render: () => , +}; + +export const Groups: Story = { + render: () => , +}; + +export const Scrollable: Story = { + render: () => , +}; diff --git a/src/components/ui/Select.stories.tsx b/src/components/ui/Select.stories.tsx index 4281eb5..c003b9c 100644 --- a/src/components/ui/Select.stories.tsx +++ b/src/components/ui/Select.stories.tsx @@ -1,11 +1,17 @@ import type { Meta, StoryObj } from "@storybook/react"; +import { useState } from "react"; import { Select, SelectContent, + SelectGroup, SelectItem, + SelectLabel, + SelectSeparator, SelectTrigger, SelectValue, -} from "./select"; + Label, + Button, +} from "./index"; const meta = { title: "UI/Select", @@ -18,16 +24,216 @@ export default meta; type Story = StoryObj; +// ─── Default ──────────────────────── + export const Default: Story = { render: () => ( + ), +}; + +// ─── With Groups ──────────────────────── + +export const WithGroups: Story = { + render: () => ( + + ), +}; + +// ─── With Label ──────────────────────── + +export const WithLabel: Story = { + render: () => ( +
+ + +
+ ), +}; + +// ─── Small Size ──────────────────────── + +export const SmallSize: Story = { + render: () => ( + + ), +}; + +// ─── Disabled Items ──────────────────────── + +export const DisabledItems: Story = { + render: () => ( + + ), +}; + +// ─── Controlled ──────────────────────── + +function SelectControlledDemo() { + const [value, setValue] = useState("banana"); + + return ( +
+ +

+ Selected: {value} +

+ +
+ ); +} + +export const Controlled: Story = { + render: () => , +}; + +// ─── Disabled ──────────────────────── + +export const Disabled: Story = { + render: () => ( + + ), +}; + +// ─── Scrollable (many items) ──────────────────────── + +export const Scrollable: Story = { + render: () => ( + ), diff --git a/src/components/ui/SmartMultiSelect.stories.tsx b/src/components/ui/SmartMultiSelect.stories.tsx new file mode 100644 index 0000000..010889f --- /dev/null +++ b/src/components/ui/SmartMultiSelect.stories.tsx @@ -0,0 +1,624 @@ +import { useState, useCallback } from "react"; +import type { Meta, StoryObj } from "@storybook/react"; +import { + SmartMultiSelect, + type SmartMultiSelectOption, + type SmartMultiSelectCreateContext, +} from "./smart-multi-select"; +import { Input, Label, Button } from "./index"; +import { UserIcon } from "lucide-react"; + +const allFrameworks: SmartMultiSelectOption[] = [ + { value: "next.js", label: "Next.js" }, + { value: "remix", label: "Remix" }, + { value: "astro", label: "Astro" }, + { value: "nuxt", label: "Nuxt" }, + { value: "sveltekit", label: "SvelteKit" }, + { value: "gatsby", label: "Gatsby" }, + { value: "angular", label: "Angular" }, + { value: "vue", label: "Vue" }, + { value: "react", label: "React" }, + { value: "solid-start", label: "SolidStart" }, +]; + +const meta = { + title: "UI/SmartMultiSelect", + component: SmartMultiSelect, + parameters: { layout: "centered" }, + tags: ["autodocs"], +} satisfies Meta; + +export default meta; + +type Story = StoryObj; + +// ─── Basic (sync mode) ──────────────────────── + +function BasicDemo() { + const [value, setValue] = useState([]); + + return ( + + ); +} + +export const Basic: Story = { + render: () => , +}; + +// ─── With Default Values ──────────────────────── + +function DefaultValueDemo() { + const [value, setValue] = useState(["react", "vue", "next.js"]); + + return ( + + ); +} + +export const WithDefaultValues: Story = { + render: () => , +}; + +// ─── Max Count ──────────────────────── + +function MaxCountDemo() { + const [value, setValue] = useState([ + "react", + "vue", + "next.js", + "remix", + "astro", + ]); + + return ( + + ); +} + +export const MaxCount: Story = { + render: () => , +}; + +// ─── Hide Select All ──────────────────────── + +function HideSelectAllDemo() { + const [value, setValue] = useState([]); + + return ( + + ); +} + +export const HideSelectAll: Story = { + render: () => , +}; + +// ─── Custom Label Function ──────────────────────── + +function CustomLabelDemo() { + const [value, setValue] = useState([]); + + return ( + ( +
+ + {option.label.charAt(0)} + + {option.label} + {isSelected && ( + + selected + + )} +
+ )} + /> + ); +} + +export const CustomLabel: Story = { + render: () => , +}; + +// ─── Async Search ──────────────────────── + +function AsyncSearchDemo() { + const [value, setValue] = useState([]); + const [options, setOptions] = useState([]); + const [loading, setLoading] = useState(false); + + const handleSearch = useCallback((query: string) => { + if (!query) { + setOptions([]); + return; + } + setLoading(true); + setTimeout(() => { + setOptions( + allFrameworks.filter((f) => + f.label.toLowerCase().includes(query.toLowerCase()) + ) + ); + setLoading(false); + }, 500); + }, []); + + return ( + + ); +} + +export const AsyncSearch: Story = { + render: () => , +}; + +// ─── Async with Error ──────────────────────── + +function AsyncErrorDemo() { + const [value, setValue] = useState([]); + + return ( + + ); +} + +export const AsyncError: Story = { + render: () => , +}; + +// ─── Async with Preselected ──────────────────────── + +function AsyncPreselectedDemo() { + const [value, setValue] = useState(["react", "vue"]); + const [options, setOptions] = useState( + allFrameworks + ); + const [loading, setLoading] = useState(false); + + const handleSearch = useCallback((query: string) => { + if (!query) { + setOptions(allFrameworks); + return; + } + setLoading(true); + setTimeout(() => { + setOptions( + allFrameworks.filter((f) => + f.label.toLowerCase().includes(query.toLowerCase()) + ) + ); + setLoading(false); + }, 400); + }, []); + + return ( + + ); +} + +export const AsyncPreselected: Story = { + render: () => , +}; + +// ─── Invalid ──────────────────────── + +function InvalidDemo() { + const [value, setValue] = useState([]); + + return ( + + ); +} + +export const Invalid: Story = { + render: () => , +}; + +// ─── Disabled ──────────────────────── + +export const Disabled: Story = { + render: () => ( + {}} + placeholder="Disabled combobox" + disabled + /> + ), +}; + +// ─── Custom Texts ──────────────────────── + +function CustomTextsDemo() { + const [value, setValue] = useState(["react"]); + + return ( + + ); +} + +export const CustomTexts: Story = { + render: () => , +}; + +// ─── Static (no server request) ──────────────────────── + +function StaticOptionsDemo() { + const [value, setValue] = useState([]); + + return ( + + ); +} + +export const StaticOptions: Story = { + render: () => , +}; + +// ─── Static with few options ──────────────────────── + +const statusOptions: SmartMultiSelectOption[] = [ + { value: "active", label: "Active" }, + { value: "inactive", label: "Inactive" }, + { value: "pending", label: "Pending" }, +]; + +function FewStaticOptionsDemo() { + const [value, setValue] = useState([]); + + return ( + + ); +} + +export const FewStaticOptions: Story = { + render: () => , +}; + +// ─── Disabled Search ──────────────────────── + +function DisabledSearchDemo() { + const [value, setValue] = useState([]); + + return ( + + ); +} + +export const DisabledSearch: Story = { + render: () => , +}; + +// ─── Create with Default Form ──────────────────────── + +function CreateDefaultDemo() { + const [value, setValue] = useState([]); + const [options, setOptions] = useState([ + ...allFrameworks, + ]); + + return ( + { + const newOption = { value: name.toLowerCase(), label: name }; + setOptions((prev) => [...prev, newOption]); + done(newOption.value); + }} + selectOnCreate + /> + ); +} + +export const CreateDefault: Story = { + render: () => , +}; + +// ─── Create with Custom Form ──────────────────────── + +function MultiCreateForm({ + ctx, + onCreated, +}: { + ctx: SmartMultiSelectCreateContext; + onCreated: (option: SmartMultiSelectOption) => void; +}) { + const [name, setName] = useState(ctx.searchValue); + return ( +
+

Create new item

+ setName(e.target.value)} + placeholder="Item name..." + autoFocus + onKeyDown={(e) => e.stopPropagation()} + /> +
+ + +
+
+ ); +} + +function CreateCustomFormDemo() { + const [value, setValue] = useState([]); + const [options, setOptions] = useState([ + ...allFrameworks, + ]); + + const addOption = (opt: SmartMultiSelectOption) => { + setOptions((prev) => [...prev, opt]); + setValue((prev) => [...prev, opt.value]); + }; + + return ( + ( + + )} + /> + ); +} + +export const CreateCustomForm: Story = { + render: () => , +}; + +// ─── Create with Complex Form ──────────────────────── + +function MultiComplexCreateForm({ + ctx, + onCreated, +}: { + ctx: SmartMultiSelectCreateContext; + onCreated: (option: SmartMultiSelectOption) => void; +}) { + const [name, setName] = useState(ctx.searchValue); + const [description, setDescription] = useState(""); + return ( +
+

Create new framework

+
+ + setName(e.target.value)} + placeholder="Framework name..." + autoFocus + onKeyDown={(e) => e.stopPropagation()} + /> +
+
+ + setDescription(e.target.value)} + placeholder="Short description..." + onKeyDown={(e) => e.stopPropagation()} + /> +
+
+ + +
+
+ ); +} + +function CreateComplexFormDemo() { + const [value, setValue] = useState([]); + const [options, setOptions] = useState([ + ...allFrameworks, + ]); + + const addOption = (opt: SmartMultiSelectOption) => { + setOptions((prev) => [...prev, opt]); + setValue((prev) => [...prev, opt.value]); + }; + + return ( + ( + + )} + /> + ); +} + +export const CreateComplexForm: Story = { + render: () => , +}; + +// ─── With Start Icon ──────────────────────── + +function WithStartIconDemo() { + const [value, setValue] = useState([]); + + return ( + } + /> + ); +} + +export const WithStartIcon: Story = { + render: () => , +}; + +// ─── No Chevron ──────────────────────── + +function NoChevronDemo() { + const [value, setValue] = useState([]); + + return ( + + ); +} + +export const NoChevron: Story = { + render: () => , +}; + +// ─── Start Icon + No End Icon ──────────────────────── + +function StartIconNoEndDemo() { + const [value, setValue] = useState([]); + + return ( + } + endIcon={null} + /> + ); +} + +export const StartIconNoEndIcon: Story = { + render: () => , +}; diff --git a/src/components/ui/SmartSelect.stories.tsx b/src/components/ui/SmartSelect.stories.tsx new file mode 100644 index 0000000..2209568 --- /dev/null +++ b/src/components/ui/SmartSelect.stories.tsx @@ -0,0 +1,812 @@ +import { useState, useCallback } from "react"; +import type { Meta, StoryObj } from "@storybook/react"; +import { + SmartSelect, + type SmartSelectOption, + type SmartSelectCreateContext, +} from "./smart-select"; +import { Input, Label, Button } from "./index"; +import { SearchIcon, UserIcon } from "lucide-react"; + +const allFrameworks: SmartSelectOption[] = [ + { value: "next.js", label: "Next.js" }, + { value: "remix", label: "Remix" }, + { value: "astro", label: "Astro" }, + { value: "nuxt", label: "Nuxt" }, + { value: "sveltekit", label: "SvelteKit" }, + { value: "gatsby", label: "Gatsby" }, + { value: "angular", label: "Angular" }, + { value: "vue", label: "Vue" }, + { value: "react", label: "React" }, + { value: "solid-start", label: "SolidStart" }, +]; + +function useAsyncSearch( + data: SmartSelectOption[], + delay = 500 +) { + const [options, setOptions] = useState([]); + const [loading, setLoading] = useState(false); + + const handleSearch = useCallback( + (query: string) => { + if (!query) { + setOptions([]); + return; + } + setLoading(true); + setTimeout(() => { + setOptions( + data.filter((f) => + f.label.toLowerCase().includes(query.toLowerCase()) + ) + ); + setLoading(false); + }, delay); + }, + [data, delay] + ); + + return { options, loading, handleSearch }; +} + +const meta = { + title: "UI/SmartSelect", + component: SmartSelect, + parameters: { layout: "centered" }, + tags: ["autodocs"], +} satisfies Meta; + +export default meta; + +type Story = StoryObj; + +// ─── Basic ──────────────────────── + +function SmartSelectBasicDemo() { + const [value, setValue] = useState(""); + const { options, loading, handleSearch } = useAsyncSearch(allFrameworks); + + return ( + + ); +} + +export const Basic: Story = { + render: () => , +}; + +// ─── Clear Button ──────────────────────── + +function SmartSelectClearDemo() { + const [value, setValue] = useState(""); + const { options, loading, handleSearch } = useAsyncSearch(allFrameworks); + + return ( + + ); +} + +export const ClearButton: Story = { + render: () => , +}; + +// ─── Custom Items (with description) ──────────────────────── + +const frameworksWithDesc: SmartSelectOption[] = [ + { value: "next.js", label: "Next.js", description: "The React Framework" }, + { value: "remix", label: "Remix", description: "Build better websites" }, + { value: "astro", label: "Astro", description: "The web framework for content" }, + { value: "nuxt", label: "Nuxt", description: "The Intuitive Vue Framework" }, + { value: "sveltekit", label: "SvelteKit", description: "Web development, streamlined" }, +]; + +function SmartSelectCustomItemsDemo() { + const [value, setValue] = useState(""); + const { options, loading, handleSearch } = useAsyncSearch(frameworksWithDesc); + + return ( + + ); +} + +export const CustomItems: Story = { + render: () => , +}; + +// ─── Custom Render ──────────────────────── + +function SmartSelectCustomRenderDemo() { + const [value, setValue] = useState(""); + const { options, loading, handleSearch } = useAsyncSearch(frameworksWithDesc); + + return ( + ( +
+
+ + {option.label.charAt(0)} + +
+ {option.label} + {option.description && ( + + {option.description} + + )} +
+
+ {isSelected && ( + Selected + )} +
+ )} + /> + ); +} + +export const CustomRender: Story = { + render: () => , +}; + +// ─── Groups ──────────────────────── + +const groupedFrameworks: SmartSelectOption[] = [ + { value: "next.js", label: "Next.js", group: "React" }, + { value: "remix", label: "Remix", group: "React" }, + { value: "gatsby", label: "Gatsby", group: "React" }, + { value: "nuxt", label: "Nuxt", group: "Vue" }, + { value: "sveltekit", label: "SvelteKit", group: "Svelte" }, + { value: "astro", label: "Astro", group: "Multi-framework" }, + { value: "angular", label: "Angular", group: "Standalone" }, +]; + +function SmartSelectGroupsDemo() { + const [value, setValue] = useState(""); + const { options, loading, handleSearch } = useAsyncSearch(groupedFrameworks); + + return ( + + ); +} + +export const Groups: Story = { + render: () => , +}; + +// ─── Custom Messages ──────────────────────── + +function SmartSelectCustomMessagesDemo() { + const [value, setValue] = useState(""); + const { options, loading, handleSearch } = useAsyncSearch(allFrameworks, 800); + + return ( + + ); +} + +export const CustomMessages: Story = { + render: () => , +}; + +// ─── User Search (with disabled items) ──────────────────────── + +function SmartSelectUsersDemo() { + const [value, setValue] = useState(""); + const [options, setOptions] = useState([]); + const [loading, setLoading] = useState(false); + + const handleSearch = useCallback((query: string) => { + if (!query) { + setOptions([]); + return; + } + setLoading(true); + setTimeout(() => { + setOptions([ + { value: "user-1", label: `${query} Smith` }, + { value: "user-2", label: `${query} Johnson` }, + { value: "user-3", label: `${query} Williams` }, + { + value: "user-4", + label: `${query} Brown (inactive)`, + disabled: true, + }, + ]); + setLoading(false); + }, 600); + }, []); + + return ( + + ); +} + +export const UserSearch: Story = { + render: () => , +}; + +// ─── Invalid ──────────────────────── + +function SmartSelectInvalidDemo() { + const [value, setValue] = useState(""); + const { options, loading, handleSearch } = useAsyncSearch(allFrameworks); + + return ( + + ); +} + +export const Invalid: Story = { + render: () => , +}; + +// ─── Disabled ──────────────────────── + +export const Disabled: Story = { + render: () => ( + {}} + options={[]} + disabled + placeholder="Disabled combobox" + /> + ), +}; + +// ─── Preselected ──────────────────────── + +function SmartSelectPreselectedDemo() { + const [value, setValue] = useState("react"); + const [options, setOptions] = useState(allFrameworks); + const [loading, setLoading] = useState(false); + + const handleSearch = useCallback((query: string) => { + if (!query) { + setOptions(allFrameworks); + return; + } + setLoading(true); + setTimeout(() => { + setOptions( + allFrameworks.filter((f) => + f.label.toLowerCase().includes(query.toLowerCase()) + ) + ); + setLoading(false); + }, 300); + }, []); + + return ( + + ); +} + +export const Preselected: Story = { + render: () => , +}; + +// ─── Static (no server request) ──────────────────────── + +const staticOptions: SmartSelectOption[] = [ + { value: "active", label: "Active" }, + { value: "inactive", label: "Inactive" }, +]; + +function SmartSelectStaticDemo() { + const [value, setValue] = useState(""); + const [options, setOptions] = useState(allFrameworks); + + return ( + { + if (!query) { + setOptions(allFrameworks); + return; + } + setOptions( + allFrameworks.filter((f) => + f.label.toLowerCase().includes(query.toLowerCase()) + ) + ); + }} + options={options} + value={value} + onValueChange={setValue} + placeholder="Select framework..." + idleMessage="All frameworks" + showClear + /> + ); +} + +export const StaticOptions: Story = { + render: () => , +}; + +// ─── Static with few options ──────────────────────── + +function SmartSelectFewOptionsDemo() { + const [value, setValue] = useState(""); + + return ( + {}} + options={staticOptions} + value={value} + onValueChange={setValue} + placeholder="Select status..." + idleMessage="Choose a status" + showClear + className="w-[180px]" + + /> + ); +} + +export const FewStaticOptions: Story = { + render: () => , +}; + +// ─── Disabled Search ──────────────────────── + +function SmartSelectNoSearchDemo() { + const [value, setValue] = useState(""); + + return ( + {}} + options={[...staticOptions]} + value={value} + onValueChange={setValue} + placeholder="Select status..." + idleMessage="Choose a status" + disableSearch + className="w-[180px]" + + /> + ); +} + +export const DisabledSearch: Story = { + render: () => , +}; + +// ─── Create with Default Form ──────────────────────── + +function SmartSelectCreateDefaultDemo() { + const [value, setValue] = useState(""); + const [options, setOptions] = useState([...allFrameworks]); + const [filteredOptions, setFilteredOptions] = useState([ + ...allFrameworks, + ]); + + return ( + { + if (!query) { + setFilteredOptions(options); + return; + } + setFilteredOptions( + options.filter((f) => + f.label.toLowerCase().includes(query.toLowerCase()) + ) + ); + }} + options={filteredOptions} + value={value} + onValueChange={setValue} + placeholder="Select or create..." + idleMessage="All frameworks" + showClear + className="w-[280px]" + + onCreate={(name, done) => { + const newOption = { value: name.toLowerCase(), label: name }; + setOptions((prev) => { + const updated = [...prev, newOption]; + setFilteredOptions(updated); + return updated; + }); + done(newOption.value); + }} + selectOnCreate + /> + ); +} + +export const CreateDefault: Story = { + render: () => , +}; + +// ─── Create with Custom Form (simple) ──────────────────────── + +function SimpleCreateForm({ + ctx, + onCreated, +}: { + ctx: SmartSelectCreateContext; + onCreated: (option: SmartSelectOption) => void; +}) { + const [name, setName] = useState(ctx.searchValue); + return ( +
+

Create new framework

+ setName(e.target.value)} + placeholder="Framework name..." + autoFocus + onKeyDown={(e) => e.stopPropagation()} + /> +
+ + +
+
+ ); +} + +function SmartSelectCreateCustomSimpleDemo() { + const [value, setValue] = useState(""); + const [options, setOptions] = useState([...allFrameworks]); + const [filteredOptions, setFilteredOptions] = useState([ + ...allFrameworks, + ]); + + const addOption = (opt: SmartSelectOption) => { + setOptions((prev) => { + const updated = [...prev, opt]; + setFilteredOptions(updated); + return updated; + }); + setValue(opt.value); + }; + + return ( + { + if (!query) { + setFilteredOptions(options); + return; + } + setFilteredOptions( + options.filter((f) => + f.label.toLowerCase().includes(query.toLowerCase()) + ) + ); + }} + options={filteredOptions} + value={value} + onValueChange={setValue} + placeholder="Select or create..." + idleMessage="All frameworks" + showClear + className="w-[300px]" + + renderCreateForm={(ctx) => ( + + )} + /> + ); +} + +export const CreateCustomSimple: Story = { + render: () => , +}; + +// ─── Create with Custom Form (complex) ──────────────────────── + +function ComplexCreateForm({ + ctx, + onCreated, +}: { + ctx: SmartSelectCreateContext; + onCreated: (option: SmartSelectOption) => void; +}) { + const [name, setName] = useState(ctx.searchValue); + const [description, setDescription] = useState(""); + const [group, setGroup] = useState(""); + return ( +
+

Create new framework

+
+ + setName(e.target.value)} + placeholder="Framework name..." + autoFocus + onKeyDown={(e) => e.stopPropagation()} + /> +
+
+ + setDescription(e.target.value)} + placeholder="Short description..." + onKeyDown={(e) => e.stopPropagation()} + /> +
+
+ + setGroup(e.target.value)} + placeholder="e.g. React, Vue..." + onKeyDown={(e) => e.stopPropagation()} + /> +
+
+ + +
+
+ ); +} + +function SmartSelectCreateCustomComplexDemo() { + const [value, setValue] = useState(""); + const [options, setOptions] = useState([...allFrameworks]); + const [filteredOptions, setFilteredOptions] = useState([ + ...allFrameworks, + ]); + + const addOption = (opt: SmartSelectOption) => { + setOptions((prev) => { + const updated = [...prev, opt]; + setFilteredOptions(updated); + return updated; + }); + setValue(opt.value); + }; + + return ( + { + if (!query) { + setFilteredOptions(options); + return; + } + setFilteredOptions( + options.filter((f) => + f.label.toLowerCase().includes(query.toLowerCase()) + ) + ); + }} + options={filteredOptions} + value={value} + onValueChange={setValue} + placeholder="Select or create..." + idleMessage="All frameworks" + showClear + className="w-[350px]" + + renderCreateForm={(ctx) => ( + + )} + /> + ); +} + +export const CreateCustomComplex: Story = { + render: () => , +}; + +// ─── With Start Icon ──────────────────────── + +function SmartSelectStartIconDemo() { + const [value, setValue] = useState(""); + const [options, setOptions] = useState(allFrameworks); + + return ( + { + if (!query) { setOptions(allFrameworks); return; } + setOptions(allFrameworks.filter((f) => f.label.toLowerCase().includes(query.toLowerCase()))); + }} + options={options} + value={value} + onValueChange={setValue} + placeholder="Guest Customer" + idleMessage="All customers" + startIcon={} + showClear + /> + ); +} + +export const WithStartIcon: Story = { + render: () => , +}; + +// ─── Custom End Icon ──────────────────────── + +function SmartSelectCustomEndIconDemo() { + const [value, setValue] = useState(""); + const [options, setOptions] = useState(allFrameworks); + + return ( + { + if (!query) { setOptions(allFrameworks); return; } + setOptions(allFrameworks.filter((f) => f.label.toLowerCase().includes(query.toLowerCase()))); + }} + options={options} + value={value} + onValueChange={setValue} + placeholder="Search..." + idleMessage="All frameworks" + endIcon={} + /> + ); +} + +export const CustomEndIcon: Story = { + render: () => , +}; + +// ─── No Chevron ──────────────────────── + +function SmartSelectNoChevronDemo() { + const [value, setValue] = useState(""); + const [options, setOptions] = useState(allFrameworks); + + return ( + { + if (!query) { setOptions(allFrameworks); return; } + setOptions(allFrameworks.filter((f) => f.label.toLowerCase().includes(query.toLowerCase()))); + }} + options={options} + value={value} + onValueChange={setValue} + placeholder="Select framework..." + idleMessage="All frameworks" + showChevron={false} + showClear + /> + ); +} + +export const NoChevron: Story = { + render: () => , +}; + +// ─── Start Icon + No End Icon ──────────────────────── + +function SmartSelectStartIconNoEndDemo() { + const [value, setValue] = useState(""); + const [options, setOptions] = useState(allFrameworks); + + return ( + { + if (!query) { setOptions(allFrameworks); return; } + setOptions(allFrameworks.filter((f) => f.label.toLowerCase().includes(query.toLowerCase()))); + }} + options={options} + value={value} + onValueChange={setValue} + placeholder="Guest Customer" + idleMessage="All customers" + startIcon={} + endIcon={null} + showClear + /> + ); +} + +export const StartIconNoEndIcon: Story = { + render: () => , +}; diff --git a/src/components/ui/combobox.tsx b/src/components/ui/combobox.tsx index b3b72e7..7d86eca 100644 --- a/src/components/ui/combobox.tsx +++ b/src/components/ui/combobox.tsx @@ -79,7 +79,7 @@ const ComboboxInput = React.forwardRef<
} {...props} /> - {showTrigger && } + {showTrigger && } {showClear && } {children}
@@ -237,13 +237,14 @@ function ComboboxSeparator({ ); } -function ComboboxChips({ - className, - ...props -}: React.ComponentPropsWithRef & - ComboboxPrimitive.Chips.Props) { +const ComboboxChips = React.forwardRef< + HTMLDivElement, + React.ComponentPropsWithRef & + ComboboxPrimitive.Chips.Props +>(({ className, ...props }, ref) => { return ( ); -} +}); +ComboboxChips.displayName = "ComboboxChips"; function ComboboxChip({ className, diff --git a/src/components/ui/command.tsx b/src/components/ui/command.tsx new file mode 100644 index 0000000..4544808 --- /dev/null +++ b/src/components/ui/command.tsx @@ -0,0 +1,181 @@ +"use client" + +import * as React from "react" +import { Command as CommandPrimitive } from "cmdk" +import { SearchIcon } from "lucide-react" + +import { cn } from "@/lib/utils" +import { + Dialog, + DialogContent, + DialogDescription, + DialogHeader, + DialogTitle, +} from "@/components/ui/dialog" + +function Command({ className, ...props }: React.ComponentProps) { + return ( + + ) +} + +function CommandDialog({ + title = "Command Palette", + description = "Search for a command to run...", + children, + showCloseButton = true, + ...props +}: Omit, "children"> & { + title?: string + description?: string + showCloseButton?: boolean + children?: React.ReactNode +}) { + return ( + + + + {title} + {description} + + + {children} + + + + ) +} + +function CommandInput({ + className, + ...props +}: React.ComponentProps) { + return ( +
+ + +
+ ) +} + +function CommandList({ + className, + ...props +}: React.ComponentProps) { + return ( + + ) +} + +function CommandEmpty({ + className, + ...props +}: React.ComponentProps) { + return ( + + ) +} + +function CommandGroup({ + className, + ...props +}: React.ComponentProps) { + return ( + + ) +} + +function CommandSeparator({ + className, + ...props +}: React.ComponentProps) { + return ( + + ) +} + +function CommandItem({ + className, + ...props +}: React.ComponentProps) { + return ( + + ) +} + +function CommandShortcut({ + className, + ...props +}: React.ComponentProps<"span">) { + return ( + + ) +} + +export { + Command, + CommandDialog, + CommandEmpty, + CommandGroup, + CommandInput, + CommandItem, + CommandList, + CommandSeparator, + CommandShortcut, +} diff --git a/src/components/ui/index.ts b/src/components/ui/index.ts index 722eb83..cee164b 100644 --- a/src/components/ui/index.ts +++ b/src/components/ui/index.ts @@ -96,6 +96,36 @@ export { SelectValue, } from "./select"; +// SmartMultiSelect component +export { + SmartMultiSelect, + type SmartMultiSelectOption, + type SmartMultiSelectProps, + type SmartMultiSelectRef, + type SmartMultiSelectCreateContext, +} from "./smart-multi-select"; + +// SmartSelect component +export { + SmartSelect, + type SmartSelectOption, + type SmartSelectProps, + type SmartSelectCreateContext, +} from "./smart-select"; + +// Command component (cmdk) +export { + Command, + CommandDialog, + CommandEmpty, + CommandGroup, + CommandInput, + CommandItem, + CommandList, + CommandSeparator, + CommandShortcut, +} from "./command"; + // Combobox component export { CombineInput, type CombineInputProps } from "./combine-input"; export { diff --git a/src/components/ui/popover.tsx b/src/components/ui/popover.tsx index e1f107f..cedaace 100644 --- a/src/components/ui/popover.tsx +++ b/src/components/ui/popover.tsx @@ -43,7 +43,7 @@ function PopoverContent({ void + /** Close the popover */ + close: () => void +} + +export interface SmartMultiSelectProps { + /** An array of options to display */ + options: SmartMultiSelectOption[] + /** Whether the select is async. If true, filtering is handled externally */ + async?: boolean + /** Whether options are currently loading. Works only when async is true */ + loading?: boolean + /** Error object. If set, the error message is shown. Works only when async is true */ + error?: Error | null + /** The default selected values when the component mounts */ + defaultValue?: string[] + /** The selected values (controlled) */ + value?: string[] + /** Placeholder text when no values are selected */ + placeholder?: string + /** Placeholder text for the search input */ + searchPlaceholder?: string + /** Maximum number of badge items to display. Extra items are summarized */ + maxCount?: number + /** Additional class names for the trigger */ + className?: string + /** Additional class names for the popover content */ + contentClassName?: string + /** Text for the clear button */ + clearText?: string + /** Text for the close button */ + closeText?: string + /** Whether to hide the select all option */ + hideSelectAll?: boolean + /** Whether to clear search input when popover closes */ + clearSearchOnClose?: boolean + /** Controlled search value */ + searchValue?: string + /** Whether the combobox is disabled */ + disabled?: boolean + /** Mark combobox as invalid */ + invalid?: boolean + /** Whether to disable the search input */ + disableSearch?: boolean + /** Custom label function for rendering each option */ + labelFunc?: ( + option: SmartMultiSelectOption, + isSelected: boolean, + index: number + ) => React.ReactNode + /** Callback when selected values change */ + onValueChange: (value: string[]) => void + /** Callback when search input changes */ + onSearch?: (value: string) => void + /** + * Render function for the create form. Receives a context with searchValue, + * clearSearch, and close helpers. Return any React node. + */ + renderCreateForm?: (ctx: SmartMultiSelectCreateContext) => React.ReactNode + /** Whether to auto-select the newly created item @default false */ + selectOnCreate?: boolean + /** + * Callback fired after a new item is created via the default create form. + * Receives the name and a done callback to call when finished. + */ + onCreate?: (name: string, done: (createdValue?: string) => void) => void + /** Text for the default create button @default "Create" */ + createButtonText?: string + /** Icon to show at the start of the trigger (e.g. a person icon) */ + startIcon?: React.ReactNode + /** Custom icon to replace the default chevron. Set to `null` to hide */ + endIcon?: React.ReactNode + /** Whether to show the chevron arrow @default true */ + showChevron?: boolean +} + +export interface SmartMultiSelectRef { + /** Programmatically control the popover open/close state */ + setIsPopoverOpen: (open: boolean) => void + /** Programmatically set the search input value */ + setSearchValue: (value: string) => void +} + +const SmartMultiSelect = React.forwardRef< + SmartMultiSelectRef, + SmartMultiSelectProps +>( + ( + { + options, + async = false, + loading = false, + error = null, + defaultValue = [], + value, + placeholder = "Select...", + searchPlaceholder = "Search...", + maxCount = 3, + className, + contentClassName, + clearText = "Clear", + closeText = "Close", + hideSelectAll = false, + clearSearchOnClose = false, + searchValue, + disabled = false, + invalid = false, + disableSearch = false, + labelFunc, + onValueChange, + onSearch, + renderCreateForm, + selectOnCreate = false, + onCreate, + createButtonText = "Create", + startIcon, + endIcon, + showChevron = true, + }, + ref + ) => { + const [selectedValues, setSelectedValues] = + React.useState(defaultValue) + const [isPopoverOpen, setIsPopoverOpen] = React.useState(false) + const [searchValueState, setSearchValueState] = React.useState( + searchValue || "" + ) + const [showCreate, setShowCreate] = React.useState(false) + const [reserveOptions, setReserveOptions] = React.useState< + Record + >({}) + const optionsRef = React.useRef>({}) + const isInit = React.useRef(false) + + const hasCreateCapability = !!(renderCreateForm || onCreate) + + const handleInputKeyDown = ( + event: React.KeyboardEvent + ) => { + if (event.key === "Enter") { + setIsPopoverOpen(true) + } else if (event.key === "Backspace" && !event.currentTarget.value) { + const newSelectedValues = [...selectedValues] + newSelectedValues.pop() + setSelectedValues(newSelectedValues) + onValueChange(newSelectedValues) + } + } + + const toggleOption = (optionValue: string) => { + const isSelected = selectedValues.includes(optionValue) + const newSelectedValues = isSelected + ? selectedValues.filter((v) => v !== optionValue) + : [...selectedValues, optionValue] + setSelectedValues(newSelectedValues) + onValueChange(newSelectedValues) + } + + const handleClear = () => { + setSelectedValues([]) + onValueChange([]) + } + + const clearExtraOptions = () => { + const newSelectedValues = selectedValues.slice(0, maxCount) + setSelectedValues(newSelectedValues) + onValueChange(newSelectedValues) + } + + const toggleAll = () => { + if (selectedValues.length === options.length) { + handleClear() + } else { + const allValues = options.map((option) => option.value) + setSelectedValues(allValues) + onValueChange(allValues) + } + } + + const clearSearch = React.useCallback(() => { + setSearchValueState("") + setShowCreate(false) + }, []) + + const createCtx: SmartMultiSelectCreateContext = React.useMemo( + () => ({ + searchValue: searchValueState, + clearSearch, + close: () => setIsPopoverOpen(false), + }), + [searchValueState, clearSearch] + ) + + const handleDefaultCreate = React.useCallback(() => { + if (!onCreate || !searchValueState.trim()) return + onCreate(searchValueState.trim(), (createdValue?: string) => { + if (selectOnCreate && createdValue) { + const newSelected = [...selectedValues, createdValue] + setSelectedValues(newSelected) + onValueChange(newSelected) + } + clearSearch() + }) + }, [onCreate, searchValueState, selectOnCreate, selectedValues, onValueChange, clearSearch]) + + const showCreateForm = + hasCreateCapability && + (showCreate || (searchValueState && !loading && options.length === 0)) + + // Cache options for async mode + React.useEffect(() => { + const temp = options.reduce( + (acc, option) => { + acc[option.value] = option + return acc + }, + {} as Record + ) + if (async) { + if (!isInit.current) { + optionsRef.current = temp + setReserveOptions(temp) + isInit.current = true + } else { + const selectedCache = selectedValues.reduce( + (acc, val) => { + const option = optionsRef.current[val] + if (option) { + acc[option.value] = option + } + return acc + }, + {} as Record + ) + optionsRef.current = { ...temp, ...selectedCache } + setReserveOptions({ ...temp, ...selectedCache }) + } + } + }, [async, options, selectedValues]) + + React.useEffect(() => { + if (value) { + setSelectedValues(value) + } + }, [value]) + + React.useEffect(() => { + if (searchValue !== undefined) { + setSearchValueState(searchValue) + } + }, [searchValue]) + + React.useImperativeHandle(ref, () => ({ + setIsPopoverOpen, + setSearchValue: setSearchValueState, + })) + + const getOptionLabel = (val: string) => { + if (async) { + return reserveOptions[val]?.label ?? val + } + return options.find((o) => o.value === val)?.label ?? val + } + + const renderDefaultCreateForm = () => ( +
+
+ setSearchValueState(e.target.value)} + placeholder="Enter name..." + autoFocus + onKeyDown={(e) => { + if (e.key === "Enter") { + e.preventDefault() + handleDefaultCreate() + } + e.stopPropagation() + }} + /> + +
+
+ ) + + return ( + { + if (disabled) return + setIsPopoverOpen(open) + if (!open) { + setShowCreate(false) + if (clearSearchOnClose) { + setSearchValueState("") + onSearch?.("") + } + } + }} + > + + } + > + {selectedValues.length > 0 ? ( +
+ {startIcon && ( + {startIcon} + )} +
+ {selectedValues.slice(0, maxCount).map((val) => ( + + {getOptionLabel(val)} + { + e.stopPropagation() + e.preventDefault() + toggleOption(val) + }} + > + + + + ))} + {selectedValues.length > maxCount && ( + + {`+${selectedValues.length - maxCount}`} + { + e.stopPropagation() + e.preventDefault() + clearExtraOptions() + }} + > + + + + )} +
+
+ { + e.stopPropagation() + e.preventDefault() + handleClear() + }} + > + + + {(showChevron || endIcon !== undefined) && ( + <> +
+ {endIcon !== undefined + ? endIcon && {endIcon} + : showChevron && + } + + )} +
+
+ ) : ( +
+ {startIcon && ( + {startIcon} + )} + + {placeholder} + + {endIcon !== undefined + ? endIcon && {endIcon} + : showChevron && + } +
+ )} + + + + {!disableSearch && ( +
+ { + setSearchValueState(val) + setShowCreate(false) + onSearch?.(val) + }} + onKeyDown={handleInputKeyDown} + /> + {hasCreateCapability && ( + + )} +
+ )} + + {!showCreateForm && ( + <> + {async && error && ( +
+ {error.message} +
+ )} + {async && loading && options.length === 0 && ( +
+ + + Searching... + +
+ )} + {async ? ( + !loading && + !error && + options.length === 0 && + !hasCreateCapability && ( +
+ No results found. +
+ ) + ) : ( + No results found. + )} + + {!async && !hideSelectAll && options.length > 0 && ( + +
+ +
+ Select all +
+ )} + {options.map((option, index) => { + const isSelected = selectedValues.includes(option.value) + return ( + toggleOption(option.value)} + className="cursor-pointer" + > +
+ +
+ {labelFunc + ? labelFunc(option, isSelected, index) + : option.label} +
+ ) + })} +
+ {options.length > 0 && } + +
+ {selectedValues.length > 0 && ( + <> + + {clearText} + +
+ + )} + setIsPopoverOpen(false)} + className="flex-1 justify-center cursor-pointer max-w-full" + > + {closeText} + +
+ + + )} + + {showCreateForm && ( + renderCreateForm + ? renderCreateForm(createCtx) + : renderDefaultCreateForm() + )} + + + + ) + } +) + +SmartMultiSelect.displayName = "SmartMultiSelect" + +export { SmartMultiSelect } diff --git a/src/components/ui/smart-select.tsx b/src/components/ui/smart-select.tsx new file mode 100644 index 0000000..cfeaeb9 --- /dev/null +++ b/src/components/ui/smart-select.tsx @@ -0,0 +1,431 @@ +"use client" + +import * as React from "react" +import { CheckIcon, ChevronDownIcon, Loader2Icon, PlusIcon, XIcon } from "lucide-react" +import { Button } from "@/components/ui/button" +import { + Command, + CommandEmpty, + CommandGroup, + CommandInput, + CommandItem, + CommandList, + CommandSeparator, +} from "@/components/ui/command" +import { + Popover, + PopoverContent, + PopoverTrigger, +} from "@/components/ui/popover" +import { cn } from "@/lib/utils" + +export interface SmartSelectOption { + /** Unique value for the option */ + value: string + /** Display label */ + label: string + /** Optional description shown below label */ + description?: string + /** Whether the option is disabled */ + disabled?: boolean + /** Optional group name for grouping options */ + group?: string +} + +export interface SmartSelectCreateContext { + /** Current search/input value */ + searchValue: string + /** Clear the search input and close the create form */ + clearSearch: () => void + /** Close the popover */ + close: () => void +} + +export interface SmartSelectProps { + /** Callback triggered on search input change (after debounce). Optional for static options */ + onSearch?: (query: string) => void | Promise + /** Options to display in the dropdown */ + options: SmartSelectOption[] + /** Currently selected value */ + value?: string + /** Callback when selection changes */ + onValueChange?: (value: string) => void + /** Whether results are currently loading */ + loading?: boolean + /** Placeholder text for the trigger button */ + placeholder?: string + /** Placeholder text for the search input */ + searchPlaceholder?: string + /** Message shown when no results are found */ + emptyMessage?: string + /** Message shown before the user starts typing */ + idleMessage?: string + /** Whether to show a clear button when a value is selected */ + showClear?: boolean + /** Whether the combobox is disabled */ + disabled?: boolean + /** Mark combobox as invalid */ + invalid?: boolean + /** Whether to disable the search input */ + disableSearch?: boolean + /** Debounce delay in milliseconds @default 300 */ + debounceMs?: number + /** Additional class for the trigger button */ + className?: string + /** Width class for the popover content @default "w-[250px]" */ + contentClassName?: string + /** Custom render function for each option */ + renderOption?: (option: SmartSelectOption, isSelected: boolean) => React.ReactNode + /** + * Render function for the create form. Receives a context with searchValue, + * clearSearch, and close helpers. Return any React node — a simple input or + * a complex form. The form is shown when no results match or when the user + * clicks the create button. + */ + renderCreateForm?: (ctx: SmartSelectCreateContext) => React.ReactNode + /** Whether to auto-select the newly created item @default false */ + selectOnCreate?: boolean + /** + * Callback fired after a new item is created via the default create form. + * If you use `renderCreateForm`, you handle creation yourself. + * Receives the name and a done callback to call when finished. + */ + onCreate?: (name: string, done: (createdValue?: string) => void) => void + /** Text for the default create button @default "Create" */ + createButtonText?: string + /** Icon to show at the start of the trigger (e.g. a person icon) */ + startIcon?: React.ReactNode + /** Custom icon to replace the default chevron. Set to `null` to hide */ + endIcon?: React.ReactNode + /** Whether to show the chevron arrow @default true */ + showChevron?: boolean +} + +function SmartSelect({ + onSearch, + options, + value = "", + onValueChange, + loading = false, + placeholder = "Select an option...", + searchPlaceholder = "Type to search...", + emptyMessage = "No results found.", + idleMessage = "Start typing to search", + showClear = false, + disabled = false, + invalid = false, + disableSearch = false, + debounceMs = 300, + className, + contentClassName, + renderOption, + renderCreateForm, + selectOnCreate = false, + onCreate, + createButtonText = "Create", + startIcon, + endIcon, + showChevron = true, +}: SmartSelectProps) { + const [open, setOpen] = React.useState(false) + const [search, setSearch] = React.useState("") + const [showCreate, setShowCreate] = React.useState(false) + const timerRef = React.useRef | null>(null) + + const selectedOption = React.useMemo( + () => options.find((opt) => opt.value === value), + [options, value] + ) + + const handleSearchChange = React.useCallback( + (query: string) => { + setSearch(query) + setShowCreate(false) + + if (timerRef.current) { + clearTimeout(timerRef.current) + } + + timerRef.current = setTimeout(() => { + onSearch?.(query) + }, debounceMs) + }, + [onSearch, debounceMs] + ) + + React.useEffect(() => { + return () => { + if (timerRef.current) { + clearTimeout(timerRef.current) + } + } + }, []) + + React.useEffect(() => { + if (!open) { + setSearch("") + setShowCreate(false) + } + }, [open]) + + const groupedOptions = React.useMemo(() => { + const groups = new Map() + const ungrouped: SmartSelectOption[] = [] + + for (const opt of options) { + if (opt.group) { + const group = groups.get(opt.group) || [] + group.push(opt) + groups.set(opt.group, group) + } else { + ungrouped.push(opt) + } + } + + return { groups, ungrouped } + }, [options]) + + const handleClear = React.useCallback( + (e: React.MouseEvent) => { + e.stopPropagation() + onValueChange?.("") + }, + [onValueChange] + ) + + const clearSearch = React.useCallback(() => { + setSearch("") + setShowCreate(false) + }, []) + + const createCtx: SmartSelectCreateContext = React.useMemo( + () => ({ + searchValue: search, + clearSearch, + close: () => setOpen(false), + }), + [search, clearSearch] + ) + + const handleDefaultCreate = React.useCallback(() => { + if (!onCreate || !search.trim()) return + onCreate(search.trim(), (createdValue?: string) => { + if (selectOnCreate && createdValue) { + onValueChange?.(createdValue) + } + clearSearch() + setShowCreate(false) + }) + }, [onCreate, search, selectOnCreate, onValueChange, clearSearch]) + + const hasCreateCapability = !!(renderCreateForm || onCreate) + const showCreateForm = + hasCreateCapability && + (showCreate || (search && !loading && options.length === 0)) + + const renderItems = (items: SmartSelectOption[]) => + items.map((option) => { + const isSelected = value === option.value + return ( + { + onValueChange?.(currentValue === value ? "" : currentValue) + setOpen(false) + }} + > + {renderOption ? ( + renderOption(option, isSelected) + ) : ( + <> + {option.description ? ( +
+ {option.label} + + {option.description} + +
+ ) : ( + option.label + )} + + + )} +
+ ) + }) + + const renderGroupedContent = () => { + const { groups, ungrouped } = groupedOptions + const entries = Array.from(groups.entries()) + + return ( + <> + {ungrouped.length > 0 && ( + {renderItems(ungrouped)} + )} + {entries.map(([groupName, items], index) => ( + + {(index > 0 || ungrouped.length > 0) && } + + {renderItems(items)} + + + ))} + + ) + } + + const renderDefaultCreateForm = () => ( +
+
+ setSearch(e.target.value)} + placeholder="Enter name..." + autoFocus + onKeyDown={(e) => { + if (e.key === "Enter") { + e.preventDefault() + handleDefaultCreate() + } + e.stopPropagation() + }} + /> + +
+
+ ) + + return ( + + + } + > + {startIcon && ( + {startIcon} + )} + + {selectedOption?.label || value || placeholder} + +
+ {showClear && value && ( + { + if (e.key === "Enter" || e.key === " ") handleClear(e as unknown as React.MouseEvent) + }} + > + + Clear + + )} + {endIcon !== undefined + ? endIcon && {endIcon} + : showChevron && + } +
+
+ + + {!disableSearch && ( +
+ + {hasCreateCapability && ( + + )} +
+ )} + + {!showCreateForm && ( + <> + {loading ? ( +
+ + + Searching... + +
+ ) : options.length > 0 ? ( + groupedOptions.groups.size > 0 + ? renderGroupedContent() + : {renderItems(options)} + ) : !search ? ( +
+ {idleMessage} +
+ ) : !hasCreateCapability ? ( + {emptyMessage} + ) : null} + + )} +
+ {showCreateForm && ( + renderCreateForm + ? renderCreateForm(createCtx) + : renderDefaultCreateForm() + )} +
+
+
+ ) +} + +export { SmartSelect } +export type { SmartSelectOption as SmartSelectOptionType } diff --git a/src/themes/index.ts b/src/themes/index.ts index d32239f..648bb1d 100644 --- a/src/themes/index.ts +++ b/src/themes/index.ts @@ -672,6 +672,1106 @@ export const twitterDarkTheme: ThemeTokens = { "shadow-2xl": "0px 2px 0px 0px hsl(202.8169 89.1213% 53.1373% / 0.00)", }; +/** + * Claude theme - Warm terracotta primary with cream backgrounds + */ +export const claudeTheme: ThemeTokens = { + background: "oklch(0.9818 0.0054 95.0986)", + foreground: "oklch(0.3438 0.0269 95.7226)", + card: "oklch(0.9818 0.0054 95.0986)", + cardForeground: "oklch(0.1908 0.0020 106.5859)", + popover: "oklch(1.0000 0 0)", + popoverForeground: "oklch(0.2671 0.0196 98.9390)", + primary: "oklch(0.6171 0.1375 39.0427)", + primaryForeground: "oklch(1.0000 0 0)", + secondary: "oklch(0.9245 0.0138 92.9892)", + secondaryForeground: "oklch(0.4334 0.0177 98.6048)", + muted: "oklch(0.9341 0.0153 90.2390)", + mutedForeground: "oklch(0.6059 0.0075 97.4233)", + accent: "oklch(0.9245 0.0138 92.9892)", + accentForeground: "oklch(0.2671 0.0196 98.9390)", + destructive: "oklch(0.1908 0.0020 106.5859)", + destructiveForeground: "oklch(1.0000 0 0)", + border: "oklch(0.8847 0.0069 97.3627)", + input: "oklch(0.7621 0.0156 98.3528)", + ring: "oklch(0.6171 0.1375 39.0427)", + chart1: "oklch(0.5583 0.1276 42.9956)", + chart2: "oklch(0.6898 0.1581 290.4107)", + chart3: "oklch(0.8816 0.0276 93.1280)", + chart4: "oklch(0.8822 0.0403 298.1792)", + chart5: "oklch(0.5608 0.1348 42.0584)", + sidebar: "oklch(0.9663 0.0080 98.8792)", + sidebarForeground: "oklch(0.3590 0.0051 106.6524)", + sidebarPrimary: "oklch(0.6171 0.1375 39.0427)", + sidebarPrimaryForeground: "oklch(0.9881 0 0)", + sidebarAccent: "oklch(0.9245 0.0138 92.9892)", + sidebarAccentForeground: "oklch(0.3250 0 0)", + sidebarBorder: "oklch(0.9401 0 0)", + sidebarRing: "oklch(0.7731 0 0)", + fontSans: "ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, 'Noto Sans', sans-serif, 'Apple Color Emoji', 'Segoe UI Emoji', 'Segoe UI Symbol', 'Noto Color Emoji'", + fontSerif: "ui-serif, Georgia, Cambria, \"Times New Roman\", Times, serif", + fontMono: "ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, \"Liberation Mono\", \"Courier New\", monospace", + radius: "0.5rem", + "shadow-2xs": "0 1px 3px 0px hsl(0 0% 0% / 0.05)", + "shadow-xs": "0 1px 3px 0px hsl(0 0% 0% / 0.05)", + "shadow-sm": "0 1px 3px 0px hsl(0 0% 0% / 0.10), 0 1px 2px -1px hsl(0 0% 0% / 0.10)", + shadow: "0 1px 3px 0px hsl(0 0% 0% / 0.10), 0 1px 2px -1px hsl(0 0% 0% / 0.10)", + "shadow-md": "0 1px 3px 0px hsl(0 0% 0% / 0.10), 0 2px 4px -1px hsl(0 0% 0% / 0.10)", + "shadow-lg": "0 1px 3px 0px hsl(0 0% 0% / 0.10), 0 4px 6px -1px hsl(0 0% 0% / 0.10)", + "shadow-xl": "0 1px 3px 0px hsl(0 0% 0% / 0.10), 0 8px 10px -1px hsl(0 0% 0% / 0.10)", + "shadow-2xl": "0 1px 3px 0px hsl(0 0% 0% / 0.25)", +}; + +/** + * Claude dark theme + */ +export const claudeDarkTheme: ThemeTokens = { + background: "oklch(0.2679 0.0036 106.6427)", + foreground: "oklch(0.8074 0.0142 93.0137)", + card: "oklch(0.2679 0.0036 106.6427)", + cardForeground: "oklch(0.9818 0.0054 95.0986)", + popover: "oklch(0.3085 0.0035 106.6039)", + popoverForeground: "oklch(0.9211 0.0040 106.4781)", + primary: "oklch(0.6724 0.1308 38.7559)", + primaryForeground: "oklch(1.0000 0 0)", + secondary: "oklch(0.9818 0.0054 95.0986)", + secondaryForeground: "oklch(0.3085 0.0035 106.6039)", + muted: "oklch(0.2213 0.0038 106.7070)", + mutedForeground: "oklch(0.7713 0.0169 99.0657)", + accent: "oklch(0.2130 0.0078 95.4245)", + accentForeground: "oklch(0.9663 0.0080 98.8792)", + destructive: "oklch(0.6368 0.2078 25.3313)", + destructiveForeground: "oklch(1.0000 0 0)", + border: "oklch(0.3618 0.0101 106.8928)", + input: "oklch(0.4336 0.0113 100.2195)", + ring: "oklch(0.6724 0.1308 38.7559)", + chart1: "oklch(0.5583 0.1276 42.9956)", + chart2: "oklch(0.6898 0.1581 290.4107)", + chart3: "oklch(0.2130 0.0078 95.4245)", + chart4: "oklch(0.3074 0.0516 289.3230)", + chart5: "oklch(0.5608 0.1348 42.0584)", + sidebar: "oklch(0.2357 0.0024 67.7077)", + sidebarForeground: "oklch(0.8074 0.0142 93.0137)", + sidebarPrimary: "oklch(0.3250 0 0)", + sidebarPrimaryForeground: "oklch(0.9881 0 0)", + sidebarAccent: "oklch(0.1680 0.0020 106.6177)", + sidebarAccentForeground: "oklch(0.8074 0.0142 93.0137)", + sidebarBorder: "oklch(0.9401 0 0)", + sidebarRing: "oklch(0.7731 0 0)", + fontSans: "ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, 'Noto Sans', sans-serif, 'Apple Color Emoji', 'Segoe UI Emoji', 'Segoe UI Symbol', 'Noto Color Emoji'", + fontSerif: "ui-serif, Georgia, Cambria, \"Times New Roman\", Times, serif", + fontMono: "ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, \"Liberation Mono\", \"Courier New\", monospace", + radius: "0.5rem", + "shadow-2xs": "0 1px 3px 0px hsl(0 0% 0% / 0.05)", + "shadow-xs": "0 1px 3px 0px hsl(0 0% 0% / 0.05)", + "shadow-sm": "0 1px 3px 0px hsl(0 0% 0% / 0.10), 0 1px 2px -1px hsl(0 0% 0% / 0.10)", + shadow: "0 1px 3px 0px hsl(0 0% 0% / 0.10), 0 1px 2px -1px hsl(0 0% 0% / 0.10)", + "shadow-md": "0 1px 3px 0px hsl(0 0% 0% / 0.10), 0 2px 4px -1px hsl(0 0% 0% / 0.10)", + "shadow-lg": "0 1px 3px 0px hsl(0 0% 0% / 0.10), 0 4px 6px -1px hsl(0 0% 0% / 0.10)", + "shadow-xl": "0 1px 3px 0px hsl(0 0% 0% / 0.10), 0 8px 10px -1px hsl(0 0% 0% / 0.10)", + "shadow-2xl": "0 1px 3px 0px hsl(0 0% 0% / 0.25)", +}; + +/** + * Claymorphism theme - Soft clay-like surfaces with purple primary and warm tones + */ +export const claymorphismTheme: ThemeTokens = { + background: "oklch(0.9232 0.0026 48.7171)", + foreground: "oklch(0.2795 0.0368 260.0310)", + card: "oklch(0.9699 0.0013 106.4238)", + cardForeground: "oklch(0.2795 0.0368 260.0310)", + popover: "oklch(0.9699 0.0013 106.4238)", + popoverForeground: "oklch(0.2795 0.0368 260.0310)", + primary: "oklch(0.5854 0.2041 277.1173)", + primaryForeground: "oklch(1.0000 0 0)", + secondary: "oklch(0.8687 0.0043 56.3660)", + secondaryForeground: "oklch(0.4461 0.0263 256.8018)", + muted: "oklch(0.9232 0.0026 48.7171)", + mutedForeground: "oklch(0.5510 0.0234 264.3637)", + accent: "oklch(0.9376 0.0260 321.9388)", + accentForeground: "oklch(0.3729 0.0306 259.7328)", + destructive: "oklch(0.6368 0.2078 25.3313)", + destructiveForeground: "oklch(1.0000 0 0)", + border: "oklch(0.8687 0.0043 56.3660)", + input: "oklch(0.8687 0.0043 56.3660)", + ring: "oklch(0.5854 0.2041 277.1173)", + chart1: "oklch(0.5854 0.2041 277.1173)", + chart2: "oklch(0.5106 0.2301 276.9656)", + chart3: "oklch(0.4568 0.2146 277.0229)", + chart4: "oklch(0.3984 0.1773 277.3662)", + chart5: "oklch(0.3588 0.1354 278.6973)", + sidebar: "oklch(0.8687 0.0043 56.3660)", + sidebarForeground: "oklch(0.2795 0.0368 260.0310)", + sidebarPrimary: "oklch(0.5854 0.2041 277.1173)", + sidebarPrimaryForeground: "oklch(1.0000 0 0)", + sidebarAccent: "oklch(0.9376 0.0260 321.9388)", + sidebarAccentForeground: "oklch(0.3729 0.0306 259.7328)", + sidebarBorder: "oklch(0.8687 0.0043 56.3660)", + sidebarRing: "oklch(0.5854 0.2041 277.1173)", + fontSans: "Plus Jakarta Sans, sans-serif", + fontSerif: "Lora, serif", + fontMono: "Roboto Mono, monospace", + radius: "1.25rem", + "shadow-2xs": "2px 2px 10px 4px hsl(240 4% 60% / 0.09)", + "shadow-xs": "2px 2px 10px 4px hsl(240 4% 60% / 0.09)", + "shadow-sm": "2px 2px 10px 4px hsl(240 4% 60% / 0.18), 2px 1px 2px 3px hsl(240 4% 60% / 0.18)", + shadow: "2px 2px 10px 4px hsl(240 4% 60% / 0.18), 2px 1px 2px 3px hsl(240 4% 60% / 0.18)", + "shadow-md": "2px 2px 10px 4px hsl(240 4% 60% / 0.18), 2px 2px 4px 3px hsl(240 4% 60% / 0.18)", + "shadow-lg": "2px 2px 10px 4px hsl(240 4% 60% / 0.18), 2px 4px 6px 3px hsl(240 4% 60% / 0.18)", + "shadow-xl": "2px 2px 10px 4px hsl(240 4% 60% / 0.18), 2px 8px 10px 3px hsl(240 4% 60% / 0.18)", + "shadow-2xl": "2px 2px 10px 4px hsl(240 4% 60% / 0.45)", +}; + +/** + * Claymorphism dark theme + */ +export const claymorphismDarkTheme: ThemeTokens = { + background: "oklch(0.2244 0.0074 67.4370)", + foreground: "oklch(0.9288 0.0126 255.5078)", + card: "oklch(0.2801 0.0080 59.3379)", + cardForeground: "oklch(0.9288 0.0126 255.5078)", + popover: "oklch(0.2801 0.0080 59.3379)", + popoverForeground: "oklch(0.9288 0.0126 255.5078)", + primary: "oklch(0.6801 0.1583 276.9349)", + primaryForeground: "oklch(0.2244 0.0074 67.4370)", + secondary: "oklch(0.3359 0.0077 59.4197)", + secondaryForeground: "oklch(0.8717 0.0093 258.3382)", + muted: "oklch(0.2287 0.0074 67.4469)", + mutedForeground: "oklch(0.7137 0.0192 261.3246)", + accent: "oklch(0.3896 0.0074 59.4734)", + accentForeground: "oklch(0.8717 0.0093 258.3382)", + destructive: "oklch(0.6368 0.2078 25.3313)", + destructiveForeground: "oklch(0.2244 0.0074 67.4370)", + border: "oklch(0.3359 0.0077 59.4197)", + input: "oklch(0.3359 0.0077 59.4197)", + ring: "oklch(0.6801 0.1583 276.9349)", + chart1: "oklch(0.6801 0.1583 276.9349)", + chart2: "oklch(0.5854 0.2041 277.1173)", + chart3: "oklch(0.5106 0.2301 276.9656)", + chart4: "oklch(0.4568 0.2146 277.0229)", + chart5: "oklch(0.3984 0.1773 277.3662)", + sidebar: "oklch(0.3359 0.0077 59.4197)", + sidebarForeground: "oklch(0.9288 0.0126 255.5078)", + sidebarPrimary: "oklch(0.6801 0.1583 276.9349)", + sidebarPrimaryForeground: "oklch(0.2244 0.0074 67.4370)", + sidebarAccent: "oklch(0.3896 0.0074 59.4734)", + sidebarAccentForeground: "oklch(0.8717 0.0093 258.3382)", + sidebarBorder: "oklch(0.3359 0.0077 59.4197)", + sidebarRing: "oklch(0.6801 0.1583 276.9349)", + fontSans: "Plus Jakarta Sans, sans-serif", + fontSerif: "Lora, serif", + fontMono: "Roboto Mono, monospace", + radius: "1.25rem", + "shadow-2xs": "2px 2px 10px 4px hsl(0 0% 0% / 0.09)", + "shadow-xs": "2px 2px 10px 4px hsl(0 0% 0% / 0.09)", + "shadow-sm": "2px 2px 10px 4px hsl(0 0% 0% / 0.18), 2px 1px 2px 3px hsl(0 0% 0% / 0.18)", + shadow: "2px 2px 10px 4px hsl(0 0% 0% / 0.18), 2px 1px 2px 3px hsl(0 0% 0% / 0.18)", + "shadow-md": "2px 2px 10px 4px hsl(0 0% 0% / 0.18), 2px 2px 4px 3px hsl(0 0% 0% / 0.18)", + "shadow-lg": "2px 2px 10px 4px hsl(0 0% 0% / 0.18), 2px 4px 6px 3px hsl(0 0% 0% / 0.18)", + "shadow-xl": "2px 2px 10px 4px hsl(0 0% 0% / 0.18), 2px 8px 10px 3px hsl(0 0% 0% / 0.18)", + "shadow-2xl": "2px 2px 10px 4px hsl(0 0% 0% / 0.45)", +}; + +/** + * Clean Slate theme - Crisp purple-violet primary on cool blue-grey surfaces + */ +export const cleanSlateTheme: ThemeTokens = { + background: "oklch(0.9842 0.0034 247.8575)", + foreground: "oklch(0.2795 0.0368 260.0310)", + card: "oklch(1.0000 0 0)", + cardForeground: "oklch(0.2795 0.0368 260.0310)", + popover: "oklch(1.0000 0 0)", + popoverForeground: "oklch(0.2795 0.0368 260.0310)", + primary: "oklch(0.5854 0.2041 277.1173)", + primaryForeground: "oklch(1.0000 0 0)", + secondary: "oklch(0.9276 0.0058 264.5313)", + secondaryForeground: "oklch(0.3729 0.0306 259.7328)", + muted: "oklch(0.9670 0.0029 264.5419)", + mutedForeground: "oklch(0.5510 0.0234 264.3637)", + accent: "oklch(0.9299 0.0334 272.7879)", + accentForeground: "oklch(0.3729 0.0306 259.7328)", + destructive: "oklch(0.6368 0.2078 25.3313)", + destructiveForeground: "oklch(1.0000 0 0)", + border: "oklch(0.8717 0.0093 258.3382)", + input: "oklch(0.8717 0.0093 258.3382)", + ring: "oklch(0.5854 0.2041 277.1173)", + chart1: "oklch(0.5854 0.2041 277.1173)", + chart2: "oklch(0.5106 0.2301 276.9656)", + chart3: "oklch(0.4568 0.2146 277.0229)", + chart4: "oklch(0.3984 0.1773 277.3662)", + chart5: "oklch(0.3588 0.1354 278.6973)", + sidebar: "oklch(0.9670 0.0029 264.5419)", + sidebarForeground: "oklch(0.2795 0.0368 260.0310)", + sidebarPrimary: "oklch(0.5854 0.2041 277.1173)", + sidebarPrimaryForeground: "oklch(1.0000 0 0)", + sidebarAccent: "oklch(0.9299 0.0334 272.7879)", + sidebarAccentForeground: "oklch(0.3729 0.0306 259.7328)", + sidebarBorder: "oklch(0.8717 0.0093 258.3382)", + sidebarRing: "oklch(0.5854 0.2041 277.1173)", + fontSans: "Inter, sans-serif", + fontSerif: "Merriweather, serif", + fontMono: "JetBrains Mono, monospace", + radius: "0.5rem", + "shadow-2xs": "0px 4px 8px -1px hsl(0 0% 0% / 0.05)", + "shadow-xs": "0px 4px 8px -1px hsl(0 0% 0% / 0.05)", + "shadow-sm": "0px 4px 8px -1px hsl(0 0% 0% / 0.10), 0px 1px 2px -2px hsl(0 0% 0% / 0.10)", + shadow: "0px 4px 8px -1px hsl(0 0% 0% / 0.10), 0px 1px 2px -2px hsl(0 0% 0% / 0.10)", + "shadow-md": "0px 4px 8px -1px hsl(0 0% 0% / 0.10), 0px 2px 4px -2px hsl(0 0% 0% / 0.10)", + "shadow-lg": "0px 4px 8px -1px hsl(0 0% 0% / 0.10), 0px 4px 6px -2px hsl(0 0% 0% / 0.10)", + "shadow-xl": "0px 4px 8px -1px hsl(0 0% 0% / 0.10), 0px 8px 10px -2px hsl(0 0% 0% / 0.10)", + "shadow-2xl": "0px 4px 8px -1px hsl(0 0% 0% / 0.25)", +}; + +/** + * Clean Slate dark theme + */ +export const cleanSlateDarkTheme: ThemeTokens = { + background: "oklch(0.2077 0.0398 265.7549)", + foreground: "oklch(0.9288 0.0126 255.5078)", + card: "oklch(0.2795 0.0368 260.0310)", + cardForeground: "oklch(0.9288 0.0126 255.5078)", + popover: "oklch(0.2795 0.0368 260.0310)", + popoverForeground: "oklch(0.9288 0.0126 255.5078)", + primary: "oklch(0.6801 0.1583 276.9349)", + primaryForeground: "oklch(0.2077 0.0398 265.7549)", + secondary: "oklch(0.3351 0.0331 260.9120)", + secondaryForeground: "oklch(0.8717 0.0093 258.3382)", + muted: "oklch(0.2427 0.0381 259.9437)", + mutedForeground: "oklch(0.7137 0.0192 261.3246)", + accent: "oklch(0.3729 0.0306 259.7328)", + accentForeground: "oklch(0.8717 0.0093 258.3382)", + destructive: "oklch(0.6368 0.2078 25.3313)", + destructiveForeground: "oklch(0.2077 0.0398 265.7549)", + border: "oklch(0.4461 0.0263 256.8018)", + input: "oklch(0.4461 0.0263 256.8018)", + ring: "oklch(0.6801 0.1583 276.9349)", + chart1: "oklch(0.6801 0.1583 276.9349)", + chart2: "oklch(0.5854 0.2041 277.1173)", + chart3: "oklch(0.5106 0.2301 276.9656)", + chart4: "oklch(0.4568 0.2146 277.0229)", + chart5: "oklch(0.3984 0.1773 277.3662)", + sidebar: "oklch(0.2795 0.0368 260.0310)", + sidebarForeground: "oklch(0.9288 0.0126 255.5078)", + sidebarPrimary: "oklch(0.6801 0.1583 276.9349)", + sidebarPrimaryForeground: "oklch(0.2077 0.0398 265.7549)", + sidebarAccent: "oklch(0.3729 0.0306 259.7328)", + sidebarAccentForeground: "oklch(0.8717 0.0093 258.3382)", + sidebarBorder: "oklch(0.4461 0.0263 256.8018)", + sidebarRing: "oklch(0.6801 0.1583 276.9349)", + fontSans: "Inter, sans-serif", + fontSerif: "Merriweather, serif", + fontMono: "JetBrains Mono, monospace", + radius: "0.5rem", + "shadow-2xs": "0px 4px 8px -1px hsl(0 0% 0% / 0.05)", + "shadow-xs": "0px 4px 8px -1px hsl(0 0% 0% / 0.05)", + "shadow-sm": "0px 4px 8px -1px hsl(0 0% 0% / 0.10), 0px 1px 2px -2px hsl(0 0% 0% / 0.10)", + shadow: "0px 4px 8px -1px hsl(0 0% 0% / 0.10), 0px 1px 2px -2px hsl(0 0% 0% / 0.10)", + "shadow-md": "0px 4px 8px -1px hsl(0 0% 0% / 0.10), 0px 2px 4px -2px hsl(0 0% 0% / 0.10)", + "shadow-lg": "0px 4px 8px -1px hsl(0 0% 0% / 0.10), 0px 4px 6px -2px hsl(0 0% 0% / 0.10)", + "shadow-xl": "0px 4px 8px -1px hsl(0 0% 0% / 0.10), 0px 8px 10px -2px hsl(0 0% 0% / 0.10)", + "shadow-2xl": "0px 4px 8px -1px hsl(0 0% 0% / 0.25)", +}; + +/** + * Modern Minimal theme - Clean blue-violet primary on pure white with tight radius + */ +export const modernMinimalTheme: ThemeTokens = { + background: "oklch(1.0000 0 0)", + foreground: "oklch(0.3211 0 0)", + card: "oklch(1.0000 0 0)", + cardForeground: "oklch(0.3211 0 0)", + popover: "oklch(1.0000 0 0)", + popoverForeground: "oklch(0.3211 0 0)", + primary: "oklch(0.6231 0.1880 259.8145)", + primaryForeground: "oklch(1.0000 0 0)", + secondary: "oklch(0.9670 0.0029 264.5419)", + secondaryForeground: "oklch(0.4461 0.0263 256.8018)", + muted: "oklch(0.9846 0.0017 247.8389)", + mutedForeground: "oklch(0.5510 0.0234 264.3637)", + accent: "oklch(0.9514 0.0250 236.8242)", + accentForeground: "oklch(0.3791 0.1378 265.5222)", + destructive: "oklch(0.6368 0.2078 25.3313)", + destructiveForeground: "oklch(1.0000 0 0)", + border: "oklch(0.9276 0.0058 264.5313)", + input: "oklch(0.9276 0.0058 264.5313)", + ring: "oklch(0.6231 0.1880 259.8145)", + chart1: "oklch(0.6231 0.1880 259.8145)", + chart2: "oklch(0.5461 0.2152 262.8809)", + chart3: "oklch(0.4882 0.2172 264.3763)", + chart4: "oklch(0.4244 0.1809 265.6377)", + chart5: "oklch(0.3791 0.1378 265.5222)", + sidebar: "oklch(0.9846 0.0017 247.8389)", + sidebarForeground: "oklch(0.3211 0 0)", + sidebarPrimary: "oklch(0.6231 0.1880 259.8145)", + sidebarPrimaryForeground: "oklch(1.0000 0 0)", + sidebarAccent: "oklch(0.9514 0.0250 236.8242)", + sidebarAccentForeground: "oklch(0.3791 0.1378 265.5222)", + sidebarBorder: "oklch(0.9276 0.0058 264.5313)", + sidebarRing: "oklch(0.6231 0.1880 259.8145)", + fontSans: "Inter, sans-serif", + fontSerif: "Source Serif 4, serif", + fontMono: "JetBrains Mono, monospace", + radius: "0.375rem", + "shadow-2xs": "0 1px 3px 0px hsl(0 0% 0% / 0.05)", + "shadow-xs": "0 1px 3px 0px hsl(0 0% 0% / 0.05)", + "shadow-sm": "0 1px 3px 0px hsl(0 0% 0% / 0.10), 0 1px 2px -1px hsl(0 0% 0% / 0.10)", + shadow: "0 1px 3px 0px hsl(0 0% 0% / 0.10), 0 1px 2px -1px hsl(0 0% 0% / 0.10)", + "shadow-md": "0 1px 3px 0px hsl(0 0% 0% / 0.10), 0 2px 4px -1px hsl(0 0% 0% / 0.10)", + "shadow-lg": "0 1px 3px 0px hsl(0 0% 0% / 0.10), 0 4px 6px -1px hsl(0 0% 0% / 0.10)", + "shadow-xl": "0 1px 3px 0px hsl(0 0% 0% / 0.10), 0 8px 10px -1px hsl(0 0% 0% / 0.10)", + "shadow-2xl": "0 1px 3px 0px hsl(0 0% 0% / 0.25)", +}; + +/** + * Modern Minimal dark theme + */ +export const modernMinimalDarkTheme: ThemeTokens = { + background: "oklch(0.2046 0 0)", + foreground: "oklch(0.9219 0 0)", + card: "oklch(0.2686 0 0)", + cardForeground: "oklch(0.9219 0 0)", + popover: "oklch(0.2686 0 0)", + popoverForeground: "oklch(0.9219 0 0)", + primary: "oklch(0.6231 0.1880 259.8145)", + primaryForeground: "oklch(1.0000 0 0)", + secondary: "oklch(0.2686 0 0)", + secondaryForeground: "oklch(0.9219 0 0)", + muted: "oklch(0.2393 0 0)", + mutedForeground: "oklch(0.7155 0 0)", + accent: "oklch(0.3791 0.1378 265.5222)", + accentForeground: "oklch(0.8823 0.0571 254.1284)", + destructive: "oklch(0.6368 0.2078 25.3313)", + destructiveForeground: "oklch(1.0000 0 0)", + border: "oklch(0.3715 0 0)", + input: "oklch(0.3715 0 0)", + ring: "oklch(0.6231 0.1880 259.8145)", + chart1: "oklch(0.7137 0.1434 254.6240)", + chart2: "oklch(0.6231 0.1880 259.8145)", + chart3: "oklch(0.5461 0.2152 262.8809)", + chart4: "oklch(0.4882 0.2172 264.3763)", + chart5: "oklch(0.4244 0.1809 265.6377)", + sidebar: "oklch(0.2046 0 0)", + sidebarForeground: "oklch(0.9219 0 0)", + sidebarPrimary: "oklch(0.6231 0.1880 259.8145)", + sidebarPrimaryForeground: "oklch(1.0000 0 0)", + sidebarAccent: "oklch(0.3791 0.1378 265.5222)", + sidebarAccentForeground: "oklch(0.8823 0.0571 254.1284)", + sidebarBorder: "oklch(0.3715 0 0)", + sidebarRing: "oklch(0.6231 0.1880 259.8145)", + fontSans: "Inter, sans-serif", + fontSerif: "Source Serif 4, serif", + fontMono: "JetBrains Mono, monospace", + radius: "0.375rem", + "shadow-2xs": "0 1px 3px 0px hsl(0 0% 0% / 0.05)", + "shadow-xs": "0 1px 3px 0px hsl(0 0% 0% / 0.05)", + "shadow-sm": "0 1px 3px 0px hsl(0 0% 0% / 0.10), 0 1px 2px -1px hsl(0 0% 0% / 0.10)", + shadow: "0 1px 3px 0px hsl(0 0% 0% / 0.10), 0 1px 2px -1px hsl(0 0% 0% / 0.10)", + "shadow-md": "0 1px 3px 0px hsl(0 0% 0% / 0.10), 0 2px 4px -1px hsl(0 0% 0% / 0.10)", + "shadow-lg": "0 1px 3px 0px hsl(0 0% 0% / 0.10), 0 4px 6px -1px hsl(0 0% 0% / 0.10)", + "shadow-xl": "0 1px 3px 0px hsl(0 0% 0% / 0.10), 0 8px 10px -1px hsl(0 0% 0% / 0.10)", + "shadow-2xl": "0 1px 3px 0px hsl(0 0% 0% / 0.25)", +}; + +/** + * Nature theme - Earthy green primary on warm parchment backgrounds + */ +export const natureTheme: ThemeTokens = { + background: "oklch(0.9711 0.0074 80.7211)", + foreground: "oklch(0.3000 0.0358 30.2042)", + card: "oklch(0.9711 0.0074 80.7211)", + cardForeground: "oklch(0.3000 0.0358 30.2042)", + popover: "oklch(0.9711 0.0074 80.7211)", + popoverForeground: "oklch(0.3000 0.0358 30.2042)", + primary: "oklch(0.5234 0.1347 144.1672)", + primaryForeground: "oklch(1.0000 0 0)", + secondary: "oklch(0.9571 0.0210 147.6360)", + secondaryForeground: "oklch(0.4254 0.1159 144.3078)", + muted: "oklch(0.9370 0.0142 74.4218)", + mutedForeground: "oklch(0.4495 0.0486 39.2110)", + accent: "oklch(0.8952 0.0504 146.0366)", + accentForeground: "oklch(0.4254 0.1159 144.3078)", + destructive: "oklch(0.5386 0.1937 26.7249)", + destructiveForeground: "oklch(1.0000 0 0)", + border: "oklch(0.8805 0.0208 74.6428)", + input: "oklch(0.8805 0.0208 74.6428)", + ring: "oklch(0.5234 0.1347 144.1672)", + chart1: "oklch(0.6731 0.1624 144.2083)", + chart2: "oklch(0.5752 0.1446 144.1813)", + chart3: "oklch(0.5234 0.1347 144.1672)", + chart4: "oklch(0.4254 0.1159 144.3078)", + chart5: "oklch(0.2157 0.0453 145.7256)", + sidebar: "oklch(0.9370 0.0142 74.4218)", + sidebarForeground: "oklch(0.3000 0.0358 30.2042)", + sidebarPrimary: "oklch(0.5234 0.1347 144.1672)", + sidebarPrimaryForeground: "oklch(1.0000 0 0)", + sidebarAccent: "oklch(0.8952 0.0504 146.0366)", + sidebarAccentForeground: "oklch(0.4254 0.1159 144.3078)", + sidebarBorder: "oklch(0.8805 0.0208 74.6428)", + sidebarRing: "oklch(0.5234 0.1347 144.1672)", + fontSans: "Montserrat, sans-serif", + fontSerif: "Merriweather, serif", + fontMono: "Source Code Pro, monospace", + radius: "0.5rem", + "shadow-2xs": "0 1px 3px 0px hsl(0 0% 0% / 0.05)", + "shadow-xs": "0 1px 3px 0px hsl(0 0% 0% / 0.05)", + "shadow-sm": "0 1px 3px 0px hsl(0 0% 0% / 0.10), 0 1px 2px -1px hsl(0 0% 0% / 0.10)", + shadow: "0 1px 3px 0px hsl(0 0% 0% / 0.10), 0 1px 2px -1px hsl(0 0% 0% / 0.10)", + "shadow-md": "0 1px 3px 0px hsl(0 0% 0% / 0.10), 0 2px 4px -1px hsl(0 0% 0% / 0.10)", + "shadow-lg": "0 1px 3px 0px hsl(0 0% 0% / 0.10), 0 4px 6px -1px hsl(0 0% 0% / 0.10)", + "shadow-xl": "0 1px 3px 0px hsl(0 0% 0% / 0.10), 0 8px 10px -1px hsl(0 0% 0% / 0.10)", + "shadow-2xl": "0 1px 3px 0px hsl(0 0% 0% / 0.25)", +}; + +/** + * Nature dark theme + */ +export const natureDarkTheme: ThemeTokens = { + background: "oklch(0.2683 0.0279 150.7681)", + foreground: "oklch(0.9423 0.0097 72.6595)", + card: "oklch(0.3327 0.0271 146.9867)", + cardForeground: "oklch(0.9423 0.0097 72.6595)", + popover: "oklch(0.3327 0.0271 146.9867)", + popoverForeground: "oklch(0.9423 0.0097 72.6595)", + primary: "oklch(0.6731 0.1624 144.2083)", + primaryForeground: "oklch(0.2157 0.0453 145.7256)", + secondary: "oklch(0.3942 0.0265 142.9926)", + secondaryForeground: "oklch(0.8970 0.0166 142.5518)", + muted: "oklch(0.2926 0.0212 147.7496)", + mutedForeground: "oklch(0.8579 0.0174 76.0955)", + accent: "oklch(0.5752 0.1446 144.1813)", + accentForeground: "oklch(0.9423 0.0097 72.6595)", + destructive: "oklch(0.5386 0.1937 26.7249)", + destructiveForeground: "oklch(0.9423 0.0097 72.6595)", + border: "oklch(0.3942 0.0265 142.9926)", + input: "oklch(0.3942 0.0265 142.9926)", + ring: "oklch(0.6731 0.1624 144.2083)", + chart1: "oklch(0.7660 0.1179 145.2950)", + chart2: "oklch(0.7185 0.1417 144.8887)", + chart3: "oklch(0.6731 0.1624 144.2083)", + chart4: "oklch(0.6291 0.1543 144.2031)", + chart5: "oklch(0.5752 0.1446 144.1813)", + sidebar: "oklch(0.2683 0.0279 150.7681)", + sidebarForeground: "oklch(0.9423 0.0097 72.6595)", + sidebarPrimary: "oklch(0.6731 0.1624 144.2083)", + sidebarPrimaryForeground: "oklch(0.2157 0.0453 145.7256)", + sidebarAccent: "oklch(0.5752 0.1446 144.1813)", + sidebarAccentForeground: "oklch(0.9423 0.0097 72.6595)", + sidebarBorder: "oklch(0.3942 0.0265 142.9926)", + sidebarRing: "oklch(0.6731 0.1624 144.2083)", + fontSans: "Montserrat, sans-serif", + fontSerif: "Merriweather, serif", + fontMono: "Source Code Pro, monospace", + radius: "0.5rem", + "shadow-2xs": "0 1px 3px 0px hsl(0 0% 0% / 0.05)", + "shadow-xs": "0 1px 3px 0px hsl(0 0% 0% / 0.05)", + "shadow-sm": "0 1px 3px 0px hsl(0 0% 0% / 0.10), 0 1px 2px -1px hsl(0 0% 0% / 0.10)", + shadow: "0 1px 3px 0px hsl(0 0% 0% / 0.10), 0 1px 2px -1px hsl(0 0% 0% / 0.10)", + "shadow-md": "0 1px 3px 0px hsl(0 0% 0% / 0.10), 0 2px 4px -1px hsl(0 0% 0% / 0.10)", + "shadow-lg": "0 1px 3px 0px hsl(0 0% 0% / 0.10), 0 4px 6px -1px hsl(0 0% 0% / 0.10)", + "shadow-xl": "0 1px 3px 0px hsl(0 0% 0% / 0.10), 0 8px 10px -1px hsl(0 0% 0% / 0.10)", + "shadow-2xl": "0 1px 3px 0px hsl(0 0% 0% / 0.25)", +}; + +/** + * Neo Brutalism theme - Bold flat colors, hard black borders, zero radius, offset shadows + */ +export const neoBrutalismTheme: ThemeTokens = { + background: "oklch(1.0000 0 0)", + foreground: "oklch(0 0 0)", + card: "oklch(1.0000 0 0)", + cardForeground: "oklch(0 0 0)", + popover: "oklch(1.0000 0 0)", + popoverForeground: "oklch(0 0 0)", + primary: "oklch(0.6489 0.2370 26.9728)", + primaryForeground: "oklch(1.0000 0 0)", + secondary: "oklch(0.9680 0.2110 109.7692)", + secondaryForeground: "oklch(0 0 0)", + muted: "oklch(0.9551 0 0)", + mutedForeground: "oklch(0.3211 0 0)", + accent: "oklch(0.5635 0.2408 260.8178)", + accentForeground: "oklch(1.0000 0 0)", + destructive: "oklch(0 0 0)", + destructiveForeground: "oklch(1.0000 0 0)", + border: "oklch(0 0 0)", + input: "oklch(0 0 0)", + ring: "oklch(0.6489 0.2370 26.9728)", + chart1: "oklch(0.6489 0.2370 26.9728)", + chart2: "oklch(0.9680 0.2110 109.7692)", + chart3: "oklch(0.5635 0.2408 260.8178)", + chart4: "oklch(0.7323 0.2492 142.4953)", + chart5: "oklch(0.5931 0.2726 328.3634)", + sidebar: "oklch(0.9551 0 0)", + sidebarForeground: "oklch(0 0 0)", + sidebarPrimary: "oklch(0.6489 0.2370 26.9728)", + sidebarPrimaryForeground: "oklch(1.0000 0 0)", + sidebarAccent: "oklch(0.5635 0.2408 260.8178)", + sidebarAccentForeground: "oklch(1.0000 0 0)", + sidebarBorder: "oklch(0 0 0)", + sidebarRing: "oklch(0.6489 0.2370 26.9728)", + fontSans: "DM Sans, sans-serif", + fontSerif: "ui-serif, Georgia, Cambria, \"Times New Roman\", Times, serif", + fontMono: "Space Mono, monospace", + radius: "0px", + "shadow-2xs": "4px 4px 0px 0px hsl(0 0% 0% / 0.50)", + "shadow-xs": "4px 4px 0px 0px hsl(0 0% 0% / 0.50)", + "shadow-sm": "4px 4px 0px 0px hsl(0 0% 0% / 1.00), 4px 1px 2px -1px hsl(0 0% 0% / 1.00)", + shadow: "4px 4px 0px 0px hsl(0 0% 0% / 1.00), 4px 1px 2px -1px hsl(0 0% 0% / 1.00)", + "shadow-md": "4px 4px 0px 0px hsl(0 0% 0% / 1.00), 4px 2px 4px -1px hsl(0 0% 0% / 1.00)", + "shadow-lg": "4px 4px 0px 0px hsl(0 0% 0% / 1.00), 4px 4px 6px -1px hsl(0 0% 0% / 1.00)", + "shadow-xl": "4px 4px 0px 0px hsl(0 0% 0% / 1.00), 4px 8px 10px -1px hsl(0 0% 0% / 1.00)", + "shadow-2xl": "4px 4px 0px 0px hsl(0 0% 0% / 2.50)", +}; + +/** + * Neo Brutalism dark theme + */ +export const neoBrutalismDarkTheme: ThemeTokens = { + background: "oklch(0 0 0)", + foreground: "oklch(1.0000 0 0)", + card: "oklch(0.3211 0 0)", + cardForeground: "oklch(1.0000 0 0)", + popover: "oklch(0.3211 0 0)", + popoverForeground: "oklch(1.0000 0 0)", + primary: "oklch(0.7044 0.1872 23.1858)", + primaryForeground: "oklch(0 0 0)", + secondary: "oklch(0.9691 0.2005 109.6228)", + secondaryForeground: "oklch(0 0 0)", + muted: "oklch(0.2178 0 0)", + mutedForeground: "oklch(0.8452 0 0)", + accent: "oklch(0.6755 0.1765 252.2592)", + accentForeground: "oklch(0 0 0)", + destructive: "oklch(1.0000 0 0)", + destructiveForeground: "oklch(0 0 0)", + border: "oklch(1.0000 0 0)", + input: "oklch(1.0000 0 0)", + ring: "oklch(0.7044 0.1872 23.1858)", + chart1: "oklch(0.7044 0.1872 23.1858)", + chart2: "oklch(0.9691 0.2005 109.6228)", + chart3: "oklch(0.6755 0.1765 252.2592)", + chart4: "oklch(0.7395 0.2268 142.8504)", + chart5: "oklch(0.6131 0.2458 328.0714)", + sidebar: "oklch(0 0 0)", + sidebarForeground: "oklch(1.0000 0 0)", + sidebarPrimary: "oklch(0.7044 0.1872 23.1858)", + sidebarPrimaryForeground: "oklch(0 0 0)", + sidebarAccent: "oklch(0.6755 0.1765 252.2592)", + sidebarAccentForeground: "oklch(0 0 0)", + sidebarBorder: "oklch(1.0000 0 0)", + sidebarRing: "oklch(0.7044 0.1872 23.1858)", + fontSans: "DM Sans, sans-serif", + fontSerif: "ui-serif, Georgia, Cambria, \"Times New Roman\", Times, serif", + fontMono: "Space Mono, monospace", + radius: "0px", + "shadow-2xs": "4px 4px 0px 0px hsl(0 0% 0% / 0.50)", + "shadow-xs": "4px 4px 0px 0px hsl(0 0% 0% / 0.50)", + "shadow-sm": "4px 4px 0px 0px hsl(0 0% 0% / 1.00), 4px 1px 2px -1px hsl(0 0% 0% / 1.00)", + shadow: "4px 4px 0px 0px hsl(0 0% 0% / 1.00), 4px 1px 2px -1px hsl(0 0% 0% / 1.00)", + "shadow-md": "4px 4px 0px 0px hsl(0 0% 0% / 1.00), 4px 2px 4px -1px hsl(0 0% 0% / 1.00)", + "shadow-lg": "4px 4px 0px 0px hsl(0 0% 0% / 1.00), 4px 4px 6px -1px hsl(0 0% 0% / 1.00)", + "shadow-xl": "4px 4px 0px 0px hsl(0 0% 0% / 1.00), 4px 8px 10px -1px hsl(0 0% 0% / 1.00)", + "shadow-2xl": "4px 4px 0px 0px hsl(0 0% 0% / 2.50)", +}; + +/** + * Notebook theme - Handwritten feel with grayscale tones, warm accents, and subtle shadows + */ +export const notebookTheme: ThemeTokens = { + background: "oklch(0.9821 0 0)", + foreground: "oklch(0.3485 0 0)", + card: "oklch(1.0000 0 0)", + cardForeground: "oklch(0.3485 0 0)", + popover: "oklch(1.0000 0 0)", + popoverForeground: "oklch(0.3485 0 0)", + primary: "oklch(0.4891 0 0)", + primaryForeground: "oklch(0.9551 0 0)", + secondary: "oklch(0.9006 0 0)", + secondaryForeground: "oklch(0.3485 0 0)", + muted: "oklch(0.9158 0 0)", + mutedForeground: "oklch(0.4313 0 0)", + accent: "oklch(0.9354 0.0456 94.8549)", + accentForeground: "oklch(0.4015 0.0436 37.9587)", + destructive: "oklch(0.6627 0.0978 20.0041)", + destructiveForeground: "oklch(1.0000 0 0)", + border: "oklch(0.5538 0.0025 17.2320)", + input: "oklch(1.0000 0 0)", + ring: "oklch(0.7058 0 0)", + chart1: "oklch(0.3211 0 0)", + chart2: "oklch(0.4495 0 0)", + chart3: "oklch(0.5693 0 0)", + chart4: "oklch(0.6830 0 0)", + chart5: "oklch(0.7921 0 0)", + sidebar: "oklch(0.9551 0 0)", + sidebarForeground: "oklch(0.3485 0 0)", + sidebarPrimary: "oklch(0.4891 0 0)", + sidebarPrimaryForeground: "oklch(0.9551 0 0)", + sidebarAccent: "oklch(0.9354 0.0456 94.8549)", + sidebarAccentForeground: "oklch(0.4015 0.0436 37.9587)", + sidebarBorder: "oklch(0.8078 0 0)", + sidebarRing: "oklch(0.7058 0 0)", + fontSans: "Architects Daughter, sans-serif", + fontSerif: "\"Times New Roman\", Times, serif", + fontMono: "\"Courier New\", Courier, monospace", + radius: "0.625rem", + "shadow-2xs": "1px 4px 5px 0px hsl(0 0% 0% / 0.01)", + "shadow-xs": "1px 4px 5px 0px hsl(0 0% 0% / 0.01)", + "shadow-sm": "1px 4px 5px 0px hsl(0 0% 0% / 0.03), 1px 1px 2px -1px hsl(0 0% 0% / 0.03)", + shadow: "1px 4px 5px 0px hsl(0 0% 0% / 0.03), 1px 1px 2px -1px hsl(0 0% 0% / 0.03)", + "shadow-md": "1px 4px 5px 0px hsl(0 0% 0% / 0.03), 1px 2px 4px -1px hsl(0 0% 0% / 0.03)", + "shadow-lg": "1px 4px 5px 0px hsl(0 0% 0% / 0.03), 1px 4px 6px -1px hsl(0 0% 0% / 0.03)", + "shadow-xl": "1px 4px 5px 0px hsl(0 0% 0% / 0.03), 1px 8px 10px -1px hsl(0 0% 0% / 0.03)", + "shadow-2xl": "1px 4px 5px 0px hsl(0 0% 0% / 0.07)", +}; + +/** + * Notebook dark theme + */ +export const notebookDarkTheme: ThemeTokens = { + background: "oklch(0.2891 0 0)", + foreground: "oklch(0.8945 0 0)", + card: "oklch(0.3211 0 0)", + cardForeground: "oklch(0.8945 0 0)", + popover: "oklch(0.3211 0 0)", + popoverForeground: "oklch(0.8945 0 0)", + primary: "oklch(0.7572 0 0)", + primaryForeground: "oklch(0.2891 0 0)", + secondary: "oklch(0.4676 0 0)", + secondaryForeground: "oklch(0.8078 0 0)", + muted: "oklch(0.3904 0 0)", + mutedForeground: "oklch(0.7058 0 0)", + accent: "oklch(0.9067 0 0)", + accentForeground: "oklch(0.3211 0 0)", + destructive: "oklch(0.7915 0.0491 18.2410)", + destructiveForeground: "oklch(0.2891 0 0)", + border: "oklch(0.4276 0 0)", + input: "oklch(0.3211 0 0)", + ring: "oklch(0.8078 0 0)", + chart1: "oklch(0.9521 0 0)", + chart2: "oklch(0.8576 0 0)", + chart3: "oklch(0.7572 0 0)", + chart4: "oklch(0.6534 0 0)", + chart5: "oklch(0.5452 0 0)", + sidebar: "oklch(0.2478 0 0)", + sidebarForeground: "oklch(0.8945 0 0)", + sidebarPrimary: "oklch(0.7572 0 0)", + sidebarPrimaryForeground: "oklch(0.2478 0 0)", + sidebarAccent: "oklch(0.9067 0 0)", + sidebarAccentForeground: "oklch(0.3211 0 0)", + sidebarBorder: "oklch(0.4276 0 0)", + sidebarRing: "oklch(0.8078 0 0)", + fontSans: "Architects Daughter, sans-serif", + fontSerif: "Georgia, serif", + fontMono: "\"Fira Code\", \"Courier New\", monospace", + radius: "0.625rem", + "shadow-2xs": "1px 4px 5px 0px hsl(0 0% 0% / 0.01)", + "shadow-xs": "1px 4px 5px 0px hsl(0 0% 0% / 0.01)", + "shadow-sm": "1px 4px 5px 0px hsl(0 0% 0% / 0.03), 1px 1px 2px -1px hsl(0 0% 0% / 0.03)", + shadow: "1px 4px 5px 0px hsl(0 0% 0% / 0.03), 1px 1px 2px -1px hsl(0 0% 0% / 0.03)", + "shadow-md": "1px 4px 5px 0px hsl(0 0% 0% / 0.03), 1px 2px 4px -1px hsl(0 0% 0% / 0.03)", + "shadow-lg": "1px 4px 5px 0px hsl(0 0% 0% / 0.03), 1px 4px 6px -1px hsl(0 0% 0% / 0.03)", + "shadow-xl": "1px 4px 5px 0px hsl(0 0% 0% / 0.03), 1px 8px 10px -1px hsl(0 0% 0% / 0.03)", + "shadow-2xl": "1px 4px 5px 0px hsl(0 0% 0% / 0.07)", +}; + +/** + * Ocean Breeze theme - Teal-green primary on cool blue-grey with DM Sans + */ +export const oceanBreezeTheme: ThemeTokens = { + background: "oklch(0.9751 0.0127 244.2507)", + foreground: "oklch(0.3729 0.0306 259.7328)", + card: "oklch(1.0000 0 0)", + cardForeground: "oklch(0.3729 0.0306 259.7328)", + popover: "oklch(1.0000 0 0)", + popoverForeground: "oklch(0.3729 0.0306 259.7328)", + primary: "oklch(0.7227 0.1920 149.5793)", + primaryForeground: "oklch(1.0000 0 0)", + secondary: "oklch(0.9514 0.0250 236.8242)", + secondaryForeground: "oklch(0.4461 0.0263 256.8018)", + muted: "oklch(0.9670 0.0029 264.5419)", + mutedForeground: "oklch(0.5510 0.0234 264.3637)", + accent: "oklch(0.9505 0.0507 163.0508)", + accentForeground: "oklch(0.3729 0.0306 259.7328)", + destructive: "oklch(0.6368 0.2078 25.3313)", + destructiveForeground: "oklch(1.0000 0 0)", + border: "oklch(0.9276 0.0058 264.5313)", + input: "oklch(0.9276 0.0058 264.5313)", + ring: "oklch(0.7227 0.1920 149.5793)", + chart1: "oklch(0.7227 0.1920 149.5793)", + chart2: "oklch(0.6959 0.1491 162.4796)", + chart3: "oklch(0.5960 0.1274 163.2254)", + chart4: "oklch(0.5081 0.1049 165.6121)", + chart5: "oklch(0.4318 0.0865 166.9128)", + sidebar: "oklch(0.9514 0.0250 236.8242)", + sidebarForeground: "oklch(0.3729 0.0306 259.7328)", + sidebarPrimary: "oklch(0.7227 0.1920 149.5793)", + sidebarPrimaryForeground: "oklch(1.0000 0 0)", + sidebarAccent: "oklch(0.9505 0.0507 163.0508)", + sidebarAccentForeground: "oklch(0.3729 0.0306 259.7328)", + sidebarBorder: "oklch(0.9276 0.0058 264.5313)", + sidebarRing: "oklch(0.7227 0.1920 149.5793)", + fontSans: "DM Sans, sans-serif", + fontSerif: "Lora, serif", + fontMono: "IBM Plex Mono, monospace", + radius: "0.5rem", + "shadow-2xs": "0px 4px 8px -1px hsl(0 0% 0% / 0.05)", + "shadow-xs": "0px 4px 8px -1px hsl(0 0% 0% / 0.05)", + "shadow-sm": "0px 4px 8px -1px hsl(0 0% 0% / 0.10), 0px 1px 2px -2px hsl(0 0% 0% / 0.10)", + shadow: "0px 4px 8px -1px hsl(0 0% 0% / 0.10), 0px 1px 2px -2px hsl(0 0% 0% / 0.10)", + "shadow-md": "0px 4px 8px -1px hsl(0 0% 0% / 0.10), 0px 2px 4px -2px hsl(0 0% 0% / 0.10)", + "shadow-lg": "0px 4px 8px -1px hsl(0 0% 0% / 0.10), 0px 4px 6px -2px hsl(0 0% 0% / 0.10)", + "shadow-xl": "0px 4px 8px -1px hsl(0 0% 0% / 0.10), 0px 8px 10px -2px hsl(0 0% 0% / 0.10)", + "shadow-2xl": "0px 4px 8px -1px hsl(0 0% 0% / 0.25)", +}; + +/** + * Ocean Breeze dark theme + */ +export const oceanBreezeDarkTheme: ThemeTokens = { + background: "oklch(0.2077 0.0398 265.7549)", + foreground: "oklch(0.8717 0.0093 258.3382)", + card: "oklch(0.2795 0.0368 260.0310)", + cardForeground: "oklch(0.8717 0.0093 258.3382)", + popover: "oklch(0.2795 0.0368 260.0310)", + popoverForeground: "oklch(0.8717 0.0093 258.3382)", + primary: "oklch(0.7729 0.1535 163.2231)", + primaryForeground: "oklch(0.2077 0.0398 265.7549)", + secondary: "oklch(0.3351 0.0331 260.9120)", + secondaryForeground: "oklch(0.7118 0.0129 286.0665)", + muted: "oklch(0.2463 0.0275 259.9628)", + mutedForeground: "oklch(0.5510 0.0234 264.3637)", + accent: "oklch(0.3729 0.0306 259.7328)", + accentForeground: "oklch(0.7118 0.0129 286.0665)", + destructive: "oklch(0.6368 0.2078 25.3313)", + destructiveForeground: "oklch(0.2077 0.0398 265.7549)", + border: "oklch(0.4461 0.0263 256.8018)", + input: "oklch(0.4461 0.0263 256.8018)", + ring: "oklch(0.7729 0.1535 163.2231)", + chart1: "oklch(0.7729 0.1535 163.2231)", + chart2: "oklch(0.7845 0.1325 181.9120)", + chart3: "oklch(0.7227 0.1920 149.5793)", + chart4: "oklch(0.6959 0.1491 162.4796)", + chart5: "oklch(0.5960 0.1274 163.2254)", + sidebar: "oklch(0.2795 0.0368 260.0310)", + sidebarForeground: "oklch(0.8717 0.0093 258.3382)", + sidebarPrimary: "oklch(0.7729 0.1535 163.2231)", + sidebarPrimaryForeground: "oklch(0.2077 0.0398 265.7549)", + sidebarAccent: "oklch(0.3729 0.0306 259.7328)", + sidebarAccentForeground: "oklch(0.7118 0.0129 286.0665)", + sidebarBorder: "oklch(0.4461 0.0263 256.8018)", + sidebarRing: "oklch(0.7729 0.1535 163.2231)", + fontSans: "DM Sans, sans-serif", + fontSerif: "Lora, serif", + fontMono: "IBM Plex Mono, monospace", + radius: "0.5rem", + "shadow-2xs": "0px 4px 8px -1px hsl(0 0% 0% / 0.05)", + "shadow-xs": "0px 4px 8px -1px hsl(0 0% 0% / 0.05)", + "shadow-sm": "0px 4px 8px -1px hsl(0 0% 0% / 0.10), 0px 1px 2px -2px hsl(0 0% 0% / 0.10)", + shadow: "0px 4px 8px -1px hsl(0 0% 0% / 0.10), 0px 1px 2px -2px hsl(0 0% 0% / 0.10)", + "shadow-md": "0px 4px 8px -1px hsl(0 0% 0% / 0.10), 0px 2px 4px -2px hsl(0 0% 0% / 0.10)", + "shadow-lg": "0px 4px 8px -1px hsl(0 0% 0% / 0.10), 0px 4px 6px -2px hsl(0 0% 0% / 0.10)", + "shadow-xl": "0px 4px 8px -1px hsl(0 0% 0% / 0.10), 0px 8px 10px -2px hsl(0 0% 0% / 0.10)", + "shadow-2xl": "0px 4px 8px -1px hsl(0 0% 0% / 0.25)", +}; + +/** + * Supabase theme - Mint-green primary on near-white, Outfit font, colorful charts + */ +export const supabaseTheme: ThemeTokens = { + background: "oklch(0.9911 0 0)", + foreground: "oklch(0.2046 0 0)", + card: "oklch(0.9911 0 0)", + cardForeground: "oklch(0.2046 0 0)", + popover: "oklch(0.9911 0 0)", + popoverForeground: "oklch(0.4386 0 0)", + primary: "oklch(0.8348 0.1302 160.9080)", + primaryForeground: "oklch(0.2626 0.0147 166.4589)", + secondary: "oklch(0.9940 0 0)", + secondaryForeground: "oklch(0.2046 0 0)", + muted: "oklch(0.9461 0 0)", + mutedForeground: "oklch(0.2435 0 0)", + accent: "oklch(0.9461 0 0)", + accentForeground: "oklch(0.2435 0 0)", + destructive: "oklch(0.5523 0.1927 32.7272)", + destructiveForeground: "oklch(0.9934 0.0032 17.2118)", + border: "oklch(0.9037 0 0)", + input: "oklch(0.9731 0 0)", + ring: "oklch(0.8348 0.1302 160.9080)", + chart1: "oklch(0.8348 0.1302 160.9080)", + chart2: "oklch(0.6231 0.1880 259.8145)", + chart3: "oklch(0.6056 0.2189 292.7172)", + chart4: "oklch(0.7686 0.1647 70.0804)", + chart5: "oklch(0.6959 0.1491 162.4796)", + sidebar: "oklch(0.9911 0 0)", + sidebarForeground: "oklch(0.5452 0 0)", + sidebarPrimary: "oklch(0.8348 0.1302 160.9080)", + sidebarPrimaryForeground: "oklch(0.2626 0.0147 166.4589)", + sidebarAccent: "oklch(0.9461 0 0)", + sidebarAccentForeground: "oklch(0.2435 0 0)", + sidebarBorder: "oklch(0.9037 0 0)", + sidebarRing: "oklch(0.8348 0.1302 160.9080)", + fontSans: "Outfit, sans-serif", + fontSerif: "ui-serif, Georgia, Cambria, \"Times New Roman\", Times, serif", + fontMono: "monospace", + radius: "0.5rem", + "shadow-2xs": "0px 1px 3px 0px hsl(0 0% 0% / 0.09)", + "shadow-xs": "0px 1px 3px 0px hsl(0 0% 0% / 0.09)", + "shadow-sm": "0px 1px 3px 0px hsl(0 0% 0% / 0.17), 0px 1px 2px -1px hsl(0 0% 0% / 0.17)", + shadow: "0px 1px 3px 0px hsl(0 0% 0% / 0.17), 0px 1px 2px -1px hsl(0 0% 0% / 0.17)", + "shadow-md": "0px 1px 3px 0px hsl(0 0% 0% / 0.17), 0px 2px 4px -1px hsl(0 0% 0% / 0.17)", + "shadow-lg": "0px 1px 3px 0px hsl(0 0% 0% / 0.17), 0px 4px 6px -1px hsl(0 0% 0% / 0.17)", + "shadow-xl": "0px 1px 3px 0px hsl(0 0% 0% / 0.17), 0px 8px 10px -1px hsl(0 0% 0% / 0.17)", + "shadow-2xl": "0px 1px 3px 0px hsl(0 0% 0% / 0.43)", +}; + +/** + * Supabase dark theme + */ +export const supabaseDarkTheme: ThemeTokens = { + background: "oklch(0.1822 0 0)", + foreground: "oklch(0.9288 0.0126 255.5078)", + card: "oklch(0.2046 0 0)", + cardForeground: "oklch(0.9288 0.0126 255.5078)", + popover: "oklch(0.2603 0 0)", + popoverForeground: "oklch(0.7348 0 0)", + primary: "oklch(0.4365 0.1044 156.7556)", + primaryForeground: "oklch(0.9213 0.0135 167.1556)", + secondary: "oklch(0.2603 0 0)", + secondaryForeground: "oklch(0.9851 0 0)", + muted: "oklch(0.2393 0 0)", + mutedForeground: "oklch(0.7122 0 0)", + accent: "oklch(0.3132 0 0)", + accentForeground: "oklch(0.9851 0 0)", + destructive: "oklch(0.3123 0.0852 29.7877)", + destructiveForeground: "oklch(0.9368 0.0045 34.3092)", + border: "oklch(0.2809 0 0)", + input: "oklch(0.2603 0 0)", + ring: "oklch(0.8003 0.1821 151.7110)", + chart1: "oklch(0.8003 0.1821 151.7110)", + chart2: "oklch(0.7137 0.1434 254.6240)", + chart3: "oklch(0.7090 0.1592 293.5412)", + chart4: "oklch(0.8369 0.1644 84.4286)", + chart5: "oklch(0.7845 0.1325 181.9120)", + sidebar: "oklch(0.1822 0 0)", + sidebarForeground: "oklch(0.6301 0 0)", + sidebarPrimary: "oklch(0.4365 0.1044 156.7556)", + sidebarPrimaryForeground: "oklch(0.9213 0.0135 167.1556)", + sidebarAccent: "oklch(0.3132 0 0)", + sidebarAccentForeground: "oklch(0.9851 0 0)", + sidebarBorder: "oklch(0.2809 0 0)", + sidebarRing: "oklch(0.8003 0.1821 151.7110)", + fontSans: "Outfit, sans-serif", + fontSerif: "ui-serif, Georgia, Cambria, \"Times New Roman\", Times, serif", + fontMono: "monospace", + radius: "0.5rem", + "shadow-2xs": "0px 1px 3px 0px hsl(0 0% 0% / 0.09)", + "shadow-xs": "0px 1px 3px 0px hsl(0 0% 0% / 0.09)", + "shadow-sm": "0px 1px 3px 0px hsl(0 0% 0% / 0.17), 0px 1px 2px -1px hsl(0 0% 0% / 0.17)", + shadow: "0px 1px 3px 0px hsl(0 0% 0% / 0.17), 0px 1px 2px -1px hsl(0 0% 0% / 0.17)", + "shadow-md": "0px 1px 3px 0px hsl(0 0% 0% / 0.17), 0px 2px 4px -1px hsl(0 0% 0% / 0.17)", + "shadow-lg": "0px 1px 3px 0px hsl(0 0% 0% / 0.17), 0px 4px 6px -1px hsl(0 0% 0% / 0.17)", + "shadow-xl": "0px 1px 3px 0px hsl(0 0% 0% / 0.17), 0px 8px 10px -1px hsl(0 0% 0% / 0.17)", + "shadow-2xl": "0px 1px 3px 0px hsl(0 0% 0% / 0.43)", +}; + +/** + * Terminal theme - Green-on-black CRT aesthetic, VT323 monospace font, neon glow shadows + */ +export const terminalTheme: ThemeTokens = { + background: "oklch(0 0 0)", + foreground: "oklch(0.8686 0.2776 144.4661)", + card: "oklch(0.1149 0 0)", + cardForeground: "oklch(0.8686 0.2776 144.4661)", + popover: "oklch(0 0 0)", + popoverForeground: "oklch(0.8686 0.2776 144.4661)", + primary: "oklch(0.8686 0.2776 144.4661)", + primaryForeground: "oklch(0 0 0)", + secondary: "oklch(0.3053 0.1039 142.4953)", + secondaryForeground: "oklch(0.8686 0.2776 144.4661)", + muted: "oklch(0.1887 0.0642 142.4953)", + mutedForeground: "oklch(0.5638 0.1872 143.2450)", + accent: "oklch(0.8686 0.2776 144.4661)", + accentForeground: "oklch(0 0 0)", + destructive: "oklch(0.6280 0.2577 29.2339)", + destructiveForeground: "oklch(1.0000 0 0)", + border: "oklch(0.3053 0.1039 142.4953)", + input: "oklch(0 0 0)", + ring: "oklch(0.8686 0.2776 144.4661)", + chart1: "oklch(0.8686 0.2776 144.4661)", + chart2: "oklch(0.5638 0.1872 143.2450)", + chart3: "oklch(0.3053 0.1039 142.4953)", + chart4: "oklch(0.1179 0.0327 343.3438)", + chart5: "oklch(0.8686 0.2776 144.4661)", + sidebar: "oklch(0.1149 0 0)", + sidebarForeground: "oklch(0.8686 0.2776 144.4661)", + sidebarPrimary: "oklch(0.8686 0.2776 144.4661)", + sidebarPrimaryForeground: "oklch(0 0 0)", + sidebarAccent: "oklch(0.3053 0.1039 142.4953)", + sidebarAccentForeground: "oklch(0.8686 0.2776 144.4661)", + sidebarBorder: "oklch(0.3053 0.1039 142.4953)", + sidebarRing: "oklch(0.8686 0.2776 144.4661)", + fontSans: "\"VT323\", \"Courier New\", monospace", + fontSerif: "Georgia, serif", + fontMono: "\"VT323\", monospace", + radius: "0rem", + "shadow-2xs": "0px 0px 10px 1px hsl(135.2941 100% 50% / 0.10)", + "shadow-xs": "0px 0px 10px 1px hsl(135.2941 100% 50% / 0.10)", + "shadow-sm": "0px 0px 10px 1px hsl(135.2941 100% 50% / 0.20), 0px 1px 2px 0px hsl(135.2941 100% 50% / 0.20)", + shadow: "0px 0px 10px 1px hsl(135.2941 100% 50% / 0.20), 0px 1px 2px 0px hsl(135.2941 100% 50% / 0.20)", + "shadow-md": "0px 0px 10px 1px hsl(135.2941 100% 50% / 0.20), 0px 2px 4px 0px hsl(135.2941 100% 50% / 0.20)", + "shadow-lg": "0px 0px 10px 1px hsl(135.2941 100% 50% / 0.20), 0px 4px 6px 0px hsl(135.2941 100% 50% / 0.20)", + "shadow-xl": "0px 0px 10px 1px hsl(135.2941 100% 50% / 0.20), 0px 8px 10px 0px hsl(135.2941 100% 50% / 0.20)", + "shadow-2xl": "0px 0px 10px 1px hsl(135.2941 100% 50% / 0.50)", +}; + +/** + * Terminal dark theme (intensified glow) + */ +export const terminalDarkTheme: ThemeTokens = { + background: "oklch(0 0 0)", + foreground: "oklch(0.8686 0.2776 144.4661)", + card: "oklch(0.1149 0 0)", + cardForeground: "oklch(0.8686 0.2776 144.4661)", + popover: "oklch(0 0 0)", + popoverForeground: "oklch(0.8686 0.2776 144.4661)", + primary: "oklch(0.8686 0.2776 144.4661)", + primaryForeground: "oklch(0 0 0)", + secondary: "oklch(0.3053 0.1039 142.4953)", + secondaryForeground: "oklch(0.8686 0.2776 144.4661)", + muted: "oklch(0.1887 0.0642 142.4953)", + mutedForeground: "oklch(0.5638 0.1872 143.2450)", + accent: "oklch(0.8686 0.2776 144.4661)", + accentForeground: "oklch(0 0 0)", + destructive: "oklch(0.6280 0.2577 29.2339)", + destructiveForeground: "oklch(1.0000 0 0)", + border: "oklch(0.3053 0.1039 142.4953)", + input: "oklch(0 0 0)", + ring: "oklch(0.8686 0.2776 144.4661)", + chart1: "oklch(0.8686 0.2776 144.4661)", + chart2: "oklch(0.5638 0.1872 143.2450)", + chart3: "oklch(0.3053 0.1039 142.4953)", + chart4: "oklch(0.1179 0.0327 343.3438)", + chart5: "oklch(0.8686 0.2776 144.4661)", + sidebar: "oklch(0.1149 0 0)", + sidebarForeground: "oklch(0.8686 0.2776 144.4661)", + sidebarPrimary: "oklch(0.8686 0.2776 144.4661)", + sidebarPrimaryForeground: "oklch(0 0 0)", + sidebarAccent: "oklch(0.3053 0.1039 142.4953)", + sidebarAccentForeground: "oklch(0.8686 0.2776 144.4661)", + sidebarBorder: "oklch(0.3053 0.1039 142.4953)", + sidebarRing: "oklch(0.8686 0.2776 144.4661)", + fontSans: "\"VT323\", \"Courier New\", monospace", + fontSerif: "Georgia, serif", + fontMono: "\"VT323\", monospace", + radius: "0rem", + "shadow-2xs": "0px 0px 15px 2px hsl(135.2941 100% 50% / 0.20)", + "shadow-xs": "0px 0px 15px 2px hsl(135.2941 100% 50% / 0.20)", + "shadow-sm": "0px 0px 15px 2px hsl(135.2941 100% 50% / 0.40), 0px 1px 2px 1px hsl(135.2941 100% 50% / 0.40)", + shadow: "0px 0px 15px 2px hsl(135.2941 100% 50% / 0.40), 0px 1px 2px 1px hsl(135.2941 100% 50% / 0.40)", + "shadow-md": "0px 0px 15px 2px hsl(135.2941 100% 50% / 0.40), 0px 2px 4px 1px hsl(135.2941 100% 50% / 0.40)", + "shadow-lg": "0px 0px 15px 2px hsl(135.2941 100% 50% / 0.40), 0px 4px 6px 1px hsl(135.2941 100% 50% / 0.40)", + "shadow-xl": "0px 0px 15px 2px hsl(135.2941 100% 50% / 0.40), 0px 8px 10px 1px hsl(135.2941 100% 50% / 0.40)", + "shadow-2xl": "0px 0px 15px 2px hsl(135.2941 100% 50% / 1.00)", +}; + +/** + * WhatsApp theme - Teal primary with green accents, rounded 1rem radius, system fonts + */ +export const whatsappTheme: ThemeTokens = { + background: "oklch(0.9605 0.0046 258.3248)", + foreground: "oklch(0.2153 0.0187 235.1251)", + card: "oklch(1.0000 0 0)", + cardForeground: "oklch(0.2153 0.0187 235.1251)", + popover: "oklch(1.0000 0 0)", + popoverForeground: "oklch(0.2153 0.0187 235.1251)", + primary: "oklch(0.4335 0.0754 182.2315)", + primaryForeground: "oklch(1.0000 0 0)", + secondary: "oklch(0.9644 0.0208 166.1014)", + secondaryForeground: "oklch(0.4335 0.0754 182.2315)", + muted: "oklch(0.9605 0.0046 258.3248)", + mutedForeground: "oklch(0.5589 0.0255 233.7233)", + accent: "oklch(0.7610 0.2015 149.7403)", + accentForeground: "oklch(1.0000 0 0)", + destructive: "oklch(0.6257 0.2058 29.0773)", + destructiveForeground: "oklch(1.0000 0 0)", + border: "oklch(0.9436 0.0051 228.8204)", + input: "oklch(0.9436 0.0051 228.8204)", + ring: "oklch(0.7610 0.2015 149.7403)", + chart1: "oklch(0.7610 0.2015 149.7403)", + chart2: "oklch(0.4335 0.0754 182.2315)", + chart3: "oklch(0.5762 0.0995 182.3964)", + chart4: "oklch(0.7356 0.1370 232.8053)", + chart5: "oklch(0.6509 0.1283 170.4258)", + sidebar: "oklch(1.0000 0 0)", + sidebarForeground: "oklch(0.2153 0.0187 235.1251)", + sidebarPrimary: "oklch(0.4335 0.0754 182.2315)", + sidebarPrimaryForeground: "oklch(1.0000 0 0)", + sidebarAccent: "oklch(0.9644 0.0208 166.1014)", + sidebarAccentForeground: "oklch(0.4335 0.0754 182.2315)", + sidebarBorder: "oklch(0.9436 0.0051 228.8204)", + sidebarRing: "oklch(0.7610 0.2015 149.7403)", + fontSans: "Segoe UI, Helvetica Neue, Helvetica, Lucida Grande, Arial, Ubuntu, Cantarell, Fira Sans, sans-serif", + fontSerif: "Georgia, serif", + fontMono: "SFMono-Regular, Menlo, Monaco, Consolas, Liberation Mono, Courier New, monospace", + radius: "1rem", + "shadow-2xs": "0px 2px 10px 0px hsl(0 0% 0% / 0.05)", + "shadow-xs": "0px 2px 10px 0px hsl(0 0% 0% / 0.05)", + "shadow-sm": "0px 2px 10px 0px hsl(0 0% 0% / 0.10), 0px 1px 2px -1px hsl(0 0% 0% / 0.10)", + shadow: "0px 2px 10px 0px hsl(0 0% 0% / 0.10), 0px 1px 2px -1px hsl(0 0% 0% / 0.10)", + "shadow-md": "0px 2px 10px 0px hsl(0 0% 0% / 0.10), 0px 2px 4px -1px hsl(0 0% 0% / 0.10)", + "shadow-lg": "0px 2px 10px 0px hsl(0 0% 0% / 0.10), 0px 4px 6px -1px hsl(0 0% 0% / 0.10)", + "shadow-xl": "0px 2px 10px 0px hsl(0 0% 0% / 0.10), 0px 8px 10px -1px hsl(0 0% 0% / 0.10)", + "shadow-2xl": "0px 2px 10px 0px hsl(0 0% 0% / 0.25)", +}; + +/** + * WhatsApp dark theme + */ +export const whatsappDarkTheme: ThemeTokens = { + background: "oklch(0.1854 0.0182 238.2143)", + foreground: "oklch(0.9436 0.0051 228.8204)", + card: "oklch(0.2848 0.0230 235.6578)", + cardForeground: "oklch(0.9436 0.0051 228.8204)", + popover: "oklch(0.2848 0.0230 235.6578)", + popoverForeground: "oklch(0.9436 0.0051 228.8204)", + primary: "oklch(0.6509 0.1283 170.4258)", + primaryForeground: "oklch(0.2153 0.0187 235.1251)", + secondary: "oklch(0.2933 0.0423 172.8195)", + secondaryForeground: "oklch(0.6509 0.1283 170.4258)", + muted: "oklch(0.2456 0.0195 239.1061)", + mutedForeground: "oklch(0.6637 0.0236 235.1968)", + accent: "oklch(0.7610 0.2015 149.7403)", + accentForeground: "oklch(0.2153 0.0187 235.1251)", + destructive: "oklch(0.6257 0.2058 29.0773)", + destructiveForeground: "oklch(0.9436 0.0051 228.8204)", + border: "oklch(0.3351 0.0253 234.8586)", + input: "oklch(0.3351 0.0253 234.8586)", + ring: "oklch(0.6509 0.1283 170.4258)", + chart1: "oklch(0.7610 0.2015 149.7403)", + chart2: "oklch(0.6509 0.1283 170.4258)", + chart3: "oklch(0.5762 0.0995 182.3964)", + chart4: "oklch(0.7356 0.1370 232.8053)", + chart5: "oklch(0.4335 0.0754 182.2315)", + sidebar: "oklch(0.2153 0.0187 235.1251)", + sidebarForeground: "oklch(0.9436 0.0051 228.8204)", + sidebarPrimary: "oklch(0.6509 0.1283 170.4258)", + sidebarPrimaryForeground: "oklch(0.2153 0.0187 235.1251)", + sidebarAccent: "oklch(0.2933 0.0423 172.8195)", + sidebarAccentForeground: "oklch(0.6509 0.1283 170.4258)", + sidebarBorder: "oklch(0.3351 0.0253 234.8586)", + sidebarRing: "oklch(0.6509 0.1283 170.4258)", + fontSans: "Segoe UI, Helvetica Neue, Helvetica, Lucida Grande, Arial, Ubuntu, Cantarell, Fira Sans, sans-serif", + fontSerif: "Georgia, serif", + fontMono: "SFMono-Regular, Menlo, Monaco, Consolas, Liberation Mono, Courier New, monospace", + radius: "1rem", + "shadow-2xs": "0px 4px 12px 0px hsl(0 0% 0% / 0.20)", + "shadow-xs": "0px 4px 12px 0px hsl(0 0% 0% / 0.20)", + "shadow-sm": "0px 4px 12px 0px hsl(0 0% 0% / 0.40), 0px 1px 2px -1px hsl(0 0% 0% / 0.40)", + shadow: "0px 4px 12px 0px hsl(0 0% 0% / 0.40), 0px 1px 2px -1px hsl(0 0% 0% / 0.40)", + "shadow-md": "0px 4px 12px 0px hsl(0 0% 0% / 0.40), 0px 2px 4px -1px hsl(0 0% 0% / 0.40)", + "shadow-lg": "0px 4px 12px 0px hsl(0 0% 0% / 0.40), 0px 4px 6px -1px hsl(0 0% 0% / 0.40)", + "shadow-xl": "0px 4px 12px 0px hsl(0 0% 0% / 0.40), 0px 8px 10px -1px hsl(0 0% 0% / 0.40)", + "shadow-2xl": "0px 4px 12px 0px hsl(0 0% 0% / 1.00)", +}; + /** * Helper to create a custom theme with defaults filled in */