├── .editorconfig ├── .eslintrc ├── .github └── workflows │ └── test.yml ├── .gitignore ├── .prettierignore ├── .prettierrc ├── .vscode └── extensions.json ├── LICENSE ├── README.md ├── cmdk-solid ├── package.json ├── src │ ├── command-score.ts │ └── index.tsx └── tsup.config.ts ├── env.d.ts ├── package.json ├── playwright.config.ts ├── pnpm-lock.yaml ├── pnpm-workspace.yaml ├── test ├── .gitignore ├── README.md ├── app.config.ts ├── package.json ├── public │ └── favicon.ico ├── src │ ├── app.css │ ├── app.tsx │ ├── entry-client.tsx │ ├── entry-server.tsx │ ├── global.d.ts │ └── routes │ │ ├── [...404].tsx │ │ ├── dialog.tsx │ │ ├── documentation.tsx │ │ ├── errors.tsx │ │ ├── group-filter.tsx │ │ ├── group.tsx │ │ ├── huge.tsx │ │ ├── index.tsx │ │ ├── item-advanced.tsx │ │ ├── item.tsx │ │ ├── keybinds.tsx │ │ ├── numeric.tsx │ │ ├── portal.tsx │ │ ├── props.tsx │ │ └── sort.tsx ├── tests │ ├── basic.spec.ts │ ├── dialog.test.ts │ ├── group-filter.test.ts │ ├── group.test.ts │ ├── item.test.ts │ ├── keybind.test.ts │ ├── numeric.test.ts │ ├── props.test.ts │ └── sort.spec.ts └── tsconfig.json ├── tsconfig.json ├── vitest.config.ts └── website ├── .gitignore ├── README.md ├── app.config.ts ├── package.json ├── postcss.config.js ├── public ├── favicon.ico ├── favicon.svg ├── grid.svg ├── inter-var-latin.woff2 ├── kiz.jpg ├── line.svg ├── og.png ├── paco.png ├── rauno.jpeg ├── robots.txt └── vercel.svg ├── src ├── app.tsx ├── components │ ├── Counter.css │ ├── Counter.tsx │ ├── cmdk │ │ ├── framer.tsx │ │ ├── linear.tsx │ │ ├── raycast.tsx │ │ ├── shadcn.tsx │ │ └── vercel.tsx │ ├── code │ │ ├── code.module.scss │ │ └── index.tsx │ ├── icons │ │ ├── icons.module.scss │ │ └── index.tsx │ ├── index.tsx │ └── ui │ │ ├── command.tsx │ │ └── dialog.tsx ├── entry-client.tsx ├── entry-server.tsx ├── global.d.ts ├── lib │ └── utils.ts ├── routes │ ├── [...404].tsx │ └── index.tsx └── styles │ ├── cmdk │ ├── framer.scss │ ├── linear.scss │ ├── raycast.scss │ └── vercel.scss │ ├── globals.scss │ └── index.module.scss ├── tailwind.config.js └── tsconfig.json /.editorconfig: -------------------------------------------------------------------------------- 1 | # editorconfig.org 2 | root = true 3 | 4 | [*] 5 | indent_style = space 6 | indent_size = 2 7 | end_of_line = lf 8 | charset = utf-8 9 | trim_trailing_whitespace = true 10 | insert_final_newline = true 11 | -------------------------------------------------------------------------------- /.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "root": true, 3 | "parser": "@typescript-eslint/parser", 4 | "plugins": ["@typescript-eslint", "no-only-tests", "eslint-comments"], 5 | "ignorePatterns": ["node_modules", "dist", "dev", "tsup.config.ts", "vitest.config.ts"], 6 | "parserOptions": { 7 | "project": "./tsconfig.json", 8 | "tsconfigRootDir": ".", 9 | "sourceType": "module" 10 | }, 11 | "rules": { 12 | "prefer-const": "warn", 13 | "no-console": "warn", 14 | "no-debugger": "warn", 15 | "@typescript-eslint/no-unused-vars": [ 16 | "warn", 17 | { 18 | "argsIgnorePattern": "^_", 19 | "varsIgnorePattern": "^_", 20 | "caughtErrorsIgnorePattern": "^_" 21 | } 22 | ], 23 | "@typescript-eslint/no-unnecessary-type-assertion": "warn", 24 | "@typescript-eslint/no-unnecessary-condition": "warn", 25 | "@typescript-eslint/no-useless-empty-export": "warn", 26 | "no-only-tests/no-only-tests": "warn", 27 | "eslint-comments/no-unused-disable": "warn" 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /.github/workflows/test.yml: -------------------------------------------------------------------------------- 1 | name: Run E2E tests 2 | 3 | on: 4 | push: 5 | branches: [main] 6 | pull_request: 7 | branches: [main] 8 | 9 | jobs: 10 | test: 11 | runs-on: ubuntu-latest 12 | 13 | steps: 14 | - uses: actions/checkout@v3 15 | - uses: pnpm/action-setup@v2 # respects packageManager in package.json 16 | - uses: actions/setup-node@v3 17 | with: 18 | cache: 'pnpm' 19 | - run: pnpm install 20 | - run: pnpm build 21 | - run: pnpm test:format 22 | - run: pnpm playwright install --with-deps 23 | - run: pnpm test || exit 1 24 | - name: Upload test results 25 | if: always() 26 | uses: actions/upload-artifact@v4 27 | with: 28 | name: playwright-report 29 | path: playwright-report.json 30 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | .idea 3 | .env 4 | .env.local 5 | .env.development 6 | .env.development.local 7 | *.log 8 | 9 | .vercel/ 10 | .turbo/ 11 | .next/ 12 | .yalc/ 13 | build/ 14 | dist/ 15 | node_modules/ 16 | .vercel 17 | test-results/ 18 | 19 | # tsup 20 | tsup.config.bundled_*.{m,c,}s -------------------------------------------------------------------------------- /.prettierignore: -------------------------------------------------------------------------------- 1 | .output 2 | .vinxi 3 | dist 4 | pnpm-lock.yaml 5 | .pnpm-store 6 | .vercel 7 | -------------------------------------------------------------------------------- /.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "semi": false, 3 | "singleQuote": true, 4 | "tabWidth": 2, 5 | "trailingComma": "all", 6 | "printWidth": 120 7 | } 8 | -------------------------------------------------------------------------------- /.vscode/extensions.json: -------------------------------------------------------------------------------- 1 | { 2 | "recommendations": ["esbenp.prettier-vscode", "dbaeumer.vscode-eslint"] 3 | } 4 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2024 Kieran Molloy 4 | 5 | CMDK Solid was ported from CMDK (https://github.com/pacocoursey) 6 | 7 | --- 8 | Copyright (c) 2022 Paco Coursey 9 | 10 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 13 | 14 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -------------------------------------------------------------------------------- /cmdk-solid/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "cmdk-solid", 3 | "version": "1.1.2", 4 | "license": "MIT", 5 | "main": "./dist/index.cjs", 6 | "module": "./dist/index.js", 7 | "types": "./dist/index.d.ts", 8 | "files": [ 9 | "dist" 10 | ], 11 | "exports": { 12 | "solid": "./dist/index.jsx", 13 | "import": { 14 | "types": "./dist/index.d.ts", 15 | "default": "./dist/index.js" 16 | }, 17 | "require": { 18 | "types": "./dist/index.d.cts", 19 | "default": "./dist/index.cjs" 20 | } 21 | }, 22 | "scripts": { 23 | "prepublishOnly": "cp ../README.md . && pnpm build", 24 | "postpublish": "rm README.md", 25 | "build": "tsup src", 26 | "dev": "tsup src --watch" 27 | }, 28 | "peerDependencies": { 29 | "solid-js": "^1.8.0" 30 | }, 31 | "dependencies": { 32 | "@kobalte/core": "^0.12.4", 33 | "@kobalte/utils": "^0.9.0", 34 | "@solid-primitives/deep": "^0.2.7", 35 | "@solid-primitives/mutation-observer": "^1.1.17" 36 | }, 37 | "devDependencies": {}, 38 | "sideEffects": false, 39 | "repository": { 40 | "type": "git", 41 | "url": "git+https://github.com/create-signal/cmdk-solid.git" 42 | }, 43 | "bugs": { 44 | "url": "https://github.com/create-signal/cmdk-solid/issues" 45 | }, 46 | "homepage": "https://github.com/create-signal/cmdk-solid#readme", 47 | "author": { 48 | "name": "Kieran Molloy", 49 | "url": "https://github.com/create-signal" 50 | }, 51 | "browser": {}, 52 | "typesVersions": {}, 53 | "type": "module" 54 | } 55 | -------------------------------------------------------------------------------- /cmdk-solid/src/command-score.ts: -------------------------------------------------------------------------------- 1 | // The scores are arranged so that a continuous match of characters will 2 | // result in a total score of 1. 3 | // 4 | // The best case, this character is a match, and either this is the start 5 | // of the string, or the previous character was also a match. 6 | var SCORE_CONTINUE_MATCH = 1, 7 | // A new match at the start of a word scores better than a new match 8 | // elsewhere as it's more likely that the user will type the starts 9 | // of fragments. 10 | // NOTE: We score word jumps between spaces slightly higher than slashes, brackets 11 | // hyphens, etc. 12 | SCORE_SPACE_WORD_JUMP = 0.9, 13 | SCORE_NON_SPACE_WORD_JUMP = 0.8, 14 | // Any other match isn't ideal, but we include it for completeness. 15 | SCORE_CHARACTER_JUMP = 0.17, 16 | // If the user transposed two letters, it should be significantly penalized. 17 | // 18 | // i.e. "ouch" is more likely than "curtain" when "uc" is typed. 19 | SCORE_TRANSPOSITION = 0.1, 20 | // The goodness of a match should decay slightly with each missing 21 | // character. 22 | // 23 | // i.e. "bad" is more likely than "bard" when "bd" is typed. 24 | // 25 | // This will not change the order of suggestions based on SCORE_* until 26 | // 100 characters are inserted between matches. 27 | PENALTY_SKIPPED = 0.999, 28 | // The goodness of an exact-case match should be higher than a 29 | // case-insensitive match by a small amount. 30 | // 31 | // i.e. "HTML" is more likely than "haml" when "HM" is typed. 32 | // 33 | // This will not change the order of suggestions based on SCORE_* until 34 | // 1000 characters are inserted between matches. 35 | PENALTY_CASE_MISMATCH = 0.9999, 36 | // Match higher for letters closer to the beginning of the word 37 | PENALTY_DISTANCE_FROM_START = 0.9, 38 | // If the word has more characters than the user typed, it should 39 | // be penalised slightly. 40 | // 41 | // i.e. "html" is more likely than "html5" if I type "html". 42 | // 43 | // However, it may well be the case that there's a sensible secondary 44 | // ordering (like alphabetical) that it makes sense to rely on when 45 | // there are many prefix matches, so we don't make the penalty increase 46 | // with the number of tokens. 47 | PENALTY_NOT_COMPLETE = 0.99 48 | 49 | var IS_GAP_REGEXP = /[\\\/_+.#"@\[\(\{&]/, 50 | COUNT_GAPS_REGEXP = /[\\\/_+.#"@\[\(\{&]/g, 51 | IS_SPACE_REGEXP = /[\s-]/, 52 | COUNT_SPACE_REGEXP = /[\s-]/g 53 | 54 | function commandScoreInner( 55 | string: string, 56 | abbreviation: string, 57 | lowerString: string, 58 | lowerAbbreviation: string, 59 | stringIndex: number, 60 | abbreviationIndex: number, 61 | memoizedResults: any, 62 | ) { 63 | if (abbreviationIndex === abbreviation.length) { 64 | if (stringIndex === string.length) { 65 | return SCORE_CONTINUE_MATCH 66 | } 67 | return PENALTY_NOT_COMPLETE 68 | } 69 | 70 | var memoizeKey = `${stringIndex},${abbreviationIndex}` 71 | if (memoizedResults[memoizeKey] !== undefined) { 72 | return memoizedResults[memoizeKey] 73 | } 74 | 75 | var abbreviationChar = lowerAbbreviation.charAt(abbreviationIndex) 76 | var index = lowerString.indexOf(abbreviationChar, stringIndex) 77 | var highScore = 0 78 | 79 | var score, transposedScore, wordBreaks, spaceBreaks 80 | 81 | while (index >= 0) { 82 | score = commandScoreInner( 83 | string, 84 | abbreviation, 85 | lowerString, 86 | lowerAbbreviation, 87 | index + 1, 88 | abbreviationIndex + 1, 89 | memoizedResults, 90 | ) 91 | if (score > highScore) { 92 | if (index === stringIndex) { 93 | score *= SCORE_CONTINUE_MATCH 94 | } else if (IS_GAP_REGEXP.test(string.charAt(index - 1))) { 95 | score *= SCORE_NON_SPACE_WORD_JUMP 96 | wordBreaks = string.slice(stringIndex, index - 1).match(COUNT_GAPS_REGEXP) 97 | if (wordBreaks && stringIndex > 0) { 98 | score *= Math.pow(PENALTY_SKIPPED, wordBreaks.length) 99 | } 100 | } else if (IS_SPACE_REGEXP.test(string.charAt(index - 1))) { 101 | score *= SCORE_SPACE_WORD_JUMP 102 | spaceBreaks = string.slice(stringIndex, index - 1).match(COUNT_SPACE_REGEXP) 103 | if (spaceBreaks && stringIndex > 0) { 104 | score *= Math.pow(PENALTY_SKIPPED, spaceBreaks.length) 105 | } 106 | } else { 107 | score *= SCORE_CHARACTER_JUMP 108 | if (stringIndex > 0) { 109 | score *= Math.pow(PENALTY_SKIPPED, index - stringIndex) 110 | } 111 | } 112 | 113 | if (string.charAt(index) !== abbreviation.charAt(abbreviationIndex)) { 114 | score *= PENALTY_CASE_MISMATCH 115 | } 116 | } 117 | 118 | if ( 119 | (score < SCORE_TRANSPOSITION && 120 | lowerString.charAt(index - 1) === lowerAbbreviation.charAt(abbreviationIndex + 1)) || 121 | (lowerAbbreviation.charAt(abbreviationIndex + 1) === lowerAbbreviation.charAt(abbreviationIndex) && // allow duplicate letters. Ref #7428 122 | lowerString.charAt(index - 1) !== lowerAbbreviation.charAt(abbreviationIndex)) 123 | ) { 124 | transposedScore = commandScoreInner( 125 | string, 126 | abbreviation, 127 | lowerString, 128 | lowerAbbreviation, 129 | index + 1, 130 | abbreviationIndex + 2, 131 | memoizedResults, 132 | ) 133 | 134 | if (transposedScore * SCORE_TRANSPOSITION > score) { 135 | score = transposedScore * SCORE_TRANSPOSITION 136 | } 137 | } 138 | 139 | if (score > highScore) { 140 | highScore = score 141 | } 142 | 143 | index = lowerString.indexOf(abbreviationChar, index + 1) 144 | } 145 | 146 | memoizedResults[memoizeKey] = highScore 147 | return highScore 148 | } 149 | 150 | function formatInput(string: string) { 151 | // convert all valid space characters to space so they match each other 152 | return string.toLowerCase().replace(COUNT_SPACE_REGEXP, ' ') 153 | } 154 | 155 | export function commandScore(string: string, abbreviation: string, aliases?: string[]): number { 156 | /* NOTE: 157 | * in the original, we used to do the lower-casing on each recursive call, but this meant that toLowerCase() 158 | * was the dominating cost in the algorithm, passing both is a little ugly, but considerably faster. 159 | */ 160 | string = aliases && aliases.length > 0 ? `${string + ' ' + aliases.join(' ')}` : string 161 | return commandScoreInner(string, abbreviation, formatInput(string), formatInput(abbreviation), 0, 0, {}) 162 | } 163 | -------------------------------------------------------------------------------- /cmdk-solid/tsup.config.ts: -------------------------------------------------------------------------------- 1 | import { defineConfig } from 'tsup' 2 | import * as preset from 'tsup-preset-solid' 3 | 4 | const preset_options: preset.PresetOptions = { 5 | // array or single object 6 | entries: [ 7 | // default entry (index) 8 | { 9 | // entries with '.tsx' extension will have `solid` export condition generated 10 | entry: 'src/index.tsx', 11 | // will generate a separate development entry 12 | dev_entry: false, 13 | }, 14 | ], 15 | // Set to `true` to remove all `console.*` calls and `debugger` statements in prod builds 16 | drop_console: false, 17 | // Set to `true` to generate a CommonJS build alongside ESM 18 | cjs: true, 19 | } 20 | 21 | const CI = 22 | process.env['CI'] === 'true' || 23 | process.env['GITHUB_ACTIONS'] === 'true' || 24 | process.env['CI'] === '"1"' || 25 | process.env['GITHUB_ACTIONS'] === '"1"' 26 | 27 | export default defineConfig((config) => { 28 | const watching = !!config.watch 29 | 30 | const parsed_options = preset.parsePresetOptions(preset_options, watching) 31 | 32 | if (!watching && !CI) { 33 | const package_fields = preset.generatePackageExports(parsed_options) 34 | 35 | console.log(`package.json: \n\n${JSON.stringify(package_fields, null, 2)}\n\n`) 36 | 37 | // will update ./package.json with the correct export fields 38 | preset.writePackageJson(package_fields) 39 | } 40 | 41 | return preset.generateTsupOptions(parsed_options) 42 | }) 43 | -------------------------------------------------------------------------------- /env.d.ts: -------------------------------------------------------------------------------- 1 | declare global { 2 | interface ImportMeta { 3 | env: { 4 | NODE_ENV: 'production' | 'development' 5 | PROD: boolean 6 | DEV: boolean 7 | } 8 | } 9 | namespace NodeJS { 10 | interface ProcessEnv { 11 | NODE_ENV: 'production' | 'development' 12 | PROD: boolean 13 | DEV: boolean 14 | } 15 | } 16 | } 17 | 18 | export {} 19 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "cmdk-solid-root", 3 | "version": "1.1.0", 4 | "description": "Fast, Unstyled, Command Menu for SolidJS", 5 | "license": "MIT", 6 | "contributors": [], 7 | "repository": { 8 | "type": "git", 9 | "url": "git+https://github.com/create-signal/cmdk-solid.git" 10 | }, 11 | "homepage": "https://github.com/create-signal/cmdk-solid#readme", 12 | "bugs": { 13 | "url": "https://github.com/create-signal/cmdk-solid/issues" 14 | }, 15 | "files": [ 16 | "dist" 17 | ], 18 | "private": true, 19 | "sideEffects": false, 20 | "type": "module", 21 | "main": "./dist/index.js", 22 | "module": "./dist/index.js", 23 | "types": "./dist/index.d.ts", 24 | "browser": {}, 25 | "exports": { 26 | "solid": { 27 | "development": "./dist/dev.jsx", 28 | "import": "./dist/index.jsx" 29 | }, 30 | "development": { 31 | "import": { 32 | "types": "./dist/index.d.ts", 33 | "default": "./dist/dev.js" 34 | } 35 | }, 36 | "import": { 37 | "types": "./dist/index.d.ts", 38 | "default": "./dist/index.js" 39 | } 40 | }, 41 | "typesVersions": {}, 42 | "scripts": { 43 | "build": "pnpm -F cmdk-solid build", 44 | "dev": "pnpm -F cmdk-solid build --watch", 45 | "website": "pnpm -F website dev", 46 | "testsite": "pnpm -F test dev", 47 | "format": "prettier '**/*.{js,jsx,ts,tsx,json,md,mdx,css,scss,yaml,yml}' --write", 48 | "preinstall": "npx only-allow pnpm", 49 | "test:format": "prettier '**/*.{js,jsx,ts,tsx,json,md,mdx,css,scss,yaml,yml}' --check", 50 | "test": "playwright test" 51 | }, 52 | "peerDependencies": { 53 | "solid-js": "^1.8" 54 | }, 55 | "devDependencies": { 56 | "@playwright/test": "1.49.1", 57 | "@typescript-eslint/eslint-plugin": "^6.1.0", 58 | "@typescript-eslint/parser": "^6.1.0", 59 | "concurrently": "^8.2.0", 60 | "esbuild": "^0.18.15", 61 | "esbuild-plugin-solid": "^0.5.0", 62 | "eslint": "^8.45.0", 63 | "eslint-plugin-eslint-comments": "^3.2.0", 64 | "eslint-plugin-no-only-tests": "^3.1.0", 65 | "jsdom": "^22.1.0", 66 | "prettier": "3.0.0", 67 | "solid-js": "^1.8", 68 | "tsup": "^8.0.0", 69 | "tsup-preset-solid": "^2.0.1", 70 | "typescript": "^5.1.6", 71 | "vite": "^4.4.6", 72 | "vite-plugin-solid": "^2.7.0", 73 | "vitest": "^0.33.0" 74 | }, 75 | "keywords": [ 76 | "solid" 77 | ], 78 | "packageManager": "pnpm@8.6.0", 79 | "engines": { 80 | "node": ">=18", 81 | "pnpm": ">=8.6.0" 82 | } 83 | } 84 | -------------------------------------------------------------------------------- /playwright.config.ts: -------------------------------------------------------------------------------- 1 | import { PlaywrightTestConfig, devices } from '@playwright/test' 2 | 3 | const config: PlaywrightTestConfig = { 4 | forbidOnly: !!process.env.CI, 5 | retries: process.env.CI ? 2 : 0, 6 | reporter: process.env.CI ? [['github'], ['json', { outputFile: 'playwright-report.json' }]] : 'list', 7 | testDir: './test/tests', 8 | use: { 9 | trace: 'on-first-retry', 10 | baseURL: 'http://localhost:3000', 11 | }, 12 | timeout: 30000, 13 | expect: { 14 | timeout: 10000, 15 | }, 16 | ...(!process.env.CI && { workers: 1 }), 17 | webServer: { 18 | command: 'npm run dev', 19 | port: 3000, 20 | reuseExistingServer: !process.env.CI, 21 | cwd: './test', 22 | }, 23 | projects: [ 24 | { 25 | name: 'chromium', 26 | use: { ...devices['Desktop Chrome'] }, 27 | }, 28 | ], 29 | } 30 | 31 | export default config 32 | -------------------------------------------------------------------------------- /pnpm-workspace.yaml: -------------------------------------------------------------------------------- 1 | packages: 2 | - 'website' 3 | - 'cmdk-solid' 4 | - 'test' 5 | -------------------------------------------------------------------------------- /test/.gitignore: -------------------------------------------------------------------------------- 1 | 2 | dist 3 | .solid 4 | .output 5 | .vercel 6 | .netlify 7 | netlify 8 | .vinxi 9 | 10 | # Environment 11 | .env 12 | .env*.local 13 | 14 | # dependencies 15 | /node_modules 16 | 17 | # IDEs and editors 18 | /.idea 19 | .project 20 | .classpath 21 | *.launch 22 | .settings/ 23 | 24 | # Temp 25 | gitignore 26 | 27 | # System Files 28 | .DS_Store 29 | Thumbs.db 30 | -------------------------------------------------------------------------------- /test/README.md: -------------------------------------------------------------------------------- 1 | # SolidStart 2 | 3 | Everything you need to build a Solid project, powered by [`solid-start`](https://start.solidjs.com); 4 | 5 | ## Creating a project 6 | 7 | ```bash 8 | # create a new project in the current directory 9 | npm init solid@latest 10 | 11 | # create a new project in my-app 12 | npm init solid@latest my-app 13 | ``` 14 | 15 | ## Developing 16 | 17 | Once you've created a project and installed dependencies with `npm install` (or `pnpm install` or `yarn`), start a development server: 18 | 19 | ```bash 20 | npm run dev 21 | 22 | # or start the server and open the app in a new browser tab 23 | npm run dev -- --open 24 | ``` 25 | 26 | ## Building 27 | 28 | Solid apps are built with _presets_, which optimise your project for deployment to different environments. 29 | 30 | By default, `npm run build` will generate a Node app that you can run with `npm start`. To use a different preset, add it to the `devDependencies` in `package.json` and specify in your `app.config.js`. 31 | 32 | ## This project was created with the [Solid CLI](https://solid-cli.netlify.app) 33 | -------------------------------------------------------------------------------- /test/app.config.ts: -------------------------------------------------------------------------------- 1 | import { defineConfig } from '@solidjs/start/config' 2 | 3 | export default defineConfig({}) 4 | -------------------------------------------------------------------------------- /test/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "test", 3 | "type": "module", 4 | "scripts": { 5 | "dev": "vinxi dev", 6 | "build": "pnpm -F 'website^...' build && npx vinxi build", 7 | "start": "vinxi start" 8 | }, 9 | "dependencies": { 10 | "@kobalte/core": "^0.12.4", 11 | "@solidjs/router": "^0.13.0", 12 | "@solidjs/start": "^0.7.4", 13 | "cmdk-solid": "workspace:*", 14 | "solid-js": "^1.8.15", 15 | "vinxi": "^0.3.10" 16 | }, 17 | "engines": { 18 | "node": ">=18" 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /test/public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/create-signal/cmdk-solid/3228bd458e924b1fadf30340b124e6e8e08f38d2/test/public/favicon.ico -------------------------------------------------------------------------------- /test/src/app.css: -------------------------------------------------------------------------------- 1 | body { 2 | font-family: Gordita, Roboto, Oxygen, Ubuntu, Cantarell, 'Open Sans', 'Helvetica Neue', sans-serif; 3 | } 4 | 5 | a { 6 | margin-right: 1rem; 7 | } 8 | 9 | main { 10 | text-align: center; 11 | padding: 1em; 12 | margin: 0 auto; 13 | } 14 | 15 | h1 { 16 | color: #335d92; 17 | text-transform: uppercase; 18 | font-size: 4rem; 19 | font-weight: 100; 20 | line-height: 1.1; 21 | margin: 4rem auto; 22 | max-width: 14rem; 23 | } 24 | 25 | p { 26 | max-width: 14rem; 27 | margin: 2rem auto; 28 | line-height: 1.35; 29 | } 30 | 31 | @media (min-width: 480px) { 32 | h1 { 33 | max-width: none; 34 | } 35 | 36 | p { 37 | max-width: none; 38 | } 39 | } 40 | 41 | .increment { 42 | font-family: inherit; 43 | font-size: inherit; 44 | padding: 1em 2em; 45 | color: #335d92; 46 | background-color: rgba(68, 107, 158, 0.1); 47 | border-radius: 2em; 48 | border: 2px solid rgba(68, 107, 158, 0); 49 | outline: none; 50 | width: 200px; 51 | font-variant-numeric: tabular-nums; 52 | } 53 | 54 | .increment:focus { 55 | border: 2px solid #335d92; 56 | } 57 | 58 | .increment:active { 59 | background-color: rgba(68, 107, 158, 0.2); 60 | } 61 | -------------------------------------------------------------------------------- /test/src/app.tsx: -------------------------------------------------------------------------------- 1 | import { Router } from '@solidjs/router' 2 | import { FileRoutes } from '@solidjs/start/router' 3 | import './app.css' 4 | import { Suspense } from 'solid-js' 5 | 6 | export default function App() { 7 | return ( 8 | {props.children}}> 9 | 10 | 11 | ) 12 | } 13 | -------------------------------------------------------------------------------- /test/src/entry-client.tsx: -------------------------------------------------------------------------------- 1 | // @refresh reload 2 | import { mount, StartClient } from '@solidjs/start/client' 3 | 4 | mount(() => , document.getElementById('app')!) 5 | -------------------------------------------------------------------------------- /test/src/entry-server.tsx: -------------------------------------------------------------------------------- 1 | // @refresh reload 2 | import { createHandler, StartServer } from '@solidjs/start/server' 3 | 4 | export default createHandler(() => ( 5 | ( 7 | 8 | 9 | 10 | 11 | 12 | {assets} 13 | 14 | 15 |
{children}
16 | {scripts} 17 | 18 | 19 | )} 20 | /> 21 | )) 22 | -------------------------------------------------------------------------------- /test/src/global.d.ts: -------------------------------------------------------------------------------- 1 | /// 2 | -------------------------------------------------------------------------------- /test/src/routes/[...404].tsx: -------------------------------------------------------------------------------- 1 | import { HttpStatusCode } from '@solidjs/start' 2 | 3 | export default function NotFound() { 4 | return ( 5 |
6 | 7 |

