@wallarm-org/design-system 1.32.0 → 2.0.0-rc-fix-stepper.1

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.
@@ -23,7 +23,7 @@ export interface CodeEditorRootProps extends CodeEditorRootNativeProps, Testable
23
23
  * document would be seeded with the previous document's text.
24
24
  */
25
25
  documentId?: string;
26
- /** Focusable, selectable, searchable, copyable — but not editable by the user */
26
+ /** Non-editable and unfocusable content; text remains selectable, searchable and copyable */
27
27
  readOnly?: boolean;
28
28
  /** Offsets gutter numbers; `lines`, `folds`, `diagnostics` use absolute numbers */
29
29
  startingLineNumber?: number;
@@ -41,9 +41,20 @@ const SearchPanel = ({ view, query, matchCount, readOnly, testId })=>{
41
41
  ]
42
42
  });
43
43
  };
44
+ const blurReadOnlyContent = ()=>{
45
+ if (view.state.readOnly && view.hasFocus) view.contentDOM.blur();
46
+ };
47
+ const closeSearch = ()=>{
48
+ closeSearchPanel(view);
49
+ blurReadOnlyContent();
50
+ };
44
51
  const handleKeyDown = (event, onEnter)=>{
45
52
  if (event.nativeEvent.isComposing || 229 === event.keyCode) return;
46
- if (runScopeHandlers(view, event.nativeEvent, 'search-panel')) return void event.preventDefault();
53
+ if (runScopeHandlers(view, event.nativeEvent, 'search-panel')) {
54
+ event.preventDefault();
55
+ blurReadOnlyContent();
56
+ return;
57
+ }
47
58
  if ('Enter' === event.key) {
48
59
  event.preventDefault();
49
60
  onEnter(event.shiftKey);
@@ -53,7 +64,10 @@ const SearchPanel = ({ view, query, matchCount, readOnly, testId })=>{
53
64
  const handleReplaceKeyDown = (event)=>handleKeyDown(event, ()=>replaceNext(view));
54
65
  const handlePanelKeyDown = (event)=>{
55
66
  if (event.target instanceof HTMLInputElement) return;
56
- if (runScopeHandlers(view, event.nativeEvent, 'search-panel')) event.preventDefault();
67
+ if (runScopeHandlers(view, event.nativeEvent, 'search-panel')) {
68
+ event.preventDefault();
69
+ blurReadOnlyContent();
70
+ }
57
71
  };
58
72
  const canSearch = query.valid;
59
73
  return /*#__PURE__*/ jsxs("div", {
@@ -154,7 +168,7 @@ const SearchPanel = ({ view, query, matchCount, readOnly, testId })=>{
154
168
  className: "ml-auto",
155
169
  "aria-label": "Close search",
156
170
  "data-testid": slot('search-close'),
157
- onClick: ()=>closeSearchPanel(view),
171
+ onClick: closeSearch,
158
172
  children: /*#__PURE__*/ jsx(X, {})
159
173
  })
160
174
  ]
@@ -4,9 +4,9 @@ import type { CodeEditorCompletionContext, CodeEditorCompletionSource, CodeEdito
4
4
  /** Public, engine-free view of a CodeMirror completion request (spec §4). */
5
5
  export declare const buildCompletionContext: (ctx: CompletionContext, language: CodeEditorLanguage, startingLineNumber: number) => CodeEditorCompletionContext;
6
6
  /**
7
- * Autocomplete (spec §7.14): built-in HTTP source for `http`, the JSON Schema source when
8
- * `schema` is set, then consumer sources, all through `override` (language-data sources are
9
- * off). `completionKeymap` comes with `defaultKeymap: true` at `Prec.highest`, so Enter,
7
+ * Autocomplete: HTTP and JSON Schema sources, consumer sources, then language keywords and
8
+ * document words, all through `override` (language-data sources are off).
9
+ * `completionKeymap` comes with `defaultKeymap: true` at `Prec.highest`, so Enter,
10
10
  * arrows and Escape reach the open list before `indentWithTab` / fullscreen Escape.
11
11
  */
12
12
  export declare const completionExtension: (config: {
@@ -1,4 +1,4 @@
1
- import { autocompletion } from "@codemirror/autocomplete";
1
+ import { autocompletion, completeAnyWord, completeFromList } from "@codemirror/autocomplete";
2
2
  import { EditorView } from "@codemirror/view";
3
3
  import { cn } from "../../../utils/cn.js";
4
4
  import { dropdownMenuItemVariants } from "../../DropdownMenu/classes.js";
@@ -106,13 +106,153 @@ const adaptSource = (source, language, startingLineNumber)=>(ctx)=>{
106
106
  }
107
107
  };
108
108
  const reopensAfter = (completion)=>'string' == typeof completion.apply && completion.apply.endsWith(': ');
109
+ const LANGUAGE_KEYWORDS = {
110
+ json: [
111
+ 'true',
112
+ 'false',
113
+ 'null'
114
+ ],
115
+ yaml: [
116
+ 'true',
117
+ 'false',
118
+ 'null'
119
+ ],
120
+ bash: [
121
+ 'echo',
122
+ 'export',
123
+ 'if',
124
+ 'then',
125
+ 'else',
126
+ 'fi',
127
+ 'for',
128
+ 'in',
129
+ 'do',
130
+ 'done',
131
+ 'function'
132
+ ],
133
+ javascript: [
134
+ 'const',
135
+ 'let',
136
+ 'var',
137
+ 'function',
138
+ 'return',
139
+ 'if',
140
+ 'else',
141
+ 'for',
142
+ 'while',
143
+ 'class',
144
+ 'import',
145
+ 'export',
146
+ 'from',
147
+ 'async',
148
+ 'await',
149
+ 'true',
150
+ 'false',
151
+ 'null',
152
+ 'undefined'
153
+ ],
154
+ typescript: [
155
+ 'const',
156
+ 'let',
157
+ 'function',
158
+ 'return',
159
+ 'if',
160
+ 'else',
161
+ 'class',
162
+ 'import',
163
+ 'export',
164
+ 'from',
165
+ 'async',
166
+ 'await',
167
+ 'interface',
168
+ 'type',
169
+ 'enum',
170
+ 'extends',
171
+ 'implements',
172
+ 'public',
173
+ 'private',
174
+ 'readonly',
175
+ 'true',
176
+ 'false',
177
+ 'null',
178
+ 'undefined'
179
+ ],
180
+ python: [
181
+ 'def',
182
+ 'class',
183
+ 'return',
184
+ 'if',
185
+ 'elif',
186
+ 'else',
187
+ 'for',
188
+ 'while',
189
+ 'in',
190
+ 'import',
191
+ 'from',
192
+ 'as',
193
+ 'async',
194
+ 'await',
195
+ 'try',
196
+ 'except',
197
+ 'with',
198
+ 'True',
199
+ 'False',
200
+ 'None'
201
+ ],
202
+ lua: [
203
+ 'function',
204
+ 'local',
205
+ 'return',
206
+ 'if',
207
+ 'then',
208
+ 'elseif',
209
+ 'else',
210
+ 'end',
211
+ 'for',
212
+ 'while',
213
+ 'do',
214
+ 'repeat',
215
+ 'until',
216
+ 'true',
217
+ 'false',
218
+ 'nil'
219
+ ]
220
+ };
221
+ const isInsideQuotedString = (text)=>{
222
+ let quoted = false;
223
+ for(let i = 0; i < text.length; i++)if ('\\' === text[i]) i++;
224
+ else if ('"' === text[i]) quoted = !quoted;
225
+ return quoted;
226
+ };
227
+ const builtInSources = (language)=>{
228
+ const keywords = LANGUAGE_KEYWORDS[language];
229
+ if (!keywords) return [
230
+ completeAnyWord
231
+ ];
232
+ const completeKeywords = completeFromList(keywords.map((label)=>({
233
+ label,
234
+ type: 'keyword'
235
+ })));
236
+ if ('json' === language) return [
237
+ (ctx)=>{
238
+ const line = ctx.state.doc.lineAt(ctx.pos);
239
+ const before = line.text.slice(0, ctx.pos - line.from);
240
+ if (isInsideQuotedString(before)) return completeAnyWord(ctx);
241
+ return /(?:^|[:,\[])[\s\w]*$/.test(before) ? completeKeywords(ctx) : null;
242
+ }
243
+ ];
244
+ return [
245
+ completeKeywords,
246
+ completeAnyWord
247
+ ];
248
+ };
109
249
  const completionExtension = (config)=>{
110
250
  const { language, schema, sources, startingLineNumber } = config;
111
251
  const override = [];
112
252
  if ('http' === language) override.push(adaptSource(httpCompletions, language, startingLineNumber));
113
253
  if (void 0 !== schema && isJsonLike(language)) override.push(schemaCompletionSource(()=>schema, (state)=>jsonRegion(state, language)));
114
254
  for (const source of sources)override.push(adaptSource(source, language, startingLineNumber));
115
- if (0 === override.length) return [];
255
+ override.push(...builtInSources(language));
116
256
  return [
117
257
  autocompletion({
118
258
  override,
@@ -79,7 +79,8 @@ const SLOT_DEPS = {
79
79
  ],
80
80
  contentAttributes: [
81
81
  'contentAttributes',
82
- 'testId'
82
+ 'testId',
83
+ 'readOnly'
83
84
  ],
84
85
  cspNonce: [
85
86
  'cspNonce'
@@ -184,6 +185,7 @@ const featureExtensions = (options, callbacks)=>({
184
185
  });
185
186
  const contentAttributesFor = (options)=>{
186
187
  const attrs = sanitizeContentAttributes(options.contentAttributes);
188
+ if (options.readOnly) delete attrs.tabindex;
187
189
  if (options.testId) attrs['data-testid'] = `${options.testId}--editor`;
188
190
  return attrs;
189
191
  };
@@ -225,7 +227,10 @@ const gutterAria = [
225
227
  ];
226
228
  const slotExtensions = (options, callbacks)=>({
227
229
  language: ()=>languageExtension(options.language),
228
- readOnly: ()=>EditorState.readOnly.of(options.readOnly),
230
+ readOnly: ()=>[
231
+ EditorState.readOnly.of(options.readOnly),
232
+ EditorView.editable.of(!options.readOnly)
233
+ ],
229
234
  wrap: ()=>options.wrapLines ? EditorView.lineWrapping : [],
230
235
  maxHeight: ()=>maxHeightTheme(options.maxHeight),
231
236
  contentAttributes: ()=>EditorView.contentAttributes.of(contentAttributesFor(options)),
@@ -298,6 +303,10 @@ const createEditor = (parent, initialOptions, callbacks)=>{
298
303
  state: createState(initialOptions.value, initialOptions),
299
304
  parent
300
305
  });
306
+ const preventReadOnlyMouseFocus = (event)=>{
307
+ if (view.state.readOnly && event.target instanceof Node && view.contentDOM.contains(event.target)) event.stopPropagation();
308
+ };
309
+ view.dom.addEventListener('mousedown', preventReadOnlyMouseFocus, true);
301
310
  const ensureLazyLanguage = (lang)=>{
302
311
  if (!isLazyLanguage(lang)) return;
303
312
  loadLanguageExtension(lang).then((extension)=>{
@@ -346,13 +355,20 @@ const createEditor = (parent, initialOptions, callbacks)=>{
346
355
  const update = (next)=>{
347
356
  const prev = options;
348
357
  options = next;
349
- if (next.documentId !== prev.documentId) return void swapDocument(prev, next);
358
+ if (next.documentId !== prev.documentId) {
359
+ swapDocument(prev, next);
360
+ if (next.readOnly && view.hasFocus) view.contentDOM.blur();
361
+ return;
362
+ }
350
363
  reconfigure(prev, next);
364
+ if (next.readOnly && view.hasFocus) view.contentDOM.blur();
351
365
  if (next.language !== prev.language) ensureLazyLanguage(next.language);
352
366
  syncValue(next.value);
353
367
  };
354
368
  const api = {
355
- focus: ()=>view.focus(),
369
+ focus: ()=>{
370
+ if (!view.state.readOnly) view.focus();
371
+ },
356
372
  getValue: ()=>view.state.doc.toString(),
357
373
  insertText: (text)=>{
358
374
  if (view.state.readOnly) return;
@@ -380,6 +396,7 @@ const createEditor = (parent, initialOptions, callbacks)=>{
380
396
  destroy: ()=>{
381
397
  destroyed = true;
382
398
  cache.clear();
399
+ view.dom.removeEventListener('mousedown', preventReadOnlyMouseFocus, true);
383
400
  view.destroy();
384
401
  }
385
402
  };
@@ -81,7 +81,8 @@ const FilterDropdownTrigger = ({ ref, className, children, onKeyDown, 'aria-labe
81
81
  "aria-hidden": true,
82
82
  className: "inline-flex shrink-0",
83
83
  children: /*#__PURE__*/ jsx(NumericBadge, {
84
- type: "primary",
84
+ type: "secondary",
85
+ color: "neutral",
85
86
  children: value.length
86
87
  })
87
88
  })
@@ -1,10 +1,13 @@
1
- import type { FC, HTMLAttributes } from 'react';
1
+ import { type FC, type HTMLAttributes, type Ref } from 'react';
2
2
  import type { VariantProps } from 'class-variance-authority';
3
3
  import type { TestableProps } from '../../utils/testId';
4
4
  import { numericBadgeVariants } from './classes';
5
- type NumericBadgeNativeProps = HTMLAttributes<HTMLDivElement>;
6
- type NumericBadgeVariantsProps = Omit<VariantProps<typeof numericBadgeVariants>, 'clickable'>;
5
+ type NumericBadgeNativeProps = Omit<HTMLAttributes<HTMLDivElement>, 'color'>;
6
+ type NumericBadgeVariantsProps = Omit<VariantProps<typeof numericBadgeVariants>, 'clickable' | 'iconOnly'>;
7
7
  export type NumericBadgeProps = NumericBadgeNativeProps & NumericBadgeVariantsProps & TestableProps & {
8
+ ref?: Ref<HTMLDivElement>;
9
+ /** Applies the badge to its child element, such as a span or a native button. */
10
+ asChild?: boolean;
8
11
  'data-slot'?: string;
9
12
  };
10
13
  export declare const NumericBadge: FC<NumericBadgeProps>;
@@ -5,17 +5,28 @@ const figmaNodeUrl = 'https://www.figma.com/design/VKb5gW46uSGw0rqrhZsbXT/wip-co
5
5
  code_connect.connect(NumericBadge, figmaNodeUrl, {
6
6
  props: {
7
7
  type: code_connect["enum"]('Type', {
8
- Primary: 'primary',
9
- 'Primary-alt': 'primary-alt',
8
+ Solid: 'solid',
9
+ Secondary: 'secondary',
10
+ Outline: 'outline'
11
+ }),
12
+ color: code_connect["enum"]('Color', {
13
+ Neutral: 'neutral',
14
+ 'Neutral-alt': 'neutral-alt',
10
15
  Brand: 'brand',
11
- Destructive: 'destructive',
12
- Outline: 'outline',
13
- 'New / Info': 'info'
16
+ Danger: 'danger',
17
+ Info: 'info',
18
+ Success: 'success'
19
+ }),
20
+ size: code_connect["enum"]('Size', {
21
+ Medium: 'default',
22
+ Small: 'small'
14
23
  }),
15
24
  children: code_connect.string('#')
16
25
  },
17
- example: ({ type, children })=>/*#__PURE__*/ jsx(NumericBadge, {
26
+ example: ({ type, color, size, children })=>/*#__PURE__*/ jsx(NumericBadge, {
18
27
  type: type,
28
+ color: color,
29
+ size: size,
19
30
  children: children
20
31
  })
21
32
  });
@@ -1,19 +1,63 @@
1
1
  import { jsx } from "react/jsx-runtime";
2
+ import { isValidElement, useRef } from "react";
3
+ import { Slot } from "@radix-ui/react-slot";
2
4
  import { cn } from "../../utils/cn.js";
5
+ import { isIconOnly } from "../../utils/isIconOnly.js";
3
6
  import { numericBadgeVariants } from "./classes.js";
4
- const NumericBadge = ({ type = 'primary', size = 'default', onClick, 'data-slot': dataSlot, ...props })=>{
5
- const isClickable = !!onClick;
6
- return /*#__PURE__*/ jsx("div", {
7
+ const hasNativeKeyboardActivation = (element)=>'BUTTON' === element.tagName || 'INPUT' === element.tagName || 'A' === element.tagName && element.hasAttribute('href');
8
+ const NumericBadge = ({ type = 'secondary', color = 'solid' === type ? 'brand' : 'neutral', size = 'default', asChild = false, children, className, ref, role, tabIndex, onClick, onKeyDown, onKeyUp, onBlur, 'data-slot': dataSlot, ...props })=>{
9
+ const child = asChild && /*#__PURE__*/ isValidElement(children) ? children : null;
10
+ const childProps = child?.props;
11
+ const nativeChild = child?.type === 'button' || child?.type === 'input' || child?.type === 'a' && childProps?.href != null;
12
+ const isClickable = !childProps?.disabled && !!(onClick || childProps?.onClick || nativeChild);
13
+ const spacePressed = useRef(false);
14
+ const Comp = asChild ? Slot : 'div';
15
+ const handleKeyDown = (event)=>{
16
+ onKeyDown?.(event);
17
+ if (event.defaultPrevented || !isClickable || event.target !== event.currentTarget || hasNativeKeyboardActivation(event.currentTarget)) return;
18
+ if ('Enter' === event.key) {
19
+ event.preventDefault();
20
+ event.currentTarget.click();
21
+ } else if (' ' === event.key) {
22
+ event.preventDefault();
23
+ spacePressed.current = true;
24
+ }
25
+ };
26
+ const handleKeyUp = (event)=>{
27
+ onKeyUp?.(event);
28
+ if (' ' !== event.key) return;
29
+ const activate = spacePressed.current;
30
+ spacePressed.current = false;
31
+ if (activate && !event.defaultPrevented && isClickable && event.target === event.currentTarget && !hasNativeKeyboardActivation(event.currentTarget)) {
32
+ event.preventDefault();
33
+ event.currentTarget.click();
34
+ }
35
+ };
36
+ return /*#__PURE__*/ jsx(Comp, {
7
37
  ...props,
8
- tabIndex: isClickable ? 0 : void 0,
38
+ ref: ref,
39
+ role: role ?? (isClickable && !nativeChild ? 'button' : void 0),
40
+ tabIndex: tabIndex ?? (isClickable && !nativeChild ? 0 : void 0),
9
41
  "data-slot": dataSlot ?? 'numeric-badge',
10
42
  "data-type": type,
11
- onClick: onClick,
43
+ "data-color": color,
44
+ onClick: onClick ? (event)=>{
45
+ if (!event.defaultPrevented) onClick(event);
46
+ } : void 0,
47
+ onKeyDown: handleKeyDown,
48
+ onKeyUp: handleKeyUp,
49
+ onBlur: (event)=>{
50
+ spacePressed.current = false;
51
+ onBlur?.(event);
52
+ },
12
53
  className: cn(numericBadgeVariants({
13
54
  type,
55
+ color,
14
56
  size,
15
- clickable: isClickable
16
- }))
57
+ clickable: isClickable,
58
+ iconOnly: isIconOnly(child ? child.props.children : children)
59
+ }), className),
60
+ children: children
17
61
  });
18
62
  };
19
63
  NumericBadge.displayName = 'NumericBadge';
@@ -1,5 +1,7 @@
1
1
  export declare const numericBadgeVariants: (props?: ({
2
- type?: "brand" | "destructive" | "info" | "outline" | "primary" | "primary-alt" | null | undefined;
2
+ type?: "outline" | "secondary" | "solid" | null | undefined;
3
+ color?: "brand" | "danger" | "info" | "neutral" | "neutral-alt" | "success" | null | undefined;
3
4
  size?: "default" | "small" | null | undefined;
5
+ iconOnly?: boolean | null | undefined;
4
6
  clickable?: boolean | null | undefined;
5
7
  } & import("class-variance-authority/types").ClassProp) | undefined) => string;
@@ -1,24 +1,92 @@
1
1
  import { cva } from "class-variance-authority";
2
- const numericBadgeVariants = cva('inline-flex items-center rounded-full font-mono font-medium transition-colors w-max', {
2
+ const numericBadgeVariants = cva('inline-flex w-max shrink-0 items-center justify-center whitespace-nowrap rounded-full font-mono font-medium transition-colors', {
3
3
  variants: {
4
4
  type: {
5
- primary: 'bg-states-primary-active text-text-primary',
6
- 'primary-alt': 'bg-states-primary-alt-active text-text-primary-alt',
7
- brand: 'bg-bg-fill-brand text-text-primary-alt',
8
- destructive: 'bg-bg-fill-danger text-text-primary-alt',
9
- outline: 'border-1 border-border-primary bg-component-outline-button-bg text-text-primary',
10
- info: 'bg-states-info-active text-text-info'
5
+ solid: '',
6
+ secondary: '',
7
+ outline: 'border-1'
8
+ },
9
+ color: {
10
+ neutral: '',
11
+ 'neutral-alt': '',
12
+ brand: '',
13
+ danger: '',
14
+ success: '',
15
+ info: ''
11
16
  },
12
17
  size: {
13
- default: 'px-5 py-2 text-xs',
14
- small: 'p-3 text-2xs'
18
+ default: 'h-20 min-w-16 px-5 py-2 text-xs [&_svg]:icon-md',
19
+ small: 'h-16 min-w-12 px-3 py-2 text-2xs [&_svg]:icon-sm'
20
+ },
21
+ iconOnly: {
22
+ true: ''
15
23
  },
16
24
  clickable: {
17
- true: 'cursor-pointer overlay active:outline-none active:ring-3',
25
+ true: 'cursor-pointer overlay active:outline-none active:ring-3 focus-visible:outline-none focus-visible:ring-3',
18
26
  false: ''
19
27
  }
20
28
  },
21
29
  compoundVariants: [
30
+ {
31
+ type: 'solid',
32
+ color: 'brand',
33
+ className: 'bg-bg-fill-brand text-text-primary-alt'
34
+ },
35
+ {
36
+ type: 'solid',
37
+ color: 'danger',
38
+ className: 'bg-bg-fill-danger text-text-primary-alt'
39
+ },
40
+ {
41
+ type: 'secondary',
42
+ color: 'neutral',
43
+ className: 'bg-states-primary-active text-text-primary'
44
+ },
45
+ {
46
+ type: 'secondary',
47
+ color: 'neutral-alt',
48
+ className: 'bg-states-primary-alt-active text-text-primary-alt'
49
+ },
50
+ {
51
+ type: 'secondary',
52
+ color: 'brand',
53
+ className: 'bg-states-brand-active text-text-brand'
54
+ },
55
+ {
56
+ type: 'secondary',
57
+ color: 'danger',
58
+ className: 'bg-states-danger-active text-text-danger'
59
+ },
60
+ {
61
+ type: 'secondary',
62
+ color: 'success',
63
+ className: 'bg-states-success-active text-text-success'
64
+ },
65
+ {
66
+ type: 'secondary',
67
+ color: 'info',
68
+ className: 'bg-states-info-active text-text-info'
69
+ },
70
+ {
71
+ type: 'outline',
72
+ color: 'neutral',
73
+ className: 'border-border-primary bg-component-outline-button-bg text-text-primary'
74
+ },
75
+ {
76
+ type: 'outline',
77
+ color: 'brand',
78
+ className: 'border-border-brand bg-bg-brand text-text-brand'
79
+ },
80
+ {
81
+ type: 'outline',
82
+ color: 'danger',
83
+ className: 'border-border-danger bg-bg-danger text-text-danger'
84
+ },
85
+ {
86
+ type: 'outline',
87
+ color: 'success',
88
+ className: 'border-border-success bg-bg-success text-text-success'
89
+ },
22
90
  {
23
91
  type: 'outline',
24
92
  size: 'default',
@@ -27,37 +95,84 @@ const numericBadgeVariants = cva('inline-flex items-center rounded-full font-mon
27
95
  {
28
96
  type: 'outline',
29
97
  size: 'small',
30
- className: 'p-2'
98
+ className: 'px-2 py-1'
99
+ },
100
+ {
101
+ iconOnly: true,
102
+ className: 'px-2 py-2'
103
+ },
104
+ {
105
+ type: 'outline',
106
+ iconOnly: true,
107
+ className: 'px-1 py-1'
31
108
  },
32
109
  {
33
110
  clickable: true,
34
- type: 'primary',
35
- className: 'hover:overlay-states-primary-hover active:overlay-states-primary-hover active:ring-focus-primary'
111
+ type: [
112
+ 'secondary',
113
+ 'outline'
114
+ ],
115
+ color: 'neutral',
116
+ className: 'hover:overlay-states-primary-hover active:overlay-states-primary-hover'
36
117
  },
37
118
  {
38
119
  clickable: true,
39
- type: 'primary-alt',
40
- className: 'hover:overlay-states-primary-alt-hover active:overlay-states-primary-alt-hover active:ring-focus-primary'
120
+ type: 'secondary',
121
+ color: 'neutral-alt',
122
+ className: 'hover:overlay-states-primary-alt-hover active:overlay-states-primary-alt-hover'
41
123
  },
42
124
  {
43
125
  clickable: true,
44
- type: 'brand',
45
- className: 'hover:overlay-states-on-fill-hover active:overlay-states-on-fill-hover active:ring-focus-brand'
126
+ type: 'solid',
127
+ color: 'brand',
128
+ className: 'hover:overlay-states-on-fill-hover active:overlay-states-on-fill-hover active:ring-focus-brand focus-visible:ring-focus-brand'
46
129
  },
47
130
  {
48
131
  clickable: true,
49
- type: 'destructive',
50
- className: 'hover:overlay-states-on-fill-hover active:overlay-states-on-fill-hover active:ring-focus-destructive'
132
+ type: 'solid',
133
+ color: 'danger',
134
+ className: 'hover:overlay-states-on-fill-hover active:overlay-states-on-fill-hover active:ring-focus-destructive focus-visible:ring-focus-destructive'
51
135
  },
52
136
  {
53
137
  clickable: true,
54
- type: 'outline',
55
- className: 'hover:overlay-states-primary-hover active:overlay-states-primary-hover active:ring-focus-primary'
138
+ type: [
139
+ 'secondary',
140
+ 'outline'
141
+ ],
142
+ color: 'brand',
143
+ className: 'hover:overlay-states-brand-hover active:overlay-states-brand-hover'
144
+ },
145
+ {
146
+ clickable: true,
147
+ type: [
148
+ 'secondary',
149
+ 'outline'
150
+ ],
151
+ color: 'danger',
152
+ className: 'hover:overlay-states-danger-hover active:overlay-states-danger-hover'
153
+ },
154
+ {
155
+ clickable: true,
156
+ type: [
157
+ 'secondary',
158
+ 'outline'
159
+ ],
160
+ color: 'success',
161
+ className: 'hover:overlay-states-success-hover active:overlay-states-success-hover'
162
+ },
163
+ {
164
+ clickable: true,
165
+ type: 'secondary',
166
+ color: 'info',
167
+ className: 'hover:overlay-states-info-hover active:overlay-states-info-hover'
56
168
  },
57
169
  {
58
170
  clickable: true,
59
- type: 'info',
60
- className: 'hover:overlay-states-info-hover active:overlay-states-info-hover active:ring-focus-primary'
171
+ type: [
172
+ 'secondary',
173
+ 'outline'
174
+ ],
175
+ className: 'active:ring-focus-primary focus-visible:ring-focus-primary'
61
176
  }
62
177
  ]
63
178
  });
@@ -3,6 +3,7 @@ import { Steps } from "@ark-ui/react/steps";
3
3
  import { Check } from "../../icons/index.js";
4
4
  import { cn } from "../../utils/cn.js";
5
5
  import { useTestId } from "../../utils/testId.js";
6
+ import { NumericBadge } from "../NumericBadge/index.js";
6
7
  import { stepperIndicatorVariants } from "./classes.js";
7
8
  import { useStepperItemContext } from "./StepperContext.js";
8
9
  const renderDefault = (status, index)=>{
@@ -17,16 +18,21 @@ const StepperIndicator = ({ children, className, ref, 'data-testid': testIdProp,
17
18
  const testId = useTestId('indicator', testIdProp);
18
19
  return /*#__PURE__*/ jsx(Steps.Indicator, {
19
20
  asChild: true,
20
- children: /*#__PURE__*/ jsx("span", {
21
- ...rest,
22
- ref: ref,
21
+ children: /*#__PURE__*/ jsx(NumericBadge, {
22
+ asChild: true,
23
+ type: 'upcoming' === status ? 'outline' : 'secondary',
24
+ color: 'active' === status ? 'brand' : 'danger' === status ? 'danger' : 'neutral',
23
25
  "data-slot": "stepper-indicator",
24
- "data-status": status,
25
- "data-testid": testId,
26
26
  className: cn(stepperIndicatorVariants({
27
27
  status
28
28
  }), className),
29
- children: children ?? renderDefault(status, index)
29
+ children: /*#__PURE__*/ jsx("span", {
30
+ ...rest,
31
+ ref: ref,
32
+ "data-status": status,
33
+ "data-testid": testId,
34
+ children: children ?? renderDefault(status, index)
35
+ })
30
36
  })
31
37
  });
32
38
  };
@@ -6,7 +6,7 @@ export interface StepperListProps extends OlHTMLAttributes<HTMLOListElement>, Te
6
6
  ref?: Ref<HTMLOListElement>;
7
7
  }
8
8
  /**
9
- * The step bar: an `<ol>` with the Figma bottom border and padding. Name it with `aria-label` or
10
- * `aria-labelledby`.
9
+ * The step bar: an `<ol>` with 24px side insets and 12px bottom padding, as in Figma.
10
+ * Name it with `aria-label` or `aria-labelledby`.
11
11
  */
12
12
  export declare const StepperList: FC<StepperListProps>;
@@ -1,16 +1,16 @@
1
1
  import { cva } from "class-variance-authority";
2
2
  import { cn } from "../../utils/cn.js";
3
3
  const stepperVariants = cva('min-w-0');
4
- const stepperListVariants = cva('m-0 flex min-w-0 list-none items-start border-b-1 border-border-primary-light px-24 py-8');
4
+ const stepperListVariants = cva('m-0 flex min-w-0 list-none items-start px-24 pb-12');
5
5
  const stepperItemVariants = cva('flex min-w-0 items-start');
6
6
  const stepperTriggerVariants = cva(cn('grid min-w-0 cursor-pointer grid-cols-[auto_minmax(0,1fr)] grid-rows-[20px] items-start gap-x-4 bg-transparent p-0 text-left', 'rounded-6 focus-visible:outline-none focus-visible:ring-3 focus-visible:ring-focus-primary'));
7
- const stepperIndicatorVariants = cva("col-start-1 row-span-2 row-start-1 inline-flex h-20 min-w-16 shrink-0 items-center justify-center rounded-full font-mono font-medium text-xs [font-feature-settings:'liga'_0]", {
7
+ const stepperIndicatorVariants = cva("col-start-1 row-span-2 row-start-1 min-w-16 [font-feature-settings:'liga'_0] [&_svg]:icon-sm", {
8
8
  variants: {
9
9
  status: {
10
- active: 'bg-states-brand-active px-4 py-2 text-text-brand',
11
- danger: 'bg-states-danger-active px-4 py-2 text-text-danger',
12
- completed: 'bg-states-primary-active px-2 py-2 text-icon-primary',
13
- upcoming: 'border-1 border-border-primary bg-component-outline-button-bg px-3 py-1 text-text-primary'
10
+ active: 'px-4 py-2',
11
+ danger: 'px-4 py-2',
12
+ completed: 'px-2 py-2 text-icon-primary',
13
+ upcoming: 'px-3 py-1'
14
14
  }
15
15
  },
16
16
  defaultVariants: {
@@ -1,6 +1,6 @@
1
1
  {
2
- "version": "1.31.0",
3
- "generatedAt": "2026-10-01T11:56:51.596Z",
2
+ "version": "1.33.0",
3
+ "generatedAt": "2026-10-02T12:33:35.613Z",
4
4
  "components": [
5
5
  {
6
6
  "name": "Accordion",
@@ -19208,7 +19208,7 @@
19208
19208
  "name": "readOnly",
19209
19209
  "type": "boolean | undefined",
19210
19210
  "required": false,
19211
- "description": "Focusable, selectable, searchable, copyable — but not editable by the user"
19211
+ "description": "Non-editable and unfocusable content; text remains selectable, searchable and copyable"
19212
19212
  },
19213
19213
  {
19214
19214
  "name": "startingLineNumber",
@@ -19882,7 +19882,7 @@
19882
19882
  "name": "readOnly",
19883
19883
  "type": "boolean | undefined",
19884
19884
  "required": false,
19885
- "description": "Focusable, selectable, searchable, copyable — but not editable by the user",
19885
+ "description": "Non-editable and unfocusable content; text remains selectable, searchable and copyable",
19886
19886
  "defaultValue": "false"
19887
19887
  },
19888
19888
  {
@@ -20301,7 +20301,7 @@
20301
20301
  {
20302
20302
  "name": "ReadOnly",
20303
20303
  "code": "() => (\n <div style={{ width: '600px' }}>\n <CodeEditorRoot\n value={httpResponseCode}\n language='http'\n readOnly\n data-testid='code-editor-read-only'\n >\n <CodeSnippetHeader>\n <CodeSnippetTitle>Response</CodeSnippetTitle>\n <CodeSnippetActions>\n <CodeSnippetCopyButton />\n </CodeSnippetActions>\n </CodeSnippetHeader>\n <CodeEditorContent lineNumbers aria-label='HTTP response, read only' />\n </CodeEditorRoot>\n </div>\n)",
20304
- "description": "`readOnly` still lets you focus, select, search and copy, but not type. Use it when the value\ncan change elsewhere but not here. If it never changes, use `CodeSnippet`."
20304
+ "description": "`readOnly` lets you select, search and copy without focusing the code or showing a caret. Use it when the value\ncan change elsewhere but not here. If it never changes, use `CodeSnippet`."
20305
20305
  },
20306
20306
  {
20307
20307
  "name": "Tabs",
@@ -20344,9 +20344,9 @@
20344
20344
  "description": "Parity check for the three sizes: the same font size, line height and gutter padding per size."
20345
20345
  },
20346
20346
  {
20347
- "name": "ParityChrome",
20348
- "code": "() => (\n <VStack gap={24}>\n <ParityPair testId='parity-chrome-header' code={sampleCode} language='text'>\n {PARITY_HEADER_CHROME}\n </ParityPair>\n <ParityPair testId='parity-chrome-floating' code={sampleCode} language='text'>\n {PARITY_FLOATING_CHROME}\n </ParityPair>\n </VStack>\n)",
20349
- "description": "Parity check for the shared chrome: a header with tabs and actions, and floating actions\nwithout a header, sit in the same place with the same spacing."
20347
+ "name": "ParityFrame",
20348
+ "code": "() => (\n <VStack gap={24}>\n <ParityPair testId='parity-frame-header' code={sampleCode} language='text'>\n {PARITY_HEADER_FRAME}\n </ParityPair>\n <ParityPair testId='parity-frame-floating' code={sampleCode} language='text'>\n {PARITY_FLOATING_FRAME}\n </ParityPair>\n </VStack>\n)",
20349
+ "description": "Parity check for the shared frame: a header with tabs and actions, and floating actions\nwithout a header, sit in the same place with the same spacing."
20350
20350
  },
20351
20351
  {
20352
20352
  "name": "ParityShowMore",
@@ -20391,7 +20391,7 @@
20391
20391
  {
20392
20392
  "name": "LongDocument",
20393
20393
  "code": "() => <LongDocumentDemo />",
20394
- "description": "2 000 read-only lines clamped by `maxLines` — the top of the envelope the colour painter is\nbuilt for, where `readOnly` still lets the text be focused, searched and copied."
20394
+ "description": "2 000 read-only lines clamped by `maxLines` — the top of the envelope the colour painter is\nbuilt for, where `readOnly` still lets the text be selected, searched and copied."
20395
20395
  }
20396
20396
  ]
20397
20397
  },
@@ -56848,11 +56848,6 @@
56848
56848
  "type": "string | undefined",
56849
56849
  "required": false
56850
56850
  },
56851
- {
56852
- "name": "color",
56853
- "type": "string | undefined",
56854
- "required": false
56855
- },
56856
56851
  {
56857
56852
  "name": "itemProp",
56858
56853
  "type": "string | undefined",
@@ -56938,28 +56933,49 @@
56938
56933
  "required": false,
56939
56934
  "description": "@see {@link https://developer.mozilla.org/en-US/docs/Web/HTML/Global_attributes/part}"
56940
56935
  },
56936
+ {
56937
+ "name": "color",
56938
+ "type": "{ neutral: string; 'neutral-alt': string; brand: string; danger: string; success: string; info: string; }",
56939
+ "required": false,
56940
+ "defaultValue": "type === 'solid' ? 'brand' : 'neutral"
56941
+ },
56941
56942
  {
56942
56943
  "name": "type",
56943
- "type": "{ primary: string; 'primary-alt': string; brand: string; destructive: string; outline: string; info: string; }",
56944
+ "type": "{ solid: string; secondary: string; outline: string; }",
56944
56945
  "required": false,
56945
- "defaultValue": "primary"
56946
+ "defaultValue": "secondary"
56946
56947
  },
56947
56948
  {
56948
56949
  "name": "size",
56949
56950
  "type": "{ default: string; small: string; }",
56950
56951
  "required": false,
56951
56952
  "defaultValue": "default"
56953
+ },
56954
+ {
56955
+ "name": "asChild",
56956
+ "type": "boolean | undefined",
56957
+ "required": false,
56958
+ "description": "Applies the badge to its child element, such as a span or a native button.",
56959
+ "defaultValue": "false"
56952
56960
  }
56953
56961
  ],
56954
56962
  "variants": [
56955
56963
  {
56956
56964
  "name": "type",
56957
56965
  "options": [
56958
- "primary",
56959
- "primary-alt",
56966
+ "solid",
56967
+ "secondary",
56968
+ "outline"
56969
+ ]
56970
+ },
56971
+ {
56972
+ "name": "color",
56973
+ "options": [
56974
+ "neutral",
56975
+ "neutral-alt",
56960
56976
  "brand",
56961
- "destructive",
56962
- "outline",
56977
+ "danger",
56978
+ "success",
56963
56979
  "info"
56964
56980
  ]
56965
56981
  },
@@ -56970,6 +56986,12 @@
56970
56986
  "small"
56971
56987
  ]
56972
56988
  },
56989
+ {
56990
+ "name": "iconOnly",
56991
+ "options": [
56992
+ "true"
56993
+ ]
56994
+ },
56973
56995
  {
56974
56996
  "name": "clickable",
56975
56997
  "options": [
@@ -56982,7 +57004,7 @@
56982
57004
  "examples": [
56983
57005
  {
56984
57006
  "name": "Basic",
56985
- "code": "() => <NumericBadge>1</NumericBadge>",
57007
+ "code": "args => <NumericBadge {...args}>1</NumericBadge>",
56986
57008
  "description": "The default count. Keep it to what the reader can take in at a glance; a four-digit count is\na number they need to read, not see."
56987
57009
  },
56988
57010
  {
@@ -56992,8 +57014,18 @@
56992
57014
  },
56993
57015
  {
56994
57016
  "name": "Types",
56995
- "code": "() => (\n // One row per type, so the label always sits beside the badges it names.\n <VStack gap={8} align='start'>\n {(['primary', 'brand', 'destructive', 'outline', 'info'] as const).map(type => (\n <HStack key={type} gap={12} align='center'>\n <span className='sb-annotation w-144 text-right'>{type}</span>\n <NumericBadge type={type} size='default'>\n 1\n </NumericBadge>\n <NumericBadge type={type} size='small'>\n 1\n </NumericBadge>\n </HStack>\n ))}\n\n <HStack gap={12} align='center'>\n <span className='sb-annotation w-144 text-right'>primary-alt</span>\n <div className='inline-flex items-center gap-12 bg-component-tooltip-bg p-8'>\n <NumericBadge type='primary-alt' size='default'>\n 1\n </NumericBadge>\n <NumericBadge type='primary-alt' size='small'>\n 1\n </NumericBadge>\n </div>\n </HStack>\n </VStack>\n)",
56996
- "description": "The palette, including `destructive` and `brand`. Reach for a plain type unless the count\nitself is the thing demanding attention."
57017
+ "code": "() => (\n // One row per type, so the label always sits beside the badges it names.\n <VStack gap={8} align='start'>\n {combinations.map(({ type, color }) => (\n <HStack key={`${type}-${color}`} gap={12} align='center'>\n <span className='sb-annotation w-256 whitespace-nowrap text-right'>\n {type} / {color}\n </span>\n <div\n className={`inline-flex items-center gap-12 p-8 ${color === 'neutral-alt' ? 'bg-component-tooltip-bg' : ''}`}\n >\n <NumericBadge type={type} color={color} size='default'>\n 1\n </NumericBadge>\n <NumericBadge type={type} color={color} size='small'>\n 1\n </NumericBadge>\n </div>\n </HStack>\n ))}\n </VStack>\n)",
57018
+ "description": "Every supported Type × Color combination, in Medium and Small."
57019
+ },
57020
+ {
57021
+ "name": "Content",
57022
+ "code": "() => (\n <VStack gap={16} align='start'>\n {(['default', 'small'] as const).map(size => (\n <HStack key={size} gap={12} align='center'>\n <span className='sb-annotation w-96 text-right'>{size}</span>\n <NumericBadge size={size}>1</NumericBadge>\n <NumericBadge size={size}>99</NumericBadge>\n <NumericBadge size={size} color='danger' aria-label='Needs attention'>\n !\n </NumericBadge>\n <NumericBadge size={size} color='info' aria-label='Unknown'>\n ?\n </NumericBadge>\n <NumericBadge size={size} color='success' aria-label='Completed'>\n <Check />\n </NumericBadge>\n </HStack>\n ))}\n </VStack>\n)",
57023
+ "description": "Numbers, short glyphs, and icons share the same badge heights."
57024
+ },
57025
+ {
57026
+ "name": "Clickable",
57027
+ "code": "() => {\n const [activations, setActivations] = useState(0);\n\n return (\n <VStack gap={12} align='start'>\n {combinations.map(({ type, color }) => (\n <HStack key={`${type}-${color}`} gap={12} align='center'>\n <span className='sb-annotation w-256 whitespace-nowrap text-right'>\n {type} / {color}\n </span>\n <div className={color === 'neutral-alt' ? 'bg-component-tooltip-bg p-8' : 'p-8'}>\n <NumericBadge\n asChild\n type={type}\n color={color}\n onClick={() => setActivations(count => count + 1)}\n >\n <button type='button' aria-label={`${type} ${color}`}>\n 1\n </button>\n </NumericBadge>\n </div>\n </HStack>\n ))}\n <output aria-label='Activations'>{activations}</output>\n </VStack>\n );\n}",
57028
+ "description": "Use a native button for an action; Tab, Enter, and Space work as expected."
56997
57029
  }
56998
57030
  ]
56999
57031
  },
@@ -95981,7 +96013,7 @@
95981
96013
  {
95982
96014
  "name": "InDrawer",
95983
96015
  "code": "() => {\n const { step, statuses, goTo } = usePolicyFlow();\n const { nextRef, submitRef, onBackClick, onNextClick } = useFooterFocus(step);\n const [submitted, setSubmitted] = useState(0);\n const last = step === POLICY_STEPS.length - 1;\n const onSubmit = (event: FormEvent) => {\n event.preventDefault();\n setSubmitted(n => n + 1);\n };\n return (\n <Drawer data-testid='drawer' width={640}>\n <DrawerTrigger asChild>\n <Button variant='outline' color='neutral' data-testid='drawer-open'>\n Create policy\n </Button>\n </DrawerTrigger>\n <DrawerContent>\n <DrawerHeader>\n <DrawerTitle>Create policy</DrawerTitle>\n </DrawerHeader>\n <form onSubmit={onSubmit} className='flex min-h-0 flex-1 flex-col'>\n <Stepper\n data-testid='stepper'\n count={POLICY_STEPS.length}\n step={step}\n onStepChange={details => goTo(details.step)}\n className='flex min-h-0 flex-1 flex-col'\n >\n <StepperList aria-label='Create policy steps'>\n {POLICY_STEPS.map((title, i) => (\n <StepperItem key={title} index={i} status={statuses[i]}>\n <StepperTrigger>\n <StepperIndicator />\n <StepperTitle>{title}</StepperTitle>\n </StepperTrigger>\n <StepperSeparator />\n </StepperItem>\n ))}\n </StepperList>\n <DrawerBody>\n {POLICY_STEPS.map((title, i) => (\n <StepperContent key={title} index={i} className='py-16'>\n <BodyPlaceholder label={`${title} fields`} />\n </StepperContent>\n ))}\n <span className='sb-annotation' data-testid='stepper-submitted'>\n Submitted: {submitted}\n </span>\n </DrawerBody>\n <DrawerFooter>\n {step > 0 && (\n <DrawerFooterControls placement='left'>\n <StepperPrevTrigger asChild onClick={onBackClick}>\n <Button variant='outline' color='neutral' size='large'>\n Back\n </Button>\n </StepperPrevTrigger>\n </DrawerFooterControls>\n )}\n <DrawerFooterControls placement='right'>\n {last ? (\n <Button\n ref={submitRef}\n type='submit'\n variant='primary'\n color='brand'\n size='large'\n >\n Create policy\n </Button>\n ) : (\n <StepperNextTrigger asChild onClick={onNextClick}>\n <Button ref={nextRef} variant='primary' color='brand' size='large'>\n Next: {POLICY_STEPS[step + 1]}\n </Button>\n </StepperNextTrigger>\n )}\n </DrawerFooterControls>\n </DrawerFooter>\n </Stepper>\n </form>\n </DrawerContent>\n </Drawer>\n );\n}",
95984
- "description": "Inside a create drawer: the step list sits under the header, `StepperContent` shows the current\nstep's body (every body stays mounted, so fields keep their values) and the footer moves with\nBack / Next. Steps never submit the form; only the last step's own submit button does. Focus\nstays on the footer when Back or Next unmounts. Drawer parts inside the `Stepper` root take its\ntest ids (`stepper--body`, `stepper--footer`), not the Drawer's."
96016
+ "description": "Inside a create drawer: the step list sits under the header, `StepperContent` shows the current\nstep's body (every body stays mounted, so fields keep their values) and the footer moves with\nBack / Next. Steps never submit the form; only the last step's own submit button does. Focus\nstays on the footer when Back or Next unmounts. The list sits directly below the header,\nwith 24px side insets, 12px bottom padding and no divider. Drawer parts inside the `Stepper`\nroot take its test ids (`stepper--body`, `stepper--footer`), not the Drawer's."
95985
96017
  },
95986
96018
  {
95987
96019
  "name": "LongLabel",
@@ -106837,7 +106869,7 @@
106837
106869
  "props": [
106838
106870
  {
106839
106871
  "name": "color",
106840
- "type": "string | undefined",
106872
+ "type": "{ neutral: string; 'neutral-alt': string; brand: string; danger: string; success: string; info: string; }",
106841
106873
  "required": false
106842
106874
  },
106843
106875
  {
@@ -107104,6 +107136,12 @@
107104
107136
  "type": "string | undefined",
107105
107137
  "required": false,
107106
107138
  "description": "@see {@link https://developer.mozilla.org/en-US/docs/Web/HTML/Global_attributes/part}"
107139
+ },
107140
+ {
107141
+ "name": "asChild",
107142
+ "type": "boolean | undefined",
107143
+ "required": false,
107144
+ "description": "Applies the badge to its child element, such as a span or a native button."
107107
107145
  }
107108
107146
  ]
107109
107147
  },
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@wallarm-org/design-system",
3
- "version": "1.32.0",
3
+ "version": "2.0.0-rc-fix-stepper.1",
4
4
  "description": "Core design system library with React components and Storybook documentation",
5
5
  "publishConfig": {
6
6
  "access": "public",