Add all files
This commit is contained in:
23
themes/lucentui/views/components/form/checkbox.blade.php
Normal file
23
themes/lucentui/views/components/form/checkbox.blade.php
Normal file
@@ -0,0 +1,23 @@
|
||||
@props([
|
||||
'name',
|
||||
'id' => null,
|
||||
'label' => null,
|
||||
'divClass' => '',
|
||||
])
|
||||
<div class="flex items-center {{ $divClass ?? '' }}">
|
||||
{{-- <input type="hidden" name="{{ $name }}" value="0" /> --}}
|
||||
<input type="checkbox" name="{{ $name }}" id="{{ $id ?? $name }}"
|
||||
{{ $attributes->except(['label', 'name', 'id', 'class', 'divClass', 'required']) }}
|
||||
class="form-checkbox size-4 text-primary rounded focus:ring-secondary hover:bg-secondary ring-offset-primary-800 focus:ring-2 bg-background-secondary border-neutral" />
|
||||
<label class="ml-2 text-sm text-primary-100" for="{{ $id ?? $name }}">
|
||||
@if(isset($label))
|
||||
{{ $label }}
|
||||
@else
|
||||
{{ $slot }}
|
||||
@endif
|
||||
</label>
|
||||
|
||||
@error($name)
|
||||
<p class="text-red-500 text-xs">{{ $message }}</p>
|
||||
@enderror
|
||||
</div>
|
||||
149
themes/lucentui/views/components/form/configoption.blade.php
Normal file
149
themes/lucentui/views/components/form/configoption.blade.php
Normal file
@@ -0,0 +1,149 @@
|
||||
<div class="space-y-4">
|
||||
@switch($config->type)
|
||||
@case('select')
|
||||
<div class="group">
|
||||
<x-form.select
|
||||
name="{{ $name }}"
|
||||
:label="__($config->label ?? $config->name)"
|
||||
:required="$config->required ?? false"
|
||||
:selected="config('configs.' . $config->name)"
|
||||
:multiple="$config->multiple ?? false"
|
||||
wire:model.live="{{ $name }}"
|
||||
:placeholder="$config->placeholder ?? ''"
|
||||
class="transition-all duration-300 focus:ring-2 focus:ring-primary focus:border-primary-500 rounded-xl"
|
||||
>
|
||||
{{ $slot }}
|
||||
</x-form.select>
|
||||
</div>
|
||||
@break
|
||||
|
||||
@case('slider')
|
||||
<div x-data="{
|
||||
options: @js($config->children->map(fn($child) => ['option' => $child->name, 'value' => $child->id, 'price' => ($showPriceTag && $child->price(billing_period: $plan->billing_period, billing_unit: $plan->billing_unit)->available) ? (string)$child->price(billing_period: $plan->billing_period, billing_unit: $plan->billing_unit) : ''])),
|
||||
showPriceTag: @js($showPriceTag),
|
||||
selectedOption: 0,
|
||||
backendOption: $wire.entangle('{{ $name }}').live,
|
||||
progressOption: '0%',
|
||||
segmentsWidthOption: '0%',
|
||||
|
||||
init() {
|
||||
const initialValue = this.$wire.get('{{ $name }}');
|
||||
const foundIndex = this.options.findIndex(plan => plan.value == initialValue);
|
||||
if (foundIndex !== -1) {
|
||||
this.selectedOption = foundIndex;
|
||||
}
|
||||
this.updateSliderVisuals();
|
||||
$watch('selectedOption', Alpine.debounce(() => this.backendOption = this.options[this.selectedOption].value, 300))
|
||||
},
|
||||
|
||||
updateSliderVisuals() {
|
||||
this.progressOption = `${(this.selectedOption / (this.options.length - 1)) * 100}%`;
|
||||
this.segmentsWidthOption = `${100 / (this.options.length - 1)}%`;
|
||||
},
|
||||
|
||||
setOptionValue(index) {
|
||||
this.selectedOption = parseInt(index);
|
||||
this.updateSliderVisuals();
|
||||
}
|
||||
}" class="flex flex-col gap-1 relative">
|
||||
<div class="relative flex items-center" :style="`--progress:${progressOption};--segments-width:${segmentsWidthOption}`">
|
||||
<div class="
|
||||
absolute left-2.5 right-2.5 h-1.5 bg-background-secondary rounded-full overflow-hidden transition-all duration-500 ease-in-out
|
||||
before:absolute before:inset-0 before:bg-primary
|
||||
before:[mask-image:_linear-gradient(to_right,theme(colors.white),theme(colors.white)_var(--progress),transparent_var(--progress))]
|
||||
[&[x-cloak]]:hidden" aria-hidden="true" x-cloak></div>
|
||||
<input class="
|
||||
relative appearance-none cursor-pointer w-full bg-transparent focus:outline-none transition-all duration-500 ease-in-out
|
||||
[&::-webkit-slider-thumb]:appearance-none [&::-webkit-slider-thumb]:h-5 [&::-webkit-slider-thumb]:w-5
|
||||
[&::-webkit-slider-thumb]:rounded-full [&::-webkit-slider-thumb]:bg-white [&::-webkit-slider-thumb]:shadow-none
|
||||
[&::-webkit-slider-thumb]:focus:ring-0 [&::-moz-range-thumb]:h-5 [&::-moz-range-thumb]:w-5
|
||||
[&::-moz-range-thumb]:rounded-full [&::-moz-range-thumb]:bg-base [&::-moz-range-thumb]:border-none
|
||||
[&::-moz-range-thumb]:shadow-none [&::-moz-range-thumb]:focus:ring-0
|
||||
" type="range" min="0" :max="options.length - 1" x-model="selectedOption" @input="setOptionValue(selectedOption)" aria-label="Option Slider">
|
||||
</div>
|
||||
<!-- Options -->
|
||||
<ul class="flex justify-between text-xs font-medium text-light px-2.5">
|
||||
<template x-for="(plan, index) in options" :key="index">
|
||||
<li class="relative @if($showPriceTag) pb-7 @else pb-2 @endif">
|
||||
<button @click="setOptionValue(index)" class="fixed flex flex-col items-center -translate-x-1/2">
|
||||
<span class="hidden lg:inline text-sm font-semibold" x-text="`${plan.option}`"></span>
|
||||
<span class="hidden lg:inline text-sm font-semibold" x-text="`${plan.price}`"></span>
|
||||
<span class="inline lg:hidden" x-text="`${plan.option}`"></span>
|
||||
</button>
|
||||
</li>
|
||||
</template>
|
||||
</ul>
|
||||
</div>
|
||||
@break
|
||||
|
||||
@case('text')
|
||||
@case('password')
|
||||
|
||||
@case('email')
|
||||
@case('number')
|
||||
|
||||
@case('color')
|
||||
@case('file')
|
||||
<div class="group">
|
||||
<x-form.input
|
||||
name="{{ $name }}"
|
||||
:type="$config->type"
|
||||
:label="__($config->label ?? $config->name)"
|
||||
:placeholder="$config->placeholder ?? ($config->default ?? '')"
|
||||
:required="$config->required ?? false"
|
||||
wire:model.live="{{ $name }}"
|
||||
class="transition-all duration-300 focus:ring-2 focus:ring-primary focus:border-primary rounded-xl border-2 border-gray hover:border-gray"
|
||||
/>
|
||||
</div>
|
||||
@break
|
||||
|
||||
@case('checkbox')
|
||||
<div class="group">
|
||||
<x-form.checkbox
|
||||
name="{{ $name }}"
|
||||
type="checkbox"
|
||||
:label="__($config->label ?? $config->name)"
|
||||
:required="$config->required ?? false"
|
||||
:checked="config('configs.' . $config->name) ? true : false"
|
||||
wire:model="{{ $name }}"
|
||||
class="rounded-2xl border-2 border-gray-200 text-primary-600 focus:ring-primary focus:ring-2 transition-all duration-300"
|
||||
/>
|
||||
</div>
|
||||
@break
|
||||
|
||||
@case('radio')
|
||||
<div class="group">
|
||||
<x-form.radio
|
||||
name="{{ $name }}"
|
||||
:label="__($config->label ?? $config->name)"
|
||||
:selected="config('configs.' . $config->name)"
|
||||
:required="$config->required ?? false"
|
||||
wire:model="{{ $name }}"
|
||||
class="border-2 border-gray-200 text-primary-600 focus:ring-primary focus:ring-2 transition-all duration-300"
|
||||
>
|
||||
{{ $slot }}
|
||||
</x-form.radio>
|
||||
</div>
|
||||
@break
|
||||
|
||||
@default
|
||||
<div class="p-4 bg-gray-50 rounded-xl border-2 border-dashed border-gray-300">
|
||||
<p class="text-sm text-gray-500 text-center">Unsupported field type: {{ $config->type }}</p>
|
||||
</div>
|
||||
@endswitch
|
||||
|
||||
@if(isset($config->description))
|
||||
<div class="mt-3">
|
||||
@if(isset($config->link))
|
||||
<a href="{{ $config->link }}"
|
||||
class="inline-flex items-center text-sm text-primary-600 hover:text-primary-700 transition-colors duration-200 group">
|
||||
<span class="mr-2">{{ $config->description }}</span>
|
||||
<x-ri-arrow-right-long-line class="w-4 h-4 transform transition-transform duration-200 group-hover:translate-x-1" />
|
||||
</a>
|
||||
@else
|
||||
<p class="text-sm text-gray-600 leading-relaxed">{{ $config->description }}</p>
|
||||
</div>
|
||||
</div>
|
||||
@endif
|
||||
@endif
|
||||
</div>
|
||||
22
themes/lucentui/views/components/form/input.blade.php
Normal file
22
themes/lucentui/views/components/form/input.blade.php
Normal file
@@ -0,0 +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 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>
|
||||
@@ -0,0 +1,258 @@
|
||||
@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>
|
||||
// Enhanced password strength validation
|
||||
document.addEventListener('DOMContentLoaded', function() {
|
||||
const passwordInputs = document.querySelectorAll('[data-hs-strong-password]');
|
||||
|
||||
passwordInputs.forEach(function(input) {
|
||||
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.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();
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
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>
|
||||
43
themes/lucentui/views/components/form/properties.blade.php
Normal file
43
themes/lucentui/views/components/form/properties.blade.php
Normal file
@@ -0,0 +1,43 @@
|
||||
@props(['properties', 'custom_properties' => []])
|
||||
|
||||
@foreach ($custom_properties as $property)
|
||||
@switch($property->type)
|
||||
@case('date')
|
||||
@case('string')
|
||||
|
||||
@case('number')
|
||||
<x-form.input :type="$property->type" name="properties.{{ $property->key }}" :label="$property->name" :required="$property->required"
|
||||
wire:model="properties.{{ $property->key }}" :value="$properties[$property->key] ?? ''" />
|
||||
@break
|
||||
|
||||
@case('checkbox')
|
||||
<x-form.checkbox name="properties.{{ $property->key }}" :label="$property->name" :required="$property->required"
|
||||
wire:model="properties.{{ $property->key }}" :checked="$properties[$property->key] ?? false" />
|
||||
@break
|
||||
|
||||
@case('radio')
|
||||
@foreach ($property->allowed_values as $value)
|
||||
<div class="flex items-center">
|
||||
<input type="radio" value="{{ $value }}" name="properties.{{ $property->key }}" type="radio"
|
||||
@checked($properties[$property->key] === $value ?? false) label="{{ $value }}" @required($property->required)
|
||||
wire:model="properties.{{ $property->key }}"
|
||||
class="form-radio size-4 text-primary rounded-full focus:ring-secondary hover:bg-secondary ring-offset-primary-800 focus:ring-2 bg-background-secondary border-neutral" />
|
||||
<label class="ml-2 text-sm text-primary-100"
|
||||
for="properties.{{ $property->key }}">{{ $value }}</label>
|
||||
</div>
|
||||
@endforeach
|
||||
@break
|
||||
|
||||
@case('select')
|
||||
<x-form.select name="properties.{{ $property->key }}" :label="$property->name"
|
||||
wire:model="properties.{{ $property->key }}" :required="$property->required" :options="$property->allowed_values" :selected="$properties[$property->key] ?? ''" />
|
||||
@break
|
||||
|
||||
@case('text')
|
||||
<x-form.textarea :type="$property->type" name="properties.{{ $property->key }}" :label="$property->name" :required="$property->required"
|
||||
wire:model="properties.{{ $property->key }}">{{ $properties[$property->key] ?? '' }}</x-form.textarea>
|
||||
@break
|
||||
|
||||
@default
|
||||
@endswitch
|
||||
@endforeach
|
||||
45
themes/lucentui/views/components/form/radio.blade.php
Normal file
45
themes/lucentui/views/components/form/radio.blade.php
Normal file
@@ -0,0 +1,45 @@
|
||||
@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-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="block px-2.5 py-2.5 w-full text-sm text-primary-100 border-neutral rounded-md outline-none focus:outline-none focus:border-secondary transition-all duration-300 ease-in-out">
|
||||
@if (count($options) == 0 && $slot)
|
||||
{{ $slot }}
|
||||
@else
|
||||
@foreach ($options as $key => $option)
|
||||
<div class="flex items-center gap-2">
|
||||
<input type="radio" id="{{ $name }}_{{ $key }}" name="{{ $name }}"
|
||||
value="{{ gettype($options) == 'array' ? $option : $key }}" {{ ($multiple && $selected ? in_array($key,
|
||||
$selected) : $selected==$option) ? 'checked' : '' }} />
|
||||
<label for="{{ $name }}_{{ $key }}">
|
||||
{{ $option }}
|
||||
</label>
|
||||
</div>
|
||||
@endforeach
|
||||
@endif
|
||||
</div>
|
||||
|
||||
@error($name)
|
||||
<p class="text-red-500 text-xs">{{ $message }}</p>
|
||||
@enderror
|
||||
</fieldset>
|
||||
46
themes/lucentui/views/components/form/select.blade.php
Normal file
46
themes/lucentui/views/components/form/select.blade.php
Normal file
@@ -0,0 +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-primary-100 bg-background-secondary border-2 border-neutral
|
||||
rounded-md outline-none focus:outline-none focus:border-secondary 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
|
||||
</fieldset>
|
||||
38
themes/lucentui/views/components/form/setting.blade.php
Normal file
38
themes/lucentui/views/components/form/setting.blade.php
Normal file
@@ -0,0 +1,38 @@
|
||||
<div class="flex flex-col gap-1">
|
||||
@switch($setting->type)
|
||||
@case('select')
|
||||
<x-form.select name="fields.{{ $key }}.{{ $setting->name }}" :label="__($setting->label ?? $setting->name)" :required="$setting->required ?? false"
|
||||
:options="$setting->options" :selected="config('settings.' . $setting->name)" :multiple="$setting->multiple ?? false"
|
||||
wire:model="fields.{{ $key }}.{{ $setting->name }}" />
|
||||
@break
|
||||
|
||||
@case('text')
|
||||
@case('password')
|
||||
|
||||
@case('email')
|
||||
@case('number')
|
||||
|
||||
@case('color')
|
||||
@case('file')
|
||||
<x-form.input name="fields.{{ $key }}.{{ $setting->name }}" :type="$setting->type" :label="__($setting->label ?? $setting->name)"
|
||||
:placeholder="$setting->default ?? ''" :required="$setting->required ?? false" wire:model="fields.{{ $key }}.{{ $setting->name }}" />
|
||||
@break
|
||||
|
||||
@case('checkbox')
|
||||
<x-form.checkbox name="fields.{{ $key }}.{{ $setting->name }}" type="checkbox" :label="__($setting->label ?? $setting->name)"
|
||||
:required="$setting->required ?? false" :checked="config('settings.' . $setting->name) ? true : false" wire:model="fields.{{ $key }}.{{ $setting->name }}" />
|
||||
@break
|
||||
|
||||
@default
|
||||
@endswitch
|
||||
@isset($setting->description)
|
||||
@isset($setting->link)
|
||||
<a href="{{ $setting->link }}" class="text-xs text-primary-500 hover:underline hover:text-secondary group">
|
||||
{{ $setting->description }}
|
||||
<x-ri-arrow-right-long-line class="ml-1 size-3 inline-block -rotate-45 group-hover:rotate-0 transition" />
|
||||
</a>
|
||||
@else
|
||||
<p class="text-xs text-primary-500">{{ $setting->description }}</p>
|
||||
@endisset
|
||||
@endisset
|
||||
</div>
|
||||
33
themes/lucentui/views/components/form/textarea.blade.php
Normal file
33
themes/lucentui/views/components/form/textarea.blade.php
Normal file
@@ -0,0 +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-primary-100 bg-primary-800 border-2 border-neutral rounded-md outline-none focus:outline-none focus:border-secondary transition-all duration-300 ease-in-out disabled:bg-primary-700 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)>{{ $slot }}</textarea>
|
||||
@error($name)
|
||||
<p class="text-red-500 text-xs">{{ $message }}</p>
|
||||
@enderror
|
||||
</fieldset>
|
||||
Reference in New Issue
Block a user