@yarcl/react 0.1.0 → 0.2.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/README.md +14 -2
- package/{src/apply.ts → dist/apply.d.ts} +4 -23
- package/dist/apply.js +37 -0
- package/dist/classes.d.ts +13 -0
- package/dist/classes.js +22 -0
- package/dist/cli.d.ts +2 -0
- package/dist/cli.js +58 -0
- package/dist/color.js +49 -32
- package/dist/components/Accordion.d.ts +96 -0
- package/dist/components/Accordion.js +148 -0
- package/dist/components/Alert.d.ts +60 -0
- package/dist/components/Alert.js +61 -0
- package/dist/components/Avatar.d.ts +100 -0
- package/dist/components/Avatar.js +89 -0
- package/dist/components/Badge.d.ts +44 -0
- package/dist/components/Badge.js +43 -0
- package/dist/components/Breadcrumb.d.ts +77 -0
- package/dist/components/Breadcrumb.js +126 -0
- package/dist/components/Button.d.ts +18 -0
- package/dist/components/Button.js +33 -0
- package/dist/components/ButtonGroup.d.ts +29 -0
- package/dist/components/ButtonGroup.js +37 -0
- package/dist/components/Card.d.ts +36 -0
- package/dist/components/Card.js +26 -0
- package/dist/components/Checkbox.d.ts +28 -0
- package/dist/components/Checkbox.js +38 -0
- package/dist/components/Combobox.d.ts +75 -0
- package/dist/components/Combobox.js +362 -0
- package/dist/components/CommandPalette.d.ts +92 -0
- package/dist/components/CommandPalette.js +293 -0
- package/dist/components/DatePicker.d.ts +157 -0
- package/dist/components/DatePicker.js +230 -0
- package/dist/components/Dialog.d.ts +25 -0
- package/dist/components/Dialog.js +29 -0
- package/dist/components/Divider.d.ts +23 -0
- package/dist/components/Divider.js +25 -0
- package/dist/components/Drawer.d.ts +21 -0
- package/dist/components/Drawer.js +25 -0
- package/dist/components/EmptyState.d.ts +51 -0
- package/dist/components/EmptyState.js +47 -0
- package/dist/components/Field.d.ts +27 -0
- package/dist/components/Field.js +59 -0
- package/dist/components/Heading.d.ts +26 -0
- package/dist/components/Heading.js +25 -0
- package/dist/components/HoverCard.d.ts +47 -0
- package/dist/components/HoverCard.js +53 -0
- package/dist/components/IconButton.d.ts +19 -0
- package/dist/components/IconButton.js +32 -0
- package/dist/components/Inline.d.ts +21 -0
- package/dist/components/Inline.js +24 -0
- package/dist/components/Input.d.ts +19 -0
- package/dist/components/Input.js +25 -0
- package/dist/components/Label.d.ts +46 -0
- package/dist/components/Label.js +33 -0
- package/dist/components/Link.d.ts +27 -0
- package/dist/components/Link.js +26 -0
- package/dist/components/Menu.d.ts +73 -0
- package/dist/components/Menu.js +143 -0
- package/dist/components/Modal.d.ts +45 -0
- package/dist/components/Modal.js +110 -0
- package/dist/components/NumberInput.d.ts +49 -0
- package/dist/components/NumberInput.js +130 -0
- package/dist/components/Pagination.d.ts +86 -0
- package/dist/components/Pagination.js +119 -0
- package/dist/components/Popover.d.ts +72 -0
- package/dist/components/Popover.js +87 -0
- package/dist/components/Progress.d.ts +60 -0
- package/dist/components/Progress.js +59 -0
- package/dist/components/Radio.d.ts +32 -0
- package/dist/components/Radio.js +47 -0
- package/dist/components/RadioGroup.d.ts +57 -0
- package/dist/components/RadioGroup.js +68 -0
- package/dist/components/Select.d.ts +47 -0
- package/dist/components/Select.js +138 -0
- package/dist/components/Skeleton.d.ts +46 -0
- package/dist/components/Skeleton.js +40 -0
- package/dist/components/Slider.d.ts +126 -0
- package/dist/components/Slider.js +166 -0
- package/dist/components/Spinner.d.ts +23 -0
- package/dist/components/Spinner.js +23 -0
- package/dist/components/Stack.d.ts +35 -0
- package/dist/components/Stack.js +24 -0
- package/dist/components/Switch.d.ts +27 -0
- package/dist/components/Switch.js +33 -0
- package/dist/components/Table.d.ts +71 -0
- package/dist/components/Table.js +61 -0
- package/dist/components/Tabs.d.ts +66 -0
- package/dist/components/Tabs.js +109 -0
- package/dist/components/Text.d.ts +32 -0
- package/dist/components/Text.js +28 -0
- package/dist/components/Textarea.d.ts +15 -0
- package/dist/components/Textarea.js +24 -0
- package/dist/components/Toast.d.ts +90 -0
- package/dist/components/Toast.js +156 -0
- package/dist/components/ToggleGroup.d.ts +79 -0
- package/dist/components/ToggleGroup.js +101 -0
- package/dist/components/Tooltip.d.ts +46 -0
- package/dist/components/Tooltip.js +62 -0
- package/dist/components/VisuallyHidden.d.ts +18 -0
- package/dist/components/VisuallyHidden.js +22 -0
- package/dist/contrast.d.ts +32 -0
- package/dist/css.d.ts +2 -2
- package/dist/css.js +193 -157
- package/dist/define.d.ts +40 -15
- package/dist/define.js +30 -27
- package/dist/demo.d.ts +2 -0
- package/dist/demo.js +2 -0
- package/dist/esbuild.cjs +5 -0
- package/dist/esbuild.d.ts +4 -0
- package/dist/esbuild.js +6 -0
- package/dist/field-context.d.ts +16 -0
- package/dist/field-context.js +17 -0
- package/dist/floating.d.ts +12 -0
- package/dist/floating.js +52 -0
- package/dist/hooks.d.ts +1 -0
- package/dist/hooks.js +12 -0
- package/{src/index.ts → dist/index.d.ts} +11 -33
- package/dist/index.js +50 -0
- package/dist/init.d.ts +18 -0
- package/dist/init.js +224 -0
- package/dist/plugin.d.ts +10 -26
- package/dist/plugin.js +152 -78
- package/dist/reference/DemoYarcl.d.ts +12 -0
- package/dist/reference/DemoYarcl.js +312 -0
- package/dist/reference/DesignReference.d.ts +24 -0
- package/dist/reference/DesignReference.js +403 -0
- package/dist/reference/index.js +2 -0
- package/dist/rollup.cjs +5 -0
- package/dist/rollup.d.ts +4 -0
- package/dist/rollup.js +6 -0
- package/dist/rspack.cjs +5 -0
- package/dist/rspack.d.ts +4 -0
- package/dist/rspack.js +6 -0
- package/dist/runtime.d.ts +13 -0
- package/dist/runtime.js +30 -0
- package/dist/setup.d.ts +6 -0
- package/dist/setup.js +33 -0
- package/{src → dist}/styles.css +224 -106
- package/dist/themes/bloom.d.ts +295 -0
- package/dist/themes/bloom.js +202 -0
- package/dist/themes/brutalist.d.ts +277 -0
- package/dist/themes/brutalist.js +199 -0
- package/dist/themes/compact.d.ts +274 -0
- package/dist/themes/compact.js +200 -0
- package/dist/themes/contract.d.ts +23 -0
- package/dist/themes/editorial.d.ts +277 -0
- package/dist/themes/editorial.js +183 -0
- package/dist/themes/index.d.ts +1425 -0
- package/dist/themes/index.js +45 -0
- package/{src/types.ts → dist/types.d.ts} +40 -37
- package/dist/vite.cjs +5 -0
- package/dist/vite.d.ts +4 -0
- package/dist/vite.js +6 -0
- package/dist/webpack.cjs +5 -0
- package/dist/webpack.d.ts +4 -0
- package/dist/webpack.js +6 -0
- package/dist/yarcl.config.d.ts +276 -0
- package/dist/yarcl.config.js +273 -0
- package/package.json +69 -17
- package/src/classes.ts +0 -23
- package/src/color.ts +0 -48
- package/src/components/Accordion.tsx +0 -274
- package/src/components/Alert.tsx +0 -85
- package/src/components/Badge.tsx +0 -79
- package/src/components/Breadcrumb.tsx +0 -239
- package/src/components/Button.tsx +0 -58
- package/src/components/ButtonGroup.tsx +0 -61
- package/src/components/Card.tsx +0 -50
- package/src/components/Checkbox.tsx +0 -49
- package/src/components/Combobox.tsx +0 -522
- package/src/components/CommandPalette.tsx +0 -452
- package/src/components/DatePicker.tsx +0 -432
- package/src/components/Dialog.tsx +0 -39
- package/src/components/Divider.tsx +0 -34
- package/src/components/Drawer.tsx +0 -31
- package/src/components/Field.tsx +0 -61
- package/src/components/Heading.tsx +0 -40
- package/src/components/HoverCard.tsx +0 -108
- package/src/components/IconButton.tsx +0 -59
- package/src/components/Inline.tsx +0 -42
- package/src/components/Input.tsx +0 -28
- package/src/components/Label.tsx +0 -58
- package/src/components/Link.tsx +0 -41
- package/src/components/Menu.tsx +0 -207
- package/src/components/Modal.tsx +0 -164
- package/src/components/NumberInput.tsx +0 -208
- package/src/components/Pagination.tsx +0 -227
- package/src/components/Popover.tsx +0 -140
- package/src/components/Progress.tsx +0 -122
- package/src/components/Radio.tsx +0 -59
- package/src/components/RadioGroup.tsx +0 -114
- package/src/components/Select.tsx +0 -197
- package/src/components/Skeleton.tsx +0 -95
- package/src/components/Slider.tsx +0 -315
- package/src/components/Spinner.tsx +0 -38
- package/src/components/Stack.tsx +0 -57
- package/src/components/Switch.tsx +0 -42
- package/src/components/Table.tsx +0 -99
- package/src/components/Tabs.tsx +0 -159
- package/src/components/Text.tsx +0 -67
- package/src/components/Textarea.tsx +0 -26
- package/src/components/Toast.tsx +0 -190
- package/src/components/ToggleGroup.tsx +0 -227
- package/src/components/Tooltip.tsx +0 -78
- package/src/contrast.ts +0 -82
- package/src/css.ts +0 -228
- package/src/define.check.ts +0 -259
- package/src/define.ts +0 -379
- package/src/field-context.ts +0 -31
- package/src/floating.tsx +0 -61
- package/src/hooks.ts +0 -19
- package/src/plugin.ts +0 -94
- package/src/reference/DesignReference.tsx +0 -374
- package/src/runtime.ts +0 -38
- package/src/themes/bloom.ts +0 -72
- package/src/themes/brutalist.ts +0 -63
- package/src/themes/compact.ts +0 -63
- package/src/themes/contract.ts +0 -24
- package/src/themes/editorial.ts +0 -55
- package/src/themes/index.ts +0 -47
- package/src/yarcl.config.ts +0 -104
- /package/{src/reference/index.ts → dist/reference/index.d.ts} +0 -0
- /package/{src → dist}/reference/reference.css +0 -0
package/src/components/Tabs.tsx
DELETED
|
@@ -1,159 +0,0 @@
|
|
|
1
|
-
import {
|
|
2
|
-
createContext,
|
|
3
|
-
useContext,
|
|
4
|
-
useId,
|
|
5
|
-
type ComponentProps,
|
|
6
|
-
type KeyboardEvent,
|
|
7
|
-
type ReactNode,
|
|
8
|
-
} from 'react';
|
|
9
|
-
import { colorClass, cx, sizeClass } from '../classes';
|
|
10
|
-
import { useControllable } from '../hooks';
|
|
11
|
-
import type { Color, Size } from '../types';
|
|
12
|
-
import { useDefaults } from '../runtime';
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
interface TabsContextValue {
|
|
16
|
-
value: string;
|
|
17
|
-
select: (value: string) => void;
|
|
18
|
-
idFor: (kind: 'tab' | 'panel', value: string) => string;
|
|
19
|
-
}
|
|
20
|
-
|
|
21
|
-
const TabsContext = createContext<TabsContextValue | null>(null);
|
|
22
|
-
|
|
23
|
-
function useTabsContext(component: string) {
|
|
24
|
-
const context = useContext(TabsContext);
|
|
25
|
-
if (!context) throw new Error(`yarcl: <${component}> must be inside <Tabs>`);
|
|
26
|
-
return context;
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
/** Props shared by both forms of {@link TabsProps}. */
|
|
30
|
-
export interface TabsBaseProps extends Omit<ComponentProps<'div'>, 'defaultValue' | 'color'> {
|
|
31
|
-
/** Called with the newly selected tab's value. */
|
|
32
|
-
onValueChange?: (value: string) => void;
|
|
33
|
-
/**
|
|
34
|
-
* Tab height and font size, from the `sizes` config.
|
|
35
|
-
* @default config.defaults.size
|
|
36
|
-
*/
|
|
37
|
-
size?: Size;
|
|
38
|
-
/**
|
|
39
|
-
* Color of the selected tab's indicator, from the `colors` config.
|
|
40
|
-
* @default config.defaults.color
|
|
41
|
-
*/
|
|
42
|
-
color?: Color;
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
/** Props for {@link Tabs}. Pass `value` (controlled) or `defaultValue` (uncontrolled). */
|
|
46
|
-
export type TabsProps = TabsBaseProps &
|
|
47
|
-
(
|
|
48
|
-
| { /** Selected tab (controlled). */ value: string; defaultValue?: never }
|
|
49
|
-
| { value?: never; /** Initially selected tab (uncontrolled). */ defaultValue: string }
|
|
50
|
-
);
|
|
51
|
-
|
|
52
|
-
function TabsRoot({ value: valueProp, defaultValue, onValueChange, size, color, className, ...props }: TabsProps) {
|
|
53
|
-
const own = useDefaults('Tabs');
|
|
54
|
-
const [value, select] = useControllable(valueProp, defaultValue ?? '', onValueChange);
|
|
55
|
-
const baseId = useId();
|
|
56
|
-
const idFor = (kind: 'tab' | 'panel', v: string) => `${baseId}-${kind}-${v.replace(/[^\w-]/g, '_')}`;
|
|
57
|
-
return (
|
|
58
|
-
<TabsContext.Provider value={{ value, select, idFor }}>
|
|
59
|
-
<div className={cx('yarcl-tabs', sizeClass(size ?? own.size), colorClass(color ?? own.color), className)} {...props} />
|
|
60
|
-
</TabsContext.Provider>
|
|
61
|
-
);
|
|
62
|
-
}
|
|
63
|
-
|
|
64
|
-
/** Props for `Tabs.List`. */
|
|
65
|
-
export interface TabsListProps extends ComponentProps<'div'> {
|
|
66
|
-
/** `Tabs.Trigger`s. */
|
|
67
|
-
children?: ReactNode;
|
|
68
|
-
}
|
|
69
|
-
|
|
70
|
-
function TabsList({ className, onKeyDown, ...props }: TabsListProps) {
|
|
71
|
-
const { select } = useTabsContext('Tabs.List');
|
|
72
|
-
|
|
73
|
-
function handleKeyDown(event: KeyboardEvent<HTMLDivElement>) {
|
|
74
|
-
onKeyDown?.(event);
|
|
75
|
-
const tabs = [...event.currentTarget.querySelectorAll<HTMLButtonElement>('[role="tab"]:not(:disabled)')];
|
|
76
|
-
const current = tabs.indexOf(document.activeElement as HTMLButtonElement);
|
|
77
|
-
if (current === -1) return;
|
|
78
|
-
const next = {
|
|
79
|
-
ArrowRight: (current + 1) % tabs.length,
|
|
80
|
-
ArrowLeft: (current - 1 + tabs.length) % tabs.length,
|
|
81
|
-
Home: 0,
|
|
82
|
-
End: tabs.length - 1,
|
|
83
|
-
}[event.key];
|
|
84
|
-
if (next === undefined) return;
|
|
85
|
-
event.preventDefault();
|
|
86
|
-
tabs[next].focus();
|
|
87
|
-
select(tabs[next].dataset.value!);
|
|
88
|
-
}
|
|
89
|
-
|
|
90
|
-
return <div role="tablist" className={cx('yarcl-tabs-list', className)} onKeyDown={handleKeyDown} {...props} />;
|
|
91
|
-
}
|
|
92
|
-
|
|
93
|
-
/** Props for `Tabs.Trigger`. */
|
|
94
|
-
export interface TabsTriggerProps extends Omit<ComponentProps<'button'>, 'value'> {
|
|
95
|
-
/** Identifies the tab; matches a `Tabs.Panel`'s `value`. */
|
|
96
|
-
value: string;
|
|
97
|
-
}
|
|
98
|
-
|
|
99
|
-
function TabsTrigger({ value, className, onClick, ...props }: TabsTriggerProps) {
|
|
100
|
-
const context = useTabsContext('Tabs.Trigger');
|
|
101
|
-
const selected = context.value === value;
|
|
102
|
-
return (
|
|
103
|
-
<button
|
|
104
|
-
type="button"
|
|
105
|
-
role="tab"
|
|
106
|
-
id={context.idFor('tab', value)}
|
|
107
|
-
aria-controls={context.idFor('panel', value)}
|
|
108
|
-
aria-selected={selected}
|
|
109
|
-
tabIndex={selected ? 0 : -1}
|
|
110
|
-
data-value={value}
|
|
111
|
-
className={cx('yarcl-tabs-trigger', className)}
|
|
112
|
-
onClick={(event) => {
|
|
113
|
-
onClick?.(event);
|
|
114
|
-
context.select(value);
|
|
115
|
-
}}
|
|
116
|
-
{...props}
|
|
117
|
-
/>
|
|
118
|
-
);
|
|
119
|
-
}
|
|
120
|
-
|
|
121
|
-
/** Props for `Tabs.Panel`. */
|
|
122
|
-
export interface TabsPanelProps extends Omit<ComponentProps<'div'>, 'value'> {
|
|
123
|
-
/** Matches the `value` of the `Tabs.Trigger` that shows this panel. */
|
|
124
|
-
value: string;
|
|
125
|
-
}
|
|
126
|
-
|
|
127
|
-
function TabsPanel({ value, className, ...props }: TabsPanelProps) {
|
|
128
|
-
const context = useTabsContext('Tabs.Panel');
|
|
129
|
-
const selected = context.value === value;
|
|
130
|
-
return (
|
|
131
|
-
<div
|
|
132
|
-
role="tabpanel"
|
|
133
|
-
id={context.idFor('panel', value)}
|
|
134
|
-
aria-labelledby={context.idFor('tab', value)}
|
|
135
|
-
tabIndex={0}
|
|
136
|
-
hidden={!selected}
|
|
137
|
-
className={cx('yarcl-tabs-panel', className)}
|
|
138
|
-
{...props}
|
|
139
|
-
/>
|
|
140
|
-
);
|
|
141
|
-
}
|
|
142
|
-
|
|
143
|
-
/**
|
|
144
|
-
* Switches between panels of content. Arrow keys move between tabs, Home and End jump to the ends.
|
|
145
|
-
* Tab height comes from the shared size scale.
|
|
146
|
-
*
|
|
147
|
-
* @example
|
|
148
|
-
* ```tsx
|
|
149
|
-
* <Tabs defaultValue="overview">
|
|
150
|
-
* <Tabs.List aria-label="Project">
|
|
151
|
-
* <Tabs.Trigger value="overview">Overview</Tabs.Trigger>
|
|
152
|
-
* <Tabs.Trigger value="settings">Settings</Tabs.Trigger>
|
|
153
|
-
* </Tabs.List>
|
|
154
|
-
* <Tabs.Panel value="overview">…</Tabs.Panel>
|
|
155
|
-
* <Tabs.Panel value="settings">…</Tabs.Panel>
|
|
156
|
-
* </Tabs>
|
|
157
|
-
* ```
|
|
158
|
-
*/
|
|
159
|
-
export const Tabs = Object.assign(TabsRoot, { List: TabsList, Trigger: TabsTrigger, Panel: TabsPanel });
|
package/src/components/Text.tsx
DELETED
|
@@ -1,67 +0,0 @@
|
|
|
1
|
-
import type { ComponentProps, CSSProperties } from 'react';
|
|
2
|
-
import { colorClass, cx, typeClass } from '../classes';
|
|
3
|
-
import type { Color, TextStyle } from '../types';
|
|
4
|
-
import { useConfig, useDefaults } from '../runtime';
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
/** Elements {@link Text} can render as. */
|
|
8
|
-
export type TextElement = 'span' | 'p' | 'div' | 'strong' | 'em' | 'small' | 'code' | 'label';
|
|
9
|
-
|
|
10
|
-
/** Props for {@link Text}. */
|
|
11
|
-
export interface TextProps extends Omit<ComponentProps<'span'>, 'color'> {
|
|
12
|
-
/**
|
|
13
|
-
* Element to render.
|
|
14
|
-
* @default 'span'
|
|
15
|
-
*/
|
|
16
|
-
as?: TextElement;
|
|
17
|
-
/**
|
|
18
|
-
* Text style, from the `typography.styles` config.
|
|
19
|
-
* @default config.defaults.textStyle
|
|
20
|
-
*/
|
|
21
|
-
textStyle?: TextStyle;
|
|
22
|
-
/** Semantic color, from the `colors` config. Inherits the surrounding color when omitted. */
|
|
23
|
-
color?: Color;
|
|
24
|
-
/** Uses the muted neutral color, for secondary text. */
|
|
25
|
-
muted?: boolean;
|
|
26
|
-
/** Truncates with an ellipsis: `true` for one line, a number for that many lines. */
|
|
27
|
-
truncate?: boolean | number;
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
/**
|
|
31
|
-
* Text in one of the consumer's text styles.
|
|
32
|
-
*
|
|
33
|
-
* @example
|
|
34
|
-
* ```tsx
|
|
35
|
-
* <Text as="p" textStyle="caption" muted>Updated 2 minutes ago</Text>
|
|
36
|
-
* ```
|
|
37
|
-
*/
|
|
38
|
-
export function Text({
|
|
39
|
-
as = 'span',
|
|
40
|
-
textStyle,
|
|
41
|
-
color,
|
|
42
|
-
muted,
|
|
43
|
-
truncate,
|
|
44
|
-
className,
|
|
45
|
-
style,
|
|
46
|
-
...props
|
|
47
|
-
}: TextProps) {
|
|
48
|
-
const config = useConfig();
|
|
49
|
-
const own = useDefaults('Text');
|
|
50
|
-
const Tag = as as 'span';
|
|
51
|
-
const lines = truncate === true ? 1 : truncate || 0;
|
|
52
|
-
return (
|
|
53
|
-
<Tag
|
|
54
|
-
className={cx(
|
|
55
|
-
'yarcl-text',
|
|
56
|
-
typeClass(textStyle ?? own.textStyle ?? config.defaults.textStyle),
|
|
57
|
-
(color ?? own.color) && cx('yarcl-text-colored', colorClass(color ?? own.color)),
|
|
58
|
-
muted && 'yarcl-text-muted',
|
|
59
|
-
lines === 1 && 'yarcl-text-truncate',
|
|
60
|
-
lines > 1 && 'yarcl-text-clamp',
|
|
61
|
-
className,
|
|
62
|
-
)}
|
|
63
|
-
style={lines > 1 ? ({ '--yarcl-lines': lines, ...style } as CSSProperties) : style}
|
|
64
|
-
{...props}
|
|
65
|
-
/>
|
|
66
|
-
);
|
|
67
|
-
}
|
|
@@ -1,26 +0,0 @@
|
|
|
1
|
-
import type { ComponentProps } from 'react';
|
|
2
|
-
import { colorClass, cx, radiusClass, sizeClass } from '../classes';
|
|
3
|
-
import { useFieldProps } from '../field-context';
|
|
4
|
-
import type { TokenProps } from '../types';
|
|
5
|
-
import { useDefaults } from '../runtime';
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
/** Props for {@link Textarea}. Accepts all native `<textarea>` attributes except `color`. */
|
|
9
|
-
export interface TextareaProps extends Omit<ComponentProps<'textarea'>, 'color'>, TokenProps {}
|
|
10
|
-
|
|
11
|
-
/**
|
|
12
|
-
* A multi-line text input. Uses the size's font size and padding, and grows from
|
|
13
|
-
* a minimum of one control height. Inside a {@link Field}, it is labelled and described automatically.
|
|
14
|
-
*
|
|
15
|
-
* @example
|
|
16
|
-
* ```tsx
|
|
17
|
-
* <Textarea rows={4} placeholder="Message" />
|
|
18
|
-
* ```
|
|
19
|
-
*/
|
|
20
|
-
export function Textarea(props: TextareaProps) {
|
|
21
|
-
const own = useDefaults('Textarea');
|
|
22
|
-
const { size, radius, color, className, ...rest } = useFieldProps(props);
|
|
23
|
-
return (
|
|
24
|
-
<textarea className={cx('yarcl-textarea', sizeClass(size ?? own.size), radiusClass(radius ?? own.radius, size ?? own.size), colorClass(color ?? own.color), className)} {...rest} />
|
|
25
|
-
);
|
|
26
|
-
}
|
package/src/components/Toast.tsx
DELETED
|
@@ -1,190 +0,0 @@
|
|
|
1
|
-
import { useEffect, useRef, useState, useSyncExternalStore, type ReactNode } from 'react';
|
|
2
|
-
import { createPortal } from 'react-dom';
|
|
3
|
-
import { colorClass, cx } from '../classes';
|
|
4
|
-
import type { Color } from '../types';
|
|
5
|
-
import { useConfig, useDefaults } from '../runtime';
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
/** Options for {@link toast}. */
|
|
9
|
-
export interface ToastOptions {
|
|
10
|
-
/** Main message. */
|
|
11
|
-
title: ReactNode;
|
|
12
|
-
/** Supporting text. */
|
|
13
|
-
description?: ReactNode;
|
|
14
|
-
/**
|
|
15
|
-
* Accent color, from the `colors` config.
|
|
16
|
-
* @default config.defaults.color
|
|
17
|
-
*/
|
|
18
|
-
color?: Color;
|
|
19
|
-
/**
|
|
20
|
-
* Time before it dismisses itself, in ms. Paused while hovered or focused. `0` keeps it until dismissed.
|
|
21
|
-
* @default config.timing.toastDuration
|
|
22
|
-
*/
|
|
23
|
-
duration?: number;
|
|
24
|
-
/** A single action, e.g. Undo. Choosing it dismisses the toast. */
|
|
25
|
-
action?: { label: string; onClick: () => void };
|
|
26
|
-
/** Announces immediately (`role="alert"`) instead of politely. Use for errors that need attention. */
|
|
27
|
-
urgent?: boolean;
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
interface ToastEntry extends ToastOptions {
|
|
31
|
-
id: string;
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
let entries: ToastEntry[] = [];
|
|
35
|
-
let counter = 0;
|
|
36
|
-
const listeners = new Set<() => void>();
|
|
37
|
-
|
|
38
|
-
function emit(next: ToastEntry[]) {
|
|
39
|
-
entries = next;
|
|
40
|
-
listeners.forEach((listener) => listener());
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
function subscribe(listener: () => void) {
|
|
44
|
-
listeners.add(listener);
|
|
45
|
-
return () => listeners.delete(listener);
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
/**
|
|
49
|
-
* Shows a toast notification. Requires one {@link Toaster} rendered in the app.
|
|
50
|
-
* Returns the toast's id, for {@link toast.dismiss}.
|
|
51
|
-
*
|
|
52
|
-
* @example
|
|
53
|
-
* ```ts
|
|
54
|
-
* toast({ title: 'Saved', color: 'success' });
|
|
55
|
-
* toast({ title: 'Message deleted', action: { label: 'Undo', onClick: restore } });
|
|
56
|
-
* ```
|
|
57
|
-
*/
|
|
58
|
-
export function toast(options: ToastOptions): string {
|
|
59
|
-
const id = `yarcl-toast-${++counter}`;
|
|
60
|
-
emit([...entries, { id, ...options }]);
|
|
61
|
-
return id;
|
|
62
|
-
}
|
|
63
|
-
|
|
64
|
-
/** Dismisses a toast by id, or all toasts when called without one. */
|
|
65
|
-
toast.dismiss = (id?: string) => emit(id ? entries.filter((entry) => entry.id !== id) : []);
|
|
66
|
-
|
|
67
|
-
/** Props for {@link Toaster}. */
|
|
68
|
-
export interface ToasterProps {
|
|
69
|
-
/**
|
|
70
|
-
* Screen corner or edge where toasts appear.
|
|
71
|
-
* @default 'bottom-right'
|
|
72
|
-
*/
|
|
73
|
-
placement?: 'top-left' | 'top-center' | 'top-right' | 'bottom-left' | 'bottom-center' | 'bottom-right';
|
|
74
|
-
/**
|
|
75
|
-
* Maximum toasts shown at once. When exceeded, the oldest are dismissed.
|
|
76
|
-
* @default 4
|
|
77
|
-
*/
|
|
78
|
-
limit?: number;
|
|
79
|
-
/**
|
|
80
|
-
* Accessible name of the notifications region.
|
|
81
|
-
* @default 'Notifications'
|
|
82
|
-
*/
|
|
83
|
-
label?: string;
|
|
84
|
-
}
|
|
85
|
-
|
|
86
|
-
function useModalHost() {
|
|
87
|
-
const [host, setHost] = useState<HTMLElement | null>(null);
|
|
88
|
-
useEffect(() => {
|
|
89
|
-
const update = () => {
|
|
90
|
-
const modals = [...document.querySelectorAll('dialog')].filter((dialog) => dialog.matches(':modal'));
|
|
91
|
-
setHost(modals.at(-1) ?? null);
|
|
92
|
-
};
|
|
93
|
-
const observer = new MutationObserver(update);
|
|
94
|
-
observer.observe(document.body, { subtree: true, childList: true, attributes: true, attributeFilter: ['open'] });
|
|
95
|
-
update();
|
|
96
|
-
return () => observer.disconnect();
|
|
97
|
-
}, []);
|
|
98
|
-
return host;
|
|
99
|
-
}
|
|
100
|
-
|
|
101
|
-
/**
|
|
102
|
-
* Renders toasts created with {@link toast}. Render once, near the app root.
|
|
103
|
-
* Lives in the browser's top layer, so toasts show above everything. While a modal
|
|
104
|
-
* {@link Dialog} or {@link Drawer} is open, toasts render inside it so they stay interactive.
|
|
105
|
-
*
|
|
106
|
-
* @example
|
|
107
|
-
* ```tsx
|
|
108
|
-
* <App />
|
|
109
|
-
* <Toaster placement="top-right" />
|
|
110
|
-
* ```
|
|
111
|
-
*/
|
|
112
|
-
export function Toaster({ placement = 'bottom-right', limit = 4, label = 'Notifications' }: ToasterProps) {
|
|
113
|
-
const all = useSyncExternalStore(subscribe, () => entries, () => entries);
|
|
114
|
-
const visible = all.slice(-limit);
|
|
115
|
-
const ref = useRef<HTMLElement>(null);
|
|
116
|
-
const host = useModalHost();
|
|
117
|
-
|
|
118
|
-
useEffect(() => {
|
|
119
|
-
if (all.length > limit) emit(all.slice(-limit));
|
|
120
|
-
}, [all, limit]);
|
|
121
|
-
|
|
122
|
-
useEffect(() => {
|
|
123
|
-
const region = ref.current;
|
|
124
|
-
if (!region?.showPopover) return;
|
|
125
|
-
if (region.matches(':popover-open')) region.hidePopover();
|
|
126
|
-
if (visible.length > 0) region.showPopover();
|
|
127
|
-
}, [visible.length, all, host]);
|
|
128
|
-
|
|
129
|
-
const region = (
|
|
130
|
-
<section
|
|
131
|
-
ref={ref}
|
|
132
|
-
popover="manual"
|
|
133
|
-
aria-label={label}
|
|
134
|
-
className={cx('yarcl-toaster', `yarcl-toaster-${placement}`)}
|
|
135
|
-
>
|
|
136
|
-
{(placement.startsWith('top') ? [...visible].reverse() : visible).map((entry) => (
|
|
137
|
-
<ToastItem key={entry.id} entry={entry} />
|
|
138
|
-
))}
|
|
139
|
-
</section>
|
|
140
|
-
);
|
|
141
|
-
|
|
142
|
-
return host ? createPortal(region, host) : region;
|
|
143
|
-
}
|
|
144
|
-
|
|
145
|
-
function ToastItem({ entry }: { entry: ToastEntry }) {
|
|
146
|
-
const own = useDefaults('Toast');
|
|
147
|
-
const config = useConfig();
|
|
148
|
-
const { id, title, description, color, action, urgent } = entry;
|
|
149
|
-
const duration = entry.duration ?? config.timing.toastDuration;
|
|
150
|
-
const [paused, setPaused] = useState(false);
|
|
151
|
-
|
|
152
|
-
useEffect(() => {
|
|
153
|
-
if (paused || duration <= 0) return;
|
|
154
|
-
const timer = setTimeout(() => toast.dismiss(id), duration);
|
|
155
|
-
return () => clearTimeout(timer);
|
|
156
|
-
}, [id, duration, paused]);
|
|
157
|
-
|
|
158
|
-
return (
|
|
159
|
-
<div
|
|
160
|
-
role={urgent ? 'alert' : 'status'}
|
|
161
|
-
className={cx('yarcl-toast', colorClass(color ?? own.color))}
|
|
162
|
-
onMouseEnter={() => setPaused(true)}
|
|
163
|
-
onMouseLeave={() => setPaused(false)}
|
|
164
|
-
onFocus={() => setPaused(true)}
|
|
165
|
-
onBlur={() => setPaused(false)}
|
|
166
|
-
>
|
|
167
|
-
<div className="yarcl-toast-text">
|
|
168
|
-
<div className="yarcl-toast-title">{title}</div>
|
|
169
|
-
{description != null && <div className="yarcl-toast-description">{description}</div>}
|
|
170
|
-
</div>
|
|
171
|
-
{action && (
|
|
172
|
-
<button
|
|
173
|
-
type="button"
|
|
174
|
-
className="yarcl-toast-action"
|
|
175
|
-
onClick={() => {
|
|
176
|
-
action.onClick();
|
|
177
|
-
toast.dismiss(id);
|
|
178
|
-
}}
|
|
179
|
-
>
|
|
180
|
-
{action.label}
|
|
181
|
-
</button>
|
|
182
|
-
)}
|
|
183
|
-
<button type="button" className="yarcl-toast-close" aria-label="Dismiss" onClick={() => toast.dismiss(id)}>
|
|
184
|
-
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" aria-hidden="true">
|
|
185
|
-
<path d="M6 6l12 12M18 6 6 18" />
|
|
186
|
-
</svg>
|
|
187
|
-
</button>
|
|
188
|
-
</div>
|
|
189
|
-
);
|
|
190
|
-
}
|
|
@@ -1,227 +0,0 @@
|
|
|
1
|
-
import {
|
|
2
|
-
createContext,
|
|
3
|
-
useContext,
|
|
4
|
-
useLayoutEffect,
|
|
5
|
-
useRef,
|
|
6
|
-
type ComponentProps,
|
|
7
|
-
type KeyboardEvent,
|
|
8
|
-
type ReactNode,
|
|
9
|
-
} from 'react';
|
|
10
|
-
import { cx } from '../classes';
|
|
11
|
-
import { useControllable } from '../hooks';
|
|
12
|
-
import type { Color, Radius, Size, Variant } from '../types';
|
|
13
|
-
import { Button } from './Button';
|
|
14
|
-
import { useConfig, useDefaults } from '../runtime';
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
interface ToggleGroupContextValue {
|
|
18
|
-
selected: string[];
|
|
19
|
-
toggle: (value: string) => void;
|
|
20
|
-
size?: Size;
|
|
21
|
-
color?: Color;
|
|
22
|
-
radius?: Radius | 'size';
|
|
23
|
-
variant: Variant;
|
|
24
|
-
selectedVariant: Variant;
|
|
25
|
-
disabled?: boolean;
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
const ToggleGroupContext = createContext<ToggleGroupContextValue | null>(null);
|
|
29
|
-
|
|
30
|
-
/** Props shared by both forms of {@link ToggleGroupProps}. */
|
|
31
|
-
export interface ToggleGroupBaseProps extends Omit<ComponentProps<'div'>, 'defaultValue' | 'onChange' | 'color'> {
|
|
32
|
-
/** Size of every item, from the `sizes` config. */
|
|
33
|
-
size?: Size;
|
|
34
|
-
/** Color of every item, from the `colors` config. */
|
|
35
|
-
color?: Color;
|
|
36
|
-
/** Radius of every item, from the `radii` config, or `'size'` to match the item size. */
|
|
37
|
-
radius?: Radius | 'size';
|
|
38
|
-
/**
|
|
39
|
-
* Variant of unselected items, from the `variants` config.
|
|
40
|
-
* @default config.defaults.softVariant
|
|
41
|
-
*/
|
|
42
|
-
variant?: Variant;
|
|
43
|
-
/**
|
|
44
|
-
* Variant of selected items, from the `variants` config.
|
|
45
|
-
* @default config.defaults.variant
|
|
46
|
-
*/
|
|
47
|
-
selectedVariant?: Variant;
|
|
48
|
-
/**
|
|
49
|
-
* Joins the items into one segmented control.
|
|
50
|
-
* @default true
|
|
51
|
-
*/
|
|
52
|
-
attached?: boolean;
|
|
53
|
-
/** Disables every item. */
|
|
54
|
-
disabled?: boolean;
|
|
55
|
-
/** `ToggleGroup.Item`s. */
|
|
56
|
-
children?: ReactNode;
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
/** Props for {@link ToggleGroup}. `type` decides whether one or many items can be on. */
|
|
60
|
-
export type ToggleGroupProps = ToggleGroupBaseProps &
|
|
61
|
-
(
|
|
62
|
-
| {
|
|
63
|
-
/** One item on at a time, like a segmented control. */
|
|
64
|
-
type: 'single';
|
|
65
|
-
/** Controlled selected value. */
|
|
66
|
-
value?: string | null;
|
|
67
|
-
/** Initially selected value when uncontrolled. */
|
|
68
|
-
defaultValue?: string | null;
|
|
69
|
-
/** Called with the selected value, or `null` when the selected item is turned off. */
|
|
70
|
-
onValueChange?: (value: string | null) => void;
|
|
71
|
-
/** Prevents turning the selected item off, so one is always on. */
|
|
72
|
-
required?: boolean;
|
|
73
|
-
}
|
|
74
|
-
| {
|
|
75
|
-
/** Any number of items on, like formatting buttons. */
|
|
76
|
-
type: 'multiple';
|
|
77
|
-
/** Controlled selected values. */
|
|
78
|
-
value?: string[];
|
|
79
|
-
/** Initially selected values when uncontrolled. */
|
|
80
|
-
defaultValue?: string[];
|
|
81
|
-
/** Called with all selected values. */
|
|
82
|
-
onValueChange?: (value: string[]) => void;
|
|
83
|
-
required?: never;
|
|
84
|
-
}
|
|
85
|
-
);
|
|
86
|
-
|
|
87
|
-
const toArray = (value: string | string[] | null | undefined) => (value == null ? [] : Array.isArray(value) ? value : [value]);
|
|
88
|
-
|
|
89
|
-
function ToggleGroupRoot(props: ToggleGroupProps) {
|
|
90
|
-
const config = useConfig();
|
|
91
|
-
const own = useDefaults('ToggleGroup');
|
|
92
|
-
const {
|
|
93
|
-
type,
|
|
94
|
-
value,
|
|
95
|
-
defaultValue,
|
|
96
|
-
onValueChange,
|
|
97
|
-
required,
|
|
98
|
-
size = own.size,
|
|
99
|
-
color = own.color,
|
|
100
|
-
radius = own.radius,
|
|
101
|
-
variant = own.variant ?? config.defaults.softVariant,
|
|
102
|
-
selectedVariant = own.selectedVariant ?? config.defaults.variant,
|
|
103
|
-
attached = true,
|
|
104
|
-
disabled,
|
|
105
|
-
className,
|
|
106
|
-
onKeyDown,
|
|
107
|
-
children,
|
|
108
|
-
...rest
|
|
109
|
-
} = props;
|
|
110
|
-
|
|
111
|
-
const [selected, setSelected] = useControllable<string[]>(
|
|
112
|
-
value === undefined ? undefined : toArray(value),
|
|
113
|
-
toArray(defaultValue),
|
|
114
|
-
(next) =>
|
|
115
|
-
type === 'multiple'
|
|
116
|
-
? (onValueChange as (v: string[]) => void)?.(next)
|
|
117
|
-
: (onValueChange as (v: string | null) => void)?.(next[0] ?? null),
|
|
118
|
-
);
|
|
119
|
-
|
|
120
|
-
const ref = useRef<HTMLDivElement>(null);
|
|
121
|
-
useLayoutEffect(() => {
|
|
122
|
-
const items = [...(ref.current?.querySelectorAll<HTMLButtonElement>('.yarcl-toggle-item') ?? [])];
|
|
123
|
-
const stop = items.find((item) => item.getAttribute('aria-pressed') === 'true' && !item.disabled) ?? items.find((item) => !item.disabled);
|
|
124
|
-
items.forEach((item) => (item.tabIndex = item === stop ? 0 : -1));
|
|
125
|
-
});
|
|
126
|
-
|
|
127
|
-
function toggle(item: string) {
|
|
128
|
-
const on = selected.includes(item);
|
|
129
|
-
if (type === 'multiple') setSelected(on ? selected.filter((v) => v !== item) : [...selected, item]);
|
|
130
|
-
else if (!on) setSelected([item]);
|
|
131
|
-
else if (!required) setSelected([]);
|
|
132
|
-
}
|
|
133
|
-
|
|
134
|
-
function handleKeyDown(event: KeyboardEvent<HTMLDivElement>) {
|
|
135
|
-
onKeyDown?.(event);
|
|
136
|
-
const items = [...event.currentTarget.querySelectorAll<HTMLButtonElement>('.yarcl-toggle-item:not(:disabled)')];
|
|
137
|
-
const current = items.indexOf(document.activeElement as HTMLButtonElement);
|
|
138
|
-
if (current === -1) return;
|
|
139
|
-
const next = {
|
|
140
|
-
ArrowRight: (current + 1) % items.length,
|
|
141
|
-
ArrowDown: (current + 1) % items.length,
|
|
142
|
-
ArrowLeft: (current - 1 + items.length) % items.length,
|
|
143
|
-
ArrowUp: (current - 1 + items.length) % items.length,
|
|
144
|
-
Home: 0,
|
|
145
|
-
End: items.length - 1,
|
|
146
|
-
}[event.key];
|
|
147
|
-
if (next === undefined) return;
|
|
148
|
-
event.preventDefault();
|
|
149
|
-
items.forEach((item, i) => (item.tabIndex = i === next ? 0 : -1));
|
|
150
|
-
items[next].focus();
|
|
151
|
-
}
|
|
152
|
-
|
|
153
|
-
return (
|
|
154
|
-
<ToggleGroupContext.Provider
|
|
155
|
-
value={{
|
|
156
|
-
selected,
|
|
157
|
-
toggle,
|
|
158
|
-
size,
|
|
159
|
-
color,
|
|
160
|
-
radius,
|
|
161
|
-
variant,
|
|
162
|
-
selectedVariant,
|
|
163
|
-
disabled,
|
|
164
|
-
}}
|
|
165
|
-
>
|
|
166
|
-
<div
|
|
167
|
-
ref={ref}
|
|
168
|
-
role="group"
|
|
169
|
-
className={cx(
|
|
170
|
-
'yarcl-button-group yarcl-button-group-horizontal yarcl-toggle-group',
|
|
171
|
-
attached && 'yarcl-button-group-attached',
|
|
172
|
-
className,
|
|
173
|
-
)}
|
|
174
|
-
onKeyDown={handleKeyDown}
|
|
175
|
-
{...rest}
|
|
176
|
-
>
|
|
177
|
-
{children}
|
|
178
|
-
</div>
|
|
179
|
-
</ToggleGroupContext.Provider>
|
|
180
|
-
);
|
|
181
|
-
}
|
|
182
|
-
|
|
183
|
-
/** Props for `ToggleGroup.Item`. */
|
|
184
|
-
export interface ToggleGroupItemProps extends Omit<ComponentProps<'button'>, 'value' | 'color'> {
|
|
185
|
-
/** Identifies the item in the group's value. */
|
|
186
|
-
value: string;
|
|
187
|
-
/** Makes the item square, for icon-only items. Give it an `aria-label`. */
|
|
188
|
-
icon?: boolean;
|
|
189
|
-
}
|
|
190
|
-
|
|
191
|
-
function ToggleGroupItem({ value, icon, disabled, className, onClick, ...props }: ToggleGroupItemProps) {
|
|
192
|
-
const context = useContext(ToggleGroupContext);
|
|
193
|
-
if (!context) throw new Error('yarcl: <ToggleGroup.Item> must be inside <ToggleGroup>');
|
|
194
|
-
const on = context.selected.includes(value);
|
|
195
|
-
return (
|
|
196
|
-
<Button
|
|
197
|
-
aria-pressed={on}
|
|
198
|
-
data-value={value}
|
|
199
|
-
disabled={disabled || context.disabled}
|
|
200
|
-
size={context.size}
|
|
201
|
-
color={context.color}
|
|
202
|
-
radius={context.radius}
|
|
203
|
-
variant={on ? context.selectedVariant : context.variant}
|
|
204
|
-
className={cx('yarcl-toggle-item', icon && 'yarcl-icon-button', className)}
|
|
205
|
-
onClick={(event) => {
|
|
206
|
-
onClick?.(event);
|
|
207
|
-
context.toggle(value);
|
|
208
|
-
}}
|
|
209
|
-
{...props}
|
|
210
|
-
/>
|
|
211
|
-
);
|
|
212
|
-
}
|
|
213
|
-
|
|
214
|
-
/**
|
|
215
|
-
* A set of toggle buttons. `type="single"` behaves like a segmented control,
|
|
216
|
-
* `type="multiple"` like formatting buttons. Items use `aria-pressed`; arrow keys move focus.
|
|
217
|
-
*
|
|
218
|
-
* @example
|
|
219
|
-
* ```tsx
|
|
220
|
-
* <ToggleGroup type="single" defaultValue="week" aria-label="Range" required>
|
|
221
|
-
* <ToggleGroup.Item value="day">Day</ToggleGroup.Item>
|
|
222
|
-
* <ToggleGroup.Item value="week">Week</ToggleGroup.Item>
|
|
223
|
-
* <ToggleGroup.Item value="month">Month</ToggleGroup.Item>
|
|
224
|
-
* </ToggleGroup>
|
|
225
|
-
* ```
|
|
226
|
-
*/
|
|
227
|
-
export const ToggleGroup = Object.assign(ToggleGroupRoot, { Item: ToggleGroupItem });
|