@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,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).
|