feat(ui): standard modal shell — fixed header/footer, scrollable body, 2-col

Issue 3: the New-Automation modal was taller than the screen and the whole
dialog scrolled. New <x-modal> component: header + footer are fixed (shrink-0),
only the body scrolls, capped to max-h-85dvh so the page never scrolls. Refactored
create-automation (now a responsive 2-column grid, widened to 3xl), add-device,
ring-connect and add-person onto it — the standard for every modal going forward.
Verified: modal fits a 720px viewport with no page scroll; create flow still works.

12/12 tabs clean, suite green.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
feat/phase-1-bootstrap
HomeOS Bootstrap 2026-07-18 10:09:24 +02:00
parent 2fa15decb4
commit f028528f1f
6 changed files with 215 additions and 215 deletions

View File

@ -34,7 +34,7 @@ class CreateAutomation extends ModalComponent
public static function modalMaxWidth(): string
{
return 'lg';
return '3xl';
}
/** @return array<int, array{value:string,label:string,controllable:bool}> */

View File

@ -0,0 +1,28 @@
@props(['title' => null, 'icon' => null])
{{-- Standard modal frame: header + footer stay fixed, only the body scrolls, capped to the
viewport so a tall form never scrolls the whole dialog. Body content controls its own
padding/columns. Works on phone, tablet and desktop. --}}
<div class="flex flex-col max-h-[85dvh] w-full">
<header class="flex items-center gap-3 px-5 py-4 border-b border-line-soft shrink-0">
@if ($icon)
<span class="grid place-items-center w-9 h-9 rounded-lg bg-accent/10 text-accent shrink-0"><x-icon :name="$icon" :size="18" /></span>
@endif
<h2 class="text-[15px] font-bold text-ink truncate">{{ $title }}</h2>
<button type="button" wire:click="closeModal"
class="ml-auto grid place-items-center w-9 h-9 rounded-lg text-ink-3 hover:bg-raised hover:text-ink transition-colors shrink-0"
aria-label="{{ __('common.close') }}">
<x-icon name="close" :size="18" />
</button>
</header>
<div class="flex-1 overflow-y-auto min-h-0">
{{ $slot }}
</div>
@isset($footer)
<footer class="flex items-center justify-end gap-2 px-5 py-3.5 border-t border-line-soft shrink-0">
{{ $footer }}
</footer>
@endisset
</div>

View File

