@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,235 @@
1
+ ---
2
+ component: ScCounter
3
+ package: "@streamoid/ui"
4
+ category: forms
5
+ status: stable
6
+ renders: div
7
+ tags: [counter, stepper, number, quantity, increment, decrement, plus-minus, clamp, min, max]
8
+ related: [ScTextField, ScOnlyField, ScSlider, ScFieldButton]
9
+ do_not_confuse_with: [ScSlider, ScTextField, ScBadges, ScProgressBar]
10
+ used_by: [catalogix]
11
+ required_props: [count, onChange]
12
+ ---
13
+
14
+ # ScCounter
15
+
16
+ **A −/value/+ numeric stepper with a click-to-edit inline input.** The value is a
17
+ click target: tap it and it becomes a `<input type="number">` that closes on blur.
18
+ Optional `min`/`max` clamp both the buttons and typed input.
19
+
20
+ ## TL;DR for agents
21
+
22
+ - **Reach for it when:** the user nudges a small bounded integer — page size, image
23
+ count, retry limit, a PLP range bound.
24
+ - **Don't reach for it when:** the number is free-form or large (→ `ScTextField`),
25
+ it's a continuous range (→ native `<input type="range">`; `ScSlider` does not
26
+ work), or it's a read-only count (→ `ScBadges`).
27
+ - **Four things that will bite you:**
28
+ 1. `min`/`max` are **opt-in**. Omit them and −/+ walk into negatives / infinity.
29
+ 2. Typing in the inline input can hand your `onChange` **`0`** (empty field) or
30
+ **`NaN`** — guard it.
31
+ 3. The −/+ hit areas are plain `<div onClick>`: no keyboard, no `aria-label`,
32
+ no `disabled`.
33
+ 4. The root is `width: 100%` — it fills its parent unless you constrain it.
34
+
35
+ ---
36
+
37
+ ## 1. How to use it
38
+
39
+ ### Import
40
+
41
+ ```tsx
42
+ import { ScCounter } from "@streamoid/ui";
43
+ import "@streamoid/ui/dist/index.css"; // once, at your app root
44
+ ```
45
+
46
+ ### Minimal usage
47
+
48
+ ```tsx
49
+ <ScCounter count={qty} onChange={setQty} />
50
+ ```
51
+
52
+ ### Props
53
+
54
+ | Prop | Type | Default | Notes |
55
+ |---|---|---|---|
56
+ | `count` | `number` | — | **Required.** Fully controlled; the component keeps no value state. |
57
+ | `onChange` | `(next: number) => void` | — | **Required.** Receives the already-clamped value from the buttons, and `Number(input.value)` from typing. |
58
+ | `label` | `ReactNode` | – | Trailing unit, rendered *inside* the value as `{count} {label}` (e.g. `5 images`). In edit mode it moves to the right of the input. |
59
+ | `min` | `number` | – | Opt-in lower clamp (`Math.max`). Omit for unbounded. |
60
+ | `max` | `number` | – | Opt-in upper clamp (`Math.min`). Omit for unbounded. |
61
+ | `className` | `string` | – | Properly joined (`filter(Boolean).join(" ")`) — no stray `undefined` here, unlike most DS components. |
62
+ | `style` | `React.CSSProperties` | – | Applied to the root. The **only** styling escape hatch. |
63
+
64
+ ⚠️ `IScCounterProps` does **not** extend `HTMLAttributes`. There is no `id`,
65
+ `onClick`, `data-*`, `aria-*` or `ref` passthrough — `className` and `style` only.
66
+
67
+ ### What renders in each mode
68
+
69
+ | Mode | Middle region | Entered by | Left by |
70
+ |---|---|---|---|
71
+ | Display (default) | `<div>{count} {label}</div>`, `cursor: pointer` | initial render | clicking it |
72
+ | Edit | `<input type="number" autoFocus>` + `<div>{label}</div>` | clicking the value | `onBlur` |
73
+
74
+ ### Recipes
75
+
76
+ ```tsx
77
+ // Bounded quantity with a unit
78
+ <ScCounter count={imageCount} onChange={setImageCount} min={1} max={20} label="images" />
79
+
80
+ // Guard the typed path against "" → 0 and non-numeric → NaN
81
+ <ScCounter
82
+ count={pageSize}
83
+ min={10}
84
+ max={200}
85
+ onChange={(next) => {
86
+ if (Number.isNaN(next)) return;
87
+ setPageSize(next);
88
+ }}
89
+ />
90
+
91
+ // Constrain the width — the root is width:100%
92
+ <div style={{ width: 180 }}>
93
+ <ScCounter count={qty} onChange={setQty} min={0} />
94
+ </div>
95
+
96
+ // Legacy wrapper shape (Catalogix): customStyle → style
97
+ <ScCounter count={props.count} onChange={props.onChange} label={props.label} style={props.customStyle} />
98
+ ```
99
+
100
+ ---
101
+
102
+ ## 2. Where to use it
103
+
104
+ - **Catalogix**, via `app/components/CounterInput` — the PLP range/count inputs and
105
+ anywhere a small integer is nudged. That shim exists only to keep the legacy
106
+ `customStyle` prop name.
107
+ - **Inside modals and drawers** where a bounded quantity sits beside other fields.
108
+ Because it has no caption of its own, pair it with your own label or a
109
+ `ScTextField`-style caption if it must match a field stack.
110
+
111
+ No other DS component composes it.
112
+
113
+ ---
114
+
115
+ ## 3. When to use it
116
+
117
+ ### Use it when
118
+
119
+ - The value is a **small integer** the user adjusts by ±1 more often than they type.
120
+ - Bounds matter and you want them enforced in **one** place for both buttons and
121
+ keyboard entry (`min`/`max` clamp both).
122
+ - You want the compact pill look (hardcoded `16px` radius / `12px 16px` padding,
123
+ `--alias-fill-neutral-neutralplus` fill) rather than a full text field.
124
+
125
+ ### Don't use it — reach for this instead
126
+
127
+ | Situation | Use instead |
128
+ |---|---|
129
+ | Free-form or large numbers, or numbers needing validation messages | `ScTextField` (with a caption + helper text) |
130
+ | A number with no caption but full field chrome | `ScOnlyField` |
131
+ | Continuous value (opacity, strength) | native `<input type="range">` — **not** `ScSlider`, which is non-functional |
132
+ | Read-only count/badge ("12 items") | `ScBadges` |
133
+ | Progress toward a total | `ScProgressBar` |
134
+ | An action button beside a field | `ScFieldButton` |
135
+
136
+ ### Don't confuse with
137
+
138
+ | You may actually want | Not this |
139
+ |---|---|
140
+ | `ScSlider` — looks like a range control but is a static 5-segment graphic with a dead callback | `ScCounter` is the only DS control with working numeric clamping |
141
+ | `ScTextField` — captioned text input, `value`/`onChange(event)` | `ScCounter`'s `onChange` receives a **number**, not an event |
142
+ | `ScBadges` — non-interactive count chip | This one mutates |
143
+
144
+ ---
145
+
146
+ ## 4. Why to use it
147
+
148
+ - **Clamping in one place.** `min`/`max` are applied inside `set()`, so the buttons
149
+ *and* the typed input obey the same bounds. Hand-rolled steppers routinely clamp
150
+ the buttons and forget the keyboard path.
151
+ - **Click-to-edit without the state machine.** The display↔input swap, `autoFocus`
152
+ and blur-to-close are already wired; you only own the number.
153
+ - **The pill themes.** Fill `--alias-fill-neutral-neutralplus`, border
154
+ `--alias-surface-infohover`, and the inline input re-declares the same fill plus
155
+ `color: inherit`, so the field doesn't flash white in dark mode — the classic
156
+ hand-rolled bug. (Note: unlike most DS components its radius and padding are
157
+ hardcoded `16px` / `12px 16px`, not tokens.)
158
+ - **Correct icons.** `SiconMinus`/`SiconPlus` at 24px with
159
+ `--alias-text-and-icons-primary`, matching every other icon pair in the product.
160
+
161
+ ---
162
+
163
+ ## Gotchas
164
+
165
+ **1. `min`/`max` are optional and default to unbounded.** The comment in the source
166
+ calls this "legacy behavior". Without them, − takes you to `-1`, `-2`, …
167
+
168
+ ```tsx
169
+ // WRONG — reaches negative quantities
170
+ <ScCounter count={qty} onChange={setQty} />
171
+
172
+ // RIGHT
173
+ <ScCounter count={qty} onChange={setQty} min={0} max={99} />
174
+ ```
175
+
176
+ **2. Typed input can produce `0` or `NaN`.** The handler is
177
+ `onChange(Number(e.target.value))`. Clearing the field gives `Number("") === 0`
178
+ (then clamped to `min` if set); a `type="number"` field can also hold intermediate
179
+ junk like `"-"` or `"1e"`, yielding `NaN` straight into your state.
180
+
181
+ ```tsx
182
+ // RIGHT — reject NaN at the boundary
183
+ onChange={(next) => Number.isNaN(next) || setQty(next)}
184
+ ```
185
+
186
+ **3. The steppers are not buttons.** `<div className={styles.step} onClick=…>` with
187
+ decorative icons: no `tabIndex`, no `role="button"`, no keyboard, no `aria-label`,
188
+ and **no way to disable them at the bounds** — at `max` the + still looks live, it
189
+ just clamps. There is no `disabled` prop for the whole control either.
190
+
191
+ **4. The inline input is `width: 30%`** of its flex container, so 4+ digit values get
192
+ visually cropped while editing. Constrain the root narrower, or don't use this for
193
+ large numbers.
194
+
195
+ **5. `width: 100%` on the root.** It stretches to its parent and distributes children
196
+ with `justify-content: space-around`, so in a wide parent the − and + drift far
197
+ apart. Wrap it in a fixed-width box.
198
+
199
+ **6. `label` is inside the value, not a caption.** It renders as `{count} {label}` on
200
+ the same click target, and there is no field caption. If you need "Quantity" above
201
+ the control, render that yourself.
202
+
203
+ **7. Icon colours have no fallback.** `color="var(--alias-text-and-icons-primary)"`
204
+ is passed without a literal fallback, so without `@streamoid/ui/dist/index.css` (or
205
+ `@streamoid/tokens`) loaded the icons render with no colour.
206
+
207
+ **8. No `HTMLAttributes` passthrough.** `id`, `data-testid`, `aria-*`, `onClick` and
208
+ `ref` are all unavailable. Wrap it in a div if a test or a tooltip needs a hook.
209
+
210
+ **9. Blur closes the editor unconditionally** — including when the field is empty.
211
+ Combined with Gotcha 2 that means "clear and click away" silently commits `0`
212
+ (or `min`).
213
+
214
+ ---
215
+
216
+ ## In the wild
217
+
218
+ ```jsx
219
+ // catalogix/dashboard app/components/CounterInput/index.jsx:9
220
+ <ScCounter
221
+ count={props.count}
222
+ onChange={props.onChange}
223
+ label={props.label}
224
+ style={props.customStyle}
225
+ />
226
+ ```
227
+
228
+ ---
229
+
230
+ ## Related
231
+
232
+ - `ScTextField` / `ScOnlyField` — captioned/plain inputs for free-form values.
233
+ - `ScSlider` — looks like the continuous sibling; it is non-functional, don't reach for it.
234
+ - `ScProgressBar` — display-only progress.
235
+ - `ScFieldButton` — field-height action button if the counter needs an "Apply" beside it.
@@ -0,0 +1,247 @@
1
+ ---
2
+ component: ScCreditsUsageCard
3
+ package: "@streamoid/ui"
4
+ category: billing
5
+ status: stable
6
+ renders: div
7
+ tags: [credits, usage, meter, progress, quota, buy-credits, billing, remaining]
8
+ related: [ScCreditsUsageCardMobile, ScPlanDetailsCard, ScProgressBar, CreditWarningBanner, ScPairtext]
9
+ do_not_confuse_with: [ScCreditsUsageCardMobile, ScPlanDetailsCard, ScProgressBar, CreditWarningBanner]
10
+ used_by: [cxo]
11
+ ---
12
+
13
+ # ScCreditsUsageCard
14
+
15
+ **The credits meter on the Billing overview.** A `Credits used` header with a
16
+ credits icon and the `8,450 / 30,000` count on the right, a divider, then a
17
+ progress bar, a remaining-credits caption and a fixed-width "Buy credits" button
18
+ that can swap its own label on hover (used as a cheap "Pro plan required" tooltip).
19
+
20
+ ## TL;DR for agents
21
+
22
+ - **Reach for it when:** you need the credits-consumed meter, normally to the right
23
+ of `ScPlanDetailsCard`.
24
+ - **Don't reach for it when:** you want the plan summary (→ `ScPlanDetailsCard`),
25
+ a bare bar (→ `ScProgressBar`), the low-credits sidebar warning
26
+ (→ `CreditWarningBanner`), or the mobile billing screen
27
+ (→ `ScCreditsUsageCardMobile`).
28
+ - **Four things that will bite you:**
29
+ 1. `progress`, `usageText` and `remainingText` are **three unrelated props**.
30
+ Nothing derives one from another; they will disagree if you let them.
31
+ 2. In live usage `progress` is the **percent used** (the bar fills up) while
32
+ `remainingText` states the percent **remaining** — the component's own
33
+ defaults (`progress={60}` + `"60% Remaining"`) contradict that convention.
34
+ 3. The button is pinned to **7.5rem wide, 2.5rem tall, `overflow: hidden`**, so a
35
+ long `buyButtonHoverText` clips.
36
+ 4. `buyButtonHoverText` still swaps while `buyButtonState="disabled"` — that is
37
+ the whole point, but it means "disabled" is not inert.
38
+
39
+ ---
40
+
41
+ ## 1. How to use it
42
+
43
+ ### Import
44
+
45
+ ```tsx
46
+ import { ScCreditsUsageCard } from "@streamoid/ui";
47
+ import "@streamoid/ui/dist/index.css"; // once, at your app root
48
+ ```
49
+
50
+ ### Minimal usage
51
+
52
+ ```tsx
53
+ <ScCreditsUsageCard
54
+ usageText="8,450 / 30,000"
55
+ remainingText="21,550 (72%) Remaining"
56
+ progress={28}
57
+ onBuyClick={() => setShowBuyCreditsModal(true)}
58
+ />
59
+ ```
60
+
61
+ ### Props
62
+
63
+ | Prop | Type | Default | Notes |
64
+ |---|---|---|---|
65
+ | `usageLabel` | `string` | `"Credits usage"` | ⚠️ Real default. Header text beside the credits icon. Live CXO passes `"Credits used"`. |
66
+ | `usageText` | `string` | `"8,450 / 30,000"` | ⚠️ Real default. Right-aligned `used / total`. Pre-formatted by you. |
67
+ | `remainingText` | `string` | `"60% Remaining"` | ⚠️ Real default. Caption right of the bar. `white-space: nowrap`. |
68
+ | `buyButtonText` | `string` | `"Buy credits"` | ⚠️ Real default. CTA label. |
69
+ | `buyButtonState` | `"default"` \| `"disabled"` | `"default"` | Forwarded to `ScButton.state`; `disabled` → `pointer-events: none`, `aria-disabled`, `tabIndex={-1}`. |
70
+ | `buyButtonHoverText` | `string` | – | Replaces the label while the pointer is over the button wrapper. Works even when disabled. |
71
+ | `onBuyClick` | `() => void` | – | CTA click. Blocked by `buyButtonState="disabled"`. |
72
+ | `progress` | `number` | `60` | ⚠️ Real default. Bar fill percentage. Clamped to 0–100 by `ScProgressBar`. |
73
+ | `className` | `string` | – | Appended to the root class. |
74
+ | `...props` | `HTMLAttributes<HTMLDivElement>` | – | Spread onto the root div. |
75
+
76
+ ### Recipes
77
+
78
+ ```tsx
79
+ // The live CXO idiom: bar = % used, caption = credits + % remaining,
80
+ // and the CTA is gated on the plan with a hover explanation.
81
+ <ScCreditsUsageCard
82
+ usageLabel="Credits used"
83
+ usageText={`${formatCredits(used)} / ${formatCredits(total)}`}
84
+ remainingText={`${formatCredits(available)} (${100 - pctUsed}%) Remaining`}
85
+ progress={pctUsed}
86
+ buyButtonText="Buy credits"
87
+ buyButtonState={isFreePlan ? "disabled" : "default"}
88
+ buyButtonHoverText={isFreePlan ? "Pro plan required" : undefined}
89
+ className="flex-1 min-w-0"
90
+ onBuyClick={() => setShowBuyCreditsModal(true)}
91
+ />
92
+
93
+ // Unknown quota (data still loading / no subscription): keep the three props
94
+ // consistent rather than letting the defaults leak through.
95
+ <ScCreditsUsageCard usageText="— / —" remainingText="—" progress={0} buyButtonState="disabled" />
96
+
97
+ // Paired with the plan card in one row
98
+ <div className="flex items-start w-full" style={{ gap: "var(--spacing-6xl)" }}>
99
+ <ScPlanDetailsCard className="flex-1 min-w-0" {...plan} />
100
+ <ScCreditsUsageCard className="flex-1 min-w-0" {...credits} />
101
+ </div>
102
+ ```
103
+
104
+ ---
105
+
106
+ ## 2. Where to use it
107
+
108
+ - **Top-right card of the Billing overview**, beside `ScPlanDetailsCard`. One per
109
+ page.
110
+ - Rendered from the shared **`@streamoid/settings`** billing page
111
+ (`packages/settings/src/billing-content.tsx`), which **CXO** mounts through
112
+ `src/app/components/settings-content.tsx`.
113
+ - This card does not adapt: `.progressContainer` is a fixed 2.875rem tall, the
114
+ button is a fixed 7.5rem wide and `.remainingText` is `nowrap`. Below `md` you are
115
+ expected to switch to `ScCreditsUsageCardMobile`. The shared settings billing page
116
+ has **no mobile branch** today — the only wiring of the mobile twin is CXO's
117
+ `src/app/components/mobile-billing-content.tsx:171`, reached from CXO's orphaned
118
+ local `billing-content.tsx`.
119
+
120
+ ---
121
+
122
+ ## 3. When to use it
123
+
124
+ ### Use it when
125
+
126
+ - You are showing **consumption against a quota** with a single "top up" action.
127
+ - The numbers are already formatted (thousands separators, locale) — this card does
128
+ no maths beyond clamping `progress`.
129
+
130
+ ### Don't use it — reach for this instead
131
+
132
+ | Situation | Use instead |
133
+ |---|---|
134
+ | Current plan name / price / status badge | `ScPlanDetailsCard` |
135
+ | Just a bar, no card chrome | `ScProgressBar` |
136
+ | A "you're nearly out of credits" alert in the sidebar | `CreditWarningBanner` |
137
+ | Mobile billing screen | `ScCreditsUsageCardMobile` |
138
+ | A real tooltip (rich content, keyboard accessible, positioned) | Your own tooltip — `buyButtonHoverText` is only a label swap |
139
+ | A second action next to "Buy credits" | Not supported; there is exactly one CTA |
140
+
141
+ ### Don't confuse with
142
+
143
+ | You may actually want | Not this |
144
+ |---|---|
145
+ | `ScCreditsUsageCardMobile` — narrower prop list (**no `buyButtonState`, no `buyButtonHoverText`**) | Do not assume prop parity with the desktop card |
146
+ | `CreditWarningBanner` — sidebar warning strip with its own "Buy credits" CTA | This is the billing-page meter, not the alert |
147
+ | `ScPlanDetailsCard` — visually near-identical shell (1.5rem padding, header row, divider, right-hand CTA) | Easy to confuse in a diff; different props entirely |
148
+
149
+ ---
150
+
151
+ ## 4. Why to use it
152
+
153
+ - **The bar is clamped and tokenised.** `ScProgressBar` pins the fill to 0–100 and
154
+ colours it from the alias tokens, so a bad `progress` value can't paint outside
155
+ the track or pick a hardcoded green.
156
+ - **Hover-explained disabled CTA in one prop.** Free-plan users need to be told
157
+ *why* "Buy credits" is dead; `buyButtonHoverText` does that without a tooltip
158
+ library or a second element.
159
+ - **Shares its shell with `ScPlanDetailsCard`** (same padding, radius, divider,
160
+ header `ScPairtext`), so the two cards read as a matched pair in the billing row.
161
+
162
+ ---
163
+
164
+ ## Gotchas
165
+
166
+ **1. The three numbers can lie to each other.** `progress` does not derive from
167
+ `usageText`, and `remainingText` is free text. Compute all three from one source.
168
+
169
+ ```tsx
170
+ // WRONG — bar says 60% full while the caption says 60% left
171
+ <ScCreditsUsageCard usageText="12,000 / 30,000" remainingText="60% Remaining" progress={60} />
172
+
173
+ // RIGHT — one source of truth
174
+ const pctUsed = Math.round((used / total) * 100);
175
+ <ScCreditsUsageCard
176
+ usageText={`${used} / ${total}`}
177
+ remainingText={`${100 - pctUsed}% Remaining`}
178
+ progress={pctUsed}
179
+ />
180
+ ```
181
+
182
+ **2. `progress` means "used" in production.** The live call site passes
183
+ `progressPct = % used` while the caption shows `remainingPct`. The DS defaults
184
+ (`60` + `"60% Remaining"`) imply the opposite — don't copy the defaults as a spec.
185
+
186
+ **3. Long hover labels clip.** `.scButtonInstance` forces `width: 7.5rem` and
187
+ `ScButton` is `height: 2.5rem; overflow: hidden` with no `white-space: nowrap`, so
188
+ a long label wraps inside a 1.5rem-tall content box and gets cut. Keep
189
+ `buyButtonHoverText` about as short as `"Buy credits"` (`"Pro plan required"` is
190
+ already at the edge).
191
+
192
+ **4. Disabled is not inert.** `buyButtonState="disabled"` stops the click, but the
193
+ hover wrapper still fires `mouseenter`/`mouseleave`, so the label still swaps
194
+ (intended) — don't rely on `disabled` to freeze the card visually.
195
+
196
+ **5. `remainingText` is `nowrap` and squeezes the bar.** The bar is `flex: 1`; a
197
+ long caption steals its width instead of wrapping. Keep it terse.
198
+
199
+ **6. You cannot change the header or button icon.** `SiconCredits` is hardcoded in
200
+ the header `ScPairtext`, and the `SiconHome` handed to `ScButton` never renders
201
+ (no `styleVariant`), so the CTA is label-only.
202
+
203
+ **7. `.buttonTooltip` in the CSS module is dead.** The stylesheet still carries a
204
+ real absolutely-positioned tooltip (`opacity` transition, `z-index: 10`) that the
205
+ TSX never renders — the component implements the label swap instead. Don't try to
206
+ activate it from host CSS; nothing applies that class.
207
+
208
+ **8. Missing `className` leaks `"undefined"` into the class list** (root is
209
+ `styles.scCreditsUsageCard + " " + className`). Cosmetic, but visible in snapshots.
210
+
211
+ **9. Trailing spaces in the DOM.** `usageText` and `remainingText` are rendered as
212
+ `{value}` followed by a literal space; exact-match text assertions should trim.
213
+
214
+ ---
215
+
216
+ ## In the wild
217
+
218
+ ```tsx
219
+ // npm-components packages/settings/src/billing-content.tsx:2052
220
+ <ScCreditsUsageCard
221
+ usageLabel="Credits used"
222
+ usageText={usageText}
223
+ remainingText={
224
+ availableCredits != null
225
+ ? `${formatCredits(availableCredits)} (${remainingPct}%) Remaining`
226
+ : `${remainingPct}% Remaining`
227
+ }
228
+ buyButtonText="Buy credits"
229
+ buyButtonState={isFreePlan ? "disabled" : "default"}
230
+ buyButtonHoverText={isFreePlan ? "Pro plan required" : undefined}
231
+ progress={progressPct}
232
+ className="flex-1 min-w-0"
233
+ onBuyClick={() => setShowBuyCreditsModal(true)}
234
+ />
235
+ ```
236
+
237
+ ---
238
+
239
+ ## Related
240
+
241
+ - `ScCreditsUsageCardMobile` — the mobile twin (fewer props: no button state, no
242
+ hover text).
243
+ - `ScPlanDetailsCard` — the card it sits beside.
244
+ - `ScProgressBar` — the bar it renders; use directly if you don't need the card.
245
+ - `CreditWarningBanner` — the sidebar low-credits alert. ⚠️ Note the export has
246
+ **no `Sc` prefix** (`import { CreditWarningBanner } from "@streamoid/ui"`);
247
+ `ScCreditWarningBanner` does not exist.