diff --git a/resources/boost/guidelines/core.blade.php b/resources/boost/guidelines/core.blade.php new file mode 100644 index 0000000..aed51c3 --- /dev/null +++ b/resources/boost/guidelines/core.blade.php @@ -0,0 +1 @@ +This project uses **Aire** (`glhd/aire`) for all HTML form rendering. Always use Aire for forms — never write raw `
` or `` tags. When doing any work related to forms, activate the `aire-development` skill. diff --git a/resources/boost/skills/aire-development/SKILL.md b/resources/boost/skills/aire-development/SKILL.md new file mode 100644 index 0000000..01e8b86 --- /dev/null +++ b/resources/boost/skills/aire-development/SKILL.md @@ -0,0 +1,325 @@ +--- +name: aire-development +description: Build and work with Aire forms in Laravel, including the fluent PHP API, data binding, validation, and customization. +--- + +# Aire Form Development + +## When to use this skill + +Use this skill when creating, modifying, debugging, or styling HTML forms in a Laravel application that uses the `glhd/aire` package. This includes any work involving form elements, data binding, validation, or form customization. + +## Overview + +Aire is a Laravel form builder with a fluent API. It is accessed via the `Aire` facade (`Galahad\Aire\Support\Facades\Aire`). Aire automatically handles CSRF tokens, HTTP method spoofing, old input repopulation, and server-side validation error display. + +## Opening and Closing Forms + +Every form must be opened and closed. Aire uses output buffering between open and close to capture form fields. + +```php +{{ Aire::open()->route('users.store') }} + {{-- form fields --}} +{{ Aire::close() }} +``` + +### Setting the Action + +```php +// From a named route (method is inferred automatically): +Aire::open()->route('users.store') +Aire::open()->route('users.update', $user) + +// From a URL: +Aire::open()->action('/users') + +// Resourceful (auto-detects store vs. update based on model existence): +Aire::open()->resourceful($user) +Aire::open()->resourceful($user, 'admin.users') +``` + +### HTTP Methods + +Aire automatically infers the method from the route. You can also set it explicitly: + +```php +Aire::open()->route('users.store') // POST (inferred) +Aire::open()->route('users.update', $user) // PUT (inferred) +Aire::open()->post() +Aire::open()->put() +Aire::open()->patch() +Aire::open()->delete() +``` + +For `PUT`, `PATCH`, and `DELETE`, Aire automatically adds a hidden `_method` field and sets the real method to `POST`. + +### Form Encoding + +```php +Aire::open()->multipart() // multipart/form-data (required for file uploads) +Aire::open()->urlEncoded() // application/x-www-form-urlencoded +``` + +## Data Binding + +Bind an Eloquent model, array, or object to auto-populate form fields: + +```php +{{ Aire::open()->route('users.update', $user)->bind($user) }} + {{ Aire::input('name', 'Name') }} {{-- pre-filled with $user->name --}} +{{ Aire::close() }} +``` + +Or with `resourceful` (which calls `bind` internally): + +```php +{{ Aire::open()->resourceful($user) }} +``` + +**Value precedence** (highest to lowest): +1. Explicitly set via `->value()` +2. Old input from session (after validation failure) +3. Bound data from the model/array/object + +## Available Form Elements + +### Text Inputs + +```php +Aire::input('name', 'Full Name') +Aire::email('email', 'Email Address') +Aire::password('password', 'Password') +Aire::search('q', 'Search') +Aire::tel('phone', 'Phone') +Aire::url('website', 'Website') +Aire::number('age', 'Age') +Aire::range('rating', 'Rating', 1, 10) +``` + +### Date and Time Inputs + +```php +Aire::date('start_date', 'Start Date') +Aire::dateTime('event_at', 'Event Date/Time') +Aire::dateTimeLocal('local_at', 'Local Date/Time') +Aire::time('start_time', 'Start Time') +Aire::month('birth_month', 'Birth Month') +Aire::week('target_week', 'Target Week') +``` + +### Other Inputs + +```php +Aire::color('theme_color', 'Theme Color') +Aire::file('avatar', 'Avatar') +Aire::hidden('user_id', $user->id) +``` + +### Textarea + +```php +Aire::textArea('bio', 'Biography') +``` + +### Select + +```php +// Options as key => value array: +Aire::select(['draft' => 'Draft', 'published' => 'Published'], 'status', 'Status') + +// Timezone select (pre-populated): +Aire::timezoneSelect('timezone', 'Timezone') +``` + +### Checkboxes + +```php +// Single checkbox: +Aire::checkbox('terms', 'I agree to the terms') + +// Checkbox group (multiple values): +Aire::checkboxGroup(['red' => 'Red', 'blue' => 'Blue', 'green' => 'Green'], 'colors', 'Favorite Colors') +``` + +### Radio Buttons + +```php +Aire::radioGroup(['sm' => 'Small', 'md' => 'Medium', 'lg' => 'Large'], 'size', 'Size') +``` + +### Buttons + +```php +Aire::submit('Save') +Aire::button('Cancel') +``` + +### Error Summary + +Display a summary of all validation errors at the top of the form: + +```php +Aire::summary() // Shows error count +Aire::summary()->verbose() // Shows itemized error list +``` + +## Fluent Element Methods + +All elements support chaining. Common methods available on every input element: + +```php +Aire::input('name', 'Name') + ->id('custom-id') // Set the element ID + ->value('default') // Set an explicit value + ->required() // HTML required attribute + ->disabled() // HTML disabled attribute + ->readOnly() // HTML readonly attribute + ->placeholder('Enter name') // Placeholder text + ->autoComplete('name') // Autocomplete attribute + ->autoFocus() // Autofocus attribute + ->addClass('custom-class') // Add CSS class + ->removeClass('old-class') // Remove CSS class + ->data('key', 'value') // data-* attribute + ->variant('lg') // Apply a theme variant + ->variants('lg primary') // Apply multiple variants +``` + +## Grouping (Labels, Help Text, Errors) + +By default, every form element is wrapped in a "group" that renders a label, the input, help text, and validation errors together. This is controlled by the `group_by_default` config option. + +```php +Aire::input('name') + ->label('Full Name') // Set the label text + ->helpText('Enter your legal name') // Help text below the input + ->withoutGroup() // Render input without the group wrapper + ->grouped() // Force grouping (if disabled by default) + ->groupClass('mb-4') // Add CSS class to the group wrapper + ->groupId('name-group') // Set group wrapper ID + ->prepend('$') // Prepend content inside the group + ->append('.00') // Append content inside the group +``` + +## Validation + +### Server-Side Validation Errors + +Aire automatically reads errors from the session (set by Laravel's `validate()` or `FormRequest`) and displays them inline within each element's group. No extra configuration needed. + +### Using a Custom Error Bag + +```php +Aire::open()->errorBag('login') +``` + +### Client-Side Validation + +Aire includes optional JavaScript-based client-side validation using the `validatorjs` library: + +```php +// Pass validation rules directly: +Aire::open() + ->route('users.store') + ->validate([ + 'name' => 'required|min:3', + 'email' => 'required|email', + ]) + +// Or reference a FormRequest class: +Aire::open() + ->route('users.store') + ->validate(StoreUserRequest::class) + +// Disable client-side validation for a specific form: +Aire::open()->withoutValidation() +``` + +## Alpine.js Integration + +Aire can generate `x-data` and `x-model` attributes for Alpine.js: + +```php +Aire::open()->asAlpineComponent() +Aire::open()->asAlpineComponent(['extra_key' => 'value']) +``` + +When enabled, each input element will get an `x-model` attribute matching its name, and the form gets an `x-data` attribute with JSON-serialized initial values. + +## Configuration + +Publish the config file to customize Aire's behavior: + +```bash +php artisan vendor:publish --tag=aire-config +``` + +This publishes `config/aire.php` where you can set: + +- `default_classes`: CSS classes for each element type +- `variant_classes`: Named style variants (e.g., `sm`, `lg`, `primary`) +- `validation_classes`: CSS classes for validation states (`none`, `valid`, `invalid`) +- `group_by_default`: Whether elements are grouped by default +- `validate_by_default`: Whether client-side validation is on by default + +You can also publish and override Blade templates: + +```bash +php artisan vendor:publish --tag=aire-views +``` + +## Complete Form Examples + +### Create Form + +```php +{{ Aire::open()->route('posts.store') }} + {{ Aire::summary() }} + {{ Aire::input('title', 'Title')->required() }} + {{ Aire::textArea('body', 'Content')->autoSize() }} + {{ Aire::select(['draft' => 'Draft', 'published' => 'Published'], 'status', 'Status') }} + {{ Aire::submit('Create Post') }} +{{ Aire::close() }} +``` + +### Edit Form with Model Binding + +```php +{{ Aire::open()->resourceful($post) }} + {{ Aire::summary() }} + {{ Aire::input('title', 'Title')->required() }} + {{ Aire::textArea('body', 'Content')->autoSize() }} + {{ Aire::select(['draft' => 'Draft', 'published' => 'Published'], 'status', 'Status') }} + {{ Aire::submit('Update Post') }} +{{ Aire::close() }} +``` + +### Delete Form + +```php +{{ Aire::open()->route('posts.destroy', $post) }} + {{ Aire::submit('Delete Post')->variant('danger') }} +{{ Aire::close() }} +``` + +### Form with Client-Side Validation + +```php +{{ Aire::open()->route('users.store')->validate(StoreUserRequest::class) }} + {{ Aire::summary() }} + {{ Aire::input('name', 'Name')->required() }} + {{ Aire::email('email', 'Email')->required() }} + {{ Aire::password('password', 'Password')->required() }} + {{ Aire::password('password_confirmation', 'Confirm Password')->required() }} + {{ Aire::submit('Register') }} +{{ Aire::close() }} +``` + +### File Upload Form + +```php +{{ Aire::open()->route('avatars.store')->multipart() }} + {{ Aire::file('avatar', 'Profile Photo') }} + {{ Aire::submit('Upload') }} +{{ Aire::close() }} +``` +