@ -1,50 +1,44 @@
<div class="flex flex-col">
<header class="flex items-center gap-3 px-5 py-4 border-b border-line-soft">
<span class="grid place-items-center w-9 h-9 rounded-lg bg-accent/10 text-accent shrink-0"><x-icon name="devices" :size="18" /></span>
<h2 class="text-[15px] font-bold text-ink">{{ __('devices.add_title') }}</h2>
<button type="button" wire:click="closeModal" class="ml-auto grid place-items-center w-9 h-9 rounded-lg text-ink-3 hover:bg-raised hover:text-ink transition-colors" aria-label="{{ __('common.close') }}">
<x-icon name="close" :size="18" />
</button>
</header>
<form wire:submit="save">
<x-modal :title="__('devices.add_title')" icon="devices">
<div class="p-5 flex flex-col gap-4">
<p class="text-[12px] text-ink-3 leading-relaxed">{{ __('devices.add_intro') }}</p>
<form wire:submit="save" class="p-5 flex flex-col gap-4">
<p class="text-[12px] text-ink-3 leading-relaxed">{{ __('devices.add_intro') }}</p>
@if ($error)
<p class="rounded-lg border border-offline/30 bg-offline/10 px-3 py-2 text-[12.5px] text-offline">{{ $error }}</p>
@endif
@if ($error)
<p class="rounded-lg border border-offline/30 bg-offline/10 px-3 py-2 text-[12.5px] text-offline">{{ $error }}</p>
@endif
<div class="flex flex-col gap-1.5">
<label for="d-ip" class="text-[12px] font-semibold text-ink-2">{{ __('devices.add_ip') }}</label>
<input wire:model="ip" id="d-ip" type="text" inputmode="decimal" placeholder="10.10.30.78" autofocus
class="w-full rounded-lg bg-raised border border-line px-3 py-2.5 text-sm font-mono text-ink outline-none focus:border-accent focus:ring-1 focus:ring-accent">
@error('ip') <p class="text-[12px] text-offline">{{ $message }}</p> @enderror
</div>
<div class="flex flex-col gap-1.5">
<label for="d-ip" class="text-[12px] font-semibold text-ink-2">{{ __('devices.add_ip') }}</label>
<input wire:model="ip" id="d-ip" type="text" inputmode="decimal" placeholder="10.10.30.78" autofocus
class="w-full rounded-lg bg-raised border border-line px-3 py-2.5 text-sm font-mono text-ink outline-none focus:border-accent focus:ring-1 focus:ring-accent">
@error('ip') <p class="text-[12px] text-offline">{{ $message }}</p> @enderror
<div class="flex flex-col gap-1.5">
<label for="d-name" class="text-[12px] font-semibold text-ink-2">{{ __('devices.name') }} <span class="text-ink-3 font-normal">({{ __('devices.add_optional') }})</span></label>
<input wire:model="name" id="d-name" type="text"
class="w-full rounded-lg bg-raised border border-line px-3 py-2.5 text-sm text-ink outline-none focus:border-accent focus:ring-1 focus:ring-accent">
@error('name') <p class="text-[12px] text-offline">{{ $message }}</p> @enderror
</div>
<div class="flex flex-col gap-1.5">
<label for="d-room" class="text-[12px] font-semibold text-ink-2">{{ __('devices.room') }} <span class="text-ink-3 font-normal">({{ __('devices.add_optional') }})</span></label>
<select wire:model="roomId" id="d-room" class="w-full rounded-lg bg-raised border border-line px-3 py-2.5 text-sm text-ink outline-none focus:border-accent">
<option value="">{{ __('devices.no_room') }}</option>
@foreach ($rooms as $room)
<option value="{{ $room->id }}">{{ $room->name }}</option>
@endforeach
</select>
</div>
</div>
<div class="flex flex-col gap-1.5">
<label for="d-name" class="text-[12px] font-semibold text-ink-2">{{ __('devices.name') }} <span class="text-ink-3 font-normal">({{ __('devices.add_optional') }})</span></label>
<input wire:model="name" id="d-name" type="text"
class="w-full rounded-lg bg-raised border border-line px-3 py-2.5 text-sm text-ink outline-none focus:border-accent focus:ring-1 focus:ring-accent">
@error('name') <p class="text-[12px] text-offline">{{ $message }}</p> @enderror
</div>
<div class="flex flex-col gap-1.5">
<label for="d-room" class="text-[12px] font-semibold text-ink-2">{{ __('devices.room') }} <span class="text-ink-3 font-normal">({{ __('devices.add_optional') }})</span></label>
<select wire:model="roomId" id="d-room" class="w-full rounded-lg bg-raised border border-line px-3 py-2.5 text-sm text-ink outline-none focus:border-accent">
<option value="">{{ __('devices.no_room') }}</option>
@foreach ($rooms as $room)
<option value="{{ $room->id }}">{{ $room->name }}</option>
@endforeach
</select>
</div>
<div class="flex items-center justify-end gap-2 pt-1">
<x-slot:footer>
<button type="button" wire:click="closeModal" class="rounded-lg border border-line px-3.5 py-2 text-[13px] font-semibold text-ink-2 hover:text-ink hover:bg-raised transition-colors">{{ __('common.cancel') }}</button>
<button type="submit" wire:loading.attr="disabled" wire:target="save"
class="rounded-lg bg-accent px-3.5 py-2 text-[13px] font-bold text-base hover:brightness-110 transition-[filter] disabled:opacity-60">
<span wire:loading.remove wire:target="save">{{ __('devices.add_submit') }}</span>
<span wire:loading wire:target="save">{{ __('devices.add_probing') }}</span>
</button>
</div>
</form>
</div>
</x-slot:footer>
</x-modal>
</form>

