Skip to content

Commit f68d9cd

Browse files
committed
feat(primitives): add Button component
1 parent 70c2f24 commit f68d9cd

File tree

16 files changed

+749
-0
lines changed

16 files changed

+749
-0
lines changed
Lines changed: 146 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,146 @@
1+
# Button
2+
3+
Trigger an action or event, such as submitting a form, opening a dialog, or performing operations like canceling or deleting.
4+
5+
## Features
6+
7+
- Built with a native `button` element.
8+
9+
- Supports a `pending` state to indicate ongoing actions and provide feedback to assistive technologies.
10+
11+
- Keyboard event support for `Space` and `Enter` keys.
12+
13+
## Import
14+
15+
```tsx
16+
import { Button } from 'qwik-primitives';
17+
```
18+
19+
## Anatomy
20+
21+
Import all parts and piece them together.
22+
23+
```tsx
24+
import { component$ } from '@builder.io/qwik';
25+
import { Button } from 'qwik-primitives';
26+
27+
const ButtonDemo = component$(() => {
28+
return (
29+
<Button.Root>
30+
<Button.Content />
31+
<Button.Fallback />
32+
</Button.Root>
33+
);
34+
});
35+
```
36+
37+
## Usage
38+
39+
```tsx
40+
import { component$ } from '@builder.io/qwik';
41+
import { Button } from 'qwik-primitives';
42+
43+
const ButtonDemo = component$(() => {
44+
return (
45+
<Button.Root>
46+
<Button.Content>Save data</Button.Content>
47+
</Button.Root>
48+
);
49+
});
50+
```
51+
52+
## API Reference
53+
54+
### Root
55+
56+
Contains all the parts of a button. This component is based on the `button` element.
57+
58+
| Prop | Type | Default | Description |
59+
| ---------- | --------------------------------- | ---------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
60+
| `as` | `FunctionComponent` | `-` | Change the default rendered element for the one passed as, merging their props and behavior. Read our [Composition](https://github.com/ZAHON/qwik-primitives/blob/main/packages/primitives/docs/composition.md) guide for more details. |
61+
| `type` | `"button" \| "submit" \| "reset"` | `"button"` | The behavior of the button when used in an HTML form. |
62+
| `disabled` | `boolean` | `-` | When `true`, prevents the user from interacting with the button. |
63+
| `pending` | `boolean` | `-` | Whether the button is in a pending state. |
64+
| `style` | `CSSProperties` | `-` | The inline style for the element. |
65+
66+
| Data attribute | Values |
67+
| ----------------- | ------------------------------------------------------- |
68+
| `[data-scope]` | `"button"` |
69+
| `[data-part]` | `"root"` |
70+
| `[data-disabled]` | Present when the button is disabled |
71+
| `[data-pending]` | Present when the button is currently in a pending state |
72+
73+
### Content
74+
75+
Contains the content for the button. By default, it will render only when the button is not currently in a `pending` state, use the `forceMount` prop to always render the content. This component is based on the `span` element.
76+
77+
| Prop | Type | Default | Description |
78+
| ------------ | ------------------- | ------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
79+
| `as` | `FunctionComponent` | `-` | Change the default rendered element for the one passed as, merging their props and behavior. Read our [Composition](https://github.com/ZAHON/qwik-primitives/blob/main/packages/primitives/docs/composition.md) guide for more details. |
80+
| `forceMount` | `boolean` | `-` | Used to force mounting when more control is needed. |
81+
| `style` | `CSSProperties` | `-` | The inline style for the element. |
82+
83+
| Data attribute | Values |
84+
| ----------------- | ------------------------------------------------------- |
85+
| `[data-scope]` | `"button"` |
86+
| `[data-part]` | `"content"` |
87+
| `[data-disabled]` | Present when the button is disabled |
88+
| `[data-pending]` | Present when the button is currently in a pending state |
89+
90+
### Fallback
91+
92+
An element that renders when the button is currently in a `pending` state. For more control use the `forceMount` prop to always render the component. This component is based on the `span` element.
93+
94+
| Prop | Type | Default | Description |
95+
| ------------ | ------------------- | ------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
96+
| `as` | `FunctionComponent` | `-` | Change the default rendered element for the one passed as, merging their props and behavior. Read our [Composition](https://github.com/ZAHON/qwik-primitives/blob/main/packages/primitives/docs/composition.md) guide for more details. |
97+
| `forceMount` | `boolean` | `-` | Used to force mounting when more control is needed. |
98+
| `style` | `CSSProperties` | `-` | The inline style for the element. |
99+
100+
| Data attribute | Values |
101+
| ----------------- | ------------------------------------------------------- |
102+
| `[data-scope]` | `"button"` |
103+
| `[data-part]` | `"fallback"` |
104+
| `[data-disabled]` | Present when the button is disabled |
105+
| `[data-pending]` | Present when the button is currently in a pending state |
106+
107+
## Examples
108+
109+
### Pending
110+
111+
A button can be put into a pending state using the `pending` prop. This is useful when an action takes a long time to complete, and you want to provide feedback to the user that the action is in progress. The pending state announces the state change to assistive technologies.
112+
113+
```tsx
114+
import { component$, useSignal, $ } from '@builder.io/qwik';
115+
import { Button } from 'qwik-primitives';
116+
117+
const ButtonDemo = component$(() => {
118+
const isPending = useSignal(false);
119+
120+
const handleClick$ = $(() => {
121+
if (!isPending.value) {
122+
isPending.value = true;
123+
124+
setTimeout(() => {
125+
isPending.value = false;
126+
}, 5000);
127+
}
128+
});
129+
130+
return (
131+
<Button.Root pending={isPending.value} onClick$={handleClick$}>
132+
<Button.Content>Save data</Button.Content>
133+
<Button.Fallback>Saving...</Button.Fallback>
134+
</Button.Root>
135+
);
136+
});
137+
```
138+
139+
## Accessibility
140+
141+
### Keyboard Interactions
142+
143+
| Key | Description |
144+
| ------- | --------------------- |
145+
| `Space` | Activates the button. |
146+
| `Enter` | Activates the button. |
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
import type { ButtonContentProps } from './button-content.types';
2+
import { component$, useContext, Slot } from '@builder.io/qwik';
3+
import { ButtonContext } from '../button-context';
4+
5+
/**
6+
* Contains the content for the button.
7+
* By default, it will render only when the button is not currently in a `pending` state,
8+
* use the `forceMount` prop to always render the content.
9+
* This component is based on the `span` element.
10+
*/
11+
export const ButtonContent = component$<ButtonContentProps>((props) => {
12+
const { as, forceMount, ...others } = props;
13+
14+
const { isDisabled, isPending } = useContext(ButtonContext);
15+
16+
const Component = as || 'span';
17+
18+
return (
19+
(forceMount || !isPending.value) && (
20+
<Component
21+
aria-hidden={forceMount && isPending.value ? 'true' : undefined}
22+
data-qwik-primitives-button-content=""
23+
data-scope="button"
24+
data-part="content"
25+
data-disabled={isDisabled.value ? '' : undefined}
26+
data-pending={isPending.value ? '' : undefined}
27+
{...others}
28+
>
29+
<Slot />
30+
</Component>
31+
)
32+
);
33+
});
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
import type { PropsOf, FunctionComponent, CSSProperties } from '@builder.io/qwik';
2+
3+
export interface ButtonContentProps extends PropsOf<'span'> {
4+
/**
5+
* Change the default rendered element for the one passed as, merging their props and behavior.
6+
*
7+
* Read our [Composition](https://github.com/ZAHON/qwik-primitives/blob/main/packages/primitives/docs/composition.md) guide for more details.
8+
*/
9+
as?: FunctionComponent;
10+
11+
/**
12+
* Used to force mounting when more control is needed.
13+
*/
14+
forceMount?: boolean;
15+
16+
/**
17+
* The inline style for the element.
18+
*/
19+
style?: CSSProperties;
20+
}
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
export type { ButtonContentProps } from './button-content.types';
2+
export { ButtonContent } from './button-content';
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
import type { ButtonContextValue } from './button-context.types';
2+
import { createContextId } from '@builder.io/qwik';
3+
4+
export const ButtonContext = createContextId<ButtonContextValue>('qwik-primitives-button-context');
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
import type { ReadonlySignal } from '@builder.io/qwik';
2+
3+
export interface ButtonContextValue {
4+
/**
5+
* When `true`, prevents the user from interacting with the button.
6+
*/
7+
isDisabled: ReadonlySignal<boolean | undefined>;
8+
9+
/**
10+
* Whether the button is in a pending state.
11+
*/
12+
isPending: ReadonlySignal<boolean | undefined>;
13+
}
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
export type { ButtonContextValue } from './button-context.types';
2+
export { ButtonContext } from './button-context';
Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
import type { ButtonFallbackProps } from './button-fallback.types';
2+
import { component$, useContext, Slot } from '@builder.io/qwik';
3+
import { ButtonContext } from '../button-context';
4+
5+
/**
6+
* An element that renders when the button is currently in a `pending` state.
7+
* For more control use the `forceMount` prop to always render the component.
8+
* This component is based on the `span` element.
9+
*/
10+
export const ButtonFallback = component$<ButtonFallbackProps>((props) => {
11+
const { as, forceMount, ...others } = props;
12+
13+
const { isDisabled, isPending } = useContext(ButtonContext);
14+
15+
const Component = as || 'span';
16+
17+
return (
18+
(forceMount || isPending.value) && (
19+
<Component
20+
aria-hidden={forceMount && !isPending.value ? 'true' : undefined}
21+
data-qwik-primitives-button-fallback=""
22+
data-scope="button"
23+
data-part="fallback"
24+
data-disabled={isDisabled.value ? '' : undefined}
25+
data-pending={isPending.value ? '' : undefined}
26+
{...others}
27+
>
28+
<Slot />
29+
</Component>
30+
)
31+
);
32+
});
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
import type { PropsOf, FunctionComponent, CSSProperties } from '@builder.io/qwik';
2+
3+
export interface ButtonFallbackProps extends PropsOf<'span'> {
4+
/**
5+
* Change the default rendered element for the one passed as, merging their props and behavior.
6+
*
7+
* Read our [Composition](https://github.com/ZAHON/qwik-primitives/blob/main/packages/primitives/docs/composition.md) guide for more details.
8+
*/
9+
as?: FunctionComponent;
10+
11+
/**
12+
* Used to force mounting when more control is needed.
13+
*/
14+
forceMount?: boolean;
15+
16+
/**
17+
* The inline style for the element.
18+
*/
19+
style?: CSSProperties;
20+
}
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
export type { ButtonFallbackProps } from './button-fallback.types';
2+
export { ButtonFallback } from './button-fallback';

0 commit comments

Comments
 (0)