@ubie/vitals-ui-consumer 0.0.1

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 (170) hide show
  1. package/.storybook/main.ts +14 -0
  2. package/.storybook/preview.tsx +25 -0
  3. package/.storybook/vitest.setup.ts +7 -0
  4. package/dist/chunk-DKo7XVKm.mjs +33 -0
  5. package/dist/index.d.mts +1720 -0
  6. package/dist/index.d.mts.map +1 -0
  7. package/dist/index.mjs +10594 -0
  8. package/dist/index.mjs.map +1 -0
  9. package/dist/style.css +2299 -0
  10. package/package.json +47 -0
  11. package/src/components/Accordion/Accordion.module.css +75 -0
  12. package/src/components/Accordion/Accordion.spec.tsx +18 -0
  13. package/src/components/Accordion/Accordion.stories.tsx +61 -0
  14. package/src/components/Accordion/Accordion.tsx +89 -0
  15. package/src/components/ActionHalfModal/ActionHalfModal.module.css +180 -0
  16. package/src/components/ActionHalfModal/ActionHalfModal.spec.tsx +57 -0
  17. package/src/components/ActionHalfModal/ActionHalfModal.stories.tsx +469 -0
  18. package/src/components/ActionHalfModal/ActionHalfModal.tsx +269 -0
  19. package/src/components/ActionModal/ActionModal.module.css +145 -0
  20. package/src/components/ActionModal/ActionModal.spec.tsx +57 -0
  21. package/src/components/ActionModal/ActionModal.stories.tsx +302 -0
  22. package/src/components/ActionModal/ActionModal.tsx +232 -0
  23. package/src/components/Bold/Bold.module.css +4 -0
  24. package/src/components/Bold/Bold.spec.tsx +24 -0
  25. package/src/components/Bold/Bold.stories.tsx +54 -0
  26. package/src/components/Bold/Bold.tsx +31 -0
  27. package/src/components/Box/Box.module.css +46 -0
  28. package/src/components/Box/Box.spec.tsx +188 -0
  29. package/src/components/Box/Box.tsx +242 -0
  30. package/src/components/Button/Button.module.css +261 -0
  31. package/src/components/Button/Button.spec.tsx +82 -0
  32. package/src/components/Button/Button.tsx +99 -0
  33. package/src/components/Button/ButtonTypes.ts +107 -0
  34. package/src/components/Button/LinkButton.spec.tsx +86 -0
  35. package/src/components/Button/LinkButton.tsx +80 -0
  36. package/src/components/Button/VariantIcon.tsx +20 -0
  37. package/src/components/Button/useIcon.tsx +16 -0
  38. package/src/components/ButtonCard/ButtonCard.module.css +35 -0
  39. package/src/components/ButtonCard/ButtonCard.spec.tsx +18 -0
  40. package/src/components/ButtonCard/ButtonCard.stories.tsx +54 -0
  41. package/src/components/ButtonCard/ButtonCard.tsx +18 -0
  42. package/src/components/Center/Center.module.css +19 -0
  43. package/src/components/Center/Center.spec.tsx +143 -0
  44. package/src/components/Center/Center.tsx +108 -0
  45. package/src/components/Checkbox/Checkbox.module.css +124 -0
  46. package/src/components/Checkbox/Checkbox.spec.tsx +17 -0
  47. package/src/components/Checkbox/Checkbox.stories.tsx +213 -0
  48. package/src/components/Checkbox/Checkbox.tsx +50 -0
  49. package/src/components/CheckboxCard/CheckboxCard.module.css +102 -0
  50. package/src/components/CheckboxCard/CheckboxCard.spec.tsx +16 -0
  51. package/src/components/CheckboxCard/CheckboxCard.stories.tsx +205 -0
  52. package/src/components/CheckboxCard/CheckboxCard.tsx +53 -0
  53. package/src/components/CheckboxGroup/CheckboxGroup.module.css +16 -0
  54. package/src/components/CheckboxGroup/CheckboxGroup.spec.tsx +17 -0
  55. package/src/components/CheckboxGroup/CheckboxGroup.tsx +64 -0
  56. package/src/components/Color/Color.module.css +3 -0
  57. package/src/components/Color/Color.spec.tsx +24 -0
  58. package/src/components/Color/Color.stories.tsx +71 -0
  59. package/src/components/Color/Color.tsx +28 -0
  60. package/src/components/Divider/Divider.module.css +9 -0
  61. package/src/components/Divider/Divider.spec.tsx +42 -0
  62. package/src/components/Divider/Divider.stories.tsx +77 -0
  63. package/src/components/Divider/Divider.tsx +49 -0
  64. package/src/components/ErrorMessage/ErrorMessage.module.css +8 -0
  65. package/src/components/ErrorMessage/ErrorMessage.spec.tsx +12 -0
  66. package/src/components/ErrorMessage/ErrorMessage.tsx +20 -0
  67. package/src/components/Flex/Flex.module.css +24 -0
  68. package/src/components/Flex/Flex.spec.tsx +188 -0
  69. package/src/components/Flex/Flex.tsx +173 -0
  70. package/src/components/FlexItem/FlexItem.module.css +14 -0
  71. package/src/components/FlexItem/FlexItem.spec.tsx +84 -0
  72. package/src/components/FlexItem/FlexItem.tsx +106 -0
  73. package/src/components/Heading/Heading.module.css +131 -0
  74. package/src/components/Heading/Heading.tsx +86 -0
  75. package/src/components/HelperMessage/HelperMessage.module.css +8 -0
  76. package/src/components/HelperMessage/HelperMessage.tsx +15 -0
  77. package/src/components/Icon/Icon.module.css +6 -0
  78. package/src/components/Icon/Icon.spec.tsx +24 -0
  79. package/src/components/Icon/Icon.stories.tsx +100 -0
  80. package/src/components/Icon/Icon.tsx +101 -0
  81. package/src/components/Input/Input.module.css +51 -0
  82. package/src/components/Input/Input.spec.tsx +14 -0
  83. package/src/components/Input/Input.tsx +27 -0
  84. package/src/components/Label/Label.module.css +14 -0
  85. package/src/components/Label/Label.tsx +39 -0
  86. package/src/components/LinkCard/LinkCard.module.css +72 -0
  87. package/src/components/LinkCard/LinkCard.tsx +96 -0
  88. package/src/components/MessageHalfModal/MessageHalfModal.module.css +181 -0
  89. package/src/components/MessageHalfModal/MessageHalfModal.spec.tsx +73 -0
  90. package/src/components/MessageHalfModal/MessageHalfModal.stories.tsx +242 -0
  91. package/src/components/MessageHalfModal/MessageHalfModal.tsx +194 -0
  92. package/src/components/MessageModal/MessageModal.module.css +149 -0
  93. package/src/components/MessageModal/MessageModal.spec.tsx +57 -0
  94. package/src/components/MessageModal/MessageModal.stories.tsx +223 -0
  95. package/src/components/MessageModal/MessageModal.tsx +178 -0
  96. package/src/components/Pre/Pre.module.css +8 -0
  97. package/src/components/Pre/Pre.spec.tsx +11 -0
  98. package/src/components/Pre/Pre.stories.tsx +76 -0
  99. package/src/components/Pre/Pre.tsx +40 -0
  100. package/src/components/RadioButton/RadioButton.module.css +92 -0
  101. package/src/components/RadioButton/RadioButton.spec.tsx +25 -0
  102. package/src/components/RadioButton/RadioButton.tsx +55 -0
  103. package/src/components/RadioCard/RadioCard.module.css +109 -0
  104. package/src/components/RadioCard/RadioCard.tsx +61 -0
  105. package/src/components/RadioGroup/RadioGroup.module.css +16 -0
  106. package/src/components/RadioGroup/RadioGroup.spec.tsx +17 -0
  107. package/src/components/RadioGroup/RadioGroup.tsx +60 -0
  108. package/src/components/Select/Select.module.css +70 -0
  109. package/src/components/Select/Select.spec.tsx +12 -0
  110. package/src/components/Select/Select.tsx +56 -0
  111. package/src/components/Stack/Stack.module.css +10 -0
  112. package/src/components/Stack/Stack.spec.tsx +177 -0
  113. package/src/components/Stack/Stack.tsx +151 -0
  114. package/src/components/Stepper/Stepper.module.css +137 -0
  115. package/src/components/Stepper/Stepper.spec.tsx +198 -0
  116. package/src/components/Stepper/Stepper.stories.tsx +192 -0
  117. package/src/components/Stepper/Stepper.tsx +70 -0
  118. package/src/components/Stepper/StepperItem.tsx +113 -0
  119. package/src/components/Text/Text.module.css +168 -0
  120. package/src/components/Text/Text.tsx +192 -0
  121. package/src/components/TextArea/TextArea.module.css +46 -0
  122. package/src/components/TextArea/TextArea.spec.tsx +13 -0
  123. package/src/components/TextArea/TextArea.tsx +29 -0
  124. package/src/components/Toggle/Toggle.module.css +71 -0
  125. package/src/components/Toggle/Toggle.spec.tsx +21 -0
  126. package/src/components/Toggle/Toggle.tsx +56 -0
  127. package/src/font.ts +2 -0
  128. package/src/hooks/useScrollable.ts +58 -0
  129. package/src/icons/AppleIcon.tsx +14 -0
  130. package/src/icons/GoogleIcon.tsx +27 -0
  131. package/src/icons/LINEIcon.tsx +16 -0
  132. package/src/index.ts +35 -0
  133. package/src/sharedComponents/RequiredLabel/RequiredLabel.module.css +10 -0
  134. package/src/sharedComponents/RequiredLabel/RequiredLabel.tsx +8 -0
  135. package/src/sharedComponents/VisuallyHidden/VisuallyHidden.module.css +15 -0
  136. package/src/sharedComponents/VisuallyHidden/VisuallyHidden.tsx +22 -0
  137. package/src/stories/Accordion.stories.portable.ts +4 -0
  138. package/src/stories/Box.stories.tsx +474 -0
  139. package/src/stories/Button.stories.tsx +262 -0
  140. package/src/stories/Center.stories.tsx +126 -0
  141. package/src/stories/ErrorMessage.stories.tsx +19 -0
  142. package/src/stories/Flex.stories.tsx +345 -0
  143. package/src/stories/Form.stories.tsx +83 -0
  144. package/src/stories/Heading.stories.tsx +263 -0
  145. package/src/stories/HelperMessage.stories.tsx +22 -0
  146. package/src/stories/Input.stories.tsx +145 -0
  147. package/src/stories/Label.stories.tsx +32 -0
  148. package/src/stories/LinkButton.stories.tsx +207 -0
  149. package/src/stories/LinkCard.stories.tsx +90 -0
  150. package/src/stories/RadioButton.stories.tsx +168 -0
  151. package/src/stories/RadioCard.stories.tsx +236 -0
  152. package/src/stories/Select.stories.tsx +97 -0
  153. package/src/stories/Stack.stories.tsx +167 -0
  154. package/src/stories/Text.stories.tsx +396 -0
  155. package/src/stories/TextArea.stories.tsx +49 -0
  156. package/src/stories/Toggle.stories.tsx +30 -0
  157. package/src/test/vitest-jest-dom.d.ts +12 -0
  158. package/src/types/attributes.ts +6 -0
  159. package/src/types/global.d.ts +11 -0
  160. package/src/types/icon.ts +3 -0
  161. package/src/types/style.ts +254 -0
  162. package/src/utils/component.ts +8 -0
  163. package/src/utils/style.spec.ts +57 -0
  164. package/src/utils/style.ts +387 -0
  165. package/src/utils/types.ts +8 -0
  166. package/tsconfig.json +18 -0
  167. package/tsconfig.spec-lint.tsbuildinfo +1 -0
  168. package/tsconfig.tsbuildinfo +1 -0
  169. package/vite.config.ts +50 -0
  170. package/vitest.shims.d.ts +1 -0
