Skip to main content

Forms

Password Input code

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

resources/views/components/ui/password-input.blade.php

@props([
    'name' => null,
    'value' => null,
    'placeholder' => '••••••••',
    'size' => 'base',
    'invalid' => false,
    'disabled' => false,
    'meter' => true,
    'toggleable' => true,
])

@php
    /*
    | Password input with a show/hide toggle and a live strength meter. The
    | meter scores length, case mix, digits and symbols (0–4 segments).
    |
    | Usage:
    |   <x-ui.password-input name="password" />
    |   <x-ui.password-input name="current_password" :meter="false" />
    */
    $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(' ', [
        'block w-full rounded-lg bg-white dark:bg-gray-900 text-gray-900 dark:text-gray-100 placeholder-gray-400 dark:placeholder-gray-500 shadow-sm transition focus:ring-1 disabled:opacity-50 disabled:cursor-not-allowed',
        $sizes[$size] ?? $sizes['base'],
        'ps-3.5 pe-10',
        $ring,
    ]);

    $inputId = $attributes->get('id') ?: $name;
@endphp

<div
    x-data="{
        show: false,
        value: @js((string) ($value ?? '')),
        get score() {
            const v = this.value;
            if (! v) { return 0; }
            let points = 0;
            if (v.length >= 8) { points++; }
            if (v.length >= 12) { points++; }
            if (/[a-z]/.test(v) && /[A-Z]/.test(v)) { points++; }
            if (/\d/.test(v)) { points++; }
            if (/[^A-Za-z0-9]/.test(v)) { points++; }
            if (points <= 1) { return 1; }
            if (points === 2) { return 2; }
            if (points <= 4) { return 3; }
            return 4;
        },
        get strengthLabel() {
            return ['', 'Weak', 'Fair', 'Good', 'Strong'][this.score];
        },
        get barClass() {
            return ['', 'bg-red-500', 'bg-amber-500', 'bg-accent-500', 'bg-green-500'][this.score];
        },
        get labelClass() {
            return ['text-gray-400', 'text-red-500', 'text-amber-500', 'text-accent-500', 'text-green-500'][this.score];
        },
    }"
    class="{{ $attributes->get('class') }}"
>
    <div class="relative">
        <input
            :type="show ? 'text' : 'password'"
            x-model="value"
            @disabled($disabled)
            @if ($inputId) id="{{ $inputId }}" @endif
            @if ($name) name="{{ $name }}" @endif
            placeholder="{{ $placeholder }}"
            @if ($invalid) aria-invalid="true" @endif
            autocomplete="new-password"
            {{ $attributes->except(['class', 'id', 'name', 'placeholder']) }}
            class="{{ $classes }}"
        >

        @if ($toggleable)
            <button
                type="button"
                @click="show = ! show"
                :aria-label="show ? 'Hide password' : 'Show password'"
                :aria-pressed="show.toString()"
                class="absolute inset-y-0 end-0 flex items-center pe-3 text-gray-400 transition hover:text-gray-600 dark:text-gray-500 dark:hover:text-gray-300 cursor-pointer"
            >
                <x-ui.icon name="eye" size="base" x-show="! show" />
                <x-ui.icon name="eye-slash" size="base" x-show="show" x-cloak />
            </button>
        @endif
    </div>

    @if ($meter)
        <div class="mt-2 flex items-center gap-2" aria-live="polite">
            <div class="flex flex-1 gap-1.5" aria-hidden="true">
                @foreach ([1, 2, 3, 4] as $segment)
                    <span
                        class="h-1 flex-1 rounded-full transition-colors"
                        :class="score >= {{ $segment }} ? barClass : 'bg-gray-200 dark:bg-gray-800'"
                    ></span>
                @endforeach
            </div>
            <span class="text-xs font-medium text-gray-400" :class="labelClass" x-text="strengthLabel"></span>
        </div>
    @endif
</div>