@streamoid/ui 0.6.16 → 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 +43 -37
- package/dist/index.d.mts +10 -0
- package/dist/index.d.ts +10 -0
- package/package.json +3 -2
|
@@ -0,0 +1,212 @@
|
|
|
1
|
+
---
|
|
2
|
+
component: ScSettingsNav
|
|
3
|
+
package: "@streamoid/ui"
|
|
4
|
+
category: layout
|
|
5
|
+
status: legacy
|
|
6
|
+
renders: div
|
|
7
|
+
tags: [settings, nav, left-nav, sidebar, profile, workspace, referral, static, figma-dump]
|
|
8
|
+
related: [ScSidebarMenu, ScSettingsTabComp, StreamoidSidebar, ScProfileSettingsComp]
|
|
9
|
+
do_not_confuse_with: [ScSettingsTabComp, ScSidebarMenu, ScSidebar, StreamoidSidebar, ScProfileSettingsComp]
|
|
10
|
+
---
|
|
11
|
+
|
|
12
|
+
# ScSettingsNav
|
|
13
|
+
|
|
14
|
+
**A hardcoded 256×290px settings left-nav card — Profile / Workspace / Referral, with
|
|
15
|
+
Profile permanently active and nothing clickable.** It takes no props other than
|
|
16
|
+
`className`. Treat it as a Figma snapshot, not a component: there is no way to change
|
|
17
|
+
the items, the active row, or to attach a handler.
|
|
18
|
+
|
|
19
|
+
> **status: legacy.** No host app renders it, and it cannot be configured. For a real
|
|
20
|
+
> settings nav, compose `ScSidebarMenu` rows yourself, or use `ScSettingsTabComp` for a
|
|
21
|
+
> horizontal tab strip.
|
|
22
|
+
|
|
23
|
+
## TL;DR for agents
|
|
24
|
+
|
|
25
|
+
- **Reach for it when:** you need a static visual placeholder of the settings nav
|
|
26
|
+
(a design mock, a screenshot, a Figma-parity check).
|
|
27
|
+
- **Don't reach for it when:** you are building a working settings screen. It has no
|
|
28
|
+
`items`, no `activeItem`, no `onSelect`. → compose `ScSidebarMenu`, or use
|
|
29
|
+
`ScSettingsTabComp` / `StreamoidSidebar`.
|
|
30
|
+
- **Three things that will bite you:**
|
|
31
|
+
1. The three labels — **"Profile", "Workspace", "Referral"** — are baked into the
|
|
32
|
+
source. No prop, no children, no localisation.
|
|
33
|
+
2. **"Profile" is always `state="active"`.** Navigation cannot be reflected.
|
|
34
|
+
3. **Nothing has an `onClick`.** The card is inert; clicks fall through to the parent.
|
|
35
|
+
|
|
36
|
+
---
|
|
37
|
+
|
|
38
|
+
## 1. How to use it
|
|
39
|
+
|
|
40
|
+
### Import
|
|
41
|
+
|
|
42
|
+
```tsx
|
|
43
|
+
import { ScSettingsNav } from "@streamoid/ui";
|
|
44
|
+
import "@streamoid/ui/dist/index.css"; // once, at your app root
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
### Minimal usage
|
|
48
|
+
|
|
49
|
+
```tsx
|
|
50
|
+
<ScSettingsNav />
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
That is also the maximal usage.
|
|
54
|
+
|
|
55
|
+
### Props
|
|
56
|
+
|
|
57
|
+
| Prop | Type | Default | Notes |
|
|
58
|
+
|---|---|---|---|
|
|
59
|
+
| `className` | `string` | – | Appended after the internal class. Your only lever — use it to override the fixed `16rem × 18.125rem` box. |
|
|
60
|
+
| `...props` | `HTMLAttributes<HTMLDivElement>` | – | Spread onto the root div. `onClick` here fires for the whole card, not per row — you cannot tell which item was hit. |
|
|
61
|
+
|
|
62
|
+
There are **no** item, label, icon, active or handler props.
|
|
63
|
+
|
|
64
|
+
### What it renders, verbatim
|
|
65
|
+
|
|
66
|
+
| Row | Icon | `state` |
|
|
67
|
+
|---|---|---|
|
|
68
|
+
| `"Profile"` | `SiconTeam` | `"active"` |
|
|
69
|
+
| `"Workspace"` | `SiconWorkspace` | `"default"` |
|
|
70
|
+
| `"Referral"` | `SiconReferral` | `"default"` |
|
|
71
|
+
|
|
72
|
+
Container: `--alias-surface-base` fill, `--radius-3xl` corners, 1px
|
|
73
|
+
`--alias-border-subtle` border, `--spacing-md` padding, **fixed `width: 16rem` and
|
|
74
|
+
`height: 18.125rem`**.
|
|
75
|
+
|
|
76
|
+
### What to write instead
|
|
77
|
+
|
|
78
|
+
```tsx
|
|
79
|
+
// A working settings nav — the same look, with real behaviour
|
|
80
|
+
const ITEMS = [
|
|
81
|
+
{ id: "profile", label: "Profile", icon: <SiconTeam /> },
|
|
82
|
+
{ id: "workspace", label: "Workspace", icon: <SiconWorkspace /> },
|
|
83
|
+
{ id: "referral", label: "Referral", icon: <SiconReferral /> },
|
|
84
|
+
];
|
|
85
|
+
|
|
86
|
+
<div className={styles.navCard}> {/* your own surface/border/radius */}
|
|
87
|
+
{ITEMS.map((it) => (
|
|
88
|
+
<ScSidebarMenu
|
|
89
|
+
key={it.id}
|
|
90
|
+
icon={it.icon}
|
|
91
|
+
text={it.label}
|
|
92
|
+
state={active === it.id ? "active" : "default"}
|
|
93
|
+
onClick={() => setActive(it.id)}
|
|
94
|
+
/>
|
|
95
|
+
))}
|
|
96
|
+
</div>
|
|
97
|
+
```
|
|
98
|
+
|
|
99
|
+
---
|
|
100
|
+
|
|
101
|
+
## 2. Where to use it
|
|
102
|
+
|
|
103
|
+
Nowhere in production today. Grepping CXO, Photogenix, Catalogix, Artifax and
|
|
104
|
+
`@streamoid/settings` finds zero render sites.
|
|
105
|
+
|
|
106
|
+
Where the *pattern* lives instead:
|
|
107
|
+
|
|
108
|
+
- **Desktop settings navigation** is handled by `@streamoid/settings`, which uses
|
|
109
|
+
`ScTabComp`-based switchers and its own layout rather than a nav card.
|
|
110
|
+
- **Mobile settings** uses a horizontal `ScSettingsTabComp` strip
|
|
111
|
+
(`cxo-dashboard src/app/components/mobile-settings-content.tsx`).
|
|
112
|
+
- **App-level navigation** is `StreamoidSidebar` (config-driven `topItems` / `sections`).
|
|
113
|
+
|
|
114
|
+
---
|
|
115
|
+
|
|
116
|
+
## 3. When to use it
|
|
117
|
+
|
|
118
|
+
### Use it when
|
|
119
|
+
|
|
120
|
+
- You need a pixel-parity static mock of the settings nav card and nothing more.
|
|
121
|
+
|
|
122
|
+
### Don't use it — reach for this instead
|
|
123
|
+
|
|
124
|
+
| Situation | Use instead |
|
|
125
|
+
|---|---|
|
|
126
|
+
| A functioning settings left-nav | your own container + `ScSidebarMenu` rows (see the snippet above) |
|
|
127
|
+
| A horizontal settings tab strip | `ScSettingsTabComp` |
|
|
128
|
+
| The app's primary navigation | `StreamoidSidebar` (config-driven; also `ScCatalogixSidebar` / `ScArtifaxSidebar`) |
|
|
129
|
+
| One nav row | `ScSidebarMenu` |
|
|
130
|
+
| The profile-settings screen scaffold | `ScProfileSettingsComp` |
|
|
131
|
+
| A compact 2–5 way view toggle | `ScTabSwitcher` + `ScTabComp` |
|
|
132
|
+
| A data-driven tab bar | `ScTabs` |
|
|
133
|
+
|
|
134
|
+
### Don't confuse with
|
|
135
|
+
|
|
136
|
+
| You may actually want | Not this |
|
|
137
|
+
|---|---|
|
|
138
|
+
| `ScSettingsTabComp` — a **single horizontal underline tab** with `tabName`/`active`/`onClick` | `ScSettingsNav` is a vertical card with no API |
|
|
139
|
+
| `ScSidebarMenu` — the configurable nav row this card is built from | Use the row, not the card |
|
|
140
|
+
| `ScSidebar` / `ScSidebarIcons` / `ScSidebarProfile` — the other pre-`StreamoidSidebar` parts | Same era, same problem: superseded |
|
|
141
|
+
| `ScProfileSettingsComp` — the profile-settings content scaffold | Different surface |
|
|
142
|
+
|
|
143
|
+
---
|
|
144
|
+
|
|
145
|
+
## 4. Why to use it
|
|
146
|
+
|
|
147
|
+
Honestly: don't. The only thing you'd get is Figma parity for a static screenshot.
|
|
148
|
+
|
|
149
|
+
What you *lose* by using it: any ability to route, to reflect the current section, to
|
|
150
|
+
add or remove items, to localise labels, or to fit a container (it is a fixed
|
|
151
|
+
256×290px box). Everything valuable in it — the token fill, the border, the row
|
|
152
|
+
treatment — is available directly from `ScSidebarMenu` plus four CSS declarations.
|
|
153
|
+
|
|
154
|
+
---
|
|
155
|
+
|
|
156
|
+
## Gotchas
|
|
157
|
+
|
|
158
|
+
**1. Hardcoded English labels.** "Profile", "Workspace", "Referral" are string
|
|
159
|
+
literals in the render body. No `items` prop exists.
|
|
160
|
+
|
|
161
|
+
**2. "Profile" is always the active row.** `state="active"` is hardcoded on the first
|
|
162
|
+
`ScSidebarMenu`, so the card contradicts your actual route the moment the user
|
|
163
|
+
navigates.
|
|
164
|
+
|
|
165
|
+
**3. No handlers anywhere.** Rows don't take `onClick` here. An `onClick` on the root
|
|
166
|
+
tells you the card was clicked, not which row.
|
|
167
|
+
|
|
168
|
+
```tsx
|
|
169
|
+
// WRONG — there is no way to know which item was clicked
|
|
170
|
+
<ScSettingsNav onClick={(e) => route(e.target)} />
|
|
171
|
+
|
|
172
|
+
// RIGHT — build the list yourself
|
|
173
|
+
{items.map((it) => <ScSidebarMenu key={it.id} text={it.label} onClick={() => route(it.id)} />)}
|
|
174
|
+
```
|
|
175
|
+
|
|
176
|
+
**4. Fixed size.** `width: 16rem; height: 18.125rem` — the card keeps its 290px height
|
|
177
|
+
with three rows and will not grow for a fourth (you can't add one anyway). Override via
|
|
178
|
+
`className` if you must embed it.
|
|
179
|
+
|
|
180
|
+
**5. `className` is concatenated unguarded** → `class="scSettingsNav undefined"` when
|
|
181
|
+
omitted.
|
|
182
|
+
|
|
183
|
+
**6. `--alias-border-subtle` at 1px, not the 0.5px divider.** If you place it beside
|
|
184
|
+
cards that use `--alias-shadow-*` elevation, its border reads heavier than its
|
|
185
|
+
neighbours in light mode.
|
|
186
|
+
|
|
187
|
+
**7. Not deprecated in source, but unused everywhere.** Nothing warns you at build
|
|
188
|
+
time. Its `status: legacy` here is the only signal — don't take its presence in
|
|
189
|
+
`index.ts` as an endorsement.
|
|
190
|
+
|
|
191
|
+
---
|
|
192
|
+
|
|
193
|
+
## In the wild
|
|
194
|
+
|
|
195
|
+
_No host render site found — used by the agent runtime / composed internally._
|
|
196
|
+
|
|
197
|
+
Neither is true, strictly: it has **no** consumer at all — not the four dashboards, not
|
|
198
|
+
`@streamoid/settings`, not another DS component. If a settings nav card is needed
|
|
199
|
+
again, it most likely belongs in `@streamoid/settings` beside the settings/billing
|
|
200
|
+
screens, rebuilt with an `items` + `activeId` + `onSelect` API on top of
|
|
201
|
+
`ScSidebarMenu`.
|
|
202
|
+
|
|
203
|
+
---
|
|
204
|
+
|
|
205
|
+
## Related
|
|
206
|
+
|
|
207
|
+
- `ScSidebarMenu` — the configurable nav row; the right building block.
|
|
208
|
+
- `ScSettingsTabComp` — the horizontal settings tab actually used by CXO.
|
|
209
|
+
- `StreamoidSidebar` — the current shared, config-driven app shell nav.
|
|
210
|
+
- `ScProfileSettingsComp` / `ScProfileOptions` — profile-side settings scaffolding.
|
|
211
|
+
- `ScSidebar`, `ScSidebarIcons`, `ScSidebarProfile`, `ScSidebarSwitchMenu`,
|
|
212
|
+
`ScLogoUnit` — the other superseded pre-`StreamoidSidebar` parts.
|
|
@@ -0,0 +1,260 @@
|
|
|
1
|
+
---
|
|
2
|
+
component: ScSettingsTabComp
|
|
3
|
+
package: "@streamoid/ui"
|
|
4
|
+
category: layout
|
|
5
|
+
status: stable
|
|
6
|
+
renders: div
|
|
7
|
+
tags: [tab, underline-tab, settings, tab-strip, mobile-settings, modal-tabs, section-switch]
|
|
8
|
+
related: [ScTabComp, ScTabSwitcher, ScTabs, ScTabField, ScSettingsNav]
|
|
9
|
+
do_not_confuse_with: [ScTabComp, ScTabSwitcher, ScTabs, ScSettingsNav, ScSelectionPill]
|
|
10
|
+
used_by: [cxo, catalogix]
|
|
11
|
+
---
|
|
12
|
+
|
|
13
|
+
# ScSettingsTabComp
|
|
14
|
+
|
|
15
|
+
**One underlined tab in a horizontal strip.** 56px tall, 12px padding, muted 16px
|
|
16
|
+
label; when `active` it turns primary-coloured and grows a 1.25px underline. This is
|
|
17
|
+
the settings/modal tab vocabulary — flat and underlined — as opposed to the filled
|
|
18
|
+
`ScTabComp` pill inside a `ScTabSwitcher` track.
|
|
19
|
+
|
|
20
|
+
## TL;DR for agents
|
|
21
|
+
|
|
22
|
+
- **Reach for it when:** you need a settings-style tab strip — Profile / Workspace,
|
|
23
|
+
or a modal's section tabs — where the active tab is marked by an underline.
|
|
24
|
+
- **Don't reach for it when:** you want the bordered filled segmented control
|
|
25
|
+
(→ `ScTabSwitcher` + `ScTabComp`), a data-driven pill bar (→ `ScTabs`), or a form
|
|
26
|
+
field (→ `ScTabField`).
|
|
27
|
+
- **Four things that will bite you:**
|
|
28
|
+
1. `active` is a **real boolean** here — the exact opposite of `ScTabComp`, whose
|
|
29
|
+
`active` is the string `"true"`/`"false"`.
|
|
30
|
+
2. **No props are spread.** No `style`, no `role`, no `aria-selected`, no
|
|
31
|
+
`data-testid`, no `id`. Only `tabName`, `active`, `onClick`, `className`.
|
|
32
|
+
3. It draws **only its own underline**. The strip's baseline border is the parent's job.
|
|
33
|
+
4. `tabName` defaults to `"Tab"`, and the 3.5rem height is fixed.
|
|
34
|
+
|
|
35
|
+
---
|
|
36
|
+
|
|
37
|
+
## 1. How to use it
|
|
38
|
+
|
|
39
|
+
### Import
|
|
40
|
+
|
|
41
|
+
```tsx
|
|
42
|
+
import { ScSettingsTabComp } from "@streamoid/ui";
|
|
43
|
+
import "@streamoid/ui/dist/index.css"; // once, at your app root
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
### Minimal usage
|
|
47
|
+
|
|
48
|
+
```tsx
|
|
49
|
+
<ScSettingsTabComp tabName="Profile" active={tab === "profile"} onClick={() => setTab("profile")} />
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
### Props
|
|
53
|
+
|
|
54
|
+
| Prop | Type | Default | Notes |
|
|
55
|
+
|---|---|---|---|
|
|
56
|
+
| `tabName` | `string` | `"Tab"` | ⚠️ Real default. Rendered in a `<span>`, `white-space: nowrap`, no truncation. |
|
|
57
|
+
| `active` | `boolean` | `false` | **Boolean** (unlike `ScTabComp`). Adds the underline and switches the label to `--alias-text-and-icons-primary`. |
|
|
58
|
+
| `onClick` | `() => void` | – | No event argument — the handler takes no parameters. |
|
|
59
|
+
| `className` | `string` | – | Appended last (guarded with `?? ""`, so no stray `"undefined"`). Your only styling lever. |
|
|
60
|
+
|
|
61
|
+
Everything else is unavailable: the component destructures exactly these four props and
|
|
62
|
+
spreads nothing.
|
|
63
|
+
|
|
64
|
+
### What renders in each state
|
|
65
|
+
|
|
66
|
+
| `active` | Label colour | Underline |
|
|
67
|
+
|---|---|---|
|
|
68
|
+
| `false` | `--alias-text-and-icons-muted` | none |
|
|
69
|
+
| `true` | `--alias-text-and-icons-primary` | `border-bottom: 1.25px solid --alias-text-and-icons-primary` |
|
|
70
|
+
|
|
71
|
+
Box in both states: `height: 3.5rem`, `padding: var(--spacing-xl)` (0.75rem),
|
|
72
|
+
`flex-shrink: 0`, `cursor: pointer`, centred content, 16px/1.5rem label.
|
|
73
|
+
|
|
74
|
+
### Recipes
|
|
75
|
+
|
|
76
|
+
```tsx
|
|
77
|
+
// The strip: the PARENT draws the baseline, the tab draws only its own underline
|
|
78
|
+
<div
|
|
79
|
+
className="flex shrink-0"
|
|
80
|
+
style={{
|
|
81
|
+
borderBottom: "0.5px solid var(--alias-border-divider)",
|
|
82
|
+
padding: "0 var(--spacing-md)",
|
|
83
|
+
gap: "var(--spacing-md)",
|
|
84
|
+
}}
|
|
85
|
+
>
|
|
86
|
+
<ScSettingsTabComp tabName="Profile" active={tab === "profile"} onClick={() => setTab("profile")} />
|
|
87
|
+
<ScSettingsTabComp tabName="Workspace" active={tab === "workspace"} onClick={() => setTab("workspace")} />
|
|
88
|
+
</div>
|
|
89
|
+
|
|
90
|
+
// From data
|
|
91
|
+
{sections.map((s) => (
|
|
92
|
+
<ScSettingsTabComp
|
|
93
|
+
key={s.value}
|
|
94
|
+
tabName={s.label}
|
|
95
|
+
active={activeTab === s.value}
|
|
96
|
+
onClick={() => onClickTab(s.value)}
|
|
97
|
+
/>
|
|
98
|
+
))}
|
|
99
|
+
|
|
100
|
+
// Need aria/roles or a test hook? wrap it — they can't be passed through
|
|
101
|
+
<div role="tab" aria-selected={isActive} data-testid={`tab-${id}`}>
|
|
102
|
+
<ScSettingsTabComp tabName={label} active={isActive} onClick={select} />
|
|
103
|
+
</div>
|
|
104
|
+
```
|
|
105
|
+
|
|
106
|
+
---
|
|
107
|
+
|
|
108
|
+
## 2. Where to use it
|
|
109
|
+
|
|
110
|
+
- **CXO mobile settings** — the Profile / Workspace strip at the top of
|
|
111
|
+
`mobile-settings-content`.
|
|
112
|
+
- **Catalogix modals** — the folder-tab strip inside the shared `Modal` component
|
|
113
|
+
(which reverses the array to preserve the legacy `row-reverse` order).
|
|
114
|
+
- Any **section switcher above a scrolling content pane** where an underline reads
|
|
115
|
+
better than a filled pill: settings, detail screens, modal sections.
|
|
116
|
+
|
|
117
|
+
---
|
|
118
|
+
|
|
119
|
+
## 3. When to use it
|
|
120
|
+
|
|
121
|
+
### Use it when
|
|
122
|
+
|
|
123
|
+
- The tabs sit **directly above the content they switch**, with a baseline rule.
|
|
124
|
+
- Labels are words (not icons) and the underline treatment matches the surface.
|
|
125
|
+
- You are matching the existing settings / modal look.
|
|
126
|
+
|
|
127
|
+
### Don't use it — reach for this instead
|
|
128
|
+
|
|
129
|
+
| Situation | Use instead |
|
|
130
|
+
|---|---|
|
|
131
|
+
| Compact filled segmented control, 2–5 ways | `ScTabSwitcher` + `ScTabComp` |
|
|
132
|
+
| Icon-only tabs | `ScTabComp` (`type="icon-only"`) — this component has no icon support |
|
|
133
|
+
| Long or dynamic tab lists that must wrap | `ScTabs` (`allowWrap`, `isCompact`) |
|
|
134
|
+
| A segmented control that is a **saved form value** | `ScTabField` |
|
|
135
|
+
| Dark-fill selection pills (Catalogix curation) | `ScSelectionPillGroup` |
|
|
136
|
+
| A vertical settings nav | your own `ScSidebarMenu` list (**not** `ScSettingsNav` — see its README) |
|
|
137
|
+
| Something that needs `role="tab"` on the element itself | wrap this, or use `ScSelectionPill` (a real `<button>`) |
|
|
138
|
+
|
|
139
|
+
### Don't confuse with
|
|
140
|
+
|
|
141
|
+
| You may actually want | Not this |
|
|
142
|
+
|---|---|
|
|
143
|
+
| `ScTabComp` — filled pill for a `ScTabSwitcher`; `active` is the **string** `"true"`/`"false"`; spreads DOM props | `ScSettingsTabComp` is an underline tab; `active` is a **boolean**; spreads nothing |
|
|
144
|
+
| `ScSettingsNav` — the static vertical settings nav card (legacy, no API) | This is one horizontal tab |
|
|
145
|
+
| `ScTabs` — the whole row from data | This is one tab; you map them yourself |
|
|
146
|
+
| `ScTabField` — label + switcher, for forms | Different surface and API |
|
|
147
|
+
|
|
148
|
+
### The tab family at a glance
|
|
149
|
+
|
|
150
|
+
| Component | Shape | Selection API | Root | Spreads DOM props? |
|
|
151
|
+
|---|---|---|---|---|
|
|
152
|
+
| `ScSettingsTabComp` | underline tab | `active` (**boolean**) + `onClick()` | `div` | ❌ |
|
|
153
|
+
| `ScTabComp` | filled pill cell | `active="true" \| "false"` (**strings**) | `div` | ✅ |
|
|
154
|
+
| `ScTabSwitcher` | 2–5 slot bordered track | none — children own it | `div` | ✅ |
|
|
155
|
+
| `ScTabs` | N pills, data-driven | `activeTab` + `onClickTab(value)` | `div` | ❌ |
|
|
156
|
+
| `ScTabField` | field label + 2-tab switcher | `activeTab` + `onTabChange` | `div` | ❌ |
|
|
157
|
+
| `ScSelectionPill` | one segmented pill | `selected` (**boolean**) + `onSelect(value)` | `button[type="button"][aria-pressed]` | ✅ |
|
|
158
|
+
| `ScSelectionPillGroup` | segmented pill row | `value` + `onChange(value)` | `div[role="tablist"]` of `button`s | ✅ |
|
|
159
|
+
|
|
160
|
+
---
|
|
161
|
+
|
|
162
|
+
## 4. Why to use it
|
|
163
|
+
|
|
164
|
+
- **The active marker is a token, not a colour.** Label and underline both use
|
|
165
|
+
`--alias-text-and-icons-primary`, so the strip inverts correctly between themes —
|
|
166
|
+
a hand-rolled `border-bottom: 1.25px solid #f5f5f5` disappears on the light canvas.
|
|
167
|
+
- **1.25px is a real design value**, not a rounding of 1px. Getting it wrong is the
|
|
168
|
+
most visible way a hand-built tab strip looks "off" next to a DS one.
|
|
169
|
+
- **The 56px row height is shared** with the mobile settings header rhythm, so tabs
|
|
170
|
+
line up with adjacent chrome without magic numbers at the call site.
|
|
171
|
+
- **Tiny surface area** — four props, no state — which is why both CXO and Catalogix
|
|
172
|
+
could drop their own tab-strip implementations and keep their existing APIs.
|
|
173
|
+
|
|
174
|
+
---
|
|
175
|
+
|
|
176
|
+
## Gotchas
|
|
177
|
+
|
|
178
|
+
**1. `active` is a boolean — the inverse of `ScTabComp`.** Switching between the two
|
|
179
|
+
components is the most common mistake in this family.
|
|
180
|
+
|
|
181
|
+
```tsx
|
|
182
|
+
// WRONG — TS error: Type 'string' is not assignable to type 'boolean | undefined'
|
|
183
|
+
<ScSettingsTabComp tabName="Profile" active={tab === "profile" ? "true" : "false"} />
|
|
184
|
+
|
|
185
|
+
// RIGHT
|
|
186
|
+
<ScSettingsTabComp tabName="Profile" active={tab === "profile"} />
|
|
187
|
+
```
|
|
188
|
+
|
|
189
|
+
The variant class is built by concatenation (`styles["active-" + active]`), so in a
|
|
190
|
+
plain-JS host (Catalogix) the string form happens to resolve to the same class and the
|
|
191
|
+
mistake never surfaces at runtime — it only fails to typecheck. Don't rely on that.
|
|
192
|
+
|
|
193
|
+
**2. Nothing is spread onto the DOM.** `style`, `role`, `aria-selected`, `id`,
|
|
194
|
+
`data-*` are silently unavailable — they are not in the props type at all.
|
|
195
|
+
|
|
196
|
+
```tsx
|
|
197
|
+
// WRONG — TS error; and there is no runtime pass-through either
|
|
198
|
+
<ScSettingsTabComp tabName="Profile" active role="tab" style={{ flex: 1 }} />
|
|
199
|
+
|
|
200
|
+
// RIGHT — wrap it, or use className
|
|
201
|
+
<div role="tab" aria-selected style={{ flex: 1 }}>
|
|
202
|
+
<ScSettingsTabComp tabName="Profile" active className={styles.tab} />
|
|
203
|
+
</div>
|
|
204
|
+
```
|
|
205
|
+
|
|
206
|
+
**3. The strip's baseline border is yours.** The tab only paints its own 1.25px
|
|
207
|
+
underline; without a container `border-bottom` the inactive tabs float over nothing.
|
|
208
|
+
CXO's strip sets `borderBottom: 0.5px solid <divider>` on the flex row.
|
|
209
|
+
|
|
210
|
+
**4. Fixed 3.5rem height.** No `size` prop. Override with a `className` if you need a
|
|
211
|
+
denser strip — and expect the underline to move with it.
|
|
212
|
+
|
|
213
|
+
**5. `tabName` defaults to `"Tab"`.** Forget the prop and you ship the placeholder.
|
|
214
|
+
|
|
215
|
+
**6. No icon support.** There is no `icon` prop and no slot; `tabName` is a `string`,
|
|
216
|
+
not a `ReactNode`, so you cannot pass an element.
|
|
217
|
+
|
|
218
|
+
**7. No truncation.** `white-space: nowrap` with no `max-width`, no ellipsis, no
|
|
219
|
+
`title`. Long labels widen the strip and force horizontal overflow on mobile.
|
|
220
|
+
|
|
221
|
+
**8. `div` + `onClick`: no keyboard, no focus ring.** Not focusable, no Enter/Space,
|
|
222
|
+
no `aria-selected`. For an accessible strip, wrap each tab (see recipe) or use
|
|
223
|
+
`ScSelectionPillGroup`.
|
|
224
|
+
|
|
225
|
+
**9. `onClick` receives no event.** The signature is `() => void`, so you cannot
|
|
226
|
+
`stopPropagation` or read the target. Do that in a wrapper.
|
|
227
|
+
|
|
228
|
+
---
|
|
229
|
+
|
|
230
|
+
## In the wild
|
|
231
|
+
|
|
232
|
+
```tsx
|
|
233
|
+
// cxo-dashboard src/app/components/mobile-settings-content.tsx:233
|
|
234
|
+
<ScSettingsTabComp
|
|
235
|
+
tabName="Profile"
|
|
236
|
+
active={activeTab === "profile"}
|
|
237
|
+
onClick={() => setActiveTab("profile")}
|
|
238
|
+
/>
|
|
239
|
+
```
|
|
240
|
+
|
|
241
|
+
```jsx
|
|
242
|
+
// catalogix/dashboard app/components/Modal/index.jsx:354
|
|
243
|
+
<ScSettingsTabComp
|
|
244
|
+
key={`${idx}-${value}`}
|
|
245
|
+
tabName={formatTabLabel(label)}
|
|
246
|
+
active={props.activeTab == value}
|
|
247
|
+
onClick={() => props.onClickTab(value)}
|
|
248
|
+
/>
|
|
249
|
+
```
|
|
250
|
+
|
|
251
|
+
---
|
|
252
|
+
|
|
253
|
+
## Related
|
|
254
|
+
|
|
255
|
+
- `ScTabComp` — the filled pill cell; note the string `active` prop.
|
|
256
|
+
- `ScTabSwitcher` — bordered 2–5 slot track for `ScTabComp`s.
|
|
257
|
+
- `ScTabs` — data-driven wrapping pill bar.
|
|
258
|
+
- `ScTabField` — labelled form field wrapping a 2-tab switcher.
|
|
259
|
+
- `ScSettingsNav` — **legacy** static vertical settings nav card; don't use it.
|
|
260
|
+
- `ScSelectionPillGroup` — accessible segmented control (real buttons).
|