@qeetrix/react 0.0.1 → 0.0.4
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/CHANGELOG.md +288 -0
- package/README.md +240 -4
- package/dist/accordion/accordion.d.ts +42 -0
- package/dist/accordion/accordion.js +72 -0
- package/dist/accordion/index.d.ts +2 -0
- package/dist/accordion/index.js +1 -0
- package/dist/avatar/avatar.d.ts +21 -0
- package/dist/avatar/avatar.js +43 -0
- package/dist/avatar/index.d.ts +2 -0
- package/dist/avatar/index.js +1 -0
- package/dist/badge/badge.d.ts +14 -0
- package/dist/badge/badge.js +25 -0
- package/dist/badge/index.d.ts +2 -0
- package/dist/badge/index.js +1 -0
- package/dist/box/box.d.ts +27 -0
- package/dist/box/box.js +21 -0
- package/dist/box/index.d.ts +2 -0
- package/dist/box/index.js +1 -0
- package/dist/button/button.d.ts +50 -0
- package/dist/button/button.js +78 -0
- package/dist/button/index.d.ts +2 -0
- package/dist/button/index.js +1 -0
- package/dist/card/card.d.ts +30 -0
- package/dist/card/card.js +56 -0
- package/dist/card/index.d.ts +2 -0
- package/dist/card/index.js +1 -0
- package/dist/checkbox/checkbox.d.ts +33 -0
- package/dist/checkbox/checkbox.js +61 -0
- package/dist/checkbox/index.d.ts +2 -0
- package/dist/checkbox/index.js +1 -0
- package/dist/css/accordion.css +73 -0
- package/dist/css/avatar.css +50 -0
- package/dist/css/badge.css +59 -0
- package/dist/css/box.css +163 -0
- package/dist/css/button.css +243 -0
- package/dist/css/card.css +75 -0
- package/dist/css/checkbox.css +67 -0
- package/dist/css/dialog.css +128 -0
- package/dist/css/drawer.css +129 -0
- package/dist/css/experimental/alert.css +89 -0
- package/dist/css/experimental/app-shell.css +258 -0
- package/dist/css/experimental/breadcrumb.css +67 -0
- package/dist/css/experimental/copy-button.css +13 -0
- package/dist/css/experimental/empty-state.css +63 -0
- package/dist/css/experimental/otp-input.css +84 -0
- package/dist/css/experimental/page-header.css +110 -0
- package/dist/css/experimental/password-input.css +21 -0
- package/dist/css/experimental/sidebar.css +234 -0
- package/dist/css/experimental/stat.css +115 -0
- package/dist/css/experimental/top-bar.css +299 -0
- package/dist/css/field.css +121 -0
- package/dist/css/fieldset.css +37 -0
- package/dist/css/grid.css +86 -0
- package/dist/css/heading.css +93 -0
- package/dist/css/inline.css +85 -0
- package/dist/css/input.css +108 -0
- package/dist/css/link.css +45 -0
- package/dist/css/menu.css +166 -0
- package/dist/css/popover.css +72 -0
- package/dist/css/progress.css +102 -0
- package/dist/css/radio.css +54 -0
- package/dist/css/scroll-area.css +70 -0
- package/dist/css/select.css +189 -0
- package/dist/css/separator.css +35 -0
- package/dist/css/skeleton.css +39 -0
- package/dist/css/skip-link.css +33 -0
- package/dist/css/slider.css +108 -0
- package/dist/css/spinner.css +55 -0
- package/dist/css/stack.css +76 -0
- package/dist/css/switch.css +112 -0
- package/dist/css/table.css +68 -0
- package/dist/css/tabs.css +123 -0
- package/dist/css/text-field.css +4 -0
- package/dist/css/text.css +106 -0
- package/dist/css/toast.css +115 -0
- package/dist/css/toggle.css +125 -0
- package/dist/css/tooltip.css +47 -0
- package/dist/css/visually-hidden.css +15 -0
- package/dist/dialog/dialog.d.ts +54 -0
- package/dist/dialog/dialog.js +121 -0
- package/dist/dialog/index.d.ts +2 -0
- package/dist/dialog/index.js +1 -0
- package/dist/drawer/drawer.d.ts +49 -0
- package/dist/drawer/drawer.js +102 -0
- package/dist/drawer/index.d.ts +2 -0
- package/dist/drawer/index.js +1 -0
- package/dist/experimental/alert/alert.d.ts +29 -0
- package/dist/experimental/alert/alert.js +72 -0
- package/dist/experimental/alert/index.d.ts +2 -0
- package/dist/experimental/app-shell/app-shell.d.ts +60 -0
- package/dist/experimental/app-shell/app-shell.js +224 -0
- package/dist/experimental/app-shell/index.d.ts +2 -0
- package/dist/experimental/breadcrumb/breadcrumb.d.ts +27 -0
- package/dist/experimental/breadcrumb/breadcrumb.js +48 -0
- package/dist/experimental/breadcrumb/index.d.ts +2 -0
- package/dist/experimental/copy-button/copy-button.d.ts +27 -0
- package/dist/experimental/copy-button/copy-button.js +61 -0
- package/dist/experimental/copy-button/index.d.ts +2 -0
- package/dist/experimental/empty-state/data-state.d.ts +23 -0
- package/dist/experimental/empty-state/data-state.js +40 -0
- package/dist/experimental/empty-state/empty-state.d.ts +26 -0
- package/dist/experimental/empty-state/empty-state.js +46 -0
- package/dist/experimental/empty-state/index.d.ts +3 -0
- package/dist/experimental/index.d.ts +24 -0
- package/dist/experimental/index.js +13 -0
- package/dist/experimental/otp-input/index.d.ts +2 -0
- package/dist/experimental/otp-input/otp-input.d.ts +27 -0
- package/dist/experimental/otp-input/otp-input.js +39 -0
- package/dist/experimental/page-header/index.d.ts +2 -0
- package/dist/experimental/page-header/page-header.d.ts +32 -0
- package/dist/experimental/page-header/page-header.js +73 -0
- package/dist/experimental/password-input/index.d.ts +2 -0
- package/dist/experimental/password-input/password-input.d.ts +26 -0
- package/dist/experimental/password-input/password-input.js +103 -0
- package/dist/experimental/sidebar/index.d.ts +2 -0
- package/dist/experimental/sidebar/sidebar.d.ts +64 -0
- package/dist/experimental/sidebar/sidebar.js +200 -0
- package/dist/experimental/stat/index.d.ts +2 -0
- package/dist/experimental/stat/stat.d.ts +31 -0
- package/dist/experimental/stat/stat.js +90 -0
- package/dist/experimental/top-bar/index.d.ts +2 -0
- package/dist/experimental/top-bar/top-bar.d.ts +60 -0
- package/dist/experimental/top-bar/top-bar.js +162 -0
- package/dist/field/choice.d.ts +21 -0
- package/dist/field/choice.js +67 -0
- package/dist/field/field.d.ts +31 -0
- package/dist/field/field.js +78 -0
- package/dist/field/index.d.ts +3 -0
- package/dist/field/index.js +1 -0
- package/dist/fieldset/fieldset.d.ts +19 -0
- package/dist/fieldset/fieldset.js +29 -0
- package/dist/fieldset/index.d.ts +2 -0
- package/dist/fieldset/index.js +1 -0
- package/dist/format/format.d.ts +12 -0
- package/dist/format/format.js +50 -0
- package/dist/format/index.d.ts +2 -0
- package/dist/format/index.js +1 -0
- package/dist/grid/grid.d.ts +30 -0
- package/dist/grid/grid.js +36 -0
- package/dist/grid/index.d.ts +2 -0
- package/dist/grid/index.js +1 -0
- package/dist/heading/heading.d.ts +23 -0
- package/dist/heading/heading.js +30 -0
- package/dist/heading/index.d.ts +2 -0
- package/dist/heading/index.js +1 -0
- package/dist/index.d.ts +84 -1
- package/dist/index.js +40 -0
- package/dist/inline/index.d.ts +2 -0
- package/dist/inline/index.js +1 -0
- package/dist/inline/inline.d.ts +19 -0
- package/dist/inline/inline.js +16 -0
- package/dist/input/index.d.ts +2 -0
- package/dist/input/index.js +1 -0
- package/dist/input/input.d.ts +37 -0
- package/dist/input/input.js +63 -0
- package/dist/internal/control.d.ts +9 -0
- package/dist/internal/control.js +4 -0
- package/dist/internal/cx.js +7 -0
- package/dist/internal/media.js +15 -0
- package/dist/internal/messages.d.ts +75 -0
- package/dist/internal/messages.js +61 -0
- package/dist/internal/refs.js +23 -0
- package/dist/internal/render.d.ts +6 -0
- package/dist/internal/render.js +22 -0
- package/dist/internal/space.d.ts +9 -0
- package/dist/internal/text.d.ts +5 -0
- package/dist/internal/text.js +18 -0
- package/dist/link/index.d.ts +2 -0
- package/dist/link/index.js +1 -0
- package/dist/link/link.d.ts +19 -0
- package/dist/link/link.js +32 -0
- package/dist/locales/hi.d.ts +8 -0
- package/dist/locales/hi.js +60 -0
- package/dist/menu/index.d.ts +2 -0
- package/dist/menu/index.js +1 -0
- package/dist/menu/menu.d.ts +76 -0
- package/dist/menu/menu.js +172 -0
- package/dist/popover/index.d.ts +2 -0
- package/dist/popover/index.js +1 -0
- package/dist/popover/popover.d.ts +33 -0
- package/dist/popover/popover.js +48 -0
- package/dist/progress/index.d.ts +2 -0
- package/dist/progress/index.js +1 -0
- package/dist/progress/progress.d.ts +26 -0
- package/dist/progress/progress.js +62 -0
- package/dist/provider/index.d.ts +3 -0
- package/dist/provider/index.js +1 -0
- package/dist/provider/provider.d.ts +46 -0
- package/dist/provider/provider.js +90 -0
- package/dist/radio/index.d.ts +2 -0
- package/dist/radio/index.js +1 -0
- package/dist/radio/radio.d.ts +31 -0
- package/dist/radio/radio.js +47 -0
- package/dist/scroll-area/index.d.ts +2 -0
- package/dist/scroll-area/index.js +1 -0
- package/dist/scroll-area/scroll-area.d.ts +18 -0
- package/dist/scroll-area/scroll-area.js +39 -0
- package/dist/select/index.d.ts +2 -0
- package/dist/select/index.js +1 -0
- package/dist/select/select.d.ts +36 -0
- package/dist/select/select.js +82 -0
- package/dist/separator/index.d.ts +2 -0
- package/dist/separator/index.js +1 -0
- package/dist/separator/separator.d.ts +16 -0
- package/dist/separator/separator.js +16 -0
- package/dist/skeleton/index.d.ts +2 -0
- package/dist/skeleton/index.js +1 -0
- package/dist/skeleton/skeleton.d.ts +12 -0
- package/dist/skeleton/skeleton.js +18 -0
- package/dist/skip-link/index.d.ts +2 -0
- package/dist/skip-link/index.js +1 -0
- package/dist/skip-link/skip-link.d.ts +13 -0
- package/dist/skip-link/skip-link.js +18 -0
- package/dist/slider/index.d.ts +2 -0
- package/dist/slider/index.js +1 -0
- package/dist/slider/slider.d.ts +20 -0
- package/dist/slider/slider.js +58 -0
- package/dist/spinner/index.d.ts +2 -0
- package/dist/spinner/index.js +1 -0
- package/dist/spinner/spinner.d.ts +15 -0
- package/dist/spinner/spinner.js +19 -0
- package/dist/stack/index.d.ts +2 -0
- package/dist/stack/index.js +1 -0
- package/dist/stack/stack.d.ts +17 -0
- package/dist/stack/stack.js +15 -0
- package/dist/styles.css +4548 -0
- package/dist/switch/index.d.ts +2 -0
- package/dist/switch/index.js +1 -0
- package/dist/switch/switch.d.ts +18 -0
- package/dist/switch/switch.js +31 -0
- package/dist/table/index.d.ts +2 -0
- package/dist/table/index.js +1 -0
- package/dist/table/table.d.ts +36 -0
- package/dist/table/table.js +69 -0
- package/dist/tabs/index.d.ts +2 -0
- package/dist/tabs/index.js +1 -0
- package/dist/tabs/tabs.d.ts +43 -0
- package/dist/tabs/tabs.js +66 -0
- package/dist/text/index.d.ts +3 -0
- package/dist/text/index.js +1 -0
- package/dist/text/text.d.ts +24 -0
- package/dist/text/text.js +24 -0
- package/dist/text-field/index.d.ts +2 -0
- package/dist/text-field/index.js +1 -0
- package/dist/text-field/text-field.d.ts +15 -0
- package/dist/text-field/text-field.js +43 -0
- package/dist/toast/index.d.ts +2 -0
- package/dist/toast/index.js +1 -0
- package/dist/toast/toast.d.ts +57 -0
- package/dist/toast/toast.js +124 -0
- package/dist/toggle/index.d.ts +2 -0
- package/dist/toggle/index.js +1 -0
- package/dist/toggle/toggle.d.ts +48 -0
- package/dist/toggle/toggle.js +54 -0
- package/dist/tooltip/index.d.ts +2 -0
- package/dist/tooltip/index.js +1 -0
- package/dist/tooltip/tooltip.d.ts +26 -0
- package/dist/tooltip/tooltip.js +39 -0
- package/dist/visually-hidden/index.d.ts +2 -0
- package/dist/visually-hidden/index.js +1 -0
- package/dist/visually-hidden/visually-hidden.d.ts +8 -0
- package/dist/visually-hidden/visually-hidden.js +12 -0
- package/package.json +170 -2
|
@@ -0,0 +1,224 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { cx } from "../../internal/cx.js";
|
|
3
|
+
import { Tooltip } from "../../tooltip/tooltip.js";
|
|
4
|
+
import { IconButton } from "../../button/button.js";
|
|
5
|
+
import { useMediaQuery } from "../../internal/media.js";
|
|
6
|
+
import { useMessages } from "../../provider/provider.js";
|
|
7
|
+
import { SkipLink } from "../../skip-link/skip-link.js";
|
|
8
|
+
import { jsx, jsxs } from "react/jsx-runtime";
|
|
9
|
+
import { createContext, use, useEffect, useId, useRef, useState } from "react";
|
|
10
|
+
import { useDirection } from "@base-ui/react/direction-provider";
|
|
11
|
+
import { XIcon } from "@qeetrix/icons/icons/x";
|
|
12
|
+
import { Drawer } from "@base-ui/react/drawer";
|
|
13
|
+
import { PanelLeftCloseIcon } from "@qeetrix/icons/icons/panel-left-close";
|
|
14
|
+
import { PanelLeftOpenIcon } from "@qeetrix/icons/icons/panel-left-open";
|
|
15
|
+
// src/experimental/app-shell/app-shell.tsx
|
|
16
|
+
/** The `expanded` window class (tokens `breakpoint.expanded`): the sidebar is a column from here. */
|
|
17
|
+
const WIDE = "(width >= 840px)";
|
|
18
|
+
/** How long the pointer rests on the rail before it expands over the page. */
|
|
19
|
+
const PEEK_DELAY = 200;
|
|
20
|
+
const ShellContext = createContext(null);
|
|
21
|
+
const outside = {
|
|
22
|
+
collapsed: false,
|
|
23
|
+
inDrawer: false,
|
|
24
|
+
narrow: false,
|
|
25
|
+
expand: () => {},
|
|
26
|
+
closeDrawer: () => {},
|
|
27
|
+
openNavigation: () => {},
|
|
28
|
+
drawers: [],
|
|
29
|
+
activeDrawer: null,
|
|
30
|
+
toggleDrawer: () => {},
|
|
31
|
+
strings: {
|
|
32
|
+
collapse: "",
|
|
33
|
+
expand: "",
|
|
34
|
+
open: "",
|
|
35
|
+
navigation: "",
|
|
36
|
+
tools: "",
|
|
37
|
+
closePanel: ""
|
|
38
|
+
}
|
|
39
|
+
};
|
|
40
|
+
/** The shell's state, for its own parts. Outside an AppShell, a sidebar is expanded. */
|
|
41
|
+
function useShellState() {
|
|
42
|
+
return use(ShellContext) ?? outside;
|
|
43
|
+
}
|
|
44
|
+
/** The AppShell's state, for content inside its sidebar or bar. */
|
|
45
|
+
function useAppShell() {
|
|
46
|
+
const { collapsed, inDrawer } = useShellState();
|
|
47
|
+
return { collapsed: collapsed && !inDrawer };
|
|
48
|
+
}
|
|
49
|
+
/**
|
|
50
|
+
* The frame of a Qeet console (07-components.md, C1 · Ruled): the black scope bar across the full
|
|
51
|
+
* width; the ruled navigation column, which collapses to a 48px icon rail that expands while
|
|
52
|
+
* hovered or focused; the page; and a panel on the end edge that the bar's tools open. Seams are
|
|
53
|
+
* 1px rules, never shadows. Compact by default.
|
|
54
|
+
*/
|
|
55
|
+
function AppShell({ topBar, sidebar, drawers = [], activeDrawer, defaultActiveDrawer = null, onActiveDrawerChange, notifications, density = "compact", children, sidebarCollapsed, defaultSidebarCollapsed = false, onSidebarCollapsedChange, mainId = "main", messages, className, ...props }) {
|
|
56
|
+
const strings = useMessages("appShell", messages);
|
|
57
|
+
const skip = useMessages("skipLink");
|
|
58
|
+
const wide = useMediaQuery(WIDE, true);
|
|
59
|
+
const rtl = useDirection() === "rtl";
|
|
60
|
+
const panelTitle = useId();
|
|
61
|
+
const [ownCollapsed, setOwnCollapsed] = useState(defaultSidebarCollapsed);
|
|
62
|
+
const collapsed = sidebarCollapsed ?? ownCollapsed;
|
|
63
|
+
const setCollapsed = (next) => {
|
|
64
|
+
if (sidebarCollapsed === void 0) setOwnCollapsed(next);
|
|
65
|
+
onSidebarCollapsedChange?.(next);
|
|
66
|
+
};
|
|
67
|
+
const [ownDrawer, setOwnDrawer] = useState(defaultActiveDrawer);
|
|
68
|
+
const active = activeDrawer === void 0 ? ownDrawer : activeDrawer;
|
|
69
|
+
const setActive = (next) => {
|
|
70
|
+
if (activeDrawer === void 0) setOwnDrawer(next);
|
|
71
|
+
onActiveDrawerChange?.(next);
|
|
72
|
+
};
|
|
73
|
+
const panel = drawers.find((drawer) => drawer.id === active);
|
|
74
|
+
const [navOpen, setNavOpen] = useState(false);
|
|
75
|
+
const [peek, setPeek] = useState(false);
|
|
76
|
+
const timer = useRef(void 0);
|
|
77
|
+
useEffect(() => () => window.clearTimeout(timer.current), []);
|
|
78
|
+
const startPeek = () => {
|
|
79
|
+
if (!collapsed) return;
|
|
80
|
+
window.clearTimeout(timer.current);
|
|
81
|
+
timer.current = window.setTimeout(() => setPeek(true), PEEK_DELAY);
|
|
82
|
+
};
|
|
83
|
+
const endPeek = () => {
|
|
84
|
+
window.clearTimeout(timer.current);
|
|
85
|
+
setPeek(false);
|
|
86
|
+
};
|
|
87
|
+
const shell = {
|
|
88
|
+
collapsed: collapsed && !peek,
|
|
89
|
+
inDrawer: false,
|
|
90
|
+
narrow: !wide,
|
|
91
|
+
expand: () => {
|
|
92
|
+
endPeek();
|
|
93
|
+
setCollapsed(false);
|
|
94
|
+
},
|
|
95
|
+
closeDrawer: () => setNavOpen(false),
|
|
96
|
+
openNavigation: () => setNavOpen(true),
|
|
97
|
+
drawers,
|
|
98
|
+
activeDrawer: active,
|
|
99
|
+
toggleDrawer: (id) => setActive(active === id ? null : id),
|
|
100
|
+
strings
|
|
101
|
+
};
|
|
102
|
+
const collapseControl = /* @__PURE__ */ jsxs("button", {
|
|
103
|
+
type: "button",
|
|
104
|
+
className: "qx-app-shell-collapse",
|
|
105
|
+
"aria-expanded": !collapsed,
|
|
106
|
+
onClick: () => {
|
|
107
|
+
endPeek();
|
|
108
|
+
setCollapsed(!collapsed);
|
|
109
|
+
},
|
|
110
|
+
children: [/* @__PURE__ */ jsx(collapsed ? PanelLeftOpenIcon : PanelLeftCloseIcon, {
|
|
111
|
+
shape: "round",
|
|
112
|
+
size: 16,
|
|
113
|
+
"aria-hidden": true,
|
|
114
|
+
className: "qx-app-shell-collapse-icon"
|
|
115
|
+
}), /* @__PURE__ */ jsx("span", {
|
|
116
|
+
className: "qx-app-shell-collapse-label",
|
|
117
|
+
children: collapsed ? strings.expand : strings.collapse
|
|
118
|
+
})]
|
|
119
|
+
});
|
|
120
|
+
return /* @__PURE__ */ jsx(ShellContext, {
|
|
121
|
+
value: shell,
|
|
122
|
+
children: /* @__PURE__ */ jsxs("div", {
|
|
123
|
+
...props,
|
|
124
|
+
className: cx("qx-app-shell", className),
|
|
125
|
+
"data-slot": "app-shell",
|
|
126
|
+
"data-qx-density": density === "inherit" ? void 0 : density,
|
|
127
|
+
"data-collapsed": collapsed ? "" : void 0,
|
|
128
|
+
"data-drawer-open": panel ? "" : void 0,
|
|
129
|
+
children: [
|
|
130
|
+
/* @__PURE__ */ jsx(SkipLink, {
|
|
131
|
+
href: `#${mainId}`,
|
|
132
|
+
children: skip.label
|
|
133
|
+
}),
|
|
134
|
+
topBar != null && /* @__PURE__ */ jsx("div", {
|
|
135
|
+
className: "qx-app-shell-top",
|
|
136
|
+
children: topBar
|
|
137
|
+
}),
|
|
138
|
+
sidebar != null && /* @__PURE__ */ jsx("div", {
|
|
139
|
+
className: "qx-app-shell-nav",
|
|
140
|
+
"data-slot": "app-shell-nav",
|
|
141
|
+
"data-peek": peek ? "" : void 0,
|
|
142
|
+
onPointerEnter: startPeek,
|
|
143
|
+
onPointerLeave: endPeek,
|
|
144
|
+
onFocus: () => collapsed && setPeek(true),
|
|
145
|
+
onBlur: (event) => {
|
|
146
|
+
if (!event.currentTarget.contains(event.relatedTarget)) setPeek(false);
|
|
147
|
+
},
|
|
148
|
+
children: /* @__PURE__ */ jsxs("div", {
|
|
149
|
+
className: "qx-app-shell-nav-panel",
|
|
150
|
+
children: [/* @__PURE__ */ jsx("div", {
|
|
151
|
+
className: "qx-app-shell-nav-body",
|
|
152
|
+
children: sidebar
|
|
153
|
+
}), /* @__PURE__ */ jsx("div", {
|
|
154
|
+
className: "qx-app-shell-nav-foot",
|
|
155
|
+
children: /* @__PURE__ */ jsx(Tooltip, {
|
|
156
|
+
content: strings.expand,
|
|
157
|
+
side: "inline-end",
|
|
158
|
+
disabled: !collapsed || peek,
|
|
159
|
+
children: collapseControl
|
|
160
|
+
})
|
|
161
|
+
})]
|
|
162
|
+
})
|
|
163
|
+
}),
|
|
164
|
+
/* @__PURE__ */ jsxs("main", {
|
|
165
|
+
id: mainId,
|
|
166
|
+
tabIndex: -1,
|
|
167
|
+
className: "qx-app-shell-main",
|
|
168
|
+
"data-slot": "app-shell-main",
|
|
169
|
+
children: [notifications != null && /* @__PURE__ */ jsx("div", {
|
|
170
|
+
className: "qx-app-shell-notifications",
|
|
171
|
+
"data-slot": "app-shell-notifications",
|
|
172
|
+
children: notifications
|
|
173
|
+
}), children]
|
|
174
|
+
}),
|
|
175
|
+
panel && /* @__PURE__ */ jsxs("aside", {
|
|
176
|
+
className: "qx-app-shell-panel",
|
|
177
|
+
"data-slot": "app-shell-panel",
|
|
178
|
+
"aria-labelledby": panelTitle,
|
|
179
|
+
children: [/* @__PURE__ */ jsxs("div", {
|
|
180
|
+
className: "qx-app-shell-panel-header",
|
|
181
|
+
children: [/* @__PURE__ */ jsx("h2", {
|
|
182
|
+
id: panelTitle,
|
|
183
|
+
className: "qx-app-shell-panel-title",
|
|
184
|
+
children: panel.label
|
|
185
|
+
}), /* @__PURE__ */ jsx(IconButton, {
|
|
186
|
+
icon: XIcon,
|
|
187
|
+
"aria-label": strings.closePanel,
|
|
188
|
+
size: "sm",
|
|
189
|
+
tone: "neutral",
|
|
190
|
+
tooltip: false,
|
|
191
|
+
onClick: () => setActive(null)
|
|
192
|
+
})]
|
|
193
|
+
}), /* @__PURE__ */ jsx("div", {
|
|
194
|
+
className: "qx-app-shell-panel-body",
|
|
195
|
+
children: panel.content
|
|
196
|
+
})]
|
|
197
|
+
}),
|
|
198
|
+
sidebar != null && /* @__PURE__ */ jsx(Drawer.Root, {
|
|
199
|
+
open: navOpen && !wide,
|
|
200
|
+
onOpenChange: setNavOpen,
|
|
201
|
+
swipeDirection: rtl ? "right" : "left",
|
|
202
|
+
children: /* @__PURE__ */ jsxs(Drawer.Portal, { children: [/* @__PURE__ */ jsx(Drawer.Backdrop, { className: "qx-app-shell-backdrop" }), /* @__PURE__ */ jsx(Drawer.Viewport, {
|
|
203
|
+
className: "qx-app-shell-viewport",
|
|
204
|
+
children: /* @__PURE__ */ jsx(Drawer.Popup, {
|
|
205
|
+
className: "qx-app-shell-drawer",
|
|
206
|
+
"data-slot": "app-shell-drawer",
|
|
207
|
+
"data-qx-density": density === "inherit" ? void 0 : density,
|
|
208
|
+
"aria-label": strings.navigation,
|
|
209
|
+
children: /* @__PURE__ */ jsx(ShellContext, {
|
|
210
|
+
value: {
|
|
211
|
+
...shell,
|
|
212
|
+
collapsed: false,
|
|
213
|
+
inDrawer: true
|
|
214
|
+
},
|
|
215
|
+
children: sidebar
|
|
216
|
+
})
|
|
217
|
+
})
|
|
218
|
+
})] })
|
|
219
|
+
})
|
|
220
|
+
]
|
|
221
|
+
})
|
|
222
|
+
});
|
|
223
|
+
}
|
|
224
|
+
export { AppShell, useAppShell, useShellState };
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import { RenderProp } from "../../internal/render.js";
|
|
2
|
+
import { ComponentProps, ReactElement, ReactNode } from "react";
|
|
3
|
+
export interface BreadcrumbProps extends ComponentProps<"nav"> {
|
|
4
|
+
/**
|
|
5
|
+
* Names the navigation landmark. Default "Breadcrumb". Breadcrumb is server-safe, so
|
|
6
|
+
* QeetrixProvider's strings don't reach it: pass a translation here.
|
|
7
|
+
*/
|
|
8
|
+
label?: string;
|
|
9
|
+
/** BreadcrumbItems, the current page last. */
|
|
10
|
+
children?: ReactNode;
|
|
11
|
+
}
|
|
12
|
+
export interface BreadcrumbItemProps extends Omit<ComponentProps<"li">, "children"> {
|
|
13
|
+
/** Where it goes. The current item takes none. */
|
|
14
|
+
href?: string;
|
|
15
|
+
/** A router link instead of `<a>`, e.g. `render={<Link to="/users" />}`. */
|
|
16
|
+
render?: RenderProp;
|
|
17
|
+
/** The current page: plain text with `aria-current="page"`. */
|
|
18
|
+
current?: boolean;
|
|
19
|
+
children: ReactNode;
|
|
20
|
+
}
|
|
21
|
+
/**
|
|
22
|
+
* Where the page sits, in a console header (07-components.md, C1): an ordered list of links,
|
|
23
|
+
* separated by sharp chevrons that mirror in right-to-left text. Server-safe.
|
|
24
|
+
*/
|
|
25
|
+
export declare function Breadcrumb({ label, className, children, ...props }: BreadcrumbProps): ReactElement;
|
|
26
|
+
/** One step. A long label truncates and keeps its full text as a title. */
|
|
27
|
+
export declare function BreadcrumbItem({ href, render, current, className, children, ...props }: BreadcrumbItemProps): ReactElement;
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import { cx } from "../../internal/cx.js";
|
|
2
|
+
import { renderElement } from "../../internal/render.js";
|
|
3
|
+
import { messages } from "../../internal/messages.js";
|
|
4
|
+
import { jsx, jsxs } from "react/jsx-runtime";
|
|
5
|
+
import { ChevronRightIcon } from "@qeetrix/icons/icons/chevron-right";
|
|
6
|
+
// src/experimental/breadcrumb/breadcrumb.tsx
|
|
7
|
+
/**
|
|
8
|
+
* Where the page sits, in a console header (07-components.md, C1): an ordered list of links,
|
|
9
|
+
* separated by sharp chevrons that mirror in right-to-left text. Server-safe.
|
|
10
|
+
*/
|
|
11
|
+
function Breadcrumb({ label = messages.breadcrumb.label, className, children, ...props }) {
|
|
12
|
+
return /* @__PURE__ */ jsx("nav", {
|
|
13
|
+
...props,
|
|
14
|
+
"aria-label": label,
|
|
15
|
+
className: cx("qx-breadcrumb", className),
|
|
16
|
+
"data-slot": "breadcrumb",
|
|
17
|
+
children: /* @__PURE__ */ jsx("ol", {
|
|
18
|
+
className: "qx-breadcrumb-list",
|
|
19
|
+
children
|
|
20
|
+
})
|
|
21
|
+
});
|
|
22
|
+
}
|
|
23
|
+
/** One step. A long label truncates and keeps its full text as a title. */
|
|
24
|
+
function BreadcrumbItem({ href, render, current, className, children, ...props }) {
|
|
25
|
+
const title = typeof children === "string" ? children : void 0;
|
|
26
|
+
return /* @__PURE__ */ jsxs("li", {
|
|
27
|
+
...props,
|
|
28
|
+
className: cx("qx-breadcrumb-item", className),
|
|
29
|
+
"data-slot": "breadcrumb-item",
|
|
30
|
+
children: [/* @__PURE__ */ jsx(ChevronRightIcon, {
|
|
31
|
+
shape: "sharp",
|
|
32
|
+
size: 12,
|
|
33
|
+
"aria-hidden": true,
|
|
34
|
+
className: "qx-breadcrumb-separator"
|
|
35
|
+
}), current ? /* @__PURE__ */ jsx("span", {
|
|
36
|
+
className: "qx-breadcrumb-current",
|
|
37
|
+
"aria-current": "page",
|
|
38
|
+
title,
|
|
39
|
+
children
|
|
40
|
+
}) : renderElement("a", render, {
|
|
41
|
+
href,
|
|
42
|
+
className: "qx-breadcrumb-link",
|
|
43
|
+
title,
|
|
44
|
+
children
|
|
45
|
+
})]
|
|
46
|
+
});
|
|
47
|
+
}
|
|
48
|
+
export { Breadcrumb, BreadcrumbItem };
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import { IconButtonProps } from "../../button/button.js";
|
|
2
|
+
import { QeetrixMessages } from "../../internal/messages.js";
|
|
3
|
+
import { ReactElement, ReactNode } from "react";
|
|
4
|
+
/** Where a copy stands: ready, just copied, or failed (no clipboard access). */
|
|
5
|
+
export type CopyButtonState = "idle" | "copied" | "failed";
|
|
6
|
+
export interface CopyButtonProps extends Omit<IconButtonProps, "icon" | "aria-label" | "tooltip" | "loading" | "onClick" | "onCopy" | "value"> {
|
|
7
|
+
/** The text to copy, or a function that returns it (it may be async, e.g. a fresh token). */
|
|
8
|
+
value: string | (() => string | Promise<string>);
|
|
9
|
+
/** What it copies, as the button's name and tooltip: "Copy user ID". Default "Copy". */
|
|
10
|
+
label?: string;
|
|
11
|
+
/** A visible label makes it a Button (label first, icon last) instead of an IconButton. */
|
|
12
|
+
children?: ReactNode;
|
|
13
|
+
/** Called with the text once it's on the clipboard. */
|
|
14
|
+
onCopied?: (value: string) => void;
|
|
15
|
+
/** Called when copying fails, e.g. no clipboard access outside a secure context. */
|
|
16
|
+
onCopyError?: (error: unknown) => void;
|
|
17
|
+
/** How long "Copied" (or the failure) shows before it resets, in milliseconds. Default 2000. */
|
|
18
|
+
resetAfter?: number;
|
|
19
|
+
/** This button's strings, over QeetrixProvider's and the English defaults. */
|
|
20
|
+
messages?: Partial<QeetrixMessages["copyButton"]>;
|
|
21
|
+
}
|
|
22
|
+
/**
|
|
23
|
+
* Copies text to the clipboard (07-components.md, C2). Focus stays on the button; the icon turns
|
|
24
|
+
* into a check, and "Copied" is announced politely, then it resets. It fails visibly, never
|
|
25
|
+
* silently, where the page has no clipboard access.
|
|
26
|
+
*/
|
|
27
|
+
export declare function CopyButton({ value, label, children, onCopied, onCopyError, resetAfter, messages, variant, tone, className, ...props }: CopyButtonProps): ReactElement;
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { cx } from "../../internal/cx.js";
|
|
3
|
+
import { Button, IconButton } from "../../button/button.js";
|
|
4
|
+
import { useMessages } from "../../provider/provider.js";
|
|
5
|
+
import { VisuallyHidden } from "../../visually-hidden/visually-hidden.js";
|
|
6
|
+
import { Fragment, jsx, jsxs } from "react/jsx-runtime";
|
|
7
|
+
import { useEffect, useRef, useState } from "react";
|
|
8
|
+
import { CheckIcon } from "@qeetrix/icons/icons/check";
|
|
9
|
+
import { CircleAlertIcon } from "@qeetrix/icons/icons/circle-alert";
|
|
10
|
+
import { CopyIcon } from "@qeetrix/icons/icons/copy";
|
|
11
|
+
// src/experimental/copy-button/copy-button.tsx
|
|
12
|
+
/**
|
|
13
|
+
* Copies text to the clipboard (07-components.md, C2). Focus stays on the button; the icon turns
|
|
14
|
+
* into a check, and "Copied" is announced politely, then it resets. It fails visibly, never
|
|
15
|
+
* silently, where the page has no clipboard access.
|
|
16
|
+
*/
|
|
17
|
+
function CopyButton({ value, label, children, onCopied, onCopyError, resetAfter = 2e3, messages, variant = "ghost", tone = "neutral", className, ...props }) {
|
|
18
|
+
const strings = useMessages("copyButton", messages);
|
|
19
|
+
const [state, setState] = useState("idle");
|
|
20
|
+
const timer = useRef(void 0);
|
|
21
|
+
useEffect(() => () => window.clearTimeout(timer.current), []);
|
|
22
|
+
const copy = async () => {
|
|
23
|
+
window.clearTimeout(timer.current);
|
|
24
|
+
setState("idle");
|
|
25
|
+
try {
|
|
26
|
+
const text = typeof value === "function" ? await value() : value;
|
|
27
|
+
if (!navigator.clipboard?.writeText) throw new Error("The clipboard isn't available here.");
|
|
28
|
+
await navigator.clipboard.writeText(text);
|
|
29
|
+
setState("copied");
|
|
30
|
+
onCopied?.(text);
|
|
31
|
+
} catch (error) {
|
|
32
|
+
setState("failed");
|
|
33
|
+
onCopyError?.(error);
|
|
34
|
+
}
|
|
35
|
+
timer.current = window.setTimeout(() => setState("idle"), resetAfter);
|
|
36
|
+
};
|
|
37
|
+
const Icon = state === "copied" ? CheckIcon : state === "failed" ? CircleAlertIcon : CopyIcon;
|
|
38
|
+
const announcement = state === "copied" ? strings.copied : state === "failed" ? strings.failed : "";
|
|
39
|
+
const name = label ?? strings.copy;
|
|
40
|
+
const shared = {
|
|
41
|
+
...props,
|
|
42
|
+
variant,
|
|
43
|
+
tone,
|
|
44
|
+
icon: Icon,
|
|
45
|
+
className: cx("qx-copy-button", className),
|
|
46
|
+
"data-state": state,
|
|
47
|
+
onClick: copy
|
|
48
|
+
};
|
|
49
|
+
return /* @__PURE__ */ jsxs(Fragment, { children: [children != null ? /* @__PURE__ */ jsx(Button, {
|
|
50
|
+
...shared,
|
|
51
|
+
children
|
|
52
|
+
}) : /* @__PURE__ */ jsx(IconButton, {
|
|
53
|
+
...shared,
|
|
54
|
+
"aria-label": name,
|
|
55
|
+
tooltip: announcement || name
|
|
56
|
+
}), /* @__PURE__ */ jsx(VisuallyHidden, {
|
|
57
|
+
role: "status",
|
|
58
|
+
children: announcement
|
|
59
|
+
})] });
|
|
60
|
+
}
|
|
61
|
+
export { CopyButton };
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import { QeetrixMessages } from "../../internal/messages.js";
|
|
2
|
+
import { ReactElement, ReactNode } from "react";
|
|
3
|
+
export interface DataStateProps {
|
|
4
|
+
/** The content is loading. It wins over every other state. */
|
|
5
|
+
loading?: boolean;
|
|
6
|
+
/** Anything truthy shows the error state. The raw error is never shown: log it in the product. */
|
|
7
|
+
error?: unknown;
|
|
8
|
+
/** The content loaded and there's nothing in it. */
|
|
9
|
+
empty?: boolean;
|
|
10
|
+
/** The EmptyState to show when empty. */
|
|
11
|
+
emptyState?: ReactNode;
|
|
12
|
+
/** Adds a "Try again" button to the error state. */
|
|
13
|
+
onRetry?: () => void;
|
|
14
|
+
/** The content, shown when it's loaded and not empty. */
|
|
15
|
+
children?: ReactNode;
|
|
16
|
+
/** The error state's strings, over QeetrixProvider's and the English defaults. */
|
|
17
|
+
messages?: Partial<QeetrixMessages["dataState"]>;
|
|
18
|
+
}
|
|
19
|
+
/**
|
|
20
|
+
* The four states of loaded content in one place (07-components.md, C1): loading, then error, then
|
|
21
|
+
* empty, then the content.
|
|
22
|
+
*/
|
|
23
|
+
export declare function DataState({ loading, error, empty, emptyState, onRetry, children, messages }: DataStateProps): ReactElement;
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { Spinner } from "../../spinner/spinner.js";
|
|
3
|
+
import { Button } from "../../button/button.js";
|
|
4
|
+
import { useMessages } from "../../provider/provider.js";
|
|
5
|
+
import { EmptyState } from "./empty-state.js";
|
|
6
|
+
import { Fragment, jsx } from "react/jsx-runtime";
|
|
7
|
+
import { CircleAlertIcon } from "@qeetrix/icons/icons/circle-alert";
|
|
8
|
+
// src/experimental/empty-state/data-state.tsx
|
|
9
|
+
/**
|
|
10
|
+
* The four states of loaded content in one place (07-components.md, C1): loading, then error, then
|
|
11
|
+
* empty, then the content.
|
|
12
|
+
*/
|
|
13
|
+
function DataState({ loading, error, empty, emptyState, onRetry, children, messages }) {
|
|
14
|
+
const strings = useMessages("dataState", messages);
|
|
15
|
+
const spinner = useMessages("spinner");
|
|
16
|
+
if (loading) return /* @__PURE__ */ jsx("div", {
|
|
17
|
+
className: "qx-data-state",
|
|
18
|
+
"data-slot": "data-state",
|
|
19
|
+
"aria-busy": "true",
|
|
20
|
+
children: /* @__PURE__ */ jsx(Spinner, {
|
|
21
|
+
size: "lg",
|
|
22
|
+
label: spinner.label
|
|
23
|
+
})
|
|
24
|
+
});
|
|
25
|
+
if (error) return /* @__PURE__ */ jsx(EmptyState, {
|
|
26
|
+
size: "section",
|
|
27
|
+
tone: "danger",
|
|
28
|
+
icon: CircleAlertIcon,
|
|
29
|
+
title: strings.errorTitle,
|
|
30
|
+
description: strings.errorDescription,
|
|
31
|
+
actions: onRetry && /* @__PURE__ */ jsx(Button, {
|
|
32
|
+
variant: "soft",
|
|
33
|
+
onClick: onRetry,
|
|
34
|
+
children: strings.retry
|
|
35
|
+
})
|
|
36
|
+
});
|
|
37
|
+
if (empty) return /* @__PURE__ */ jsx(Fragment, { children: emptyState });
|
|
38
|
+
return /* @__PURE__ */ jsx(Fragment, { children });
|
|
39
|
+
}
|
|
40
|
+
export { DataState };
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import { HeadingLevel } from "../../heading/heading.js";
|
|
2
|
+
import { QeetrixIcon } from "../../internal/control.js";
|
|
3
|
+
import { ComponentProps, ReactElement, ReactNode } from "react";
|
|
4
|
+
export type EmptyStateSize = "page" | "section";
|
|
5
|
+
export type EmptyStateTone = "neutral" | "danger";
|
|
6
|
+
export interface EmptyStateProps extends Omit<ComponentProps<"div">, "title"> {
|
|
7
|
+
/** A 24px sharp icon from `@qeetrix/icons`. */
|
|
8
|
+
icon?: QeetrixIcon;
|
|
9
|
+
/** What's missing or what went wrong, in a few words: "No users yet". */
|
|
10
|
+
title: ReactNode;
|
|
11
|
+
/** One or two sentences: why, and what to do. */
|
|
12
|
+
description?: ReactNode;
|
|
13
|
+
/** Buttons, one primary at most. */
|
|
14
|
+
actions?: ReactNode;
|
|
15
|
+
/** `page` (default) for a whole view; `section` inside a card or table. */
|
|
16
|
+
size?: EmptyStateSize;
|
|
17
|
+
/** `danger` colours the icon for an error. Default `neutral`. */
|
|
18
|
+
tone?: EmptyStateTone;
|
|
19
|
+
/** Make the title a heading, when this is the page's main message. Otherwise it's styled text. */
|
|
20
|
+
level?: HeadingLevel;
|
|
21
|
+
}
|
|
22
|
+
/**
|
|
23
|
+
* The message where content would be: nothing yet, no results, or an error (07-components.md, C1).
|
|
24
|
+
* Centred, at most 480px wide. Server-safe.
|
|
25
|
+
*/
|
|
26
|
+
export declare function EmptyState({ icon: Icon, title, description, actions, size, tone, level, className, ...props }: EmptyStateProps): ReactElement;
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import { cx } from "../../internal/cx.js";
|
|
2
|
+
import { Heading } from "../../heading/heading.js";
|
|
3
|
+
import { jsx, jsxs } from "react/jsx-runtime";
|
|
4
|
+
// src/experimental/empty-state/empty-state.tsx
|
|
5
|
+
/**
|
|
6
|
+
* The message where content would be: nothing yet, no results, or an error (07-components.md, C1).
|
|
7
|
+
* Centred, at most 480px wide. Server-safe.
|
|
8
|
+
*/
|
|
9
|
+
function EmptyState({ icon: Icon, title, description, actions, size = "page", tone = "neutral", level, className, ...props }) {
|
|
10
|
+
return /* @__PURE__ */ jsxs("div", {
|
|
11
|
+
...props,
|
|
12
|
+
className: cx("qx-empty-state", className),
|
|
13
|
+
"data-slot": "empty-state",
|
|
14
|
+
"data-size": size,
|
|
15
|
+
"data-tone": tone,
|
|
16
|
+
children: [
|
|
17
|
+
Icon && /* @__PURE__ */ jsx(Icon, {
|
|
18
|
+
shape: "sharp",
|
|
19
|
+
size: 24,
|
|
20
|
+
"aria-hidden": true,
|
|
21
|
+
className: "qx-empty-state-icon"
|
|
22
|
+
}),
|
|
23
|
+
level !== void 0 ? /* @__PURE__ */ jsx(Heading, {
|
|
24
|
+
level,
|
|
25
|
+
size: size === "page" ? "title-md" : "title-sm",
|
|
26
|
+
align: "center",
|
|
27
|
+
children: title
|
|
28
|
+
}) : /* @__PURE__ */ jsx("p", {
|
|
29
|
+
className: "qx-empty-state-title",
|
|
30
|
+
"data-slot": "empty-state-title",
|
|
31
|
+
children: title
|
|
32
|
+
}),
|
|
33
|
+
description != null && /* @__PURE__ */ jsx("p", {
|
|
34
|
+
className: "qx-empty-state-description",
|
|
35
|
+
"data-slot": "empty-state-description",
|
|
36
|
+
children: description
|
|
37
|
+
}),
|
|
38
|
+
actions != null && /* @__PURE__ */ jsx("div", {
|
|
39
|
+
className: "qx-empty-state-actions",
|
|
40
|
+
"data-slot": "empty-state-actions",
|
|
41
|
+
children: actions
|
|
42
|
+
})
|
|
43
|
+
]
|
|
44
|
+
});
|
|
45
|
+
}
|
|
46
|
+
export { EmptyState };
|
|
@@ -0,0 +1,3 @@
|
|
|
1
|
+
import { DataState, DataStateProps } from "./data-state.js";
|
|
2
|
+
import { EmptyState, EmptyStateProps, EmptyStateSize, EmptyStateTone } from "./empty-state.js";
|
|
3
|
+
export { DataState, type DataStateProps, EmptyState, type EmptyStateProps, type EmptyStateSize, type EmptyStateTone };
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import { Alert, AlertProps, AlertTone } from "./alert/alert.js";
|
|
2
|
+
import "./alert/index.js";
|
|
3
|
+
import { AppShell, AppShellDensity, AppShellDrawer, AppShellProps, AppShellState, useAppShell } from "./app-shell/app-shell.js";
|
|
4
|
+
import "./app-shell/index.js";
|
|
5
|
+
import { Breadcrumb, BreadcrumbItem, BreadcrumbItemProps, BreadcrumbProps } from "./breadcrumb/breadcrumb.js";
|
|
6
|
+
import "./breadcrumb/index.js";
|
|
7
|
+
import { CopyButton, CopyButtonProps, CopyButtonState } from "./copy-button/copy-button.js";
|
|
8
|
+
import "./copy-button/index.js";
|
|
9
|
+
import { DataState, DataStateProps } from "./empty-state/data-state.js";
|
|
10
|
+
import { EmptyState, EmptyStateProps, EmptyStateSize, EmptyStateTone } from "./empty-state/empty-state.js";
|
|
11
|
+
import "./empty-state/index.js";
|
|
12
|
+
import { OTPInput, OTPInputProps, OTPInputSize } from "./otp-input/otp-input.js";
|
|
13
|
+
import "./otp-input/index.js";
|
|
14
|
+
import { PageContent, PageContentProps, PageContentWidth, PageHeader, PageHeaderProps } from "./page-header/page-header.js";
|
|
15
|
+
import "./page-header/index.js";
|
|
16
|
+
import { PasswordInput, PasswordInputProps } from "./password-input/password-input.js";
|
|
17
|
+
import "./password-input/index.js";
|
|
18
|
+
import { Sidebar, SidebarGroup, SidebarGroupProps, SidebarItem, SidebarItemProps, SidebarProps } from "./sidebar/sidebar.js";
|
|
19
|
+
import "./sidebar/index.js";
|
|
20
|
+
import { Stat, StatGroup, StatGroupProps, StatProps, StatTone } from "./stat/stat.js";
|
|
21
|
+
import "./stat/index.js";
|
|
22
|
+
import { TopBar, TopBarProps, TopBarSearch, TopBarSearchProps, TopBarStatus, TopBarSwitcher, TopBarSwitcherProps } from "./top-bar/top-bar.js";
|
|
23
|
+
import "./top-bar/index.js";
|
|
24
|
+
export { Alert, type AlertProps, type AlertTone, AppShell, type AppShellDensity, type AppShellDrawer, type AppShellProps, type AppShellState, Breadcrumb, BreadcrumbItem, type BreadcrumbItemProps, type BreadcrumbProps, CopyButton, type CopyButtonProps, type CopyButtonState, DataState, type DataStateProps, EmptyState, type EmptyStateProps, type EmptyStateSize, type EmptyStateTone, OTPInput, type OTPInputProps, type OTPInputSize, PageContent, type PageContentProps, type PageContentWidth, PageHeader, type PageHeaderProps, PasswordInput, type PasswordInputProps, Sidebar, SidebarGroup, type SidebarGroupProps, SidebarItem, type SidebarItemProps, type SidebarProps, Stat, StatGroup, type StatGroupProps, type StatProps, type StatTone, TopBar, type TopBarProps, TopBarSearch, type TopBarSearchProps, type TopBarStatus, TopBarSwitcher, type TopBarSwitcherProps, useAppShell };
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { Alert } from "./alert/alert.js";
|
|
2
|
+
import { AppShell, useAppShell } from "./app-shell/app-shell.js";
|
|
3
|
+
import { Breadcrumb, BreadcrumbItem } from "./breadcrumb/breadcrumb.js";
|
|
4
|
+
import { CopyButton } from "./copy-button/copy-button.js";
|
|
5
|
+
import { EmptyState } from "./empty-state/empty-state.js";
|
|
6
|
+
import { DataState } from "./empty-state/data-state.js";
|
|
7
|
+
import { OTPInput } from "./otp-input/otp-input.js";
|
|
8
|
+
import { PageContent, PageHeader } from "./page-header/page-header.js";
|
|
9
|
+
import { PasswordInput } from "./password-input/password-input.js";
|
|
10
|
+
import { Sidebar, SidebarGroup, SidebarItem } from "./sidebar/sidebar.js";
|
|
11
|
+
import { Stat, StatGroup } from "./stat/stat.js";
|
|
12
|
+
import { TopBar, TopBarSearch, TopBarSwitcher } from "./top-bar/top-bar.js";
|
|
13
|
+
export { Alert, AppShell, Breadcrumb, BreadcrumbItem, CopyButton, DataState, EmptyState, OTPInput, PageContent, PageHeader, PasswordInput, Sidebar, SidebarGroup, SidebarItem, Stat, StatGroup, TopBar, TopBarSearch, TopBarSwitcher, useAppShell };
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import { QeetrixMessages } from "../../internal/messages.js";
|
|
2
|
+
import { CSSProperties, ReactElement } from "react";
|
|
3
|
+
import { OTPField } from "@base-ui/react/otp-field";
|
|
4
|
+
export type OTPInputSize = "md" | "lg";
|
|
5
|
+
export interface OTPInputProps extends Omit<OTPField.Root.Props, "className" | "style" | "children" | "render" | "length"> {
|
|
6
|
+
/** How many characters. Default 6. */
|
|
7
|
+
length?: number;
|
|
8
|
+
/** Split the slots into groups, e.g. `[3, 3]` shows 123 – 456. The sizes must add up to `length`. */
|
|
9
|
+
groups?: readonly number[];
|
|
10
|
+
/** `md` (default) in consoles; `lg` on sign-in pages. */
|
|
11
|
+
size?: OTPInputSize;
|
|
12
|
+
/** Names the first slot when there's no Field label. */
|
|
13
|
+
"aria-label"?: string;
|
|
14
|
+
"aria-labelledby"?: string;
|
|
15
|
+
/** Placement only. */
|
|
16
|
+
className?: string;
|
|
17
|
+
style?: CSSProperties;
|
|
18
|
+
/** This input's strings, over QeetrixProvider's and the English defaults. */
|
|
19
|
+
messages?: Partial<QeetrixMessages["otpInput"]>;
|
|
20
|
+
}
|
|
21
|
+
/**
|
|
22
|
+
* A one-time code in separate slots (07-components.md, C2), on Base UI's OTP Field: typing moves on,
|
|
23
|
+
* Backspace moves back, pasting fills every slot, and the phone's SMS autofill
|
|
24
|
+
* (`autocomplete="one-time-code"`) lands in one go. Numeric by default. The first slot takes the
|
|
25
|
+
* field's label; the others are announced as "Character 2 of 6".
|
|
26
|
+
*/
|
|
27
|
+
export declare function OTPInput({ length, groups, size, "aria-label": ariaLabel, "aria-labelledby": ariaLabelledBy, className, style, messages, ...props }: OTPInputProps): ReactElement;
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { cx } from "../../internal/cx.js";
|
|
3
|
+
import { useMessages } from "../../provider/provider.js";
|
|
4
|
+
import { jsx, jsxs } from "react/jsx-runtime";
|
|
5
|
+
import { Fragment as Fragment$1 } from "react";
|
|
6
|
+
import { OTPField } from "@base-ui/react/otp-field";
|
|
7
|
+
// src/experimental/otp-input/otp-input.tsx
|
|
8
|
+
/**
|
|
9
|
+
* A one-time code in separate slots (07-components.md, C2), on Base UI's OTP Field: typing moves on,
|
|
10
|
+
* Backspace moves back, pasting fills every slot, and the phone's SMS autofill
|
|
11
|
+
* (`autocomplete="one-time-code"`) lands in one go. Numeric by default. The first slot takes the
|
|
12
|
+
* field's label; the others are announced as "Character 2 of 6".
|
|
13
|
+
*/
|
|
14
|
+
function OTPInput({ length = 6, groups, size = "md", "aria-label": ariaLabel, "aria-labelledby": ariaLabelledBy, className, style, messages, ...props }) {
|
|
15
|
+
const strings = useMessages("otpInput", messages);
|
|
16
|
+
const breaks = /* @__PURE__ */ new Set();
|
|
17
|
+
let at = 0;
|
|
18
|
+
for (const group of groups ?? []) {
|
|
19
|
+
at += group;
|
|
20
|
+
if (at < length) breaks.add(at);
|
|
21
|
+
}
|
|
22
|
+
return /* @__PURE__ */ jsx(OTPField.Root, {
|
|
23
|
+
...props,
|
|
24
|
+
length,
|
|
25
|
+
className: cx("qx-otp-input", className),
|
|
26
|
+
style,
|
|
27
|
+
"data-slot": "otp-input",
|
|
28
|
+
"data-size": size,
|
|
29
|
+
children: Array.from({ length }, (_, index) => /* @__PURE__ */ jsxs(Fragment$1, { children: [breaks.has(index) && /* @__PURE__ */ jsx(OTPField.Separator, { className: "qx-otp-input-separator" }), /* @__PURE__ */ jsx(OTPField.Input, {
|
|
30
|
+
className: "qx-otp-input-slot",
|
|
31
|
+
"data-slot": "otp-input-slot",
|
|
32
|
+
...index === 0 ? {
|
|
33
|
+
"aria-label": ariaLabel,
|
|
34
|
+
"aria-labelledby": ariaLabelledBy
|
|
35
|
+
} : { "aria-label": strings.character.replace("{index}", String(index + 1)).replace("{length}", String(length)) }
|
|
36
|
+
})] }, index))
|
|
37
|
+
});
|
|
38
|
+
}
|
|
39
|
+
export { OTPInput };
|