@rovula/ui 0.1.26 → 0.1.28

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 (29) hide show
  1. package/dist/cjs/bundle.css +12 -0
  2. package/dist/cjs/bundle.js +3 -3
  3. package/dist/cjs/bundle.js.map +1 -1
  4. package/dist/cjs/types/components/Avatar/Avatar.d.ts +1 -1
  5. package/dist/cjs/types/components/Avatar/Avatar.stories.d.ts +1 -1
  6. package/dist/cjs/types/components/Avatar/Avatar.styles.d.ts +1 -0
  7. package/dist/cjs/types/patterns/confirm-dialog/ConfirmDialog.d.ts +2 -0
  8. package/dist/cjs/types/patterns/confirm-dialog/ConfirmDialog.stories.d.ts +3 -0
  9. package/dist/components/Avatar/Avatar.js +2 -1
  10. package/dist/components/Avatar/Avatar.styles.js +3 -0
  11. package/dist/components/Avatar/AvatarBase.js +1 -1
  12. package/dist/esm/bundle.css +12 -0
  13. package/dist/esm/bundle.js +2 -2
  14. package/dist/esm/bundle.js.map +1 -1
  15. package/dist/esm/types/components/Avatar/Avatar.d.ts +1 -1
  16. package/dist/esm/types/components/Avatar/Avatar.stories.d.ts +1 -1
  17. package/dist/esm/types/components/Avatar/Avatar.styles.d.ts +1 -0
  18. package/dist/esm/types/patterns/confirm-dialog/ConfirmDialog.d.ts +2 -0
  19. package/dist/esm/types/patterns/confirm-dialog/ConfirmDialog.stories.d.ts +3 -0
  20. package/dist/index.d.ts +3 -1
  21. package/dist/patterns/confirm-dialog/ConfirmDialog.js +18 -7
  22. package/dist/patterns/confirm-dialog/ConfirmDialog.stories.js +33 -0
  23. package/dist/src/theme/global.css +15 -0
  24. package/package.json +1 -1
  25. package/src/components/Avatar/Avatar.styles.ts +4 -1
  26. package/src/components/Avatar/Avatar.tsx +3 -2
  27. package/src/components/Avatar/AvatarBase.tsx +3 -3
  28. package/src/patterns/confirm-dialog/ConfirmDialog.stories.tsx +71 -0
  29. package/src/patterns/confirm-dialog/ConfirmDialog.tsx +24 -9
@@ -22,7 +22,7 @@ type AvatarImageProps = {
22
22
  type: "image";
23
23
  } & BaseAvatarProps;
24
24
  type AvatarIconProps = {
25
- icon: string;
25
+ icon: React.ReactNode;
26
26
  type: "icon";
27
27
  } & BaseAvatarProps;
28
28
  export type AvatarProps = AvatarTextProps | AvatarImageProps | AvatarIconProps;
