@milktop/react-ui 0.0.0-stage → 0.1.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 (250) hide show
  1. package/README.md +173 -2
  2. package/dist/accordion/Accordion.d.ts +65 -0
  3. package/dist/accordion/Accordion.js +61 -0
  4. package/dist/accordion/accordion.css +17 -0
  5. package/dist/action-bar/ActionBar.d.ts +33 -0
  6. package/dist/action-bar/ActionBar.js +48 -0
  7. package/dist/action-bar/action-bar.css +22 -0
  8. package/dist/alert/Alert.d.ts +28 -0
  9. package/dist/alert/Alert.js +12 -0
  10. package/dist/alert/alert.css +26 -0
  11. package/dist/app-shell/AppShell.d.ts +38 -0
  12. package/dist/app-shell/AppShell.js +92 -0
  13. package/dist/app-shell/Topbar.d.ts +20 -0
  14. package/dist/app-shell/Topbar.js +13 -0
  15. package/dist/app-shell/app-shell.css +41 -0
  16. package/dist/app-shell/shell.d.ts +9 -0
  17. package/dist/app-shell/shell.js +3 -0
  18. package/dist/attachments/Attachments.d.ts +84 -0
  19. package/dist/attachments/Attachments.js +235 -0
  20. package/dist/attachments/attachments.css +86 -0
  21. package/dist/avatar/Avatar.d.ts +31 -0
  22. package/dist/avatar/Avatar.js +41 -0
  23. package/dist/avatar/AvatarGroup.d.ts +41 -0
  24. package/dist/avatar/AvatarGroup.js +17 -0
  25. package/dist/avatar/avatar.css +73 -0
  26. package/dist/badge/Badge.d.ts +26 -0
  27. package/dist/badge/Badge.js +10 -0
  28. package/dist/badge/badge.css +24 -0
  29. package/dist/banner/Banner.d.ts +33 -0
  30. package/dist/banner/Banner.js +14 -0
  31. package/dist/banner/banner.css +25 -0
  32. package/dist/breadcrumbs/Breadcrumbs.d.ts +39 -0
  33. package/dist/breadcrumbs/Breadcrumbs.js +28 -0
  34. package/dist/breadcrumbs/breadcrumbs.css +18 -0
  35. package/dist/button/Button.d.ts +40 -0
  36. package/dist/button/Button.js +19 -0
  37. package/dist/button/button.css +63 -0
  38. package/dist/card/Card.d.ts +355 -0
  39. package/dist/card/Card.js +56 -0
  40. package/dist/card/card.css +43 -0
  41. package/dist/chart/AreaChart.d.ts +19 -0
  42. package/dist/chart/AreaChart.js +61 -0
  43. package/dist/chart/BarChart.d.ts +3 -0
  44. package/dist/chart/BarChart.js +60 -0
  45. package/dist/chart/ChartFrame.d.ts +25 -0
  46. package/dist/chart/ChartFrame.js +22 -0
  47. package/dist/chart/DonutChart.d.ts +20 -0
  48. package/dist/chart/DonutChart.js +41 -0
  49. package/dist/chart/Sparkline.d.ts +26 -0
  50. package/dist/chart/Sparkline.js +36 -0
  51. package/dist/chart/chart.css +51 -0
  52. package/dist/chart/chart.d.ts +23 -0
  53. package/dist/chart/chart.js +19 -0
  54. package/dist/checkbox/Checkbox.d.ts +50 -0
  55. package/dist/checkbox/Checkbox.js +19 -0
  56. package/dist/checkbox/CheckboxGroup.d.ts +44 -0
  57. package/dist/checkbox/CheckboxGroup.js +80 -0
  58. package/dist/checkbox/checkbox.css +49 -0
  59. package/dist/collapsible/Collapsible.d.ts +41 -0
  60. package/dist/collapsible/Collapsible.js +15 -0
  61. package/dist/collapsible/collapsible.css +20 -0
  62. package/dist/color-scheme.d.ts +18 -0
  63. package/dist/color-scheme.js +84 -0
  64. package/dist/command/Command.d.ts +64 -0
  65. package/dist/command/Command.js +162 -0
  66. package/dist/command/command.css +47 -0
  67. package/dist/confirm/confirm.css +5 -0
  68. package/dist/confirm/confirm.d.ts +42 -0
  69. package/dist/confirm/confirm.js +115 -0
  70. package/dist/copy-button/CopyButton.d.ts +30 -0
  71. package/dist/copy-button/CopyButton.js +23 -0
  72. package/dist/copy-button/copy-button.css +12 -0
  73. package/dist/css.d.ts +2 -0
  74. package/dist/data-list/DataList.d.ts +55 -0
  75. package/dist/data-list/DataList.js +43 -0
  76. package/dist/data-list/data-list.css +19 -0
  77. package/dist/date-picker/DatePicker.d.ts +95 -0
  78. package/dist/date-picker/DatePicker.js +237 -0
  79. package/dist/date-picker/date-picker.css +53 -0
  80. package/dist/dialog/Dialog.d.ts +98 -0
  81. package/dist/dialog/Dialog.js +92 -0
  82. package/dist/dialog/dialog.css +31 -0
  83. package/dist/divider/Divider.d.ts +27 -0
  84. package/dist/divider/Divider.js +8 -0
  85. package/dist/divider/divider.css +12 -0
  86. package/dist/editable/Editable.d.ts +47 -0
  87. package/dist/editable/Editable.js +51 -0
  88. package/dist/editable/editable.css +25 -0
  89. package/dist/editor/Editor.d.ts +59 -0
  90. package/dist/editor/Editor.js +220 -0
  91. package/dist/editor/editor.css +42 -0
  92. package/dist/editor/image.d.ts +1 -0
  93. package/dist/editor/image.js +23 -0
  94. package/dist/empty-state/EmptyState.d.ts +22 -0
  95. package/dist/empty-state/EmptyState.js +9 -0
  96. package/dist/empty-state/empty-state.css +14 -0
  97. package/dist/event-calendar/EventCalendar.d.ts +213 -0
  98. package/dist/event-calendar/EventCalendar.js +686 -0
  99. package/dist/event-calendar/dates.d.ts +8 -0
  100. package/dist/event-calendar/dates.js +21 -0
  101. package/dist/event-calendar/event-calendar.css +157 -0
  102. package/dist/field/Field.d.ts +66 -0
  103. package/dist/field/Field.js +23 -0
  104. package/dist/field/field.css +26 -0
  105. package/dist/file-upload/FileUpload.d.ts +60 -0
  106. package/dist/file-upload/FileUpload.js +102 -0
  107. package/dist/file-upload/file-upload.css +32 -0
  108. package/dist/format/FormatNumber.d.ts +42 -0
  109. package/dist/format/FormatNumber.js +59 -0
  110. package/dist/format/LocaleProvider.d.ts +33 -0
  111. package/dist/format/LocaleProvider.js +48 -0
  112. package/dist/format/format.d.ts +37 -0
  113. package/dist/format/format.js +74 -0
  114. package/dist/format/messages.d.ts +211 -0
  115. package/dist/format/messages.js +202 -0
  116. package/dist/highlight/Highlight.d.ts +11 -0
  117. package/dist/highlight/Highlight.js +14 -0
  118. package/dist/highlight/highlight.css +17 -0
  119. package/dist/hover-card/HoverCard.d.ts +47 -0
  120. package/dist/hover-card/HoverCard.js +18 -0
  121. package/dist/hover-card/hover-card.css +19 -0
  122. package/dist/index.d.ts +84 -0
  123. package/dist/index.js +85 -0
  124. package/dist/input/Input.d.ts +41 -0
  125. package/dist/input/Input.js +48 -0
  126. package/dist/input/input.css +25 -0
  127. package/dist/kbd/Kbd.d.ts +19 -0
  128. package/dist/kbd/Kbd.js +20 -0
  129. package/dist/kbd/kbd.css +8 -0
  130. package/dist/menu/Menu.d.ts +223 -0
  131. package/dist/menu/Menu.js +275 -0
  132. package/dist/menu/menu.css +38 -0
  133. package/dist/native-select/NativeSelect.d.ts +36 -0
  134. package/dist/native-select/NativeSelect.js +51 -0
  135. package/dist/native-select/native-select.css +16 -0
  136. package/dist/number-input/NumberInput.d.ts +56 -0
  137. package/dist/number-input/NumberInput.js +67 -0
  138. package/dist/number-input/number-input.css +21 -0
  139. package/dist/page/Page.d.ts +30 -0
  140. package/dist/page/Page.js +9 -0
  141. package/dist/page/page.css +17 -0
  142. package/dist/pagination/Pagination.d.ts +31 -0
  143. package/dist/pagination/Pagination.js +58 -0
  144. package/dist/pagination/pagination.css +15 -0
  145. package/dist/password-input/PasswordInput.d.ts +47 -0
  146. package/dist/password-input/PasswordInput.js +35 -0
  147. package/dist/password-input/password-input.css +18 -0
  148. package/dist/pin-input/PinInput.d.ts +45 -0
  149. package/dist/pin-input/PinInput.js +22 -0
  150. package/dist/pin-input/pin-input.css +13 -0
  151. package/dist/popover/Popover.d.ts +65 -0
  152. package/dist/popover/Popover.js +26 -0
  153. package/dist/popover/popover.css +16 -0
  154. package/dist/progress/Progress.d.ts +56 -0
  155. package/dist/progress/Progress.js +28 -0
  156. package/dist/progress/progress.css +24 -0
  157. package/dist/prose/Prose.d.ts +23 -0
  158. package/dist/prose/Prose.js +10 -0
  159. package/dist/prose/prose.css +29 -0
  160. package/dist/radio-group/RadioGroup.d.ts +59 -0
  161. package/dist/radio-group/RadioGroup.js +37 -0
  162. package/dist/radio-group/radio-group.css +30 -0
  163. package/dist/rolling-number/RollingNumber.d.ts +27 -0
  164. package/dist/rolling-number/RollingNumber.js +18 -0
  165. package/dist/rolling-number/rolling-number.css +10 -0
  166. package/dist/segment-group/Segmented.d.ts +68 -0
  167. package/dist/segment-group/Segmented.js +79 -0
  168. package/dist/segment-group/segment-group.css +25 -0
  169. package/dist/select/ListSelect.d.ts +5 -0
  170. package/dist/select/ListSelect.js +82 -0
  171. package/dist/select/SearchSelect.d.ts +43 -0
  172. package/dist/select/SearchSelect.js +219 -0
  173. package/dist/select/Select.d.ts +147 -0
  174. package/dist/select/Select.js +32 -0
  175. package/dist/select/items.d.ts +17 -0
  176. package/dist/select/items.js +14 -0
  177. package/dist/select/search-select.css +45 -0
  178. package/dist/select/select.css +52 -0
  179. package/dist/sheet/Sheet.d.ts +88 -0
  180. package/dist/sheet/Sheet.js +66 -0
  181. package/dist/sheet/sheet.css +44 -0
  182. package/dist/sidebar/NavGroup.d.ts +29 -0
  183. package/dist/sidebar/NavGroup.js +78 -0
  184. package/dist/sidebar/NavItem.d.ts +33 -0
  185. package/dist/sidebar/NavItem.js +50 -0
  186. package/dist/sidebar/NavSection.d.ts +20 -0
  187. package/dist/sidebar/NavSection.js +44 -0
  188. package/dist/sidebar/Sidebar.d.ts +65 -0
  189. package/dist/sidebar/Sidebar.js +59 -0
  190. package/dist/sidebar/SidebarUser.d.ts +31 -0
  191. package/dist/sidebar/SidebarUser.js +14 -0
  192. package/dist/sidebar/context.d.ts +13 -0
  193. package/dist/sidebar/context.js +6 -0
  194. package/dist/sidebar/sidebar.css +109 -0
  195. package/dist/skeleton/Skeleton.d.ts +25 -0
  196. package/dist/skeleton/Skeleton.js +8 -0
  197. package/dist/skeleton/skeleton.css +13 -0
  198. package/dist/slider/Slider.d.ts +70 -0
  199. package/dist/slider/Slider.js +65 -0
  200. package/dist/slider/slider.css +17 -0
  201. package/dist/sortable-list/SortableList.d.ts +46 -0
  202. package/dist/sortable-list/SortableList.js +85 -0
  203. package/dist/sortable-list/sortable-list.css +25 -0
  204. package/dist/spinner/Spinner.d.ts +18 -0
  205. package/dist/spinner/Spinner.js +11 -0
  206. package/dist/spinner/spinner.css +9 -0
  207. package/dist/stat/Stat.d.ts +42 -0
  208. package/dist/stat/Stat.js +21 -0
  209. package/dist/stat/Stats.d.ts +23 -0
  210. package/dist/stat/Stats.js +8 -0
  211. package/dist/stat/stat.css +34 -0
  212. package/dist/swatch-picker/SwatchPicker.d.ts +45 -0
  213. package/dist/swatch-picker/SwatchPicker.js +54 -0
  214. package/dist/swatch-picker/swatch-picker.css +38 -0
  215. package/dist/switch/Switch.d.ts +39 -0
  216. package/dist/switch/Switch.js +17 -0
  217. package/dist/switch/switch.css +13 -0
  218. package/dist/table/Table.d.ts +90 -0
  219. package/dist/table/Table.js +145 -0
  220. package/dist/table/table.css +44 -0
  221. package/dist/tabs/Tabs.d.ts +87 -0
  222. package/dist/tabs/Tabs.js +90 -0
  223. package/dist/tabs/tabs.css +53 -0
  224. package/dist/tags-input/TagsInput.d.ts +69 -0
  225. package/dist/tags-input/TagsInput.js +95 -0
  226. package/dist/tags-input/tags-input.css +37 -0
  227. package/dist/textarea/Textarea.d.ts +24 -0
  228. package/dist/textarea/Textarea.js +51 -0
  229. package/dist/textarea/textarea.css +14 -0
  230. package/dist/theme-toggle/ThemeToggle.d.ts +49 -0
  231. package/dist/theme-toggle/ThemeToggle.js +71 -0
  232. package/dist/theme-toggle/theme-toggle.css +23 -0
  233. package/dist/theme.css +119 -0
  234. package/dist/time-input/TimeInput.d.ts +25 -0
  235. package/dist/time-input/TimeInput.js +20 -0
  236. package/dist/timeline/Timeline.d.ts +50 -0
  237. package/dist/timeline/Timeline.js +15 -0
  238. package/dist/timeline/timeline.css +29 -0
  239. package/dist/toast/Toaster.d.ts +36 -0
  240. package/dist/toast/Toaster.js +25 -0
  241. package/dist/toast/toast.css +33 -0
  242. package/dist/tooltip/Tooltip.d.ts +55 -0
  243. package/dist/tooltip/Tooltip.js +48 -0
  244. package/dist/tooltip/tooltip.css +18 -0
  245. package/dist/tree-view/TreeView.d.ts +86 -0
  246. package/dist/tree-view/TreeView.js +74 -0
  247. package/dist/tree-view/tree-view.css +29 -0
  248. package/dist/utils.d.ts +19 -0
  249. package/dist/utils.js +59 -0
  250. package/package.json +99 -4
