@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.
Files changed (131) hide show
  1. package/README.md +35 -18
  2. package/dist/docs/AGENTS.md +321 -0
  3. package/dist/docs/CreditWarningBanner.md +305 -0
  4. package/dist/docs/InvoiceHistoryMobile.md +222 -0
  5. package/dist/docs/ScAccess.md +259 -0
  6. package/dist/docs/ScAppCard.md +244 -0
  7. package/dist/docs/ScAppCardForCopilot.md +230 -0
  8. package/dist/docs/ScAppCardV3.md +273 -0
  9. package/dist/docs/ScAppField.md +308 -0
  10. package/dist/docs/ScAppListingCard.md +271 -0
  11. package/dist/docs/ScAppSwitchPanel.md +286 -0
  12. package/dist/docs/ScAppcardLogos.md +226 -0
  13. package/dist/docs/ScArtifaxInvite.md +262 -0
  14. package/dist/docs/ScArtifaxSidebar.md +330 -0
  15. package/dist/docs/ScAskAgentButton.md +307 -0
  16. package/dist/docs/ScBadges.md +261 -0
  17. package/dist/docs/ScBeacon.md +244 -0
  18. package/dist/docs/ScBillingHistoryHeader.md +210 -0
  19. package/dist/docs/ScBillingHistoryTableList.md +243 -0
  20. package/dist/docs/ScBillingLogsTableHeader.md +212 -0
  21. package/dist/docs/ScBillingLogsTableList.md +251 -0
  22. package/dist/docs/ScBriefCard.md +255 -0
  23. package/dist/docs/ScButton.md +251 -0
  24. package/dist/docs/ScCalendar.md +268 -0
  25. package/dist/docs/ScCalendarDateComps.md +264 -0
  26. package/dist/docs/ScCatalogixInvite.md +345 -0
  27. package/dist/docs/ScCatalogixSidebar.md +337 -0
  28. package/dist/docs/ScCatalogixStoreHeader.md +246 -0
  29. package/dist/docs/ScCatalogixStoreTableList.md +316 -0
  30. package/dist/docs/ScCheckField.md +233 -0
  31. package/dist/docs/ScCheckbox.md +272 -0
  32. package/dist/docs/ScCounter.md +235 -0
  33. package/dist/docs/ScCreditsUsageCard.md +247 -0
  34. package/dist/docs/ScCreditsUsageCardMobile.md +224 -0
  35. package/dist/docs/ScDefaultCard.md +269 -0
  36. package/dist/docs/ScDp.md +245 -0
  37. package/dist/docs/ScDrawer.md +318 -0
  38. package/dist/docs/ScFieldButton.md +255 -0
  39. package/dist/docs/ScFileField.md +268 -0
  40. package/dist/docs/ScGoogleSignIn.md +250 -0
  41. package/dist/docs/ScGuide.md +278 -0
  42. package/dist/docs/ScHDivider.md +213 -0
  43. package/dist/docs/ScHeader.md +222 -0
  44. package/dist/docs/ScImageField.md +253 -0
  45. package/dist/docs/ScInChatList.md +277 -0
  46. package/dist/docs/ScInChatMessage.md +205 -0
  47. package/dist/docs/ScInfoPopup.md +248 -0
  48. package/dist/docs/ScIntialProfileCover.md +233 -0
  49. package/dist/docs/ScInvoiceHistoryMobile.md +187 -0
  50. package/dist/docs/ScLogoUnit.md +232 -0
  51. package/dist/docs/ScMappingCard.md +241 -0
  52. package/dist/docs/ScMediaApproval.md +301 -0
  53. package/dist/docs/ScMediaSelect.md +310 -0
  54. package/dist/docs/ScMenuOptions.md +308 -0
  55. package/dist/docs/ScMobileBottomAction.md +252 -0
  56. package/dist/docs/ScMobileTopNav.md +279 -0
  57. package/dist/docs/ScModal.md +291 -0
  58. package/dist/docs/ScOnlyField.md +302 -0
  59. package/dist/docs/ScOnlyIcon.md +213 -0
  60. package/dist/docs/ScPagination.md +284 -0
  61. package/dist/docs/ScPairtext.md +287 -0
  62. package/dist/docs/ScPendingAction.md +238 -0
  63. package/dist/docs/ScPhtogenixInvite.md +275 -0
  64. package/dist/docs/ScPlanCard.md +302 -0
  65. package/dist/docs/ScPlanComparison.md +264 -0
  66. package/dist/docs/ScPlanDetailsCard.md +246 -0
  67. package/dist/docs/ScPlanDetailsCardMobile.md +240 -0
  68. package/dist/docs/ScPopUpMenu.md +224 -0
  69. package/dist/docs/ScProfile.md +234 -0
  70. package/dist/docs/ScProfileImageUpdate.md +261 -0
  71. package/dist/docs/ScProfileOptions.md +245 -0
  72. package/dist/docs/ScProfilePopup.md +396 -0
  73. package/dist/docs/ScProfileSettingsComp.md +250 -0
  74. package/dist/docs/ScProfileV2Mobile.md +216 -0
  75. package/dist/docs/ScProgressBar.md +267 -0
  76. package/dist/docs/ScQuickPrompt.md +277 -0
  77. package/dist/docs/ScRadio.md +228 -0
  78. package/dist/docs/ScReferralCardMobile.md +226 -0
  79. package/dist/docs/ScReferralTableHeader.md +260 -0
  80. package/dist/docs/ScReferralTableList.md +293 -0
  81. package/dist/docs/ScRole.md +226 -0
  82. package/dist/docs/ScRoleMobile.md +199 -0
  83. package/dist/docs/ScSelect.md +270 -0
  84. package/dist/docs/ScSelection.md +256 -0
  85. package/dist/docs/ScSelectionList.md +272 -0
  86. package/dist/docs/ScSelectionPill.md +240 -0
  87. package/dist/docs/ScSelectionPillGroup.md +302 -0
  88. package/dist/docs/ScSettingsNav.md +212 -0
  89. package/dist/docs/ScSettingsTabComp.md +260 -0
  90. package/dist/docs/ScSideBarLogoUnit.md +340 -0
  91. package/dist/docs/ScSidebar.md +243 -0
  92. package/dist/docs/ScSidebarIcons.md +232 -0
  93. package/dist/docs/ScSidebarMenu.md +283 -0
  94. package/dist/docs/ScSidebarProfile.md +231 -0
  95. package/dist/docs/ScSidebarSwitchMenu.md +258 -0
  96. package/dist/docs/ScSlider.md +194 -0
  97. package/dist/docs/ScStoreCard.md +252 -0
  98. package/dist/docs/ScStrLogo.md +253 -0
  99. package/dist/docs/ScStreamoidWordmark.md +302 -0
  100. package/dist/docs/ScSubAgent.md +226 -0
  101. package/dist/docs/ScTabComp.md +308 -0
  102. package/dist/docs/ScTabField.md +258 -0
  103. package/dist/docs/ScTabSwitcher.md +307 -0
  104. package/dist/docs/ScTableHeader.md +261 -0
  105. package/dist/docs/ScTableList.md +301 -0
  106. package/dist/docs/ScTableListMobile.md +282 -0
  107. package/dist/docs/ScTabs.md +268 -0
  108. package/dist/docs/ScTaxonomyPill.md +263 -0
  109. package/dist/docs/ScTextArea.md +259 -0
  110. package/dist/docs/ScTextField.md +324 -0
  111. package/dist/docs/ScThinkingStepIcon.md +249 -0
  112. package/dist/docs/ScTodoList.md +288 -0
  113. package/dist/docs/ScToggleSwitch.md +229 -0
  114. package/dist/docs/ScUsageHistoryMobile.md +194 -0
  115. package/dist/docs/ScVDivider.md +215 -0
  116. package/dist/docs/ScValueMappingL1.md +256 -0
  117. package/dist/docs/ScVersion.md +251 -0
  118. package/dist/docs/ScWorkspace.md +233 -0
  119. package/dist/docs/ScWorkspaceCard.md +234 -0
  120. package/dist/docs/ScWorkspaceSettingsMobile.md +265 -0
  121. package/dist/docs/ScWorkspaceSwitchCard.md +314 -0
  122. package/dist/docs/ScWorkspaceSwitchMobile.md +241 -0
  123. package/dist/docs/ScWorkspaceSwitchMobileV2.md +278 -0
  124. package/dist/docs/StreamoidSidebar.md +403 -0
  125. package/dist/docs/StreamoidWorkspaceSwitcher.md +307 -0
  126. package/dist/docs/UsageHistoryMobile.md +235 -0
  127. package/dist/docs/components.json +4849 -0
  128. package/dist/index.css +43 -37
  129. package/dist/index.d.mts +10 -0
  130. package/dist/index.d.ts +10 -0
  131. package/package.json +3 -2
