Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions apps/www/src/components/playground/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -44,4 +44,5 @@ export * from './tabs-examples';
export * from './text-area-examples';
export * from './text-examples';
export * from './toast-examples';
export * from './toolbar-examples';
export * from './tooltip-examples';
95 changes: 95 additions & 0 deletions apps/www/src/components/playground/toolbar-examples.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
'use client';

import {
FontBoldIcon,
FontItalicIcon,
TextAlignCenterIcon,
TextAlignLeftIcon,
TextAlignRightIcon,
UnderlineIcon
} from '@radix-ui/react-icons';
import { Button, Flex, IconButton, Text, Toolbar } from '@raystack/apsara';
import PlaygroundLayout from './playground-layout';

export function ToolbarExamples() {
return (
<PlaygroundLayout title='Toolbar'>
<Flex direction='column' gap='large'>
<Text>Default:</Text>
<Toolbar>
<Toolbar.Button>Bold</Toolbar.Button>
<Toolbar.Button>Italic</Toolbar.Button>
<Toolbar.Separator />
<Toolbar.Button>Left</Toolbar.Button>
<Toolbar.Button>Center</Toolbar.Button>
<Toolbar.Button>Right</Toolbar.Button>
</Toolbar>

<Text>Grouped with composition:</Text>
<Toolbar>
<Toolbar.Button
render={<Button variant='text' color='neutral' size='small' />}
>
32px
</Toolbar.Button>
<Toolbar.Separator />
<Toolbar.Group>
<Toolbar.Button
render={
<IconButton variant='text' color='neutral' size='small' />
}
>
<FontBoldIcon />
</Toolbar.Button>
<Toolbar.Button
render={
<IconButton variant='text' color='neutral' size='small' />
}
>
<FontItalicIcon />
</Toolbar.Button>
<Toolbar.Button
render={
<IconButton variant='text' color='neutral' size='small' />
}
>
<UnderlineIcon />
</Toolbar.Button>
</Toolbar.Group>
<Toolbar.Separator />
<Toolbar.Group>
<Toolbar.Button
render={
<IconButton variant='text' color='neutral' size='small' />
}
>
<TextAlignLeftIcon />
</Toolbar.Button>
<Toolbar.Button
render={
<IconButton variant='text' color='neutral' size='small' />
}
>
<TextAlignCenterIcon />
</Toolbar.Button>
<Toolbar.Button
render={
<IconButton variant='text' color='neutral' size='small' />
}
>
<TextAlignRightIcon />
</Toolbar.Button>
</Toolbar.Group>
</Toolbar>

<Text>Disabled:</Text>
<Toolbar disabled>
<Toolbar.Button>Bold</Toolbar.Button>
<Toolbar.Button>Italic</Toolbar.Button>
<Toolbar.Separator />
<Toolbar.Button>Left</Toolbar.Button>
</Toolbar>
</Flex>
</PlaygroundLayout>
);
}
61 changes: 61 additions & 0 deletions apps/www/src/content/docs/components/toolbar/demo.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
'use client';

export const preview = {
type: 'code',
code: `<Toolbar>
<Toolbar.Button>Bold</Toolbar.Button>
<Toolbar.Button>Italic</Toolbar.Button>
<Toolbar.Button>Underline</Toolbar.Button>
<Toolbar.Separator />
<Toolbar.Button>Left</Toolbar.Button>
<Toolbar.Button>Center</Toolbar.Button>
<Toolbar.Button>Right</Toolbar.Button>
</Toolbar>`
};

export const groupDemo = {
type: 'code',
code: `<Toolbar>
<Toolbar.Group>
<Toolbar.Button>Bold</Toolbar.Button>
<Toolbar.Button>Italic</Toolbar.Button>
<Toolbar.Button>Underline</Toolbar.Button>
</Toolbar.Group>
<Toolbar.Separator />
<Toolbar.Group>
<Toolbar.Button>Left</Toolbar.Button>
<Toolbar.Button>Center</Toolbar.Button>
<Toolbar.Button>Right</Toolbar.Button>
</Toolbar.Group>
</Toolbar>`
};

