@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.
Files changed (121) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +121 -0
  3. package/dist/components/action-bar/ActionBar.d.ts +28 -0
  4. package/dist/components/action-bar/ActionBar.js +73 -0
  5. package/dist/components/badge/Badge.d.ts +28 -0
  6. package/dist/components/badge/Badge.js +79 -0
  7. package/dist/components/button/Button.d.ts +7 -0
  8. package/dist/components/button/Button.js +86 -0
  9. package/dist/components/card/Card.d.ts +2 -0
  10. package/dist/components/card/Card.js +17 -0
  11. package/dist/components/checkbox/Checkbox.d.ts +8 -0
  12. package/dist/components/checkbox/Checkbox.js +102 -0
  13. package/dist/components/code-block/CodeBlock.d.ts +10 -0
  14. package/dist/components/code-block/CodeBlock.js +103 -0
  15. package/dist/components/composer/Composer.d.ts +27 -0
  16. package/dist/components/composer/Composer.js +278 -0
  17. package/dist/components/data-table/DataTable.d.ts +38 -0
  18. package/dist/components/data-table/DataTable.js +256 -0
  19. package/dist/components/dialog/Dialog.d.ts +36 -0
  20. package/dist/components/dialog/Dialog.js +77 -0
  21. package/dist/components/diff-viewer/DiffViewer.d.ts +13 -0
  22. package/dist/components/diff-viewer/DiffViewer.js +133 -0
  23. package/dist/components/dropdown/DropdownMenu.d.ts +43 -0
  24. package/dist/components/dropdown/DropdownMenu.js +86 -0
  25. package/dist/components/dropzone/Dropzone.d.ts +32 -0
  26. package/dist/components/dropzone/Dropzone.js +242 -0
  27. package/dist/components/empty-state/EmptyState.d.ts +9 -0
  28. package/dist/components/empty-state/EmptyState.js +41 -0
  29. package/dist/components/file-row/FileRow.d.ts +32 -0
  30. package/dist/components/file-row/FileRow.js +153 -0
  31. package/dist/components/handoff-receipt/HandoffReceipt.d.ts +15 -0
  32. package/dist/components/handoff-receipt/HandoffReceipt.js +147 -0
  33. package/dist/components/input/Input.d.ts +48 -0
  34. package/dist/components/input/Input.js +64 -0
  35. package/dist/components/interaction-card/InteractionCard.d.ts +59 -0
  36. package/dist/components/interaction-card/InteractionCard.js +238 -0
  37. package/dist/components/kbd/Kbd.d.ts +9 -0
  38. package/dist/components/kbd/Kbd.js +43 -0
  39. package/dist/components/label/Field.d.ts +11 -0
  40. package/dist/components/label/Field.js +28 -0
  41. package/dist/components/label/Label.d.ts +7 -0
  42. package/dist/components/label/Label.js +21 -0
  43. package/dist/components/label/fieldContext.d.ts +26 -0
  44. package/dist/components/label/fieldContext.js +16 -0
  45. package/dist/components/message/Message.d.ts +29 -0
  46. package/dist/components/message/Message.js +94 -0
  47. package/dist/components/password-input/PasswordInput.d.ts +20 -0
  48. package/dist/components/password-input/PasswordInput.js +141 -0
  49. package/dist/components/popover/Popover.d.ts +38 -0
  50. package/dist/components/popover/Popover.js +37 -0
  51. package/dist/components/progress/Progress.d.ts +36 -0
  52. package/dist/components/progress/Progress.js +200 -0
  53. package/dist/components/radio/Radio.d.ts +7 -0
  54. package/dist/components/radio/Radio.js +89 -0
  55. package/dist/components/radio/RadioGroup.d.ts +21 -0
  56. package/dist/components/radio/RadioGroup.js +34 -0
  57. package/dist/components/reasoning-fold/ReasoningFold.d.ts +10 -0
  58. package/dist/components/reasoning-fold/ReasoningFold.js +92 -0
  59. package/dist/components/recovery-key/RecoveryKey.d.ts +17 -0
  60. package/dist/components/recovery-key/RecoveryKey.js +132 -0
  61. package/dist/components/select/Select.d.ts +31 -0
  62. package/dist/components/select/Select.js +209 -0
  63. package/dist/components/skeleton/Skeleton.d.ts +20 -0
  64. package/dist/components/skeleton/Skeleton.js +57 -0
  65. package/dist/components/switch/Switch.d.ts +7 -0
  66. package/dist/components/switch/Switch.js +108 -0
  67. package/dist/components/tabs/Tabs.d.ts +28 -0
  68. package/dist/components/tabs/Tabs.js +133 -0
  69. package/dist/components/text/Text.d.ts +7 -0
  70. package/dist/components/text/Text.js +31 -0
  71. package/dist/components/textarea/Textarea.d.ts +7 -0
  72. package/dist/components/textarea/Textarea.js +25 -0
  73. package/dist/components/toast/Toast.d.ts +56 -0
  74. package/dist/components/toast/Toast.js +175 -0
  75. package/dist/components/tool-card/ToolCard.d.ts +55 -0
  76. package/dist/components/tool-card/ToolCard.js +281 -0
  77. package/dist/components/tooltip/Tooltip.d.ts +12 -0
  78. package/dist/components/tooltip/Tooltip.js +38 -0
  79. package/dist/components/voice-indicator/VoiceIndicator.d.ts +7 -0
  80. package/dist/components/voice-indicator/VoiceIndicator.js +51 -0
  81. package/dist/index.d.ts +55 -0
  82. package/dist/index.js +38 -0
  83. package/dist/lib/diff.d.ts +28 -0
  84. package/dist/lib/diff.js +133 -0
  85. package/dist/lib/format.d.ts +2 -0
  86. package/dist/lib/format.js +24 -0
  87. package/dist/lib/format.test.d.ts +1 -0
  88. package/dist/lib/format.test.js +37 -0
  89. package/dist/lib/mergeRefs.d.ts +2 -0
  90. package/dist/lib/mergeRefs.js +8 -0
  91. package/dist/lib/motion.d.ts +1 -0
  92. package/dist/lib/motion.js +10 -0
  93. package/dist/lib/paths.d.ts +3 -0
  94. package/dist/lib/paths.js +11 -0
  95. package/dist/lib/popup.d.ts +29 -0
  96. package/dist/lib/popup.js +27 -0
  97. package/dist/lib/progress.d.ts +2 -0
  98. package/dist/lib/progress.js +28 -0
  99. package/dist/lib/silk.d.ts +66 -0
  100. package/dist/lib/silk.js +337 -0
  101. package/dist/lib/styled.d.ts +29 -0
  102. package/dist/lib/styled.js +30 -0
  103. package/dist/lib/svgId.d.ts +1 -0
  104. package/dist/lib/svgId.js +4 -0
  105. package/dist/lib/useAnimationFrame.d.ts +1 -0
  106. package/dist/lib/useAnimationFrame.js +21 -0
  107. package/dist/lib/useControllableState.d.ts +1 -0
  108. package/dist/lib/useControllableState.js +14 -0
  109. package/dist/lib/useCopy.d.ts +4 -0
  110. package/dist/lib/useCopy.js +19 -0
  111. package/dist/lib/voice.d.ts +2 -0
  112. package/dist/lib/voice.js +30 -0
  113. package/dist/lib/weave.d.ts +28 -0
  114. package/dist/lib/weave.js +83 -0
  115. package/dist/scrollbar.css +29 -0
  116. package/dist/themes.stylex.d.ts +49 -0
  117. package/dist/themes.stylex.js +52 -0
  118. package/dist/tokens.css +109 -0
  119. package/dist/tokens.stylex.d.ts +126 -0
  120. package/dist/tokens.stylex.js +149 -0
  121. package/package.json +81 -0
