@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,403 @@
1
+ ---
2
+ component: StreamoidSidebar
3
+ package: "@streamoid/ui"
4
+ category: sidebar
5
+ status: stable
6
+ renders: div
7
+ tags: [sidebar, nav, shell, rail, collapse, expand, app-shell, switcher, primary-navigation]
8
+ related: [ScSideBarLogoUnit, ScAppSwitchPanel, ScSidebarMenu, ScAskAgentButton, CreditWarningBanner, ScArtifaxSidebar]
9
+ do_not_confuse_with: [ScSidebar, ScArtifaxSidebar, ScCatalogixSidebar, ScSidebarMenu, ScLogoUnit]
10
+ used_by: [cxo, photogenix, catalogix]
11
+ required_props: [expanded, onToggle, config, iconMap]
12
+ ---
13
+
14
+ # StreamoidSidebar
15
+
16
+ **The current shared sidebar shell for every desktop Streamoid app.** It renders the
17
+ rounded 256px rail (or the 56px collapsed rail), a logo slot, the nav, an optional
18
+ gradient assistant CTA, a pre-footer slot, the profile row and the collapse toggle —
19
+ plus the app-switch panel as an *overlay* so opening it never pushes the nav down.
20
+
21
+ Note the name: it is **not** `Sc`-prefixed. `ScSidebar` is a different, legacy component.
22
+
23
+ ## TL;DR for agents
24
+
25
+ - **Reach for it when:** you are building (or fixing) a desktop app's primary
26
+ left navigation and you want the same chrome CXO, Photogenix and Catalogix use.
27
+ - **Don't reach for it when:** you are in Artifax (→ `ScArtifaxSidebar`, which owns
28
+ collapsible sections), you want one nav row (→ `ScSidebarMenu`), or you want the
29
+ product-switch list itself (→ `ScAppSwitchPanel`).
30
+ - **Five things that will bite you:**
31
+ 1. `versionText` defaults to **`"v1.0.0"`** — a fake version string. Every host
32
+ passes `versionText=""`.
33
+ 2. `bodyContent` is **ignored unless `showBody` is `true`**, and when it renders
34
+ it **replaces** `config.sections` entirely.
35
+ 3. `toggleInProfile` puts the collapse toggle inside the profile row — so with
36
+ `toggleInProfile` and **no `profile`, there is no toggle at all**.
37
+ 4. `config` and `iconMap` are **required** even when you drive the whole nav
38
+ through `bodyContent`. Pass `{ sections: [] }` and `{}`.
39
+ 5. Layout comes from **Tailwind utility class names** (`flex`, `shrink-0`,
40
+ `truncate`, `min-w-0`, `overflow-y-auto`). `dist/index.css` does **not** ship
41
+ them — your app must have Tailwind (all three hosts do).
42
+
43
+ ---
44
+
45
+ ## 1. How to use it
46
+
47
+ ### Import
48
+
49
+ ```tsx
50
+ import {
51
+ StreamoidSidebar,
52
+ type SidebarConfig,
53
+ type SidebarIconMap,
54
+ } from "@streamoid/ui";
55
+ import "@streamoid/ui/dist/index.css"; // once, at your app root
56
+ ```
57
+
58
+ ### Minimal usage
59
+
60
+ ```tsx
61
+ const config: SidebarConfig = {
62
+ sections: [{ id: "main", label: "Manage", items: [{ id: "stores", label: "Stores", iconKey: "bag" }] }],
63
+ };
64
+ const iconMap: SidebarIconMap = { bag: <SiconBag /> };
65
+
66
+ <StreamoidSidebar
67
+ expanded={expanded}
68
+ onToggle={() => setExpanded((v) => !v)}
69
+ config={config}
70
+ iconMap={iconMap}
71
+ activeItemId="stores"
72
+ onItemSelect={(item) => navigate(`/${item.id}`)}
73
+ versionText="" // else you ship "v1.0.0"
74
+ />
75
+ ```
76
+
77
+ ### Props
78
+
79
+ | Prop | Type | Default | Notes |
80
+ |---|---|---|---|
81
+ | `expanded` | `boolean` | — | **Required.** You own the state. `true` → 256px rounded card; `false` → 56px rail. Two completely different render branches. |
82
+ | `onToggle` | `() => void` | — | **Required.** Fired by the collapse toggle (and the mobile close icon). |
83
+ | `config` | `SidebarConfig` | — | **Required.** `{ topItem?, sections, bottomItems? }`. Pass `{ sections: [] }` if you drive nav via `bodyContent`. |
84
+ | `iconMap` | `SidebarIconMap` | — | **Required.** `Record<iconKey, ReactNode \| ({item, active, expanded}) => ReactNode>`. A missing key renders **no icon**, silently. |
85
+ | `activeItemId` | `string` | – | Matching item gets `state="active"`. |
86
+ | `highlightedItemIds` | `Set<string>` | – | Items get `state="default-highlight"`. ⚠️ Only honoured for `topItems` in the **expanded** rail — see Gotcha 8. |
87
+ | `onItemSelect` | `(item, sectionId?) => void` | – | `sectionId` is `"top"`, `"bottom"`, or the section's `id`. |
88
+ | `moreIcon` | `ReactNode` | – | Trailing "…" affordance on a row. ⚠️ Expanded rail passes it to **top items only**. |
89
+ | `onMoreClick` | `(item, sectionId?) => void` | – | Same restriction as `moreIcon`. |
90
+ | `topItems` | `SidebarMenuItemConfig[]` | – | Stacked items above the first divider. **Supersedes `config.topItem`.** |
91
+ | `topItemStates` | `Record<string, "default" \| "active" \| "hover" \| "default-highlight" \| "default-active">` | – | Per-id override; wins over `activeItemId` / `highlightedItemIds`. Top items only. |
92
+ | `expandedLogo` | `ReactNode` | – | Slot. Normally a `ScSideBarLogoUnit state="expanded"`. |
93
+ | `collapsedLogo` | `ReactNode` | – | Slot. Normally a `ScSideBarLogoUnit state="collapsed"`. |
94
+ | `profile` | `SidebarProfileConfig` | – | `{ name, subtitle?, avatar?, onClick? }`. Footer row (expanded) / avatar only (collapsed). |
95
+ | `versionText` | `string` | `"v1.0.0"` | ⚠️ **Real default.** Pass `""` to hide (all hosts do). Empty string still renders the footer wrapper + toggle. |
96
+ | `toggleIcon` | `ReactNode \| ((expanded: boolean) => ReactNode)` | – | No default glyph — omit it and the toggle is an **invisible click target**. |
97
+ | `toggleInProfile` | `boolean` | `false` | Footer = profile (flex-1) + a separate toggle button beside it, no version line. Expanded only. Requires `profile`. |
98
+ | `hideFooter` | `boolean` | `false` | Hides the version/toggle footer. ⚠️ Asymmetric with `isMobile` — see Gotcha 6. |
99
+ | `assistantCta` | `AssistantCta \| null` | – | Renders `ScAskAgentSlot` (the gradient "Ask CXO" pill / collapsed square) above `preFooterContent`. |
100
+ | `preFooterContent` | `ReactNode` | – | Slot between the nav and the bottom items — where `CreditWarningBanner` goes. |
101
+ | `bodyContent` | `ReactNode` | – | Slot replacing the whole sections area. **Only rendered when `showBody`.** |
102
+ | `showBody` | `boolean` | `false` | ⚠️ Gate for `bodyContent`. `false` → your body is dropped and `config.sections` renders instead. |
103
+ | `switchPanel` | `ReactNode` | – | The app-switch list (normally `ScAppSwitchPanel`). Mounted whenever supplied **and `!isMobile`**, so open/close can animate. |
104
+ | `switchPanelOpen` | `boolean` | `false` | Drives the open styling, the backdrop and `pointer-events`. |
105
+ | `onSwitchPanelClose` | `() => void` | – | Fired by the dim backdrop (expanded) / the fixed click-catcher (collapsed). |
106
+ | `isMobile` | `boolean` | `false` | Moves the toggle into the logo row, forces `hideFooter` in the expanded branch, and **disables `switchPanel` entirely**. |
107
+ | `className` | `string` | – | Appended on the **outer** wrapper, not the card. |
108
+ | `style` | `CSSProperties` | – | Merged **after** the wrapper's padding in the expanded branch, so `style={{ padding: 0 }}` wins. |
109
+
110
+ ### Types
111
+
112
+ ```ts
113
+ interface SidebarMenuItemConfig { id: string; label?: string; iconKey: string }
114
+ interface SidebarSectionConfig { id: string; label?: string; items: SidebarMenuItemConfig[] }
115
+ interface SidebarConfig { topItem?: SidebarMenuItemConfig; sections: SidebarSectionConfig[]; bottomItems?: SidebarMenuItemConfig[] }
116
+ interface SidebarProfileConfig { name: string; subtitle?: string; avatar?: ReactNode; onClick?: () => void }
117
+ type SidebarIconRenderer = (ctx: { item: SidebarMenuItemConfig; active: boolean; expanded: boolean }) => ReactNode;
118
+ type SidebarIconMap = Record<string, ReactNode | SidebarIconRenderer>;
119
+ ```
120
+
121
+ ### What renders in each rail
122
+
123
+ | Region | `expanded` | `expanded` + `isMobile` | collapsed |
124
+ |---|---|---|---|
125
+ | Outer | 16px padding wrapper → 256px card, 16px radius, 1px subtle border overlay | same | no padding, 56px-ish rail, right border only |
126
+ | Logo row | `expandedLogo` (+ toggle only when `isMobile`) | + close/toggle icon on the right | `collapsedLogo` |
127
+ | Top items | `topItems ?? config.topItem`, wrapped in two dividers | same | same, tighter gaps |
128
+ | Body | `showBody ? bodyContent : config.sections` (labels shown) | same | `showBody ? bodyContent : config.sections` (**labels dropped**) |
129
+ | Assistant CTA | `ScAskAgentSlot` pill | pill | `ScAskAgentSlot collapsed` square |
130
+ | Pre-footer | `preFooterContent` | same | same |
131
+ | Bottom items | `config.bottomItems` | same | same |
132
+ | Footer | profile + (toggle inline, or version + toggle) | footer hidden (`isMobile`) | avatar, then toggle + `versionText` (56px wide) |
133
+ | Switch panel | animated drop-down inside a bordered card + 90% backdrop dim | **not rendered** | 240px flyout at `left: calc(100% + 8px)`, fixed transparent click-catcher |
134
+
135
+ ### Recipes
136
+
137
+ ```tsx
138
+ // The idiomatic host wiring (what CXO, Photogenix and Catalogix all do):
139
+ // nav rendered through bodyContent, empty config, no version, toggle in profile.
140
+ <StreamoidSidebar
141
+ expanded={!collapsed}
142
+ onToggle={() => setCollapsed((v) => !v)}
143
+ config={{ sections: [] }}
144
+ iconMap={{}}
145
+ showBody
146
+ bodyContent={<MyNav collapsed={collapsed} />}
147
+ expandedLogo={<ScSideBarLogoUnit wordmark={<MyWordmark />} onClick={toggleAppList} />}
148
+ collapsedLogo={<ScSideBarLogoUnit state="collapsed" wordmark={<MyMark />} onClick={toggleAppList} hover={appListOpen ? true : undefined} />}
149
+ switchPanel={apps.length ? <ScAppSwitchPanel apps={apps} /> : undefined}
150
+ switchPanelOpen={appListOpen}
151
+ onSwitchPanelClose={() => setAppListOpen(false)}
152
+ profile={{ name, subtitle: workspace, avatar: <ScDp type="initial" initial={initial} size={32} />, onClick: openProfile }}
153
+ versionText=""
154
+ toggleInProfile
155
+ toggleIcon={(exp) => <SiconCollapse size={20} style={exp ? undefined : { transform: "rotate(180deg)" }} />}
156
+ />
157
+
158
+ // Config-driven nav (the JSON path — see sidebar-config.example.json)
159
+ <StreamoidSidebar
160
+ expanded={expanded}
161
+ onToggle={toggle}
162
+ config={sidebarConfigJson as SidebarConfig}
163
+ iconMap={{ plus: <SiconPlus />, dot: ({ active }) => <Dot on={active} /> }}
164
+ activeItemId={activeId}
165
+ onItemSelect={(item, sectionId) => route(sectionId, item.id)}
166
+ versionText=""
167
+ />
168
+
169
+ // Gradient assistant CTA + low-credits banner in the pre-footer
170
+ <StreamoidSidebar
171
+ /* … */
172
+ assistantCta={{ label: "Ask CXO", icon: <SiconBolt size={20} color="currentColor" />, active: askOpen, onClick: toggleAsk }}
173
+ preFooterContent={
174
+ lowCredits ? (
175
+ <CreditWarningBanner
176
+ availableCredits={credits}
177
+ remainingPct={Math.round(pct)}
178
+ level="danger"
179
+ expanded={expanded}
180
+ onBuyCredits={goBilling}
181
+ onCollapsedClick={onToggle}
182
+ />
183
+ ) : undefined
184
+ }
185
+ />
186
+ ```
187
+
188
+ ---
189
+
190
+ ## 2. Where to use it
191
+
192
+ - **The primary left navigation of every desktop app.** CXO
193
+ (`src/app/components/app-sidebar.tsx`), Photogenix
194
+ (`dashboard/client/src/components/layout/Sidebar.tsx`) and Catalogix
195
+ (`app/containers/LeftMenu/index.jsx`) each render exactly one, at the root of
196
+ their app shell.
197
+ - It composes, or expects you to compose into its slots:
198
+ `ScSideBarLogoUnit` (logo slots), `ScAppSwitchPanel` (`switchPanel`),
199
+ `ScSidebarMenu` (rendered internally from `config`, or by you inside
200
+ `bodyContent`), `ScAskAgentSlot` (from `assistantCta`),
201
+ `CreditWarningBanner` (`preFooterContent`), `ScDp` (`profile.avatar`).
202
+ - `ScCatalogixSidebar` is a thin wrapper around it. Artifax deliberately does
203
+ **not** use it (`ScArtifaxSidebar` instead).
204
+
205
+ ---
206
+
207
+ ## 3. When to use it
208
+
209
+ ### Use it when
210
+
211
+ - You need the shared app-shell rail: 256/56px, rounded card, dividers, profile
212
+ footer, collapse toggle, animated app-switch overlay.
213
+ - You want the app switcher to behave identically everywhere — as an overlay,
214
+ never pushing the nav down.
215
+ - Your nav is either a plain data structure (`config`) *or* app-specific enough
216
+ that you'd rather render it yourself (`bodyContent`) while keeping the chrome.
217
+
218
+ ### Don't use it — reach for this instead
219
+
220
+ | Situation | Use instead |
221
+ |---|---|
222
+ | Artifax's sidebar (collapsible labelled sections, no `switchPanel`) | `ScArtifaxSidebar` |
223
+ | Catalogix with default Stores/AI-Training/Insights nav | `ScCatalogixSidebar` (a wrapper — but Catalogix now composes `StreamoidSidebar` directly) |
224
+ | One nav row | `ScSidebarMenu` |
225
+ | One product-switch row (icon + name + description) | `ScSidebarSwitchMenu` |
226
+ | The product-switch list itself | `ScAppSwitchPanel` |
227
+ | The logo + switch chevron row | `ScSideBarLogoUnit` |
228
+ | Mobile top bar / bottom action bar | `ScMobileTopNav` / `ScMobileBottomAction` |
229
+ | A settings-page left nav | `ScSettingsNav` / `ScSettingsTabComp` |
230
+ | The workspace switcher | `ScWorkspaceSwitchCard` / `StreamoidWorkspaceSwitcher` |
231
+
232
+ ### Don't confuse with
233
+
234
+ | You may actually want | Not this |
235
+ |---|---|
236
+ | `ScSidebar` — a legacy, pre-`StreamoidSidebar` shell with only `state` + `onToggle` | `StreamoidSidebar` is the current one |
237
+ | `ScSidebarMenu` — one row | This is the whole rail |
238
+ | `ScLogoUnit` — legacy logo part | Use `ScSideBarLogoUnit` |
239
+ | `ScSidebarProfile` / `ScSidebarSwitchMenu` — à-la-carte legacy parts | This renders the profile row itself |
240
+ | `ScArtifaxSidebar` — sibling shell, **not** a wrapper of this one | Different props (`sections`, `openSections`), no `switchPanel` |
241
+ | `ScCatalogixSidebar` — **is** a wrapper of this one | Hides most props behind Catalogix defaults |
242
+
243
+ ---
244
+
245
+ ## 4. Why to use it
246
+
247
+ - **The two rails are already correct.** Expanded and collapsed are separate
248
+ render branches with different padding, gaps, divider rules and footer layout.
249
+ Reimplementing that from a design spec is a day of pixel-chasing.
250
+ - **The switch-panel overlay is the hard part, and it's done.** Expanded, it
251
+ animates via `grid-template-rows: 0fr → 1fr` inside an absolutely-positioned
252
+ card so it floats instead of pushing the nav down; the rest of the rail dims to
253
+ 90%. Collapsed, it becomes a 240px flyout with a fixed transparent click-catcher.
254
+ Both stay mounted so *closing* animates too.
255
+ - **Slot design, not a fork.** Every app has a different nav; `bodyContent`,
256
+ `preFooterContent`, `switchPanel` and the logo slots let you keep the chrome and
257
+ own the content, which is why all three apps converged on one component.
258
+ - **Token-only surfaces.** `--alias-surface-base`, `--alias-border-subtle`,
259
+ `--alias-fill-neutral-neutral` for hover — it flips to light mode with no
260
+ conditionals in your code.
261
+ - **Layout-stable toggling.** The switch card uses `inset box-shadow` rather than
262
+ `border` so opening it can't shift a single pixel of the card below.
263
+
264
+ ---
265
+
266
+ ## Gotchas
267
+
268
+ **1. `versionText` defaults to `"v1.0.0"`.** A hardcoded fake version in your
269
+ production nav. Every host passes `""`.
270
+
271
+ ```tsx
272
+ // WRONG — ships "v1.0.0"
273
+ <StreamoidSidebar expanded onToggle={t} config={c} iconMap={m} />
274
+
275
+ // RIGHT
276
+ <StreamoidSidebar expanded onToggle={t} config={c} iconMap={m} versionText="" />
277
+ ```
278
+
279
+ **2. `bodyContent` needs `showBody`, and then it replaces `config.sections`.**
280
+
281
+ ```tsx
282
+ // WRONG — body silently dropped, empty sections render instead
283
+ <StreamoidSidebar config={{ sections: [] }} bodyContent={<MyNav />} … />
284
+
285
+ // RIGHT
286
+ <StreamoidSidebar config={{ sections: [] }} showBody bodyContent={<MyNav />} … />
287
+ ```
288
+
289
+ **3. `toggleInProfile` without `profile` removes the toggle entirely.** The
290
+ expanded footer is either *(profile + inline toggle)* or *(version + toggle)*;
291
+ `toggleInProfile` selects the first branch, and that branch only renders inside
292
+ `profile ? … : null`. No profile → no collapse affordance at all.
293
+
294
+ **4. There is no default `toggleIcon`.** `resolveToggleIcon` returns `null` when
295
+ `toggleIcon` is omitted, so the toggle becomes an invisible (but clickable) box.
296
+ Always pass a glyph, and rotate it yourself for the collapsed state.
297
+
298
+ **5. The logo → nav divider is only drawn on the `topItems` path.** With no
299
+ `topItems`/`config.topItem` and `showBody`, nothing separates the logo from your
300
+ body — which is why all three hosts emit their own `<ScHDivider />` as the first
301
+ child of `bodyContent`.
302
+
303
+ **6. `hideFooter` and `isMobile` are asymmetric.** Expanded uses
304
+ `hideFooter || isMobile`; the **collapsed** branch checks `hideFooter` only. A
305
+ collapsed mobile rail still renders the version/toggle footer.
306
+
307
+ **7. `switchPanel` is dead when `isMobile`.** `hasSwitchCard = !!(switchPanel && !isMobile)`
308
+ — so on mobile the logo row's own toggle is your only affordance, and
309
+ `onSwitchPanelClose` never fires.
310
+
311
+ **8. `moreIcon` / `onMoreClick` / `highlightedItemIds` are dropped for section and
312
+ bottom items in the *expanded* rail.** They are only forwarded to `topItems` there,
313
+ while the collapsed rail forwards them everywhere. A "…" menu that appears when
314
+ collapsed and vanishes when expanded is this, not your code.
315
+
316
+ ```tsx
317
+ // Expanded sections get no more-menu. Render your rows yourself if you need one:
318
+ <StreamoidSidebar showBody bodyContent={
319
+ items.map((i) => <ScSidebarMenu key={i.id} text={i.label} moreIcon={<SiconMore />} onMoreClick={…} />)
320
+ } … />
321
+ ```
322
+
323
+ **9. Section labels only exist in the expanded rail.** `SectionHeader` isn't
324
+ rendered in the collapsed branch — don't put meaning-bearing text there only.
325
+
326
+ **10. The profile row and the collapse toggle are `div`s with `onClick`.** No
327
+ `<button>`, no `tabIndex`, no keyboard access, and hover is wired through
328
+ `onMouseEnter`/`onMouseLeave` writing inline styles. The root is a plain `div` with
329
+ no `<nav>`/`aria-label`; wrap it yourself:
330
+
331
+ ```tsx
332
+ <nav aria-label="Main navigation"><StreamoidSidebar … /></nav>
333
+ ```
334
+
335
+ **11. Widths are hardcoded.** Expanded card `width: 256`, inside a
336
+ `var(--spacing-3xl)` padded wrapper (≈288px footprint); the collapsed footer block
337
+ is `width: 56`. Not responsive, not overridable via props — hosts measure these
338
+ constants themselves.
339
+
340
+ **12. Tailwind utilities are a hard requirement.** The component's structure is
341
+ `className="flex flex-col flex-1 min-h-0 overflow-y-auto shrink-0 truncate …"` with
342
+ inline styles only for colours/spacing. `dist/index.css` ships the
343
+ `.text-text-*` typography classes but **none** of those utilities, and no
344
+ `.sidebar-scroll` rule either (that class is a hook for the host to style the
345
+ scrollbar). Without Tailwind the rail collapses and labels stop truncating.
346
+
347
+ **13. The collapsed flyout is `position: absolute` inside the rail.** Any host
348
+ ancestor with `overflow: hidden` will clip it. Hosts that can't guarantee that
349
+ (Artifax) portal their own flyout to `document.body` instead.
350
+
351
+ **14. `iconMap` misses fail silently.** A wrong `iconKey` yields `null` — in the
352
+ collapsed rail that is a blank, still-clickable row.
353
+
354
+ ---
355
+
356
+ ## In the wild
357
+
358
+ ```tsx
359
+ // cxo-dashboard src/app/components/app-sidebar.tsx:965
360
+ <StreamoidSidebar
361
+ expanded={sidebarExpanded}
362
+ onToggle={onToggle}
363
+ isMobile={isMobile}
364
+ showBody={showCopilotPanel}
365
+ config={isMobile ? MOBILE_SIDEBAR_CONFIG : BASE_SIDEBAR_CONFIG}
366
+ iconMap={iconMap}
367
+ onItemSelect={onItemSelect}
368
+ expandedLogo={<ExpandedLogo onClick={toggleAppList} />}
369
+ collapsedLogo={
370
+ <div style={{ paddingBottom: "var(--spacing-md)" }}>
371
+ <CollapsedLogo onClick={toggleAppList} open={appListOpen} />
372
+ </div>
373
+ }
374
+ bodyContent={sidebarContent}
375
+ switchPanel={switchPanelContent}
376
+ switchPanelOpen={appListOpen}
377
+ onSwitchPanelClose={() => setAppListOpen(false)}
378
+ preFooterContent={creditWarning ? <CreditWarningBanner … /> : undefined}
379
+ profile={{ name: displayName, subtitle: displayWorkspace, avatar: <ProfileAvatar … />, onClick: … }}
380
+ versionText=""
381
+ toggleInProfile
382
+ toggleIcon={(exp) => <SiconCollapse className="size-5" style={exp ? undefined : { transform: "rotate(180deg)" }} />}
383
+ />
384
+ ```
385
+
386
+ Also: `catalogix/dashboard app/containers/LeftMenu/index.jsx:991` (adds
387
+ `assistantCta` + `activeItemId`), and
388
+ `photogenix_v2 dashboard/client/src/components/layout/Sidebar.tsx:854`
389
+ (`preFooterContent` carries an upgrade CTA + low-credits node).
390
+
391
+ ---
392
+
393
+ ## Related
394
+
395
+ - `ScSideBarLogoUnit` — what goes in `expandedLogo` / `collapsedLogo`.
396
+ - `ScAppSwitchPanel` — what goes in `switchPanel`.
397
+ - `ScSidebarMenu` — the row this renders from `config`; render it yourself inside `bodyContent`.
398
+ - `ScAskAgentButton` / `ScAskAgentSlot` — what `assistantCta` renders.
399
+ - `CreditWarningBanner` — the usual `preFooterContent`.
400
+ - `ScArtifaxSidebar` — the sibling shell used by Artifax.
401
+ - `ScCatalogixSidebar` — a Catalogix-flavoured wrapper of this component.
402
+ - `ScSidebar` / `ScLogoUnit` / `ScSidebarProfile` / `ScSidebarSwitchMenu` — legacy parts this replaced.
403
+ - `sidebar-config.example.json` (this folder) — a complete `SidebarConfig` to copy.