@molecule/app-ui-react 1.0.2 → 1.2.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 (32) hide show
  1. package/README.md +521 -1
  2. package/dist/components/ConfirmButton.d.ts +71 -0
  3. package/dist/components/ConfirmButton.d.ts.map +1 -0
  4. package/dist/components/ConfirmButton.js +88 -0
  5. package/dist/components/ConfirmButton.js.map +1 -0
  6. package/dist/components/ConfirmDialog.d.ts +71 -0
  7. package/dist/components/ConfirmDialog.d.ts.map +1 -0
  8. package/dist/components/ConfirmDialog.js +20 -0
  9. package/dist/components/ConfirmDialog.js.map +1 -0
  10. package/dist/components/Icon.d.ts.map +1 -1
  11. package/dist/components/Icon.js +12 -1
  12. package/dist/components/Icon.js.map +1 -1
  13. package/dist/components/LoadErrorBanner.d.ts +71 -0
  14. package/dist/components/LoadErrorBanner.d.ts.map +1 -0
  15. package/dist/components/LoadErrorBanner.js +31 -0
  16. package/dist/components/LoadErrorBanner.js.map +1 -0
  17. package/dist/components/PromptDialog.d.ts +91 -0
  18. package/dist/components/PromptDialog.d.ts.map +1 -0
  19. package/dist/components/PromptDialog.js +90 -0
  20. package/dist/components/PromptDialog.js.map +1 -0
  21. package/dist/components/ResponsiveAppShell.d.ts +327 -0
  22. package/dist/components/ResponsiveAppShell.d.ts.map +1 -0
  23. package/dist/components/ResponsiveAppShell.js +470 -0
  24. package/dist/components/ResponsiveAppShell.js.map +1 -0
  25. package/dist/components/UserMenu.d.ts.map +1 -1
  26. package/dist/components/UserMenu.js +5 -1
  27. package/dist/components/UserMenu.js.map +1 -1
  28. package/dist/components/index.d.ts +5 -0
  29. package/dist/components/index.d.ts.map +1 -1
  30. package/dist/components/index.js +5 -0
  31. package/dist/components/index.js.map +1 -1
  32. package/package.json +2 -2
