@streamoid/ui 0.6.16 → 0.6.18

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (131) hide show
  1. package/README.md +35 -18
  2. package/dist/docs/AGENTS.md +321 -0
  3. package/dist/docs/CreditWarningBanner.md +305 -0
  4. package/dist/docs/InvoiceHistoryMobile.md +222 -0
  5. package/dist/docs/ScAccess.md +259 -0
  6. package/dist/docs/ScAppCard.md +244 -0
  7. package/dist/docs/ScAppCardForCopilot.md +230 -0
  8. package/dist/docs/ScAppCardV3.md +273 -0
  9. package/dist/docs/ScAppField.md +308 -0
  10. package/dist/docs/ScAppListingCard.md +271 -0
  11. package/dist/docs/ScAppSwitchPanel.md +286 -0
  12. package/dist/docs/ScAppcardLogos.md +226 -0
  13. package/dist/docs/ScArtifaxInvite.md +262 -0
  14. package/dist/docs/ScArtifaxSidebar.md +330 -0
  15. package/dist/docs/ScAskAgentButton.md +307 -0
  16. package/dist/docs/ScBadges.md +261 -0
  17. package/dist/docs/ScBeacon.md +244 -0
  18. package/dist/docs/ScBillingHistoryHeader.md +210 -0
  19. package/dist/docs/ScBillingHistoryTableList.md +243 -0
  20. package/dist/docs/ScBillingLogsTableHeader.md +212 -0
  21. package/dist/docs/ScBillingLogsTableList.md +251 -0
  22. package/dist/docs/ScBriefCard.md +255 -0
  23. package/dist/docs/ScButton.md +251 -0
  24. package/dist/docs/ScCalendar.md +268 -0
  25. package/dist/docs/ScCalendarDateComps.md +264 -0
  26. package/dist/docs/ScCatalogixInvite.md +345 -0
  27. package/dist/docs/ScCatalogixSidebar.md +337 -0
  28. package/dist/docs/ScCatalogixStoreHeader.md +246 -0
  29. package/dist/docs/ScCatalogixStoreTableList.md +316 -0
  30. package/dist/docs/ScCheckField.md +233 -0
  31. package/dist/docs/ScCheckbox.md +272 -0
  32. package/dist/docs/ScCounter.md +235 -0
  33. package/dist/docs/ScCreditsUsageCard.md +247 -0
  34. package/dist/docs/ScCreditsUsageCardMobile.md +224 -0
  35. package/dist/docs/ScDefaultCard.md +269 -0
  36. package/dist/docs/ScDp.md +245 -0
  37. package/dist/docs/ScDrawer.md +318 -0
  38. package/dist/docs/ScFieldButton.md +255 -0
  39. package/dist/docs/ScFileField.md +268 -0
  40. package/dist/docs/ScGoogleSignIn.md +250 -0
  41. package/dist/docs/ScGuide.md +278 -0
  42. package/dist/docs/ScHDivider.md +213 -0
  43. package/dist/docs/ScHeader.md +222 -0
  44. package/dist/docs/ScImageField.md +253 -0
  45. package/dist/docs/ScInChatList.md +277 -0
  46. package/dist/docs/ScInChatMessage.md +205 -0
  47. package/dist/docs/ScInfoPopup.md +248 -0
  48. package/dist/docs/ScIntialProfileCover.md +233 -0
  49. package/dist/docs/ScInvoiceHistoryMobile.md +187 -0
  50. package/dist/docs/ScLogoUnit.md +232 -0
  51. package/dist/docs/ScMappingCard.md +241 -0
  52. package/dist/docs/ScMediaApproval.md +301 -0
  53. package/dist/docs/ScMediaSelect.md +310 -0
  54. package/dist/docs/ScMenuOptions.md +308 -0
  55. package/dist/docs/ScMobileBottomAction.md +252 -0
  56. package/dist/docs/ScMobileTopNav.md +279 -0
  57. package/dist/docs/ScModal.md +291 -0
  58. package/dist/docs/ScOnlyField.md +302 -0
  59. package/dist/docs/ScOnlyIcon.md +213 -0
  60. package/dist/docs/ScPagination.md +284 -0
  61. package/dist/docs/ScPairtext.md +287 -0
  62. package/dist/docs/ScPendingAction.md +238 -0
  63. package/dist/docs/ScPhtogenixInvite.md +275 -0
  64. package/dist/docs/ScPlanCard.md +302 -0
  65. package/dist/docs/ScPlanComparison.md +264 -0
  66. package/dist/docs/ScPlanDetailsCard.md +246 -0
  67. package/dist/docs/ScPlanDetailsCardMobile.md +240 -0
  68. package/dist/docs/ScPopUpMenu.md +224 -0
  69. package/dist/docs/ScProfile.md +234 -0
  70. package/dist/docs/ScProfileImageUpdate.md +261 -0
  71. package/dist/docs/ScProfileOptions.md +245 -0
  72. package/dist/docs/ScProfilePopup.md +396 -0
  73. package/dist/docs/ScProfileSettingsComp.md +250 -0
  74. package/dist/docs/ScProfileV2Mobile.md +216 -0
  75. package/dist/docs/ScProgressBar.md +267 -0
  76. package/dist/docs/ScQuickPrompt.md +277 -0
  77. package/dist/docs/ScRadio.md +228 -0
  78. package/dist/docs/ScReferralCardMobile.md +226 -0
  79. package/dist/docs/ScReferralTableHeader.md +260 -0
  80. package/dist/docs/ScReferralTableList.md +293 -0
  81. package/dist/docs/ScRole.md +226 -0
  82. package/dist/docs/ScRoleMobile.md +199 -0
  83. package/dist/docs/ScSelect.md +270 -0
  84. package/dist/docs/ScSelection.md +256 -0
  85. package/dist/docs/ScSelectionList.md +272 -0
  86. package/dist/docs/ScSelectionPill.md +240 -0
  87. package/dist/docs/ScSelectionPillGroup.md +302 -0
  88. package/dist/docs/ScSettingsNav.md +212 -0
  89. package/dist/docs/ScSettingsTabComp.md +260 -0
  90. package/dist/docs/ScSideBarLogoUnit.md +340 -0
  91. package/dist/docs/ScSidebar.md +243 -0
  92. package/dist/docs/ScSidebarIcons.md +232 -0
  93. package/dist/docs/ScSidebarMenu.md +283 -0
  94. package/dist/docs/ScSidebarProfile.md +231 -0
  95. package/dist/docs/ScSidebarSwitchMenu.md +258 -0
  96. package/dist/docs/ScSlider.md +194 -0
  97. package/dist/docs/ScStoreCard.md +252 -0
  98. package/dist/docs/ScStrLogo.md +253 -0
  99. package/dist/docs/ScStreamoidWordmark.md +302 -0
  100. package/dist/docs/ScSubAgent.md +226 -0
  101. package/dist/docs/ScTabComp.md +308 -0
  102. package/dist/docs/ScTabField.md +258 -0
  103. package/dist/docs/ScTabSwitcher.md +307 -0
  104. package/dist/docs/ScTableHeader.md +261 -0
  105. package/dist/docs/ScTableList.md +301 -0
  106. package/dist/docs/ScTableListMobile.md +282 -0
  107. package/dist/docs/ScTabs.md +268 -0
  108. package/dist/docs/ScTaxonomyPill.md +263 -0
  109. package/dist/docs/ScTextArea.md +259 -0
  110. package/dist/docs/ScTextField.md +324 -0
  111. package/dist/docs/ScThinkingStepIcon.md +249 -0
  112. package/dist/docs/ScTodoList.md +288 -0
  113. package/dist/docs/ScToggleSwitch.md +229 -0
  114. package/dist/docs/ScUsageHistoryMobile.md +194 -0
  115. package/dist/docs/ScVDivider.md +215 -0
  116. package/dist/docs/ScValueMappingL1.md +256 -0
  117. package/dist/docs/ScVersion.md +251 -0
  118. package/dist/docs/ScWorkspace.md +233 -0
  119. package/dist/docs/ScWorkspaceCard.md +234 -0
  120. package/dist/docs/ScWorkspaceSettingsMobile.md +265 -0
  121. package/dist/docs/ScWorkspaceSwitchCard.md +314 -0
  122. package/dist/docs/ScWorkspaceSwitchMobile.md +241 -0
  123. package/dist/docs/ScWorkspaceSwitchMobileV2.md +278 -0
  124. package/dist/docs/StreamoidSidebar.md +403 -0
  125. package/dist/docs/StreamoidWorkspaceSwitcher.md +307 -0
  126. package/dist/docs/UsageHistoryMobile.md +235 -0
  127. package/dist/docs/components.json +4849 -0
  128. package/dist/index.css +43 -37
  129. package/dist/index.d.mts +10 -0
  130. package/dist/index.d.ts +10 -0
  131. package/package.json +3 -2
