From 011290bd6318d3a3fbd99d580a7b3c90c59024e3 Mon Sep 17 00:00:00 2001 From: Shane Rosenthal Date: Mon, 3 Aug 2026 18:00:02 -0400 Subject: [PATCH] docs(mobile/4): accordion, @selectionChange, per-platform icons, native:font overhaul Documents the four mobile-ui features that landed today: - New accordion page: header/content structure, live expanded binding, @change contract, and why native:model alone won't drive it - text-input: caret & selection reporting (@selectionChange) and per-platform leading/trailing icon attributes - button/chip/tab-row: ios-icon / android-icon overrides, and the iOS button height fix note - text: native:font rewritten for the interactive single-family flow, config aliases, --reset/--clear-cache, and the removed --weights/--italic All behavior verified against the released package source. Co-Authored-By: Claude Fable 5 --- .../mobile/4/edge-components/accordion.md | 147 ++++++++++++++++++ .../docs/mobile/4/edge-components/button.md | 30 ++++ .../docs/mobile/4/edge-components/chip.md | 14 ++ .../docs/mobile/4/edge-components/tab-row.md | 13 ++ .../mobile/4/edge-components/text-input.md | 129 ++++++++++++++- .../docs/mobile/4/edge-components/text.md | 53 +++++-- 6 files changed, 374 insertions(+), 12 deletions(-) create mode 100644 resources/views/docs/mobile/4/edge-components/accordion.md diff --git a/resources/views/docs/mobile/4/edge-components/accordion.md b/resources/views/docs/mobile/4/edge-components/accordion.md new file mode 100644 index 000000000..c5da9816d --- /dev/null +++ b/resources/views/docs/mobile/4/edge-components/accordion.md @@ -0,0 +1,147 @@ +--- +title: Accordion +order: 90 +--- + +## Overview + +An expandable disclosure section — a tappable header row that opens and closes a collapsible content area. +Renders as a SwiftUI `DisclosureGroup` on iOS and a Compose column with an `AnimatedVisibility` content area +on Android. + +An accordion wraps two child tags: a [``](#props) holding the always-visible row, and a +[``](#props) holding the collapsible body. Both accept any child elements. + +@verbatim +```blade +@php $showSpecs = false; @endphp + + + + Specifications + + + Weight — 1.24 kg + Battery — up to 18 hours + + +``` +@endverbatim + +`showSpecs` is a public bool property on your component — the `@php` line stands in for +`public bool $showSpecs = false;` — and `toggleSpecs()` is called with the new state whenever the user +taps the header. + +Both platforms draw their own trailing chevron — the `DisclosureGroup` indicator on iOS, a rotating Material +arrow on Android — so don't add one to your header content. Tapping anywhere on the header row toggles the +section on both platforms. + +## Props + +- `expanded` - Whether the content area is open (optional, boolean, default: `false`). This is a live binding, + not just a mount-time hint — assigning the bound property from PHP animates the group open or closed at any + point, so patterns like "expand all" work +- `a11y-label` - Accessibility label (optional) +- `a11y-hint` - Accessibility hint (optional) + +`` and `` take no props of their own beyond the same +`a11y-label` / `a11y-hint` pair — they exist to mark which children belong to which slot. + +## Events + +- `@change` - Component method called when the user expands or collapses the section. Receives the new + expanded state as a boolean parameter + + + +## Two-way Binding + +The accordion's state prop is `expanded`, not `value` — and the `native:model` directive expands to a +`:value` binding, which the accordion doesn't read. So unlike [``](toggle), `native:model` +alone won't drive an accordion. Write the pair explicitly: bind `:expanded` and point `@change` at +`__syncProperty`, the same sync handler `native:model` generates. + +@verbatim +```blade +@php $showDetails = false; @endphp + + + + Order details + + + 3 items · Arrives Thursday + + + +{{ $showDetails ? 'Expanded' : 'Collapsed' }} +``` +@endverbatim + +Tapping the header syncs `showDetails` back automatically — the echo below updates on every toggle. The sync +runs both ways: set `$showDetails` from any component method and the group animates to match. Both renderers +echo-prevent, so adopting a programmatic value never fires `@change` back at your component. + +`sync-mode` and `debounce-ms` are not accepted — a header tap is a single discrete event, so there is nothing +to defer or debounce. + +## Examples + +### FAQ list + +@verbatim +```blade +@php $faqs = [ + ['q' => 'How do I reset my password?', 'a' => 'Use the link on the sign-in screen.'], + ['q' => 'Can I change my plan later?', 'a' => 'Yes — upgrades apply immediately, downgrades at renewal.'], +]; @endphp + + + @foreach ($faqs as $faq) + + + {{ $faq['q'] }} + + + {{ $faq['a'] }} + + + + @endforeach + +``` +@endverbatim + +Accordions without `:expanded` start collapsed, and without `@change` they still toggle natively — wire the +event only when your component needs to know. + +## Element + +```php +use Native\Mobile\UI\Elements\Accordion; +use Native\Mobile\UI\Elements\AccordionContent; +use Native\Mobile\UI\Elements\AccordionHeader; +use Native\Mobile\UI\Elements\Text; + +Accordion::make( + AccordionHeader::make(Text::make('Specifications')), + AccordionContent::make(Text::make('Weight — 1.24 kg')), +) + ->expanded(true) + ->onChange('toggleSpecs'); +``` + +- `make(Element ...$children)` - Create an accordion from its header and content children +- `expanded(bool $expanded)` - Open or close the content area +- `onChange(string $method)` - Component method invoked when the user toggles the section +- `a11yLabel(string $value)` - Accessibility label +- `a11yHint(string $value)` - Accessibility hint + +`AccordionHeader::make(...)` and `AccordionContent::make(...)` take their child elements the same way and +expose only the two a11y setters. diff --git a/resources/views/docs/mobile/4/edge-components/button.md b/resources/views/docs/mobile/4/edge-components/button.md index f9bf3bffa..ce0ece8e8 100644 --- a/resources/views/docs/mobile/4/edge-components/button.md +++ b/resources/views/docs/mobile/4/edge-components/button.md @@ -31,7 +31,13 @@ Slot content is treated as plain text — nested tags are stripped and whitespac in `config/native-ui.php`) — see [Theming](../digging-deeper/theming) - `size` - `sm`, `md` (default), `lg` - `icon` - A leading [icon](icon#icon-name-reference) name (optional) +- `ios-icon` / `android-icon` - Per-platform overrides for the leading icon: an [SF Symbol](icon#ios-sf-symbols) + name on iOS, a [Material Icon](icon#android-material-icons) name on Android. `icon` is the fallback on whichever + platform has no override — or skip it and declare only per-platform icons. `iosIcon` / `androidIcon` and the + ``-style `ios` / `android` are accepted as aliases - `icon-trailing` - A trailing [icon](icon#icon-name-reference) name (optional) +- `ios-icon-trailing` / `android-icon-trailing` - Per-platform overrides for the trailing icon, mirroring + `ios-icon` / `android-icon` (camelCase aliases accepted; no `ios` / `android` shorthand for this slot) - `font` - Custom font for the label: a `resources/fonts/` file token or a config alias like `accent` (optional, string) — see [Text › Custom fonts](text#custom-fonts) - `line-height` - Label line height as a multiplier of the font size (optional, float) - `line-height-px` - Label line height as an absolute value in pixels (optional, float) @@ -96,6 +102,30 @@ attributes are intentionally dropped before reaching the renderer. ``` @endverbatim +### Platform icons + +When one shared name doesn't map well on both platforms, give each platform its own symbol — an +[SF Symbol](icon#ios-sf-symbols) name on iOS, a [Material Icon](icon#android-material-icons) name on Android: + +@verbatim +```blade + +``` +@endverbatim + +Resolution happens per platform: each side uses its override, falling back to `icon` where none is given. Bound +`:ios-icon` / `:android-icon` also accept `App\Icons` enum cases, exactly like +[``'s `:ios` / `:android`](icon#typed-icon-enums). The trailing slot works the same way via +`ios-icon-trailing` / `android-icon-trailing`. + +On iOS the icon's layout height is pinned to the size-driven icon size, so button height depends only on `size` — +not on which SF Symbol the button carries. Tall glyphs like `wave.3.right` no longer stretch the button. + ### Loading state @verbatim diff --git a/resources/views/docs/mobile/4/edge-components/chip.md b/resources/views/docs/mobile/4/edge-components/chip.md index 0e3d88e0f..bf17b0779 100644 --- a/resources/views/docs/mobile/4/edge-components/chip.md +++ b/resources/views/docs/mobile/4/edge-components/chip.md @@ -27,6 +27,10 @@ fully rounded and can be adjusted with `rounded-*` classes. - `label` - Chip text (optional, string). Can also be passed as the first argument to `make()` - `selected` / `value` - Whether the chip is active (optional, boolean, default: `false`) - `icon` - Leading [icon](icon#icon-name-reference) name (optional, string) +- `ios-icon` / `android-icon` - Per-platform overrides for the leading icon: an [SF Symbol](icon#ios-sf-symbols) + name on iOS, a [Material Icon](icon#android-material-icons) name on Android. `icon` is the fallback on whichever + platform has no override. Bound `:ios-icon` / `:android-icon` also accept + [enum cases](icon#typed-icon-enums); `iosIcon` / `androidIcon` and `ios` / `android` are accepted as aliases - `disabled` - Disable the chip (optional, boolean, default: `false`) - `a11y-label` - Accessibility label (optional) - `a11y-hint` - Accessibility hint (optional) @@ -83,6 +87,16 @@ driving `selected` from one property keeps the row single-select. ``` @endverbatim +When the platforms need different symbols, override per platform — an SF Symbol on iOS, a Material name on Android: + +@verbatim +```blade +@php $tapToPay = false; @endphp + + +``` +@endverbatim + ## Element ```php diff --git a/resources/views/docs/mobile/4/edge-components/tab-row.md b/resources/views/docs/mobile/4/edge-components/tab-row.md index 310c1d06f..bece3222b 100644 --- a/resources/views/docs/mobile/4/edge-components/tab-row.md +++ b/resources/views/docs/mobile/4/edge-components/tab-row.md @@ -68,6 +68,10 @@ Here `currentTab` stands in for `public int $currentTab = 0;` on your component. - `label` - Tab label (required, string). Can also be passed as the first argument to `make()` - `icon` - Optional [icon](icon#icon-name-reference) name rendered above the label +- `ios-icon` / `android-icon` - Per-platform overrides for the tab icon: an [SF Symbol](icon#ios-sf-symbols) name + on iOS, a [Material Icon](icon#android-material-icons) name on Android. `icon` is the fallback on whichever + platform has no override. Bound `:ios-icon` / `:android-icon` also accept + [enum cases](icon#typed-icon-enums); `iosIcon` / `androidIcon` and `ios` / `android` are accepted as aliases - `a11y-label` - Accessibility label override (optional) ## Examples @@ -119,6 +123,15 @@ typically add `fill` to the outer column and the content panes so they occupy th ``` @endverbatim +When a shared name doesn't map well on both platforms, give a tab per-platform icons — an SF Symbol on iOS, a +Material name on Android: + +@verbatim +```blade + +``` +@endverbatim + ## Element ```php diff --git a/resources/views/docs/mobile/4/edge-components/text-input.md b/resources/views/docs/mobile/4/edge-components/text-input.md index f1f75f15b..3d1121ad7 100644 --- a/resources/views/docs/mobile/4/edge-components/text-input.md +++ b/resources/views/docs/mobile/4/edge-components/text-input.md @@ -70,6 +70,9 @@ All three variants accept the same shared prop set. The bare variant adds a `col - `sync-mode` - How change events dispatch back to your component: `live` (default), `blur`, or `debounce`. Usually set via the `native:model` modifiers below, but accepted directly too - `debounce-ms` - Milliseconds of inactivity before a `debounce` sync fires (optional, int, default: `300`) +- `selection-debounce-ms` - Coalescing window for `@selectionChange` events (optional, int, default: `150`). `0` or + less means the default; positive values are floored at one frame (16ms). See + [Caret and selection reporting](#caret-and-selection-reporting) ### Decorations @@ -77,6 +80,9 @@ All three variants accept the same shared prop set. The bare variant adds a `col - `suffix` - Text rendered after the input (optional, string) - `leading-icon` - Icon name rendered at the start (optional, string) - `trailing-icon` - Icon name rendered at the end (optional, string) +- `ios-leading-icon` / `android-leading-icon` - Per-platform overrides for `leading-icon` (optional). See + [Per-platform icons](#per-platform-icons) +- `ios-trailing-icon` / `android-trailing-icon` - Per-platform overrides for `trailing-icon` (optional) ### Typography @@ -94,6 +100,9 @@ All three variants accept the same shared prop set. The bare variant adds a `col - `@change` - Component method called when the text changes. Receives the new value - `@submit` - Component method called when the user submits (e.g. presses return). Receives the current value +- `@selectionChange` - Component method called when the caret moves or the selection changes. Receives the full + current text plus the selection start and end offsets — see + [Caret and selection reporting](#caret-and-selection-reporting)