@rovula/ui 0.1.15 → 0.1.18

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.
@@ -44,6 +44,8 @@ export * from "./components/Tree";
44
44
  export * from "./components/FocusedScrollView/FocusedScrollView";
45
45
  export * from "./components/RadioGroup/RadioGroup";
46
46
  export * from "./components/Form";
47
+ export * from "./patterns/confirm-dialog/ConfirmDialog";
48
+ export * from "./patterns/form-dialog/FormDialog";
47
49
  export type { ButtonProps } from "./components/Button/Button";
48
50
  export type { InputProps } from "./components/TextInput/TextInput";
49
51
  export type { PasswordInputProps } from "./components/PasswordInput/PasswordInput";
@@ -20,5 +20,8 @@ export type ConfirmDialogProps = {
20
20
  * When true, hides the cancel button — useful for info/error alerts that only need one action.
21
21
  */
22
22
  hideCancelButton?: boolean;
23
+ testId?: string;
24
+ cancelClassName?: string;
25
+ confirmClassName?: string;
23
26
  };
24
27
  export declare const ConfirmDialog: React.FC<ConfirmDialogProps>;
@@ -21,6 +21,9 @@ declare const meta: {
21
21
  trigger?: React.ReactNode;
22
22
  typeToConfirm?: string | undefined;
23
23
  hideCancelButton?: boolean | undefined;
24
+ testId?: string | undefined;
25
+ cancelClassName?: string | undefined;
26
+ confirmClassName?: string | undefined;
24
27
  }>) => import("react/jsx-runtime").JSX.Element)[];
