Skip to content
Brok UI

Loading…

No results

Resource Pages

Open source

Authenticated resource workflows — searchable and selectable index, create, edit, and detail pages with activity history, plus the operator set: an alert-led index, a waiting-for work queue, a stepped record and one create/edit form with save blockers.

8 of 8

Preview

resources/views/pages/admin-form.blade.php Blade
{{--
    Admin Form — create and edit as one page with two starting points.

    Creating a record and correcting one are the same set of decisions with
    a different starting point, so they share this page rather than each
    growing a field set that drifts. `mode="create"` renders it empty;
    `mode="edit"` renders it on an existing record.

    The composition follows the order the decisions are actually made: what
    kind of thing this is (which decides which fields exist AT ALL), what it
    does, who it applies to, what limits it, and when it runs. Each of those
    is one `admin/record-section`; the whole form reads top to bottom as a
    sentence. The end-side rail carries the consequence beside the work: a
    live summary of what will be written, then what still blocks saving,
    then the terminal actions — which never disappear, only disable with the
    reason beside them (`admin/save-blockers`, which also docks itself to the
    viewport bottom under `lg`, where the rail would otherwise sit a full
    page-scroll below the field being typed).

    Readiness is stated twice on purpose — as a pill in the page header and
    in the rail — because on a long form one of the two is always off screen.

    Server-first: every field is a real named control inside one POST form,
    validation errors come back through the shared `$errors` bag into
    `<x-ui.field>`, and the blockers list is whatever the host computes.
    A Livewire or Alpine host can bind the same props live.

    Generalised from the Noord-C admin's `discounts/form/page.blade.php`,
    `customers/form/page.blade.php` and `bundles/form/page.blade.php`.
    Left out as domain logic: the discount rule builder, the customer
    address lookup, the bundle component picker.
--}}
@props([
    'mode' => 'create',
    'title' => null,
    'subtitle' => null,
    'action' => '#',
    'method' => 'POST',
    'cancelHref' => '#',
    'recordHref' => '#',
    'duplicatedFrom' => null,
    'values' => null,
    'types' => null,
    'blockers' => null,
    'summary' => null,
])

@php
    // Shapes documented in item.json knowledge.props:
    //   duplicatedFrom: ['title', 'href'] — where a copy came from, and what was not copied, said before the operator goes looking.
    //   values: the record's current field values, keyed by input name.
    //   types: [['value', 'label', 'hint']] — the "what kind of thing" choice that decides which fields exist.
    //   blockers: the save-blockers `blockers` array; [] means the form is ready.
    //   summary: the fact-list `facts` array for the live summary card.
    $isEdit = $mode === 'edit';
    $title ??= $isEdit ? __('Edit discount') : __('New discount');
    $subtitle ??= $isEdit
        ? __('Changes apply to new orders only; orders that already used this code keep what they were given.')
        : __('A discount is a rule the checkout applies. Decide what kind it is first — that decides which fields exist.');

    $types ??= [
        ['value' => 'percentage', 'label' => __('Percentage off'), 'hint' => __('A share of the line price.')],
        ['value' => 'fixed', 'label' => __('Fixed amount off'), 'hint' => __('A sum off the order.')],
        ['value' => 'shipping', 'label' => __('Free shipping'), 'hint' => __('Waives the shipping fee.')],
    ];

    $values ??= $isEdit ? [
        'title' => 'Spring welcome',
        'type' => 'percentage',
        'code' => 'SPRING10',
        'value' => '10',
        'audience' => 'everyone',
        'minimum' => '25.00',
        'uses' => '500',
        'starts_at' => '2026-09-01',
        'ends_at' => '2026-10-31',
        'active' => true,
    ] : [
        'title' => '',
        'type' => 'percentage',
        'code' => '',
        'value' => '',
        'audience' => 'everyone',
        'minimum' => '',
        'uses' => '',
        'starts_at' => '',
        'ends_at' => '',
        'active' => false,
    ];

    $blockers ??= $isEdit ? [] : [
        ['field' => 'title', 'text' => __('Give the discount a name.'), 'target' => 'title'],
        ['field' => 'code', 'text' => __('Set the code customers will type.'), 'target' => 'code'],
        ['field' => 'value', 'text' => __('Set how much comes off.'), 'target' => 'value'],
    ];
    $ready = $blockers === [];

    $summary ??= [
        ['label' => __('Rule'), 'value' => $isEdit ? __('10% off orders over €25.00') : __('Not decided yet')],
        ['label' => __('Audience'), 'value' => __('Everyone')],
        ['label' => __('Ceiling'), 'value' => $isEdit ? __('500 uses') : __('No limit'), 'tone' => $isEdit ? null : 'attention'],
        ['label' => __('Window'), 'value' => $isEdit ? '1 Sep – 31 Oct 2026' : __('Open-ended')],
    ];

    $banners = $duplicatedFrom
        ? [[
            'tone' => 'info',
            'title' => __('Duplicated from :name', ['name' => $duplicatedFrom['title'] ?? '']),
            'body' => __('Everything was copied except the code, which has to be unique.'),
            'actionLabel' => $duplicatedFrom['title'] ?? __('Open original'),
            'actionHref' => $duplicatedFrom['href'] ?? '#',
        ]]
        : [];

    $trail = array_values(array_filter([
        ['label' => __('Discounts'), 'href' => '#'],
        $isEdit ? ['label' => $values['title'] ?? '', 'href' => $recordHref] : null,
        ['label' => $title],
    ]));
