Skip to content
Open
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: 14 additions & 1 deletion docs/components/accordion.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,9 @@ An accordion item is one panel with two parts inside it:
`containerEffects` for layered fills, strokes, and backdrop blur painted with
it.
- **`trigger`** — the always-visible header row (leading icon, title, trailing
icon). It owns only its own internal layout, such as padding and spacing.
icon). It owns only its own internal layout, such as padding and spacing. Use
`triggerEffects` for an outline, such as a focus ring, drawn around the
header alone.
- **`content`** — the expanded body. It owns its own padding and any interior
divider, drawn as a foreground border so it does not inset the body by a pixel.

Expand Down Expand Up @@ -339,6 +341,17 @@ Configures layered fills, strokes, and backdrop blur painted with `container`.

Configures the trigger row style.

#### `triggerEffects(RemixBoxEffectsMix value)`

Configures an outline and paint layers drawn around the trigger row only,
without taking layout space. Use it in `onFocusVisible` for a focus ring that
does not wrap the revealed content, unlike `containerEffects`. When the
`container` clips, an outside ring is cropped at the panel edge; use a negative
`outlineOffset` to draw it inside the trigger. A custom `builder` replaces the
default trigger row, but its widget is still wrapped in `triggerEffects`, so the
ring shows around a custom trigger. The `trigger` box (padding, decoration) does
not apply to it.

#### `leadingIcon(IconStyler value)`

Configures the leading icon style.
Expand Down
5 changes: 5 additions & 0 deletions docs/components/disclosure.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -95,6 +95,11 @@ and `borderRadius` to the trigger. Style the other parts explicitly:
- `container(...)` — frame around trigger and content
- `containerEffects(...)` — shadows, strokes, backdrop blur
- `trigger(...)` / `content(...)`
- `triggerEffects(...)` — an outline, such as a focus ring, around the trigger
alone; `containerEffects` wraps the revealed content too. When `container`
clips, an outside ring is cropped at the panel edge; use a negative
`outlineOffset` to draw it inside the trigger. It also wraps the output of a
custom `triggerBuilder`.
- `onExpanded(...)` / `onCollapsed(...)`
- `onHovered`, `onPressed`, `onFocused`, `onFocusVisible`, `onDisabled`

Expand Down
36 changes: 36 additions & 0 deletions docs/components/menu.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -318,6 +318,42 @@ that panel directly contains a checkbox or radio group. Nested submenu panels
decide this independently. A custom `trailingIcon` replaces the default
directional submenu chevron.

## Focus rings

`MenuTriggerStyler.containerEffects` and `MenuItemStyler.containerEffects`
paint an outline around the trigger or a row without taking layout space, so
focus does not resize either. `MenuStyler.containerEffects` is separate: it
paints the popup panel.

```dart
import 'package:flutter/material.dart';
import 'package:remix/remix.dart';

final menuRing = RemixBoxEffectsMix(
outline: BorderSideMix(color: Colors.indigo, width: 2),
outlineOffset: 2,
);

final ringedMenuStyle = MenuStyler()
.trigger(
MenuTriggerStyler().onFocusVisible(
MenuTriggerStyler().containerEffects(menuRing),
),
)
.item(
MenuItemStyler().onFocusVisible(
MenuItemStyler().containerEffects(menuRing),
),
);
```

A popup that clips (`clipBehavior` on `overlay`) crops an outside row ring at
its edge. Pad the popup by at least the ring's offset plus width, or use a
negative `outlineOffset` to draw the row ring inside the row.

A `RemixMenuTrigger.builder` that returns a different widget instead of its
child replaces the styled trigger and its effects.

## Fortal widgets

The application-owned Fortal preset provides a themed widget for this component:
Expand Down
7 changes: 7 additions & 0 deletions docs/components/select.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -491,6 +491,13 @@ Sets single icon shadow

Configures the menu item container style.

#### `containerEffects(RemixBoxEffectsMix value)`

Configures an outline and paint layers drawn around the item row without
taking layout space, such as a focus ring in `onFocusVisible`. When the popup
content clips (`clipBehavior` on its container), an outside ring is cropped at
the popup edge; use a negative `outlineOffset` to draw it inside the row.

#### `text(TextStyler value)`

Sets menu item text styling.
Expand Down
7 changes: 7 additions & 0 deletions docs/components/sidebar.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -265,6 +265,13 @@ A destination's `style` merges after the panel's default destination style, so
local exceptions retain shared hover, focus, selected, and disabled variants.
A raw `SidebarSpec` is authoritative and bypasses both fluent layers.

`SidebarDestinationStyler` is a `ToggleStyler`, so `containerEffects` draws an
outline around a destination without taking layout space, collapsed or expanded.
Put a focus ring in an `onFocusVisible` variant. In a bounded-height sidebar the
destinations scroll, and the scroll region crops a ring drawn outside a
destination at its edge: keep the ring within the `content` padding, or use a
negative `outlineOffset`.

