@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,277 @@
1
+ ---
2
+ component: ScQuickPrompt
3
+ package: "@streamoid/ui"
4
+ category: chat-agent
5
+ status: stable
6
+ renders: div
7
+ tags: [chat, prompt, suggestion, starter, empty-state, copilot, card, bolt]
8
+ related: [ScAppCardForCopilot, ScBriefCard, ScDefaultCard, ScAskAgentButton, ScInChatMessage]
9
+ do_not_confuse_with: [ScBriefCard, ScDefaultCard, ScAppCardForCopilot, ScAppListingCard]
10
+ used_by: [cxo, photogenix]
11
+ ---
12
+
13
+ # ScQuickPrompt
14
+
15
+ **A suggested-prompt card for an empty chat.** A bolt tile and a pill with the target
16
+ app's name across the top, then a medium heading and a muted description — the thing
17
+ a user clicks to seed the conversation. Bordered card on the canvas surface.
18
+
19
+ ## TL;DR for agents
20
+
21
+ - **Reach for it when:** you're building the chat/copilot empty state and want a row
22
+ or grid of 2–4 clickable starter prompts.
23
+ - **Don't reach for it when:** you want an app tile in the copilot app switcher
24
+ (→ `ScAppCardForCopilot`), a saved-brief card (→ `ScBriefCard`), a generic titled
25
+ option card (→ `ScDefaultCard`), or the gradient "Ask CXO" CTA
26
+ (→ `ScAskAgentButton`).
27
+ - **Three things that will bite you:**
28
+ 1. It has a **hardcoded `width: 15.333125rem`** (245px). In a flex row or grid you
29
+ must override it (`!w-auto min-w-0 flex-1`) or every card stays 245px wide.
30
+ 2. Every prop has a **real Photogenix default** — omit them and you ship
31
+ "Photogenix / Swap Model and Background / Replace the current model and…".
32
+ 3. It is **not a button**: a plain `div`, no `cursor: pointer`, no `role`, no
33
+ `tabIndex`, no keyboard. `onClick` only works because `HTMLAttributes` are spread.
34
+
35
+ ---
36
+
37
+ ## 1. How to use it
38
+
39
+ ### Import
40
+
41
+ ```tsx
42
+ import { ScQuickPrompt } from "@streamoid/ui";
43
+ import "@streamoid/ui/dist/index.css"; // once, at your app root
44
+ ```
45
+
46
+ ### Minimal usage
47
+
48
+ ```tsx
49
+ <ScQuickPrompt
50
+ appName="Photogenix"
51
+ heading="Swap Background"
52
+ description="Change the background of this image to a minimalist studio"
53
+ onClick={() => sendPrompt("Change the background of this image to a minimalist studio")}
54
+ style={{ cursor: "pointer" }}
55
+ />
56
+ ```
57
+
58
+ ### Props
59
+
60
+ | Prop | Type | Default | Notes |
61
+ |---|---|---|---|
62
+ | `appName` | `string` | `"Photogenix"` | ⚠️ Real default. Rendered in the top-right outlined pill, `text-tertiary` 12/18. A trailing space is appended by the component. |
63
+ | `heading` | `string` | `"Swap Model and Background"` | ⚠️ Real default. `text-secondary`, 16/24 medium. Wraps freely — no truncation. |
64
+ | `description` | `string` | `"Replace the current model and background with new ones that match my brand's aesthetic and tone."` | ⚠️ Real default. `text-muted`, 14/20. No line clamp — long strings make the card taller. |
65
+ | `className` | `string` | – | Appended after the internal class. **Always concatenated**, so omitting it puts a literal `undefined` class on the root (harmless, but visible in the DOM). |
66
+ | `...props` | `HTMLAttributes<HTMLDivElement>` | – | `onClick`, `style`, `role`, `tabIndex`, `onKeyDown`, `data-*`, `aria-*` spread onto the root. **This is the only way to make it interactive.** |
67
+
68
+ There is **no `icon` prop** — the tile always renders `SiconBolt`. There is no
69
+ `prompt`/`value` prop either: the text you send is your own concern, and is usually
70
+ not the same string as `description`.
71
+
72
+ ### Recipes
73
+
74
+ ```tsx
75
+ // A responsive grid of starters (Photogenix's copilot empty state)
76
+ const suggestions = [
77
+ { heading: "Swap Background", description: "Change the background of this image to a minimalist studio", prompt: "Change the background of this image to a minimalist studio" },
78
+ { heading: "Generate Video", description: "Create a cinematic product video with pan-up motion", prompt: "Create a cinematic product video with pan-up motion" },
79
+ ];
80
+
81
+ <div className="grid grid-cols-1 md:grid-cols-2 gap-3 w-full">
82
+ {suggestions.map((item, i) => (
83
+ <ScQuickPrompt
84
+ key={i}
85
+ appName="Photogenix"
86
+ heading={item.heading}
87
+ description={item.description}
88
+ onClick={() => onSelect(item.prompt)}
89
+ style={{ cursor: "pointer" }}
90
+ />
91
+ ))}
92
+ </div>
93
+
94
+ // An equal-width flex row — the fixed 245px width MUST be overridden
95
+ <div style={{ display: "flex", gap: "var(--spacing-8xl)", width: "100%" }}>
96
+ {prompts.map((p) => (
97
+ <ScQuickPrompt key={p.id} {...p} className="flex-1 !w-auto min-w-0" />
98
+ ))}
99
+ </div>
100
+
101
+ // Keyboard-operable (the component gives you none of this)
102
+ <ScQuickPrompt
103
+ appName="Catalogix"
104
+ heading="Map my feed"
105
+ description="Map the columns of my latest feed to the taxonomy"
106
+ role="button"
107
+ tabIndex={0}
108
+ aria-label="Quick prompt: Map my feed"
109
+ style={{ cursor: "pointer" }}
110
+ onClick={send}
111
+ onKeyDown={(e) => { if (e.key === "Enter" || e.key === " ") { e.preventDefault(); send(); } }}
112
+ />
113
+ ```
114
+
115
+ ---
116
+
117
+ ## 2. Where to use it
118
+
119
+ - **The chat/copilot empty state** — a row or 2×2 grid of starters shown before the
120
+ first turn. Photogenix renders exactly this in
121
+ `dashboard/client/src/components/copilot/CopilotSuggestions.tsx`.
122
+ - **The CXO landing/home hero**, as a row of three cards under the composer — the one
123
+ member of the chat family that genuinely lives in a host dashboard.
124
+ - Any **"what can this app do?"** affordance where the `appName` pill is meaningful
125
+ because the prompt is routed to a specific product.
126
+
127
+ Unlike the rest of the `chat-agent` family, this one is host-facing: CXO and
128
+ Photogenix both render it directly.
129
+
130
+ ---
131
+
132
+ ## 3. When to use it
133
+
134
+ ### Use it when
135
+
136
+ - The card's job is to **send a prompt**, and naming the destination app adds
137
+ information.
138
+ - You want the heading/description type scale and the bordered canvas card that
139
+ matches the composer above it.
140
+
141
+ ### Don't use it — reach for this instead
142
+
143
+ | Situation | Use instead |
144
+ |---|---|
145
+ | A tile that **launches an app** from the copilot switcher | `ScAppCardForCopilot` |
146
+ | A tile in the app listing / marketing grid | `ScAppListingCard` / `ScAppCard` / `ScAppCardV3` |
147
+ | A saved brief or previous conversation | `ScBriefCard` |
148
+ | A generic pick-one option card (no app attribution) | `ScDefaultCard` |
149
+ | The gradient "Ask CXO" assistant CTA in a sidebar | `ScAskAgentButton` / `ScAskAgentSlot` |
150
+ | A short filter/segment choice | `ScSelectionPill` |
151
+ | An actual message in the transcript | `ScInChatMessage` |
152
+
153
+ ### Don't confuse with
154
+
155
+ | You may actually want | Not this |
156
+ |---|---|
157
+ | `ScBriefCard` — a *saved* brief with its own metadata | This is a *suggested* prompt with no persistence |
158
+ | `ScAppCardForCopilot` — app identity + logo, launches the app | This shows a bolt + app *name pill*, and sends text |
159
+ | `ScDefaultCard` — title/description card with no app pill and no fixed width | This one is 245px wide and app-attributed |
160
+ | A component that owns the prompt string | It renders three strings; the prompt you send is separate |
161
+
162
+ ---
163
+
164
+ ## 4. Why to use it
165
+
166
+ - **The three-level type ramp is pre-set** (12/18 tertiary app pill → 16/24 medium
167
+ secondary heading → 14/20 muted description). Getting that hierarchy right by hand
168
+ is the difference between a starter card that scans and one that reads as a wall.
169
+ - **Canvas-on-canvas correctness.** The card sits on `--alias-surface-canvas` with a
170
+ `--alias-border-divider` hairline, so it stays distinguishable from the page in
171
+ light mode instead of turning into a white box on white.
172
+ - **Consistent across two products.** CXO home and the Photogenix copilot render the
173
+ same component, so a change to the starter-card shape lands in both.
174
+ - **The bolt tile is the product's "quick action" mark**, already sized and boxed at
175
+ 8px padding on `--alias-fill-neutral-neutral`.
176
+
177
+ ---
178
+
179
+ ## Gotchas
180
+
181
+ **1. The width is hardcoded to 245px.** `width: 15.333125rem` on the root.
182
+
183
+ ```tsx
184
+ // WRONG — three 245px cards with a gap, not a full-width row
185
+ <div className="flex gap-4 w-full">
186
+ {prompts.map((p) => <ScQuickPrompt key={p.id} {...p} />)}
187
+ </div>
188
+
189
+ // RIGHT — override the width and allow shrinking
190
+ <div className="flex gap-4 w-full">
191
+ {prompts.map((p) => <ScQuickPrompt key={p.id} {...p} className="flex-1 !w-auto min-w-0" />)}
192
+ </div>
193
+ ```
194
+
195
+ Note the `!` — `className` is appended to the component's own class, but both are single
196
+ class selectors, so specificity ties and the winner is whichever rule comes later in the
197
+ stylesheet. `@streamoid/ui/dist/index.css` is typically imported before your utility CSS,
198
+ but don't rely on that: use `!w-auto` (or `style={{ width: "auto" }}`) so the override is
199
+ unambiguous.
200
+
201
+ **2. It is not interactive.** No `cursor: pointer`, no `role="button"`, no `tabIndex`,
202
+ no key handling. A card that looks clickable but isn't is the most common bug here.
203
+
204
+ ```tsx
205
+ // WRONG — clickable to a mouse, invisible to a keyboard, no affordance
206
+ <ScQuickPrompt heading="Map my feed" description="…" onClick={send} />
207
+
208
+ // RIGHT
209
+ <ScQuickPrompt
210
+ heading="Map my feed" description="…"
211
+ role="button" tabIndex={0} style={{ cursor: "pointer" }}
212
+ onClick={send}
213
+ onKeyDown={(e) => { if (e.key === "Enter" || e.key === " ") { e.preventDefault(); send(); } }}
214
+ />
215
+ ```
216
+
217
+ **3. All three text props default to Photogenix demo copy.** Omitting `appName` on a
218
+ Catalogix surface ships the word "Photogenix".
219
+
220
+ **4. `description` has no clamp.** Cards in a row will have different heights if the
221
+ descriptions differ in length; the row does not equalise them. Cap the string, or set
222
+ `-webkit-line-clamp` via `className`.
223
+
224
+ **5. There is no icon prop.** `SiconBolt` is hardcoded. If a spec asks for a
225
+ per-prompt icon, this component cannot do it — extend the DS rather than overlaying
226
+ one.
227
+
228
+ **6. Omitting `className` puts a literal `undefined` class on the root.** The class
229
+ list is built with plain string concatenation (`styles.scQuickPrompt + " " + className`),
230
+ unlike most components which filter falsy values. Cosmetic, but it will show up in
231
+ snapshot tests — pass `className=""` if a test diff matters.
232
+
233
+ **7. `appName`, `heading` and `description` each render with a trailing space** in the
234
+ JSX (`{appName} `). Harmless for layout, but `toHaveTextContent("Photogenix")` style
235
+ assertions on exact strings need trimming.
236
+
237
+ **8. The card has no selected/active state.** If a starter should stay highlighted
238
+ after being clicked, you need your own `className` skin.
239
+
240
+ ---
241
+
242
+ ## In the wild
243
+
244
+ ```tsx
245
+ // cxo-dashboard src/app/components/landing-content.tsx:380
246
+ <ScQuickPrompt
247
+ appName="Photogenix"
248
+ heading="Swap Model and Background"
249
+ description="Replace the current model and background with new ones that match my brand's aesthetic and tone."
250
+ className="flex-1 !w-auto min-w-0"
251
+ style={{
252
+ color: "var(--alias-text---icons-primary)",
253
+ }}
254
+ />
255
+ ```
256
+
257
+ ```tsx
258
+ // photogenix_v2 dashboard/client/src/components/copilot/CopilotSuggestions.tsx:24
259
+ <ScQuickPrompt
260
+ key={i}
261
+ appName="Photogenix"
262
+ heading={item.heading}
263
+ description={item.description}
264
+ onClick={() => onSelect(item.prompt)}
265
+ style={{ cursor: 'pointer' }}
266
+ />
267
+ ```
268
+
269
+ ---
270
+
271
+ ## Related
272
+
273
+ - `ScAppCardForCopilot` — the app-launcher tile in the copilot switcher.
274
+ - `ScBriefCard` — a saved brief/conversation card in the chat surface.
275
+ - `ScDefaultCard` — generic titled option card, no app attribution, no fixed width.
276
+ - `ScAskAgentButton` — the gradient assistant CTA that opens the chat in the first place.
277
+ - `ScInChatMessage` — what the transcript renders once a starter has been sent.
@@ -0,0 +1,228 @@
1
+ ---
2
+ component: ScRadio
3
+ package: "@streamoid/ui"
4
+ category: forms
5
+ status: stable
6
+ renders: div
7
+ tags: [radio, radio-button, single-select, option, choice, presentational]
8
+ related: [ScPairtext, ScCheckbox, ScTabField, ScSelectionPillGroup, ScSelection]
9
+ do_not_confuse_with: [ScCheckbox, ScPairtext, ScSelection, ScSelectionPill, ScTabField]
10
+ used_by: [catalogix]
11
+ ---
12
+
13
+ # ScRadio
14
+
15
+ **The bare radio glyph — a 24×24 ring, and a dot when selected.** It is
16
+ **purely presentational**: there is no `checked`, no `onChange`, no `value`, no
17
+ `name`, and no group component. You pass `state` and wire the click yourself.
18
+
19
+ ## TL;DR for agents
20
+
21
+ - **Reach for it when:** you are building a single-choice option list and laying out
22
+ the rows (label, description, disabled styling) yourself.
23
+ - **Don't reach for it when:** you want a labelled row for free (→ `ScPairtext`
24
+ with `type="radio"`), a segmented single-choice *field* (→ `ScTabField`), pills
25
+ (→ `ScSelectionPillGroup`), or the in-chat option list (→ `ScSelection`, agent
26
+ runtime).
27
+ - **Three things that will bite you:**
28
+ 1. **There is no `onChange` and no `checked`.** Only `state`. Put your `onClick`
29
+ on the radio or (better) on the whole row.
30
+ 2. **No group.** There is no `ScRadioGroup` — mutual exclusion is entirely yours.
31
+ 3. **No `disabled` prop.** Gate your own handler and dim it with your own class.
32
+
33
+ ---
34
+
35
+ ## 1. How to use it
36
+
37
+ ### Import
38
+
39
+ ```tsx
40
+ import { ScRadio } from "@streamoid/ui";
41
+ import "@streamoid/ui/dist/index.css"; // once, at your app root
42
+ ```
43
+
44
+ ### Minimal usage
45
+
46
+ ```tsx
47
+ <ScRadio
48
+ state={mode === "one-time" ? "selected" : "default"}
49
+ onClick={() => setMode("one-time")}
50
+ />
51
+ ```
52
+
53
+ ### Props
54
+
55
+ | Prop | Type | Default | Notes |
56
+ |---|---|---|---|
57
+ | `state` | `"default"` \| `"selected"` | `"default"` | The **only** state axis. `selected` adds the centre dot and brightens the ring. |
58
+ | `className` | `string` | – | Concatenated between the base and state classes. Also how you resize it (see Gotcha 6). |
59
+ | `...props` | `HTMLAttributes<HTMLDivElement>` | – | `onClick`, `style`, `id`, `role`, `aria-*`, `data-*` spread onto the root div. **`onClick` comes from here — it is not a declared prop.** |
60
+
61
+ ### What renders in each state
62
+
63
+ | `state` | Ring | Dot | Tokens |
64
+ |---|---|---|---|
65
+ | `default` | muted 1.5px ring | — | `--alias-text-and-icons-muted` |
66
+ | `selected` | bright ring | 6px radius filled circle | `--alias-text-and-icons-primary` (both) |
67
+
68
+ ### Recipes
69
+
70
+ ```tsx
71
+ // The Catalogix idiom: click target is the whole row, radio is decoration
72
+ {OPTIONS.map((o) => (
73
+ <div
74
+ key={o.value}
75
+ style={{ display: "flex", gap: 8, alignItems: "center", cursor: "pointer" }}
76
+ onClick={() => setValue(o.value)}
77
+ >
78
+ <ScRadio state={value === o.value ? "selected" : "default"} />
79
+ <span>{o.label}</span>
80
+ </div>
81
+ ))}
82
+
83
+ // "Disabled" option — gate the handler, dim it yourself
84
+ <div className={cx(styles.option, isLocked && styles.disabledOption)}>
85
+ <ScRadio
86
+ state={value === "one-time" ? "selected" : "default"}
87
+ onClick={() => !isLocked && setValue("one-time")}
88
+ />
89
+ <span>One Time</span>
90
+ </div>
91
+
92
+ // Want the label for free instead? Go one level up.
93
+ <ScPairtext type="radio" content="One Time" checked={value === "one-time"}
94
+ onChange={() => setValue("one-time")} />
95
+
96
+ // Dense contexts: size it from the outside — the SVG fills the box
97
+ <ScRadio state="selected" style={{ width: 16, height: 16 }} />
98
+ ```
99
+
100
+ ---
101
+
102
+ ## 2. Where to use it
103
+
104
+ - **Catalogix option lists** — `AddNewSyncRule` (One Time / Recurring),
105
+ `Assets/AddAssets` (upload-mode options), `FeedMapping/SelectHeaderRow` (pick which
106
+ preview row is the header). Seven call sites, all the same pattern: a row div owns
107
+ the click, the radio just reflects state.
108
+ - **Inside the DS** — `ScPairtext` (`type="radio"`) and `ScSelection` (the in-chat
109
+ option row, **agent runtime**) both render one.
110
+ - **Table cells** where one row must be picked (`SelectHeaderRow`).
111
+
112
+ ---
113
+
114
+ ## 3. When to use it
115
+
116
+ ### Use it when
117
+
118
+ - The choices are **mutually exclusive** and shown as a vertical list with labels or
119
+ descriptions you control.
120
+ - You already own the selection state and just need the correct glyph.
121
+
122
+ ### Don't use it — reach for this instead
123
+
124
+ | Situation | Use instead |
125
+ |---|---|
126
+ | Ring **plus** a text label as one click target | `ScPairtext` (`type="radio"`) |
127
+ | A labelled single-choice **field** in a form stack | `ScTabField` (caption + segmented switcher) |
128
+ | 2–5 mutually exclusive views shown as pills | `ScSelectionPillGroup` |
129
+ | A page/panel tab bar | `ScTabs` / `ScTabSwitcher` + `ScTabComp` |
130
+ | Multi-select (more than one active) | `ScCheckbox` / `ScCheckField` |
131
+ | An option row in the **agent chat transcript** | `ScSelection` / `ScSelectionList` (chat runtime) |
132
+ | A real form radio that posts with a `<form>` | native `<input type="radio" name=…>` — this div has no name/value |
133
+
134
+ ### Don't confuse with
135
+
136
+ | You may actually want | Not this |
137
+ |---|---|
138
+ | `ScCheckbox` — has `checked` + `onChange` + a `partial` state | `ScRadio` has **none** of those; it is `state`-only |
139
+ | `ScSelection` — the in-chat radio *row* with title + description (**agent runtime**) | `ScRadio` is a host-app glyph |
140
+ | `ScSelectionPill` — segmented tab pill, single-select but a completely different shape | Not a radio |
141
+ | `ScPairtext` — the labelled row that wraps this | This is only the ring |
142
+
143
+ Note the asymmetry with its sibling: `ScCheckbox` grew `checked`/`onChange`/`disabled`
144
+ props, `ScRadio` did not. Don't assume they share an API.
145
+
146
+ ---
147
+
148
+ ## 4. Why to use it
149
+
150
+ - **Theme-correct ring and dot.** The two states are one SVG path plus a circle,
151
+ filled from `--alias-text-and-icons-muted` / `-primary`, so the unselected ring
152
+ stays visible against both the dark and light surfaces. Hand-drawn `border-radius`
153
+ radios routinely disappear in light mode.
154
+ - **It scales cleanly.** The CSS forces the SVG to `100%/100%` with `aspect-ratio: 1`,
155
+ so overriding width/height shrinks the ring *and* the dot proportionally — the
156
+ literal `width="24"` attributes on the SVG would otherwise spill.
157
+ - **Zero API surface to get wrong.** One prop. The selection logic lives in your
158
+ reducer where it belongs, and no two call sites can disagree about the glyph.
159
+
160
+ ---
161
+
162
+ ## Gotchas
163
+
164
+ **1. No `onChange`, no `checked`.** They exist on `ScCheckbox`, not here. `checked`
165
+ is a type error — but **`onChange` compiles and silently never fires**, because
166
+ `HTMLAttributes` declares `onChange` for every div and nothing in `ScRadio` ever
167
+ calls it. Use `state` + `onClick`.
168
+
169
+ ```tsx
170
+ // WRONG — compiles, renders, and does nothing on click
171
+ <ScRadio state="default" onChange={() => setValue("a")} />
172
+
173
+ // RIGHT
174
+ <ScRadio state={value === "a" ? "selected" : "default"} onClick={() => setValue("a")} />
175
+ ```
176
+
177
+ **2. It is not an `<input type="radio">`.** No `name`, no `value`, no form
178
+ participation, no `role="radio"`, no `aria-checked`, no `tabIndex`, no arrow-key
179
+ roving between options. If the surface needs a11y, add it:
180
+
181
+ ```tsx
182
+ <div role="radiogroup" aria-label="Schedule">
183
+ <ScRadio role="radio" aria-checked={v === "one-time"} tabIndex={0}
184
+ state={v === "one-time" ? "selected" : "default"}
185
+ onClick={() => setV("one-time")} />
186
+ </div>
187
+ ```
188
+
189
+ **3. `cursor: pointer` is always on** — even with no `onClick` and even when your row
190
+ treats the option as locked. The CSS sets it unconditionally, so a "disabled" option
191
+ still looks clickable unless you override the cursor yourself.
192
+
193
+ **4. No `disabled`.** Guard inside your handler *and* dim with your own class; that's
194
+ what `AddNewSyncRule` does (`!isOneTimeDisabled && …` plus a `disabled-option` class).
195
+
196
+ **5. Click the label? Nothing happens.** Same trap as `ScCheckbox`: no inner input,
197
+ so `<label>` gives you no free hit area. Put `onClick` on the row wrapper.
198
+
199
+ **6. Resize from the outside only.** There is no `size` prop (unlike `ScCheckbox`).
200
+ Use `style={{ width, height }}` or a `className` — the SVG will follow.
201
+
202
+ **7. `className` is string-concatenated.** With `className` unset the DOM class list
203
+ contains a literal `undefined`. Don't assert on the class string in snapshots.
204
+
205
+ ---
206
+
207
+ ## In the wild
208
+
209
+ ```jsx
210
+ // catalogix/dashboard app/components/AddNewSyncRule/index.jsx:403
211
+ <ScRadio
212
+ state={syncData.scheduleType === "one-time" ? "selected" : "default"}
213
+ onClick={() =>
214
+ !isOneTimeDisabled &&
215
+ setSyncData({ ...syncData, scheduleType: "one-time" })
216
+ }
217
+ />
218
+ ```
219
+
220
+ ---
221
+
222
+ ## Related
223
+
224
+ - `ScPairtext` — label + radio (or checkbox, or icon) as one clickable row.
225
+ - `ScCheckbox` — the multi-select sibling; note its richer API (`checked`, `onChange`, `partial`).
226
+ - `ScTabField` — labelled single-choice field for form stacks.
227
+ - `ScSelectionPillGroup` — segmented single-choice for filters/views.
228
+ - `ScSelection` / `ScSelectionList` — the in-chat option family (**agent runtime**, not dashboards).