@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,32 @@
|
|
|
1
|
+
import { ComponentProps, ReactElement, ReactNode } from "react";
|
|
2
|
+
export interface PageHeaderProps extends Omit<ComponentProps<"div">, "title"> {
|
|
3
|
+
/** Where the page sits: a Breadcrumb. */
|
|
4
|
+
breadcrumb?: ReactNode;
|
|
5
|
+
/** The page title: the page's one `<h1>`. */
|
|
6
|
+
title: ReactNode;
|
|
7
|
+
/** A number beside the title, e.g. the table's total; numbers are formatted in `en-IN`. */
|
|
8
|
+
count?: ReactNode;
|
|
9
|
+
/** A Badge beside the title. */
|
|
10
|
+
status?: ReactNode;
|
|
11
|
+
/** One line under the title. */
|
|
12
|
+
description?: ReactNode;
|
|
13
|
+
/** Buttons at the end: the primary last; at most one solid and one soft, the rest in a menu. */
|
|
14
|
+
actions?: ReactNode;
|
|
15
|
+
/** Key numbers for the page: a StatGroup, ruled edge to edge under the title. */
|
|
16
|
+
summary?: ReactNode;
|
|
17
|
+
/** A TabsList, attached to the header's foot. */
|
|
18
|
+
tabs?: ReactNode;
|
|
19
|
+
}
|
|
20
|
+
export type PageContentWidth = "full" | "narrow";
|
|
21
|
+
export interface PageContentProps extends ComponentProps<"div"> {
|
|
22
|
+
/** `full` (default) for tables and dashboards; `narrow` (800px) for forms and settings. */
|
|
23
|
+
width?: PageContentWidth;
|
|
24
|
+
}
|
|
25
|
+
/**
|
|
26
|
+
* The head of a console page (07-components.md, C1 · Ruled): breadcrumb, title with its count and
|
|
27
|
+
* status, actions, an optional strip of key numbers, and tabs, on a ruled band above the work
|
|
28
|
+
* surface. Server-safe.
|
|
29
|
+
*/
|
|
30
|
+
export declare function PageHeader({ breadcrumb, title, count, status, description, actions, summary, tabs, className, ...props }: PageHeaderProps): ReactElement;
|
|
31
|
+
/** The work surface under a PageHeader. Server-safe. */
|
|
32
|
+
export declare function PageContent({ width, className, ...props }: PageContentProps): ReactElement;
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
import { cx } from "../../internal/cx.js";
|
|
2
|
+
import { Heading } from "../../heading/heading.js";
|
|
3
|
+
import { formatNumber } from "../../format/format.js";
|
|
4
|
+
import { jsx, jsxs } from "react/jsx-runtime";
|
|
5
|
+
// src/experimental/page-header/page-header.tsx
|
|
6
|
+
/**
|
|
7
|
+
* The head of a console page (07-components.md, C1 · Ruled): breadcrumb, title with its count and
|
|
8
|
+
* status, actions, an optional strip of key numbers, and tabs, on a ruled band above the work
|
|
9
|
+
* surface. Server-safe.
|
|
10
|
+
*/
|
|
11
|
+
function PageHeader({ breadcrumb, title, count, status, description, actions, summary, tabs, className, ...props }) {
|
|
12
|
+
return /* @__PURE__ */ jsxs("div", {
|
|
13
|
+
...props,
|
|
14
|
+
className: cx("qx-page-header", className),
|
|
15
|
+
"data-slot": "page-header",
|
|
16
|
+
"data-foot": tabs != null ? "tabs" : summary != null ? "summary" : void 0,
|
|
17
|
+
children: [
|
|
18
|
+
breadcrumb != null && /* @__PURE__ */ jsx("div", {
|
|
19
|
+
className: "qx-page-header-breadcrumb",
|
|
20
|
+
children: breadcrumb
|
|
21
|
+
}),
|
|
22
|
+
/* @__PURE__ */ jsxs("div", {
|
|
23
|
+
className: "qx-page-header-row",
|
|
24
|
+
children: [/* @__PURE__ */ jsxs("div", {
|
|
25
|
+
className: "qx-page-header-titles",
|
|
26
|
+
children: [/* @__PURE__ */ jsxs("div", {
|
|
27
|
+
className: "qx-page-header-title",
|
|
28
|
+
children: [
|
|
29
|
+
/* @__PURE__ */ jsx(Heading, {
|
|
30
|
+
level: 1,
|
|
31
|
+
size: "title-lg",
|
|
32
|
+
children: title
|
|
33
|
+
}),
|
|
34
|
+
count != null && /* @__PURE__ */ jsx("span", {
|
|
35
|
+
className: "qx-page-header-count",
|
|
36
|
+
"data-slot": "page-header-count",
|
|
37
|
+
children: typeof count === "number" ? formatNumber(count) : count
|
|
38
|
+
}),
|
|
39
|
+
status
|
|
40
|
+
]
|
|
41
|
+
}), description != null && /* @__PURE__ */ jsx("p", {
|
|
42
|
+
className: "qx-page-header-description",
|
|
43
|
+
"data-slot": "page-header-description",
|
|
44
|
+
children: description
|
|
45
|
+
})]
|
|
46
|
+
}), actions != null && /* @__PURE__ */ jsx("div", {
|
|
47
|
+
className: "qx-page-header-actions",
|
|
48
|
+
"data-slot": "page-header-actions",
|
|
49
|
+
children: actions
|
|
50
|
+
})]
|
|
51
|
+
}),
|
|
52
|
+
summary != null && /* @__PURE__ */ jsx("div", {
|
|
53
|
+
className: "qx-page-header-summary",
|
|
54
|
+
"data-slot": "page-header-summary",
|
|
55
|
+
children: summary
|
|
56
|
+
}),
|
|
57
|
+
tabs != null && /* @__PURE__ */ jsx("div", {
|
|
58
|
+
className: "qx-page-header-tabs",
|
|
59
|
+
children: tabs
|
|
60
|
+
})
|
|
61
|
+
]
|
|
62
|
+
});
|
|
63
|
+
}
|
|
64
|
+
/** The work surface under a PageHeader. Server-safe. */
|
|
65
|
+
function PageContent({ width = "full", className, ...props }) {
|
|
66
|
+
return /* @__PURE__ */ jsx("div", {
|
|
67
|
+
...props,
|
|
68
|
+
className: cx("qx-page-content", className),
|
|
69
|
+
"data-slot": "page-content",
|
|
70
|
+
"data-width": width
|
|
71
|
+
});
|
|
72
|
+
}
|
|
73
|
+
export { PageContent, PageHeader };
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import { QeetrixMessages } from "../../internal/messages.js";
|
|
2
|
+
import { InputProps } from "../../input/input.js";
|
|
3
|
+
import { ReactElement } from "react";
|
|
4
|
+
export interface PasswordInputProps extends Omit<InputProps, "type"> {
|
|
5
|
+
/**
|
|
6
|
+
* `current-password` (default) to sign in, `new-password` to create one, so password managers
|
|
7
|
+
* fill or suggest; `off` for a secret that isn't the user's password, such as an API key.
|
|
8
|
+
*/
|
|
9
|
+
autoComplete?: "current-password" | "new-password" | "off";
|
|
10
|
+
/** Whether the value shows as text, controlled. */
|
|
11
|
+
visible?: boolean;
|
|
12
|
+
/** Whether it starts visible when uncontrolled. Default `false`: people may not be alone. */
|
|
13
|
+
defaultVisible?: boolean;
|
|
14
|
+
/** Called with the new visibility. */
|
|
15
|
+
onVisibleChange?: (visible: boolean) => void;
|
|
16
|
+
/** Show "Caps Lock is on" while typing with it on. Default `true`. */
|
|
17
|
+
capsLockWarning?: boolean;
|
|
18
|
+
/** This input's strings, over QeetrixProvider's and the English defaults. */
|
|
19
|
+
messages?: Partial<QeetrixMessages["passwordInput"]>;
|
|
20
|
+
}
|
|
21
|
+
/**
|
|
22
|
+
* A password or secret field (07-components.md, C2): an Input with a Show password toggle. It
|
|
23
|
+
* hides again when its form is submitted, so the browser never stores it as plain text; spelling
|
|
24
|
+
* and capitalisation tools stay off; paste always works, and there's no length cap.
|
|
25
|
+
*/
|
|
26
|
+
export declare function PasswordInput({ autoComplete, visible, defaultVisible, onVisibleChange, capsLockWarning, messages, endAdornment, disabled, ref, onKeyDown, onKeyUp, onBlur, className, ...props }: PasswordInputProps): ReactElement;
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { IconButton } from "../../button/button.js";
|
|
3
|
+
import { useMessages } from "../../provider/provider.js";
|
|
4
|
+
import { mergeRefs } from "../../internal/refs.js";
|
|
5
|
+
import { Input } from "../../input/input.js";
|
|
6
|
+
import { VisuallyHidden } from "../../visually-hidden/visually-hidden.js";
|
|
7
|
+
import { Fragment, jsx, jsxs } from "react/jsx-runtime";
|
|
8
|
+
import { useEffect, useEffectEvent, useRef, useState } from "react";
|
|
9
|
+
import { ArrowBigUpIcon } from "@qeetrix/icons/icons/arrow-big-up";
|
|
10
|
+
import { EyeIcon } from "@qeetrix/icons/icons/eye";
|
|
11
|
+
import { EyeOffIcon } from "@qeetrix/icons/icons/eye-off";
|
|
12
|
+
// src/experimental/password-input/password-input.tsx
|
|
13
|
+
/**
|
|
14
|
+
* A password or secret field (07-components.md, C2): an Input with a Show password toggle. It
|
|
15
|
+
* hides again when its form is submitted, so the browser never stores it as plain text; spelling
|
|
16
|
+
* and capitalisation tools stay off; paste always works, and there's no length cap.
|
|
17
|
+
*/
|
|
18
|
+
function PasswordInput({ autoComplete = "current-password", visible, defaultVisible = false, onVisibleChange, capsLockWarning = true, messages, endAdornment, disabled, ref, onKeyDown, onKeyUp, onBlur, className, ...props }) {
|
|
19
|
+
const strings = useMessages("passwordInput", messages);
|
|
20
|
+
const input = useRef(null);
|
|
21
|
+
const [own, setOwn] = useState(defaultVisible);
|
|
22
|
+
const shown = visible ?? own;
|
|
23
|
+
const [announcement, setAnnouncement] = useState("");
|
|
24
|
+
const [capsLock, setCapsLock] = useState(false);
|
|
25
|
+
const setShown = (next) => {
|
|
26
|
+
if (visible === void 0) setOwn(next);
|
|
27
|
+
onVisibleChange?.(next);
|
|
28
|
+
};
|
|
29
|
+
const hideOnSubmit = useEffectEvent(() => {
|
|
30
|
+
if (input.current) input.current.type = "password";
|
|
31
|
+
if (shown) setShown(false);
|
|
32
|
+
});
|
|
33
|
+
useEffect(() => {
|
|
34
|
+
const form = input.current?.form;
|
|
35
|
+
if (!form) return;
|
|
36
|
+
const onSubmit = () => hideOnSubmit();
|
|
37
|
+
form.addEventListener("submit", onSubmit);
|
|
38
|
+
return () => form.removeEventListener("submit", onSubmit);
|
|
39
|
+
}, []);
|
|
40
|
+
const toggle = () => {
|
|
41
|
+
const field = input.current;
|
|
42
|
+
const selection = field ? [field.selectionStart, field.selectionEnd] : [null, null];
|
|
43
|
+
setShown(!shown);
|
|
44
|
+
setAnnouncement(shown ? strings.hidden : strings.shown);
|
|
45
|
+
requestAnimationFrame(() => {
|
|
46
|
+
const [start, end] = selection;
|
|
47
|
+
if (field && start != null && end != null) field.setSelectionRange(start, end);
|
|
48
|
+
});
|
|
49
|
+
};
|
|
50
|
+
const readCapsLock = (event) => {
|
|
51
|
+
if (capsLockWarning) setCapsLock(event.getModifierState("CapsLock"));
|
|
52
|
+
};
|
|
53
|
+
return /* @__PURE__ */ jsx(Input, {
|
|
54
|
+
...props,
|
|
55
|
+
ref: mergeRefs(ref, input),
|
|
56
|
+
type: shown ? "text" : "password",
|
|
57
|
+
autoComplete,
|
|
58
|
+
spellCheck: false,
|
|
59
|
+
autoCapitalize: "none",
|
|
60
|
+
autoCorrect: "off",
|
|
61
|
+
disabled,
|
|
62
|
+
className: className ? `qx-password-input ${className}` : "qx-password-input",
|
|
63
|
+
onKeyDown: (event) => {
|
|
64
|
+
readCapsLock(event);
|
|
65
|
+
onKeyDown?.(event);
|
|
66
|
+
},
|
|
67
|
+
onKeyUp: (event) => {
|
|
68
|
+
readCapsLock(event);
|
|
69
|
+
onKeyUp?.(event);
|
|
70
|
+
},
|
|
71
|
+
onBlur: (event) => {
|
|
72
|
+
setCapsLock(false);
|
|
73
|
+
onBlur?.(event);
|
|
74
|
+
},
|
|
75
|
+
endAdornment: /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
76
|
+
capsLock && /* @__PURE__ */ jsxs("span", {
|
|
77
|
+
className: "qx-password-input-caps",
|
|
78
|
+
"data-slot": "password-input-caps",
|
|
79
|
+
children: [/* @__PURE__ */ jsx(ArrowBigUpIcon, {
|
|
80
|
+
shape: "sharp",
|
|
81
|
+
size: 14,
|
|
82
|
+
"aria-hidden": true
|
|
83
|
+
}), strings.capsLock]
|
|
84
|
+
}),
|
|
85
|
+
endAdornment,
|
|
86
|
+
/* @__PURE__ */ jsx(IconButton, {
|
|
87
|
+
icon: shown ? EyeOffIcon : EyeIcon,
|
|
88
|
+
"aria-label": strings.show,
|
|
89
|
+
"aria-pressed": shown,
|
|
90
|
+
size: "sm",
|
|
91
|
+
tone: "neutral",
|
|
92
|
+
disabled,
|
|
93
|
+
onClick: toggle,
|
|
94
|
+
className: "qx-password-input-toggle"
|
|
95
|
+
}),
|
|
96
|
+
/* @__PURE__ */ jsx(VisuallyHidden, {
|
|
97
|
+
"aria-live": "polite",
|
|
98
|
+
children: announcement
|
|
99
|
+
})
|
|
100
|
+
] })
|
|
101
|
+
});
|
|
102
|
+
}
|
|
103
|
+
export { PasswordInput };
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
import { RenderProp } from "../../internal/render.js";
|
|
2
|
+
import { QeetrixIcon } from "../../internal/control.js";
|
|
3
|
+
import { QeetrixMessages } from "../../internal/messages.js";
|
|
4
|
+
import { CSSProperties, ComponentProps, MouseEvent, ReactElement, ReactNode } from "react";
|
|
5
|
+
export interface SidebarProps extends Omit<ComponentProps<"div">, "children" | "title"> {
|
|
6
|
+
/** Names the navigation landmark, e.g. the product: "Qeet Pay". */
|
|
7
|
+
label: string;
|
|
8
|
+
/** The area's heading at the top, e.g. "Identity". */
|
|
9
|
+
title?: ReactNode;
|
|
10
|
+
/** Add "Filter navigation", which narrows the items by label as you type. */
|
|
11
|
+
filter?: boolean;
|
|
12
|
+
/** Below the items, e.g. a link to what's new. */
|
|
13
|
+
footer?: ReactNode;
|
|
14
|
+
/** SidebarGroups. */
|
|
15
|
+
children?: ReactNode;
|
|
16
|
+
/** The filter's strings, over QeetrixProvider's and the English defaults. */
|
|
17
|
+
messages?: Partial<QeetrixMessages["sidebar"]>;
|
|
18
|
+
}
|
|
19
|
+
export interface SidebarGroupProps extends Omit<ComponentProps<"div">, "children"> {
|
|
20
|
+
/** The group's label; without one, the group is separated by space alone. */
|
|
21
|
+
label?: ReactNode;
|
|
22
|
+
/** SidebarItems. */
|
|
23
|
+
children?: ReactNode;
|
|
24
|
+
}
|
|
25
|
+
export interface SidebarItemProps {
|
|
26
|
+
/** The item's text. In the rail it becomes the tooltip and stays the accessible name. */
|
|
27
|
+
label: string;
|
|
28
|
+
/** Where it goes. A category (an item with children) takes no `href`. */
|
|
29
|
+
href?: string;
|
|
30
|
+
/** A router link instead of `<a>`, e.g. `render={<Link to="/users" />}`. */
|
|
31
|
+
render?: RenderProp;
|
|
32
|
+
/**
|
|
33
|
+
* A 16px icon from `@qeetrix/icons`, in its round shape: navigation is the one place Qeetrix uses
|
|
34
|
+
* round icons. Icons represent destinations, not actions.
|
|
35
|
+
*/
|
|
36
|
+
icon?: QeetrixIcon;
|
|
37
|
+
/** Show the icon's filled drawing while current. Only for icons that have one. */
|
|
38
|
+
filledWhenCurrent?: boolean;
|
|
39
|
+
/** This item is the current page: `aria-current="page"` and the Ember bar. */
|
|
40
|
+
current?: boolean;
|
|
41
|
+
/** At the end: a count or a Badge. Hidden in the rail. */
|
|
42
|
+
badge?: ReactNode;
|
|
43
|
+
/** A category's initial state. By default it starts open when it holds the current page. */
|
|
44
|
+
defaultOpen?: boolean;
|
|
45
|
+
/** Called when the link is followed. */
|
|
46
|
+
onClick?: (event: MouseEvent<HTMLElement>) => void;
|
|
47
|
+
/** One level of nested SidebarItems: the item becomes a category. */
|
|
48
|
+
children?: ReactNode;
|
|
49
|
+
className?: string;
|
|
50
|
+
style?: CSSProperties;
|
|
51
|
+
}
|
|
52
|
+
/**
|
|
53
|
+
* A console's navigation (07-components.md, C1): links only, one level of nesting, every item in
|
|
54
|
+
* the Tab order. Inside an AppShell it follows the shell: a rail when collapsed, a drawer on narrow
|
|
55
|
+
* screens.
|
|
56
|
+
*/
|
|
57
|
+
export declare function Sidebar({ label, title, filter, footer, messages, className, children, ...props }: SidebarProps): ReactElement;
|
|
58
|
+
/** Items under one label. */
|
|
59
|
+
export declare function SidebarGroup({ label, className, children, ...props }: SidebarGroupProps): ReactElement;
|
|
60
|
+
/**
|
|
61
|
+
* A destination, or a category of destinations. A closed category carries the current mark for its
|
|
62
|
+
* current child, so the current page is always marked.
|
|
63
|
+
*/
|
|
64
|
+
export declare function SidebarItem({ label, href, render, icon: Icon, filledWhenCurrent, current, badge, defaultOpen, onClick, className, style, children }: SidebarItemProps): ReactElement | null;
|
|
@@ -0,0 +1,200 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { cx } from "../../internal/cx.js";
|
|
3
|
+
import { renderElement } from "../../internal/render.js";
|
|
4
|
+
import { Tooltip } from "../../tooltip/tooltip.js";
|
|
5
|
+
import { IconButton } from "../../button/button.js";
|
|
6
|
+
import { useMessages } from "../../provider/provider.js";
|
|
7
|
+
import { Input } from "../../input/input.js";
|
|
8
|
+
import { useShellState } from "../app-shell/app-shell.js";
|
|
9
|
+
import { ChevronDownIcon } from "@qeetrix/icons/icons/chevron-down";
|
|
10
|
+
import { Fragment, jsx, jsxs } from "react/jsx-runtime";
|
|
11
|
+
import { Children, createContext, isValidElement, use, useId, useRef, useState } from "react";
|
|
12
|
+
import { SearchIcon } from "@qeetrix/icons/icons/search";
|
|
13
|
+
// src/experimental/sidebar/sidebar.tsx
|
|
14
|
+
/** The filter's text, lower-cased; and whether an enclosing category matched it by its own label. */
|
|
15
|
+
const FilterContext = createContext("");
|
|
16
|
+
const ParentMatchContext = createContext(false);
|
|
17
|
+
/**
|
|
18
|
+
* A console's navigation (07-components.md, C1): links only, one level of nesting, every item in
|
|
19
|
+
* the Tab order. Inside an AppShell it follows the shell: a rail when collapsed, a drawer on narrow
|
|
20
|
+
* screens.
|
|
21
|
+
*/
|
|
22
|
+
function Sidebar({ label, title, filter, footer, messages, className, children, ...props }) {
|
|
23
|
+
const shell = useShellState();
|
|
24
|
+
const rail = shell.collapsed && !shell.inDrawer;
|
|
25
|
+
const strings = useMessages("sidebar", messages);
|
|
26
|
+
const [query, setQuery] = useState("");
|
|
27
|
+
const filterRef = useRef(null);
|
|
28
|
+
return /* @__PURE__ */ jsxs("div", {
|
|
29
|
+
...props,
|
|
30
|
+
className: cx("qx-sidebar", className),
|
|
31
|
+
"data-slot": "sidebar",
|
|
32
|
+
"data-rail": rail ? "" : void 0,
|
|
33
|
+
children: [
|
|
34
|
+
(title != null || filter) && /* @__PURE__ */ jsxs("div", {
|
|
35
|
+
className: "qx-sidebar-header",
|
|
36
|
+
"data-slot": "sidebar-header",
|
|
37
|
+
children: [
|
|
38
|
+
filter && rail && /* @__PURE__ */ jsx(IconButton, {
|
|
39
|
+
icon: SearchIcon,
|
|
40
|
+
"aria-label": strings.filter,
|
|
41
|
+
tone: "neutral",
|
|
42
|
+
className: "qx-sidebar-rail-search",
|
|
43
|
+
onClick: () => {
|
|
44
|
+
shell.expand();
|
|
45
|
+
requestAnimationFrame(() => filterRef.current?.focus());
|
|
46
|
+
}
|
|
47
|
+
}),
|
|
48
|
+
title != null && /* @__PURE__ */ jsx("div", {
|
|
49
|
+
className: "qx-sidebar-title",
|
|
50
|
+
children: title
|
|
51
|
+
}),
|
|
52
|
+
filter && /* @__PURE__ */ jsx(Input, {
|
|
53
|
+
ref: filterRef,
|
|
54
|
+
type: "search",
|
|
55
|
+
icon: SearchIcon,
|
|
56
|
+
value: query,
|
|
57
|
+
onValueChange: setQuery,
|
|
58
|
+
"aria-label": strings.filter,
|
|
59
|
+
placeholder: strings.filter,
|
|
60
|
+
className: "qx-sidebar-filter"
|
|
61
|
+
})
|
|
62
|
+
]
|
|
63
|
+
}),
|
|
64
|
+
/* @__PURE__ */ jsxs("nav", {
|
|
65
|
+
"aria-label": label,
|
|
66
|
+
className: "qx-sidebar-nav",
|
|
67
|
+
"data-slot": "sidebar-nav",
|
|
68
|
+
children: [/* @__PURE__ */ jsx(FilterContext, {
|
|
69
|
+
value: query.trim().toLocaleLowerCase(),
|
|
70
|
+
children
|
|
71
|
+
}), /* @__PURE__ */ jsx("p", {
|
|
72
|
+
className: "qx-sidebar-empty",
|
|
73
|
+
role: "status",
|
|
74
|
+
children: query.trim() ? strings.noMatches : ""
|
|
75
|
+
})]
|
|
76
|
+
}),
|
|
77
|
+
footer != null && /* @__PURE__ */ jsx("div", {
|
|
78
|
+
className: "qx-sidebar-footer",
|
|
79
|
+
"data-slot": "sidebar-footer",
|
|
80
|
+
children: footer
|
|
81
|
+
})
|
|
82
|
+
]
|
|
83
|
+
});
|
|
84
|
+
}
|
|
85
|
+
/** Items under one label. */
|
|
86
|
+
function SidebarGroup({ label, className, children, ...props }) {
|
|
87
|
+
const id = useId();
|
|
88
|
+
return /* @__PURE__ */ jsxs("div", {
|
|
89
|
+
...props,
|
|
90
|
+
className: cx("qx-sidebar-group", className),
|
|
91
|
+
"data-slot": "sidebar-group",
|
|
92
|
+
children: [label != null && /* @__PURE__ */ jsx("div", {
|
|
93
|
+
id,
|
|
94
|
+
className: "qx-sidebar-group-label",
|
|
95
|
+
"data-slot": "sidebar-group-label",
|
|
96
|
+
children: label
|
|
97
|
+
}), /* @__PURE__ */ jsx("ul", {
|
|
98
|
+
className: "qx-sidebar-list",
|
|
99
|
+
"aria-labelledby": label != null ? id : void 0,
|
|
100
|
+
children
|
|
101
|
+
})]
|
|
102
|
+
});
|
|
103
|
+
}
|
|
104
|
+
/**
|
|
105
|
+
* A destination, or a category of destinations. A closed category carries the current mark for its
|
|
106
|
+
* current child, so the current page is always marked.
|
|
107
|
+
*/
|
|
108
|
+
function SidebarItem({ label, href, render, icon: Icon, filledWhenCurrent, current, badge, defaultOpen, onClick, className, style, children }) {
|
|
109
|
+
const shell = useShellState();
|
|
110
|
+
const query = use(FilterContext);
|
|
111
|
+
const parentMatched = use(ParentMatchContext);
|
|
112
|
+
const rail = shell.collapsed && !shell.inDrawer;
|
|
113
|
+
const panelId = useId();
|
|
114
|
+
const nested = Children.toArray(children).filter(isValidElement);
|
|
115
|
+
const isCategory = nested.length > 0;
|
|
116
|
+
const holdsCurrent = nested.some((child) => child.props.current);
|
|
117
|
+
const [open, setOpen] = useState(defaultOpen ?? holdsCurrent);
|
|
118
|
+
const matches = (text) => text.toLocaleLowerCase().includes(query);
|
|
119
|
+
const selfMatch = query === "" || parentMatched || matches(label);
|
|
120
|
+
const childMatch = query !== "" && nested.some((child) => matches(String(child.props.label)));
|
|
121
|
+
if (!selfMatch && !childMatch) return null;
|
|
122
|
+
const expanded = (query !== "" ? true : open) && !rail;
|
|
123
|
+
const marked = current || isCategory && holdsCurrent && !expanded;
|
|
124
|
+
const Filled = Icon;
|
|
125
|
+
const content = /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
126
|
+
Icon && /* @__PURE__ */ jsx(Icon, {
|
|
127
|
+
shape: "round",
|
|
128
|
+
size: 16,
|
|
129
|
+
"aria-hidden": true,
|
|
130
|
+
className: "qx-sidebar-icon qx-sidebar-icon-outline"
|
|
131
|
+
}),
|
|
132
|
+
Filled && filledWhenCurrent && /* @__PURE__ */ jsx(Filled, {
|
|
133
|
+
shape: "round",
|
|
134
|
+
variant: "filled",
|
|
135
|
+
size: 16,
|
|
136
|
+
"aria-hidden": true,
|
|
137
|
+
className: "qx-sidebar-icon qx-sidebar-icon-filled"
|
|
138
|
+
}),
|
|
139
|
+
/* @__PURE__ */ jsx("span", {
|
|
140
|
+
className: "qx-sidebar-label",
|
|
141
|
+
children: label
|
|
142
|
+
}),
|
|
143
|
+
badge != null && /* @__PURE__ */ jsx("span", {
|
|
144
|
+
className: "qx-sidebar-badge",
|
|
145
|
+
children: badge
|
|
146
|
+
}),
|
|
147
|
+
isCategory && /* @__PURE__ */ jsx(ChevronDownIcon, {
|
|
148
|
+
shape: "round",
|
|
149
|
+
size: 16,
|
|
150
|
+
"aria-hidden": true,
|
|
151
|
+
className: "qx-sidebar-chevron"
|
|
152
|
+
})
|
|
153
|
+
] });
|
|
154
|
+
let control = isCategory ? /* @__PURE__ */ jsx("button", {
|
|
155
|
+
type: "button",
|
|
156
|
+
className: "qx-sidebar-item",
|
|
157
|
+
"aria-expanded": expanded,
|
|
158
|
+
"aria-controls": panelId,
|
|
159
|
+
"data-current": marked ? "" : void 0,
|
|
160
|
+
"data-open": expanded ? "" : void 0,
|
|
161
|
+
onClick: () => {
|
|
162
|
+
if (rail) {
|
|
163
|
+
shell.expand();
|
|
164
|
+
setOpen(true);
|
|
165
|
+
} else setOpen(!open);
|
|
166
|
+
},
|
|
167
|
+
children: content
|
|
168
|
+
}) : renderElement("a", render, {
|
|
169
|
+
href,
|
|
170
|
+
className: "qx-sidebar-item",
|
|
171
|
+
"aria-current": current ? "page" : void 0,
|
|
172
|
+
"data-current": current ? "" : void 0,
|
|
173
|
+
onClick: (event) => {
|
|
174
|
+
onClick?.(event);
|
|
175
|
+
if (shell.inDrawer) shell.closeDrawer();
|
|
176
|
+
},
|
|
177
|
+
children: content
|
|
178
|
+
});
|
|
179
|
+
control = /* @__PURE__ */ jsx(Tooltip, {
|
|
180
|
+
content: label,
|
|
181
|
+
side: "inline-end",
|
|
182
|
+
disabled: !rail,
|
|
183
|
+
children: control
|
|
184
|
+
});
|
|
185
|
+
return /* @__PURE__ */ jsxs("li", {
|
|
186
|
+
className: cx("qx-sidebar-entry", className),
|
|
187
|
+
style,
|
|
188
|
+
"data-slot": "sidebar-item",
|
|
189
|
+
children: [control, isCategory && /* @__PURE__ */ jsx("ul", {
|
|
190
|
+
id: panelId,
|
|
191
|
+
className: "qx-sidebar-list qx-sidebar-sublist",
|
|
192
|
+
hidden: !expanded,
|
|
193
|
+
children: /* @__PURE__ */ jsx(ParentMatchContext, {
|
|
194
|
+
value: query !== "" && matches(label),
|
|
195
|
+
children: nested
|
|
196
|
+
})
|
|
197
|
+
})]
|
|
198
|
+
});
|
|
199
|
+
}
|
|
200
|
+
export { Sidebar, SidebarGroup, SidebarItem };
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import { ComponentProps, ReactElement, ReactNode } from "react";
|
|
2
|
+
/** What the note says about the number: `positive`, `negative` or `warning` colour it. */
|
|
3
|
+
export type StatTone = "neutral" | "positive" | "negative" | "warning";
|
|
4
|
+
export interface StatGroupProps extends ComponentProps<"dl"> {
|
|
5
|
+
/** Stats, side by side in equal ruled cells. */
|
|
6
|
+
children?: ReactNode;
|
|
7
|
+
}
|
|
8
|
+
export interface StatProps extends Omit<ComponentProps<"div">, "children"> {
|
|
9
|
+
/** What's counted: "Total users". */
|
|
10
|
+
label: ReactNode;
|
|
11
|
+
/** The number. A `number` is formatted for `locale`; pass a string for units or percentages. */
|
|
12
|
+
value: ReactNode;
|
|
13
|
+
/** One line under the value: the change or the context, e.g. "+38 this week". */
|
|
14
|
+
note?: ReactNode;
|
|
15
|
+
/** Colours the note. Default `neutral`. */
|
|
16
|
+
tone?: StatTone;
|
|
17
|
+
/**
|
|
18
|
+
* Recent values, oldest first, drawn as a small line beside the number. It's decorative: say what
|
|
19
|
+
* matters about the trend in `note`.
|
|
20
|
+
*/
|
|
21
|
+
trend?: readonly number[];
|
|
22
|
+
/** The locale for a numeric `value`. Default `en-IN`. */
|
|
23
|
+
locale?: string;
|
|
24
|
+
}
|
|
25
|
+
/**
|
|
26
|
+
* Key numbers in a ruled strip (07-components.md, C1): a `<dl>` of Stats in equal cells with rules
|
|
27
|
+
* between them. In a PageHeader's `summary` it runs edge to edge. Server-safe.
|
|
28
|
+
*/
|
|
29
|
+
export declare function StatGroup({ className, ...props }: StatGroupProps): ReactElement;
|
|
30
|
+
/** One number with its label, a note and an optional trend. Use inside a StatGroup. Server-safe. */
|
|
31
|
+
export declare function Stat({ label, value, note, tone, trend, locale, className, ...props }: StatProps): ReactElement;
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
import { cx } from "../../internal/cx.js";
|
|
2
|
+
import { formatNumber } from "../../format/format.js";
|
|
3
|
+
import { jsx, jsxs } from "react/jsx-runtime";
|
|
4
|
+
// src/experimental/stat/stat.tsx
|
|
5
|
+
const TREND = {
|
|
6
|
+
width: 88,
|
|
7
|
+
height: 28,
|
|
8
|
+
pad: 3
|
|
9
|
+
};
|
|
10
|
+
/** The trend line and its area, scaled into the box; the last point is marked. */
|
|
11
|
+
function trendGeometry(values) {
|
|
12
|
+
const { width, height, pad } = TREND;
|
|
13
|
+
const min = Math.min(...values);
|
|
14
|
+
const span = Math.max(...values) - min;
|
|
15
|
+
const points = values.map((value, index) => ({
|
|
16
|
+
x: index / (values.length - 1) * width,
|
|
17
|
+
y: span === 0 ? height / 2 : height - pad - (value - min) / span * (height - 2 * pad)
|
|
18
|
+
}));
|
|
19
|
+
const line = points.map(({ x, y }, i) => `${i === 0 ? "M" : "L"}${x.toFixed(1)} ${y.toFixed(1)}`);
|
|
20
|
+
return {
|
|
21
|
+
line: line.join(" "),
|
|
22
|
+
area: `${line.join(" ")} L${width} ${height} L0 ${height} Z`,
|
|
23
|
+
end: points[points.length - 1]
|
|
24
|
+
};
|
|
25
|
+
}
|
|
26
|
+
/**
|
|
27
|
+
* Key numbers in a ruled strip (07-components.md, C1): a `<dl>` of Stats in equal cells with rules
|
|
28
|
+
* between them. In a PageHeader's `summary` it runs edge to edge. Server-safe.
|
|
29
|
+
*/
|
|
30
|
+
function StatGroup({ className, ...props }) {
|
|
31
|
+
return /* @__PURE__ */ jsx("dl", {
|
|
32
|
+
...props,
|
|
33
|
+
className: cx("qx-stat-group", className),
|
|
34
|
+
"data-slot": "stat-group"
|
|
35
|
+
});
|
|
36
|
+
}
|
|
37
|
+
/** One number with its label, a note and an optional trend. Use inside a StatGroup. Server-safe. */
|
|
38
|
+
function Stat({ label, value, note, tone = "neutral", trend, locale, className, ...props }) {
|
|
39
|
+
const geometry = trend && trend.length > 1 ? trendGeometry(trend) : void 0;
|
|
40
|
+
return /* @__PURE__ */ jsxs("div", {
|
|
41
|
+
...props,
|
|
42
|
+
className: cx("qx-stat", className),
|
|
43
|
+
"data-slot": "stat",
|
|
44
|
+
children: [
|
|
45
|
+
/* @__PURE__ */ jsx("dt", {
|
|
46
|
+
className: "qx-stat-label",
|
|
47
|
+
"data-slot": "stat-label",
|
|
48
|
+
children: label
|
|
49
|
+
}),
|
|
50
|
+
/* @__PURE__ */ jsx("dd", {
|
|
51
|
+
className: "qx-stat-value",
|
|
52
|
+
"data-slot": "stat-value",
|
|
53
|
+
children: typeof value === "number" ? formatNumber(value, locale) : value
|
|
54
|
+
}),
|
|
55
|
+
geometry && /* @__PURE__ */ jsx("dd", {
|
|
56
|
+
className: "qx-stat-trend-cell",
|
|
57
|
+
children: /* @__PURE__ */ jsxs("svg", {
|
|
58
|
+
className: "qx-stat-trend",
|
|
59
|
+
"data-slot": "stat-trend",
|
|
60
|
+
viewBox: `0 0 ${TREND.width} ${TREND.height}`,
|
|
61
|
+
"aria-hidden": "true",
|
|
62
|
+
focusable: "false",
|
|
63
|
+
children: [
|
|
64
|
+
/* @__PURE__ */ jsx("path", {
|
|
65
|
+
className: "qx-stat-trend-area",
|
|
66
|
+
d: geometry.area
|
|
67
|
+
}),
|
|
68
|
+
/* @__PURE__ */ jsx("path", {
|
|
69
|
+
className: "qx-stat-trend-line",
|
|
70
|
+
d: geometry.line
|
|
71
|
+
}),
|
|
72
|
+
/* @__PURE__ */ jsx("circle", {
|
|
73
|
+
className: "qx-stat-trend-end",
|
|
74
|
+
cx: geometry.end.x,
|
|
75
|
+
cy: geometry.end.y,
|
|
76
|
+
r: 2.5
|
|
77
|
+
})
|
|
78
|
+
]
|
|
79
|
+
})
|
|
80
|
+
}),
|
|
81
|
+
note != null && /* @__PURE__ */ jsx("dd", {
|
|
82
|
+
className: "qx-stat-note",
|
|
83
|
+
"data-slot": "stat-note",
|
|
84
|
+
"data-tone": tone,
|
|
85
|
+
children: note
|
|
86
|
+
})
|
|
87
|
+
]
|
|
88
|
+
});
|
|
89
|
+
}
|
|
90
|
+
export { Stat, StatGroup };
|