@xsolla/xui-tag 0.222.0 → 0.224.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/README.md CHANGED
@@ -1,6 +1,6 @@
1
1
  # Tag
2
2
 
3
- A cross-platform React tag component for displaying labels, categories, and removable chips. Supports multiple tones, solid/outlined types, and optional left/right icons.
3
+ A cross-platform React tag component for displaying labels, categories, and removable chips. Supports multiple tones, solid/outlined types, and optional left/right icons. The package also ships `TagInput`, a text field that turns typed values into removable chips.
4
4
  <!-- BEGIN:xui-mcp-instructions:tag -->
5
5
  A compact label chip used to categorise, classify, or annotate content. Renders as a pill-shaped container with a text label and optional leading and trailing icons. Supports two visual types (solid fill and outlined), seven semantic tones, and five sizes. Used in lists, cards, table cells, filters, and any context where a short categorical attribute needs to be visually marked.
6
6
 
@@ -36,7 +36,7 @@ A compact label chip used to categorise, classify, or annotate content. Renders
36
36
  - Do not rely on Tone (colour) alone to communicate meaning — the label text or icon must also be present (WCAG 1.4.1 Use of Colour). For icon-only tags, provide aria-label on the element.
37
37
  - Removable tags: the remove button (Icon right) must have aria-label=*"Remove [tag name]"* so screen readers can announce which tag is being removed. The button must be focusable and respond to Enter / Space.
38
38
  - When tags are inside a list, wrap them in a <ul> with <li> elements and an aria-label on the list describing the group — e.g. aria-label=*"Product tags"*.
39
- - Ensure sufficient colour contrast between the tag label text and the tag background in both Solid and Outlined variants, for all tones, meeting WCAG AA (4.5:1 for small text at XS/S sizes).
39
+ - Ensure sufficient colour contrast between the tag label text and the tag background in both Solid and Outlined variants, for all tones, meeting WCAG AA (4.5:1 for small text at XS/S sizes).
40
40
  <!-- END:xui-mcp-instructions:tag -->
41
41
 
42
42
  ## Installation
@@ -208,7 +208,8 @@ import { Tag } from "@xsolla/xui-tag";
208
208
  type="solid" // Solid or outlined
209
209
  iconLeft={<Icon />} // Optional leading icon
210
210
  iconRight={<Icon />} // Optional trailing icon
211
- onRemove={handleRemove} // Makes tag removable (renders X icon)
211
+ onRemove={handleRemove} // Makes tag removable (renders an X button)
212
+ removeLabel="Remove brand" // Optional accessible name for the X button
212
213
  >
213
214
  Tag Label
214
215
  </Tag>;
