@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,308 @@
1
+ ---
2
+ component: ScAppField
3
+ package: "@streamoid/ui"
4
+ category: forms
5
+ status: stable
6
+ renders: div > (label row) + div > [ScArtifaxInvite, ScPhtogenixInvite, ScCatalogixInvite]
7
+ tags: [app-permission, invite, teams, member, toggle, artifax, photogenix, catalogix, store-access]
8
+ related: [ScArtifaxInvite, ScPhtogenixInvite, ScCatalogixInvite, ScCheckField, ScTabField, ScToggleSwitch, ScAppCard]
9
+ do_not_confuse_with: [ScTextField, ScOnlyField, ScAppCard, ScAppSwitchPanel, ScCheckField]
10
+ used_by: [cxo]
11
+ ---
12
+
13
+ # ScAppField
14
+
15
+ **Not a generic field — the "App permission" block from the invite/manage-member
16
+ form.** It renders a label row plus exactly three hardcoded app rows, in this
17
+ order: **Artifax**, **Photogenix**, **Catalogix** (each an app card with a toggle),
18
+ and when Catalogix is on, its nested store-access list with a search box and a
19
+ checkbox per store.
20
+
21
+ ## TL;DR for agents
22
+
23
+ - **Reach for it when:** you are building the *invite a user* / *manage a user*
24
+ form and need the standard per-app permission section.
25
+ - **Don't reach for it when:** you want a generic labelled field (→ `ScTextField`),
26
+ a label + checkbox group (→ `ScCheckField`), or control over which apps appear
27
+ (→ compose `ScArtifaxInvite` / `ScPhtogenixInvite` / `ScCatalogixInvite`
28
+ yourself).
29
+ - **Five things that will bite you:**
30
+ 1. The app list is **hardcoded**. No `apps` prop, no children, no reordering,
31
+ no way to hide an app.
32
+ 2. Every row defaults to **enabled** — omit `artifaxEnabled` /
33
+ `photogenixEnabled` / `catalogixEnabled` and all three render as ON.
34
+ 3. Enable Catalogix without passing `catalogixStores` and you ship **five
35
+ placeholder rows literally reading "Pairtext"**.
36
+ 4. The descriptions are placeholder copy: *"Info about artifax"*, *"Info about
37
+ photogenix"*, *"Info about catalogix"* — and `ScAppField` exposes **no props
38
+ to change them**.
39
+ 5. The root has no `width`, unlike every other `SC-*Field`. In a flex column
40
+ you must stretch it yourself (`className="w-full"`).
41
+
42
+ ---
43
+
44
+ ## 1. How to use it
45
+
46
+ ### Import
47
+
48
+ ```tsx
49
+ import { ScAppField } from "@streamoid/ui";
50
+ import "@streamoid/ui/dist/index.css"; // once, at your app root
51
+ ```
52
+
53
+ ### Minimal usage
54
+
55
+ There is no useful "minimal" call — omitting the enabled flags turns everything
56
+ on and omitting `catalogixStores` renders placeholders. The shortest *correct*
57
+ call is:
58
+
59
+ ```tsx
60
+ <ScAppField
61
+ artifaxEnabled={artifaxOn}
62
+ onArtifaxToggle={setArtifaxOn}
63
+ photogenixEnabled={photogenixOn}
64
+ onPhotogenixToggle={setPhotogenixOn}
65
+ catalogixEnabled={catalogixOn}
66
+ onCatalogixToggle={setCatalogixOn}
67
+ catalogixStores={stores}
68
+ catalogixSelectedStores={selectedStores}
69
+ onCatalogixStoreToggle={toggleStore}
70
+ className="w-full"
71
+ />
72
+ ```
73
+
74
+ ### Props
75
+
76
+ Extends `React.HTMLAttributes<HTMLDivElement>`; unlisted props are spread onto
77
+ the root div.
78
+
79
+ | Prop | Type | Default | Notes |
80
+ |---|---|---|---|
81
+ | `appFieldTitle` | `string` | `"App permission"` | ⚠️ Hardcoded English default; the only label prop. |
82
+ | `artifaxEnabled` | `boolean` | – | ⚠️ `undefined` ⇒ the row falls back to `ScArtifaxInvite`'s own `active="true"` default ⇒ **ON**. |
83
+ | `onArtifaxToggle` | `(active: boolean) => void` | – | Fired by the row's `ScToggleSwitch`. |
84
+ | `photogenixEnabled` | `boolean` | – | ⚠️ Same: `undefined` ⇒ ON. |
85
+ | `onPhotogenixToggle` | `(active: boolean) => void` | – | |
86
+ | `catalogixEnabled` | `boolean` | – | ⚠️ Same: `ScAppField` hardcodes `active="true"` on that row, so `undefined` ⇒ ON **and expanded**. |
87
+ | `onCatalogixToggle` | `(active: boolean) => void` | – | |
88
+ | `catalogixStores` | `{ id: string; name: string }[]` | – | ⚠️ `undefined` ⇒ 5 placeholder rows reading `"Pairtext"`. `[]` ⇒ the "No stores found" empty state. |
89
+ | `catalogixSelectedStores` | `string[]` | – | Ticked stores. Also drives the "N Selected" counter. |
90
+ | `onCatalogixStoreToggle` | `(storeId: string) => void` | – | Called with the store id; **you** own add/remove. |
91
+ | `catalogixSearchValue` | `string` | – | Controlled search text. Filtering is **not** done for you. |
92
+ | `onCatalogixSearchChange` | `(query: string) => void` | – | Receives the raw string. |
93
+ | `className` | `string` | – | Appended to the root class list. |
94
+
95
+ #### What is fixed and cannot be changed through this component
96
+
97
+ | Fixed thing | Value |
98
+ |---|---|
99
+ | Rows and order | Artifax → Photogenix → Catalogix |
100
+ | App names | `"Artifax"`, `"Photogenix"`, `"Catalogix"` |
101
+ | App descriptions | `"Info about artifax"`, `"Info about photogenix"`, `"Info about catalogix"` |
102
+ | App icons | Artifax/Catalogix: `SiconArtifacts`; Photogenix: `SiconPhotogenix` |
103
+ | Store-access strings | `"Store access"`, `"N Selected"`, `"Search stores..."` |
104
+
105
+ ### Recipes
106
+
107
+ ```tsx
108
+ // The full invite-form wiring (CXO mobile teams popup)
109
+ <ScAppField
110
+ appFieldTitle="App permission"
111
+ artifaxEnabled={artifaxOn}
112
+ onArtifaxToggle={setArtifaxOn}
113
+ photogenixEnabled={photogenixOn}
114
+ onPhotogenixToggle={setPhotogenixOn}
115
+ catalogixEnabled={catalogixOn}
116
+ onCatalogixToggle={setCatalogixOn}
117
+ catalogixStores={stores}
118
+ catalogixSelectedStores={selectedStores}
119
+ onCatalogixStoreToggle={(id) =>
120
+ setSelectedStores((prev) =>
121
+ prev.includes(id) ? prev.filter((s) => s !== id) : [...prev, id]
122
+ )
123
+ }
124
+ catalogixSearchValue={storeSearch}
125
+ onCatalogixSearchChange={setStoreSearch}
126
+ className="w-full shrink-0"
127
+ />
128
+
129
+ // YOU must filter — the search box only reports the query
130
+ const visibleStores = useMemo(
131
+ () => allStores.filter((s) => s.name.toLowerCase().includes(storeSearch.toLowerCase())),
132
+ [allStores, storeSearch]
133
+ );
134
+ <ScAppField catalogixStores={visibleStores} catalogixSearchValue={storeSearch} … />
135
+
136
+ // Only two apps, or a different order? Don't use ScAppField — compose the rows.
137
+ <div className="flex flex-col">
138
+ <ScPhtogenixInvite
139
+ enabled={photogenixOn}
140
+ onToggle={setPhotogenixOn}
141
+ scAppCardappDescription="Generate on-brand product imagery" // note the prop name
142
+ />
143
+ <ScCatalogixInvite
144
+ enabled={catalogixOn}
145
+ onToggle={setCatalogixOn}
146
+ appDescription="Manage catalogue and feeds"
147
+ stores={stores}
148
+ selectedStores={selectedStores}
149
+ onStoreToggle={toggleStore}
150
+ />
151
+ </div>
152
+ ```
153
+
154
+ ---
155
+
156
+ ## 2. Where to use it
157
+
158
+ - **CXO mobile teams flows** — `mobile-teams-invite-popup.tsx` and
159
+ `mobile-teams-manage-popup.tsx`, stacked after `ScTextField` (Email ID),
160
+ `ScTabField` (Role) and `ScCheckField` (Permission), inside a mobile popup with
161
+ an `ScButton` footer. It is rendered only for `role === "member"`.
162
+ - Its natural desktop home is the invite/manage-user modal. CXO's desktop
163
+ `invite-update-modal.tsx` does **not** use it today — it hand-rolls the section
164
+ — so treat desktop parity as an open migration, not as prior art.
165
+
166
+ CXO is the only host. Catalogix and Photogenix have their own team screens and do
167
+ not render this.
168
+
169
+ ---
170
+
171
+ ## 3. When to use it
172
+
173
+ ### Use it when
174
+
175
+ - You are reproducing the **Streamoid invite/manage-member form** exactly as
176
+ designed, with all three apps present.
177
+ - You want the Catalogix store-access sub-panel (header, counter, search box,
178
+ checkbox rows) without building it.
179
+
180
+ ### Don't use it — reach for this instead
181
+
182
+ | Situation | Use instead |
183
+ |---|---|
184
+ | A generic labelled input | `ScTextField` (or `ScOnlyField` unlabelled) |
185
+ | A label + a group of checkboxes | `ScCheckField` |
186
+ | A label + a segmented single choice (e.g. Role) | `ScTabField` |
187
+ | One app row, or a subset/reordering of apps | `ScArtifaxInvite` / `ScCatalogixInvite` (real `appName`/`appDescription`/`appIcon`) and `ScPhtogenixInvite` (same three, but named `scAppCardappName`/`scAppCardappDescription`/`scAppCardappIcon`) |
188
+ | A single on/off switch | `ScToggleSwitch` |
189
+ | Showing an app (icon + name + description) with no toggle | `ScAppCard` |
190
+ | Letting the user *switch* to another app | `ScAppSwitchPanel` / `ScAppCardV3` |
191
+
192
+ ### Don't confuse with
193
+
194
+ | You may actually want | Not this |
195
+ |---|---|
196
+ | `ScTextField` / `ScOnlyField` — the name pattern `Sc*Field` suggests an input; this one is a **permissions block** | `ScAppField` has no input of its own beyond the nested store search |
197
+ | `ScAppCard` — the icon + name + description row itself (composed *inside* each invite row) | `ScAppField` is the whole three-app section |
198
+ | `ScAppSwitchPanel` / `ScAppCardV3` / `ScAppListingCard` — app **navigation/marketing** surfaces | `ScAppField` is app **permission** in a form |
199
+ | `ScCheckField` — label + checkbox list, which is what "app permission" looks like at first glance | `ScAppField` uses toggles and nested store access |
200
+
201
+ ---
202
+
203
+ ## 4. Why to use it
204
+
205
+ - **Three composite rows for one prop set.** Each row is an `ScAppCard` +
206
+ `ScToggleSwitch`; the Catalogix row adds a search field and a checkbox list.
207
+ That is four DS components and ~120 lines of wiring you don't write.
208
+ - **Correct enabled→expanded behaviour.** The Catalogix store panel is mounted
209
+ only while the app is on, and the "N Selected" counter is derived from
210
+ `catalogixSelectedStores` — so the counter can't drift from the ticks.
211
+ - **One surface for the permission model.** When a fourth app ships, it lands
212
+ here once instead of in every host's invite modal.
213
+ - **Token-correct nesting.** The container uses `surface-basesubtle` with
214
+ `radius-3xl` and forces each row to `align-self: stretch`, which is exactly the
215
+ case that collapses when hand-rolled inside a constrained flex column.
216
+
217
+ ---
218
+
219
+ ## Gotchas
220
+
221
+ **1. Everything defaults to ON.** All three rows compute
222
+ `enabled !== undefined ? enabled : active === "true"`, and every `active` default
223
+ in play is `"true"` (Artifax's own default, Photogenix's own default, and
224
+ Catalogix's `active="true"` hardcoded by `ScAppField`).
225
+
226
+ ```tsx
227
+ // WRONG — renders all three apps enabled, Catalogix expanded
228
+ <ScAppField onArtifaxToggle={a} onPhotogenixToggle={p} onCatalogixToggle={c} />
229
+
230
+ // RIGHT — always pass the three booleans explicitly
231
+ <ScAppField
232
+ artifaxEnabled={false} onArtifaxToggle={a}
233
+ photogenixEnabled={false} onPhotogenixToggle={p}
234
+ catalogixEnabled={false} onCatalogixToggle={c}
235
+ />
236
+ ```
237
+
238
+ **2. Missing `catalogixStores` renders placeholder rows.** `ScCatalogixInvite`
239
+ falls back to five `ScPairtext` checkbox rows; since they get no `content`, each
240
+ one renders `ScPairtext`'s default label — the literal word **"Pairtext"**. Pass
241
+ `[]` for the real "No stores found" empty state.
242
+
243
+ **3. The search box does not filter.** It is an `ScOnlyField` reporting the query
244
+ via `onCatalogixSearchChange`. Filter `catalogixStores` yourself.
245
+
246
+ **4. `onCatalogixStoreToggle` is a signal, not a state update.** It receives only
247
+ the store id — you must toggle membership in `catalogixSelectedStores`.
248
+
249
+ **5. App names, descriptions and icons are not overridable here.** `ScAppField`
250
+ passes literal strings to the child rows (Photogenix gets
251
+ `"Info about photogenix"` and `SiconPhotogenix`; Artifax and Catalogix fall
252
+ through to their own placeholder copy). If the design has real copy, drop
253
+ `ScAppField` and compose the three `*Invite` components, which *do* expose the copy
254
+ props — `appName`/`appDescription`/`appIcon` on `ScArtifaxInvite` and
255
+ `ScCatalogixInvite`, but `scAppCardappName`/`scAppCardappDescription`/
256
+ `scAppCardappIcon` on `ScPhtogenixInvite` (which also does **not** extend
257
+ `HTMLAttributes`, so it spreads nothing onto its root).
258
+
259
+ **6. No width.** `.scAppField` is a flex column with `align-items: flex-start`
260
+ and **no `width: 100%`** (unlike `ScTextField`/`ScOnlyField`/`ScTextArea`). Inside
261
+ a constrained flex column it can shrink to content — pass `className="w-full
262
+ shrink-0"` as the CXO call sites do.
263
+
264
+ **7. The label row always renders,** even with `appFieldTitle=""` — you get an
265
+ empty 24 px row. There is no `labelGroup` switch.
266
+
267
+ **8. Hardcoded English throughout** (`"App permission"`, `"Store access"`,
268
+ `"N Selected"`, `"Search stores..."`, `"No stores found"`). Not localisable
269
+ without changing the DS.
270
+
271
+ ---
272
+
273
+ ## In the wild
274
+
275
+ ```tsx
276
+ // cxo-dashboard src/app/components/mobile-teams-invite-popup.tsx:162
277
+ <ScAppField
278
+ appFieldTitle="App permission"
279
+ artifaxEnabled={artifaxOn}
280
+ onArtifaxToggle={setArtifaxOn}
281
+ photogenixEnabled={photogenixOn}
282
+ onPhotogenixToggle={setPhotogenixOn}
283
+ catalogixEnabled={catalogixOn}
284
+ onCatalogixToggle={setCatalogixOn}
285
+ catalogixStores={stores}
286
+ catalogixSelectedStores={selectedStores}
287
+ onCatalogixStoreToggle={(id) => {
288
+ setSelectedStores((prev) =>
289
+ prev.includes(id) ? prev.filter((s) => s !== id) : [...prev, id]
290
+ );
291
+ }}
292
+ catalogixSearchValue={storeSearch}
293
+ onCatalogixSearchChange={setStoreSearch}
294
+ className="w-full shrink-0"
295
+ />
296
+ ```
297
+
298
+ ---
299
+
300
+ ## Related
301
+
302
+ - `ScArtifaxInvite` / `ScPhtogenixInvite` / `ScCatalogixInvite` — the three rows;
303
+ use them directly when you need real copy, a subset, or a different order.
304
+ - `ScAppCard` — the icon + name + description unit inside each row.
305
+ - `ScToggleSwitch` — the switch each row renders.
306
+ - `ScCheckField` / `ScTabField` / `ScTextField` — the sibling fields in the same
307
+ invite form.
308
+ - `ScPairtext` — what each store row is made of.
@@ -0,0 +1,271 @@
1
+ ---
2
+ component: ScAppListingCard
3
+ package: "@streamoid/ui"
4
+ category: cards
5
+ status: stable
6
+ renders: div
7
+ tags: [card, tile, app, product, listing, launcher, grid, service-catalog, cxo]
8
+ related: [ScAppCardV3, ScAppCard, ScDefaultCard, ScStoreCard, ScAppCardForCopilot]
9
+ do_not_confuse_with: [ScAppCard, ScAppCardV3, ScAppCardForCopilot, ScDefaultCard]
10
+ used_by: [cxo]
11
+ ---
12
+
13
+ # ScAppListingCard
14
+
15
+ **The big product tile in a CXO app-landing grid.** An icon in a rounded chip, a
16
+ title beside it, and a description line underneath — one 24px-padded card that
17
+ scales up and lights its icon chip with a purple→peach gradient on hover.
18
+
19
+ ## TL;DR for agents
20
+
21
+ - **Reach for it when:** you are building a grid of *entry points into a product*
22
+ (Catalogix / Photogenix / Artifax landing pages in CXO) and each tile is
23
+ icon + title + one-line description.
24
+ - **Don't reach for it when:** you need the compact app-switcher tile with the
25
+ gradient border and selected state (→ `ScAppCardV3`), the 200px icon+name row
26
+ inside a toggle field (→ `ScAppCard`), the dark glassy copilot card
27
+ (→ `ScAppCardForCopilot`), or a generic "pick one option" card
28
+ (→ `ScDefaultCard`).
29
+ - **Four things that will bite you:**
30
+ 1. The description prop is **`descrp`**, not `description`. Every sibling card
31
+ spells it differently. Typo it and you ship the default sentence.
32
+ 2. **All three content props have real defaults** — a missing `icon` renders a
33
+ 24px shopping cart, a missing `title` renders `"Stores"`.
34
+ 3. `cursor: pointer` is baked into the CSS whether or not you pass `onClick`,
35
+ and there is no `role`/`tabIndex` — you must add both yourself.
36
+ 4. `:hover` applies `transform: scale(1.03)`. In a tight grid the card grows
37
+ over its neighbours.
38
+
39
+ ---
40
+
41
+ ## 1. How to use it
42
+
43
+ ### Import
44
+
45
+ ```tsx
46
+ import { ScAppListingCard } from "@streamoid/ui";
47
+ import "@streamoid/ui/dist/index.css"; // once, at your app root
48
+ ```
49
+
50
+ ### Minimal usage
51
+
52
+ ```tsx
53
+ <ScAppListingCard
54
+ icon={<SiconStores size={24} />}
55
+ title="Stores"
56
+ descrp="Centralized product and asset management for every storefront"
57
+ />
58
+ ```
59
+
60
+ ### Props
61
+
62
+ | Prop | Type | Default | Notes |
63
+ |---|---|---|---|
64
+ | `icon` | `JSX.Element` | `<SiconCart />` | ⚠️ Has a real default — a 24px cart. Typed `JSX.Element`, **not** `ReactNode`, so a `ReactNode`-typed variable needs a cast. Rendered inside a padded chip that has no fixed size; the icon's own `size` decides the chip size. |
65
+ | `title` | `string` | `"Stores"` | ⚠️ Real default. `text-md-medium`, `flex: 1`, no truncation — long titles wrap. |
66
+ | `descrp` | `string` | `"Centralized product and asset management for every storefront"` | ⚠️ Real default, and ⚠️ **spelled `descrp`**. `text-sm-regular`, tertiary, no clamp — long copy grows the card. |
67
+ | `className` | `string` | – | Appended after the internal class. |
68
+ | `...props` | `HTMLAttributes<HTMLDivElement>` | – | `onClick`, `style`, `role`, `tabIndex`, `onKeyDown`, `aria-*` all land on the root div. |
69
+
70
+ ### Recipes
71
+
72
+ ```tsx
73
+ // The CXO idiom: a service-catalog entry, made clickable and keyboard-reachable
74
+ <ScAppListingCard
75
+ icon={<img src={item.imgUrl} alt={item.title} className="w-6 h-6" />}
76
+ title={item.title}
77
+ descrp={item.descrp}
78
+ role="button"
79
+ tabIndex={0}
80
+ style={{ width: "100%", height: "100%", cursor: item.pageUrl ? "pointer" : "default" }}
81
+ onClick={() => item.pageUrl && window.open(item.pageUrl, "_blank", "noopener,noreferrer")}
82
+ onKeyDown={(e) => {
83
+ if (e.key === "Enter" || e.key === " ") {
84
+ e.preventDefault();
85
+ item.pageUrl && window.open(item.pageUrl, "_blank", "noopener,noreferrer");
86
+ }
87
+ }}
88
+ />
89
+
90
+ // A non-clickable informational tile — kill the inherited pointer cursor
91
+ <ScAppListingCard
92
+ icon={<SiconInfo size={24} />}
93
+ title="Coming soon"
94
+ descrp="Asset pipelines land in the next release."
95
+ style={{ cursor: "default" }}
96
+ />
97
+
98
+ // Equal-height responsive grid (the card has no intrinsic width)
99
+ <div style={{ display: "grid", gridTemplateColumns: "repeat(auto-fill, minmax(280px, 1fr))", gap: 16 }}>
100
+ {cards.map((c) => (
101
+ <ScAppListingCard key={c.id} icon={c.icon} title={c.title} descrp={c.descrp}
102
+ style={{ width: "100%", height: "100%" }} />
103
+ ))}
104
+ </div>
105
+ ```
106
+
107
+ ---
108
+
109
+ ## 2. Where to use it
110
+
111
+ - **CXO's per-product landing pages** — `photogenix-content`, `catalogix-content`
112
+ and `artifax-content` each render a grid of these from the workspace's
113
+ `serviceCatalog`. This is the component's whole reason for existing.
114
+ - Any **app/product launcher grid** where a tile is one navigable destination.
115
+
116
+ It composes nothing from the DS — the icon is entirely yours. It is *not* rendered
117
+ inside any other DS component, so it never appears unless you place it.
118
+
119
+ ---
120
+
121
+ ## 3. When to use it
122
+
123
+ ### Use it when
124
+
125
+ - The tile is a **destination**, not a form control: clicking it navigates or opens.
126
+ - You want a description long enough to read (two-line body copy), which the
127
+ compact `ScAppCardV3` tile cannot carry as comfortably.
128
+ - The surface is a **page-level grid** with generous space (24px padding, 16px radius).
129
+
130
+ ### Don't use it — reach for this instead
131
+
132
+ | Situation | Use instead |
133
+ |---|---|
134
+ | The cross-product app switcher grid (5-up, gradient border, `active` tile) | `ScAppCardV3` |
135
+ | A 200px icon + name + subtitle row sitting next to a toggle | `ScAppCard` (that's what `ScAppField` / the `Sc*Invite` trio use) |
136
+ | A dark glassy floating card with a product wordmark and hover arrow | `ScAppCardForCopilot` |
137
+ | "Choose one of these options" in a modal/drawer, centred title + hint | `ScDefaultCard` |
138
+ | A store summary tile (name, product count, status beacon) | `ScStoreCard` |
139
+ | A row in a table rather than a tile in a grid | `ScTableList` / `ScTableHeader` |
140
+ | A workspace, not a product | `ScWorkspaceSwitchCard` / `StreamoidWorkspaceSwitcher` |
141
+
142
+ ### Don't confuse with
143
+
144
+ | You may actually want | Not this |
145
+ |---|---|
146
+ | `ScAppCardV3` — compact switcher tile, gradient hover border, `active` prop, `description` | `ScAppListingCard` is the large landing tile with `descrp` and a scale-up hover |
147
+ | `ScAppCard` — fixed `width: 12.5rem` horizontal row, prop is `appDescription` | This card is a full-width column tile |
148
+ | `ScAppCardForCopilot` — real `<button>`, requires `product`, renders a wordmark not an icon | This card is a `div` and takes an arbitrary `icon` |
149
+
150
+ Prop-name cheat sheet for the whole app-card family — the single biggest source of
151
+ silent "why is my text the default?" bugs:
152
+
153
+ | Component | Title prop | Description prop | Icon prop |
154
+ |---|---|---|---|
155
+ | `ScAppListingCard` | `title` | **`descrp`** | `icon` |
156
+ | `ScAppCard` | `appName` | `appDescription` | `appIcon` |
157
+ | `ScAppCardV3` | `appName` | `description` | `appIcon` |
158
+ | `ScAppCardForCopilot` | *(none — `product` wordmark)* | `subText` | *(none)* |
159
+ | `ScDefaultCard` | `title` | `description` | *(none)* |
160
+ | `ScBriefCard` | *(none)* | `description` | *(none)* |
161
+
162
+ ---
163
+
164
+ ## 4. Why to use it
165
+
166
+ - **Surface, border, radius and type ramp are tokenised** (`--alias-surface-base`,
167
+ `--alias-border-subtle`, `--text-text-md-medium-*`), so the tile is correct in
168
+ both themes without a conditional at the call site.
169
+ - **The hover choreography is already tuned** — background lift, border warm-up,
170
+ a 1.03 scale and the icon-chip gradient all transition together over 200ms.
171
+ Hand-rolled tiles in this grid historically drifted apart on every one of those.
172
+ - **One place to change the product-grid look.** CXO renders this on three landing
173
+ pages; a design tweak ships to all of them at once.
174
+ - **It spreads native div attributes**, so you can bolt on `role`, `tabIndex`,
175
+ `onKeyDown` and data attributes without wrapping it in another element that
176
+ would break the grid sizing.
177
+
178
+ ---
179
+
180
+ ## Gotchas
181
+
182
+ **1. `descrp`, not `description`.** TypeScript will not save you: an unknown extra
183
+ prop on a `HTMLAttributes`-extending interface is only caught for literal objects,
184
+ and even when it errors the runtime behaviour is "render the default sentence".
185
+
186
+ ```tsx
187
+ // WRONG — renders "Centralized product and asset management for every storefront"
188
+ <ScAppListingCard title="Assets" description="Manage your creative assets" />
189
+
190
+ // RIGHT
191
+ <ScAppListingCard title="Assets" descrp="Manage your creative assets" />
192
+ ```
193
+
194
+ **2. Every content prop has a real default.** Passing `icon={undefined}` (very easy
195
+ when your data model has an optional icon) re-triggers the default and ships a
196
+ shopping cart.
197
+
198
+ ```tsx
199
+ // WRONG — card.icon is optional; when it's undefined you get SiconCart
200
+ <ScAppListingCard icon={card.icon} title={card.title} descrp={card.descrp} />
201
+
202
+ // RIGHT — decide explicitly
203
+ <ScAppListingCard
204
+ icon={card.icon ?? <SiconStores size={24} />}
205
+ title={card.title}
206
+ descrp={card.descrp}
207
+ />
208
+ ```
209
+
210
+ **3. `icon` is typed `JSX.Element`, not `ReactNode`.** `ScAppCardV3.appIcon` *is*
211
+ `ReactNode`. If your card model types the icon as `ReactNode`, narrow or cast it
212
+ here — do not "fix" it by widening at the call site with `as any` and forgetting.
213
+
214
+ **4. It is not keyboard accessible on its own.** No `role`, no `tabIndex`, no key
215
+ handling — but the CSS sets `cursor: pointer` unconditionally, so it *looks*
216
+ clickable. Add `role="button" tabIndex={0} onKeyDown` whenever you pass `onClick`,
217
+ and override `cursor` when you don't.
218
+
219
+ **5. `:hover` scales the card to 1.03.** In a dense grid the hovered tile overlaps
220
+ its neighbours (there is no `z-index` management). Give the grid enough gap, or
221
+ override `transform` via `className` if the design does not want it.
222
+
223
+ **6. The hover accents are hardcoded, not tokens.** The hover border is `#ffa16b`
224
+ and the icon chip's hover fill is `linear-gradient(135deg, #6d00db, #ffb88c)`.
225
+ They are identical in dark and light mode — do not expect them to follow a theme
226
+ or a brand override.
227
+
228
+ **7. No truncation anywhere.** `title` wraps, `descrp` has no line clamp, and the
229
+ card has no fixed height. In a grid, either cap your copy or set
230
+ `height: 100%` on every card (as CXO does) so rows stay even.
231
+
232
+ **8. `className` is concatenated unguarded.** The root is
233
+ `styles.scAppListingCard + " " + className`, so when you omit `className` the DOM
234
+ gets a literal `undefined` class. Harmless in the browser, noisy in snapshot tests
235
+ and in DOM-diffing assertions.
236
+
237
+ **9. `title` and `descrp` render with a trailing space** (`{title} ` in the JSX).
238
+ Exact-string DOM assertions must allow for it.
239
+
240
+ ---
241
+
242
+ ## In the wild
243
+
244
+ ```tsx
245
+ // cxo-dashboard src/app/components/photogenix-content.tsx:56
246
+ <ScAppListingCard
247
+ icon={card.icon}
248
+ title={card.title}
249
+ descrp={card.descrp}
250
+ style={{
251
+ width: "100%",
252
+ height: "100%",
253
+ cursor: card.pageUrl ? "pointer" : "default",
254
+ }}
255
+ onClick={card.pageUrl ? handleClick : undefined}
256
+ />
257
+ ```
258
+
259
+ The same call shape appears in `src/app/components/catalogix-content.tsx:56` and
260
+ `src/app/components/artifax-content.tsx:56`.
261
+
262
+ ---
263
+
264
+ ## Related
265
+
266
+ - `ScAppCardV3` — the compact app-switcher tile (gradient border, `active` state).
267
+ - `ScAppCard` — the 200px icon+name row used inside `ScAppField`'s invite rows.
268
+ - `ScAppCardForCopilot` — dark glassy wordmark card, a real `<button>`.
269
+ - `ScDefaultCard` — centred "choose an option" card with a forced `hover` state.
270
+ - `ScStoreCard` — the store-listing equivalent of this tile.
271
+ - `@streamoid/icons` — the `Sicon*` you pass as `icon`; see `packages/icons/ICONS.md`.