@endphp

<main
    id="main-content"
    data-slot="admin-form-page"
    data-surface="admin"
    data-mode="{{ $isEdit ? 'edit' : 'create' }}"
    {{ $attributes->merge(['class' => 'min-h-screen bg-background text-foreground']) }}
>
    <x-ui.admin.page-bar :title="$title" :trail="$trail" :record="[]" :nav-toggle="false" />

    <div class="mx-auto flex max-w-screen-2xl flex-col gap-6 px-4 py-6 pb-24 sm:px-6 lg:pb-6">
        <x-ui.admin.page-header :title="$title" :scope="$subtitle">
            <x-slot:actions>
                <x-ui.badge :tone="$ready ? 'positive' : 'attention'" size="status">
                    {{ $ready ? __('Ready to save') : trans_choice(':count thing missing|:count things missing', count($blockers), ['count' => count($blockers)]) }}
                </x-ui.badge>
                @if ($isEdit)
                    <x-ui.button variant="ghost" size="sm" :href="$recordHref">{{ __('Back to record') }}</x-ui.button>
                @endif
            </x-slot:actions>
        </x-ui.admin.page-header>

        <x-ui.admin.attention-banners :banners="$banners" />

        <x-ui.form :action="$action" :method="$method" class="grid items-start gap-6 !space-y-0 lg:grid-cols-[minmax(0,1fr)_20rem]">
            <div class="flex min-w-0 flex-col gap-6">
                <x-ui.admin.record-section id="section-kind" :title="__('What kind')" :hint="__('Decides which of the fields below exist. Locked once the discount has been used.')">
                    <x-ui.field name="title" :label="__('Name')" :hint="__('Internal. Customers see the code, not the name.')" required>
                        <x-ui.input id="title" :value="$values['title'] ?? ''" autocomplete="off" />
                    </x-ui.field>

                    <fieldset class="mt-6">
                        <legend class="text-sm font-medium">{{ __('Type') }}</legend>
                        <div class="mt-2 grid gap-2 sm:grid-cols-3">
                            @foreach ($types as $type)
                                <x-ui.choicebox
                                    type="radio"
                                    name="type"
                                    :value="$type['value']"
                                    :title="$type['label']"
                                    :description="$type['hint'] ?? null"
                                    :checked="($values['type'] ?? null) === $type['value']"
                                />
                            @endforeach
                        </div>
                    </fieldset>
                </x-ui.admin.record-section>

                <x-ui.admin.record-section id="section-value" :title="__('What the customer gets')" :hint="__('The code and the amount. A percentage over 50% is legitimate, but it is chosen on purpose.')">
                    <div class="grid gap-6 sm:grid-cols-2">
                        <x-ui.field name="code" :label="__('Code')" :hint="__('Upper-case letters and digits, 4–16 characters.')" required>
                            <x-ui.input id="code" :value="$values['code'] ?? ''" autocomplete="off" spellcheck="false" class="font-mono uppercase" />
                        </x-ui.field>
                        <x-ui.field name="value" :label="__('Value')" :hint="__('Percentage or amount, depending on the type.')" required>
                            <x-ui.input id="value" type="number" inputmode="decimal" min="0" step="0.01" :value="$values['value'] ?? ''" />
                        </x-ui.field>
                    </div>
                </x-ui.admin.record-section>

                <x-ui.admin.record-section id="section-audience" :title="__('Who may use it')" :hint="__('Everyone, or a segment. A minimum order keeps a code from being applied to a single low-value line.')">
                    <div class="grid gap-6 sm:grid-cols-2">
                        <x-ui.field name="audience" :label="__('Audience')">
                            <x-ui.select id="audience">
                                <option value="everyone" @selected(($values['audience'] ?? '') === 'everyone')>{{ __('Everyone') }}</option>
                                <option value="new" @selected(($values['audience'] ?? '') === 'new')>{{ __('First-time customers') }}</option>
                                <option value="segment" @selected(($values['audience'] ?? '') === 'segment')>{{ __('A segment…') }}</option>
                            </x-ui.select>
                        </x-ui.field>
                        <x-ui.field name="minimum" :label="__('Minimum order')" :hint="__('Leave empty for no minimum.')">
                            <x-ui.input id="minimum" type="number" inputmode="decimal" min="0" step="0.01" :value="$values['minimum'] ?? ''" />
                        </x-ui.field>
                    </div>
                </x-ui.admin.record-section>

                <x-ui.admin.record-section id="section-limits" :title="__('What limits it')" :hint="__('Without a ceiling a campaign can run away. State it here as a figure, before it runs.')">
                    <div class="grid gap-6 sm:grid-cols-2">
                        <x-ui.field name="uses" :label="__('Total uses')" :hint="__('Across all customers.')">
                            <x-ui.input id="uses" type="number" inputmode="numeric" min="0" step="1" :value="$values['uses'] ?? ''" />
                        </x-ui.field>
                        <x-ui.field name="notes" :label="__('Internal note')" :hint="__('Why this discount exists. Shown on the record, never to customers.')">
                            <x-ui.textarea id="notes" rows="3"></x-ui.textarea>
                        </x-ui.field>
                    </div>
                </x-ui.admin.record-section>

                <x-ui.admin.record-section id="section-window" :title="__('When it runs')" :hint="__('Time moves the window on its own; the switch is a decision a person makes. They are kept apart so a paused campaign is never confused with an expired one.')">
                    <div class="grid gap-6 sm:grid-cols-2">
                        <x-ui.field name="starts_at" :label="__('Starts')">
                            <x-ui.input id="starts_at" type="date" :value="$values['starts_at'] ?? ''" />
                        </x-ui.field>
                        <x-ui.field name="ends_at" :label="__('Ends')" :hint="__('Leave empty for an open-ended window.')">
                            <x-ui.input id="ends_at" type="date" :value="$values['ends_at'] ?? ''" />
                        </x-ui.field>
                    </div>
                    <div class="mt-6">
                        <x-ui.switch name="active" id="active" :label="__('Switched on')" :checked="(bool) ($values['active'] ?? false)" />
                    </div>
                </x-ui.admin.record-section>
            </div>

            <aside class="flex min-w-0 flex-col gap-6 lg:sticky lg:top-20" aria-label="{{ __('Summary and actions') }}">
                <x-ui.admin.record-section :title="__('Summary')" :hint="__('What saving would write.')">
                    <x-ui.fact-list :facts="$summary" />
                </x-ui.admin.record-section>

                <x-ui.admin.save-blockers
                    :blockers="$blockers"
                    :actions="[
                        ['label' => $isEdit ? __('Save changes') : __('Create discount'), 'primary' => true],
                        ['label' => __('Save as draft'), 'variant' => 'outline', 'requiresValid' => false],
                    ]"
                    :cancel-href="$cancelHref"
                    :note="__('⌘S saves. Blockers clear as each field is fixed.')"
                    :dock-label="__('Form actions')"
                />
            </aside>
        </x-ui.form>
    </div>
