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