@scality/core-ui 0.209.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.209.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",
@@ -1,16 +1,38 @@
1
1
  import ts from 'typescript';
2
2
 
3
3
  /**
4
- * Returns true when the TypeScript type or any member of a union — is a
5
- * numeric type (number primitive or a number literal type such as `42`).
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 typeIncludesNumber(type) {
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
- // Handle union types: `number | undefined`, `number | null`, `string | number`, …
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
- return type.types.some(t => typeIncludesNumber(t));
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 (typeIncludesNumber(type)) {
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
- // ── Union types that include number ───────────────────────────────────
217
+ // ── Pure number types with nullish companions ─────────────────────────
196
218
  {
197
- // number | undefined — when defined it would render an unformatted number
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
- // string | number
204
- code: 'declare const value: string | number; const el = <Text>{value}</Text>;',
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 are used to trigger an action or event when activated.
19
+ Buttons trigger an action or state change.
20
20
 
21
- ## Size & style
21
+ ## Hierarchy & grouping
22
22
 
23
- ### Style
23
+ **One primary button per view or modal.** A single `variant="primary"` defines the intended action at any given moment.
24
24
 
25
- - Height: 2rem
26
- - Border-radius: 3px
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
- ### Padding
28
+ Align button groups with the container's right side by default; adjust only when visual balance requires it.
30
29
 
31
- Button’s label measurement:
30
+ ### Wizard / multi-step flows
32
31
 
33
- - Line height: 1.25rem
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
- ### Button spacing
34
+ Never use "Next". It is not an action verb.
39
35
 
40
- - Horizontal spacing: 1rem
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 should be as short and clear as possible and should describe the action the button performs.\
40
+ Button labels must be short, clear, and describe the action performed.
47
41
 
48
- - Use one or two words if possible,
49
- - Remove most prepositions and articles (a, an, the).
50
- - Examples: Cancel, Close, Create, Delete, Edit, Learn More, Review, Save, Study, View Scores, etc.,
51
- - Stick to using verbs (Complete, Start, Finish, Search) or a simple verb + noun combination for buttons (Next page, Submit post, Learn more),
52
- - Capitalize the noun after the verb (Create Folder, Create Node, Edit Location).
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
- ## Variations
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
- The button can be modified in different way to match its usage.
53
+ ## Variations
60
54
 
61
55
  ### Variant
62
56
 
63
- Buttons have a set of predefined variants to use in different context.
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 should only appear once within a group of buttons (or even a screen).
71
- For example, Continue in a form.
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 for a secondary action within a group of buttons.
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 for low-emphasis action, as an alternative to Primary or Secondary buttons.
84
- It's suitable for dismissive action, such as the "Cancel" button.
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 action, typically delete actions.
91
- Actions triggered by such button often require additional validation, as the critical action cannot be undone.
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
- ### Links
105
+ ### Button vs Link
96
106
 
97
- The Button component can be used as a link to another element of the UI.
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
- <Canvas layout="fullscreen" of={ButtonStories.LinkButton} />
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 for the button component : default and inline.
118
+ Only 2 sizes are available: default and inline.
105
119
 
106
- Inline size is use for constraint spaces, like in tables or in key/values sections.
107
- On all others cases, use the default size.
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
- Clicks on the button should trigger no action, and the cursor should display a "not-allowed" indicator. \
116
- A tooltip explaining the reason for the button’s disabled state should appear on hover.
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 State
134
+ #### Loading state
121
135
 
122
- Display a spinner animation within the button. \
123
- Disable any interactions during the loading process.
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
- Every button gets a label.\
145
- Icons are optional and mainly used to show how buttons are different, to aid memory and differentiation.\
146
- Universally understood icons work well (ie. print, close, play/pause, save).\
147
- Use standard icons when their use matches their meaning, or at least the user's intent.\
148
- Avoid creating new icons for every action, especially infrequently used ones.\
149
- Avoid using an icon alone in a button. If no label is provided, then use a tooltip.
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
- Icons are to be positioned only on the left side of the text.\
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.IconButtonWithTooltip}
181
+ of={ButtonStories.GhostButtons}
167
182
  sourceState="none"
168
183
  />
169
184
 
170
185
  ### Accessibility
171
186
 
172
- - Icons in Button Design: \
173
- Easily recognizable icons help with the quick recognition of a button’s function. If the icon is not clear enough, it loses its purpose, so in such cases, avoid using icons.
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.GhostButtons}
192
+ of={ButtonStories.IconButtonWithTooltip}
192
193
  sourceState="none"
193
194
  />
194
195
 
195
- ### Playground
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