export const compositionDemo = {
type: 'code',
code: `<Toolbar>
<Toolbar.Button render={<Button variant="text" color="neutral" size="small" />}>
32px
</Toolbar.Button>
<Toolbar.Separator />
<Toolbar.Group>
<Toolbar.Button render={<IconButton variant="text" color="neutral" size="small" />}>
<FontBoldIcon />
</Toolbar.Button>
<Toolbar.Button render={<IconButton variant="text" color="neutral" size="small" />}>
<FontItalicIcon />
</Toolbar.Button>
</Toolbar.Group>
<Toolbar.Separator />
<Toolbar.Link href="https://example.com">Help</Toolbar.Link>
</Toolbar>`
};

export const disabledDemo = {
type: 'code',
code: `<Toolbar disabled>
<Toolbar.Button>Bold</Toolbar.Button>
<Toolbar.Button>Italic</Toolbar.Button>
<Toolbar.Separator />
<Toolbar.Button>Left</Toolbar.Button>
</Toolbar>`
};
95 changes: 95 additions & 0 deletions apps/www/src/content/docs/components/toolbar/index.mdx
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
---
title: Toolbar
description: A container for grouping interactive controls with accessible keyboard navigation.
source: packages/raystack/components/toolbar
tag: new
---

import { preview, groupDemo, compositionDemo, disabledDemo } from "./demo.ts";

<Demo data={preview} />

## Anatomy

Import and assemble the component:

```tsx
import { Toolbar } from '@raystack/apsara'

<Toolbar>
<Toolbar.Button />
<Toolbar.Group>
<Toolbar.Button />
<Toolbar.Button />
</Toolbar.Group>
<Toolbar.Separator />
<Toolbar.Link />
<Toolbar.Input />
</Toolbar>
```

## API Reference

### Root

Groups all toolbar controls. Manages keyboard navigation between items.

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

### Button

An interactive button within the toolbar. Supports the `render` prop to compose with other components like `Button` or `IconButton`.

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

### Group

Groups related toolbar items together.

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

### Separator

A visual divider between toolbar sections.

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

### Link

An anchor element for navigation within the toolbar.

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

### Input

A native input element with integrated keyboard navigation.

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

## Examples

### Grouped

Use `Toolbar.Group` to group related buttons.

<Demo data={groupDemo} />

### Composition

Use the `render` prop on `Toolbar.Button` to compose with Apsara components like `Button` and `IconButton`.

<Demo data={compositionDemo} />

### Disabled

Disable all toolbar items by setting `disabled` on the root.

<Demo data={disabledDemo} />

## Accessibility

- Uses `role="toolbar"` for proper semantic grouping
- Arrow keys navigate between focusable items
- `Home` and `End` keys jump to first and last items
- Focus loops by default (configurable via `loopFocus`)
- Disabled items remain focusable for screen reader discoverability
95 changes: 95 additions & 0 deletions apps/www/src/content/docs/components/toolbar/props.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
export interface ToolbarProps {
/**
* Whether keyboard navigation loops from the last item back to the first.
* @defaultValue true
*/
loopFocus?: boolean;

/**
* The orientation of the toolbar layout.
* @defaultValue "horizontal"
*/
orientation?: 'horizontal' | 'vertical';

/**
* Whether the toolbar and all its items are disabled.
* @defaultValue false
*/
disabled?: boolean;

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

export interface ToolbarButtonProps {
/**
* Whether the button remains focusable when disabled.
* @defaultValue true
*/
focusableWhenDisabled?: boolean;

/**
* Whether the button is disabled.
* @defaultValue false
*/
disabled?: boolean;

/**
* Allows you to replace the component's HTML element with a different tag, or compose it with another component.
*
* @remarks `ReactElement | function`
*/
render?:
| React.ReactElement
| ((props: React.HTMLAttributes<HTMLElement>) => React.ReactElement);

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

export interface ToolbarGroupProps {
/**
* Whether the group and all its items are disabled.
* @defaultValue false
*/
disabled?: boolean;

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

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

export interface ToolbarLinkProps {
/**
* Allows you to replace the component's HTML element with a different tag, or compose it with another component.
*
* @remarks `ReactElement | function`
*/
render?:
| React.ReactElement
| ((props: React.HTMLAttributes<HTMLElement>) => React.ReactElement);

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

export interface ToolbarInputProps {
/**
* Whether the input remains focusable when disabled.
* @defaultValue true
*/
focusableWhenDisabled?: boolean;

/**
* Whether the input is disabled.
* @defaultValue false
*/
disabled?: boolean;

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