View File

@ -1,41 +1,35 @@
<div class="flex flex-col">
<header class="flex items-center gap-3 px-5 py-4 border-b border-line-soft">
<span class="grid place-items-center w-9 h-9 rounded-lg bg-accent/10 text-accent shrink-0"><x-icon name="persons" :size="18" /></span>
<h2 class="text-[15px] font-bold text-ink">{{ __('persons.add_title') }}</h2>
<button type="button" wire:click="closeModal" class="ml-auto grid place-items-center w-9 h-9 rounded-lg text-ink-3 hover:bg-raised hover:text-ink transition-colors" aria-label="{{ __('common.close') }}">
<x-icon name="close" :size="18" />
</button>
</header>
<form wire:submit="save">
<x-modal :title="__('persons.add_title')" icon="persons">
<div class="p-5 flex flex-col gap-4">
<div class="flex flex-col gap-1.5">
<label for="p-name" class="text-[12px] font-semibold text-ink-2">{{ __('devices.name') }}</label>
<input wire:model="name" id="p-name" type="text" autofocus
class="w-full rounded-lg bg-raised border border-line px-3 py-2.5 text-sm text-ink outline-none focus:border-accent focus:ring-1 focus:ring-accent">
@error('name') <p class="text-[12px] text-offline">{{ $message }}</p> @enderror
</div>
<form wire:submit="save" class="p-5 flex flex-col gap-4">
<div class="flex flex-col gap-1.5">
<label for="p-name" class="text-[12px] font-semibold text-ink-2">{{ __('devices.name') }}</label>
<input wire:model="name" id="p-name" type="text" autofocus
class="w-full rounded-lg bg-raised border border-line px-3 py-2.5 text-sm text-ink outline-none focus:border-accent focus:ring-1 focus:ring-accent">
@error('name') <p class="text-[12px] text-offline">{{ $message }}</p> @enderror
<div class="flex flex-col gap-1.5">
<label for="p-mac" class="text-[12px] font-semibold text-ink-2">{{ __('persons.device') }}</label>
@if ($unifiError)
<p class="text-[12px] text-warning">{{ __('persons.unifi_error') }}</p>
<input wire:model="mac" id="p-mac" type="text" placeholder="aa:bb:cc:dd:ee:ff"
class="w-full rounded-lg bg-raised border border-line px-3 py-2.5 text-sm font-mono text-ink outline-none focus:border-accent focus:ring-1 focus:ring-accent">
@else
<select wire:model="mac" id="p-mac" class="w-full rounded-lg bg-raised border border-line px-3 py-2.5 text-sm text-ink outline-none focus:border-accent">
<option value="">{{ __('persons.pick_client') }}</option>
@foreach ($clientOptions as $o)
<option value="{{ $o['mac'] }}">{{ $o['label'] }} · {{ $o['mac'] }}</option>
@endforeach
</select>
<p class="text-[11.5px] text-ink-3">{{ __('persons.client_hint') }}</p>
@endif
@error('mac') <p class="text-[12px] text-offline">{{ $message }}</p> @enderror
</div>
</div>
<div class="flex flex-col gap-1.5">
<label for="p-mac" class="text-[12px] font-semibold text-ink-2">{{ __('persons.device') }}</label>
@if ($unifiError)
<p class="text-[12px] text-warning">{{ __('persons.unifi_error') }}</p>
<input wire:model="mac" id="p-mac" type="text" placeholder="aa:bb:cc:dd:ee:ff"
class="w-full rounded-lg bg-raised border border-line px-3 py-2.5 text-sm font-mono text-ink outline-none focus:border-accent focus:ring-1 focus:ring-accent">
@else
<select wire:model="mac" id="p-mac" class="w-full rounded-lg bg-raised border border-line px-3 py-2.5 text-sm text-ink outline-none focus:border-accent">
<option value="">{{ __('persons.pick_client') }}</option>
@foreach ($clientOptions as $o)
<option value="{{ $o['mac'] }}">{{ $o['label'] }} · {{ $o['mac'] }}</option>
@endforeach
</select>
<p class="text-[11.5px] text-ink-3">{{ __('persons.client_hint') }}</p>
@endif
@error('mac') <p class="text-[12px] text-offline">{{ $message }}</p> @enderror
</div>
<div class="flex items-center justify-end gap-2 pt-1">
<x-slot:footer>
<button type="button" wire:click="closeModal" class="rounded-lg border border-line px-3.5 py-2 text-[13px] font-semibold text-ink-2 hover:text-ink hover:bg-raised transition-colors">{{ __('common.cancel') }}</button>
<button type="submit" class="rounded-lg bg-accent px-3.5 py-2 text-[13px] font-bold text-base hover:brightness-110 transition-[filter]">{{ __('persons.add') }}</button>
</div>
</form>
</div>
</x-slot:footer>
</x-modal>
</form>

