@streamoid/ui 0.6.17 → 0.6.18

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (131) hide show
  1. package/README.md +35 -18
  2. package/dist/docs/AGENTS.md +321 -0
  3. package/dist/docs/CreditWarningBanner.md +305 -0
  4. package/dist/docs/InvoiceHistoryMobile.md +222 -0
  5. package/dist/docs/ScAccess.md +259 -0
  6. package/dist/docs/ScAppCard.md +244 -0
  7. package/dist/docs/ScAppCardForCopilot.md +230 -0
  8. package/dist/docs/ScAppCardV3.md +273 -0
  9. package/dist/docs/ScAppField.md +308 -0
  10. package/dist/docs/ScAppListingCard.md +271 -0
  11. package/dist/docs/ScAppSwitchPanel.md +286 -0
  12. package/dist/docs/ScAppcardLogos.md +226 -0
  13. package/dist/docs/ScArtifaxInvite.md +262 -0
  14. package/dist/docs/ScArtifaxSidebar.md +330 -0
  15. package/dist/docs/ScAskAgentButton.md +307 -0
  16. package/dist/docs/ScBadges.md +261 -0
  17. package/dist/docs/ScBeacon.md +244 -0
  18. package/dist/docs/ScBillingHistoryHeader.md +210 -0
  19. package/dist/docs/ScBillingHistoryTableList.md +243 -0
  20. package/dist/docs/ScBillingLogsTableHeader.md +212 -0
  21. package/dist/docs/ScBillingLogsTableList.md +251 -0
  22. package/dist/docs/ScBriefCard.md +255 -0
  23. package/dist/docs/ScButton.md +251 -0
  24. package/dist/docs/ScCalendar.md +268 -0
  25. package/dist/docs/ScCalendarDateComps.md +264 -0
  26. package/dist/docs/ScCatalogixInvite.md +345 -0
  27. package/dist/docs/ScCatalogixSidebar.md +337 -0
  28. package/dist/docs/ScCatalogixStoreHeader.md +246 -0
  29. package/dist/docs/ScCatalogixStoreTableList.md +316 -0
  30. package/dist/docs/ScCheckField.md +233 -0
  31. package/dist/docs/ScCheckbox.md +272 -0
  32. package/dist/docs/ScCounter.md +235 -0
  33. package/dist/docs/ScCreditsUsageCard.md +247 -0
  34. package/dist/docs/ScCreditsUsageCardMobile.md +224 -0
  35. package/dist/docs/ScDefaultCard.md +269 -0
  36. package/dist/docs/ScDp.md +245 -0
  37. package/dist/docs/ScDrawer.md +318 -0
  38. package/dist/docs/ScFieldButton.md +255 -0
  39. package/dist/docs/ScFileField.md +268 -0
  40. package/dist/docs/ScGoogleSignIn.md +250 -0
  41. package/dist/docs/ScGuide.md +278 -0
  42. package/dist/docs/ScHDivider.md +213 -0
  43. package/dist/docs/ScHeader.md +222 -0
  44. package/dist/docs/ScImageField.md +253 -0
  45. package/dist/docs/ScInChatList.md +277 -0
  46. package/dist/docs/ScInChatMessage.md +205 -0
  47. package/dist/docs/ScInfoPopup.md +248 -0
  48. package/dist/docs/ScIntialProfileCover.md +233 -0
  49. package/dist/docs/ScInvoiceHistoryMobile.md +187 -0
  50. package/dist/docs/ScLogoUnit.md +232 -0
  51. package/dist/docs/ScMappingCard.md +241 -0
  52. package/dist/docs/ScMediaApproval.md +301 -0
  53. package/dist/docs/ScMediaSelect.md +310 -0
  54. package/dist/docs/ScMenuOptions.md +308 -0
  55. package/dist/docs/ScMobileBottomAction.md +252 -0
  56. package/dist/docs/ScMobileTopNav.md +279 -0
  57. package/dist/docs/ScModal.md +291 -0
  58. package/dist/docs/ScOnlyField.md +302 -0
  59. package/dist/docs/ScOnlyIcon.md +213 -0
  60. package/dist/docs/ScPagination.md +284 -0
  61. package/dist/docs/ScPairtext.md +287 -0
  62. package/dist/docs/ScPendingAction.md +238 -0
  63. package/dist/docs/ScPhtogenixInvite.md +275 -0
  64. package/dist/docs/ScPlanCard.md +302 -0
  65. package/dist/docs/ScPlanComparison.md +264 -0
  66. package/dist/docs/ScPlanDetailsCard.md +246 -0
  67. package/dist/docs/ScPlanDetailsCardMobile.md +240 -0
  68. package/dist/docs/ScPopUpMenu.md +224 -0
  69. package/dist/docs/ScProfile.md +234 -0
  70. package/dist/docs/ScProfileImageUpdate.md +261 -0
  71. package/dist/docs/ScProfileOptions.md +245 -0
  72. package/dist/docs/ScProfilePopup.md +396 -0
  73. package/dist/docs/ScProfileSettingsComp.md +250 -0
  74. package/dist/docs/ScProfileV2Mobile.md +216 -0
  75. package/dist/docs/ScProgressBar.md +267 -0
  76. package/dist/docs/ScQuickPrompt.md +277 -0
  77. package/dist/docs/ScRadio.md +228 -0
  78. package/dist/docs/ScReferralCardMobile.md +226 -0
  79. package/dist/docs/ScReferralTableHeader.md +260 -0
  80. package/dist/docs/ScReferralTableList.md +293 -0
  81. package/dist/docs/ScRole.md +226 -0
  82. package/dist/docs/ScRoleMobile.md +199 -0
  83. package/dist/docs/ScSelect.md +270 -0
  84. package/dist/docs/ScSelection.md +256 -0
  85. package/dist/docs/ScSelectionList.md +272 -0
  86. package/dist/docs/ScSelectionPill.md +240 -0
  87. package/dist/docs/ScSelectionPillGroup.md +302 -0
  88. package/dist/docs/ScSettingsNav.md +212 -0
  89. package/dist/docs/ScSettingsTabComp.md +260 -0
  90. package/dist/docs/ScSideBarLogoUnit.md +340 -0
  91. package/dist/docs/ScSidebar.md +243 -0
  92. package/dist/docs/ScSidebarIcons.md +232 -0
  93. package/dist/docs/ScSidebarMenu.md +283 -0
  94. package/dist/docs/ScSidebarProfile.md +231 -0
  95. package/dist/docs/ScSidebarSwitchMenu.md +258 -0
  96. package/dist/docs/ScSlider.md +194 -0
  97. package/dist/docs/ScStoreCard.md +252 -0
  98. package/dist/docs/ScStrLogo.md +253 -0
  99. package/dist/docs/ScStreamoidWordmark.md +302 -0
  100. package/dist/docs/ScSubAgent.md +226 -0
  101. package/dist/docs/ScTabComp.md +308 -0
  102. package/dist/docs/ScTabField.md +258 -0
  103. package/dist/docs/ScTabSwitcher.md +307 -0
  104. package/dist/docs/ScTableHeader.md +261 -0
  105. package/dist/docs/ScTableList.md +301 -0
  106. package/dist/docs/ScTableListMobile.md +282 -0
  107. package/dist/docs/ScTabs.md +268 -0
  108. package/dist/docs/ScTaxonomyPill.md +263 -0
  109. package/dist/docs/ScTextArea.md +259 -0
  110. package/dist/docs/ScTextField.md +324 -0
  111. package/dist/docs/ScThinkingStepIcon.md +249 -0
  112. package/dist/docs/ScTodoList.md +288 -0
  113. package/dist/docs/ScToggleSwitch.md +229 -0
  114. package/dist/docs/ScUsageHistoryMobile.md +194 -0
  115. package/dist/docs/ScVDivider.md +215 -0
  116. package/dist/docs/ScValueMappingL1.md +256 -0
  117. package/dist/docs/ScVersion.md +251 -0
  118. package/dist/docs/ScWorkspace.md +233 -0
  119. package/dist/docs/ScWorkspaceCard.md +234 -0
  120. package/dist/docs/ScWorkspaceSettingsMobile.md +265 -0
  121. package/dist/docs/ScWorkspaceSwitchCard.md +314 -0
  122. package/dist/docs/ScWorkspaceSwitchMobile.md +241 -0
  123. package/dist/docs/ScWorkspaceSwitchMobileV2.md +278 -0
  124. package/dist/docs/StreamoidSidebar.md +403 -0
  125. package/dist/docs/StreamoidWorkspaceSwitcher.md +307 -0
  126. package/dist/docs/UsageHistoryMobile.md +235 -0
  127. package/dist/docs/components.json +4849 -0
  128. package/dist/index.css +36 -36
  129. package/dist/index.d.mts +10 -0
  130. package/dist/index.d.ts +10 -0
  131. package/package.json +3 -2
