Skip to content
Merged
37 changes: 32 additions & 5 deletions apps/www/src/content/docs/components/command/demo.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,14 +27,26 @@ export const preview = {
<Command.Label>Actions</Command.Label>
<Command.Item
leadingIcon={<TransformIcon />}
trailingIcon={<Command.Shortcut>⌘ ⇧ A</Command.Shortcut>}
trailingIcon={
<Kbd.Group variant="ghost">
<Kbd aria-label="Command">⌘</Kbd>
<Kbd aria-label="Shift">⇧</Kbd>
<Kbd>A</Kbd>
</Kbd.Group>
}
onClick={() => setOpen(false)}
>
Create AOI...
</Command.Item>
<Command.Item
leadingIcon={<Share2Icon />}
trailingIcon={<Command.Shortcut>⌘ ⇧ W</Command.Shortcut>}
trailingIcon={
<Kbd.Group variant="ghost">
<Kbd aria-label="Command">⌘</Kbd>
<Kbd aria-label="Shift">⇧</Kbd>
<Kbd>W</Kbd>
</Kbd.Group>
}
onClick={() => setOpen(false)}
disabled
>
Expand Down Expand Up @@ -137,19 +149,34 @@ export const shortcutDemo = {
<Command.Group>
<Command.Label>Suggestions</Command.Label>
<Command.Item
trailingIcon={<Command.Shortcut>⌘ P</Command.Shortcut>}
trailingIcon={
<Kbd.Group variant="ghost">
<Kbd aria-label="Command">⌘</Kbd>
<Kbd>P</Kbd>
</Kbd.Group>
}
onClick={() => setOpen(false)}
>
Profile
</Command.Item>
<Command.Item
trailingIcon={<Command.Shortcut>⌘ B</Command.Shortcut>}
trailingIcon={
<Kbd.Group variant="ghost">
<Kbd aria-label="Command">⌘</Kbd>
<Kbd>B</Kbd>
</Kbd.Group>
}
onClick={() => setOpen(false)}
>
Billing
</Command.Item>
<Command.Item
trailingIcon={<Command.Shortcut>⌘ S</Command.Shortcut>}
trailingIcon={
<Kbd.Group variant="ghost">
<Kbd aria-label="Command">⌘</Kbd>
<Kbd>S</Kbd>
</Kbd.Group>
}
onClick={() => setOpen(false)}
>
Settings
Expand Down
10 changes: 1 addition & 9 deletions apps/www/src/content/docs/components/command/index.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -73,7 +73,7 @@ Rendered when no items are visible (all filtered out, or the list is empty). Aut

### Item

A selectable entry. When `value` is omitted and `children` is a string, the string is used as the value. `onClick` fires on pointer click **and** on keyboard `Enter` when the item is highlighted.
A selectable entry. When `value` is omitted and `children` is a string, the string is used as the value. `onClick` fires on pointer click **and** on keyboard `Enter` when the item is highlighted. Pass a [`Kbd`](/docs/components/kbd) as `trailingIcon` to show a keyboard hint — use the `ghost` variant so the keys sit on the item's own background.

<auto-type-table path="./props.ts" name="CommandItemProps" />

Expand All @@ -95,12 +95,6 @@ Visual divider between groups. The separator is hidden automatically while the u

<auto-type-table path="./props.ts" name="CommandSeparatorProps" />

### Shortcut

A `<kbd>` element for keyboard hints. Typically passed as `trailingIcon` on `Command.Item`.

<auto-type-table path="./props.ts" name="CommandShortcutProps" />

### Dialog

`Command.Dialog` is an alias for the Base UI Dialog root. Pair it with `Command.DialogTrigger` and `Command.DialogContent` to render the command menu in a centered modal.
Expand Down Expand Up @@ -131,8 +125,6 @@ Every rendered part carries a stable `data-slot` attribute for [styling and test
| `command-item-label` | The item's label text |
| `command-item-trailing-icon` | Trailing icon inside an item (when provided) |
| `command-separator` | `Command.Separator` |
| `command-shortcut` | `Command.Shortcut` wrapper |
| `command-shortcut-key` | Each `<kbd>` key inside a shortcut |
| `command-dialog-trigger` | `Command.DialogTrigger` |
| `command-dialog-viewport` | Viewport wrapper for the dialog popup |
| `command-dialog-content` | `Command.DialogContent` popup |
Expand Down
7 changes: 1 addition & 6 deletions apps/www/src/content/docs/components/command/props.ts
Original file line number Diff line number Diff line change
Expand Up @@ -103,7 +103,7 @@ export interface CommandItemProps {
/** Icon rendered before the item label. */
leadingIcon?: React.ReactNode;

/** Node rendered after the item label (e.g. `Command.Shortcut` or an icon). */
/** Node rendered after the item label (e.g. a `Kbd` shortcut hint or an icon). */
trailingIcon?: React.ReactNode;

/**
Expand Down Expand Up @@ -131,11 +131,6 @@ export interface CommandSeparatorProps {
className?: string;
}

export interface CommandShortcutProps {
/** Additional CSS class names. */
className?: string;
}

export interface CommandDialogProps {
/** Controlled open state. */
open?: boolean;
Expand Down
121 changes: 121 additions & 0 deletions apps/www/src/content/docs/components/kbd/demo.ts
Comment thread
rohanchkrabrty marked this conversation as resolved.
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>`
};
110 changes: 110 additions & 0 deletions apps/www/src/content/docs/components/kbd/index.mdx

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The 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

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The 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.
30 changes: 30 additions & 0 deletions apps/www/src/content/docs/components/kbd/props.ts
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';
Comment thread
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';
Comment thread
coderabbitai[bot] marked this conversation as resolved.

/** Additional CSS class names. */
className?: string;
}
Loading
Loading