@streamoid/ui 0.6.17 → 0.6.18

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (131) hide show
  1. package/README.md +35 -18
  2. package/dist/docs/AGENTS.md +321 -0
  3. package/dist/docs/CreditWarningBanner.md +305 -0
  4. package/dist/docs/InvoiceHistoryMobile.md +222 -0
  5. package/dist/docs/ScAccess.md +259 -0
  6. package/dist/docs/ScAppCard.md +244 -0
  7. package/dist/docs/ScAppCardForCopilot.md +230 -0
  8. package/dist/docs/ScAppCardV3.md +273 -0
  9. package/dist/docs/ScAppField.md +308 -0
  10. package/dist/docs/ScAppListingCard.md +271 -0
  11. package/dist/docs/ScAppSwitchPanel.md +286 -0
  12. package/dist/docs/ScAppcardLogos.md +226 -0
  13. package/dist/docs/ScArtifaxInvite.md +262 -0
  14. package/dist/docs/ScArtifaxSidebar.md +330 -0
  15. package/dist/docs/ScAskAgentButton.md +307 -0
  16. package/dist/docs/ScBadges.md +261 -0
  17. package/dist/docs/ScBeacon.md +244 -0
  18. package/dist/docs/ScBillingHistoryHeader.md +210 -0
  19. package/dist/docs/ScBillingHistoryTableList.md +243 -0
  20. package/dist/docs/ScBillingLogsTableHeader.md +212 -0
  21. package/dist/docs/ScBillingLogsTableList.md +251 -0
  22. package/dist/docs/ScBriefCard.md +255 -0
  23. package/dist/docs/ScButton.md +251 -0
  24. package/dist/docs/ScCalendar.md +268 -0
  25. package/dist/docs/ScCalendarDateComps.md +264 -0
  26. package/dist/docs/ScCatalogixInvite.md +345 -0
  27. package/dist/docs/ScCatalogixSidebar.md +337 -0
  28. package/dist/docs/ScCatalogixStoreHeader.md +246 -0
  29. package/dist/docs/ScCatalogixStoreTableList.md +316 -0
  30. package/dist/docs/ScCheckField.md +233 -0
  31. package/dist/docs/ScCheckbox.md +272 -0
  32. package/dist/docs/ScCounter.md +235 -0
  33. package/dist/docs/ScCreditsUsageCard.md +247 -0
  34. package/dist/docs/ScCreditsUsageCardMobile.md +224 -0
  35. package/dist/docs/ScDefaultCard.md +269 -0
  36. package/dist/docs/ScDp.md +245 -0
  37. package/dist/docs/ScDrawer.md +318 -0
  38. package/dist/docs/ScFieldButton.md +255 -0
  39. package/dist/docs/ScFileField.md +268 -0
  40. package/dist/docs/ScGoogleSignIn.md +250 -0
  41. package/dist/docs/ScGuide.md +278 -0
  42. package/dist/docs/ScHDivider.md +213 -0
  43. package/dist/docs/ScHeader.md +222 -0
  44. package/dist/docs/ScImageField.md +253 -0
  45. package/dist/docs/ScInChatList.md +277 -0
  46. package/dist/docs/ScInChatMessage.md +205 -0
  47. package/dist/docs/ScInfoPopup.md +248 -0
  48. package/dist/docs/ScIntialProfileCover.md +233 -0
  49. package/dist/docs/ScInvoiceHistoryMobile.md +187 -0
  50. package/dist/docs/ScLogoUnit.md +232 -0
  51. package/dist/docs/ScMappingCard.md +241 -0
  52. package/dist/docs/ScMediaApproval.md +301 -0
  53. package/dist/docs/ScMediaSelect.md +310 -0
  54. package/dist/docs/ScMenuOptions.md +308 -0
  55. package/dist/docs/ScMobileBottomAction.md +252 -0
  56. package/dist/docs/ScMobileTopNav.md +279 -0
  57. package/dist/docs/ScModal.md +291 -0
  58. package/dist/docs/ScOnlyField.md +302 -0
  59. package/dist/docs/ScOnlyIcon.md +213 -0
  60. package/dist/docs/ScPagination.md +284 -0
  61. package/dist/docs/ScPairtext.md +287 -0
  62. package/dist/docs/ScPendingAction.md +238 -0
  63. package/dist/docs/ScPhtogenixInvite.md +275 -0
  64. package/dist/docs/ScPlanCard.md +302 -0
  65. package/dist/docs/ScPlanComparison.md +264 -0
  66. package/dist/docs/ScPlanDetailsCard.md +246 -0
  67. package/dist/docs/ScPlanDetailsCardMobile.md +240 -0
  68. package/dist/docs/ScPopUpMenu.md +224 -0
  69. package/dist/docs/ScProfile.md +234 -0
  70. package/dist/docs/ScProfileImageUpdate.md +261 -0
  71. package/dist/docs/ScProfileOptions.md +245 -0
  72. package/dist/docs/ScProfilePopup.md +396 -0
  73. package/dist/docs/ScProfileSettingsComp.md +250 -0
  74. package/dist/docs/ScProfileV2Mobile.md +216 -0
  75. package/dist/docs/ScProgressBar.md +267 -0
  76. package/dist/docs/ScQuickPrompt.md +277 -0
  77. package/dist/docs/ScRadio.md +228 -0
  78. package/dist/docs/ScReferralCardMobile.md +226 -0
  79. package/dist/docs/ScReferralTableHeader.md +260 -0
  80. package/dist/docs/ScReferralTableList.md +293 -0
  81. package/dist/docs/ScRole.md +226 -0
  82. package/dist/docs/ScRoleMobile.md +199 -0
  83. package/dist/docs/ScSelect.md +270 -0
  84. package/dist/docs/ScSelection.md +256 -0
  85. package/dist/docs/ScSelectionList.md +272 -0
  86. package/dist/docs/ScSelectionPill.md +240 -0
  87. package/dist/docs/ScSelectionPillGroup.md +302 -0
  88. package/dist/docs/ScSettingsNav.md +212 -0
  89. package/dist/docs/ScSettingsTabComp.md +260 -0
  90. package/dist/docs/ScSideBarLogoUnit.md +340 -0
  91. package/dist/docs/ScSidebar.md +243 -0
  92. package/dist/docs/ScSidebarIcons.md +232 -0
  93. package/dist/docs/ScSidebarMenu.md +283 -0
  94. package/dist/docs/ScSidebarProfile.md +231 -0
  95. package/dist/docs/ScSidebarSwitchMenu.md +258 -0
  96. package/dist/docs/ScSlider.md +194 -0
  97. package/dist/docs/ScStoreCard.md +252 -0
  98. package/dist/docs/ScStrLogo.md +253 -0
  99. package/dist/docs/ScStreamoidWordmark.md +302 -0
  100. package/dist/docs/ScSubAgent.md +226 -0
  101. package/dist/docs/ScTabComp.md +308 -0
  102. package/dist/docs/ScTabField.md +258 -0
  103. package/dist/docs/ScTabSwitcher.md +307 -0
  104. package/dist/docs/ScTableHeader.md +261 -0
  105. package/dist/docs/ScTableList.md +301 -0
  106. package/dist/docs/ScTableListMobile.md +282 -0
  107. package/dist/docs/ScTabs.md +268 -0
  108. package/dist/docs/ScTaxonomyPill.md +263 -0
  109. package/dist/docs/ScTextArea.md +259 -0
  110. package/dist/docs/ScTextField.md +324 -0
  111. package/dist/docs/ScThinkingStepIcon.md +249 -0
  112. package/dist/docs/ScTodoList.md +288 -0
  113. package/dist/docs/ScToggleSwitch.md +229 -0
  114. package/dist/docs/ScUsageHistoryMobile.md +194 -0
  115. package/dist/docs/ScVDivider.md +215 -0
  116. package/dist/docs/ScValueMappingL1.md +256 -0
  117. package/dist/docs/ScVersion.md +251 -0
  118. package/dist/docs/ScWorkspace.md +233 -0
  119. package/dist/docs/ScWorkspaceCard.md +234 -0
  120. package/dist/docs/ScWorkspaceSettingsMobile.md +265 -0
  121. package/dist/docs/ScWorkspaceSwitchCard.md +314 -0
  122. package/dist/docs/ScWorkspaceSwitchMobile.md +241 -0
  123. package/dist/docs/ScWorkspaceSwitchMobileV2.md +278 -0
  124. package/dist/docs/StreamoidSidebar.md +403 -0
  125. package/dist/docs/StreamoidWorkspaceSwitcher.md +307 -0
  126. package/dist/docs/UsageHistoryMobile.md +235 -0
  127. package/dist/docs/components.json +4849 -0
  128. package/dist/index.css +36 -36
  129. package/dist/index.d.mts +10 -0
  130. package/dist/index.d.ts +10 -0
  131. package/package.json +3 -2
@@ -0,0 +1,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.