@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,216 @@
1
+ ---
2
+ component: ScProfileV2Mobile
3
+ package: "@streamoid/ui"
4
+ category: mobile
5
+ status: stable
6
+ renders: div
7
+ tags: [mobile, profile, avatar, initials, name, email, user, identity]
8
+ related: [ScProfile, ScDp, ScTableListMobile, ScReferralCardMobile, ScProfilePopup]
9
+ do_not_confuse_with: [ScProfile, ScProfileOptions, ScProfilePopup, ScProfileSettingsComp, ScDp, ScTableListMobile]
10
+ ---
11
+
12
+ # ScProfileV2Mobile
13
+
14
+ **A 40px avatar + name + email, as one mobile identity row.** Purely presentational:
15
+ a circular image (or two-letter initials derived from `name`) on the left or right,
16
+ with a two-line text block beside it. No menu, no chevron, no click behaviour.
17
+
18
+ ## TL;DR for agents
19
+
20
+ - **Reach for it when:** a mobile sheet or header needs "who is signed in" as an
21
+ avatar plus name/email, and nothing else.
22
+ - **Don't reach for it when:** you're in the desktop sidebar footer (→ `ScProfile`,
23
+ which has a `collapsed` mode), you need the profile *menu* (→ `ScProfileOptions` /
24
+ `ScProfilePopup`), or the row is a team-member list item with role and permission
25
+ icons (→ `ScTableListMobile`).
26
+ - **Three things that will bite you:**
27
+ 1. **It is `width: 288px`, fixed.** Not `100%`. It will not fill your container
28
+ and it will overflow anything narrower than 288px.
29
+ 2. **`name` and `email` default to `"Chris Hemsworth"` / `"chris@kepler.com"`.**
30
+ 3. `className` is concatenated unguarded, so omitting it puts the literal class
31
+ `undefined` on the root element.
32
+
33
+ ---
34
+
35
+ ## 1. How to use it
36
+
37
+ ### Import
38
+
39
+ ```tsx
40
+ import { ScProfileV2Mobile } from "@streamoid/ui";
41
+ import "@streamoid/ui/dist/index.css"; // once, at your app root
42
+ ```
43
+
44
+ ### Minimal usage
45
+
46
+ ```tsx
47
+ <ScProfileV2Mobile name={user.name} email={user.email} imageUrl={user.avatarUrl} />
48
+ ```
49
+
50
+ ### Props
51
+
52
+ | Prop | Type | Default | Notes |
53
+ |---|---|---|---|
54
+ | `name` | `string` | `"Chris Hemsworth"` | ⚠️ Demo default. Also the source of the initials **and** the `<img alt>`. |
55
+ | `email` | `string` | `"chris@kepler.com"` | ⚠️ Demo default. Rendered as the grey 12px sub-line — it is really "subtitle", any string works. |
56
+ | `imageUrl` | `string` | – | When set, renders `<img src>` (`object-fit: cover`). When unset, renders initials. No error fallback — see Gotcha 4. |
57
+ | `dpPosition` | `"left"` \| `"right"` | `"left"` | Which side the avatar sits on. The text block always stays in the middle/flex-grow slot. |
58
+ | `className` | `string` | – | Appended after internal classes. Unguarded concat — see Gotcha 3. |
59
+ | `...props` | `React.HTMLAttributes<HTMLDivElement>` | – | Spread onto the root `div`. This is the only way to attach `onClick`, `role`, `style`. |
60
+
61
+ ### How the initials are derived
62
+
63
+ `name.split(" ").map(n => n[0]).join("").toUpperCase().slice(0, 2)`
64
+
65
+ | `name` | Initials |
66
+ |---|---|
67
+ | `"Chris Hemsworth"` | `CH` |
68
+ | `"chris"` | `C` |
69
+ | `"Ana Maria De Souza"` | `AM` (sliced to 2) |
70
+ | `""` | *(empty circle)* |
71
+ | `" Chris"` | `C` — empty segments contribute nothing |
72
+
73
+ There is no `initials` prop. If you need to control the initials (workspace codes,
74
+ single-letter avatars), use `ScDp` with `initial=` instead.
75
+
76
+ ### Recipes
77
+
78
+ ```tsx
79
+ // Avatar on the right — e.g. a right-aligned mobile header slot
80
+ <ScProfileV2Mobile
81
+ name={user.name}
82
+ email={user.email}
83
+ imageUrl={user.avatarUrl}
84
+ dpPosition="right"
85
+ />
86
+
87
+ // Make it fill its container (the component is 288px by default)
88
+ <ScProfileV2Mobile name={user.name} email={user.email} className="w-full" />
89
+
90
+ // Make it tappable — the component gives you no semantics, so add them
91
+ <ScProfileV2Mobile
92
+ name={user.name}
93
+ email={user.email}
94
+ role="button"
95
+ tabIndex={0}
96
+ onClick={openProfileSheet}
97
+ onKeyDown={(e) => (e.key === "Enter" || e.key === " ") && openProfileSheet()}
98
+ className="w-full cursor-pointer"
99
+ />
100
+ ```
101
+
102
+ ---
103
+
104
+ ## 2. Where to use it
105
+
106
+ Intended for CXO's mobile surface: the header of a profile bottom sheet, the top of
107
+ a mobile settings screen, or the identity line in a mobile menu — anywhere the
108
+ desktop would use `ScProfile` in the sidebar footer.
109
+
110
+ It composes nothing from the DS (its avatar is hand-rolled, not `ScDp`).
111
+
112
+ ---
113
+
114
+ ## 3. When to use it
115
+
116
+ ### Use it when
117
+
118
+ - You need avatar + two text lines, on mobile, with no interaction.
119
+ - The initials can be mechanically derived from the display name.
120
+
121
+ ### Don't use it — reach for this instead
122
+
123
+ | Situation | Use instead |
124
+ |---|---|
125
+ | Desktop sidebar footer profile (needs an expanded/collapsed mode) | `ScProfile` — has `collapsed`, `subText`, `profileImage` |
126
+ | The profile **menu** (theme toggle, settings, log out) | `ScProfilePopup` / `ScProfileOptions` |
127
+ | A whole profile settings screen | `ScProfileSettingsComp` |
128
+ | Just an avatar, with controllable initials and a workspace/profile shape | `ScDp` (`variant="workspace" \| "profile"`, `size`, `initial`) |
129
+ | Avatar upload / crop | `ScProfileImageUpdate` |
130
+ | A team-member row with role badge + access icons | `ScTableListMobile` |
131
+ | A referral row with a status badge and credits | `ScReferralCardMobile` |
132
+ | A workspace (not a person) identity row | `ScWorkspaceSwitchMobileV2` |
133
+
134
+ ### Don't confuse with
135
+
136
+ | You may actually want | Not this |
137
+ |---|---|
138
+ | `ScProfile` — the desktop profile block; `subText` not `email`, `profileImage` not `imageUrl` | `ScProfileV2Mobile` uses different prop names for the same data |
139
+ | `ScProfileOptions` — a single menu row inside a profile menu | Not an identity display |
140
+ | `ScProfilePopup` — the whole floating profile menu | Not an identity display |
141
+ | `ScDp` — the avatar primitive | `ScProfileV2Mobile` re-implements a 40px circle rather than composing `ScDp` |
142
+ | `ScTableListMobile` — `name` + `email` + `role` + icon groups + `onRowClick` | The superset row; use it for lists |
143
+
144
+ There is **no** `ScProfileV1Mobile`. The "V2" is a design-iteration marker, not a
145
+ version you can choose.
146
+
147
+ ---
148
+
149
+ ## 4. Why to use it
150
+
151
+ - **The one job it does well:** name/email truncation. Both lines get
152
+ `overflow: hidden; text-overflow: ellipsis; white-space: nowrap` inside a
153
+ `flex: 1 0 0; min-width: 0` column, which is the exact combination hand-rolled
154
+ identity rows get wrong (long emails blowing out the row).
155
+ - **`dpPosition` handles mirrored layouts** without you re-ordering DOM.
156
+ - **Consistent 40px avatar and 14/12px type pair** with the rest of the mobile
157
+ family (`ScTableListMobile`, `ScReferralCardMobile` use the same metrics).
158
+
159
+ Given the fixed width, treat this as a thin convenience —
160
+ `ScDp` + two `<p>`s is a legitimate alternative if you need control.
161
+
162
+ ---
163
+
164
+ ## Gotchas
165
+
166
+ **1. Fixed `width: 288px`.** The root is not fluid.
167
+
168
+ ```tsx
169
+ // WRONG — stays 288px inside a full-width sheet, and overflows a 320px phone
170
+ // with side padding
171
+ <ScProfileV2Mobile name={n} email={e} />
172
+
173
+ // RIGHT
174
+ <ScProfileV2Mobile name={n} email={e} className="w-full" />
175
+ ```
176
+
177
+ **2. Demo defaults.** `name="Chris Hemsworth"`, `email="chris@kepler.com"`. Rendering
178
+ it bare in a placeholder screen ships a fake person.
179
+
180
+ **3. `className` is concatenated with `+ " " + className`, unguarded.** Omit it and
181
+ the root renders `class="ScProfileV2Mobile_scProfileV2Mobile undefined"`. Harmless
182
+ visually, but it breaks exact-`class` snapshot assertions and
183
+ `[class="…"]` selectors.
184
+
185
+ **4. A broken `imageUrl` shows a broken image, not initials.** There is no `onError`
186
+ fallback — the initials branch is chosen purely on `imageUrl` being truthy. Validate
187
+ the URL, or use `ScDp` (which falls back to `initial`).
188
+
189
+ **5. No interaction semantics.** It's a plain `div`: no `role`, no `tabIndex`, no
190
+ `cursor: pointer`. Attaching `onClick` via `...props` works but is invisible and
191
+ keyboard-inaccessible until you add the rest yourself.
192
+
193
+ **6. `email` is unvalidated free text** and is not rendered as a `mailto:` link.
194
+
195
+ ---
196
+
197
+ ## In the wild
198
+
199
+ _No host render site found — used by the agent runtime / composed internally._
200
+
201
+ To be precise: it is exported from `@streamoid/ui` but **no** host app renders it,
202
+ and it is not agent-runtime either — it is currently unused. Where it belongs is
203
+ CXO's mobile settings/profile surface
204
+ (`cxo-dashboard/src/app/components/mobile-settings-content.tsx`), which today shows
205
+ identity with hand-rolled markup, and `mobile-teams-content.tsx`, which uses the
206
+ richer `ScTableListMobile` for member rows.
207
+
208
+ ---
209
+
210
+ ## Related
211
+
212
+ - `ScProfile` — the desktop sibling with a collapsed mode.
213
+ - `ScDp` — the avatar primitive; use it when you need to control the initials.
214
+ - `ScTableListMobile` — the mobile list-row superset (role + access icons + click).
215
+ - `ScProfilePopup` / `ScProfileOptions` — the profile menu, not the identity block.
216
+ - `ScWorkspaceSwitchMobileV2` — the workspace equivalent of this row.
@@ -0,0 +1,267 @@
1
+ ---
2
+ component: ScProgressBar
3
+ package: "@streamoid/ui"
4
+ category: overlays
5
+ status: stable
6
+ renders: div (track) > div (fill)
7
+ tags: [progress, progressbar, meter, percent, loading, upload, credits, determinate]
8
+ related: [ScCreditsUsageCard, ScBadges, ScBeacon, ScSlider, ScButton]
9
+ do_not_confuse_with: [ScSlider, ScBadges, ScCounter]
10
+ used_by: [photogenix]
11
+ ---
12
+
13
+ # ScProgressBar
14
+
15
+ **The determinate progress track.** A pill-shaped 310px track with 8px of inner
16
+ padding and a 6px amber fill whose width is set inline from `progress` (a
17
+ percentage, clamped 0–100). That's the whole component — no label, no percentage
18
+ text, no tone, no indeterminate mode.
19
+
20
+ ## TL;DR for agents
21
+
22
+ - **Reach for it when:** you have a known-percentage operation to show — an upload,
23
+ a batch render, credits consumed.
24
+ - **Don't reach for it when:** the value is user-adjustable (→ `ScSlider`), you
25
+ don't know the percentage (there is no indeterminate mode — use a spinner, e.g.
26
+ `ScButton loading`), or you want the credits meter with its labels and CTA
27
+ (→ `ScCreditsUsageCard`, which already renders one).
28
+ - **Four things that will bite you:**
29
+ 1. `progress` defaults to **`50`** ⚠️ — forget it and the bar looks half-done.
30
+ 2. Fixed `width: 19.375rem` (310px). It will **not** fill its parent until you
31
+ override the width.
32
+ 3. The fill is **always amber** (`--alias-fill-warning-solid`). There is no
33
+ `tone` / `variant` — no green "complete", no red "failed".
34
+ 4. No `role="progressbar"` and no `aria-value*`. Add them yourself via the spread.
35
+
36
+ ---
37
+
38
+ ## 1. How to use it
39
+
40
+ ### Import
41
+
42
+ ```tsx
43
+ import { ScProgressBar } from "@streamoid/ui";
44
+ import "@streamoid/ui/dist/index.css"; // once, at your app root
45
+ ```
46
+
47
+ ### Minimal usage
48
+
49
+ ```tsx
50
+ <ScProgressBar progress={72} />
51
+ ```
52
+
53
+ Filling its container (what you almost always want):
54
+
55
+ ```tsx
56
+ <ScProgressBar progress={72} style={{ width: "100%" }} />
57
+ ```
58
+
59
+ ### Props
60
+
61
+ | Prop | Type | Default | Notes |
62
+ |---|---|---|---|
63
+ | `progress` | `number` | `50` | ⚠️ Real default. A **percentage 0–100**, clamped internally with `Math.min(100, Math.max(0, progress))`. `NaN` produces `width: NaN%` (nothing painted). |
64
+ | `className` | `string` | – | Concatenated unconditionally — see Gotcha 8. |
65
+ | `...props` | `HTMLAttributes<HTMLDivElement>` | – | Spread onto the **track** div: `style`, `role`, `aria-*`, `title`, `data-*`, `onClick`. |
66
+
67
+ ### What it actually renders
68
+
69
+ ```html
70
+ <div class="scProgressBar {className}" {...props}>
71
+ <!-- background --alias-fill-neutral-neutral · radius full · padding 8px
72
+ width 19.375rem (310px) · overflow:hidden -->
73
+ <div class="progressFill" style="width: {clamped}%">
74
+ <!-- background --alias-fill-warning-solid · radius full · height 0.375rem (6px) -->
75
+ </div>
76
+ </div>
77
+ ```
78
+
79
+ Total height is 22px (6px fill + 8px padding top and bottom).
80
+
81
+ ### Recipes
82
+
83
+ ```tsx
84
+ // Percentage text beside the bar, bar flexes (the Photogenix idiom)
85
+ <div className="flex items-center gap-2 w-[160px]">
86
+ <div className="flex-1 min-w-0">
87
+ <ScProgressBar progress={pct} style={{ width: "100%" }} />
88
+ </div>
89
+ <span className="w-10 text-right">{Math.round(pct)}%</span>
90
+ </div>
91
+
92
+ // Derive the percentage — pass 0–100, never a fraction
93
+ <ScProgressBar progress={total > 0 ? Math.round((done / total) * 100) : 0} />
94
+
95
+ // Accessible progress (the component gives you none of this)
96
+ <ScProgressBar
97
+ progress={pct}
98
+ role="progressbar"
99
+ aria-valuenow={Math.round(pct)}
100
+ aria-valuemin={0}
101
+ aria-valuemax={100}
102
+ aria-label="Importing images"
103
+ style={{ width: "100%" }}
104
+ />
105
+
106
+ // Recolour on completion — there is no `tone` prop, so do it in CSS
107
+ <ScProgressBar progress={pct} className={pct >= 100 ? "done" : undefined} style={{ width: "100%" }} />
108
+ // .done > div { background: var(--alias-fill-success-solid) !important; }
109
+ ```
110
+
111
+ ---
112
+
113
+ ## 2. Where to use it
114
+
115
+ - **Photogenix batch + upload flows** — `studio/UploadZipModal` (import progress),
116
+ `studio/batchDetail/BatchExportDrawer` (export), `SPhComps/SPHCXBatchList`
117
+ (per-row render progress in the Shopify app). Photogenix is the only host consumer.
118
+ - **`ScCreditsUsageCard`** composes one internally for the credits meter — that card
119
+ is the right entry point for anything credits-shaped, and it neutralises the fixed
120
+ width with `flex: 1 !important; width: unset !important`.
121
+ - **Any long-running determinate job row** — feed imports, exports, bulk edits.
122
+
123
+ ---
124
+
125
+ ## 3. When to use it
126
+
127
+ ### Use it when
128
+
129
+ - You know the **percentage complete** and it will change over time.
130
+ - The operation is long enough that the user needs reassurance (uploads, renders,
131
+ imports).
132
+ - You want the track/fill radius, height and neutral track colour to match the
133
+ credits meter.
134
+
135
+ ### Don't use it — reach for this instead
136
+
137
+ | Situation | Use instead |
138
+ |---|---|
139
+ | The value is a control the user drags | `ScSlider` |
140
+ | A bounded numeric input with −/+ | `ScCounter` |
141
+ | You don't know the percentage (spinner / indeterminate) | `ScButton loading`, or your app's spinner — this bar has no indeterminate mode |
142
+ | Credits remaining with labels + a "Buy credits" CTA | `ScCreditsUsageCard` / `ScCreditsUsageCardMobile` |
143
+ | A pass/fail or state marker | `ScBadges` / `ScBeacon` |
144
+ | A multi-step wizard indicator | `ScGuide`'s `stepLabel`, or `ScTabs` |
145
+ | A count that isn't out of a total | `ScBadges` |
146
+
147
+ ### Don't confuse with
148
+
149
+ | You may actually want | Not this |
150
+ |---|---|
151
+ | `ScSlider` — interactive range input | `ScProgressBar` is read-only output |
152
+ | `ScCreditsUsageCard` — the whole credits meter (label + bar + remaining text + CTA) | `ScProgressBar` is just the bar |
153
+ | A styled `<progress>` element | This is two divs with an inline width; no native semantics |
154
+
155
+ ---
156
+
157
+ ## 4. Why to use it
158
+
159
+ - **Clamping is already done.** `Math.min(100, Math.max(0, progress))` means a
160
+ server sending `104` or `-3` cannot overflow the track or produce a negative
161
+ width — the classic hand-rolled-bar bug.
162
+ - **Tokenised track and fill.** `--alias-fill-neutral-neutral` behind
163
+ `--alias-fill-warning-solid` stay distinguishable in both themes; a hardcoded
164
+ `#eee` track vanishes in dark mode and a hardcoded dark track vanishes in light.
165
+ - **`overflow: hidden` + `border-radius: full` on the track** means the fill's own
166
+ rounded ends are clipped correctly at both extremes, instead of the fill's corners
167
+ poking outside the track.
168
+ - **It matches the credits meter for free** — the same component is inside
169
+ `ScCreditsUsageCard`, so an upload bar and a credits bar look like siblings.
170
+ - **One line of change surface.** Height, radius and colour live in one CSS module,
171
+ not scattered across every progress UI in three apps.
172
+
173
+ ---
174
+
175
+ ## Gotchas
176
+
177
+ **1. `progress` defaults to 50.** A bar rendered with no props reads "half done".
178
+
179
+ **2. It's a percentage, not a fraction.** `progress={0.75}` renders a 0.75% sliver.
180
+
181
+ ```tsx
182
+ // WRONG
183
+ <ScProgressBar progress={done / total} />
184
+
185
+ // RIGHT
186
+ <ScProgressBar progress={total > 0 ? (done / total) * 100 : 0} />
187
+ ```
188
+
189
+ **3. Fixed `width: 19.375rem` — it does not fill its parent.** This is the most
190
+ common complaint. Two fixes, both in use:
191
+
192
+ ```tsx
193
+ // via the spread
194
+ <ScProgressBar progress={pct} style={{ width: "100%" }} />
195
+ ```
196
+
197
+ ```css
198
+ /* via className, when the bar must flex inside a row (what ScCreditsUsageCard does) */
199
+ .barInstance { flex: 1 !important; width: unset !important; }
200
+ ```
201
+
202
+ The `!important` is needed because the module's `width` is on the same element.
203
+
204
+ **4. `progress={100}` never looks completely full.** The inline width is a
205
+ percentage of the **padded content box**, and the track carries `padding: 0.5rem`,
206
+ so 8px of track always remains visible at each end. Don't use "the bar is flush" as
207
+ your completion cue — show a label or swap in a `ScBadges` / `ScBeacon`.
208
+
209
+ **5. The fill is amber for every value.** `--alias-fill-warning-solid`, hardcoded.
210
+ There is no `tone`, `variant` or `color` prop, so a green "complete" or a red
211
+ "failed" bar requires your own CSS override on the child div.
212
+
213
+ **6. No a11y whatsoever.** Plain divs — no `role="progressbar"`, no `aria-valuenow`,
214
+ no accessible name. Screen readers announce nothing. Pass the aria via the spread
215
+ (see Recipes); it lands on the track, which is the right element.
216
+
217
+ **7. No transition on `width`.** Values jump. If you poll progress at long
218
+ intervals, add `transition: width .3s ease` to the fill via a `className`
219
+ descendant selector.
220
+
221
+ **8. `className` is concatenated unconditionally.** Omit it and the class attribute
222
+ contains the literal string `undefined`. Cosmetic; breaks exact-match snapshots.
223
+
224
+ **9. Fixed 22px total height, no `size` prop.** A thin 2px inline bar is not
225
+ available; override `padding` and the child's `height` if you need one.
226
+
227
+ **10. `NaN` renders nothing, silently.** `total = 0` in a naive
228
+ `done / total * 100` gives `NaN`, `Math.min/max` propagate it, and the fill gets
229
+ `width: NaN%`. Guard the divisor.
230
+
231
+ ---
232
+
233
+ ## In the wild
234
+
235
+ ```tsx
236
+ // photogenix_v2 shopify/client/src/components/SPhComps/SPHCXBatchList.tsx:181
237
+ <div className="shrink-0 w-[160px] flex items-center gap-2">
238
+ <div className="flex-1 min-w-0">
239
+ <ScProgressBar
240
+ progress={progressPercent}
241
+ style={{ width: '100%' }}
242
+ />
243
+ </div>
244
+ <span className="w-10 shrink-0 text-right text-[14px] leading-[20px]">
245
+ {Math.round(progressPercent)}%
246
+ </span>
247
+ </div>
248
+ ```
249
+
250
+ ```tsx
251
+ // photogenix_v2 dashboard/client/src/components/studio/UploadZipModal.tsx:513
252
+ <ScProgressBar
253
+ progress={importProgress.total > 0
254
+ ? Math.round((importProgress.completed / importProgress.total) * 100)
255
+ : 0}
256
+ />
257
+ ```
258
+
259
+ ---
260
+
261
+ ## Related
262
+
263
+ - `ScCreditsUsageCard` / `ScCreditsUsageCardMobile` — the credits meter; embeds this bar and shows how to unpin its width.
264
+ - `ScSlider` — the interactive range sibling.
265
+ - `ScCounter` — bounded numeric stepper.
266
+ - `ScBadges` / `ScBeacon` — for the terminal state once progress hits 100.
267
+ - `ScButton` (`loading`) — the indeterminate case this component doesn't cover.