@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
@@ -1,6 +1,6 @@
1
1
  details {
2
2
  --details-w: 100%;
3
- --details-h: fit-content;
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: 0.5rem;
11
+ --details-radius: var(--border-radius);
12
12
  --summary-cursor: pointer;
13
- --summary-transitions: all 0.75s linear;
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: var(--max-h-open);
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 '@storybook/react'
2
- import { within, expect, userEvent } from '@storybook/test'
1
+ import { StoryObj, Meta } from "@storybook/react";
2
+ import { within, expect, userEvent } from "@storybook/test";
3
3
 
4
-
5
- import Details from './details'
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: 'FP.REACT Components/Details',
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: '^on.*' },
40
+ actions: { argTypesRegex: "^on.*" },
42
41
  decorators: [
43
42
  (Story) => (
44
- <div className="container" style={{ minWidth: '50vw' }}>
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(canvas.getByRole('group', { name: /details dropdown/i })).toBeInTheDocument()
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('group')).toBeInTheDocument()
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
- <Details
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
- <Details
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
- <Details
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('Summary Section');
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('group', { name: /details dropdown/i });
136
- expect(detailsElement).toHaveAttribute('open');
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 '#components/ui'
2
- import React from 'react'
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<'details'> &
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 || 'Details dropdown'}
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 = 'Details'
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;