@rovula/ui 0.1.14 → 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.
- package/dist/cjs/bundle.js +2 -2
- package/dist/cjs/bundle.js.map +1 -1
- package/dist/cjs/types/components/Form/Form.d.ts +1 -1
- package/dist/cjs/types/patterns/confirm-dialog/ConfirmDialog.d.ts +24 -0
- package/dist/cjs/types/patterns/confirm-dialog/ConfirmDialog.stories.d.ts +53 -0
- package/dist/cjs/types/patterns/form-dialog/FormDialog.d.ts +39 -0
- package/dist/cjs/types/patterns/form-dialog/FormDialog.stories.d.ts +62 -0
- package/dist/components/AlertDialog/AlertDialog.js +1 -1
- package/dist/components/Dialog/Dialog.js +1 -1
- package/dist/components/Form/Form.js +15 -4
- package/dist/esm/bundle.js +1 -1
- package/dist/esm/bundle.js.map +1 -1
- package/dist/esm/types/components/Form/Form.d.ts +1 -1
- package/dist/esm/types/patterns/confirm-dialog/ConfirmDialog.d.ts +24 -0
- package/dist/esm/types/patterns/confirm-dialog/ConfirmDialog.stories.d.ts +53 -0
- package/dist/esm/types/patterns/form-dialog/FormDialog.d.ts +39 -0
- package/dist/esm/types/patterns/form-dialog/FormDialog.stories.d.ts +62 -0
- package/dist/index.d.ts +1 -1
- package/dist/patterns/confirm-dialog/ConfirmDialog.js +44 -0
- package/dist/patterns/confirm-dialog/ConfirmDialog.stories.js +103 -0
- package/dist/patterns/form-dialog/FormDialog.js +10 -0
- package/dist/patterns/form-dialog/FormDialog.stories.js +223 -0
- package/package.json +1 -1
- package/src/components/AlertDialog/AlertDialog.tsx +11 -13
- package/src/components/Dialog/Dialog.tsx +3 -9
- package/src/components/Form/Form.tsx +19 -4
- package/src/patterns/confirm-dialog/ConfirmDialog.stories.tsx +193 -0
- package/src/patterns/confirm-dialog/ConfirmDialog.tsx +153 -0
- package/src/patterns/form-dialog/FormDialog.stories.tsx +437 -0
- package/src/patterns/form-dialog/FormDialog.tsx +137 -0
|
@@ -40,7 +40,7 @@ export declare const createControlledForm: <TFieldValues extends FieldValues>({
|
|
|
40
40
|
FormRoot: React.ForwardRefExoticComponent<Omit<FormProps<TFieldValues>, "methods" | "defaultValues" | "controllerRef"> & React.RefAttributes<FormController<TFieldValues>>>;
|
|
41
41
|
};
|
|
42
42
|
export declare const useControlledForm: <TFieldValues extends FieldValues>({ defaultValues, controllerRef, resolver, validationSchema, mode, reValidateMode, }: UseControlledFormOptions<TFieldValues>) => {
|
|
43
|
-
methods: UseFormReturn<TFieldValues>;
|
|
43
|
+
methods: UseFormReturn<TFieldValues, any, TFieldValues>;
|
|
44
44
|
FormRoot: React.ForwardRefExoticComponent<Omit<FormProps<TFieldValues>, "methods" | "defaultValues" | "controllerRef"> & React.RefAttributes<FormController<TFieldValues>>>;
|
|
45
45
|
};
|
|
46
46
|
type FormComponent = <TFieldValues extends FieldValues>(props: FormProps<TFieldValues> & {
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import * as React from "react";
|
|
2
|
+
export type ConfirmDialogProps = {
|
|
3
|
+
open?: boolean;
|
|
4
|
+
onOpenChange?: (open: boolean) => void;
|
|
5
|
+
title: string;
|
|
6
|
+
description?: string;
|
|
7
|
+
confirmLabel?: string;
|
|
8
|
+
cancelLabel?: string;
|
|
9
|
+
onConfirm?: () => void;
|
|
10
|
+
onCancel?: () => void;
|
|
11
|
+
/** Fires whenever the dialog closes, regardless of how (cancel button, overlay, Escape). */
|
|
12
|
+
onClose?: () => void;
|
|
13
|
+
trigger?: React.ReactNode;
|
|
14
|
+
/**
|
|
15
|
+
* When provided, the user must type this exact text before the confirm button is enabled.
|
|
16
|
+
* e.g. typeToConfirm="confirm" or typeToConfirm="delete"
|
|
17
|
+
*/
|
|
18
|
+
typeToConfirm?: string;
|
|
19
|
+
/**
|
|
20
|
+
* When true, hides the cancel button — useful for info/error alerts that only need one action.
|
|
21
|
+
*/
|
|
22
|
+
hideCancelButton?: boolean;
|
|
23
|
+
};
|
|
24
|
+
export declare const ConfirmDialog: React.FC<ConfirmDialogProps>;
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
import React from "react";
|
|
2
|
+
import type { StoryObj } from "@storybook/react";
|
|
3
|
+
import { ConfirmDialog } from "./ConfirmDialog";
|
|
4
|
+
declare const meta: {
|
|
5
|
+
title: string;
|
|
6
|
+
component: React.FC<import("./ConfirmDialog").ConfirmDialogProps>;
|
|
7
|
+
tags: string[];
|
|
8
|
+
parameters: {
|
|
9
|
+
layout: string;
|
|
10
|
+
};
|
|
11
|
+
decorators: ((Story: import("@storybook/csf").PartialStoryFn<import("@storybook/react").ReactRenderer, {
|
|
12
|
+
open?: boolean | undefined;
|
|
13
|
+
onOpenChange?: ((open: boolean) => void) | undefined;
|
|
14
|
+
title: string;
|
|
15
|
+
description?: string | undefined;
|
|
16
|
+
confirmLabel?: string | undefined;
|
|
17
|
+
cancelLabel?: string | undefined;
|
|
18
|
+
onConfirm?: (() => void) | undefined;
|
|
19
|
+
onCancel?: (() => void) | undefined;
|
|
20
|
+
onClose?: (() => void) | undefined;
|
|
21
|
+
trigger?: React.ReactNode;
|
|
22
|
+
typeToConfirm?: string | undefined;
|
|
23
|
+
hideCancelButton?: boolean | undefined;
|
|
24
|
+
}>) => import("react/jsx-runtime").JSX.Element)[];
|
|
25
|
+
argTypes: {
|
|
26
|
+
open: {
|
|
27
|
+
control: "boolean";
|
|
28
|
+
};
|
|
29
|
+
typeToConfirm: {
|
|
30
|
+
control: "text";
|
|
31
|
+
};
|
|
32
|
+
title: {
|
|
33
|
+
control: "text";
|
|
34
|
+
};
|
|
35
|
+
description: {
|
|
36
|
+
control: "text";
|
|
37
|
+
};
|
|
38
|
+
confirmLabel: {
|
|
39
|
+
control: "text";
|
|
40
|
+
};
|
|
41
|
+
cancelLabel: {
|
|
42
|
+
control: "text";
|
|
43
|
+
};
|
|
44
|
+
};
|
|
45
|
+
};
|
|
46
|
+
export default meta;
|
|
47
|
+
type Story = StoryObj<typeof ConfirmDialog>;
|
|
48
|
+
export declare const Default: Story;
|
|
49
|
+
export declare const WithoutDescription: Story;
|
|
50
|
+
export declare const WithoutCancelButton: Story;
|
|
51
|
+
export declare const RequireConfirmText: Story;
|
|
52
|
+
export declare const FigmaDefaultOpen: Story;
|
|
53
|
+
export declare const FigmaRequirePasswordDefaultOpen: Story;
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import * as React from "react";
|
|
2
|
+
import Button from "@/components/Button/Button";
|
|
3
|
+
export type FormDialogAction = {
|
|
4
|
+
label: string;
|
|
5
|
+
onClick?: () => void;
|
|
6
|
+
variant?: React.ComponentProps<typeof Button>["variant"];
|
|
7
|
+
color?: React.ComponentProps<typeof Button>["color"];
|
|
8
|
+
disabled?: boolean;
|
|
9
|
+
isLoading?: boolean;
|
|
10
|
+
type?: "button" | "submit" | "reset";
|
|
11
|
+
};
|
|
12
|
+
export type FormDialogProps = {
|
|
13
|
+
open?: boolean;
|
|
14
|
+
onOpenChange?: (open: boolean) => void;
|
|
15
|
+
title: string;
|
|
16
|
+
description?: string;
|
|
17
|
+
children?: React.ReactNode;
|
|
18
|
+
trigger?: React.ReactNode;
|
|
19
|
+
/**
|
|
20
|
+
* Primary action (right side of footer).
|
|
21
|
+
*/
|
|
22
|
+
confirmAction?: FormDialogAction;
|
|
23
|
+
/**
|
|
24
|
+
* Secondary cancel action (right side of footer, outline style).
|
|
25
|
+
*/
|
|
26
|
+
cancelAction?: FormDialogAction;
|
|
27
|
+
/**
|
|
28
|
+
* Optional extra action placed on the left side of the footer.
|
|
29
|
+
*/
|
|
30
|
+
extraAction?: FormDialogAction;
|
|
31
|
+
scrollable?: boolean;
|
|
32
|
+
className?: string;
|
|
33
|
+
/**
|
|
34
|
+
* When provided, the confirm button becomes type="submit" and is linked to this form id.
|
|
35
|
+
* Use together with a <Form id={formId} .../> inside children.
|
|
36
|
+
*/
|
|
37
|
+
formId?: string;
|
|
38
|
+
};
|
|
39
|
+
export declare const FormDialog: React.FC<FormDialogProps>;
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
import React from "react";
|
|
2
|
+
import type { StoryObj } from "@storybook/react";
|
|
3
|
+
import { FormDialog } from "./FormDialog";
|
|
4
|
+
declare const meta: {
|
|
5
|
+
title: string;
|
|
6
|
+
component: React.FC<import("./FormDialog").FormDialogProps>;
|
|
7
|
+
tags: string[];
|
|
8
|
+
parameters: {
|
|
9
|
+
layout: string;
|
|
10
|
+
};
|
|
11
|
+
decorators: ((Story: import("@storybook/csf").PartialStoryFn<import("@storybook/react").ReactRenderer, {
|
|
12
|
+
open?: boolean | undefined;
|
|
13
|
+
onOpenChange?: ((open: boolean) => void) | undefined;
|
|
14
|
+
title: string;
|
|
15
|
+
description?: string | undefined;
|
|
16
|
+
children?: React.ReactNode;
|
|
17
|
+
trigger?: React.ReactNode;
|
|
18
|
+
confirmAction?: import("./FormDialog").FormDialogAction | undefined;
|
|
19
|
+
cancelAction?: import("./FormDialog").FormDialogAction | undefined;
|
|
20
|
+
extraAction?: import("./FormDialog").FormDialogAction | undefined;
|
|
21
|
+
scrollable?: boolean | undefined;
|
|
22
|
+
className?: string | undefined;
|
|
23
|
+
formId?: string | undefined;
|
|
24
|
+
}>) => import("react/jsx-runtime").JSX.Element)[];
|
|
25
|
+
argTypes: {
|
|
26
|
+
open: {
|
|
27
|
+
control: "boolean";
|
|
28
|
+
};
|
|
29
|
+
title: {
|
|
30
|
+
control: "text";
|
|
31
|
+
};
|
|
32
|
+
description: {
|
|
33
|
+
control: "text";
|
|
34
|
+
};
|
|
35
|
+
scrollable: {
|
|
36
|
+
control: "boolean";
|
|
37
|
+
};
|
|
38
|
+
};
|
|
39
|
+
};
|
|
40
|
+
export default meta;
|
|
41
|
+
type Story = StoryObj<typeof FormDialog>;
|
|
42
|
+
export declare const Default: Story;
|
|
43
|
+
export declare const WithExtraAction: Story;
|
|
44
|
+
export declare const WithForm: Story;
|
|
45
|
+
export declare const FigmaFormDefaultOpen: Story;
|
|
46
|
+
export declare const FigmaFormWithActionDefaultOpen: Story;
|
|
47
|
+
/**
|
|
48
|
+
* Pattern A: useControlledForm
|
|
49
|
+
*
|
|
50
|
+
* Use when you need to:
|
|
51
|
+
* - access formState (isValid, isDirty) to control the confirm button
|
|
52
|
+
* - reset the form on close
|
|
53
|
+
* - trigger validation outside the form element
|
|
54
|
+
*/
|
|
55
|
+
export declare const WithRovulaForm: Story;
|
|
56
|
+
/**
|
|
57
|
+
* Pattern B: useControlledForm + async API call + loading state
|
|
58
|
+
*
|
|
59
|
+
* Use when the confirm action calls an API.
|
|
60
|
+
* isLoading disables + shows spinner on the confirm button while the request is in flight.
|
|
61
|
+
*/
|
|
62
|
+
export declare const WithRovulaFormAndApiLoading: Story;
|
|
@@ -25,7 +25,7 @@ const AlertDialogOverlay = React.forwardRef((_a, ref) => {
|
|
|
25
25
|
AlertDialogOverlay.displayName = AlertDialogPrimitive.Overlay.displayName;
|
|
26
26
|
const AlertDialogContent = React.forwardRef((_a, ref) => {
|
|
27
27
|
var { className } = _a, props = __rest(_a, ["className"]);
|
|
28
|
-
return (_jsxs(AlertDialogPortal, { children: [_jsx(AlertDialogOverlay, {}), _jsx(AlertDialogPrimitive.Content, Object.assign({ ref: ref, className: cn("fixed left-[50%] top-[50%] z-50 grid w-[calc(100%-32px)] max-w-[460px] translate-x-[-50%] translate-y-[-50%] gap-6 rounded-md bg-modal-surface px-6 py-8 text-text-contrast-max shadow-[0px_12px_24px_-4px_rgba(0,0,0,0.12)] duration-200 focus:outline-none focus-visible:outline-none 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%]", className) }, props))] }));
|
|
28
|
+
return (_jsxs(AlertDialogPortal, { children: [_jsx(AlertDialogOverlay, {}), _jsx(AlertDialogPrimitive.Content, Object.assign({ ref: ref, className: cn("fixed left-[50%] top-[50%] z-50 grid w-[calc(100%-32px)] max-w-[460px] translate-x-[-50%] translate-y-[-50%] gap-6 rounded-md bg-modal-surface px-6 py-8 text-text-contrast-max shadow-[0px_12px_24px_-4px_rgba(0,0,0,0.12)] duration-200 focus:outline-none focus-visible:outline-none outline-none 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%]", className) }, props))] }));
|
|
29
29
|
});
|
|
30
30
|
AlertDialogContent.displayName = AlertDialogPrimitive.Content.displayName;
|
|
31
31
|
const AlertDialogHeader = (_a) => {
|
|
@@ -26,7 +26,7 @@ const DialogOverlay = React.forwardRef((_a, ref) => {
|
|
|
26
26
|
DialogOverlay.displayName = DialogPrimitive.Overlay.displayName;
|
|
27
27
|
const DialogContent = React.forwardRef((_a, ref) => {
|
|
28
28
|
var { className, children, showCloseButton = false, closeButtonClassName } = _a, props = __rest(_a, ["className", "children", "showCloseButton", "closeButtonClassName"]);
|
|
29
|
-
return (_jsxs(DialogPortal, { children: [_jsx(DialogOverlay, {}), _jsxs(DialogPrimitive.Content, Object.assign({ ref: ref, className: cn("fixed left-[50%] top-[50%] z-50 flex w-[calc(100%-32px)] max-w-[650px] translate-x-[-50%] translate-y-[-50%] flex-col gap-6 rounded-md bg-modal-surface p-8 text-text-g-contrast-medium shadow-[0px_12px_24px_-4px_rgba(0,0,0,0.12)] duration-200 focus:outline-none focus-visible:outline-none 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%]", className) }, props, { children: [children, showCloseButton && (_jsxs(DialogPrimitive.Close, { className: cn("absolute right-8 top-8 rounded-sm opacity-70 ring-offset-bg-bg1 transition-opacity hover:opacity-100 focus:outline-none focus:ring-2 focus:ring-input-active-stroke focus:ring-offset-2 disabled:pointer-events-none data-[state=open]:bg-primary data-[state=open]:text-state-primary-text-solid", closeButtonClassName), children: [_jsx(XMarkIcon, { className: "h-4 w-4" }), _jsx("span", { className: "sr-only", children: "Close" })] }))] }))] }));
|
|
29
|
+
return (_jsxs(DialogPortal, { children: [_jsx(DialogOverlay, {}), _jsxs(DialogPrimitive.Content, Object.assign({ ref: ref, className: cn("fixed left-[50%] top-[50%] z-50 flex w-[calc(100%-32px)] max-w-[650px] translate-x-[-50%] translate-y-[-50%] flex-col gap-6 rounded-md bg-modal-surface p-8 text-text-g-contrast-medium shadow-[0px_12px_24px_-4px_rgba(0,0,0,0.12)] duration-200 focus:outline-none focus-visible:outline-none outline-none 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%]", className) }, props, { children: [children, showCloseButton && (_jsxs(DialogPrimitive.Close, { className: cn("absolute right-8 top-8 rounded-sm opacity-70 ring-offset-bg-bg1 transition-opacity hover:opacity-100 focus:outline-none focus:ring-2 focus:ring-input-active-stroke focus:ring-offset-2 disabled:pointer-events-none data-[state=open]:bg-primary data-[state=open]:text-state-primary-text-solid", closeButtonClassName), children: [_jsx(XMarkIcon, { className: "h-4 w-4" }), _jsx("span", { className: "sr-only", children: "Close" })] }))] }))] }));
|
|
30
30
|
});
|
|
31
31
|
DialogContent.displayName = DialogPrimitive.Content.displayName;
|
|
32
32
|
const DialogHeader = (_a) => {
|
|
@@ -35,11 +35,22 @@ export const useControlledForm = ({ defaultValues, controllerRef, resolver, vali
|
|
|
35
35
|
mode,
|
|
36
36
|
reValidateMode,
|
|
37
37
|
});
|
|
38
|
-
|
|
38
|
+
// Keep FormRoot component reference stable across re-renders.
|
|
39
|
+
// createControlledForm creates a new component type (via forwardRef) on every call —
|
|
40
|
+
// if FormRoot changes reference, React unmounts + remounts the entire form tree,
|
|
41
|
+
// causing inputs to lose focus whenever parent state changes (e.g. isValid).
|
|
42
|
+
const stableRef = React.useRef(null);
|
|
43
|
+
if (!stableRef.current) {
|
|
44
|
+
stableRef.current = createControlledForm({
|
|
45
|
+
methods,
|
|
46
|
+
defaultValues,
|
|
47
|
+
controllerRef,
|
|
48
|
+
});
|
|
49
|
+
}
|
|
50
|
+
return {
|
|
39
51
|
methods,
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
});
|
|
52
|
+
FormRoot: stableRef.current.FormRoot,
|
|
53
|
+
};
|
|
43
54
|
};
|
|
44
55
|
const FormInner = (_a, ref) => {
|
|
45
56
|
var { children, defaultValues, methods: externalMethods, controllerRef, onSubmit, onInvalidSubmit, resolver, validationSchema, mode = "onSubmit", reValidateMode = "onChange", noValidate = true } = _a, formProps = __rest(_a, ["children", "defaultValues", "methods", "controllerRef", "onSubmit", "onInvalidSubmit", "resolver", "validationSchema", "mode", "reValidateMode", "noValidate"]);
|