@@ -34,7 +34,7 @@ declare const meta: {
34
34
  fallbackClassName?: string | undefined;
35
35
  style?: React.CSSProperties | undefined;
36
36
  } | {
37
- icon: string & React.ReactNode;
37
+ icon: React.ReactNode;
38
38
  type: "icon";
39
39
  imageUrl?: string | undefined;
40
40
  text?: string | undefined;
@@ -1,4 +1,5 @@
1
1
  export declare const avatarVariants: (props?: ({
2
2
  size?: "sm" | "md" | "lg" | "xxs" | "xs" | null | undefined;
3
3
  rounded?: "none" | "normal" | "full" | null | undefined;
4
+ hasImage?: boolean | null | undefined;
4
5
  } & import("class-variance-authority/dist/types").ClassProp) | undefined) => string;
@@ -21,6 +21,8 @@ export type ConfirmDialogProps = {
21
21
  * When true, hides the cancel button — useful for info/error alerts that only need one action.
22
22
  */
23
23
  hideCancelButton?: boolean;
24
+ /** When true, shows a loading spinner on the confirm button and disables all interactive controls. */
25
+ isLoading?: boolean;
24
26
  testId?: string;
25
27
  cancelClassName?: string;
26
28
  confirmClassName?: string;
@@ -22,6 +22,7 @@ declare const meta: {
22
22
  trigger?: React.ReactNode;
23
23
  typeToConfirm?: string | undefined;
24
24
  hideCancelButton?: boolean | undefined;
25
+ isLoading?: boolean | undefined;
25
26
  testId?: string | undefined;
26
27
  cancelClassName?: string | undefined;
27
28
  confirmClassName?: string | undefined;
@@ -55,3 +56,5 @@ export declare const WithoutCancelButton: Story;
55
56
  export declare const RequireConfirmText: Story;
56
57
  export declare const FigmaDefaultOpen: Story;
57
58
  export declare const FigmaRequirePasswordDefaultOpen: Story;
59
+ export declare const WithLoading: Story;
60
+ export declare const WithLoadingAndTypeToConfirm: Story;
package/dist/index.d.ts CHANGED
@@ -542,7 +542,7 @@ type AvatarImageProps = {
542
542
  type: "image";
543
543
  } & BaseAvatarProps;
544
544
  type AvatarIconProps = {
545
- icon: string;
545
+ icon: React.ReactNode;
546
546
  type: "icon";
547
547
  } & BaseAvatarProps;
548
548
  type AvatarProps = AvatarTextProps | AvatarImageProps | AvatarIconProps;
@@ -1318,6 +1318,8 @@ type ConfirmDialogProps = {
1318
1318
  * When true, hides the cancel button — useful for info/error alerts that only need one action.
1319
1319
  */
1320
1320
  hideCancelButton?: boolean;
1321
+ /** When true, shows a loading spinner on the confirm button and disables all interactive controls. */
1322
+ isLoading?: boolean;
1321
1323
  testId?: string;
1322
1324
  cancelClassName?: string;
1323
1325
  confirmClassName?: string;
@@ -5,7 +5,8 @@ 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, children, confirmLabel = "Confirm", cancelLabel = "Cancel", onConfirm, onCancel, onClose, trigger, typeToConfirm, hideCancelButton = false, testId, cancelClassName, confirmClassName, }) => {
8
+ import Loading from "@/components/Loading/Loading";
9
+ export const ConfirmDialog = ({ open, onOpenChange, title, description, children, confirmLabel = "Confirm", cancelLabel = "Cancel", onConfirm, onCancel, onClose, trigger, typeToConfirm, hideCancelButton = false, isLoading = false, testId, cancelClassName, confirmClassName, }) => {
9
10
  const requiresInput = !!typeToConfirm;
10
11
  const validationSchema = React.useMemo(() => yup.object({
11
12
  confirmInput: yup
@@ -20,20 +21,24 @@ export const ConfirmDialog = ({ open, onOpenChange, title, description, children
20
21
  reValidateMode: "onChange",
21
22
  });
22
23
  const isFormValid = methods.formState.isValid;
24
+ React.useEffect(() => {
25
+ if (!open) {
26
+ methods.reset();
27
+ }
28
+ }, [open]);
23
29
  const handleOpenChange = (nextOpen) => {
30
+ if (isLoading)
31
+ return;
24
32
  if (!nextOpen) {
25
- methods.reset();
26
33
  onClose === null || onClose === void 0 ? void 0 : onClose();
27
34
  }
28
35
  onOpenChange === null || onOpenChange === void 0 ? void 0 : onOpenChange(nextOpen);
29
36
  };
30
37
  const handleCancel = () => {
31
- methods.reset();
32
38
  onCancel === null || onCancel === void 0 ? void 0 : onCancel();
33
39
  onClose === null || onClose === void 0 ? void 0 : onClose();
34
40
  };
35
41
  const handleSubmit = () => {
36
- methods.reset();
37
42
  onConfirm === null || onConfirm === void 0 ? void 0 : onConfirm();
38
43
  };
39
44
  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 }))] }), children, requiresInput && (_jsxs(FormRoot, { className: "flex flex-col gap-4 w-full", onSubmit: handleSubmit, children: [_jsxs("p", { className: "typography-small1 text-text-contrast-max", children: ["Type \u201C", typeToConfirm, "\u201D to proceed."] }), _jsx(Field, { name: "confirmInput", component: TextInput, componentProps: {
@@ -43,8 +48,14 @@ export const ConfirmDialog = ({ open, onOpenChange, title, description, children
43
48
  keepFooterSpace: true,
44
49
  fullwidth: true,
45
50
  testId: testId && `${testId}-type-to-confirm-input`,
46
- } })] })), _jsxs(AlertDialogFooter, { children: [!hideCancelButton && (_jsx(AlertDialogCancel, { className: cancelClassName, "data-testid": testId && `${testId}-cancel-button`, onClick: handleCancel, children: cancelLabel })), _jsx(AlertDialogAction, { type: "button", disabled: requiresInput && !isFormValid, onClick: requiresInput
47
- ? () => methods.handleSubmit(handleSubmit)()
48
- : () => onConfirm === null || onConfirm === void 0 ? void 0 : onConfirm(), className: confirmClassName, "data-testid": testId && `${testId}-confirm-button`, children: confirmLabel })] })] })] }));
51
+ }, disabled: isLoading })] })), _jsxs(AlertDialogFooter, { children: [!hideCancelButton && (_jsx(AlertDialogCancel, { className: cancelClassName, "data-testid": testId && `${testId}-cancel-button`, onClick: handleCancel, disabled: isLoading, children: cancelLabel })), _jsxs(AlertDialogAction, { type: "button", disabled: isLoading || (requiresInput && !isFormValid), onClick: (e) => {
52
+ e.preventDefault();
53
+ if (requiresInput) {
54
+ methods.handleSubmit(handleSubmit)();
55
+ }
56
+ else {
57
+ onConfirm === null || onConfirm === void 0 ? void 0 : onConfirm();
58
+ }
59
+ }, className: confirmClassName, "data-testid": testId && `${testId}-confirm-button`, children: [isLoading && _jsx(Loading, {}), confirmLabel] })] })] })] }));
49
60
  };
