Skip to content
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

feat: added chip component #507

Open
wants to merge 1 commit into
base: main
Choose a base branch
from
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
53 changes: 53 additions & 0 deletions apps/docs/src/examples/chip.module.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
.section {
display: flex;
flex-direction: row;
column-gap: 10px;
}

.chip {
display: flex;
column-gap: 2px;
border: 1px solid black;
width: max-content;
padding: 5px 10px;
border-radius: 30px;
cursor: pointer;
background-color: #0369a0;
}

.chip:hover {
background-color: #0092e0;
}

.chip__label {
color: #fff;
}

.chip[aria-disabled="true"] {
background-color: #0369a0;
opacity: 0.5;
cursor: not-allowed;
}

.chip__deletable {
display: flex;
column-gap: 10px;
border: 1px solid black;
background-color: aliceblue;
width: max-content;
padding: 5px 10px;
border-radius: 30px;
cursor: pointer;
background-color: #0369a0;
}

.chip__deletable:hover {
background-color: #0092e0;
}


.delete {
color: black;
font-weight: 500;
font-size: 16px;
}
25 changes: 25 additions & 0 deletions apps/docs/src/examples/chip.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
import { Chip } from "@kobalte/core/chip";

import style from "./chip.module.css";

console.log('style', style);

export function BasicExample() {
return (
<div class={style.section}>
<Chip onClick={() => console.log('onClickHandler - Chip A')} class={style.chip}>
<Chip.Label class={style.chip__label}>Chip A</Chip.Label>
</Chip>
<Chip onClick={() => console.log('onClickHandler - Chip B')} class={style.chip}>
<Chip.Label class={style.chip__label}>Chip B</Chip.Label>
</Chip>
<Chip onClick={() => console.log('this is a disabled chip')} class={style.chip} disabled={true}>
<Chip.Label class={style.chip__label}>Disabled Chip C</Chip.Label>
</Chip>
<Chip onClick={() => console.log('delete me on click!!!')} class={style.chip__deletable}>
<Chip.Label class={style.chip__label}>Deletable Chip D</Chip.Label>
<span class={style.delete}>X</span>
</Chip>
</div>
)
}
4 changes: 4 additions & 0 deletions apps/docs/src/routes/docs/core.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,10 @@ const CORE_NAV_SECTIONS: NavSection[] = [
title: "Checkbox",
href: "/docs/core/components/checkbox",
},
{
title: "Chip",
href: "/docs/core/components/chip",
},
{
title: "Collapsible",
href: "/docs/core/components/collapsible",
Expand Down
162 changes: 162 additions & 0 deletions apps/docs/src/routes/docs/core/components/chip.mdx
Original file line number Diff line number Diff line change
@@ -0,0 +1,162 @@
import { Preview, TabsSnippets, Kbd } from "../../../../components";
import { BasicExample } from "../../../../examples/chip";

# Chip

- Chips are compact elements that represent an input or an action.

## Import

```ts
import { Chip } from "@kobalte/core/chip";
// or
import { Root } from "@kobalte/core/chip";
// or (deprecated)
import { Chip } from "@kobalte/core";
```

## Features

- **Flexible Usage**: Can be used as a simple display tag, an interactive button, or a deletable item.
- **Keyboard Accessible**: Supports `Enter` and `Space` key interactions to ensure accessibility when clickable.
- **Polymorphic Design**: By default, the chip is a `<div>` with `role="button"` for accessibility but can easily be wrapped in other elements if needed.
- **ARIA Attributes**: Implements necessary ARIA roles for accessibility (`role="button"`, `aria-disabled`).
- **Composable Subcomponents**: Includes a `Chip.Label` component to separate label content from other chip elements.

## Anatomy

The Chip consists of:

- **Chip** - The root container for the chip.
- **Chip.Label** - The label that is highly configurable which gives information to the user.

```tsx
<Chip>
<Chip.Label></Chip.Label>
</Chip>
```

## Example

<Preview>
<BasicExample />
</Preview>

<TabsSnippets>
<TabsSnippets.List>
<TabsSnippets.Trigger value="index.tsx">index.tsx</TabsSnippets.Trigger>
<TabsSnippets.Trigger value="style.css">style.css</TabsSnippets.Trigger>
</TabsSnippets.List>
{/* <!-- prettier-ignore-start -->*/}
<TabsSnippets.Content value="index.tsx">
```tsx
import { Chip } from "@kobalte/core/chip";
import "./style.css";

function App() {
return (
<div class={section}>
<Chip onClick={() => console.log('onClickHandler - Chip A')} class={chip}>
<Chip.Label class={chip__label}>Chip A</Chip.Label>
</Chip>
<Chip onClick={() => console.log('onClickHandler - Chip B')} class={chip}>
<Chip.Label class={chip__label}>Chip B</Chip.Label>
</Chip>
<Chip onClick={() => console.log('this is a disabled chip')} class={chip} disabled={true}>
<Chip.Label class={chip__label}>Disabled Chip C</Chip.Label>
</Chip>
<Chip onClick={() => console.log('delete me on click!!!')} class={chip__deletable}>
<Chip.Label class={chip__label}>Deletable Chip D</Chip.Label>
<span class={delete}>X</span>
</Chip>
</div>
);
}
```

</TabsSnippets.Content>
<TabsSnippets.Content value="style.css">
```css
.section {
display: flex;
flex-direction: row;
column-gap: 10px;
}

.chip {
display: flex;
column-gap: 2px;
border: 1px solid black;
width: max-content;
padding: 5px 10px;
border-radius: 30px;
cursor: pointer;
background-color: #0369a0;
}

.chip:hover {
background-color: #0092e0;
}

.chip__label {
color: #fff;
}

.chip[aria-disabled="true"] {
background-color: #0369a0;
opacity: 0.5;
cursor: not-allowed;
}

.chip__deletable {
display: flex;
column-gap: 10px;
border: 1px solid black;
background-color: aliceblue;
width: max-content;
padding: 5px 10px;
border-radius: 30px;
cursor: pointer;
background-color: #0369a0;
}

.chip__deletable:hover {
background-color: #0092e0;
}

.delete {
color: black;
font-weight: 500;
font-size: 16px;
}
```

</TabsSnippets.Content>
{/* <!-- prettier-ignore-end -->*/}
</TabsSnippets>

## API Reference

### Chip

`Chip` is equivalent to the `Root` import from `@kobalte/core/chip` (and deprecated `Chip.Root`).

| Prop | Description |
| :----------- | :------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| disabled | `boolean` <br/> If true, the component is disabled. |
| onClick | Callback function to handle onClick events. Keyboard events for enter and space would also trigger this callback on pressing `Enter` or `Space`. |

### Chip.Label

| Prop | Description |
| :----------- | :------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| label | `string` <br/> Uses label, if no children is provided |

## Accessibility

### Keyboard Interactions

| Key | Description |
| :--------------- | :------------------------------- |
| <Kbd>Enter</Kbd> | Triggers the onClick handler when pressed. |
| <Kbd>Space</Kbd> | Triggers the onClick handler when pressed. |
Empty file.
22 changes: 22 additions & 0 deletions packages/core/src/chip/chip-label.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
import { type ValidComponent, type JSX, splitProps } from "solid-js";
import { type ElementOf, Polymorphic, type PolymorphicProps } from "../polymorphic";

export interface LabelOptions {
label?: string;
children?: JSX.Element;
}

export interface LabelCommonProps<T extends HTMLElement = HTMLElement> {
id?: string;
style?: JSX.CSSProperties | string;
}

export type LabelProps<
T extends ValidComponent | HTMLElement = HTMLElement,
> = LabelOptions & Partial<LabelCommonProps<ElementOf<T>>>;

export function Label<T extends ValidComponent = "span">(props: PolymorphicProps<T, LabelProps<T>>) {
const [local, others] = splitProps(props, ["label", "children"]);

return <Polymorphic as="span" class="chip__label" {...others}>{local.label || local.children}</Polymorphic>
}
61 changes: 61 additions & 0 deletions packages/core/src/chip/chip-root.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
import { createSignal, mergeProps, splitProps, type JSX, type ValidComponent } from "solid-js";
import { type ElementOf, Polymorphic, type PolymorphicProps } from "../polymorphic";
import { mergeDefaultProps } from "@kobalte/utils";

export interface ChipRootOptions {
/** Event handler called when the chip is clicked. */
onClick?: () => void;
/** Whether to disable the chip or not... */
disabled?: boolean;
/** The children of the chip. */
children?: JSX.Element;
}

export interface ChipCommonProps<T extends HTMLElement = HTMLElement> {
id?: string;
style?: JSX.CSSProperties | string;
}

export type ChipRootProps<
T extends ValidComponent | HTMLElement = HTMLElement,
> = ChipRootOptions & Partial<ChipCommonProps<ElementOf<T>>>;

export function Chip<T extends ValidComponent = "div">(
props: PolymorphicProps<T, ChipRootProps<T>>,
) {
// Merging default values
const mergedProps = mergeDefaultProps(
{ disabled: false },
props as ChipRootProps,
);

const [local, others] = splitProps(mergedProps, ["disabled", "onClick"]);

const handleSelect = () => {
if (!local.disabled) {
local.onClick?.();
}
};

const handleKeyDown = (event: KeyboardEvent) => {
if (!local.disabled && (event.key === "Enter" || event.key === " ")) {
event.preventDefault(); // Prevents scrolling when using the Space key
handleSelect();
}
};

return (
<Polymorphic
as="div"
class="chip__root"
role="button"
tabindex={local.disabled ? -1 : 0}
aria-disabled={local.disabled}
onClick={handleSelect}
onKeyDown={handleKeyDown}
{...others}
>
{props.children}
</Polymorphic>
);
}
Loading