返回 DeepSeek-Reasonix
PromptShelf.tsx
根目录 / desktop / frontend / src / components / PromptShelf.tsx
1 import type { ReactNode, RefObject } from "react";
2
3 export { PromptAction, PromptDescriptionDisclosure, PromptDescriptionToggle } from "./PromptAction";
4
5 export function PromptShelf({
6 className,
7 cardClassName,
8 titleId,
9 title,
10 badges,
11 meta,
12 actions,
13 children,
14 crumbs,
15 note,
16 quickActions,
17 headerActions,
18 footer,
19 barRef,
20 role = "dialog",
21 decision = false,
22 actionsRole = "listbox",
23 }: {
24 className?: string;
25 cardClassName?: string;
26 titleId: string;
27 title: ReactNode;
28 badges?: ReactNode;
29 meta?: ReactNode;
30 actions?: ReactNode;
31 children?: ReactNode;
32 crumbs?: ReactNode;
33 // Rendered between the actions grid and the quick actions; used for
34 // focus-following detail previews and similar footnotes.
35 note?: ReactNode;
36 quickActions?: ReactNode;
37 headerActions?: ReactNode;
38 // Sticky confirm bar for select-then-confirm decision surfaces.
39 footer?: ReactNode;
40 barRef?: RefObject<HTMLDivElement | null>;
41 role?: "dialog" | "region";
42 // Decision surfaces keep a vertical full-width option list and a fixed
43 // confirm footer; all preceding content shares one viewport-bounded scroll.
44 decision?: boolean;
45 // Select-then-confirm surfaces use listbox. Immediate actions are a group of
46 // buttons so assistive technology does not announce them as pending choices.
47 actionsRole?: "listbox" | "group";
48 }) {
49 return (
50 <div
51 className={[
52 "prompt-shelf",
53 decision ? "prompt-shelf--decision" : "",
54 className ?? "",
55 ]
56 .filter(Boolean)
57 .join(" ")}
58 aria-live="polite"
59 >
60 <div
61 ref={barRef}
62 className={["prompt-shelf__card", cardClassName ?? ""].filter(Boolean).join(" ")}
63 role={role}
64 aria-modal={role === "dialog" ? "false" : undefined}
65 aria-labelledby={titleId}
66 tabIndex={-1}
67 >
68 <div className="prompt-shelf__content">
69 <div className="prompt-shelf__header">
70 <div className="prompt-shelf__copy">
71 <div id={titleId} className="prompt-shelf__title">
72 <span className="prompt-shelf__heading">{title}</span>
73 {badges && <span className="prompt-shelf__badges">{badges}</span>}
74 </div>
75 {meta && <div className="prompt-shelf__meta">{meta}</div>}
76 </div>
77 {headerActions && <div className="prompt-shelf__header-actions">{headerActions}</div>}
78 </div>
79 {crumbs}
80 {children && <div className="prompt-shelf__body">{children}</div>}
81 {actions && <div className="prompt-shelf__actions" role={actionsRole}>{actions}</div>}
82 {note && <div className="prompt-shelf__footnote">{note}</div>}
83 {quickActions && <div className="prompt-shelf__quick-actions">{quickActions}</div>}
84 </div>
85 {footer && <div className="prompt-shelf__footer">{footer}</div>}
86 </div>
87 </div>
88 );
89 }
90
91 export function PromptBadge({ children, tone }: { children: ReactNode; tone?: "default" | "amber" | "danger" }) {
92 return (
93 <span
94 className={[
95 "prompt-shelf__badge",
96 tone === "amber" ? " prompt-shelf__badge--amber" : "",
97 tone === "danger" ? " prompt-shelf__badge--danger" : "",
98 ].join("")}
99 >
100 {children}
101 </span>
102 );
103 }
104
105 export function PromptHeaderAction({
106 children,
107 onClick,
108 ariaLabel,
109 disabled = false,
110 }: {
111 children: ReactNode;
112 onClick: () => void;
113 ariaLabel?: string;
114 disabled?: boolean;
115 }) {
116 return (
117 <button
118 className="prompt-shelf__header-button"
119 type="button"
120 onClick={onClick}
121 aria-label={ariaLabel}
122 disabled={disabled}
123 >
124 {children}
125 </button>
126 );
127 }
128
129 export function DecisionConfirmBar({
130 hint,
131 confirmLabel,
132 onConfirm,
133 secondaryLabel,
134 onSecondary,
135 disabled = false,
136 confirmDisabled = false,
137 danger = false,
138 }: {
139 hint: ReactNode;
140 confirmLabel: ReactNode;
141 onConfirm: () => void;
142 secondaryLabel?: ReactNode;
143 onSecondary?: () => void;
144 disabled?: boolean;
145 confirmDisabled?: boolean;
146 danger?: boolean;
147 }) {
148 return (
149 <div className="decision-confirm-bar">
150 {secondaryLabel && onSecondary && (
151 <button
152 type="button"
153 className="btn btn--small decision-confirm-bar__secondary"
154 onClick={onSecondary}
155 disabled={disabled}
156 >
157 {secondaryLabel}
158 </button>
159 )}
160 <div className="decision-confirm-bar__hint">{hint}</div>
161 <button
162 type="button"
163 className={[
164 "btn btn--small decision-confirm-bar__confirm",
165 danger ? "btn--danger" : "btn--primary",
166 ].join(" ")}
167 onClick={onConfirm}
168 disabled={disabled || confirmDisabled}
169 >
170 {confirmLabel}
171 </button>
172 </div>
173 );
174 }
175
175 lines Plain Text