50
61
  ConfirmDialog.displayName = "ConfirmDialog";
@@ -101,3 +101,36 @@ export const FigmaRequirePasswordDefaultOpen = {
101
101
  return (_jsx(ConfirmDialog, Object.assign({}, args, { open: open, onOpenChange: (next) => updateArgs({ open: next }), onConfirm: () => updateArgs({ open: false }), onCancel: () => updateArgs({ open: false }) })));
102
102
  },
103
103
  };
104
+ export const WithLoading = {
105
+ args: {
106
+ open: false,
107
+ title: "Delete project?",
108
+ description: "This action cannot be undone.",
109
+ confirmLabel: "Delete",
110
+ cancelLabel: "Cancel",
111
+ },
112
+ render: (args) => {
113
+ const [{ open, isLoading }, updateArgs] = useArgs();
114
+ return (_jsx(ConfirmDialog, Object.assign({}, args, { open: open, isLoading: isLoading, onOpenChange: (next) => updateArgs({ open: next }), onConfirm: () => {
115
+ updateArgs({ isLoading: true });
116
+ setTimeout(() => updateArgs({ isLoading: false, open: false }), 2000);
117
+ }, onCancel: () => updateArgs({ open: false }), trigger: _jsx(Button, { fullwidth: false, color: "error", onClick: () => updateArgs({ open: true, isLoading: false }), children: "Delete" }) })));
118
+ },
119
+ };
120
+ export const WithLoadingAndTypeToConfirm = {
121
+ args: {
122
+ open: false,
123
+ title: "Delete project?",
124
+ description: "This action cannot be undone.",
125
+ confirmLabel: "Delete",
126
+ cancelLabel: "Cancel",
127
+ typeToConfirm: "delete",
128
+ },
129
+ render: (args) => {
130
+ const [{ open, isLoading }, updateArgs] = useArgs();
131
+ return (_jsx(ConfirmDialog, Object.assign({}, args, { open: open, isLoading: isLoading, onOpenChange: (next) => updateArgs({ open: next }), onConfirm: () => {
132
+ updateArgs({ isLoading: true });
133
+ setTimeout(() => updateArgs({ isLoading: false, open: false }), 5000);
134
+ }, onCancel: () => updateArgs({ open: false }), trigger: _jsx(Button, { fullwidth: false, color: "error", onClick: () => updateArgs({ open: true, isLoading: false }), children: "Delete (type to confirm)" }) })));
135
+ },
136
+ };
@@ -7055,6 +7055,11 @@ input[type=number] {
7055
7055
  background-color: color-mix(in srgb, var(--transparent-warning-8) calc(100% * var(--tw-bg-opacity, 1)), transparent);
7056
7056
  }
7057
7057
 