@@ -0,0 +1,28 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import * as stylex from "@stylexjs/stylex";
3
+ import { useId, useMemo } from "react";
4
+ import { styled } from "../../lib/styled.js";
5
+ import { color, font, space, text } from "../../tokens.stylex.js";
6
+ import { FieldContext } from "./fieldContext.js";
7
+ import { Label } from "./Label.js";
8
+ const styles = stylex.create({
9
+ group: { display: "grid", gap: space.xxs },
10
+ dimmed: { opacity: { default: 1, ":has(:disabled)": 0.5 } },
11
+ help: { margin: 0, fontFamily: font.body, fontSize: text.xs, color: color.textMuted },
12
+ error: { margin: 0, fontFamily: font.body, fontSize: text.xs, color: color.danger },
13
+ });
14
+ export function Field({ label, help, error, required = false, optional, disabled = false, children, ...props }) {
15
+ const base = useId();
16
+ const controlId = `${base}control`;
17
+ const helpId = `${base}help`;
18
+ const errorId = `${base}error`;
19
+ const describedBy = [error ? errorId : null, help ? helpId : null].filter(Boolean).join(" ");
20
+ const value = useMemo(() => ({
21
+ controlId,
22
+ describedBy: describedBy || undefined,
23
+ invalid: Boolean(error),
24
+ required,
25
+ disabled,
26
+ }), [controlId, describedBy, error, required, disabled]);
27
+ return (_jsx("div", { ...props, ...styled(props, styles.group, styles.dimmed), children: _jsxs(FieldContext, { value: value, children: [label != null && (_jsx(Label, { htmlFor: controlId, required: required, optional: optional, children: label })), children, error != null && (_jsx("p", { id: errorId, role: "alert", ...stylex.props(styles.error), children: error })), help != null && (_jsx("p", { id: helpId, ...stylex.props(styles.help), children: help }))] }) }));
28
+ }
@@ -0,0 +1,7 @@
1
+ import type { ComponentProps } from "react";
2
+ export type LabelProps = ComponentProps<"label"> & {
3
+ required?: boolean;
4
+ optional?: boolean;
5
+ optionalLabel?: string;
6
+ };
7
+ export declare function Label({ required, optional, optionalLabel, children, ...props }: LabelProps): import("react").JSX.Element;
@@ -0,0 +1,21 @@
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, text } from "../../tokens.stylex.js";
5
+ const styles = stylex.create({
6
+ base: {
7
+ display: "flex",
8
+ alignItems: "baseline",
9
+ gap: "0.35rem",
10
+ fontFamily: font.body,
11
+ fontSize: text.sm,
12
+ fontWeight: 500,
13
+ lineHeight: text.leadingSnug,
14
+ color: color.text,
15
+ },
16
+ required: { color: color.danger },
17
+ optional: { fontWeight: 400, fontSize: text.xs, color: color.textFaint },
18
+ });
19
+ export function Label({ required, optional, optionalLabel = "選填", children, ...props }) {
20
+ return (_jsxs("label", { ...props, ...styled(props, styles.base), children: [children, required && (_jsx("span", { "aria-hidden": "true", ...stylex.props(styles.required), children: "*" })), optional && _jsxs("span", { ...stylex.props(styles.optional), children: [" ", optionalLabel] })] }));
21
+ }
@@ -0,0 +1,26 @@
1
+ import { type AriaAttributes } from "react";
2
+ export type FieldContextValue = {
3
+ controlId: string;
4
+ describedBy?: string;
5
+ invalid: boolean;
6
+ required: boolean;
7
+ disabled: boolean;
8
+ };
9
+ export declare const FieldContext: import("react").Context<FieldContextValue | null>;
10
+ type ControlProps = {
11
+ "aria-describedby"?: string;
12
+ "aria-invalid"?: AriaAttributes["aria-invalid"];
13
+ required?: boolean;
14
+ disabled?: boolean;
15
+ };
16
+ export type FieldControlProps = {
17
+ id?: string;
18
+ "aria-describedby"?: string;
19
+ required?: boolean;
20
+ disabled?: boolean;
21
+ };
22
+ /** A Field owns its control's id, so a caller-supplied `id` is ignored inside one. */
23
+ export declare function useFieldControl(props: ControlProps): FieldControlProps & {
24
+ invalid: boolean;
25
+ };
26
+ export {};
@@ -0,0 +1,16 @@
1
+ import { createContext, useContext } from "react";
2
+ export const FieldContext = createContext(null);
3
+ /** A Field owns its control's id, so a caller-supplied `id` is ignored inside one. */
4
+ export function useFieldControl(props) {
5
+ const field = useContext(FieldContext);
6
+ const ownInvalid = props["aria-invalid"] === true || props["aria-invalid"] === "true";
7
+ if (!field)
8
+ return { invalid: ownInvalid };
9
+ return {
10
+ id: field.controlId,
11
+ "aria-describedby": props["aria-describedby"] ?? field.describedBy,
12
+ required: props.required ?? field.required,
13
+ disabled: props.disabled ?? field.disabled,
14
+ invalid: ownInvalid || field.invalid,
15
+ };
16
+ }
@@ -0,0 +1,29 @@
1
+ import type { CSSProperties } from "react";
2
+ import { type ReactNode, type Ref } from "react";
3
+ export declare function useMessageBody(): {
4
+ current: HTMLElement | null;
5
+ } | null;
6
+ export type ThreadProps = {
7
+ children: ReactNode;
8
+ className?: string;
9
+ style?: CSSProperties;
10
+ ref?: Ref<HTMLDivElement>;
11
+ };
12
+ export declare function Thread({ children, ...rest }: ThreadProps): import("react").JSX.Element;
13
+ export type UserMessageProps = {
14
+ children: ReactNode;
15
+ authorLabel?: string;
16
+ };
17
+ export declare function UserMessage({ children, authorLabel }: UserMessageProps): import("react").JSX.Element;
18
+ export type AssistantMessageProps = {
19
+ streaming?: boolean;
20
+ pending?: boolean;
21
+ pendingLabel?: string;
22
+ authorLabel?: string;
23
+ children?: ReactNode;
24
+ };
25
+ export declare function AssistantMessage({ streaming, pending, pendingLabel, authorLabel, children, }: AssistantMessageProps): import("react").JSX.Element;
26
+ export type TextPartProps = {
27
+ children: ReactNode;
28
+ };
29
+ export declare function TextPart({ children }: TextPartProps): import("react").JSX.Element;
@@ -0,0 +1,94 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import * as stylex from "@stylexjs/stylex";
3
+ import { Children, createContext, isValidElement, useContext, useRef } from "react";
4
+ import { styled } from "../../lib/styled.js";
5
+ import { color, radius, space, text } from "../../tokens.stylex.js";
6
+ const REDUCED = "@media (prefers-reduced-motion: reduce)";
7
+ const blink = stylex.keyframes({ "50%": { opacity: 0 } });
8
+ const pulse = stylex.keyframes({ "50%": { opacity: 0.3, scale: 0.8 } });
9
+ const styles = stylex.create({
10
+ thread: {
11
+ display: "flex",
12
+ flexDirection: "column",
13
+ gap: space.lg,
14
+ paddingInline: space.md,
15
+ paddingBlock: space.lg,
16
+ },
17
+ turn: { display: "flex", flexDirection: "column", gap: space.xs },
18
+ userTurn: { alignItems: "flex-end" },
19
+ bubble: {
20
+ maxWidth: "85%",
21
+ backgroundColor: `color-mix(in srgb, ${color.border} 45%, ${color.surface})`,
22
+ borderRadius: radius.xl,
23
+ borderEndEndRadius: radius.sm,
24
+ paddingBlock: space.xs,
25
+ paddingInline: space.sm,
26
+ fontSize: text.sm,
27
+ lineHeight: text.leadingRelaxed,
28
+ color: color.text,
29
+ },
30
+ assistant: {
31
+ position: "relative",
32
+ paddingBottom: space.xl,
33
+ marginBottom: `calc(${space.xl} * -1)`,
34
+ contentVisibility: "auto",
35
+ containIntrinsicSize: "auto 200px",
36
+ "--ak-action-bar-opacity": { default: "0", ":hover": "1" },
37
+ },
38
+ part: { margin: 0, fontSize: text.sm, lineHeight: text.leadingRelaxed, color: color.text },
39
+ cursor: {
40
+ display: "inline-block",
41
+ width: 1,
42
+ height: "1.05em",
43
+ backgroundColor: color.text,
44
+ verticalAlign: "text-bottom",
45
+ marginInlineStart: 1,
46
+ animationName: { default: blink, [REDUCED]: "none" },
47
+ animationDuration: "1s",
48
+ animationTimingFunction: "steps(2, start)",
49
+ animationIterationCount: "infinite",
50
+ },
51
+ srOnly: {
52
+ position: "absolute",
53
+ width: 1,
54
+ height: 1,
55
+ padding: 0,
56
+ margin: -1,
57
+ overflow: "hidden",
58
+ clipPath: "inset(50%)",
59
+ whiteSpace: "nowrap",
60
+ borderWidth: 0,
61
+ },
62
+ pending: {
63
+ display: "inline-block",
64
+ width: "0.5rem",
65
+ height: "0.5rem",
66
+ borderRadius: radius.full,
67
+ backgroundColor: color.textMuted,
68
+ animationName: { default: pulse, [REDUCED]: "none" },
69
+ animationDuration: "1.2s",
70
+ animationTimingFunction: "ease-in-out",
71
+ animationIterationCount: "infinite",
72
+ },
73
+ });
74
+ const StreamingContext = createContext(false);
75
+ const MessageBodyContext = createContext(null);
76
+ export function useMessageBody() {
77
+ return useContext(MessageBodyContext);
78
+ }
79
+ export function Thread({ children, ...rest }) {
80
+ return (_jsx("div", { ...rest, ...styled(rest, styles.thread), children: children }));
81
+ }
82
+ export function UserMessage({ children, authorLabel = "你說:" }) {
83
+ return (_jsxs("div", { ...stylex.props(styles.turn, styles.userTurn), children: [_jsx("span", { ...stylex.props(styles.srOnly), children: authorLabel }), _jsx("div", { ...stylex.props(styles.bubble), children: children })] }));
84
+ }
85
+ export function AssistantMessage({ streaming = false, pending = false, pendingLabel = "回覆中", authorLabel = "助理說:", children, }) {
86
+ const body = useRef(null);
87
+ const parts = Children.toArray(children);
88
+ const lastText = parts.reduce((found, part, index) => (isValidElement(part) && part.type === TextPart ? index : found), -1);
89
+ return (_jsx(MessageBodyContext, { value: body, children: _jsxs("div", { ref: body, ...stylex.props(styles.turn, styles.assistant), children: [_jsx("span", { ...stylex.props(styles.srOnly), children: authorLabel }), pending ? (_jsxs("span", { role: "status", children: [_jsx("span", { "aria-hidden": "true", ...stylex.props(styles.pending) }), _jsx("span", { ...stylex.props(styles.srOnly), children: pendingLabel })] })) : (parts.map((part, index) => (_jsx(StreamingContext, { value: streaming && index === lastText, children: part }, isValidElement(part) && part.key != null ? part.key : index))))] }) }));
90
+ }
91
+ export function TextPart({ children }) {
92
+ const streaming = useContext(StreamingContext);
93
+ return (_jsxs("p", { "data-ak-message-text": "", ...stylex.props(styles.part), children: [children, streaming && _jsx("span", { "aria-hidden": "true", ...stylex.props(styles.cursor) })] }));
94
+ }
@@ -0,0 +1,20 @@
1
+ import { type ComponentProps } from "react";
2
+ /** Length thresholds (8 / 12 / 20) × character classes. 12 matches storage's MIN_LENGTH. */
3
+ export declare function defaultScorer(value: string): number;
4
+ export type PasswordInputProps = Omit<ComponentProps<"input">, "type" | "size" | "value"> & {
5
+ value?: string;
6
+ defaultValue?: string;
7
+ onValueChange?: (value: string) => void;
8
+ meter?: boolean;
9
+ scorer?: (value: string) => number;
10
+ levelLabels?: string[];
11
+ capsLockWarning?: boolean;
12
+ capsLockLabel?: string;
13
+ confirmOf?: string;
14
+ mismatchLabel?: string;
15
+ showLabel?: string;
16
+ hideLabel?: string;
17
+ shownStatus?: string;
18
+ invalid?: boolean;
19
+ };
20
+ export declare function PasswordInput({ value, defaultValue, onValueChange, meter, scorer, levelLabels, capsLockWarning, capsLockLabel, confirmOf, mismatchLabel, showLabel, hideLabel, shownStatus, invalid, autoComplete, ...props }: PasswordInputProps): import("react").JSX.Element;
@@ -0,0 +1,141 @@
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 { reset, styled } from "../../lib/styled.js";
5
+ import { useControllableState } from "../../lib/useControllableState.js";
6
+ import { color, font, radius, space, text } from "../../tokens.stylex.js";
7
+ import { controlStyles } from "../input/Input.js";
8
+ import { useFieldControl } from "../label/fieldContext.js";
9
+ const REDUCED = "@media (prefers-reduced-motion: reduce)";
10
+ const styles = stylex.create({
11
+ field: { position: "relative" },
12
+ input: { paddingInlineEnd: "2.4rem" },
13
+ toggle: {
14
+ position: "absolute",
15
+ insetInlineEnd: space.xxs,
16
+ insetBlockStart: "50%",
17
+ translate: "0 -50%",
18
+ display: "grid",
19
+ placeItems: "center",
20
+ width: "1.75rem",
21
+ height: "1.75rem",
22
+ borderRadius: radius.sm,
23
+ color: color.textMuted,
24
+ backgroundColor: { default: "transparent", ":hover": color.accentSubtle },
25
+ cursor: "pointer",
26
+ outline: { default: "none", ":focus-visible": `2px solid ${color.focusRing}` },
27
+ },
28
+ meter: { display: "grid", gap: space.xxs, marginTop: space.xxs },
29
+ bars: { display: "flex", gap: space.xxs },
30
+ bar: {
31
+ height: "0.25rem",
32
+ flex: 1,
33
+ borderRadius: radius.full,
34
+ backgroundColor: color.border,
35
+ transitionProperty: "background-color",
36
+ transitionDuration: { default: "160ms", [REDUCED]: "0s" },
37
+ },
38
+ barWeak: { backgroundColor: color.danger },
39
+ barFair: { backgroundColor: color.warning },
40
+ barStrong: { backgroundColor: color.accent },
41
+ label: { fontFamily: font.body, fontSize: text.xs, color: color.textMuted, minHeight: "1.2em", margin: 0 },
42
+ labelWeak: { color: color.danger },
43
+ warningIcon: { color: color.warning, flex: "none" },
44
+ caps: {
45
+ display: "flex",
46
+ alignItems: "center",
47
+ gap: space.xxs,
48
+ backgroundColor: color.warningSubtle,
49
+ color: color.text,
50
+ borderRadius: radius.sm,
51
+ paddingBlock: space.xxs,
52
+ paddingInline: space.xs,
53
+ fontFamily: font.body,
54
+ fontSize: text.xs,
55
+ marginTop: space.xxs,
56
+ },
57
+ error: { fontFamily: font.body, fontSize: text.xs, color: color.danger, margin: 0, marginTop: space.xxs },
58
+ srOnly: {
59
+ position: "absolute",
60
+ width: 1,
61
+ height: 1,
62
+ padding: 0,
63
+ margin: -1,
64
+ overflow: "hidden",
65
+ clipPath: "inset(50%)",
66
+ whiteSpace: "nowrap",
67
+ borderWidth: 0,
68
+ },
69
+ });
70
+ const LEVEL_LABELS = [
71
+ "passphrase 無法找回,忘了就只能靠復原金鑰。",
72
+ "弱 — 再長一點。",
73
+ "可 — 建議混入更多種字元。",
74
+ "強",
75
+ "很強",
76
+ ];
77
+ /** Length thresholds (8 / 12 / 20) × character classes. 12 matches storage's MIN_LENGTH. */
78
+ export function defaultScorer(value) {
79
+ if (!value)
80
+ return 0;
81
+ let classes = 0;
82
+ for (const pattern of [/[a-z]/, /[A-Z]/, /\d/, /[^A-Za-z0-9]/])
83
+ if (pattern.test(value))
84
+ classes += 1;
85
+ if (value.length < 8)
86
+ return 1;
87
+ if (value.length < 12)
88
+ return classes >= 3 ? 2 : 1;
89
+ if (value.length < 20)
90
+ return classes >= 3 ? 3 : 2;
91
+ return classes >= 2 ? 4 : 3;
92
+ }
93
+ const BAR_TONE = [null, styles.barWeak, styles.barFair, styles.barStrong, styles.barStrong];
94
+ function EyeIcon({ off }) {
95
+ return off ? (_jsx("svg", { width: "15", height: "15", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", "aria-hidden": "true", children: _jsx("path", { d: "M2 12s3.5-7 10-7c1.8 0 3.4.5 4.7 1.3M22 12s-3.5 7-10 7c-1.8 0-3.4-.5-4.7-1.3M3 3l18 18" }) })) : (_jsxs("svg", { width: "15", height: "15", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", "aria-hidden": "true", children: [_jsx("path", { d: "M2 12s3.5-7 10-7 10 7 10 7-3.5 7-10 7-10-7-10-7Z" }), _jsx("circle", { cx: "12", cy: "12", r: "3" })] }));
96
+ }
97
+ function WarningIcon() {
98
+ return (_jsxs("svg", { width: "13", height: "13", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", "aria-hidden": "true", children: [_jsx("path", { d: "m12 3 10 18H2L12 3Z" }), _jsx("path", { d: "M12 10v4m0 3h.01" })] }));
99
+ }
100
+ export function PasswordInput({ value, defaultValue = "", onValueChange, meter = false, scorer = defaultScorer, levelLabels = LEVEL_LABELS, capsLockWarning = true, capsLockLabel = "Caps Lock 開著。", confirmOf, mismatchLabel = "兩次輸入的 passphrase 不一樣。", showLabel = "顯示 passphrase", hideLabel = "隱藏 passphrase", shownStatus = "passphrase 已顯示", invalid, autoComplete = "new-password", ...props }) {
101
+ const base = useId();
102
+ const meterId = `${base}meter`;
103
+ const capsId = `${base}caps`;
104
+ const errorId = `${base}error`;
105
+ const { invalid: fieldInvalid, ...field } = useFieldControl(props);
106
+ const [current, setCurrent] = useControllableState(value, defaultValue, onValueChange);
107
+ const [revealed, setRevealed] = useState(false);
108
+ const [capsOn, setCapsOn] = useState(false);
109
+ const mismatched = confirmOf != null && current.length > 0 && current !== confirmOf;
110
+ const isInvalid = invalid ?? (mismatched || fieldInvalid);
111
+ const level = meter ? scorer(current) : 0;
112
+ const describedBy = [
113
+ meter ? meterId : null,
114
+ capsLockWarning && capsOn ? capsId : null,
115
+ mismatched ? errorId : null,
116
+ field["aria-describedby"],
117
+ ]
118
+ .filter(Boolean)
119
+ .join(" ");
120
+ function readCapsLock(event) {
121
+ if (!capsLockWarning || typeof event.getModifierState !== "function")
122
+ return;
123
+ setCapsOn(event.getModifierState("CapsLock"));
124
+ }
125
+ return (_jsxs("div", { children: [_jsxs("div", { ...stylex.props(styles.field), children: [_jsx("input", { ...props, ...field, type: revealed ? "text" : "password", autoComplete: autoComplete, value: current, "aria-invalid": isInvalid || undefined, "aria-describedby": describedBy || undefined, onChange: (event) => {
126
+ setCurrent(event.currentTarget.value);
127
+ props.onChange?.(event);
128
+ }, onKeyDown: (event) => {
129
+ readCapsLock(event);
130
+ props.onKeyDown?.(event);
131
+ }, onKeyUp: (event) => {
132
+ readCapsLock(event);
133
+ props.onKeyUp?.(event);
134
+ }, onBlur: (event) => {
135
+ setCapsOn(false);
136
+ props.onBlur?.(event);
137
+ }, ...styled(props, controlStyles.base, controlStyles.md, styles.input, isInvalid && controlStyles.invalid) }), _jsx("button", { type: "button", "aria-label": revealed ? hideLabel : showLabel, "aria-pressed": revealed, onClick: (event) => {
138
+ setRevealed((shown) => !shown);
139
+ event.currentTarget.parentElement?.querySelector("input")?.focus();
140
+ }, ...stylex.props(reset.control, styles.toggle), children: _jsx(EyeIcon, { off: revealed }) })] }), meter && (_jsxs("div", { id: meterId, ...stylex.props(styles.meter), children: [_jsx("div", { "aria-hidden": "true", ...stylex.props(styles.bars), children: [0, 1, 2, 3].map((index) => (_jsx("i", { ...stylex.props(styles.bar, index < level && BAR_TONE[level]) }, index))) }), _jsx("p", { "aria-live": "polite", ...stylex.props(styles.label, level === 1 && styles.labelWeak), children: levelLabels[level] ?? "" })] })), capsLockWarning && (_jsx("p", { id: capsId, role: "status", ...stylex.props(capsOn ? styles.caps : styles.srOnly), children: capsOn && (_jsxs(_Fragment, { children: [_jsx(WarningIcon, {}), capsLockLabel] })) })), _jsx("p", { id: errorId, ...stylex.props(mismatched ? styles.error : styles.srOnly), children: mismatched ? mismatchLabel : "" }), _jsx("span", { role: "status", ...stylex.props(styles.srOnly), children: revealed ? shownStatus : "" })] }));
141
+ }
@@ -0,0 +1,38 @@
1
+ import type { ReactElement, ReactNode } from "react";
2
+ export type PopoverProps = {
3
+ open?: boolean;
4
+ defaultOpen?: boolean;
5
+ onOpenChange?: (open: boolean) => void;
6
+ modal?: boolean;
7
+ children: ReactNode;
8
+ };
9
+ export declare function Popover({ children, ...props }: PopoverProps): import("react").JSX.Element;
10
+ export declare function PopoverTrigger({ children }: {
11
+ children: ReactElement;
12
+ }): import("react").JSX.Element;
13
+ export declare const PopoverTitle: ({ children }: {
14
+ children: ReactNode;
15
+ }) => import("react").JSX.Element;
16
+ export declare const PopoverDescription: ({ children }: {
17
+ children: ReactNode;
18
+ }) => import("react").JSX.Element;
19
+ export declare const PopoverClose: ({ children }: {
20
+ children: ReactElement;
21
+ }) => import("react").JSX.Element;
22
+ type PopoverContentBase = {
23
+ side?: "top" | "bottom" | "left" | "right";
24
+ align?: "start" | "center" | "end";
25
+ sideOffset?: number;
26
+ className?: string;
27
+ children: ReactNode;
28
+ };
29
+ /** role="dialog" needs a name: pass `aria-label`, or render a `PopoverTitle` inside. */
30
+ export type PopoverContentProps = PopoverContentBase & ({
31
+ "aria-label": string;
32
+ titled?: never;
33
+ } | {
34
+ "aria-label"?: never;
35
+ titled: true;
36
+ });
37
+ export declare function PopoverContent({ side, align, sideOffset, children, ...props }: PopoverContentProps): import("react").JSX.Element;
38
+ export {};
@@ -0,0 +1,37 @@
1
+ import { jsx as _jsx } from "react/jsx-runtime";
2
+ import { Popover as BasePopover } from "@base-ui/react/popover";
3
+ import * as stylex from "@stylexjs/stylex";
4
+ import { popupStyles } from "../../lib/popup.js";
5
+ import { styled } from "../../lib/styled.js";
6
+ import { color, font, space, text } from "../../tokens.stylex.js";
7
+ const styles = stylex.create({
8
+ panel: {
9
+ minWidth: "11rem",
10
+ padding: space.sm,
11
+ fontFamily: font.body,
12
+ fontSize: text.sm,
13
+ color: color.text,
14
+ },
15
+ title: {
16
+ fontFamily: font.display,
17
+ fontSize: text.base,
18
+ fontWeight: 500,
19
+ lineHeight: text.leadingSnug,
20
+ margin: 0,
21
+ marginBottom: space.xxs,
22
+ },
23
+ description: { color: color.textMuted, margin: 0 },
24
+ });
25
+ export function Popover({ children, ...props }) {
26
+ return _jsx(BasePopover.Root, { ...props, children: children });
27
+ }
28
+ export function PopoverTrigger({ children }) {
29
+ return _jsx(BasePopover.Trigger, { render: children });
30
+ }
31
+ export const PopoverTitle = ({ children }) => (_jsx(BasePopover.Title, { ...stylex.props(styles.title), children: children }));
32
+ export const PopoverDescription = ({ children }) => (_jsx(BasePopover.Description, { ...stylex.props(styles.description), children: children }));
33
+ export const PopoverClose = ({ children }) => (_jsx(BasePopover.Close, { render: children }));
34
+ export function PopoverContent({ side = "bottom", align = "center", sideOffset = 8, children, ...props }) {
35
+ const { titled: _titled, ...rest } = props;
36
+ return (_jsx(BasePopover.Portal, { children: _jsx(BasePopover.Positioner, { side: side, align: align, sideOffset: sideOffset, ...stylex.props(popupStyles.positioner), children: _jsx(BasePopover.Popup, { ...rest, ...styled(rest, popupStyles.surface, styles.panel), children: children }) }) }));
37
+ }
@@ -0,0 +1,36 @@
1
+ declare const SPINNER_SIZES: {
2
+ readonly sm: 18;
3
+ readonly md: 28;
4
+ readonly lg: 40;
5
+ };
6
+ export type SpinnerProps = {
7
+ size?: keyof typeof SPINNER_SIZES;
8
+ label?: string;
9
+ };
10
+ export declare function Spinner({ size, label }: SpinnerProps): import("react").JSX.Element;
11
+ export type ProgressProps = {
12
+ value?: number;
13
+ /** Human-readable reading, e.g. "3 則訊息交接中 · 64%". Required by NOTES for determinate progress. */
14
+ valueText: string;
15
+ stages?: string[];
16
+ className?: string;
17
+ "aria-label": string;
18
+ };
19
+ export declare function Progress({ value, valueText, stages, ...rest }: ProgressProps): import("react").JSX.Element;
20
+ export type ProgressBallProps = {
21
+ value: number;
22
+ size?: number;
23
+ valueText: string;
24
+ className?: string;
25
+ "aria-label": string;
26
+ };
27
+ export declare function ProgressBall({ value, size, valueText, ...rest }: ProgressBallProps): import("react").JSX.Element;
28
+ export type ProgressRingProps = {
29
+ value: number;
30
+ size?: number;
31
+ valueText: string;
32
+ className?: string;
33
+ "aria-label": string;
34
+ };
35
+ export declare function ProgressRing({ value, size, valueText, ...rest }: ProgressRingProps): import("react").JSX.Element;
36
+ export {};