@streamoid/ui 0.6.17 → 0.6.19

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 (134) hide show
  1. package/README.md +35 -18
  2. package/dist/docs/AGENTS.md +325 -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 +210 -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/ScWorkspaceAccountMenu.md +115 -0
  120. package/dist/docs/ScWorkspaceCard.md +234 -0
  121. package/dist/docs/ScWorkspaceSettingsMobile.md +265 -0
  122. package/dist/docs/ScWorkspaceSwitchCard.md +312 -0
  123. package/dist/docs/ScWorkspaceSwitchMobile.md +241 -0
  124. package/dist/docs/ScWorkspaceSwitchMobileV2.md +278 -0
  125. package/dist/docs/StreamoidSidebar.md +413 -0
  126. package/dist/docs/StreamoidWorkspaceSwitcher.md +307 -0
  127. package/dist/docs/UsageHistoryMobile.md +235 -0
  128. package/dist/docs/components.json +4931 -0
  129. package/dist/index.css +361 -36
  130. package/dist/index.d.mts +213 -88
  131. package/dist/index.d.ts +213 -88
  132. package/dist/index.js +2486 -1629
  133. package/dist/index.mjs +2487 -1620
  134. package/package.json +5 -3
@@ -0,0 +1,286 @@
1
+ ---
2
+ component: ScAppSwitchPanel
3
+ also_exports: [ScAppSwitchRow]
4
+ package: "@streamoid/ui"
5
+ category: sidebar
6
+ status: stable
7
+ renders: div
8
+ tags: [app-switcher, switch-app, product-switcher, cross-product, tactix, artifax, photogenix, catalogix, streamoid, wordmark]
9
+ related: [ScSideBarLogoUnit, StreamoidSidebar, ScSidebarSwitchMenu, ScWorkspaceSwitchCard]
10
+ do_not_confuse_with: [ScSidebarSwitchMenu, StreamoidWorkspaceSwitcher, ScWorkspaceSwitchCard, ScAppListingCard, ScAppCard]
11
+ used_by: [cxo, photogenix, catalogix, artifax]
12
+ required_props: [apps]
13
+ ---
14
+
15
+ # ScAppSwitchPanel
16
+
17
+ **The cross-product "switch app" list — one identical implementation for all four
18
+ apps.** A bare column of 60px rows, each showing the target product's wordmark
19
+ (gray, brightening to white on hover) and a `>` chevron that fades in. It has no
20
+ card, no border and no background: the surrounding sidebar or flyout supplies those.
21
+
22
+ ## TL;DR for agents
23
+
24
+ - **Reach for it when:** you need the "go to another Streamoid product" list —
25
+ in `StreamoidSidebar`'s `switchPanel`, or in your own portalled flyout.
26
+ - **Don't reach for it when:** you're switching **workspaces** rather than products
27
+ (→ `ScWorkspaceSwitchCard` / `StreamoidWorkspaceSwitcher`), you want one row with
28
+ an icon + description (→ `ScSidebarSwitchMenu`), or you want a product tile in a
29
+ launcher grid (→ `ScAppListingCard`).
30
+ - **Four things that will bite you:**
31
+ 1. It **re-sorts your array** into a canonical product order. Your ordering is
32
+ discarded.
33
+ 2. If a product matches an embedded wordmark, `name` **and** `description` are
34
+ **not rendered at all** — the wordmark replaces them.
35
+ 3. Matching is a **substring** test over `` `${key} ${name}`.toLowerCase() ``. A key
36
+ containing `"cxo"` or `"streamoid"` gets the Streamoid wordmark.
37
+ 4. It does **not** exclude the current app. That's the host's job.
38
+
39
+ ---
40
+
41
+ ## 1. How to use it
42
+
43
+ ### Import
44
+
45
+ ```tsx
46
+ import {
47
+ ScAppSwitchPanel,
48
+ ScAppSwitchRow,
49
+ type ScAppSwitchItem,
50
+ } from "@streamoid/ui";
51
+ import "@streamoid/ui/dist/index.css"; // once, at your app root
52
+ ```
53
+
54
+ ### Minimal usage
55
+
56
+ ```tsx
57
+ <ScAppSwitchPanel
58
+ apps={[
59
+ { key: "artifax", name: "Artifax", onSelect: () => open(artifaxUrl) },
60
+ { key: "photogenix", name: "Photogenix", onSelect: () => open(photogenixUrl) },
61
+ ]}
62
+ />
63
+ ```
64
+
65
+ ### Props — `ScAppSwitchPanel`
66
+
67
+ | Prop | Type | Default | Notes |
68
+ |---|---|---|---|
69
+ | `apps` | `ScAppSwitchItem[]` | — | **Required.** ⚠️ **Re-sorted internally** into canonical order; unknown products keep their relative order and go last. |
70
+ | `className` | `string` | – | On the root column. |
71
+ | `style` | `CSSProperties` | – | Merged **after** the base inline styles, so it can override `paddingBottom`. |
72
+
73
+ ### Props — `ScAppSwitchRow`
74
+
75
+ | Prop | Type | Default | Notes |
76
+ |---|---|---|---|
77
+ | `item` | `ScAppSwitchItem` | — | **Required.** The only prop — no `className`, no `style`. Rarely used directly; the panel renders one per app. |
78
+
79
+ ### `ScAppSwitchItem`
80
+
81
+ | Field | Type | Notes |
82
+ |---|---|---|
83
+ | `key` | `string` | React key **and** half of the wordmark-matching string. |
84
+ | `name` | `string` | Matching input, and the fallback label. ⚠️ **Not rendered** when a wordmark matches. Always used for the row's `aria-label` (`"Switch to {name}"`). |
85
+ | `description` | `string` | Optional tagline. ⚠️ **Only rendered for products with no embedded wordmark.** |
86
+ | `onSelect` | `() => void` | Row click. |
87
+
88
+ ### Wordmark matching + canonical order
89
+
90
+ | Order | Matches (substring, case-insensitive, on `key + " " + name`) | Renders |
91
+ |---|---|---|
92
+ | 1 | `tactix` | Tactix wordmark (100×34) |
93
+ | 2 | `artifax`, `design studio` | Artifax wordmark (116×32) |
94
+ | 3 | `photogenix`, `photo studio` | Photogenix wordmark (142×36) |
95
+ | 4 | `catalogix`, `catalog studio` | Catalogix wordmark (94×36) |
96
+ | 5 | `streamoid`, `cxo` | Streamoid wordmark (110×34) |
97
+ | last | anything else | `name` (16px semibold) + `description` (14px tertiary) |
98
+
99
+ ### Recipes
100
+
101
+ ```tsx
102
+ // Inside StreamoidSidebar — the sidebar positions it for both rails
103
+ <StreamoidSidebar
104
+ switchPanel={apps.length ? <ScAppSwitchPanel apps={apps} /> : undefined}
105
+ switchPanelOpen={appListOpen}
106
+ onSwitchPanelClose={() => setAppListOpen(false)}
107
+ /* … */
108
+ />
109
+
110
+ // Mapping a host service catalog, excluding the current app yourself
111
+ const apps: ScAppSwitchItem[] = serviceCatalog
112
+ .filter((s) => s.key !== CURRENT_APP_KEY) // the panel will NOT do this
113
+ .map((s) => ({
114
+ key: s.key,
115
+ name: s.name,
116
+ description: s.desc, // only shows for unknown products
117
+ onSelect: () => window.open(s.pageUrl, "_blank"),
118
+ }));
119
+
120
+ <ScAppSwitchPanel apps={apps} />
121
+
122
+ // In your own portalled flyout (ScArtifaxSidebar has no switchPanel slot).
123
+ // The panel brings its own bottom padding — don't add a second one.
124
+ createPortal(
125
+ <div style={{ width: 240, background: "var(--alias-surface-base)", border: "1px solid var(--alias-border-default)", borderRadius: "var(--radius-3xl)", overflow: "hidden" }}>
126
+ <ScAppSwitchPanel apps={apps} />
127
+ </div>,
128
+ document.body,
129
+ )
130
+ ```
131
+
132
+ ---
133
+
134
+ ## 2. Where to use it
135
+
136
+ - **`StreamoidSidebar`'s `switchPanel` slot** — CXO, Photogenix and Catalogix all do
137
+ this. The sidebar renders it as an animated drop-down card when expanded and as a
138
+ 240px right-side flyout when collapsed.
139
+ - **A host-owned portalled flyout** — Artifax, because `ScArtifaxSidebar` has no
140
+ switch slot and the sidebar body would clip an inline panel.
141
+ - Triggered by `ScSideBarLogoUnit`; it never renders its own trigger.
142
+ - All four apps use it, which is the point: the switcher looks and orders identically
143
+ everywhere regardless of the order each backend returns.
144
+
145
+ ---
146
+
147
+ ## 3. When to use it
148
+
149
+ ### Use it when
150
+
151
+ - The user is moving between **Streamoid products** (Tactix / Artifax / Photogenix /
152
+ Catalogix / Streamoid CXO).
153
+ - You want the canonical order and the embedded brand wordmarks for free.
154
+ - The list lives inside chrome you already own (a sidebar card, a flyout, a popover).
155
+
156
+ ### Don't use it — reach for this instead
157
+
158
+ | Situation | Use instead |
159
+ |---|---|
160
+ | Switching **workspace/tenant**, not product | `ScWorkspaceSwitchCard`, `StreamoidWorkspaceSwitcher`, `ScWorkspaceSwitchMobile` |
161
+ | A single product row with an icon, name **and** description you control | `ScSidebarSwitchMenu` |
162
+ | A product tile in a launcher grid / marketing surface | `ScAppListingCard`, `ScAppCardV3` |
163
+ | Products shown as nav items in the sidebar body | `ScSidebarMenu` via `config.sections` |
164
+ | An arbitrary popover menu of actions | `ScPopUpMenu` + `ScMenuOptions` |
165
+ | You need to render the current app as "selected" | Nothing here supports it — this list is "other apps" only |
166
+
167
+ ### Don't confuse with
168
+
169
+ | You may actually want | Not this |
170
+ |---|---|
171
+ | `ScSidebarSwitchMenu` — one row, `icon` + `text` + `description`, five `state`s, `variant="collapsed"` | `ScAppSwitchPanel` renders whole rows with **embedded** wordmarks and no state |
172
+ | `StreamoidWorkspaceSwitcher` / `ScWorkspaceSwitchCard` — workspaces (note: there is no `ScWorkspaceSwitcher` export) | This is products |
173
+ | `ScAppCard` / `ScAppCardV3` / `ScAppListingCard` — product **cards** | This is a compact switch list |
174
+ | `ScSideBarLogoUnit` — the trigger row | This is the panel it opens |
175
+
176
+ ---
177
+
178
+ ## 4. Why to use it
179
+
180
+ - **One canonical order, enforced in the component.** Every backend returns the
181
+ service catalog in a different order; the panel sorts by a fixed product list so
182
+ the switcher never reshuffles between apps. The Artifax test suite asserts exactly
183
+ this ("imposes its own canonical order regardless of the order supplied").
184
+ - **Brand wordmarks that respect the theme.** Each wordmark is drawn as a **CSS mask**
185
+ (`mask-image` data URI + `background-color`), so its colour is a token —
186
+ `--alias-text---icons-tertiary` at rest, `-primary` on hover. A plain `<img>` logo
187
+ would be stuck at one colour and invisible in light mode.
188
+ - **Real `<button type="button">` per row** with `aria-label="Switch to {name}"`, and
189
+ the wordmark span marked `aria-hidden` so the announcement isn't duplicated.
190
+ - **No chrome, deliberately.** Because it ships no card or border, the identical panel
191
+ drops into a sidebar drop-down, a 240px collapsed flyout and a portalled overlay
192
+ without fighting the host's surface.
193
+ - **One place to add a product.** New product ⇒ one entry in the `WORDMARKS` table,
194
+ and it appears, in the right position, in all four apps.
195
+
196
+ ---
197
+
198
+ ## Gotchas
199
+
200
+ **1. Your order is discarded.** `ordered = [...apps].sort((a, b) => orderIndex(a) - orderIndex(b))`.
201
+ Sorting the array before passing it is wasted work; you cannot pin a product to the top.
202
+
203
+ **2. A matched wordmark hides `name` and `description`.** The row renders either the
204
+ mask wordmark **or** the name/description block, never both.
205
+
206
+ ```tsx
207
+ // POINTLESS — "Design generation" is never shown; "artifax" matches the wordmark
208
+ <ScAppSwitchPanel apps={[{ key: "artifax", name: "Artifax", description: "Design generation", onSelect }]} />
209
+
210
+ // description only surfaces for products with no embedded mark
211
+ <ScAppSwitchPanel apps={[{ key: "insights", name: "Insights", description: "Reporting", onSelect }]} />
212
+ ```
213
+
214
+ **3. Matching is a loose substring test on `key + " " + name`.** `"cxo"` and
215
+ `"streamoid"` both map to the Streamoid wordmark, `"design studio"` maps to Artifax,
216
+ `"photo studio"` to Photogenix, `"catalog studio"` to Catalogix. An internal key like
217
+ `"cxo-insights"` will render the **Streamoid** wordmark, not a text row.
218
+
219
+ **4. The current app is not excluded.** The source comment is explicit — "The current
220
+ app is expected to be excluded by the host." Forget the filter and the sidebar offers
221
+ to switch to the app you're already in.
222
+
223
+ **5. Hover is React state, not CSS.** Each row keeps `useState(hover)` and colours
224
+ itself inline. So (a) there is no `:hover` rule to override from the host, and (b)
225
+ the chevron has `opacity: 0` until a real `mouseenter` — it will be missing from
226
+ snapshots and from any screenshot harness that fakes `:hover`.
227
+
228
+ **6. There's no active/selected/disabled state.** No `selected`, no `disabled`, no
229
+ loading row. Hosts that need a loading state render their own skeleton **instead of**
230
+ the panel (CXO does exactly that).
231
+
232
+ **7. It brings its own `paddingBottom: var(--spacing-md)`.** Host flyouts note this
233
+ ("applies its own bottom padding; don't double it"). Override via `style` if needed.
234
+
235
+ **8. Row height is a hardcoded 60px** with design-literal padding
236
+ (`15px 12px 12px 16px`). Long text rows do not grow — a two-line
237
+ `name` + `description` at 16px/14px only just fits.
238
+
239
+ **9. Everything is inline styles; there is no CSS module.** `className` gets you a
240
+ hook on the root only, and inline styles beat your stylesheet — restyle by wrapping,
241
+ not by targeting.
242
+
243
+ **10. `aria-label` is hardcoded English** (`Switch to {name}`), as is the entire
244
+ wordmark set. Not localisable without changing the DS.
245
+
246
+ **11. The wordmark assets are not public API.** `STREAMOID`, `ARTIFAX`,
247
+ `PHOTOGENIX`, `CATALOGIX`, `TACTIX` and `WordmarkAsset` live in
248
+ `SC-AppSwitch/wordmarks.ts` but are **not** re-exported from `@streamoid/ui` (only
249
+ `ScAppSwitchPanel`, `ScAppSwitchRow`, `ScAppSwitchItem`,
250
+ `IScAppSwitchPanelProps` are). For a wordmark you can place yourself, use
251
+ `ProductWordmark` from `SC-SideBarLogoUnit` — a different, path-based set.
252
+
253
+ **12. `mask-image: url("data:image/svg+xml,…")`.** If your app ships a strict CSP,
254
+ `img-src`/`default-src` must allow `data:` or the wordmarks render as blank boxes
255
+ (the coloured background shows through nothing).
256
+
257
+ ---
258
+
259
+ ## In the wild
260
+
261
+ ```tsx
262
+ // cxo-dashboard src/app/components/app-sidebar.tsx:901
263
+ <ScAppSwitchPanel
264
+ apps={switchApps.map((app) => ({
265
+ key: app.key,
266
+ name: app.name,
267
+ description: app.desc,
268
+ onSelect: () => openSwitchApp(app.pageUrl),
269
+ }))}
270
+ />
271
+ ```
272
+
273
+ Also: `catalogix/dashboard app/containers/LeftMenu/index.jsx:1002` (straight into
274
+ `switchPanel`) and `artifax packages/shared/src/components/DashboardSidebar.tsx:1474`
275
+ (inside the host's portalled `SwitchFlyout`).
276
+
277
+ ---
278
+
279
+ ## Related
280
+
281
+ - `ScSideBarLogoUnit` — the row that opens this panel.
282
+ - `StreamoidSidebar` — pass this as `switchPanel`; it handles both rails and the backdrop.
283
+ - `ScArtifaxSidebar` — has **no** switch slot; portal the panel yourself.
284
+ - `ScSidebarSwitchMenu` — the older per-row switcher, still used by `ScCatalogixSidebar`.
285
+ - `ScWorkspaceSwitchCard` / `StreamoidWorkspaceSwitcher` — the workspace equivalent.
286
+ - `ProductWordmark` (SC-SideBarLogoUnit) — the other, exported wordmark set.
@@ -0,0 +1,226 @@
1
+ ---
2
+ component: ScAppcardLogos
3
+ package: "@streamoid/ui"
4
+ category: brand
5
+ status: stable
6
+ renders: span[role="img"]
7
+ tags: [wordmark, logo, brand, product, tactix, artifax, photogenix, catalogix, svg, currentColor]
8
+ related: [ScAppCardForCopilot, ProductWordmark, ProductCollapsedMark, ScStreamoidWordmark, ScStrLogo]
9
+ do_not_confuse_with: [ProductWordmark, ScStreamoidWordmark, ScStreamoidMascot, ScCxoCopilotLogo, ScAppCard]
10
+ used_by: [agent]
11
+ required_props: [product]
12
+ ---
13
+
14
+ # ScAppcardLogos
15
+
16
+ **One single-colour product wordmark, 14px tall.** Inline SVG for Tactix, Artifax,
17
+ Photogenix or Catalogix, filled with `currentColor` so the parent's text colour
18
+ paints it. Ships the type `AppcardProduct` used by `ScAppCardForCopilot`.
19
+
20
+ ## TL;DR for agents
21
+
22
+ - **Reach for it when:** you need a product's *name as artwork* — inside a card,
23
+ chip or pill where a hover/theme colour change should recolour the mark for free.
24
+ - **Don't reach for it when:** you want the sidebar-header wordmark
25
+ (→ `ProductWordmark` / `ProductCollapsedMark`), the Streamoid brand
26
+ (→ `ScStreamoidWordmark` / `ScStreamoidMascot`), the CXO copilot mark
27
+ (→ `ScCxoCopilotLogo`), or an app icon plus a typed name (→ `ScAppCard`).
28
+ - **Four things that will bite you:**
29
+ 1. `product` is **required** and the set is exactly four keys — `tactix`,
30
+ `artifax`, `photogenix`, `catalogix`. **There is no Streamoid or CXO wordmark.**
31
+ 2. Widths differ per product (56 / 54 / 88 / 72 px at a fixed 14px height), so a
32
+ row of them is **not** visually uniform.
33
+ 3. It sets `role="img"` with **`aria-label={product}` — the raw lowercase key**
34
+ ("photogenix"), not a display name.
35
+ 4. The SVG is injected with `dangerouslySetInnerHTML`. Safe here (the markup is a
36
+ compile-time constant), but it means you cannot target inner paths from a
37
+ parent stylesheet by anything other than element selectors.
38
+
39
+ ---
40
+
41
+ ## 1. How to use it
42
+
43
+ ### Import
44
+
45
+ ```tsx
46
+ import { ScAppcardLogos, type AppcardProduct } from "@streamoid/ui";
47
+ // No CSS import needed for this component — it styles itself inline.
48
+ ```
49
+
50
+ ### Minimal usage
51
+
52
+ ```tsx
53
+ <ScAppcardLogos product="catalogix" />
54
+ ```
55
+
56
+ ### Props
57
+
58
+ | Prop | Type | Default | Notes |
59
+ |---|---|---|---|
60
+ | `product` | `AppcardProduct` = `"tactix" \| "artifax" \| "photogenix" \| "catalogix"` | — | **Required.** Selects the wordmark. Also becomes `aria-label` verbatim. |
61
+ | `color` | `string` | – | Sets `color` on the wrapper `<span>`; the SVG paths are `fill="currentColor"`, so this is how you tint the mark. Omit it and the mark inherits the surrounding text colour. |
62
+ | `className` | `string` | – | Applied to the wrapper `<span>` (the usual place to set `height`, `flex-shrink`, or a hover colour). |
63
+ | `style` | `CSSProperties` | – | Merged **after** the built-in `display: inline-flex; align-items: center; color` — so `style` can override `color`, and `style={{ display: "block" }}` works. |
64
+
65
+ There is no props spread: `onClick`, `data-*`, `aria-hidden` etc. are not accepted.
66
+
67
+ ### Wordmark geometry
68
+
69
+ | `product` | SVG width | SVG height |
70
+ |---|---|---|
71
+ | `tactix` | 56 | 14 |
72
+ | `artifax` | 54 | 14 |
73
+ | `photogenix` | 88 | 14 |
74
+ | `catalogix` | 72 | 14 |
75
+
76
+ ### Recipes
77
+
78
+ ```tsx
79
+ // Inherit the parent's colour — the idiomatic use. The mark follows a hover
80
+ // transition on the parent for free, because it's currentColor all the way down.
81
+ <button className="text-[var(--alias-text---icons-tertiary)] hover:text-[var(--alias-text---icons-primary)]">
82
+ <ScAppcardLogos product="photogenix" />
83
+ </button>
84
+
85
+ // Pin it white on a dark/glassy card (what ScAppCardForCopilot does)
86
+ <ScAppcardLogos product="artifax" color="var(--alias-text---icons-fullwhite, #ffffff)" />
87
+
88
+ // Scale it: the SVG has width/height attributes, so constrain via CSS, not props
89
+ <ScAppcardLogos product="catalogix" style={{ height: 18 }} className="[&>svg]:h-full [&>svg]:w-auto" />
90
+
91
+ // Iterate the whole set type-safely
92
+ const PRODUCTS: AppcardProduct[] = ["tactix", "artifax", "photogenix", "catalogix"];
93
+ {PRODUCTS.map((p) => <ScAppcardLogos key={p} product={p} />)}
94
+ ```
95
+
96
+ ---
97
+
98
+ ## 2. Where to use it
99
+
100
+ - **`ScAppCardForCopilot`** renders it as the card's identity, pinned white with a
101
+ 14px height.
102
+ - **The agent's empty chat state** (`stream-agent`) renders it directly inside
103
+ hand-rolled dashed-border product pills — the wordmark is tertiary by default and
104
+ goes primary on hover purely via `currentColor`.
105
+ - Any **compact "which product" affordance** on a dark or coloured surface where a
106
+ wordmark reads better than an icon + label.
107
+
108
+ Generated from Figma `AppcardLogos` (node `6383:9997`); the SVG strings live in
109
+ `SC-AppcardLogos/sources.ts` and are auto-generated — **do not hand-edit them**.
110
+
111
+ ---
112
+
113
+ ## 3. When to use it
114
+
115
+ ### Use it when
116
+
117
+ - The mark must **recolour with its context** (hover, theme, inverse surface) and
118
+ you do not want to ship two PNGs.
119
+ - Space is tight and 14px of wordmark is the whole identity.
120
+ - You need the `AppcardProduct` union to key your own product data.
121
+
122
+ ### Don't use it — reach for this instead
123
+
124
+ | Situation | Use instead |
125
+ |---|---|
126
+ | A sidebar header's product wordmark (expanded state) | `ProductWordmark` |
127
+ | A collapsed sidebar's square product mark | `ProductCollapsedMark` (check `hasCollapsedMark`) |
128
+ | The Streamoid wordmark (98:32) or mascot | `ScStreamoidWordmark` / `ScStreamoidMascot` (SC-Brand) |
129
+ | The plain header Streamoid lockup vs the app-switcher lockup | `ScStreamoidWordmark` (SC-Brand) — those two sizes are distinct, don't conflate |
130
+ | The CXO Copilot logo | `ScCxoCopilotLogo` |
131
+ | The generic Streamoid `S` logo | `ScStrLogo` |
132
+ | An app *icon* (not the name) plus a typed label | `ScAppCard` / `ScAppListingCard` with an `Sicon*` |
133
+ | A whole clickable product card | `ScAppCardForCopilot` |
134
+
135
+ ### Don't confuse with
136
+
137
+ | You may actually want | Not this |
138
+ |---|---|
139
+ | `ProductWordmark` — the sidebar wordmark set, sized for the shell header | `ScAppcardLogos` is the 14px *appcard* set; different sizes, different Figma source |
140
+ | `ScStreamoidWordmark` — the company mark | This set has **no** Streamoid entry |
141
+ | `ScAppCard` — icon + name + subtitle row | This is only the name, as vector art |
142
+
143
+ ---
144
+
145
+ ## 4. Why to use it
146
+
147
+ - **One asset, every colour.** `fill="currentColor"` inline SVG means the mark
148
+ inherits hover, focus, theme and inverse-surface colours with no second asset and
149
+ no `filter: invert()` hack.
150
+ - **Inline, not `<img>`** — deliberately, per the source comment: an `<img>` cannot
151
+ pick up `currentColor`, so the wordmark would need a per-theme file.
152
+ - **No network request, no flash.** The SVG is compiled into the bundle, so the
153
+ mark is painted on first render (important in the agent's empty state, which is
154
+ the first thing a user sees).
155
+ - **The `AppcardProduct` union is the single source of truth** for "which products
156
+ have an appcard wordmark", and it is derived from the wordmark map itself — add a
157
+ product to `sources.ts` and every consumer's types widen automatically.
158
+ - **`role="img"` + a label** means assistive tech announces a product rather than
159
+ skipping an unlabelled decorative span.
160
+
161
+ ---
162
+
163
+ ## Gotchas
164
+
165
+ **1. Only four products exist.** `product="streamoid"` is a type error, and there is
166
+ no CXO mark here.
167
+
168
+ ```tsx
169
+ // WRONG — not in the union
170
+ <ScAppcardLogos product="streamoid" />
171
+
172
+ // RIGHT
173
+ <ScStreamoidWordmark />
174
+ ```
175
+
176
+ **2. `aria-label` is the raw key.** Screen readers say "photogenix", lowercase. If
177
+ the surrounding control needs a nicer name, label the control itself — you cannot
178
+ override the span's label (no props spread).
179
+
180
+ **3. Uneven widths.** Photogenix is 88px wide, Artifax 54px. A flex row of the four
181
+ marks will look ragged; add `justify-content` / fixed-width slots if the design
182
+ expects a grid. `ScAppCardForCopilot` inherits this — its `width: fit-content` cards
183
+ come out different widths per product.
184
+
185
+ **4. `size` does not exist.** Unlike `Sicon*`, there is no size prop. The inner SVG
186
+ carries hardcoded `width`/`height` attributes, so scaling requires CSS on the SVG
187
+ itself (`[&>svg]{height:100%;width:auto}` or similar), not just a height on the span.
188
+
189
+ **5. `style` beats `color`.** The wrapper's inline style is
190
+ `{ display: "inline-flex", alignItems: "center", color, ...style }` — a `color` inside
191
+ `style` silently wins over the `color` prop. Pick one.
192
+
193
+ **6. `APPCARD_WORDMARKS` is not exported.** Only `ScAppcardLogos` and the
194
+ `AppcardProduct` type are public. Do not try to import the raw SVG map from
195
+ `@streamoid/ui`; if you need the list of products, iterate a literal array typed as
196
+ `AppcardProduct[]`.
197
+
198
+ **7. Don't hand-edit `sources.ts`.** It is auto-generated from Figma and its four
199
+ wordmark lines each contain a very large inline SVG string — grep it, never open it
200
+ whole, and regenerate rather than patch.
201
+
202
+ ---
203
+
204
+ ## In the wild
205
+
206
+ ```tsx
207
+ // stream-agent frontend/src/components/chat/EmptyChat.tsx:228
208
+ <ScAppcardLogos product={p.product} className="shrink-0" />
209
+ ```
210
+
211
+ No host render site found in cxo / photogenix / catalogix / artifax — the call site
212
+ above is the **agent runtime** (`used_by: [agent]`).
213
+ Inside the DS it is composed by `ScAppCardForCopilot`
214
+ (`packages/ui/src/SC-AppCardForCopilot/ScAppCardForCopilot.tsx:36`). If a host
215
+ dashboard ever needs it, the natural home is a product-switcher chip or an
216
+ empty-state "jump to product" row.
217
+
218
+ ---
219
+
220
+ ## Related
221
+
222
+ - `ScAppCardForCopilot` — the dark glassy card built around this wordmark.
223
+ - `ProductWordmark` / `ProductCollapsedMark` — the sidebar-shell wordmark set.
224
+ - `ScStreamoidWordmark` / `ScStreamoidMascot` — company marks (both live in SC-Brand).
225
+ - `ScCxoCopilotLogo` / `ScStrLogo` — CXO and Streamoid logo marks.
226
+ - `@streamoid/icons` — `Sicon*` product/feature icons, when you want a glyph not a wordmark.