7058
+ .bg-white {
7059
+ --tw-bg-opacity: 1;
7060
+ background-color: rgb(255 255 255 / var(--tw-bg-opacity, 1));
7061
+ }
7062
+
7058
7063
  .bg-white-transparent-12 {
7059
7064
  --tw-bg-opacity: 1;
7060
7065
  background-color: color-mix(in srgb, var(--transparent-white-12) calc(100% * var(--tw-bg-opacity, 1)), transparent);
@@ -7325,6 +7330,16 @@ input[type=number] {
7325
7330
  stroke: color-mix(in srgb, var(--state-primary-default) calc(100% * 1), transparent);
7326
7331
  }
7327
7332
 
7333
+ .object-cover {
7334
+ -o-object-fit: cover;
7335
+ object-fit: cover;
7336
+ }
7337
+
7338
+ .object-top {
7339
+ -o-object-position: top;
7340
+ object-position: top;
7341
+ }
7342
+
7328
7343
  .\!p-0 {
7329
7344
  padding: 0px !important;
7330
7345
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@rovula/ui",
3
- "version": "0.1.26",
3
+ "version": "0.1.28",
4
4
  "main": "dist/cjs/bundle.js",
5
5
  "module": "dist/esm/bundle.js",
6
6
  "types": "dist/index.d.ts",
@@ -18,11 +18,14 @@ export const avatarVariants = cva(
18
18
  full: "rounded-full",
19
19
  none: "rounded-none",
20
20
  },
21
+ hasImage: {
22
+ true: "bg-white",
23
+ },
21
24
  },
22
25
 
23
26
  defaultVariants: {
24
27
  size: "md",
25
28
  rounded: "normal",
26
29
  },
27
- }
30
+ },
28
31
  );
@@ -31,7 +31,7 @@ type AvatarImageProps = {
31
31
  } & BaseAvatarProps;
32
32
 
33
33
  type AvatarIconProps = {
34
- icon: string;
34
+ icon: React.ReactNode;
35
35
  type: "icon";
36
36
  } & BaseAvatarProps;
37
37
 
@@ -61,7 +61,8 @@ const Avatar: React.FC<AvatarProps> = ({
61
61
  children,
62
62
  style,
63
63
  }) => {
64
- const avatarClassname = avatarVariants({ size, rounded });
64
+ const hasImage = !!imageUrl;
65
+ const avatarClassname = avatarVariants({ size, rounded, hasImage });
65
66
 
66
67
  return (
67
68
  <AvatarBase className={cn(avatarClassname, className)} style={style}>
@@ -13,7 +13,7 @@ const AvatarBase = React.forwardRef<
13
13
  ref={ref}
14
14
  className={cn(
15
15
  "relative flex h-10 w-10 shrink-0 overflow-hidden rounded-full",
16
- className
16
+ className,
17
17
  )}
18
18
  {...props}
19
19
  />
@@ -26,7 +26,7 @@ const AvatarImage = React.forwardRef<
26
26
  >(({ className, ...props }, ref) => (
27
27
  <AvatarPrimitive.Image
28
28
  ref={ref}
29
- className={cn("aspect-square h-full w-full", className)}
29
+ className={cn("object-cover object-top h-full w-full", className)}
30
30
  {...props}
31
31
  />
32
32
  ));
@@ -40,7 +40,7 @@ const AvatarFallback = React.forwardRef<
40
40
  ref={ref}
41
41
  className={cn(
42
42
  "flex h-full w-full items-center justify-center rounded-full bg-inherit text-inherit",
43
- className
43
+ className,
44
44
  )}
45
45
  {...props}
46
46
  />
@@ -191,3 +191,74 @@ export const FigmaRequirePasswordDefaultOpen: Story = {
191
191
  );
192
192
  },
193
193
  };
194
+
195
+ export const WithLoading: Story = {
196
+ args: {
197
+ open: false,
198
+ title: "Delete project?",
199
+ description: "This action cannot be undone.",
200
+ confirmLabel: "Delete",
201
+ cancelLabel: "Cancel",
202
+ },
203
+ render: (args) => {
204
+ const [{ open, isLoading }, updateArgs] = useArgs();
205
+ return (
206
+ <ConfirmDialog
207
+ {...args}
208
+ open={open}
209
+ isLoading={isLoading}
210
+ onOpenChange={(next) => updateArgs({ open: next })}
211
+ onConfirm={() => {
212
+ updateArgs({ isLoading: true });
213
+ setTimeout(() => updateArgs({ isLoading: false, open: false }), 2000);
214
+ }}
215
+ onCancel={() => updateArgs({ open: false })}
216
+ trigger={
217
+ <Button
218
+ fullwidth={false}
219
+ color="error"
220
+ onClick={() => updateArgs({ open: true, isLoading: false })}
221
+ >
222
+ Delete
223
+ </Button>
224
+ }
225
+ />
226
+ );
227
+ },
228
+ };
229
+
230
+ export const WithLoadingAndTypeToConfirm: Story = {
231
+ args: {
232
+ open: false,
233
+ title: "Delete project?",
234
+ description: "This action cannot be undone.",
235
+ confirmLabel: "Delete",
236
+ cancelLabel: "Cancel",
237
+ typeToConfirm: "delete",
238
+ },
239
+ render: (args) => {
240
+ const [{ open, isLoading }, updateArgs] = useArgs();
241
+ return (
242
+ <ConfirmDialog
243
+ {...args}
244
+ open={open}
245
+ isLoading={isLoading}
246
+ onOpenChange={(next) => updateArgs({ open: next })}
247
+ onConfirm={() => {
248
+ updateArgs({ isLoading: true });
249
+ setTimeout(() => updateArgs({ isLoading: false, open: false }), 5000);
250
+ }}
251
+ onCancel={() => updateArgs({ open: false })}
252
+ trigger={
253
+ <Button
254
+ fullwidth={false}
255
+ color="error"
256
+ onClick={() => updateArgs({ open: true, isLoading: false })}
257
+ >
258
+ Delete (type to confirm)
259
+ </Button>
260
+ }
261
+ />
262
+ );
263
+ },
264
+ };
@@ -15,6 +15,7 @@ import {
15
15
  } from "@/components/AlertDialog/AlertDialog";
