@streamoid/ui 0.6.16 → 0.6.18
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +35 -18
- package/dist/docs/AGENTS.md +321 -0
- package/dist/docs/CreditWarningBanner.md +305 -0
- package/dist/docs/InvoiceHistoryMobile.md +222 -0
- package/dist/docs/ScAccess.md +259 -0
- package/dist/docs/ScAppCard.md +244 -0
- package/dist/docs/ScAppCardForCopilot.md +230 -0
- package/dist/docs/ScAppCardV3.md +273 -0
- package/dist/docs/ScAppField.md +308 -0
- package/dist/docs/ScAppListingCard.md +271 -0
- package/dist/docs/ScAppSwitchPanel.md +286 -0
- package/dist/docs/ScAppcardLogos.md +226 -0
- package/dist/docs/ScArtifaxInvite.md +262 -0
- package/dist/docs/ScArtifaxSidebar.md +330 -0
- package/dist/docs/ScAskAgentButton.md +307 -0
- package/dist/docs/ScBadges.md +261 -0
- package/dist/docs/ScBeacon.md +244 -0
- package/dist/docs/ScBillingHistoryHeader.md +210 -0
- package/dist/docs/ScBillingHistoryTableList.md +243 -0
- package/dist/docs/ScBillingLogsTableHeader.md +212 -0
- package/dist/docs/ScBillingLogsTableList.md +251 -0
- package/dist/docs/ScBriefCard.md +255 -0
- package/dist/docs/ScButton.md +251 -0
- package/dist/docs/ScCalendar.md +268 -0
- package/dist/docs/ScCalendarDateComps.md +264 -0
- package/dist/docs/ScCatalogixInvite.md +345 -0
- package/dist/docs/ScCatalogixSidebar.md +337 -0
- package/dist/docs/ScCatalogixStoreHeader.md +246 -0
- package/dist/docs/ScCatalogixStoreTableList.md +316 -0
- package/dist/docs/ScCheckField.md +233 -0
- package/dist/docs/ScCheckbox.md +272 -0
- package/dist/docs/ScCounter.md +235 -0
- package/dist/docs/ScCreditsUsageCard.md +247 -0
- package/dist/docs/ScCreditsUsageCardMobile.md +224 -0
- package/dist/docs/ScDefaultCard.md +269 -0
- package/dist/docs/ScDp.md +245 -0
- package/dist/docs/ScDrawer.md +318 -0
- package/dist/docs/ScFieldButton.md +255 -0
- package/dist/docs/ScFileField.md +268 -0
- package/dist/docs/ScGoogleSignIn.md +250 -0
- package/dist/docs/ScGuide.md +278 -0
- package/dist/docs/ScHDivider.md +213 -0
- package/dist/docs/ScHeader.md +222 -0
- package/dist/docs/ScImageField.md +253 -0
- package/dist/docs/ScInChatList.md +277 -0
- package/dist/docs/ScInChatMessage.md +205 -0
- package/dist/docs/ScInfoPopup.md +248 -0
- package/dist/docs/ScIntialProfileCover.md +233 -0
- package/dist/docs/ScInvoiceHistoryMobile.md +187 -0
- package/dist/docs/ScLogoUnit.md +232 -0
- package/dist/docs/ScMappingCard.md +241 -0
- package/dist/docs/ScMediaApproval.md +301 -0
- package/dist/docs/ScMediaSelect.md +310 -0
- package/dist/docs/ScMenuOptions.md +308 -0
- package/dist/docs/ScMobileBottomAction.md +252 -0
- package/dist/docs/ScMobileTopNav.md +279 -0
- package/dist/docs/ScModal.md +291 -0
- package/dist/docs/ScOnlyField.md +302 -0
- package/dist/docs/ScOnlyIcon.md +213 -0
- package/dist/docs/ScPagination.md +284 -0
- package/dist/docs/ScPairtext.md +287 -0
- package/dist/docs/ScPendingAction.md +238 -0
- package/dist/docs/ScPhtogenixInvite.md +275 -0
- package/dist/docs/ScPlanCard.md +302 -0
- package/dist/docs/ScPlanComparison.md +264 -0
- package/dist/docs/ScPlanDetailsCard.md +246 -0
- package/dist/docs/ScPlanDetailsCardMobile.md +240 -0
- package/dist/docs/ScPopUpMenu.md +224 -0
- package/dist/docs/ScProfile.md +234 -0
- package/dist/docs/ScProfileImageUpdate.md +261 -0
- package/dist/docs/ScProfileOptions.md +245 -0
- package/dist/docs/ScProfilePopup.md +396 -0
- package/dist/docs/ScProfileSettingsComp.md +250 -0
- package/dist/docs/ScProfileV2Mobile.md +216 -0
- package/dist/docs/ScProgressBar.md +267 -0
- package/dist/docs/ScQuickPrompt.md +277 -0
- package/dist/docs/ScRadio.md +228 -0
- package/dist/docs/ScReferralCardMobile.md +226 -0
- package/dist/docs/ScReferralTableHeader.md +260 -0
- package/dist/docs/ScReferralTableList.md +293 -0
- package/dist/docs/ScRole.md +226 -0
- package/dist/docs/ScRoleMobile.md +199 -0
- package/dist/docs/ScSelect.md +270 -0
- package/dist/docs/ScSelection.md +256 -0
- package/dist/docs/ScSelectionList.md +272 -0
- package/dist/docs/ScSelectionPill.md +240 -0
- package/dist/docs/ScSelectionPillGroup.md +302 -0
- package/dist/docs/ScSettingsNav.md +212 -0
- package/dist/docs/ScSettingsTabComp.md +260 -0
- package/dist/docs/ScSideBarLogoUnit.md +340 -0
- package/dist/docs/ScSidebar.md +243 -0
- package/dist/docs/ScSidebarIcons.md +232 -0
- package/dist/docs/ScSidebarMenu.md +283 -0
- package/dist/docs/ScSidebarProfile.md +231 -0
- package/dist/docs/ScSidebarSwitchMenu.md +258 -0
- package/dist/docs/ScSlider.md +194 -0
- package/dist/docs/ScStoreCard.md +252 -0
- package/dist/docs/ScStrLogo.md +253 -0
- package/dist/docs/ScStreamoidWordmark.md +302 -0
- package/dist/docs/ScSubAgent.md +226 -0
- package/dist/docs/ScTabComp.md +308 -0
- package/dist/docs/ScTabField.md +258 -0
- package/dist/docs/ScTabSwitcher.md +307 -0
- package/dist/docs/ScTableHeader.md +261 -0
- package/dist/docs/ScTableList.md +301 -0
- package/dist/docs/ScTableListMobile.md +282 -0
- package/dist/docs/ScTabs.md +268 -0
- package/dist/docs/ScTaxonomyPill.md +263 -0
- package/dist/docs/ScTextArea.md +259 -0
- package/dist/docs/ScTextField.md +324 -0
- package/dist/docs/ScThinkingStepIcon.md +249 -0
- package/dist/docs/ScTodoList.md +288 -0
- package/dist/docs/ScToggleSwitch.md +229 -0
- package/dist/docs/ScUsageHistoryMobile.md +194 -0
- package/dist/docs/ScVDivider.md +215 -0
- package/dist/docs/ScValueMappingL1.md +256 -0
- package/dist/docs/ScVersion.md +251 -0
- package/dist/docs/ScWorkspace.md +233 -0
- package/dist/docs/ScWorkspaceCard.md +234 -0
- package/dist/docs/ScWorkspaceSettingsMobile.md +265 -0
- package/dist/docs/ScWorkspaceSwitchCard.md +314 -0
- package/dist/docs/ScWorkspaceSwitchMobile.md +241 -0
- package/dist/docs/ScWorkspaceSwitchMobileV2.md +278 -0
- package/dist/docs/StreamoidSidebar.md +403 -0
- package/dist/docs/StreamoidWorkspaceSwitcher.md +307 -0
- package/dist/docs/UsageHistoryMobile.md +235 -0
- package/dist/docs/components.json +4849 -0
- package/dist/index.css +43 -37
- package/dist/index.d.mts +10 -0
- package/dist/index.d.ts +10 -0
- package/package.json +3 -2
|
@@ -0,0 +1,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.
|