@rovula/ui 0.1.13 → 0.1.15

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 (41) hide show
  1. package/dist/cjs/bundle.css +7 -36
  2. package/dist/cjs/bundle.js +2 -2
  3. package/dist/cjs/bundle.js.map +1 -1
  4. package/dist/cjs/types/components/AlertDialog/AlertDialog.stories.d.ts +3 -0
  5. package/dist/cjs/types/components/Dialog/Dialog.stories.d.ts +4 -1
  6. package/dist/cjs/types/components/Form/Form.d.ts +1 -1
  7. package/dist/cjs/types/patterns/confirm-dialog/ConfirmDialog.d.ts +24 -0
  8. package/dist/cjs/types/patterns/confirm-dialog/ConfirmDialog.stories.d.ts +53 -0
  9. package/dist/cjs/types/patterns/form-dialog/FormDialog.d.ts +39 -0
  10. package/dist/cjs/types/patterns/form-dialog/FormDialog.stories.d.ts +62 -0
  11. package/dist/components/AlertDialog/AlertDialog.js +5 -5
  12. package/dist/components/AlertDialog/AlertDialog.stories.js +22 -0
  13. package/dist/components/Dialog/Dialog.js +6 -6
  14. package/dist/components/Dialog/Dialog.stories.js +6 -34
  15. package/dist/components/Form/Form.js +15 -4
  16. package/dist/esm/bundle.css +7 -36
  17. package/dist/esm/bundle.js +1 -1
  18. package/dist/esm/bundle.js.map +1 -1
  19. package/dist/esm/types/components/AlertDialog/AlertDialog.stories.d.ts +3 -0
  20. package/dist/esm/types/components/Dialog/Dialog.stories.d.ts +4 -1
  21. package/dist/esm/types/components/Form/Form.d.ts +1 -1
  22. package/dist/esm/types/patterns/confirm-dialog/ConfirmDialog.d.ts +24 -0
  23. package/dist/esm/types/patterns/confirm-dialog/ConfirmDialog.stories.d.ts +53 -0
  24. package/dist/esm/types/patterns/form-dialog/FormDialog.d.ts +39 -0
  25. package/dist/esm/types/patterns/form-dialog/FormDialog.stories.d.ts +62 -0
  26. package/dist/index.d.ts +1 -1
  27. package/dist/patterns/confirm-dialog/ConfirmDialog.js +44 -0
  28. package/dist/patterns/confirm-dialog/ConfirmDialog.stories.js +103 -0
  29. package/dist/patterns/form-dialog/FormDialog.js +10 -0
  30. package/dist/patterns/form-dialog/FormDialog.stories.js +223 -0
  31. package/dist/src/theme/global.css +9 -40
  32. package/package.json +1 -1
  33. package/src/components/AlertDialog/AlertDialog.stories.tsx +69 -2
  34. package/src/components/AlertDialog/AlertDialog.tsx +13 -15
  35. package/src/components/Dialog/Dialog.stories.tsx +62 -99
  36. package/src/components/Dialog/Dialog.tsx +6 -12
  37. package/src/components/Form/Form.tsx +19 -4
  38. package/src/patterns/confirm-dialog/ConfirmDialog.stories.tsx +193 -0
  39. package/src/patterns/confirm-dialog/ConfirmDialog.tsx +153 -0
  40. package/src/patterns/form-dialog/FormDialog.stories.tsx +437 -0
  41. package/src/patterns/form-dialog/FormDialog.tsx +137 -0
