@streamoid/ui 0.6.17 → 0.6.19
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +35 -18
- package/dist/docs/AGENTS.md +325 -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 +210 -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/ScWorkspaceAccountMenu.md +115 -0
- package/dist/docs/ScWorkspaceCard.md +234 -0
- package/dist/docs/ScWorkspaceSettingsMobile.md +265 -0
- package/dist/docs/ScWorkspaceSwitchCard.md +312 -0
- package/dist/docs/ScWorkspaceSwitchMobile.md +241 -0
- package/dist/docs/ScWorkspaceSwitchMobileV2.md +278 -0
- package/dist/docs/StreamoidSidebar.md +413 -0
- package/dist/docs/StreamoidWorkspaceSwitcher.md +307 -0
- package/dist/docs/UsageHistoryMobile.md +235 -0
- package/dist/docs/components.json +4931 -0
- package/dist/index.css +361 -36
- package/dist/index.d.mts +213 -88
- package/dist/index.d.ts +213 -88
- package/dist/index.js +2486 -1629
- package/dist/index.mjs +2487 -1620
- package/package.json +5 -3
|
@@ -0,0 +1,261 @@
|
|
|
1
|
+
---
|
|
2
|
+
component: ScProfileImageUpdate
|
|
3
|
+
package: "@streamoid/ui"
|
|
4
|
+
category: profile
|
|
5
|
+
status: stable
|
|
6
|
+
renders: div
|
|
7
|
+
tags: [avatar, profile, picture, upload, delete, initials, settings, image]
|
|
8
|
+
related: [ScDp, ScIntialProfileCover, ScImageField, ScFileField, ScProfileSettingsComp]
|
|
9
|
+
do_not_confuse_with: [ScImageField, ScDp, ScIntialProfileCover, ScProfile, ScProfileSettingsComp]
|
|
10
|
+
used_by: [cxo]
|
|
11
|
+
---
|
|
12
|
+
|
|
13
|
+
# ScProfileImageUpdate
|
|
14
|
+
|
|
15
|
+
**The avatar editor block at the top of a profile settings screen.** A 100px circle
|
|
16
|
+
(the image, or initials on a grey disc) with an upload / delete icon-button pair
|
|
17
|
+
underneath, split by a vertical divider. Nothing else — no label, no filename, no
|
|
18
|
+
crop.
|
|
19
|
+
|
|
20
|
+
## TL;DR for agents
|
|
21
|
+
|
|
22
|
+
- **Reach for it when:** a **profile settings** screen needs "change your picture" —
|
|
23
|
+
circle above, two icon buttons below.
|
|
24
|
+
- **Don't reach for it when:** you want a labelled rectangular image field in a form
|
|
25
|
+
(→ `ScImageField`), any non-image file (→ `ScFileField`), or you're only *showing*
|
|
26
|
+
an avatar (→ `ScDp`).
|
|
27
|
+
- **Four things that will bite you:**
|
|
28
|
+
1. The **delete button always renders**, even with no image. Gate `onDelete`
|
|
29
|
+
yourself or the user "deletes" nothing and you fire a request.
|
|
30
|
+
2. Both buttons are **unlabelled** `ScButton`s → `div[role="button"]` with no
|
|
31
|
+
`aria-label`. Add your own via a wrapper; you can't pass one through.
|
|
32
|
+
3. `accept="image/*"` is **hardcoded**; there is no `accept`, no size limit, no
|
|
33
|
+
loading/uploading state.
|
|
34
|
+
4. **Fixed 7.5rem wide / 6.25rem circle.** No `size` prop (unlike `ScDp`).
|
|
35
|
+
|
|
36
|
+
---
|
|
37
|
+
|
|
38
|
+
## 1. How to use it
|
|
39
|
+
|
|
40
|
+
### Import
|
|
41
|
+
|
|
42
|
+
```tsx
|
|
43
|
+
import { ScProfileImageUpdate } from "@streamoid/ui";
|
|
44
|
+
import "@streamoid/ui/dist/index.css"; // once, at your app root
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
### Minimal usage
|
|
48
|
+
|
|
49
|
+
```tsx
|
|
50
|
+
<ScProfileImageUpdate
|
|
51
|
+
imageUrl={profileImage}
|
|
52
|
+
initials={profileInitials}
|
|
53
|
+
onUpload={handleProfileImageUpload}
|
|
54
|
+
onDelete={handleProfileImageDelete}
|
|
55
|
+
/>
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
### Props
|
|
59
|
+
|
|
60
|
+
| Prop | Type | Default | Notes |
|
|
61
|
+
|---|---|---|---|
|
|
62
|
+
| `imageUrl` | `string` | – | Remote URL or blob URL. Truthy → `<img alt="profile">`; falsy → the initials disc. **No `onError` fallback** — a broken URL shows a broken image, it does not fall back to initials (`ScDp` does). |
|
|
63
|
+
| `initials` | `string` | `""` | Shown only when `imageUrl` is falsy. Default is an **empty grey circle** — pass 1–2 characters. |
|
|
64
|
+
| `onUpload` | `(file: File) => void` | – | Fires with the chosen `File`. The input's value is reset afterwards, so re-picking the same file fires again. |
|
|
65
|
+
| `onDelete` | `() => void` | – | The trash button. **Always clickable** — see Gotcha 1. |
|
|
66
|
+
| `className` | `string` | – | Concatenated after the root class. |
|
|
67
|
+
| `...props` | `HTMLAttributes<HTMLDivElement>` | – | Spread onto the root `div` — `style`, `data-*`, `aria-*`, `onClick` all work. (This one *does* extend `HTMLAttributes`; `ScImageField` and `ScFileField` do not.) |
|
|
68
|
+
|
|
69
|
+
### What renders in each state
|
|
70
|
+
|
|
71
|
+
| `imageUrl` | Circle | Buttons |
|
|
72
|
+
|---|---|---|
|
|
73
|
+
| truthy | `<img>` cropped `object-fit: cover` to a 6.25rem circle | ⬆ upload · │ · 🗑 delete |
|
|
74
|
+
| falsy | `initials` centred on `--alias-fill-neutral-neutralselected`, 1.75rem / weight 500 | ⬆ upload · │ · 🗑 delete (still) |
|
|
75
|
+
|
|
76
|
+
### Recipes
|
|
77
|
+
|
|
78
|
+
```tsx
|
|
79
|
+
// The standard settings pattern: local preview, server persist
|
|
80
|
+
const [preview, setPreview] = useState<string | null>(null);
|
|
81
|
+
|
|
82
|
+
<ScProfileImageUpdate
|
|
83
|
+
imageUrl={preview ?? user.avatarUrl}
|
|
84
|
+
initials={initialsOf(user.fullName)}
|
|
85
|
+
onUpload={async (file) => {
|
|
86
|
+
setPreview(URL.createObjectURL(file)); // instant feedback
|
|
87
|
+
await api.uploadAvatar(file); // then persist
|
|
88
|
+
}}
|
|
89
|
+
onDelete={async () => {
|
|
90
|
+
if (preview) { URL.revokeObjectURL(preview); setPreview(null); }
|
|
91
|
+
await api.deleteAvatar();
|
|
92
|
+
}}
|
|
93
|
+
/>
|
|
94
|
+
|
|
95
|
+
// Guard the delete so it can't fire on an empty avatar
|
|
96
|
+
<ScProfileImageUpdate
|
|
97
|
+
imageUrl={avatar}
|
|
98
|
+
initials={initials}
|
|
99
|
+
onUpload={upload}
|
|
100
|
+
onDelete={avatar ? remove : undefined} // button still shows, but does nothing
|
|
101
|
+
/>
|
|
102
|
+
|
|
103
|
+
// Validate before you accept the file (there is no accept/size prop)
|
|
104
|
+
<ScProfileImageUpdate
|
|
105
|
+
imageUrl={avatar}
|
|
106
|
+
initials={initials}
|
|
107
|
+
onUpload={(f) => {
|
|
108
|
+
if (f.size > 2_000_000) return toast("Keep it under 2 MB");
|
|
109
|
+
upload(f);
|
|
110
|
+
}}
|
|
111
|
+
/>
|
|
112
|
+
```
|
|
113
|
+
|
|
114
|
+
---
|
|
115
|
+
|
|
116
|
+
## 2. Where to use it
|
|
117
|
+
|
|
118
|
+
- **CXO mobile settings → Profile tab**, centred at the top of the field stack, above
|
|
119
|
+
the `ScTextField`s for name and email. That is its only live consumer.
|
|
120
|
+
- Any **"My profile" / "Account" settings screen** in a host app: it is the intended
|
|
121
|
+
partner for `ScProfileSettingsComp`'s name/email rows.
|
|
122
|
+
- Only for a **person's** avatar. A workspace/brand logo is a rectangle in a form —
|
|
123
|
+
that is `ScImageField`.
|
|
124
|
+
|
|
125
|
+
Internally it composes `ScButton` (×2, `styleVariant="icon-only"`,
|
|
126
|
+
`variant="tertiary"`, `size="md"`) and `ScVDivider`.
|
|
127
|
+
|
|
128
|
+
---
|
|
129
|
+
|
|
130
|
+
## 3. When to use it
|
|
131
|
+
|
|
132
|
+
### Use it when
|
|
133
|
+
|
|
134
|
+
- The image is a **person's avatar**, round, and the affordances should be two small
|
|
135
|
+
icon buttons rather than a dropzone.
|
|
136
|
+
- You have both operations: set a new picture **and** remove the current one.
|
|
137
|
+
- The layout is a centred column — it is a fixed 7.5rem-wide block, not a
|
|
138
|
+
full-width form row.
|
|
139
|
+
|
|
140
|
+
### Don't use it — reach for this instead
|
|
141
|
+
|
|
142
|
+
| Situation | Use instead |
|
|
143
|
+
|---|---|
|
|
144
|
+
| A labelled, full-width, rectangular image field inside a form (workspace logo, brand mark) | `ScImageField` |
|
|
145
|
+
| Any non-image file, or you need progress / an error line | `ScFileField` |
|
|
146
|
+
| Only **displaying** an avatar (header, sidebar, table row, switcher) | `ScDp` — has `size`, `variant="workspace" \| "profile"`, and a real image-error fallback to initials |
|
|
147
|
+
| Only displaying **initials** at an arbitrary size | `ScIntialProfileCover` |
|
|
148
|
+
| The whole profile settings form (name, email rows) | `ScProfileSettingsComp` |
|
|
149
|
+
| The profile *menu* / popover in the header | `ScProfile`, `ScProfileOptions`, `ScProfilePopup` |
|
|
150
|
+
| A different avatar size | `ScDp` — this component has no `size` prop |
|
|
151
|
+
|
|
152
|
+
### Don't confuse with
|
|
153
|
+
|
|
154
|
+
| You may actually want | Not this |
|
|
155
|
+
|---|---|
|
|
156
|
+
| `ScImageField` — labelled 120px **rectangle**, thumbnail + filename + one red delete icon, `onFileSelect(file)` | `ScProfileImageUpdate` is a 100px **circle** with two icon buttons and no label or filename |
|
|
157
|
+
| `ScDp` — read-only avatar with `size`, `type="initial" \| "image"`, `variant`, and `onError` → initials | This component uploads but cannot resize and has no error fallback |
|
|
158
|
+
| `ScIntialProfileCover` — initials-only disc, takes `intial` (note the spelling) and `size` | Not an editor |
|
|
159
|
+
| `ScProfile` / `ScProfileOptions` / `ScProfilePopup` / `ScProfileSettingsComp` | Menu/row/form components, nothing to do with picking an image |
|
|
160
|
+
|
|
161
|
+
Prop-name trap across the family: this one takes **`imageUrl`**, `ScImageField`
|
|
162
|
+
takes **`imagePreview`**, `ScDp` takes **`imageUrl`** + **`initial`** (singular),
|
|
163
|
+
`ScIntialProfileCover` takes **`intial`** (sic). None are interchangeable.
|
|
164
|
+
|
|
165
|
+
---
|
|
166
|
+
|
|
167
|
+
## 4. Why to use it
|
|
168
|
+
|
|
169
|
+
- **The Figma block, exactly.** 7.5rem column, 6.25rem circle, 0.5rem gap, 0.625rem
|
|
170
|
+
between buttons, a 1.5rem-tall divider, 1.25rem icons — all pinned so every
|
|
171
|
+
product's profile screen looks identical.
|
|
172
|
+
- **Initials fallback is already tokenised** (`fill-neutral-neutralselected` disc,
|
|
173
|
+
`text-and-icons-primary` glyphs at 1.75rem/500), so an avatar-less user doesn't
|
|
174
|
+
get a blank hole or a light-mode contrast bug.
|
|
175
|
+
- **The file input is reset after every pick**, so the "user re-selects the same
|
|
176
|
+
file and nothing happens" bug is already handled.
|
|
177
|
+
- **Buttons inherit `ScButton`** — so hover, `tabIndex={0}`, Enter/Space activation
|
|
178
|
+
and the tertiary token set come for free, and a future button restyle reaches this
|
|
179
|
+
component automatically. (There is no focus *ring*: `ScButton.module.css` has no
|
|
180
|
+
`:focus` rule, so the buttons are focusable but invisibly so.)
|
|
181
|
+
|
|
182
|
+
---
|
|
183
|
+
|
|
184
|
+
## Gotchas
|
|
185
|
+
|
|
186
|
+
**1. Delete is never disabled.** There is no `canDelete`/`state` prop and no gate on
|
|
187
|
+
`imageUrl`. A user with no picture can still click the trash.
|
|
188
|
+
|
|
189
|
+
```tsx
|
|
190
|
+
// WRONG — fires a DELETE for an avatar that isn't there
|
|
191
|
+
<ScProfileImageUpdate initials="NS" onDelete={() => api.deleteAvatar()} />
|
|
192
|
+
|
|
193
|
+
// RIGHT — no-op when there's nothing to remove
|
|
194
|
+
<ScProfileImageUpdate initials="NS" imageUrl={avatar} onDelete={avatar ? remove : undefined} />
|
|
195
|
+
```
|
|
196
|
+
|
|
197
|
+
**2. Neither button has an accessible name.** Both are `ScButton`
|
|
198
|
+
`styleVariant="icon-only"` with no `aria-label`, and `ScButton` renders
|
|
199
|
+
`div[role="button"]` (with `tabIndex={0}` and its own Enter/Space handler, but no
|
|
200
|
+
visible focus style). A screen reader announces two unnamed buttons. You cannot pass
|
|
201
|
+
labels through — the props stop at `onUpload`/`onDelete`. Wrap the component and
|
|
202
|
+
label the region, or fix the DS:
|
|
203
|
+
|
|
204
|
+
```tsx
|
|
205
|
+
<div role="group" aria-label="Profile picture">
|
|
206
|
+
<ScProfileImageUpdate … />
|
|
207
|
+
</div>
|
|
208
|
+
```
|
|
209
|
+
|
|
210
|
+
**3. `className` is concatenated with `" " + className` unguarded**, so when you omit
|
|
211
|
+
it the root's class list literally contains `undefined`:
|
|
212
|
+
`class="scProfileImageUpdate undefined"`. Cosmetic, but never assert on the exact
|
|
213
|
+
`className` string, and don't write a global `.undefined` rule (it would hit dozens
|
|
214
|
+
of DS components that do this).
|
|
215
|
+
|
|
216
|
+
**4. `accept="image/*"` is hardcoded.** No `accept`, no `maxSize`, no type filter.
|
|
217
|
+
Validate inside `onUpload` — after the picker has already closed.
|
|
218
|
+
|
|
219
|
+
**5. No loading / uploading state.** Nothing indicates an in-flight upload and
|
|
220
|
+
nothing blocks a second click. Set `imageUrl` to a blob preview immediately and
|
|
221
|
+
disable your own surrounding UI if the request is slow.
|
|
222
|
+
|
|
223
|
+
**6. A broken `imageUrl` does not fall back to initials.** There is no `onError`
|
|
224
|
+
handler (`ScDp` has one). A 404 avatar URL renders the browser's broken-image glyph
|
|
225
|
+
inside the circle. If your URLs can 404, prefer `ScDp` for display and keep this
|
|
226
|
+
component for the edit affordance only, or pre-validate the URL.
|
|
227
|
+
|
|
228
|
+
**7. Fixed size, and the image is cropped.** `width: 7.5rem` on the root,
|
|
229
|
+
`6.25rem` square + `border-radius: full` + `object-fit: cover` on the image. A
|
|
230
|
+
non-square upload is centre-cropped with no crop UI. There is no `size` prop.
|
|
231
|
+
|
|
232
|
+
**8. `initials` defaults to `""`.** Forget it and an avatar-less user gets a blank
|
|
233
|
+
grey circle. Also: only 1–2 characters fit at 1.75rem inside 100px.
|
|
234
|
+
|
|
235
|
+
**9. It reports nothing back about the delete.** `onDelete` takes no argument and the
|
|
236
|
+
component keeps no state, so the circle only clears when *you* clear `imageUrl`.
|
|
237
|
+
|
|
238
|
+
---
|
|
239
|
+
|
|
240
|
+
## In the wild
|
|
241
|
+
|
|
242
|
+
```tsx
|
|
243
|
+
// cxo-dashboard src/app/components/mobile-settings-content.tsx:252
|
|
244
|
+
<ScProfileImageUpdate
|
|
245
|
+
imageUrl={profileImage}
|
|
246
|
+
initials={profileInitials}
|
|
247
|
+
onUpload={handleProfileImageUpload}
|
|
248
|
+
onDelete={handleProfileImageDelete}
|
|
249
|
+
/>
|
|
250
|
+
```
|
|
251
|
+
|
|
252
|
+
---
|
|
253
|
+
|
|
254
|
+
## Related
|
|
255
|
+
|
|
256
|
+
- `ScDp` — the read-only avatar with `size`, `variant` and an image-error fallback; use it everywhere you only display.
|
|
257
|
+
- `ScIntialProfileCover` — initials-only disc (`intial`, `size`).
|
|
258
|
+
- `ScImageField` — the labelled rectangular image field for logos inside forms.
|
|
259
|
+
- `ScFileField` — non-image uploads, with progress and error.
|
|
260
|
+
- `ScProfileSettingsComp` — the name/email rows this block sits above.
|
|
261
|
+
- `ScButton` / `ScVDivider` — what it composes internally.
|
|
@@ -0,0 +1,245 @@
|
|
|
1
|
+
---
|
|
2
|
+
component: ScProfileOptions
|
|
3
|
+
package: "@streamoid/ui"
|
|
4
|
+
category: profile
|
|
5
|
+
status: legacy
|
|
6
|
+
renders: div
|
|
7
|
+
tags: [profile-menu, profile-dropdown, account-menu, logout, theme-switcher, figma-export, static]
|
|
8
|
+
related: [ScProfilePopup, ScProfile, ScWorkspace, ScMenuOptions, ScTabSwitcher]
|
|
9
|
+
do_not_confuse_with: [ScProfilePopup, ScProfileSettingsComp, ScProfile, ScPopUpMenu, ScMenuOptions]
|
|
10
|
+
---
|
|
11
|
+
|
|
12
|
+
# ScProfileOptions
|
|
13
|
+
|
|
14
|
+
**Superseded — use `ScProfilePopup`.** This is the untouched Figma export of the
|
|
15
|
+
profile dropdown (`SC-Profile options`, node 6666:25516): a 264px panel with a
|
|
16
|
+
profile header, workspace + credits badge, four menu rows, a logout row and a
|
|
17
|
+
three-way theme switcher — **all values hardcoded and nothing wired**. Its only prop
|
|
18
|
+
is `className`. `ScProfilePopup` is the same design node rebuilt with real props,
|
|
19
|
+
slots, keyboard handling and a forwarded ref.
|
|
20
|
+
|
|
21
|
+
## TL;DR for agents
|
|
22
|
+
|
|
23
|
+
- **Reach for it when:** never, in new code. Keep it only to avoid breaking an
|
|
24
|
+
existing import.
|
|
25
|
+
- **Don't reach for it when:** you need a working profile menu → **`ScProfilePopup`**.
|
|
26
|
+
- **Four things that will bite you:**
|
|
27
|
+
1. There are **no data props**. Name, email, workspace, credits, labels and version
|
|
28
|
+
are all literal strings inside the component.
|
|
29
|
+
2. There are **no callbacks**. Logout, Billing, Settings, Switch workspace and the
|
|
30
|
+
theme tabs do nothing at all.
|
|
31
|
+
3. The first menu row renders `ScMenuOptions`' own default label — literally
|
|
32
|
+
**"Menu Option"** — because it is passed an `icon` but no `text`.
|
|
33
|
+
4. It shows "Chris Hemsworth", "Kepler workspace" and "1000 credits left" to your
|
|
34
|
+
real users.
|
|
35
|
+
|
|
36
|
+
---
|
|
37
|
+
|
|
38
|
+
## 1. How to use it
|
|
39
|
+
|
|
40
|
+
### Import
|
|
41
|
+
|
|
42
|
+
```tsx
|
|
43
|
+
import { ScProfilePopup } from "@streamoid/ui"; // ← the replacement
|
|
44
|
+
import { ScProfileOptions } from "@streamoid/ui"; // legacy, className-only
|
|
45
|
+
import "@streamoid/ui/dist/index.css"; // once, at your app root
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
### Minimal usage
|
|
49
|
+
|
|
50
|
+
The whole API:
|
|
51
|
+
|
|
52
|
+
```tsx
|
|
53
|
+
<ScProfileOptions />
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
The migration — same panel, real data:
|
|
57
|
+
|
|
58
|
+
```tsx
|
|
59
|
+
<ScProfilePopup
|
|
60
|
+
name={user.name}
|
|
61
|
+
email={user.email}
|
|
62
|
+
avatar={user.avatarUrl}
|
|
63
|
+
workspaceName={ws.name}
|
|
64
|
+
userRole={ws.role}
|
|
65
|
+
credits={credits}
|
|
66
|
+
version={`v${APP_VERSION}`}
|
|
67
|
+
themeMode={themePreference}
|
|
68
|
+
onThemeModeChange={setThemePreference}
|
|
69
|
+
onLogout={logout}
|
|
70
|
+
onSwitchWorkspace={openSwitcher}
|
|
71
|
+
onTeams={() => go("team")}
|
|
72
|
+
onBilling={() => go("billing")}
|
|
73
|
+
onSettings={() => go("account")}
|
|
74
|
+
/>
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
### Props
|
|
78
|
+
|
|
79
|
+
| Prop | Type | Default | Notes |
|
|
80
|
+
|---|---|---|---|
|
|
81
|
+
| `className` | `string` | – | Appended (concatenated unconditionally — you get a literal `undefined` class when omitted). |
|
|
82
|
+
| `...props` | `HTMLAttributes<HTMLDivElement>` | – | Spread onto the root div. An `onClick` here fires for the whole panel, which is not useful. |
|
|
83
|
+
|
|
84
|
+
That is the entire public surface.
|
|
85
|
+
|
|
86
|
+
### What it renders (all hardcoded)
|
|
87
|
+
|
|
88
|
+
| Region | Fixed content |
|
|
89
|
+
|---|---|
|
|
90
|
+
| Header | `ScProfile` with **no props** → "Chris Hemsworth" / "Kepler workspace" / broken `profile-image0.png` |
|
|
91
|
+
| Workspace | `ScWorkspace` with **no props** → "Kepler" / "Admin" / broken image, plus `ScBadges` "1000 credits left" (`success`, `opaque`) |
|
|
92
|
+
| Menu row 1 | `SiconTeam` + **"Menu Option"** (the `ScMenuOptions` default label) |
|
|
93
|
+
| Menu row 2 | `SiconBilling` + "Billing" |
|
|
94
|
+
| Button | `ScButton` "Switch workspace", `variant="secondary"`, `size="md"`, `SiconHome` icon |
|
|
95
|
+
| Menu row 3 | `SiconBolt` + "What's new", `variant="with-v"` → shows `ScMenuOptions`' default version **"v2.0.21"** |
|
|
96
|
+
| Menu row 4 | `SiconSettings` + "All settings" |
|
|
97
|
+
| Logout | `SiconLogout` + "Logout", `variant="error"` — a div, not a button |
|
|
98
|
+
| Theme | `ScTabSwitcher` with 3 icon-only `ScTabComp`s; **System is hardcoded active** |
|
|
99
|
+
|
|
100
|
+
### Recipes
|
|
101
|
+
|
|
102
|
+
There is nothing to configure. The only useful recipe is the replacement above.
|
|
103
|
+
|
|
104
|
+
---
|
|
105
|
+
|
|
106
|
+
## 2. Where to use it
|
|
107
|
+
|
|
108
|
+
Nowhere new. It is the design-time artefact of the profile dropdown; the shipped
|
|
109
|
+
surface is `ScProfilePopup`:
|
|
110
|
+
|
|
111
|
+
- **Catalogix** — `app/components/AppSettingsPopup` renders `ScProfilePopup` and
|
|
112
|
+
notes in its own header comment that it replaced ~430 lines of hand-assembled
|
|
113
|
+
panel.
|
|
114
|
+
- **CXO / Photogenix / Artifax** — profile menus live in their sidebar shells.
|
|
115
|
+
|
|
116
|
+
---
|
|
117
|
+
|
|
118
|
+
## 3. When to use it
|
|
119
|
+
|
|
120
|
+
### Use it when
|
|
121
|
+
|
|
122
|
+
- Never in product code. Legitimate uses are: a Figma-parity screenshot of the
|
|
123
|
+
original node, or keeping an old import compiling until it is migrated.
|
|
124
|
+
|
|
125
|
+
### Don't use it — reach for this instead
|
|
126
|
+
|
|
127
|
+
| Situation | Use instead |
|
|
128
|
+
|---|---|
|
|
129
|
+
| A working profile dropdown with real identity, callbacks and slots | `ScProfilePopup` |
|
|
130
|
+
| Only the identity row | `ScProfile` |
|
|
131
|
+
| Only one menu row | `ScMenuOptions` |
|
|
132
|
+
| A generic popover of options anchored to a trigger | `ScPopUpMenu` + `ScMenuOptions` |
|
|
133
|
+
| The theme switcher on its own | `ScTabSwitcher` + `ScTabComp` (`type="icon-only"`) |
|
|
134
|
+
| The account settings **form** | compose `ScProfileImageUpdate` + `ScTextField` (see `ScProfileSettingsComp`) |
|
|
135
|
+
|
|
136
|
+
### Don't confuse with
|
|
137
|
+
|
|
138
|
+
| You may actually want | Not this |
|
|
139
|
+
|---|---|
|
|
140
|
+
| `ScProfilePopup` — the live, propped panel built from the same Figma node | `ScProfileOptions` is the static export of it |
|
|
141
|
+
| `ScProfileSettingsComp` — the account **form** scaffold (first/last/email) | this is the **menu** |
|
|
142
|
+
| `ScProfile` — the identity row alone | this is the whole panel |
|
|
143
|
+
| `ScPopUpMenu` — generic anchored popover | this is the profile-specific panel body |
|
|
144
|
+
|
|
145
|
+
`ScProfileOptions` vs `ScProfilePopup` vs `ScProfileSettingsComp` vs `ScProfile` is
|
|
146
|
+
the highest-traffic mis-pick in this folder: **menu (static) / menu (live) / form /
|
|
147
|
+
row**.
|
|
148
|
+
|
|
149
|
+
---
|
|
150
|
+
|
|
151
|
+
## 4. Why to use it
|
|
152
|
+
|
|
153
|
+
Honestly: you shouldn't. What `ScProfilePopup` gives you over it —
|
|
154
|
+
|
|
155
|
+
- **Real props and callbacks** for every row, plus `show*` flags to drop rows.
|
|
156
|
+
- **Four nesting slots** (`headerActions`, `workspaceContent`, `menuContent`,
|
|
157
|
+
`preFooterContent`) with a stretch rule so nested DS components lay out correctly.
|
|
158
|
+
- **Keyboard access** — Enter/Space on the div-based rows, `role="radiogroup"` +
|
|
159
|
+
`role="radio"` + `aria-checked` on the theme tabs, focus-visible rings everywhere.
|
|
160
|
+
- **A forwarded ref** on the panel root, for positioning and outside-click dismissal.
|
|
161
|
+
- **Icon colour fixed** — `ScMenuOptions` colours only its label, so the built-in
|
|
162
|
+
icons inherit whatever the host happened to set; the popup drives them from
|
|
163
|
+
`--alias-text-and-icons-primary`.
|
|
164
|
+
|
|
165
|
+
The one thing this component still documents well is the **panel shell recipe**:
|
|
166
|
+
`surface-subtle` + `radius-4xl` + `0.03125rem border-subtle` + `backdrop-filter:
|
|
167
|
+
blur(2.625rem)`, 264px wide. `ScProfilePopup` reproduces it at 320px using an inset
|
|
168
|
+
box-shadow instead of a border (a real border eats a pixel of the content width).
|
|
169
|
+
|
|
170
|
+
---
|
|
171
|
+
|
|
172
|
+
## Gotchas
|
|
173
|
+
|
|
174
|
+
**1. `ScProfile` and `ScWorkspace` are rendered with no props.** Both fall back to
|
|
175
|
+
their placeholder identity **and** to `profileImage="profile-image0.png"`, so the
|
|
176
|
+
panel shows two broken-image glyphs in any host.
|
|
177
|
+
|
|
178
|
+
**2. "Menu Option" ships to production.** The first row passes only an `icon`:
|
|
179
|
+
|
|
180
|
+
```tsx
|
|
181
|
+
// what the component actually does — text is omitted, so ScMenuOptions' default wins
|
|
182
|
+
<ScMenuOptions icon={<SiconTeam />} /> // renders "Menu Option"
|
|
183
|
+
```
|
|
184
|
+
|
|
185
|
+
The intended label was "Teams" (`ScProfilePopup` fixes it with `teamsLabel="Teams"`).
|
|
186
|
+
|
|
187
|
+
**3. The version stamp is a lie.** The "What's new" row uses `variant="with-v"`
|
|
188
|
+
without a `version`, so `ScMenuOptions`' default `"v2.0.21"` renders regardless of
|
|
189
|
+
your app version.
|
|
190
|
+
|
|
191
|
+
**4. Nothing is interactive.** Every row is a bare `<div>` with no `onClick`, no
|
|
192
|
+
`role`, no `tabIndex`. Not clickable, not focusable, invisible to screen readers as a
|
|
193
|
+
menu.
|
|
194
|
+
|
|
195
|
+
**5. The theme switcher's active tab is hardcoded.** `active="true"` sits on the
|
|
196
|
+
System tab literally; clicking Light or Dark does nothing.
|
|
197
|
+
|
|
198
|
+
**6. The `scTabComp*` props passed to `ScTabSwitcher` are dead.**
|
|
199
|
+
`scTabCompicon` / `scTabCompactive` / `scTabComptype` are destructured to `_`-prefixed
|
|
200
|
+
names inside `ScTabSwitcher` and never used. Only `component`…`component5` render.
|
|
201
|
+
|
|
202
|
+
**7. Fixed 264px width** (`width: 16.5rem`) with `backdrop-filter: blur(42px)` — the
|
|
203
|
+
blur is invisible over an opaque background, and the panel will not stretch to a
|
|
204
|
+
host container.
|
|
205
|
+
|
|
206
|
+
**8. A real `border-width: 0.03125rem`** (0.5px) is used here, so the panel's outer
|
|
207
|
+
box is 1px wider and taller than the Figma frame. `ScProfilePopup` switched to
|
|
208
|
+
`box-shadow: inset` for exactly this reason.
|
|
209
|
+
|
|
210
|
+
---
|
|
211
|
+
|
|
212
|
+
## In the wild
|
|
213
|
+
|
|
214
|
+
_No host render site found — used by the agent runtime / composed internally._
|
|
215
|
+
|
|
216
|
+
No app imports it. Its live replacement is rendered here:
|
|
217
|
+
|
|
218
|
+
```jsx
|
|
219
|
+
// catalogix/dashboard app/components/AppSettingsPopup/index.jsx:75
|
|
220
|
+
<ScProfilePopup
|
|
221
|
+
data-theme="Mode 1"
|
|
222
|
+
role="menu"
|
|
223
|
+
aria-label="Profile menu"
|
|
224
|
+
name={(userData && (userData.name || userData.email)) || ""}
|
|
225
|
+
email={(userData && userData.email) || ""}
|
|
226
|
+
avatar={userData?.imageUrl || undefined}
|
|
227
|
+
onProfileClick={() => goSettings("account")}
|
|
228
|
+
workspaceName={selectedWorkspace?.name || ""}
|
|
229
|
+
userRole={selectedWorkspace?.role || ""}
|
|
230
|
+
credits={creditsLabel}
|
|
231
|
+
onSwitchWorkspace={() => showWorkspaceList()}
|
|
232
|
+
themeMode={themePreference}
|
|
233
|
+
onThemeModeChange={setThemePreference}
|
|
234
|
+
/>
|
|
235
|
+
```
|
|
236
|
+
|
|
237
|
+
---
|
|
238
|
+
|
|
239
|
+
## Related
|
|
240
|
+
|
|
241
|
+
- `ScProfilePopup` — **the replacement.** Same design node, real API.
|
|
242
|
+
- `ScProfile` / `ScWorkspace` — the two identity rows it stacks.
|
|
243
|
+
- `ScMenuOptions` — one menu row (`variant="with-v" | "error" | "default"`).
|
|
244
|
+
- `ScTabSwitcher` / `ScTabComp` — the theme switcher pair.
|
|
245
|
+
- `ScBadges` — the credits chip it uses (the popup inlines its own credits pill instead).
|