25
28
  argTypes: {
26
29
  open: {
@@ -8,6 +8,7 @@ export type FormDialogAction = {
8
8
  disabled?: boolean;
9
9
  isLoading?: boolean;
10
10
  type?: "button" | "submit" | "reset";
11
+ className?: string;
11
12
  };
12
13
  export type FormDialogProps = {
13
14
  open?: boolean;
@@ -35,5 +36,6 @@ export type FormDialogProps = {
35
36
  * Use together with a <Form id={formId} .../> inside children.
36
37
  */
37
38
  formId?: string;
39
+ testId?: string;
38
40
  };
39
41
  export declare const FormDialog: React.FC<FormDialogProps>;
@@ -21,6 +21,7 @@ declare const meta: {
21
21
  scrollable?: boolean | undefined;
22
22
  className?: string | undefined;
23
23
  formId?: string | undefined;
24
+ testId?: string | undefined;
24
25
  }>) => import("react/jsx-runtime").JSX.Element)[];
25
26
  argTypes: {
26
27
  open: {
package/dist/index.d.ts CHANGED
@@ -19,6 +19,7 @@ import { ToastProviderProps } from '@radix-ui/react-toast';
19
19
  import * as RadioGroupPrimitive from '@radix-ui/react-radio-group';
20
20
  import { FieldValues, UseFormReturn, DefaultValues, SubmitHandler, SubmitErrorHandler, Resolver, Mode, FieldPath, RegisterOptions, FieldPathValue } from 'react-hook-form';
21
21
  import * as yup from 'yup';
22
+ import Button$1 from '@/components/Button/Button';
22
23
  import { ClassValue } from 'clsx';
23
24
 
24
25
  type ButtonProps = {
@@ -1281,6 +1282,73 @@ declare const useOptionBridge: <TValue extends OptionValue = string, TOption ext
1281
1282
  optionsByValue: Map<TValue, TOption>;
1282
1283
  };
1283
1284
 
1285
+ type ConfirmDialogProps = {
1286
+ open?: boolean;
1287
+ onOpenChange?: (open: boolean) => void;
1288
+ title: string;
1289
+ description?: string;
1290
+ confirmLabel?: string;
1291
+ cancelLabel?: string;
1292
+ onConfirm?: () => void;
1293
+ onCancel?: () => void;
1294
+ /** Fires whenever the dialog closes, regardless of how (cancel button, overlay, Escape). */
1295
+ onClose?: () => void;
1296
+ trigger?: React.ReactNode;
1297
+ /**
1298
+ * When provided, the user must type this exact text before the confirm button is enabled.
1299
+ * e.g. typeToConfirm="confirm" or typeToConfirm="delete"
1300
+ */
1301
+ typeToConfirm?: string;
1302
+ /**
1303
+ * When true, hides the cancel button — useful for info/error alerts that only need one action.
1304
+ */
1305
+ hideCancelButton?: boolean;
1306
+ testId?: string;
1307
+ cancelClassName?: string;
1308
+ confirmClassName?: string;
1309
+ };
1310
+ declare const ConfirmDialog: React.FC<ConfirmDialogProps>;
1311
+
1312
+ type FormDialogAction = {
1313
+ label: string;
1314
+ onClick?: () => void;
1315
+ variant?: React.ComponentProps<typeof Button$1>["variant"];
1316
+ color?: React.ComponentProps<typeof Button$1>["color"];
1317
+ disabled?: boolean;
1318
+ isLoading?: boolean;
1319
+ type?: "button" | "submit" | "reset";
1320
+ className?: string;
1321
+ };
1322
+ type FormDialogProps = {
1323
+ open?: boolean;
1324
+ onOpenChange?: (open: boolean) => void;
1325
+ title: string;
1326
+ description?: string;
1327
+ children?: React.ReactNode;
1328
+ trigger?: React.ReactNode;
1329
+ /**
1330
+ * Primary action (right side of footer).
1331
+ */
1332
+ confirmAction?: FormDialogAction;
1333
+ /**
1334
+ * Secondary cancel action (right side of footer, outline style).
1335
+ */
1336
+ cancelAction?: FormDialogAction;
1337
+ /**
1338
+ * Optional extra action placed on the left side of the footer.
1339
+ */
1340
+ extraAction?: FormDialogAction;
1341
+ scrollable?: boolean;
1342
+ className?: string;
1343
+ /**
1344
+ * When provided, the confirm button becomes type="submit" and is linked to this form id.
1345
+ * Use together with a <Form id={formId} .../> inside children.
1346
+ */
1347
+ formId?: string;
1348
+ testId?: string;
1349
+ };
1350
+ declare const FormDialog: React.FC<FormDialogProps>;
1351
+
1284
1352
  declare const resloveTimestamp: (timestamp: number) => number;
1285
1353
  declare const getStartDateOfDay: (date: Date) => Date;
1286
1354
  declare const getEndDateOfDay: (date: Date) => Date;
@@ -1720,4 +1788,4 @@ declare const srgbToHex: (color: string) => string;
1720
1788
  */
1721
1789
  declare function getLucideIconNames(): Promise<string[]>;
1722
1790
 
1723
- export { ActionButton, AlertDialog, AlertDialogAction, AlertDialogCancel, AlertDialogContent, AlertDialogDescription, AlertDialogFooter, AlertDialogHeader, AlertDialogOverlay, AlertDialogPortal, AlertDialogTitle, AlertDialogTrigger, Avatar, AvatarGroup, type AvatarGroupProps, type AvatarProps, Button, type ButtonProps, Calendar, Checkbox, Collapsible, type ControlledFormFactoryOptions, type CustomSliderProps, DataTable, type DataTableProps, DatePicker, Dialog, DialogBody, DialogClose, DialogContent, DialogDescription, DialogFooter, DialogHeader, DialogOverlay, DialogPortal, DialogTitle, DialogTrigger, Dropdown, DropdownMenu, DropdownMenuCheckboxItem, DropdownMenuContent, DropdownMenuGroup, DropdownMenuItem, DropdownMenuLabel, DropdownMenuPortal, DropdownMenuRadioGroup, DropdownMenuRadioItem, DropdownMenuSeparator, DropdownMenuShortcut, DropdownMenuSub, DropdownMenuSubContent, DropdownMenuSubTrigger, DropdownMenuTrigger, type DropdownProps, Field, FieldMessage, type FieldMessageProps, type FieldProps, FocusedScrollView, Footer, type FooterProps, type FooterVariant, Form, type FormController, type FormProps, Icon, Input, InputFilter, type InputFilterProps, type InputProps, Label, Loading, type MaskRule, MaskedTextInput, type MaskedTextInputProps, Menu, MenuItem, type MenuItemType, MenuLabel, type MenuOption, type MenuProps, MenuSeparator, Navbar, type NavbarProps, type NavbarVariant, NumberInput, type NumberInputProps, type OptionLike, type Options$1 as Options, OtpInput, OtpInputGroup, type OtpInputGroupProps, type OtpInputProps, PasswordInput, type PasswordInputProps, Popover, PopoverContent, PopoverTrigger, ProgressBar, RadioGroup, RadioGroupItem, Search, type SearchProps, Slider, type SliderProps, Switch, THEME_COLOR_KEYS, Table, TableBody, TableCaption, TableCell, TableFooter, TableHead, TableHeader, TableRow, Tabs, Text, TextArea, type TextAreaProps, TextInput, type ThemeColorKey, Toast$1 as Toast, ToastAction, type ToastActionElement, ToastClose, ToastDescription, type ToastProps, ToastProvider, ToastTitle, ToastViewport, Toaster, Tooltip, TooltipArrow, TooltipContent, TooltipProvider, TooltipSimple, TooltipTrigger, Tree, type TreeData, TreeItem, type TreeItemProps, type TreeProps, type UseControlledFormOptions, type UseOptionBridgeOptions, ValidationHintList, type ValidationHintListProps, type ValidationHintMode, type ValidationHintRule, type ValidationHintState, cn, createControlledForm, createYupResolver, getEndDateOfDay, getLucideIconNames, getStartDateOfDay, getStartEndTimestampOfDay, getThemeColor, getThemeColors, getTimestampUTC, reducer, resloveTimestamp, srgbToHex, toast, useControlledForm, useOptionBridge, usePrevious, useToast };
1791
+ export { ActionButton, AlertDialog, AlertDialogAction, AlertDialogCancel, AlertDialogContent, AlertDialogDescription, AlertDialogFooter, AlertDialogHeader, AlertDialogOverlay, AlertDialogPortal, AlertDialogTitle, AlertDialogTrigger, Avatar, AvatarGroup, type AvatarGroupProps, type AvatarProps, Button, type ButtonProps, Calendar, Checkbox, Collapsible, ConfirmDialog, type ConfirmDialogProps, type ControlledFormFactoryOptions, type CustomSliderProps, DataTable, type DataTableProps, DatePicker, Dialog, DialogBody, DialogClose, DialogContent, DialogDescription, DialogFooter, DialogHeader, DialogOverlay, DialogPortal, DialogTitle, DialogTrigger, Dropdown, DropdownMenu, DropdownMenuCheckboxItem, DropdownMenuContent, DropdownMenuGroup, DropdownMenuItem, DropdownMenuLabel, DropdownMenuPortal, DropdownMenuRadioGroup, DropdownMenuRadioItem, DropdownMenuSeparator, DropdownMenuShortcut, DropdownMenuSub, DropdownMenuSubContent, DropdownMenuSubTrigger, DropdownMenuTrigger, type DropdownProps, Field, FieldMessage, type FieldMessageProps, type FieldProps, FocusedScrollView, Footer, type FooterProps, type FooterVariant, Form, type FormController, FormDialog, type FormDialogAction, type FormDialogProps, type FormProps, Icon, Input, InputFilter, type InputFilterProps, type InputProps, Label, Loading, type MaskRule, MaskedTextInput, type MaskedTextInputProps, Menu, MenuItem, type MenuItemType, MenuLabel, type MenuOption, type MenuProps, MenuSeparator, Navbar, type NavbarProps, type NavbarVariant, NumberInput, type NumberInputProps, type OptionLike, type Options$1 as Options, OtpInput, OtpInputGroup, type OtpInputGroupProps, type OtpInputProps, PasswordInput, type PasswordInputProps, Popover, PopoverContent, PopoverTrigger, ProgressBar, RadioGroup, RadioGroupItem, Search, type SearchProps, Slider, type SliderProps, Switch, THEME_COLOR_KEYS, Table, TableBody, TableCaption, TableCell, TableFooter, TableHead, TableHeader, TableRow, Tabs, Text, TextArea, type TextAreaProps, TextInput, type ThemeColorKey, Toast$1 as Toast, ToastAction, type ToastActionElement, ToastClose, ToastDescription, type ToastProps, ToastProvider, ToastTitle, ToastViewport, Toaster, Tooltip, TooltipArrow, TooltipContent, TooltipProvider, TooltipSimple, TooltipTrigger, Tree, type TreeData, TreeItem, type TreeItemProps, type TreeProps, type UseControlledFormOptions, type UseOptionBridgeOptions, ValidationHintList, type ValidationHintListProps, type ValidationHintMode, type ValidationHintRule, type ValidationHintState, cn, createControlledForm, createYupResolver, getEndDateOfDay, getLucideIconNames, getStartDateOfDay, getStartEndTimestampOfDay, getThemeColor, getThemeColors, getTimestampUTC, reducer, resloveTimestamp, srgbToHex, toast, useControlledForm, useOptionBridge, usePrevious, useToast };
package/dist/index.js CHANGED
@@ -46,6 +46,9 @@ export * from "./components/Tree";
46
46
  export * from "./components/FocusedScrollView/FocusedScrollView";
47
47
  export * from "./components/RadioGroup/RadioGroup";
48
48
  export * from "./components/Form";
49
+ // Patterns
50
+ export * from "./patterns/confirm-dialog/ConfirmDialog";
51
+ export * from "./patterns/form-dialog/FormDialog";
49
52
  // UTILS
50
53
  export { resloveTimestamp, getStartDateOfDay, getEndDateOfDay, getStartEndTimestampOfDay, getTimestampUTC, } from "./utils/datetime";
51
54
  // Hooks
@@ -5,7 +5,7 @@ import * as yup from "yup";
5
5
  import { AlertDialog, AlertDialogContent, AlertDialogHeader, AlertDialogTitle, AlertDialogDescription, AlertDialogFooter, AlertDialogAction, AlertDialogCancel, AlertDialogTrigger, } from "@/components/AlertDialog/AlertDialog";
6
6
  import { useControlledForm, Field } from "@/components/Form";
7
7
  import { TextInput } from "@/components/TextInput/TextInput";
8
- export const ConfirmDialog = ({ open, onOpenChange, title, description, confirmLabel = "Confirm", cancelLabel = "Cancel", onConfirm, onCancel, onClose, trigger, typeToConfirm, hideCancelButton = false, }) => {
8
+ export const ConfirmDialog = ({ open, onOpenChange, title, description, confirmLabel = "Confirm", cancelLabel = "Cancel", onConfirm, onCancel, onClose, trigger, typeToConfirm, hideCancelButton = false, testId, cancelClassName, confirmClassName, }) => {
9
9
  const formId = React.useId();
10
10
  const requiresInput = !!typeToConfirm;
11
11
  const validationSchema = React.useMemo(() => yup.object({
@@ -33,12 +33,13 @@ export const ConfirmDialog = ({ open, onOpenChange, title, description, confirmL
33
33
  onCancel === null || onCancel === void 0 ? void 0 : onCancel();
34
34
  onClose === null || onClose === void 0 ? void 0 : onClose();
35
35
  };
36
- return (_jsxs(AlertDialog, { open: open, onOpenChange: handleOpenChange, children: [trigger && _jsx(AlertDialogTrigger, { asChild: true, children: trigger }), _jsxs(AlertDialogContent, { children: [_jsxs(AlertDialogHeader, { children: [_jsx(AlertDialogTitle, { children: title }), description && (_jsx(AlertDialogDescription, { children: description }))] }), requiresInput && (_jsxs(FormRoot, { id: formId, className: "flex flex-col gap-4 w-full", onSubmit: () => onConfirm === null || onConfirm === void 0 ? void 0 : onConfirm(), children: [_jsxs("p", { className: "typography-small1 text-text-contrast-max", children: ["Type \u201C", typeToConfirm, "\u201D to proceed."] }), _jsx(Field, { name: "confirmInput", component: TextInput, componentProps: {
36
+ return (_jsxs(AlertDialog, { open: open, onOpenChange: handleOpenChange, children: [trigger && _jsx(AlertDialogTrigger, { asChild: true, children: trigger }), _jsxs(AlertDialogContent, { "data-testid": testId, children: [_jsxs(AlertDialogHeader, { children: [_jsx(AlertDialogTitle, { "data-testid": testId && `${testId}-title`, children: title }), description && (_jsx(AlertDialogDescription, { "data-testid": testId && `${testId}-description`, children: description }))] }), requiresInput && (_jsxs(FormRoot, { id: formId, className: "flex flex-col gap-4 w-full", onSubmit: () => onConfirm === null || onConfirm === void 0 ? void 0 : onConfirm(), children: [_jsxs("p", { className: "typography-small1 text-text-contrast-max", children: ["Type \u201C", typeToConfirm, "\u201D to proceed."] }), _jsx(Field, { name: "confirmInput", component: TextInput, componentProps: {
37
37
  label: "Type to confirm",
38
38
  required: true,
39
39
  hasClearIcon: true,
40
40
  keepFooterSpace: true,
41
41
  fullwidth: true,
42
- } })] })), _jsxs(AlertDialogFooter, { children: [!hideCancelButton && (_jsx(AlertDialogCancel, { onClick: handleCancel, children: cancelLabel })), _jsx(AlertDialogAction, { type: "submit", form: requiresInput ? formId : undefined, disabled: requiresInput && !isFormValid, onClick: requiresInput ? undefined : () => onConfirm === null || onConfirm === void 0 ? void 0 : onConfirm(), children: confirmLabel })] })] })] }));
42
+ testId: testId && `${testId}-type-to-confirm-input`,
43
+ } })] })), _jsxs(AlertDialogFooter, { children: [!hideCancelButton && (_jsx(AlertDialogCancel, { className: cancelClassName, "data-testid": testId && `${testId}-cancel-button`, onClick: handleCancel, children: cancelLabel })), _jsx(AlertDialogAction, { type: "submit", form: requiresInput ? formId : undefined, disabled: requiresInput && !isFormValid, onClick: requiresInput ? undefined : () => onConfirm === null || onConfirm === void 0 ? void 0 : onConfirm(), className: confirmClassName, "data-testid": testId && `${testId}-confirm-button`, children: confirmLabel })] })] })] }));
43
44
  };
44
45
  ConfirmDialog.displayName = "ConfirmDialog";
@@ -2,9 +2,9 @@
2
2
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
3
3
  import { Dialog, DialogContent, DialogHeader, DialogTitle, DialogDescription, DialogBody, DialogFooter, DialogTrigger, } from "@/components/Dialog/Dialog";
4
4
  import Button from "@/components/Button/Button";
5
- export const FormDialog = ({ open, onOpenChange, title, description, children, trigger, confirmAction, cancelAction, extraAction, scrollable = false, className, formId, }) => {
5
+ export const FormDialog = ({ open, onOpenChange, title, description, children, trigger, confirmAction, cancelAction, extraAction, scrollable = false, className, formId, testId, }) => {
6
6
  var _a, _b, _c, _d, _e, _f, _g, _h, _j;
7
7
  const hasFooter = confirmAction || cancelAction || extraAction;
8
- return (_jsxs(Dialog, { open: open, onOpenChange: onOpenChange, children: [trigger && _jsx(DialogTrigger, { asChild: true, children: trigger }), _jsxs(DialogContent, { className: className, children: [_jsxs(DialogHeader, { children: [_jsx(DialogTitle, { children: title }), description && (_jsx(DialogDescription, { children: description }))] }), children && (_jsx(DialogBody, { scrollable: scrollable, children: children })), hasFooter && (_jsxs(DialogFooter, { className: extraAction ? "justify-between" : undefined, children: [extraAction && (_jsx(Button, { type: (_a = extraAction.type) !== null && _a !== void 0 ? _a : "button", variant: (_b = extraAction.variant) !== null && _b !== void 0 ? _b : "outline", color: (_c = extraAction.color) !== null && _c !== void 0 ? _c : "secondary", fullwidth: false, disabled: extraAction.disabled, isLoading: extraAction.isLoading, onClick: extraAction.onClick, children: extraAction.label })), _jsxs("div", { className: "flex items-center gap-4", children: [cancelAction && (_jsx(Button, { type: (_d = cancelAction.type) !== null && _d !== void 0 ? _d : "button", variant: (_e = cancelAction.variant) !== null && _e !== void 0 ? _e : "outline", color: (_f = cancelAction.color) !== null && _f !== void 0 ? _f : "primary", fullwidth: false, disabled: cancelAction.disabled, isLoading: cancelAction.isLoading, onClick: cancelAction.onClick, children: cancelAction.label })), confirmAction && (_jsx(Button, { type: formId ? "submit" : ((_g = confirmAction.type) !== null && _g !== void 0 ? _g : "button"), form: formId, variant: (_h = confirmAction.variant) !== null && _h !== void 0 ? _h : "solid", color: (_j = confirmAction.color) !== null && _j !== void 0 ? _j : "primary", fullwidth: false, disabled: confirmAction.disabled, isLoading: confirmAction.isLoading, onClick: confirmAction.onClick, children: confirmAction.label }))] })] }))] })] }));
8
+ return (_jsxs(Dialog, { open: open, onOpenChange: onOpenChange, children: [trigger && _jsx(DialogTrigger, { asChild: true, children: trigger }), _jsxs(DialogContent, { className: className, "data-testid": testId, children: [_jsxs(DialogHeader, { children: [_jsx(DialogTitle, { "data-testid": testId && `${testId}-title`, children: title }), description && (_jsx(DialogDescription, { "data-testid": testId && `${testId}-description`, children: description }))] }), children && (_jsx(DialogBody, { scrollable: scrollable, children: children })), hasFooter && (_jsxs(DialogFooter, { className: extraAction ? "justify-between" : undefined, children: [extraAction && (_jsx(Button, { type: (_a = extraAction.type) !== null && _a !== void 0 ? _a : "button", variant: (_b = extraAction.variant) !== null && _b !== void 0 ? _b : "outline", color: (_c = extraAction.color) !== null && _c !== void 0 ? _c : "secondary", fullwidth: false, disabled: extraAction.disabled, isLoading: extraAction.isLoading, onClick: extraAction.onClick, className: extraAction.className, "data-testid": testId && `${testId}-extra-button`, children: extraAction.label })), _jsxs("div", { className: "flex items-center gap-4", children: [cancelAction && (_jsx(Button, { type: (_d = cancelAction.type) !== null && _d !== void 0 ? _d : "button", variant: (_e = cancelAction.variant) !== null && _e !== void 0 ? _e : "outline", color: (_f = cancelAction.color) !== null && _f !== void 0 ? _f : "primary", fullwidth: false, disabled: cancelAction.disabled, isLoading: cancelAction.isLoading, onClick: cancelAction.onClick, className: cancelAction.className, "data-testid": testId && `${testId}-cancel-button`, children: cancelAction.label })), confirmAction && (_jsx(Button, { type: formId ? "submit" : (_g = confirmAction.type) !== null && _g !== void 0 ? _g : "button", form: formId, variant: (_h = confirmAction.variant) !== null && _h !== void 0 ? _h : "solid", color: (_j = confirmAction.color) !== null && _j !== void 0 ? _j : "primary", fullwidth: false, disabled: confirmAction.disabled, isLoading: confirmAction.isLoading, onClick: confirmAction.onClick, className: confirmAction.className, "data-testid": testId && `${testId}-confirm-button`, children: confirmAction.label }))] })] }))] })] }));
9
9
  };
10
10
  FormDialog.displayName = "FormDialog";
@@ -4259,6 +4259,10 @@ input[type=number] {
4259
4259
  min-width: 18rem;
4260
4260
  }
4261
4261
 
4262
+ .min-w-\[100px\] {
4263
+ min-width: 100px;
4264
+ }
4265
+
4262
4266
  .min-w-\[154px\] {
4263
4267
  min-width: 154px;
4264
4268
  }
@@ -4641,6 +4645,10 @@ input[type=number] {
4641
4645
  white-space: nowrap;
4642
4646
  }
4643
4647
 
4648
+ .whitespace-pre-line {
4649
+ white-space: pre-line;
4650
+ }
4651
+
4644
4652
  .break-all {
4645
4653
  word-break: break-all;
4646
4654
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@rovula/ui",
3
- "version": "0.1.15",
3
+ "version": "0.1.18",
4
4
  "main": "dist/cjs/bundle.js",
5
5
  "module": "dist/esm/bundle.js",
6
6
  "types": "dist/index.d.ts",
@@ -101,7 +101,7 @@ const AlertDialogAction = React.forwardRef<
101
101
  ref={ref}
102
102
  className={cn(
103
103
  buttonVariants({ fullwidth: false }),
104
- "w-[100px] justify-center",
104
+ "min-w-[100px] justify-center",
105
105
  className,
106
106
  )}
107
107
  {...props}
@@ -117,7 +117,7 @@ const AlertDialogCancel = React.forwardRef<
117
117
  ref={ref}
118
118
  className={cn(
119
119
  buttonVariants({ fullwidth: false, variant: "outline" }),
120
- "w-[100px] justify-center",
120
+ "min-w-[100px] justify-center whitespace-pre-line",
121
121
  className,
122
122
  )}
123
123
  {...props}
package/src/index.ts CHANGED
@@ -52,6 +52,10 @@ export * from "./components/FocusedScrollView/FocusedScrollView";
52
52
  export * from "./components/RadioGroup/RadioGroup";
53
53
  export * from "./components/Form";
54
54
 
55
+ // Patterns
56
+ export * from "./patterns/confirm-dialog/ConfirmDialog";
57
+ export * from "./patterns/form-dialog/FormDialog";
58
+
55
59
  // Export component types
56
60
  export type { ButtonProps } from "./components/Button/Button";
57
61
  export type { InputProps } from "./components/TextInput/TextInput";
@@ -37,6 +37,9 @@ export type ConfirmDialogProps = {
37
37
  * When true, hides the cancel button — useful for info/error alerts that only need one action.
38
38
  */
39
39
  hideCancelButton?: boolean;
40
+ testId?: string;
41
+ cancelClassName?: string;
42
+ confirmClassName?: string;
40
43
  };
41
44
 
42
45
  type ConfirmFormValues = { confirmInput: string };
@@ -54,6 +57,9 @@ export const ConfirmDialog: React.FC<ConfirmDialogProps> = ({
54
57
  trigger,
55
58
  typeToConfirm,
56
59
  hideCancelButton = false,
60
+ testId,
61
+ cancelClassName,
62
+ confirmClassName,
57
63
  }) => {
58
64
  const formId = React.useId();
59
65
  const requiresInput = !!typeToConfirm;
@@ -99,11 +105,15 @@ export const ConfirmDialog: React.FC<ConfirmDialogProps> = ({
99
105
  return (
100
106
  <AlertDialog open={open} onOpenChange={handleOpenChange}>
101
107
  {trigger && <AlertDialogTrigger asChild>{trigger}</AlertDialogTrigger>}
102
- <AlertDialogContent>
108
+ <AlertDialogContent data-testid={testId}>
103
109
  <AlertDialogHeader>
104
- <AlertDialogTitle>{title}</AlertDialogTitle>
110
+ <AlertDialogTitle data-testid={testId && `${testId}-title`}>
111
+ {title}
112
+ </AlertDialogTitle>
105
113
  {description && (
106
- <AlertDialogDescription>{description}</AlertDialogDescription>
114
+ <AlertDialogDescription data-testid={testId && `${testId}-description`}>
115
+ {description}
116
+ </AlertDialogDescription>
107
117
  )}
108
118
  </AlertDialogHeader>
109
119
 
@@ -125,6 +135,7 @@ export const ConfirmDialog: React.FC<ConfirmDialogProps> = ({
125
135
  hasClearIcon: true,
126
136
  keepFooterSpace: true,
127
137
  fullwidth: true,
138
+ testId: testId && `${testId}-type-to-confirm-input`,
128
139
  }}
129
140
  />
130
141
  </FormRoot>
@@ -132,7 +143,11 @@ export const ConfirmDialog: React.FC<ConfirmDialogProps> = ({
132
143
 
133
144
  <AlertDialogFooter>
134
145
  {!hideCancelButton && (
135
- <AlertDialogCancel onClick={handleCancel}>
146
+ <AlertDialogCancel
147
+ className={cancelClassName}
148
+ data-testid={testId && `${testId}-cancel-button`}
149
+ onClick={handleCancel}
150
+ >
136
151
  {cancelLabel}
137
152
  </AlertDialogCancel>
138
153
  )}
@@ -141,6 +156,8 @@ export const ConfirmDialog: React.FC<ConfirmDialogProps> = ({
141
156
  form={requiresInput ? formId : undefined}
142
157
  disabled={requiresInput && !isFormValid}
143
158
  onClick={requiresInput ? undefined : () => onConfirm?.()}
159
+ className={confirmClassName}
160
+ data-testid={testId && `${testId}-confirm-button`}
144
161
  >
145
162
  {confirmLabel}
146
163
  </AlertDialogAction>
@@ -22,6 +22,7 @@ export type FormDialogAction = {
22
22
  disabled?: boolean;
23
23
  isLoading?: boolean;
24
24
  type?: "button" | "submit" | "reset";
25
+ className?: string;
25
26
  };
26
27
 
27
28
  export type FormDialogProps = {
@@ -50,6 +51,7 @@ export type FormDialogProps = {
50
51
  * Use together with a <Form id={formId} .../> inside children.
51
52
  */
52
53
  formId?: string;
54
+ testId?: string;
53
55
  };
54
56
 
55
57
  export const FormDialog: React.FC<FormDialogProps> = ({
@@ -65,17 +67,20 @@ export const FormDialog: React.FC<FormDialogProps> = ({
65
67
  scrollable = false,
66
68
  className,
67
69
  formId,
70
+ testId,
68
71
  }) => {
69
72
  const hasFooter = confirmAction || cancelAction || extraAction;
70
73
 
71
74
  return (
72
75
  <Dialog open={open} onOpenChange={onOpenChange}>
73
76
  {trigger && <DialogTrigger asChild>{trigger}</DialogTrigger>}
74
- <DialogContent className={className}>
77
+ <DialogContent className={className} data-testid={testId}>
75
78
  <DialogHeader>
76
- <DialogTitle>{title}</DialogTitle>
79
+ <DialogTitle data-testid={testId && `${testId}-title`}>{title}</DialogTitle>
77
80
  {description && (
78
- <DialogDescription>{description}</DialogDescription>
81
+ <DialogDescription data-testid={testId && `${testId}-description`}>
82
+ {description}
83
+ </DialogDescription>
79
84
  )}
80
85
  </DialogHeader>
81
86
 
@@ -94,6 +99,8 @@ export const FormDialog: React.FC<FormDialogProps> = ({
94
99
  disabled={extraAction.disabled}
95
100
  isLoading={extraAction.isLoading}
96
101
  onClick={extraAction.onClick}
102
+ className={extraAction.className}
103
+ data-testid={testId && `${testId}-extra-button`}
97
104
  >
98
105
  {extraAction.label}
99
106
  </Button>
@@ -108,13 +115,15 @@ export const FormDialog: React.FC<FormDialogProps> = ({
108
115
  disabled={cancelAction.disabled}
109
116
  isLoading={cancelAction.isLoading}
110
117
  onClick={cancelAction.onClick}
118
+ className={cancelAction.className}
119
+ data-testid={testId && `${testId}-cancel-button`}
111
120
  >
112
121
  {cancelAction.label}
113
122
  </Button>
114
123
  )}
115
124
  {confirmAction && (
116
125
  <Button
117
- type={formId ? "submit" : (confirmAction.type ?? "button")}
126
+ type={formId ? "submit" : confirmAction.type ?? "button"}
118
127
  form={formId}
119
128
  variant={confirmAction.variant ?? "solid"}
120
129
  color={confirmAction.color ?? "primary"}
@@ -122,6 +131,8 @@ export const FormDialog: React.FC<FormDialogProps> = ({
122
131
  disabled={confirmAction.disabled}
123
132
  isLoading={confirmAction.isLoading}
124
133
  onClick={confirmAction.onClick}
134
+ className={confirmAction.className}
135
+ data-testid={testId && `${testId}-confirm-button`}
125
136
  >
126
137
  {confirmAction.label}
127
138
  </Button>