@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.
- 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 +36 -36
- package/dist/index.d.mts +10 -0
- package/dist/index.d.ts +10 -0
- package/package.json +3 -2
|
@@ -0,0 +1,245 @@
|
|
|
1
|
+
---
|
|
2
|
+
component: ScDp
|
|
3
|
+
package: "@streamoid/ui"
|
|
4
|
+
category: profile
|
|
5
|
+
status: stable
|
|
6
|
+
renders: div
|
|
7
|
+
tags: [avatar, dp, display-picture, initials, profile-image, workspace-logo, fallback]
|
|
8
|
+
related: [ScIntialProfileCover, ScProfile, ScWorkspace, ScProfileImageUpdate, ScWorkspaceSwitchCard]
|
|
9
|
+
do_not_confuse_with: [ScIntialProfileCover, ScProfileImageUpdate, ScProfile]
|
|
10
|
+
used_by: [cxo, photogenix, artifax]
|
|
11
|
+
---
|
|
12
|
+
|
|
13
|
+
# ScDp
|
|
14
|
+
|
|
15
|
+
**The avatar primitive.** A square box that shows an image if you have one and
|
|
16
|
+
initials if you don't — circular for people (`variant="profile"`), rounded-rect for
|
|
17
|
+
workspaces (`variant="workspace"`). It is the only avatar in the library that has a
|
|
18
|
+
real image-error fallback.
|
|
19
|
+
|
|
20
|
+
## TL;DR for agents
|
|
21
|
+
|
|
22
|
+
- **Reach for it when:** you need any avatar — a user's face, a workspace logo, an
|
|
23
|
+
assignee chip, a sidebar profile button.
|
|
24
|
+
- **Don't reach for it when:** you need the upload/crop control (→ `ScProfileImageUpdate`),
|
|
25
|
+
or an initials tile with no image path at all (→ `ScIntialProfileCover`).
|
|
26
|
+
- **Four things that will bite you:**
|
|
27
|
+
1. `initial` defaults to **`"WS"`** — forget it and every avatar reads "WS".
|
|
28
|
+
2. `size` is **unitless pixels**, and defaults to **60**. `size={12}` gives you a
|
|
29
|
+
12px avatar, not `12rem`.
|
|
30
|
+
3. The initials text is a **fixed 1rem** and never scales with `size`. Below ~32px
|
|
31
|
+
it gets clipped by `overflow: hidden`.
|
|
32
|
+
4. `type="image"` with no `imageUrl` silently renders initials. That is the
|
|
33
|
+
fallback, not a bug — but it means a typo in `imageUrl` looks like "no image".
|
|
34
|
+
|
|
35
|
+
---
|
|
36
|
+
|
|
37
|
+
## 1. How to use it
|
|
38
|
+
|
|
39
|
+
### Import
|
|
40
|
+
|
|
41
|
+
```tsx
|
|
42
|
+
import { ScDp } from "@streamoid/ui";
|
|
43
|
+
import "@streamoid/ui/dist/index.css"; // once, at your app root
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
### Minimal usage
|
|
47
|
+
|
|
48
|
+
```tsx
|
|
49
|
+
<ScDp type="initial" variant="profile" initial="NR" size={40} />
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
### Props
|
|
53
|
+
|
|
54
|
+
| Prop | Type | Default | Notes |
|
|
55
|
+
|---|---|---|---|
|
|
56
|
+
| `type` | `"initial"` \| `"image"` | `"initial"` | `"image"` only takes effect when `imageUrl` is set **and** the image loads. |
|
|
57
|
+
| `variant` | `"workspace"` \| `"profile"` | `"workspace"` | ⚠️ Defaults to the **rounded-rect** workspace shape. Pass `"profile"` for a circle. |
|
|
58
|
+
| `initial` | `string` | `"WS"` | ⚠️ Real default. Shown when `type="initial"` **and** as the fallback when the image 404s. |
|
|
59
|
+
| `imageUrl` | `string` | – | `<img src>`. On `onError` the component flips to initials permanently. |
|
|
60
|
+
| `size` | `number` | `60` | Width **and** height in **px** (written as inline `width`/`height`). `aspect-ratio: 1` is enforced in CSS. |
|
|
61
|
+
| `className` | `string` | – | Appended after the internal classes. |
|
|
62
|
+
| `style` | `CSSProperties` | – | Spread **after** `size`, so `style={{ width: 90 }}` **overrides** `size`. |
|
|
63
|
+
|
|
64
|
+
Nothing else is accepted — `ScDpProps` does **not** extend `HTMLAttributes`, so
|
|
65
|
+
`onClick`, `data-*`, `aria-*` and `title` are **not** forwarded. Wrap it.
|
|
66
|
+
|
|
67
|
+
### What renders
|
|
68
|
+
|
|
69
|
+
| `type` | `imageUrl` | Image loaded | Renders |
|
|
70
|
+
|---|---|---|---|
|
|
71
|
+
| `"initial"` | anything | – | initials on `fill-neutral-neutralselected` |
|
|
72
|
+
| `"image"` | unset | – | initials (fallback) |
|
|
73
|
+
| `"image"` | set | ✓ | `<img alt="" object-fit: cover>` |
|
|
74
|
+
| `"image"` | set | ✗ (onError) | initials, for the rest of the component's life |
|
|
75
|
+
|
|
76
|
+
### Recipes
|
|
77
|
+
|
|
78
|
+
```tsx
|
|
79
|
+
// Person avatar with image + initials fallback (the standard host idiom)
|
|
80
|
+
<ScDp
|
|
81
|
+
type={user.imageUrl ? "image" : "initial"}
|
|
82
|
+
variant="profile"
|
|
83
|
+
initial={(user.name || "U").charAt(0).toUpperCase()}
|
|
84
|
+
imageUrl={user.imageUrl}
|
|
85
|
+
size={40}
|
|
86
|
+
/>
|
|
87
|
+
|
|
88
|
+
// Workspace logo tile — rounded rect, custom radius via style
|
|
89
|
+
<ScDp
|
|
90
|
+
type="initial"
|
|
91
|
+
variant="workspace"
|
|
92
|
+
initial="AC"
|
|
93
|
+
size={64}
|
|
94
|
+
style={{ borderRadius: "var(--radius-xl, 12px)" }}
|
|
95
|
+
/>
|
|
96
|
+
|
|
97
|
+
// Clickable avatar — ScDp forwards no handlers, so wrap it
|
|
98
|
+
<button type="button" onClick={openProfile} aria-label="Open profile menu">
|
|
99
|
+
<ScDp type="initial" variant="profile" initial="NR" size={32} />
|
|
100
|
+
</button>
|
|
101
|
+
```
|
|
102
|
+
|
|
103
|
+
---
|
|
104
|
+
|
|
105
|
+
## 2. Where to use it
|
|
106
|
+
|
|
107
|
+
- **Sidebar profile footers** — Photogenix
|
|
108
|
+
`dashboard/client/src/components/layout/Sidebar.tsx:871`/`:923` and Artifax
|
|
109
|
+
`packages/shared/src/components/DashboardSidebar.tsx:698`/`:853`/`:885` each render
|
|
110
|
+
one in their own sidebar shell. (Catalogix does **not** use it — its profile menu is
|
|
111
|
+
`ScProfilePopup`, which composes `ScProfile`/`ScWorkspace` and their own `<img>`.)
|
|
112
|
+
- **Workspace switch rows** — `ScWorkspaceSwitchCard` and
|
|
113
|
+
`ScWorkspaceSwitchMobileV2` compose it internally (the card forces it to 90px).
|
|
114
|
+
- **Assignee / member pickers** — Photogenix `AssigneeSelect.tsx:66` and
|
|
115
|
+
`SPHCProfileDropdownField.tsx:60`.
|
|
116
|
+
- **Team tables** — one per member row, via `ProfileAvatar` in `@streamoid/settings`
|
|
117
|
+
(`organization-content.tsx:141`).
|
|
118
|
+
|
|
119
|
+
CXO (`src/app/components/profile-avatar.tsx`) and `@streamoid/settings`
|
|
120
|
+
(`packages/settings/src/profile-avatar.tsx`) both wrap it in a local `ProfileAvatar`
|
|
121
|
+
helper that picks `variant` from a `borderRadius` string; prefer passing `variant`
|
|
122
|
+
directly in new code.
|
|
123
|
+
|
|
124
|
+
---
|
|
125
|
+
|
|
126
|
+
## 3. When to use it
|
|
127
|
+
|
|
128
|
+
### Use it when
|
|
129
|
+
|
|
130
|
+
- You have an identity (person or workspace) and **may or may not** have an image.
|
|
131
|
+
- You want the initials fallback, the `object-fit: cover` crop and the
|
|
132
|
+
`aspect-ratio: 1` guarantee without writing them.
|
|
133
|
+
|
|
134
|
+
### Don't use it — reach for this instead
|
|
135
|
+
|
|
136
|
+
| Situation | Use instead |
|
|
137
|
+
|---|---|
|
|
138
|
+
| Avatar **upload / delete** control on a settings form | `ScProfileImageUpdate` |
|
|
139
|
+
| Initials-only tile, no image branch, 60px design block | `ScIntialProfileCover` |
|
|
140
|
+
| Avatar **plus** name and sub-line as one row | `ScProfile` (person) / `ScWorkspace` (workspace, internal) |
|
|
141
|
+
| A whole workspace row with role/plan/owner | `ScWorkspaceSwitchCard` |
|
|
142
|
+
| A status dot or count chip | `ScBadges` / `ScBeacon` |
|
|
143
|
+
|
|
144
|
+
### Don't confuse with
|
|
145
|
+
|
|
146
|
+
| You may actually want | Not this |
|
|
147
|
+
|---|---|
|
|
148
|
+
| `ScIntialProfileCover` — fixed 60px initials block, `intial` (sic) prop, no image support | `ScDp` is the one with the image path and the error fallback |
|
|
149
|
+
| `ScProfileImageUpdate` — the editable avatar with upload/delete | `ScDp` is display-only |
|
|
150
|
+
| `ScProfile` — avatar **+ name + subtext** row | `ScDp` is the avatar alone |
|
|
151
|
+
|
|
152
|
+
---
|
|
153
|
+
|
|
154
|
+
## 4. Why to use it
|
|
155
|
+
|
|
156
|
+
- **The fallback chain is already right.** Image → `onError` → initials, with the
|
|
157
|
+
background, radius and centring holding in both branches. Hand-rolled avatars
|
|
158
|
+
usually show a broken-image glyph instead.
|
|
159
|
+
- **No layout collapse.** `flex-shrink: 0` + `aspect-ratio: 1` mean it keeps its
|
|
160
|
+
size inside a constrained flex row — the single most common avatar bug in the
|
|
161
|
+
host shells.
|
|
162
|
+
- **Two shapes, one prop.** `variant` maps 1:1 to the Figma person/workspace
|
|
163
|
+
distinction (`radius-full` vs `radius-md`), so specs translate directly.
|
|
164
|
+
- **Token-driven.** Background and text come from `--alias-fill-neutral-neutralselected`
|
|
165
|
+
and `--alias-text-and-icons-primary`, so it flips with the theme.
|
|
166
|
+
|
|
167
|
+
---
|
|
168
|
+
|
|
169
|
+
## Gotchas
|
|
170
|
+
|
|
171
|
+
**1. `initial` defaults to `"WS"`.** A missing `initial` ships workspace placeholder
|
|
172
|
+
copy onto a person's avatar.
|
|
173
|
+
|
|
174
|
+
```tsx
|
|
175
|
+
// WRONG — renders "WS"
|
|
176
|
+
<ScDp type="initial" variant="profile" size={40} />
|
|
177
|
+
|
|
178
|
+
// RIGHT
|
|
179
|
+
<ScDp type="initial" variant="profile" initial="NR" size={40} />
|
|
180
|
+
```
|
|
181
|
+
|
|
182
|
+
**2. `variant` defaults to `"workspace"` (a rounded rect).** People need
|
|
183
|
+
`variant="profile"` for the circle.
|
|
184
|
+
|
|
185
|
+
**3. `size` is px and the initials font is not.** `.initials` is hardcoded to
|
|
186
|
+
`font-size-md` (1rem) / `line-height-md` (1.5rem) with `overflow: hidden` and
|
|
187
|
+
`white-space: nowrap` on the parent, so at `size={24}` two initials get clipped.
|
|
188
|
+
Use one character below ~32px, or restyle via `className`.
|
|
189
|
+
|
|
190
|
+
**4. Multi-character initials get cut, not shrunk.** `.scDp` is `overflow: hidden`
|
|
191
|
+
and `.initials` is `nowrap` + `ellipsis`. Three letters at 40px will clip.
|
|
192
|
+
|
|
193
|
+
**5. No handlers are forwarded.** `ScDpProps` lists only 7 keys —
|
|
194
|
+
`onClick`, `onKeyDown`, `role`, `tabIndex`, `title`, `data-*` and `aria-*` are all
|
|
195
|
+
dropped by TypeScript. Wrap it in a `<button>` for interaction.
|
|
196
|
+
|
|
197
|
+
```tsx
|
|
198
|
+
// WRONG — type error, and no click even if you cast it
|
|
199
|
+
<ScDp type="initial" initial="NR" onClick={open} />
|
|
200
|
+
|
|
201
|
+
// RIGHT
|
|
202
|
+
<button type="button" onClick={open} aria-label="Profile"><ScDp type="initial" initial="NR" /></button>
|
|
203
|
+
```
|
|
204
|
+
|
|
205
|
+
**6. The image is decorative.** It renders `alt=""` and `pointer-events: none`.
|
|
206
|
+
Screen readers announce nothing — put the name in the surrounding label yourself.
|
|
207
|
+
|
|
208
|
+
**7. `style` beats `size`.** The inline style object is `{ width: size, height: size,
|
|
209
|
+
...style }`, so any `width`/`height` you pass in `style` wins. Parents can also beat
|
|
210
|
+
both: `ScWorkspaceSwitchCard` forces `90px !important` on its nested `ScDp`.
|
|
211
|
+
|
|
212
|
+
**8. The error fallback is sticky.** Once `onError` fires, `imageFailed` state stays
|
|
213
|
+
`true` for the component's lifetime — changing `imageUrl` afterwards will not retry.
|
|
214
|
+
Remount (change the `key`) if you need a retry.
|
|
215
|
+
|
|
216
|
+
---
|
|
217
|
+
|
|
218
|
+
## In the wild
|
|
219
|
+
|
|
220
|
+
```tsx
|
|
221
|
+
// cxo-dashboard src/app/components/profile-avatar.tsx:26
|
|
222
|
+
<ScDp
|
|
223
|
+
type={profileImage ? "image" : "initial"}
|
|
224
|
+
variant={isProfile ? "profile" : "workspace"}
|
|
225
|
+
initial={initial}
|
|
226
|
+
imageUrl={profileImage}
|
|
227
|
+
size={size}
|
|
228
|
+
className={initialsClassName}
|
|
229
|
+
/>
|
|
230
|
+
```
|
|
231
|
+
|
|
232
|
+
```tsx
|
|
233
|
+
// artifax packages/shared/src/components/DashboardSidebar.tsx:853
|
|
234
|
+
<ScDp type="initial" variant="profile" initial={initial} size={40} />
|
|
235
|
+
```
|
|
236
|
+
|
|
237
|
+
---
|
|
238
|
+
|
|
239
|
+
## Related
|
|
240
|
+
|
|
241
|
+
- `ScIntialProfileCover` — initials-only 60px tile, no image branch.
|
|
242
|
+
- `ScProfileImageUpdate` — the editable avatar (upload / delete).
|
|
243
|
+
- `ScProfile` / `ScWorkspace` — avatar + text rows built on their own `<img>`, not on `ScDp`.
|
|
244
|
+
- `ScWorkspaceSwitchCard` / `ScWorkspaceSwitchMobileV2` — compose `ScDp` internally.
|
|
245
|
+
- `ScProfilePopup` — the profile menu panel; uses `ScProfile`, not `ScDp`.
|
|
@@ -0,0 +1,318 @@
|
|
|
1
|
+
---
|
|
2
|
+
component: ScDrawer
|
|
3
|
+
package: "@streamoid/ui"
|
|
4
|
+
category: overlays
|
|
5
|
+
status: stable
|
|
6
|
+
renders: div (portalled to document.body; optional sibling backdrop div)
|
|
7
|
+
tags: [drawer, side-panel, slide-out, sidedrawer, overlay, portal, right, left, zindex]
|
|
8
|
+
related: [ScModal, ScProfilePopup, ScAppSwitchPanel, ScButton]
|
|
9
|
+
do_not_confuse_with: [ScModal, ScSidebar, StreamoidSidebar, ScProfilePopup]
|
|
10
|
+
used_by: [catalogix]
|
|
11
|
+
---
|
|
12
|
+
|
|
13
|
+
# ScDrawer
|
|
14
|
+
|
|
15
|
+
**The edge-anchored full-height slide-out panel.** A `position: fixed` 512px panel
|
|
16
|
+
pinned to the right (or left) edge, portalled to `<body>`, with self-contained
|
|
17
|
+
outside-click and Escape dismissal and an **opt-in** dim backdrop. Everything inside
|
|
18
|
+
— header, close button, scroll area, footer — is `children`.
|
|
19
|
+
|
|
20
|
+
## TL;DR for agents
|
|
21
|
+
|
|
22
|
+
- **Reach for it when:** you need a create/edit panel or a detail pane that slides
|
|
23
|
+
in from the screen edge and stays full height.
|
|
24
|
+
- **Don't reach for it when:** the dialog should be centered and blocking
|
|
25
|
+
(→ `ScModal`), it's the app's primary navigation rail (→ `StreamoidSidebar`), or
|
|
26
|
+
it's the profile flyout (→ `ScProfilePopup`).
|
|
27
|
+
- **Five things that will bite you:**
|
|
28
|
+
1. `backdrop` defaults to **`false`** ⚠️. The page behind stays fully
|
|
29
|
+
interactive and un-dimmed.
|
|
30
|
+
2. The bare defaults (512px, `surface-subtleraised`, a 24px rounded inner edge,
|
|
31
|
+
`z-index: 10`) are **overridden by every real call site**. Copy the house
|
|
32
|
+
style below or you ship a second, mismatched family of drawer.
|
|
33
|
+
3. `.content` ships `padding: 56px 96px 0 96px`. Almost everyone passes
|
|
34
|
+
`contentStyle={{ padding: 0 }}`.
|
|
35
|
+
4. Both `.drawer` and `.content` are `overflow: hidden` — **no scrolling, and
|
|
36
|
+
dropdowns are clipped**.
|
|
37
|
+
5. `closeOnOutside` is a document-level `mousedown` listener, so clicking **any**
|
|
38
|
+
other body-level portal (a `react-select` menu, a nested `ScModal`, a toast)
|
|
39
|
+
closes the drawer.
|
|
40
|
+
|
|
41
|
+
---
|
|
42
|
+
|
|
43
|
+
## 1. How to use it
|
|
44
|
+
|
|
45
|
+
### Import
|
|
46
|
+
|
|
47
|
+
```tsx
|
|
48
|
+
import { ScDrawer } from "@streamoid/ui";
|
|
49
|
+
import "@streamoid/ui/dist/index.css"; // once, at your app root
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
### Minimal usage — the house style
|
|
53
|
+
|
|
54
|
+
Do not use the bare defaults. This block is the shape every Catalogix call site
|
|
55
|
+
spells out, and it is what `app/components/SideDrawer` bakes in:
|
|
56
|
+
|
|
57
|
+
```tsx
|
|
58
|
+
{isOpen && (
|
|
59
|
+
<ScDrawer
|
|
60
|
+
onClose={close}
|
|
61
|
+
side="right"
|
|
62
|
+
backdrop
|
|
63
|
+
zIndex={70}
|
|
64
|
+
style={{
|
|
65
|
+
width: "480px",
|
|
66
|
+
maxWidth: "100vw",
|
|
67
|
+
background: "var(--alias-surface-raised)",
|
|
68
|
+
borderRadius: 0,
|
|
69
|
+
}}
|
|
70
|
+
contentStyle={{ padding: 0 }}
|
|
71
|
+
>
|
|
72
|
+
<MyPanelContent onClose={close} />
|
|
73
|
+
</ScDrawer>
|
|
74
|
+
)}
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
### Props
|
|
78
|
+
|
|
79
|
+
| Prop | Type | Default | Notes |
|
|
80
|
+
|---|---|---|---|
|
|
81
|
+
| `children` | `ReactNode` | – | The whole panel body. No header/close/footer is provided. |
|
|
82
|
+
| `open` | `boolean` | `true` | ⚠️ Defaults to open. `false` returns `null` (unmounts; no transition). |
|
|
83
|
+
| `onClose` | `() => void` | – | Fired by a document mousedown outside the panel (which includes the backdrop) and by Escape. Optional. |
|
|
84
|
+
| `side` | `"right"` \| `"left"` | `"right"` | Sets the edge, the rounded corner pair and the shadow direction. |
|
|
85
|
+
| `backdrop` | `boolean` | `false` | ⚠️ Off by default (preserves the legacy backdrop-less `SideDrawer`). When on, renders an `aria-hidden` `rgba(0,0,0,0.5)` scrim at `zIndex - 1`. The scrim has **no click handler of its own** — clicking it closes only because it is outside the panel ref, so `closeOnOutside={false}` makes the scrim inert. |
|
|
86
|
+
| `zIndex` | `number` | – | Panel = `zIndex`, backdrop = `zIndex - 1`. Needed when the drawer opens over another body-level overlay. Unset → the CSS `z-index: 10` / `9`. |
|
|
87
|
+
| `closeOnOutside` | `boolean` | `true` | Document `mousedown` outside the panel ref → `onClose`. See Gotcha 5. |
|
|
88
|
+
| `closeOnEsc` | `boolean` | `true` | Document `keydown` Escape → `onClose`. |
|
|
89
|
+
| `className` | `string` | – | On the **panel** (appended after `.drawer` + `.side-*`). |
|
|
90
|
+
| `style` | `CSSProperties` | – | On the **panel**. This is where width / background / radius go. |
|
|
91
|
+
| `contentClassName` | `string` | – | On the inner content box. |
|
|
92
|
+
| `contentStyle` | `CSSProperties` | – | On the inner content box. This is where you kill the padding. |
|
|
93
|
+
|
|
94
|
+
There is no `...props` spread — anything not in this table is a type error.
|
|
95
|
+
|
|
96
|
+
### What it actually renders
|
|
97
|
+
|
|
98
|
+
```html
|
|
99
|
+
<!-- portalled into document.body -->
|
|
100
|
+
<div class="backdrop" aria-hidden /> <!-- only when backdrop; z-index: 9 (or zIndex-1) -->
|
|
101
|
+
<div class="drawer side-right {className}" style={style}>
|
|
102
|
+
<!-- 512px · height:100vh · position:fixed · top:0 · right:0
|
|
103
|
+
background: --alias-surface-subtleraised · overflow:hidden · z-index:10
|
|
104
|
+
radius 24px 0 0 24px · box-shadow -8px 0 56px 8px rgba(0,0,0,.16) -->
|
|
105
|
+
<div class="content {contentClassName}" style={contentStyle}>
|
|
106
|
+
<!-- padding: 56px 96px 0 96px · height:100% · flex column · overflow:hidden -->
|
|
107
|
+
{children}
|
|
108
|
+
</div>
|
|
109
|
+
</div>
|
|
110
|
+
```
|
|
111
|
+
|
|
112
|
+
### Recipes
|
|
113
|
+
|
|
114
|
+
```tsx
|
|
115
|
+
// Sticky header + scrolling body + sticky footer (the layout you almost always want)
|
|
116
|
+
<ScDrawer onClose={close} backdrop zIndex={70}
|
|
117
|
+
style={{ width: 480, maxWidth: "100vw", background: "var(--alias-surface-raised)", borderRadius: 0 }}
|
|
118
|
+
contentStyle={{ padding: 0 }}
|
|
119
|
+
>
|
|
120
|
+
<div style={{ display: "flex", flexDirection: "column", height: "100%" }}>
|
|
121
|
+
<header style={{ flexShrink: 0, padding: 24 }}>
|
|
122
|
+
Create store
|
|
123
|
+
<SiconClose size={24} onClick={close} />
|
|
124
|
+
</header>
|
|
125
|
+
<div style={{ flex: 1, minHeight: 0, overflowY: "auto", padding: 24 }}>{form}</div>
|
|
126
|
+
<footer style={{ flexShrink: 0, padding: 24 }}>
|
|
127
|
+
<ScButton text="Create" variant="mono" size="md" onClick={submit} />
|
|
128
|
+
</footer>
|
|
129
|
+
</div>
|
|
130
|
+
</ScDrawer>
|
|
131
|
+
|
|
132
|
+
// Drawer opened from inside a modal — raise both, and stop the modal stealing the click
|
|
133
|
+
<ScDrawer onClose={close} backdrop zIndex={1200} />
|
|
134
|
+
|
|
135
|
+
// A drawer containing a react-select / date picker that portals to body
|
|
136
|
+
<ScDrawer onClose={close} closeOnOutside={false} backdrop />
|
|
137
|
+
// …and close it from your own explicit ✕ / Cancel instead.
|
|
138
|
+
|
|
139
|
+
// Left-anchored (filters / nav-adjacent)
|
|
140
|
+
<ScDrawer side="left" onClose={close} backdrop style={{ width: 360, borderRadius: 0 }} />
|
|
141
|
+
```
|
|
142
|
+
|
|
143
|
+
---
|
|
144
|
+
|
|
145
|
+
## 2. Where to use it
|
|
146
|
+
|
|
147
|
+
- **Catalogix create/edit panels** — `CreateStore`, `AddProductsOptions`,
|
|
148
|
+
`AddHierarchyModal`, `TaxonomyAttributesModal`, `EditAttributesModal` all render
|
|
149
|
+
one directly with the house style block.
|
|
150
|
+
- **`app/components/SideDrawer`** — the thin wrapper that bakes the same chrome in,
|
|
151
|
+
so wrapper-backed panes match the direct call sites. Prefer the wrapper inside
|
|
152
|
+
Catalogix; use `ScDrawer` directly elsewhere.
|
|
153
|
+
- **Detail / inspector panes** beside a table or grid.
|
|
154
|
+
- **Export / batch progress panels** — Photogenix's `BatchExportDrawer` is an
|
|
155
|
+
app-local drawer that should converge here.
|
|
156
|
+
|
|
157
|
+
Catalogix is the only host consumer today.
|
|
158
|
+
|
|
159
|
+
---
|
|
160
|
+
|
|
161
|
+
## 3. When to use it
|
|
162
|
+
|
|
163
|
+
### Use it when
|
|
164
|
+
|
|
165
|
+
- The panel is **secondary to the page** — you want the list behind it to stay
|
|
166
|
+
visible for context (that is exactly why `backdrop` is opt-in).
|
|
167
|
+
- The content is tall/form-shaped and benefits from full viewport height.
|
|
168
|
+
- You need the panel to escape a transformed / `overflow: hidden` ancestor — the
|
|
169
|
+
`<body>` portal is the main reason to prefer this over your own fixed div.
|
|
170
|
+
|
|
171
|
+
### Don't use it — reach for this instead
|
|
172
|
+
|
|
173
|
+
| Situation | Use instead |
|
|
174
|
+
|---|---|
|
|
175
|
+
| Centered blocking dialog / confirm | `ScModal` |
|
|
176
|
+
| The app's primary navigation rail (persistent, collapsible) | `StreamoidSidebar` (+ `ScSidebarMenu`, `ScSideBarLogoUnit`) |
|
|
177
|
+
| Cross-product app switcher | `ScAppSwitchPanel` via `StreamoidSidebar`'s `switchPanel` / `switchPanelOpen` — **not** a drawer |
|
|
178
|
+
| Profile / account flyout | `ScProfilePopup` (panel body only; you position and dismiss it) |
|
|
179
|
+
| Small contextual help bubble | `ScInfoPopup` |
|
|
180
|
+
| A menu of actions | `ScMenuOptions` rows in your own positioned container |
|
|
181
|
+
| Mobile bottom sheet | no DS component yet — build it app-local, or use `ScModal` with `contentStyle` |
|
|
182
|
+
|
|
183
|
+
### Don't confuse with
|
|
184
|
+
|
|
185
|
+
| You may actually want | Not this |
|
|
186
|
+
|---|---|
|
|
187
|
+
| `ScModal` — centered, always-scrimmed, `z-index: 999`, no `zIndex` prop | `ScDrawer` is edge-anchored, `z-index: 10`, scrim opt-in |
|
|
188
|
+
| `StreamoidSidebar` / `ScSidebar` — persistent app chrome | `ScDrawer` is a transient overlay |
|
|
189
|
+
| Catalogix's `SideDrawer` — the *wrapper* with the house style baked in | `ScDrawer` is the bare DS primitive |
|
|
190
|
+
|
|
191
|
+
---
|
|
192
|
+
|
|
193
|
+
## 4. Why to use it
|
|
194
|
+
|
|
195
|
+
- **Portalled, so it is actually fixed.** `createPortal(…, document.body)` makes the
|
|
196
|
+
panel viewport-anchored regardless of transformed or clipped ancestors — the
|
|
197
|
+
bug that kills every hand-rolled drawer.
|
|
198
|
+
- **Dismissal is self-contained.** No dependency on the app's `OutsideClick`
|
|
199
|
+
helper: the component owns a `panelRef` + document `mousedown` + `keydown` pair,
|
|
200
|
+
added and torn down with `open`. Every DS consumer gets identical behaviour.
|
|
201
|
+
- **The stacking problem has a real answer.** `zIndex` moves panel *and* backdrop
|
|
202
|
+
together (`zIndex` / `zIndex - 1`), which is the only reliable way to open a
|
|
203
|
+
drawer over another body-level overlay.
|
|
204
|
+
- **Side symmetry is free.** `side` flips the anchor, the rounded corner pair *and*
|
|
205
|
+
the shadow direction in one prop; hand-rolling the mirror is where the shadow
|
|
206
|
+
usually ends up pointing the wrong way.
|
|
207
|
+
- **Tokenised surface.** The panel background is `--alias-surface-subtleraised`
|
|
208
|
+
(or `surface-raised` in the house style), so it stays distinguishable from the
|
|
209
|
+
canvas in both themes.
|
|
210
|
+
|
|
211
|
+
---
|
|
212
|
+
|
|
213
|
+
## Gotchas
|
|
214
|
+
|
|
215
|
+
**1. `backdrop` defaults to `false`.** This is deliberate legacy parity with
|
|
216
|
+
Catalogix's old `SideDrawer`, but it means by default the page behind is neither
|
|
217
|
+
dimmed nor blocked — clicks land on it (and, via `closeOnOutside`, close the
|
|
218
|
+
drawer as a side effect). Pass `backdrop` for anything form-shaped.
|
|
219
|
+
|
|
220
|
+
**2. The bare defaults are not the product's drawer.** Left alone you get **512px,
|
|
221
|
+
`surface-subtleraised`, a 24px rounded inner edge, no backdrop, `z-index: 10`** —
|
|
222
|
+
visibly different from every drawer already shipped. The shipped chrome is:
|
|
223
|
+
|
|
224
|
+
```tsx
|
|
225
|
+
// RIGHT — the house style; matches CreateStore / SideDrawer / all Taxonomy panes
|
|
226
|
+
<ScDrawer
|
|
227
|
+
backdrop zIndex={70}
|
|
228
|
+
style={{ width: "480px", maxWidth: "100vw",
|
|
229
|
+
background: "var(--alias-surface-raised)", borderRadius: 0 }}
|
|
230
|
+
contentStyle={{ padding: 0 }}
|
|
231
|
+
/>
|
|
232
|
+
```
|
|
233
|
+
|
|
234
|
+
**3. `.content` has `padding: 56px 96px 0 96px`.** 96px of horizontal padding and
|
|
235
|
+
zero at the bottom. Unless your panel is a narrow centered form, pass
|
|
236
|
+
`contentStyle={{ padding: 0 }}` and do padding yourself.
|
|
237
|
+
|
|
238
|
+
**4. `overflow: hidden` twice — nothing scrolls, and popovers are clipped.** Both
|
|
239
|
+
`.drawer` and `.content` clip. Long content is simply cut off, and any dropdown
|
|
240
|
+
opened inside is truncated at the panel edge.
|
|
241
|
+
|
|
242
|
+
```tsx
|
|
243
|
+
// WRONG — form taller than the viewport is unreachable
|
|
244
|
+
<ScDrawer><LongForm /></ScDrawer>
|
|
245
|
+
|
|
246
|
+
// RIGHT — your own scroll container inside, and portal menus to body
|
|
247
|
+
<ScDrawer contentStyle={{ padding: 0 }}>
|
|
248
|
+
<div style={{ height: "100%", overflowY: "auto", padding: 24 }}><LongForm /></div>
|
|
249
|
+
</ScDrawer>
|
|
250
|
+
<Select menuPortalTarget={document.body} styles={{ menuPortal: (b) => ({ ...b, zIndex: 9999 }) }} />
|
|
251
|
+
```
|
|
252
|
+
|
|
253
|
+
**5. `closeOnOutside` fights every other body-level portal.** The check is
|
|
254
|
+
`!panelRef.current.contains(e.target)`. A `react-select` menu, date picker, tooltip
|
|
255
|
+
or nested `ScModal` portalled to `<body>` is *not* inside the panel, so clicking it
|
|
256
|
+
closes the drawer. Either keep those menus inside the panel DOM, or set
|
|
257
|
+
`closeOnOutside={false}` and provide an explicit ✕.
|
|
258
|
+
|
|
259
|
+
**6. `open` defaults to `true`.** Mounting it shows it. Mount conditionally or
|
|
260
|
+
always pass `open`.
|
|
261
|
+
|
|
262
|
+
**7. `style.zIndex` loses to the `zIndex` prop.** The component merges as
|
|
263
|
+
`{ ...style, zIndex }`, so your inline z-index is overwritten whenever `zIndex` is
|
|
264
|
+
set — and setting only `style={{ zIndex }}` moves the panel but leaves the backdrop
|
|
265
|
+
at `9`, putting the scrim behind other content. Always use the prop.
|
|
266
|
+
|
|
267
|
+
**8. `width: 512px` with no `max-width`.** On a narrow viewport the panel overflows
|
|
268
|
+
horizontally. Every call site adds `maxWidth: "100vw"` — do the same.
|
|
269
|
+
|
|
270
|
+
**9. `height: 100vh`, not `100dvh`.** On mobile Safari the panel runs under the URL
|
|
271
|
+
bar, and it always ignores your host's fixed header. Override in `style` if that
|
|
272
|
+
matters.
|
|
273
|
+
|
|
274
|
+
**10. No dialog a11y.** No `role="dialog"`, no `aria-modal`, no focus trap, no focus
|
|
275
|
+
restore, no body-scroll lock, and (unlike `ScModal`) no way to add aria via a spread
|
|
276
|
+
— wrap your children in your own `role="dialog"` element.
|
|
277
|
+
|
|
278
|
+
**11. No slide-in animation.** `open={false}` unmounts instantly; there is no
|
|
279
|
+
transform transition. Animate inside `children` if you need motion.
|
|
280
|
+
|
|
281
|
+
---
|
|
282
|
+
|
|
283
|
+
## In the wild
|
|
284
|
+
|
|
285
|
+
```jsx
|
|
286
|
+
// catalogix/dashboard app/components/CreateStore/index.jsx:409
|
|
287
|
+
<ScDrawer
|
|
288
|
+
onClose={() => closeModal && closeModal()}
|
|
289
|
+
side="right"
|
|
290
|
+
backdrop
|
|
291
|
+
zIndex={70}
|
|
292
|
+
style={{
|
|
293
|
+
width: "480px",
|
|
294
|
+
maxWidth: "100vw",
|
|
295
|
+
background: "var(--alias-surface-raised)",
|
|
296
|
+
borderRadius: 0,
|
|
297
|
+
}}
|
|
298
|
+
contentStyle={{ padding: 0 }}
|
|
299
|
+
>
|
|
300
|
+
<div className={styles["create-store"]}>
|
|
301
|
+
<div className={styles["drawer-header"]}>
|
|
302
|
+
<span className={styles["title"]}>{updateStore ? "Update Store" : "Create Store"}</span>
|
|
303
|
+
<SiconClose size={24} color="var(--alias-text-and-icons-primary)" onClick={closeModal} />
|
|
304
|
+
</div>
|
|
305
|
+
{/* … */}
|
|
306
|
+
</div>
|
|
307
|
+
</ScDrawer>
|
|
308
|
+
```
|
|
309
|
+
|
|
310
|
+
---
|
|
311
|
+
|
|
312
|
+
## Related
|
|
313
|
+
|
|
314
|
+
- `ScModal` — centered blocking sibling; same portal/Escape contract, always scrimmed, no `zIndex` prop.
|
|
315
|
+
- `ScProfilePopup` — the profile flyout panel body (no overlay of its own).
|
|
316
|
+
- `ScAppSwitchPanel` — the app switcher; goes through `StreamoidSidebar`'s `switchPanel`, not a drawer.
|
|
317
|
+
- `StreamoidSidebar` — persistent nav rail; the thing a drawer is *not*.
|
|
318
|
+
- `ScButton` — the footer actions you compose inside.
|