@@ -0,0 +1,88 @@
1
+ import { jsx as _jsx } from "react/jsx-runtime";
2
+ /**
3
+ * ConfirmButton component.
4
+ *
5
+ * @module
6
+ */
7
+ import { forwardRef, useEffect, useRef, useState } from 'react';
8
+ import { useTranslation } from '@molecule/app-react';
9
+ import { getClassMap } from '@molecule/app-ui';
10
+ /**
11
+ * Two-step destructive-action button: the first click ARMS it ("Confirm?"),
12
+ * the second commits. Auto-disarms after {@link ConfirmButtonProps.disarmSeconds}
13
+ * (default 4s) so an armed-then-abandoned row can't surprise anyone later.
14
+ *
15
+ * Replaces the fleet's hand-rolled arm-confirm pattern (previously
16
+ * reimplemented per app — button flips, dialogs, `window.confirm`, or worse,
17
+ * nothing at all) with one accessible, touch-friendly implementation:
18
+ * the whole button is the target (≥36px via the button CVA), the armed
19
+ * state is announced through `aria-live`, and Escape disarms.
20
+ *
21
+ * @example
22
+ * ```tsx
23
+ * <ConfirmButton
24
+ * label="Delete"
25
+ * confirmLabel="Confirm delete?"
26
+ * onConfirm={() => deleteClient(client.id)}
27
+ * testId="client-delete"
28
+ * />
29
+ * ```
30
+ */
31
+ export const ConfirmButton = forwardRef(({ onConfirm, label, confirmLabel, pendingLabel, disarmSeconds = 4, disabled, testId, className, color, size, variant, ...rest }, ref) => {
32
+ const cm = getClassMap();
33
+ const { t } = useTranslation();
34
+ const [phase, setPhase] = useState('idle');
35
+ const disarmTimer = useRef(null);
36
+ const disarm = () => {
37
+ if (disarmTimer.current)
38
+ clearTimeout(disarmTimer.current);
39
+ disarmTimer.current = null;
40
+ setPhase('idle');
41
+ };
42
+ useEffect(() => () => {
43
+ if (disarmTimer.current)
44
+ clearTimeout(disarmTimer.current);
45
+ }, []);
46
+ useEffect(() => {
47
+ if (phase !== 'armed' || disarmSeconds <= 0)
48
+ return;
49
+ disarmTimer.current = setTimeout(disarm, disarmSeconds * 1000);
50
+ return () => {
51
+ if (disarmTimer.current)
52
+ clearTimeout(disarmTimer.current);
53
+ };
54
+ }, [phase, disarmSeconds]);
55
+ useEffect(() => {
56
+ if (phase !== 'armed')
57
+ return;
58
+ const onKey = (e) => {
59
+ if (e.key === 'Escape')
60
+ disarm();
61
+ };
62
+ window.addEventListener('keydown', onKey);
63
+ return () => window.removeEventListener('keydown', onKey);
64
+ }, [phase]);
65
+ const handleClick = async () => {
66
+ if (phase === 'idle') {
67
+ setPhase('armed');
68
+ return;
69
+ }
70
+ if (phase === 'pending')
71
+ return;
72
+ setPhase('pending');
73
+ try {
74
+ await onConfirm();
75
+ }
76
+ finally {
77
+ setPhase('idle');
78
+ }
79
+ };
80
+ const visibleLabel = phase === 'pending'
81
+ ? (pendingLabel ?? t('confirmButton.pending', {}, { defaultValue: 'Working…' }))
82
+ : phase === 'armed'
83
+ ? (confirmLabel ?? t('confirmButton.confirm', {}, { defaultValue: 'Confirm?' }))
84
+ : label;
85
+ return (_jsx("button", { ref: ref, type: "button", onClick: () => void handleClick(), disabled: disabled || phase === 'pending', "aria-live": "polite", "data-state": phase, "data-testid": testId, className: cm.cn(cm.button({ color: phase === 'armed' ? 'error' : (color ?? 'primary'), size, variant }), className), ...rest, children: visibleLabel }));
86
+ });
87
+ ConfirmButton.displayName = 'ConfirmButton';
88
+ //# sourceMappingURL=ConfirmButton.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ConfirmButton.js","sourceRoot":"","sources":["../../src/components/ConfirmButton.tsx"],"names":[],"mappings":"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"}
@@ -0,0 +1,71 @@
1
+ /**
2
+ * ConfirmDialog component — the shared "Are you sure?" modal.
3
+ *
4
+ * Promoted from `@molecule/app-danger-zone-react` (2026-09-24) so every app
5
+ * confirms destructive actions through the framework bundle instead of
6
+ * hand-rolling `window.confirm` (broken headless + on touch) or reaching for
7
+ * the danger-zone feature package just to render one dialog. For an INLINE
8
+ * two-click arm/commit affordance use {@link ConfirmButton}; for a dialog
9
+ * that also collects text use {@link PromptDialog}.
10
+ *
11
+ * @example
12
+ * ```tsx
13
+ * import { ConfirmDialog } from '@molecule/app-ui-react'
14
+ *
15
+ * <ConfirmDialog
16
+ * open={confirmOpen}
17
+ * onClose={() => setConfirmOpen(false)}
18
+ * title="Delete vault?"
19
+ * description="This action cannot be undone."
20
+ * confirmLabel={t('vault.delete', {}, { defaultValue: 'Delete' })}
21
+ * onConfirm={handleDelete}
22
+ * />
23
+ * ```
24
+ *
25
+ * @remarks
26
+ * Does NOT track `onConfirm`'s promise and does NOT close itself on confirm:
27
+ * set `loading` yourself while the action runs, then call `onClose()` when it
28
+ * settles — otherwise users can double-fire the destructive action or be left
29
+ * staring at an open dialog. Cancel/Confirm fall back to the `confirm.*`
30
+ * i18n keys (companion bond: `@molecule/app-locales-danger-zone`); pass
31
+ * `confirmLabel`/`cancelLabel` for action-specific wording ("Delete",
32
+ * "Revoke"). `destructive` (default true) only switches the confirm button
33
+ * color. `data-mol-id` for e2e locators goes on YOUR trigger button; the
34
+ * dialog's own buttons are text-labeled ("Cancel"/confirm label) so
35
+ * `getByRole('button', { name })` reaches them.
36
+ *
37
+ * @module
38
+ */
39
+ import type { JSX, ReactNode } from 'react';
40
+ /** Props for {@link ConfirmDialog}. */
41
+ export interface ConfirmDialogProps {
42
+ /** Whether the dialog is open. */
43
+ open: boolean;
44
+ /** Called when the dialog should close (escape, backdrop, cancel). */
45
+ onClose: () => void;
46
+ /** Title. */
47
+ title: ReactNode;
48
+ /** Body / warning text. */
49
+ description: ReactNode;
50
+ /** Confirm-button label (e.g. "Delete"). */
51
+ confirmLabel?: ReactNode;
52
+ /** Cancel-button label. */
53
+ cancelLabel?: ReactNode;
54
+ /** Called when the user confirms. */
55
+ onConfirm: () => void | Promise<void>;
56
+ /** Is the confirm action destructive? Defaults to true. */
57
+ destructive?: boolean;
58
+ /** Extra body content between description and footer. */
59
+ children?: ReactNode;
60
+ /** Disables both buttons. Set it yourself while your `onConfirm` promise is pending — the dialog does not track it and does not auto-close. */
61
+ loading?: boolean;
62
+ }
63
+ /**
64
+ * Are-you-sure-style confirmation modal for destructive actions.
65
+ *
66
+ * Use standalone (around delete buttons, revoke tokens, irreversible
67
+ * migrations) or with a DangerZoneSection action.
68
+ * @param props - Component props (see {@link ConfirmDialogProps}).
69
+ */
70
+ export declare function ConfirmDialog({ open, onClose, title, description, confirmLabel, cancelLabel, onConfirm, destructive, children, loading, }: ConfirmDialogProps): JSX.Element;
71
+ //# sourceMappingURL=ConfirmDialog.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ConfirmDialog.d.ts","sourceRoot":"","sources":["../../src/components/ConfirmDialog.tsx"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAqCG;AAEH,OAAO,KAAK,EAAE,GAAG,EAAE,SAAS,EAAE,MAAM,OAAO,CAAA;AAQ3C,uCAAuC;AACvC,MAAM,WAAW,kBAAkB;IACjC,kCAAkC;IAClC,IAAI,EAAE,OAAO,CAAA;IACb,sEAAsE;IACtE,OAAO,EAAE,MAAM,IAAI,CAAA;IACnB,aAAa;IACb,KAAK,EAAE,SAAS,CAAA;IAChB,2BAA2B;IAC3B,WAAW,EAAE,SAAS,CAAA;IACtB,4CAA4C;IAC5C,YAAY,CAAC,EAAE,SAAS,CAAA;IACxB,2BAA2B;IAC3B,WAAW,CAAC,EAAE,SAAS,CAAA;IACvB,qCAAqC;IACrC,SAAS,EAAE,MAAM,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,CAAA;IACrC,2DAA2D;IAC3D,WAAW,CAAC,EAAE,OAAO,CAAA;IACrB,yDAAyD;IACzD,QAAQ,CAAC,EAAE,SAAS,CAAA;IACpB,+IAA+I;IAC/I,OAAO,CAAC,EAAE,OAAO,CAAA;CAClB;AAED;;;;;;GAMG;AACH,wBAAgB,aAAa,CAAC,EAC5B,IAAI,EACJ,OAAO,EACP,KAAK,EACL,WAAW,EACX,YAAY,EACZ,WAAW,EACX,SAAS,EACT,WAAkB,EAClB,QAAQ,EACR,OAAO,GACR,EAAE,kBAAkB,GAAG,GAAG,CAAC,OAAO,CA2BlC"}
@@ -0,0 +1,20 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { useTranslation } from '@molecule/app-react';
3
+ import { getClassMap } from '@molecule/app-ui';
4
+ import { Button } from './Button.js';
5
+ import { Modal } from './Modal.js';
6
+ /**
7
+ * Are-you-sure-style confirmation modal for destructive actions.
8
+ *
9
+ * Use standalone (around delete buttons, revoke tokens, irreversible
10
+ * migrations) or with a DangerZoneSection action.
11
+ * @param props - Component props (see {@link ConfirmDialogProps}).
12
+ */
13
+ export function ConfirmDialog({ open, onClose, title, description, confirmLabel, cancelLabel, onConfirm, destructive = true, children, loading, }) {
14
+ const cm = getClassMap();
15
+ const { t } = useTranslation();
16
+ return (_jsx(Modal, { open: open, onClose: onClose, children: _jsxs("div", { className: cm.stack(3), children: [_jsx("h2", { className: cm.cn(cm.textSize('lg'), cm.fontWeight('bold')), children: title }), _jsx("p", { className: cm.textSize('sm'), children: description }), children, _jsxs("div", { className: cm.flex({ justify: 'end', gap: 'sm' }), children: [_jsx(Button, { variant: "ghost", onClick: onClose, disabled: loading, children: cancelLabel ?? t('confirm.cancel', {}, { defaultValue: 'Cancel' }) }), _jsx(Button, { variant: "solid", color: destructive ? 'error' : 'primary', onClick: () => void onConfirm(), disabled: loading, children: loading
17
+ ? '…'
18
+ : (confirmLabel ?? t('confirm.confirm', {}, { defaultValue: 'Confirm' })) })] })] }) }));
19
+ }
20
+ //# sourceMappingURL=ConfirmDialog.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ConfirmDialog.js","sourceRoot":"","sources":["../../src/components/ConfirmDialog.tsx"],"names":[],"mappings":";AAyCA,OAAO,EAAE,cAAc,EAAE,MAAM,qBAAqB,CAAA;AACpD,OAAO,EAAE,WAAW,EAAE,MAAM,kBAAkB,CAAA;AAE9C,OAAO,EAAE,MAAM,EAAE,MAAM,aAAa,CAAA;AACpC,OAAO,EAAE,KAAK,EAAE,MAAM,YAAY,CAAA;AA0BlC;;;;;;GAMG;AACH,MAAM,UAAU,aAAa,CAAC,EAC5B,IAAI,EACJ,OAAO,EACP,KAAK,EACL,WAAW,EACX,YAAY,EACZ,WAAW,EACX,SAAS,EACT,WAAW,GAAG,IAAI,EAClB,QAAQ,EACR,OAAO,GACY;IACnB,MAAM,EAAE,GAAG,WAAW,EAAE,CAAA;IACxB,MAAM,EAAE,CAAC,EAAE,GAAG,cAAc,EAAE,CAAA;IAC9B,OAAO,CACL,KAAC,KAAK,IAAC,IAAI,EAAE,IAAI,EAAE,OAAO,EAAE,OAAO,YACjC,eAAK,SAAS,EAAE,EAAE,CAAC,KAAK,CAAC,CAAC,CAAC,aACzB,aAAI,SAAS,EAAE,EAAE,CAAC,EAAE,CAAC,EAAE,CAAC,QAAQ,CAAC,IAAI,CAAC,EAAE,EAAE,CAAC,UAAU,CAAC,MAAM,CAAC,CAAC,YAAG,KAAK,GAAM,EAC5E,YAAG,SAAS,EAAE,EAAE,CAAC,QAAQ,CAAC,IAAI,CAAC,YAAG,WAAW,GAAK,EACjD,QAAQ,EACT,eAAK,SAAS,EAAE,EAAE,CAAC,IAAI,CAAC,EAAE,OAAO,EAAE,KAAK,EAAE,GAAG,EAAE,IAAI,EAAE,CAAC,aACpD,KAAC,MAAM,IAAC,OAAO,EAAC,OAAO,EAAC,OAAO,EAAE,OAAO,EAAE,QAAQ,EAAE,OAAO,YACxD,WAAW,IAAI,CAAC,CAAC,gBAAgB,EAAE,EAAE,EAAE,EAAE,YAAY,EAAE,QAAQ,EAAE,CAAC,GAC5D,EACT,KAAC,MAAM,IACL,OAAO,EAAC,OAAO,EACf,KAAK,EAAE,WAAW,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,SAAS,EACxC,OAAO,EAAE,GAAG,EAAE,CAAC,KAAK,SAAS,EAAE,EAC/B,QAAQ,EAAE,OAAO,YAEhB,OAAO;gCACN,CAAC,CAAC,GAAG;gCACL,CAAC,CAAC,CAAC,YAAY,IAAI,CAAC,CAAC,iBAAiB,EAAE,EAAE,EAAE,EAAE,YAAY,EAAE,SAAS,EAAE,CAAC,CAAC,GACpE,IACL,IACF,GACA,CACT,CAAA;AACH,CAAC"}
@@ -1 +1 @@
1
- {"version":3,"file":"Icon.d.ts","sourceRoot":"","sources":["../../src/components/Icon.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,EAAE,KAAK,QAAQ,EAAE,MAAM,OAAO,CAAA;AAE5C,OAAO,EAAW,KAAK,QAAQ,EAAE,MAAM,qBAAqB,CAAA;AAE5D;;;;;;;GAOG;AACH,MAAM,WAAW,SAAU,SAAQ,IAAI,CACrC,QAAQ,CAAC,aAAa,CAAC,EACvB,OAAO,GAAG,QAAQ,GAAG,SAAS,GAAG,MAAM,CACxC;IACC;;;;;OAKG;IACH,IAAI,EAAE,QAAQ,CAAA;IACd,sEAAsE;IACtE,IAAI,CAAC,EAAE,MAAM,CAAA;IACb,gDAAgD;IAChD,SAAS,CAAC,EAAE,MAAM,CAAA;CACnB;AAED;;;;;;;;;;;;;;;;;;;;;;GAsBG;AACH,wBAAgB,IAAI,CAAC,EAAE,IAAI,EAAE,IAAS,EAAE,SAAS,EAAE,GAAG,IAAI,EAAE,EAAE,SAAS,GAAG,KAAK,CAAC,GAAG,CAAC,OAAO,CAwD1F"}
1
+ {"version":3,"file":"Icon.d.ts","sourceRoot":"","sources":["../../src/components/Icon.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,EAAE,KAAK,QAAQ,EAAE,MAAM,OAAO,CAAA;AAE5C,OAAO,EAAW,KAAK,QAAQ,EAAE,MAAM,qBAAqB,CAAA;AAE5D;;;;;;;GAOG;AACH,MAAM,WAAW,SAAU,SAAQ,IAAI,CACrC,QAAQ,CAAC,aAAa,CAAC,EACvB,OAAO,GAAG,QAAQ,GAAG,SAAS,GAAG,MAAM,CACxC;IACC;;;;;OAKG;IACH,IAAI,EAAE,QAAQ,CAAA;IACd,sEAAsE;IACtE,IAAI,CAAC,EAAE,MAAM,CAAA;IACb,gDAAgD;IAChD,SAAS,CAAC,EAAE,MAAM,CAAA;CACnB;AAED;;;;;;;;;;;;;;;;;;;;;;GAsBG;AACH,wBAAgB,IAAI,CAAC,EAAE,IAAI,EAAE,IAAS,EAAE,SAAS,EAAE,GAAG,IAAI,EAAE,EAAE,SAAS,GAAG,KAAK,CAAC,GAAG,CAAC,OAAO,CAiF1F"}
@@ -24,7 +24,18 @@ import { getIcon } from '@molecule/app-icons';
24
24
  * @returns An `<svg>` element rendering the named glyph.
25
25
  */
26
26
  export function Icon({ name, size = 20, className, ...rest }) {
27
- const icon = getIcon(name);
27
+ // Graceful degradation: an icon name missing from the bonded set (icon-set
28
+ // version skew, a custom-set with a smaller catalog) must NOT crash the
29
+ // whole app — multi-vendor-marketplace shipped a dead app over this. Warn
30
+ // once per name and render a neutral placeholder circle instead.
31
+ let icon;
32
+ try {
33
+ icon = getIcon(name);
34
+ }
35
+ catch (error) {
36
+ console.warn(`Icon: no bonded glyph for "${name}" — rendering a placeholder.`, error);
37
+ return (_jsx("svg", { width: size, height: size, viewBox: "0 0 20 20", fill: "none", className: className, "aria-hidden": rest['aria-label'] == null && rest['aria-labelledby'] == null && rest.role == null, focusable: "false", ...rest, children: _jsx("circle", { cx: "10", cy: "10", r: "8", fill: "currentColor", opacity: "0.15" }) }));
38
+ }
28
39
  const viewBox = icon.viewBox || '0 0 20 20';
29
40
  // Placed BEFORE {...rest} so an explicit caller aria-hidden/role wins.
30
41
  const decorativeProps = rest['aria-label'] == null && rest['aria-labelledby'] == null && rest.role == null
@@ -1 +1 @@
1
- {"version":3,"file":"Icon.js","sourceRoot":"","sources":["../../src/components/Icon.tsx"],"names":[],"mappings":";AAEA,OAAO,EAAE,OAAO,EAAiB,MAAM,qBAAqB,CAAA;AA2B5D;;;;;;;;;;;;;;;;;;;;;;GAsBG;AACH,MAAM,UAAU,IAAI,CAAC,EAAE,IAAI,EAAE,IAAI,GAAG,EAAE,EAAE,SAAS,EAAE,GAAG,IAAI,EAAa;IACrE,MAAM,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,CAAA;IAC1B,MAAM,OAAO,GAAG,IAAI,CAAC,OAAO,IAAI,WAAW,CAAA;IAC3C,uEAAuE;IACvE,MAAM,eAAe,GACnB,IAAI,CAAC,YAAY,CAAC,IAAI,IAAI,IAAI,IAAI,CAAC,iBAAiB,CAAC,IAAI,IAAI,IAAI,IAAI,CAAC,IAAI,IAAI,IAAI;QAChF,CAAC,CAAE,EAAE,aAAa,EAAE,IAAI,EAAE,SAAS,EAAE,OAAO,EAAY;QACxD,CAAC,CAAC,EAAE,CAAA;IAER,IAAI,IAAI,CAAC,GAAG,EAAE,CAAC;QACb,OAAO,CACL,cACE,KAAK,EAAE,IAAI,EACX,MAAM,EAAE,IAAI,EACZ,OAAO,EAAE,OAAO,EAChB,IAAI,EAAE,IAAI,CAAC,IAAI,IAAI,MAAM,EACzB,SAAS,EAAE,SAAS,EACpB,uBAAuB,EAAE,EAAE,MAAM,EAAE,IAAI,CAAC,GAAG,EAAE,KACzC,eAAe,KACf,IAAI,GACR,CACH,CAAA;IACH,CAAC;IAED,MAAM,SAAS,GAAG,CAAC,CAAC,IAAI,CAAC,MAAM,CAAA;IAE/B,OAAO,CACL,cACE,KAAK,EAAE,IAAI,EACX,MAAM,EAAE,IAAI,EACZ,OAAO,EAAE,OAAO,EAChB,IAAI,EAAE,SAAS,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,IAAI,CAAC,IAAI,IAAI,cAAc,KAClD,eAAe,KACf,CAAC,SAAS;YACZ,CAAC,CAAC;gBACE,MAAM,EAAE,IAAI,CAAC,MAAM;gBACnB,WAAW,EAAE,IAAI,CAAC,WAAW;gBAC7B,aAAa,EAAE,IAAI,CAAC,aAAa;gBACjC,cAAc,EAAE,IAAI,CAAC,cAAc;aACpC;YACH,CAAC,CAAC,EAAE,CAAC,EACP,SAAS,EAAE,SAAS,KAChB,IAAI,YAEP,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC,CACxB,eAEE,CAAC,EAAE,CAAC,CAAC,CAAC,KACF,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,IAAI,EAAE,CAAC,CAAC,IAAI,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC,KAChC,CAAC,CAAC,CAAC,QAAQ,CAAC,CAAC,CAAC,EAAE,QAAQ,EAAE,CAAC,CAAC,QAAQ,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC,KAC5C,CAAC,CAAC,CAAC,QAAQ,CAAC,CAAC,CAAC,EAAE,QAAQ,EAAE,CAAC,CAAC,QAAQ,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC,KAC5C,CAAC,CAAC,CAAC,OAAO,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,OAAO,EAAE,CAAC,CAAC,OAAO,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC,IALtD,CAAC,CAMN,CACH,CAAC,GACE,CACP,CAAA;AACH,CAAC"}
1
+ {"version":3,"file":"Icon.js","sourceRoot":"","sources":["../../src/components/Icon.tsx"],"names":[],"mappings":"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"}
@@ -0,0 +1,71 @@
1
+ /**
2
+ * LoadErrorBanner component — the shared "load failed" banner with Retry.
3
+ *
4
+ * The fleet hand-rolled this in dozens of apps (an error line + a Retry
5
+ * button, each drifting in wording, styling, and pending behavior). Use it
6
+ * wherever a page/section fetch FAILS — it pairs with `Skeleton` for the
7
+ * loading state and `EmptyState` for the empty one. For form-submit errors
8
+ * use `<Alert status="error">` instead; this banner is specifically the
9
+ * load-failure-with-retry pattern, and its Retry button owns the
10
+ * pending/disabled state so the caller cannot double-fire it.
11
+ *
12
+ * @example
13
+ * ```tsx
14
+ * import { LoadErrorBanner } from '@molecule/app-ui-react'
15
+ *
16
+ * {error ? (
17
+ * <LoadErrorBanner
18
+ * message={t('decks.loadFailed', {}, { defaultValue: 'Could not load decks.' })}
19
+ * onRetry={load} // return the promise — pending state is automatic
20
+ * />
21
+ * ) : (
22
+ * <DeckList decks={decks} />
23
+ * )}
24
+ * ```
25
+ *
26
+ * @remarks
27
+ * Pass `onRetry` as a promise-returning function; while it is pending the
28
+ * retry button is disabled and shows a localized "Retrying…" label, and a
29
+ * rejection is NOT swallowed silently — the banner stays (callers render
30
+ * persistent failure by keeping `error` set, which keeps this banner
31
+ * mounted). Omit `onRetry` and no retry button renders (display-only
32
+ * failure). `data-mol-id` on the ROOT goes through rest props (fleet
33
+ * locator convention); the retry button carries `retryMolId`
34
+ * (default `load-error-retry`) so e2e can click it without knowing the
35
+ * page. Announced politely (`role="status"` via the inner Alert's
36
+ * `live={false}`) — a load failure on initial render has nothing to
37
+ * interrupt.
38
+ *
39
+ * @module
40
+ */
41
+ import type { JSX } from 'react';
42
+ import React from 'react';
43
+ /**
44
+ * Props for {@link LoadErrorBanner}. Rest props are forwarded to the root
45
+ * `<div>`, so they are typed as its attributes.
46
+ */
47
+ export interface LoadErrorBannerProps extends Omit<React.HTMLAttributes<HTMLDivElement>, 'children'> {
48
+ /** The primary error line (e.g. the localized "Could not load X."). */
49
+ message: React.ReactNode;
50
+ /** Secondary line under the message (status code, hint). */
51
+ detail?: React.ReactNode;
52
+ /**
53
+ * Called when Retry is clicked. Return the promise; while pending, the
54
+ * button is disabled and shows "Retrying…". Omit for a display-only
55
+ * banner.
56
+ */
57
+ onRetry?: () => void | Promise<void>;
58
+ /** Retry-button label. Falls back to a localized "Retry". */
59
+ retryLabel?: React.ReactNode;
60
+ /** `data-mol-id` for the retry button (default `load-error-retry`). */
61
+ retryMolId?: string;
62
+ /** Extra content between message and retry row (e.g. an error code). */
63
+ children?: React.ReactNode;
64
+ }
65
+ /**
66
+ * Error banner with an owned pending-state Retry button.
67
+ *
68
+ * @param props - Component props (see {@link LoadErrorBannerProps}).
69
+ */
70
+ export declare function LoadErrorBanner({ message, detail, onRetry, retryLabel, retryMolId, children, ...rest }: LoadErrorBannerProps): JSX.Element;
71
+ //# sourceMappingURL=LoadErrorBanner.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"LoadErrorBanner.d.ts","sourceRoot":"","sources":["../../src/components/LoadErrorBanner.tsx"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAuCG;AAEH,OAAO,KAAK,EAAE,GAAG,EAAE,MAAM,OAAO,CAAA;AAChC,OAAO,KAAmB,MAAM,OAAO,CAAA;AAQvC;;;GAGG;AACH,MAAM,WAAW,oBAAqB,SAAQ,IAAI,CAChD,KAAK,CAAC,cAAc,CAAC,cAAc,CAAC,EACpC,UAAU,CACX;IACC,uEAAuE;IACvE,OAAO,EAAE,KAAK,CAAC,SAAS,CAAA;IACxB,4DAA4D;IAC5D,MAAM,CAAC,EAAE,KAAK,CAAC,SAAS,CAAA;IACxB;;;;OAIG;IACH,OAAO,CAAC,EAAE,MAAM,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,CAAA;IACpC,6DAA6D;IAC7D,UAAU,CAAC,EAAE,KAAK,CAAC,SAAS,CAAA;IAC5B,uEAAuE;IACvE,UAAU,CAAC,EAAE,MAAM,CAAA;IACnB,wEAAwE;IACxE,QAAQ,CAAC,EAAE,KAAK,CAAC,SAAS,CAAA;CAC3B;AAED;;;;GAIG;AACH,wBAAgB,eAAe,CAAC,EAC9B,OAAO,EACP,MAAM,EACN,OAAO,EACP,UAAU,EACV,UAA+B,EAC/B,QAAQ,EACR,GAAG,IAAI,EACR,EAAE,oBAAoB,GAAG,GAAG,CAAC,OAAO,CA4CpC"}
@@ -0,0 +1,31 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { useState } from 'react';
3
+ import { useTranslation } from '@molecule/app-react';
4
+ import { getClassMap } from '@molecule/app-ui';
5
+ import { Alert } from './Alert.js';
6
+ import { Button } from './Button.js';
7
+ /**
8
+ * Error banner with an owned pending-state Retry button.
9
+ *
10
+ * @param props - Component props (see {@link LoadErrorBannerProps}).
11
+ */
12
+ export function LoadErrorBanner({ message, detail, onRetry, retryLabel, retryMolId = 'load-error-retry', children, ...rest }) {
13
+ const cm = getClassMap();
14
+ const { t } = useTranslation();
15
+ const [pending, setPending] = useState(false);
16
+ const handleRetry = () => {
17
+ if (!onRetry || pending)
18
+ return;
19
+ setPending(true);
20
+ // A rejected retry means "still failing": the caller owns that error and
21
+ // keeps showing the banner, so ignoring it here only stops it becoming an
22
+ // unhandled rejection.
23
+ void Promise.resolve(onRetry())
24
+ .catch((_error) => undefined)
25
+ .finally(() => setPending(false));
26
+ };
27
+ return (_jsx("div", { ...rest, children: _jsx(Alert, { status: "error", live: false, children: _jsxs("div", { className: cm.stack(2), children: [_jsx("p", { className: cm.textSize('sm'), children: message }), detail ? (_jsx("p", { className: cm.cn(cm.textSize('xs'), 'text-on-surface-variant'), children: detail })) : null, children, onRetry ? (_jsx("div", { className: cm.flex({ justify: 'start' }), children: _jsx(Button, { variant: "outline", size: "sm", onClick: handleRetry, disabled: pending, "data-mol-id": retryMolId, children: pending
28
+ ? t('loadError.retrying', {}, { defaultValue: 'Retrying…' })
29
+ : (retryLabel ?? t('loadError.retry', {}, { defaultValue: 'Retry' })) }) })) : null] }) }) }));
30
+ }
31
+ //# sourceMappingURL=LoadErrorBanner.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"LoadErrorBanner.js","sourceRoot":"","sources":["../../src/components/LoadErrorBanner.tsx"],"names":[],"mappings":";AA0CA,OAAc,EAAE,QAAQ,EAAE,MAAM,OAAO,CAAA;AAEvC,OAAO,EAAE,cAAc,EAAE,MAAM,qBAAqB,CAAA;AACpD,OAAO,EAAE,WAAW,EAAE,MAAM,kBAAkB,CAAA;AAE9C,OAAO,EAAE,KAAK,EAAE,MAAM,YAAY,CAAA;AAClC,OAAO,EAAE,MAAM,EAAE,MAAM,aAAa,CAAA;AA4BpC;;;;GAIG;AACH,MAAM,UAAU,eAAe,CAAC,EAC9B,OAAO,EACP,MAAM,EACN,OAAO,EACP,UAAU,EACV,UAAU,GAAG,kBAAkB,EAC/B,QAAQ,EACR,GAAG,IAAI,EACc;IACrB,MAAM,EAAE,GAAG,WAAW,EAAE,CAAA;IACxB,MAAM,EAAE,CAAC,EAAE,GAAG,cAAc,EAAE,CAAA;IAC9B,MAAM,CAAC,OAAO,EAAE,UAAU,CAAC,GAAG,QAAQ,CAAC,KAAK,CAAC,CAAA;IAE7C,MAAM,WAAW,GAAG,GAAS,EAAE;QAC7B,IAAI,CAAC,OAAO,IAAI,OAAO;YAAE,OAAM;QAC/B,UAAU,CAAC,IAAI,CAAC,CAAA;QAChB,yEAAyE;QACzE,0EAA0E;QAC1E,uBAAuB;QACvB,KAAK,OAAO,CAAC,OAAO,CAAC,OAAO,EAAE,CAAC;aAC5B,KAAK,CAAC,CAAC,MAAe,EAAE,EAAE,CAAC,SAAS,CAAC;aACrC,OAAO,CAAC,GAAG,EAAE,CAAC,UAAU,CAAC,KAAK,CAAC,CAAC,CAAA;IACrC,CAAC,CAAA;IAED,OAAO,CACL,iBAAS,IAAI,YACX,KAAC,KAAK,IAAC,MAAM,EAAC,OAAO,EAAC,IAAI,EAAE,KAAK,YAC/B,eAAK,SAAS,EAAE,EAAE,CAAC,KAAK,CAAC,CAAC,CAAC,aACzB,YAAG,SAAS,EAAE,EAAE,CAAC,QAAQ,CAAC,IAAI,CAAC,YAAG,OAAO,GAAK,EAC7C,MAAM,CAAC,CAAC,CAAC,CACR,YAAG,SAAS,EAAE,EAAE,CAAC,EAAE,CAAC,EAAE,CAAC,QAAQ,CAAC,IAAI,CAAC,EAAE,yBAAyB,CAAC,YAAG,MAAM,GAAK,CAChF,CAAC,CAAC,CAAC,IAAI,EACP,QAAQ,EACR,OAAO,CAAC,CAAC,CAAC,CACT,cAAK,SAAS,EAAE,EAAE,CAAC,IAAI,CAAC,EAAE,OAAO,EAAE,OAAO,EAAE,CAAC,YAC3C,KAAC,MAAM,IACL,OAAO,EAAC,SAAS,EACjB,IAAI,EAAC,IAAI,EACT,OAAO,EAAE,WAAW,EACpB,QAAQ,EAAE,OAAO,iBACJ,UAAU,YAEtB,OAAO;gCACN,CAAC,CAAC,CAAC,CAAC,oBAAoB,EAAE,EAAE,EAAE,EAAE,YAAY,EAAE,WAAW,EAAE,CAAC;gCAC5D,CAAC,CAAC,CAAC,UAAU,IAAI,CAAC,CAAC,iBAAiB,EAAE,EAAE,EAAE,EAAE,YAAY,EAAE,OAAO,EAAE,CAAC,CAAC,GAChE,GACL,CACP,CAAC,CAAC,CAAC,IAAI,IACJ,GACA,GACJ,CACP,CAAA;AACH,CAAC"}
@@ -0,0 +1,91 @@
1
+ /**
2
+ * PromptDialog component — the shared "ask for a short string" modal.
3
+ *
4
+ * The shared home for what `window.prompt` used to do (renaming, renaming a
5
+ * board, creating a tag, "invite by email"). `window.prompt` is broken
6
+ * headless (it hangs e2e) and awkward on touch; this renders the same
7
+ * interaction as a real dialog built on {@link Modal}. For a confirm WITHOUT
8
+ * an input use {@link ConfirmDialog}; for an INLINE two-click arm/commit
9
+ * button use {@link ConfirmButton}.
10
+ *
11
+ * @example
12
+ * ```tsx
13
+ * import { PromptDialog } from '@molecule/app-ui-react'
14
+ *
15
+ * <PromptDialog
16
+ * open={renameOpen}
17
+ * onClose={() => setRenameOpen(false)}
18
+ * title={t('board.renameTitle', {}, { defaultValue: 'Rename board' })}
19
+ * placeholder={t('board.namePlaceholder', {}, { defaultValue: 'Board name' })}
20
+ * initialValue={board.name}
21
+ * confirmLabel={t('common.save', {}, { defaultValue: 'Save' })}
22
+ * onSubmit={async (name) => {
23
+ * await renameBoard(board.id, name) // resolve → dialog closes
24
+ * }}
25
+ * />
26
+ * ```
27
+ *
28
+ * @remarks
29
+ * `onSubmit` receives the RAW entered value (no trimming — the caller owns
30
+ * validation). Resolve it and the dialog closes and resets; reject or throw
31
+ * and the dialog STAYS OPEN with the value intact — render your error
32
+ * message through `children` (e.g. an `<Alert status="error">`), since the
33
+ * rejection itself is deliberately swallowed here. While the promise is
34
+ * pending the input and both buttons are disabled, so the action cannot
35
+ * double-fire. Enter submits; Escape and the backdrop cancel exactly like a
36
+ * Modal. Optional `inputMolId` / `confirmMolId` / `cancelMolId` render as
37
+ * `data-mol-id` attributes for e2e locators; the buttons are also
38
+ * text-labeled so `getByRole('button', { name })` reaches them.
39
+ *
40
+ * WIZARD GOTCHA (learned the hard way, fleet migration 2026-09-24): a
41
+ * fulfilled submit calls `onClose()` — "close", not "cancel". A multi-stage
42
+ * flow that advances its stage INSIDE `onSubmit` gets clobbered if its
43
+ * `onClose` unconditionally resets to idle (the auto-close fires right
44
+ * after the stage advance). Make `onClose` stage-aware via functional
45
+ * state (`setStage((cur) => (cur === 'name' ? 'idle' : cur))`) so cancel
46
+ * still resets but the post-submit auto-close doesn't undo the advance.
47
+ *
48
+ * @module
49
+ */
50
+ import React, { type JSX } from 'react';
51
+ /** Props for {@link PromptDialog}. */
52
+ export interface PromptDialogProps {
53
+ /** Whether the dialog is open. */
54
+ open: boolean;
55
+ /** Called when the dialog should close (escape, backdrop, cancel, or a fulfilled {@link PromptDialogProps.onSubmit}). */
56
+ onClose: () => void;
57
+ /** Dialog heading. */
58
+ title: React.ReactNode;
59
+ /** Explanatory line under the title. */
60
+ description?: React.ReactNode;
61
+ /** Input placeholder. */
62
+ placeholder?: string;
63
+ /** Value the input starts from every time the dialog opens. */
64
+ initialValue?: string;
65
+ /**
66
+ * Called with the entered value on confirm. Resolve to close the dialog;
67
+ * reject or throw to keep it open (show the error via {@link PromptDialogProps.children}).
68
+ */
69
+ onSubmit: (value: string) => void | Promise<void>;
70
+ /** Confirm-button label. Falls back to a localized "Confirm". */
71
+ confirmLabel?: React.ReactNode;
72
+ /** Cancel-button label. Falls back to a localized "Cancel". */
73
+ cancelLabel?: React.ReactNode;
74
+ /** Render the confirm button in the error color (e.g. an irreversible reset). */
75
+ destructive?: boolean;
76
+ /** `data-mol-id` for the text input. */
77
+ inputMolId?: string;
78
+ /** `data-mol-id` for the confirm button. */
79
+ confirmMolId?: string;
80
+ /** `data-mol-id` for the cancel button. */
81
+ cancelMolId?: string;
82
+ /** Extra body content under the input (error banners, hints). */
83
+ children?: React.ReactNode;
84
+ }
85
+ /**
86
+ * Modal that asks the user for a short string, replacing `window.prompt`.
87
+ *
88
+ * @param props - Component props (see {@link PromptDialogProps}).
89
+ */
90
+ export declare function PromptDialog({ open, onClose, title, description, placeholder, initialValue, onSubmit, confirmLabel, cancelLabel, destructive, inputMolId, confirmMolId, cancelMolId, children, }: PromptDialogProps): JSX.Element;
91
+ //# sourceMappingURL=PromptDialog.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"PromptDialog.d.ts","sourceRoot":"","sources":["../../src/components/PromptDialog.tsx"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAgDG;AAEH,OAAO,KAAK,EAAE,EAAE,KAAK,GAAG,EAAuB,MAAM,OAAO,CAAA;AAS5D,sCAAsC;AACtC,MAAM,WAAW,iBAAiB;IAChC,kCAAkC;IAClC,IAAI,EAAE,OAAO,CAAA;IACb,yHAAyH;IACzH,OAAO,EAAE,MAAM,IAAI,CAAA;IACnB,sBAAsB;IACtB,KAAK,EAAE,KAAK,CAAC,SAAS,CAAA;IACtB,wCAAwC;IACxC,WAAW,CAAC,EAAE,KAAK,CAAC,SAAS,CAAA;IAC7B,yBAAyB;IACzB,WAAW,CAAC,EAAE,MAAM,CAAA;IACpB,+DAA+D;IAC/D,YAAY,CAAC,EAAE,MAAM,CAAA;IACrB;;;OAGG;IACH,QAAQ,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,CAAA;IACjD,iEAAiE;IACjE,YAAY,CAAC,EAAE,KAAK,CAAC,SAAS,CAAA;IAC9B,+DAA+D;IAC/D,WAAW,CAAC,EAAE,KAAK,CAAC,SAAS,CAAA;IAC7B,iFAAiF;IACjF,WAAW,CAAC,EAAE,OAAO,CAAA;IACrB,wCAAwC;IACxC,UAAU,CAAC,EAAE,MAAM,CAAA;IACnB,4CAA4C;IAC5C,YAAY,CAAC,EAAE,MAAM,CAAA;IACrB,2CAA2C;IAC3C,WAAW,CAAC,EAAE,MAAM,CAAA;IACpB,iEAAiE;IACjE,QAAQ,CAAC,EAAE,KAAK,CAAC,SAAS,CAAA;CAC3B;AAED;;;;GAIG;AACH,wBAAgB,YAAY,CAAC,EAC3B,IAAI,EACJ,OAAO,EACP,KAAK,EACL,WAAW,EACX,WAAW,EACX,YAAiB,EACjB,QAAQ,EACR,YAAY,EACZ,WAAW,EACX,WAAmB,EACnB,UAAU,EACV,YAAY,EACZ,WAAW,EACX,QAAQ,GACT,EAAE,iBAAiB,GAAG,GAAG,CAAC,OAAO,CAoEjC"}
@@ -0,0 +1,90 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ /**
3
+ * PromptDialog component — the shared "ask for a short string" modal.
4
+ *
5
+ * The shared home for what `window.prompt` used to do (renaming, renaming a
6
+ * board, creating a tag, "invite by email"). `window.prompt` is broken
7
+ * headless (it hangs e2e) and awkward on touch; this renders the same
8
+ * interaction as a real dialog built on {@link Modal}. For a confirm WITHOUT
9
+ * an input use {@link ConfirmDialog}; for an INLINE two-click arm/commit
10
+ * button use {@link ConfirmButton}.
11
+ *
12
+ * @example
13
+ * ```tsx
14
+ * import { PromptDialog } from '@molecule/app-ui-react'
15
+ *
16
+ * <PromptDialog
17
+ * open={renameOpen}
18
+ * onClose={() => setRenameOpen(false)}
19
+ * title={t('board.renameTitle', {}, { defaultValue: 'Rename board' })}
20
+ * placeholder={t('board.namePlaceholder', {}, { defaultValue: 'Board name' })}
21
+ * initialValue={board.name}
22
+ * confirmLabel={t('common.save', {}, { defaultValue: 'Save' })}
23
+ * onSubmit={async (name) => {
24
+ * await renameBoard(board.id, name) // resolve → dialog closes
25
+ * }}
26
+ * />
27
+ * ```
28
+ *
29
+ * @remarks
30
+ * `onSubmit` receives the RAW entered value (no trimming — the caller owns
31
+ * validation). Resolve it and the dialog closes and resets; reject or throw
32
+ * and the dialog STAYS OPEN with the value intact — render your error
33
+ * message through `children` (e.g. an `<Alert status="error">`), since the
34
+ * rejection itself is deliberately swallowed here. While the promise is
35
+ * pending the input and both buttons are disabled, so the action cannot
36
+ * double-fire. Enter submits; Escape and the backdrop cancel exactly like a
37
+ * Modal. Optional `inputMolId` / `confirmMolId` / `cancelMolId` render as
38
+ * `data-mol-id` attributes for e2e locators; the buttons are also
39
+ * text-labeled so `getByRole('button', { name })` reaches them.
40
+ *
41
+ * WIZARD GOTCHA (learned the hard way, fleet migration 2026-09-24): a
42
+ * fulfilled submit calls `onClose()` — "close", not "cancel". A multi-stage
43
+ * flow that advances its stage INSIDE `onSubmit` gets clobbered if its
44
+ * `onClose` unconditionally resets to idle (the auto-close fires right
45
+ * after the stage advance). Make `onClose` stage-aware via functional
46
+ * state (`setStage((cur) => (cur === 'name' ? 'idle' : cur))`) so cancel
47
+ * still resets but the post-submit auto-close doesn't undo the advance.
48
+ *
49
+ * @module
50
+ */
51
+ import { useEffect, useState } from 'react';
52
+ import { useTranslation } from '@molecule/app-react';
53
+ import { getClassMap } from '@molecule/app-ui';
54
+ import { Button } from './Button.js';
55
+ import { Input } from './Input.js';
56
+ import { Modal } from './Modal.js';
57
+ /**
58
+ * Modal that asks the user for a short string, replacing `window.prompt`.
59
+ *
60
+ * @param props - Component props (see {@link PromptDialogProps}).
61
+ */
62
+ export function PromptDialog({ open, onClose, title, description, placeholder, initialValue = '', onSubmit, confirmLabel, cancelLabel, destructive = false, inputMolId, confirmMolId, cancelMolId, children, }) {
63
+ const cm = getClassMap();
64
+ const { t } = useTranslation();
65
+ const [value, setValue] = useState(initialValue);
66
+ const [pending, setPending] = useState(false);
67
+ // Re-seed from initialValue each open so a reopened dialog shows the
68
+ // CURRENT value, not whatever the user typed last time.
69
+ useEffect(() => {
70
+ if (open) {
71
+ setValue(initialValue);
72
+ setPending(false);
73
+ }
74
+ }, [open, initialValue]);
75
+ const handleSubmit = (e) => {
76
+ e.preventDefault();
77
+ if (pending)
78
+ return;
79
+ setPending(true);
80
+ void Promise.resolve(onSubmit(value))
81
+ .then(() => onClose())
82
+ .catch(() => {
83
+ // Deliberate: a rejection keeps the dialog open (see @remarks);
84
+ // the caller renders the error through `children`.
85
+ setPending(false);
86
+ });
87
+ };
88
+ return (_jsx(Modal, { open: open, onClose: pending ? () => { } : onClose, children: _jsx("form", { onSubmit: handleSubmit, children: _jsxs("div", { className: cm.stack(3), children: [_jsx("h2", { className: cm.cn(cm.textSize('lg'), cm.fontWeight('bold')), children: title }), description ? _jsx("p", { className: cm.textSize('sm'), children: description }) : null, _jsx(Input, { value: value, onChange: (e) => setValue(e.target.value), placeholder: placeholder, "aria-label": typeof title === 'string' ? title : placeholder, disabled: pending, autoFocus: true, "data-mol-id": inputMolId }), children, _jsxs("div", { className: cm.flex({ justify: 'end', gap: 'sm' }), children: [_jsx(Button, { type: "button", variant: "ghost", onClick: onClose, disabled: pending, "data-mol-id": cancelMolId, children: cancelLabel ?? t('prompt.cancel', {}, { defaultValue: 'Cancel' }) }), _jsx(Button, { type: "submit", variant: "solid", color: destructive ? 'error' : 'primary', disabled: pending, "data-mol-id": confirmMolId, children: confirmLabel ?? t('prompt.confirm', {}, { defaultValue: 'Confirm' }) })] })] }) }) }));
89
+ }
90
+ //# sourceMappingURL=PromptDialog.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"PromptDialog.js","sourceRoot":"","sources":["../../src/components/PromptDialog.tsx"],"names":[],"mappings":"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"}