@streamoid/ui 0.6.16 → 0.6.18
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +35 -18
- package/dist/docs/AGENTS.md +321 -0
- package/dist/docs/CreditWarningBanner.md +305 -0
- package/dist/docs/InvoiceHistoryMobile.md +222 -0
- package/dist/docs/ScAccess.md +259 -0
- package/dist/docs/ScAppCard.md +244 -0
- package/dist/docs/ScAppCardForCopilot.md +230 -0
- package/dist/docs/ScAppCardV3.md +273 -0
- package/dist/docs/ScAppField.md +308 -0
- package/dist/docs/ScAppListingCard.md +271 -0
- package/dist/docs/ScAppSwitchPanel.md +286 -0
- package/dist/docs/ScAppcardLogos.md +226 -0
- package/dist/docs/ScArtifaxInvite.md +262 -0
- package/dist/docs/ScArtifaxSidebar.md +330 -0
- package/dist/docs/ScAskAgentButton.md +307 -0
- package/dist/docs/ScBadges.md +261 -0
- package/dist/docs/ScBeacon.md +244 -0
- package/dist/docs/ScBillingHistoryHeader.md +210 -0
- package/dist/docs/ScBillingHistoryTableList.md +243 -0
- package/dist/docs/ScBillingLogsTableHeader.md +212 -0
- package/dist/docs/ScBillingLogsTableList.md +251 -0
- package/dist/docs/ScBriefCard.md +255 -0
- package/dist/docs/ScButton.md +251 -0
- package/dist/docs/ScCalendar.md +268 -0
- package/dist/docs/ScCalendarDateComps.md +264 -0
- package/dist/docs/ScCatalogixInvite.md +345 -0
- package/dist/docs/ScCatalogixSidebar.md +337 -0
- package/dist/docs/ScCatalogixStoreHeader.md +246 -0
- package/dist/docs/ScCatalogixStoreTableList.md +316 -0
- package/dist/docs/ScCheckField.md +233 -0
- package/dist/docs/ScCheckbox.md +272 -0
- package/dist/docs/ScCounter.md +235 -0
- package/dist/docs/ScCreditsUsageCard.md +247 -0
- package/dist/docs/ScCreditsUsageCardMobile.md +224 -0
- package/dist/docs/ScDefaultCard.md +269 -0
- package/dist/docs/ScDp.md +245 -0
- package/dist/docs/ScDrawer.md +318 -0
- package/dist/docs/ScFieldButton.md +255 -0
- package/dist/docs/ScFileField.md +268 -0
- package/dist/docs/ScGoogleSignIn.md +250 -0
- package/dist/docs/ScGuide.md +278 -0
- package/dist/docs/ScHDivider.md +213 -0
- package/dist/docs/ScHeader.md +222 -0
- package/dist/docs/ScImageField.md +253 -0
- package/dist/docs/ScInChatList.md +277 -0
- package/dist/docs/ScInChatMessage.md +205 -0
- package/dist/docs/ScInfoPopup.md +248 -0
- package/dist/docs/ScIntialProfileCover.md +233 -0
- package/dist/docs/ScInvoiceHistoryMobile.md +187 -0
- package/dist/docs/ScLogoUnit.md +232 -0
- package/dist/docs/ScMappingCard.md +241 -0
- package/dist/docs/ScMediaApproval.md +301 -0
- package/dist/docs/ScMediaSelect.md +310 -0
- package/dist/docs/ScMenuOptions.md +308 -0
- package/dist/docs/ScMobileBottomAction.md +252 -0
- package/dist/docs/ScMobileTopNav.md +279 -0
- package/dist/docs/ScModal.md +291 -0
- package/dist/docs/ScOnlyField.md +302 -0
- package/dist/docs/ScOnlyIcon.md +213 -0
- package/dist/docs/ScPagination.md +284 -0
- package/dist/docs/ScPairtext.md +287 -0
- package/dist/docs/ScPendingAction.md +238 -0
- package/dist/docs/ScPhtogenixInvite.md +275 -0
- package/dist/docs/ScPlanCard.md +302 -0
- package/dist/docs/ScPlanComparison.md +264 -0
- package/dist/docs/ScPlanDetailsCard.md +246 -0
- package/dist/docs/ScPlanDetailsCardMobile.md +240 -0
- package/dist/docs/ScPopUpMenu.md +224 -0
- package/dist/docs/ScProfile.md +234 -0
- package/dist/docs/ScProfileImageUpdate.md +261 -0
- package/dist/docs/ScProfileOptions.md +245 -0
- package/dist/docs/ScProfilePopup.md +396 -0
- package/dist/docs/ScProfileSettingsComp.md +250 -0
- package/dist/docs/ScProfileV2Mobile.md +216 -0
- package/dist/docs/ScProgressBar.md +267 -0
- package/dist/docs/ScQuickPrompt.md +277 -0
- package/dist/docs/ScRadio.md +228 -0
- package/dist/docs/ScReferralCardMobile.md +226 -0
- package/dist/docs/ScReferralTableHeader.md +260 -0
- package/dist/docs/ScReferralTableList.md +293 -0
- package/dist/docs/ScRole.md +226 -0
- package/dist/docs/ScRoleMobile.md +199 -0
- package/dist/docs/ScSelect.md +270 -0
- package/dist/docs/ScSelection.md +256 -0
- package/dist/docs/ScSelectionList.md +272 -0
- package/dist/docs/ScSelectionPill.md +240 -0
- package/dist/docs/ScSelectionPillGroup.md +302 -0
- package/dist/docs/ScSettingsNav.md +212 -0
- package/dist/docs/ScSettingsTabComp.md +260 -0
- package/dist/docs/ScSideBarLogoUnit.md +340 -0
- package/dist/docs/ScSidebar.md +243 -0
- package/dist/docs/ScSidebarIcons.md +232 -0
- package/dist/docs/ScSidebarMenu.md +283 -0
- package/dist/docs/ScSidebarProfile.md +231 -0
- package/dist/docs/ScSidebarSwitchMenu.md +258 -0
- package/dist/docs/ScSlider.md +194 -0
- package/dist/docs/ScStoreCard.md +252 -0
- package/dist/docs/ScStrLogo.md +253 -0
- package/dist/docs/ScStreamoidWordmark.md +302 -0
- package/dist/docs/ScSubAgent.md +226 -0
- package/dist/docs/ScTabComp.md +308 -0
- package/dist/docs/ScTabField.md +258 -0
- package/dist/docs/ScTabSwitcher.md +307 -0
- package/dist/docs/ScTableHeader.md +261 -0
- package/dist/docs/ScTableList.md +301 -0
- package/dist/docs/ScTableListMobile.md +282 -0
- package/dist/docs/ScTabs.md +268 -0
- package/dist/docs/ScTaxonomyPill.md +263 -0
- package/dist/docs/ScTextArea.md +259 -0
- package/dist/docs/ScTextField.md +324 -0
- package/dist/docs/ScThinkingStepIcon.md +249 -0
- package/dist/docs/ScTodoList.md +288 -0
- package/dist/docs/ScToggleSwitch.md +229 -0
- package/dist/docs/ScUsageHistoryMobile.md +194 -0
- package/dist/docs/ScVDivider.md +215 -0
- package/dist/docs/ScValueMappingL1.md +256 -0
- package/dist/docs/ScVersion.md +251 -0
- package/dist/docs/ScWorkspace.md +233 -0
- package/dist/docs/ScWorkspaceCard.md +234 -0
- package/dist/docs/ScWorkspaceSettingsMobile.md +265 -0
- package/dist/docs/ScWorkspaceSwitchCard.md +314 -0
- package/dist/docs/ScWorkspaceSwitchMobile.md +241 -0
- package/dist/docs/ScWorkspaceSwitchMobileV2.md +278 -0
- package/dist/docs/StreamoidSidebar.md +403 -0
- package/dist/docs/StreamoidWorkspaceSwitcher.md +307 -0
- package/dist/docs/UsageHistoryMobile.md +235 -0
- package/dist/docs/components.json +4849 -0
- package/dist/index.css +43 -37
- package/dist/index.d.mts +10 -0
- package/dist/index.d.ts +10 -0
- package/package.json +3 -2
|
@@ -0,0 +1,249 @@
|
|
|
1
|
+
---
|
|
2
|
+
component: ScThinkingStepIcon
|
|
3
|
+
package: "@streamoid/ui"
|
|
4
|
+
category: chat-agent
|
|
5
|
+
status: stable
|
|
6
|
+
renders: span
|
|
7
|
+
tags: [chat, agent, spinner, status, step, thinking, reasoning, tick, error, glyph, inchat]
|
|
8
|
+
related: [ScInChatList, ScSubAgent, ScProgressBar, ScBeacon, ScTodoList]
|
|
9
|
+
do_not_confuse_with: [ScBeacon, ScProgressBar, ScBadges, ScInChatList]
|
|
10
|
+
---
|
|
11
|
+
|
|
12
|
+
# ScThinkingStepIcon
|
|
13
|
+
|
|
14
|
+
**The 12px status glyph for one agent reasoning step.** Four mutually exclusive
|
|
15
|
+
inline-SVG paths in one `<span>`: a hollow ring (not started), a spinning 8-spoke
|
|
16
|
+
asterisk (working), a circled tick in success green (completed), a circled cross in
|
|
17
|
+
error red (error). Stroke colour is the only thing `state` changes.
|
|
18
|
+
|
|
19
|
+
## TL;DR for agents
|
|
20
|
+
|
|
21
|
+
- **Reach for it when:** you are laying out your own step/progress row and need the
|
|
22
|
+
product's pending → working → done → failed glyph.
|
|
23
|
+
- **Don't reach for it when:** you want the whole step row (→ `ScInChatList`, which
|
|
24
|
+
renders this internally), a determinate progress bar (→ `ScProgressBar`), a live
|
|
25
|
+
"something changed here" dot (→ `ScBeacon`), or a status label
|
|
26
|
+
(→ `ScBadges`).
|
|
27
|
+
- **Three things that will bite you:**
|
|
28
|
+
1. `state="default"` draws the ring in `--alias-border-divider` — **deliberately
|
|
29
|
+
almost invisible**. It reads as "not started", not as "loading".
|
|
30
|
+
2. The SVG is `aria-hidden`. The glyph conveys nothing to a screen reader unless
|
|
31
|
+
you label the wrapper yourself.
|
|
32
|
+
3. `strokeWidth` is fixed at `1.25` **in viewBox units** (viewBox `13.25`), so the
|
|
33
|
+
rendered stroke is `1.25 × size / 13.25` px — ~1.1px at the 12px default, ~3px at
|
|
34
|
+
`size={32}`. You cannot keep a hairline stroke while scaling the glyph up.
|
|
35
|
+
|
|
36
|
+
---
|
|
37
|
+
|
|
38
|
+
## 1. How to use it
|
|
39
|
+
|
|
40
|
+
### Import
|
|
41
|
+
|
|
42
|
+
```tsx
|
|
43
|
+
import { ScThinkingStepIcon } from "@streamoid/ui";
|
|
44
|
+
import "@streamoid/ui/dist/index.css"; // once, at your app root
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
### Minimal usage
|
|
48
|
+
|
|
49
|
+
```tsx
|
|
50
|
+
<ScThinkingStepIcon state="working" />
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
### Props
|
|
54
|
+
|
|
55
|
+
| Prop | Type | Default | Notes |
|
|
56
|
+
|---|---|---|---|
|
|
57
|
+
| `state` | `"default"` \| `"working"` \| `"completed"` \| `"error"` | `"default"` | Selects both the path and the stroke token. The `IcListIconState` type exported from `ScInChatList` is the same union. |
|
|
58
|
+
| `size` | `number` | `12` | Pixels. Applied to the wrapper `<span>` (`width`/`height`) **and** the `<svg>` width/height. |
|
|
59
|
+
| `className` | `string` | – | Inserted between the base class and the state class. |
|
|
60
|
+
| `style` | `React.CSSProperties` | – | Merged **after** `width`/`height`, so `style={{ width: 20 }}` overrides `size` for the span (but not for the svg). |
|
|
61
|
+
| `...props` | `Omit<HTMLAttributes<HTMLSpanElement>, "children">` | – | `role`, `aria-label`, `title`, `data-*`, `onClick` spread onto the span. `children` is deliberately excluded. |
|
|
62
|
+
|
|
63
|
+
### What renders in each state
|
|
64
|
+
|
|
65
|
+
| `state` | Glyph | Stroke token | Animation |
|
|
66
|
+
|---|---|---|---|
|
|
67
|
+
| `default` | hollow circle, r=6 | `--alias-border-divider` (`#242424`) | — |
|
|
68
|
+
| `working` | 8-spoke asterisk | `--alias-text-and-icons-primary` | `rotate` 360° / 1s linear infinite |
|
|
69
|
+
| `completed` | near-full circle + tick | `--alias-text-and-icons-success` (`#00b96b`) | — |
|
|
70
|
+
| `error` | circle + ✕ | `--alias-text-and-icons-error` (`#d11a1a`) | — |
|
|
71
|
+
|
|
72
|
+
`.stroke { fill: none }` on every path, and `.glyph { overflow: visible }` so the
|
|
73
|
+
1.25 stroke is never clipped at the viewBox edge.
|
|
74
|
+
|
|
75
|
+
### Recipes
|
|
76
|
+
|
|
77
|
+
```tsx
|
|
78
|
+
// Map your own status enum onto the glyph
|
|
79
|
+
const glyphState = (s: StepStatus) =>
|
|
80
|
+
s === "running" ? "working" : s === "done" ? "completed" : s === "failed" ? "error" : "default";
|
|
81
|
+
|
|
82
|
+
<ScThinkingStepIcon state={glyphState(step.status)} />
|
|
83
|
+
|
|
84
|
+
// A bespoke step row (what ScInChatList does internally, in a 20×26 gutter)
|
|
85
|
+
<div style={{ display: "flex", alignItems: "flex-start", gap: 8 }}>
|
|
86
|
+
<span style={{ display: "flex", alignItems: "center", width: 20, height: 26, flexShrink: 0 }}>
|
|
87
|
+
<ScThinkingStepIcon state="working" />
|
|
88
|
+
</span>
|
|
89
|
+
<p>Resolving the taxonomy…</p>
|
|
90
|
+
</div>
|
|
91
|
+
|
|
92
|
+
// Announced to assistive tech (the svg is aria-hidden, so label the span)
|
|
93
|
+
<ScThinkingStepIcon state="working" role="img" aria-label="In progress" />
|
|
94
|
+
|
|
95
|
+
// Larger, for a standalone empty/loading state
|
|
96
|
+
<ScThinkingStepIcon state="working" size={24} />
|
|
97
|
+
```
|
|
98
|
+
|
|
99
|
+
---
|
|
100
|
+
|
|
101
|
+
## 2. Where to use it
|
|
102
|
+
|
|
103
|
+
- **Inside `ScInChatList`**, as its `leadingIndicator` — this is where 100% of current
|
|
104
|
+
usage comes from. `ScInChatList` renders `<ScThinkingStepIcon state={iconState} />`
|
|
105
|
+
at the default 12px inside a fixed 20×26 gutter, which is what keeps a stack of step
|
|
106
|
+
titles optically aligned as the glyph swaps.
|
|
107
|
+
- **Your own step/progress rows** in the chat surface, when `ScInChatList`'s layout
|
|
108
|
+
(single-line truncating title + tool pill) is not what you want.
|
|
109
|
+
- **Anywhere a four-state indeterminate status glyph** fits: an import queue, a deploy
|
|
110
|
+
log, a pre-flight checklist.
|
|
111
|
+
|
|
112
|
+
If you are also going to want a title, a description and a tool pill, stop and use
|
|
113
|
+
`ScInChatList` instead of assembling them.
|
|
114
|
+
|
|
115
|
+
---
|
|
116
|
+
|
|
117
|
+
## 3. When to use it
|
|
118
|
+
|
|
119
|
+
### Use it when
|
|
120
|
+
|
|
121
|
+
- The status is **indeterminate** (no percentage) and has exactly these four outcomes.
|
|
122
|
+
- The glyph sits next to text that already says what the step is — the icon is
|
|
123
|
+
reinforcement, not the message.
|
|
124
|
+
|
|
125
|
+
### Don't use it — reach for this instead
|
|
126
|
+
|
|
127
|
+
| Situation | Use instead |
|
|
128
|
+
|---|---|
|
|
129
|
+
| A whole agent step row (glyph + title + description + tool pill) | `ScInChatList` |
|
|
130
|
+
| A "Using <sub-agent>…" chip | `ScSubAgent` |
|
|
131
|
+
| Determinate progress (a percentage, credits used, upload %) | `ScProgressBar` |
|
|
132
|
+
| A small attention dot / "new here" marker in a dashboard | `ScBeacon` |
|
|
133
|
+
| A textual status chip ("Active", "Failed") | `ScBadges` |
|
|
134
|
+
| A checkbox the user toggles | `ScTodoList` (its own 18px circle) or `ScCheckbox` |
|
|
135
|
+
| A button-level loading state | `ScButton`'s `loading` prop |
|
|
136
|
+
|
|
137
|
+
### Don't confuse with
|
|
138
|
+
|
|
139
|
+
| You may actually want | Not this |
|
|
140
|
+
|---|---|
|
|
141
|
+
| `ScBeacon` — a tonal attention dot with its own tone prop | This is a four-state step glyph with fixed semantics |
|
|
142
|
+
| `ScProgressBar` — determinate, has a value | This is indeterminate; `working` spins forever |
|
|
143
|
+
| `ScInChatList` — the row that already contains this glyph | Rendering both nests two glyphs |
|
|
144
|
+
| `ScTodoList`'s circle — an 18px **interactive** checkbox | This glyph is decorative and inert |
|
|
145
|
+
|
|
146
|
+
---
|
|
147
|
+
|
|
148
|
+
## 4. Why to use it
|
|
149
|
+
|
|
150
|
+
- **Four semantic tokens, one component.** `border-divider` / `text-primary` /
|
|
151
|
+
`text-and-icons-success` / `text-and-icons-error` mean the glyph matches every other
|
|
152
|
+
status signal in the product and inverts correctly in light mode — the exact thing a
|
|
153
|
+
hand-rolled `#00b96b` tick fails at.
|
|
154
|
+
- **`prefers-reduced-motion` is already honoured.** The spin animation is disabled
|
|
155
|
+
under the media query, so you don't ship a vestibular-trigger spinner.
|
|
156
|
+
- **`transform-box: fill-box` + `transform-origin: center`** on the spinner: the
|
|
157
|
+
asterisk rotates about its own centre rather than the SVG origin, which is the bug
|
|
158
|
+
every hand-rolled SVG spinner has.
|
|
159
|
+
- **Optically consistent stroke.** One `1.25` stroke across all four glyphs in a shared
|
|
160
|
+
`13.25` viewBox means the ring, spinner, tick and cross carry identical visual
|
|
161
|
+
weight — swapping states doesn't make the row "jump".
|
|
162
|
+
- **It is the same glyph `ScInChatList` uses**, so a bespoke row you build will match
|
|
163
|
+
the transcript rows next to it exactly.
|
|
164
|
+
|
|
165
|
+
---
|
|
166
|
+
|
|
167
|
+
## Gotchas
|
|
168
|
+
|
|
169
|
+
**1. `default` is nearly invisible, on purpose.** `--alias-border-divider` is the
|
|
170
|
+
faintest token in the palette. If you were reaching for "loading", you want
|
|
171
|
+
`"working"`.
|
|
172
|
+
|
|
173
|
+
```tsx
|
|
174
|
+
// WRONG — a step that is running looks like a step that hasn't started
|
|
175
|
+
<ScThinkingStepIcon />
|
|
176
|
+
|
|
177
|
+
// RIGHT
|
|
178
|
+
<ScThinkingStepIcon state="working" />
|
|
179
|
+
```
|
|
180
|
+
|
|
181
|
+
**2. It announces nothing.** The `<svg>` is `aria-hidden="true"` and the span has no
|
|
182
|
+
role. Screen-reader users get the glyph's meaning only from adjacent text — or from a
|
|
183
|
+
label you add.
|
|
184
|
+
|
|
185
|
+
```tsx
|
|
186
|
+
// RIGHT — when the glyph is the only signal
|
|
187
|
+
<ScThinkingStepIcon state="error" role="img" aria-label="Step failed" />
|
|
188
|
+
```
|
|
189
|
+
|
|
190
|
+
**3. The stroke scales with `size`, and you cannot control it.** `strokeWidth="1.25"`
|
|
191
|
+
is in viewBox units, so the rendered stroke is `1.25 × size / 13.25` px: ~1.1px at 12,
|
|
192
|
+
~2.3px at 24, ~3px at 32. There is no `strokeWidth` prop, so a large glyph gets a
|
|
193
|
+
proportionally heavy stroke next to UI drawn at a fixed 1.5px.
|
|
194
|
+
|
|
195
|
+
**4. `style` can desynchronise the span from the svg.** `style` is merged after
|
|
196
|
+
`width`/`height`, so `style={{ width: 24, height: 24 }}` resizes the box but leaves the
|
|
197
|
+
SVG at `size`. Use `size` to resize; use `style` only for margins/positioning.
|
|
198
|
+
|
|
199
|
+
```tsx
|
|
200
|
+
// WRONG — 24px box containing a 12px glyph
|
|
201
|
+
<ScThinkingStepIcon state="working" style={{ width: 24, height: 24 }} />
|
|
202
|
+
|
|
203
|
+
// RIGHT
|
|
204
|
+
<ScThinkingStepIcon state="working" size={24} />
|
|
205
|
+
```
|
|
206
|
+
|
|
207
|
+
**5. `children` is excluded from the props type.** You cannot nest anything inside it —
|
|
208
|
+
by design, it is a leaf.
|
|
209
|
+
|
|
210
|
+
**6. The colour is not overridable.** There is no `color` prop and the stroke is set by
|
|
211
|
+
a state class, so `style={{ color: … }}` does nothing (the paths use `stroke`, not
|
|
212
|
+
`currentColor`). To recolour, you must beat `.state-*` in specificity — which means you
|
|
213
|
+
probably want a different component.
|
|
214
|
+
|
|
215
|
+
**7. `completed` is a circle with a deliberate gap.** The tick path exits the ring at the top
|
|
216
|
+
right, so at very small sizes (`size < 10`) the open arc reads as a broken circle.
|
|
217
|
+
Don't go below the 12px default.
|
|
218
|
+
|
|
219
|
+
**8. Two spaces creep into the class list** when `className` is omitted
|
|
220
|
+
(`base + " " + "" + " " + state`). Harmless, but exact `className` assertions in
|
|
221
|
+
snapshot tests will see it.
|
|
222
|
+
|
|
223
|
+
---
|
|
224
|
+
|
|
225
|
+
## In the wild
|
|
226
|
+
|
|
227
|
+
_No host render site found — used by the agent runtime / composed internally._
|
|
228
|
+
|
|
229
|
+
It is composed **inside `ScInChatList`** rather than called directly:
|
|
230
|
+
|
|
231
|
+
```tsx
|
|
232
|
+
// @streamoid/ui packages/ui/src/SC-InChatList/ScInChatList.tsx:49
|
|
233
|
+
<ScThinkingStepIcon state={iconState} />
|
|
234
|
+
```
|
|
235
|
+
|
|
236
|
+
That row component is itself rendered by the chat runtime
|
|
237
|
+
(`stream-agent frontend/src/components/chat/AgentSteps.tsx:448`). If you need the
|
|
238
|
+
glyph directly, the natural home is a bespoke step/progress row in the same chat
|
|
239
|
+
surface.
|
|
240
|
+
|
|
241
|
+
---
|
|
242
|
+
|
|
243
|
+
## Related
|
|
244
|
+
|
|
245
|
+
- `ScInChatList` — the step row that already renders this glyph; prefer it.
|
|
246
|
+
- `ScSubAgent` — the delegation pill in the same steps stack.
|
|
247
|
+
- `ScProgressBar` — determinate progress with a value.
|
|
248
|
+
- `ScBeacon` — the dashboard-side attention dot.
|
|
249
|
+
- `ScTodoList` — its own 18px interactive check circle, not this glyph.
|
|
@@ -0,0 +1,288 @@
|
|
|
1
|
+
---
|
|
2
|
+
component: ScTodoList
|
|
3
|
+
package: "@streamoid/ui"
|
|
4
|
+
category: chat-agent
|
|
5
|
+
status: stable
|
|
6
|
+
renders: div
|
|
7
|
+
tags: [chat, agent, todo, checklist, tasks, plan, dynamicform, add, edit, delete]
|
|
8
|
+
related: [ScCheckField, ScCheckbox, ScThinkingStepIcon, ScInChatList, ScMediaSelect]
|
|
9
|
+
do_not_confuse_with: [ScCheckField, ScCheckbox, ScInChatList, ScTableList, ScMenuOptions]
|
|
10
|
+
used_by: [agent]
|
|
11
|
+
---
|
|
12
|
+
|
|
13
|
+
# ScTodoList
|
|
14
|
+
|
|
15
|
+
**The agent's task checklist, editable by the user.** A "`n` of `m` completed" header,
|
|
16
|
+
one row per item (18px circle + text + hover-revealed edit/delete), and an always-on
|
|
17
|
+
"add an item" input with a `+` button. Items are yours; the edit buffer is the
|
|
18
|
+
component's.
|
|
19
|
+
|
|
20
|
+
## TL;DR for agents
|
|
21
|
+
|
|
22
|
+
- **Reach for it when:** the agent produced a plan or task list and the user must be
|
|
23
|
+
able to tick, rename, remove and add items — the `todo_list` field of the agent's
|
|
24
|
+
in-chat DynamicForm.
|
|
25
|
+
- **Don't reach for it when:** the list is read-only agent progress
|
|
26
|
+
(→ `ScInChatList`, one row per step), it's a form's multi-select
|
|
27
|
+
(→ `ScCheckField` / `ScCheckbox`), or it's tabular data (→ `ScTableList`).
|
|
28
|
+
- **Four things that will bite you:**
|
|
29
|
+
1. The **add row always renders** — even with zero items, even with no `onAdd`
|
|
30
|
+
handler. There is no way to make the list read-only.
|
|
31
|
+
2. The per-row **edit/delete buttons are `opacity: 0; pointer-events: none` until
|
|
32
|
+
`:hover`/`:focus-within`** — unreachable on touch devices.
|
|
33
|
+
3. `onAdd` receives the **untrimmed** string, even though the empty check is done on
|
|
34
|
+
the trimmed one.
|
|
35
|
+
4. `items` is fully controlled but the **edit text is internal state** — the
|
|
36
|
+
component won't re-render an in-progress edit if you change `items` underneath it.
|
|
37
|
+
|
|
38
|
+
---
|
|
39
|
+
|
|
40
|
+
## 1. How to use it
|
|
41
|
+
|
|
42
|
+
### Import
|
|
43
|
+
|
|
44
|
+
```tsx
|
|
45
|
+
import { ScTodoList } from "@streamoid/ui";
|
|
46
|
+
import type { IScTodoItem } from "@streamoid/ui";
|
|
47
|
+
import "@streamoid/ui/dist/index.css"; // once, at your app root
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
### Minimal usage
|
|
51
|
+
|
|
52
|
+
```tsx
|
|
53
|
+
<ScTodoList
|
|
54
|
+
items={todos} // IScTodoItem[]
|
|
55
|
+
onToggle={(id) => toggle(id)}
|
|
56
|
+
onAdd={(content) => add(content.trim())}
|
|
57
|
+
/>
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
### Props
|
|
61
|
+
|
|
62
|
+
| Prop | Type | Default | Notes |
|
|
63
|
+
|---|---|---|---|
|
|
64
|
+
| `items` | `IScTodoItem[]` | `[]` | Controlled. Rendered in array order — there is no sorting and no drag-reorder. |
|
|
65
|
+
| `onToggle` | `(id: string) => void` | – | Fires on the circle button. You flip `status` yourself. |
|
|
66
|
+
| `onEdit` | `(id: string, content: string) => void` | – | Fires on Enter **or blur** of the inline input. Receives the raw buffer, **not trimmed**. |
|
|
67
|
+
| `onDelete` | `(id: string) => void` | – | Fires on the trash button. No confirmation. |
|
|
68
|
+
| `onAdd` | `(content: string) => void` | – | Fires on Enter in the add input or on the `+` button. Receives the **untrimmed** value. |
|
|
69
|
+
| `addPlaceholder` | `string` | `"Add an item"` | The only overridable string in the component. |
|
|
70
|
+
| `className` | `string` | – | Appended after the internal class. |
|
|
71
|
+
|
|
72
|
+
`IScTodoItem`
|
|
73
|
+
|
|
74
|
+
| Field | Type | Notes |
|
|
75
|
+
|---|---|---|
|
|
76
|
+
| `id` | `string` | React key **and** the identity passed to every callback. Must be unique and stable. |
|
|
77
|
+
| `content` | `string` | Row text. Wraps (`word-break: break-word`). |
|
|
78
|
+
| `status` | `"pending"` \| `"completed"` | `"completed"` ⇒ filled circle + tick, muted text, `line-through`. Any other value renders as pending. |
|
|
79
|
+
|
|
80
|
+
⚠️ `IScTodoListProps` does **not** extend `HTMLAttributes` — no `style`, `id`,
|
|
81
|
+
`data-*`, `aria-*` or `onClick` on the root. Wrap it if you need those.
|
|
82
|
+
|
|
83
|
+
### What renders when
|
|
84
|
+
|
|
85
|
+
| Region | Condition |
|
|
86
|
+
|---|---|
|
|
87
|
+
| `"n of m completed"` header | `items.length > 0` only |
|
|
88
|
+
| Row edit/delete buttons | only while the row is `:hover` or `:focus-within`, and only when that row is **not** being edited |
|
|
89
|
+
| Inline edit input | while that row's id is the internal `editingId` |
|
|
90
|
+
| Add row (input + `+`) | **always** |
|
|
91
|
+
| `+` button enabled | only when the trimmed add-input value is non-empty |
|
|
92
|
+
|
|
93
|
+
### Recipes
|
|
94
|
+
|
|
95
|
+
```tsx
|
|
96
|
+
// The canonical DynamicForm wiring — the form owns the array
|
|
97
|
+
<ScTodoList
|
|
98
|
+
items={todoItems.map((i) => ({
|
|
99
|
+
id: i.id,
|
|
100
|
+
content: i.content,
|
|
101
|
+
status: i.status === "completed" ? "completed" : "pending",
|
|
102
|
+
}))}
|
|
103
|
+
onToggle={(id) => handleTodoToggle(fieldId, id)}
|
|
104
|
+
onEdit={(id, content) => handleTodoEdit(fieldId, id, content)}
|
|
105
|
+
onDelete={(id) => handleTodoDelete(fieldId, id)}
|
|
106
|
+
onAdd={(content) => handleTodoAdd(fieldId, content)}
|
|
107
|
+
addPlaceholder="Ask for follow-up changes"
|
|
108
|
+
/>
|
|
109
|
+
|
|
110
|
+
// Reducer-style state, and trimming at the boundary (the component doesn't)
|
|
111
|
+
const [items, setItems] = useState<IScTodoItem[]>([]);
|
|
112
|
+
<ScTodoList
|
|
113
|
+
items={items}
|
|
114
|
+
onToggle={(id) =>
|
|
115
|
+
setItems((xs) => xs.map((x) => x.id === id
|
|
116
|
+
? { ...x, status: x.status === "completed" ? "pending" : "completed" }
|
|
117
|
+
: x))}
|
|
118
|
+
onEdit={(id, content) => {
|
|
119
|
+
const next = content.trim();
|
|
120
|
+
if (!next) return; // reject empty renames yourself
|
|
121
|
+
setItems((xs) => xs.map((x) => (x.id === id ? { ...x, content: next } : x)));
|
|
122
|
+
}}
|
|
123
|
+
onDelete={(id) => setItems((xs) => xs.filter((x) => x.id !== id))}
|
|
124
|
+
onAdd={(content) => setItems((xs) => [...xs, { id: crypto.randomUUID(), content: content.trim(), status: "pending" }])}
|
|
125
|
+
/>
|
|
126
|
+
|
|
127
|
+
// Read-only-ish: you still get the add row, so hide it in CSS.
|
|
128
|
+
// The add row is the LAST CHILD OF THE COMPONENT'S OWN ROOT, so you need two levels:
|
|
129
|
+
// .agentPlan > div > div:last-child { display: none }
|
|
130
|
+
<div className="agentPlan"><ScTodoList items={plan} /></div>
|
|
131
|
+
```
|
|
132
|
+
|
|
133
|
+
---
|
|
134
|
+
|
|
135
|
+
## 2. Where to use it
|
|
136
|
+
|
|
137
|
+
- **The agent's in-chat DynamicForm**, `todo_list` field type — the only real consumer.
|
|
138
|
+
Live at `stream-agent packages/chat-components/src/DynamicForm.tsx`, where the agent
|
|
139
|
+
proposes a plan and the user edits it before submitting.
|
|
140
|
+
- **A plan-review step in the chat transcript**, under the reasoning steps
|
|
141
|
+
(`ScInChatList`) that produced it.
|
|
142
|
+
- Any surface where the **user negotiates a short list with the agent**. It is not a
|
|
143
|
+
general-purpose dashboard task list: there is no persistence, no assignee, no due
|
|
144
|
+
date, no reordering, no grouping.
|
|
145
|
+
|
|
146
|
+
---
|
|
147
|
+
|
|
148
|
+
## 3. When to use it
|
|
149
|
+
|
|
150
|
+
### Use it when
|
|
151
|
+
|
|
152
|
+
- The list is **short** (a handful of items), flat, and fully in view.
|
|
153
|
+
- All four mutations — toggle, rename, delete, add — are things the user should be
|
|
154
|
+
able to do inline.
|
|
155
|
+
- The completion count is useful feedback on its own.
|
|
156
|
+
|
|
157
|
+
### Don't use it — reach for this instead
|
|
158
|
+
|
|
159
|
+
| Situation | Use instead |
|
|
160
|
+
|---|---|
|
|
161
|
+
| Read-only agent progress, one row per step, with per-row status glyphs | `ScInChatList` |
|
|
162
|
+
| A labelled group of checkboxes in a form (fixed options) | `ScCheckField` |
|
|
163
|
+
| A single checkbox | `ScCheckbox` |
|
|
164
|
+
| Approving/rejecting one suggested line | `ScSelectionList` |
|
|
165
|
+
| Picking media thumbnails | `ScMediaSelect` |
|
|
166
|
+
| Tabular rows with columns and actions | `ScTableList` |
|
|
167
|
+
| A dropdown of actions | `ScMenuOptions` inside `ScPopUpMenu` |
|
|
168
|
+
| Anything needing reorder, nesting, assignees or due dates | build it — this component has none of that |
|
|
169
|
+
|
|
170
|
+
### Don't confuse with
|
|
171
|
+
|
|
172
|
+
| You may actually want | Not this |
|
|
173
|
+
|---|---|
|
|
174
|
+
| `ScCheckField` — a **form field**: label + fixed checkbox options, no add/delete | This is a mutable list with its own add row |
|
|
175
|
+
| `ScInChatList` — read-only step rows with spinner/tick/cross | This has interactive circles and no "working" state |
|
|
176
|
+
| `ScThinkingStepIcon` — the agent's 12px status glyph | The row circle here is a bespoke 18px **button**, not that glyph |
|
|
177
|
+
| A `<ul>`/`<li>` list | It renders nested `div`s — no list semantics for screen readers |
|
|
178
|
+
|
|
179
|
+
---
|
|
180
|
+
|
|
181
|
+
## 4. Why to use it
|
|
182
|
+
|
|
183
|
+
- **Four mutations, one component, no reflow.** The edit/delete buttons keep their
|
|
184
|
+
layout space at `opacity: 0` and only fade in on hover, so rows never jump width when
|
|
185
|
+
the pointer enters — the classic hand-rolled bug.
|
|
186
|
+
- **The edit affordance is already correct**: `autoFocus`, Enter to commit, Escape to
|
|
187
|
+
cancel, blur to commit. Getting blur-vs-Escape ordering right by hand is fiddly (here
|
|
188
|
+
`cancelEdit` unmounts the input before blur can fire, so Escape genuinely cancels).
|
|
189
|
+
- **Completion is communicated twice** — muted colour *and* strikethrough — so it
|
|
190
|
+
survives greyscale and colour-blindness.
|
|
191
|
+
- **The check circle is drawn with tokens** (`--alias-fill-base-base` fill,
|
|
192
|
+
`--alias-text-and-icons-inverse` tick), so the "filled" circle inverts properly in
|
|
193
|
+
light mode instead of becoming a white dot on white.
|
|
194
|
+
- **The add input matches the DS field skin** (`fill-neutral-neutralplus` → `neutral`
|
|
195
|
+
on hover/focus, `border-subtle` → `border-default`), so the list sits correctly inside
|
|
196
|
+
a form built from `ScTextField`/`ScTextArea`.
|
|
197
|
+
|
|
198
|
+
---
|
|
199
|
+
|
|
200
|
+
## Gotchas
|
|
201
|
+
|
|
202
|
+
**1. You cannot hide the add row.** It renders unconditionally, with no `onAdd` needed.
|
|
203
|
+
An agent-owned, read-only plan will still show an editable input and a `+`.
|
|
204
|
+
|
|
205
|
+
```tsx
|
|
206
|
+
// WRONG — assumes omitting onAdd removes the affordance
|
|
207
|
+
<ScTodoList items={plan} /> // still renders the input + `+` button
|
|
208
|
+
|
|
209
|
+
// RIGHT — wrap and suppress it, or don't use this component for read-only lists
|
|
210
|
+
<div className="readOnlyPlan"><ScTodoList items={plan} /></div>
|
|
211
|
+
// .readOnlyPlan > div > div:last-child { display: none }
|
|
212
|
+
// ^ the wrapper ^ the component root ^ .addRow
|
|
213
|
+
// (`.readOnlyPlan > div:last-child` would match the component's own root and hide
|
|
214
|
+
// the whole list — the add row is one level deeper.)
|
|
215
|
+
```
|
|
216
|
+
|
|
217
|
+
**2. Row actions are hover-only.** `.actions { opacity: 0; pointer-events: none }`
|
|
218
|
+
until `.row:hover` / `.row:focus-within`. On a touch device there is no hover, so
|
|
219
|
+
**edit and delete are unreachable**. Do not use this component on a mobile surface
|
|
220
|
+
without overriding that rule.
|
|
221
|
+
|
|
222
|
+
**3. `onAdd` and `onEdit` hand you untrimmed strings.** `commitAdd` guards on
|
|
223
|
+
`newValue.trim().length === 0` but then calls `onAdd?.(newValue)` — the raw value.
|
|
224
|
+
Trim at your boundary or you will store `" fix colours "`.
|
|
225
|
+
|
|
226
|
+
**4. Blur commits an edit.** Clicking anywhere else while editing calls
|
|
227
|
+
`onEdit(id, buffer)` — including with an **empty** buffer, which will blank the item if
|
|
228
|
+
you don't guard. Escape is the only way to cancel.
|
|
229
|
+
|
|
230
|
+
**5. The edit buffer is internal.** `editingId`/`editValue` are `useState` inside the
|
|
231
|
+
component. If a websocket updates `items` mid-edit, the input keeps the stale buffer and
|
|
232
|
+
the next blur overwrites the fresh content. Pause external updates while `onEdit` is
|
|
233
|
+
pending, or key the whole component on a revision id.
|
|
234
|
+
|
|
235
|
+
**6. `id` must be stable.** It's the React key and the callback identity. Index-based
|
|
236
|
+
ids will misroute toggles the moment an item is deleted.
|
|
237
|
+
|
|
238
|
+
**7. No confirmation on delete.** The trash button fires `onDelete` immediately. Add
|
|
239
|
+
your own undo if the items matter.
|
|
240
|
+
|
|
241
|
+
**8. The header only appears when there is at least one item.** An empty list shows
|
|
242
|
+
just the add row — no "0 of 0 completed". Render your own empty state above it if you
|
|
243
|
+
want copy there.
|
|
244
|
+
|
|
245
|
+
**9. Hardcoded English.** `"{n} of {m} completed"`, and the aria-labels
|
|
246
|
+
`"Mark as completed"` / `"Mark as pending"` / `"Edit item"` / `"Delete item"` /
|
|
247
|
+
`"Add item"`. Only `addPlaceholder` is overridable. Not usable in a localised surface
|
|
248
|
+
without changing the DS.
|
|
249
|
+
|
|
250
|
+
**10. No root-level props.** `IScTodoListProps` doesn't extend `HTMLAttributes`, so
|
|
251
|
+
`style`, `id`, `data-testid` and `aria-*` are type errors. Wrap the component.
|
|
252
|
+
|
|
253
|
+
**11. Not a list element.** Rows are `div`s, not `<li>`, and there is no `role="list"`.
|
|
254
|
+
Screen readers get a run of buttons and spans with no count or position.
|
|
255
|
+
|
|
256
|
+
---
|
|
257
|
+
|
|
258
|
+
## In the wild
|
|
259
|
+
|
|
260
|
+
Rendered by the **agent runtime**, not by any of the four dashboards — the live call
|
|
261
|
+
site is the chat DynamicForm's `todo_list` field in the `stream-agent` repo
|
|
262
|
+
(`@streamoid/chat-components`):
|
|
263
|
+
|
|
264
|
+
```tsx
|
|
265
|
+
// stream-agent packages/chat-components/src/DynamicForm.tsx:951
|
|
266
|
+
<ScTodoList
|
|
267
|
+
items={todoItems.map((i) => ({
|
|
268
|
+
id: i.id,
|
|
269
|
+
content: i.content,
|
|
270
|
+
status: i.status === "completed" ? "completed" : "pending",
|
|
271
|
+
}))}
|
|
272
|
+
onToggle={(id) => handleTodoToggle(field.id, id)}
|
|
273
|
+
onEdit={(id, content) => handleTodoEdit(field.id, id, content)}
|
|
274
|
+
onDelete={(id) => handleTodoDelete(field.id, id)}
|
|
275
|
+
onAdd={(content) => handleTodoAdd(field.id, content)}
|
|
276
|
+
addPlaceholder="Ask for follow-up changes"
|
|
277
|
+
/>
|
|
278
|
+
```
|
|
279
|
+
|
|
280
|
+
---
|
|
281
|
+
|
|
282
|
+
## Related
|
|
283
|
+
|
|
284
|
+
- `ScInChatList` — read-only agent step rows; use it for progress, not tasks.
|
|
285
|
+
- `ScCheckField` / `ScCheckbox` — the form-side checkbox vocabulary.
|
|
286
|
+
- `ScSelectionList` — accept/decline one suggested line instead of editing a list.
|
|
287
|
+
- `ScMediaSelect` — the same "pick from what the agent produced" idea, for media.
|
|
288
|
+
- `ScThinkingStepIcon` — the agent's status glyph, not the row circle used here.
|