</main>

Installation

terminal
php artisan ui:add pages/admin-form

Registry contract

Install confidence

php artisan ui:add pages/admin-form writes only the generated targets below. The CLI validates each file hash before writing and prompts before replacing local changes unless --force is used.

Version
1.0.3
License
open
Stability
stable
Contract
v4
Foundation
≥ 1.0.0
Type Generated target
blade resources/views/pages/admin-form.blade.php

Registry dependencies

admin-page-bar admin-page-header admin-attention-banners admin-record-section admin-save-blockers form field input select textarea switch choicebox fact-list badge button

Package dependencies

composer: jml/brok:^0.2

Use with AI

A brief for your coding agent: what the page is, the install command, how to render it, its props and the rules. Copy it, or open a prompt about this page in an assistant.

admin-form.md
# Brok UI page: Admin Form (`admin-form`)

Create and edit as one page — a page header with a readiness pill, attention banners, decision-ordered record sections of real fields, and an end-side rail with a live summary and save blockers that dock on mobile.

Brok UI is a Laravel Blade registry. `ui:add` copies this page into the app as plain Blade the app owns; it composes installed `<brok:*>` primitives and semantic design tokens.

## Install

```bash
php artisan ui:add pages/admin-form
```

## Render it

```blade
<x-pages.admin-form />
```

