@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.
Files changed (223) hide show
  1. package/README.md +14 -2
  2. package/{src/apply.ts → dist/apply.d.ts} +4 -23
  3. package/dist/apply.js +37 -0
  4. package/dist/classes.d.ts +13 -0
  5. package/dist/classes.js +22 -0
  6. package/dist/cli.d.ts +2 -0
  7. package/dist/cli.js +58 -0
  8. package/dist/color.js +49 -32
  9. package/dist/components/Accordion.d.ts +96 -0
  10. package/dist/components/Accordion.js +148 -0
  11. package/dist/components/Alert.d.ts +60 -0
  12. package/dist/components/Alert.js +61 -0
  13. package/dist/components/Avatar.d.ts +100 -0
  14. package/dist/components/Avatar.js +89 -0
  15. package/dist/components/Badge.d.ts +44 -0
  16. package/dist/components/Badge.js +43 -0
  17. package/dist/components/Breadcrumb.d.ts +77 -0
  18. package/dist/components/Breadcrumb.js +126 -0
  19. package/dist/components/Button.d.ts +18 -0
  20. package/dist/components/Button.js +33 -0
  21. package/dist/components/ButtonGroup.d.ts +29 -0
  22. package/dist/components/ButtonGroup.js +37 -0
  23. package/dist/components/Card.d.ts +36 -0
  24. package/dist/components/Card.js +26 -0
  25. package/dist/components/Checkbox.d.ts +28 -0
  26. package/dist/components/Checkbox.js +38 -0
  27. package/dist/components/Combobox.d.ts +75 -0
  28. package/dist/components/Combobox.js +362 -0
  29. package/dist/components/CommandPalette.d.ts +92 -0
  30. package/dist/components/CommandPalette.js +293 -0
  31. package/dist/components/DatePicker.d.ts +157 -0
  32. package/dist/components/DatePicker.js +230 -0
  33. package/dist/components/Dialog.d.ts +25 -0
  34. package/dist/components/Dialog.js +29 -0
  35. package/dist/components/Divider.d.ts +23 -0
  36. package/dist/components/Divider.js +25 -0
  37. package/dist/components/Drawer.d.ts +21 -0
  38. package/dist/components/Drawer.js +25 -0
  39. package/dist/components/EmptyState.d.ts +51 -0
  40. package/dist/components/EmptyState.js +47 -0
  41. package/dist/components/Field.d.ts +27 -0
  42. package/dist/components/Field.js +59 -0
  43. package/dist/components/Heading.d.ts +26 -0
  44. package/dist/components/Heading.js +25 -0
  45. package/dist/components/HoverCard.d.ts +47 -0
  46. package/dist/components/HoverCard.js +53 -0
  47. package/dist/components/IconButton.d.ts +19 -0
  48. package/dist/components/IconButton.js +32 -0
  49. package/dist/components/Inline.d.ts +21 -0
  50. package/dist/components/Inline.js +24 -0
  51. package/dist/components/Input.d.ts +19 -0
  52. package/dist/components/Input.js +25 -0
  53. package/dist/components/Label.d.ts +46 -0
  54. package/dist/components/Label.js +33 -0
  55. package/dist/components/Link.d.ts +27 -0
  56. package/dist/components/Link.js +26 -0
  57. package/dist/components/Menu.d.ts +73 -0
  58. package/dist/components/Menu.js +143 -0
  59. package/dist/components/Modal.d.ts +45 -0
  60. package/dist/components/Modal.js +110 -0
  61. package/dist/components/NumberInput.d.ts +49 -0
  62. package/dist/components/NumberInput.js +130 -0
  63. package/dist/components/Pagination.d.ts +86 -0
  64. package/dist/components/Pagination.js +119 -0
  65. package/dist/components/Popover.d.ts +72 -0
  66. package/dist/components/Popover.js +87 -0
  67. package/dist/components/Progress.d.ts +60 -0
  68. package/dist/components/Progress.js +59 -0
  69. package/dist/components/Radio.d.ts +32 -0
  70. package/dist/components/Radio.js +47 -0
  71. package/dist/components/RadioGroup.d.ts +57 -0
  72. package/dist/components/RadioGroup.js +68 -0
  73. package/dist/components/Select.d.ts +47 -0
  74. package/dist/components/Select.js +138 -0
  75. package/dist/components/Skeleton.d.ts +46 -0
  76. package/dist/components/Skeleton.js +40 -0
  77. package/dist/components/Slider.d.ts +126 -0
  78. package/dist/components/Slider.js +166 -0
  79. package/dist/components/Spinner.d.ts +23 -0
  80. package/dist/components/Spinner.js +23 -0
  81. package/dist/components/Stack.d.ts +35 -0
  82. package/dist/components/Stack.js +24 -0
  83. package/dist/components/Switch.d.ts +27 -0
  84. package/dist/components/Switch.js +33 -0
  85. package/dist/components/Table.d.ts +71 -0
  86. package/dist/components/Table.js +61 -0
  87. package/dist/components/Tabs.d.ts +66 -0
  88. package/dist/components/Tabs.js +109 -0
  89. package/dist/components/Text.d.ts +32 -0
  90. package/dist/components/Text.js +28 -0
  91. package/dist/components/Textarea.d.ts +15 -0
  92. package/dist/components/Textarea.js +24 -0
  93. package/dist/components/Toast.d.ts +90 -0
  94. package/dist/components/Toast.js +156 -0
  95. package/dist/components/ToggleGroup.d.ts +79 -0
  96. package/dist/components/ToggleGroup.js +101 -0
  97. package/dist/components/Tooltip.d.ts +46 -0
  98. package/dist/components/Tooltip.js +62 -0
  99. package/dist/components/VisuallyHidden.d.ts +18 -0
  100. package/dist/components/VisuallyHidden.js +22 -0
  101. package/dist/contrast.d.ts +32 -0
  102. package/dist/css.d.ts +2 -2
  103. package/dist/css.js +193 -157
  104. package/dist/define.d.ts +40 -15
  105. package/dist/define.js +30 -27
  106. package/dist/demo.d.ts +2 -0
  107. package/dist/demo.js +2 -0
  108. package/dist/esbuild.cjs +5 -0
  109. package/dist/esbuild.d.ts +4 -0
  110. package/dist/esbuild.js +6 -0
  111. package/dist/field-context.d.ts +16 -0
  112. package/dist/field-context.js +17 -0
  113. package/dist/floating.d.ts +12 -0
  114. package/dist/floating.js +52 -0
  115. package/dist/hooks.d.ts +1 -0
  116. package/dist/hooks.js +12 -0
  117. package/{src/index.ts → dist/index.d.ts} +11 -33
  118. package/dist/index.js +50 -0
  119. package/dist/init.d.ts +18 -0
  120. package/dist/init.js +224 -0
  121. package/dist/plugin.d.ts +10 -26
  122. package/dist/plugin.js +152 -78
  123. package/dist/reference/DemoYarcl.d.ts +12 -0
  124. package/dist/reference/DemoYarcl.js +312 -0
  125. package/dist/reference/DesignReference.d.ts +24 -0
  126. package/dist/reference/DesignReference.js +403 -0
  127. package/dist/reference/index.js +2 -0
  128. package/dist/rollup.cjs +5 -0
  129. package/dist/rollup.d.ts +4 -0
  130. package/dist/rollup.js +6 -0
  131. package/dist/rspack.cjs +5 -0
  132. package/dist/rspack.d.ts +4 -0
  133. package/dist/rspack.js +6 -0
  134. package/dist/runtime.d.ts +13 -0
  135. package/dist/runtime.js +30 -0
  136. package/dist/setup.d.ts +6 -0
  137. package/dist/setup.js +33 -0
  138. package/{src → dist}/styles.css +224 -106
  139. package/dist/themes/bloom.d.ts +295 -0
  140. package/dist/themes/bloom.js +202 -0
  141. package/dist/themes/brutalist.d.ts +277 -0
  142. package/dist/themes/brutalist.js +199 -0
  143. package/dist/themes/compact.d.ts +274 -0
  144. package/dist/themes/compact.js +200 -0
  145. package/dist/themes/contract.d.ts +23 -0
  146. package/dist/themes/editorial.d.ts +277 -0
  147. package/dist/themes/editorial.js +183 -0
  148. package/dist/themes/index.d.ts +1425 -0
  149. package/dist/themes/index.js +45 -0
  150. package/{src/types.ts → dist/types.d.ts} +40 -37
  151. package/dist/vite.cjs +5 -0
  152. package/dist/vite.d.ts +4 -0
  153. package/dist/vite.js +6 -0
  154. package/dist/webpack.cjs +5 -0
  155. package/dist/webpack.d.ts +4 -0
  156. package/dist/webpack.js +6 -0
  157. package/dist/yarcl.config.d.ts +276 -0
  158. package/dist/yarcl.config.js +273 -0
  159. package/package.json +69 -17
  160. package/src/classes.ts +0 -23
  161. package/src/color.ts +0 -48
  162. package/src/components/Accordion.tsx +0 -274
  163. package/src/components/Alert.tsx +0 -85
  164. package/src/components/Badge.tsx +0 -79
  165. package/src/components/Breadcrumb.tsx +0 -239
  166. package/src/components/Button.tsx +0 -58
  167. package/src/components/ButtonGroup.tsx +0 -61
  168. package/src/components/Card.tsx +0 -50
  169. package/src/components/Checkbox.tsx +0 -49
  170. package/src/components/Combobox.tsx +0 -522
  171. package/src/components/CommandPalette.tsx +0 -452
  172. package/src/components/DatePicker.tsx +0 -432
  173. package/src/components/Dialog.tsx +0 -39
  174. package/src/components/Divider.tsx +0 -34
  175. package/src/components/Drawer.tsx +0 -31
  176. package/src/components/Field.tsx +0 -61
  177. package/src/components/Heading.tsx +0 -40
  178. package/src/components/HoverCard.tsx +0 -108
  179. package/src/components/IconButton.tsx +0 -59
  180. package/src/components/Inline.tsx +0 -42
  181. package/src/components/Input.tsx +0 -28
  182. package/src/components/Label.tsx +0 -58
  183. package/src/components/Link.tsx +0 -41
  184. package/src/components/Menu.tsx +0 -207
  185. package/src/components/Modal.tsx +0 -164
  186. package/src/components/NumberInput.tsx +0 -208
  187. package/src/components/Pagination.tsx +0 -227
  188. package/src/components/Popover.tsx +0 -140
  189. package/src/components/Progress.tsx +0 -122
  190. package/src/components/Radio.tsx +0 -59
  191. package/src/components/RadioGroup.tsx +0 -114
  192. package/src/components/Select.tsx +0 -197
  193. package/src/components/Skeleton.tsx +0 -95
  194. package/src/components/Slider.tsx +0 -315
  195. package/src/components/Spinner.tsx +0 -38
  196. package/src/components/Stack.tsx +0 -57
  197. package/src/components/Switch.tsx +0 -42
  198. package/src/components/Table.tsx +0 -99
  199. package/src/components/Tabs.tsx +0 -159
  200. package/src/components/Text.tsx +0 -67
  201. package/src/components/Textarea.tsx +0 -26
  202. package/src/components/Toast.tsx +0 -190
  203. package/src/components/ToggleGroup.tsx +0 -227
  204. package/src/components/Tooltip.tsx +0 -78
  205. package/src/contrast.ts +0 -82
  206. package/src/css.ts +0 -228
  207. package/src/define.check.ts +0 -259
  208. package/src/define.ts +0 -379
  209. package/src/field-context.ts +0 -31
  210. package/src/floating.tsx +0 -61
  211. package/src/hooks.ts +0 -19
  212. package/src/plugin.ts +0 -94
  213. package/src/reference/DesignReference.tsx +0 -374
  214. package/src/runtime.ts +0 -38
  215. package/src/themes/bloom.ts +0 -72
  216. package/src/themes/brutalist.ts +0 -63
  217. package/src/themes/compact.ts +0 -63
  218. package/src/themes/contract.ts +0 -24
  219. package/src/themes/editorial.ts +0 -55
  220. package/src/themes/index.ts +0 -47
  221. package/src/yarcl.config.ts +0 -104
  222. /package/{src/reference/index.ts → dist/reference/index.d.ts} +0 -0
  223. /package/{src → dist}/reference/reference.css +0 -0