@@ -0,0 +1,199 @@
1
+ ---
2
+ component: ScRoleMobile
3
+ package: "@streamoid/ui"
4
+ category: mobile
5
+ status: stable
6
+ renders: div
7
+ tags: [mobile, role, admin, member, badge, chip, permission, label]
8
+ related: [ScRole, ScBadges, ScTableListMobile, ScWorkspaceSwitchMobileV2, ScAccess]
9
+ do_not_confuse_with: [ScRole, ScBadges, ScTaxonomyPill, ScSelectionPill]
10
+ ---
11
+
12
+ # ScRoleMobile
13
+
14
+ **A fixed 64×32 "Admin" / "Member" chip for mobile rows.** Two hardcoded looks: admin
15
+ = blue info fill with blue text, member = neutral grey fill with grey text. The label
16
+ text is derived from the `role` prop — there is no way to change the words.
17
+
18
+ ## TL;DR for agents
19
+
20
+ - **Reach for it when:** a mobile list row or card needs the user's role as a chip
21
+ and the only two roles are admin and member.
22
+ - **Don't reach for it when:** the desktop needs the same chip (→ `ScRole`, which has
23
+ a `label` prop), the label is anything other than Admin/Member (→ `ScBadges`), or
24
+ the chip is meant to be tappable/selectable (→ `ScSelectionPill`).
25
+ - **Three things that will bite you:**
26
+ 1. **The label is hardcoded English**, computed as `role === "admin" ? "Admin" : "Member"`.
27
+ No `label` prop — unlike its desktop twin `ScRole`.
28
+ 2. **`role` is the prop name, so you can never set the ARIA `role` attribute.**
29
+ The types intersect down to `"admin" | "member"`.
30
+ 3. **Fixed `width: 64px; height: 32px`** — "Member" fits, longer words would not,
31
+ and it never fills its container.
32
+
33
+ ---
34
+
35
+ ## 1. How to use it
36
+
37
+ ### Import
38
+
39
+ ```tsx
40
+ import { ScRoleMobile } from "@streamoid/ui";
41
+ import "@streamoid/ui/dist/index.css"; // once, at your app root
42
+ ```
43
+
44
+ ### Minimal usage
45
+
46
+ ```tsx
47
+ <ScRoleMobile role={member.permissions.role === "admin" ? "admin" : "member"} />
48
+ ```
49
+
50
+ ### Props
51
+
52
+ | Prop | Type | Default | Notes |
53
+ |---|---|---|---|
54
+ | `role` | `"admin"` \| `"member"` | `"admin"` | ⚠️ Defaults to **admin** — the more privileged value. Drives both the skin and the label text. |
55
+ | `className` | `string` | – | Appended after internal classes. Unguarded concat — see Gotcha 4. |
56
+ | `...props` | `React.HTMLAttributes<HTMLDivElement>` | – | Spread onto the root `div`. Note `role` is consumed by this component, so the ARIA attribute is unavailable. |
57
+
58
+ ### What each value renders
59
+
60
+ | `role` | Label | Background | Border | Text |
61
+ |---|---|---|---|---|
62
+ | `"admin"` | `Admin` | `--alias-fill-info-soft` | `--alias-border-infoplus` | `--alias-text-and-icons-infocont` |
63
+ | `"member"` | `Member` | `--alias-fill-neutral-neutral` | `--alias-border-default` | `--alias-text-and-icons-tertiary` |
64
+
65
+ Anything that isn't exactly `"admin"` falls to the member branch for the label, but the
66
+ skin class is `styles["role-" + role]`, so a value outside the union produces an
67
+ **unstyled** chip (no background, a `1px solid` border with no colour).
68
+
69
+ ### Recipes
70
+
71
+ ```tsx
72
+ // Mapping a backend role that has more than two values
73
+ <ScRoleMobile role={m.role === "admin" || m.role === "owner" ? "admin" : "member"} />
74
+
75
+ // Need "Owner" / "Viewer" / "Billing" wording? This chip cannot do it — use ScBadges
76
+ import { ScBadges } from "@streamoid/ui";
77
+ <ScBadges text="Owner" variant="info" styleVariant="opaque" />
78
+
79
+ // Announce it to assistive tech — you cannot pass role="…", so use aria-label on
80
+ // the wrapper, or aria-label directly (the prop name collision only blocks `role`)
81
+ <ScRoleMobile role="member" aria-label="Workspace role: Member" />
82
+ ```
83
+
84
+ ---
85
+
86
+ ## 2. Where to use it
87
+
88
+ The role slot of a mobile list row or card — the mobile counterpart of the role column
89
+ in a desktop members table. In practice its job is already done inline by the mobile
90
+ components that need it: `ScTableListMobile`, `ScWorkspaceSettingsMobile` and
91
+ `ScWorkspaceSwitchMobile` each render their **own** role badge rather than composing
92
+ this one, so `ScRoleMobile` is only for rows you assemble yourself.
93
+
94
+ ---
95
+
96
+ ## 3. When to use it
97
+
98
+ ### Use it when
99
+
100
+ - The role vocabulary really is just admin/member.
101
+ - You are hand-assembling a mobile row and want the same chip metrics as the rest of
102
+ the mobile family.
103
+
104
+ ### Don't use it — reach for this instead
105
+
106
+ | Situation | Use instead |
107
+ |---|---|
108
+ | Desktop members table / settings row | `ScRole` — same two types, **plus a `label` prop** |
109
+ | Any other word ("Owner", "Viewer", "Pending", "Billing admin") | `ScBadges` (`text` + `variant` + `styleVariant`) |
110
+ | A status chip that must change colour by state | `ScBadges` |
111
+ | A tappable filter/segment chip | `ScSelectionPill` (+ `ScSelectionPillGroup`) |
112
+ | A taxonomy tree node chip | `ScTaxonomyPill` |
113
+ | Per-app access / permission icons | `ScAccess`, or the `accessIcons` / `permissionIcons` props of `ScTableListMobile` |
114
+ | A whole member row (avatar, name, email, role, icons) | `ScTableListMobile` |
115
+
116
+ ### Don't confuse with
117
+
118
+ | You may actually want | Not this |
119
+ |---|---|
120
+ | `ScRole` — desktop twin. Prop is **`type`**, not `role`, and it has `label` so you can print any word | `ScRoleMobile` has neither |
121
+ | `ScBadges` — the general status chip; any text, several variants | `ScRoleMobile` is two hardcoded words |
122
+ | `ScSelectionPill` — interactive segmented pill with `aria-pressed` | `ScRoleMobile` is a static `div` |
123
+ | The role badge inside `ScWorkspaceSwitchMobile` / `ScWorkspaceSettingsMobile` | Those are **internal** markup with their own metrics (80px wide, `--radius-xl`, always info-blue). This component is not what they use. |
124
+
125
+ ---
126
+
127
+ ## 4. Why to use it
128
+
129
+ - **The two role skins are already decided** — info-soft/infoplus for admin,
130
+ neutral/default for member — matching how role is coloured everywhere else in the
131
+ product, so an admin looks like an admin on every screen.
132
+ - **Fixed metrics mean lists line up.** 64×32 with `--radius-md` keeps the chip column
133
+ the same width down a list without you setting min-widths.
134
+ - **One prop.** There is genuinely nothing to get wrong except the five gotchas below.
135
+
136
+ If you need any flexibility at all — different words, more roles, colour by state —
137
+ `ScBadges` is the right primitive and this component is a trap.
138
+
139
+ ---
140
+
141
+ ## Gotchas
142
+
143
+ **1. The label is hardcoded English.** `"Admin"` / `"Member"` are literals in the
144
+ render body. Do not use this component on a localised surface.
145
+
146
+ ```tsx
147
+ // IMPOSSIBLE — there is no label prop (this is a type error)
148
+ <ScRoleMobile role="admin" label="Administrateur" />
149
+
150
+ // The desktop twin CAN do it
151
+ <ScRole type="admin" label="Administrateur" />
152
+ ```
153
+
154
+ **2. You cannot set the ARIA `role` attribute.** `role` is destructured as the
155
+ component's own prop, and because the signature is
156
+ `IScRoleMobileProps & React.HTMLAttributes<HTMLDivElement>`, the type narrows to
157
+ `"admin" | "member"`:
158
+
159
+ ```tsx
160
+ // TYPE ERROR — "status" is not assignable to "admin" | "member"
161
+ <ScRoleMobile role="status" />
162
+
163
+ // Workaround: aria-* still works, or wrap it
164
+ <span role="status"><ScRoleMobile role="member" /></span>
165
+ ```
166
+
167
+ **3. `role` defaults to `"admin"`.** Forgetting the prop grants everyone the
168
+ privileged-looking chip. Always pass it explicitly.
169
+
170
+ **4. `className` is concatenated unguarded.** Omit it and the root carries a literal
171
+ `undefined` class. Cosmetic, but it breaks exact-class assertions.
172
+
173
+ **5. An out-of-union `role` renders an unstyled chip.** `styles["role-" + role]` is
174
+ `undefined` for anything else, and the base class sets `border: 1px solid` with no
175
+ colour. TypeScript catches this; plain JS hosts (Catalogix, Photogenix) will not.
176
+
177
+ ---
178
+
179
+ ## In the wild
180
+
181
+ _No host render site found — used by the agent runtime / composed internally._
182
+
183
+ To be precise: it is exported from `@streamoid/ui` but no host app renders it, and it
184
+ is not agent-runtime — it is currently unused. Where it belongs is CXO's mobile teams
185
+ screen; that screen instead passes a plain string to the row component
186
+ (`cxo-dashboard/src/app/components/mobile-teams-content.tsx:311`,
187
+ `role={member.permissions.role === "admin" ? "Admin" : "Member"}` on
188
+ `ScTableListMobile`), which renders its own badge.
189
+
190
+ ---
191
+
192
+ ## Related
193
+
194
+ - `ScRole` — the desktop twin; prop is `type`, and it accepts a custom `label`.
195
+ - `ScBadges` — reach for this the moment the wording or colour must vary.
196
+ - `ScTableListMobile` — the mobile member row; renders its own role badge.
197
+ - `ScWorkspaceSettingsMobile` / `ScWorkspaceSwitchMobile` — also render their own
198
+ (always-blue, 80px) role badge rather than this chip.
199
+ - `ScAccess` — per-app access rows, the other half of "what can this user do".
@@ -0,0 +1,270 @@
1
+ ---
2
+ component: ScSelect
3
+ package: "@streamoid/ui"
4
+ category: forms
5
+ status: stable
6
+ renders: div > button[aria-haspopup="listbox"] + div[role="listbox"]
7
+ tags: [select, dropdown, picker, listbox, options, combobox, choice]
8
+ related: [ScTextField, ScTabField, ScPopUpMenu, ScMenuOptions, ScMappingCard, ScSelectionPillGroup]
9
+ do_not_confuse_with: [ScSelection, ScSelectionList, ScSelectionPill, ScSelectionPillGroup, ScPopUpMenu, ScMenuOptions]
10
+ ---
11
+
12
+ # ScSelect
13
+
14
+ **The DS-native dropdown.** A 48 px field-shaped `<button>` showing the selected
15
+ label (or a placeholder) plus a chevron that rotates when open, and an
16
+ absolutely-positioned `role="listbox"` panel of options — each option optionally
17
+ carrying a second description line. It owns its own open/closed state and closes
18
+ on an outside `mousedown`.
19
+
20
+ ## TL;DR for agents
21
+
22
+ - **Reach for it when:** the user picks **one** value from a list that is too long
23
+ or too wordy for inline pills, and you want DS chrome without wiring a trigger
24
+ yourself.
25
+ - **Don't reach for it when:** the menu must escape a clipping parent (it is
26
+ **not portalled**), you need multi-select/search/async options, or the "menu" is
27
+ really a list of *actions* (→ `ScPopUpMenu` + `ScMenuOptions`).
28
+ - **Five things that will bite you:**
29
+ 1. Options are keyed by **`id`**, not `value` — `{ id, label, description? }`.
30
+ 2. `placeholder` defaults to the literal `"Select an option..."`.
31
+ 3. `label` renders **only** when you also pass `labelGroup="label"`.
32
+ 4. The menu is `position: absolute; z-index: 20` inside the component. Any
33
+ ancestor with `overflow: hidden` clips it — including `ScMappingCard` and
34
+ `ScTextField`'s own field container.
35
+ 5. **No keyboard support in the list.** The trigger is a real button, but the
36
+ options are `div`s: no arrow keys, no Enter-to-select, no Escape-to-close,
37
+ no focus management.
38
+
39
+ ---
40
+
41
+ ## 1. How to use it
42
+
43
+ ### Import
44
+
45
+ ```tsx
46
+ import { ScSelect } from "@streamoid/ui";
47
+ import type { IScSelectOption } from "@streamoid/ui";
48
+ import "@streamoid/ui/dist/index.css"; // once, at your app root
49
+ ```
50
+
51
+ ### Minimal usage
52
+
53
+ ```tsx
54
+ <ScSelect
55
+ options={[
56
+ { id: "admin", label: "Admin" },
57
+ { id: "member", label: "Member" },
58
+ ]}
59
+ value={role}
60
+ onChange={setRole}
61
+ />
62
+ ```
63
+
64
+ ### Props
65
+
66
+ `ScSelect` does **not** extend `HTMLAttributes` and does **not** spread extra
67
+ props. `className` is the only escape hatch — no `style`, `id`, `data-*`,
68
+ `onBlur`, `name` or `required`.
69
+
70
+ | Prop | Type | Default | Notes |
71
+ |---|---|---|---|
72
+ | `options` | `IScSelectOption[]` | `[]` | ⚠️ Empty default: with no options the panel opens blank. |
73
+ | `value` | `string` | – | Matched against `option.id`. Controlled — no internal value state. |
74
+ | `onChange` | `(id: string) => void` | – | Called with the option **`id`**, then the menu closes. |
75
+ | `placeholder` | `string` | `"Select an option..."` | ⚠️ Real default, hardcoded English. |
76
+ | `label` | `string` | – | Rendered only when `labelGroup === "label"` **and** `label` is truthy. |
77
+ | `labelGroup` | `""` \| `"label"` | `""` | Gates the label row. |
78
+ | `disabled` | `boolean` | `false` | Disables the native button, blocks toggling, greys text + chevron. |
79
+ | `className` | `string` | – | Appended to the root class list. |
80
+
81
+ `IScSelectOption`
82
+
83
+ | Field | Type | Notes |
84
+ |---|---|---|
85
+ | `id` | `string` | **Required.** The value passed to `onChange`; also the React key. |
86
+ | `label` | `string` | **Required.** Trigger text and option title. |
87
+ | `description` | `string` | Optional 12 px muted second line inside the option row. |
88
+
89
+ #### Derived visual state (you cannot set it)
90
+
91
+ | Condition | State | Trigger skin |
92
+ |---|---|---|
93
+ | `disabled` | `disabled` | base fill, subtle border, `not-allowed`, disabled text |
94
+ | menu open | `active` | raised fill, **strong** border |
95
+ | a matching option | `filled` | raised fill, default border |
96
+ | otherwise | `default` | base fill, subtle border, muted placeholder text |
97
+
98
+ ### Recipes
99
+
100
+ ```tsx
101
+ // Labelled, with descriptions
102
+ <ScSelect
103
+ label="Role"
104
+ labelGroup="label"
105
+ options={[
106
+ { id: "admin", label: "Admin", description: "Full access, can invite users" },
107
+ { id: "member", label: "Member", description: "Scoped access you configure" },
108
+ ]}
109
+ value={role}
110
+ onChange={setRole}
111
+ />
112
+
113
+ // Mapping API objects — remember `id`, not `value`
114
+ <ScSelect
115
+ options={attributes.map((a) => ({ id: a.code, label: a.displayName }))}
116
+ value={target}
117
+ onChange={setTarget}
118
+ />
119
+
120
+ // Width/margins: className only (there is no `style` prop)
121
+ <ScSelect className="my-select" options={opts} value={v} onChange={setV} />
122
+ ```
123
+
124
+ ---
125
+
126
+ ## 2. Where to use it
127
+
128
+ - **Simple settings and form dropdowns** where the option list is static and
129
+ short (roles, units, sort orders, statuses).
130
+ - **Inside `ScModal` / `ScDrawer`** — but check the container: the menu is not
131
+ portalled, so a scrolling modal body will clip or scroll it away.
132
+
133
+ The hosts currently solve dropdowns two other ways, which is why this component
134
+ has no host render site yet:
135
+
136
+ | Host | What it does instead |
137
+ |---|---|
138
+ | Catalogix | `app/components/DsDropdown` — a read-only `ScTextField` as the trigger with a hand-rolled, host-owned menu; plus `react-select` (portalled) for feed mapping |
139
+ | `@streamoid/settings` | wraps `ScTextField` in a clickable div for the specialization picker |
140
+
141
+ `ScSelect` is the intended replacement for those wrappers whenever the menu does
142
+ **not** need to escape a clipping ancestor.
143
+
144
+ ---
145
+
146
+ ## 3. When to use it
147
+
148
+ ### Use it when
149
+
150
+ - One value, from **5–30** static options, where pills/tabs would not fit.
151
+ - Options benefit from a **second description line** (`description`).
152
+ - You want the trigger to look identical to `ScTextField` (same 48 px pill, same
153
+ fill/border tokens, same focus skin) with none of the wrapper work.
154
+
155
+ ### Don't use it — reach for this instead
156
+
157
+ | Situation | Use instead |
158
+ |---|---|
159
+ | 2–4 mutually exclusive choices in a form, all visible | `ScTabField` |
160
+ | 2–5 filters/views over a list | `ScSelectionPillGroup` |
161
+ | A list of **actions** (Rename, Duplicate, Delete) | `ScPopUpMenu` + `ScMenuOptions` |
162
+ | Multi-select, type-ahead search, async/paged options, grouped options | not in the DS — use the host's `react-select` (portalled) or build it |
163
+ | The menu must escape `overflow: hidden` (e.g. inside `ScMappingCard`'s `targets` slot) | a portalled select of your own — `ScSelect` will be clipped |
164
+ | Native form participation (`name`, `required`, uncontrolled submit) | a real `<select>`, styled with the tokens |
165
+ | Free text with an optional suggestion list | `ScTextField` + your own list |
166
+
167
+ ### Don't confuse with
168
+
169
+ | You may actually want | Not this |
170
+ |---|---|
171
+ | `ScSelection` — an in-chat radio row (title + description) in the **agent runtime** | `ScSelect` is a host-app dropdown |
172
+ | `ScSelectionList` — an in-chat approve/reject list, also agent runtime | unrelated |
173
+ | `ScSelectionPill` / `ScSelectionPillGroup` — a segmented pill row in the dashboards | `ScSelect` is a collapsed menu |
174
+ | `ScPopUpMenu` / `ScMenuOptions` — an action menu hung off an icon | `ScSelect` sets a **value** |
175
+
176
+ The `ScSelect` / `ScSelection*` name collision is one of the top sources of wrong
177
+ picks in this library: **`ScSelect` = dropdown (dashboards)**,
178
+ **`ScSelection*` = chat transcript UI (agent runtime)**.
179
+
180
+ ---
181
+
182
+ ## 4. Why to use it
183
+
184
+ - **The trigger already matches the field vocabulary** — `fill-neutral-neutralplus`
185
+ → `neutral` on hover/filled, `border-subtle` → `border-strong` when open, the
186
+ same `radius-3xl` and 48 px height as `ScTextField`. A hand-rolled trigger drifts
187
+ immediately.
188
+ - **Correct listbox semantics on the parts that matter**: `aria-haspopup="listbox"`,
189
+ `aria-expanded` on a real `<button type="button">`, `role="listbox"` on the panel
190
+ and `role="option"` + `aria-selected` on each row.
191
+ - **Outside-click closing is already handled** (a `mousedown` listener registered
192
+ only while open, and cleaned up on close) — the single most commonly forgotten
193
+ piece of a hand-rolled dropdown.
194
+ - **Descriptions in options** are tokenised (12 px muted) so a two-line option row
195
+ looks the same everywhere.
196
+
197
+ ---
198
+
199
+ ## Gotchas
200
+
201
+ **1. Options use `id`, not `value`.** Nearly every other list-shaped DS prop in
202
+ this library (`ScTabField.tabs`, `ScSelectionPillGroup.options`) uses
203
+ `{ label, value }`. This one is `{ id, label, description? }`.
204
+
205
+ ```tsx
206
+ // WRONG — never matches; the trigger shows the placeholder forever
207
+ <ScSelect options={[{ value: "admin", label: "Admin" }]} value="admin" />
208
+
209
+ // RIGHT
210
+ <ScSelect options={[{ id: "admin", label: "Admin" }]} value="admin" onChange={setRole} />
211
+ ```
212
+
213
+ **2. The menu is not portalled.** It is `position: absolute; top: 100%;
214
+ z-index: 20` inside `.selectArea`. Any ancestor with `overflow: hidden` clips it —
215
+ notably `ScMappingCard` (whose README warns about exactly this) and
216
+ `ScTextField`'s field container. Lift the select out, or use a portalled select.
217
+
218
+ **3. `label` alone renders nothing.**
219
+
220
+ ```tsx
221
+ // WRONG — the label is dropped
222
+ <ScSelect label="Role" options={opts} value={v} onChange={setV} />
223
+
224
+ // RIGHT
225
+ <ScSelect label="Role" labelGroup="label" options={opts} value={v} onChange={setV} />
226
+ ```
227
+
228
+ **4. No keyboard navigation inside the list.** Options are `div`s with `onClick`.
229
+ Keyboard and screen-reader users can open the button but cannot reach the options.
230
+ Don't use it as the only path to a critical setting until this is fixed in the DS.
231
+
232
+ **5. There is no `style` prop and no prop spreading.** `className` is the whole
233
+ API surface for layout. You cannot attach `data-testid`, `id`, `onBlur` or
234
+ `aria-label` to the root.
235
+
236
+ **6. The chevron is a hardcoded inline SVG,** not an `Sicon*`, and there is no
237
+ left-icon or adornment slot on the trigger. If the spec puts an avatar, a colour
238
+ swatch or a custom caret in the closed state, you cannot express it here — use the
239
+ `ScTextField`-as-trigger pattern instead.
240
+
241
+ **7. Fonts are hardcoded `Inter, sans-serif`** in this stylesheet rather than
242
+ `--text-text-sm-regular-font-family` like the other fields. Sizes/colours are
243
+ tokenised; the family is not. Harmless today, a drift risk if the body font
244
+ changes.
245
+
246
+ **8. Open state is internal.** There is no `open`/`onOpenChange` prop, so you
247
+ cannot open it programmatically or coordinate it with another popover.
248
+
249
+ ---
250
+
251
+ ## In the wild
252
+
253
+ _No host render site found — used by the agent runtime / composed internally._
254
+
255
+ Most likely home: the Catalogix feed **Map-Attributes** target rows
256
+ (`ScMappingCard`'s `targets` slot) and simple settings dropdowns — but note
257
+ Gotcha 2, since `ScMappingCard` sets `overflow: hidden`. Today Catalogix uses
258
+ `app/components/DsDropdown` (an `ScTextField` trigger with its own menu) and
259
+ `react-select` for the portalled cases.
260
+
261
+ ---
262
+
263
+ ## Related
264
+
265
+ - `ScTextField` — what the trigger is modelled on; also the current host pattern
266
+ for hand-rolled dropdowns.
267
+ - `ScTabField` — a segmented field when there are only 2–4 options.
268
+ - `ScPopUpMenu` / `ScMenuOptions` — action menus, not value pickers.
269
+ - `ScSelectionPillGroup` — visible segmented filters over a list.
270
+ - `ScSelection` / `ScSelectionList` — the unrelated in-chat family (agent runtime).