Flux durch TallStackUI 4 ersetzt
- TallStackUI 4 (MIT) statt Flux; Komponenten mit Präfix ts- (<x-ts-…>) - Layouts, Login-, Passwort-, 2FA- und Einstellungsseiten neu aufgebaut - Livewire-Skripte explizit in beiden Layouts, damit Alpine auch auf den Fortify-Seiten ohne Livewire-Komponente läuft - Dark Mode über tallstackui_darkTheme(), ohne Aufblitzen beim Laden - Registrierungs-View und ungenutzte Layout-Varianten entfernt - Test: Seiten laden keine Ressourcen von fremden Hosts (Vorgabe "alles lokal") Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5.5
parent
dedce594a0
commit
b297518810
@@ -1,17 +1,6 @@
|
||||
@props([
|
||||
'sidebar' => false,
|
||||
])
|
||||
|
||||
@if($sidebar)
|
||||
<flux:sidebar.brand :name="config('app.name', 'Laravel')" {{ $attributes }}>
|
||||
<x-slot name="logo" class="flex aspect-square size-8 items-center justify-center rounded-md bg-accent-content text-accent-foreground">
|
||||
<x-app-logo-icon class="size-5 fill-current text-white dark:text-black" />
|
||||
</x-slot>
|
||||
</flux:sidebar.brand>
|
||||
@else
|
||||
<flux:brand :name="config('app.name', 'Laravel')" {{ $attributes }}>
|
||||
<x-slot name="logo" class="flex aspect-square size-8 items-center justify-center rounded-md bg-accent-content text-accent-foreground">
|
||||
<x-app-logo-icon class="size-5 fill-current text-white dark:text-black" />
|
||||
</x-slot>
|
||||
</flux:brand>
|
||||
@endif
|
||||
<a href="{{ route('dashboard') }}" wire:navigate {{ $attributes->merge(['class' => 'flex items-center gap-2']) }}>
|
||||
<span class="flex aspect-square size-8 shrink-0 items-center justify-center rounded-md bg-primary-600 text-white">
|
||||
<x-app-logo-icon class="size-5 fill-current" />
|
||||
</span>
|
||||
<span class="truncate font-semibold text-gray-900 dark:text-dark-50">{{ config('app.name', 'Laravel') }}</span>
|
||||
</a>
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
'description',
|
||||
])
|
||||
|
||||
<div class="flex w-full flex-col text-center">
|
||||
<flux:heading size="xl" level="1">{{ $title }}</flux:heading>
|
||||
<flux:subheading>{{ $description }}</flux:subheading>
|
||||
<div class="flex w-full flex-col gap-1 text-center">
|
||||
<x-heading level="1" size="xl">{{ $title }}</x-heading>
|
||||
<x-subheading>{{ $description }}</x-subheading>
|
||||
</div>
|
||||
|
||||
@@ -1,39 +0,0 @@
|
||||
<flux:dropdown position="bottom" align="start">
|
||||
<flux:sidebar.profile
|
||||
:name="auth()->user()->name"
|
||||
:initials="auth()->user()->initials()"
|
||||
icon:trailing="chevrons-up-down"
|
||||
data-test="sidebar-menu-button"
|
||||
/>
|
||||
|
||||
<flux:menu>
|
||||
<div class="flex items-center gap-2 px-1 py-1.5 text-start text-sm">
|
||||
<flux:avatar
|
||||
:name="auth()->user()->name"
|
||||
:initials="auth()->user()->initials()"
|
||||
/>
|
||||
<div class="grid flex-1 text-start text-sm leading-tight">
|
||||
<flux:heading class="truncate">{{ auth()->user()->name }}</flux:heading>
|
||||
<flux:text class="truncate">{{ auth()->user()->email }}</flux:text>
|
||||
</div>
|
||||
</div>
|
||||
<flux:menu.separator />
|
||||
<flux:menu.radio.group>
|
||||
<flux:menu.item :href="route('profile.edit')" icon="cog" wire:navigate>
|
||||
{{ __('Settings') }}
|
||||
</flux:menu.item>
|
||||
<form method="POST" action="{{ route('logout') }}" class="w-full">
|
||||
@csrf
|
||||
<flux:menu.item
|
||||
as="button"
|
||||
type="submit"
|
||||
icon="arrow-right-start-on-rectangle"
|
||||
class="w-full cursor-pointer"
|
||||
data-test="logout-button"
|
||||
>
|
||||
{{ __('Log out') }}
|
||||
</flux:menu.item>
|
||||
</form>
|
||||
</flux:menu.radio.group>
|
||||
</flux:menu>
|
||||
</flux:dropdown>
|
||||
@@ -0,0 +1,14 @@
|
||||
@props([
|
||||
'level' => 2,
|
||||
'size' => 'base',
|
||||
])
|
||||
|
||||
@php
|
||||
$sizeClass = match ($size) {
|
||||
'xl' => 'text-2xl',
|
||||
'lg' => 'text-lg',
|
||||
default => 'text-base',
|
||||
};
|
||||
@endphp
|
||||
|
||||
<h{{ $level }} {{ $attributes->class([$sizeClass, 'font-semibold text-gray-900 dark:text-dark-50']) }}>{{ $slot }}</h{{ $level }}>
|
||||
@@ -66,50 +66,37 @@
|
||||
}"
|
||||
>
|
||||
<template x-if="!supported">
|
||||
<flux:text>{{ __('Passkeys are not supported in this browser.') }}</flux:text>
|
||||
<p class="text-sm text-gray-600 dark:text-dark-300">{{ __('Passkeys are not supported in this browser.') }}</p>
|
||||
</template>
|
||||
|
||||
<template x-if="supported && !showForm">
|
||||
<div>
|
||||
<flux:button
|
||||
variant="primary"
|
||||
icon="plus"
|
||||
x-on:click="showForm = true"
|
||||
>
|
||||
<x-ts-button icon="plus" x-on:click="showForm = true">
|
||||
{{ __('Add passkey') }}
|
||||
</flux:button>
|
||||
</x-ts-button>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<template x-if="supported && showForm">
|
||||
<div class="space-y-4 rounded-lg border border-zinc-200 dark:border-zinc-700 bg-zinc-50 dark:bg-zinc-800/50 p-4">
|
||||
<flux:input
|
||||
label="{{ __('Passkey name') }}"
|
||||
x-model="name"
|
||||
placeholder="{{ __('e.g., MacBook Pro, iPhone') }}"
|
||||
x-on:keydown.enter.prevent="register()"
|
||||
x-ref="passkeyNameInput"
|
||||
x-init="$nextTick(() => $refs.passkeyNameInput?.focus())"
|
||||
/>
|
||||
<flux:text class="!mt-1">{{ __('Give this passkey a name to help you identify it later.') }}</flux:text>
|
||||
<div class="space-y-4 rounded-lg border border-gray-200 bg-gray-50 p-4 dark:border-dark-600 dark:bg-dark-700/50">
|
||||
<x-ts-input :label="__('Passkey name')"
|
||||
x-model="name"
|
||||
:placeholder="__('e.g., MacBook Pro, iPhone')"
|
||||
x-on:keydown.enter.prevent="register()"
|
||||
x-ref="passkeyNameInput"
|
||||
x-init="$nextTick(() => $refs.passkeyNameInput?.focus())"
|
||||
:hint="__('Give this passkey a name to help you identify it later.')" />
|
||||
|
||||
<p x-show="error" x-text="error" x-cloak class="text-sm text-red-600 dark:text-red-400"></p>
|
||||
|
||||
<div class="flex gap-2">
|
||||
<flux:button
|
||||
variant="primary"
|
||||
x-on:click="register()"
|
||||
x-bind:disabled="loading || !name.trim()"
|
||||
>
|
||||
<x-ts-button x-on:click="register()" x-bind:disabled="loading || !name.trim()">
|
||||
<span x-show="!loading">{{ __('Register passkey') }}</span>
|
||||
<span x-show="loading" x-cloak>{{ __('Registering...') }}</span>
|
||||
</flux:button>
|
||||
<flux:button
|
||||
variant="ghost"
|
||||
x-on:click="cancel()"
|
||||
>
|
||||
</x-ts-button>
|
||||
<x-ts-button flat color="secondary" x-on:click="cancel()">
|
||||
{{ __('Cancel') }}
|
||||
</flux:button>
|
||||
</x-ts-button>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@@ -47,26 +47,24 @@
|
||||
<template x-if="supported">
|
||||
<div>
|
||||
<div class="grid gap-2">
|
||||
<flux:button
|
||||
variant="outline"
|
||||
icon="finger-print"
|
||||
class="w-full"
|
||||
x-on:click="verify()"
|
||||
x-bind:disabled="loading"
|
||||
>
|
||||
<x-ts-button outline
|
||||
icon="finger-print"
|
||||
class="w-full"
|
||||
x-on:click="verify()"
|
||||
x-bind:disabled="loading">
|
||||
<span x-show="!loading">{{ $label }}</span>
|
||||
<span x-show="loading" x-cloak>{{ $loadingLabel }}</span>
|
||||
</flux:button>
|
||||
</x-ts-button>
|
||||
<p x-show="error" x-text="error" x-cloak
|
||||
class="text-sm text-center text-red-600 dark:text-red-400"></p>
|
||||
class="text-center text-sm text-red-600 dark:text-red-400"></p>
|
||||
</div>
|
||||
|
||||
<div class="relative my-6">
|
||||
<div class="absolute inset-0 flex items-center">
|
||||
<div class="w-full border-t border-zinc-200 dark:border-zinc-700"></div>
|
||||
<div class="w-full border-t border-gray-200 dark:border-dark-600"></div>
|
||||
</div>
|
||||
<div class="relative flex justify-center text-xs uppercase">
|
||||
<span class="px-2 text-zinc-500 dark:text-zinc-400 bg-white dark:bg-zinc-900">
|
||||
<span class="bg-white px-2 text-gray-500 dark:bg-dark-800 dark:text-dark-300">
|
||||
{{ $separator }}
|
||||
</span>
|
||||
</div>
|
||||
|
||||
@@ -1,17 +1,28 @@
|
||||
<div class="flex items-start max-md:flex-col">
|
||||
<div class="me-10 w-full pb-4 md:w-[220px]">
|
||||
<flux:navlist aria-label="{{ __('Settings') }}">
|
||||
<flux:navlist.item :href="route('profile.edit')" wire:navigate>{{ __('Profile') }}</flux:navlist.item>
|
||||
<flux:navlist.item :href="route('security.edit')" wire:navigate>{{ __('Security') }}</flux:navlist.item>
|
||||
<flux:navlist.item :href="route('appearance.edit')" wire:navigate>{{ __('Appearance') }}</flux:navlist.item>
|
||||
</flux:navlist>
|
||||
</div>
|
||||
<nav class="me-10 w-full pb-4 md:w-[220px]" aria-label="{{ __('Settings') }}">
|
||||
<ul class="flex flex-col gap-1">
|
||||
@foreach ([
|
||||
'profile.edit' => __('Profile'),
|
||||
'security.edit' => __('Security'),
|
||||
'appearance.edit' => __('Appearance'),
|
||||
] as $route => $label)
|
||||
<li>
|
||||
<a href="{{ route($route) }}"
|
||||
wire:navigate
|
||||
wire:current="bg-gray-100 text-gray-900 dark:bg-dark-700 dark:text-dark-50"
|
||||
class="block rounded-md px-3 py-2 text-sm font-medium text-gray-600 hover:bg-gray-100 dark:text-dark-300 dark:hover:bg-dark-700">
|
||||
{{ $label }}
|
||||
</a>
|
||||
</li>
|
||||
@endforeach
|
||||
</ul>
|
||||
</nav>
|
||||
|
||||
<flux:separator class="md:hidden" />
|
||||
<hr class="w-full border-gray-200 md:hidden dark:border-dark-600" />
|
||||
|
||||
<div class="flex-1 self-stretch max-md:pt-6">
|
||||
<flux:heading>{{ $heading ?? '' }}</flux:heading>
|
||||
<flux:subheading>{{ $subheading ?? '' }}</flux:subheading>
|
||||
<x-heading>{{ $heading ?? '' }}</x-heading>
|
||||
<x-subheading>{{ $subheading ?? '' }}</x-subheading>
|
||||
|
||||
<div class="mt-5 w-full max-w-lg">
|
||||
{{ $slot }}
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
<p {{ $attributes->class('text-sm text-gray-500 dark:text-dark-300') }}>{{ $slot }}</p>
|
||||
@@ -0,0 +1,33 @@
|
||||
<div>
|
||||
<x-ts-dropdown position="bottom-end">
|
||||
<x-slot:action>
|
||||
<button type="button"
|
||||
x-on:click="show = !show"
|
||||
class="flex shrink-0 cursor-pointer items-center gap-2 rounded-md p-1 text-sm hover:bg-gray-100 dark:hover:bg-dark-700"
|
||||
data-test="user-menu-button">
|
||||
{{-- Nur Initialen: ohne image/model lädt ts-avatar nichts von externen Diensten. --}}
|
||||
<x-ts-avatar :text="auth()->user()->initials()" sm />
|
||||
<span class="hidden whitespace-nowrap font-medium text-gray-700 sm:inline dark:text-dark-200">{{ auth()->user()->name }}</span>
|
||||
<x-ts-icon name="chevron-down" class="size-4 shrink-0 text-gray-500 dark:text-dark-400" />
|
||||
</button>
|
||||
</x-slot:action>
|
||||
|
||||
<x-slot:header>
|
||||
<div class="px-2 py-1 text-sm">
|
||||
<p class="truncate font-medium text-gray-900 dark:text-dark-50">{{ auth()->user()->name }}</p>
|
||||
<p class="truncate text-gray-500 dark:text-dark-300">{{ auth()->user()->email }}</p>
|
||||
</div>
|
||||
</x-slot:header>
|
||||
|
||||
<x-ts-dropdown.items :text="__('Settings')" icon="cog-6-tooth" :href="route('profile.edit')" navigate />
|
||||
<x-ts-dropdown.items :text="__('Log out')"
|
||||
icon="arrow-right-start-on-rectangle"
|
||||
separator
|
||||
x-on:click="document.getElementById('logout-form').submit()"
|
||||
data-test="logout-button" />
|
||||
</x-ts-dropdown>
|
||||
|
||||
<form id="logout-form" method="POST" action="{{ route('logout') }}" class="hidden">
|
||||
@csrf
|
||||
</form>
|
||||
</div>
|
||||
Reference in New Issue
Block a user