@@ -0,0 +1,28 @@
1
+ import { useConfig, useDefaults } from "../runtime.js";
2
+ import { colorClass, cx, typeClass } from "../classes.js";
3
+ import { jsx } from "react/jsx-runtime";
4
+ //#region src/components/Text.tsx
5
+ /**
6
+ * Text in one of the consumer's text styles.
7
+ *
8
+ * @example
9
+ * ```tsx
10
+ * <Text as="p" textStyle="caption" muted>Updated 2 minutes ago</Text>
11
+ * ```
12
+ */
13
+ function Text({ as = "span", textStyle, color, muted, truncate, className, style, ...props }) {
14
+ const config = useConfig();
15
+ const own = useDefaults("Text");
16
+ const Tag = as;
17
+ const lines = truncate === true ? 1 : truncate || 0;
18
+ return /* @__PURE__ */ jsx(Tag, {
19
+ className: cx("yarcl-text", typeClass(textStyle ?? own.textStyle ?? config.defaults.textStyle), (color ?? own.color) && cx("yarcl-text-colored", colorClass(color ?? own.color)), muted && "yarcl-text-muted", lines === 1 && "yarcl-text-truncate", lines > 1 && "yarcl-text-clamp", className),
20
+ style: lines > 1 ? {
21
+ "--yarcl-lines": lines,
22
+ ...style
23
+ } : style,
24
+ ...props
25
+ });
26
+ }
27
+ //#endregion
28
+ export { Text };
@@ -0,0 +1,15 @@
1
+ import type { ComponentProps } from 'react';
2
+ import type { TokenProps } from '../types';
3
+ /** Props for {@link Textarea}. Accepts all native `<textarea>` attributes except `color`. */
4
+ export interface TextareaProps extends Omit<ComponentProps<'textarea'>, 'color'>, TokenProps<'Textarea'> {
5
+ }
6
+ /**
7
+ * A multi-line text input. Uses the size's font size and padding, and grows from
8
+ * a minimum of one control height. Inside a {@link Field}, it is labelled and described automatically.
9
+ *
10
+ * @example
11
+ * ```tsx
12
+ * <Textarea rows={4} placeholder="Message" />
13
+ * ```
14
+ */
15
+ export declare function Textarea(props: TextareaProps): import("react").JSX.Element;
@@ -0,0 +1,24 @@
1
+ import { useDefaults } from "../runtime.js";
2
+ import { colorClass, cx, radiusClass, sizeClass } from "../classes.js";
3
+ import { useFieldProps } from "../field-context.js";
4
+ import { jsx } from "react/jsx-runtime";
5
+ //#region src/components/Textarea.tsx
6
+ /**
7
+ * A multi-line text input. Uses the size's font size and padding, and grows from
8
+ * a minimum of one control height. Inside a {@link Field}, it is labelled and described automatically.
9
+ *
10
+ * @example
11
+ * ```tsx
12
+ * <Textarea rows={4} placeholder="Message" />
13
+ * ```
14
+ */
15
+ function Textarea(props) {
16
+ const own = useDefaults("Textarea");
17
+ const { size, radius, color, className, ...rest } = useFieldProps(props);
18
+ return /* @__PURE__ */ jsx("textarea", {
19
+ className: cx("yarcl-textarea", sizeClass(size ?? own.size, "Textarea"), radiusClass(radius ?? own.radius, size ?? own.size), colorClass(color ?? own.color), className),
20
+ ...rest
21
+ });
22
+ }
23
+ //#endregion
24
+ export { Textarea };
@@ -0,0 +1,90 @@
1
+ import { type ReactNode } from 'react';
2
+ import type { Color, Radius, Spacing, TextStyle } from '../types';
3
+ /** Options for {@link toast}. */
4
+ export interface ToastOptions {
5
+ /** Main message. */
6
+ title: ReactNode;
7
+ /** Supporting text. */
8
+ description?: ReactNode;
9
+ /**
10
+ * Accent color, from the `colors` config.
11
+ * @default config.defaults.color
12
+ */
13
+ color?: Color;
14
+ /**
15
+ * Corner radius, from the `radii` config.
16
+ * @default config.components.Toast.radius ?? config.defaults.radius
17
+ */
18
+ radius?: Radius;
19
+ /**
20
+ * Space between the message, action and dismiss button, from the `spacing` config.
21
+ * @default config.components.Toast.gap ?? config.defaults.gap
22
+ */
23
+ gap?: Spacing;
24
+ /**
25
+ * Inner spacing, from the `spacing` config.
26
+ * @default config.components.Toast.padding ?? config.defaults.padding
27
+ */
28
+ padding?: Spacing;
29
+ /**
30
+ * Typography style, from the `typography.styles` config.
31
+ * @default config.components.Toast.textStyle ?? config.defaults.labelStyle
32
+ */
33
+ textStyle?: TextStyle;
34
+ /**
35
+ * Time before it dismisses itself, in ms. Paused while hovered or focused. `0` keeps it until dismissed.
36
+ * @default config.timing.toastDuration
37
+ */
38
+ duration?: number;
39
+ /** A single action, e.g. Undo. Choosing it dismisses the toast. */
40
+ action?: {
41
+ label: string;
42
+ onClick: () => void;
43
+ };
44
+ /** Announces immediately (`role="alert"`) instead of politely. Use for errors that need attention. */
45
+ urgent?: boolean;
46
+ }
47
+ /**
48
+ * Shows a toast notification. Requires one {@link Toaster} rendered in the app.
49
+ * Returns the toast's id, for {@link toast.dismiss}.
50
+ *
51
+ * @example
52
+ * ```ts
53
+ * toast({ title: 'Saved', color: 'success' });
54
+ * toast({ title: 'Message deleted', action: { label: 'Undo', onClick: restore } });
55
+ * ```
56
+ */
57
+ export declare function toast(options: ToastOptions): string;
58
+ export declare namespace toast {
59
+ var dismiss: (id?: string) => void;
60
+ }
61
+ /** Props for {@link Toaster}. */
62
+ export interface ToasterProps {
63
+ /**
64
+ * Screen corner or edge where toasts appear.
65
+ * @default 'bottom-right'
66
+ */
67
+ placement?: 'top-left' | 'top-center' | 'top-right' | 'bottom-left' | 'bottom-center' | 'bottom-right';
68
+ /**
69
+ * Maximum toasts shown at once. When exceeded, the oldest are dismissed.
70
+ * @default 4
71
+ */
72
+ limit?: number;
73
+ /**
74
+ * Accessible name of the notifications region.
75
+ * @default 'Notifications'
76
+ */
77
+ label?: string;
78
+ }
79
+ /**
80
+ * Renders toasts created with {@link toast}. Render once, near the app root.
81
+ * Lives in the browser's top layer, so toasts show above everything. While a modal
82
+ * {@link Dialog} or {@link Drawer} is open, toasts render inside it so they stay interactive.
83
+ *
84
+ * @example
85
+ * ```tsx
86
+ * <App />
87
+ * <Toaster placement="top-right" />
88
+ * ```
89
+ */
90
+ export declare function Toaster({ placement, limit, label }: ToasterProps): import("react").JSX.Element;
@@ -0,0 +1,156 @@
1
+ import { useConfig, useDefaults } from "../runtime.js";
2
+ import { colorClass, cx, gapClass, paddingClass, radiusClass, typeClass } from "../classes.js";
3
+ import { useEffect, useRef, useState, useSyncExternalStore } from "react";
4
+ import { jsx, jsxs } from "react/jsx-runtime";
5
+ import { createPortal } from "react-dom";
6
+ //#region src/components/Toast.tsx
7
+ var entries = [];
8
+ var counter = 0;
9
+ var listeners = /* @__PURE__ */ new Set();
10
+ function emit(next) {
11
+ entries = next;
12
+ listeners.forEach((listener) => listener());
13
+ }
14
+ function subscribe(listener) {
15
+ listeners.add(listener);
16
+ return () => listeners.delete(listener);
17
+ }
18
+ /**
19
+ * Shows a toast notification. Requires one {@link Toaster} rendered in the app.
20
+ * Returns the toast's id, for {@link toast.dismiss}.
21
+ *
22
+ * @example
23
+ * ```ts
24
+ * toast({ title: 'Saved', color: 'success' });
25
+ * toast({ title: 'Message deleted', action: { label: 'Undo', onClick: restore } });
26
+ * ```
27
+ */
28
+ function toast(options) {
29
+ const id = `yarcl-toast-${++counter}`;
30
+ emit([...entries, {
31
+ id,
32
+ ...options
33
+ }]);
34
+ return id;
35
+ }
36
+ /** Dismisses a toast by id, or all toasts when called without one. */
37
+ toast.dismiss = (id) => emit(id ? entries.filter((entry) => entry.id !== id) : []);
38
+ function useModalHost() {
39
+ const [host, setHost] = useState(null);
40
+ useEffect(() => {
41
+ const update = () => {
42
+ const modals = [...document.querySelectorAll("dialog")].filter((dialog) => dialog.matches(":modal"));
43
+ setHost(modals.at(-1) ?? null);
44
+ };
45
+ const observer = new MutationObserver(update);
46
+ observer.observe(document.body, {
47
+ subtree: true,
48
+ childList: true,
49
+ attributes: true,
50
+ attributeFilter: ["open"]
51
+ });
52
+ update();
53
+ return () => observer.disconnect();
54
+ }, []);
55
+ return host;
56
+ }
57
+ /**
58
+ * Renders toasts created with {@link toast}. Render once, near the app root.
59
+ * Lives in the browser's top layer, so toasts show above everything. While a modal
60
+ * {@link Dialog} or {@link Drawer} is open, toasts render inside it so they stay interactive.
61
+ *
62
+ * @example
63
+ * ```tsx
64
+ * <App />
65
+ * <Toaster placement="top-right" />
66
+ * ```
67
+ */
68
+ function Toaster({ placement = "bottom-right", limit = 4, label = "Notifications" }) {
69
+ const all = useSyncExternalStore(subscribe, () => entries, () => entries);
70
+ const visible = all.slice(-limit);
71
+ const ref = useRef(null);
72
+ const host = useModalHost();
73
+ useEffect(() => {
74
+ if (all.length > limit) emit(all.slice(-limit));
75
+ }, [all, limit]);
76
+ useEffect(() => {
77
+ const region = ref.current;
78
+ if (!region?.showPopover) return;
79
+ if (region.matches(":popover-open")) region.hidePopover();
80
+ if (visible.length > 0) region.showPopover();
81
+ }, [
82
+ visible.length,
83
+ all,
84
+ host
85
+ ]);
86
+ const region = /* @__PURE__ */ jsx("section", {
87
+ ref,
88
+ popover: "manual",
89
+ "aria-label": label,
90
+ className: cx("yarcl-toaster", `yarcl-toaster-${placement}`),
91
+ children: (placement.startsWith("top") ? [...visible].reverse() : visible).map((entry) => /* @__PURE__ */ jsx(ToastItem, { entry }, entry.id))
92
+ });
93
+ return host ? createPortal(region, host) : region;
94
+ }
95
+ function ToastItem({ entry }) {
96
+ const own = useDefaults("Toast");
97
+ const config = useConfig();
98
+ const { id, title, description, color, radius, gap, padding, textStyle, action, urgent } = entry;
99
+ const duration = entry.duration ?? config.timing.toastDuration;
100
+ const [paused, setPaused] = useState(false);
101
+ useEffect(() => {
102
+ if (paused || duration <= 0) return;
103
+ const timer = setTimeout(() => toast.dismiss(id), duration);
104
+ return () => clearTimeout(timer);
105
+ }, [
106
+ id,
107
+ duration,
108
+ paused
109
+ ]);
110
+ return /* @__PURE__ */ jsxs("div", {
111
+ role: urgent ? "alert" : "status",
112
+ className: cx("yarcl-toast", colorClass(color ?? own.color), radiusClass(radius ?? own.radius), gapClass(gap ?? own.gap), paddingClass(padding ?? own.padding), typeClass(textStyle ?? own.textStyle ?? config.defaults.labelStyle)),
113
+ onMouseEnter: () => setPaused(true),
114
+ onMouseLeave: () => setPaused(false),
115
+ onFocus: () => setPaused(true),
116
+ onBlur: () => setPaused(false),
117
+ children: [
118
+ /* @__PURE__ */ jsxs("div", {
119
+ className: "yarcl-toast-text",
120
+ children: [/* @__PURE__ */ jsx("div", {
121
+ className: "yarcl-toast-title",
122
+ children: title
123
+ }), description != null && /* @__PURE__ */ jsx("div", {
124
+ className: "yarcl-toast-description",
125
+ children: description
126
+ })]
127
+ }),
128
+ action && /* @__PURE__ */ jsx("button", {
129
+ type: "button",
130
+ className: "yarcl-toast-action",
131
+ onClick: () => {
132
+ action.onClick();
133
+ toast.dismiss(id);
134
+ },
135
+ children: action.label
136
+ }),
137
+ /* @__PURE__ */ jsx("button", {
138
+ type: "button",
139
+ className: "yarcl-toast-close",
140
+ "aria-label": "Dismiss",
141
+ onClick: () => toast.dismiss(id),
142
+ children: /* @__PURE__ */ jsx("svg", {
143
+ viewBox: "0 0 24 24",
144
+ fill: "none",
145
+ stroke: "currentColor",
146
+ strokeWidth: "2",
147
+ strokeLinecap: "round",
148
+ "aria-hidden": "true",
149
+ children: /* @__PURE__ */ jsx("path", { d: "M6 6l12 12M18 6 6 18" })
150
+ })
151
+ })
152
+ ]
153
+ });
154
+ }
155
+ //#endregion
156
+ export { Toaster, toast };
@@ -0,0 +1,79 @@
1
+ import { type ComponentProps, type ReactNode } from 'react';
2
+ import type { Color, ComponentSize, Radius, Variant } from '../types';
3
+ /** Props shared by both forms of {@link ToggleGroupProps}. */
4
+ export interface ToggleGroupBaseProps extends Omit<ComponentProps<'div'>, 'defaultValue' | 'onChange' | 'color'> {
5
+ /** Size of every item, from the `sizes` config. */
6
+ size?: ComponentSize<'ToggleGroup'>;
7
+ /** Color of every item, from the `colors` config. */
8
+ color?: Color;
9
+ /** Radius of every item, from the `radii` config, or `'size'` to match the item size. */
10
+ radius?: Radius | 'size';
11
+ /**
12
+ * Variant of unselected items, from the `variants` config.
13
+ * @default config.defaults.softVariant
14
+ */
15
+ variant?: Variant;
16
+ /**
17
+ * Variant of selected items, from the `variants` config.
18
+ * @default config.defaults.variant
19
+ */
20
+ selectedVariant?: Variant;
21
+ /**
22
+ * Joins the items into one segmented control.
23
+ * @default true
24
+ */
25
+ attached?: boolean;
26
+ /** Disables every item. */
27
+ disabled?: boolean;
28
+ /** `ToggleGroup.Item`s. */
29
+ children?: ReactNode;
30
+ }
31
+ /** Props for {@link ToggleGroup}. `type` decides whether one or many items can be on. */
32
+ export type ToggleGroupProps = ToggleGroupBaseProps & ({
33
+ /** One item on at a time, like a segmented control. */
34
+ type: 'single';
35
+ /** Controlled selected value. */
36
+ value?: string | null;
37
+ /** Initially selected value when uncontrolled. */
38
+ defaultValue?: string | null;
39
+ /** Called with the selected value, or `null` when the selected item is turned off. */
40
+ onValueChange?: (value: string | null) => void;
41
+ /** Prevents turning the selected item off, so one is always on. */
42
+ required?: boolean;
43
+ } | {
44
+ /** Any number of items on, like formatting buttons. */
45
+ type: 'multiple';
46
+ /** Controlled selected values. */
47
+ value?: string[];
48
+ /** Initially selected values when uncontrolled. */
49
+ defaultValue?: string[];
50
+ /** Called with all selected values. */
51
+ onValueChange?: (value: string[]) => void;
52
+ required?: never;
53
+ });
54
+ declare function ToggleGroupRoot(props: ToggleGroupProps): import("react").JSX.Element;
55
+ /** Props for `ToggleGroup.Item`. */
56
+ export interface ToggleGroupItemProps extends Omit<ComponentProps<'button'>, 'value' | 'color'> {
57
+ /** Identifies the item in the group's value. */
58
+ value: string;
59
+ /** Makes the item square, for icon-only items. Give it an `aria-label`. */
60
+ icon?: boolean;
61
+ }
62
+ declare function ToggleGroupItem({ value, icon, disabled, className, onClick, ...props }: ToggleGroupItemProps): import("react").JSX.Element;
63
+ /**
64
+ * A set of toggle buttons. `type="single"` behaves like a segmented control,
65
+ * `type="multiple"` like formatting buttons. Items use `aria-pressed`; arrow keys move focus.
66
+ *
67
+ * @example
68
+ * ```tsx
69
+ * <ToggleGroup type="single" defaultValue="week" aria-label="Range" required>
70
+ * <ToggleGroup.Item value="day">Day</ToggleGroup.Item>
71
+ * <ToggleGroup.Item value="week">Week</ToggleGroup.Item>
72
+ * <ToggleGroup.Item value="month">Month</ToggleGroup.Item>
73
+ * </ToggleGroup>
74
+ * ```
75
+ */
76
+ export declare const ToggleGroup: typeof ToggleGroupRoot & {
77
+ Item: typeof ToggleGroupItem;
78
+ };
79
+ export {};
@@ -0,0 +1,101 @@
1
+ import { useConfig, useDefaults } from "../runtime.js";
2
+ import { cx, sizeClass } from "../classes.js";
3
+ import { Button } from "./Button.js";
4
+ import { useControllable } from "../hooks.js";
5
+ import { createContext, useContext, useLayoutEffect, useRef } from "react";
6
+ import { jsx } from "react/jsx-runtime";
7
+ //#region src/components/ToggleGroup.tsx
8
+ var ToggleGroupContext = createContext(null);
9
+ var toArray = (value) => value == null ? [] : Array.isArray(value) ? value : [value];
10
+ function ToggleGroupRoot(props) {
11
+ const config = useConfig();
12
+ const own = useDefaults("ToggleGroup");
13
+ const { type, value, defaultValue, onValueChange, required, size = own.size, color = own.color, radius = own.radius, variant = own.variant ?? config.defaults.softVariant, selectedVariant = own.selectedVariant ?? config.defaults.variant, attached = true, disabled, className, onKeyDown, children, ...rest } = props;
14
+ const [selected, setSelected] = useControllable(value === void 0 ? void 0 : toArray(value), toArray(defaultValue), (next) => type === "multiple" ? onValueChange?.(next) : onValueChange?.(next[0] ?? null));
15
+ const ref = useRef(null);
16
+ useLayoutEffect(() => {
17
+ const items = [...ref.current?.querySelectorAll(".yarcl-toggle-item") ?? []];
18
+ const stop = items.find((item) => item.getAttribute("aria-pressed") === "true" && !item.disabled) ?? items.find((item) => !item.disabled);
19
+ items.forEach((item) => item.tabIndex = item === stop ? 0 : -1);
20
+ });
21
+ function toggle(item) {
22
+ const on = selected.includes(item);
23
+ if (type === "multiple") setSelected(on ? selected.filter((v) => v !== item) : [...selected, item]);
24
+ else if (!on) setSelected([item]);
25
+ else if (!required) setSelected([]);
26
+ }
27
+ function handleKeyDown(event) {
28
+ onKeyDown?.(event);
29
+ const items = [...event.currentTarget.querySelectorAll(".yarcl-toggle-item:not(:disabled)")];
30
+ const current = items.indexOf(document.activeElement);
31
+ if (current === -1) return;
32
+ const next = {
33
+ ArrowRight: (current + 1) % items.length,
34
+ ArrowDown: (current + 1) % items.length,
35
+ ArrowLeft: (current - 1 + items.length) % items.length,
36
+ ArrowUp: (current - 1 + items.length) % items.length,
37
+ Home: 0,
38
+ End: items.length - 1
39
+ }[event.key];
40
+ if (next === void 0) return;
41
+ event.preventDefault();
42
+ items.forEach((item, i) => item.tabIndex = i === next ? 0 : -1);
43
+ items[next].focus();
44
+ }
45
+ return /* @__PURE__ */ jsx(ToggleGroupContext.Provider, {
46
+ value: {
47
+ selected,
48
+ toggle,
49
+ size,
50
+ color,
51
+ radius,
52
+ variant,
53
+ selectedVariant,
54
+ disabled
55
+ },
56
+ children: /* @__PURE__ */ jsx("div", {
57
+ ref,
58
+ role: "group",
59
+ className: cx("yarcl-button-group yarcl-button-group-horizontal yarcl-toggle-group", sizeClass(size, "ToggleGroup"), attached && "yarcl-button-group-attached", className),
60
+ onKeyDown: handleKeyDown,
61
+ ...rest,
62
+ children
63
+ })
64
+ });
65
+ }
66
+ function ToggleGroupItem({ value, icon, disabled, className, onClick, ...props }) {
67
+ const context = useContext(ToggleGroupContext);
68
+ if (!context) throw new Error("yarcl: <ToggleGroup.Item> must be inside <ToggleGroup>");
69
+ const on = context.selected.includes(value);
70
+ return /* @__PURE__ */ jsx(Button, {
71
+ "aria-pressed": on,
72
+ "data-value": value,
73
+ disabled: disabled || context.disabled,
74
+ size: context.size,
75
+ color: context.color,
76
+ radius: context.radius,
77
+ variant: on ? context.selectedVariant : context.variant,
78
+ className: cx("yarcl-toggle-item", icon && "yarcl-icon-button", className),
79
+ onClick: (event) => {
80
+ onClick?.(event);
81
+ context.toggle(value);
82
+ },
83
+ ...props
84
+ });
85
+ }
86
+ /**
87
+ * A set of toggle buttons. `type="single"` behaves like a segmented control,
88
+ * `type="multiple"` like formatting buttons. Items use `aria-pressed`; arrow keys move focus.
89
+ *
90
+ * @example
91
+ * ```tsx
92
+ * <ToggleGroup type="single" defaultValue="week" aria-label="Range" required>
93
+ * <ToggleGroup.Item value="day">Day</ToggleGroup.Item>
94
+ * <ToggleGroup.Item value="week">Week</ToggleGroup.Item>
95
+ * <ToggleGroup.Item value="month">Month</ToggleGroup.Item>
96
+ * </ToggleGroup>
97
+ * ```
98
+ */
99
+ var ToggleGroup = Object.assign(ToggleGroupRoot, { Item: ToggleGroupItem });
100
+ //#endregion
101
+ export { ToggleGroup };
@@ -0,0 +1,46 @@
1
+ import { type Placement } from '@floating-ui/react';
2
+ import { type ReactElement, type ReactNode } from 'react';
3
+ import type { Radius, Spacing, TextStyle } from '../types';
4
+ /** Props for {@link Tooltip}. */
5
+ export interface TooltipProps {
6
+ /** Short text describing the trigger. Keep it plain; tooltips can't contain interactive content. */
7
+ content: ReactNode;
8
+ /** A single focusable element, e.g. an {@link IconButton}. */
9
+ children: ReactElement;
10
+ /**
11
+ * Preferred side. Flips when there isn't room.
12
+ * @default 'top'
13
+ */
14
+ placement?: Placement;
15
+ /**
16
+ * Delay before opening on hover, in ms. Opens immediately on keyboard focus.
17
+ * @default config.timing.tooltipDelay
18
+ */
19
+ delay?: number;
20
+ /**
21
+ * Corner radius, from the `radii` config.
22
+ * @default config.components.Tooltip.radius ?? config.defaults.radius
23
+ */
24
+ radius?: Radius;
25
+ /**
26
+ * Inner spacing, from the `spacing` config.
27
+ * @default config.components.Tooltip.padding ?? config.defaults.padding
28
+ */
29
+ padding?: Spacing;
30
+ /**
31
+ * Typography style, from the `typography.styles` config.
32
+ * @default config.components.Tooltip.textStyle ?? config.defaults.helperStyle
33
+ */
34
+ textStyle?: TextStyle;
35
+ }
36
+ /**
37
+ * A short label shown on hover and keyboard focus.
38
+ *
39
+ * @example
40
+ * ```tsx
41
+ * <Tooltip content="Search">
42
+ * <IconButton aria-label="Search"><SearchIcon /></IconButton>
43
+ * </Tooltip>
44
+ * ```
45
+ */
46
+ export declare function Tooltip({ content, children, placement, delay, radius, padding, textStyle }: TooltipProps): import("react").JSX.Element;
@@ -0,0 +1,62 @@
1
+ import { useConfig, useDefaults } from "../runtime.js";
2
+ import { cx, paddingClass, radiusClass, typeClass } from "../classes.js";
3
+ import { floatingMiddleware, useTrigger } from "../floating.js";
4
+ import { useRef, useState } from "react";
5
+ import { Fragment, jsx, jsxs } from "react/jsx-runtime";
6
+ import { FloatingArrow, FloatingPortal, autoUpdate, useDismiss, useFloating, useFocus, useHover, useInteractions, useRole } from "@floating-ui/react";
7
+ //#region src/components/Tooltip.tsx
8
+ /**
9
+ * A short label shown on hover and keyboard focus.
10
+ *
11
+ * @example
12
+ * ```tsx
13
+ * <Tooltip content="Search">
14
+ * <IconButton aria-label="Search"><SearchIcon /></IconButton>
15
+ * </Tooltip>
16
+ * ```
17
+ */
18
+ function Tooltip({ content, children, placement = "top", delay, radius, padding, textStyle }) {
19
+ const config = useConfig();
20
+ const own = useDefaults("Tooltip");
21
+ const [open, setOpen] = useState(false);
22
+ const arrowRef = useRef(null);
23
+ const { refs, floatingStyles, context } = useFloating({
24
+ open,
25
+ onOpenChange: setOpen,
26
+ placement,
27
+ transform: false,
28
+ whileElementsMounted: autoUpdate,
29
+ middleware: floatingMiddleware({
30
+ gap: 8,
31
+ arrowRef
32
+ })
33
+ });
34
+ const { getReferenceProps, getFloatingProps } = useInteractions([
35
+ useHover(context, {
36
+ move: false,
37
+ delay: {
38
+ open: delay ?? config.timing.tooltipDelay,
39
+ close: 0
40
+ }
41
+ }),
42
+ useFocus(context),
43
+ useDismiss(context),
44
+ useRole(context, { role: "tooltip" })
45
+ ]);
46
+ const trigger = useTrigger(children, refs.setReference, getReferenceProps);
47
+ return /* @__PURE__ */ jsxs(Fragment, { children: [trigger, open && /* @__PURE__ */ jsx(FloatingPortal, { children: /* @__PURE__ */ jsxs("div", {
48
+ ref: refs.setFloating,
49
+ style: floatingStyles,
50
+ className: cx("yarcl-floating yarcl-tooltip", radiusClass(radius ?? own.radius), paddingClass(padding ?? own.padding), typeClass(textStyle ?? own.textStyle ?? config.defaults.helperStyle)),
51
+ ...getFloatingProps(),
52
+ children: [content, /* @__PURE__ */ jsx(FloatingArrow, {
53
+ ref: arrowRef,
54
+ context,
55
+ className: "yarcl-tooltip-arrow",
56
+ width: 10,
57
+ height: 5
58
+ })]
59
+ }) })] });
60
+ }
61
+ //#endregion
62
+ export { Tooltip };
@@ -0,0 +1,18 @@
1
+ import type { ComponentProps, ReactNode } from 'react';
2
+ /** Props for {@link VisuallyHidden}. */
3
+ export interface VisuallyHiddenProps extends ComponentProps<'span'> {
4
+ /** Content exposed to assistive technology. */
5
+ children?: ReactNode;
6
+ }
7
+ /**
8
+ * Keeps content available to assistive technology while removing it from the visual layout.
9
+ *
10
+ * @example
11
+ * ```tsx
12
+ * <Button>
13
+ * <SearchIcon />
14
+ * <VisuallyHidden>Search</VisuallyHidden>
15
+ * </Button>
16
+ * ```
17
+ */
18
+ export declare function VisuallyHidden({ className, ...props }: VisuallyHiddenProps): import("react").JSX.Element;
@@ -0,0 +1,22 @@
1
+ import { cx } from "../classes.js";
2
+ import { jsx } from "react/jsx-runtime";
3
+ //#region src/components/VisuallyHidden.tsx
4
+ /**
5
+ * Keeps content available to assistive technology while removing it from the visual layout.
6
+ *
7
+ * @example
8
+ * ```tsx
9
+ * <Button>
10
+ * <SearchIcon />
11
+ * <VisuallyHidden>Search</VisuallyHidden>
12
+ * </Button>
13
+ * ```
14
+ */
15
+ function VisuallyHidden({ className, ...props }) {
16
+ return /* @__PURE__ */ jsx("span", {
17
+ className: cx("yarcl-visually-hidden", className),
18
+ ...props
19
+ });
20
+ }
21
+ //#endregion
22
+ export { VisuallyHidden };
@@ -0,0 +1,32 @@
1
+ import type { YarclShape } from './define';
2
+ /** A color combination in a config that falls below the required contrast. */
3
+ export interface ContrastIssue {
4
+ /** The config path of the text color, e.g. `colors.warning` or `neutrals.muted`. */
5
+ path: string;
6
+ /** The color scheme the issue occurs in. */
7
+ scheme: 'light' | 'dark';
8
+ /** The contrast ratio, or `null` when it can't be computed (non-hex values). */
9
+ ratio: number | null;
10
+ /** A readable description of the problem. */
11
+ message: string;
12
+ }
13
+ /**
14
+ * Checks a config for text that doesn't reach a contrast ratio (WCAG AA, 4.5:1, by default)
15
+ * in light or dark mode. Returns the problems instead of printing them, so you can run it in a
16
+ * test, a CI step or a script.
17
+ *
18
+ * Checks the text drawn on each semantic color (its computed or configured `on`), and the
19
+ * neutral `text` and `muted` colors on the `bg` and `surface` backgrounds. Colors drawn as text
20
+ * need no check: their readable shade is computed automatically.
21
+ *
22
+ * @example
23
+ * ```ts
24
+ * import { checkContrast } from '@yarcl/react/contrast';
25
+ * import config from './src/yarcl.config';
26
+ *
27
+ * test('design system is readable', () => {
28
+ * expect(checkContrast(config)).toEqual([]);
29
+ * });
30
+ * ```
31
+ */
32
+ export declare function checkContrast(config: YarclShape, min?: number): ContrastIssue[];