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