@scality/core-ui 0.203.0 → 0.205.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.
- package/.github/workflows/github-pages.yml +4 -4
- package/.github/workflows/post-release.yml +25 -12
- package/__mocks__/fileMock.js +1 -1
- package/__mocks__/styleMock.js +1 -1
- package/__mocks__/uuid.js +1 -5
- package/dist/components/drawer/Drawer.component.d.ts +17 -0
- package/dist/components/drawer/Drawer.component.d.ts.map +1 -0
- package/dist/components/drawer/Drawer.component.js +132 -0
- package/dist/components/icon/iconTable.d.ts +4 -0
- package/dist/components/icon/iconTable.d.ts.map +1 -1
- package/dist/components/icon/iconTable.js +4 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -0
- package/dist/next.d.ts +1 -0
- package/dist/next.d.ts.map +1 -1
- package/dist/next.js +1 -0
- package/dist/style/theme.d.ts +1 -0
- package/dist/style/theme.d.ts.map +1 -1
- package/dist/style/theme.js +1 -0
- package/global-setup.js +1 -1
- package/jest.config.js +1 -1
- package/package.json +13 -3
- package/src/lib/components/drawer/Drawer.component.test.tsx +108 -0
- package/src/lib/components/drawer/Drawer.component.tsx +207 -0
- package/src/lib/components/icon/iconTable.ts +4 -0
- package/src/lib/index.ts +1 -0
- package/src/lib/next.ts +1 -0
- package/src/lib/style/theme.ts +1 -0
- package/src/lib/valalint/README.md +50 -0
- package/src/lib/valalint/index.js +49 -0
- package/src/lib/valalint/rules/modal-button-forbidden-label.js +87 -0
- package/src/lib/valalint/rules/modal-button-forbidden-label.test.js +157 -0
- package/src/lib/valalint/rules/no-raw-number-in-jsx.js +64 -0
- package/src/lib/valalint/rules/no-raw-number-in-jsx.test.js +237 -0
- package/src/lib/valalint/rules/technical-sentence-case.js +93 -0
- package/src/lib/valalint/rules/technical-sentence-case.test.js +167 -0
- 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
|
+
};
|