16
16
  import { useControlledForm, Field } from "@/components/Form";
17
17
  import { TextInput } from "@/components/TextInput/TextInput";
18
+ import Loading from "@/components/Loading/Loading";
18
19
 
19
20
  export type ConfirmDialogProps = {
20
21
  open?: boolean;
@@ -38,6 +39,8 @@ export type ConfirmDialogProps = {
38
39
  * When true, hides the cancel button — useful for info/error alerts that only need one action.
39
40
  */
40
41
  hideCancelButton?: boolean;
42
+ /** When true, shows a loading spinner on the confirm button and disables all interactive controls. */
43
+ isLoading?: boolean;
41
44
  testId?: string;
42
45
  cancelClassName?: string;
43
46
  confirmClassName?: string;
@@ -59,6 +62,7 @@ export const ConfirmDialog: React.FC<ConfirmDialogProps> = ({
59
62
  trigger,
60
63
  typeToConfirm,
61
64
  hideCancelButton = false,
65
+ isLoading = false,
62
66
  testId,
63
67
  cancelClassName,
64
68
  confirmClassName,
@@ -89,22 +93,26 @@ export const ConfirmDialog: React.FC<ConfirmDialogProps> = ({
89
93
 
90
94
  const isFormValid = methods.formState.isValid;
91
95
 
96
+ React.useEffect(() => {
97
+ if (!open) {
98
+ methods.reset();
99
+ }
100
+ }, [open]);
101
+
92
102
  const handleOpenChange = (nextOpen: boolean) => {
103
+ if (isLoading) return;
93
104
  if (!nextOpen) {
94
- methods.reset();
95
105
  onClose?.();
96
106
  }
97
107
  onOpenChange?.(nextOpen);
98
108
  };
99
109
 
100
110
  const handleCancel = () => {
101
- methods.reset();
102
111
  onCancel?.();
103
112
  onClose?.();
104
113
  };
105
114
 
106
115
  const handleSubmit = () => {
107
- methods.reset();
108
116
  onConfirm?.();
109
117
  };
110
118
 
@@ -146,6 +154,7 @@ export const ConfirmDialog: React.FC<ConfirmDialogProps> = ({
146
154
  fullwidth: true,
147
155
  testId: testId && `${testId}-type-to-confirm-input`,
148
156
  }}
157
+ disabled={isLoading}
149
158
  />
150
159
  </FormRoot>
151
160
  )}
@@ -156,21 +165,27 @@ export const ConfirmDialog: React.FC<ConfirmDialogProps> = ({
156
165
  className={cancelClassName}
157
166
  data-testid={testId && `${testId}-cancel-button`}
158
167
  onClick={handleCancel}
168
+ disabled={isLoading}
159
169
  >
160
170
  {cancelLabel}
161
171
  </AlertDialogCancel>
162
172
  )}
163
173
  <AlertDialogAction
164
174
  type="button"
165
- disabled={requiresInput && !isFormValid}
166
- onClick={
167
- requiresInput
168
- ? () => methods.handleSubmit(handleSubmit)()
169
- : () => onConfirm?.()
170
- }
175
+ disabled={isLoading || (requiresInput && !isFormValid)}
176
+ onClick={(e) => {
177
+ e.preventDefault();
178
+
179
+ if (requiresInput) {
180
+ methods.handleSubmit(handleSubmit)();
181
+ } else {
182
+ onConfirm?.();
183
+ }
184
+ }}
171
185
  className={confirmClassName}
172
186
  data-testid={testId && `${testId}-confirm-button`}
173
187
  >
188
+ {isLoading && <Loading />}
174
189
  {confirmLabel}
175
190
  </AlertDialogAction>
176
191
  </AlertDialogFooter>