@streamoid/ui 0.6.17 → 0.6.19
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 +325 -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 +210 -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/ScWorkspaceAccountMenu.md +115 -0
- package/dist/docs/ScWorkspaceCard.md +234 -0
- package/dist/docs/ScWorkspaceSettingsMobile.md +265 -0
- package/dist/docs/ScWorkspaceSwitchCard.md +312 -0
- package/dist/docs/ScWorkspaceSwitchMobile.md +241 -0
- package/dist/docs/ScWorkspaceSwitchMobileV2.md +278 -0
- package/dist/docs/StreamoidSidebar.md +413 -0
- package/dist/docs/StreamoidWorkspaceSwitcher.md +307 -0
- package/dist/docs/UsageHistoryMobile.md +235 -0
- package/dist/docs/components.json +4931 -0
- package/dist/index.css +361 -36
- package/dist/index.d.mts +213 -88
- package/dist/index.d.ts +213 -88
- package/dist/index.js +2486 -1629
- package/dist/index.mjs +2487 -1620
- package/package.json +5 -3
|
@@ -0,0 +1,268 @@
|
|
|
1
|
+
---
|
|
2
|
+
component: ScTabs
|
|
3
|
+
package: "@streamoid/ui"
|
|
4
|
+
category: layout
|
|
5
|
+
status: stable
|
|
6
|
+
renders: div
|
|
7
|
+
tags: [tabs, tab-bar, pills, filter, segmented, n-tabs, view-switch, wrap, compact]
|
|
8
|
+
related: [ScTabComp, ScTabSwitcher, ScTabField, ScSettingsTabComp, ScSelectionPillGroup]
|
|
9
|
+
do_not_confuse_with: [ScTabComp, ScTabSwitcher, ScTabField, ScSettingsTabComp, ScSelectionPill]
|
|
10
|
+
used_by: [catalogix]
|
|
11
|
+
required_props: [tabs]
|
|
12
|
+
---
|
|
13
|
+
|
|
14
|
+
# ScTabs
|
|
15
|
+
|
|
16
|
+
**The data-driven N-tab pill bar.** You hand it an array — strings or
|
|
17
|
+
`{ label, value }` — and it renders one rounded pill per entry, calling
|
|
18
|
+
`onClickTab(value)` on click. It is the only tab component in the library that scales
|
|
19
|
+
past five tabs and the only one that takes its tabs as data.
|
|
20
|
+
|
|
21
|
+
## TL;DR for agents
|
|
22
|
+
|
|
23
|
+
- **Reach for it when:** you have an arbitrary-length list of views/filters (6, 12,
|
|
24
|
+
20 tabs) and want them as a wrapping row of pills.
|
|
25
|
+
- **Don't reach for it when:** you have exactly 2–5 tabs in a bordered segmented
|
|
26
|
+
control (→ `ScTabSwitcher` + `ScTabComp`), an underlined settings strip
|
|
27
|
+
(→ `ScSettingsTabComp`), or a segmented control inside a form (→ `ScTabField`).
|
|
28
|
+
- **Four things that will bite you:**
|
|
29
|
+
1. The **active pill's background is `--alias-surface-canvas`** — the page floor
|
|
30
|
+
colour. On a canvas-coloured background, "active" is invisible. Put it on a card.
|
|
31
|
+
2. `activeTab` is compared against the **value**, never the label.
|
|
32
|
+
3. Labels are transformed: `_` → space, and CSS `text-transform: capitalize`.
|
|
33
|
+
You cannot render a lowercase label.
|
|
34
|
+
4. **No props are spread.** No `style`, no `id`, no `aria-*`, no `data-*` — and no
|
|
35
|
+
roles, no keyboard: they're `<div onClick>`s.
|
|
36
|
+
|
|
37
|
+
---
|
|
38
|
+
|
|
39
|
+
## 1. How to use it
|
|
40
|
+
|
|
41
|
+
### Import
|
|
42
|
+
|
|
43
|
+
```tsx
|
|
44
|
+
import { ScTabs } from "@streamoid/ui";
|
|
45
|
+
import type { ScTab } from "@streamoid/ui"; // only if you type the array yourself
|
|
46
|
+
import "@streamoid/ui/dist/index.css"; // once, at your app root
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
### Minimal usage
|
|
50
|
+
|
|
51
|
+
```tsx
|
|
52
|
+
<ScTabs tabs={["all", "images", "videos"]} activeTab={tab} onClickTab={setTab} />
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
### Props
|
|
56
|
+
|
|
57
|
+
| Prop | Type | Default | Notes |
|
|
58
|
+
|---|---|---|---|
|
|
59
|
+
| `tabs` | `ScTab[]` where `ScTab = string \| { label?: string; value?: string }` | — | **Required.** Strings and objects can be mixed. For objects, `label` falls back to `value` and `value` falls back to `label`; both missing → an empty pill with value `""`. |
|
|
60
|
+
| `activeTab` | `string` | – | Matched against each tab's **value**. Controlled — the component keeps no state. |
|
|
61
|
+
| `onClickTab` | `(value: string) => void` | – | Called with the resolved value. Optional; omit and the pills are inert. |
|
|
62
|
+
| `isAutoWidth` | `boolean` | `undefined` (→ full width) | `width: max-content` instead of `width: 100%`. |
|
|
63
|
+
| `allowWrap` | `boolean` | `undefined` | `flex-wrap: wrap` + `row-gap: 8px`. Needed for long tab lists. |
|
|
64
|
+
| `isCompact` | `boolean` | `undefined` | Denser pills: gap 12→8px, padding 6/12→4/10px, font 14→13px. |
|
|
65
|
+
| `className` | `string` | – | Appended last (properly filtered — no stray `"undefined"`). |
|
|
66
|
+
|
|
67
|
+
### Sizing / density matrix
|
|
68
|
+
|
|
69
|
+
| Flag | Row | Pill |
|
|
70
|
+
|---|---|---|
|
|
71
|
+
| *(none)* | `width: 100%`, `gap: 12px`, no wrap | `padding: 6px 12px`, `14px/20px` |
|
|
72
|
+
| `isAutoWidth` | `width: max-content` | unchanged |
|
|
73
|
+
| `allowWrap` | wraps, `row-gap: 8px` | unchanged |
|
|
74
|
+
| `isCompact` | `gap: 8px` | `padding: 4px 10px`, `13px/18px` |
|
|
75
|
+
|
|
76
|
+
### Recipes
|
|
77
|
+
|
|
78
|
+
```tsx
|
|
79
|
+
// Long filter list above a grid — wrap and go compact
|
|
80
|
+
<ScTabs
|
|
81
|
+
tabs={TAB_OPTIONS} // e.g. ["all", "in_progress", "failed", …]
|
|
82
|
+
activeTab={activeTab}
|
|
83
|
+
onClickTab={setActiveTab}
|
|
84
|
+
allowWrap
|
|
85
|
+
isCompact
|
|
86
|
+
/>
|
|
87
|
+
|
|
88
|
+
// Object tabs: display text differs from the state value
|
|
89
|
+
<ScTabs
|
|
90
|
+
tabs={[
|
|
91
|
+
{ label: "In progress", value: "in_progress" },
|
|
92
|
+
{ label: "Failed", value: "error" },
|
|
93
|
+
]}
|
|
94
|
+
activeTab={status} // must be "in_progress" / "error", NOT the label
|
|
95
|
+
onClickTab={setStatus}
|
|
96
|
+
/>
|
|
97
|
+
|
|
98
|
+
// Shrink-wrapped, inside a header row next to other controls
|
|
99
|
+
<div className="flex items-center justify-between">
|
|
100
|
+
<ScTabs tabs={views} activeTab={view} onClickTab={setView} isAutoWidth />
|
|
101
|
+
<ScButton text="Export" variant="outline" size="sm" />
|
|
102
|
+
</div>
|
|
103
|
+
```
|
|
104
|
+
|
|
105
|
+
---
|
|
106
|
+
|
|
107
|
+
## 2. Where to use it
|
|
108
|
+
|
|
109
|
+
- **Above a list or grid that it filters** — Catalogix's Assets → Activity panel is
|
|
110
|
+
the live consumer, via a thin local `Tabs` wrapper that just forwards props.
|
|
111
|
+
- **Panel/section switching** where the set of sections comes from data (a config,
|
|
112
|
+
an API response, a taxonomy) rather than being hardcoded in JSX.
|
|
113
|
+
- Anywhere the tab count exceeds five: `ScTabSwitcher` physically cannot render a
|
|
114
|
+
sixth tab.
|
|
115
|
+
|
|
116
|
+
Catalogix is the only host app using it today.
|
|
117
|
+
|
|
118
|
+
---
|
|
119
|
+
|
|
120
|
+
## 3. When to use it
|
|
121
|
+
|
|
122
|
+
### Use it when
|
|
123
|
+
|
|
124
|
+
- The tab list is **dynamic** or **long** (data-driven, wrapping).
|
|
125
|
+
- You want a controlled `activeTab` / `onClickTab` pair rather than wiring
|
|
126
|
+
`active`/`onClick` on every individual tab.
|
|
127
|
+
|
|
128
|
+
### Don't use it — reach for this instead
|
|
129
|
+
|
|
130
|
+
| Situation | Use instead |
|
|
131
|
+
|---|---|
|
|
132
|
+
| 2–5 tabs in the bordered "pill container" segmented control | `ScTabSwitcher` with `component`…`component5` slots of `ScTabComp` |
|
|
133
|
+
| One individual tab cell (icon-only or text-only) | `ScTabComp` |
|
|
134
|
+
| A segmented control **inside a form**, with a field label | `ScTabField` |
|
|
135
|
+
| Settings / mobile-settings underline tab strip | `ScSettingsTabComp` |
|
|
136
|
+
| The static settings left-nav card | `ScSettingsNav` (legacy — see its README) |
|
|
137
|
+
| Filter pills whose selected state must be a dark fill + inverse text | `ScSelectionPillGroup` |
|
|
138
|
+
| A taxonomy tree node with a count and chevron | `ScTaxonomyPill` |
|
|
139
|
+
| Non-interactive status chips | `ScBadges` |
|
|
140
|
+
|
|
141
|
+
### Don't confuse with
|
|
142
|
+
|
|
143
|
+
| You may actually want | Not this |
|
|
144
|
+
|---|---|
|
|
145
|
+
| `ScTabComp` — a single tab cell whose `active` prop is the **string** `"true"`/`"false"` | `ScTabs` owns the whole row and takes `activeTab` as a value string |
|
|
146
|
+
| `ScTabSwitcher` — a fixed 2–5 slot container with a border and background | `ScTabs` is a borderless, unbounded pill row |
|
|
147
|
+
| `ScSelectionPill` — a real `<button aria-pressed>` | `ScTabs` renders `<div onClick>` with no a11y |
|
|
148
|
+
| `ScSettingsTabComp` — underline tab, `active` is a **boolean** | `ScTabs` pills are filled, not underlined |
|
|
149
|
+
|
|
150
|
+
### The tab family at a glance
|
|
151
|
+
|
|
152
|
+
| Component | Shape | Selection API | Root | Spreads DOM props? |
|
|
153
|
+
|---|---|---|---|---|
|
|
154
|
+
| `ScTabs` | N pills, data-driven | `activeTab` + `onClickTab(value)` | `div` > `div` | ❌ |
|
|
155
|
+
| `ScTabComp` | one tab cell | `active="true" \| "false"` (strings) | `div` | ✅ |
|
|
156
|
+
| `ScTabSwitcher` | 2–5 slot container | none — children own it | `div` | ✅ |
|
|
157
|
+
| `ScTabField` | field label + 2-tab switcher | `activeTab` + `onTabChange` | `div` | ❌ |
|
|
158
|
+
| `ScSettingsTabComp` | underline tab | `active` (boolean) | `div` | ❌ |
|
|
159
|
+
| `ScSelectionPill` | one segmented pill | `selected` (boolean) + `onSelect(value)` | `button[type="button"][aria-pressed]` | ✅ |
|
|
160
|
+
| `ScSelectionPillGroup` | segmented pill row | `value` + `onChange(value)` | `div[role="tablist"]` of `button`s | ✅ |
|
|
161
|
+
|
|
162
|
+
---
|
|
163
|
+
|
|
164
|
+
## 4. Why to use it
|
|
165
|
+
|
|
166
|
+
- **The only tab component that takes data.** Every other tab in the library is a
|
|
167
|
+
hand-placed slot; this one maps an array, so config-driven screens don't need N
|
|
168
|
+
hardcoded JSX branches.
|
|
169
|
+
- **`allowWrap` + `isCompact` solve the long-list problem** that `ScTabSwitcher`
|
|
170
|
+
can't: a 12-tab activity filter fits in two rows without horizontal scroll.
|
|
171
|
+
- **String tabs are tolerated on purpose.** `"in_progress"` renders as
|
|
172
|
+
"In Progress" without a formatting helper at the call site — which is exactly why
|
|
173
|
+
Catalogix could drop its own `Tabs` implementation and keep its API.
|
|
174
|
+
- **Token-styled**, so the active pill and the idle border track the theme instead of
|
|
175
|
+
a hardcoded grey.
|
|
176
|
+
|
|
177
|
+
---
|
|
178
|
+
|
|
179
|
+
## Gotchas
|
|
180
|
+
|
|
181
|
+
**1. The active pill can be invisible.** `.active { background: var(--alias-surface-canvas) }`
|
|
182
|
+
and the idle pill's border is the *same* token. `surface-canvas` is the page floor
|
|
183
|
+
(`#f1f1f1` in light, near-black in dark), so on a canvas-coloured background the
|
|
184
|
+
selected pill has no contrast against its surroundings.
|
|
185
|
+
|
|
186
|
+
```tsx
|
|
187
|
+
// WRONG — page background is the canvas; "active" reads as nothing
|
|
188
|
+
<main style={{ background: "var(--alias-surface-canvas)" }}>
|
|
189
|
+
<ScTabs tabs={views} activeTab={view} onClickTab={setView} />
|
|
190
|
+
</main>
|
|
191
|
+
|
|
192
|
+
// RIGHT — sit it on a raised surface
|
|
193
|
+
<section style={{ background: "var(--alias-surface-base)" }}>
|
|
194
|
+
<ScTabs tabs={views} activeTab={view} onClickTab={setView} />
|
|
195
|
+
</section>
|
|
196
|
+
```
|
|
197
|
+
|
|
198
|
+
**2. `activeTab` matches the value, not the label.**
|
|
199
|
+
|
|
200
|
+
```tsx
|
|
201
|
+
// WRONG — nothing ever looks active
|
|
202
|
+
<ScTabs tabs={[{ label: "In progress", value: "in_progress" }]} activeTab="In progress" />
|
|
203
|
+
|
|
204
|
+
// RIGHT
|
|
205
|
+
<ScTabs tabs={[{ label: "In progress", value: "in_progress" }]} activeTab="in_progress" />
|
|
206
|
+
```
|
|
207
|
+
|
|
208
|
+
**3. Your label text is rewritten.** Underscores become spaces
|
|
209
|
+
(`label.replace(/_/g, " ")`) and `text-transform: capitalize` title-cases every word.
|
|
210
|
+
`"api_key"` renders as "Api Key". If you need exact casing (`"API key"`), this
|
|
211
|
+
component cannot give it to you.
|
|
212
|
+
|
|
213
|
+
**4. Nothing is spread onto the DOM.** `style`, `id`, `data-testid`, `aria-label`,
|
|
214
|
+
`role` are all dropped — the signature only accepts the seven props above.
|
|
215
|
+
Wrap it in your own div for layout and test hooks.
|
|
216
|
+
|
|
217
|
+
**5. No a11y and no keyboard.** Pills are `<div onClick>`: not focusable, no
|
|
218
|
+
`role="tablist"`/`role="tab"`, no `aria-selected`, no arrow-key navigation. If the
|
|
219
|
+
surface needs to be accessible, supply the roles on a wrapper and consider
|
|
220
|
+
`ScSelectionPillGroup` (real buttons with `aria-pressed`) instead.
|
|
221
|
+
|
|
222
|
+
**6. It is full width by default.** `width: 100%` — in a flex row it will eat the
|
|
223
|
+
available space and push siblings out. Pass `isAutoWidth` when it shares a row.
|
|
224
|
+
|
|
225
|
+
**7. Empty/duplicate tabs fail quietly.** `{}` produces a blank pill with value `""`;
|
|
226
|
+
if `activeTab` is `""` (a common "no filter" sentinel) that blank pill renders
|
|
227
|
+
active. Two tabs with the same value both light up. React keys are
|
|
228
|
+
`` `${idx}-${value}` ``, so duplicates don't warn.
|
|
229
|
+
|
|
230
|
+
**8. `isAutoWidth` / `allowWrap` / `isCompact` are un-defaulted booleans**, so
|
|
231
|
+
`undefined` means "off". There is no way to force full width once `isAutoWidth` is
|
|
232
|
+
`true` other than not passing it.
|
|
233
|
+
|
|
234
|
+
---
|
|
235
|
+
|
|
236
|
+
## In the wild
|
|
237
|
+
|
|
238
|
+
```jsx
|
|
239
|
+
// catalogix/dashboard app/components/Tabs/index.jsx:10
|
|
240
|
+
<ScTabs
|
|
241
|
+
tabs={props.tabs}
|
|
242
|
+
activeTab={props.activeTab}
|
|
243
|
+
onClickTab={props.onClickTab}
|
|
244
|
+
isAutoWidth={props.isAutoWidth}
|
|
245
|
+
allowWrap={props.allowWrap}
|
|
246
|
+
isCompact={props.isCompact}
|
|
247
|
+
/>
|
|
248
|
+
|
|
249
|
+
// …consumed at catalogix/dashboard app/components/Assets/Activity/index.jsx:1209
|
|
250
|
+
<Tabs
|
|
251
|
+
tabs={TAB_OPTIONS}
|
|
252
|
+
activeTab={activeTab}
|
|
253
|
+
allowWrap={true}
|
|
254
|
+
isCompact={true}
|
|
255
|
+
onClickTab={setActiveTab}
|
|
256
|
+
/>
|
|
257
|
+
```
|
|
258
|
+
|
|
259
|
+
---
|
|
260
|
+
|
|
261
|
+
## Related
|
|
262
|
+
|
|
263
|
+
- `ScTabComp` — one tab cell; the atom of the 2–5 tab segmented control.
|
|
264
|
+
- `ScTabSwitcher` — the bordered 2–5 slot container for `ScTabComp`s.
|
|
265
|
+
- `ScTabField` — form field wrapping a 2-tab switcher with a label.
|
|
266
|
+
- `ScSettingsTabComp` — the underline tab used by settings strips.
|
|
267
|
+
- `ScSelectionPillGroup` — accessible segmented pills (real buttons) when a11y matters.
|
|
268
|
+
- `ScTaxonomyPill` — hierarchy chips, visually adjacent but structurally unrelated.
|
|
@@ -0,0 +1,263 @@
|
|
|
1
|
+
---
|
|
2
|
+
component: ScTaxonomyPill
|
|
3
|
+
package: "@streamoid/ui"
|
|
4
|
+
category: feed-taxonomy
|
|
5
|
+
status: stable
|
|
6
|
+
renders: div
|
|
7
|
+
tags: [taxonomy, hierarchy, tree, node, chip, pill, expand, collapse, chevron, catalogix, ontology]
|
|
8
|
+
related: [ScSelectionPill, ScSelectionPillGroup, ScValueMappingL1, ScBadges, ScDefaultCard]
|
|
9
|
+
do_not_confuse_with: [ScSelectionPill, ScSelectionPillGroup, ScBadges, ScSelection]
|
|
10
|
+
used_by: [catalogix]
|
|
11
|
+
---
|
|
12
|
+
|
|
13
|
+
# ScTaxonomyPill
|
|
14
|
+
|
|
15
|
+
**The node chip of a taxonomy tree.** One component with three mutually exclusive
|
|
16
|
+
renders: a labelled node (`pill`), a "N more children" affordance (`expand`, count +
|
|
17
|
+
down chevron), and a "fold this level away" affordance (`collapse`, up chevron only).
|
|
18
|
+
|
|
19
|
+
## TL;DR for agents
|
|
20
|
+
|
|
21
|
+
- **Reach for it when:** you're drawing a category/ontology tree and need the node
|
|
22
|
+
chips and their expand/collapse controls to look like one family.
|
|
23
|
+
- **Don't reach for it when:** you want a segmented single-choice row
|
|
24
|
+
(→ `ScSelectionPill` / `ScSelectionPillGroup`), a non-interactive status or count
|
|
25
|
+
chip (→ `ScBadges`), or a flat selectable list row (→ `ScValueMappingL1`).
|
|
26
|
+
- **Four things that will bite you:**
|
|
27
|
+
1. `label` defaults to **`"All Products"`** and `count` defaults to **`3`**. Both
|
|
28
|
+
are real Catalogix copy, so a forgotten prop looks like working UI.
|
|
29
|
+
2. `type` decides which props are read. `expand` ignores `label`; `collapse`
|
|
30
|
+
ignores both.
|
|
31
|
+
3. It's a `<div>` with `cursor: pointer` and **no click handling of its own** — no
|
|
32
|
+
`role`, no `tabIndex`, no `onKeyDown`. The host wraps it.
|
|
33
|
+
4. `expand` and `collapse` are **two separate renders you choose between**, not a
|
|
34
|
+
toggle. There is no `expanded` boolean.
|
|
35
|
+
|
|
36
|
+
---
|
|
37
|
+
|
|
38
|
+
## 1. How to use it
|
|
39
|
+
|
|
40
|
+
### Import
|
|
41
|
+
|
|
42
|
+
```tsx
|
|
43
|
+
import { ScTaxonomyPill } from "@streamoid/ui";
|
|
44
|
+
import "@streamoid/ui/dist/index.css"; // once, at your app root
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
### Minimal usage
|
|
48
|
+
|
|
49
|
+
```tsx
|
|
50
|
+
<ScTaxonomyPill type="pill" label="Topwear" />
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
### Props
|
|
54
|
+
|
|
55
|
+
| Prop | Type | Default | Notes |
|
|
56
|
+
|---|---|---|---|
|
|
57
|
+
| `type` | `"pill"` \| `"expand"` \| `"collapse"` | `"pill"` | Which of the three renders you get. See the matrix below. |
|
|
58
|
+
| `state` | `"default"` \| `"hover"` | `"default"` | Forced visual state. Real `:hover` applies the same skin, so you rarely need this outside screenshots/Figma parity. |
|
|
59
|
+
| `label` | `string` | `"All Products"` | ⚠️ Has a real default. **Only read when `type="pill"`.** Never wraps (`white-space: nowrap`). |
|
|
60
|
+
| `count` | `number \| string` | `3` | ⚠️ Has a real default. **Only read when `type="expand"`.** `string` is allowed, so `"12+"` works. |
|
|
61
|
+
| `className` | `string` | – | Appended after the internal classes. |
|
|
62
|
+
| `...props` | `HTMLAttributes<HTMLDivElement>` | – | `onClick`, `role`, `tabIndex`, `aria-*`, `style` all land on the root div. |
|
|
63
|
+
|
|
64
|
+
### What renders for each `type`
|
|
65
|
+
|
|
66
|
+
| `type` | Renders | Props read | Props ignored | Inner padding |
|
|
67
|
+
|---|---|---|---|---|
|
|
68
|
+
| `pill` | the `label` in a `text-sm` span | `label` | `count` | `0.5rem 1rem` |
|
|
69
|
+
| `expand` | the `count`, then a `SiconDown` chevron | `count` | `label` | `0.125rem 0.25rem 0.125rem 0.5rem` |
|
|
70
|
+
| `collapse` | a `SiconUp` chevron, nothing else | – | `label`, `count` | `0.25rem` |
|
|
71
|
+
|
|
72
|
+
Both chevrons are hard-coded and locked to `0.75rem` square with `!important`.
|
|
73
|
+
|
|
74
|
+
### Recipes
|
|
75
|
+
|
|
76
|
+
```tsx
|
|
77
|
+
// A node with its expand affordance (the host owns both click targets)
|
|
78
|
+
<div className="node-row">
|
|
79
|
+
<div role="button" tabIndex={0} onClick={() => selectNode(node)}
|
|
80
|
+
onKeyDown={(e) => (e.key === "Enter" || e.key === " ") && selectNode(node)}>
|
|
81
|
+
<ScTaxonomyPill type="pill" label={node.groupName} />
|
|
82
|
+
</div>
|
|
83
|
+
|
|
84
|
+
{node.groups.length > 0 && !isExpanded && (
|
|
85
|
+
<div role="button" tabIndex={0} aria-label={`Show ${node.groups.length} sub-groups`}
|
|
86
|
+
onClick={() => expand(node)}>
|
|
87
|
+
<ScTaxonomyPill type="expand" count={node.groups.length} />
|
|
88
|
+
</div>
|
|
89
|
+
)}
|
|
90
|
+
</div>
|
|
91
|
+
|
|
92
|
+
// The fold-away control, once the level is open
|
|
93
|
+
{isExpanded && (
|
|
94
|
+
<div role="button" tabIndex={0} aria-label="Collapse level" onClick={() => collapse(node)}>
|
|
95
|
+
<ScTaxonomyPill type="collapse" />
|
|
96
|
+
</div>
|
|
97
|
+
)}
|
|
98
|
+
|
|
99
|
+
// The synthetic root node of an empty taxonomy — the default label IS the copy here
|
|
100
|
+
<ScTaxonomyPill type="pill" label="All Products" />
|
|
101
|
+
|
|
102
|
+
// Non-numeric count
|
|
103
|
+
<ScTaxonomyPill type="expand" count="99+" />
|
|
104
|
+
```
|
|
105
|
+
|
|
106
|
+
---
|
|
107
|
+
|
|
108
|
+
## 2. Where to use it
|
|
109
|
+
|
|
110
|
+
- **Catalogix taxonomy / ontology trees.** `RecursiveTaxonomyTree` renders all three
|
|
111
|
+
types: `pill` per node, `expand` on any node with hidden children, `collapse` on the
|
|
112
|
+
open branch.
|
|
113
|
+
- **The Create-Taxonomy hierarchy page**, where a single `pill` labelled
|
|
114
|
+
"All Products" stands in as the root before any taxonomy exists.
|
|
115
|
+
- Any future **hierarchy browser** — attribute trees, category maps, folder trees —
|
|
116
|
+
where nodes are connected by drawn lines and each node needs a compact chip.
|
|
117
|
+
|
|
118
|
+
It composes nothing and it is never rendered *by* another DS component; hosts place
|
|
119
|
+
it inside their own tree layout (the connector lines, indentation and click targets
|
|
120
|
+
are all host CSS).
|
|
121
|
+
|
|
122
|
+
---
|
|
123
|
+
|
|
124
|
+
## 3. When to use it
|
|
125
|
+
|
|
126
|
+
### Use it when
|
|
127
|
+
|
|
128
|
+
- The chips sit in a **hierarchy**: nodes plus per-branch expand/collapse controls.
|
|
129
|
+
- You want the node chip, the count affordance and the fold control to share one
|
|
130
|
+
radius, one fill and one hover behaviour.
|
|
131
|
+
- The count next to a node means **"children hidden behind this"**, not a status.
|
|
132
|
+
|
|
133
|
+
### Don't use it — reach for this instead
|
|
134
|
+
|
|
135
|
+
| Situation | Use instead |
|
|
136
|
+
|---|---|
|
|
137
|
+
| Segmented single-choice row (2–5 mutually exclusive views) | `ScSelectionPillGroup` (+ `ScSelectionPill`) |
|
|
138
|
+
| A pill that needs to look **selected** and report it to a screen reader | `ScSelectionPill` — this component has no `selected` state and no `aria-pressed` |
|
|
139
|
+
| Non-interactive status / count badge | `ScBadges` |
|
|
140
|
+
| Flat vertical list of selectable values | `ScValueMappingL1` |
|
|
141
|
+
| "Pick one of these options" card with a title and description | `ScDefaultCard` |
|
|
142
|
+
| A chevron-only disclosure button in a toolbar or form | `ScOnlyIcon` — `type="collapse"` is a tree control, not a generic icon button |
|
|
143
|
+
| An option row in the agent chat transcript | `ScSelection` / `ScSelectionList` (chat runtime, unrelated) |
|
|
144
|
+
|
|
145
|
+
### Don't confuse with
|
|
146
|
+
|
|
147
|
+
| You may actually want | Not this |
|
|
148
|
+
|---|---|
|
|
149
|
+
| `ScSelectionPill` — real `<button type="button">`, `aria-pressed`, `selected` skin, `onSelect(value)` | `ScTaxonomyPill` is a `<div>` with no selection concept at all |
|
|
150
|
+
| `ScSelectionPillGroup` — renders and tracks a row of pills for you | This component renders exactly one chip and tracks nothing |
|
|
151
|
+
| `ScBadges` — count/status chip that is *not* clickable | This chip has `cursor: pointer` baked in, so it always looks clickable |
|
|
152
|
+
| `ScSelection` / `ScSelectionList` — chat-runtime option rows | Different family, different surface (stream-agent) |
|
|
153
|
+
|
|
154
|
+
Both `ScTaxonomyPill` and `ScSelectionPill` default `label` to **`"All Products"`**.
|
|
155
|
+
That shared default is the single easiest way to grab the wrong one and not notice.
|
|
156
|
+
Deciding rule: *pills in a tree* → `ScTaxonomyPill`; *pills in a row that filters
|
|
157
|
+
something* → `ScSelectionPill`.
|
|
158
|
+
|
|
159
|
+
---
|
|
160
|
+
|
|
161
|
+
## 4. Why to use it
|
|
162
|
+
|
|
163
|
+
- **One chip family across three roles.** Node, count and fold controls share
|
|
164
|
+
`--alias-fill-neutral-neutralactive`, `--alias-border-default` and the same
|
|
165
|
+
`radius-xl`, so a tree reads as one object instead of three widgets.
|
|
166
|
+
- **The hover token is the DS's neutral hover**
|
|
167
|
+
(`fill-neutral-neutralhovertoactive`), matching pills, menu rows and list items
|
|
168
|
+
elsewhere — hand-rolled trees usually invent a different hover grey per screen.
|
|
169
|
+
- **Chevron colour follows the chip.** The icons are rendered with an explicit
|
|
170
|
+
`color="currentColor"` prop (no `cloneElement`), `.icon` is `color: inherit`, and
|
|
171
|
+
the chip sets `color` from
|
|
172
|
+
`--alias-text-and-icons-primary`, so chevrons never drift from the label colour or
|
|
173
|
+
break in light mode.
|
|
174
|
+
- **`count` accepts a string**, so overflow formatting (`"99+"`) needs no wrapper.
|
|
175
|
+
|
|
176
|
+
---
|
|
177
|
+
|
|
178
|
+
## Gotchas
|
|
179
|
+
|
|
180
|
+
**1. `label` defaults to `"All Products"`, `count` defaults to `3`.** Forget either
|
|
181
|
+
and you ship plausible-looking placeholder data.
|
|
182
|
+
|
|
183
|
+
```tsx
|
|
184
|
+
// WRONG — renders "All Products"
|
|
185
|
+
<ScTaxonomyPill type="pill" />
|
|
186
|
+
|
|
187
|
+
// WRONG — renders "3" regardless of the real child count
|
|
188
|
+
<ScTaxonomyPill type="expand" />
|
|
189
|
+
|
|
190
|
+
// RIGHT
|
|
191
|
+
<ScTaxonomyPill type="pill" label={node.groupName} />
|
|
192
|
+
<ScTaxonomyPill type="expand" count={node.groups.length} />
|
|
193
|
+
```
|
|
194
|
+
|
|
195
|
+
**2. `type` silently drops the props it doesn't use.** `label` on an `expand` pill and
|
|
196
|
+
either prop on a `collapse` pill are dead code.
|
|
197
|
+
|
|
198
|
+
```tsx
|
|
199
|
+
// WRONG — the label never renders; you get the count "7" and a chevron
|
|
200
|
+
<ScTaxonomyPill type="expand" label="Topwear" count={7} />
|
|
201
|
+
```
|
|
202
|
+
|
|
203
|
+
**3. `type="collapse"` has no accessible name at all.** It renders a bare chevron
|
|
204
|
+
`<svg>` — no text, no `aria-label`, no `<title>`. Put `aria-label` on the pill (or on
|
|
205
|
+
your wrapper) or screen readers announce nothing.
|
|
206
|
+
|
|
207
|
+
**4. It doesn't handle clicks.** `cursor: pointer` is styling only. `onClick` works
|
|
208
|
+
because `...props` is spread onto the div, but you still get no keyboard access —
|
|
209
|
+
add `role="button"`, `tabIndex={0}` and `onKeyDown`. The Catalogix tree wraps each
|
|
210
|
+
pill in its own handler div and calls `e.stopPropagation()` on the expand control so
|
|
211
|
+
the node's own click doesn't also fire.
|
|
212
|
+
|
|
213
|
+
**5. There is no `expanded` prop.** `expand` and `collapse` are separate renders you
|
|
214
|
+
switch between based on your own state. A single `<ScTaxonomyPill type="expand">` will
|
|
215
|
+
never turn into a collapse control by itself.
|
|
216
|
+
|
|
217
|
+
**6. No `selected` / `active` state.** Unlike `ScValueMappingL1` (`active`) and
|
|
218
|
+
`ScSelectionPill` (`selected`), `state` is only `default | hover`. "This is the
|
|
219
|
+
current node" styling must come from your `className`.
|
|
220
|
+
|
|
221
|
+
**7. The chevron size is unoverridable.** The icons render at their own `size={24}`
|
|
222
|
+
default, but `.icon { width: .75rem !important; height: .75rem !important }` clamps
|
|
223
|
+
them to 12px and beats your className too. Don't try to scale it — and there's no
|
|
224
|
+
icon prop, so you can't swap the glyph either.
|
|
225
|
+
|
|
226
|
+
**8. Giving the root a width won't stretch the content.** The root is
|
|
227
|
+
`display: inline-flex; flex-direction: column; align-items: flex-start`, so the
|
|
228
|
+
padded inner block stays left-hugging inside a wider root. Equal-width pills need
|
|
229
|
+
`min-width` on the *inner* content, which the DS doesn't expose — set `width` on the
|
|
230
|
+
root **and** accept left alignment, or space the tree with the parent's layout.
|
|
231
|
+
|
|
232
|
+
**9. `label` never wraps.** Long node names extend the chip horizontally and push the
|
|
233
|
+
tree wide; there's no ellipsis and no `title` tooltip (unlike `ScValueMappingL1`).
|
|
234
|
+
Truncate the string before passing it if your tree is width-constrained.
|
|
235
|
+
|
|
236
|
+
---
|
|
237
|
+
|
|
238
|
+
## In the wild
|
|
239
|
+
|
|
240
|
+
```jsx
|
|
241
|
+
// catalogix/dashboard app/components/TaxonomyComponent/RecursiveTaxonomyTree/index.jsx:193
|
|
242
|
+
<ScTaxonomyPill type="pill" label={node.groupName} />
|
|
243
|
+
```
|
|
244
|
+
|
|
245
|
+
```jsx
|
|
246
|
+
// catalogix/dashboard app/components/TaxonomyComponent/RecursiveTaxonomyTree/index.jsx:45
|
|
247
|
+
return <ScTaxonomyPill type="expand" count={childrenCount} />;
|
|
248
|
+
```
|
|
249
|
+
|
|
250
|
+
```jsx
|
|
251
|
+
// catalogix/dashboard app/containers/CreateTaxonomy/HierarchyPage/index.jsx:307
|
|
252
|
+
<ScTaxonomyPill type="pill" label="All Products" />
|
|
253
|
+
```
|
|
254
|
+
|
|
255
|
+
---
|
|
256
|
+
|
|
257
|
+
## Related
|
|
258
|
+
|
|
259
|
+
- `ScSelectionPill` / `ScSelectionPillGroup` — the segmented-row pill family; same shared `"All Products"` default label, completely different job.
|
|
260
|
+
- `ScValueMappingL1` — flat selectable list row, when the data is a list rather than a tree.
|
|
261
|
+
- `ScBadges` — when the chip is informational and must not look clickable.
|
|
262
|
+
- `ScDefaultCard` — the "choose a taxonomy" option cards that sit on the screens leading into these trees.
|
|
263
|
+
- `@streamoid/icons` — `SiconDown` / `SiconUp` are the chevrons this component hard-codes; see `packages/icons/ICONS.md`.
|