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
15 changes: 15 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,20 @@
# Changelog

## 0.3.0 (unreleased)

- **Component styles** — Material's component-theme tier, on flow_ui's
tokens: every major widget now takes an optional style object of
color and text overrides (`FlowComposerStyle`, `FlowMessageStyle`,
`FlowMarkdownStyle`, `FlowCodeBlockStyle`, `FlowErrorStateStyle`,
`FlowMessageActionsStyle`, `FlowPillStyle`, `FlowSuggestionStyle`,
joining `FlowMenuStyle`), and `FlowTheme` carries an app-wide default
for each (`FlowTheme.markdownStyle`, …). Resolution is field by field —
the widget's style wins over the theme's, tokens beneath both; text
fields merge over their role's base. `FlowMarkdownStyle` opens up the
markdown surface per element: heading cuts, the link color, the
inline-code chip, quote, table and rule inks. All additive — nothing
breaks.

## 0.2.0

- **Typography** — title and label roles now carry an emphasised cut
Expand Down
11 changes: 11 additions & 0 deletions docs/src/content/docs/components/code-block.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -148,6 +148,17 @@ FlowTheme(
)
```

## Restyling

`FlowCodeBlockStyle` overrides the block's chrome without touching the
global tokens: `backgroundColor` (transparent lets the code sit on the
host's own surface), `borderColor` (transparent renders it borderless),
`hoverBorderColor`, `headerStyle`, and `codeStyle` (both merged over
their defaults). Token inks inside the code stay with the theme's
`FlowSyntaxColors`. Install one on `FlowTheme.codeBlockStyle` for every
block — markdown fences included; the widget's `style` wins field by
field.

## Key API

- `FlowCodeBlock` — `code`, `language` (id or alias, case-insensitive),
Expand Down
10 changes: 10 additions & 0 deletions docs/src/content/docs/components/composer.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,16 @@ The composer renders a pending-attachments row above the field — pass
`attachments:` and `onRemoveAttachment:`. The [Attachments](/components/attachments/)
page shows the full flow, from the "+" menu to the sent message.

## Restyling

`FlowComposerStyle` overrides the composer's colors without touching the
global tokens: `backgroundColor` (the raised card's fill), `outlineColor`
(flattens the gradient hairline to one solid color in every state),
`sendBackgroundColor` and `sendForegroundColor` (the send/stop disc),
`textStyle` (merged over the field's default), and `hintColor`. Install
one on `FlowTheme.composerStyle` for every composer; the widget's `style`
wins field by field.

## Key API

- `onSend` — required; receives the trimmed draft, never empty text.
Expand Down
8 changes: 8 additions & 0 deletions docs/src/content/docs/components/error-state.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -106,6 +106,14 @@ FlowChatView(
)
```

## Restyling

`FlowErrorStateStyle` overrides the card's look without touching the
global tokens: `backgroundColor`, `borderColor`, `glyphColor`,
`titleStyle`, and `messageStyle` (both merged over their defaults).
Install one on `FlowTheme.errorStateStyle` for every error card — failed
turns in a thread included; the widget's `style` wins field by field.

## Key API

