@yarcl/react 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +53 -0
- package/dist/color.d.ts +12 -0
- package/dist/color.js +41 -0
- package/dist/css.d.ts +10 -0
- package/dist/css.js +175 -0
- package/dist/define.d.ts +383 -0
- package/dist/define.js +29 -0
- package/dist/plugin.d.ts +33 -0
- package/dist/plugin.js +80 -0
- package/package.json +66 -0
- package/src/apply.ts +46 -0
- package/src/classes.ts +23 -0
- package/src/color.ts +48 -0
- package/src/components/Accordion.tsx +274 -0
- package/src/components/Alert.tsx +85 -0
- package/src/components/Badge.tsx +79 -0
- package/src/components/Breadcrumb.tsx +239 -0
- package/src/components/Button.tsx +58 -0
- package/src/components/ButtonGroup.tsx +61 -0
- package/src/components/Card.tsx +50 -0
- package/src/components/Checkbox.tsx +49 -0
- package/src/components/Combobox.tsx +522 -0
- package/src/components/CommandPalette.tsx +452 -0
- package/src/components/DatePicker.tsx +432 -0
- package/src/components/Dialog.tsx +39 -0
- package/src/components/Divider.tsx +34 -0
- package/src/components/Drawer.tsx +31 -0
- package/src/components/Field.tsx +61 -0
- package/src/components/Heading.tsx +40 -0
- package/src/components/HoverCard.tsx +108 -0
- package/src/components/IconButton.tsx +59 -0
- package/src/components/Inline.tsx +42 -0
- package/src/components/Input.tsx +28 -0
- package/src/components/Label.tsx +58 -0
- package/src/components/Link.tsx +41 -0
- package/src/components/Menu.tsx +207 -0
- package/src/components/Modal.tsx +164 -0
- package/src/components/NumberInput.tsx +208 -0
- package/src/components/Pagination.tsx +227 -0
- package/src/components/Popover.tsx +140 -0
- package/src/components/Progress.tsx +122 -0
- package/src/components/Radio.tsx +59 -0
- package/src/components/RadioGroup.tsx +114 -0
- package/src/components/Select.tsx +197 -0
- package/src/components/Skeleton.tsx +95 -0
- package/src/components/Slider.tsx +315 -0
- package/src/components/Spinner.tsx +38 -0
- package/src/components/Stack.tsx +57 -0
- package/src/components/Switch.tsx +42 -0
- package/src/components/Table.tsx +99 -0
- package/src/components/Tabs.tsx +159 -0
- package/src/components/Text.tsx +67 -0
- package/src/components/Textarea.tsx +26 -0
- package/src/components/Toast.tsx +190 -0
- package/src/components/ToggleGroup.tsx +227 -0
- package/src/components/Tooltip.tsx +78 -0
- package/src/contrast.ts +82 -0
- package/src/css.ts +228 -0
- package/src/define.check.ts +259 -0
- package/src/define.ts +379 -0
- package/src/field-context.ts +31 -0
- package/src/floating.tsx +61 -0
- package/src/hooks.ts +19 -0
- package/src/index.ts +119 -0
- package/src/plugin.ts +94 -0
- package/src/reference/DesignReference.tsx +374 -0
- package/src/reference/index.ts +2 -0
- package/src/reference/reference.css +58 -0
- package/src/runtime.ts +38 -0
- package/src/styles.css +1971 -0
- package/src/themes/bloom.ts +72 -0
- package/src/themes/brutalist.ts +63 -0
- package/src/themes/compact.ts +63 -0
- package/src/themes/contract.ts +24 -0
- package/src/themes/editorial.ts +55 -0
- package/src/themes/index.ts +47 -0
- package/src/types.ts +69 -0
- package/src/yarcl.config.ts +104 -0
|
@@ -0,0 +1,239 @@
|
|
|
1
|
+
import {
|
|
2
|
+
Children,
|
|
3
|
+
createContext,
|
|
4
|
+
isValidElement,
|
|
5
|
+
useContext,
|
|
6
|
+
useRef,
|
|
7
|
+
useState,
|
|
8
|
+
type ComponentProps,
|
|
9
|
+
type ReactNode,
|
|
10
|
+
} from 'react';
|
|
11
|
+
import { colorClass, cx, typeClass } from '../classes';
|
|
12
|
+
import type { Color, TextStyle } from '../types';
|
|
13
|
+
import { useConfig, useDefaults } from '../runtime';
|
|
14
|
+
import type { LinkProps } from './Link';
|
|
15
|
+
|
|
16
|
+
interface BreadcrumbItemContextValue {
|
|
17
|
+
current: boolean;
|
|
18
|
+
separator: ReactNode;
|
|
19
|
+
color: Color;
|
|
20
|
+
underline: NonNullable<LinkProps['underline']>;
|
|
21
|
+
itemRef?: (node: HTMLElement | null) => void;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
const BreadcrumbItemContext = createContext<BreadcrumbItemContextValue | null>(null);
|
|
25
|
+
|
|
26
|
+
function ChevronIcon() {
|
|
27
|
+
return (
|
|
28
|
+
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round" aria-hidden="true">
|
|
29
|
+
<path d="m9 6 6 6-6 6" />
|
|
30
|
+
</svg>
|
|
31
|
+
);
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
function Separator({ children }: { children: ReactNode }) {
|
|
35
|
+
return (
|
|
36
|
+
<span className="yarcl-breadcrumb-separator" aria-hidden="true">
|
|
37
|
+
{children}
|
|
38
|
+
</span>
|
|
39
|
+
);
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
/** Props for {@link Breadcrumb}. Accepts all native `<nav>` attributes except `color`. */
|
|
43
|
+
export interface BreadcrumbProps extends Omit<ComponentProps<'nav'>, 'color'> {
|
|
44
|
+
/** `Breadcrumb.Item`s, from the root to the current page. */
|
|
45
|
+
children?: ReactNode;
|
|
46
|
+
/**
|
|
47
|
+
* Content between items. Hidden from screen readers.
|
|
48
|
+
* @default a chevron icon
|
|
49
|
+
* @example
|
|
50
|
+
* ```tsx
|
|
51
|
+
* <Breadcrumb separator="/">…</Breadcrumb>
|
|
52
|
+
* ```
|
|
53
|
+
*/
|
|
54
|
+
separator?: ReactNode;
|
|
55
|
+
/** Collapses the middle of the trail into an expandable ellipsis when there are more items than this. Without it, the trail never collapses. */
|
|
56
|
+
maxItems?: number;
|
|
57
|
+
/**
|
|
58
|
+
* Items shown before the ellipsis when collapsed.
|
|
59
|
+
* @default 1
|
|
60
|
+
*/
|
|
61
|
+
itemsBeforeCollapse?: number;
|
|
62
|
+
/**
|
|
63
|
+
* Items shown after the ellipsis when collapsed.
|
|
64
|
+
* @default 2
|
|
65
|
+
*/
|
|
66
|
+
itemsAfterCollapse?: number;
|
|
67
|
+
/**
|
|
68
|
+
* Accessible label of the ellipsis button.
|
|
69
|
+
* @default 'Show all breadcrumbs'
|
|
70
|
+
*/
|
|
71
|
+
expandLabel?: string;
|
|
72
|
+
/**
|
|
73
|
+
* Text style, from the `typography.styles` config.
|
|
74
|
+
* @default config.defaults.textStyle
|
|
75
|
+
*/
|
|
76
|
+
textStyle?: TextStyle;
|
|
77
|
+
/**
|
|
78
|
+
* Link color, from the `colors` config.
|
|
79
|
+
* @default config.defaults.color
|
|
80
|
+
*/
|
|
81
|
+
color?: Color;
|
|
82
|
+
/**
|
|
83
|
+
* When to underline the links.
|
|
84
|
+
* @default 'hover'
|
|
85
|
+
*/
|
|
86
|
+
underline?: LinkProps['underline'];
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
function BreadcrumbRoot({
|
|
90
|
+
children,
|
|
91
|
+
separator = <ChevronIcon />,
|
|
92
|
+
maxItems,
|
|
93
|
+
itemsBeforeCollapse = 1,
|
|
94
|
+
itemsAfterCollapse = 2,
|
|
95
|
+
expandLabel = 'Show all breadcrumbs',
|
|
96
|
+
textStyle,
|
|
97
|
+
color,
|
|
98
|
+
underline = 'hover',
|
|
99
|
+
className,
|
|
100
|
+
'aria-label': ariaLabel = 'Breadcrumb',
|
|
101
|
+
...props
|
|
102
|
+
}: BreadcrumbProps) {
|
|
103
|
+
const config = useConfig();
|
|
104
|
+
const own = useDefaults('Breadcrumb');
|
|
105
|
+
const [expanded, setExpanded] = useState(false);
|
|
106
|
+
const focusNext = useRef(false);
|
|
107
|
+
const items = Children.toArray(children).filter(isValidElement);
|
|
108
|
+
const resolvedColor = color ?? own.color ?? config.defaults.color;
|
|
109
|
+
const before = Math.max(0, itemsBeforeCollapse);
|
|
110
|
+
const after = Math.max(1, itemsAfterCollapse);
|
|
111
|
+
const collapsible = maxItems !== undefined && items.length > maxItems && before + after < items.length;
|
|
112
|
+
const collapsed = collapsible && !expanded;
|
|
113
|
+
const hiddenEnd = items.length - after;
|
|
114
|
+
|
|
115
|
+
const context = (index: number): BreadcrumbItemContextValue => ({
|
|
116
|
+
current: index === items.length - 1,
|
|
117
|
+
separator,
|
|
118
|
+
color: resolvedColor,
|
|
119
|
+
underline,
|
|
120
|
+
itemRef:
|
|
121
|
+
collapsible && index === before
|
|
122
|
+
? (node) => {
|
|
123
|
+
if (node && focusNext.current) {
|
|
124
|
+
focusNext.current = false;
|
|
125
|
+
node.focus();
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
: undefined,
|
|
129
|
+
});
|
|
130
|
+
|
|
131
|
+
const rendered: ReactNode[] = [];
|
|
132
|
+
items.forEach((item, index) => {
|
|
133
|
+
if (collapsed && index >= before && index < hiddenEnd) {
|
|
134
|
+
if (index === before) {
|
|
135
|
+
rendered.push(
|
|
136
|
+
<li key="yarcl-breadcrumb-ellipsis" className="yarcl-breadcrumb-item">
|
|
137
|
+
<button
|
|
138
|
+
type="button"
|
|
139
|
+
className="yarcl-breadcrumb-ellipsis"
|
|
140
|
+
aria-label={expandLabel}
|
|
141
|
+
onClick={() => {
|
|
142
|
+
focusNext.current = true;
|
|
143
|
+
setExpanded(true);
|
|
144
|
+
}}
|
|
145
|
+
>
|
|
146
|
+
<span aria-hidden="true">…</span>
|
|
147
|
+
</button>
|
|
148
|
+
<Separator>{separator}</Separator>
|
|
149
|
+
</li>,
|
|
150
|
+
);
|
|
151
|
+
}
|
|
152
|
+
return;
|
|
153
|
+
}
|
|
154
|
+
rendered.push(
|
|
155
|
+
<BreadcrumbItemContext.Provider key={item.key ?? index} value={context(index)}>
|
|
156
|
+
{item}
|
|
157
|
+
</BreadcrumbItemContext.Provider>,
|
|
158
|
+
);
|
|
159
|
+
});
|
|
160
|
+
|
|
161
|
+
return (
|
|
162
|
+
<nav
|
|
163
|
+
aria-label={ariaLabel}
|
|
164
|
+
className={cx(
|
|
165
|
+
'yarcl-breadcrumb',
|
|
166
|
+
typeClass(textStyle ?? own.textStyle ?? config.defaults.textStyle),
|
|
167
|
+
colorClass(resolvedColor),
|
|
168
|
+
className,
|
|
169
|
+
)}
|
|
170
|
+
{...props}
|
|
171
|
+
>
|
|
172
|
+
<ol className="yarcl-breadcrumb-list">{rendered}</ol>
|
|
173
|
+
</nav>
|
|
174
|
+
);
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
/** Props for `Breadcrumb.Item`. Accepts all native `<a>` attributes except `color`. */
|
|
178
|
+
export interface BreadcrumbItemProps extends Omit<ComponentProps<'a'>, 'color'> {
|
|
179
|
+
/** The item's label. */
|
|
180
|
+
children?: ReactNode;
|
|
181
|
+
/** Where the item links to. Without it, the item renders as plain text. */
|
|
182
|
+
href?: string;
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
function BreadcrumbItem({ href, className, children, ...props }: BreadcrumbItemProps) {
|
|
186
|
+
const context = useContext(BreadcrumbItemContext);
|
|
187
|
+
if (!context) throw new Error('yarcl: <Breadcrumb.Item> must be inside <Breadcrumb>');
|
|
188
|
+
const { current, separator, color, underline, itemRef } = context;
|
|
189
|
+
return (
|
|
190
|
+
<li className="yarcl-breadcrumb-item">
|
|
191
|
+
{href ? (
|
|
192
|
+
<a
|
|
193
|
+
ref={itemRef}
|
|
194
|
+
href={href}
|
|
195
|
+
aria-current={current ? 'page' : undefined}
|
|
196
|
+
className={cx(
|
|
197
|
+
'yarcl-link',
|
|
198
|
+
current ? 'yarcl-breadcrumb-current yarcl-link-underline-none' : `yarcl-link-underline-${underline}`,
|
|
199
|
+
colorClass(color),
|
|
200
|
+
className,
|
|
201
|
+
)}
|
|
202
|
+
{...props}
|
|
203
|
+
>
|
|
204
|
+
{children}
|
|
205
|
+
</a>
|
|
206
|
+
) : (
|
|
207
|
+
<span
|
|
208
|
+
ref={itemRef}
|
|
209
|
+
tabIndex={itemRef ? -1 : undefined}
|
|
210
|
+
aria-current={current ? 'page' : undefined}
|
|
211
|
+
className={cx('yarcl-breadcrumb-text', current && 'yarcl-breadcrumb-current', className)}
|
|
212
|
+
{...(props as ComponentProps<'span'>)}
|
|
213
|
+
>
|
|
214
|
+
{children}
|
|
215
|
+
</span>
|
|
216
|
+
)}
|
|
217
|
+
{!current && <Separator>{separator}</Separator>}
|
|
218
|
+
</li>
|
|
219
|
+
);
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
/**
|
|
223
|
+
* Shows where the current page sits in a hierarchy. The last item is the current page
|
|
224
|
+
* and gets `aria-current="page"`. Set `maxItems` to collapse long trails into an ellipsis
|
|
225
|
+
* that expands the full trail.
|
|
226
|
+
*
|
|
227
|
+
* @example
|
|
228
|
+
* ```tsx
|
|
229
|
+
* <Breadcrumb maxItems={4}>
|
|
230
|
+
* <Breadcrumb.Item href="/">Home</Breadcrumb.Item>
|
|
231
|
+
* <Breadcrumb.Item href="/projects">Projects</Breadcrumb.Item>
|
|
232
|
+
* <Breadcrumb.Item href="/projects/apollo">Apollo</Breadcrumb.Item>
|
|
233
|
+
* <Breadcrumb.Item>Settings</Breadcrumb.Item>
|
|
234
|
+
* </Breadcrumb>
|
|
235
|
+
* ```
|
|
236
|
+
*/
|
|
237
|
+
export const Breadcrumb = Object.assign(BreadcrumbRoot, {
|
|
238
|
+
Item: BreadcrumbItem,
|
|
239
|
+
});
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
import type { ComponentProps } from 'react';
|
|
2
|
+
import { colorClass, cx, radiusClass, sizeClass, variantClass } from '../classes';
|
|
3
|
+
import type { TokenProps, VariantProps } from '../types';
|
|
4
|
+
import { useButtonGroup } from './ButtonGroup';
|
|
5
|
+
import { Spinner } from './Spinner';
|
|
6
|
+
import { useDefaults } from '../runtime';
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
/** Props for {@link Button}. Accepts all native `<button>` attributes except `color`. */
|
|
10
|
+
export interface ButtonProps extends Omit<ComponentProps<'button'>, 'color'>, TokenProps, VariantProps {
|
|
11
|
+
/** Shows a {@link Spinner}, disables the button and sets `aria-busy`. */
|
|
12
|
+
loading?: boolean;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* A button styled from the consumer's design tokens.
|
|
17
|
+
* Shares the size scale with {@link Input}, so both have the same height at the same size.
|
|
18
|
+
* Icons (`<svg>`) inside are sized from the size's `iconSize`.
|
|
19
|
+
*
|
|
20
|
+
* @example
|
|
21
|
+
* ```tsx
|
|
22
|
+
* <Button size="lg" color="danger" variant="outline" radius="pill">Delete</Button>
|
|
23
|
+
* ```
|
|
24
|
+
*/
|
|
25
|
+
export function Button({
|
|
26
|
+
size,
|
|
27
|
+
radius,
|
|
28
|
+
color,
|
|
29
|
+
variant,
|
|
30
|
+
loading,
|
|
31
|
+
disabled,
|
|
32
|
+
className,
|
|
33
|
+
type = 'button',
|
|
34
|
+
children,
|
|
35
|
+
...props
|
|
36
|
+
}: ButtonProps) {
|
|
37
|
+
const own = useDefaults('Button');
|
|
38
|
+
const group = useButtonGroup();
|
|
39
|
+
return (
|
|
40
|
+
<button
|
|
41
|
+
type={type}
|
|
42
|
+
className={cx(
|
|
43
|
+
'yarcl-button',
|
|
44
|
+
sizeClass(size ?? group?.size ?? own.size),
|
|
45
|
+
radiusClass(radius ?? group?.radius ?? own.radius, size ?? group?.size ?? own.size),
|
|
46
|
+
colorClass(color ?? group?.color ?? own.color),
|
|
47
|
+
variantClass(variant ?? group?.variant ?? own.variant),
|
|
48
|
+
className,
|
|
49
|
+
)}
|
|
50
|
+
disabled={disabled || loading}
|
|
51
|
+
aria-busy={loading || undefined}
|
|
52
|
+
{...props}
|
|
53
|
+
>
|
|
54
|
+
{loading && <Spinner label="" aria-hidden="true" />}
|
|
55
|
+
{children}
|
|
56
|
+
</button>
|
|
57
|
+
);
|
|
58
|
+
}
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
import { createContext, useContext, type ComponentProps } from 'react';
|
|
2
|
+
import { cx } from '../classes';
|
|
3
|
+
import type { TokenProps, VariantProps } from '../types';
|
|
4
|
+
|
|
5
|
+
const ButtonGroupContext = createContext<(TokenProps & VariantProps) | null>(null);
|
|
6
|
+
|
|
7
|
+
export function useButtonGroup() {
|
|
8
|
+
return useContext(ButtonGroupContext);
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
/** Props for {@link ButtonGroup}. Token props are passed down to every button inside. */
|
|
12
|
+
export interface ButtonGroupProps extends Omit<ComponentProps<'div'>, 'color'>, TokenProps, VariantProps {
|
|
13
|
+
/**
|
|
14
|
+
* Joins the buttons into one control with shared borders.
|
|
15
|
+
* @default true
|
|
16
|
+
*/
|
|
17
|
+
attached?: boolean;
|
|
18
|
+
/**
|
|
19
|
+
* Direction of the buttons.
|
|
20
|
+
* @default 'horizontal'
|
|
21
|
+
*/
|
|
22
|
+
orientation?: 'horizontal' | 'vertical';
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* Groups related {@link Button}s and {@link IconButton}s. Buttons inherit `size`, `color`,
|
|
27
|
+
* `variant` and `radius` from the group unless they set their own. Give the group an `aria-label`.
|
|
28
|
+
*
|
|
29
|
+
* @example
|
|
30
|
+
* ```tsx
|
|
31
|
+
* <ButtonGroup aria-label="Pagination" variant="outline">
|
|
32
|
+
* <Button>Previous</Button>
|
|
33
|
+
* <Button>Next</Button>
|
|
34
|
+
* </ButtonGroup>
|
|
35
|
+
* ```
|
|
36
|
+
*/
|
|
37
|
+
export function ButtonGroup({
|
|
38
|
+
size,
|
|
39
|
+
color,
|
|
40
|
+
variant,
|
|
41
|
+
radius,
|
|
42
|
+
attached = true,
|
|
43
|
+
orientation = 'horizontal',
|
|
44
|
+
className,
|
|
45
|
+
...props
|
|
46
|
+
}: ButtonGroupProps) {
|
|
47
|
+
return (
|
|
48
|
+
<ButtonGroupContext.Provider value={{ size, color, variant, radius }}>
|
|
49
|
+
<div
|
|
50
|
+
role="group"
|
|
51
|
+
className={cx(
|
|
52
|
+
'yarcl-button-group',
|
|
53
|
+
`yarcl-button-group-${orientation}`,
|
|
54
|
+
attached && 'yarcl-button-group-attached',
|
|
55
|
+
className,
|
|
56
|
+
)}
|
|
57
|
+
{...props}
|
|
58
|
+
/>
|
|
59
|
+
</ButtonGroupContext.Provider>
|
|
60
|
+
);
|
|
61
|
+
}
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import type { ComponentProps } from 'react';
|
|
2
|
+
import { cx, paddingClass, radiusClass, shadowClass } from '../classes';
|
|
3
|
+
import type { Radius, Shadow, Spacing } from '../types';
|
|
4
|
+
import { useDefaults } from '../runtime';
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
/** Props for {@link Card}. */
|
|
8
|
+
export interface CardProps extends ComponentProps<'div'> {
|
|
9
|
+
/**
|
|
10
|
+
* Element to render.
|
|
11
|
+
* @default 'div'
|
|
12
|
+
*/
|
|
13
|
+
as?: 'div' | 'section' | 'article' | 'aside';
|
|
14
|
+
/**
|
|
15
|
+
* Inner padding, from the `spacing` config.
|
|
16
|
+
* @default config.defaults.padding
|
|
17
|
+
*/
|
|
18
|
+
padding?: Spacing;
|
|
19
|
+
/**
|
|
20
|
+
* Corner radius, from the `radii` config.
|
|
21
|
+
* @default config.defaults.radius
|
|
22
|
+
*/
|
|
23
|
+
radius?: Radius;
|
|
24
|
+
/** Elevation, from the `shadows` config. No shadow when omitted. */
|
|
25
|
+
shadow?: Shadow;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* A bordered surface for grouping content.
|
|
30
|
+
*
|
|
31
|
+
* @example
|
|
32
|
+
* ```tsx
|
|
33
|
+
* <Card shadow="md">
|
|
34
|
+
* <Stack>
|
|
35
|
+
* <Heading level={3}>Plan</Heading>
|
|
36
|
+
* <Text muted>Pro, billed yearly</Text>
|
|
37
|
+
* </Stack>
|
|
38
|
+
* </Card>
|
|
39
|
+
* ```
|
|
40
|
+
*/
|
|
41
|
+
export function Card({ as = 'div', padding, radius, shadow, className, ...props }: CardProps) {
|
|
42
|
+
const own = useDefaults('Card');
|
|
43
|
+
const Tag = as as 'div';
|
|
44
|
+
return (
|
|
45
|
+
<Tag
|
|
46
|
+
className={cx('yarcl-card', paddingClass(padding ?? own.padding), radiusClass(radius ?? own.radius), shadowClass(shadow ?? own.shadow), className)}
|
|
47
|
+
{...props}
|
|
48
|
+
/>
|
|
49
|
+
);
|
|
50
|
+
}
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import { useEffect, useImperativeHandle, useRef, type ComponentProps, type ReactNode } from 'react';
|
|
2
|
+
import { colorClass, cx, sizeClass } from '../classes';
|
|
3
|
+
import { useFieldProps } from '../field-context';
|
|
4
|
+
import type { Color, Size } from '../types';
|
|
5
|
+
import { useDefaults } from '../runtime';
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
/** Props for {@link Checkbox}. Accepts all native `<input>` attributes except `type`, `color` and `size`. */
|
|
9
|
+
export interface CheckboxProps extends Omit<ComponentProps<'input'>, 'type' | 'color' | 'size' | 'children'> {
|
|
10
|
+
/** Label shown next to the box. */
|
|
11
|
+
children?: ReactNode;
|
|
12
|
+
/**
|
|
13
|
+
* Box size, from the `sizes` config (uses the size's `iconSize`).
|
|
14
|
+
* @default config.defaults.size
|
|
15
|
+
*/
|
|
16
|
+
size?: Size;
|
|
17
|
+
/**
|
|
18
|
+
* Color of the checked box, from the `colors` config.
|
|
19
|
+
* @default config.defaults.color
|
|
20
|
+
*/
|
|
21
|
+
color?: Color;
|
|
22
|
+
/** Shows a mixed state. Cleared by the browser when the user toggles the box. */
|
|
23
|
+
indeterminate?: boolean;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* A checkbox with an optional label.
|
|
28
|
+
*
|
|
29
|
+
* @example
|
|
30
|
+
* ```tsx
|
|
31
|
+
* <Checkbox defaultChecked>Remember me</Checkbox>
|
|
32
|
+
* ```
|
|
33
|
+
*/
|
|
34
|
+
export function Checkbox(props: CheckboxProps) {
|
|
35
|
+
const own = useDefaults('Checkbox');
|
|
36
|
+
const { children, size, color, indeterminate = false, className, style, ref, ...rest } = useFieldProps(props);
|
|
37
|
+
const inner = useRef<HTMLInputElement>(null);
|
|
38
|
+
useImperativeHandle(ref, () => inner.current!, []);
|
|
39
|
+
useEffect(() => {
|
|
40
|
+
if (inner.current) inner.current.indeterminate = indeterminate;
|
|
41
|
+
}, [indeterminate]);
|
|
42
|
+
|
|
43
|
+
return (
|
|
44
|
+
<label className={cx('yarcl-checkbox', sizeClass(size ?? own.size), colorClass(color ?? own.color), className)} style={style}>
|
|
45
|
+
<input ref={inner} type="checkbox" className="yarcl-checkbox-input" {...rest} />
|
|
46
|
+
{children != null && <span className="yarcl-checkbox-label">{children}</span>}
|
|
47
|
+
</label>
|
|
48
|
+
);
|
|
49
|
+
}
|