## Props

- `mode` ('create'|'edit', default `create`) — Which starting point the page renders.
- `title` (string|null, default `null`) — Page title; derived from mode.
- `subtitle` (string|null, default `null`) — Scope sentence; derived from mode.
- `action` (string, default `#`) — Form POST target.
- `method` (string, default `POST`) — Form method.
- `cancel-href` (string, default `#`) — Cancel link.
- `record-href` (string, default `#`) — Back-to-record link in edit mode.
- `duplicated-from` (array|null, default `null`) — ['title','href'] renders the duplicated-from banner.
- `values` (array|null, default `null`) — Field values keyed by input name.
- `types` (array|null, default `null`) — [['value','label','hint']] for the kind choice.
- `blockers` (array|null, default `null`) — Save-blockers array; `[]` means ready.
- `summary` (array|null, default `null`) — Fact-list rows for the summary card.

## Use when

- Any create/edit screen in an admin: the same page renders empty or on an existing record.
- Save must stay visible with the reason it is disabled, on desktop and on a tablet.

## Avoid when

- A multi-step wizard — use `form-wizard`.
- Settings pages with many independent groups — use `pages/settings-layout`.

## Rules

- Render the installed page with `<x-pages.admin-form />` and pass data through its props; edit the copied file only for structural changes.
- Keep the semantic design tokens (`bg-background`, `text-muted-foreground`); never swap in raw colour utilities.
- Keep the `data-slot` attributes and the logical (start/end) spacing so the markup still mirrors under `dir="rtl"`.

## Links

- Docs: https://brokui.dev/pages/admin-form
- Registry JSON (files, props, contract): https://brokui.dev/r/open/pages/admin-form.json

Working in Claude Code, Cursor or Codex? Give the agent the whole registry through the MCP server or the Brok UI skill.

Guidance

Use when

  • Any create/edit screen in an admin: the same page renders empty or on an existing record.
  • Save must stay visible with the reason it is disabled, on desktop and on a tablet.

Avoid when

  • A multi-step wizard — use `form-wizard`.
  • Settings pages with many independent groups — use `pages/settings-layout`.

Anti-patterns

  • Do not replace semantic props with conflicting utility classes.
  • Do not remove labels, focus styles, or state attributes.
Anatomy
admin-form-page
Theming hooks
admin-form-page

Usage

Render the page as a component, or wire it to a route. Pass data where useful:

resources/views/welcome.blade.php Blade
<x-pages.admin-form />

Built from blocks & primitives

This page composes installed <brok:*> primitives and semantic design tokens only — it does not reimplement any primitive. Re-theme it (light, dark, admin, customer) by editing CSS variables; flip the preview to RTL to confirm it mirrors.

Source

The exact, editable file ui:add writes into your app. The preview above renders this same source — there are no preview-only pages.

