@ttt-productions/ui-core 0.11.17 → 0.11.19

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 (55) hide show
  1. package/dist/react/components/consequence-dialog.d.ts.map +1 -1
  2. package/dist/react/components/consequence-dialog.js +7 -2
  3. package/dist/react/components/consequence-dialog.js.map +1 -1
  4. package/package.json +2 -4
  5. package/src/index.ts +0 -3
  6. package/src/lib/format-numbers.ts +0 -17
  7. package/src/lib/utils.ts +0 -9
  8. package/src/react/components/accordion.tsx +0 -53
  9. package/src/react/components/alert-dialog.tsx +0 -131
  10. package/src/react/components/alert.tsx +0 -59
  11. package/src/react/components/avatar.tsx +0 -51
  12. package/src/react/components/badge.tsx +0 -31
  13. package/src/react/components/button.tsx +0 -102
  14. package/src/react/components/card.tsx +0 -54
  15. package/src/react/components/checkbox.tsx +0 -28
  16. package/src/react/components/chunk-error-recovery.tsx +0 -57
  17. package/src/react/components/collapsible.tsx +0 -11
  18. package/src/react/components/consequence-dialog.tsx +0 -274
  19. package/src/react/components/date-picker.tsx +0 -567
  20. package/src/react/components/dialog.tsx +0 -114
  21. package/src/react/components/dropdown-menu.tsx +0 -200
  22. package/src/react/components/end-of-list-indicator.tsx +0 -27
  23. package/src/react/components/form.tsx +0 -174
  24. package/src/react/components/input.tsx +0 -23
  25. package/src/react/components/label.tsx +0 -21
  26. package/src/react/components/layout/screen-adaptive-view.tsx +0 -106
  27. package/src/react/components/list-pagination.tsx +0 -101
  28. package/src/react/components/menubar.tsx +0 -220
  29. package/src/react/components/popover.tsx +0 -31
  30. package/src/react/components/progress.tsx +0 -27
  31. package/src/react/components/radio-group.tsx +0 -36
  32. package/src/react/components/relative-time.tsx +0 -56
  33. package/src/react/components/scroll-area.tsx +0 -56
  34. package/src/react/components/scroll-to-top-button.tsx +0 -85
  35. package/src/react/components/search-dropdown.tsx +0 -233
  36. package/src/react/components/select.tsx +0 -165
  37. package/src/react/components/separator.tsx +0 -26
  38. package/src/react/components/sheet.tsx +0 -140
  39. package/src/react/components/skeleton.tsx +0 -16
  40. package/src/react/components/slider.tsx +0 -25
  41. package/src/react/components/spinner.tsx +0 -49
  42. package/src/react/components/switch.tsx +0 -41
  43. package/src/react/components/table.tsx +0 -121
  44. package/src/react/components/tabs.tsx +0 -59
  45. package/src/react/components/textarea.tsx +0 -22
  46. package/src/react/components/toast.tsx +0 -193
  47. package/src/react/components/toaster.tsx +0 -43
  48. package/src/react/components/tooltip.tsx +0 -28
  49. package/src/react/hooks/use-async-action.ts +0 -63
  50. package/src/react/hooks/use-cursor-page.ts +0 -139
  51. package/src/react/hooks/use-media-query.tsx +0 -26
  52. package/src/react/hooks/use-paged-list.ts +0 -106
  53. package/src/react/hooks/use-return-scroll.ts +0 -149
  54. package/src/react/hooks/use-toast.tsx +0 -176
  55. package/src/react/index.ts +0 -85
@@ -1 +1 @@
1
- {"version":3,"file":"consequence-dialog.d.ts","sourceRoot":"","sources":["../../../src/react/components/consequence-dialog.tsx"],"names":[],"mappings":"AAkBA;;;;;GAKG;AACH,MAAM,WAAW,uBAAuB;IACtC,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,KAAK,CAAC,EAAE,KAAK,CAAC,SAAS,CAAC;IACxB,KAAK,EAAE,MAAM,CAAC;IACd,QAAQ,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,IAAI,CAAC;IAClC,IAAI,CAAC,EAAE,MAAM,CAAC;CACf;AAED;;;GAGG;AACH,MAAM,WAAW,kCAAkC;IACjD,MAAM,EAAE,MAAM,CAAC;IACf,KAAK,CAAC,EAAE,KAAK,CAAC,SAAS,CAAC;CACzB;AAED,MAAM,WAAW,sBAAsB;IACrC,uFAAuF;IACvF,OAAO,CAAC,EAAE,KAAK,CAAC,SAAS,CAAC;IAC1B,0DAA0D;IAC1D,IAAI,CAAC,EAAE,OAAO,CAAC;IACf,YAAY,CAAC,EAAE,CAAC,IAAI,EAAE,OAAO,KAAK,IAAI,CAAC;IAEvC,6EAA6E;IAC7E,KAAK,EAAE,KAAK,CAAC,SAAS,CAAC;IAEvB,oFAAoF;IACpF,eAAe,CAAC,EAAE,KAAK,CAAC,SAAS,CAAC;IAClC,wFAAwF;IACxF,aAAa,CAAC,EAAE,KAAK,CAAC,SAAS,CAAC;IAChC,6DAA6D;IAC7D,aAAa,CAAC,EAAE,KAAK,CAAC,SAAS,CAAC;IAEhC,8CAA8C;IAC9C,MAAM,CAAC,EAAE,uBAAuB,CAAC;IACjC,sDAAsD;IACtD,iBAAiB,CAAC,EAAE,kCAAkC,CAAC;IAEvD,YAAY,EAAE,KAAK,CAAC,SAAS,CAAC;IAC9B,WAAW,CAAC,EAAE,KAAK,CAAC,SAAS,CAAC;IAC9B,8DAA8D;IAC9D,WAAW,CAAC,EAAE,OAAO,CAAC;IAEtB;;;;;;OAMG;IACH,SAAS,EAAE,MAAM,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;CACvC;AA0BD;;;;;;;;;;GAUG;AACH,wBAAgB,iBAAiB,CAAC,EAChC,OAAO,EACP,IAAI,EACJ,YAAY,EACZ,KAAK,EACL,eAAe,EACf,aAAa,EACb,aAAa,EACb,MAAM,EACN,iBAAiB,EACjB,YAAY,EACZ,WAAsB,EACtB,WAAmB,EACnB,SAAS,GACV,EAAE,sBAAsB,+BA+IxB"}
1
+ {"version":3,"file":"consequence-dialog.d.ts","sourceRoot":"","sources":["../../../src/react/components/consequence-dialog.tsx"],"names":[],"mappings":"AAkBA;;;;;GAKG;AACH,MAAM,WAAW,uBAAuB;IACtC,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,KAAK,CAAC,EAAE,KAAK,CAAC,SAAS,CAAC;IACxB,KAAK,EAAE,MAAM,CAAC;IACd,QAAQ,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,IAAI,CAAC;IAClC,IAAI,CAAC,EAAE,MAAM,CAAC;CACf;AAED;;;GAGG;AACH,MAAM,WAAW,kCAAkC;IACjD,MAAM,EAAE,MAAM,CAAC;IACf,KAAK,CAAC,EAAE,KAAK,CAAC,SAAS,CAAC;CACzB;AAED,MAAM,WAAW,sBAAsB;IACrC,uFAAuF;IACvF,OAAO,CAAC,EAAE,KAAK,CAAC,SAAS,CAAC;IAC1B,0DAA0D;IAC1D,IAAI,CAAC,EAAE,OAAO,CAAC;IACf,YAAY,CAAC,EAAE,CAAC,IAAI,EAAE,OAAO,KAAK,IAAI,CAAC;IAEvC,6EAA6E;IAC7E,KAAK,EAAE,KAAK,CAAC,SAAS,CAAC;IAEvB,oFAAoF;IACpF,eAAe,CAAC,EAAE,KAAK,CAAC,SAAS,CAAC;IAClC,wFAAwF;IACxF,aAAa,CAAC,EAAE,KAAK,CAAC,SAAS,CAAC;IAChC,6DAA6D;IAC7D,aAAa,CAAC,EAAE,KAAK,CAAC,SAAS,CAAC;IAEhC,8CAA8C;IAC9C,MAAM,CAAC,EAAE,uBAAuB,CAAC;IACjC,sDAAsD;IACtD,iBAAiB,CAAC,EAAE,kCAAkC,CAAC;IAEvD,YAAY,EAAE,KAAK,CAAC,SAAS,CAAC;IAC9B,WAAW,CAAC,EAAE,KAAK,CAAC,SAAS,CAAC;IAC9B,8DAA8D;IAC9D,WAAW,CAAC,EAAE,OAAO,CAAC;IAEtB;;;;;;OAMG;IACH,SAAS,EAAE,MAAM,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;CACvC;AA+BD;;;;;;;;;;GAUG;AACH,wBAAgB,iBAAiB,CAAC,EAChC,OAAO,EACP,IAAI,EACJ,YAAY,EACZ,KAAK,EACL,eAAe,EACf,aAAa,EACb,aAAa,EACb,MAAM,EACN,iBAAiB,EACjB,YAAY,EACZ,WAAsB,EACtB,WAAmB,EACnB,SAAS,GACV,EAAE,sBAAsB,+BAkJxB"}
@@ -8,8 +8,13 @@ import { Input } from './input.js';
8
8
  import { Label } from './label.js';
