@streamoid/ui 0.6.17 → 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 +36 -36
  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,396 @@
1
+ ---
2
+ component: ScProfilePopup
3
+ package: "@streamoid/ui"
4
+ category: profile
5
+ status: stable
6
+ renders: div
7
+ tags: [profile-menu, profile-dropdown, account-menu, logout, theme-switcher, credits, switch-workspace, panel, slots]
8
+ related: [ScProfile, ScWorkspace, ScMenuOptions, ScTabSwitcher, ScWorkspaceSwitchCard, StreamoidSidebar]
9
+ do_not_confuse_with: [ScProfileOptions, ScProfileSettingsComp, ScProfile, ScPopUpMenu, ScAppSwitchPanel]
10
+ used_by: [catalogix]
11
+ ---
12
+
13
+ # ScProfilePopup
14
+
15
+ **The profile dropdown panel — the whole thing, propped and wired.** Identity header
16
+ with a logout button, a workspace block with a credits pill and "Switch workspace", a
17
+ four-row menu (Teams / Billing / All settings / What's new + version), and a
18
+ three-way theme switcher. It is the **panel body only**: it renders no overlay, owns
19
+ no open state, and forwards its ref so the host can position it and detect
20
+ outside clicks — the same split `StreamoidSidebar` uses for `switchPanel`.
21
+
22
+ ## TL;DR for agents
23
+
24
+ - **Reach for it when:** you need the app's profile/account menu. This is the one.
25
+ - **Don't reach for it when:** you want the static Figma export of the same node
26
+ (→ `ScProfileOptions`, legacy), the account **form** (→ `ScProfileImageUpdate` +
27
+ `ScTextField`), or a generic anchored popover (→ `ScPopUpMenu`).
28
+ - **Five things that will bite you:**
29
+ 1. **No overlay, no open state, no dismissal.** You render it conditionally,
30
+ position it, and wire outside-click yourself via the forwarded `ref`.
31
+ 2. Omit `avatar` / `workspaceImage` and the nested `ScProfile` / `ScWorkspace`
32
+ fall back to `profileImage="profile-image0.png"` → **two broken-image glyphs**.
33
+ 3. Identity defaults are placeholders: **"Chris Hemsworth" / "chris@kepler.com" /
34
+ "Kepler workspace" / "Admin"**.
35
+ 4. `themeMode` is **controlled** — no internal state. Without
36
+ `onThemeModeChange` the tabs are decorative.
37
+ 5. Width is `20rem` via the `--sc-profile-popup-width` custom property, and every
38
+ **direct child gets `width: unset !important`** — including whatever you put in
39
+ the slots.
40
+
41
+ ---
42
+
43
+ ## 1. How to use it
44
+
45
+ ### Import
46
+
47
+ ```tsx
48
+ import { ScProfilePopup, type ScProfilePopupThemeMode } from "@streamoid/ui";
49
+ import "@streamoid/ui/dist/index.css"; // once, at your app root
50
+ ```
51
+
52
+ ### Minimal usage
53
+
54
+ ```tsx
55
+ <ScProfilePopup
56
+ name={user.name}
57
+ email={user.email}
58
+ avatar={user.avatarUrl}
59
+ workspaceName={ws.name}
60
+ userRole={ws.role}
61
+ onLogout={logout}
62
+ />
63
+ ```
64
+
65
+ Wired as a real dropdown (the host owns open/close):
66
+
67
+ ```tsx
68
+ const panelRef = useRef<HTMLDivElement>(null);
69
+
70
+ useEffect(() => {
71
+ if (!open) return;
72
+ const onDown = (e: MouseEvent) => {
73
+ if (!panelRef.current?.contains(e.target as Node)) setOpen(false);
74
+ };
75
+ document.addEventListener("mousedown", onDown);
76
+ return () => document.removeEventListener("mousedown", onDown);
77
+ }, [open]);
78
+
79
+ {open && (
80
+ <div style={{ position: "absolute", bottom: 8, left: 8, zIndex: 50 }}>
81
+ <ScProfilePopup ref={panelRef} role="menu" aria-label="Profile menu" /* …data… */ />
82
+ </div>
83
+ )}
84
+ ```
85
+
86
+ ### Props
87
+
88
+ **Identity (header)**
89
+
90
+ | Prop | Type | Default | Notes |
91
+ |---|---|---|---|
92
+ | `name` | `string` | `"Chris Hemsworth"` | ⚠️ Real default. First header line. |
93
+ | `email` | `string` | `"chris@kepler.com"` | ⚠️ Real default. Second header line (passed as `ScProfile`'s `subText`). |
94
+ | `avatar` | `string` | – | ⚠️ Spread only when truthy; omit it and `ScProfile`'s broken `profile-image0.png` default renders. Forced to 32px. |
95
+ | `onProfileClick` | `() => void` | – | Makes the identity block a real `<button>` **and** enables its hover fill + focus ring. Without it the block is a non-interactive div by design. |
96
+ | `onLogout` | `() => void` | – | The error-coloured icon button top-right. |
97
+ | `logoutLabel` | `string` | `"Log out"` | Its `aria-label` **and** `title`. Not visible text. |
98
+ | `showLogout` | `boolean` | `true` | ⚠️ Defaults on. Set `false` to hide the icon. |
99
+ | `headerActions` | `ReactNode` | – | **SLOT** — extra trailing controls, in a row left of the logout button. |
100
+
101
+ **Workspace block**
102
+
103
+ | Prop | Type | Default | Notes |
104
+ |---|---|---|---|
105
+ | `workspaceName` | `string` | `"Kepler workspace"` | ⚠️ Real default. |
106
+ | `userRole` | `string` | `"Admin"` | ⚠️ Real default. Free text, tertiary grey — no `ScRole` colour semantics. |
107
+ | `workspaceImage` | `string` | – | ⚠️ Spread only when truthy; same broken-default trap as `avatar`. Forced to 32px. |
108
+ | `credits` | `ReactNode` | – | Credits pill (icon + value). **Omit to hide the pill entirely**; `credits={0}` still renders (only `null`/`undefined` hide it). |
109
+ | `onSwitchWorkspace` | `() => void` | – | The "Switch workspace" button. |
110
+ | `switchWorkspaceLabel` | `string` | `"Switch workspace"` | Visible button label. |
111
+ | `showWorkspace` | `boolean` | `true` | Hides the identity+credits row only. |
112
+ | `showSwitchWorkspace` | `boolean` | `true` | Hides the button only. |
113
+ | `workspaceContent` | `ReactNode` | – | **SLOT** — appended below "Switch workspace", inside the 8px inset rail. |
114
+
115
+ **Menu rows**
116
+
117
+ | Prop | Type | Default | Notes |
118
+ |---|---|---|---|
119
+ | `onTeams` / `onBilling` / `onSettings` / `onWhatsNew` | `() => void` | – | Row handlers. A row with no handler still renders and still looks clickable. |
120
+ | `teamsLabel` | `string` | `"Teams"` | |
121
+ | `billingLabel` | `string` | `"Billing"` | |
122
+ | `settingsLabel` | `string` | `"All settings"` | |
123
+ | `whatsNewLabel` | `string` | `"What's new"` | |
124
+ | `version` | `string` | – | Trailing version tag on the What's-new row. Its presence flips that row to `ScMenuOptions` `variant="with-v"`; absent, no version shows. Pass the `"v"` yourself (`` `v${APP_VERSION}` ``). |
125
+ | `showTeams` / `showBilling` / `showSettings` / `showWhatsNew` | `boolean` | `true` | ⚠️ All default **on**. |
126
+ | `menuContent` | `ReactNode` | – | **SLOT** — appended below the built-in rows, same stack and gap. |
127
+
128
+ **Theme switcher, footer, root**
129
+
130
+ | Prop | Type | Default | Notes |
131
+ |---|---|---|---|
132
+ | `themeMode` | `"system"` \| `"light"` \| `"dark"` | `"system"` | Controlled. Exported as `ScProfilePopupThemeMode`. |
133
+ | `onThemeModeChange` | `(mode) => void` | – | Fires on click/Enter/Space. Without it the tabs do nothing. |
134
+ | `showThemeSwitcher` | `boolean` | `true` | |
135
+ | `preFooterContent` | `ReactNode` | – | **SLOT** — between the menu group and the theme switcher (e.g. `CreditWarningBanner`). |
136
+ | `children` | `ReactNode` | – | **SLOT** — after the theme switcher, at the very bottom. |
137
+ | `className` | `string` | – | Appended only when provided (this component does it correctly). |
138
+ | `ref` | `Ref<HTMLDivElement>` | – | The **panel root** — use it for placement and outside-click checks. |
139
+ | `...props` | `HTMLAttributes<HTMLDivElement>` | – | `role`, `aria-label`, `style`, `data-*` spread onto the root. |
140
+
141
+ ### Which blocks render
142
+
143
+ | Block | Rendered when | Divider above it |
144
+ |---|---|---|
145
+ | Header | always | — |
146
+ | Workspace | `showWorkspace \|\| showSwitchWorkspace \|\| workspaceContent` | yes |
147
+ | Menu | `showTeams \|\| showBilling \|\| showSettings \|\| showWhatsNew \|\| menuContent` | yes |
148
+ | `preFooterContent` | when passed | no |
149
+ | Theme switcher | `showThemeSwitcher` | no |
150
+ | `children` | when passed | no |
151
+
152
+ ### Recipes
153
+
154
+ ```tsx
155
+ // Full production wiring
156
+ <ScProfilePopup
157
+ ref={panelRef}
158
+ role="menu"
159
+ aria-label="Profile menu"
160
+ name={user.name || user.email}
161
+ email={user.email}
162
+ avatar={user.imageUrl || undefined}
163
+ onProfileClick={() => go("account")}
164
+ onLogout={() => void logout()}
165
+ workspaceName={ws.name}
166
+ userRole={ws.role}
167
+ workspaceImage={ws.imageUrl || undefined}
168
+ credits={creditsLoading ? "…" : credits.toLocaleString()}
169
+ onSwitchWorkspace={openWorkspaceSwitcher}
170
+ onTeams={() => go("team")}
171
+ onBilling={() => go("billing")}
172
+ onSettings={() => go("account")}
173
+ onWhatsNew={() => window.open(CHANGELOG_URL, "_blank")}
174
+ version={`v${APP_VERSION}`}
175
+ themeMode={themePreference}
176
+ onThemeModeChange={setThemePreference}
177
+ />
178
+
179
+ // Trimmed panel: identity + logout + theme only
180
+ <ScProfilePopup
181
+ name={user.name}
182
+ email={user.email}
183
+ avatar={user.avatarUrl}
184
+ showWorkspace={false}
185
+ showSwitchWorkspace={false}
186
+ showTeams={false}
187
+ showBilling={false}
188
+ showSettings={false}
189
+ showWhatsNew={false}
190
+ onLogout={logout}
191
+ themeMode={theme}
192
+ onThemeModeChange={setTheme}
193
+ />
194
+
195
+ // Extra rows + a banner, via slots (each stack stretches its children)
196
+ <ScProfilePopup
197
+ /* …identity… */
198
+ menuContent={<ScMenuOptions icon={<SiconCatalog size={24} />} text="Catalogs" />}
199
+ preFooterContent={<CreditWarningBanner /* … */ />}
200
+ />
201
+
202
+ // Stretch to a container instead of the 320px default
203
+ <ScProfilePopup style={{ ["--sc-profile-popup-width" as string]: "100%" }} /* … */ />
204
+ ```
205
+
206
+ ---
207
+
208
+ ## 2. Where to use it
209
+
210
+ - **Sidebar profile footer** — anchored above the profile row in the desktop shells.
211
+ Catalogix renders it from `app/components/AppSettingsPopup`, which its own header
212
+ comment describes as replacing ~430 lines of hand-assembled panel.
213
+ - **Top-bar avatar menus** — same panel, positioned bottom-right of the trigger.
214
+ - Pair `onSwitchWorkspace` with the workspace-switch modal
215
+ (`ScWorkspaceSwitchCard` rows) rather than nesting a switcher inside the popup.
216
+
217
+ Composed internally from `ScProfile`, `ScWorkspace`, `ScHDivider`, `ScMenuOptions`,
218
+ `ScButton` and `ScTabSwitcher`/`ScTabComp` — do not rebuild those inside your slots.
219
+
220
+ ---
221
+
222
+ ## 3. When to use it
223
+
224
+ ### Use it when
225
+
226
+ - You need the standard profile menu with any subset of its rows — the `show*` flags
227
+ let you drop blocks without forking the panel.
228
+ - You want theme switching that matches the tokens contract (write the mode to your
229
+ own store; the host turns it into `<html data-theme>`).
230
+
231
+ ### Don't use it — reach for this instead
232
+
233
+ | Situation | Use instead |
234
+ |---|---|
235
+ | Static Figma-parity screenshot of this node | `ScProfileOptions` (legacy, className-only) |
236
+ | The account settings **form** | `ScProfileImageUpdate` + `ScTextField` |
237
+ | A generic anchored menu of arbitrary options | `ScPopUpMenu` + `ScMenuOptions` |
238
+ | The cross-product app switcher | `ScAppSwitchPanel` (pass via `StreamoidSidebar`'s `switchPanel`) |
239
+ | A workspace **list/switcher** UI | `ScWorkspaceSwitchCard` rows in your own modal |
240
+ | Just the identity row | `ScProfile` |
241
+ | Mobile profile surface | `ScProfileV2Mobile` + the `*Mobile` family |
242
+
243
+ ### Don't confuse with
244
+
245
+ | You may actually want | Not this |
246
+ |---|---|
247
+ | `ScProfileOptions` — the static export of this very Figma node (no props, no callbacks) | `ScProfilePopup` is the live one |
248
+ | `ScProfileSettingsComp` — the account form card | this is the menu |
249
+ | `ScProfile` — the identity row this renders as its header | this is the whole panel |
250
+ | `ScAppSwitchPanel` — switches **products**, not accounts | different panel, different slot in the sidebar |
251
+ | `ScPopUpMenu` — owns anchoring/positioning | this owns neither; the host positions it |
252
+
253
+ And on the other axis: this is a **panel body**, not a popover. No `open` prop, no
254
+ portal, no backdrop, no Escape handling. If you wanted something that manages its own
255
+ visibility, you want your own conditional render (or `ScPopUpMenu`).
256
+
257
+ ---
258
+
259
+ ## 4. Why to use it
260
+
261
+ - **Every row is already keyboard-reachable.** `ScMenuOptions` and `ScTabComp` are
262
+ divs; this component adds `role="button"`, `tabIndex={0}` and Enter/Space handling to
263
+ each row, `role="radiogroup"`/`role="radio"`+`aria-checked` to the theme tabs, and
264
+ `:focus-visible` rings throughout. Hand-assembling the panel loses all of it.
265
+ - **Two icon-colour bugs are fixed here, not in your app.** `ScMenuOptions` colours
266
+ only its label (its icon is a sibling that inherits `color`), and `ScTabComp`
267
+ recolours only `.tabText` when active — so a naive assembly renders muted icons
268
+ beside near-white labels and a light icon on a light active tab. The panel drives
269
+ both from `currentColor`.
270
+ - **Figma-exact geometry without borders that shift.** The panel, logout button and
271
+ credits pill use `box-shadow: inset` instead of borders because Figma strokes these
272
+ frames on the inside — a real border would add a pixel and push every divider out
273
+ of alignment.
274
+ - **Slots that lay out correctly for free.** `.scProfilePopup > *` and each stack
275
+ force `align-self: stretch; width: unset`, so nested DS components (which carry
276
+ intrinsic widths like `ScMenuOptions`' 200px) stretch like the built-in rows with
277
+ no extra CSS.
278
+ - **One panel, four apps.** Row labels, visibility and version are props, so per-app
279
+ differences don't need a fork.
280
+
281
+ ---
282
+
283
+ ## Gotchas
284
+
285
+ **1. It renders no overlay and owns no state.** There is no `open`, no backdrop, no
286
+ Escape key, no portal. Conditionally render it, position the wrapper, and use the
287
+ forwarded ref for outside-click.
288
+
289
+ ```tsx
290
+ // WRONG — always visible, unpositioned, undismissable
291
+ <ScProfilePopup name={user.name} />
292
+
293
+ // RIGHT
294
+ {open && <div style={{ position: "absolute", bottom: 8, left: 8, zIndex: 50 }}>
295
+ <ScProfilePopup ref={panelRef} name={user.name} />
296
+ </div>}
297
+ ```
298
+
299
+ **2. Missing `avatar`/`workspaceImage` = broken images.** Both are spread
300
+ conditionally (`{...(avatar ? { profileImage: avatar } : {})}`), so a falsy value
301
+ lets `ScProfile`/`ScWorkspace`'s `"profile-image0.png"` default through. Pass
302
+ `|| undefined` — not `|| ""` — and prefer a real fallback URL.
303
+
304
+ ```tsx
305
+ // WRONG — "" is falsy, so the broken default renders anyway
306
+ <ScProfilePopup avatar={user.imageUrl || ""} />
307
+
308
+ // RIGHT
309
+ <ScProfilePopup avatar={user.imageUrl || undefined} />
310
+ ```
311
+
312
+ **3. Placeholder identity ships if you forget a prop.** "Chris Hemsworth",
313
+ "chris@kepler.com", "Kepler workspace", "Admin". Pass `""` deliberately when a value
314
+ is genuinely unknown — the hosts do exactly that.
315
+
316
+ **4. All eight `show*` flags default to `true`.** Every block renders unless you
317
+ explicitly turn it off, and a row with no handler still looks clickable
318
+ (`cursor: pointer` comes from `.menuOptionInstance`, not from the handler). Either
319
+ wire the handler or set the flag to `false`.
320
+
321
+ **5. `themeMode` is controlled.** No internal state. Clicking a tab calls
322
+ `onThemeModeChange` and nothing else — the active tab only moves when you re-render
323
+ with a new `themeMode`.
324
+
325
+ **6. `width` overrides need the custom property.** The panel is
326
+ `width: var(--sc-profile-popup-width, 20rem)`. An inline `style={{ width: "100%" }}`
327
+ does win (inline beats the class), but the documented lever is the property:
328
+ `style={{ ["--sc-profile-popup-width"]: "100%" }}`. Note the panel's `> *` rule
329
+ applies `width: unset !important` to **children**, so a child cannot set its own width
330
+ — that is deliberate, but it means a fixed-width component in a slot will stretch.
331
+
332
+ **7. `credits` uses a `!= null` check.** `credits={0}` renders a pill reading "0";
333
+ only `null`/`undefined` hide it. Pass `undefined` while loading, or a placeholder
334
+ string like `"…"` (what Catalogix does).
335
+
336
+ **8. `logoutLabel` is not visible text.** It is the `aria-label` and `title` of an
337
+ icon-only button. Setting it does not add a "Log out" row — the design has none.
338
+
339
+ **9. `userRole` is unstyled free text.** No admin/member colouring here; if you want
340
+ the coloured pill, use `ScRole` in `workspaceContent`.
341
+
342
+ **10. The "Switch workspace" hover fill is darker than its resting fill.** That is
343
+ per the design node, implemented deliberately (`neutralactive` → `neutralselected`),
344
+ and it is restated with `!important` because the resting `!important` would otherwise
345
+ kill `ScButton`'s own hover. Don't "fix" it without a design decision.
346
+
347
+ **11. `backdrop-filter: blur(42px)` needs something behind it.** Over an opaque host
348
+ background the blur is invisible; the panel then reads as flat `surface-subtle`.
349
+
350
+ **12. `role="menu"` is yours to add.** The root is a plain `div` — the hosts pass
351
+ `role="menu"` and `aria-label` through the spread. Note that the rows are
352
+ `role="button"`, not `role="menuitem"`, so a strict `menu`/`menuitem` pairing is not
353
+ what you get.
354
+
355
+ ---
356
+
357
+ ## In the wild
358
+
359
+ ```jsx
360
+ // catalogix/dashboard app/components/AppSettingsPopup/index.jsx:75
361
+ <ScProfilePopup
362
+ data-theme="Mode 1"
363
+ role="menu"
364
+ aria-label="Profile menu"
365
+ style={{ width: "100%" }}
366
+ name={(userData && (userData.name || userData.email)) || ""}
367
+ email={(userData && userData.email) || ""}
368
+ avatar={userData?.imageUrl || undefined}
369
+ onProfileClick={() => goSettings("account")}
370
+ onLogout={() => { logoutUser().then(() => clearUserData()); }}
371
+ workspaceName={selectedWorkspace?.name || ""}
372
+ userRole={selectedWorkspace?.role || ""}
373
+ workspaceImage={selectedWorkspace?.imageUrl || undefined}
374
+ credits={creditsLabel}
375
+ onSwitchWorkspace={() => showWorkspaceList()}
376
+ onTeams={() => goSettings("team")}
377
+ onBilling={() => goSettings("billing")}
378
+ onSettings={() => goSettings("account")}
379
+ onWhatsNew={() => { onClose(); window.open(CHANGELOG_URL, "_blank"); }}
380
+ version={appVersion ? `v${appVersion}` : ""}
381
+ themeMode={themePreference}
382
+ onThemeModeChange={setThemePreference}
383
+ />
384
+ ```
385
+
386
+ ---
387
+
388
+ ## Related
389
+
390
+ - `ScProfileOptions` — the legacy static export of the same Figma node; migrate off it.
391
+ - `ScProfile` / `ScWorkspace` — the two identity rows it composes (`ScWorkspace` is not exported).
392
+ - `ScMenuOptions` — one menu row; use it in `menuContent` for extra rows.
393
+ - `ScTabSwitcher` / `ScTabComp` — the theme switcher pair.
394
+ - `ScWorkspaceSwitchCard` — the rows for the modal `onSwitchWorkspace` should open.
395
+ - `CreditWarningBanner` — the usual `preFooterContent`.
396
+ - `StreamoidSidebar` — the shell this panel is anchored inside; same host-owns-visibility pattern as `switchPanel`.
@@ -0,0 +1,250 @@
1
+ ---
2
+ component: ScProfileSettingsComp
3
+ package: "@streamoid/ui"
4
+ category: profile
5
+ status: legacy
6
+ renders: div
7
+ tags: [profile-settings, account-form, first-name, last-name, email, figma-export, static, read-only]
8
+ related: [ScProfileImageUpdate, ScTextField, ScSettingsTabComp, ScProfilePopup, ScAppField]
9
+ do_not_confuse_with: [ScProfileOptions, ScProfilePopup, ScProfileImageUpdate, ScProfile, ScSettingsTabComp]
10
+ ---
11
+
12
+ # ScProfileSettingsComp
13
+
14
+ **A static Figma scaffold of the account-settings card, not a working form.** It
15
+ renders `ScProfileImageUpdate` beside three `ScTextField`s (First Name / Last Name /
16
+ Email ID) — but the three values arrive as **placeholders** on `state="filled"`
17
+ fields, there is no `onChange` anywhere, and `ScProfileImageUpdate` is rendered with
18
+ no props at all. Nothing it renders can be edited or submitted.
19
+
20
+ ## TL;DR for agents
21
+
22
+ - **Reach for it when:** you want a pixel reference for the account card's layout
23
+ (24px padding, avatar column + 16px-gapped field stack).
24
+ - **Don't reach for it when:** you are building the real settings screen — compose
25
+ `ScProfileImageUpdate` + `ScTextField` yourself. That is what CXO and
26
+ `@streamoid/settings` do.
27
+ - **Four things that will bite you:**
28
+ 1. `firstName` / `lastName` / `email` are passed as `placeholderFilled`, so they
29
+ are **placeholder text, not input values**. Nothing you type is kept.
30
+ 2. There is **no `onChange`, no `onSubmit`, no `onUpload`, no `onDelete`** — no
31
+ callback of any kind.
32
+ 3. Defaults are placeholder identity: **"Chris" / "Hemsworth" /
33
+ "chris@kepler.com"**.
34
+ 4. The email field renders a hardcoded **`SiconLock`** on the right and the two
35
+ name fields render **no icon at all** (`tfGroup="none"` discards the icons the
36
+ component passes them). None of it is configurable.
37
+
38
+ ---
39
+
40
+ ## 1. How to use it
41
+
42
+ ### Import
43
+
44
+ ```tsx
45
+ import { ScProfileImageUpdate, ScTextField } from "@streamoid/ui"; // ← what to build with
46
+ import { ScProfileSettingsComp } from "@streamoid/ui"; // legacy scaffold
47
+ import "@streamoid/ui/dist/index.css"; // once, at your app root
48
+ ```
49
+
50
+ ### Minimal usage
51
+
52
+ The whole API:
53
+
54
+ ```tsx
55
+ <ScProfileSettingsComp
56
+ firstName={user.firstName}
57
+ lastName={user.lastName}
58
+ email={user.email}
59
+ />
60
+ ```
61
+
62
+ The working equivalent — the same layout with real state:
63
+
64
+ ```tsx
65
+ <div style={{
66
+ display: "flex", gap: "1.5rem", padding: "1.5rem",
67
+ borderRadius: "1rem", background: "var(--alias-surface-base)",
68
+ }}>
69
+ <ScProfileImageUpdate
70
+ imageUrl={user.avatarUrl}
71
+ initials={initials}
72
+ onUpload={(file) => void uploadAvatar(file)}
73
+ onDelete={() => void removeAvatar()}
74
+ />
75
+ <div style={{ display: "flex", flexDirection: "column", gap: "1rem", flex: 1 }}>
76
+ <div style={{ display: "flex", gap: "1rem" }}>
77
+ <ScTextField
78
+ label="First Name"
79
+ value={firstName}
80
+ onChange={(e) => setFirstName(e.target.value)}
81
+ style={{ flex: 1 }}
82
+ />
83
+ <ScTextField
84
+ label="Last Name"
85
+ value={lastName}
86
+ onChange={(e) => setLastName(e.target.value)}
87
+ style={{ flex: 1 }}
88
+ />
89
+ </div>
90
+ <ScTextField label="Email ID" value={email} state="disabled" readOnly />
91
+ </div>
92
+ </div>
93
+ ```
94
+
95
+ ### Props
96
+
97
+ | Prop | Type | Default | Notes |
98
+ |---|---|---|---|
99
+ | `firstName` | `string` | `"Chris"` | ⚠️ Real default. Passed as `placeholderFilled` to the First Name field — **placeholder, not value**. |
100
+ | `lastName` | `string` | `"Hemsworth"` | ⚠️ Real default. Same placeholder treatment. |
101
+ | `email` | `string` | `"chris@kepler.com"` | ⚠️ Real default. Same placeholder treatment. |
102
+ | `className` | `string` | – | Appended (concatenated unconditionally — a literal `undefined` class when omitted). |
103
+ | `...props` | `HTMLAttributes<HTMLDivElement>` | – | Spread onto the card root div. |
104
+
105
+ No callbacks, no `avatar`/`imageUrl`, no labels, no field state. That is the whole
106
+ surface.
107
+
108
+ ### What it renders (fixed)
109
+
110
+ | Region | Fixed content |
111
+ |---|---|
112
+ | Left column | `ScProfileImageUpdate` with **no props** → no image, no initials, dead upload/delete |
113
+ | Field row | `ScTextField` "First Name" + "Last Name", `state="filled"`, `tfGroup="none"` → **no icons rendered** |
114
+ | Below | `ScTextField` "Email ID", **no `state`** (so default, not `filled`), `tfGroup` left at its `"icon-right"` default → renders `SiconLock` on the right only |
115
+ | Card | `surface-base`, `radius: 1rem`, `padding: 1.5rem`, row layout, `gap: 1.5rem` |
116
+
117
+ ### Recipes
118
+
119
+ There is nothing to configure — see the working equivalent above.
120
+
121
+ ---
122
+
123
+ ## 2. Where to use it
124
+
125
+ Nowhere new. The real account screen lives in **`@streamoid/settings`**, which CXO
126
+ consumes; its avatar control is `ScProfileImageUpdate` used directly (CXO also
127
+ renders `ScProfileImageUpdate` itself in `src/app/components/mobile-settings-content.tsx:252`).
128
+ Keep this component only as a layout reference next to `ScSettingsTabComp` /
129
+ `ScSettingsNav`.
130
+
131
+ ---
132
+
133
+ ## 3. When to use it
134
+
135
+ ### Use it when
136
+
137
+ - You need the Figma measurements of the account card and nothing else.
138
+
139
+ ### Don't use it — reach for this instead
140
+
141
+ | Situation | Use instead |
142
+ |---|---|
143
+ | A real, editable account form | `ScProfileImageUpdate` + `ScTextField` composed by hand (see recipe) |
144
+ | Just the avatar upload/delete control | `ScProfileImageUpdate` |
145
+ | A labelled field bound to app config | `ScAppField` |
146
+ | The settings page tab strip / left nav | `ScSettingsTabComp` / `ScSettingsNav` |
147
+ | The profile **dropdown menu** | `ScProfilePopup` |
148
+ | A read-only identity row | `ScProfile` |
149
+ | Mobile settings surface | `ScProfileV2Mobile` + the `*Mobile` field set |
150
+
151
+ ### Don't confuse with
152
+
153
+ | You may actually want | Not this |
154
+ |---|---|
155
+ | `ScProfileOptions` / `ScProfilePopup` — the profile **menu** panel | this is the **form** card |
156
+ | `ScProfileImageUpdate` — the avatar upload control (real callbacks) | this wraps it but passes it nothing |
157
+ | `ScProfile` — avatar + name + subtext row | this is a settings card |
158
+ | `ScSettingsTabComp` — the settings page scaffold (tabs) | this is one card inside such a page |
159
+
160
+ Four `ScProfile*` names, four different things: **`ScProfile`** = identity row ·
161
+ **`ScProfileOptions`** = static menu · **`ScProfilePopup`** = live menu ·
162
+ **`ScProfileSettingsComp`** = static form card.
163
+
164
+ ---
165
+
166
+ ## 4. Why to use it
167
+
168
+ The only durable value is the **layout spec** it encodes, which you should copy into
169
+ your own composition:
170
+
171
+ - Card: `--alias-surface-base`, `border-radius: 1rem`, `padding: var(--spacing-6xl)`
172
+ (1.5rem), horizontal flex, `gap: 1.5rem`.
173
+ - Avatar column `flex-shrink: 0`; field column `flex: 1`, vertical, `gap: 1rem`.
174
+ - Name fields side by side, each `flex: 1; width: unset`, `gap: var(--spacing-3xl)`
175
+ (1rem); email field stretched full width.
176
+
177
+ Everything else — field behaviour, validation, upload — you have to supply, at which
178
+ point composing the two primitives directly is strictly less work than fighting this
179
+ wrapper.
180
+
181
+ ---
182
+
183
+ ## Gotchas
184
+
185
+ **1. The values are placeholders.** `placeholderFilled={firstName}` puts your data in
186
+ `ScTextField`'s placeholder slot on a `state="filled"` field. It looks like a value
187
+ and behaves like a hint; it is never an input value and it never round-trips.
188
+
189
+ ```tsx
190
+ // WRONG — looks bound, saves nothing, and typing does not update state
191
+ <ScProfileSettingsComp firstName={form.firstName} lastName={form.lastName} email={form.email} />
192
+
193
+ // RIGHT — own the state
194
+ <ScTextField label="First Name" value={form.firstName} onChange={(e) => set("firstName", e.target.value)} />
195
+ ```
196
+
197
+ **2. `ScProfileImageUpdate` gets no props.** No `imageUrl`, no `initials`, no
198
+ `onUpload`, no `onDelete` — the avatar renders empty and the upload/delete affordances
199
+ are inert.
200
+
201
+ **3. Placeholder identity ships to users.** Omit all three props and the card reads
202
+ "Chris / Hemsworth / chris@kepler.com".
203
+
204
+ **4. Most of the icons it passes are dead, and the one that renders is hardcoded.**
205
+ All three fields are handed a `SiconBolt` for both slots, but `ScTextField` gates
206
+ icons on `tfGroup`: the name fields use `tfGroup="none"` so **neither icon renders**,
207
+ and the email field leaves `tfGroup` at its `"icon-right"` default so **only
208
+ `SiconLock`** renders. There is no prop to change or remove it. Passing decorative
209
+ bolts to name fields is itself a sign this is an unfinished export.
210
+
211
+ **5. Labels are hardcoded English** — "First Name", "Last Name", "Email ID". No label
212
+ props; unusable in a localised surface without changing the DS.
213
+
214
+ **6. The email field's `state` is inconsistent.** The two name fields are
215
+ `state="filled"`; the email field is left at `ScTextField`'s default. The three fields
216
+ therefore do not render as a matched set.
217
+
218
+ **7. `className` is concatenated unconditionally** → `class="scProfileSettingsComp undefined"`
219
+ when omitted.
220
+
221
+ **8. The card has no `max-width` and is a `row` flex.** In a narrow settings column
222
+ the avatar and fields will squeeze rather than wrap.
223
+
224
+ ---
225
+
226
+ ## In the wild
227
+
228
+ _No host render site found — used by the agent runtime / composed internally._
229
+
230
+ No app renders it. The primitive it wraps is used directly:
231
+
232
+ ```tsx
233
+ // cxo-dashboard src/app/components/mobile-settings-content.tsx:252
234
+ <ScProfileImageUpdate
235
+ ```
236
+
237
+ The real desktop account screen lives in `@streamoid/settings`
238
+ (`packages/settings/src/`), which composes `ScProfileImageUpdate` and `ScTextField`
239
+ itself. If this component is ever revived it belongs there, behind real
240
+ `value`/`onChange` props.
241
+
242
+ ---
243
+
244
+ ## Related
245
+
246
+ - `ScProfileImageUpdate` — the avatar control, with `imageUrl`/`initials`/`onUpload`/`onDelete`.
247
+ - `ScTextField` — the field primitive; use `value` + `onChange`, not `placeholderFilled`.
248
+ - `ScAppField` — label + control unit for settings/config forms.
249
+ - `ScSettingsTabComp` / `ScSettingsNav` — the settings page scaffold this card sits in.
250
+ - `ScProfilePopup` — the profile menu (different component entirely).