@qeetrix/react 0.0.2 → 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 +116 -1
- package/README.md +99 -0
- package/dist/button/button.d.ts +6 -2
- package/dist/button/button.js +5 -1
- package/dist/css/avatar.css +6 -0
- package/dist/css/badge.css +6 -0
- package/dist/css/button.css +81 -6
- package/dist/css/checkbox.css +9 -0
- package/dist/css/dialog.css +7 -0
- package/dist/css/drawer.css +6 -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/input.css +10 -0
- package/dist/css/menu.css +17 -0
- package/dist/css/progress.css +14 -0
- package/dist/css/scroll-area.css +7 -0
- package/dist/css/select.css +16 -0
- package/dist/css/separator.css +7 -0
- package/dist/css/skeleton.css +6 -0
- package/dist/css/slider.css +22 -0
- package/dist/css/switch.css +33 -0
- package/dist/css/tabs.css +11 -0
- package/dist/css/toast.css +7 -0
- package/dist/css/toggle.css +13 -0
- package/dist/css/tooltip.css +6 -0
- package/dist/dialog/dialog.d.ts +4 -1
- package/dist/dialog/dialog.js +4 -3
- package/dist/drawer/drawer.d.ts +4 -1
- package/dist/drawer/drawer.js +5 -4
- 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/field.d.ts +4 -1
- package/dist/field/field.js +6 -5
- 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/index.d.ts +6 -2
- package/dist/index.js +3 -1
- package/dist/internal/media.js +15 -0
- package/dist/internal/messages.d.ts +75 -0
- package/dist/internal/messages.js +44 -5
- package/dist/link/link.d.ts +7 -1
- package/dist/link/link.js +3 -2
- package/dist/locales/hi.d.ts +8 -0
- package/dist/locales/hi.js +60 -0
- package/dist/progress/progress.d.ts +2 -2
- package/dist/progress/progress.js +7 -2
- 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/slider/slider.d.ts +5 -2
- package/dist/slider/slider.js +10 -3
- package/dist/styles.css +1704 -104
- package/dist/text-field/text-field.d.ts +2 -2
- package/dist/text-field/text-field.js +4 -2
- package/dist/toast/toast.d.ts +4 -1
- package/dist/toast/toast.js +7 -6
- package/package.json +18 -2
package/dist/css/select.css
CHANGED
|
@@ -170,4 +170,20 @@
|
|
|
170
170
|
font-variation-settings: "GEOM" var(--qx-font-geometry-body);
|
|
171
171
|
color: var(--qx-color-text-subtle);
|
|
172
172
|
}
|
|
173
|
+
|
|
174
|
+
@media (forced-colors: active) {
|
|
175
|
+
.qx-select-trigger {
|
|
176
|
+
border: var(--qx-border-width-default) solid ButtonText;
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
.qx-select-option[data-highlighted] {
|
|
180
|
+
forced-color-adjust: none;
|
|
181
|
+
color: highlighttext;
|
|
182
|
+
background: highlight;
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
.qx-select-option[data-highlighted] .qx-select-option-check {
|
|
186
|
+
color: highlighttext;
|
|
187
|
+
}
|
|
188
|
+
}
|
|
173
189
|
}
|
package/dist/css/separator.css
CHANGED
package/dist/css/skeleton.css
CHANGED
package/dist/css/slider.css
CHANGED
|
@@ -83,4 +83,26 @@
|
|
|
83
83
|
background: var(--qx-color-icon-disabled);
|
|
84
84
|
cursor: not-allowed;
|
|
85
85
|
}
|
|
86
|
+
|
|
87
|
+
@media (forced-colors: active) {
|
|
88
|
+
.qx-slider-track, .qx-slider-indicator, .qx-slider-thumb {
|
|
89
|
+
forced-color-adjust: none;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
.qx-slider-track {
|
|
93
|
+
background: graytext;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
.qx-slider-indicator, .qx-slider-thumb {
|
|
97
|
+
background: highlight;
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
.qx-slider-thumb {
|
|
101
|
+
box-shadow: 0 0 0 var(--qx-border-width-strong) Canvas;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
.qx-slider-thumb[data-disabled], [data-disabled] .qx-slider-indicator {
|
|
105
|
+
background: graytext;
|
|
106
|
+
}
|
|
107
|
+
}
|
|
86
108
|
}
|
package/dist/css/switch.css
CHANGED
|
@@ -76,4 +76,37 @@
|
|
|
76
76
|
.qx-choice-label > .qx-switch {
|
|
77
77
|
margin-block: 1px;
|
|
78
78
|
}
|
|
79
|
+
|
|
80
|
+
@media (forced-colors: active) {
|
|
81
|
+
.qx-switch, .qx-switch-thumb {
|
|
82
|
+
forced-color-adjust: none;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
.qx-switch {
|
|
86
|
+
background: canvas;
|
|
87
|
+
border-color: canvastext;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
.qx-switch-thumb {
|
|
91
|
+
background: canvastext;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
.qx-switch[data-checked] {
|
|
95
|
+
background: highlight;
|
|
96
|
+
border-color: highlight;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
.qx-switch-thumb[data-checked] {
|
|
100
|
+
background: highlighttext;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
.qx-switch[data-disabled] {
|
|
104
|
+
background: canvas;
|
|
105
|
+
border-color: graytext;
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
.qx-switch-thumb[data-disabled] {
|
|
109
|
+
background: graytext;
|
|
110
|
+
}
|
|
111
|
+
}
|
|
79
112
|
}
|
package/dist/css/tabs.css
CHANGED
|
@@ -109,4 +109,15 @@
|
|
|
109
109
|
outline: var(--qx-focus-ring-width) solid var(--qx-color-border-focus);
|
|
110
110
|
outline-offset: var(--qx-focus-ring-offset);
|
|
111
111
|
}
|
|
112
|
+
|
|
113
|
+
@media (forced-colors: active) {
|
|
114
|
+
.qx-tabs-list:not([data-orientation="vertical"]) {
|
|
115
|
+
border-block-end: var(--qx-border-width-strong) solid CanvasText;
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
.qx-tabs-indicator {
|
|
119
|
+
forced-color-adjust: none;
|
|
120
|
+
background: highlight;
|
|
121
|
+
}
|
|
122
|
+
}
|
|
112
123
|
}
|
package/dist/css/toast.css
CHANGED
package/dist/css/toggle.css
CHANGED
|
@@ -109,4 +109,17 @@
|
|
|
109
109
|
.qx-toggle-group[data-variant="segmented"][data-size="lg"] > .qx-toggle {
|
|
110
110
|
block-size: calc(var(--qx-size-control-lg) - 2 * var(--qx-border-width-default));
|
|
111
111
|
}
|
|
112
|
+
|
|
113
|
+
@media (forced-colors: active) {
|
|
114
|
+
.qx-toggle {
|
|
115
|
+
border: var(--qx-border-width-default) solid ButtonText;
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
.qx-toggle[data-pressed] {
|
|
119
|
+
forced-color-adjust: none;
|
|
120
|
+
color: highlighttext;
|
|
121
|
+
background: highlight;
|
|
122
|
+
border-color: highlight;
|
|
123
|
+
}
|
|
124
|
+
}
|
|
112
125
|
}
|
package/dist/css/tooltip.css
CHANGED
package/dist/dialog/dialog.d.ts
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { QeetrixMessages } from "../internal/messages.js";
|
|
1
2
|
import { CSSProperties, ComponentProps, ReactElement, ReactNode } from "react";
|
|
2
3
|
import { Dialog } from "@base-ui/react/dialog";
|
|
3
4
|
/** The kind of decision: `standard`, or a `danger` / `warning` alert dialog. */
|
|
@@ -24,6 +25,8 @@ export interface DialogContentProps extends Omit<Dialog.Popup.Props, "className"
|
|
|
24
25
|
description?: ReactNode;
|
|
25
26
|
/** Width: `sm` 400, `md` 560 (default) or `lg` 800px. */
|
|
26
27
|
size?: DialogSize;
|
|
28
|
+
/** The close button's label, over QeetrixProvider's and the English default. */
|
|
29
|
+
messages?: Partial<QeetrixMessages["overlay"]>;
|
|
27
30
|
/** Placement only. */
|
|
28
31
|
className?: string;
|
|
29
32
|
style?: CSSProperties;
|
|
@@ -43,7 +46,7 @@ export declare function DialogClose(props: DialogCloseProps): ReactElement;
|
|
|
43
46
|
* body and footer. The close button comes last in the DOM, so initial focus lands on the first field
|
|
44
47
|
* or else the footer's first (least destructive) action.
|
|
45
48
|
*/
|
|
46
|
-
export declare function DialogContent({ title, description, size, className, children, ...props }: DialogContentProps): ReactElement;
|
|
49
|
+
export declare function DialogContent({ title, description, size, messages, className, children, ...props }: DialogContentProps): ReactElement;
|
|
47
50
|
/** The dialog's content; it scrolls when the dialog reaches its maximum height. */
|
|
48
51
|
export declare function DialogBody({ className, ...props }: DialogBodyProps): ReactElement;
|
|
49
52
|
/** The actions, end-aligned: tertiary first, then the primary or destructive action. */
|
package/dist/dialog/dialog.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
"use client";
|
|
2
2
|
import { cx } from "../internal/cx.js";
|
|
3
|
-
import { messages } from "../internal/messages.js";
|
|
4
3
|
import { IconButton } from "../button/button.js";
|
|
4
|
+
import { useMessages } from "../provider/provider.js";
|
|
5
5
|
import { jsx, jsxs } from "react/jsx-runtime";
|
|
6
6
|
import { createContext, use } from "react";
|
|
7
7
|
import { AlertDialog } from "@base-ui/react/alert-dialog";
|
|
@@ -59,8 +59,9 @@ function DialogClose(props) {
|
|
|
59
59
|
* body and footer. The close button comes last in the DOM, so initial focus lands on the first field
|
|
60
60
|
* or else the footer's first (least destructive) action.
|
|
61
61
|
*/
|
|
62
|
-
function DialogContent({ title, description, size = "md", className, children, ...props }) {
|
|
62
|
+
function DialogContent({ title, description, size = "md", messages, className, children, ...props }) {
|
|
63
63
|
const { intent, dismissible } = use(DialogContext);
|
|
64
|
+
const strings = useMessages("overlay", messages);
|
|
64
65
|
return /* @__PURE__ */ jsxs(Dialog.Portal, { children: [/* @__PURE__ */ jsx(Dialog.Backdrop, {
|
|
65
66
|
className: "qx-dialog-backdrop",
|
|
66
67
|
"data-slot": "dialog-backdrop"
|
|
@@ -93,7 +94,7 @@ function DialogContent({ title, description, size = "md", className, children, .
|
|
|
93
94
|
"data-slot": "dialog-close",
|
|
94
95
|
children: /* @__PURE__ */ jsx(Dialog.Close, { render: /* @__PURE__ */ jsx(IconButton, {
|
|
95
96
|
icon: XIcon,
|
|
96
|
-
"aria-label":
|
|
97
|
+
"aria-label": strings.close,
|
|
97
98
|
size: "sm"
|
|
98
99
|
}) })
|
|
99
100
|
})
|
package/dist/drawer/drawer.d.ts
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { QeetrixMessages } from "../internal/messages.js";
|
|
1
2
|
import { CSSProperties, ComponentProps, ReactElement, ReactNode } from "react";
|
|
2
3
|
import { Drawer } from "@base-ui/react/drawer";
|
|
3
4
|
/** The edge a drawer opens from, in the text direction: `end` is the right edge in English. */
|
|
@@ -16,6 +17,8 @@ export interface DrawerContentProps extends Omit<Drawer.Popup.Props, "className"
|
|
|
16
17
|
description?: ReactNode;
|
|
17
18
|
/** Width: `sm` 400, `md` 560 (default) or `lg` 800px. */
|
|
18
19
|
size?: DrawerSize;
|
|
20
|
+
/** The close button's label, over QeetrixProvider's and the English default. */
|
|
21
|
+
messages?: Partial<QeetrixMessages["overlay"]>;
|
|
19
22
|
/** Placement only. */
|
|
20
23
|
className?: string;
|
|
21
24
|
style?: CSSProperties;
|
|
@@ -38,7 +41,7 @@ export declare function DrawerClose(props: DrawerCloseProps): ReactElement;
|
|
|
38
41
|
* focus goes to the first focusable element in the body, as in a dialog; on touch it goes to the
|
|
39
42
|
* drawer itself, so a phone's keyboard doesn't open over it.
|
|
40
43
|
*/
|
|
41
|
-
export declare function DrawerContent({ title, description, size, initialFocus, ref, className, children, ...props }: DrawerContentProps): ReactElement;
|
|
44
|
+
export declare function DrawerContent({ title, description, size, messages, initialFocus, ref, className, children, ...props }: DrawerContentProps): ReactElement;
|
|
42
45
|
/** The drawer's content; it scrolls, and text in it can be selected without starting a swipe. */
|
|
43
46
|
export declare function DrawerBody({ className, ...props }: DrawerBodyProps): ReactElement;
|
|
44
47
|
/** The actions, end-aligned under a rule: tertiary first, then the primary action. */
|
package/dist/drawer/drawer.js
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
"use client";
|
|
2
2
|
import { cx } from "../internal/cx.js";
|
|
3
|
-
import { messages } from "../internal/messages.js";
|
|
4
3
|
import { IconButton } from "../button/button.js";
|
|
4
|
+
import { useMessages } from "../provider/provider.js";
|
|
5
5
|
import { mergeRefs } from "../internal/refs.js";
|
|
6
6
|
import { jsx, jsxs } from "react/jsx-runtime";
|
|
7
7
|
import { createContext, use, useRef } from "react";
|
|
8
|
-
import { XIcon } from "@qeetrix/icons/icons/x";
|
|
9
8
|
import { useDirection } from "@base-ui/react/direction-provider";
|
|
9
|
+
import { XIcon } from "@qeetrix/icons/icons/x";
|
|
10
10
|
import { Drawer } from "@base-ui/react/drawer";
|
|
11
11
|
// src/drawer/drawer.tsx
|
|
12
12
|
const SideContext = createContext("end");
|
|
@@ -37,8 +37,9 @@ function DrawerClose(props) {
|
|
|
37
37
|
* focus goes to the first focusable element in the body, as in a dialog; on touch it goes to the
|
|
38
38
|
* drawer itself, so a phone's keyboard doesn't open over it.
|
|
39
39
|
*/
|
|
40
|
-
function DrawerContent({ title, description, size = "md", initialFocus, ref, className, children, ...props }) {
|
|
40
|
+
function DrawerContent({ title, description, size = "md", messages, initialFocus, ref, className, children, ...props }) {
|
|
41
41
|
const side = use(SideContext);
|
|
42
|
+
const strings = useMessages("overlay", messages);
|
|
42
43
|
const popup = useRef(null);
|
|
43
44
|
const firstInBody = (type) => type === "touch" || (popup.current?.querySelector(`[data-slot="drawer-body"] :is(${FOCUSABLE})`) ?? true);
|
|
44
45
|
return /* @__PURE__ */ jsxs(Drawer.Portal, { children: [/* @__PURE__ */ jsx(Drawer.Backdrop, {
|
|
@@ -74,7 +75,7 @@ function DrawerContent({ title, description, size = "md", initialFocus, ref, cla
|
|
|
74
75
|
"data-slot": "drawer-close",
|
|
75
76
|
children: /* @__PURE__ */ jsx(Drawer.Close, { render: /* @__PURE__ */ jsx(IconButton, {
|
|
76
77
|
icon: XIcon,
|
|
77
|
-
"aria-label":
|
|
78
|
+
"aria-label": strings.close,
|
|
78
79
|
size: "sm"
|
|
79
80
|
}) })
|
|
80
81
|
})
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import { QeetrixMessages } from "../../internal/messages.js";
|
|
2
|
+
import { ComponentProps, ReactElement, ReactNode } from "react";
|
|
3
|
+
export type AlertTone = "info" | "success" | "warning" | "danger";
|
|
4
|
+
export interface AlertProps extends Omit<ComponentProps<"div">, "title"> {
|
|
5
|
+
/** Default `info`. The icon and a hidden word ("Warning:") carry it, never colour alone. */
|
|
6
|
+
tone?: AlertTone;
|
|
7
|
+
/** What happened, in a few words: "Directory sync failed". */
|
|
8
|
+
title: ReactNode;
|
|
9
|
+
/** One or two sentences: why, and what to do. */
|
|
10
|
+
children?: ReactNode;
|
|
11
|
+
/** Buttons or links under the text: one primary at most. */
|
|
12
|
+
actions?: ReactNode;
|
|
13
|
+
/** Adds a Dismiss button. The product decides whether the alert comes back. */
|
|
14
|
+
onDismiss?: () => void;
|
|
15
|
+
/**
|
|
16
|
+
* Only for an alert that appears after something happens: `polite` (`role="status"`) waits its
|
|
17
|
+
* turn; `urgent` (`role="alert"`) interrupts, for errors that block the task. An alert that is
|
|
18
|
+
* there when the page loads needs neither.
|
|
19
|
+
*/
|
|
20
|
+
announce?: "polite" | "urgent";
|
|
21
|
+
/** This alert's strings, over QeetrixProvider's and the English defaults. */
|
|
22
|
+
messages?: Partial<QeetrixMessages["alert"]>;
|
|
23
|
+
}
|
|
24
|
+
/**
|
|
25
|
+
* A message in the page (07-components.md, C2): above a form, inside a card, or across the top of
|
|
26
|
+
* a console page in AppShell's `notifications`. A tinted surface with the 3px status edge on its
|
|
27
|
+
* start side. It never takes focus and never disappears by itself.
|
|
28
|
+
*/
|
|
29
|
+
export declare function Alert({ tone, title, children, actions, onDismiss, announce, messages, className, ...props }: AlertProps): ReactElement;
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { cx } from "../../internal/cx.js";
|
|
3
|
+
import { IconButton } from "../../button/button.js";
|
|
4
|
+
import { useMessages } from "../../provider/provider.js";
|
|
5
|
+
import { VisuallyHidden } from "../../visually-hidden/visually-hidden.js";
|
|
6
|
+
import { jsx, jsxs } from "react/jsx-runtime";
|
|
7
|
+
import { CircleAlertIcon } from "@qeetrix/icons/icons/circle-alert";
|
|
8
|
+
import { XIcon } from "@qeetrix/icons/icons/x";
|
|
9
|
+
import { CircleCheckIcon } from "@qeetrix/icons/icons/circle-check";
|
|
10
|
+
import { InfoIcon } from "@qeetrix/icons/icons/info";
|
|
11
|
+
import { TriangleAlertIcon } from "@qeetrix/icons/icons/triangle-alert";
|
|
12
|
+
// src/experimental/alert/alert.tsx
|
|
13
|
+
const ICONS = {
|
|
14
|
+
info: InfoIcon,
|
|
15
|
+
success: CircleCheckIcon,
|
|
16
|
+
warning: TriangleAlertIcon,
|
|
17
|
+
danger: CircleAlertIcon
|
|
18
|
+
};
|
|
19
|
+
/**
|
|
20
|
+
* A message in the page (07-components.md, C2): above a form, inside a card, or across the top of
|
|
21
|
+
* a console page in AppShell's `notifications`. A tinted surface with the 3px status edge on its
|
|
22
|
+
* start side. It never takes focus and never disappears by itself.
|
|
23
|
+
*/
|
|
24
|
+
function Alert({ tone = "info", title, children, actions, onDismiss, announce, messages, className, ...props }) {
|
|
25
|
+
const strings = useMessages("alert", messages);
|
|
26
|
+
const Icon = ICONS[tone];
|
|
27
|
+
return /* @__PURE__ */ jsxs("div", {
|
|
28
|
+
...props,
|
|
29
|
+
role: announce === "urgent" ? "alert" : announce === "polite" ? "status" : void 0,
|
|
30
|
+
className: cx("qx-alert", className),
|
|
31
|
+
"data-slot": "alert",
|
|
32
|
+
"data-tone": tone,
|
|
33
|
+
children: [
|
|
34
|
+
/* @__PURE__ */ jsx(Icon, {
|
|
35
|
+
shape: "sharp",
|
|
36
|
+
size: 16,
|
|
37
|
+
"aria-hidden": true,
|
|
38
|
+
className: "qx-alert-icon"
|
|
39
|
+
}),
|
|
40
|
+
/* @__PURE__ */ jsxs("div", {
|
|
41
|
+
className: "qx-alert-body",
|
|
42
|
+
children: [
|
|
43
|
+
/* @__PURE__ */ jsxs("div", {
|
|
44
|
+
className: "qx-alert-title",
|
|
45
|
+
"data-slot": "alert-title",
|
|
46
|
+
children: [/* @__PURE__ */ jsx(VisuallyHidden, { children: `${strings[tone]}: ` }), title]
|
|
47
|
+
}),
|
|
48
|
+
children != null && /* @__PURE__ */ jsx("div", {
|
|
49
|
+
className: "qx-alert-description",
|
|
50
|
+
"data-slot": "alert-description",
|
|
51
|
+
children
|
|
52
|
+
}),
|
|
53
|
+
actions != null && /* @__PURE__ */ jsx("div", {
|
|
54
|
+
className: "qx-alert-actions",
|
|
55
|
+
"data-slot": "alert-actions",
|
|
56
|
+
children: actions
|
|
57
|
+
})
|
|
58
|
+
]
|
|
59
|
+
}),
|
|
60
|
+
onDismiss && /* @__PURE__ */ jsx(IconButton, {
|
|
61
|
+
icon: XIcon,
|
|
62
|
+
"aria-label": strings.dismiss,
|
|
63
|
+
size: "sm",
|
|
64
|
+
tone: "neutral",
|
|
65
|
+
tooltip: false,
|
|
66
|
+
onClick: onDismiss,
|
|
67
|
+
className: "qx-alert-dismiss"
|
|
68
|
+
})
|
|
69
|
+
]
|
|
70
|
+
});
|
|
71
|
+
}
|
|
72
|
+
export { Alert };
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
import { QeetrixIcon } from "../../internal/control.js";
|
|
2
|
+
import { QeetrixMessages } from "../../internal/messages.js";
|
|
3
|
+
import { ComponentProps, ReactElement, ReactNode } from "react";
|
|
4
|
+
/** The frame's density; `inherit` leaves it to the page. */
|
|
5
|
+
export type AppShellDensity = "compact" | "default" | "comfortable" | "inherit";
|
|
6
|
+
/** An end-edge panel: help, the assistant, activity. */
|
|
7
|
+
export interface AppShellDrawer {
|
|
8
|
+
id: string;
|
|
9
|
+
/** The trigger's label and the panel's title. */
|
|
10
|
+
label: string;
|
|
11
|
+
/** The trigger's icon, from `@qeetrix/icons`. */
|
|
12
|
+
icon: QeetrixIcon;
|
|
13
|
+
/** The panel's content. */
|
|
14
|
+
content: ReactNode;
|
|
15
|
+
/** A dot on the trigger: something new inside. */
|
|
16
|
+
badge?: boolean;
|
|
17
|
+
}
|
|
18
|
+
export interface AppShellProps extends Omit<ComponentProps<"div">, "children"> {
|
|
19
|
+
/** The global bar: a TopBar. */
|
|
20
|
+
topBar?: ReactNode;
|
|
21
|
+
/** The navigation: a Sidebar. */
|
|
22
|
+
sidebar?: ReactNode;
|
|
23
|
+
/** End-edge panels: help, the assistant, activity. Their triggers sit in the TopBar. */
|
|
24
|
+
drawers?: AppShellDrawer[];
|
|
25
|
+
/** The open panel's id, or `null`, controlled. */
|
|
26
|
+
activeDrawer?: string | null;
|
|
27
|
+
/** The open panel when uncontrolled. Default none. */
|
|
28
|
+
defaultActiveDrawer?: string | null;
|
|
29
|
+
/** Called with the panel that opens, or `null` when it closes. */
|
|
30
|
+
onActiveDrawerChange?: (id: string | null) => void;
|
|
31
|
+
/** Page messages (Alerts) above the page header. */
|
|
32
|
+
notifications?: ReactNode;
|
|
33
|
+
/** The frame's density. Default `compact`: consoles are dense (design rules §15). */
|
|
34
|
+
density?: AppShellDensity;
|
|
35
|
+
/** The page: a PageHeader and PageContent. */
|
|
36
|
+
children?: ReactNode;
|
|
37
|
+
/** Whether the sidebar is collapsed to its icon rail, controlled. */
|
|
38
|
+
sidebarCollapsed?: boolean;
|
|
39
|
+
/** The initial collapsed state when uncontrolled. Default `false`. */
|
|
40
|
+
defaultSidebarCollapsed?: boolean;
|
|
41
|
+
/** Called with the new collapsed state. Persist it in the product, e.g. the user's settings. */
|
|
42
|
+
onSidebarCollapsedChange?: (collapsed: boolean) => void;
|
|
43
|
+
/** The main region's id, which the built-in skip link targets. Default `main`. */
|
|
44
|
+
mainId?: string;
|
|
45
|
+
/** The shell's strings, over QeetrixProvider's and the English defaults. */
|
|
46
|
+
messages?: Partial<QeetrixMessages["appShell"]>;
|
|
47
|
+
}
|
|
48
|
+
export interface AppShellState {
|
|
49
|
+
/** The sidebar is showing as its icon rail. */
|
|
50
|
+
collapsed: boolean;
|
|
51
|
+
}
|
|
52
|
+
/** The AppShell's state, for content inside its sidebar or bar. */
|
|
53
|
+
export declare function useAppShell(): AppShellState;
|
|
54
|
+
/**
|
|
55
|
+
* The frame of a Qeet console (07-components.md, C1 · Ruled): the black scope bar across the full
|
|
56
|
+
* width; the ruled navigation column, which collapses to a 48px icon rail that expands while
|
|
57
|
+
* hovered or focused; the page; and a panel on the end edge that the bar's tools open. Seams are
|
|
58
|
+
* 1px rules, never shadows. Compact by default.
|
|
59
|
+
*/
|
|
60
|
+
export declare function AppShell({ topBar, sidebar, drawers, activeDrawer, defaultActiveDrawer, onActiveDrawerChange, notifications, density, children, sidebarCollapsed, defaultSidebarCollapsed, onSidebarCollapsedChange, mainId, messages, className, ...props }: AppShellProps): ReactElement;
|
|
@@ -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 };
|