- `FlowErrorState` — `title`, `message`, `onRetry` (null hides the
Expand Down
12 changes: 12 additions & 0 deletions docs/src/content/docs/components/markdown.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,18 @@ contract code parts use — one `onCodeCopy` handler and one
streaming its copy affordance stays hidden, exactly like a streaming
`FlowCodePart`.

## Restyling

`FlowMarkdownStyle` overrides the document's per-element styling without
touching the global tokens: `h1Style` through `h6Style` (merged over each
level's role), `linkColor`, `inlineCodeStyle` and `codeChipColor` (the
face and the painted chip), `quoteColor` and `quoteBarColor`,
`tableHeaderStyle`, `tableCellStyle`, `tableBorderColor` and
`tableDividerColor`, and `ruleColor`. Install one on
`FlowTheme.markdownStyle` for every markdown surface — assistant turns in
a thread included; the widget's `markdownStyle` wins field by field.
Fences restyle through `FlowCodeBlockStyle`.

## Key API

- `text` — the markdown source received so far.
Expand Down
15 changes: 9 additions & 6 deletions docs/src/content/docs/components/menu.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -82,12 +82,15 @@ its `localizationsDelegates`.

## Restyling

`FlowMenuStyle` overrides the menu's colors and metrics per instance
without touching the global tokens: `backgroundColor`, `borderColor`,
`separatorColor`, `hoverColor`, `labelStyle`, `descriptionStyle`,
`iconColor`, `checkColor`, `accentColor`, `menuRadius`, `sheetRadius`,
`minWidth`, and `barrierColor`. Paddings are baked spec metrics, not style
fields.
`FlowMenuStyle` overrides the menu's colors and metrics without touching
the global tokens: `backgroundColor`, `borderColor`, `separatorColor`,
`hoverColor`, `labelStyle`, `descriptionStyle`, `iconColor`, `checkColor`,
`accentColor`, `menuRadius`, `sheetRadius`, `minWidth`, and
`barrierColor`. Paddings are baked spec metrics, not style fields.

Install one on `FlowTheme.menuStyle` to restyle every menu and model
selector app-wide; a widget's own `menuStyle` wins field by field, and
the tokens sit beneath both.

## Key API

Expand Down
8 changes: 8 additions & 0 deletions docs/src/content/docs/components/message-actions.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,14 @@ FlowMessage(
)
```

## Restyling

`FlowMessageActionsStyle` overrides the strip's inks without touching the
global tokens: `iconColor`, `hoverIconColor`, `selectedColor`, and
`hoverColor` (the wash behind a hovered action). Install one on
`FlowTheme.messageActionsStyle` for every actions row; the widget's
`style` wins field by field.

## Key API

- `FlowMessageAction.copy / .regenerate / .edit / .thumbUp / .thumbDown` —
Expand Down
9 changes: 9 additions & 0 deletions docs/src/content/docs/components/message-thread.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -102,6 +102,15 @@ extension seam — it carries a `type` your builder switches on and an
arbitrary `data` payload, so the host injects its own content (a tool
card, a chart) without the library knowing what it is.

## Restyling

`FlowMessageStyle` overrides the user bubble without touching the global
tokens: `bubbleColor` and `bubbleTextColor` (a failed turn keeps the
error treatment regardless). Install one on `FlowTheme.messageStyle` for
every message; a `FlowMessage`'s own `style` wins field by field.
Assistant prose is restyled through `FlowMarkdownStyle`, code through
`FlowCodeBlockStyle` — see those components' pages.

## Key classes

- `FlowThread` — the scrolling conversation; give it a `ScrollController`
Expand Down
8 changes: 8 additions & 0 deletions docs/src/content/docs/components/pill.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,14 @@ keep concurrent pills few:

<FlowDemo demo="pill" variant="composer" height={260} title="Toggled from the add menu" />

## Restyling

`FlowPillStyle` overrides the pill's colors without touching the global
tokens: `backgroundColor`, `hoverColor`, `borderColor`, `iconColor`,
`labelStyle` (merged over the default label), and `removeColor`. Install
one on `FlowTheme.pillStyle` for every pill; the widget's `style` wins
field by field.

## Key API

- `icon` — always drawn; the pill's whole identity in the icon-only
Expand Down
8 changes: 8 additions & 0 deletions docs/src/content/docs/components/suggestions.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -114,6 +114,14 @@ Column(
)
```

## Restyling

`FlowSuggestionStyle` overrides a suggestion's colors without touching
the global tokens: `backgroundColor`, `borderColor` (the outlined form's
hairline), `hoverColor`, `foregroundColor`, and `labelStyle` (merged over
the default). Install one on `FlowTheme.suggestionStyle` for every
suggestion; the widget's `style` wins field by field.

## Key API

- `FlowSuggestion` — `label`, optional `icon`, `outlined`, `onTap`,
Expand Down
25 changes: 25 additions & 0 deletions docs/src/content/docs/theming.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -117,6 +117,31 @@ FlowTheme(
carries — size, weight, line height, tracking — and takes a `package:` for
a font that ships inside one.

## Component styles

Between the tokens and a single widget sit the component styles —
Material's component-theme tier. Each major widget has a `FlowXStyle`
data bag of optional overrides (`FlowMenuStyle`, `FlowMarkdownStyle`,
`FlowComposerStyle`, `FlowMessageStyle`, `FlowCodeBlockStyle`,
`FlowErrorStateStyle`, `FlowMessageActionsStyle`, `FlowPillStyle`,
`FlowSuggestionStyle`), and the theme can carry an app-wide default for
each:

```dart title="Restyle every instance once"
FlowTheme(
colors: FlowColors.light,
markdownStyle: const FlowMarkdownStyle(linkColor: brandBlue),
codeBlockStyle: const FlowCodeBlockStyle(borderColor: Colors.transparent),
)
```

Resolution is field by field: a widget's own style object wins over the
theme's, and any field neither sets falls through to the tokens. Text
style fields merge over their role's base, so setting one property never
restates the rest. Every field is documented on its style class, with
the token it defaults to. The `Restyling` section on each component's
page lists its fields.

## Spacing and radii are not tokens

Following Material's structure, each component bakes its own metrics from
Expand Down
10 changes: 9 additions & 1 deletion lib/flow_ui.dart
Original file line number Diff line number Diff line change
Expand Up @@ -17,18 +17,26 @@ export 'src/widgets/flow_attachment_group.dart';
export 'src/widgets/flow_attachment_preview.dart';
export 'src/widgets/flow_chat_view.dart';
export 'src/widgets/flow_code_block.dart';
export 'src/styles/flow_code_block_style.dart';
export 'src/widgets/flow_composer.dart';
export 'src/styles/flow_composer_style.dart';
export 'src/widgets/flow_error_state.dart';
export 'src/styles/flow_error_state_style.dart';
export 'src/widgets/flow_greeting.dart';
export 'src/widgets/flow_markdown.dart';
export 'src/styles/flow_markdown_style.dart';
export 'src/widgets/flow_menu.dart';
export 'src/widgets/flow_menu_style.dart';
export 'src/styles/flow_menu_style.dart';
export 'src/widgets/flow_message.dart';
export 'src/widgets/flow_message_actions.dart';
export 'src/styles/flow_message_actions_style.dart';
export 'src/styles/flow_message_style.dart';
export 'src/widgets/flow_model_selector.dart';
export 'src/widgets/flow_pill.dart';
export 'src/styles/flow_pill_style.dart';
export 'src/widgets/flow_shimmer_text.dart';
export 'src/widgets/flow_streaming_text.dart';
export 'src/widgets/flow_suggestion.dart';
export 'src/styles/flow_suggestion_style.dart';
export 'src/widgets/flow_thinking_indicator.dart';
export 'src/widgets/flow_thread.dart';
96 changes: 96 additions & 0 deletions lib/src/styles/flow_code_block_style.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
import 'package:material_ui/material_ui.dart';

/// Host overrides for [FlowCodeBlock]'s look, on top of the theme tokens.
///
/// Every field is optional; null falls back to the token-derived default
/// noted on the field. Token inks inside the code come from the theme's
/// `FlowSyntaxColors`, not from here. Install one on
/// [FlowTheme.codeBlockStyle] to restyle every block — fences in markdown
/// included; a widget's own `style` wins field by field:
///
/// ```dart
/// FlowCodeBlock(
/// code: source,
/// language: 'dart',
/// style: const FlowCodeBlockStyle(
/// backgroundColor: Colors.transparent,
/// borderColor: Colors.transparent,
/// ),
/// )
/// ```
@immutable
class FlowCodeBlockStyle {
const FlowCodeBlockStyle({
this.backgroundColor,
this.borderColor,
this.hoverBorderColor,
this.headerStyle,
this.codeStyle,
});

/// The card's fill. Defaults to `surfaceContainerLowest`. Transparent
/// lets the code sit directly on the host's own surface.
final Color? backgroundColor;

/// The card's hairline. Defaults to `outlineVariant`; transparent
/// renders the block borderless.
final Color? borderColor;

/// The hairline while hovered. Defaults to `surfaceContainerHighest`;
/// when only [borderColor] is set, hover keeps that color instead of
/// firming.
final Color? hoverBorderColor;

/// Merged over the header label's default `bodySmall` +
/// `onSurfaceMuted` style.
final TextStyle? headerStyle;

/// Merged over the code's default `code` + `onSurface` style. Token
/// colors from `FlowSyntaxColors` still apply per span.
final TextStyle? codeStyle;

/// A copy where [other]'s fields win over this style's.
FlowCodeBlockStyle merge(FlowCodeBlockStyle? other) {
if (other == null) return this;
return FlowCodeBlockStyle(
backgroundColor: other.backgroundColor ?? backgroundColor,
borderColor: other.borderColor ?? borderColor,
hoverBorderColor: other.hoverBorderColor ?? hoverBorderColor,
headerStyle: other.headerStyle ?? headerStyle,
codeStyle: other.codeStyle ?? codeStyle,
);
}

/// Linear interpolation, for theme transitions. A null [other] returns
/// this style unchanged.
FlowCodeBlockStyle lerp(FlowCodeBlockStyle? other, double t) {
if (other == null) return this;
return FlowCodeBlockStyle(
backgroundColor: Color.lerp(backgroundColor, other.backgroundColor, t),
borderColor: Color.lerp(borderColor, other.borderColor, t),
hoverBorderColor: Color.lerp(hoverBorderColor, other.hoverBorderColor, t),
headerStyle: TextStyle.lerp(headerStyle, other.headerStyle, t),
codeStyle: TextStyle.lerp(codeStyle, other.codeStyle, t),
);
}

@override
bool operator ==(Object other) {
if (identical(this, other)) return true;
return other is FlowCodeBlockStyle &&
other.backgroundColor == backgroundColor &&
other.borderColor == borderColor &&
other.hoverBorderColor == hoverBorderColor &&
other.headerStyle == headerStyle &&
other.codeStyle == codeStyle;
}

@override
int get hashCode => Object.hash(
backgroundColor,
borderColor,
hoverBorderColor,
headerStyle,
codeStyle,
);
}
Loading