prime-ui-kit 0.7.7 → 0.7.9

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": "prime-ui-kit",
3
- "version": "0.7.7",
3
+ "version": "0.7.9",
4
4
  "description": "React 19 UI kit: CSS Modules, semantic design tokens (--prime-sys-*), composable components — forms, modals, selects, tables, navigation, overlays. TypeScript, ESM, a11y-oriented.",
5
5
  "main": "./dist/index.js",
6
6
  "module": "./dist/index.js",
@@ -19,28 +19,63 @@ A multi-select field: selected values appear as removable chips (styled like [Ta
19
19
 
20
20
  ## Composition
21
21
 
22
- - **`TagSelect.Root`** — owns **`options`**, **`value`** / **`defaultValue`**, **`onValueChange`**, **`creatable`**, **`onCreated`**, **`placeholder`**, **`hint`**, **`size`** (same [`Select`](../select) size axis as **`Select.Root`**), **`disabled`**, **`hasError`**, **`aria-label`** / **`aria-labelledby`**. Renders the chip row + input, a portaled listbox when open, and optional **`ScrollContainer`** for the option list.
22
+ - **`TagSelect.Root`** — owns **`options`**, **`value`** / **`defaultValue`**, **`onValueChange`**, **`creatable`**, **`onCreated`**, **`optionManagement`** (опционально: меню «⋯» у строки — переименование, палитра цветов, удаление из справочника), **`placeholder`**, **`hint`**, **`size`** (same [`Select`](../select) size axis as **`Select.Root`**), **`disabled`**, **`hasError`**, **`aria-label`** / **`aria-labelledby`**. Renders the chip row + input, a portaled listbox when open, and optional **`ScrollContainer`** for the option list.
23
23
 
24
24
  ### Canonical example
25
25
 
26
+ Controlled **`options`**, **`creatable`**, and **`optionManagement`**: keep **`options`** in React state; in **`onUpdate`**, append a row when **`tagValue`** is not found (covers tags created in-session before the parent list includes them).
27
+
26
28
  ```tsx
27
29
  import * as React from "react";
30
+ import type { TagSelectOption } from "prime-ui-kit";
28
31
  import { TagSelect } from "prime-ui-kit";
29
32
 
33
+ const initialOptions: TagSelectOption[] = [
34
+ { value: "telegram", label: "Telegram", color: "blue" },
35
+ { value: "whatsapp", label: "WhatsApp", color: "green" },
36
+ ];
37
+
30
38
  export function Example() {
31
- const [value, setValue] = React.useState<string[]>(["eng"]);
39
+ const [value, setValue] = React.useState<string[]>([]);
40
+ const [options, setOptions] = React.useState<TagSelectOption[]>(initialOptions);
32
41
 
33
42
  return (
34
43
  <TagSelect.Root
35
- options={[
36
- { value: "eng", label: "Engineering", color: "blue" },
37
- { value: "design", label: "Design", color: "purple" },
38
- { value: "sales", label: "Sales", color: "green" },
39
- ]}
44
+ options={options}
40
45
  value={value}
41
46
  onValueChange={setValue}
42
- placeholder="Add team…"
43
- aria-label="Teams"
47
+ creatable
48
+ optionManagement={{
49
+ onUpdate: (tagValue, updates) => {
50
+ setOptions((prev) => {
51
+ const i = prev.findIndex((o) => o.value === tagValue);
52
+ if (i === -1) {
53
+ return [
54
+ ...prev,
55
+ {
56
+ value: tagValue,
57
+ label: updates.label ?? tagValue,
58
+ color: updates.color ?? "gray",
59
+ },
60
+ ];
61
+ }
62
+ return prev.map((o) =>
63
+ o.value === tagValue
64
+ ? {
65
+ ...o,
66
+ ...(updates.label !== undefined ? { label: updates.label } : {}),
67
+ ...(updates.color !== undefined ? { color: updates.color } : {}),
68
+ }
69
+ : o,
70
+ );
71
+ });
72
+ },
73
+ onDelete: (tagValue) => {
74
+ setOptions((prev) => prev.filter((o) => o.value !== tagValue));
75
+ },
76
+ }}
77
+ placeholder="Channel…"
78
+ aria-label="Contact channels"
44
79
  />
45
80
  );
46
81
  }
@@ -50,12 +85,12 @@ export function Example() {
50
85
 
51
86
  | File | Intent |
52
87
  |------|--------|
53
- | [`examples/pattern-canonical.tsx`](./examples/pattern-canonical.tsx) | Controlled multi-select from **`options`** (`prime-ui-kit` import). |
54
- | [`examples/pattern-features.tsx`](./examples/pattern-features.tsx) | **`creatable`** + filter; mirrors [`playground/snippets/tag-select/features.tsx`](../../../playground/snippets/tag-select/features.tsx). |
88
+ | [`examples/pattern-canonical.tsx`](./examples/pattern-canonical.tsx) | Full stack: **`creatable`**, **`optionManagement`** (⋯), controlled **`options`**; **`prime-ui-kit`** import. |
89
+ | [`examples/pattern-features.tsx`](./examples/pattern-features.tsx) | Same as canonical with **`@/`** imports; mirrors [`playground/snippets/tag-select/features.tsx`](../../../playground/snippets/tag-select/features.tsx). |
55
90
 
56
91
  ### Playground
57
92
 
58
- Live demo: **`playground/sections/TagSelectSection.tsx`** — snippet **`playground/snippets/tag-select/features.tsx`**.
93
+ Live demo: **`playground/sections/TagSelectSection.tsx`** — snippet **`playground/snippets/tag-select/features.tsx`** (same behavior as the canonical example).
59
94
 
60
95
  **LLM note:** Prefer runnable files under **`./examples/*.tsx`** for prop combinations; this page keeps the contract (rules + API) authoritative.
61
96
 
@@ -86,6 +121,7 @@ Live demo: **`playground/sections/TagSelectSection.tsx`** — snippet **`playgro
86
121
  | placeholder | `string` | `""` | No | Input placeholder |
87
122
  | hasError | `boolean` | `false` | No | Error styling |
88
123
  | size | `SelectSize` | `"m"` | No | Same size axis as **`Select.Root`** |
124
+ | optionManagement | `TagSelectOptionManagement` | — | No | Меню редактирования опции в списке (⋯): **`onUpdate`**, **`onDelete`**, опционально подписи |
89
125
  | id | `string` | — | No | Root id (listbox and input ids are derived) |
90
126
  | className | `string` | — | No | Extra class on the root |
91
127
  | aria-label | `string` | — | No | Accessible name |
@@ -100,6 +136,16 @@ Live demo: **`playground/sections/TagSelectSection.tsx`** — snippet **`playgro
100
136
  | color | `BadgeColor` | No | **`Badge`** **`filled`** color in the list |
101
137
  | disabled | `boolean` | No | Option not selectable |
102
138
 
139
+ ### TagSelectOptionManagement
140
+
141
+ | Field | Type | Required | Description |
142
+ |-------|------|----------|-------------|
143
+ | onUpdate | `(value: string, updates: { label?: string; color?: BadgeColor }) => void` | Yes | Обновить подпись и/или цвет; **`value`** опции не меняется |
144
+ | onDelete | `(value: string) => void` | Yes | Удалить опцию из справочника; выбранные значения с этим **`value`** снимаются |
145
+ | colorsSectionLabel | `string` | No | Заголовок блока цветов (по умолчанию «Colors») |
146
+ | deleteLabel | `string` | No | Подпись кнопки удаления (по умолчанию «Delete») |
147
+ | editMenuAriaLabelPrefix | `string` | No | Префикс **`aria-label`** кнопки ⋯ (по умолчанию «Edit tag») |
148
+
103
149
  ## Related
104
150
 
105
151
  - [Select](../select/COMPONENT.md)
@@ -1,27 +1,68 @@
1
+ import type { TagSelectOption } from "prime-ui-kit";
1
2
  import { TagSelect, Typography } from "prime-ui-kit";
2
3
  import * as React from "react";
3
4
 
4
5
  import styles from "./examples.module.css";
5
6
 
6
- /** Controlled multi-select from `options`; `prime-ui-kit` import for published consumers. */
7
+ const initialOptions: TagSelectOption[] = [
8
+ { value: "telegram", label: "Telegram", color: "blue" },
9
+ { value: "whatsapp", label: "WhatsApp", color: "green" },
10
+ { value: "facebook", label: "Facebook", color: "purple" },
11
+ { value: "viber", label: "Viber", color: "pink" },
12
+ ];
13
+
14
+ /**
15
+ * Full surface: controlled **`options`**, **`creatable`**, **`optionManagement`** (⋯ rename, colors, delete).
16
+ * New tags stay in the list until refresh; **`onUpdate`** must append when **`value`** is not yet in **`options`**.
17
+ */
7
18
  export default function TagSelectPatternCanonicalExample() {
8
- const [value, setValue] = React.useState<string[]>(["eng"]);
19
+ const [value, setValue] = React.useState<string[]>([]);
20
+ const [options, setOptions] = React.useState<TagSelectOption[]>(initialOptions);
9
21
 
10
22
  return (
11
23
  <div className={`${styles.stack} ${styles.stackNarrow}`}>
12
24
  <TagSelect.Root
13
- options={[
14
- { value: "eng", label: "Engineering", color: "blue" },
15
- { value: "design", label: "Design", color: "purple" },
16
- { value: "sales", label: "Sales", color: "green" },
17
- ]}
25
+ options={options}
18
26
  value={value}
19
27
  onValueChange={setValue}
20
- placeholder="Add team…"
21
- aria-label="Teams"
28
+ creatable
29
+ optionManagement={{
30
+ onUpdate: (tagValue, updates) => {
31
+ setOptions((prev) => {
32
+ const i = prev.findIndex((o) => o.value === tagValue);
33
+ if (i === -1) {
34
+ return [
35
+ ...prev,
36
+ {
37
+ value: tagValue,
38
+ label: updates.label ?? tagValue,
39
+ color: updates.color ?? "gray",
40
+ },
41
+ ];
42
+ }
43
+ return prev.map((o) =>
44
+ o.value === tagValue
45
+ ? {
46
+ ...o,
47
+ ...(updates.label !== undefined ? { label: updates.label } : {}),
48
+ ...(updates.color !== undefined ? { color: updates.color } : {}),
49
+ }
50
+ : o,
51
+ );
52
+ });
53
+ },
54
+ onDelete: (tagValue) => {
55
+ setOptions((prev) => prev.filter((o) => o.value !== tagValue));
56
+ },
57
+ colorsSectionLabel: "Colors",
58
+ deleteLabel: "Delete",
59
+ }}
60
+ placeholder="Channel…"
61
+ aria-label="Contact channels"
22
62
  />
23
63
  <Typography.Root as="p" variant="caption" tone="muted" className={styles.caption}>
24
- Selected: <code>{value.join(", ") || ""}</code>
64
+ Filter, create tags, use to rename, pick a color, or remove from the list. Selected:{" "}
65
+ <code>{value.join(", ") || "—"}</code>
25
66
  </Typography.Root>
26
67
  </div>
27
68
  );
@@ -1,31 +1,67 @@
1
1
  import * as React from "react";
2
2
 
3
+ import type { TagSelectOption } from "@/components/tag-select/TagSelect";
3
4
  import { TagSelect } from "@/components/tag-select/TagSelect";
4
5
  import { Typography } from "@/components/typography/Typography";
5
6
 
6
7
  import styles from "./examples.module.css";
7
8
 
8
- /** `creatable` + filter; mirrors `playground/snippets/tag-select/features.tsx`. */
9
+ const initialOptions: TagSelectOption[] = [
10
+ { value: "telegram", label: "Telegram", color: "blue" },
11
+ { value: "whatsapp", label: "WhatsApp", color: "green" },
12
+ { value: "facebook", label: "Facebook", color: "purple" },
13
+ { value: "viber", label: "Viber", color: "pink" },
14
+ ];
15
+
16
+ /** Same as `pattern-canonical.tsx` but `@/` imports; mirrors `playground/snippets/tag-select/features.tsx`. */
9
17
  export default function TagSelectPatternFeaturesExample() {
10
18
  const [value, setValue] = React.useState<string[]>([]);
19
+ const [options, setOptions] = React.useState<TagSelectOption[]>(initialOptions);
11
20
 
12
21
  return (
13
22
  <div className={`${styles.stack} ${styles.stackNarrow}`}>
14
23
  <TagSelect.Root
15
- options={[
16
- { value: "telegram", label: "telegram", color: "blue" },
17
- { value: "whatsapp", label: "whatsapp", color: "green" },
18
- { value: "facebook", label: "facebook", color: "purple" },
19
- { value: "viber", label: "viber", color: "pink" },
20
- ]}
24
+ options={options}
21
25
  value={value}
22
26
  onValueChange={setValue}
23
27
  creatable
28
+ optionManagement={{
29
+ onUpdate: (tagValue, updates) => {
30
+ setOptions((prev) => {
31
+ const i = prev.findIndex((o) => o.value === tagValue);
32
+ if (i === -1) {
33
+ return [
34
+ ...prev,
35
+ {
36
+ value: tagValue,
37
+ label: updates.label ?? tagValue,
38
+ color: updates.color ?? "gray",
39
+ },
40
+ ];
41
+ }
42
+ return prev.map((o) =>
43
+ o.value === tagValue
44
+ ? {
45
+ ...o,
46
+ ...(updates.label !== undefined ? { label: updates.label } : {}),
47
+ ...(updates.color !== undefined ? { color: updates.color } : {}),
48
+ }
49
+ : o,
50
+ );
51
+ });
52
+ },
53
+ onDelete: (tagValue) => {
54
+ setOptions((prev) => prev.filter((o) => o.value !== tagValue));
55
+ },
56
+ colorsSectionLabel: "Colors",
57
+ deleteLabel: "Delete",
58
+ }}
24
59
  placeholder="Channel…"
25
60
  aria-label="Contact channels"
26
61
  />
27
62
  <Typography.Root as="p" variant="caption" tone="muted" className={styles.caption}>
28
- Type to filter; add a new channel if it is not in the list.
63
+ Filter, create tags, use to rename, pick a color, or remove from the list. Selected:{" "}
64
+ <code>{value.join(", ") || "—"}</code>
29
65
  </Typography.Root>
30
66
  </div>
31
67
  );