Add custom component

This commit is contained in:
Lance Pioch 2024-07-03 12:13:46 -04:00
parent a9c7eeddde
commit 0f798e5edb
4 changed files with 340 additions and 25 deletions

View File

@ -0,0 +1,10 @@
<?php
namespace App\Filament\Forms;
use Filament\Forms\Components\Select;
class SelectEndpoint extends Select
{
protected string $view = 'filament.components.select';
}

View File

@ -2,6 +2,7 @@
namespace App\Filament\Resources\ServerResource\Pages;
use App\Filament\Forms\SelectEndpoint;
use App\Filament\Resources\ServerResource;
use App\Models\Egg;
use App\Models\Node;
@ -320,38 +321,19 @@ class CreateServer extends CreateRecord
->columns(4)
->schema([
Forms\Components\TagsInput::make('ports')
->columnSpanFull()
->columnSpan(2)
->hintIcon('tabler-question-mark')
->hintIconTooltip('Ports are limited from 1025 to 65535')
->placeholder('Example: 25565, 8080, 1337-1340')
->splitKeys(['Tab', ' ', ','])
->helperText(new HtmlString('
These are the ports that users can connect to this Server through.
You would typically port forward these on your home network.
'))
These are the ports that users can connect to this Server through.
You would typically port forward these on your home network.
'))
->label('Ports')
->afterStateUpdated(self::ports(...))
->live(),
Forms\Components\Repeater::make('ip')
->columnSpan(2)
->defaultItems(5)
->label('IP Assignments')
->live()
->addable(false)
->deletable(false)
->reorderable(false)
->hintIcon('tabler-question-mark')
->hintIconTooltip('These are the IPs available on the selected Node.')
->simple(
Forms\Components\Select::make('port')
->live()
->placeholder('Select an IP')
// ->afterStateUpdated()
->options(fn () => $this->node?->ipAddresses())
->required(),
),
Forms\Components\Repeater::make('assignments')
->columnSpan(2)
->defaultItems(fn () => count($this->eggDefaultPorts))
@ -372,11 +354,13 @@ class CreateServer extends CreateRecord
->deletable(false)
->reorderable(false)
->simple(
Forms\Components\Select::make('port')
SelectEndpoint::make('port')
->columnSpanFull()
->label('')
->live()
->placeholder('Select a Port')
->disabled(fn (Forms\Get $get) => empty($get('../../ports')) || empty($get('../../assignments')))
->prefix(function (Forms\Components\Component $component) {
->suffix(function (Forms\Components\Component $component) {
$key = str($component->getStatePath())->beforeLast('.')->afterLast('.')->toString();
return $key;

View File

@ -0,0 +1,223 @@
@props([
'alpineDisabled' => null,
'alpineValid' => null,
'disabled' => false,
'inlinePrefix' => false,
'inlineSuffix' => false,
'prefix' => null,
'prefixActions' => [],
'prefixIcon' => null,
'prefixIconColor' => 'gray',
'prefixIconAlias' => null,
'suffix' => null,
'suffixActions' => [],
'suffixIcon' => null,
'suffixIconColor' => 'gray',
'suffixIconAlias' => null,
'valid' => true,
])
@php
$prefixActions = array_filter(
$prefixActions,
fn (\Filament\Forms\Components\Actions\Action $prefixAction): bool => $prefixAction->isVisible(),
);
$suffixActions = array_filter(
$suffixActions,
fn (\Filament\Forms\Components\Actions\Action $suffixAction): bool => $suffixAction->isVisible(),
);
$hasPrefix = count($prefixActions) || $prefixIcon || filled($prefix);
$hasSuffix = count($suffixActions) || $suffixIcon || filled($suffix);
$hasAlpineDisabledClasses = filled($alpineDisabled);
$hasAlpineValidClasses = filled($alpineValid);
$hasAlpineClasses = $hasAlpineDisabledClasses || $hasAlpineValidClasses;
$enabledWrapperClasses = 'bg-white dark:bg-white/5 [&:not(:has(.fi-ac-action:focus))]:focus-within:ring-2';
$disabledWrapperClasses = 'fi-disabled bg-gray-50 dark:bg-transparent';
$validWrapperClasses = 'ring-gray-950/10';
$invalidWrapperClasses = 'fi-invalid ring-danger-600 dark:ring-danger-500';
$enabledValidWrapperClasses = 'dark:ring-white/20 [&:not(:has(.fi-ac-action:focus))]:focus-within:ring-primary-600 dark:[&:not(:has(.fi-ac-action:focus))]:focus-within:ring-primary-500';
$enabledInvalidWrapperClasses = '[&:not(:has(.fi-ac-action:focus))]:focus-within:ring-danger-600 dark:[&:not(:has(.fi-ac-action:focus))]:focus-within:ring-danger-500';
$disabledValidWrapperClasses = 'dark:ring-white/10';
$actionsClasses = 'flex items-center gap-3';
$labelClasses = 'fi-input-wrp-label whitespace-nowrap text-sm text-gray-500 dark:text-gray-400';
$getIconClasses = fn (string | array $color = 'gray'): string => \Illuminate\Support\Arr::toCssClasses([
'fi-input-wrp-icon h-5 w-5',
match ($color) {
'gray' => 'text-gray-400 dark:text-gray-500',
default => 'text-custom-500',
},
]);
$getIconStyles = fn (string | array $color = 'gray'): string => \Illuminate\Support\Arr::toCssStyles([
\Filament\Support\get_color_css_variables(
$color,
shades: [500],
alias: 'input-wrapper.icon',
) => $color !== 'gray',
]);
$wireTarget = $attributes->whereStartsWith(['wire:target'])->first();
$hasLoadingIndicator = filled($wireTarget);
if ($hasLoadingIndicator) {
$loadingIndicatorTarget = html_entity_decode($wireTarget, ENT_QUOTES);
}
@endphp
<div
@if ($hasAlpineClasses)
x-bind:class="{
{{ $hasAlpineDisabledClasses ? "'{$enabledWrapperClasses}': ! ({$alpineDisabled})," : null }}
{{ $hasAlpineDisabledClasses ? "'{$disabledWrapperClasses}': {$alpineDisabled}," : null }}
{{ $hasAlpineValidClasses ? "'{$validWrapperClasses}': {$alpineValid}," : null }}
{{ $hasAlpineValidClasses ? "'{$invalidWrapperClasses}': ! ({$alpineValid})," : null }}
{{ ($hasAlpineDisabledClasses && $hasAlpineValidClasses) ? "'{$enabledValidWrapperClasses}': ! ({$alpineDisabled}) && {$alpineValid}," : null }}
{{ ($hasAlpineDisabledClasses && $hasAlpineValidClasses) ? "'{$enabledInvalidWrapperClasses}': ! ({$alpineDisabled}) && ! ({$alpineValid})," : null }}
{{ ($hasAlpineDisabledClasses && $hasAlpineValidClasses) ? "'{$disabledValidWrapperClasses}': {$alpineDisabled} && ! ({$alpineValid})," : null }}
}"
@endif
{{
$attributes
->except(['wire:target'])
->class([
'fi-input-wrp flex rounded-lg shadow-sm ring-1 transition duration-75',
$enabledWrapperClasses => (! $hasAlpineClasses) && (! $disabled),
$disabledWrapperClasses => (! $hasAlpineClasses) && $disabled,
$validWrapperClasses => (! $hasAlpineClasses) && $valid,
$invalidWrapperClasses => (! $hasAlpineClasses) && (! $valid),
$enabledValidWrapperClasses => (! $hasAlpineClasses) && (! $disabled) && $valid,
$enabledInvalidWrapperClasses => (! $hasAlpineClasses) && (! $disabled) && (! $valid),
$disabledValidWrapperClasses => (! $hasAlpineClasses) && $disabled && $valid,
])
}}
>
@php $hasPrefix = true; @endphp
<div
@class([
'items-center gap-x-3 ps-3',
'flex' => $hasPrefix,
'hidden' => ! $hasPrefix,
'pe-1' => $inlinePrefix && filled($prefix),
'pe-2' => $inlinePrefix && blank($prefix),
'border-e border-gray-200 pe-3 ps-3 dark:border-white/10' => ! $inlinePrefix,
])
>
@if (count($prefixActions))
<div class="{{ $actionsClasses }}">
@foreach ($prefixActions as $prefixAction)
{{ $prefixAction }}
@endforeach
</div>
@endif
@if ($prefixIcon)
<x-filament::icon
:attributes="
\Filament\Support\prepare_inherited_attributes(
new \Illuminate\View\ComponentAttributeBag([
'alias' => $prefixIconAlias,
'icon' => $prefixIcon,
'wire:loading.remove.delay.' . config('filament.livewire_loading_delay', 'default') => $hasLoadingIndicator,
'wire:target' => $hasLoadingIndicator ? $loadingIndicatorTarget : null,
])
)
->class([$getIconClasses($prefixIconColor)])
->style([$getIconStyles($prefixIconColor)])
"
/>
@endif
@if ($hasLoadingIndicator)
<x-filament::loading-indicator
:attributes="
\Filament\Support\prepare_inherited_attributes(
new \Illuminate\View\ComponentAttributeBag([
'wire:loading.delay.' . config('filament.livewire_loading_delay', 'default') => $hasPrefix,
'wire:target' => $hasPrefix ? $loadingIndicatorTarget : null,
])
)->class([$getIconClasses()])
"
/>
@endif
<span class="{{ $labelClasses }}">
<x-filament::input.select>
@foreach ((\App\Models\Node::find(6))->ipAddresses() as $ip)
<option value="{{ $ip }}">{{ $ip }}</option>
@endforeach
</x-filament::input.select>
</span>
</div>
<div
@class([
'items-center gap-x-3 ps-3',
'flex' => $hasPrefix,
'pe-1' => $inlinePrefix && filled($prefix),
'pe-2' => $inlinePrefix && blank($prefix),
'border-e border-gray-200 pe-3 ps-3 dark:border-white/10' => ! $inlinePrefix,
])
>
<span class="{{ $labelClasses }}">:</span>
</div>
<div
@if ($hasLoadingIndicator && (! $hasPrefix))
@if ($inlinePrefix)
wire:loading.delay.{{ config('filament.livewire_loading_delay', 'default') }}.class.remove="ps-3"
@endif
wire:target="{{ $loadingIndicatorTarget }}"
@endif
@class([
'min-w-0 flex-1',
'ps-3' => $hasLoadingIndicator && (! $hasPrefix) && $inlinePrefix,
])
>
{{ $slot }}
</div>
@if ($hasSuffix)
<div
@class([
'flex items-center gap-x-3 pe-3',
'ps-1' => $inlineSuffix && filled($suffix),
'ps-2' => $inlineSuffix && blank($suffix),
'border-s border-gray-200 ps-3 dark:border-white/10' => ! $inlineSuffix,
])
>
@if (filled($suffix))
<span class="{{ $labelClasses }}">
{{ $suffix }}
</span>
@endif
@if ($suffixIcon)
<x-filament::icon
:alias="$suffixIconAlias"
:icon="$suffixIcon"
:class="$getIconClasses($suffixIconColor)"
:style="$getIconStyles($suffixIconColor)"
/>
@endif
@if (count($suffixActions))
<div class="{{ $actionsClasses }}">
@foreach ($suffixActions as $suffixAction)
{{ $suffixAction }}
@endforeach
</div>
@endif
</div>
@endif
</div>

View File

@ -0,0 +1,98 @@
@php
use Filament\Support\Facades\FilamentView;
$canSelectPlaceholder = $canSelectPlaceholder();
$isDisabled = $isDisabled();
$isPrefixInline = $isPrefixInline();
$isSuffixInline = $isSuffixInline();
$prefixActions = $getPrefixActions();
$prefixIcon = $getPrefixIcon();
$prefixLabel = $getPrefixLabel();
$suffixActions = $getSuffixActions();
$suffixIcon = $getSuffixIcon();
$suffixLabel = $getSuffixLabel();
$statePath = $getStatePath();
@endphp
<x-dynamic-component
:component="$getFieldWrapperView()"
:field="$field"
:inline-label-vertical-alignment="\Filament\Support\Enums\VerticalAlignment::Center"
>
<x-filament.wrapper
:disabled="$isDisabled"
:inline-prefix="$isPrefixInline"
:inline-suffix="$isSuffixInline"
:prefix="$prefixLabel"
:prefix-actions="$prefixActions"
:prefix-icon="$prefixIcon"
:prefix-icon-color="$getPrefixIconColor()"
:suffix="$suffixLabel"
:suffix-actions="$suffixActions"
:suffix-icon="$suffixIcon"
:suffix-icon-color="$getSuffixIconColor()"
:valid="! $errors->has($statePath)"
:attributes="
\Filament\Support\prepare_inherited_attributes($getExtraAttributeBag())
->class(['fi-fo-select'])
"
>
<x-filament::input.select
:autofocus="$isAutofocused()"
:disabled="$isDisabled"
:id="$getId()"
:inline-prefix="$isPrefixInline && (count($prefixActions) || $prefixIcon || filled($prefixLabel))"
:inline-suffix="$isSuffixInline && (count($suffixActions) || $suffixIcon || filled($suffixLabel))"
:required="$isRequired() && (! $isConcealed())"
:attributes="
$getExtraInputAttributeBag()
->merge([
$applyStateBindingModifiers('wire:model') => $statePath,
], escape: false)
"
>
@php
$isHtmlAllowed = $isHtmlAllowed();
@endphp
@if ($canSelectPlaceholder)
<option value="">
@if (! $isDisabled)
{{ $getPlaceholder() }}
@endif
</option>
@endif
@foreach ($getOptions() as $value => $label)
@if (is_array($label))
<optgroup label="{{ $value }}">
@foreach ($label as $groupedValue => $groupedLabel)
<option
@disabled($isOptionDisabled($groupedValue, $groupedLabel))
value="{{ $groupedValue }}"
>
@if ($isHtmlAllowed)
{!! $groupedLabel !!}
@else
{{ $groupedLabel }}
@endif
</option>
@endforeach
</optgroup>
@else
<option
@disabled($isOptionDisabled($value, $label))
value="{{ $value }}"
>
@if ($isHtmlAllowed)
{!! $label !!}
@else
{{ $label }}
@endif
</option>
@endif
@endforeach
</x-filament::input.select>
</x-filament.wrapper>
</x-dynamic-component>