resources/views/pages/admin-form.blade.php Blade
{{--
    Admin Form — create and edit as one page with two starting points.

    Creating a record and correcting one are the same set of decisions with
    a different starting point, so they share this page rather than each
    growing a field set that drifts. `mode="create"` renders it empty;
    `mode="edit"` renders it on an existing record.

    The composition follows the order the decisions are actually made: what
    kind of thing this is (which decides which fields exist AT ALL), what it
    does, who it applies to, what limits it, and when it runs. Each of those
    is one `admin/record-section`; the whole form reads top to bottom as a
    sentence. The end-side rail carries the consequence beside the work: a
    live summary of what will be written, then what still blocks saving,
    then the terminal actions — which never disappear, only disable with the
    reason beside them (`admin/save-blockers`, which also docks itself to the
    viewport bottom under `lg`, where the rail would otherwise sit a full
    page-scroll below the field being typed).

    Readiness is stated twice on purpose — as a pill in the page header and
    in the rail — because on a long form one of the two is always off screen.

    Server-first: every field is a real named control inside one POST form,
    validation errors come back through the shared `$errors` bag into
    `<x-ui.field>`, and the blockers list is whatever the host computes.
    A Livewire or Alpine host can bind the same props live.

    Generalised from the Noord-C admin's `discounts/form/page.blade.php`,
    `customers/form/page.blade.php` and `bundles/form/page.blade.php`.
    Left out as domain logic: the discount rule builder, the customer
    address lookup, the bundle component picker.
--}}
@props([
    'mode' => 'create',
    'title' => null,
    'subtitle' => null,
    'action' => '#',
    'method' => 'POST',
    'cancelHref' => '#',
    'recordHref' => '#',
    'duplicatedFrom' => null,
    'values' => null,
    'types' => null,
    'blockers' => null,
    'summary' => null,
])

@php
    // Shapes documented in item.json knowledge.props:
    //   duplicatedFrom: ['title', 'href'] — where a copy came from, and what was not copied, said before the operator goes looking.
    //   values: the record's current field values, keyed by input name.
    //   types: [['value', 'label', 'hint']] — the "what kind of thing" choice that decides which fields exist.
    //   blockers: the save-blockers `blockers` array; [] means the form is ready.
    //   summary: the fact-list `facts` array for the live summary card.
    $isEdit = $mode === 'edit';
    $title ??= $isEdit ? __('Edit discount') : __('New discount');
    $subtitle ??= $isEdit
        ? __('Changes apply to new orders only; orders that already used this code keep what they were given.')
        : __('A discount is a rule the checkout applies. Decide what kind it is first — that decides which fields exist.');

    $types ??= [
        ['value' => 'percentage', 'label' => __('Percentage off'), 'hint' => __('A share of the line price.')],
        ['value' => 'fixed', 'label' => __('Fixed amount off'), 'hint' => __('A sum off the order.')],
        ['value' => 'shipping', 'label' => __('Free shipping'), 'hint' => __('Waives the shipping fee.')],
    ];

    $values ??= $isEdit ? [
        'title' => 'Spring welcome',
        'type' => 'percentage',
        'code' => 'SPRING10',
        'value' => '10',
        'audience' => 'everyone',
        'minimum' => '25.00',
        'uses' => '500',
        'starts_at' => '2026-09-01',
        'ends_at' => '2026-10-31',
        'active' => true,
    ] : [
        'title' => '',
        'type' => 'percentage',
        'code' => '',
        'value' => '',
        'audience' => 'everyone',
        'minimum' => '',
        'uses' => '',
        'starts_at' => '',
        'ends_at' => '',
        'active' => false,
    ];

    $blockers ??= $isEdit ? [] : [
        ['field' => 'title', 'text' => __('Give the discount a name.'), 'target' => 'title'],
        ['field' => 'code', 'text' => __('Set the code customers will type.'), 'target' => 'code'],
        ['field' => 'value', 'text' => __('Set how much comes off.'), 'target' => 'value'],
    ];
    $ready = $blockers === [];

    $summary ??= [
        ['label' => __('Rule'), 'value' => $isEdit ? __('10% off orders over €25.00') : __('Not decided yet')],
        ['label' => __('Audience'), 'value' => __('Everyone')],
        ['label' => __('Ceiling'), 'value' => $isEdit ? __('500 uses') : __('No limit'), 'tone' => $isEdit ? null : 'attention'],
        ['label' => __('Window'), 'value' => $isEdit ? '1 Sep – 31 Oct 2026' : __('Open-ended')],
    ];

    $banners = $duplicatedFrom
        ? [[
            'tone' => 'info',
            'title' => __('Duplicated from :name', ['name' => $duplicatedFrom['title'] ?? '']),
            'body' => __('Everything was copied except the code, which has to be unique.'),
            'actionLabel' => $duplicatedFrom['title'] ?? __('Open original'),
            'actionHref' => $duplicatedFrom['href'] ?? '#',
        ]]
        : [];

    $trail = array_values(array_filter([
        ['label' => __('Discounts'), 'href' => '#'],
        $isEdit ? ['label' => $values['title'] ?? '', 'href' => $recordHref] : null,
        ['label' => $title],
    ]));
