@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
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
details {
|
|
2
2
|
--details-w: 100%;
|
|
3
|
-
--details-h:
|
|
3
|
+
--details-h: max-content;
|
|
4
4
|
--details-border: 1px solid #dfdfdf;
|
|
5
5
|
--details-display: flex;
|
|
6
6
|
--details-justify: flex-start;
|
|
@@ -8,9 +8,9 @@ details {
|
|
|
8
8
|
--details-gap: 0rem;
|
|
9
9
|
--details-px: 1.5rem;
|
|
10
10
|
--details-py: 1rem;
|
|
11
|
-
--details-radius:
|
|
11
|
+
--details-radius: var(--border-radius);
|
|
12
12
|
--summary-cursor: pointer;
|
|
13
|
-
--summary-transitions: all 0.75s
|
|
13
|
+
--summary-transitions: all 0.75s ease-in-out;
|
|
14
14
|
--summary-display: flex;
|
|
15
15
|
--summary-justify: flex-start;
|
|
16
16
|
--summary-align: center;
|
|
@@ -18,6 +18,7 @@ details {
|
|
|
18
18
|
--max-h-closed: 6.25rem;
|
|
19
19
|
--max-h-open: 50rem;
|
|
20
20
|
|
|
21
|
+
interpolate-size: allow-keywords;
|
|
21
22
|
display: var(--details-display);
|
|
22
23
|
flex-direction: var(--details-direction);
|
|
23
24
|
justify-content: var(--details-justify);
|
|
@@ -29,6 +30,25 @@ details {
|
|
|
29
30
|
overflow: clip;
|
|
30
31
|
border-radius: var(--details-radius);
|
|
31
32
|
|
|
33
|
+
// Handle multiple details elements
|
|
34
|
+
& + details {
|
|
35
|
+
border-radius: 0; // remove radius from middle elements
|
|
36
|
+
border-top: none; // optional: remove double borders
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
&:first-of-type {
|
|
40
|
+
border-radius: var(--details-radius) var(--details-radius) 0 0;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
&:last-of-type {
|
|
44
|
+
border-radius: 0 0 var(--details-radius) var(--details-radius);
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
// If it's the only details element, keep original radius
|
|
48
|
+
&:only-of-type {
|
|
49
|
+
border-radius: var(--details-radius);
|
|
50
|
+
}
|
|
51
|
+
|
|
32
52
|
&::marker {
|
|
33
53
|
content: none;
|
|
34
54
|
}
|
|
@@ -43,19 +63,19 @@ details {
|
|
|
43
63
|
list-style: none;
|
|
44
64
|
border-top-left-radius: var(--details-radius);
|
|
45
65
|
border-top-right-radius: var(--details-radius);
|
|
66
|
+
transition: var(--summary-transitions);
|
|
46
67
|
|
|
47
68
|
&::-webkit-details-marker {
|
|
48
69
|
display: none;
|
|
49
70
|
}
|
|
50
71
|
|
|
51
|
-
&:focus-within
|
|
72
|
+
&:focus-within {
|
|
52
73
|
outline: none;
|
|
53
74
|
border-bottom: solid 2px var(--details-border);
|
|
54
75
|
background-color: whitesmoke;
|
|
55
76
|
}
|
|
56
77
|
|
|
57
78
|
/* This ensures no bullet points are shown */
|
|
58
|
-
|
|
59
79
|
&:hover {
|
|
60
80
|
cursor: var(--summary-cursor);
|
|
61
81
|
}
|
|
@@ -72,7 +92,7 @@ details {
|
|
|
72
92
|
}
|
|
73
93
|
|
|
74
94
|
&[open] {
|
|
75
|
-
max-height:
|
|
95
|
+
max-height: max-content;
|
|
76
96
|
transition: var(--summary-transitions);
|
|
77
97
|
> summary {
|
|
78
98
|
border-bottom: var(--details-border);
|
|
@@ -1,9 +1,8 @@
|
|
|
1
|
-
import { StoryObj, Meta } from
|
|
2
|
-
import { within, expect, userEvent } from
|
|
1
|
+
import { StoryObj, Meta } from "@storybook/react";
|
|
2
|
+
import { within, expect, userEvent } from "@storybook/test";
|
|
3
3
|
|
|
4
|
-
|
|
5
|
-
import
|
|
6
|
-
import Icons from '../icons/icon'
|
|
4
|
+
import Details from "./details";
|
|
5
|
+
import Icons from "../icons/icon";
|
|
7
6
|
|
|
8
7
|
const content = (
|
|
9
8
|
<>
|
|
@@ -25,49 +24,51 @@ const content = (
|
|
|
25
24
|
hic est placeat!
|
|
26
25
|
</p>
|
|
27
26
|
</>
|
|
28
|
-
)
|
|
27
|
+
);
|
|
29
28
|
|
|
30
|
-
const icon = <Icons.Add
|
|
29
|
+
const icon = <Icons.Add />;
|
|
31
30
|
|
|
32
31
|
const meta: Meta<typeof Details> = {
|
|
33
|
-
title:
|
|
32
|
+
title: "FP.REACT Components/Details",
|
|
34
33
|
component: Details,
|
|
34
|
+
tags: ["rc"],
|
|
35
35
|
args: {
|
|
36
|
-
// @ts-ignore
|
|
37
36
|
children: content,
|
|
38
37
|
icon: icon,
|
|
39
38
|
summary: <>Summary Section</>,
|
|
40
39
|
},
|
|
41
|
-
actions: { argTypesRegex:
|
|
40
|
+
actions: { argTypesRegex: "^on.*" },
|
|
42
41
|
decorators: [
|
|
43
42
|
(Story) => (
|
|
44
|
-
<div className="container" style={{ minWidth:
|
|
43
|
+
<div className="container" style={{ minWidth: "50vw" }}>
|
|
45
44
|
<Story />
|
|
46
45
|
</div>
|
|
47
46
|
),
|
|
48
47
|
],
|
|
49
|
-
} as Story
|
|
48
|
+
} as Story;
|
|
50
49
|
|
|
51
|
-
export default meta
|
|
52
|
-
type Story = StoryObj<typeof Details
|
|
50
|
+
export default meta;
|
|
51
|
+
type Story = StoryObj<typeof Details>;
|
|
53
52
|
|
|
54
53
|
export const DetailsDropdown: Story = {
|
|
55
54
|
args: {},
|
|
56
55
|
play: async ({ canvasElement }) => {
|
|
57
|
-
const canvas = within(canvasElement)
|
|
58
|
-
expect(
|
|
56
|
+
const canvas = within(canvasElement);
|
|
57
|
+
expect(
|
|
58
|
+
canvas.getByRole("group", { name: /details dropdown/i })
|
|
59
|
+
).toBeInTheDocument();
|
|
59
60
|
},
|
|
60
|
-
} as Story
|
|
61
|
+
} as Story;
|
|
61
62
|
|
|
62
63
|
export const DetailsOpen: Story = {
|
|
63
64
|
args: {
|
|
64
65
|
open: true,
|
|
65
66
|
},
|
|
66
67
|
play: async ({ canvasElement }) => {
|
|
67
|
-
const canvas = within(canvasElement)
|
|
68
|
-
expect(canvas.getByRole(
|
|
68
|
+
const canvas = within(canvasElement);
|
|
69
|
+
expect(canvas.getByRole("group")).toBeInTheDocument();
|
|
69
70
|
},
|
|
70
|
-
} as Story
|
|
71
|
+
} as Story;
|
|
71
72
|
|
|
72
73
|
export const CustomDropdown: Story = {
|
|
73
74
|
render: () => (
|
|
@@ -87,12 +88,12 @@ export const CustomDropdown: Story = {
|
|
|
87
88
|
</p>
|
|
88
89
|
</>
|
|
89
90
|
),
|
|
90
|
-
} as Story
|
|
91
|
+
} as Story;
|
|
91
92
|
|
|
92
93
|
export const DetailsAccordion: Story = {
|
|
93
94
|
render: () => (
|
|
94
95
|
<>
|
|
95
|
-
|
|
96
|
+
<Details
|
|
96
97
|
summary="Summary Section"
|
|
97
98
|
icon={icon}
|
|
98
99
|
ariaLabel="Details Section"
|
|
@@ -100,7 +101,7 @@ export const DetailsAccordion: Story = {
|
|
|
100
101
|
>
|
|
101
102
|
{content}
|
|
102
103
|
</Details>
|
|
103
|
-
|
|
104
|
+
<Details
|
|
104
105
|
summary="Summary Section"
|
|
105
106
|
icon={icon}
|
|
106
107
|
ariaLabel="Details Section"
|
|
@@ -108,7 +109,7 @@ export const DetailsAccordion: Story = {
|
|
|
108
109
|
>
|
|
109
110
|
{content}
|
|
110
111
|
</Details>
|
|
111
|
-
|
|
112
|
+
<Details
|
|
112
113
|
summary="Summary Section"
|
|
113
114
|
icon={icon}
|
|
114
115
|
ariaLabel="Details Section"
|
|
@@ -117,8 +118,8 @@ export const DetailsAccordion: Story = {
|
|
|
117
118
|
{content}
|
|
118
119
|
</Details>
|
|
119
120
|
</>
|
|
120
|
-
)
|
|
121
|
-
} as Story
|
|
121
|
+
),
|
|
122
|
+
} as Story;
|
|
122
123
|
|
|
123
124
|
export const DetailsInteractionTest: Story = {
|
|
124
125
|
args: {},
|
|
@@ -126,13 +127,15 @@ export const DetailsInteractionTest: Story = {
|
|
|
126
127
|
const canvas = within(canvasElement);
|
|
127
128
|
|
|
128
129
|
// Find the summary element
|
|
129
|
-
const summaryElement = canvas.getByText(
|
|
130
|
+
const summaryElement = canvas.getByText("Summary Section");
|
|
130
131
|
|
|
131
132
|
// Simulate a click on the summary element
|
|
132
133
|
await userEvent.click(summaryElement);
|
|
133
134
|
|
|
134
135
|
// Assert that the details element is open
|
|
135
|
-
const detailsElement = canvas.getByRole(
|
|
136
|
-
|
|
136
|
+
const detailsElement = canvas.getByRole("group", {
|
|
137
|
+
name: /details dropdown/i,
|
|
138
|
+
});
|
|
139
|
+
expect(detailsElement).toHaveAttribute("open");
|
|
137
140
|
},
|
|
138
|
-
}
|
|
141
|
+
};
|
|
@@ -1,19 +1,19 @@
|
|
|
1
|
-
import UI from
|
|
2
|
-
import React from
|
|
1
|
+
import UI from "#components/ui";
|
|
2
|
+
import React from "react";
|
|
3
3
|
|
|
4
4
|
type DetailsProps = {
|
|
5
5
|
/**
|
|
6
6
|
* The summary text shown for the details.
|
|
7
7
|
* Required.
|
|
8
8
|
*/
|
|
9
|
-
summary: React.ReactNode
|
|
9
|
+
summary: React.ReactNode;
|
|
10
10
|
|
|
11
11
|
/**
|
|
12
12
|
* The aria-label element for accessibility.
|
|
13
13
|
*/
|
|
14
|
-
ariaLabel: string
|
|
15
|
-
} & React.ComponentProps<
|
|
16
|
-
Partial<React.ComponentProps<typeof UI
|
|
14
|
+
ariaLabel: string;
|
|
15
|
+
} & React.ComponentProps<"details"> &
|
|
16
|
+
Partial<React.ComponentProps<typeof UI>>;
|
|
17
17
|
|
|
18
18
|
/**3
|
|
19
19
|
* Details component props interface.
|
|
@@ -42,16 +42,16 @@ export const Details = ({
|
|
|
42
42
|
ref,
|
|
43
43
|
...props
|
|
44
44
|
}: DetailsProps) => {
|
|
45
|
-
const defaultStyles: React.CSSProperties = { ...styles }
|
|
45
|
+
const defaultStyles: React.CSSProperties = { ...styles };
|
|
46
46
|
|
|
47
47
|
const onPointerDownCallback = (e: React.PointerEvent<HTMLDetailsElement>) => {
|
|
48
|
-
if (onPointerDown) onPointerDown?.(e)
|
|
49
|
-
if (onPointerDown) onPointerDown?.(e)
|
|
50
|
-
}
|
|
48
|
+
if (onPointerDown) onPointerDown?.(e);
|
|
49
|
+
if (onPointerDown) onPointerDown?.(e);
|
|
50
|
+
};
|
|
51
51
|
|
|
52
52
|
const onToggleCallback = (e: React.PointerEvent<HTMLDetailsElement>) => {
|
|
53
|
-
if (onToggle) onPointerDown?.(e)
|
|
54
|
-
}
|
|
53
|
+
if (onToggle) onPointerDown?.(e);
|
|
54
|
+
};
|
|
55
55
|
return (
|
|
56
56
|
<UI
|
|
57
57
|
as="details"
|
|
@@ -60,7 +60,7 @@ export const Details = ({
|
|
|
60
60
|
onToggle={onToggleCallback}
|
|
61
61
|
ref={ref}
|
|
62
62
|
open={open}
|
|
63
|
-
aria-label={ariaLabel ||
|
|
63
|
+
aria-label={ariaLabel || "Details dropdown"}
|
|
64
64
|
// aria-roledescription="detail accordion"
|
|
65
65
|
name={name}
|
|
66
66
|
{...props}
|
|
@@ -71,8 +71,8 @@ export const Details = ({
|
|
|
71
71
|
</UI>
|
|
72
72
|
<UI as="section">{children}</UI>
|
|
73
73
|
</UI>
|
|
74
|
-
)
|
|
75
|
-
}
|
|
74
|
+
);
|
|
75
|
+
};
|
|
76
76
|
|
|
77
|
-
export default Details
|
|
78
|
-
Details.displayName =
|
|
77
|
+
export default Details;
|
|
78
|
+
Details.displayName = "Details";
|
|
@@ -0,0 +1,187 @@
|
|
|
1
|
+
import { Meta } from "@storybook/blocks";
|
|
2
|
+
|
|
3
|
+
<Meta title="FP.REACT Components/Dialog/Readme" />
|
|
4
|
+
|
|
5
|
+
# Dialog Components
|
|
6
|
+
|
|
7
|
+
A flexible Dialog component system for building accessible modal dialogs in
|
|
8
|
+
React applications. The component supports:
|
|
9
|
+
|
|
10
|
+
- Controlled modal behavior with built-in state management
|
|
11
|
+
- Full ARIA accessibility support out of the box
|
|
12
|
+
- Customizable headers and content layout
|
|
13
|
+
- Multiple variants including standard dialogs and alert dialogs
|
|
14
|
+
- Event handling for open, close, and cancel actions
|
|
15
|
+
- Responsive design with inline rendering option
|
|
16
|
+
- Modal and non-modal dialog variants via `isAlertDialog`
|
|
17
|
+
- Built-in header with close functionality
|
|
18
|
+
- Configurable footer with confirm/cancel actions
|
|
19
|
+
- Click-outside-to-close behavior
|
|
20
|
+
- Focus management and keyboard navigation
|
|
21
|
+
|
|
22
|
+
Built with TypeScript and React, this component follows modern best practices
|
|
23
|
+
and provides a developer-friendly API for implementing modal dialogs in your web
|
|
24
|
+
applications.
|
|
25
|
+
|
|
26
|
+
## Overview
|
|
27
|
+
|
|
28
|
+
The dialog component system consists of the following key parts:
|
|
29
|
+
|
|
30
|
+
### Dialog Component
|
|
31
|
+
|
|
32
|
+
The dialog system consists of:
|
|
33
|
+
|
|
34
|
+
- `Dialog`: Main modal component
|
|
35
|
+
- `DialogHeader`: Header with title and close button
|
|
36
|
+
- `DialogFooter`: Footer with confirm/cancel actions
|
|
37
|
+
|
|
38
|
+
### Props
|
|
39
|
+
|
|
40
|
+
The Dialog component accepts two types of props - required core props and
|
|
41
|
+
optional configuration props.
|
|
42
|
+
|
|
43
|
+
#### Required Props
|
|
44
|
+
|
|
45
|
+
- `dialogTitle` (`string`): Title text displayed in dialog header
|
|
46
|
+
- `children` (`React.ReactNode`): Content rendered inside dialog body
|
|
47
|
+
- `showDialog` (`boolean`): Controls dialog visibility state
|
|
48
|
+
|
|
49
|
+
#### Optional Props
|
|
50
|
+
|
|
51
|
+
- `isAlertDialog` (`boolean`, default: `false`): Renders as non-modal alert
|
|
52
|
+
dialog
|
|
53
|
+
- `onClose` (`() => void`): Callback when dialog closes
|
|
54
|
+
- `onConfirm` (`() => void | Promise<void>`): Confirmation action callback
|
|
55
|
+
- `confirmLabel` (`string`, default: "Confirm"): Custom confirm button text
|
|
56
|
+
- `cancelLabel` (`string`, default: "Cancel"): Custom cancel button text
|
|
57
|
+
- `className` (`string`, default: ""): Additional CSS classes
|
|
58
|
+
|
|
59
|
+
The component also inherits props from:
|
|
60
|
+
|
|
61
|
+
- `React.ComponentProps<typeof UI>`
|
|
62
|
+
- `React.ComponentProps<dialog>`
|
|
63
|
+
|
|
64
|
+
### Usage Examples
|
|
65
|
+
|
|
66
|
+
```tsx
|
|
67
|
+
// Basic usage
|
|
68
|
+
import { Dialog } from "./dialog";
|
|
69
|
+
|
|
70
|
+
function MyComponent() {
|
|
71
|
+
const [isOpen, setIsOpen] = React.useState(false);
|
|
72
|
+
|
|
73
|
+
return (
|
|
74
|
+
<>
|
|
75
|
+
<button onClick={() => setIsOpen(true)}>Open Dialog</button>
|
|
76
|
+
<Dialog
|
|
77
|
+
isOpen={isOpen}
|
|
78
|
+
onClose={() => setIsOpen(false)}
|
|
79
|
+
dialogTitle="My Dialog"
|
|
80
|
+
dialogId="example-dialog"
|
|
81
|
+
>
|
|
82
|
+
<div>Dialog content goes here</div>
|
|
83
|
+
</Dialog>
|
|
84
|
+
</>
|
|
85
|
+
);
|
|
86
|
+
}
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
## Alert Dialog Example
|
|
90
|
+
|
|
91
|
+
A basic example of an Alert Dialog component implementation. Alert dialogs are
|
|
92
|
+
used to show important messages that require user acknowledgment or
|
|
93
|
+
confirmation.
|
|
94
|
+
|
|
95
|
+
### Features
|
|
96
|
+
|
|
97
|
+
- Uses the `Dialog` component with `isAlertDialog` prop set to true
|
|
98
|
+
- Displays a warning message with a confirmation button
|
|
99
|
+
- Custom dialog title "Warning"
|
|
100
|
+
- Unique dialog identifier "alert-dialog"
|
|
101
|
+
|
|
102
|
+
### Usage
|
|
103
|
+
|
|
104
|
+
```tsx
|
|
105
|
+
function AlertExample() {
|
|
106
|
+
return (
|
|
107
|
+
<Dialog isAlertDialog dialogTitle="Warning" dialogId="alert-dialog">
|
|
108
|
+
<p>This action cannot be undone. Continue?</p>
|
|
109
|
+
<button onClick={() => {}}>Confirm</button>
|
|
110
|
+
</Dialog>
|
|
111
|
+
);
|
|
112
|
+
}
|
|
113
|
+
```
|
|
114
|
+
|
|
115
|
+
### Custom header dialog example
|
|
116
|
+
|
|
117
|
+
```tsx
|
|
118
|
+
// Custom header dialog
|
|
119
|
+
function CustomDialog() {
|
|
120
|
+
return (
|
|
121
|
+
<Dialog hideDialogHeader dialogId="custom-dialog">
|
|
122
|
+
<h2>Custom Header</h2>
|
|
123
|
+
<div>Content with custom header</div>
|
|
124
|
+
</Dialog>
|
|
125
|
+
);
|
|
126
|
+
}
|
|
127
|
+
```
|
|
128
|
+
|
|
129
|
+
## Styles
|
|
130
|
+
|
|
131
|
+
This `dialog.scss` file defines the styling for a dialog component with
|
|
132
|
+
customizable properties using CSS variables.
|
|
133
|
+
|
|
134
|
+
## CSS Custom Properties
|
|
135
|
+
|
|
136
|
+
### Dialog Dimensions and Spacing
|
|
137
|
+
|
|
138
|
+
- `--dialog-min-w`: Minimum width of the dialog (320px)
|
|
139
|
+
- `--dialog-gap`: Spacing between dialog elements (0.75rem)
|
|
140
|
+
- `--dialog-padding`: General padding inside dialog (0.75rem)
|
|
141
|
+
- `--dialog-padding-inline`: Horizontal padding inside dialog (1rem)
|
|
142
|
+
|
|
143
|
+
### Border Properties
|
|
144
|
+
|
|
145
|
+
- `--dialog-border-color`: Color of dialog border (lightgray)
|
|
146
|
+
- `--dialog-border-width`: Width of dialog border (thin)
|
|
147
|
+
- `--dialog-border-style`: Style of dialog border (solid)
|
|
148
|
+
- `--dialog-border-radius`: Border radius of dialog corners (0.5rem)
|
|
149
|
+
|
|
150
|
+
### Dialog Button Styles
|
|
151
|
+
|
|
152
|
+
- `--dialog-button-bg`: Background color for dialog buttons (transparent)
|
|
153
|
+
- `--dialog-button-border`: Border style for dialog buttons (transparent thin
|
|
154
|
+
solid)
|
|
155
|
+
- `--dialog-button-hover-bg`: Background color for button hover state
|
|
156
|
+
(whitesmoke)
|
|
157
|
+
- `--dialog-close-color`: Color for close button (gray)
|
|
158
|
+
|
|
159
|
+
### Layout Properties
|
|
160
|
+
|
|
161
|
+
- `--dialog-display`: Display property for dialog (flex)
|
|
162
|
+
- `--dialog-flex-direction`: Flex direction for dialog layout (column)
|
|
163
|
+
|
|
164
|
+
## Component Structure
|
|
165
|
+
|
|
166
|
+
### Base Dialog
|
|
167
|
+
|
|
168
|
+
- Sets minimum width and spacing
|
|
169
|
+
- Applies border and padding styles
|
|
170
|
+
- Implements flex layout when dialog is open
|
|
171
|
+
|
|
172
|
+
### Dialog Header
|
|
173
|
+
|
|
174
|
+
- Implements a flex layout with space-between alignment
|
|
175
|
+
- Contains title (h3) and close button
|
|
176
|
+
- Custom button styling with hover states
|
|
177
|
+
|
|
178
|
+
### Alert Dialog Actions
|
|
179
|
+
|
|
180
|
+
- Container for dialog action buttons
|
|
181
|
+
- Uses flex layout with left alignment
|
|
182
|
+
- Includes gap spacing between buttons
|
|
183
|
+
|
|
184
|
+
## Usage
|
|
185
|
+
|
|
186
|
+
To customize the dialog appearance, override the CSS custom properties in your
|
|
187
|
+
stylesheet.
|
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
import { StoryObj, Meta } from "@storybook/react";
|
|
2
|
+
import { within, expect, userEvent, waitFor } from "@storybook/test";
|
|
3
|
+
|
|
4
|
+
import DialogModal from "./dialog-modal";
|
|
5
|
+
import WithInstructions from "#/decorators/instructions";
|
|
6
|
+
const meta: Meta<typeof DialogModal> = {
|
|
7
|
+
title: "FP.REACT Components/Dialog/DialogModal",
|
|
8
|
+
component: DialogModal,
|
|
9
|
+
tags: ["autodocs", "experimental"],
|
|
10
|
+
parameters: {
|
|
11
|
+
actions: { argTypesRegex: "^on.*" },
|
|
12
|
+
docs: {
|
|
13
|
+
description: {
|
|
14
|
+
component:
|
|
15
|
+
"DialogModal is a modal dialog component that provides an accessible overlay for displaying content.",
|
|
16
|
+
},
|
|
17
|
+
},
|
|
18
|
+
},
|
|
19
|
+
args: {
|
|
20
|
+
children: "Dialog Content",
|
|
21
|
+
title: "Dialog Title",
|
|
22
|
+
isOpen: false,
|
|
23
|
+
onClose: () => {},
|
|
24
|
+
},
|
|
25
|
+
} as Meta;
|
|
26
|
+
|
|
27
|
+
export default meta;
|
|
28
|
+
type Story = StoryObj<typeof DialogModal>;
|
|
29
|
+
|
|
30
|
+
export const Default: Story = {
|
|
31
|
+
args: {
|
|
32
|
+
children:
|
|
33
|
+
"DialogModal is a modal dialog component that provides an accessible overlay for displaying content.",
|
|
34
|
+
},
|
|
35
|
+
decorators: [WithInstructions()],
|
|
36
|
+
play: async ({ canvasElement }) => {
|
|
37
|
+
const canvas = within(canvasElement);
|
|
38
|
+
expect(canvas.getByRole("dialog")).toBeInTheDocument();
|
|
39
|
+
},
|
|
40
|
+
} as Story;
|
|
41
|
+
|
|
42
|
+
const instructions = (
|
|
43
|
+
<div>
|
|
44
|
+
<p>
|
|
45
|
+
In this example, the dialog is opened and closed using the Storybook
|
|
46
|
+
interactions.{" "}
|
|
47
|
+
</p>
|
|
48
|
+
</div>
|
|
49
|
+
);
|
|
50
|
+
|
|
51
|
+
export const ModalInteractions: Story = {
|
|
52
|
+
args: {
|
|
53
|
+
children: "This dialog can be opened and closed using the button",
|
|
54
|
+
dialogTitle: "Interactive Dialog",
|
|
55
|
+
btnLabel: "Open Dialog",
|
|
56
|
+
},
|
|
57
|
+
decorators: [WithInstructions(instructions)],
|
|
58
|
+
play: async ({ canvasElement, step }) => {
|
|
59
|
+
const canvas = within(canvasElement);
|
|
60
|
+
|
|
61
|
+
// Find and click the open button
|
|
62
|
+
const openButton = canvas.getByRole("button", { name: /open dialog/i });
|
|
63
|
+
|
|
64
|
+
await step("Open Dialog", async () => {
|
|
65
|
+
await step("Open Dialog", async () => {
|
|
66
|
+
await userEvent.click(openButton, { delay: 1500 }); // Verify dialog is open
|
|
67
|
+
const dialog = canvas.getByRole("dialog");
|
|
68
|
+
expect(dialog).toBeVisible();
|
|
69
|
+
});
|
|
70
|
+
});
|
|
71
|
+
|
|
72
|
+
await step("Close Dialog", async () => {
|
|
73
|
+
const dialog = canvas.getByRole("dialog");
|
|
74
|
+
|
|
75
|
+
// Find and click close button
|
|
76
|
+
const closeButton = canvas.getByRole("button", {
|
|
77
|
+
name: /close dialog/i,
|
|
78
|
+
});
|
|
79
|
+
expect(closeButton).toHaveFocus();
|
|
80
|
+
await userEvent.click(closeButton, { delay: 1000 });
|
|
81
|
+
// Verify dialog is closed
|
|
82
|
+
expect(dialog).not.toBeVisible();
|
|
83
|
+
});
|
|
84
|
+
|
|
85
|
+
await step("Dialog focus order, close with cancel button", async () => {
|
|
86
|
+
await userEvent.click(openButton, { delay: 1000 });
|
|
87
|
+
const dialog = canvas.getByRole("dialog");
|
|
88
|
+
expect(dialog).toBeVisible();
|
|
89
|
+
expect(
|
|
90
|
+
canvas.getByRole("button", { name: /close dialog/i })
|
|
91
|
+
).toHaveFocus();
|
|
92
|
+
const cancelButton = canvas.getByRole("button", { name: /cancel/i });
|
|
93
|
+
await userEvent.tab();
|
|
94
|
+
expect(cancelButton).toHaveFocus();
|
|
95
|
+
await userEvent.keyboard(" ", { delay: 1000 });
|
|
96
|
+
expect(dialog).not.toBeVisible();
|
|
97
|
+
expect(openButton).toHaveFocus();
|
|
98
|
+
});
|
|
99
|
+
|
|
100
|
+
await step("Close Dialog with Escape Key", async () => {
|
|
101
|
+
expect(openButton).toHaveFocus();
|
|
102
|
+
await userEvent.click(openButton, { delay: 1000 });
|
|
103
|
+
await userEvent.tab();
|
|
104
|
+
expect(openButton).not.toHaveFocus();
|
|
105
|
+
|
|
106
|
+
const dialog = canvas.getByRole("dialog");
|
|
107
|
+
await userEvent.keyboard(" "); // Close the dialog with the keyboard
|
|
108
|
+
await waitFor(() => {
|
|
109
|
+
expect(dialog).not.toBeVisible();
|
|
110
|
+
});
|
|
111
|
+
});
|
|
112
|
+
},
|
|
113
|
+
} as Story;
|