Private
Public Access
1
0

Your descriptive commit message

This commit is contained in:
2026-11-02 01:29:01 +01:00
parent 68043f0416
commit d4bf5b1b0a
4960 changed files with 102120 additions and 46287 deletions

View File

@@ -1,31 +1,31 @@
@props([
'name',
'id' => null,
'label' => null,
'divClass' => '',
])
<div class="flex items-center {{ $divClass ?? '' }}">
<div class="relative">
<input type="checkbox" name="{{ $name }}" id="{{ $id ?? $name }}"
{{ $attributes->except(['label', 'name', 'id', 'class', 'divClass', 'required']) }}
class="peer form-checkbox size-5 text-primary rounded border-2 border-neutral bg-background-secondary/50
focus:ring-2 focus:ring-primary/30 focus:ring-offset-0 focus:outline-none
checked:bg-primary checked:border-primary
hover:border-primary/50 transition-all duration-200 cursor-pointer
appearance-none" />
<svg class="absolute top-1/2 left-1/2 -translate-x-1/2 -translate-y-1/2 size-3 text-white pointer-events-none opacity-0 scale-50 peer-checked:opacity-100 peer-checked:scale-100 transition-all duration-200" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="3">
<path stroke-linecap="round" stroke-linejoin="round" d="M5 13l4 4L19 7" />
</svg>
</div>
<label class="ml-2 text-sm text-color-base cursor-pointer select-none" for="{{ $id ?? $name }}">
@if(isset($label))
{{ $label }}
@else
{{ $slot }}
@endif
</label>
@error($name)
<p class="text-red-500 text-xs ml-2">{{ $message }}</p>
@enderror
</div>
@props([
'name',
'id' => null,
'label' => null,
'divClass' => '',
])
<div class="flex items-center {{ $divClass ?? '' }}">
<div class="relative">
<input type="checkbox" name="{{ $name }}" id="{{ $id ?? $name }}"
{{ $attributes->except(['label', 'name', 'id', 'class', 'divClass', 'required']) }}
class="peer form-checkbox size-5 text-primary rounded border-2 border-neutral/70 bg-background-secondary/50
focus:ring-2 focus:ring-primary/40 focus:ring-offset-0 focus:outline-none
checked:bg-primary checked:border-primary checked:shadow-[0_0_12px_-2px_hsl(var(--color-primary)/0.55)]
hover:border-primary/60 transition-all duration-200 cursor-pointer
appearance-none" />
<svg class="absolute top-1/2 left-1/2 -translate-x-1/2 -translate-y-1/2 size-3 text-white pointer-events-none opacity-0 scale-50 peer-checked:opacity-100 peer-checked:scale-100 transition-all duration-200" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="3">
<path stroke-linecap="round" stroke-linejoin="round" d="M5 13l4 4L19 7" />
</svg>
</div>
<label class="ml-2 text-sm text-color-base cursor-pointer select-none" for="{{ $id ?? $name }}">
@if(isset($label))
{{ $label }}
@else
{{ $slot }}
@endif
</label>
@error($name)
<p class="text-error text-xs ml-2">{{ $message }}</p>
@enderror
</div>

View File

@@ -1,22 +1,22 @@
@props(['name', 'label' => null, 'required' => false, 'divClass' => null, 'class' => null,'placeholder' => null, 'id' => null, 'type' => null, 'hideRequiredIndicator' => false, 'dirty' => false])
<fieldset class="flex flex-col relative w-full {{ $divClass ?? '' }}">
@if ($label)
<legend class="mb-1">
<label for="{{ $name }}"
class="text-sm font-bold text-base">
{{ $label }}
@if ($required && !$hideRequiredIndicator)
<span class="text-red-500">*</span>
@endif
</label>
</legend>
@endif
<input type="{{ $type ?? 'text' }}" id="{{ $id ?? $name }}" name="{{ $name }}"
class="block w-full text-sm text-base bg-background-secondary/50 border border-neutral rounded-md shadow-sm focus:outline-none focus:ring-2 focus:ring-primary/30 focus:border-primary transition-all duration-300 ease-in-out disabled:bg-background-secondary/50 disabled:cursor-not-allowed {{ $class ?? '' }} @if ($type !== 'color') px-2.5 py-2.5 @endif"
placeholder="{{ $placeholder ?? ($label ?? '') }}"
@if ($dirty && isset($attributes['wire:model'])) wire:dirty.class="!border-yellow-600" @endif
{{ $attributes->except(['placeholder', 'label', 'id', 'name', 'type', 'class', 'divClass', 'required', 'hideRequiredIndicator', 'dirty']) }} @required($required) />
@error($name)
<p class="text-red-500 text-xs">{{ $message }}</p>
@enderror
</fieldset>
@props(['name', 'label' => null, 'required' => false, 'divClass' => null, 'class' => null,'placeholder' => null, 'id' => null, 'type' => null, 'hideRequiredIndicator' => false, 'dirty' => false])
<fieldset class="flex flex-col relative w-full {{ $divClass ?? '' }}">
@if ($label)
<legend class="mb-1">
<label for="{{ $name }}"
class="text-sm font-bold text-base">
{{ $label }}
@if ($required && !$hideRequiredIndicator)
<span class="text-error">*</span>
@endif
</label>
</legend>
@endif
<input type="{{ $type ?? 'text' }}" id="{{ $id ?? $name }}" name="{{ $name }}"
class="block w-full text-sm text-base bg-background-secondary/40 border border-neutral/60 rounded-md shadow-sm focus:outline-none focus:ring-2 focus:ring-primary/40 focus:border-primary hover:border-neutral/90 transition-all duration-300 ease-in-out disabled:bg-background-secondary/30 disabled:cursor-not-allowed placeholder:text-base/40 {{ $class ?? '' }} @if ($type !== 'color') px-3 py-2.5 @endif"
placeholder="{{ $placeholder ?? ($label ?? '') }}"
@if ($dirty && isset($attributes['wire:model'])) wire:dirty.class="!border-yellow-600" @endif
{{ $attributes->except(['placeholder', 'label', 'id', 'name', 'type', 'class', 'divClass', 'required', 'hideRequiredIndicator', 'dirty']) }} @required($required) />
@error($name)
<p class="text-red-500 text-xs">{{ $message }}</p>
@enderror
</fieldset>

