mirror of
https://github.com/HolgerHatGarKeineNode/einundzwanzig-nostr.git
synced 2026-01-28 07:43:18 +00:00
🎨 Add new Flux icons: implement multiple reusable icon components (e.g., hand-raised, hand-thumb-up, heart, hashtag, home) with variant support for improved UI consistency.
This commit is contained in:
22
resources/views/flux/input/clearable.blade.php
Normal file
22
resources/views/flux/input/clearable.blade.php
Normal file
@@ -0,0 +1,22 @@
|
||||
@blaze
|
||||
|
||||
@php
|
||||
$attributes = $attributes->merge([
|
||||
'variant' => 'subtle',
|
||||
'class' => '-me-1 [[data-flux-input]:has(input:placeholder-shown)_&]:hidden [[data-flux-input]:has(input[disabled])_&]:hidden',
|
||||
'square' => true,
|
||||
'size' => null,
|
||||
]);
|
||||
@endphp
|
||||
|
||||
<flux:button
|
||||
:$attributes
|
||||
:size="$size === 'sm' || $size === 'xs' ? 'xs' : 'sm'"
|
||||
x-data
|
||||
x-on:click="let input = $el.closest('[data-flux-input]').querySelector('input'); input.value = ''; input.dispatchEvent(new Event('input', { bubbles: false })); input.dispatchEvent(new Event('change', { bubbles: false })); input.focus()"
|
||||
tabindex="-1"
|
||||
aria-label="Clear input"
|
||||
data-flux-clear-button
|
||||
>
|
||||
<flux:icon.x-mark variant="micro" />
|
||||
</flux:button>
|
||||
22
resources/views/flux/input/copyable.blade.php
Normal file
22
resources/views/flux/input/copyable.blade.php
Normal file
@@ -0,0 +1,22 @@
|
||||
@blaze
|
||||
|
||||
@php
|
||||
$attributes = $attributes->merge([
|
||||
'variant' => 'subtle',
|
||||
'class' => '-me-1',
|
||||
'square' => true,
|
||||
'size' => null,
|
||||
]);
|
||||
@endphp
|
||||
|
||||
<flux:button
|
||||
:$attributes
|
||||
:size="$size === 'sm' || $size === 'xs' ? 'xs' : 'sm'"
|
||||
x-data="{ copied: false }"
|
||||
x-on:click="copied = ! copied; navigator.clipboard && navigator.clipboard.writeText($el.closest('[data-flux-input]').querySelector('input').value); setTimeout(() => copied = false, 2000)"
|
||||
x-bind:data-copyable-copied="copied"
|
||||
aria-label="{{ __('Copy to clipboard') }}"
|
||||
>
|
||||
<flux:icon.clipboard-document-check variant="mini" class="hidden [[data-copyable-copied]>&]:block" />
|
||||
<flux:icon.clipboard-document variant="mini" class="block [[data-copyable-copied]>&]:hidden" />
|
||||
</flux:button>
|
||||
18
resources/views/flux/input/expandable.blade.php
Normal file
18
resources/views/flux/input/expandable.blade.php
Normal file
@@ -0,0 +1,18 @@
|
||||
@blaze
|
||||
|
||||
@php
|
||||
$attributes = $attributes->merge([
|
||||
'variant' => 'subtle',
|
||||
'class' => '-me-1',
|
||||
'square' => true,
|
||||
'size' => null,
|
||||
]);
|
||||
@endphp
|
||||
|
||||
<flux:button
|
||||
:$attributes
|
||||
:size="$size === 'sm' || $size === 'xs' ? 'xs' : 'sm'"
|
||||
x-on:click="$el.closest('[data-flux-input]').querySelector('input').value = ''"
|
||||
>
|
||||
<flux:icon.chevron-down variant="micro" />
|
||||
</flux:button>
|
||||
79
resources/views/flux/input/file.blade.php
Normal file
79
resources/views/flux/input/file.blade.php
Normal file
@@ -0,0 +1,79 @@
|
||||
@blaze
|
||||
|
||||
@php
|
||||
extract(Flux::forwardedAttributes($attributes, [
|
||||
'name',
|
||||
'multiple',
|
||||
'size',
|
||||
]));
|
||||
@endphp
|
||||
|
||||
@props([
|
||||
'name' => $attributes->whereStartsWith('wire:model')->first(),
|
||||
'multiple' => null,
|
||||
'size' => null,
|
||||
])
|
||||
|
||||
@php
|
||||
$classes = Flux::classes()
|
||||
->add('w-full flex items-center gap-4')
|
||||
->add('[[data-flux-input-group]_&]:items-stretch [[data-flux-input-group]_&]:gap-0')
|
||||
|
||||
// NOTE: We need to add relative positioning here to prevent odd overflow behaviors because of
|
||||
// "sr-only": https://github.com/tailwindlabs/tailwindcss/discussions/12429
|
||||
->add('relative')
|
||||
;
|
||||
|
||||
[ $styleAttributes, $attributes ] = Flux::splitAttributes($attributes);
|
||||
@endphp
|
||||
|
||||
<div
|
||||
{{ $styleAttributes->class($classes) }}
|
||||
data-flux-input-file
|
||||
wire:ignore
|
||||
tabindex="0"
|
||||
x-data {{-- This is here to "scope" the x-ref references inside this component from interfering with others outside... --}}
|
||||
x-on:click.prevent.stop="$refs.input.click()"
|
||||
x-on:keydown.enter.prevent.stop="$refs.input.click()"
|
||||
x-on:keydown.space.prevent.stop
|
||||
x-on:keyup.space.prevent.stop="$refs.input.click()"
|
||||
x-on:change="$refs.name.textContent = $event.target.files[1] ? ($event.target.files.length + ' {!! __('files') !!}') : ($event.target.files[0]?.name || '{!! __('No file chosen') !!}')"
|
||||
>
|
||||
<input
|
||||
x-ref="input"
|
||||
x-on:click.stop {{-- Without this, the parent element's click listener will ".prevent" the file input from being clicked... --}}
|
||||
{{-- This is here because clearing the input via .value = "" doesn't trigger a change event... --}}
|
||||
{{-- We need it to so that we can know to clear the selected file labels when the input is cleared... --}}
|
||||
x-init="Object.defineProperty($el, 'value', {
|
||||
...Object.getOwnPropertyDescriptor(HTMLInputElement.prototype, 'value'),
|
||||
set(value) {
|
||||
Object.getOwnPropertyDescriptor(HTMLInputElement.prototype, 'value').set.call(this, value);
|
||||
|
||||
if(! value) this.dispatchEvent(new Event('change', { bubbles: true }))
|
||||
}
|
||||
})"
|
||||
type="file"
|
||||
class="sr-only"
|
||||
tabindex="-1"
|
||||
{{ $attributes }} {{ $multiple ? 'multiple' : '' }} @if($name)name="{{ $name }}"@endif
|
||||
>
|
||||
|
||||
<flux:button as="div" class="cursor-pointer" :$size aria-hidden="true">
|
||||
<?php if ($multiple) : ?>
|
||||
{!! __('Choose files') !!}
|
||||
<?php else : ?>
|
||||
{!! __('Choose file') !!}
|
||||
<?php endif; ?>
|
||||
</flux:button>
|
||||
|
||||
<div
|
||||
x-ref="name"
|
||||
@class([
|
||||
'cursor-default select-none truncate whitespace-nowrap text-sm text-zinc-500 dark:text-zinc-400 font-medium',
|
||||
'[[data-flux-input-group]_&]:flex-1 [[data-flux-input-group]_&]:border-e [[data-flux-input-group]_&]:border-y [[data-flux-input-group]_&]:shadow-xs [[data-flux-input-group]_&]:border-zinc-200 dark:[[data-flux-input-group]_&]:border-zinc-600 [[data-flux-input-group]_&]:px-4 [[data-flux-input-group]_&]:bg-white dark:[[data-flux-input-group]_&]:bg-zinc-700 [[data-flux-input-group]_&]:flex [[data-flux-input-group]_&]:items-center dark:[[data-flux-input-group]_&]:text-zinc-300',
|
||||
])
|
||||
aria-hidden="true"
|
||||
>
|
||||
{!! __('No file chosen') !!}
|
||||
</div>
|
||||
</div>
|
||||
15
resources/views/flux/input/group/affix.blade.php
Normal file
15
resources/views/flux/input/group/affix.blade.php
Normal file
@@ -0,0 +1,15 @@
|
||||
@blaze
|
||||
|
||||
@php
|
||||
$classes = Flux::classes([
|
||||
'flex items-center px-4 text-sm whitespace-nowrap',
|
||||
'text-zinc-800 dark:text-zinc-200',
|
||||
'bg-zinc-800/5 dark:bg-white/20',
|
||||
'border-zinc-200 dark:border-white/10',
|
||||
'border border-x-zinc-100 shadow-xs',
|
||||
]);
|
||||
@endphp
|
||||
|
||||
<div {{ $attributes->class($classes) }} data-flux-input-group-label>
|
||||
{{ $slot }}
|
||||
</div>
|
||||
52
resources/views/flux/input/group/index.blade.php
Normal file
52
resources/views/flux/input/group/index.blade.php
Normal file
@@ -0,0 +1,52 @@
|
||||
@props([
|
||||
'name' => $attributes->whereStartsWith('wire:model')->first(),
|
||||
])
|
||||
|
||||
@php
|
||||
$classes = Flux::classes()
|
||||
->add('w-full flex')
|
||||
->add('*:data-flux-input:grow')
|
||||
->add([
|
||||
// With the external borders, let's always make sure the first and last children have outside borders.
|
||||
// For internal borders, we will ensure that all left borders are removed, but the right borders remain.
|
||||
// But when there is a input groupsuffix, then there should be no right internal border.
|
||||
// That way we shouldn't ever have a double border...
|
||||
|
||||
// All inputs borders...
|
||||
'[&>[data-flux-input]:last-child:not(:first-child)>[data-flux-group-target]:not([data-invalid])]:border-s-0',
|
||||
'[&>[data-flux-input]:not(:first-child):not(:last-child)>[data-flux-group-target]:not([data-invalid])]:border-s-0',
|
||||
'[&>[data-flux-input]:has(+[data-flux-input-group-suffix])>[data-flux-group-target]:not([data-invalid])]:border-e-0',
|
||||
|
||||
// Selects and date pickers borders...
|
||||
'[&>*:last-child:not(:first-child)>[data-flux-group-target]:not([data-invalid])]:border-s-0',
|
||||
'[&>*:not(:first-child):not(:last-child)>[data-flux-group-target]:not([data-invalid])]:border-s-0',
|
||||
'[&>*:has(+[data-flux-input-group-suffix])>[data-flux-group-target]:not([data-invalid])]:border-e-0',
|
||||
|
||||
// Buttons borders...
|
||||
'[&>[data-flux-group-target]:last-child:not(:first-child)]:border-s-0',
|
||||
'[&>[data-flux-group-target]:not(:first-child):not(:last-child)]:border-s-0',
|
||||
'[&>[data-flux-group-target]:has(+[data-flux-input-group-suffix])]:border-e-0',
|
||||
|
||||
// "Weld" the borders of inputs together by overriding their border radiuses...
|
||||
'[&>[data-flux-group-target]:not(:first-child):not(:last-child)]:rounded-none',
|
||||
'[&>[data-flux-group-target]:first-child:not(:last-child)]:rounded-e-none',
|
||||
'[&>[data-flux-group-target]:last-child:not(:first-child)]:rounded-s-none',
|
||||
|
||||
// "Weld" borders for sub-children of group targets (button element inside ui-select element, etc.)...
|
||||
'[&>*:not(:first-child):not(:last-child):not(:only-child)>[data-flux-group-target]]:rounded-none',
|
||||
'[&>*:first-child:not(:last-child)>[data-flux-group-target]]:rounded-e-none',
|
||||
'[&>*:last-child:not(:first-child)>[data-flux-group-target]]:rounded-s-none',
|
||||
|
||||
// "Weld" borders for sub-sub-children of group targets (input element inside div inside ui-select element (combobox))...
|
||||
'[&>*:not(:first-child):not(:last-child):not(:only-child)>[data-flux-input]>[data-flux-group-target]]:rounded-none',
|
||||
'[&>*:first-child:not(:last-child)>[data-flux-input]>[data-flux-group-target]]:rounded-e-none',
|
||||
'[&>*:last-child:not(:first-child)>[data-flux-input]>[data-flux-group-target]]:rounded-s-none',
|
||||
])
|
||||
;
|
||||
@endphp
|
||||
|
||||
<flux:with-field :$attributes :$name>
|
||||
<div {{ $attributes->class($classes) }} data-flux-input-group>
|
||||
{{ $slot }}
|
||||
</div>
|
||||
</flux:with-field>
|
||||
16
resources/views/flux/input/group/prefix.blade.php
Normal file
16
resources/views/flux/input/group/prefix.blade.php
Normal file
@@ -0,0 +1,16 @@
|
||||
@blaze
|
||||
|
||||
@php
|
||||
$classes = Flux::classes([
|
||||
'flex items-center px-4 text-sm whitespace-nowrap',
|
||||
'text-zinc-800 dark:text-zinc-200',
|
||||
'bg-zinc-800/5 dark:bg-white/20',
|
||||
'border-zinc-200 dark:border-white/10',
|
||||
'rounded-s-lg',
|
||||
'border-s border-t border-b shadow-xs',
|
||||
]);
|
||||
@endphp
|
||||
|
||||
<div {{ $attributes->class($classes) }} data-flux-input-group-prefix>
|
||||
{{ $slot }}
|
||||
</div>
|
||||
16
resources/views/flux/input/group/suffix.blade.php
Normal file
16
resources/views/flux/input/group/suffix.blade.php
Normal file
@@ -0,0 +1,16 @@
|
||||
@blaze
|
||||
|
||||
@php
|
||||
$classes = Flux::classes([
|
||||
'flex items-center px-4 text-sm whitespace-nowrap',
|
||||
'text-zinc-800 dark:text-zinc-200',
|
||||
'bg-zinc-800/5 dark:bg-white/20',
|
||||
'border-zinc-200 dark:border-white/10',
|
||||
'rounded-e-lg',
|
||||
'border-e border-t border-b shadow-xs',
|
||||
]);
|
||||
@endphp
|
||||
|
||||
<div {{ $attributes->class($classes) }} data-flux-input-group-suffix>
|
||||
{{ $slot }}
|
||||
</div>
|
||||
233
resources/views/flux/input/index.blade.php
Normal file
233
resources/views/flux/input/index.blade.php
Normal file
@@ -0,0 +1,233 @@
|
||||
@php $iconTrailing ??= $attributes->pluck('icon:trailing'); @endphp
|
||||
@php $iconLeading ??= $attributes->pluck('icon:leading'); @endphp
|
||||
@php $iconVariant ??= $attributes->pluck('icon:variant'); @endphp
|
||||
@php $maskDynamic ??= $attributes->pluck('mask:dynamic'); @endphp
|
||||
|
||||
@props([
|
||||
'name' => $attributes->whereStartsWith('wire:model')->first(),
|
||||
'iconVariant' => 'mini',
|
||||
'variant' => 'outline',
|
||||
'iconTrailing' => null,
|
||||
'iconLeading' => null,
|
||||
'maskDynamic' => null,
|
||||
'expandable' => null,
|
||||
'clearable' => null,
|
||||
'copyable' => null,
|
||||
'viewable' => null,
|
||||
'invalid' => null,
|
||||
'loading' => null,
|
||||
'type' => 'text',
|
||||
'mask' => null,
|
||||
'size' => null,
|
||||
'icon' => null,
|
||||
'kbd' => null,
|
||||
'as' => null,
|
||||
])
|
||||
|
||||
@php
|
||||
|
||||
// There are a few loading scenarios that this covers:
|
||||
// If `:loading="false"` then never show loading.
|
||||
// If `:loading="true"` then always show loading.
|
||||
// If `:loading="foo"` then show loading when `foo` request is happening.
|
||||
// If `wire:model` then never show loading.
|
||||
// If `wire:model.live` then show loading when the `wire:model` value request is happening.
|
||||
$wireModel = $attributes->wire('model');
|
||||
$wireTarget = null;
|
||||
|
||||
if ($loading !== false) {
|
||||
if ($loading === true) {
|
||||
$loading = true;
|
||||
} elseif ($wireModel?->directive) {
|
||||
$loading = $wireModel->hasModifier('live');
|
||||
$wireTarget = $loading ? $wireModel->value() : null;
|
||||
} else {
|
||||
$wireTarget = $loading;
|
||||
$loading = (bool) $loading;
|
||||
}
|
||||
}
|
||||
|
||||
$invalid ??= ($name && $errors->has($name));
|
||||
|
||||
$iconLeading ??= $icon;
|
||||
|
||||
$hasLeadingIcon = (bool) ($iconLeading);
|
||||
$countOfTrailingIcons = collect([
|
||||
(bool) $iconTrailing,
|
||||
(bool) $kbd,
|
||||
(bool) $clearable,
|
||||
(bool) $copyable,
|
||||
(bool) $viewable,
|
||||
(bool) $expandable,
|
||||
])->filter()->count();
|
||||
|
||||
$iconClasses = Flux::classes()
|
||||
// When using the outline icon variant, we need to size it down to match the default icon sizes...
|
||||
->add($iconVariant === 'outline' ? 'size-5' : '')
|
||||
;
|
||||
|
||||
$inputLoadingClasses = Flux::classes()
|
||||
// When loading, we need to add some extra padding to the input to account for the loading icon...
|
||||
->add(match ($countOfTrailingIcons) {
|
||||
0 => 'pe-10',
|
||||
1 => 'pe-16',
|
||||
2 => 'pe-23',
|
||||
3 => 'pe-30',
|
||||
4 => 'pe-37',
|
||||
5 => 'pe-44',
|
||||
6 => 'pe-51',
|
||||
})
|
||||
;
|
||||
|
||||
$classes = Flux::classes()
|
||||
->add('w-full border rounded-lg block disabled:shadow-none dark:shadow-none')
|
||||
->add('appearance-none') // Without this, input[type="date"] on mobile doesn't respect w-full...
|
||||
->add(match ($size) {
|
||||
default => 'text-base sm:text-sm py-2 h-10 leading-[1.375rem]', // This makes the height of the input 40px (same as buttons and such...)
|
||||
'sm' => 'text-sm py-1.5 h-8 leading-[1.125rem]',
|
||||
'xs' => 'text-xs py-1.5 h-6 leading-[1.125rem]',
|
||||
})
|
||||
->add(match ($hasLeadingIcon) {
|
||||
true => 'ps-10',
|
||||
false => 'ps-3',
|
||||
})
|
||||
->add(match ($countOfTrailingIcons) {
|
||||
// Make sure there's enough padding on the right side of the input to account for all the icons...
|
||||
0 => 'pe-3',
|
||||
1 => 'pe-10',
|
||||
2 => 'pe-16',
|
||||
3 => 'pe-23',
|
||||
4 => 'pe-30',
|
||||
5 => 'pe-37',
|
||||
6 => 'pe-44',
|
||||
})
|
||||
->add(match ($variant) { // Background...
|
||||
'outline' => 'bg-bg-surface disabled:bg-bg-elevated',
|
||||
'filled' => 'bg-bg-elevated disabled:bg-bg-surface',
|
||||
})
|
||||
->add(match ($variant) { // Text color
|
||||
'outline' => 'text-text-primary disabled:text-text-disabled placeholder-text-disabled disabled:placeholder-text-disabled/70',
|
||||
'filled' => 'text-text-primary placeholder-text-tertiary disabled:placeholder-text-disabled',
|
||||
})
|
||||
->add(match ($variant) { // Border...
|
||||
'outline' => 'shadow-none border-border-default disabled:border-border-subtle focus:border-orange-primary focus:ring-1 focus:ring-orange-primary/30',
|
||||
'filled' => 'border-0',
|
||||
})
|
||||
->add(match ($variant) { // Invalid...
|
||||
'outline' => 'data-invalid:shadow-none data-invalid:border-red-500 dark:data-invalid:border-red-500 disabled:data-invalid:border-red-500 dark:disabled:data-invalid:border-red-500',
|
||||
'filled' => 'data-invalid:border-red-500'
|
||||
})
|
||||
->add($attributes->pluck('class:input'))
|
||||
;
|
||||
@endphp
|
||||
|
||||
<?php if ($type === 'file'): ?>
|
||||
<flux:with-field :$attributes :$name>
|
||||
<flux:input.file :$attributes :$name :$size />
|
||||
</flux:with-field>
|
||||
<?php elseif ($as !== 'button'): ?>
|
||||
<flux:with-field :$attributes :$name>
|
||||
<div {{ $attributes->only('class')->class('w-full relative block group/input') }} data-flux-input>
|
||||
<?php if (is_string($iconLeading)): ?>
|
||||
<div class="pointer-events-none absolute top-0 bottom-0 border-s border-transparent flex items-center justify-center text-xs text-text-tertiary ps-3 start-0">
|
||||
<flux:icon :icon="$iconLeading" :variant="$iconVariant" :class="$iconClasses" />
|
||||
</div>
|
||||
<?php elseif ($iconLeading): ?>
|
||||
<div {{ $iconLeading->attributes->class('absolute top-0 bottom-0 border-s border-transparent flex items-center justify-center text-xs text-text-tertiary ps-3 start-0') }}>
|
||||
{{ $iconLeading }}
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
|
||||
<input
|
||||
type="{{ $type }}"
|
||||
{{-- Leave file inputs unstyled... --}}
|
||||
{{ $attributes->except('class')->class($type === 'file' ? '' : $classes) }}
|
||||
@isset ($name) name="{{ $name }}" @endisset
|
||||
@if ($maskDynamic) x-mask:dynamic="{{ $maskDynamic }}" @elseif ($mask) x-mask="{{ $mask }}" @endif
|
||||
@if ($invalid) aria-invalid="true" data-invalid @endif
|
||||
@if (is_numeric($size)) size="{{ $size }}" @endif
|
||||
data-flux-control
|
||||
data-flux-group-target
|
||||
@if ($loading) wire:loading.class="{{ $inputLoadingClasses }}" @endif
|
||||
@if ($loading && $wireTarget) wire:target="{{ $wireTarget }}" @endif
|
||||
>
|
||||
|
||||
<?php if ($loading || $countOfTrailingIcons > 0): ?>
|
||||
<div class="absolute top-0 bottom-0 flex items-center gap-x-1.5 pe-2 border-e border-transparent end-0 text-xs text-zinc-400">
|
||||
{{-- Icon should be text-zinc-400/75 --}}
|
||||
<?php if ($loading): ?>
|
||||
<flux:icon name="loading" :variant="$iconVariant" :class="$iconClasses" wire:loading :wire:target="$wireTarget" />
|
||||
<?php endif; ?>
|
||||
|
||||
<?php if ($clearable): ?>
|
||||
<flux:input.clearable inset="left right" :$size />
|
||||
<?php endif; ?>
|
||||
|
||||
<?php if ($kbd): ?>
|
||||
<span class="pointer-events-none">{{ $kbd }}</span>
|
||||
<?php endif; ?>
|
||||
|
||||
<?php if ($expandable): ?>
|
||||
<flux:input.expandable inset="left right" :$size />
|
||||
<?php endif; ?>
|
||||
|
||||
<?php if ($copyable): ?>
|
||||
<flux:input.copyable inset="left right" :$size />
|
||||
<?php endif; ?>
|
||||
|
||||
<?php if ($viewable): ?>
|
||||
<flux:input.viewable inset="left right" :$size />
|
||||
<?php endif; ?>
|
||||
|
||||
<?php if (is_string($iconTrailing)): ?>
|
||||
<?php
|
||||
$trailingIconClasses = clone $iconClasses;
|
||||
$trailingIconClasses->add('text-zinc-400/75 dark:text-white/60 pointer-events-none');
|
||||
?>
|
||||
<flux:icon :icon="$iconTrailing" :variant="$iconVariant" :class="$trailingIconClasses" />
|
||||
<?php elseif ($iconTrailing): ?>
|
||||
{{ $iconTrailing }}
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
</flux:with-field>
|
||||
<?php else: ?>
|
||||
<button {{ $attributes->merge(['type' => 'button'])->class([$classes, 'w-full relative flex']) }}>
|
||||
<?php if (is_string($iconLeading)): ?>
|
||||
<div class="absolute top-0 bottom-0 flex items-center justify-center text-xs text-zinc-400/75 ps-3 start-0">
|
||||
<flux:icon :icon="$iconLeading" :variant="$iconVariant" :class="$iconClasses" />
|
||||
</div>
|
||||
<?php elseif ($iconLeading): ?>
|
||||
<div {{ $iconLeading->attributes->class('absolute top-0 bottom-0 flex items-center justify-center text-xs text-zinc-400/75 ps-3 start-0') }}>
|
||||
{{ $iconLeading }}
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
|
||||
<?php if ($attributes->has('placeholder')): ?>
|
||||
<div class="block self-center text-start flex-1 font-medium text-zinc-400 dark:text-white/40">
|
||||
{{ $attributes->get('placeholder') }}
|
||||
</div>
|
||||
<?php else: ?>
|
||||
<div class="text-start self-center flex-1 font-medium text-zinc-800 dark:text-white">
|
||||
{{ $slot }}
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
|
||||
<?php if ($kbd): ?>
|
||||
<div class="absolute top-0 bottom-0 flex items-center justify-center text-xs text-zinc-400/75 pe-4 end-0">
|
||||
{{ $kbd }}
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
|
||||
<?php if (is_string($iconTrailing)): ?>
|
||||
<div class="absolute top-0 bottom-0 flex items-center justify-center text-xs text-zinc-400/75 pe-3 end-0">
|
||||
<flux:icon :icon="$iconTrailing" :variant="$iconVariant" :class="$iconClasses" />
|
||||
</div>
|
||||
<?php elseif ($iconTrailing): ?>
|
||||
<div {{ $iconTrailing->attributes->class('absolute top-0 bottom-0 flex items-center justify-center text-xs text-zinc-400/75 pe-2 end-0') }}>
|
||||
{{ $iconTrailing }}
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
</button>
|
||||
<?php endif; ?>
|
||||
37
resources/views/flux/input/viewable.blade.php
Normal file
37
resources/views/flux/input/viewable.blade.php
Normal file
@@ -0,0 +1,37 @@
|
||||
@blaze
|
||||
|
||||
@php
|
||||
$attributes = $attributes->merge([
|
||||
'variant' => 'subtle',
|
||||
'class' => '-me-1',
|
||||
'square' => true,
|
||||
'size' => null,
|
||||
]);
|
||||
@endphp
|
||||
|
||||
<flux:button
|
||||
:$attributes
|
||||
:size="$size === 'sm' || $size === 'xs' ? 'xs' : 'sm'"
|
||||
x-data="{ open: false }"
|
||||
x-on:click="open = ! open; $el.closest('[data-flux-input]').querySelector('input').setAttribute('type', open ? 'text' : 'password')"
|
||||
x-bind:data-viewable-open="open"
|
||||
aria-label="{{ __('Toggle password visibility') }}"
|
||||
|
||||
{{-- We need to make the input type "durable" (immune to Livewire morph manipulations): --}}
|
||||
x-init="
|
||||
let input = $el.closest('[data-flux-input]')?.querySelector('input');
|
||||
|
||||
if (! input) return;
|
||||
|
||||
let observer = new MutationObserver(() => {
|
||||
let type = open ? 'text' : 'password';
|
||||
if (input.getAttribute('type') === type) return;
|
||||
input.setAttribute('type', type)
|
||||
});
|
||||
|
||||
observer.observe(input, { attributes: true, attributeFilter: ['type'] });
|
||||
"
|
||||
>
|
||||
<flux:icon.eye-slash variant="micro" class="hidden [[data-viewable-open]>&]:block" />
|
||||
<flux:icon.eye variant="micro" class="block [[data-viewable-open]>&]:hidden" />
|
||||
</flux:button>
|
||||
Reference in New Issue
Block a user