@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,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.
|