@@ -0,0 +1,193 @@
1
+ import React from "react";
2
+ import type { Meta, StoryObj } from "@storybook/react";
3
+ import { useArgs } from "@storybook/preview-api";
4
+ import { ConfirmDialog } from "./ConfirmDialog";
5
+ import Button from "@/components/Button/Button";
6
+
7
+ const meta = {
8
+ title: "Patterns/ConfirmDialog",
9
+ component: ConfirmDialog,
10
+ tags: ["autodocs"],
11
+ parameters: {
12
+ layout: "fullscreen",
13
+ },
14
+ decorators: [
15
+ (Story) => (
16
+ <div className="p-5 flex w-full">
17
+ <Story />
18
+ </div>
19
+ ),
20
+ ],
21
+ argTypes: {
22
+ open: { control: "boolean" },
23
+ typeToConfirm: { control: "text" },
24
+ title: { control: "text" },
25
+ description: { control: "text" },
26
+ confirmLabel: { control: "text" },
27
+ cancelLabel: { control: "text" },
28
+ },
29
+ } satisfies Meta<typeof ConfirmDialog>;
30
+
31
+ export default meta;
32
+ type Story = StoryObj<typeof ConfirmDialog>;
33
+
34
+ export const Default: Story = {
35
+ args: {
36
+ open: false,
37
+ title: "Are you sure?",
38
+ description: "This action cannot be undone.",
39
+ confirmLabel: "Confirm",
40
+ cancelLabel: "Cancel",
41
+ },
42
+ render: (args) => {
43
+ const [{ open }, updateArgs] = useArgs();
44
+ return (
45
+ <ConfirmDialog
46
+ {...args}
47
+ open={open}
48
+ onOpenChange={(next) => updateArgs({ open: next })}
49
+ onConfirm={() => updateArgs({ open: false })}
50
+ onCancel={() => updateArgs({ open: false })}
51
+ trigger={
52
+ <Button fullwidth={false} onClick={() => updateArgs({ open: true })}>
53
+ Open
54
+ </Button>
55
+ }
56
+ />
57
+ );
58
+ },
59
+ };
60
+
61
+ export const WithoutDescription: Story = {
62
+ args: {
63
+ open: false,
64
+ title: "Delete item?",
65
+ confirmLabel: "Delete",
66
+ cancelLabel: "Cancel",
67
+ },
68
+ render: (args) => {
69
+ const [{ open }, updateArgs] = useArgs();
70
+ return (
71
+ <ConfirmDialog
72
+ {...args}
73
+ open={open}
74
+ onOpenChange={(next) => updateArgs({ open: next })}
75
+ onConfirm={() => updateArgs({ open: false })}
76
+ onCancel={() => updateArgs({ open: false })}
77
+ trigger={
78
+ <Button
79
+ fullwidth={false}
80
+ color="error"
81
+ onClick={() => updateArgs({ open: true })}
82
+ >
83
+ Delete
84
+ </Button>
85
+ }
86
+ />
87
+ );
88
+ },
89
+ };
90
+
91
+ export const WithoutCancelButton: Story = {
92
+ args: {
93
+ open: false,
94
+ title: "Are you sure?",
95
+ description: "This action cannot be undone.",
96
+ confirmLabel: "Confirm",
97
+ hideCancelButton: true,
98
+ },
99
+ render: (args) => {
100
+ const [{ open }, updateArgs] = useArgs();
101
+ return (
102
+ <ConfirmDialog
103
+ {...args}
104
+ open={open}
105
+ onOpenChange={(next) => updateArgs({ open: next })}
106
+ onConfirm={() => updateArgs({ open: false })}
107
+ onCancel={() => updateArgs({ open: false })}
108
+ trigger={
109
+ <Button
110
+ fullwidth={false}
111
+ color="error"
112
+ onClick={() => updateArgs({ open: true })}
113
+ >
114
+ Confirm
115
+ </Button>
116
+ }
117
+ />
118
+ );
119
+ },
120
+ };
121
+
122
+ export const RequireConfirmText: Story = {
123
+ args: {
124
+ open: false,
125
+ title: "Title",
126
+ description: "Subtitle description",
127
+ confirmLabel: "Confirm",
128
+ cancelLabel: "Cancel",
129
+ typeToConfirm: "confirm",
130
+ },
131
+ render: (args) => {
132
+ const [{ open }, updateArgs] = useArgs();
133
+ return (
134
+ <ConfirmDialog
135
+ {...args}
136
+ open={open}
137
+ onOpenChange={(next) => updateArgs({ open: next })}
138
+ onConfirm={() => updateArgs({ open: false })}
139
+ onCancel={() => updateArgs({ open: false })}
140
+ trigger={
141
+ <Button fullwidth={false} onClick={() => updateArgs({ open: true })}>
142
+ Open (require confirm)
143
+ </Button>
144
+ }
145
+ />
146
+ );
147
+ },
148
+ };
149
+
150
+ export const FigmaDefaultOpen: Story = {
151
+ args: {
152
+ open: false,
153
+ title: "Title",
154
+ description: "Subtitle description",
155
+ confirmLabel: "Confirm",
156
+ cancelLabel: "Cancel",
157
+ },
158
+ render: (args) => {
159
+ const [{ open }, updateArgs] = useArgs();
160
+ return (
161
+ <ConfirmDialog
162
+ {...args}
163
+ open={open}
164
+ onOpenChange={(next) => updateArgs({ open: next })}
165
+ onConfirm={() => updateArgs({ open: false })}
166
+ onCancel={() => updateArgs({ open: false })}
167
+ />
168
+ );
169
+ },
170
+ };
171
+
172
+ export const FigmaRequirePasswordDefaultOpen: Story = {
173
+ args: {
174
+ open: false,
175
+ title: "Title",
176
+ description: "Subtitle description",
177
+ confirmLabel: "Confirm",
178
+ cancelLabel: "Cancel",
179
+ typeToConfirm: "confirm",
180
+ },
181
+ render: (args) => {
182
+ const [{ open }, updateArgs] = useArgs();
183
+ return (
184
+ <ConfirmDialog
185
+ {...args}
186
+ open={open}
187
+ onOpenChange={(next) => updateArgs({ open: next })}
188
+ onConfirm={() => updateArgs({ open: false })}
189
+ onCancel={() => updateArgs({ open: false })}
190
+ />
191
+ );
192
+ },
193
+ };
@@ -0,0 +1,153 @@
1
+ "use client";
2
+
3
+ import * as React from "react";
4
+ import * as yup from "yup";
5
+ import {
6
+ AlertDialog,
7
+ AlertDialogContent,
8
+ AlertDialogHeader,
9
+ AlertDialogTitle,
10
+ AlertDialogDescription,
11
+ AlertDialogFooter,
12
+ AlertDialogAction,
13
+ AlertDialogCancel,
14
+ AlertDialogTrigger,
15
+ } from "@/components/AlertDialog/AlertDialog";
16
+ import { useControlledForm, Field } from "@/components/Form";
17
+ import { TextInput } from "@/components/TextInput/TextInput";
18
+
19
+ export type ConfirmDialogProps = {
20
+ open?: boolean;
21
+ onOpenChange?: (open: boolean) => void;
22
+ title: string;
23
+ description?: string;
24
+ confirmLabel?: string;
25
+ cancelLabel?: string;
26
+ onConfirm?: () => void;
27
+ onCancel?: () => void;
28
+ /** Fires whenever the dialog closes, regardless of how (cancel button, overlay, Escape). */
29
+ onClose?: () => void;
30
+ trigger?: React.ReactNode;
31
+ /**
32
+ * When provided, the user must type this exact text before the confirm button is enabled.
33
+ * e.g. typeToConfirm="confirm" or typeToConfirm="delete"
34
+ */
35
+ typeToConfirm?: string;
36
+ /**
37
+ * When true, hides the cancel button — useful for info/error alerts that only need one action.
38
+ */
39
+ hideCancelButton?: boolean;
40
+ };
41
+
42
+ type ConfirmFormValues = { confirmInput: string };
43
+
44
+ export const ConfirmDialog: React.FC<ConfirmDialogProps> = ({
45
+ open,
46
+ onOpenChange,
47
+ title,
48
+ description,
49
+ confirmLabel = "Confirm",
50
+ cancelLabel = "Cancel",
51
+ onConfirm,
52
+ onCancel,
53
+ onClose,
54
+ trigger,
55
+ typeToConfirm,
56
+ hideCancelButton = false,
57
+ }) => {
58
+ const formId = React.useId();
59
+ const requiresInput = !!typeToConfirm;
60
+
61
+ const validationSchema = React.useMemo(
62
+ () =>
63
+ yup.object({
64
+ confirmInput: yup
65
+ .string()
66
+ .required("This field is required.")
67
+ .test(
68
+ "type-to-confirm",
69
+ `Please type '${typeToConfirm}' to proceed`,
70
+ (value) => value === typeToConfirm,
71
+ ),
72
+ }),
73
+ [typeToConfirm],
74
+ );
75
+
76
+ const { methods, FormRoot } = useControlledForm<ConfirmFormValues>({
77
+ defaultValues: { confirmInput: "" },
78
+ validationSchema,
79
+ mode: "onTouched",
80
+ reValidateMode: "onChange",
81
+ });
82
+
83
+ const isFormValid = methods.formState.isValid;
84
+
85
+ const handleOpenChange = (nextOpen: boolean) => {
86
+ if (!nextOpen) {
87
+ methods.reset();
88
+ onClose?.();
89
+ }
90
+ onOpenChange?.(nextOpen);
91
+ };
92
+
93
+ const handleCancel = () => {
94
+ methods.reset();
95
+ onCancel?.();
96
+ onClose?.();
97
+ };
98
+
99
+ return (
100
+ <AlertDialog open={open} onOpenChange={handleOpenChange}>
101
+ {trigger && <AlertDialogTrigger asChild>{trigger}</AlertDialogTrigger>}
102
+ <AlertDialogContent>
103
+ <AlertDialogHeader>
104
+ <AlertDialogTitle>{title}</AlertDialogTitle>
105
+ {description && (
106
+ <AlertDialogDescription>{description}</AlertDialogDescription>
107
+ )}
108
+ </AlertDialogHeader>
109
+
110
+ {requiresInput && (
111
+ <FormRoot
112
+ id={formId}
113
+ className="flex flex-col gap-4 w-full"
114
+ onSubmit={() => onConfirm?.()}
115
+ >
116
+ <p className="typography-small1 text-text-contrast-max">
117
+ Type &ldquo;{typeToConfirm}&rdquo; to proceed.
118
+ </p>
119
+ <Field<ConfirmFormValues, "confirmInput">
120
+ name="confirmInput"
121
+ component={TextInput}
122
+ componentProps={{
123
+ label: "Type to confirm",
124
+ required: true,
125
+ hasClearIcon: true,
126
+ keepFooterSpace: true,
127
+ fullwidth: true,
128
+ }}
129
+ />
130
+ </FormRoot>
131
+ )}
132
+
133
+ <AlertDialogFooter>
134
+ {!hideCancelButton && (
135
+ <AlertDialogCancel onClick={handleCancel}>
136
+ {cancelLabel}
137
+ </AlertDialogCancel>
138
+ )}
139
+ <AlertDialogAction
140
+ type="submit"
141
+ form={requiresInput ? formId : undefined}
142
+ disabled={requiresInput && !isFormValid}
143
+ onClick={requiresInput ? undefined : () => onConfirm?.()}
144
+ >
145
+ {confirmLabel}
146
+ </AlertDialogAction>
147
+ </AlertDialogFooter>
148
+ </AlertDialogContent>
149
+ </AlertDialog>
150
+ );
151
+ };
152
+
153
+ ConfirmDialog.displayName = "ConfirmDialog";