@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.
Files changed (134) hide show
  1. package/README.md +35 -18
  2. package/dist/docs/AGENTS.md +325 -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 +210 -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/ScWorkspaceAccountMenu.md +115 -0
  120. package/dist/docs/ScWorkspaceCard.md +234 -0
  121. package/dist/docs/ScWorkspaceSettingsMobile.md +265 -0
  122. package/dist/docs/ScWorkspaceSwitchCard.md +312 -0
  123. package/dist/docs/ScWorkspaceSwitchMobile.md +241 -0
  124. package/dist/docs/ScWorkspaceSwitchMobileV2.md +278 -0
  125. package/dist/docs/StreamoidSidebar.md +413 -0
  126. package/dist/docs/StreamoidWorkspaceSwitcher.md +307 -0
  127. package/dist/docs/UsageHistoryMobile.md +235 -0
  128. package/dist/docs/components.json +4931 -0
  129. package/dist/index.css +361 -36
  130. package/dist/index.d.mts +213 -88
  131. package/dist/index.d.ts +213 -88
  132. package/dist/index.js +2486 -1629
  133. package/dist/index.mjs +2487 -1620
  134. package/package.json +5 -3
@@ -0,0 +1,262 @@
1
+ ---
2
+ component: ScArtifaxInvite
3
+ package: "@streamoid/ui"
4
+ category: cards
5
+ status: stable
6
+ renders: div
7
+ tags: [invite, app-access, artifax, toggle, permissions, user-management, settings, onboarding, row]
8
+ related: [ScPhtogenixInvite, ScCatalogixInvite, ScAppCard, ScToggleSwitch, ScAccess]
9
+ do_not_confuse_with: [ScPhtogenixInvite, ScCatalogixInvite, ScAppCard, ScAppListingCard, ScAppCardV3, ScAppCardForCopilot, ScAccess]
10
+ ---
11
+
12
+ # ScArtifaxInvite
13
+
14
+ **One "does this user get Artifax?" row for the invite / edit-user modal.** An
15
+ `ScAppCard` (icon + app name + description) on the left, an `ScToggleSwitch` on the
16
+ right, a hairline divider underneath. Nothing else — no store scoping, no roles.
17
+
18
+ ## TL;DR for agents
19
+
20
+ - **Reach for it when:** you are building the per-app access list in an invite or
21
+ edit-user flow and need the **Artifax** row (or any app whose access is a plain
22
+ on/off with no sub-scope).
23
+ - **Don't reach for it when:** the app needs sub-scoping under the toggle
24
+ (→ `ScCatalogixInvite`, which adds the store-access panel), it's the Photogenix
25
+ row (→ `ScPhtogenixInvite`, **different prop names**), or you just want to
26
+ display an app without a toggle (→ `ScAppCard`).
27
+ - **Four things that will bite you:**
28
+ 1. ⚠️ It defaults to **ON**: `active` defaults to `"true"`. Pass `enabled`
29
+ explicitly, always.
30
+ 2. ⚠️ Every string defaults to Artifax placeholder copy — `appName="Artifax"`,
31
+ `appDescription="Info about artifax"`. That description is Figma filler, not
32
+ product copy.
33
+ 3. `active` is the string `"true"`/`"false"` (a Figma variant), `enabled` is the
34
+ boolean. `enabled` wins when defined. Use `enabled`.
35
+ 4. It's fully controlled: `onToggle(next)` fires, the component does not change
36
+ its own state.
37
+
38
+ ---
39
+
40
+ ## 1. How to use it
41
+
42
+ ### Import
43
+
44
+ ```tsx
45
+ import { ScArtifaxInvite } from "@streamoid/ui";
46
+ import "@streamoid/ui/dist/index.css"; // once, at your app root
47
+ ```
48
+
49
+ ### Minimal usage
50
+
51
+ ```tsx
52
+ <ScArtifaxInvite
53
+ enabled={artifaxOn}
54
+ onToggle={setArtifaxOn}
55
+ appDescription="Design brief and asset workflows"
56
+ />
57
+ ```
58
+
59
+ ### Props
60
+
61
+ | Prop | Type | Default | Notes |
62
+ |---|---|---|---|
63
+ | `enabled` | `boolean` | – | The real on/off. Overrides `active` whenever it is not `undefined`. **Use this one.** |
64
+ | `active` | `"true"` \| `"false"` | `"true"` | ⚠️ Figma-variant leftover, a *string*. Only consulted when `enabled` is `undefined` — so the row renders **ON** by default. |
65
+ | `onToggle` | `(active: boolean) => void` | – | Fires with the **next** value. No-op if omitted: the toggle then looks live but never changes. |
66
+ | `appName` | `string` | `"Artifax"` | ⚠️ Real default. Passed to `ScAppCard`. |
67
+ | `appDescription` | `string` | `"Info about artifax"` | ⚠️ Real default, and it is placeholder copy. Always set it. |
68
+ | `appIcon` | `JSX.Element` | `<SiconArtifacts />` | ⚠️ Has a default. Correct for Artifax; wrong if you reuse this row for another app. |
69
+ | `className` | `string` | – | Appended after internal classes. |
70
+ | `...props` | `Omit<HTMLAttributes<HTMLDivElement>, "onToggle">` | – | Spread onto the root `<div>` — `onClick`, `style`, `data-*` all work here (unlike `ScPhtogenixInvite`). |
71
+
72
+ ### Recipes
73
+
74
+ ```tsx
75
+ // The standard app-access list: one controlled row per app
76
+ const [access, setAccess] = useState({ artifax: false, photogenix: false });
77
+
78
+ <ScArtifaxInvite
79
+ enabled={access.artifax}
80
+ onToggle={(next) => setAccess((a) => ({ ...a, artifax: next }))}
81
+ appDescription="Design brief and asset workflows"
82
+ />
83
+ <ScPhtogenixInvite
84
+ enabled={access.photogenix}
85
+ onToggle={(next) => setAccess((a) => ({ ...a, photogenix: next }))}
86
+ scAppCardappDescription="AI product photography" // note the prop names
87
+ scAppCardappIcon={<SiconPhotogenix size={24} />}
88
+ />
89
+
90
+ // Reusing the row for a different app (it is app-agnostic apart from its defaults)
91
+ <ScArtifaxInvite
92
+ appName="Tactix"
93
+ appDescription="Merch planning and allocation"
94
+ appIcon={<SiconArtifacts size={24} />}
95
+ enabled={tactixOn}
96
+ onToggle={setTactixOn}
97
+ />
98
+
99
+ // Mark the last row so you can drop the divider from your side
100
+ <ScArtifaxInvite
101
+ enabled={artifaxOn}
102
+ onToggle={setArtifaxOn}
103
+ className={styles.lastRow} // .lastRow { border-bottom-width: 0 }
104
+ />
105
+ ```
106
+
107
+ ---
108
+
109
+ ## 2. Where to use it
110
+
111
+ - **The invite-user modal's "App access" section** — a stacked list of app rows,
112
+ one per product, inside `ScModal`/`ScDrawer`.
113
+ - **The edit-user / manage-access drawer** — same list, pre-populated.
114
+ - **First-run onboarding** ("which products do you want enabled?").
115
+
116
+ It composes `ScAppCard` (flexed to fill) + `ScToggleSwitch` (`flex-shrink: 0`) and
117
+ draws its own bottom divider, so a vertical stack of these rows needs no
118
+ `ScHDivider` between them.
119
+
120
+ ---
121
+
122
+ ## 3. When to use it
123
+
124
+ ### Use it when
125
+
126
+ - Access to the app is a **single boolean** with no sub-resource to pick.
127
+ - You want the row to match the Catalogix/Photogenix rows beside it — same
128
+ padding (`--spacing-3xl`), same divider, same toggle geometry.
129
+
130
+ ### Don't use it — reach for this instead
131
+
132
+ | Situation | Use instead |
133
+ |---|---|
134
+ | The app's access needs a nested resource picker (stores, projects) | `ScCatalogixInvite` |
135
+ | The Photogenix row | `ScPhtogenixInvite` — same layout, **prefixed** prop names |
136
+ | App icon + name + description with **no** toggle | `ScAppCard` |
137
+ | A read-only "which apps does this user have" indicator (icons only) | `ScAccess` |
138
+ | A marketing/launcher card for an app | `ScAppListingCard` / `ScAppCardV3` |
139
+ | The app card inside the copilot/agent surface | `ScAppCardForCopilot` |
140
+ | Just the switch, in your own row | `ScToggleSwitch` |
141
+ | A labelled boolean inside a normal form | `ScCheckField` / `ScCheckbox` |
142
+ | Switching the *current* app | `ScAppSwitchPanel` |
143
+
144
+ ### Don't confuse with
145
+
146
+ | You may actually want | Not this |
147
+ |---|---|
148
+ | `ScCatalogixInvite` — same header row **plus** a store-access panel that appears when enabled; defaults to **OFF** | `ScArtifaxInvite` is header-only and defaults to **ON** |
149
+ | `ScPhtogenixInvite` — identical markup, but props are `scAppCardappName` / `scAppCardappDescription` / `scAppCardappIcon`, and it **drops** `...props` | `ScArtifaxInvite` uses clean `appName`/`appDescription`/`appIcon` and forwards DOM props |
150
+ | `ScAppCard` — the inner icon/name/description block, no toggle, no divider | This row wraps one |
151
+ | `ScAccess` — up to three app icons in a row, read-only, no labels | Not an editable access control |
152
+
153
+ The trio is **not** app-locked: nothing but the default strings and the default
154
+ icon ties `ScArtifaxInvite` to Artifax. Pick the one whose *shape* you need
155
+ (plain row vs. row + sub-scope), then override the copy.
156
+
157
+ ---
158
+
159
+ ## 4. Why to use it
160
+
161
+ - **The row is the design.** 16px padding, 8px gap, a 0.5px
162
+ `--alias-border-divider` bottom hairline, `ScAppCard` flexed to `1` and the
163
+ toggle pinned at `flex-shrink: 0` — that's the whole spec, and getting the
164
+ divider/flex pair wrong is exactly how these lists end up ragged.
165
+ - **The toggle is the shared one.** `ScToggleSwitch` renders the two exact Figma
166
+ SVG states (on: base fill + inverse knob; off: neutral fill + divider stroke),
167
+ token-coloured, so it matches every other switch in the product across themes.
168
+ - **`enabled`/`onToggle` is a plain controlled-boolean contract**, which is what
169
+ an invite form wants: one piece of state per app, submitted together.
170
+ - **One place to change.** `@streamoid/settings` currently hand-rolls this row
171
+ three times over (with its own local `ToggleSwitch`); adopting these components
172
+ is how that duplication goes away.
173
+
174
+ ---
175
+
176
+ ## Gotchas
177
+
178
+ **1. It renders ON unless you say otherwise.** `active = "true"` is the default and
179
+ `enabled` is `undefined` until you pass it.
180
+
181
+ ```tsx
182
+ // WRONG — an invite form that starts with Artifax already granted
183
+ <ScArtifaxInvite onToggle={setArtifaxOn} />
184
+
185
+ // RIGHT
186
+ <ScArtifaxInvite enabled={artifaxOn} onToggle={setArtifaxOn} />
187
+ ```
188
+
189
+ Note the inconsistency across the trio: `ScArtifaxInvite` and `ScPhtogenixInvite`
190
+ default to `"true"`, `ScCatalogixInvite` defaults to `"false"`.
191
+
192
+ **2. `active` is a string, not a boolean.** `active={true}` is a type error and
193
+ `active="false"` reads as off — but any `enabled` you pass overrides it. Treat
194
+ `active` as legacy and never mix the two.
195
+
196
+ ```tsx
197
+ // WRONG — mixed sources of truth; `enabled` silently wins
198
+ <ScArtifaxInvite active="false" enabled={artifaxOn} onToggle={setArtifaxOn} />
199
+
200
+ // RIGHT
201
+ <ScArtifaxInvite enabled={artifaxOn} onToggle={setArtifaxOn} />
202
+ ```
203
+
204
+ **3. `appDescription` defaults to `"Info about artifax"`.** That is Figma filler
205
+ that will ship to users. Same for the `ScAppCard` fallbacks underneath it.
206
+
207
+ **4. It is stateless.** Omit `onToggle` and the switch is a dead pixel — clicking
208
+ does nothing, because `checked` is recomputed from your props on every render.
209
+
210
+ **5. You cannot disable the toggle.** `ScToggleSwitch` supports `disabled`, but
211
+ this row never forwards it and exposes no such prop. For a read-only row, render
212
+ `ScAppCard` + your own indicator, or gate the whole section.
213
+
214
+ **6. Only the switch is clickable.** The root `<div>` gets no handler, `role` or
215
+ keyboard affordance of its own (you can spread your own `onClick` via `...props`,
216
+ but nothing focusable appears); the switch itself is a `div` with `onClick` (not a
217
+ real `<input type="checkbox">`), so there is **no keyboard or screen-reader
218
+ support**. If access control has to be operable by keyboard, wrap or replace it —
219
+ `ScCheckField` gives you real semantics.
220
+
221
+ **7. `styles["active-true"]` doesn't exist.** The stylesheet only defines
222
+ `.active-false` (which merely thickens the divider from 0.5px to 1px), so in the
223
+ ON state the variant class resolves to `undefined` and lands in the class
224
+ attribute as the literal string `"undefined"` — along with `className` when you
225
+ omit it. Don't write selectors or snapshot assertions against the class list.
226
+
227
+ **8. Rows carry their own bottom divider.** Stacking these gives you the
228
+ separators for free, but the last row also draws one. Strip it with `className`.
229
+
230
+ ---
231
+
232
+ ## In the wild
233
+
234
+ _No host render site found — used by the agent runtime / composed internally._
235
+
236
+ The surface it models is real but hand-rolled in the shared settings package,
237
+ which reimplements this row (and its own local `ToggleSwitch`) instead of
238
+ importing it:
239
+
240
+ ```tsx
241
+ // @streamoid/settings packages/settings/src/invite-update-modal.tsx:617
242
+ <ToggleSwitch // local component, not ScToggleSwitch
243
+ active={artifaxOn}
244
+ onClick={() => { setArtifaxOn(!artifaxOn); markChanged(); }}
245
+ />
246
+ // …wrapped in hand-written markup that duplicates ScAppCard, including the
247
+ // literal strings "Artifax" and "Info about artifax" (lines ~596-620).
248
+ // The local ToggleSwitch is defined at invite-update-modal.tsx:84.
249
+ ```
250
+
251
+ That modal (rendered by CXO settings → Teams → invite/edit user) is where this
252
+ component belongs.
253
+
254
+ ---
255
+
256
+ ## Related
257
+
258
+ - `ScPhtogenixInvite` — the Photogenix twin; watch the `scAppCard*` prop prefix.
259
+ - `ScCatalogixInvite` — the same row plus a store-access panel when enabled.
260
+ - `ScAppCard` — the icon/name/description block inside it.
261
+ - `ScToggleSwitch` — the switch on the right, if you need it standalone.
262
+ - `ScAccess` — read-only app-access icon row.
@@ -0,0 +1,330 @@
1
+ ---
2
+ component: ScArtifaxSidebar
3
+ package: "@streamoid/ui"
4
+ category: sidebar
5
+ status: stable
6
+ renders: div
7
+ tags: [sidebar, artifax, nav, shell, rail, collapsible-sections, accordion, collapse]
8
+ related: [StreamoidSidebar, ScSideBarLogoUnit, ScAppSwitchPanel, ScSidebarMenu, ScAskAgentButton, CreditWarningBanner]
9
+ do_not_confuse_with: [StreamoidSidebar, ScCatalogixSidebar, ScArtifaxInvite, ScSidebar]
10
+ used_by: [artifax]
11
+ required_props: [expanded, onToggle, sections]
12
+ ---
13
+
14
+ # ScArtifaxSidebar
15
+
16
+ **The Artifax app shell's sidebar — the one shell with collapsible, labelled nav
17
+ sections.** Same silhouette as `StreamoidSidebar` (256px rounded card / 56px rail,
18
+ logo slot, credit banner, gradient assistant CTA, profile + inline collapse toggle),
19
+ but the nav is an accordion of uppercase-labelled sections, and it has **no
20
+ `switchPanel` slot** — the app switcher is the host's job.
21
+
22
+ ## TL;DR for agents
23
+
24
+ - **Reach for it when:** you are working on the Artifax (or Tactix) shell, or you
25
+ need a sidebar whose sections the user can collapse.
26
+ - **Don't reach for it when:** you are in CXO / Photogenix / Catalogix
27
+ (→ `StreamoidSidebar`), or you need the built-in app-switch overlay
28
+ (`StreamoidSidebar`'s `switchPanel` — this component has none).
29
+ - **Four things that will bite you:**
30
+ 1. Uncontrolled open-state is captured **once, at mount**. Sections that arrive
31
+ later (async nav) render **closed**.
32
+ 2. Passing `openSections={{}}` switches it to controlled mode with **everything
33
+ closed** — `isControlled = !!openSections`, and `{}` is truthy.
34
+ 3. A missing `iconMap` entry renders a **dashed-circle placeholder**, not nothing.
35
+ 4. `.footerToggle > *` forces your toggle icon to **24×24 via CSS** — the `size`
36
+ prop on your `Sicon*` is overridden.
37
+
38
+ ---
39
+
40
+ ## 1. How to use it
41
+
42
+ ### Import
43
+
44
+ ```tsx
45
+ import {
46
+ ScArtifaxSidebar,
47
+ type ArtifaxSection,
48
+ type ArtifaxSidebarIconMap,
49
+ type ArtifaxSidebarAssistantCta,
50
+ } from "@streamoid/ui";
51
+ import "@streamoid/ui/dist/index.css"; // once, at your app root
52
+ ```
53
+
54
+ ### Minimal usage
55
+
56
+ ```tsx
57
+ <ScArtifaxSidebar
58
+ expanded={!collapsed}
59
+ onToggle={() => setCollapsed((v) => !v)}
60
+ sections={[
61
+ { id: "work", label: "Workspace", items: [{ id: "projects", label: "Projects", iconKey: "folder" }] },
62
+ ]}
63
+ iconMap={{ folder: <SiconFolder /> }}
64
+ activeItemId="projects"
65
+ onItemSelect={(item) => navigate(`/${item.id}`)}
66
+ />
67
+ ```
68
+
69
+ ### Props
70
+
71
+ | Prop | Type | Default | Notes |
72
+ |---|---|---|---|
73
+ | `expanded` | `boolean` | — | **Required.** `true` → 256px card inside a 16px padded wrapper; `false` → 56px rail with a right border. Separate render branches. |
74
+ | `onToggle` | `() => void` | — | **Required.** Fired by the footer toggle. |
75
+ | `sections` | `ArtifaxSection[]` | — | **Required.** `{ id, label?, collapsible?, defaultOpen?, items }[]`. A `ScHDivider` is drawn before every section except the first. |
76
+ | `iconMap` | `ArtifaxSidebarIconMap` | – | `Record<iconKey, ReactNode \| ({item, active, expanded}) => ReactNode>`. ⚠️ Missing key → dashed-circle placeholder icon. |
77
+ | `activeItemId` | `string` | – | Matching row gets `state="active"`; `active` is also passed to your icon renderer. |
78
+ | `onItemSelect` | `(item: ArtifaxMenuItem, sectionId: string) => void` | – | Fired on row click. |
79
+ | `expandedLogo` | `ReactNode` | – | Slot. Omit → **no logo at all** (there is no built-in default). |
80
+ | `collapsedLogo` | `ReactNode` | – | Slot, in a fixed 48×48 box. |
81
+ | `creditWarning` | `ArtifaxSidebarCreditWarning \| null` | – | `{ availableCredits, remainingPct, level?, onBuyCredits }`. `level` defaults to **`"danger"`** here. Renders `CreditWarningBanner`. |
82
+ | `assistantCta` | `ArtifaxSidebarAssistantCta \| null` | – | Alias of `AssistantCta`. Renders `ScAskAgentSlot` — the gradient "Ask CXO" pill / collapsed square. |
83
+ | `profile` | `ArtifaxSidebarProfile` | – | `{ name, subtitle?, avatar?, onClick? }`. Omit and an `aria-hidden` spacer keeps the toggle right-aligned. |
84
+ | `toggleIcon` | `ReactNode \| ((expanded: boolean) => ReactNode)` | `<SiconCollapse />` | ⚠️ **Has a real default** (unlike `StreamoidSidebar`). It does **not** rotate when collapsed — do that yourself. |
85
+ | `openSections` | `Record<string, boolean>` | – | Presence switches to **controlled** accordion mode. |
86
+ | `onSectionToggle` | `(sectionId: string, open: boolean) => void` | – | Fires in **both** modes (uncontrolled too). |
87
+ | `className` | `string` | – | On the outer wrapper — Artifax uses it to scope per-app CSS. |
88
+ | `style` | `CSSProperties` | – | On the outer wrapper. |
89
+
90
+ ### Types
91
+
92
+ ```ts
93
+ interface ArtifaxMenuItem { id: string; label: string; iconKey: string }
94
+ interface ArtifaxSection {
95
+ id: string;
96
+ label?: string; // no label → no header, section is always open
97
+ collapsible?: boolean; // false → header renders but is disabled, always open
98
+ defaultOpen?: boolean; // read ONCE, at mount. Defaults to true.
99
+ items: ArtifaxMenuItem[];
100
+ }
101
+ interface ArtifaxSidebarCreditWarning { availableCredits: number; remainingPct: number; level?: "warning" | "danger"; onBuyCredits: () => void }
102
+ type ArtifaxSidebarAssistantCta = AssistantCta; // { label, icon?, active?, ariaLabel?, onClick }
103
+ ```
104
+
105
+ ### What renders in each rail
106
+
107
+ | Region | expanded | collapsed |
108
+ |---|---|---|
109
+ | Container | 256px, 16px radius, 1px `border-subtle` overlay, inside a 16px padded wrapper | 56px wide, `border-right` only, no wrapper padding |
110
+ | Logo | `expandedLogo`, 8px padding, full width | `collapsedLogo` in a 48×48 centred box |
111
+ | Section header | uppercase 10px/1px-tracked label + `SiconUp`/`SiconDown` chevron | **not rendered** |
112
+ | Items | `ScSidebarMenu variant="expanded"` with `text` | `variant="collapsed"`, `text` omitted |
113
+ | Collapsibility | honoured | **ignored** — all items always render |
114
+ | Credit banner | `CreditWarningBanner expanded` | `CreditWarningBanner expanded={false}` (40×40 square) |
115
+ | Assistant CTA | `ScAskAgentSlot` pill | `ScAskAgentSlot collapsed` square |
116
+ | Footer | profile (flex-1, hover bg) + toggle, `aria-label="Collapse sidebar"` | avatar above toggle, `aria-label="Expand sidebar"` |
117
+
118
+ ### Recipes
119
+
120
+ ```tsx
121
+ // Controlled accordion — persist which sections are open
122
+ const [openSections, setOpenSections] = useState<Record<string, boolean>>({ work: true, recent: false });
123
+
124
+ <ScArtifaxSidebar
125
+ expanded={!collapsed}
126
+ onToggle={() => setCollapsed((v) => !v)}
127
+ sections={sections}
128
+ openSections={openSections}
129
+ onSectionToggle={(id, open) => setOpenSections((p) => ({ ...p, [id]: open }))}
130
+ iconMap={iconMap}
131
+ />
132
+
133
+ // Icon renderer that tracks the active row
134
+ <ScArtifaxSidebar
135
+ /* … */
136
+ iconMap={{
137
+ folder: ({ active }) => (
138
+ <SiconFolder color={active ? "var(--alias-text---icons-primary)" : "var(--alias-text---icons-tertiary)"} />
139
+ ),
140
+ }}
141
+ />
142
+
143
+ // Gradient assistant CTA + low credits, plus a collapsed-aware toggle glyph
144
+ <ScArtifaxSidebar
145
+ /* … */
146
+ assistantCta={{ label: "Ask CXO", icon: <SiconBolt className="h-5 w-5" />, active: assistantOpen, onClick: toggleAssistant }}
147
+ creditWarning={{ availableCredits: 120, remainingPct: 4, onBuyCredits: goBilling }}
148
+ toggleIcon={(expanded) => <SiconCollapse className={expanded ? "" : "rotate-180"} />}
149
+ />
150
+
151
+ // The app switcher — this component has no slot for it, so portal your own
152
+ <>
153
+ <ScArtifaxSidebar expandedLogo={<div ref={anchorRef}><ScSideBarLogoUnit wordmark={wm} onClick={() => setOpen(v => !v)} /></div>} … />
154
+ {open ? createPortal(<Flyout anchorRef={anchorRef}><ScAppSwitchPanel apps={apps} /></Flyout>, document.body) : null}
155
+ </>
156
+ ```
157
+
158
+ ---
159
+
160
+ ## 2. Where to use it
161
+
162
+ - **The Artifax dashboard shell** — `packages/shared/src/components/DashboardSidebar.tsx`,
163
+ shared by the Artifax and Tactix front-ends. That file is the reference wiring for
164
+ everything this component deliberately leaves out (app switcher, profile menu,
165
+ workspace modal).
166
+ - It composes internally: `ScSidebarMenu`, `ScHDivider`, `CreditWarningBanner`,
167
+ `ScAskAgentSlot`, and `SiconCollapse`/`SiconUp`/`SiconDown`.
168
+ - You supply into its slots: `ScSideBarLogoUnit` (both logo slots), `ScDp`
169
+ (`profile.avatar`).
170
+
171
+ ---
172
+
173
+ ## 3. When to use it
174
+
175
+ ### Use it when
176
+
177
+ - You are in the Artifax/Tactix shell.
178
+ - The nav has **several labelled groups** the user should be able to collapse —
179
+ `StreamoidSidebar` has section labels but no accordion.
180
+ - You want the credit banner and assistant CTA wired by prop rather than by slot.
181
+
182
+ ### Don't use it — reach for this instead
183
+
184
+ | Situation | Use instead |
185
+ |---|---|
186
+ | CXO / Photogenix / Catalogix sidebar | `StreamoidSidebar` |
187
+ | You need the built-in app-switch overlay + backdrop | `StreamoidSidebar` (`switchPanel` / `switchPanelOpen`) |
188
+ | You want a free-form nav body instead of `sections` | `StreamoidSidebar` (`showBody` + `bodyContent`) |
189
+ | One nav row | `ScSidebarMenu` |
190
+ | The product-switch list | `ScAppSwitchPanel` (portal it yourself here) |
191
+ | Artifax's invite/onboarding card | `ScArtifaxInvite` — unrelated component |
192
+
193
+ ### Don't confuse with
194
+
195
+ | You may actually want | Not this |
196
+ |---|---|
197
+ | `StreamoidSidebar` — the shared shell; different prop names (`config`, `topItems`, `switchPanel`, `versionText`) | `ScArtifaxSidebar` is a **sibling**, not a wrapper |
198
+ | `ScCatalogixSidebar` — *is* a wrapper of `StreamoidSidebar` | This one is standalone |
199
+ | `ScArtifaxInvite` — the Artifax invite card | Nothing to do with navigation |
200
+ | `ScSidebar` — legacy shell | Superseded |
201
+
202
+ ---
203
+
204
+ ## 4. Why to use it
205
+
206
+ - **The accordion is already correct in both modes.** Controlled and uncontrolled,
207
+ `collapsible: false` headers that render but don't toggle, label-less sections that
208
+ are permanently open — three branches you'd otherwise re-derive.
209
+ - **Both rails are real designs, not a CSS `width` change.** The collapsed rail
210
+ drops labels and section headers, centres everything, and stacks avatar-over-toggle;
211
+ the expanded one puts the toggle inline in the profile row.
212
+ - **CSS-module scoped, token-only.** `--alias-surface-base`, `--alias-border-subtle`,
213
+ `--alias-fill-neutral-neutral` hovers, `--alias-text-and-icons-*` — light mode is free.
214
+ - **Real `<button>`s throughout.** Section headers, profile, toggle and the assistant
215
+ CTA are all native `<button type="button">`s — unlike `StreamoidSidebar`, whose
216
+ profile row and toggle are `div`s with `onClick`. (`aria-label` is set on the
217
+ footer toggle — `"Collapse sidebar"` / `"Expand sidebar"` — and on the collapsed
218
+ profile button; the section header and the expanded profile are labelled by their
219
+ own text.)
220
+ - **One assistant CTA implementation.** `assistantCta` renders the shared
221
+ `SC-AskAgentButton`, so the Artifax pill and the CXO/Catalogix pill cannot drift.
222
+
223
+ ---
224
+
225
+ ## Gotchas
226
+
227
+ **1. Uncontrolled open-state is frozen at mount.** The internal map is built in a
228
+ `useState` initialiser from `defaultOpen ?? true`. A section added later has no key,
229
+ `!!internalOpen[id]` is `false`, and it renders **closed** — the classic
230
+ "sections load from the API and are all collapsed" bug.
231
+
232
+ ```tsx
233
+ // WRONG — sections arrive after mount, all render closed
234
+ <ScArtifaxSidebar sections={sectionsFromApi} … />
235
+
236
+ // RIGHT — either don't mount until loaded…
237
+ {sectionsFromApi.length ? <ScArtifaxSidebar sections={sectionsFromApi} … /> : <Skeleton />}
238
+ // …or drive it controlled
239
+ <ScArtifaxSidebar sections={sectionsFromApi} openSections={open} onSectionToggle={set} … />
240
+ ```
241
+
242
+ **2. `openSections={{}}` means "controlled, all closed".** The mode switch is
243
+ `isControlled = !!openSections`, and `{}` is truthy. Never pass an empty object as a
244
+ placeholder.
245
+
246
+ **3. A missing `iconMap` key renders a dashed circle.** `resolveIcon` falls back to
247
+ `ArtifaxPlaceholderIcon` (a 20px box with a 10px dashed-border `::before`). It looks
248
+ like a deliberate design, so typo'd `iconKey`s survive review.
249
+
250
+ **4. CSS overrides your toggle icon's size.** `.footerToggle > * { width: 1.5rem; height: 1.5rem }`
251
+ wins over an svg's `width`/`height` attributes. `<SiconCollapse size={16} />` still
252
+ renders 24px. Use a wrapper with an inline style if you truly need another size.
253
+
254
+ **5. `toggleIcon` doesn't rotate itself.** The default is a bare `<SiconCollapse />`
255
+ in both states. Artifax passes `(expanded) => <SiconCollapse className={expanded ? "" : "rotate-180"} />`.
256
+
257
+ **6. The scroll body clips popovers.** `.body` is `overflow-y: auto; overflow-x: hidden`.
258
+ Anything you open from the logo slot, a nav row or the profile row must be portalled
259
+ to `document.body` — which is exactly why the Artifax host renders `SwitchFlyout` and
260
+ `ProfileMenu` as portals.
261
+
262
+ **7. There is no app-switcher slot.** No `switchPanel`, no `switchPanelOpen`, no
263
+ backdrop. Clicking `ScSideBarLogoUnit` in the logo slot only calls your handler; you
264
+ own the panel, its position and its outside-click.
265
+
266
+ **8. Collapsed ignores `collapsible` / `label` / `defaultOpen` entirely.** Every
267
+ item of every section renders. A section the user collapsed reappears when they
268
+ collapse the rail.
269
+
270
+ **9. `creditWarning.onCollapsedClick` doesn't exist here.** The collapsed 40×40
271
+ square falls back to `onBuyCredits`, so collapsing the rail turns the banner into a
272
+ "go to billing" button. (`StreamoidSidebar` + `CreditWarningBanner` directly *do*
273
+ support `onCollapsedClick`.) The banner also dismisses itself permanently on ✕ with
274
+ no callback — see `CreditWarningBanner`'s own README.
275
+
276
+ **10. Hardcoded English.** `"Collapse sidebar"` / `"Expand sidebar"` aria-labels, and
277
+ section labels are force-uppercased via `text-transform` (so `label` should be
278
+ written in sentence case, not shouted).
279
+
280
+ **11. No logo default.** Omit `expandedLogo`/`collapsedLogo` and the slot simply
281
+ isn't rendered — no fallback wordmark.
282
+
283
+ **12. Widths are hardcoded** — `16rem` expanded, `3.5rem` collapsed, 48×48 collapsed
284
+ logo box, 32×32 avatar. Not overridable by props.
285
+
286
+ ---
287
+
288
+ ## In the wild
289
+
290
+ ```tsx
291
+ // artifax packages/shared/src/components/DashboardSidebar.tsx:641
292
+ <ScArtifaxSidebar
293
+ className={nav.rootClassName}
294
+ expanded={!collapsed}
295
+ onToggle={() => setCollapsed((v) => !v)}
296
+ sections={nav.sections}
297
+ iconMap={nav.iconMap}
298
+ activeItemId={activeItemId}
299
+ onItemSelect={handleItemSelect}
300
+ expandedLogo={
301
+ <div ref={expandedSwitchRef} className="flex w-full flex-col">
302
+ <ScSideBarLogoUnit state="expanded" wordmark={nav.wordmark} onClick={() => setAppListOpen((v) => !v)} switchAriaLabel="Switch product" />
303
+ </div>
304
+ }
305
+ collapsedLogo={
306
+ <div ref={collapsedLogoRef} className="flex w-full flex-col items-center">
307
+ <ScSideBarLogoUnit state="collapsed" wordmark={nav.mark} onClick={() => setAppListOpen((v) => !v)} switchAriaLabel="Switch product" />
308
+ </div>
309
+ }
310
+ creditWarning={creditWarning}
311
+ assistantCta={
312
+ onToggleAssistant
313
+ ? { label: ASK_CXO_LABEL, icon: <SiconBolt className="h-5 w-5" />, active: assistantOpen, onClick: onToggleAssistant }
314
+ : undefined
315
+ }
316
+ profile={{ name: userName, subtitle: workspaceName, avatar: <ScDp type="initial" variant="profile" initial={userInitial} size={32} />, onClick: () => setProfileMenuOpen((v) => !v) }}
317
+ toggleIcon={(expanded) => <SiconCollapse className={expanded ? "" : "rotate-180"} />}
318
+ />
319
+ ```
320
+
321
+ ---
322
+
323
+ ## Related
324
+
325
+ - `StreamoidSidebar` — the shell every other desktop app uses; has `switchPanel`, `bodyContent`, `versionText`.
326
+ - `ScSideBarLogoUnit` — what goes in the two logo slots.
327
+ - `ScAppSwitchPanel` — the product-switch list you must portal yourself here.
328
+ - `ScAskAgentButton` / `ScAskAgentSlot` — what `assistantCta` renders.
329
+ - `CreditWarningBanner` — what `creditWarning` renders (read its gotchas: self-dismiss, raw `remainingPct`).
330
+ - `ScSidebarMenu` — the nav row rendered per item.