goobs-frontend 0.8.22 → 0.8.24

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 (80) hide show
  1. package/package.json +22 -2
  2. package/src/app/layout.tsx +18 -0
  3. package/src/components/Accordion/accordion.stories.tsx +325 -0
  4. package/src/components/Button/button.stories.tsx +157 -0
  5. package/src/components/Button/index.tsx +25 -48
  6. package/src/components/Card/card.stories.tsx +367 -0
  7. package/src/components/Checkbox/checkbox.stories.tsx +133 -0
  8. package/src/components/CodeCopy/codecopy.stories.tsx +128 -0
  9. package/src/components/ComplexTextEditor/MarkdownEditor/index.tsx +5 -3
  10. package/src/components/ComplexTextEditor/editor.stories.tsx +177 -0
  11. package/src/components/ComplexTextEditor/utils/useMarkdownEditor.tsx +29 -11
  12. package/src/components/ComplexTextEditor/utils/useRichtextEditor.tsx +4 -4
  13. package/src/components/ConfirmationCodeInput/codeinput.stories.tsx +170 -0
  14. package/src/components/ConfirmationCodeInput/index.tsx +28 -14
  15. package/src/components/DataGrid/Table/ColumnHeaderRow/index.tsx +26 -17
  16. package/src/components/DataGrid/Table/Rows/index.tsx +76 -17
  17. package/src/components/DataGrid/Table/index.tsx +25 -17
  18. package/src/components/DataGrid/datagrid.stories.tsx +307 -0
  19. package/src/components/DataGrid/index.tsx +12 -13
  20. package/src/components/DataGrid/utils/useComputeTableResize.tsx +27 -7
  21. package/src/components/DataGrid/utils/useToolbarSearchbar.tsx +59 -36
  22. package/src/components/DateField/datefield.stories.tsx +144 -0
  23. package/src/components/Dropdown/dropdown.stories.tsx +268 -0
  24. package/src/components/Dropdown/index.tsx +200 -62
  25. package/src/components/Grid/index.tsx +72 -94
  26. package/src/components/IncrementNumberField/incrementnumberfield.stories.tsx +152 -0
  27. package/src/components/Nav/index.tsx +104 -118
  28. package/src/components/Nav/nav.stories.tsx +283 -0
  29. package/src/components/NumberField/numberfield.stories.tsx +137 -0
  30. package/src/components/PasswordField/passwordfield.stories.tsx +120 -0
  31. package/src/components/PhoneNumberField/phonenumberfield.stories.tsx +134 -0
  32. package/src/components/PricingTable/index.tsx +23 -4
  33. package/src/components/PricingTable/pricingtable.stories.tsx +144 -0
  34. package/src/components/ProjectBoard/AddTask/client.tsx +20 -49
  35. package/src/components/ProjectBoard/ManageTask/client.tsx +17 -21
  36. package/src/components/ProjectBoard/index.tsx +4 -15
  37. package/src/components/ProjectBoard/projectboard.stories.tsx +384 -0
  38. package/src/components/QRCode/qrcode.stories.tsx +117 -0
  39. package/src/components/RadioGroup/index.tsx +1 -1
  40. package/src/components/RadioGroup/radiogroup.stories.tsx +155 -0
  41. package/src/components/SearchableDropdown/index.tsx +18 -32
  42. package/src/components/SearchableDropdown/searchabledropdown.stories.tsx +230 -0
  43. package/src/components/Searchbar/searchbar.stories.tsx +144 -0
  44. package/src/components/Stepper/stepper.stories.tsx +255 -0
  45. package/src/components/Tabs/tabs.stories.tsx +212 -0
  46. package/src/components/TextField/textfield.stories.tsx +225 -0
  47. package/src/components/Toolbar/toolbar.stories.tsx +169 -0
  48. package/src/components/Tooltip/tooltip.stories.tsx +170 -0
  49. package/src/components/TransferList/index.tsx +1 -1
  50. package/src/components/TransferList/transferlist.stories.tsx +212 -0
  51. package/src/components/Typography/typography.stories.tsx +147 -0
  52. package/src/stories/Button.stories.ts +53 -0
  53. package/src/stories/Button.tsx +47 -0
  54. package/src/stories/Configure.mdx +451 -0
  55. package/src/stories/Header.stories.ts +33 -0
  56. package/src/stories/Header.tsx +71 -0
  57. package/src/stories/Page.stories.ts +32 -0
  58. package/src/stories/Page.tsx +91 -0
  59. package/src/stories/assets/accessibility.png +0 -0
  60. package/src/stories/assets/accessibility.svg +1 -0
  61. package/src/stories/assets/addon-library.png +0 -0
  62. package/src/stories/assets/assets.png +0 -0
  63. package/src/stories/assets/avif-test-image.avif +0 -0
  64. package/src/stories/assets/context.png +0 -0
  65. package/src/stories/assets/discord.svg +1 -0
  66. package/src/stories/assets/docs.png +0 -0
  67. package/src/stories/assets/figma-plugin.png +0 -0
  68. package/src/stories/assets/github.svg +1 -0
  69. package/src/stories/assets/share.png +0 -0
  70. package/src/stories/assets/styling.png +0 -0
  71. package/src/stories/assets/testing.png +0 -0
  72. package/src/stories/assets/theming.png +0 -0
  73. package/src/stories/assets/tutorials.svg +1 -0
  74. package/src/stories/assets/youtube.svg +1 -0
  75. package/src/stories/button.css +30 -0
  76. package/src/stories/header.css +32 -0
  77. package/src/stories/page.css +69 -0
  78. package/src/app/_app.tsx +0 -8
  79. package/src/components/Grid/defaultconfig.tsx +0 -131
  80. package/src/components/ProjectBoard/jotai/task.ts +0 -0
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "goobs-frontend",
3
- "version": "0.8.22",
3
+ "version": "0.8.24",
4
4
  "type": "module",
