@anyknown/ui 0.1.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/LICENSE +21 -0
- package/README.md +121 -0
- package/dist/components/action-bar/ActionBar.d.ts +28 -0
- package/dist/components/action-bar/ActionBar.js +73 -0
- package/dist/components/badge/Badge.d.ts +28 -0
- package/dist/components/badge/Badge.js +79 -0
- package/dist/components/button/Button.d.ts +7 -0
- package/dist/components/button/Button.js +86 -0
- package/dist/components/card/Card.d.ts +2 -0
- package/dist/components/card/Card.js +17 -0
- package/dist/components/checkbox/Checkbox.d.ts +8 -0
- package/dist/components/checkbox/Checkbox.js +102 -0
- package/dist/components/code-block/CodeBlock.d.ts +10 -0
- package/dist/components/code-block/CodeBlock.js +103 -0
- package/dist/components/composer/Composer.d.ts +27 -0
- package/dist/components/composer/Composer.js +278 -0
- package/dist/components/data-table/DataTable.d.ts +38 -0
- package/dist/components/data-table/DataTable.js +256 -0
- package/dist/components/dialog/Dialog.d.ts +36 -0
- package/dist/components/dialog/Dialog.js +77 -0
- package/dist/components/diff-viewer/DiffViewer.d.ts +13 -0
- package/dist/components/diff-viewer/DiffViewer.js +133 -0
- package/dist/components/dropdown/DropdownMenu.d.ts +43 -0
- package/dist/components/dropdown/DropdownMenu.js +86 -0
- package/dist/components/dropzone/Dropzone.d.ts +32 -0
- package/dist/components/dropzone/Dropzone.js +242 -0
- package/dist/components/empty-state/EmptyState.d.ts +9 -0
- package/dist/components/empty-state/EmptyState.js +41 -0
- package/dist/components/file-row/FileRow.d.ts +32 -0
- package/dist/components/file-row/FileRow.js +153 -0
- package/dist/components/handoff-receipt/HandoffReceipt.d.ts +15 -0
- package/dist/components/handoff-receipt/HandoffReceipt.js +147 -0
- package/dist/components/input/Input.d.ts +48 -0
- package/dist/components/input/Input.js +64 -0
- package/dist/components/interaction-card/InteractionCard.d.ts +59 -0
- package/dist/components/interaction-card/InteractionCard.js +238 -0
- package/dist/components/kbd/Kbd.d.ts +9 -0
- package/dist/components/kbd/Kbd.js +43 -0
- package/dist/components/label/Field.d.ts +11 -0
- package/dist/components/label/Field.js +28 -0
- package/dist/components/label/Label.d.ts +7 -0
- package/dist/components/label/Label.js +21 -0
- package/dist/components/label/fieldContext.d.ts +26 -0
- package/dist/components/label/fieldContext.js +16 -0
- package/dist/components/message/Message.d.ts +29 -0
- package/dist/components/message/Message.js +94 -0
- package/dist/components/password-input/PasswordInput.d.ts +20 -0
- package/dist/components/password-input/PasswordInput.js +141 -0
- package/dist/components/popover/Popover.d.ts +38 -0
- package/dist/components/popover/Popover.js +37 -0
- package/dist/components/progress/Progress.d.ts +36 -0
- package/dist/components/progress/Progress.js +200 -0
- package/dist/components/radio/Radio.d.ts +7 -0
- package/dist/components/radio/Radio.js +89 -0
- package/dist/components/radio/RadioGroup.d.ts +21 -0
- package/dist/components/radio/RadioGroup.js +34 -0
- package/dist/components/reasoning-fold/ReasoningFold.d.ts +10 -0
- package/dist/components/reasoning-fold/ReasoningFold.js +92 -0
- package/dist/components/recovery-key/RecoveryKey.d.ts +17 -0
- package/dist/components/recovery-key/RecoveryKey.js +132 -0
- package/dist/components/select/Select.d.ts +31 -0
- package/dist/components/select/Select.js +209 -0
- package/dist/components/skeleton/Skeleton.d.ts +20 -0
- package/dist/components/skeleton/Skeleton.js +57 -0
- package/dist/components/switch/Switch.d.ts +7 -0
- package/dist/components/switch/Switch.js +108 -0
- package/dist/components/tabs/Tabs.d.ts +28 -0
- package/dist/components/tabs/Tabs.js +133 -0
- package/dist/components/text/Text.d.ts +7 -0
- package/dist/components/text/Text.js +31 -0
- package/dist/components/textarea/Textarea.d.ts +7 -0
- package/dist/components/textarea/Textarea.js +25 -0
- package/dist/components/toast/Toast.d.ts +56 -0
- package/dist/components/toast/Toast.js +175 -0
- package/dist/components/tool-card/ToolCard.d.ts +55 -0
- package/dist/components/tool-card/ToolCard.js +281 -0
- package/dist/components/tooltip/Tooltip.d.ts +12 -0
- package/dist/components/tooltip/Tooltip.js +38 -0
- package/dist/components/voice-indicator/VoiceIndicator.d.ts +7 -0
- package/dist/components/voice-indicator/VoiceIndicator.js +51 -0
- package/dist/index.d.ts +55 -0
- package/dist/index.js +38 -0
- package/dist/lib/diff.d.ts +28 -0
- package/dist/lib/diff.js +133 -0
- package/dist/lib/format.d.ts +2 -0
- package/dist/lib/format.js +24 -0
- package/dist/lib/format.test.d.ts +1 -0
- package/dist/lib/format.test.js +37 -0
- package/dist/lib/mergeRefs.d.ts +2 -0
- package/dist/lib/mergeRefs.js +8 -0
- package/dist/lib/motion.d.ts +1 -0
- package/dist/lib/motion.js +10 -0
- package/dist/lib/paths.d.ts +3 -0
- package/dist/lib/paths.js +11 -0
- package/dist/lib/popup.d.ts +29 -0
- package/dist/lib/popup.js +27 -0
- package/dist/lib/progress.d.ts +2 -0
- package/dist/lib/progress.js +28 -0
- package/dist/lib/silk.d.ts +66 -0
- package/dist/lib/silk.js +337 -0
- package/dist/lib/styled.d.ts +29 -0
- package/dist/lib/styled.js +30 -0
- package/dist/lib/svgId.d.ts +1 -0
- package/dist/lib/svgId.js +4 -0
- package/dist/lib/useAnimationFrame.d.ts +1 -0
- package/dist/lib/useAnimationFrame.js +21 -0
- package/dist/lib/useControllableState.d.ts +1 -0
- package/dist/lib/useControllableState.js +14 -0
- package/dist/lib/useCopy.d.ts +4 -0
- package/dist/lib/useCopy.js +19 -0
- package/dist/lib/voice.d.ts +2 -0
- package/dist/lib/voice.js +30 -0
- package/dist/lib/weave.d.ts +28 -0
- package/dist/lib/weave.js +83 -0
- package/dist/scrollbar.css +29 -0
- package/dist/themes.stylex.d.ts +49 -0
- package/dist/themes.stylex.js +52 -0
- package/dist/tokens.css +109 -0
- package/dist/tokens.stylex.d.ts +126 -0
- package/dist/tokens.stylex.js +149 -0
- package/package.json +81 -0
|
@@ -0,0 +1,147 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import * as stylex from "@stylexjs/stylex";
|
|
3
|
+
import { useCallback, useId, useState } from "react";
|
|
4
|
+
import { KNOT_LEAD, KNOT_LOOP } from "../../lib/paths.js";
|
|
5
|
+
import { SilkBody } from "../../lib/silk.js";
|
|
6
|
+
import { color, font, motion, radius, space, text, yarnSecondary } from "../../tokens.stylex.js";
|
|
7
|
+
const REDUCED = "@media (prefers-reduced-motion: reduce)";
|
|
8
|
+
// 卡面 = 淺色布(TEXTURE-GUIDE §3、handoff-receipt/NOTES.md):沒有 background,
|
|
9
|
+
// 實心由紗織成;動態取「僅展示面」子集 — hover 帶動 + 光澤帶,無窩、無掃光。
|
|
10
|
+
// 布是固定高度 480 的長布,展開只是露出同一塊布更多 → 高度變化零重織、織紋恆定。
|
|
11
|
+
const styles = stylex.create({
|
|
12
|
+
receipt: {
|
|
13
|
+
position: "relative",
|
|
14
|
+
overflow: "hidden",
|
|
15
|
+
borderWidth: 1,
|
|
16
|
+
borderStyle: "solid",
|
|
17
|
+
borderColor: color.border,
|
|
18
|
+
borderRadius: radius.md,
|
|
19
|
+
},
|
|
20
|
+
face: { position: "absolute", top: 0, left: 0, pointerEvents: "none" },
|
|
21
|
+
row: {
|
|
22
|
+
// 注意:StyleX 0.19 會靜默丟掉 `all: unset`(編不出任何規則),原生按鈕的
|
|
23
|
+
// buttonface 底色與 outset 邊框會蓋住布 —— 要逐項重設
|
|
24
|
+
appearance: "none",
|
|
25
|
+
backgroundColor: "transparent",
|
|
26
|
+
borderWidth: 0,
|
|
27
|
+
position: "relative",
|
|
28
|
+
display: "flex",
|
|
29
|
+
alignItems: "center",
|
|
30
|
+
gap: space.xs,
|
|
31
|
+
width: "100%",
|
|
32
|
+
boxSizing: "border-box",
|
|
33
|
+
paddingBlock: space.xs,
|
|
34
|
+
paddingInline: space.sm,
|
|
35
|
+
cursor: "pointer",
|
|
36
|
+
fontFamily: font.body,
|
|
37
|
+
fontSize: text.xs,
|
|
38
|
+
color: { default: color.textMuted, ":hover": color.text },
|
|
39
|
+
borderRadius: radius.md,
|
|
40
|
+
outline: { default: "none", ":focus-visible": `2px solid ${color.focusRing}` },
|
|
41
|
+
outlineOffset: -2,
|
|
42
|
+
},
|
|
43
|
+
rule: {
|
|
44
|
+
flex: 1,
|
|
45
|
+
borderTopWidth: 1,
|
|
46
|
+
borderTopStyle: "dashed",
|
|
47
|
+
borderTopColor: color.borderStrong,
|
|
48
|
+
minWidth: space.md,
|
|
49
|
+
transitionProperty: "border-color",
|
|
50
|
+
transitionDuration: { default: "300ms", [REDUCED]: "0s" },
|
|
51
|
+
},
|
|
52
|
+
ruleOpen: { borderTopStyle: "solid", borderTopColor: color.accent },
|
|
53
|
+
label: { whiteSpace: "nowrap", display: "flex", alignItems: "center", gap: space.xxs },
|
|
54
|
+
mono: { fontFamily: font.mono, fontSize: "0.76rem", lineHeight: 1 },
|
|
55
|
+
knot: { flex: "none", overflow: "visible" },
|
|
56
|
+
lead: {
|
|
57
|
+
stroke: color.textFaint,
|
|
58
|
+
transitionProperty: "opacity",
|
|
59
|
+
transitionDuration: { default: motion.normal, [REDUCED]: "0s" },
|
|
60
|
+
},
|
|
61
|
+
leadHidden: { opacity: 0 },
|
|
62
|
+
loop: {
|
|
63
|
+
stroke: color.accent,
|
|
64
|
+
strokeDasharray: 100,
|
|
65
|
+
strokeDashoffset: 100,
|
|
66
|
+
transitionProperty: "stroke-dashoffset",
|
|
67
|
+
transitionDuration: { default: "500ms", [REDUCED]: "0s" },
|
|
68
|
+
transitionTimingFunction: "ease",
|
|
69
|
+
transitionDelay: { default: "120ms", [REDUCED]: "0s" },
|
|
70
|
+
},
|
|
71
|
+
loopTied: { strokeDashoffset: 0 },
|
|
72
|
+
chevron: {
|
|
73
|
+
flex: "none",
|
|
74
|
+
color: color.textFaint,
|
|
75
|
+
transitionProperty: "rotate",
|
|
76
|
+
transitionDuration: { default: "160ms", [REDUCED]: "0s" },
|
|
77
|
+
transitionTimingFunction: "ease",
|
|
78
|
+
},
|
|
79
|
+
chevronOpen: { rotate: "180deg" },
|
|
80
|
+
// 展開 = 0fr → 1fr(雙向平滑,不是 display 硬切);內容 opacity 跟進
|
|
81
|
+
bodyWrap: {
|
|
82
|
+
position: "relative",
|
|
83
|
+
display: "grid",
|
|
84
|
+
gridTemplateRows: "0fr",
|
|
85
|
+
transitionProperty: "grid-template-rows",
|
|
86
|
+
transitionDuration: { default: "240ms", [REDUCED]: "0s" },
|
|
87
|
+
transitionTimingFunction: "cubic-bezier(0.16, 1, 0.3, 1)",
|
|
88
|
+
},
|
|
89
|
+
bodyWrapOpen: { gridTemplateRows: "1fr" },
|
|
90
|
+
bodyClip: { overflow: "hidden", minHeight: 0 },
|
|
91
|
+
body: {
|
|
92
|
+
borderTopWidth: 1,
|
|
93
|
+
borderTopStyle: "solid",
|
|
94
|
+
borderTopColor: color.border,
|
|
95
|
+
paddingBlock: space.xs,
|
|
96
|
+
paddingInline: space.sm,
|
|
97
|
+
fontFamily: font.body,
|
|
98
|
+
fontSize: text.xs,
|
|
99
|
+
display: "grid",
|
|
100
|
+
gap: space.xxs,
|
|
101
|
+
opacity: 0,
|
|
102
|
+
transitionProperty: "opacity",
|
|
103
|
+
transitionDuration: { default: "200ms", [REDUCED]: "0s" },
|
|
104
|
+
transitionTimingFunction: "ease-out",
|
|
105
|
+
},
|
|
106
|
+
bodyOpen: { opacity: 1 },
|
|
107
|
+
check: { display: "flex", alignItems: "baseline", gap: space.xxs, margin: 0 },
|
|
108
|
+
checkIcon: { flex: "none", color: color.accent, translate: "0 2px" },
|
|
109
|
+
checkTitle: { fontWeight: 500, whiteSpace: "nowrap", color: color.text },
|
|
110
|
+
checkText: { color: color.textMuted },
|
|
111
|
+
summary: {
|
|
112
|
+
margin: 0,
|
|
113
|
+
color: color.textMuted,
|
|
114
|
+
borderInlineStartWidth: 2,
|
|
115
|
+
borderInlineStartStyle: "solid",
|
|
116
|
+
borderInlineStartColor: color.border,
|
|
117
|
+
paddingInlineStart: space.xs,
|
|
118
|
+
},
|
|
119
|
+
});
|
|
120
|
+
const REASON_LABEL = {
|
|
121
|
+
"soft-threshold": "",
|
|
122
|
+
"hard-limit": "(硬上限)",
|
|
123
|
+
"state-transition": "(狀態切換)",
|
|
124
|
+
};
|
|
125
|
+
function CheckIcon() {
|
|
126
|
+
return (_jsx("svg", { width: "13", height: "13", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", "aria-hidden": "true", ...stylex.props(styles.checkIcon), children: _jsx("path", { d: "m5 13 4 4L19 7" }) }));
|
|
127
|
+
}
|
|
128
|
+
export function HandoffReceipt({ at, ctxPercent, reason = "soft-threshold", memory, ledgerCount, handoffSummary, defaultOpen = false, }) {
|
|
129
|
+
const bodyId = useId();
|
|
130
|
+
const [open, setOpen] = useState(defaultOpen);
|
|
131
|
+
// ref callback + cleanup(React 19)管動態的生命週期,不用 effect;client-only
|
|
132
|
+
const face = useCallback((node) => {
|
|
133
|
+
if (!node)
|
|
134
|
+
return;
|
|
135
|
+
const silk = new SilkBody(node.parentElement, node, {
|
|
136
|
+
palette: yarnSecondary,
|
|
137
|
+
mode: "hover",
|
|
138
|
+
bandMax: 0.5,
|
|
139
|
+
pitch: 2,
|
|
140
|
+
hiCount: 12,
|
|
141
|
+
hiSpan: 0.95,
|
|
142
|
+
fixedHeight: 480,
|
|
143
|
+
});
|
|
144
|
+
return () => silk.destroy();
|
|
145
|
+
}, []);
|
|
146
|
+
return (_jsxs("div", { ...stylex.props(styles.receipt), children: [_jsx("svg", { ref: face, "aria-hidden": "true", ...stylex.props(styles.face) }), _jsxs("button", { type: "button", "aria-expanded": open, "aria-controls": bodyId, onClick: () => setOpen((value) => !value), ...stylex.props(styles.row), children: [_jsx("span", { "aria-hidden": "true", ...stylex.props(styles.rule, open && styles.ruleOpen) }), _jsxs("span", { ...stylex.props(styles.label), children: [_jsxs("svg", { width: "15", height: "13", viewBox: "0 0 15 13", fill: "none", strokeWidth: "1.7", strokeLinecap: "round", "aria-hidden": "true", ...stylex.props(styles.knot), children: [_jsx("path", { d: KNOT_LEAD, ...stylex.props(styles.lead, open && styles.leadHidden) }), _jsx("path", { d: KNOT_LOOP, pathLength: "100", ...stylex.props(styles.loop, open && styles.loopTied) })] }), _jsxs("span", { children: ["換班完成 · ", _jsx("span", { ...stylex.props(styles.mono), children: at }), " · ", _jsx("span", { ...stylex.props(styles.mono), children: `ctx ${ctxPercent}%` }), REASON_LABEL[reason], " → 新 session"] })] }), _jsx("svg", { width: "12", height: "12", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", "aria-hidden": "true", ...stylex.props(styles.chevron, open && styles.chevronOpen), children: _jsx("path", { d: "m6 9 6 6 6-6" }) }), _jsx("span", { "aria-hidden": "true", ...stylex.props(styles.rule, open && styles.ruleOpen) })] }), _jsx("div", { ...stylex.props(styles.bodyWrap, open && styles.bodyWrapOpen), children: _jsx("div", { ...stylex.props(styles.bodyClip), children: _jsxs("div", { id: bodyId, inert: !open, ...stylex.props(styles.body, open && styles.bodyOpen), children: [_jsxs("p", { ...stylex.props(styles.check), children: [_jsx(CheckIcon, {}), _jsx("b", { ...stylex.props(styles.checkTitle), children: "\u8A18\u61B6" }), _jsxs("span", { ...stylex.props(styles.checkText), children: [`${memory.count} 筆耐久事實已落盤`, memory.items?.length ? `(${memory.items.join("、")})` : "", "\u3002"] })] }), _jsxs("p", { ...stylex.props(styles.check), children: [_jsx(CheckIcon, {}), _jsx("b", { ...stylex.props(styles.checkTitle), children: "\u6458\u8981" }), _jsx("span", { ...stylex.props(styles.checkText), children: "handoff \u5DF2\u4EA4\u7D66\u4E0B\u4E00\u8F2A,\u8B80\u5F8C\u5373\u92B7\u6BC0\u3002" })] }), _jsxs("p", { ...stylex.props(styles.check), children: [_jsx(CheckIcon, {}), _jsx("b", { ...stylex.props(styles.checkTitle), children: "Ledger" }), _jsx("span", { ...stylex.props(styles.checkText), children: `本輪 ${ledgerCount} 條收據可查,不進新 context。` })] }), handoffSummary != null && _jsx("p", { ...stylex.props(styles.summary), children: handoffSummary })] }) }) })] }));
|
|
147
|
+
}
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import * as stylex from "@stylexjs/stylex";
|
|
2
|
+
import type { ComponentProps, ReactNode } from "react";
|
|
3
|
+
export declare const controlStyles: Readonly<{
|
|
4
|
+
readonly base: Readonly<{
|
|
5
|
+
readonly width: stylex.StyleXClassNameFor<"width", "100%">;
|
|
6
|
+
readonly boxSizing: stylex.StyleXClassNameFor<"boxSizing", "border-box">;
|
|
7
|
+
readonly backgroundColor: stylex.StyleXClassNameFor<"backgroundColor", string>;
|
|
8
|
+
readonly borderWidth: stylex.StyleXClassNameFor<"borderWidth", 1>;
|
|
9
|
+
readonly borderStyle: stylex.StyleXClassNameFor<"borderStyle", "solid">;
|
|
10
|
+
readonly borderColor: stylex.StyleXClassNameFor<"borderColor", string>;
|
|
11
|
+
readonly borderRadius: stylex.StyleXClassNameFor<"borderRadius", string>;
|
|
12
|
+
readonly color: stylex.StyleXClassNameFor<"color", string>;
|
|
13
|
+
readonly fontFamily: stylex.StyleXClassNameFor<"fontFamily", string>;
|
|
14
|
+
readonly fontSize: stylex.StyleXClassNameFor<"fontSize", string>;
|
|
15
|
+
readonly lineHeight: stylex.StyleXClassNameFor<"lineHeight", string>;
|
|
16
|
+
readonly transitionProperty: stylex.StyleXClassNameFor<"transitionProperty", "border-color">;
|
|
17
|
+
readonly transitionDuration: stylex.StyleXClassNameFor<"transitionDuration", string>;
|
|
18
|
+
readonly transitionTimingFunction: stylex.StyleXClassNameFor<"transitionTimingFunction", string>;
|
|
19
|
+
readonly outline: stylex.StyleXClassNameFor<"outline", "none" | `2px solid ${stylex.StyleXVar<string>}`>;
|
|
20
|
+
readonly outlineOffset: stylex.StyleXClassNameFor<"outlineOffset", -1>;
|
|
21
|
+
readonly cursor: stylex.StyleXClassNameFor<"cursor", "auto" | "not-allowed">;
|
|
22
|
+
readonly opacity: stylex.StyleXClassNameFor<"opacity", 1 | 0.5>;
|
|
23
|
+
readonly "::placeholder": stylex.StyleXClassNameFor<"::placeholder", {
|
|
24
|
+
readonly color: stylex.StyleXVar<string>;
|
|
25
|
+
}>;
|
|
26
|
+
}>;
|
|
27
|
+
readonly invalid: Readonly<{
|
|
28
|
+
readonly borderColor: stylex.StyleXClassNameFor<"borderColor", string>;
|
|
29
|
+
readonly outlineColor: stylex.StyleXClassNameFor<"outlineColor", string>;
|
|
30
|
+
}>;
|
|
31
|
+
readonly md: Readonly<{
|
|
32
|
+
readonly minHeight: stylex.StyleXClassNameFor<"minHeight", "2.25rem">;
|
|
33
|
+
readonly paddingBlock: stylex.StyleXClassNameFor<"paddingBlock", string>;
|
|
34
|
+
readonly paddingInline: stylex.StyleXClassNameFor<"paddingInline", string>;
|
|
35
|
+
}>;
|
|
36
|
+
readonly sm: Readonly<{
|
|
37
|
+
readonly minHeight: stylex.StyleXClassNameFor<"minHeight", "1.75rem">;
|
|
38
|
+
readonly paddingBlock: stylex.StyleXClassNameFor<"paddingBlock", string>;
|
|
39
|
+
readonly paddingInline: stylex.StyleXClassNameFor<"paddingInline", string>;
|
|
40
|
+
readonly fontSize: stylex.StyleXClassNameFor<"fontSize", string>;
|
|
41
|
+
}>;
|
|
42
|
+
}>;
|
|
43
|
+
export type InputProps = Omit<ComponentProps<"input">, "size"> & {
|
|
44
|
+
size?: "sm" | "md";
|
|
45
|
+
invalid?: boolean;
|
|
46
|
+
leadingIcon?: ReactNode;
|
|
47
|
+
};
|
|
48
|
+
export declare function Input({ size, invalid, leadingIcon, ...props }: InputProps): import("react").JSX.Element;
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import * as stylex from "@stylexjs/stylex";
|
|
3
|
+
import { styled } from "../../lib/styled.js";
|
|
4
|
+
import { color, font, motion, radius, space, text } from "../../tokens.stylex.js";
|
|
5
|
+
import { useFieldControl } from "../label/fieldContext.js";
|
|
6
|
+
export const controlStyles = stylex.create({
|
|
7
|
+
base: {
|
|
8
|
+
width: "100%",
|
|
9
|
+
// 沒有 border-box,minHeight 2.25rem 是「內容」36px,再加 padding+border → 54px;
|
|
10
|
+
// 元件不能靠 app 端有沒有 `*{box-sizing:border-box}` reset
|
|
11
|
+
boxSizing: "border-box",
|
|
12
|
+
backgroundColor: color.surface,
|
|
13
|
+
borderWidth: 1,
|
|
14
|
+
borderStyle: "solid",
|
|
15
|
+
borderColor: {
|
|
16
|
+
default: color.border,
|
|
17
|
+
":hover:not(:disabled)": color.borderStrong,
|
|
18
|
+
":focus-visible": color.focusRing,
|
|
19
|
+
},
|
|
20
|
+
borderRadius: radius.md,
|
|
21
|
+
color: color.text,
|
|
22
|
+
fontFamily: font.body,
|
|
23
|
+
fontSize: text.sm,
|
|
24
|
+
// 單行控件的行高只要裝得下字:1.5 會把 md 撐到 39px,跟 button/select 的
|
|
25
|
+
// 2.25rem 對不齊(Textarea 自己蓋回 leadingRelaxed,多行照樣好讀)
|
|
26
|
+
lineHeight: text.leadingTight,
|
|
27
|
+
transitionProperty: "border-color",
|
|
28
|
+
transitionDuration: { default: motion.fast, "@media (prefers-reduced-motion: reduce)": "0s" },
|
|
29
|
+
transitionTimingFunction: motion.ease,
|
|
30
|
+
outline: { default: "none", ":focus-visible": `2px solid ${color.focusRing}` },
|
|
31
|
+
outlineOffset: -1,
|
|
32
|
+
cursor: { default: "auto", ":disabled": "not-allowed" },
|
|
33
|
+
opacity: { default: 1, ":disabled": 0.5 },
|
|
34
|
+
"::placeholder": { color: color.textFaint },
|
|
35
|
+
},
|
|
36
|
+
invalid: {
|
|
37
|
+
borderColor: { default: color.danger, ":hover:not(:disabled)": color.danger },
|
|
38
|
+
outlineColor: color.danger,
|
|
39
|
+
},
|
|
40
|
+
md: { minHeight: "2.25rem", paddingBlock: space.xs, paddingInline: space.sm },
|
|
41
|
+
sm: { minHeight: "1.75rem", paddingBlock: space.xxs, paddingInline: space.xs, fontSize: text.xs },
|
|
42
|
+
});
|
|
43
|
+
const styles = stylex.create({
|
|
44
|
+
affix: { position: "relative", display: "block", width: "100%" },
|
|
45
|
+
icon: {
|
|
46
|
+
position: "absolute",
|
|
47
|
+
insetInlineStart: space.xs,
|
|
48
|
+
insetBlockStart: "50%",
|
|
49
|
+
transform: "translateY(-50%)",
|
|
50
|
+
display: "flex",
|
|
51
|
+
color: color.textFaint,
|
|
52
|
+
pointerEvents: "none",
|
|
53
|
+
},
|
|
54
|
+
withIconMd: { paddingInlineStart: space.xl },
|
|
55
|
+
withIconSm: { paddingInlineStart: space.lg },
|
|
56
|
+
});
|
|
57
|
+
export function Input({ size = "md", invalid, leadingIcon, ...props }) {
|
|
58
|
+
const { invalid: fieldInvalid, ...field } = useFieldControl(props);
|
|
59
|
+
const isInvalid = invalid ?? fieldInvalid;
|
|
60
|
+
const input = (_jsx("input", { ...props, ...field, "aria-invalid": isInvalid || undefined, ...styled(props, controlStyles.base, controlStyles[size], isInvalid && controlStyles.invalid, Boolean(leadingIcon) && (size === "sm" ? styles.withIconSm : styles.withIconMd)) }));
|
|
61
|
+
if (!leadingIcon)
|
|
62
|
+
return input;
|
|
63
|
+
return (_jsxs("span", { ...stylex.props(styles.affix), children: [_jsx("span", { "aria-hidden": "true", ...stylex.props(styles.icon), children: leadingIcon }), input] }));
|
|
64
|
+
}
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
import { type ReactNode } from "react";
|
|
2
|
+
export type PermissionReply = "once" | {
|
|
3
|
+
always: string;
|
|
4
|
+
} | {
|
|
5
|
+
reject: true;
|
|
6
|
+
message?: string;
|
|
7
|
+
};
|
|
8
|
+
export type PermissionReceipt = {
|
|
9
|
+
text: string;
|
|
10
|
+
rejected?: boolean;
|
|
11
|
+
};
|
|
12
|
+
export type PermissionCardProps = {
|
|
13
|
+
verb: string;
|
|
14
|
+
subject: string;
|
|
15
|
+
scope?: string;
|
|
16
|
+
policyHint?: ReactNode;
|
|
17
|
+
blockingLabel?: string;
|
|
18
|
+
onReply?: (reply: PermissionReply) => void;
|
|
19
|
+
resolved?: PermissionReceipt;
|
|
20
|
+
};
|
|
21
|
+
export declare function PermissionCard({ verb, subject, scope, policyHint, blockingLabel, onReply, resolved, }: PermissionCardProps): import("react").JSX.Element;
|
|
22
|
+
export type DecisionOption = {
|
|
23
|
+
value: string;
|
|
24
|
+
label: string;
|
|
25
|
+
description?: string;
|
|
26
|
+
recommended?: boolean;
|
|
27
|
+
};
|
|
28
|
+
export type DecisionBlock = {
|
|
29
|
+
kind: "markdown";
|
|
30
|
+
id: string;
|
|
31
|
+
text: string;
|
|
32
|
+
} | {
|
|
33
|
+
kind: "options";
|
|
34
|
+
id: string;
|
|
35
|
+
label?: string;
|
|
36
|
+
multiple?: boolean;
|
|
37
|
+
required?: boolean;
|
|
38
|
+
options: DecisionOption[];
|
|
39
|
+
} | {
|
|
40
|
+
kind: "text";
|
|
41
|
+
id: string;
|
|
42
|
+
label?: string;
|
|
43
|
+
placeholder?: string;
|
|
44
|
+
required?: boolean;
|
|
45
|
+
};
|
|
46
|
+
export type DecisionAnswer = Record<string, string | string[]>;
|
|
47
|
+
export type DecisionCardProps = {
|
|
48
|
+
title: string;
|
|
49
|
+
blocks: DecisionBlock[];
|
|
50
|
+
blocking?: boolean;
|
|
51
|
+
deadlineLabel?: string;
|
|
52
|
+
submitLabel?: string;
|
|
53
|
+
recommendedLabel?: string;
|
|
54
|
+
onAnswer?: (answer: DecisionAnswer) => void;
|
|
55
|
+
resolved?: {
|
|
56
|
+
text: string;
|
|
57
|
+
};
|
|
58
|
+
};
|
|
59
|
+
export declare function DecisionCard({ title, blocks, blocking, deadlineLabel, submitLabel, recommendedLabel, onAnswer, resolved, }: DecisionCardProps): import("react").JSX.Element;
|
|
@@ -0,0 +1,238 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
|
|
2
|
+
import * as stylex from "@stylexjs/stylex";
|
|
3
|
+
import { useId, useState } from "react";
|
|
4
|
+
import { color, font, motion, radius, space, text } from "../../tokens.stylex.js";
|
|
5
|
+
import { Button } from "../button/Button.js";
|
|
6
|
+
import { Checkbox } from "../checkbox/Checkbox.js";
|
|
7
|
+
import { Radio } from "../radio/Radio.js";
|
|
8
|
+
import { RadioGroup } from "../radio/RadioGroup.js";
|
|
9
|
+
const REDUCED = "@media (prefers-reduced-motion: reduce)";
|
|
10
|
+
const styles = stylex.create({
|
|
11
|
+
card: {
|
|
12
|
+
backgroundColor: color.surface,
|
|
13
|
+
borderWidth: 1,
|
|
14
|
+
borderStyle: "solid",
|
|
15
|
+
borderColor: color.border,
|
|
16
|
+
borderRadius: radius.lg,
|
|
17
|
+
paddingBlock: space.sm,
|
|
18
|
+
paddingInline: space.md,
|
|
19
|
+
display: "grid",
|
|
20
|
+
gap: space.xs,
|
|
21
|
+
fontFamily: font.body,
|
|
22
|
+
transitionProperty: "border-color",
|
|
23
|
+
transitionDuration: { default: "180ms", [REDUCED]: "0s" },
|
|
24
|
+
},
|
|
25
|
+
permissionPending: { borderColor: color.warning },
|
|
26
|
+
decisionPending: { borderColor: color.accent },
|
|
27
|
+
head: { display: "flex", alignItems: "center", gap: space.xs },
|
|
28
|
+
headIconWarning: { flex: "none", color: color.warning },
|
|
29
|
+
headIconAccent: { flex: "none", color: color.accent },
|
|
30
|
+
verb: { fontSize: text.sm, fontWeight: 500, lineHeight: text.leadingSnug, color: color.text },
|
|
31
|
+
state: {
|
|
32
|
+
marginInlineStart: "auto",
|
|
33
|
+
fontFamily: font.mono,
|
|
34
|
+
fontSize: "0.68rem",
|
|
35
|
+
fontWeight: 600,
|
|
36
|
+
lineHeight: 1,
|
|
37
|
+
letterSpacing: "0.06em",
|
|
38
|
+
textTransform: "uppercase",
|
|
39
|
+
whiteSpace: "nowrap",
|
|
40
|
+
},
|
|
41
|
+
stateWarning: { color: color.warning },
|
|
42
|
+
stateAccent: { color: color.accent },
|
|
43
|
+
stateQuiet: { color: color.textFaint },
|
|
44
|
+
body: { display: "grid", gap: space.xs },
|
|
45
|
+
mono: {
|
|
46
|
+
fontFamily: font.mono,
|
|
47
|
+
fontSize: "0.82rem",
|
|
48
|
+
lineHeight: text.leadingNormal,
|
|
49
|
+
backgroundColor: color.bg,
|
|
50
|
+
borderWidth: 1,
|
|
51
|
+
borderStyle: "solid",
|
|
52
|
+
borderColor: color.border,
|
|
53
|
+
borderRadius: radius.sm,
|
|
54
|
+
paddingBlock: space.xs,
|
|
55
|
+
paddingInline: space.xs,
|
|
56
|
+
overflowX: "auto",
|
|
57
|
+
whiteSpace: "pre",
|
|
58
|
+
margin: 0,
|
|
59
|
+
color: color.text,
|
|
60
|
+
outline: { default: "none", ":focus-visible": `2px solid ${color.focusRing}` },
|
|
61
|
+
outlineOffset: -2,
|
|
62
|
+
},
|
|
63
|
+
actions: { display: "flex", gap: space.xs, flexWrap: "wrap" },
|
|
64
|
+
shortcut: {
|
|
65
|
+
fontFamily: font.mono,
|
|
66
|
+
fontSize: "0.68rem",
|
|
67
|
+
fontWeight: 500,
|
|
68
|
+
opacity: 0.65,
|
|
69
|
+
marginInlineStart: space.xxs,
|
|
70
|
+
},
|
|
71
|
+
policy: {
|
|
72
|
+
display: "flex",
|
|
73
|
+
alignItems: "center",
|
|
74
|
+
gap: space.xxs,
|
|
75
|
+
fontSize: "0.78rem",
|
|
76
|
+
color: color.textMuted,
|
|
77
|
+
borderTopWidth: 1,
|
|
78
|
+
borderTopStyle: "solid",
|
|
79
|
+
borderTopColor: color.border,
|
|
80
|
+
paddingTop: space.xs,
|
|
81
|
+
margin: 0,
|
|
82
|
+
},
|
|
83
|
+
policyIcon: { flex: "none", color: color.textFaint },
|
|
84
|
+
question: {
|
|
85
|
+
fontSize: "0.95rem",
|
|
86
|
+
fontWeight: 500,
|
|
87
|
+
lineHeight: text.leadingSnug,
|
|
88
|
+
margin: 0,
|
|
89
|
+
color: color.text,
|
|
90
|
+
},
|
|
91
|
+
markdown: { fontSize: text.sm, lineHeight: text.leadingRelaxed, margin: 0, color: color.textMuted },
|
|
92
|
+
options: {
|
|
93
|
+
display: "grid",
|
|
94
|
+
gap: space.xxs,
|
|
95
|
+
margin: 0,
|
|
96
|
+
padding: 0,
|
|
97
|
+
borderWidth: 0,
|
|
98
|
+
listStyle: "none",
|
|
99
|
+
},
|
|
100
|
+
srOnly: {
|
|
101
|
+
position: "absolute",
|
|
102
|
+
width: 1,
|
|
103
|
+
height: 1,
|
|
104
|
+
padding: 0,
|
|
105
|
+
margin: -1,
|
|
106
|
+
overflow: "hidden",
|
|
107
|
+
clipPath: "inset(50%)",
|
|
108
|
+
whiteSpace: "nowrap",
|
|
109
|
+
borderWidth: 0,
|
|
110
|
+
},
|
|
111
|
+
recommended: {
|
|
112
|
+
fontFamily: font.mono,
|
|
113
|
+
fontSize: "0.62rem",
|
|
114
|
+
fontWeight: 600,
|
|
115
|
+
lineHeight: 1,
|
|
116
|
+
letterSpacing: "0.05em",
|
|
117
|
+
textTransform: "uppercase",
|
|
118
|
+
color: color.accent,
|
|
119
|
+
backgroundColor: color.accentSubtle,
|
|
120
|
+
borderRadius: radius.sm,
|
|
121
|
+
paddingBlock: "0.15rem",
|
|
122
|
+
paddingInline: space.xxs,
|
|
123
|
+
marginInlineStart: space.xxs,
|
|
124
|
+
verticalAlign: "middle",
|
|
125
|
+
},
|
|
126
|
+
free: {
|
|
127
|
+
width: "100%",
|
|
128
|
+
boxSizing: "border-box",
|
|
129
|
+
backgroundColor: color.bg,
|
|
130
|
+
borderWidth: 1,
|
|
131
|
+
borderStyle: "solid",
|
|
132
|
+
borderColor: { default: color.border, ":focus-visible": color.focusRing },
|
|
133
|
+
borderRadius: radius.sm,
|
|
134
|
+
color: color.text,
|
|
135
|
+
fontFamily: font.body,
|
|
136
|
+
fontSize: text.sm,
|
|
137
|
+
paddingBlock: space.xs,
|
|
138
|
+
paddingInline: space.xs,
|
|
139
|
+
resize: "vertical",
|
|
140
|
+
minHeight: "2.4rem",
|
|
141
|
+
transitionProperty: "border-color",
|
|
142
|
+
transitionDuration: { default: motion.fast, [REDUCED]: "0s" },
|
|
143
|
+
outline: { default: "none", ":focus-visible": `2px solid ${color.focusRing}` },
|
|
144
|
+
outlineOffset: -1,
|
|
145
|
+
"::placeholder": { color: color.textFaint },
|
|
146
|
+
},
|
|
147
|
+
receipt: {
|
|
148
|
+
display: "flex",
|
|
149
|
+
alignItems: "center",
|
|
150
|
+
gap: space.xs,
|
|
151
|
+
fontSize: text.sm,
|
|
152
|
+
color: color.textMuted,
|
|
153
|
+
margin: 0,
|
|
154
|
+
},
|
|
155
|
+
receiptIcon: { flex: "none", color: color.accent },
|
|
156
|
+
receiptIconRejected: { color: color.danger },
|
|
157
|
+
receiptMono: { fontFamily: font.mono },
|
|
158
|
+
});
|
|
159
|
+
function LockIcon() {
|
|
160
|
+
return (_jsxs("svg", { width: "15", height: "15", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", "aria-hidden": "true", ...stylex.props(styles.headIconWarning), children: [_jsx("rect", { x: "4", y: "10", width: "16", height: "10", rx: "2" }), _jsx("path", { d: "M8 10V7a4 4 0 0 1 8 0v3" })] }));
|
|
161
|
+
}
|
|
162
|
+
function DecideIcon() {
|
|
163
|
+
return (_jsxs("svg", { width: "15", height: "15", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", "aria-hidden": "true", ...stylex.props(styles.headIconAccent), children: [_jsx("path", { d: "M9 18h6M10 21h4" }), _jsx("path", { d: "M12 3a6 6 0 0 0-4 10.5c.7.6 1 1.5 1 2.5h6c0-1 .3-1.9 1-2.5A6 6 0 0 0 12 3Z" })] }));
|
|
164
|
+
}
|
|
165
|
+
function InfoIcon() {
|
|
166
|
+
return (_jsxs("svg", { width: "13", height: "13", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", "aria-hidden": "true", ...stylex.props(styles.policyIcon), children: [_jsx("circle", { cx: "12", cy: "12", r: "9" }), _jsx("path", { d: "M12 8v4m0 4h.01" })] }));
|
|
167
|
+
}
|
|
168
|
+
function ReceiptIcon({ rejected }) {
|
|
169
|
+
return (_jsx("svg", { width: "14", height: "14", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", "aria-hidden": "true", ...stylex.props(styles.receiptIcon, rejected && styles.receiptIconRejected), children: _jsx("path", { d: "m5 13 4 4L19 7" }) }));
|
|
170
|
+
}
|
|
171
|
+
export function PermissionCard({ verb, subject, scope = "這個指令", policyHint, blockingLabel = "等你才能繼續", onReply, resolved, }) {
|
|
172
|
+
// Plain Enter is left to the focused button's own activation; only the
|
|
173
|
+
// card-level shortcuts are intercepted.
|
|
174
|
+
function onKeyDown(event) {
|
|
175
|
+
if (resolved)
|
|
176
|
+
return;
|
|
177
|
+
if (event.key === "Enter" && (event.metaKey || event.ctrlKey)) {
|
|
178
|
+
event.preventDefault();
|
|
179
|
+
onReply?.({ always: scope });
|
|
180
|
+
}
|
|
181
|
+
else if (event.key === "Escape") {
|
|
182
|
+
event.preventDefault();
|
|
183
|
+
onReply?.({ reject: true });
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
return (_jsxs("div", { role: "group", "aria-label": `${verb}:${subject}`, ...stylex.props(styles.card, !resolved && styles.permissionPending), children: [_jsxs("div", { ...stylex.props(styles.head), children: [_jsx(LockIcon, {}), _jsx("span", { ...stylex.props(styles.verb), children: verb }), _jsx("span", { ...stylex.props(styles.state, resolved ? styles.stateQuiet : styles.stateWarning), children: resolved ? "已回覆" : blockingLabel })] }), _jsx("p", { "aria-live": "polite", ...stylex.props(resolved ? styles.receipt : styles.srOnly), children: resolved != null && (_jsxs(_Fragment, { children: [_jsx(ReceiptIcon, { rejected: resolved.rejected }), resolved.text, " · ", _jsx("code", { ...stylex.props(styles.receiptMono), children: subject })] })) }), resolved ? null : (_jsxs("div", { ...stylex.props(styles.body), children: [_jsx("pre", { tabIndex: 0, role: "region", "aria-label": verb, ...stylex.props(styles.mono), children: subject }), _jsxs("div", { ...stylex.props(styles.actions), children: [_jsxs(Button, { onKeyDown: onKeyDown, onClick: () => onReply?.("once"), children: ["\u5141\u8A31\u4E00\u6B21", _jsx("kbd", { "aria-hidden": "true", ...stylex.props(styles.shortcut), children: "\u23CE" })] }), _jsxs(Button, { variant: "secondary", "aria-keyshortcuts": "Meta+Enter Control+Enter", onKeyDown: onKeyDown, onClick: () => onReply?.({ always: scope }), children: ["\u7E3D\u662F\u5141\u8A31", _jsx("kbd", { "aria-hidden": "true", ...stylex.props(styles.shortcut), children: "\u2318\u23CE" })] }), _jsxs(Button, { variant: "dangerGhost", "aria-keyshortcuts": "Escape", onKeyDown: onKeyDown, onClick: () => onReply?.({ reject: true }), children: ["\u62D2\u7D55", _jsx("kbd", { "aria-hidden": "true", ...stylex.props(styles.shortcut), children: "Esc" })] })] }), policyHint != null && (_jsxs("p", { ...stylex.props(styles.policy), children: [_jsx(InfoIcon, {}), policyHint] }))] }))] }));
|
|
187
|
+
}
|
|
188
|
+
function optionLabel(option) {
|
|
189
|
+
if (!option.recommended)
|
|
190
|
+
return option.label;
|
|
191
|
+
return (_jsxs(_Fragment, { children: [option.label, _jsx("span", { ...stylex.props(styles.recommended), children: "\u5EFA\u8B70" })] }));
|
|
192
|
+
}
|
|
193
|
+
export function DecisionCard({ title, blocks, blocking = false, deadlineLabel, submitLabel = "送出決定", recommendedLabel = "照建議", onAnswer, resolved, }) {
|
|
194
|
+
const base = useId();
|
|
195
|
+
const [answer, setAnswer] = useState({});
|
|
196
|
+
const missing = blocks.some((block) => {
|
|
197
|
+
if (block.kind === "markdown" || !block.required)
|
|
198
|
+
return false;
|
|
199
|
+
const value = answer[block.id];
|
|
200
|
+
return value == null || value.length === 0;
|
|
201
|
+
});
|
|
202
|
+
const recommended = {};
|
|
203
|
+
for (const block of blocks) {
|
|
204
|
+
if (block.kind !== "options")
|
|
205
|
+
continue;
|
|
206
|
+
const picks = block.options.filter((option) => option.recommended).map((option) => option.value);
|
|
207
|
+
if (picks.length === 0)
|
|
208
|
+
continue;
|
|
209
|
+
recommended[block.id] = block.multiple ? picks : picks[0];
|
|
210
|
+
}
|
|
211
|
+
const hasRecommendation = Object.keys(recommended).length > 0;
|
|
212
|
+
const merged = { ...answer, ...recommended };
|
|
213
|
+
const recommendationMissing = blocks.some((block) => {
|
|
214
|
+
if (block.kind === "markdown" || !block.required)
|
|
215
|
+
return false;
|
|
216
|
+
const value = merged[block.id];
|
|
217
|
+
return value == null || value.length === 0;
|
|
218
|
+
});
|
|
219
|
+
function set(id, value) {
|
|
220
|
+
setAnswer((current) => ({ ...current, [id]: value }));
|
|
221
|
+
}
|
|
222
|
+
return (_jsxs("div", { ...stylex.props(styles.card, !resolved && blocking && styles.decisionPending), children: [_jsxs("div", { ...stylex.props(styles.head), children: [_jsx(DecideIcon, {}), _jsx("span", { ...stylex.props(styles.verb), children: "\u6C7A\u5B9A" }), _jsx("span", { ...stylex.props(styles.state, resolved ? styles.stateQuiet : blocking ? styles.stateAccent : styles.stateQuiet), children: resolved ? "已決定" : blocking ? "等你才能繼續" : (deadlineLabel ?? "等你") })] }), _jsx("p", { "aria-live": "polite", ...stylex.props(resolved ? styles.receipt : styles.srOnly), children: resolved != null && (_jsxs(_Fragment, { children: [_jsx(ReceiptIcon, {}), resolved.text] })) }), resolved ? null : (_jsxs("div", { ...stylex.props(styles.body), children: [_jsx("p", { ...stylex.props(styles.question), children: title }), blocks.map((block) => {
|
|
223
|
+
if (block.kind === "markdown") {
|
|
224
|
+
return (_jsx("p", { ...stylex.props(styles.markdown), children: block.text }, block.id));
|
|
225
|
+
}
|
|
226
|
+
if (block.kind === "text") {
|
|
227
|
+
return (_jsx("textarea", { rows: 1, "aria-label": block.label ?? title, placeholder: block.placeholder, required: block.required, value: answer[block.id] ?? "", onChange: (event) => set(block.id, event.currentTarget.value), ...stylex.props(styles.free) }, block.id));
|
|
228
|
+
}
|
|
229
|
+
const selected = answer[block.id];
|
|
230
|
+
// label 沒給時,組名走 aria-label(視覺上不重複卡片標題)
|
|
231
|
+
const groupLabel = block.label == null ? title : undefined;
|
|
232
|
+
if (block.multiple) {
|
|
233
|
+
const picks = Array.isArray(selected) ? selected : [];
|
|
234
|
+
return (_jsxs("fieldset", { "aria-label": groupLabel, "aria-required": block.required || undefined, ...stylex.props(styles.options), children: [block.label != null && _jsx("legend", { ...stylex.props(styles.question), children: block.label }), block.options.map((option) => (_jsx(Checkbox, { name: `${base}${block.id}`, value: option.value, checked: picks.includes(option.value), onCheckedChange: (on) => set(block.id, on ? [...picks, option.value] : picks.filter((v) => v !== option.value)), label: optionLabel(option), description: option.description }, option.value)))] }, block.id));
|
|
235
|
+
}
|
|
236
|
+
return (_jsx(RadioGroup, { name: `${base}${block.id}`, legend: block.label, "aria-label": groupLabel, "aria-required": block.required || undefined, variant: "card", value: typeof selected === "string" ? selected : "", onValueChange: (value) => set(block.id, value), children: block.options.map((option) => (_jsx(Radio, { value: option.value, label: optionLabel(option), description: option.description }, option.value))) }, block.id));
|
|
237
|
+
}), _jsxs("div", { ...stylex.props(styles.actions), children: [_jsx(Button, { disabled: missing, onClick: () => onAnswer?.(answer), children: submitLabel }), hasRecommendation && (_jsx(Button, { variant: "secondary", disabled: recommendationMissing, onClick: () => onAnswer?.(merged), children: recommendedLabel }))] })] }))] }));
|
|
238
|
+
}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import { type ComponentProps, type ReactNode } from "react";
|
|
2
|
+
export declare const KbdToneContext: import("react").Context<"default" | "inverted">;
|
|
3
|
+
export type KbdProps = ComponentProps<"kbd">;
|
|
4
|
+
export declare function Kbd(props: KbdProps): import("react").JSX.Element;
|
|
5
|
+
export type KbdGroupProps = Omit<ComponentProps<"span">, "children"> & {
|
|
6
|
+
keys: string[];
|
|
7
|
+
separator?: ReactNode;
|
|
8
|
+
};
|
|
9
|
+
export declare function KbdGroup({ keys, separator, ...props }: KbdGroupProps): import("react").JSX.Element;
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import * as stylex from "@stylexjs/stylex";
|
|
3
|
+
import { Fragment, createContext, useContext } from "react";
|
|
4
|
+
import { styled } from "../../lib/styled.js";
|
|
5
|
+
import { color, font, radius, space } from "../../tokens.stylex.js";
|
|
6
|
+
export const KbdToneContext = createContext("default");
|
|
7
|
+
const styles = stylex.create({
|
|
8
|
+
key: {
|
|
9
|
+
display: "inline-grid",
|
|
10
|
+
placeItems: "center",
|
|
11
|
+
minWidth: "1.4rem",
|
|
12
|
+
height: "1.4rem",
|
|
13
|
+
paddingInline: space.xxs,
|
|
14
|
+
backgroundColor: color.surface,
|
|
15
|
+
borderWidth: 1,
|
|
16
|
+
borderStyle: "solid",
|
|
17
|
+
borderColor: color.border,
|
|
18
|
+
borderBottomColor: color.borderStrong,
|
|
19
|
+
borderRadius: radius.sm,
|
|
20
|
+
boxShadow: `0 1px 0 ${color.borderStrong}`,
|
|
21
|
+
fontFamily: font.mono,
|
|
22
|
+
fontSize: "0.72rem",
|
|
23
|
+
fontWeight: 500,
|
|
24
|
+
lineHeight: 1,
|
|
25
|
+
color: color.textMuted,
|
|
26
|
+
},
|
|
27
|
+
inverted: {
|
|
28
|
+
backgroundColor: "color-mix(in srgb, currentColor 16%, transparent)",
|
|
29
|
+
borderColor: "color-mix(in srgb, currentColor 28%, transparent)",
|
|
30
|
+
borderBottomColor: "color-mix(in srgb, currentColor 28%, transparent)",
|
|
31
|
+
boxShadow: "none",
|
|
32
|
+
color: "inherit",
|
|
33
|
+
},
|
|
34
|
+
combo: { display: "inline-flex", gap: "0.2rem", alignItems: "center" },
|
|
35
|
+
sequence: { display: "inline-flex", gap: space.xxs, alignItems: "center", color: color.textFaint },
|
|
36
|
+
});
|
|
37
|
+
export function Kbd(props) {
|
|
38
|
+
const tone = useContext(KbdToneContext);
|
|
39
|
+
return _jsx("kbd", { ...props, ...styled(props, styles.key, tone === "inverted" && styles.inverted) });
|
|
40
|
+
}
|
|
41
|
+
export function KbdGroup({ keys, separator, ...props }) {
|
|
42
|
+
return (_jsx("span", { ...props, ...styled(props, separator == null ? styles.combo : styles.sequence), children: keys.map((key, index) => (_jsxs(Fragment, { children: [index > 0 && separator, _jsx(Kbd, { children: key })] }, `${key}-${index}`))) }));
|
|
43
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { type ComponentProps, type ReactNode } from "react";
|
|
2
|
+
export type FieldProps = Omit<ComponentProps<"div">, "children"> & {
|
|
3
|
+
label?: ReactNode;
|
|
4
|
+
help?: ReactNode;
|
|
5
|
+
error?: ReactNode;
|
|
6
|
+
required?: boolean;
|
|
7
|
+
optional?: boolean;
|
|
8
|
+
disabled?: boolean;
|
|
9
|
+
children: ReactNode;
|
|
10
|
+
};
|
|
11
|
+
export declare function Field({ label, help, error, required, optional, disabled, children, ...props }: FieldProps): import("react").JSX.Element;
|