@scality/core-ui 0.203.0 → 0.204.0

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 (34) hide show
  1. package/.github/workflows/github-pages.yml +4 -4
  2. package/.github/workflows/post-release.yml +25 -12
  3. package/__mocks__/fileMock.js +1 -1
  4. package/__mocks__/styleMock.js +1 -1
  5. package/__mocks__/uuid.js +1 -5
  6. package/dist/components/drawer/Drawer.component.d.ts +17 -0
  7. package/dist/components/drawer/Drawer.component.d.ts.map +1 -0
  8. package/dist/components/drawer/Drawer.component.js +132 -0
  9. package/dist/index.d.ts +1 -0
  10. package/dist/index.d.ts.map +1 -1
  11. package/dist/index.js +1 -0
  12. package/dist/next.d.ts +1 -0
  13. package/dist/next.d.ts.map +1 -1
  14. package/dist/next.js +1 -0
  15. package/dist/style/theme.d.ts +1 -0
  16. package/dist/style/theme.d.ts.map +1 -1
  17. package/dist/style/theme.js +1 -0
  18. package/global-setup.js +1 -1
  19. package/jest.config.js +1 -1
  20. package/package.json +13 -3
  21. package/src/lib/components/drawer/Drawer.component.test.tsx +108 -0
  22. package/src/lib/components/drawer/Drawer.component.tsx +207 -0
  23. package/src/lib/index.ts +1 -0
  24. package/src/lib/next.ts +1 -0
  25. package/src/lib/style/theme.ts +1 -0
  26. package/src/lib/valalint/README.md +50 -0
  27. package/src/lib/valalint/index.js +49 -0
  28. package/src/lib/valalint/rules/modal-button-forbidden-label.js +87 -0
  29. package/src/lib/valalint/rules/modal-button-forbidden-label.test.js +157 -0
  30. package/src/lib/valalint/rules/no-raw-number-in-jsx.js +64 -0
  31. package/src/lib/valalint/rules/no-raw-number-in-jsx.test.js +237 -0
  32. package/src/lib/valalint/rules/technical-sentence-case.js +93 -0
  33. package/src/lib/valalint/rules/technical-sentence-case.test.js +167 -0
  34. package/stories/drawer.stories.tsx +135 -0