Page Not Found

8 |

9 | Visit{' '} 10 | 11 | start.solidjs.com 12 | {' '} 13 | to learn how to build SolidStart apps. 14 |

15 |
16 | ) 17 | } 18 | -------------------------------------------------------------------------------- /test/src/routes/dialog.tsx: -------------------------------------------------------------------------------- 1 | import { Command } from 'cmdk-solid' 2 | import { createSignal, onMount } from 'solid-js' 3 | 4 | const Page = () => { 5 | const [open, setOpen] = createSignal(false) 6 | 7 | onMount(() => { 8 | setOpen(true) 9 | }) 10 | 11 | return ( 12 |
13 | 14 | 15 | 16 | No results. 17 | console.log('Item selected')}>Item 18 | Value 19 | 20 | 21 |
22 | ) 23 | } 24 | 25 | export default Page 26 | -------------------------------------------------------------------------------- /test/src/routes/documentation.tsx: -------------------------------------------------------------------------------- 1 | import { Command, CommandItemProps, useCommandState } from 'cmdk-solid' 2 | import { For, Show, createEffect, createSignal, onCleanup, onMount } from 'solid-js' 3 | import { Popover } from '@kobalte/core' 4 | 5 | const CommandMenu = () => { 6 | const [open, setOpen] = createSignal(true) 7 | let ref: HTMLDivElement | undefined 8 | 9 | // Toggle the menu when ⌘K is pressed 10 | onMount(() => { 11 | const down = (e: KeyboardEvent) => { 12 | if (e.key === 'k' && (e.metaKey || e.ctrlKey)) { 13 | e.preventDefault() 14 | setOpen((open) => !open) 15 | } 16 | } 17 | 18 | document.addEventListener('keydown', down) 19 | onCleanup(() => document.removeEventListener('keydown', down)) 20 | }) 21 | 22 | return ( 23 |
24 |
25 | 35 | 36 | 37 | 38 | a 39 | b 40 | 41 | c 42 | 43 | 44 | Apple 45 | 46 | 47 | 48 |
49 | ) 50 | } 51 | 52 | const CustomEmpty = () => { 53 | const search = useCommandState((state) => state.search) 54 | 55 | createEffect(() => { 56 | console.log('Searched for', search()) 57 | }) 58 | 59 | return No results found for {search()} 60 | } 61 | 62 | const CommandMenuWithPages = () => { 63 | const [search, setSearch] = createSignal('') 64 | const [pages, setPages] = createSignal([]) 65 | const page = () => pages()[pages().length - 1] 66 | 67 | return ( 68 | { 70 | // Escape goes to previous page 71 | // Backspace goes to previous page when search is empty 72 | if (e.key === 'Escape' || (e.key === 'Backspace' && !search())) { 73 | e.preventDefault() 74 | setPages((pages) => pages.slice(0, -1)) 75 | } 76 | }} 77 | > 78 | 79 | 80 | 81 | setPages([...pages(), 'projects'])}>Search projects… 82 | setPages([...pages(), 'teams'])}>Join a team… 83 | 84 | 85 | 86 | Project A 87 | Project B 88 | 89 | 90 | 91 | Team 1 92 | Team 2 93 | 94 | 95 | 96 | ) 97 | } 98 | 99 | const SubItemsSearch = () => { 100 | const SubItem = (props: CommandItemProps) => { 101 | const search = useCommandState((state) => state.search) 102 | return ( 103 | 104 | 105 | 106 | ) 107 | } 108 | 109 | return ( 110 | 111 | 112 | 113 | Change theme… 114 | Change theme to dark 115 | Change theme to light 116 | 117 | 118 | ) 119 | } 120 | 121 | const asyncResource = async () => { 122 | await new Promise((r) => setTimeout(r, 5000)) 123 | return ['apple', 'banana', 'cherry'] 124 | } 125 | 126 | const AsyncCommandMenu = () => { 127 | const [loading, setLoading] = createSignal(false) 128 | const [items, setItems] = createSignal([]) 129 | 130 | onMount(() => { 131 | async function getItems() { 132 | setLoading(true) 133 | const res = await asyncResource() 134 | setItems(res) 135 | setLoading(false) 136 | } 137 | 138 | getItems() 139 | }) 140 | 141 | return ( 142 | 143 | 144 | 145 | 146 | Fetching words… 147 | 148 | 149 | {(item) => { 150 | return {item} 151 | }} 152 | 153 | 154 | 155 | ) 156 | } 157 | 158 | const PopoverTest = () => { 159 | return ( 160 | 161 | 162 | Toggle popover 163 | 164 | 165 | 166 | 167 | 168 | 169 | 170 | Apple 171 | 172 | 173 | 174 | 175 | 176 | ) 177 | } 178 | 179 | const Page = () => { 180 | return ( 181 |
182 | 183 | 184 | 185 | 186 | 187 | 188 |
189 | ) 190 | } 191 | 192 | export default Page 193 | -------------------------------------------------------------------------------- /test/src/routes/errors.tsx: -------------------------------------------------------------------------------- 1 | import { Command } from 'cmdk-solid' 2 | 3 | const Page = () => { 4 | return ( 5 | 6 | No results. 7 | 8 | Giraffe 9 | Chicken 10 | 11 | 12 | A 13 | B 14 | Z 15 | 16 | 17 | One 18 | Two 19 | Three 20 | 21 | 22 | ) 23 | } 24 | 25 | export default Page 26 | -------------------------------------------------------------------------------- /test/src/routes/group-filter.tsx: -------------------------------------------------------------------------------- 1 | import { Command } from 'cmdk-solid' 2 | import { For, createSignal } from 'solid-js' 3 | 4 | type NavElement = { 5 | title: string 6 | href: string 7 | external?: boolean 8 | status?: 'new' | 'updated' 9 | } 10 | 11 | type NavCategory = { 12 | title: string 13 | items: NavElement[] 14 | } 15 | 16 | const Page = () => { 17 | const [search, setSearch] = createSignal('') 18 | const config: NavCategory[] = [ 19 | { 20 | title: 'Getting Started', 21 | items: [ 22 | { 23 | title: 'Introduction', 24 | href: '/docs/introduction', 25 | }, 26 | { 27 | title: 'Installation', 28 | href: '/docs/installation/overview', 29 | }, 30 | { 31 | title: 'Dark Mode', 32 | href: '/docs/dark-mode/overview', 33 | }, 34 | { 35 | title: 'CLI', 36 | href: '/docs/cli', 37 | }, 38 | { 39 | title: 'Figma', 40 | href: '/docs/figma', 41 | }, 42 | { 43 | title: 'About', 44 | href: '/docs/about', 45 | }, 46 | ], 47 | }, 48 | { 49 | title: 'Visualizations', 50 | items: [ 51 | { 52 | title: 'Bar List', 53 | href: '/docs/components/bar-list', 54 | status: 'new', 55 | }, 56 | { 57 | title: 'Charts', 58 | href: '/docs/components/charts', 59 | }, 60 | { 61 | title: 'Delta Bar', 62 | href: '/docs/components/delta-bar', 63 | }, 64 | { 65 | title: 'Progress', 66 | href: '/docs/components/progress', 67 | }, 68 | { 69 | title: 'Progress Circle', 70 | href: '/docs/components/progress-circle', 71 | }, 72 | ], 73 | }, 74 | { 75 | title: 'UI', 76 | items: [ 77 | { 78 | title: 'Accordion', 79 | href: '/docs/components/accordion', 80 | }, 81 | { 82 | title: 'Alert', 83 | href: '/docs/components/alert', 84 | }, 85 | { 86 | title: 'Alert Dialog', 87 | href: '/docs/components/alert-dialog', 88 | }, 89 | { 90 | title: 'Aspect Ratio', 91 | href: '/docs/components/aspect-ratio', 92 | status: 'new', 93 | }, 94 | { 95 | title: 'Avatar', 96 | href: '/docs/components/avatar', 97 | }, 98 | { 99 | title: 'Badge', 100 | href: '/docs/components/badge', 101 | }, 102 | { 103 | title: 'Badge Delta', 104 | href: '/docs/components/badge-delta', 105 | }, 106 | { 107 | title: 'Breadcrumb', 108 | href: '/docs/components/breadcrumb', 109 | status: 'new', 110 | }, 111 | { 112 | title: 'Button', 113 | href: '/docs/components/button', 114 | }, 115 | { 116 | title: 'Callout', 117 | href: '/docs/components/callout', 118 | }, 119 | { 120 | title: 'Card', 121 | href: '/docs/components/card', 122 | }, 123 | { 124 | title: 'Carousel', 125 | href: '/docs/components/carousel', 126 | }, 127 | { 128 | title: 'Checkbox', 129 | href: '/docs/components/checkbox', 130 | }, 131 | { 132 | title: 'Collapsible', 133 | href: '/docs/components/collapsible', 134 | }, 135 | { 136 | title: 'Combobox', 137 | href: '/docs/components/combobox', 138 | }, 139 | { 140 | title: 'Command', 141 | href: '/docs/components/command', 142 | }, 143 | { 144 | title: 'Context Menu', 145 | href: '/docs/components/context-menu', 146 | }, 147 | { 148 | title: 'Dialog', 149 | href: '/docs/components/dialog', 150 | }, 151 | { 152 | title: 'Drawer', 153 | href: '/docs/components/drawer', 154 | }, 155 | { 156 | title: 'Dropdown Menu', 157 | href: '/docs/components/dropdown-menu', 158 | }, 159 | { 160 | title: 'Hover Card', 161 | href: '/docs/components/hover-card', 162 | }, 163 | { 164 | title: 'Label', 165 | href: '/docs/components/label', 166 | }, 167 | { 168 | title: 'Menubar', 169 | href: '/docs/components/menubar', 170 | }, 171 | { 172 | title: 'Navigation Menu', 173 | href: '/docs/components/navigation-menu', 174 | status: 'new', 175 | }, 176 | { 177 | title: 'Number Field', 178 | href: '/docs/components/number-field', 179 | }, 180 | { 181 | title: 'OTP Field', 182 | href: '/docs/components/otp-field', 183 | status: 'new', 184 | }, 185 | { 186 | title: 'Pagination', 187 | href: '/docs/components/pagination', 188 | }, 189 | { 190 | title: 'Popover', 191 | href: '/docs/components/popover', 192 | }, 193 | { 194 | title: 'Radio Group', 195 | href: '/docs/components/radio-group', 196 | }, 197 | { 198 | title: 'Resizable', 199 | href: '/docs/components/resizable', 200 | }, 201 | { 202 | title: 'Select', 203 | href: '/docs/components/select', 204 | }, 205 | { 206 | title: 'Separator', 207 | href: '/docs/components/separator', 208 | }, 209 | { 210 | title: 'Sheet', 211 | href: '/docs/components/sheet', 212 | }, 213 | { 214 | title: 'Skeleton', 215 | href: '/docs/components/skeleton', 216 | }, 217 | { 218 | title: 'Slider', 219 | href: '/docs/components/slider', 220 | }, 221 | { 222 | title: 'Switch', 223 | href: '/docs/components/switch', 224 | status: 'updated', 225 | }, 226 | { 227 | title: 'Table', 228 | href: '/docs/components/table', 229 | }, 230 | { 231 | title: 'Tabs', 232 | href: '/docs/components/tabs', 233 | }, 234 | { 235 | title: 'Text Field', 236 | href: '/docs/components/text-field', 237 | status: 'new', 238 | }, 239 | { 240 | title: 'Timeline', 241 | href: '/docs/components/timeline', 242 | }, 243 | { 244 | title: 'Toast', 245 | href: '/docs/components/toast', 246 | }, 247 | { 248 | title: 'Toggle', 249 | href: '/docs/components/toggle', 250 | }, 251 | { 252 | title: 'Toggle Group', 253 | href: '/docs/components/toggle-group', 254 | }, 255 | { 256 | title: 'Tooltip', 257 | href: '/docs/components/tooltip', 258 | }, 259 | ], 260 | }, 261 | { 262 | title: 'Layout', 263 | items: [ 264 | { 265 | title: 'Flex', 266 | href: '/docs/components/flex', 267 | }, 268 | { 269 | title: 'Grid', 270 | href: '/docs/components/grid', 271 | }, 272 | ], 273 | }, 274 | ] 275 | 276 | return ( 277 |
278 | 279 | 280 | 281 | No results. 282 | 283 | {(category) => ( 284 | 285 | {(item) => {item.title}} 286 | 287 | )} 288 | 289 | 290 | 291 |
292 | ) 293 | } 294 | 295 | export default Page 296 | -------------------------------------------------------------------------------- /test/src/routes/group.tsx: -------------------------------------------------------------------------------- 1 | import { Command } from 'cmdk-solid' 2 | import { Show, createSignal } from 'solid-js' 3 | 4 | const Page = () => { 5 | const [search, setSearch] = createSignal('') 6 | const [forceMount, setForceMount] = createSignal(false) 7 | 8 | return ( 9 |
10 | 13 | 14 | 15 | 16 | 17 | No results. 18 | 19 | Giraffe 20 | Chicken 21 | 22 | 23 | 24 | A 25 | B 26 | Z 27 | 28 | 29 | 30 | 31 | One 32 | Two 33 | Three 34 | 35 | 36 | 37 | 38 |
39 | ) 40 | } 41 | 42 | export default Page 43 | -------------------------------------------------------------------------------- /test/src/routes/huge.tsx: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/create-signal/cmdk-solid/3228bd458e924b1fadf30340b124e6e8e08f38d2/test/src/routes/huge.tsx -------------------------------------------------------------------------------- /test/src/routes/index.tsx: -------------------------------------------------------------------------------- 1 | import { Command } from 'cmdk-solid' 2 | 3 | const Page = () => { 4 | return ( 5 |
6 | 7 | 8 | 9 | No results. 10 | console.log('Item selected')} class="item"> 11 | Item 12 | 13 | 14 | Value 15 | 16 | 17 | 18 |
19 | ) 20 | } 21 | 22 | export default Page 23 | -------------------------------------------------------------------------------- /test/src/routes/item-advanced.tsx: -------------------------------------------------------------------------------- 1 | import { Command } from 'cmdk-solid' 2 | import { createSignal } from 'solid-js' 3 | 4 | const Page = () => { 5 | const [count, setCount] = createSignal(0) 6 | 7 | return ( 8 |
9 | 12 | 13 | 14 | 15 | 16 | No results. 17 | Item A {count()} 18 | Item B {count()} 19 | 20 | 21 |
22 | ) 23 | } 24 | 25 | export default Page 26 | -------------------------------------------------------------------------------- /test/src/routes/item.tsx: -------------------------------------------------------------------------------- 1 | import { Command } from 'cmdk-solid' 2 | import { Show, createSignal } from 'solid-js' 3 | 4 | const Page = () => { 5 | const [unmount, setUnmount] = createSignal(false) 6 | const [mount, setMount] = createSignal(false) 7 | const [many, setMany] = createSignal(false) 8 | const [forceMount, setForceMount] = createSignal(false) 9 | 10 | return ( 11 |
12 | 15 | {JSON.stringify(mount())} 16 | 19 | 20 | 23 | 24 | 27 | 28 | 29 | 30 | 31 | No results. 32 | 33 | A 34 | 35 | 36 | <> 37 | 1 38 | 2 39 | 3 40 | 41 | 42 | 43 | B 44 | 45 | 46 | 47 |
48 | ) 49 | } 50 | 51 | export default Page 52 | -------------------------------------------------------------------------------- /test/src/routes/keybinds.tsx: -------------------------------------------------------------------------------- 1 | import { useSearchParams } from '@solidjs/router' 2 | import { Command } from 'cmdk-solid' 3 | 4 | const Page = () => { 5 | const [params] = useSearchParams() 6 | return ( 7 |
8 | 9 | 10 | 11 | No results. 12 | 13 | 14 | Disabled 15 | 16 | 17 | First 18 | 19 | 20 | A 21 | B 22 | Z 23 | 24 | 25 | 26 | Apple 27 | Banana 28 | Orange 29 | Dragon Fruit 30 | Pear 31 | 32 | 33 | Last 34 | 35 | 36 | Disabled 3 37 | 38 | 39 | 40 |
41 | ) 42 | } 43 | 44 | export default Page 45 | -------------------------------------------------------------------------------- /test/src/routes/numeric.tsx: -------------------------------------------------------------------------------- 1 | import { Command } from 'cmdk-solid' 2 | 3 | const Page = () => { 4 | return ( 5 |
6 | 7 | 8 | 9 | No results. 10 | 11 | To be removed 12 | 13 | 14 | Not to be removed 15 | 16 | 17 | 18 |
19 | ) 20 | } 21 | 22 | export default Page 23 | -------------------------------------------------------------------------------- /test/src/routes/portal.tsx: -------------------------------------------------------------------------------- 1 | import { Command } from 'cmdk-solid' 2 | import { Show, createSignal, onMount } from 'solid-js' 3 | import { Portal } from 'solid-js/web' 4 | 5 | const Page = () => { 6 | const [render, setRender] = createSignal(false) 7 | const [search, setSearch] = createSignal('') 8 | const [open, setOpen] = createSignal(true) 9 | onMount(() => setRender(true)) 10 | 11 | return ( 12 | 13 |
14 | 17 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | Apple 29 | Banana 30 | Cherry 31 | Dragonfruit 32 | Elderberry 33 | Fig 34 | Grape 35 | Honeydew 36 | Jackfruit 37 | Kiwi 38 | Lemon 39 | Mango 40 | Nectarine 41 | Orange 42 | Papaya 43 | Quince 44 | Raspberry 45 | Strawberry 46 | Tangerine 47 | Ugli 48 | Watermelon 49 | Xigua 50 | Yuzu 51 | Zucchini 52 | 53 | 54 | 55 | 56 |
57 |
58 | ) 59 | } 60 | 61 | export default Page 62 | -------------------------------------------------------------------------------- /test/src/routes/props.tsx: -------------------------------------------------------------------------------- 1 | import { useSearchParams, useIsRouting } from '@solidjs/router' 2 | import { Command } from 'cmdk-solid' 3 | import { createEffect, createSignal } from 'solid-js' 4 | 5 | const Page = () => { 6 | const [value, setValue] = createSignal('ant') 7 | const [search, setSearch] = createSignal('') 8 | const [shouldFilter, setShouldFilter] = createSignal(true) 9 | const [customFilter, setCustomFilter] = createSignal(false) 10 | const [params] = useSearchParams() 11 | const isRouting = useIsRouting() 12 | 13 | createEffect(() => { 14 | if (!isRouting()) { 15 | setShouldFilter(params.shouldFilter === 'false' ? false : true) 16 | setCustomFilter(params.customFilter === 'true' ? true : false) 17 | setValue(params.initialValue ?? 'ant') 18 | } 19 | }) 20 | 21 | return ( 22 |
23 |
{value()}
24 |
{search()}
25 | 26 | 29 | 32 | 33 | { 40 | console.log(item, search) 41 | if (!search || !item) return 1 42 | return item.endsWith(search) ? 1 : 0 43 | } 44 | : undefined 45 | } 46 | > 47 | 48 | 49 | ant 50 | anteater 51 | 52 | 53 |
54 | ) 55 | } 56 | 57 | export default Page 58 | -------------------------------------------------------------------------------- /test/src/routes/sort.tsx: -------------------------------------------------------------------------------- 1 | import { Command } from 'cmdk-solid' 2 | 3 | const Page = () => { 4 | return ( 5 |
6 | 7 | 8 | 9 | No results. 10 | 11 | 12 | Apple 13 | 14 | 15 | Rasberry 16 | 17 | 18 | 19 | 20 | Dewberry 21 | 22 | Strawberry 23 | 24 | 25 | 26 |
27 | ) 28 | } 29 | 30 | export default Page 31 | -------------------------------------------------------------------------------- /test/tests/basic.spec.ts: -------------------------------------------------------------------------------- 1 | import { expect, test } from '@playwright/test' 2 | 3 | test.describe('basic behavior', async () => { 4 | test.beforeEach(async ({ page }) => { 5 | await page.goto('/') 6 | }) 7 | 8 | test('input props are forwarded', async ({ page }) => { 9 | const input = page.locator(`input[placeholder="Search…"]`) 10 | await expect(input).toHaveCount(1) 11 | }) 12 | 13 | test('item value is derived from textContent', async ({ page }) => { 14 | const item = page.locator(`[cmdk-item][data-value="Item"]`) 15 | await expect(item).toHaveText('Item') 16 | }) 17 | 18 | test('item value prop is preferred over textContent', async ({ page }) => { 19 | const item = page.locator(`[cmdk-item][data-value="xxx"]`) 20 | await expect(item).toHaveText('Value') 21 | }) 22 | 23 | test('item onSelect is called on click', async ({ page }) => { 24 | const item = page.locator(`[cmdk-item][data-value="Item"]`) 25 | const [message] = await Promise.all([ 26 | page.waitForEvent('console', (message) => message.text() == 'Item selected'), 27 | item.click(), 28 | ]) 29 | expect(message.text()).toEqual('Item selected') 30 | }) 31 | 32 | test('first item is selected by default', async ({ page }) => { 33 | const item = page.locator(`[cmdk-item][aria-selected="true"]`) 34 | await expect(item).toHaveText('Item') 35 | }) 36 | 37 | test('first item is selected when search changes', async ({ page }) => { 38 | const input = page.locator(`[cmdk-input]`) 39 | await input.type('x') 40 | const selected = page.locator(`[cmdk-item][aria-selected="true"]`) 41 | await expect(selected).toHaveText('Value') 42 | }) 43 | 44 | test('items filter when searching', async ({ page }) => { 45 | const input = page.locator(`[cmdk-input]`) 46 | await input.type('x') 47 | const removed = page.locator(`[cmdk-item][data-value="Item"]`) 48 | const remains = page.locator(`[cmdk-item][data-value="xxx"]`) 49 | await expect(removed).toHaveCount(0) 50 | await expect(remains).toHaveCount(1) 51 | }) 52 | 53 | test('items filter when searching by keywords', async ({ page }) => { 54 | const input = page.locator(`[cmdk-input]`) 55 | await input.type('key') 56 | const removed = page.locator(`[cmdk-item][data-value="xxx"]`) 57 | const remains = page.locator(`[cmdk-item][data-value="Item"]`) 58 | await expect(removed).toHaveCount(0) 59 | await expect(remains).toHaveCount(1) 60 | }) 61 | 62 | test('empty component renders when there are no results', async ({ page }) => { 63 | const input = page.locator('[cmdk-input]') 64 | await input.type('z') 65 | await expect(page.locator(`[cmdk-item]`)).toHaveCount(0) 66 | await expect(page.locator(`[cmdk-empty]`)).toHaveText('No results.') 67 | }) 68 | 69 | test('className is applied to each part', async ({ page }) => { 70 | await expect(page.locator(`.root`)).toHaveCount(1) 71 | await expect(page.locator(`.input`)).toHaveCount(1) 72 | await expect(page.locator(`.list`)).toHaveCount(1) 73 | await expect(page.locator(`.item`)).toHaveCount(2) 74 | await page.locator('[cmdk-input]').type('zzzz') 75 | await expect(page.locator(`.item`)).toHaveCount(0) 76 | await expect(page.locator(`.empty`)).toHaveCount(1) 77 | }) 78 | }) 79 | -------------------------------------------------------------------------------- /test/tests/dialog.test.ts: -------------------------------------------------------------------------------- 1 | import { expect, test } from '@playwright/test' 2 | 3 | test.describe('dialog', async () => { 4 | test.beforeEach(async ({ page }) => { 5 | await page.goto('/dialog') 6 | }) 7 | 8 | test('dialog renders in portal', async ({ page }) => { 9 | await expect(page.locator(`[cmdk-dialog]`)).toHaveCount(1) 10 | await expect(page.locator(`[cmdk-overlay]`)).toHaveCount(1) 11 | }) 12 | }) 13 | -------------------------------------------------------------------------------- /test/tests/group-filter.test.ts: -------------------------------------------------------------------------------- 1 | import { expect, test } from '@playwright/test' 2 | 3 | test.describe('group filters', async () => { 4 | test.beforeEach(async ({ page }) => { 5 | await page.goto('/group-filter') 6 | }) 7 | 8 | test('search results stay in sync with term', async ({ page }) => { 9 | await page.locator(`[cmdk-input]`).fill('bar') 10 | await expect(page.locator(`[cmdk-item][data-value="Installation"]`)).not.toBeVisible() 11 | await expect(page.locator(`[cmdk-item][data-value="Bar List"]`)).toBeVisible() 12 | await page.locator(`[cmdk-input]`).fill('ba') 13 | await expect(page.locator(`[cmdk-item][data-value="About"]`)).toBeVisible() 14 | }) 15 | }) 16 | -------------------------------------------------------------------------------- /test/tests/group.test.ts: -------------------------------------------------------------------------------- 1 | import { expect, test } from '@playwright/test' 2 | 3 | test.describe('group', async () => { 4 | test.beforeEach(async ({ page }) => { 5 | await page.goto('/group') 6 | }) 7 | 8 | test('groups are shown/hidden based on item matches', async ({ page }) => { 9 | await page.locator(`[cmdk-input]`).fill('z') 10 | await expect(page.locator(`[cmdk-group][data-value="Animals"]`)).not.toBeVisible() 11 | await expect(page.locator(`[cmdk-group][data-value="Letters"]`)).toBeVisible() 12 | }) 13 | 14 | test('group can be progressively rendered', async ({ page }) => { 15 | await expect(page.locator(`[cmdk-group][data-value="Numbers"]`)).not.toBeVisible() 16 | await page.locator(`[cmdk-input]`).fill('t') 17 | await expect(page.locator(`[cmdk-group][data-value="Animals"]`)).not.toBeVisible() 18 | await expect(page.locator(`[cmdk-group][data-value="Letters"]`)).not.toBeVisible() 19 | await expect(page.locator(`[cmdk-group][data-value="Numbers"]`)).toBeVisible() 20 | }) 21 | 22 | test('mounted group still rendered with filter using forceMount', async ({ page }) => { 23 | await page.locator(`data-testid=forceMount`).click() 24 | await page.locator(`[cmdk-input]`).fill('Giraffe') 25 | await expect(page.locator(`[cmdk-group][data-value="Letters"]`)).toBeVisible() 26 | }) 27 | }) 28 | -------------------------------------------------------------------------------- /test/tests/item.test.ts: -------------------------------------------------------------------------------- 1 | import { expect, test } from '@playwright/test' 2 | 3 | test.describe('item', async () => { 4 | test.beforeEach(async ({ page }) => { 5 | await page.goto('/item') 6 | }) 7 | 8 | test('mounted item matches search', async ({ page }) => { 9 | await page.locator(`[cmdk-input]`).type('b') 10 | await expect(page.locator(`[cmdk-item]`)).toHaveCount(0) 11 | await page.locator(`data-testid=mount`).click() 12 | await expect(page.locator(`[cmdk-item]`)).toHaveText('B') 13 | }) 14 | 15 | test('mounted item does not match search', async ({ page }) => { 16 | await page.locator(`[cmdk-input]`).type('z') 17 | await expect(page.locator(`[cmdk-item]`)).toHaveCount(0) 18 | await page.locator(`data-testid=mount`).click() 19 | await expect(page.locator(`[cmdk-item]`)).toHaveCount(0) 20 | }) 21 | 22 | test('unmount item that is selected', async ({ page }) => { 23 | await page.locator(`data-testid=mount`).click() 24 | await expect(page.locator(`[cmdk-item][aria-selected="true"]`)).toHaveText('A') 25 | await page.locator(`data-testid=unmount`).click() 26 | await expect(page.locator(`[cmdk-item]`)).toHaveCount(1) 27 | await expect(page.locator(`[cmdk-item][aria-selected="true"]`)).toHaveText('B') 28 | }) 29 | 30 | test('unmount item that is the only result', async ({ page }) => { 31 | await page.locator(`data-testid=unmount`).click() 32 | await expect(page.locator(`[cmdk-item]`)).toHaveCount(0) 33 | }) 34 | 35 | test('mount item that is the only result', async ({ page }) => { 36 | await page.locator(`data-testid=unmount`).click() 37 | await expect(page.locator(`[cmdk-empty]`)).toHaveCount(1) 38 | await page.locator(`data-testid=mount`).click() 39 | await expect(page.locator(`[cmdk-empty]`)).toHaveCount(0) 40 | await expect(page.locator(`[cmdk-item]`)).toHaveCount(1) 41 | }) 42 | 43 | test('selected does not change when mounting new items', async ({ page }) => { 44 | await page.locator(`data-testid=mount`).click() 45 | await page.locator(`[cmdk-item][data-value="B"]`).click() 46 | await expect(page.locator(`[cmdk-item][aria-selected="true"]`)).toHaveText('B') 47 | await page.locator(`data-testid=many`).click() 48 | await expect(page.locator(`[cmdk-item][aria-selected="true"]`)).toHaveText('B') 49 | }) 50 | 51 | test('mounted item still rendered with filter usingForceMount', async ({ page }) => { 52 | await page.locator(`data-testid=forceMount`).click() 53 | await page.locator(`[cmdk-input]`).type('z') 54 | await expect(page.locator(`[cmdk-item]`)).toHaveCount(1) 55 | }) 56 | }) 57 | 58 | test.describe('item advanced', async () => { 59 | test.beforeEach(async ({ page }) => { 60 | await page.goto('/item-advanced') 61 | }) 62 | 63 | test('re-rendering re-matches implicit textContent value', async ({ page }) => { 64 | await expect(page.locator(`[cmdk-item]`)).toHaveCount(2) 65 | await page.locator(`[cmdk-input]`).type('2') 66 | const button = page.locator(`data-testid=increment`) 67 | await button.click() 68 | await expect(page.locator(`[cmdk-item]`)).toHaveCount(0) 69 | await button.click() 70 | await expect(page.locator(`[cmdk-item]`)).toHaveCount(2) 71 | }) 72 | }) 73 | -------------------------------------------------------------------------------- /test/tests/keybind.test.ts: -------------------------------------------------------------------------------- 1 | import { expect, test } from '@playwright/test' 2 | 3 | test.describe('arrow keybinds', async () => { 4 | test.beforeEach(async ({ page }) => { 5 | await page.goto('/keybinds') 6 | }) 7 | 8 | test('arrow up/down changes selected item', async ({ page }) => { 9 | await expect(page.locator(`[cmdk-item][aria-selected="true"]`)).toHaveAttribute('data-value', 'first') 10 | await page.locator(`[cmdk-input]`).press('ArrowDown') 11 | await expect(page.locator(`[cmdk-item][aria-selected="true"]`)).toHaveAttribute('data-value', 'A') 12 | await page.locator(`[cmdk-input]`).press('ArrowUp') 13 | await expect(page.locator(`[cmdk-item][aria-selected="true"]`)).toHaveAttribute('data-value', 'first') 14 | }) 15 | 16 | test('meta arrow up/down goes to first and last item', async ({ page }) => { 17 | await expect(page.locator(`[cmdk-item][aria-selected="true"]`)).toHaveAttribute('data-value', 'first') 18 | await page.locator(`[cmdk-input]`).press('Meta+ArrowDown') 19 | await expect(page.locator(`[cmdk-item][aria-selected="true"]`)).toHaveAttribute('data-value', 'last') 20 | await page.locator(`[cmdk-input]`).press('Meta+ArrowUp') 21 | await expect(page.locator(`[cmdk-item][aria-selected="true"]`)).toHaveAttribute('data-value', 'first') 22 | }) 23 | 24 | test('alt arrow up/down goes to next and prev item', async ({ page }) => { 25 | await expect(page.locator(`[cmdk-item][aria-selected="true"]`)).toHaveAttribute('data-value', 'first') 26 | await page.locator(`[cmdk-input]`).press('Alt+ArrowDown') 27 | await expect(page.locator(`[cmdk-item][aria-selected="true"]`)).toHaveAttribute('data-value', 'A') 28 | await page.locator(`[cmdk-input]`).press('Alt+ArrowDown') 29 | await expect(page.locator(`[cmdk-item][aria-selected="true"]`)).toHaveAttribute('data-value', 'Apple') 30 | await page.locator(`[cmdk-input]`).press('Alt+ArrowUp') 31 | await expect(page.locator(`[cmdk-item][aria-selected="true"]`)).toHaveAttribute('data-value', 'A') 32 | await page.locator(`[cmdk-input]`).press('Alt+ArrowUp') 33 | await expect(page.locator(`[cmdk-item][aria-selected="true"]`)).toHaveAttribute('data-value', 'first') 34 | }) 35 | }) 36 | 37 | test.describe('vim jk keybinds', async () => { 38 | test.beforeEach(async ({ page }) => { 39 | await page.goto('/keybinds') 40 | }) 41 | 42 | test('ctrl j/k changes selected item', async ({ page }) => { 43 | await expect(page.locator(`[cmdk-item][aria-selected="true"]`)).toHaveAttribute('data-value', 'first') 44 | await page.locator(`[cmdk-input]`).press('Control+j') 45 | await expect(page.locator(`[cmdk-item][aria-selected="true"]`)).toHaveAttribute('data-value', 'A') 46 | await page.locator(`[cmdk-input]`).press('Control+k') 47 | await expect(page.locator(`[cmdk-item][aria-selected="true"]`)).toHaveAttribute('data-value', 'first') 48 | }) 49 | 50 | test('meta ctrl j/k goes to first and last item', async ({ page }) => { 51 | await expect(page.locator(`[cmdk-item][aria-selected="true"]`)).toHaveAttribute('data-value', 'first') 52 | await page.locator(`[cmdk-input]`).press('Meta+Control+j') 53 | await expect(page.locator(`[cmdk-item][aria-selected="true"]`)).toHaveAttribute('data-value', 'last') 54 | await page.locator(`[cmdk-input]`).press('Meta+Control+k') 55 | await expect(page.locator(`[cmdk-item][aria-selected="true"]`)).toHaveAttribute('data-value', 'first') 56 | }) 57 | 58 | test('alt ctrl j/k goes to next and prev item', async ({ page }) => { 59 | await expect(page.locator(`[cmdk-item][aria-selected="true"]`)).toHaveAttribute('data-value', 'first') 60 | await page.locator(`[cmdk-input]`).press('Alt+Control+j') 61 | await expect(page.locator(`[cmdk-item][aria-selected="true"]`)).toHaveAttribute('data-value', 'A') 62 | await page.locator(`[cmdk-input]`).press('Alt+Control+j') 63 | await expect(page.locator(`[cmdk-item][aria-selected="true"]`)).toHaveAttribute('data-value', 'Apple') 64 | await page.locator(`[cmdk-input]`).press('Alt+Control+k') 65 | await expect(page.locator(`[cmdk-item][aria-selected="true"]`)).toHaveAttribute('data-value', 'A') 66 | await page.locator(`[cmdk-input]`).press('Alt+Control+k') 67 | await expect(page.locator(`[cmdk-item][aria-selected="true"]`)).toHaveAttribute('data-value', 'first') 68 | }) 69 | }) 70 | 71 | test.describe('vim np keybinds', async () => { 72 | test.beforeEach(async ({ page }) => { 73 | await page.goto('/keybinds') 74 | }) 75 | 76 | test('ctrl n/p changes selected item', async ({ page }) => { 77 | await expect(page.locator(`[cmdk-item][aria-selected="true"]`)).toHaveAttribute('data-value', 'first') 78 | await page.locator(`[cmdk-input]`).press('Control+n') 79 | await expect(page.locator(`[cmdk-item][aria-selected="true"]`)).toHaveAttribute('data-value', 'A') 80 | await page.locator(`[cmdk-input]`).press('Control+p') 81 | await expect(page.locator(`[cmdk-item][aria-selected="true"]`)).toHaveAttribute('data-value', 'first') 82 | }) 83 | 84 | test('meta ctrl n/p goes to first and last item', async ({ page }) => { 85 | await expect(page.locator(`[cmdk-item][aria-selected="true"]`)).toHaveAttribute('data-value', 'first') 86 | await page.locator(`[cmdk-input]`).press('Meta+Control+n') 87 | await expect(page.locator(`[cmdk-item][aria-selected="true"]`)).toHaveAttribute('data-value', 'last') 88 | await page.locator(`[cmdk-input]`).press('Meta+Control+p') 89 | await expect(page.locator(`[cmdk-item][aria-selected="true"]`)).toHaveAttribute('data-value', 'first') 90 | }) 91 | 92 | test('alt ctrl n/p goes to next and prev item', async ({ page }) => { 93 | await expect(page.locator(`[cmdk-item][aria-selected="true"]`)).toHaveAttribute('data-value', 'first') 94 | await page.locator(`[cmdk-input]`).press('Alt+Control+n') 95 | await expect(page.locator(`[cmdk-item][aria-selected="true"]`)).toHaveAttribute('data-value', 'A') 96 | await page.locator(`[cmdk-input]`).press('Alt+Control+n') 97 | await expect(page.locator(`[cmdk-item][aria-selected="true"]`)).toHaveAttribute('data-value', 'Apple') 98 | await page.locator(`[cmdk-input]`).press('Alt+Control+p') 99 | await expect(page.locator(`[cmdk-item][aria-selected="true"]`)).toHaveAttribute('data-value', 'A') 100 | await page.locator(`[cmdk-input]`).press('Alt+Control+p') 101 | await expect(page.locator(`[cmdk-item][aria-selected="true"]`)).toHaveAttribute('data-value', 'first') 102 | }) 103 | }) 104 | 105 | test.describe('no-vim keybinds', async () => { 106 | test.beforeEach(async ({ page }) => { 107 | await page.goto('/keybinds?noVim=true') 108 | }) 109 | 110 | test('ctrl j/k does nothing', async ({ page }) => { 111 | await expect(page.locator(`[cmdk-item][aria-selected="true"]`)).toHaveAttribute('data-value', 'first') 112 | await page.locator(`[cmdk-input]`).press('Control+j') 113 | await expect(page.locator(`[cmdk-item][aria-selected="true"]`)).toHaveAttribute('data-value', 'first') 114 | await page.locator(`[cmdk-input]`).press('Control+k') 115 | await expect(page.locator(`[cmdk-item][aria-selected="true"]`)).toHaveAttribute('data-value', 'first') 116 | }) 117 | 118 | test('ctrl n/p does nothing', async ({ page }) => { 119 | await expect(page.locator(`[cmdk-item][aria-selected="true"]`)).toHaveAttribute('data-value', 'first') 120 | await page.locator(`[cmdk-input]`).press('Control+n') 121 | await expect(page.locator(`[cmdk-item][aria-selected="true"]`)).toHaveAttribute('data-value', 'first') 122 | await page.locator(`[cmdk-input]`).press('Control+p') 123 | await expect(page.locator(`[cmdk-item][aria-selected="true"]`)).toHaveAttribute('data-value', 'first') 124 | }) 125 | }) 126 | -------------------------------------------------------------------------------- /test/tests/numeric.test.ts: -------------------------------------------------------------------------------- 1 | import { expect, test } from '@playwright/test' 2 | 3 | test.describe('behavior for numeric values', async () => { 4 | test.beforeEach(async ({ page }) => { 5 | await page.goto('/numeric') 6 | }) 7 | 8 | test('items filter correctly on numeric inputs', async ({ page }) => { 9 | const input = page.locator(`[cmdk-input]`) 10 | await input.type('112') 11 | const removed = page.locator(`[cmdk-item][data-value="removed"]`) 12 | const remains = page.locator(`[cmdk-item][data-value="foo.bar112.value"]`) 13 | await expect(removed).toHaveCount(0) 14 | await expect(remains).toHaveCount(1) 15 | }) 16 | 17 | test('items filter correctly on non-numeric inputs', async ({ page }) => { 18 | const input = page.locator(`[cmdk-input]`) 19 | await input.type('bar') 20 | const removed = page.locator(`[cmdk-item][data-value="removed"]`) 21 | const remains = page.locator(`[cmdk-item][data-value="foo.bar112.value"]`) 22 | await expect(removed).toHaveCount(0) 23 | await expect(remains).toHaveCount(1) 24 | }) 25 | }) 26 | -------------------------------------------------------------------------------- /test/tests/props.test.ts: -------------------------------------------------------------------------------- 1 | import { expect, test } from '@playwright/test' 2 | 3 | test.describe('props', async () => { 4 | test('results do not change when filtering is disabled', async ({ page }) => { 5 | await page.goto('/props?shouldFilter=false') 6 | await expect(page.locator(`[cmdk-item]`)).toHaveCount(2) 7 | await page.locator(`[cmdk-input]`).type('z') 8 | await expect(page.locator(`[cmdk-item]`)).toHaveCount(2) 9 | }) 10 | 11 | test('results match against custom filter', async ({ page }) => { 12 | await page.goto('/props?customFilter=true', { waitUntil: 'networkidle' }) 13 | await page.locator(`[cmdk-input]`).type(`ant`) 14 | await expect(page.locator(`[cmdk-item]`)).toHaveAttribute('data-value', 'ant') 15 | }) 16 | 17 | test('controlled value', async ({ page }) => { 18 | await page.goto('/props') 19 | await expect(page.locator(`[cmdk-item][aria-selected="true"]`)).toHaveAttribute('data-value', 'ant') 20 | await page.locator(`data-testid=controlledValue`).click() 21 | await expect(page.locator(`[cmdk-item][aria-selected="true"]`)).toHaveAttribute('data-value', 'anteater') 22 | }) 23 | 24 | test('keep controlled value if empty results', async ({ page }) => { 25 | await page.goto('/props') 26 | await expect(page.locator(`[data-testid=value]`)).toHaveText('ant') 27 | await page.locator(`[cmdk-input]`).fill('d') 28 | await expect(page.locator(`[data-testid=value]`)).toHaveText('') 29 | await page.locator(`[cmdk-input]`).fill('ant') 30 | await expect(page.locator(`[data-testid=value]`)).toHaveText('ant') 31 | }) 32 | 33 | test('controlled search', async ({ page }) => { 34 | await page.goto('/props') 35 | await expect(page.locator(`[cmdk-item][aria-selected="true"]`)).toHaveAttribute('data-value', 'ant') 36 | await page.locator(`data-testid=controlledSearch`).click() 37 | await expect(page.locator(`[cmdk-item][aria-selected="true"]`)).toHaveAttribute('data-value', 'anteater') 38 | }) 39 | 40 | test('keep focus on the provided initial value', async ({ page }) => { 41 | await page.goto('/props?initialValue=anteater') 42 | await expect(page.locator(`[cmdk-item][aria-selected="true"]`)).toHaveAttribute('data-value', 'anteater') 43 | }) 44 | }) 45 | -------------------------------------------------------------------------------- /test/tests/sort.spec.ts: -------------------------------------------------------------------------------- 1 | import { expect, test } from '@playwright/test' 2 | 3 | test.describe('sorting behavior', async () => { 4 | test.beforeEach(async ({ page }) => { 5 | await page.goto('/sort') 6 | }) 7 | 8 | //! Removed because i'm leaving a sorting bug in the code 9 | /* 10 | test('sorts group first when internal score is higher', async ({ page }) => { 11 | await page.locator(`[cmdk-input]`).type('ewberry') 12 | await expect(page.locator(`[cmdk-item]`).first()).toHaveText('Dewberry') 13 | await expect(page.locator(`[cmdk-item]`).nth(1)).toHaveText('Apple') 14 | }) 15 | 16 | test('sorts items inside group', async ({ page }) => { 17 | await page.locator(`[cmdk-input]`).type('asberry') 18 | await expect(page.locator(`[cmdk-item]`).first()).toHaveText('Rasberry') 19 | await expect(page.locator(`[cmdk-item]`).nth(1)).toHaveText('Apple') 20 | }) 21 | */ 22 | }) 23 | -------------------------------------------------------------------------------- /test/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "ESNext", 4 | "module": "ESNext", 5 | "moduleResolution": "node", 6 | "allowSyntheticDefaultImports": true, 7 | "esModuleInterop": true, 8 | "jsx": "preserve", 9 | "jsxImportSource": "solid-js", 10 | "allowJs": true, 11 | "strict": true, 12 | "noEmit": true, 13 | "types": ["vinxi/client", "vite/client"], 14 | "isolatedModules": true, 15 | "paths": { 16 | "~/*": ["./src/*"] 17 | } 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "strict": true, 4 | "target": "ESNext", 5 | "module": "ESNext", 6 | "lib": ["DOM", "DOM.Iterable", "ESNext"], 7 | "moduleResolution": "node", 8 | "resolveJsonModule": true, 9 | "esModuleInterop": true, 10 | "noEmit": true, 11 | "isolatedModules": true, 12 | "skipLibCheck": true, 13 | "allowSyntheticDefaultImports": true, 14 | "forceConsistentCasingInFileNames": true, 15 | "noUncheckedIndexedAccess": true, 16 | "jsx": "preserve", 17 | "jsxImportSource": "solid-js", 18 | "types": [], 19 | "baseUrl": "." 20 | }, 21 | "include": ["**/*.ts", "**/*.tsx", "**/*.js", "**/*.jsx"], 22 | "exclude": ["node_modules", "dist", "./dev"] 23 | } 24 | -------------------------------------------------------------------------------- /vitest.config.ts: -------------------------------------------------------------------------------- 1 | import { defineConfig } from 'vitest/config' 2 | import solidPlugin from 'vite-plugin-solid' 3 | 4 | export default defineConfig(({ mode }) => { 5 | // to test in server environment, run with "--mode ssr" or "--mode test:ssr" flag 6 | // loads only server.test.ts file 7 | const testSSR = mode === 'test:ssr' || mode === 'ssr' 8 | 9 | return { 10 | plugins: [ 11 | solidPlugin({ 12 | // https://github.com/solidjs/solid-refresh/issues/29 13 | hot: false, 14 | // For testing SSR we need to do a SSR JSX transform 15 | solid: { generate: testSSR ? 'ssr' : 'dom' }, 16 | }), 17 | ], 18 | test: { 19 | watch: false, 20 | isolate: !testSSR, 21 | env: { 22 | NODE_ENV: testSSR ? 'production' : 'development', 23 | DEV: testSSR ? '' : '1', 24 | SSR: testSSR ? '1' : '', 25 | PROD: testSSR ? '1' : '', 26 | }, 27 | environment: testSSR ? 'node' : 'jsdom', 28 | transformMode: { web: [/\.[jt]sx$/] }, 29 | ...(testSSR 30 | ? { 31 | include: ['test/server.test.{ts,tsx}'], 32 | } 33 | : { 34 | include: ['test/*.test.{ts,tsx}'], 35 | exclude: ['test/server.test.{ts,tsx}'], 36 | }), 37 | }, 38 | resolve: { 39 | conditions: testSSR ? ['node'] : ['browser', 'development'], 40 | }, 41 | } 42 | }) 43 | -------------------------------------------------------------------------------- /website/.gitignore: -------------------------------------------------------------------------------- 1 | 2 | dist 3 | .solid 4 | .output 5 | .vercel 6 | .netlify 7 | netlify 8 | .vinxi 9 | 10 | # Environment 11 | .env 12 | .env*.local 13 | 14 | # dependencies 15 | /node_modules 16 | 17 | # IDEs and editors 18 | /.idea 19 | .project 20 | .classpath 21 | *.launch 22 | .settings/ 23 | 24 | # Temp 25 | gitignore 26 | 27 | # System Files 28 | .DS_Store 29 | Thumbs.db 30 | -------------------------------------------------------------------------------- /website/README.md: -------------------------------------------------------------------------------- 1 | # SolidStart 2 | 3 | Everything you need to build a Solid project, powered by [`solid-start`](https://start.solidjs.com); 4 | 5 | ## Creating a project 6 | 7 | ```bash 8 | # create a new project in the current directory 9 | npm init solid@latest 10 | 11 | # create a new project in my-app 12 | npm init solid@latest my-app 13 | ``` 14 | 15 | ## Developing 16 | 17 | Once you've created a project and installed dependencies with `npm install` (or `pnpm install` or `yarn`), start a development server: 18 | 19 | ```bash 20 | npm run dev 21 | 22 | # or start the server and open the app in a new browser tab 23 | npm run dev -- --open 24 | ``` 25 | 26 | ## Building 27 | 28 | Solid apps are built with _presets_, which optimise your project for deployment to different environments. 29 | 30 | By default, `npm run build` will generate a Node app that you can run with `npm start`. To use a different preset, add it to the `devDependencies` in `package.json` and specify in your `app.config.js`. 31 | 32 | ## This project was created with the [Solid CLI](https://solid-cli.netlify.app) 33 | -------------------------------------------------------------------------------- /website/app.config.ts: -------------------------------------------------------------------------------- 1 | import { defineConfig } from '@solidjs/start/config' 2 | 3 | export default defineConfig({ 4 | server: { 5 | preset: 'vercel-static', 6 | }, 7 | }) 8 | -------------------------------------------------------------------------------- /website/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "website", 3 | "type": "module", 4 | "scripts": { 5 | "dev": "vinxi dev", 6 | "build": "vinxi build", 7 | "start": "vinxi start", 8 | "version": "vinxi version" 9 | }, 10 | "dependencies": { 11 | "@kobalte/core": "^0.12.4", 12 | "@solid-primitives/intersection-observer": "^2.1.6", 13 | "@solidjs/meta": "^0.29.2", 14 | "@solidjs/router": "^0.13.0", 15 | "@solidjs/start": "^0.7.3", 16 | "clsx": "^2.1.0", 17 | "cmdk-solid": "workspace:*", 18 | "copy-to-clipboard": "^3.3.3", 19 | "lucide-solid": "^0.358.0", 20 | "solid-js": "^1.8.15", 21 | "solid-motionone": "^1.0.0", 22 | "tailwind-merge": "^2.2.2", 23 | "tailwindcss-animate": "^1.0.7", 24 | "vinxi": "^0.3.10" 25 | }, 26 | "engines": { 27 | "node": ">=18" 28 | }, 29 | "devDependencies": { 30 | "autoprefixer": "^10.4.18", 31 | "postcss": "^8.4.36", 32 | "sass": "^1.71.1", 33 | "tailwindcss": "^3.4.1" 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /website/postcss.config.js: -------------------------------------------------------------------------------- 1 | export default { 2 | plugins: { 3 | tailwindcss: {}, 4 | autoprefixer: {}, 5 | }, 6 | } 7 | -------------------------------------------------------------------------------- /website/public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/create-signal/cmdk-solid/3228bd458e924b1fadf30340b124e6e8e08f38d2/website/public/favicon.ico -------------------------------------------------------------------------------- /website/public/favicon.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /website/public/grid.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /website/public/inter-var-latin.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/create-signal/cmdk-solid/3228bd458e924b1fadf30340b124e6e8e08f38d2/website/public/inter-var-latin.woff2 -------------------------------------------------------------------------------- /website/public/kiz.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/create-signal/cmdk-solid/3228bd458e924b1fadf30340b124e6e8e08f38d2/website/public/kiz.jpg -------------------------------------------------------------------------------- /website/public/line.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /website/public/og.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/create-signal/cmdk-solid/3228bd458e924b1fadf30340b124e6e8e08f38d2/website/public/og.png -------------------------------------------------------------------------------- /website/public/paco.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/create-signal/cmdk-solid/3228bd458e924b1fadf30340b124e6e8e08f38d2/website/public/paco.png -------------------------------------------------------------------------------- /website/public/rauno.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/create-signal/cmdk-solid/3228bd458e924b1fadf30340b124e6e8e08f38d2/website/public/rauno.jpeg -------------------------------------------------------------------------------- /website/public/robots.txt: -------------------------------------------------------------------------------- 1 | User-agent: * 2 | Disallow: 3 | -------------------------------------------------------------------------------- /website/public/vercel.svg: -------------------------------------------------------------------------------- 1 | 3 | 4 | -------------------------------------------------------------------------------- /website/src/app.tsx: -------------------------------------------------------------------------------- 1 | import { Meta, MetaProvider, Title } from '@solidjs/meta' 2 | import { Router } from '@solidjs/router' 3 | import { FileRoutes } from '@solidjs/start/router' 4 | import { Suspense } from 'solid-js' 5 | import './styles/globals.scss' 6 | 7 | import './styles/cmdk/framer.scss' 8 | import './styles/cmdk/linear.scss' 9 | import './styles/cmdk/raycast.scss' 10 | import './styles/cmdk/vercel.scss' 11 | 12 | const title = '⌘K for SolidJS' 13 | const description = 'Fast, composable, unstyled command menu for SolidJS' 14 | 15 | export default function App() { 16 | return ( 17 | ( 19 | 20 | 21 | {description} - {title} 22 | 23 | 24 | 25 | {props.children} 26 | 27 | )} 28 | > 29 | 30 | 31 | ) 32 | } 33 | -------------------------------------------------------------------------------- /website/src/components/Counter.css: -------------------------------------------------------------------------------- 1 | .increment { 2 | font-family: inherit; 3 | font-size: inherit; 4 | padding: 1em 2em; 5 | color: #335d92; 6 | background-color: rgba(68, 107, 158, 0.1); 7 | border-radius: 2em; 8 | border: 2px solid rgba(68, 107, 158, 0); 9 | outline: none; 10 | width: 200px; 11 | font-variant-numeric: tabular-nums; 12 | } 13 | 14 | .increment:focus { 15 | border: 2px solid #335d92; 16 | } 17 | 18 | .increment:active { 19 | background-color: rgba(68, 107, 158, 0.2); 20 | } 21 | -------------------------------------------------------------------------------- /website/src/components/Counter.tsx: -------------------------------------------------------------------------------- 1 | 'use client' 2 | import { createSignal } from 'solid-js' 3 | import './Counter.css' 4 | 5 | export default function Counter() { 6 | const [count, setCount] = createSignal(0) 7 | return ( 8 | 11 | ) 12 | } 13 | -------------------------------------------------------------------------------- /website/src/components/cmdk/framer.tsx: -------------------------------------------------------------------------------- 1 | import { Command } from 'cmdk-solid' 2 | import { JSX, createSignal } from 'solid-js' 3 | import { Show } from 'solid-js/web' 4 | 5 | export function FramerCMDK() { 6 | const [value, setValue] = createSignal('Button') 7 | 8 | return ( 9 |
10 | 11 |
12 | 13 | 14 |
15 | 16 |
17 |
18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 |
42 |
43 |
44 | 45 |
66 |
67 |
68 |
69 |
70 | ) 71 | } 72 | 73 | function Button() { 74 | return 75 | } 76 | 77 | function Input() { 78 | return 79 | } 80 | 81 | function Badge() { 82 | return
Badge
83 | } 84 | 85 | function Radio() { 86 | return ( 87 | 91 | ) 92 | } 93 | 94 | function Slider() { 95 | return ( 96 |
97 |
98 |
99 | ) 100 | } 101 | 102 | function Avatar() { 103 | return Avatar of Rauno 104 | } 105 | 106 | function Container() { 107 | return
108 | } 109 | 110 | function Item({ children, value, subtitle }: { children: JSX.Element; value: string; subtitle: string }) { 111 | return ( 112 | {}}> 113 |
{children}
114 |
115 | {value} 116 | {subtitle} 117 |
118 |
119 | ) 120 | } 121 | 122 | function ButtonIcon() { 123 | return ( 124 | 125 | 131 | 132 | ) 133 | } 134 | 135 | function InputIcon() { 136 | return ( 137 | 138 | 144 | 145 | ) 146 | } 147 | 148 | function RadioIcon() { 149 | return ( 150 | 151 | 157 | 158 | ) 159 | } 160 | 161 | function BadgeIcon() { 162 | return ( 163 | 164 | 170 | 171 | ) 172 | } 173 | 174 | function ToggleIcon() { 175 | return ( 176 | 177 | 183 | 184 | ) 185 | } 186 | 187 | function AvatarIcon() { 188 | return ( 189 | 190 | 196 | 197 | ) 198 | } 199 | 200 | function ContainerIcon() { 201 | return ( 202 | 203 | 209 | 210 | ) 211 | } 212 | 213 | function SearchIcon() { 214 | return ( 215 | 223 | 224 | 225 | ) 226 | } 227 | 228 | function SliderIcon() { 229 | return ( 230 | 231 | 237 | 238 | ) 239 | } 240 | -------------------------------------------------------------------------------- /website/src/components/cmdk/linear.tsx: -------------------------------------------------------------------------------- 1 | import { Command } from 'cmdk-solid' 2 | import { For } from 'solid-js' 3 | 4 | export function LinearCMDK() { 5 | return ( 6 |
7 | 8 |
Issue - FUN-343
9 | 10 | 11 | No results found. 12 | 13 | {({ icon, label, shortcut }) => ( 14 | 15 | {icon()} 16 | {label} 17 |
18 | {(key) => {key}} 19 |
20 |
21 | )} 22 |
23 |
24 |
25 |
26 | ) 27 | } 28 | 29 | const items = [ 30 | { 31 | icon: AssignToIcon, 32 | label: 'Assign to...', 33 | shortcut: ['A'], 34 | }, 35 | { 36 | icon: AssignToMeIcon, 37 | label: 'Assign to me', 38 | shortcut: ['I'], 39 | }, 40 | { 41 | icon: ChangeStatusIcon, 42 | label: 'Change status...', 43 | shortcut: ['S'], 44 | }, 45 | { 46 | icon: ChangePriorityIcon, 47 | label: 'Change priority...', 48 | shortcut: ['P'], 49 | }, 50 | { 51 | icon: ChangeLabelsIcon, 52 | label: 'Change labels...', 53 | shortcut: ['L'], 54 | }, 55 | { 56 | icon: RemoveLabelIcon, 57 | label: 'Remove label...', 58 | shortcut: ['⇧', 'L'], 59 | }, 60 | { 61 | icon: SetDueDateIcon, 62 | label: 'Set due date...', 63 | shortcut: ['⇧', 'D'], 64 | }, 65 | ] 66 | 67 | function AssignToIcon() { 68 | return ( 69 | 70 | 71 | 72 | ) 73 | } 74 | 75 | function AssignToMeIcon() { 76 | return ( 77 | 78 | 79 | 84 | 85 | 86 | ) 87 | } 88 | 89 | function ChangeStatusIcon() { 90 | return ( 91 | 92 | 93 | 98 | 99 | ) 100 | } 101 | 102 | function ChangePriorityIcon() { 103 | return ( 104 | 105 | 106 | 107 | 108 | 109 | ) 110 | } 111 | 112 | function ChangeLabelsIcon() { 113 | return ( 114 | 115 | 120 | 121 | ) 122 | } 123 | 124 | function RemoveLabelIcon() { 125 | return ( 126 | 127 | 132 | 133 | ) 134 | } 135 | 136 | function SetDueDateIcon() { 137 | return ( 138 | 139 | 144 | 145 | ) 146 | } 147 | -------------------------------------------------------------------------------- /website/src/components/cmdk/raycast.tsx: -------------------------------------------------------------------------------- 1 | import { Popover } from '@kobalte/core' 2 | import { Command } from 'cmdk-solid' 3 | import { For, JSX, createEffect, createSignal, onCleanup, onMount } from 'solid-js' 4 | import { FigmaIcon, LinearIcon, Logo, RaycastIcon, SlackIcon, YouTubeIcon } from '../../components' 5 | 6 | export function RaycastCMDK() { 7 | const [value, setValue] = createSignal('linear') 8 | const [inputRef, setInputRef] = createSignal(null) 9 | const [listRef, setListRef] = createSignal(null) 10 | 11 | createEffect(() => { 12 | inputRef()?.focus() 13 | }) 14 | 15 | return ( 16 |
17 | 18 |
19 | 20 |
21 | 22 | No results found. 23 | 24 | 25 | 26 | 32 | 33 | Linear 34 | 35 | 36 | 37 | 38 | 39 | Figma 40 | 41 | 42 | 43 | 44 | 45 | Slack 46 | 47 | 48 | 49 | 50 | 51 | YouTube 52 | 53 | 54 | 55 | 56 | 57 | Raycast 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | Clipboard History 66 | 67 | 68 | 69 | Import Extension 70 | 71 | 72 | 73 | Manage Extensions 74 | 75 | 76 | 77 | 78 |
79 | 80 | 81 | 85 | 86 |
87 | 88 | 89 |
90 | 91 |
92 | ) 93 | } 94 | 95 | function Item({ 96 | children, 97 | value, 98 | keywords, 99 | isCommand = false, 100 | }: { 101 | children: JSX.Element 102 | value: string 103 | keywords?: string[] 104 | isCommand?: boolean 105 | }) { 106 | return ( 107 | {}}> 108 | {children} 109 | {isCommand ? 'Command' : 'Application'} 110 | 111 | ) 112 | } 113 | 114 | function SubCommand(props: { inputRef: HTMLInputElement | null; listRef: HTMLElement | null; selectedValue: string }) { 115 | const [open, setOpen] = createSignal(false) 116 | 117 | onMount(() => { 118 | function listener(e: KeyboardEvent) { 119 | if (e.key === 'k' && e.metaKey) { 120 | e.preventDefault() 121 | setOpen((o) => !o) 122 | } 123 | } 124 | 125 | document.addEventListener('keydown', listener) 126 | 127 | onCleanup(() => { 128 | document.removeEventListener('keydown', listener) 129 | }) 130 | }) 131 | 132 | createEffect(() => { 133 | const el = props.listRef 134 | 135 | if (!el) return 136 | 137 | if (open()) { 138 | el.style.overflow = 'hidden' 139 | } else { 140 | el.style.overflow = '' 141 | } 142 | }) 143 | 144 | return ( 145 | 146 | 147 | 148 | Actions 149 | 150 | K 151 | 152 | 153 | 154 | { 157 | //? Note: This doesn't appear to work 158 | e.preventDefault() 159 | props.inputRef?.focus() 160 | }} 161 | > 162 | 163 | 164 | 165 | 166 | 167 | Open Application 168 | 169 | 170 | 171 | Show in Finder 172 | 173 | 174 | 175 | Show Info in Finder 176 | 177 | 178 | 179 | Add to Favorites 180 | 181 | 182 | 183 | 184 | 185 | 186 | 187 | 188 | ) 189 | } 190 | 191 | function SubItem(props: { children: JSX.Element; shortcut: string }) { 192 | return ( 193 | 194 | {props.children} 195 |
196 | {(key) => {key}} 197 |
198 |
199 | ) 200 | } 201 | 202 | function RaycastDarkIcon() { 203 | return ( 204 | 205 | 211 | 212 | ) 213 | } 214 | 215 | function WindowIcon() { 216 | return ( 217 | 218 | 225 | 226 | ) 227 | } 228 | 229 | function FinderIcon() { 230 | return ( 231 | 232 | 239 | 240 | ) 241 | } 242 | 243 | function StarIcon() { 244 | return ( 245 | 246 | 253 | 254 | ) 255 | } 256 | 257 | function ClipboardIcon() { 258 | return ( 259 |
260 | 261 | 268 | 269 |
270 | ) 271 | } 272 | 273 | function HammerIcon() { 274 | return ( 275 |
276 | 277 | 284 | 285 |
286 | ) 287 | } 288 | -------------------------------------------------------------------------------- /website/src/components/cmdk/shadcn.tsx: -------------------------------------------------------------------------------- 1 | import { CalendarIcon, MailIcon, RocketIcon, SettingsIcon, SmileIcon, UserIcon } from 'lucide-solid' 2 | import { 3 | Command, 4 | CommandEmpty, 5 | CommandGroup, 6 | CommandInput, 7 | CommandItem, 8 | CommandList, 9 | CommandSeparator, 10 | CommandShortcut, 11 | } from '~/components/ui/command' 12 | 13 | export function ShadcnCMDK() { 14 | return ( 15 | 16 | 17 | 18 | No results found. 19 | 20 | 21 | 22 | Calendar 23 | 24 | 25 | 26 | Search Emoji 27 | 28 | 29 | 30 | Launch 31 | 32 | 33 | 34 | 35 | 36 | 37 | Profile 38 | ⌘P 39 | 40 | 41 | 42 | Mail 43 | ⌘B 44 | 45 | 46 | 47 | Settings 48 | ⌘S 49 | 50 | 51 | 52 | 53 | ) 54 | } 55 | -------------------------------------------------------------------------------- /website/src/components/cmdk/vercel.tsx: -------------------------------------------------------------------------------- 1 | import { Command } from 'cmdk-solid' 2 | import { For, JSX, Show, createSignal } from 'solid-js' 3 | 4 | export function VercelCMDK() { 5 | let ref: HTMLDivElement 6 | const [inputValue, setInputValue] = createSignal('') 7 | 8 | const [pages, setPages] = createSignal(['home']) 9 | const activePage = () => pages()[pages().length - 1] 10 | const isHome = () => activePage() === 'home' 11 | 12 | const popPage = () => { 13 | setPages((pages) => { 14 | const x = [...pages] 15 | x.splice(-1, 1) 16 | return x 17 | }) 18 | } 19 | 20 | function bounce() { 21 | if (ref) { 22 | ref.style.transform = 'scale(0.96)' 23 | setTimeout(() => { 24 | if (ref) { 25 | ref.style.transform = '' 26 | } 27 | }, 100) 28 | 29 | setInputValue('') 30 | } 31 | } 32 | 33 | return ( 34 |
35 | (ref = el)} 37 | onKeyDown={(e: KeyboardEvent) => { 38 | if (e.key === 'Enter') { 39 | bounce() 40 | } 41 | 42 | if (isHome() || inputValue().length) { 43 | return 44 | } 45 | 46 | if (e.key === 'Backspace') { 47 | e.preventDefault() 48 | popPage() 49 | bounce() 50 | } 51 | }} 52 | > 53 |
54 | {(page) =>
{page}
}
55 |
56 | { 60 | setInputValue(value) 61 | }} 62 | /> 63 | 64 | No results found. 65 | {activePage() === 'home' && setPages([...pages(), 'projects'])} />} 66 | {activePage() === 'projects' && } 67 | 68 |
69 |
70 | ) 71 | } 72 | 73 | function Home(props: { searchProjects: Function }) { 74 | return ( 75 | <> 76 | 77 | { 80 | props.searchProjects() 81 | }} 82 | > 83 | 84 | Search Projects... 85 | 86 | 87 | 88 | Create New Project... 89 | 90 | 91 | 92 | 93 | 94 | Search Teams... 95 | 96 | 97 | 98 | Create New Team... 99 | 100 | 101 | 102 | 103 | 104 | Search Docs... 105 | 106 | 107 | 108 | Send Feedback... 109 | 110 | 111 | 112 | Contact Support 113 | 114 | 115 | 116 | ) 117 | } 118 | 119 | function Projects() { 120 | return ( 121 | <> 122 | Project 1 123 | Project 2 124 | Project 3 125 | Project 4 126 | Project 5 127 | Project 6 128 | 129 | ) 130 | } 131 | 132 | function Item(props: { children: JSX.Element; shortcut?: string; onSelect?: (value: string) => void }) { 133 | return ( 134 | 135 | {props.children} 136 | 137 |
138 | {(key) => {key}} 139 |
140 |
141 |
142 | ) 143 | } 144 | 145 | function ProjectsIcon() { 146 | return ( 147 | 158 | 159 | 160 | 161 | 162 | 163 | ) 164 | } 165 | 166 | function PlusIcon() { 167 | return ( 168 | 179 | 180 | 181 | 182 | ) 183 | } 184 | 185 | function TeamsIcon() { 186 | return ( 187 | 198 | 199 | 200 | 201 | 202 | 203 | ) 204 | } 205 | 206 | function CopyIcon() { 207 | return ( 208 | 219 | 220 | 221 | ) 222 | } 223 | 224 | function DocsIcon() { 225 | return ( 226 | 237 | 238 | 239 | 240 | 241 | 242 | 243 | ) 244 | } 245 | 246 | function FeedbackIcon() { 247 | return ( 248 | 259 | 260 | 261 | ) 262 | } 263 | 264 | function ContactIcon() { 265 | return ( 266 | 277 | 278 | 279 | 280 | ) 281 | } 282 | -------------------------------------------------------------------------------- /website/src/components/code/code.module.scss: -------------------------------------------------------------------------------- 1 | .root { 2 | border-radius: 12px; 3 | padding: 16px; 4 | backdrop-filter: blur(10px); 5 | border: 1px solid var(--gray6); 6 | position: relative; 7 | line-height: 16px; 8 | background: var(--lowContrast); 9 | white-space: pre-wrap; 10 | box-shadow: rgb(0 0 0 / 10%) 0px 5px 30px -5px; 11 | color: var(--gray12); 12 | 13 | @media (prefers-color-scheme: dark) { 14 | background: var(--grayA2); 15 | } 16 | 17 | button { 18 | display: flex; 19 | align-items: center; 20 | justify-content: center; 21 | width: 32px; 22 | height: 32px; 23 | background: var(--grayA3); 24 | border-radius: 8px; 25 | position: absolute; 26 | top: 12px; 27 | right: 12px; 28 | color: var(--gray11); 29 | cursor: copy; 30 | transition: 31 | color 150ms ease, 32 | background 150ms ease, 33 | transform 150ms ease; 34 | 35 | &:hover { 36 | color: var(--gray12); 37 | background: var(--grayA4); 38 | } 39 | 40 | &:active { 41 | color: var(--gray12); 42 | background: var(--grayA5); 43 | transform: scale(0.96); 44 | } 45 | } 46 | } 47 | 48 | .shine { 49 | @media (prefers-color-scheme: dark) { 50 | background: linear-gradient( 51 | 90deg, 52 | rgba(56, 189, 248, 0), 53 | var(--gray5) 20%, 54 | var(--gray9) 67.19%, 55 | rgba(236, 72, 153, 0) 56 | ); 57 | height: 1px; 58 | position: absolute; 59 | top: -1px; 60 | width: 97%; 61 | z-index: -1; 62 | } 63 | } 64 | 65 | @media (max-width: 640px) { 66 | .root { 67 | :global(.token-line) { 68 | font-size: 11px !important; 69 | } 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /website/src/components/code/index.tsx: -------------------------------------------------------------------------------- 1 | import copy from 'copy-to-clipboard' 2 | import styles from './code.module.scss' 3 | import { CopyIcon } from '../icons' 4 | 5 | const theme = { 6 | plain: { 7 | color: 'var(--gray12)', 8 | fontSize: 12, 9 | fontFamily: 'Menlo, monospace', 10 | }, 11 | styles: [ 12 | { 13 | types: ['comment'], 14 | style: { 15 | color: 'var(--gray9)', 16 | }, 17 | }, 18 | { 19 | types: ['atrule', 'keyword', 'attr-name', 'selector'], 20 | style: { 21 | color: 'var(--gray10)', 22 | }, 23 | }, 24 | { 25 | types: ['punctuation', 'operator'], 26 | style: { 27 | color: 'var(--gray9)', 28 | }, 29 | }, 30 | { 31 | types: ['class-name', 'function', 'tag'], 32 | style: { 33 | color: 'var(--gray12)', 34 | }, 35 | }, 36 | ], 37 | } 38 | 39 | export function Code(props: { children: string }) { 40 | return ( 41 |
42 |       
50 |       
51 |
{props.children}
52 | {/*tokens.map((line, i) => ( 53 |
54 | {line.map((token, key) => ( 55 | 56 | ))} 57 |
58 | ))*/} 59 |
60 | ) 61 | } 62 | -------------------------------------------------------------------------------- /website/src/components/icons/icons.module.scss: -------------------------------------------------------------------------------- 1 | .blurLogo { 2 | display: flex; 3 | align-items: center; 4 | justify-content: center; 5 | position: relative; 6 | border-radius: 4px; 7 | overflow: hidden; 8 | box-shadow: inset 0 0 1px 1px rgba(0, 0, 0, 0.015); 9 | 10 | .bg { 11 | display: flex; 12 | align-items: center; 13 | justify-content: center; 14 | position: absolute; 15 | z-index: 1; 16 | pointer-events: none; 17 | user-select: none; 18 | top: 0; 19 | left: 0; 20 | width: 100%; 21 | height: 100%; 22 | transform: scale(1.5) translateZ(0); 23 | filter: blur(12px) opacity(0.4) saturate(100%); 24 | transition: filter 150ms ease; 25 | } 26 | 27 | .inner { 28 | display: flex; 29 | align-items: center; 30 | justify-content: center; 31 | object-fit: cover; 32 | width: 100%; 33 | height: 100%; 34 | user-select: none; 35 | pointer-events: none; 36 | border-radius: inherit; 37 | z-index: 2; 38 | 39 | svg { 40 | width: 14px; 41 | height: 14px; 42 | filter: drop-shadow(0 4px 4px rgba(0, 0, 0, 0.16)); 43 | transition: filter 150ms ease; 44 | } 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /website/src/components/icons/index.tsx: -------------------------------------------------------------------------------- 1 | import { JSX } from 'solid-js' 2 | import styles from './icons.module.scss' 3 | 4 | export function FigmaIcon() { 5 | return ( 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | ) 14 | } 15 | 16 | export function RaycastIcon() { 17 | return ( 18 | 19 | 25 | 26 | ) 27 | } 28 | 29 | export function YouTubeIcon() { 30 | return ( 31 | 32 | 36 | 37 | 38 | ) 39 | } 40 | 41 | export function SlackIcon() { 42 | return ( 43 | 44 | 48 | 52 | 56 | 60 | 64 | 68 | 72 | 76 | 77 | ) 78 | } 79 | 80 | export function VercelIcon() { 81 | return ( 82 | 83 | 84 | 85 | ) 86 | } 87 | 88 | export function LinearIcon(props: { style?: string }) { 89 | return ( 90 | 91 | 95 | 99 | 103 | 107 | 108 | ) 109 | } 110 | 111 | export function ShadcnIcon(props: { style?: string }) { 112 | return ( 113 | 114 | 115 | 126 | 137 | 138 | ) 139 | } 140 | export function Logo({ children, size = '20px' }: { children: JSX.Element; size?: string }) { 141 | return ( 142 |
149 |
150 | {children} 151 |
152 |
{children}
153 |
154 | ) 155 | } 156 | 157 | export function CopyIcon() { 158 | return ( 159 | 160 | 166 | 172 | 173 | ) 174 | } 175 | 176 | export function CopiedIcon() { 177 | return ( 178 | 179 | 180 | 181 | ) 182 | } 183 | 184 | export function GitHubIcon() { 185 | return ( 186 | 187 | 191 | 192 | ) 193 | } 194 | 195 | export function FramerIcon() { 196 | return ( 197 | 198 | 202 | 203 | ) 204 | } 205 | -------------------------------------------------------------------------------- /website/src/components/index.tsx: -------------------------------------------------------------------------------- 1 | export * from './cmdk/framer' 2 | export * from './cmdk/linear' 3 | export * from './cmdk/vercel' 4 | export * from './cmdk/raycast' 5 | export * from './cmdk/shadcn' 6 | export * from './icons' 7 | export * from './code' 8 | -------------------------------------------------------------------------------- /website/src/components/ui/command.tsx: -------------------------------------------------------------------------------- 1 | import { 2 | Command as CommandPrimitive, 3 | CommandRootProps, 4 | CommandInputProps, 5 | CommandListProps, 6 | CommandEmptyProps, 7 | CommandGroupProps, 8 | CommandSeparatorProps, 9 | CommandItemProps, 10 | } from 'cmdk-solid' 11 | 12 | import { cn } from '~/lib/utils' 13 | import { Dialog, DialogContent } from '~/components/ui/dialog' 14 | import { Component, JSX, ParentComponent } from 'solid-js' 15 | import { DialogRootProps } from '@kobalte/core/dist/types/dialog' 16 | import { Search } from 'lucide-solid' 17 | 18 | const Command: ParentComponent = (props) => ( 19 | 23 | ) 24 | 25 | interface CommandDialogProps extends DialogRootProps {} 26 | 27 | const CommandDialog: ParentComponent = (props) => { 28 | return ( 29 | 30 | 31 | 32 | {props.children} 33 | 34 | 35 | 36 | ) 37 | } 38 | 39 | const CommandInput: Component = (props) => ( 40 |
41 | 42 | 49 |
50 | ) 51 | 52 | const CommandList: ParentComponent = (props) => ( 53 | 54 | ) 55 | 56 | const CommandEmpty: ParentComponent = (props) => ( 57 | 58 | ) 59 | 60 | const CommandGroup: ParentComponent = (props) => ( 61 | 68 | ) 69 | 70 | const CommandSeparator: Component = (props) => ( 71 | 72 | ) 73 | 74 | const CommandItem: ParentComponent = (props) => ( 75 | 82 | ) 83 | 84 | const CommandShortcut: ParentComponent> = (props) => { 85 | return 86 | } 87 | 88 | export { 89 | Command, 90 | CommandDialog, 91 | CommandInput, 92 | CommandList, 93 | CommandEmpty, 94 | CommandGroup, 95 | CommandItem, 96 | CommandShortcut, 97 | CommandSeparator, 98 | } 99 | -------------------------------------------------------------------------------- /website/src/components/ui/dialog.tsx: -------------------------------------------------------------------------------- 1 | import { Dialog as DialogPrimitive } from '@kobalte/core' 2 | import { DialogOverlayProps } from '@kobalte/core/dist/types/dialog' 3 | import { X } from 'lucide-solid' 4 | import { Component, JSX, ParentComponent } from 'solid-js' 5 | import { cn } from '~/lib/utils' 6 | 7 | const Dialog = DialogPrimitive.Root 8 | 9 | const DialogTrigger = DialogPrimitive.Trigger 10 | 11 | const DialogPortal = DialogPrimitive.Portal 12 | 13 | const DialogClose = DialogPrimitive.CloseButton 14 | 15 | const DialogOverlay: ParentComponent = (props) => ( 16 | 23 | ) 24 | 25 | const DialogContent: ParentComponent = (props) => ( 26 | 27 | 28 | 35 | {props.children} 36 | 37 | 38 | Close 39 | 40 | 41 | 42 | ) 43 | 44 | const DialogHeader: Component> = (props) => ( 45 |
46 | ) 47 | 48 | const DialogFooter: ParentComponent> = (props) => ( 49 |
50 | ) 51 | 52 | const DialogTitle: ParentComponent = (props) => ( 53 | 54 | ) 55 | 56 | const DialogDescription: ParentComponent = (props) => ( 57 | 58 | ) 59 | 60 | export { 61 | Dialog, 62 | DialogPortal, 63 | DialogOverlay, 64 | DialogTrigger, 65 | DialogClose, 66 | DialogContent, 67 | DialogHeader, 68 | DialogFooter, 69 | DialogTitle, 70 | DialogDescription, 71 | } 72 | -------------------------------------------------------------------------------- /website/src/entry-client.tsx: -------------------------------------------------------------------------------- 1 | // @refresh reload 2 | import { mount, StartClient } from '@solidjs/start/client' 3 | 4 | mount(() => , document.getElementById('app')!) 5 | -------------------------------------------------------------------------------- /website/src/entry-server.tsx: -------------------------------------------------------------------------------- 1 | // @refresh reload 2 | import { createHandler, StartServer } from '@solidjs/start/server' 3 | 4 | export default createHandler(() => ( 5 | ( 7 | 8 | 9 | 10 | 11 | 12 | {assets} 13 | 14 | 15 |
{children}
16 | {scripts} 17 | 18 | 19 | )} 20 | /> 21 | )) 22 | -------------------------------------------------------------------------------- /website/src/global.d.ts: -------------------------------------------------------------------------------- 1 | /// 2 | -------------------------------------------------------------------------------- /website/src/lib/utils.ts: -------------------------------------------------------------------------------- 1 | import { clsx, type ClassValue } from 'clsx' 2 | import { twMerge } from 'tailwind-merge' 3 | 4 | export function cn(...inputs: ClassValue[]) { 5 | return twMerge(clsx(inputs)) 6 | } 7 | -------------------------------------------------------------------------------- /website/src/routes/[...404].tsx: -------------------------------------------------------------------------------- 1 | import { Title } from '@solidjs/meta' 2 | import { HttpStatusCode } from '@solidjs/start' 3 | 4 | export default function NotFound() { 5 | return ( 6 |
7 | Not Found 8 | 9 |

