@streamoid/ui 0.6.16 → 0.6.18
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- 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 +43 -37
- package/dist/index.d.mts +10 -0
- package/dist/index.d.ts +10 -0
- package/package.json +3 -2
|
@@ -0,0 +1,255 @@
|
|
|
1
|
+
---
|
|
2
|
+
component: ScFieldButton
|
|
3
|
+
package: "@streamoid/ui"
|
|
4
|
+
category: actions
|
|
5
|
+
status: stable
|
|
6
|
+
renders: div
|
|
7
|
+
tags: [button, ghost-button, inline-action, attach, browse, apply, compact, toolbar, composer]
|
|
8
|
+
related: [ScButton, ScOnlyIcon, ScMenuOptions, ScTextField, ScOnlyField]
|
|
9
|
+
do_not_confuse_with: [ScButton, ScOnlyIcon, ScMenuOptions, ScTabComp]
|
|
10
|
+
used_by: [cxo]
|
|
11
|
+
---
|
|
12
|
+
|
|
13
|
+
# ScFieldButton
|
|
14
|
+
|
|
15
|
+
**A 36 px transparent text/icon chip for actions that sit next to a field.**
|
|
16
|
+
No fill and no border at rest; a tertiary label that turns primary and picks up a
|
|
17
|
+
neutral background on hover. Used for the small secondary affordances in a
|
|
18
|
+
composer or field row — "Attach", "Browse", "Apply".
|
|
19
|
+
|
|
20
|
+
## TL;DR for agents
|
|
21
|
+
|
|
22
|
+
- **Reach for it when:** you need a low-emphasis, compact action **beside** an
|
|
23
|
+
input or inside a composer toolbar, and a full `ScButton` would shout.
|
|
24
|
+
- **Don't reach for it when:** it is a real CTA (→ `ScButton`), it needs
|
|
25
|
+
keyboard/`aria` semantics, or it is a row in a dropdown menu
|
|
26
|
+
(→ `ScMenuOptions`).
|
|
27
|
+
- **Four things that will bite you:**
|
|
28
|
+
1. `type="icon-right"` renders the icon **on the LEFT** of the label, and
|
|
29
|
+
`type="icon-left"` renders it **on the RIGHT**. The names are inverted in the
|
|
30
|
+
render order — trust this table, not the prop name.
|
|
31
|
+
2. `icon` defaults to `<SiconHome />` and `text` defaults to `"Button"`.
|
|
32
|
+
3. Your own icon is **not size-normalised** — the 16 px `!important` rule only
|
|
33
|
+
applies to the built-in default. Pass a 16 px icon.
|
|
34
|
+
4. It is a plain `<div>`: no `role`, no `tabIndex`, no Enter/Space, no
|
|
35
|
+
`disabled` and no disabled styling. `onClick` works; keyboards don't.
|
|
36
|
+
|
|
37
|
+
---
|
|
38
|
+
|
|
39
|
+
## 1. How to use it
|
|
40
|
+
|
|
41
|
+
### Import
|
|
42
|
+
|
|
43
|
+
```tsx
|
|
44
|
+
import { ScFieldButton } from "@streamoid/ui";
|
|
45
|
+
import "@streamoid/ui/dist/index.css"; // once, at your app root
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
### Minimal usage
|
|
49
|
+
|
|
50
|
+
```tsx
|
|
51
|
+
<ScFieldButton text="Apply" onClick={handleApply} />
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
### Props
|
|
55
|
+
|
|
56
|
+
Extends `React.HTMLAttributes<HTMLDivElement>`; unlisted props (`onClick`,
|
|
57
|
+
`style`, `title`, `data-*`, `aria-*`) are spread onto the root div.
|
|
58
|
+
|
|
59
|
+
| Prop | Type | Default | Notes |
|
|
60
|
+
|---|---|---|---|
|
|
61
|
+
| `text` | `string` | `"Button"` | ⚠️ Real default. Rendered for `default` / `icon-left` / `icon-right`; **not** for `only-icon`. A trailing space is appended in the markup. |
|
|
62
|
+
| `icon` | `JSX.Element` | `<SiconHome />` | ⚠️ Real default — forget it on an icon `type` and you ship a house. Only the default gets the 16 px `!important` sizing. |
|
|
63
|
+
| `type` | `"default"` \| `"icon-right"` \| `"icon-left"` \| `"only-icon"` | `"default"` | ⚠️ Controls presence **and side** of the icon — see the table below. |
|
|
64
|
+
| `state` | `"default"` | `"default"` | The only allowed value. The stylesheet also has a `state-hover` skin, but the type won't let you reach it (real `:hover` already works). |
|
|
65
|
+
| `className` | `string` | – | Appended to the root class list. |
|
|
66
|
+
|
|
67
|
+
#### What each `type` renders, in DOM order
|
|
68
|
+
|
|
69
|
+
| `type` | DOM order | Visual result |
|
|
70
|
+
|---|---|---|
|
|
71
|
+
| `"default"` | label | text only |
|
|
72
|
+
| `"icon-right"` | **icon → label** | icon on the **left** ⚠️ |
|
|
73
|
+
| `"icon-left"` | **label → icon** | icon on the **right** ⚠️ |
|
|
74
|
+
| `"only-icon"` | icon | icon only, no label |
|
|
75
|
+
|
|
76
|
+
### Recipes
|
|
77
|
+
|
|
78
|
+
```tsx
|
|
79
|
+
// Leading icon + label — note type="icon-right" for an icon on the LEFT
|
|
80
|
+
<ScFieldButton
|
|
81
|
+
type="icon-right"
|
|
82
|
+
icon={<SiconAttach className="w-4 h-4" color="var(--alias-text-and-icons-tertiary)" />}
|
|
83
|
+
text="Attach"
|
|
84
|
+
onClick={handleAttachClick}
|
|
85
|
+
/>
|
|
86
|
+
|
|
87
|
+
// Trailing icon (e.g. a caret) — type="icon-left"
|
|
88
|
+
<ScFieldButton
|
|
89
|
+
type="icon-left"
|
|
90
|
+
icon={<SiconDown size={16} color="var(--alias-text-and-icons-tertiary)" />}
|
|
91
|
+
text="More"
|
|
92
|
+
onClick={openMore}
|
|
93
|
+
/>
|
|
94
|
+
|
|
95
|
+
// Icon only — give it a label for assistive tech, since there is no text node
|
|
96
|
+
<ScFieldButton
|
|
97
|
+
type="only-icon"
|
|
98
|
+
icon={<SiconClose size={16} />}
|
|
99
|
+
role="button"
|
|
100
|
+
tabIndex={0}
|
|
101
|
+
aria-label="Clear"
|
|
102
|
+
onClick={clear}
|
|
103
|
+
onKeyDown={(e) => { if (e.key === "Enter" || e.key === " ") clear(); }}
|
|
104
|
+
/>
|
|
105
|
+
|
|
106
|
+
// "Disabled" — there is no prop, so gate it yourself
|
|
107
|
+
<ScFieldButton
|
|
108
|
+
text="Apply"
|
|
109
|
+
onClick={canApply ? apply : undefined}
|
|
110
|
+
style={{ opacity: canApply ? 1 : 0.5, pointerEvents: canApply ? "auto" : "none" }}
|
|
111
|
+
/>
|
|
112
|
+
```
|
|
113
|
+
|
|
114
|
+
---
|
|
115
|
+
|
|
116
|
+
## 2. Where to use it
|
|
117
|
+
|
|
118
|
+
- **Composer / prompt toolbars** — CXO's landing composer puts an "Attach"
|
|
119
|
+
field button next to the hidden `<input type="file">` and an icon-only
|
|
120
|
+
`ScButton` for send.
|
|
121
|
+
- **Field rows** — a "Browse", "Generate", "Reset" affordance immediately after
|
|
122
|
+
an `ScTextField` / `ScOnlyField` (wrap both in a flex row; the button is 36 px
|
|
123
|
+
and the field is 48 px, so centre them).
|
|
124
|
+
- **Card and panel headers** — a tertiary text action that must not compete with
|
|
125
|
+
the primary CTA.
|
|
126
|
+
|
|
127
|
+
CXO is the only host rendering it today.
|
|
128
|
+
|
|
129
|
+
---
|
|
130
|
+
|
|
131
|
+
## 3. When to use it
|
|
132
|
+
|
|
133
|
+
### Use it when
|
|
134
|
+
|
|
135
|
+
- The action is **secondary**: it modifies the field next to it rather than
|
|
136
|
+
submitting the form.
|
|
137
|
+
- You want a **transparent** control that only shows a background on hover.
|
|
138
|
+
- 36 px is the right height — this is the compact chip, not a field-height button.
|
|
139
|
+
|
|
140
|
+
### Don't use it — reach for this instead
|
|
141
|
+
|
|
142
|
+
| Situation | Use instead |
|
|
143
|
+
|---|---|
|
|
144
|
+
| Any real CTA (Save, Invite, Delete, Upgrade) | `ScButton` — variants, sizes, loading, disabled, keyboard |
|
|
145
|
+
| You need `disabled`, a spinner, or a destructive colour | `ScButton` (`state="disabled"`, `loading`, `variant="error"`) |
|
|
146
|
+
| A bare icon affordance with keyboard support | `ScButton` with `styleVariant="icon-only"` |
|
|
147
|
+
| A row inside a dropdown/popup menu | `ScMenuOptions` (+ `ScPopUpMenu`) |
|
|
148
|
+
| A tab in a tab strip | `ScTabComp` / `ScTabs` / `ScTabSwitcher` |
|
|
149
|
+
| Navigation to a route/URL | a real `<a>` / your router's `Link` — this is a `div` |
|
|
150
|
+
| Mobile sticky footer action | `ScMobileBottomAction` |
|
|
151
|
+
|
|
152
|
+
### Don't confuse with
|
|
153
|
+
|
|
154
|
+
| You may actually want | Not this |
|
|
155
|
+
|---|---|
|
|
156
|
+
| `ScButton` — the one button primitive (`div[role="button"]`, keyboard, 6 variants × 3 types × 3 sizes) | `ScFieldButton` is a styleless 36 px ghost chip with no states |
|
|
157
|
+
| `ScOnlyIcon` — a 40 px hover square that **always** renders `SiconSettings` and takes no `icon` prop | `ScFieldButton type="only-icon"` is the one that accepts your icon |
|
|
158
|
+
| “field-height, bordered” (as older docs describe it) | it is **36 px and borderless**; it does not line up with a 48 px field unless you centre it |
|
|
159
|
+
| `ScMenuOptions` — visually similar row with icon + label, but built for menus | different padding/hover contract |
|
|
160
|
+
|
|
161
|
+
---
|
|
162
|
+
|
|
163
|
+
## 4. Why to use it
|
|
164
|
+
|
|
165
|
+
- **The hover contract is right.** Background goes to
|
|
166
|
+
`--alias-fill-neutral-neutraltohover` and the label from
|
|
167
|
+
`text-and-icons-tertiary` to `-primary` in one step, matching every other
|
|
168
|
+
tertiary affordance in the product.
|
|
169
|
+
- **Compact geometry that matches the composer spec** — 8/10 px padding around a
|
|
170
|
+
20 px line box (36 px total) with a 12 px radius.
|
|
171
|
+
- **Cheap.** No variant matrix, no canvas texture, no spinner: a genuinely light
|
|
172
|
+
component for the many small actions where `ScButton` is overkill.
|
|
173
|
+
|
|
174
|
+
---
|
|
175
|
+
|
|
176
|
+
## Gotchas
|
|
177
|
+
|
|
178
|
+
**1. The icon-side prop names are inverted.** `icon-right` puts the icon before
|
|
179
|
+
the label in the DOM (so, on the left); `icon-left` puts it after. There is no
|
|
180
|
+
`row-reverse` in the stylesheet to undo it. The CXO "Attach" call site relies on
|
|
181
|
+
this inverted behaviour.
|
|
182
|
+
|
|
183
|
+
```tsx
|
|
184
|
+
// WRONG — expecting a trailing paperclip
|
|
185
|
+
<ScFieldButton type="icon-right" icon={<SiconAttach />} text="Attach" /> // icon is on the LEFT
|
|
186
|
+
|
|
187
|
+
// RIGHT — trailing icon
|
|
188
|
+
<ScFieldButton type="icon-left" icon={<SiconDown />} text="More" /> // icon is on the RIGHT
|
|
189
|
+
```
|
|
190
|
+
|
|
191
|
+
**2. `icon` defaults to a home icon and `text` to `"Button"`.**
|
|
192
|
+
|
|
193
|
+
```tsx
|
|
194
|
+
// WRONG — renders 🏠 + "Button"
|
|
195
|
+
<ScFieldButton type="icon-right" />
|
|
196
|
+
|
|
197
|
+
// RIGHT
|
|
198
|
+
<ScFieldButton type="icon-right" icon={<SiconAttach className="w-4 h-4" />} text="Attach" />
|
|
199
|
+
```
|
|
200
|
+
|
|
201
|
+
**3. Your icon is not resized for you.** `.siconHomeInstance { width: 1rem
|
|
202
|
+
!important; height: 1rem !important }` is attached to the *default* icon element
|
|
203
|
+
only. `Sicon*` components default to `size={24}`, so a custom icon renders 50 %
|
|
204
|
+
too large. The inner `.container` is a fixed `height: 1.25rem` with no
|
|
205
|
+
`overflow: hidden`, so the oversized icon **overflows the line box** (the chip stays
|
|
206
|
+
36 px and the row looks misaligned) rather than growing the chip. Pass `size={16}`
|
|
207
|
+
or `className="w-4 h-4"` (as the CXO call site does).
|
|
208
|
+
|
|
209
|
+
**4. No colour normalisation.** Unlike `ScButton`, the icon is **not** cloned with
|
|
210
|
+
`color="currentColor"`, so it will not follow the label's tertiary→primary hover
|
|
211
|
+
transition. Set the colour explicitly on your icon (usually
|
|
212
|
+
`var(--alias-text-and-icons-tertiary)`); accept that it stays that colour on hover.
|
|
213
|
+
|
|
214
|
+
**5. It is a `div` with no semantics.** No `role`, no `tabIndex`, no
|
|
215
|
+
Enter/Space handling, no focus ring. Add `role="button" tabIndex={0}` +
|
|
216
|
+
`onKeyDown` yourself if the action matters — or use `ScButton`.
|
|
217
|
+
|
|
218
|
+
**6. There is no `disabled` prop or skin.** `state` only accepts `"default"`.
|
|
219
|
+
Gate `onClick` and dim it yourself (see the recipe).
|
|
220
|
+
|
|
221
|
+
**7. `only-icon` renders no text node**, so screen readers get nothing. Always
|
|
222
|
+
pass `aria-label` in that mode.
|
|
223
|
+
|
|
224
|
+
**8. The radius uses a non-standard token,** `var(--medium-5, 0.75rem)`, not
|
|
225
|
+
`--radius-xl`. If you are auditing tokens, this is a known outlier; don't copy the
|
|
226
|
+
pattern into new components.
|
|
227
|
+
|
|
228
|
+
---
|
|
229
|
+
|
|
230
|
+
## In the wild
|
|
231
|
+
|
|
232
|
+
```tsx
|
|
233
|
+
// cxo-dashboard src/app/components/landing-content.tsx:327
|
|
234
|
+
<ScFieldButton
|
|
235
|
+
icon={
|
|
236
|
+
<SiconAttach
|
|
237
|
+
className="w-4 h-4"
|
|
238
|
+
color="var(--alias-text---icons-tertiary)"
|
|
239
|
+
/>
|
|
240
|
+
}
|
|
241
|
+
type="icon-right"
|
|
242
|
+
text="Attach"
|
|
243
|
+
onClick={handleAttachClick}
|
|
244
|
+
/>
|
|
245
|
+
```
|
|
246
|
+
|
|
247
|
+
---
|
|
248
|
+
|
|
249
|
+
## Related
|
|
250
|
+
|
|
251
|
+
- `ScButton` — the primitive for anything that is actually a CTA.
|
|
252
|
+
- `ScOnlyIcon` — the confusable icon square (no `icon` prop; avoid).
|
|
253
|
+
- `ScMenuOptions` / `ScPopUpMenu` — the menu-row vocabulary.
|
|
254
|
+
- `ScTextField` / `ScOnlyField` — the fields this button is meant to sit beside.
|
|
255
|
+
- `@streamoid/icons` — pass `size={16}`; see `packages/icons/ICONS.md`.
|
|
@@ -0,0 +1,268 @@
|
|
|
1
|
+
---
|
|
2
|
+
component: ScFileField
|
|
3
|
+
package: "@streamoid/ui"
|
|
4
|
+
category: forms
|
|
5
|
+
status: stable
|
|
6
|
+
renders: div
|
|
7
|
+
tags: [file, upload, dropzone, csv, import, progress, attachment, form-field]
|
|
8
|
+
related: [ScImageField, ScProfileImageUpdate, ScTextField, ScFieldButton, ScProgressBar]
|
|
9
|
+
do_not_confuse_with: [ScImageField, ScProfileImageUpdate, ScAppField, ScProgressBar]
|
|
10
|
+
---
|
|
11
|
+
|
|
12
|
+
# ScFileField
|
|
13
|
+
|
|
14
|
+
**The generic file-upload form field.** A dashed dropzone with "Click to upload"
|
|
15
|
+
that swaps, once you hand it a `fileName`, into a one-line file row with a file
|
|
16
|
+
icon, an ellipsised name, an optional progress bar and an ✕ clear button. Error
|
|
17
|
+
text renders under either state.
|
|
18
|
+
|
|
19
|
+
## TL;DR for agents
|
|
20
|
+
|
|
21
|
+
- **Reach for it when:** a form needs any *non-image* file — a CSV/XLSX feed, a PDF,
|
|
22
|
+
a ZIP — and you want a filename + progress + error line for free.
|
|
23
|
+
- **Don't reach for it when:** the file is an image you want to preview
|
|
24
|
+
(→ `ScImageField`), or it's an avatar/profile picture (→ `ScProfileImageUpdate`).
|
|
25
|
+
- **Four things that will bite you:**
|
|
26
|
+
1. **It says "or drag and drop" but drag-and-drop does not work.** There are no
|
|
27
|
+
drop handlers and the `<input>` is clipped to 1px. Copy is aspirational.
|
|
28
|
+
2. It is **fully controlled and stateless**. It never remembers the file you
|
|
29
|
+
picked — nothing changes on screen until *you* set `fileName`.
|
|
30
|
+
3. It does **not** extend `HTMLAttributes`. No `style`, no `id`, no `data-testid`,
|
|
31
|
+
no `onClick` — the props table is the whole API.
|
|
32
|
+
4. Setting `fileName` **removes the dropzone entirely**. Replacing a file requires
|
|
33
|
+
a clear first.
|
|
34
|
+
|
|
35
|
+
---
|
|
36
|
+
|
|
37
|
+
## 1. How to use it
|
|
38
|
+
|
|
39
|
+
### Import
|
|
40
|
+
|
|
41
|
+
```tsx
|
|
42
|
+
import { ScFileField } from "@streamoid/ui";
|
|
43
|
+
import "@streamoid/ui/dist/index.css"; // once, at your app root
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
### Minimal usage
|
|
47
|
+
|
|
48
|
+
```tsx
|
|
49
|
+
const [file, setFile] = useState<File | null>(null);
|
|
50
|
+
|
|
51
|
+
<ScFileField
|
|
52
|
+
label="Feed file"
|
|
53
|
+
accept=".csv,.xlsx"
|
|
54
|
+
fileName={file?.name ?? null}
|
|
55
|
+
onFileSelect={(files) => setFile(files?.[0] ?? null)}
|
|
56
|
+
onClear={() => setFile(null)}
|
|
57
|
+
/>
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
### Props
|
|
61
|
+
|
|
62
|
+
| Prop | Type | Default | Notes |
|
|
63
|
+
|---|---|---|---|
|
|
64
|
+
| `label` | `string` | – | Small tertiary label above the control. Omit it and the label row is not rendered at all. **Not** wired to the input with `htmlFor` — see Gotcha 6. |
|
|
65
|
+
| `onFileSelect` | `(files: FileList \| null) => void` | – | Fires on `change` with the raw `FileList`. Take `files?.[0]` yourself unless `multiple`. |
|
|
66
|
+
| `accept` | `string` | – | Passed straight to the input's `accept`. When set, also prints a literal `Accepted: {accept}` hint under the dropzone — so pass something human-readable like `".csv,.xlsx"`, not a MIME soup. |
|
|
67
|
+
| `multiple` | `boolean` | – (falsy) | Passed to the input. **`fileName` is still a single string** — you join the names. |
|
|
68
|
+
| `fileName` | `string \| null` | – | **The state switch.** Truthy → file row; falsy → dropzone. |
|
|
69
|
+
| `uploading` | `boolean` | – (falsy) | Shows the progress bar **and hides the clear button**. Also forces the file row even when `fileName` is empty. |
|
|
70
|
+
| `progress` | `number` | `0` | 0–100. Clamped, so out-of-range values are safe. Only visible while `uploading`. |
|
|
71
|
+
| `error` | `string \| null` | – | Red 12px line under the control. Renders in **both** states. |
|
|
72
|
+
| `onClear` | `() => void` | – | The ✕ button. Not rendered while `uploading`. |
|
|
73
|
+
| `className` | `string` | – | Appended after the internal root class. |
|
|
74
|
+
|
|
75
|
+
There is **no** `...props` spread, no `disabled`, no `required`, no `id`.
|
|
76
|
+
|
|
77
|
+
### What renders in each state
|
|
78
|
+
|
|
79
|
+
| `fileName` | `uploading` | You get |
|
|
80
|
+
|---|---|---|
|
|
81
|
+
| falsy | falsy | Dashed dropzone: up-arrow, "**Click to upload** or drag and drop", `Accepted: …` |
|
|
82
|
+
| truthy | falsy | File row: file icon, name (ellipsised), ✕ clear |
|
|
83
|
+
| truthy | truthy | File row: file icon, name, progress bar. **No ✕** |
|
|
84
|
+
| falsy | truthy | File row with an **empty name** and a progress bar — usually a bug in your state |
|
|
85
|
+
|
|
86
|
+
### Recipes
|
|
87
|
+
|
|
88
|
+
```tsx
|
|
89
|
+
// Upload with progress, driven by your own XHR/fetch
|
|
90
|
+
<ScFileField
|
|
91
|
+
label="Catalog export"
|
|
92
|
+
accept=".csv,.xlsx"
|
|
93
|
+
fileName={upload.name}
|
|
94
|
+
uploading={upload.status === "sending"}
|
|
95
|
+
progress={upload.pct}
|
|
96
|
+
error={upload.status === "failed" ? upload.message : null}
|
|
97
|
+
onFileSelect={(files) => files?.[0] && start(files[0])}
|
|
98
|
+
onClear={() => reset()}
|
|
99
|
+
/>
|
|
100
|
+
|
|
101
|
+
// Multiple files — you own the summary string
|
|
102
|
+
<ScFileField
|
|
103
|
+
label="Attachments"
|
|
104
|
+
multiple
|
|
105
|
+
fileName={files.length ? files.map((f) => f.name).join(", ") : null}
|
|
106
|
+
onFileSelect={(fl) => setFiles(Array.from(fl ?? []))}
|
|
107
|
+
onClear={() => setFiles([])}
|
|
108
|
+
/>
|
|
109
|
+
|
|
110
|
+
// Client-side validation before you accept the file
|
|
111
|
+
<ScFileField
|
|
112
|
+
accept=".csv"
|
|
113
|
+
fileName={name}
|
|
114
|
+
error={err}
|
|
115
|
+
onFileSelect={(fl) => {
|
|
116
|
+
const f = fl?.[0];
|
|
117
|
+
if (!f) return;
|
|
118
|
+
if (!f.name.endsWith(".csv")) { setErr("Only .csv is supported"); return; }
|
|
119
|
+
setErr(null); setName(f.name);
|
|
120
|
+
}}
|
|
121
|
+
onClear={() => { setName(null); setErr(null); }}
|
|
122
|
+
/>
|
|
123
|
+
```
|
|
124
|
+
|
|
125
|
+
---
|
|
126
|
+
|
|
127
|
+
## 2. Where to use it
|
|
128
|
+
|
|
129
|
+
- **Import / feed-upload steps** — the "pick your CSV" step before a mapping screen
|
|
130
|
+
(`ScMappingCard` is the screen after it).
|
|
131
|
+
- **Inside `ScModal` / `ScDrawer` forms** — it is `width: 100%` and lays out as a
|
|
132
|
+
column, so it drops into a field stack next to `ScTextField` and `ScAppField`
|
|
133
|
+
without extra wrapping.
|
|
134
|
+
- **Settings panels** that accept a document rather than an image.
|
|
135
|
+
|
|
136
|
+
Currently it has **no host call site** — see "In the wild".
|
|
137
|
+
|
|
138
|
+
---
|
|
139
|
+
|
|
140
|
+
## 3. When to use it
|
|
141
|
+
|
|
142
|
+
### Use it when
|
|
143
|
+
|
|
144
|
+
- The payload is a **document or data file**, not something you'd show a thumbnail of.
|
|
145
|
+
- You want the DS's filename/progress/error line rather than inventing three more
|
|
146
|
+
bits of layout per form.
|
|
147
|
+
- Your upload is asynchronous and you have a real percentage to show.
|
|
148
|
+
|
|
149
|
+
### Don't use it — reach for this instead
|
|
150
|
+
|
|
151
|
+
| Situation | Use instead |
|
|
152
|
+
|---|---|
|
|
153
|
+
| An image with a square thumbnail preview (workspace logo, cover art) | `ScImageField` |
|
|
154
|
+
| A round avatar with upload + delete icon buttons | `ScProfileImageUpdate` |
|
|
155
|
+
| A "Browse…" button glued to a text input showing a path | `ScTextField` + `ScFieldButton` |
|
|
156
|
+
| A bare progress bar with no file semantics | `ScProgressBar` |
|
|
157
|
+
| Real drag-and-drop | Nothing in the DS does this. Wrap this component in your own `onDrop` container, or build it — see Gotcha 1. |
|
|
158
|
+
| Toggling which apps a user can access | `ScAppField` (unrelated despite "Field") |
|
|
159
|
+
|
|
160
|
+
### Don't confuse with
|
|
161
|
+
|
|
162
|
+
| You may actually want | Not this |
|
|
163
|
+
|---|---|
|
|
164
|
+
| `ScImageField` — same "Click to upload" copy, but image-only (`accept="image/*"` is hardcoded), 120px tall, shows a thumbnail | `ScFileField` is type-agnostic, has progress + error, and shows no preview |
|
|
165
|
+
| `ScProfileImageUpdate` — 100px circle + two icon buttons, avatar-shaped | `ScFileField` is a form row |
|
|
166
|
+
| `ScProgressBar` — the standalone bar | The bar here is internal and only appears while `uploading` |
|
|
167
|
+
|
|
168
|
+
The three uploaders are **not** variants of each other and share **no** props beyond
|
|
169
|
+
`className`. Even the select callbacks differ in name *and* signature:
|
|
170
|
+
`ScFileField.onFileSelect → FileList | null`, `ScImageField.onFileSelect → File`,
|
|
171
|
+
`ScProfileImageUpdate.onUpload → File`.
|
|
172
|
+
|
|
173
|
+
---
|
|
174
|
+
|
|
175
|
+
## 4. Why to use it
|
|
176
|
+
|
|
177
|
+
- **Two states, one prop.** The dropzone→file-row swap, the ellipsis on long names,
|
|
178
|
+
the progress track and the error line are 60 lines of markup you don't write.
|
|
179
|
+
- **Theme-correct dashed border.** The dropzone border, hover fill and progress fill
|
|
180
|
+
are `--alias-*` tokens, so it survives light mode. Hand-rolled dropzones are
|
|
181
|
+
usually a hardcoded `#3e3e3e` dashed border that vanishes on white.
|
|
182
|
+
- **Keyboard reaches it at all**, unlike its siblings. The input is *clipped*, not
|
|
183
|
+
`display: none`, and it's wrapped in a real `<label>`, so it stays in the tab order
|
|
184
|
+
and Space/Enter opens the picker (there is no visible focus ring, though).
|
|
185
|
+
`ScImageField` and `ScProfileImageUpdate` both use `display: none` + a
|
|
186
|
+
`div onClick`, so they are mouse-only.
|
|
187
|
+
- **Progress is clamped**, so a runaway percentage can't blow the bar out of the card.
|
|
188
|
+
|
|
189
|
+
---
|
|
190
|
+
|
|
191
|
+
## Gotchas
|
|
192
|
+
|
|
193
|
+
**1. "or drag and drop" is not implemented.** The dropzone has no `onDrop` /
|
|
194
|
+
`onDragOver` / `onDragEnter`, and the `<input type="file">` is positioned absolutely
|
|
195
|
+
at 1×1 with `clip: rect(0,0,0,0)`, so a dropped file lands on the label and nothing
|
|
196
|
+
happens. Either accept the copy as decorative or wrap it yourself:
|
|
197
|
+
|
|
198
|
+
```tsx
|
|
199
|
+
// If you actually need drop support, own it outside the component
|
|
200
|
+
<div
|
|
201
|
+
onDragOver={(e) => e.preventDefault()}
|
|
202
|
+
onDrop={(e) => { e.preventDefault(); onFileSelect(e.dataTransfer.files); }}
|
|
203
|
+
>
|
|
204
|
+
<ScFileField fileName={name} onFileSelect={onFileSelect} onClear={clear} />
|
|
205
|
+
</div>
|
|
206
|
+
```
|
|
207
|
+
|
|
208
|
+
**2. It is stateless — nothing happens until you set `fileName`.**
|
|
209
|
+
|
|
210
|
+
```tsx
|
|
211
|
+
// WRONG — user picks a file, the dropzone stays exactly as it was
|
|
212
|
+
<ScFileField onFileSelect={(f) => upload(f?.[0])} />
|
|
213
|
+
|
|
214
|
+
// RIGHT
|
|
215
|
+
<ScFileField fileName={name} onFileSelect={(f) => { setName(f?.[0]?.name ?? null); upload(f?.[0]); }} onClear={() => setName(null)} />
|
|
216
|
+
```
|
|
217
|
+
|
|
218
|
+
**3. No `style`, no `data-*`, no `onClick`.** The interface does **not** extend
|
|
219
|
+
`React.HTMLAttributes`, unlike most of the library. `className` is your only escape
|
|
220
|
+
hatch — spacing has to come from the parent or a CSS class.
|
|
221
|
+
|
|
222
|
+
**4. `uploading` hides the clear button, so there is no cancel affordance.** If a
|
|
223
|
+
user needs to abort an upload, render your own cancel next to the field.
|
|
224
|
+
|
|
225
|
+
**5. You cannot replace a file in place.** With `fileName` set, the input is
|
|
226
|
+
unmounted. The user must ✕ first. If "Replace" matters, expose your own button that
|
|
227
|
+
calls `onClear`.
|
|
228
|
+
|
|
229
|
+
**6. The `label` is decorative, not associated.** It's a sibling `<div>`; the input
|
|
230
|
+
has no `id`, no `aria-label` and no `aria-describedby`. A screen reader announces the
|
|
231
|
+
generic file input and never hears your label, the `Accepted:` hint or the `error`.
|
|
232
|
+
Add your own labelled wrapper if the surface must be accessible.
|
|
233
|
+
|
|
234
|
+
**7. All copy is hardcoded English** — `"Click to upload"`, `"or drag and drop"`,
|
|
235
|
+
`"Accepted: "`, and the `aria-label="Clear file"`. No label props. Don't use it in a
|
|
236
|
+
localised surface without changing the DS.
|
|
237
|
+
|
|
238
|
+
**8. `multiple` and `fileName` disagree.** `multiple` lets the user pick five files;
|
|
239
|
+
the row shows one string. Join the names yourself, or the user sees only the first.
|
|
240
|
+
|
|
241
|
+
**9. `uploading` alone forces the file row.** `uploading` without `fileName` renders
|
|
242
|
+
an empty name beside a progress bar. Always set both.
|
|
243
|
+
|
|
244
|
+
**10. The file row is `overflow: hidden`.** Nothing you render can escape it — but
|
|
245
|
+
you can't render anything into it anyway; there is no slot.
|
|
246
|
+
|
|
247
|
+
---
|
|
248
|
+
|
|
249
|
+
## In the wild
|
|
250
|
+
|
|
251
|
+
_No host render site found — used by the agent runtime / composed internally._
|
|
252
|
+
|
|
253
|
+
More precisely: no host app renders it yet, and it is not an agent-runtime
|
|
254
|
+
component either — it is a form primitive waiting for a consumer. The only live
|
|
255
|
+
render is the DS's own gallery, `apps/docs/pages/form-components-preview.tsx:130`,
|
|
256
|
+
which exercises all four states. It most likely belongs on the **Catalogix feed
|
|
257
|
+
upload step** (the screen immediately before `ScMappingCard`'s Map-Attributes list)
|
|
258
|
+
and in any CXO settings flow that imports a CSV.
|
|
259
|
+
|
|
260
|
+
---
|
|
261
|
+
|
|
262
|
+
## Related
|
|
263
|
+
|
|
264
|
+
- `ScImageField` — the image sibling: thumbnail preview, `image/*` only, no progress.
|
|
265
|
+
- `ScProfileImageUpdate` — round avatar with upload/delete icon buttons.
|
|
266
|
+
- `ScProgressBar` — the standalone progress bar if you need one outside a file row.
|
|
267
|
+
- `ScTextField` + `ScFieldButton` — the "path + Browse" idiom.
|
|
268
|
+
- `ScMappingCard` — the Catalogix screen this field's output feeds into.
|