@idyeah/diya 0.0.0 → 1.0.1

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 (190) hide show
  1. package/CHANGELOG.md +332 -0
  2. package/NOTICE +29 -0
  3. package/README.md +596 -5
  4. package/dist/DiyaProvider.d.ts +79 -0
  5. package/dist/DiyaProvider.js +77 -0
  6. package/dist/Icon.d.ts +10 -0
  7. package/dist/Icon.js +27 -0
  8. package/dist/accordion/Accordion.d.ts +34 -0
  9. package/dist/accordion/Accordion.js +15 -0
  10. package/dist/alert/Alert.d.ts +49 -0
  11. package/dist/alert/Alert.js +11 -0
  12. package/dist/alert/messages.d.ts +9 -0
  13. package/dist/alert/messages.js +2 -0
  14. package/dist/api.json +7277 -0
  15. package/dist/api.schema.json +214 -0
  16. package/dist/application-patterns/Chatbot.d.ts +114 -0
  17. package/dist/application-patterns/Chatbot.js +110 -0
  18. package/dist/application-patterns/SideNavigation.d.ts +74 -0
  19. package/dist/application-patterns/SideNavigation.js +31 -0
  20. package/dist/application-patterns/chatbot-messages.d.ts +20 -0
  21. package/dist/application-patterns/chatbot-messages.js +2 -0
  22. package/dist/application-patterns/side-navigation-messages.d.ts +5 -0
  23. package/dist/application-patterns/side-navigation-messages.js +2 -0
  24. package/dist/avatar/Avatar.d.ts +32 -0
  25. package/dist/avatar/Avatar.js +19 -0
  26. package/dist/avatar/messages.d.ts +7 -0
  27. package/dist/avatar/messages.js +2 -0
  28. package/dist/badge/Badge.d.ts +42 -0
  29. package/dist/badge/Badge.js +16 -0
  30. package/dist/badge/badge-utils.d.ts +6 -0
  31. package/dist/badge/badge-utils.js +24 -0
  32. package/dist/badge/messages.d.ts +4 -0
  33. package/dist/badge/messages.js +2 -0
  34. package/dist/breadcrumbs/Breadcrumbs.d.ts +34 -0
  35. package/dist/breadcrumbs/Breadcrumbs.js +19 -0
  36. package/dist/breadcrumbs/messages.d.ts +5 -0
  37. package/dist/breadcrumbs/messages.js +2 -0
  38. package/dist/button/Button.d.ts +46 -0
  39. package/dist/button/Button.js +44 -0
  40. package/dist/calendar/Calendar.d.ts +55 -0
  41. package/dist/calendar/Calendar.js +61 -0
  42. package/dist/calendar/messages.d.ts +8 -0
  43. package/dist/calendar/messages.js +2 -0
  44. package/dist/calendar/month.d.ts +61 -0
  45. package/dist/calendar/month.js +134 -0
  46. package/dist/card/Card.d.ts +46 -0
  47. package/dist/card/Card.js +8 -0
  48. package/dist/choice/Choice.d.ts +72 -0
  49. package/dist/choice/Choice.js +68 -0
  50. package/dist/choice/messages.d.ts +4 -0
  51. package/dist/choice/messages.js +2 -0
  52. package/dist/code-block/CodeBlock.d.ts +20 -0
  53. package/dist/code-block/CodeBlock.js +49 -0
  54. package/dist/code-block/messages.d.ts +8 -0
  55. package/dist/code-block/messages.js +2 -0
  56. package/dist/combobox/Combobox.d.ts +60 -0
  57. package/dist/combobox/Combobox.js +160 -0
  58. package/dist/combobox/messages.d.ts +9 -0
  59. package/dist/combobox/messages.js +2 -0
  60. package/dist/combobox/useAnchoredPopover.d.ts +5 -0
  61. package/dist/combobox/useAnchoredPopover.js +100 -0
  62. package/dist/data-viz/DataViz.d.ts +140 -0
  63. package/dist/data-viz/DataViz.js +469 -0
  64. package/dist/data-viz/index.d.ts +2 -0
  65. package/dist/data-viz/index.js +1 -0
  66. package/dist/data-viz/messages.d.ts +11 -0
  67. package/dist/data-viz/messages.js +2 -0
  68. package/dist/date-picker/DatePicker.d.ts +57 -0
  69. package/dist/date-picker/DatePicker.js +146 -0
  70. package/dist/date-picker/messages.d.ts +13 -0
  71. package/dist/date-picker/messages.js +2 -0
  72. package/dist/dialog/Dialog.d.ts +84 -0
  73. package/dist/dialog/Dialog.js +107 -0
  74. package/dist/dialog/messages.d.ts +9 -0
  75. package/dist/dialog/messages.js +2 -0
  76. package/dist/fonts/DIYASans-Italic.woff2 +0 -0
  77. package/dist/fonts/DIYASans.woff2 +0 -0
  78. package/dist/fonts/OFL.txt +94 -0
  79. package/dist/index.d.ts +50 -0
  80. package/dist/index.js +46 -0
  81. package/dist/internal/brand.d.ts +33 -0
  82. package/dist/internal/brand.js +57 -0
  83. package/dist/internal/cx.d.ts +2 -0
  84. package/dist/internal/cx.js +4 -0
  85. package/dist/internal/link.d.ts +14 -0
  86. package/dist/internal/link.js +7 -0
  87. package/dist/internal/slot.d.ts +15 -0
  88. package/dist/internal/slot.js +67 -0
  89. package/dist/layout/Inline.d.ts +4 -0
  90. package/dist/layout/Inline.js +7 -0
  91. package/dist/layout/Stack.d.ts +4 -0
  92. package/dist/layout/Stack.js +7 -0
  93. package/dist/layout/layout.d.ts +32 -0
  94. package/dist/layout/layout.js +12 -0
  95. package/dist/link/TextLink.d.ts +23 -0
  96. package/dist/link/TextLink.js +17 -0
  97. package/dist/list/List.d.ts +48 -0
  98. package/dist/list/List.js +17 -0
  99. package/dist/list/messages.d.ts +4 -0
  100. package/dist/list/messages.js +2 -0
  101. package/dist/menu/Menu.d.ts +49 -0
  102. package/dist/menu/Menu.js +54 -0
  103. package/dist/messages-defaults.d.ts +201 -0
  104. package/dist/messages-defaults.js +61 -0
  105. package/dist/messages.d.ts +12 -0
  106. package/dist/messages.js +1 -0
  107. package/dist/metric/Metric.d.ts +42 -0
  108. package/dist/metric/Metric.js +11 -0
  109. package/dist/metric/messages.d.ts +9 -0
  110. package/dist/metric/messages.js +2 -0
  111. package/dist/overlay/FloatingSurface.d.ts +29 -0
  112. package/dist/overlay/FloatingSurface.js +254 -0
  113. package/dist/page-header/PageHeader.d.ts +38 -0
  114. package/dist/page-header/PageHeader.js +10 -0
  115. package/dist/pagination/Pagination.d.ts +44 -0
  116. package/dist/pagination/Pagination.js +26 -0
  117. package/dist/pagination/messages.d.ts +11 -0
  118. package/dist/pagination/messages.js +2 -0
  119. package/dist/popover/Popover.d.ts +23 -0
  120. package/dist/popover/Popover.js +8 -0
  121. package/dist/progress/Progress.d.ts +33 -0
  122. package/dist/progress/Progress.js +14 -0
  123. package/dist/progress/messages.d.ts +4 -0
  124. package/dist/progress/messages.js +2 -0
  125. package/dist/prose/Prose.d.ts +14 -0
  126. package/dist/prose/Prose.js +7 -0
  127. package/dist/rating/Rating.d.ts +32 -0
  128. package/dist/rating/Rating.js +27 -0
  129. package/dist/rating/messages.d.ts +6 -0
  130. package/dist/rating/messages.js +2 -0
  131. package/dist/search/Search.d.ts +11 -0
  132. package/dist/search/Search.js +9 -0
  133. package/dist/select/Select.d.ts +65 -0
  134. package/dist/select/Select.js +19 -0
  135. package/dist/select/messages.d.ts +4 -0
  136. package/dist/select/messages.js +2 -0
  137. package/dist/site-header/SiteHeader.d.ts +88 -0
  138. package/dist/site-header/SiteHeader.js +211 -0
  139. package/dist/site-header/messages.d.ts +8 -0
  140. package/dist/site-header/messages.js +2 -0
  141. package/dist/slider/Slider.d.ts +54 -0
  142. package/dist/slider/Slider.js +89 -0
  143. package/dist/slider/messages.d.ts +5 -0
  144. package/dist/slider/messages.js +2 -0
  145. package/dist/styles.css +3386 -0
  146. package/dist/switch/Switch.d.ts +47 -0
  147. package/dist/switch/Switch.js +18 -0
  148. package/dist/table/Table.d.ts +99 -0
  149. package/dist/table/Table.js +158 -0
  150. package/dist/table/messages.d.ts +23 -0
  151. package/dist/table/messages.js +2 -0
  152. package/dist/tabs/Tabs.d.ts +49 -0
  153. package/dist/tabs/Tabs.js +26 -0
  154. package/dist/tag/Tag.d.ts +37 -0
  155. package/dist/tag/Tag.js +20 -0
  156. package/dist/tag/messages.d.ts +4 -0
  157. package/dist/tag/messages.js +2 -0
  158. package/dist/text-area/TextArea.d.ts +47 -0
  159. package/dist/text-area/TextArea.js +19 -0
  160. package/dist/text-input/TextInput.d.ts +53 -0
  161. package/dist/text-input/TextInput.js +35 -0
  162. package/dist/text-input/messages.d.ts +4 -0
  163. package/dist/text-input/messages.js +3 -0
  164. package/dist/text-input/native-field.d.ts +15 -0
  165. package/dist/text-input/native-field.js +56 -0
  166. package/dist/toast/Toast.d.ts +24 -0
  167. package/dist/toast/Toast.js +79 -0
  168. package/dist/toast/messages.d.ts +4 -0
  169. package/dist/toast/messages.js +2 -0
  170. package/dist/tooltip/Tooltip.d.ts +22 -0
  171. package/dist/tooltip/Tooltip.js +92 -0
  172. package/dist/workflow/ContentState.d.ts +44 -0
  173. package/dist/workflow/ContentState.js +11 -0
  174. package/dist/workflow/Drawer.d.ts +48 -0
  175. package/dist/workflow/Drawer.js +51 -0
  176. package/dist/workflow/FileUpload.d.ts +67 -0
  177. package/dist/workflow/FileUpload.js +47 -0
  178. package/dist/workflow/Form.d.ts +52 -0
  179. package/dist/workflow/Form.js +16 -0
  180. package/dist/workflow/Wizard.d.ts +76 -0
  181. package/dist/workflow/Wizard.js +31 -0
  182. package/dist/workflow/drawer-messages.d.ts +4 -0
  183. package/dist/workflow/drawer-messages.js +2 -0
  184. package/dist/workflow/file-upload-messages.d.ts +11 -0
  185. package/dist/workflow/file-upload-messages.js +2 -0
  186. package/dist/workflow/form-messages.d.ts +5 -0
  187. package/dist/workflow/form-messages.js +2 -0
  188. package/dist/workflow/wizard-messages.d.ts +9 -0
  189. package/dist/workflow/wizard-messages.js +2 -0
  190. package/package.json +82 -7
