@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,198 @@
1
+ import { render, screen } from "@testing-library/react";
2
+ import { Stepper } from "./Stepper";
3
+ import { StepperItem } from "./StepperItem";
4
+
5
+ describe("<Stepper>", () => {
6
+ it("renders steps correctly", () => {
7
+ render(
8
+ <Stepper
9
+ data-testid="stepper"
10
+ steps={[{ label: "Step 1" }, { label: "Step 2" }, { label: "Step 3" }]}
11
+ />,
12
+ );
13
+
14
+ expect(screen.getByTestId("stepper")).toBeInTheDocument();
15
+ expect(screen.getByText("Step 1")).toBeInTheDocument();
16
+ expect(screen.getByText("Step 2")).toBeInTheDocument();
17
+ expect(screen.getByText("Step 3")).toBeInTheDocument();
18
+ });
19
+
20
+ it("applies correct status based on currentStep", () => {
21
+ render(
22
+ <Stepper
23
+ currentStep={1}
24
+ steps={[{ label: "Step 1" }, { label: "Step 2" }, { label: "Step 3" }]}
25
+ />,
26
+ );
27
+
28
+ const step0 = screen.getByText("Step 1").closest('[class*="stepperItem"]');
29
+ const step1 = screen.getByText("Step 2").closest('[class*="stepperItem"]');
30
+ const step2 = screen.getByText("Step 3").closest('[class*="stepperItem"]');
31
+
32
+ // Step 0 should be done (index < currentStep)
33
+ expect(step0?.className).toMatch(/done/);
34
+ // Step 1 should be current (index === currentStep)
35
+ expect(step1?.className).toMatch(/current/);
36
+ // Step 2 should be undone (index > currentStep)
37
+ expect(step2?.className).toMatch(/undone/);
38
+ });
39
+
40
+ it("sets first and last child properties correctly", () => {
41
+ render(<Stepper steps={[{ label: "First" }, { label: "Middle" }, { label: "Last" }]} />);
42
+
43
+ const first = screen.getByText("First").closest('[class*="stepperItem"]');
44
+ const middle = screen.getByText("Middle").closest('[class*="stepperItem"]');
45
+ const last = screen.getByText("Last").closest('[class*="stepperItem"]');
46
+
47
+ // First item should hide left border line
48
+ const firstLeftBorderLine = first?.querySelector('[class*="leftBorder"] [class*="border"]');
49
+ const firstRightBorderLine = first?.querySelector('[class*="rightBorder"] [class*="border"]');
50
+ expect(firstLeftBorderLine?.className).toMatch(/hidden/);
51
+ expect(firstRightBorderLine?.className).not.toMatch(/hidden/);
52
+
53
+ // Middle item should show both border lines
54
+ const middleLeftBorderLine = middle?.querySelector('[class*="leftBorder"] [class*="border"]');
55
+ const middleRightBorderLine = middle?.querySelector('[class*="rightBorder"] [class*="border"]');
56
+ expect(middleLeftBorderLine?.className).not.toMatch(/hidden/);
57
+ expect(middleRightBorderLine?.className).not.toMatch(/hidden/);
58
+
59
+ // Last item should hide right border line
60
+ const lastLeftBorderLine = last?.querySelector('[class*="leftBorder"] [class*="border"]');
61
+ const lastRightBorderLine = last?.querySelector('[class*="rightBorder"] [class*="border"]');
62
+ expect(lastLeftBorderLine?.className).not.toMatch(/hidden/);
63
+ expect(lastRightBorderLine?.className).toMatch(/hidden/);
64
+ });
65
+
66
+ it("has all margins through m prop", () => {
67
+ render(
68
+ <Stepper m="xxs" data-testid="stepper" steps={[{ label: "Step 1" }, { label: "Step 2" }]} />,
69
+ );
70
+ const stepper = screen.getByTestId("stepper");
71
+
72
+ expect(stepper).toHaveStyle("--margin-top: var(--size-spacing-xxs)");
73
+ expect(stepper).toHaveStyle("--margin-right: var(--size-spacing-xxs)");
74
+ expect(stepper).toHaveStyle("--margin-bottom: var(--size-spacing-xxs)");
75
+ expect(stepper).toHaveStyle("--margin-left: var(--size-spacing-xxs)");
76
+ });
77
+
78
+ it("defaults to currentStep 0 when not specified", () => {
79
+ render(<Stepper steps={[{ label: "Step 1" }, { label: "Step 2" }]} />);
80
+
81
+ const step0 = screen.getByText("Step 1").closest('[class*="stepperItem"]');
82
+ const step1 = screen.getByText("Step 2").closest('[class*="stepperItem"]');
83
+
84
+ expect(step0?.className).toMatch(/current/);
85
+ expect(step1?.className).toMatch(/undone/);
86
+ });
87
+
88
+ it("applies correct border line colors based on step position", () => {
89
+ render(
90
+ <Stepper
91
+ currentStep={1}
92
+ steps={[{ label: "Step 1" }, { label: "Step 2" }, { label: "Step 3" }]}
93
+ />,
94
+ );
95
+
96
+ const step0 = screen.getByText("Step 1").closest('[class*="stepperItem"]');
97
+ const step1 = screen.getByText("Step 2").closest('[class*="stepperItem"]');
98
+ const step2 = screen.getByText("Step 3").closest('[class*="stepperItem"]');
99
+
100
+ // Step 0 (done): both borders should be blue (left and right of current step)
101
+ const step0RightBorder = step0?.querySelector('[class*="rightBorder"] [class*="border"]');
102
+ expect(step0RightBorder?.className).toMatch(/borderColorBlue/);
103
+
104
+ // Step 1 (current): left border blue, right border gray
105
+ const step1LeftBorder = step1?.querySelector('[class*="leftBorder"] [class*="border"]');
106
+ const step1RightBorder = step1?.querySelector('[class*="rightBorder"] [class*="border"]');
107
+ expect(step1LeftBorder?.className).toMatch(/borderColorBlue/);
108
+ expect(step1RightBorder?.className).toMatch(/borderColorGray/);
109
+
110
+ // Step 2 (undone): left border gray
111
+ const step2LeftBorder = step2?.querySelector('[class*="leftBorder"] [class*="border"]');
112
+ expect(step2LeftBorder?.className).toMatch(/borderColorGray/);
113
+ });
114
+
115
+ it("applies gray border color for lines to the right of current step", () => {
116
+ render(<Stepper currentStep={0} steps={[{ label: "Step 1" }, { label: "Step 2" }]} />);
117
+
118
+ const step0 = screen.getByText("Step 1").closest('[class*="stepperItem"]');
119
+ const step1 = screen.getByText("Step 2").closest('[class*="stepperItem"]');
120
+
121
+ // Step 0 (current): right border should be gray
122
+ const step0RightBorder = step0?.querySelector('[class*="rightBorder"] [class*="border"]');
123
+ expect(step0RightBorder?.className).toMatch(/borderColorGray/);
124
+
125
+ // Step 1 (undone): left border should be gray
126
+ const step1LeftBorder = step1?.querySelector('[class*="leftBorder"] [class*="border"]');
127
+ expect(step1LeftBorder?.className).toMatch(/borderColorGray/);
128
+ });
129
+ });
130
+
131
+ describe("<StepperItem>", () => {
132
+ it("renders label correctly", () => {
133
+ render(<StepperItem label="Test Step" />);
134
+ expect(screen.getByText("Test Step")).toBeInTheDocument();
135
+ });
136
+
137
+ it("applies correct CSS classes based on status", () => {
138
+ const { rerender } = render(<StepperItem label="Test" status="current" data-testid="item" />);
139
+
140
+ expect(screen.getByTestId("item").className).toMatch(/current/);
141
+
142
+ rerender(<StepperItem label="Test" status="done" data-testid="item" />);
143
+ expect(screen.getByTestId("item").className).toMatch(/done/);
144
+
145
+ rerender(<StepperItem label="Test" status="undone" data-testid="item" />);
146
+ expect(screen.getByTestId("item").className).toMatch(/undone/);
147
+ });
148
+
149
+ it("renders custom icons when provided in steps", () => {
150
+ render(
151
+ <Stepper
152
+ currentStep={0}
153
+ steps={[{ label: "Home", icon: "HomeOutlineIcon" }, { label: "Settings" }]}
154
+ />,
155
+ );
156
+
157
+ // Icon component should be rendered for the first step
158
+ const step0 = screen.getByText("Home").closest('[class*="stepperItem"]');
159
+ const iconWrapper = step0?.querySelector('[class*="iconWrapper"]');
160
+ expect(iconWrapper).toBeInTheDocument();
161
+ });
162
+
163
+ it("uses custom done icon when step is completed", () => {
164
+ render(
165
+ <Stepper
166
+ currentStep={1}
167
+ steps={[{ label: "Done Step", doneIcon: "CheckAIcon" }, { label: "Current Step" }]}
168
+ />,
169
+ );
170
+
171
+ const step0 = screen.getByText("Done Step").closest('[class*="stepperItem"]');
172
+ const iconWrapper = step0?.querySelector('[class*="iconWrapper"]');
173
+ expect(iconWrapper).toBeInTheDocument();
174
+ });
175
+
176
+ it("hides borders correctly for first and last items", () => {
177
+ const { rerender } = render(<StepperItem label="Test" isFirst={true} data-testid="item" />);
178
+
179
+ const leftBorderLine = screen
180
+ .getByTestId("item")
181
+ .querySelector('[class*="leftBorder"] [class*="border"]');
182
+ const rightBorderLine = screen
183
+ .getByTestId("item")
184
+ .querySelector('[class*="rightBorder"] [class*="border"]');
185
+ expect(leftBorderLine?.className).toMatch(/hidden/);
186
+ expect(rightBorderLine?.className).not.toMatch(/hidden/);
187
+
188
+ rerender(<StepperItem label="Test" isLast={true} data-testid="item" />);
189
+ const leftBorderLine2 = screen
190
+ .getByTestId("item")
191
+ .querySelector('[class*="leftBorder"] [class*="border"]');
192
+ const rightBorderLine2 = screen
193
+ .getByTestId("item")
194
+ .querySelector('[class*="rightBorder"] [class*="border"]');
195
+ expect(leftBorderLine2?.className).not.toMatch(/hidden/);
196
+ expect(rightBorderLine2?.className).toMatch(/hidden/);
197
+ });
198
+ });
@@ -0,0 +1,192 @@
1
+ import { Meta, StoryObj } from "@storybook/react-vite";
2
+ import { ComponentProps } from "react";
3
+ import { Stepper } from "./Stepper";
4
+
5
+ export default {
6
+ title: "Navigation/Stepper",
7
+ component: Stepper,
8
+ parameters: {
9
+ docs: {
10
+ description: {
11
+ component: "ステップ形式のナビゲーションコンポーネント。進行状況を視覚的に表示します。",
12
+ },
13
+ },
14
+ },
15
+ } satisfies Meta<typeof Stepper>;
16
+
17
+ type Story = StoryObj<typeof Stepper>;
18
+
19
+ const defaultArgs = {
20
+ currentStep: 1,
21
+ } satisfies Partial<ComponentProps<typeof Stepper>>;
22
+
23
+ export const Default: Story = {
24
+ render: (args) => (
25
+ <Stepper
26
+ {...args}
27
+ steps={[{ label: "ステップ1" }, { label: "ステップ2" }, { label: "ステップ3" }]}
28
+ />
29
+ ),
30
+ args: defaultArgs,
31
+ };
32
+
33
+ export const ThreeSteps: Story = {
34
+ render: () => {
35
+ const steps = [{ label: "ステップ1" }, { label: "ステップ2" }, { label: "ステップ3" }];
36
+
37
+ return (
38
+ <div style={{ display: "flex", flexDirection: "column", gap: "32px" }}>
39
+ <div>
40
+ <h2>Current Step: 0</h2>
41
+ <Stepper currentStep={0} steps={steps} />
42
+ </div>
43
+
44
+ <div>
45
+ <h2>Current Step: 1</h2>
46
+ <Stepper currentStep={1} steps={steps} />
47
+ </div>
48
+
49
+ <div>
50
+ <h2>Current Step: 2</h2>
51
+ <Stepper currentStep={2} steps={steps} />
52
+ </div>
53
+ </div>
54
+ );
55
+ },
56
+ };
57
+
58
+ export const FourSteps: Story = {
59
+ render: () => (
60
+ <Stepper
61
+ currentStep={1}
62
+ steps={[
63
+ { label: "知りたいこと" },
64
+ { label: "受診について" },
65
+ { label: "他に知りたいこと" },
66
+ { label: "確認と入力" },
67
+ ]}
68
+ />
69
+ ),
70
+ };
71
+
72
+ export const FiveSteps: Story = {
73
+ render: () => (
74
+ <Stepper
75
+ currentStep={2}
76
+ steps={[
77
+ { label: "ステップ1" },
78
+ { label: "ステップ2" },
79
+ { label: "ステップ3" },
80
+ { label: "ステップ4" },
81
+ { label: "ステップ5" },
82
+ ]}
83
+ />
84
+ ),
85
+ };
86
+
87
+ export const LongLabels: Story = {
88
+ render: () => (
89
+ <Stepper
90
+ currentStep={1}
91
+ steps={[
92
+ { label: "ステップステップステップステップステップステップ" },
93
+ { label: "改行\nも\nできます" },
94
+ {
95
+ label: "ステップステップステップステップステップステップステップステップステップステップ",
96
+ },
97
+ ]}
98
+ />
99
+ ),
100
+ };
101
+
102
+ export const CustomIcons: Story = {
103
+ render: () => (
104
+ <Stepper
105
+ currentStep={1}
106
+ steps={[
107
+ { label: "ホーム", icon: "HomeOutlineIcon" },
108
+ { label: "設定", icon: "SetupIcon" },
109
+ { label: "完了", icon: "CheckAIcon", doneIcon: "CheckAIcon" },
110
+ ]}
111
+ />
112
+ ),
113
+ };
114
+
115
+ export const ProgressStates: Story = {
116
+ render: () => {
117
+ const steps = [{ label: "ステップ1" }, { label: "ステップ2" }, { label: "ステップ3" }];
118
+
119
+ return (
120
+ <div style={{ display: "flex", flexDirection: "column", gap: "32px" }}>
121
+ <div>
122
+ <h3>Step 0 (開始)</h3>
123
+ <Stepper currentStep={0} steps={steps} />
124
+ </div>
125
+
126
+ <div>
127
+ <h3>Step 1 (進行中)</h3>
128
+ <Stepper currentStep={1} steps={steps} />
129
+ </div>
130
+
131
+ <div>
132
+ <h3>Step 2 (完了間近)</h3>
133
+ <Stepper currentStep={2} steps={steps} />
134
+ </div>
135
+ </div>
136
+ );
137
+ },
138
+ };
139
+
140
+ export const WithMargins: Story = {
141
+ render: () => (
142
+ <div style={{ border: "1px dashed #ccc", padding: "16px" }}>
143
+ <Stepper
144
+ m="lg"
145
+ currentStep={1}
146
+ steps={[{ label: "ステップ1" }, { label: "ステップ2" }, { label: "ステップ3" }]}
147
+ />
148
+ </div>
149
+ ),
150
+ };
151
+
152
+ export const DifferentWidths: Story = {
153
+ render: () => (
154
+ <div style={{ display: "flex", flexDirection: "column", gap: "32px" }}>
155
+ <div style={{ width: "280px", border: "1px solid #eee", padding: "16px" }}>
156
+ <h4>幅280px</h4>
157
+ <Stepper
158
+ currentStep={1}
159
+ steps={[{ label: "ステップ" }, { label: "ステップ" }, { label: "ステップ" }]}
160
+ />
161
+ </div>
162
+
163
+ <div style={{ width: "440px", border: "1px solid #eee", padding: "16px" }}>
164
+ <h4>幅440px</h4>
165
+ <Stepper
166
+ currentStep={1}
167
+ steps={[
168
+ { label: "知りたいこと" },
169
+ { label: "受診について" },
170
+ { label: "他に知りたいこと" },
171
+ { label: "確認と入力" },
172
+ ]}
173
+ />
174
+ </div>
175
+
176
+ <div style={{ width: "640px", border: "1px solid #eee", padding: "16px" }}>
177
+ <h4>幅640px</h4>
178
+ <Stepper
179
+ currentStep={1}
180
+ steps={[
181
+ { label: "ステップステップステップステップステップステップ" },
182
+ { label: "改行\nも\nできます" },
183
+ {
184
+ label:
185
+ "ステップステップステップステップステップステップステップステップステップステップ",
186
+ },
187
+ ]}
188
+ />
189
+ </div>
190
+ </div>
191
+ ),
192
+ };
@@ -0,0 +1,70 @@
1
+ "use client";
2
+
3
+ import { type CSSProperties } from "react";
4
+ import styles from "./Stepper.module.css";
5
+ import { StepperItem } from "./StepperItem";
6
+ import { marginVariables } from "../../utils/style";
7
+ import type { CustomDataAttributeProps } from "../../types/attributes";
8
+ import type { IconName } from "../../types/icon";
9
+ import type { Spacing } from "../../types/style";
10
+
11
+ export type StepStatus = "current" | "undone" | "done";
12
+
13
+ export interface StepData {
14
+ label: string;
15
+ icon?: IconName;
16
+ doneIcon?: IconName;
17
+ }
18
+
19
+ export interface StepperProps extends CustomDataAttributeProps {
20
+ steps: StepData[];
21
+ currentStep?: number;
22
+ // Margin props
23
+ m?: Spacing;
24
+ mx?: Spacing;
25
+ my?: Spacing;
26
+ mt?: Spacing;
27
+ mr?: Spacing;
28
+ mb?: Spacing;
29
+ ml?: Spacing;
30
+ }
31
+
32
+ export const Stepper = ({
33
+ steps,
34
+ currentStep = 0,
35
+ m,
36
+ mx,
37
+ my,
38
+ mt,
39
+ mr,
40
+ mb,
41
+ ml,
42
+ ...props
43
+ }: StepperProps) => {
44
+ const marginStyles = marginVariables({ m, mx, my, mt, mr, mb, ml });
45
+
46
+ return (
47
+ <div className={styles.stepper} style={marginStyles as CSSProperties} {...props}>
48
+ {steps.map((step, index) => {
49
+ const status: StepStatus =
50
+ index < currentStep ? "done" : index === currentStep ? "current" : "undone";
51
+
52
+ return (
53
+ <StepperItem
54
+ key={index}
55
+ label={step.label}
56
+ icon={step.icon}
57
+ doneIcon={step.doneIcon}
58
+ status={status}
59
+ isFirst={index === 0}
60
+ isLast={index === steps.length - 1}
61
+ stepIndex={index}
62
+ currentStep={currentStep}
63
+ />
64
+ );
65
+ })}
66
+ </div>
67
+ );
68
+ };
69
+
70
+ export type { StepperItemProps } from "./StepperItem";
@@ -0,0 +1,113 @@
1
+ "use client";
2
+
3
+ import { clsx } from "clsx";
4
+ import styles from "./Stepper.module.css";
5
+ import { Icon } from "../Icon/Icon";
6
+ import type { StepStatus } from "./Stepper";
7
+ import type { CustomDataAttributeProps } from "../../types/attributes";
8
+ import type { IconName } from "../../types/icon";
9
+
10
+ export interface StepperItemProps extends CustomDataAttributeProps {
11
+ label: string;
12
+ icon?: IconName;
13
+ doneIcon?: IconName;
14
+ status?: StepStatus;
15
+ isFirst?: boolean;
16
+ isLast?: boolean;
17
+ stepIndex?: number;
18
+ currentStep?: number;
19
+ }
20
+
21
+ export const StepperItem = ({
22
+ label,
23
+ icon,
24
+ doneIcon,
25
+ status = "undone",
26
+ isFirst = false,
27
+ isLast = false,
28
+ stepIndex = 0,
29
+ currentStep = 0,
30
+ ...props
31
+ }: StepperItemProps) => {
32
+ const renderIcon = () => {
33
+ // カスタムアイコンが指定されている場合はそれを使用
34
+ if (status === "done" && doneIcon) {
35
+ return <Icon icon={doneIcon} />;
36
+ }
37
+ if ((status === "current" || status === "undone") && icon) {
38
+ return <Icon icon={icon} />;
39
+ }
40
+
41
+ // デフォルトの状態に応じた描画
42
+ if (status === "done") {
43
+ // 白い丸で囲まれたチェックアイコン
44
+ return (
45
+ <div className={styles.doneCircle}>
46
+ <Icon icon="CheckAIcon" />
47
+ </div>
48
+ );
49
+ }
50
+
51
+ if (status === "current") {
52
+ // 塗りつぶされた青い丸
53
+ return <div className={styles.currentCircle} />;
54
+ }
55
+
56
+ // undone: グレーの枠の塗りつぶされていない丸
57
+ return <div className={styles.undoneCircle} />;
58
+ };
59
+
60
+ const itemClass = clsx({
61
+ [styles.stepperItem]: true,
62
+ [styles.current]: status === "current",
63
+ [styles.done]: status === "done",
64
+ [styles.undone]: status === "undone",
65
+ });
66
+
67
+ const leftBorderClass = clsx({
68
+ [styles.leftBorder]: true,
69
+ });
70
+
71
+ const rightBorderClass = clsx({
72
+ [styles.rightBorder]: true,
73
+ });
74
+
75
+ // 左の線: 現在のステップより左(つまり stepIndex <= currentStep)の場合は青
76
+ const leftBorderLineClass = clsx({
77
+ [styles.border]: true,
78
+ [styles.hidden]: isFirst,
79
+ [styles.borderColorBlue]: !isFirst && stepIndex <= currentStep,
80
+ [styles.borderColorGray]: !isFirst && stepIndex > currentStep,
81
+ });
82
+
83
+ // 右の線: 現在のステップより左(つまり stepIndex < currentStep)の場合は青
84
+ const rightBorderLineClass = clsx({
85
+ [styles.border]: true,
86
+ [styles.hidden]: isLast,
87
+ [styles.borderColorBlue]: !isLast && stepIndex < currentStep,
88
+ [styles.borderColorGray]: !isLast && stepIndex >= currentStep,
89
+ });
90
+
91
+ const labelClass = clsx({
92
+ [styles.label]: true,
93
+ [styles.currentLabel]: status === "current",
94
+ [styles.doneLabel]: status === "done",
95
+ [styles.undoneLabel]: status === "undone",
96
+ });
97
+
98
+ return (
99
+ <div className={itemClass} {...props}>
100
+ <div className={styles.borderLine}>
101
+ <div className={leftBorderClass}>
102
+ <div className={leftBorderLineClass} />
103
+ </div>
104
+ <div className={styles.iconSpacer} />
105
+ <div className={rightBorderClass}>
106
+ <div className={rightBorderLineClass} />
107
+ </div>
108
+ </div>
109
+ <div className={styles.iconWrapper}>{renderIcon()}</div>
110
+ <div className={labelClass}>{label}</div>
111
+ </div>
112
+ );
113
+ };