@ratio-ui/core 0.1.1 → 0.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +2 -0
- package/dist/catalogue.json +2829 -72
- package/dist/components/alert/Alert.d.ts +18 -0
- package/dist/components/alert/Alert.js +67 -0
- package/dist/components/alert/index.d.ts +2 -0
- package/dist/components/alert/index.js +1 -0
- package/dist/components/alert-dialog/AlertDialog.d.ts +48 -0
- package/dist/components/alert-dialog/AlertDialog.js +76 -0
- package/dist/components/alert-dialog/index.d.ts +2 -0
- package/dist/components/alert-dialog/index.js +1 -0
- package/dist/components/app-shell/AppShell.d.ts +22 -0
- package/dist/components/app-shell/AppShell.js +39 -0
- package/dist/components/app-shell/context.js +5 -0
- package/dist/components/app-shell/index.d.ts +2 -0
- package/dist/components/app-shell/index.js +1 -0
- package/dist/components/avatar/Avatar.d.ts +22 -0
- package/dist/components/avatar/Avatar.js +38 -0
- package/dist/components/avatar/index.d.ts +2 -0
- package/dist/components/avatar/index.js +1 -0
- package/dist/components/button/Button.d.ts +37 -0
- package/dist/components/button/Button.js +41 -0
- package/dist/components/button/index.d.ts +2 -0
- package/dist/components/button/index.js +1 -0
- package/dist/components/calendar/Calendar.d.ts +20 -0
- package/dist/components/calendar/Calendar.js +119 -0
- package/dist/components/calendar/index.d.ts +2 -0
- package/dist/components/calendar/index.js +1 -0
- package/dist/components/card/Card.d.ts +33 -0
- package/dist/components/card/Card.js +53 -0
- package/dist/components/card/index.d.ts +2 -0
- package/dist/components/card/index.js +1 -0
- package/dist/components/checkbox/Checkbox.d.ts +10 -0
- package/dist/components/checkbox/Checkbox.js +28 -0
- package/dist/components/checkbox/index.d.ts +2 -0
- package/dist/components/checkbox/index.js +1 -0
- package/dist/components/checkbox-list/CheckboxList.d.ts +36 -0
- package/dist/components/checkbox-list/CheckboxList.js +93 -0
- package/dist/components/checkbox-list/index.d.ts +2 -0
- package/dist/components/checkbox-list/index.js +1 -0
- package/dist/components/collapsible/Collapsible.d.ts +24 -0
- package/dist/components/collapsible/Collapsible.js +58 -0
- package/dist/components/collapsible/index.d.ts +2 -0
- package/dist/components/collapsible/index.js +1 -0
- package/dist/components/data-table/DataTable.d.ts +65 -0
- package/dist/components/data-table/DataTable.js +141 -0
- package/dist/components/data-table/index.d.ts +2 -0
- package/dist/components/data-table/index.js +1 -0
- package/dist/components/date-picker/DatePicker.d.ts +33 -0
- package/dist/components/date-picker/DatePicker.js +104 -0
- package/dist/components/date-picker/index.d.ts +2 -0
- package/dist/components/date-picker/index.js +1 -0
- package/dist/components/date-range-picker/DateRangePicker.d.ts +39 -0
- package/dist/components/date-range-picker/DateRangePicker.js +142 -0
- package/dist/components/date-range-picker/index.d.ts +2 -0
- package/dist/components/date-range-picker/index.js +1 -0
- package/dist/components/definition/Definition.d.ts +14 -0
- package/dist/components/definition/Definition.js +29 -0
- package/dist/components/definition/index.d.ts +2 -0
- package/dist/components/definition/index.js +1 -0
- package/dist/components/dialog/Dialog.d.ts +44 -0
- package/dist/components/dialog/Dialog.js +79 -0
- package/dist/components/dialog/index.d.ts +2 -0
- package/dist/components/dialog/index.js +1 -0
- package/dist/components/dropdown-menu/DropdownMenu.d.ts +73 -0
- package/dist/components/dropdown-menu/DropdownMenu.js +116 -0
- package/dist/components/dropdown-menu/index.d.ts +2 -0
- package/dist/components/dropdown-menu/index.js +1 -0
- package/dist/components/empty-state/EmptyState.d.ts +15 -0
- package/dist/components/empty-state/EmptyState.js +30 -0
- package/dist/components/empty-state/index.d.ts +2 -0
- package/dist/components/empty-state/index.js +1 -0
- package/dist/components/field/Field.d.ts +35 -0
- package/dist/components/field/Field.js +67 -0
- package/dist/components/field/Label.d.ts +8 -0
- package/dist/components/field/Label.js +13 -0
- package/dist/components/field/index.d.ts +3 -0
- package/dist/components/field/index.js +3 -0
- package/dist/components/filter-tabs/FilterTabs.d.ts +28 -0
- package/dist/components/filter-tabs/FilterTabs.js +37 -0
- package/dist/components/filter-tabs/index.d.ts +2 -0
- package/dist/components/filter-tabs/index.js +1 -0
- package/dist/components/global-search/GlobalSearch.d.ts +51 -0
- package/dist/components/global-search/GlobalSearch.js +144 -0
- package/dist/components/global-search/index.d.ts +2 -0
- package/dist/components/global-search/index.js +1 -0
- package/dist/components/icon/registry.d.ts +3 -2
- package/dist/components/icon/registry.js +57 -1
- package/dist/components/input/Input.d.ts +19 -0
- package/dist/components/input/Input.js +26 -0
- package/dist/components/input/index.d.ts +2 -0
- package/dist/components/input/index.js +1 -0
- package/dist/components/input-group/InputGroup.d.ts +42 -0
- package/dist/components/input-group/InputGroup.js +83 -0
- package/dist/components/input-group/index.d.ts +2 -0
- package/dist/components/input-group/index.js +1 -0
- package/dist/components/kbd/Kbd.d.ts +17 -0
- package/dist/components/kbd/Kbd.js +21 -0
- package/dist/components/kbd/index.d.ts +2 -0
- package/dist/components/kbd/index.js +1 -0
- package/dist/components/link/Link.d.ts +22 -0
- package/dist/components/link/Link.js +31 -0
- package/dist/components/link/index.d.ts +2 -0
- package/dist/components/link/index.js +1 -0
- package/dist/components/link-provider/LinkProvider.d.ts +29 -0
- package/dist/components/link-provider/LinkProvider.js +15 -0
- package/dist/components/link-provider/RatioLink.js +18 -0
- package/dist/components/link-provider/context.js +4 -0
- package/dist/components/link-provider/index.d.ts +2 -0
- package/dist/components/link-provider/index.js +1 -0
- package/dist/components/media-grid/MediaGrid.d.ts +49 -0
- package/dist/components/media-grid/MediaGrid.js +303 -0
- package/dist/components/media-grid/index.d.ts +2 -0
- package/dist/components/media-grid/index.js +1 -0
- package/dist/components/metric-strip/MetricStrip.d.ts +23 -0
- package/dist/components/metric-strip/MetricStrip.js +39 -0
- package/dist/components/metric-strip/index.d.ts +2 -0
- package/dist/components/metric-strip/index.js +1 -0
- package/dist/components/money-input/MoneyInput.d.ts +30 -0
- package/dist/components/money-input/MoneyInput.js +72 -0
- package/dist/components/money-input/index.d.ts +2 -0
- package/dist/components/money-input/index.js +1 -0
- package/dist/components/page-header/PageHeader.d.ts +37 -0
- package/dist/components/page-header/PageHeader.js +62 -0
- package/dist/components/page-header/index.d.ts +2 -0
- package/dist/components/page-header/index.js +1 -0
- package/dist/components/pagination/Pagination.d.ts +18 -0
- package/dist/components/pagination/Pagination.js +59 -0
- package/dist/components/pagination/index.d.ts +2 -0
- package/dist/components/pagination/index.js +1 -0
- package/dist/components/popover/Popover.d.ts +27 -0
- package/dist/components/popover/Popover.js +47 -0
- package/dist/components/popover/index.d.ts +2 -0
- package/dist/components/popover/index.js +1 -0
- package/dist/components/save-bar/SaveBar.d.ts +27 -0
- package/dist/components/save-bar/SaveBar.js +37 -0
- package/dist/components/save-bar/index.d.ts +2 -0
- package/dist/components/save-bar/index.js +1 -0
- package/dist/components/search-field/SearchField.d.ts +23 -0
- package/dist/components/search-field/SearchField.js +66 -0
- package/dist/components/search-field/index.d.ts +2 -0
- package/dist/components/search-field/index.js +1 -0
- package/dist/components/select/Select.d.ts +40 -0
- package/dist/components/select/Select.js +84 -0
- package/dist/components/select/index.d.ts +2 -0
- package/dist/components/select/index.js +1 -0
- package/dist/components/side-nav/SideNav.d.ts +64 -0
- package/dist/components/side-nav/SideNav.js +194 -0
- package/dist/components/side-nav/index.d.ts +2 -0
- package/dist/components/side-nav/index.js +1 -0
- package/dist/components/skeleton/Skeleton.d.ts +13 -0
- package/dist/components/skeleton/Skeleton.js +21 -0
- package/dist/components/skeleton/index.d.ts +2 -0
- package/dist/components/skeleton/index.js +1 -0
- package/dist/components/spinner/Spinner.d.ts +11 -0
- package/dist/components/spinner/Spinner.js +16 -0
- package/dist/components/spinner/index.d.ts +2 -0
- package/dist/components/spinner/index.js +1 -0
- package/dist/components/status-pill/StatusPill.d.ts +14 -0
- package/dist/components/status-pill/StatusPill.js +19 -0
- package/dist/components/status-pill/index.d.ts +2 -0
- package/dist/components/status-pill/index.js +1 -0
- package/dist/components/summary-chips/SummaryChips.d.ts +17 -0
- package/dist/components/summary-chips/SummaryChips.js +19 -0
- package/dist/components/summary-chips/index.d.ts +2 -0
- package/dist/components/summary-chips/index.js +1 -0
- package/dist/components/switch/Switch.d.ts +10 -0
- package/dist/components/switch/Switch.js +20 -0
- package/dist/components/switch/index.d.ts +2 -0
- package/dist/components/switch/index.js +1 -0
- package/dist/components/table/Table.d.ts +46 -0
- package/dist/components/table/Table.js +87 -0
- package/dist/components/table/index.d.ts +2 -0
- package/dist/components/table/index.js +1 -0
- package/dist/components/table-loader/TableLoader.d.ts +31 -0
- package/dist/components/table-loader/TableLoader.js +35 -0
- package/dist/components/table-loader/index.d.ts +2 -0
- package/dist/components/table-loader/index.js +1 -0
- package/dist/components/tabs/Tabs.d.ts +23 -0
- package/dist/components/tabs/Tabs.js +41 -0
- package/dist/components/tabs/index.d.ts +2 -0
- package/dist/components/tabs/index.js +1 -0
- package/dist/components/tag-input/TagInput.d.ts +44 -0
- package/dist/components/tag-input/TagInput.js +102 -0
- package/dist/components/tag-input/index.d.ts +2 -0
- package/dist/components/tag-input/index.js +1 -0
- package/dist/components/textarea/Textarea.d.ts +15 -0
- package/dist/components/textarea/Textarea.js +21 -0
- package/dist/components/textarea/index.d.ts +2 -0
- package/dist/components/textarea/index.js +1 -0
- package/dist/components/toaster/Toaster.d.ts +32 -0
- package/dist/components/toaster/Toaster.js +89 -0
- package/dist/components/toaster/index.d.ts +2 -0
- package/dist/components/toaster/index.js +1 -0
- package/dist/components/tooltip/Tooltip.d.ts +18 -0
- package/dist/components/tooltip/Tooltip.js +35 -0
- package/dist/components/tooltip/index.d.ts +2 -0
- package/dist/components/tooltip/index.js +1 -0
- package/dist/components/top-bar/StoreSwitcher.d.ts +26 -0
- package/dist/components/top-bar/StoreSwitcher.js +48 -0
- package/dist/components/top-bar/TopBar.d.ts +24 -0
- package/dist/components/top-bar/TopBar.js +53 -0
- package/dist/components/top-bar/index.d.ts +3 -0
- package/dist/components/top-bar/index.js +3 -0
- package/dist/components/unit-input/UnitInput.d.ts +33 -0
- package/dist/components/unit-input/UnitInput.js +47 -0
- package/dist/components/unit-input/index.d.ts +2 -0
- package/dist/components/unit-input/index.js +1 -0
- package/dist/index.d.ts +102 -1
- package/dist/index.js +54 -2
- package/dist/lib/cn.js +38 -27
- package/dist/lib/format.d.ts +92 -0
- package/dist/lib/format.js +222 -0
- package/dist/styles.css +1 -1
- package/package.json +205 -3
|
@@ -0,0 +1,303 @@
|
|
|
1
|
+
import { Icon } from "../icon/Icon.js";
|
|
2
|
+
import { Button } from "../button/Button.js";
|
|
3
|
+
import { formatCount } from "../../lib/format.js";
|
|
4
|
+
import { jsx, jsxs } from "react/jsx-runtime";
|
|
5
|
+
import { useEffect, useId, useRef, useState } from "react";
|
|
6
|
+
import { DndContext, DragOverlay, KeyboardSensor, MouseSensor, TouchSensor, closestCenter, useSensor, useSensors } from "@dnd-kit/core";
|
|
7
|
+
import { SortableContext, arrayMove, rectSortingStrategy, sortableKeyboardCoordinates, useSortable } from "@dnd-kit/sortable";
|
|
8
|
+
import { Progress } from "@base-ui/react/progress";
|
|
9
|
+
import { useDropzone } from "react-dropzone";
|
|
10
|
+
// src/components/media-grid/MediaGrid.tsx
|
|
11
|
+
const orderKey = (ids) => ids.join("\n");
|
|
12
|
+
const IMAGES = { "image/*": [] };
|
|
13
|
+
const INSTRUCTIONS = { draggable: "To move an image, press space or enter to pick it up, the arrow keys to move it, space or enter to drop it and escape to cancel. The Move left, Move right and Make cover buttons move it too" };
|
|
14
|
+
function MediaTile({ item, index, last, disabled, onAction }) {
|
|
15
|
+
const { attributes, listeners, setNodeRef, isDragging } = useSortable({
|
|
16
|
+
id: item.id,
|
|
17
|
+
disabled
|
|
18
|
+
});
|
|
19
|
+
const id = useId();
|
|
20
|
+
const isCover = index === 0;
|
|
21
|
+
const uploading = item.progress !== void 0 && item.progress < 100 && !item.error;
|
|
22
|
+
const describedBy = [
|
|
23
|
+
attributes["aria-describedby"],
|
|
24
|
+
isCover ? `${id}-cover` : null,
|
|
25
|
+
item.error ? `${id}-error` : null
|
|
26
|
+
].filter(Boolean).join(" ");
|
|
27
|
+
return /* @__PURE__ */ jsxs("li", {
|
|
28
|
+
"data-slot": "media-grid-item",
|
|
29
|
+
"data-media-id": item.id,
|
|
30
|
+
className: "rt:flex rt:w-24 rt:flex-col rt:gap-1",
|
|
31
|
+
children: [
|
|
32
|
+
/* @__PURE__ */ jsxs("div", {
|
|
33
|
+
className: "rt:relative rt:size-24",
|
|
34
|
+
children: [
|
|
35
|
+
/* @__PURE__ */ jsx("div", {
|
|
36
|
+
ref: setNodeRef,
|
|
37
|
+
...attributes,
|
|
38
|
+
...listeners,
|
|
39
|
+
"aria-describedby": describedBy,
|
|
40
|
+
"aria-invalid": item.error ? true : void 0,
|
|
41
|
+
"data-dragging": isDragging || void 0,
|
|
42
|
+
className: "rt:size-full rt:cursor-grab rt:overflow-hidden rt:rounded-lg rt:bg-muted rt:p-px rt:shadow-ring-subtle rt:forced-outline rt:select-none rt:transition-colors rt:focus-visible:focus-ring rt:aria-invalid:shadow-ring-invalid rt:aria-invalid:focus-visible:shadow-ring-invalid rt:aria-disabled:cursor-default rt:data-dragging:opacity-50",
|
|
43
|
+
children: /* @__PURE__ */ jsx("img", {
|
|
44
|
+
src: item.src,
|
|
45
|
+
alt: item.alt,
|
|
46
|
+
draggable: false,
|
|
47
|
+
className: "rt:size-full rt:rounded-md rt:object-cover"
|
|
48
|
+
})
|
|
49
|
+
}),
|
|
50
|
+
isCover ? /* @__PURE__ */ jsx("span", {
|
|
51
|
+
id: `${id}-cover`,
|
|
52
|
+
className: "rt:pointer-events-none rt:absolute rt:top-1 rt:left-1 rt:rounded-sm rt:bg-background rt:px-1.5 rt:text-c2 rt:text-foreground",
|
|
53
|
+
children: "Cover"
|
|
54
|
+
}) : null,
|
|
55
|
+
uploading ? /* @__PURE__ */ jsx(Progress.Root, {
|
|
56
|
+
value: item.progress ?? 0,
|
|
57
|
+
"aria-label": `Uploading ${item.alt}`,
|
|
58
|
+
className: "rt:pointer-events-none rt:absolute rt:inset-x-2 rt:bottom-2",
|
|
59
|
+
children: /* @__PURE__ */ jsx(Progress.Track, {
|
|
60
|
+
"data-slot": "media-grid-progress-track",
|
|
61
|
+
className: "rt:h-progress rt:overflow-hidden rt:bg-progress-track",
|
|
62
|
+
children: /* @__PURE__ */ jsx(Progress.Indicator, { className: "rt:h-full rt:bg-progress" })
|
|
63
|
+
})
|
|
64
|
+
}) : null
|
|
65
|
+
]
|
|
66
|
+
}),
|
|
67
|
+
item.error ? /* @__PURE__ */ jsxs("span", {
|
|
68
|
+
className: "rt:flex rt:items-start rt:gap-1 rt:text-error-icon",
|
|
69
|
+
children: [/* @__PURE__ */ jsx("span", {
|
|
70
|
+
className: "rt:mt-0.5 rt:flex",
|
|
71
|
+
children: /* @__PURE__ */ jsx(Icon, { name: "alert-triangle" })
|
|
72
|
+
}), /* @__PURE__ */ jsx("span", {
|
|
73
|
+
id: `${id}-error`,
|
|
74
|
+
className: "rt:text-c1 rt:text-error",
|
|
75
|
+
children: item.error
|
|
76
|
+
})]
|
|
77
|
+
}) : null,
|
|
78
|
+
/* @__PURE__ */ jsxs("div", {
|
|
79
|
+
role: "group",
|
|
80
|
+
"aria-label": item.alt,
|
|
81
|
+
className: "rt:flex rt:flex-col rt:items-start",
|
|
82
|
+
children: [/* @__PURE__ */ jsxs("div", {
|
|
83
|
+
className: "rt:flex rt:w-full rt:justify-between",
|
|
84
|
+
children: [
|
|
85
|
+
/* @__PURE__ */ jsx(Button, {
|
|
86
|
+
variant: "ghost",
|
|
87
|
+
iconOnly: true,
|
|
88
|
+
"aria-label": "Move left",
|
|
89
|
+
"data-media-action": "left",
|
|
90
|
+
disabled: disabled || isCover,
|
|
91
|
+
onClick: () => onAction("left", index),
|
|
92
|
+
children: /* @__PURE__ */ jsx(Icon, { name: "chevron-left" })
|
|
93
|
+
}),
|
|
94
|
+
/* @__PURE__ */ jsx(Button, {
|
|
95
|
+
variant: "ghost",
|
|
96
|
+
iconOnly: true,
|
|
97
|
+
"aria-label": "Move right",
|
|
98
|
+
"data-media-action": "right",
|
|
99
|
+
disabled: disabled || last,
|
|
100
|
+
onClick: () => onAction("right", index),
|
|
101
|
+
children: /* @__PURE__ */ jsx(Icon, { name: "chevron-right" })
|
|
102
|
+
}),
|
|
103
|
+
/* @__PURE__ */ jsx(Button, {
|
|
104
|
+
variant: "ghost",
|
|
105
|
+
iconOnly: true,
|
|
106
|
+
"aria-label": "Remove",
|
|
107
|
+
"data-media-action": "remove",
|
|
108
|
+
disabled,
|
|
109
|
+
onClick: () => onAction("remove", index),
|
|
110
|
+
children: /* @__PURE__ */ jsx(Icon, { name: "x" })
|
|
111
|
+
})
|
|
112
|
+
]
|
|
113
|
+
}), isCover ? null : /* @__PURE__ */ jsx(Button, {
|
|
114
|
+
variant: "link",
|
|
115
|
+
"data-media-action": "cover",
|
|
116
|
+
disabled,
|
|
117
|
+
onClick: () => onAction("cover", index),
|
|
118
|
+
children: "Make cover"
|
|
119
|
+
})]
|
|
120
|
+
})
|
|
121
|
+
]
|
|
122
|
+
});
|
|
123
|
+
}
|
|
124
|
+
/**
|
|
125
|
+
* The media of a product: a row of square thumbs with an add tile that opens the file picker and takes dropped files.
|
|
126
|
+
* Thumbs reorder by drag (pointer, touch or keyboard) and by the Move left, Move right and Make cover buttons, so no
|
|
127
|
+
* drag is needed. The first image is the cover and carries a badge. Uploads show a thin progress bar and failures an
|
|
128
|
+
* error under the thumb. Every move, cover change and removal is announced to screen readers.
|
|
129
|
+
*/
|
|
130
|
+
function MediaGrid({ items, onAdd, onReorder, onRemove, onMakeCover, accept = IMAGES, disabled = false, addLabel = "Add media", "aria-label": ariaLabel, "aria-labelledby": labelledBy, ...rest }) {
|
|
131
|
+
const ids = items.map((i) => i.id);
|
|
132
|
+
const total = formatCount(items.length);
|
|
133
|
+
const [activeId, setActiveId] = useState(null);
|
|
134
|
+
const pendingRef = useRef(null);
|
|
135
|
+
const statusRef = useRef(null);
|
|
136
|
+
const { getRootProps, getInputProps, open, isDragActive, rootRef } = useDropzone({
|
|
137
|
+
accept,
|
|
138
|
+
disabled,
|
|
139
|
+
multiple: true,
|
|
140
|
+
noClick: true,
|
|
141
|
+
noKeyboard: true,
|
|
142
|
+
onDrop: (files) => {
|
|
143
|
+
if (files.length > 0) onAdd(files);
|
|
144
|
+
}
|
|
145
|
+
});
|
|
146
|
+
const sensors = useSensors(useSensor(MouseSensor, { activationConstraint: { distance: 4 } }), useSensor(TouchSensor, { activationConstraint: {
|
|
147
|
+
delay: 200,
|
|
148
|
+
tolerance: 6
|
|
149
|
+
} }), useSensor(KeyboardSensor, { coordinateGetter: sortableKeyboardCoordinates }));
|
|
150
|
+
useEffect(() => {
|
|
151
|
+
const want = pendingRef.current;
|
|
152
|
+
const root = rootRef.current;
|
|
153
|
+
if (!want || !root) return;
|
|
154
|
+
const now = orderKey(items.map((i) => i.id));
|
|
155
|
+
if (now === want.before) return;
|
|
156
|
+
pendingRef.current = null;
|
|
157
|
+
if (now !== want.expected) return;
|
|
158
|
+
if (statusRef.current) statusRef.current.textContent = want.message;
|
|
159
|
+
const focused = document.activeElement;
|
|
160
|
+
if (focused && focused !== document.body && !root.contains(focused)) return;
|
|
161
|
+
const tile = Array.from(root.querySelectorAll("[data-slot=\"media-grid-item\"]")).find((el) => el.dataset.mediaId === want.id);
|
|
162
|
+
for (const action of want.actions) {
|
|
163
|
+
const target = tile?.querySelector(`button[data-media-action="${action}"]`);
|
|
164
|
+
if (target && !target.disabled) {
|
|
165
|
+
target.focus();
|
|
166
|
+
return;
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
root.querySelector("button[data-media-action=\"add\"]")?.focus();
|
|
170
|
+
}, [items, rootRef]);
|
|
171
|
+
const altOf = (id) => items.find((i) => i.id === id)?.alt ?? "";
|
|
172
|
+
const positionOf = (id) => formatCount(ids.indexOf(String(id)) + 1);
|
|
173
|
+
const announcements = {
|
|
174
|
+
onDragStart: ({ active }) => `Picked up ${altOf(active.id)}, position ${positionOf(active.id)} of ${total}`,
|
|
175
|
+
onDragOver: ({ active, over }) => over ? `${altOf(active.id)} is over position ${positionOf(over.id)} of ${total}` : `${altOf(active.id)} is not over a position`,
|
|
176
|
+
onDragEnd: ({ active, over }) => over ? `${altOf(active.id)} dropped at position ${positionOf(over.id)} of ${total}` : `${altOf(active.id)} dropped`,
|
|
177
|
+
onDragCancel: ({ active }) => `Moving ${altOf(active.id)} cancelled`
|
|
178
|
+
};
|
|
179
|
+
const onAction = (action, index) => {
|
|
180
|
+
const item = items[index];
|
|
181
|
+
if (!item) return;
|
|
182
|
+
const before = orderKey(ids);
|
|
183
|
+
if (action === "remove") {
|
|
184
|
+
const next = ids.filter((id) => id !== item.id);
|
|
185
|
+
pendingRef.current = {
|
|
186
|
+
before,
|
|
187
|
+
expected: orderKey(next),
|
|
188
|
+
message: `${item.alt} removed`,
|
|
189
|
+
id: (items[index + 1] ?? items[index - 1])?.id ?? null,
|
|
190
|
+
actions: ["remove"]
|
|
191
|
+
};
|
|
192
|
+
onRemove(item.id);
|
|
193
|
+
return;
|
|
194
|
+
}
|
|
195
|
+
if (action === "cover") {
|
|
196
|
+
const next = arrayMove(ids, index, 0);
|
|
197
|
+
pendingRef.current = {
|
|
198
|
+
before,
|
|
199
|
+
expected: orderKey(next),
|
|
200
|
+
message: `${item.alt} is now the cover`,
|
|
201
|
+
id: item.id,
|
|
202
|
+
actions: ["right", "remove"]
|
|
203
|
+
};
|
|
204
|
+
onReorder(next);
|
|
205
|
+
onMakeCover?.(item.id);
|
|
206
|
+
return;
|
|
207
|
+
}
|
|
208
|
+
const to = action === "left" ? index - 1 : index + 1;
|
|
209
|
+
if (to < 0 || to >= items.length) return;
|
|
210
|
+
const next = arrayMove(ids, index, to);
|
|
211
|
+
pendingRef.current = {
|
|
212
|
+
before,
|
|
213
|
+
expected: orderKey(next),
|
|
214
|
+
message: `${item.alt} moved to position ${formatCount(to + 1)} of ${total}`,
|
|
215
|
+
id: item.id,
|
|
216
|
+
actions: action === "left" ? [
|
|
217
|
+
"left",
|
|
218
|
+
"right",
|
|
219
|
+
"remove"
|
|
220
|
+
] : [
|
|
221
|
+
"right",
|
|
222
|
+
"left",
|
|
223
|
+
"remove"
|
|
224
|
+
]
|
|
225
|
+
};
|
|
226
|
+
onReorder(next);
|
|
227
|
+
};
|
|
228
|
+
const onDragEnd = ({ active, over }) => {
|
|
229
|
+
setActiveId(null);
|
|
230
|
+
if (!over || active.id === over.id) return;
|
|
231
|
+
const from = ids.indexOf(String(active.id));
|
|
232
|
+
const to = ids.indexOf(String(over.id));
|
|
233
|
+
if (from < 0 || to < 0) return;
|
|
234
|
+
onReorder(arrayMove(ids, from, to));
|
|
235
|
+
};
|
|
236
|
+
const active = items.find((i) => i.id === activeId);
|
|
237
|
+
return /* @__PURE__ */ jsxs("div", {
|
|
238
|
+
...getRootProps({ role: "group" }),
|
|
239
|
+
...rest,
|
|
240
|
+
"aria-label": labelledBy ? ariaLabel : ariaLabel ?? "Media",
|
|
241
|
+
"aria-labelledby": labelledBy,
|
|
242
|
+
"data-slot": "media-grid",
|
|
243
|
+
className: "rt:flex rt:flex-col",
|
|
244
|
+
children: [
|
|
245
|
+
/* @__PURE__ */ jsx("input", { ...getInputProps() }),
|
|
246
|
+
/* @__PURE__ */ jsxs(DndContext, {
|
|
247
|
+
sensors,
|
|
248
|
+
collisionDetection: closestCenter,
|
|
249
|
+
accessibility: {
|
|
250
|
+
announcements,
|
|
251
|
+
screenReaderInstructions: INSTRUCTIONS
|
|
252
|
+
},
|
|
253
|
+
onDragStart: ({ active: dragged }) => setActiveId(String(dragged.id)),
|
|
254
|
+
onDragEnd,
|
|
255
|
+
onDragCancel: () => setActiveId(null),
|
|
256
|
+
children: [/* @__PURE__ */ jsx(SortableContext, {
|
|
257
|
+
items: ids,
|
|
258
|
+
strategy: rectSortingStrategy,
|
|
259
|
+
children: /* @__PURE__ */ jsxs("ul", {
|
|
260
|
+
className: "rt:flex rt:flex-wrap rt:gap-2",
|
|
261
|
+
children: [items.map((item, index) => /* @__PURE__ */ jsx(MediaTile, {
|
|
262
|
+
item,
|
|
263
|
+
index,
|
|
264
|
+
last: index === items.length - 1,
|
|
265
|
+
disabled,
|
|
266
|
+
onAction
|
|
267
|
+
}, item.id)), /* @__PURE__ */ jsx("li", {
|
|
268
|
+
className: "rt:flex",
|
|
269
|
+
children: /* @__PURE__ */ jsx("button", {
|
|
270
|
+
type: "button",
|
|
271
|
+
"aria-label": addLabel,
|
|
272
|
+
"data-media-action": "add",
|
|
273
|
+
"data-drag-active": isDragActive || void 0,
|
|
274
|
+
disabled,
|
|
275
|
+
onClick: () => open(),
|
|
276
|
+
className: "rt:flex rt:size-24 rt:cursor-pointer rt:items-center rt:justify-center rt:rounded-lg rt:border rt:border-dashed rt:border-border-strong rt:bg-background rt:text-icon rt:transition-colors rt:hover:bg-muted rt:focus-visible:focus-ring rt:data-drag-active:border-primary rt:data-drag-active:bg-primary-tint rt:disabled:pointer-events-none rt:disabled:cursor-not-allowed rt:disabled:opacity-50",
|
|
277
|
+
children: /* @__PURE__ */ jsx(Icon, {
|
|
278
|
+
name: "plus",
|
|
279
|
+
size: 16
|
|
280
|
+
})
|
|
281
|
+
})
|
|
282
|
+
})]
|
|
283
|
+
})
|
|
284
|
+
}), /* @__PURE__ */ jsx(DragOverlay, {
|
|
285
|
+
dropAnimation: null,
|
|
286
|
+
children: active ? /* @__PURE__ */ jsx("img", {
|
|
287
|
+
src: active.src,
|
|
288
|
+
alt: "",
|
|
289
|
+
draggable: false,
|
|
290
|
+
className: "rt:size-24 rt:cursor-grabbing rt:rounded-lg rt:object-cover rt:shadow-ring-subtle"
|
|
291
|
+
}) : null
|
|
292
|
+
})]
|
|
293
|
+
}),
|
|
294
|
+
/* @__PURE__ */ jsx("div", {
|
|
295
|
+
ref: statusRef,
|
|
296
|
+
"data-slot": "media-grid-status",
|
|
297
|
+
role: "status",
|
|
298
|
+
className: "rt:sr-only"
|
|
299
|
+
})
|
|
300
|
+
]
|
|
301
|
+
});
|
|
302
|
+
}
|
|
303
|
+
export { MediaGrid };
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { MediaGrid } from "./MediaGrid.js";
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import { JSX, ReactNode } from "react";
|
|
2
|
+
/** One metric of the strip. */
|
|
3
|
+
export type MetricSegment = {
|
|
4
|
+
/** A stable key. */
|
|
5
|
+
id: string;
|
|
6
|
+
/** The metric's name. */
|
|
7
|
+
title: string;
|
|
8
|
+
/** A sentence that explains the title; the title gets a dotted underline and opens it. */
|
|
9
|
+
definition?: ReactNode;
|
|
10
|
+
/** The value. A number is formatted by format; a string is shown as it is; null or undefined shows No data. */
|
|
11
|
+
value?: number | string | null;
|
|
12
|
+
/** How a number is written: money (rupees, Indian grouping) or count (123456). Defaults to count. */
|
|
13
|
+
format?: 'money' | 'count';
|
|
14
|
+
};
|
|
15
|
+
/** Props of MetricStrip. */
|
|
16
|
+
export type MetricStripProps = {
|
|
17
|
+
/** The metrics, left to right. */
|
|
18
|
+
segments: MetricSegment[];
|
|
19
|
+
/** The accessible name of the strip. */
|
|
20
|
+
label?: string;
|
|
21
|
+
};
|
|
22
|
+
/** A row of metrics in one hairline container: each segment is a title, optionally with a definition, over its value. */
|
|
23
|
+
export declare function MetricStrip({ segments, label }: MetricStripProps): JSX.Element;
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import { formatCount, formatMoney } from "../../lib/format.js";
|
|
2
|
+
import { Definition } from "../definition/Definition.js";
|
|
3
|
+
import { jsx, jsxs } from "react/jsx-runtime";
|
|
4
|
+
// src/components/metric-strip/MetricStrip.tsx
|
|
5
|
+
function show(segment) {
|
|
6
|
+
const { value, format = "count" } = segment;
|
|
7
|
+
if (value === null || value === void 0) return "No data";
|
|
8
|
+
if (typeof value === "string") return value;
|
|
9
|
+
return format === "money" ? formatMoney(value) : formatCount(value);
|
|
10
|
+
}
|
|
11
|
+
/** A row of metrics in one hairline container: each segment is a title, optionally with a definition, over its value. */
|
|
12
|
+
function MetricStrip({ segments, label }) {
|
|
13
|
+
return /* @__PURE__ */ jsx("div", {
|
|
14
|
+
role: "group",
|
|
15
|
+
"aria-label": label,
|
|
16
|
+
className: "rt:overflow-hidden rt:rounded-md rt:border rt:border-border rt:bg-background",
|
|
17
|
+
children: /* @__PURE__ */ jsx("div", {
|
|
18
|
+
className: "rt:-mr-px rt:-mb-px rt:flex rt:flex-wrap",
|
|
19
|
+
children: segments.map((segment) => {
|
|
20
|
+
const empty = segment.value === null || segment.value === void 0;
|
|
21
|
+
return /* @__PURE__ */ jsxs("div", {
|
|
22
|
+
"data-segment": "",
|
|
23
|
+
className: "rt:flex rt:min-w-0 rt:flex-auto rt:flex-col rt:gap-1 rt:border-r rt:border-b rt:border-divider rt:p-6",
|
|
24
|
+
children: [/* @__PURE__ */ jsx("span", {
|
|
25
|
+
className: "rt:text-b1 rt:text-muted-foreground",
|
|
26
|
+
children: segment.definition !== void 0 ? /* @__PURE__ */ jsx(Definition, {
|
|
27
|
+
definition: segment.definition,
|
|
28
|
+
children: segment.title
|
|
29
|
+
}) : segment.title
|
|
30
|
+
}), /* @__PURE__ */ jsx("span", {
|
|
31
|
+
className: empty ? "rt:text-b2 rt:text-muted-foreground" : "rt:text-s1 rt:text-foreground-strong",
|
|
32
|
+
children: show(segment)
|
|
33
|
+
})]
|
|
34
|
+
}, segment.id);
|
|
35
|
+
})
|
|
36
|
+
})
|
|
37
|
+
});
|
|
38
|
+
}
|
|
39
|
+
export { MetricStrip };
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { MetricStrip } from "./MetricStrip.js";
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import { InputGroupSize } from "../input-group/InputGroup.js";
|
|
2
|
+
import "../input-group/index.js";
|
|
3
|
+
import { InputHTMLAttributes, JSX, Ref } from "react";
|
|
4
|
+
/**
|
|
5
|
+
* The props of MoneyInput: the HTML attributes of an input, except className, style, size and the ones the field sets
|
|
6
|
+
* itself (type, inputMode, step, onChange), with the amount in integer paise in and out. Mark an invalid field with
|
|
7
|
+
* aria-invalid, which draws the error ring around the whole group.
|
|
8
|
+
*/
|
|
9
|
+
export type MoneyInputProps = Omit<InputHTMLAttributes<HTMLInputElement>, 'className' | 'style' | 'size' | 'type' | 'inputMode' | 'step' | 'value' | 'defaultValue' | 'onChange' | 'min' | 'max'> & {
|
|
10
|
+
/** The amount in integer paise (12345650 is 1,23,456 rupees and 50 paise), or null for an empty field. Pass it to control the field. */
|
|
11
|
+
value?: number | null;
|
|
12
|
+
/** The starting amount in integer paise when the field is not controlled. Defaults to empty. */
|
|
13
|
+
defaultValue?: number | null;
|
|
14
|
+
/** Called with the amount in integer paise as it is typed, pasted or cleared (null when empty). */
|
|
15
|
+
onValueChange?: (paise: number | null) => void;
|
|
16
|
+
/** The smallest amount in integer paise; the field moves a smaller one up to it on blur. Leave it out to allow negatives. */
|
|
17
|
+
min?: number;
|
|
18
|
+
/** The largest amount in integer paise; the field moves a larger one down to it on blur. */
|
|
19
|
+
max?: number;
|
|
20
|
+
/** The height in px: 36 for forms, 32 for prices inside a table. Defaults to 36. */
|
|
21
|
+
size?: InputGroupSize;
|
|
22
|
+
/** Receives the input element. */
|
|
23
|
+
ref?: Ref<HTMLInputElement>;
|
|
24
|
+
};
|
|
25
|
+
/**
|
|
26
|
+
* A money field: the currency symbol before the amount, inside one ring. It takes and gives integer paise, shows the
|
|
27
|
+
* amount grouped as India groups it with two decimals (1,23,456.50) once the field loses focus, and reads a pasted
|
|
28
|
+
* amount written with the currency symbol or with either grouping. Under a name, a form submits paise.
|
|
29
|
+
*/
|
|
30
|
+
export declare function MoneyInput({ value, defaultValue, onValueChange, min, max, size, id, name, disabled, readOnly, required, onPaste, 'aria-describedby': describedBy, ref, ...rest }: MoneyInputProps): JSX.Element;
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
import { FORMAT, MONEY_NUMBER_FORMAT, amountToPaise, currencySymbol, formatPaiseForEditing, paiseToAmount, parseMoney } from "../../lib/format.js";
|
|
2
|
+
import { InputGroup, InputGroupAddon, InputGroupInput, InputGroupText } from "../input-group/InputGroup.js";
|
|
3
|
+
import { jsx, jsxs } from "react/jsx-runtime";
|
|
4
|
+
import { useId, useState } from "react";
|
|
5
|
+
import { NumberField } from "@base-ui/react/number-field";
|
|
6
|
+
// src/components/money-input/MoneyInput.tsx
|
|
7
|
+
const amountOf = (paise) => paise === void 0 ? void 0 : paiseToAmount(paise);
|
|
8
|
+
/**
|
|
9
|
+
* Writes text into the input the way typing does (the value setter React tracks, then an input event), so NumberField
|
|
10
|
+
* reads it with its own change handler and keeps its text and value in step.
|
|
11
|
+
*/
|
|
12
|
+
function typeInto(input, text) {
|
|
13
|
+
Object.getOwnPropertyDescriptor(HTMLInputElement.prototype, "value")?.set?.call(input, text);
|
|
14
|
+
input.dispatchEvent(new Event("input", { bubbles: true }));
|
|
15
|
+
}
|
|
16
|
+
/**
|
|
17
|
+
* A money field: the currency symbol before the amount, inside one ring. It takes and gives integer paise, shows the
|
|
18
|
+
* amount grouped as India groups it with two decimals (1,23,456.50) once the field loses focus, and reads a pasted
|
|
19
|
+
* amount written with the currency symbol or with either grouping. Under a name, a form submits paise.
|
|
20
|
+
*/
|
|
21
|
+
function MoneyInput({ value, defaultValue = null, onValueChange, min, max, size = 36, id, name, disabled, readOnly, required, onPaste, "aria-describedby": describedBy, ref, ...rest }) {
|
|
22
|
+
const [ownPaise, setOwnPaise] = useState(defaultValue);
|
|
23
|
+
const paise = value === void 0 ? ownPaise : value;
|
|
24
|
+
const symbolId = useId();
|
|
25
|
+
const change = (amount) => {
|
|
26
|
+
const next = amount === null ? null : amountToPaise(amount);
|
|
27
|
+
if (value === void 0) setOwnPaise(next);
|
|
28
|
+
onValueChange?.(next);
|
|
29
|
+
};
|
|
30
|
+
const paste = (event) => {
|
|
31
|
+
onPaste?.(event);
|
|
32
|
+
const input = event.currentTarget;
|
|
33
|
+
if (event.defaultPrevented || input.readOnly) return;
|
|
34
|
+
event.preventDefault();
|
|
35
|
+
const { value: text, selectionStart, selectionEnd } = input;
|
|
36
|
+
const pasted = event.clipboardData.getData("text/plain");
|
|
37
|
+
const next = parseMoney(text.slice(0, selectionStart ?? text.length) + pasted + text.slice(selectionEnd ?? text.length));
|
|
38
|
+
if (next !== null) typeInto(input, formatPaiseForEditing(next));
|
|
39
|
+
};
|
|
40
|
+
return /* @__PURE__ */ jsxs(NumberField.Root, {
|
|
41
|
+
id,
|
|
42
|
+
value: paise === null ? null : paiseToAmount(paise),
|
|
43
|
+
onValueChange: change,
|
|
44
|
+
locale: FORMAT.locale,
|
|
45
|
+
format: MONEY_NUMBER_FORMAT,
|
|
46
|
+
min: amountOf(min),
|
|
47
|
+
max: amountOf(max),
|
|
48
|
+
disabled,
|
|
49
|
+
readOnly,
|
|
50
|
+
required,
|
|
51
|
+
children: [/* @__PURE__ */ jsxs(InputGroup, {
|
|
52
|
+
size,
|
|
53
|
+
"aria-disabled": disabled || void 0,
|
|
54
|
+
children: [/* @__PURE__ */ jsx(InputGroupAddon, { children: /* @__PURE__ */ jsx(InputGroupText, {
|
|
55
|
+
id: symbolId,
|
|
56
|
+
children: currencySymbol()
|
|
57
|
+
}) }), /* @__PURE__ */ jsx(NumberField.Input, {
|
|
58
|
+
render: /* @__PURE__ */ jsx(InputGroupInput, {}),
|
|
59
|
+
ref,
|
|
60
|
+
"aria-describedby": describedBy === void 0 ? symbolId : `${symbolId} ${describedBy}`,
|
|
61
|
+
onPaste: paste,
|
|
62
|
+
...rest
|
|
63
|
+
})]
|
|
64
|
+
}), name !== void 0 && /* @__PURE__ */ jsx("input", {
|
|
65
|
+
type: "hidden",
|
|
66
|
+
name,
|
|
67
|
+
value: paise ?? "",
|
|
68
|
+
disabled
|
|
69
|
+
})]
|
|
70
|
+
});
|
|
71
|
+
}
|
|
72
|
+
export { MoneyInput };
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { MoneyInput } from "./MoneyInput.js";
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import { StatusPillTone } from "../status-pill/StatusPill.js";
|
|
2
|
+
import "../status-pill/index.js";
|
|
3
|
+
import { JSX, ReactNode } from "react";
|
|
4
|
+
/** One entry of the overflow menu. */
|
|
5
|
+
export type PageHeaderMenuItem = {
|
|
6
|
+
/** The text of the item. */
|
|
7
|
+
label: string;
|
|
8
|
+
/** Runs when the item is chosen. */
|
|
9
|
+
onSelect: () => void;
|
|
10
|
+
/** Draws the item in the danger colour, for an action that deletes. */
|
|
11
|
+
destructive?: boolean;
|
|
12
|
+
};
|
|
13
|
+
/** Props of PageHeader. */
|
|
14
|
+
export type PageHeaderProps = {
|
|
15
|
+
/** The page title, shown as the level 1 heading. */
|
|
16
|
+
title: string;
|
|
17
|
+
/** An icon or logo shown before the title. */
|
|
18
|
+
icon?: ReactNode;
|
|
19
|
+
/** The state of the record, one word, beside the title. */
|
|
20
|
+
status?: {
|
|
21
|
+
label: string;
|
|
22
|
+
tone?: StatusPillTone;
|
|
23
|
+
};
|
|
24
|
+
/** Where the back link goes. The link shows only with this. On a router, LinkProvider draws it with the app's own link. */
|
|
25
|
+
backHref?: string;
|
|
26
|
+
/** The text of the back link, such as the parent page's name. Defaults to Back. */
|
|
27
|
+
backLabel?: string;
|
|
28
|
+
/** Buttons on the right, such as Export and Add product. */
|
|
29
|
+
actions?: ReactNode;
|
|
30
|
+
/** Entries of the overflow menu. The More actions button shows only when there is at least one. */
|
|
31
|
+
menuItems?: PageHeaderMenuItem[];
|
|
32
|
+
};
|
|
33
|
+
/**
|
|
34
|
+
* The top of a page: an optional back link, then an icon, the title and a status pill on the left, and the actions with
|
|
35
|
+
* an overflow menu on the right. It wraps on a phone.
|
|
36
|
+
*/
|
|
37
|
+
export declare function PageHeader({ title, icon, status, backHref, backLabel, actions, menuItems }: PageHeaderProps): JSX.Element;
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
import { Icon } from "../icon/Icon.js";
|
|
2
|
+
import { Button } from "../button/Button.js";
|
|
3
|
+
import { RatioLink } from "../link-provider/RatioLink.js";
|
|
4
|
+
import { DropdownMenu, DropdownMenuContent, DropdownMenuItem, DropdownMenuTrigger } from "../dropdown-menu/DropdownMenu.js";
|
|
5
|
+
import { StatusPill } from "../status-pill/StatusPill.js";
|
|
6
|
+
import { jsx, jsxs } from "react/jsx-runtime";
|
|
7
|
+
// src/components/page-header/PageHeader.tsx
|
|
8
|
+
/**
|
|
9
|
+
* The top of a page: an optional back link, then an icon, the title and a status pill on the left, and the actions with
|
|
10
|
+
* an overflow menu on the right. It wraps on a phone.
|
|
11
|
+
*/
|
|
12
|
+
function PageHeader({ title, icon, status, backHref, backLabel = "Back", actions, menuItems }) {
|
|
13
|
+
const hasMenu = menuItems !== void 0 && menuItems.length > 0;
|
|
14
|
+
return /* @__PURE__ */ jsxs("header", {
|
|
15
|
+
className: "rt:flex rt:flex-col rt:gap-2",
|
|
16
|
+
children: [backHref !== void 0 && /* @__PURE__ */ jsxs(RatioLink, {
|
|
17
|
+
href: backHref,
|
|
18
|
+
className: "rt:inline-flex rt:w-fit rt:items-center rt:gap-1 rt:rounded-sm rt:text-b1 rt:text-muted-foreground rt:transition-colors rt:hover:text-foreground rt:focus-visible:focus-ring",
|
|
19
|
+
children: [/* @__PURE__ */ jsx(Icon, {
|
|
20
|
+
name: "chevron-left",
|
|
21
|
+
size: 8
|
|
22
|
+
}), backLabel]
|
|
23
|
+
}), /* @__PURE__ */ jsxs("div", {
|
|
24
|
+
className: "rt:flex rt:flex-wrap rt:items-center rt:justify-between rt:gap-x-3 rt:gap-y-2",
|
|
25
|
+
children: [/* @__PURE__ */ jsxs("div", {
|
|
26
|
+
className: "rt:flex rt:min-w-0 rt:items-center rt:gap-3",
|
|
27
|
+
children: [
|
|
28
|
+
icon !== void 0 && /* @__PURE__ */ jsx("span", {
|
|
29
|
+
className: "rt:flex rt:shrink-0 rt:items-center",
|
|
30
|
+
children: icon
|
|
31
|
+
}),
|
|
32
|
+
/* @__PURE__ */ jsx("h1", {
|
|
33
|
+
className: "rt:m-0 rt:min-w-0 rt:truncate rt:text-h5 rt:text-foreground-strong",
|
|
34
|
+
children: title
|
|
35
|
+
}),
|
|
36
|
+
status !== void 0 && /* @__PURE__ */ jsx(StatusPill, {
|
|
37
|
+
tone: status.tone,
|
|
38
|
+
children: status.label
|
|
39
|
+
})
|
|
40
|
+
]
|
|
41
|
+
}), (actions !== void 0 || hasMenu) && /* @__PURE__ */ jsxs("div", {
|
|
42
|
+
className: "rt:flex rt:flex-wrap rt:items-center rt:gap-2",
|
|
43
|
+
children: [actions, hasMenu && /* @__PURE__ */ jsxs(DropdownMenu, { children: [/* @__PURE__ */ jsxs(DropdownMenuTrigger, {
|
|
44
|
+
render: /* @__PURE__ */ jsx(Button, { variant: "secondary" }),
|
|
45
|
+
children: ["More actions", /* @__PURE__ */ jsx(Icon, {
|
|
46
|
+
name: "nav-caret-down",
|
|
47
|
+
size: 8
|
|
48
|
+
})]
|
|
49
|
+
}), /* @__PURE__ */ jsx(DropdownMenuContent, {
|
|
50
|
+
side: "bottom",
|
|
51
|
+
align: "end",
|
|
52
|
+
children: menuItems.map((item) => /* @__PURE__ */ jsx(DropdownMenuItem, {
|
|
53
|
+
variant: item.destructive ? "destructive" : void 0,
|
|
54
|
+
onClick: item.onSelect,
|
|
55
|
+
children: item.label
|
|
56
|
+
}, item.label))
|
|
57
|
+
})] })]
|
|
58
|
+
})]
|
|
59
|
+
})]
|
|
60
|
+
});
|
|
61
|
+
}
|
|
62
|
+
export { PageHeader };
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { PageHeader } from "./PageHeader.js";
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { ComponentProps, JSX } from "react";
|
|
2
|
+
/** The props of Pagination: the attributes of a nav element, except className, style and children, and the page being shown. */
|
|
3
|
+
export type PaginationProps = Omit<ComponentProps<'nav'>, 'className' | 'style' | 'children'> & {
|
|
4
|
+
/** The page being shown, counting from 1. */
|
|
5
|
+
page: number;
|
|
6
|
+
/** How many results one page holds. */
|
|
7
|
+
pageSize: number;
|
|
8
|
+
/** How many results there are in all, across every page. */
|
|
9
|
+
total: number;
|
|
10
|
+
/** Called with the page to show when Previous or Next is pressed. */
|
|
11
|
+
onPageChange: (page: number) => void;
|
|
12
|
+
};
|
|
13
|
+
/**
|
|
14
|
+
* The footer under a table: Previous and Next arrows and the range on show, written "1 – 20 of 84 results". An arrow is
|
|
15
|
+
* dimmed to 50% and disabled (aria-disabled, still focusable) when there is no page that way. The parent owns the page: it passes `page` and updates it in
|
|
16
|
+
* `onPageChange`.
|
|
17
|
+
*/
|
|
18
|
+
export declare function Pagination({ page, pageSize, total, onPageChange, 'aria-label': label, ...rest }: PaginationProps): JSX.Element;
|