View File

@ -1,119 +1,109 @@
<div class="flex flex-col">
<header class="flex items-center gap-3 px-5 py-4 border-b border-line-soft">
<span class="grid place-items-center w-9 h-9 rounded-lg bg-accent/10 text-accent shrink-0"><x-icon name="automation" :size="18" /></span>
<h2 class="text-[15px] font-bold text-ink">{{ __('automations.add_title') }}</h2>
<button type="button" wire:click="closeModal" class="ml-auto grid place-items-center w-9 h-9 rounded-lg text-ink-3 hover:bg-raised hover:text-ink transition-colors" aria-label="{{ __('common.close') }}">
<x-icon name="close" :size="18" />
</button>
</header>
<form wire:submit="save">
<x-modal :title="__('automations.add_title')" icon="automation">
<div class="p-5 grid grid-cols-1 sm:grid-cols-2 gap-4">
<div class="flex flex-col gap-1.5 sm:col-span-2">
<label for="a-name" class="text-[12px] font-semibold text-ink-2">{{ __('automations.name') }}</label>
<input wire:model="name" id="a-name" type="text" autofocus
class="w-full rounded-lg bg-raised border border-line px-3 py-2.5 text-sm text-ink outline-none focus:border-accent focus:ring-1 focus:ring-accent">
@error('name') <p class="text-[12px] text-offline">{{ $message }}</p> @enderror
</div>
<form wire:submit="save" class="p-5 flex flex-col gap-4">
<div class="flex flex-col gap-1.5">
<label for="a-name" class="text-[12px] font-semibold text-ink-2">{{ __('automations.name') }}</label>
<input wire:model="name" id="a-name" type="text" autofocus
class="w-full rounded-lg bg-raised border border-line px-3 py-2.5 text-sm text-ink outline-none focus:border-accent focus:ring-1 focus:ring-accent">
@error('name') <p class="text-[12px] text-offline">{{ $message }}</p> @enderror
</div>
{{-- Trigger --}}
<fieldset class="flex flex-col gap-3 rounded-xl border border-line-soft p-3.5">
<legend class="px-1.5 text-[11px] font-bold uppercase tracking-wide text-ink-3">{{ __('automations.trigger') }}</legend>
{{-- Trigger --}}
<fieldset class="flex flex-col gap-3 rounded-xl border border-line-soft p-3.5">
<legend class="px-1.5 text-[11px] font-bold uppercase tracking-wide text-ink-3">{{ __('automations.trigger') }}</legend>
<div class="flex flex-col gap-1.5">
<select wire:model.live="triggerType" class="w-full rounded-lg bg-raised border border-line px-3 py-2.5 text-sm text-ink outline-none focus:border-accent">
<option value="state_change">{{ __('automations.trigger_state_change') }}</option>
<option value="time">{{ __('automations.trigger_time') }}</option>
</select>
</div>
@if ($triggerType === 'state_change')
<div class="flex flex-col gap-1.5">
<label for="a-tent" class="text-[12px] font-semibold text-ink-2">{{ __('automations.trigger_entity') }}</label>
<select wire:model="triggerEntity" id="a-tent" class="w-full rounded-lg bg-raised border border-line px-3 py-2.5 text-sm text-ink outline-none focus:border-accent">
<option value=""></option>
@foreach ($this->entityOptions as $o)
<option value="{{ $o['value'] }}">{{ $o['label'] }}</option>
@endforeach
</select>
@error('triggerEntity') <p class="text-[12px] text-offline">{{ $message }}</p> @enderror
</div>
<div class="flex flex-col gap-1.5">
<label for="a-teq" class="text-[12px] font-semibold text-ink-2">{{ __('automations.trigger_equals') }}</label>
<select wire:model="triggerEquals" id="a-teq" class="w-full rounded-lg bg-raised border border-line px-3 py-2.5 text-sm text-ink outline-none focus:border-accent">
<option value="1">{{ __('automations.state_on') }}</option>
<option value="0">{{ __('automations.state_off') }}</option>
</select>
</div>
@else
<div class="flex flex-col gap-1.5">
<label for="a-at" class="text-[12px] font-semibold text-ink-2">{{ __('automations.trigger_at') }}</label>
<input wire:model="triggerAt" id="a-at" type="time"
class="w-full rounded-lg bg-raised border border-line px-3 py-2.5 text-sm text-ink outline-none focus:border-accent focus:ring-1 focus:ring-accent">
@error('triggerAt') <p class="text-[12px] text-offline">{{ $message }}</p> @enderror
</div>
@endif
</fieldset>
@if ($triggerType === 'state_change')
<div class="flex flex-col gap-1.5">
<label for="a-tent" class="text-[12px] font-semibold text-ink-2">{{ __('automations.trigger_entity') }}</label>
<select wire:model="triggerEntity" id="a-tent" class="w-full rounded-lg bg-raised border border-line px-3 py-2.5 text-sm text-ink outline-none focus:border-accent">
<option value=""></option>
@foreach ($this->entityOptions as $o)
<option value="{{ $o['value'] }}">{{ $o['label'] }}</option>
@endforeach
</select>
@error('triggerEntity') <p class="text-[12px] text-offline">{{ $message }}</p> @enderror
</div>
<div class="flex flex-col gap-1.5">
<label for="a-teq" class="text-[12px] font-semibold text-ink-2">{{ __('automations.trigger_equals') }}</label>
<select wire:model="triggerEquals" id="a-teq" class="w-full rounded-lg bg-raised border border-line px-3 py-2.5 text-sm text-ink outline-none focus:border-accent">
<option value="1">{{ __('automations.state_on') }}</option>
<option value="0">{{ __('automations.state_off') }}</option>
</select>
</div>
@else
<div class="flex flex-col gap-1.5">
<label for="a-at" class="text-[12px] font-semibold text-ink-2">{{ __('automations.trigger_at') }}</label>
<input wire:model="triggerAt" id="a-at" type="time"
class="w-full rounded-lg bg-raised border border-line px-3 py-2.5 text-sm text-ink outline-none focus:border-accent focus:ring-1 focus:ring-accent">
@error('triggerAt') <p class="text-[12px] text-offline">{{ $message }}</p> @enderror
</div>
@endif
</fieldset>
{{-- Action --}}
<fieldset class="flex flex-col gap-3 rounded-xl border border-line-soft p-3.5">
<legend class="px-1.5 text-[11px] font-bold uppercase tracking-wide text-ink-3">{{ __('automations.action') }}</legend>
{{-- Action --}}
<fieldset class="flex flex-col gap-3 rounded-xl border border-line-soft p-3.5">
<legend class="px-1.5 text-[11px] font-bold uppercase tracking-wide text-ink-3">{{ __('automations.action') }}</legend>
<div class="flex flex-col gap-1.5">
<select wire:model.live="actionType" class="w-full rounded-lg bg-raised border border-line px-3 py-2.5 text-sm text-ink outline-none focus:border-accent">
<option value="switch">{{ __('automations.action_switch') }}</option>
<option value="notify">{{ __('automations.action_notify') }}</option>
</select>
@if ($actionType === 'switch')
<div class="flex flex-col gap-1.5">
<label for="a-aent" class="text-[12px] font-semibold text-ink-2">{{ __('automations.action_entity') }}</label>
<select wire:model="actionEntity" id="a-aent" class="w-full rounded-lg bg-raised border border-line px-3 py-2.5 text-sm text-ink outline-none focus:border-accent">
<option value=""></option>
@foreach ($this->entityOptions as $o)
@if ($o['controllable'])
<option value="{{ $o['value'] }}">{{ $o['label'] }}</option>
@endif
@endforeach
</select>
@error('actionEntity') <p class="text-[12px] text-offline">{{ $message }}</p> @enderror
</div>
<div class="flex flex-col gap-1.5">
<label for="a-aon" class="text-[12px] font-semibold text-ink-2">{{ __('automations.action_on') }}</label>
<select wire:model="actionOn" id="a-aon" class="w-full rounded-lg bg-raised border border-line px-3 py-2.5 text-sm text-ink outline-none focus:border-accent">
<option value="1">{{ __('automations.state_on') }}</option>
<option value="0">{{ __('automations.state_off') }}</option>
</select>
</div>
@else
<div class="flex flex-col gap-1.5">
<label for="a-msg" class="text-[12px] font-semibold text-ink-2">{{ __('automations.message') }}</label>
<input wire:model="actionMessage" id="a-msg" type="text"
class="w-full rounded-lg bg-raised border border-line px-3 py-2.5 text-sm text-ink outline-none focus:border-accent focus:ring-1 focus:ring-accent">
@error('actionMessage') <p class="text-[12px] text-offline">{{ $message }}</p> @enderror
</div>
@endif
</fieldset>
<div class="flex flex-col gap-1.5">
<label for="a-cd" class="text-[12px] font-semibold text-ink-2">{{ __('automations.cooldown') }}</label>
<input wire:model="cooldownSeconds" id="a-cd" type="number" min="0" max="86400"
class="w-full rounded-lg bg-raised border border-line px-3 py-2.5 text-sm text-ink outline-none focus:border-accent focus:ring-1 focus:ring-accent">
<p class="text-[11.5px] text-ink-3">{{ __('automations.cooldown_hint') }}</p>
@error('cooldownSeconds') <p class="text-[12px] text-offline">{{ $message }}</p> @enderror
</div>
@if ($actionType === 'switch')
<div class="flex flex-col gap-1.5">
<label for="a-aent" class="text-[12px] font-semibold text-ink-2">{{ __('automations.action_entity') }}</label>
<select wire:model="actionEntity" id="a-aent" class="w-full rounded-lg bg-raised border border-line px-3 py-2.5 text-sm text-ink outline-none focus:border-accent">
<option value=""></option>
@foreach ($this->entityOptions as $o)
@if ($o['controllable'])
<option value="{{ $o['value'] }}">{{ $o['label'] }}</option>
@endif
@endforeach
</select>
@error('actionEntity') <p class="text-[12px] text-offline">{{ $message }}</p> @enderror
</div>
<div class="flex flex-col gap-1.5">
<label for="a-aon" class="text-[12px] font-semibold text-ink-2">{{ __('automations.action_on') }}</label>
<select wire:model="actionOn" id="a-aon" class="w-full rounded-lg bg-raised border border-line px-3 py-2.5 text-sm text-ink outline-none focus:border-accent">
<option value="1">{{ __('automations.state_on') }}</option>
<option value="0">{{ __('automations.state_off') }}</option>
</select>
</div>
@else
<div class="flex flex-col gap-1.5">
<label for="a-msg" class="text-[12px] font-semibold text-ink-2">{{ __('automations.message') }}</label>
<input wire:model="actionMessage" id="a-msg" type="text"
class="w-full rounded-lg bg-raised border border-line px-3 py-2.5 text-sm text-ink outline-none focus:border-accent focus:ring-1 focus:ring-accent">
@error('actionMessage') <p class="text-[12px] text-offline">{{ $message }}</p> @enderror
</div>
@endif
</fieldset>
<div class="flex flex-col gap-1.5">
<label for="a-cd" class="text-[12px] font-semibold text-ink-2">{{ __('automations.cooldown') }}</label>
<input wire:model="cooldownSeconds" id="a-cd" type="number" min="0" max="86400"
class="w-full rounded-lg bg-raised border border-line px-3 py-2.5 text-sm text-ink outline-none focus:border-accent focus:ring-1 focus:ring-accent">
<p class="text-[11.5px] text-ink-3">{{ __('automations.cooldown_hint') }}</p>
@error('cooldownSeconds') <p class="text-[12px] text-offline">{{ $message }}</p> @enderror
<label class="flex items-start gap-3 cursor-pointer self-end pb-1">
<input wire:model="dryRun" type="checkbox" class="mt-0.5 w-4 h-4 rounded border-line text-accent focus:ring-accent">
<span class="flex flex-col">
<span class="text-[13px] font-semibold text-ink">{{ __('automations.dry_run') }}</span>
<span class="text-[11.5px] text-ink-3">{{ __('automations.dry_run_hint') }}</span>
</span>
</label>
</div>
<label class="flex items-start gap-3 cursor-pointer">
<input wire:model="dryRun" type="checkbox" class="mt-0.5 w-4 h-4 rounded border-line text-accent focus:ring-accent">
<span class="flex flex-col">
<span class="text-[13px] font-semibold text-ink">{{ __('automations.dry_run') }}</span>
<span class="text-[11.5px] text-ink-3">{{ __('automations.dry_run_hint') }}</span>
</span>
</label>
<div class="flex items-center justify-end gap-2 pt-1">
<x-slot:footer>
<button type="button" wire:click="closeModal" class="rounded-lg border border-line px-3.5 py-2 text-[13px] font-semibold text-ink-2 hover:text-ink hover:bg-raised transition-colors">{{ __('common.cancel') }}</button>
<button type="submit" class="rounded-lg bg-accent px-3.5 py-2 text-[13px] font-bold text-base hover:brightness-110 transition-[filter]">{{ __('automations.add') }}</button>
</div>
</form>
</div>
</x-slot:footer>
</x-modal>
</form>