5
5
  "description": "A comprehensive React-based UI library built on Material-UI, offering a wide range of customizable components including grids, typography, buttons, cards, forms, navigation, pricing tables, steppers, tooltips, accordions, and more. Designed for building responsive and consistent user interfaces with advanced features like form validation, theming, and code syntax highlighting.",
6
6
  "license": "MIT",
@@ -16,7 +16,10 @@
16
16
  "dev": "next dev",
17
17
  "build": "next build",
18
18
  "start": "next start",
19
- "lint": "next lint"
19
+ "lint": "next lint",
20
+ "storybook": "storybook dev -p 6006",
21
+ "chromatic": "chromatic --project-token=chpt_2d34a5f45351b6c",
22
+ "build-storybook": "storybook build"
20
23
  },
21
24
  "dependencies": {
22
25
  "@emotion/cache": "^11.14.0",
@@ -37,20 +40,32 @@
37
40
  "slate-dom": "^0.111.0",
38
41
  "slate-history": "^0.110.3",
39
42
  "slate-react": "^0.112.0",
43
+ "storybook": "^8.4.7",
40
44
  "zod": "^3.24.1",
41
45
  "zod-formik-adapter": "^1.3.0"
42
46
  },
43
47
  "devDependencies": {
48
+ "@chromatic-com/storybook": "^3.2.3",
44
49
  "@next/eslint-plugin-next": "^15.1.4",
50
+ "@storybook/addon-essentials": "^8.4.7",
51
+ "@storybook/addon-interactions": "^8.4.7",
52
+ "@storybook/addon-onboarding": "8.4.7",
53
+ "@storybook/blocks": "8.4.7",
54
+ "@storybook/nextjs": "^8.4.7",
55
+ "@storybook/react": "^8.4.7",
56
+ "@storybook/test": "^8.4.7",
57
+ "@storybook/testing-library": "^0.2.2",
45
58
  "@types/node": "^22.10.5",
46
59
  "@types/react": "19.0.5",
47
60
  "@types/react-dom": "^19.0.3",
48
61
  "@typescript-eslint/eslint-plugin": "^8.19.1",
49
62
  "@typescript-eslint/parser": "^8.19.1",
63
+ "chromatic": "^11.24.0",
50
64
  "eslint": "^9.18.0",
51
65
  "eslint-config-next": "^15.1.4",
52
66
  "eslint-config-prettier": "^9.1.0",
53
67
  "eslint-plugin-prettier": "^5.2.1",
68
+ "eslint-plugin-storybook": "^0.11.2",
54
69
  "prettier": "^3.4.2",
55
70
  "react": "^19.0.0",
56
71
  "react-dom": "^19.0.0",
@@ -121,5 +136,10 @@
121
136
  "resolutions": {
122
137
  "string-width": "^4.2.3",
123
138
  "strip-ansi": "^6.0.1"
139
+ },
140
+ "eslintConfig": {
141
+ "extends": [
142
+ "plugin:storybook/recommended"
143
+ ]
124
144
  }
125
145
  }
