Forms
Color Picker code
Copy the implementation or inspect every file that belongs to this component unit.
resources/views/components/ui/color-picker.blade.php
@props([
'name' => null,
'value' => null,
'swatches' => null,
'size' => 'base',
'invalid' => false,
'disabled' => false,
])
@php
/*
| Color picker. A trigger showing the current swatch + hex opens a popover
| with preset swatches, a native colour wheel and a hex text field. The
| chosen value is submitted as a `#rrggbb` hex string via a hidden input
| bound to `name`. Override the presets with the `swatches` array.
|
| Usage:
| <x-ui.color-picker name="brand_color" />
| <x-ui.color-picker name="accent" :swatches="['#ef4444', '#f59e0b', '#10b981', '#3b82f6']" />
| <x-ui.color-picker x-model="characterColor" :value="$characterColor" />
*/
$value ??= \App\Support\ThemeTokens::brand('600');
$sizes = [
'sm' => 'text-sm py-1.5',
'base' => 'text-sm py-2.5',
'lg' => 'text-base py-3',
];
$ring = $invalid
? 'border-red-400 dark:border-red-500 focus:border-red-500 focus:ring-red-500'
: 'border-gray-300 dark:border-gray-700 focus:border-brand-500 focus:ring-brand-500';
$classes = implode(' ', [
'flex w-full items-center gap-2.5 rounded-lg bg-white dark:bg-gray-900 text-gray-900 dark:text-gray-100 shadow-sm ps-2.5 pe-3.5 text-left transition focus:ring-1 disabled:opacity-50 disabled:cursor-not-allowed',
$sizes[$size] ?? $sizes['base'],
$ring,
]);
$defaultSwatches = [
\App\Support\ThemeTokens::brand('600'),
\App\Support\ThemeTokens::brand('500'),
\App\Support\ThemeTokens::accent('600'),
\App\Support\ThemeTokens::accent('500'),
'#ef4444', '#f59e0b', '#16a34a', '#0ea5e9',
'#6366f1', '#8b5cf6', '#ec4899', \App\Support\ThemeTokens::gray('500'),
];
$palette = collect($swatches ?: $defaultSwatches)
->map(fn ($hex) => strtolower((string) $hex))
->values()
->all();
@endphp
<div
x-data="{
open: false,
color: @js(strtolower((string) $value)),
disabled: @js((bool) $disabled),
swatches: @js($palette),
setColor(hex) {
const v = hex.toLowerCase();
if (/^#[0-9a-f]{6}$/.test(v)) { this.color = v; }
},
onHexInput(event) {
let v = event.target.value.trim().toLowerCase();
if (v && ! v.startsWith('#')) { v = '#' + v; }
if (/^#[0-9a-f]{6}$/.test(v)) { this.color = v; }
},
isActive(hex) { return this.color === hex.toLowerCase(); },
}"
x-modelable="color"
@click.outside="open = false"
@keydown.escape.window="open = false"
{{ $attributes->class(['relative']) }}
>
@if ($name)
<input type="hidden" name="{{ $name }}" :value="color">
@endif
<button
type="button"
@disabled($disabled)
@if ($invalid) aria-invalid="true" @endif
@click="open = ! open"
class="{{ $classes }}"
>
<span class="h-6 w-6 shrink-0 rounded-md ring-1 ring-inset ring-black/10 dark:ring-white/10" :style="`background-color: ${color}`"></span>
<span class="flex-1 font-medium uppercase tabular-nums" x-text="color"></span>
<x-ui.icon name="caret-down" weight="bold" size="sm" class="text-gray-400 dark:text-gray-500" ::class="open ? 'rotate-180 transition-transform' : 'transition-transform'" />
</button>
<div
x-show="open" x-cloak
x-transition:enter="transition ease-out duration-150"
x-transition:enter-start="opacity-0 scale-95"
x-transition:enter-end="opacity-100 scale-100"
class="absolute z-50 mt-2 w-64 rounded-xl border border-gray-200 dark:border-gray-800 bg-white dark:bg-gray-900 p-3 shadow-lg ring-1 ring-black/5"
>
<div class="grid grid-cols-6 gap-2">
<template x-for="hex in swatches" :key="hex">
<button
type="button"
@click="setColor(hex)"
:style="`background-color: ${hex}`"
:class="isActive(hex) ? 'ring-2 ring-offset-2 ring-offset-white dark:ring-offset-gray-900 ring-brand-500' : 'ring-1 ring-inset ring-black/10 dark:ring-white/10'"
class="h-7 w-7 rounded-lg transition hover:scale-110 cursor-pointer"
:aria-label="hex"
>
<x-ui.icon name="check" weight="bold" size="sm" class="text-white mix-blend-difference" x-show="isActive(hex)" />
</button>
</template>
</div>
<div class="mt-3 flex items-center gap-2 border-t border-gray-100 dark:border-gray-800 pt-3">
<label class="relative h-9 w-9 shrink-0 cursor-pointer overflow-hidden rounded-lg ring-1 ring-inset ring-black/10 dark:ring-white/10" :style="`background-color: ${color}`">
<input type="color" :value="color" @input="setColor($event.target.value)" class="absolute inset-0 h-full w-full cursor-pointer opacity-0">
</label>
<div class="relative flex-1">
<span class="pointer-events-none absolute inset-y-0 start-0 flex items-center ps-2.5 text-gray-400 dark:text-gray-500">#</span>
<input
type="text"
maxlength="7"
:value="color"
@input="onHexInput($event)"
spellcheck="false"
class="block w-full rounded-lg border border-gray-300 dark:border-gray-700 bg-white dark:bg-gray-900 py-1.5 ps-6 pe-2.5 text-sm uppercase tabular-nums text-gray-900 dark:text-gray-100 shadow-sm transition focus:border-brand-500 focus:ring-1 focus:ring-brand-500"
>
</div>
</div>
</div>
</div>