@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.
- package/README.md +173 -2
- package/dist/accordion/Accordion.d.ts +65 -0
- package/dist/accordion/Accordion.js +61 -0
- package/dist/accordion/accordion.css +17 -0
- package/dist/action-bar/ActionBar.d.ts +33 -0
- package/dist/action-bar/ActionBar.js +48 -0
- package/dist/action-bar/action-bar.css +22 -0
- package/dist/alert/Alert.d.ts +28 -0
- package/dist/alert/Alert.js +12 -0
- package/dist/alert/alert.css +32 -0
- package/dist/app-shell/AppShell.d.ts +38 -0
- package/dist/app-shell/AppShell.js +92 -0
- package/dist/app-shell/Topbar.d.ts +20 -0
- package/dist/app-shell/Topbar.js +13 -0
- package/dist/app-shell/app-shell.css +41 -0
- package/dist/app-shell/shell.d.ts +9 -0
- package/dist/app-shell/shell.js +3 -0
- package/dist/attachments/Attachments.d.ts +84 -0
- package/dist/attachments/Attachments.js +235 -0
- package/dist/attachments/attachments.css +86 -0
- package/dist/avatar/Avatar.d.ts +31 -0
- package/dist/avatar/Avatar.js +41 -0
- package/dist/avatar/AvatarGroup.d.ts +41 -0
- package/dist/avatar/AvatarGroup.js +17 -0
- package/dist/avatar/avatar.css +73 -0
- package/dist/badge/Badge.d.ts +26 -0
- package/dist/badge/Badge.js +10 -0
- package/dist/badge/badge.css +24 -0
- package/dist/banner/Banner.d.ts +33 -0
- package/dist/banner/Banner.js +14 -0
- package/dist/banner/banner.css +25 -0
- package/dist/breadcrumbs/Breadcrumbs.d.ts +39 -0
- package/dist/breadcrumbs/Breadcrumbs.js +28 -0
- package/dist/breadcrumbs/breadcrumbs.css +18 -0
- package/dist/button/Button.d.ts +40 -0
- package/dist/button/Button.js +19 -0
- package/dist/button/button.css +63 -0
- package/dist/card/Card.d.ts +355 -0
- package/dist/card/Card.js +56 -0
- package/dist/card/card.css +43 -0
- package/dist/chart/AreaChart.d.ts +19 -0
- package/dist/chart/AreaChart.js +61 -0
- package/dist/chart/BarChart.d.ts +3 -0
- package/dist/chart/BarChart.js +60 -0
- package/dist/chart/ChartFrame.d.ts +25 -0
- package/dist/chart/ChartFrame.js +22 -0
- package/dist/chart/DonutChart.d.ts +20 -0
- package/dist/chart/DonutChart.js +41 -0
- package/dist/chart/Sparkline.d.ts +26 -0
- package/dist/chart/Sparkline.js +36 -0
- package/dist/chart/chart.css +51 -0
- package/dist/chart/chart.d.ts +23 -0
- package/dist/chart/chart.js +19 -0
- package/dist/checkbox/Checkbox.d.ts +50 -0
- package/dist/checkbox/Checkbox.js +19 -0
- package/dist/checkbox/CheckboxGroup.d.ts +44 -0
- package/dist/checkbox/CheckboxGroup.js +80 -0
- package/dist/checkbox/checkbox.css +49 -0
- package/dist/collapsible/Collapsible.d.ts +41 -0
- package/dist/collapsible/Collapsible.js +15 -0
- package/dist/collapsible/collapsible.css +20 -0
- package/dist/color-scheme.d.ts +18 -0
- package/dist/color-scheme.js +84 -0
- package/dist/command/Command.d.ts +64 -0
- package/dist/command/Command.js +162 -0
- package/dist/command/command.css +47 -0
- package/dist/confirm/confirm.css +5 -0
- package/dist/confirm/confirm.d.ts +42 -0
- package/dist/confirm/confirm.js +115 -0
- package/dist/copy-button/CopyButton.d.ts +30 -0
- package/dist/copy-button/CopyButton.js +23 -0
- package/dist/copy-button/copy-button.css +12 -0
- package/dist/css.d.ts +2 -0
- package/dist/data-list/DataList.d.ts +55 -0
- package/dist/data-list/DataList.js +43 -0
- package/dist/data-list/data-list.css +19 -0
- package/dist/date-picker/DatePicker.d.ts +95 -0
- package/dist/date-picker/DatePicker.js +237 -0
- package/dist/date-picker/date-picker.css +53 -0
- package/dist/dialog/Dialog.d.ts +98 -0
- package/dist/dialog/Dialog.js +92 -0
- package/dist/dialog/dialog.css +31 -0
- package/dist/divider/Divider.d.ts +27 -0
- package/dist/divider/Divider.js +8 -0
- package/dist/divider/divider.css +12 -0
- package/dist/editable/Editable.d.ts +47 -0
- package/dist/editable/Editable.js +51 -0
- package/dist/editable/editable.css +25 -0
- package/dist/editor/Editor.d.ts +59 -0
- package/dist/editor/Editor.js +220 -0
- package/dist/editor/editor.css +42 -0
- package/dist/editor/image.d.ts +1 -0
- package/dist/editor/image.js +23 -0
- package/dist/empty-state/EmptyState.d.ts +22 -0
- package/dist/empty-state/EmptyState.js +9 -0
- package/dist/empty-state/empty-state.css +14 -0
- package/dist/event-calendar/EventCalendar.d.ts +213 -0
- package/dist/event-calendar/EventCalendar.js +686 -0
- package/dist/event-calendar/dates.d.ts +8 -0
- package/dist/event-calendar/dates.js +21 -0
- package/dist/event-calendar/event-calendar.css +157 -0
- package/dist/field/Field.d.ts +66 -0
- package/dist/field/Field.js +23 -0
- package/dist/field/field.css +26 -0
- package/dist/file-upload/FileUpload.d.ts +60 -0
- package/dist/file-upload/FileUpload.js +102 -0
- package/dist/file-upload/file-upload.css +32 -0
- package/dist/format/FormatNumber.d.ts +42 -0
- package/dist/format/FormatNumber.js +59 -0
- package/dist/format/LocaleProvider.d.ts +33 -0
- package/dist/format/LocaleProvider.js +48 -0
- package/dist/format/format.d.ts +37 -0
- package/dist/format/format.js +74 -0
- package/dist/format/messages.d.ts +211 -0
- package/dist/format/messages.js +202 -0
- package/dist/highlight/Highlight.d.ts +11 -0
- package/dist/highlight/Highlight.js +14 -0
- package/dist/highlight/highlight.css +17 -0
- package/dist/hover-card/HoverCard.d.ts +47 -0
- package/dist/hover-card/HoverCard.js +18 -0
- package/dist/hover-card/hover-card.css +19 -0
- package/dist/index.d.ts +84 -0
- package/dist/index.js +85 -0
- package/dist/input/Input.d.ts +41 -0
- package/dist/input/Input.js +48 -0
- package/dist/input/input.css +25 -0
- package/dist/kbd/Kbd.d.ts +19 -0
- package/dist/kbd/Kbd.js +20 -0
- package/dist/kbd/kbd.css +8 -0
- package/dist/menu/Menu.d.ts +223 -0
- package/dist/menu/Menu.js +275 -0
- package/dist/menu/menu.css +38 -0
- package/dist/native-select/NativeSelect.d.ts +36 -0
- package/dist/native-select/NativeSelect.js +51 -0
- package/dist/native-select/native-select.css +16 -0
- package/dist/number-input/NumberInput.d.ts +56 -0
- package/dist/number-input/NumberInput.js +67 -0
- package/dist/number-input/number-input.css +21 -0
- package/dist/page/Page.d.ts +30 -0
- package/dist/page/Page.js +9 -0
- package/dist/page/page.css +17 -0
- package/dist/pagination/Pagination.d.ts +31 -0
- package/dist/pagination/Pagination.js +58 -0
- package/dist/pagination/pagination.css +15 -0
- package/dist/password-input/PasswordInput.d.ts +47 -0
- package/dist/password-input/PasswordInput.js +35 -0
- package/dist/password-input/password-input.css +18 -0
- package/dist/pin-input/PinInput.d.ts +45 -0
- package/dist/pin-input/PinInput.js +22 -0
- package/dist/pin-input/pin-input.css +13 -0
- package/dist/popover/Popover.d.ts +65 -0
- package/dist/popover/Popover.js +26 -0
- package/dist/popover/popover.css +16 -0
- package/dist/progress/Progress.d.ts +56 -0
- package/dist/progress/Progress.js +28 -0
- package/dist/progress/progress.css +24 -0
- package/dist/prose/Prose.d.ts +23 -0
- package/dist/prose/Prose.js +10 -0
- package/dist/prose/prose.css +29 -0
- package/dist/radio-group/RadioGroup.d.ts +59 -0
- package/dist/radio-group/RadioGroup.js +37 -0
- package/dist/radio-group/radio-group.css +30 -0
- package/dist/rolling-number/RollingNumber.d.ts +27 -0
- package/dist/rolling-number/RollingNumber.js +18 -0
- package/dist/rolling-number/rolling-number.css +10 -0
- package/dist/segment-group/Segmented.d.ts +68 -0
- package/dist/segment-group/Segmented.js +79 -0
- package/dist/segment-group/segment-group.css +25 -0
- package/dist/select/ListSelect.d.ts +5 -0
- package/dist/select/ListSelect.js +82 -0
- package/dist/select/SearchSelect.d.ts +43 -0
- package/dist/select/SearchSelect.js +219 -0
- package/dist/select/Select.d.ts +147 -0
- package/dist/select/Select.js +32 -0
- package/dist/select/items.d.ts +17 -0
- package/dist/select/items.js +14 -0
- package/dist/select/search-select.css +45 -0
- package/dist/select/select.css +52 -0
- package/dist/sheet/Sheet.d.ts +88 -0
- package/dist/sheet/Sheet.js +66 -0
- package/dist/sheet/sheet.css +44 -0
- package/dist/sidebar/NavGroup.d.ts +29 -0
- package/dist/sidebar/NavGroup.js +78 -0
- package/dist/sidebar/NavItem.d.ts +33 -0
- package/dist/sidebar/NavItem.js +50 -0
- package/dist/sidebar/NavSection.d.ts +20 -0
- package/dist/sidebar/NavSection.js +44 -0
- package/dist/sidebar/Sidebar.d.ts +65 -0
- package/dist/sidebar/Sidebar.js +59 -0
- package/dist/sidebar/SidebarUser.d.ts +31 -0
- package/dist/sidebar/SidebarUser.js +14 -0
- package/dist/sidebar/context.d.ts +13 -0
- package/dist/sidebar/context.js +6 -0
- package/dist/sidebar/sidebar.css +109 -0
- package/dist/skeleton/Skeleton.d.ts +25 -0
- package/dist/skeleton/Skeleton.js +8 -0
- package/dist/skeleton/skeleton.css +13 -0
- package/dist/slider/Slider.d.ts +70 -0
- package/dist/slider/Slider.js +65 -0
- package/dist/slider/slider.css +17 -0
- package/dist/sortable-list/SortableList.d.ts +46 -0
- package/dist/sortable-list/SortableList.js +85 -0
- package/dist/sortable-list/sortable-list.css +25 -0
- package/dist/spinner/Spinner.d.ts +18 -0
- package/dist/spinner/Spinner.js +11 -0
- package/dist/spinner/spinner.css +9 -0
- package/dist/stat/Stat.d.ts +42 -0
- package/dist/stat/Stat.js +21 -0
- package/dist/stat/Stats.d.ts +23 -0
- package/dist/stat/Stats.js +8 -0
- package/dist/stat/stat.css +34 -0
- package/dist/swatch-picker/SwatchPicker.d.ts +45 -0
- package/dist/swatch-picker/SwatchPicker.js +54 -0
- package/dist/swatch-picker/swatch-picker.css +38 -0
- package/dist/switch/Switch.d.ts +39 -0
- package/dist/switch/Switch.js +17 -0
- package/dist/switch/switch.css +13 -0
- package/dist/table/Table.d.ts +90 -0
- package/dist/table/Table.js +145 -0
- package/dist/table/table.css +44 -0
- package/dist/tabs/Tabs.d.ts +87 -0
- package/dist/tabs/Tabs.js +90 -0
- package/dist/tabs/tabs.css +53 -0
- package/dist/tags-input/TagsInput.d.ts +69 -0
- package/dist/tags-input/TagsInput.js +95 -0
- package/dist/tags-input/tags-input.css +37 -0
- package/dist/textarea/Textarea.d.ts +24 -0
- package/dist/textarea/Textarea.js +51 -0
- package/dist/textarea/textarea.css +14 -0
- package/dist/theme-toggle/ThemeToggle.d.ts +49 -0
- package/dist/theme-toggle/ThemeToggle.js +71 -0
- package/dist/theme-toggle/theme-toggle.css +23 -0
- package/dist/theme.css +121 -0
- package/dist/time-input/TimeInput.d.ts +25 -0
- package/dist/time-input/TimeInput.js +20 -0
- package/dist/timeline/Timeline.d.ts +50 -0
- package/dist/timeline/Timeline.js +15 -0
- package/dist/timeline/timeline.css +29 -0
- package/dist/toast/Toaster.d.ts +36 -0
- package/dist/toast/Toaster.js +25 -0
- package/dist/toast/toast.css +33 -0
- package/dist/tooltip/Tooltip.d.ts +55 -0
- package/dist/tooltip/Tooltip.js +48 -0
- package/dist/tooltip/tooltip.css +18 -0
- package/dist/tree-view/TreeView.d.ts +86 -0
- package/dist/tree-view/TreeView.js +74 -0
- package/dist/tree-view/tree-view.css +29 -0
- package/dist/utils.d.ts +19 -0
- package/dist/utils.js +59 -0
- 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 });
|