-
Notifications
You must be signed in to change notification settings - Fork 13
feat(kbd): add Kbd component, remove Command.Shortcut #886
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
d679eef
31f1af2
b5a3b97
5a6df31
9fe9d1f
fc96563
4369888
1063296
1729340
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,121 @@ | ||
| 'use client'; | ||
|
|
||
| import type { ComponentPropsType } from '@/components/demo/types'; | ||
| import { getPropsString } from '@/lib/utils'; | ||
|
|
||
| export const getCode = (props: ComponentPropsType) => { | ||
| const { children, ...rest } = props; | ||
|
|
||
| return `<Kbd${getPropsString(rest)}>${children}</Kbd>`; | ||
| }; | ||
|
|
||
| export const playground = { | ||
| type: 'playground', | ||
| controls: { | ||
| variant: { | ||
| type: 'select', | ||
| options: ['solid', 'ghost'], | ||
| defaultValue: 'solid' | ||
| }, | ||
| children: { | ||
| type: 'text', | ||
| initialValue: 'Esc' | ||
| } | ||
| }, | ||
| getCode | ||
| }; | ||
|
|
||
| export const singleDemo = { | ||
| type: 'code', | ||
| code: `<Flex gap={5} align="center"> | ||
| <Kbd>Esc</Kbd> | ||
| <Kbd aria-label="Command">⌘</Kbd> | ||
| <Kbd aria-label="Shift">⇧</Kbd> | ||
| <Kbd aria-label="Enter">↵</Kbd> | ||
| <Kbd>Tab</Kbd> | ||
| </Flex>` | ||
| }; | ||
|
|
||
| export const variantDemo = { | ||
| type: 'code', | ||
| code: `<Flex gap={7} align="center"> | ||
| <Kbd.Group> | ||
| <Kbd aria-label="Command">⌘</Kbd> | ||
| <Kbd>K</Kbd> | ||
| </Kbd.Group> | ||
| <Kbd.Group variant="ghost"> | ||
| <Kbd aria-label="Command">⌘</Kbd> | ||
| <Kbd>K</Kbd> | ||
| </Kbd.Group> | ||
| </Flex>` | ||
| }; | ||
|
|
||
| export const groupDemo = { | ||
| type: 'code', | ||
| code: `<Flex gap={7} align="center"> | ||
| <Kbd.Group> | ||
| <Kbd aria-label="Command">⌘</Kbd> | ||
| <Kbd>K</Kbd> | ||
| </Kbd.Group> | ||
| <Kbd.Group> | ||
| <Kbd aria-label="Command">⌘</Kbd> | ||
| <Kbd aria-label="Shift">⇧</Kbd> | ||
| <Kbd>P</Kbd> | ||
| </Kbd.Group> | ||
| </Flex>` | ||
| }; | ||
|
|
||
| export const separatorDemo = { | ||
| type: 'code', | ||
| tabs: [ | ||
| { | ||
| name: 'Plus', | ||
| code: `<Kbd.Group> | ||
| <Kbd aria-label="Command">⌘</Kbd> | ||
| + | ||
| <Kbd>K</Kbd> | ||
| </Kbd.Group>` | ||
| }, | ||
| { | ||
| name: 'Then', | ||
| code: `<Kbd.Group> | ||
| <Kbd>G</Kbd> | ||
| then | ||
| <Kbd>P</Kbd> | ||
| </Kbd.Group>` | ||
| } | ||
| ] | ||
| }; | ||
|
|
||
| export const withTextDemo = { | ||
| type: 'code', | ||
| code: `<Text size="small" variant="secondary"> | ||
| Press <Kbd.Group><Kbd aria-label="Command">⌘</Kbd><Kbd>K</Kbd></Kbd.Group> to open the command palette. | ||
| </Text>` | ||
| }; | ||
|
|
||
| export const withInputDemo = { | ||
| type: 'code', | ||
| code: `<Input | ||
| placeholder="Search projects" | ||
| trailingIcon={<Kbd variant="ghost" aria-label="Command K">⌘K</Kbd>} | ||
| />` | ||
| }; | ||
|
|
||
| export const withTooltipDemo = { | ||
| type: 'code', | ||
| code: `<Tooltip> | ||
| <Tooltip.Trigger render={<Button variant="outline" />}> | ||
| Search | ||
| </Tooltip.Trigger> | ||
| <Tooltip.Content> | ||
| <Flex gap={3} align="center"> | ||
| Open search | ||
| <Kbd.Group variant="ghost"> | ||
| <Kbd aria-label="Command">⌘</Kbd> | ||
| <Kbd>K</Kbd> | ||
| </Kbd.Group> | ||
| </Flex> | ||
| </Tooltip.Content> | ||
| </Tooltip>` | ||
| }; |
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Let's add a playground too since we are introducing variants now |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,110 @@ | ||
| --- | ||
| title: Kbd | ||
| description: A component for displaying keyboard keys and shortcuts. | ||
| source: packages/raystack/components/kbd | ||
| tag: new | ||
| --- | ||
|
|
||
| import { | ||
| playground, | ||
| singleDemo, | ||
| variantDemo, | ||
| groupDemo, | ||
| separatorDemo, | ||
| withTextDemo, | ||
| withInputDemo, | ||
| withTooltipDemo, | ||
| } from "./demo.ts"; | ||
|
|
||
| <Demo data={playground} /> | ||
|
|
||
| ## Anatomy | ||
|
|
||
| Import and assemble the component. A single `Kbd` renders one key; wrap several in `Kbd.Group` to show a sequence. | ||
|
|
||
| ```tsx | ||
| import { Kbd } from "@raystack/apsara"; | ||
|
|
||
| <Kbd>Esc</Kbd> | ||
|
|
||
| <Kbd.Group> | ||
| <Kbd>⌘</Kbd> | ||
| <Kbd>K</Kbd> | ||
| </Kbd.Group> | ||
| ``` | ||
|
|
||
| ## API Reference | ||
|
|
||
| Both parts render a `<kbd>` element and forward any native attributes (`id`, `title`, `aria-label`, …) to it. | ||
|
|
||
| ### Root | ||
|
|
||
| A single keyboard key. Renders a `<kbd>` element. | ||
|
|
||
| <auto-type-table path="./props.ts" name="KbdProps" /> | ||
|
|
||
| ### Group | ||
|
|
||
| Groups multiple keyboard keys for key combinations. | ||
|
|
||
| <auto-type-table path="./props.ts" name="KbdGroupProps" /> | ||
|
|
||
| ### Slots | ||
|
|
||
| Every rendered part carries a stable `data-slot` attribute for [styling and testing](/docs/styling#with-data-slot): | ||
|
|
||
| | Slot | Element | | ||
| |------|---------| | ||
| | `kbd` | Each individual key | | ||
| | `kbd-group` | The `Kbd.Group` wrapper | | ||
|
|
||
| ## Examples | ||
|
|
||
| ### Single keys | ||
|
|
||
| Use `Kbd` on its own for a one-key hint. Keys share a minimum width so a narrow `K` lines up with a wide `⌘`. | ||
|
|
||
| <Demo data={singleDemo} /> | ||
|
|
||
| ### Variants | ||
|
|
||
| `solid` is the default and suits standalone hints. Use `ghost` on surfaces that already have their own background, such as a command item, a tooltip, or an input. | ||
|
|
||
| <Demo data={variantDemo} /> | ||
|
|
||
| ### Sequences | ||
|
|
||
| Wrap keys in `Kbd.Group` to show a chord. Setting `variant` on the group sets the variant for the keys inside, and an individual `Kbd` can override it. | ||
|
|
||
| <Demo data={groupDemo} /> | ||
|
|
||
| ### Separators | ||
|
|
||
| `Kbd.Group` renders whatever you put between the keys, so separators are plain text. Use `+` for keys pressed together and a word like `then` for keys pressed in order. Separator text takes the surrounding typography rather than the key styling. | ||
|
|
||
| <Demo data={separatorDemo} /> | ||
|
|
||
| ### Inline with text | ||
|
|
||
| Keys sit on the text baseline, so they can be dropped straight into a sentence. | ||
|
|
||
| <Demo data={withTextDemo} /> | ||
|
Comment on lines
+87
to
+91
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Even tho the example says Inline with text, the actual example is rendering in side a Flex, so it's misleading |
||
|
|
||
| ### In an input | ||
|
|
||
| Surface a focus shortcut in a search field. Use a single `ghost` key here — the input's trailing slot is sized for an icon, so a multi-key `Kbd.Group` will be clipped. | ||
|
|
||
| <Demo data={withInputDemo} /> | ||
|
|
||
| ### In a tooltip | ||
|
|
||
| A common use is surfacing a shortcut alongside the action it triggers. | ||
|
|
||
| <Demo data={withTooltipDemo} /> | ||
|
|
||
| ## Accessibility | ||
|
|
||
| - `Kbd` is presentational: it styles a key. The `<kbd>` element carries no ARIA role and no accessible name of its own, so it adds no semantics to the text around it. | ||
| - A symbol on its own — `⌘`, `⇧`, `↵` — carries no name for the key it stands for, and how any given screen reader reads the bare glyph varies. Give the key a name whenever the symbol is the only cue: `<Kbd aria-label="Command">⌘</Kbd>`. | ||
| - Keys are not focusable and carry no interaction. Keep the shortcut wired to a real handler elsewhere — `Kbd` only displays it. | ||
| - Keys ignore pointer events and text selection, so clicking or dragging across a command item does not highlight the key labels. | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,30 @@ | ||
| import type { ReactNode } from 'react'; | ||
|
|
||
| export interface KbdProps { | ||
| /** The key to display, e.g. `⌘`, `Esc`, or `Enter`. */ | ||
| children?: ReactNode; | ||
|
|
||
| /** | ||
| * Visual style variant. Inherited from a parent `Kbd.Group` when set there. | ||
| * @defaultValue "solid" | ||
| */ | ||
| variant?: 'solid' | 'ghost'; | ||
|
coderabbitai[bot] marked this conversation as resolved.
|
||
|
|
||
| /** Additional CSS class names. */ | ||
| className?: string; | ||
| } | ||
|
|
||
| export interface KbdGroupProps { | ||
| /** The keys in the sequence, plus any plain-text separators between them. */ | ||
| children?: ReactNode; | ||
|
|
||
| /** | ||
| * Visual style variant inherited by every key in the group. A key's own | ||
| * `variant` takes precedence over it. | ||
| * @defaultValue "solid" | ||
| */ | ||
| variant?: 'solid' | 'ghost'; | ||
|
coderabbitai[bot] marked this conversation as resolved.
|
||
|
|
||
| /** Additional CSS class names. */ | ||
| className?: string; | ||
| } | ||
Uh oh!
There was an error while loading. Please reload this page.