Skip to content

feat: modernize drawer - #5052

Open
ziarno wants to merge 13 commits into
callstack:mainfrom
ziarno:feat/modernize-drawer
Open

feat: modernize drawer#5052
ziarno wants to merge 13 commits into
callstack:mainfrom
ziarno:feat/modernize-drawer

Conversation

@ziarno

@ziarno ziarno commented Aug 16, 2026

Copy link
Copy Markdown

Motivation

Spec alignment

  • Trailing padding. Drawer.Item reserved 36dp trailing / 28dp leading. Both are now 28dp per spec, so items sit 8dp wider.
  • Active label weight. Active Drawer.Item labels now use labelLargeEmphasized (700). Inactive labels stay at 500.
  • Section divider colour. Drawer.Section drew its divider in a fixed Palette.neutralVariant50 grey that ignored the theme and was wrong in dark mode. It now uses theme.colors.outlineVariant, like every other Divider.
  • Active indicator shape. Drawer.CollapsedItem computed its radius as itemSize / 2; it now resolves the full shape token through the theme.
  • Motion. The collapsed item's indicator animation was a hardcoded 150ms linear ramp. It now uses the short3 duration and standard easing tokens.
  • Disabled state. Disabled destinations were visually identical to enabled ones. They are now dimmed via the shared state.opacity.disabled token.
  • Keyboard focus. Drawer.Item gains a focus ring in theme.colors.secondary.

Token extraction

New src/components/Drawer/tokens.ts exports DrawerItemTokens, DrawerSectionTokens and DrawerCollapsedItemTokens.

Supporting theme changes

  • TypescaleKey gains the 15 *Emphasized keys. The values already existed in the token set — only the types were missing.
  • state.focusIndicator gains innerOffset, needed to inset the focus ring.

A note on drawer width. The example app deliberately sets no drawer width. react-native-drawer-layout's default — min(360dp, containerWidth − 56dp) — is MD3's 360dp container plus MD2's 56dp edge gap; a flat 360dp leaves almost no scrim to tap and dismiss on narrow phones (also noticed by flutter/flutter#123380).

Related issue

Closes #4981

Test plan

  • yarn test (Drawer unit tests + snapshots updated in this branch)
  • Visual check of Drawer.Item, Drawer.CollapsedItem and Drawer.Section in the example app (expanded, collapsed/rail, active/inactive/disabled states) in both light and dark themes

Filip Jarno and others added 13 commits August 11, 2026 10:56
Bring `Drawer.Item` and `Drawer.Section` in line with the current
`md.comp.navigation-drawer` token set and extract component-specific
tokens. The public API is unchanged.

- Extract `Drawer/tokens.ts` with `DrawerItemTokens`,
  `DrawerSectionTokens` and `DrawerCollapsedItemTokens`. The last one
  cites `md.comp.navigation-rail`, which is what actually specifies
  `Drawer.CollapsedItem`.
- Correct the item's trailing padding from 36dp to the spec'd 28dp, so
  both container insets match.
- Emphasize the active destination's label, per
  `md.comp.navigation-drawer.active.label-text.weight`.
- Add the MD3 focus indicator. It uses the *inner* offset, so the ring
  is drawn inside the active indicator: destinations sit flush, and an
  outer ring would overlap its neighbours.
- Resolve the active indicator radius from the `full` shape token
  instead of hardcoding `extraLarge`.
- Drop the hardcoded `Palette.neutralVariant50` section divider colour,
  which ignored the theme and was wrong in dark mode. Drawer divider
  tokens are deprecated in MD3, so the standalone divider spec
  (`outlineVariant`, 1dp) applies.
- Use `marginStart`/`marginEnd` for directional spacing. React Native
  auto-swaps left/right in RTL, but react-native-web does not.
- Collapse the leftover v2/v3 style splits and the duplicated typescale
  spread in `Drawer.Item` and `Drawer.Section`.

Supporting theme changes, both additive:

- `TypescaleKey` gains the 15 `*Emphasized` keys. The values already
  existed in `md.sys.typescale` but no type exposed them.
- `md.sys.state.focusIndicator` gains `innerOffset`.

`Drawer.CollapsedItem` renders identically; only its magic numbers moved
into tokens.

Refs: callstack#4981

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
- Add a `Drawer` screen to the example app, inside a 360dp surface
  standing in for the drawer container so the active indicator sits at
  its spec'd proportions. Covers active/inactive destinations, no icon,
  a truncated label, disabled, both trailing-slot shapes, sections
  separated by a divider, and collapsed destinations.
- Give the example app's own drawer the MD3 container: 360dp wide,
  `surfaceContainerLow`, and rounded corners on the ending edge only.
  Collapse its leftover v3 style split while there.