9
9
  import { Zap, Clock, RotateCcw } from 'lucide-react';
10
10
  const SLOT_ICON_CLASS = 'icon-xs mt-0.5 shrink-0 text-muted-foreground';
11
+ /**
12
+ * One consequence slot: the icon beside a block column whose label sits on its own line above
13
+ * the slot text. Every element is a block: the `stack-*` spacing is a vertical margin, which an
14
+ * inline element ignores.
15
+ */
11
16
  function ConsequenceRow({ icon: Icon, label, children, }) {
12
- return (_jsxs("span", { className: "flex items-start gap-2", children: [_jsx(Icon, { className: SLOT_ICON_CLASS, "aria-hidden": "true" }), _jsxs("span", { className: "stack-1 min-w-0", children: [_jsx("span", { className: "text-caption font-medium uppercase tracking-wide text-muted-foreground", children: label }), _jsx("span", { className: "text-small text-foreground", children: children })] })] }));
17
+ return (_jsxs("div", { className: "flex items-start gap-2", children: [_jsx(Icon, { className: SLOT_ICON_CLASS, "aria-hidden": "true" }), _jsxs("div", { className: "stack-1 min-w-0", children: [_jsx("div", { className: "text-caption font-medium uppercase tracking-wide text-muted-foreground", children: label }), _jsx("div", { className: "text-small text-foreground", children: children })] })] }));
13
18
  }