@@ -0,0 +1,237 @@
1
+ /**
2
+ * @jest-environment node
3
+ */
4
+ import { RuleTester } from '@typescript-eslint/rule-tester';
5
+ import rule from './no-raw-number-in-jsx.js';
6
+ import path from 'path';
7
+
8
+ // Jest runs from the project root, so process.cwd() is the workspace root
9
+ // where tsconfig.json lives.
10
+ const tsconfigRootDir = process.cwd();
11
+
12
+ // Virtual filename placed at the workspace root so it is matched by the
13
+ // allowDefaultProject glob ('*.tsx') and uses the workspace tsconfig.
14
+ const filename = path.join(tsconfigRootDir, 'test.tsx');
15
+
16
+ const tester = new RuleTester({
17
+ languageOptions: {
18
+ parserOptions: {
19
+ ecmaFeatures: { jsx: true },
20
+ ecmaVersion: 2020,
21
+ projectService: {
22
+ allowDefaultProject: ['*.tsx'],
23
+ defaultProject: 'tsconfig.json',
24
+ },
25
+ tsconfigRootDir,
26
+ },
27
+ },
28
+ });
29
+
30
+ const ERROR =
31
+ 'Avoid rendering raw numbers in JSX text. Use formatISONumber(value) to apply proper ISO formatting (space as thousands separator, leading zero before decimal point).';
32
+
33
+ tester.run('no-raw-number-in-jsx', rule, {
34
+ // ─── Valid ────────────────────────────────────────────────────────────────
35
+ valid: [
36
+ // ── Numbers in JSX attribute values — never flagged ───────────────────
37
+ {
38
+ // Numeric literal in an attribute
39
+ code: 'const el = <input value={42} />;',
40
+ filename,
41
+ },
42
+ {
43
+ // Typed number variable in an attribute
44
+ code: 'declare const max: number; const el = <Chart max={max} />;',
45
+ filename,
46
+ },
47
+ {
48
+ // Multiple numeric attributes
49
+ code: 'declare const n: number; const el = <Slider step={0.5} min={0} max={n} />;',
50
+ filename,
51
+ },
52
+
53
+ // ── String types in JSX children — not flagged ────────────────────────
54
+ {
55
+ code: 'declare const name: string; const el = <Text>{name}</Text>;',
56
+ filename,
57
+ },
58
+ {
59
+ code: 'const el = <Text>{"hello"}</Text>;',
60
+ filename,
61
+ },
62
+ {
63
+ code: 'const el = <Text>{`template`}</Text>;',
64
+ filename,
65
+ },
66
+ {
67
+ // Template literal containing a number expression is still a string
68
+ // eslint-disable-next-line no-template-curly-in-string
69
+ code: 'declare const count: number; const el = <Text>{`Total: ${count}`}</Text>;',
70
+ filename,
71
+ },
72
+
73
+ // ── formatISONumber returns string — not flagged ───────────────────────
74
+ {
75
+ code: 'declare function formatISONumber(n: number, opts?: object): string; declare const count: number; const el = <Text>{formatISONumber(count)}</Text>;',
76
+ filename,
77
+ },
78
+
79
+ // ── Methods that return strings — not flagged ──────────────────────────
80
+ {
81
+ // Number.prototype.toFixed returns string
82
+ code: 'declare const value: number; const el = <Text>{value.toFixed(2)}</Text>;',
83
+ filename,
84
+ },
85
+ {
86
+ // Number.prototype.toPrecision returns string
87
+ code: 'declare const value: number; const el = <Text>{value.toPrecision(4)}</Text>;',
88
+ filename,
89
+ },
90
+ {
91
+ // String.prototype.toUpperCase returns string
92
+ code: 'declare const label: string; const el = <Text>{label.toUpperCase()}</Text>;',
93
+ filename,
94
+ },
95
+
96
+ // ── Other non-numeric types — not flagged ─────────────────────────────
97
+ {
98
+ code: 'const el = <Text>{null}</Text>;',
99
+ filename,
100
+ },
101
+ {
102
+ code: 'declare const flag: boolean; const el = <Text>{flag}</Text>;',
103
+ filename,
104
+ },
105
+ {
106
+ // string + number → string (TS widens to string)
107
+ code: 'declare const count: number; const el = <Text>{count + " items"}</Text>;',
108
+ filename,
109
+ },
110
+ {
111
+ // Conditional producing string
112
+ code: 'declare const flag: boolean; const el = <Text>{flag ? "yes" : "no"}</Text>;',
113
+ filename,
114
+ },
115
+
116
+ // ── JSX comment — not flagged ──────────────────────────────────────────
117
+ {
118
+ code: 'const el = <Text>{/* comment */}</Text>;',
119
+ filename,
120
+ },
121
+ ],
122
+
123
+ // ─── Invalid ──────────────────────────────────────────────────────────────
124
+ invalid: [
125
+ // ── Numeric literals (TypeFlags.NumberLiteral) ─────────────────────────
126
+ {
127
+ code: 'const el = <Text>{42}</Text>;',
128
+ filename,
129
+ errors: [{ message: ERROR }],
130
+ },
131
+ {
132
+ code: 'const el = <Text>{0}</Text>;',
133
+ filename,
134
+ errors: [{ message: ERROR }],
135
+ },
136
+ {
137
+ code: 'const el = <Text>{3.14}</Text>;',
138
+ filename,
139
+ errors: [{ message: ERROR }],
140
+ },
141
+
142
+ // ── Typed number variables (TypeFlags.Number) ─────────────────────────
143
+ {
144
+ code: 'declare const count: number; const el = <Text>{count}</Text>;',
145
+ filename,
146
+ errors: [{ message: ERROR }],
147
+ },
148
+ {
149
+ // Works on any element tag
150
+ code: 'declare const price: number; const el = <Tooltip>{price}</Tooltip>;',
151
+ filename,
152
+ errors: [{ message: ERROR }],
153
+ },
154
+
155
+ // ── Arithmetic that produces a number ─────────────────────────────────
156
+ {
157
+ // number + number → number
158
+ code: 'declare const a: number; declare const b: number; const el = <Text>{a + b}</Text>;',
159
+ filename,
160
+ errors: [{ message: ERROR }],
161
+ },
162
+ {
163
+ code: 'declare const total: number; declare const tax: number; const el = <Text>{total - tax}</Text>;',
164
+ filename,
165
+ errors: [{ message: ERROR }],
166
+ },
167
+ {
168
+ code: 'declare const bytes: number; const el = <Text>{bytes / 1024}</Text>;',
169
+ filename,
170
+ errors: [{ message: ERROR }],
171
+ },
172
+
173
+ // ── Unary minus on a number ───────────────────────────────────────────
174
+ {
175
+ code: 'declare const n: number; const el = <Text>{-n}</Text>;',
176
+ filename,
177
+ errors: [{ message: ERROR }],
178
+ },
179
+
180
+ // ── Properties that return number ─────────────────────────────────────
181
+ {
182
+ // Array.length → number
183
+ code: 'declare const items: string[]; const el = <Text>{items.length}</Text>;',
184
+ filename,
185
+ errors: [{ message: ERROR }],
186
+ },
187
+
188
+ // ── Functions that return number ───────────────────────────────────────
189
+ {
190
+ code: 'declare function getCount(): number; const el = <Text>{getCount()}</Text>;',
191
+ filename,
192
+ errors: [{ message: ERROR }],
193
+ },
194
+
195
+ // ── Union types that include number ───────────────────────────────────
196
+ {
197
+ // number | undefined — when defined it would render an unformatted number
198
+ code: 'declare const count: number | undefined; const el = <Text>{count}</Text>;',
199
+ filename,
200
+ errors: [{ message: ERROR }],
201
+ },
202
+ {
203
+ // string | number
204
+ code: 'declare const value: string | number; const el = <Text>{value}</Text>;',
205
+ filename,
206
+ errors: [{ message: ERROR }],
207
+ },
208
+
209
+ // ── Real-world patterns ────────────────────────────────────────────────
210
+ {
211
+ // Numeric literal mixed with JSX text siblings
212
+ code: 'const el = <Text>Total: {1234567}</Text>;',
213
+ filename,
214
+ errors: [{ message: ERROR }],
215
+ },
216
+ {
217
+ // Multiple violations in the same element
218
+ code: 'declare const a: number; declare const b: number; const el = <Text>{a}{b}</Text>;',
219
+ filename,
220
+ errors: [{ message: ERROR }, { message: ERROR }],
221
+ },
222
+ {
223
+ // Nested expressions
224
+ code: 'declare const a: number; declare const b: number; const el = <Text><span>{a}</span></Text>;',
225
+ filename,
226
+ errors: [{ message: ERROR }],
227
+ },
228
+ ],
229
+ });
230
+
231
+ // RuleTester.run() throws if any case fails, so reaching this line means all
232
+ // cases passed. Jest needs at least one explicit assertion per test file.
233
+ describe('no-raw-number-in-jsx', () => {
234
+ it('passes all RuleTester cases', () => {
235
+ expect(true).toBe(true);
236
+ });
237
+ });
@@ -0,0 +1,93 @@
1
+ const RESOURCE_NAMES = [
2
+ 'Bucket', 'Object', 'Node', 'Disk', 'Volume', 'Cluster', 'Policy', 'User',
3
+ 'Group', 'Role', 'Workflow', 'Rule', 'Account', 'License', 'Location', 'Alert', 'Certificate'
4
+ ];
5
+ const TARGET_COMPONENTS = ['Button', 'Text', 'Tooltip'];
6
+
7
+ function checkSentenceCase(text) {
8
+ const trimmed = text.trim();
9
+ if (!trimmed) return true;
10
+ const firstLetter = trimmed.charAt(0);
11
+ return firstLetter === firstLetter.toUpperCase();
12
+ }
13
+
14
+ function checkResourceNames(text) {
15
+ for (const resource of RESOURCE_NAMES) {
16
+ const regex = new RegExp(`\\b${resource.toLowerCase()}\\b`);
17
+ const match = text.match(regex);
18
+ if (match) {
19
+ return resource;
20
+ }
21
+ }
22
+ return null;
23
+ }
24
+
25
+ const technicalSentenceCase = {
26
+ meta: {
27
+ type: 'suggestion',
28
+ docs: {
29
+ description: 'Enforce Technical Sentence Case for UI text',
30
+ category: 'Stylistic Issues',
31
+ recommended: false,
32
+ },
33
+ schema: [],
34
+ fixable: 'code',
35
+ },
36
+
37
+ create(context) {
38
+ return {
39
+ JSXElement(node) {
40
+ const name = node.openingElement.name.name;
41
+ if (!TARGET_COMPONENTS.includes(name)) return;
42
+
43
+ // Find the index of the first non-empty JSXText child that appears
44
+ // before any non-text node. Sentence case only applies to that child.
45
+ let firstTextChildIndex = -1;
46
+ for (let i = 0; i < node.children.length; i++) {
47
+ const child = node.children[i];
48
+ if (child.type === 'JSXText' && child.value.trim()) {
49
+ firstTextChildIndex = i;
50
+ break;
51
+ } else if (child.type !== 'JSXText') {
52
+ break; // expression comes first — can't determine sentence start
53
+ }
54
+ }
55
+
56
+ node.children.forEach((child, index) => {
57
+ // Only check for JSXText (e.g., <Button>text</Button>)
58
+ if (child.type === 'JSXText') {
59
+ const text = child.value.trim();
60
+ if (!text) return;
61
+
62
+ // Compose the fixed text for both fixes if needed
63
+ let fixedText = text;
64
+
65
+ // Sentence case fix — only on the first visible text fragment
66
+ if (index === firstTextChildIndex && !checkSentenceCase(text)) {
67
+ fixedText = fixedText.charAt(0).toUpperCase() + fixedText.slice(1);
68
+ context.report({
69
+ node: child,
70
+ message: `Text in <${name}> should start with a capital letter (sentence case).`,
71
+ fix: fixer => fixer.replaceText(child, ' ' + fixedText + ' ')
72
+ });
73
+ }
74
+
75
+ // Resource name fix
76
+ const wrongResource = checkResourceNames(text);
77
+ if (wrongResource) {
78
+ const regex = new RegExp(`\\b${wrongResource.toLowerCase()}\\b`, 'g');
79
+ fixedText = fixedText.replace(regex, wrongResource);
80
+ context.report({
81
+ node: child,
82
+ message: `Resource name "${wrongResource}" in <${name}> must be capitalized.`,
83
+ fix: fixer => fixer.replaceText(child, fixedText)
84
+ });
85
+ }
86
+ }
87
+ });
88
+ }
89
+ };
90
+ }
91
+ };
92
+
93
+ export default technicalSentenceCase;
@@ -0,0 +1,167 @@
1
+ import { RuleTester } from 'eslint';
2
+ import rule from './technical-sentence-case.js';
3
+ import * as tsParser from '@typescript-eslint/parser';
4
+
5
+ const tester = new RuleTester({
6
+ parser: tsParser,
7
+ parserOptions: {
8
+ ecmaFeatures: { jsx: true },
9
+ ecmaVersion: 2020,
10
+ },
11
+ });
12
+
13
+ tester.run('technical-sentence-case', rule, {
14
+ // ─── Valid ────────────────────────────────────────────────────────────────
15
+ valid: [
16
+ // Non-targeted components are never checked
17
+ { code: '<div>hello world</div>' },
18
+ { code: '<span>bucket deleted</span>' },
19
+ { code: '<label>some text</label>' },
20
+
21
+ // Correctly capitalised plain text
22
+ { code: '<Button>Save</Button>' },
23
+ { code: '<Button>Delete item</Button>' },
24
+ { code: '<Text>Welcome back</Text>' },
25
+ { code: '<Tooltip>Click here to continue</Tooltip>' },
26
+
27
+ // Correctly capitalised resource names
28
+ { code: '<Button>Delete Bucket</Button>' },
29
+ { code: '<Text>Node is unreachable</Text>' },
30
+ { code: '<Tooltip>View Cluster details</Tooltip>' },
31
+ { code: '<Button>Assign Role</Button>' },
32
+ { code: '<Text>Edit Policy</Text>' },
33
+
34
+ // Empty / whitespace-only children are ignored
35
+ { code: '<Button> </Button>' },
36
+ { code: '<Text>{variable}</Text>' },
37
+ { code: `<Text>Text with jsx text {"in the middle"} of it</Text>` },
38
+ ],
39
+
40
+ // ─── Invalid ──────────────────────────────────────────────────────────────
41
+ invalid: [
42
+ // ── Sentence case violations ──────────────────────────────────────────
43
+
44
+ {
45
+ code: '<Button>save</Button>',
46
+ errors: [
47
+ {
48
+ message: 'Text in <Button> should start with a capital letter (sentence case).',
49
+ },
50
+ ],
51
+ output: '<Button> Save </Button>',
52
+ },
53
+ {
54
+ code: '<Text>delete item</Text>',
55
+ errors: [
56
+ {
57
+ message: 'Text in <Text> should start with a capital letter (sentence case).',
58
+ },
59
+ ],
60
+ output: '<Text> Delete item </Text>',
61
+ },
62
+ {
63
+ code: '<Tooltip>click here to continue</Tooltip>',
64
+ errors: [
65
+ {
66
+ message:
67
+ 'Text in <Tooltip> should start with a capital letter (sentence case).',
68
+ },
69
+ ],
70
+ output: '<Tooltip> Click here to continue </Tooltip>',
71
+ },
72
+
73
+ // ── Resource name violations ───────────────────────────────────────────
74
+
75
+ {
76
+ code: '<Button>Delete bucket</Button>',
77
+ errors: [
78
+ {
79
+ message: 'Resource name "Bucket" in <Button> must be capitalized.',
80
+ },
81
+ ],
82
+ output: '<Button>Delete Bucket</Button>',
83
+ },
84
+ {
85
+ code: '<Text>node is unreachable</Text>',
86
+ errors: [
87
+ // sentence case violation reported first
88
+ {
89
+ message: 'Text in <Text> should start with a capital letter (sentence case).',
90
+ },
91
+ // resource name violation reported second
92
+ {
93
+ message: 'Resource name "Node" in <Text> must be capitalized.',
94
+ },
95
+ ],
96
+ // ESLint applies only the first fix in a single pass; the sentence-case
97
+ // fixer fires first and capitalises the leading "n" → "Node" is then correct,
98
+ // but we verify here that the sentence-case fix alone is consistent.
99
+ output: '<Text> Node is unreachable </Text>',
100
+ },
101
+ {
102
+ code: '<Button>Edit policy</Button>',
103
+ errors: [
104
+ {
105
+ message: 'Resource name "Policy" in <Button> must be capitalized.',
106
+ },
107
+ ],
108
+ output: '<Button>Edit Policy</Button>',
109
+ },
110
+ {
111
+ code: '<Tooltip>View cluster details</Tooltip>',
112
+ errors: [
113
+ {
114
+ message: 'Resource name "Cluster" in <Tooltip> must be capitalized.',
115
+ },
116
+ ],
117
+ output: '<Tooltip>View Cluster details</Tooltip>',
118
+ },
119
+ {
120
+ // checkResourceNames returns on the first match found in RESOURCE_NAMES order.
121
+ // RESOURCE_NAMES = [..., 'User' (index 7), ..., 'Role' (index 9), ...]
122
+ // → only "User" is reported; "Role" is shadowed by the early return.
123
+ code: '<Text>assign role to user</Text>',
124
+ errors: [
125
+ {
126
+ message: 'Text in <Text> should start with a capital letter (sentence case).',
127
+ },
128
+ {
129
+ message: 'Resource name "User" in <Text> must be capitalized.',
130
+ },
131
+ ],
132
+ // sentence-case fixer fires first; only one fix is applied per pass
133
+ output: '<Text> Assign role to user </Text>',
134
+ },
135
+
136
+ // ── All targeted components are checked ───────────────────────────────
137
+
138
+ {
139
+ code: '<Text>welcome</Text>',
140
+ errors: [
141
+ {
142
+ message: 'Text in <Text> should start with a capital letter (sentence case).',
143
+ },
144
+ ],
145
+ output: '<Text> Welcome </Text>',
146
+ },
147
+ {
148
+ code: '<Tooltip>hover for more info</Tooltip>',
149
+ errors: [
150
+ {
151
+ message:
152
+ 'Text in <Tooltip> should start with a capital letter (sentence case).',
153
+ },
154
+ ],
155
+ output: '<Tooltip> Hover for more info </Tooltip>',
156
+ },
157
+ ],
158
+ });
159
+
160
+ // RuleTester.run() throws if any case fails, so reaching this line means all
161
+ // cases passed. Jest needs at least one explicit assertion per test file.
162
+ describe('technical-sentence-case', () => {
163
+ it('passes all RuleTester cases', () => {
164
+ // Execution of tester.run() above already validated everything.
165
+ expect(true).toBe(true);
166
+ });
167
+ });
@@ -0,0 +1,135 @@
1
+ import { useState } from 'react';
2
+ import { Button } from '../src/lib/components/buttonv2/Buttonv2.component';
3
+ import { Drawer } from '../src/lib/components/drawer/Drawer.component';
4
+ import { Wrapper } from './common';
5
+
6
+ export default {
7
+ title: 'Components/Feedback/Drawer',
8
+ component: Drawer,
9
+ decorators: [
10
+ (story) => <Wrapper style={{ minHeight: '30vh' }}>{story()}</Wrapper>,
11
+ ],
12
+ argTypes: {
13
+ position: {
14
+ control: 'select',
15
+ options: ['left', 'right', 'top', 'bottom'],
16
+ },
17
+ size: { control: 'text' },
18
+ overlay: { control: 'boolean' },
19
+ showCloseButton: { control: 'boolean' },
20
+ },
21
+ };
22
+
23
+ export const Default = {
24
+ render: (args) => {
25
+ const [isOpen, setIsOpen] = useState(false);
26
+ return (
27
+ <>
28
+ <Button onClick={() => setIsOpen(true)} label="Open Drawer" />
29
+ <Drawer {...args} isOpen={isOpen} close={() => setIsOpen(false)} />
30
+ </>
31
+ );
32
+ },
33
+ args: {
34
+ title: 'Drawer Title',
35
+ position: 'left',
36
+ size: '400px',
37
+ overlay: false,
38
+ children: (
39
+ <div>
40
+ <p>This is the drawer content.</p>
41
+ <p>The app remains visible and interactive behind the drawer.</p>
42
+ </div>
43
+ ),
44
+ },
45
+ };
46
+
47
+ export const WithFooter = {
48
+ render: (args) => {
49
+ const [isOpen, setIsOpen] = useState(false);
50
+ return (
51
+ <>
52
+ <Button onClick={() => setIsOpen(true)} label="Open Drawer" />
53
+ <Drawer
54
+ {...args}
55
+ isOpen={isOpen}
56
+ close={() => setIsOpen(false)}
57
+ footer={
58
+ <>
59
+ <Button
60
+ label="Cancel"
61
+ variant="outline"
62
+ onClick={() => setIsOpen(false)}
63
+ style={{ minWidth: '6rem' }}
64
+ />
65
+ <Button
66
+ label="Save"
67
+ variant="primary"
68
+ onClick={() => setIsOpen(false)}
69
+ style={{ minWidth: '6rem' }}
70
+ />
71
+ </>
72
+ }
73
+ />
74
+ </>
75
+ );
76
+ },
77
+ args: {
78
+ title: 'Settings',
79
+ position: 'left',
80
+ size: '400px',
81
+ overlay: false,
82
+ children: (
83
+ <div>
84
+ <p>Drawer with footer actions.</p>
85
+ <p>Use footer for save, cancel, or reset buttons.</p>
86
+ </div>
87
+ ),
88
+ },
89
+ };
90
+
91
+ export const WithOverlay = {
92
+ render: (args) => {
93
+ const [isOpen, setIsOpen] = useState(false);
94
+ return (
95
+ <>
96
+ <Button
97
+ onClick={() => setIsOpen(true)}
98
+ label="Open Drawer with Overlay"
99
+ />
100
+ <Drawer {...args} isOpen={isOpen} close={() => setIsOpen(false)} />
101
+ </>
102
+ );
103
+ },
104
+ args: {
105
+ title: 'Overlay Drawer',
106
+ position: 'left',
107
+ size: '400px',
108
+ overlay: true,
109
+ children: (
110
+ <div>
111
+ <p>This drawer has a backdrop overlay.</p>
112
+ <p>Click the overlay or press Escape to close.</p>
113
+ </div>
114
+ ),
115
+ },
116
+ };
117
+
118
+ export const RightPosition = {
119
+ render: (args) => {
120
+ const [isOpen, setIsOpen] = useState(false);
121
+ return (
122
+ <>
123
+ <Button onClick={() => setIsOpen(true)} label="Open Right Drawer" />
124
+ <Drawer {...args} isOpen={isOpen} close={() => setIsOpen(false)} />
125
+ </>
126
+ );
127
+ },
128
+ args: {
129
+ title: 'Right Drawer',
130
+ position: 'right',
131
+ size: '350px',
132
+ overlay: false,
133
+ children: <p>Drawer sliding in from the right.</p>,
134
+ },
135
+ };