@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.
- package/README.md +35 -18
- package/dist/docs/AGENTS.md +321 -0
- package/dist/docs/CreditWarningBanner.md +305 -0
- package/dist/docs/InvoiceHistoryMobile.md +222 -0
- package/dist/docs/ScAccess.md +259 -0
- package/dist/docs/ScAppCard.md +244 -0
- package/dist/docs/ScAppCardForCopilot.md +230 -0
- package/dist/docs/ScAppCardV3.md +273 -0
- package/dist/docs/ScAppField.md +308 -0
- package/dist/docs/ScAppListingCard.md +271 -0
- package/dist/docs/ScAppSwitchPanel.md +286 -0
- package/dist/docs/ScAppcardLogos.md +226 -0
- package/dist/docs/ScArtifaxInvite.md +262 -0
- package/dist/docs/ScArtifaxSidebar.md +330 -0
- package/dist/docs/ScAskAgentButton.md +307 -0
- package/dist/docs/ScBadges.md +261 -0
- package/dist/docs/ScBeacon.md +244 -0
- package/dist/docs/ScBillingHistoryHeader.md +210 -0
- package/dist/docs/ScBillingHistoryTableList.md +243 -0
- package/dist/docs/ScBillingLogsTableHeader.md +212 -0
- package/dist/docs/ScBillingLogsTableList.md +251 -0
- package/dist/docs/ScBriefCard.md +255 -0
- package/dist/docs/ScButton.md +251 -0
- package/dist/docs/ScCalendar.md +268 -0
- package/dist/docs/ScCalendarDateComps.md +264 -0
- package/dist/docs/ScCatalogixInvite.md +345 -0
- package/dist/docs/ScCatalogixSidebar.md +337 -0
- package/dist/docs/ScCatalogixStoreHeader.md +246 -0
- package/dist/docs/ScCatalogixStoreTableList.md +316 -0
- package/dist/docs/ScCheckField.md +233 -0
- package/dist/docs/ScCheckbox.md +272 -0
- package/dist/docs/ScCounter.md +235 -0
- package/dist/docs/ScCreditsUsageCard.md +247 -0
- package/dist/docs/ScCreditsUsageCardMobile.md +224 -0
- package/dist/docs/ScDefaultCard.md +269 -0
- package/dist/docs/ScDp.md +245 -0
- package/dist/docs/ScDrawer.md +318 -0
- package/dist/docs/ScFieldButton.md +255 -0
- package/dist/docs/ScFileField.md +268 -0
- package/dist/docs/ScGoogleSignIn.md +250 -0
- package/dist/docs/ScGuide.md +278 -0
- package/dist/docs/ScHDivider.md +213 -0
- package/dist/docs/ScHeader.md +222 -0
- package/dist/docs/ScImageField.md +253 -0
- package/dist/docs/ScInChatList.md +277 -0
- package/dist/docs/ScInChatMessage.md +205 -0
- package/dist/docs/ScInfoPopup.md +248 -0
- package/dist/docs/ScIntialProfileCover.md +233 -0
- package/dist/docs/ScInvoiceHistoryMobile.md +187 -0
- package/dist/docs/ScLogoUnit.md +232 -0
- package/dist/docs/ScMappingCard.md +241 -0
- package/dist/docs/ScMediaApproval.md +301 -0
- package/dist/docs/ScMediaSelect.md +310 -0
- package/dist/docs/ScMenuOptions.md +308 -0
- package/dist/docs/ScMobileBottomAction.md +252 -0
- package/dist/docs/ScMobileTopNav.md +279 -0
- package/dist/docs/ScModal.md +291 -0
- package/dist/docs/ScOnlyField.md +302 -0
- package/dist/docs/ScOnlyIcon.md +213 -0
- package/dist/docs/ScPagination.md +284 -0
- package/dist/docs/ScPairtext.md +287 -0
- package/dist/docs/ScPendingAction.md +238 -0
- package/dist/docs/ScPhtogenixInvite.md +275 -0
- package/dist/docs/ScPlanCard.md +302 -0
- package/dist/docs/ScPlanComparison.md +264 -0
- package/dist/docs/ScPlanDetailsCard.md +246 -0
- package/dist/docs/ScPlanDetailsCardMobile.md +240 -0
- package/dist/docs/ScPopUpMenu.md +224 -0
- package/dist/docs/ScProfile.md +234 -0
- package/dist/docs/ScProfileImageUpdate.md +261 -0
- package/dist/docs/ScProfileOptions.md +245 -0
- package/dist/docs/ScProfilePopup.md +396 -0
- package/dist/docs/ScProfileSettingsComp.md +250 -0
- package/dist/docs/ScProfileV2Mobile.md +216 -0
- package/dist/docs/ScProgressBar.md +267 -0
- package/dist/docs/ScQuickPrompt.md +277 -0
- package/dist/docs/ScRadio.md +228 -0
- package/dist/docs/ScReferralCardMobile.md +226 -0
- package/dist/docs/ScReferralTableHeader.md +260 -0
- package/dist/docs/ScReferralTableList.md +293 -0
- package/dist/docs/ScRole.md +226 -0
- package/dist/docs/ScRoleMobile.md +199 -0
- package/dist/docs/ScSelect.md +270 -0
- package/dist/docs/ScSelection.md +256 -0
- package/dist/docs/ScSelectionList.md +272 -0
- package/dist/docs/ScSelectionPill.md +240 -0
- package/dist/docs/ScSelectionPillGroup.md +302 -0
- package/dist/docs/ScSettingsNav.md +212 -0
- package/dist/docs/ScSettingsTabComp.md +260 -0
- package/dist/docs/ScSideBarLogoUnit.md +340 -0
- package/dist/docs/ScSidebar.md +243 -0
- package/dist/docs/ScSidebarIcons.md +232 -0
- package/dist/docs/ScSidebarMenu.md +283 -0
- package/dist/docs/ScSidebarProfile.md +231 -0
- package/dist/docs/ScSidebarSwitchMenu.md +258 -0
- package/dist/docs/ScSlider.md +194 -0
- package/dist/docs/ScStoreCard.md +252 -0
- package/dist/docs/ScStrLogo.md +253 -0
- package/dist/docs/ScStreamoidWordmark.md +302 -0
- package/dist/docs/ScSubAgent.md +226 -0
- package/dist/docs/ScTabComp.md +308 -0
- package/dist/docs/ScTabField.md +258 -0
- package/dist/docs/ScTabSwitcher.md +307 -0
- package/dist/docs/ScTableHeader.md +261 -0
- package/dist/docs/ScTableList.md +301 -0
- package/dist/docs/ScTableListMobile.md +282 -0
- package/dist/docs/ScTabs.md +268 -0
- package/dist/docs/ScTaxonomyPill.md +263 -0
- package/dist/docs/ScTextArea.md +259 -0
- package/dist/docs/ScTextField.md +324 -0
- package/dist/docs/ScThinkingStepIcon.md +249 -0
- package/dist/docs/ScTodoList.md +288 -0
- package/dist/docs/ScToggleSwitch.md +229 -0
- package/dist/docs/ScUsageHistoryMobile.md +194 -0
- package/dist/docs/ScVDivider.md +215 -0
- package/dist/docs/ScValueMappingL1.md +256 -0
- package/dist/docs/ScVersion.md +251 -0
- package/dist/docs/ScWorkspace.md +233 -0
- package/dist/docs/ScWorkspaceCard.md +234 -0
- package/dist/docs/ScWorkspaceSettingsMobile.md +265 -0
- package/dist/docs/ScWorkspaceSwitchCard.md +314 -0
- package/dist/docs/ScWorkspaceSwitchMobile.md +241 -0
- package/dist/docs/ScWorkspaceSwitchMobileV2.md +278 -0
- package/dist/docs/StreamoidSidebar.md +403 -0
- package/dist/docs/StreamoidWorkspaceSwitcher.md +307 -0
- package/dist/docs/UsageHistoryMobile.md +235 -0
- package/dist/docs/components.json +4849 -0
- package/dist/index.css +36 -36
- package/dist/index.d.mts +10 -0
- package/dist/index.d.ts +10 -0
- 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.
|