@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,230 @@
1
+ ---
2
+ component: ScAppCardForCopilot
3
+ package: "@streamoid/ui"
4
+ category: cards
5
+ status: stable
6
+ renders: button[type="button"]
7
+ tags: [card, copilot, agent, glassy, dark, wordmark, product, tagline, arrow, hover]
8
+ related: [ScAppcardLogos, ScAppCardV3, ScAppListingCard, ScBriefCard, ScQuickPrompt]
9
+ do_not_confuse_with: [ScAppCardV3, ScAppCard, ScAppListingCard, ScAppcardLogos]
10
+ # used_by deliberately omitted: nothing renders this yet — see "In the wild".
11
+ required_props: [product]
12
+ ---
13
+
14
+ # ScAppCardForCopilot
15
+
16
+ **A small, deliberately dark, glassy product card for the copilot surface.** It
17
+ renders a single-colour product wordmark with a right-chevron that fades in on
18
+ hover, and a grey tagline underneath — as a real `<button>` sized to its content.
19
+
20
+ ## TL;DR for agents
21
+
22
+ - **Reach for it when:** the copilot / agent panel needs a compact "jump to this
23
+ product" affordance floating over content, identified by a **wordmark** rather
24
+ than an icon and label.
25
+ - **Don't reach for it when:** you are in a host dashboard grid
26
+ (→ `ScAppCardV3` for the switcher, `ScAppListingCard` for a landing page), or you
27
+ only need the wordmark art itself (→ `ScAppcardLogos`).
28
+ - **Four things that will bite you:**
29
+ 1. `subText` defaults to **`"Planning & Buying"`** — Tactix's tagline. Render a
30
+ Photogenix card without setting it and you mislabel the product.
31
+ 2. The card is **hardcoded `#101010` with `#9e9e9e` text in both themes**. It does
32
+ not follow the light theme, on purpose. On a light page it will look like a
33
+ black chip.
34
+ 3. **The prop list is closed**: only `product`, `subText`, `onClick`, `className`,
35
+ `style`. No `aria-label`, no `data-*`, no `disabled`, no `id` — it does not
36
+ spread `ButtonHTMLAttributes`.
37
+ 4. `product` is **required** and is a key of the wordmark set —
38
+ `"tactix" | "artifax" | "photogenix" | "catalogix"`. There is no Streamoid
39
+ wordmark here.
40
+
41
+ ---
42
+
43
+ ## 1. How to use it
44
+
45
+ ### Import
46
+
47
+ ```tsx
48
+ import { ScAppCardForCopilot } from "@streamoid/ui";
49
+ import "@streamoid/ui/dist/index.css"; // once, at your app root
50
+ ```
51
+
52
+ ### Minimal usage
53
+
54
+ ```tsx
55
+ <ScAppCardForCopilot product="photogenix" subText="Product imagery" onClick={open} />
56
+ ```
57
+
58
+ ### Props
59
+
60
+ | Prop | Type | Default | Notes |
61
+ |---|---|---|---|
62
+ | `product` | `AppcardProduct` = `"tactix" \| "artifax" \| "photogenix" \| "catalogix"` | — | **Required.** Selects the wordmark rendered by `ScAppcardLogos`. Also becomes the wordmark's `aria-label`, i.e. the raw lowercase key. |
63
+ | `subText` | `string` | `"Planning & Buying"` | ⚠️ Real default, and it is **Tactix's** tagline. `12px/18px`, colour hardcoded `#9e9e9e`. |
64
+ | `onClick` | `() => void` | – | No event argument. Fires on click **and** on Enter/Space, because the root is a real `<button>`. |
65
+ | `className` | `string` | – | Appended after the internal class. |
66
+ | `style` | `CSSProperties` | – | Applied to the `<button>`. The only way to override the fixed `width: fit-content`. |
67
+
68
+ There is **no** props spread — anything not in this table is dropped at compile time.
69
+
70
+ ### Recipes
71
+
72
+ ```tsx
73
+ // A row of product cards in the copilot panel — always set subText per product
74
+ const PRODUCTS = [
75
+ { product: "tactix", subText: "Planning & Buying" },
76
+ { product: "artifax", subText: "Creative operations" },
77
+ { product: "photogenix", subText: "Product imagery" },
78
+ { product: "catalogix", subText: "Catalog enrichment" },
79
+ ] as const;
80
+
81
+ <div style={{ display: "flex", flexWrap: "wrap", gap: 12 }}>
82
+ {PRODUCTS.map((p) => (
83
+ <ScAppCardForCopilot
84
+ key={p.product}
85
+ product={p.product}
86
+ subText={p.subText}
87
+ onClick={() => openProduct(p.product)}
88
+ />
89
+ ))}
90
+ </div>
91
+
92
+ // Make it fill a column instead of hugging its content
93
+ <ScAppCardForCopilot product="catalogix" subText="Catalog enrichment"
94
+ style={{ width: "100%" }} onClick={go} />
95
+ ```
96
+
97
+ ---
98
+
99
+ ## 2. Where to use it
100
+
101
+ - **The copilot / agent chat panel** (`stream-agent` → `@streamoid/agent`) — a
102
+ floating card over the transcript or the empty state. Built from Figma
103
+ `AppCardForCopilot` (node `6383:9929`).
104
+ - It is intentionally fixed-dark because it "floats over content"; it belongs on a
105
+ dark-ish or image-backed surface, not inline in a light settings page.
106
+
107
+ It composes `ScAppcardLogos` for the wordmark and `SiconRight` for the chevron;
108
+ nothing else in the DS renders it.
109
+
110
+ ---
111
+
112
+ ## 3. When to use it
113
+
114
+ ### Use it when
115
+
116
+ - The product is best identified by its **wordmark** (brand recognition), not by an
117
+ icon plus a typed name.
118
+ - The surface is the **copilot**, and the card is meant to read as a glassy overlay
119
+ chip rather than a page card.
120
+ - You want real button semantics (Enter/Space, focus ring, no form submit) for free.
121
+
122
+ ### Don't use it — reach for this instead
123
+
124
+ | Situation | Use instead |
125
+ |---|---|
126
+ | The host app-switcher grid (gradient border, `active` tile) | `ScAppCardV3` |
127
+ | A product tile on a CXO landing page | `ScAppListingCard` |
128
+ | A 200px icon + name + subtitle row beside a toggle | `ScAppCard` |
129
+ | Just the wordmark art, no card | `ScAppcardLogos` |
130
+ | The Streamoid wordmark or mascot | `ScStreamoidWordmark` / `ScStreamoidMascot` (SC-Brand) |
131
+ | A per-app wordmark for a sidebar header | `ProductWordmark` / `ProductCollapsedMark` |
132
+ | An agent quick-prompt card (description only, gradient border) | `ScBriefCard` (via `ScQuickPrompt`) |
133
+ | A card that must follow the light theme | `ScAppCardV3` / `ScDefaultCard` — this one is fixed dark by design |
134
+
135
+ ### Don't confuse with
136
+
137
+ | You may actually want | Not this |
138
+ |---|---|
139
+ | `ScAppcardLogos` — the bare wordmark `<span role="img">`, no card, no button | This wraps that wordmark in a dark button with a tagline and hover arrow |
140
+ | `ScAppCardV3` — token-driven, theme-following, icon + typed name + `active` | This is fixed-dark, wordmark-only, with no selected state |
141
+ | `ScAppCard` — chrome-less identity row for settings | Different family entirely |
142
+
143
+ ### Don't confuse the surfaces either
144
+
145
+ This is the **only** card in the family that lives on the agent surface *and*
146
+ renders a wordmark. If your design shows a typed product name, you want
147
+ `ScAppCardV3` or `ScAppListingCard`.
148
+
149
+ ---
150
+
151
+ ## 4. Why to use it
152
+
153
+ - **Real button semantics.** Unlike `ScButton` (a `div[role="button"]`) and unlike
154
+ every other card here (plain `div`s), this is a genuine
155
+ `<button type="button">`: Enter/Space work, focus-visible works, and it can never
156
+ accidentally submit a form.
157
+ - **The hover choreography is wired to focus too.** `.card:hover .arrow` *and*
158
+ `.card:focus-visible .arrow` both reveal the chevron, so keyboard users get the
159
+ same affordance as mouse users — a detail hand-rolled versions always miss.
160
+ - **Wordmark colouring is solved.** The wordmark is inline SVG on `currentColor`,
161
+ and the card pins `color: --alias-text---icons-fullwhite`, so the mark is always
162
+ crisp white on the dark glass without you exporting a second asset.
163
+ - **Figma parity** with `AppCardForCopilot` (`6383:9929`), including the
164
+ `blur(4px)` glass, the 16px radius and the 12/16px padding pair.
165
+
166
+ ---
167
+
168
+ ## Gotchas
169
+
170
+ **1. `subText` defaults to Tactix's tagline.** This is the trap of the component.
171
+
172
+ ```tsx
173
+ // WRONG — a Photogenix card labelled "Planning & Buying"
174
+ <ScAppCardForCopilot product="photogenix" onClick={go} />
175
+
176
+ // RIGHT
177
+ <ScAppCardForCopilot product="photogenix" subText="Product imagery" onClick={go} />
178
+ ```
179
+
180
+ **2. Fixed dark in both themes.** `background: #101010` and `color: #9e9e9e` are
181
+ literals, not tokens. This is deliberate (see the CSS header comment) — do not
182
+ "fix" it by adding tokens without a design decision, and do not use the card on a
183
+ white page expecting it to invert.
184
+
185
+ **3. No props spread.** You cannot pass `aria-label`, `id`, `data-testid`,
186
+ `disabled`, `type`, `onKeyDown` or `onMouseEnter`. If you need any of them, wrap the
187
+ card or extend the component.
188
+
189
+ **4. The accessible name is assembled, and partly a slug.** The button has no label
190
+ of its own; its name comes from the wordmark's `aria-label={product}` (the raw
191
+ lowercase key, e.g. `"photogenix"`) plus `subText`. Screen readers therefore
192
+ announce roughly *"photogenix Product imagery, button"*. Acceptable, but if the
193
+ surface has accessibility requirements, prefer wrapping with your own labelled
194
+ control.
195
+
196
+ **5. `width: fit-content`.** The card hugs its content, so a row of them will have
197
+ uneven widths (the wordmarks are 54–88px wide). Set `style={{ width: "100%" }}` or
198
+ a fixed width if the design wants a uniform row.
199
+
200
+ **6. The chevron is invisible until hover/focus.** `opacity: 0` by default. In a
201
+ screenshot test without hover simulation you will not see it — that is correct
202
+ behaviour, not a bug.
203
+
204
+ **7. `product` is a closed union of four keys.** There is no `"streamoid"` and no
205
+ `"cxo"` wordmark in `APPCARD_WORDMARKS`; use `ScStreamoidWordmark` or
206
+ `ScCxoCopilotLogo` for those.
207
+
208
+ ---
209
+
210
+ ## In the wild
211
+
212
+ _No host render site found — used by the agent runtime / composed internally._
213
+
214
+ It is also not yet rendered inside `stream-agent`: that repo's empty state
215
+ hand-rolls dashed-border wordmark pills instead
216
+ (`stream-agent frontend/src/components/chat/EmptyChat.tsx:228` renders
217
+ `<ScAppcardLogos product={p.product} className="shrink-0" />` inside its own
218
+ `<button>`). So `ScAppCardForCopilot` is currently an unused Figma port. It belongs
219
+ in the copilot panel's product row — replacing exactly that hand-rolled pill — or in
220
+ any future agent surface that offers "open this product" cards.
221
+
222
+ ---
223
+
224
+ ## Related
225
+
226
+ - `ScAppcardLogos` — the wordmark this card renders; use it alone when you don't want the card.
227
+ - `ScAppCardV3` — the host-app switcher tile (theme-following, icon-based).
228
+ - `ScBriefCard` / `ScQuickPrompt` — the other agent-surface card family.
229
+ - `ScStreamoidWordmark` / `ScStreamoidMascot` — Streamoid brand marks (SC-Brand).
230
+ - `ProductWordmark` — the per-product wordmark used in sidebar headers.
@@ -0,0 +1,273 @@
1
+ ---
2
+ component: ScAppCardV3
3
+ package: "@streamoid/ui"
4
+ category: cards
5
+ status: stable
6
+ renders: div
7
+ tags: [card, tile, app, product, app-switcher, gradient-border, glow, active, v3]
8
+ related: [ScBriefCard, ScAppListingCard, ScAppCard, ScAppSwitchPanel, ScAppCardForCopilot]
9
+ do_not_confuse_with: [ScAppCard, ScAppListingCard, ScBriefCard, ScAppCardForCopilot]
10
+ used_by: [cxo]
11
+ ---
12
+
13
+ # ScAppCardV3
14
+
15
+ **The app-switcher tile: name on the left, glass icon pill on the right, one
16
+ description line under both.** Wrapped in a 1px gradient-border shell that warms to
17
+ coral on hover, with a coral radial glow blooming from the bottom-right corner.
18
+
19
+ ## TL;DR for agents
20
+
21
+ - **Reach for it when:** you are building the cross-product app-switcher grid
22
+ (or any dense grid of equal-height product tiles) and want the coral
23
+ gradient-border + glow treatment.
24
+ - **Don't reach for it when:** you want the roomy landing-page product tile
25
+ (→ `ScAppListingCard`), a description-only card in the same shell
26
+ (→ `ScBriefCard`), a 200px identity row (→ `ScAppCard`), or the dark glassy
27
+ wordmark card (→ `ScAppCardForCopilot`).
28
+ - **Four things that will bite you:**
29
+ 1. `active` sets the inner background to `--alias-surface-raised`, which is
30
+ **`#ffffff` in light mode — the same as the default surface.** The active
31
+ state is effectively invisible in light mode.
32
+ 2. Your `appIcon` is force-sized to **20×20** (`.iconPillIcon > * { width:100%;
33
+ height:100% }`), so the icon's own `size` prop is overridden.
34
+ 3. `{...props}` (including `onClick`) lands on the **outer** `.cardBorder`
35
+ wrapper — but there is no `role`/`tabIndex`/key handling, so it is not
36
+ keyboard reachable until you add them.
37
+ 4. The inner card is `height: 100%`; the **wrapper** has no height, so in a grid
38
+ you must stretch the wrapper (`height: 100%` or `align-items: stretch`) for
39
+ tiles to line up.
40
+
41
+ ---
42
+
43
+ ## 1. How to use it
44
+
45
+ ### Import
46
+
47
+ ```tsx
48
+ import { ScAppCardV3 } from "@streamoid/ui";
49
+ import "@streamoid/ui/dist/index.css"; // once, at your app root
50
+ ```
51
+
52
+ ### Minimal usage
53
+
54
+ ```tsx
55
+ <ScAppCardV3
56
+ appName="Stores"
57
+ description="Centralized product and asset management for every storefront"
58
+ appIcon={<SiconStores />}
59
+ />
60
+ ```
61
+
62
+ ### Props
63
+
64
+ | Prop | Type | Default | Notes |
65
+ |---|---|---|---|
66
+ | `appIcon` | `ReactNode` | `<SiconCart />` | ⚠️ Real default — a cart. Rendered inside the glass pill and **force-sized to 20×20** by CSS. |
67
+ | `appName` | `string` | `"Stores"` | ⚠️ Real default. `text-md-medium`, primary, `flex: 1`, `overflow: hidden; text-overflow: ellipsis` — but **no `white-space: nowrap`**, so multi-word names still wrap rather than ellipsise. |
68
+ | `description` | `string` | `"Centralized product and asset management for every storefront"` | ⚠️ Real default. `text-xs-regular`, tertiary, no clamp — long copy grows the tile. |
69
+ | `active` | `boolean` | `false` | Swaps the inner background to `--alias-surface-raised`. See Gotcha 1 — a no-op in light mode. |
70
+ | `className` | `string` | – | Applied to the **outer** `.cardBorder` wrapper, not the inner card. |
71
+ | `style` | `CSSProperties` | – | Applied to the outer wrapper. This is where you set `width` / `height` / `cursor`. |
72
+ | `...props` | `HTMLAttributes<HTMLDivElement>` | – | Spread onto the outer wrapper (`onClick`, `role`, `tabIndex`, `onKeyDown`, `aria-*`, `data-*`). |
73
+
74
+ ### What the DOM looks like
75
+
76
+ ```
77
+ div.cardBorder ← className, style, {...props}, the 1px gradient "border"
78
+ └─ div.scAppCardV3 ← surface, radius, padding, overflow:clip, height:100%
79
+ ├─ div.glow ← decorative, pointer-events:none
80
+ ├─ div.glowHover ← coral radial bloom, opacity 0 → 1 on wrapper :hover
81
+ └─ div.header
82
+ ├─ div.iconRow → p.appName + div.iconPill(bg/icon/shadow)
83
+ └─ p.description
84
+ ```
85
+
86
+ ### Recipes
87
+
88
+ ```tsx
89
+ // The CXO app-switcher grid (5-up), clickable + keyboard reachable
90
+ <div style={{ display: "grid", gridTemplateColumns: "repeat(5, minmax(0, 1fr))", gap: 16 }}>
91
+ {group.cards.map((card) => (
92
+ <ScAppCardV3
93
+ key={card.id}
94
+ appName={card.title}
95
+ description={card.desc}
96
+ appIcon={card.icon}
97
+ role="button"
98
+ tabIndex={0}
99
+ style={{ width: "100%", height: "100%", cursor: card.pageUrl ? "pointer" : "default" }}
100
+ onClick={() => handleCardClick(card.pageUrl)}
101
+ onKeyDown={(e) => {
102
+ if (e.key === "Enter" || e.key === " ") { e.preventDefault(); handleCardClick(card.pageUrl); }
103
+ }}
104
+ />
105
+ ))}
106
+ </div>
107
+
108
+ // Marking the current app — pair `active` with something that survives light mode
109
+ <ScAppCardV3
110
+ appName={app.title}
111
+ description={app.desc}
112
+ appIcon={app.icon}
113
+ active={app.id === currentAppId}
114
+ aria-current={app.id === currentAppId ? "true" : undefined}
115
+ style={{ width: "100%", height: "100%" }}
116
+ />
117
+ ```
118
+
119
+ ---
120
+
121
+ ## 2. Where to use it
122
+
123
+ - **CXO's app switcher** — the desktop overlay (`app-switcher.tsx`, a 5-column grid)
124
+ and the mobile sheet (`mobile-app-switcher.tsx`, 2 columns). These are its only
125
+ render sites.
126
+ - Any **grid of product/feature entry points** that wants the coral gradient-border
127
+ treatment rather than the scale-up hover of `ScAppListingCard`.
128
+ - Photogenix's internal component-library catalogue lists it as a subcomponent of
129
+ its templates section, i.e. it is on that team's radar but not yet rendered.
130
+
131
+ Its shell (gradient border + glow) is shared verbatim with `ScBriefCard`. CXO's
132
+ `specialization-picker-modal.tsx` **ports that CSS by hand** rather than using the
133
+ component — if you are touching the shell, that copy needs updating too.
134
+
135
+ ---
136
+
137
+ ## 3. When to use it
138
+
139
+ ### Use it when
140
+
141
+ - Tiles are **dense and equal-height** (a switcher grid, a feature picker) and the
142
+ description is one short line.
143
+ - You want the **coral gradient border + corner glow** hover language.
144
+ - You need a **selected/current** tile in the grid (`active`) — and you are on dark
145
+ mode, or you add your own light-mode affordance.
146
+
147
+ ### Don't use it — reach for this instead
148
+
149
+ | Situation | Use instead |
150
+ |---|---|
151
+ | A roomy landing-page product tile (24px padding, scale-up hover) | `ScAppListingCard` |
152
+ | The same shell but description-only, centred (agent quick prompts) | `ScBriefCard` |
153
+ | A 200px icon+name+subtitle identity row beside a control | `ScAppCard` |
154
+ | A dark glassy card showing a product **wordmark** with a hover arrow | `ScAppCardForCopilot` |
155
+ | The whole app-switcher flyout, already assembled | `ScAppSwitchPanel` (pass it to `StreamoidSidebar`'s `switchPanel`) |
156
+ | "Choose one of these options" in a modal | `ScDefaultCard` |
157
+ | A store tile | `ScStoreCard` |
158
+
159
+ ### Don't confuse with
160
+
161
+ | You may actually want | Not this |
162
+ |---|---|
163
+ | `ScBriefCard` — identical shell, but only `description` (centred), no name, no icon | `ScAppCardV3` always renders a name row and an icon pill |
164
+ | `ScAppListingCard` — bigger tile, `title`/`descrp`, scale(1.03) hover, no gradient border | This tile is compact with a gradient border |
165
+ | `ScAppCard` — the v1 of the name; a 200px chrome-less row | "V3" does **not** mean `ScAppCard` is deprecated; they are different shapes |
166
+ | `ScAppSwitchPanel` — the whole switcher card/overlay | This is one tile inside such a grid |
167
+
168
+ Prop-name cheat sheet: this card uses `appName` + **`description`**;
169
+ `ScAppCard` uses `appName` + `appDescription`; `ScAppListingCard` uses `title` +
170
+ `descrp`.
171
+
172
+ ---
173
+
174
+ ## 4. Why to use it
175
+
176
+ - **The gradient border actually works.** A CSS gradient cannot be a `border` and
177
+ keep `border-radius`; the component solves it with a 1px-padded wrapper whose
178
+ background is the gradient and an inner card at `calc(radius - 1px)`. Getting
179
+ that right by hand is the usual source of hairline seams and clipped corners.
180
+ - **The glow is two layers with a real transition** (`glow` fading out while
181
+ `glowHover` fades in over 600ms) and both are `pointer-events: none`, so the
182
+ bloom never eats your clicks.
183
+ - **The glass icon pill is three stacked layers** — translucent black at
184
+ `blur(40px)`, an inset shadow, then the icon — and the icon is normalised to
185
+ 20×20 so a grid of mixed icon sources still looks uniform.
186
+ - **`overflow: clip` on the inner card** keeps the glow and the pill inside the
187
+ rounded corners without clipping the wrapper's gradient ring.
188
+ - **One shell, two components.** Fixing the shell here fixes `ScBriefCard` too.
189
+
190
+ ---
191
+
192
+ ## Gotchas
193
+
194
+ **1. `active` is invisible in light mode.** It sets `--alias-surface-raised`, and in
195
+ the light theme `surface-base`, `surface-basesubtle` and `surface-raised` all
196
+ resolve to `#ffffff`. Add your own marker (border, `aria-current`, a badge) if the
197
+ selected tile must read in both themes.
198
+
199
+ **2. Your icon's `size` is overridden.** `.iconPillIcon` is 20×20 and forces
200
+ `width/height: 100%` on its direct child.
201
+
202
+ ```tsx
203
+ // POINTLESS — the CSS forces 20×20 regardless
204
+ <ScAppCardV3 appIcon={<SiconStores size={32} />} />
205
+
206
+ // RIGHT — let the pill size it
207
+ <ScAppCardV3 appIcon={<SiconStores />} />
208
+ ```
209
+
210
+ **3. The default icon is a cart.** Pass `appIcon={maybeUndefined}` and you re-trigger
211
+ the default. Decide explicitly with `?? <SiconX />`.
212
+
213
+ **4. Not keyboard accessible out of the box.** `cursor: pointer` is baked into the
214
+ inner card, so it looks clickable, but there is no `role`, `tabIndex` or key
215
+ handling. Add all three alongside `onClick` (neither CXO switcher currently does —
216
+ that is a known gap, not a pattern to copy).
217
+
218
+ **5. `className`/`style`/`onClick` go to the wrapper, not the card.** If you write a
219
+ `className` that expects to sit on the padded surface (e.g. overriding `padding` or
220
+ `background`), it will not apply — target `> div` from your class, or restyle via
221
+ tokens.
222
+
223
+ **6. Grid alignment needs the wrapper stretched.** `height: 100%` is on the inner
224
+ card only. In a CSS grid the wrapper is stretched by default (`align-items:
225
+ stretch`), which is why both CXO call sites get away with passing only
226
+ `style={{ width: "100%" }}`. In a flex row, or with `align-items: start`, you must
227
+ also pass `style={{ height: "100%" }}`.
228
+
229
+ **7. `appName` ellipsis is half-configured.** It has `overflow: hidden` and
230
+ `text-overflow: ellipsis` but no `white-space: nowrap`, so a two-word name wraps to
231
+ a second line and shifts the description down instead of showing an ellipsis. Keep
232
+ names short, or clamp them yourself.
233
+
234
+ **8. The hover accents are hardcoded, not tokens.** The border gradient ends at
235
+ `#EE5E3A` and the glow is a stack of `rgba(200,60,20,…)` stops. Identical in dark
236
+ and light; no brand override hook.
237
+
238
+ **9. `backdrop-filter: blur(4px)` on the inner card** creates a containing block for
239
+ fixed-position descendants. Do not render a `position: fixed` popover from inside
240
+ the tile — portal it to `document.body`.
241
+
242
+ **10. There is no `onClick` in the interface** — it arrives via
243
+ `HTMLAttributes<HTMLDivElement>`. Do not look for a documented handler prop; the
244
+ same is true of `role`, `tabIndex` and `aria-*`.
245
+
246
+ ---
247
+
248
+ ## In the wild
249
+
250
+ ```tsx
251
+ // cxo-dashboard src/app/components/app-switcher.tsx:366
252
+ <ScAppCardV3
253
+ key={card.id}
254
+ appName={card.title}
255
+ description={card.desc}
256
+ appIcon={card.icon}
257
+ style={{ width: "100%", cursor: card.pageUrl ? "pointer" : "default" }}
258
+ onClick={() => handleCardClick(card.pageUrl)}
259
+ />
260
+ ```
261
+
262
+ The mobile sheet renders the same call in a 2-column grid at
263
+ `src/app/components/mobile-app-switcher.tsx:327`.
264
+
265
+ ---
266
+
267
+ ## Related
268
+
269
+ - `ScBriefCard` — the same gradient-border shell, description-only (agent runtime).
270
+ - `ScAppListingCard` — the larger landing-page product tile.
271
+ - `ScAppCard` — the 200px chrome-less identity row (not an older version of this).
272
+ - `ScAppCardForCopilot` — dark glassy wordmark card; a real `<button>`.
273
+ - `ScAppSwitchPanel` — the assembled app-switcher overlay this tile belongs in.