@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
|
@@ -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[];
|