Skip to main content

Forms

Theme Color Picker code

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

resources/views/components/ui/theme-color-picker.blade.php

@props([
    'align' => 'right',
    'fullWidth' => false,
    'placement' => 'bottom',
])

@php
    /*
    | Compact theme colour picker bound to the Alpine `theme` store. Shows the
    | current palette swatch in a pill trigger and opens a scrollable panel of
    | all configured themes.
    |
    | Usage:
    |   <x-ui.theme-color-picker />
    |   <x-ui.theme-color-picker class="block w-full px-3 sm:hidden" align="left" :full-width="true" />
    */
    $themes = collect(config('themes.items', []));

    $panelId = 'theme-color-picker-'.substr(md5($align.($fullWidth ? 'full' : 'compact')), 0, 8);

    $alignClass = $align === 'left'
        ? ($placement === 'top' ? 'origin-bottom-left start-0' : 'origin-top-left start-0')
        : ($placement === 'top' ? 'origin-bottom-right end-0' : 'origin-top-right end-0');

    $placementClass = $placement === 'top' ? 'bottom-full mb-1.5' : 'mt-1.5';

    $panelZIndexClass = $placement === 'top' ? 'z-[100]' : 'z-50';

    $triggerClass = $fullWidth
        ? 'flex w-full items-center gap-2.5 rounded-lg border border-gray-300 bg-white px-3 py-2 text-left shadow-sm transition hover:border-gray-400 focus:outline-none focus:ring-2 focus:ring-brand-500 focus:ring-offset-2 dark:border-gray-700 dark:bg-gray-900 dark:hover:border-gray-600 dark:focus:ring-offset-gray-950'
        : 'inline-flex h-9 max-w-[9.5rem] items-center gap-2 rounded-lg border border-gray-300/80 bg-white/90 px-2.5 shadow-sm backdrop-blur-sm transition hover:border-gray-400 hover:bg-white focus:outline-none focus:ring-2 focus:ring-brand-500 focus:ring-offset-2 dark:border-gray-700/80 dark:bg-gray-900/90 dark:hover:border-gray-600 dark:hover:bg-gray-900 dark:focus:ring-offset-gray-950';
@endphp

@if ($themes->count() > 1)
    <div
        x-data="{ open: false }"
        @click.outside="open = false"
        @keydown.escape.window="open = false"
        {{ $attributes->class(['relative']) }}
    >
        <button
            type="button"
            @click="open = ! open"
            :aria-expanded="open.toString()"
            aria-controls="{{ $panelId }}"
            aria-haspopup="listbox"
            :title="'{{ __('Theme colour') }}: ' + $store.theme.currentColorLabel"
            aria-label="{{ __('Theme colour') }}"
            @class([$triggerClass])
        >
            @foreach ($themes as $theme)
                <span
                    x-show="$store.theme.color === @js($theme['value'])"
                    x-cloak
                    class="flex min-w-0 flex-1 items-center gap-2"
                >
                    <span
                        class="h-4 w-4 shrink-0 rounded-md border border-black/10 shadow-sm ring-1 ring-black/5 dark:border-white/10 dark:ring-white/10"
                        style="background-color: {{ $theme['accent'] }}"
                        aria-hidden="true"
                    ></span>

                    <span @class([
                        'truncate font-semibold text-gray-700 dark:text-gray-200',
                        'text-base' => $fullWidth,
                        'text-xs' => ! $fullWidth,
                    ])>{{ __($theme['name']) }}</span>
                </span>
            @endforeach

            <x-ui.icon
                name="caret-down"
                weight="bold"
                :size="$fullWidth ? 'sm' : 'xs'"
                class="shrink-0 text-gray-400 transition-transform dark:text-gray-500"
                x-bind:class="open && 'rotate-180'"
            />
        </button>

        <div
            id="{{ $panelId }}"
            x-show="open"
            x-cloak
            x-transition:enter="transition ease-out duration-150"
            x-transition:enter-start="opacity-0 scale-95 -translate-y-0.5"
            x-transition:enter-end="opacity-100 scale-100 translate-y-0"
            x-transition:leave="transition ease-in duration-100"
            x-transition:leave-start="opacity-100 scale-100 translate-y-0"
            x-transition:leave-end="opacity-0 scale-95 -translate-y-0.5"
            role="listbox"
            aria-label="{{ __('Theme colour') }}"
            @class([
                'absolute overflow-hidden rounded-xl border border-gray-200/80 bg-white/95 shadow-lg ring-1 ring-black/5 backdrop-blur-lg dark:border-gray-800/80 dark:bg-gray-950/95 dark:ring-white/10',
                $panelZIndexClass,
                $placementClass,
                $alignClass,
                'w-full' => $fullWidth,
                'w-56' => ! $fullWidth,
            ])
        >
            <div class="border-b border-gray-200/80 px-3 py-2 dark:border-gray-800/80">
                <p class="text-[11px] font-semibold uppercase tracking-[0.14em] text-gray-500 dark:text-gray-400">{{ __('Theme colour') }}</p>
            </div>

            <div class="max-h-56 space-y-0.5 overflow-y-auto p-1.5">
                @foreach ($themes as $theme)
                    <button
                        type="button"
                        role="option"
                        :aria-selected="($store.theme.color === @js($theme['value'])).toString()"
                        @click="$store.theme.setColorTheme(@js($theme['value'])); open = false"
                        :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 cursor-pointer items-center gap-2.5 rounded-lg border px-2.5 py-2 text-left transition"
                    >
                        <span
                            class="h-6 w-6 shrink-0 rounded-md 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 truncate text-sm font-medium text-gray-900 dark:text-white">{{ __($theme['name']) }}</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>
@endif