@@ -0,0 +1,307 @@
1
+ ---
2
+ component: ScAskAgentButton
3
+ also_exports: [ScAskAgentSlot, AssistantCta]
4
+ package: "@streamoid/ui"
5
+ category: sidebar
6
+ status: stable
7
+ renders: button[type="button"]
8
+ tags: [ask-cxo, assistant, agent, cta, gradient, copilot, sidebar-footer, pill]
9
+ related: [StreamoidSidebar, ScArtifaxSidebar, ScButton, ScQuickPrompt]
10
+ do_not_confuse_with: [ScButton, ScCxoCopilotLogo, ScQuickPrompt, ScSidebarMenu]
11
+ used_by: [catalogix, artifax]
12
+ required_props: [cta]
13
+ ---
14
+
15
+ # ScAskAgentButton
16
+
17
+ **The gradient "Ask CXO" call-to-action that sits just above a sidebar's profile
18
+ row.** A red→coral gradient pill with a leading icon and a label when expanded; a
19
+ 40×40 gradient square with only the icon when collapsed. `ScAskAgentSlot` is the same
20
+ button plus the rail insets both sidebars use.
21
+
22
+ You will normally never render it yourself: pass `assistantCta` to `StreamoidSidebar`
23
+ or `ScArtifaxSidebar` and they render `ScAskAgentSlot` for you.
24
+
25
+ ## TL;DR for agents
26
+
27
+ - **Reach for it when:** you need the assistant-panel CTA **outside** a DS sidebar.
28
+ Inside one, use the sidebar's `assistantCta` prop instead.
29
+ - **Don't reach for it when:** it's an ordinary action (→ `ScButton`), a suggested
30
+ prompt chip in a chat (→ `ScQuickPrompt`), or a nav row (→ `ScSidebarMenu`).
31
+ - **Three things that will bite you:**
32
+ 1. `collapsed` drops the label, so with **no `icon` the collapsed square renders
33
+ empty** — a 40×40 patch of gradient with nothing in it.
34
+ 2. Colours are **deliberately not tokens**: a hardcoded gradient and `#f5f5f5`
35
+ text. It looks identical in light and dark mode. That's the design, not a bug.
36
+ 3. There is **no `disabled`** and no `loading`. Every click fires.
37
+
38
+ ---
39
+
40
+ ## 1. How to use it
41
+
42
+ ### Import
43
+
44
+ ```tsx
45
+ import {
46
+ ScAskAgentButton,
47
+ ScAskAgentSlot,
48
+ type AssistantCta,
49
+ } from "@streamoid/ui";
50
+ import "@streamoid/ui/dist/index.css"; // once, at your app root
51
+ ```
52
+
53
+ ### Minimal usage
54
+
55
+ Through a sidebar (the normal path — the sidebar picks the right variant per rail):
56
+
57
+ ```tsx
58
+ <StreamoidSidebar
59
+ assistantCta={{
60
+ label: "Ask CXO",
61
+ icon: <SiconBolt size={20} color="currentColor" />,
62
+ active: askOpen,
63
+ onClick: () => setAskOpen((v) => !v),
64
+ }}
65
+ /* … */
66
+ />
67
+ ```
68
+
69
+ Standalone:
70
+
71
+ ```tsx
72
+ <ScAskAgentSlot
73
+ cta={{ label: "Ask CXO", icon: <SiconBolt size={20} />, onClick: openAssistant }}
74
+ />
75
+ ```
76
+
77
+ ### Props — `ScAskAgentButton` and `ScAskAgentSlot`
78
+
79
+ Both take exactly the same two props (`IScAskAgentButtonProps`).
80
+
81
+ | Prop | Type | Default | Notes |
82
+ |---|---|---|---|
83
+ | `cta` | `AssistantCta` | — | **Required.** The whole contract lives in this object. |
84
+ | `collapsed` | `boolean` | `false` | `true` → 40×40 icon-only square, label dropped, `flex-shrink: 0`. `false` → full-width pill. |
85
+
86
+ ### `AssistantCta`
87
+
88
+ | Field | Type | Default | Notes |
89
+ |---|---|---|---|
90
+ | `label` | `string` | — | **Required.** Visible text when expanded. When collapsed it is **invisible but still the accessible name** — never drop it. |
91
+ | `icon` | `ReactNode` | – | Leading icon; rendered in both states. ⚠️ Optional in the type, **mandatory in practice for `collapsed`** (it is the only content). |
92
+ | `active` | `boolean` | – | Pressed styling (a 1.5px inset white ring) and `aria-pressed`. Wire it to "the panel this opens is visible". |
93
+ | `ariaLabel` | `string` | falls back to `label` | Used for both `aria-label` and the native `title` tooltip. |
94
+ | `onClick` | `() => void` | — | **Required.** |
95
+
96
+ ### What renders in each state
97
+
98
+ | | `collapsed={false}` | `collapsed={true}` |
99
+ |---|---|---|
100
+ | Button | `width: 100%`, `padding: var(--spacing-xl)` (12px) | `2.5rem × 2.5rem`, `padding: var(--spacing-md)` |
101
+ | Icon | rendered, svg pinned to 20×20 | rendered, the only content |
102
+ | Label | 12px/600, `nowrap`, `#f5f5f5` | **not rendered** |
103
+ | Slot insets | `padding: 0 8px 16px` (full width) | `padding: 8px 0`, centred |
104
+ | `aria-pressed` | `cta.active ?? undefined` | same |
105
+
106
+ ### Recipes
107
+
108
+ ```tsx
109
+ // Both rails, driven by the sidebar (Catalogix / Artifax)
110
+ <ScArtifaxSidebar
111
+ expanded={!collapsed}
112
+ assistantCta={
113
+ onToggleAssistant
114
+ ? { label: "Ask CXO", icon: <SiconBolt className="h-5 w-5" />, active: assistantOpen, onClick: onToggleAssistant }
115
+ : undefined // omit/null hides the CTA entirely
116
+ }
117
+ /* … */
118
+ />
119
+
120
+ // Standalone, mirroring the sidebar rails yourself
121
+ {expanded
122
+ ? <ScAskAgentSlot cta={cta} />
123
+ : <ScAskAgentSlot cta={cta} collapsed />}
124
+
125
+ // Bare button (no rail insets) — e.g. in a page header or an empty state
126
+ <ScAskAgentButton
127
+ cta={{
128
+ label: "Ask CXO",
129
+ ariaLabel: "Open the CXO assistant", // distinct tooltip / accessible name
130
+ icon: <SiconBolt size={20} />,
131
+ active: panelOpen,
132
+ onClick: togglePanel,
133
+ }}
134
+ />
135
+
136
+ // "Disabled" isn't supported — gate at the call site instead
137
+ {canUseAssistant ? <ScAskAgentSlot cta={cta} /> : null}
138
+ ```
139
+
140
+ ---
141
+
142
+ ## 2. Where to use it
143
+
144
+ - **The pre-footer slot of a sidebar rail**, immediately above `preFooterContent` and
145
+ the profile row. `StreamoidSidebar` renders `<ScAskAgentSlot cta={assistantCta} />`
146
+ (expanded) / `<ScAskAgentSlot cta={assistantCta} collapsed />` (collapsed);
147
+ `ScArtifaxSidebar` does the same.
148
+ - **Live hosts:** Catalogix (`LeftMenu`, via `StreamoidSidebar`) and Artifax
149
+ (`DashboardSidebar`, via `ScArtifaxSidebar`). CXO instead exposes "Ask CXO" as a
150
+ **top nav item** in its sidebar body, so it does not use this component.
151
+ - Standalone use (bare `ScAskAgentButton`) is fine anywhere the CTA must not be
152
+ inset for a 256px rail.
153
+
154
+ ---
155
+
156
+ ## 3. When to use it
157
+
158
+ ### Use it when
159
+
160
+ - The action is **"open the assistant/copilot panel"** and it should read as a
161
+ special, branded affordance rather than a regular button.
162
+ - You need it to survive rail collapse without a second implementation.
163
+ - You want `aria-pressed` to track whether the panel is open.
164
+
165
+ ### Don't use it — reach for this instead
166
+
167
+ | Situation | Use instead |
168
+ |---|---|
169
+ | Any ordinary action or CTA | `ScButton` (`variant="mono"` / `"primary"` / `"outline"`) |
170
+ | A suggested-prompt chip inside the chat transcript | `ScQuickPrompt` |
171
+ | A nav row that happens to route to the assistant | `ScSidebarMenu`, or `StreamoidSidebar`'s `topItems` — what CXO does |
172
+ | The CXO Copilot brand lockup / logo | `ScCxoCopilotLogo` |
173
+ | A destructive/primary form submit | `ScButton variant="error"` / `"primary"` |
174
+ | You need `disabled`, `loading` or a spinner | `ScButton` — this component has none |
175
+
176
+ ### Don't confuse with
177
+
178
+ | You may actually want | Not this |
179
+ |---|---|
180
+ | `ScButton` — six variants, tokenised, `loading`, `state="disabled"` | This is one fixed brand gradient with no states beyond `active` |
181
+ | `ScQuickPrompt` — chat-runtime prompt chips | Different family, different surface |
182
+ | `ScCxoCopilotLogo` — brand art | Not interactive |
183
+ | `ScSidebarMenu` with an icon | The nav-row treatment; no gradient |
184
+
185
+ ---
186
+
187
+ ## 4. Why to use it
188
+
189
+ - **One gradient, one place.** The CSS was extracted verbatim from
190
+ `ScArtifaxSidebar.module.css` so Artifax's pill and the CXO/Catalogix pill cannot
191
+ drift. Matches Figma `SC-AskAgent` (node 6141-7469): red→coral 135° gradient, 0.5px
192
+ red-tinted stroke, `var(--radius-2xl)` corners (**14px** with `@streamoid/tokens`
193
+ loaded — the `0.75rem`/12px in the CSS is only the no-token fallback), 12px padding.
194
+ - **Deliberately theme-*in*variant, correctly.** The gradient is always dark, so the
195
+ label and icon are pinned to `#f5f5f5` rather than `--alias-text-*`. Using the token
196
+ would make the text black-on-red in light mode. Hand-rolling this is exactly how
197
+ that bug ships.
198
+ - **Both rails in one prop.** `collapsed` swaps a full-width pill for a 40×40 square
199
+ and drops the label while keeping the accessible name — no duplicate markup, no
200
+ divergent hover.
201
+ - **Correct toggle semantics.** Real `<button type="button">` with `aria-pressed`,
202
+ a `title` tooltip, and a `:focus-visible` ring designed to be visible **on the
203
+ gradient** (white, 2px, 2px offset).
204
+ - **The slot owns the rail geometry.** `ScAskAgentSlot` carries the Figma insets
205
+ (8px horizontal, 16px gap before the divider), so a sidebar only decides *whether*
206
+ to show the CTA.
207
+
208
+ ---
209
+
210
+ ## Gotchas
211
+
212
+ **1. Collapsed with no `icon` renders an empty gradient square.** The label is
213
+ suppressed and `icon` is optional, so nothing is left.
214
+
215
+ ```tsx
216
+ // WRONG — a blank 40×40 gradient tile
217
+ <ScAskAgentSlot cta={{ label: "Ask CXO", onClick: open }} collapsed />
218
+
219
+ // RIGHT
220
+ <ScAskAgentSlot cta={{ label: "Ask CXO", icon: <SiconBolt size={20} />, onClick: open }} collapsed />
221
+ ```
222
+
223
+ **2. Don't drop `label` when collapsing.** It is required, and it supplies both
224
+ `aria-label` and `title` when `ariaLabel` is unset — the collapsed square's only
225
+ accessible name.
226
+
227
+ **3. It ignores your theme.** Gradient `rgba(217,21,54) → rgba(238,94,58)`, border
228
+ `rgba(255,0,0,0.32)`, text/icon `#f5f5f5`, hover `filter: brightness(1.08)` — all
229
+ literal, none tokenised. It will not respond to light mode, `data-theme`, or a
230
+ `color` you set on a parent.
231
+
232
+ **4. Your icon's colour is not overridden, but the container's is.**
233
+ `.askAgentIcon` sets `color: #f5f5f5`, so an icon drawn with `currentColor`
234
+ (`<SiconBolt color="currentColor" />`) comes out light — which is what you want. An
235
+ icon with an explicit token colour will keep it and may be unreadable on the gradient.
236
+
237
+ **5. The svg is pinned to 20×20 at class-level specificity.**
238
+ `.askAgentIcon :where(svg) { width: 1.25rem; height: 1.25rem }`. `:where()` adds no
239
+ specificity, so a Tailwind `h-5 w-5` on your icon *ties* and the winner depends on
240
+ stylesheet order. If you need a different size, set it inline:
241
+ `<MyIcon style={{ width: 24, height: 24 }} />`.
242
+
243
+ **6. No `disabled`, no `loading`, no `state`.** Gate rendering at the call site
244
+ (Artifax only builds the `assistantCta` object when the host supplies a toggle).
245
+
246
+ **7. `active` is styling *and* semantics.** It adds an inset white ring **and** sets
247
+ `aria-pressed`. Don't use it as a "highlight" for anything other than "the thing this
248
+ opens is currently open", or screen readers will lie.
249
+
250
+ **8. Don't double it up.** Both sidebars render this from `assistantCta`. Adding your
251
+ own `ScAskAgentSlot` to `preFooterContent` gives you two pills stacked.
252
+
253
+ **9. `ScAskAgentButton` vs `ScAskAgentSlot`.** The button has **no outer margin**; the
254
+ slot adds the rail insets (`0 8px 16px` expanded, `8px 0` collapsed). Inside a sidebar
255
+ rail use the slot; elsewhere use the button and space it yourself.
256
+
257
+ **10. The focus ring is white.** `outline: 2px solid rgba(255,255,255,0.9)` — it's
258
+ designed to sit on the gradient. On a white page background the offset ring is
259
+ effectively invisible.
260
+
261
+ **11. `ArtifaxSidebarAssistantCta` is just an alias of `AssistantCta`.** Kept as a
262
+ named export for back-compat; the same object satisfies both sidebars.
263
+
264
+ ---
265
+
266
+ ## In the wild
267
+
268
+ The hosts render it through their sidebar's `assistantCta` prop rather than importing
269
+ the component:
270
+
271
+ ```jsx
272
+ // catalogix/dashboard app/containers/LeftMenu/index.jsx:1005 (→ StreamoidSidebar renders ScAskAgentSlot)
273
+ assistantCta={{
274
+ label: "Ask CXO",
275
+ icon: <SiconBolt size={20} color="currentColor" />,
276
+ active: askCxoOpen,
277
+ onClick: toggleAskCxo,
278
+ }}
279
+ ```
280
+
281
+ ```tsx
282
+ // artifax packages/shared/src/components/DashboardSidebar.tsx:684 (→ ScArtifaxSidebar renders ScAskAgentSlot)
283
+ assistantCta={
284
+ onToggleAssistant
285
+ ? {
286
+ label: ASK_CXO_LABEL,
287
+ icon: <SiconBolt className="h-5 w-5" />,
288
+ active: assistantOpen,
289
+ onClick: onToggleAssistant,
290
+ }
291
+ : undefined
292
+ }
293
+ ```
294
+
295
+ No host imports `ScAskAgentButton` / `ScAskAgentSlot` directly — if you need it
296
+ outside a DS sidebar rail, you are the first, and `ScAskAgentButton` (not the slot)
297
+ is the right entry point.
298
+
299
+ ---
300
+
301
+ ## Related
302
+
303
+ - `StreamoidSidebar` — pass `assistantCta`; it renders the slot in both rails.
304
+ - `ScArtifaxSidebar` — same, via `ArtifaxSidebarAssistantCta`.
305
+ - `ScButton` — the tokenised, stateful button for everything that isn't this CTA.
306
+ - `ScQuickPrompt` — suggested-prompt chips inside the chat surface.
307
+ - `@streamoid/icons` — `SiconBolt` is the icon both hosts pass.
@@ -0,0 +1,261 @@
1
+ ---
2
+ component: ScBadges
3
+ package: "@streamoid/ui"
4
+ category: overlays
5
+ status: stable
6
+ renders: div
7
+ tags: [badge, chip, status, tag, label, pill, count, success, warning, error, info]
8
+ related: [ScBeacon, ScRole, ScSelectionPill, ScTaxonomyPill, ScProgressBar]
9
+ do_not_confuse_with: [ScBeacon, ScSelectionPill, ScTaxonomyPill, ScRole, ScButton]
10
+ used_by: [cxo, photogenix, artifax]
11
+ ---
12
+
13
+ # ScBadges
14
+
15
+ **The status chip.** A small rounded box with one line of 12px text, in five
16
+ semantic colours × two skins (`solid` filled, `opaque` outlined). Display-only:
17
+ it takes no click handler, no style, no aria — just `text`, `variant`,
18
+ `styleVariant`, `className`.
19
+
20
+ ## TL;DR for agents
21
+
22
+ - **Reach for it when:** you need to state a status, a count or a tag next to
23
+ something — "Revoked", "Active", "1,240 credits", "Needs attention".
24
+ - **Don't reach for it when:** it should be clickable (→ `ScSelectionPill`,
25
+ `ScTaxonomyPill`, `ScButton`), it's a bare status dot with no text
26
+ (→ `ScBeacon`), or it's specifically an Admin/Member role (→ `ScRole`).
27
+ - **Four things that will bite you:**
28
+ 1. `...props` is destructured but **never spread**. `onClick`, `style`,
29
+ `title`, `data-*`, `aria-*` are all dropped *and* are type errors. Wrap it.
30
+ 2. The root is `align-self: stretch` ⚠️ — inside a flex row it grows to the
31
+ tallest sibling's height instead of hugging its text.
32
+ 3. `text` defaults to `"Badge"`.
33
+ 4. `styleVariant="solid"` + `variant="info"` is the one solid combination that
34
+ does **not** force white text — a real contrast inconsistency.
35
+
36
+ ---
37
+
38
+ ## 1. How to use it
39
+
40
+ ### Import
41
+
42
+ ```tsx
43
+ import { ScBadges } from "@streamoid/ui";
44
+ import "@streamoid/ui/dist/index.css"; // once, at your app root
45
+ ```
46
+
47
+ ### Minimal usage
48
+
49
+ ```tsx
50
+ <ScBadges text="Revoked" variant="error" />
51
+ ```
52
+
53
+ ### Props
54
+
55
+ | Prop | Type | Default | Notes |
56
+ |---|---|---|---|
57
+ | `text` | `string` | `"Badge"` | ⚠️ Real default. Plain string only — build the label yourself (`` `${n} credits` ``). |
58
+ | `variant` | `"default"` \| `"success"` \| `"warning"` \| `"error"` \| `"info"` | `"default"` | The semantic role. |
59
+ | `styleVariant` | `"solid"` \| `"opaque"` | `"solid"` | `solid` = filled, `opaque` = 1px semantic border + semantic text on a **transparent** background (`background: unset`). |
60
+ | `className` | `string` | – | Concatenated unconditionally — see Gotcha 5. **The only escape hatch you get.** |
61
+
62
+ `IScBadgesProps` does **not** extend `HTMLAttributes`, and the component's
63
+ `...props` rest is collected and thrown away. There is no `onClick`, `style`,
64
+ `title`, `size`, `icon` or `onClose`.
65
+
66
+ ### The colour matrix
67
+
68
+ | `variant` | `styleVariant="solid"` | `styleVariant="opaque"` |
69
+ |---|---|---|
70
+ | `default` | fill `--alias-fill-neutral-neutralactive`, text `…-primary` | border `--alias-border-strong`, text `…-tertiary` |
71
+ | `success` | fill `--alias-fill-success-solidactive`, text **`…-fullwhite`** | border `--alias-border-successplus`, text `…-success` |
72
+ | `warning` | fill `--alias-fill-warning-solidactive`, text **`…-fullwhite`** | border `--alias-border-warningplus`, text `…-warning` |
73
+ | `error` | fill `--alias-fill-error-solidactive`, text **`…-fullwhite`** | border `--alias-border-errorplus`, text `…-error` |
74
+ | `info` | fill `--alias-fill-info-solid`, text `…-primary` ⚠️ | border `--alias-border-infoplus`, text `…-info` |
75
+
76
+ ### Recipes
77
+
78
+ ```tsx
79
+ // Outlined status chip — the most common host choice (quieter in dense lists)
80
+ <ScBadges text={label} variant={statusVariant} styleVariant="opaque" />
81
+
82
+ // Make it clickable / hoverable — wrap it, you cannot pass onClick
83
+ <span role="button" tabIndex={0} onClick={filterByStatus} style={{ cursor: "pointer" }}>
84
+ <ScBadges text="Needs attention" variant="warning" styleVariant="opaque" />
85
+ </span>
86
+
87
+ // Stop it stretching to the row height
88
+ <div style={{ display: "flex", alignItems: "center", gap: 8 }}>
89
+ <span>Feed status</span>
90
+ <ScBadges text="Live" variant="success" styleVariant="opaque" className="hug" />
91
+ </div>
92
+ // .hug { align-self: center; } /* or: flex-shrink:0; align-self:auto */
93
+
94
+ // Map your domain status onto a variant once, at the boundary
95
+ const VARIANT = { active: "success", pending: "warning", failed: "error", draft: "default" } as const;
96
+ <ScBadges text={STATUS_LABEL[s]} variant={VARIANT[s]} styleVariant="opaque" />
97
+ ```
98
+
99
+ ---
100
+
101
+ ## 2. Where to use it
102
+
103
+ - **Table / list rows** — status column. CXO's token list marks revoked API tokens;
104
+ Photogenix wraps it as `StatusBadge` / `ConsistencyStatusPill` for render batches.
105
+ - **Card headers** — `ScPlanDetailsCard` (+ its Mobile twin), `ScWorkspaceCard` and
106
+ `ScProfileOptions` each render one internally; pass their own badge prop rather
107
+ than nesting your own.
108
+ - **Sidebar footer** — Artifax puts the credits figure in an `opaque` badge beside
109
+ the workspace name.
110
+ - **Referral / billing tables** — `ScReferralTableList` renders one per row.
111
+ - **Gallery / media tiles** — Photogenix's `RendersGallery`.
112
+
113
+ Used by CXO, Photogenix and Artifax; Catalogix has app-local status pills. Part of
114
+ the catalog's "universal core".
115
+
116
+ ---
117
+
118
+ ## 3. When to use it
119
+
120
+ ### Use it when
121
+
122
+ - The content is a **short, non-interactive fact**: a state, a count, a tag.
123
+ - You want the semantic colour pair (fill/border + text) to be identical to every
124
+ other status in the product, in both themes.
125
+ - The badge sits **beside** its subject, not on top of it.
126
+
127
+ ### Don't use it — reach for this instead
128
+
129
+ | Situation | Use instead |
130
+ |---|---|
131
+ | The chip is a filter/tab the user clicks | `ScSelectionPill` (+ `ScSelectionPillGroup`) |
132
+ | A node in a taxonomy/hierarchy tree with counts and expand/collapse | `ScTaxonomyPill` |
133
+ | Specifically an Admin / Member role marker | `ScRole` / `ScRoleMobile` |
134
+ | A bare coloured status dot, no text | `ScBeacon` |
135
+ | A removable input token with an ✕ | no DS component — build it app-local; this badge has no close affordance |
136
+ | A progress/quantity meter | `ScProgressBar` |
137
+ | A call to action | `ScButton` |
138
+ | A validation message under a field | `ScTextField`'s helper/error text |
139
+ | A dismissible page-level alert | `CreditWarningBanner` (exported without the `Sc` prefix), or your app's banner |
140
+
141
+ ### Don't confuse with
142
+
143
+ | You may actually want | Not this |
144
+ |---|---|
145
+ | `ScBeacon` — a 20px two-ring status **dot**, 4 tones (no `info`), `aria-hidden` | `ScBadges` is the text chip; the two are often used together |
146
+ | `ScSelectionPill` — a real `<button aria-pressed>` for segmented selection | `ScBadges` is a non-interactive `<div>` |
147
+ | `ScTaxonomyPill` — tree node with `pill`/`expand`/`collapse` types | Visually similar, structurally different |
148
+ | `ScRole` — dedicated `admin`/`member` badge | Use it instead of hand-mapping roles onto variants |
149
+
150
+ ---
151
+
152
+ ## 4. Why to use it
153
+
154
+ - **The semantic pairs are already correct in both themes.** Each `opaque` variant
155
+ pairs a `--alias-border-*plus` (a very dark tinted border) with the matching
156
+ `--alias-text-and-icons-*`, and each `solid` variant pairs a `*-solidactive` fill
157
+ with `fullwhite` text. Hand-rolled chips are the most common place a status colour
158
+ becomes unreadable when light mode collapses the surfaces.
159
+ - **`fullwhite`, not `primary`, on solid fills.** Solid success/warning/error force
160
+ `--alias-text-and-icons-fullwhite` — deliberately *not* the theme-flipping primary
161
+ token, because the fill stays saturated in both themes. Getting that wrong is the
162
+ classic light-mode "white text on light green" bug.
163
+ - **One vocabulary of five statuses.** Every status in the product resolves to the
164
+ same five colours, so users learn the palette once.
165
+ - **It's tiny and it composes.** `flex-shrink: 0` and centred content mean it sits
166
+ cleanly in a table cell, a card header or a flex row without pushing anything
167
+ around — and five DS cards already embed one.
168
+
169
+ ---
170
+
171
+ ## Gotchas
172
+
173
+ **1. `...props` is collected and never spread.** Look at the source: the rest
174
+ element exists but the returned `<div>` receives only `className`. So there is *no*
175
+ way to attach a handler, an inline style, a tooltip or a test id — and because the
176
+ interface doesn't extend `HTMLAttributes`, TypeScript rejects them anyway.
177
+
178
+ ```tsx
179
+ // WRONG — type error, and nothing happens at runtime either
180
+ <ScBadges text="Revoked" variant="error" onClick={undo} title="Revoked 2 days ago" />
181
+
182
+ // RIGHT — wrap it
183
+ <span title="Revoked 2 days ago" onClick={undo} role="button" tabIndex={0}>
184
+ <ScBadges text="Revoked" variant="error" />
185
+ </span>
186
+ ```
187
+
188
+ **2. `align-self: stretch` on the root.** This is the surprise that produces
189
+ "why is my badge tall?". In a flex **row** it stretches to the tallest sibling; in a
190
+ flex **column** it stretches to full width.
191
+
192
+ ```tsx
193
+ // WRONG — badge grows to the height of the two-line name block beside it
194
+ <div style={{ display: "flex" }}>
195
+ <div><b>{name}</b><br />{email}</div>
196
+ <ScBadges text="Admin" />
197
+ </div>
198
+
199
+ // RIGHT — pin it, or give the parent alignItems
200
+ <div style={{ display: "flex", alignItems: "center" }}>…</div>
201
+ ```
202
+
203
+ **3. `text` defaults to `"Badge"`.** Forget the prop and you ship the placeholder.
204
+
205
+ **4. Solid `info` keeps primary text.** Solid success/warning/error force
206
+ `fullwhite`; `info` does not — it renders `--alias-text-and-icons-primary` on a
207
+ `--alias-fill-info-solid` blue, which in light mode is dark-on-blue. Prefer
208
+ `styleVariant="opaque"` for `info`.
209
+
210
+ **5. `className` is concatenated unconditionally.** Omit it and the class attribute
211
+ contains the literal string `undefined`. Harmless in the browser, breaks
212
+ exact-match snapshots.
213
+
214
+ **6. `opaque` has `background: unset`.** The chip is transparent, so it inherits
215
+ whatever is behind it — over a busy or coloured surface the text can lose contrast.
216
+ `solid` is the safer choice on non-flat backgrounds.
217
+
218
+ **7. No `size`, no `icon`, no `onClose`.** Text is always 12px/1.125rem, padding
219
+ always `4px 8px`, radius always `--radius-md`. A leading dot must be a separate
220
+ `ScBeacon` next to it; a removable token is not this component.
221
+
222
+ **8. `text` is `string`, not `ReactNode`.** You cannot put a `<b>` or an icon
223
+ inside. Interpolate the string.
224
+
225
+ **9. Five DS cards already render one.** `ScPlanDetailsCard`,
226
+ `ScPlanDetailsCardMobile`, `ScWorkspaceCard`, `ScProfileOptions` and
227
+ `ScReferralTableList` embed a badge internally. Pass their badge/status prop instead
228
+ of nesting a second one.
229
+
230
+ ---
231
+
232
+ ## In the wild
233
+
234
+ ```tsx
235
+ // photogenix_v2 dashboard/client/src/components/studio/StatusBadge.tsx:95
236
+ <ScBadges
237
+ text={label}
238
+ variant={getStatusVariant(status)}
239
+ styleVariant="opaque"
240
+ className={className}
241
+ />
242
+ ```
243
+
244
+ ```tsx
245
+ // artifax packages/shared/src/components/DashboardSidebar.tsx:901
246
+ <ScBadges
247
+ text={creditsLabel}
248
+ variant={typeof credits === 'number' && credits > 0 ? 'success' : 'default'}
249
+ styleVariant="opaque"
250
+ />
251
+ ```
252
+
253
+ ---
254
+
255
+ ## Related
256
+
257
+ - `ScBeacon` — the dot-only status marker; pair it with a badge or use it alone in dense tables.
258
+ - `ScRole` / `ScRoleMobile` — the purpose-built Admin/Member badge.
259
+ - `ScSelectionPill` / `ScTaxonomyPill` — the *interactive* chip vocabulary.
260
+ - `ScProgressBar` — when the status is really a quantity.
261
+ - `ScPlanDetailsCard` / `ScWorkspaceCard` / `ScReferralTableList` — DS parents that already render one.