@@ -0,0 +1,194 @@
1
+ "use client";
2
+
3
+ import { Dialog, DialogPanel, DialogTitle, Transition, TransitionChild } from "@headlessui/react";
4
+ import { clsx } from "clsx";
5
+ import { FC, Fragment, PropsWithChildren, ReactNode, useCallback, useRef } from "react";
6
+ import styles from "./MessageHalfModal.module.css";
7
+ import { useScrollable } from "../../hooks/useScrollable";
8
+ import { VisuallyHidden } from "../../sharedComponents/VisuallyHidden/VisuallyHidden";
9
+ import { CustomDataAttributeProps } from "../../types/attributes";
10
+ import { opacityToClassName } from "../../utils/style";
11
+ import { Button } from "../Button/Button";
12
+
13
+ type Opacity = "normal" | "darker";
14
+
15
+ type BaseProps = {
16
+ /**
17
+ * 閉じるアクションが実行された場合のコールバック
18
+ */
19
+ onClose: () => void;
20
+ /**
21
+ * ヘッダーに表示する見出しテキスト
22
+ */
23
+ header?: string;
24
+ /**
25
+ * 閉じるボタンのラベル
26
+ * @default 閉じる
27
+ */
28
+ closeLabel?: string;
29
+ /**
30
+ * オーバーレイの透過度
31
+ * @default normal
32
+ */
33
+ overlayOpacity?: Opacity;
34
+ /**
35
+ * 閉じるボタンを表示するかどうか
36
+ * @default true
37
+ */
38
+ showClose?: boolean;
39
+ /**
40
+ * モーダルを開くかどうか
41
+ * @default true
42
+ */
43
+ open?: boolean;
44
+ /**
45
+ * openを無視してモーダルを開いたままにするか。アニメーションライブラリとの連携で、ActionHalfModal自身が開閉に関与しない場合に使用
46
+ */
47
+ isStatic?: boolean;
48
+ /**
49
+ * モーダルをフルスクリーンで表示する
50
+ */
51
+ fullscreen?: boolean;
52
+ /**
53
+ * ネイティブ要素のid属性。ページで固有のIDを指定
54
+ */
55
+ id?: string;
56
+ /**
57
+ * ネイティブのaria-labelledby属性。独自の見出しを実装する場合にダイアログとの紐づけに使用。ページで固有のIDを指定
58
+ */
59
+ ariaLabelledby?: string;
60
+ /**
61
+ * ヒーローエリア(見出しの更に上)に配置するコンテンツ
62
+ */
63
+ hero?: ReactNode;
64
+ /**
65
+ * ヘッダーを固定表示
66
+ * heroが指定されている場合は無効
67
+ */
68
+ stickyHeader?: boolean;
69
+ /**
70
+ * フッターを固定表示
71
+ */
72
+ stickyFooter?: boolean;
73
+ } & CustomDataAttributeProps;
74
+
75
+ type Props = BaseProps;
76
+
77
+ export const MessageHalfModal: FC<PropsWithChildren<Props>> = ({
78
+ children,
79
+ onClose,
80
+ header,
81
+ closeLabel = "閉じる",
82
+ overlayOpacity = "normal",
83
+ showClose = true,
84
+ open = true,
85
+ isStatic = false,
86
+ fullscreen = false,
87
+ ariaLabelledby,
88
+ hero,
89
+ stickyHeader = false,
90
+ stickyFooter = false,
91
+ ...otherProps
92
+ }) => {
93
+ const opacityClassName = opacityToClassName(overlayOpacity);
94
+
95
+ const initialFocusRef = useRef(null);
96
+
97
+ const dialogRef = useCallback(
98
+ (node: HTMLDivElement | null) => {
99
+ if (node !== null && header == null && ariaLabelledby != null) {
100
+ node.setAttribute("aria-labelledby", ariaLabelledby);
101
+ } else if (node !== null && header == null && ariaLabelledby == null) {
102
+ node.removeAttribute("aria-labelledby");
103
+ }
104
+ },
105
+ [ariaLabelledby, header],
106
+ );
107
+
108
+ const { scrollContainerRef, canScrollUp, canScrollDown } = useScrollable();
109
+
110
+ return (
111
+ <Transition show={open}>
112
+ <Dialog
113
+ ref={dialogRef}
114
+ static={isStatic}
115
+ onClose={onClose}
116
+ className={clsx(styles.modal, fullscreen && styles.fullscreen)}
117
+ initialFocus={initialFocusRef}
118
+ {...otherProps}
119
+ >
120
+ <TransitionChild
121
+ as={Fragment}
122
+ enter={styles.overlayEnter}
123
+ enterFrom={styles.overlayEnterFrom}
124
+ enterTo={styles.overlayEnterTo}
125
+ leave={styles.overlayLeave}
126
+ leaveFrom={styles.overlayLeaveFrom}
127
+ leaveTo={styles.overlayLeaveTo}
128
+ >
129
+ <div className={clsx(styles.overlay, styles[opacityClassName])} />
130
+ </TransitionChild>
131
+ <TransitionChild
132
+ as={Fragment}
133
+ enter={styles.panelEnter}
134
+ enterFrom={styles.panelEnterFrom}
135
+ enterTo={styles.panelEnterTo}
136
+ leave={styles.panelLeave}
137
+ leaveFrom={styles.panelLeaveFrom}
138
+ leaveTo={styles.panelLeaveTo}
139
+ >
140
+ <DialogPanel
141
+ className={clsx(styles.dialog, {
142
+ [styles.fullscreen]: fullscreen,
143
+ })}
144
+ >
145
+ {header === undefined ? (
146
+ <VisuallyHidden as="p" tabIndex={-1} ref={initialFocusRef}>
147
+ ダイアログ
148
+ </VisuallyHidden>
149
+ ) : null}
150
+ <div className={styles.scrollContainer} ref={scrollContainerRef}>
151
+ <div
152
+ className={clsx(styles.mainContent, {
153
+ [styles.headerLess]: header === undefined && hero === undefined,
154
+ [styles.fullscreen]: fullscreen,
155
+ })}
156
+ >
157
+ {hero !== undefined ? <div className={styles.hero}>{hero}</div> : null}
158
+ {header !== undefined ? (
159
+ <DialogTitle
160
+ tabIndex={-1}
161
+ ref={initialFocusRef}
162
+ className={clsx(
163
+ styles.header,
164
+ !hero && stickyHeader && styles.sticky,
165
+ canScrollUp && styles.canScroll,
166
+ )}
167
+ >
168
+ {header}
169
+ </DialogTitle>
170
+ ) : null}
171
+ <div className={clsx(styles.body, { [styles.fullscreen]: fullscreen })}>
172
+ {children}
173
+ </div>
174
+ <div
175
+ className={clsx(
176
+ styles.buttonContainer,
177
+ showClose && stickyFooter && styles.sticky,
178
+ showClose && canScrollDown && styles.canScroll,
179
+ )}
180
+ >
181
+ {showClose && (
182
+ <Button variant="primary" onClick={onClose} aria-label={closeLabel}>
183
+ {closeLabel}
184
+ </Button>
185
+ )}
186
+ </div>
187
+ </div>
188
+ </div>
189
+ </DialogPanel>
190
+ </TransitionChild>
191
+ </Dialog>
192
+ </Transition>
193
+ );
194
+ };
@@ -0,0 +1,149 @@
1
+ .modal {
2
+ position: fixed;
3
+ inset: 0;
4
+ z-index: var(--z-index-modal);
5
+ display: flex;
6
+ align-items: center;
7
+ justify-content: center;
8
+ }
9
+
10
+ .overlay {
11
+ position: fixed;
12
+ inset: 0;
13
+ }
14
+
15
+ .normalOverlay {
16
+ background: rgb(0 0 0 / 50%);
17
+ }
18
+
19
+ .darkerOverlay {
20
+ background: rgb(0 0 0 / 80%);
21
+ }
22
+
23
+ .dialog {
24
+ position: relative;
25
+ box-sizing: border-box;
26
+ display: flex;
27
+ flex-direction: column;
28
+ width: calc(100% - 32px);
29
+ max-width: 600px;
30
+ max-height: calc(100% - 48px);
31
+ margin: 0 auto;
32
+ overflow: hidden;
33
+ background: #fff;
34
+ border-radius: var(--radius-lg);
35
+ }
36
+
37
+ .dialog.fixedHeight {
38
+ height: calc(100% - 48px);
39
+ }
40
+
41
+ .scrollContainer {
42
+ height: 100%;
43
+ overflow-y: auto;
44
+ }
45
+
46
+ .mainContent {
47
+ box-sizing: border-box;
48
+ display: flex;
49
+ flex-direction: column;
50
+ }
51
+
52
+ .mainContent.fixedHeight {
53
+ min-height: 100%;
54
+ }
55
+
56
+ .mainContent.headerLess {
57
+ padding-top: var(--size-spacing-xl);
58
+ }
59
+
60
+ .header {
61
+ padding: var(--size-spacing-lg) var(--size-spacing-md);
62
+ font-size: var(--text-heading-xs-size);
63
+ font-weight: bold;
64
+ line-height: var(--text-heading-xs-line);
65
+ text-align: center;
66
+ white-space: pre-wrap;
67
+ background: var(--color-ubie-white);
68
+
69
+ /* May receive focus in the initial display. */
70
+ outline: none;
71
+ }
72
+
73
+ .header.sticky {
74
+ position: sticky;
75
+ top: 0;
76
+
77
+ /* Ensure sticky header appears above content that might overlap it (e.g., with position or transform) */
78
+ z-index: 1;
79
+ }
80
+
81
+ .header.sticky.canScroll {
82
+ border-bottom: 1px solid var(--color-outline);
83
+ }
84
+
85
+ .body {
86
+ /*
87
+ * Ensure the header always appears in front of body content.
88
+ * - Place header and body in the same stacking context
89
+ * - Set body's z-index lower than header's z-index
90
+ * This prevents the header from being hidden by body content,
91
+ * regardless of how high z-index values are specified within the body.
92
+ */
93
+ z-index: 0;
94
+ padding-right: var(--size-spacing-md);
95
+ padding-left: var(--size-spacing-md);
96
+ text-align: center;
97
+ }
98
+
99
+ .body.fixedHeight {
100
+ flex-grow: 1;
101
+ min-height: 400px;
102
+ overflow: hidden;
103
+ }
104
+
105
+ .hero {
106
+ flex: 0 0 auto;
107
+ overflow: hidden;
108
+ }
109
+
110
+ .footer {
111
+ padding: var(--size-spacing-lg) var(--size-spacing-md) var(--size-spacing-md);
112
+ background-color: var(--color-ubie-white);
113
+ }
114
+
115
+ .footer.sticky {
116
+ position: sticky;
117
+ bottom: 0;
118
+ }
119
+
120
+ .footer.sticky.canScroll {
121
+ border-top: 1px solid var(--color-outline);
122
+ }
123
+
124
+ .panelEnter {
125
+ transition-timing-function: ease-out;
126
+ transition-duration: 250ms;
127
+ transition-property: opacity;
128
+ }
129
+
130
+ .panelEnterFrom {
131
+ opacity: 0;
132
+ }
133
+
134
+ .panelEnterTo {
135
+ opacity: 1;
136
+ }
137
+
138
+ .panelLeave {
139
+ transition-timing-function: ease-in;
140
+ transition-duration: 200ms;
141
+ }
142
+
143
+ .panelLeaveFrom {
144
+ opacity: 1;
145
+ }
146
+
147
+ .panelLeaveTo {
148
+ opacity: 0;
149
+ }
@@ -0,0 +1,57 @@
1
+ import { composeStory } from "@storybook/react-vite";
2
+ import { render, screen } from "@testing-library/react";
3
+ import { userEvent } from "@testing-library/user-event";
4
+ import Meta, { WithId, CustomHeader, Default } from "./MessageModal.stories";
5
+
6
+ const WithIdStory = composeStory(WithId, Meta);
7
+ const CustomHeaderStory = composeStory(CustomHeader, Meta);
8
+ const DefaultStory = composeStory(Default, Meta);
9
+
10
+ const user = userEvent.setup();
11
+
12
+ describe("MessageModal", () => {
13
+ test("Add id", async () => {
14
+ render(<WithIdStory />);
15
+
16
+ await user.click(await screen.findByRole("button"));
17
+
18
+ const dialogElement = await screen.findByRole("dialog");
19
+
20
+ expect(dialogElement).toHaveAttribute("id", "dialog-id");
21
+ });
22
+
23
+ test("Custom heading and dialogue can be tied together", async () => {
24
+ render(<CustomHeaderStory />);
25
+
26
+ await user.click(await screen.findByRole("button"));
27
+
28
+ const dialogElement = await screen.findByRole("dialog");
29
+ const dialogHeadingElement = await screen.findByRole("heading");
30
+
31
+ expect(dialogElement).toHaveAttribute("aria-labelledby", "header-id");
32
+ expect(dialogHeadingElement).toHaveAttribute("id", "header-id");
33
+ });
34
+
35
+ test("If no header prop is present, text is inserted that serves as the default heading", async () => {
36
+ render(<CustomHeaderStory />);
37
+
38
+ await user.click(await screen.findByRole("button"));
39
+
40
+ const dialogHeadingElement = await screen.findByText("ダイアログ");
41
+
42
+ expect(document.activeElement).toEqual(dialogHeadingElement);
43
+ });
44
+
45
+ test("header prop can be used to automatically link to a dialog", async () => {
46
+ render(<DefaultStory />);
47
+
48
+ const dialogElement = await screen.findByRole("dialog");
49
+ const dialogHeadingElement = await screen.findByRole("heading");
50
+
51
+ expect(dialogElement).toHaveAttribute("aria-labelledby");
52
+ expect(dialogHeadingElement).toHaveAttribute(
53
+ "id",
54
+ dialogElement.getAttribute("aria-labelledby"),
55
+ );
56
+ });
57
+ });
@@ -0,0 +1,223 @@
1
+ import { Meta, StoryObj } from "@storybook/react-vite";
2
+ import { ComponentProps, useState } from "react";
3
+ import { MessageModal } from "./MessageModal";
4
+
5
+ const meta: Meta<typeof MessageModal> = {
6
+ title: "Modal/MessageModal",
7
+ component: MessageModal,
8
+ argTypes: {
9
+ overlayOpacity: {
10
+ options: ["normal", "darker"],
11
+ control: { type: "radio" },
12
+ },
13
+ },
14
+ };
15
+
16
+ const LongBody = () => (
17
+ <>
18
+ <p>
19
+ Lorem Ipsum is simply dummy text of the printing and typesetting industry. Lorem Ipsum has
20
+ been the industry&apos;s standard dummy text ever since the 1500s, when an unknown printer
21
+ took a galley of type and scrambled it to make a type specimen book. It has survived not only
22
+ five centuries, but also the leap into electronic typesetting, remaining essentially
23
+ unchanged. It was popularised in the 1960s with the release of Letraset sheets containing
24
+ Lorem Ipsum passages, and more recently with desktop publishing software like Aldus PageMaker
25
+ including versions of Lorem Ipsum.
26
+ </p>
27
+ <p>
28
+ Contrary to popular belief, Lorem Ipsum is not simply random text. It has roots in a piece of
29
+ classical Latin literature from 45 BC, making it over 2000 years old. Richard McClintock, a
30
+ Latin professor at Hampden-Sydney College in Virginia, looked up one of the more obscure Latin
31
+ words, consectetur, from a Lorem Ipsum passage, and going through the cites of the word in
32
+ classical literature, discovered the undoubtable source. Lorem Ipsum comes from sections
33
+ 1.10.32 and 1.10.33 of &quot;de Finibus Bonorum et Malorum&quot; (The Extremes of Good and
34
+ Evil) by Cicero, written in 45 BC. This book is a treatise on the theory of ethics, very
35
+ popular during the Renaissance. The first line of Lorem Ipsum, &quot;Lorem ipsum dolor sit
36
+ amet..&quot;, comes from a line in section 1.10.32.
37
+ </p>
38
+ </>
39
+ );
40
+
41
+ export default meta;
42
+
43
+ type Story = StoryObj<typeof MessageModal>;
44
+
45
+ const defaultArgs: Partial<ComponentProps<typeof MessageModal>> = {
46
+ header: "メッセージ",
47
+ overlayOpacity: "normal",
48
+ isStatic: false,
49
+ fixedHeight: false,
50
+ children: <LongBody />,
51
+ stickyHeader: false,
52
+ stickyFooter: false,
53
+ } as const satisfies Partial<ComponentProps<typeof MessageModal>>;
54
+
55
+ export const Default: Story = {
56
+ render: (args) => {
57
+ const [open, setOpen] = useState(true);
58
+
59
+ return (
60
+ <>
61
+ <button type="button" onClick={() => setOpen(true)}>
62
+ Open Modal
63
+ </button>
64
+ <MessageModal {...args} open={open} onClose={() => setOpen(false)} />
65
+ </>
66
+ );
67
+ },
68
+ args: {
69
+ ...defaultArgs,
70
+ header: "メッセージ",
71
+ },
72
+ };
73
+
74
+ export const FixedHeight: Story = {
75
+ render: (args) => {
76
+ const [open, setOpen] = useState(false);
77
+
78
+ return (
79
+ <>
80
+ <button type="button" onClick={() => setOpen(true)}>
81
+ Open Modal
82
+ </button>
83
+ <MessageModal {...args} open={open} onClose={() => setOpen(false)} />
84
+ </>
85
+ );
86
+ },
87
+ args: { ...defaultArgs, fixedHeight: true },
88
+ };
89
+
90
+ export const OverlayDarker: Story = {
91
+ render: (args) => {
92
+ const [open, setOpen] = useState(false);
93
+
94
+ return (
95
+ <>
96
+ <button type="button" onClick={() => setOpen(true)}>
97
+ Open Modal
98
+ </button>
99
+ <MessageModal {...args} open={open} onClose={() => setOpen(false)} fixedHeight />
100
+ </>
101
+ );
102
+ },
103
+ args: {
104
+ ...defaultArgs,
105
+ overlayOpacity: "darker",
106
+ },
107
+ };
108
+
109
+ export const CustomDataAttribute: Story = {
110
+ render: (args) => {
111
+ const [open, setOpen] = useState(false);
112
+
113
+ return (
114
+ <>
115
+ <button type="button" onClick={() => setOpen(true)}>
116
+ Open Modal
117
+ </button>
118
+ <MessageModal {...args} open={open} onClose={() => setOpen(false)} />
119
+ </>
120
+ );
121
+ },
122
+ args: {
123
+ ...defaultArgs,
124
+ "data-test-id": "message-modal-custom",
125
+ },
126
+ };
127
+
128
+ export const WithId: Story = {
129
+ render: (args) => {
130
+ const [open, setOpen] = useState(false);
131
+
132
+ return (
133
+ <>
134
+ <button type="button" onClick={() => setOpen(true)}>
135
+ Open Modal
136
+ </button>
137
+ <MessageModal {...args} open={open} onClose={() => setOpen(false)} />
138
+ </>
139
+ );
140
+ },
141
+ args: {
142
+ ...defaultArgs,
143
+ id: "dialog-id",
144
+ },
145
+ };
146
+
147
+ export const CustomHeader: Story = {
148
+ render: (args) => {
149
+ const [open, setOpen] = useState(false);
150
+
151
+ const headerId = "header-id";
152
+
153
+ return (
154
+ <>
155
+ <button type="button" onClick={() => setOpen(true)}>
156
+ Open Modal
157
+ </button>
158
+ <MessageModal
159
+ ariaLabelledby={headerId}
160
+ {...args}
161
+ open={open}
162
+ onClose={() => setOpen(false)}
163
+ >
164
+ <h2 id={headerId}>Heading</h2>
165
+ <LongBody />
166
+ </MessageModal>
167
+ </>
168
+ );
169
+ },
170
+ args: {
171
+ ...defaultArgs,
172
+ header: undefined,
173
+ },
174
+ };
175
+
176
+ export const WithHero: Story = {
177
+ render: (args) => {
178
+ const [open, setOpen] = useState(false);
179
+
180
+ return (
181
+ <>
182
+ <button type="button" onClick={() => setOpen(true)}>
183
+ Open Modal
184
+ </button>
185
+ <MessageModal {...args} open={open} onClose={() => setOpen(false)} />
186
+ </>
187
+ );
188
+ },
189
+ args: {
190
+ ...defaultArgs,
191
+ header: "メッセージ",
192
+ hero: (
193
+ <img
194
+ src="/images/placeholder.svg"
195
+ alt="Illustration: Modal"
196
+ style={{ width: "100%", height: "auto", verticalAlign: "bottom" }}
197
+ width={560}
198
+ height={315}
199
+ />
200
+ ),
201
+ },
202
+ };
203
+
204
+ export const StickyHeaderAndFooter: Story = {
205
+ render: (args) => {
206
+ const [open, setOpen] = useState(true);
207
+
208
+ return (
209
+ <>
210
+ <button type="button" onClick={() => setOpen(true)}>
211
+ Open Modal
212
+ </button>
213
+ <MessageModal {...args} open={open} onClose={() => setOpen(false)} />
214
+ </>
215
+ );
216
+ },
217
+ args: {
218
+ ...defaultArgs,
219
+ header: "メッセージ",
220
+ stickyHeader: true,
221
+ stickyFooter: true,
222
+ },
223
+ };