- Document the section divider and focus indicator colour roles.
- Add a `Drawer` section to the 6.x migration guide. The API is
  unchanged, but four visual changes are worth calling out for anyone
  pixel-matching against 5.x.
- Recapture `drawer-item` and `drawer-section` screenshots against the
  new rendering. Both are now device-agnostic crops; the old
  `drawer-section` shot was an Android emulator window, which cannot be
  reproduced from an iOS Simulator. `drawer-collapsed` is unchanged,
  since `Drawer.CollapsedItem` renders identically.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Add `.agents/CONTEXT.md`, a glossary of the Material Design terms this
project keeps getting wrong in review — the ref/sys/comp token tiers,
state layer, focus indicator, active indicator, standard vs modal
drawer.

Add `.agents/adr/` with the three Drawer decisions a future reader would
otherwise have to reverse-engineer: why Paper ships drawer destinations
but no container, why `TypescaleKey` grew the emphasized keys, and why
`Drawer.Item` alone among modernized components keeps `TouchableRipple`
rather than rendering its own state layer.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The hand-maintained `docs/src/data/themeColors.ts` still referenced v5 color
roles and pre-v6 component behaviour, so the generated `ThemeColorsTable`
documented roles that no longer exist on `ThemeColors`.

Removed roles that are not on the type:
- `surfaceDisabled` / `onSurfaceDisabled` - v6 draws disabled states from
  `onSurface` at a reduced opacity
- `backdrop` (Modal) - now `scrim` at 32%
- `elevarion.level3` - typo, and the surface FAB variant no longer exists

Re-checked every remaining entry against what the component renders:
- Appbar / Appbar.Header elevated: `surfaceContainer`, not `elevation.level2`
- Button elevated: `surfaceContainerLow`; outlined border: `outlineVariant`
- Dialog and Searchbar: `surfaceContainerHigh`
- IconButton outlined border: `outlineVariant`
- SegmentedButtons checked border: `outline`; disabled border: `outlineVariant`
- FAB: replaced the old variant list with the MD3 `primary`/`secondary`/
  `tertiary`/`tonal*` variants, and swapped the removed `AnimatedFAB` entry
  for `FAB.Extended`
- Snackbar: document container and label colors alongside the icon color

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
`disabled` blocked touch but had no visual effect, so a disabled
destination was indistinguishable from an enabled one.

MD3's navigation drawer and rail token sets define no disabled state, so
fall back to the generic disabled-content opacity
(`md.sys.state.opacity.disabled`) via a shared `stateOpacity` token.
Applied at container level, as Checkbox and Menu.Item do, so the active
indicator, icon, label and trailing slot dim together.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Git worktrees created under .claude/worktrees/ were crawled by Jest from
the repo root, doubling the collected suites (110 instead of 55) and
producing spurious failures from mismatched snapshots and duplicate
module registration across checkouts. This also hit the lefthook
pre-commit hook, which runs `test` from the repo root.

Add <rootDir>/.claude/ to modulePathIgnorePatterns, which scopes both
test discovery and module resolution.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Revert changes that belong in their own PR, and finish wiring up the
token modules introduced earlier in this branch.

- Revert the theme colour table corrections for Appbar, Button, Dialog,
  FAB, IconButton, Menu, Modal, Searchbar, SegmentedButtons and
  Snackbar, along with the `.claude` jest ignore. The Drawer's own
  entries (`Drawer.Item` focus indicator, `Drawer.Section` divider)
  stay, since those document changes made here.
- Drive the collapsed item's press animation from `theme.motion` rather
  than a hardcoded 150ms. `short3` is that same duration; the easing now
  follows the MD3 standard curve instead of Animated's default.
- Resolve the collapsed item's active indicator radius through
  `resolveCornerRadius`, as `Drawer.Item` already does.
- Colour the `Drawer.Item` label from the `*LabelTextColor` tokens,
  which were declared but never read.
- Hide the focus indicator on disabled destinations.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The screenshots taken in b746c65 showed different views than the ones
they replaced, so they no longer read as a before/after of the same UI.
Recapture both against the views the originals used:

- `drawer-item`: a tight crop of a single active "Sent mail" destination,
  with the same relative margins as the 5.x crop.
- `drawer-section`: the example app's own drawer, open on the "Example
  items" section with Inbox active.

Both are Android emulator captures. They lack the macOS window chrome the
5.x shots had, since that needs an interactive window capture.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@ziarno ziarno changed the title Feat/modernize drawer feat: modernize drawer Aug 16, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

feat: modernize Drawer to latest Material Design Specs

1 participant