@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.
Files changed (131) hide show
  1. package/README.md +35 -18
  2. package/dist/docs/AGENTS.md +321 -0
  3. package/dist/docs/CreditWarningBanner.md +305 -0
  4. package/dist/docs/InvoiceHistoryMobile.md +222 -0
  5. package/dist/docs/ScAccess.md +259 -0
  6. package/dist/docs/ScAppCard.md +244 -0
  7. package/dist/docs/ScAppCardForCopilot.md +230 -0
  8. package/dist/docs/ScAppCardV3.md +273 -0
  9. package/dist/docs/ScAppField.md +308 -0
  10. package/dist/docs/ScAppListingCard.md +271 -0
  11. package/dist/docs/ScAppSwitchPanel.md +286 -0
  12. package/dist/docs/ScAppcardLogos.md +226 -0
  13. package/dist/docs/ScArtifaxInvite.md +262 -0
  14. package/dist/docs/ScArtifaxSidebar.md +330 -0
  15. package/dist/docs/ScAskAgentButton.md +307 -0
  16. package/dist/docs/ScBadges.md +261 -0
  17. package/dist/docs/ScBeacon.md +244 -0
  18. package/dist/docs/ScBillingHistoryHeader.md +210 -0
  19. package/dist/docs/ScBillingHistoryTableList.md +243 -0
  20. package/dist/docs/ScBillingLogsTableHeader.md +212 -0
  21. package/dist/docs/ScBillingLogsTableList.md +251 -0
  22. package/dist/docs/ScBriefCard.md +255 -0
  23. package/dist/docs/ScButton.md +251 -0
  24. package/dist/docs/ScCalendar.md +268 -0
  25. package/dist/docs/ScCalendarDateComps.md +264 -0
  26. package/dist/docs/ScCatalogixInvite.md +345 -0
  27. package/dist/docs/ScCatalogixSidebar.md +337 -0
  28. package/dist/docs/ScCatalogixStoreHeader.md +246 -0
  29. package/dist/docs/ScCatalogixStoreTableList.md +316 -0
  30. package/dist/docs/ScCheckField.md +233 -0
  31. package/dist/docs/ScCheckbox.md +272 -0
  32. package/dist/docs/ScCounter.md +235 -0
  33. package/dist/docs/ScCreditsUsageCard.md +247 -0
  34. package/dist/docs/ScCreditsUsageCardMobile.md +224 -0
  35. package/dist/docs/ScDefaultCard.md +269 -0
  36. package/dist/docs/ScDp.md +245 -0
  37. package/dist/docs/ScDrawer.md +318 -0
  38. package/dist/docs/ScFieldButton.md +255 -0
  39. package/dist/docs/ScFileField.md +268 -0
  40. package/dist/docs/ScGoogleSignIn.md +250 -0
  41. package/dist/docs/ScGuide.md +278 -0
  42. package/dist/docs/ScHDivider.md +213 -0
  43. package/dist/docs/ScHeader.md +222 -0
  44. package/dist/docs/ScImageField.md +253 -0
  45. package/dist/docs/ScInChatList.md +277 -0
  46. package/dist/docs/ScInChatMessage.md +205 -0
  47. package/dist/docs/ScInfoPopup.md +248 -0
  48. package/dist/docs/ScIntialProfileCover.md +233 -0
  49. package/dist/docs/ScInvoiceHistoryMobile.md +187 -0
  50. package/dist/docs/ScLogoUnit.md +232 -0
  51. package/dist/docs/ScMappingCard.md +241 -0
  52. package/dist/docs/ScMediaApproval.md +301 -0
  53. package/dist/docs/ScMediaSelect.md +310 -0
  54. package/dist/docs/ScMenuOptions.md +308 -0
  55. package/dist/docs/ScMobileBottomAction.md +252 -0
  56. package/dist/docs/ScMobileTopNav.md +279 -0
  57. package/dist/docs/ScModal.md +291 -0
  58. package/dist/docs/ScOnlyField.md +302 -0
  59. package/dist/docs/ScOnlyIcon.md +213 -0
  60. package/dist/docs/ScPagination.md +284 -0
  61. package/dist/docs/ScPairtext.md +287 -0
  62. package/dist/docs/ScPendingAction.md +238 -0
  63. package/dist/docs/ScPhtogenixInvite.md +275 -0
  64. package/dist/docs/ScPlanCard.md +302 -0
  65. package/dist/docs/ScPlanComparison.md +264 -0
  66. package/dist/docs/ScPlanDetailsCard.md +246 -0
  67. package/dist/docs/ScPlanDetailsCardMobile.md +240 -0
  68. package/dist/docs/ScPopUpMenu.md +224 -0
  69. package/dist/docs/ScProfile.md +234 -0
  70. package/dist/docs/ScProfileImageUpdate.md +261 -0
  71. package/dist/docs/ScProfileOptions.md +245 -0
  72. package/dist/docs/ScProfilePopup.md +396 -0
  73. package/dist/docs/ScProfileSettingsComp.md +250 -0
  74. package/dist/docs/ScProfileV2Mobile.md +216 -0
  75. package/dist/docs/ScProgressBar.md +267 -0
  76. package/dist/docs/ScQuickPrompt.md +277 -0
  77. package/dist/docs/ScRadio.md +228 -0
  78. package/dist/docs/ScReferralCardMobile.md +226 -0
  79. package/dist/docs/ScReferralTableHeader.md +260 -0
  80. package/dist/docs/ScReferralTableList.md +293 -0
  81. package/dist/docs/ScRole.md +226 -0
  82. package/dist/docs/ScRoleMobile.md +199 -0
  83. package/dist/docs/ScSelect.md +270 -0
  84. package/dist/docs/ScSelection.md +256 -0
  85. package/dist/docs/ScSelectionList.md +272 -0
  86. package/dist/docs/ScSelectionPill.md +240 -0
  87. package/dist/docs/ScSelectionPillGroup.md +302 -0
  88. package/dist/docs/ScSettingsNav.md +212 -0
  89. package/dist/docs/ScSettingsTabComp.md +260 -0
  90. package/dist/docs/ScSideBarLogoUnit.md +340 -0
  91. package/dist/docs/ScSidebar.md +243 -0
  92. package/dist/docs/ScSidebarIcons.md +232 -0
  93. package/dist/docs/ScSidebarMenu.md +283 -0
  94. package/dist/docs/ScSidebarProfile.md +231 -0
  95. package/dist/docs/ScSidebarSwitchMenu.md +258 -0
  96. package/dist/docs/ScSlider.md +194 -0
  97. package/dist/docs/ScStoreCard.md +252 -0
  98. package/dist/docs/ScStrLogo.md +253 -0
  99. package/dist/docs/ScStreamoidWordmark.md +302 -0
  100. package/dist/docs/ScSubAgent.md +226 -0
  101. package/dist/docs/ScTabComp.md +308 -0
  102. package/dist/docs/ScTabField.md +258 -0
  103. package/dist/docs/ScTabSwitcher.md +307 -0
  104. package/dist/docs/ScTableHeader.md +261 -0
  105. package/dist/docs/ScTableList.md +301 -0
  106. package/dist/docs/ScTableListMobile.md +282 -0
  107. package/dist/docs/ScTabs.md +268 -0
  108. package/dist/docs/ScTaxonomyPill.md +263 -0
  109. package/dist/docs/ScTextArea.md +259 -0
  110. package/dist/docs/ScTextField.md +324 -0
  111. package/dist/docs/ScThinkingStepIcon.md +249 -0
  112. package/dist/docs/ScTodoList.md +288 -0
  113. package/dist/docs/ScToggleSwitch.md +229 -0
  114. package/dist/docs/ScUsageHistoryMobile.md +194 -0
  115. package/dist/docs/ScVDivider.md +215 -0
  116. package/dist/docs/ScValueMappingL1.md +256 -0
  117. package/dist/docs/ScVersion.md +251 -0
  118. package/dist/docs/ScWorkspace.md +233 -0
  119. package/dist/docs/ScWorkspaceCard.md +234 -0
  120. package/dist/docs/ScWorkspaceSettingsMobile.md +265 -0
  121. package/dist/docs/ScWorkspaceSwitchCard.md +314 -0
  122. package/dist/docs/ScWorkspaceSwitchMobile.md +241 -0
  123. package/dist/docs/ScWorkspaceSwitchMobileV2.md +278 -0
  124. package/dist/docs/StreamoidSidebar.md +403 -0
  125. package/dist/docs/StreamoidWorkspaceSwitcher.md +307 -0
  126. package/dist/docs/UsageHistoryMobile.md +235 -0
  127. package/dist/docs/components.json +4849 -0
  128. package/dist/index.css +36 -36
  129. package/dist/index.d.mts +10 -0
  130. package/dist/index.d.ts +10 -0
  131. package/package.json +3 -2
@@ -0,0 +1,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.