@@ -0,0 +1,307 @@
1
+ ---
2
+ component: ScTabSwitcher
3
+ package: "@streamoid/ui"
4
+ category: layout
5
+ status: stable
6
+ renders: div
7
+ tags: [segmented, tab-switcher, toggle, view-switch, 2-tab, 3-tab, slots, container]
8
+ related: [ScTabComp, ScTabField, ScTabs, ScSettingsTabComp, ScSelectionPillGroup]
9
+ do_not_confuse_with: [ScTabs, ScTabComp, ScTabField, ScSettingsTabComp, ScSelectionPillGroup]
10
+ used_by: [cxo, photogenix, catalogix, artifax]
11
+ ---
12
+
13
+ # ScTabSwitcher
14
+
15
+ **The bordered shell around a 2–5 way segmented control.** It draws the rounded
16
+ `surface-base` track with a hairline border and 4px padding, then renders whatever you
17
+ put in its `component` … `component5` slots. It holds **no state**: each `ScTabComp`
18
+ child owns its own `active` and `onClick`.
19
+
20
+ ## TL;DR for agents
21
+
22
+ - **Reach for it when:** you have a compact 2–5 way view toggle and want the standard
23
+ track: theme switcher, Active/Pending, grid/list, Fast/Pro.
24
+ - **Don't reach for it when:** the tab list is dynamic or longer than five
25
+ (→ `ScTabs`), it's a settings underline strip (→ `ScSettingsTabComp`), or it's a
26
+ labelled form field (→ `ScTabField`, which wraps this for you).
27
+ - **Four things that will bite you:**
28
+ 1. **Empty slots are not empty.** A missing `component` renders a *default*
29
+ `ScTabComp` — a home icon, and the first one is `active="true"`.
30
+ 2. `tabCount` is a **string** (`"2"`…`"5"`) and it **gates** the slots:
31
+ `component3` is dropped unless `tabCount` is `"3"`+.
32
+ 3. Children you supply need **`style={{ flex: 1 }}`** or the tabs come out unequal.
33
+ 4. `scTabCompicon` / `scTabCompactive` / `scTabComptype` are accepted by the type
34
+ and **completely ignored** — Figma leftovers.
35
+
36
+ ---
37
+
38
+ ## 1. How to use it
39
+
40
+ ### Import
41
+
42
+ ```tsx
43
+ import { ScTabSwitcher, ScTabComp } from "@streamoid/ui";
44
+ import "@streamoid/ui/dist/index.css"; // once, at your app root
45
+ ```
46
+
47
+ ### Minimal usage
48
+
49
+ ```tsx
50
+ <ScTabSwitcher
51
+ tabCount="2"
52
+ component={
53
+ <ScTabComp text="Active" type="text-only"
54
+ active={tab === "active" ? "true" : "false"}
55
+ onClick={() => setTab("active")} style={{ flex: 1 }} />
56
+ }
57
+ component2={
58
+ <ScTabComp text="Pending" type="text-only"
59
+ active={tab === "pending" ? "true" : "false"}
60
+ onClick={() => setTab("pending")} style={{ flex: 1 }} />
61
+ }
62
+ />
63
+ ```
64
+
65
+ ### Props
66
+
67
+ | Prop | Type | Default | Notes |
68
+ |---|---|---|---|
69
+ | `tabCount` | `"2"` \| `"3"` \| `"4"` \| `"5"` | `"2"` | ⚠️ A **string**. Controls how many slots render — slots above it are dropped. Applies **no styling** (see Gotcha 5). |
70
+ | `component` | `JSX.Element` | ⚠️ a default `ScTabComp` with `active="true"` and `SiconHome` | Slot 1. Always rendered. |
71
+ | `component2` | `JSX.Element` | ⚠️ a default `ScTabComp` with `SiconHome` | Slot 2. Always rendered. |
72
+ | `component3` | `JSX.Element` | ⚠️ a default `ScTabComp` with `SiconHome` | Slot 3. Rendered only when `tabCount` is `"3"`/`"4"`/`"5"`. |
73
+ | `component4` | `JSX.Element` | ⚠️ a default `ScTabComp` with `SiconHome` | Slot 4. Rendered only when `tabCount` is `"4"`/`"5"`. |
74
+ | `component5` | `JSX.Element` | ⚠️ a default `ScTabComp` with `SiconHome` | Slot 5. Rendered only when `tabCount` is `"5"`. |
75
+ | `scTabCompicon` | `JSX.Element` | – | **Ignored.** Destructured to `_scTabCompicon` and dropped. |
76
+ | `scTabCompactive` | `string` | – | **Ignored.** |
77
+ | `scTabComptype` | `string` | – | **Ignored.** |
78
+ | `className` | `string` | – | Appended after the internal classes. Hosts use it for `w-full`. |
79
+ | `...props` | `HTMLAttributes<HTMLDivElement>` | – | Spread onto the track div: `style`, `role="tablist"`, `aria-label`, `data-*`. |
80
+
81
+ ### What renders for each `tabCount`
82
+
83
+ | `tabCount` | Slots rendered | Slots silently dropped |
84
+ |---|---|---|
85
+ | `"2"` (default) | `component`, `component2` | 3, 4, 5 |
86
+ | `"3"` | + `component3` | 4, 5 |
87
+ | `"4"` | + `component4` | 5 |
88
+ | `"5"` | all five | — |
89
+
90
+ ### Recipes
91
+
92
+ ```tsx
93
+ // Three icon tabs, full width (Photogenix / Artifax theme switcher)
94
+ <ScTabSwitcher
95
+ tabCount="3"
96
+ className="w-full"
97
+ role="tablist"
98
+ aria-label="Theme"
99
+ component={<ScTabComp type="icon-only" style={{ flex: 1 }}
100
+ active={theme === "system" ? "true" : "false"} onClick={() => setTheme("system")}
101
+ icon={<SiconSystem className="w-5 h-5"
102
+ color={theme === "system" ? "var(--alias-text---icons-inverse)" : "var(--alias-text---icons-muted)"} />} />}
103
+ component2={<ScTabComp type="icon-only" style={{ flex: 1 }}
104
+ active={theme === "light" ? "true" : "false"} onClick={() => setTheme("light")}
105
+ icon={<SiconLight className="w-5 h-5"
106
+ color={theme === "light" ? "var(--alias-text---icons-inverse)" : "var(--alias-text---icons-muted)"} />} />}
107
+ component3={<ScTabComp type="icon-only" style={{ flex: 1 }}
108
+ active={theme === "dark" ? "true" : "false"} onClick={() => setTheme("dark")}
109
+ icon={<SiconDark className="w-5 h-5"
110
+ color={theme === "dark" ? "var(--alias-text---icons-inverse)" : "var(--alias-text---icons-muted)"} />} />}
111
+ />
112
+
113
+ // Shrink-wrapped in a header row (it stretches by default — wrap it)
114
+ <div className="inline-flex">
115
+ <ScTabSwitcher tabCount="2" component={…} component2={…} />
116
+ </div>
117
+
118
+ // Driving both slots from one array (do NOT exceed tabCount)
119
+ <ScTabSwitcher
120
+ tabCount="2"
121
+ component={renderTab(views[0])}
122
+ component2={renderTab(views[1])}
123
+ />
124
+ ```
125
+
126
+ ---
127
+
128
+ ## 2. Where to use it
129
+
130
+ - **Sidebar footers** — the light/dark/system theme switcher in Photogenix's
131
+ `Sidebar`, Artifax's `DashboardSidebar`, and the DS's own `ScProfilePopup` /
132
+ `ScProfileOptions`.
133
+ - **Table/list header rows** — CXO teams & organization (Active/Pending), Catalogix
134
+ PLP views.
135
+ - **Inspector panels** — Photogenix retouch/reference views, Catalogix's text
136
+ attribute editor.
137
+ - **Inside `ScTabField`** — the DS field wrapper composes one for you.
138
+
139
+ All four host apps use it; it's part of the universal core.
140
+
141
+ ---
142
+
143
+ ## 3. When to use it
144
+
145
+ ### Use it when
146
+
147
+ - The control has **2–5 fixed** segments known at author time.
148
+ - You want the standard bordered track (radius `xl`, 0.5px `border-divider`,
149
+ `surface-base` fill) instead of hand-rolling it.
150
+
151
+ ### Don't use it — reach for this instead
152
+
153
+ | Situation | Use instead |
154
+ |---|---|
155
+ | 6+ segments, or segments from data | `ScTabs` |
156
+ | A single tab cell on its own | `ScTabComp` standalone (no track) |
157
+ | A labelled segmented control inside a form | `ScTabField` — it wraps this + two `ScTabComp`s |
158
+ | Settings / mobile-settings underline strip | `ScSettingsTabComp` |
159
+ | Accessible segmented control (real buttons + `aria-pressed`) | `ScSelectionPillGroup` |
160
+ | Pills whose selected state is a dark fill (Catalogix curation look) | `ScSelectionPillGroup` |
161
+ | Two plain actions side by side | two `ScButton`s |
162
+
163
+ ### Don't confuse with
164
+
165
+ | You may actually want | Not this |
166
+ |---|---|
167
+ | `ScTabs` — data-driven, unbounded, borderless pill row | `ScTabSwitcher` is a fixed 2–5 slot bordered track |
168
+ | `ScTabField` — already contains a switcher + a field label | Don't nest a switcher inside your own label markup |
169
+ | `ScSelectionPillGroup` — owns the active value and gives you `onChange` | `ScTabSwitcher` owns nothing; each child tracks itself |
170
+ | `ScSettingsTabComp` — underline tabs, no track | different visual family entirely |
171
+
172
+ ### The tab family at a glance
173
+
174
+ | Component | Shape | Selection API | Root | Spreads DOM props? |
175
+ |---|---|---|---|---|
176
+ | `ScTabSwitcher` | 2–5 slot bordered track | none — children own it | `div` | ✅ |
177
+ | `ScTabComp` | one tab cell | `active="true" \| "false"` (strings) | `div` | ✅ |
178
+ | `ScTabs` | N pills, data-driven | `activeTab` + `onClickTab(value)` | `div` | ❌ |
179
+ | `ScTabField` | field label + 2-tab switcher | `activeTab` + `onTabChange` | `div` | ❌ |
180
+ | `ScSettingsTabComp` | underline tab | `active` (boolean) | `div` | ❌ |
181
+ | `ScSelectionPillGroup` | segmented pills | `value` + `onChange` | `div` of `button`s | ✅ |
182
+
183
+ ---
184
+
185
+ ## 4. Why to use it
186
+
187
+ - **The track is the part everyone gets wrong.** Radius `--radius-xl`, 0.5px
188
+ `--alias-border-divider`, `--alias-surface-base` fill, `--spacing-xs` padding,
189
+ zero gap — five values that must match the theme switcher in four apps.
190
+ - **Slots, not data.** Because each segment is a full element, one segment can carry a
191
+ tooltip wrapper, a different icon set, or a disabled state without the container
192
+ needing to know — which is why Photogenix can wrap each tab in its own `Tooltip`.
193
+ - **Stateless by design.** Selection stays in your store; there is no internal state to
194
+ desync from a URL param or a persisted theme.
195
+ - **`align-self: stretch` + `flex-shrink: 0`** means it fills a sidebar column without
196
+ width plumbing and won't collapse in a constrained flex parent.
197
+
198
+ ---
199
+
200
+ ## Gotchas
201
+
202
+ **1. Missing slots render placeholder tabs.** `component`…`component5` each fall back
203
+ to a default `ScTabComp` with `SiconHome`; slot 1's fallback is also `active="true"`.
204
+
205
+ ```tsx
206
+ // WRONG — renders your tab plus a second, home-icon "Tab"
207
+ <ScTabSwitcher tabCount="2" component={<ScTabComp text="Active" style={{ flex: 1 }} />} />
208
+
209
+ // RIGHT — fill every slot up to tabCount
210
+ <ScTabSwitcher
211
+ tabCount="2"
212
+ component={<ScTabComp text="Active" type="text-only" style={{ flex: 1 }} />}
213
+ component2={<ScTabComp text="Pending" type="text-only" style={{ flex: 1 }} />}
214
+ />
215
+ ```
216
+
217
+ **2. `tabCount` gates the slots — and it's a string.**
218
+
219
+ ```tsx
220
+ // WRONG — component3 never renders (tabCount defaults to "2")
221
+ <ScTabSwitcher component={a} component2={b} component3={c} />
222
+
223
+ // ALSO WRONG — numeric literal is a type error
224
+ <ScTabSwitcher tabCount={3} … />
225
+
226
+ // RIGHT
227
+ <ScTabSwitcher tabCount="3" component={a} component2={b} component3={c} />
228
+ ```
229
+
230
+ **3. Custom children need `style={{ flex: 1 }}`.** The `flex: 1; align-self: unset`
231
+ override is applied through an internal class that only the *default* children get.
232
+ Without it your tabs size to their content and the track looks lopsided. Every host
233
+ call site passes it.
234
+
235
+ **4. Three props are dead.** `scTabCompicon`, `scTabCompactive`, `scTabComptype` exist
236
+ only so old Figma-generated call sites still typecheck. They are destructured into
237
+ `_`-prefixed locals and never used. Do not try to configure children through them.
238
+
239
+ **5. `tabCount` applies no CSS.** The component computes
240
+ `styles["tab-count-" + tabCount]`, but the stylesheet defines no `.tab-count-*` rules —
241
+ so the class resolves to `undefined` and lands in the class string as the literal text
242
+ `"undefined"`. `tabCount` is purely a render-count switch.
243
+
244
+ **6. It stretches.** `align-self: stretch` on the root means a flex-column parent makes
245
+ it full width. Wrap it in an `inline-flex` container when it should shrink-wrap
246
+ (Photogenix's `QualityToggle` does exactly that).
247
+
248
+ **7. No `role`, no keyboard, no roving tabindex.** The track is a plain div and the
249
+ children are divs. Add `role="tablist"` here and `role="tab"`/`aria-selected` on the
250
+ children if the control needs to be accessible — props are spread on both, so it
251
+ compiles.
252
+
253
+ **8. `className` is concatenated unguarded**; omitting it puts `"undefined"` in the
254
+ class list.
255
+
256
+ **9. `ScTabField` only wires slots 1 and 2.** If you pass it three tabs it sets
257
+ `tabCount="3"` but leaves `component3` unset — so the third tab renders as the home-icon
258
+ placeholder. Use `ScTabSwitcher` directly for 3+.
259
+
260
+ ---
261
+
262
+ ## In the wild
263
+
264
+ ```tsx
265
+ // cxo-dashboard src/app/components/teams-content.tsx:1171
266
+ <ScTabSwitcher
267
+ tabCount="2"
268
+ component={
269
+ <ScTabComp
270
+ text="Active"
271
+ type="text-only"
272
+ active={activeTab === "active" ? "true" : "false"}
273
+ onClick={() => setActiveTab("active")}
274
+ style={{ flex: 1 }}
275
+ />
276
+ }
277
+ component2={
278
+ <ScTabComp
279
+ text="Pending"
280
+ type="text-only"
281
+ active={activeTab === "pending" ? "true" : "false"}
282
+ onClick={() => setActiveTab("pending")}
283
+ style={{ flex: 1 }}
284
+ />
285
+ }
286
+ />
287
+
288
+ // photogenix_v2 dashboard/client/src/components/layout/Sidebar.tsx:997
289
+ <ScTabSwitcher tabCount="3" className="w-full"
290
+ component={<ScTabComp icon={<SiconSystem className="w-5 h-5" … />} active={…} type="icon-only" onClick={() => setTheme("system")} style={{ flex: 1 }} />}
291
+ component2={…}
292
+ component3={…}
293
+ />
294
+ ```
295
+
296
+ ---
297
+
298
+ ## Related
299
+
300
+ - `ScTabComp` — the tab cell that goes in the slots; read its README for the
301
+ `active="true"` string trap.
302
+ - `ScTabField` — labelled form field that composes this with two `ScTabComp`s.
303
+ - `ScTabs` — data-driven N-tab pill bar for 6+ or dynamic tabs.
304
+ - `ScSettingsTabComp` — underline tab strip for settings screens.
305
+ - `ScSelectionPillGroup` — accessible segmented control that owns its value.
306
+ - `ScProfilePopup` / `ScProfileOptions` — DS components that already contain a
307
+ theme switcher built from this.
@@ -0,0 +1,261 @@
1
+ ---
2
+ component: ScTableHeader
3
+ package: "@streamoid/ui"
4
+ category: tables
5
+ status: stable
6
+ renders: div
7
+ tags: [table, header, column-headers, members, team, users, invites, pending, settings]
8
+ related: [ScTableList, ScHeader, ScTableListMobile, ScCatalogixStoreHeader, ScBillingHistoryHeader, ScBillingLogsTableHeader, ScReferralTableHeader]
9
+ do_not_confuse_with: [ScHeader, ScReferralTableHeader, ScCatalogixStoreHeader, ScBillingHistoryHeader, ScBillingLogsTableHeader, ScMobileTopNav, ScTableList]
10
+ used_by: [cxo]
11
+ ---
12
+
13
+ # ScTableHeader
14
+
15
+ **The column-header strip of the Teams / members table.** A shaded flex row of five
16
+ uppercase muted labels — USER, ROLE, ACCESS, INVITED BY, then either SIGNED ON or
17
+ INVITE ACTION depending on `type` — rendered as five internal `ScHeader` cells on the
18
+ exact widths the member-row markup uses. It is the *ruler*; the rows are the data.
19
+
20
+ ## TL;DR for agents
21
+
22
+ - **Reach for it when:** you are building the Teams / members / pending-invites table
23
+ and need the header strip above the rows.
24
+ - **Don't reach for it when:** you need a *different* table's header
25
+ (→ `ScCatalogixStoreHeader`, `ScBillingHistoryHeader`, `ScBillingLogsTableHeader`,
26
+ `ScReferralTableHeader`), a single sortable column label (→ `ScHeader`), or the
27
+ mobile members list, which has no header at all (→ `ScTableListMobile`).
28
+ - **Four things that will bite you:**
29
+ 1. **Every column label is hardcoded English.** There is no label prop. `type` is
30
+ the *only* prop besides `className`.
31
+ 2. **`...props` is destructured but never spread.** `style`, `onClick`, `data-*`,
32
+ `aria-*` are silently dropped (and are type errors — the interface does not
33
+ extend `HTMLAttributes`). Wrap it in your own div instead.
34
+ 3. The USER column shows a **descending chevron that is pure decoration** —
35
+ nothing is sortable, nothing is clickable.
36
+ 4. It is **not a `<table>`**. No `role="table"`, no `columnheader`, no `aria-sort`.
37
+
38
+ ---
39
+
40
+ ## 1. How to use it
41
+
42
+ ### Import
43
+
44
+ ```tsx
45
+ import { ScTableHeader } from "@streamoid/ui";
46
+ import "@streamoid/ui/dist/index.css"; // once, at your app root
47
+ ```
48
+
49
+ ### Minimal usage
50
+
51
+ ```tsx
52
+ <ScTableHeader />
53
+ ```
54
+
55
+ ### Props
56
+
57
+ | Prop | Type | Default | Notes |
58
+ |---|---|---|---|
59
+ | `type` | `"default"` \| `"pending"` | `"default"` | Picks the fifth column: `default` → `SIGNED ON` (7.5rem), `pending` → `INVITE ACTION` (10rem). Nothing else changes. |
60
+ | `className` | `string` | – | Appended after the internal class. The only escape hatch — there is no `style` prop. |
61
+
62
+ That is the entire API. ⚠️ `...props` exists in the signature but is **never applied
63
+ to the DOM**, and `IScTableHeaderProps` does not extend `React.HTMLAttributes`, so
64
+ anything else you pass is both a compile error and a no-op.
65
+
66
+ ### What renders in each `type`
67
+
68
+ | Column | `type="default"` | `type="pending"` | Width |
69
+ |---|---|---|---|
70
+ | USER | ✅ (with a down chevron) | ✅ (with a down chevron) | `flex: 1` |
71
+ | ROLE | ✅ | ✅ | `5rem` |
72
+ | ACCESS | ✅ | ✅ | `11.25rem` |
73
+ | INVITED BY | ✅ | ✅ | `7.5rem` |
74
+ | SIGNED ON | ✅ | — | `7.5rem` |
75
+ | INVITE ACTION | — | ✅ | `10rem` |
76
+
77
+ ### The column contract (shared with the row markup)
78
+
79
+ `ScTableList` — and every host's hand-rolled equivalent — matches these exactly:
80
+
81
+ | Column | Header width | Row element | Row width |
82
+ |---|---|---|---|
83
+ | USER | `flex: 1` | `ScProfile` | `flex: 1` |
84
+ | ROLE | `5rem` | `ScRole` | `5rem` (fixed inside `ScRole`) |
85
+ | ACCESS | `11.25rem` | the access-icon group wrapper | `11.25rem` |
86
+ | INVITED BY | `7.5rem` | a truncating text cell | `7.5rem` |
87
+ | SIGNED ON | `7.5rem` | a truncating text cell | `7.5rem` |
88
+ | INVITE ACTION | `10rem` | `ScPendingAction` | `10rem` (fixed inside `ScPendingAction`) |
89
+
90
+ Both sides use `gap: 1.5rem` and `padding-inline: 1rem`. That, plus the widths above,
91
+ is what makes header and rows line up. Change neither via `className` unless you
92
+ change both.
93
+
94
+ ### Recipes
95
+
96
+ ```tsx
97
+ // Tabbed Teams table — the tab decides the fifth column, header and rows together
98
+ <ScTableHeader type={activeTab === "active" ? "default" : "pending"} />
99
+ {members.map((m) => (
100
+ <ScTableList
101
+ key={m.id}
102
+ variant={activeTab} // must track the header's `type`
103
+ userEmail={m.userEmail}
104
+ invitedBy={m.invitedBy}
105
+ signedOn={m.signedOn}
106
+ />
107
+ ))}
108
+ // ...or your own row on the widths below — which is what both hosts actually do,
109
+ // because ScTableList hardcodes name/role/access. See SC-tableList/README.md.
110
+
111
+ // Make it sticky. You cannot pass `style`, so use className (or wrap it).
112
+ <ScTableHeader className={styles.stickyHead} />
113
+ /* .stickyHead { position: sticky; top: 0; z-index: 1; background: var(--alias-surface-base); } */
114
+
115
+ // The wrapper form — what @streamoid/settings actually does, because `style` is dropped
116
+ // (CXO's own teams-content.tsx renders it unwrapped, so its header scrolls away)
117
+ <div style={{ position: "sticky", top: 0, zIndex: 1, backgroundColor: "var(--alias-surface-base)" }}>
118
+ <ScTableHeader type="pending" />
119
+ </div>
120
+ ```
121
+
122
+ ---
123
+
124
+ ## 2. Where to use it
125
+
126
+ - **Settings → Teams**, desktop only, on both tabs: Active members (`type="default"`)
127
+ and Pending invitations (`type="pending"`). This is the reason it exists.
128
+ - Rendered directly by CXO's `teams-content.tsx`, and by the shared
129
+ `@streamoid/settings` Teams screen that ships into CXO's desktop settings.
130
+ - Nothing else should use it: the column set *is* the workspace-member schema, baked
131
+ into the JSX with no label props.
132
+
133
+ It composes five `ScHeader` cells and a background. Nothing else.
134
+
135
+ ---
136
+
137
+ ## 3. When to use it
138
+
139
+ ### Use it when
140
+
141
+ - You are rendering **workspace members or pending invites** in a desktop table and
142
+ want the header pinned to the same widths the DS row uses.
143
+ - You want the "shaded header strip over transparent rows" treatment shared by every
144
+ table in the products.
145
+
146
+ ### Don't use it — reach for this instead
147
+
148
+ | Situation | Use instead |
149
+ |---|---|
150
+ | Catalogix stores list header | `ScCatalogixStoreHeader` (and it takes label props, unlike this) |
151
+ | Billing history table header (Invoice / Amount / Date / Action) | `ScBillingHistoryHeader` |
152
+ | Credit & usage logs header (App / Activity / Used by / Date / Credits used / Balance) | `ScBillingLogsTableHeader` |
153
+ | Referral history header (User / Date / Status / Reward) | `ScReferralTableHeader` |
154
+ | One sortable column label with an asc/desc chevron | `ScHeader` — this strip is five of them, pre-labelled |
155
+ | A genuinely generic N-column table header | Compose `ScHeader` cells yourself; the labels and widths here are hard-wired |
156
+ | The mobile members list | `ScTableListMobile` — the mobile pattern is stacked cards with **no header row** |
157
+ | A page title bar with actions | Neither this nor `ScHeader`; use your page layout + `ScButton` |
158
+
159
+ ### Don't confuse with
160
+
161
+ | You may actually want | Not this |
162
+ |---|---|
163
+ | `ScHeader` — a **single** column-label cell, `state="up" \| "down" \| "none"`, sort chevron | `ScTableHeader` is the whole strip and hardcodes the five labels |
164
+ | `ScReferralTableHeader` — same shape, **referral** columns, no `type` prop at all | This one is the members table |
165
+ | `ScCatalogixStoreHeader` — same shape but every label is a **prop**, plus `showAssets`/`showCreatedBy` gating | This one has no label props |
166
+ | `ScTableList` — the *row* sibling, not the header | Different component; see its README before using it |
167
+ | `ScMobileTopNav` — the mobile app bar | Unrelated; "header" here means column headers |
168
+
169
+ ---
170
+
171
+ ## 4. Why to use it
172
+
173
+ - **It is the alignment contract.** The widths (`flex:1`, `5`, `11.25`, `7.5`, then
174
+ `7.5` or `10` rem), the `1.5rem` gap and the `1rem` inline padding exist in this
175
+ file and in the row file. Hand-rolling the header is how member tables shear apart
176
+ when someone adds a column.
177
+ - **The `type` switch is the pending/active difference in one prop** — you don't
178
+ duplicate a five-column strip per tab.
179
+ - **Two-tone that survives light mode.** The strip paints
180
+ `--alias-fill-neutral-neutral` while rows stay transparent, so it still reads as a
181
+ header when the theme flips, instead of relying on a grey that collapses to white.
182
+ - **Label typography is already the DS's** `font-size-xs` + `--alias-text-and-icons-muted`
183
+ + uppercase, matching every other table header in the products.
184
+
185
+ ---
186
+
187
+ ## Gotchas
188
+
189
+ **1. There are no label props.** "User", "Role", "Access", "Invited by", "Signed On"
190
+ and "Invite action" are literal strings inside the component. Do not use it on a
191
+ localised surface without changing the DS first.
192
+
193
+ **2. `...props` is destructured and thrown away.** The root div receives only
194
+ `className`. `style`, `onClick`, `id`, `data-*` and `aria-*` never reach the DOM.
195
+
196
+ ```tsx
197
+ // WRONG — type error, and even with a cast the style is dropped
198
+ <ScTableHeader type="pending" style={{ position: "sticky", top: 0 }} />
199
+
200
+ // RIGHT — className, or your own wrapper
201
+ <div style={{ position: "sticky", top: 0, zIndex: 1 }}>
202
+ <ScTableHeader type="pending" />
203
+ </div>
204
+ ```
205
+
206
+ **3. The USER chevron is decoration.** The first cell is `<ScHeader state="down">`, so
207
+ it renders a double-down arrow that *looks* like "sorted descending". No `onClick` is
208
+ passed to any cell and there is no sort prop anywhere. Either wire sorting outside the
209
+ component or expect users to click a dead chevron.
210
+
211
+ **4. It is not a table.** No `role="table"`/`row`/`columnheader`, no `<th>`, no
212
+ `scope`, no `aria-sort`. Row cells are not associated with these labels for assistive
213
+ tech. If accessibility matters on the screen, add roles on your own wrapper and rows.
214
+
215
+ **5. Not sticky, and it doesn't own the scroll.** Long member lists scroll the header
216
+ away. Add `position: sticky; top: 0` **plus** an opaque `background` and a `z-index`
217
+ (the rows are `position: relative`, so without one they paint over it).
218
+
219
+ **6. `className` lands as the literal string `"undefined"` when omitted.** The class
220
+ list is built by string concatenation (`scTableHeader undefined type-default`).
221
+ Harmless in the browser, but exact-string class assertions in tests will fail.
222
+
223
+ **7. `type` must be exactly `"default"` or `"pending"`.** The variant class is
224
+ `styles["type-" + type]`; an unknown value yields the class `undefined` and the base
225
+ widths, so you get the four common columns and no fifth one, silently.
226
+
227
+ **8. `flex-shrink: 0` + `align-self: stretch`.** It expects to be a direct child of a
228
+ `flex-direction: column` container that is at least as wide as
229
+ `flex:1 + 5 + 11.25 + 7.5 + 7.5rem + gaps`. Below that the flexible USER column
230
+ collapses first; the fixed columns never shrink and will overflow the container
231
+ horizontally.
232
+
233
+ ---
234
+
235
+ ## In the wild
236
+
237
+ ```tsx
238
+ // cxo-dashboard src/app/components/teams-content.tsx:1225
239
+ <ScTableHeader
240
+ type={activeTab === "active" ? "default" : "pending"}
241
+ />
242
+ ```
243
+
244
+ The shared settings package does the same at
245
+ `npm-components packages/settings/src/teams-content.tsx:1149`, there wrapped in a
246
+ `position: sticky` div (Gotcha 5 / Gotcha 2). In both places the rows underneath are a
247
+ local `TeamRow`, **not** `ScTableList` — see `SC-tableList/README.md` for why.
248
+
249
+ ---
250
+
251
+ ## Related
252
+
253
+ - `ScTableList` — the DS row sibling for this header (read its README first; most of
254
+ its content is hardcoded and hosts hand-roll the row instead).
255
+ - `ScHeader` — the single column-label cell this strip is built from; use it to
256
+ compose a header for a table the DS doesn't cover.
257
+ - `ScRole`, `ScAccess`, `ScProfile`, `ScPendingAction` — the per-cell pieces the row
258
+ side of this contract is made of.
259
+ - `ScTableListMobile` — the mobile members list; stacked cards, no header strip.
260
+ - `ScReferralTableHeader` / `ScBillingHistoryHeader` / `ScBillingLogsTableHeader` /
261
+ `ScCatalogixStoreHeader` — the other domain-specific header/row pairs, same pattern.