@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.
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 +43 -37
  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,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.