14
19
  /**
15
20
  * A reusable confirm-before-a-consequential-action dialog. Three optional, consistently-styled
@@ -74,6 +79,6 @@ export function ConsequenceDialog({ trigger, open, onOpenChange, title, immediat
74
79
  return (_jsxs(AlertDialog, { open: actualOpen, onOpenChange: handleOpenChange, children: [trigger ? _jsx(AlertDialogTrigger, { asChild: true, children: trigger }) : null, _jsxs(AlertDialogContent, { onEscapeKeyDown: (e) => {
75
80
  if (pending)
76
81
  e.preventDefault();
77
- }, children: [_jsxs(AlertDialogHeader, { children: [_jsx(AlertDialogTitle, { children: title }), _jsx(AlertDialogDescription, { asChild: true, children: _jsxs("span", { className: "stack-3", children: [immediateEffect ? (_jsx(ConsequenceRow, { icon: Zap, label: "Immediately", children: immediateEffect })) : null, delayedEffect ? (_jsx(ConsequenceRow, { icon: Clock, label: "Afterward", children: delayedEffect })) : null, reversibility ? (_jsx(ConsequenceRow, { icon: RotateCcw, label: "Reversibility", children: reversibility })) : null] }) })] }), reason ? (_jsxs("div", { className: "stack-2", children: [_jsx(Label, { htmlFor: reasonId, children: reason.label ?? 'Reason' }), _jsx(Textarea, { id: reasonId, value: reason.value, onChange: (e) => reason.onChange(e.target.value), placeholder: reason.placeholder, maxLength: reason.maxLength, rows: reason.rows ?? 3, disabled: pending })] })) : null, typedConfirmation ? (_jsxs("div", { className: "stack-2", children: [_jsx(Label, { htmlFor: typedId, children: typedConfirmation.label ?? (_jsxs(_Fragment, { children: ["Type ", _jsx("span", { className: "font-mono", children: typedConfirmation.phrase }), " to confirm"] })) }), _jsx(Input, { id: typedId, value: typedValue, onChange: (e) => setTypedValue(e.target.value), placeholder: typedConfirmation.phrase, disabled: pending, autoComplete: "off" })] })) : null, _jsxs(AlertDialogFooter, { children: [_jsx(Button, { variant: "outline", onClick: () => handleOpenChange(false), disabled: pending, children: cancelLabel }), _jsx(Button, { variant: destructive ? 'destructive' : 'default', onClick: handleConfirm, disabled: !reasonOk || !typedOk, pending: pending, children: confirmLabel })] })] })] }));
82
+ }, children: [_jsxs(AlertDialogHeader, { children: [_jsx(AlertDialogTitle, { children: title }), _jsx(AlertDialogDescription, { asChild: true, children: _jsxs("div", { className: "stack-3 text-left", children: [immediateEffect ? (_jsx(ConsequenceRow, { icon: Zap, label: "Immediately", children: immediateEffect })) : null, delayedEffect ? (_jsx(ConsequenceRow, { icon: Clock, label: "Afterward", children: delayedEffect })) : null, reversibility ? (_jsx(ConsequenceRow, { icon: RotateCcw, label: "Reversibility", children: reversibility })) : null] }) })] }), reason ? (_jsxs("div", { className: "stack-2", children: [_jsx(Label, { htmlFor: reasonId, children: reason.label ?? 'Reason' }), _jsx(Textarea, { id: reasonId, value: reason.value, onChange: (e) => reason.onChange(e.target.value), placeholder: reason.placeholder, maxLength: reason.maxLength, rows: reason.rows ?? 3, disabled: pending })] })) : null, typedConfirmation ? (_jsxs("div", { className: "stack-2", children: [_jsx(Label, { htmlFor: typedId, children: typedConfirmation.label ?? (_jsxs(_Fragment, { children: ["Type ", _jsx("span", { className: "font-mono", children: typedConfirmation.phrase }), " to confirm"] })) }), _jsx(Input, { id: typedId, value: typedValue, onChange: (e) => setTypedValue(e.target.value), placeholder: typedConfirmation.phrase, disabled: pending, autoComplete: "off" })] })) : null, _jsxs(AlertDialogFooter, { children: [_jsx(Button, { variant: "outline", onClick: () => handleOpenChange(false), disabled: pending, children: cancelLabel }), _jsx(Button, { variant: destructive ? 'destructive' : 'default', onClick: handleConfirm, disabled: !reasonOk || !typedOk, pending: pending, children: confirmLabel })] })] })] }));
78
83
  }
79
84
  //# sourceMappingURL=consequence-dialog.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"consequence-dialog.js","sourceRoot":"","sources":["../../../src/react/components/consequence-dialog.tsx"],"names":[],"mappings":"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","sourcesContent":["'use client';\n\nimport { useCallback, useId, useState } from 'react';\nimport {\n AlertDialog,\n AlertDialogTrigger,\n AlertDialogContent,\n AlertDialogHeader,\n AlertDialogFooter,\n AlertDialogTitle,\n AlertDialogDescription,\n} from './alert-dialog.js';\nimport { Button } from './button.js';\nimport { Textarea } from './textarea.js';\nimport { Input } from './input.js';\nimport { Label } from './label.js';\nimport { Zap, Clock, RotateCcw } from 'lucide-react';\n\n/**\n * The inline required-reason config. The caller OWNS the reason value/onChange (draft state lives at\n * the call site). Any `minLength`/`maxLength` the caller supplies MUST come from a caller-owned\n * constant — this component declares no business numbers and no copy; it only derives the gate + the\n * Textarea `maxLength` from the values it is handed.\n */\nexport interface ConsequenceDialogReason {\n required?: boolean;\n minLength?: number;\n maxLength?: number;\n placeholder?: string;\n label?: React.ReactNode;\n value: string;\n onChange: (value: string) => void;\n rows?: number;\n}\n\n/**\n * The exact-match typed-confirmation gate. `phrase` is business copy owned by the CALLER; the\n * component only compares the typed value against it.\n */\nexport interface ConsequenceDialogTypedConfirmation {\n phrase: string;\n label?: React.ReactNode;\n}\n\nexport interface ConsequenceDialogProps {\n /** Uncontrolled open model: the element that opens the dialog (rendered `asChild`). */\n trigger?: React.ReactNode;\n /** Controlled open model. Provide with `onOpenChange`. */\n open?: boolean;\n onOpenChange?: (open: boolean) => void;\n\n /** Heading — a string or node so callers can interpolate amounts / names. */\n title: React.ReactNode;\n\n /** SLOT 1 — what happens right now (e.g. \"initiates a Stripe refund of $X now\"). */\n immediateEffect?: React.ReactNode;\n /** SLOT 2 — what settles later, out of band (webhook / worker / async confirmation). */\n delayedEffect?: React.ReactNode;\n /** SLOT 3 — whether/how this can be undone or superseded. */\n reversibility?: React.ReactNode;\n\n /** Inline required-reason field. Optional. */\n reason?: ConsequenceDialogReason;\n /** Exact-phrase typed-confirmation gate. Optional. */\n typedConfirmation?: ConsequenceDialogTypedConfirmation;\n\n confirmLabel: React.ReactNode;\n cancelLabel?: React.ReactNode;\n /** `true` styles the confirm button with `bg-destructive`. */\n destructive?: boolean;\n\n /**\n * Runs on confirm. May be async — while it is pending the dialog STAYS OPEN, both buttons disable,\n * and the confirm button shows its pending spinner. Closes on resolve. If it rejects the dialog\n * stays open so the caller's own surface (error toast, TOTP step-up) can show and the operator can\n * retry or cancel. This component takes no Sentry/monitoring dependency — every error path\n * belongs to the caller's `onConfirm`.\n */\n onConfirm: () => void | Promise<void>;\n}\n\nconst SLOT_ICON_CLASS = 'icon-xs mt-0.5 shrink-0 text-muted-foreground';\n\nfunction ConsequenceRow({\n icon: Icon,\n label,\n children,\n}: {\n icon: typeof Zap;\n label: string;\n children: React.ReactNode;\n}) {\n return (\n <span className=\"flex items-start gap-2\">\n <Icon className={SLOT_ICON_CLASS} aria-hidden=\"true\" />\n <span className=\"stack-1 min-w-0\">\n <span className=\"text-caption font-medium uppercase tracking-wide text-muted-foreground\">\n {label}\n </span>\n <span className=\"text-small text-foreground\">{children}</span>\n </span>\n </span>\n );\n}\n\n/**\n * A reusable confirm-before-a-consequential-action dialog. Three optional, consistently-styled\n * consequence slots (immediate / afterward / reversibility), an optional inline required-reason field,\n * and an optional exact-phrase typed-confirmation gate. Composes the ui-core AlertDialog primitives\n * (Radix) — it never re-implements them. Supports BOTH a `trigger` (uncontrolled) and `open` /\n * `onOpenChange` (controlled).\n *\n * Pending model: the confirm button is a plain `Button` (NOT `AlertDialogAction`), so Radix does not\n * auto-close on click; open state is managed here so the dialog stays open with a visible spinner\n * until `onConfirm` resolves.\n */\nexport function ConsequenceDialog({\n trigger,\n open,\n onOpenChange,\n title,\n immediateEffect,\n delayedEffect,\n reversibility,\n reason,\n typedConfirmation,\n confirmLabel,\n cancelLabel = 'Cancel',\n destructive = false,\n onConfirm,\n}: ConsequenceDialogProps) {\n const isControlled = open !== undefined;\n const [internalOpen, setInternalOpen] = useState(false);\n const [typedValue, setTypedValue] = useState('');\n const [pending, setPending] = useState(false);\n\n const actualOpen = isControlled ? open : internalOpen;\n\n const reasonId = useId();\n const typedId = useId();\n\n const setOpenState = useCallback(\n (next: boolean) => {\n if (isControlled) onOpenChange?.(next);\n else setInternalOpen(next);\n },\n [isControlled, onOpenChange],\n );\n\n // Never allow an open-state change (Escape, cancel, controlled parent) to fight the pending action.\n const handleOpenChange = useCallback(\n (next: boolean) => {\n if (pending) return;\n setTypedValue(''); // reset the typed gate on every open/close\n setOpenState(next);\n },\n [pending, setOpenState],\n );\n\n const handleConfirm = useCallback(async () => {\n if (pending) return;\n setPending(true);\n try {\n await onConfirm();\n setTypedValue('');\n setOpenState(false);\n } catch {\n // Keep the dialog open — the caller's onConfirm surfaced the error (toast / step-up). The\n // operator can retry or cancel. Never rethrow, never monitor here.\n } finally {\n setPending(false);\n }\n }, [pending, onConfirm, setOpenState]);\n\n const reasonTrimmedLength = reason ? reason.value.trim().length : 0;\n const reasonProvided = reasonTrimmedLength > 0;\n const reasonOk = !reason\n ? true\n : reason.required && !reasonProvided\n ? false\n : !reasonProvided\n ? true // optional + empty is fine\n : (reason.minLength === undefined || reasonTrimmedLength >= reason.minLength) &&\n (reason.maxLength === undefined || reason.value.length <= reason.maxLength);\n\n const typedOk = !typedConfirmation ? true : typedValue === typedConfirmation.phrase;\n\n return (\n <AlertDialog open={actualOpen} onOpenChange={handleOpenChange}>\n {trigger ? <AlertDialogTrigger asChild>{trigger}</AlertDialogTrigger> : null}\n <AlertDialogContent\n onEscapeKeyDown={(e) => {\n if (pending) e.preventDefault();\n }}\n >\n <AlertDialogHeader>\n <AlertDialogTitle>{title}</AlertDialogTitle>\n <AlertDialogDescription asChild>\n <span className=\"stack-3\">\n {immediateEffect ? (\n <ConsequenceRow icon={Zap} label=\"Immediately\">\n {immediateEffect}\n </ConsequenceRow>\n ) : null}\n {delayedEffect ? (\n <ConsequenceRow icon={Clock} label=\"Afterward\">\n {delayedEffect}\n </ConsequenceRow>\n ) : null}\n {reversibility ? (\n <ConsequenceRow icon={RotateCcw} label=\"Reversibility\">\n {reversibility}\n </ConsequenceRow>\n ) : null}\n </span>\n </AlertDialogDescription>\n </AlertDialogHeader>\n\n {reason ? (\n <div className=\"stack-2\">\n <Label htmlFor={reasonId}>{reason.label ?? 'Reason'}</Label>\n <Textarea\n id={reasonId}\n value={reason.value}\n onChange={(e) => reason.onChange(e.target.value)}\n placeholder={reason.placeholder}\n maxLength={reason.maxLength}\n rows={reason.rows ?? 3}\n disabled={pending}\n />\n </div>\n ) : null}\n\n {typedConfirmation ? (\n <div className=\"stack-2\">\n <Label htmlFor={typedId}>\n {typedConfirmation.label ?? (\n <>\n Type <span className=\"font-mono\">{typedConfirmation.phrase}</span> to confirm\n </>\n )}\n </Label>\n <Input\n id={typedId}\n value={typedValue}\n onChange={(e) => setTypedValue(e.target.value)}\n placeholder={typedConfirmation.phrase}\n disabled={pending}\n autoComplete=\"off\"\n />\n </div>\n ) : null}\n\n <AlertDialogFooter>\n <Button\n variant=\"outline\"\n onClick={() => handleOpenChange(false)}\n disabled={pending}\n >\n {cancelLabel}\n </Button>\n <Button\n variant={destructive ? 'destructive' : 'default'}\n onClick={handleConfirm}\n disabled={!reasonOk || !typedOk}\n pending={pending}\n >\n {confirmLabel}\n </Button>\n </AlertDialogFooter>\n </AlertDialogContent>\n </AlertDialog>\n );\n}\n"]}