View File

@ -1,45 +1,39 @@
<div class="flex flex-col">
<header class="flex items-center gap-3 px-5 py-4 border-b border-line-soft">
<span class="grid place-items-center w-9 h-9 rounded-lg bg-accent/10 text-accent shrink-0"><x-icon name="doorbell" :size="18" /></span>
<h2 class="text-[15px] font-bold text-ink">{{ __('addons.ring.connect_title') }}</h2>
<button type="button" wire:click="closeModal" class="ml-auto grid place-items-center w-9 h-9 rounded-lg text-ink-3 hover:bg-raised hover:text-ink transition-colors" aria-label="{{ __('common.close') }}">
<x-icon name="close" :size="18" />
</button>
</header>
<form wire:submit="save">
<x-modal :title="__('addons.ring.connect_title')" icon="doorbell">
<div class="p-5 flex flex-col gap-4">
<p class="text-[12px] text-ink-3 leading-relaxed flex items-start gap-2">
<x-icon name="cloud" :size="15" class="mt-0.5 shrink-0" />
<span>{{ __('addons.ring.backend_note') }}</span>
</p>
<form wire:submit="save" class="p-5 flex flex-col gap-4">
<p class="text-[12px] text-ink-3 leading-relaxed flex items-start gap-2">
<x-icon name="cloud" :size="15" class="mt-0.5 shrink-0" />
<span>{{ __('addons.ring.backend_note') }}</span>
</p>
<div class="flex flex-col gap-1.5">
<label for="ring-email" class="text-[12px] font-semibold text-ink-2">{{ __('addons.ring.email') }}</label>
<input wire:model="email" id="ring-email" type="email" autocomplete="off" autofocus
class="w-full rounded-lg bg-raised border border-line px-3 py-2.5 text-sm text-ink outline-none focus:border-accent focus:ring-1 focus:ring-accent">
@error('email') <p class="text-[12px] text-offline">{{ $message }}</p> @enderror
</div>
<div class="flex flex-col gap-1.5">
<label for="ring-email" class="text-[12px] font-semibold text-ink-2">{{ __('addons.ring.email') }}</label>
<input wire:model="email" id="ring-email" type="email" autocomplete="off" autofocus
class="w-full rounded-lg bg-raised border border-line px-3 py-2.5 text-sm text-ink outline-none focus:border-accent focus:ring-1 focus:ring-accent">
@error('email') <p class="text-[12px] text-offline">{{ $message }}</p> @enderror
<div class="flex flex-col gap-1.5">
<label for="ring-password" class="text-[12px] font-semibold text-ink-2">{{ __('addons.ring.password') }}</label>
<input wire:model="password" id="ring-password" type="password" autocomplete="new-password"
class="w-full rounded-lg bg-raised border border-line px-3 py-2.5 text-sm text-ink outline-none focus:border-accent focus:ring-1 focus:ring-accent">
@if ($hasStoredPassword)
<p class="text-[11.5px] text-ink-3">{{ __('addons.ring.password_stored') }}</p>
@endif
@error('password') <p class="text-[12px] text-offline">{{ $message }}</p> @enderror
</div>
<div class="flex flex-col gap-1.5">
<label for="ring-code" class="text-[12px] font-semibold text-ink-2">{{ __('addons.ring.code') }}</label>
<input wire:model="code" id="ring-code" type="text" inputmode="numeric" autocomplete="one-time-code"
class="w-full rounded-lg bg-raised border border-line px-3 py-2.5 text-sm font-mono text-ink outline-none focus:border-accent focus:ring-1 focus:ring-accent">
@error('code') <p class="text-[12px] text-offline">{{ $message }}</p> @enderror
</div>
</div>
<div class="flex flex-col gap-1.5">
<label for="ring-password" class="text-[12px] font-semibold text-ink-2">{{ __('addons.ring.password') }}</label>
<input wire:model="password" id="ring-password" type="password" autocomplete="new-password"
class="w-full rounded-lg bg-raised border border-line px-3 py-2.5 text-sm text-ink outline-none focus:border-accent focus:ring-1 focus:ring-accent">
@if ($hasStoredPassword)
<p class="text-[11.5px] text-ink-3">{{ __('addons.ring.password_stored') }}</p>
@endif
@error('password') <p class="text-[12px] text-offline">{{ $message }}</p> @enderror
</div>
<div class="flex flex-col gap-1.5">
<label for="ring-code" class="text-[12px] font-semibold text-ink-2">{{ __('addons.ring.code') }}</label>
<input wire:model="code" id="ring-code" type="text" inputmode="numeric" autocomplete="one-time-code"
class="w-full rounded-lg bg-raised border border-line px-3 py-2.5 text-sm font-mono text-ink outline-none focus:border-accent focus:ring-1 focus:ring-accent">
@error('code') <p class="text-[12px] text-offline">{{ $message }}</p> @enderror
</div>
<div class="flex items-center justify-end gap-2 pt-1">
<x-slot:footer>
<button type="button" wire:click="closeModal" class="rounded-lg border border-line px-3.5 py-2 text-[13px] font-semibold text-ink-2 hover:text-ink hover:bg-raised transition-colors">{{ __('common.cancel') }}</button>
<button type="submit" class="rounded-lg bg-accent px-3.5 py-2 text-[13px] font-bold text-base hover:brightness-110 transition-[filter]">{{ __('addons.ring.save') }}</button>
</div>
</form>
</div>
</x-slot:footer>
</x-modal>
</form>