@streamoid/ui 0.6.17 → 0.6.18
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +35 -18
- package/dist/docs/AGENTS.md +321 -0
- package/dist/docs/CreditWarningBanner.md +305 -0
- package/dist/docs/InvoiceHistoryMobile.md +222 -0
- package/dist/docs/ScAccess.md +259 -0
- package/dist/docs/ScAppCard.md +244 -0
- package/dist/docs/ScAppCardForCopilot.md +230 -0
- package/dist/docs/ScAppCardV3.md +273 -0
- package/dist/docs/ScAppField.md +308 -0
- package/dist/docs/ScAppListingCard.md +271 -0
- package/dist/docs/ScAppSwitchPanel.md +286 -0
- package/dist/docs/ScAppcardLogos.md +226 -0
- package/dist/docs/ScArtifaxInvite.md +262 -0
- package/dist/docs/ScArtifaxSidebar.md +330 -0
- package/dist/docs/ScAskAgentButton.md +307 -0
- package/dist/docs/ScBadges.md +261 -0
- package/dist/docs/ScBeacon.md +244 -0
- package/dist/docs/ScBillingHistoryHeader.md +210 -0
- package/dist/docs/ScBillingHistoryTableList.md +243 -0
- package/dist/docs/ScBillingLogsTableHeader.md +212 -0
- package/dist/docs/ScBillingLogsTableList.md +251 -0
- package/dist/docs/ScBriefCard.md +255 -0
- package/dist/docs/ScButton.md +251 -0
- package/dist/docs/ScCalendar.md +268 -0
- package/dist/docs/ScCalendarDateComps.md +264 -0
- package/dist/docs/ScCatalogixInvite.md +345 -0
- package/dist/docs/ScCatalogixSidebar.md +337 -0
- package/dist/docs/ScCatalogixStoreHeader.md +246 -0
- package/dist/docs/ScCatalogixStoreTableList.md +316 -0
- package/dist/docs/ScCheckField.md +233 -0
- package/dist/docs/ScCheckbox.md +272 -0
- package/dist/docs/ScCounter.md +235 -0
- package/dist/docs/ScCreditsUsageCard.md +247 -0
- package/dist/docs/ScCreditsUsageCardMobile.md +224 -0
- package/dist/docs/ScDefaultCard.md +269 -0
- package/dist/docs/ScDp.md +245 -0
- package/dist/docs/ScDrawer.md +318 -0
- package/dist/docs/ScFieldButton.md +255 -0
- package/dist/docs/ScFileField.md +268 -0
- package/dist/docs/ScGoogleSignIn.md +250 -0
- package/dist/docs/ScGuide.md +278 -0
- package/dist/docs/ScHDivider.md +213 -0
- package/dist/docs/ScHeader.md +222 -0
- package/dist/docs/ScImageField.md +253 -0
- package/dist/docs/ScInChatList.md +277 -0
- package/dist/docs/ScInChatMessage.md +205 -0
- package/dist/docs/ScInfoPopup.md +248 -0
- package/dist/docs/ScIntialProfileCover.md +233 -0
- package/dist/docs/ScInvoiceHistoryMobile.md +187 -0
- package/dist/docs/ScLogoUnit.md +232 -0
- package/dist/docs/ScMappingCard.md +241 -0
- package/dist/docs/ScMediaApproval.md +301 -0
- package/dist/docs/ScMediaSelect.md +310 -0
- package/dist/docs/ScMenuOptions.md +308 -0
- package/dist/docs/ScMobileBottomAction.md +252 -0
- package/dist/docs/ScMobileTopNav.md +279 -0
- package/dist/docs/ScModal.md +291 -0
- package/dist/docs/ScOnlyField.md +302 -0
- package/dist/docs/ScOnlyIcon.md +213 -0
- package/dist/docs/ScPagination.md +284 -0
- package/dist/docs/ScPairtext.md +287 -0
- package/dist/docs/ScPendingAction.md +238 -0
- package/dist/docs/ScPhtogenixInvite.md +275 -0
- package/dist/docs/ScPlanCard.md +302 -0
- package/dist/docs/ScPlanComparison.md +264 -0
- package/dist/docs/ScPlanDetailsCard.md +246 -0
- package/dist/docs/ScPlanDetailsCardMobile.md +240 -0
- package/dist/docs/ScPopUpMenu.md +224 -0
- package/dist/docs/ScProfile.md +234 -0
- package/dist/docs/ScProfileImageUpdate.md +261 -0
- package/dist/docs/ScProfileOptions.md +245 -0
- package/dist/docs/ScProfilePopup.md +396 -0
- package/dist/docs/ScProfileSettingsComp.md +250 -0
- package/dist/docs/ScProfileV2Mobile.md +216 -0
- package/dist/docs/ScProgressBar.md +267 -0
- package/dist/docs/ScQuickPrompt.md +277 -0
- package/dist/docs/ScRadio.md +228 -0
- package/dist/docs/ScReferralCardMobile.md +226 -0
- package/dist/docs/ScReferralTableHeader.md +260 -0
- package/dist/docs/ScReferralTableList.md +293 -0
- package/dist/docs/ScRole.md +226 -0
- package/dist/docs/ScRoleMobile.md +199 -0
- package/dist/docs/ScSelect.md +270 -0
- package/dist/docs/ScSelection.md +256 -0
- package/dist/docs/ScSelectionList.md +272 -0
- package/dist/docs/ScSelectionPill.md +240 -0
- package/dist/docs/ScSelectionPillGroup.md +302 -0
- package/dist/docs/ScSettingsNav.md +212 -0
- package/dist/docs/ScSettingsTabComp.md +260 -0
- package/dist/docs/ScSideBarLogoUnit.md +340 -0
- package/dist/docs/ScSidebar.md +243 -0
- package/dist/docs/ScSidebarIcons.md +232 -0
- package/dist/docs/ScSidebarMenu.md +283 -0
- package/dist/docs/ScSidebarProfile.md +231 -0
- package/dist/docs/ScSidebarSwitchMenu.md +258 -0
- package/dist/docs/ScSlider.md +194 -0
- package/dist/docs/ScStoreCard.md +252 -0
- package/dist/docs/ScStrLogo.md +253 -0
- package/dist/docs/ScStreamoidWordmark.md +302 -0
- package/dist/docs/ScSubAgent.md +226 -0
- package/dist/docs/ScTabComp.md +308 -0
- package/dist/docs/ScTabField.md +258 -0
- package/dist/docs/ScTabSwitcher.md +307 -0
- package/dist/docs/ScTableHeader.md +261 -0
- package/dist/docs/ScTableList.md +301 -0
- package/dist/docs/ScTableListMobile.md +282 -0
- package/dist/docs/ScTabs.md +268 -0
- package/dist/docs/ScTaxonomyPill.md +263 -0
- package/dist/docs/ScTextArea.md +259 -0
- package/dist/docs/ScTextField.md +324 -0
- package/dist/docs/ScThinkingStepIcon.md +249 -0
- package/dist/docs/ScTodoList.md +288 -0
- package/dist/docs/ScToggleSwitch.md +229 -0
- package/dist/docs/ScUsageHistoryMobile.md +194 -0
- package/dist/docs/ScVDivider.md +215 -0
- package/dist/docs/ScValueMappingL1.md +256 -0
- package/dist/docs/ScVersion.md +251 -0
- package/dist/docs/ScWorkspace.md +233 -0
- package/dist/docs/ScWorkspaceCard.md +234 -0
- package/dist/docs/ScWorkspaceSettingsMobile.md +265 -0
- package/dist/docs/ScWorkspaceSwitchCard.md +314 -0
- package/dist/docs/ScWorkspaceSwitchMobile.md +241 -0
- package/dist/docs/ScWorkspaceSwitchMobileV2.md +278 -0
- package/dist/docs/StreamoidSidebar.md +403 -0
- package/dist/docs/StreamoidWorkspaceSwitcher.md +307 -0
- package/dist/docs/UsageHistoryMobile.md +235 -0
- package/dist/docs/components.json +4849 -0
- package/dist/index.css +36 -36
- package/dist/index.d.mts +10 -0
- package/dist/index.d.ts +10 -0
- package/package.json +3 -2
|
@@ -0,0 +1,307 @@
|
|
|
1
|
+
---
|
|
2
|
+
component: StreamoidWorkspaceSwitcher
|
|
3
|
+
package: "@streamoid/ui"
|
|
4
|
+
category: overlays
|
|
5
|
+
status: legacy
|
|
6
|
+
renders: div
|
|
7
|
+
tags: [workspace-switcher, switch-workspace, modal, overlay, tenant, workspace-list, tailwind]
|
|
8
|
+
related: [ScWorkspaceSwitchCard, ScIntialProfileCover, ScRole, ScPairtext, ScProfilePopup]
|
|
9
|
+
do_not_confuse_with: [ScWorkspaceSwitchCard, ScWorkspaceCard, ScWorkspace, ScWorkspaceSwitchMobileV2, ScModal]
|
|
10
|
+
required_props: [open, onClose, workspaces, onSwitchWorkspace]
|
|
11
|
+
---
|
|
12
|
+
|
|
13
|
+
# StreamoidWorkspaceSwitcher
|
|
14
|
+
|
|
15
|
+
**The DS's prebuilt "switch workspace" modal — and the one component in this family
|
|
16
|
+
that needs Tailwind in the host.** A 1000×60vh centred dialog with a title bar, a
|
|
17
|
+
close icon, a scrolling list of workspace rows (initials tile, name, role pill, user
|
|
18
|
+
count, owner, and a Switch / Current button), Escape-to-close and backdrop-click
|
|
19
|
+
dismissal. Note the export is **`StreamoidWorkspaceSwitcher`** — no `Sc` prefix.
|
|
20
|
+
|
|
21
|
+
## TL;DR for agents
|
|
22
|
+
|
|
23
|
+
- **Reach for it when:** your host already runs Tailwind and you want the whole
|
|
24
|
+
switch-workspace modal without building the shell.
|
|
25
|
+
- **Don't reach for it when:** you want the row only (→ `ScWorkspaceSwitchCard`, what
|
|
26
|
+
all four apps actually use), your host has no Tailwind, or you need the current/other
|
|
27
|
+
grouping and a search box (this component has neither).
|
|
28
|
+
- **Four things that will bite you:**
|
|
29
|
+
1. Its layout is written in **Tailwind utility classes** (`fixed inset-0 z-50`,
|
|
30
|
+
`flex`, `truncate`, `size-full`, `min-w-0`, `overflow-y-auto`) that
|
|
31
|
+
`@streamoid/ui/dist/index.css` **does not ship**. Without Tailwind in the host
|
|
32
|
+
the modal does not overlay, does not scroll and does not centre.
|
|
33
|
+
2. **No host app uses it.** CXO, Catalogix, Photogenix and Artifax each hand-roll
|
|
34
|
+
the modal around `ScWorkspaceSwitchCard` instead.
|
|
35
|
+
3. It returns `null` when `open` is false — it does **not** render a hidden panel,
|
|
36
|
+
and it does not portal, so it inherits your stacking context.
|
|
37
|
+
4. It has no `className` and no style escape hatch. Every dimension (1000px cap,
|
|
38
|
+
60vh height, the 80/120/200px column widths) is hardcoded inline.
|
|
39
|
+
|
|
40
|
+
---
|
|
41
|
+
|
|
42
|
+
## 1. How to use it
|
|
43
|
+
|
|
44
|
+
### Import
|
|
45
|
+
|
|
46
|
+
```tsx
|
|
47
|
+
import {
|
|
48
|
+
StreamoidWorkspaceSwitcher,
|
|
49
|
+
type WorkspaceSwitcherItem,
|
|
50
|
+
type WorkspaceSwitcherConfig,
|
|
51
|
+
} from "@streamoid/ui";
|
|
52
|
+
import "@streamoid/ui/dist/index.css"; // once, at your app root — plus Tailwind, see Gotcha 1
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
> The previous version of this file said `@streamoid/workspace`. **That package does
|
|
56
|
+
> not exist** — this component ships in `@streamoid/ui`.
|
|
57
|
+
|
|
58
|
+
### Minimal usage
|
|
59
|
+
|
|
60
|
+
```tsx
|
|
61
|
+
<StreamoidWorkspaceSwitcher
|
|
62
|
+
open={open}
|
|
63
|
+
onClose={() => setOpen(false)}
|
|
64
|
+
workspaces={workspaces}
|
|
65
|
+
onSwitchWorkspace={(ws) => void switchTo(ws.id)}
|
|
66
|
+
/>
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
### Props
|
|
70
|
+
|
|
71
|
+
`StreamoidWorkspaceSwitcherProps`
|
|
72
|
+
|
|
73
|
+
| Prop | Type | Default | Notes |
|
|
74
|
+
|---|---|---|---|
|
|
75
|
+
| `open` | `boolean` | — | **Required.** `false` → returns `null` (nothing mounted). |
|
|
76
|
+
| `onClose` | `() => void` | — | **Required.** Called by the close icon, a backdrop click (only when the click target *is* the backdrop), and Escape. |
|
|
77
|
+
| `workspaces` | `WorkspaceSwitcherItem[]` | — | **Required.** Rendered in array order — no grouping, no sorting, no search. |
|
|
78
|
+
| `onSwitchWorkspace` | `(ws: WorkspaceSwitcherItem) => void \| Promise<void>` | — | **Required.** Fires only for rows where `isCurrent` is falsy; a current row closes the modal instead. |
|
|
79
|
+
| `loading` | `boolean` | `false` | Shows `config.loadingText` **only while `workspaces` is also empty**. No spinner, no skeleton. |
|
|
80
|
+
| `config` | `WorkspaceSwitcherConfig` | – | The four label strings. |
|
|
81
|
+
|
|
82
|
+
`WorkspaceSwitcherItem`
|
|
83
|
+
|
|
84
|
+
| Field | Type | Notes |
|
|
85
|
+
|---|---|---|
|
|
86
|
+
| `id` | `string` | Required; used as the React `key`. |
|
|
87
|
+
| `name` | `string` | Required. Clamped to **2 lines** (`-webkit-line-clamp: 2`). |
|
|
88
|
+
| `initials` | `string?` | Falls back to first letters of the first two words, else `"WS"`. |
|
|
89
|
+
| `role` | `"admin" \| "member" \| string` | Normalised: **only the exact lowercase `"admin"` is admin**; everything else is member. |
|
|
90
|
+
| `userCount` | `string?` | Free text — you format it (`"12 Users"`). Renders `"–"` when absent. |
|
|
91
|
+
| `ownerEmail` | `string?` | Renders `"–"` when absent. |
|
|
92
|
+
| `isCurrent` | `boolean?` | Swaps the trailing button to a disabled "Current workspace" at `opacity: 0.5`. |
|
|
93
|
+
|
|
94
|
+
`WorkspaceSwitcherConfig`
|
|
95
|
+
|
|
96
|
+
| Field | Type | Default | Notes |
|
|
97
|
+
|---|---|---|---|
|
|
98
|
+
| `title` | `string?` | `"Switch workspace"` | Dialog title. |
|
|
99
|
+
| `loadingText` | `string?` | `"Loading workspaces…"` | Shown only when `loading && workspaces.length === 0`. |
|
|
100
|
+
| `currentWorkspaceText` | `string?` | `"Current workspace"` | Disabled button label on the current row. |
|
|
101
|
+
| `switchWorkspaceText` | `string?` | `"Switch workspace"` | Button label on every other row. |
|
|
102
|
+
|
|
103
|
+
### What each row renders
|
|
104
|
+
|
|
105
|
+
| Slot | Content | Width |
|
|
106
|
+
|---|---|---|
|
|
107
|
+
| Tile | `ScIntialProfileCover` — **initials only, no image support** | 60px (its default) |
|
|
108
|
+
| Name | 2-line clamp | flex |
|
|
109
|
+
| Role | `ScRole type={admin\|member}` | forced `80px` inline |
|
|
110
|
+
| Users | `ScPairtext` with `SiconTeam` | `120px` inline |
|
|
111
|
+
| Owner | `SiconCrown` + truncated email | `200px` inline |
|
|
112
|
+
| Action | `ScButton` — `secondary` + `icon-left` switch, or `outline` + `state="disabled"` | `200px` inline |
|
|
113
|
+
|
|
114
|
+
### Recipes
|
|
115
|
+
|
|
116
|
+
```tsx
|
|
117
|
+
// Driving it from your workspace store
|
|
118
|
+
<StreamoidWorkspaceSwitcher
|
|
119
|
+
open={switcherOpen}
|
|
120
|
+
onClose={() => setSwitcherOpen(false)}
|
|
121
|
+
loading={wsLoading}
|
|
122
|
+
workspaces={workspaces.map((w) => ({
|
|
123
|
+
id: w.id,
|
|
124
|
+
name: w.name,
|
|
125
|
+
initials: w.initials,
|
|
126
|
+
role: w.role === "admin" ? "admin" : "member", // exact lowercase, see Gotcha 5
|
|
127
|
+
userCount: `${w.memberCount} Users`, // you format the string
|
|
128
|
+
ownerEmail: w.ownerEmail,
|
|
129
|
+
isCurrent: w.id === currentWorkspaceId,
|
|
130
|
+
}))}
|
|
131
|
+
onSwitchWorkspace={async (ws) => { await switchTo(ws.id); setSwitcherOpen(false); }}
|
|
132
|
+
config={{ title: "Switch workspace", switchWorkspaceText: "Switch" }}
|
|
133
|
+
/>
|
|
134
|
+
|
|
135
|
+
// Opened from the profile menu
|
|
136
|
+
<ScProfilePopup /* … */ onSwitchWorkspace={() => setSwitcherOpen(true)} />
|
|
137
|
+
|
|
138
|
+
// What the hosts do instead: own the shell, use the row
|
|
139
|
+
<ScModal open={open} onClose={close}>
|
|
140
|
+
{others.map((ws) => (
|
|
141
|
+
<ScWorkspaceSwitchCard key={ws.id} wsName={ws.name} role={ws.role} plan={ws.plan}
|
|
142
|
+
ownerId={ws.ownerEmail} type="otherWS" onSwitch={() => void switchTo(ws)} />
|
|
143
|
+
))}
|
|
144
|
+
</ScModal>
|
|
145
|
+
```
|
|
146
|
+
|
|
147
|
+
A JSON shape for the labels + rows is checked in beside the source at
|
|
148
|
+
`src/SC-WorkspaceSwitcher/workspace-switcher.example.json`.
|
|
149
|
+
|
|
150
|
+
---
|
|
151
|
+
|
|
152
|
+
## 2. Where to use it
|
|
153
|
+
|
|
154
|
+
Intended for any desktop shell's "Switch workspace" flow, opened from
|
|
155
|
+
`ScProfilePopup`'s `onSwitchWorkspace` or a sidebar workspace row.
|
|
156
|
+
|
|
157
|
+
In practice **all four hosts built their own modal** and reused only the row:
|
|
158
|
+
|
|
159
|
+
| App | Their shell |
|
|
160
|
+
|---|---|
|
|
161
|
+
| CXO | `src/app/components/app-workspace-switcher.tsx` (+ search + current/other sections) |
|
|
162
|
+
| Catalogix | `app/containers/LeftMenu/WorkspaceListing/index.jsx` |
|
|
163
|
+
| Photogenix | `dashboard/client/src/components/layout/Sidebar.tsx` |
|
|
164
|
+
| Artifax | `packages/shared/src/components/DashboardSidebar.tsx` (also fetches plans per row) |
|
|
165
|
+
|
|
166
|
+
They did that because they needed search, current/other grouping, a plan column and
|
|
167
|
+
per-row settings/leave actions — none of which this component offers.
|
|
168
|
+
|
|
169
|
+
---
|
|
170
|
+
|
|
171
|
+
## 3. When to use it
|
|
172
|
+
|
|
173
|
+
### Use it when
|
|
174
|
+
|
|
175
|
+
- The host already ships Tailwind **and** a flat, unsearchable list of workspaces is
|
|
176
|
+
genuinely enough.
|
|
177
|
+
- You want Escape + backdrop dismissal handed to you.
|
|
178
|
+
|
|
179
|
+
### Don't use it — reach for this instead
|
|
180
|
+
|
|
181
|
+
| Situation | Use instead |
|
|
182
|
+
|---|---|
|
|
183
|
+
| Anything in a non-Tailwind host | `ScWorkspaceSwitchCard` inside `ScModal` / `ScDrawer` |
|
|
184
|
+
| You need search, grouping, or a plan column | `ScWorkspaceSwitchCard` in your own shell (what all four apps do) |
|
|
185
|
+
| You need per-row settings / leave actions | `ScWorkspaceSwitchCard`'s `onSettings` / `onLeave` |
|
|
186
|
+
| Workspace rows with avatars (not just initials) | `ScWorkspaceSwitchCard` (`imageUrl` → `ScDp`) |
|
|
187
|
+
| Mobile | `ScWorkspaceSwitchMobileV2` |
|
|
188
|
+
| A generic modal shell you fill yourself | `ScModal` |
|
|
189
|
+
| A workspace card with a user count and a CTA | `ScWorkspaceCard` |
|
|
190
|
+
|
|
191
|
+
### Don't confuse with
|
|
192
|
+
|
|
193
|
+
| You may actually want | Not this |
|
|
194
|
+
|---|---|
|
|
195
|
+
| `ScWorkspaceSwitchCard` — **one row**, `wsName`/`role`/`plan`/`ownerId`, hover actions, avatar support | `StreamoidWorkspaceSwitcher` is the whole modal, initials only, no plan |
|
|
196
|
+
| `ScWorkspace` — the bare identity line (not exported) | this is a dialog |
|
|
197
|
+
| `ScWorkspaceCard` — card + persistent CTA | this lists many |
|
|
198
|
+
| `ScModal` — the DS modal primitive | this is a purpose-built dialog with no children slot |
|
|
199
|
+
| `ScProfilePopup` — the profile menu that *opens* this | different panel |
|
|
200
|
+
|
|
201
|
+
Naming: this is the only workspace component **without** the `Sc` prefix — the sibling
|
|
202
|
+
of `StreamoidSidebar` in that respect. `ScWorkspaceSwitcher` does not exist.
|
|
203
|
+
|
|
204
|
+
---
|
|
205
|
+
|
|
206
|
+
## 4. Why to use it
|
|
207
|
+
|
|
208
|
+
What it does give you, if the Tailwind precondition holds:
|
|
209
|
+
|
|
210
|
+
- **Dismissal wired three ways** — the close icon, Escape (a `keydown` listener added
|
|
211
|
+
only while `open`, and cleaned up), and a backdrop click that correctly checks
|
|
212
|
+
`e.target === e.currentTarget` so clicks inside the dialog don't close it.
|
|
213
|
+
- **`isCurrent` handled** — the current row's button becomes a disabled
|
|
214
|
+
"Current workspace", and clicking its switch path closes the modal instead of firing
|
|
215
|
+
`onSwitchWorkspace`.
|
|
216
|
+
- **Async-aware** — `onSwitchWorkspace` may return a promise; the component `void`s it
|
|
217
|
+
rather than assuming sync.
|
|
218
|
+
- **Composed from real DS parts** — `ScIntialProfileCover`, `ScRole`, `ScPairtext`,
|
|
219
|
+
`ScVDivider`, `ScButton` — so the row internals stay on-token.
|
|
220
|
+
- **Initials derivation and role normalisation** are built in.
|
|
221
|
+
|
|
222
|
+
What it costs: a Tailwind dependency, no style hooks, no search/grouping, and a
|
|
223
|
+
divergence from the row every host already renders. That is why it is marked
|
|
224
|
+
**legacy** here — not because it is broken, but because the ecosystem chose
|
|
225
|
+
`ScWorkspaceSwitchCard` + a host shell.
|
|
226
|
+
|
|
227
|
+
---
|
|
228
|
+
|
|
229
|
+
## Gotchas
|
|
230
|
+
|
|
231
|
+
**1. It needs Tailwind, and the DS does not ship it.** The overlay, centring,
|
|
232
|
+
scrolling and truncation are Tailwind utilities (`fixed inset-0 z-50 flex items-center
|
|
233
|
+
justify-center`, `flex-1 min-h-0 overflow-y-auto`, `truncate`, `size-full`, `w-6 h-6`,
|
|
234
|
+
`shrink-0`). None of those class names exist in `@streamoid/ui/dist/index.css`. In a
|
|
235
|
+
plain-CSS host the "modal" renders as an inline block in the document flow.
|
|
236
|
+
|
|
237
|
+
```tsx
|
|
238
|
+
// WRONG in a non-Tailwind host — renders unstyled, in flow, with no overlay
|
|
239
|
+
<StreamoidWorkspaceSwitcher open workspaces={list} onClose={close} onSwitchWorkspace={go} />
|
|
240
|
+
|
|
241
|
+
// RIGHT — build the shell with tokens/ScModal and reuse the row
|
|
242
|
+
<ScModal open={open} onClose={close}>
|
|
243
|
+
{list.map((ws) => <ScWorkspaceSwitchCard key={ws.id} wsName={ws.name} type="otherWS" onSwitch={() => go(ws)} /* … */ />)}
|
|
244
|
+
</ScModal>
|
|
245
|
+
```
|
|
246
|
+
|
|
247
|
+
**2. No `className`, no `style`, no spread.** `StreamoidWorkspaceSwitcherProps` has
|
|
248
|
+
exactly six keys. Width (`min(1000px, 90vw)`), height (`60vh`) and the 80/120/200px
|
|
249
|
+
column widths are inline literals you cannot reach.
|
|
250
|
+
|
|
251
|
+
**3. It does not portal.** The `fixed` root is rendered where you mount it, so it
|
|
252
|
+
inherits ancestor `transform` / `filter` / `contain` — any of which turn `fixed` into
|
|
253
|
+
"relative to that ancestor" and break the overlay. Mount it near the app root.
|
|
254
|
+
|
|
255
|
+
**4. `loading` only shows while the list is empty.** `loading && workspaces.length === 0`
|
|
256
|
+
is the condition, and the fallback is a single line of text — no spinner, and no
|
|
257
|
+
indication during a refresh of an already-populated list.
|
|
258
|
+
|
|
259
|
+
**5. Role matching is exact-lowercase.** `normalizeRole` is
|
|
260
|
+
`role === "admin" ? "admin" : "member"` — `"Admin"`, `"ADMIN"` and `"owner"` all render
|
|
261
|
+
the grey **Member** pill. Lowercase before you pass it. (This differs from
|
|
262
|
+
`ScWorkspaceSwitchCard`, which lowercases for you.)
|
|
263
|
+
|
|
264
|
+
**6. `userCount` is a formatted string, not a number.** Pass `"12 Users"`; a bare
|
|
265
|
+
number renders as bare digits with no unit.
|
|
266
|
+
|
|
267
|
+
**7. Initials only — no avatars.** The tile is `ScIntialProfileCover`, which has no
|
|
268
|
+
image support at all. `ScWorkspaceSwitchCard` uses `ScDp` and accepts `imageUrl`.
|
|
269
|
+
|
|
270
|
+
**8. `name` is clamped to two lines, not ellipsised to one.** Row height varies with
|
|
271
|
+
name length.
|
|
272
|
+
|
|
273
|
+
**9. The dialog has no ARIA.** No `role="dialog"`, no `aria-modal`, no `aria-labelledby`,
|
|
274
|
+
no focus trap and no focus restore on close. Escape works; nothing else about it is
|
|
275
|
+
accessible. Wrap it or use `ScModal` if that matters.
|
|
276
|
+
|
|
277
|
+
**10. Nothing in the rows is keyboard-reachable except the buttons.** The close "icon"
|
|
278
|
+
is a `<div onClick>` with `cursor: pointer` — not a button, not focusable, no
|
|
279
|
+
`aria-label`.
|
|
280
|
+
|
|
281
|
+
**11. `config` is all-or-nothing per field, and there is no `emptyText`.** An empty
|
|
282
|
+
`workspaces` array with `loading={false}` renders a blank scroll area.
|
|
283
|
+
|
|
284
|
+
---
|
|
285
|
+
|
|
286
|
+
## In the wild
|
|
287
|
+
|
|
288
|
+
_No host render site found — used by the agent runtime / composed internally._
|
|
289
|
+
|
|
290
|
+
No app imports it (the only hits outside the package are Vite's prebundled dep cache,
|
|
291
|
+
`artifax/frontend-react/.vite/deps/@streamoid_ui.js`, which is build output, not a
|
|
292
|
+
render site). The surface it belongs on is the workspace-switch modal each host builds
|
|
293
|
+
by hand — e.g. `cxo-dashboard src/app/components/app-workspace-switcher.tsx:408` or
|
|
294
|
+
`catalogix/dashboard app/containers/LeftMenu/WorkspaceListing/index.jsx:371`, both of
|
|
295
|
+
which render `ScWorkspaceSwitchCard` rows inside their own shell. Adopting this
|
|
296
|
+
component would mean adding search + current/other grouping + a plan column to it
|
|
297
|
+
first.
|
|
298
|
+
|
|
299
|
+
---
|
|
300
|
+
|
|
301
|
+
## Related
|
|
302
|
+
|
|
303
|
+
- `ScWorkspaceSwitchCard` — **the row all four hosts actually use**; richer than the row here.
|
|
304
|
+
- `ScModal` / `ScDrawer` — the shells to build your own switcher in.
|
|
305
|
+
- `ScIntialProfileCover` / `ScRole` / `ScPairtext` / `ScButton` — the parts each row composes.
|
|
306
|
+
- `ScProfilePopup` — `onSwitchWorkspace` is the trigger for this flow.
|
|
307
|
+
- `ScWorkspaceSwitchMobileV2` — the mobile surface.
|
|
@@ -0,0 +1,235 @@
|
|
|
1
|
+
---
|
|
2
|
+
component: UsageHistoryMobile
|
|
3
|
+
package: "@streamoid/ui"
|
|
4
|
+
category: mobile
|
|
5
|
+
status: stable
|
|
6
|
+
renders: div
|
|
7
|
+
tags: [mobile, billing, usage, credits, log, history, row, list-item, cxo]
|
|
8
|
+
related: [InvoiceHistoryMobile, ScBillingLogsTableList, ScBillingLogsTableHeader, ScUsageHistoryMobile]
|
|
9
|
+
do_not_confuse_with: [ScUsageHistoryMobile, InvoiceHistoryMobile, ScBillingLogsTableList, ScCreditsUsageCardMobile]
|
|
10
|
+
used_by: [cxo]
|
|
11
|
+
---
|
|
12
|
+
|
|
13
|
+
# UsageHistoryMobile
|
|
14
|
+
|
|
15
|
+
**One credit-usage log row in CXO's mobile billing list.** Two rows inside one row:
|
|
16
|
+
service name + a **red** credit figure on top, then `user · date` + a running balance
|
|
17
|
+
underneath, with a 0.5px bottom border. Note the export name: **no `Sc` prefix**.
|
|
18
|
+
|
|
19
|
+
## TL;DR for agents
|
|
20
|
+
|
|
21
|
+
- **Reach for it when:** you are rendering the "Usage history" tab of a mobile billing
|
|
22
|
+
screen, one component per credit-debit log entry.
|
|
23
|
+
- **Don't reach for it when:** you're on desktop (→ `ScBillingLogsTableHeader` +
|
|
24
|
+
`ScBillingLogsTableList`), the row is an invoice (→ `InvoiceHistoryMobile`), or you
|
|
25
|
+
want the aggregate credits meter (→ `ScCreditsUsageCardMobile`).
|
|
26
|
+
- **Four things that will bite you:**
|
|
27
|
+
1. **The export is `UsageHistoryMobile`, not `ScUsageHistoryMobile`.** A
|
|
28
|
+
`ScUsageHistoryMobile` exists in the source tree
|
|
29
|
+
(`src/SC-UsageHistory-Mobile/`) but is **not exported**.
|
|
30
|
+
2. **No `onClick`, and no props spread.** `className` is the only extra prop.
|
|
31
|
+
3. **`credits` is always red** and **`balance` always tertiary grey** — a credit
|
|
32
|
+
*refund* still renders as a debit.
|
|
33
|
+
4. All five text props have demo defaults (`"Artifax Generation"`, `"50"`,
|
|
34
|
+
`"Rohan"`, `"Nov 9th, 2025"`, `"84,902"`).
|
|
35
|
+
|
|
36
|
+
---
|
|
37
|
+
|
|
38
|
+
## 1. How to use it
|
|
39
|
+
|
|
40
|
+
### Import
|
|
41
|
+
|
|
42
|
+
```tsx
|
|
43
|
+
import { UsageHistoryMobile } from "@streamoid/ui";
|
|
44
|
+
import "@streamoid/ui/dist/index.css"; // once, at your app root
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
### Minimal usage
|
|
48
|
+
|
|
49
|
+
```tsx
|
|
50
|
+
{logs.map((log) => (
|
|
51
|
+
<UsageHistoryMobile
|
|
52
|
+
key={log.id}
|
|
53
|
+
serviceName={log.serviceName}
|
|
54
|
+
credits={log.amount.toLocaleString("en-US")}
|
|
55
|
+
userName={log.userName}
|
|
56
|
+
date={formatDate(log.createdAt)}
|
|
57
|
+
balance={log.balanceAfter.toLocaleString("en-US")}
|
|
58
|
+
/>
|
|
59
|
+
))}
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
### Props
|
|
63
|
+
|
|
64
|
+
| Prop | Type | Default | Notes |
|
|
65
|
+
|---|---|---|---|
|
|
66
|
+
| `serviceName` | `string` | `"Artifax Generation"` | ⚠️ Demo default. 14px primary, top-left, `flex: 1` + ellipsis. |
|
|
67
|
+
| `credits` | `string` | `"50"` | ⚠️ Demo default. 16px/600 top-right, hardcoded `--alias-text-and-icons-error` **red**. You supply any sign/prefix. |
|
|
68
|
+
| `userName` | `string` | `"Rohan"` | ⚠️ Demo default. Bottom-left, before a 12px vertical divider. |
|
|
69
|
+
| `date` | `string` | `"Nov 9th, 2025"` | ⚠️ Demo default. Bottom-left, after the divider. Pre-formatted string. |
|
|
70
|
+
| `balance` | `string` | `"84,902"` | ⚠️ Demo default. Bottom-right, tertiary grey — the balance **after** this entry. |
|
|
71
|
+
| `className` | `string` | – | Appended after internal classes (safely — uses `className ?? ""`). **The only extra prop it accepts.** |
|
|
72
|
+
|
|
73
|
+
That is the whole API. No `onClick`, no `...props`, no icon, no status.
|
|
74
|
+
|
|
75
|
+
### Layout
|
|
76
|
+
|
|
77
|
+
| | left | right |
|
|
78
|
+
|---|---|---|
|
|
79
|
+
| **row 1** | `serviceName` (14px primary, truncates) | `credits` (16px/600 **red**) |
|
|
80
|
+
| **row 2** | `userName` │ `date` (14px tertiary, 9px gap, 0.5px vertical rule) | `balance` (14px tertiary) |
|
|
81
|
+
|
|
82
|
+
Row 2's left group is `flex: 1; min-width: 0`, but `userName` and `date` are each
|
|
83
|
+
`flex-shrink: 0`, so a very long user name pushes the date out rather than truncating
|
|
84
|
+
itself.
|
|
85
|
+
|
|
86
|
+
### Recipes
|
|
87
|
+
|
|
88
|
+
```tsx
|
|
89
|
+
// Make the row tappable — the component has no onClick
|
|
90
|
+
<div role="button" tabIndex={0} onClick={() => openLog(log)} className="cursor-pointer">
|
|
91
|
+
<UsageHistoryMobile
|
|
92
|
+
serviceName={log.service?.serviceGroupName ?? "—"}
|
|
93
|
+
credits={log.amount.toLocaleString("en-US")}
|
|
94
|
+
userName={userMap[log.triggeredByUserId] ?? "User"}
|
|
95
|
+
date={formatDate(log.createdAt)}
|
|
96
|
+
balance={log.balanceAfter.toLocaleString("en-US")}
|
|
97
|
+
/>
|
|
98
|
+
</div>
|
|
99
|
+
|
|
100
|
+
// System-triggered entries have no user — show an em dash, not "undefined"
|
|
101
|
+
<UsageHistoryMobile
|
|
102
|
+
serviceName={log.service?.serviceGroupName ?? log.meta?.info ?? "—"}
|
|
103
|
+
credits={log.amount.toLocaleString("en-US")}
|
|
104
|
+
userName={log.triggeredByUserId ? (userMap[log.triggeredByUserId] ?? "User") : "—"}
|
|
105
|
+
date={formatDate(log.createdAt)}
|
|
106
|
+
balance={formatCredits(log.balanceAfter)}
|
|
107
|
+
/>
|
|
108
|
+
```
|
|
109
|
+
|
|
110
|
+
---
|
|
111
|
+
|
|
112
|
+
## 2. Where to use it
|
|
113
|
+
|
|
114
|
+
- **CXO mobile billing screen**, inside the "Usage history" tab of the
|
|
115
|
+
`ScTabSwitcher`, below `ScPlanDetailsCardMobile` and `ScCreditsUsageCardMobile`.
|
|
116
|
+
- Any narrow scrolling ledger of "what was spent, by whom, when, balance after".
|
|
117
|
+
|
|
118
|
+
The row draws its own bottom border, so stack them with no gap and no dividers of your
|
|
119
|
+
own. It composes nothing from the DS (the vertical rule is a 0.5px `border-left` div,
|
|
120
|
+
not `ScVDivider`).
|
|
121
|
+
|
|
122
|
+
---
|
|
123
|
+
|
|
124
|
+
## 3. When to use it
|
|
125
|
+
|
|
126
|
+
### Use it when
|
|
127
|
+
|
|
128
|
+
- The viewport is mobile and each log entry needs four facts plus a service name.
|
|
129
|
+
- You already have every value as a display string.
|
|
130
|
+
- The list is read-only, or you own the tap target yourself.
|
|
131
|
+
|
|
132
|
+
### Don't use it — reach for this instead
|
|
133
|
+
|
|
134
|
+
| Situation | Use instead |
|
|
135
|
+
|---|---|
|
|
136
|
+
| Desktop credit/usage log table | `ScBillingLogsTableHeader` + `ScBillingLogsTableList` |
|
|
137
|
+
| Mobile invoice rows (money in, green) | `InvoiceHistoryMobile` |
|
|
138
|
+
| Desktop invoice table | `ScBillingHistoryHeader` + `ScBillingHistoryTableList` |
|
|
139
|
+
| The credits meter / "buy credits" summary | `ScCreditsUsageCardMobile` |
|
|
140
|
+
| A generic member row on mobile | `ScTableListMobile` |
|
|
141
|
+
| A row that must be clickable with DS semantics | `ScTableListMobile` (`onRowClick`) |
|
|
142
|
+
|
|
143
|
+
### Don't confuse with
|
|
144
|
+
|
|
145
|
+
| You may actually want | Not this |
|
|
146
|
+
|---|---|
|
|
147
|
+
| `ScUsageHistoryMobile` in `src/SC-UsageHistory-Mobile/` — a near-identical copy with `title`/`usageCount`/`totalCount` instead of `serviceName`/`credits`/`balance`, plus `onClick` | It is **not exported** from `index.ts`, its CSS is **not** in `dist/index.css`. Importing it will fail. |
|
|
148
|
+
| `InvoiceHistoryMobile` — same chrome, one text column, **green** amount | Usage is red credits out; invoices are green money in |
|
|
149
|
+
| `ScCreditsUsageCardMobile` — the aggregate card at the top of the screen | This is a single ledger line |
|
|
150
|
+
| `ScBillingLogsTableList` — the desktop row | Different metrics; needs a header component |
|
|
151
|
+
|
|
152
|
+
---
|
|
153
|
+
|
|
154
|
+
## 4. Why to use it
|
|
155
|
+
|
|
156
|
+
- **Four values in a 2×2 grid without a table.** The nested flex rows with
|
|
157
|
+
`min-width: 0` and per-cell `flex-shrink` are exactly what hand-rolled mobile
|
|
158
|
+
ledgers get wrong — the money never wraps and never gets pushed off-screen.
|
|
159
|
+
- **Colour semantics match the invoice row.** Red `--alias-text-and-icons-error` for
|
|
160
|
+
credits out, green for money in, tertiary grey for context — one consistent reading
|
|
161
|
+
across both tabs.
|
|
162
|
+
- **Correct tokens.** It uses the real `--alias-text-and-icons-*` names (unlike much of
|
|
163
|
+
the mobile family), so it genuinely flips light/dark.
|
|
164
|
+
- **Self-dividing**, so no trailing rule at the end of the list.
|
|
165
|
+
|
|
166
|
+
---
|
|
167
|
+
|
|
168
|
+
## Gotchas
|
|
169
|
+
|
|
170
|
+
**1. The name has no `Sc` prefix.** Autocomplete for `Sc…` will not find it.
|
|
171
|
+
|
|
172
|
+
```tsx
|
|
173
|
+
// WRONG — not exported
|
|
174
|
+
import { ScUsageHistoryMobile } from "@streamoid/ui";
|
|
175
|
+
|
|
176
|
+
// RIGHT
|
|
177
|
+
import { UsageHistoryMobile } from "@streamoid/ui";
|
|
178
|
+
```
|
|
179
|
+
|
|
180
|
+
**2. No props spread at all.** The signature is
|
|
181
|
+
`({ serviceName, credits, userName, date, balance, className })`.
|
|
182
|
+
|
|
183
|
+
```tsx
|
|
184
|
+
// SILENTLY IGNORED — none of these reach the DOM
|
|
185
|
+
<UsageHistoryMobile serviceName="X" onClick={open} style={{ opacity: 0.5 }} data-id="1" />
|
|
186
|
+
|
|
187
|
+
// RIGHT — wrap it
|
|
188
|
+
<div onClick={open} data-id="1"><UsageHistoryMobile serviceName="X" /></div>
|
|
189
|
+
```
|
|
190
|
+
|
|
191
|
+
**3. `credits` is unconditionally red.** There is no sign handling and no variant, so a
|
|
192
|
+
credit grant or refund still reads as a debit. If you need both directions, prefix the
|
|
193
|
+
string (`"+ 500"`) and accept the colour, or use a different row.
|
|
194
|
+
|
|
195
|
+
**4. `balance` is not computed.** The component does no arithmetic — it is whatever
|
|
196
|
+
string you pass. Passing the same value for `credits` and `balance` renders a
|
|
197
|
+
plausible-looking but wrong ledger, so derive the running balance from your data.
|
|
198
|
+
|
|
199
|
+
**5. Demo defaults on all five props.** Bare, it renders
|
|
200
|
+
`Artifax Generation / 50 / Rohan · Nov 9th, 2025 / 84,902` — indistinguishable from
|
|
201
|
+
real data in a screenshot.
|
|
202
|
+
|
|
203
|
+
**6. `userName` and `date` don't truncate, they shove.** Both are `flex-shrink: 0`.
|
|
204
|
+
A long user name pushes the date out of the visible row instead of ellipsising. Cap
|
|
205
|
+
long names yourself.
|
|
206
|
+
|
|
207
|
+
**7. The vertical rule between user and date is unconditional.** With
|
|
208
|
+
`userName=""` you get a leading rule. Pass `"—"` rather than an empty string.
|
|
209
|
+
|
|
210
|
+
---
|
|
211
|
+
|
|
212
|
+
## In the wild
|
|
213
|
+
|
|
214
|
+
```tsx
|
|
215
|
+
// cxo-dashboard src/app/components/mobile-billing-content.tsx:254
|
|
216
|
+
<UsageHistoryMobile
|
|
217
|
+
key={log._id}
|
|
218
|
+
serviceName={log.service?.serviceGroupName ?? log.meta?.info ?? "—"}
|
|
219
|
+
credits={log.amount.toLocaleString("en-US")}
|
|
220
|
+
userName={log.triggeredByUserId ? (userMap[log.triggeredByUserId] ?? "User") : "—"}
|
|
221
|
+
date={formatDate(log.createdAt)}
|
|
222
|
+
balance={formatCredits(log.amount)}
|
|
223
|
+
/>
|
|
224
|
+
```
|
|
225
|
+
|
|
226
|
+
---
|
|
227
|
+
|
|
228
|
+
## Related
|
|
229
|
+
|
|
230
|
+
- `InvoiceHistoryMobile` — the sibling row for the "Invoice history" tab.
|
|
231
|
+
- `ScUsageHistoryMobile` (`src/SC-UsageHistory-Mobile/`) — the unexported earlier copy;
|
|
232
|
+
do not import it.
|
|
233
|
+
- `ScBillingLogsTableHeader` / `ScBillingLogsTableList` — the desktop table.
|
|
234
|
+
- `ScCreditsUsageCardMobile` — the aggregate meter above the tabs.
|
|
235
|
+
- `ScTabSwitcher` / `ScTabComp` — what toggles invoice vs usage.
|