Skip to content

Commit b25e317

Browse files
committed
refactor(client): rewrite UI primitives batch 1 for relicensing
Behavioral contract preserved exactly: PromptInput form-scoped status, slot sections, ghost icon button with portal tooltip, and status-derived submit; Dialog controlled/uncontrolled open state, trigger focus restoration, portal content, Escape/overlay dismissal, scroll lock, focus trap, and hidden title; Reasoning controllable disclosure with stream timing and auto open/close; Tooltip delayed hover/long-press with portal positioning; Confirmation approval-state children and shared action button. Props, exports, DOM nesting, class strings, data-slot attributes, i18n keys, portals, and keyboard/focus flows unchanged. Replacement design: private slot-section renderer, context provider with forwarded-ref helper and registered panel callback, private disclosure/duration hooks, placement map with style builder, and a single conditional state component replacing the derived implementations. Part of the relicensing program in docs/RELICENSING.md.
1 parent b7f02f4 commit b25e317

5 files changed

Lines changed: 510 additions & 745 deletions

File tree

src/shared/view/ui/Confirmation.tsx

Lines changed: 30 additions & 80 deletions
Original file line numberDiff line numberDiff line change
@@ -5,56 +5,34 @@ import { cn } from '../../../utils/cn';
55
import { Alert } from './Alert';
66
import { Button } from './Button';
77

8-
/* ─── Context ────────────────────────────────────────────────────── */
9-
108
type ApprovalState = 'pending' | 'approved' | 'rejected' | undefined;
9+
type ConfirmationContextValue = { approval: ApprovalState };
1110

12-
interface ConfirmationContextValue {
13-
approval: ApprovalState;
14-
}
15-
16-
const ConfirmationContext = React.createContext<ConfirmationContextValue | null>(null);
11+
const confirmationContext = React.createContext<ConfirmationContextValue | null>(null);
1712

