@milktop/react-ui 0.0.0-stage → 0.1.2

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 +32 -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 +121 -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,52 @@
1
+ @layer ui {
2
+ [data-scope=select][data-part=root] { display: inline-block; position: relative; min-width: 12rem;
3
+ color: var(--ui-text); font-family: var(--ui-font);
4
+ --tag-bg: var(--ui-hover); --tag-text: var(--ui-text); --tag-border: transparent;
5
+ &[data-variant=accent] { --tag-bg: var(--ui-accent-soft); --tag-text: var(--ui-accent-soft-text); }
6
+ &[data-variant=outline] { --tag-bg: var(--ui-surface); --tag-border: var(--ui-border); } }
7
+ /* With tags: the picks as chips in the trigger, which grows to fit them. */
8
+ [data-scope=select][data-part=tags] { display: flex; flex-wrap: wrap; gap: 0.25rem; min-width: 0; padding: 0.25rem 0; }
9
+ [data-scope=select][data-part=trigger]:has([data-part=tags]) { height: auto; min-height: var(--ui-control-height); }
10
+ [data-scope=select][data-part=tag] { display: inline-flex; align-items: center; height: 1.5rem; padding: 0 0.5rem; box-sizing: border-box;
11
+ border-radius: calc(var(--ui-radius) - 2px); background: var(--tag-bg); color: var(--tag-text); border: 1px solid var(--tag-border);
12
+ font-size: 13px; font-weight: 500; white-space: nowrap; }
13
+ [data-scope=select][data-part=label] { display: block; font-size: 14px; font-weight: 500; margin-bottom: 0.375rem; }
14
+ [data-scope=select][data-part=control] { display: flex; align-items: center; position: relative; }
15
+ [data-scope=select][data-part=trigger] { display: flex; align-items: center; justify-content: space-between; gap: 0.5rem;
16
+ width: 100%; height: var(--ui-control-height); padding: 0 0.75rem; box-sizing: border-box;
17
+ background: var(--ui-surface); border: 1px solid var(--ui-border); border-radius: var(--ui-radius);
18
+ color: inherit; font-family: var(--ui-font); font-size: 14px; text-align: left; cursor: pointer;
19
+ &:focus-visible { border-color: var(--ui-ring); outline: 2px solid var(--ui-ring-soft); }
20
+ &[data-disabled] { opacity: 0.5; cursor: not-allowed; }
21
+ &[data-invalid], &[aria-invalid=true] { border-color: var(--ui-danger); }
22
+ [data-size=sm] & { height: var(--ui-control-height-sm); padding: 0 0.625rem; font-size: 13px; } }
23
+ [data-scope=select][data-part=clear-trigger] { position: absolute; right: 2rem; display: grid; place-items: center;
24
+ width: 1.5rem; height: 1.5rem; border: none; background: transparent; border-radius: 4px; color: var(--ui-muted); cursor: pointer;
25
+ &:hover { background: var(--ui-hover); color: var(--ui-text); } }
26
+ /* Room for the clear button, between the value and the chevron. */
27
+ [data-scope=select][data-part=control]:has([data-part=clear-trigger]:not([hidden])) [data-part=value-text] { margin-right: auto; padding-right: 1.75rem; }
28
+ [data-scope=select][data-part=value-text] { white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
29
+ &[data-placeholder-shown] { color: var(--ui-muted); } }
30
+ [data-scope=select][data-part=indicator] { display: inline-flex; color: var(--ui-muted); transition: rotate 150ms;
31
+ &[data-state=open] { rotate: 180deg; } }
32
+
33
+ /* Zag copies the content's z-index onto its positioner. */
34
+ [data-scope=select][data-part=content] { z-index: 50; margin: 0; padding: 0.25rem; list-style: none; box-sizing: border-box;
35
+ outline: none; max-height: min(300px, var(--available-height)); overflow-y: auto;
36
+ background: var(--ui-surface); border: 1px solid var(--ui-border);
37
+ border-radius: calc(var(--ui-radius) + 2px); box-shadow: var(--ui-shadow); }
38
+ [data-scope=select][data-part=empty] { padding: 0.5rem; font-size: 14px; color: var(--ui-muted); }
39
+ [data-scope=select][data-part=item] { display: flex; align-items: center; justify-content: space-between; gap: 0.5rem;
40
+ padding: 0.375rem 0.5rem; border-radius: 4px; font-size: 14px; cursor: pointer;
41
+ &[data-highlighted] { background: var(--ui-hover); }
42
+ &[data-state=checked] { font-weight: 500; }
43
+ &[data-disabled] { opacity: 0.4; cursor: not-allowed; } }
44
+ [data-scope=select][data-part=item-indicator] { display: none; color: var(--ui-accent);
45
+ &[data-state=checked] { display: inline-flex; } }
46
+ /* `start`: before the value (a colour's dot, an icon). */
47
+ [data-scope=select][data-part=start] { display: inline-flex; align-items: center; flex-shrink: 0; color: var(--ui-muted);
48
+ [data-part=control] > & { margin-left: 0.625rem; } }
49
+ [data-scope=select][data-part=value-text]:not([data-placeholder]) { display: inline-flex; align-items: center; gap: 0.375rem; }
50
+ /* After a `start`, the value fills the space up to the chevron. */
51
+ [data-scope=select][data-part=start] + [data-part=value-text] { flex-grow: 1; justify-content: flex-start; text-align: left; }
52
+ }
@@ -0,0 +1,88 @@
1
+ /**
2
+ * A panel that slides in from an edge of the screen, for viewing or editing
3
+ * something without leaving the page.
4
+ *
5
+ * <Sheet heading="Edit student" open={editing} onOpenChange={setEditing}
6
+ * footer={<Button variant="primary">Save</Button>}>
7
+ * …body…
8
+ * </Sheet>
9
+ *
10
+ * A modal dialog underneath: focus is trapped, the page behind doesn't
11
+ * scroll, Escape or the backdrop closes it, and focus returns to the trigger.
12
+ *
13
+ * - `side`: 'right' (default), 'left', 'top' or 'bottom'
14
+ * - `size`: 'sm', 'md' (default) or 'lg': the width (or height for top and bottom)
15
+ * - `open`, `defaultOpen`, `onOpenChange`, `trigger`, `heading`, `description`,
16
+ * `footer`, `closable`, `alert`: as for Dialog
17
+ *
18
+ * Or composed from Sheet.Header (heading, description), Sheet.Body and
19
+ * Sheet.Footer, with a Sheet.Trigger child, as Dialog is. By hand, Dialog's
20
+ * Ark parts work too: give them data-scope="sheet" (Sheet.Content and the
21
+ * rest here do that for you).
22
+ */
23
+ import { type ComponentProps, type ReactElement, type ReactNode } from 'react';
24
+ import { Dialog as Ark, type DialogOpenChangeDetails } from '@ark-ui/react/dialog';
25
+ import 'iconify-icon';
26
+ import { type Classes } from '../utils.js';
27
+ import '../theme.css';
28
+ import './sheet.css';
29
+ export type SheetProps = Omit<Ark.ContentProps, 'children'> & {
30
+ open?: boolean;
31
+ defaultOpen?: boolean;
32
+ onOpenChange?: (open: boolean, details: DialogOpenChangeDetails) => void;
33
+ side?: 'right' | 'left' | 'top' | 'bottom';
34
+ size?: 'sm' | 'md' | 'lg';
35
+ heading?: ReactNode;
36
+ description?: ReactNode;
37
+ alert?: boolean;
38
+ closable?: boolean;
39
+ trigger?: ReactElement | null;
40
+ footer?: ReactNode;
41
+ classes?: Classes<'backdrop' | 'positioner' | 'content' | 'closeTrigger' | 'header' | 'title' | 'description' | 'body' | 'footer'>;
42
+ children?: ReactNode;
43
+ };
44
+ type HeaderProps = ComponentProps<'div'> & {
45
+ heading?: ReactNode;
46
+ description?: ReactNode;
47
+ };
48
+ declare function Header({ heading, description, children, ...rest }: HeaderProps): import("react").JSX.Element;
49
+ declare function SheetShortcut({ open, defaultOpen, onOpenChange, side, size, heading, description, alert, closable, trigger, footer, className, classes, children, ...rest }: SheetProps): import("react").JSX.Element;
50
+ export declare const Sheet: typeof SheetShortcut & {
51
+ Header: typeof Header;
52
+ Body: {
53
+ (props: import("react").DetailedHTMLProps<import("react").HTMLAttributes<HTMLDivElement>, HTMLDivElement>): ReactElement<any, string | import("react").JSXElementConstructor<any>>;
54
+ displayName: string;
55
+ };
56
+ Footer: {
57
+ (props: import("react").DetailedHTMLProps<import("react").HTMLAttributes<HTMLDivElement>, HTMLDivElement>): ReactElement<any, string | import("react").JSXElementConstructor<any>>;
58
+ displayName: string;
59
+ };
60
+ Root: (props: Ark.RootProps) => import("react").JSX.Element;
61
+ Trigger: import("react").ForwardRefExoticComponent<Ark.TriggerProps & import("react").RefAttributes<HTMLButtonElement>>;
62
+ Backdrop: {
63
+ (props: Ark.BackdropProps & import("react").RefAttributes<HTMLDivElement>): import("react").JSX.Element;
64
+ displayName: string;
65
+ };
66
+ Positioner: {
67
+ (props: Ark.PositionerProps & import("react").RefAttributes<HTMLDivElement>): import("react").JSX.Element;
68
+ displayName: string;
69
+ };
70
+ Content: {
71
+ (props: Ark.ContentProps & import("react").RefAttributes<HTMLDivElement>): import("react").JSX.Element;
72
+ displayName: string;
73
+ };
74
+ CloseTrigger: {
75
+ (props: Ark.CloseTriggerProps & import("react").RefAttributes<HTMLButtonElement>): import("react").JSX.Element;
76
+ displayName: string;
77
+ };
78
+ Title: {
79
+ (props: Ark.TitleProps & import("react").RefAttributes<HTMLHeadingElement>): import("react").JSX.Element;
80
+ displayName: string;
81
+ };
82
+ Description: {
83
+ (props: Ark.DescriptionProps & import("react").RefAttributes<HTMLDivElement>): import("react").JSX.Element;
84
+ displayName: string;
85
+ };
86
+ Context: (props: Ark.ContextProps) => ReactNode;
87
+ };
88
+ export {};
@@ -0,0 +1,66 @@
1
+ import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
2
+ /**
3
+ * A panel that slides in from an edge of the screen, for viewing or editing
4
+ * something without leaving the page.
5
+ *
6
+ * <Sheet heading="Edit student" open={editing} onOpenChange={setEditing}
7
+ * footer={<Button variant="primary">Save</Button>}>
8
+ * …body…
9
+ * </Sheet>
10
+ *
11
+ * A modal dialog underneath: focus is trapped, the page behind doesn't
12
+ * scroll, Escape or the backdrop closes it, and focus returns to the trigger.
13
+ *
14
+ * - `side`: 'right' (default), 'left', 'top' or 'bottom'
15
+ * - `size`: 'sm', 'md' (default) or 'lg': the width (or height for top and bottom)
16
+ * - `open`, `defaultOpen`, `onOpenChange`, `trigger`, `heading`, `description`,
17
+ * `footer`, `closable`, `alert`: as for Dialog
18
+ *
19
+ * Or composed from Sheet.Header (heading, description), Sheet.Body and
20
+ * Sheet.Footer, with a Sheet.Trigger child, as Dialog is. By hand, Dialog's
21
+ * Ark parts work too: give them data-scope="sheet" (Sheet.Content and the
22
+ * rest here do that for you).
23
+ */
24
+ import { Children, isValidElement } from 'react';
25
+ import { Dialog as Ark } from '@ark-ui/react/dialog';
26
+ import { Portal } from '@ark-ui/react/portal';
27
+ import 'iconify-icon';
28
+ import { cx, on, partOf, partsOf, withParts } from '../utils.js';
29
+ import { useMessages } from '../format/LocaleProvider.js';
30
+ import '../theme.css';
31
+ import './sheet.css';
32
+ // Ark's dialog stamps data-scope="dialog"; ours wins, so Dialog's CSS stays off.
33
+ const scoped = (name) => ({ 'data-scope': 'sheet', 'data-part': name });
34
+ function Header({ heading = null, description = null, children, ...rest }) {
35
+ return (_jsxs("div", { ...scoped('header'), ...rest, children: [heading && _jsx(Ark.Title, { ...scoped('title'), children: heading }), description && _jsx(Ark.Description, { ...scoped('description'), children: description }), children] }));
36
+ }
37
+ const Body = partOf('sheet', 'body');
38
+ const Footer = partOf('sheet', 'footer');
39
+ const layout = [Header, Body, Footer];
40
+ // Ark's parts under the sheet scope, for building one by hand.
41
+ const withScope = (Part, name) => {
42
+ const Scoped = (props) => _jsx(Part, { ...props, ...scoped(name) });
43
+ Scoped.displayName = `Sheet.${name}`;
44
+ return Scoped;
45
+ };
46
+ function SheetShortcut({ open, defaultOpen, onOpenChange, side = 'right', size = 'md', heading = null, description = null, alert = false, closable = true, trigger = null, footer = null, className, classes, children, ...rest }) {
47
+ const t = useMessages();
48
+ const part = partsOf('sheet', classes);
49
+ const nodes = Children.toArray(children);
50
+ const composed = nodes.some((node) => isValidElement(node) && layout.includes(node.type));
51
+ const triggers = nodes.filter((node) => isValidElement(node) && node.type === Ark.Trigger);
52
+ const content = composed ? nodes.filter((node) => !triggers.includes(node)) : children;
53
+ return (_jsxs(Ark.Root, { ...(open !== undefined && { open }), ...(defaultOpen !== undefined && { defaultOpen }), ...on('onOpenChange', onOpenChange, (details) => details.open), role: alert ? 'alertdialog' : 'dialog', closeOnInteractOutside: !alert, lazyMount: true, unmountOnExit: true, children: [trigger && _jsx(Ark.Trigger, { asChild: true, children: trigger }), composed && triggers, _jsxs(Portal, { children: [_jsx(Ark.Backdrop, { ...part('backdrop') }), _jsx(Ark.Positioner, { ...part('positioner'), "data-side": side, children: _jsxs(Ark.Content, { ...rest, ...part('content'), className: cx(className, classes?.content), "data-side": side, "data-size": size, children: [closable && _jsx(Ark.CloseTrigger, { ...part('close-trigger'), "aria-label": t.common.close, children: _jsx("iconify-icon", { icon: "lucide:x" }) }), composed ? content : _jsxs(_Fragment, { children: [(heading || description) && (_jsxs("div", { ...part('header'), children: [heading && _jsx(Ark.Title, { ...part('title'), children: heading }), description && _jsx(Ark.Description, { ...part('description'), children: description })] })), _jsx("div", { ...part('body'), children: children }), footer && _jsx("div", { ...part('footer'), children: footer })] })] }) })] })] }));
54
+ }
55
+ export const Sheet = withParts(SheetShortcut, {
56
+ Header, Body, Footer,
57
+ Root: Ark.Root,
58
+ Trigger: Ark.Trigger,
59
+ Backdrop: withScope(Ark.Backdrop, 'backdrop'),
60
+ Positioner: withScope(Ark.Positioner, 'positioner'),
61
+ Content: withScope(Ark.Content, 'content'),
62
+ CloseTrigger: withScope(Ark.CloseTrigger, 'close-trigger'),
63
+ Title: withScope(Ark.Title, 'title'),
64
+ Description: withScope(Ark.Description, 'description'),
65
+ Context: Ark.Context,
66
+ });
@@ -0,0 +1,44 @@
1
+ @layer ui {
2
+ [data-scope=sheet][data-part=backdrop] { position: fixed; inset: 0; z-index: 100; background: rgba(0, 0, 0, 0.45);
3
+ &[data-state=open] { animation: ui-sheet-fade-in 200ms ease-out; }
4
+ &[data-state=closed] { animation: ui-sheet-fade-out 160ms ease-in forwards; } }
5
+ /* Covers the screen and lines the panel up against one edge; clicks pass through to the backdrop. */
6
+ [data-scope=sheet][data-part=positioner] { position: fixed; inset: 0; z-index: 100; display: flex; pointer-events: none;
7
+ &[data-side=right] { justify-content: flex-end; }
8
+ &[data-side=top] { align-items: flex-start; }
9
+ &[data-side=bottom] { align-items: flex-end; } }
10
+ [data-scope=sheet][data-part=content] {
11
+ position: relative; display: flex; flex-direction: column; box-sizing: border-box; outline: none; pointer-events: auto;
12
+ background: var(--ui-surface); color: var(--ui-text); font-family: var(--ui-font); font-size: 14px; box-shadow: var(--ui-shadow);
13
+ &[data-state=open] { animation: ui-sheet-in 240ms cubic-bezier(0.32, 0.72, 0, 1); }
14
+ &[data-state=closed] { animation: ui-sheet-out 180ms ease-in forwards; }
15
+ /* Left and right: full height, a width by size. */
16
+ &:is([data-side=right], [data-side=left]) { height: 100dvh; width: 28rem; max-width: calc(100vw - 2.5rem);
17
+ &[data-size=sm] { width: 20rem; }
18
+ &[data-size=lg] { width: 40rem; } }
19
+ &[data-side=right] { border-left: 1px solid var(--ui-border); --sheet-from: translateX(100%); }
20
+ &[data-side=left] { border-right: 1px solid var(--ui-border); --sheet-from: translateX(-100%); }
21
+ /* Top and bottom: full width, a height that fits its content up to a limit. */
22
+ &:is([data-side=top], [data-side=bottom]) { width: 100%; max-height: 70dvh;
23
+ &[data-size=sm] { max-height: 40dvh; }
24
+ &[data-size=lg] { max-height: 90dvh; } }
25
+ &[data-side=top] { border-bottom: 1px solid var(--ui-border); border-radius: 0 0 calc(var(--ui-radius) + 6px) calc(var(--ui-radius) + 6px); --sheet-from: translateY(-100%); }
26
+ &[data-side=bottom] { border-top: 1px solid var(--ui-border); border-radius: calc(var(--ui-radius) + 6px) calc(var(--ui-radius) + 6px) 0 0; --sheet-from: translateY(100%); }
27
+ }
28
+ [data-scope=sheet][data-part=header] { padding: 1.5rem 3rem 0 1.5rem; flex-shrink: 0; }
29
+ [data-scope=sheet][data-part=title] { margin: 0; font-size: 18px; font-weight: 600; }
30
+ [data-scope=sheet][data-part=description] { margin: 0.375rem 0 0; color: var(--ui-muted); }
31
+ /* The body scrolls; the header and footer stay put. */
32
+ [data-scope=sheet][data-part=body] { flex-grow: 1; min-height: 0; padding: 1rem 1.5rem; overflow-y: auto;
33
+ [data-part=close-trigger] + & { padding: 1.5rem 3.5rem 1rem 1.5rem; } }
34
+ [data-scope=sheet][data-part=footer] { display: flex; justify-content: flex-end; gap: 0.5rem; flex-wrap: wrap; padding: 1rem 1.5rem; flex-shrink: 0;
35
+ border-top: 1px solid var(--ui-border); }
36
+ [data-scope=sheet][data-part=close-trigger] { position: absolute; top: 1rem; right: 1rem; display: grid; place-items: center; width: 2rem; height: 2rem;
37
+ border: none; background: transparent; border-radius: 6px; color: var(--ui-muted); cursor: pointer; font-size: 16px;
38
+ &:hover { background: var(--ui-hover); color: var(--ui-text); }
39
+ &:focus-visible { outline: 2px solid var(--ui-ring-soft); } }
40
+ @keyframes ui-sheet-fade-in { from { opacity: 0; } }
41
+ @keyframes ui-sheet-fade-out { to { opacity: 0; } }
42
+ @keyframes ui-sheet-in { from { transform: var(--sheet-from); } }
43
+ @keyframes ui-sheet-out { to { transform: var(--sheet-from); } }
44
+ }
@@ -0,0 +1,29 @@
1
+ /**
2
+ * Nav items under a label that opens and closes.
3
+ *
4
+ * <NavGroup label="Forms" icon="lucide:form" defaultOpen>
5
+ * <NavItem href="/input">Input</NavItem>
6
+ * </NavGroup>
7
+ *
8
+ * - `label`: its text; `icon`: an Iconify name
9
+ * - `open`, `defaultOpen`; `onOpenChange`: called with the new open state.
10
+ * In an `accordion` Sidebar, opening one closes the others.
11
+ *
12
+ * In the rail only its icon shows, and its items open beside it as a flyout:
13
+ * on hover, or on click for keyboard and touch. Escape, leaving it or
14
+ * following a link closes it.
15
+ */
16
+ import { type HTMLAttributes } from 'react';
17
+ import 'iconify-icon';
18
+ import { type Classes } from '../utils.js';
19
+ import '../theme.css';
20
+ import './sidebar.css';
21
+ export type NavGroupProps = HTMLAttributes<HTMLDivElement> & {
22
+ label: string;
23
+ icon?: string | null;
24
+ open?: boolean;
25
+ defaultOpen?: boolean;
26
+ onOpenChange?: (open: boolean) => void;
27
+ classes?: Classes<'root' | 'trigger' | 'icon' | 'text' | 'indicator' | 'children' | 'panel' | 'flyoutHeading'>;
28
+ };
29
+ export declare function NavGroup({ label, icon, open: openProp, defaultOpen, onOpenChange, className, classes, children, ...rest }: NavGroupProps): import("react").JSX.Element;
@@ -0,0 +1,78 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ /**
3
+ * Nav items under a label that opens and closes.
4
+ *
5
+ * <NavGroup label="Forms" icon="lucide:form" defaultOpen>
6
+ * <NavItem href="/input">Input</NavItem>
7
+ * </NavGroup>
8
+ *
9
+ * - `label`: its text; `icon`: an Iconify name
10
+ * - `open`, `defaultOpen`; `onOpenChange`: called with the new open state.
11
+ * In an `accordion` Sidebar, opening one closes the others.
12
+ *
13
+ * In the rail only its icon shows, and its items open beside it as a flyout:
14
+ * on hover, or on click for keyboard and touch. Escape, leaving it or
15
+ * following a link closes it.
16
+ */
17
+ import { useContext, useEffect, useId, useRef, useState } from 'react';
18
+ import 'iconify-icon';
19
+ import { useShell } from '../app-shell/shell.js';
20
+ import { cx, partsOf, useControllable } from '../utils.js';
21
+ import { FlyoutContext, SectionContext, useSidebar } from './context.js';
22
+ import '../theme.css';
23
+ import './sidebar.css';
24
+ export function NavGroup({ label, icon = null, open: openProp, defaultOpen = false, onOpenChange, className, classes, children, ...rest }) {
25
+ const shell = useShell();
26
+ const sidebar = useSidebar();
27
+ const section = useContext(SectionContext);
28
+ const part = partsOf('nav-group', classes);
29
+ const id = useId();
30
+ const rail = !!shell?.rail;
31
+ const [own, setOwn] = useControllable(openProp, defaultOpen, onOpenChange);
32
+ // In an accordion, open only while it's the one open group (or none has
33
+ // been picked yet, as at the start).
34
+ const open = own && !(sidebar?.accordion && sidebar.openGroup !== null && sidebar.openGroup !== id);
35
+ const setOpen = (next) => {
36
+ setOwn(next);
37
+ if (next && sidebar?.accordion)
38
+ sidebar.setOpenGroup(id);
39
+ };
40
+ // The latest setter, for the section's "collapse all".
41
+ const latest = useRef(setOpen);
42
+ useEffect(() => { latest.current = setOpen; });
43
+ useEffect(() => section?.register(id, (next) => latest.current(next)), [section, id]);
44
+ useEffect(() => section?.report(id, open), [section, id, open]);
45
+ const [flyout, setFlyout] = useState(false);
46
+ const [position, setPosition] = useState({ top: 0, left: 0 });
47
+ const root = useRef(null);
48
+ const toggleEl = useRef(null);
49
+ const showing = rail && flyout;
50
+ function showFlyout() {
51
+ if (!rail || !toggleEl.current)
52
+ return;
53
+ const box = toggleEl.current.getBoundingClientRect();
54
+ setPosition({ top: box.top, left: box.right });
55
+ setFlyout(true);
56
+ }
57
+ function toggle() {
58
+ if (!rail)
59
+ setOpen(!open);
60
+ else if (showing)
61
+ setFlyout(false);
62
+ else
63
+ showFlyout();
64
+ }
65
+ // Leaving by keyboard closes the flyout.
66
+ function focusLeft(e) {
67
+ if (showing && !root.current?.contains(e.relatedTarget))
68
+ setFlyout(false);
69
+ }
70
+ function escaped(e) {
71
+ if (e.key !== 'Escape' || !showing)
72
+ return;
73
+ setFlyout(false);
74
+ toggleEl.current?.focus();
75
+ }
76
+ return (_jsxs("div", { ...rest, ...part('root'), className: cx(className, classes?.root), ref: root, "data-open": open || undefined, "data-rail": rail || undefined, "data-flyout": showing || undefined, onPointerEnter: showFlyout, onPointerLeave: () => setFlyout(false), onBlur: focusLeft, onKeyDown: escaped, children: [_jsxs("button", { ...part('trigger'), ref: toggleEl, type: "button", "aria-expanded": rail ? showing : open, "aria-controls": `${id}-panel`, "aria-label": rail ? label : undefined, onClick: toggle, children: [icon && _jsx("iconify-icon", { ...part('icon'), icon: icon, "aria-hidden": "true" }), _jsx("span", { ...part('text'), children: label }), _jsx("iconify-icon", { ...part('indicator'), icon: "lucide:chevron-down", "aria-hidden": "true" })] }), (rail ? showing : open) && (_jsx("div", { ...part('children'), id: `${id}-panel`, role: "presentation", style: { '--flyout-top': `${position.top}px`, '--flyout-left': `${position.left}px` }, onClick: (e) => { if (e.target.closest('a'))
77
+ setFlyout(false); }, children: _jsxs("div", { ...part('panel'), children: [rail && _jsx("div", { ...part('flyout-heading'), "aria-hidden": "true", children: label }), _jsx(FlyoutContext, { value: true, children: children })] }) }))] }));
78
+ }
@@ -0,0 +1,33 @@
1
+ /**
2
+ * A sidebar link (or a button, without `href`). Other props (onClick,
3
+ * data-*) go on the link.
4
+ *
5
+ * <NavItem icon="lucide:users" href="/students" active={isCurrent('/students')} badge={3}>Students</NavItem>
6
+ * <NavItem icon="lucide:users" link={<Link to="/students" />}>Students</NavItem>
7
+ *
8
+ * - `icon`: an Iconify name
9
+ * - `href`: where it goes (without one it's a button: give it `onClick`)
10
+ * - `link`: your router's link element to render in place of the <a>
11
+ * (`<Link to="…" />`); it gets the item's props and content
12
+ * - `active`: the current page (sets aria-current)
13
+ * - `badge`: a count or short text on the right (hidden at 0)
14
+ * - `label`: its text, in place of the content
15
+ *
16
+ * In the rail only the icon shows, with its text in a tooltip (inside a
17
+ * group's flyout the text stays); in the drawer, following a link closes it.
18
+ */
19
+ import { type AnchorHTMLAttributes, type ReactElement, type ReactNode } from 'react';
20
+ import 'iconify-icon';
21
+ import { type Classes } from '../utils.js';
22
+ import '../theme.css';
23
+ import './sidebar.css';
24
+ export type NavItemProps = Omit<AnchorHTMLAttributes<HTMLElement>, 'href'> & {
25
+ icon?: string | null;
26
+ href?: string | null;
27
+ link?: ReactElement | null;
28
+ active?: boolean;
29
+ badge?: ReactNode;
30
+ label?: string | null;
31
+ classes?: Classes<'root' | 'link' | 'icon' | 'text' | 'badge'>;
32
+ };
33
+ export declare function NavItem({ icon, href, link, active, badge, label, onClick, className, classes, children, ...rest }: NavItemProps): import("react").JSX.Element;
@@ -0,0 +1,50 @@
1
+ import { jsx as _jsx, Fragment as _Fragment, jsxs as _jsxs } from "react/jsx-runtime";
2
+ /**
3
+ * A sidebar link (or a button, without `href`). Other props (onClick,
4
+ * data-*) go on the link.
5
+ *
6
+ * <NavItem icon="lucide:users" href="/students" active={isCurrent('/students')} badge={3}>Students</NavItem>
7
+ * <NavItem icon="lucide:users" link={<Link to="/students" />}>Students</NavItem>
8
+ *
9
+ * - `icon`: an Iconify name
10
+ * - `href`: where it goes (without one it's a button: give it `onClick`)
11
+ * - `link`: your router's link element to render in place of the <a>
12
+ * (`<Link to="…" />`); it gets the item's props and content
13
+ * - `active`: the current page (sets aria-current)
14
+ * - `badge`: a count or short text on the right (hidden at 0)
15
+ * - `label`: its text, in place of the content
16
+ *
17
+ * In the rail only the icon shows, with its text in a tooltip (inside a
18
+ * group's flyout the text stays); in the drawer, following a link closes it.
19
+ */
20
+ import { Children, cloneElement, isValidElement, useContext } from 'react';
21
+ import 'iconify-icon';
22
+ import { Tooltip } from '../tooltip/Tooltip.js';
23
+ import { useShell } from '../app-shell/shell.js';
24
+ import { cx, partsOf } from '../utils.js';
25
+ import { FlyoutContext } from './context.js';
26
+ import '../theme.css';
27
+ import './sidebar.css';
28
+ // The text of some content, for the rail's tooltip.
29
+ const textOf = (node) => Children.toArray(node).map((child) => typeof child === 'string' || typeof child === 'number' ? String(child)
30
+ : isValidElement(child) ? textOf(child.props.children) : '').join('').trim();
31
+ export function NavItem({ icon = null, href = null, link = null, active = false, badge = null, label = null, onClick, className, classes, children, ...rest }) {
32
+ const shell = useShell();
33
+ const flyout = useContext(FlyoutContext);
34
+ const part = partsOf('nav-item', classes);
35
+ const rail = !!shell?.rail && !flyout;
36
+ const text = label ?? textOf(children);
37
+ function follow(e) {
38
+ onClick?.(e);
39
+ if ((href || link) && shell?.mobile)
40
+ shell.close();
41
+ }
42
+ const props = {
43
+ ...rest, ...part('link'), onClick: follow,
44
+ 'aria-current': active ? 'page' : undefined, 'aria-label': rail ? text : undefined,
45
+ };
46
+ const content = _jsxs(_Fragment, { children: [icon && _jsx("iconify-icon", { ...part('icon'), icon: icon, "aria-hidden": "true" }), _jsx("span", { ...part('text'), children: children ?? label }), badge !== null && badge !== undefined && badge !== 0 && _jsx("span", { ...part('badge'), children: badge })] });
47
+ return (_jsx("div", { ...part('root'), className: cx(className, classes?.root), "data-active": active || undefined, children: _jsx(Tooltip, { content: text, placement: "right", disabled: !rail, children: link ? cloneElement(link, props, content)
48
+ : href ? _jsx("a", { ...props, href: href, children: content })
49
+ : _jsx("button", { ...props, type: "button", children: content }) }) }));
50
+ }
@@ -0,0 +1,20 @@
1
+ /**
2
+ * A group of nav items, with an optional heading (hidden in the rail).
3
+ *
4
+ * <NavSection heading="Settings" collapsible>…NavGroups…</NavSection>
5
+ *
6
+ * - `heading`
7
+ * - `collapsible`: a button beside the heading that collapses (or expands)
8
+ * all the section's groups at once
9
+ */
10
+ import { type HTMLAttributes, type ReactNode } from 'react';
11
+ import 'iconify-icon';
12
+ import { type Classes } from '../utils.js';
13
+ import '../theme.css';
14
+ import './sidebar.css';
15
+ export type NavSectionProps = HTMLAttributes<HTMLDivElement> & {
16
+ heading?: ReactNode;
17
+ collapsible?: boolean;
18
+ classes?: Classes<'root' | 'headingRow' | 'heading' | 'collapseAll'>;
19
+ };
20
+ export declare function NavSection({ heading, collapsible, className, classes, children, ...rest }: NavSectionProps): import("react").JSX.Element;
@@ -0,0 +1,44 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ /**
3
+ * A group of nav items, with an optional heading (hidden in the rail).
4
+ *
5
+ * <NavSection heading="Settings" collapsible>…NavGroups…</NavSection>
6
+ *
7
+ * - `heading`
8
+ * - `collapsible`: a button beside the heading that collapses (or expands)
9
+ * all the section's groups at once
10
+ */
11
+ import { useMemo, useRef, useState } from 'react';
12
+ import 'iconify-icon';
13
+ import { useShell } from '../app-shell/shell.js';
14
+ import { cx, partsOf } from '../utils.js';
15
+ import { useMessages } from '../format/LocaleProvider.js';
16
+ import { SectionContext } from './context.js';
17
+ import '../theme.css';
18
+ import './sidebar.css';
19
+ export function NavSection({ heading = null, collapsible = false, className, classes, children, ...rest }) {
20
+ const t = useMessages();
21
+ const shell = useShell();
22
+ const part = partsOf('nav-section', classes);
23
+ const setters = useRef(new Map());
24
+ const [openIds, setOpenIds] = useState([]);
25
+ const anyOpen = openIds.length > 0;
26
+ const section = useMemo(() => ({
27
+ register(id, setOpen) {
28
+ setters.current.set(id, setOpen);
29
+ return () => {
30
+ setters.current.delete(id);
31
+ setOpenIds((ids) => ids.filter((other) => other !== id));
32
+ };
33
+ },
34
+ report(id, open) {
35
+ setOpenIds((ids) => (open === ids.includes(id) ? ids : open ? [...ids, id] : ids.filter((other) => other !== id)));
36
+ },
37
+ }), []);
38
+ function toggleAll() {
39
+ for (const setOpen of setters.current.values())
40
+ setOpen(!anyOpen);
41
+ }
42
+ const toggleLabel = anyOpen ? t.shell.collapseAll : t.shell.expandAll;
43
+ return (_jsx(SectionContext, { value: section, children: _jsxs("div", { ...rest, ...part('root'), className: cx(className, classes?.root), role: "group", "aria-label": typeof heading === 'string' ? heading : undefined, children: [(heading || collapsible) && (_jsxs("div", { ...part('heading-row'), children: [_jsx("div", { ...part('heading'), "aria-hidden": "true", children: heading }), collapsible && !shell?.rail && (_jsx("button", { ...part('collapse-all'), type: "button", onClick: toggleAll, "aria-label": toggleLabel, title: toggleLabel, children: _jsx("iconify-icon", { icon: anyOpen ? 'lucide:chevrons-down-up' : 'lucide:chevrons-up-down', "aria-hidden": "true" }) }))] })), children] }) }));
44
+ }
@@ -0,0 +1,65 @@
1
+ /**
2
+ * An app sidebar: a logo, the nav, and a footer. In an AppShell it collapses
3
+ * to an icon rail (its button, or ⌘B) and becomes a drawer on phones.
4
+ *
5
+ * <Sidebar logo="TutorApp" footer={<SidebarUser name="Ada Lovelace" items={account} />}>
6
+ * <NavSection heading="Menu">
7
+ * <NavItem icon="lucide:house" href="/" active>Dashboard</NavItem>
8
+ * <NavGroup label="Settings" icon="lucide:settings">…</NavGroup>
9
+ * </NavSection>
10
+ * </Sidebar>
11
+ *
12
+ * - `logo`; `logoCollapsed`: shown instead in the rail (a mark)
13
+ * - `footer`: along the bottom (a SidebarUser)
14
+ * - `accordion`: one NavGroup open at a time
15
+ * - `items`: the nav as data, in place of (or before) the content:
16
+ * `[{ heading, items: [{ label, icon, href, active, badge, items }] }]`
17
+ * - `edge`: a thin strip along the right edge that collapses or expands it
18
+ * on click: 'rail' (default) shows it only while collapsed, true always,
19
+ * false never
20
+ * - `label`: the nav's accessible name ('Main')
21
+ * - restyle its items with the `--ui-sidebar-*` tokens, e.g.
22
+ * style={{ '--ui-sidebar-active': 'var(--ui-accent-soft)' }}
23
+ *
24
+ * Its parts are on it too: Sidebar.Section, Sidebar.Group, Sidebar.Item and
25
+ * Sidebar.User are NavSection, NavGroup, NavItem and SidebarUser.
26
+ */
27
+ import { type HTMLAttributes, type ReactNode } from 'react';
28
+ import 'iconify-icon';
29
+ import { type Classes } from '../utils.js';
30
+ import { NavSection } from './NavSection.js';
31
+ import { NavGroup } from './NavGroup.js';
32
+ import { NavItem } from './NavItem.js';
33
+ import { SidebarUser } from './SidebarUser.js';
34
+ import '../theme.css';
35
+ import './sidebar.css';
36
+ export type SidebarLink = {
37
+ label: string;
38
+ icon?: string;
39
+ href?: string;
40
+ active?: boolean;
41
+ badge?: ReactNode;
42
+ items?: SidebarLink[];
43
+ };
44
+ export type SidebarSection = {
45
+ heading?: ReactNode;
46
+ items?: SidebarLink[];
47
+ };
48
+ export type SidebarProps = HTMLAttributes<HTMLElement> & {
49
+ logo?: ReactNode;
50
+ logoCollapsed?: ReactNode;
51
+ footer?: ReactNode;
52
+ accordion?: boolean;
53
+ items?: SidebarSection[] | null;
54
+ edge?: 'rail' | boolean;
55
+ label?: string;
56
+ classes?: Classes<'root' | 'header' | 'logo' | 'toggle' | 'nav' | 'footer' | 'edge'>;
57
+ };
58
+ declare function SidebarShortcut({ logo, logoCollapsed, footer, accordion, items, edge, label: labelProp, className, classes, children, ...rest }: SidebarProps): import("react").JSX.Element;
59
+ export declare const Sidebar: typeof SidebarShortcut & {
60
+ Section: typeof NavSection;
61
+ Group: typeof NavGroup;
62
+ Item: typeof NavItem;
63
+ User: typeof SidebarUser;
64
+ };
65
+ export {};
@@ -0,0 +1,59 @@
1
+ import { jsx as _jsx, Fragment as _Fragment, jsxs as _jsxs } from "react/jsx-runtime";
2
+ /**
3
+ * An app sidebar: a logo, the nav, and a footer. In an AppShell it collapses
4
+ * to an icon rail (its button, or ⌘B) and becomes a drawer on phones.
5
+ *
6
+ * <Sidebar logo="TutorApp" footer={<SidebarUser name="Ada Lovelace" items={account} />}>
7
+ * <NavSection heading="Menu">
8
+ * <NavItem icon="lucide:house" href="/" active>Dashboard</NavItem>
9
+ * <NavGroup label="Settings" icon="lucide:settings">…</NavGroup>
10
+ * </NavSection>
11
+ * </Sidebar>
12
+ *
13
+ * - `logo`; `logoCollapsed`: shown instead in the rail (a mark)
14
+ * - `footer`: along the bottom (a SidebarUser)
15
+ * - `accordion`: one NavGroup open at a time
16
+ * - `items`: the nav as data, in place of (or before) the content:
17
+ * `[{ heading, items: [{ label, icon, href, active, badge, items }] }]`
18
+ * - `edge`: a thin strip along the right edge that collapses or expands it
19
+ * on click: 'rail' (default) shows it only while collapsed, true always,
20
+ * false never
21
+ * - `label`: the nav's accessible name ('Main')
22
+ * - restyle its items with the `--ui-sidebar-*` tokens, e.g.
23
+ * style={{ '--ui-sidebar-active': 'var(--ui-accent-soft)' }}
24
+ *
25
+ * Its parts are on it too: Sidebar.Section, Sidebar.Group, Sidebar.Item and
26
+ * Sidebar.User are NavSection, NavGroup, NavItem and SidebarUser.
27
+ */
28
+ import { useMemo, useState } from 'react';
29
+ import 'iconify-icon';
30
+ import { Tooltip } from '../tooltip/Tooltip.js';
31
+ import { useShell } from '../app-shell/shell.js';
32
+ import { cx, partsOf, withParts } from '../utils.js';
33
+ import { useMessages } from '../format/LocaleProvider.js';
34
+ import { SidebarContext } from './context.js';
35
+ import { NavSection } from './NavSection.js';
36
+ import { NavGroup } from './NavGroup.js';
37
+ import { NavItem } from './NavItem.js';
38
+ import { SidebarUser } from './SidebarUser.js';
39
+ import '../theme.css';
40
+ import './sidebar.css';
41
+ function SidebarShortcut({ logo = null, logoCollapsed = null, footer = null, accordion = false, items = null, edge = 'rail', label: labelProp, className, classes, children, ...rest }) {
42
+ const t = useMessages();
43
+ const label = labelProp ?? t.shell.nav;
44
+ const shell = useShell();
45
+ const part = partsOf('sidebar', classes);
46
+ const rail = !!shell?.rail;
47
+ // The open accordion group, by id.
48
+ const [openGroup, setOpenGroup] = useState(null);
49
+ const sidebar = useMemo(() => ({ accordion, openGroup, setOpenGroup }), [accordion, openGroup]);
50
+ const toggleButton = (expand) => (_jsx(Tooltip, { content: `${expand ? t.shell.expandSidebar : t.shell.collapseSidebar} (⌘B)`, placement: "right", children: _jsx("button", { ...part('toggle'), type: "button", onClick: shell?.toggle, "aria-label": expand ? t.shell.expandSidebar : t.shell.collapseSidebar, children: _jsx("iconify-icon", { icon: expand ? 'lucide:panel-left-open' : 'lucide:panel-left-close', "aria-hidden": "true" }) }) }));
51
+ return (_jsx(SidebarContext, { value: sidebar, children: _jsxs("aside", { ...rest, ...part('root'), className: cx(className, classes?.root), "data-rail": rail || undefined, "data-mobile": shell?.mobile || undefined, "data-open": shell?.drawerOpen || undefined, children: [(logo || shell) && (_jsx("div", { ...part('header'), children: rail
52
+ ? logoCollapsed && _jsx("div", { ...part('logo'), children: logoCollapsed })
53
+ : _jsxs(_Fragment, { children: [logo && _jsx("div", { ...part('logo'), children: logo }), shell?.mobile
54
+ ? _jsx("button", { ...part('toggle'), type: "button", onClick: shell.toggle, "aria-label": t.shell.closeMenu, children: _jsx("iconify-icon", { icon: "lucide:x", "aria-hidden": "true" }) })
55
+ : shell && toggleButton(false)] }) })), _jsxs("nav", { ...part('nav'), "aria-label": label, children: [(items ?? []).map((section, i) => (_jsx(NavSection, { heading: section.heading, children: (section.items ?? []).map((item, j) => item.items
56
+ ? _jsx(NavGroup, { label: item.label, icon: item.icon, defaultOpen: item.items.some((child) => child.active), children: item.items.map((child, k) => (_jsx(NavItem, { href: child.href, icon: child.icon, active: child.active, badge: child.badge, label: child.label }, k))) }, j)
57
+ : _jsx(NavItem, { href: item.href, icon: item.icon, active: item.active, badge: item.badge, label: item.label }, j)) }, i))), children] }), (footer || rail) && (_jsxs("div", { ...part('footer'), children: [footer, rail && toggleButton(true)] })), shell && !shell.mobile && (edge === 'rail' ? rail : edge) && (_jsx("button", { ...part('edge'), type: "button", tabIndex: -1, "aria-hidden": "true", title: rail ? t.shell.expandSidebar : t.shell.collapseSidebar, onMouseDown: (e) => e.preventDefault(), onClick: shell.toggle }))] }) }));
58
+ }
59
+ export const Sidebar = withParts(SidebarShortcut, { Section: NavSection, Group: NavGroup, Item: NavItem, User: SidebarUser });