@streamoid/ui 0.6.17 → 0.6.18
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +35 -18
- package/dist/docs/AGENTS.md +321 -0
- package/dist/docs/CreditWarningBanner.md +305 -0
- package/dist/docs/InvoiceHistoryMobile.md +222 -0
- package/dist/docs/ScAccess.md +259 -0
- package/dist/docs/ScAppCard.md +244 -0
- package/dist/docs/ScAppCardForCopilot.md +230 -0
- package/dist/docs/ScAppCardV3.md +273 -0
- package/dist/docs/ScAppField.md +308 -0
- package/dist/docs/ScAppListingCard.md +271 -0
- package/dist/docs/ScAppSwitchPanel.md +286 -0
- package/dist/docs/ScAppcardLogos.md +226 -0
- package/dist/docs/ScArtifaxInvite.md +262 -0
- package/dist/docs/ScArtifaxSidebar.md +330 -0
- package/dist/docs/ScAskAgentButton.md +307 -0
- package/dist/docs/ScBadges.md +261 -0
- package/dist/docs/ScBeacon.md +244 -0
- package/dist/docs/ScBillingHistoryHeader.md +210 -0
- package/dist/docs/ScBillingHistoryTableList.md +243 -0
- package/dist/docs/ScBillingLogsTableHeader.md +212 -0
- package/dist/docs/ScBillingLogsTableList.md +251 -0
- package/dist/docs/ScBriefCard.md +255 -0
- package/dist/docs/ScButton.md +251 -0
- package/dist/docs/ScCalendar.md +268 -0
- package/dist/docs/ScCalendarDateComps.md +264 -0
- package/dist/docs/ScCatalogixInvite.md +345 -0
- package/dist/docs/ScCatalogixSidebar.md +337 -0
- package/dist/docs/ScCatalogixStoreHeader.md +246 -0
- package/dist/docs/ScCatalogixStoreTableList.md +316 -0
- package/dist/docs/ScCheckField.md +233 -0
- package/dist/docs/ScCheckbox.md +272 -0
- package/dist/docs/ScCounter.md +235 -0
- package/dist/docs/ScCreditsUsageCard.md +247 -0
- package/dist/docs/ScCreditsUsageCardMobile.md +224 -0
- package/dist/docs/ScDefaultCard.md +269 -0
- package/dist/docs/ScDp.md +245 -0
- package/dist/docs/ScDrawer.md +318 -0
- package/dist/docs/ScFieldButton.md +255 -0
- package/dist/docs/ScFileField.md +268 -0
- package/dist/docs/ScGoogleSignIn.md +250 -0
- package/dist/docs/ScGuide.md +278 -0
- package/dist/docs/ScHDivider.md +213 -0
- package/dist/docs/ScHeader.md +222 -0
- package/dist/docs/ScImageField.md +253 -0
- package/dist/docs/ScInChatList.md +277 -0
- package/dist/docs/ScInChatMessage.md +205 -0
- package/dist/docs/ScInfoPopup.md +248 -0
- package/dist/docs/ScIntialProfileCover.md +233 -0
- package/dist/docs/ScInvoiceHistoryMobile.md +187 -0
- package/dist/docs/ScLogoUnit.md +232 -0
- package/dist/docs/ScMappingCard.md +241 -0
- package/dist/docs/ScMediaApproval.md +301 -0
- package/dist/docs/ScMediaSelect.md +310 -0
- package/dist/docs/ScMenuOptions.md +308 -0
- package/dist/docs/ScMobileBottomAction.md +252 -0
- package/dist/docs/ScMobileTopNav.md +279 -0
- package/dist/docs/ScModal.md +291 -0
- package/dist/docs/ScOnlyField.md +302 -0
- package/dist/docs/ScOnlyIcon.md +213 -0
- package/dist/docs/ScPagination.md +284 -0
- package/dist/docs/ScPairtext.md +287 -0
- package/dist/docs/ScPendingAction.md +238 -0
- package/dist/docs/ScPhtogenixInvite.md +275 -0
- package/dist/docs/ScPlanCard.md +302 -0
- package/dist/docs/ScPlanComparison.md +264 -0
- package/dist/docs/ScPlanDetailsCard.md +246 -0
- package/dist/docs/ScPlanDetailsCardMobile.md +240 -0
- package/dist/docs/ScPopUpMenu.md +224 -0
- package/dist/docs/ScProfile.md +234 -0
- package/dist/docs/ScProfileImageUpdate.md +261 -0
- package/dist/docs/ScProfileOptions.md +245 -0
- package/dist/docs/ScProfilePopup.md +396 -0
- package/dist/docs/ScProfileSettingsComp.md +250 -0
- package/dist/docs/ScProfileV2Mobile.md +216 -0
- package/dist/docs/ScProgressBar.md +267 -0
- package/dist/docs/ScQuickPrompt.md +277 -0
- package/dist/docs/ScRadio.md +228 -0
- package/dist/docs/ScReferralCardMobile.md +226 -0
- package/dist/docs/ScReferralTableHeader.md +260 -0
- package/dist/docs/ScReferralTableList.md +293 -0
- package/dist/docs/ScRole.md +226 -0
- package/dist/docs/ScRoleMobile.md +199 -0
- package/dist/docs/ScSelect.md +270 -0
- package/dist/docs/ScSelection.md +256 -0
- package/dist/docs/ScSelectionList.md +272 -0
- package/dist/docs/ScSelectionPill.md +240 -0
- package/dist/docs/ScSelectionPillGroup.md +302 -0
- package/dist/docs/ScSettingsNav.md +212 -0
- package/dist/docs/ScSettingsTabComp.md +260 -0
- package/dist/docs/ScSideBarLogoUnit.md +340 -0
- package/dist/docs/ScSidebar.md +243 -0
- package/dist/docs/ScSidebarIcons.md +232 -0
- package/dist/docs/ScSidebarMenu.md +283 -0
- package/dist/docs/ScSidebarProfile.md +231 -0
- package/dist/docs/ScSidebarSwitchMenu.md +258 -0
- package/dist/docs/ScSlider.md +194 -0
- package/dist/docs/ScStoreCard.md +252 -0
- package/dist/docs/ScStrLogo.md +253 -0
- package/dist/docs/ScStreamoidWordmark.md +302 -0
- package/dist/docs/ScSubAgent.md +226 -0
- package/dist/docs/ScTabComp.md +308 -0
- package/dist/docs/ScTabField.md +258 -0
- package/dist/docs/ScTabSwitcher.md +307 -0
- package/dist/docs/ScTableHeader.md +261 -0
- package/dist/docs/ScTableList.md +301 -0
- package/dist/docs/ScTableListMobile.md +282 -0
- package/dist/docs/ScTabs.md +268 -0
- package/dist/docs/ScTaxonomyPill.md +263 -0
- package/dist/docs/ScTextArea.md +259 -0
- package/dist/docs/ScTextField.md +324 -0
- package/dist/docs/ScThinkingStepIcon.md +249 -0
- package/dist/docs/ScTodoList.md +288 -0
- package/dist/docs/ScToggleSwitch.md +229 -0
- package/dist/docs/ScUsageHistoryMobile.md +194 -0
- package/dist/docs/ScVDivider.md +215 -0
- package/dist/docs/ScValueMappingL1.md +256 -0
- package/dist/docs/ScVersion.md +251 -0
- package/dist/docs/ScWorkspace.md +233 -0
- package/dist/docs/ScWorkspaceCard.md +234 -0
- package/dist/docs/ScWorkspaceSettingsMobile.md +265 -0
- package/dist/docs/ScWorkspaceSwitchCard.md +314 -0
- package/dist/docs/ScWorkspaceSwitchMobile.md +241 -0
- package/dist/docs/ScWorkspaceSwitchMobileV2.md +278 -0
- package/dist/docs/StreamoidSidebar.md +403 -0
- package/dist/docs/StreamoidWorkspaceSwitcher.md +307 -0
- package/dist/docs/UsageHistoryMobile.md +235 -0
- package/dist/docs/components.json +4849 -0
- package/dist/index.css +36 -36
- package/dist/index.d.mts +10 -0
- package/dist/index.d.ts +10 -0
- package/package.json +3 -2
|
@@ -0,0 +1,259 @@
|
|
|
1
|
+
---
|
|
2
|
+
component: ScTextArea
|
|
3
|
+
package: "@streamoid/ui"
|
|
4
|
+
category: forms
|
|
5
|
+
status: stable
|
|
6
|
+
renders: div > (label?) + div > textarea
|
|
7
|
+
tags: [textarea, multiline, description, long-text, form, resize, error-state]
|
|
8
|
+
related: [ScTextField, ScOnlyField, ScSelect, ScCheckField]
|
|
9
|
+
do_not_confuse_with: [ScTextField, ScOnlyField]
|
|
10
|
+
used_by: [catalogix]
|
|
11
|
+
---
|
|
12
|
+
|
|
13
|
+
# ScTextArea
|
|
14
|
+
|
|
15
|
+
**The multi-line text field.** Same pill chrome as `ScTextField` — tertiary label
|
|
16
|
+
row, tokenised fill/border, focus ring — wrapped around a vertically resizable
|
|
17
|
+
`<textarea>` that starts at 72 px tall. It is the **only** text field in the
|
|
18
|
+
library with real `error` and `disabled` styling (`ScTextField` and `ScOnlyField`
|
|
19
|
+
accept those `state` values and paint nothing).
|
|
20
|
+
|
|
21
|
+
## TL;DR for agents
|
|
22
|
+
|
|
23
|
+
- **Reach for it when:** the user types more than one line — descriptions,
|
|
24
|
+
product copy, notes, prompts, feedback.
|
|
25
|
+
- **Don't reach for it when:** the value is a single line (→ `ScTextField`), it is
|
|
26
|
+
a bare search input (→ `ScOnlyField`), or it is a choice (→ `ScSelect`).
|
|
27
|
+
- **Four things that will bite you:**
|
|
28
|
+
1. `label` renders nothing unless you also pass `labelGroup="label"`.
|
|
29
|
+
2. `labelGroup="text"` renders the label plus the hardcoded string `"info"` on
|
|
30
|
+
the right — `info` defaults to that literal word.
|
|
31
|
+
3. `placeholderFilled` defaults to `"Placeholder"`.
|
|
32
|
+
4. There are **no icons, no slots and no style escape hatches** — `style` goes
|
|
33
|
+
to the root wrapper only. `ScTextField`'s `containerStyle`/`inputStyle` do
|
|
34
|
+
not exist here.
|
|
35
|
+
|
|
36
|
+
---
|
|
37
|
+
|
|
38
|
+
## 1. How to use it
|
|
39
|
+
|
|
40
|
+
### Import
|
|
41
|
+
|
|
42
|
+
```tsx
|
|
43
|
+
import { ScTextArea } from "@streamoid/ui";
|
|
44
|
+
import "@streamoid/ui/dist/index.css"; // once, at your app root
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
### Minimal usage
|
|
48
|
+
|
|
49
|
+
```tsx
|
|
50
|
+
<ScTextArea
|
|
51
|
+
placeholderFilled="E.g.: Info"
|
|
52
|
+
value={description}
|
|
53
|
+
onChange={(e) => setDescription(e.target.value)}
|
|
54
|
+
/>
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
### Props
|
|
58
|
+
|
|
59
|
+
Extends `React.TextareaHTMLAttributes<HTMLTextAreaElement>`; everything not
|
|
60
|
+
listed below (`value`, `onChange`, `rows`, `maxLength`, `disabled`, `readOnly`,
|
|
61
|
+
`name`) is spread onto the `<textarea>`.
|
|
62
|
+
|
|
63
|
+
| Prop | Type | Default | Notes |
|
|
64
|
+
|---|---|---|---|
|
|
65
|
+
| `label` | `string` | `""` | Only rendered when `labelGroup` is truthy. A trailing space is appended in the markup. |
|
|
66
|
+
| `labelGroup` | `""` \| `"label"` \| `"text"` | `""` | ⚠️ Gates the label row. `""` → no row. `"label"` → label only. `"text"` → label + `info` on the right. |
|
|
67
|
+
| `info` | `string` | `"info"` | ⚠️ Real default, and it is the literal word "info". Only rendered when `labelGroup="text"`. |
|
|
68
|
+
| `placeholderFilled` | `string` | `"Placeholder"` | ⚠️ Real default. A native `placeholder` prop overrides it (spread later). |
|
|
69
|
+
| `state` | `"default"` \| `"active"` \| `"error"` \| `"disabled"` \| `"filled"` | *derived* | Omit and it derives: `disabled` prop → focused → non-empty `value` → `default`. **All five have real styles here.** |
|
|
70
|
+
| `className` | `string` | – | Appended to the root class list. |
|
|
71
|
+
| `style` | `CSSProperties` | – | Applied to the **root wrapper**, not the textarea or the container. |
|
|
72
|
+
|
|
73
|
+
#### What each `state` paints
|
|
74
|
+
|
|
75
|
+
| `state` | Container | Text |
|
|
76
|
+
|---|---|---|
|
|
77
|
+
| `default` | base fill, subtle border | muted |
|
|
78
|
+
| `filled` | raised fill, default border | primary |
|
|
79
|
+
| `active` | raised fill, **strong** 1 px border | primary |
|
|
80
|
+
| `error` | **red 1 px border** (`text-and-icons-error`) | muted |
|
|
81
|
+
| `disabled` | base fill, subtle border | disabled colour, `cursor: not-allowed`, `resize: none` |
|
|
82
|
+
|
|
83
|
+
### Recipes
|
|
84
|
+
|
|
85
|
+
```tsx
|
|
86
|
+
// Labelled description field
|
|
87
|
+
<ScTextArea
|
|
88
|
+
label="Description"
|
|
89
|
+
labelGroup="label"
|
|
90
|
+
placeholderFilled="What is this attribute for?"
|
|
91
|
+
value={description}
|
|
92
|
+
maxLength={500}
|
|
93
|
+
onChange={(e) => setDescription(e.target.value)}
|
|
94
|
+
/>
|
|
95
|
+
|
|
96
|
+
// Validation — this is the one text field where `state="error"` actually shows
|
|
97
|
+
<ScTextArea
|
|
98
|
+
label="Prompt"
|
|
99
|
+
labelGroup="label"
|
|
100
|
+
state={touched && !value ? "error" : undefined} // undefined ⇒ auto-derive
|
|
101
|
+
value={value}
|
|
102
|
+
onChange={(e) => setValue(e.target.value)}
|
|
103
|
+
/>
|
|
104
|
+
|
|
105
|
+
// Grow it with `rows` — it is spread onto the <textarea>, and any value above the
|
|
106
|
+
// stylesheet's min-height (4rem ≈ 3 rows) wins. `style` lands on the ROOT wrapper,
|
|
107
|
+
// so it cannot make the box taller; use `className` if you need to restyle the box.
|
|
108
|
+
<ScTextArea
|
|
109
|
+
value={body}
|
|
110
|
+
onChange={(e) => setBody(e.target.value)}
|
|
111
|
+
rows={8}
|
|
112
|
+
/>
|
|
113
|
+
|
|
114
|
+
// Read-only / disabled
|
|
115
|
+
<ScTextArea value={generatedCopy} disabled /> {/* derives state="disabled" */}
|
|
116
|
+
```
|
|
117
|
+
|
|
118
|
+
---
|
|
119
|
+
|
|
120
|
+
## 2. Where to use it
|
|
121
|
+
|
|
122
|
+
- **Catalogix product editing** — `ProductsListTableV2/EditTextAttribute` uses it
|
|
123
|
+
for "Active Content" (long product titles/descriptions).
|
|
124
|
+
- **Catalogix taxonomy modals** — the collapsible "Description" block in
|
|
125
|
+
`AddHierarchyModal` and `TaxonomyAttributesModal`.
|
|
126
|
+
- Anywhere a form stacks `ScTextField` rows and one of them is long-form: put the
|
|
127
|
+
`ScTextArea` last in the stack so its resize handle doesn't push siblings.
|
|
128
|
+
|
|
129
|
+
Catalogix is the only host currently rendering it.
|
|
130
|
+
|
|
131
|
+
---
|
|
132
|
+
|
|
133
|
+
## 3. When to use it
|
|
134
|
+
|
|
135
|
+
### Use it when
|
|
136
|
+
|
|
137
|
+
- The value is genuinely multi-line, or long enough that a 48 px single-line
|
|
138
|
+
field would hide most of it.
|
|
139
|
+
- You want a **visible error border** driven by a prop — the other text fields
|
|
140
|
+
don't have one.
|
|
141
|
+
- You want the user to be able to **drag the field taller** (`resize: vertical`
|
|
142
|
+
is on by default).
|
|
143
|
+
|
|
144
|
+
### Don't use it — reach for this instead
|
|
145
|
+
|
|
146
|
+
| Situation | Use instead |
|
|
147
|
+
|---|---|
|
|
148
|
+
| One line of text with a label, `*`, info popover or helper line | `ScTextField` |
|
|
149
|
+
| Bare/compact input, search box, 40 px or 32 px height | `ScOnlyField` |
|
|
150
|
+
| Choose one of N | `ScSelect` (or `ScTabField` for 2–4 inline) |
|
|
151
|
+
| Rich text / markdown editing | not in the DS — bring your own editor and reuse the tokens |
|
|
152
|
+
| Chat composer | the agent runtime owns the composer, not `@streamoid/ui` |
|
|
153
|
+
| Read-only long text | plain markup with the `--text-text-sm-regular-*` tokens; a disabled textarea reads as broken |
|
|
154
|
+
|
|
155
|
+
### Don't confuse with
|
|
156
|
+
|
|
157
|
+
| You may actually want | Not this |
|
|
158
|
+
|---|---|
|
|
159
|
+
| `ScTextField` — single line, forwards a ref, has `required`/`info`/`desc`/`leftSlot`/`rightSlot` and per-layer style hatches | `ScTextArea` has none of those, but is the only text field whose `error`/`disabled` states paint |
|
|
160
|
+
| `ScOnlyField` — no label at all, `onInputChange(value)` instead of `onChange(event)` | `ScTextArea` uses the native `onChange(event)` |
|
|
161
|
+
| `ScTextField` + `tall` — grows the container around a **single-line input** | that is not a textarea; text still scrolls horizontally |
|
|
162
|
+
|
|
163
|
+
---
|
|
164
|
+
|
|
165
|
+
## 4. Why to use it
|
|
166
|
+
|
|
167
|
+
- **It matches `ScTextField` pixel-for-pixel** on fill, border, radius, label
|
|
168
|
+
typography and focus ring, so a form mixing the two looks like one form.
|
|
169
|
+
- **A complete state machine.** `error` and `disabled` are implemented (red
|
|
170
|
+
border; disabled colour + `cursor: not-allowed` + `resize: none`), which is not
|
|
171
|
+
true of `ScTextField` or `ScOnlyField`.
|
|
172
|
+
- **Auto-derived focus/filled.** You get the "raised when it has content" skin
|
|
173
|
+
without tracking focus yourself, and your `onFocus`/`onBlur` still fire.
|
|
174
|
+
- **Sensible resize defaults** — `resize: vertical` only, so the user can't drag
|
|
175
|
+
it wider and break your layout.
|
|
176
|
+
|
|
177
|
+
---
|
|
178
|
+
|
|
179
|
+
## Gotchas
|
|
180
|
+
|
|
181
|
+
**1. `label` alone renders nothing.** The row is gated purely on `labelGroup`.
|
|
182
|
+
|
|
183
|
+
```tsx
|
|
184
|
+
// WRONG — no label appears
|
|
185
|
+
<ScTextArea label="Description" value={v} onChange={onChange} />
|
|
186
|
+
|
|
187
|
+
// RIGHT
|
|
188
|
+
<ScTextArea label="Description" labelGroup="label" value={v} onChange={onChange} />
|
|
189
|
+
```
|
|
190
|
+
|
|
191
|
+
**2. `labelGroup="text"` ships the word "info".** `info` defaults to the literal
|
|
192
|
+
string `"info"`, right-aligned and muted. Pass real copy (a character count, a
|
|
193
|
+
hint) or don't use `"text"`.
|
|
194
|
+
|
|
195
|
+
```tsx
|
|
196
|
+
// WRONG — renders "info" next to the label
|
|
197
|
+
<ScTextArea label="Description" labelGroup="text" />
|
|
198
|
+
|
|
199
|
+
// RIGHT
|
|
200
|
+
<ScTextArea label="Description" labelGroup="text" info={`${value.length}/500`} />
|
|
201
|
+
```
|
|
202
|
+
|
|
203
|
+
**3. `labelGroup="none"` is a type error** even though hosts pass it. `"none"` is
|
|
204
|
+
truthy, so it renders an empty label row that the stylesheet then hides via
|
|
205
|
+
`.label-group-none { display: none }`. It happens to look right — but the
|
|
206
|
+
supported way to hide the label is to **omit `labelGroup` entirely**.
|
|
207
|
+
|
|
208
|
+
**4. `placeholderFilled` defaults to `"Placeholder"`.** Pass `placeholderFilled=""`
|
|
209
|
+
if you want no placeholder at all (Catalogix's edit-attribute screen does).
|
|
210
|
+
|
|
211
|
+
**5. No style escape hatches.** `style` is applied to the root wrapper only;
|
|
212
|
+
there is no `containerStyle` or `inputStyle` (those are `ScTextField`-only).
|
|
213
|
+
A `style={{ height }}` on the root does **not** grow the box (the container is a
|
|
214
|
+
`flex-shrink: 0` child with its own `min-height`) — size it with `rows`, or with a
|
|
215
|
+
`className` rule that targets the container/textarea, and note that CSS-module
|
|
216
|
+
specificity may need an attribute selector to win — see `ScTextField`'s "Why"
|
|
217
|
+
section for the host workaround.
|
|
218
|
+
|
|
219
|
+
**6. No ref, no icons, no slots.** There is no `tfGroup`, no `tfLeftIcon`, no
|
|
220
|
+
`leftSlot`/`rightSlot`, and no forwarded ref. If a spec puts an icon or a
|
|
221
|
+
character counter inside the box, you have to wrap the component yourself.
|
|
222
|
+
|
|
223
|
+
**7. `rows` fights the stylesheet.** The `<textarea>` carries
|
|
224
|
+
`min-height: 4rem` and the container `min-height: 4.5rem`, so `rows` values below
|
|
225
|
+
about 3 are ignored. Larger values do apply (there is no `height` rule) — see the
|
|
226
|
+
recipe.
|
|
227
|
+
|
|
228
|
+
**8. Derived `filled` needs a controlled `value`.** The derivation reads
|
|
229
|
+
`props.value`; an uncontrolled textarea with `defaultValue` stays in the
|
|
230
|
+
`default` skin with muted text.
|
|
231
|
+
|
|
232
|
+
---
|
|
233
|
+
|
|
234
|
+
## In the wild
|
|
235
|
+
|
|
236
|
+
```jsx
|
|
237
|
+
// catalogix/dashboard app/containers/Products/ProductsListTableV2/EditTextAttribute/index.jsx:396
|
|
238
|
+
<ScTextArea
|
|
239
|
+
key={defaultValue}
|
|
240
|
+
className={styles["active-textarea"]}
|
|
241
|
+
placeholderFilled=""
|
|
242
|
+
value={selectedValue}
|
|
243
|
+
onChange={(e) => {
|
|
244
|
+
setSelectedValue(e.target.value);
|
|
245
|
+
if (optionInView === "similar") {
|
|
246
|
+
throttle("get_similar_title", getCurrentStoreSimilarTitles);
|
|
247
|
+
}
|
|
248
|
+
}}
|
|
249
|
+
/>
|
|
250
|
+
```
|
|
251
|
+
|
|
252
|
+
---
|
|
253
|
+
|
|
254
|
+
## Related
|
|
255
|
+
|
|
256
|
+
- `ScTextField` — the single-line sibling; richer API, weaker state styling.
|
|
257
|
+
- `ScOnlyField` — unlabelled/compact input with `size` variants.
|
|
258
|
+
- `ScSelect` — when the long text is really a choice.
|
|
259
|
+
- `ScCheckField` / `ScTabField` — the other field types you'll stack beside it.
|
|
@@ -0,0 +1,324 @@
|
|
|
1
|
+
---
|
|
2
|
+
component: ScTextField
|
|
3
|
+
package: "@streamoid/ui"
|
|
4
|
+
category: forms
|
|
5
|
+
status: stable
|
|
6
|
+
renders: div > (label?) + div > input
|
|
7
|
+
tags: [input, text, textfield, form, label, placeholder, required, helper, desc, info, slot]
|
|
8
|
+
related: [ScOnlyField, ScTextArea, ScSelect, ScFieldButton, ScTabField, ScCheckField, ScFileField, ScInfoPopup]
|
|
9
|
+
do_not_confuse_with: [ScOnlyField, ScTextArea, ScAppField, ScOnlyIcon]
|
|
10
|
+
used_by: [cxo, catalogix, photogenix, artifax]
|
|
11
|
+
---
|
|
12
|
+
|
|
13
|
+
# ScTextField
|
|
14
|
+
|
|
15
|
+
**The default single-line text input for every Streamoid form.** Renders a
|
|
16
|
+
tertiary-coloured label row (optional `*`, optional info popover), a 48 px pill
|
|
17
|
+
container holding a borderless `<input>` with up to two icons/slots, and an
|
|
18
|
+
optional helper line underneath. It is the only field in the library that
|
|
19
|
+
forwards a ref to its `<input>`.
|
|
20
|
+
|
|
21
|
+
## TL;DR for agents
|
|
22
|
+
|
|
23
|
+
- **Reach for it when:** you need a labelled single-line input inside a form,
|
|
24
|
+
modal or settings panel.
|
|
25
|
+
- **Don't reach for it when:** you want a bare input with no label and size
|
|
26
|
+
variants (→ `ScOnlyField`), multi-line text (→ `ScTextArea`), or a dropdown
|
|
27
|
+
(→ `ScSelect`).
|
|
28
|
+
- **Five things that will bite you:**
|
|
29
|
+
1. `tfGroup` defaults to `"icon-right"` and `tfRightIcon` defaults to a
|
|
30
|
+
**lightning bolt** — forget both and you ship a ⚡. Pass `tfGroup="none"`.
|
|
31
|
+
2. `placeholderFilled` defaults to the literal string `"Placeholder"`.
|
|
32
|
+
3. `onClick` / `onKeyDown` / `aria-*` land on the **`<input>`**, not the root.
|
|
33
|
+
`className` / `style` land on the **root**. Wrap the field in your own div
|
|
34
|
+
if you need whole-field click behaviour.
|
|
35
|
+
4. `state="error"` and `state="disabled"` have **no styles in the stylesheet**.
|
|
36
|
+
No red border, no grey-out — and worse, they force the input text to the
|
|
37
|
+
*muted* colour.
|
|
38
|
+
5. The field container is `overflow: hidden`, so anything absolutely
|
|
39
|
+
positioned inside `rightSlot` (a menu, a tooltip) gets clipped.
|
|
40
|
+
|
|
41
|
+
---
|
|
42
|
+
|
|
43
|
+
## 1. How to use it
|
|
44
|
+
|
|
45
|
+
### Import
|
|
46
|
+
|
|
47
|
+
```tsx
|
|
48
|
+
import { ScTextField } from "@streamoid/ui";
|
|
49
|
+
import "@streamoid/ui/dist/index.css"; // once, at your app root
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
### Minimal usage
|
|
53
|
+
|
|
54
|
+
```tsx
|
|
55
|
+
<ScTextField
|
|
56
|
+
label="Full Name"
|
|
57
|
+
labelGroup="label"
|
|
58
|
+
placeholderFilled="Enter your name"
|
|
59
|
+
tfGroup="none"
|
|
60
|
+
value={fullName}
|
|
61
|
+
onChange={(e) => setFullName(e.target.value)}
|
|
62
|
+
/>
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
### Props
|
|
66
|
+
|
|
67
|
+
Extends `React.InputHTMLAttributes<HTMLInputElement>`; everything not listed
|
|
68
|
+
below is spread onto the `<input>`. Ref is forwarded to the `<input>`.
|
|
69
|
+
|
|
70
|
+
| Prop | Type | Default | Notes |
|
|
71
|
+
|---|---|---|---|
|
|
72
|
+
| `label` | `string` | `""` | Label text. Only rendered when the label row is shown — see `labelGroup`. |
|
|
73
|
+
| `labelGroup` | `""` \| `"label"` \| `"text"` \| `"icon"` | `""` | ⚠️ Gates the whole label row. `""` → no row at all. `"label"` → label only. `"text"` → label + the hardcoded string `"info"` on the right. `"icon"` → label + a bolt icon. |
|
|
74
|
+
| `tfGroup` | `"icon-right"` \| `"icon-left"` \| `"icon-left-right"` \| `"none"` | `"icon-right"` | ⚠️ Which built-in icons render. **`"none"` is what most call sites want.** |
|
|
75
|
+
| `tfRightIcon` | `JSX.Element` | `<SiconBolt />` | ⚠️ Real default. Rendered for `icon-right` / `icon-left-right`. Not size-normalised — pass `size={24}`. |
|
|
76
|
+
| `tfLeftIcon` | `JSX.Element` | `<SiconBolt />` | ⚠️ Real default. Rendered for `icon-left` / `icon-left-right`. |
|
|
77
|
+
| `placeholderFilled` | `string` | `"Placeholder"` | ⚠️ Real default. Overridden by a native `placeholder` prop (spread later). |
|
|
78
|
+
| `state` | `"default"` \| `"active"` \| `"error"` \| `"disabled"` \| `"filled"` | *derived* | Omit it and it is derived: `disabled` → focused → non-empty `value` → `"default"`. Only `active` / `filled` / hover have styles. |
|
|
79
|
+
| `required` | `boolean` | – | Adds a red `*` after the label **and forces the label row to render**. |
|
|
80
|
+
| `info` | `ReactNode` | – | Content for an `ScInfoPopup` beside the label. Also forces the label row to render. |
|
|
81
|
+
| `desc` | `ReactNode` | – | Helper text under the field (12 px, muted). Skipped when `null`/`undefined`/`false`. |
|
|
82
|
+
| `tall` | `boolean` | – | Container becomes `min-height: 5rem`, top-aligned. Still a single-line `<input>`. |
|
|
83
|
+
| `leftSlot` | `ReactNode` | – | Arbitrary adornment before the left icon. |
|
|
84
|
+
| `rightSlot` | `ReactNode` | – | Arbitrary adornment after the right icon. |
|
|
85
|
+
| `className` | `string` | – | Appended to the **root** class list. |
|
|
86
|
+
| `rootClassName` / `rootStyle` | `string` / `CSSProperties` | – | Extra escape hatches on the root. `style` is merged first, then `rootStyle`. |
|
|
87
|
+
| `containerClassName` / `containerStyle` | `string` / `CSSProperties` | – | On the 48 px field container. Use this to change height/radius/padding. |
|
|
88
|
+
| `inputStyle` | `CSSProperties` | – | On the `<input>` itself. |
|
|
89
|
+
| `descClassName` / `descStyle` | `string` / `CSSProperties` | – | On the helper line. |
|
|
90
|
+
|
|
91
|
+
#### What each `state` actually paints
|
|
92
|
+
|
|
93
|
+
| `state` | Container | Input text |
|
|
94
|
+
|---|---|---|
|
|
95
|
+
| `default` | base fill, subtle border | muted |
|
|
96
|
+
| `filled` | raised fill, default border | primary |
|
|
97
|
+
| `active` | raised fill, **strong** 1 px border | primary |
|
|
98
|
+
| `error` | **nothing** (no rule exists) | muted |
|
|
99
|
+
| `disabled` | **nothing** (no rule exists) | muted, and `:hover` still lights up |
|
|
100
|
+
|
|
101
|
+
### Recipes
|
|
102
|
+
|
|
103
|
+
```tsx
|
|
104
|
+
// Read-only field with a lock — the CXO settings idiom
|
|
105
|
+
<ScTextField
|
|
106
|
+
label="Email ID"
|
|
107
|
+
labelGroup="label"
|
|
108
|
+
value={user?.email ?? ""}
|
|
109
|
+
state="filled" // NOT "disabled" — see the table
|
|
110
|
+
tfGroup="icon-right"
|
|
111
|
+
tfRightIcon={<SiconLock size={24} color="var(--alias-text-and-icons-muted)" />}
|
|
112
|
+
readOnly
|
|
113
|
+
/>
|
|
114
|
+
|
|
115
|
+
// Required field with an info popover and a helper line
|
|
116
|
+
<ScTextField
|
|
117
|
+
label="Store name"
|
|
118
|
+
labelGroup="label"
|
|
119
|
+
required
|
|
120
|
+
info="Shown to shoppers on your storefront."
|
|
121
|
+
desc="3–40 characters, letters and spaces only."
|
|
122
|
+
tfGroup="none"
|
|
123
|
+
value={name}
|
|
124
|
+
onChange={(e) => setName(e.target.value)}
|
|
125
|
+
/>
|
|
126
|
+
|
|
127
|
+
// Dropdown trigger: the field is read-only, YOUR wrapper owns the click
|
|
128
|
+
// (onClick on ScTextField would only fire on the inner <input>)
|
|
129
|
+
<div onClick={() => setOpen((v) => !v)} aria-haspopup="listbox" aria-expanded={open}>
|
|
130
|
+
<ScTextField
|
|
131
|
+
tfGroup="icon-right"
|
|
132
|
+
tfRightIcon={<SiconDown size={20} color="var(--alias-text-and-icons-tertiary)" />}
|
|
133
|
+
value={displayText}
|
|
134
|
+
placeholder="Select…"
|
|
135
|
+
readOnly
|
|
136
|
+
onKeyDown={(e) => {
|
|
137
|
+
if (e.key === "Enter" || e.key === " ") { e.preventDefault(); setOpen((v) => !v); }
|
|
138
|
+
}}
|
|
139
|
+
/>
|
|
140
|
+
</div>
|
|
141
|
+
{open && <MyMenu />} {/* render OUTSIDE the field: the container clips */}
|
|
142
|
+
|
|
143
|
+
// Overriding DS geometry — use containerStyle, not className
|
|
144
|
+
<ScTextField tfGroup="none" containerStyle={{ height: "2.5rem", borderRadius: "0.75rem" }} />
|
|
145
|
+
```
|
|
146
|
+
|
|
147
|
+
---
|
|
148
|
+
|
|
149
|
+
## 2. Where to use it
|
|
150
|
+
|
|
151
|
+
- **Settings and profile forms** — the `@streamoid/settings` package stacks it for
|
|
152
|
+
name/email/referral rows; CXO's mobile settings do the same.
|
|
153
|
+
- **Create/rename modals** — Catalogix `CreateStore`, `CreateWorkspace`,
|
|
154
|
+
`RenameProfile`, the Taxonomy add/edit modals.
|
|
155
|
+
- **As a dropdown trigger** — Catalogix's `DsDropdown` and the settings
|
|
156
|
+
specialization picker both render a read-only `ScTextField` and own the menu
|
|
157
|
+
themselves.
|
|
158
|
+
- **Inside `ScModal` / `ScDrawer` / mobile popups**, stacked with `ScTabField`,
|
|
159
|
+
`ScCheckField` and `ScAppField`, with an `ScButton` footer.
|
|
160
|
+
|
|
161
|
+
All four host apps use it; it is part of the universal core.
|
|
162
|
+
|
|
163
|
+
---
|
|
164
|
+
|
|
165
|
+
## 3. When to use it
|
|
166
|
+
|
|
167
|
+
### Use it when
|
|
168
|
+
|
|
169
|
+
- The input needs a **visible label**, a required marker, an info popover or a
|
|
170
|
+
helper/description line.
|
|
171
|
+
- You need a **ref** to the input (autofocus, imperative select, form libraries) —
|
|
172
|
+
this is the only field in the library that forwards one.
|
|
173
|
+
- You need **native input props** (`type`, `maxLength`, `name`, `readOnly`,
|
|
174
|
+
`autoComplete`, `onKeyDown`) passed straight through.
|
|
175
|
+
|
|
176
|
+
### Don't use it — reach for this instead
|
|
177
|
+
|
|
178
|
+
| Situation | Use instead |
|
|
179
|
+
|---|---|
|
|
180
|
+
| Bare input, no label, and you need 40 px or 32 px height | `ScOnlyField` (`size="medium"` / `"small"`) |
|
|
181
|
+
| Multi-line text (descriptions, prompts) | `ScTextArea` |
|
|
182
|
+
| Choose one of N options from a menu | `ScSelect` |
|
|
183
|
+
| Choose one of 2–4 options shown inline, in a form | `ScTabField` |
|
|
184
|
+
| Boolean options with a shared label | `ScCheckField` |
|
|
185
|
+
| File / image upload | `ScFileField` / `ScImageField` |
|
|
186
|
+
| Per-app permission toggles | `ScAppField` |
|
|
187
|
+
| A small action sitting next to the field ("Attach", "Browse") | `ScFieldButton` |
|
|
188
|
+
| A search box in a toolbar or panel header | `ScOnlyField` with `tfRightIcon={<SiconSearch/>}` |
|
|
189
|
+
|
|
190
|
+
### Don't confuse with
|
|
191
|
+
|
|
192
|
+
| You may actually want | Not this |
|
|
193
|
+
|---|---|
|
|
194
|
+
| `ScOnlyField` — no label row *ever*, `size` variants, `onInputChange(value)` instead of `onChange(event)` | `ScTextField` is the labelled, ref-forwarding, native-`onChange` one |
|
|
195
|
+
| `ScAppField` — despite the name, **not** a generic field wrapper; it is a hardcoded three-app permission block | `ScTextField` is the text input |
|
|
196
|
+
| `ScOnlyIcon` — an icon square, nothing to do with fields | `ScOnlyField` is the input; `ScOnlyIcon` is not |
|
|
197
|
+
| `ScTextArea` — multi-line, and the only one of the three with real `error`/`disabled` styling | `ScTextField` ignores those states |
|
|
198
|
+
|
|
199
|
+
---
|
|
200
|
+
|
|
201
|
+
## 4. Why to use it
|
|
202
|
+
|
|
203
|
+
- **The 48 px pill, the radius, the fill/border pairs and the focus ring are all
|
|
204
|
+
tokens.** Light mode and dark mode both resolve correctly with no conditional
|
|
205
|
+
in your code.
|
|
206
|
+
- **Focus tracking is free.** The component keeps its own `focused` state and
|
|
207
|
+
derives `active`/`filled`, and it still calls your `onFocus`/`onBlur`.
|
|
208
|
+
- **Escape hatches at every level** — `rootStyle`, `containerStyle`, `inputStyle`,
|
|
209
|
+
`descStyle`. You will need them: CSS-module rules beat a plain utility class on
|
|
210
|
+
ties, which is why hosts carry a
|
|
211
|
+
`.sc-text-field-full-width[class*="ScTextField_scTextField"]` hack in their
|
|
212
|
+
global CSS. Prefer `containerStyle` over fighting specificity.
|
|
213
|
+
- **`required` + `info` + `desc` in one component** means the label row, the red
|
|
214
|
+
asterisk and the popover match across all four apps instead of being
|
|
215
|
+
re-invented per screen.
|
|
216
|
+
|
|
217
|
+
---
|
|
218
|
+
|
|
219
|
+
## Gotchas
|
|
220
|
+
|
|
221
|
+
**1. The default field has a lightning bolt in it.** `tfGroup="icon-right"` +
|
|
222
|
+
`tfRightIcon={<SiconBolt/>}` are both defaults.
|
|
223
|
+
|
|
224
|
+
```tsx
|
|
225
|
+
// WRONG — renders a ⚡ inside the field
|
|
226
|
+
<ScTextField label="Name" labelGroup="label" />
|
|
227
|
+
|
|
228
|
+
// RIGHT
|
|
229
|
+
<ScTextField label="Name" labelGroup="label" tfGroup="none" />
|
|
230
|
+
```
|
|
231
|
+
|
|
232
|
+
**2. `label` alone renders nothing.** The label row is gated on
|
|
233
|
+
`labelGroup || required || info`. Always pair `label` with `labelGroup="label"`.
|
|
234
|
+
|
|
235
|
+
```tsx
|
|
236
|
+
// WRONG — the label is dropped
|
|
237
|
+
<ScTextField label="Email ID" tfGroup="none" />
|
|
238
|
+
|
|
239
|
+
// RIGHT
|
|
240
|
+
<ScTextField label="Email ID" labelGroup="label" tfGroup="none" />
|
|
241
|
+
```
|
|
242
|
+
|
|
243
|
+
**3. `state="error"` and `state="disabled"` are cosmetically dead.** There is no
|
|
244
|
+
`.state-error` or `.state-disabled` rule in `ScTextField.module.css`. Passing
|
|
245
|
+
either only *suppresses* the derived `filled`/`active` skins, leaving your typed
|
|
246
|
+
value grey. For a genuine error, render your own message via `desc` and colour
|
|
247
|
+
the container with `containerStyle`; for disabled, pass the native `disabled`
|
|
248
|
+
attribute (which the browser greys) — and note the hover skin still fires.
|
|
249
|
+
|
|
250
|
+
**4. Handlers go to the `<input>`, not the field.** `{...props}` is spread on the
|
|
251
|
+
input, so `onClick` never fires when the user clicks the icon or the padding.
|
|
252
|
+
Wrap the component in your own div for whole-field clicks (both the settings
|
|
253
|
+
specialization picker and Catalogix's `DsDropdown` do exactly this).
|
|
254
|
+
|
|
255
|
+
**5. `overflow: hidden` on the container clips slot content.** A menu, popover or
|
|
256
|
+
tooltip rendered inside `leftSlot`/`rightSlot` will be cut off. Render it as a
|
|
257
|
+
sibling of `ScTextField` (or portal it to `document.body`).
|
|
258
|
+
|
|
259
|
+
**6. `labelGroup="text"` renders the hardcoded English word `"info"`.** There is
|
|
260
|
+
no prop for it. `labelGroup="icon"` renders a bolt. Neither is localisable —
|
|
261
|
+
use `info` (the real popover) instead.
|
|
262
|
+
|
|
263
|
+
**7. A native `placeholder` wins over `placeholderFilled`.** `{...props}` is
|
|
264
|
+
spread after `placeholder={placeholderFilled}`. Both work; don't pass both.
|
|
265
|
+
|
|
266
|
+
**8. Derived `filled` needs a controlled `value`.** The derivation reads
|
|
267
|
+
`props.value`; with `defaultValue` (uncontrolled) the field stays in the
|
|
268
|
+
`default` skin and the text renders muted. Control the value or pass
|
|
269
|
+
`state="filled"`.
|
|
270
|
+
|
|
271
|
+
**9. `tall` does not make it multi-line.** It only grows the container to
|
|
272
|
+
`min-height: 5rem` around a single-line `<input>`. For real multi-line use
|
|
273
|
+
`ScTextArea`.
|
|
274
|
+
|
|
275
|
+
**10. There are no `size` variants.** Height is a fixed `3rem`. If a spec calls
|
|
276
|
+
for a 40 px or 32 px field, use `ScOnlyField` (`size="medium"`/`"small"`) or
|
|
277
|
+
override with `containerStyle`.
|
|
278
|
+
|
|
279
|
+
---
|
|
280
|
+
|
|
281
|
+
## In the wild
|
|
282
|
+
|
|
283
|
+
```tsx
|
|
284
|
+
// cxo-dashboard src/app/components/mobile-settings-content.tsx:259
|
|
285
|
+
<ScTextField
|
|
286
|
+
key="fullName"
|
|
287
|
+
label="Full Name"
|
|
288
|
+
labelGroup="label"
|
|
289
|
+
placeholderFilled="Enter your name"
|
|
290
|
+
value={fullName}
|
|
291
|
+
onChange={(e: React.ChangeEvent<HTMLInputElement>) => setFullName(e.target.value)}
|
|
292
|
+
tfGroup="none"
|
|
293
|
+
className="w-full"
|
|
294
|
+
/>
|
|
295
|
+
```
|
|
296
|
+
|
|
297
|
+
```jsx
|
|
298
|
+
// catalogix/dashboard app/components/DsDropdown/index.jsx:380
|
|
299
|
+
<ScTextField
|
|
300
|
+
className="sc-text-field-full-width"
|
|
301
|
+
tfGroup="icon-right"
|
|
302
|
+
tfRightIcon={<span className={cx(styles["chev"], open && styles["chev-open"])}>
|
|
303
|
+
<SiconDown size={20} color="var(--alias-text-and-icons-tertiary)" />
|
|
304
|
+
</span>}
|
|
305
|
+
rightSlot={rightSlot}
|
|
306
|
+
state={fieldState}
|
|
307
|
+
value={displayText}
|
|
308
|
+
placeholder={placeholder}
|
|
309
|
+
readOnly
|
|
310
|
+
aria-haspopup="listbox"
|
|
311
|
+
aria-expanded={open}
|
|
312
|
+
/>
|
|
313
|
+
```
|
|
314
|
+
|
|
315
|
+
---
|
|
316
|
+
|
|
317
|
+
## Related
|
|
318
|
+
|
|
319
|
+
- `ScOnlyField` — the unlabelled sibling with `size` variants; use for search and dense rows.
|
|
320
|
+
- `ScTextArea` — multi-line, and the only text field with real `error`/`disabled` skins.
|
|
321
|
+
- `ScSelect` — DS-native dropdown when you don't want to hand-roll a trigger.
|
|
322
|
+
- `ScInfoPopup` — what the `info` prop renders; usable standalone.
|
|
323
|
+
- `ScFieldButton` — the 36 px inline action that pairs with a field.
|
|
324
|
+
- `ScTabField` / `ScCheckField` / `ScFileField` / `ScImageField` — the rest of the form vocabulary.
|