1813
const useConfirmation = () => {
19-
const context = React.useContext(ConfirmationContext);
20-
if (!context) {
14+
const confirmation = React.useContext(confirmationContext);
15+
if (confirmation === null) {
2116
throw new Error('Confirmation components must be used within Confirmation');
2217
}
23-
return context;
18+
return confirmation;
2419
};
2520

26-
/* ─── Confirmation (root) ────────────────────────────────────────── */
27-
28-
export interface ConfirmationProps extends React.HTMLAttributes<HTMLDivElement> {
29-
approval?: ApprovalState;
30-
}
31-
32-
export const Confirmation: React.FC<ConfirmationProps> = ({
33-
className,
34-
approval = 'pending',
35-
children,
36-
...props
37-
}) => {
38-
const contextValue = React.useMemo(() => ({ approval }), [approval]);
21+
export interface ConfirmationProps extends React.HTMLAttributes<HTMLDivElement> { approval?: ApprovalState; }
3922

23+
export const Confirmation: React.FC<ConfirmationProps> = ({ className, approval = 'pending', children, ...props }) => {
24+
const state = React.useMemo(() => ({ approval }), [approval]);
4025
return (
41-
<ConfirmationContext.Provider value={contextValue}>
42-
<Alert className={cn('flex flex-col gap-2', className)} {...props}>
43-
{children}
44-
</Alert>
45-
</ConfirmationContext.Provider>
26+
<confirmationContext.Provider value={state}>
27+
<Alert className={cn('flex flex-col gap-2', className)} {...props}>{children}</Alert>
28+
</confirmationContext.Provider>
4629
);
4730
};
4831
Confirmation.displayName = 'Confirmation';
4932

50-
/* ─── ConfirmationTitle ──────────────────────────────────────────── */
51-
5233
export type ConfirmationTitleProps = React.HTMLAttributes<HTMLDivElement>;
5334

54-
export const ConfirmationTitle: React.FC<ConfirmationTitleProps> = ({
55-
className,
56-
...props
57-
}) => (
35+
export const ConfirmationTitle: React.FC<ConfirmationTitleProps> = ({ className, ...props }) => (
5836
<div
5937
data-slot="confirmation-title"
6038
className={cn('inline text-sm text-muted-foreground', className)}
@@ -63,56 +41,35 @@ export const ConfirmationTitle: React.FC<ConfirmationTitleProps> = ({
6341
);
6442
ConfirmationTitle.displayName = 'ConfirmationTitle';
6543

66-
/* ─── ConfirmationRequest — visible only when pending ────────────── */
44+
type ConfirmationStateChildren = { children?: React.ReactNode };
6745

68-
export interface ConfirmationRequestProps {
69-
children?: React.ReactNode;
46+
function ConfirmationState({ approval, children }: ConfirmationStateChildren & { approval: Exclude<ApprovalState, undefined> }) {
47+
return useConfirmation().approval === approval ? <>{children}</> : null;
7048
}
7149

72-
export const ConfirmationRequest: React.FC<ConfirmationRequestProps> = ({ children }) => {
73-
const { approval } = useConfirmation();
74-
if (approval !== 'pending') return null;
75-
return <>{children}</>;
76-
};
50+
export interface ConfirmationRequestProps { children?: React.ReactNode; }
51+
export const ConfirmationRequest: React.FC<ConfirmationRequestProps> = ({ children }) => (
52+
<ConfirmationState approval="pending">{children}</ConfirmationState>
53+
);
7754
ConfirmationRequest.displayName = 'ConfirmationRequest';
7855

79-
/* ─── ConfirmationAccepted — visible only when approved ──────────── */
80-
81-
export interface ConfirmationAcceptedProps {
82-
children?: React.ReactNode;
83-
}
84-
85-
export const ConfirmationAccepted: React.FC<ConfirmationAcceptedProps> = ({ children }) => {
86-
const { approval } = useConfirmation();
87-
if (approval !== 'approved') return null;
88-
return <>{children}</>;
89-
};
56+
export interface ConfirmationAcceptedProps { children?: React.ReactNode; }
57+
export const ConfirmationAccepted: React.FC<ConfirmationAcceptedProps> = ({ children }) => (
58+
<ConfirmationState approval="approved">{children}</ConfirmationState>
59+
);
9060
ConfirmationAccepted.displayName = 'ConfirmationAccepted';
9161

92-
/* ─── ConfirmationRejected — visible only when rejected ──────────── */
93-
94-
export interface ConfirmationRejectedProps {
95-
children?: React.ReactNode;
96-
}
97-
98-
export const ConfirmationRejected: React.FC<ConfirmationRejectedProps> = ({ children }) => {
99-
const { approval } = useConfirmation();
100-
if (approval !== 'rejected') return null;
101-
return <>{children}</>;
102-
};
62+
export interface ConfirmationRejectedProps { children?: React.ReactNode; }
63+
export const ConfirmationRejected: React.FC<ConfirmationRejectedProps> = ({ children }) => (
64+
<ConfirmationState approval="rejected">{children}</ConfirmationState>
65+
);
10366
ConfirmationRejected.displayName = 'ConfirmationRejected';
10467

105-
/* ─── ConfirmationActions — visible only when pending ────────────── */
106-
10768
export type ConfirmationActionsProps = React.HTMLAttributes<HTMLDivElement>;
10869

109-
export const ConfirmationActions: React.FC<ConfirmationActionsProps> = ({
110-
className,
111-
...props
112-
}) => {
70+
export const ConfirmationActions: React.FC<ConfirmationActionsProps> = ({ className, ...props }) => {
11371
const { approval } = useConfirmation();
11472
if (approval !== 'pending') return null;
115-
11673
return (
11774
<div
11875
data-slot="confirmation-actions"
@@ -123,16 +80,9 @@ export const ConfirmationActions: React.FC<ConfirmationActionsProps> = ({
12380
};
12481
ConfirmationActions.displayName = 'ConfirmationActions';
12582

126-
/* ─── ConfirmationAction — styled button ─────────────────────────── */
127-
128-
export type ConfirmationActionProps = React.ButtonHTMLAttributes<HTMLButtonElement> & {
129-
variant?: 'default' | 'outline' | 'ghost' | 'destructive';
130-
};
83+
export type ConfirmationActionProps = React.ButtonHTMLAttributes<HTMLButtonElement> & { variant?: 'default' | 'outline' | 'ghost' | 'destructive'; };
13184

132-
export const ConfirmationAction: React.FC<ConfirmationActionProps> = ({
133-
variant = 'default',
134-
...props
135-
}) => (
85+
export const ConfirmationAction: React.FC<ConfirmationActionProps> = ({ variant = 'default', ...props }) => (
13686
<Button className="h-8 px-3 text-sm" variant={variant} type="button" {...props} />
13787
);
13888
ConfirmationAction.displayName = 'ConfirmationAction';

0 commit comments

Comments
 (0)