Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -98,7 +98,7 @@ Status legend: ⬜ Todo · ✅ Done
| 9 | Thread List | host-labeled sections; unread dot, pinned glyph, leading icon slot; single selection by id | ✅ |
| 10 | Message actions | | ✅ |
| 11 | Streaming text | | ✅ |
| 12 | Message composer | | ✅ |
| 12 | Message composer | full card; compact single-row pill that opens into the card as the draft grows; expands to fill a fixed height | ✅ |
| 13 | Model selector | effort & overflow submenus; sheet on phones | ✅ |
| 14 | Menu | icon-trigger menu: groups, submenus, toggles; sheet on phones | ✅ |
| 15 | Attachments | images and files, type pill; built-in picker and web file drop; videos pending | ✅ |
Expand Down
2 changes: 1 addition & 1 deletion CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -102,7 +102,7 @@ Values come from the Flow UI Figma file. Role names follow Material 3's `ColorSc
| 9 | Thread List | host-labeled sections; unread dot, pinned glyph, leading icon slot; single selection by id | ✅ |
| 10 | Message actions | | ✅ |
| 11 | Streaming text | | ✅ |
| 12 | Message composer | | ✅ |
| 12 | Message composer | full card; compact single-row pill that opens into the card as the draft grows; expands to fill a fixed height | ✅ |
| 13 | Model selector | effort & overflow submenus; sheet on phones | ✅ |
| 14 | Menu | icon-trigger menu: groups, submenus, toggles; sheet on phones | ✅ |
| 15 | Attachments | images and files, type pill; built-in picker and web file drop; videos pending | ✅ |
Expand Down
95 changes: 89 additions & 6 deletions docs/src/content/docs/components/composer.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -13,10 +13,12 @@ the draft, a send button that becomes **stop** while the model is
generating, and slots for host actions on the design's rail. `onSend` is
required — a composer needs somewhere to send to. The card sits on
`surfaceBright` behind a gradient hairline that firms on hover and focus,
standing at the design's 116px with an empty draft — which also drains the
send disc to the disabled ink. The whole card is the field's click target:
its field a single line with an empty draft — which also drains the send
disc to the disabled ink. The whole card is the field's click target:
a click anywhere on it that is not a control focuses the field, and the
pointer reads as a text cursor over all of it.
pointer reads as a text cursor over all of it. For simpler flows there is
also a [compact](#compact) layout: a single-row pill that opens into this
card as the draft grows.

## Composed

Expand Down Expand Up @@ -63,23 +65,97 @@ FlowComposer(
)
```

## Compact

`layout: FlowComposerLayout.compact` folds the card into a single row: the
attach button and `leadingActions` on the left, the field in the middle,
`trailingActions` and send on the right, and no action row beneath. Only
what you wire is drawn, so the bare form is a field and a send button in a
50px pill.

<FlowDemo demo="composer" variant="compact" height={300} title="The compact composer" />

```dart title="The same composer, one row"
FlowComposer(
layout: FlowComposerLayout.compact,
onSend: send,
onAttachmentsPicked: (picked) => setState(() => pending.addAll(picked)),
attachTooltip: 'Attach files',
attachments: pending,
onRemoveAttachment: removePending,
)
```

Attachments don't change the layout: the pending strip sits above the row
inside the card, and the field stays inline with the controls. The pill
opens into the full card by itself only when the draft needs the room, once
it wraps past one line from a long line or an explicit newline. It folds
back when the draft is empty, which is what a send leaves behind with
`clearOnSend`, so deleting back to one line never moves the controls out
from under the caret. The field keeps its focus and its draft through both.

`layout` is the form the composer takes with an empty draft. To open the
card by hand, from a button of your own, flip it to
`FlowComposerLayout.expanded`.

<Aside type="tip" title="Mind the row on phones">
Everything in a compact composer shares one row, so keep inline actions to
icons on narrow screens. `FlowPill` already drops its label on phones; the
model selector's label is the one to watch.
</Aside>

## Fixed height

The card normally hugs its draft: one line when empty, growing to
`maxLines`. For a composer at a fixed height, set `expands: true` and size
it from the parent. The card fills the height it is given, and the field
takes the space above the action row and scrolls inside it, so `maxLines`
no longer applies.

<FlowDemo demo="composer" variant="fixed" height={340} title="A composer at a fixed height" />

```dart title="Filling a 240px box"
SizedBox(
height: 240,
child: FlowComposer(
expands: true,
onSend: send,
),
)
```

Like `TextField.expands`, it needs a bounded height from its parent, so
inside a `Column` wrap it in `Expanded`. It works with the full card only:
the compact layout is a single row, and combining the two asserts.
`FlowChatView` docks its composer at its natural height, so `expands` is
for a composer you place and size yourself.

## Send and stop

`isStreaming` drives the send button: `false` shows send, `true` swaps it
for stop and routes taps to `onStop`. Flip it from your generation state
and the composer stays honest about what a tap will do.

Name the two buttons with `sendTooltip` and `stopTooltip`. flow_ui ships
no copy, so the words are yours: they show on hover and are what a screen
reader announces, and both buttons read as buttons with their enabled
state.

<FlowDemo demo="composer" variant="streaming" height={300} title="The stop state" />

## Typing

On hardware keyboards **Enter sends** and **Shift+Enter** inserts a
newline — `submitOnEnter: false` opts out; mobile soft keyboards keep
their newline key and send from the button. The field grows with the draft
their newline key and send from the button. While an input method is
composing, Enter confirms the composition instead of sending, and while a
reply streams it does nothing rather than adding a newline. The field grows with the draft
to `maxLines` (6) and scrolls beyond it, and `clearOnSend` empties it
after a successful send. `onSend` receives the trimmed text, and fires
empty only when something is attached — a picture with no caption is
still a message.
still a message. In the [compact](#compact) layout the field holds a
single line, and the pill opens into the full card the moment the draft
wraps.

## Attach

Expand Down Expand Up @@ -345,9 +421,16 @@ wins field by field.
## Key API

- `onSend` — required; receives the trimmed draft, never empty text.
- `isStreaming` + `onStop` — the send/stop swap.
- `isStreaming` + `onStop` — the send/stop swap; `sendTooltip` and
`stopTooltip` name the two buttons.
- `enabled`, `submitOnEnter`, `clearOnSend`, `maxLines` — field behavior;
all default on, with a 6-line growth cap.
- `layout` — `FlowComposerLayout.expanded`, the card and the default, or
`FlowComposerLayout.compact`, the single-row pill that opens into the
card as the draft grows and folds back once it is empty.
- `expands` — fills the height the parent gives the card, the field taking
the space above the action row and scrolling inside it; needs a bounded
height and the full card.
- `leadingActions` / `trailingActions` — widget slots on the input rail;
any widget works, the flow_ui menus just fit the design out of the box.
- `attachments`, `onRemoveAttachment`, `removeAttachmentTooltip` — the
Expand Down
6 changes: 6 additions & 0 deletions packages/flow_ui/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,12 @@
- Makes `FlowTypography.standard` and `FlowTheme.typography` `const` again, and `FlowTypography.code` / `codeInline` plain fields with const defaults.
- Deprecates `FlowTypography.recut` in favour of `copyWith(fontWeight:, fontStyle:)` — the bundled families declare every cut, so the engine resolves it from the weight on the style. Removed in 0.6.0.
- Adds FlowTool, the tool-call card: one mark morphs with the call's status (a still asterisk while staged, turning while it runs, a check or an error glyph once settled), the host's title and primary argument sit on the row, and the input and output render as code blocks behind a disclosure, with a failure readable without a tap. FlowToolPart renders it in a thread, keyed by the provider's call id and copying through FlowThread.onCodeCopy; FlowToolStyle joins the component styles with a FlowTheme.toolStyle default. Metrics are provisional pending a design frame.
- Adds a compact layout to FlowComposer: `layout: FlowComposerLayout.compact` folds the card into a single-row pill, with the attach button, `leadingActions`, the field, `trailingActions` and send inline, no action row, and nothing drawn for what is not wired. Pending attachments sit in a strip above the row, the field still inline. It opens into the full card by itself once the draft wraps past one line, and folds back when the draft is empty, keeping the field's focus and draft through both. `FlowComposerLayout.expanded`, the default, is unchanged. Metrics are provisional pending a design frame.
- Tightens the full FlowComposer card: the field no longer keeps a 38px floor under a one-line draft, and the gap above the action row drops from 16 to 12, so an empty card stands at about 95px instead of 116.
- Adds FlowComposer.expands for a composer at a fixed height: the card fills the height its parent gives it, and the field takes the space above the action row and scrolls inside it. Like TextField.expands it needs a bounded height, and it works with the full card only.
- Adds FlowComposer.sendTooltip and stopTooltip, host-localized names for the send and stop buttons. Both now read as buttons with their enabled state, so a disabled send button no longer drops out of the accessibility tree.
- Fixes Enter sending the draft while an input method is still composing: Enter now confirms the composition, as in any text field.
- Enter no longer adds a newline while a reply streams, or when held down after a send; Shift+Enter still inserts one.

## 0.3.0

Expand Down
4 changes: 3 additions & 1 deletion packages/flow_ui/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@
| [`FlowToast`](https://flowui.stac.dev/components/toast) | Floating notice: a glyph, one wrapping line and a cross on a frosted card; `showFlowToast` floats it in the nearest Overlay, stacked and auto-dismissing, with a handle to dismiss it early |
| [`FlowTool`](https://flowui.stac.dev/components/tool) | Tool-call card: one status mark that settles from a turning asterisk into a check or an error glyph, the call's title and primary argument, and its input and output behind a disclosure; tool parts render it in a thread |
| [`FlowMessageActions`](https://flowui.stac.dev/components/message-actions) | Copy / regenerate / edit / feedback row under a message |
| [`FlowComposer`](https://flowui.stac.dev/components/composer) | Multiline input with send/stop, attachments strip, the platform's file dialog (`showFlowAttachmentPicker` from your own menu, or a built-in attach button), image paste and card-scoped drop (web), and leading/trailing action slots |
| [`FlowComposer`](https://flowui.stac.dev/components/composer) | Multiline input with send/stop, attachments strip, the platform's file dialog (`showFlowAttachmentPicker` from your own menu, or a built-in attach button), image paste and card-scoped drop (web), and leading/trailing action slots; a full card, or a compact single-row pill that opens into it as the draft grows |
| [`FlowMenu`](https://flowui.stac.dev/components/menu) | Icon-triggered menu with groups, submenus, and toggles — anchored card on desktop, bottom sheet on phones |
| [`FlowModelSelector`](https://flowui.stac.dev/components/model-selector) | Model picker with effort and overflow submenus, sheet on phones |
| [`FlowPill`](https://flowui.stac.dev/components/pill) | Removable pill for an enabled tool or mode in the composer's action row — label auto-drops on phones |
Expand Down Expand Up @@ -187,6 +187,8 @@ class _ChatPageState extends State<ChatPage> {
isStreaming: _generating,
onSend: _send,
onStop: myBackend.stop,
sendTooltip: 'Send',
stopTooltip: 'Stop',
),
),
);
Expand Down
2 changes: 2 additions & 0 deletions packages/flow_ui/example/lib/main.dart
Original file line number Diff line number Diff line change
Expand Up @@ -499,6 +499,8 @@ class _ChatScreenState extends State<ChatScreen> {
isStreaming: _generating,
onSend: _send,
onStop: _stop,
sendTooltip: 'Send',
stopTooltip: 'Stop',
// Picking goes through the "+" menu below; paste and drop land
// in the same place — three ways in, one handler.
onAttachmentsPasted: _addAttachments,
Expand Down
Loading
Loading