@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,255 @@
1
+ ---
2
+ component: ScBriefCard
3
+ package: "@streamoid/ui"
4
+ category: chat-agent
5
+ status: stable
6
+ renders: div
7
+ tags: [card, brief, quick-prompt, suggestion, agent, copilot, chat, gradient-border, glow, active]
8
+ related: [ScAppCardV3, ScQuickPrompt, ScInChatMessage, ScSelection, ScAppCardForCopilot]
9
+ do_not_confuse_with: [ScQuickPrompt, ScAppCardV3, ScDefaultCard, ScAppCardForCopilot]
10
+ used_by: [agent]
11
+ ---
12
+
13
+ # ScBriefCard
14
+
15
+ **A single suggested-prompt card in the agent's empty chat state.** One centred
16
+ paragraph of text, nothing else — wrapped in the same 1px gradient-border shell as
17
+ `ScAppCardV3`, with the coral corner glow blooming on hover.
18
+
19
+ **This is a chat/agent-runtime component** (`stream-agent` / `@streamoid/agent`), not
20
+ a host-dashboard one. Its absence from CXO, Photogenix, Catalogix and Artifax is
21
+ expected, not a bug.
22
+
23
+ ## TL;DR for agents
24
+
25
+ - **Reach for it when:** you are building the agent's suggested-prompt / "brief"
26
+ grid and each card is a single sentence the user can click to send.
27
+ - **Don't reach for it when:** you want the DS's older three-line prompt chip with an
28
+ app name and a bolt icon (→ `ScQuickPrompt`, which is what **CXO's** landing page
29
+ uses), a product tile with a name and icon (→ `ScAppCardV3`), or a titled option
30
+ card in a dashboard (→ `ScDefaultCard`).
31
+ - **Four things that will bite you:**
32
+ 1. **There is only `description`** — no title, no icon, no author, no label. The
33
+ card is one paragraph.
34
+ 2. `description` defaults to `"Centralized product and asset management for every
35
+ storefront"` — copy borrowed from the app cards, which makes no sense as a brief.
36
+ 3. `active` sets `--alias-surface-raised`, which is **`#ffffff` in light mode, the
37
+ same as the default surface** — the active state is invisible in light mode.
38
+ 4. No `role`/`tabIndex`/key handling, but `cursor: pointer` is baked in. The agent's
39
+ own `QuickPrompt` wrapper adds `role="button" tabIndex={0} aria-label onKeyDown`;
40
+ copy that.
41
+
42
+ ---
43
+
44
+ ## 1. How to use it
45
+
46
+ ### Import
47
+
48
+ ```tsx
49
+ import { ScBriefCard } from "@streamoid/ui";
50
+ import "@streamoid/ui/dist/index.css"; // once, at your app root
51
+ ```
52
+
53
+ ### Minimal usage
54
+
55
+ ```tsx
56
+ <ScBriefCard description="Summarise last week's sell-through by category." />
57
+ ```
58
+
59
+ ### Props
60
+
61
+ | Prop | Type | Default | Notes |
62
+ |---|---|---|---|
63
+ | `description` | `string` | `"Centralized product and asset management for every storefront"` | ⚠️ Real default, and it is app-card copy — always set it. `text-xs-regular`, **primary** colour (not tertiary — brighter than `ScAppCardV3`'s description), centred, `word-break: break-word`, no clamp. |
64
+ | `active` | `boolean` | `false` | Swaps the inner background to `--alias-surface-raised`. See Gotcha 3 — a no-op in light mode. |
65
+ | `className` | `string` | – | Applied to the **outer** `.cardBorder` wrapper, not the inner card. |
66
+ | `style` | `CSSProperties` | – | Applied to the outer wrapper. Where you set `width` / `height`. |
67
+ | `...props` | `HTMLAttributes<HTMLDivElement>` | – | Spread onto the outer wrapper (`onClick`, `role`, `tabIndex`, `aria-label`, `onKeyDown`, `data-*`). |
68
+
69
+ ### What the DOM looks like
70
+
71
+ ```
72
+ div.cardBorder ← className, style, {...props}; the 1px gradient "border"
73
+ └─ div.scBriefCard ← surface, 20px padding, overflow:clip, height:100%, centred
74
+ ├─ div.glow ← decorative, pointer-events:none
75
+ ├─ div.glowHover ← coral radial bloom, opacity 0 → 1 on wrapper :hover
76
+ └─ div.body → p.description
77
+ ```
78
+
79
+ Identical shell to `ScAppCardV3`; only the body differs (centred single paragraph,
80
+ 20px padding instead of 16px).
81
+
82
+ ### Recipes
83
+
84
+ ```tsx
85
+ // The agent idiom (stream-agent's QuickPrompt wrapper) — accessible + focus ring
86
+ <ScBriefCard
87
+ role="button"
88
+ tabIndex={0}
89
+ aria-label={title}
90
+ description={description || title}
91
+ className="h-full cursor-pointer outline-none focus-visible:ring-2 focus-visible:ring-[var(--alias-border-focus)]"
92
+ onClick={() => onClick?.(promptValue)}
93
+ onKeyDown={(e) => {
94
+ if (e.key === "Enter" || e.key === " ") { e.preventDefault(); onClick?.(promptValue); }
95
+ }}
96
+ />
97
+
98
+ // An equal-height grid of briefs
99
+ <div style={{ display: "grid", gridTemplateColumns: "repeat(auto-fit, minmax(180px, 1fr))", gap: 12 }}>
100
+ {briefs.map((b) => (
101
+ <ScBriefCard key={b.id} description={b.text} style={{ height: "100%" }}
102
+ role="button" tabIndex={0} onClick={() => send(b.text)} />
103
+ ))}
104
+ </div>
105
+
106
+ // Marking the brief that's currently running — add a light-mode-safe cue too
107
+ <ScBriefCard description={b.text} active={b.id === runningId} aria-current={b.id === runningId ? "true" : undefined} />
108
+ ```
109
+
110
+ ---
111
+
112
+ ## 2. Where to use it
113
+
114
+ - **The agent chat's empty state / suggested-prompt grid.** `stream-agent`'s local
115
+ `QuickPrompt` component is a thin wrapper around this card: it takes
116
+ `title`/`value`/`description`, falls back to `description || title`, and adds the
117
+ button semantics and focus ring.
118
+ - Any **agent surface offering short one-line actions** where the text *is* the whole
119
+ card.
120
+
121
+ Note what the wrapper reveals: `QuickPrompt`'s `icon` prop is accepted purely for
122
+ call-site compatibility and thrown away, because **the Figma brief card has no
123
+ icon**. Don't try to add one.
124
+
125
+ ---
126
+
127
+ ## 3. When to use it
128
+
129
+ ### Use it when
130
+
131
+ - The card content is **one sentence**, and clicking it sends that sentence.
132
+ - You are inside the agent/copilot runtime and want the coral gradient-border
133
+ language shared with `ScAppCardV3`.
134
+ - Cards sit in an equal-height grid where the shell's `height: 100%` helps.
135
+
136
+ ### Don't use it — reach for this instead
137
+
138
+ | Situation | Use instead |
139
+ |---|---|
140
+ | The DS prompt chip with app name + bolt icon + heading + description (CXO home) | `ScQuickPrompt` |
141
+ | A product tile with a name and an icon pill | `ScAppCardV3` |
142
+ | A dark glassy product **wordmark** card on the copilot surface | `ScAppCardForCopilot` |
143
+ | A titled "choose an option" card in a dashboard | `ScDefaultCard` |
144
+ | An in-chat message bubble | `ScInChatMessage` (inside `ScInChatList`) |
145
+ | An in-chat radio row with title + description | `ScSelection` / `ScSelectionList` |
146
+ | A pending action awaiting confirmation | `ScPendingAction` |
147
+ | A segmented filter row in a dashboard | `ScSelectionPill` / `ScSelectionPillGroup` |
148
+
149
+ ### Don't confuse with
150
+
151
+ | You may actually want | Not this |
152
+ |---|---|
153
+ | `ScQuickPrompt` — `appName` + `heading` + `description` + a `SiconBolt` chip; **rendered by CXO's landing page** | `ScBriefCard` is description-only and lives in the agent runtime |
154
+ | `ScAppCardV3` — same shell, but `appName` + `description` + icon pill, description in *tertiary* | This card is one *primary*-coloured paragraph, centred |
155
+ | `ScDefaultCard` — plain subtle border, centred title + hint, `state="hover"` prop | No gradient border, no glow, no `active` |
156
+ | `ScAppCardForCopilot` — real `<button>`, fixed dark, wordmark | This is a `div` and follows the theme |
157
+
158
+ The naming trap: **the agent's `QuickPrompt` renders `ScBriefCard`, while the DS's
159
+ `ScQuickPrompt` is a different, older card that CXO uses.** Two components, similar
160
+ names, opposite surfaces.
161
+
162
+ ---
163
+
164
+ ## 4. Why to use it
165
+
166
+ - **Shell parity with `ScAppCardV3` for free.** The gradient border (1px-padded
167
+ wrapper + `calc(radius - 1px)` inner card) and the two-layer glow are the same CSS,
168
+ so briefs and app cards read as one family. Fixing one shell fixes both.
169
+ - **The glow never eats clicks.** Both glow layers are `pointer-events: none`, and
170
+ `overflow: clip` keeps them inside the rounded corners.
171
+ - **Primary-coloured body text.** A brief is the *content*, not a caption, so the
172
+ paragraph uses `--alias-text-and-icons-primary` — the reason this card is not just
173
+ `ScAppCardV3` with the name removed.
174
+ - **`word-break: break-word` + no clamp** means a long prompt grows the card instead
175
+ of clipping, which is what you want when the text is the clickable payload.
176
+ - **`height: 100%`** on the inner card keeps a grid of briefs even without measuring.
177
+
178
+ ---
179
+
180
+ ## Gotchas
181
+
182
+ **1. The default copy is wrong for this card.** `description` defaults to the app-card
183
+ sentence about storefront asset management.
184
+
185
+ ```tsx
186
+ // WRONG — renders "Centralized product and asset management for every storefront"
187
+ <ScBriefCard onClick={send} />
188
+
189
+ // RIGHT
190
+ <ScBriefCard description="Show me the top 10 SKUs by margin this month." onClick={send} />
191
+ ```
192
+
193
+ **2. There is no title and no icon.** Only `description`. If your data has a short
194
+ title and a longer body, pick one — the agent wrapper resolves this as
195
+ `description || title`.
196
+
197
+ **3. `active` is invisible in light mode.** `--alias-surface-raised` and
198
+ `--alias-surface-base` both resolve to `#ffffff` in the light theme. Pair `active`
199
+ with `aria-current` and a visual cue that survives (border, ring, badge).
200
+
201
+ **4. Not keyboard accessible on its own.** `cursor: pointer` is baked in but there is
202
+ no `role`, `tabIndex` or key handling — and no focus ring. Add all of them, as
203
+ `QuickPrompt` does, or keyboard users cannot trigger the prompt at all.
204
+
205
+ **5. `className`, `style` and `onClick` go to the wrapper, not the inner card.** A
206
+ class that tries to override `padding` or `background` will not apply to the padded
207
+ surface; target `> div`, or restyle via tokens.
208
+
209
+ **6. The accent colours are hardcoded.** The border gradient ends at `#EE5E3A`; the
210
+ glow is a stack of `rgba(200,60,20,…)` stops. Identical in dark and light, with no
211
+ brand override.
212
+
213
+ **7. `backdrop-filter: blur(4px)` on the inner card** creates a containing block for
214
+ `position: fixed` descendants. Portal any popover to `document.body`.
215
+
216
+ **8. Grid alignment needs the wrapper stretched.** `height: 100%` is on the inner card
217
+ only; in a flex row (or with `align-items: start`) pass `style={{ height: "100%" }}` —
218
+ or a `h-full` class, which is what the agent wrapper does.
219
+
220
+ ---
221
+
222
+ ## In the wild
223
+
224
+ ```tsx
225
+ // stream-agent frontend/src/components/chat/QuickPrompt.tsx:31
226
+ <ScBriefCard
227
+ role="button"
228
+ tabIndex={0}
229
+ aria-label={title}
230
+ description={cardDescription}
231
+ className={[
232
+ 'h-full cursor-pointer outline-none transition-transform',
233
+ 'focus-visible:ring-2 focus-visible:ring-[var(--alias-border-focus)]',
234
+ 'focus-visible:ring-offset-2 focus-visible:ring-offset-[var(--alias-surface-base)]',
235
+ className,
236
+ ].join(' ')}
237
+ onClick={() => onClick?.(promptValue)}
238
+ onKeyDown={handleKeyDown}
239
+ />
240
+ ```
241
+
242
+ _No host render site found — used by the agent runtime / composed internally._
243
+ That is correct and expected: this card belongs to the chat runtime. The nearest
244
+ host-dashboard equivalent is `ScQuickPrompt`, rendered by
245
+ `cxo-dashboard src/app/components/landing-content.tsx:380`.
246
+
247
+ ---
248
+
249
+ ## Related
250
+
251
+ - `ScQuickPrompt` — the DS's other prompt card (app name + bolt + heading); used by CXO's home.
252
+ - `ScAppCardV3` — the same gradient-border shell with a name row and icon pill.
253
+ - `ScAppCardForCopilot` — the other agent-surface card, wordmark-based and fixed dark.
254
+ - `ScInChatList` / `ScInChatMessage` — the transcript components these briefs sit above.
255
+ - `ScSelection` / `ScSelectionList` / `ScPendingAction` — the rest of the in-chat vocabulary.
@@ -0,0 +1,251 @@
1
+ ---
2
+ component: ScButton
3
+ package: "@streamoid/ui"
4
+ category: actions
5
+ status: stable
6
+ renders: div[role="button"]
7
+ tags: [button, cta, submit, action, click, icon-button, primary, danger]
8
+ related: [ScFieldButton, ScOnlyIcon, ScMobileBottomAction, ScAskAgentButton]
9
+ do_not_confuse_with: [ScFieldButton, ScOnlyIcon, ScPopUpMenu]
10
+ used_by: [cxo, photogenix, catalogix, artifax]
11
+ ---
12
+
13
+ # ScButton
14
+
15
+ **The one button primitive.** Every call-to-action across all four Streamoid apps
16
+ goes through this component — Photogenix alone renders it 373 times.
17
+
18
+ ## TL;DR for agents
19
+
20
+ - **Reach for it when:** you need any clickable action with a label, an icon, or both.
21
+ - **Don't reach for it when:** the action sits inline with a form field (→ `ScFieldButton`),
22
+ it's a bare icon affordance in a toolbar (→ `ScOnlyIcon`), or it's a mobile
23
+ screen's sticky footer action (→ `ScMobileBottomAction`).
24
+ - **Three things that will bite you:**
25
+ 1. It renders a `<div role="button">`, **not** a `<button>`. No native form submit.
26
+ 2. `type` is a **style** prop, not the HTML button type. `type="submit"` does nothing.
27
+ 3. There is **no `disabled` prop** — disable with `state="disabled"`.
28
+
29
+ ---
30
+
31
+ ## 1. How to use it
32
+
33
+ ### Import
34
+
35
+ ```tsx
36
+ import { ScButton } from "@streamoid/ui";
37
+ import "@streamoid/ui/dist/index.css"; // once, at your app root
38
+ ```
39
+
40
+ ### Minimal usage
41
+
42
+ ```tsx
43
+ <ScButton text="Save changes" variant="mono" size="md" onClick={handleSave} />
44
+ ```
45
+
46
+ ### Props
47
+
48
+ | Prop | Type | Default | Notes |
49
+ |---|---|---|---|
50
+ | `text` | `string` | `"Button"` | The label. **Ignored when `children` is passed.** |
51
+ | `children` | `ReactNode` | – | Overrides `text` **and suppresses the icon entirely**. |
52
+ | `icon` | `JSX.Element` | `<SiconHome />` | ⚠️ Has a real default. Only rendered when `styleVariant` is `icon-left` / `icon-right` / `icon-only`. |
53
+ | `styleVariant` | `"default"` \| `"icon-right"` \| `"icon-left"` \| `"icon-only"` | `"default"` | Whether and where the icon renders. `"default"` = label only. |
54
+ | `variant` | `"primary"` \| `"secondary"` \| `"tertiary"` \| `"outline"` \| `"error"` \| `"mono"` | `"primary"` | Colour role. `mono` = black/white, flips with the theme. `error` = destructive. |
55
+ | `type` | `"primary"` \| `"secondary"` \| `"tertiary"` | `"primary"` | A **second style axis** multiplied with `variant` (emphasis within the role). **Not** the HTML `type` attribute. |
56
+ | `size` | `"lg"` \| `"md"` \| `"sm"` | `"lg"` | Also drives the icon stroke width (1.25 for `sm`, 1.5 otherwise). |
57
+ | `state` | `"default"` \| `"hover"` \| `"disabled"` | `"default"` | `"disabled"` → `pointer-events: none`, `cursor: not-allowed`, `tabIndex={-1}`, `aria-disabled`. `"hover"` force-renders hover styling (Figma parity / screenshots). |
58
+ | `loading` | `boolean` | `false` | Swaps all content for a spinner. **Does not disable the button** — see Gotcha 5. |
59
+ | `className` | `string` | – | Appended after the internal classes, so it wins on equal specificity. |
60
+ | `...props` | `HTMLAttributes<HTMLDivElement>` | – | `onClick`, `style`, `aria-*`, `data-*` are spread onto the root div. |
61
+
62
+ ### Recipes
63
+
64
+ ```tsx
65
+ // Primary CTA, disabled until the form is valid (the standard host idiom)
66
+ <ScButton
67
+ text="Invite user"
68
+ variant="primary"
69
+ size="md"
70
+ state={isSubmitDisabled ? "disabled" : "default"}
71
+ onClick={handleInvite}
72
+ />
73
+
74
+ // Destructive action
75
+ <ScButton text="Remove user" variant="error" size="md" onClick={onRemove} />
76
+
77
+ // Async submit — spinner AND blocked input
78
+ <ScButton
79
+ text="Publishing…"
80
+ variant="mono"
81
+ loading={isSaving}
82
+ state={isSaving ? "disabled" : "default"}
83
+ onClick={handlePublish}
84
+ />
85
+
86
+ // Icon + label
87
+ <ScButton
88
+ text="Add store"
89
+ styleVariant="icon-left"
90
+ icon={<SiconPlus size={20} />}
91
+ variant="outline"
92
+ size="md"
93
+ />
94
+
95
+ // Icon only
96
+ <ScButton
97
+ styleVariant="icon-only"
98
+ icon={<SiconLogout size={24} />}
99
+ variant="error"
100
+ type="tertiary"
101
+ size="md"
102
+ aria-label="Log out"
103
+ />
104
+ ```
105
+
106
+ ---
107
+
108
+ ## 2. Where to use it
109
+
110
+ - **Modal and drawer footers** — the confirm/cancel pair (`ScModal`, `ScDrawer`).
111
+ - **Form submits** — at the end of `ScTextField` / `ScAppField` stacks.
112
+ - **Card CTAs** — inside `ScPlanCard`, `ScCreditsUsageCard`, `ScWorkspaceCard`
113
+ (several of these already render one internally; pass their `buttonText` /
114
+ `onUpgrade` props rather than nesting your own).
115
+ - **Sidebar footers and banners** — `ScCreditWarningBanner`'s "Buy credits".
116
+ - **Page headers** — the primary action beside a `ScHeader`.
117
+
118
+ All four host apps use it. CXO wraps it in a local `app/components/Button` shim
119
+ that maps legacy names (`primary`→`mono`, `danger`→`error`, `transparent`→`outline`)
120
+ so old call sites keep working.
121
+
122
+ ---
123
+
124
+ ## 3. When to use it
125
+
126
+ ### Use it when
127
+
128
+ - The element performs an **action** (mutates state, submits, opens, deletes).
129
+ - You want the label, icon sizing, spinner, focus ring, and theme behaviour to
130
+ match every other button in the product without thinking about it.
131
+
132
+ ### Don't use it — reach for this instead
133
+
134
+ | Situation | Use instead |
135
+ |---|---|
136
+ | Action sitting inline with a form field ("Browse", "Apply") | `ScFieldButton` — matches field height and border |
137
+ | Bare square icon affordance in a toolbar | `ScOnlyIcon` |
138
+ | Mobile screen's sticky bottom action bar (1 or 2 buttons) | `ScMobileBottomAction` |
139
+ | Navigation to another route/URL | a real `<a>` / your router's `<Link>` — this is a `div`, so it gets no link semantics, no middle-click, no "open in new tab" |
140
+ | Opening a menu of choices | `ScPopUpMenu` + `ScMenuOptions` |
141
+ | The gradient "Ask CXO" assistant CTA in a sidebar | `ScAskAgentButton` / `ScAskAgentSlot` |
142
+ | A row in a segmented/tabbed control | `ScSelectionPill` / `ScTabs` / `ScTabSwitcher` |
143
+
144
+ ### Don't confuse with
145
+
146
+ | You may actually want | Not this |
147
+ |---|---|
148
+ | HTML `type="submit"` behaviour | `type` here is a style axis. Wire `onClick` instead. |
149
+ | `variant="primary"` in Catalogix | The orange gradient is **retired** there — use `mono` or `outline`. |
150
+
151
+ ---
152
+
153
+ ## 4. Why to use it
154
+
155
+ - **Theme correctness for free.** Styled entirely off `--alias-*` tokens, so it
156
+ flips between dark and light without a single conditional in your code. Hand-rolled
157
+ buttons are the most common source of light-mode contrast bugs.
158
+ - **Icon normalisation.** Your icon is cloned with `color="currentColor"` and a
159
+ size-matched `strokeWidth`, so icon and label always share a colour and optical weight.
160
+ - **Keyboard access already handled.** Enter and Space invoke `onClick`; `tabIndex`
161
+ and `aria-disabled` track `state`. A hand-rolled `<div onClick>` gives you none of that.
162
+ - **Figma parity.** `variant` × `type` × `size` × `state` maps 1:1 onto the design
163
+ library, so a spec can be implemented by reading prop names off the design.
164
+ - **One place to change.** A tweak to the button shape ships to 400+ call sites at once.
165
+
166
+ ---
167
+
168
+ ## Gotchas
169
+
170
+ **1. It is not a native `<button>`.** It renders `<div role="button" tabIndex={0}>`.
171
+ It will never submit a form.
172
+
173
+ ```tsx
174
+ // WRONG — no form submission happens
175
+ <form onSubmit={save}><ScButton text="Save" type="submit" /></form>
176
+
177
+ // RIGHT
178
+ <form onSubmit={save}><ScButton text="Save" onClick={save} /></form>
179
+ ```
180
+
181
+ **2. `type` is not the HTML type.** `type="primary" | "secondary" | "tertiary"` is a
182
+ style axis that combines with `variant`. Passing `"submit"` is a type error and has
183
+ no runtime effect.
184
+
185
+ **3. `icon` defaults to a home icon.** If you set an icon `styleVariant` and forget
186
+ `icon`, you ship a house.
187
+
188
+ ```tsx
189
+ // WRONG — renders SiconHome
190
+ <ScButton text="Delete" styleVariant="icon-left" />
191
+
192
+ // RIGHT
193
+ <ScButton text="Delete" styleVariant="icon-left" icon={<SiconDelete size={20} />} />
194
+ ```
195
+
196
+ **4. `children` silently drops the icon.** The icon only renders on the `text` path.
197
+
198
+ ```tsx
199
+ // WRONG — icon never appears
200
+ <ScButton styleVariant="icon-left" icon={<SiconPlus />}>Add</ScButton>
201
+
202
+ // RIGHT
203
+ <ScButton styleVariant="icon-left" icon={<SiconPlus />} text="Add" />
204
+ ```
205
+
206
+ **5. `loading` does not disable.** The spinner shows but clicks still fire — a
207
+ double-submit waiting to happen. Pair it with `state="disabled"`.
208
+
209
+ **6. Your icon's `color` prop is overwritten.** The component clones the icon with
210
+ `color="currentColor"` so it inherits the button's text colour. Setting a colour on
211
+ the icon is dead code — control it via `variant` instead.
212
+
213
+ ```tsx
214
+ // POINTLESS — color is overwritten by cloneElement
215
+ <ScButton styleVariant="icon-only" icon={<SiconLogout color="var(--alias-text---icons-error)" />} />
216
+
217
+ // RIGHT — the variant colours both label and icon
218
+ <ScButton styleVariant="icon-only" icon={<SiconLogout />} variant="error" type="tertiary" />
219
+ ```
220
+
221
+ **7. `variant="primary"` + `type="primary"` is non-deterministic.** That combination
222
+ generates a canvas noise texture at runtime using `Math.random()`, and needs a DOM.
223
+ Avoid it in snapshot tests, and expect nothing painted during SSR.
224
+
225
+ **8. `state="hover"` is for design parity, not interaction.** It force-renders the
226
+ hover skin. Never wire it to your own mouse handlers — real `:hover` already works.
227
+
228
+ ---
229
+
230
+ ## In the wild
231
+
232
+ ```tsx
233
+ // cxo-dashboard src/app/components/invite-update-modal.tsx:927
234
+ <ScButton
235
+ text="Remove user"
236
+ variant="error"
237
+ size="md"
238
+ state={canRemove ? "default" : "disabled"}
239
+ onClick={canRemove ? onRemove : undefined}
240
+ />
241
+ ```
242
+
243
+ ---
244
+
245
+ ## Related
246
+
247
+ - `ScFieldButton` — field-height sibling for form-adjacent actions.
248
+ - `ScOnlyIcon` — icon-only affordance without button chrome.
249
+ - `ScMobileBottomAction` — mobile sticky footer, composes 1–2 buttons.
250
+ - `ScAskAgentButton` / `ScAskAgentSlot` — the gradient assistant CTA.
251
+ - `@streamoid/icons` — every `Sicon*`; check `packages/icons/ICONS.md` before drawing a new one.