@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.
Files changed (158) hide show
  1. package/libs/chunk-PWVRDQ3R.js +8 -0
  2. package/libs/chunk-PWVRDQ3R.js.map +1 -0
  3. package/libs/chunk-SVS4MX3U.cjs +31 -0
  4. package/libs/chunk-SVS4MX3U.cjs.map +1 -0
  5. package/libs/{icons-2f29127c.d.ts → icons-31ace3de.d.ts} +87 -81
  6. package/libs/icons.cjs +2 -2
  7. package/libs/icons.d.cts +1 -1
  8. package/libs/icons.d.ts +1 -1
  9. package/libs/icons.js +1 -1
  10. package/libs/index.cjs +42 -42
  11. package/libs/index.cjs.map +1 -1
  12. package/libs/index.d.cts +59 -29
  13. package/libs/index.d.ts +59 -29
  14. package/libs/index.js +7 -7
  15. package/libs/index.js.map +1 -1
  16. package/package.json +4 -3
  17. package/src/components/README.mdx +84 -0
  18. package/src/components/alert/README.mdx +86 -0
  19. package/src/components/alert/alert.mdx +74 -0
  20. package/src/components/alert/alert.scss +80 -0
  21. package/src/components/alert/alert.stories.tsx +132 -0
  22. package/src/components/alert/alert.tsx +154 -0
  23. package/src/components/alert/elements/README.mdx +77 -0
  24. package/src/components/alert/elements/dismiss-button.stories.tsx +31 -0
  25. package/src/components/alert/elements/dismiss-button.tsx +28 -0
  26. package/src/components/badge/badge.mdx +124 -0
  27. package/src/components/badge/badge.scss +4 -4
  28. package/src/components/badge/badge.stories.tsx +21 -23
  29. package/src/components/breadcrumbs/breadcrumb.scss +2 -2
  30. package/src/components/breadcrumbs/breadcrumb.stories.tsx +42 -47
  31. package/src/components/buttons/button.scss +41 -15
  32. package/src/components/buttons/button.stories.tsx +8 -1
  33. package/src/components/buttons/button.test.tsx +72 -72
  34. package/src/components/cards/card.stories.tsx +15 -15
  35. package/src/components/details/details.scss +26 -6
  36. package/src/components/details/details.stories.tsx +33 -30
  37. package/src/components/details/details.tsx +17 -17
  38. package/src/components/dialog/README.mdx +187 -0
  39. package/src/components/dialog/dialog-modal.stories.tsx +113 -0
  40. package/src/components/dialog/dialog-modal.tsx +111 -0
  41. package/src/components/dialog/dialog.scss +76 -0
  42. package/src/components/dialog/dialog.stories.tsx +116 -0
  43. package/src/components/dialog/dialog.tsx +128 -0
  44. package/src/components/dialog/hooks/useClickOutside.ts +33 -0
  45. package/src/components/dialog/views/README.mdx +182 -0
  46. package/src/components/dialog/views/dialog-footer.tsx +45 -0
  47. package/src/components/dialog/views/dialog-header.stories.tsx +42 -0
  48. package/src/components/dialog/views/dialog-header.tsx +61 -0
  49. package/src/components/form/form.stories.tsx +16 -16
  50. package/src/components/form/input.stories.tsx +62 -62
  51. package/src/components/form/select.stories.tsx +22 -15
  52. package/src/components/heading/heading.stories.tsx +32 -33
  53. package/src/components/heading/heading.tsx +1 -1
  54. package/src/components/icons/components/add.tsx +14 -14
  55. package/src/components/icons/components/alert-solid.tsx +36 -0
  56. package/src/components/icons/components/alert-square-solid.tsx +36 -0
  57. package/src/components/icons/components/info-solid.tsx +40 -0
  58. package/src/components/icons/components/info.tsx +36 -0
  59. package/src/components/icons/components/question-solid.tsx +36 -0
  60. package/src/components/icons/components/success-solid.tsx +36 -0
  61. package/src/components/icons/components/svg.tsx +0 -1
  62. package/src/components/icons/components/warn-solid.tsx +36 -0
  63. package/src/components/icons/icon.scss +1 -3
  64. package/src/components/icons/icon.stories.tsx +87 -78
  65. package/src/components/icons/icon.tsx +57 -52
  66. package/src/components/icons/index.ts +36 -29
  67. package/src/components/icons/types.ts +1 -1
  68. package/src/components/images/figure.stories.tsx +13 -13
  69. package/src/components/images/img.stories.tsx +12 -12
  70. package/src/components/link/link.stories.tsx +32 -35
  71. package/src/components/link/link.tsx +27 -14
  72. package/src/components/list/list.stories.tsx +16 -16
  73. package/src/components/modal/dialog.tsx +13 -12
  74. package/src/components/modal/modal.tsx +28 -30
  75. package/src/components/nav/nav.stories.tsx +25 -24
  76. package/src/components/popover/popover.stories.tsx +17 -18
  77. package/src/components/progress/progress.stories.tsx +14 -20
  78. package/src/components/tag/tag.stories.tsx +17 -18
  79. package/src/components/text/text.stories.tsx +28 -29
  80. package/src/components/text-to-speech/TextToSpeech.stories.tsx +100 -101
  81. package/src/components/ui.tsx +28 -25
  82. package/src/decorators/instructions.tsx +44 -0
  83. package/src/hooks/useDialogClickHandler.ts +26 -0
  84. package/src/index.scss +23 -22
  85. package/src/index.ts +31 -30
  86. package/src/patterns/page/page-header.stories.tsx +17 -21
  87. package/src/sass/_globals.scss +14 -32
  88. package/src/sass/_styles.scss +2 -1
  89. package/src/sass/styles/_colors.scss +13 -0
  90. package/src/styles/alert/alert.css +68 -0
  91. package/src/styles/alert/alert.css.map +1 -0
  92. package/src/styles/badge/badge.css +3 -3
  93. package/src/styles/breadcrumbs/breadcrumb.css +1 -1
  94. package/src/styles/buttons/button.css +25 -2
  95. package/src/styles/buttons/button.css.map +1 -1
  96. package/src/styles/details/details.css +19 -4
  97. package/src/styles/details/details.css.map +1 -1
  98. package/src/styles/dialog/dialog.css +76 -0
  99. package/src/styles/dialog/dialog.css.map +1 -0
  100. package/src/styles/icons/icon.css +1 -3
  101. package/src/styles/icons/icon.css.map +1 -1
  102. package/src/styles/index.css +213 -60
  103. package/src/styles/index.css.map +1 -1
  104. package/libs/chunk-TBM2QIVT.js +0 -8
  105. package/libs/chunk-TBM2QIVT.js.map +0 -1
  106. package/libs/chunk-VAH6X2DZ.cjs +0 -31
  107. package/libs/chunk-VAH6X2DZ.cjs.map +0 -1
  108. package/libs/components/badge/badge.css +0 -1
  109. package/libs/components/badge/badge.css.map +0 -1
  110. package/libs/components/badge/badge.min.css +0 -3
  111. package/libs/components/breadcrumbs/breadcrumb.css +0 -1
  112. package/libs/components/breadcrumbs/breadcrumb.css.map +0 -1
  113. package/libs/components/breadcrumbs/breadcrumb.min.css +0 -3
  114. package/libs/components/buttons/button.css +0 -1
  115. package/libs/components/buttons/button.css.map +0 -1
  116. package/libs/components/buttons/button.min.css +0 -3
  117. package/libs/components/cards/card-style.css +0 -1
  118. package/libs/components/cards/card-style.css.map +0 -1
  119. package/libs/components/cards/card-style.min.css +0 -3
  120. package/libs/components/cards/card.css +0 -1
  121. package/libs/components/cards/card.css.map +0 -1
  122. package/libs/components/cards/card.min.css +0 -3
  123. package/libs/components/details/details.css +0 -1
  124. package/libs/components/details/details.css.map +0 -1
  125. package/libs/components/details/details.min.css +0 -3
  126. package/libs/components/form/form.css +0 -1
  127. package/libs/components/form/form.css.map +0 -1
  128. package/libs/components/form/form.min.css +0 -3
  129. package/libs/components/icons/icon.css +0 -1
  130. package/libs/components/icons/icon.css.map +0 -1
  131. package/libs/components/icons/icon.min.css +0 -3
  132. package/libs/components/images/img.css +0 -1
  133. package/libs/components/images/img.css.map +0 -1
  134. package/libs/components/images/img.min.css +0 -3
  135. package/libs/components/layout/landmarks.css +0 -1
  136. package/libs/components/layout/landmarks.css.map +0 -1
  137. package/libs/components/layout/landmarks.min.css +0 -3
  138. package/libs/components/link/link.css +0 -1
  139. package/libs/components/link/link.css.map +0 -1
  140. package/libs/components/link/link.min.css +0 -3
  141. package/libs/components/nav/nav.css +0 -1
  142. package/libs/components/nav/nav.css.map +0 -1
  143. package/libs/components/nav/nav.min.css +0 -3
  144. package/libs/components/progress/progress.css +0 -1
  145. package/libs/components/progress/progress.css.map +0 -1
  146. package/libs/components/progress/progress.min.css +0 -3
  147. package/libs/components/styles/index.css +0 -1
  148. package/libs/components/styles/index.css.map +0 -1
  149. package/libs/components/styles/index.min.css +0 -3
  150. package/libs/components/tag/tag.css +0 -1
  151. package/libs/components/tag/tag.css.map +0 -1
  152. package/libs/components/tag/tag.min.css +0 -3
  153. package/libs/components/text-to-speech/text-to-speech.css +0 -1
  154. package/libs/components/text-to-speech/text-to-speech.css.map +0 -1
  155. package/libs/components/text-to-speech/text-to-speech.min.css +0 -3
  156. package/libs/index.css +0 -1
  157. package/libs/index.css.map +0 -1
  158. 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;