├── .all-contributorsrc
├── .editorconfig
├── .eslintrc.json
├── .github
├── ISSUE_TEMPLATE.md
├── PULL_REQUEST_TEMPLATE.md
├── dependabot.yml
└── workflows
│ ├── release.yaml
│ └── test.yaml
├── .gitignore
├── .husky
└── commit-msg
├── .prettierrc.json
├── .releaserc.json
├── .vscode
├── extensions.json
├── launch.json
└── tasks.json
├── CHANGELOG.md
├── CODE_OF_CONDUCT.md
├── CONTRIBUTING.md
├── ISSUE_TEMPLATE.md
├── LICENSE
├── PULL_REQUEST_TEMPLATE.md
├── README.md
├── angular.json
├── assets
└── ngxpert cmdk cover.png
├── commitlint.config.js
├── cypress.config.ts
├── cypress
├── basic.cy.ts
├── fixtures
│ └── example.json
├── group.cy.ts
├── item.cy.ts
├── keybindings.cy.ts
├── props.cy.ts
└── support
│ ├── commands.ts
│ ├── component-index.html
│ └── component.ts
├── hooks
└── pre-commit.js
├── karma.conf.js
├── logo.svg
├── package-lock.json
├── package.json
├── projects
└── ngxpert
│ └── cmdk
│ ├── .eslintrc.json
│ ├── README.md
│ ├── karma.conf.js
│ ├── ng-package.json
│ ├── package.json
│ ├── src
│ ├── lib
│ │ ├── cmdk.module.ts
│ │ ├── cmdk.service.spec.ts
│ │ ├── cmdk.service.ts
│ │ ├── components
│ │ │ ├── command
│ │ │ │ ├── command.component.html
│ │ │ │ ├── command.component.spec.ts
│ │ │ │ └── command.component.ts
│ │ │ ├── group
│ │ │ │ ├── group.component.html
│ │ │ │ ├── group.component.scss
│ │ │ │ └── group.component.ts
│ │ │ ├── list
│ │ │ │ ├── list.component.html
│ │ │ │ └── list.component.ts
│ │ │ └── separator
│ │ │ │ ├── separator.component.html
│ │ │ │ ├── separator.component.scss
│ │ │ │ └── separator.component.ts
│ │ ├── directives
│ │ │ ├── empty
│ │ │ │ ├── empty.directive.spec.ts
│ │ │ │ └── empty.directive.ts
│ │ │ ├── input
│ │ │ │ ├── input.directive.spec.ts
│ │ │ │ └── input.directive.ts
│ │ │ ├── item
│ │ │ │ ├── item.directive.spec.ts
│ │ │ │ └── item.directive.ts
│ │ │ └── loader
│ │ │ │ ├── loader.directive.spec.ts
│ │ │ │ └── loader.directive.ts
│ │ └── types.ts
│ ├── public-api.ts
│ └── test.ts
│ ├── styles
│ ├── framer.scss
│ ├── globals.scss
│ ├── linear.scss
│ ├── raycast.scss
│ └── vercel.scss
│ ├── tsconfig.lib.json
│ ├── tsconfig.lib.prod.json
│ └── tsconfig.spec.json
├── src
├── app
│ ├── app.component.html
│ ├── app.component.ts
│ ├── core
│ │ └── services
│ │ │ ├── code-highlight.service.spec.ts
│ │ │ └── code-highlight.service.ts
│ ├── icons
│ │ ├── assign-to-icon
│ │ │ └── assign-to-icon.component.ts
│ │ ├── assign-to-me-icon
│ │ │ └── assign-to-me-icon.component.ts
│ │ ├── avatar
│ │ │ └── avatar.component.ts
│ │ ├── badge
│ │ │ └── badge.component.ts
│ │ ├── button
│ │ │ └── button.component.ts
│ │ ├── change-labels-icon
│ │ │ └── change-labels-icon.component.ts
│ │ ├── change-priority-icon
│ │ │ └── change-priority-icon.component.ts
│ │ ├── change-status-icon
│ │ │ └── change-status-icon.component.ts
│ │ ├── clipboard-icon
│ │ │ └── clipboard-icon.component.ts
│ │ ├── contact-icon
│ │ │ └── contact-icon.component.ts
│ │ ├── copied
│ │ │ └── copied.component.ts
│ │ ├── copy
│ │ │ └── copy.component.ts
│ │ ├── docs-icon
│ │ │ └── docs-icon.component.ts
│ │ ├── feedback-icon
│ │ │ └── feedback-icon.component.ts
│ │ ├── figma
│ │ │ └── figma.component.ts
│ │ ├── finder-icon
│ │ │ └── finder-icon.component.ts
│ │ ├── framer
│ │ │ └── framer.component.ts
│ │ ├── github
│ │ │ └── github.component.ts
│ │ ├── hammer-icon
│ │ │ └── hammer-icon.component.ts
│ │ ├── icons.module.ts
│ │ ├── input
│ │ │ └── input.component.ts
│ │ ├── layout
│ │ │ └── layout.component.ts
│ │ ├── linear-icon
│ │ │ └── linear-icon.component.ts
│ │ ├── logo
│ │ │ └── logo.component.ts
│ │ ├── plus-icon
│ │ │ └── plus-icon.component.ts
│ │ ├── projects-icon
│ │ │ └── projects-icon.component.ts
│ │ ├── radio
│ │ │ └── radio.component.ts
│ │ ├── raycast-dark-icon
│ │ │ └── raycast-dark-icon.component.ts
│ │ ├── raycast-icon
│ │ │ └── raycast-icon.component.ts
│ │ ├── raycast-light-icon
│ │ │ └── raycast-light-icon.component.ts
│ │ ├── remove-label-icon
│ │ │ └── remove-label-icon.component.ts
│ │ ├── search
│ │ │ └── search.component.ts
│ │ ├── set-due-date-icon
│ │ │ └── set-due-date-icon.component.ts
│ │ ├── slack-icon
│ │ │ └── slack-icon.component.ts
│ │ ├── slider
│ │ │ └── slider.component.ts
│ │ ├── star-icon
│ │ │ └── star-icon.component.ts
│ │ ├── teams-icon
│ │ │ └── teams-icon.component.ts
│ │ ├── vercel-icon
│ │ │ └── vercel-icon.component.ts
│ │ ├── window-icon
│ │ │ └── window-icon.component.ts
│ │ └── you-tube-icon
│ │ │ └── you-tube-icon.component.ts
│ ├── sections
│ │ ├── footer
│ │ │ ├── footer.component.html
│ │ │ └── footer.component.ts
│ │ ├── meta
│ │ │ ├── meta.component.html
│ │ │ └── meta.component.ts
│ │ └── theme-switcher
│ │ │ ├── theme-switcher.component.html
│ │ │ └── theme-switcher.component.ts
│ ├── shared
│ │ ├── components
│ │ │ └── code-block
│ │ │ │ ├── code-block.component.html
│ │ │ │ ├── code-block.component.scss
│ │ │ │ └── code-block.component.ts
│ │ └── shared.module.ts
│ ├── tests
│ │ ├── group.component.ts
│ │ ├── item-advanced.component.ts
│ │ ├── item.component.ts
│ │ ├── keybindings.component.ts
│ │ ├── props.component.ts
│ │ └── tests.module.ts
│ └── themes
│ │ ├── framer
│ │ ├── framer.component.html
│ │ └── framer.component.ts
│ │ ├── linear
│ │ ├── linear.component.html
│ │ └── linear.component.ts
│ │ ├── raycast
│ │ ├── raycast.component.html
│ │ ├── raycast.component.ts
│ │ ├── sub-command-dialog
│ │ │ ├── sub-command-dialog.component.html
│ │ │ ├── sub-command-dialog.component.scss
│ │ │ └── sub-command-dialog.component.ts
│ │ └── sub-command
│ │ │ ├── sub-command.component.html
│ │ │ ├── sub-command.component.scss
│ │ │ └── sub-command.component.ts
│ │ └── vercel
│ │ ├── vercel.component.html
│ │ ├── vercel.component.scss
│ │ └── vercel.component.ts
├── assets
│ ├── .gitkeep
│ ├── grid.svg
│ ├── line.svg
│ └── ngxpert cdk.svg
├── environments
│ ├── environment.prod.ts
│ └── environment.ts
├── favicon.ico
├── index.html
├── main.ts
├── polyfills.ts
├── styles.scss
├── test.ts
└── types.d.ts
├── tsconfig.app.json
├── tsconfig.json
└── tsconfig.spec.json
/.all-contributorsrc:
--------------------------------------------------------------------------------
1 | {
2 | "projectName": "cmdk",
3 | "projectOwner": "@ngxpert",
4 | "repoType": "github",
5 | "repoHost": "https://github.com",
6 | "files": [
7 | "README.md"
8 | ],
9 | "imageSize": 100,
10 | "commit": true,
11 | "commitConvention": "angular",
12 | "contributors": [
13 | {
14 | "login": "pacocoursey",
15 | "name": "Paco",
16 | "avatar_url": "https://avatars.githubusercontent.com/u/34928425?v=4",
17 | "profile": "https://github.com/pacocoursey",
18 | "contributions": [
19 | "design",
20 | "doc",
21 | "ideas",
22 | "research"
23 | ]
24 | },
25 | {
26 | "login": "shhdharmen",
27 | "name": "Dharmen Shah",
28 | "avatar_url": "https://avatars.githubusercontent.com/u/6831283?v=4",
29 | "profile": "https://github.com/shhdharmen",
30 | "contributions": [
31 | "a11y",
32 | "code",
33 | "content",
34 | "design",
35 | "doc",
36 | "example",
37 | "ideas",
38 | "maintenance",
39 | "platform",
40 | "projectManagement",
41 | "research"
42 | ]
43 | },
44 | {
45 | "login": "NetanelBasal",
46 | "name": "Netanel Basal",
47 | "avatar_url": "https://avatars.githubusercontent.com/u/6745730?v=4",
48 | "profile": "https://www.netbasal.com/",
49 | "contributions": [
50 | "question",
51 | "business",
52 | "fundingFinding",
53 | "ideas",
54 | "maintenance",
55 | "mentoring",
56 | "projectManagement",
57 | "research",
58 | "review"
59 | ]
60 | }
61 | ],
62 | "contributorsPerLine": 7
63 | }
64 |
--------------------------------------------------------------------------------
/.editorconfig:
--------------------------------------------------------------------------------
1 | # Editor configuration, see https://editorconfig.org
2 | root = true
3 |
4 | [*]
5 | charset = utf-8
6 | indent_style = space
7 | indent_size = 2
8 | insert_final_newline = true
9 | trim_trailing_whitespace = true
10 |
11 | [*.ts]
12 | quote_type = single
13 |
14 | [*.md]
15 | max_line_length = off
16 | trim_trailing_whitespace = false
17 |
--------------------------------------------------------------------------------
/.eslintrc.json:
--------------------------------------------------------------------------------
1 | {
2 | "root": true,
3 | "ignorePatterns": [
4 | "projects/**/*"
5 | ],
6 | "overrides": [
7 | {
8 | "files": [
9 | "*.ts"
10 | ],
11 | "parserOptions": {
12 | "project": [
13 | "tsconfig.json"
14 | ],
15 | "createDefaultProgram": true
16 | },
17 | "extends": [
18 | "plugin:@angular-eslint/recommended",
19 | "plugin:@angular-eslint/template/process-inline-templates"
20 | ],
21 | "rules": {
22 | "@angular-eslint/directive-selector": [
23 | "error",
24 | {
25 | "type": "attribute",
26 | "prefix": "app",
27 | "style": "camelCase"
28 | }
29 | ],
30 | "@angular-eslint/component-selector": [
31 | "error",
32 | {
33 | "type": "element",
34 | "prefix": "app",
35 | "style": "kebab-case"
36 | }
37 | ]
38 | }
39 | },
40 | {
41 | "files": [
42 | "*.html"
43 | ],
44 | "extends": [
45 | "plugin:@angular-eslint/template/recommended"
46 | ],
47 | "rules": {}
48 | }
49 | ]
50 | }
51 |
--------------------------------------------------------------------------------
/.github/ISSUE_TEMPLATE.md:
--------------------------------------------------------------------------------
1 |
6 |
7 | ## I'm submitting a...
8 |
9 |
10 |
11 | [ ] Regression (a behavior that used to work and stopped working in a new release)
12 | [ ] Bug report
13 | [ ] Performance issue
14 | [ ] Feature request
15 | [ ] Documentation issue or request
16 | [ ] Support request
17 | [ ] Other... Please describe:
18 |
19 |
20 | ## Current behavior
21 |
22 |
23 |
24 | ## Expected behavior
25 |
26 |
27 |
28 | ## Minimal reproduction of the problem with instructions
29 |
30 | ## What is the motivation / use case for changing the behavior?
31 |
32 |
33 |
34 | ## Environment
35 |
36 |
37 | Angular version: X.Y.Z
38 |
39 |
40 | Browser:
41 | - [ ] Chrome (desktop) version XX
42 | - [ ] Chrome (Android) version XX
43 | - [ ] Chrome (iOS) version XX
44 | - [ ] Firefox version XX
45 | - [ ] Safari (desktop) version XX
46 | - [ ] Safari (iOS) version XX
47 | - [ ] IE version XX
48 | - [ ] Edge version XX
49 |
50 | For Tooling issues:
51 | - Node version: XX
52 | - Platform:
53 |
54 | Others:
55 |
56 |
57 |
--------------------------------------------------------------------------------
/.github/PULL_REQUEST_TEMPLATE.md:
--------------------------------------------------------------------------------
1 | ## PR Checklist
2 |
3 | Please check if your PR fulfills the following requirements:
4 |
5 | - [ ] The commit message follows our guidelines: CONTRIBUTING.md#commit
6 | - [ ] Tests for the changes have been added (for bug fixes / features)
7 | - [ ] Docs have been added / updated (for bug fixes / features)
8 |
9 | ## PR Type
10 |
11 | What kind of change does this PR introduce?
12 |
13 |
14 |
15 | ```
16 | [ ] Bugfix
17 | [ ] Feature
18 | [ ] Code style update (formatting, local variables)
19 | [ ] Refactoring (no functional changes, no api changes)
20 | [ ] Build related changes
21 | [ ] CI related changes
22 | [ ] Documentation content changes
23 | [ ] Other... Please describe:
24 | ```
25 |
26 | ## What is the current behavior?
27 |
28 |
29 |
30 | Issue Number: N/A
31 |
32 | ## What is the new behavior?
33 |
34 | ## Does this PR introduce a breaking change?
35 |
36 | ```
37 | [ ] Yes
38 | [ ] No
39 | ```
40 |
41 |
42 |
43 | ## Other information
44 |
--------------------------------------------------------------------------------
/.github/dependabot.yml:
--------------------------------------------------------------------------------
1 | # To get started with Dependabot version updates, you'll need to specify which
2 | # package ecosystems to update and where the package manifests are located.
3 | # Please see the documentation for all configuration options:
4 | # https://docs.github.com/code-security/dependabot/dependabot-version-updates/configuration-options-for-the-dependabot.yml-file
5 |
6 | version: 2
7 | updates:
8 | - package-ecosystem: "npm" # See documentation for possible values
9 | directories:
10 | - "/"
11 | - "/projects/ngxpert/cmdk"
12 | schedule:
13 | interval: "weekly"
14 |
--------------------------------------------------------------------------------
/.github/workflows/release.yaml:
--------------------------------------------------------------------------------
1 | name: Make a Release and Deploy on GitHub Pages
2 |
3 | on:
4 | push:
5 | branches:
6 | - main
7 |
8 | jobs:
9 | build:
10 | runs-on: ubuntu-latest
11 |
12 | steps:
13 | - uses: actions/checkout@v4
14 | - name: Use Node.js
15 | uses: actions/setup-node@v4
16 | with:
17 | node-version: "20.x"
18 |
19 | - name: Cypress run
20 | uses: cypress-io/github-action@v6
21 | with:
22 | component: true
23 |
24 | - name: Install dependencies
25 | env:
26 | HUSKY_SKIP_INSTALL: true
27 | run: npm ci
28 |
29 | - name: NPM install, build and deploy
30 | env:
31 | GH_TOKEN: ${{ secrets.GH_TOKEN }}
32 | NPM_TOKEN: ${{ secrets.NPM_TOKEN }}
33 | run: |
34 | npm run build
35 | npm run build:lib
36 | npx semantic-release
37 | npx angular-cli-ghpages --name="mr. Dharmen's Bot" --email=shhdharmen@gmail.com --dir=dist/cmdk/browser
38 |
--------------------------------------------------------------------------------
/.github/workflows/test.yaml:
--------------------------------------------------------------------------------
1 | name: Test, Build and Publish a beta version
2 |
3 | on:
4 | push:
5 | branches-ignore:
6 | - main
7 | pull_request:
8 | branches:
9 | - main
10 |
11 | jobs:
12 | build:
13 | runs-on: ubuntu-latest
14 |
15 | steps:
16 | - uses: actions/checkout@v4
17 | - name: Use Node.js
18 | uses: actions/setup-node@v4
19 | with:
20 | node-version: "20.x"
21 |
22 | - name: Install dependencies
23 | env:
24 | HUSKY_SKIP_INSTALL: true
25 | run: npm ci
26 |
27 | - name: Cypress run
28 | uses: cypress-io/github-action@v6
29 | with:
30 | component: true
31 |
32 | - name: Lint, build and release
33 | env:
34 | GH_TOKEN: ${{ secrets.GH_TOKEN }}
35 | NPM_TOKEN: ${{ secrets.NPM_TOKEN }}
36 | run: |
37 | npm run lint
38 | npm run build:lib
39 | npx semantic-release --debug
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # See http://help.github.com/ignore-files/ for more about ignoring files.
2 |
3 | # Compiled output
4 | /dist
5 | /tmp
6 | /out-tsc
7 | /bazel-out
8 |
9 | # Node
10 | /node_modules
11 | npm-debug.log
12 | yarn-error.log
13 |
14 | # IDEs and editors
15 | .idea/
16 | .project
17 | .classpath
18 | .c9/
19 | *.launch
20 | .settings/
21 | *.sublime-workspace
22 |
23 | # Visual Studio Code
24 | .vscode/*
25 | !.vscode/settings.json
26 | !.vscode/tasks.json
27 | !.vscode/launch.json
28 | !.vscode/extensions.json
29 | .history/*
30 |
31 | # Miscellaneous
32 | /.angular/cache
33 | .sass-cache/
34 | /connect.lock
35 | /coverage
36 | /libpeerconnection.log
37 | testem.log
38 | /typings
39 |
40 | # System files
41 | .DS_Store
42 | Thumbs.db
43 |
44 | .nx
--------------------------------------------------------------------------------
/.husky/commit-msg:
--------------------------------------------------------------------------------
1 | #!/bin/sh
2 | . "$(dirname "$0")/_/husky.sh"
3 |
4 | npx --no -- commitlint --edit "${1}"
5 |
--------------------------------------------------------------------------------
/.prettierrc.json:
--------------------------------------------------------------------------------
1 | {}
--------------------------------------------------------------------------------
/.releaserc.json:
--------------------------------------------------------------------------------
1 | {
2 | "branches": [
3 | "+([0-9])?(.{+([0-9]),x}).x",
4 | "main",
5 | "next",
6 | "next-major",
7 | { "name": "beta", "prerelease": true },
8 | { "name": "development", "prerelease": true },
9 | { "name": "alpha", "prerelease": true }
10 | ],
11 | "preset": "angular",
12 | "plugins": [
13 | "@semantic-release/commit-analyzer",
14 | "@semantic-release/release-notes-generator",
15 | "@semantic-release/changelog",
16 | [
17 | "@semantic-release/npm",
18 | {
19 | "pkgRoot": "./dist/ngxpert/cmdk",
20 | "tarballDir": "dist"
21 | }
22 | ],
23 | [
24 | "@semantic-release/github",
25 | {
26 | "assets": ["dist/*.tgz"]
27 | }
28 | ],
29 | "@semantic-release/git"
30 | ]
31 | }
32 |
--------------------------------------------------------------------------------
/.vscode/extensions.json:
--------------------------------------------------------------------------------
1 | {
2 | // For more information, visit: https://go.microsoft.com/fwlink/?linkid=827846
3 | "recommendations": ["angular.ng-template"]
4 | }
5 |
--------------------------------------------------------------------------------
/.vscode/launch.json:
--------------------------------------------------------------------------------
1 | {
2 | // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
3 | "version": "0.2.0",
4 | "configurations": [
5 | {
6 | "name": "ng serve",
7 | "type": "pwa-chrome",
8 | "request": "launch",
9 | "preLaunchTask": "npm: start",
10 | "url": "http://localhost:4200/"
11 | },
12 | {
13 | "name": "ng test",
14 | "type": "chrome",
15 | "request": "launch",
16 | "preLaunchTask": "npm: test",
17 | "url": "http://localhost:9876/debug.html"
18 | }
19 | ]
20 | }
21 |
--------------------------------------------------------------------------------
/.vscode/tasks.json:
--------------------------------------------------------------------------------
1 | {
2 | // For more information, visit: https://go.microsoft.com/fwlink/?LinkId=733558
3 | "version": "2.0.0",
4 | "tasks": [
5 | {
6 | "type": "npm",
7 | "script": "start",
8 | "isBackground": true,
9 | "problemMatcher": {
10 | "owner": "typescript",
11 | "pattern": "$tsc",
12 | "background": {
13 | "activeOnStart": true,
14 | "beginsPattern": {
15 | "regexp": "(.*?)"
16 | },
17 | "endsPattern": {
18 | "regexp": "bundle generation complete"
19 | }
20 | }
21 | }
22 | },
23 | {
24 | "type": "npm",
25 | "script": "test",
26 | "isBackground": true,
27 | "problemMatcher": {
28 | "owner": "typescript",
29 | "pattern": "$tsc",
30 | "background": {
31 | "activeOnStart": true,
32 | "beginsPattern": {
33 | "regexp": "(.*?)"
34 | },
35 | "endsPattern": {
36 | "regexp": "bundle generation complete"
37 | }
38 | }
39 | }
40 | }
41 | ]
42 | }
43 |
--------------------------------------------------------------------------------
/CHANGELOG.md:
--------------------------------------------------------------------------------
1 | # [3.0.0](https://github.com/ngxpert/cmdk/compare/v2.0.0...v3.0.0) (2024-06-23)
2 |
3 |
4 | ### Features
5 |
6 | * ng update @angular/cdk ([855bd8a](https://github.com/ngxpert/cmdk/commit/855bd8a858d8ab0519ca934b8368f6d681e16e57))
7 | * ng update @angular/cli ([7c0b271](https://github.com/ngxpert/cmdk/commit/7c0b2717e28d95fac1c554ca5a769dd3e9e954df))
8 |
9 |
10 | ### BREAKING CHANGES
11 |
12 | * Updating angular cdk to 18
13 | * Adding support for Angular version 18
14 |
15 | # [2.0.0](https://github.com/ngxpert/cmdk/compare/v1.0.0...v2.0.0) (2024-04-21)
16 |
17 |
18 | ### chore
19 |
20 | * add support for angular v17 ([2b2422c](https://github.com/ngxpert/cmdk/commit/2b2422c3e06f095db9fe189d41ff1152a57709e7))
21 | * update ngneat overview to v6 ([614bd0c](https://github.com/ngxpert/cmdk/commit/614bd0cf5cc929eae76204e9f4bc22fdfd4b0f85))
22 |
23 |
24 | ### Features
25 |
26 | * ng update changes, ngneat overview changes ([cfd241f](https://github.com/ngxpert/cmdk/commit/cfd241f6a3091dfbfff0e8c1e8061b5e5bed232c))
27 |
28 |
29 | ### BREAKING CHANGES
30 |
31 | * update ngneat overview to v6
32 | * migrated to angular v17
33 |
34 | # 1.0.0 (2024-02-29)
35 |
36 |
37 | ### Bug Fixes
38 |
39 | * **command.component:** re-listen to keymanager changes when items change ([f89136a](https://github.com/ngxpert/cmdk/commit/f89136a1206cd6741e4ef6c8945a0cf0735eb4c5)), closes [#4](https://github.com/ngxpert/cmdk/issues/4)
40 | * fix ng-add schematics for standalone app ([036e9ac](https://github.com/ngxpert/cmdk/commit/036e9ac0f157759a57370bc98f43972e8ec49364))
41 |
42 |
43 | ### Documentation
44 |
45 | * add versions ([ab16198](https://github.com/ngxpert/cmdk/commit/ab16198b6a49dca48aaff27df93aae5d8eeb56c6)), closes [#7](https://github.com/ngxpert/cmdk/issues/7)
46 |
47 |
48 | ### Features
49 |
50 | * access content children ([1c29f3e](https://github.com/ngxpert/cmdk/commit/1c29f3e1fbb6a20bab0bd185572dd33ef40b87ce))
51 | * add classes in host for group, list and separator ([393fc34](https://github.com/ngxpert/cmdk/commit/393fc34af6a000850460cd928031e4fd75f6621f))
52 | * add loader ([21abc42](https://github.com/ngxpert/cmdk/commit/21abc42dd42f1d8eaa07fef086d03c739196089c))
53 | * add loop in cmdk-list and improve docs ([1b8c977](https://github.com/ngxpert/cmdk/commit/1b8c977b8f3c7bb1d26b66520dad21e1077980e4))
54 | * change item component to directive ([e6b26ca](https://github.com/ngxpert/cmdk/commit/e6b26ca66a96d40b20ba82f5c130d96cdcfbcf27))
55 | * directives behavior and options configuration ([a167295](https://github.com/ngxpert/cmdk/commit/a167295dcc19561a6b6f048a98279877bc1ad28d))
56 | * examples and remove usage of ng-template ([6c84743](https://github.com/ngxpert/cmdk/commit/6c84743bf7931ea4cdc171ac370c4b62365d0347))
57 | * first commit ([78d4843](https://github.com/ngxpert/cmdk/commit/78d48431e3bed79fa1acbd917dcd29362641bc3c))
58 | * group, list and item components ([9de269e](https://github.com/ngxpert/cmdk/commit/9de269eee414ffafaa2185558cc68eaa1977afa1))
59 | * handle display through lib css ([10b59c3](https://github.com/ngxpert/cmdk/commit/10b59c3b34756acba7afdcf43188891f2f773334))
60 | * handle dynamic value and item changes ([050badc](https://github.com/ngxpert/cmdk/commit/050badc58dd1ba4a6dbf0cfa72cdaafbdb46023b))
61 | * handle group and item renderring dynamically based on search ([5fab515](https://github.com/ngxpert/cmdk/commit/5fab51582d7223e256e463c7ab464c4a29dbc6e9))
62 | * handle majority of functinalities from commans component ([8df63da](https://github.com/ngxpert/cmdk/commit/8df63dab616eb6c5b09acefcbf696055379154b8))
63 | * initial files ([803a759](https://github.com/ngxpert/cmdk/commit/803a75902a757ca478ac6dde7e71d0b50f44bcf5))
64 | * lib dependencies, disabled items ([1030b05](https://github.com/ngxpert/cmdk/commit/1030b0593c89a4f9a8d1ff78b4348952f379225f))
65 | * remove styles ([7958784](https://github.com/ngxpert/cmdk/commit/79587847d4f323a110f6093e3c795ee29b80e190))
66 | * repo change ([9f499c0](https://github.com/ngxpert/cmdk/commit/9f499c0c54c8e9e1ee88fa60afdbee1f8740a9e4))
67 | * scroll active item into view ([39ccf37](https://github.com/ngxpert/cmdk/commit/39ccf375300f1adb1791efca81ec41526073682c))
68 | * use angular/cdk key manager ([4b97449](https://github.com/ngxpert/cmdk/commit/4b97449e181746f6d9de64719e734be6965de199))
69 | * use ng-template with @ngneat/overview ([beec82e](https://github.com/ngxpert/cmdk/commit/beec82e9c4d753b9fd8c72d14ffb18ebb23feea3))
70 |
71 |
72 | ### BREAKING CHANGES
73 |
74 | * Supports Angular 16
75 |
--------------------------------------------------------------------------------
/CODE_OF_CONDUCT.md:
--------------------------------------------------------------------------------
1 | # Contributor Covenant Code of Conduct
2 |
3 | ## Our Pledge
4 |
5 | In the interest of fostering an open and welcoming environment, we as contributors and maintainers pledge to making participation in our project and our community a harassment-free experience for everyone, regardless of age, body size, disability, ethnicity, gender identity and expression, level of experience, nationality, personal appearance, race, religion, or sexual identity and orientation.
6 |
7 | ## Our Standards
8 |
9 | Examples of behavior that contributes to creating a positive environment include:
10 |
11 | * Using welcoming and inclusive language
12 | * Being respectful of differing viewpoints and experiences
13 | * Gracefully accepting constructive criticism
14 | * Focusing on what is best for the community
15 | * Showing empathy towards other community members
16 |
17 | Examples of unacceptable behavior by participants include:
18 |
19 | * The use of sexualized language or imagery and unwelcome sexual attention or advances
20 | * Trolling, insulting/derogatory comments, and personal or political attacks
21 | * Public or private harassment
22 | * Publishing others' private information, such as a physical or electronic address, without explicit permission
23 | * Other conduct which could reasonably be considered inappropriate in a professional setting
24 |
25 | ## Our Responsibilities
26 |
27 | Project maintainers are responsible for clarifying the standards of acceptable behavior and are expected to take appropriate and fair corrective action in response to any instances of unacceptable behavior.
28 |
29 | Project maintainers have the right and responsibility to remove, edit, or reject comments, commits, code, wiki edits, issues, and other contributions that are not aligned to this Code of Conduct, or to ban temporarily or permanently any contributor for other behaviors that they deem inappropriate, threatening, offensive, or harmful.
30 |
31 | ## Scope
32 |
33 | This Code of Conduct applies both within project spaces and in public spaces when an individual is representing the project or its community. Examples of representing a project or community include using an official project e-mail address, posting via an official social media account, or acting as an appointed representative at an online or offline event. Representation of a project may be further defined and clarified by project maintainers.
34 |
35 | ## Enforcement
36 |
37 | Instances of abusive, harassing, or otherwise unacceptable behavior may be reported by contacting the project team at shhdharmen@gmail.com. The project team will review and investigate all complaints, and will respond in a way that it deems appropriate to the circumstances. The project team is obligated to maintain confidentiality with regard to the reporter of an incident. Further details of specific enforcement policies may be posted separately.
38 |
39 | Project maintainers who do not follow or enforce the Code of Conduct in good faith may face temporary or permanent repercussions as determined by other members of the project's leadership.
40 |
41 | ## Attribution
42 |
43 | This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4, available at [http://contributor-covenant.org/version/1/4][version]
44 |
45 | [homepage]: http://contributor-covenant.org
46 | [version]: http://contributor-covenant.org/version/1/4/
47 |
--------------------------------------------------------------------------------
/CONTRIBUTING.md:
--------------------------------------------------------------------------------
1 | # Contributing to CMDK
2 |
3 | 🙏 We would ❤️ for you to contribute to cmdk and help make it even better than it is today!
4 |
5 | ## Developing
6 |
7 | Start by installing all dependencies:
8 |
9 | ```bash
10 | npm i
11 | ```
12 |
13 | Run the playground app:
14 |
15 | ```bash
16 | npm start
17 | ```
18 |
19 | ## Testing
20 |
21 | ### Run cypress tests
22 |
23 | ```bash
24 | npm start
25 | npm run unit
26 | ```
27 |
28 | Cypress window will open, you can click on individual tests.
29 |
30 | ## Building
31 |
32 | ```bash
33 | npm run build:lib
34 | ```
35 |
36 | ## Coding Rules
37 |
38 | To ensure consistency throughout the source code, keep these rules in mind as you are working:
39 |
40 | - All features or bug fixes **must be tested** by one or more specs (unit-tests).
41 | - All public API methods **must be documented**.
42 |
43 | ## Commit Message Guidelines
44 |
45 | ### TL;DR
46 |
47 | Simply run commit script after staging your files to take care about commit message guidelines
48 |
49 | ```bash
50 | npm run commit
51 | ```
52 |
53 | ### Details
54 |
55 | We have very precise rules over how our git commit messages can be formatted. This leads to **more
56 | readable messages** that are easy to follow when looking through the **project history**. But also,
57 | we use the git commit messages to **generate the cmdk changelog**.
58 |
59 | #### Commit Message Format
60 |
61 | Each commit message consists of a **header**, a **body** and a **footer**. The header has a special
62 | format that includes a **type**, a **scope** and a **subject**:
63 |
64 | ```
65 | ():
66 |
67 |
68 |
69 |
70 | ```
71 |
72 | The **header** is mandatory and the **scope** of the header is optional.
73 |
74 | Any line of the commit message cannot be longer 100 characters! This allows the message to be easier
75 | to read on GitHub as well as in various git tools.
76 |
77 | The footer should contain a [closing reference to an issue](https://help.github.com/articles/closing-issues-via-commit-messages/) if any.
78 |
79 | Samples: (even more [samples](https://github.com/angular/angular/commits/master))
80 |
81 | ```
82 | docs(changelog): update changelog to beta.5
83 | ```
84 |
85 | ```
86 | fix(release): need to depend on latest rxjs and zone.js
87 |
88 | The version in our package.json gets copied to the one we publish, and users need the latest of these.
89 | ```
90 |
--------------------------------------------------------------------------------
/ISSUE_TEMPLATE.md:
--------------------------------------------------------------------------------
1 |
5 |
6 | ## I'm submitting a...
7 |
8 |
9 |
10 | [ ] Regression (a behavior that used to work and stopped working in a new release)
11 | [ ] Bug report
12 | [ ] Performance issue
13 | [ ] Feature request
14 | [ ] Documentation issue or request
15 | [ ] Support request
16 | [ ] Other... Please describe:
17 |
18 |
19 | ## Current behavior
20 |
21 |
22 |
23 | ## Expected behavior
24 |
25 |
26 |
27 | ## Minimal reproduction of the problem with instructions
28 |
29 | ## What is the motivation / use case for changing the behavior?
30 |
31 |
32 |
33 | ## Environment
34 |
35 |
36 | Angular version: X.Y.Z
37 |
38 |
39 | Browser:
40 | - [ ] Chrome (desktop) version XX
41 | - [ ] Chrome (Android) version XX
42 | - [ ] Chrome (iOS) version XX
43 | - [ ] Firefox version XX
44 | - [ ] Safari (desktop) version XX
45 | - [ ] Safari (iOS) version XX
46 | - [ ] IE version XX
47 | - [ ] Edge version XX
48 |
49 | For Tooling issues:
50 | - Node version: XX
51 | - Platform:
52 |
53 | Others:
54 |
55 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | The MIT License
2 |
3 | Permission is hereby granted, free of charge, to any person obtaining a copy
4 | of this software and associated documentation files (the "Software"), to deal
5 | in the Software without restriction, including without limitation the rights
6 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
7 | copies of the Software, and to permit persons to whom the Software is
8 | furnished to do so, subject to the following conditions:
9 |
10 | The above copyright notice and this permission notice shall be included in
11 | all copies or substantial portions of the Software.
12 |
13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
15 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
16 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
17 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
18 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
19 | THE SOFTWARE.
20 |
21 |
--------------------------------------------------------------------------------
/PULL_REQUEST_TEMPLATE.md:
--------------------------------------------------------------------------------
1 | ## PR Checklist
2 |
3 | Please check if your PR fulfills the following requirements:
4 |
5 | - [ ] The commit message follows our guidelines: CONTRIBUTING.md#commit
6 | - [ ] Tests for the changes have been added (for bug fixes / features)
7 | - [ ] Docs have been added / updated (for bug fixes / features)
8 |
9 | ## PR Type
10 |
11 | What kind of change does this PR introduce?
12 |
13 |
14 |
15 | ```
16 | [ ] Bugfix
17 | [ ] Feature
18 | [ ] Code style update (formatting, local variables)
19 | [ ] Refactoring (no functional changes, no api changes)
20 | [ ] Build related changes
21 | [ ] CI related changes
22 | [ ] Documentation content changes
23 | [ ] Other... Please describe:
24 | ```
25 |
26 | ## What is the current behavior?
27 |
28 |
29 |
30 | Issue Number: N/A
31 |
32 | ## What is the new behavior?
33 |
34 | ## Does this PR introduce a breaking change?
35 |
36 | ```
37 | [ ] Yes
38 | [ ] No
39 | ```
40 |
41 |
42 |
43 | ## Other information
--------------------------------------------------------------------------------
/angular.json:
--------------------------------------------------------------------------------
1 | {
2 | "$schema": "./node_modules/@angular/cli/lib/config/schema.json",
3 | "version": 1,
4 | "newProjectRoot": "projects",
5 | "projects": {
6 | "cmdk": {
7 | "projectType": "application",
8 | "schematics": {
9 | "@schematics/angular:component": {
10 | "style": "scss"
11 | }
12 | },
13 | "root": "",
14 | "sourceRoot": "src",
15 | "prefix": "app",
16 | "architect": {
17 | "build": {
18 | "builder": "@angular-devkit/build-angular:application",
19 | "options": {
20 | "outputPath": {
21 | "base": "dist/cmdk"
22 | },
23 | "index": "src/index.html",
24 | "polyfills": [
25 | "src/polyfills.ts"
26 | ],
27 | "tsConfig": "tsconfig.app.json",
28 | "inlineStyleLanguage": "scss",
29 | "assets": ["src/favicon.ico", "src/assets"],
30 | "styles": [
31 | "src/styles.scss",
32 | "node_modules/prismjs/themes/prism-okaidia.min.css"
33 | ],
34 | "scripts": [],
35 | "browser": "src/main.ts"
36 | },
37 | "configurations": {
38 | "production": {
39 | "budgets": [
40 | {
41 | "type": "initial",
42 | "maximumWarning": "500kb",
43 | "maximumError": "1mb"
44 | },
45 | {
46 | "type": "anyComponentStyle",
47 | "maximumWarning": "2kb",
48 | "maximumError": "4kb"
49 | }
50 | ],
51 | "fileReplacements": [
52 | {
53 | "replace": "src/environments/environment.ts",
54 | "with": "src/environments/environment.prod.ts"
55 | }
56 | ],
57 | "outputHashing": "all"
58 | },
59 | "development": {
60 | "optimization": false,
61 | "extractLicenses": false,
62 | "sourceMap": true,
63 | "namedChunks": true
64 | }
65 | },
66 | "defaultConfiguration": "production"
67 | },
68 | "serve": {
69 | "builder": "@angular-devkit/build-angular:dev-server",
70 | "configurations": {
71 | "production": {
72 | "buildTarget": "cmdk:build:production"
73 | },
74 | "development": {
75 | "buildTarget": "cmdk:build:development"
76 | }
77 | },
78 | "defaultConfiguration": "development"
79 | },
80 | "extract-i18n": {
81 | "builder": "@angular-devkit/build-angular:extract-i18n",
82 | "options": {
83 | "buildTarget": "cmdk:build"
84 | }
85 | },
86 | "test": {
87 | "builder": "@angular-devkit/build-angular:karma",
88 | "options": {
89 | "main": "src/test.ts",
90 | "polyfills": "src/polyfills.ts",
91 | "tsConfig": "tsconfig.spec.json",
92 | "karmaConfig": "karma.conf.js",
93 | "inlineStyleLanguage": "scss",
94 | "assets": ["src/favicon.ico", "src/assets"],
95 | "styles": [
96 | "src/styles.scss",
97 | "node_modules/prismjs/themes/prism-okaidia.min.css"
98 | ],
99 | "scripts": []
100 | }
101 | },
102 | "deploy": {
103 | "builder": "angular-cli-ghpages:deploy"
104 | }
105 | }
106 | },
107 | "@ngxpert/cmdk": {
108 | "projectType": "library",
109 | "schematics": {
110 | "@schematics/angular:component": {
111 | "style": "scss"
112 | }
113 | },
114 | "root": "projects/ngxpert/cmdk",
115 | "sourceRoot": "projects/ngxpert/cmdk/src",
116 | "prefix": "cmdk",
117 | "architect": {
118 | "build": {
119 | "builder": "@angular-devkit/build-angular:ng-packagr",
120 | "options": {
121 | "project": "projects/ngxpert/cmdk/ng-package.json"
122 | },
123 | "configurations": {
124 | "production": {
125 | "tsConfig": "projects/ngxpert/cmdk/tsconfig.lib.prod.json"
126 | },
127 | "development": {
128 | "tsConfig": "projects/ngxpert/cmdk/tsconfig.lib.json"
129 | }
130 | },
131 | "defaultConfiguration": "production"
132 | },
133 | "test": {
134 | "builder": "@angular-devkit/build-angular:karma",
135 | "options": {
136 | "main": "projects/ngxpert/cmdk/src/test.ts",
137 | "tsConfig": "projects/ngxpert/cmdk/tsconfig.spec.json",
138 | "karmaConfig": "projects/ngxpert/cmdk/karma.conf.js"
139 | }
140 | },
141 | "lint": {
142 | "builder": "@angular-eslint/builder:lint",
143 | "options": {
144 | "lintFilePatterns": [
145 | "projects/ngxpert/cmdk/**/*.ts",
146 | "projects/ngxpert/cmdk/**/*.html"
147 | ]
148 | }
149 | }
150 | }
151 | }
152 | },
153 | "cli": {
154 | "analytics": false
155 | },
156 | "schematics": {
157 | "@angular-eslint/schematics:application": {
158 | "setParserOptionsProject": true
159 | },
160 | "@angular-eslint/schematics:library": {
161 | "setParserOptionsProject": true
162 | }
163 | }
164 | }
165 |
--------------------------------------------------------------------------------
/assets/ngxpert cmdk cover.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ngxpert/cmdk/3fc6fb1cf1c19911a475ed9d9b9321b5ba2599a3/assets/ngxpert cmdk cover.png
--------------------------------------------------------------------------------
/commitlint.config.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | extends: ['@commitlint/config-conventional']
3 | };
4 |
--------------------------------------------------------------------------------
/cypress.config.ts:
--------------------------------------------------------------------------------
1 | import { defineConfig } from 'cypress';
2 |
3 | export default defineConfig({
4 | component: {
5 | devServer: {
6 | framework: 'angular',
7 | bundler: 'webpack',
8 | options: {
9 | projectConfig: {
10 | root: './',
11 | sourceRoot: 'src',
12 | buildOptions: {
13 | outputPath: 'dist/browser',
14 | },
15 | },
16 | },
17 | },
18 | specPattern: '**/*.cy.ts',
19 | },
20 | });
21 |
--------------------------------------------------------------------------------
/cypress/basic.cy.ts:
--------------------------------------------------------------------------------
1 | import { CmdkModule } from '@ngxpert/cmdk';
2 | import { createOutputSpy } from 'cypress/angular';
3 |
4 | describe('basic behavior', () => {
5 | beforeEach(() => {
6 | cy.mount(
7 | `
8 |
9 |
10 | No results.
11 |
12 | Item
13 |
14 |
15 | Value
16 |
17 |
18 | `,
19 | {
20 | imports: [CmdkModule.forRoot()],
21 | componentProperties: {
22 | onSelected: createOutputSpy('onSelectedSpy'),
23 | },
24 | }
25 | );
26 | });
27 |
28 | it('item value is derived from textContent', () => {
29 | const item = cy.get(`[cmdkitem][data-value="item"]`);
30 | item.should('contain.text', 'Item');
31 | });
32 |
33 | it('item value prop is preferred over textContent', () => {
34 | const item = cy.get(`[cmdkitem][data-value="xxx"]`);
35 | item.should('contain.text', 'Value');
36 | });
37 |
38 | it('item selected is called on click', () => {
39 | const item = cy.get(`[cmdkitem][data-value="item"]`);
40 | item.click();
41 | cy.get('@onSelectedSpy').should('have.been.called');
42 | });
43 |
44 | it('first item is selected by default', () => {
45 | const item = cy.get(`[cmdkitem][aria-selected]`);
46 | item.should('contain.text', 'Item');
47 | });
48 |
49 | it('first item is selected when search changes', () => {
50 | const input = cy.get(`[cmdkinput]`);
51 | input.type('x');
52 | const selected = cy.get(`[cmdkitem][aria-selected]`);
53 | selected.should('contain.text', 'Value');
54 | });
55 |
56 | it('items filter when searching', () => {
57 | const input = cy.get(`[cmdkinput]`);
58 | input.type('x');
59 | const removed = cy.get('[cmdk-hidden="true"]');
60 | removed.should('contain.text', 'Item');
61 | const remains = cy.get('.cmdk-item-filtered');
62 | remains.should('contain.text', 'Value');
63 | });
64 |
65 | it('empty component renders when there are no results', () => {
66 | const input = cy.get('[cmdkinput]');
67 | input.type('z');
68 | const filteredItems = cy.get('.cmdk-item-filtered');
69 | filteredItems.should('have.length', 0);
70 | cy.get(`.cmdk-empty`).should('have.length', 1);
71 | });
72 |
73 | it('classes is applied to each part', () => {
74 | cy.get(`.cmdk-command`).should('have.length', 1);
75 | cy.get(`.cmdk-input`).should('have.length', 1);
76 | cy.get(`.cmdk-list`).should('have.length', 1);
77 | cy.get(`.cmdk-item`).should('have.length', 2);
78 | cy.get('[cmdkinput]').type('zzzz');
79 | cy.get(`.cmdk-item-filtered`).should('have.length', 0);
80 | cy.get(`.cmdk-empty`).should('have.length', 1);
81 | });
82 | });
83 |
--------------------------------------------------------------------------------
/cypress/fixtures/example.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "Using fixtures to represent data",
3 | "email": "hello@cypress.io",
4 | "body": "Fixtures are a great way to mock data for responses to routes"
5 | }
6 |
--------------------------------------------------------------------------------
/cypress/group.cy.ts:
--------------------------------------------------------------------------------
1 | import { TestsModule } from 'src/app/tests/tests.module';
2 |
3 | describe('group', async () => {
4 | beforeEach(() => {
5 | cy.mount(` `, { imports: [TestsModule] });
6 | });
7 |
8 | it('groups are shown/hidden based on item matches', () => {
9 | cy.get(`[cmdkinput]`).type('z');
10 | cy.get(`cmdk-group[data-value="animals"]`).should('not.be.visible');
11 | cy.get(`cmdk-group[data-value="letters"]`).should('be.visible');
12 | });
13 |
14 | it('group can be progressively rendered', async () => {
15 | cy.get(`cmdk-group[data-value="numbers"]`).should('not.be.visible');
16 | cy.get(`[cmdk-input]`).type('t');
17 | cy.get(`cmdk-group[data-value="animals"]`).should('not.be.visible');
18 | cy.get(`cmdk-group[data-value="letters"]`).should('not.be.visible');
19 | cy.get(`cmdk-group[data-value="numbers"]`).should('be.visible');
20 | });
21 | });
22 |
--------------------------------------------------------------------------------
/cypress/item.cy.ts:
--------------------------------------------------------------------------------
1 | import { TestsModule } from 'src/app/tests/tests.module';
2 |
3 | describe('item', () => {
4 | beforeEach(() => {
5 | cy.mount(` `, { imports: [TestsModule] });
6 | });
7 |
8 | it('mounted item matches search', () => {
9 | cy.get(`[cmdkinput]`).type('b');
10 | cy.get(`[cmdkitem]`).should('not.be.visible');
11 | cy.get(`[data-testid="mount"]`).click();
12 | cy.get(`[cmdkitem]`).should('contain.text', 'B');
13 | });
14 |
15 | it('mounted item does not match search', () => {
16 | cy.get(`[cmdkinput]`).type('z');
17 | cy.get(`[cmdkitem]`).should('not.be.visible');
18 | cy.get(`[data-testid="mount"]`).click();
19 | cy.get(`[cmdkitem]`).should('not.be.visible');
20 | });
21 |
22 | it('unmount item that is selected', () => {
23 | cy.get(`[data-testid="mount"]`).click();
24 | cy.get(`[cmdkitem][aria-selected="true"]`).should('contain.text', 'A');
25 | cy.get(`[data-testid="unmount"]`).click();
26 | cy.get(`[cmdkitem]`).should('have.length', 1);
27 | cy.get(`[cmdkitem][aria-selected="true"]`).should('contain.text', 'B');
28 | });
29 |
30 | it('unmount item that is the only result', () => {
31 | cy.get(`[data-testid="unmount"]`).click();
32 | cy.get(`[cmdkitem]`).should('have.length', 0);
33 | });
34 |
35 | it('mount item that is the only result', () => {
36 | cy.get(`[data-testid="unmount"]`).click();
37 | cy.get(`.cmdk-empty`).should('have.length', 1);
38 | cy.get(`[data-testid="mount"]`).click();
39 | cy.get(`.cmdk-empty`).should('have.length', 0);
40 | cy.get(`[cmdkitem]`).should('have.length', 1);
41 | });
42 |
43 | it('selected does not change when mounting new items', () => {
44 | cy.get(`[data-testid="mount"]`).click();
45 | cy.get(`[cmdkitem][data-value="b"]`).click();
46 | cy.get(`[cmdkitem][aria-selected="true"]`).should('contain.text', 'B');
47 | cy.get(`[data-testid="many"]`).click();
48 | cy.get(`[cmdkitem][aria-selected="true"]`).should('contain.text', 'B');
49 | });
50 | });
51 |
52 | describe('item advanced', () => {
53 | beforeEach(() => {
54 | cy.mount(' ', {
55 | imports: [TestsModule],
56 | });
57 | });
58 |
59 | it('re-rendering re-matches implicit textContent value', () => {
60 | cy.get(`[cmdkitem]`).should('have.length', 2);
61 | cy.get(`[cmdkinput]`).type('2');
62 | const button = cy.get(`[data-testid="increment"]`);
63 | button.click();
64 | cy.get(`[cmdkitem]`).should('not.be.visible');
65 | button.click();
66 | cy.get(`[cmdkitem]`).should('have.length', 2);
67 | });
68 | });
69 |
--------------------------------------------------------------------------------
/cypress/keybindings.cy.ts:
--------------------------------------------------------------------------------
1 | import { TestsModule } from 'src/app/tests/tests.module';
2 |
3 | describe('arrow keybinds', async () => {
4 | beforeEach(() => {
5 | cy.mount(' ', { imports: [TestsModule] });
6 | });
7 |
8 | test('arrow up/down changes selected item', () => {
9 | cy.get(`[cmdkitem][aria-selected="true"]`).should(
10 | 'have.data',
11 | 'value',
12 | 'first'
13 | );
14 | cy.get(`[cmdkinput]`).type('{downArrow}');
15 | cy.get(`[cmdkitem][aria-selected="true"]`).should(
16 | 'have.data',
17 | 'value',
18 | 'a'
19 | );
20 | cy.get(`[cmdkinput]`).type('{upArrow}');
21 | cy.get(`[cmdkitem][aria-selected="true"]`).should(
22 | 'have.data',
23 | 'value',
24 | 'first'
25 | );
26 | });
27 |
28 | test('page up/down goes to first and last item', () => {
29 | cy.get(`[cmdkitem][aria-selected="true"]`).should(
30 | 'have.data',
31 | 'value',
32 | 'first'
33 | );
34 | cy.get(`[cmdkinput]`).type('{pageDown}');
35 | cy.get(`[cmdkitem][aria-selected="true"]`).should(
36 | 'have.data',
37 | 'value',
38 | 'last'
39 | );
40 | cy.get(`[cmdkinput]`).type('{pageUp}');
41 | cy.get(`[cmdkitem][aria-selected="true"]`).should(
42 | 'have.data',
43 | 'value',
44 | 'first'
45 | );
46 | });
47 | });
48 |
--------------------------------------------------------------------------------
/cypress/props.cy.ts:
--------------------------------------------------------------------------------
1 | import { TestsModule } from 'src/app/tests/tests.module';
2 |
3 | describe('props', () => {
4 | it('results match against custom filter', async () => {
5 | cy.mount(" ", {
6 | imports: [TestsModule],
7 | });
8 | cy.get(`[cmdkinput]`).type(`ant`);
9 | cy.get(`[cmdkitem]`).should('have.data', 'value', 'ant');
10 | });
11 |
12 | it('controlled value', () => {
13 | cy.mount(' ', {
14 | imports: [TestsModule],
15 | });
16 | cy.get(`[cmdkitem][aria-selected="true"]`).should(
17 | 'have.data',
18 | 'value',
19 | 'ant'
20 | );
21 | cy.get(`[data-testid="controlledValue"]`).click();
22 | cy.get(`[cmdkitem][aria-selected="true"]`).should(
23 | 'have.data',
24 | 'value',
25 | 'anteater'
26 | );
27 | });
28 |
29 | it('controlled search', () => {
30 | cy.mount(' ', {
31 | imports: [TestsModule],
32 | });
33 | cy.get(`[cmdkitem][aria-selected="true"]`).should(
34 | 'have.data',
35 | 'value',
36 | 'ant'
37 | );
38 | cy.get(`[data-testid="controlledSearch"]`).click();
39 | cy.get(`[cmdkitem][aria-selected="true"]`).should(
40 | 'have.data',
41 | 'value',
42 | 'anteater'
43 | );
44 | });
45 | });
46 |
--------------------------------------------------------------------------------
/cypress/support/commands.ts:
--------------------------------------------------------------------------------
1 | ///
2 | // ***********************************************
3 | // This example commands.ts shows you how to
4 | // create various custom commands and overwrite
5 | // existing commands.
6 | //
7 | // For more comprehensive examples of custom
8 | // commands please read more here:
9 | // https://on.cypress.io/custom-commands
10 | // ***********************************************
11 | //
12 | //
13 | // -- This is a parent command --
14 | // Cypress.Commands.add('login', (email, password) => { ... })
15 | //
16 | //
17 | // -- This is a child command --
18 | // Cypress.Commands.add('drag', { prevSubject: 'element'}, (subject, options) => { ... })
19 | //
20 | //
21 | // -- This is a dual command --
22 | // Cypress.Commands.add('dismiss', { prevSubject: 'optional'}, (subject, options) => { ... })
23 | //
24 | //
25 | // -- This will overwrite an existing command --
26 | // Cypress.Commands.overwrite('visit', (originalFn, url, options) => { ... })
27 | //
28 | // declare global {
29 | // namespace Cypress {
30 | // interface Chainable {
31 | // login(email: string, password: string): Chainable
32 | // drag(subject: string, options?: Partial): Chainable
33 | // dismiss(subject: string, options?: Partial): Chainable
34 | // visit(originalFn: CommandOriginalFn, url: string, options: Partial): Chainable
35 | // }
36 | // }
37 | // }
--------------------------------------------------------------------------------
/cypress/support/component-index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 | Components App
8 |
9 |
10 |
11 |
12 |
--------------------------------------------------------------------------------
/cypress/support/component.ts:
--------------------------------------------------------------------------------
1 | // ***********************************************************
2 | // This example support/component.ts is processed and
3 | // loaded automatically before your test files.
4 | //
5 | // This is a great place to put global configuration and
6 | // behavior that modifies Cypress.
7 | //
8 | // You can change the location of this file or turn off
9 | // automatically serving support files with the
10 | // 'supportFile' configuration option.
11 | //
12 | // You can read more here:
13 | // https://on.cypress.io/configuration
14 | // ***********************************************************
15 |
16 | // Import commands.js using ES2015 syntax:
17 | import './commands'
18 |
19 | // Alternatively you can use CommonJS syntax:
20 | // require('./commands')
21 |
22 | import { mount } from 'cypress/angular'
23 |
24 | // Augment the Cypress namespace to include type definitions for
25 | // your custom command.
26 | // Alternatively, can be defined in cypress/support/component.d.ts
27 | // with a at the top of your spec.
28 | declare global {
29 | namespace Cypress {
30 | interface Chainable {
31 | mount: typeof mount
32 | }
33 | }
34 | }
35 |
36 | Cypress.Commands.add('mount', mount)
37 |
38 | // Example use:
39 | // cy.mount(MyComponent)
--------------------------------------------------------------------------------
/hooks/pre-commit.js:
--------------------------------------------------------------------------------
1 | const { execSync } = require('child_process');
2 | const chalk = require('chalk');
3 |
4 | /** Map of forbidden words and their match regex */
5 | const words = {
6 | fit: '\\s*fit\\(',
7 | fdescribe: '\\s*fdescribe\\(',
8 | debugger: '(debugger);?'
9 | };
10 | let status = 0;
11 | for (let word of Object.keys(words)) {
12 | const matchRegex = words[word];
13 | const gitCommand = `git diff --staged -G"${matchRegex}" --name-only`;
14 | const badFiles = execSync(gitCommand).toString();
15 | const filesAsArray = badFiles.split('\n');
16 | const tsFileRegex = /\.ts$/;
17 | const onlyTsFiles = filesAsArray.filter(file => tsFileRegex.test(file.trim()));
18 | if (onlyTsFiles.length) {
19 | status = 1;
20 | console.log(chalk.bgRed.black.bold(`The following files contains '${word}' in them:`));
21 | console.log(chalk.bgRed.black(onlyTsFiles.join('\n')));
22 | }
23 | }
24 | process.exit(status);
25 |
--------------------------------------------------------------------------------
/karma.conf.js:
--------------------------------------------------------------------------------
1 | // Karma configuration file, see link for more information
2 | // https://karma-runner.github.io/1.0/config/configuration-file.html
3 |
4 | module.exports = function (config) {
5 | config.set({
6 | basePath: '',
7 | frameworks: ['jasmine', '@angular-devkit/build-angular'],
8 | plugins: [
9 | require('karma-jasmine'),
10 | require('karma-chrome-launcher'),
11 | require('karma-jasmine-html-reporter'),
12 | require('karma-coverage'),
13 | require('@angular-devkit/build-angular/plugins/karma')
14 | ],
15 | client: {
16 | jasmine: {
17 | // you can add configuration options for Jasmine here
18 | // the possible options are listed at https://jasmine.github.io/api/edge/Configuration.html
19 | // for example, you can disable the random execution with `random: false`
20 | // or set a specific seed with `seed: 4321`
21 | },
22 | clearContext: false // leave Jasmine Spec Runner output visible in browser
23 | },
24 | jasmineHtmlReporter: {
25 | suppressAll: true // removes the duplicated traces
26 | },
27 | coverageReporter: {
28 | dir: require('path').join(__dirname, './coverage/cmdk'),
29 | subdir: '.',
30 | reporters: [
31 | { type: 'html' },
32 | { type: 'text-summary' }
33 | ]
34 | },
35 | reporters: ['progress', 'kjhtml'],
36 | port: 9876,
37 | colors: true,
38 | logLevel: config.LOG_INFO,
39 | autoWatch: true,
40 | browsers: ['Chrome'],
41 | singleRun: false,
42 | restartOnFileChange: true
43 | });
44 | };
45 |
--------------------------------------------------------------------------------
/logo.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "cmdk",
3 | "version": "0.0.0",
4 | "scripts": {
5 | "ng": "ng",
6 | "start": "ng serve",
7 | "build": "ng build --base-href=/cmdk/",
8 | "watch": "ng build --watch --configuration development",
9 | "contributors:add": "all-contributors add",
10 | "hooks:pre-commit": "node hooks/pre-commit.js",
11 | "commit": "cz",
12 | "deploy": "npx angular-cli-ghpages --dir=dist/cmdk",
13 | "copy": "cpx README.md dist/ngxpert/cmdk",
14 | "build:lib": "ng build @ngxpert/cmdk && npm run copy",
15 | "test:lib": "ng test @ngxpert/cmdk",
16 | "test:lib:headless": "cross-env CI=true npm run test:lib",
17 | "postbuild:lib": "npm run build --prefix projects/ngxpert/cmdk",
18 | "semantic-release": "semantic-release",
19 | "lint": "ng lint",
20 | "unit": "npm run cy:open",
21 | "cy:open": "cypress open --component",
22 | "test": "npm run ci:cy-run",
23 | "ci:cy-run": "start-server-and-test ci:start-server http://localhost:4200 cy:run",
24 | "ci:start-server": "serve -l 4200 -s ./dist/cmdk/browser",
25 | "cy:run": "cypress run --component"
26 | },
27 | "private": true,
28 | "dependencies": {
29 | "@angular/animations": "^18.0.4",
30 | "@angular/cdk": "^18.0.4",
31 | "@angular/common": "^18.0.4",
32 | "@angular/compiler": "^18.0.4",
33 | "@angular/core": "^18.0.4",
34 | "@angular/forms": "^18.0.4",
35 | "@angular/platform-browser": "^18.0.4",
36 | "@angular/platform-browser-dynamic": "^18.0.4",
37 | "@angular/router": "^18.0.4",
38 | "@ngneat/overview": "6.0.0",
39 | "@ngneat/until-destroy": "10.0.0",
40 | "prismjs": "^1.30.0",
41 | "rxjs": "~7.5.0",
42 | "tslib": "^2.3.0",
43 | "zone.js": "~0.14.4"
44 | },
45 | "devDependencies": {
46 | "@angular-devkit/build-angular": "^18.0.5",
47 | "@angular-devkit/core": "^18.0.5",
48 | "@angular-eslint/builder": "18.0.1",
49 | "@angular-eslint/eslint-plugin": "18.0.1",
50 | "@angular-eslint/eslint-plugin-template": "18.0.1",
51 | "@angular-eslint/schematics": "18.0.1",
52 | "@angular-eslint/template-parser": "18.0.1",
53 | "@angular/cli": "~18.0.5",
54 | "@angular/compiler-cli": "^18.0.4",
55 | "@commitlint/cli": "^17.0.3",
56 | "@commitlint/config-conventional": "^17.0.3",
57 | "@ngneat/spectator": "^19.4.1",
58 | "@semantic-release/changelog": "^6.0.1",
59 | "@semantic-release/git": "^10.0.1",
60 | "@types/jasmine": "~5.1.7",
61 | "@types/prismjs": "^1.26.0",
62 | "@typescript-eslint/eslint-plugin": "^7.13.1",
63 | "@typescript-eslint/parser": "^7.13.1",
64 | "all-contributors-cli": "^6.20.0",
65 | "angular-cli-ghpages": "^2.0.1",
66 | "commitizen": "^4.2.5",
67 | "cpx": "^1.5.0",
68 | "cross-env": "^7.0.3",
69 | "cypress": "^13.12.0",
70 | "eslint": "^8.57.0",
71 | "husky": "^8.0.1",
72 | "jasmine-core": "~4.2.0",
73 | "jsonc-parser": "^3.1.0",
74 | "karma": "~6.4.0",
75 | "karma-chrome-launcher": "~3.1.0",
76 | "karma-coverage": "~2.2.1",
77 | "karma-jasmine": "~5.1.0",
78 | "karma-jasmine-html-reporter": "~2.0.0",
79 | "lint-staged": "^13.0.3",
80 | "ng-packagr": "^18.0.0",
81 | "prettier": "^2.7.1",
82 | "sass": "^1.58.3",
83 | "semantic-release": "^19.0.3",
84 | "serve": "^14.2.3",
85 | "start-server-and-test": "^2.0.4",
86 | "typescript": "~5.4.5"
87 | },
88 | "config": {
89 | "commitizen": {
90 | "path": "cz-conventional-changelog"
91 | }
92 | },
93 | "lint-staged": {
94 | "src/**/*.{js,json,css,scss,ts,html,component.html}": [
95 | "prettier --write"
96 | ]
97 | },
98 | "husky": {
99 | "hooks": {
100 | "commit-msg": "commitlint -E HUSKY_GIT_PARAMS",
101 | "pre-commit": "npm run hooks:pre-commit && lint-staged"
102 | }
103 | }
104 | }
105 |
--------------------------------------------------------------------------------
/projects/ngxpert/cmdk/.eslintrc.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "../../../.eslintrc.json",
3 | "ignorePatterns": [
4 | "!**/*"
5 | ],
6 | "overrides": [
7 | {
8 | "files": [
9 | "*.ts"
10 | ],
11 | "parserOptions": {
12 | "project": [
13 | "projects/ngxpert/cmdk/tsconfig.lib.json",
14 | "projects/ngxpert/cmdk/tsconfig.spec.json"
15 | ],
16 | "createDefaultProgram": true
17 | },
18 | "rules": {
19 | "@angular-eslint/directive-selector": [
20 | "error",
21 | {
22 | "type": "attribute",
23 | "prefix": "cmdk",
24 | "style": "camelCase"
25 | }
26 | ],
27 | "@angular-eslint/component-selector": [
28 | "error",
29 | {
30 | "type": "element",
31 | "prefix": "cmdk",
32 | "style": "kebab-case"
33 | }
34 | ]
35 | }
36 | },
37 | {
38 | "files": [
39 | "*.html"
40 | ],
41 | "rules": {}
42 | }
43 | ]
44 | }
45 |
--------------------------------------------------------------------------------
/projects/ngxpert/cmdk/README.md:
--------------------------------------------------------------------------------
1 | # Cmdk
2 |
3 | This library was generated with [Angular CLI](https://github.com/angular/angular-cli) version 14.1.0.
4 |
5 | ## Code scaffolding
6 |
7 | Run `ng generate component component-name --project cmdk` to generate a new component. You can also use `ng generate directive|pipe|service|class|guard|interface|enum|module --project cmdk`.
8 | > Note: Don't forget to add `--project cmdk` or else it will be added to the default project in your `angular.json` file.
9 |
10 | ## Build
11 |
12 | Run `ng build cmdk` to build the project. The build artifacts will be stored in the `dist/` directory.
13 |
14 | ## Publishing
15 |
16 | After building your library with `ng build cmdk`, go to the dist folder `cd dist/cmdk` and run `npm publish`.
17 |
18 | ## Running unit tests
19 |
20 | Run `ng test cmdk` to execute the unit tests via [Karma](https://karma-runner.github.io).
21 |
22 | ## Further help
23 |
24 | To get more help on the Angular CLI use `ng help` or go check out the [Angular CLI Overview and Command Reference](https://angular.io/cli) page.
25 |
--------------------------------------------------------------------------------
/projects/ngxpert/cmdk/karma.conf.js:
--------------------------------------------------------------------------------
1 | // Karma configuration file, see link for more information
2 | // https://karma-runner.github.io/1.0/config/configuration-file.html
3 |
4 | module.exports = function (config) {
5 | config.set({
6 | basePath: '',
7 | frameworks: ['jasmine', '@angular-devkit/build-angular'],
8 | plugins: [
9 | require('karma-jasmine'),
10 | require('karma-chrome-launcher'),
11 | require('karma-jasmine-html-reporter'),
12 | require('karma-coverage'),
13 | require('@angular-devkit/build-angular/plugins/karma')
14 | ],
15 | client: {
16 | jasmine: {
17 | // you can add configuration options for Jasmine here
18 | // the possible options are listed at https://jasmine.github.io/api/edge/Configuration.html
19 | // for example, you can disable the random execution with `random: false`
20 | // or set a specific seed with `seed: 4321`
21 | },
22 | clearContext: false // leave Jasmine Spec Runner output visible in browser
23 | },
24 | jasmineHtmlReporter: {
25 | suppressAll: true // removes the duplicated traces
26 | },
27 | coverageReporter: {
28 | dir: require('path').join(__dirname, '../../../coverage/ngxpert/cmdk'),
29 | subdir: '.',
30 | reporters: [
31 | { type: 'html' },
32 | { type: 'text-summary' }
33 | ]
34 | },
35 | reporters: ['progress', 'kjhtml'],
36 | port: 9876,
37 | colors: true,
38 | logLevel: config.LOG_INFO,
39 | autoWatch: true,
40 | browsers: [process.env.CI ? 'ChromeHeadless' : 'Chrome'],
41 | singleRun: process.env.CI,
42 | restartOnFileChange: true
43 | });
44 | };
45 |
--------------------------------------------------------------------------------
/projects/ngxpert/cmdk/ng-package.json:
--------------------------------------------------------------------------------
1 | {
2 | "$schema": "../../../node_modules/ng-packagr/ng-package.schema.json",
3 | "dest": "../../../dist/ngxpert/cmdk",
4 | "lib": {
5 | "entryFile": "src/public-api.ts"
6 | },
7 | "allowedNonPeerDependencies": [
8 | "@ngneat/overview",
9 | "@ngneat/until-destroy",
10 | "@angular/cdk"
11 | ],
12 | "assets": [{ "glob": "**/*.*", "input": "styles", "output": "styles/scss" }]
13 | }
--------------------------------------------------------------------------------
/projects/ngxpert/cmdk/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "@ngxpert/cmdk",
3 | "version": "1.0.0",
4 | "peerDependencies": {
5 | "@angular/common": "^18.0.0 || ^19.0.0",
6 | "@angular/core": "^18.0.0 || ^19.0.0"
7 | },
8 | "dependencies": {
9 | "tslib": "^2.3.0",
10 | "@ngneat/overview": ">=6.0.0",
11 | "@ngneat/until-destroy": ">=10.0.0",
12 | "@angular/cdk": "^18.0.0 || ^19.0.0"
13 | },
14 | "keywords": [
15 | "angular",
16 | "angular 2",
17 | "cmdk",
18 | "menu",
19 | "anglar menu"
20 | ],
21 | "license": "MIT",
22 | "publishConfig": {
23 | "access": "public"
24 | },
25 | "bugs": {
26 | "url": "https://github.com/ngxpert/cmdk/issue"
27 | },
28 | "homepage": "https://github.com/ngxpert/cmdk#readme",
29 | "repository": {
30 | "type": "git",
31 | "url": "https://github.com/ngxpert/cmdk"
32 | },
33 | "schematics": "./schematics/collection.json",
34 | "scripts": {
35 | "css": "sass --no-source-map styles:../../../dist/ngxpert/cmdk/styles",
36 | "build": "npm run css"
37 | }
38 | }
39 |
--------------------------------------------------------------------------------
/projects/ngxpert/cmdk/src/lib/cmdk.module.ts:
--------------------------------------------------------------------------------
1 | import { ModuleWithProviders, NgModule } from '@angular/core';
2 | import { InputDirective } from './directives/input/input.directive';
3 | import { EmptyDirective } from './directives/empty/empty.directive';
4 | import { CommandComponent } from './components/command/command.component';
5 | import { GroupComponent } from './components/group/group.component';
6 | import { SeparatorComponent } from './components/separator/separator.component';
7 | import { CommonModule } from '@angular/common';
8 | import { ItemDirective } from './directives/item/item.directive';
9 | import { A11yModule } from '@angular/cdk/a11y';
10 | import { LoaderDirective } from './directives/loader/loader.directive';
11 | import { ListComponent } from './components/list/list.component';
12 |
13 | const ComponentsAndDirectives = [
14 | CommandComponent,
15 | InputDirective,
16 | EmptyDirective,
17 | GroupComponent,
18 | SeparatorComponent,
19 | ItemDirective,
20 | LoaderDirective,
21 | ListComponent,
22 | ];
23 | @NgModule({
24 | imports: [CommonModule, A11yModule, ...ComponentsAndDirectives],
25 | exports: ComponentsAndDirectives,
26 | })
27 | export class CmdkModule {
28 | static forRoot(): ModuleWithProviders {
29 | return {
30 | ngModule: CmdkModule,
31 | };
32 | }
33 | }
34 |
--------------------------------------------------------------------------------
/projects/ngxpert/cmdk/src/lib/cmdk.service.spec.ts:
--------------------------------------------------------------------------------
1 | import { createServiceFactory, SpectatorService } from '@ngxpert/spectator';
2 | import { CmdkService } from './cmdk.service';
3 |
4 | describe('CmdkService', () => {
5 | let spectator: SpectatorService;
6 | const createService = createServiceFactory(CmdkService);
7 |
8 | beforeEach(() => spectator = createService());
9 |
10 | it('should...', () => {
11 | expect(spectator.service).toBeTruthy();
12 | });
13 | });
--------------------------------------------------------------------------------
/projects/ngxpert/cmdk/src/lib/cmdk.service.ts:
--------------------------------------------------------------------------------
1 | import { Injectable } from '@angular/core';
2 | import { Subject } from 'rxjs';
3 |
4 | @Injectable()
5 | export class CmdkService {
6 | private _searchSub = new Subject();
7 | search$ = this._searchSub.asObservable();
8 | private _itemClickedSub = new Subject();
9 | itemClicked$ = this._itemClickedSub.asObservable();
10 | private _itemValueChangedSub = new Subject<{
11 | oldValue: string;
12 | newValue: string;
13 | }>();
14 | itemValueChanged$ = this._itemValueChangedSub.asObservable();
15 |
16 | setSearch(value: string) {
17 | this._searchSub.next(value);
18 | }
19 |
20 | itemClicked(value: string) {
21 | this._itemClickedSub.next(value);
22 | }
23 |
24 | itemValueChanged(oldValue: string, newValue: string) {
25 | this._itemValueChangedSub.next({ oldValue, newValue });
26 | }
27 | }
28 |
--------------------------------------------------------------------------------
/projects/ngxpert/cmdk/src/lib/components/command/command.component.html:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/projects/ngxpert/cmdk/src/lib/components/command/command.component.spec.ts:
--------------------------------------------------------------------------------
1 | import { ComponentFixture, TestBed } from '@angular/core/testing';
2 |
3 | import { CommandComponent } from './command.component';
4 |
5 | describe('CommandComponent', () => {
6 | let component: CommandComponent;
7 | let fixture: ComponentFixture;
8 |
9 | beforeEach(async () => {
10 | await TestBed.configureTestingModule({
11 | imports: [CommandComponent]
12 | })
13 | .compileComponents();
14 |
15 | fixture = TestBed.createComponent(CommandComponent);
16 | component = fixture.componentInstance;
17 | fixture.detectChanges();
18 | });
19 |
20 | it('should create', () => {
21 | expect(component).toBeTruthy();
22 | });
23 | });
24 |
--------------------------------------------------------------------------------
/projects/ngxpert/cmdk/src/lib/components/group/group.component.html:
--------------------------------------------------------------------------------
1 | @if (label) {
2 |
3 |
4 |
5 | }
6 |
7 |
8 |
9 |
--------------------------------------------------------------------------------
/projects/ngxpert/cmdk/src/lib/components/group/group.component.scss:
--------------------------------------------------------------------------------
1 | :host {
2 | &[cmdk-hidden="true"] {
3 | display: none;
4 | }
5 | }
--------------------------------------------------------------------------------
/projects/ngxpert/cmdk/src/lib/components/group/group.component.ts:
--------------------------------------------------------------------------------
1 | import {
2 | ChangeDetectionStrategy,
3 | ChangeDetectorRef,
4 | Component,
5 | ContentChildren,
6 | HostBinding,
7 | inject,
8 | Input,
9 | QueryList,
10 | } from '@angular/core';
11 | import { Content, DynamicViewDirective } from '@ngneat/overview';
12 | import { ItemDirective } from '../../directives/item/item.directive';
13 | import { CmdkGroupProps } from '../../types';
14 |
15 |
16 | let cmdkGroupId = 0;
17 |
18 | @Component({
19 | selector: 'cmdk-group',
20 | templateUrl: './group.component.html',
21 | changeDetection: ChangeDetectionStrategy.OnPush,
22 | // eslint-disable-next-line @angular-eslint/no-host-metadata-property
23 | host: {
24 | class: 'cmdk-group',
25 | },
26 | styleUrls: ['./group.component.scss'],
27 | standalone: true,
28 | imports: [DynamicViewDirective],
29 | })
30 | export class GroupComponent implements CmdkGroupProps {
31 | @Input() label?: Content;
32 | @Input() ariaLabel?: string;
33 |
34 | @ContentChildren(ItemDirective, { descendants: true })
35 | items!: QueryList;
36 |
37 | showGroup = true;
38 | private _active = false;
39 | readonly groupId = `cmdk-group-${cmdkGroupId++}`;
40 | _cdr = inject(ChangeDetectorRef);
41 |
42 | get filteredItems() {
43 | return this.items?.filter((item) => item.filtered);
44 | }
45 |
46 | get active() {
47 | return this._active;
48 | }
49 | set active(value: boolean) {
50 | this._active = value;
51 | this._cdr.markForCheck();
52 | }
53 |
54 | get filtered() {
55 | return this.filteredItems.length > 0;
56 | }
57 |
58 | @HostBinding('id')
59 | get id() {
60 | return this.groupId;
61 | }
62 |
63 | @HostBinding('class.cmdk-group-active')
64 | get activeClass() {
65 | return this.active;
66 | }
67 |
68 | @HostBinding('attr.cmdk-hidden')
69 | get hidden() {
70 | return !this.showGroup;
71 | }
72 |
73 | @HostBinding('attr.data-value')
74 | get dataValue() {
75 | return this.label?.toString().toLowerCase();
76 | }
77 | }
78 |
--------------------------------------------------------------------------------
/projects/ngxpert/cmdk/src/lib/components/list/list.component.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
--------------------------------------------------------------------------------
/projects/ngxpert/cmdk/src/lib/components/list/list.component.ts:
--------------------------------------------------------------------------------
1 | import {
2 | AfterViewInit,
3 | ChangeDetectionStrategy,
4 | ChangeDetectorRef,
5 | Component,
6 | ContentChildren,
7 | ElementRef,
8 | HostBinding,
9 | inject,
10 | Input,
11 | OnDestroy,
12 | QueryList,
13 | ViewChild,
14 | } from '@angular/core';
15 | import { ItemDirective } from '../../directives/item/item.directive';
16 | import { CmdkListProps } from '../../types';
17 |
18 | let cmdkListId = 0;
19 |
20 | @Component({
21 | selector: 'cmdk-list',
22 | templateUrl: './list.component.html',
23 | changeDetection: ChangeDetectionStrategy.OnPush,
24 | // eslint-disable-next-line @angular-eslint/no-host-metadata-property
25 | host: {
26 | class: 'cmdk-list',
27 | },
28 | standalone: true,
29 | })
30 | export class ListComponent implements AfterViewInit, OnDestroy, CmdkListProps {
31 | @Input() ariaLabel?: string;
32 |
33 | @ContentChildren(ItemDirective, { descendants: true })
34 | items!: QueryList;
35 |
36 | @ViewChild('height') heightEle!: ElementRef;
37 |
38 | private _active = false;
39 | private _elementRef = inject>(ElementRef);
40 | private _animationFrame: number | undefined;
41 | readonly listId = `cmdk-list-${cmdkListId++}`;
42 | _cdr = inject(ChangeDetectorRef);
43 | private _observer: ResizeObserver | undefined;
44 |
45 | get filteredItems() {
46 | return this.items?.filter((item) => item.filtered);
47 | }
48 |
49 | get active() {
50 | return this._active;
51 | }
52 | set active(value: boolean) {
53 | this._active = value;
54 | this._cdr.markForCheck();
55 | }
56 |
57 | get filtered() {
58 | return this.filteredItems.length > 0;
59 | }
60 |
61 | @HostBinding('id')
62 | get id() {
63 | return this.listId;
64 | }
65 |
66 | @HostBinding('class.cmdk-list-active')
67 | get activeClass() {
68 | return this.active;
69 | }
70 |
71 | ngAfterViewInit() {
72 | if (this.heightEle.nativeElement && this._elementRef.nativeElement) {
73 | const el = this.heightEle.nativeElement;
74 | const wrapper = this._elementRef.nativeElement;
75 |
76 | this._observer = new ResizeObserver(() => {
77 | this._animationFrame = requestAnimationFrame(() => {
78 | const height = el.getBoundingClientRect().height;
79 | wrapper.style.setProperty(
80 | `--cmdk-list-height`,
81 | height.toFixed(1) + 'px'
82 | );
83 | this._cdr.markForCheck();
84 | });
85 | });
86 | this._observer.observe(el);
87 | }
88 | }
89 |
90 | ngOnDestroy() {
91 | if (this._animationFrame !== undefined) {
92 | cancelAnimationFrame(this._animationFrame);
93 | }
94 | if (this._observer && this.heightEle.nativeElement) {
95 | this._observer.unobserve(this.heightEle.nativeElement);
96 | }
97 | }
98 | }
99 |
--------------------------------------------------------------------------------
/projects/ngxpert/cmdk/src/lib/components/separator/separator.component.html:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/projects/ngxpert/cmdk/src/lib/components/separator/separator.component.scss:
--------------------------------------------------------------------------------
1 | :host {
2 | &[cmdk-hidden="true"] {
3 | display: none;
4 | }
5 | }
--------------------------------------------------------------------------------
/projects/ngxpert/cmdk/src/lib/components/separator/separator.component.ts:
--------------------------------------------------------------------------------
1 | import {
2 | ChangeDetectionStrategy,
3 | ChangeDetectorRef,
4 | Component,
5 | HostBinding,
6 | inject,
7 | } from '@angular/core';
8 |
9 | /**
10 | * A visual and semantic separator between items or groups.
11 | * Visible when the search query is empty or `alwaysRender` is true, hidden otherwise.
12 | */
13 | @Component({
14 | selector: 'cmdk-separator',
15 | templateUrl: './separator.component.html',
16 | changeDetection: ChangeDetectionStrategy.OnPush,
17 | // eslint-disable-next-line @angular-eslint/no-host-metadata-property
18 | host: {
19 | class: 'cmdk-separator',
20 | },
21 | styleUrls: ['./separator.component.scss'],
22 | standalone: true,
23 | })
24 | export class SeparatorComponent {
25 | showSeparator = true;
26 | public cdr = inject(ChangeDetectorRef);
27 |
28 | @HostBinding('attr.cmdk-hidden')
29 | get hidden() {
30 | return !this.showSeparator;
31 | }
32 | }
33 |
--------------------------------------------------------------------------------
/projects/ngxpert/cmdk/src/lib/directives/empty/empty.directive.spec.ts:
--------------------------------------------------------------------------------
1 | import { EmptyDirective } from './empty.directive';
2 | import { ComponentFixture, TestBed } from '@angular/core/testing';
3 |
4 | import { CUSTOM_ELEMENTS_SCHEMA, Component } from '@angular/core';
5 | @Component({
6 | template: ` Testing Empty Directive
`,
7 | standalone: true,
8 | })
9 | class EmptyDirectiveComponent {}
10 |
11 | describe('EmptyDirective', () => {
12 | let fixture: ComponentFixture;
13 | beforeEach(async () => {
14 | await TestBed.configureTestingModule({
15 | imports: [EmptyDirective, EmptyDirectiveComponent],
16 | schemas: [CUSTOM_ELEMENTS_SCHEMA],
17 | }).compileComponents();
18 | fixture = TestBed.createComponent(EmptyDirectiveComponent);
19 |
20 | fixture.detectChanges(); // initial binding
21 | });
22 |
23 | it('should have empty text', () => {
24 | const textContent = fixture.nativeElement.textContent;
25 | expect(textContent).toBe('Testing Empty Directive');
26 | });
27 | });
28 |
--------------------------------------------------------------------------------
/projects/ngxpert/cmdk/src/lib/directives/empty/empty.directive.ts:
--------------------------------------------------------------------------------
1 | import {
2 | Directive,
3 | inject,
4 | Renderer2,
5 | TemplateRef,
6 | ViewContainerRef,
7 | } from '@angular/core';
8 |
9 | @Directive({
10 | selector: '[cmdkEmpty]',
11 | // eslint-disable-next-line @angular-eslint/no-host-metadata-property
12 | host: {
13 | class: 'cmdk-empty',
14 | },
15 | standalone: true,
16 | })
17 | export class EmptyDirective {
18 | private _hasView = false;
19 | private _templateRef = inject(TemplateRef);
20 | private _viewContainer = inject(ViewContainerRef);
21 | private _renderer2 = inject(Renderer2);
22 |
23 | set cmdkEmpty(condition: boolean | string) {
24 | if (condition && !this._hasView) {
25 | const emb = this._viewContainer.createEmbeddedView(this._templateRef);
26 | this._renderer2.addClass(emb.rootNodes[0], 'cmdk-empty');
27 | this._hasView = true;
28 | } else if (!condition && this._hasView) {
29 | this._viewContainer.clear();
30 | this._hasView = false;
31 | }
32 | }
33 | }
34 |
--------------------------------------------------------------------------------
/projects/ngxpert/cmdk/src/lib/directives/input/input.directive.spec.ts:
--------------------------------------------------------------------------------
1 | import { InputDirective } from './input.directive';
2 | import { ComponentFixture, TestBed } from '@angular/core/testing';
3 |
4 | import { CUSTOM_ELEMENTS_SCHEMA, Component } from '@angular/core';
5 | @Component({
6 | template: ` `,
7 | standalone: true,
8 | })
9 | class InputDirectiveComponent {}
10 |
11 | describe('InputDirective', () => {
12 | let fixture: ComponentFixture;
13 | let component: InputDirectiveComponent;
14 | beforeEach(async () => {
15 | await TestBed.configureTestingModule({
16 | imports: [InputDirective, InputDirectiveComponent],
17 | schemas: [CUSTOM_ELEMENTS_SCHEMA],
18 | }).compileComponents();
19 | fixture = TestBed.createComponent(InputDirectiveComponent);
20 | component = fixture.componentInstance;
21 | fixture.detectChanges(); // initial binding
22 | });
23 |
24 | it('should exist', () => {
25 | expect(component).toBeDefined();
26 | });
27 | });
28 |
--------------------------------------------------------------------------------
/projects/ngxpert/cmdk/src/lib/directives/input/input.directive.ts:
--------------------------------------------------------------------------------
1 | import {
2 | AfterViewInit,
3 | Directive,
4 | ElementRef,
5 | inject,
6 | Input,
7 | OnDestroy,
8 | } from '@angular/core';
9 | import { CmdkService } from '../../cmdk.service';
10 | import { CmdkInputProps } from '../../types';
11 |
12 | @Directive({
13 | selector: 'input[cmdkInput]',
14 | // eslint-disable-next-line @angular-eslint/no-host-metadata-property
15 | host: {
16 | class: 'cmdk-input',
17 | },
18 | standalone: true,
19 | })
20 | export class InputDirective
21 | implements AfterViewInit, OnDestroy, CmdkInputProps
22 | {
23 | @Input() updateOn: 'blur' | 'change' | 'input' = 'input';
24 | @Input()
25 | set value(value: string) {
26 | this.search(value);
27 | }
28 | private _cmdkService = inject(CmdkService);
29 | private _elementRef = inject>(ElementRef);
30 |
31 | search(value: string) {
32 | this._cmdkService.setSearch(value);
33 | }
34 |
35 | ngAfterViewInit(): void {
36 | this._elementRef.nativeElement.addEventListener(
37 | this.updateOn,
38 | (ev: Event) => this.search((ev.target as HTMLInputElement).value)
39 | );
40 | }
41 |
42 | ngOnDestroy(): void {
43 | this._elementRef.nativeElement.removeAllListeners &&
44 | this._elementRef.nativeElement.removeAllListeners(this.updateOn);
45 | }
46 | }
47 |
--------------------------------------------------------------------------------
/projects/ngxpert/cmdk/src/lib/directives/item/item.directive.spec.ts:
--------------------------------------------------------------------------------
1 | import { ItemDirective } from './item.directive';
2 | import { ComponentFixture, TestBed } from '@angular/core/testing';
3 |
4 | import { CUSTOM_ELEMENTS_SCHEMA, Component } from '@angular/core';
5 | @Component({
6 | template: `
`,
7 | standalone: true,
8 | })
9 | class ItemDirectiveComponent {}
10 |
11 | describe('ItemDirective', () => {
12 | let fixture: ComponentFixture;
13 | let component: ItemDirectiveComponent;
14 | beforeEach(async () => {
15 | await TestBed.configureTestingModule({
16 | imports: [ItemDirective, ItemDirectiveComponent],
17 | schemas: [CUSTOM_ELEMENTS_SCHEMA],
18 | }).compileComponents();
19 | fixture = TestBed.createComponent(ItemDirectiveComponent);
20 | component = fixture.componentInstance;
21 | fixture.detectChanges(); // initial binding
22 | });
23 |
24 | it('should exist', () => {
25 | expect(component).toBeDefined();
26 | });
27 | });
28 |
--------------------------------------------------------------------------------
/projects/ngxpert/cmdk/src/lib/directives/item/item.directive.ts:
--------------------------------------------------------------------------------
1 | import { ListKeyManagerOption } from '@angular/cdk/a11y';
2 | import { coerceBooleanProperty } from '@angular/cdk/coercion';
3 | import {
4 | AfterContentInit,
5 | Directive,
6 | ElementRef,
7 | EventEmitter,
8 | HostBinding,
9 | HostListener,
10 | inject,
11 | Input,
12 | Output,
13 | } from '@angular/core';
14 | import { UntilDestroy } from '@ngneat/until-destroy';
15 | import { CmdkService } from '../../cmdk.service';
16 | import { CmdkItemProps } from '../../types';
17 |
18 | let cmdkItemId = 0;
19 |
20 | @UntilDestroy()
21 | @Directive({
22 | selector: '[cmdkItem]',
23 | // eslint-disable-next-line @angular-eslint/no-host-metadata-property
24 | host: {
25 | class: 'cmdk-item',
26 | },
27 | standalone: true,
28 | })
29 | export class ItemDirective
30 | implements CmdkItemProps, ListKeyManagerOption, AfterContentInit
31 | {
32 | private _disabled = false;
33 | @Input()
34 | set disabled(value: any) {
35 | this._disabled = coerceBooleanProperty(value);
36 | }
37 | get disabled() {
38 | return this._disabled;
39 | }
40 | @Output() selected = new EventEmitter();
41 | originalDisplay = '';
42 | getLabel?(): string {
43 | throw new Error('Method not implemented.');
44 | }
45 | setActiveStyles(): void {
46 | this.active = true;
47 | }
48 | setInactiveStyles(): void {
49 | this.active = false;
50 | }
51 | @Input() filtered = true;
52 | private _active = false;
53 | private _value: string = '';
54 | @Input()
55 | set value(value: string) {
56 | if (value !== this.value) {
57 | this._cmdkService.itemValueChanged(this.value, value);
58 | this._value = value;
59 | }
60 | }
61 | get value() {
62 | return this._value
63 | ? this._value
64 | : this._elementRef.nativeElement.textContent.trim().toLowerCase();
65 | }
66 |
67 | private _cmdkService = inject(CmdkService);
68 |
69 | readonly itemId = `cmdk-item-${cmdkItemId++}`;
70 | _elementRef = inject(ElementRef);
71 |
72 | @HostListener('click')
73 | onClick() {
74 | this.selected.emit();
75 | }
76 |
77 | @HostListener('keydown', ['event'])
78 | onKeyUp(ev: KeyboardEvent) {
79 | if (ev?.key === 'Enter') {
80 | this.selected.emit();
81 | }
82 | }
83 |
84 | @HostBinding('attr.cmdk-hidden')
85 | get hidden() {
86 | return !this.filtered;
87 | }
88 |
89 | @HostBinding('style.display')
90 | get display() {
91 | return this.filtered ? this.originalDisplay : 'none';
92 | }
93 |
94 | @HostBinding('attr.data-value')
95 | get dataValue() {
96 | return this.value;
97 | }
98 |
99 | @HostBinding('attr.role')
100 | get attrRole() {
101 | return 'option';
102 | }
103 |
104 | @HostBinding('type')
105 | get buttonType() {
106 | return 'button';
107 | }
108 |
109 | @HostBinding('attr.aria-selected')
110 | get isSelected() {
111 | return this._active;
112 | }
113 |
114 | @HostBinding('class.cmdk-item-active')
115 | get active() {
116 | return this._active;
117 | }
118 |
119 | @HostBinding('class.cmdk-item-disabled')
120 | get itemDisabled() {
121 | return this.disabled;
122 | }
123 |
124 | @HostBinding('class.cmdk-item-filtered')
125 | get itemFiltered() {
126 | return this.filtered;
127 | }
128 |
129 | set active(value: boolean) {
130 | this._active = value;
131 | }
132 |
133 | @HostListener('mouseup')
134 | onMouseUp() {
135 | this._cmdkService.itemClicked(this.value);
136 | }
137 |
138 | ngAfterContentInit(): void {
139 | this.originalDisplay = window.getComputedStyle(
140 | this._elementRef.nativeElement
141 | ).display;
142 | }
143 | }
144 |
--------------------------------------------------------------------------------
/projects/ngxpert/cmdk/src/lib/directives/loader/loader.directive.spec.ts:
--------------------------------------------------------------------------------
1 | import { LoaderDirective } from './loader.directive';
2 | import { ComponentFixture, TestBed } from '@angular/core/testing';
3 |
4 | import { CUSTOM_ELEMENTS_SCHEMA, Component } from '@angular/core';
5 | @Component({
6 | template: ` Testing Loader Directive
`,
7 | standalone: true,
8 | })
9 | class LoaderDirectiveComponent {}
10 |
11 | describe('LoaderDirective', () => {
12 | let fixture: ComponentFixture;
13 | beforeEach(async () => {
14 | await TestBed.configureTestingModule({
15 | imports: [LoaderDirective, LoaderDirectiveComponent],
16 | schemas: [CUSTOM_ELEMENTS_SCHEMA],
17 | }).compileComponents();
18 | fixture = TestBed.createComponent(LoaderDirectiveComponent);
19 |
20 | fixture.detectChanges(); // initial binding
21 | });
22 |
23 | it('should have loader text', () => {
24 | const textContent = fixture.nativeElement.textContent;
25 | expect(textContent).toBe('Testing Loader Directive');
26 | });
27 | });
28 |
--------------------------------------------------------------------------------
/projects/ngxpert/cmdk/src/lib/directives/loader/loader.directive.ts:
--------------------------------------------------------------------------------
1 | import {
2 | Directive,
3 | TemplateRef,
4 | ViewContainerRef,
5 | inject,
6 | } from '@angular/core';
7 |
8 | @Directive({
9 | selector: '[cmdkLoader]',
10 | // eslint-disable-next-line @angular-eslint/no-host-metadata-property
11 | host: {
12 | class: 'cmdk-loader',
13 | },
14 | standalone: true,
15 | })
16 | export class LoaderDirective {
17 | private _hasView = false;
18 | private _templateRef = inject(TemplateRef);
19 | private _viewContainer = inject(ViewContainerRef);
20 |
21 | set cmdkLoader(condition: boolean | undefined) {
22 | if (condition && !this._hasView) {
23 | this._viewContainer.createEmbeddedView(this._templateRef);
24 | this._hasView = true;
25 | } else if (!condition && this._hasView) {
26 | this._viewContainer.clear();
27 | this._hasView = false;
28 | }
29 | }
30 | }
31 |
--------------------------------------------------------------------------------
/projects/ngxpert/cmdk/src/lib/types.ts:
--------------------------------------------------------------------------------
1 | import { EventEmitter } from '@angular/core';
2 | import { Content } from '@ngneat/overview';
3 |
4 | export interface CmdkCommandProps {
5 | /**
6 | * Accessible Label for this command menu. Not shown visibly.
7 | */
8 | ariaLabel?: string;
9 | /**
10 | * Custom filter function for whether each command menu item should matches the given search query.
11 | * It should return a boolean, false being hidden entirely. You can pass null to disable default filtering.
12 | * @default
13 | */
14 | filter?: ((value: string, search: string) => boolean) | null | undefined;
15 | /**
16 | * Optional controlled state of the selected command menu item.
17 | */
18 | value?: string;
19 | /**
20 | * Event handler called when the selected item of the menu changes.
21 | */
22 | valueChanged: EventEmitter;
23 | /**
24 | * Optional indicator to show loader
25 | */
26 | loading?: boolean;
27 | /**
28 | * Optionally set to `true` to turn on looping around when using the arrow keys.
29 | */
30 | loop?: boolean;
31 | }
32 |
33 | export interface CmdkGroupProps {
34 | /**
35 | * Label for this command menu.
36 | */
37 | label?: Content;
38 | /**
39 | * Accessible Label for this command menu. Not shown visibly.
40 | */
41 | ariaLabel?: string;
42 | }
43 |
44 | export interface CmdkListProps extends Omit {}
45 | export interface CmdkItemProps {
46 | /**
47 | * Contextual Value of the list-item
48 | */
49 | value: string | undefined;
50 | /**
51 | * Contextually mark the item as disabled. Keyboard navigation will skip this item.
52 | */
53 | disabled?: boolean;
54 | /**
55 | * Event handler called when the item is selected
56 | */
57 | selected: EventEmitter;
58 | }
59 |
60 | export interface CmdkInputProps {
61 | /**
62 | * Optional indicator to provide event listener when filtering should happen
63 | * @default input
64 | */
65 | updateOn?: 'blur' | 'change' | 'input';
66 | }
--------------------------------------------------------------------------------
/projects/ngxpert/cmdk/src/public-api.ts:
--------------------------------------------------------------------------------
1 | /*
2 | * Public API Surface of cmdk
3 | */
4 |
5 | export * from './lib/cmdk.module';
6 | export * from './lib/cmdk.service';
7 | export * from './lib/components/command/command.component';
8 | export * from './lib/components/group/group.component';
9 | export * from './lib/components/separator/separator.component';
10 | export * from './lib/components/list/list.component';
11 | export * from './lib/directives/empty/empty.directive';
12 | export * from './lib/directives/input/input.directive';
13 | export * from './lib/directives/item/item.directive';
14 | export * from './lib/directives/loader/loader.directive';
15 | export * from './lib/types';
--------------------------------------------------------------------------------
/projects/ngxpert/cmdk/src/test.ts:
--------------------------------------------------------------------------------
1 | // This file is required by karma.conf.js and loads recursively all the .spec and framework files
2 |
3 | import 'zone.js';
4 | import 'zone.js/testing';
5 | import { getTestBed } from '@angular/core/testing';
6 | import {
7 | BrowserDynamicTestingModule,
8 | platformBrowserDynamicTesting
9 | } from '@angular/platform-browser-dynamic/testing';
10 |
11 | // First, initialize the Angular testing environment.
12 | getTestBed().initTestEnvironment(
13 | BrowserDynamicTestingModule,
14 | platformBrowserDynamicTesting(),
15 | );
16 |
--------------------------------------------------------------------------------
/projects/ngxpert/cmdk/styles/framer.scss:
--------------------------------------------------------------------------------
1 | .framer {
2 |
3 | .cmdk-group, .cmdk-list, .cmdk-command {
4 | display: block;
5 | }
6 | .cmdk-command {
7 | max-width: 640px;
8 | padding: 8px;
9 | background: #ffffff;
10 | border-radius: 16px;
11 | overflow: hidden;
12 | font-family: var(--font-sans);
13 | border: 1px solid var(--gray6);
14 | box-shadow: var(--cmdk-shadow);
15 |
16 | .dark & {
17 | background: var(--gray2);
18 | }
19 | }
20 |
21 | .cmdk-framer-header {
22 | display: flex;
23 | align-items: center;
24 | gap: 8px;
25 | height: 48px;
26 | padding: 0 8px;
27 | border-bottom: 1px solid var(--gray5);
28 | margin-bottom: 12px;
29 | padding-bottom: 8px;
30 |
31 | svg {
32 | width: 20px;
33 | height: 20px;
34 | color: var(--gray9);
35 | transform: translateY(1px);
36 | }
37 | }
38 |
39 | .cmdk-input {
40 | font-family: var(--font-sans);
41 | border: none;
42 | width: 100%;
43 | font-size: 16px;
44 | outline: none;
45 | background: var(--bg);
46 | color: var(--gray12);
47 |
48 | &::placeholder {
49 | color: var(--gray9);
50 | }
51 | }
52 |
53 | .cmdk-item {
54 | content-visibility: auto;
55 |
56 | cursor: pointer;
57 | border-radius: 12px;
58 | font-size: 14px;
59 | display: flex;
60 | align-items: center;
61 | gap: 12px;
62 | color: var(--gray12);
63 | padding: 8px 8px;
64 | margin-right: 8px;
65 | font-weight: 500;
66 | transition: all 150ms ease;
67 | transition-property: none;
68 | width: 100%;
69 | text-align: left;
70 |
71 | &:focus {
72 | outline: none;
73 | }
74 |
75 | &:hover, &:focus {
76 | background: var(--blue2);
77 | }
78 |
79 | &[aria-selected='true'] {
80 | background: var(--blue9);
81 | color: #ffffff;
82 |
83 | .cmdk-framer-item-subtitle {
84 | color: #ffffff;
85 | }
86 | }
87 |
88 | &[aria-disabled='true'] {
89 | color: var(--gray8);
90 | cursor: not-allowed;
91 | }
92 |
93 | & + .cmdk-item {
94 | margin-top: 4px;
95 | }
96 |
97 | svg {
98 | width: 16px;
99 | height: 16px;
100 | color: #ffffff;
101 | }
102 | }
103 |
104 | .cmdk-framer-icon-wrapper {
105 | display: flex;
106 | align-items: center;
107 | justify-content: center;
108 | min-width: 32px;
109 | height: 32px;
110 | background: orange;
111 | border-radius: 8px;
112 | line-height: 0.7;
113 | }
114 |
115 | .cmdk-framer-item-meta {
116 | display: flex;
117 | flex-direction: column;
118 | gap: 4px;
119 | }
120 |
121 | .cmdk-framer-item-subtitle {
122 | font-size: 12px;
123 | font-weight: 400;
124 | color: var(--gray11);
125 | }
126 |
127 | .cmdk-framer-items {
128 | min-height: 308px;
129 | display: flex;
130 | }
131 |
132 | .cmdk-framer-left {
133 | width: 40%;
134 | }
135 |
136 | .cmdk-framer-separator {
137 | width: 1px;
138 | border: 0;
139 | margin-right: 8px;
140 | background: var(--gray6);
141 | }
142 |
143 | .cmdk-framer-right {
144 | display: flex;
145 | align-items: center;
146 | justify-content: center;
147 | border-radius: 8px;
148 | margin-left: 8px;
149 | width: 60%;
150 |
151 | .cmdk-framer-badge {
152 | background: var(--blue3);
153 | padding: 0 8px;
154 | height: 28px;
155 | font-size: 14px;
156 | line-height: 28px;
157 | color: var(--blue11);
158 | border-radius: 9999px;
159 | font-weight: 500;
160 | }
161 |
162 | .cmdk-framer-slider {
163 | height: 20px;
164 | width: 200px;
165 | background: linear-gradient(90deg, var(--blue9) 40%, var(--gray3) 0%);
166 | border-radius: 9999px;
167 |
168 | div {
169 | width: 20px;
170 | height: 20px;
171 | background: #ffffff;
172 | border-radius: 9999px;
173 | box-shadow: 0 1px 3px -1px rgba(0, 0, 0, 0.32);
174 | transform: translateX(70px);
175 | }
176 | }
177 |
178 | .cmdk-framer-avatar {
179 | border-radius: 50%;
180 | }
181 |
182 | .cmdk-framer-container {
183 | width: 100px;
184 | height: 100px;
185 | background: var(--blue9);
186 | border-radius: 16px;
187 | }
188 | }
189 |
190 | .cmdk-list {
191 | overflow: auto;
192 | }
193 |
194 | .cmdk-separator {
195 | padding: 0 16px;
196 | display: block;
197 | hr {
198 | height: 1px;
199 | width: 100%;
200 | background: var(--gray3);
201 | margin: 4px 0;
202 | }
203 | }
204 |
205 | .cmdk-group-label {
206 | user-select: none;
207 | font-size: 12px;
208 | color: var(--gray11);
209 | padding: 0 8px;
210 | display: flex;
211 | align-items: center;
212 | margin-bottom: 8px;
213 | }
214 |
215 | .cmdk-empty {
216 | font-size: 14px;
217 | padding: 32px;
218 | white-space: pre-wrap;
219 | color: var(--gray11);
220 | }
221 | }
222 |
223 | @media (max-width: 640px) {
224 | .framer {
225 |
226 | .cmdk-framer-item-subtitle {
227 | display: none;
228 | }
229 | }
230 | }
231 |
--------------------------------------------------------------------------------
/projects/ngxpert/cmdk/styles/globals.scss:
--------------------------------------------------------------------------------
1 | ::selection {
2 | background: hotpink;
3 | color: white;
4 | }
5 |
6 | html,
7 | body {
8 | padding: 0;
9 | margin: 0;
10 | font-family: var(--font-sans);
11 | }
12 |
13 | body {
14 | background: var(--app-bg);
15 | overflow-x: hidden;
16 | }
17 |
18 | button {
19 | background: none;
20 | font-family: var(--font-sans);
21 | padding: 0;
22 | border: 0;
23 | }
24 |
25 | h1,
26 | h2,
27 | h3,
28 | h4,
29 | h5,
30 | h6,
31 | p {
32 | margin: 0;
33 | }
34 |
35 | a {
36 | color: inherit;
37 | text-decoration: none;
38 | }
39 |
40 | *,
41 | *::after,
42 | *::before {
43 | box-sizing: border-box;
44 | -webkit-font-smoothing: antialiased;
45 | -moz-osx-font-smoothing: grayscale;
46 | }
47 |
48 | :root {
49 | --font-sans: 'Inter', --apple-system, BlinkMacSystemFont, Segoe UI, Roboto, Oxygen, Ubuntu, Cantarell, Fira Sans,
50 | Droid Sans, Helvetica Neue, sans-serif;
51 | --app-bg: var(--gray1);
52 | --cmdk-shadow: 0 16px 70px rgb(0 0 0 / 20%);
53 | --cmdk-list-height: 300px;
54 |
55 | --lowContrast: #ffffff;
56 | --highContrast: #000000;
57 |
58 | --gray1: hsl(0, 0%, 99%);
59 | --gray2: hsl(0, 0%, 97.3%);
60 | --gray3: hsl(0, 0%, 95.1%);
61 | --gray4: hsl(0, 0%, 93%);
62 | --gray5: hsl(0, 0%, 90.9%);
63 | --gray6: hsl(0, 0%, 88.7%);
64 | --gray7: hsl(0, 0%, 85.8%);
65 | --gray8: hsl(0, 0%, 78%);
66 | --gray9: hsl(0, 0%, 56.1%);
67 | --gray10: hsl(0, 0%, 52.3%);
68 | --gray11: hsl(0, 0%, 43.5%);
69 | --gray12: hsl(0, 0%, 9%);
70 |
71 | --grayA1: hsla(0, 0%, 0%, 0.012);
72 | --grayA2: hsla(0, 0%, 0%, 0.027);
73 | --grayA3: hsla(0, 0%, 0%, 0.047);
74 | --grayA4: hsla(0, 0%, 0%, 0.071);
75 | --grayA5: hsla(0, 0%, 0%, 0.09);
76 | --grayA6: hsla(0, 0%, 0%, 0.114);
77 | --grayA7: hsla(0, 0%, 0%, 0.141);
78 | --grayA8: hsla(0, 0%, 0%, 0.22);
79 | --grayA9: hsla(0, 0%, 0%, 0.439);
80 | --grayA10: hsla(0, 0%, 0%, 0.478);
81 | --grayA11: hsla(0, 0%, 0%, 0.565);
82 | --grayA12: hsla(0, 0%, 0%, 0.91);
83 |
84 | --blue1: hsl(206, 100%, 99.2%);
85 | --blue2: hsl(210, 100%, 98%);
86 | --blue3: hsl(209, 100%, 96.5%);
87 | --blue4: hsl(210, 98.8%, 94%);
88 | --blue5: hsl(209, 95%, 90.1%);
89 | --blue6: hsl(209, 81.2%, 84.5%);
90 | --blue7: hsl(208, 77.5%, 76.9%);
91 | --blue8: hsl(206, 81.9%, 65.3%);
92 | --blue9: hsl(206, 100%, 50%);
93 | --blue10: hsl(208, 100%, 47.3%);
94 | --blue11: hsl(211, 100%, 43.2%);
95 | --blue12: hsl(211, 100%, 15%);
96 | }
97 |
98 | .dark {
99 | --app-bg: var(--gray1);
100 |
101 | --lowContrast: #000000;
102 | --highContrast: #ffffff;
103 |
104 | --gray1: hsl(0, 0%, 8.5%);
105 | --gray2: hsl(0, 0%, 11%);
106 | --gray3: hsl(0, 0%, 13.6%);
107 | --gray4: hsl(0, 0%, 15.8%);
108 | --gray5: hsl(0, 0%, 17.9%);
109 | --gray6: hsl(0, 0%, 20.5%);
110 | --gray7: hsl(0, 0%, 24.3%);
111 | --gray8: hsl(0, 0%, 31.2%);
112 | --gray9: hsl(0, 0%, 43.9%);
113 | --gray10: hsl(0, 0%, 49.4%);
114 | --gray11: hsl(0, 0%, 62.8%);
115 | --gray12: hsl(0, 0%, 93%);
116 |
117 | --grayA1: hsla(0, 0%, 100%, 0);
118 | --grayA2: hsla(0, 0%, 100%, 0.026);
119 | --grayA3: hsla(0, 0%, 100%, 0.056);
120 | --grayA4: hsla(0, 0%, 100%, 0.077);
121 | --grayA5: hsla(0, 0%, 100%, 0.103);
122 | --grayA6: hsla(0, 0%, 100%, 0.129);
123 | --grayA7: hsla(0, 0%, 100%, 0.172);
124 | --grayA8: hsla(0, 0%, 100%, 0.249);
125 | --grayA9: hsla(0, 0%, 100%, 0.386);
126 | --grayA10: hsla(0, 0%, 100%, 0.446);
127 | --grayA11: hsla(0, 0%, 100%, 0.592);
128 | --grayA12: hsla(0, 0%, 100%, 0.923);
129 |
130 | --blue1: hsl(212, 35%, 9.2%);
131 | --blue2: hsl(216, 50%, 11.8%);
132 | --blue3: hsl(214, 59.4%, 15.3%);
133 | --blue4: hsl(214, 65.8%, 17.9%);
134 | --blue5: hsl(213, 71.2%, 20.2%);
135 | --blue6: hsl(212, 77.4%, 23.1%);
136 | --blue7: hsl(211, 85.1%, 27.4%);
137 | --blue8: hsl(211, 89.7%, 34.1%);
138 | --blue9: hsl(206, 100%, 50%);
139 | --blue10: hsl(209, 100%, 60.6%);
140 | --blue11: hsl(210, 100%, 66.1%);
141 | --blue12: hsl(206, 98%, 95.8%);
142 | }
143 |
--------------------------------------------------------------------------------
/projects/ngxpert/cmdk/styles/linear.scss:
--------------------------------------------------------------------------------
1 | .linear {
2 |
3 | .cmdk-group, .cmdk-list, .cmdk-command {
4 | display: block;
5 | }
6 | .cmdk-command {
7 | max-width: 640px;
8 | background: #ffffff;
9 | border-radius: 8px;
10 | overflow: hidden;
11 | padding: 0;
12 | font-family: var(--font-sans);
13 | box-shadow: var(--cmdk-shadow);
14 |
15 | .dark & {
16 | background: linear-gradient(136.61deg, rgb(39, 40, 43) 13.72%, rgb(45, 46, 49) 74.3%);
17 | }
18 | }
19 |
20 | .cmdk-linear-badge {
21 | height: 24px;
22 | padding: 0 8px;
23 | font-size: 12px;
24 | color: var(--gray11);
25 | background: var(--gray3);
26 | border-radius: 4px;
27 | width: fit-content;
28 | display: flex;
29 | align-items: center;
30 | margin: 16px 16px 0;
31 | }
32 |
33 | .cmdk-linear-shortcuts {
34 | display: flex;
35 | margin-left: auto;
36 | gap: 8px;
37 |
38 | kbd {
39 | font-family: var(--font-sans);
40 | font-size: 13px;
41 | color: var(--gray11);
42 | }
43 | }
44 |
45 | .cmdk-input {
46 | font-family: var(--font-sans);
47 | border: none;
48 | width: 100%;
49 | font-size: 18px;
50 | padding: 20px;
51 | outline: none;
52 | background: var(--bg);
53 | color: var(--gray12);
54 | border-bottom: 1px solid var(--gray6);
55 | border-radius: 0;
56 | caret-color: #6e5ed2;
57 | margin: 0;
58 |
59 | &::placeholder {
60 | color: var(--gray9);
61 | }
62 | }
63 |
64 | .cmdk-item {
65 | content-visibility: auto;
66 |
67 | cursor: pointer;
68 | height: 48px;
69 | font-size: 14px;
70 | display: flex;
71 | align-items: center;
72 | gap: 12px;
73 | padding: 0 16px;
74 | color: var(--gray12);
75 | user-select: none;
76 | will-change: background, color;
77 | transition: all 150ms ease;
78 | transition-property: none;
79 | position: relative;
80 | width: 100%;
81 |
82 | &[aria-selected='true'] {
83 | background: var(--gray3);
84 |
85 | svg {
86 | color: var(--gray12);
87 | }
88 |
89 | &:after {
90 | content: '';
91 | position: absolute;
92 | left: 0;
93 | z-index: 123;
94 | width: 3px;
95 | height: 100%;
96 | background: #5f6ad2;
97 | }
98 | }
99 |
100 | &[aria-disabled='true'] {
101 | color: var(--gray8);
102 | cursor: not-allowed;
103 | }
104 |
105 | &:active {
106 | transition-property: background;
107 | background: var(--gray4);
108 | }
109 |
110 | &:focus {
111 | outline: none;
112 | }
113 |
114 | &:hover, &:focus {
115 | transition-property: background;
116 | background: var(--gray2);
117 | }
118 |
119 | & + .cmdk-item {
120 | margin-top: 4px;
121 | }
122 |
123 | svg {
124 | width: 16px;
125 | height: 16px;
126 | color: var(--gray10);
127 | }
128 | }
129 |
130 | .cmdk-list {
131 | height: min(300px, var(--cmdk-list-height));
132 | max-height: 400px;
133 | overflow: auto;
134 | overscroll-behavior: contain;
135 | transition: 100ms ease;
136 | transition-property: height;
137 | }
138 |
139 | .cmdk-group-label {
140 | user-select: none;
141 | font-size: 12px;
142 | color: var(--gray11);
143 | padding: 0 8px;
144 | display: flex;
145 | align-items: center;
146 | }
147 |
148 | .cmdk-empty {
149 | font-size: 14px;
150 | display: flex;
151 | align-items: center;
152 | justify-content: center;
153 | height: 64px;
154 | white-space: pre-wrap;
155 | color: var(--gray11);
156 | }
157 | }
158 |
--------------------------------------------------------------------------------
/projects/ngxpert/cmdk/styles/vercel.scss:
--------------------------------------------------------------------------------
1 | .vercel {
2 |
3 | .cmdk-group, .cmdk-list, .cmdk-command {
4 | display: block;
5 | }
6 | .cmdk-command {
7 | max-width: 640px;
8 | padding: 8px;
9 | background: #ffffff;
10 | border-radius: 12px;
11 | overflow: hidden;
12 | font-family: var(--font-sans);
13 | border: 1px solid var(--gray6);
14 | box-shadow: var(--cmdk-shadow);
15 | transition: transform 100ms ease;
16 |
17 | .dark & {
18 | background: rgba(22, 22, 22, 0.7);
19 | }
20 | }
21 |
22 | .cmdk-input {
23 | font-family: var(--font-sans);
24 | border: none;
25 | width: 100%;
26 | font-size: 17px;
27 | padding: 8px 8px 16px 8px;
28 | outline: none;
29 | background: var(--bg);
30 | color: var(--gray12);
31 | border-bottom: 1px solid var(--gray6);
32 | margin-bottom: 16px;
33 | border-radius: 0;
34 |
35 | &::placeholder {
36 | color: var(--gray9);
37 | }
38 | }
39 |
40 | .cmdk-vercel-badge {
41 | height: 20px;
42 | background: var(--grayA3);
43 | display: inline-flex;
44 | align-items: center;
45 | padding: 0 8px;
46 | font-size: 12px;
47 | color: var(--grayA11);
48 | border-radius: 4px;
49 | margin: 4px 0 4px 4px;
50 | user-select: none;
51 | text-transform: capitalize;
52 | font-weight: 500;
53 | }
54 |
55 | .cmdk-item {
56 | content-visibility: auto;
57 |
58 | cursor: pointer;
59 | height: 48px;
60 | width: 100%;
61 | border-radius: 8px;
62 | font-size: 14px;
63 | display: flex;
64 | align-items: center;
65 | gap: 8px;
66 | padding: 0 16px;
67 | color: var(--gray11);
68 | user-select: none;
69 | will-change: background, color;
70 | transition: all 150ms ease;
71 | transition-property: none;
72 |
73 | &[aria-selected='true'] {
74 | background: var(--grayA3);
75 | color: var(--gray12);
76 | }
77 |
78 | &[aria-disabled='true'] {
79 | color: var(--gray8);
80 | cursor: not-allowed;
81 | }
82 |
83 | &:active {
84 | transition-property: background;
85 | background: var(--gray4);
86 | }
87 |
88 | &:focus {
89 | outline: none;
90 | }
91 |
92 | & + .cmdk-item {
93 | margin-top: 4px;
94 | }
95 |
96 | svg {
97 | width: 18px;
98 | height: 18px;
99 | }
100 | }
101 |
102 | .cmdk-list {
103 | height: min(330px, calc(var(--cmdk-list-height)));
104 | max-height: 400px;
105 | overflow: auto;
106 | overscroll-behavior: contain;
107 | transition: 100ms ease;
108 | transition-property: height;
109 | }
110 |
111 | .cmdk-vercel-shortcuts {
112 | display: flex;
113 | margin-left: auto;
114 | gap: 8px;
115 |
116 | kbd {
117 | font-family: var(--font-sans);
118 | font-size: 12px;
119 | min-width: 20px;
120 | padding: 4px;
121 | height: 20px;
122 | border-radius: 4px;
123 | color: var(--gray11);
124 | background: var(--gray4);
125 | display: inline-flex;
126 | align-items: center;
127 | justify-content: center;
128 | text-transform: uppercase;
129 | }
130 | }
131 |
132 | .cmdk-separator hr {
133 | height: 1px;
134 | width: 100%;
135 | background: var(--gray5);
136 | margin: 4px 0;
137 | }
138 |
139 | .cmdk-group {
140 | display: block;
141 | }
142 |
143 | *:not([hidden]) + .cmdk-group {
144 | margin-top: 8px;
145 | }
146 |
147 | .cmdk-group-label {
148 | user-select: none;
149 | font-size: 12px;
150 | color: var(--gray11);
151 | padding: 0 8px;
152 | display: flex;
153 | align-items: center;
154 | margin-bottom: 8px;
155 | }
156 |
157 | .cmdk-empty {
158 | font-size: 14px;
159 | display: flex;
160 | align-items: center;
161 | justify-content: center;
162 | height: 48px;
163 | white-space: pre-wrap;
164 | color: var(--gray11);
165 | }
166 | }
167 |
--------------------------------------------------------------------------------
/projects/ngxpert/cmdk/tsconfig.lib.json:
--------------------------------------------------------------------------------
1 | /* To learn more about this file see: https://angular.io/config/tsconfig. */
2 | {
3 | "extends": "../../../tsconfig.json",
4 | "compilerOptions": {
5 | "outDir": "../../../out-tsc/lib",
6 | "declaration": true,
7 | "declarationMap": true,
8 | "inlineSources": true,
9 | "types": []
10 | },
11 | "exclude": [
12 | "src/test.ts",
13 | "**/*.spec.ts"
14 | ]
15 | }
16 |
--------------------------------------------------------------------------------
/projects/ngxpert/cmdk/tsconfig.lib.prod.json:
--------------------------------------------------------------------------------
1 | /* To learn more about this file see: https://angular.io/config/tsconfig. */
2 | {
3 | "extends": "./tsconfig.lib.json",
4 | "compilerOptions": {
5 | "declarationMap": false
6 | },
7 | "angularCompilerOptions": {
8 | "compilationMode": "partial"
9 | }
10 | }
11 |
--------------------------------------------------------------------------------
/projects/ngxpert/cmdk/tsconfig.spec.json:
--------------------------------------------------------------------------------
1 | /* To learn more about this file see: https://angular.io/config/tsconfig. */
2 | {
3 | "extends": "../../../tsconfig.json",
4 | "compilerOptions": {
5 | "outDir": "../../../out-tsc/spec",
6 | "types": [
7 | "jasmine"
8 | ]
9 | },
10 | "files": [
11 | "src/test.ts"
12 | ],
13 | "include": [
14 | "**/*.spec.ts",
15 | "**/*.d.ts"
16 | ]
17 | }
18 |
--------------------------------------------------------------------------------
/src/app/app.component.html:
--------------------------------------------------------------------------------
1 |
2 | @if (currentTheme === 'light') {
3 | 🌚
4 | } @else {
5 | 🌞
6 | }
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
--------------------------------------------------------------------------------
/src/app/app.component.ts:
--------------------------------------------------------------------------------
1 | import { HttpClient } from '@angular/common/http';
2 | import { Component } from '@angular/core';
3 | import { map } from 'rxjs';
4 | import { FooterComponent } from './sections/footer/footer.component';
5 | import { CodeBlockComponent } from './shared/components/code-block/code-block.component';
6 | import { ThemeSwitcherComponent } from './sections/theme-switcher/theme-switcher.component';
7 | import { MetaComponent } from './sections/meta/meta.component';
8 | import { AsyncPipe } from '@angular/common';
9 |
10 | @Component({
11 | selector: 'app-root',
12 | templateUrl: './app.component.html',
13 | standalone: true,
14 | imports: [
15 | MetaComponent,
16 | ThemeSwitcherComponent,
17 | CodeBlockComponent,
18 | FooterComponent,
19 | AsyncPipe
20 | ],
21 | })
22 | export class AppComponent {
23 | currentTheme: 'light' | 'dark' = 'light';
24 | snippet = `
25 |
26 |
27 | No results found.
28 |
29 |
30 | a
31 | b
32 |
33 | c
34 |
35 |
36 |
37 | Apple
38 | `;
39 | version$: any;
40 |
41 | toggleTheme() {
42 | if (this.currentTheme === 'light') {
43 | this.currentTheme = 'dark';
44 | } else {
45 | this.currentTheme = 'light';
46 | }
47 |
48 | document.body.classList.toggle('dark');
49 | }
50 |
51 | constructor(private http: HttpClient) {
52 | this.version$ = this.http
53 | .get('https://registry.npmjs.org/@ngxpert/cmdk')
54 | .pipe(map((data: any) => data['dist-tags']['latest']));
55 | }
56 | }
57 |
--------------------------------------------------------------------------------
/src/app/core/services/code-highlight.service.spec.ts:
--------------------------------------------------------------------------------
1 | import { TestBed } from '@angular/core/testing';
2 |
3 | import { CodeHighlightService } from './code-highlight.service';
4 |
5 | describe('CodeHighlightService', () => {
6 | let service: CodeHighlightService;
7 |
8 | beforeEach(() => {
9 | TestBed.configureTestingModule({});
10 | service = TestBed.inject(CodeHighlightService);
11 | });
12 |
13 | it('should be created', () => {
14 | expect(service).toBeTruthy();
15 | });
16 | });
17 |
--------------------------------------------------------------------------------
/src/app/core/services/code-highlight.service.ts:
--------------------------------------------------------------------------------
1 | import { Injectable } from '@angular/core';
2 | import { highlightElement } from 'prismjs';
3 | import 'prismjs/components/prism-javascript';
4 | import 'prismjs/components/prism-bash';
5 | import 'prismjs/components/prism-typescript';
6 |
7 | @Injectable({
8 | providedIn: 'root',
9 | })
10 | export class CodeHighlightService {
11 | highlightElement(el: HTMLElement) {
12 | highlightElement(el);
13 | }
14 | }
15 |
--------------------------------------------------------------------------------
/src/app/icons/assign-to-icon/assign-to-icon.component.ts:
--------------------------------------------------------------------------------
1 | import { Component } from '@angular/core';
2 |
3 | @Component({
4 | selector: 'app-assign-to-icon',
5 | template: `
11 |
14 | `,
15 | styles: [],
16 | standalone: true,
17 | })
18 | export class AssignToIconComponent {}
19 |
--------------------------------------------------------------------------------
/src/app/icons/assign-to-me-icon/assign-to-me-icon.component.ts:
--------------------------------------------------------------------------------
1 | import { Component } from '@angular/core';
2 |
3 | @Component({
4 | selector: 'app-assign-to-me-icon',
5 | template: `
11 |
14 |
19 |
22 | `,
23 | styles: [],
24 | standalone: true,
25 | })
26 | export class AssignToMeIconComponent {}
27 |
--------------------------------------------------------------------------------
/src/app/icons/avatar/avatar.component.ts:
--------------------------------------------------------------------------------
1 | import { Component } from '@angular/core';
2 |
3 | @Component({
4 | selector: 'app-avatar',
5 | template: `
12 |
18 | `,
19 | styles: [],
20 | standalone: true,
21 | })
22 | export class AvatarComponent {}
23 |
--------------------------------------------------------------------------------
/src/app/icons/badge/badge.component.ts:
--------------------------------------------------------------------------------
1 | import { Component } from '@angular/core';
2 |
3 | @Component({
4 | selector: 'app-badge',
5 | template: `
12 |
18 | `,
19 | styles: [],
20 | standalone: true,
21 | })
22 | export class BadgeComponent {}
23 |
--------------------------------------------------------------------------------
/src/app/icons/button/button.component.ts:
--------------------------------------------------------------------------------
1 | import { Component } from '@angular/core';
2 |
3 | @Component({
4 | selector: 'app-button',
5 | template: `
12 |
18 | `,
19 | styles: [],
20 | standalone: true,
21 | })
22 | export class ButtonComponent {}
23 |
--------------------------------------------------------------------------------
/src/app/icons/change-labels-icon/change-labels-icon.component.ts:
--------------------------------------------------------------------------------
1 | import { Component } from '@angular/core';
2 |
3 | @Component({
4 | selector: 'app-change-labels-icon',
5 | template: `
11 |
16 | `,
17 | styles: [],
18 | standalone: true,
19 | })
20 | export class ChangeLabelsIconComponent {}
21 |
--------------------------------------------------------------------------------
/src/app/icons/change-priority-icon/change-priority-icon.component.ts:
--------------------------------------------------------------------------------
1 | import { Component } from '@angular/core';
2 |
3 | @Component({
4 | selector: 'app-change-priority-icon',
5 | template: `
11 |
12 |
13 |
14 | `,
15 | styles: [],
16 | standalone: true,
17 | })
18 | export class ChangePriorityIconComponent {}
19 |
--------------------------------------------------------------------------------
/src/app/icons/change-status-icon/change-status-icon.component.ts:
--------------------------------------------------------------------------------
1 | import { Component } from '@angular/core';
2 |
3 | @Component({
4 | selector: 'app-change-status-icon',
5 | template: `
11 |
14 |
19 | `,
20 | styles: [],
21 | standalone: true,
22 | })
23 | export class ChangeStatusIconComponent {}
24 |
--------------------------------------------------------------------------------
/src/app/icons/clipboard-icon/clipboard-icon.component.ts:
--------------------------------------------------------------------------------
1 | import { Component } from '@angular/core';
2 |
3 | @Component({
4 | selector: 'app-clipboard-icon',
5 | template: ``,
22 | styles: [],
23 | standalone: true,
24 | })
25 | export class ClipboardIconComponent {}
26 |
--------------------------------------------------------------------------------
/src/app/icons/contact-icon/contact-icon.component.ts:
--------------------------------------------------------------------------------
1 | import { Component } from '@angular/core';
2 |
3 | @Component({
4 | selector: 'app-contact-icon',
5 | template: `
16 |
19 |
20 | `,
21 | styles: [],
22 | standalone: true,
23 | })
24 | export class ContactIconComponent {}
25 |
--------------------------------------------------------------------------------
/src/app/icons/copied/copied.component.ts:
--------------------------------------------------------------------------------
1 | import { Component, Input } from '@angular/core';
2 |
3 | @Component({
4 | selector: 'app-copied',
5 | template: `
13 |
19 | `,
20 | styles: [],
21 | standalone: true,
22 | })
23 | export class CopiedComponent {
24 | @Input() height = '16';
25 | @Input() width = '16';
26 | }
27 |
--------------------------------------------------------------------------------
/src/app/icons/copy/copy.component.ts:
--------------------------------------------------------------------------------
1 | import { Component, Input } from '@angular/core';
2 |
3 | @Component({
4 | selector: 'app-copy',
5 | template: `
12 |
18 |
24 | `,
25 | styles: [],
26 | standalone: true,
27 | })
28 | export class CopyComponent {
29 | @Input() height = '16';
30 | @Input() width = '16';
31 | }
32 |
--------------------------------------------------------------------------------
/src/app/icons/docs-icon/docs-icon.component.ts:
--------------------------------------------------------------------------------
1 | import { Component } from '@angular/core';
2 |
3 | @Component({
4 | selector: 'app-docs-icon',
5 | template: `
16 |
17 |
18 |
19 |
20 |
21 | `,
22 | styles: [],
23 | standalone: true,
24 | })
25 | export class DocsIconComponent {}
26 |
--------------------------------------------------------------------------------
/src/app/icons/feedback-icon/feedback-icon.component.ts:
--------------------------------------------------------------------------------
1 | import { Component } from '@angular/core';
2 |
3 | @Component({
4 | selector: 'app-feedback-icon',
5 | template: `
16 |
19 | `,
20 | styles: [],
21 | standalone: true,
22 | })
23 | export class FeedbackIconComponent {}
24 |
--------------------------------------------------------------------------------
/src/app/icons/figma/figma.component.ts:
--------------------------------------------------------------------------------
1 | import { Component } from '@angular/core';
2 |
3 | @Component({
4 | selector: 'app-figma',
5 | template: `
11 |
15 |
19 |
23 |
27 |
28 | `,
29 | styles: [],
30 | standalone: true,
31 | })
32 | export class FigmaComponent {}
33 |
--------------------------------------------------------------------------------
/src/app/icons/finder-icon/finder-icon.component.ts:
--------------------------------------------------------------------------------
1 | import { Component } from '@angular/core';
2 |
3 | @Component({
4 | selector: 'app-finder-icon',
5 | template: `
12 |
19 | `,
20 | styles: [],
21 | standalone: true,
22 | })
23 | export class FinderIconComponent {}
24 |
--------------------------------------------------------------------------------
/src/app/icons/framer/framer.component.ts:
--------------------------------------------------------------------------------
1 | import { Component } from '@angular/core';
2 |
3 | @Component({
4 | selector: 'app-framer-icon',
5 | template: `
6 |
10 | `,
11 | styles: [],
12 | standalone: true,
13 | })
14 | export class FramerIconComponent {}
15 |
--------------------------------------------------------------------------------
/src/app/icons/github/github.component.ts:
--------------------------------------------------------------------------------
1 | import { Component } from '@angular/core';
2 |
3 | @Component({
4 | selector: 'app-github',
5 | template: `
12 |
16 | `,
17 | styles: [],
18 | standalone: true,
19 | })
20 | export class GithubComponent {}
21 |
--------------------------------------------------------------------------------
/src/app/icons/hammer-icon/hammer-icon.component.ts:
--------------------------------------------------------------------------------
1 | import { Component } from '@angular/core';
2 |
3 | @Component({
4 | selector: 'app-hammer-icon',
5 | template: ``,
22 | styles: [],
23 | standalone: true,
24 | })
25 | export class HammerIconComponent {}
26 |
--------------------------------------------------------------------------------
/src/app/icons/icons.module.ts:
--------------------------------------------------------------------------------
1 | import { NgModule } from '@angular/core';
2 | import { CommonModule } from '@angular/common';
3 | import { SearchComponent } from './search/search.component';
4 | import { AvatarComponent } from './avatar/avatar.component';
5 | import { BadgeComponent } from './badge/badge.component';
6 | import { ButtonComponent } from './button/button.component';
7 | import { CopiedComponent } from './copied/copied.component';
8 | import { CopyComponent } from './copy/copy.component';
9 | import { FramerIconComponent } from './framer/framer.component';
10 | import { GithubComponent } from './github/github.component';
11 | import { InputComponent } from './input/input.component';
12 | import { LayoutComponent } from './layout/layout.component';
13 | import { LinearIconComponent } from './linear-icon/linear-icon.component';
14 | import { RadioComponent } from './radio/radio.component';
15 | import { RaycastIconComponent } from './raycast-icon/raycast-icon.component';
16 | import { SliderComponent } from './slider/slider.component';
17 | import { VercelIconComponent } from './vercel-icon/vercel-icon.component';
18 | import { AssignToIconComponent } from './assign-to-icon/assign-to-icon.component';
19 | import { AssignToMeIconComponent } from './assign-to-me-icon/assign-to-me-icon.component';
20 | import { ChangeLabelsIconComponent } from './change-labels-icon/change-labels-icon.component';
21 | import { ChangePriorityIconComponent } from './change-priority-icon/change-priority-icon.component';
22 | import { ChangeStatusIconComponent } from './change-status-icon/change-status-icon.component';
23 | import { RemoveLabelIconComponent } from './remove-label-icon/remove-label-icon.component';
24 | import { SetDueDateIconComponent } from './set-due-date-icon/set-due-date-icon.component';
25 | import { LogoComponent } from './logo/logo.component';
26 | import { ClipboardIconComponent } from './clipboard-icon/clipboard-icon.component';
27 | import { FigmaComponent } from './figma/figma.component';
28 | import { HammerIconComponent } from './hammer-icon/hammer-icon.component';
29 | import { RaycastDarkIconComponent } from './raycast-dark-icon/raycast-dark-icon.component';
30 | import { RaycastLightIconComponent } from './raycast-light-icon/raycast-light-icon.component';
31 | import { SlackIconComponent } from './slack-icon/slack-icon.component';
32 | import { YouTubeIconComponent } from './you-tube-icon/you-tube-icon.component';
33 | import { WindowIconComponent } from './window-icon/window-icon.component';
34 | import { FinderIconComponent } from './finder-icon/finder-icon.component';
35 | import { StarIconComponent } from './star-icon/star-icon.component';
36 | import { ContactIconComponent } from './contact-icon/contact-icon.component';
37 | import { DocsIconComponent } from './docs-icon/docs-icon.component';
38 | import { FeedbackIconComponent } from './feedback-icon/feedback-icon.component';
39 | import { PlusIconComponent } from './plus-icon/plus-icon.component';
40 | import { TeamsIconComponent } from './teams-icon/teams-icon.component';
41 |
42 | const ICONS = [
43 | AvatarComponent,
44 | BadgeComponent,
45 | ButtonComponent,
46 | CopiedComponent,
47 | CopyComponent,
48 | FramerIconComponent,
49 | GithubComponent,
50 | InputComponent,
51 | LayoutComponent,
52 | LinearIconComponent,
53 | RadioComponent,
54 | RaycastIconComponent,
55 | SearchComponent,
56 | SliderComponent,
57 | VercelIconComponent,
58 | AssignToIconComponent,
59 | AssignToMeIconComponent,
60 | ChangeLabelsIconComponent,
61 | ChangePriorityIconComponent,
62 | ChangeStatusIconComponent,
63 | RemoveLabelIconComponent,
64 | SetDueDateIconComponent,
65 | LogoComponent,
66 | ClipboardIconComponent,
67 | FigmaComponent,
68 | HammerIconComponent,
69 | RaycastDarkIconComponent,
70 | RaycastLightIconComponent,
71 | SlackIconComponent,
72 | YouTubeIconComponent,
73 | WindowIconComponent,
74 | FinderIconComponent,
75 | StarIconComponent,
76 | ContactIconComponent,
77 | DocsIconComponent,
78 | FeedbackIconComponent,
79 | PlusIconComponent,
80 | TeamsIconComponent,
81 | ];
82 |
83 | @NgModule({
84 | imports: [CommonModule, ...ICONS],
85 | exports: ICONS,
86 | })
87 | export class IconsModule {}
88 |
--------------------------------------------------------------------------------
/src/app/icons/input/input.component.ts:
--------------------------------------------------------------------------------
1 | import { Component } from '@angular/core';
2 |
3 | @Component({
4 | selector: 'app-input',
5 | template: `
12 |
18 | `,
19 | styles: [],
20 | standalone: true,
21 | })
22 | export class InputComponent {}
23 |
--------------------------------------------------------------------------------
/src/app/icons/layout/layout.component.ts:
--------------------------------------------------------------------------------
1 | import { Component } from '@angular/core';
2 |
3 | @Component({
4 | selector: 'app-layout',
5 | template: `
12 |
18 | `,
19 | styles: [],
20 | standalone: true,
21 | })
22 | export class LayoutComponent {}
23 |
--------------------------------------------------------------------------------
/src/app/icons/linear-icon/linear-icon.component.ts:
--------------------------------------------------------------------------------
1 | import { Component } from '@angular/core';
2 |
3 | @Component({
4 | selector: 'app-linear-icon',
5 | template: `
12 |
16 |
20 |
24 |
28 | `,
29 | styles: [],
30 | standalone: true,
31 | })
32 | export class LinearIconComponent {}
33 |
--------------------------------------------------------------------------------
/src/app/icons/logo/logo.component.ts:
--------------------------------------------------------------------------------
1 | import { Component, Input } from '@angular/core';
2 | import { NgStyle } from '@angular/common';
3 |
4 | @Component({
5 | selector: 'app-logo',
6 | template: `
7 |
8 |
9 |
10 |
11 |
12 |
13 |
`,
14 | standalone: true,
15 | imports: [NgStyle],
16 | })
17 | export class LogoComponent {
18 | @Input() size = 20;
19 | }
20 |
--------------------------------------------------------------------------------
/src/app/icons/plus-icon/plus-icon.component.ts:
--------------------------------------------------------------------------------
1 | import { Component } from '@angular/core';
2 |
3 | @Component({
4 | selector: 'app-plus-icon',
5 | template: `
16 |
17 |
18 | `,
19 | styles: [],
20 | standalone: true,
21 | })
22 | export class PlusIconComponent {}
23 |
--------------------------------------------------------------------------------
/src/app/icons/projects-icon/projects-icon.component.ts:
--------------------------------------------------------------------------------
1 | import { Component } from '@angular/core';
2 |
3 | @Component({
4 | selector: 'app-projects-icon',
5 | template: `
16 |
17 |
18 |
19 |
20 | `,
21 | styles: [],
22 | })
23 | export class ProjectsIconComponent {}
24 |
--------------------------------------------------------------------------------
/src/app/icons/radio/radio.component.ts:
--------------------------------------------------------------------------------
1 | import { Component } from '@angular/core';
2 |
3 | @Component({
4 | selector: 'app-radio',
5 | template: `
12 |
18 | `,
19 | styles: [],
20 | standalone: true,
21 | })
22 | export class RadioComponent {}
23 |
--------------------------------------------------------------------------------
/src/app/icons/raycast-dark-icon/raycast-dark-icon.component.ts:
--------------------------------------------------------------------------------
1 | import { Component } from '@angular/core';
2 |
3 | @Component({
4 | selector: 'app-raycast-dark-icon',
5 | template: `
12 |
18 | `,
19 | styles: [],
20 | standalone: true,
21 | })
22 | export class RaycastDarkIconComponent {}
23 |
--------------------------------------------------------------------------------
/src/app/icons/raycast-icon/raycast-icon.component.ts:
--------------------------------------------------------------------------------
1 | import { Component } from '@angular/core';
2 |
3 | @Component({
4 | selector: 'app-raycast-icon',
5 | template: `
12 |
18 | `,
19 | styles: [],
20 | standalone: true,
21 | })
22 | export class RaycastIconComponent {}
23 |
--------------------------------------------------------------------------------
/src/app/icons/raycast-light-icon/raycast-light-icon.component.ts:
--------------------------------------------------------------------------------
1 | import { Component } from '@angular/core';
2 |
3 | @Component({
4 | selector: 'app-raycast-light-icon',
5 | template: `
12 |
18 | `,
19 | styles: [],
20 | standalone: true,
21 | })
22 | export class RaycastLightIconComponent {}
23 |
--------------------------------------------------------------------------------
/src/app/icons/remove-label-icon/remove-label-icon.component.ts:
--------------------------------------------------------------------------------
1 | import { Component } from '@angular/core';
2 |
3 | @Component({
4 | selector: 'app-remove-label-icon',
5 | template: `
11 |
16 | `,
17 | styles: [],
18 | standalone: true,
19 | })
20 | export class RemoveLabelIconComponent {}
21 |
--------------------------------------------------------------------------------
/src/app/icons/search/search.component.ts:
--------------------------------------------------------------------------------
1 | import { Component } from '@angular/core';
2 |
3 | @Component({
4 | selector: 'app-search',
5 | template: `
6 |
14 |
19 |
20 | `,
21 | styles: [],
22 | standalone: true,
23 | })
24 | export class SearchComponent {}
25 |
--------------------------------------------------------------------------------
/src/app/icons/set-due-date-icon/set-due-date-icon.component.ts:
--------------------------------------------------------------------------------
1 | import { Component } from '@angular/core';
2 |
3 | @Component({
4 | selector: 'app-set-due-date-icon',
5 | template: `
11 |
16 | `,
17 | styles: [],
18 | standalone: true,
19 | })
20 | export class SetDueDateIconComponent {}
21 |
--------------------------------------------------------------------------------
/src/app/icons/slack-icon/slack-icon.component.ts:
--------------------------------------------------------------------------------
1 | import { Component } from '@angular/core';
2 |
3 | @Component({
4 | selector: 'app-slack-icon',
5 | template: `
11 |
15 |
19 |
23 |
27 |
31 |
35 |
39 |
43 | `,
44 | styles: [],
45 | standalone: true,
46 | })
47 | export class SlackIconComponent {}
48 |
--------------------------------------------------------------------------------
/src/app/icons/slider/slider.component.ts:
--------------------------------------------------------------------------------
1 | import { Component } from '@angular/core';
2 |
3 | @Component({
4 | selector: 'app-slider',
5 | template: `
12 |
18 | `,
19 | styles: [],
20 | standalone: true,
21 | })
22 | export class SliderComponent {}
23 |
--------------------------------------------------------------------------------
/src/app/icons/star-icon/star-icon.component.ts:
--------------------------------------------------------------------------------
1 | import { Component } from '@angular/core';
2 |
3 | @Component({
4 | selector: 'app-star-icon',
5 | template: `
12 |
19 | `,
20 | styles: [],
21 | standalone: true,
22 | })
23 | export class StarIconComponent {}
24 |
--------------------------------------------------------------------------------
/src/app/icons/teams-icon/teams-icon.component.ts:
--------------------------------------------------------------------------------
1 | import { Component } from '@angular/core';
2 |
3 | @Component({
4 | selector: 'app-teams-icon',
5 | template: `
16 |
17 |
18 |
19 |
20 | `,
21 | styles: [],
22 | standalone: true,
23 | })
24 | export class TeamsIconComponent {}
25 |
--------------------------------------------------------------------------------
/src/app/icons/vercel-icon/vercel-icon.component.ts:
--------------------------------------------------------------------------------
1 | import { Component } from '@angular/core';
2 |
3 | @Component({
4 | selector: 'app-vercel-icon',
5 | template: `
11 |
12 | `,
13 | styles: [],
14 | standalone: true,
15 | })
16 | export class VercelIconComponent {}
17 |
--------------------------------------------------------------------------------
/src/app/icons/window-icon/window-icon.component.ts:
--------------------------------------------------------------------------------
1 | import { Component } from '@angular/core';
2 |
3 | @Component({
4 | selector: 'app-window-icon',
5 | template: `
12 |
19 | `,
20 | styles: [],
21 | standalone: true,
22 | })
23 | export class WindowIconComponent {}
24 |
--------------------------------------------------------------------------------
/src/app/icons/you-tube-icon/you-tube-icon.component.ts:
--------------------------------------------------------------------------------
1 | import { Component } from '@angular/core';
2 |
3 | @Component({
4 | selector: 'app-you-tube-icon',
5 | template: `
11 |
15 |
16 | `,
17 | styles: [],
18 | standalone: true,
19 | })
20 | export class YouTubeIconComponent {}
21 |
--------------------------------------------------------------------------------
/src/app/sections/footer/footer.component.html:
--------------------------------------------------------------------------------
1 |
19 |
--------------------------------------------------------------------------------
/src/app/sections/footer/footer.component.ts:
--------------------------------------------------------------------------------
1 | import { Component } from '@angular/core';
2 |
3 | @Component({
4 | selector: 'app-footer',
5 | templateUrl: './footer.component.html',
6 | standalone: true,
7 | })
8 | export class FooterComponent {
9 |
10 | }
11 |
--------------------------------------------------------------------------------
/src/app/sections/meta/meta.component.html:
--------------------------------------------------------------------------------
1 |
43 |
--------------------------------------------------------------------------------
/src/app/sections/meta/meta.component.ts:
--------------------------------------------------------------------------------
1 | import { Component, Input } from '@angular/core';
2 | import { CopyComponent } from '../../icons/copy/copy.component';
3 | import { GithubComponent } from '../../icons/github/github.component';
4 | import { CopiedComponent } from '../../icons/copied/copied.component';
5 |
6 |
7 | @Component({
8 | selector: 'app-meta',
9 | templateUrl: './meta.component.html',
10 | standalone: true,
11 | imports: [
12 | CopiedComponent,
13 | GithubComponent,
14 | CopyComponent
15 | ],
16 | })
17 | export class MetaComponent {
18 | @Input() version: string | null = '0.0.1';
19 | copied = false;
20 | async copyInstall() {
21 | try {
22 | await navigator.clipboard.writeText(`npm i @ngxpert/cmdk`);
23 | this.copied = true;
24 | setTimeout(() => {
25 | this.copied = false;
26 | }, 2000);
27 | } catch (e) {}
28 | }
29 | }
30 |
--------------------------------------------------------------------------------
/src/app/sections/theme-switcher/theme-switcher.component.html:
--------------------------------------------------------------------------------
1 |
2 | @for (theme of allThemes; track theme) {
3 |
7 |
8 | {{ theme.label }}
9 | @if (theme.value === currentTheme) {
10 |
11 | }
12 |
13 | }
14 |
15 |
16 |
17 | @switch (currentTheme) {
18 | @case ('framer') {
19 |
22 | }
23 | @case ('linear') {
24 |
27 | }
28 | @case ('raycast') {
29 |
32 | }
33 | @case ('vercel') {
34 |
37 | }
38 | }
39 |
40 |
--------------------------------------------------------------------------------
/src/app/sections/theme-switcher/theme-switcher.component.ts:
--------------------------------------------------------------------------------
1 | import { Component } from '@angular/core';
2 | import { Content, DynamicViewDirective } from '@ngneat/overview';
3 | import { FramerIconComponent } from 'src/app/icons/framer/framer.component';
4 | import { LinearIconComponent } from 'src/app/icons/linear-icon/linear-icon.component';
5 | import { RaycastIconComponent } from 'src/app/icons/raycast-icon/raycast-icon.component';
6 | import { VercelIconComponent } from 'src/app/icons/vercel-icon/vercel-icon.component';
7 | import { Theme } from 'src/types';
8 | import { VercelComponent } from '../../themes/vercel/vercel.component';
9 | import { RaycastComponent } from '../../themes/raycast/raycast.component';
10 | import { LinearComponent } from '../../themes/linear/linear.component';
11 | import { FramerComponent } from '../../themes/framer/framer.component';
12 |
13 |
14 | @Component({
15 | selector: 'app-theme-switcher',
16 | templateUrl: './theme-switcher.component.html',
17 | standalone: true,
18 | imports: [
19 | FramerComponent,
20 | LinearComponent,
21 | RaycastComponent,
22 | VercelComponent,
23 | DynamicViewDirective
24 | ],
25 | })
26 | export class ThemeSwitcherComponent {
27 | currentTheme: Theme = 'raycast';
28 | allThemes: Array<{ label: string; icon: Content; value: Theme }> = [
29 | { label: 'Raycast', value: 'raycast', icon: RaycastIconComponent },
30 | { label: 'Linear', value: 'linear', icon: LinearIconComponent },
31 | { label: 'Vercel', value: 'vercel', icon: VercelIconComponent },
32 | { label: 'Framer', value: 'framer', icon: FramerIconComponent },
33 | ];
34 |
35 | setCurrentTheme(theme: Theme) {
36 | this.currentTheme = theme;
37 | }
38 | }
39 |
--------------------------------------------------------------------------------
/src/app/shared/components/code-block/code-block.component.html:
--------------------------------------------------------------------------------
1 |
2 |
3 | @if (copied) {
4 |
5 | } @else {
6 |
7 | }
8 |
9 |
10 | {{ snippet }}
11 |
12 |
13 |
--------------------------------------------------------------------------------
/src/app/shared/components/code-block/code-block.component.scss:
--------------------------------------------------------------------------------
1 | code,
2 | code[class*='language-'],
3 | pre,
4 | pre[class*='language-'] {
5 | font-family: 'JetBrains Mono', monospace !important;
6 | font-size: 14px !important;
7 | position: relative;
8 | }
9 |
10 | .copy-button {
11 | position: absolute;
12 | top: 8px;
13 | right: 8px;
14 | height: 40px;
15 | color: #fff;
16 | border-radius: 9999px;
17 | font-size: 14px;
18 | display: flex;
19 | width: 60px;
20 | align-items: center;
21 | justify-content: center;
22 | z-index: 1;
23 | cursor: pointer;
24 | }
--------------------------------------------------------------------------------
/src/app/shared/components/code-block/code-block.component.ts:
--------------------------------------------------------------------------------
1 | import {
2 | Component,
3 | ElementRef,
4 | Input,
5 | OnChanges,
6 | ViewChild,
7 | } from '@angular/core';
8 | import { CodeHighlightService } from '../../../core/services/code-highlight.service';
9 | import { CopyComponent } from '../../../icons/copy/copy.component';
10 | import { CopiedComponent } from '../../../icons/copied/copied.component';
11 |
12 |
13 | @Component({
14 | selector: 'app-code-block',
15 | templateUrl: './code-block.component.html',
16 | styleUrls: ['./code-block.component.scss'],
17 | standalone: true,
18 | imports: [
19 | CopiedComponent,
20 | CopyComponent
21 | ],
22 | })
23 | export class CodeBlockComponent implements OnChanges {
24 | @Input() language = 'typescript';
25 | @Input()
26 | containerClass!: string;
27 | @Input()
28 | snippet!: string;
29 |
30 | @ViewChild('code') codeTemplateRef!: ElementRef;
31 | copied = false;
32 |
33 | constructor(private codeHighlightService: CodeHighlightService) {}
34 |
35 | ngOnChanges(): void {
36 | if (this.codeTemplateRef && this.codeTemplateRef.nativeElement) {
37 | setTimeout(() => {
38 | this.codeHighlightService.highlightElement(
39 | this.codeTemplateRef.nativeElement
40 | );
41 | });
42 | }
43 | }
44 |
45 | async copySnippet() {
46 | try {
47 | await navigator.clipboard.writeText(this.snippet);
48 | this.copied = true;
49 | setTimeout(() => {
50 | this.copied = false;
51 | }, 2000);
52 | } catch (e) {}
53 | }
54 | }
55 |
--------------------------------------------------------------------------------
/src/app/shared/shared.module.ts:
--------------------------------------------------------------------------------
1 | import { NgModule } from '@angular/core';
2 | import { CommonModule } from '@angular/common';
3 | import { CodeBlockComponent } from './components/code-block/code-block.component';
4 | import { IconsModule } from '../icons/icons.module';
5 |
6 | const COMPONENTS = [CodeBlockComponent];
7 |
8 | @NgModule({
9 | imports: [CommonModule, IconsModule, ...COMPONENTS],
10 | exports: COMPONENTS,
11 | })
12 | export class SharedModule {}
13 |
--------------------------------------------------------------------------------
/src/app/tests/group.component.ts:
--------------------------------------------------------------------------------
1 | import { Component } from '@angular/core';
2 |
3 | @Component({
4 | selector: 'app-group',
5 | template: `
6 |
7 |
13 |
14 | No results.
15 |
16 | Giraffe
17 | Chicken
18 |
19 |
20 |
21 | A
22 | B
23 | Z
24 |
25 |
26 | @if (search) {
27 |
28 | One
29 | Two
30 | Three
31 |
32 | }
33 |
34 |
35 | `,
36 | })
37 | export class GroupComponent {
38 | search = '';
39 |
40 | setSearch(ev: Event) {
41 | this.search = (ev.target as HTMLInputElement).value;
42 | }
43 | }
44 |
--------------------------------------------------------------------------------
/src/app/tests/item-advanced.component.ts:
--------------------------------------------------------------------------------
1 | import { Component } from '@angular/core';
2 |
3 | @Component({
4 | selector: 'app-item-advanced',
5 | template: `
6 |
7 |
8 | Increment count
9 |
10 |
11 |
12 |
13 | No results.
14 |
15 | Item A {{ count }}
16 |
17 |
18 | Item B {{ count }}
19 |
20 |
21 |
22 |
23 | `,
24 | })
25 | export class ItemAdvancedComponent {
26 | count = 0;
27 |
28 | setCount() {
29 | this.count++;
30 | }
31 | }
32 |
--------------------------------------------------------------------------------
/src/app/tests/item.component.ts:
--------------------------------------------------------------------------------
1 | import { Component } from '@angular/core';
2 |
3 | @Component({
4 | selector: 'app-item',
5 | template: `
6 |
7 |
8 | Toggle item B
9 |
10 |
11 |
12 | Toggle item A
13 |
14 |
15 |
16 | Toggle many items
17 |
18 |
19 |
20 |
21 |
22 | No results.
23 | @if (!unmount) {
24 | A
25 | }
26 | @if (many) {
27 | 1
28 | 2
29 | 3
30 | }
31 | @if (mount) {
32 | B
33 | }
34 |
35 |
36 |
37 | `,
38 | })
39 | export class ItemComponent {
40 | mount = false;
41 | unmount = false;
42 | many = false;
43 | }
44 |
--------------------------------------------------------------------------------
/src/app/tests/keybindings.component.ts:
--------------------------------------------------------------------------------
1 | import { Component } from '@angular/core';
2 |
3 | @Component({
4 | selector: 'app-keybindings',
5 | template: `
6 |
7 |
8 |
9 | No results.
10 |
11 | Disabled
12 |
13 | First
14 |
15 |
16 | A
17 | B
18 | Z
19 |
20 |
21 |
22 | Apple
23 | Banana
24 | Orange
25 | Dragon Fruit
26 | Pear
27 |
28 |
29 | Last
30 |
31 | Disabled 3
32 |
33 |
34 | `,
35 | styles: [],
36 | })
37 | export class KeybindingsComponent {}
38 |
--------------------------------------------------------------------------------
/src/app/tests/props.component.ts:
--------------------------------------------------------------------------------
1 | import { Component, Input } from '@angular/core';
2 |
3 | @Component({
4 | selector: 'app-props',
5 | template: `
6 |
7 |
{{ value }}
8 |
{{ search }}
9 |
10 |
11 | Change value
12 |
13 |
14 | Change search value
15 |
16 |
17 | @if (customFilter) {
18 |
23 |
29 |
30 | ant
31 | anteater
32 |
33 |
34 | }
35 | @if (!customFilter) {
36 |
40 |
46 |
47 | ant
48 | anteater
49 |
50 |
51 | }
52 |
53 | `,
54 | styles: [],
55 | })
56 | export class PropsComponent {
57 | @Input() customFilter = false;
58 | value = 'ant';
59 | search = '';
60 | setValue(value: string) {
61 | this.value = value;
62 | }
63 | setSearch(search: string | Event) {
64 | if (typeof search === 'string') {
65 | this.search = search;
66 | } else {
67 | this.search = (search.target as HTMLInputElement).value;
68 | }
69 | }
70 | customFilterFn(item: string, search: string) {
71 | return item.toLowerCase().endsWith(search.toLowerCase());
72 | }
73 | }
74 |
--------------------------------------------------------------------------------
/src/app/tests/tests.module.ts:
--------------------------------------------------------------------------------
1 | import { NgModule } from '@angular/core';
2 | import { CommonModule } from '@angular/common';
3 | import { GroupComponent } from './group.component';
4 | import { CmdkModule } from '@ngxpert/cmdk';
5 | import { ItemComponent } from './item.component';
6 | import { ItemAdvancedComponent } from './item-advanced.component';
7 | import { KeybindingsComponent } from './keybindings.component';
8 | import { PropsComponent } from './props.component';
9 |
10 | @NgModule({
11 | declarations: [
12 | GroupComponent,
13 | ItemComponent,
14 | ItemAdvancedComponent,
15 | KeybindingsComponent,
16 | PropsComponent,
17 | ],
18 | imports: [CommonModule, CmdkModule.forRoot()],
19 | exports: [
20 | GroupComponent,
21 | ItemComponent,
22 | ItemAdvancedComponent,
23 | KeybindingsComponent,
24 | PropsComponent,
25 | ],
26 | })
27 | export class TestsModule {}
28 |
--------------------------------------------------------------------------------
/src/app/themes/framer/framer.component.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
11 |
12 |
13 |
14 |
15 |
16 | @for (component of components; track component) {
17 |
21 |
22 |
23 |
24 |
25 | {{ component.value }}
26 | {{
27 | component.subTitle
28 | }}
29 |
30 |
31 | }
32 |
33 |
34 |
35 |
36 | @switch (value) {
37 | @case ('Button') {
38 |
39 | Button
40 |
41 | }
42 | @case ('Input') {
43 |
44 |
45 |
46 | }
47 | @case ('Radio') {
48 |
49 |
50 |
52 | Huey
53 |
54 |
55 | }
56 | @case ('Badge') {
57 |
60 | }
61 | @case ('Slider') {
62 |
65 | }
66 | @case ('Avatar') {
67 |
68 |
69 |
70 | }
71 | @case ('Container') {
72 |
75 | }
76 | }
77 |
78 |
79 |
80 |
81 |
82 |
83 |
--------------------------------------------------------------------------------
/src/app/themes/framer/framer.component.ts:
--------------------------------------------------------------------------------
1 | import { Component } from '@angular/core';
2 | import { Content, DynamicViewDirective } from '@ngneat/overview';
3 | import { AvatarComponent } from 'src/app/icons/avatar/avatar.component';
4 | import { BadgeComponent } from 'src/app/icons/badge/badge.component';
5 | import { ButtonComponent } from 'src/app/icons/button/button.component';
6 | import { InputComponent } from 'src/app/icons/input/input.component';
7 | import { LayoutComponent } from 'src/app/icons/layout/layout.component';
8 | import { RadioComponent } from 'src/app/icons/radio/radio.component';
9 | import { SliderComponent } from 'src/app/icons/slider/slider.component';
10 |
11 | import {
12 | CommandComponent,
13 | GroupComponent,
14 | InputDirective,
15 | ItemDirective,
16 | ListComponent,
17 | } from '@ngxpert/cmdk';
18 | import { SearchComponent } from 'src/app/icons/search/search.component';
19 |
20 | @Component({
21 | selector: 'app-framer',
22 | templateUrl: './framer.component.html',
23 | standalone: true,
24 | imports: [
25 | CommandComponent,
26 | SearchComponent,
27 | InputDirective,
28 | ListComponent,
29 | GroupComponent,
30 | ItemDirective,
31 | DynamicViewDirective
32 | ],
33 | })
34 | export class FramerComponent {
35 | value = 'Button';
36 | readonly components: Array<{
37 | value: string;
38 | icon: Content;
39 | subTitle: string;
40 | }> = [
41 | {
42 | value: 'Button',
43 | icon: ButtonComponent,
44 | subTitle: 'Trigger actions',
45 | },
46 | {
47 | value: 'Input',
48 | icon: InputComponent,
49 | subTitle: 'Retrieve user input',
50 | },
51 | {
52 | value: 'Radio',
53 | icon: RadioComponent,
54 | subTitle: 'Single choice input',
55 | },
56 | {
57 | value: 'Badge',
58 | icon: BadgeComponent,
59 | subTitle: 'Annotate context',
60 | },
61 | {
62 | value: 'Slider',
63 | icon: SliderComponent,
64 | subTitle: 'Free range picker',
65 | },
66 | {
67 | value: 'Avatar',
68 | icon: AvatarComponent,
69 | subTitle: 'Illustrate the user',
70 | },
71 | {
72 | value: 'Container',
73 | icon: LayoutComponent,
74 | subTitle: 'Lay out the items',
75 | },
76 | ];
77 |
78 | setValue(value: string) {
79 | this.value = value;
80 | }
81 | }
82 |
--------------------------------------------------------------------------------
/src/app/themes/linear/linear.component.html:
--------------------------------------------------------------------------------
1 |
2 |
3 | Issue - FUN-343
4 |
5 |
6 | No results found.
7 | @for (item of items; track item) {
8 |
9 |
10 | {{ item.label }}
11 |
12 | @for (key of item.shortcut; track key) {
13 | {{ key }}
14 | }
15 |
16 |
17 | }
18 |
19 |
20 |
21 |
--------------------------------------------------------------------------------
/src/app/themes/linear/linear.component.ts:
--------------------------------------------------------------------------------
1 | import { Component } from '@angular/core';
2 | import { AssignToIconComponent } from 'src/app/icons/assign-to-icon/assign-to-icon.component';
3 | import { AssignToMeIconComponent } from 'src/app/icons/assign-to-me-icon/assign-to-me-icon.component';
4 | import { ChangeLabelsIconComponent } from 'src/app/icons/change-labels-icon/change-labels-icon.component';
5 | import { ChangePriorityIconComponent } from 'src/app/icons/change-priority-icon/change-priority-icon.component';
6 | import { ChangeStatusIconComponent } from 'src/app/icons/change-status-icon/change-status-icon.component';
7 | import { RemoveLabelIconComponent } from 'src/app/icons/remove-label-icon/remove-label-icon.component';
8 | import { SetDueDateIconComponent } from 'src/app/icons/set-due-date-icon/set-due-date-icon.component';
9 | import { ItemDirective } from '../../../../projects/ngxpert/cmdk/src/lib/directives/item/item.directive';
10 |
11 | import {
12 | CommandComponent,
13 | EmptyDirective,
14 | InputDirective,
15 | ListComponent,
16 | } from '@ngxpert/cmdk';
17 | import { DynamicViewDirective } from '@ngneat/overview';
18 |
19 | @Component({
20 | selector: 'app-linear',
21 | templateUrl: './linear.component.html',
22 | standalone: true,
23 | imports: [
24 | CommandComponent,
25 | InputDirective,
26 | ListComponent,
27 | EmptyDirective,
28 | ItemDirective,
29 | DynamicViewDirective
30 | ],
31 | })
32 | export class LinearComponent {
33 | readonly items = [
34 | {
35 | icon: AssignToIconComponent,
36 | label: 'Assign to...',
37 | shortcut: ['A'],
38 | },
39 | {
40 | icon: AssignToMeIconComponent,
41 | label: 'Assign to me',
42 | shortcut: ['I'],
43 | },
44 | {
45 | icon: ChangeStatusIconComponent,
46 | label: 'Change status...',
47 | shortcut: ['S'],
48 | },
49 | {
50 | icon: ChangePriorityIconComponent,
51 | label: 'Change priority...',
52 | shortcut: ['P'],
53 | },
54 | {
55 | icon: ChangeLabelsIconComponent,
56 | label: 'Change labels...',
57 | shortcut: ['L'],
58 | },
59 | {
60 | icon: RemoveLabelIconComponent,
61 | label: 'Remove label...',
62 | shortcut: ['⇧', 'L'],
63 | },
64 | {
65 | icon: SetDueDateIconComponent,
66 | label: 'Set due date...',
67 | shortcut: ['⇧', 'D'],
68 | },
69 | ];
70 | }
71 |
--------------------------------------------------------------------------------
/src/app/themes/raycast/raycast.component.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 | No results found.
8 |
9 |
10 |
11 |
12 |
13 | Linear
14 | Application
15 |
16 |
17 |
18 |
19 |
20 | Figma
21 | Application
22 |
23 |
24 |
25 |
26 |
27 | Slack
28 | Application
29 |
30 |
31 |
32 |
33 |
34 | YouTube
35 | Application
36 |
37 |
38 |
39 |
40 |
41 | Raycast
42 | Application
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 | Clipboard History
51 | Command
52 |
53 |
54 |
55 | Import Extension
56 | Command
57 |
58 |
59 |
60 | Manage Extensions
61 | Command
62 |
63 |
64 |
65 |
66 |
79 |
80 |
--------------------------------------------------------------------------------
/src/app/themes/raycast/raycast.component.ts:
--------------------------------------------------------------------------------
1 | import { Component } from '@angular/core';
2 | import { SubCommandComponent } from './sub-command/sub-command.component';
3 | import { RaycastLightIconComponent } from '../../icons/raycast-light-icon/raycast-light-icon.component';
4 | import { HammerIconComponent } from '../../icons/hammer-icon/hammer-icon.component';
5 | import { ClipboardIconComponent } from '../../icons/clipboard-icon/clipboard-icon.component';
6 | import { RaycastIconComponent } from '../../icons/raycast-icon/raycast-icon.component';
7 | import { YouTubeIconComponent } from '../../icons/you-tube-icon/you-tube-icon.component';
8 | import { SlackIconComponent } from '../../icons/slack-icon/slack-icon.component';
9 | import { FigmaComponent } from '../../icons/figma/figma.component';
10 | import { LinearIconComponent } from '../../icons/linear-icon/linear-icon.component';
11 | import { LogoComponent } from '../../icons/logo/logo.component';
12 | import {
13 | CommandComponent,
14 | EmptyDirective,
15 | GroupComponent,
16 | InputDirective,
17 | ItemDirective,
18 | ListComponent,
19 | } from '@ngxpert/cmdk';
20 |
21 | @Component({
22 | selector: 'app-raycast',
23 | templateUrl: './raycast.component.html',
24 | standalone: true,
25 | imports: [
26 | CommandComponent,
27 | InputDirective,
28 | ListComponent,
29 | EmptyDirective,
30 | GroupComponent,
31 | ItemDirective,
32 | LogoComponent,
33 | LinearIconComponent,
34 | FigmaComponent,
35 | SlackIconComponent,
36 | YouTubeIconComponent,
37 | RaycastIconComponent,
38 | ClipboardIconComponent,
39 | HammerIconComponent,
40 | RaycastLightIconComponent,
41 | SubCommandComponent,
42 | ],
43 | })
44 | export class RaycastComponent {
45 | value = 'Linear';
46 | setValue(value: string) {
47 | this.value = value;
48 | }
49 | }
50 |
--------------------------------------------------------------------------------
/src/app/themes/raycast/sub-command-dialog/sub-command-dialog.component.html:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/app/themes/raycast/sub-command-dialog/sub-command-dialog.component.scss:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ngxpert/cmdk/3fc6fb1cf1c19911a475ed9d9b9321b5ba2599a3/src/app/themes/raycast/sub-command-dialog/sub-command-dialog.component.scss
--------------------------------------------------------------------------------
/src/app/themes/raycast/sub-command-dialog/sub-command-dialog.component.ts:
--------------------------------------------------------------------------------
1 | import { AfterViewInit, Component, ElementRef, Input, ViewChild, forwardRef } from '@angular/core';
2 | import { Content, DynamicViewDirective } from '@ngneat/overview';
3 | import { FinderIconComponent } from 'src/app/icons/finder-icon/finder-icon.component';
4 | import { StarIconComponent } from 'src/app/icons/star-icon/star-icon.component';
5 | import { WindowIconComponent } from 'src/app/icons/window-icon/window-icon.component';
6 |
7 | import {
8 | CommandComponent,
9 | GroupComponent,
10 | InputDirective,
11 | ItemDirective,
12 | ListComponent,
13 | } from '@ngxpert/cmdk';
14 |
15 | export interface SubCommandDialogData {
16 | selectedValue: string;
17 | }
18 |
19 | @Component({
20 | selector: 'app-sub-command-dialog',
21 | templateUrl: './sub-command-dialog.component.html',
22 | standalone: true,
23 | imports: [
24 | CommandComponent,
25 | ListComponent,
26 | GroupComponent,
27 | ItemDirective,
28 | forwardRef(() => RayCastSubItemComponent),
29 | InputDirective,
30 | DynamicViewDirective
31 | ],
32 | })
33 | export class SubCommandDialogComponent implements AfterViewInit {
34 | @Input() value = '';
35 | @ViewChild('input') input!: ElementRef;
36 |
37 | readonly items: Array<{ label: string; shortcuts: string; icon: Content }> = [
38 | {
39 | label: 'Open Application',
40 | shortcuts: '↵',
41 | icon: WindowIconComponent,
42 | },
43 | {
44 | label: 'Show in Finder',
45 | shortcuts: '⌘ ↵',
46 | icon: FinderIconComponent,
47 | },
48 | {
49 | label: 'Show Info in Finder',
50 | shortcuts: '⌘ I',
51 | icon: FinderIconComponent,
52 | },
53 | {
54 | label: 'Add to Favorites',
55 | shortcuts: '⌘ ⇧ F',
56 | icon: StarIconComponent,
57 | },
58 | ];
59 |
60 | ngAfterViewInit() {
61 | this.input.nativeElement.focus();
62 | }
63 | }
64 |
65 | @Component({
66 | selector: 'app-raycast-sub-item',
67 | template: `
68 |
73 | `,
74 | styles: [
75 | `
76 | :host {
77 | margin-left: auto;
78 | }
79 | `,
80 | ],
81 | standalone: true,
82 | imports: [],
83 | })
84 | export class RayCastSubItemComponent {
85 | @Input() shortcut = '';
86 | }
87 |
--------------------------------------------------------------------------------
/src/app/themes/raycast/sub-command/sub-command.component.html:
--------------------------------------------------------------------------------
1 |
2 | Actions
3 | ⌘
4 | K
5 |
6 |
11 |
12 |
--------------------------------------------------------------------------------
/src/app/themes/raycast/sub-command/sub-command.component.scss:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ngxpert/cmdk/3fc6fb1cf1c19911a475ed9d9b9321b5ba2599a3/src/app/themes/raycast/sub-command/sub-command.component.scss
--------------------------------------------------------------------------------
/src/app/themes/raycast/sub-command/sub-command.component.ts:
--------------------------------------------------------------------------------
1 | import { Component, Input, OnDestroy, OnInit } from '@angular/core';
2 | import { SubCommandDialogComponent } from '../sub-command-dialog/sub-command-dialog.component';
3 | import { CdkOverlayOrigin, CdkConnectedOverlay } from '@angular/cdk/overlay';
4 |
5 | @Component({
6 | selector: 'app-sub-command',
7 | templateUrl: './sub-command.component.html',
8 | styleUrls: ['./sub-command.component.scss'],
9 | standalone: true,
10 | imports: [
11 | CdkOverlayOrigin,
12 | CdkConnectedOverlay,
13 | SubCommandDialogComponent,
14 | ],
15 | })
16 | export class SubCommandComponent implements OnInit, OnDestroy {
17 | @Input() value: string = '';
18 | isDialogOpen = false;
19 |
20 | listener(e: KeyboardEvent) {
21 | if (e.key === 'k' && (e.metaKey || e.altKey)) {
22 | e.preventDefault();
23 | if (this.isDialogOpen) {
24 | this.isDialogOpen = false;
25 | } else {
26 | this.isDialogOpen = true;
27 | }
28 | }
29 | }
30 |
31 | ngOnInit() {
32 | document.addEventListener('keydown', (ev) => this.listener(ev));
33 | }
34 |
35 | ngOnDestroy() {
36 | document.removeEventListener('keydown', (ev) => this.listener(ev));
37 | }
38 | }
39 |
--------------------------------------------------------------------------------
/src/app/themes/vercel/vercel.component.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | @for (p of pages; track p) {
5 |
6 | {{ p }}
7 |
8 | }
9 |
10 |
16 |
17 | No results found.
18 | @if (activePage === 'home') {
19 | @for (group of groups; track group) {
20 |
21 | @for (item of group.items; track item) {
22 | @if (item.separatorOnTop) {
23 |
24 | }
25 |
30 |
31 | {{ item.label }}
32 | @if (item.shortcut) {
33 |
34 | @for (key of item.shortcut.split(' '); track key) {
35 | {{ key }}
36 | }
37 |
38 | }
39 |
40 | }
41 |
42 | }
43 | }
44 | @if (activePage === 'projects') {
45 | @for (item of projectItems; track item; let i = $index) {
46 |
50 | Project {{ i + 1 }}
51 |
52 | }
53 | }
54 |
55 |
56 |
57 |
--------------------------------------------------------------------------------
/src/app/themes/vercel/vercel.component.scss:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ngxpert/cmdk/3fc6fb1cf1c19911a475ed9d9b9321b5ba2599a3/src/app/themes/vercel/vercel.component.scss
--------------------------------------------------------------------------------
/src/app/themes/vercel/vercel.component.ts:
--------------------------------------------------------------------------------
1 | import { Component, ElementRef, ViewChild } from '@angular/core';
2 | import { Content, DynamicViewDirective } from '@ngneat/overview';
3 | import { ContactIconComponent } from 'src/app/icons/contact-icon/contact-icon.component';
4 | import { DocsIconComponent } from 'src/app/icons/docs-icon/docs-icon.component';
5 | import { FeedbackIconComponent } from 'src/app/icons/feedback-icon/feedback-icon.component';
6 | import { PlusIconComponent } from 'src/app/icons/plus-icon/plus-icon.component';
7 | import { ProjectsIconComponent } from 'src/app/icons/projects-icon/projects-icon.component';
8 | import { TeamsIconComponent } from 'src/app/icons/teams-icon/teams-icon.component';
9 | import { NgStyle } from '@angular/common';
10 | import {
11 | CommandComponent,
12 | EmptyDirective,
13 | GroupComponent,
14 | InputDirective,
15 | ItemDirective,
16 | ListComponent,
17 | SeparatorComponent,
18 | } from '@ngxpert/cmdk';
19 |
20 | @Component({
21 | selector: 'app-vercel',
22 | templateUrl: './vercel.component.html',
23 | standalone: true,
24 | imports: [
25 | CommandComponent,
26 | NgStyle,
27 | InputDirective,
28 | ListComponent,
29 | EmptyDirective,
30 | GroupComponent,
31 | SeparatorComponent,
32 | ItemDirective,
33 | DynamicViewDirective
34 | ],
35 | })
36 | export class VercelComponent {
37 | @ViewChild('cmdkCommand') cmdkCommand!: ElementRef;
38 | inputValue = '';
39 | pages: Array = ['home'];
40 | readonly groups: Array<{
41 | group: string;
42 | items: Array<{
43 | label: string;
44 | itemSelected?: () => void;
45 | icon: Content;
46 | shortcut: string;
47 | separatorOnTop?: boolean;
48 | }>;
49 | }> = [
50 | {
51 | group: 'Projects',
52 | items: [
53 | {
54 | label: 'Search Projects...',
55 | itemSelected: () => {
56 | this.searchProjects();
57 | },
58 | icon: ProjectsIconComponent,
59 | shortcut: 'S P',
60 | },
61 | {
62 | label: 'Create New Project',
63 | icon: PlusIconComponent,
64 | shortcut: '',
65 | },
66 | ],
67 | },
68 | {
69 | group: 'Teams',
70 | items: [
71 | {
72 | label: 'Search Teams...',
73 | icon: TeamsIconComponent,
74 | shortcut: '⇧ P',
75 | },
76 | {
77 | label: 'Create New Team',
78 | icon: PlusIconComponent,
79 | shortcut: '',
80 | },
81 | ],
82 | },
83 | {
84 | group: 'Help',
85 | items: [
86 | {
87 | label: 'Search Docs...',
88 | icon: DocsIconComponent,
89 | shortcut: '⇧ D',
90 | },
91 | {
92 | label: 'Send Feedback...',
93 | icon: FeedbackIconComponent,
94 | shortcut: '',
95 | },
96 | {
97 | label: 'Contact Support',
98 | icon: ContactIconComponent,
99 | shortcut: '',
100 | separatorOnTop: true,
101 | },
102 | ],
103 | },
104 | ];
105 | readonly projectItems = new Array(6);
106 | styleTransform = '';
107 | get activePage() {
108 | return this.pages[this.pages.length - 1];
109 | }
110 | get isHome() {
111 | return this.activePage === 'home';
112 | }
113 | setInputValue(ev: Event) {
114 | this.inputValue = (ev.target as HTMLInputElement).value;
115 | }
116 | onKeyDown(ev: KeyboardEvent) {
117 | if (ev.key === 'Enter') {
118 | this.bounce();
119 | }
120 |
121 | if (this.isHome || this.inputValue.length) {
122 | return;
123 | }
124 |
125 | if (ev.key === 'Backspace') {
126 | ev.preventDefault();
127 | this.popPage();
128 | this.bounce();
129 | }
130 | }
131 | popPage() {
132 | this.pages.splice(-1, 1);
133 | }
134 | bounce() {
135 | this.styleTransform = 'scale(0.96)';
136 | setTimeout(() => {
137 | this.styleTransform = '';
138 | }, 100);
139 | }
140 | searchProjects() {
141 | this.pages.push('projects');
142 | }
143 | }
144 |
--------------------------------------------------------------------------------
/src/assets/.gitkeep:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ngxpert/cmdk/3fc6fb1cf1c19911a475ed9d9b9321b5ba2599a3/src/assets/.gitkeep
--------------------------------------------------------------------------------
/src/assets/grid.svg:
--------------------------------------------------------------------------------
1 |
2 |
5 |
7 |
--------------------------------------------------------------------------------
/src/assets/line.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
--------------------------------------------------------------------------------
/src/assets/ngxpert cdk.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/src/environments/environment.prod.ts:
--------------------------------------------------------------------------------
1 | export const environment = {
2 | production: true
3 | };
4 |
--------------------------------------------------------------------------------
/src/environments/environment.ts:
--------------------------------------------------------------------------------
1 | // This file can be replaced during build by using the `fileReplacements` array.
2 | // `ng build` replaces `environment.ts` with `environment.prod.ts`.
3 | // The list of file replacements can be found in `angular.json`.
4 |
5 | export const environment = {
6 | production: false
7 | };
8 |
9 | /*
10 | * For easier debugging in development mode, you can import the following file
11 | * to ignore zone related error stack frames such as `zone.run`, `zoneDelegate.invokeTask`.
12 | *
13 | * This import should be commented out in production mode because it will have a negative impact
14 | * on performance if an error is thrown.
15 | */
16 | // import 'zone.js/plugins/zone-error'; // Included with Angular CLI.
17 |
--------------------------------------------------------------------------------
/src/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ngxpert/cmdk/3fc6fb1cf1c19911a475ed9d9b9321b5ba2599a3/src/favicon.ico
--------------------------------------------------------------------------------
/src/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | @ngxpert/cmdk
6 |
7 |
8 |
9 |
10 |
11 |
12 |
16 |
17 |
18 |
19 |
20 |
21 |
25 |
29 |
30 |
31 |
32 |
33 |
34 |
38 |
42 |
43 |
44 |
45 |
49 |
53 |
54 |
55 |
56 |
60 |
69 |
70 |
71 |
72 |
73 |
--------------------------------------------------------------------------------
/src/main.ts:
--------------------------------------------------------------------------------
1 | import { enableProdMode, importProvidersFrom } from '@angular/core';
2 |
3 | import { environment } from './environments/environment';
4 | import { AppComponent } from './app/app.component';
5 | import { withInterceptorsFromDi, provideHttpClient } from '@angular/common/http';
6 | import { A11yModule } from '@angular/cdk/a11y';
7 | import { OverlayModule } from '@angular/cdk/overlay';
8 | import { SharedModule } from './app/shared/shared.module';
9 | import { IconsModule } from './app/icons/icons.module';
10 | import { FormsModule, ReactiveFormsModule } from '@angular/forms';
11 | import { CmdkModule } from '@ngxpert/cmdk';
12 | import { BrowserModule, bootstrapApplication } from '@angular/platform-browser';
13 |
14 | if (environment.production) {
15 | enableProdMode();
16 | }
17 |
18 | bootstrapApplication(AppComponent, {
19 | providers: [
20 | importProvidersFrom(BrowserModule, CmdkModule, FormsModule, ReactiveFormsModule, IconsModule, SharedModule, OverlayModule, A11yModule),
21 | provideHttpClient(withInterceptorsFromDi())
22 | ]
23 | })
24 | .catch(err => console.error(err));
25 |
--------------------------------------------------------------------------------
/src/polyfills.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * This file includes polyfills needed by Angular and is loaded before the app.
3 | * You can add your own extra polyfills to this file.
4 | *
5 | * This file is divided into 2 sections:
6 | * 1. Browser polyfills. These are applied before loading ZoneJS and are sorted by browsers.
7 | * 2. Application imports. Files imported after ZoneJS that should be loaded before your main
8 | * file.
9 | *
10 | * The current setup is for so-called "evergreen" browsers; the last versions of browsers that
11 | * automatically update themselves. This includes recent versions of Safari, Chrome (including
12 | * Opera), Edge on the desktop, and iOS and Chrome on mobile.
13 | *
14 | * Learn more in https://angular.io/guide/browser-support
15 | */
16 |
17 | /***************************************************************************************************
18 | * BROWSER POLYFILLS
19 | */
20 |
21 | /**
22 | * By default, zone.js will patch all possible macroTask and DomEvents
23 | * user can disable parts of macroTask/DomEvents patch by setting following flags
24 | * because those flags need to be set before `zone.js` being loaded, and webpack
25 | * will put import in the top of bundle, so user need to create a separate file
26 | * in this directory (for example: zone-flags.ts), and put the following flags
27 | * into that file, and then add the following code before importing zone.js.
28 | * import './zone-flags';
29 | *
30 | * The flags allowed in zone-flags.ts are listed here.
31 | *
32 | * The following flags will work for all browsers.
33 | *
34 | * (window as any).__Zone_disable_requestAnimationFrame = true; // disable patch requestAnimationFrame
35 | * (window as any).__Zone_disable_on_property = true; // disable patch onProperty such as onclick
36 | * (window as any).__zone_symbol__UNPATCHED_EVENTS = ['scroll', 'mousemove']; // disable patch specified eventNames
37 | *
38 | * in IE/Edge developer tools, the addEventListener will also be wrapped by zone.js
39 | * with the following flag, it will bypass `zone.js` patch for IE/Edge
40 | *
41 | * (window as any).__Zone_enable_cross_context_check = true;
42 | *
43 | */
44 |
45 | /***************************************************************************************************
46 | * Zone JS is required by default for Angular itself.
47 | */
48 | import 'zone.js'; // Included with Angular CLI.
49 |
50 |
51 | /***************************************************************************************************
52 | * APPLICATION IMPORTS
53 | */
54 |
--------------------------------------------------------------------------------
/src/test.ts:
--------------------------------------------------------------------------------
1 | // This file is required by karma.conf.js and loads recursively all the .spec and framework files
2 |
3 | import 'zone.js/testing';
4 | import { getTestBed } from '@angular/core/testing';
5 | import {
6 | BrowserDynamicTestingModule,
7 | platformBrowserDynamicTesting
8 | } from '@angular/platform-browser-dynamic/testing';
9 |
10 | // First, initialize the Angular testing environment.
11 | getTestBed().initTestEnvironment(
12 | BrowserDynamicTestingModule,
13 | platformBrowserDynamicTesting(),
14 | );
15 |
--------------------------------------------------------------------------------
/src/types.d.ts:
--------------------------------------------------------------------------------
1 | export type Theme = 'linear' | 'raycast' | 'vercel' | 'framer';
2 |
--------------------------------------------------------------------------------
/tsconfig.app.json:
--------------------------------------------------------------------------------
1 | /* To learn more about this file see: https://angular.io/config/tsconfig. */
2 | {
3 | "extends": "./tsconfig.json",
4 | "compilerOptions": {
5 | "outDir": "./out-tsc/app",
6 | "types": []
7 | },
8 | "files": [
9 | "src/main.ts",
10 | "src/polyfills.ts"
11 | ],
12 | "include": [
13 | "src/**/*.d.ts"
14 | ]
15 | }
16 |
--------------------------------------------------------------------------------
/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compileOnSave": false,
3 | "compilerOptions": {
4 | "baseUrl": "./",
5 | "outDir": "./dist/out-tsc",
6 | "forceConsistentCasingInFileNames": true,
7 | "esModuleInterop": true,
8 | "strict": true,
9 | "noImplicitOverride": true,
10 | "noPropertyAccessFromIndexSignature": true,
11 | "noImplicitReturns": true,
12 | "noFallthroughCasesInSwitch": true,
13 | "sourceMap": true,
14 | "declaration": false,
15 | "experimentalDecorators": true,
16 | "moduleResolution": "node",
17 | "importHelpers": true,
18 | "target": "ES2022",
19 | "module": "es2020",
20 | "lib": [
21 | "es2020",
22 | "dom"
23 | ],
24 | "paths": {
25 | "@ngxpert/cmdk": [
26 | "projects/ngxpert/cmdk/src/public-api.ts"
27 | ]
28 | },
29 | "useDefineForClassFields": false
30 | },
31 | "angularCompilerOptions": {
32 | "enableI18nLegacyMessageIdFormat": false,
33 | "strictInjectionParameters": true,
34 | "strictInputAccessModifiers": true,
35 | "strictTemplates": true
36 | }
37 | }
--------------------------------------------------------------------------------
/tsconfig.spec.json:
--------------------------------------------------------------------------------
1 | /* To learn more about this file see: https://angular.io/config/tsconfig. */
2 | {
3 | "extends": "./tsconfig.json",
4 | "compilerOptions": {
5 | "outDir": "./out-tsc/spec",
6 | "types": [
7 | "jasmine"
8 | ]
9 | },
10 | "files": [
11 | "src/test.ts",
12 | "src/polyfills.ts"
13 | ],
14 | "include": [
15 | "src/**/*.spec.ts",
16 | "src/**/*.d.ts"
17 | ]
18 | }
19 |
--------------------------------------------------------------------------------