<CodeGroup title="Fluent styling" defaultLanguage="dart">
```dart
import 'package:flutter/material.dart';
Expand Down
6 changes: 6 additions & 0 deletions docs/components/tabs.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -356,6 +356,12 @@ Methods on `TabStyler` configure each interactive tab item.

Configures the tab item flex box style.

#### `containerEffects(RemixBoxEffectsMix value)`

Configures an outline and paint layers drawn around the tab without taking
layout space, such as a focus ring in `onFocusVisible`. A `builder` that
returns a different widget replaces the styled container and its effects.

#### `label(TextStyler value)`

Configures the tab label style.
Expand Down
5 changes: 5 additions & 0 deletions docs/components/toggle.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -193,6 +193,11 @@ Optional. Cursor when hovering over the toggle.

Configures the toggle container style.

#### `containerEffects(RemixBoxEffectsMix value)`

Configures an outline and paint layers drawn around the toggle without taking
layout space, such as a focus ring in `onFocusVisible`.

#### `label(TextStyler value)`

Configures the label text style.
Expand Down
37 changes: 37 additions & 0 deletions docs/components/toggle_group.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -137,6 +137,43 @@ Widget buildStatusToggleGroup({
```
</CodeGroup>

## Focus rings

`ToggleGroupItemStyler.containerEffects` paints an outline around the item
without taking layout space, so focus does not resize the item or shift its
neighbors. Put it in an `onFocusVisible` variant.

A group usually clips its items to a rounded outline, and the clip also crops
the ring of an end item when the ring is drawn outside the item. Draw the ring
inside instead: give `outlineOffset` the negative of the ring width.

<CodeGroup title="Inset focus ring" defaultLanguage="dart">
```dart
import 'package:flutter/material.dart';
import 'package:remix/remix.dart';

final insetRing = RemixBoxEffectsMix(
outline: BorderSideMix(color: Colors.indigo, width: 2),
outlineOffset: -2,
);

final segmentedStyle = ToggleGroupStyler(
container: FlexBoxStyler()
.borderRadius(.circular(8))
.clipBehavior(.antiAlias),
item: ToggleGroupItemStyler()
.padding(.horizontal(12))
.padding(.vertical(8))
.onFocusVisible(ToggleGroupItemStyler().containerEffects(insetRing)),
);
```
</CodeGroup>

A group that does not clip can use a positive offset to draw the ring outside
the item. Items paint in order, so a filled item after the focused one covers
that side of an outside ring; in joined rows use the inset ring, or leave a gap
of at least the ring's offset plus width.

## Constructor

<CodeGroup title="RemixToggleGroup" defaultLanguage="dart">
Expand Down
24 changes: 24 additions & 0 deletions packages/remix/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,27 @@
## Unreleased

### Features

- Add `containerEffects` to `TabStyler`, `ToggleStyler`,
`ToggleGroupItemStyler`, `SelectMenuItemStyler`, `MenuItemStyler`, and
`MenuTriggerStyler`, and `triggerEffects` to `AccordionStyler` and
`DisclosureStyler`. An outline or paint layer draws around the part without
taking layout space, so a focus ring no longer resizes the part or shifts its
neighbors. `RemixSidebar` destinations, which use `ToggleStyler` through
`SidebarDestinationStyler`, draw `containerEffects` too. On the accordion and
disclosure, `containerEffects` still covers the whole panel and
`triggerEffects` covers only the trigger, including the widget a custom
`RemixAccordion.builder` or `RemixDisclosure.triggerBuilder` returns. A toggle
group styled with `clipBehavior` (every shipped preset sets one) crops an
outside ring on its end items; use a negative `outlineOffset` there.

### Fixes

- Tab, toggle, toggle group item, select menu item, menu item, menu trigger,
accordion trigger, and disclosure trigger content keeps its state when a
style adds or removes a decoration such as a focus ring. Previously such a
change rebuilt the part's content from scratch.

## 1.0.0-beta.13

### Breaking changes
Expand Down
23 changes: 23 additions & 0 deletions packages/remix/lib/src/components/accordion/accordion.g.dart

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

12 changes: 12 additions & 0 deletions packages/remix/lib/src/components/accordion/accordion_spec.dart
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,12 @@ class AccordionSpec with _$AccordionSpec {
@override
@MixableField(forwardStyler: true)
final StyleSpec<FlexBoxSpec> trigger;

/// Outline and paint layers drawn around [trigger] only, so a focus ring
/// does not wrap the revealed content.
@override
@MixableField(setterType: RemixBoxEffectsMix)
final RemixBoxEffectsSpec? triggerEffects;
@override
final StyleSpec<IconSpec> leadingIcon;
@override
Expand All @@ -36,6 +42,7 @@ class AccordionSpec with _$AccordionSpec {
StyleSpec<BoxSpec>? container,
this.containerEffects,
StyleSpec<FlexBoxSpec>? trigger,
this.triggerEffects,
StyleSpec<IconSpec>? leadingIcon,
StyleSpec<TextSpec>? title,
StyleSpec<IconSpec>? trailingIcon,
Expand All @@ -60,6 +67,11 @@ class AccordionSpec with _$AccordionSpec {
other.containerEffects,
t,
),
triggerEffects: RemixBoxEffectsSpec.lerpNullable(
triggerEffects,
other.triggerEffects,
t,
),
);
}
}
Expand Down
Loading
Loading