@streamoid/ui 0.6.17 → 0.6.18

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.
Files changed (131) hide show
  1. package/README.md +35 -18
  2. package/dist/docs/AGENTS.md +321 -0
  3. package/dist/docs/CreditWarningBanner.md +305 -0
  4. package/dist/docs/InvoiceHistoryMobile.md +222 -0
  5. package/dist/docs/ScAccess.md +259 -0
  6. package/dist/docs/ScAppCard.md +244 -0
  7. package/dist/docs/ScAppCardForCopilot.md +230 -0
  8. package/dist/docs/ScAppCardV3.md +273 -0
  9. package/dist/docs/ScAppField.md +308 -0
  10. package/dist/docs/ScAppListingCard.md +271 -0
  11. package/dist/docs/ScAppSwitchPanel.md +286 -0
  12. package/dist/docs/ScAppcardLogos.md +226 -0
  13. package/dist/docs/ScArtifaxInvite.md +262 -0
  14. package/dist/docs/ScArtifaxSidebar.md +330 -0
  15. package/dist/docs/ScAskAgentButton.md +307 -0
  16. package/dist/docs/ScBadges.md +261 -0
  17. package/dist/docs/ScBeacon.md +244 -0
  18. package/dist/docs/ScBillingHistoryHeader.md +210 -0
  19. package/dist/docs/ScBillingHistoryTableList.md +243 -0
  20. package/dist/docs/ScBillingLogsTableHeader.md +212 -0
  21. package/dist/docs/ScBillingLogsTableList.md +251 -0
  22. package/dist/docs/ScBriefCard.md +255 -0
  23. package/dist/docs/ScButton.md +251 -0
  24. package/dist/docs/ScCalendar.md +268 -0
  25. package/dist/docs/ScCalendarDateComps.md +264 -0
  26. package/dist/docs/ScCatalogixInvite.md +345 -0
  27. package/dist/docs/ScCatalogixSidebar.md +337 -0
  28. package/dist/docs/ScCatalogixStoreHeader.md +246 -0
  29. package/dist/docs/ScCatalogixStoreTableList.md +316 -0
  30. package/dist/docs/ScCheckField.md +233 -0
  31. package/dist/docs/ScCheckbox.md +272 -0
  32. package/dist/docs/ScCounter.md +235 -0
  33. package/dist/docs/ScCreditsUsageCard.md +247 -0
  34. package/dist/docs/ScCreditsUsageCardMobile.md +224 -0
  35. package/dist/docs/ScDefaultCard.md +269 -0
  36. package/dist/docs/ScDp.md +245 -0
  37. package/dist/docs/ScDrawer.md +318 -0
  38. package/dist/docs/ScFieldButton.md +255 -0
  39. package/dist/docs/ScFileField.md +268 -0
  40. package/dist/docs/ScGoogleSignIn.md +250 -0
  41. package/dist/docs/ScGuide.md +278 -0
  42. package/dist/docs/ScHDivider.md +213 -0
  43. package/dist/docs/ScHeader.md +222 -0
  44. package/dist/docs/ScImageField.md +253 -0
  45. package/dist/docs/ScInChatList.md +277 -0
  46. package/dist/docs/ScInChatMessage.md +205 -0
  47. package/dist/docs/ScInfoPopup.md +248 -0
  48. package/dist/docs/ScIntialProfileCover.md +233 -0
  49. package/dist/docs/ScInvoiceHistoryMobile.md +187 -0
  50. package/dist/docs/ScLogoUnit.md +232 -0
  51. package/dist/docs/ScMappingCard.md +241 -0
  52. package/dist/docs/ScMediaApproval.md +301 -0
  53. package/dist/docs/ScMediaSelect.md +310 -0
  54. package/dist/docs/ScMenuOptions.md +308 -0
  55. package/dist/docs/ScMobileBottomAction.md +252 -0
  56. package/dist/docs/ScMobileTopNav.md +279 -0
  57. package/dist/docs/ScModal.md +291 -0
  58. package/dist/docs/ScOnlyField.md +302 -0
  59. package/dist/docs/ScOnlyIcon.md +213 -0
  60. package/dist/docs/ScPagination.md +284 -0
  61. package/dist/docs/ScPairtext.md +287 -0
  62. package/dist/docs/ScPendingAction.md +238 -0
  63. package/dist/docs/ScPhtogenixInvite.md +275 -0
  64. package/dist/docs/ScPlanCard.md +302 -0
  65. package/dist/docs/ScPlanComparison.md +264 -0
  66. package/dist/docs/ScPlanDetailsCard.md +246 -0
  67. package/dist/docs/ScPlanDetailsCardMobile.md +240 -0
  68. package/dist/docs/ScPopUpMenu.md +224 -0
  69. package/dist/docs/ScProfile.md +234 -0
  70. package/dist/docs/ScProfileImageUpdate.md +261 -0
  71. package/dist/docs/ScProfileOptions.md +245 -0
  72. package/dist/docs/ScProfilePopup.md +396 -0
  73. package/dist/docs/ScProfileSettingsComp.md +250 -0
  74. package/dist/docs/ScProfileV2Mobile.md +216 -0
  75. package/dist/docs/ScProgressBar.md +267 -0
  76. package/dist/docs/ScQuickPrompt.md +277 -0
  77. package/dist/docs/ScRadio.md +228 -0
  78. package/dist/docs/ScReferralCardMobile.md +226 -0
  79. package/dist/docs/ScReferralTableHeader.md +260 -0
  80. package/dist/docs/ScReferralTableList.md +293 -0
  81. package/dist/docs/ScRole.md +226 -0
  82. package/dist/docs/ScRoleMobile.md +199 -0
  83. package/dist/docs/ScSelect.md +270 -0
  84. package/dist/docs/ScSelection.md +256 -0
  85. package/dist/docs/ScSelectionList.md +272 -0
  86. package/dist/docs/ScSelectionPill.md +240 -0
  87. package/dist/docs/ScSelectionPillGroup.md +302 -0
  88. package/dist/docs/ScSettingsNav.md +212 -0
  89. package/dist/docs/ScSettingsTabComp.md +260 -0
  90. package/dist/docs/ScSideBarLogoUnit.md +340 -0
  91. package/dist/docs/ScSidebar.md +243 -0
  92. package/dist/docs/ScSidebarIcons.md +232 -0
  93. package/dist/docs/ScSidebarMenu.md +283 -0
  94. package/dist/docs/ScSidebarProfile.md +231 -0
  95. package/dist/docs/ScSidebarSwitchMenu.md +258 -0
  96. package/dist/docs/ScSlider.md +194 -0
  97. package/dist/docs/ScStoreCard.md +252 -0
  98. package/dist/docs/ScStrLogo.md +253 -0
  99. package/dist/docs/ScStreamoidWordmark.md +302 -0
  100. package/dist/docs/ScSubAgent.md +226 -0
  101. package/dist/docs/ScTabComp.md +308 -0
  102. package/dist/docs/ScTabField.md +258 -0
  103. package/dist/docs/ScTabSwitcher.md +307 -0
  104. package/dist/docs/ScTableHeader.md +261 -0
  105. package/dist/docs/ScTableList.md +301 -0
  106. package/dist/docs/ScTableListMobile.md +282 -0
  107. package/dist/docs/ScTabs.md +268 -0
  108. package/dist/docs/ScTaxonomyPill.md +263 -0
  109. package/dist/docs/ScTextArea.md +259 -0
  110. package/dist/docs/ScTextField.md +324 -0
  111. package/dist/docs/ScThinkingStepIcon.md +249 -0
  112. package/dist/docs/ScTodoList.md +288 -0
  113. package/dist/docs/ScToggleSwitch.md +229 -0
  114. package/dist/docs/ScUsageHistoryMobile.md +194 -0
  115. package/dist/docs/ScVDivider.md +215 -0
  116. package/dist/docs/ScValueMappingL1.md +256 -0
  117. package/dist/docs/ScVersion.md +251 -0
  118. package/dist/docs/ScWorkspace.md +233 -0
  119. package/dist/docs/ScWorkspaceCard.md +234 -0
  120. package/dist/docs/ScWorkspaceSettingsMobile.md +265 -0
  121. package/dist/docs/ScWorkspaceSwitchCard.md +314 -0
  122. package/dist/docs/ScWorkspaceSwitchMobile.md +241 -0
  123. package/dist/docs/ScWorkspaceSwitchMobileV2.md +278 -0
  124. package/dist/docs/StreamoidSidebar.md +403 -0
  125. package/dist/docs/StreamoidWorkspaceSwitcher.md +307 -0
  126. package/dist/docs/UsageHistoryMobile.md +235 -0
  127. package/dist/docs/components.json +4849 -0
  128. package/dist/index.css +36 -36
  129. package/dist/index.d.mts +10 -0
  130. package/dist/index.d.ts +10 -0
  131. package/package.json +3 -2