@@ -268,48 +269,33 @@ export default function StatusTags() {
268
269
 
269
270
  ### Tag Input
270
271
 
272
+ Use `TagInput` to let users type free-form values that become removable chips inside the field. Chips wrap onto new lines as the list grows.
273
+
271
274
  ```tsx
272
275
  import * as React from "react";
273
- import { Tag } from "@xsolla/xui-tag";
274
- import { Input } from "@xsolla/xui-input";
276
+ import { TagInput } from "@xsolla/xui-tag";
275
277
 
276
- export default function TagInput() {
277
- const [tags, setTags] = React.useState(["react", "typescript"]);
278
- const [inputValue, setInputValue] = React.useState("");
279
-
280
- const addTag = () => {
281
- if (inputValue.trim() && !tags.includes(inputValue.trim())) {
282
- setTags([...tags, inputValue.trim()]);
283
- setInputValue("");
284
- }
285
- };
278
+ export default function ItemIdsField() {
279
+ const [ids, setIds] = React.useState<string[]>([]);
286
280
 
287
281
  return (
288
- <div>
289
- <div
290
- style={{ display: "flex", gap: 8, flexWrap: "wrap", marginBottom: 8 }}
291
- >
292
- {tags.map((tag) => (
293
- <Tag
294
- key={tag}
295
- tone="primary"
296
- onRemove={() => setTags(tags.filter((t) => t !== tag))}
297
- >
298
- {tag}
299
- </Tag>
300
- ))}
301
- </div>
302
- <Input
303
- value={inputValue}
304
- onChangeText={setInputValue}
305
- placeholder="Add a tag..."
306
- onKeyDown={(e) => e.key === "Enter" && addTag()}
307
- />
308
- </div>
282
+ <TagInput
283
+ label="Epic Audience Item IDs"
284
+ value={ids}
285
+ onChange={setIds}
286
+ placeholder="Type an ID and press Enter"
287
+ />
309
288
  );
310
289
  }
311
290
  ```
312
291
 
292
+ Behaviour:
293
+
294
+ - **Enter** adds the typed text as a chip and does not submit the surrounding form. While an input method (IME) is composing, Enter confirms the composition instead.
295
+ - **Leaving the field** adds the typed text too.
296
+ - **Backspace** in an empty field removes the last chip.
297
+ - Values are kept as typed — no trimming and no de-duplication. Empty input is ignored.
298
+
313
299
  ## API Reference
314
300
 
315
301
  ### Tag
@@ -318,16 +304,17 @@ A tag/chip component.
318
304
 
319
305
  **Tag Props:**
320
306
 
321
- | Prop | Type | Default | Description |
322
- | :-------- | :--------------------------------------------------------------------------------------- | :---------- | :------------------------------------------------------------------------------------------------------------ |
323
- | `testID` | `string` | — | Test ID for testing frameworks. On web this renders as `data-testid`; on React Native it renders as `testID`. |
324
- | children | `ReactNode` | - | Tag content. Optional for icon-only tags. |
325
- | size | `"xl" \| "lg" \| "md" \| "sm" \| "xs"` | `"md"` | Size of the tag. |
326
- | tone | `"primary" \| "brand" \| "brandExtra" \| "success" \| "warning" \| "alert" \| "neutral"` | `"primary"` | Color tone. |
327
- | type | `"solid" \| "outlined"` | `"solid"` | Visual type. Solid fills background; outlined uses a border with lighter background. |
328
- | iconLeft | `ReactNode` | - | Leading icon. |
329
- | iconRight | `ReactNode` | - | Trailing icon. |
330
- | onRemove | `() => void` | - | Callback for remove button. Renders an X icon in the trailing position. |
307
+ | Prop | Type | Default | Description |
308
+ | :---------- | :--------------------------------------------------------------------------------------- | :---------- | :------------------------------------------------------------------------------------------------------------ |
309
+ | `testID` | `string` | — | Test ID for testing frameworks. On web this renders as `data-testid`; on React Native it renders as `testID`. |
310
+ | children | `ReactNode` | - | Tag content. Optional for icon-only tags. |
311
+ | size | `"xl" \| "lg" \| "md" \| "sm" \| "xs"` | `"md"` | Size of the tag. |
312
+ | tone | `"primary" \| "brand" \| "brandExtra" \| "success" \| "warning" \| "alert" \| "neutral"` | `"primary"` | Color tone. |
313
+ | type | `"solid" \| "outlined"` | `"solid"` | Visual type. Solid fills background; outlined uses a border with lighter background. |
314
+ | iconLeft | `ReactNode` | - | Leading icon. |
315
+ | iconRight | `ReactNode` | - | Trailing icon. |
316
+ | onRemove | `() => void` | - | Callback for the remove button. Renders an X button in the trailing position. |
317
+ | removeLabel | `string` | - | Accessible name of the remove button. Defaults to `"Remove {children}"` for text labels, otherwise `"Remove"`. |
331
318
 
332
319
  **Solid Tone Color Mapping:**
333
320
 
@@ -353,9 +340,32 @@ A tag/chip component.
353
340
  | alert | Alert secondary | Border alert | Content alert primary |
354
341
  | neutral | Neutral secondary | Border neutral | Content neutral primary |
355
342
 
343
+ ### TagInput
344
+
345
+ A controlled text field that turns typed values into removable chips. The ref is forwarded to the text field.
346
+
347
+ **TagInput Props:**
348
+
349
+ | Prop | Type | Default | Description |
350
+ | :------------- | :------------------------------------- | :------ | :-------------------------------------------------------------------------- |
351
+ | value | `string[]` | — | Current list of chip values. Required. |
352
+ | onChange | `(value: string[]) => void` | — | Called with the next list when a chip is added or removed. Required. |
353
+ | placeholder | `string` | - | Placeholder shown while there are no chips. |
354
+ | size | `"xl" \| "lg" \| "md" \| "sm" \| "xs"` | `"md"` | Size of the field, matching `Input`. Chips are sized to fit inside it. |
355
+ | disabled | `boolean` | `false` | Disables typing and hides the chip remove buttons. |
356
+ | error | `boolean` | - | Highlights the field as invalid. |
357
+ | errorMessage | `string` | - | Error text below the field. Also highlights the field as invalid. |
358
+ | label | `string` | - | Visible label above the field. |
359
+ | `aria-label` | `string` | - | Accessible name for the text field when no `label` is set. |
360
+ | id | `string` | - | Id of the text field. Generated when omitted. |
361
+ | name | `string` | - | Name of the text field. |
362
+ | onFocus | `() => void` | - | Called when the text field receives focus. |
363
+ | onBlur | `() => void` | - | Called when the text field loses focus, after any typed text is committed. |
364
+ | `testID` | `string` | - | Test ID on the root element. |
365
+
356
366
  ## Accessibility
357
367
 
358
- - Uses semantic elements for proper structure
359
- - Remove button includes accessible label
360
- - Keyboard accessible remove action
361
- - Focus indicator on interactive elements
368
+ - The remove control is a native `<button type="button">`: it is reachable with Tab, activates on Enter / Space, and is announced as "Remove {label}" (override with `removeLabel`)
369
+ - `TagInput` links its visible label via `aria-labelledby`, falls back to `aria-label`, and links `errorMessage` via `aria-describedby` with `aria-invalid`
370
+ - `TagInput` chips use the same accessible remove button, labelled "Remove {value}"
371
+ - Enter does not add a chip while an input method (IME) is composing
@@ -14,12 +14,57 @@ interface TagProps extends ThemeOverrideProps {
14
14
  iconLeft?: ReactNode;
15
15
  /** Icon displayed on the right side of the label */
16
16
  iconRight?: ReactNode;
17
- /** Callback when the remove (X) icon is clicked */
17
+ /** Callback when the remove (X) button is pressed */
18
18
  onRemove?: () => void;
19
+ /**
20
+ * Accessible name of the remove button. Defaults to "Remove {children}" when
21
+ * `children` is a string or number, otherwise "Remove".
22
+ */
23
+ removeLabel?: string;
19
24
  testID?: string;
20
25
  /** Extra styles merged onto the chip. Used for fills the tone tokens do not cover. */
21
26
  style?: React.CSSProperties;
22
27
  }
23
28
  declare const Tag: React.FC<TagProps>;
24
29
 
25
- export { Tag, type TagProps };
30
+ type TagInputSize = "xl" | "lg" | "md" | "sm" | "xs";
31
+ interface TagInputProps extends ThemeOverrideProps {
32
+ /** Current list of chip values (controlled). */
33
+ value: string[];
34
+ /** Called with the next list whenever a chip is added or removed. */
35
+ onChange: (value: string[]) => void;
36
+ /** Placeholder shown in the text field while no draft text is typed. */
37
+ placeholder?: string;
38
+ /** Size of the field. Chips are sized to fit inside it. */
39
+ size?: TagInputSize;
40
+ /** Disables typing and chip removal. */
41
+ disabled?: boolean;
42
+ /** Highlights the field as invalid. */
43
+ error?: boolean;
44
+ /** Error message rendered below the field. Also highlights the field as invalid. */
45
+ errorMessage?: string;
46
+ /** Visible label rendered above the field. */
47
+ label?: string;
48
+ /** Accessible label for the text field when no visible `label` is set. */
49
+ "aria-label"?: string;
50
+ /** Id of the text field. Generated when omitted. */
51
+ id?: string;
52
+ /** Name of the text field. */
53
+ name?: string;
54
+ /** Called when the text field receives focus. */
55
+ onFocus?: () => void;
56
+ /** Called when the text field loses focus (after any draft is committed). */
57
+ onBlur?: () => void;
58
+ testID?: string;
59
+ }
60
+ /**
61
+ * Text field that turns typed values into removable chips.
62
+ *
63
+ * - Enter adds the typed text as a chip and never submits the surrounding form.
64
+ * - Leaving the field adds the typed text too.
65
+ * - Backspace in an empty field removes the last chip.
66
+ * - Values are kept as typed: no trimming and no de-duplication.
67
+ */
68
+ declare const TagInput: React.ForwardRefExoticComponent<TagInputProps & React.RefAttributes<HTMLInputElement>>;
69
+
70
+ export { Tag, TagInput, type TagInputProps, type TagProps };
package/native/index.d.ts CHANGED
@@ -14,12 +14,57 @@ interface TagProps extends ThemeOverrideProps {
14
14
  iconLeft?: ReactNode;
15
15
  /** Icon displayed on the right side of the label */
16
16
  iconRight?: ReactNode;
17
- /** Callback when the remove (X) icon is clicked */
17
+ /** Callback when the remove (X) button is pressed */
18
18
  onRemove?: () => void;
19
+ /**
20
+ * Accessible name of the remove button. Defaults to "Remove {children}" when
21
+ * `children` is a string or number, otherwise "Remove".
22
+ */
23
+ removeLabel?: string;
19
24
  testID?: string;
20
25
  /** Extra styles merged onto the chip. Used for fills the tone tokens do not cover. */
21
26
  style?: React.CSSProperties;
22
27
  }
23
28
  declare const Tag: React.FC<TagProps>;
24
29
 
25
- export { Tag, type TagProps };
30
+ type TagInputSize = "xl" | "lg" | "md" | "sm" | "xs";
31
+ interface TagInputProps extends ThemeOverrideProps {
32
+ /** Current list of chip values (controlled). */
33
+ value: string[];
34
+ /** Called with the next list whenever a chip is added or removed. */
35
+ onChange: (value: string[]) => void;
36
+ /** Placeholder shown in the text field while no draft text is typed. */
37
+ placeholder?: string;
38
+ /** Size of the field. Chips are sized to fit inside it. */
39
+ size?: TagInputSize;
40
+ /** Disables typing and chip removal. */
41
+ disabled?: boolean;
42
+ /** Highlights the field as invalid. */
43
+ error?: boolean;
44
+ /** Error message rendered below the field. Also highlights the field as invalid. */
45
+ errorMessage?: string;
46
+ /** Visible label rendered above the field. */
47
+ label?: string;
48
+ /** Accessible label for the text field when no visible `label` is set. */
49
+ "aria-label"?: string;
50
+ /** Id of the text field. Generated when omitted. */
51
+ id?: string;
52
+ /** Name of the text field. */
53
+ name?: string;
54
+ /** Called when the text field receives focus. */
55
+ onFocus?: () => void;
56
+ /** Called when the text field loses focus (after any draft is committed). */
57
+ onBlur?: () => void;
58
+ testID?: string;
59
+ }
60
+ /**
61
+ * Text field that turns typed values into removable chips.
62
+ *
63
+ * - Enter adds the typed text as a chip and never submits the surrounding form.
64
+ * - Leaving the field adds the typed text too.
65
+ * - Backspace in an empty field removes the last chip.
66
+ * - Values are kept as typed: no trimming and no de-duplication.
67
+ */
68
+ declare const TagInput: React.ForwardRefExoticComponent<TagInputProps & React.RefAttributes<HTMLInputElement>>;
69
+
70
+ export { Tag, TagInput, type TagInputProps, type TagProps };