@endphp

<main
    id="main-content"
    data-slot="admin-form-page"
    data-surface="admin"
    data-mode="{{ $isEdit ? 'edit' : 'create' }}"
    {{ $attributes->merge(['class' => 'min-h-screen bg-background text-foreground']) }}
>
    <x-ui.admin.page-bar :title="$title" :trail="$trail" :record="[]" :nav-toggle="false" />

    <div class="mx-auto flex max-w-screen-2xl flex-col gap-6 px-4 py-6 pb-24 sm:px-6 lg:pb-6">
        <x-ui.admin.page-header :title="$title" :scope="$subtitle">
            <x-slot:actions>
                <x-ui.badge :tone="$ready ? 'positive' : 'attention'" size="status">
                    {{ $ready ? __('Ready to save') : trans_choice(':count thing missing|:count things missing', count($blockers), ['count' => count($blockers)]) }}
                </x-ui.badge>
                @if ($isEdit)
                    <x-ui.button variant="ghost" size="sm" :href="$recordHref">{{ __('Back to record') }}</x-ui.button>
                @endif
            </x-slot:actions>
        </x-ui.admin.page-header>

        <x-ui.admin.attention-banners :banners="$banners" />

        <x-ui.form :action="$action" :method="$method" class="grid items-start gap-6 !space-y-0 lg:grid-cols-[minmax(0,1fr)_20rem]">
            <div class="flex min-w-0 flex-col gap-6">
                <x-ui.admin.record-section id="section-kind" :title="__('What kind')" :hint="__('Decides which of the fields below exist. Locked once the discount has been used.')">
                    <x-ui.field name="title" :label="__('Name')" :hint="__('Internal. Customers see the code, not the name.')" required>
                        <x-ui.input id="title" :value="$values['title'] ?? ''" autocomplete="off" />
                    </x-ui.field>

                    <fieldset class="mt-6">
                        <legend class="text-sm font-medium">{{ __('Type') }}</legend>
                        <div class="mt-2 grid gap-2 sm:grid-cols-3">
                            @foreach ($types as $type)
                                <x-ui.choicebox
                                    type="radio"
                                    name="type"
                                    :value="$type['value']"
                                    :title="$type['label']"
                                    :description="$type['hint'] ?? null"
                                    :checked="($values['type'] ?? null) === $type['value']"
                                />
                            @endforeach
                        </div>
                    </fieldset>
                </x-ui.admin.record-section>

                <x-ui.admin.record-section id="section-value" :title="__('What the customer gets')" :hint="__('The code and the amount. A percentage over 50% is legitimate, but it is chosen on purpose.')">
                    <div class="grid gap-6 sm:grid-cols-2">
                        <x-ui.field name="code" :label="__('Code')" :hint="__('Upper-case letters and digits, 4–16 characters.')" required>
                            <x-ui.input id="code" :value="$values['code'] ?? ''" autocomplete="off" spellcheck="false" class="font-mono uppercase" />
                        </x-ui.field>
                        <x-ui.field name="value" :label="__('Value')" :hint="__('Percentage or amount, depending on the type.')" required>
                            <x-ui.input id="value" type="number" inputmode="decimal" min="0" step="0.01" :value="$values['value'] ?? ''" />
                        </x-ui.field>
                    </div>
                </x-ui.admin.record-section>

                <x-ui.admin.record-section id="section-audience" :title="__('Who may use it')" :hint="__('Everyone, or a segment. A minimum order keeps a code from being applied to a single low-value line.')">
                    <div class="grid gap-6 sm:grid-cols-2">
                        <x-ui.field name="audience" :label="__('Audience')">
                            <x-ui.select id="audience">
                                <option value="everyone" @selected(($values['audience'] ?? '') === 'everyone')>{{ __('Everyone') }}</option>
                                <option value="new" @selected(($values['audience'] ?? '') === 'new')>{{ __('First-time customers') }}</option>
                                <option value="segment" @selected(($values['audience'] ?? '') === 'segment')>{{ __('A segment…') }}</option>
                            </x-ui.select>
                        </x-ui.field>
                        <x-ui.field name="minimum" :label="__('Minimum order')" :hint="__('Leave empty for no minimum.')">
                            <x-ui.input id="minimum" type="number" inputmode="decimal" min="0" step="0.01" :value="$values['minimum'] ?? ''" />
                        </x-ui.field>
                    </div>
                </x-ui.admin.record-section>

                <x-ui.admin.record-section id="section-limits" :title="__('What limits it')" :hint="__('Without a ceiling a campaign can run away. State it here as a figure, before it runs.')">
                    <div class="grid gap-6 sm:grid-cols-2">
                        <x-ui.field name="uses" :label="__('Total uses')" :hint="__('Across all customers.')">
                            <x-ui.input id="uses" type="number" inputmode="numeric" min="0" step="1" :value="$values['uses'] ?? ''" />
                        </x-ui.field>
                        <x-ui.field name="notes" :label="__('Internal note')" :hint="__('Why this discount exists. Shown on the record, never to customers.')">
                            <x-ui.textarea id="notes" rows="3"></x-ui.textarea>
                        </x-ui.field>
                    </div>
                </x-ui.admin.record-section>

                <x-ui.admin.record-section id="section-window" :title="__('When it runs')" :hint="__('Time moves the window on its own; the switch is a decision a person makes. They are kept apart so a paused campaign is never confused with an expired one.')">
                    <div class="grid gap-6 sm:grid-cols-2">
                        <x-ui.field name="starts_at" :label="__('Starts')">
                            <x-ui.input id="starts_at" type="date" :value="$values['starts_at'] ?? ''" />
                        </x-ui.field>
                        <x-ui.field name="ends_at" :label="__('Ends')" :hint="__('Leave empty for an open-ended window.')">
                            <x-ui.input id="ends_at" type="date" :value="$values['ends_at'] ?? ''" />
                        </x-ui.field>
                    </div>
                    <div class="mt-6">
                        <x-ui.switch name="active" id="active" :label="__('Switched on')" :checked="(bool) ($values['active'] ?? false)" />
                    </div>
                </x-ui.admin.record-section>
            </div>

            <aside class="flex min-w-0 flex-col gap-6 lg:sticky lg:top-20" aria-label="{{ __('Summary and actions') }}">
                <x-ui.admin.record-section :title="__('Summary')" :hint="__('What saving would write.')">
                    <x-ui.fact-list :facts="$summary" />
                </x-ui.admin.record-section>

                <x-ui.admin.save-blockers
                    :blockers="$blockers"
                    :actions="[
                        ['label' => $isEdit ? __('Save changes') : __('Create discount'), 'primary' => true],
                        ['label' => __('Save as draft'), 'variant' => 'outline', 'requiresValid' => false],
                    ]"
                    :cancel-href="$cancelHref"
                    :note="__('⌘S saves. Blockers clear as each field is fixed.')"
                    :dock-label="__('Form actions')"
                />
            </aside>
        </x-ui.form>
    </div>
</main>

Ownership & lifecycle

Owner, release state, review evidence and adoption for this item.
Owner
Platform UI (@JoshJML)
Current version
1.0.3
Status
Stable
License
open
Deprecation
Not deprecated
Contract
v4
Foundation
≥ 1.0.0