Skip to content
Merged
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
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>
);
}
82 changes: 82 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,82 @@
'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 verticalDemo = {
type: 'code',
code: `<Toolbar orientation="vertical">
<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.Group aria-label="Alignment" render={<Toggle.Group/>}>
<Toolbar.Button render={<Toggle size={4}/>} ><TextAlignLeftIcon /></Toolbar.Button>
<Toolbar.Button render={<Toggle size={4}/>}><TextAlignRightIcon /></Toolbar.Button>
</Toolbar.Group>
<Toolbar.Separator />
<Toolbar.Group aria-label="Numerical format">
<Toolbar.Button aria-label="Format as currency">Format</Toolbar.Button>
</Toolbar.Group>
<Toolbar.Separator />
<Select defaultValue="Helvetica">
<Toolbar.Button render={<Select.Trigger variant="text" size="small" />}>
<Select.Value />
</Toolbar.Button>
<Select.Content sideOffset={8}>
<Select.Item value="Helvetica">Helvetica</Select.Item>
<Select.Item value="Arial">Arial</Select.Item>
</Select.Content>
</Select>
</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>`
};
101 changes: 101 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,101 @@
---
title: Toolbar
description: A container for grouping interactive controls with accessible keyboard navigation.
source: packages/raystack/components/toolbar
tag: new
---

import { preview, groupDemo, verticalDemo, 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>
```
Comment on lines +16 to +29
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Documentation references unimplemented components.

The anatomy section shows Toolbar.Link and Toolbar.Input usage, but these components are not yet implemented in toolbar.tsx. Update the implementation to include these components, or remove them from the documentation until they're available.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/www/src/content/docs/components/toolbar/index.mdx` around lines 16 - 29,
The docs reference Toolbar.Link and Toolbar.Input which don't exist; update the
Toolbar implementation to provide these subcomponents or remove them from docs.
Add two new exports on the Toolbar object (e.g., Toolbar.Link and Toolbar.Input)
implemented as functional components that forward props and refs to an anchor
and input respectively, accept and pass through className and children, mirror
the existing API/prop types used by Toolbar.Button/Toolbar.Group, and include
the same styling/role semantics as other subcomponents so the example in the
docs works; alternatively, if you prefer not to implement them now, remove the
Toolbar.Link and Toolbar.Input lines from the example in the docs to avoid
referencing unimplemented components.


## 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" />
Comment on lines +57 to +67
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

API reference documents missing Link and Input components.

The API reference sections for Link and Input document components that don't exist in the implementation. This will cause confusion when users try to use these documented features. Ensure the implementation in toolbar.tsx exports these components.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/www/src/content/docs/components/toolbar/index.mdx` around lines 57 - 67,
The docs reference Toolbar Link and Input components that are not exported;
update toolbar.tsx to implement and export components matching the documented
API names (e.g., ToolbarLink and ToolbarInput) and ensure their props align with
ToolbarLinkProps and ToolbarInputProps from props.ts; add the components (or
re-export existing implementations) with the expected prop types,
keyboard/navigation behavior, and named exports so the MDX auto-type-table
resolves correctly.


## Examples

### Grouped

Use `Toolbar.Group` to group related buttons.

<Demo data={groupDemo} />

### Vertical

Use the `orientation` prop to render the toolbar vertically.

<Demo data={verticalDemo} />

### Composition

Use the `render` prop to compose with other components.

<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;
}
Comment on lines +1 to +95
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Verify if other props.ts files in the docs import React or rely on global types
fd -e ts 'props.ts' apps/www/src/content/docs/components --exec head -20 {}

Repository: raystack/apsara

Length of output: 27427


Add missing React type import.

The file uses React.ReactElement and React.HTMLAttributes<HTMLElement> (lines 43-44, 73-74) without importing React. Add the explicit import:

Proposed fix
+import type React from 'react';
+
 export interface ToolbarProps {
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
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;
}
import type React from 'react';
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;
}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/www/src/content/docs/components/toolbar/props.ts` around lines 1 - 95,
This file uses React types (React.ReactElement and
React.HTMLAttributes<HTMLElement>) but doesn't import React; add an explicit
import for React at the top of the file (e.g., import * as React from 'react')
so the types used in ToolbarButtonProps.render, ToolbarLinkProps.render, and any
other React type references resolve correctly.

Loading
Loading