diff --git a/resources/boost/guidelines/core.blade.php b/resources/boost/guidelines/core.blade.php index 299e903..4c068c5 100644 --- a/resources/boost/guidelines/core.blade.php +++ b/resources/boost/guidelines/core.blade.php @@ -8,6 +8,13 @@ ### Core rules +- Activate the `nativephp-mobile-ui` skill when writing a screen's view. It lists each element's props and + events, list-item swipe actions, and the text input traps. +- Text fields are `outlined-text-input`, `filled-text-input` and `bare-text-input`. There is no `text-input`. + Bind them with `native:model.debounce.300ms` and also read the text `@submit` passes as the handler's last + argument; live binding can drop characters under fast typing. +- `` props are camelCase as written (`leadingCheckbox`, `trailingIconButton`). Swipe actions + only work on list items that are direct children of ``. - Visual styling is theme-driven ("Model 3"): buttons, inputs, toggles, and other controls take their colors, radii, and typography from the theme (`Native\Mobile\UI\Theme`). Use semantic props like `variant="primary"` diff --git a/resources/boost/skills/nativephp-mobile-ui/SKILL.md b/resources/boost/skills/nativephp-mobile-ui/SKILL.md new file mode 100644 index 0000000..e1c82bd --- /dev/null +++ b/resources/boost/skills/nativephp-mobile-ui/SKILL.md @@ -0,0 +1,117 @@ +--- +name: nativephp-mobile-ui +description: "Element reference for nativephp/mobile-ui, the plugin that renders every NativePHP EDGE element. Activate when writing or changing a NativePHP screen's Blade view: layout (column, row, stack, scroll-view), text, button, the text inputs (outlined-text-input, filled-text-input, bare-text-input), list and list-item (leading/trailing slots, swipe actions, trailing icon buttons), icon, top-bar, toggle, checkbox, select, and their events and native:model binding." +--- + +# nativephp/mobile-ui elements + +Every `` tag is drawn by this plugin, as SwiftUI on iOS and Jetpack Compose on Android. The plugin must be +registered in `plugins()` in `app/Providers/NativeServiceProvider.php` (`php artisan native:plugin:register +nativephp/mobile-ui`). The `native:` prefix is optional (`` works), but use it. + +## Events + +`@press` (alias `@tap`), `@longPress`, `@doubleTap`, `@change`, `@submit`, `@refresh`, `@endReached`. The value is a +public method name or `method(arg, ...)` with JSON arguments: `select('abc')`, `remove({{ $id }})`. The element's +own value is appended after your arguments: the text for `@submit` and `@change` on text inputs, a bool for +checkboxes and switches. Any element takes `@press`, including rows and columns. A misspelled event attribute is +dropped without an error. + +## Elements + +| Element | Props that matter | +|---|---| +| `column`, `row`, `stack`, `scroll-view`, `spacer`, `divider`, `pressable` | Tailwind `class`: `w-full h-full flex-1 gap-N p-N px-N items-center justify-between rounded-lg bg-theme-*` | +| `text` (slot is the text) | `class` (`text-lg font-bold line-through underline text-theme-on-surface`), `font` | +| `button` | label as slot or `label`; `variant` primary\|secondary\|destructive\|success\|ghost; `size` sm\|md\|lg; `icon`, `icon-trailing`, `ios-icon`, `android-icon`, `disabled`, `loading`, `a11y-label` | +| `outlined-text-input`, `filled-text-input`, `bare-text-input` | `native:model`, `placeholder`, `label`, `@submit`, `@change`, `keyboard` (text\|number\|decimal\|email\|phone\|url\|password), `secure`, `revealable`, `multiline`, `max-length`, `autofocus`, `leading-icon`, `trailing-icon`, `error`, `supporting` | +| `list` | `separator`, `plain`, `horizontal`, `on-refresh`, `on-end-reached`. Children: `list-item` or any element | +| `list-item` | see below | +| `icon` | `name` (`star.fill`, `trash`, `plus`), or per platform `ios="checkmark.circle.fill" android="check_circle"`, `:size="20"`, `class="text-theme-primary"` | +| `top-bar` | `title`, `subtitle`, `back`. Put it at the top level of the view | +| also | `toggle`, `checkbox`, `slider`, `select`, `radio-group`, `chip`, `badge`, `date-picker`, `modal`, `bottom-sheet`, `tab-row`, `progress-bar`, `activity-indicator` | + +There is no `text-input` element. It throws "Unknown native element type: text_input". + +## Text inputs + +```blade + +``` + +- Use `native:model.debounce.300ms` (`.debounce` alone is 300ms) or `.blur`. The default live mode syncs every + keystroke and echoes the value back into the field, which drops or reorders characters under fast typing. +- Take the text from `@submit` too. The handler gets it as its last argument, so a button tapped inside the + debounce window can't save a stale value: + +```php +public function save(?string $submitted = null): void +{ + $title = trim($submitted ?? $this->title); + // ... + $this->title = ''; // clears the field on the next render +} +``` + +- `keep-focus-on-submit` only works on iOS. + +## List items + +Props are written camelCase, exactly as below. Use `:prop` for PHP values. + +- Text: `headline`, `supporting`, `overline`, `headlineColor`. +- Leading slot, pick one: `leadingIcon`, `leadingCheckbox`, `leadingRadio`, `leadingMonogram`, `leadingAvatar`, + `leadingImage`. Changes go to `on-leading-change`. +- Trailing slot, pick one: `trailingIcon`, `trailingText`, `trailingCheckbox`, `trailingSwitch`, + `trailingIconButton`. Changes go to `on-trailing-change`; the icon button's press goes to `on-trailing-press`. +- Swipe: `:trailing-actions` and `:leading-actions` take arrays of `method`, `label`, `icon` (or `ios` and + `android`), `tint`, `role` (`destructive` makes it red). `on-swipe-delete` is the single-action shortcut. +- `@press` on the row, `disabled`, `native:key` for a stable identity. + +```blade + + @foreach ($notes as $note) + + @endforeach + +``` + +Swipe actions only work on list items that are direct children of ``. + +A list item can't strike through or restyle its headline. For that, build the row yourself inside a +`scroll-view`: + +```blade + + + @foreach ($notes as $note) + + + {{ $note->title }} + + + @endforeach + + +``` + +## Layout + +- `` gives a native navigation bar and handles the safe area. A screen can also return its title + from `navTitle(): string`. +- Add `safe-area` only to screens with no top bar, bottom nav or layout. +- Colours come from theme tokens (`bg-theme-*`, `text-theme-*`, `border-theme-*` with `primary`, `on-primary`, + `surface`, `on-surface`, `surface-variant`, `on-surface-variant`, `background`, `outline`), which switch for dark + mode on their own. + +## In tests + +Node types in `Native::test(...)->tree()` and `assertElement()` are snake_case: `list_item`, +`outlined_text_input`. A list item's checkbox state is `props.leading_checked`. `assertSee()` finds headlines and +swipe action labels. `press('remove(5)')` fires whatever callback was registered for that expression.