1
+ {"version":3,"file":"consequence-dialog.js","sourceRoot":"","sources":["../../../src/react/components/consequence-dialog.tsx"],"names":[],"mappings":"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","sourcesContent":["'use client';\n\nimport { useCallback, useId, useState } from 'react';\nimport {\n AlertDialog,\n AlertDialogTrigger,\n AlertDialogContent,\n AlertDialogHeader,\n AlertDialogFooter,\n AlertDialogTitle,\n AlertDialogDescription,\n} from './alert-dialog.js';\nimport { Button } from './button.js';\nimport { Textarea } from './textarea.js';\nimport { Input } from './input.js';\nimport { Label } from './label.js';\nimport { Zap, Clock, RotateCcw } from 'lucide-react';\n\n/**\n * The inline required-reason config. The caller OWNS the reason value/onChange (draft state lives at\n * the call site). Any `minLength`/`maxLength` the caller supplies MUST come from a caller-owned\n * constant — this component declares no business numbers and no copy; it only derives the gate + the\n * Textarea `maxLength` from the values it is handed.\n */\nexport interface ConsequenceDialogReason {\n required?: boolean;\n minLength?: number;\n maxLength?: number;\n placeholder?: string;\n label?: React.ReactNode;\n value: string;\n onChange: (value: string) => void;\n rows?: number;\n}\n\n/**\n * The exact-match typed-confirmation gate. `phrase` is business copy owned by the CALLER; the\n * component only compares the typed value against it.\n */\nexport interface ConsequenceDialogTypedConfirmation {\n phrase: string;\n label?: React.ReactNode;\n}\n\nexport interface ConsequenceDialogProps {\n /** Uncontrolled open model: the element that opens the dialog (rendered `asChild`). */\n trigger?: React.ReactNode;\n /** Controlled open model. Provide with `onOpenChange`. */\n open?: boolean;\n onOpenChange?: (open: boolean) => void;\n\n /** Heading — a string or node so callers can interpolate amounts / names. */\n title: React.ReactNode;\n\n /** SLOT 1 — what happens right now (e.g. \"initiates a Stripe refund of $X now\"). */\n immediateEffect?: React.ReactNode;\n /** SLOT 2 — what settles later, out of band (webhook / worker / async confirmation). */\n delayedEffect?: React.ReactNode;\n /** SLOT 3 — whether/how this can be undone or superseded. */\n reversibility?: React.ReactNode;\n\n /** Inline required-reason field. Optional. */\n reason?: ConsequenceDialogReason;\n /** Exact-phrase typed-confirmation gate. Optional. */\n typedConfirmation?: ConsequenceDialogTypedConfirmation;\n\n confirmLabel: React.ReactNode;\n cancelLabel?: React.ReactNode;\n /** `true` styles the confirm button with `bg-destructive`. */\n destructive?: boolean;\n\n /**\n * Runs on confirm. May be async — while it is pending the dialog STAYS OPEN, both buttons disable,\n * and the confirm button shows its pending spinner. Closes on resolve. If it rejects the dialog\n * stays open so the caller's own surface (error toast, TOTP step-up) can show and the operator can\n * retry or cancel. This component takes no Sentry/monitoring dependency — every error path\n * belongs to the caller's `onConfirm`.\n */\n onConfirm: () => void | Promise<void>;\n}\n\nconst SLOT_ICON_CLASS = 'icon-xs mt-0.5 shrink-0 text-muted-foreground';\n\n/**\n * One consequence slot: the icon beside a block column whose label sits on its own line above\n * the slot text. Every element is a block: the `stack-*` spacing is a vertical margin, which an\n * inline element ignores.\n */\nfunction ConsequenceRow({\n icon: Icon,\n label,\n children,\n}: {\n icon: typeof Zap;\n label: string;\n children: React.ReactNode;\n}) {\n return (\n <div className=\"flex items-start gap-2\">\n <Icon className={SLOT_ICON_CLASS} aria-hidden=\"true\" />\n <div className=\"stack-1 min-w-0\">\n <div className=\"text-caption font-medium uppercase tracking-wide text-muted-foreground\">\n {label}\n </div>\n <div className=\"text-small text-foreground\">{children}</div>\n </div>\n </div>\n );\n}\n\n/**\n * A reusable confirm-before-a-consequential-action dialog. Three optional, consistently-styled\n * consequence slots (immediate / afterward / reversibility), an optional inline required-reason field,\n * and an optional exact-phrase typed-confirmation gate. Composes the ui-core AlertDialog primitives\n * (Radix) — it never re-implements them. Supports BOTH a `trigger` (uncontrolled) and `open` /\n * `onOpenChange` (controlled).\n *\n * Pending model: the confirm button is a plain `Button` (NOT `AlertDialogAction`), so Radix does not\n * auto-close on click; open state is managed here so the dialog stays open with a visible spinner\n * until `onConfirm` resolves.\n */\nexport function ConsequenceDialog({\n trigger,\n open,\n onOpenChange,\n title,\n immediateEffect,\n delayedEffect,\n reversibility,\n reason,\n typedConfirmation,\n confirmLabel,\n cancelLabel = 'Cancel',\n destructive = false,\n onConfirm,\n}: ConsequenceDialogProps) {\n const isControlled = open !== undefined;\n const [internalOpen, setInternalOpen] = useState(false);\n const [typedValue, setTypedValue] = useState('');\n const [pending, setPending] = useState(false);\n\n const actualOpen = isControlled ? open : internalOpen;\n\n const reasonId = useId();\n const typedId = useId();\n\n const setOpenState = useCallback(\n (next: boolean) => {\n if (isControlled) onOpenChange?.(next);\n else setInternalOpen(next);\n },\n [isControlled, onOpenChange],\n );\n\n // Never allow an open-state change (Escape, cancel, controlled parent) to fight the pending action.\n const handleOpenChange = useCallback(\n (next: boolean) => {\n if (pending) return;\n setTypedValue(''); // reset the typed gate on every open/close\n setOpenState(next);\n },\n [pending, setOpenState],\n );\n\n const handleConfirm = useCallback(async () => {\n if (pending) return;\n setPending(true);\n try {\n await onConfirm();\n setTypedValue('');\n setOpenState(false);\n } catch {\n // Keep the dialog open — the caller's onConfirm surfaced the error (toast / step-up). The\n // operator can retry or cancel. Never rethrow, never monitor here.\n } finally {\n setPending(false);\n }\n }, [pending, onConfirm, setOpenState]);\n\n const reasonTrimmedLength = reason ? reason.value.trim().length : 0;\n const reasonProvided = reasonTrimmedLength > 0;\n const reasonOk = !reason\n ? true\n : reason.required && !reasonProvided\n ? false\n : !reasonProvided\n ? true // optional + empty is fine\n : (reason.minLength === undefined || reasonTrimmedLength >= reason.minLength) &&\n (reason.maxLength === undefined || reason.value.length <= reason.maxLength);\n\n const typedOk = !typedConfirmation ? true : typedValue === typedConfirmation.phrase;\n\n return (\n <AlertDialog open={actualOpen} onOpenChange={handleOpenChange}>\n {trigger ? <AlertDialogTrigger asChild>{trigger}</AlertDialogTrigger> : null}\n <AlertDialogContent\n onEscapeKeyDown={(e) => {\n if (pending) e.preventDefault();\n }}\n >\n <AlertDialogHeader>\n <AlertDialogTitle>{title}</AlertDialogTitle>\n {/* A div, not the primitive's default <p>: the rows are blocks, which a <p> cannot hold.\n Left-aligned at every width — the header centres its text on narrow screens, and a\n wrapped consequence line reads as a list, not a caption. */}\n <AlertDialogDescription asChild>\n <div className=\"stack-3 text-left\">\n {immediateEffect ? (\n <ConsequenceRow icon={Zap} label=\"Immediately\">\n {immediateEffect}\n </ConsequenceRow>\n ) : null}\n {delayedEffect ? (\n <ConsequenceRow icon={Clock} label=\"Afterward\">\n {delayedEffect}\n </ConsequenceRow>\n ) : null}\n {reversibility ? (\n <ConsequenceRow icon={RotateCcw} label=\"Reversibility\">\n {reversibility}\n </ConsequenceRow>\n ) : null}\n </div>\n </AlertDialogDescription>\n </AlertDialogHeader>\n\n {reason ? (\n <div className=\"stack-2\">\n <Label htmlFor={reasonId}>{reason.label ?? 'Reason'}</Label>\n <Textarea\n id={reasonId}\n value={reason.value}\n onChange={(e) => reason.onChange(e.target.value)}\n placeholder={reason.placeholder}\n maxLength={reason.maxLength}\n rows={reason.rows ?? 3}\n disabled={pending}\n />\n </div>\n ) : null}\n\n {typedConfirmation ? (\n <div className=\"stack-2\">\n <Label htmlFor={typedId}>\n {typedConfirmation.label ?? (\n <>\n Type <span className=\"font-mono\">{typedConfirmation.phrase}</span> to confirm\n </>\n )}\n </Label>\n <Input\n id={typedId}\n value={typedValue}\n onChange={(e) => setTypedValue(e.target.value)}\n placeholder={typedConfirmation.phrase}\n disabled={pending}\n autoComplete=\"off\"\n />\n </div>\n ) : null}\n\n <AlertDialogFooter>\n <Button\n variant=\"outline\"\n onClick={() => handleOpenChange(false)}\n disabled={pending}\n >\n {cancelLabel}\n </Button>\n <Button\n variant={destructive ? 'destructive' : 'default'}\n onClick={handleConfirm}\n disabled={!reasonOk || !typedOk}\n pending={pending}\n >\n {confirmLabel}\n </Button>\n </AlertDialogFooter>\n </AlertDialogContent>\n </AlertDialog>\n );\n}\n"]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ttt-productions/ui-core",
3
- "version": "0.11.17",
3
+ "version": "0.11.19",
4
4
  "description": "Shared UI components for TTT Productions projects",
5
5
  "repository": {
6
6
  "type": "git",
@@ -16,8 +16,7 @@
16
16
  "**/*.css"
17
17
  ],
18
18
  "files": [
19
- "dist",
20
- "src"
19
+ "dist"
21
20
  ],
22
21
  "exports": {
23
22
  ".": {
@@ -99,7 +98,6 @@
99
98
  "@types/react-dom": "^19.0.0",
100
99
  "react": "^19.2.0",
101
100
  "react-dom": "^19.2.0",
102
- "typescript": "^5.8.3",
103
101
  "react-hook-form": "^7.69.0",
104
102
  "@hookform/resolvers": "^5.2.2",
105
103
  "@ttt-productions/firebase-helpers": "^0.12.9",
package/src/index.ts DELETED
@@ -1,3 +0,0 @@
1
- // Server-safe utilities only. React components and hooks live behind ./react.
2
- export * from "./lib/utils.js";
3
- export * from "./lib/format-numbers.js";
@@ -1,17 +0,0 @@
1
- /**
2
- * Compact number formatter:
3
- * < 10,000 → "1234"
4
- * 10,000 ≤ n < 1,000,000 → "12.3k" (trailing ".0" stripped)
5
- * ≥ 1,000,000 → "1.2M" (trailing ".0" stripped)
6
- */
7
- export function formatLargeNumber(num: number): string {
8
- if (num >= 1_000_000) {
9
- const formatted = (num / 1_000_000).toFixed(1);
10
- return (formatted.endsWith(".0") ? formatted.slice(0, -2) : formatted) + "M";
11
- }
12
- if (num >= 10_000) {
13
- const formatted = (num / 1000).toFixed(1);
14
- return (formatted.endsWith(".0") ? formatted.slice(0, -2) : formatted) + "k";
15
- }
16
- return num.toString();
17
- }
package/src/lib/utils.ts DELETED
@@ -1,9 +0,0 @@
1
- import { type ClassValue, clsx } from 'clsx';
2
- import { twMerge } from 'tailwind-merge';
3
-
4
- /**
5
- * Merge Tailwind CSS classes with proper precedence
6
- */
7
- export function cn(...inputs: ClassValue[]) {
8
- return twMerge(clsx(inputs));
9
- }
@@ -1,53 +0,0 @@
1
- "use client"
2
-
3
- import * as React from "react"
4
- import * as AccordionPrimitive from "@radix-ui/react-accordion"
5
- import { ChevronDown } from "lucide-react"
6
-
7
- import { cn } from "../../lib/utils.js"
8
-
9
- const Accordion = AccordionPrimitive.Root
10
-
11
- const AccordionItem = React.forwardRef<
12
- React.ElementRef<typeof AccordionPrimitive.Item>,
13
- React.ComponentPropsWithoutRef<typeof AccordionPrimitive.Item>
14
- >(({ className, ...props }, ref) => (
15
- <AccordionPrimitive.Item ref={ref} className={cn(className)} {...props} />
16
- ))
17
- AccordionItem.displayName = "AccordionItem"
18
-
19
- const AccordionTrigger = React.forwardRef<
20
- React.ElementRef<typeof AccordionPrimitive.Trigger>,
21
- React.ComponentPropsWithoutRef<typeof AccordionPrimitive.Trigger>
22
- >(({ className, children, ...props }, ref) => (
23
- <AccordionPrimitive.Header className="flex">
24
- <AccordionPrimitive.Trigger
25
- ref={ref}
26
- className={cn(
27
- "flex flex-1 items-center justify-between py-4 font-medium transition-all hover:underline [&[data-state=open]>svg]:rotate-180",
28
- className
29
- )}
30
- {...props}
31
- >
32
- {children}
33
- <ChevronDown className="h-4 w-4 shrink-0 transition-transform duration-200" />
34
- </AccordionPrimitive.Trigger>
35
- </AccordionPrimitive.Header>
36
- ))
37
- AccordionTrigger.displayName = AccordionPrimitive.Trigger.displayName
38
-
39
- const AccordionContent = React.forwardRef<
40
- React.ElementRef<typeof AccordionPrimitive.Content>,
41
- React.ComponentPropsWithoutRef<typeof AccordionPrimitive.Content>
42
- >(({ className, children, ...props }, ref) => (
43
- <AccordionPrimitive.Content
44
- ref={ref}
45
- className="overflow-hidden text-sm transition-all data-[state=closed]:animate-accordion-up data-[state=open]:animate-accordion-down"
46
- {...props}
47
- >
48
- <div className={cn("pb-4 pt-0", className)}>{children}</div>
49
- </AccordionPrimitive.Content>
50
- ))
51
- AccordionContent.displayName = AccordionPrimitive.Content.displayName
52
-
53
- export { Accordion, AccordionItem, AccordionTrigger, AccordionContent }
@@ -1,131 +0,0 @@
1
- "use client"
2
-
3
- import * as React from "react"
4
- import * as AlertDialogPrimitive from "@radix-ui/react-alert-dialog"
5
-
6
- import { cn } from "../../lib/utils.js"
7
- import { buttonVariants } from "./button.js"
8
-
9
- const AlertDialog = AlertDialogPrimitive.Root
10
- const AlertDialogTrigger = AlertDialogPrimitive.Trigger
11
- const AlertDialogPortal = AlertDialogPrimitive.Portal
12
-
13
- const AlertDialogOverlay = React.forwardRef<
14
- React.ElementRef<typeof AlertDialogPrimitive.Overlay>,
15
- React.ComponentPropsWithoutRef<typeof AlertDialogPrimitive.Overlay>
16
- >(({ className, ...props }, ref) => (
17
- <AlertDialogPrimitive.Overlay
18
- className={cn(
19
- "fixed inset-0 z-50 bg-[color:var(--scrim)] data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0",
20
- className
21
- )}
22
- {...props}
23
- ref={ref}
24
- />
25
- ))
26
- AlertDialogOverlay.displayName = AlertDialogPrimitive.Overlay.displayName
27
-
28
- const AlertDialogContent = React.forwardRef<
29
- React.ElementRef<typeof AlertDialogPrimitive.Content>,
30
- React.ComponentPropsWithoutRef<typeof AlertDialogPrimitive.Content>
31
- >(({ className, ...props }, ref) => (
32
- <AlertDialogPortal>
33
- <AlertDialogOverlay />
34
- <AlertDialogPrimitive.Content
35
- ref={ref}
36
- className={cn(
37
- "fixed left-[50%] top-[50%] z-50 grid w-full max-w-lg translate-x-[-50%] translate-y-[-50%] gap-4 border bg-background p-6 elevation-raised duration-200 data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[state=closed]:slide-out-to-left-1/2 data-[state=closed]:slide-out-to-top-[48%] data-[state=open]:slide-in-from-left-1/2 data-[state=open]:slide-in-from-top-[48%] sm:rounded-lg [&>*]:min-w-0",
38
- className
39
- )}
40
- {...props}
41
- />
42
- </AlertDialogPortal>
43
- ))
44
- AlertDialogContent.displayName = AlertDialogPrimitive.Content.displayName
45
-
46
- const AlertDialogHeader = ({
47
- className,
48
- ...props
49
- }: React.HTMLAttributes<HTMLDivElement>) => (
50
- <div
51
- className={cn("flex flex-col space-y-2 text-center sm:text-left", className)}
52
- {...props}
53
- />
54
- )
55
- AlertDialogHeader.displayName = "AlertDialogHeader"
56
-
57
- const AlertDialogFooter = ({
58
- className,
59
- ...props
60
- }: React.HTMLAttributes<HTMLDivElement>) => (
61
- <div
62
- className={cn(
63
- "flex flex-col-reverse sm:flex-row sm:justify-end sm:space-x-2",
64
- className
65
- )}
66
- {...props}
67
- />
68
- )
69
- AlertDialogFooter.displayName = "AlertDialogFooter"
70
-
71
- const AlertDialogTitle = React.forwardRef<
72
- React.ElementRef<typeof AlertDialogPrimitive.Title>,
73
- React.ComponentPropsWithoutRef<typeof AlertDialogPrimitive.Title>
74
- >(({ className, ...props }, ref) => (
75
- <AlertDialogPrimitive.Title
76
- ref={ref}
77
- className={cn("text-lg font-semibold break-words", className)}
78
- {...props}
79
- />
80
- ))
81
- AlertDialogTitle.displayName = AlertDialogPrimitive.Title.displayName
82
-
83
- const AlertDialogDescription = React.forwardRef<
84
- React.ElementRef<typeof AlertDialogPrimitive.Description>,
85
- React.ComponentPropsWithoutRef<typeof AlertDialogPrimitive.Description>
86
- >(({ className, ...props }, ref) => (
87
- <AlertDialogPrimitive.Description
88
- ref={ref}
89
- className={cn("text-sm text-muted-foreground break-words", className)}
90
- {...props}
91
- />
92
- ))
93
- AlertDialogDescription.displayName = AlertDialogPrimitive.Description.displayName
94
-
95
- const AlertDialogAction = React.forwardRef<
96
- React.ElementRef<typeof AlertDialogPrimitive.Action>,
97
- React.ComponentPropsWithoutRef<typeof AlertDialogPrimitive.Action>
98
- >(({ className, ...props }, ref) => (
99
- <AlertDialogPrimitive.Action
100
- ref={ref}
101
- className={cn(buttonVariants(), className)}
102
- {...props}
103
- />
104
- ))
105
- AlertDialogAction.displayName = AlertDialogPrimitive.Action.displayName
106
-
107
- const AlertDialogCancel = React.forwardRef<
108
- React.ElementRef<typeof AlertDialogPrimitive.Cancel>,
109
- React.ComponentPropsWithoutRef<typeof AlertDialogPrimitive.Cancel>
110
- >(({ className, ...props }, ref) => (
111
- <AlertDialogPrimitive.Cancel
112
- ref={ref}
113
- className={cn(buttonVariants({ variant: "outline" }), "mt-2 sm:mt-0", className)}
114
- {...props}
115
- />
116
- ))
117
- AlertDialogCancel.displayName = AlertDialogPrimitive.Cancel.displayName
118
-
119
- export {
120
- AlertDialog,
121
- AlertDialogPortal,
122
- AlertDialogOverlay,
123
- AlertDialogTrigger,
124
- AlertDialogContent,
125
- AlertDialogHeader,
126
- AlertDialogFooter,
127
- AlertDialogTitle,
128
- AlertDialogDescription,
129
- AlertDialogAction,
130
- AlertDialogCancel,
131
- }
@@ -1,59 +0,0 @@
1
- import * as React from "react"
2
- import { cva, type VariantProps } from "class-variance-authority"
3
-
4
- import { cn } from "../../lib/utils.js"
5
-
6
- const alertVariants = cva(
7
- "relative w-full rounded-lg border p-4 [&>svg~*]:pl-7 [&>svg+div]:translate-y-[-3px] [&>svg]:absolute [&>svg]:left-4 [&>svg]:top-4 [&>svg]:text-foreground",
8
- {
9
- variants: {
10
- variant: {
11
- default: "bg-background text-foreground",
12
- destructive:
13
- "border-destructive/50 text-destructive dark:border-destructive [&>svg]:text-destructive",
14
- },
15
- },
16
- defaultVariants: {
17
- variant: "default",
18
- },
19
- }
20
- )
21
-
22
- const Alert = React.forwardRef<
23
- HTMLDivElement,
24
- React.HTMLAttributes<HTMLDivElement> & VariantProps<typeof alertVariants>
25
- >(({ className, variant, ...props }, ref) => (
26
- <div
27
- ref={ref}
28
- role="alert"
29
- className={cn(alertVariants({ variant }), className)}
30
- {...props}
31
- />
32
- ))
33
- Alert.displayName = "Alert"
34
-
35
- const AlertTitle = React.forwardRef<
36
- HTMLParagraphElement,
37
- React.HTMLAttributes<HTMLHeadingElement>
38
- >(({ className, ...props }, ref) => (
39
- <h5
40
- ref={ref}
41
- className={cn("mb-1 font-medium leading-none tracking-tight break-words", className)}
42
- {...props}
43
- />
44
- ))
45
- AlertTitle.displayName = "AlertTitle"
46
-
47
- const AlertDescription = React.forwardRef<
48
- HTMLParagraphElement,
49
- React.HTMLAttributes<HTMLParagraphElement>
50
- >(({ className, ...props }, ref) => (
51
- <div
52
- ref={ref}
53
- className={cn("text-sm [&_p]:leading-relaxed break-words", className)}
54
- {...props}
55
- />
56
- ))
57
- AlertDescription.displayName = "AlertDescription"
58
-
59
- export { Alert, AlertTitle, AlertDescription }
@@ -1,51 +0,0 @@
1
- "use client"
2
-
3
- import * as React from "react"
4
- import * as AvatarPrimitive from "@radix-ui/react-avatar"
5
-
6
- import { cn } from "../../lib/utils.js"
7
-
8
- const Avatar = React.forwardRef<
9
- React.ElementRef<typeof AvatarPrimitive.Root>,
10
- React.ComponentPropsWithoutRef<typeof AvatarPrimitive.Root>
11
- >(({ className, ...props }, ref) => (
12
- <AvatarPrimitive.Root
13
- ref={ref}
14
- className={cn("relative flex h-10 w-10 shrink-0 overflow-hidden rounded-full", className)}
15
- {...props}
16
- />
17
- ))
18
- Avatar.displayName = AvatarPrimitive.Root.displayName
19
-
20
- const AvatarImage = React.forwardRef<
21
- React.ElementRef<typeof AvatarPrimitive.Image>,
22
- React.ComponentPropsWithoutRef<typeof AvatarPrimitive.Image>
23
- >(({ className, src, ...props }, ref) => {
24
- if (!src) return null
25
- return (
26
- <AvatarPrimitive.Image
27
- ref={ref}
28
- src={src}
29
- className={cn("aspect-square h-full w-full", className)}
30
- {...props}
31
- />
32
- )
33
- })
34
- AvatarImage.displayName = AvatarPrimitive.Image.displayName
35
-
36
- const AvatarFallback = React.forwardRef<
37
- React.ElementRef<typeof AvatarPrimitive.Fallback>,
38
- React.ComponentPropsWithoutRef<typeof AvatarPrimitive.Fallback>
39
- >(({ className, ...props }, ref) => (
40
- <AvatarPrimitive.Fallback
41
- ref={ref}
42
- className={cn(
43
- "flex h-full w-full items-center justify-center rounded-full bg-[color:var(--avatar-fallback)] text-primary font-semibold",
44
- className
45
- )}
46
- {...props}
47
- />
48
- ))
49
- AvatarFallback.displayName = AvatarPrimitive.Fallback.displayName
50
-
51
- export { Avatar, AvatarImage, AvatarFallback }
@@ -1,31 +0,0 @@
1
- "use client";
2
-
3
- import * as React from "react";
4
- import { cva, type VariantProps } from "class-variance-authority";
5
-
6
- import { cn } from "../../lib/utils.js";
7
-
8
- const badgeVariants = cva(
9
- "inline-flex items-center rounded-full border px-2.5 py-0.5 text-xs font-semibold transition-colors focus:outline-hidden focus:ring-2 focus:ring-ring focus:ring-offset-2",
10
- {
11
- variants: {
12
- variant: {
13
- default: "border-transparent bg-primary text-primary-foreground hover:bg-primary/80",
14
- secondary: "border-transparent bg-secondary text-secondary-foreground hover:bg-secondary/80",
15
- destructive: "border-transparent bg-destructive text-destructive-foreground hover:bg-destructive/80",
16
- outline: "text-foreground",
17
- },
18
- },
19
- defaultVariants: { variant: "default" },
20
- }
21
- );
22
-
23
- export interface BadgeProps
24
- extends React.HTMLAttributes<HTMLDivElement>,
25
- VariantProps<typeof badgeVariants> {}
26
-
27
- function Badge({ className, variant, ...props }: BadgeProps) {
28
- return <div className={cn(badgeVariants({ variant }), className)} {...props} />;
29
- }
30
-
31
- export { Badge, badgeVariants };
@@ -1,102 +0,0 @@
1
- "use client"
2
-
3
- import * as React from "react"
4
- import { Slot, Slottable } from "@radix-ui/react-slot"
5
- import { cva, type VariantProps } from "class-variance-authority"
6
-
7
- import { cn } from "../../lib/utils.js"
8
- import { Spinner } from "./spinner.js"
9
-
10
- const buttonVariants = cva(
11
- "inline-flex items-center justify-center gap-2 whitespace-nowrap rounded-md text-sm font-bold ring-offset-background transition-colors focus-visible:outline-hidden focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50",
12
- {
13
- variants: {
14
- variant: {
15
- default:
16
- "button-default bg-primary text-primary-foreground hover:bg-primary/90 border-2 border-[hsl(var(--brand-primary-deep))]",
17
- destructive:
18
- "bg-destructive text-destructive-foreground hover:bg-destructive/90 border-2 border-[hsl(var(--destructive-border))]",
19
- success:
20
- "bg-[color:var(--button-success)] text-[hsl(var(--success-foreground))] hover:bg-[color:var(--button-success)]/90 border-2 border-[hsl(var(--status-success-border))]",
21
- outline:
22
- "border-2 border-border bg-background hover:bg-accent hover:text-accent-foreground text-foreground",
23
- secondary:
24
- "bg-secondary text-secondary-foreground hover:bg-secondary/80 border-2 border-border",
25
- ghost: "hover:bg-accent hover:text-accent-foreground",
26
- link: "text-primary underline-offset-4 hover:underline",
27
- inverted:
28
- "bg-[hsl(var(--inverted-background))] text-[hsl(var(--inverted-foreground))] hover:bg-[hsl(var(--inverted-background))]/90 border-2 border-[hsl(var(--inverted-border))]",
29
- },
30
- size: {
31
- default: "h-10 px-4 py-2",
32
- sm: "h-9 rounded-md px-3",
33
- lg: "h-11 rounded-md px-8",
34
- icon: "h-9 w-9",
35
- },
36
- },
37
- defaultVariants: {
38
- variant: "default",
39
- size: "default",
40
- },
41
- }
42
- )
43
-
44
- export interface ButtonProps
45
- extends React.ButtonHTMLAttributes<HTMLButtonElement>,
46
- VariantProps<typeof buttonVariants> {
47
- asChild?: boolean
48
- /**
49
- * Async work this button started is in flight. The button disables itself — so
50
- * neither a repeat click nor an implicit form resubmit can fire — sets
51
- * `aria-busy`, and shows the canonical spinner: an icon button (`size="icon"`)
52
- * swaps its icon for it; any other button shows it in place of its `icon`.
53
- */
54
- pending?: boolean
55
- /** Leading icon, rendered before the label. While `pending`, the spinner takes its place. */
56
- icon?: React.ReactNode
57
- }
58
-
59
- const Button = React.forwardRef<HTMLButtonElement, ButtonProps>(
60
- ({ className, variant, size, asChild = false, pending = false, icon, disabled, children, ...props }, ref) => {
61
- const shared = {
62
- ...props,
63
- ...(pending ? { "aria-busy": true, "data-pending": "" } : {}),
64
- disabled: disabled || pending || undefined,
65
- className: cn(buttonVariants({ variant, size, className })),
66
- }
67
- const spinner = <Spinner size={size === "lg" ? "sm" : "xs"} />
68
- const leading = pending ? spinner : icon
69
-
70
- if (asChild) {
71
- // Slot finds the Slottable among its DIRECT children, so the leading slot and the
72
- // Slottable must be siblings here — never wrapped in a fragment.
73
- return leading ? (
74
- <Slot ref={ref} {...shared}>
75
- {leading}
76
- <Slottable>{children}</Slottable>
77
- </Slot>
78
- ) : (
79
- <Slot ref={ref} {...shared}>
80
- {children}
81
- </Slot>
82
- )
83
- }
84
-
85
- return (
86
- <button ref={ref} {...shared}>
87
- {size === "icon" ? (
88
- pending ? spinner : children
89
- ) : (
90
- <>
91
- {leading}
92
- {children}
93
- </>
94
- )}
95
- </button>
96
- )
97
- }
98
- )
99
-
100
- Button.displayName = "Button"
101
-
102
- export { Button, buttonVariants }
@@ -1,54 +0,0 @@
1
- "use client"
2
-
3
- import * as React from "react"
4
- import { cn } from "../../lib/utils.js"
5
-
6
- const Card = React.forwardRef<HTMLDivElement, React.HTMLAttributes<HTMLDivElement>>(
7
- ({ className, ...props }, ref) => (
8
- <div
9
- ref={ref}
10
- className={cn("rounded-lg border-2 card-border bg-card text-card-foreground", className)}
11
- {...props}
12
- />
13
- )
14
- )
15
- Card.displayName = "Card"
16
-
17
- const CardHeader = React.forwardRef<HTMLDivElement, React.HTMLAttributes<HTMLDivElement>>(
18
- ({ className, ...props }, ref) => (
19
- <div ref={ref} className={cn("flex flex-col space-y-1.5 p-6", className)} {...props} />
20
- )
21
- )
22
- CardHeader.displayName = "CardHeader"
23
-
24
- const CardTitle = React.forwardRef<HTMLParagraphElement, React.HTMLAttributes<HTMLHeadingElement>>(
25
- ({ className, ...props }, ref) => (
26
- <h3
27
- ref={ref}
28
- className={cn("text-2xl font-semibold leading-none tracking-tight break-words card-title", className)}
29
- {...props}
30
- />
31
- )
32
- )
33
- CardTitle.displayName = "CardTitle"
34
-
35
- const CardDescription = React.forwardRef<HTMLParagraphElement, React.HTMLAttributes<HTMLParagraphElement>>(
36
- ({ className, ...props }, ref) => (
37
- <p ref={ref} className={cn("break-words card-description", className)} {...props} />
38
- )
39
- )
40
- CardDescription.displayName = "CardDescription"
41
-
42
- const CardContent = React.forwardRef<HTMLDivElement, React.HTMLAttributes<HTMLDivElement>>(
43
- ({ className, ...props }, ref) => <div ref={ref} className={cn("p-6 pt-0", className)} {...props} />
44
- )
45
- CardContent.displayName = "CardContent"
46
-
47
- const CardFooter = React.forwardRef<HTMLDivElement, React.HTMLAttributes<HTMLDivElement>>(
48
- ({ className, ...props }, ref) => (
49
- <div ref={ref} className={cn("flex items-center p-6 pt-0", className)} {...props} />
50
- )
51
- )
52
- CardFooter.displayName = "CardFooter"
53
-
54
- export { Card, CardHeader, CardFooter, CardTitle, CardDescription, CardContent }