@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.
- package/README.md +35 -18
- package/dist/docs/AGENTS.md +325 -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 +210 -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/ScWorkspaceAccountMenu.md +115 -0
- package/dist/docs/ScWorkspaceCard.md +234 -0
- package/dist/docs/ScWorkspaceSettingsMobile.md +265 -0
- package/dist/docs/ScWorkspaceSwitchCard.md +312 -0
- package/dist/docs/ScWorkspaceSwitchMobile.md +241 -0
- package/dist/docs/ScWorkspaceSwitchMobileV2.md +278 -0
- package/dist/docs/StreamoidSidebar.md +413 -0
- package/dist/docs/StreamoidWorkspaceSwitcher.md +307 -0
- package/dist/docs/UsageHistoryMobile.md +235 -0
- package/dist/docs/components.json +4931 -0
- package/dist/index.css +361 -36
- package/dist/index.d.mts +213 -88
- package/dist/index.d.ts +213 -88
- package/dist/index.js +2486 -1629
- package/dist/index.mjs +2487 -1620
- package/package.json +5 -3
|
@@ -0,0 +1,233 @@
|
|
|
1
|
+
---
|
|
2
|
+
component: ScCheckField
|
|
3
|
+
package: "@streamoid/ui"
|
|
4
|
+
category: forms
|
|
5
|
+
status: stable
|
|
6
|
+
renders: div
|
|
7
|
+
tags: [checkbox, field, form, multi-select, permissions, label, checkbox-group, pairtext]
|
|
8
|
+
related: [ScCheckbox, ScPairtext, ScTabField, ScTextField, ScAppField]
|
|
9
|
+
do_not_confuse_with: [ScCheckbox, ScPairtext, ScTabField, ScAppField]
|
|
10
|
+
used_by: [cxo]
|
|
11
|
+
---
|
|
12
|
+
|
|
13
|
+
# ScCheckField
|
|
14
|
+
|
|
15
|
+
**A labelled multi-select form field: caption on top, bordered box underneath with
|
|
16
|
+
your checkbox options in a single horizontal row.** Each option is a `ScPairtext`
|
|
17
|
+
(`type="checkbox"`), so the whole label+box pair is one click target. Designed to
|
|
18
|
+
stack with `ScTextField` / `ScTabField` inside a form.
|
|
19
|
+
|
|
20
|
+
## TL;DR for agents
|
|
21
|
+
|
|
22
|
+
- **Reach for it when:** a form needs 2 (maybe 3) boolean options under one caption
|
|
23
|
+
— "Permission: Can Use Credits / Can Invite User".
|
|
24
|
+
- **Don't reach for it when:** you need a bare glyph (→ `ScCheckbox`), one labelled
|
|
25
|
+
row on its own (→ `ScPairtext`), single-choice (→ `ScTabField`), or app-permission
|
|
26
|
+
toggles (→ `ScAppField`).
|
|
27
|
+
- **Three things that will bite you:**
|
|
28
|
+
1. Omit `checkboxes` and you ship **two placeholder rows labelled "Pairtext"** —
|
|
29
|
+
the fallback is a Figma stub, not an empty state.
|
|
30
|
+
2. The container is `flex-direction: row` with **no wrapping**. It is a
|
|
31
|
+
side-by-side pair, not a vertical checklist. Four options squash to unreadable.
|
|
32
|
+
3. `label` defaults to `"Label"` ⚠️ — forget it and that ships.
|
|
33
|
+
|
|
34
|
+
---
|
|
35
|
+
|
|
36
|
+
## 1. How to use it
|
|
37
|
+
|
|
38
|
+
### Import
|
|
39
|
+
|
|
40
|
+
```tsx
|
|
41
|
+
import { ScCheckField } from "@streamoid/ui";
|
|
42
|
+
import "@streamoid/ui/dist/index.css"; // once, at your app root
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
### Minimal usage
|
|
46
|
+
|
|
47
|
+
```tsx
|
|
48
|
+
<ScCheckField
|
|
49
|
+
label="Permission"
|
|
50
|
+
checkboxes={[
|
|
51
|
+
{ label: "Can Use Credits", checked: canUseCredits, onChange: setCanUseCredits },
|
|
52
|
+
{ label: "Can Invite User", checked: canInviteUser, onChange: setCanInviteUser },
|
|
53
|
+
]}
|
|
54
|
+
/>
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
### Props
|
|
58
|
+
|
|
59
|
+
| Prop | Type | Default | Notes |
|
|
60
|
+
|---|---|---|---|
|
|
61
|
+
| `label` | `string` | `"Label"` | ⚠️ Real default. The muted caption above the box (`--alias-text-and-icons-tertiary`, text-sm). Fixed 1.25rem height — it does not wrap. |
|
|
62
|
+
| `checkboxes` | `{ label: string; checked: boolean; onChange: (checked: boolean) => void }[]` | – | One `ScPairtext` per item, in order, each `flex: 1`. Omit it and you get the placeholder — see Gotcha 1. |
|
|
63
|
+
| `className` | `string` | – | Concatenated onto the outer column wrapper (not the bordered box). |
|
|
64
|
+
| `...props` | `HTMLAttributes<HTMLDivElement>` | – | Spread onto the root: `style`, `onClick`, `id`, `data-*`, `aria-*`. |
|
|
65
|
+
|
|
66
|
+
There is **no** `disabled`, no `required`, no error/helper text, no `partial` state,
|
|
67
|
+
and no `name` — each item is a plain controlled boolean.
|
|
68
|
+
|
|
69
|
+
### Recipes
|
|
70
|
+
|
|
71
|
+
```tsx
|
|
72
|
+
// The CXO idiom: conditional permission field inside a mobile invite form
|
|
73
|
+
{role === "member" && (
|
|
74
|
+
<ScCheckField
|
|
75
|
+
label="Permission"
|
|
76
|
+
checkboxes={[
|
|
77
|
+
{ label: "Can Use Credits", checked: canUseCredits, onChange: setCanUseCredits },
|
|
78
|
+
{ label: "Can Invite User", checked: canInviteUser, onChange: setCanInviteUser },
|
|
79
|
+
]}
|
|
80
|
+
className="w-full shrink-0"
|
|
81
|
+
/>
|
|
82
|
+
)}
|
|
83
|
+
|
|
84
|
+
// Driving it from a permissions object
|
|
85
|
+
<ScCheckField
|
|
86
|
+
label="Permission"
|
|
87
|
+
checkboxes={PERMISSIONS.map((p) => ({
|
|
88
|
+
label: p.label,
|
|
89
|
+
checked: !!perms[p.key],
|
|
90
|
+
onChange: (next) => setPerms({ ...perms, [p.key]: next }),
|
|
91
|
+
}))}
|
|
92
|
+
/>
|
|
93
|
+
|
|
94
|
+
// More than ~3 options: don't. Stack ScPairtext rows in your own column instead.
|
|
95
|
+
<div style={{ display: "flex", flexDirection: "column", gap: 8 }}>
|
|
96
|
+
{options.map((o) => (
|
|
97
|
+
<ScPairtext
|
|
98
|
+
key={o.key}
|
|
99
|
+
type="checkbox"
|
|
100
|
+
content={o.label}
|
|
101
|
+
checked={!!perms[o.key]}
|
|
102
|
+
onChange={(next) => setPerms({ ...perms, [o.key]: next })}
|
|
103
|
+
/>
|
|
104
|
+
))}
|
|
105
|
+
</div>
|
|
106
|
+
```
|
|
107
|
+
|
|
108
|
+
---
|
|
109
|
+
|
|
110
|
+
## 2. Where to use it
|
|
111
|
+
|
|
112
|
+
- **CXO's mobile teams popups** — the "Permission" field in
|
|
113
|
+
`mobile-teams-invite-popup` and `mobile-teams-manage-popup`, sitting in a vertical
|
|
114
|
+
stack between `ScTabField` ("Role") and `ScAppField` ("App permission").
|
|
115
|
+
- Any **short boolean pair inside a form column**, where the caption + bordered box
|
|
116
|
+
must match the other DS fields' rhythm (`ScTextField`, `ScTabField`, `ScOnlyField`
|
|
117
|
+
all use the same caption style and `--radius-xl` container).
|
|
118
|
+
|
|
119
|
+
It composes `ScPairtext` → `ScCheckbox` internally. Nothing else in the DS renders it.
|
|
120
|
+
|
|
121
|
+
---
|
|
122
|
+
|
|
123
|
+
## 3. When to use it
|
|
124
|
+
|
|
125
|
+
### Use it when
|
|
126
|
+
|
|
127
|
+
- The options are **boolean, few, and belong to one caption**.
|
|
128
|
+
- The value is **staged** — the user confirms with a Save/Invite button afterwards.
|
|
129
|
+
- You want the field to look like every other field in the same form stack.
|
|
130
|
+
|
|
131
|
+
### Don't use it — reach for this instead
|
|
132
|
+
|
|
133
|
+
| Situation | Use instead |
|
|
134
|
+
|---|---|
|
|
135
|
+
| Just the tickable box, no caption or container | `ScCheckbox` |
|
|
136
|
+
| One labelled option row, laid out by you | `ScPairtext` (`type="checkbox"`) |
|
|
137
|
+
| More than ~3 options, or a vertical checklist | your own column of `ScPairtext` — this field does not wrap |
|
|
138
|
+
| Exactly one of two/three choices, same field look | `ScTabField` |
|
|
139
|
+
| Per-app enable toggles + store pickers (invite flows) | `ScAppField` — it owns that whole block |
|
|
140
|
+
| A setting that applies the moment it's flipped | `ScToggleSwitch` |
|
|
141
|
+
| Text input with a caption | `ScTextField` / `ScOnlyField` |
|
|
142
|
+
|
|
143
|
+
### Don't confuse with
|
|
144
|
+
|
|
145
|
+
| You may actually want | Not this |
|
|
146
|
+
|---|---|
|
|
147
|
+
| `ScCheckbox` — the 24px glyph only | `ScCheckField` is caption + container + N rows |
|
|
148
|
+
| `ScPairtext` — a single label+control row (checkbox / radio / icon) | This field *renders* those rows for you |
|
|
149
|
+
| `ScTabField` — same caption + container chrome but **single**-choice, rendered as a segmented switcher | Both are "…Field"; only this one is multi-select |
|
|
150
|
+
| `ScAppField` — the CXO app-permission block (per-app toggles, store lists) | Different domain, not a generic checkbox field |
|
|
151
|
+
|
|
152
|
+
---
|
|
153
|
+
|
|
154
|
+
## 4. Why to use it
|
|
155
|
+
|
|
156
|
+
- **Field rhythm for free.** The caption colour/size, the `--spacing-xs` gap, the
|
|
157
|
+
`--radius-xl` container and the hairline `--alias-border-divider` border are the
|
|
158
|
+
same tokens `ScTextField` and `ScTabField` use, so a stack of mixed fields lines
|
|
159
|
+
up without per-call-site CSS.
|
|
160
|
+
- **The rows are one click target.** `ScPairtext` puts the click on the whole row and
|
|
161
|
+
sets `cursor: pointer`, so the label text toggles the box — something you don't get
|
|
162
|
+
by hand, because `ScCheckbox` has no inner `<input>` for a `<label>` to activate.
|
|
163
|
+
- **Even columns.** Each row is `flex: 1 !important`, so two options are always the
|
|
164
|
+
same width regardless of label length instead of hugging their text.
|
|
165
|
+
- **Theme-correct in one place.** The container background is
|
|
166
|
+
`--alias-surface-base`, so it stays distinct from the page surface in both themes.
|
|
167
|
+
|
|
168
|
+
---
|
|
169
|
+
|
|
170
|
+
## Gotchas
|
|
171
|
+
|
|
172
|
+
**1. No `checkboxes` → two rows labelled "Pairtext".** The fallback branch renders
|
|
173
|
+
two `ScPairtext`s with only an `icon` prop set — and `type="checkbox"` means the icon
|
|
174
|
+
is never drawn, so you see the default `content` string instead.
|
|
175
|
+
|
|
176
|
+
```tsx
|
|
177
|
+
// WRONG — renders two unchecked rows reading "Pairtext"
|
|
178
|
+
<ScCheckField label="Permission" />
|
|
179
|
+
|
|
180
|
+
// RIGHT — always pass checkboxes
|
|
181
|
+
<ScCheckField label="Permission" checkboxes={perms} />
|
|
182
|
+
```
|
|
183
|
+
|
|
184
|
+
**2. It is a horizontal row, and it does not wrap.** The container is
|
|
185
|
+
`display: flex; flex-direction: row; gap: 1rem` with no `flex-wrap`, and every item
|
|
186
|
+
is `flex: 1`. Two options is the design; three is tight; four is unusable. Long
|
|
187
|
+
labels do not truncate with an ellipsis — they wrap inside their own cell and grow
|
|
188
|
+
the box.
|
|
189
|
+
|
|
190
|
+
**3. `label` defaults to `"Label"`.** ⚠️ Always set it.
|
|
191
|
+
|
|
192
|
+
**4. Everything is controlled and per-item.** Each entry supplies its own `checked`
|
|
193
|
+
and `onChange`; the field keeps no state and there is no group-level `onChange`.
|
|
194
|
+
Forget to update your state and the box never moves.
|
|
195
|
+
|
|
196
|
+
**5. No indeterminate, no disabled.** The item shape is `checked: boolean` only, so
|
|
197
|
+
`ScCheckbox`'s `partial` state is unreachable here, and there is no way to grey out
|
|
198
|
+
a single option. Gate the whole field by conditionally rendering it (that's what CXO
|
|
199
|
+
does with `role === "member"`).
|
|
200
|
+
|
|
201
|
+
**6. `className` lands on the outer column, not the bordered box.** To restyle the
|
|
202
|
+
box itself you need a descendant selector from your own class; there is no
|
|
203
|
+
`boxClassName`.
|
|
204
|
+
|
|
205
|
+
**7. Row order is array order, and `key` is the index.** Reordering `checkboxes`
|
|
206
|
+
between renders re-labels existing rows rather than moving them. Keep the array
|
|
207
|
+
stable.
|
|
208
|
+
|
|
209
|
+
---
|
|
210
|
+
|
|
211
|
+
## In the wild
|
|
212
|
+
|
|
213
|
+
```tsx
|
|
214
|
+
// cxo-dashboard src/app/components/mobile-teams-invite-popup.tsx:150
|
|
215
|
+
<ScCheckField
|
|
216
|
+
label="Permission"
|
|
217
|
+
checkboxes={[
|
|
218
|
+
{ label: "Can Use Credits", checked: canUseCredits, onChange: setCanUseCredits },
|
|
219
|
+
{ label: "Can Invite User", checked: canInviteUser, onChange: setCanInviteUser },
|
|
220
|
+
]}
|
|
221
|
+
className="w-full shrink-0"
|
|
222
|
+
/>
|
|
223
|
+
```
|
|
224
|
+
|
|
225
|
+
---
|
|
226
|
+
|
|
227
|
+
## Related
|
|
228
|
+
|
|
229
|
+
- `ScPairtext` — the row this field is made of; use it directly for vertical lists.
|
|
230
|
+
- `ScCheckbox` — the glyph underneath, incl. the `partial` state this field can't reach.
|
|
231
|
+
- `ScTabField` — the single-choice sibling with identical caption/container chrome.
|
|
232
|
+
- `ScTextField` / `ScOnlyField` — the text fields it stacks with.
|
|
233
|
+
- `ScAppField` — the app-permission block that sits below it in CXO's invite forms.
|
|
@@ -0,0 +1,272 @@
|
|
|
1
|
+
---
|
|
2
|
+
component: ScCheckbox
|
|
3
|
+
package: "@streamoid/ui"
|
|
4
|
+
category: forms
|
|
5
|
+
status: stable
|
|
6
|
+
renders: div
|
|
7
|
+
tags: [checkbox, check, tick, tri-state, indeterminate, partial, select-all, multi-select, boolean, row-selection]
|
|
8
|
+
related: [ScCheckField, ScPairtext, ScRadio, ScToggleSwitch]
|
|
9
|
+
do_not_confuse_with: [ScCheckField, ScPairtext, ScToggleSwitch, ScRadio]
|
|
10
|
+
used_by: [catalogix, photogenix]
|
|
11
|
+
---
|
|
12
|
+
|
|
13
|
+
# ScCheckbox
|
|
14
|
+
|
|
15
|
+
**The bare checkbox glyph — a 24×24 box, nothing else.** Renders one of three
|
|
16
|
+
inline SVGs (empty ring / dash / tick) inside a `<div>` that toggles on click. It
|
|
17
|
+
carries **no label**, no native `<input>`, and no internal state.
|
|
18
|
+
|
|
19
|
+
## TL;DR for agents
|
|
20
|
+
|
|
21
|
+
- **Reach for it when:** you need a tickable box next to content you are laying out
|
|
22
|
+
yourself — table-row selection, grid-cell selection, a "select all" header.
|
|
23
|
+
- **Don't reach for it when:** you want a labelled row (→ `ScPairtext`), a whole
|
|
24
|
+
labelled multi-select *field* with a bordered container (→ `ScCheckField`), an
|
|
25
|
+
instant-apply on/off switch (→ `ScToggleSwitch`), or single-choice (→ `ScRadio`).
|
|
26
|
+
- **Four things that will bite you:**
|
|
27
|
+
1. `checked` **overrides `state`** — pass `checked` and `state="partial"` becomes
|
|
28
|
+
unreachable. Indeterminate requires `state="partial"` with `checked` omitted.
|
|
29
|
+
2. It is **not an `<input>`**. No form value, no keyboard, no `role="checkbox"`,
|
|
30
|
+
no `aria-checked`. Wrapping it in a `<label>` does nothing.
|
|
31
|
+
3. It holds **zero internal state**. If you don't move `checked`/`state`, the box
|
|
32
|
+
never visually changes, even though `onChange` fired.
|
|
33
|
+
4. `onChange` **and** `onClick` both fire on every click (`onChange` first).
|
|
34
|
+
|
|
35
|
+
---
|
|
36
|
+
|
|
37
|
+
## 1. How to use it
|
|
38
|
+
|
|
39
|
+
### Import
|
|
40
|
+
|
|
41
|
+
```tsx
|
|
42
|
+
import { ScCheckbox } from "@streamoid/ui";
|
|
43
|
+
import "@streamoid/ui/dist/index.css"; // once, at your app root
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
### Minimal usage
|
|
47
|
+
|
|
48
|
+
```tsx
|
|
49
|
+
<ScCheckbox checked={isSelected} onChange={setIsSelected} />
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
### Props
|
|
53
|
+
|
|
54
|
+
| Prop | Type | Default | Notes |
|
|
55
|
+
|---|---|---|---|
|
|
56
|
+
| `state` | `"default"` \| `"partial"` \| `"selected"` | `"default"` | The tri-state artwork. **Ignored whenever `checked` is not `undefined`.** `"partial"` = the indeterminate dash. |
|
|
57
|
+
| `checked` | `boolean` | – | Controlled boolean. When supplied it wins over `state` and maps to `selected`/`default` only. |
|
|
58
|
+
| `disabled` | `boolean` | `false` | `cursor: not-allowed` + `opacity: 0.5`, and the click handler early-returns. **Does not stop propagation** — see Gotcha 6. |
|
|
59
|
+
| `onChange` | `(checked: boolean) => void` | – | Fires with the *next* value. The handler you normally want. |
|
|
60
|
+
| `onClick` | `MouseEventHandler<HTMLDivElement>` | – | Native click, fires **after** `onChange`. Both run. |
|
|
61
|
+
| `size` | `number \| string` | – | Box size; a number becomes `px`. The SVG fills the box, so the artwork scales proportionally. Default box is `1.5rem` (24px) from CSS. |
|
|
62
|
+
| `style` | `React.CSSProperties` | – | Spread **last**, so it beats `size`, the cursor and the opacity. |
|
|
63
|
+
| `className` | `string` | – | Concatenated between the base and state classes. |
|
|
64
|
+
| `...props` | `Omit<HTMLAttributes<HTMLDivElement>, "onChange">` | – | `id`, `data-*`, `aria-*`, `title` land on the root div. |
|
|
65
|
+
|
|
66
|
+
### What renders in each state
|
|
67
|
+
|
|
68
|
+
| Effective state | Artwork | Tokens |
|
|
69
|
+
|---|---|---|
|
|
70
|
+
| `default` | 22.5px rounded square outline, 1.5 stroke | `--alias-border-default` |
|
|
71
|
+
| `partial` | filled square + horizontal dash | fill `--alias-fill-base-base`, dash `--alias-text---icons-inverse` |
|
|
72
|
+
| `selected` | filled square + tick | fill `--alias-fill-base-base`, tick `--alias-text---icons-inverse` |
|
|
73
|
+
|
|
74
|
+
Effective state is `checked !== undefined ? (checked ? "selected" : "default") : state`.
|
|
75
|
+
|
|
76
|
+
### Recipes
|
|
77
|
+
|
|
78
|
+
```tsx
|
|
79
|
+
// Row selection in a table (the Photogenix idiom) — dense 16px box
|
|
80
|
+
<ScCheckbox
|
|
81
|
+
checked={selectedIds.has(row.id)}
|
|
82
|
+
onChange={() => toggle(row.id)}
|
|
83
|
+
style={{ width: 16, height: 16 }}
|
|
84
|
+
/>
|
|
85
|
+
|
|
86
|
+
// Select-all header with an indeterminate middle state.
|
|
87
|
+
// NOTE: `checked` must be omitted, or "partial" is dropped.
|
|
88
|
+
<ScCheckbox
|
|
89
|
+
state={allSelected ? "selected" : someSelected ? "partial" : "default"}
|
|
90
|
+
onChange={() => (allSelected ? clearAll() : selectAll())}
|
|
91
|
+
/>
|
|
92
|
+
|
|
93
|
+
// Your own labelled row — the label text is NOT clickable for free
|
|
94
|
+
<div
|
|
95
|
+
style={{ display: "flex", gap: 8, alignItems: "center", cursor: "pointer" }}
|
|
96
|
+
onClick={() => setAddToAll(!addToAll)}
|
|
97
|
+
>
|
|
98
|
+
<ScCheckbox checked={addToAll} />
|
|
99
|
+
<span>Add to all style codes ({styleCodes.length})</span>
|
|
100
|
+
</div>
|
|
101
|
+
|
|
102
|
+
// Inside a clickable row — stop the cell swallowing/duplicating the click
|
|
103
|
+
<td onClick={(e) => e.stopPropagation()}>
|
|
104
|
+
<ScCheckbox checked={sel} onChange={onToggle} />
|
|
105
|
+
</td>
|
|
106
|
+
```
|
|
107
|
+
|
|
108
|
+
---
|
|
109
|
+
|
|
110
|
+
## 2. Where to use it
|
|
111
|
+
|
|
112
|
+
- **Table and grid row selection** — Photogenix's batch-detail product rows and
|
|
113
|
+
admin history view; Catalogix's PLP grid (16px via the local `Checkbox` wrapper).
|
|
114
|
+
- **"Apply to all" options in modals** — Catalogix
|
|
115
|
+
`app/containers/Assets/AddToProducts` ("Add to all style codes").
|
|
116
|
+
- **Inside the DS itself** — `ScPairtext` with `type="checkbox"` renders one, and
|
|
117
|
+
`ScCheckField` renders a row of `ScPairtext`s. If you need label + box, go up a
|
|
118
|
+
level rather than composing this by hand.
|
|
119
|
+
|
|
120
|
+
Catalogix and Photogenix both wrap it in a local shim (`app/components/Checkbox`,
|
|
121
|
+
Photogenix's test stub) to preserve legacy `partiallyChecked` / `onClick(next)` APIs.
|
|
122
|
+
|
|
123
|
+
---
|
|
124
|
+
|
|
125
|
+
## 3. When to use it
|
|
126
|
+
|
|
127
|
+
### Use it when
|
|
128
|
+
|
|
129
|
+
- The choice is **boolean and staged** (the user confirms later with a button), or
|
|
130
|
+
it selects rows for a bulk action.
|
|
131
|
+
- You are laying out the label/row yourself and only want the glyph to be
|
|
132
|
+
token-correct and theme-correct.
|
|
133
|
+
- You need the **indeterminate** dash — this is the only DS control that has one.
|
|
134
|
+
|
|
135
|
+
### Don't use it — reach for this instead
|
|
136
|
+
|
|
137
|
+
| Situation | Use instead |
|
|
138
|
+
|---|---|
|
|
139
|
+
| Box **plus** a text label as one clickable row | `ScPairtext` (`type="checkbox"`) |
|
|
140
|
+
| A labelled field: caption + bordered box + several options | `ScCheckField` |
|
|
141
|
+
| On/off that applies **immediately** (a setting) | `ScToggleSwitch` |
|
|
142
|
+
| Exactly one of N | `ScRadio`, or `ScTabField` / `ScSelectionPillGroup` for a segmented look |
|
|
143
|
+
| App-permission rows in an invite flow | `ScAppField` (already composes the toggles) |
|
|
144
|
+
| A real form control that submits with a `<form>` | a native `<input type="checkbox">` — this div never participates in form data |
|
|
145
|
+
|
|
146
|
+
### Don't confuse with
|
|
147
|
+
|
|
148
|
+
| You may actually want | Not this |
|
|
149
|
+
|---|---|
|
|
150
|
+
| `ScCheckField` — the *field*: label + bordered container + N labelled checkboxes | `ScCheckbox` is only the 24px glyph |
|
|
151
|
+
| `ScPairtext` — one label+control row; `type` picks checkbox / radio / icon | `ScCheckbox` has no text at all |
|
|
152
|
+
| `ScToggleSwitch` — 40×24 switch, different semantics (instant apply) | Not a styling variant of this |
|
|
153
|
+
| `ScSelectionList` — in-chat approve/reject row (**agent runtime**) | Unrelated to forms |
|
|
154
|
+
|
|
155
|
+
---
|
|
156
|
+
|
|
157
|
+
## 4. Why to use it
|
|
158
|
+
|
|
159
|
+
- **It themes.** Both host apps replaced baked-dark PNG/SVG `<img>` ticks with this
|
|
160
|
+
because those did not invert in light mode. The three states are drawn from
|
|
161
|
+
`--alias-*` tokens, so they resolve in both themes with no conditionals.
|
|
162
|
+
- **`size` actually scales the artwork.** The inner SVG has literal `width="24"`
|
|
163
|
+
attributes; the CSS forces `width/height: 100%` on it so a shrunk box scales the
|
|
164
|
+
tick instead of letting a 24px glyph spill out of a 16px frame. Hand-rolling that
|
|
165
|
+
is exactly the bug the CSS comment documents.
|
|
166
|
+
- **A real indeterminate state**, drawn to the design, without an
|
|
167
|
+
`input.indeterminate` imperative ref dance.
|
|
168
|
+
- **One glyph everywhere.** Row selection in Photogenix, the Catalogix PLP grid and
|
|
169
|
+
every `ScPairtext` share one shape, radius and stroke weight.
|
|
170
|
+
|
|
171
|
+
---
|
|
172
|
+
|
|
173
|
+
## Gotchas
|
|
174
|
+
|
|
175
|
+
**1. `checked` beats `state`, and kills `partial`.** The moment `checked` is not
|
|
176
|
+
`undefined` the component ignores `state` entirely.
|
|
177
|
+
|
|
178
|
+
```tsx
|
|
179
|
+
// WRONG — renders the empty box; "partial" is discarded because `checked` is defined
|
|
180
|
+
<ScCheckbox checked={false} state="partial" />
|
|
181
|
+
|
|
182
|
+
// RIGHT — omit `checked` when you need the dash
|
|
183
|
+
<ScCheckbox state="partial" onChange={selectAll} />
|
|
184
|
+
```
|
|
185
|
+
|
|
186
|
+
**2. It's not an `<input>` and has no a11y wiring.** No `role`, no `aria-checked`,
|
|
187
|
+
no `tabIndex`, no keyboard activation, no `name`/`value`, no form participation.
|
|
188
|
+
Add semantics yourself if the surface needs them:
|
|
189
|
+
|
|
190
|
+
```tsx
|
|
191
|
+
<ScCheckbox
|
|
192
|
+
checked={sel}
|
|
193
|
+
onChange={onToggle}
|
|
194
|
+
role="checkbox"
|
|
195
|
+
aria-checked={sel}
|
|
196
|
+
aria-label="Select product"
|
|
197
|
+
tabIndex={0}
|
|
198
|
+
/>
|
|
199
|
+
```
|
|
200
|
+
|
|
201
|
+
**3. There is no label, and `<label>` won't help.** Because there is no inner
|
|
202
|
+
`<input>`, wrapping the glyph and some text in a `<label>` gives you *no* click
|
|
203
|
+
target on the text. Put the `onClick` on the wrapper row (see Recipes) or use
|
|
204
|
+
`ScPairtext`.
|
|
205
|
+
|
|
206
|
+
**4. It is fully controlled with no internal state.** `onChange` firing does not
|
|
207
|
+
change the picture. If your handler doesn't move `checked`/`state`, nothing happens.
|
|
208
|
+
|
|
209
|
+
**5. `onChange` and `onClick` both fire, `onChange` first.** Don't wire the same
|
|
210
|
+
mutation to both.
|
|
211
|
+
|
|
212
|
+
```tsx
|
|
213
|
+
// WRONG — toggles twice per click
|
|
214
|
+
<ScCheckbox checked={c} onChange={() => setC(!c)} onClick={() => setC(!c)} />
|
|
215
|
+
|
|
216
|
+
// RIGHT
|
|
217
|
+
<ScCheckbox checked={c} onChange={setC} />
|
|
218
|
+
```
|
|
219
|
+
|
|
220
|
+
**6. `disabled` does not stop propagation.** It early-returns before *both*
|
|
221
|
+
`onChange` and your `onClick`, but the DOM click still bubbles — a parent row's
|
|
222
|
+
`onClick` (row navigation, expand) still fires. Stop it yourself if that matters.
|
|
223
|
+
|
|
224
|
+
**7. `style` wins over `size`, and over `disabled` styling.** `style` is spread
|
|
225
|
+
last, so `style={{ opacity: 1 }}` visually un-dims a disabled box (it stays
|
|
226
|
+
non-interactive). Photogenix sizes via `style={{ width: 16, height: 16 }}`; both
|
|
227
|
+
routes work, don't pass both.
|
|
228
|
+
|
|
229
|
+
**8. `className` is string-concatenated, not joined.** With `className` unset the
|
|
230
|
+
DOM class list literally contains `undefined`. Harmless in the browser, but it
|
|
231
|
+
shows up in snapshot tests — don't assert on the exact class string.
|
|
232
|
+
|
|
233
|
+
**9. jsdom tests need a stub.** Importing from the `@streamoid/ui` barrel pulls
|
|
234
|
+
`ScButton`, which touches `CSS.registerProperty` at module load. Both host repos
|
|
235
|
+
`vi.mock("@streamoid/ui")` with a div that surfaces `state` and forwards `onClick`
|
|
236
|
+
(see `catalogix/dashboard app/tests/formControls.test.jsx`).
|
|
237
|
+
|
|
238
|
+
---
|
|
239
|
+
|
|
240
|
+
## In the wild
|
|
241
|
+
|
|
242
|
+
```tsx
|
|
243
|
+
// photogenix_v2 dashboard/client/src/components/studio/batchDetail/BatchDetailProductRow.tsx:225
|
|
244
|
+
<ScCheckbox
|
|
245
|
+
checked={state.selectedProducts.has(product.id)}
|
|
246
|
+
onChange={() => state.toggleSelect(product.id)}
|
|
247
|
+
style={{ width: 16, height: 16 }}
|
|
248
|
+
/>
|
|
249
|
+
```
|
|
250
|
+
|
|
251
|
+
```jsx
|
|
252
|
+
// catalogix/dashboard app/components/Checkbox/index.jsx:31 (legacy-API shim)
|
|
253
|
+
<ScCheckbox
|
|
254
|
+
size={props.size}
|
|
255
|
+
state={props.partiallyChecked ? "partial" : checked ? "selected" : "default"}
|
|
256
|
+
style={{
|
|
257
|
+
...(props.style || {}),
|
|
258
|
+
...(props.disabled ? { opacity: 0.5, cursor: "not-allowed" } : {}),
|
|
259
|
+
}}
|
|
260
|
+
onClick={onClickFunc}
|
|
261
|
+
/>
|
|
262
|
+
```
|
|
263
|
+
|
|
264
|
+
---
|
|
265
|
+
|
|
266
|
+
## Related
|
|
267
|
+
|
|
268
|
+
- `ScPairtext` — label + checkbox/radio/icon as one clickable row; the level above this.
|
|
269
|
+
- `ScCheckField` — full labelled multi-select field; composes `ScPairtext`.
|
|
270
|
+
- `ScRadio` — the single-choice sibling glyph (presentational only, no `checked`).
|
|
271
|
+
- `ScToggleSwitch` — instant-apply on/off switch.
|
|
272
|
+
- `ScAppField` — app-permission block; already wires its own controls.
|