@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,232 @@
1
+ ---
2
+ component: ScSidebarIcons
3
+ package: "@streamoid/ui"
4
+ category: sidebar
5
+ status: legacy
6
+ renders: div
7
+ tags: [sidebar, icon, icon-button, hover, footer, collapse-toggle, legacy, wrapper]
8
+ related: [ScButton, ScVersion, StreamoidSidebar, ScSidebarMenu, ScOnlyIcon]
9
+ do_not_confuse_with: [ScOnlyIcon, ScSidebarMenu, ScAppcardLogos, ScButton]
10
+ ---
11
+
12
+ # ScSidebarIcons
13
+
14
+ **Legacy. A 8 px-padded, `radius-3xl` box around one icon, with a hover background —
15
+ and nothing else.** It drops every prop except `instance`, `state` and `className`, so
16
+ it cannot be clicked, focused or labelled. Superseded by
17
+ `ScButton styleVariant="icon-only"` for a real icon affordance, and by
18
+ `StreamoidSidebar`'s built-in collapse toggle for its original job.
19
+
20
+ ## TL;DR for agents
21
+
22
+ - **Reach for it when:** never. It has no click path.
23
+ - **Reach for this instead:** `ScButton styleVariant="icon-only" icon={…}` (a real,
24
+ keyboard-accessible icon affordance), or `StreamoidSidebar`'s `toggleIcon` /
25
+ `toggleInProfile` for the rail's collapse control. Note `ScOnlyIcon` is **not** a
26
+ general-purpose alternative — it hardcodes `SiconSettings` and has no `icon` prop.
27
+ - **Three things that will bite you:**
28
+ 1. ⚠️ **`...props` is destructured and never spread.** `onClick`, `aria-label`,
29
+ `data-*` are silently dropped. The interface doesn't even extend
30
+ `HTMLAttributes`, so TypeScript rejects `onClick` before you get that far.
31
+ 2. `instance` defaults to `<SiconSupport />`. Forget it and you ship a headset icon.
32
+ 3. It has a hover background but no `cursor: pointer`, no `role`, no `tabIndex`. It
33
+ looks like a button and is inert.
34
+
35
+ ---
36
+
37
+ ## 1. How to use it
38
+
39
+ ### Import
40
+
41
+ ```tsx
42
+ import { ScSidebarIcons, ScButton, StreamoidSidebar } from "@streamoid/ui";
43
+ import { SiconCollapse, SiconSupport } from "@streamoid/icons";
44
+ import "@streamoid/ui/dist/index.css"; // once, at your app root
45
+ ```
46
+
47
+ ### Minimal usage
48
+
49
+ ```tsx
50
+ // The entire useful API. Note: no onClick — see Gotcha 1.
51
+ <ScSidebarIcons instance={<SiconCollapse />} />
52
+ ```
53
+
54
+ ### Props
55
+
56
+ | Prop | Type | Default | Notes |
57
+ |---|---|---|---|
58
+ | `instance` | `JSX.Element` | `<SiconSupport />` | ⚠️ Has a real default. The single child; rendered verbatim (no `cloneElement`, no size or colour normalisation). |
59
+ | `state` | `"default"` \| `"hover"` | `"default"` | `"hover"` force-renders the `fill-neutral-neutral` background. Real `:hover` already does the same. |
60
+ | `className` | `string` | – | Concatenated onto the root. ⚠️ Unguarded — omitting it puts a literal `undefined` in the class list. |
61
+ | `...props` | – | – | ⚠️ **Destructured and discarded.** `IScSidebarIconsProps` does **not** extend `React.HTMLAttributes`, so there is no supported way to attach a handler or an aria attribute. |
62
+
63
+ The whole component is: `<div className="…">{instance}</div>` plus one hover rule.
64
+
65
+ ### Recipes
66
+
67
+ ```tsx
68
+ // You want a clickable icon → use ScButton in icon-only mode
69
+ <ScButton
70
+ styleVariant="icon-only"
71
+ icon={<SiconCollapse size={20} />}
72
+ variant="mono"
73
+ type="tertiary"
74
+ size="md"
75
+ onClick={toggleSidebar}
76
+ aria-label="Collapse sidebar"
77
+ />
78
+
79
+ // You want the rail's collapse control → StreamoidSidebar already renders it
80
+ <StreamoidSidebar
81
+ expanded={expanded}
82
+ onToggle={() => setExpanded((v) => !v)}
83
+ config={config}
84
+ iconMap={iconMap}
85
+ toggleIcon={(isExpanded) => <SiconCollapse style={{ transform: isExpanded ? undefined : "rotate(180deg)" }} />}
86
+ />
87
+
88
+ // You genuinely only want a padded, hover-tinted icon box (decorative)
89
+ <ScSidebarIcons instance={<SiconSupport />} />
90
+ ```
91
+
92
+ ---
93
+
94
+ ## 2. Where to use it
95
+
96
+ Nowhere. It has exactly two composition sites, both inside the design system and both
97
+ in the legacy family:
98
+
99
+ - `src/SC-version/ScVersion.tsx:34` and `:49` — the collapse chevron in the old rail's
100
+ version footer.
101
+ - `src/SC-Sidebar/ScSidebar.tsx:250` — passed into `ScVersion`'s `component` slot.
102
+
103
+ The current rail's collapse control lives inside `StreamoidSidebar` (driven by
104
+ `onToggle` / `toggleIcon` / `toggleInProfile`) and is a real interactive element.
105
+
106
+ ---
107
+
108
+ ## 3. When to use it
109
+
110
+ ### Use it when
111
+
112
+ - Never in new code. If you catch yourself wanting it, you want a clickable icon.
113
+
114
+ ### Don't use it — reach for this instead
115
+
116
+ | Situation | Use instead |
117
+ |---|---|
118
+ | A clickable bare icon affordance, any icon | `ScButton styleVariant="icon-only" icon={…}` |
119
+ | Specifically a settings-cog affordance | `ScOnlyIcon` — but be aware its icon is hardcoded to `SiconSettings` |
120
+ | The sidebar collapse/expand toggle | `StreamoidSidebar`'s `onToggle` + `toggleIcon` (or `toggleInProfile`) |
121
+ | An icon + label nav row in a rail | `ScSidebarMenu` |
122
+ | A trailing kebab on a nav row | `ScSidebarMenu`'s `moreIcon` + `onMoreClick` |
123
+ | A product/app logo mark | `ScAppcardLogos` / `ScSideBarLogoUnit` / `ScStreamoidMascot` |
124
+
125
+ ### Don't confuse with
126
+
127
+ | You may actually want | Not this |
128
+ |---|---|
129
+ | `ScOnlyIcon` — spreads its props (so `onClick` works) but hardcodes `SiconSettings` and exposes no `icon` prop | `ScSidebarIcons` takes any icon but is completely inert. Neither is a good general choice — use `ScButton styleVariant="icon-only"`. |
130
+ | `ScSidebarMenu` — the nav **row** (icon + label, five states) | this is just the icon chrome |
131
+ | `ScAppcardLogos` — branded product marks | this holds any icon, unstyled |
132
+
133
+ Note the plural: the folder is `SC-sidebar icons` and the export is `ScSidebarIcons`,
134
+ but it renders **one** icon. There is no multi-icon component behind that name.
135
+
136
+ ---
137
+
138
+ ## 4. Why to use it
139
+
140
+ You wouldn't. What it still encodes correctly:
141
+
142
+ - The rail's icon-slot geometry: `spacing-md` (8 px) padding, `radius-3xl` corners, so
143
+ a footer icon lines up with the 40 px collapsed nav squares above it.
144
+ - The hover fill token is `--alias-fill-neutral-neutral` — the same one
145
+ `ScSidebarMenu`, `ScSidebarSwitchMenu` and `ScSidebarProfile` use, so hovers across
146
+ the rail match.
147
+
148
+ `ScButton styleVariant="icon-only"` gives you comparable geometry *plus* a click path,
149
+ keyboard handling and `aria-disabled` wiring.
150
+
151
+ ---
152
+
153
+ ## Gotchas
154
+
155
+ **1. Props are silently dropped.** The component destructures `...props` and then never
156
+ uses it. The root div receives only `className`.
157
+
158
+ ```tsx
159
+ // WRONG — will not compile (no onClick on IScSidebarIconsProps) and would be
160
+ // dropped even if it did
161
+ <ScSidebarIcons instance={<SiconCollapse />} onClick={toggleSidebar} />
162
+
163
+ // RIGHT
164
+ <ScButton
165
+ styleVariant="icon-only"
166
+ icon={<SiconCollapse size={20} />}
167
+ variant="mono"
168
+ type="tertiary"
169
+ size="md"
170
+ onClick={toggleSidebar}
171
+ aria-label="Collapse sidebar"
172
+ />
173
+
174
+ // WORKAROUND if you must keep this component: wrap it
175
+ <div role="button" tabIndex={0} onClick={toggleSidebar} aria-label="Collapse sidebar">
176
+ <ScSidebarIcons instance={<SiconCollapse />} />
177
+ </div>
178
+ ```
179
+
180
+ **2. `instance` defaults to `<SiconSupport />`.** A live-support headset appears if you
181
+ forget it, or typo the prop name (it's `instance`, not `icon`).
182
+
183
+ **3. Interactive-looking, non-interactive.** There is a `:hover` background rule but no
184
+ `cursor: pointer`, no `role`, no `tabIndex`. Users will try to click it.
185
+
186
+ **4. No icon normalisation.** Unlike `ScButton`, your icon is rendered as-is — no
187
+ `cloneElement`, no `currentColor`, no `strokeWidth`. Set the icon's own `size` and
188
+ `color`, and size the box by wrapping or overriding via `className`.
189
+
190
+ **5. `className` produces a literal `undefined` class when omitted.**
191
+ `styles.scSidebarIcons + " " + className + " " + variantsClassName`.
192
+
193
+ **6. `state="hover"` is design parity only** — and `ScVersion`'s expanded branch passes
194
+ `state="hover"` unconditionally, so the old rail's collapse chevron is permanently
195
+ hover-skinned. Don't copy that.
196
+
197
+ **7. No intrinsic size.** The box is content-sized: `display: flex` + 8 px padding
198
+ around whatever `instance` measures. Two `ScSidebarIcons` with differently-sized icons
199
+ will be different sizes.
200
+
201
+ ---
202
+
203
+ ## In the wild
204
+
205
+ _No host render site found — used by the agent runtime / composed internally._
206
+
207
+ Composed only inside the legacy family:
208
+
209
+ ```tsx
210
+ // npm-components packages/ui/src/SC-version/ScVersion.tsx:49
211
+ <ScSidebarIcons
212
+ instance={<SiconCollapse className={styles.siconCollapseInstance} />}
213
+ state="hover"
214
+ className={styles.scSidebarIconsInstance}
215
+ />
216
+ ```
217
+
218
+ Where it *would* belong — an icon affordance in a sidebar footer — is now
219
+ `ScButton styleVariant="icon-only"`, or the collapse button `StreamoidSidebar` renders
220
+ itself.
221
+
222
+ ---
223
+
224
+ ## Related
225
+
226
+ - `ScButton` (`styleVariant="icon-only"`) — the clickable replacement. Use this.
227
+ - `ScOnlyIcon` — spreads props but hardcodes `SiconSettings`; only for a settings cog.
228
+ - `StreamoidSidebar` — owns the collapse toggle via `onToggle` / `toggleIcon`.
229
+ - `ScVersion` — the legacy footer that composes this; also legacy.
230
+ - `ScSidebarMenu` — the current, stable rail row.
231
+ - `@streamoid/icons` — every `Sicon*`; check `packages/icons/ICONS.md` before drawing
232
+ a new one.
@@ -0,0 +1,283 @@
1
+ ---
2
+ component: ScSidebarMenu
3
+ package: "@streamoid/ui"
4
+ category: sidebar
5
+ status: stable
6
+ renders: div
7
+ tags: [sidebar, nav, menu-item, nav-row, rail, collapsed, active, more-icon, kebab]
8
+ related: [StreamoidSidebar, ScSidebarSwitchMenu, ScSettingsNav, ScMenuOptions, ScSideBarLogoUnit]
9
+ do_not_confuse_with: [ScSidebarSwitchMenu, ScSidebar, ScMenuOptions, ScSettingsTabComp]
10
+ used_by: [cxo, photogenix, catalogix, artifax]
11
+ ---
12
+
13
+ # ScSidebarMenu
14
+
15
+ **One nav row in a sidebar rail.** Icon + label + an optional hover-only kebab, as a
16
+ 40 px-high pill that has an `active` skin, two "sort of active" skins, and a
17
+ 40 × 40 collapsed square. This is the only piece of the old à-la-carte sidebar family
18
+ that is still current — every desktop sidebar in the product is built out of it.
19
+
20
+ ## TL;DR for agents
21
+
22
+ - **Reach for it when:** you are laying out sidebar nav rows yourself, or building a
23
+ vertical nav that isn't the app rail (e.g. a settings nav, a panel's section list).
24
+ - **Don't reach for it when:** you want a whole app sidebar — use `StreamoidSidebar`
25
+ (it renders these rows for you from `config` + `iconMap`), or the branded
26
+ `ScCatalogixSidebar` / `ScArtifaxSidebar`. Don't reach for `ScSidebar` (legacy,
27
+ hardcoded).
28
+ - **Four things that will bite you:**
29
+ 1. It's a plain `<div>` with `cursor: pointer` — **no `role`, no `tabIndex`, no key
30
+ handling**. Keyboard and screen-reader users get nothing unless you wrap it.
31
+ 2. `variant="collapsed"` **drops the label entirely** and adds no `title`. There is
32
+ no built-in tooltip — hosts add their own.
33
+ 3. `icon` defaults to `<SiconHome />`. Forget it and you ship a house.
34
+ 4. `moreIcon` is `display: none` until `:hover`. It is unreachable on touch.
35
+
36
+ ---
37
+
38
+ ## 1. How to use it
39
+
40
+ ### Import
41
+
42
+ ```tsx
43
+ import { ScSidebarMenu } from "@streamoid/ui";
44
+ import { SiconDot } from "@streamoid/icons";
45
+ import "@streamoid/ui/dist/index.css"; // once, at your app root
46
+ ```
47
+
48
+ ### Minimal usage
49
+
50
+ ```tsx
51
+ <ScSidebarMenu
52
+ icon={<SiconDot size={24} />}
53
+ text="Insights"
54
+ state={isActive ? "active" : "default"}
55
+ variant={expanded ? "expanded" : "collapsed"}
56
+ onClick={() => go("insights")}
57
+ />
58
+ ```
59
+
60
+ ### Props
61
+
62
+ | Prop | Type | Default | Notes |
63
+ |---|---|---|---|
64
+ | `icon` | `JSX.Element` | `<SiconHome />` | ⚠️ Has a real default. Always rendered, in both variants. Clamped to 20 × 20 by CSS (`.scSidebarMenu > :first-child`) whatever size you pass the icon. |
65
+ | `text` | `string` | `"Menu Item"` | ⚠️ Has a real default. **Only rendered when `variant="expanded"`.** Single line, `text-overflow: ellipsis`. |
66
+ | `state` | `"default"` \| `"active"` \| `"hover"` \| `"default-highlight"` \| `"default-active"` | `"default"` | See the state table below. |
67
+ | `variant` | `"expanded"` \| `"collapsed"` | `"expanded"` | `collapsed` = fixed 40 × 40 square, icon centred, label and kebab omitted. |
68
+ | `moreIcon` | `ReactNode` | – | Trailing kebab/overflow affordance. **`expanded` only**, and hidden until the row is hovered. |
69
+ | `onMoreClick` | `(e: React.MouseEvent) => void` | – | Fires from the `moreIcon` wrapper; the wrapper calls `stopPropagation()` first, so the row's `onClick` does **not** also fire. |
70
+ | `className` | `string` | – | Appended after the internal class. |
71
+ | `...props` | `HTMLAttributes<HTMLDivElement>` | – | Spread onto the root div — this is how `onClick`, `style`, `aria-*`, `data-*`, `key` reach the DOM. |
72
+
73
+ ### What each `state` renders
74
+
75
+ | `state` | Background | Label colour | Used for |
76
+ |---|---|---|---|
77
+ | `default` | none (hover → `fill-neutral-neutral`) | tertiary | an ordinary row |
78
+ | `active` | `fill-neutral-neutralselected` | primary | the currently open item |
79
+ | `hover` | `fill-neutral-neutral` (forced) | tertiary | Figma parity / screenshots. Also force-reveals `moreIcon`. |
80
+ | `default-highlight` | none | primary | "has unread / needs attention" — brighter text, no fill |
81
+ | `default-active` | `surface-canvas` + a 0.5 px inset ring | primary | this section is the active one but the real selection is a child item |
82
+
83
+ ### Recipes
84
+
85
+ ```tsx
86
+ // The standard host loop — expanded/collapsed off one boolean
87
+ {items.map((item) => (
88
+ <ScSidebarMenu
89
+ key={item.id}
90
+ icon={iconMap[item.iconKey]}
91
+ text={expanded ? item.label : undefined} // collapsed hides it anyway
92
+ state={item.id === activeId ? "active" : "default"}
93
+ variant={expanded ? "expanded" : "collapsed"}
94
+ onClick={() => onItemSelect(item)}
95
+ />
96
+ ))}
97
+
98
+ // A row with an overflow menu (rename / delete on a chat thread)
99
+ <ScSidebarMenu
100
+ icon={<SiconDot size={24} />}
101
+ text={conversation.title}
102
+ state={unread ? "default-highlight" : "default"}
103
+ variant="expanded"
104
+ moreIcon={<SiconMore size={20} color="var(--alias-text---icons-tertiary)" />}
105
+ onMoreClick={() => setMenuOpenFor(conversation.id)}
106
+ />
107
+
108
+ // Collapsed rail + your own tooltip (the component supplies none)
109
+ <Tooltip.Root delayDuration={120}>
110
+ <Tooltip.Trigger asChild>
111
+ <div style={{ display: "inline-flex" }}>
112
+ <ScSidebarMenu icon={<SiconBilling />} variant="collapsed" onClick={goBilling} />
113
+ </div>
114
+ </Tooltip.Trigger>
115
+ <Tooltip.Content side="right">Billing</Tooltip.Content>
116
+ </Tooltip.Root>
117
+ ```
118
+
119
+ ---
120
+
121
+ ## 2. Where to use it
122
+
123
+ - **Inside `StreamoidSidebar`** — it already renders one per `config` item and per
124
+ `topItems` entry; you normally never call it directly there. `ScCatalogixSidebar`
125
+ and `ScArtifaxSidebar` do the same.
126
+ - **Ad-hoc nav lists a host owns** — CXO's recents/chat list and Catalogix's
127
+ workspace-service list are rendered by the host as `bodyContent` and pass
128
+ `ScSidebarMenu` rows through.
129
+ - **`ScSettingsNav`** composes three of them (Profile / Workspace / Referral).
130
+ - **Any vertical nav that should match the rail** — a panel's section list, a drawer's
131
+ navigation column.
132
+
133
+ Direct host call sites exist in CXO, Catalogix and Photogenix. Artifax gets it
134
+ indirectly through `ScArtifaxSidebar`.
135
+
136
+ ---
137
+
138
+ ## 3. When to use it
139
+
140
+ ### Use it when
141
+
142
+ - You need a **single nav row** and you are composing the rail yourself.
143
+ - You want the expanded/collapsed geometry, the five state skins and the hover-kebab
144
+ behaviour to match the shared sidebar exactly.
145
+
146
+ ### Don't use it — reach for this instead
147
+
148
+ | Situation | Use instead |
149
+ |---|---|
150
+ | You want the whole sidebar (logo, sections, profile, collapse toggle, app switcher) | `StreamoidSidebar` |
151
+ | The whole sidebar, pre-branded for Catalogix / Artifax | `ScCatalogixSidebar` / `ScArtifaxSidebar` |
152
+ | A row that also needs a **secondary description line** (app-switch list) | `ScSidebarSwitchMenu` |
153
+ | A row inside a floating popup/dropdown menu | `ScMenuOptions` |
154
+ | A horizontal tab strip | `ScTabs` / `ScTabSwitcher` / `ScSettingsTabComp` |
155
+ | The sidebar's logo/product-switch header row | `ScSideBarLogoUnit` |
156
+ | A mobile navigation surface | `ScMobileTopNav` / `ScMobileBottomAction` |
157
+
158
+ ### Don't confuse with
159
+
160
+ | You may actually want | Not this |
161
+ |---|---|
162
+ | `ScSidebarSwitchMenu` — same row shape but with `text` **and** `description`, square hover fill, used for the app-switch list | `ScSidebarMenu` is one line only |
163
+ | `ScSidebar` — the **legacy** whole-sidebar mock with hardcoded nav | This is the row primitive, and it is current |
164
+ | `ScMenuOptions` — a popup menu row (has `version` and `error` variants) | Different surface entirely |
165
+ | `StreamoidSidebar` — the current shell that renders these rows | Reach for the shell first |
166
+
167
+ ---
168
+
169
+ ## 4. Why to use it
170
+
171
+ - **The five states are the product's whole nav vocabulary.** `active`,
172
+ `default-highlight` (unread) and `default-active` (section active, child selected)
173
+ are three genuinely different visual answers that hosts got wrong by hand; they are
174
+ one prop here.
175
+ - **Expanded ↔ collapsed geometry is already right.** Collapsed is a fixed 40 × 40
176
+ square with the icon optically centred, and the icon is clamped to 20 px regardless
177
+ of the `size` you pass — so a rail of mixed icons never jitters.
178
+ - **Token-driven.** Fills come from `--alias-fill-neutral-*`, text from
179
+ `--alias-text-and-icons-*`, so light mode works with no conditionals.
180
+ - **One place to change.** `StreamoidSidebar`, `ScCatalogixSidebar`,
181
+ `ScArtifaxSidebar` and `ScSettingsNav` all render this row, so a shape change lands
182
+ in every app's nav at once.
183
+
184
+ ---
185
+
186
+ ## Gotchas
187
+
188
+ **1. No a11y wiring at all.** The root is `<div className=… onClick=…>` — no
189
+ `role="button"`, no `tabIndex`, no `onKeyDown`, no `aria-current`. It is not
190
+ reachable by keyboard.
191
+
192
+ ```tsx
193
+ // WRONG — mouse-only nav
194
+ <ScSidebarMenu text="Billing" onClick={goBilling} />
195
+
196
+ // RIGHT — supply the semantics yourself until the DS does
197
+ <ScSidebarMenu
198
+ text="Billing"
199
+ onClick={goBilling}
200
+ role="link"
201
+ tabIndex={0}
202
+ aria-current={isActive ? "page" : undefined}
203
+ onKeyDown={(e) => (e.key === "Enter" || e.key === " ") && goBilling()}
204
+ />
205
+ ```
206
+
207
+ **2. Collapsed rows have no label and no tooltip.** `variant="collapsed"` renders the
208
+ icon only; there is no `title` attribute. Every host wraps it in its own tooltip
209
+ (CXO uses Radix). Do the same or your collapsed rail is unlabelled.
210
+
211
+ **3. `icon` defaults to `<SiconHome />` and `text` to `"Menu Item"`.** Both are real
212
+ defaults — a mistyped prop name gives you a house labelled "Menu Item" rather than an
213
+ error. Note that hosts idiomatically pass `text={expanded ? label : undefined}`, which
214
+ *does* fall back to `"Menu Item"` — harmless only because collapsed never renders it.
215
+
216
+ **4. There is no "no icon" row.** `icon` is typed `JSX.Element`, and omitting it (or
217
+ passing `undefined`) falls back to `<SiconHome />` — there is no supported way to
218
+ render a label-only row. If you force a falsy icon past the type, the label becomes the
219
+ first child and the clamp rule `.scSidebarMenu > :first-child { width: 1.25rem;
220
+ height: 1.25rem }` applies to your text instead.
221
+
222
+ **5. `moreIcon` is hover-only.** `.moreIconWrapper` is `display: none` and only
223
+ becomes `flex` on `:hover` (or `state="hover"`). There is no focus-visible reveal, so
224
+ it cannot be reached by keyboard or on a touch device.
225
+
226
+ **6. `onMoreClick` swallows the row click.** The wrapper calls `stopPropagation()`, so
227
+ clicking the kebab does not also fire the row's `onClick`. That's usually what you
228
+ want — just don't wire the same handler to both.
229
+
230
+ **7. `moreIcon` is ignored when collapsed.** It lives inside the
231
+ `variant === "expanded"` branch. So does `text`.
232
+
233
+ **8. `className` is concatenated unguarded.** When you omit `className` the rendered
234
+ class list literally contains `undefined`
235
+ (`ScSidebarMenu_scSidebarMenu undefined ScSidebarMenu_state-default …`). Harmless in
236
+ the browser, but it will show up in DOM snapshots and class-name assertions.
237
+
238
+ **9. Your icon's colour is *not* normalised.** Unlike `ScButton`, this component does
239
+ not `cloneElement` your icon — it renders it as-is. Only the label colour tracks
240
+ `state`; you must colour the icon yourself (every host does:
241
+ `color={active ? "var(--alias-text---icons-primary)" : "var(--alias-text---icons-muted)"}`).
242
+
243
+ **10. `state="hover"` is for design parity.** Real `:hover` already works. Don't wire
244
+ it to mouse handlers.
245
+
246
+ ---
247
+
248
+ ## In the wild
249
+
250
+ ```tsx
251
+ // cxo-dashboard src/app/components/app-sidebar.tsx:773
252
+ <ScSidebarMenu
253
+ icon={<SiconDot size={24} color={iconColor} />}
254
+ text={conversation.title}
255
+ state={menuState}
256
+ variant="expanded"
257
+ onClick={() => handleOpenConversation(conversation.id)}
258
+ moreIcon={
259
+ <SiconMore size={20} color="var(--alias-text---icons-tertiary)" />
260
+ }
261
+ onMoreClick={() =>
262
+ setMenuOpenFor(menuOpenFor === conversation.id ? null : conversation.id)
263
+ }
264
+ />
265
+ ```
266
+
267
+ Also: `catalogix/dashboard app/containers/LeftMenu/index.jsx:815`,
268
+ `photogenix_v2/dashboard/client/src/components/layout/Sidebar.tsx:840`,
269
+ and internally at
270
+ `packages/ui/src/SC-Sidebar-new/streamoid-sidebar.tsx:173`.
271
+
272
+ ---
273
+
274
+ ## Related
275
+
276
+ - `StreamoidSidebar` (`SC-Sidebar-new`) — the current shell; renders these rows from
277
+ `config` + `iconMap`. Start here.
278
+ - `ScSidebarSwitchMenu` — two-line sibling for the app-switch list.
279
+ - `ScSideBarLogoUnit` — the rail's logo + product-switch header row.
280
+ - `ScSettingsNav` — a small stack of three of these rows.
281
+ - `ScSidebar` / `ScLogoUnit` / `ScSidebarProfile` / `ScSidebarIcons` / `ScVersion` —
282
+ the **legacy** parts of this family; all superseded by `StreamoidSidebar`.
283
+ - `ScMenuOptions` — the popup-menu row, not a rail row.