Skip to main content

General

Theme Dock code

Copy the implementation or inspect every file that belongs to this component unit.

resources/views/components/ui/theme-dock.blade.php

@props([
    'title' => 'Appearance',
])

@php
    /*
    | Fixed bottom-right preferences menu with light/dark mode and theme colour
    | controls. Mount once per page that needs a floating appearance panel.
    |
    | Usage:
    |   <x-ui.theme-dock />
    */
    $themes = collect(config('themes.items', []));
    $panelId = 'theme-dock-panel-'.substr(md5($title), 0, 8);
@endphp

<div
    x-data="{ open: false }"
    @click.outside="open = false"
    @keydown.escape.window="open = false"
    {{ $attributes->class(['fixed bottom-6 right-6 z-50']) }}
>
    <div
        id="{{ $panelId }}"
        x-show="open"
        x-cloak
        x-transition:enter="transition ease-out duration-150"
        x-transition:enter-start="translate-y-2 opacity-0"
        x-transition:enter-end="translate-y-0 opacity-100"
        x-transition:leave="transition ease-in duration-100"
        x-transition:leave-start="translate-y-0 opacity-100"
        x-transition:leave-end="translate-y-2 opacity-0"
        role="dialog"
        aria-label="{{ $title }}"
        class="absolute bottom-14 right-0 mb-2 w-72 overflow-hidden rounded-2xl border border-gray-200 bg-white shadow-xl ring-1 ring-black/5 dark:border-gray-800 dark:bg-gray-900 dark:ring-white/10"
    >
        <div class="border-b border-gray-200 px-4 py-3 dark:border-gray-800">
            <p class="text-sm font-semibold text-gray-900 dark:text-white">{{ $title }}</p>
            <p class="mt-0.5 text-xs text-gray-500 dark:text-gray-400">{{ __('Preview components in any mode or palette.') }}</p>
        </div>

        <div class="space-y-5 p-4">
            <div>
                <p class="text-[11px] font-semibold uppercase tracking-[0.14em] text-gray-500 dark:text-gray-400">{{ __('Mode') }}</p>

                <div class="mt-2 grid grid-cols-2 gap-1 rounded-xl bg-gray-100 p-1 dark:bg-gray-800">
                    <button
                        type="button"
                        @click="$store.theme.setMode('light')"
                        :class="$store.theme.current === 'light'
                            ? 'bg-white text-gray-900 shadow-sm dark:bg-gray-950 dark:text-white'
                            : 'text-gray-500 hover:text-gray-700 dark:text-gray-400 dark:hover:text-gray-200'"
                        class="inline-flex items-center justify-center gap-1.5 rounded-lg px-3 py-2 text-xs font-semibold transition"
                    >
                        <x-ui.icon name="sun" weight="fill" size="sm" />
                        {{ __('Light') }}
                    </button>

                    <button
                        type="button"
                        @click="$store.theme.setMode('dark')"
                        :class="$store.theme.current === 'dark'
                            ? 'bg-white text-gray-900 shadow-sm dark:bg-gray-950 dark:text-white'
                            : 'text-gray-500 hover:text-gray-700 dark:text-gray-400 dark:hover:text-gray-200'"
                        class="inline-flex items-center justify-center gap-1.5 rounded-lg px-3 py-2 text-xs font-semibold transition"
                    >
                        <x-ui.icon name="moon" weight="fill" size="sm" />
                        {{ __('Dark') }}
                    </button>
                </div>
            </div>

            <div>
                <p class="text-[11px] font-semibold uppercase tracking-[0.14em] text-gray-500 dark:text-gray-400">{{ __('Theme colour') }}</p>

                <div class="mt-2 max-h-52 space-y-1 overflow-y-auto pe-0.5">
                    @foreach ($themes as $theme)
                        <button
                            type="button"
                            @click="$store.theme.setColorTheme(@js($theme['value']))"
                            :class="$store.theme.color === @js($theme['value'])
                                ? 'border-brand-300 bg-brand-50/80 dark:border-brand-800 dark:bg-brand-950/40'
                                : 'border-transparent hover:bg-gray-50 dark:hover:bg-gray-800/80'"
                            class="flex w-full items-center gap-3 rounded-xl border px-3 py-2 text-left transition"
                        >
                            <span
                                class="h-8 w-8 shrink-0 rounded-lg border border-black/10 shadow-sm dark:border-white/10"
                                style="background-color: {{ $theme['accent'] }}"
                                aria-hidden="true"
                            ></span>

                            <span class="min-w-0 flex-1">
                                <span class="block truncate text-sm font-medium text-gray-900 dark:text-white">{{ __($theme['name']) }}</span>
                                <span class="block truncate text-xs text-gray-500 dark:text-gray-400">{{ $theme['role'] ?? '' }}</span>
                            </span>

                            <span
                                x-show="$store.theme.color === @js($theme['value'])"
                                x-cloak
                            >
                                <x-ui.icon
                                    name="check-circle"
                                    weight="fill"
                                    size="sm"
                                    class="shrink-0 text-brand-600 dark:text-brand-400"
                                />
                            </span>
                        </button>
                    @endforeach
                </div>
            </div>
        </div>
    </div>

    <button
        type="button"
        @click="open = ! open"
        :aria-expanded="open.toString()"
        aria-controls="{{ $panelId }}"
        aria-haspopup="dialog"
        :aria-label="open ? '{{ __('Close appearance menu') }}' : '{{ __('Open appearance menu') }}'"
        :class="open ? 'bg-gray-900 text-white dark:bg-white dark:text-gray-900' : 'bg-white text-gray-700 dark:bg-gray-900 dark:text-gray-100'"
        class="inline-flex h-12 w-12 items-center justify-center rounded-full border border-gray-200 shadow-lg transition hover:scale-105 focus:outline-none focus:ring-2 focus:ring-brand-500 focus:ring-offset-2 dark:border-gray-700 dark:ring-offset-gray-950"
    >
        <x-ui.icon name="swatches" weight="duotone" size="lg" />
    </button>
</div>