@@ -0,0 +1,18 @@
1
+ // src/app/layout.tsx
2
+ import React from 'react'
3
+
4
+ export const metadata = {
5
+ title: 'Goobs Frontend',
6
+ }
7
+
8
+ export default function RootLayout({
9
+ children,
10
+ }: {
11
+ children: React.ReactNode
12
+ }) {
13
+ return (
14
+ <html lang="en">
15
+ <body>{children}</body>
16
+ </html>
17
+ )
18
+ }
@@ -0,0 +1,325 @@
1
+ // src/components/Accordion/accordion.stories.tsx
2
+ import React from 'react'
3
+ import { Meta, StoryObj } from '@storybook/react'
4
+ import { userEvent, within, expect } from '@storybook/test'
5
+ import { Accordion, AccordionSummary, AccordionDetails } from './index'
6
+ import Typography from '../Typography'
7
+
8
+ /**
9
+ * Setup story metadata
10
+ */
11
+ const meta: Meta<typeof Accordion> = {
12
+ title: 'Components/Accordion',
13
+ component: Accordion,
14
+ parameters: {
15
+ a11y: {
16
+ disable: false,
17
+ },
18
+ },
19
+ // If you want color pickers or controls for specific props, you can define them in argTypes
20
+ argTypes: {},
21
+ }
22
+ export default meta
23
+
24
+ type Story = StoryObj<typeof Accordion>
25
+
26
+ /**
27
+ * Reusable child content
28
+ */
29
+ const sampleContent = (
30
+ <Typography
31
+ fontvariant="merriparagraph"
32
+ text="This is the accordion content."
33
+ />
34
+ )
35
+
36
+ /**
37
+ * 1) Basic single Accordion
38
+ */
39
+ export const SingleAccordion: Story = {
40
+ name: 'Single Accordion (collapsed by default)',
41
+ render: args => (
42
+ <Accordion {...args}>
43
+ <AccordionSummary>Single Accordion</AccordionSummary>
44
+ <AccordionDetails>{sampleContent}</AccordionDetails>
45
+ </Accordion>
46
+ ),
47
+ play: async ({ canvasElement }) => {
48
+ const canvas = within(canvasElement)
49
+
50
+ // 1. Verify that the accordion content is not visible initially
51
+ expect(
52
+ canvas.queryByText('This is the accordion content.')
53
+ ).not.toBeInTheDocument()
54
+
55
+ // 2. Click on the summary to expand
56
+ await userEvent.click(canvas.getByText('Single Accordion'))
57
+
58
+ // 3. Verify that the content is now visible
59
+ expect(
60
+ canvas.getByText('This is the accordion content.')
61
+ ).toBeInTheDocument()
62
+ },
63
+ }
64
+
65
+ /**
66
+ * 2) Basic single Accordion (defaultExpanded)
67
+ */
68
+ export const DefaultExpanded: Story = {
69
+ name: 'Single Accordion (defaultExpanded)',
70
+ render: args => (
71
+ <Accordion defaultExpanded {...args}>
72
+ <AccordionSummary>Default Expanded</AccordionSummary>
73
+ <AccordionDetails>{sampleContent}</AccordionDetails>
74
+ </Accordion>
75
+ ),
76
+ // Add async and return the assertion
77
+ play: async ({ canvasElement }) => {
78
+ const canvas = within(canvasElement)
79
+ // Return the assertion to properly handle the promise
80
+ return expect(
81
+ canvas.getByText('This is the accordion content.')
82
+ ).toBeInTheDocument()
83
+ },
84
+ }
85
+
86
+ /**
87
+ * 3) Multiple Accordions
88
+ */
89
+ export const MultipleAccordions: Story = {
90
+ name: 'Multiple Accordions',
91
+ render: args => (
92
+ <>
93
+ <Accordion {...args}>
94
+ <AccordionSummary>First Item</AccordionSummary>
95
+ <AccordionDetails>
96
+ <Typography
97
+ fontvariant="merriparagraph"
98
+ text="Details for first item."
99
+ />
100
+ </AccordionDetails>
101
+ </Accordion>
102
+ <Accordion {...args}>
103
+ <AccordionSummary>Second Item</AccordionSummary>
104
+ <AccordionDetails>
105
+ <Typography
106
+ fontvariant="merriparagraph"
107
+ text="Details for second item."
108
+ />
109
+ </AccordionDetails>
110
+ </Accordion>
111
+ <Accordion {...args}>
112
+ <AccordionSummary>Third Item</AccordionSummary>
113
+ <AccordionDetails>
114
+ <Typography
115
+ fontvariant="merriparagraph"
116
+ text="Details for third item."
117
+ />
118
+ </AccordionDetails>
119
+ </Accordion>
120
+ </>
121
+ ),
122
+ play: async ({ canvasElement }) => {
123
+ const canvas = within(canvasElement)
124
+
125
+ // Initially, no details are visible
126
+ expect(
127
+ canvas.queryByText('Details for first item.')
128
+ ).not.toBeInTheDocument()
129
+ expect(
130
+ canvas.queryByText('Details for second item.')
131
+ ).not.toBeInTheDocument()
132
+ expect(
133
+ canvas.queryByText('Details for third item.')
134
+ ).not.toBeInTheDocument()
135
+
136
+ // Expand the second item
137
+ await userEvent.click(canvas.getByText('Second Item'))
138
+ expect(canvas.getByText('Details for second item.')).toBeInTheDocument()
139
+
140
+ // Expand the first item
141
+ await userEvent.click(canvas.getByText('First Item'))
142
+ expect(canvas.getByText('Details for first item.')).toBeInTheDocument()
143
+
144
+ // The second item should remain expanded unless the `Accordion` is configured otherwise
145
+ expect(canvas.getByText('Details for second item.')).toBeInTheDocument()
146
+ },
147
+ }
148
+
149
+ /**
150
+ * 4) Nested Accordion
151
+ */
152
+ export const NestedAccordion: Story = {
153
+ name: 'Nested Accordion',
154
+ render: args => (
155
+ <Accordion {...args}>
156
+ <AccordionSummary>Parent Accordion</AccordionSummary>
157
+ <AccordionDetails>
158
+ <Typography fontvariant="merriparagraph" text="Top-level content" />
159
+ <Accordion {...args}>
160
+ <AccordionSummary>Nested Accordion</AccordionSummary>
161
+ <AccordionDetails>{sampleContent}</AccordionDetails>
162
+ </Accordion>
163
+ </AccordionDetails>
164
+ </Accordion>
165
+ ),
166
+ play: async ({ canvasElement }) => {
167
+ const canvas = within(canvasElement)
168
+
169
+ // The nested content is not initially visible
170
+ expect(
171
+ canvas.queryByText('This is the accordion content.')
172
+ ).not.toBeInTheDocument()
173
+
174
+ // Expand the parent
175
+ await userEvent.click(canvas.getByText('Parent Accordion'))
176
+ expect(canvas.getByText('Top-level content')).toBeInTheDocument()
177
+
178
+ // Now expand the nested accordion
179
+ await userEvent.click(canvas.getByText('Nested Accordion'))
180
+ expect(
181
+ canvas.getByText('This is the accordion content.')
182
+ ).toBeInTheDocument()
183
+ },
184
+ }
185
+
186
+ /**
187
+ * 5) Custom styles
188
+ */
189
+ export const CustomStyles: Story = {
190
+ name: 'Custom Styled Accordion',
191
+ render: args => (
192
+ <Accordion
193
+ sx={{
194
+ border: '2px solid #4caf50',
195
+ borderRadius: '8px',
196
+ marginTop: '10px',
197
+ }}
198
+ {...args}
199
+ >
200
+ <AccordionSummary>Custom Styles</AccordionSummary>
201
+ <AccordionDetails>
202
+ <Typography
203
+ fontvariant="merriparagraph"
204
+ text="Look at this fancy border!"
205
+ />
206
+ </AccordionDetails>
207
+ </Accordion>
208
+ ),
209
+ play: async ({ canvasElement }) => {
210
+ const canvas = within(canvasElement)
211
+ // Expand and verify
212
+ await userEvent.click(canvas.getByText('Custom Styles'))
213
+ expect(canvas.getByText('Look at this fancy border!')).toBeInTheDocument()
214
+ },
215
+ }
216
+
217
+ /**
218
+ * 6) Accordion with many lines of content
219
+ */
220
+ export const LargeContent: Story = {
221
+ name: 'Accordion With Large Content',
222
+ render: args => (
223
+ <Accordion {...args}>
224
+ <AccordionSummary>Lots of Text</AccordionSummary>
225
+ <AccordionDetails>
226
+ <div>
227
+ <Typography fontvariant="merriparagraph" text="Line 1" />
228
+ <Typography fontvariant="merriparagraph" text="Line 2" />
229
+ <Typography fontvariant="merriparagraph" text="Line 3" />
230
+ <Typography fontvariant="merriparagraph" text="Line 4" />
231
+ <Typography fontvariant="merriparagraph" text="Line 5" />
232
+ <Typography fontvariant="merriparagraph" text="Line 6" />
233
+ <Typography fontvariant="merriparagraph" text="Line 7" />
234
+ <Typography fontvariant="merriparagraph" text="Line 8" />
235
+ <Typography fontvariant="merriparagraph" text="Line 9" />
236
+ </div>
237
+ </AccordionDetails>
238
+ </Accordion>
239
+ ),
240
+ play: async ({ canvasElement }) => {
241
+ const canvas = within(canvasElement)
242
+ // Expand
243
+ await userEvent.click(canvas.getByText('Lots of Text'))
244
+ // Confirm a few lines
245
+ expect(canvas.getByText('Line 1')).toBeInTheDocument()
246
+ expect(canvas.getByText('Line 9')).toBeInTheDocument()
247
+ },
248
+ }
249
+
250
+ /**
251
+ * 7) Disabled Accordion
252
+ */
253
+ export const DisabledAccordion: Story = {
254
+ name: 'Disabled Accordion',
255
+ render: args => (
256
+ <Accordion disabled {...args}>
257
+ <AccordionSummary>Cannot Expand</AccordionSummary>
258
+ <AccordionDetails>{sampleContent}</AccordionDetails>
259
+ </Accordion>
260
+ ),
261
+ play: async ({ canvasElement }) => {
262
+ const canvas = within(canvasElement)
263
+ // Try to expand
264
+ await userEvent.click(canvas.getByText('Cannot Expand'))
265
+ // Content should remain hidden
266
+ expect(
267
+ canvas.queryByText('This is the accordion content.')
268
+ ).not.toBeInTheDocument()
269
+ },
270
+ }
271
+
272
+ /**
273
+ * 8) Controlled Accordion (toggle via props)
274
+ * We must put our hook usage in a separate
275
+ * React component whose name starts with a capital letter.
276
+ */
277
+ const ControlledAccordionExample = () => {
278
+ const [isExpanded, setIsExpanded] = React.useState(false)
279
+
280
+ return (
281
+ <>
282
+ <button onClick={() => setIsExpanded(!isExpanded)}>
283
+ Toggle Accordion
284
+ </button>
285
+ <Accordion expanded={isExpanded}>
286
+ <AccordionSummary>Controlled Accordion</AccordionSummary>
287
+ <AccordionDetails>
288
+ <Typography
289
+ fontvariant="merriparagraph"
290
+ text="This is controlled externally."
291
+ />
292
+ </AccordionDetails>
293
+ </Accordion>
294
+ </>
295
+ )
296
+ }
297
+
298
+ export const ControlledAccordion: Story = {
299
+ name: 'Controlled Accordion (toggle via props)',
300
+ render: () => <ControlledAccordionExample />,
301
+ play: async ({ canvasElement }) => {
302
+ const canvas = within(canvasElement)
303
+ // Initially hidden
304
+ expect(
305
+ canvas.queryByText('This is controlled externally.')
306
+ ).not.toBeInTheDocument()
307
+
308
+ // Click the external toggle button
309
+ await userEvent.click(
310
+ canvas.getByRole('button', { name: 'Toggle Accordion' })
311
+ )
312
+ // Now the content should appear
313
+ expect(
314
+ canvas.getByText('This is controlled externally.')
315
+ ).toBeInTheDocument()
316
+
317
+ // Click again to hide
318
+ await userEvent.click(
319
+ canvas.getByRole('button', { name: 'Toggle Accordion' })
320
+ )
321
+ expect(
322
+ canvas.queryByText('This is controlled externally.')
323
+ ).not.toBeInTheDocument()
324
+ },
325
+ }
@@ -0,0 +1,157 @@
1
+ // src/components/Button/button.stories.tsx
2
+
3
+ import React from 'react'
4
+ import { Meta, StoryObj } from '@storybook/react'
5
+ import { userEvent, within, expect } from '@storybook/test'
6
+ import CustomButton /* , { CustomButtonProps } */ from './index' // Remove { CustomButtonProps } if unused
7
+ // For an example icon usage, we can import something from Material UI icons:
8
+ import { Send } from '@mui/icons-material'
9
+
10
+ /**
11
+ * Storybook metadata
12
+ */
13
+ const meta: Meta<typeof CustomButton> = {
14
+ title: 'Components/Button',
15
+ component: CustomButton,
16
+ // If you want Storybook controls for certain props (color pickers, etc.), configure them here:
17
+ argTypes: {
18
+ backgroundcolor: { control: 'color' },
19
+ fontcolor: { control: 'color' },
20
+ iconcolor: { control: 'color' },
21
+ iconsize: { control: 'text' },
22
+ },
23
+ parameters: {
24
+ a11y: {
25
+ disable: false,
26
+ },
27
+ },
28
+ }
29
+ export default meta
30
+
31
+ type Story = StoryObj<typeof CustomButton>
32
+
33
+ /**
34
+ * 1) Default Button
35
+ */
36
+ export const DefaultButton: Story = {
37
+ args: {
38
+ text: 'Click Me',
39
+ },
40
+ // Optional test using Storybook "play" function
41
+ play: async ({ canvasElement }) => {
42
+ const canvas = within(canvasElement)
43
+
44
+ // Grab the button
45
+ const buttonEl = canvas.getByRole('button', { name: /click me/i })
46
+
47
+ // Simulate a user click
48
+ await userEvent.click(buttonEl)
49
+
50
+ // Verify some condition - for now, just ensure it's not disabled
51
+ expect(buttonEl).not.toBeDisabled()
52
+ },
53
+ }
54
+
55
+ /**
56
+ * 2) Disabled Button
57
+ */
58
+ export const DisabledButton: Story = {
59
+ args: {
60
+ text: 'I am disabled',
61
+ disableButton: 'true',
62
+ },
63
+ // Add an 'await' call or remove `async`
64
+ play: async ({ canvasElement }) => {
65
+ const canvas = within(canvasElement)
66
+ const buttonEl = canvas.getByRole('button', { name: /i am disabled/i })
67
+
68
+ // Attempt a click just to satisfy 'await'
69
+ await userEvent.click(buttonEl)
70
+
71
+ // This button should be disabled
72
+ expect(buttonEl).toBeDisabled()
73
+ },
74
+ }
75
+
76
+ /**
77
+ * 3) Text (Transparent) Button
78
+ * (backgroundcolor="none" => "text" style)
79
+ */
80
+ export const TextButton: Story = {
81
+ args: {
82
+ text: 'I am text only',
83
+ backgroundcolor: 'none', // Transparent
84
+ fontcolor: '#1976d2', // Typical link-blue color
85
+ },
86
+ // Add an 'await' call or remove `async`
87
+ play: async ({ canvasElement }) => {
88
+ const canvas = within(canvasElement)
89
+ const buttonEl = canvas.getByRole('button', { name: /i am text only/i })
90
+
91
+ // Confirm it is not disabled by clicking
92
+ await userEvent.click(buttonEl)
93
+ expect(buttonEl).not.toBeDisabled()
94
+ },
95
+ }
96
+
97
+ /**
98
+ * 4) Button with Icon on the Left
99
+ */
100
+ export const WithIconLeft: Story = {
101
+ args: {
102
+ text: 'Send',
103
+ icon: <Send />, // MUI icon
104
+ iconlocation: 'left', // Icon on the left
105
+ },
106
+ play: async ({ canvasElement }) => {
107
+ const canvas = within(canvasElement)
108
+ const buttonEl = canvas.getByRole('button', { name: /send/i })
109
+ await userEvent.click(buttonEl)
110
+ // We can assert it's still present
111
+ expect(buttonEl).toBeInTheDocument()
112
+ },
113
+ }
114
+
115
+ /**
116
+ * 5) Button with Icon on the Right
117
+ */
118
+ export const WithIconRight: Story = {
119
+ args: {
120
+ text: 'Send',
121
+ icon: <Send />,
122
+ iconlocation: 'right',
123
+ },
124
+ // No "play" test here, but you can add one if you like
125
+ }
126
+
127
+ /**
128
+ * 6) Button with Icon Above the Text
129
+ */
130
+ export const WithIconAbove: Story = {
131
+ args: {
132
+ text: 'Send',
133
+ icon: <Send />,
134
+ iconlocation: 'above', // Icon stacked on top
135
+ fontlocation: 'center', // Center text
136
+ },
137
+ }
138
+
139
+ /**
140
+ * 7) Custom Colors & Sizing
141
+ */
142
+ export const CustomColorsAndSize: Story = {
143
+ args: {
144
+ text: 'Custom Colors',
145
+ backgroundcolor: '#4caf50',
146
+ fontcolor: '#ffffff',
147
+ width: '120px',
148
+ height: '50px',
149
+ },
150
+ play: async ({ canvasElement }) => {
151
+ const canvas = within(canvasElement)
152
+ const buttonEl = canvas.getByRole('button', { name: /custom colors/i })
153
+
154
+ await userEvent.click(buttonEl)
155
+ expect(buttonEl).not.toBeDisabled()
156
+ },
157
+ }