@@ -0,0 +1,53 @@
1
+ import { type InputHTMLAttributes, type ReactNode } from 'react';
2
+ import { textInputMessages } from './messages.js';
3
+ declare module '../messages.js' {
4
+ interface DiyaMessages {
5
+ textInput: typeof textInputMessages;
6
+ }
7
+ }
8
+ /** Native input attributes, including `name`, `onChange`, `onBlur` and the ref, go on the input; `className` and `style` go on the outer field wrapper. */
9
+ export type TextInputProps = Omit<InputHTMLAttributes<HTMLInputElement>, 'size' | 'value' | 'defaultValue'> & {
10
+ /** Visible label above the input; also its accessible name. Hide it with hideLabel. */
11
+ label: string;
12
+ /** Hides the label visually but keeps it as the input's accessible name. */
13
+ hideLabel?: boolean;
14
+ /** Visible hint below the input, announced with it; `error` replaces it while set. */
15
+ helperText?: string;
16
+ /** Visible error message below the input; marks the field invalid while set. */
17
+ error?: string;
18
+ /** Control height and type size; defaults to 'medium'. */
19
+ size?: 'small' | 'medium' | 'large';
20
+ /** Decorative icon inside the input's start edge; hidden from assistive tech. */
21
+ leadingIcon?: ReactNode;
22
+ /** Shows a clear button while there is text; it empties the value and refocuses the input. */
23
+ clearable?: boolean;
24
+ /** The current text when controlled; pair it with `onValueChange`. */
25
+ value?: string;
26
+ /** The starting text when uncontrolled; a form reset returns to it. */
27
+ defaultValue?: string;
28
+ /** Called with the new text on every edit and when cleared. */
29
+ onValueChange?: (value: string) => void;
30
+ };
31
+ /** A single-line labeled text field with optional helper text, error, icon and clear button. */
32
+ export declare const TextInput: import("react").ForwardRefExoticComponent<Omit<InputHTMLAttributes<HTMLInputElement>, "value" | "size" | "defaultValue"> & {
33
+ /** Visible label above the input; also its accessible name. Hide it with hideLabel. */
34
+ label: string;
35
+ /** Hides the label visually but keeps it as the input's accessible name. */
36
+ hideLabel?: boolean;
37
+ /** Visible hint below the input, announced with it; `error` replaces it while set. */
38
+ helperText?: string;
39
+ /** Visible error message below the input; marks the field invalid while set. */
40
+ error?: string;
41
+ /** Control height and type size; defaults to 'medium'. */
42
+ size?: "small" | "medium" | "large";
43
+ /** Decorative icon inside the input's start edge; hidden from assistive tech. */
44
+ leadingIcon?: ReactNode;
45
+ /** Shows a clear button while there is text; it empties the value and refocuses the input. */
46
+ clearable?: boolean;
47
+ /** The current text when controlled; pair it with `onValueChange`. */
48
+ value?: string;
49
+ /** The starting text when uncontrolled; a form reset returns to it. */
50
+ defaultValue?: string;
51
+ /** Called with the new text on every edit and when cleared. */
52
+ onValueChange?: (value: string) => void;
53
+ } & import("react").RefAttributes<HTMLInputElement>>;
@@ -0,0 +1,35 @@
1
+ 'use client';
2
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
3
+ import { forwardRef, useId, useRef } from 'react';
4
+ import { Icon } from '../Icon.js';
5
+ import { useDiyaMessages } from '../DiyaProvider.js';
6
+ import { cx } from '../internal/cx.js';
7
+ import { textInputMessages } from './messages.js';
8
+ import { setNativeValue, useFormReset, useMergedRef, useNativeMirror } from './native-field.js';
9
+ /** A single-line labeled text field with optional helper text, error, icon and clear button. */
10
+ export const TextInput = forwardRef(function TextInput({ label, hideLabel = false, helperText, error, size = 'medium', leadingIcon, clearable = false, value, defaultValue, onValueChange, onChange, id, disabled, readOnly, className, style, 'aria-invalid': ariaInvalid, 'aria-describedby': describedBy, ...props }, ref) {
11
+ const generatedId = useId();
12
+ const inputId = id ?? generatedId;
13
+ const description = error || helperText;
14
+ const descriptionId = `${inputId}-description`;
15
+ const input = useRef(null);
16
+ const inputRef = useMergedRef(ref, input);
17
+ const controlled = value !== undefined;
18
+ const [current, sync] = useNativeMirror(() => input.current?.value, value ?? defaultValue ?? '');
19
+ useFormReset(input, sync);
20
+ const text = controlled ? value : current;
21
+ const t = useDiyaMessages('textInput', textInputMessages);
22
+ return (_jsxs("div", { className: cx('diya-text-field', className), style: style, "data-size": size, "data-disabled": disabled || undefined, "data-invalid": !!error || undefined, children: [_jsx("label", { htmlFor: inputId, className: hideLabel ? 'diya-sr-only' : undefined, children: label }), _jsxs("div", { className: "diya-text-field-control", children: [leadingIcon && (_jsx("span", { className: "diya-text-field-leading", "aria-hidden": "true", children: leadingIcon })), _jsx("input", { ...props, ref: inputRef, id: inputId, ...(controlled ? { value } : { defaultValue }), onChange: (event) => {
23
+ onChange?.(event);
24
+ if (!controlled)
25
+ sync();
26
+ onValueChange?.(event.target.value);
27
+ }, disabled: disabled, readOnly: readOnly, "aria-invalid": error ? true : ariaInvalid, "aria-describedby": [describedBy, description ? descriptionId : undefined].filter(Boolean).join(' ') ||
28
+ undefined }), clearable && text.length > 0 && (_jsx("button", { type: "button", className: "diya-text-field-clear", "aria-label": t.clear(label), disabled: disabled || readOnly || !text, onClick: () => {
29
+ if (!input.current)
30
+ return;
31
+ // Clearing goes through the input itself, so onChange and form libraries see it.
32
+ setNativeValue(input.current, '');
33
+ input.current.focus();
34
+ }, children: _jsx(Icon, { name: "close", size: 14 }) }))] }), description && (_jsxs("p", { id: descriptionId, className: "diya-text-field-description", children: [error && _jsx("span", { "aria-hidden": "true", children: "!" }), description] }))] }));
35
+ });
@@ -0,0 +1,4 @@
1
+ /** textInput: English defaults, overridable through DiyaProvider `messages`. */
2
+ export declare const textInputMessages: {
3
+ clear: (label: string) => string;
4
+ };
@@ -0,0 +1,3 @@
1
+ /** textInput: English defaults, overridable through DiyaProvider `messages`. */
2
+ // The label keeps its case: "Clear Search Fieldbook", never "Clear search fieldbook".
3
+ export const textInputMessages = { clear: (label) => `Clear ${label}` };
@@ -0,0 +1,15 @@
1
+ import { type ForwardedRef, type RefObject } from 'react';
2
+ /** Points a forwarded ref and DIYA's own ref at the same node. */
3
+ export declare function useMergedRef<T>(forwarded: ForwardedRef<T>, own: {
4
+ current: T | null;
5
+ }): (node: T | null) => void;
6
+ /**
7
+ * DIYA's own picture of an uncontrolled control's value, for the parts it draws itself
8
+ * (clear button, count, fill). It rereads the DOM after every render and after a form
9
+ * reset, so values written straight to the element, by react-hook-form for example, show up.
10
+ */
11
+ export declare function useNativeMirror<T>(read: () => T | undefined, initial: T, same?: (a: T, b: T) => boolean): readonly [T, () => void];
12
+ /** Calls `onReset` after the owning form has been reset, once the browser has restored default values. */
13
+ export declare function useFormReset(element: RefObject<HTMLInputElement | HTMLSelectElement | HTMLTextAreaElement | HTMLButtonElement | null>, onReset: () => void): void;
14
+ /** Sets a native control's value the way typing does, so React's onChange (and any form library) sees it. */
15
+ export declare function setNativeValue(element: HTMLInputElement | HTMLTextAreaElement, value: string): void;
@@ -0,0 +1,56 @@
1
+ 'use client';
2
+ import { useCallback, useEffect, useLayoutEffect, useRef, useState } from 'react';
3
+ const useClientLayoutEffect = typeof window === 'undefined' ? useEffect : useLayoutEffect;
4
+ /** Points a forwarded ref and DIYA's own ref at the same node. */
5
+ export function useMergedRef(forwarded, own) {
6
+ return useCallback((node) => {
7
+ own.current = node;
8
+ if (typeof forwarded === 'function')
9
+ forwarded(node);
10
+ else if (forwarded)
11
+ forwarded.current = node;
12
+ }, [forwarded, own]);
13
+ }
14
+ /**
15
+ * DIYA's own picture of an uncontrolled control's value, for the parts it draws itself
16
+ * (clear button, count, fill). It rereads the DOM after every render and after a form
17
+ * reset, so values written straight to the element, by react-hook-form for example, show up.
18
+ */
19
+ export function useNativeMirror(read, initial, same = Object.is) {
20
+ const [value, setValue] = useState(initial);
21
+ const reader = useRef(read);
22
+ reader.current = read;
23
+ const sameRef = useRef(same);
24
+ sameRef.current = same;
25
+ const sync = useCallback(() => {
26
+ const next = reader.current();
27
+ if (next !== undefined)
28
+ setValue((previous) => sameRef.current(previous, next) ? previous : next);
29
+ }, []);
30
+ useClientLayoutEffect(sync);
31
+ return [value, sync];
32
+ }
33
+ /** Calls `onReset` after the owning form has been reset, once the browser has restored default values. */
34
+ export function useFormReset(element, onReset) {
35
+ const callback = useRef(onReset);
36
+ callback.current = onReset;
37
+ useEffect(() => {
38
+ const form = element.current?.form;
39
+ if (!form)
40
+ return;
41
+ // The reset event fires before the browser restores defaults; read them afterwards.
42
+ const reset = () => queueMicrotask(() => callback.current());
43
+ form.addEventListener('reset', reset);
44
+ return () => form.removeEventListener('reset', reset);
45
+ });
46
+ }
47
+ /** Sets a native control's value the way typing does, so React's onChange (and any form library) sees it. */
48
+ export function setNativeValue(element, value) {
49
+ const prototype = Object.getPrototypeOf(element);
50
+ const setter = Object.getOwnPropertyDescriptor(prototype, 'value')?.set;
51
+ if (setter)
52
+ setter.call(element, value);
53
+ else
54
+ element.value = value;
55
+ element.dispatchEvent(new Event('input', { bubbles: true }));
56
+ }
@@ -0,0 +1,24 @@
1
+ import { type AlertProps } from '../alert/Alert.js';
2
+ import { toastMessages } from './messages.js';
3
+ declare module '../messages.js' {
4
+ interface DiyaMessages {
5
+ toast: typeof toastMessages;
6
+ }
7
+ }
8
+ export type ToastProps = Omit<AlertProps, 'onDismiss'> & {
9
+ /** Called once when the toast goes, by timer, close button or Escape; unmount the toast here. */
10
+ onDismiss: () => void;
11
+ /** Milliseconds before it dismisses itself, paused while hovered, focused or the tab is hidden; 0 or less keeps it until dismissed. Toasts with an `action` never time out. Defaults to 8000. */
12
+ duration?: number;
13
+ /** Shows a close button; a toast with an `action` always has one. Defaults to true. */
14
+ showClose?: boolean;
15
+ };
16
+ /** A temporary Alert floated above the page that dismisses itself after a while. `className`, `style` and the ref reach the Alert. */
17
+ export declare const Toast: import("react").ForwardRefExoticComponent<Omit<AlertProps, "onDismiss"> & {
18
+ /** Called once when the toast goes, by timer, close button or Escape; unmount the toast here. */
19
+ onDismiss: () => void;
20
+ /** Milliseconds before it dismisses itself, paused while hovered, focused or the tab is hidden; 0 or less keeps it until dismissed. Toasts with an `action` never time out. Defaults to 8000. */
21
+ duration?: number;
22
+ /** Shows a close button; a toast with an `action` always has one. Defaults to true. */
23
+ showClose?: boolean;
24
+ } & import("react").RefAttributes<HTMLDivElement>>;
@@ -0,0 +1,79 @@
1
+ 'use client';
2
+ import { jsx as _jsx } from "react/jsx-runtime";
3
+ import { forwardRef, useEffect, useRef, useState } from 'react';
4
+ import { createPortal } from 'react-dom';
5
+ import { Alert } from '../alert/Alert.js';
6
+ import { useDiyaMessages, useDiyaPortal } from '../DiyaProvider.js';
7
+ import { toastMessages } from './messages.js';
8
+ /** A temporary Alert floated above the page that dismisses itself after a while. `className`, `style` and the ref reach the Alert. */
9
+ export const Toast = forwardRef(function Toast({ onDismiss, duration = 8000, ...props }, ref) {
10
+ const portal = useDiyaPortal();
11
+ const t = useDiyaMessages('toast', toastMessages);
12
+ const [hovered, setHovered] = useState(false);
13
+ const [focused, setFocused] = useState(false);
14
+ const [hidden, setHidden] = useState(false);
15
+ const [mounted, setMounted] = useState(false);
16
+ const region = useRef(null);
17
+ const returnFocus = useRef(null);
18
+ const remaining = useRef(duration);
19
+ const callback = useRef(onDismiss);
20
+ callback.current = onDismiss;
21
+ // A toast is dismissed once, whether by the timer, the button or Escape,
22
+ // even if the host keeps it mounted afterwards.
23
+ const dismissed = useRef(false);
24
+ const settle = () => {
25
+ if (dismissed.current)
26
+ return;
27
+ dismissed.current = true;
28
+ callback.current();
29
+ };
30
+ useEffect(() => {
31
+ remaining.current = duration;
32
+ }, [duration]);
33
+ useEffect(() => {
34
+ setMounted(true);
35
+ returnFocus.current = document.activeElement;
36
+ const visibility = () => setHidden(document.hidden);
37
+ visibility();
38
+ document.addEventListener('visibilitychange', visibility);
39
+ return () => document.removeEventListener('visibilitychange', visibility);
40
+ }, []);
41
+ useEffect(() => {
42
+ // Actionable messages remain until dismissed so actions are not time-limited.
43
+ if (!mounted || hovered || focused || hidden || props.action || duration <= 0 || dismissed.current)
44
+ return;
45
+ const started = Date.now();
46
+ const timer = setTimeout(settle, remaining.current);
47
+ return () => {
48
+ clearTimeout(timer);
49
+ remaining.current = Math.max(0, remaining.current - (Date.now() - started));
50
+ };
51
+ }, [mounted, hovered, focused, hidden, props.action, duration]);
52
+ function dismiss() {
53
+ if (region.current?.contains(document.activeElement))
54
+ returnFocus.current?.focus();
55
+ settle();
56
+ }
57
+ if (!mounted || !portal)
58
+ return null;
59
+ const { showClose = true, ...alertProps } = props;
60
+ const modal = returnFocus.current?.closest('dialog[open]');
61
+ const target = modal && !modal.contains(portal) ? modal : portal;
62
+ return createPortal(_jsx("div", { ref: region, className: "diya-toast-region", "aria-label": t.label, onMouseEnter: () => setHovered(true), onMouseLeave: () => setHovered(false), onFocus: () => setFocused(true), onBlur: (event) => {
63
+ if (!event.currentTarget.contains(event.relatedTarget))
64
+ setFocused(false);
65
+ }, onKeyDown: (event) => {
66
+ if (event.key === 'Escape') {
67
+ event.preventDefault();
68
+ dismiss();
69
+ }
70
+ }, children: _jsx(Alert, { ...alertProps, ref: ref, action: alertProps.action
71
+ ? {
72
+ ...alertProps.action,
73
+ onClick: () => {
74
+ returnFocus.current?.focus();
75
+ alertProps.action?.onClick();
76
+ },
77
+ }
78
+ : undefined, onDismiss: showClose || props.action ? dismiss : undefined }) }), target);
79
+ });
@@ -0,0 +1,4 @@
1
+ /** toast: English defaults, overridable through DiyaProvider `messages`. */
2
+ export declare const toastMessages: {
3
+ label: string;
4
+ };
@@ -0,0 +1,2 @@
1
+ /** toast: English defaults, overridable through DiyaProvider `messages`. */
2
+ export const toastMessages = { label: 'Notification' };
@@ -0,0 +1,22 @@
1
+ import { type HTMLAttributes, type ReactNode } from 'react';
2
+ export type TooltipProps = Omit<HTMLAttributes<HTMLSpanElement>, 'content' | 'children'> & {
3
+ /** Visible text in the bubble, shown on hover, focus or tap; also read as the trigger's description. */
4
+ content: string;
5
+ /** Accessible name for the tooltip's own trigger button, not shown, such as 'Billing help'. Leave it out when the child is your own trigger. */
6
+ label?: string;
7
+ /** With `label`, noninteractive content for the tooltip's own button, such as an icon. Without it, your single trigger element, such as `<Button iconOnly aria-label="Delete">`, which gets the hover, focus and description wiring. */
8
+ children: ReactNode;
9
+ /** Preferred side for the bubble; it moves to stay on screen. Defaults to 'top'. */
10
+ placement?: 'top' | 'bottom' | 'left' | 'right';
11
+ };
12
+ /** A short hint in a bubble beside its trigger: your own child element, or the tooltip's button when `label` is set. The ref reaches the trigger. */
13
+ export declare const Tooltip: import("react").ForwardRefExoticComponent<Omit<HTMLAttributes<HTMLSpanElement>, "children" | "content"> & {
14
+ /** Visible text in the bubble, shown on hover, focus or tap; also read as the trigger's description. */
15
+ content: string;
16
+ /** Accessible name for the tooltip's own trigger button, not shown, such as 'Billing help'. Leave it out when the child is your own trigger. */
17
+ label?: string;
18
+ /** With `label`, noninteractive content for the tooltip's own button, such as an icon. Without it, your single trigger element, such as `<Button iconOnly aria-label="Delete">`, which gets the hover, focus and description wiring. */
19
+ children: ReactNode;
20
+ /** Preferred side for the bubble; it moves to stay on screen. Defaults to 'top'. */
21
+ placement?: "top" | "bottom" | "left" | "right";
22
+ } & import("react").RefAttributes<HTMLButtonElement>>;
@@ -0,0 +1,92 @@
1
+ 'use client';
2
+ import { jsx as _jsx, Fragment as _Fragment, jsxs as _jsxs } from "react/jsx-runtime";
3
+ import { cloneElement, forwardRef, isValidElement, useEffect, useId, useMemo, useRef, useState } from 'react';
4
+ import { createPortal } from 'react-dom';
5
+ import { useDiyaPortal } from '../DiyaProvider.js';
6
+ import { cx } from '../internal/cx.js';
7
+ import { composeRefs, onlyChild, renderSlot } from '../internal/slot.js';
8
+ /** A short hint in a bubble beside its trigger: your own child element, or the tooltip's button when `label` is set. The ref reaches the trigger. */
9
+ export const Tooltip = forwardRef(function Tooltip({ content, label, children, placement = 'top', className, ...props }, ref) {
10
+ const providerPortal = useDiyaPortal();
11
+ const id = useId();
12
+ const leaveTimer = useRef(undefined);
13
+ const trigger = useRef(null);
14
+ const setTrigger = useMemo(() => composeRefs(trigger, ref), [ref]);
15
+ const tip = useRef(null);
16
+ const [open, setOpen] = useState(false);
17
+ const modal = open ? trigger.current?.closest('dialog[open]') : null;
18
+ const portal = modal && !modal.contains(providerPortal) ? modal : providerPortal;
19
+ const [position, setPosition] = useState({ left: 0, top: 0, ready: false });
20
+ useEffect(() => {
21
+ if (!open)
22
+ return;
23
+ const anchor = trigger.current?.getBoundingClientRect();
24
+ const box = tip.current?.getBoundingClientRect();
25
+ if (anchor && box) {
26
+ let left = anchor.left + (anchor.width - box.width) / 2;
27
+ let top = placement === 'bottom' ? anchor.bottom + 10 : anchor.top - box.height - 10;
28
+ if (placement === 'left' || placement === 'right') {
29
+ left = placement === 'left' ? anchor.left - box.width - 10 : anchor.right + 10;
30
+ top = anchor.top + (anchor.height - box.height) / 2;
31
+ }
32
+ if (top < 8)
33
+ top = anchor.bottom + 10;
34
+ if (top + box.height > window.innerHeight - 8)
35
+ top = anchor.top - box.height - 10;
36
+ setPosition({
37
+ left: Math.max(8, Math.min(left, window.innerWidth - box.width - 8)),
38
+ top: Math.max(8, top),
39
+ ready: true,
40
+ });
41
+ }
42
+ const close = () => setOpen(false);
43
+ const escape = (event) => {
44
+ if (event.key === 'Escape')
45
+ close();
46
+ };
47
+ window.addEventListener('keydown', escape);
48
+ window.addEventListener('scroll', close, true);
49
+ window.addEventListener('resize', close);
50
+ return () => {
51
+ window.removeEventListener('keydown', escape);
52
+ window.removeEventListener('scroll', close, true);
53
+ window.removeEventListener('resize', close);
54
+ };
55
+ }, [open, placement, content, portal]);
56
+ useEffect(() => () => clearTimeout(leaveTimer.current), []);
57
+ function hideSoon() {
58
+ leaveTimer.current = setTimeout(() => setOpen(false), 120);
59
+ }
60
+ function show() {
61
+ clearTimeout(leaveTimer.current);
62
+ if (!open)
63
+ setPosition((old) => ({ ...old, ready: false }));
64
+ setOpen(true);
65
+ }
66
+ const bubble = open &&
67
+ portal &&
68
+ createPortal(_jsx("span", { ref: tip, id: id, role: "tooltip", className: "diya-tooltip-bubble", style: {
69
+ left: position.left,
70
+ top: position.top,
71
+ visibility: position.ready ? 'visible' : 'hidden',
72
+ }, onMouseEnter: () => {
73
+ clearTimeout(leaveTimer.current);
74
+ setOpen(true);
75
+ }, onMouseLeave: hideSoon, children: content }), portal);
76
+ if (label === undefined) {
77
+ const child = onlyChild(children);
78
+ if (!isValidElement(child))
79
+ return null;
80
+ const own = child;
81
+ const described = cx(own.props['aria-describedby'], open ? id : undefined) || undefined;
82
+ return (_jsxs(_Fragment, { children: [renderSlot(cloneElement(own, { 'aria-describedby': described }), {
83
+ ...props,
84
+ className,
85
+ onMouseEnter: show,
86
+ onMouseLeave: hideSoon,
87
+ onFocus: show,
88
+ onBlur: () => setOpen(false),
89
+ }, setTrigger), bubble] }));
90
+ }
91
+ return (_jsxs("span", { ...props, className: cx('diya-tooltip-anchor', className), onMouseEnter: show, onMouseLeave: hideSoon, children: [_jsx("button", { ref: setTrigger, className: "diya-tooltip-trigger", type: "button", "aria-label": label, "aria-describedby": open ? id : undefined, onFocus: show, onBlur: () => setOpen(false), onClick: show, children: children }), bubble] }));
92
+ });
@@ -0,0 +1,44 @@
1
+ import { type HTMLAttributes, type ReactNode } from 'react';
2
+ export type ContentStateProps = Omit<HTMLAttributes<HTMLElement>, 'title' | 'children'> & {
3
+ /** Visible heading that says what happened, e.g. "No projects yet". */
4
+ title: string;
5
+ /** Heading level of the title, h1 to h6. Defaults to 3. */
6
+ headingLevel?: 1 | 2 | 3 | 4 | 5 | 6;
7
+ /** Supporting text that says what to do next. */
8
+ description?: ReactNode;
9
+ /** Decorative icon above the title; hidden from assistive technology. */
10
+ icon?: ReactNode;
11
+ /** One pill button under the text. */
12
+ action?: {
13
+ /** Visible button text. */
14
+ label: string;
15
+ /** Called when the button is pressed. */
16
+ onClick: () => void;
17
+ /** Disables the button. */
18
+ disabled?: boolean;
19
+ };
20
+ /** Announce immediately (an error), rather than politely; defaults to false. */
21
+ urgent?: boolean;
22
+ };
23
+ /** An empty, error or finished state for a region, with an optional next-step button. */
24
+ export declare const ContentState: import("react").ForwardRefExoticComponent<Omit<HTMLAttributes<HTMLElement>, "children" | "title"> & {
25
+ /** Visible heading that says what happened, e.g. "No projects yet". */
26
+ title: string;
27
+ /** Heading level of the title, h1 to h6. Defaults to 3. */
28
+ headingLevel?: 1 | 2 | 3 | 4 | 5 | 6;
29
+ /** Supporting text that says what to do next. */
30
+ description?: ReactNode;
31
+ /** Decorative icon above the title; hidden from assistive technology. */
32
+ icon?: ReactNode;
33
+ /** One pill button under the text. */
34
+ action?: {
35
+ /** Visible button text. */
36
+ label: string;
37
+ /** Called when the button is pressed. */
38
+ onClick: () => void;
39
+ /** Disables the button. */
40
+ disabled?: boolean;
41
+ };
42
+ /** Announce immediately (an error), rather than politely; defaults to false. */
43
+ urgent?: boolean;
44
+ } & import("react").RefAttributes<HTMLElement>>;
@@ -0,0 +1,11 @@
1
+ 'use client';
2
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
3
+ import { forwardRef, useId } from 'react';
4
+ import { Button } from '../button/Button.js';
5
+ import { cx } from '../internal/cx.js';
6
+ /** An empty, error or finished state for a region, with an optional next-step button. */
7
+ export const ContentState = forwardRef(function ContentState({ title, headingLevel = 3, description, icon, action, urgent = false, className, ...props }, ref) {
8
+ const id = useId();
9
+ const Heading = `h${headingLevel}`;
10
+ return _jsxs("section", { "aria-labelledby": id, ...props, ref: ref, className: cx('diya-workflow-surface diya-content-state', className), children: [icon && _jsx("span", { className: "diya-workflow-symbol", "aria-hidden": "true", children: icon }), _jsxs("div", { role: urgent ? 'alert' : 'status', "aria-atomic": "true", children: [_jsx(Heading, { id: id, className: "diya-content-state-title", children: title }), description && _jsx("p", { children: description })] }), action && _jsx(Button, { disabled: action.disabled, onClick: action.onClick, children: action.label })] });
11
+ });
@@ -0,0 +1,48 @@
1
+ import { type DialogHTMLAttributes, type ReactNode } from 'react';
2
+ import { drawerMessages } from './drawer-messages.js';
3
+ declare module '../messages.js' {
4
+ interface DiyaMessages {
5
+ drawer: typeof drawerMessages;
6
+ }
7
+ }
8
+ export type DrawerProps = Omit<DialogHTMLAttributes<HTMLDialogElement>, 'title' | 'children' | 'open'> & {
9
+ /** Whether the drawer is shown; the caller owns this state. */
10
+ open: boolean;
11
+ /** Called with false when the person closes it (close button, Escape or the browser); update `open` in response. */
12
+ onOpenChange: (open: boolean) => void;
13
+ /** Visible heading; it is also the drawer's accessible name. */
14
+ title: string;
15
+ /** The title's heading level, 1 to 6; it looks the same at every level. Defaults to 3. */
16
+ headingLevel?: 1 | 2 | 3 | 4 | 5 | 6;
17
+ /** Supporting text under the title, also read as the drawer's description. */
18
+ description?: ReactNode;
19
+ /** Scrollable drawer body. */
20
+ children: ReactNode;
21
+ /** Content pinned to the bottom, usually action buttons. */
22
+ footer?: ReactNode;
23
+ /** Which edge of the page the drawer sits on; defaults to 'right'. */
24
+ side?: 'left' | 'right';
25
+ /** Shows the close icon button in the header; defaults to true. */
26
+ showClose?: boolean;
27
+ };
28
+ /** A modal panel at the side of the page that traps focus and returns it on close. The ref, `className`, `style` and other attributes go to the native `dialog` element. */
29
+ export declare const Drawer: import("react").ForwardRefExoticComponent<Omit<DialogHTMLAttributes<HTMLDialogElement>, "children" | "open" | "title"> & {
30
+ /** Whether the drawer is shown; the caller owns this state. */
31
+ open: boolean;
32
+ /** Called with false when the person closes it (close button, Escape or the browser); update `open` in response. */
33
+ onOpenChange: (open: boolean) => void;
34
+ /** Visible heading; it is also the drawer's accessible name. */
35
+ title: string;
36
+ /** The title's heading level, 1 to 6; it looks the same at every level. Defaults to 3. */
37
+ headingLevel?: 1 | 2 | 3 | 4 | 5 | 6;
38
+ /** Supporting text under the title, also read as the drawer's description. */
39
+ description?: ReactNode;
40
+ /** Scrollable drawer body. */
41
+ children: ReactNode;
42
+ /** Content pinned to the bottom, usually action buttons. */
43
+ footer?: ReactNode;
44
+ /** Which edge of the page the drawer sits on; defaults to 'right'. */
45
+ side?: "left" | "right";
46
+ /** Shows the close icon button in the header; defaults to true. */
47
+ showClose?: boolean;
48
+ } & import("react").RefAttributes<HTMLDialogElement>>;
@@ -0,0 +1,51 @@
1
+ 'use client';
2
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
3
+ import { forwardRef, useEffect, useId, useMemo, useRef } from 'react';
4
+ import { Button } from '../button/Button.js';
5
+ import { Icon } from '../Icon.js';
6
+ import { useDiyaMessages } from '../DiyaProvider.js';
7
+ import { cx } from '../internal/cx.js';
8
+ import { composeRefs } from '../internal/slot.js';
9
+ import { drawerMessages } from './drawer-messages.js';
10
+ /** A modal panel at the side of the page that traps focus and returns it on close. The ref, `className`, `style` and other attributes go to the native `dialog` element. */
11
+ export const Drawer = forwardRef(function Drawer({ open, onOpenChange, title, headingLevel = 3, description, children, footer, side = 'right', showClose = true, className, onCancel, onClose, onClick, ...props }, ref) {
12
+ const t = useDiyaMessages('drawer', drawerMessages);
13
+ const dialog = useRef(null);
14
+ const setDialog = useMemo(() => composeRefs(dialog, ref), [ref]);
15
+ const Heading = `h${headingLevel}`;
16
+ const returnFocus = useRef(null);
17
+ const id = useId();
18
+ function restoreFocus() {
19
+ if (returnFocus.current?.isConnected)
20
+ returnFocus.current.focus();
21
+ returnFocus.current = null;
22
+ }
23
+ useEffect(() => {
24
+ const node = dialog.current;
25
+ if (!node)
26
+ return;
27
+ if (open && !node.open) {
28
+ returnFocus.current = document.activeElement instanceof HTMLElement ? document.activeElement : null;
29
+ node.showModal();
30
+ }
31
+ else if (!open && node.open)
32
+ node.close();
33
+ }, [open]);
34
+ useEffect(() => {
35
+ const node = dialog.current;
36
+ return () => { if (node?.open)
37
+ node.close(); restoreFocus(); };
38
+ }, []);
39
+ return _jsxs("dialog", { ...props, ref: setDialog, className: cx('diya-workflow-surface diya-drawer-modal', className), "data-side": side, "aria-labelledby": `${id}-title`, "aria-describedby": description ? `${id}-description` : undefined, onCancel: event => { onCancel?.(event); if (event.defaultPrevented)
40
+ return; event.preventDefault(); onOpenChange(false); }, onClose: event => { onClose?.(event); if (event.currentTarget.open)
41
+ return; restoreFocus(); if (open)
42
+ onOpenChange(false); }, onClick: event => {
43
+ onClick?.(event);
44
+ // A click on the dimmed page (the dialog's backdrop) closes it, as it does a Dialog.
45
+ if (event.target !== event.currentTarget)
46
+ return;
47
+ const box = event.currentTarget.getBoundingClientRect();
48
+ if (event.clientX < box.left || event.clientX > box.right || event.clientY < box.top || event.clientY > box.bottom)
49
+ onOpenChange(false);
50
+ }, children: [_jsxs("header", { children: [_jsxs("div", { children: [_jsx(Heading, { id: `${id}-title`, className: "diya-drawer-title", children: title }), description && _jsx("p", { id: `${id}-description`, children: description })] }), showClose && _jsx(Button, { variant: "quiet", iconOnly: true, "aria-label": t.close, onClick: () => onOpenChange(false), children: _jsx(Icon, { name: "close" }) })] }), _jsx("div", { className: "diya-drawer-body", children: children }), footer && _jsx("footer", { children: footer })] });
51
+ });
@@ -0,0 +1,67 @@
1
+ import { type HTMLAttributes, type ReactNode } from 'react';
2
+ import { fileUploadMessages } from './file-upload-messages.js';
3
+ declare module '../messages.js' {
4
+ interface DiyaMessages {
5
+ fileUpload: typeof fileUploadMessages;
6
+ }
7
+ }
8
+ export type FileRejection = {
9
+ /** The file that was not added. */
10
+ file: File;
11
+ /** Why: 'type' fails `accept`, 'size' exceeds `maxBytes`. */
12
+ reason: 'type' | 'size';
13
+ };
14
+ /** Other attributes, `className`, `style` and the ref go on the outer section. */
15
+ export type FileUploadProps = Omit<HTMLAttributes<HTMLElement>, 'title' | 'children'> & {
16
+ /** The selected files; the caller owns this list. */
17
+ files: readonly File[];
18
+ /** Called with the full next list after files are added or removed. */
19
+ onFilesChange: (files: File[]) => void;
20
+ /** Accepted types, as in the native input: extensions and MIME types, e.g. ".pdf,image/*". Others are rejected. */
21
+ accept?: string;
22
+ /** Largest allowed file size in bytes; larger files are rejected. */
23
+ maxBytes?: number;
24
+ /** Keeps adding to the list instead of replacing the single file; defaults to false. */
25
+ multiple?: boolean;
26
+ /** Disables dropping, choosing and removing; defaults to false. */
27
+ disabled?: boolean;
28
+ /** Visible heading in the drop zone; it is also the region's accessible name. Defaults to "Add your files". */
29
+ title?: string;
30
+ /** Heading level of the title, 1–6; defaults to 3. */
31
+ headingLevel?: 1 | 2 | 3 | 4 | 5 | 6;
32
+ /** Text under the heading; defaults to "Drop files here or choose from your device." Pass null to hide it. */
33
+ description?: ReactNode;
34
+ /** Small text under the button, e.g. accepted types and size limit. */
35
+ helperText?: ReactNode;
36
+ /** Choose button text; defaults to "Choose files". */
37
+ chooseLabel?: string;
38
+ /** Called with the files that were not added and why; a short error also shows. */
39
+ onReject?: (rejections: FileRejection[]) => void;
40
+ };
41
+ /** A drop zone and file picker with a removable file list. Selects local files; upload, persistence and server validation belong to the caller. */
42
+ export declare const FileUpload: import("react").ForwardRefExoticComponent<Omit<HTMLAttributes<HTMLElement>, "children" | "title"> & {
43
+ /** The selected files; the caller owns this list. */
44
+ files: readonly File[];
45
+ /** Called with the full next list after files are added or removed. */
46
+ onFilesChange: (files: File[]) => void;
47
+ /** Accepted types, as in the native input: extensions and MIME types, e.g. ".pdf,image/*". Others are rejected. */
48
+ accept?: string;
49
+ /** Largest allowed file size in bytes; larger files are rejected. */
50
+ maxBytes?: number;
51
+ /** Keeps adding to the list instead of replacing the single file; defaults to false. */
52
+ multiple?: boolean;
53
+ /** Disables dropping, choosing and removing; defaults to false. */
54
+ disabled?: boolean;
55
+ /** Visible heading in the drop zone; it is also the region's accessible name. Defaults to "Add your files". */
56
+ title?: string;
57
+ /** Heading level of the title, 1–6; defaults to 3. */
58
+ headingLevel?: 1 | 2 | 3 | 4 | 5 | 6;
59
+ /** Text under the heading; defaults to "Drop files here or choose from your device." Pass null to hide it. */
60
+ description?: ReactNode;
61
+ /** Small text under the button, e.g. accepted types and size limit. */
62
+ helperText?: ReactNode;
63
+ /** Choose button text; defaults to "Choose files". */
64
+ chooseLabel?: string;
65
+ /** Called with the files that were not added and why; a short error also shows. */
66
+ onReject?: (rejections: FileRejection[]) => void;
67
+ } & import("react").RefAttributes<HTMLElement>>;