@@ -0,0 +1,240 @@
1
+ ---
2
+ component: ScSelectionPill
3
+ package: "@streamoid/ui"
4
+ category: selection
5
+ status: stable
6
+ renders: button[type="button"][aria-pressed]
7
+ tags: [pill, segmented, tab, filter, chip, toggle, single-select]
8
+ related: [ScSelectionPillGroup, ScTabs, ScTabSwitcher, ScTaxonomyPill, ScBadges]
9
+ do_not_confuse_with: [ScSelection, ScTaxonomyPill, ScBadges, ScTabComp]
10
+ used_by: [catalogix]
11
+ ---
12
+
13
+ # ScSelectionPill
14
+
15
+ **One pill in a segmented single-choice row.** Selected renders as a dark fill with
16
+ inverse text. Almost always used through `ScSelectionPillGroup`, which owns the
17
+ active value for you.
18
+
19
+ ## TL;DR for agents
20
+
21
+ - **Reach for it when:** you need a segmented control — 2–5 mutually exclusive
22
+ views or filters shown as pills, all visible at once.
23
+ - **Don't reach for it when:** you want the in-chat radio row (→ `ScSelection`,
24
+ a completely different component), a taxonomy tree node (→ `ScTaxonomyPill`),
25
+ or a non-interactive status chip (→ `ScBadges`).
26
+ - **Two things that will bite you:**
27
+ 1. `ScSelection` is **not** the singular of `ScSelectionPill`. Different family,
28
+ different surface. See "Don't confuse with".
29
+ 2. This pill is stateless. It renders `selected` — it does not track it. Use
30
+ `ScSelectionPillGroup` unless you're deliberately owning the state yourself.
31
+
32
+ ---
33
+
34
+ ## 1. How to use it
35
+
36
+ ### Import
37
+
38
+ ```tsx
39
+ import { ScSelectionPill, ScSelectionPillGroup } from "@streamoid/ui";
40
+ import "@streamoid/ui/dist/index.css"; // once, at your app root
41
+ ```
42
+
43
+ ### Minimal usage
44
+
45
+ Prefer the group — it wires selection for you:
46
+
47
+ ```tsx
48
+ <ScSelectionPillGroup
49
+ options={[
50
+ { label: "Attributes", value: "attributes" },
51
+ { label: "Pose Type", value: "pose_types" },
52
+ ]}
53
+ value={activeTab}
54
+ onChange={setActiveTab}
55
+ />
56
+ ```
57
+
58
+ A single pill, if you own the state:
59
+
60
+ ```tsx
61
+ <ScSelectionPill
62
+ label="All Products"
63
+ value="all"
64
+ selected={filter === "all"}
65
+ onSelect={setFilter}
66
+ />
67
+ ```
68
+
69
+ ### Props
70
+
71
+ `ScSelectionPill`
72
+
73
+ | Prop | Type | Default | Notes |
74
+ |---|---|---|---|
75
+ | `label` | `string` | `"All Products"` | Pill text. ⚠️ Has a real default — always set it. |
76
+ | `selected` | `boolean` | `false` | Dark fill + inverse text. Also surfaced as `aria-pressed`. |
77
+ | `value` | `string` | – | Passed back to `onSelect`. Omit and you get `onSelect(undefined)`. |
78
+ | `onSelect` | `(value?: string) => void` | – | Fires **after** `onClick`, with `value`. The handler you normally want. |
79
+ | `onClick` | `MouseEventHandler` | – | Native click, fires **first**. Both run — see Gotcha 2. |
80
+ | `className` | `string` | – | Appended after internal classes. |
81
+ | `...props` | `ButtonHTMLAttributes<HTMLButtonElement>` (minus `onSelect`) | – | Spread onto the `<button>`. |
82
+
83
+ `ScSelectionPillGroup`
84
+
85
+ | Prop | Type | Default | Notes |
86
+ |---|---|---|---|
87
+ | `options` | `{ label: string; value: string }[]` | – | Rendered in order. |
88
+ | `value` | `string` | – | Value of the active pill. Controlled — you must supply it. |
89
+ | `onChange` | `(value: string) => void` | – | Fires with the clicked pill's value. |
90
+ | `className` | `string` | – | Appended after internal classes. |
91
+ | `...props` | `HTMLAttributes<HTMLDivElement>` (minus `onChange`) | – | Spread onto the row. |
92
+
93
+ ### Recipes
94
+
95
+ ```tsx
96
+ // Counts in labels — build the string, the pill takes plain text
97
+ <ScSelectionPillGroup
98
+ options={[
99
+ { label: `Attributes (${selected.length})`, value: "attributes" },
100
+ { label: "Pose Type", value: "pose_types" },
101
+ ]}
102
+ value={activeTab}
103
+ onChange={setActiveTab}
104
+ />
105
+
106
+ // Owning state yourself over a dynamic list
107
+ <div style={{ display: "flex", gap: 8 }}>
108
+ {stores.map((s) => (
109
+ <ScSelectionPill
110
+ key={s.id}
111
+ label={s.name}
112
+ value={s.id}
113
+ selected={s.id === activeStoreId}
114
+ onSelect={setActiveStoreId}
115
+ />
116
+ ))}
117
+ </div>
118
+ ```
119
+
120
+ ---
121
+
122
+ ## 2. Where to use it
123
+
124
+ - **Above a list or grid that it filters** — the Catalogix Curation screen puts a
125
+ group directly over the attribute list it scopes.
126
+ - **Section headers inside a panel** — switching what the panel body shows without
127
+ navigating.
128
+ - **Inside `ScDrawer` / `ScModal`** — segmenting a form or picker into 2–3 views.
129
+
130
+ Catalogix is the current consumer (Curation V2). Nothing outside the segmented-row
131
+ pattern should reach for it.
132
+
133
+ ---
134
+
135
+ ## 3. When to use it
136
+
137
+ ### Use it when
138
+
139
+ - The choices are **mutually exclusive** and **few** (2–5), and showing all of
140
+ them at once is useful context.
141
+ - The switch changes **what is displayed**, not what is saved.
142
+
143
+ ### Don't use it — reach for this instead
144
+
145
+ | Situation | Use instead |
146
+ |---|---|
147
+ | Full-width in-page tab bar, possibly wrapping/compact | `ScTabs` |
148
+ | Compact 2–3 way view toggle in a page header | `ScTabSwitcher` (+ `ScTabComp`) |
149
+ | Settings-page tab strip | `ScSettingsTabComp` / `ScSettingsNav` |
150
+ | Segmented single-choice **inside a form** (a saved value) | `ScTabField` |
151
+ | A node in a taxonomy/hierarchy tree, with expand/collapse counts | `ScTaxonomyPill` |
152
+ | Non-interactive status/count chip | `ScBadges` |
153
+ | Multi-select (more than one active) | `ScCheckbox` / `ScCheckField` — this pill is single-select by design |
154
+ | An option row in the agent chat transcript | `ScSelection` / `ScSelectionList` |
155
+
156
+ ### Don't confuse with
157
+
158
+ | You may actually want | Not this |
159
+ |---|---|
160
+ | `ScSelection` — in-chat radio row with title + description, agent runtime | `ScSelectionPill` is a host-app segmented tab |
161
+ | `ScSelectionList` — in-chat approve/reject toggle row | Also agent runtime, unrelated to pills |
162
+ | `ScTaxonomyPill` — tree node with `expand`/`collapse` types and a child count | Visually similar, structurally different |
163
+
164
+ The `ScSelection*` (no "Pill") family belongs to the **chat/agent runtime**
165
+ vocabulary. `ScSelectionPill*` belongs to the **dashboards**. The names are the
166
+ single biggest source of wrong picks in this library.
167
+
168
+ ---
169
+
170
+ ## 4. Why to use it
171
+
172
+ - **The selected skin is a token pair, not a colour.** Dark fill + inverse text
173
+ resolves correctly in both themes; a hand-rolled pill typically inverts wrongly
174
+ in light mode.
175
+ - **Real button semantics.** Unlike `ScButton`, this renders an actual
176
+ `<button type="button">` with `aria-pressed` tracking `selected`, so screen
177
+ readers announce the toggle state and it can't accidentally submit a form.
178
+ - **The group removes the boilerplate** you'd otherwise repeat at every call site
179
+ (map, compare, set) and guarantees exactly one active pill.
180
+ - **Consistent with the rest of the segmented vocabulary** — pill radius, gap and
181
+ padding match `ScTabs` and `ScTaxonomyPill`.
182
+
183
+ ---
184
+
185
+ ## Gotchas
186
+
187
+ **1. `label` defaults to `"All Products"`.** Forget it and you ship Catalogix copy.
188
+
189
+ ```tsx
190
+ // WRONG — renders "All Products"
191
+ <ScSelectionPill value="pose" selected />
192
+
193
+ // RIGHT
194
+ <ScSelectionPill label="Pose Type" value="pose" selected />
195
+ ```
196
+
197
+ **2. `onClick` and `onSelect` both fire, in that order.** Don't wire the same
198
+ handler to both or it runs twice.
199
+
200
+ ```tsx
201
+ // WRONG — setFilter called twice per click
202
+ <ScSelectionPill onClick={() => setFilter("all")} onSelect={setFilter} value="all" />
203
+
204
+ // RIGHT — pick one; onSelect is the idiomatic choice
205
+ <ScSelectionPill onSelect={setFilter} value="all" />
206
+ ```
207
+
208
+ **3. It's controlled, always.** Neither the pill nor the group keeps internal
209
+ state. If you don't pass `selected` / `value`, nothing ever looks active.
210
+
211
+ **4. `onSelect` yields `undefined` when `value` is unset.** Always pass `value`
212
+ alongside `onSelect`.
213
+
214
+ **5. The group is not a `<fieldset>`/radiogroup.** It's a div of `aria-pressed`
215
+ buttons. For a genuine form radio group, use `ScRadio` or `ScTabField`.
216
+
217
+ ---
218
+
219
+ ## In the wild
220
+
221
+ ```tsx
222
+ // catalogix/dashboard app/containers/StoreSettingsV2/CurationV2/index.jsx:368
223
+ <ScSelectionPillGroup
224
+ options={[
225
+ { label: `Attributes (${selectedAttributeValues.length})`, value: "attributes" },
226
+ { label: "Pose Type", value: "pose_types" },
227
+ ]}
228
+ value={activeTab}
229
+ onChange={(tab) => setActiveTab(tab)}
230
+ />
231
+ ```
232
+
233
+ ---
234
+
235
+ ## Related
236
+
237
+ - `ScSelectionPillGroup` — the controlled wrapper; use this by default.
238
+ - `ScTabs` / `ScTabSwitcher` / `ScTabComp` — the tab-bar vocabulary.
239
+ - `ScTaxonomyPill` — hierarchy node chip with expand/collapse.
240
+ - `ScSelection` / `ScSelectionList` — the unrelated in-chat selection family.
@@ -0,0 +1,302 @@
1
+ ---
2
+ component: ScSelectionPillGroup
3
+ package: "@streamoid/ui"
4
+ category: feed-taxonomy
5
+ status: stable
6
+ renders: div[role="tablist"]
7
+ tags: [segmented, pills, tabs, tablist, filter, single-select, controlled, group, catalogix]
8
+ related: [ScSelectionPill, ScTabs, ScTabSwitcher, ScTabField, ScTaxonomyPill]
9
+ do_not_confuse_with: [ScSelection, ScSelectionList, ScTabs, ScTabSwitcher, ScSettingsTabComp]
10
+ used_by: [catalogix]
11
+ ---
12
+
13
+ # ScSelectionPillGroup
14
+
15
+ **The controlled wrapper around a row of `ScSelectionPill`s.** You hand it
16
+ `options`, the current `value` and `onChange`; it renders one pill per option, marks
17
+ exactly one selected, and wires the click. The root is a
18
+ `div[role="tablist"]` with `flex-wrap: wrap` and a `0.5rem` gap.
19
+
20
+ ## TL;DR for agents
21
+
22
+ - **Reach for it when:** you need a segmented control — 2–5 mutually exclusive views
23
+ or filters, all visible at once — and you don't want to hand-roll the
24
+ map/compare/set boilerplate.
25
+ - **Don't reach for it when:** you want the in-chat option rows (→ `ScSelection` /
26
+ `ScSelectionList`, a different family in a different package's surface), a
27
+ full-width page tab bar (→ `ScTabs`), a settings tab strip
28
+ (→ `ScSettingsTabComp`), a saved form value (→ `ScTabField`), or taxonomy tree
29
+ chips (→ `ScTaxonomyPill`).
30
+ - **Four things that will bite you:**
31
+ 1. Fully **controlled**. No internal state — if `value` doesn't match an option's
32
+ `value` exactly, nothing looks selected.
33
+ 2. Options are **label + value only**. No icons, no counts-as-badges, no per-option
34
+ `disabled`. Bake counts into the label string.
35
+ 3. It claims `role="tablist"` / `role="tab"` but wires **no `aria-controls`, no
36
+ tabpanel and no arrow-key navigation** — an incomplete ARIA tabs pattern.
37
+ 4. Duplicate `option.value`s break it twice over: duplicate React keys *and* every
38
+ matching pill renders selected.
39
+
40
+ ---
41
+
42
+ ## 1. How to use it
43
+
44
+ ### Import
45
+
46
+ ```tsx
47
+ import { ScSelectionPillGroup } from "@streamoid/ui";
48
+ import "@streamoid/ui/dist/index.css"; // once, at your app root
49
+ ```
50
+
51
+ `ScSelectionPill` is imported internally — you don't need it unless you're building
52
+ something the group can't express (see Gotcha 3).
53
+
54
+ ### Minimal usage
55
+
56
+ ```tsx
57
+ <ScSelectionPillGroup
58
+ options={[
59
+ { label: "Attributes", value: "attributes" },
60
+ { label: "Pose Type", value: "pose_types" },
61
+ ]}
62
+ value={activeTab}
63
+ onChange={setActiveTab}
64
+ />
65
+ ```
66
+
67
+ ### Props
68
+
69
+ `ScSelectionPillGroup`
70
+
71
+ | Prop | Type | Default | Notes |
72
+ |---|---|---|---|
73
+ | `options` | `IScSelectionPillOption[]` | `[]` | Rendered in source order. Defaults to empty → an empty (but still `width: 100%`) row. |
74
+ | `value` | `string` | – | `value` of the selected pill. Compared with `===`, so it is case- and whitespace-sensitive. |
75
+ | `onChange` | `(value: string) => void` | – | Fires with the clicked option's `value`. Coerced: an option with no `value` yields `""`. |
76
+ | `className` | `string` | – | Appended after the internal class. |
77
+ | `...props` | `HTMLAttributes<HTMLDivElement>` **minus `onChange`** | – | Spread onto the root **after** `role="tablist"`, so `role`/`aria-*` are overridable. |
78
+
79
+ `IScSelectionPillOption`
80
+
81
+ | Field | Type | Notes |
82
+ |---|---|---|
83
+ | `label` | `string` | **Required.** Plain text — no `ReactNode`, so no icons or markup. |
84
+ | `value` | `string` | **Required.** Also used as the React `key`. Must be unique. |
85
+
86
+ ### What the group sets on each pill
87
+
88
+ | Attribute | Value | Note |
89
+ |---|---|---|
90
+ | `label` | `option.label` | |
91
+ | `value` | `option.value` | |
92
+ | `selected` | `option.value === value` | drives the pill's `aria-pressed` too |
93
+ | `role` | `"tab"` | |
94
+ | `aria-selected` | `option.value === value` | |
95
+ | `onSelect` | `(v) => onChange?.(v ?? "")` | |
96
+
97
+ So each pill carries **both** `aria-pressed` and `aria-selected`. See Gotcha 4.
98
+
99
+ ### Recipes
100
+
101
+ ```tsx
102
+ // Counts belong in the label string — there is no count/badge slot
103
+ <ScSelectionPillGroup
104
+ options={[
105
+ { label: `Attributes (${selectedAttributeValues.length})`, value: "attributes" },
106
+ { label: "Pose Type", value: "pose_types" },
107
+ ]}
108
+ value={activeTab}
109
+ onChange={(tab) => setActiveTab(tab)}
110
+ />
111
+
112
+ // Derived from data — guarantee unique values
113
+ <ScSelectionPillGroup
114
+ options={channels.map((c) => ({ label: c.name, value: c.id }))}
115
+ value={activeChannelId}
116
+ onChange={setActiveChannelId}
117
+ />
118
+
119
+ // If it isn't really a tab strip, drop the tablist role rather than shipping a
120
+ // half-implemented ARIA tabs pattern
121
+ <ScSelectionPillGroup
122
+ role="group"
123
+ aria-label="Filter products"
124
+ options={filters}
125
+ value={filter}
126
+ onChange={setFilter}
127
+ />
128
+
129
+ // Completing the ARIA contract instead (only worth it for a real tab strip)
130
+ <>
131
+ <ScSelectionPillGroup aria-label="Curation views" options={views} value={view} onChange={setView} />
132
+ <div role="tabpanel" aria-label={activeViewLabel}>{body}</div>
133
+ </>
134
+ ```
135
+
136
+ ---
137
+
138
+ ## 2. Where to use it
139
+
140
+ - **Directly above the list or grid it scopes.** Catalogix Curation V2 puts a group
141
+ in `.select-heading`, over the `ScValueMappingL1` list of attribute values, with a
142
+ rule between them. Switching the pill swaps the whole panel body (attribute values
143
+ ↔ a pose-type multi-select).
144
+ - **Section headers inside a panel** — changing what the panel body shows without
145
+ navigating.
146
+ - **Inside `ScDrawer` / `ScModal`** to split a form or picker into 2–3 views.
147
+
148
+ The root is `width: 100%` and `flex-wrap: wrap`, so it fills its column and wraps to
149
+ a second line rather than scrolling. Put it in a container that is already the width
150
+ you want.
151
+
152
+ ---
153
+
154
+ ## 3. When to use it
155
+
156
+ ### Use it when
157
+
158
+ - Choices are **mutually exclusive** and **few** (2–5), and showing all of them at
159
+ once is useful context.
160
+ - The switch changes **what is displayed**, not what is saved.
161
+ - You already hold the active value in state (URL param, reducer, `useState`).
162
+
163
+ ### Don't use it — reach for this instead
164
+
165
+ | Situation | Use instead |
166
+ |---|---|
167
+ | Full-width in-page tab bar with underline/active rail | `ScTabs` |
168
+ | Compact 2–3 way view toggle in a page header | `ScTabSwitcher` (+ `ScTabComp`) |
169
+ | Settings-page tab strip / left nav | `ScSettingsTabComp` / `ScSettingsNav` |
170
+ | Segmented single-choice **inside a form**, whose value is saved | `ScTabField` |
171
+ | One pill whose state you own yourself, or a pill that must be `disabled` | `ScSelectionPill` directly |
172
+ | Multi-select (more than one active at a time) | `ScCheckField` / `ScCheckbox` — this group is single-select by construction |
173
+ | Taxonomy tree node chips with counts and expand/collapse | `ScTaxonomyPill` |
174
+ | Non-interactive status/count chips | `ScBadges` |
175
+ | Option rows in the agent chat transcript | `ScSelection` / `ScSelectionList` (chat runtime) |
176
+
177
+ ### Don't confuse with
178
+
179
+ | You may actually want | Not this |
180
+ |---|---|
181
+ | `ScSelection` — in-chat radio row with title + description; **chat/agent runtime** (stream-agent / `@streamoid/agent`), not the dashboards | `ScSelectionPillGroup` is a host-app segmented row |
182
+ | `ScSelectionList` — in-chat approve/reject toggle row; also chat runtime | Unrelated to pills despite the name |
183
+ | `ScTabs` — tab bar with tab chrome | This is a pill row; the two are not interchangeable skins of one control |
184
+ | `ScTaxonomyPill` — visually similar chip, but a tree node with `expand`/`collapse` types and no selected state | Different family; note both `ScSelectionPill` and `ScTaxonomyPill` default `label` to `"All Products"` |
185
+ | `ScSelectionPill` — the leaf this group renders | Import the group unless you need per-pill control |
186
+
187
+ `ScSelection*` (no "Pill") is **chat runtime**. `ScSelectionPill*` is **dashboards**.
188
+ This naming collision is the single biggest source of wrong picks in the library.
189
+
190
+ ---
191
+
192
+ ## 4. Why to use it
193
+
194
+ - **It removes the boilerplate you'd otherwise repeat at every call site** — the map,
195
+ the `===` compare, the setter — and guarantees exactly one pill is selected.
196
+ - **Real `<button>` semantics per pill.** Unlike `ScButton` (a `div[role="button"]`),
197
+ each pill is a native `<button type="button">`, so Enter/Space work for free and it
198
+ can't accidentally submit a surrounding form.
199
+ - **The selected skin is a token pair, not a colour.**
200
+ `--alias-fill-base-basehover` fill + `--alias-text-and-icons-inverse` text, so the
201
+ selected pill stays high-contrast in both themes. Hand-rolled pills are a classic
202
+ light-mode inversion bug.
203
+ - **Keyboard focus is already right.** The pill kills the mouse-click outline but
204
+ keeps a `:focus-visible` ring on `--alias-border-focus`.
205
+ - **Consistent metrics in one place.** The pill's `0.5rem 1rem` padding and
206
+ `radius-3xl` and the group's `0.5rem` gap live in two CSS modules, so every
207
+ segmented row in a host looks identical. (They are *not* shared with the other
208
+ pill-ish components: `ScTaxonomyPill` uses `radius-xl` and `ScTabs` a `100px`
209
+ radius with a `12px` gap — don't mix them in one row expecting parity.)
210
+
211
+ ---
212
+
213
+ ## Gotchas
214
+
215
+ **1. It is controlled, always.** No internal state. A `value` that doesn't `===` an
216
+ option's `value` leaves every pill unselected — and the very first render is the
217
+ usual victim.
218
+
219
+ ```tsx
220
+ // WRONG — nothing is selected; "Attributes" !== "attributes"
221
+ <ScSelectionPillGroup options={opts} value="Attributes" onChange={setTab} />
222
+
223
+ // RIGHT — seed state from an option's value
224
+ const [tab, setTab] = useState(opts[0].value);
225
+ <ScSelectionPillGroup options={opts} value={tab} onChange={setTab} />
226
+ ```
227
+
228
+ **2. `options` defaults to `[]`.** A typo'd prop name renders an empty div that still
229
+ occupies a `width: 100%` flex line — it looks like a layout gap, not an error.
230
+
231
+ **3. Options are label + value only.** `label` is `string`, not `ReactNode`, and
232
+ there is no `disabled`, `icon` or `count` field. Counts go into the string
233
+ (`` `Attributes (${n})` ``). Anything else means dropping to `ScSelectionPill`
234
+ directly — which, being a real `<button>`, does accept the native `disabled`
235
+ attribute the group can't pass through.
236
+
237
+ **4. The ARIA tabs pattern is incomplete.** The group sets `role="tablist"`, each
238
+ pill gets `role="tab"` + `aria-selected`, and the pill itself also sets
239
+ `aria-pressed` — so every pill announces two different selection states. There is no
240
+ `aria-controls`, no `id`/`tabpanel` pairing, and no arrow-key roving tabindex (Tab
241
+ steps through every pill). Either finish the contract or override the role:
242
+
243
+ ```tsx
244
+ // RIGHT when it's just a filter row, not a tab strip
245
+ <ScSelectionPillGroup role="group" aria-label="Filter" options={opts} value={v} onChange={setV} />
246
+ ```
247
+
248
+ `role` is applied **before** `...props`, so your override wins. Same for `aria-*`.
249
+
250
+ **5. `option.value` is the React key.** Duplicates give you a key warning *and*
251
+ multiple pills rendering selected at once, because selection is `value ===` and not
252
+ an index compare.
253
+
254
+ **6. `onChange` never receives `undefined`.** The group does `onChange?.(v ?? "")`.
255
+ In a JS host (Catalogix is `.jsx`) an option built without a `value` therefore fires
256
+ `onChange("")` rather than throwing — a silent "nothing selected" loop.
257
+
258
+ **7. "Selected = dark fill" is the light-theme description.** The token pair is
259
+ `--alias-fill-base-basehover` + `--alias-text-and-icons-inverse`, which in the
260
+ **dark** theme paints a *light grey* pill with dark text. That's correct and
261
+ intentional (maximum contrast against a dark canvas) — just don't read the JSDoc
262
+ literally and "fix" it.
263
+
264
+ **8. The group has no `disabled` and no busy state.** Clicks always fire. If a view
265
+ is loading, gate it in your `onChange` handler.
266
+
267
+ **9. It wraps, it doesn't scroll.** `flex-wrap: wrap` on a `width: 100%` root, so
268
+ 7 long labels silently become two rows and shift the layout below. Keep option counts
269
+ low, or shorten labels.
270
+
271
+ ---
272
+
273
+ ## In the wild
274
+
275
+ ```jsx
276
+ // catalogix/dashboard app/containers/StoreSettingsV2/CurationV2/index.jsx:368
277
+ <ScSelectionPillGroup
278
+ options={[
279
+ {
280
+ label: `Attributes (${selectedAttributeValues.length})`,
281
+ value: "attributes",
282
+ },
283
+ { label: "Pose Type", value: "pose_types" },
284
+ ]}
285
+ value={activeTab}
286
+ onChange={(tab) => setActiveTab(tab)}
287
+ />
288
+ ```
289
+
290
+ The same screen renders the `ScValueMappingL1` list underneath it — the pill group
291
+ picks the mode, the value rows are the list body.
292
+
293
+ ---
294
+
295
+ ## Related
296
+
297
+ - `ScSelectionPill` — the leaf; use it directly only when you need per-pill control (`disabled`, custom ordering, your own container).
298
+ - `ScTabs` / `ScTabSwitcher` / `ScTabComp` / `ScSettingsTabComp` — the tab-bar vocabulary; pick by surface, not by looks.
299
+ - `ScTabField` — segmented single-choice that is a *form value*.
300
+ - `ScValueMappingL1` — the list this group usually sits above in Catalogix Curation.
301
+ - `ScTaxonomyPill` — the tree-node chip family; shares the `"All Products"` default label trap.
302
+ - `ScSelection` / `ScSelectionList` — the unrelated chat-runtime selection family.