@ratio-ui/core 0.1.1 → 0.2.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 +1719 -43
- 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/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/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/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/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 +53 -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-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/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/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/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/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/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/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/index.d.ts +65 -1
- package/dist/index.js +35 -2
- package/dist/lib/cn.js +32 -27
- package/dist/lib/format.d.ts +31 -0
- package/dist/lib/format.js +65 -0
- package/dist/styles.css +1 -1
- package/package.json +128 -3
|
@@ -0,0 +1,144 @@
|
|
|
1
|
+
import { Icon } from "../icon/Icon.js";
|
|
2
|
+
import { formatCount } from "../../lib/format.js";
|
|
3
|
+
import { InputGroup, InputGroupAddon, InputGroupShortcut } from "../input-group/InputGroup.js";
|
|
4
|
+
import { Kbd, KbdGroup } from "../kbd/Kbd.js";
|
|
5
|
+
import { useEffect, useRef, useState } from "react";
|
|
6
|
+
import { Fragment, jsx, jsxs } from "react/jsx-runtime";
|
|
7
|
+
import { Autocomplete } from "@base-ui/react/autocomplete";
|
|
8
|
+
// src/components/global-search/GlobalSearch.tsx
|
|
9
|
+
/**
|
|
10
|
+
* The search in the top bar. Typing opens a panel of grouped results that the app supplies, an empty field shows recent
|
|
11
|
+
* items, and the arrow keys, Enter and Escape work the list. Cmd+K or Ctrl+K focuses the field from anywhere.
|
|
12
|
+
*/
|
|
13
|
+
function GlobalSearch({ label, value, onValueChange, groups, recent = [], onSelect, onShowMore, placeholder = "Search", emptyText = "No results" }) {
|
|
14
|
+
const [open, setOpen] = useState(false);
|
|
15
|
+
const inputRef = useRef(null);
|
|
16
|
+
useEffect(() => {
|
|
17
|
+
const onKey = (event) => {
|
|
18
|
+
if (!event.defaultPrevented && (event.metaKey || event.ctrlKey) && event.key.toLowerCase() === "k") {
|
|
19
|
+
event.preventDefault();
|
|
20
|
+
inputRef.current?.focus();
|
|
21
|
+
setOpen(true);
|
|
22
|
+
}
|
|
23
|
+
};
|
|
24
|
+
document.addEventListener("keydown", onKey);
|
|
25
|
+
return () => document.removeEventListener("keydown", onKey);
|
|
26
|
+
}, []);
|
|
27
|
+
const typed = value !== "";
|
|
28
|
+
const rows = typed ? groups.filter((group) => group.items.length > 0).map((group) => ({
|
|
29
|
+
id: group.id,
|
|
30
|
+
label: group.label,
|
|
31
|
+
items: group.total !== void 0 && group.total > group.items.length ? [...group.items, {
|
|
32
|
+
id: `${group.id}:more`,
|
|
33
|
+
label: `+${group.total - group.items.length} more`,
|
|
34
|
+
more: group
|
|
35
|
+
}] : group.items
|
|
36
|
+
})) : recent.length > 0 ? [{
|
|
37
|
+
id: "recent",
|
|
38
|
+
label: "Recent",
|
|
39
|
+
items: recent
|
|
40
|
+
}] : [];
|
|
41
|
+
const showEmpty = typed && rows.length === 0;
|
|
42
|
+
const found = typed ? groups.reduce((sum, group) => sum + (group.items.length > 0 ? Math.max(group.total ?? 0, group.items.length) : 0), 0) : 0;
|
|
43
|
+
const status = found > 0 ? `${formatCount(found)} ${found === 1 ? "result" : "results"}` : "";
|
|
44
|
+
const showPanel = open && (rows.length > 0 || showEmpty);
|
|
45
|
+
const choose = (row) => {
|
|
46
|
+
setOpen(false);
|
|
47
|
+
if (row.more !== void 0) onShowMore?.(row.more);
|
|
48
|
+
else onSelect?.(row);
|
|
49
|
+
};
|
|
50
|
+
return /* @__PURE__ */ jsxs(Autocomplete.Root, {
|
|
51
|
+
items: rows,
|
|
52
|
+
filter: null,
|
|
53
|
+
value,
|
|
54
|
+
onValueChange,
|
|
55
|
+
open: showPanel,
|
|
56
|
+
onOpenChange: setOpen,
|
|
57
|
+
itemToStringValue: (item) => item.more === void 0 ? item.label : value,
|
|
58
|
+
children: [/* @__PURE__ */ jsxs(InputGroup, { children: [
|
|
59
|
+
/* @__PURE__ */ jsx(InputGroupAddon, { children: /* @__PURE__ */ jsx(Icon, {
|
|
60
|
+
name: "search",
|
|
61
|
+
tone: "icon"
|
|
62
|
+
}) }),
|
|
63
|
+
/* @__PURE__ */ jsx(Autocomplete.Input, {
|
|
64
|
+
ref: inputRef,
|
|
65
|
+
"aria-label": label,
|
|
66
|
+
placeholder,
|
|
67
|
+
autoComplete: "off",
|
|
68
|
+
onFocus: () => setOpen(true),
|
|
69
|
+
className: "rt:h-full rt:min-w-0 rt:flex-1 rt:bg-transparent rt:text-b3 rt:text-foreground rt:placeholder:text-muted-foreground rt:outline-none"
|
|
70
|
+
}),
|
|
71
|
+
/* @__PURE__ */ jsx(InputGroupAddon, {
|
|
72
|
+
align: "end",
|
|
73
|
+
children: /* @__PURE__ */ jsx(InputGroupShortcut, {
|
|
74
|
+
"aria-hidden": "true",
|
|
75
|
+
children: "⌘K"
|
|
76
|
+
})
|
|
77
|
+
})
|
|
78
|
+
] }), /* @__PURE__ */ jsx(Autocomplete.Portal, { children: /* @__PURE__ */ jsx(Autocomplete.Positioner, {
|
|
79
|
+
align: "start",
|
|
80
|
+
sideOffset: 4,
|
|
81
|
+
className: "rt:isolate rt:z-50",
|
|
82
|
+
children: /* @__PURE__ */ jsxs(Autocomplete.Popup, {
|
|
83
|
+
"data-slot": "global-search-panel",
|
|
84
|
+
className: "rt:flex rt:w-120 rt:max-w-full rt:flex-col rt:rounded-lg rt:border rt:border-border rt:bg-popover rt:text-b3 rt:text-popover-foreground rt:outline-hidden",
|
|
85
|
+
children: [
|
|
86
|
+
/* @__PURE__ */ jsx(Autocomplete.Empty, {
|
|
87
|
+
className: "rt:text-muted-foreground rt:not-empty:p-3",
|
|
88
|
+
children: showEmpty ? emptyText : null
|
|
89
|
+
}),
|
|
90
|
+
/* @__PURE__ */ jsx(Autocomplete.Status, {
|
|
91
|
+
className: "rt:sr-only",
|
|
92
|
+
children: status
|
|
93
|
+
}),
|
|
94
|
+
/* @__PURE__ */ jsx(Autocomplete.List, {
|
|
95
|
+
className: "rt:max-h-80 rt:overflow-y-auto rt:p-1 rt:outline-none",
|
|
96
|
+
children: (group) => /* @__PURE__ */ jsxs(Autocomplete.Group, {
|
|
97
|
+
items: group.items,
|
|
98
|
+
className: "rt:flex rt:flex-col",
|
|
99
|
+
children: [/* @__PURE__ */ jsx(Autocomplete.GroupLabel, {
|
|
100
|
+
className: "rt:px-2 rt:py-1.5 rt:text-c1 rt:text-muted-foreground",
|
|
101
|
+
children: group.label
|
|
102
|
+
}), /* @__PURE__ */ jsx(Autocomplete.Collection, { children: (row) => /* @__PURE__ */ jsxs(Autocomplete.Item, {
|
|
103
|
+
value: row,
|
|
104
|
+
onClick: () => choose(row),
|
|
105
|
+
className: "rt:flex rt:cursor-default rt:items-center rt:gap-2 rt:rounded-md rt:px-2 rt:py-1.5 rt:outline-none rt:select-none rt:data-highlighted:bg-muted",
|
|
106
|
+
children: [row.icon !== void 0 ? /* @__PURE__ */ jsx(Icon, {
|
|
107
|
+
name: row.icon,
|
|
108
|
+
tone: "icon"
|
|
109
|
+
}) : null, row.more !== void 0 ? /* @__PURE__ */ jsx("span", {
|
|
110
|
+
className: "rt:text-primary",
|
|
111
|
+
children: row.label
|
|
112
|
+
}) : /* @__PURE__ */ jsxs(Fragment, { children: [/* @__PURE__ */ jsx("span", {
|
|
113
|
+
className: "rt:truncate",
|
|
114
|
+
children: row.label
|
|
115
|
+
}), row.description !== void 0 ? /* @__PURE__ */ jsx("span", {
|
|
116
|
+
className: "rt:ml-auto rt:truncate rt:text-muted-foreground",
|
|
117
|
+
children: row.description
|
|
118
|
+
}) : null] })]
|
|
119
|
+
}, row.id) })]
|
|
120
|
+
}, group.id)
|
|
121
|
+
}),
|
|
122
|
+
/* @__PURE__ */ jsxs("div", {
|
|
123
|
+
className: "rt:flex rt:items-center rt:gap-3 rt:border-t rt:border-border rt:px-3 rt:py-2 rt:text-c1 rt:text-muted-foreground",
|
|
124
|
+
children: [
|
|
125
|
+
/* @__PURE__ */ jsxs("span", {
|
|
126
|
+
className: "rt:inline-flex rt:items-center rt:gap-1",
|
|
127
|
+
children: [/* @__PURE__ */ jsxs(KbdGroup, { children: [/* @__PURE__ */ jsx(Kbd, { children: "↑" }), /* @__PURE__ */ jsx(Kbd, { children: "↓" })] }), "to move"]
|
|
128
|
+
}),
|
|
129
|
+
/* @__PURE__ */ jsxs("span", {
|
|
130
|
+
className: "rt:inline-flex rt:items-center rt:gap-1",
|
|
131
|
+
children: [/* @__PURE__ */ jsx(Kbd, { children: "↵" }), "to select"]
|
|
132
|
+
}),
|
|
133
|
+
/* @__PURE__ */ jsxs("span", {
|
|
134
|
+
className: "rt:inline-flex rt:items-center rt:gap-1",
|
|
135
|
+
children: [/* @__PURE__ */ jsx(Kbd, { children: "esc" }), "to close"]
|
|
136
|
+
})
|
|
137
|
+
]
|
|
138
|
+
})
|
|
139
|
+
]
|
|
140
|
+
})
|
|
141
|
+
}) })]
|
|
142
|
+
});
|
|
143
|
+
}
|
|
144
|
+
export { GlobalSearch };
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { GlobalSearch } from "./GlobalSearch.js";
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import "@tabler/icons-react";
|
|
2
2
|
/**
|
|
3
3
|
* The name of an icon. The design system's own glyphs come first (plus, search, export, the two pagination chevrons and
|
|
4
|
-
* the five navigation glyphs);
|
|
4
|
+
* the five navigation glyphs); the rest are Tabler icons drawn with a 1.5px line: check, x, alert-triangle, info-circle,
|
|
5
|
+
* the dashboard's navigation glyphs (nav-orders to nav-settings), import, more and menu (the phone menu button).
|
|
5
6
|
*/
|
|
6
|
-
export type IconName = 'plus' | 'search' | 'export' | 'chevron-left' | 'chevron-right' | 'nav-home' | 'nav-shortcut' | 'nav-product' | 'nav-product-2' | 'nav-chevron' | 'nav-caret-down' | 'check' | 'x' | 'alert-triangle' | 'info-circle';
|
|
7
|
+
export type IconName = 'plus' | 'search' | 'export' | 'chevron-left' | 'chevron-right' | 'nav-home' | 'nav-shortcut' | 'nav-product' | 'nav-product-2' | 'nav-chevron' | 'nav-caret-down' | 'check' | 'x' | 'alert-triangle' | 'info-circle' | 'nav-orders' | 'nav-customers' | 'nav-growth' | 'nav-discounts' | 'nav-content' | 'nav-markets' | 'nav-analytics' | 'nav-sales-channels' | 'nav-apps' | 'nav-settings' | 'import' | 'more' | 'menu';
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { CHEVRON_LEFT, CHEVRON_RIGHT, EXPORT, NAV_CHEVRON, NAV_HOME, NAV_PRODUCT, NAV_PRODUCT_2, NAV_SHORTCUT, PLUS, SEARCH } from "./glyphs.js";
|
|
2
|
-
import { IconAlertTriangle, IconCheck, IconInfoCircle, IconX } from "@tabler/icons-react";
|
|
2
|
+
import { IconAlertTriangle, IconApps, IconBuildingStore, IconChartBar, IconCheck, IconDiscount2, IconDots, IconFileImport, IconFileText, IconInfoCircle, IconMenu2, IconReceipt, IconSettings, IconTrendingUp, IconUsers, IconWorld, IconX } from "@tabler/icons-react";
|
|
3
3
|
// src/components/icon/registry.ts
|
|
4
4
|
/**
|
|
5
5
|
* Every icon by name. nav-chevron and nav-caret-down are one glyph under two names, as in the design system, whose
|
|
@@ -65,6 +65,58 @@ const ICONS = {
|
|
|
65
65
|
"info-circle": {
|
|
66
66
|
kind: "tabler",
|
|
67
67
|
component: IconInfoCircle
|
|
68
|
+
},
|
|
69
|
+
"nav-orders": {
|
|
70
|
+
kind: "tabler",
|
|
71
|
+
component: IconReceipt
|
|
72
|
+
},
|
|
73
|
+
"nav-customers": {
|
|
74
|
+
kind: "tabler",
|
|
75
|
+
component: IconUsers
|
|
76
|
+
},
|
|
77
|
+
"nav-growth": {
|
|
78
|
+
kind: "tabler",
|
|
79
|
+
component: IconTrendingUp
|
|
80
|
+
},
|
|
81
|
+
"nav-discounts": {
|
|
82
|
+
kind: "tabler",
|
|
83
|
+
component: IconDiscount2
|
|
84
|
+
},
|
|
85
|
+
"nav-content": {
|
|
86
|
+
kind: "tabler",
|
|
87
|
+
component: IconFileText
|
|
88
|
+
},
|
|
89
|
+
"nav-markets": {
|
|
90
|
+
kind: "tabler",
|
|
91
|
+
component: IconWorld
|
|
92
|
+
},
|
|
93
|
+
"nav-analytics": {
|
|
94
|
+
kind: "tabler",
|
|
95
|
+
component: IconChartBar
|
|
96
|
+
},
|
|
97
|
+
"nav-sales-channels": {
|
|
98
|
+
kind: "tabler",
|
|
99
|
+
component: IconBuildingStore
|
|
100
|
+
},
|
|
101
|
+
"nav-apps": {
|
|
102
|
+
kind: "tabler",
|
|
103
|
+
component: IconApps
|
|
104
|
+
},
|
|
105
|
+
"nav-settings": {
|
|
106
|
+
kind: "tabler",
|
|
107
|
+
component: IconSettings
|
|
108
|
+
},
|
|
109
|
+
import: {
|
|
110
|
+
kind: "tabler",
|
|
111
|
+
component: IconFileImport
|
|
112
|
+
},
|
|
113
|
+
more: {
|
|
114
|
+
kind: "tabler",
|
|
115
|
+
component: IconDots
|
|
116
|
+
},
|
|
117
|
+
menu: {
|
|
118
|
+
kind: "tabler",
|
|
119
|
+
component: IconMenu2
|
|
68
120
|
}
|
|
69
121
|
};
|
|
70
122
|
export { ICONS };
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { InputHTMLAttributes, JSX, Ref } from "react";
|
|
2
|
+
/** The height of the field in px: 36 is the design system's input, 32 the compact one inside tables. */
|
|
3
|
+
export type InputSize = 36 | 32;
|
|
4
|
+
/**
|
|
5
|
+
* The props of Input: the HTML attributes of an input, except className, style and size, with the height of the field.
|
|
6
|
+
* Mark an invalid field with aria-invalid, which draws the error ring in the danger colour, also while focused.
|
|
7
|
+
*/
|
|
8
|
+
export type InputProps = Omit<InputHTMLAttributes<HTMLInputElement>, 'className' | 'style' | 'size'> & {
|
|
9
|
+
/** The height in px: 36 for forms and search, 32 for fields inside a table. Defaults to 36. */
|
|
10
|
+
size?: InputSize;
|
|
11
|
+
/** Receives the input element. */
|
|
12
|
+
ref?: Ref<HTMLInputElement>;
|
|
13
|
+
};
|
|
14
|
+
/**
|
|
15
|
+
* A single-line text field in the design system's look: 8px radius, a 1px ring that turns blue on focus, 14px body type.
|
|
16
|
+
* It takes every attribute of an input (type, inputMode, value, onChange). For a prefix, suffix, icon or shortcut hint,
|
|
17
|
+
* put an InputGroupInput in an InputGroup.
|
|
18
|
+
*/
|
|
19
|
+
export declare function Input({ size, ref, ...rest }: InputProps): JSX.Element;
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import { cn } from "../../lib/cn.js";
|
|
2
|
+
import { jsx } from "react/jsx-runtime";
|
|
3
|
+
import { cva } from "class-variance-authority";
|
|
4
|
+
import { Input } from "@base-ui/react/input";
|
|
5
|
+
// src/components/input/Input.tsx
|
|
6
|
+
const inputVariants = cva("rt:w-full rt:min-w-0 rt:rounded-lg rt:bg-background rt:text-b3 rt:text-foreground rt:placeholder:text-muted-foreground rt:shadow-ring-subtle rt:outline-none rt:transition-colors rt:forced-outline rt:focus-visible:shadow-ring-focus rt:aria-invalid:shadow-ring-invalid rt:aria-invalid:focus-visible:shadow-ring-invalid rt:disabled:pointer-events-none rt:disabled:cursor-not-allowed rt:disabled:opacity-50", {
|
|
7
|
+
variants: { size: {
|
|
8
|
+
36: "rt:h-9 rt:px-3",
|
|
9
|
+
32: "rt:h-8 rt:px-2.75"
|
|
10
|
+
} },
|
|
11
|
+
defaultVariants: { size: 36 }
|
|
12
|
+
});
|
|
13
|
+
/**
|
|
14
|
+
* A single-line text field in the design system's look: 8px radius, a 1px ring that turns blue on focus, 14px body type.
|
|
15
|
+
* It takes every attribute of an input (type, inputMode, value, onChange). For a prefix, suffix, icon or shortcut hint,
|
|
16
|
+
* put an InputGroupInput in an InputGroup.
|
|
17
|
+
*/
|
|
18
|
+
function Input$1({ size = 36, ref, ...rest }) {
|
|
19
|
+
return /* @__PURE__ */ jsx(Input, {
|
|
20
|
+
"data-slot": "input",
|
|
21
|
+
ref,
|
|
22
|
+
className: cn(inputVariants({ size })),
|
|
23
|
+
...rest
|
|
24
|
+
});
|
|
25
|
+
}
|
|
26
|
+
export { Input$1 as Input };
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { Input } from "./Input.js";
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import { HTMLAttributes, InputHTMLAttributes, JSX, Ref } from "react";
|
|
2
|
+
/** The height of the group in px: 36 is the design system's input, 32 the compact one inside tables. */
|
|
3
|
+
export type InputGroupSize = 36 | 32;
|
|
4
|
+
/** The props of InputGroup: the HTML attributes of a div, except className and style, with the height of the group. */
|
|
5
|
+
export type InputGroupProps = Omit<HTMLAttributes<HTMLDivElement>, 'className' | 'style'> & {
|
|
6
|
+
/** The height in px: 36 for forms and search, 32 for fields inside a table. Defaults to 36. */
|
|
7
|
+
size?: InputGroupSize;
|
|
8
|
+
/** Receives the group element. */
|
|
9
|
+
ref?: Ref<HTMLDivElement>;
|
|
10
|
+
};
|
|
11
|
+
/**
|
|
12
|
+
* A field with a prefix, suffix, icon or shortcut hint beside the input, inside one ring. Put an InputGroupInput in it
|
|
13
|
+
* and an InputGroupAddon on either side, for example a rupee sign before a price or kg after a weight.
|
|
14
|
+
*/
|
|
15
|
+
export declare function InputGroup({ size, ref, ...rest }: InputGroupProps): JSX.Element;
|
|
16
|
+
/** The side of the input an addon sits on: start is before it, end is after it. */
|
|
17
|
+
export type InputGroupAddonAlign = 'start' | 'end';
|
|
18
|
+
/** The props of InputGroupAddon: the HTML attributes of a div, except className and style, with its side. */
|
|
19
|
+
export type InputGroupAddonProps = Omit<HTMLAttributes<HTMLDivElement>, 'className' | 'style'> & {
|
|
20
|
+
/** The side of the input: start before it, end after it. Defaults to start. */
|
|
21
|
+
align?: InputGroupAddonAlign;
|
|
22
|
+
};
|
|
23
|
+
/**
|
|
24
|
+
* Holds an icon, a prefix, a suffix or a shortcut hint on one side of the input. Clicking it, off a button, moves focus
|
|
25
|
+
* to the input.
|
|
26
|
+
*/
|
|
27
|
+
export declare function InputGroupAddon({ align, ...rest }: InputGroupAddonProps): JSX.Element;
|
|
28
|
+
/** The props of InputGroupText: the HTML attributes of a span, except className and style. */
|
|
29
|
+
export type InputGroupTextProps = Omit<HTMLAttributes<HTMLSpanElement>, 'className' | 'style'>;
|
|
30
|
+
/** A text prefix or suffix inside an addon, such as a currency sign or a unit, in the muted colour. */
|
|
31
|
+
export declare function InputGroupText(props: InputGroupTextProps): JSX.Element;
|
|
32
|
+
/** The props of InputGroupShortcut: the HTML attributes of a kbd, except className and style. */
|
|
33
|
+
export type InputGroupShortcutProps = Omit<HTMLAttributes<HTMLElement>, 'className' | 'style'>;
|
|
34
|
+
/** A shortcut hint such as ⌘K inside an end addon, drawn as a small keycap. */
|
|
35
|
+
export declare function InputGroupShortcut(props: InputGroupShortcutProps): JSX.Element;
|
|
36
|
+
/** The props of InputGroupInput: the HTML attributes of an input, except className, style and size. */
|
|
37
|
+
export type InputGroupInputProps = Omit<InputHTMLAttributes<HTMLInputElement>, 'className' | 'style' | 'size'> & {
|
|
38
|
+
/** Receives the input element. */
|
|
39
|
+
ref?: Ref<HTMLInputElement>;
|
|
40
|
+
};
|
|
41
|
+
/** The input of an InputGroup. It draws no ring: the group does. Mark it invalid with aria-invalid. */
|
|
42
|
+
export declare function InputGroupInput({ ref, ...rest }: InputGroupInputProps): JSX.Element;
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
import { cn } from "../../lib/cn.js";
|
|
2
|
+
import { useEffect, useRef } from "react";
|
|
3
|
+
import { jsx } from "react/jsx-runtime";
|
|
4
|
+
import { cva } from "class-variance-authority";
|
|
5
|
+
import { Input } from "@base-ui/react/input";
|
|
6
|
+
// src/components/input-group/InputGroup.tsx
|
|
7
|
+
const groupVariants = cva("rt:flex rt:w-full rt:min-w-0 rt:items-center rt:gap-2 rt:rounded-lg rt:bg-background rt:shadow-ring-subtle rt:transition-colors rt:forced-outline rt:focus-within:shadow-ring-focus rt:has-aria-invalid:shadow-ring-invalid rt:has-aria-invalid:focus-within:shadow-ring-invalid rt:has-disabled:opacity-50", {
|
|
8
|
+
variants: { size: {
|
|
9
|
+
36: "rt:h-9 rt:px-3",
|
|
10
|
+
32: "rt:h-8 rt:px-2.75"
|
|
11
|
+
} },
|
|
12
|
+
defaultVariants: { size: 36 }
|
|
13
|
+
});
|
|
14
|
+
/**
|
|
15
|
+
* A field with a prefix, suffix, icon or shortcut hint beside the input, inside one ring. Put an InputGroupInput in it
|
|
16
|
+
* and an InputGroupAddon on either side, for example a rupee sign before a price or kg after a weight.
|
|
17
|
+
*/
|
|
18
|
+
function InputGroup({ size = 36, ref, ...rest }) {
|
|
19
|
+
return /* @__PURE__ */ jsx("div", {
|
|
20
|
+
"data-slot": "input-group",
|
|
21
|
+
role: "group",
|
|
22
|
+
ref,
|
|
23
|
+
className: cn(groupVariants({ size })),
|
|
24
|
+
...rest
|
|
25
|
+
});
|
|
26
|
+
}
|
|
27
|
+
const addonVariants = cva("rt:flex rt:shrink-0 rt:cursor-text rt:items-center rt:gap-2 rt:text-muted-foreground rt:select-none", {
|
|
28
|
+
variants: { align: {
|
|
29
|
+
start: "rt:order-first",
|
|
30
|
+
end: "rt:order-last"
|
|
31
|
+
} },
|
|
32
|
+
defaultVariants: { align: "start" }
|
|
33
|
+
});
|
|
34
|
+
/**
|
|
35
|
+
* Holds an icon, a prefix, a suffix or a shortcut hint on one side of the input. Clicking it, off a button, moves focus
|
|
36
|
+
* to the input.
|
|
37
|
+
*/
|
|
38
|
+
function InputGroupAddon({ align = "start", ...rest }) {
|
|
39
|
+
const addonRef = useRef(null);
|
|
40
|
+
useEffect(() => {
|
|
41
|
+
const element = addonRef.current;
|
|
42
|
+
if (element === null) return;
|
|
43
|
+
const focusInput = (event) => {
|
|
44
|
+
if (event.defaultPrevented || event.target.closest("button") !== null) return;
|
|
45
|
+
element.parentElement?.querySelector("input")?.focus();
|
|
46
|
+
};
|
|
47
|
+
element.addEventListener("click", focusInput);
|
|
48
|
+
return () => element.removeEventListener("click", focusInput);
|
|
49
|
+
}, []);
|
|
50
|
+
return /* @__PURE__ */ jsx("div", {
|
|
51
|
+
"data-slot": "input-group-addon",
|
|
52
|
+
"data-align": align,
|
|
53
|
+
ref: addonRef,
|
|
54
|
+
className: cn(addonVariants({ align })),
|
|
55
|
+
...rest
|
|
56
|
+
});
|
|
57
|
+
}
|
|
58
|
+
/** A text prefix or suffix inside an addon, such as a currency sign or a unit, in the muted colour. */
|
|
59
|
+
function InputGroupText(props) {
|
|
60
|
+
return /* @__PURE__ */ jsx("span", {
|
|
61
|
+
"data-slot": "input-group-text",
|
|
62
|
+
className: "rt:text-b3 rt:text-muted-foreground",
|
|
63
|
+
...props
|
|
64
|
+
});
|
|
65
|
+
}
|
|
66
|
+
/** A shortcut hint such as ⌘K inside an end addon, drawn as a small keycap. */
|
|
67
|
+
function InputGroupShortcut(props) {
|
|
68
|
+
return /* @__PURE__ */ jsx("kbd", {
|
|
69
|
+
"data-slot": "input-group-shortcut",
|
|
70
|
+
className: "rt:inline-flex rt:h-5 rt:items-center rt:rounded-sm rt:px-1 rt:text-c1 rt:text-muted-foreground rt:shadow-ring-hairline",
|
|
71
|
+
...props
|
|
72
|
+
});
|
|
73
|
+
}
|
|
74
|
+
/** The input of an InputGroup. It draws no ring: the group does. Mark it invalid with aria-invalid. */
|
|
75
|
+
function InputGroupInput({ ref, ...rest }) {
|
|
76
|
+
return /* @__PURE__ */ jsx(Input, {
|
|
77
|
+
"data-slot": "input-group-control",
|
|
78
|
+
ref,
|
|
79
|
+
className: "rt:h-full rt:min-w-0 rt:flex-1 rt:bg-transparent rt:text-b3 rt:text-foreground rt:placeholder:text-muted-foreground rt:outline-none rt:disabled:cursor-not-allowed",
|
|
80
|
+
...rest
|
|
81
|
+
});
|
|
82
|
+
}
|
|
83
|
+
export { InputGroup, InputGroupAddon, InputGroupInput, InputGroupShortcut, InputGroupText };
|
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
import { InputGroup, InputGroupAddon, InputGroupAddonAlign, InputGroupAddonProps, InputGroupInput, InputGroupInputProps, InputGroupProps, InputGroupShortcut, InputGroupShortcutProps, InputGroupSize, InputGroupText, InputGroupTextProps } from "./InputGroup.js";
|
|
2
|
+
export { InputGroup, InputGroupAddon, type InputGroupAddonAlign, type InputGroupAddonProps, InputGroupInput, type InputGroupInputProps, type InputGroupProps, InputGroupShortcut, type InputGroupShortcutProps, type InputGroupSize, InputGroupText, type InputGroupTextProps };
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { InputGroup, InputGroupAddon, InputGroupInput, InputGroupShortcut, InputGroupText } from "./InputGroup.js";
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { HTMLAttributes, JSX, Ref } from "react";
|
|
2
|
+
/** The props of Kbd: the HTML attributes of a kbd element, except className and style. */
|
|
3
|
+
export type KbdProps = Omit<HTMLAttributes<HTMLElement>, 'className' | 'style'> & {
|
|
4
|
+
/** The key: a symbol such as ⌘ or ↵, a letter, or a short name such as esc. */
|
|
5
|
+
children: React.ReactNode;
|
|
6
|
+
/** Receives the rendered element. */
|
|
7
|
+
ref?: Ref<HTMLElement>;
|
|
8
|
+
};
|
|
9
|
+
/** A key hint, 20px high with a 4px radius, such as ⌘K or esc. */
|
|
10
|
+
export declare function Kbd({ children, ref, ...rest }: KbdProps): JSX.Element;
|
|
11
|
+
/** The props of KbdGroup: the HTML attributes of a span, except className and style. */
|
|
12
|
+
export type KbdGroupProps = Omit<HTMLAttributes<HTMLSpanElement>, 'className' | 'style'> & {
|
|
13
|
+
/** The keys of one shortcut, each a Kbd. */
|
|
14
|
+
children: React.ReactNode;
|
|
15
|
+
};
|
|
16
|
+
/** Puts the keys of one shortcut in a row with a 4px gap. */
|
|
17
|
+
export declare function KbdGroup({ children, ...rest }: KbdGroupProps): JSX.Element;
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import { cn } from "../../lib/cn.js";
|
|
2
|
+
import { jsx } from "react/jsx-runtime";
|
|
3
|
+
// src/components/kbd/Kbd.tsx
|
|
4
|
+
/** A key hint, 20px high with a 4px radius, such as ⌘K or esc. */
|
|
5
|
+
function Kbd({ children, ref, ...rest }) {
|
|
6
|
+
return /* @__PURE__ */ jsx("kbd", {
|
|
7
|
+
...rest,
|
|
8
|
+
ref,
|
|
9
|
+
className: cn("rt:inline-flex rt:h-5 rt:min-w-5 rt:w-fit rt:items-center rt:justify-center rt:rounded-sm rt:bg-muted rt:px-1 rt:font-sans rt:text-c2 rt:text-foreground-subdued rt:select-none"),
|
|
10
|
+
children
|
|
11
|
+
});
|
|
12
|
+
}
|
|
13
|
+
/** Puts the keys of one shortcut in a row with a 4px gap. */
|
|
14
|
+
function KbdGroup({ children, ...rest }) {
|
|
15
|
+
return /* @__PURE__ */ jsx("span", {
|
|
16
|
+
...rest,
|
|
17
|
+
className: "rt:inline-flex rt:items-center rt:gap-1",
|
|
18
|
+
children
|
|
19
|
+
});
|
|
20
|
+
}
|
|
21
|
+
export { Kbd, KbdGroup };
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { Kbd, KbdGroup } from "./Kbd.js";
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import { AnchorHTMLAttributes, JSX, ReactElement, ReactNode, Ref } from "react";
|
|
2
|
+
/**
|
|
3
|
+
* What the app's link element receives from Ratio: the address, Ratio's styling class, the content and the attributes
|
|
4
|
+
* the component sets, such as aria-current and onClick. Spread them all onto the element.
|
|
5
|
+
*/
|
|
6
|
+
export type LinkRenderProps = Omit<AnchorHTMLAttributes<HTMLAnchorElement>, 'href'> & {
|
|
7
|
+
/** Where the link goes. A router link takes it as its own address prop, such as React Router's `to`. */
|
|
8
|
+
href: string;
|
|
9
|
+
/** Receives the rendered element. */
|
|
10
|
+
ref?: Ref<HTMLAnchorElement>;
|
|
11
|
+
};
|
|
12
|
+
/** The app's link element: a function that draws it from the link props. */
|
|
13
|
+
export type LinkRender = (props: LinkRenderProps) => ReactElement;
|
|
14
|
+
/** The props of LinkProvider. */
|
|
15
|
+
export type LinkProviderProps = {
|
|
16
|
+
/**
|
|
17
|
+
* Draws every link Ratio renders. The function receives href, the styling class, the content and attributes such as
|
|
18
|
+
* aria-current and onClick, all to spread onto the element: `({ href, ...props }) => <RouterLink to={href} {...props} />`.
|
|
19
|
+
*/
|
|
20
|
+
render: LinkRender;
|
|
21
|
+
/** The app, or the part of it that the router serves. */
|
|
22
|
+
children: ReactNode;
|
|
23
|
+
};
|
|
24
|
+
/**
|
|
25
|
+
* Draws every link of Ratio's components with the app's own router link, so a click navigates without a page reload:
|
|
26
|
+
* SideNav rows with an href, DataTable row links and the PageHeader back link. Wrap the app once, at its root. Without
|
|
27
|
+
* it those links are plain <a href> elements, which reload the page.
|
|
28
|
+
*/
|
|
29
|
+
export declare function LinkProvider({ render, children }: LinkProviderProps): JSX.Element;
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { LinkContext } from "./context.js";
|
|
2
|
+
import { jsx } from "react/jsx-runtime";
|
|
3
|
+
// src/components/link-provider/LinkProvider.tsx
|
|
4
|
+
/**
|
|
5
|
+
* Draws every link of Ratio's components with the app's own router link, so a click navigates without a page reload:
|
|
6
|
+
* SideNav rows with an href, DataTable row links and the PageHeader back link. Wrap the app once, at its root. Without
|
|
7
|
+
* it those links are plain <a href> elements, which reload the page.
|
|
8
|
+
*/
|
|
9
|
+
function LinkProvider({ render, children }) {
|
|
10
|
+
return /* @__PURE__ */ jsx(LinkContext, {
|
|
11
|
+
value: render,
|
|
12
|
+
children
|
|
13
|
+
});
|
|
14
|
+
}
|
|
15
|
+
export { LinkProvider };
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { LinkContext } from "./context.js";
|
|
2
|
+
import { use } from "react";
|
|
3
|
+
import { useRender } from "@base-ui/react/use-render";
|
|
4
|
+
// src/components/link-provider/RatioLink.tsx
|
|
5
|
+
/**
|
|
6
|
+
* A link of one of Ratio's components, drawn by the app's LinkProvider, or a plain <a> without one. For the package's own
|
|
7
|
+
* components only; the package does not export it.
|
|
8
|
+
*/
|
|
9
|
+
function RatioLink({ ref, ...props }) {
|
|
10
|
+
const render = use(LinkContext);
|
|
11
|
+
return useRender({
|
|
12
|
+
defaultTagName: "a",
|
|
13
|
+
render: render ? (merged) => render(merged) : void 0,
|
|
14
|
+
ref,
|
|
15
|
+
props
|
|
16
|
+
});
|
|
17
|
+
}
|
|
18
|
+
export { RatioLink };
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { LinkProvider } from "./LinkProvider.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";
|