@scality/core-ui 0.208.0 → 0.210.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/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@scality/core-ui",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.210.0",
|
|
4
4
|
"description": "Scality common React component library",
|
|
5
5
|
"author": "Scality Engineering",
|
|
6
6
|
"license": "SEE LICENSE IN LICENSE",
|
|
@@ -15,7 +15,11 @@
|
|
|
15
15
|
},
|
|
16
16
|
"./eslint-plugin": "./src/lib/valalint/index.mjs",
|
|
17
17
|
"./dist/*.css": "./dist/*.css",
|
|
18
|
-
"./dist/*":
|
|
18
|
+
"./dist/*": {
|
|
19
|
+
"types": "./dist/*.d.ts",
|
|
20
|
+
"import": "./dist/*.js",
|
|
21
|
+
"default": "./dist/*.js"
|
|
22
|
+
}
|
|
19
23
|
},
|
|
20
24
|
"sideEffects": false,
|
|
21
25
|
"peerDependencies": {
|
|
@@ -1,16 +1,38 @@
|
|
|
1
1
|
import ts from 'typescript';
|
|
2
2
|
|
|
3
3
|
/**
|
|
4
|
-
* Returns true when the TypeScript type
|
|
5
|
-
*
|
|
4
|
+
* Returns true when the TypeScript type is a numeric type (number primitive or
|
|
5
|
+
* a number literal type such as `42`), or a union where number is the only
|
|
6
|
+
* non-nullish type (e.g., `number | undefined`, `number | null`).
|
|
7
|
+
*
|
|
8
|
+
* Does NOT match broad types like ReactNode where number is just one of many
|
|
9
|
+
* non-nullish options.
|
|
6
10
|
*/
|
|
7
|
-
function
|
|
11
|
+
function isPureNumberType(type) {
|
|
8
12
|
if (type.flags & ts.TypeFlags.Number) return true;
|
|
9
13
|
if (type.flags & ts.TypeFlags.NumberLiteral) return true;
|
|
10
|
-
|
|
14
|
+
|
|
15
|
+
// Handle union types: only flag if number is the primary type
|
|
16
|
+
// (i.e., the only non-nullish member)
|
|
11
17
|
if (type.flags & ts.TypeFlags.Union) {
|
|
12
|
-
|
|
18
|
+
let hasNumber = false;
|
|
19
|
+
let hasOtherNonNullishType = false;
|
|
20
|
+
|
|
21
|
+
for (const t of type.types) {
|
|
22
|
+
// Check if this member is a number
|
|
23
|
+
if (t.flags & ts.TypeFlags.Number || t.flags & ts.TypeFlags.NumberLiteral) {
|
|
24
|
+
hasNumber = true;
|
|
25
|
+
}
|
|
26
|
+
// Check if this member is a non-nullish, non-number type
|
|
27
|
+
else if (!(t.flags & (ts.TypeFlags.Null | ts.TypeFlags.Undefined | ts.TypeFlags.Void))) {
|
|
28
|
+
hasOtherNonNullishType = true;
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
// Only flag if we have number and no other non-nullish types
|
|
33
|
+
return hasNumber && !hasOtherNonNullishType;
|
|
13
34
|
}
|
|
35
|
+
|
|
14
36
|
return false;
|
|
15
37
|
}
|
|
16
38
|
|
|
@@ -53,7 +75,7 @@ const noRawNumberInJsx = {
|
|
|
53
75
|
const tsNode = services.esTreeNodeToTSNodeMap.get(expression);
|
|
54
76
|
const type = checker.getTypeAtLocation(tsNode);
|
|
55
77
|
|
|
56
|
-
if (
|
|
78
|
+
if (isPureNumberType(type)) {
|
|
57
79
|
context.report({ node, messageId: 'rawNumber' });
|
|
58
80
|
}
|
|
59
81
|
},
|
|
@@ -118,6 +118,28 @@ tester.run('no-raw-number-in-jsx', rule, {
|
|
|
118
118
|
code: 'const el = <Text>{/* comment */}</Text>;',
|
|
119
119
|
filename,
|
|
120
120
|
},
|
|
121
|
+
|
|
122
|
+
// ── Broad union types containing number — not flagged ─────────────────
|
|
123
|
+
{
|
|
124
|
+
// string | number — broad union, not a pure number type
|
|
125
|
+
code: 'declare const value: string | number; const el = <Text>{value}</Text>;',
|
|
126
|
+
filename,
|
|
127
|
+
},
|
|
128
|
+
{
|
|
129
|
+
// boolean | number — broad union
|
|
130
|
+
code: 'declare const value: boolean | number; const el = <Text>{value}</Text>;',
|
|
131
|
+
filename,
|
|
132
|
+
},
|
|
133
|
+
{
|
|
134
|
+
// ReactNode type — includes number but is not a pure number type
|
|
135
|
+
code: 'import type { ReactNode } from "react"; declare const content: ReactNode; const el = <Text>{content}</Text>;',
|
|
136
|
+
filename,
|
|
137
|
+
},
|
|
138
|
+
{
|
|
139
|
+
// string | number | null — multiple non-nullish types
|
|
140
|
+
code: 'declare const value: string | number | null; const el = <Text>{value}</Text>;',
|
|
141
|
+
filename,
|
|
142
|
+
},
|
|
121
143
|
],
|
|
122
144
|
|
|
123
145
|
// ─── Invalid ──────────────────────────────────────────────────────────────
|
|
@@ -192,16 +214,22 @@ tester.run('no-raw-number-in-jsx', rule, {
|
|
|
192
214
|
errors: [{ message: ERROR }],
|
|
193
215
|
},
|
|
194
216
|
|
|
195
|
-
// ──
|
|
217
|
+
// ── Pure number types with nullish companions ─────────────────────────
|
|
196
218
|
{
|
|
197
|
-
// number | undefined —
|
|
219
|
+
// number | undefined — pure number type, only null/undefined companions
|
|
198
220
|
code: 'declare const count: number | undefined; const el = <Text>{count}</Text>;',
|
|
199
221
|
filename,
|
|
200
222
|
errors: [{ message: ERROR }],
|
|
201
223
|
},
|
|
202
224
|
{
|
|
203
|
-
//
|
|
204
|
-
code: 'declare const
|
|
225
|
+
// number | null — pure number type with null
|
|
226
|
+
code: 'declare const count: number | null; const el = <Text>{count}</Text>;',
|
|
227
|
+
filename,
|
|
228
|
+
errors: [{ message: ERROR }],
|
|
229
|
+
},
|
|
230
|
+
{
|
|
231
|
+
// number | null | undefined — pure number type with multiple nullish
|
|
232
|
+
code: 'declare const count: number | null | undefined; const el = <Text>{count}</Text>;',
|
|
205
233
|
filename,
|
|
206
234
|
errors: [{ message: ERROR }],
|
|
207
235
|
},
|
|
@@ -16,95 +16,109 @@ import * as ButtonStories from './button.stories';
|
|
|
16
16
|
|
|
17
17
|
# Button
|
|
18
18
|
|
|
19
|
-
Buttons
|
|
19
|
+
Buttons trigger an action or state change.
|
|
20
20
|
|
|
21
|
-
##
|
|
21
|
+
## Hierarchy & grouping
|
|
22
22
|
|
|
23
|
-
|
|
23
|
+
**One primary button per view or modal.** A single `variant="primary"` defines the intended action at any given moment.
|
|
24
24
|
|
|
25
|
-
-
|
|
26
|
-
-
|
|
27
|
-
- Minimum width: 5rem
|
|
25
|
+
- Primary button: far right of the group.
|
|
26
|
+
- Dismissive actions (Cancel, Go back): `variant="outline"`, placed on the left.
|
|
28
27
|
|
|
29
|
-
|
|
28
|
+
Align button groups with the container's right side by default; adjust only when visual balance requires it.
|
|
30
29
|
|
|
31
|
-
|
|
30
|
+
### Wizard / multi-step flows
|
|
32
31
|
|
|
33
|
-
-
|
|
34
|
-
- Vertical padding: 0.5rem
|
|
35
|
-
- Horizontal padding of 1rem
|
|
36
|
-
- Space between icon & label: 0.5rem
|
|
32
|
+
In multi-step flows, group all navigation buttons on the right. "Continue" (or the equivalent forward action) is always `primary`. "Back" sits to its left as `outline`.
|
|
37
33
|
|
|
38
|
-
|
|
34
|
+
Never use "Next". It is not an action verb.
|
|
39
35
|
|
|
40
|
-
|
|
41
|
-
- Vertical spacing: 0.75rem
|
|
42
|
-
- Space between 2 buttons: 1.5rem - when there is no space constraint
|
|
36
|
+
<Canvas layout="fullscreen" of={ButtonStories.SimpleForm} sourceState="none" />
|
|
43
37
|
|
|
44
38
|
## Usage
|
|
45
39
|
|
|
46
|
-
Button labels
|
|
40
|
+
Button labels must be short, clear, and describe the action performed.
|
|
47
41
|
|
|
48
|
-
|
|
49
|
-
-
|
|
50
|
-
-
|
|
51
|
-
-
|
|
52
|
-
-
|
|
53
|
-
- Maintain labelling method consistency across all of your buttons,
|
|
54
|
-
- Don't use punctuation or exclamation marks (!),
|
|
55
|
-
- And, most importantly, don't write "Click here".
|
|
42
|
+
**✅ Do**
|
|
43
|
+
- Use an action verb + noun: "Save", "Create project", "Delete"
|
|
44
|
+
- Prefer 1–2 words; 3 words maximum. A single verb ("Save", "Add", "Delete") is acceptable. Pairing it with a resource name ("Save settings", "Add node", "Delete volume") is preferred for clarity.
|
|
45
|
+
- Sentence case but capitalize resource names: "Create Folder", "Edit Node"
|
|
46
|
+
- Use a `Link` component instead when the action is purely navigational (no mutation, no side effect).
|
|
56
47
|
|
|
57
|
-
|
|
48
|
+
**❌ Don't**
|
|
49
|
+
- Use a noun alone: "Confirmation", "Settings"
|
|
50
|
+
- Add punctuation or exclamation marks
|
|
51
|
+
- Write "Click here" or generic labels like "Yes", "OK"
|
|
58
52
|
|
|
59
|
-
|
|
53
|
+
## Variations
|
|
60
54
|
|
|
61
55
|
### Variant
|
|
62
56
|
|
|
63
|
-
|
|
57
|
+
Use this table to choose the right variant:
|
|
58
|
+
|
|
59
|
+
| Situation | Variant |
|
|
60
|
+
|---|---|
|
|
61
|
+
| Main / intended action on the page or in a modal | `primary` |
|
|
62
|
+
| Irreversible or destructive action (delete, reset) | `danger` |
|
|
63
|
+
| Action that must be accessible but must not attract attention (dismissal, low-priority utility) | `outline` |
|
|
64
|
+
| Secondary action alongside a primary | `secondary` |
|
|
65
|
+
| Low-emphasis utility in a constrained space (table, key-value list) | ghost (no variant, icon only) |
|
|
64
66
|
|
|
65
67
|
<Canvas layout="fullscreen" of={ButtonStories.DefaultButtons} />
|
|
66
68
|
<br />
|
|
67
69
|
|
|
68
70
|
#### Primary
|
|
69
71
|
|
|
70
|
-
Used for the main action. It
|
|
71
|
-
For example,
|
|
72
|
+
Used for the main action. It must appear only once within a group of buttons or a screen.
|
|
73
|
+
For example, "Continue" in a form.
|
|
72
74
|
|
|
73
75
|
<Canvas of={ButtonStories.Primary} layout="fullscreen" />
|
|
74
76
|
|
|
75
77
|
#### Secondary
|
|
76
78
|
|
|
77
|
-
Used
|
|
79
|
+
Used when two constructive actions coexist and both advance the flow, but one is more important.
|
|
80
|
+
|
|
81
|
+
**Example:** "Save draft" (secondary) + "Publish" (primary). Both move the user forward, but publishing is the intended outcome.
|
|
78
82
|
|
|
79
83
|
<Canvas layout="fullscreen" of={ButtonStories.Secondary} />
|
|
80
84
|
|
|
81
85
|
#### Outline
|
|
82
86
|
|
|
83
|
-
Used
|
|
84
|
-
|
|
87
|
+
Used when an action must be accessible without attracting attention. It signals "available, but not the priority."
|
|
88
|
+
|
|
89
|
+
Two typical cases:
|
|
90
|
+
- **Dismissal / exit:** Cancel, Go back, Close. Any action that steps back from the current flow.
|
|
91
|
+
- **Low-priority utility:** Export, Download, Preview, Copy link. Actions that are useful but must not compete visually with the primary action.
|
|
92
|
+
|
|
93
|
+
Prefer Outline over Secondary when the action doesn't advance the main flow.
|
|
85
94
|
|
|
86
95
|
<Canvas layout="fullscreen" of={ButtonStories.Outline} />
|
|
87
96
|
|
|
88
97
|
#### Danger
|
|
89
98
|
|
|
90
|
-
Used for critical
|
|
91
|
-
|
|
99
|
+
Used for critical, irreversible actions (typically delete). When the main action is destructive, use `danger` instead of `primary`. Never use them alongside each other. In a destructive confirmation context, `danger` takes the primary position (right side of the group).
|
|
100
|
+
|
|
101
|
+
Destructive confirmation is always handled through a modal or an explicit checkbox. Never use a double-click pattern on the button itself.
|
|
92
102
|
|
|
93
103
|
<Canvas layout="fullscreen" of={ButtonStories.Danger} />
|
|
94
104
|
|
|
95
|
-
###
|
|
105
|
+
### Button vs Link
|
|
96
106
|
|
|
97
|
-
|
|
98
|
-
When the link send to another site, use the "External-link-alt" icon.
|
|
107
|
+
Use a `Link` component for any navigation, internal or external. `Button` is reserved for actions that trigger a mutation or a state change.
|
|
99
108
|
|
|
100
|
-
|
|
109
|
+
| Use case | Component |
|
|
110
|
+
|---|---|
|
|
111
|
+
| Navigate to another page or URL | `Link` |
|
|
112
|
+
| Open an external site | `Link` with an external indicator |
|
|
113
|
+
| Trigger a mutation, form submission, or state change | `Button` |
|
|
114
|
+
| Open a modal, panel, or dialog | `Button` |
|
|
101
115
|
|
|
102
116
|
### Size
|
|
103
117
|
|
|
104
|
-
Only 2 sizes are available
|
|
118
|
+
Only 2 sizes are available: default and inline.
|
|
105
119
|
|
|
106
|
-
Inline
|
|
107
|
-
|
|
120
|
+
Inline is used for constrained spaces like tables or key/value sections.
|
|
121
|
+
In all other cases, use default.
|
|
108
122
|
|
|
109
123
|
<Canvas layout="fullscreen" of={ButtonStories.ButtonSizes} />
|
|
110
124
|
|
|
@@ -112,48 +126,36 @@ On all others cases, use the default size.
|
|
|
112
126
|
|
|
113
127
|
#### Disabled state
|
|
114
128
|
|
|
115
|
-
|
|
116
|
-
A tooltip
|
|
129
|
+
The button triggers no action and shows a not-allowed cursor.
|
|
130
|
+
A tooltip **must** appear on hover explaining why the button is disabled. Exception: when the reason is self-evident, such as a form with required fields not yet filled.
|
|
117
131
|
|
|
118
132
|
<Canvas layout="fullscreen" of={ButtonStories.ButtonDisabled} />
|
|
119
133
|
|
|
120
|
-
#### Loading
|
|
134
|
+
#### Loading state
|
|
121
135
|
|
|
122
|
-
|
|
123
|
-
|
|
136
|
+
Displays a spinner within the button and disables all interactions.
|
|
137
|
+
Use for async operations (API calls, form submissions) where the result is not immediate.
|
|
138
|
+
The button label changes to the gerund form with ellipsis: "Save" becomes "Saving…", "Delete" becomes "Deleting…".
|
|
124
139
|
|
|
125
140
|
<Canvas
|
|
126
141
|
layout="fullscreen"
|
|
127
142
|
of={ButtonStories.ButtonLoading}
|
|
128
|
-
|
|
129
143
|
/>
|
|
130
144
|
|
|
131
|
-
## Group of Button
|
|
132
|
-
|
|
133
|
-
- Primary buttons placed on the right of a group.
|
|
134
|
-
- Dismissive actions placed on the left. It allows the user to return to the previous screen or step in the process.
|
|
135
|
-
|
|
136
|
-
Align button groups with the container's right side by default; however, based on the use case and visual balance it can be aligned differently.
|
|
137
|
-
|
|
138
|
-
<Canvas layout="fullscreen" of={ButtonStories.SimpleForm} sourceState="none" />
|
|
139
|
-
|
|
140
145
|
## Icon on buttons
|
|
141
146
|
|
|
142
147
|
### Usage
|
|
143
148
|
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
Use standard icons when their
|
|
148
|
-
Avoid creating new icons for
|
|
149
|
-
|
|
149
|
+
- Icons are optional. Use them to aid recognition and differentiation between actions.
|
|
150
|
+
- An icon-only button (no label) is acceptable for universally understood actions such as edit, copy, delete, or refresh. For less obvious actions, prefer pairing the icon with a label.
|
|
151
|
+
- Prefer universally understood icons (print, close, play/pause, save).
|
|
152
|
+
- Use standard icons only when their meaning matches the action.
|
|
153
|
+
- Avoid creating new icons for infrequently used actions.
|
|
154
|
+
- If no label is provided, a tooltip is required.
|
|
150
155
|
|
|
151
156
|
### Placement
|
|
152
157
|
|
|
153
|
-
|
|
154
|
-
Icons should be vertically center-aligned with the text.\
|
|
155
|
-
The icon should be (approximately) the same height as the text.\
|
|
156
|
-
Seeing the icon first help users to scan the page more easily, except for few cases such as navigation (right arrow).
|
|
158
|
+
Seeing the icon first helps users scan the page more easily, except for a few cases such as navigation (right arrow).
|
|
157
159
|
|
|
158
160
|
<Canvas
|
|
159
161
|
layout="fullscreen"
|
|
@@ -161,38 +163,43 @@ Seeing the icon first help users to scan the page more easily, except for few ca
|
|
|
161
163
|
sourceState="none"
|
|
162
164
|
/>
|
|
163
165
|
|
|
166
|
+
### Ghost buttons (icon-only)
|
|
167
|
+
|
|
168
|
+
Ghost buttons are low-emphasis, icon-only buttons without a `variant`. They are effective in space-limited areas like tables or key-value lists.
|
|
169
|
+
|
|
170
|
+
**✅ Do**
|
|
171
|
+
- Always include a tooltip with a verb phrase: "Refresh metrics", "Export data"
|
|
172
|
+
- Limit ghost buttons to common, universally understood actions
|
|
173
|
+
|
|
174
|
+
**❌ Don't**
|
|
175
|
+
- Use a standalone noun as tooltip text ("Refresh", "Export"). It must describe the action.
|
|
176
|
+
- Use them instead of Outline buttons when the action needs to be clearly recognized as a button
|
|
177
|
+
- Create too many different ghost button instances in the same context
|
|
178
|
+
|
|
164
179
|
<Canvas
|
|
165
180
|
layout="fullscreen"
|
|
166
|
-
of={ButtonStories.
|
|
181
|
+
of={ButtonStories.GhostButtons}
|
|
167
182
|
sourceState="none"
|
|
168
183
|
/>
|
|
169
184
|
|
|
170
185
|
### Accessibility
|
|
171
186
|
|
|
172
|
-
-
|
|
173
|
-
|
|
174
|
-
- Aria Labels: \
|
|
175
|
-
Providing descriptive labels for screen readers is really helpful, especially for buttons that use icons only.
|
|
176
|
-
|
|
177
|
-
## Ghost Buttons
|
|
178
|
-
|
|
179
|
-
Ghost buttons are subtle and contribute to a minimalist interface design.
|
|
180
|
-
They are low-emphasis, making them ideal for non-primary actions in UIs.
|
|
181
|
-
They're also effective in space-limited areas like tables or key-value lists.
|
|
182
|
-
|
|
183
|
-
- Ensure tooltips are included for clarity
|
|
184
|
-
- Don't confuse them with non-interactive elements like descriptive icons or status indicators
|
|
185
|
-
- Use them for secondary or less crucial actions.
|
|
186
|
-
- Don't opt for them over Outline buttons, which are more visibly recognizable as buttons.
|
|
187
|
-
- Create too many different instances of ghost buttons; limit them to common actions (e.g. link to other UIs).
|
|
187
|
+
- Use icons that are easily recognizable. If the icon is ambiguous, omit it. An unclear icon is worse than no icon.
|
|
188
|
+
- For icon-only buttons, the `tooltip.overlay` text is automatically used as `aria-label`. It must describe the action with a verb.
|
|
188
189
|
|
|
189
190
|
<Canvas
|
|
190
191
|
layout="fullscreen"
|
|
191
|
-
of={ButtonStories.
|
|
192
|
+
of={ButtonStories.IconButtonWithTooltip}
|
|
192
193
|
sourceState="none"
|
|
193
194
|
/>
|
|
194
195
|
|
|
195
|
-
|
|
196
|
+
## Empty states
|
|
197
|
+
|
|
198
|
+
When a view has no content yet, use a single `primary` button as the main call to action. It should be centered, standalone, and paired with an `Icon` component to reinforce the action.
|
|
199
|
+
|
|
200
|
+
Do not place secondary or outline buttons in empty states. The goal is a single, clear entry point.
|
|
201
|
+
|
|
202
|
+
## Playground
|
|
196
203
|
|
|
197
204
|
<Canvas of={ButtonStories.Playground} layout="fullscreen" />
|
|
198
205
|
|