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
14 changes: 9 additions & 5 deletions apps/www/src/components/chip-input-demo.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { Input } from '@raystack/apsara';
import { Chip, Input } from '@raystack/apsara';
import { useState } from 'react';

export default function ChipInputDemo() {
Expand All @@ -23,10 +23,14 @@ export default function ChipInputDemo() {
setInput('');
}
}}
chips={chips.map((c, i) => ({
label: c.label,
onRemove: () => setChips(prev => prev.filter((_, j) => j !== i))
}))}
chips={chips.map((c, i) => (
<Chip key={i}>
{c.label}
<Chip.Remove
onClick={() => setChips(prev => prev.filter((_, j) => j !== i))}
/>
</Chip>
))}
maxChipsVisible={4}
/>
);
Expand Down
3 changes: 1 addition & 2 deletions apps/www/src/components/dataview-demo.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -628,10 +628,9 @@ function SelectionBar() {
size='large'
color='neutral'
leadingIcon={<Frame size={16} strokeWidth={1.5} />}
isDismissible
onDismiss={() => table.resetRowSelection()}
>
{selectedCount} selected
<Chip.Remove onClick={() => table.resetRowSelection()} />
</Chip>
<FloatingActions.Separator />
<Button variant='outline' color='neutral' size='small'>
Expand Down
45 changes: 45 additions & 0 deletions apps/www/src/content/docs/(overview)/upgrading.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,51 @@ One section per release, newest first, with only the changes that need action
from you. The full record of every release, features and fixes included, is
on [GitHub releases](https://github.com/raystack/apsara/releases).

## Unreleased: `Chip.Remove` replaces the dismiss props

### 1. Move `isDismissible` and `onDismiss` to `Chip.Remove`

`Chip` has no `isDismissible` or `onDismiss`. Add `Chip.Remove` as the last
child, and move the handler to its `onClick`.

```tsx
// Before
<Chip isDismissible onDismiss={handleRemove}>Tag</Chip>

// After
<Chip>
Tag
<Chip.Remove onClick={handleRemove} />
</Chip>
```

The remove button slots are `chip-remove` and `chip-remove-icon`. They were
`chip-dismiss` and `chip-dismiss-icon`. A `trailingIcon` no longer hides when
the chip has a remove button.

### 2. Pass chip props or elements to `Input` `chips`

Each item in `Input` `chips` is `Chip` props or a `Chip` element. `label` is
now `children`. For a removable chip, pass an element with `Chip.Remove`.

```tsx
// Before
<Input chips={[{ label: 'Tag', onRemove: handleRemove }]} />

// After
<Input
chips={[
<Chip key="tag">
Tag
<Chip.Remove onClick={handleRemove} />
</Chip>
]}
/>
```

A disabled input disables the chips that you pass as props. Chip elements
render as given, so pass `disabled` to them.

## Unreleased: date filters compare whole days

`DataView`, `DataTable` and `FilterChip` read and compare dates as calendar
Expand Down
24 changes: 17 additions & 7 deletions apps/www/src/content/docs/components/chip/demo.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,10 @@ import type { ComponentPropsType } from '@/components/demo/types';
import { getPropsString } from '@/lib/utils';

export const getCode = (props: ComponentPropsType) => {
const { children, ...rest } = props;
const { children, removable, ...rest } = props;
const remove = removable ? '<Chip.Remove />' : '';

return `<Chip${getPropsString(rest)}>${children}</Chip>`;
return `<Chip${getPropsString(rest)}>${children}${remove}</Chip>`;
};

export const playground = {
Expand All @@ -27,7 +28,7 @@ export const playground = {
options: ['neutral', 'accent', 'danger', 'success', 'warning'],
defaultValue: 'neutral'
},
isDismissible: {
removable: {
type: 'checkbox',
defaultValue: false
},
Expand Down Expand Up @@ -80,13 +81,22 @@ export const colorDemo = {
</Flex>`
};

export const dismissableDemo = {
export const removeDemo = {
type: 'code',
code: `
<Flex gap={9}>
<Chip isDismissible onDismiss={() => alert('dismissed')} aria-label="Dismissible chip">Dismissable Chip</Chip>
<Chip variant="outline" color="accent" isDismissible onDismiss={() => alert('dismissed')} aria-label="Dismissible chip">Dismissable Chip</Chip>
<Chip variant="filled" color="accent" isDismissible onDismiss={() => alert('dismissed')} aria-label="Dismissible chip">Dismissable Chip</Chip>
<Chip>
Removable Chip
<Chip.Remove onClick={() => alert('removed')} />
</Chip>
<Chip variant="outline" color="accent">
Removable Chip
<Chip.Remove onClick={() => alert('removed')} />
</Chip>
<Chip variant="filled" color="accent">
Removable Chip
<Chip.Remove onClick={() => alert('removed')} />
</Chip>
</Flex>`
};

Expand Down
30 changes: 20 additions & 10 deletions apps/www/src/content/docs/components/chip/index.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ import {
variantsDemo,
sizesDemo,
colorDemo,
dismissableDemo,
removeDemo,
iconsDemo,
} from "./demo.ts";

Expand All @@ -22,12 +22,14 @@ Import and assemble the component:
```tsx
import { Chip } from "@raystack/apsara";

<Chip />
<Chip>
<Chip.Remove />
</Chip>
```

## Usage

Appearance first, meaning variant, size and color, then the two things a chip can carry: icons and a dismiss action.
Appearance first, meaning variant, size and color, then the two things a chip can carry: icons and a remove button.

### Variant

Expand Down Expand Up @@ -56,19 +58,27 @@ Chips can include leading and trailing icons to provide additional context or ac

<Demo data={iconsDemo} />

### Dismissible
### Remove button

Chips can be made dismissible by adding the isDismissible prop and an onDismiss handler.
Add `Chip.Remove` as the last child to show a remove button. Handle the removal in its `onClick`. The button is named "Remove" plus the chip text, and it is disabled when the chip is disabled.

<Demo data={dismissableDemo} />
<Demo data={removeDemo} />

To render the button as another element, pass `render`. For example, `Combobox` renders it as Base UI's `Combobox.ChipRemove`.

## API Reference

Renders a compact element for tags, labels, or attributes.

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

The chip renders a `<button>` when `onClick` is set and it is not dismissible, and a `<span>` otherwise, so `ref` points to whichever element is rendered.
The chip renders a `<button>` when `onClick` is set and it has no `Chip.Remove` child, and a `<span>` otherwise, so `ref` points to whichever element is rendered.

### Remove Props

A button that removes the chip. Place it as a direct child of `Chip`.

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

### Slots

Expand All @@ -78,9 +88,9 @@ Every rendered part carries a stable `data-slot` attribute for [styling and test
|------|---------|
| `chip` | The root element (`<button>` when interactive, `<span>` otherwise) |
| `chip-leading-icon` | Wrapper around `leadingIcon` (when set) |
| `chip-trailing-icon` | Wrapper around `trailingIcon` (when set and not dismissible) |
| `chip-dismiss` | The dismiss button (when `isDismissible`) |
| `chip-dismiss-icon` | The dismiss icon (when `isDismissible`) |
| `chip-trailing-icon` | Wrapper around `trailingIcon` (when set) |
| `chip-remove` | The `Chip.Remove` button |
| `chip-remove-icon` | The icon inside `Chip.Remove` |

## Accessibility

Expand Down
34 changes: 26 additions & 8 deletions apps/www/src/content/docs/components/chip/props.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,14 +23,6 @@ export interface ChipProps {
/** ReactNode to display as an icon after the label */
trailingIcon?: React.ReactNode;

/**
* Boolean to show a dismiss button (replaces trailingIcon)
*/
isDismissible?: boolean;

/** Callback function when dismiss button is clicked */
onDismiss?: () => void;

/** Content to display inside the chip */
children?: React.ReactNode;

Expand All @@ -54,4 +46,30 @@ export interface ChipProps {

/** Ref to the rendered element. */
ref?: React.Ref<HTMLSpanElement>;

/** Renders the chip as another element, or with a function that receives the chip props. */
render?: React.ReactElement | ((props: object) => React.ReactElement);
}

export interface ChipRemoveProps {
/** Called when the button is pressed. */
onClick?: React.MouseEventHandler<HTMLButtonElement>;

/**
* Accessible name of the button.
* @defaultValue "Remove" plus the chip text
*/
'aria-label'?: string;

/**
* The icon inside the button.
* @defaultValue <XIcon />
*/
children?: React.ReactNode;

/** Renders the button as another element, or with a function that receives the button props. */
render?: React.ReactElement | ((props: object) => React.ReactElement);

/** Additional CSS class names */
className?: string;
}
35 changes: 33 additions & 2 deletions apps/www/src/content/docs/components/combobox/demo.ts
Original file line number Diff line number Diff line change
Expand Up @@ -78,20 +78,51 @@ export const groupDemo = {
<Combobox.Input placeholder="Search items" width={240} />
<Combobox.Content>
<Combobox.Group>
<Combobox.Label>Fruits</Combobox.Label>
<Combobox.GroupLabel>Fruits</Combobox.GroupLabel>
<Combobox.Item value="apple">Apple</Combobox.Item>
<Combobox.Item value="banana">Banana</Combobox.Item>
</Combobox.Group>
<Combobox.Separator />
<Combobox.Group>
<Combobox.Label>Vegetables</Combobox.Label>
<Combobox.GroupLabel>Vegetables</Combobox.GroupLabel>
<Combobox.Item value="carrot">Carrot</Combobox.Item>
<Combobox.Item value="broccoli">Broccoli</Combobox.Item>
</Combobox.Group>
</Combobox.Content>
</Combobox>`
};

export const labelDemo = {
type: 'code',
code: `
<Combobox multiple>
<Flex direction="column" gap={2}>
<Combobox.Label>Fruits</Combobox.Label>
<Combobox.Input placeholder="Select fruits" width={300} showClearButton />
</Flex>
<Combobox.Content>
<Combobox.Item>Apple</Combobox.Item>
<Combobox.Item>Banana</Combobox.Item>
<Combobox.Item>Grape</Combobox.Item>
<Combobox.Item>Orange</Combobox.Item>
</Combobox.Content>
</Combobox>`
};

export const emptyDemo = {
type: 'code',
code: `
<Combobox>
<Combobox.Input placeholder="Type 'xyz'" width={240} />
<Combobox.Content>
<Combobox.Empty>No fruit matches your search</Combobox.Empty>
<Combobox.Item>Apple</Combobox.Item>
<Combobox.Item>Banana</Combobox.Item>
<Combobox.Item>Grape</Combobox.Item>
</Combobox.Content>
</Combobox>`
};

export const iconDemo = {
type: 'code',
code: `
Expand Down
Loading
Loading