Page Not Found

10 |

11 | Visit{' '} 12 | 13 | start.solidjs.com 14 | {' '} 15 | to learn how to build SolidStart apps. 16 |

17 |
18 | ) 19 | } 20 | -------------------------------------------------------------------------------- /website/src/routes/index.tsx: -------------------------------------------------------------------------------- 1 | import { 2 | Code, 3 | CopiedIcon, 4 | CopyIcon, 5 | FramerCMDK, 6 | FramerIcon, 7 | GitHubIcon, 8 | LinearCMDK, 9 | LinearIcon, 10 | RaycastCMDK, 11 | RaycastIcon, 12 | ShadcnIcon, 13 | VercelCMDK, 14 | VercelIcon, 15 | ShadcnCMDK, 16 | } from '../components' 17 | import { Motion, MotionComponentProps } from 'solid-motionone' 18 | import { Accessor, JSX, createContext, createEffect, createSignal, onCleanup, onMount, useContext } from 'solid-js' 19 | import packageJSON from '../../../cmdk-solid/package.json' 20 | import styles from '../styles/index.module.scss' 21 | import { For, Show } from 'solid-js/web' 22 | import { createVisibilityObserver } from '@solid-primitives/intersection-observer' 23 | 24 | type TTheme = { 25 | theme: Accessor 26 | setTheme: Function 27 | } 28 | 29 | type Themes = 'linear' | 'raycast' | 'vercel' | 'framer' | 'shadcn' 30 | 31 | const ThemeContext = createContext({} as TTheme) 32 | 33 | export default function Index() { 34 | const [theme, setTheme] = createSignal('linear') 35 | 36 | return ( 37 |
38 |
39 |
40 |
41 | 42 |

⌘K for SolidJS

43 |

44 | Fast, composable, unstyled command menu for SolidJS, Ported from{' '} 45 | 46 | @pacocoursey's ⌘K for React. 47 | 48 |

49 |
50 | 51 |
52 | 53 | 54 |
55 |
56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | {' '} 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 |
89 | 90 | 91 |
92 |
93 |
94 | ) 95 | } 96 | 97 | function CMDKWrapper(props: MotionComponentProps & { children: JSX.Element }) { 98 | return ( 99 | 109 | ) 110 | } 111 | 112 | function InstallButton() { 113 | const [copied, setCopied] = createSignal(false) 114 | 115 | return ( 116 | 135 | ) 136 | } 137 | 138 | function GitHubButton() { 139 | return ( 140 | 146 | 147 | create-signal/cmdk-solid 148 | 149 | ) 150 | } 151 | 152 | function VersionBadge() { 153 | return v{packageJSON.version} 154 | } 155 | 156 | const themes = [ 157 | { 158 | icon: RaycastIcon, 159 | key: 'raycast', 160 | }, 161 | { 162 | icon: LinearIcon, 163 | key: 'linear', 164 | }, 165 | { 166 | icon: VercelIcon, 167 | key: 'vercel', 168 | }, 169 | { 170 | icon: FramerIcon, 171 | key: 'framer', 172 | }, 173 | { 174 | icon: ShadcnIcon, 175 | key: 'shadcn', 176 | }, 177 | ] 178 | 179 | function ThemeSwitcher() { 180 | const { theme, setTheme } = useContext(ThemeContext) 181 | const [showArrowKeyHint, setShowArrowKeyHint] = createSignal(false) 182 | 183 | onMount(() => { 184 | function listener(e: KeyboardEvent) { 185 | const themeNames = themes.map((t) => t.key) 186 | 187 | if (e.key === 'ArrowRight') { 188 | const currentIndex = themeNames.indexOf(theme()) 189 | const nextIndex = currentIndex + 1 190 | const nextItem = themeNames[nextIndex] 191 | 192 | if (nextItem) { 193 | setTheme(nextItem) 194 | } 195 | } 196 | 197 | if (e.key === 'ArrowLeft') { 198 | const currentIndex = themeNames.indexOf(theme()) 199 | const prevIndex = currentIndex - 1 200 | const prevItem = themeNames[prevIndex] 201 | 202 | if (prevItem) { 203 | setTheme(prevItem) 204 | } 205 | } 206 | } 207 | 208 | document.addEventListener('keydown', listener) 209 | 210 | onCleanup(() => { 211 | document.removeEventListener('keydown', listener) 212 | }) 213 | }) 214 | 215 | return ( 216 |
217 | 218 | 219 | {({ key, icon }) => { 220 | const isActive = () => theme() === key 221 | return ( 222 | 238 | ) 239 | }} 240 | 241 | 247 | → 248 | 249 |
250 | ) 251 | } 252 | 253 | function Codeblock() { 254 | const code = `import { Command } from 'cmdk-solid'; 255 | 256 | 257 | 258 | 259 | 260 | 261 | Hang on… 262 | 263 | 264 | No results found. 265 | 266 | 267 | Apple 268 | Orange 269 | 270 | Pear 271 | Blueberry 272 | 273 | 274 | Fish 275 | 276 | ` 277 | 278 | return ( 279 |
280 |
281 |
282 | {code} 283 |
284 | ) 285 | } 286 | 287 | function Footer() { 288 | let el: HTMLDivElement | undefined 289 | const visibilityState = createVisibilityObserver({ rootMargin: '100px' })(() => el) 290 | const [visible, setVisible] = createSignal(false) 291 | 292 | createEffect(() => { 293 | if (visibilityState()) { 294 | setVisible(true) 295 | } 296 | }) 297 | 298 | return ( 299 | <> 300 | 309 | 310 | ) 311 | } 312 | -------------------------------------------------------------------------------- /website/src/styles/cmdk/framer.scss: -------------------------------------------------------------------------------- 1 | .framer { 2 | [cmdk-root] { 3 | max-width: 640px; 4 | width: 100%; 5 | padding: 8px; 6 | background: #ffffff; 7 | border-radius: 16px; 8 | overflow: hidden; 9 | font-family: var(--font-sans); 10 | border: 1px solid var(--gray6); 11 | box-shadow: var(--cmdk-shadow); 12 | outline: none; 13 | 14 | .dark & { 15 | background: var(--gray2); 16 | } 17 | } 18 | 19 | [cmdk-framer-header] { 20 | display: flex; 21 | align-items: center; 22 | gap: 8px; 23 | height: 48px; 24 | padding: 0 8px; 25 | border-bottom: 1px solid var(--gray5); 26 | margin-bottom: 12px; 27 | padding-bottom: 8px; 28 | 29 | svg { 30 | width: 20px; 31 | height: 20px; 32 | color: var(--gray9); 33 | transform: translateY(1px); 34 | } 35 | } 36 | 37 | [cmdk-input] { 38 | font-family: var(--font-sans); 39 | border: none; 40 | width: 100%; 41 | font-size: 16px; 42 | outline: none; 43 | background: var(--bg); 44 | color: var(--gray12); 45 | 46 | &::placeholder { 47 | color: var(--gray9); 48 | } 49 | } 50 | 51 | [cmdk-item] { 52 | content-visibility: auto; 53 | 54 | cursor: pointer; 55 | border-radius: 12px; 56 | font-size: 14px; 57 | display: flex; 58 | align-items: center; 59 | gap: 12px; 60 | color: var(--gray12); 61 | padding: 8px 8px; 62 | margin-right: 8px; 63 | font-weight: 500; 64 | transition: all 150ms ease; 65 | transition-property: none; 66 | 67 | &[data-selected='true'] { 68 | background: var(--blue9); 69 | color: #ffffff; 70 | 71 | [cmdk-framer-item-subtitle] { 72 | color: #ffffff; 73 | } 74 | } 75 | 76 | &[data-disabled='true'] { 77 | color: var(--gray8); 78 | cursor: not-allowed; 79 | } 80 | 81 | & + [cmdk-item] { 82 | margin-top: 4px; 83 | } 84 | 85 | svg { 86 | width: 16px; 87 | height: 16px; 88 | color: #ffffff; 89 | } 90 | } 91 | 92 | [cmdk-framer-icon-wrapper] { 93 | display: flex; 94 | align-items: center; 95 | justify-content: center; 96 | min-width: 32px; 97 | height: 32px; 98 | background: orange; 99 | border-radius: 8px; 100 | } 101 | 102 | [cmdk-framer-item-meta] { 103 | display: flex; 104 | flex-direction: column; 105 | gap: 4px; 106 | } 107 | 108 | [cmdk-framer-item-subtitle] { 109 | font-size: 12px; 110 | font-weight: 400; 111 | color: var(--gray11); 112 | } 113 | 114 | [cmdk-framer-items] { 115 | min-height: 308px; 116 | display: flex; 117 | } 118 | 119 | [cmdk-framer-left] { 120 | width: 40%; 121 | } 122 | 123 | [cmdk-framer-separator] { 124 | width: 1px; 125 | border: 0; 126 | margin-right: 8px; 127 | background: var(--gray6); 128 | } 129 | 130 | [cmdk-framer-right] { 131 | display: flex; 132 | align-items: center; 133 | justify-content: center; 134 | border-radius: 8px; 135 | margin-left: 8px; 136 | width: 60%; 137 | 138 | button { 139 | width: 120px; 140 | height: 40px; 141 | background: var(--blue9); 142 | border-radius: 6px; 143 | font-weight: 500; 144 | color: white; 145 | font-size: 14px; 146 | } 147 | 148 | input[type='text'] { 149 | height: 40px; 150 | width: 160px; 151 | border: 1px solid var(--gray6); 152 | background: #ffffff; 153 | border-radius: 6px; 154 | padding: 0 8px; 155 | font-size: 14px; 156 | font-family: var(--font-sans); 157 | box-shadow: 0 2px 4px -1px rgba(0, 0, 0, 0.08); 158 | 159 | &::placeholder { 160 | color: var(--gray9); 161 | } 162 | 163 | @media (prefers-color-scheme: dark) { 164 | background: var(--gray3); 165 | } 166 | } 167 | 168 | [cmdk-framer-radio] { 169 | display: flex; 170 | align-items: center; 171 | gap: 4px; 172 | color: var(--gray12); 173 | font-weight: 500; 174 | font-size: 14px; 175 | accent-color: var(--blue9); 176 | 177 | input { 178 | width: 20px; 179 | height: 20px; 180 | } 181 | } 182 | 183 | img { 184 | width: 40px; 185 | height: 40px; 186 | border-radius: 9999px; 187 | border: 1px solid var(--gray6); 188 | } 189 | 190 | [cmdk-framer-container] { 191 | width: 100px; 192 | height: 100px; 193 | background: var(--blue9); 194 | border-radius: 16px; 195 | } 196 | 197 | [cmdk-framer-badge] { 198 | background: var(--blue3); 199 | padding: 0 8px; 200 | height: 28px; 201 | font-size: 14px; 202 | line-height: 28px; 203 | color: var(--blue11); 204 | border-radius: 9999px; 205 | font-weight: 500; 206 | } 207 | 208 | [cmdk-framer-slider] { 209 | height: 20px; 210 | width: 200px; 211 | background: linear-gradient(90deg, var(--blue9) 40%, var(--gray3) 0%); 212 | border-radius: 9999px; 213 | 214 | div { 215 | width: 20px; 216 | height: 20px; 217 | background: #ffffff; 218 | border-radius: 9999px; 219 | box-shadow: 0 1px 3px -1px rgba(0, 0, 0, 0.32); 220 | transform: translateX(70px); 221 | } 222 | } 223 | } 224 | 225 | [cmdk-list] { 226 | overflow: auto; 227 | } 228 | 229 | [cmdk-separator] { 230 | height: 1px; 231 | width: 100%; 232 | background: var(--gray5); 233 | margin: 4px 0; 234 | } 235 | 236 | [cmdk-group-heading] { 237 | user-select: none; 238 | font-size: 12px; 239 | color: var(--gray11); 240 | padding: 0 8px; 241 | display: flex; 242 | align-items: center; 243 | margin-bottom: 8px; 244 | } 245 | 246 | [cmdk-empty] { 247 | font-size: 14px; 248 | padding: 32px; 249 | white-space: pre-wrap; 250 | color: var(--gray11); 251 | } 252 | } 253 | 254 | @media (max-width: 640px) { 255 | .framer { 256 | [cmdk-framer-icon-wrapper] { 257 | } 258 | 259 | [cmdk-framer-item-subtitle] { 260 | display: none; 261 | } 262 | } 263 | } 264 | -------------------------------------------------------------------------------- /website/src/styles/cmdk/linear.scss: -------------------------------------------------------------------------------- 1 | .linear { 2 | [cmdk-root] { 3 | max-width: 640px; 4 | width: 100%; 5 | background: #ffffff; 6 | border-radius: 8px; 7 | overflow: hidden; 8 | padding: 0; 9 | font-family: var(--font-sans); 10 | box-shadow: var(--cmdk-shadow); 11 | outline: none; 12 | 13 | .dark & { 14 | background: linear-gradient(136.61deg, rgb(39, 40, 43) 13.72%, rgb(45, 46, 49) 74.3%); 15 | } 16 | } 17 | 18 | [cmdk-linear-badge] { 19 | height: 24px; 20 | padding: 0 8px; 21 | font-size: 12px; 22 | color: var(--gray11); 23 | background: var(--gray3); 24 | border-radius: 4px; 25 | width: fit-content; 26 | display: flex; 27 | align-items: center; 28 | margin: 16px 16px 0; 29 | } 30 | 31 | [cmdk-linear-shortcuts] { 32 | display: flex; 33 | margin-left: auto; 34 | gap: 8px; 35 | 36 | kbd { 37 | font-family: var(--font-sans); 38 | font-size: 13px; 39 | color: var(--gray11); 40 | } 41 | } 42 | 43 | [cmdk-input] { 44 | font-family: var(--font-sans); 45 | border: none; 46 | width: 100%; 47 | font-size: 18px; 48 | padding: 20px; 49 | outline: none; 50 | background: var(--bg); 51 | color: var(--gray12); 52 | border-bottom: 1px solid var(--gray6); 53 | border-radius: 0; 54 | caret-color: #6e5ed2; 55 | margin: 0; 56 | 57 | &::placeholder { 58 | color: var(--gray9); 59 | } 60 | } 61 | 62 | [cmdk-item] { 63 | content-visibility: auto; 64 | 65 | cursor: pointer; 66 | height: 48px; 67 | font-size: 14px; 68 | display: flex; 69 | align-items: center; 70 | gap: 12px; 71 | padding: 0 16px; 72 | color: var(--gray12); 73 | user-select: none; 74 | will-change: background, color; 75 | transition: all 150ms ease; 76 | transition-property: none; 77 | position: relative; 78 | 79 | &[data-selected='true'] { 80 | background: var(--gray3); 81 | 82 | svg { 83 | color: var(--gray12); 84 | } 85 | 86 | &:after { 87 | content: ''; 88 | position: absolute; 89 | left: 0; 90 | z-index: 123; 91 | width: 3px; 92 | height: 100%; 93 | background: #5f6ad2; 94 | } 95 | } 96 | 97 | &[data-disabled='true'] { 98 | color: var(--gray8); 99 | cursor: not-allowed; 100 | } 101 | 102 | &:active { 103 | transition-property: background; 104 | background: var(--gray4); 105 | } 106 | 107 | & + [cmdk-item] { 108 | margin-top: 4px; 109 | } 110 | 111 | svg { 112 | width: 16px; 113 | height: 16px; 114 | color: var(--gray10); 115 | } 116 | } 117 | 118 | [cmdk-list] { 119 | height: min(300px, var(--cmdk-list-height)); 120 | max-height: 400px; 121 | overflow: auto; 122 | overscroll-behavior: contain; 123 | transition: 100ms ease; 124 | transition-property: height; 125 | } 126 | 127 | [cmdk-group-heading] { 128 | user-select: none; 129 | font-size: 12px; 130 | color: var(--gray11); 131 | padding: 0 8px; 132 | display: flex; 133 | align-items: center; 134 | } 135 | 136 | [cmdk-empty] { 137 | font-size: 14px; 138 | display: flex; 139 | align-items: center; 140 | justify-content: center; 141 | height: 64px; 142 | white-space: pre-wrap; 143 | color: var(--gray11); 144 | } 145 | } 146 | -------------------------------------------------------------------------------- /website/src/styles/cmdk/raycast.scss: -------------------------------------------------------------------------------- 1 | .raycast { 2 | [cmdk-root] { 3 | max-width: 640px; 4 | width: 100%; 5 | background: var(--gray1); 6 | border-radius: 12px; 7 | padding: 8px 0; 8 | font-family: var(--font-sans); 9 | box-shadow: var(--cmdk-shadow); 10 | border: 1px solid var(--gray6); 11 | position: relative; 12 | outline: none; 13 | 14 | .dark & { 15 | background: var(--gray2); 16 | border: 0; 17 | 18 | &:after { 19 | content: ''; 20 | background: linear-gradient( 21 | to right, 22 | var(--gray6) 20%, 23 | var(--gray6) 40%, 24 | var(--gray10) 50%, 25 | var(--gray10) 55%, 26 | var(--gray6) 70%, 27 | var(--gray6) 100% 28 | ); 29 | z-index: -1; 30 | position: absolute; 31 | border-radius: 12px; 32 | top: -1px; 33 | left: -1px; 34 | width: calc(100% + 2px); 35 | height: calc(100% + 2px); 36 | animation: shine 3s ease forwards 0.1s; 37 | background-size: 200% auto; 38 | } 39 | 40 | &:before { 41 | content: ''; 42 | z-index: -1; 43 | position: absolute; 44 | border-radius: 12px; 45 | top: -1px; 46 | left: -1px; 47 | width: calc(100% + 2px); 48 | height: calc(100% + 2px); 49 | box-shadow: 0 0 0 1px transparent; 50 | animation: border 1s linear forwards 0.5s; 51 | } 52 | } 53 | 54 | kbd { 55 | font-family: var(--font-sans); 56 | background: var(--gray3); 57 | color: var(--gray11); 58 | height: 20px; 59 | width: 20px; 60 | border-radius: 4px; 61 | padding: 0 4px; 62 | display: flex; 63 | align-items: center; 64 | justify-content: center; 65 | 66 | &:first-of-type { 67 | margin-left: 8px; 68 | } 69 | } 70 | } 71 | 72 | [cmdk-input] { 73 | font-family: var(--font-sans); 74 | border: none; 75 | width: 100%; 76 | font-size: 15px; 77 | padding: 8px 16px; 78 | outline: none; 79 | background: var(--bg); 80 | color: var(--gray12); 81 | 82 | &::placeholder { 83 | color: var(--gray9); 84 | } 85 | } 86 | 87 | [cmdk-raycast-top-shine] { 88 | .dark & { 89 | background: linear-gradient( 90 | 90deg, 91 | rgba(56, 189, 248, 0), 92 | var(--gray5) 20%, 93 | var(--gray9) 67.19%, 94 | rgba(236, 72, 153, 0) 95 | ); 96 | height: 1px; 97 | position: absolute; 98 | top: -1px; 99 | width: 100%; 100 | z-index: -1; 101 | opacity: 0; 102 | animation: showTopShine 0.1s ease forwards 0.2s; 103 | } 104 | } 105 | 106 | [cmdk-raycast-loader] { 107 | --loader-color: var(--gray9); 108 | border: 0; 109 | width: 100%; 110 | width: 100%; 111 | left: 0; 112 | height: 1px; 113 | background: var(--gray6); 114 | position: relative; 115 | overflow: visible; 116 | display: block; 117 | margin-top: 12px; 118 | margin-bottom: 12px; 119 | 120 | &:after { 121 | content: ''; 122 | width: 50%; 123 | height: 1px; 124 | position: absolute; 125 | background: linear-gradient(90deg, transparent 0%, var(--loader-color) 50%, transparent 100%); 126 | top: -1px; 127 | opacity: 0; 128 | animation-duration: 1.5s; 129 | animation-delay: 1s; 130 | animation-timing-function: ease; 131 | animation-name: loading; 132 | } 133 | } 134 | 135 | [cmdk-item] { 136 | content-visibility: auto; 137 | 138 | cursor: pointer; 139 | height: 40px; 140 | border-radius: 8px; 141 | font-size: 14px; 142 | display: flex; 143 | align-items: center; 144 | gap: 8px; 145 | padding: 0 8px; 146 | color: var(--gray12); 147 | user-select: none; 148 | will-change: background, color; 149 | transition: all 150ms ease; 150 | transition-property: none; 151 | 152 | &[data-selected='true'] { 153 | background: var(--gray4); 154 | color: var(--gray12); 155 | } 156 | 157 | &[data-disabled='true'] { 158 | color: var(--gray8); 159 | cursor: not-allowed; 160 | } 161 | 162 | &:active { 163 | transition-property: background; 164 | background: var(--gray4); 165 | } 166 | 167 | &:first-child { 168 | margin-top: 8px; 169 | } 170 | 171 | & + [cmdk-item] { 172 | margin-top: 4px; 173 | } 174 | 175 | svg { 176 | width: 18px; 177 | height: 18px; 178 | } 179 | } 180 | 181 | [cmdk-raycast-meta] { 182 | margin-left: auto; 183 | color: var(--gray11); 184 | font-size: 13px; 185 | } 186 | 187 | [cmdk-list] { 188 | padding: 0 8px; 189 | height: 393px; 190 | overflow: auto; 191 | overscroll-behavior: contain; 192 | scroll-padding-block-end: 40px; 193 | transition: 100ms ease; 194 | transition-property: height; 195 | padding-bottom: 40px; 196 | } 197 | 198 | [cmdk-raycast-open-trigger], 199 | [cmdk-raycast-subcommand-trigger] { 200 | color: var(--gray11); 201 | padding: 0px 4px 0px 8px; 202 | border-radius: 6px; 203 | font-weight: 500; 204 | font-size: 12px; 205 | height: 28px; 206 | letter-spacing: -0.25px; 207 | } 208 | 209 | [cmdk-raycast-clipboard-icon], 210 | [cmdk-raycast-hammer-icon] { 211 | width: 20px; 212 | height: 20px; 213 | border-radius: 6px; 214 | display: flex; 215 | align-items: center; 216 | justify-content: center; 217 | color: #ffffff; 218 | 219 | svg { 220 | width: 14px; 221 | height: 14px; 222 | } 223 | } 224 | 225 | [cmdk-raycast-clipboard-icon] { 226 | background: linear-gradient(to bottom, #f55354, #eb4646); 227 | } 228 | 229 | [cmdk-raycast-hammer-icon] { 230 | background: linear-gradient(to bottom, #6cb9a3, #2c6459); 231 | } 232 | 233 | [cmdk-raycast-open-trigger] { 234 | display: flex; 235 | align-items: center; 236 | color: var(--gray12); 237 | } 238 | 239 | [cmdk-raycast-subcommand-trigger] { 240 | display: flex; 241 | align-items: center; 242 | gap: 4px; 243 | right: 8px; 244 | bottom: 8px; 245 | 246 | svg { 247 | width: 14px; 248 | height: 14px; 249 | } 250 | 251 | hr { 252 | height: 100%; 253 | background: var(--gray6); 254 | border: 0; 255 | width: 1px; 256 | } 257 | 258 | &[aria-expanded='true'], 259 | &:hover { 260 | background: var(--gray4); 261 | 262 | kbd { 263 | background: var(--gray7); 264 | } 265 | } 266 | } 267 | 268 | [cmdk-separator] { 269 | height: 1px; 270 | width: 100%; 271 | background: var(--gray5); 272 | margin: 4px 0; 273 | } 274 | 275 | *:not([hidden]) + [cmdk-group] { 276 | margin-top: 8px; 277 | } 278 | 279 | [cmdk-group-heading] { 280 | user-select: none; 281 | font-size: 12px; 282 | color: var(--gray11); 283 | padding: 0 8px; 284 | display: flex; 285 | align-items: center; 286 | } 287 | 288 | [cmdk-raycast-footer] { 289 | display: flex; 290 | height: 40px; 291 | align-items: center; 292 | width: 100%; 293 | position: absolute; 294 | background: var(--gray1); 295 | bottom: 0; 296 | padding: 8px; 297 | border-top: 1px solid var(--gray6); 298 | border-radius: 0 0 12px 12px; 299 | 300 | svg { 301 | width: 20px; 302 | height: 20px; 303 | filter: grayscale(1); 304 | margin-right: auto; 305 | } 306 | 307 | hr { 308 | height: 12px; 309 | width: 1px; 310 | border: 0; 311 | background: var(--gray6); 312 | margin: 0 4px 0px 12px; 313 | } 314 | 315 | @media (prefers-color-scheme: dark) { 316 | background: var(--gray2); 317 | } 318 | } 319 | 320 | [cmdk-dialog] { 321 | z-index: var(--layer-portal); 322 | position: fixed; 323 | left: 50%; 324 | top: var(--page-top); 325 | transform: translateX(-50%); 326 | 327 | [cmdk] { 328 | width: 640px; 329 | transform-origin: center center; 330 | animation: dialogIn var(--transition-fast) forwards; 331 | } 332 | 333 | &[data-state='closed'] [cmdk] { 334 | animation: dialogOut var(--transition-fast) forwards; 335 | } 336 | } 337 | 338 | [cmdk-empty] { 339 | font-size: 14px; 340 | display: flex; 341 | align-items: center; 342 | justify-content: center; 343 | height: 64px; 344 | white-space: pre-wrap; 345 | color: var(--gray11); 346 | } 347 | } 348 | 349 | @keyframes loading { 350 | 0% { 351 | opacity: 0; 352 | transform: translateX(0); 353 | } 354 | 355 | 50% { 356 | opacity: 1; 357 | transform: translateX(100%); 358 | } 359 | 360 | 100% { 361 | opacity: 0; 362 | transform: translateX(0); 363 | } 364 | } 365 | 366 | @keyframes shine { 367 | to { 368 | background-position: 200% center; 369 | opacity: 0; 370 | } 371 | } 372 | 373 | @keyframes border { 374 | to { 375 | box-shadow: 0 0 0 1px var(--gray6); 376 | } 377 | } 378 | 379 | @keyframes showTopShine { 380 | to { 381 | opacity: 1; 382 | } 383 | } 384 | 385 | .raycast-submenu { 386 | z-index: 1000; 387 | [cmdk-root] { 388 | display: flex; 389 | flex-direction: column; 390 | width: 320px; 391 | border: 1px solid var(--gray6); 392 | background: var(--gray2); 393 | border-radius: 8px; 394 | } 395 | 396 | [cmdk-list] { 397 | padding: 8px; 398 | overflow: auto; 399 | overscroll-behavior: contain; 400 | transition: 100ms ease; 401 | transition-property: height; 402 | } 403 | 404 | [cmdk-item] { 405 | height: 40px; 406 | 407 | cursor: pointer; 408 | height: 40px; 409 | border-radius: 8px; 410 | font-size: 13px; 411 | display: flex; 412 | align-items: center; 413 | gap: 8px; 414 | padding: 0 8px; 415 | color: var(--gray12); 416 | user-select: none; 417 | will-change: background, color; 418 | transition: all 150ms ease; 419 | transition-property: none; 420 | 421 | &[aria-selected='true'] { 422 | background: var(--gray5); 423 | color: var(--gray12); 424 | 425 | [cmdk-raycast-submenu-shortcuts] kbd { 426 | background: var(--gray7); 427 | } 428 | } 429 | 430 | &[aria-disabled='true'] { 431 | color: var(--gray8); 432 | cursor: not-allowed; 433 | } 434 | 435 | svg { 436 | width: 16px; 437 | height: 16px; 438 | } 439 | 440 | [cmdk-raycast-submenu-shortcuts] { 441 | display: flex; 442 | margin-left: auto; 443 | gap: 2px; 444 | 445 | kbd { 446 | font-family: var(--font-sans); 447 | background: var(--gray5); 448 | color: var(--gray11); 449 | height: 20px; 450 | width: 20px; 451 | border-radius: 4px; 452 | padding: 0 4px; 453 | font-size: 12px; 454 | display: flex; 455 | align-items: center; 456 | justify-content: center; 457 | 458 | &:first-of-type { 459 | margin-left: 8px; 460 | } 461 | } 462 | } 463 | } 464 | 465 | [cmdk-group-heading] { 466 | text-transform: capitalize; 467 | font-size: 12px; 468 | color: var(--gray11); 469 | font-weight: 500; 470 | margin-bottom: 8px; 471 | margin-top: 8px; 472 | margin-left: 4px; 473 | } 474 | 475 | [cmdk-input] { 476 | padding: 12px; 477 | font-family: var(--font-sans); 478 | border: 0; 479 | border-top: 1px solid var(--gray6); 480 | font-size: 13px; 481 | background: transparent; 482 | margin-top: auto; 483 | width: 100%; 484 | outline: 0; 485 | border-radius: 0; 486 | } 487 | 488 | animation-duration: 0.2s; 489 | animation-timing-function: ease; 490 | animation-fill-mode: forwards; 491 | transform-origin: var(--kb-popper-content-transform-origin); 492 | 493 | &[data-state='open'] { 494 | animation-name: slideIn; 495 | } 496 | 497 | &[data-state='closed'] { 498 | animation-name: slideOut; 499 | } 500 | 501 | [cmdk-empty] { 502 | display: flex; 503 | align-items: center; 504 | justify-content: center; 505 | height: 64px; 506 | white-space: pre-wrap; 507 | font-size: 14px; 508 | color: var(--gray11); 509 | } 510 | } 511 | 512 | @keyframes slideIn { 513 | 0% { 514 | opacity: 0; 515 | transform: scale(0.96); 516 | } 517 | 518 | 100% { 519 | opacity: 1; 520 | transform: scale(1); 521 | } 522 | } 523 | 524 | @keyframes slideOut { 525 | 0% { 526 | opacity: 1; 527 | transform: scale(1); 528 | } 529 | 530 | 100% { 531 | opacity: 0; 532 | transform: scale(0.96); 533 | } 534 | } 535 | 536 | @media (max-width: 640px) { 537 | .raycast { 538 | [cmdk-input] { 539 | font-size: 16px; 540 | } 541 | } 542 | } 543 | -------------------------------------------------------------------------------- /website/src/styles/cmdk/vercel.scss: -------------------------------------------------------------------------------- 1 | .vercel { 2 | [cmdk-root] { 3 | max-width: 640px; 4 | width: 100%; 5 | padding: 8px; 6 | background: #ffffff; 7 | border-radius: 12px; 8 | overflow: hidden; 9 | font-family: var(--font-sans); 10 | border: 1px solid var(--gray6); 11 | box-shadow: var(--cmdk-shadow); 12 | transition: transform 100ms ease; 13 | outline: none; 14 | 15 | .dark & { 16 | background: rgba(22, 22, 22, 0.7); 17 | } 18 | } 19 | 20 | [cmdk-input] { 21 | font-family: var(--font-sans); 22 | border: none; 23 | width: 100%; 24 | font-size: 17px; 25 | padding: 8px 8px 16px 8px; 26 | outline: none; 27 | background: var(--bg); 28 | color: var(--gray12); 29 | border-bottom: 1px solid var(--gray6); 30 | margin-bottom: 16px; 31 | border-radius: 0; 32 | 33 | &::placeholder { 34 | color: var(--gray9); 35 | } 36 | } 37 | 38 | [cmdk-vercel-badge] { 39 | height: 20px; 40 | background: var(--grayA3); 41 | display: inline-flex; 42 | align-items: center; 43 | padding: 0 8px; 44 | font-size: 12px; 45 | color: var(--grayA11); 46 | border-radius: 4px; 47 | margin: 4px 0 4px 4px; 48 | user-select: none; 49 | text-transform: capitalize; 50 | font-weight: 500; 51 | } 52 | 53 | [cmdk-item] { 54 | content-visibility: auto; 55 | 56 | cursor: pointer; 57 | height: 48px; 58 | border-radius: 8px; 59 | font-size: 14px; 60 | display: flex; 61 | align-items: center; 62 | gap: 8px; 63 | padding: 0 16px; 64 | color: var(--gray11); 65 | user-select: none; 66 | will-change: background, color; 67 | transition: all 150ms ease; 68 | transition-property: none; 69 | 70 | &[data-selected='true'] { 71 | background: var(--grayA3); 72 | color: var(--gray12); 73 | } 74 | 75 | &[data-disabled='true'] { 76 | color: var(--gray8); 77 | cursor: not-allowed; 78 | } 79 | 80 | &:active { 81 | transition-property: background; 82 | background: var(--gray4); 83 | } 84 | 85 | & + [cmdk-item] { 86 | margin-top: 4px; 87 | } 88 | 89 | svg { 90 | width: 18px; 91 | height: 18px; 92 | } 93 | } 94 | 95 | [cmdk-list] { 96 | height: min(330px, calc(var(--cmdk-list-height))); 97 | max-height: 400px; 98 | overflow: auto; 99 | overscroll-behavior: contain; 100 | transition: 100ms ease; 101 | transition-property: height; 102 | } 103 | 104 | [cmdk-vercel-shortcuts] { 105 | display: flex; 106 | margin-left: auto; 107 | gap: 8px; 108 | 109 | kbd { 110 | font-family: var(--font-sans); 111 | font-size: 12px; 112 | min-width: 20px; 113 | padding: 4px; 114 | height: 20px; 115 | border-radius: 4px; 116 | color: var(--gray11); 117 | background: var(--gray4); 118 | display: inline-flex; 119 | align-items: center; 120 | justify-content: center; 121 | text-transform: uppercase; 122 | } 123 | } 124 | 125 | [cmdk-separator] { 126 | height: 1px; 127 | width: 100%; 128 | background: var(--gray5); 129 | margin: 4px 0; 130 | } 131 | 132 | *:not([hidden]) + [cmdk-group] { 133 | margin-top: 8px; 134 | } 135 | 136 | [cmdk-group-heading] { 137 | user-select: none; 138 | font-size: 12px; 139 | color: var(--gray11); 140 | padding: 0 8px; 141 | display: flex; 142 | align-items: center; 143 | margin-bottom: 8px; 144 | } 145 | 146 | [cmdk-empty] { 147 | font-size: 14px; 148 | display: flex; 149 | align-items: center; 150 | justify-content: center; 151 | height: 48px; 152 | white-space: pre-wrap; 153 | color: var(--gray11); 154 | } 155 | } 156 | -------------------------------------------------------------------------------- /website/src/styles/globals.scss: -------------------------------------------------------------------------------- 1 | @tailwind base; 2 | @tailwind components; 3 | @tailwind utilities; 4 | 5 | @layer base { 6 | .tailwind-reset { 7 | color-scheme: light; 8 | } 9 | .tailwind-reset *, 10 | .tailwind-reset ::before, 11 | .tailwind-reset ::after { 12 | border-width: 0; 13 | border-style: solid; 14 | border-color: theme('borderColor.DEFAULT', currentColor); 15 | } 16 | } 17 | 18 | @layer base { 19 | :root { 20 | --background: 0 0% 100%; 21 | --foreground: 222.2 47.4% 11.2%; 22 | 23 | --muted: 210 40% 96.1%; 24 | --muted-foreground: 215.4 16.3% 46.9%; 25 | 26 | --popover: 0 0% 100%; 27 | --popover-foreground: 222.2 47.4% 11.2%; 28 | 29 | --border: 214.3 31.8% 91.4%; 30 | --input: 214.3 31.8% 91.4%; 31 | 32 | --card: 0 0% 100%; 33 | --card-foreground: 222.2 47.4% 11.2%; 34 | 35 | --primary: 222.2 47.4% 11.2%; 36 | --primary-foreground: 210 40% 98%; 37 | 38 | --secondary: 210 40% 96.1%; 39 | --secondary-foreground: 222.2 47.4% 11.2%; 40 | 41 | --accent: 210 40% 96.1%; 42 | --accent-foreground: 222.2 47.4% 11.2%; 43 | 44 | --destructive: 0 100% 50%; 45 | --destructive-foreground: 210 40% 98%; 46 | 47 | --ring: 215 20.2% 65.1%; 48 | 49 | --radius: 0.5rem; 50 | } 51 | } 52 | 53 | @font-face { 54 | font-family: 'Inter'; 55 | font-style: normal; 56 | font-weight: 100 900; // Range of weights supported 57 | font-display: optional; 58 | src: url(/inter-var-latin.woff2) format('woff2'); 59 | unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+2000-206F, U+2074, U+20AC, 60 | U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD; 61 | } 62 | 63 | ::selection { 64 | background: hotpink; 65 | color: white; 66 | } 67 | 68 | html, 69 | body { 70 | padding: 0; 71 | margin: 0; 72 | font-family: var(--font-sans); 73 | } 74 | 75 | body { 76 | background: var(--app-bg); 77 | overflow-x: hidden; 78 | } 79 | 80 | button { 81 | background: none; 82 | font-family: var(--font-sans); 83 | padding: 0; 84 | border: 0; 85 | } 86 | 87 | h1, 88 | h2, 89 | h3, 90 | h4, 91 | h5, 92 | h6, 93 | p { 94 | margin: 0; 95 | } 96 | 97 | a { 98 | color: inherit; 99 | text-decoration: none; 100 | } 101 | 102 | *, 103 | *::after, 104 | *::before { 105 | box-sizing: border-box; 106 | -webkit-font-smoothing: antialiased; 107 | -moz-osx-font-smoothing: grayscale; 108 | } 109 | 110 | :root { 111 | --font-sans: 'Inter', --apple-system, BlinkMacSystemFont, Segoe UI, Roboto, Oxygen, Ubuntu, Cantarell, Fira Sans, 112 | Droid Sans, Helvetica Neue, sans-serif; 113 | --app-bg: var(--gray1); 114 | --cmdk-shadow: 0 16px 70px rgb(0 0 0 / 20%); 115 | 116 | --lowContrast: #ffffff; 117 | --highContrast: #000000; 118 | 119 | --gray1: hsl(0, 0%, 99%); 120 | --gray2: hsl(0, 0%, 97.3%); 121 | --gray3: hsl(0, 0%, 95.1%); 122 | --gray4: hsl(0, 0%, 93%); 123 | --gray5: hsl(0, 0%, 90.9%); 124 | --gray6: hsl(0, 0%, 88.7%); 125 | --gray7: hsl(0, 0%, 85.8%); 126 | --gray8: hsl(0, 0%, 78%); 127 | --gray9: hsl(0, 0%, 56.1%); 128 | --gray10: hsl(0, 0%, 52.3%); 129 | --gray11: hsl(0, 0%, 43.5%); 130 | --gray12: hsl(0, 0%, 9%); 131 | 132 | --grayA1: hsla(0, 0%, 0%, 0.012); 133 | --grayA2: hsla(0, 0%, 0%, 0.027); 134 | --grayA3: hsla(0, 0%, 0%, 0.047); 135 | --grayA4: hsla(0, 0%, 0%, 0.071); 136 | --grayA5: hsla(0, 0%, 0%, 0.09); 137 | --grayA6: hsla(0, 0%, 0%, 0.114); 138 | --grayA7: hsla(0, 0%, 0%, 0.141); 139 | --grayA8: hsla(0, 0%, 0%, 0.22); 140 | --grayA9: hsla(0, 0%, 0%, 0.439); 141 | --grayA10: hsla(0, 0%, 0%, 0.478); 142 | --grayA11: hsla(0, 0%, 0%, 0.565); 143 | --grayA12: hsla(0, 0%, 0%, 0.91); 144 | 145 | --blue1: hsl(206, 100%, 99.2%); 146 | --blue2: hsl(210, 100%, 98%); 147 | --blue3: hsl(209, 100%, 96.5%); 148 | --blue4: hsl(210, 98.8%, 94%); 149 | --blue5: hsl(209, 95%, 90.1%); 150 | --blue6: hsl(209, 81.2%, 84.5%); 151 | --blue7: hsl(208, 77.5%, 76.9%); 152 | --blue8: hsl(206, 81.9%, 65.3%); 153 | --blue9: hsl(206, 100%, 50%); 154 | --blue10: hsl(208, 100%, 47.3%); 155 | --blue11: hsl(211, 100%, 43.2%); 156 | --blue12: hsl(211, 100%, 15%); 157 | } 158 | 159 | .dark { 160 | --app-bg: var(--gray1); 161 | 162 | --lowContrast: #000000; 163 | --highContrast: #ffffff; 164 | 165 | --gray1: hsl(0, 0%, 8.5%); 166 | --gray2: hsl(0, 0%, 11%); 167 | --gray3: hsl(0, 0%, 13.6%); 168 | --gray4: hsl(0, 0%, 15.8%); 169 | --gray5: hsl(0, 0%, 17.9%); 170 | --gray6: hsl(0, 0%, 20.5%); 171 | --gray7: hsl(0, 0%, 24.3%); 172 | --gray8: hsl(0, 0%, 31.2%); 173 | --gray9: hsl(0, 0%, 43.9%); 174 | --gray10: hsl(0, 0%, 49.4%); 175 | --gray11: hsl(0, 0%, 62.8%); 176 | --gray12: hsl(0, 0%, 93%); 177 | 178 | --grayA1: hsla(0, 0%, 100%, 0); 179 | --grayA2: hsla(0, 0%, 100%, 0.026); 180 | --grayA3: hsla(0, 0%, 100%, 0.056); 181 | --grayA4: hsla(0, 0%, 100%, 0.077); 182 | --grayA5: hsla(0, 0%, 100%, 0.103); 183 | --grayA6: hsla(0, 0%, 100%, 0.129); 184 | --grayA7: hsla(0, 0%, 100%, 0.172); 185 | --grayA8: hsla(0, 0%, 100%, 0.249); 186 | --grayA9: hsla(0, 0%, 100%, 0.386); 187 | --grayA10: hsla(0, 0%, 100%, 0.446); 188 | --grayA11: hsla(0, 0%, 100%, 0.592); 189 | --grayA12: hsla(0, 0%, 100%, 0.923); 190 | 191 | --blue1: hsl(212, 35%, 9.2%); 192 | --blue2: hsl(216, 50%, 11.8%); 193 | --blue3: hsl(214, 59.4%, 15.3%); 194 | --blue4: hsl(214, 65.8%, 17.9%); 195 | --blue5: hsl(213, 71.2%, 20.2%); 196 | --blue6: hsl(212, 77.4%, 23.1%); 197 | --blue7: hsl(211, 85.1%, 27.4%); 198 | --blue8: hsl(211, 89.7%, 34.1%); 199 | --blue9: hsl(206, 100%, 50%); 200 | --blue10: hsl(209, 100%, 60.6%); 201 | --blue11: hsl(210, 100%, 66.1%); 202 | --blue12: hsl(206, 98%, 95.8%); 203 | } 204 | -------------------------------------------------------------------------------- /website/src/styles/index.module.scss: -------------------------------------------------------------------------------- 1 | .main { 2 | width: 100vw; 3 | min-height: 100vh; 4 | position: relative; 5 | display: flex; 6 | justify-content: center; 7 | padding: 120px 24px 160px 24px; 8 | 9 | &:before { 10 | background: radial-gradient(circle, rgba(2, 0, 36, 0) 0, var(--gray1) 100%); 11 | position: absolute; 12 | content: ''; 13 | z-index: 2; 14 | width: 100%; 15 | height: 100%; 16 | top: 0; 17 | } 18 | 19 | &:after { 20 | content: ''; 21 | background-image: url('/grid.svg'); 22 | z-index: -1; 23 | position: absolute; 24 | width: 100%; 25 | height: 100%; 26 | top: 0; 27 | opacity: 0.2; 28 | filter: invert(1); 29 | 30 | @media (prefers-color-scheme: dark) { 31 | filter: unset; 32 | } 33 | } 34 | 35 | h1 { 36 | font-size: 32px; 37 | color: var(--gray12); 38 | font-weight: 600; 39 | letter-spacing: -2px; 40 | line-height: 40px; 41 | } 42 | 43 | p { 44 | color: var(--gray11); 45 | margin-top: 8px; 46 | font-size: 16px; 47 | } 48 | } 49 | 50 | .content { 51 | height: fit-content; 52 | position: relative; 53 | z-index: 3; 54 | width: 100%; 55 | max-width: 640px; 56 | 57 | &:after { 58 | background-image: conic-gradient(from 315deg, rgba(68, 108, 158, 1), rgba(80, 137, 199, 0.4)); 59 | position: absolute; 60 | content: ''; 61 | z-index: 2; 62 | width: 100%; 63 | height: 100%; 64 | filter: blur(100px) saturate(300%); 65 | z-index: -1; 66 | top: 80px; 67 | opacity: 0.2; 68 | transform: translateZ(0); 69 | 70 | @media (prefers-color-scheme: dark) { 71 | opacity: 0.1; 72 | } 73 | } 74 | } 75 | 76 | [cmdk-root] { 77 | margin: 0px auto; 78 | } 79 | .info { 80 | max-width: 400px; 81 | line-height: 24px; 82 | a { 83 | text-decoration: underline; 84 | } 85 | } 86 | .meta { 87 | display: flex; 88 | align-items: center; 89 | justify-content: space-between; 90 | margin-bottom: 48px; 91 | flex-wrap: wrap; 92 | gap: 16px; 93 | } 94 | 95 | .buttons { 96 | display: flex; 97 | flex-direction: column; 98 | align-items: flex-end; 99 | gap: 12px; 100 | transform: translateY(12px); 101 | } 102 | 103 | .githubButton, 104 | .installButton, 105 | .switcher button { 106 | height: 40px; 107 | color: var(--gray12); 108 | border-radius: 9999px; 109 | font-size: 14px; 110 | transition-duration: 150ms; 111 | transition-property: background, color, transform; 112 | transition-timing-function: ease-in; 113 | will-change: transform; 114 | } 115 | 116 | .githubButton { 117 | width: 215px; 118 | padding: 0 12px; 119 | display: inline-flex; 120 | align-items: center; 121 | gap: 8px; 122 | font-weight: 500; 123 | 124 | &:hover { 125 | background: var(--grayA3); 126 | } 127 | 128 | &:active { 129 | background: var(--grayA5); 130 | transform: scale(0.97); 131 | } 132 | 133 | &:focus-visible { 134 | outline: 0; 135 | outline: 2px solid var(--gray7); 136 | } 137 | } 138 | 139 | .installButton { 140 | background: var(--grayA3); 141 | display: flex; 142 | align-items: center; 143 | gap: 16px; 144 | padding: 0px 8px 0 16px; 145 | cursor: copy; 146 | font-weight: 500; 147 | 148 | &:hover { 149 | background: var(--grayA4); 150 | 151 | span { 152 | background: var(--grayA5); 153 | 154 | svg { 155 | color: var(--gray12); 156 | } 157 | } 158 | } 159 | 160 | &:focus-visible { 161 | outline: 0; 162 | outline: 2px solid var(--gray7); 163 | outline-offset: 2px; 164 | } 165 | 166 | &:active { 167 | background: var(--gray5); 168 | transform: scale(0.97); 169 | } 170 | 171 | span { 172 | width: 28px; 173 | height: 28px; 174 | display: flex; 175 | align-items: center; 176 | justify-content: center; 177 | margin-left: auto; 178 | background: var(--grayA3); 179 | border-radius: 9999px; 180 | transition: background 150ms ease; 181 | 182 | svg { 183 | size: 16px; 184 | color: var(--gray11); 185 | transition: color 150ms ease; 186 | } 187 | } 188 | } 189 | 190 | .switcher { 191 | display: grid; 192 | grid-template-columns: repeat(5, 100px); 193 | align-items: center; 194 | justify-content: center; 195 | gap: 4px; 196 | margin-top: 48px; 197 | position: relative; 198 | 199 | button { 200 | height: 32px; 201 | line-height: 32px; 202 | display: flex; 203 | align-items: center; 204 | margin: auto; 205 | gap: 8px; 206 | padding: 0 16px; 207 | border-radius: 9999px; 208 | color: var(--gray11); 209 | font-size: 14px; 210 | cursor: pointer; 211 | user-select: none; 212 | position: relative; 213 | text-transform: capitalize; 214 | 215 | &:hover { 216 | color: var(--gray12); 217 | } 218 | 219 | &:active { 220 | transform: scale(0.96); 221 | } 222 | 223 | &:focus-visible { 224 | outline: 0; 225 | outline: 2px solid var(--gray7); 226 | } 227 | 228 | svg { 229 | width: 14px; 230 | height: 14px; 231 | } 232 | 233 | &[data-selected='true'] { 234 | color: var(--gray12); 235 | 236 | &:hover .activeTheme { 237 | background: var(--grayA6); 238 | } 239 | 240 | &:active { 241 | transform: scale(0.96); 242 | 243 | .activeTheme { 244 | background: var(--grayA7); 245 | } 246 | } 247 | } 248 | } 249 | 250 | .activeTheme { 251 | background: var(--grayA5); 252 | border-radius: 9999px; 253 | height: 32px; 254 | width: 100%; 255 | top: 0; 256 | position: absolute; 257 | left: 0; 258 | } 259 | 260 | .arrow { 261 | color: var(--gray11); 262 | user-select: none; 263 | position: absolute; 264 | } 265 | } 266 | 267 | .header { 268 | position: absolute; 269 | left: 0; 270 | top: -64px; 271 | gap: 8px; 272 | background: var(--gray3); 273 | padding: 4px; 274 | display: flex; 275 | align-items: center; 276 | border-radius: 9999px; 277 | 278 | button { 279 | display: flex; 280 | align-items: center; 281 | justify-content: center; 282 | width: 28px; 283 | height: 28px; 284 | padding: 4px; 285 | border-radius: 9999px; 286 | color: var(--gray11); 287 | 288 | svg { 289 | width: 16px; 290 | height: 16px; 291 | } 292 | 293 | &[aria-selected='true'] { 294 | background: #ffffff; 295 | color: var(--gray12); 296 | box-shadow: 297 | 0px 2px 5px -2px rgb(0 0 0 / 15%), 298 | 0 1px 3px -1px rgb(0 0 0 / 20%); 299 | } 300 | } 301 | } 302 | 303 | .versionBadge { 304 | display: inline-flex; 305 | align-items: center; 306 | justify-content: center; 307 | color: var(--grayA11); 308 | background: var(--grayA3); 309 | padding: 4px 8px; 310 | border-radius: 4px; 311 | font-weight: 500; 312 | font-size: 14px; 313 | margin-bottom: 8px; 314 | 315 | @media (prefers-color-scheme: dark) { 316 | background: var(--grayA2); 317 | } 318 | } 319 | 320 | .codeBlock { 321 | margin-top: 72px; 322 | position: relative; 323 | } 324 | 325 | .footer { 326 | display: flex; 327 | align-items: center; 328 | gap: 4px; 329 | width: fit-content; 330 | margin: 32px auto; 331 | bottom: 16px; 332 | color: var(--gray11); 333 | font-size: 13px; 334 | z-index: 3; 335 | position: absolute; 336 | bottom: 0; 337 | 338 | a { 339 | display: inline-flex; 340 | align-items: center; 341 | gap: 4px; 342 | color: var(--gray12); 343 | font-weight: 500; 344 | border-radius: 9999px; 345 | padding: 4px; 346 | margin: 0 -2px; 347 | transition: background 150ms ease; 348 | 349 | &:hover, 350 | &:focus-visible { 351 | background: var(--grayA4); 352 | outline: 0; 353 | } 354 | } 355 | 356 | img { 357 | width: 20px; 358 | height: 20px; 359 | border: 1px solid var(--gray5); 360 | border-radius: 9999px; 361 | } 362 | } 363 | 364 | .line { 365 | height: 20px; 366 | width: 180px; 367 | margin: 64px auto; 368 | background-image: url('/line.svg'); 369 | filter: invert(1); 370 | mask-image: linear-gradient(90deg, transparent, #fff 4rem, #fff calc(100% - 4rem), transparent); 371 | 372 | @media (prefers-color-scheme: dark) { 373 | filter: unset; 374 | } 375 | } 376 | 377 | .line2 { 378 | height: 1px; 379 | width: 300px; 380 | background: var(--gray7); 381 | position: absolute; 382 | top: 0; 383 | mask-image: linear-gradient(90deg, transparent, #fff 4rem, #fff calc(100% - 4rem), transparent); 384 | } 385 | 386 | .line3 { 387 | height: 300px; 388 | width: calc(100% + 32px); 389 | position: absolute; 390 | top: -16px; 391 | left: -16px; 392 | 393 | border-radius: 16px 16px 0 0; 394 | --size: 1px; 395 | --gradient: linear-gradient(to top, var(--gray1), var(--gray7)); 396 | 397 | &::before { 398 | content: ''; 399 | position: absolute; 400 | inset: 0; 401 | border-radius: inherit; 402 | padding: var(--size); 403 | background: linear-gradient(to top, var(--gray1), var(--gray7)); 404 | mask: 405 | linear-gradient(black, black) content-box, 406 | linear-gradient(black, black); 407 | mask-composite: exclude; 408 | transform: translateZ(0); 409 | 410 | @media (prefers-color-scheme: dark) { 411 | mask: none; 412 | mask-composite: none; 413 | opacity: 0.2; 414 | backdrop-filter: blur(20px); 415 | } 416 | } 417 | } 418 | 419 | .raunoSignature, 420 | .pacoSignature { 421 | position: absolute; 422 | height: fit-content; 423 | color: var(--gray11); 424 | pointer-events: none; 425 | } 426 | 427 | .raunoSignature { 428 | width: 120px; 429 | stroke-dashoffset: 1; 430 | stroke-dasharray: 1; 431 | right: -48px; 432 | } 433 | 434 | .pacoSignature { 435 | width: 120px; 436 | stroke-dashoffset: 1; 437 | stroke-dasharray: 1; 438 | left: -8px; 439 | } 440 | 441 | .footerText { 442 | display: flex; 443 | display: flex; 444 | align-items: center; 445 | gap: 4px; 446 | opacity: 0; 447 | } 448 | 449 | .footer[data-animate='true'] { 450 | .raunoSignature path { 451 | animation: drawRaunoSignature 1.5s ease forwards 0.5s; 452 | } 453 | 454 | .pacoSignature path { 455 | animation: drawPacoSignature 0.8s linear forwards 0.5s; 456 | } 457 | 458 | .footerText { 459 | animation: showFooter 1s linear forwards 0s; 460 | } 461 | } 462 | 463 | @keyframes drawPacoSignature { 464 | 100% { 465 | stroke-dashoffset: 0; 466 | } 467 | } 468 | 469 | @keyframes drawRaunoSignature { 470 | 100% { 471 | stroke-dashoffset: 0; 472 | } 473 | } 474 | 475 | @keyframes showFooter { 476 | 100% { 477 | opacity: 1; 478 | } 479 | } 480 | 481 | @media (max-width: 640px) { 482 | .main { 483 | padding-top: 24px; 484 | padding-bottom: 120px; 485 | } 486 | 487 | .switcher { 488 | grid-template-columns: repeat(2, 100px); 489 | gap: 16px; 490 | 491 | .arrow { 492 | display: none; 493 | } 494 | } 495 | } 496 | -------------------------------------------------------------------------------- /website/tailwind.config.js: -------------------------------------------------------------------------------- 1 | /** @type {import('tailwindcss').Config} */ 2 | const { fontFamily } = require('tailwindcss/defaultTheme') 3 | 4 | module.exports = { 5 | darkMode: 'selector', 6 | content: ['./src/**/*.{html,tsx}'], 7 | theme: { 8 | container: { 9 | center: true, 10 | padding: '2rem', 11 | screens: { 12 | '2xl': '1400px', 13 | }, 14 | }, 15 | extend: { 16 | colors: { 17 | border: 'hsl(var(--border))', 18 | input: 'hsl(var(--input))', 19 | ring: 'hsl(var(--ring))', 20 | background: 'hsl(var(--background))', 21 | foreground: 'hsl(var(--foreground))', 22 | primary: { 23 | DEFAULT: 'hsl(var(--primary))', 24 | foreground: 'hsl(var(--primary-foreground))', 25 | }, 26 | secondary: { 27 | DEFAULT: 'hsl(var(--secondary))', 28 | foreground: 'hsl(var(--secondary-foreground))', 29 | }, 30 | destructive: { 31 | DEFAULT: 'hsl(var(--destructive))', 32 | foreground: 'hsl(var(--destructive-foreground))', 33 | }, 34 | muted: { 35 | DEFAULT: 'hsl(var(--muted))', 36 | foreground: 'hsl(var(--muted-foreground))', 37 | }, 38 | accent: { 39 | DEFAULT: 'hsl(var(--accent))', 40 | foreground: 'hsl(var(--accent-foreground))', 41 | }, 42 | popover: { 43 | DEFAULT: 'hsl(var(--popover))', 44 | foreground: 'hsl(var(--popover-foreground))', 45 | }, 46 | card: { 47 | DEFAULT: 'hsl(var(--card))', 48 | foreground: 'hsl(var(--card-foreground))', 49 | }, 50 | }, 51 | borderRadius: { 52 | lg: `var(--radius)`, 53 | md: `calc(var(--radius) - 2px)`, 54 | sm: 'calc(var(--radius) - 4px)', 55 | }, 56 | fontFamily: { 57 | sans: ['var(--font-sans)', ...fontFamily.sans], 58 | }, 59 | keyframes: { 60 | 'accordion-down': { 61 | from: { height: '0' }, 62 | to: { height: 'var(--radix-accordion-content-height)' }, 63 | }, 64 | 'accordion-up': { 65 | from: { height: 'var(--radix-accordion-content-height)' }, 66 | to: { height: '0' }, 67 | }, 68 | }, 69 | animation: { 70 | 'accordion-down': 'accordion-down 0.2s ease-out', 71 | 'accordion-up': 'accordion-up 0.2s ease-out', 72 | }, 73 | lineHeight: { 74 | base: '1em', 75 | }, 76 | }, 77 | }, 78 | plugins: [require('tailwindcss-animate')], 79 | corePlugins: { 80 | preflight: false, 81 | }, 82 | } 83 | -------------------------------------------------------------------------------- /website/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "ESNext", 4 | "module": "ESNext", 5 | "moduleResolution": "node", 6 | "allowSyntheticDefaultImports": true, 7 | "resolveJsonModule": true, 8 | "esModuleInterop": true, 9 | "jsx": "preserve", 10 | "jsxImportSource": "solid-js", 11 | "allowJs": true, 12 | "strict": true, 13 | "noEmit": true, 14 | "types": ["vinxi/client", "vite/client"], 15 | "isolatedModules": true, 16 | "paths": { 17 | "~/*": ["./src/*"] 18 | } 19 | } 20 | } 21 | --------------------------------------------------------------------------------