@anyknown/ui 0.7.0 → 0.8.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +7 -8
- package/dist/brand.css +101 -214
- package/dist/components/button/Button.d.ts +3 -2
- package/dist/components/button/Button.js +32 -48
- package/dist/components/checkbox/Checkbox.js +24 -48
- package/dist/components/dialog/Dialog.js +1 -1
- package/dist/components/empty-state/EmptyState.js +1 -1
- package/dist/components/ghost/Ghost.d.ts +16 -0
- package/dist/components/ghost/Ghost.js +50 -0
- package/dist/components/group/Group.d.ts +66 -0
- package/dist/components/group/Group.js +166 -0
- package/dist/components/handoff-receipt/HandoffReceipt.js +12 -42
- package/dist/components/icon/Chevron.d.ts +9 -0
- package/dist/components/icon/Chevron.js +6 -0
- package/dist/components/icon/icon.d.ts +35 -0
- package/dist/components/icon/icon.js +10 -0
- package/dist/components/icon-button/IconButton.d.ts +17 -0
- package/dist/components/icon-button/IconButton.js +66 -0
- package/dist/components/page/Page.d.ts +64 -0
- package/dist/components/page/Page.js +163 -0
- package/dist/components/popover/Popover.js +1 -1
- package/dist/components/progress/Progress.js +49 -104
- package/dist/components/radio/Radio.js +17 -28
- package/dist/components/segmented/Segmented.d.ts +12 -0
- package/dist/components/segmented/Segmented.js +43 -0
- package/dist/components/settings-rows/SettingsRows.d.ts +25 -0
- package/dist/components/settings-rows/SettingsRows.js +63 -0
- package/dist/components/spin/Spin.d.ts +5 -0
- package/dist/components/spin/Spin.js +25 -0
- package/dist/components/status-chip/StatusChip.d.ts +18 -0
- package/dist/components/status-chip/StatusChip.js +68 -0
- package/dist/components/switch/Switch.js +20 -63
- package/dist/components/table/Table.d.ts +39 -0
- package/dist/components/table/Table.js +125 -0
- package/dist/components/table/TableCells.d.ts +52 -0
- package/dist/components/table/TableCells.js +103 -0
- package/dist/components/tabs/Tabs.js +16 -48
- package/dist/components/text/Text.js +2 -2
- package/dist/components/toast/Toast.js +7 -8
- package/dist/index.d.ts +11 -0
- package/dist/index.js +11 -0
- package/dist/themes.stylex.d.ts +33 -496
- package/dist/themes.stylex.js +25 -253
- package/dist/tokens.css +53 -126
- package/dist/tokens.stylex.d.ts +36 -54
- package/dist/tokens.stylex.js +62 -90
- package/package.json +1 -1
- package/dist/lib/paths.d.ts +0 -3
- package/dist/lib/paths.js +0 -11
- package/dist/lib/progress.d.ts +0 -2
- package/dist/lib/progress.js +0 -28
- package/dist/lib/silk.d.ts +0 -66
- package/dist/lib/silk.js +0 -337
- package/dist/lib/svgId.d.ts +0 -1
- package/dist/lib/svgId.js +0 -4
- package/dist/lib/weave.d.ts +0 -28
- package/dist/lib/weave.js +0 -83
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import type { SVGAttributes } from "react";
|
|
2
|
+
declare const PATH: {
|
|
3
|
+
readonly right: "m9 18 6-6-6-6";
|
|
4
|
+
readonly down: "m6 9 6 6 6-6";
|
|
5
|
+
};
|
|
6
|
+
export declare function Chevron({ direction, ...props }: SVGAttributes<SVGSVGElement> & {
|
|
7
|
+
direction: keyof typeof PATH;
|
|
8
|
+
}): import("react").JSX.Element;
|
|
9
|
+
export {};
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
|
+
import { ICON_STROKE } from "./icon.js";
|
|
3
|
+
const PATH = { right: "m9 18 6-6-6-6", down: "m6 9 6 6 6-6" };
|
|
4
|
+
export function Chevron({ direction, ...props }) {
|
|
5
|
+
return (_jsx("svg", { viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: ICON_STROKE, strokeLinecap: "round", strokeLinejoin: "round", "aria-hidden": "true", ...props, children: _jsx("path", { d: PATH[direction] }) }));
|
|
6
|
+
}
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import * as stylex from "@stylexjs/stylex";
|
|
2
|
+
/** An icon draws at the size it is given; these are the five in use. Stroke 2, currentColor. */
|
|
3
|
+
export declare const icon: Readonly<{
|
|
4
|
+
readonly xs: Readonly<{
|
|
5
|
+
readonly flexShrink: stylex.StyleXClassNameFor<"flexShrink", 0>;
|
|
6
|
+
readonly height: stylex.StyleXClassNameFor<"height", 12>;
|
|
7
|
+
readonly pointerEvents: stylex.StyleXClassNameFor<"pointerEvents", "none">;
|
|
8
|
+
readonly width: stylex.StyleXClassNameFor<"width", 12>;
|
|
9
|
+
}>;
|
|
10
|
+
readonly sm: Readonly<{
|
|
11
|
+
readonly flexShrink: stylex.StyleXClassNameFor<"flexShrink", 0>;
|
|
12
|
+
readonly height: stylex.StyleXClassNameFor<"height", 14>;
|
|
13
|
+
readonly pointerEvents: stylex.StyleXClassNameFor<"pointerEvents", "none">;
|
|
14
|
+
readonly width: stylex.StyleXClassNameFor<"width", 14>;
|
|
15
|
+
}>;
|
|
16
|
+
readonly md: Readonly<{
|
|
17
|
+
readonly flexShrink: stylex.StyleXClassNameFor<"flexShrink", 0>;
|
|
18
|
+
readonly height: stylex.StyleXClassNameFor<"height", 16>;
|
|
19
|
+
readonly pointerEvents: stylex.StyleXClassNameFor<"pointerEvents", "none">;
|
|
20
|
+
readonly width: stylex.StyleXClassNameFor<"width", 16>;
|
|
21
|
+
}>;
|
|
22
|
+
readonly base: Readonly<{
|
|
23
|
+
readonly flexShrink: stylex.StyleXClassNameFor<"flexShrink", 0>;
|
|
24
|
+
readonly height: stylex.StyleXClassNameFor<"height", 18>;
|
|
25
|
+
readonly pointerEvents: stylex.StyleXClassNameFor<"pointerEvents", "none">;
|
|
26
|
+
readonly width: stylex.StyleXClassNameFor<"width", 18>;
|
|
27
|
+
}>;
|
|
28
|
+
readonly lg: Readonly<{
|
|
29
|
+
readonly flexShrink: stylex.StyleXClassNameFor<"flexShrink", 0>;
|
|
30
|
+
readonly height: stylex.StyleXClassNameFor<"height", 20>;
|
|
31
|
+
readonly pointerEvents: stylex.StyleXClassNameFor<"pointerEvents", "none">;
|
|
32
|
+
readonly width: stylex.StyleXClassNameFor<"width", 20>;
|
|
33
|
+
}>;
|
|
34
|
+
}>;
|
|
35
|
+
export declare const ICON_STROKE = 2;
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import * as stylex from "@stylexjs/stylex";
|
|
2
|
+
/** An icon draws at the size it is given; these are the five in use. Stroke 2, currentColor. */
|
|
3
|
+
export const icon = stylex.create({
|
|
4
|
+
xs: { flexShrink: 0, height: 12, pointerEvents: "none", width: 12 },
|
|
5
|
+
sm: { flexShrink: 0, height: 14, pointerEvents: "none", width: 14 },
|
|
6
|
+
md: { flexShrink: 0, height: 16, pointerEvents: "none", width: 16 },
|
|
7
|
+
base: { flexShrink: 0, height: 18, pointerEvents: "none", width: 18 },
|
|
8
|
+
lg: { flexShrink: 0, height: 20, pointerEvents: "none", width: 20 },
|
|
9
|
+
});
|
|
10
|
+
export const ICON_STROKE = 2;
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import * as stylex from "@stylexjs/stylex";
|
|
2
|
+
import type { ButtonHTMLAttributes, ReactNode, Ref } from "react";
|
|
3
|
+
export type IconButtonProps = Omit<ButtonHTMLAttributes<HTMLButtonElement>, "type"> & {
|
|
4
|
+
/** The only place the name is written: the tooltip and the accessible name. */
|
|
5
|
+
label: string;
|
|
6
|
+
side?: "top" | "bottom" | "left" | "right";
|
|
7
|
+
/** `aria-current`: the page this button stands for is the one open. */
|
|
8
|
+
current?: boolean;
|
|
9
|
+
/** Pressed open: the popover it owns is showing. */
|
|
10
|
+
open?: boolean;
|
|
11
|
+
size?: "md" | "sm" | "xs";
|
|
12
|
+
badge?: number;
|
|
13
|
+
children: ReactNode;
|
|
14
|
+
ref?: Ref<HTMLButtonElement>;
|
|
15
|
+
sx?: stylex.StyleXStyles;
|
|
16
|
+
};
|
|
17
|
+
export declare function IconButton({ label, side, current, open, size, badge, children, sx, className, style, ...props }: IconButtonProps): import("react").JSX.Element;
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import * as stylex from "@stylexjs/stylex";
|
|
3
|
+
import { color, corner, font, ink, motion, type } from "../../tokens.stylex.js";
|
|
4
|
+
import { Tooltip } from "../tooltip/Tooltip.js";
|
|
5
|
+
/**
|
|
6
|
+
* `.ib`: a square that holds one glyph. Nothing at rest, an ink wash under the pointer, the
|
|
7
|
+
* name in a tooltip; a badge in the corner when it counts something.
|
|
8
|
+
*/
|
|
9
|
+
const REDUCED = "@media (prefers-reduced-motion: reduce)";
|
|
10
|
+
const styles = stylex.create({
|
|
11
|
+
root: {
|
|
12
|
+
alignItems: "center",
|
|
13
|
+
backgroundColor: { default: "transparent", ":hover": ink.n8 },
|
|
14
|
+
borderRadius: corner.ib,
|
|
15
|
+
borderStyle: "none",
|
|
16
|
+
borderWidth: 0,
|
|
17
|
+
color: { default: color.textMuted, ":hover": color.text },
|
|
18
|
+
cursor: "pointer",
|
|
19
|
+
display: "inline-grid",
|
|
20
|
+
flex: "none",
|
|
21
|
+
fontFamily: "inherit",
|
|
22
|
+
fontSize: "inherit",
|
|
23
|
+
height: "2.25rem",
|
|
24
|
+
justifyItems: "center",
|
|
25
|
+
lineHeight: "inherit",
|
|
26
|
+
margin: 0,
|
|
27
|
+
outline: { default: "none", ":focus-visible": `2px solid ${color.accent}` },
|
|
28
|
+
outlineOffset: 2,
|
|
29
|
+
padding: 0,
|
|
30
|
+
placeItems: "center",
|
|
31
|
+
position: "relative",
|
|
32
|
+
scale: { default: "1", ":active": "0.96" },
|
|
33
|
+
transitionDuration: { default: motion.fast, [REDUCED]: "0s" },
|
|
34
|
+
transitionProperty: "background-color, color",
|
|
35
|
+
transitionTimingFunction: motion.easeOut,
|
|
36
|
+
width: "2.25rem",
|
|
37
|
+
},
|
|
38
|
+
current: { color: color.text },
|
|
39
|
+
open: { backgroundColor: ink.n8, color: color.text },
|
|
40
|
+
sm: { height: "2rem", width: "2rem" },
|
|
41
|
+
xs: { height: "1.75rem", width: "1.75rem" },
|
|
42
|
+
badge: {
|
|
43
|
+
backgroundColor: color.accent,
|
|
44
|
+
borderRadius: 8,
|
|
45
|
+
color: color.accentText,
|
|
46
|
+
fontFamily: font.mono,
|
|
47
|
+
fontSize: type.t1,
|
|
48
|
+
fontWeight: 500,
|
|
49
|
+
height: 16,
|
|
50
|
+
insetInlineEnd: 2,
|
|
51
|
+
lineHeight: "16px",
|
|
52
|
+
minWidth: 16,
|
|
53
|
+
paddingInline: 4,
|
|
54
|
+
position: "absolute",
|
|
55
|
+
textAlign: "center",
|
|
56
|
+
top: 2,
|
|
57
|
+
},
|
|
58
|
+
});
|
|
59
|
+
export function IconButton({ label, side = "right", current = false, open = false, size = "md", badge, children, sx, className, style, ...props }) {
|
|
60
|
+
// A tooltip renders its trigger through the child and hands it a class and a style; merged.
|
|
61
|
+
const own = stylex.props(styles.root, current && styles.current, open && styles.open, size === "sm" && styles.sm, size === "xs" && styles.xs, sx);
|
|
62
|
+
const button = (_jsxs("button", { type: "button", "aria-label": label, ...(current ? { "aria-current": "page" } : {}), ...props, className: [own.className, className].filter(Boolean).join(" "), style: { ...own.style, ...style }, children: [children, badge !== undefined && badge > 0 && _jsx("span", { ...stylex.props(styles.badge), children: badge })] }));
|
|
63
|
+
if (open)
|
|
64
|
+
return button;
|
|
65
|
+
return (_jsx(Tooltip, { content: label, side: side, children: button }));
|
|
66
|
+
}
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
import * as stylex from "@stylexjs/stylex";
|
|
2
|
+
import type { ReactNode } from "react";
|
|
3
|
+
type Sx = {
|
|
4
|
+
sx?: stylex.StyleXStyles;
|
|
5
|
+
};
|
|
6
|
+
export type PageHeadProps = {
|
|
7
|
+
title: ReactNode;
|
|
8
|
+
/** The summary beside the title, before the spacer: `已連接 3 · 工具 49`. */
|
|
9
|
+
lead?: ReactNode;
|
|
10
|
+
/** The summary on the right, after the spacer: `已設定 3 / 8`, a stat line. */
|
|
11
|
+
summary?: ReactNode;
|
|
12
|
+
actions?: ReactNode;
|
|
13
|
+
} & Sx;
|
|
14
|
+
export declare function PageHead({ title, lead, summary, actions, sx }: PageHeadProps): import("react").JSX.Element;
|
|
15
|
+
/** A number inside a summary or a stat line: mono, in the text colour. */
|
|
16
|
+
export declare function B({ children }: {
|
|
17
|
+
children: ReactNode;
|
|
18
|
+
}): import("react").JSX.Element;
|
|
19
|
+
export type SectionLabelProps = {
|
|
20
|
+
children: ReactNode;
|
|
21
|
+
end?: ReactNode;
|
|
22
|
+
first?: boolean;
|
|
23
|
+
} & Sx;
|
|
24
|
+
export declare function SectionLabel({ children, end, first, sx }: SectionLabelProps): import("react").JSX.Element;
|
|
25
|
+
export declare function Sub({ children, sx }: {
|
|
26
|
+
children: ReactNode;
|
|
27
|
+
} & Sx): import("react").JSX.Element;
|
|
28
|
+
export declare function FootNote({ children, sx }: {
|
|
29
|
+
children: ReactNode;
|
|
30
|
+
} & Sx): import("react").JSX.Element;
|
|
31
|
+
export declare function Hint({ children, sx }: {
|
|
32
|
+
children: ReactNode;
|
|
33
|
+
} & Sx): import("react").JSX.Element;
|
|
34
|
+
export declare function Snippet({ children, sx }: {
|
|
35
|
+
children: ReactNode;
|
|
36
|
+
} & Sx): import("react").JSX.Element;
|
|
37
|
+
/** `.dpanel`: the bordered surface a section's rows sit on; `padded` when it holds prose. */
|
|
38
|
+
export declare function Panel({ children, padded, sx }: {
|
|
39
|
+
children: ReactNode;
|
|
40
|
+
padded?: boolean;
|
|
41
|
+
} & Sx): import("react").JSX.Element;
|
|
42
|
+
export type StatLineProps = {
|
|
43
|
+
children: ReactNode;
|
|
44
|
+
} & Sx;
|
|
45
|
+
export declare function StatLine({ children, sx }: StatLineProps): import("react").JSX.Element;
|
|
46
|
+
export declare function StatBar({ percent, label, text }: {
|
|
47
|
+
percent: number;
|
|
48
|
+
label?: string;
|
|
49
|
+
text?: string;
|
|
50
|
+
}): import("react").JSX.Element;
|
|
51
|
+
export type BarsProps = {
|
|
52
|
+
values: number[];
|
|
53
|
+
/** What a bar says when the pointer is on it. */
|
|
54
|
+
tip: (value: number, index: number) => string;
|
|
55
|
+
from: string;
|
|
56
|
+
to: string;
|
|
57
|
+
sx?: stylex.StyleXStyles;
|
|
58
|
+
};
|
|
59
|
+
/** `.bars` + `.bx`: one bar a day, the first and last day named under them. */
|
|
60
|
+
export declare function Bars({ values, tip, from, to, sx }: BarsProps): import("react").JSX.Element;
|
|
61
|
+
export declare function Faint({ children, sx }: {
|
|
62
|
+
children: ReactNode;
|
|
63
|
+
} & Sx): import("react").JSX.Element;
|
|
64
|
+
export {};
|
|
@@ -0,0 +1,163 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import * as stylex from "@stylexjs/stylex";
|
|
3
|
+
import { color, corner, font, space, tone, type } from "../../tokens.stylex.js";
|
|
4
|
+
/**
|
|
5
|
+
* The words around the rows: the page's one line (`.page-h`), the label a section wears
|
|
6
|
+
* (`.slab`), the sentence under a group (`.sub` / `.foot-note`), a code block, a stat line.
|
|
7
|
+
*/
|
|
8
|
+
const styles = stylex.create({
|
|
9
|
+
head: {
|
|
10
|
+
alignItems: "baseline",
|
|
11
|
+
color: color.text,
|
|
12
|
+
display: "flex",
|
|
13
|
+
flexWrap: "wrap",
|
|
14
|
+
gap: space.sm,
|
|
15
|
+
lineHeight: type.body,
|
|
16
|
+
marginBlock: `0 ${space.xs}`,
|
|
17
|
+
},
|
|
18
|
+
h1: { fontSize: type.t4, fontWeight: 600, lineHeight: type.tight, margin: 0 },
|
|
19
|
+
sp: { flex: 1 },
|
|
20
|
+
summary: { color: color.textMuted, fontSize: type.t2, minWidth: 0 },
|
|
21
|
+
b: { color: color.text, fontFamily: font.mono, fontWeight: 400 },
|
|
22
|
+
slab: {
|
|
23
|
+
alignItems: "center",
|
|
24
|
+
color: color.textMuted,
|
|
25
|
+
display: "flex",
|
|
26
|
+
fontFamily: font.mono,
|
|
27
|
+
fontSize: type.t1,
|
|
28
|
+
gap: space.xs,
|
|
29
|
+
lineHeight: type.body,
|
|
30
|
+
marginBlock: `${space.lg} ${space.xs}`,
|
|
31
|
+
},
|
|
32
|
+
slabFirst: { marginBlockStart: space.md },
|
|
33
|
+
n: { marginInlineStart: "auto" },
|
|
34
|
+
sub: {
|
|
35
|
+
color: color.textMuted,
|
|
36
|
+
fontSize: type.t2,
|
|
37
|
+
lineHeight: type.body,
|
|
38
|
+
marginBlock: `${space.sm} 0`,
|
|
39
|
+
},
|
|
40
|
+
foot: {
|
|
41
|
+
color: color.textMuted,
|
|
42
|
+
fontSize: type.t2,
|
|
43
|
+
lineHeight: type.body,
|
|
44
|
+
marginBlock: `${space.xs} 0`,
|
|
45
|
+
},
|
|
46
|
+
hint: {
|
|
47
|
+
color: color.textMuted,
|
|
48
|
+
fontSize: type.t2,
|
|
49
|
+
lineHeight: type.body,
|
|
50
|
+
marginBlock: `0 ${space.xs}`,
|
|
51
|
+
},
|
|
52
|
+
code: {
|
|
53
|
+
backgroundColor: color.layer3,
|
|
54
|
+
borderRadius: corner.sm,
|
|
55
|
+
color: color.textMuted,
|
|
56
|
+
fontFamily: font.mono,
|
|
57
|
+
fontSize: type.t1,
|
|
58
|
+
lineHeight: 1.6,
|
|
59
|
+
margin: 0,
|
|
60
|
+
maxWidth: "44rem",
|
|
61
|
+
overflowWrap: "break-word",
|
|
62
|
+
paddingBlock: space.xs,
|
|
63
|
+
paddingInline: space.sm,
|
|
64
|
+
whiteSpace: "pre-wrap",
|
|
65
|
+
wordBreak: "break-word",
|
|
66
|
+
},
|
|
67
|
+
panel: {
|
|
68
|
+
backgroundColor: color.surface,
|
|
69
|
+
borderRadius: corner.card,
|
|
70
|
+
boxShadow: `inset 0 0 0 1px ${color.border}`,
|
|
71
|
+
color: color.text,
|
|
72
|
+
fontSize: type.t2,
|
|
73
|
+
lineHeight: type.body,
|
|
74
|
+
},
|
|
75
|
+
padded: { display: "flex", flexDirection: "column", gap: space.xs, padding: space.sm },
|
|
76
|
+
stat: {
|
|
77
|
+
alignItems: "center",
|
|
78
|
+
color: color.textMuted,
|
|
79
|
+
display: "flex",
|
|
80
|
+
flexWrap: "wrap",
|
|
81
|
+
fontSize: type.t2,
|
|
82
|
+
gap: space.xs,
|
|
83
|
+
lineHeight: type.body,
|
|
84
|
+
},
|
|
85
|
+
sbar: {
|
|
86
|
+
backgroundColor: color.layer4,
|
|
87
|
+
borderRadius: 2,
|
|
88
|
+
flex: "none",
|
|
89
|
+
height: 4,
|
|
90
|
+
overflow: "hidden",
|
|
91
|
+
width: "8rem",
|
|
92
|
+
},
|
|
93
|
+
sfill: (percent) => ({
|
|
94
|
+
backgroundColor: color.accent,
|
|
95
|
+
display: "block",
|
|
96
|
+
height: "100%",
|
|
97
|
+
width: `${percent}%`,
|
|
98
|
+
}),
|
|
99
|
+
bars: { alignItems: "flex-end", display: "flex", gap: 2, height: "6rem" },
|
|
100
|
+
bar: {
|
|
101
|
+
backgroundColor: {
|
|
102
|
+
default: `color-mix(in srgb, ${color.accent} 60%, transparent)`,
|
|
103
|
+
":hover": color.accent,
|
|
104
|
+
},
|
|
105
|
+
borderRadius: "2px 2px 0 0",
|
|
106
|
+
flex: 1,
|
|
107
|
+
minWidth: 0,
|
|
108
|
+
position: "relative",
|
|
109
|
+
},
|
|
110
|
+
barH: (percent) => ({ height: `${percent}%` }),
|
|
111
|
+
bx: {
|
|
112
|
+
color: color.textMuted,
|
|
113
|
+
display: "flex",
|
|
114
|
+
fontFamily: font.mono,
|
|
115
|
+
fontSize: type.t1,
|
|
116
|
+
justifyContent: "space-between",
|
|
117
|
+
marginBlockStart: space.xxs,
|
|
118
|
+
},
|
|
119
|
+
faint: { color: tone.faint },
|
|
120
|
+
});
|
|
121
|
+
export function PageHead({ title, lead, summary, actions, sx }) {
|
|
122
|
+
return (_jsxs("div", { ...stylex.props(styles.head, sx), children: [_jsx("h1", { ...stylex.props(styles.h1), children: title }), lead !== undefined && _jsx("span", { ...stylex.props(styles.summary), children: lead }), _jsx("span", { ...stylex.props(styles.sp) }), summary !== undefined && _jsx("span", { ...stylex.props(styles.summary), children: summary }), actions] }));
|
|
123
|
+
}
|
|
124
|
+
/** A number inside a summary or a stat line: mono, in the text colour. */
|
|
125
|
+
export function B({ children }) {
|
|
126
|
+
return _jsx("b", { ...stylex.props(styles.b), children: children });
|
|
127
|
+
}
|
|
128
|
+
export function SectionLabel({ children, end, first = false, sx }) {
|
|
129
|
+
return (_jsxs("div", { ...stylex.props(styles.slab, first && styles.slabFirst, sx), children: [children, end !== undefined && _jsx("span", { ...stylex.props(styles.n), children: end })] }));
|
|
130
|
+
}
|
|
131
|
+
export function Sub({ children, sx }) {
|
|
132
|
+
return _jsx("p", { ...stylex.props(styles.sub, sx), children: children });
|
|
133
|
+
}
|
|
134
|
+
export function FootNote({ children, sx }) {
|
|
135
|
+
return _jsx("p", { ...stylex.props(styles.foot, sx), children: children });
|
|
136
|
+
}
|
|
137
|
+
export function Hint({ children, sx }) {
|
|
138
|
+
return _jsx("p", { ...stylex.props(styles.hint, sx), children: children });
|
|
139
|
+
}
|
|
140
|
+
export function Snippet({ children, sx }) {
|
|
141
|
+
return _jsx("pre", { ...stylex.props(styles.code, sx), children: children });
|
|
142
|
+
}
|
|
143
|
+
/** `.dpanel`: the bordered surface a section's rows sit on; `padded` when it holds prose. */
|
|
144
|
+
export function Panel({ children, padded = false, sx }) {
|
|
145
|
+
return _jsx("div", { ...stylex.props(styles.panel, padded && styles.padded, sx), children: children });
|
|
146
|
+
}
|
|
147
|
+
export function StatLine({ children, sx }) {
|
|
148
|
+
return _jsx("span", { ...stylex.props(styles.stat, sx), children: children });
|
|
149
|
+
}
|
|
150
|
+
export function StatBar({ percent, label, text }) {
|
|
151
|
+
const value = Math.max(0, Math.min(100, percent));
|
|
152
|
+
return (_jsx("span", { ...stylex.props(styles.sbar),
|
|
153
|
+
// oxlint-disable-next-line jsx-a11y/prefer-tag-over-role -- a 4px reading, not a control
|
|
154
|
+
role: "progressbar", "aria-valuemin": 0, "aria-valuemax": 100, "aria-valuenow": Math.round(value), ...(label ? { "aria-label": label } : {}), ...(text ? { "aria-valuetext": text } : {}), children: _jsx("i", { ...stylex.props(styles.sfill(value)) }) }));
|
|
155
|
+
}
|
|
156
|
+
/** `.bars` + `.bx`: one bar a day, the first and last day named under them. */
|
|
157
|
+
export function Bars({ values, tip, from, to, sx }) {
|
|
158
|
+
const max = Math.max(1, ...values);
|
|
159
|
+
return (_jsxs("div", { ...stylex.props(sx), children: [_jsx("div", { ...stylex.props(styles.bars), "aria-label": `${from} – ${to}`, children: values.map((value, index) => (_jsx("i", { title: tip(value, index), ...stylex.props(styles.bar, styles.barH(Math.round((value / max) * 100))) }, index))) }), _jsxs("div", { ...stylex.props(styles.bx), children: [_jsx("span", { children: from }), _jsx("span", { children: to })] })] }));
|
|
160
|
+
}
|
|
161
|
+
export function Faint({ children, sx }) {
|
|
162
|
+
return _jsx("span", { ...stylex.props(styles.faint, sx), children: children });
|
|
163
|
+
}
|
|
@@ -1,68 +1,52 @@
|
|
|
1
|
-
import { jsx as _jsx,
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
2
|
import * as stylex from "@stylexjs/stylex";
|
|
3
|
-
import { useCallback,
|
|
4
|
-
import { BALL_STRANDS, ballDashOffset } from "../../lib/progress.js";
|
|
3
|
+
import { useCallback, useState } from "react";
|
|
5
4
|
import { styled } from "../../lib/styled.js";
|
|
6
|
-
import {
|
|
7
|
-
import { buildWeave, weaveRand } from "../../lib/weave.js";
|
|
8
|
-
import { color, font, motion, radius, space, yarn } from "../../tokens.stylex.js";
|
|
5
|
+
import { color, font, motion, radius, space } from "../../tokens.stylex.js";
|
|
9
6
|
const REDUCED = "@media (prefers-reduced-motion: reduce)";
|
|
10
7
|
const STAGES = ["掃描對話", "挑出耐久事實", "合併重複", "落盤固定"];
|
|
11
|
-
// 進度 =
|
|
12
|
-
//
|
|
13
|
-
|
|
14
|
-
// 全部 CSS,零 rAF。
|
|
15
|
-
const TRACK_H = 20;
|
|
16
|
-
const PAD = 2;
|
|
17
|
-
const CLOTH_H = TRACK_H - 2 - PAD * 2;
|
|
8
|
+
// 進度 = 一條軌加一段填充:軌是 border 色的 4px 帶,填充是實心 accent,
|
|
9
|
+
// 寬度就是讀數。不定量時同一段填充在軌上等速滑過。全部 CSS,零 rAF。
|
|
10
|
+
const TRACK_H = 4;
|
|
18
11
|
const RING = 60;
|
|
19
|
-
const BAND =
|
|
12
|
+
const BAND = 6;
|
|
13
|
+
const SWEEP_W = 40;
|
|
20
14
|
const spin = stylex.keyframes({ from: { rotate: "0deg" }, to: { rotate: "360deg" } });
|
|
21
|
-
const sweep = stylex.keyframes({
|
|
15
|
+
const sweep = stylex.keyframes({
|
|
16
|
+
from: { insetInlineStart: `-${SWEEP_W}%` },
|
|
17
|
+
to: { insetInlineStart: "100%" },
|
|
18
|
+
});
|
|
22
19
|
const styles = stylex.create({
|
|
23
20
|
svg: { display: "block" },
|
|
24
21
|
track: {
|
|
25
|
-
|
|
26
|
-
alignItems: "center",
|
|
22
|
+
position: "relative",
|
|
27
23
|
width: "100%",
|
|
28
24
|
height: TRACK_H,
|
|
29
25
|
boxSizing: "border-box",
|
|
30
|
-
padding: PAD,
|
|
31
|
-
borderWidth: 1,
|
|
32
|
-
borderStyle: "solid",
|
|
33
|
-
borderColor: color.border,
|
|
34
26
|
borderRadius: radius.full,
|
|
35
|
-
backgroundColor: color.
|
|
27
|
+
backgroundColor: color.border,
|
|
36
28
|
overflow: "hidden",
|
|
37
29
|
},
|
|
38
|
-
|
|
39
|
-
position: "
|
|
40
|
-
|
|
30
|
+
fill: {
|
|
31
|
+
position: "absolute",
|
|
32
|
+
top: 0,
|
|
33
|
+
bottom: 0,
|
|
34
|
+
insetInlineStart: 0,
|
|
41
35
|
borderRadius: radius.full,
|
|
42
|
-
|
|
43
|
-
filter: yarn.shadow,
|
|
36
|
+
backgroundColor: color.accent,
|
|
44
37
|
transitionProperty: "width",
|
|
45
38
|
transitionDuration: { default: motion.fast, [REDUCED]: "0s" },
|
|
46
39
|
transitionTimingFunction: "linear",
|
|
47
40
|
},
|
|
48
41
|
grow: (percent) => ({ width: `${percent}%` }),
|
|
49
42
|
window: {
|
|
50
|
-
width:
|
|
51
|
-
insetInlineStart: 0,
|
|
43
|
+
width: `${SWEEP_W}%`,
|
|
44
|
+
insetInlineStart: { default: null, [REDUCED]: 0 },
|
|
52
45
|
animationName: { default: sweep, [REDUCED]: "none" },
|
|
53
46
|
animationDuration: "1.8s",
|
|
54
47
|
animationTimingFunction: "linear",
|
|
55
48
|
animationIterationCount: "infinite",
|
|
56
49
|
},
|
|
57
|
-
cloth: { fill: "none", strokeLinecap: "round" },
|
|
58
|
-
fUn: { stroke: yarn.un },
|
|
59
|
-
fSh: { stroke: yarn.sh, opacity: 0.5 },
|
|
60
|
-
fY0: { stroke: yarn.y0 },
|
|
61
|
-
fY1: { stroke: yarn.y1 },
|
|
62
|
-
fY2: { stroke: yarn.y2 },
|
|
63
|
-
fY3: { stroke: yarn.y3 },
|
|
64
|
-
fY4: { stroke: yarn.y4 },
|
|
65
|
-
fHi: { stroke: yarn.hi, opacity: 0.45 },
|
|
66
50
|
tidy: { display: "grid", gap: space.xs },
|
|
67
51
|
stage: {
|
|
68
52
|
fontFamily: font.mono,
|
|
@@ -74,24 +58,30 @@ const styles = stylex.create({
|
|
|
74
58
|
fontVariantNumeric: "tabular-nums",
|
|
75
59
|
},
|
|
76
60
|
ringWrap: { position: "relative", display: "inline-grid", placeItems: "center" },
|
|
77
|
-
ringTrack: { fill: "none", stroke: color.
|
|
78
|
-
|
|
79
|
-
|
|
61
|
+
ringTrack: { fill: "none", stroke: color.border },
|
|
62
|
+
ringArc: {
|
|
63
|
+
fill: "none",
|
|
64
|
+
stroke: color.accent,
|
|
65
|
+
strokeLinecap: "round",
|
|
66
|
+
transform: "rotate(-90deg)",
|
|
80
67
|
transformBox: "fill-box",
|
|
81
68
|
transformOrigin: "center",
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
animationIterationCount: "infinite",
|
|
69
|
+
transitionProperty: "stroke-dasharray",
|
|
70
|
+
transitionDuration: { default: motion.fast, [REDUCED]: "0s" },
|
|
71
|
+
transitionTimingFunction: "linear",
|
|
86
72
|
},
|
|
87
|
-
|
|
73
|
+
spinArc: {
|
|
88
74
|
fill: "none",
|
|
89
|
-
stroke:
|
|
90
|
-
strokeWidth: 1.5,
|
|
75
|
+
stroke: "currentColor",
|
|
91
76
|
strokeLinecap: "round",
|
|
92
|
-
|
|
77
|
+
transformBox: "fill-box",
|
|
78
|
+
transformOrigin: "center",
|
|
79
|
+
animationName: { default: spin, [REDUCED]: "none" },
|
|
80
|
+
animationDuration: "0.8s",
|
|
81
|
+
animationTimingFunction: "linear",
|
|
82
|
+
animationIterationCount: "infinite",
|
|
93
83
|
},
|
|
94
|
-
statusHost: { display: "inline-flex" },
|
|
84
|
+
statusHost: { display: "inline-flex", color: color.textMuted },
|
|
95
85
|
srOnly: {
|
|
96
86
|
position: "absolute",
|
|
97
87
|
width: 1,
|
|
@@ -113,62 +103,17 @@ const styles = stylex.create({
|
|
|
113
103
|
fontVariantNumeric: "tabular-nums",
|
|
114
104
|
},
|
|
115
105
|
});
|
|
116
|
-
const BUCKETS = [styles.fY0, styles.fY1, styles.fY2, styles.fY3, styles.fY4];
|
|
117
|
-
function Cloth({ layers }) {
|
|
118
|
-
return (_jsxs(_Fragment, { children: [_jsx("g", { ...stylex.props(styles.fUn), children: layers.under.map((strand, index) => (_jsx("path", { d: strand.d, strokeWidth: strand.sw }, index))) }), _jsx("g", { ...stylex.props(styles.fSh), children: layers.seams.map((strand, index) => (_jsx("path", { d: strand.d, strokeWidth: strand.sw }, index))) }), layers.face.map((strand, index) => (_jsx("path", { d: strand.d, strokeWidth: strand.sw, ...stylex.props(BUCKETS[strand.bucket]) }, index))), _jsx("g", { ...stylex.props(styles.fHi), children: layers.hi.map((strand, index) => (_jsx("path", { d: strand.d, strokeWidth: strand.sw }, index))) })] }));
|
|
119
|
-
}
|
|
120
|
-
// 量到容器內寬才織(ref callback + ResizeObserver,不用 effect);布永遠是整條軌的寬度,
|
|
121
|
-
// 所以填充長出來時露出的是同一塊布的更多段,不是把布拉長。
|
|
122
106
|
function Bar({ percent }) {
|
|
123
|
-
|
|
124
|
-
const measure = useCallback((node) => {
|
|
125
|
-
if (!node)
|
|
126
|
-
return;
|
|
127
|
-
const read = () => setInner((current) => {
|
|
128
|
-
const next = node.clientWidth - PAD * 2;
|
|
129
|
-
return current === next ? current : next;
|
|
130
|
-
});
|
|
131
|
-
read();
|
|
132
|
-
const observer = new ResizeObserver(read);
|
|
133
|
-
observer.observe(node);
|
|
134
|
-
return () => observer.disconnect();
|
|
135
|
-
}, []);
|
|
136
|
-
const cloth = useMemo(() => (inner > 0 ? buildWeave({ w: inner, h: CLOTH_H }, weaveRand()) : null), [inner]);
|
|
137
|
-
return (_jsx("span", { ref: measure, ...stylex.props(styles.track), children: cloth != null && (_jsx("span", { ...stylex.props(styles.body, percent != null ? styles.grow(percent) : styles.window, styles.cloth), children: _jsx("svg", { width: inner, height: CLOTH_H, viewBox: `0 0 ${inner} ${CLOTH_H}`, "aria-hidden": "true", ...stylex.props(styles.svg, styles.cloth), children: _jsx(Cloth, { layers: cloth }) }) })) }));
|
|
138
|
-
}
|
|
139
|
-
// 環形:同一塊布在後面,弧形取景框開到 percent(和 radio 的鏡頭同一個邏輯)
|
|
140
|
-
function ringSector(percent) {
|
|
141
|
-
const centre = RING / 2;
|
|
142
|
-
const outer = centre - 1;
|
|
143
|
-
const nner = outer - BAND;
|
|
144
|
-
const sweepDeg = Math.min(359.99, (percent / 100) * 360);
|
|
145
|
-
const a0 = -Math.PI / 2;
|
|
146
|
-
const a1 = a0 + (sweepDeg * Math.PI) / 180;
|
|
147
|
-
const large = sweepDeg > 180 ? 1 : 0;
|
|
148
|
-
const point = (r, angle) => `${(centre + r * Math.cos(angle)).toFixed(2)},${(centre + r * Math.sin(angle)).toFixed(2)}`;
|
|
149
|
-
return [
|
|
150
|
-
`M${point(outer, a0)}`,
|
|
151
|
-
`A${outer},${outer} 0 ${large} 1 ${point(outer, a1)}`,
|
|
152
|
-
`L${point(nner, a1)}`,
|
|
153
|
-
`A${nner},${nner} 0 ${large} 0 ${point(nner, a0)}`,
|
|
154
|
-
"Z",
|
|
155
|
-
].join("");
|
|
107
|
+
return (_jsx("span", { ...stylex.props(styles.track), children: _jsx("span", { ...stylex.props(styles.fill, percent != null ? styles.grow(percent) : styles.window) }) }));
|
|
156
108
|
}
|
|
157
109
|
const SPINNER_SIZES = { sm: 18, md: 28, lg: 40 };
|
|
158
|
-
// spinner = 一段布做的弧在轉。布在後面不動(每個尺寸在 module scope 各織一塊,
|
|
159
|
-
// SSR 安全),轉的是遮罩上那道弧 —— 和 radio 的鏡頭、tabs 的取景窗同一個邏輯。
|
|
160
|
-
const SPINNER_CLOTH = {
|
|
161
|
-
sm: buildWeave({ w: SPINNER_SIZES.sm, h: SPINNER_SIZES.sm }, weaveRand()),
|
|
162
|
-
md: buildWeave({ w: SPINNER_SIZES.md, h: SPINNER_SIZES.md }, weaveRand()),
|
|
163
|
-
lg: buildWeave({ w: SPINNER_SIZES.lg, h: SPINNER_SIZES.lg }, weaveRand()),
|
|
164
|
-
};
|
|
165
110
|
const clampPercent = (value) => Math.max(0, Math.min(100, value));
|
|
111
|
+
// spinner = 一段圓弧在轉:pathLength 100,72 長的弧留 28 的缺口。
|
|
166
112
|
export function Spinner({ size = "md", label = "載入中" }) {
|
|
167
|
-
const maskId = useSvgId("ak-spin");
|
|
168
113
|
const px = SPINNER_SIZES[size];
|
|
169
114
|
const band = px * 0.135;
|
|
170
|
-
const
|
|
171
|
-
return (_jsxs("span", { role: "status", "aria-label": label, ...stylex.props(styles.statusHost), children: [
|
|
115
|
+
const r = (px - band) / 2 - 0.5;
|
|
116
|
+
return (_jsxs("span", { role: "status", "aria-label": label, ...stylex.props(styles.statusHost), children: [_jsx("svg", { width: px, height: px, viewBox: `0 0 ${px} ${px}`, "aria-hidden": "true", ...stylex.props(styles.svg), children: _jsx("circle", { cx: px / 2, cy: px / 2, r: r, strokeWidth: band, pathLength: "100", strokeDasharray: "72 28", ...stylex.props(styles.spinArc) }) }), _jsx("span", { ...stylex.props(styles.srOnly), children: label })] }));
|
|
172
117
|
}
|
|
173
118
|
export function Progress({ value, valueText, stages = STAGES, ...rest }) {
|
|
174
119
|
if (value == null)
|
|
@@ -177,7 +122,7 @@ export function Progress({ value, valueText, stages = STAGES, ...rest }) {
|
|
|
177
122
|
return (_jsx("div", { role: "progressbar", "aria-valuemin": 0, "aria-valuemax": 100, "aria-valuenow": Math.round(percent), "aria-valuetext": valueText, ...rest, ...styled(rest, styles.svg), children: _jsx(Bar, { percent: percent }) }));
|
|
178
123
|
}
|
|
179
124
|
// 不定量:沒有真的進度可報,所以不給 aria-valuenow,也不顯示百分比。
|
|
180
|
-
//
|
|
125
|
+
// 一段填充在軌道上走完再走一次,底下是現在在做什麼。
|
|
181
126
|
function ProgressTidy({ stages, valueText, ...rest }) {
|
|
182
127
|
const [step, setStep] = useState(0);
|
|
183
128
|
const cycle = useCallback((node) => {
|
|
@@ -190,11 +135,11 @@ function ProgressTidy({ stages, valueText, ...rest }) {
|
|
|
190
135
|
}
|
|
191
136
|
export function ProgressBall({ value, size = 48, valueText, ...rest }) {
|
|
192
137
|
const percent = clampPercent(value);
|
|
193
|
-
|
|
138
|
+
const band = size * 0.12;
|
|
139
|
+
const r = size / 2 - band / 2 - 1;
|
|
140
|
+
return (_jsxs("svg", { width: size, height: size, viewBox: `0 0 ${size} ${size}`, role: "progressbar", "aria-valuemin": 0, "aria-valuemax": 100, "aria-valuenow": Math.round(percent), "aria-valuetext": valueText, ...rest, ...styled(rest, styles.svg), children: [_jsx("circle", { cx: size / 2, cy: size / 2, r: r, strokeWidth: band, ...stylex.props(styles.ringTrack) }), _jsx("circle", { cx: size / 2, cy: size / 2, r: r, strokeWidth: band, pathLength: "100", strokeDasharray: `${percent} ${100 - percent}`, ...stylex.props(styles.ringArc) })] }));
|
|
194
141
|
}
|
|
195
142
|
export function ProgressRing({ value, size = RING, valueText, ...rest }) {
|
|
196
143
|
const percent = clampPercent(value);
|
|
197
|
-
|
|
198
|
-
const cloth = useMemo(() => buildWeave({ w: RING, h: RING }, weaveRand()), []);
|
|
199
|
-
return (_jsxs("span", { role: "progressbar", "aria-valuemin": 0, "aria-valuemax": 100, "aria-valuenow": Math.round(percent), "aria-valuetext": valueText, ...rest, ...styled(rest, styles.ringWrap), children: [_jsxs("svg", { width: size, height: size, viewBox: `0 0 ${RING} ${RING}`, "aria-hidden": "true", ...stylex.props(styles.svg), children: [_jsx("defs", { children: _jsx("clipPath", { id: clipId, children: _jsx("path", { d: ringSector(percent) }) }) }), _jsx("circle", { cx: RING / 2, cy: RING / 2, r: RING / 2 - 1 - BAND / 2, strokeWidth: BAND, ...stylex.props(styles.ringTrack) }), _jsx("g", { clipPath: `url(#${clipId})`, ...stylex.props(styles.cloth, styles.ringCloth), children: _jsx(Cloth, { layers: cloth }) })] }), _jsx("span", { "aria-hidden": "true", ...stylex.props(styles.value), children: `${Math.round(percent)}%` })] }));
|
|
144
|
+
return (_jsxs("span", { role: "progressbar", "aria-valuemin": 0, "aria-valuemax": 100, "aria-valuenow": Math.round(percent), "aria-valuetext": valueText, ...rest, ...styled(rest, styles.ringWrap), children: [_jsxs("svg", { width: size, height: size, viewBox: `0 0 ${RING} ${RING}`, "aria-hidden": "true", ...stylex.props(styles.svg), children: [_jsx("circle", { cx: RING / 2, cy: RING / 2, r: RING / 2 - BAND / 2 - 1, strokeWidth: BAND, ...stylex.props(styles.ringTrack) }), _jsx("circle", { cx: RING / 2, cy: RING / 2, r: RING / 2 - BAND / 2 - 1, strokeWidth: BAND, pathLength: "100", strokeDasharray: `${percent} ${100 - percent}`, ...stylex.props(styles.ringArc) })] }), _jsx("span", { "aria-hidden": "true", ...stylex.props(styles.value), children: `${Math.round(percent)}%` })] }));
|
|
200
145
|
}
|