View File

@@ -1,274 +1,274 @@
@props([
'name' => 'password',
'label' => null,
'required' => false,
'divClass' => null,
'class' => null,
'placeholder' => null,
'id' => null,
'hideRequiredIndicator' => false,
'dirty' => false,
'minStrengthLevel' => 3 // 1=red, 2=yellow, 3=blue, 4=green
])
<fieldset class="flex flex-col relative w-full mt-4 {{ $divClass ?? '' }}">
@if ($label)
<legend class="mb-1">
<label for="{{ $name }}"
class="text-sm font-bold text-base">
{{ $label }}
@if ($required && !$hideRequiredIndicator)
<span class="text-red-500">*</span>
@endif
</label>
</legend>
@endif
<div class="relative">
<input
type="password"
id="{{ $id ?? $name }}"
name="{{ $name }}"
placeholder="{{ $placeholder ?? ($label ?? '') }}"
class="block w-full text-sm text-base bg-background-secondary/50 border border-neutral rounded-md shadow-sm focus:outline-none transition-all duration-300 ease-in-out disabled:bg-background-secondary/50 disabled:cursor-not-allowed px-2.5 py-2.5 pr-10 {{ $class ?? '' }}"
@if ($dirty && isset($attributes['wire:model'])) wire:dirty.class="!border-yellow-600" @endif
{{ $attributes->except(['placeholder', 'label', 'id', 'name', 'class', 'divClass', 'required', 'hideRequiredIndicator', 'dirty', 'minStrengthLevel']) }}
@required($required)
data-hs-strong-password='{
"target": "#{{ $id ?? $name }}-strength",
"hints": "#{{ $id ?? $name }}-hints",
"minStrengthLevel": {{ $minStrengthLevel }},
"stripClasses": "hs-strong-password:opacity-100 hs-strong-password-accepted:bg-teal-500 h-2 flex-auto rounded-full bg-blue-500 opacity-50 mx-1"
}'
>
</div>
<div id="{{ $id ?? $name }}-strength" class="flex mt-2 -mx-1" aria-live="polite"></div>
<div id="{{ $id ?? $name }}-hints" class="mt-3">
<div class="text-xs text-primary-100 mb-2 font-medium">Password requirements:</div>
<div class="flex flex-wrap gap-2 text-xs">
<div class="flex items-center gap-x-2" data-hs-strong-password-hints-rule-text="min-length">
<span class="hidden" data-check>
<svg class="w-3 h-3 text-green-500" fill="currentColor" viewBox="0 0 20 20">
<path fill-rule="evenodd" d="M16.707 5.293a1 1 0 010 1.414l-8 8a1 1 0 01-1.414 0l-4-4a1 1 0 011.414-1.414L8 12.586l7.293-7.293a1 1 0 011.414 0z" clip-rule="evenodd"></path>
</svg>
</span>
<span data-uncheck>
<svg class="w-3 h-3 text-neutral-400" fill="currentColor" viewBox="0 0 20 20">
<path fill-rule="evenodd" d="M4.293 4.293a1 1 0 011.414 0L10 8.586l4.293-4.293a1 1 0 111.414 1.414L11.414 10l4.293 4.293a1 1 0 01-1.414 1.414L10 11.414l-4.293 4.293a1 1 0 01-1.414-1.414L8.586 10 4.293 5.707a1 1 0 010-1.414z" clip-rule="evenodd"></path>
</svg>
</span>
<span class="text-base">Minimum 8 characters</span>
</div>
<div class="flex items-center gap-x-2" data-hs-strong-password-hints-rule-text="lowercase">
<span class="hidden" data-check>
<svg class="w-3 h-3 text-green-500" fill="currentColor" viewBox="0 0 20 20">
<path fill-rule="evenodd" d="M16.707 5.293a1 1 0 010 1.414l-8 8a1 1 0 01-1.414 0l-4-4a1 1 0 011.414-1.414L8 12.586l7.293-7.293a1 1 0 011.414 0z" clip-rule="evenodd"></path>
</svg>
</span>
<span data-uncheck>
<svg class="w-3 h-3 text-neutral-400" fill="currentColor" viewBox="0 0 20 20">
<path fill-rule="evenodd" d="M4.293 4.293a1 1 0 011.414 0L10 8.586l4.293-4.293a1 1 0 111.414 1.414L11.414 10l4.293 4.293a1 1 0 01-1.414 1.414L10 11.414l-4.293 4.293a1 1 0 01-1.414-1.414L8.586 10 4.293 5.707a1 1 0 010-1.414z" clip-rule="evenodd"></path>
</svg>
</span>
<span class="text-base">One lowercase letter</span>
</div>
<div class="flex items-center gap-x-2" data-hs-strong-password-hints-rule-text="uppercase">
<span class="hidden" data-check>
<svg class="w-3 h-3 text-green-500" fill="currentColor" viewBox="0 0 20 20">
<path fill-rule="evenodd" d="M16.707 5.293a1 1 0 010 1.414l-8 8a1 1 0 01-1.414 0l-4-4a1 1 0 011.414-1.414L8 12.586l7.293-7.293a1 1 0 011.414 0z" clip-rule="evenodd"></path>
</svg>
</span>
<span data-uncheck>
<svg class="w-3 h-3 text-neutral-400" fill="currentColor" viewBox="0 0 20 20">
<path fill-rule="evenodd" d="M4.293 4.293a1 1 0 011.414 0L10 8.586l4.293-4.293a1 1 0 111.414 1.414L11.414 10l4.293 4.293a1 1 0 01-1.414 1.414L10 11.414l-4.293 4.293a1 1 0 01-1.414-1.414L8.586 10 4.293 5.707a1 1 0 010-1.414z" clip-rule="evenodd"></path>
</svg>
</span>
<span class="text-base">One uppercase letter</span>
</div>
<div class="flex items-center gap-x-2" data-hs-strong-password-hints-rule-text="numbers">
<span class="hidden" data-check>
<svg class="w-3 h-3 text-green-500" fill="currentColor" viewBox="0 0 20 20">
<path fill-rule="evenodd" d="M16.707 5.293a1 1 0 010 1.414l-8 8a1 1 0 01-1.414 0l-4-4a1 1 0 011.414-1.414L8 12.586l7.293-7.293a1 1 0 011.414 0z" clip-rule="evenodd"></path>
</svg>
</span>
<span data-uncheck>
<svg class="w-3 h-3 text-neutral-400" fill="currentColor" viewBox="0 0 20 20">
<path fill-rule="evenodd" d="M4.293 4.293a1 1 0 011.414 0L10 8.586l4.293-4.293a1 1 0 111.414 1.414L11.414 10l4.293 4.293a1 1 0 01-1.414 1.414L10 11.414l-4.293 4.293a1 1 0 01-1.414-1.414L8.586 10 4.293 5.707a1 1 0 010-1.414z" clip-rule="evenodd"></path>
</svg>
</span>
<span class="text-base">One number</span>
</div>
<div class="flex items-center gap-x-2" data-hs-strong-password-hints-rule-text="special-characters">
<span class="hidden" data-check>
<svg class="w-3 h-3 text-green-500" fill="currentColor" viewBox="0 0 20 20">
<path fill-rule="evenodd" d="M16.707 5.293a1 1 0 010 1.414l-8 8a1 1 0 01-1.414 0l-4-4a1 1 0 011.414-1.414L8 12.586l7.293-7.293a1 1 0 011.414 0z" clip-rule="evenodd"></path>
</svg>
</span>
<span data-uncheck>
<svg class="w-3 h-3 text-neutral-400" fill="currentColor" viewBox="0 0 20 20">
<path fill-rule="evenodd" d="M4.293 4.293a1 1 0 011.414 0L10 8.586l4.293-4.293a1 1 0 111.414 1.414L11.414 10l4.293 4.293a1 1 0 01-1.414 1.414L10 11.414l-4.293 4.293a1 1 0 01-1.414-1.414L8.586 10 4.293 5.707a1 1 0 010-1.414z" clip-rule="evenodd"></path>
</svg>
</span>
<span class="text-base">One special character (!@#$%^&*)</span>
</div>
</div>
</div>
@error($name)
<p class="text-red-500 text-xs">{{ $message }}</p>
@enderror
</fieldset>
<script data-navigate-track>
// Enhanced password strength validation
(function() {
// Avoid duplicate initialization
const initPasswordStrength = function() {
const passwordInputs = document.querySelectorAll('[data-hs-strong-password]:not([data-password-strength-initialized])');
passwordInputs.forEach(function(input) {
// Mark as initialized to prevent duplicate event listeners
input.setAttribute('data-password-strength-initialized', 'true');
const config = JSON.parse(input.getAttribute('data-hs-strong-password'));
const strengthBar = document.querySelector(config.target);
const hints = document.querySelector(config.hints);
const warningElement = document.querySelector(config.target.replace('-strength', '-warning'));
const minStrengthLevel = config.minStrengthLevel || 3;
// Initial setup - create empty bars
if (strengthBar) {
strengthBar.innerHTML = '';
for (let i = 0; i < 5; i++) {
const bar = document.createElement('div');
bar.className = 'h-2 flex-auto rounded-full mx-1 bg-neutral-300 transition-all duration-300';
strengthBar.appendChild(bar);
}
}
// Input event listener
input.addEventListener('input', function() {
const strengthLevel = checkPasswordStrength(input.value, strengthBar, hints, warningElement, minStrengthLevel);
// Set custom validity based on strength level
if (input.value && strengthLevel < minStrengthLevel) {
input.setCustomValidity('Password terlalu lemah! Minimal harus mencapai level orange.');
} else {
input.setCustomValidity('');
}
});
// Prevent form submission if password is too weak
const form = input.closest('form');
if (form && !form.hasAttribute('data-password-form-initialized')) {
form.setAttribute('data-password-form-initialized', 'true');
form.addEventListener('submit', function(e) {
const strengthLevel = getCurrentPasswordStrength(input.value);
if (input.value && strengthLevel < minStrengthLevel) {
e.preventDefault();
input.focus();
// Show warning
if (warningElement) {
warningElement.classList.remove('hidden');
}
// Show validation message
input.reportValidity();
}
});
}
});
};
// Run on DOMContentLoaded and Livewire navigation
if (document.readyState === 'loading') {
document.addEventListener('DOMContentLoaded', initPasswordStrength);
} else {
initPasswordStrength();
}
document.addEventListener('livewire:navigated', initPasswordStrength);
})();
function getCurrentPasswordStrength(password) {
const checks = {
'min-length': password.length >= 8,
'lowercase': /[a-z]/.test(password),
'uppercase': /[A-Z]/.test(password),
'numbers': /\d/.test(password),
'special-characters': /[!@#$%^&*()_+\-=\[\]{};':"\\|,.<>\/?]/.test(password)
};
return Object.values(checks).filter(Boolean).length;
}
function checkPasswordStrength(password, strengthBar, hintsContainer, warningElement, minStrengthLevel) {
const checks = {
'min-length': password.length >= 8,
'lowercase': /[a-z]/.test(password),
'uppercase': /[A-Z]/.test(password),
'numbers': /\d/.test(password),
'special-characters': /[!@#$%^&*()_+\-=\[\]{};':"\\|,.<>\/?]/.test(password)
};
const passed = Object.values(checks).filter(Boolean).length;
// Update strength bars
if (strengthBar) {
const bars = strengthBar.querySelectorAll('div');
bars.forEach((bar, index) => {
// Reset classes
bar.className = 'h-2 flex-auto rounded-full mx-1 transition-all duration-300';
if (index < passed) {
if (passed <= 2) {
bar.classList.add('bg-red-500');
} else if (passed === 3) {
bar.classList.add('bg-yellow-500'); // Orange/Yellow level
} else if (passed === 4) {
bar.classList.add('bg-blue-500');
} else if (passed === 5) {
bar.classList.add('bg-green-500');
}
} else {
bar.classList.add('bg-neutral-300');
}
});
}
// Show/hide warning based on strength
if (warningElement) {
if (password && passed < minStrengthLevel) {
warningElement.classList.remove('hidden');
} else {
warningElement.classList.add('hidden');
}
}
// Update requirement icons
if (hintsContainer) {
Object.keys(checks).forEach(function(rule) {
const ruleElement = hintsContainer.querySelector(`[data-hs-strong-password-hints-rule-text="${rule}"]`);
if (ruleElement) {
const checkIcon = ruleElement.querySelector('[data-check]');
const uncheckIcon = ruleElement.querySelector('[data-uncheck]');
if (checks[rule]) {
checkIcon.classList.remove('hidden');
uncheckIcon.classList.add('hidden');
} else {
checkIcon.classList.add('hidden');
uncheckIcon.classList.remove('hidden');
}
}
});
}
return passed;
}
@props([
'name' => 'password',
'label' => null,
'required' => false,
'divClass' => null,
'class' => null,
'placeholder' => null,
'id' => null,
'hideRequiredIndicator' => false,
'dirty' => false,
'minStrengthLevel' => 3 // 1=red, 2=yellow, 3=blue, 4=green
])
<fieldset class="flex flex-col relative w-full mt-4 {{ $divClass ?? '' }}">
@if ($label)
<legend class="mb-1">
<label for="{{ $name }}"
class="text-sm font-bold text-base">
{{ $label }}
@if ($required && !$hideRequiredIndicator)
<span class="text-red-500">*</span>
@endif
</label>
</legend>
@endif
<div class="relative">
<input
type="password"
id="{{ $id ?? $name }}"
name="{{ $name }}"
placeholder="{{ $placeholder ?? ($label ?? '') }}"
class="block w-full text-sm text-base bg-background-secondary/40 border border-neutral/60 rounded-md shadow-sm focus:outline-none focus:ring-2 focus:ring-primary/40 focus:border-primary hover:border-neutral transition-all duration-300 ease-in-out disabled:bg-background-secondary/30 disabled:cursor-not-allowed px-2.5 py-2.5 pr-10 {{ $class ?? '' }}"
@if ($dirty && isset($attributes['wire:model'])) wire:dirty.class="!border-yellow-600" @endif
{{ $attributes->except(['placeholder', 'label', 'id', 'name', 'class', 'divClass', 'required', 'hideRequiredIndicator', 'dirty', 'minStrengthLevel']) }}
@required($required)
data-hs-strong-password='{
"target": "#{{ $id ?? $name }}-strength",
"hints": "#{{ $id ?? $name }}-hints",
"minStrengthLevel": {{ $minStrengthLevel }},
"stripClasses": "hs-strong-password:opacity-100 hs-strong-password-accepted:bg-teal-500 h-2 flex-auto rounded-full bg-blue-500 opacity-50 mx-1"
}'
>
</div>
<div id="{{ $id ?? $name }}-strength" class="flex mt-2 -mx-1" aria-live="polite"></div>
<div id="{{ $id ?? $name }}-hints" class="mt-3">
<div class="text-xs text-primary-100 mb-2 font-medium">Password requirements:</div>
<div class="flex flex-wrap gap-2 text-xs">
<div class="flex items-center gap-x-2" data-hs-strong-password-hints-rule-text="min-length">
<span class="hidden" data-check>
<svg class="w-3 h-3 text-green-500" fill="currentColor" viewBox="0 0 20 20">
<path fill-rule="evenodd" d="M16.707 5.293a1 1 0 010 1.414l-8 8a1 1 0 01-1.414 0l-4-4a1 1 0 011.414-1.414L8 12.586l7.293-7.293a1 1 0 011.414 0z" clip-rule="evenodd"></path>
</svg>
</span>
<span data-uncheck>
<svg class="w-3 h-3 text-neutral-400" fill="currentColor" viewBox="0 0 20 20">
<path fill-rule="evenodd" d="M4.293 4.293a1 1 0 011.414 0L10 8.586l4.293-4.293a1 1 0 111.414 1.414L11.414 10l4.293 4.293a1 1 0 01-1.414 1.414L10 11.414l-4.293 4.293a1 1 0 01-1.414-1.414L8.586 10 4.293 5.707a1 1 0 010-1.414z" clip-rule="evenodd"></path>
</svg>
</span>
<span class="text-base">Minimum 8 characters</span>
</div>
<div class="flex items-center gap-x-2" data-hs-strong-password-hints-rule-text="lowercase">
<span class="hidden" data-check>
<svg class="w-3 h-3 text-green-500" fill="currentColor" viewBox="0 0 20 20">
<path fill-rule="evenodd" d="M16.707 5.293a1 1 0 010 1.414l-8 8a1 1 0 01-1.414 0l-4-4a1 1 0 011.414-1.414L8 12.586l7.293-7.293a1 1 0 011.414 0z" clip-rule="evenodd"></path>
</svg>
</span>
<span data-uncheck>
<svg class="w-3 h-3 text-neutral-400" fill="currentColor" viewBox="0 0 20 20">
<path fill-rule="evenodd" d="M4.293 4.293a1 1 0 011.414 0L10 8.586l4.293-4.293a1 1 0 111.414 1.414L11.414 10l4.293 4.293a1 1 0 01-1.414 1.414L10 11.414l-4.293 4.293a1 1 0 01-1.414-1.414L8.586 10 4.293 5.707a1 1 0 010-1.414z" clip-rule="evenodd"></path>
</svg>
</span>
<span class="text-base">One lowercase letter</span>
</div>
<div class="flex items-center gap-x-2" data-hs-strong-password-hints-rule-text="uppercase">
<span class="hidden" data-check>
<svg class="w-3 h-3 text-green-500" fill="currentColor" viewBox="0 0 20 20">
<path fill-rule="evenodd" d="M16.707 5.293a1 1 0 010 1.414l-8 8a1 1 0 01-1.414 0l-4-4a1 1 0 011.414-1.414L8 12.586l7.293-7.293a1 1 0 011.414 0z" clip-rule="evenodd"></path>
</svg>
</span>
<span data-uncheck>
<svg class="w-3 h-3 text-neutral-400" fill="currentColor" viewBox="0 0 20 20">
<path fill-rule="evenodd" d="M4.293 4.293a1 1 0 011.414 0L10 8.586l4.293-4.293a1 1 0 111.414 1.414L11.414 10l4.293 4.293a1 1 0 01-1.414 1.414L10 11.414l-4.293 4.293a1 1 0 01-1.414-1.414L8.586 10 4.293 5.707a1 1 0 010-1.414z" clip-rule="evenodd"></path>
</svg>
</span>
<span class="text-base">One uppercase letter</span>
</div>
<div class="flex items-center gap-x-2" data-hs-strong-password-hints-rule-text="numbers">
<span class="hidden" data-check>
<svg class="w-3 h-3 text-green-500" fill="currentColor" viewBox="0 0 20 20">
<path fill-rule="evenodd" d="M16.707 5.293a1 1 0 010 1.414l-8 8a1 1 0 01-1.414 0l-4-4a1 1 0 011.414-1.414L8 12.586l7.293-7.293a1 1 0 011.414 0z" clip-rule="evenodd"></path>
</svg>
</span>
<span data-uncheck>
<svg class="w-3 h-3 text-neutral-400" fill="currentColor" viewBox="0 0 20 20">
<path fill-rule="evenodd" d="M4.293 4.293a1 1 0 011.414 0L10 8.586l4.293-4.293a1 1 0 111.414 1.414L11.414 10l4.293 4.293a1 1 0 01-1.414 1.414L10 11.414l-4.293 4.293a1 1 0 01-1.414-1.414L8.586 10 4.293 5.707a1 1 0 010-1.414z" clip-rule="evenodd"></path>
</svg>
</span>
<span class="text-base">One number</span>
</div>
<div class="flex items-center gap-x-2" data-hs-strong-password-hints-rule-text="special-characters">
<span class="hidden" data-check>
<svg class="w-3 h-3 text-green-500" fill="currentColor" viewBox="0 0 20 20">
<path fill-rule="evenodd" d="M16.707 5.293a1 1 0 010 1.414l-8 8a1 1 0 01-1.414 0l-4-4a1 1 0 011.414-1.414L8 12.586l7.293-7.293a1 1 0 011.414 0z" clip-rule="evenodd"></path>
</svg>
</span>
<span data-uncheck>
<svg class="w-3 h-3 text-neutral-400" fill="currentColor" viewBox="0 0 20 20">
<path fill-rule="evenodd" d="M4.293 4.293a1 1 0 011.414 0L10 8.586l4.293-4.293a1 1 0 111.414 1.414L11.414 10l4.293 4.293a1 1 0 01-1.414 1.414L10 11.414l-4.293 4.293a1 1 0 01-1.414-1.414L8.586 10 4.293 5.707a1 1 0 010-1.414z" clip-rule="evenodd"></path>
</svg>
</span>
<span class="text-base">One special character (!@#$%^&*)</span>
</div>
</div>
</div>
@error($name)
<p class="text-red-500 text-xs">{{ $message }}</p>
@enderror
</fieldset>
<script data-navigate-track>
// Enhanced password strength validation
(function() {
// Avoid duplicate initialization
const initPasswordStrength = function() {
const passwordInputs = document.querySelectorAll('[data-hs-strong-password]:not([data-password-strength-initialized])');
passwordInputs.forEach(function(input) {
// Mark as initialized to prevent duplicate event listeners
input.setAttribute('data-password-strength-initialized', 'true');
const config = JSON.parse(input.getAttribute('data-hs-strong-password'));
const strengthBar = document.querySelector(config.target);
const hints = document.querySelector(config.hints);
const warningElement = document.querySelector(config.target.replace('-strength', '-warning'));
const minStrengthLevel = config.minStrengthLevel || 3;
// Initial setup - create empty bars
if (strengthBar) {
strengthBar.innerHTML = '';
for (let i = 0; i < 5; i++) {
const bar = document.createElement('div');
bar.className = 'h-2 flex-auto rounded-full mx-1 bg-neutral-300 transition-all duration-300';
strengthBar.appendChild(bar);
}
}
// Input event listener
input.addEventListener('input', function() {
const strengthLevel = checkPasswordStrength(input.value, strengthBar, hints, warningElement, minStrengthLevel);
// Set custom validity based on strength level
if (input.value && strengthLevel < minStrengthLevel) {
input.setCustomValidity('Password terlalu lemah! Minimal harus mencapai level orange.');
} else {
input.setCustomValidity('');
}
});
// Prevent form submission if password is too weak
const form = input.closest('form');
if (form && !form.hasAttribute('data-password-form-initialized')) {
form.setAttribute('data-password-form-initialized', 'true');
form.addEventListener('submit', function(e) {
const strengthLevel = getCurrentPasswordStrength(input.value);
if (input.value && strengthLevel < minStrengthLevel) {
e.preventDefault();
input.focus();
// Show warning
if (warningElement) {
warningElement.classList.remove('hidden');
}
// Show validation message
input.reportValidity();
}
});
}
});
};
// Run on DOMContentLoaded and Livewire navigation
if (document.readyState === 'loading') {
document.addEventListener('DOMContentLoaded', initPasswordStrength);
} else {
initPasswordStrength();
}
document.addEventListener('livewire:navigated', initPasswordStrength);
})();
function getCurrentPasswordStrength(password) {
const checks = {
'min-length': password.length >= 8,
'lowercase': /[a-z]/.test(password),
'uppercase': /[A-Z]/.test(password),
'numbers': /\d/.test(password),
'special-characters': /[!@#$%^&*()_+\-=\[\]{};':"\\|,.<>\/?]/.test(password)
};
return Object.values(checks).filter(Boolean).length;
}
function checkPasswordStrength(password, strengthBar, hintsContainer, warningElement, minStrengthLevel) {
const checks = {
'min-length': password.length >= 8,
'lowercase': /[a-z]/.test(password),
'uppercase': /[A-Z]/.test(password),
'numbers': /\d/.test(password),
'special-characters': /[!@#$%^&*()_+\-=\[\]{};':"\\|,.<>\/?]/.test(password)
};
const passed = Object.values(checks).filter(Boolean).length;
// Update strength bars
if (strengthBar) {
const bars = strengthBar.querySelectorAll('div');
bars.forEach((bar, index) => {
// Reset classes
bar.className = 'h-2 flex-auto rounded-full mx-1 transition-all duration-300';
if (index < passed) {
if (passed <= 2) {
bar.classList.add('bg-red-500');
} else if (passed === 3) {
bar.classList.add('bg-yellow-500'); // Orange/Yellow level
} else if (passed === 4) {
bar.classList.add('bg-blue-500');
} else if (passed === 5) {
bar.classList.add('bg-green-500');
}
} else {
bar.classList.add('bg-neutral-300');
}
});
}
// Show/hide warning based on strength
if (warningElement) {
if (password && passed < minStrengthLevel) {
warningElement.classList.remove('hidden');
} else {
warningElement.classList.add('hidden');
}
}
// Update requirement icons
if (hintsContainer) {
Object.keys(checks).forEach(function(rule) {
const ruleElement = hintsContainer.querySelector(`[data-hs-strong-password-hints-rule-text="${rule}"]`);
if (ruleElement) {
const checkIcon = ruleElement.querySelector('[data-check]');
const uncheckIcon = ruleElement.querySelector('[data-uncheck]');
if (checks[rule]) {
checkIcon.classList.remove('hidden');
uncheckIcon.classList.add('hidden');
} else {
checkIcon.classList.add('hidden');
uncheckIcon.classList.remove('hidden');
}
}
});
}
return passed;
}
</script>

View File

@@ -1,51 +1,51 @@
@props([
'name',
'label' => null,
'options' => [],
'selected' => null,
'multiple' => false,
'required' => false,
'divClass' => null,
'hideRequiredIndicator' => false,
])
<fieldset class="flex flex-col relative w-full {{ $divClass ?? '' }}" name="{{ $name }}">
@if ($label)
<legend class="mb-2">
<label for="{{ $name }}" class="text-sm font-bold text-color-base">
{{ $label }}
@if ($required && !$hideRequiredIndicator)
<span class="text-red-500">*</span>
@endif
</label>
</legend>
@endif
<div class="block w-full text-sm text-color-base space-y-2">
@if (count($options) == 0 && $slot)
{{ $slot }}
@else
@foreach ($options as $key => $option)
<div class="flex items-center gap-3 group">
<div class="relative">
<input type="radio" id="{{ $name }}_{{ $key }}" name="{{ $name }}"
value="{{ gettype($options) == 'array' ? $option : $key }}"
{{ ($multiple && $selected ? in_array($key, $selected) : $selected==$option) ? 'checked' : '' }}
class="peer form-radio size-5 text-primary border-2 border-neutral bg-background-secondary/50
focus:ring-2 focus:ring-primary/30 focus:ring-offset-0 focus:outline-none
checked:bg-primary checked:border-primary
hover:border-primary/50 transition-all duration-200 cursor-pointer
appearance-none rounded-full" />
<div class="absolute top-1/2 left-1/2 -translate-x-1/2 -translate-y-1/2 size-2 bg-white rounded-full pointer-events-none opacity-0 scale-0 peer-checked:opacity-100 peer-checked:scale-100 transition-all duration-200"></div>
</div>
<label for="{{ $name }}_{{ $key }}" class="text-color-base cursor-pointer select-none group-hover:text-primary transition-colors duration-200">
{{ $option }}
</label>
</div>
@endforeach
@endif
</div>
@error($name)
<p class="text-red-500 text-xs mt-1">{{ $message }}</p>
@enderror
@props([
'name',
'label' => null,
'options' => [],
'selected' => null,
'multiple' => false,
'required' => false,
'divClass' => null,
'hideRequiredIndicator' => false,
])
<fieldset class="flex flex-col relative w-full {{ $divClass ?? '' }}" name="{{ $name }}">
@if ($label)
<legend class="mb-2">
<label for="{{ $name }}" class="text-sm font-bold text-color-base">
{{ $label }}
@if ($required && !$hideRequiredIndicator)
<span class="text-error">*</span>
@endif
</label>
</legend>
@endif
<div class="block w-full text-sm text-color-base space-y-2">
@if (count($options) == 0 && $slot)
{{ $slot }}
@else
@foreach ($options as $key => $option)
<div class="flex items-center gap-3 group">
<div class="relative">
<input type="radio" id="{{ $name }}_{{ $key }}" name="{{ $name }}"
value="{{ gettype($options) == 'array' ? $option : $key }}"
{{ ($multiple && $selected ? in_array($key, $selected) : $selected==$option) ? 'checked' : '' }}
class="peer form-radio size-5 text-primary border-2 border-neutral/70 bg-background-secondary/40
focus:ring-2 focus:ring-primary/40 focus:ring-offset-0 focus:outline-none
checked:bg-primary checked:border-primary checked:shadow-[0_0_12px_-2px_hsl(var(--color-primary)/0.55)]
hover:border-primary/60 transition-all duration-200 cursor-pointer
appearance-none rounded-full" />
<div class="absolute top-1/2 left-1/2 -translate-x-1/2 -translate-y-1/2 size-2 bg-white rounded-full pointer-events-none opacity-0 scale-0 peer-checked:opacity-100 peer-checked:scale-100 transition-all duration-200"></div>
</div>
<label for="{{ $name }}_{{ $key }}" class="text-color-base cursor-pointer select-none group-hover:text-primary transition-colors duration-200">
{{ $option }}
</label>
</div>
@endforeach
@endif
</div>
@error($name)
<p class="text-error text-xs mt-1">{{ $message }}</p>
@enderror
</fieldset>

View File

@@ -1,46 +1,46 @@
@props([
'name',
'label' => null,
'options' => [],
'selected' => null,
'multiple' => false,
'required' => false,
'divClass' => null,
'hideRequiredIndicator' => false,
])
<fieldset class="flex flex-col relative w-full {{ $divClass ?? '' }}">
@if ($label)
<legend class="mb-1">
<label for="{{ $name }}"
class="text-sm font-bold text-base">
{{ $label }}
@if ($required && !$hideRequiredIndicator)
<span class="text-red-500">*</span>
@endif
</label>
</legend>
@endif
<select id="{{ $id ?? $name }}" {{ $multiple ? 'multiple' : '' }} {{ $attributes->only(['required', 'wire:model',
'wire:dirty.class', 'wire:model.live']) }}
class="block px-2.5 py-2.5 w-full text-sm text-base bg-background-secondary/50 border border-neutral
rounded-md shadow-sm outline-none focus:outline-none focus:ring-2 focus:ring-primary/30 focus:border-primary transition-all duration-300 ease-in-out form-select">
@if (count($options) == 0 && $slot)
{{ $slot }}
@else
@foreach ($options as $key => $option)
<option value="{{ gettype($options) == 'array' ? $option : $key }}" {{ ($multiple && $selected ? in_array($key,
$selected) : $selected==$option) ? 'selected' : '' }}>
{{ $option }}</option>
@endforeach
@endif
</select>
@if ($multiple)
<p class="text-xs text-base">
{{ __('Pro tip: Hold down the Ctrl (Windows) / Command (Mac) button to select multiple options.') }}</p>
@endif
@error($name)
<p class="text-red-500 text-xs">{{ $message }}</p>
@enderror
@props([
'name',
'label' => null,
'options' => [],
'selected' => null,
'multiple' => false,
'required' => false,
'divClass' => null,
'hideRequiredIndicator' => false,
])
<fieldset class="flex flex-col relative w-full {{ $divClass ?? '' }}">
@if ($label)
<legend class="mb-1">
<label for="{{ $name }}"
class="text-sm font-bold text-base">
{{ $label }}
@if ($required && !$hideRequiredIndicator)
<span class="text-error">*</span>
@endif
</label>
</legend>
@endif
<select id="{{ $id ?? $name }}" {{ $multiple ? 'multiple' : '' }} {{ $attributes->only(['required', 'wire:model',
'wire:dirty.class', 'wire:model.live']) }}
class="block px-2.5 py-2.5 w-full text-sm text-base bg-background-secondary/40 border border-neutral/60
rounded-md shadow-sm outline-none focus:outline-none focus:ring-2 focus:ring-primary/40 focus:border-primary hover:border-neutral transition-all duration-300 ease-in-out form-select">
@if (count($options) == 0 && $slot)
{{ $slot }}
@else
@foreach ($options as $key => $option)
<option value="{{ gettype($options) == 'array' ? $option : $key }}" {{ ($multiple && $selected ? in_array($key,
$selected) : $selected==$option) ? 'selected' : '' }}>
{{ $option }}</option>
@endforeach
@endif
</select>
@if ($multiple)
<p class="text-xs text-base">
{{ __('Pro tip: Hold down the Ctrl (Windows) / Command (Mac) button to select multiple options.') }}</p>
@endif
@error($name)
<p class="text-error text-xs mt-1">{{ $message }}</p>
@enderror
</fieldset>

View File

@@ -1,33 +1,33 @@
@props([
'name',
'label' => null,
'required' => false,
'divClass' => null,
'class' => null,
'placeholder' => null,
'id' => null,
'type' => null,
'hideRequiredIndicator' => false,
'dirty' => false,
])
<fieldset class="flex flex-col relative mt-3 w-full {{ $divClass ?? '' }}">
@if ($label)
<legend>
<label for="{{ $name }}"
class="text-sm text-primary-100 absolute -translate-y-1/2 start-1 ml-1 bg-primary-800 px-2 rounded-md">
{{ $label }}
@if ($required && !$hideRequiredIndicator)
<span class="text-red-500">*</span>
@endif
</label>
</legend>
@endif
<textarea type="{{ $type ?? 'text' }}" id="{{ $id ?? $name }}" name="{{ $name }}"
class="block w-full text-sm text-base bg-background-secondary/50 border border-neutral rounded-md shadow-sm focus:outline-none focus:ring-2 focus:ring-primary/30 focus:border-primary transition-all duration-300 ease-in-out disabled:bg-background-secondary/50 disabled:cursor-not-allowed resize-y min-h-[100px] {{ $class ?? '' }} @if ($type !== 'color') px-2.5 py-2.5 @endif"
placeholder="{{ $placeholder ?? ($label ?? '') }}" @if ($dirty && isset($attributes['wire:model']))
wire:dirty.class="!border-yellow-600" @endif {{ $attributes->except(['placeholder', 'label', 'id', 'name', 'type', 'class', 'divClass', 'required', 'hideRequiredIndicator', 'dirty']) }}
@required($required)>{{ $slot }}</textarea>
@error($name)
<p class="text-red-500 text-xs">{{ $message }}</p>
@enderror
@props([
'name',
'label' => null,
'required' => false,
'divClass' => null,
'class' => null,
'placeholder' => null,
'id' => null,
'type' => null,
'hideRequiredIndicator' => false,
'dirty' => false,
])
<fieldset class="flex flex-col relative mt-3 w-full {{ $divClass ?? '' }}">
@if ($label)
<legend>
<label for="{{ $name }}"
class="text-sm text-primary-100 absolute -translate-y-1/2 start-1 ml-1 bg-primary-800 px-2 rounded-md">
{{ $label }}
@if ($required && !$hideRequiredIndicator)
<span class="text-error">*</span>
@endif
</label>
</legend>
@endif
<textarea type="{{ $type ?? 'text' }}" id="{{ $id ?? $name }}" name="{{ $name }}"
class="block w-full text-sm text-base bg-background-secondary/40 border border-neutral/60 rounded-md shadow-sm focus:outline-none focus:ring-2 focus:ring-primary/40 focus:border-primary hover:border-neutral transition-all duration-300 ease-in-out disabled:bg-background-secondary/30 disabled:cursor-not-allowed resize-y min-h-[100px] {{ $class ?? '' }} @if ($type !== 'color') px-2.5 py-2.5 @endif"
placeholder="{{ $placeholder ?? ($label ?? '') }}" @if ($dirty && isset($attributes['wire:model']))
wire:dirty.class="!border-yellow-600" @endif {{ $attributes->except(['placeholder', 'label', 'id', 'name', 'type', 'class', 'divClass', 'required', 'hideRequiredIndicator', 'dirty']) }}
@required($required)>{{ $slot }}</textarea>
@error($name)
<p class="text-error text-xs mt-1">{{ $message }}</p>
@enderror
</fieldset>