@@ -0,0 +1,87 @@
1
+ /**
2
+ * Tabs: the Tab children are the panels, and the tab list is built from
3
+ * their labels.
4
+ *
5
+ * <Tabs value={tab} onValueChange={setTab}>
6
+ * <Tab value="lessons" label="Lessons" icon="lucide:calendar">…</Tab>
7
+ * <Tab value="invoices" label="Invoices" count={2} countColor="danger">…</Tab>
8
+ * </Tabs>
9
+ *
10
+ * Arrow keys move between tabs (selecting as they go), Home/End jump to the
11
+ * ends. With no value, the first enabled tab is selected.
12
+ *
13
+ * - `value`, `defaultValue`: the selected Tab's value; `onValueChange`: called with it
14
+ * - `variant`: 'line' (default, an underline) or 'pills'
15
+ * - `counts`: where the tabs' counts sit: 'inline' (default, after the label)
16
+ * or 'corner' (top right, like a button's)
17
+ * - `renderTrigger`: a tab's content, given the tab's props; its count still follows
18
+ *
19
+ * By hand, with Ark's parts (data-variant on the root picks the look):
20
+ *
21
+ * <Tabs.Root defaultValue="a" data-variant="pills">
22
+ * <Tabs.List>
23
+ * <Tabs.Trigger value="a">First <Tabs.Count>3</Tabs.Count></Tabs.Trigger>
24
+ * <Tabs.Trigger value="b">Second</Tabs.Trigger>
25
+ * <Tabs.Indicator />
26
+ * </Tabs.List>
27
+ * <Tabs.Content value="a">…</Tabs.Content>
28
+ * <Tabs.Content value="b">…</Tabs.Content>
29
+ * </Tabs.Root>
30
+ */
31
+ import { type ReactElement, type ReactNode } from 'react';
32
+ import { Tabs as Ark, type TabsValueChangeDetails } from '@ark-ui/react/tabs';
33
+ import 'iconify-icon';
34
+ import { type Classes } from '../utils.js';
35
+ import '../theme.css';
36
+ import './tabs.css';
37
+ type Value = string | number;
38
+ export type TabProps = Omit<Ark.ContentProps, 'value'> & {
39
+ value: Value;
40
+ label?: ReactNode;
41
+ icon?: string | null;
42
+ disabled?: boolean;
43
+ count?: number | null;
44
+ max?: number;
45
+ dot?: boolean;
46
+ pulse?: boolean;
47
+ countColor?: 'neutral' | 'accent' | 'danger' | 'success' | 'warning';
48
+ countLabel?: string;
49
+ };
50
+ /**
51
+ * One panel of a Tabs; `label` (and an optional `icon`) make its tab.
52
+ *
53
+ * - `value`: identifies it (the Tabs' value when selected)
54
+ * - `label`: its text; `icon`: an Iconify name before it; `disabled`
55
+ * - `count`: a small count after the label (hidden at 0 or null); past `max`
56
+ * it shows e.g. "99+"
57
+ * - `dot`: a dot there instead, for "something to look at"
58
+ * - `pulse`: a ring ripples out from the count or dot (not when the system
59
+ * asks for less motion)
60
+ * - `countColor`: 'neutral' (default, quiet), 'accent', 'danger', 'success'
61
+ * or 'warning', for the dot too
62
+ * - `countLabel`: what the count is, for assistive tech ("Invoices, 2 overdue")
63
+ */
64
+ export declare function Tab({ value, label: _label, icon: _icon, disabled: _disabled, count: _count, max: _max, dot: _dot, pulse: _pulse, countColor: _countColor, countLabel: _countLabel, className, ...rest }: TabProps): import("react").JSX.Element;
65
+ export type TabsProps = Omit<Ark.RootProps, 'value' | 'defaultValue' | 'onValueChange'> & {
66
+ value?: Value | null;
67
+ defaultValue?: Value | null;
68
+ onValueChange?: (value: Value | null, details: TabsValueChangeDetails) => void;
69
+ variant?: 'line' | 'pills';
70
+ counts?: 'inline' | 'corner';
71
+ renderTrigger?: (tab: TabProps) => ReactNode;
72
+ classes?: Classes<'root' | 'list' | 'trigger' | 'icon' | 'count' | 'indicator' | 'panels' | 'content'>;
73
+ };
74
+ declare function TabsShortcut({ value, defaultValue, onValueChange, variant, counts, renderTrigger, className, classes, children, ...rest }: TabsProps): import("react").JSX.Element;
75
+ export declare const Tabs: typeof TabsShortcut & {
76
+ Root: import("react").ForwardRefExoticComponent<Ark.RootProps & import("react").RefAttributes<HTMLDivElement>>;
77
+ List: import("react").ForwardRefExoticComponent<Ark.ListProps & import("react").RefAttributes<HTMLDivElement>>;
78
+ Trigger: import("react").ForwardRefExoticComponent<Ark.TriggerProps & import("react").RefAttributes<HTMLButtonElement>>;
79
+ Indicator: import("react").ForwardRefExoticComponent<Ark.IndicatorProps & import("react").RefAttributes<HTMLDivElement>>;
80
+ Content: import("react").ForwardRefExoticComponent<Ark.ContentProps & import("react").RefAttributes<HTMLDivElement>>;
81
+ Context: (props: Ark.ContextProps) => ReactNode;
82
+ Count: {
83
+ (props: import("react").DetailedHTMLProps<import("react").HTMLAttributes<HTMLSpanElement>, HTMLSpanElement>): ReactElement<any, string | import("react").JSXElementConstructor<any>>;
84
+ displayName: string;
85
+ };
86
+ };
87
+ export {};
@@ -0,0 +1,90 @@
1
+ import { jsx as _jsx, Fragment as _Fragment, jsxs as _jsxs } from "react/jsx-runtime";
2
+ /**
3
+ * Tabs: the Tab children are the panels, and the tab list is built from
4
+ * their labels.
5
+ *
6
+ * <Tabs value={tab} onValueChange={setTab}>
7
+ * <Tab value="lessons" label="Lessons" icon="lucide:calendar">…</Tab>
8
+ * <Tab value="invoices" label="Invoices" count={2} countColor="danger">…</Tab>
9
+ * </Tabs>
10
+ *
11
+ * Arrow keys move between tabs (selecting as they go), Home/End jump to the
12
+ * ends. With no value, the first enabled tab is selected.
13
+ *
14
+ * - `value`, `defaultValue`: the selected Tab's value; `onValueChange`: called with it
15
+ * - `variant`: 'line' (default, an underline) or 'pills'
16
+ * - `counts`: where the tabs' counts sit: 'inline' (default, after the label)
17
+ * or 'corner' (top right, like a button's)
18
+ * - `renderTrigger`: a tab's content, given the tab's props; its count still follows
19
+ *
20
+ * By hand, with Ark's parts (data-variant on the root picks the look):
21
+ *
22
+ * <Tabs.Root defaultValue="a" data-variant="pills">
23
+ * <Tabs.List>
24
+ * <Tabs.Trigger value="a">First <Tabs.Count>3</Tabs.Count></Tabs.Trigger>
25
+ * <Tabs.Trigger value="b">Second</Tabs.Trigger>
26
+ * <Tabs.Indicator />
27
+ * </Tabs.List>
28
+ * <Tabs.Content value="a">…</Tabs.Content>
29
+ * <Tabs.Content value="b">…</Tabs.Content>
30
+ * </Tabs.Root>
31
+ */
32
+ import { Children, isValidElement, useEffect } from 'react';
33
+ import { Tabs as Ark } from '@ark-ui/react/tabs';
34
+ import 'iconify-icon';
35
+ import { cx, partOf, partsOf, useControllable, withParts } from '../utils.js';
36
+ import '../theme.css';
37
+ import './tabs.css';
38
+ /**
39
+ * One panel of a Tabs; `label` (and an optional `icon`) make its tab.
40
+ *
41
+ * - `value`: identifies it (the Tabs' value when selected)
42
+ * - `label`: its text; `icon`: an Iconify name before it; `disabled`
43
+ * - `count`: a small count after the label (hidden at 0 or null); past `max`
44
+ * it shows e.g. "99+"
45
+ * - `dot`: a dot there instead, for "something to look at"
46
+ * - `pulse`: a ring ripples out from the count or dot (not when the system
47
+ * asks for less motion)
48
+ * - `countColor`: 'neutral' (default, quiet), 'accent', 'danger', 'success'
49
+ * or 'warning', for the dot too
50
+ * - `countLabel`: what the count is, for assistive tech ("Invoices, 2 overdue")
51
+ */
52
+ export function Tab({ value, label: _label, icon: _icon, disabled: _disabled, count: _count, max: _max, dot: _dot, pulse: _pulse, countColor: _countColor, countLabel: _countLabel, className, ...rest }) {
53
+ return _jsx(Ark.Content, { ...rest, value: String(value), "data-scope": "tabs", "data-part": "content", className: className });
54
+ }
55
+ function TabsShortcut({ value, defaultValue, onValueChange, variant = 'line', counts = 'inline', renderTrigger, className, classes, children, ...rest }) {
56
+ const part = partsOf('tabs', classes);
57
+ const tabs = Children.toArray(children).filter((child) => isValidElement(child) && child.type === Tab);
58
+ // With no value, the first enabled tab.
59
+ const first = tabs.find((tab) => !tab.props.disabled)?.props.value ?? null;
60
+ const [current, setCurrent] = useControllable(value, defaultValue ?? first, undefined);
61
+ const shown = current ?? first;
62
+ // A null value (controlled or not) selects the first enabled tab, and says so.
63
+ useEffect(() => {
64
+ if (current != null || first == null)
65
+ return;
66
+ setCurrent(first);
67
+ onValueChange?.(first, { value: String(first) });
68
+ }, [current, first]); // eslint-disable-line react-hooks/exhaustive-deps
69
+ // Zag's values are strings; map them back to the tabs' own.
70
+ function change(details) {
71
+ const next = tabs.find((tab) => String(tab.props.value) === details.value)?.props.value ?? null;
72
+ setCurrent(next);
73
+ onValueChange?.(next, details);
74
+ }
75
+ return (_jsxs(Ark.Root, { ...rest, ...part('root'), className: cx(className, classes?.root), "data-variant": variant, "data-counts": counts, value: shown == null ? null : String(shown), onValueChange: change, children: [_jsxs(Ark.List, { ...part('list'), children: [tabs.map(({ props: tab }) => {
76
+ const { count, max = 99, dot, pulse, countColor = 'neutral', countLabel = '' } = tab;
77
+ const badge = count && count > 0 ? (count > max ? `${max}+` : String(count)) : dot ? '' : null;
78
+ const spoken = count && count > 0 ? [count, countLabel].filter(Boolean).join(' ') : dot ? countLabel : '';
79
+ return (_jsxs(Ark.Trigger, { value: String(tab.value), disabled: !!tab.disabled, ...part('trigger'), children: [renderTrigger ? renderTrigger(tab) : _jsxs(_Fragment, { children: [tab.icon && _jsx("iconify-icon", { ...part('icon'), icon: tab.icon, "aria-hidden": "true" }), _jsx("span", { children: tab.label })] }), badge != null && _jsxs(_Fragment, { children: [_jsx("span", { ...part('count'), "aria-hidden": "true", "data-dot": badge === '' || undefined, "data-pulse": pulse || undefined, "data-color": countColor, children: badge }), spoken && _jsxs("span", { ...part('sr-only'), children: [", ", spoken] })] })] }, String(tab.value)));
80
+ }), _jsx(Ark.Indicator, { ...part('indicator') })] }), _jsx("div", { ...part('panels'), children: children })] }));
81
+ }
82
+ export const Tabs = withParts(TabsShortcut, {
83
+ Root: Ark.Root,
84
+ List: Ark.List,
85
+ Trigger: Ark.Trigger,
86
+ Indicator: Ark.Indicator,
87
+ Content: Ark.Content,
88
+ Context: Ark.Context,
89
+ Count: partOf('tabs', 'count', 'span'),
90
+ });
@@ -0,0 +1,53 @@
1
+ @layer ui {
2
+ [data-scope=tabs][data-part=root] { display: block; width: 100%; color: var(--ui-text); font-family: var(--ui-font); }
3
+ [data-scope=tabs][data-part=list] { position: relative; display: flex; gap: 0.25rem; overflow-x: auto;
4
+ [data-variant=line] > & { border-bottom: 1px solid var(--ui-border); }
5
+ /* Pills: a raised pill behind the selected tab, like Segmented. */
6
+ [data-variant=pills] > & { display: inline-flex; padding: 0.25rem; background: var(--ui-hover); border-radius: var(--ui-radius); } }
7
+ [data-scope=tabs][data-part=trigger] {
8
+ position: relative; z-index: 1; display: inline-flex; align-items: center; gap: 0.5rem; height: var(--ui-control-height); padding: 0 0.75rem;
9
+ border: none; background: transparent; color: var(--ui-muted); font: inherit; font-size: 14px; font-weight: 500; white-space: nowrap;
10
+ cursor: pointer; border-radius: var(--ui-radius);
11
+ &:hover, &[data-selected] { color: var(--ui-text); }
12
+ &:focus-visible { outline: 2px solid var(--ui-ring-soft); }
13
+ &[data-disabled] { opacity: 0.4; cursor: not-allowed; }
14
+ /* Taller than a control, so the underlined tabs have room to breathe. */
15
+ [data-variant=line] & { border-radius: 0; height: calc(var(--ui-control-height) + 0.5rem); padding: 0 0.875rem; }
16
+ [data-variant=pills] & { height: calc(var(--ui-control-height) - 8px); border-radius: calc(var(--ui-radius) - 2px); } }
17
+ [data-scope=tabs][data-part=icon] { display: block; font-size: 16px; }
18
+ /* The count after a label: quiet by default, coloured for status. */
19
+ [data-scope=tabs][data-part=count] { position: relative; display: inline-grid; place-items: center; min-width: 18px; height: 18px;
20
+ padding: 0 5px; box-sizing: border-box; border-radius: 9999px; isolation: isolate;
21
+ font-size: 11px; font-weight: 600; line-height: 1; font-variant-numeric: tabular-nums;
22
+ --count-bg: var(--ui-hover); --count-text: var(--ui-muted); background: var(--count-bg); color: var(--count-text);
23
+ &[data-color=accent] { --count-bg: var(--ui-accent); --count-text: var(--ui-accent-text); }
24
+ &[data-color=danger] { --count-bg: var(--ui-danger); --count-text: white; }
25
+ &[data-color=success] { --count-bg: var(--ui-success); --count-text: white; }
26
+ &[data-color=warning] { --count-bg: #f59e0b; --count-text: #451a03; }
27
+ &[data-dot] { min-width: 0; width: 8px; height: 8px; padding: 0; }
28
+ &[data-dot][data-color=neutral] { --count-bg: var(--ui-muted); }
29
+ [data-selected] > &[data-color=neutral]:not([data-dot]) { --count-bg: var(--ui-accent-soft); --count-text: var(--ui-accent-soft-text); }
30
+ /* A copy of its colour that grows and fades behind it. */
31
+ &[data-pulse]::after { content: ''; position: absolute; inset: 0; z-index: -1; border-radius: 9999px; background: inherit;
32
+ animation: ui-ping 1.6s cubic-bezier(0, 0, 0.2, 1) infinite; }
33
+ /* Corner counts: small, raised into the tab's top right (in room the
34
+ tab makes for them, below), ringed in the page colour like a button's. */
35
+ [data-counts=corner] & { position: absolute; top: 0.375rem; right: 0.25rem; min-width: 15px; height: 15px;
36
+ padding: 0 4px; font-size: 10px; box-shadow: 0 0 0 2px var(--ui-surface);
37
+ [data-variant=pills] & { top: 0.125rem; right: 0.125rem; }
38
+ &[data-dot] { min-width: 0; width: 7px; height: 7px; padding: 0; top: 0.625rem; right: 0.5rem;
39
+ [data-variant=pills] & { top: 0.375rem; right: 0.375rem; } } } }
40
+ /* A tab with a corner count makes room for it on the right. */
41
+ [data-counts=corner] [data-scope=tabs][data-part=trigger]:has(> [data-part=count]) { padding-right: 1.375rem;
42
+ &:has(> [data-dot]) { padding-right: 1.125rem; } }
43
+ [data-scope=tabs][data-part=sr-only] { position: absolute; width: 1px; height: 1px; overflow: hidden; clip: rect(0 0 0 0); white-space: nowrap; }
44
+ @keyframes ui-ping { from { scale: 1; opacity: 0.7; } 75%, to { scale: 2.2; opacity: 0; } }
45
+ @media (prefers-reduced-motion: reduce) { [data-scope=tabs][data-part=count]::after { animation: none; } }
46
+ [data-scope=tabs][data-part=indicator] { transition-property: var(--transition-property); transition-duration: 200ms;
47
+ [data-variant=line] & { position: absolute; bottom: -1px; left: var(--left); width: var(--width); height: 2px; background: var(--ui-accent); }
48
+ [data-variant=pills] & { position: absolute; z-index: 0; top: var(--top); left: var(--left); width: var(--width); height: var(--height);
49
+ background: var(--ui-surface); border-radius: calc(var(--ui-radius) - 2px); box-shadow: 0 1px 3px rgba(0, 0, 0, 0.12); } }
50
+ [data-scope=tabs][data-part=panels] { padding-top: 1rem; }
51
+ [data-scope=tabs][data-part=content] { display: block; outline: none;
52
+ &:focus-visible { outline: 2px solid var(--ui-ring-soft); outline-offset: 4px; border-radius: 4px; } }
53
+ }
@@ -0,0 +1,69 @@
1
+ /**
2
+ * A tags input: type and press Enter (or a comma) to add a tag; Backspace
3
+ * removes the last; double-click a tag to edit it.
4
+ *
5
+ * <TagsInput label="Topics" value={topics} onValueChange={setTopics} placeholder="Add a topic…" />
6
+ *
7
+ * - `value`, `defaultValue`: arrays of strings; `onValueChange`: called with the new array
8
+ * - `max`: the most tags allowed
9
+ * - `variant`: the tags' look: 'subtle', 'accent' or 'outline'
10
+ * - `editable`: false to stop editing tags in place
11
+ * - `delimiter`: typing it adds a tag too (',')
12
+ * - `validate`: function({ inputValue, value }) returning whether to add it; a
13
+ * rejected tag stays in the box to fix, and the field flashes red
14
+ * - `allowDuplicates`: let the same tag in twice
15
+ * - `addOnPaste`: split pasted text by the delimiter into tags (on by default)
16
+ * - `name`: for plain form posts (comma-separated)
17
+ *
18
+ * By hand, with Ark's parts:
19
+ *
20
+ * <TagsInput.Root>
21
+ * <TagsInput.Label>Topics</TagsInput.Label>
22
+ * <TagsInput.Control>
23
+ * <TagsInput.Context>{(api) => api.value.map((tag, index) => (
24
+ * <TagsInput.Item key={index} index={index} value={tag}>
25
+ * <TagsInput.ItemPreview><TagsInput.ItemText>{tag}</TagsInput.ItemText><TagsInput.ItemDeleteTrigger>×</TagsInput.ItemDeleteTrigger></TagsInput.ItemPreview>
26
+ * <TagsInput.ItemInput />
27
+ * </TagsInput.Item>
28
+ * ))}</TagsInput.Context>
29
+ * <TagsInput.Input />
30
+ * </TagsInput.Control>
31
+ * <TagsInput.HiddenInput />
32
+ * </TagsInput.Root>
33
+ */
34
+ import { type ReactNode } from 'react';
35
+ import { TagsInput as Ark, type TagsInputValueChangeDetails } from '@ark-ui/react/tags-input';
36
+ import 'iconify-icon';
37
+ import { type Classes } from '../utils.js';
38
+ import '../theme.css';
39
+ import './tags-input.css';
40
+ export type TagsInputProps = Omit<Ark.RootProps, 'value' | 'defaultValue' | 'onValueChange' | 'label'> & {
41
+ label?: ReactNode;
42
+ value?: string[];
43
+ defaultValue?: string[];
44
+ onValueChange?: (value: string[], details: TagsInputValueChangeDetails) => void;
45
+ placeholder?: string;
46
+ variant?: 'subtle' | 'accent' | 'outline';
47
+ classes?: Classes<'root' | 'label' | 'control' | 'tags' | 'item' | 'itemPreview' | 'itemText' | 'itemDeleteTrigger' | 'itemInput' | 'input' | 'clearTrigger'>;
48
+ };
49
+ declare function TagsInputShortcut({ label, value, defaultValue, onValueChange, placeholder: placeholderProp, variant, max, delimiter, validate, allowDuplicates, addOnPaste, editable, name, disabled, className, classes, ...rest }: TagsInputProps): import("react").JSX.Element;
50
+ export declare const TagsInput: typeof TagsInputShortcut & {
51
+ Root: import("react").ForwardRefExoticComponent<Ark.RootProps & import("react").RefAttributes<HTMLDivElement>>;
52
+ RootProvider: import("react").ForwardRefExoticComponent<Ark.RootProviderProps & import("react").RefAttributes<HTMLDivElement>>;
53
+ Label: import("react").ForwardRefExoticComponent<Ark.LabelProps & import("react").RefAttributes<HTMLLabelElement>>;
54
+ Control: import("react").ForwardRefExoticComponent<Ark.ControlProps & import("react").RefAttributes<HTMLDivElement>>;
55
+ Input: import("react").ForwardRefExoticComponent<Ark.InputProps & import("react").RefAttributes<HTMLInputElement>>;
56
+ Item: import("react").ForwardRefExoticComponent<Ark.ItemProps & import("react").RefAttributes<HTMLDivElement>>;
57
+ ItemPreview: import("react").ForwardRefExoticComponent<Ark.ItemPreviewProps & import("react").RefAttributes<HTMLDivElement>>;
58
+ ItemText: import("react").ForwardRefExoticComponent<Ark.ItemTextProps & import("react").RefAttributes<HTMLSpanElement>>;
59
+ ItemDeleteTrigger: import("react").ForwardRefExoticComponent<Ark.ItemDeleteTriggerProps & import("react").RefAttributes<HTMLButtonElement>>;
60
+ ItemInput: import("react").ForwardRefExoticComponent<Ark.ItemInputProps & import("react").RefAttributes<HTMLInputElement>>;
61
+ ClearTrigger: import("react").ForwardRefExoticComponent<Ark.ClearTriggerProps & import("react").RefAttributes<HTMLButtonElement>>;
62
+ HiddenInput: import("react").ForwardRefExoticComponent<Ark.HiddenInputProps & import("react").RefAttributes<HTMLInputElement>>;
63
+ Context: (props: Ark.ContextProps) => ReactNode;
64
+ Tags: {
65
+ (props: import("react").DetailedHTMLProps<import("react").HTMLAttributes<HTMLDivElement>, HTMLDivElement>): import("react").ReactElement<any, string | import("react").JSXElementConstructor<any>>;
66
+ displayName: string;
67
+ };
68
+ };
69
+ export {};
@@ -0,0 +1,95 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ /**
3
+ * A tags input: type and press Enter (or a comma) to add a tag; Backspace
4
+ * removes the last; double-click a tag to edit it.
5
+ *
6
+ * <TagsInput label="Topics" value={topics} onValueChange={setTopics} placeholder="Add a topic…" />
7
+ *
8
+ * - `value`, `defaultValue`: arrays of strings; `onValueChange`: called with the new array
9
+ * - `max`: the most tags allowed
10
+ * - `variant`: the tags' look: 'subtle', 'accent' or 'outline'
11
+ * - `editable`: false to stop editing tags in place
12
+ * - `delimiter`: typing it adds a tag too (',')
13
+ * - `validate`: function({ inputValue, value }) returning whether to add it; a
14
+ * rejected tag stays in the box to fix, and the field flashes red
15
+ * - `allowDuplicates`: let the same tag in twice
16
+ * - `addOnPaste`: split pasted text by the delimiter into tags (on by default)
17
+ * - `name`: for plain form posts (comma-separated)
18
+ *
19
+ * By hand, with Ark's parts:
20
+ *
21
+ * <TagsInput.Root>
22
+ * <TagsInput.Label>Topics</TagsInput.Label>
23
+ * <TagsInput.Control>
24
+ * <TagsInput.Context>{(api) => api.value.map((tag, index) => (
25
+ * <TagsInput.Item key={index} index={index} value={tag}>
26
+ * <TagsInput.ItemPreview><TagsInput.ItemText>{tag}</TagsInput.ItemText><TagsInput.ItemDeleteTrigger>×</TagsInput.ItemDeleteTrigger></TagsInput.ItemPreview>
27
+ * <TagsInput.ItemInput />
28
+ * </TagsInput.Item>
29
+ * ))}</TagsInput.Context>
30
+ * <TagsInput.Input />
31
+ * </TagsInput.Control>
32
+ * <TagsInput.HiddenInput />
33
+ * </TagsInput.Root>
34
+ */
35
+ import { useRef, useState } from 'react';
36
+ import { TagsInput as Ark, useTagsInput } from '@ark-ui/react/tags-input';
37
+ import { useFieldContext } from '@ark-ui/react/field';
38
+ import 'iconify-icon';
39
+ import { cx, partOf, partsOf, useControllable, withParts } from '../utils.js';
40
+ import { useMessages } from '../format/LocaleProvider.js';
41
+ import '../theme.css';
42
+ import './tags-input.css';
43
+ function TagsInputShortcut({ label = null, value, defaultValue, onValueChange, placeholder: placeholderProp, variant = 'subtle', max = Infinity, delimiter = ',', validate, allowDuplicates = false, addOnPaste = true, editable = true, name, disabled, className, classes, ...rest }) {
44
+ const t = useMessages();
45
+ const placeholder = placeholderProp ?? t.tagsInput.placeholder;
46
+ const field = useFieldContext();
47
+ const part = partsOf('tags-input', classes);
48
+ const [tags, setTags] = useControllable(value, defaultValue ?? [], undefined);
49
+ const input = useRef(null);
50
+ // What was typed, to put back when `validate` turns it down (Zag clears it).
51
+ const typed = useRef('');
52
+ const [rejected, setRejected] = useState(false);
53
+ const props = {
54
+ value: tags, max, delimiter, allowDuplicates, addOnPaste, editable, blurBehavior: 'add',
55
+ ...(validate && { validate }), ...(name && { name }), ...(disabled !== undefined && { disabled }),
56
+ ...(t.zag.tagsInput && { translations: t.zag.tagsInput }),
57
+ onInputValueChange: (details) => { if (details.inputValue)
58
+ typed.current = details.inputValue; },
59
+ onValueInvalid: () => {
60
+ const text = typed.current;
61
+ // Zag clears the box in the next frame; put the text back after that.
62
+ requestAnimationFrame(() => requestAnimationFrame(() => api.current.setInputValue(text)));
63
+ setRejected(true);
64
+ setTimeout(() => setRejected(false), 700);
65
+ },
66
+ onValueChange: (details) => {
67
+ // Zag clears its own copy of the typed text after adding a tag, but not
68
+ // always the box itself (after a delimiter), so clear that too.
69
+ if (details.value.length > tags.length && input.current)
70
+ input.current.value = '';
71
+ setTags(details.value);
72
+ onValueChange?.(details.value, details);
73
+ },
74
+ };
75
+ const tagsInput = useTagsInput(props);
76
+ const api = useRef(tagsInput);
77
+ api.current = tagsInput;
78
+ return (_jsxs(Ark.RootProvider, { value: tagsInput, ...rest, ...part('root'), className: cx(className, classes?.root), "data-variant": variant, children: [label && !field && _jsx(Ark.Label, { ...part('label'), children: label }), _jsxs(Ark.Control, { ...part('control'), "data-rejected": rejected || undefined, children: [_jsxs("div", { ...part('tags'), children: [tagsInput.value.map((tag, index) => (_jsxs(Ark.Item, { index: index, value: tag, ...part('item'), children: [_jsxs(Ark.ItemPreview, { ...part('item-preview'), children: [_jsx(Ark.ItemText, { ...part('item-text'), children: tag }), _jsx(Ark.ItemDeleteTrigger, { ...part('item-delete-trigger'), children: _jsx("iconify-icon", { icon: "lucide:x" }) })] }), _jsx(Ark.ItemInput, { ...part('item-input') })] }, `${index}:${tag}`))), _jsx(Ark.Input, { ref: input, ...part('input'), placeholder: tagsInput.value.length ? '' : placeholder })] }), _jsx(Ark.ClearTrigger, { ...part('clear-trigger'), children: _jsx("iconify-icon", { icon: "lucide:x" }) })] }), _jsx(Ark.HiddenInput, {})] }));
79
+ }
80
+ export const TagsInput = withParts(TagsInputShortcut, {
81
+ Root: Ark.Root,
82
+ RootProvider: Ark.RootProvider,
83
+ Label: Ark.Label,
84
+ Control: Ark.Control,
85
+ Input: Ark.Input,
86
+ Item: Ark.Item,
87
+ ItemPreview: Ark.ItemPreview,
88
+ ItemText: Ark.ItemText,
89
+ ItemDeleteTrigger: Ark.ItemDeleteTrigger,
90
+ ItemInput: Ark.ItemInput,
91
+ ClearTrigger: Ark.ClearTrigger,
92
+ HiddenInput: Ark.HiddenInput,
93
+ Context: Ark.Context,
94
+ Tags: partOf('tags-input', 'tags'),
95
+ });
@@ -0,0 +1,37 @@
1
+ /* Tag parts are matched by Zag's names (item-preview, item-delete-trigger…);
2
+ `classes` uses the short ones (tag, tagRemove…). */
3
+ @layer ui {
4
+ [data-scope=tags-input][data-part=root] { display: block; min-width: 0; color: var(--ui-text); font-family: var(--ui-font);
5
+ --tag-bg: var(--ui-hover); --tag-text: var(--ui-text); --tag-border: transparent;
6
+ &[data-variant=accent] { --tag-bg: var(--ui-accent-soft); --tag-text: var(--ui-accent-soft-text); }
7
+ &[data-variant=outline] { --tag-bg: var(--ui-surface); --tag-border: var(--ui-border); } }
8
+ [data-scope=tags-input][data-part=label] { display: block; font-size: 14px; font-weight: 500; margin-bottom: 0.375rem; }
9
+ [data-scope=tags-input][data-part=control] { display: flex; align-items: center; gap: 0.25rem; min-height: var(--ui-control-height);
10
+ padding: 0.25rem 0.25rem 0.25rem 0.375rem; box-sizing: border-box; background: var(--ui-surface);
11
+ border: 1px solid var(--ui-border); border-radius: var(--ui-radius); font-size: 14px; cursor: text;
12
+ &:focus-within { border-color: var(--ui-ring); outline: 2px solid var(--ui-ring-soft); }
13
+ &[data-invalid], &:has([aria-invalid=true]), &[data-rejected] { border-color: var(--ui-danger); }
14
+ &[data-rejected] { animation: ui-shake 300ms; }
15
+ &[data-disabled] { opacity: 0.5; } }
16
+ [data-scope=tags-input][data-part=tags] { display: flex; flex-wrap: wrap; align-items: center; flex: 1; min-width: 0; gap: 0.375rem; }
17
+ [data-scope=tags-input][data-part=item-preview] { display: flex; align-items: center; gap: 0.25rem; height: 1.625rem;
18
+ padding: 0 0.25rem 0 0.625rem; box-sizing: border-box; border-radius: calc(var(--ui-radius) - 2px);
19
+ background: var(--tag-bg); color: var(--tag-text); border: 1px solid var(--tag-border);
20
+ font-size: 13px; font-weight: 500; line-height: 1;
21
+ &[data-highlighted] { outline: 2px solid var(--ui-ring); } }
22
+ [data-scope=tags-input][data-part=item-text] { white-space: nowrap; overflow: hidden; text-overflow: ellipsis; max-width: 12rem; }
23
+ [data-scope=tags-input][data-part=item-delete-trigger] { display: grid; place-items: center; width: 1.125rem; height: 1.125rem; padding: 0;
24
+ border: none; background: transparent; border-radius: 4px; color: inherit; opacity: 0.6; cursor: pointer;
25
+ &:hover { opacity: 1; background: rgba(0, 0, 0, 0.08); } }
26
+ [data-scope=tags-input][data-part=item-input] { height: 1.625rem; width: 7rem; padding: 0 0.5rem; box-sizing: border-box;
27
+ border: 1px solid var(--ui-ring); border-radius: calc(var(--ui-radius) - 2px); background: var(--ui-surface);
28
+ color: inherit; font-family: inherit; font-size: 13px; outline: none; }
29
+ [data-scope=tags-input][data-part=input] { flex: 1; min-width: 4rem; height: 1.625rem; padding: 0 0.25rem; border: none;
30
+ background: transparent; outline: none; color: inherit; font: inherit;
31
+ &::placeholder { color: var(--ui-muted); } }
32
+ [data-scope=tags-input][data-part=clear-trigger] { display: grid; place-items: center; flex-shrink: 0; align-self: center;
33
+ width: 1.75rem; height: 1.75rem; border: none; background: transparent; border-radius: 4px; color: var(--ui-muted); cursor: pointer;
34
+ &:hover { background: var(--ui-hover); color: var(--ui-text); } }
35
+ @keyframes ui-shake { 25% { translate: -3px 0; } 75% { translate: 3px 0; } }
36
+ @media (prefers-reduced-motion: reduce) { [data-scope=tags-input][data-part=control][data-rejected] { animation: none; } }
37
+ }
@@ -0,0 +1,24 @@
1
+ /**
2
+ * A textarea that grows with its content, from `rows` lines up to `maxRows`
3
+ * (then it scrolls). Other attributes go on the <textarea>.
4
+ *
5
+ * <Textarea value={notes} onValueChange={setNotes} rows={3} maxRows={8} placeholder="Notes" />
6
+ *
7
+ * - `label`: a label above it (skipped inside a Field)
8
+ * - `value`, `defaultValue`; `onValueChange`: called with the new text
9
+ * - `rows`: the starting height in lines
10
+ * - `maxRows`: grow up to this many lines, then scroll
11
+ * - `autogrow`: false for a fixed height the user can resize
12
+ */
13
+ import { type ComponentProps, type ReactNode } from 'react';
14
+ import { type Classes } from '../utils.js';
15
+ import '../theme.css';
16
+ import './textarea.css';
17
+ export type TextareaProps = ComponentProps<'textarea'> & {
18
+ label?: ReactNode;
19
+ onValueChange?: (value: string) => void;
20
+ maxRows?: number | null;
21
+ autogrow?: boolean;
22
+ classes?: Classes<'root' | 'label' | 'control' | 'textarea'>;
23
+ };
24
+ export declare function Textarea({ label, rows, maxRows, autogrow, onValueChange, onChange, value, className, classes, ref, ...rest }: TextareaProps): import("react").JSX.Element;
@@ -0,0 +1,51 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ /**
3
+ * A textarea that grows with its content, from `rows` lines up to `maxRows`
4
+ * (then it scrolls). Other attributes go on the <textarea>.
5
+ *
6
+ * <Textarea value={notes} onValueChange={setNotes} rows={3} maxRows={8} placeholder="Notes" />
7
+ *
8
+ * - `label`: a label above it (skipped inside a Field)
9
+ * - `value`, `defaultValue`; `onValueChange`: called with the new text
10
+ * - `rows`: the starting height in lines
11
+ * - `maxRows`: grow up to this many lines, then scroll
12
+ * - `autogrow`: false for a fixed height the user can resize
13
+ */
14
+ import { useCallback, useId, useLayoutEffect, useRef } from 'react';
15
+ import { Field, useFieldContext } from '@ark-ui/react/field';
16
+ import { cx, partsOf } from '../utils.js';
17
+ import '../theme.css';
18
+ import './textarea.css';
19
+ export function Textarea({ label = null, rows = 3, maxRows = null, autogrow = true, onValueChange, onChange, value, className, classes, ref, ...rest }) {
20
+ const field = useFieldContext();
21
+ const part = partsOf('textarea', classes);
22
+ const ownId = useId();
23
+ const id = rest.id ?? `${ownId}-textarea`;
24
+ const el = useRef(null);
25
+ // Fits the height to the content, capped at maxRows.
26
+ const fit = useCallback(() => {
27
+ const area = el.current;
28
+ if (!autogrow || !area)
29
+ return;
30
+ const line = parseFloat(getComputedStyle(area).lineHeight);
31
+ const max = maxRows ? line * maxRows : Infinity;
32
+ area.style.height = 'auto';
33
+ area.style.height = `${Math.min(area.scrollHeight, max)}px`;
34
+ area.style.overflowY = area.scrollHeight > max ? 'auto' : 'hidden';
35
+ }, [autogrow, maxRows]);
36
+ useLayoutEffect(fit, [fit, value]);
37
+ function change(e) {
38
+ onChange?.(e);
39
+ onValueChange?.(e.target.value);
40
+ fit();
41
+ }
42
+ // Both refs: ours, for the height, and the caller's.
43
+ const refs = (node) => {
44
+ el.current = node;
45
+ if (typeof ref === 'function')
46
+ ref(node);
47
+ else if (ref)
48
+ ref.current = node;
49
+ };
50
+ return (_jsxs("div", { ...part('root'), className: cx(className, classes?.root), children: [label && !field && _jsx("label", { ...part('label'), htmlFor: id, children: label }), _jsx("div", { ...part('control'), children: _jsx(Field.Textarea, { ref: refs, rows: rows, id: field ? undefined : id, value: value, ...rest, ...part('textarea'), "data-autogrow": autogrow || undefined, onChange: change }) })] }));
51
+ }
@@ -0,0 +1,14 @@
1
+ @layer ui {
2
+ [data-scope=textarea][data-part=root] { display: block; min-width: 0; color: var(--ui-text); font-family: var(--ui-font); }
3
+ [data-scope=textarea][data-part=label] { display: block; font-size: 14px; font-weight: 500; margin-bottom: 0.375rem; }
4
+ [data-scope=textarea][data-part=control] { display: block; min-width: 0; padding: 0.5rem 0.75rem; box-sizing: border-box;
5
+ background: var(--ui-surface); border: 1px solid var(--ui-border); border-radius: var(--ui-radius);
6
+ color: var(--ui-text); font-family: var(--ui-font); font-size: 14px;
7
+ &:focus-within { border-color: var(--ui-ring); outline: 2px solid var(--ui-ring-soft); }
8
+ &:has([aria-invalid=true]) { border-color: var(--ui-danger); }
9
+ &:has(:disabled) { opacity: 0.5; } }
10
+ [data-scope=textarea][data-part=textarea] { display: block; width: 100%; margin: 0; padding: 0; border: none; background: transparent;
11
+ outline: none; color: inherit; font: inherit; line-height: 1.5; resize: vertical;
12
+ &::placeholder { color: var(--ui-muted); }
13
+ &[data-autogrow] { resize: none; overflow: hidden; } }
14
+ }
@@ -0,0 +1,49 @@
1
+ /**
2
+ * A light, dark and system switch, driving `colorScheme`, so every toggle on
3
+ * the page stays in step and the choice is saved.
4
+ *
5
+ * <ThemeToggle />
6
+ * <ThemeToggle variant="menu" />
7
+ * <ThemeToggle mobile="menu" />
8
+ * <ThemeToggle variant="toggle" />
9
+ *
10
+ * - `variant`: 'segmented' (default: icon-only buttons with tooltips), 'menu'
11
+ * (one button showing the current choice, opening a menu of all of them) or
12
+ * 'toggle' (one button flipping between light and dark)
13
+ * - `mobile`: the variant below `breakpoint` (e.g. 'menu' where space is tight)
14
+ * - `breakpoint`: the width in px `mobile` applies under (768)
15
+ * - `options`: which schemes to offer, in order (system, light, dark)
16
+ * - `system`: false leaves out 'system' whatever `options` says
17
+ * - `labels`: the tooltips and accessible names, `{ light, dark, system }`
18
+ * - `label`: names the group for assistive tech
19
+ * - `size`: 'sm', 'md' (default) or 'lg', for every variant
20
+ * - `placement`: where the menu variant opens ('bottom-end', or 'bottom' with `iconOnly`)
21
+ * - `keepOpen`: the menu variant stays open after a choice
22
+ * - `iconOnly`: the menu variant lists just the icons, the current one highlighted
23
+ * - `onChange`: called with 'light', 'dark' or 'system'
24
+ */
25
+ import { type ComponentProps } from 'react';
26
+ import { Menu } from '../menu/Menu.js';
27
+ import 'iconify-icon';
28
+ import { type Scheme } from '../color-scheme.js';
29
+ import { type Classes } from '../utils.js';
30
+ import '../theme.css';
31
+ import './theme-toggle.css';
32
+ type Variant = 'segmented' | 'menu' | 'toggle';
33
+ export type ThemeToggleProps = Omit<ComponentProps<'div'>, 'onChange'> & {
34
+ variant?: Variant;
35
+ mobile?: Variant | null;
36
+ breakpoint?: number;
37
+ options?: Scheme[];
38
+ system?: boolean;
39
+ labels?: Record<Scheme, string>;
40
+ label?: string;
41
+ size?: 'sm' | 'md' | 'lg';
42
+ placement?: NonNullable<Parameters<typeof Menu>[0]['placement']> | null;
43
+ keepOpen?: boolean;
44
+ iconOnly?: boolean;
45
+ onChange?: (scheme: Scheme) => void;
46
+ classes?: Classes<'root' | 'trigger' | 'triggerIcon'>;
47
+ };
48
+ export declare function ThemeToggle({ variant, mobile, breakpoint, options, system, labels: labelsProp, label: labelProp, placement, size, keepOpen, iconOnly, onChange, className, classes, ...rest }: ThemeToggleProps): import("react").JSX.Element;
49
+ export {};