@fpkit/acss 0.5.4 → 0.5.6
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/libs/chunk-PWVRDQ3R.js +8 -0
- package/libs/chunk-PWVRDQ3R.js.map +1 -0
- package/libs/chunk-SVS4MX3U.cjs +31 -0
- package/libs/chunk-SVS4MX3U.cjs.map +1 -0
- package/libs/{icons-2f29127c.d.ts → icons-31ace3de.d.ts} +87 -81
- package/libs/icons.cjs +2 -2
- package/libs/icons.d.cts +1 -1
- package/libs/icons.d.ts +1 -1
- package/libs/icons.js +1 -1
- package/libs/index.cjs +42 -42
- package/libs/index.cjs.map +1 -1
- package/libs/index.d.cts +59 -29
- package/libs/index.d.ts +59 -29
- package/libs/index.js +7 -7
- package/libs/index.js.map +1 -1
- package/package.json +4 -3
- package/src/components/README.mdx +84 -0
- package/src/components/alert/README.mdx +86 -0
- package/src/components/alert/alert.mdx +74 -0
- package/src/components/alert/alert.scss +80 -0
- package/src/components/alert/alert.stories.tsx +132 -0
- package/src/components/alert/alert.tsx +154 -0
- package/src/components/alert/elements/README.mdx +77 -0
- package/src/components/alert/elements/dismiss-button.stories.tsx +31 -0
- package/src/components/alert/elements/dismiss-button.tsx +28 -0
- package/src/components/badge/badge.mdx +124 -0
- package/src/components/badge/badge.scss +4 -4
- package/src/components/badge/badge.stories.tsx +21 -23
- package/src/components/breadcrumbs/breadcrumb.scss +2 -2
- package/src/components/breadcrumbs/breadcrumb.stories.tsx +42 -47
- package/src/components/buttons/button.scss +41 -15
- package/src/components/buttons/button.stories.tsx +8 -1
- package/src/components/buttons/button.test.tsx +72 -72
- package/src/components/cards/card.stories.tsx +15 -15
- package/src/components/details/details.scss +26 -6
- package/src/components/details/details.stories.tsx +33 -30
- package/src/components/details/details.tsx +17 -17
- package/src/components/dialog/README.mdx +187 -0
- package/src/components/dialog/dialog-modal.stories.tsx +113 -0
- package/src/components/dialog/dialog-modal.tsx +111 -0
- package/src/components/dialog/dialog.scss +76 -0
- package/src/components/dialog/dialog.stories.tsx +116 -0
- package/src/components/dialog/dialog.tsx +128 -0
- package/src/components/dialog/hooks/useClickOutside.ts +33 -0
- package/src/components/dialog/views/README.mdx +182 -0
- package/src/components/dialog/views/dialog-footer.tsx +45 -0
- package/src/components/dialog/views/dialog-header.stories.tsx +42 -0
- package/src/components/dialog/views/dialog-header.tsx +61 -0
- package/src/components/form/form.stories.tsx +16 -16
- package/src/components/form/input.stories.tsx +62 -62
- package/src/components/form/select.stories.tsx +22 -15
- package/src/components/heading/heading.stories.tsx +32 -33
- package/src/components/heading/heading.tsx +1 -1
- package/src/components/icons/components/add.tsx +14 -14
- package/src/components/icons/components/alert-solid.tsx +36 -0
- package/src/components/icons/components/alert-square-solid.tsx +36 -0
- package/src/components/icons/components/info-solid.tsx +40 -0
- package/src/components/icons/components/info.tsx +36 -0
- package/src/components/icons/components/question-solid.tsx +36 -0
- package/src/components/icons/components/success-solid.tsx +36 -0
- package/src/components/icons/components/svg.tsx +0 -1
- package/src/components/icons/components/warn-solid.tsx +36 -0
- package/src/components/icons/icon.scss +1 -3
- package/src/components/icons/icon.stories.tsx +87 -78
- package/src/components/icons/icon.tsx +57 -52
- package/src/components/icons/index.ts +36 -29
- package/src/components/icons/types.ts +1 -1
- package/src/components/images/figure.stories.tsx +13 -13
- package/src/components/images/img.stories.tsx +12 -12
- package/src/components/link/link.stories.tsx +32 -35
- package/src/components/link/link.tsx +27 -14
- package/src/components/list/list.stories.tsx +16 -16
- package/src/components/modal/dialog.tsx +13 -12
- package/src/components/modal/modal.tsx +28 -30
- package/src/components/nav/nav.stories.tsx +25 -24
- package/src/components/popover/popover.stories.tsx +17 -18
- package/src/components/progress/progress.stories.tsx +14 -20
- package/src/components/tag/tag.stories.tsx +17 -18
- package/src/components/text/text.stories.tsx +28 -29
- package/src/components/text-to-speech/TextToSpeech.stories.tsx +100 -101
- package/src/components/ui.tsx +28 -25
- package/src/decorators/instructions.tsx +44 -0
- package/src/hooks/useDialogClickHandler.ts +26 -0
- package/src/index.scss +23 -22
- package/src/index.ts +31 -30
- package/src/patterns/page/page-header.stories.tsx +17 -21
- package/src/sass/_globals.scss +14 -32
- package/src/sass/_styles.scss +2 -1
- package/src/sass/styles/_colors.scss +13 -0
- package/src/styles/alert/alert.css +68 -0
- package/src/styles/alert/alert.css.map +1 -0
- package/src/styles/badge/badge.css +3 -3
- package/src/styles/breadcrumbs/breadcrumb.css +1 -1
- package/src/styles/buttons/button.css +25 -2
- package/src/styles/buttons/button.css.map +1 -1
- package/src/styles/details/details.css +19 -4
- package/src/styles/details/details.css.map +1 -1
- package/src/styles/dialog/dialog.css +76 -0
- package/src/styles/dialog/dialog.css.map +1 -0
- package/src/styles/icons/icon.css +1 -3
- package/src/styles/icons/icon.css.map +1 -1
- package/src/styles/index.css +213 -60
- package/src/styles/index.css.map +1 -1
- package/libs/chunk-TBM2QIVT.js +0 -8
- package/libs/chunk-TBM2QIVT.js.map +0 -1
- package/libs/chunk-VAH6X2DZ.cjs +0 -31
- package/libs/chunk-VAH6X2DZ.cjs.map +0 -1
- package/libs/components/badge/badge.css +0 -1
- package/libs/components/badge/badge.css.map +0 -1
- package/libs/components/badge/badge.min.css +0 -3
- package/libs/components/breadcrumbs/breadcrumb.css +0 -1
- package/libs/components/breadcrumbs/breadcrumb.css.map +0 -1
- package/libs/components/breadcrumbs/breadcrumb.min.css +0 -3
- package/libs/components/buttons/button.css +0 -1
- package/libs/components/buttons/button.css.map +0 -1
- package/libs/components/buttons/button.min.css +0 -3
- package/libs/components/cards/card-style.css +0 -1
- package/libs/components/cards/card-style.css.map +0 -1
- package/libs/components/cards/card-style.min.css +0 -3
- package/libs/components/cards/card.css +0 -1
- package/libs/components/cards/card.css.map +0 -1
- package/libs/components/cards/card.min.css +0 -3
- package/libs/components/details/details.css +0 -1
- package/libs/components/details/details.css.map +0 -1
- package/libs/components/details/details.min.css +0 -3
- package/libs/components/form/form.css +0 -1
- package/libs/components/form/form.css.map +0 -1
- package/libs/components/form/form.min.css +0 -3
- package/libs/components/icons/icon.css +0 -1
- package/libs/components/icons/icon.css.map +0 -1
- package/libs/components/icons/icon.min.css +0 -3
- package/libs/components/images/img.css +0 -1
- package/libs/components/images/img.css.map +0 -1
- package/libs/components/images/img.min.css +0 -3
- package/libs/components/layout/landmarks.css +0 -1
- package/libs/components/layout/landmarks.css.map +0 -1
- package/libs/components/layout/landmarks.min.css +0 -3
- package/libs/components/link/link.css +0 -1
- package/libs/components/link/link.css.map +0 -1
- package/libs/components/link/link.min.css +0 -3
- package/libs/components/nav/nav.css +0 -1
- package/libs/components/nav/nav.css.map +0 -1
- package/libs/components/nav/nav.min.css +0 -3
- package/libs/components/progress/progress.css +0 -1
- package/libs/components/progress/progress.css.map +0 -1
- package/libs/components/progress/progress.min.css +0 -3
- package/libs/components/styles/index.css +0 -1
- package/libs/components/styles/index.css.map +0 -1
- package/libs/components/styles/index.min.css +0 -3
- package/libs/components/tag/tag.css +0 -1
- package/libs/components/tag/tag.css.map +0 -1
- package/libs/components/tag/tag.min.css +0 -3
- package/libs/components/text-to-speech/text-to-speech.css +0 -1
- package/libs/components/text-to-speech/text-to-speech.css.map +0 -1
- package/libs/components/text-to-speech/text-to-speech.min.css +0 -3
- package/libs/index.css +0 -1
- package/libs/index.css.map +0 -1
- package/src/components/readme.stories.mdx +0 -7
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
// Dialog.tsx
|
|
2
|
+
import React from "react";
|
|
3
|
+
import Dialog from "./dialog";
|
|
4
|
+
import Button from "#components/buttons/button.jsx";
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Additional props for the DialogModal component, extending the props of the Dialog component.
|
|
8
|
+
*
|
|
9
|
+
* @property {string} [className] - Optional className for the dialog content wrapper.
|
|
10
|
+
* @property {string} [btnLabel] - Label for the button that opens the dialog.
|
|
11
|
+
* @property {() => void} [btnOnClick] - Callback function to be called when the button is clicked.
|
|
12
|
+
* @property {"sm" | "md" | "lg"} [btnSize] - Size of the button that opens the dialog.
|
|
13
|
+
*/
|
|
14
|
+
interface DialogModalProps extends React.ComponentProps<typeof Dialog> {
|
|
15
|
+
/** Optional className for the dialog content wrapper */
|
|
16
|
+
className?: string;
|
|
17
|
+
btnLabel?: string;
|
|
18
|
+
btnOnClick?: () => void;
|
|
19
|
+
btnSize?: "sm" | "md" | "lg";
|
|
20
|
+
btnProps?: React.ComponentProps<typeof Button>;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* A modal dialog component that provides an accessible overlay with an optional trigger button.
|
|
25
|
+
* Extends the base Dialog component with additional button control functionality.
|
|
26
|
+
*
|
|
27
|
+
* @component
|
|
28
|
+
* @param {Object} props - Component props
|
|
29
|
+
* @param {boolean} [props.isAlertDialog] - Whether this is an alert dialog requiring user action
|
|
30
|
+
* @param {() => void} [props.onClose] - Callback when dialog is closed
|
|
31
|
+
* @param {string} [props.dialogTitle] - Title displayed in dialog header
|
|
32
|
+
* @param {string} [props.dialogLabel] - Accessible label for the dialog
|
|
33
|
+
* @param {string} [props.btnLabel="Open Dialog"] - Text label for the trigger button
|
|
34
|
+
* @param {"sm" | "md" | "lg"} [props.btnSize="md"] - Size of the trigger button
|
|
35
|
+
* @param {() => void} [props.btnOnClick] - Callback when trigger button is clicked
|
|
36
|
+
* @param {React.ReactNode} props.children - Content to display inside the dialog
|
|
37
|
+
* @param {() => void} [props.onConfirm] - Callback when confirm button is clicked (for alert dialogs)
|
|
38
|
+
* @param {string} [props.confirmLabel="Confirm"] - Text for the confirm button
|
|
39
|
+
* @param {string} [props.cancelLabel="Cancel"] - Text for the cancel button
|
|
40
|
+
* @param {string} [props.className] - Additional CSS class for the dialog wrapper
|
|
41
|
+
* @param {React.ComponentProps<typeof Button>} [props.btnProps] - Additional props for the trigger button
|
|
42
|
+
* @returns {JSX.Element} A dialog component with a trigger button
|
|
43
|
+
*
|
|
44
|
+
* @example
|
|
45
|
+
* ```jsx
|
|
46
|
+
* <DialogModal
|
|
47
|
+
* dialogTitle="Confirm Action"
|
|
48
|
+
* btnLabel="Open Modal"
|
|
49
|
+
* btnSize="lg"
|
|
50
|
+
* onClose={() => console.log('Dialog closed')}
|
|
51
|
+
* >
|
|
52
|
+
* <p>Dialog content goes here</p>
|
|
53
|
+
* </DialogModal>
|
|
54
|
+
* ```
|
|
55
|
+
*/
|
|
56
|
+
|
|
57
|
+
export const DialogModal: React.FC<DialogModalProps> = ({
|
|
58
|
+
isAlertDialog,
|
|
59
|
+
onClose,
|
|
60
|
+
dialogTitle,
|
|
61
|
+
dialogLabel,
|
|
62
|
+
btnLabel = "Open Dialog",
|
|
63
|
+
btnSize = "sm",
|
|
64
|
+
btnOnClick,
|
|
65
|
+
children,
|
|
66
|
+
onConfirm,
|
|
67
|
+
confirmLabel = "Confirm",
|
|
68
|
+
cancelLabel = "Cancel",
|
|
69
|
+
className,
|
|
70
|
+
btnProps,
|
|
71
|
+
}) => {
|
|
72
|
+
const [isOpen, setIsOpen] = React.useState(false);
|
|
73
|
+
|
|
74
|
+
const handleClose = () => {
|
|
75
|
+
setIsOpen(false);
|
|
76
|
+
onClose?.();
|
|
77
|
+
};
|
|
78
|
+
const handleButtonClick = () => {
|
|
79
|
+
setIsOpen(true);
|
|
80
|
+
btnOnClick?.();
|
|
81
|
+
};
|
|
82
|
+
|
|
83
|
+
const dialogBtnProps = {
|
|
84
|
+
type: "button" as "button" | "submit" | "reset",
|
|
85
|
+
onClick: handleButtonClick,
|
|
86
|
+
"data-btn": btnSize,
|
|
87
|
+
...btnProps,
|
|
88
|
+
};
|
|
89
|
+
|
|
90
|
+
return (
|
|
91
|
+
<>
|
|
92
|
+
<Button {...dialogBtnProps}>{btnLabel}</Button>
|
|
93
|
+
<Dialog
|
|
94
|
+
showDialog={isOpen}
|
|
95
|
+
dialogTitle={dialogTitle}
|
|
96
|
+
onClose={handleClose}
|
|
97
|
+
title={dialogTitle}
|
|
98
|
+
dialogLabel={dialogLabel}
|
|
99
|
+
className={className}
|
|
100
|
+
isAlertDialog={isAlertDialog}
|
|
101
|
+
onConfirm={onConfirm}
|
|
102
|
+
confirmLabel={confirmLabel}
|
|
103
|
+
cancelLabel={cancelLabel}
|
|
104
|
+
>
|
|
105
|
+
{children}
|
|
106
|
+
</Dialog>
|
|
107
|
+
</>
|
|
108
|
+
);
|
|
109
|
+
};
|
|
110
|
+
export default DialogModal;
|
|
111
|
+
DialogModal.displayName = "Dialog Modal";
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
:root {
|
|
2
|
+
--dialog-min-w: max(20rem, 80%);
|
|
3
|
+
--dialog-gap: 0.625rem;
|
|
4
|
+
--dialog-border-color: lightgray;
|
|
5
|
+
--dialog-border-width: thin;
|
|
6
|
+
--dialog-border-style: solid;
|
|
7
|
+
--dialog-border-radius: var(--border-radius);
|
|
8
|
+
--dialog-padding: 1.5rem;
|
|
9
|
+
--dialog-padding-inline: 1rem;
|
|
10
|
+
--dialog-close-color: gray;
|
|
11
|
+
--dialog-button-bg: transparent;
|
|
12
|
+
--dialog-button-border: transparent thin solid;
|
|
13
|
+
--dialog-button-hover-bg: whitesmoke;
|
|
14
|
+
--dialog-display: flex;
|
|
15
|
+
--dialog-flex-direction: column;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
dialog {
|
|
19
|
+
width: var(--dialog-min-w);
|
|
20
|
+
min-width: var(--min-w);
|
|
21
|
+
gap: var(--dialog-gap);
|
|
22
|
+
border: var(--dialog-border-color) var(--dialog-border-width) solid;
|
|
23
|
+
border-radius: var(--dialog-border-radius);
|
|
24
|
+
padding: var(--dialog-padding);
|
|
25
|
+
padding-block-start: calc(var(--dialog-padding) - 0rem);
|
|
26
|
+
|
|
27
|
+
&[open] {
|
|
28
|
+
display: var(--dialog-display);
|
|
29
|
+
flex-direction: var(--dialog-flex-direction);
|
|
30
|
+
gap: var(--dialog-gap);
|
|
31
|
+
}
|
|
32
|
+
section {
|
|
33
|
+
width: 100%;
|
|
34
|
+
display: flex;
|
|
35
|
+
justify-content: start;
|
|
36
|
+
gap: var(--dialog-gap);
|
|
37
|
+
flex-direction: var(--dialog-flex-direction);
|
|
38
|
+
margin-block-start: 0;
|
|
39
|
+
--sect-y: 0;
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
.dialog-header {
|
|
44
|
+
display: flex;
|
|
45
|
+
justify-content: space-between;
|
|
46
|
+
align-items: center;
|
|
47
|
+
width: 100%;
|
|
48
|
+
min-width: 100%;
|
|
49
|
+
h3 {
|
|
50
|
+
margin-block-start: 0;
|
|
51
|
+
margin-block-end: 0;
|
|
52
|
+
}
|
|
53
|
+
.dialog-close {
|
|
54
|
+
margin-block-end: 0;
|
|
55
|
+
}
|
|
56
|
+
button[type="button"] {
|
|
57
|
+
background-color: var(--dialog-button-bg);
|
|
58
|
+
border: var(--dialog-button-border);
|
|
59
|
+
cursor: pointer;
|
|
60
|
+
&:hover,
|
|
61
|
+
&:focus {
|
|
62
|
+
border-color: var(--dialog-close-color);
|
|
63
|
+
background-color: var(--dialog-button-hover-bg);
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
.alert-dialog-actions,
|
|
69
|
+
.dialog-footer {
|
|
70
|
+
display: flex;
|
|
71
|
+
flex-direction: row;
|
|
72
|
+
flex-wrap: wrap;
|
|
73
|
+
justify-content: var(--dialog-footer-justify, flex-end);
|
|
74
|
+
gap: var(--dialog-gap);
|
|
75
|
+
width: 100%;
|
|
76
|
+
}
|
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
import { StoryObj, Meta, StoryFn } from "@storybook/react";
|
|
2
|
+
import { within, expect, userEvent } from "@storybook/test";
|
|
3
|
+
|
|
4
|
+
import Dialog from "./dialog";
|
|
5
|
+
import React from "react";
|
|
6
|
+
|
|
7
|
+
const content =
|
|
8
|
+
"This is a dialog component used to display modal dialogs. It can be used to show important information or prompt the user for input.";
|
|
9
|
+
|
|
10
|
+
const buttonDecorator = [
|
|
11
|
+
(Story: StoryFn) => {
|
|
12
|
+
const [isOpen, setIsOpen] = React.useState(false);
|
|
13
|
+
return (
|
|
14
|
+
<div>
|
|
15
|
+
<button onClick={() => setIsOpen(true)}>Open Dialog</button>
|
|
16
|
+
<Story
|
|
17
|
+
args={{
|
|
18
|
+
showDialog: isOpen,
|
|
19
|
+
onClose: () => setIsOpen(false),
|
|
20
|
+
dialogTitle: "Dialog Button",
|
|
21
|
+
children: content,
|
|
22
|
+
}}
|
|
23
|
+
/>
|
|
24
|
+
<section>{content}</section>
|
|
25
|
+
</div>
|
|
26
|
+
);
|
|
27
|
+
},
|
|
28
|
+
];
|
|
29
|
+
|
|
30
|
+
const meta: Meta<typeof Dialog> = {
|
|
31
|
+
title: "FP.REACT Components/Dialog/Dialogs",
|
|
32
|
+
component: Dialog,
|
|
33
|
+
tags: ["alpha"],
|
|
34
|
+
parameters: {
|
|
35
|
+
docs: {
|
|
36
|
+
description: {
|
|
37
|
+
component: "Dialog component for displaying modal dialogs.",
|
|
38
|
+
},
|
|
39
|
+
},
|
|
40
|
+
},
|
|
41
|
+
args: {
|
|
42
|
+
children: content,
|
|
43
|
+
},
|
|
44
|
+
decorators: [
|
|
45
|
+
(Story) => {
|
|
46
|
+
return (
|
|
47
|
+
<div
|
|
48
|
+
style={{
|
|
49
|
+
display: "flex",
|
|
50
|
+
justifyContent: "center",
|
|
51
|
+
alignItems: "center",
|
|
52
|
+
width: "500px",
|
|
53
|
+
marginInline: "20px",
|
|
54
|
+
marginBlockStart: "5rem",
|
|
55
|
+
}}
|
|
56
|
+
>
|
|
57
|
+
<Story />
|
|
58
|
+
</div>
|
|
59
|
+
);
|
|
60
|
+
},
|
|
61
|
+
],
|
|
62
|
+
} as Story;
|
|
63
|
+
|
|
64
|
+
export default meta;
|
|
65
|
+
type Story = StoryObj<typeof Dialog>;
|
|
66
|
+
|
|
67
|
+
export const BasicDialog: Story = {
|
|
68
|
+
args: {
|
|
69
|
+
isAlertDialog: false,
|
|
70
|
+
showDialog: true,
|
|
71
|
+
dialogTitle: "Basic Dialog",
|
|
72
|
+
},
|
|
73
|
+
} as Story;
|
|
74
|
+
|
|
75
|
+
/**
|
|
76
|
+
* Show the dialog by default
|
|
77
|
+
* set the showDialog prop to true
|
|
78
|
+
*/
|
|
79
|
+
export const NonModalDialog: Story = {
|
|
80
|
+
args: {
|
|
81
|
+
showDialog: true,
|
|
82
|
+
isAlertDialog: true,
|
|
83
|
+
dialogTitle: "Non Modal Dialog",
|
|
84
|
+
},
|
|
85
|
+
play: async ({ canvasElement }) => {
|
|
86
|
+
const canvas = within(canvasElement);
|
|
87
|
+
await expect(canvas.getByRole("alertdialog")).toBeInTheDocument();
|
|
88
|
+
},
|
|
89
|
+
} as Story;
|
|
90
|
+
|
|
91
|
+
export const DialogWithButton: Story = {
|
|
92
|
+
decorators: buttonDecorator,
|
|
93
|
+
} as Story;
|
|
94
|
+
|
|
95
|
+
export const DialogInteractions: Story = {
|
|
96
|
+
args: {
|
|
97
|
+
isAlertDialog: false,
|
|
98
|
+
showDialog: true,
|
|
99
|
+
dialogTitle: "Dialog Interactions",
|
|
100
|
+
},
|
|
101
|
+
play: async ({ canvasElement, step }) => {
|
|
102
|
+
const canvas = within(canvasElement);
|
|
103
|
+
const dialog = canvas.getByRole("dialog");
|
|
104
|
+
const closeButton = canvas.getByRole("button", { name: /close dialog/i });
|
|
105
|
+
|
|
106
|
+
await step("Modal is rendered", async () => {
|
|
107
|
+
await expect(dialog).toBeInTheDocument();
|
|
108
|
+
await expect(closeButton).toBeInTheDocument();
|
|
109
|
+
});
|
|
110
|
+
|
|
111
|
+
await step("Close modal", async () => {
|
|
112
|
+
await userEvent.click(closeButton, { delay: 1000 });
|
|
113
|
+
// await expect(dialog).not.toBeInTheDocument();
|
|
114
|
+
});
|
|
115
|
+
},
|
|
116
|
+
} as Story;
|
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
import React, { useRef, useEffect, CSSProperties } from "react";
|
|
2
|
+
import UI from "#components/ui";
|
|
3
|
+
import DialogHeader from "#components/dialog/views/dialog-header";
|
|
4
|
+
import DialogFooter from "#components/dialog/views/dialog-footer";
|
|
5
|
+
import { useDialogClickHandler } from "#hooks/useDialogClickHandler.js";
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Defines the props for the Dialog component.
|
|
9
|
+
*
|
|
10
|
+
* @property {boolean} [showDialog] - Determines whether the dialog should be shown.
|
|
11
|
+
* @property {boolean} [isAlertDialog] - Determines whether the dialog should be displayed as an alert dialog.
|
|
12
|
+
* @property {() => void} [onClose] - A callback function to be called when the dialog is closed.
|
|
13
|
+
* @property {string} dialogTitle - The title of the dialog.
|
|
14
|
+
* @property {string} [dialogLabel] - An optional label for the dialog.
|
|
15
|
+
* @property {React.ReactNode} children - The content to be displayed inside the dialog.
|
|
16
|
+
* @property {() => void | Promise<void>} [onConfirm] - A callback function to be called when the user confirms the dialog.
|
|
17
|
+
* @property {string} [confirmLabel] - The label for the confirm button.
|
|
18
|
+
* @property {string} [cancelLabel] - The label for the cancel button.
|
|
19
|
+
* @property {string} [className] - An optional CSS class name to be applied to the dialog.
|
|
20
|
+
* @property {CSSProperties} [styles] - Optional inline styles to be applied to the dialog.
|
|
21
|
+
*/
|
|
22
|
+
type DialogModalProps = React.ComponentProps<typeof UI> &
|
|
23
|
+
React.ComponentProps<"dialog"> & {
|
|
24
|
+
dialogTitle: string;
|
|
25
|
+
dialogLabel?: string;
|
|
26
|
+
children: React.ReactNode;
|
|
27
|
+
showDialog?: boolean;
|
|
28
|
+
isAlertDialog?: boolean;
|
|
29
|
+
onClose?: () => void;
|
|
30
|
+
onConfirm?: () => void | Promise<void>;
|
|
31
|
+
confirmLabel?: string;
|
|
32
|
+
cancelLabel?: string;
|
|
33
|
+
className?: string;
|
|
34
|
+
hideFooter?: boolean;
|
|
35
|
+
styles?: CSSProperties;
|
|
36
|
+
};
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* Renders a dialog modal component with customizable content and behavior.
|
|
40
|
+
*
|
|
41
|
+
* @param showDialog - Determines whether the dialog should be shown.
|
|
42
|
+
* @param isAlertDialog - Determines whether the dialog should be displayed as an alert dialog.
|
|
43
|
+
* @param onClose - A callback function to be called when the dialog is closed.
|
|
44
|
+
* @param dialogTitle - The title of the dialog.
|
|
45
|
+
* @param dialogLabel - An optional label for the dialog.
|
|
46
|
+
* @param children - The content to be displayed inside the dialog.
|
|
47
|
+
* @param onConfirm - A callback function to be called when the user confirms the dialog.
|
|
48
|
+
* @param confirmLabel - The label for the confirm button.
|
|
49
|
+
* @param cancelLabel - The label for the cancel button.
|
|
50
|
+
* @param className - An optional CSS class name to be applied to the dialog.
|
|
51
|
+
* @param styles - Optional inline styles to be applied to the dialog.
|
|
52
|
+
*/
|
|
53
|
+
export const Dialog: React.FC<DialogModalProps> = ({
|
|
54
|
+
showDialog,
|
|
55
|
+
isAlertDialog,
|
|
56
|
+
onClose,
|
|
57
|
+
dialogTitle,
|
|
58
|
+
dialogLabel,
|
|
59
|
+
children,
|
|
60
|
+
onConfirm,
|
|
61
|
+
confirmLabel = "Confirm",
|
|
62
|
+
cancelLabel = "Cancel",
|
|
63
|
+
className = "",
|
|
64
|
+
hideFooter,
|
|
65
|
+
styles,
|
|
66
|
+
}) => {
|
|
67
|
+
const dialogRef = useRef<HTMLDialogElement>(null);
|
|
68
|
+
const [isOpen, setIsOpen] = React.useState(showDialog);
|
|
69
|
+
|
|
70
|
+
useEffect(() => {
|
|
71
|
+
setIsOpen(showDialog);
|
|
72
|
+
}, [showDialog]);
|
|
73
|
+
|
|
74
|
+
useEffect(() => {
|
|
75
|
+
const dialog = dialogRef.current;
|
|
76
|
+
if (!dialog) return;
|
|
77
|
+
|
|
78
|
+
if (isOpen) {
|
|
79
|
+
if (isAlertDialog) {
|
|
80
|
+
dialog.show();
|
|
81
|
+
} else {
|
|
82
|
+
dialog.showModal();
|
|
83
|
+
}
|
|
84
|
+
} else {
|
|
85
|
+
dialog.close();
|
|
86
|
+
}
|
|
87
|
+
}, [isOpen, isAlertDialog]);
|
|
88
|
+
|
|
89
|
+
const handleClose = () => {
|
|
90
|
+
if (onClose) onClose();
|
|
91
|
+
setIsOpen(false);
|
|
92
|
+
};
|
|
93
|
+
|
|
94
|
+
const handleClickOutside = useDialogClickHandler(dialogRef, handleClose);
|
|
95
|
+
|
|
96
|
+
return (
|
|
97
|
+
<UI
|
|
98
|
+
as="dialog"
|
|
99
|
+
role={isAlertDialog ? "alertdialog" : "dialog"}
|
|
100
|
+
ref={dialogRef}
|
|
101
|
+
onClose={handleClose}
|
|
102
|
+
onClick={handleClickOutside}
|
|
103
|
+
aria-modal={isOpen ? "true" : undefined}
|
|
104
|
+
className={`${"dialog-modal"} ${className}`}
|
|
105
|
+
aria-label={dialogLabel}
|
|
106
|
+
style={styles}
|
|
107
|
+
>
|
|
108
|
+
<DialogHeader dialogTitle={dialogTitle} onClick={handleClose} />
|
|
109
|
+
|
|
110
|
+
<UI
|
|
111
|
+
as="section"
|
|
112
|
+
className={`dialog-content ${className}`}
|
|
113
|
+
onClick={(e: React.MouseEvent) => e.stopPropagation()}
|
|
114
|
+
>
|
|
115
|
+
{children}
|
|
116
|
+
{!hideFooter && (
|
|
117
|
+
<DialogFooter
|
|
118
|
+
onClose={handleClose}
|
|
119
|
+
onConfirm={onConfirm}
|
|
120
|
+
confirmLabel={confirmLabel}
|
|
121
|
+
cancelLabel={cancelLabel}
|
|
122
|
+
/>
|
|
123
|
+
)}
|
|
124
|
+
</UI>
|
|
125
|
+
</UI>
|
|
126
|
+
);
|
|
127
|
+
};
|
|
128
|
+
export default React.memo(Dialog);
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
// hooks/useClickOutside.ts
|
|
2
|
+
import { RefObject } from "react";
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Hook that handles click outside detection for any HTML element
|
|
6
|
+
* @param ref Reference to the element to detect clicks outside of
|
|
7
|
+
* @param handler Function to call when a click outside is detected
|
|
8
|
+
* @returns Click event handler function
|
|
9
|
+
*/
|
|
10
|
+
const useClickOutside = (
|
|
11
|
+
ref: RefObject<HTMLElement>,
|
|
12
|
+
handler: () => void
|
|
13
|
+
): ((e: React.MouseEvent<HTMLElement>) => void) => {
|
|
14
|
+
return (e: React.MouseEvent<HTMLElement>) => {
|
|
15
|
+
const dimensions = ref.current?.getBoundingClientRect();
|
|
16
|
+
|
|
17
|
+
if (!dimensions) {
|
|
18
|
+
return;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
const isClickOutside =
|
|
22
|
+
e.clientY < dimensions.top ||
|
|
23
|
+
e.clientY > dimensions.bottom ||
|
|
24
|
+
e.clientX < dimensions.left ||
|
|
25
|
+
e.clientX > dimensions.right;
|
|
26
|
+
|
|
27
|
+
if (isClickOutside) {
|
|
28
|
+
handler();
|
|
29
|
+
}
|
|
30
|
+
};
|
|
31
|
+
};
|
|
32
|
+
|
|
33
|
+
export default useClickOutside;
|
|
@@ -0,0 +1,182 @@
|
|
|
1
|
+
import { Meta } from "@storybook/blocks";
|
|
2
|
+
|
|
3
|
+
<Meta title="FP.REACT Views/DialogHeader/Readme" />
|
|
4
|
+
|
|
5
|
+
# Dialog
|
|
6
|
+
|
|
7
|
+
## Summary
|
|
8
|
+
|
|
9
|
+
A reusable dialog/modal component that includes a header, content area, and
|
|
10
|
+
footer. The component provides customizable title, confirm, and cancel
|
|
11
|
+
functionality.
|
|
12
|
+
|
|
13
|
+
## Features
|
|
14
|
+
|
|
15
|
+
- Customizable title, confirm, and cancel functionality
|
|
16
|
+
- Built-in close button with icon
|
|
17
|
+
- Fully accessible
|
|
18
|
+
- Memoized for performance optimization
|
|
19
|
+
|
|
20
|
+
## Props
|
|
21
|
+
|
|
22
|
+
The `Dialog` component accepts the following props:
|
|
23
|
+
|
|
24
|
+
- `isOpen: boolean`: Controls the visibility of the dialog
|
|
25
|
+
- `onClose: () => void`: Callback function triggered when the dialog is closed
|
|
26
|
+
- `title: string`: The title text to display in the header
|
|
27
|
+
- `children: React.ReactNode`: The content to display inside the dialog
|
|
28
|
+
- `onConfirm?: () => void | Promise<void>`: Optional callback function triggered
|
|
29
|
+
when the confirm button is clicked
|
|
30
|
+
- `confirmLabel?: string`: Optional label text for the confirm button
|
|
31
|
+
- `cancelLabel?: string`: Optional label text for the cancel button
|
|
32
|
+
- `className?: string`: Optional className for the dialog content wrapper
|
|
33
|
+
|
|
34
|
+
## Technical Details
|
|
35
|
+
|
|
36
|
+
- Uses React.memo for performance optimization
|
|
37
|
+
- Integrates with the UI component system
|
|
38
|
+
- Implements accessible heading structure using the Heading component
|
|
39
|
+
- Uses Icon.Remove for the close button visual
|
|
40
|
+
- Includes data attributes for styling customization
|
|
41
|
+
|
|
42
|
+
## Usage Example
|
|
43
|
+
|
|
44
|
+
### Basic Usage
|
|
45
|
+
|
|
46
|
+
```tsx
|
|
47
|
+
import { Dialog } from "./dialog";
|
|
48
|
+
|
|
49
|
+
function BasicDialog() {
|
|
50
|
+
const [isOpen, setIsOpen] = useState(false);
|
|
51
|
+
|
|
52
|
+
return (
|
|
53
|
+
<>
|
|
54
|
+
<button onClick={() => setIsOpen(true)}>Open Dialog</button>
|
|
55
|
+
<Dialog
|
|
56
|
+
isOpen={isOpen}
|
|
57
|
+
onClose={() => setIsOpen(false)}
|
|
58
|
+
title="Dialog Title"
|
|
59
|
+
confirmLabel="Confirm"
|
|
60
|
+
cancelLabel="Cancel"
|
|
61
|
+
>
|
|
62
|
+
<div>Dialog content here...</div>
|
|
63
|
+
</Dialog>
|
|
64
|
+
</>
|
|
65
|
+
);
|
|
66
|
+
}
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
# DialogHeader
|
|
70
|
+
|
|
71
|
+
## Summary
|
|
72
|
+
|
|
73
|
+
A reusable header component specifically designed for dialog/modal interfaces.
|
|
74
|
+
The component provides a clean, accessible header with a title and close button
|
|
75
|
+
functionality.
|
|
76
|
+
|
|
77
|
+
## Features
|
|
78
|
+
|
|
79
|
+
- Clean, minimal header design
|
|
80
|
+
- Built-in close button with icon
|
|
81
|
+
- Customizable title using the Heading component
|
|
82
|
+
- Memoized for performance optimization
|
|
83
|
+
- Fully accessible
|
|
84
|
+
|
|
85
|
+
## Props
|
|
86
|
+
|
|
87
|
+
The `DialogHeader` component accepts the following props:
|
|
88
|
+
|
|
89
|
+
- `dialogTitle: string`: The title text to display in the header
|
|
90
|
+
- `onClick: () => void`: Callback function triggered when the close button is
|
|
91
|
+
clicked
|
|
92
|
+
|
|
93
|
+
## Technical Details
|
|
94
|
+
|
|
95
|
+
- Uses React.memo for performance optimization
|
|
96
|
+
- Integrates with the UI component system
|
|
97
|
+
- Implements accessible heading structure using the Heading component
|
|
98
|
+
- Uses Icon.Remove for the close button visual
|
|
99
|
+
- Includes data attributes for styling customization
|
|
100
|
+
|
|
101
|
+
## Usage Example
|
|
102
|
+
|
|
103
|
+
### Basic Usage
|
|
104
|
+
|
|
105
|
+
```tsx
|
|
106
|
+
import { DialogHeader } from "./dialog-header";
|
|
107
|
+
|
|
108
|
+
function BasicDialog() {
|
|
109
|
+
return (
|
|
110
|
+
<DialogHeader
|
|
111
|
+
dialogTitle="Settings"
|
|
112
|
+
onClose={() => console.log("Dialog closed")}
|
|
113
|
+
/>
|
|
114
|
+
);
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
// Example within a Dialog component
|
|
118
|
+
import { Dialog } from "./dialog";
|
|
119
|
+
import { DialogHeader } from "./dialog-header";
|
|
120
|
+
|
|
121
|
+
function SettingsDialog() {
|
|
122
|
+
const [isOpen, setIsOpen] = useState(false);
|
|
123
|
+
|
|
124
|
+
return (
|
|
125
|
+
<Dialog isOpen={isOpen}>
|
|
126
|
+
<DialogHeader
|
|
127
|
+
dialogTitle="Application Settings"
|
|
128
|
+
onClose={() => setIsOpen(false)}
|
|
129
|
+
/>
|
|
130
|
+
<div>Dialog content here...</div>
|
|
131
|
+
</Dialog>
|
|
132
|
+
);
|
|
133
|
+
}
|
|
134
|
+
```
|
|
135
|
+
|
|
136
|
+
# DialogFooter
|
|
137
|
+
|
|
138
|
+
## Summary
|
|
139
|
+
|
|
140
|
+
A reusable footer component specifically designed for dialog/modal interfaces.
|
|
141
|
+
The component provides customizable confirm and cancel buttons.
|
|
142
|
+
|
|
143
|
+
## Features
|
|
144
|
+
|
|
145
|
+
- Customizable confirm and cancel buttons
|
|
146
|
+
- Optional confirm button handler
|
|
147
|
+
- Fully accessible
|
|
148
|
+
|
|
149
|
+
## Props
|
|
150
|
+
|
|
151
|
+
The `DialogFooter` component accepts the following props:
|
|
152
|
+
|
|
153
|
+
- `onClose: () => void`: Callback function triggered when the cancel button is
|
|
154
|
+
clicked
|
|
155
|
+
- `onConfirm?: () => void | Promise<void>`: Optional callback function triggered
|
|
156
|
+
when the confirm button is clicked
|
|
157
|
+
- `confirmLabel: string`: Label text for the confirm button
|
|
158
|
+
- `cancelLabel: string`: Label text for the cancel button
|
|
159
|
+
|
|
160
|
+
## Technical Details
|
|
161
|
+
|
|
162
|
+
- Integrates with the UI component system
|
|
163
|
+
- Includes data attributes for styling customization
|
|
164
|
+
|
|
165
|
+
## Usage Example
|
|
166
|
+
|
|
167
|
+
### Basic Usage
|
|
168
|
+
|
|
169
|
+
```tsx
|
|
170
|
+
import { DialogFooter } from "./dialog-footer";
|
|
171
|
+
|
|
172
|
+
function BasicDialogFooter() {
|
|
173
|
+
return (
|
|
174
|
+
<DialogFooter
|
|
175
|
+
onClose={() => console.log("Dialog closed")}
|
|
176
|
+
onConfirm={() => console.log("Dialog confirmed")}
|
|
177
|
+
confirmLabel="Confirm"
|
|
178
|
+
cancelLabel="Cancel"
|
|
179
|
+
/>
|
|
180
|
+
);
|
|
181
|
+
}
|
|
182
|
+
```
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import React from "react";
|
|
2
|
+
import UI from "#components/ui";
|
|
3
|
+
import Button from "#components/buttons/button";
|
|
4
|
+
|
|
5
|
+
type DialogFooterProps = {
|
|
6
|
+
onClose: () => void;
|
|
7
|
+
onConfirm?: () => void | Promise<void>;
|
|
8
|
+
confirmLabel: string;
|
|
9
|
+
cancelLabel: string;
|
|
10
|
+
};
|
|
11
|
+
|
|
12
|
+
const DialogFooter: React.FC<DialogFooterProps> = ({
|
|
13
|
+
onClose,
|
|
14
|
+
onConfirm,
|
|
15
|
+
confirmLabel,
|
|
16
|
+
cancelLabel,
|
|
17
|
+
}) => {
|
|
18
|
+
return (
|
|
19
|
+
<UI as="section" className="dialog-footer">
|
|
20
|
+
{cancelLabel && (
|
|
21
|
+
<Button
|
|
22
|
+
type="button"
|
|
23
|
+
onClick={onClose}
|
|
24
|
+
className="dialog-button button-secondary"
|
|
25
|
+
data-btn="sm"
|
|
26
|
+
>
|
|
27
|
+
{cancelLabel}
|
|
28
|
+
</Button>
|
|
29
|
+
)}
|
|
30
|
+
|
|
31
|
+
{onConfirm && (
|
|
32
|
+
<Button
|
|
33
|
+
type="button"
|
|
34
|
+
onClick={onConfirm}
|
|
35
|
+
className="dialog-button button-primary"
|
|
36
|
+
data-btn="sm"
|
|
37
|
+
>
|
|
38
|
+
{confirmLabel}
|
|
39
|
+
</Button>
|
|
40
|
+
)}
|
|
41
|
+
</UI>
|
|
42
|
+
);
|
|
43
|
+
};
|
|
44
|
+
|
|
45
|
+
export default DialogFooter;
|