[增添]添加了datasource的setting数据库以及默认值

This commit is contained in:
makotocc0107
2024-08-27 09:57:44 +08:00
parent d111dfaea4
commit 72eb990970
10955 changed files with 978898 additions and 0 deletions

View File

@@ -0,0 +1,34 @@
@php
use Filament\Support\Enums\VerticalAlignment;
$verticalAlignment = $getVerticalAlignment();
if (! $verticalAlignment instanceof VerticalAlignment) {
$verticalAlignment = filled($verticalAlignment) ? (VerticalAlignment::tryFrom($verticalAlignment) ?? $verticalAlignment) : null;
}
@endphp
<div
{{
$attributes
->merge([
'id' => $getId(),
], escape: false)
->merge($getExtraAttributes(), escape: false)
->class([
'fi-fo-actions flex h-full flex-col',
match ($verticalAlignment) {
VerticalAlignment::Start => 'justify-start',
VerticalAlignment::Center => 'justify-center',
VerticalAlignment::End => 'justify-end',
default => $verticalAlignment,
},
])
}}
>
<x-filament::actions
:actions="$getChildComponentContainer()->getComponents()"
:alignment="$getAlignment()"
:full-width="$isFullWidth()"
/>
</div>

View File

@@ -0,0 +1,5 @@
@foreach ($getActions() as $action)
@if ($action->isVisible())
{{ $action }}
@endif
@endforeach

View File

@@ -0,0 +1,300 @@
@php
use Filament\Forms\Components\Actions\Action;
$containers = $getChildComponentContainers();
$blockPickerBlocks = $getBlockPickerBlocks();
$blockPickerColumns = $getBlockPickerColumns();
$blockPickerWidth = $getBlockPickerWidth();
$hasBlockPreviews = $hasBlockPreviews();
$hasInteractiveBlockPreviews = $hasInteractiveBlockPreviews();
$addAction = $getAction($getAddActionName());
$addBetweenAction = $getAction($getAddBetweenActionName());
$cloneAction = $getAction($getCloneActionName());
$collapseAllAction = $getAction($getCollapseAllActionName());
$editAction = $getAction($getEditActionName());
$expandAllAction = $getAction($getExpandAllActionName());
$deleteAction = $getAction($getDeleteActionName());
$moveDownAction = $getAction($getMoveDownActionName());
$moveUpAction = $getAction($getMoveUpActionName());
$reorderAction = $getAction($getReorderActionName());
$extraItemActions = $getExtraItemActions();
$isAddable = $isAddable();
$isCloneable = $isCloneable();
$isCollapsible = $isCollapsible();
$isDeletable = $isDeletable();
$isReorderableWithButtons = $isReorderableWithButtons();
$isReorderableWithDragAndDrop = $isReorderableWithDragAndDrop();
$collapseAllActionIsVisible = $isCollapsible && $collapseAllAction->isVisible();
$expandAllActionIsVisible = $isCollapsible && $expandAllAction->isVisible();
$statePath = $getStatePath();
@endphp
<x-dynamic-component :component="$getFieldWrapperView()" :field="$field">
<div
x-data="{}"
{{
$attributes
->merge($getExtraAttributes(), escape: false)
->class(['fi-fo-builder grid gap-y-4'])
}}
>
@if ($collapseAllActionIsVisible || $expandAllActionIsVisible)
<div
@class([
'flex gap-x-3',
'hidden' => count($containers) < 2,
])
>
@if ($collapseAllActionIsVisible)
<span
x-on:click="$dispatch('builder-collapse', '{{ $statePath }}')"
>
{{ $collapseAllAction }}
</span>
@endif
@if ($expandAllActionIsVisible)
<span
x-on:click="$dispatch('builder-expand', '{{ $statePath }}')"
>
{{ $expandAllAction }}
</span>
@endif
</div>
@endif
@if (count($containers))
<ul
x-sortable
data-sortable-animation-duration="{{ $getReorderAnimationDuration() }}"
wire:end.stop="{{ 'mountFormComponentAction(\'' . $statePath . '\', \'reorder\', { items: $event.target.sortable.toArray() })' }}"
class="space-y-4"
>
@php
$hasBlockLabels = $hasBlockLabels();
$hasBlockNumbers = $hasBlockNumbers();
@endphp
@foreach ($containers as $uuid => $item)
@php
$visibleExtraItemActions = array_filter(
$extraItemActions,
fn (Action $action): bool => $action(['item' => $uuid])->isVisible(),
);
$cloneAction = $cloneAction(['item' => $uuid]);
$cloneActionIsVisible = $isCloneable && $cloneAction->isVisible();
$deleteAction = $deleteAction(['item' => $uuid]);
$deleteActionIsVisible = $isDeletable && $deleteAction->isVisible();
$editAction = $editAction(['item' => $uuid]);
$editActionIsVisible = $hasBlockPreviews && $editAction->isVisible();
$moveDownAction = $moveDownAction(['item' => $uuid])->disabled($loop->last);
$moveDownActionIsVisible = $isReorderableWithButtons && $moveDownAction->isVisible();
$moveUpAction = $moveUpAction(['item' => $uuid])->disabled($loop->first);
$moveUpActionIsVisible = $isReorderableWithButtons && $moveUpAction->isVisible();
$reorderActionIsVisible = $isReorderableWithDragAndDrop && $reorderAction->isVisible();
@endphp
<li
wire:key="{{ $this->getId() }}.{{ $item->getStatePath() }}.{{ $field::class }}.item"
x-data="{
isCollapsed: @js($isCollapsed($item)),
}"
x-on:builder-expand.window="$event.detail === '{{ $statePath }}' && (isCollapsed = false)"
x-on:builder-collapse.window="$event.detail === '{{ $statePath }}' && (isCollapsed = true)"
x-on:expand="isCollapsed = false"
x-sortable-item="{{ $uuid }}"
class="fi-fo-builder-item rounded-xl bg-white shadow-sm ring-1 ring-gray-950/5 dark:bg-white/5 dark:ring-white/10"
x-bind:class="{ 'fi-collapsed overflow-hidden': isCollapsed }"
>
@if ($reorderActionIsVisible || $moveUpActionIsVisible || $moveDownActionIsVisible || $hasBlockLabels || $editActionIsVisible || $cloneActionIsVisible || $deleteActionIsVisible || $isCollapsible || $visibleExtraItemActions)
<div
@if ($isCollapsible)
x-on:click.stop="isCollapsed = !isCollapsed"
@endif
@class([
'fi-fo-builder-item-header flex items-center gap-x-3 overflow-hidden px-4 py-3',
'cursor-pointer select-none' => $isCollapsible,
])
>
@if ($reorderActionIsVisible || $moveUpActionIsVisible || $moveDownActionIsVisible)
<ul class="flex items-center gap-x-3">
@if ($reorderActionIsVisible)
<li
x-sortable-handle
x-on:click.stop
>
{{ $reorderAction }}
</li>
@endif
@if ($moveUpActionIsVisible || $moveDownActionIsVisible)
<li x-on:click.stop>
{{ $moveUpAction }}
</li>
<li x-on:click.stop>
{{ $moveDownAction }}
</li>
@endif
</ul>
@endif
@if ($hasBlockLabels)
<h4
@class([
'text-sm font-medium text-gray-950 dark:text-white',
'truncate' => $isBlockLabelTruncated(),
])
>
{{ $item->getParentComponent()->getLabel($item->getRawState(), $uuid) }}
@if ($hasBlockNumbers)
{{ $loop->iteration }}
@endif
</h4>
@endif
@if ($editActionIsVisible || $cloneActionIsVisible || $deleteActionIsVisible || $isCollapsible || $visibleExtraItemActions)
<ul
class="ms-auto flex items-center gap-x-3"
>
@foreach ($visibleExtraItemActions as $extraItemAction)
<li x-on:click.stop>
{{ $extraItemAction(['item' => $uuid]) }}
</li>
@endforeach
@if ($editActionIsVisible)
<li x-on:click.stop>
{{ $editAction }}
</li>
@endif
@if ($cloneActionIsVisible)
<li x-on:click.stop>
{{ $cloneAction }}
</li>
@endif
@if ($deleteActionIsVisible)
<li x-on:click.stop>
{{ $deleteAction }}
</li>
@endif
@if ($isCollapsible)
<li
class="relative transition"
x-on:click.stop="isCollapsed = !isCollapsed"
x-bind:class="{ '-rotate-180': isCollapsed }"
>
<div
class="transition"
x-bind:class="{ 'opacity-0 pointer-events-none': isCollapsed }"
>
{{ $getAction('collapse') }}
</div>
<div
class="absolute inset-0 rotate-180 transition"
x-bind:class="{ 'opacity-0 pointer-events-none': ! isCollapsed }"
>
{{ $getAction('expand') }}
</div>
</li>
@endif
</ul>
@endif
</div>
@endif
<div
x-show="! isCollapsed"
@class([
'fi-fo-builder-item-content relative border-t border-gray-100 dark:border-white/10',
'p-4' => ! $hasBlockPreviews,
])
>
@if ($hasBlockPreviews)
<div
@class([
'fi-fo-builder-item-preview',
'pointer-events-none' => ! $hasInteractiveBlockPreviews,
])
>
{{ $item->getParentComponent()->renderPreview($item->getRawState()) }}
</div>
@if ($editActionIsVisible && (! $hasInteractiveBlockPreviews))
<div
class="absolute inset-0 z-[1] cursor-pointer"
role="button"
x-on:click.stop="{{ '$wire.mountFormComponentAction(\'' . $statePath . '\', \'edit\', { item: \'' . $uuid . '\' })' }}"
></div>
@endif
@else
{{ $item }}
@endif
</div>
</li>
@if (! $loop->last)
@if ($isAddable && $addBetweenAction(['afterItem' => $uuid])->isVisible())
<li class="relative -top-2 !mt-0 h-0">
<div
class="flex w-full justify-center opacity-0 transition duration-75 hover:opacity-100"
>
<div
class="fi-fo-builder-block-picker-ctn rounded-lg bg-white dark:bg-gray-900"
>
<x-filament-forms::builder.block-picker
:action="$addBetweenAction"
:after-item="$uuid"
:columns="$blockPickerColumns"
:blocks="$blockPickerBlocks"
:state-path="$statePath"
:width="$blockPickerWidth"
>
<x-slot name="trigger">
{{ $addBetweenAction(['afterItem' => $uuid]) }}
</x-slot>
</x-filament-forms::builder.block-picker>
</div>
</div>
</li>
@elseif (filled($labelBetweenItems = $getLabelBetweenItems()))
<li
class="relative border-t border-gray-200 dark:border-white/10"
>
<span
class="absolute -top-3 left-3 px-1 text-sm font-medium"
>
{{ $labelBetweenItems }}
</span>
</li>
@endif
@endif
@endforeach
</ul>
@endif
@if ($isAddable && $addAction->isVisible())
<x-filament-forms::builder.block-picker
:action="$addAction"
:blocks="$blockPickerBlocks"
:columns="$blockPickerColumns"
:state-path="$statePath"
:width="$blockPickerWidth"
class="flex justify-center"
>
<x-slot name="trigger">
{{ $addAction }}
</x-slot>
</x-filament-forms::builder.block-picker>
@endif
</div>
</x-dynamic-component>

View File

@@ -0,0 +1,52 @@
@props([
'action',
'afterItem' => null,
'blocks',
'columns' => null,
'statePath',
'trigger',
'width' => null,
])
<x-filament::dropdown
:width="$width"
{{ $attributes->class(['fi-fo-builder-block-picker']) }}
>
<x-slot name="trigger">
{{ $trigger }}
</x-slot>
<x-filament::dropdown.list>
<x-filament::grid
:default="$columns['default'] ?? 1"
:sm="$columns['sm'] ?? null"
:md="$columns['md'] ?? null"
:lg="$columns['lg'] ?? null"
:xl="$columns['xl'] ?? null"
:two-xl="$columns['2xl'] ?? null"
direction="column"
>
@foreach ($blocks as $block)
@php
$wireClickActionArguments = ['block' => $block->getName()];
if (filled($afterItem)) {
$wireClickActionArguments['afterItem'] = $afterItem;
}
$wireClickActionArguments = \Illuminate\Support\Js::from($wireClickActionArguments);
$wireClickAction = "mountFormComponentAction('{$statePath}', '{$action->getName()}', {$wireClickActionArguments})";
@endphp
<x-filament::dropdown.list.item
:icon="$block->getIcon()"
x-on:click="close"
:wire:click="$wireClickAction"
>
{{ $block->getLabel() }}
</x-filament::dropdown.list.item>
@endforeach
</x-filament::grid>
</x-filament::dropdown.list>
</x-filament::dropdown>

View File

@@ -0,0 +1,231 @@
@php
$gridDirection = $getGridDirection() ?? 'column';
$isBulkToggleable = $isBulkToggleable();
$isDisabled = $isDisabled();
$isSearchable = $isSearchable();
$statePath = $getStatePath();
@endphp
<x-dynamic-component :component="$getFieldWrapperView()" :field="$field">
<div
x-data="{
areAllCheckboxesChecked: false,
checkboxListOptions: Array.from(
$root.querySelectorAll('.fi-fo-checkbox-list-option-label'),
),
search: '',
visibleCheckboxListOptions: [],
checkIfAllCheckboxesAreChecked: function () {
this.areAllCheckboxesChecked =
this.visibleCheckboxListOptions.length ===
this.visibleCheckboxListOptions.filter((checkboxLabel) =>
checkboxLabel.querySelector('input[type=checkbox]:checked'),
).length
},
toggleAllCheckboxes: function () {
state = ! this.areAllCheckboxesChecked
this.visibleCheckboxListOptions.forEach((checkboxLabel) => {
checkbox = checkboxLabel.querySelector('input[type=checkbox]')
checkbox.checked = state
checkbox.dispatchEvent(new Event('change'))
})
this.areAllCheckboxesChecked = state
},
updateVisibleCheckboxListOptions: function () {
this.visibleCheckboxListOptions = this.checkboxListOptions.filter(
(checkboxListItem) => {
if (
checkboxListItem
.querySelector('.fi-fo-checkbox-list-option-label')
?.innerText.toLowerCase()
.includes(this.search.toLowerCase())
) {
return true
}
return checkboxListItem
.querySelector('.fi-fo-checkbox-list-option-description')
?.innerText.toLowerCase()
.includes(this.search.toLowerCase())
},
)
},
}"
x-init="
updateVisibleCheckboxListOptions()
$nextTick(() => {
checkIfAllCheckboxesAreChecked()
})
Livewire.hook('commit', ({ component, commit, succeed, fail, respond }) => {
succeed(({ snapshot, effect }) => {
$nextTick(() => {
if (component.id !== @js($this->getId())) {
return
}
checkboxListOptions = Array.from(
$root.querySelectorAll('.fi-fo-checkbox-list-option-label'),
)
updateVisibleCheckboxListOptions()
checkIfAllCheckboxesAreChecked()
})
})
})
$watch('search', () => {
updateVisibleCheckboxListOptions()
checkIfAllCheckboxesAreChecked()
})
"
>
@if (! $isDisabled)
@if ($isSearchable)
<x-filament::input.wrapper
inline-prefix
prefix-icon="heroicon-m-magnifying-glass"
prefix-icon-alias="forms:components.checkbox-list.search-field"
class="mb-4"
>
<x-filament::input
inline-prefix
:placeholder="$getSearchPrompt()"
type="search"
:attributes="
\Filament\Support\prepare_inherited_attributes(
new \Illuminate\View\ComponentAttributeBag([
'x-model.debounce.' . $getSearchDebounce() => 'search',
])
)
"
/>
</x-filament::input.wrapper>
@endif
@if ($isBulkToggleable && count($getOptions()))
<div
x-cloak
class="mb-2"
wire:key="{{ $this->getId() }}.{{ $getStatePath() }}.{{ $field::class }}.actions"
>
<span
x-show="! areAllCheckboxesChecked"
x-on:click="toggleAllCheckboxes()"
wire:key="{{ $this->getId() }}.{{ $statePath }}.{{ $field::class }}.actions.select-all"
>
{{ $getAction('selectAll') }}
</span>
<span
x-show="areAllCheckboxesChecked"
x-on:click="toggleAllCheckboxes()"
wire:key="{{ $this->getId() }}.{{ $statePath }}.{{ $field::class }}.actions.deselect-all"
>
{{ $getAction('deselectAll') }}
</span>
</div>
@endif
@endif
<x-filament::grid
:default="$getColumns('default')"
:sm="$getColumns('sm')"
:md="$getColumns('md')"
:lg="$getColumns('lg')"
:xl="$getColumns('xl')"
:two-xl="$getColumns('2xl')"
:direction="$gridDirection"
:x-show="$isSearchable ? 'visibleCheckboxListOptions.length' : null"
:attributes="
\Filament\Support\prepare_inherited_attributes($attributes)
->merge($getExtraAttributes(), escape: false)
->class([
'fi-fo-checkbox-list gap-4',
'-mt-4' => $gridDirection === 'column',
])
"
>
@forelse ($getOptions() as $value => $label)
<div
wire:key="{{ $this->getId() }}.{{ $statePath }}.{{ $field::class }}.options.{{ $value }}"
@if ($isSearchable)
x-show="
$el
.querySelector('.fi-fo-checkbox-list-option-label')
?.innerText.toLowerCase()
.includes(search.toLowerCase()) ||
$el
.querySelector('.fi-fo-checkbox-list-option-description')
?.innerText.toLowerCase()
.includes(search.toLowerCase())
"
@endif
@class([
'break-inside-avoid pt-4' => $gridDirection === 'column',
])
>
<label
class="fi-fo-checkbox-list-option-label flex gap-x-3"
>
<x-filament::input.checkbox
:valid="! $errors->has($statePath)"
:attributes="
\Filament\Support\prepare_inherited_attributes($getExtraInputAttributeBag())
->merge([
'disabled' => $isDisabled || $isOptionDisabled($value, $label),
'value' => $value,
'wire:loading.attr' => 'disabled',
$applyStateBindingModifiers('wire:model') => $statePath,
'x-on:change' => $isBulkToggleable ? 'checkIfAllCheckboxesAreChecked()' : null,
], escape: false)
->class(['mt-1'])
"
/>
<div class="grid text-sm leading-6">
<span
class="fi-fo-checkbox-list-option-label overflow-hidden break-words font-medium text-gray-950 dark:text-white"
>
{{ $label }}
</span>
@if ($hasDescription($value))
<p
class="fi-fo-checkbox-list-option-description text-gray-500 dark:text-gray-400"
>
{{ $getDescription($value) }}
</p>
@endif
</div>
</label>
</div>
@empty
<div
wire:key="{{ $this->getId() }}.{{ $statePath }}.{{ $field::class }}.empty"
></div>
@endforelse
</x-filament::grid>
@if ($isSearchable)
<div
x-cloak
x-show="search && ! visibleCheckboxListOptions.length"
class="fi-fo-checkbox-list-no-search-results-message text-sm text-gray-500 dark:text-gray-400"
>
{{ $getNoSearchResultsMessage() }}
</div>
@endif
</div>
</x-dynamic-component>

View File

@@ -0,0 +1,36 @@
@php
$statePath = $getStatePath();
@endphp
<x-dynamic-component
:component="$getFieldWrapperView()"
:field="$field"
:inline-label-vertical-alignment="\Filament\Support\Enums\VerticalAlignment::Center"
>
@capture($content)
<x-filament::input.checkbox
:valid="! $errors->has($statePath)"
:attributes="
$attributes
->merge([
'autofocus' => $isAutofocused(),
'disabled' => $isDisabled(),
'id' => $getId(),
'required' => $isRequired() && (! $isConcealed()),
'wire:loading.attr' => 'disabled',
$applyStateBindingModifiers('wire:model') => $statePath,
], escape: false)
->merge($getExtraAttributes(), escape: false)
->merge($getExtraInputAttributes(), escape: false)
"
/>
@endcapture
@if ($isInline())
<x-slot name="labelPrefix">
{{ $content() }}
</x-slot>
@else
{{ $content() }}
@endif
</x-dynamic-component>

View File

@@ -0,0 +1,118 @@
@php
use Filament\Support\Facades\FilamentView;
$isDisabled = $isDisabled();
$isLive = $isLive();
$isLiveOnBlur = $isLiveOnBlur();
$isLiveDebounced = $isLiveDebounced();
$isPrefixInline = $isPrefixInline();
$isSuffixInline = $isSuffixInline();
$liveDebounce = $getLiveDebounce();
$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::input.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-color-picker')
"
>
<div
x-ignore
@if (FilamentView::hasSpaMode())
ax-load="visible"
@else
ax-load
@endif
ax-load-src="{{ \Filament\Support\Facades\FilamentAsset::getAlpineComponentSrc('color-picker', 'filament/forms') }}"
x-data="colorPickerFormComponent({
isAutofocused: @js($isAutofocused()),
isDisabled: @js($isDisabled),
isLive: @js($isLive),
isLiveDebounced: @js($isLiveDebounced),
isLiveOnBlur: @js($isLiveOnBlur),
liveDebounce: @js($liveDebounce),
state: $wire.$entangle('{{ $statePath }}'),
})"
x-on:keydown.esc="isOpen() && $event.stopPropagation()"
{{ $getExtraAlpineAttributeBag()->class(['flex']) }}
>
<x-filament::input
x-on:focus="$refs.panel.open($refs.input)"
x-on:keydown.enter.stop.prevent="togglePanelVisibility()"
x-ref="input"
:attributes="
\Filament\Support\prepare_inherited_attributes($getExtraInputAttributeBag())
->merge([
'autocomplete' => 'off',
'disabled' => $isDisabled,
'id' => $getId(),
'inlinePrefix' => $isPrefixInline && (count($prefixActions) || $prefixIcon || filled($prefixLabel)),
'inlineSuffix' => $isSuffixInline && (count($suffixActions) || $suffixIcon || filled($suffixLabel)),
'placeholder' => $getPlaceholder(),
'required' => $isRequired() && (! $isConcealed()),
'type' => 'text',
'x-model' . ($isLiveDebounced ? '.debounce.' . $liveDebounce : null) => 'state',
'x-on:blur' => $isLiveOnBlur ? 'isOpen() ? null : commitState()' : null,
], escape: false)
"
/>
<div
class="flex min-h-full items-center pe-3"
x-on:click="togglePanelVisibility()"
>
<div
class="h-5 w-5 select-none rounded-full"
x-bind:class="{
'ring-1 ring-inset ring-gray-200 dark:ring-white/10': ! state,
}"
x-bind:style="{ 'background-color': state }"
></div>
</div>
<div
wire:ignore.self
wire:key="{{ $this->getId() }}.{{ $statePath }}.{{ $field::class }}.panel"
x-cloak
x-float.placement.bottom-start.offset.flip.shift="{ offset: 8 }"
x-ref="panel"
class="fi-fo-color-picker-panel absolute z-10 hidden rounded-lg shadow-lg"
>
@php
$tag = match ($getFormat()) {
'hsl' => 'hsl-string',
'rgb' => 'rgb-string',
'rgba' => 'rgba-string',
default => 'hex',
} . '-color-picker';
@endphp
<{{ $tag }} color="{{ $getState() }}" />
</div>
</div>
</x-filament::input.wrapper>
</x-dynamic-component>

View File

@@ -0,0 +1,293 @@
@php
use Filament\Support\Facades\FilamentView;
$datalistOptions = $getDatalistOptions();
$extraAlpineAttributes = $getExtraAlpineAttributes();
$hasTime = $hasTime();
$id = $getId();
$isDisabled = $isDisabled();
$isPrefixInline = $isPrefixInline();
$isSuffixInline = $isSuffixInline();
$maxDate = $getMaxDate();
$minDate = $getMinDate();
$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::input.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())"
>
@if ($isNative())
<x-filament::input
:attributes="
\Filament\Support\prepare_inherited_attributes($getExtraInputAttributeBag())
->merge($extraAlpineAttributes, escape: false)
->merge([
'autofocus' => $isAutofocused(),
'disabled' => $isDisabled,
'id' => $id,
'inlinePrefix' => $isPrefixInline && (count($prefixActions) || $prefixIcon || filled($prefixLabel)),
'inlineSuffix' => $isSuffixInline && (count($suffixActions) || $suffixIcon || filled($suffixLabel)),
'list' => $datalistOptions ? $id . '-list' : null,
'max' => $hasTime ? $maxDate : ($maxDate ? \Carbon\Carbon::parse($maxDate)->toDateString() : null),
'min' => $hasTime ? $minDate : ($minDate ? \Carbon\Carbon::parse($minDate)->toDateString() : null),
'placeholder' => $getPlaceholder(),
'readonly' => $isReadOnly(),
'required' => $isRequired() && (! $isConcealed()),
'step' => $getStep(),
'type' => $getType(),
$applyStateBindingModifiers('wire:model') => $statePath,
'x-data' => count($extraAlpineAttributes) ? '{}' : null,
], escape: false)
"
/>
@else
<div
x-ignore
@if (FilamentView::hasSpaMode())
ax-load="visible"
@else
ax-load
@endif
ax-load-src="{{ \Filament\Support\Facades\FilamentAsset::getAlpineComponentSrc('date-time-picker', 'filament/forms') }}"
x-data="dateTimePickerFormComponent({
displayFormat:
'{{ convert_date_format($getDisplayFormat())->to('day.js') }}',
firstDayOfWeek: {{ $getFirstDayOfWeek() }},
isAutofocused: @js($isAutofocused()),
locale: @js($getLocale()),
shouldCloseOnDateSelection: @js($shouldCloseOnDateSelection()),
state: $wire.{{ $applyStateBindingModifiers("\$entangle('{$statePath}')") }},
})"
x-on:keydown.esc="isOpen() && $event.stopPropagation()"
{{
$attributes
->merge($getExtraAttributes(), escape: false)
->merge($getExtraAlpineAttributes(), escape: false)
->class(['fi-fo-date-time-picker'])
}}
>
<input x-ref="maxDate" type="hidden" value="{{ $maxDate }}" />
<input x-ref="minDate" type="hidden" value="{{ $minDate }}" />
<input
x-ref="disabledDates"
type="hidden"
value="{{ json_encode($getDisabledDates()) }}"
/>
<button
x-ref="button"
x-on:click="togglePanelVisibility()"
x-on:keydown.enter.stop.prevent="
if (! $el.disabled) {
isOpen() ? selectDate() : togglePanelVisibility()
}
"
x-on:keydown.arrow-left.stop.prevent="if (! $el.disabled) focusPreviousDay()"
x-on:keydown.arrow-right.stop.prevent="if (! $el.disabled) focusNextDay()"
x-on:keydown.arrow-up.stop.prevent="if (! $el.disabled) focusPreviousWeek()"
x-on:keydown.arrow-down.stop.prevent="if (! $el.disabled) focusNextWeek()"
x-on:keydown.backspace.stop.prevent="if (! $el.disabled) clearState()"
x-on:keydown.clear.stop.prevent="if (! $el.disabled) clearState()"
x-on:keydown.delete.stop.prevent="if (! $el.disabled) clearState()"
aria-label="{{ $getPlaceholder() }}"
type="button"
tabindex="-1"
@disabled($isDisabled)
{{
$getExtraTriggerAttributeBag()->class([
'w-full',
])
}}
>
<input
@disabled($isDisabled)
readonly
placeholder="{{ $getPlaceholder() }}"
wire:key="{{ $this->getId() }}.{{ $statePath }}.{{ $field::class }}.display-text"
x-model="displayText"
@if ($id = $getId()) id="{{ $id }}" @endif
@class([
'fi-fo-date-time-picker-display-text-input w-full border-none bg-transparent px-3 py-1.5 text-base text-gray-950 outline-none transition duration-75 placeholder:text-gray-400 focus:ring-0 disabled:text-gray-500 disabled:[-webkit-text-fill-color:theme(colors.gray.500)] dark:text-white dark:placeholder:text-gray-500 dark:disabled:text-gray-400 dark:disabled:[-webkit-text-fill-color:theme(colors.gray.400)] sm:text-sm sm:leading-6',
])
/>
</button>
<div
x-ref="panel"
x-cloak
x-float.placement.bottom-start.offset.flip.shift="{ offset: 8 }"
wire:ignore
wire:key="{{ $this->getId() }}.{{ $statePath }}.{{ $field::class }}.panel"
@class([
'fi-fo-date-time-picker-panel absolute z-10 rounded-lg bg-white p-4 shadow-lg ring-1 ring-gray-950/5 dark:bg-gray-900 dark:ring-white/10',
])
>
<div class="grid gap-y-3">
@if ($hasDate())
<div class="flex items-center justify-between">
<select
x-model="focusedMonth"
class="grow cursor-pointer border-none bg-transparent p-0 text-sm font-medium text-gray-950 focus:ring-0 dark:bg-gray-900 dark:text-white"
>
<template
x-for="(month, index) in months"
>
<option
x-bind:value="index"
x-text="month"
></option>
</template>
</select>
<input
type="number"
inputmode="numeric"
x-model.debounce="focusedYear"
class="w-16 border-none bg-transparent p-0 text-right text-sm text-gray-950 focus:ring-0 dark:text-white"
/>
</div>
<div class="grid grid-cols-7 gap-1">
<template
x-for="(day, index) in dayLabels"
x-bind:key="index"
>
<div
x-text="day"
class="text-center text-xs font-medium text-gray-500 dark:text-gray-400"
></div>
</template>
</div>
<div
role="grid"
class="grid grid-cols-[repeat(7,minmax(theme(spacing.7),1fr))] gap-1"
>
<template
x-for="day in emptyDaysInFocusedMonth"
x-bind:key="day"
>
<div></div>
</template>
<template
x-for="day in daysInFocusedMonth"
x-bind:key="day"
>
<div
x-text="day"
x-on:click="dayIsDisabled(day) || selectDate(day)"
x-on:mouseenter="setFocusedDay(day)"
role="option"
x-bind:aria-selected="focusedDate.date() === day"
x-bind:class="{
'text-gray-950 dark:text-white': ! dayIsToday(day) && ! dayIsSelected(day),
'cursor-pointer': ! dayIsDisabled(day),
'text-primary-600 dark:text-primary-400':
dayIsToday(day) &&
! dayIsSelected(day) &&
focusedDate.date() !== day &&
! dayIsDisabled(day),
'bg-gray-50 dark:bg-white/5':
focusedDate.date() === day &&
! dayIsSelected(day) &&
! dayIsDisabled(day),
'text-primary-600 bg-gray-50 dark:bg-white/5 dark:text-primary-400':
dayIsSelected(day),
'pointer-events-none': dayIsDisabled(day),
'opacity-50': dayIsDisabled(day),
}"
class="rounded-full text-center text-sm leading-loose transition duration-75"
></div>
</template>
</div>
@endif
@if ($hasTime)
<div
class="flex items-center justify-center rtl:flex-row-reverse"
>
<input
max="23"
min="0"
step="{{ $getHoursStep() }}"
type="number"
inputmode="numeric"
x-model.debounce="hour"
class="me-1 w-10 border-none bg-transparent p-0 text-center text-sm text-gray-950 focus:ring-0 dark:text-white"
/>
<span
class="text-sm font-medium text-gray-500 dark:text-gray-400"
>
:
</span>
<input
max="59"
min="0"
step="{{ $getMinutesStep() }}"
type="number"
inputmode="numeric"
x-model.debounce="minute"
class="me-1 w-10 border-none bg-transparent p-0 text-center text-sm text-gray-950 focus:ring-0 dark:text-white"
/>
@if ($hasSeconds())
<span
class="text-sm font-medium text-gray-500 dark:text-gray-400"
>
:
</span>
<input
max="59"
min="0"
step="{{ $getSecondsStep() }}"
type="number"
inputmode="numeric"
x-model.debounce="second"
class="me-1 w-10 border-none bg-transparent p-0 text-center text-sm text-gray-950 focus:ring-0 dark:text-white"
/>
@endif
</div>
@endif
</div>
</div>
</div>
@endif
</x-filament::input.wrapper>
@if ($datalistOptions)
<datalist id="{{ $id }}-list">
@foreach ($datalistOptions as $option)
<option value="{{ $option }}" />
@endforeach
</datalist>
@endif
</x-dynamic-component>

View File

@@ -0,0 +1,10 @@
<p
data-validation-error
{{
$attributes->class([
'fi-fo-field-wrp-error-message text-sm text-danger-600 dark:text-danger-400',
])
}}
>
{{ $slot }}
</p>

View File

@@ -0,0 +1,5 @@
<div
{{ $attributes->class(['fi-fo-field-wrp-helper-text text-sm text-gray-500']) }}
>
{{ $slot }}
</div>

View File

@@ -0,0 +1,66 @@
@props([
'actions' => [],
'color' => 'gray',
'icon' => null,
'tooltip' => null,
])
<div
{{
$attributes->class([
'fi-fo-field-wrp-hint flex items-center gap-x-3 text-sm',
])
}}
>
@if (! \Filament\Support\is_slot_empty($slot))
<span
@class([
'fi-fo-field-wrp-hint-label',
match ($color) {
'gray' => 'text-gray-500',
default => 'fi-color-custom text-custom-600 dark:text-custom-400',
},
is_string($color) ? "fi-color-{$color}" : null,
])
@style([
\Filament\Support\get_color_css_variables(
$color,
shades: [400, 600],
alias: 'forms::components.field-wrapper.hint.label',
),
])
>
{{ $slot }}
</span>
@endif
@if ($icon)
<x-filament::icon
x-data="{}"
:icon="$icon"
:x-tooltip="filled($tooltip) ? '{ content: ' . \Illuminate\Support\Js::from($tooltip) . ', theme: $store.theme }' : null"
@class([
'fi-fo-field-wrp-hint-icon h-5 w-5',
match ($color) {
'gray' => 'text-gray-400 dark:text-gray-500',
default => 'text-custom-500 dark:text-custom-400',
},
])
@style([
\Filament\Support\get_color_css_variables(
$color,
shades: [400, 500],
alias: 'forms::components.field-wrapper.hint.icon',
),
])
/>
@endif
@if (count($actions))
<div class="fi-fo-field-wrp-hint-action flex items-center gap-3">
@foreach ($actions as $action)
{{ $action }}
@endforeach
</div>
@endif
</div>

View File

@@ -0,0 +1,136 @@
@php
use Filament\Support\Enums\VerticalAlignment;
@endphp
@props([
'field' => null,
'hasInlineLabel' => null,
'hasNestedRecursiveValidationRules' => null,
'helperText' => null,
'hint' => null,
'hintActions' => null,
'hintColor' => null,
'hintIcon' => null,
'hintIconTooltip' => null,
'id' => null,
'inlineLabelVerticalAlignment' => VerticalAlignment::Start,
'isDisabled' => null,
'label' => null,
'labelPrefix' => null,
'labelSrOnly' => null,
'labelSuffix' => null,
'required' => null,
'statePath' => null,
])
@php
if ($field) {
$hasInlineLabel ??= $field->hasInlineLabel();
$hasNestedRecursiveValidationRules ??= $field instanceof \Filament\Forms\Components\Contracts\HasNestedRecursiveValidationRules;
$helperText ??= $field->getHelperText();
$hint ??= $field->getHint();
$hintActions ??= $field->getHintActions();
$hintColor ??= $field->getHintColor();
$hintIcon ??= $field->getHintIcon();
$hintIconTooltip ??= $field->getHintIconTooltip();
$id ??= $field->getId();
$isDisabled ??= $field->isDisabled();
$label ??= $field->getLabel();
$labelSrOnly ??= $field->isLabelHidden();
$required ??= $field->isMarkedAsRequired();
$statePath ??= $field->getStatePath();
}
$hintActions = array_filter(
$hintActions ?? [],
fn (\Filament\Forms\Components\Actions\Action $hintAction): bool => $hintAction->isVisible(),
);
$hasError = filled($statePath) && ($errors->has($statePath) || ($hasNestedRecursiveValidationRules && $errors->has("{$statePath}.*")));
@endphp
<div
data-field-wrapper
{{
$attributes
->merge($field?->getExtraFieldWrapperAttributes() ?? [])
->class(['fi-fo-field-wrp'])
}}
>
@if ($label && $labelSrOnly)
<label for="{{ $id }}" class="sr-only">
{{ $label }}
</label>
@endif
<div
@class([
'grid gap-y-2',
'sm:grid-cols-3 sm:gap-x-4' => $hasInlineLabel,
match ($inlineLabelVerticalAlignment) {
VerticalAlignment::Start => 'sm:items-start',
VerticalAlignment::Center => 'sm:items-center',
VerticalAlignment::End => 'sm:items-end',
} => $hasInlineLabel,
])
>
@if (($label && (! $labelSrOnly)) || $labelPrefix || $labelSuffix || filled($hint) || $hintIcon || count($hintActions))
<div
@class([
'flex items-center justify-between gap-x-3',
($label instanceof \Illuminate\View\ComponentSlot) ? $label->attributes->get('class') : null,
])
>
@if ($label && (! $labelSrOnly))
<x-filament-forms::field-wrapper.label
:for="$id"
:disabled="$isDisabled"
:prefix="$labelPrefix"
:required="$required"
:suffix="$labelSuffix"
>
{{ $label }}
</x-filament-forms::field-wrapper.label>
@elseif ($labelPrefix)
{{ $labelPrefix }}
@elseif ($labelSuffix)
{{ $labelSuffix }}
@endif
@if (filled($hint) || $hintIcon || count($hintActions))
<x-filament-forms::field-wrapper.hint
:actions="$hintActions"
:color="$hintColor"
:icon="$hintIcon"
:tooltip="$hintIconTooltip"
>
{{ $hint }}
</x-filament-forms::field-wrapper.hint>
@endif
</div>
@endif
@if ((! \Filament\Support\is_slot_empty($slot)) || $hasError || filled($helperText))
<div
@class([
'grid gap-y-2',
'sm:col-span-2' => $hasInlineLabel,
])
>
{{ $slot }}
@if ($hasError)
<x-filament-forms::field-wrapper.error-message>
{{ $errors->has($statePath) ? $errors->first($statePath) : ($hasNestedRecursiveValidationRules ? $errors->first("{$statePath}.*") : null) }}
</x-filament-forms::field-wrapper.error-message>
@endif
@if (filled($helperText))
<x-filament-forms::field-wrapper.helper-text>
{{ $helperText }}
</x-filament-forms::field-wrapper.helper-text>
@endif
</div>
@endif
</div>
</div>

View File

@@ -0,0 +1,20 @@
@props([
'disabled' => false,
'prefix' => null,
'required' => false,
'suffix' => null,
])
<label
{{ $attributes->class(['fi-fo-field-wrp-label inline-flex items-center gap-x-3']) }}
>
{{ $prefix }}
<span class="text-sm font-medium leading-6 text-gray-950 dark:text-white">
{{-- Deliberately poor formatting to ensure that the asterisk sticks to the final word in the label. --}}
{{ $slot }}@if ($required && (! $disabled))<sup class="text-danger-600 dark:text-danger-400 font-medium">*</sup>
@endif
</span>
{{ $suffix }}
</label>

View File

@@ -0,0 +1,13 @@
<x-filament::fieldset
:label="$getLabel()"
:label-hidden="$isLabelHidden()"
:attributes="
\Filament\Support\prepare_inherited_attributes($attributes)
->merge([
'id' => $getId(),
], escape: false)
->merge($getExtraAttributes(), escape: false)
"
>
{{ $getChildComponentContainer() }}
</x-filament::fieldset>

View File

@@ -0,0 +1,330 @@
@php
use Filament\Support\Enums\Alignment;
use Filament\Support\Facades\FilamentView;
$imageCropAspectRatio = $getImageCropAspectRatio();
$imageResizeTargetHeight = $getImageResizeTargetHeight();
$imageResizeTargetWidth = $getImageResizeTargetWidth();
$isAvatar = $isAvatar();
$statePath = $getStatePath();
$isDisabled = $isDisabled();
$hasImageEditor = $hasImageEditor();
$hasCircleCropper = $hasCircleCropper();
$alignment = $getAlignment() ?? Alignment::Start;
if (! $alignment instanceof Alignment) {
$alignment = filled($alignment) ? (Alignment::tryFrom($alignment) ?? $alignment) : null;
}
@endphp
<x-dynamic-component
:component="$getFieldWrapperView()"
:field="$field"
:label-sr-only="$isLabelHidden()"
>
<div
@if (FilamentView::hasSpaMode())
ax-load="visible"
@else
ax-load
@endif
ax-load-src="{{ \Filament\Support\Facades\FilamentAsset::getAlpineComponentSrc('file-upload', 'filament/forms') }}"
x-data="fileUploadFormComponent({
acceptedFileTypes: @js($getAcceptedFileTypes()),
imageEditorEmptyFillColor: @js($getImageEditorEmptyFillColor()),
imageEditorMode: @js($getImageEditorMode()),
imageEditorViewportHeight: @js($getImageEditorViewportHeight()),
imageEditorViewportWidth: @js($getImageEditorViewportWidth()),
deleteUploadedFileUsing: async (fileKey) => {
return await $wire.deleteUploadedFile(@js($statePath), fileKey)
},
getUploadedFilesUsing: async () => {
return await $wire.getFormUploadedFiles(@js($statePath))
},
hasImageEditor: @js($hasImageEditor),
hasCircleCropper: @js($hasCircleCropper),
canEditSvgs: @js($canEditSvgs()),
isSvgEditingConfirmed: @js($isSvgEditingConfirmed()),
confirmSvgEditingMessage: @js(__('filament-forms::components.file_upload.editor.svg.messages.confirmation')),
disabledSvgEditingMessage: @js(__('filament-forms::components.file_upload.editor.svg.messages.disabled')),
imageCropAspectRatio: @js($imageCropAspectRatio),
imagePreviewHeight: @js($getImagePreviewHeight()),
imageResizeMode: @js($getImageResizeMode()),
imageResizeTargetHeight: @js($imageResizeTargetHeight),
imageResizeTargetWidth: @js($imageResizeTargetWidth),
imageResizeUpscale: @js($getImageResizeUpscale()),
isAvatar: @js($isAvatar),
isDeletable: @js($isDeletable()),
isDisabled: @js($isDisabled),
isDownloadable: @js($isDownloadable()),
isMultiple: @js($isMultiple()),
isOpenable: @js($isOpenable()),
isPreviewable: @js($isPreviewable()),
isReorderable: @js($isReorderable()),
itemPanelAspectRatio: @js($getItemPanelAspectRatio()),
loadingIndicatorPosition: @js($getLoadingIndicatorPosition()),
locale: @js(app()->getLocale()),
panelAspectRatio: @js($getPanelAspectRatio()),
panelLayout: @js($getPanelLayout()),
placeholder: @js($getPlaceholder()),
maxFiles: @js($getMaxFiles()),
maxSize: @js(($size = $getMaxSize()) ? "{$size}KB" : null),
minSize: @js(($size = $getMinSize()) ? "{$size}KB" : null),
removeUploadedFileUsing: async (fileKey) => {
return await $wire.removeFormUploadedFile(@js($statePath), fileKey)
},
removeUploadedFileButtonPosition: @js($getRemoveUploadedFileButtonPosition()),
reorderUploadedFilesUsing: async (files) => {
return await $wire.reorderFormUploadedFiles(@js($statePath), files)
},
shouldAppendFiles: @js($shouldAppendFiles()),
shouldOrientImageFromExif: @js($shouldOrientImagesFromExif()),
shouldTransformImage: @js($imageCropAspectRatio || $imageResizeTargetHeight || $imageResizeTargetWidth),
state: $wire.{{ $applyStateBindingModifiers("\$entangle('{$statePath}')") }},
uploadButtonPosition: @js($getUploadButtonPosition()),
uploadingMessage: @js($getUploadingMessage()),
uploadProgressIndicatorPosition: @js($getUploadProgressIndicatorPosition()),
uploadUsing: (fileKey, file, success, error, progress) => {
$wire.upload(
`{{ $statePath }}.${fileKey}`,
file,
() => {
success(fileKey)
},
error,
(progressEvent) => {
progress(true, progressEvent.detail.progress, 100)
},
)
},
})"
wire:ignore
x-ignore
{{
$attributes
->merge([
'id' => $getId(),
], escape: false)
->merge($getExtraAttributes(), escape: false)
->merge($getExtraAlpineAttributes(), escape: false)
->class([
'fi-fo-file-upload flex [&_.filepond--root]:font-sans',
match ($alignment) {
Alignment::Start => 'justify-start',
Alignment::Center => 'justify-center',
Alignment::End => 'justify-end',
Alignment::Left => 'justify-left',
Alignment::Right => 'justify-right',
Alignment::Between, Alignment::Justify => 'justify-between',
default => $alignment,
},
])
}}
>
<div
@class([
'h-full',
'w-32' => $isAvatar,
'w-full' => ! $isAvatar,
])
>
<input
x-ref="input"
{{
$getExtraInputAttributeBag()
->merge([
'disabled' => $isDisabled,
'multiple' => $isMultiple(),
'type' => 'file',
], escape: false)
}}
/>
</div>
@if ($hasImageEditor && (! $isDisabled))
<div
x-show="isEditorOpen"
x-cloak
x-on:click.stop=""
x-trap.noscroll="isEditorOpen"
x-on:keydown.escape.window="closeEditor"
@class([
'fixed inset-0 isolate z-50 h-[100dvh] w-screen p-2 sm:p-10 md:p-20',
'fi-fo-file-upload-circle-cropper' => $hasCircleCropper,
])
>
<div
aria-hidden="true"
class="fixed inset-0 h-full w-full cursor-pointer bg-black/50"
style="will-change: transform"
></div>
<div
class="isolate z-10 flex h-full w-full items-center justify-center"
>
<div
class="mx-auto flex h-full w-full flex-col overflow-hidden rounded-xl bg-white ring-1 ring-gray-900/10 dark:bg-gray-800 dark:ring-gray-50/10 lg:flex-row"
>
<div class="w-full flex-1 overflow-auto p-4 lg:h-full">
<div class="h-full w-full">
<img x-ref="editor" class="h-full w-auto" />
</div>
</div>
<div
class="shadow-top z-[1] flex h-96 w-full flex-col overflow-auto bg-gray-50 dark:bg-gray-900/30 lg:h-full lg:max-w-xs lg:shadow-none"
>
<div class="flex-1 overflow-hidden">
<div
class="flex h-full flex-col overflow-y-auto"
>
<div class="flex-1 overflow-auto">
<div class="space-y-6 p-4">
<div class="w-full space-y-3">
@foreach ([
[
'label' => __('filament-forms::components.file_upload.editor.fields.x_position.label'),
'ref' => 'xPositionInput',
'unit' => __('filament-forms::components.file_upload.editor.fields.x_position.unit'),
'alpineSaveHandler' => 'editor.setData({...editor.getData(true), x: +$el.value})',
],
[
'label' => __('filament-forms::components.file_upload.editor.fields.y_position.label'),
'ref' => 'yPositionInput',
'unit' => __('filament-forms::components.file_upload.editor.fields.y_position.unit'),
'alpineSaveHandler' => 'editor.setData({...editor.getData(true), y: +$el.value})',
],
[
'label' => __('filament-forms::components.file_upload.editor.fields.width.label'),
'ref' => 'widthInput',
'unit' => __('filament-forms::components.file_upload.editor.fields.width.unit'),
'alpineSaveHandler' => 'editor.setData({...editor.getData(true), width: +$el.value})',
],
[
'label' => __('filament-forms::components.file_upload.editor.fields.height.label'),
'ref' => 'heightInput',
'unit' => __('filament-forms::components.file_upload.editor.fields.height.unit'),
'alpineSaveHandler' => 'editor.setData({...editor.getData(true), height: +$el.value})',
],
[
'label' => __('filament-forms::components.file_upload.editor.fields.rotation.label'),
'ref' => 'rotationInput',
'unit' => __('filament-forms::components.file_upload.editor.fields.rotation.unit'),
'alpineSaveHandler' => 'editor.rotateTo(+$el.value)',
],
] as $input)
<label
class="flex w-full items-center rounded-lg border border-gray-300 bg-gray-100 text-sm shadow-sm dark:border-gray-700 dark:bg-gray-800"
>
<span
class="flex w-20 shrink-0 items-center justify-center self-stretch border-e border-gray-300 px-2 dark:border-gray-700"
>
{{ $input['label'] }}
</span>
<input
@class([
'block w-full border-none text-sm transition duration-75 focus-visible:border-primary-500 focus-visible:ring-1 focus-visible:ring-inset focus-visible:ring-primary-500 disabled:opacity-70 dark:bg-gray-700 dark:text-white dark:focus-visible:border-primary-500',
])
x-on:keyup.enter.stop.prevent="{{ $input['alpineSaveHandler'] }}"
x-on:blur="{{ $input['alpineSaveHandler'] }}"
x-ref="{{ $input['ref'] }}"
x-on:keydown.enter.prevent
type="text"
/>
<span
class="flex w-16 items-center justify-center self-stretch border-s border-gray-300 px-2 dark:border-gray-700"
>
{{ $input['unit'] }}
</span>
</label>
@endforeach
</div>
<div class="space-y-3">
@foreach ($getImageEditorActions(iconSizeClasses: 'h-5 w-5 mx-auto') as $groupedActions)
<x-filament::button.group
class="w-full"
>
@foreach ($groupedActions as $action)
<x-filament::button
color="gray"
grouped
:icon="new \Illuminate\Support\HtmlString($action['iconHtml'])"
label-sr-only
x-on:click.stop.prevent="{{ $action['alpineClickHandler'] }}"
:x-tooltip="'{ content: ' . \Illuminate\Support\Js::from($action['label']) . ', theme: $store.theme }'"
>
{{ $action['label'] }}
</x-filament::button>
@endforeach
</x-filament::button.group>
@endforeach
</div>
@if (count($aspectRatios = $getImageEditorAspectRatiosForJs()))
<div class="space-y-3">
<div
class="text-xs text-gray-950 dark:text-white"
>
{{ __('filament-forms::components.file_upload.editor.aspect_ratios.label') }}
</div>
@foreach (collect($aspectRatios)->chunk(5) as $ratiosChunk)
<x-filament::button.group
class="w-full"
>
@foreach ($ratiosChunk as $label => $ratio)
<x-filament::button
:x-tooltip="'{ content: ' . \Illuminate\Support\Js::from(__('filament-forms::components.file_upload.editor.actions.set_aspect_ratio.label', ['ratio' => $label])) . ', theme: $store.theme }'"
x-on:click.stop.prevent="currentRatio = '{{ $label }}'; editor.setAspectRatio({{ $ratio }})"
color="gray"
x-bind:class="{'!bg-gray-50 dark:!bg-gray-700': currentRatio === '{{ $label }}'}"
grouped
>
{{ $label }}
</x-filament::button>
@endforeach
</x-filament::button.group>
@endforeach
</div>
@endif
</div>
</div>
<div
class="flex items-center gap-3 px-4 py-3"
>
<x-filament::button
color="gray"
x-on:click.prevent="pond.imageEditEditor.oncancel"
>
{{ __('filament-forms::components.file_upload.editor.actions.cancel.label') }}
</x-filament::button>
<x-filament::button
color="warning"
x-on:click.stop.prevent="editor.reset()"
class="ml-auto"
>
{{ __('filament-forms::components.file_upload.editor.actions.reset.label') }}
</x-filament::button>
<x-filament::button
color="success"
x-on:click.prevent="saveEditor"
>
{{ __('filament-forms::components.file_upload.editor.actions.save.label') }}
</x-filament::button>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
@endif
</div>
</x-dynamic-component>

View File

@@ -0,0 +1,11 @@
<div
{{
$attributes
->merge([
'id' => $getId(),
], escape: false)
->merge($getExtraAttributes(), escape: false)
}}
>
{{ $getChildComponentContainer() }}
</div>

View File

@@ -0,0 +1,11 @@
<div
{{
$attributes
->merge([
'id' => $getId(),
], escape: false)
->merge($getExtraAttributes(), escape: false)
}}
>
{{ $getChildComponentContainer() }}
</div>

View File

@@ -0,0 +1,12 @@
<input
{{
$attributes
->merge([
'id' => $getId(),
'type' => 'hidden',
$applyStateBindingModifiers('wire:model') => $getStatePath(),
], escape: false)
->merge($getExtraAttributes(), escape: false)
->class(['fi-fo-hidden'])
}}
/>

View File

@@ -0,0 +1,171 @@
@php
use Filament\Support\Facades\FilamentView;
$debounce = $getLiveDebounce();
$hasInlineLabel = $hasInlineLabel();
$isAddable = $isAddable();
$isDeletable = $isDeletable();
$isDisabled = $isDisabled();
$isReorderable = $isReorderable();
$statePath = $getStatePath();
@endphp
<x-dynamic-component
:component="$getFieldWrapperView()"
:field="$field"
:has-inline-label="$hasInlineLabel"
>
<x-slot
name="label"
@class([
'sm:pt-1.5' => $hasInlineLabel,
])
>
{{ $getLabel() }}
</x-slot>
<x-filament::input.wrapper
:disabled="$isDisabled"
:valid="! $errors->has($statePath)"
:attributes="
\Filament\Support\prepare_inherited_attributes($getExtraAttributeBag())
->class(['fi-fo-key-value'])
"
>
<div
@if (FilamentView::hasSpaMode())
ax-load="visible"
@else
ax-load
@endif
ax-load-src="{{ \Filament\Support\Facades\FilamentAsset::getAlpineComponentSrc('key-value', 'filament/forms') }}"
wire:ignore
x-data="keyValueFormComponent({
state: $wire.{{ $applyStateBindingModifiers("\$entangle('{$statePath}')") }},
})"
x-ignore
{{
$attributes
->merge($getExtraAlpineAttributes(), escape: false)
->class(['divide-y divide-gray-200 dark:divide-white/10'])
}}
>
<table
class="w-full table-auto divide-y divide-gray-200 dark:divide-white/5"
>
<thead>
<tr>
@if ($isReorderable && (! $isDisabled))
<th
scope="col"
x-show="rows.length"
class="w-9"
></th>
@endif
<th
scope="col"
class="px-3 py-2 text-start text-sm font-medium text-gray-700 dark:text-gray-200"
>
{{ $getKeyLabel() }}
</th>
<th
scope="col"
class="px-3 py-2 text-start text-sm font-medium text-gray-700 dark:text-gray-200"
>
{{ $getValueLabel() }}
</th>
@if ($isDeletable && (! $isDisabled))
<th
scope="col"
x-show="rows.length"
class="w-9"
></th>
@endif
</tr>
</thead>
<tbody
@if ($isReorderable)
x-on:end.stop="reorderRows($event)"
x-sortable
data-sortable-animation-duration="{{ $getReorderAnimationDuration() }}"
@endif
class="divide-y divide-gray-200 dark:divide-white/5"
>
<template
x-bind:key="index"
x-for="(row, index) in rows"
>
<tr
@if ($isReorderable)
x-bind:x-sortable-item="row.key"
@endif
class="divide-x divide-gray-200 dark:divide-white/5 rtl:divide-x-reverse"
>
@if ($isReorderable && (! $isDisabled))
<td class="p-0.5">
<div x-sortable-handle class="flex">
{{ $getAction('reorder') }}
</div>
</td>
@endif
<td class="w-1/2 p-0">
<x-filament::input
:disabled="(! $canEditKeys()) || $isDisabled"
:placeholder="filled($placeholder = $getKeyPlaceholder()) ? $placeholder : null"
type="text"
x-model="row.key"
:attributes="
\Filament\Support\prepare_inherited_attributes(
new \Illuminate\View\ComponentAttributeBag([
'x-on:input.debounce.' . ($debounce ?? '500ms') => 'updateState',
])
)
"
class="font-mono"
/>
</td>
<td class="w-1/2 p-0">
<x-filament::input
:disabled="(! $canEditValues()) || $isDisabled"
:placeholder="filled($placeholder = $getValuePlaceholder()) ? $placeholder : null"
type="text"
x-model="row.value"
:attributes="
\Filament\Support\prepare_inherited_attributes(
new \Illuminate\View\ComponentAttributeBag([
'x-on:input.debounce.' . ($debounce ?? '500ms') => 'updateState',
])
)
"
class="font-mono"
/>
</td>
@if ($isDeletable && (! $isDisabled))
<td class="p-0.5">
<div x-on:click="deleteRow(index)">
{{ $getAction('delete') }}
</div>
</td>
@endif
</tr>
</template>
</tbody>
</table>
@if ($isAddable && (! $isDisabled))
<div class="flex justify-center px-3 py-2">
<span x-on:click="addRow" class="flex">
{{ $getAction('add') }}
</span>
</div>
@endif
</div>
</x-filament::input.wrapper>
</x-dynamic-component>

View File

@@ -0,0 +1,7 @@
<div>
@if (filled($key = $getKey()))
@livewire($getComponent(), $getComponentProperties(), key($key))
@else
@livewire($getComponent(), $getComponentProperties())
@endif
</div>

View File

@@ -0,0 +1,58 @@
@php
use Filament\Support\Facades\FilamentView;
$statePath = $getStatePath();
@endphp
<x-dynamic-component :component="$getFieldWrapperView()" :field="$field">
@if ($isDisabled())
<div
class="fi-fo-markdown-editor fi-disabled prose block w-full max-w-none rounded-lg bg-gray-50 px-3 py-3 text-gray-500 shadow-sm ring-1 ring-gray-950/10 dark:prose-invert dark:bg-transparent dark:text-gray-400 dark:ring-white/10 sm:text-sm"
>
{!! str($getState())->markdown()->sanitizeHtml() !!}
</div>
@else
<x-filament::input.wrapper
:valid="! $errors->has($statePath)"
:attributes="
\Filament\Support\prepare_inherited_attributes($getExtraAttributeBag())
->class(['fi-fo-markdown-editor max-w-full overflow-hidden font-mono text-base text-gray-950 dark:text-white sm:text-sm'])
"
>
<div
ax-load="visible"
ax-load-src="{{ \Filament\Support\Facades\FilamentAsset::getAlpineComponentSrc('markdown-editor', 'filament/forms') }}"
x-data="markdownEditorFormComponent({
canAttachFiles: @js($hasToolbarButton('attachFiles')),
isLiveDebounced: @js($isLiveDebounced()),
isLiveOnBlur: @js($isLiveOnBlur()),
liveDebounce: @js($getNormalizedLiveDebounce()),
maxHeight: @js($getMaxHeight()),
minHeight: @js($getMinHeight()),
placeholder: @js($getPlaceholder()),
state: $wire.{{ $applyStateBindingModifiers("\$entangle('{$statePath}')", isOptimisticallyLive: false) }},
toolbarButtons: @js($getToolbarButtons()),
translations: @js(__('filament-forms::components.markdown_editor')),
uploadFileAttachmentUsing: async (file, onSuccess, onError) => {
$wire.upload(`componentFileAttachments.{{ $statePath }}`, file, () => {
$wire
.getFormComponentFileAttachmentUrl('{{ $statePath }}')
.then((url) => {
if (! url) {
return onError()
}
onSuccess(url)
})
})
},
})"
x-ignore
wire:ignore
{{ $getExtraAlpineAttributeBag() }}
>
<textarea x-ref="editor" class="hidden"></textarea>
</div>
</x-filament::input.wrapper>
@endif
</x-dynamic-component>

View File

@@ -0,0 +1,24 @@
<x-dynamic-component
:component="$getFieldWrapperView()"
:has-inline-label="$hasInlineLabel()"
:id="$getId()"
:label="$getLabel()"
:label-sr-only="$isLabelHidden()"
:helper-text="$getHelperText()"
:hint="$getHint()"
:hint-actions="$getHintActions()"
:hint-color="$getHintColor()"
:hint-icon="$getHintIcon()"
:hint-icon-tooltip="$getHintIconTooltip()"
:state-path="$getStatePath()"
>
<div
{{
$attributes
->merge($getExtraAttributes(), escape: false)
->class(['fi-fo-placeholder text-sm leading-6'])
}}
>
{{ $getContent() }}
</div>
</x-dynamic-component>

View File

@@ -0,0 +1,67 @@
@php
$gridDirection = $getGridDirection() ?? 'column';
$id = $getId();
$isDisabled = $isDisabled();
$isInline = $isInline();
$statePath = $getStatePath();
@endphp
<x-dynamic-component :component="$getFieldWrapperView()" :field="$field">
<x-filament::grid
:default="$getColumns('default')"
:sm="$getColumns('sm')"
:md="$getColumns('md')"
:lg="$getColumns('lg')"
:xl="$getColumns('xl')"
:two-xl="$getColumns('2xl')"
:is-grid="! $isInline"
:direction="$gridDirection"
:attributes="
\Filament\Support\prepare_inherited_attributes($attributes)
->merge($getExtraAttributes(), escape: false)
->class([
'fi-fo-radio gap-4',
'-mt-4' => (! $isInline) && ($gridDirection === 'column'),
'flex flex-wrap' => $isInline,
])
"
>
@foreach ($getOptions() as $value => $label)
<div
@class([
'break-inside-avoid pt-4' => (! $isInline) && ($gridDirection === 'column'),
])
>
<label class="flex gap-x-3">
<x-filament::input.radio
:valid="! $errors->has($statePath)"
:attributes="
\Filament\Support\prepare_inherited_attributes($getExtraInputAttributeBag())
->merge([
'disabled' => $isDisabled || $isOptionDisabled($value, $label),
'id' => $id . '-' . $value,
'name' => $id,
'value' => $value,
'wire:loading.attr' => 'disabled',
$applyStateBindingModifiers('wire:model') => $statePath,
], escape: false)
->class(['mt-1'])
"
/>
<div class="grid text-sm leading-6">
<span class="font-medium text-gray-950 dark:text-white">
{{ $label }}
</span>
@if ($hasDescription($value))
<p class="text-gray-500 dark:text-gray-400">
{{ $getDescription($value) }}
</p>
@endif
</div>
</label>
</div>
@endforeach
</x-filament::grid>
</x-dynamic-component>

View File

@@ -0,0 +1,246 @@
@php
use Filament\Forms\Components\Actions\Action;
$containers = $getChildComponentContainers();
$addAction = $getAction($getAddActionName());
$addBetweenAction = $getAction($getAddBetweenActionName());
$cloneAction = $getAction($getCloneActionName());
$collapseAllAction = $getAction($getCollapseAllActionName());
$expandAllAction = $getAction($getExpandAllActionName());
$deleteAction = $getAction($getDeleteActionName());
$moveDownAction = $getAction($getMoveDownActionName());
$moveUpAction = $getAction($getMoveUpActionName());
$reorderAction = $getAction($getReorderActionName());
$extraItemActions = $getExtraItemActions();
$isAddable = $isAddable();
$isCloneable = $isCloneable();
$isCollapsible = $isCollapsible();
$isDeletable = $isDeletable();
$isReorderableWithButtons = $isReorderableWithButtons();
$isReorderableWithDragAndDrop = $isReorderableWithDragAndDrop();
$collapseAllActionIsVisible = $isCollapsible && $collapseAllAction->isVisible();
$expandAllActionIsVisible = $isCollapsible && $expandAllAction->isVisible();
$statePath = $getStatePath();
@endphp
<x-dynamic-component :component="$getFieldWrapperView()" :field="$field">
<div
x-data="{}"
{{
$attributes
->merge($getExtraAttributes(), escape: false)
->class(['fi-fo-repeater grid gap-y-4'])
}}
>
@if ($collapseAllActionIsVisible || $expandAllActionIsVisible)
<div
@class([
'flex gap-x-3',
'hidden' => count($containers) < 2,
])
>
@if ($collapseAllActionIsVisible)
<span
x-on:click="$dispatch('repeater-collapse', '{{ $statePath }}')"
>
{{ $collapseAllAction }}
</span>
@endif
@if ($expandAllActionIsVisible)
<span
x-on:click="$dispatch('repeater-expand', '{{ $statePath }}')"
>
{{ $expandAllAction }}
</span>
@endif
</div>
@endif
@if (count($containers))
<ul>
<x-filament::grid
:default="$getGridColumns('default')"
:sm="$getGridColumns('sm')"
:md="$getGridColumns('md')"
:lg="$getGridColumns('lg')"
:xl="$getGridColumns('xl')"
:two-xl="$getGridColumns('2xl')"
:wire:end.stop="'mountFormComponentAction(\'' . $statePath . '\', \'reorder\', { items: $event.target.sortable.toArray() })'"
x-sortable
:data-sortable-animation-duration="$getReorderAnimationDuration()"
class="items-start gap-4"
>
@foreach ($containers as $uuid => $item)
@php
$itemLabel = $getItemLabel($uuid);
$visibleExtraItemActions = array_filter(
$extraItemActions,
fn (Action $action): bool => $action(['item' => $uuid])->isVisible(),
);
$cloneAction = $cloneAction(['item' => $uuid]);
$cloneActionIsVisible = $isCloneable && $cloneAction->isVisible();
$deleteAction = $deleteAction(['item' => $uuid]);
$deleteActionIsVisible = $isDeletable && $deleteAction->isVisible();
$moveDownAction = $moveDownAction(['item' => $uuid])->disabled($loop->last);
$moveDownActionIsVisible = $isReorderableWithButtons && $moveDownAction->isVisible();
$moveUpAction = $moveUpAction(['item' => $uuid])->disabled($loop->first);
$moveUpActionIsVisible = $isReorderableWithButtons && $moveUpAction->isVisible();
$reorderActionIsVisible = $isReorderableWithDragAndDrop && $reorderAction->isVisible();
@endphp
<li
wire:key="{{ $this->getId() }}.{{ $item->getStatePath() }}.{{ $field::class }}.item"
x-data="{
isCollapsed: @js($isCollapsed($item)),
}"
x-on:expand="isCollapsed = false"
x-on:repeater-expand.window="$event.detail === '{{ $statePath }}' && (isCollapsed = false)"
x-on:repeater-collapse.window="$event.detail === '{{ $statePath }}' && (isCollapsed = true)"
x-sortable-item="{{ $uuid }}"
class="fi-fo-repeater-item divide-y divide-gray-100 rounded-xl bg-white shadow-sm ring-1 ring-gray-950/5 dark:divide-white/10 dark:bg-white/5 dark:ring-white/10"
x-bind:class="{ 'fi-collapsed overflow-hidden': isCollapsed }"
>
@if ($reorderActionIsVisible || $moveUpActionIsVisible || $moveDownActionIsVisible || filled($itemLabel) || $cloneActionIsVisible || $deleteActionIsVisible || $isCollapsible || $visibleExtraItemActions)
<div
@if ($isCollapsible)
x-on:click.stop="isCollapsed = !isCollapsed"
@endif
@class([
'fi-fo-repeater-item-header flex items-center gap-x-3 overflow-hidden px-4 py-3',
'cursor-pointer select-none' => $isCollapsible,
])
>
@if ($reorderActionIsVisible || $moveUpActionIsVisible || $moveDownActionIsVisible)
<ul class="flex items-center gap-x-3">
@if ($reorderActionIsVisible)
<li
x-sortable-handle
x-on:click.stop
>
{{ $reorderAction }}
</li>
@endif
@if ($moveUpActionIsVisible || $moveDownActionIsVisible)
<li
x-on:click.stop
class="flex items-center justify-center"
>
{{ $moveUpAction }}
</li>
<li
x-on:click.stop
class="flex items-center justify-center"
>
{{ $moveDownAction }}
</li>
@endif
</ul>
@endif
@if (filled($itemLabel))
<h4
@class([
'text-sm font-medium text-gray-950 dark:text-white',
'truncate' => $isItemLabelTruncated(),
])
>
{{ $itemLabel }}
</h4>
@endif
@if ($cloneActionIsVisible || $deleteActionIsVisible || $isCollapsible || $visibleExtraItemActions)
<ul
class="ms-auto flex items-center gap-x-3"
>
@foreach ($visibleExtraItemActions as $extraItemAction)
<li x-on:click.stop>
{{ $extraItemAction(['item' => $uuid]) }}
</li>
@endforeach
@if ($cloneActionIsVisible)
<li x-on:click.stop>
{{ $cloneAction }}
</li>
@endif
@if ($deleteActionIsVisible)
<li x-on:click.stop>
{{ $deleteAction }}
</li>
@endif
@if ($isCollapsible)
<li
class="relative transition"
x-on:click.stop="isCollapsed = !isCollapsed"
x-bind:class="{ '-rotate-180': isCollapsed }"
>
<div
class="transition"
x-bind:class="{ 'opacity-0 pointer-events-none': isCollapsed }"
>
{{ $getAction('collapse') }}
</div>
<div
class="absolute inset-0 rotate-180 transition"
x-bind:class="{ 'opacity-0 pointer-events-none': ! isCollapsed }"
>
{{ $getAction('expand') }}
</div>
</li>
@endif
</ul>
@endif
</div>
@endif
<div
x-show="! isCollapsed"
class="fi-fo-repeater-item-content p-4"
>
{{ $item }}
</div>
</li>
@if (! $loop->last)
@if ($isAddable && $addBetweenAction(['afterItem' => $uuid])->isVisible())
<li class="flex w-full justify-center">
<div
class="fi-fo-repeater-add-between-action-ctn rounded-lg bg-white dark:bg-gray-900"
>
{{ $addBetweenAction(['afterItem' => $uuid]) }}
</div>
</li>
@elseif (filled($labelBetweenItems = $getLabelBetweenItems()))
<li
class="relative border-t border-gray-200 dark:border-white/10"
>
<span
class="absolute -top-3 left-3 px-1 text-sm font-medium"
>
{{ $labelBetweenItems }}
</span>
</li>
@endif
@endif
@endforeach
</x-filament::grid>
</ul>
@endif
@if ($isAddable && $addAction->isVisible())
<div class="flex justify-center">
{{ $addAction }}
</div>
@endif
</div>
</x-dynamic-component>

View File

@@ -0,0 +1,125 @@
@php
use Filament\Forms\Components\Actions\Action;
$containers = $getChildComponentContainers();
$addAction = $getAction($getAddActionName());
$cloneAction = $getAction($getCloneActionName());
$deleteAction = $getAction($getDeleteActionName());
$moveDownAction = $getAction($getMoveDownActionName());
$moveUpAction = $getAction($getMoveUpActionName());
$reorderAction = $getAction($getReorderActionName());
$extraItemActions = $getExtraItemActions();
$isAddable = $isAddable();
$isCloneable = $isCloneable();
$isDeletable = $isDeletable();
$isReorderableWithButtons = $isReorderableWithButtons();
$isReorderableWithDragAndDrop = $isReorderableWithDragAndDrop();
$statePath = $getStatePath();
@endphp
<x-dynamic-component :component="$getFieldWrapperView()" :field="$field">
<div
x-data="{}"
{{
$attributes
->merge($getExtraAttributes(), escape: false)
->class(['fi-fo-simple-repeater grid gap-y-4'])
}}
>
@if (count($containers))
<ul>
<x-filament::grid
:default="$getGridColumns('default')"
:sm="$getGridColumns('sm')"
:md="$getGridColumns('md')"
:lg="$getGridColumns('lg')"
:xl="$getGridColumns('xl')"
:two-xl="$getGridColumns('2xl')"
:wire:end.stop="'mountFormComponentAction(\'' . $statePath . '\', \'reorder\', { items: $event.target.sortable.toArray() })'"
x-sortable
:data-sortable-animation-duration="$getReorderAnimationDuration()"
class="gap-4"
>
@foreach ($containers as $uuid => $item)
@php
$visibleExtraItemActions = array_filter(
$extraItemActions,
fn (Action $action): bool => $action(['item' => $uuid])->isVisible(),
);
$cloneAction = $cloneAction(['item' => $uuid]);
$cloneActionIsVisible = $isCloneable && $cloneAction->isVisible();
$deleteAction = $deleteAction(['item' => $uuid]);
$deleteActionIsVisible = $isDeletable && $deleteAction->isVisible();
$moveDownAction = $moveDownAction(['item' => $uuid])->disabled($loop->last);
$moveDownActionIsVisible = $isReorderableWithButtons && $moveDownAction->isVisible();
$moveUpAction = $moveUpAction(['item' => $uuid])->disabled($loop->first);
$moveUpActionIsVisible = $isReorderableWithButtons && $moveUpAction->isVisible();
$reorderActionIsVisible = $isReorderableWithDragAndDrop && $reorderAction->isVisible();
@endphp
<li
wire:key="{{ $this->getId() }}.{{ $item->getStatePath() }}.{{ $field::class }}.item"
x-sortable-item="{{ $uuid }}"
class="fi-fo-repeater-item simple flex justify-start gap-x-3"
>
<div class="flex-1">
{{ $item }}
</div>
@if ($reorderActionIsVisible || $moveUpActionIsVisible || $moveDownActionIsVisible || $cloneActionIsVisible || $deleteActionIsVisible || $visibleExtraItemActions)
<ul class="flex items-center gap-x-1">
@if ($reorderActionIsVisible)
<li x-sortable-handle>
{{ $reorderAction }}
</li>
@endif
@if ($moveUpActionIsVisible || $moveDownActionIsVisible)
<li
class="flex items-center justify-center"
>
{{ $moveUpAction }}
</li>
<li
class="flex items-center justify-center"
>
{{ $moveDownAction }}
</li>
@endif
@foreach ($visibleExtraItemActions as $extraItemAction)
<li>
{{ $extraItemAction(['item' => $uuid]) }}
</li>
@endforeach
@if ($cloneActionIsVisible)
<li>
{{ $cloneAction }}
</li>
@endif
@if ($deleteActionIsVisible)
<li>
{{ $deleteAction }}
</li>
@endif
</ul>
@endif
</li>
@endforeach
</x-filament::grid>
</ul>
@endif
@if ($isAddable && $addAction->isVisible())
<div class="flex justify-center">
{{ $addAction }}
</div>
@endif
</div>
</x-dynamic-component>

View File

@@ -0,0 +1,512 @@
@php
use Filament\Support\Facades\FilamentView;
$id = $getId();
$statePath = $getStatePath();
@endphp
<x-dynamic-component :component="$getFieldWrapperView()" :field="$field">
@if ($isDisabled())
<div
x-data="{
state: $wire.{{ $applyStateBindingModifiers("\$entangle('{$statePath}')") }},
}"
x-html="state"
class="fi-fo-rich-editor fi-disabled prose block w-full max-w-none rounded-lg bg-gray-50 px-3 py-3 text-gray-500 shadow-sm ring-1 ring-gray-950/10 dark:prose-invert dark:bg-transparent dark:text-gray-400 dark:ring-white/10 sm:text-sm"
></div>
@else
<x-filament::input.wrapper
:valid="! $errors->has($statePath)"
:attributes="
\Filament\Support\prepare_inherited_attributes($getExtraAttributeBag())
->class(['fi-fo-rich-editor max-w-full overflow-x-auto'])
"
>
<div
@if (FilamentView::hasSpaMode())
ax-load="visible"
@else
ax-load
@endif
ax-load-src="{{ \Filament\Support\Facades\FilamentAsset::getAlpineComponentSrc('rich-editor', 'filament/forms') }}"
x-data="richEditorFormComponent({
state: $wire.{{ $applyStateBindingModifiers("\$entangle('{$statePath}')", isOptimisticallyLive: false) }},
})"
x-ignore
x-on:trix-attachment-add="
if (! $event.attachment.file) return
let attachment = $event.attachment
$wire.upload(
`componentFileAttachments.{{ $statePath }}`,
attachment.file,
() => {
$wire
.getFormComponentFileAttachmentUrl('{{ $statePath }}')
.then((url) => {
attachment.setAttributes({
url: url,
href: url,
})
})
},
)
"
x-on:trix-change="
let value = $event.target.value
$nextTick(() => {
if (! $refs.trix) {
return
}
state = value
})
"
@if ($isLiveDebounced())
x-on:trix-change.debounce.{{ $getLiveDebounce() }}="
$nextTick(() => {
if (! $refs.trix) {
return
}
$wire.call('$refresh')
})
"
@endif
@if (! $hasToolbarButton('attachFiles'))
x-on:trix-file-accept="$event.preventDefault()"
@endif
{{ $getExtraAlpineAttributeBag() }}
>
<input
id="trix-value-{{ $id }}"
x-ref="trixValue"
type="hidden"
/>
<trix-toolbar
id="trix-toolbar-{{ $id }}"
@class([
'fi-fo-rich-editor-toolbar relative flex flex-col gap-x-3 border-b border-gray-100 px-2.5 py-2 dark:border-white/10',
'hidden' => ! count($getToolbarButtons()),
])
>
<div class="flex gap-x-3 overflow-x-auto">
@if ($hasToolbarButton(['bold', 'italic', 'underline', 'strike', 'link']))
<x-filament-forms::rich-editor.toolbar.group
data-trix-button-group="text-tools"
>
@if ($hasToolbarButton('bold'))
<x-filament-forms::rich-editor.toolbar.button
data-trix-attribute="bold"
data-trix-key="b"
title="{{ __('filament-forms::components.rich_editor.toolbar_buttons.bold') }}"
tabindex="-1"
title="{{ __('filament-forms::components.rich_editor.toolbar_buttons.bold') }}"
>
<svg
class="-mx-4 h-4 dark:fill-current"
aria-hidden="true"
focusable="false"
data-prefix="fas"
data-icon="bold"
role="img"
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 384 512"
>
<path
fill="currentColor"
d="M321.1 242.4C340.1 220.1 352 191.6 352 160c0-70.59-57.42-128-128-128L32 32.01c-17.67 0-32 14.31-32 32s14.33 32 32 32h16v320H32c-17.67 0-32 14.31-32 32s14.33 32 32 32h224c70.58 0 128-57.41 128-128C384 305.3 358.6 264.8 321.1 242.4zM112 96.01H224c35.3 0 64 28.72 64 64s-28.7 64-64 64H112V96.01zM256 416H112v-128H256c35.3 0 64 28.71 64 63.1S291.3 416 256 416z"
></path>
</svg>
</x-filament-forms::rich-editor.toolbar.button>
@endif
@if ($hasToolbarButton('italic'))
<x-filament-forms::rich-editor.toolbar.button
data-trix-attribute="italic"
data-trix-key="i"
title="{{ __('filament-forms::components.rich_editor.toolbar_buttons.italic') }}"
tabindex="-1"
>
<svg
class="-mx-4 h-4 dark:fill-current"
aria-hidden="true"
focusable="false"
data-prefix="fas"
data-icon="italic"
role="img"
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 384 512"
>
<path
fill="currentColor"
d="M384 64.01c0 17.69-14.31 32-32 32h-58.67l-133.3 320H224c17.69 0 32 14.31 32 32s-14.31 32-32 32H32c-17.69 0-32-14.31-32-32s14.31-32 32-32h58.67l133.3-320H160c-17.69 0-32-14.31-32-32s14.31-32 32-32h192C369.7 32.01 384 46.33 384 64.01z"
></path>
</svg>
</x-filament-forms::rich-editor.toolbar.button>
@endif
@if ($hasToolbarButton('underline'))
<x-filament-forms::rich-editor.toolbar.button
data-trix-attribute="underline"
title="{{ __('filament-forms::components.rich_editor.toolbar_buttons.underline') }}"
tabindex="-1"
>
<svg
class="-mx-4 h-4 dark:fill-current"
aria-hidden="true"
focusable="false"
data-prefix="fas"
data-icon="underline"
role="img"
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 448 512"
>
<path
fill="currentColor"
d="M16 64c0-17.7 14.3-32 32-32h96c17.7 0 32 14.3 32 32s-14.3 32-32 32h-16v128c0 53 43 96 96 96s96-43 96-96V96h-16c-17.7 0-32-14.3-32-32s14.3-32 32-32h96c17.7 0 32 14.3 32 32s-14.3 32-32 32h-16v128c0 88.4-71.6 160-160 160S64 312.4 64 224V96H48c-17.7 0-32-14.3-32-32zM0 448c0-17.7 14.3-32 32-32h384c17.7 0 32 14.3 32 32s-14.3 32-32 32H32c-17.7 0-32-14.3-32-32z"
/>
</svg>
</x-filament-forms::rich-editor.toolbar.button>
@endif
@if ($hasToolbarButton('strike'))
<x-filament-forms::rich-editor.toolbar.button
data-trix-attribute="strike"
title="{{ __('filament-forms::components.rich_editor.toolbar_buttons.strike') }}"
tabindex="-1"
>
<svg
class="-mx-4 h-4 dark:fill-current"
aria-hidden="true"
focusable="false"
data-prefix="fas"
data-icon="strikethrough"
role="img"
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 512 512"
>
<path
fill="currentColor"
d="M332.2 319.9c17.22 12.17 22.33 26.51 18.61 48.21c-3.031 17.59-10.88 29.34-24.72 36.99c-35.44 19.75-108.5 11.96-186-19.68c-16.34-6.686-35.03 1.156-41.72 17.53s1.188 35.05 17.53 41.71c31.75 12.93 95.69 35.37 157.6 35.37c29.62 0 58.81-5.156 83.72-18.96c30.81-17.09 50.44-45.46 56.72-82.11c3.998-23.27 2.168-42.58-3.488-59.05H332.2zM488 239.9l-176.5-.0309c-15.85-5.613-31.83-10.34-46.7-14.62c-85.47-24.62-110.9-39.05-103.7-81.33c2.5-14.53 10.16-25.96 22.72-34.03c20.47-13.15 64.06-23.84 155.4 .3438c17.09 4.531 34.59-5.654 39.13-22.74c4.531-17.09-5.656-34.59-22.75-39.12c-91.31-24.18-160.7-21.62-206.3 7.654C121.8 73.72 103.6 101.1 98.09 133.1C89.26 184.5 107.9 217.3 137.2 239.9L24 239.9c-13.25 0-24 10.75-24 23.1c0 13.25 10.75 23.1 24 23.1h464c13.25 0 24-10.75 24-23.1C512 250.7 501.3 239.9 488 239.9z"
></path>
</svg>
</x-filament-forms::rich-editor.toolbar.button>
@endif
@if ($hasToolbarButton('link'))
<x-filament-forms::rich-editor.toolbar.button
data-trix-attribute="href"
data-trix-action="link"
data-trix-key="k"
title="{{ __('filament-forms::components.rich_editor.toolbar_buttons.link') }}"
tabindex="-1"
>
<svg
class="-mx-4 h-4 dark:fill-current"
aria-hidden="true"
focusable="false"
data-prefix="fas"
data-icon="link"
role="img"
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 640 512"
>
<path
fill="currentColor"
d="M598.6 41.41C570.1 13.8 534.8 0 498.6 0s-72.36 13.8-99.96 41.41l-43.36 43.36c15.11 8.012 29.47 17.58 41.91 30.02c3.146 3.146 5.898 6.518 8.742 9.838l37.96-37.96C458.5 72.05 477.1 64 498.6 64c20.67 0 40.1 8.047 54.71 22.66c14.61 14.61 22.66 34.04 22.66 54.71s-8.049 40.1-22.66 54.71l-133.3 133.3C405.5 343.1 386 352 365.4 352s-40.1-8.048-54.71-22.66C296 314.7 287.1 295.3 287.1 274.6s8.047-40.1 22.66-54.71L314.2 216.4C312.1 212.5 309.9 208.5 306.7 205.3C298.1 196.7 286.8 192 274.6 192c-11.93 0-23.1 4.664-31.61 12.97c-30.71 53.96-23.63 123.6 22.39 169.6C293 402.2 329.2 416 365.4 416c36.18 0 72.36-13.8 99.96-41.41L598.6 241.3c28.45-28.45 42.24-66.01 41.37-103.3C639.1 102.1 625.4 68.16 598.6 41.41zM234 387.4L196.1 425.3C181.5 439.1 162 448 141.4 448c-20.67 0-40.1-8.047-54.71-22.66c-14.61-14.61-22.66-34.04-22.66-54.71s8.049-40.1 22.66-54.71l133.3-133.3C234.5 168 253.1 160 274.6 160s40.1 8.048 54.71 22.66c14.62 14.61 22.66 34.04 22.66 54.71s-8.047 40.1-22.66 54.71L325.8 295.6c2.094 3.939 4.219 7.895 7.465 11.15C341.9 315.3 353.3 320 365.4 320c11.93 0 23.1-4.664 31.61-12.97c30.71-53.96 23.63-123.6-22.39-169.6C346.1 109.8 310.8 96 274.6 96C238.4 96 202.3 109.8 174.7 137.4L41.41 270.7c-27.6 27.6-41.41 63.78-41.41 99.96c-.0001 36.18 13.8 72.36 41.41 99.97C69.01 498.2 105.2 512 141.4 512c36.18 0 72.36-13.8 99.96-41.41l43.36-43.36c-15.11-8.012-29.47-17.58-41.91-30.02C239.6 394.1 236.9 390.7 234 387.4z"
></path>
</svg>
</x-filament-forms::rich-editor.toolbar.button>
@endif
</x-filament-forms::rich-editor.toolbar.group>
@endif
@if ($hasToolbarButton(['h1', 'h2', 'h3']))
<x-filament-forms::rich-editor.toolbar.group
data-trix-button-group="heading-tools"
>
@if ($hasToolbarButton('h1'))
<x-filament-forms::rich-editor.toolbar.button
data-trix-attribute="heading1"
title="{{ __('filament-forms::components.rich_editor.toolbar_buttons.h1') }}"
tabindex="-1"
>
{{ __('filament-forms::components.rich_editor.toolbar_buttons.h1') }}
</x-filament-forms::rich-editor.toolbar.button>
@endif
@if ($hasToolbarButton('h2'))
<x-filament-forms::rich-editor.toolbar.button
data-trix-attribute="heading"
title="{{ __('filament-forms::components.rich_editor.toolbar_buttons.h2') }}"
tabindex="-1"
>
{{ __('filament-forms::components.rich_editor.toolbar_buttons.h2') }}
</x-filament-forms::rich-editor.toolbar.button>
@endif
@if ($hasToolbarButton('h3'))
<x-filament-forms::rich-editor.toolbar.button
data-trix-attribute="subHeading"
title="{{ __('filament-forms::components.rich_editor.toolbar_buttons.h3') }}"
tabindex="-1"
>
{{ __('filament-forms::components.rich_editor.toolbar_buttons.h3') }}
</x-filament-forms::rich-editor.toolbar.button>
@endif
</x-filament-forms::rich-editor.toolbar.group>
@endif
@if ($hasToolbarButton(['blockquote', 'codeBlock', 'bulletList', 'orderedList']))
<x-filament-forms::rich-editor.toolbar.group
data-trix-button-group="block-tools"
>
@if ($hasToolbarButton('blockquote'))
<x-filament-forms::rich-editor.toolbar.button
data-trix-attribute="quote"
title="{{ __('filament-forms::components.rich_editor.toolbar_buttons.blockquote') }}"
tabindex="-1"
>
<svg
class="-mx-4 h-4 dark:fill-current"
aria-hidden="true"
focusable="false"
data-prefix="fas"
data-icon="quote-left"
role="img"
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 448 512"
>
<path
fill="currentColor"
d="M96 224C84.72 224 74.05 226.3 64 229.9V224c0-35.3 28.7-64 64-64c17.67 0 32-14.33 32-32S145.7 96 128 96C57.42 96 0 153.4 0 224v96c0 53.02 42.98 96 96 96s96-42.98 96-96S149 224 96 224zM352 224c-11.28 0-21.95 2.305-32 5.879V224c0-35.3 28.7-64 64-64c17.67 0 32-14.33 32-32s-14.33-32-32-32c-70.58 0-128 57.42-128 128v96c0 53.02 42.98 96 96 96s96-42.98 96-96S405 224 352 224z"
></path>
</svg>
</x-filament-forms::rich-editor.toolbar.button>
@endif
@if ($hasToolbarButton('codeBlock'))
<x-filament-forms::rich-editor.toolbar.button
data-trix-attribute="code"
title="{{ __('filament-forms::components.rich_editor.toolbar_buttons.code_block') }}"
tabindex="-1"
>
<svg
class="-mx-4 h-4 dark:fill-current"
aria-hidden="true"
focusable="false"
data-prefix="fas"
data-icon="code"
role="img"
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 640 512"
>
<path
fill="currentColor"
d="M416 31.94C416 21.75 408.1 0 384.1 0c-13.98 0-26.87 9.072-30.89 23.18l-128 448c-.8404 2.935-1.241 5.892-1.241 8.801C223.1 490.3 232 512 256 512c13.92 0 26.73-9.157 30.75-23.22l128-448C415.6 37.81 416 34.85 416 31.94zM176 143.1c0-18.28-14.95-32-32-32c-8.188 0-16.38 3.125-22.62 9.376l-112 112C3.125 239.6 0 247.8 0 255.1S3.125 272.4 9.375 278.6l112 112C127.6 396.9 135.8 399.1 144 399.1c17.05 0 32-13.73 32-32c0-8.188-3.125-16.38-9.375-22.63L77.25 255.1l89.38-89.38C172.9 160.3 176 152.2 176 143.1zM640 255.1c0-8.188-3.125-16.38-9.375-22.63l-112-112C512.4 115.1 504.2 111.1 496 111.1c-17.05 0-32 13.73-32 32c0 8.188 3.125 16.38 9.375 22.63l89.38 89.38l-89.38 89.38C467.1 351.6 464 359.8 464 367.1c0 18.28 14.95 32 32 32c8.188 0 16.38-3.125 22.62-9.376l112-112C636.9 272.4 640 264.2 640 255.1z"
></path>
</svg>
</x-filament-forms::rich-editor.toolbar.button>
@endif
@if ($hasToolbarButton('bulletList'))
<x-filament-forms::rich-editor.toolbar.button
data-trix-attribute="bullet"
title="{{ __('filament-forms::components.rich_editor.toolbar_buttons.bullet_list') }}"
tabindex="-1"
>
<svg
class="-mx-4 h-4 dark:fill-current"
aria-hidden="true"
focusable="false"
data-prefix="fas"
data-icon="list-ul"
role="img"
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 512 512"
>
<path
fill="currentColor"
d="M16 96C16 69.49 37.49 48 64 48C90.51 48 112 69.49 112 96C112 122.5 90.51 144 64 144C37.49 144 16 122.5 16 96zM480 64C497.7 64 512 78.33 512 96C512 113.7 497.7 128 480 128H192C174.3 128 160 113.7 160 96C160 78.33 174.3 64 192 64H480zM480 224C497.7 224 512 238.3 512 256C512 273.7 497.7 288 480 288H192C174.3 288 160 273.7 160 256C160 238.3 174.3 224 192 224H480zM480 384C497.7 384 512 398.3 512 416C512 433.7 497.7 448 480 448H192C174.3 448 160 433.7 160 416C160 398.3 174.3 384 192 384H480zM16 416C16 389.5 37.49 368 64 368C90.51 368 112 389.5 112 416C112 442.5 90.51 464 64 464C37.49 464 16 442.5 16 416zM112 256C112 282.5 90.51 304 64 304C37.49 304 16 282.5 16 256C16 229.5 37.49 208 64 208C90.51 208 112 229.5 112 256z"
></path>
</svg>
</x-filament-forms::rich-editor.toolbar.button>
@endif
@if ($hasToolbarButton('orderedList'))
<x-filament-forms::rich-editor.toolbar.button
data-trix-attribute="number"
title="{{ __('filament-forms::components.rich_editor.toolbar_buttons.ordered_list') }}"
tabindex="-1"
>
<svg
class="-mx-4 h-4 dark:fill-current"
aria-hidden="true"
focusable="false"
data-prefix="fas"
data-icon="list-ol"
role="img"
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 576 512"
>
<path
fill="currentColor"
d="M55.1 56.04C55.1 42.78 66.74 32.04 79.1 32.04H111.1C125.3 32.04 135.1 42.78 135.1 56.04V176H151.1C165.3 176 175.1 186.8 175.1 200C175.1 213.3 165.3 224 151.1 224H71.1C58.74 224 47.1 213.3 47.1 200C47.1 186.8 58.74 176 71.1 176H87.1V80.04H79.1C66.74 80.04 55.1 69.29 55.1 56.04V56.04zM118.7 341.2C112.1 333.8 100.4 334.3 94.65 342.4L83.53 357.9C75.83 368.7 60.84 371.2 50.05 363.5C39.26 355.8 36.77 340.8 44.47 330.1L55.59 314.5C79.33 281.2 127.9 278.8 154.8 309.6C176.1 333.1 175.6 370.5 153.7 394.3L118.8 432H152C165.3 432 176 442.7 176 456C176 469.3 165.3 480 152 480H64C54.47 480 45.84 474.4 42.02 465.6C38.19 456.9 39.9 446.7 46.36 439.7L118.4 361.7C123.7 355.9 123.8 347.1 118.7 341.2L118.7 341.2zM512 64C529.7 64 544 78.33 544 96C544 113.7 529.7 128 512 128H256C238.3 128 224 113.7 224 96C224 78.33 238.3 64 256 64H512zM512 224C529.7 224 544 238.3 544 256C544 273.7 529.7 288 512 288H256C238.3 288 224 273.7 224 256C224 238.3 238.3 224 256 224H512zM512 384C529.7 384 544 398.3 544 416C544 433.7 529.7 448 512 448H256C238.3 448 224 433.7 224 416C224 398.3 238.3 384 256 384H512z"
></path>
</svg>
</x-filament-forms::rich-editor.toolbar.button>
@endif
</x-filament-forms::rich-editor.toolbar.group>
@endif
@if ($hasToolbarButton('attachFiles'))
<x-filament-forms::rich-editor.toolbar.group
data-trix-button-group="file-tools"
>
<x-filament-forms::rich-editor.toolbar.button
data-trix-action="attachFiles"
title="{{ __('filament-forms::components.rich_editor.toolbar_buttons.attach_files') }}"
tabindex="-1"
>
<svg
class="-mx-4 h-4 dark:fill-current"
aria-hidden="true"
focusable="false"
data-prefix="fas"
data-icon="image"
role="img"
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 512 512"
>
<path
fill="currentColor"
d="M447.1 32h-484C28.64 32-.0091 60.65-.0091 96v320c0 35.35 28.65 64 63.1 64h384c35.35 0 64-28.65 64-64V96C511.1 60.65 483.3 32 447.1 32zM111.1 96c26.51 0 48 21.49 48 48S138.5 192 111.1 192s-48-21.49-48-48S85.48 96 111.1 96zM446.1 407.6C443.3 412.8 437.9 416 432 416H82.01c-6.021 0-11.53-3.379-14.26-8.75c-2.73-5.367-2.215-11.81 1.334-16.68l70-96C142.1 290.4 146.9 288 152 288s9.916 2.441 12.93 6.574l32.46 44.51l93.3-139.1C293.7 194.7 298.7 192 304 192s10.35 2.672 13.31 7.125l128 192C448.6 396 448.9 402.3 446.1 407.6z"
></path>
</svg>
</x-filament-forms::rich-editor.toolbar.button>
</x-filament-forms::rich-editor.toolbar.group>
@endif
@if ($hasToolbarButton(['undo', 'redo']))
<x-filament-forms::rich-editor.toolbar.group
data-trix-button-group="history-tools"
>
@if ($hasToolbarButton('undo'))
<x-filament-forms::rich-editor.toolbar.button
data-trix-action="undo"
data-trix-key="z"
title="{{ __('filament-forms::components.rich_editor.toolbar_buttons.undo') }}"
tabindex="-1"
>
<svg
class="-mx-4 h-4 dark:fill-current"
aria-hidden="true"
focusable="false"
data-prefix="fas"
data-icon="rotate-left"
role="img"
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 512 512"
>
<path
fill="currentColor"
d="M480 256c0 123.4-100.5 223.9-223.9 223.9c-48.84 0-95.17-15.58-134.2-44.86c-14.12-10.59-16.97-30.66-6.375-44.81c10.59-14.12 30.62-16.94 44.81-6.375c27.84 20.91 61 31.94 95.88 31.94C344.3 415.8 416 344.1 416 256s-71.69-159.8-159.8-159.8c-37.46 0-73.09 13.49-101.3 36.64l45.12 45.14c17.01 17.02 4.955 46.1-19.1 46.1H35.17C24.58 224.1 16 215.5 16 204.9V59.04c0-24.04 29.07-36.08 46.07-19.07l47.6 47.63C149.9 52.71 201.5 32.11 256.1 32.11C379.5 32.11 480 132.6 480 256z"
></path>
</svg>
</x-filament-forms::rich-editor.toolbar.button>
@endif
@if ($hasToolbarButton('redo'))
<x-filament-forms::rich-editor.toolbar.button
data-trix-action="redo"
data-trix-key="shift+z"
title="{{ __('filament-forms::components.rich_editor.toolbar_buttons.redo') }}"
tabindex="-1"
>
<svg
class="-mx-4 h-4 dark:fill-current"
aria-hidden="true"
focusable="false"
data-prefix="fas"
data-icon="rotate-right"
role="img"
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 512 512"
>
<path
fill="currentColor"
d="M468.9 32.11c13.87 0 27.18 10.77 27.18 27.04v145.9c0 10.59-8.584 19.17-19.17 19.17h-145.7c-16.28 0-27.06-13.32-27.06-27.2c0-6.634 2.461-13.4 7.96-18.9l45.12-45.14c-28.22-23.14-63.85-36.64-101.3-36.64c-88.09 0-159.8 71.69-159.8 159.8S167.8 415.9 255.9 415.9c73.14 0 89.44-38.31 115.1-38.31c18.48 0 31.97 15.04 31.97 31.96c0 35.04-81.59 70.41-147 70.41c-123.4 0-223.9-100.5-223.9-223.9S132.6 32.44 256 32.44c54.6 0 106.2 20.39 146.4 55.26l47.6-47.63C455.5 34.57 462.3 32.11 468.9 32.11z"
></path>
</svg>
</x-filament-forms::rich-editor.toolbar.button>
@endif
</x-filament-forms::rich-editor.toolbar.group>
@endif
</div>
<div x-cloak data-trix-dialogs class="trix-dialogs">
<div
data-trix-dialog="href"
data-trix-dialog-attribute="href"
class="trix-dialog trix-dialog--link"
>
<div class="trix-dialog__link-fields">
<input
aria-label="{{ __('filament-forms::components.rich_editor.dialogs.link.label') }}"
data-trix-input
disabled
name="href"
placeholder="{{ __('filament-forms::components.rich_editor.dialogs.link.placeholder') }}"
required
type="text"
inputmode="url"
class="trix-input trix-input--dialog"
/>
<div class="trix-button-group">
<input
data-trix-method="setAttribute"
type="button"
value="{{ __('filament-forms::components.rich_editor.dialogs.link.actions.link') }}"
class="trix-button trix-button--dialog"
/>
<input
data-trix-method="removeAttribute"
type="button"
value="{{ __('filament-forms::components.rich_editor.dialogs.link.actions.unlink') }}"
class="trix-button trix-button--dialog"
/>
</div>
</div>
</div>
</div>
</trix-toolbar>
<trix-editor
@if ($isAutofocused())
autofocus
@endif
id="{{ $id }}"
input="trix-value-{{ $id }}"
placeholder="{{ $getPlaceholder() }}"
toolbar="trix-toolbar-{{ $id }}"
@if ($isLiveOnBlur())
x-on:blur="$wire.call('$refresh')"
@endif
x-ref="trix"
wire:ignore
{{
$getExtraInputAttributeBag()->class([
'prose min-h-[theme(spacing.48)] max-w-none !border-none px-3 py-1.5 text-base text-gray-950 dark:prose-invert focus-visible:outline-none dark:text-white sm:text-sm sm:leading-6',
])
}}
></trix-editor>
</div>
</x-filament::input.wrapper>
@endif
</x-dynamic-component>

View File

@@ -0,0 +1,11 @@
<button
{{
$attributes
->merge([
'type' => 'button',
], escape: false)
->class(['fi-fo-rich-editor-toolbar-btn flex h-8 min-w-[theme(spacing.8)] cursor-pointer items-center justify-center rounded-lg px-2 text-sm font-semibold text-gray-700 transition duration-75 hover:bg-gray-50 focus-visible:bg-gray-50 dark:text-gray-200 dark:hover:bg-white/5 dark:focus-visible:bg-white/5 [&.trix-active]:bg-gray-50 [&.trix-active]:text-primary-600 dark:[&.trix-active]:bg-white/5 dark:[&.trix-active]:text-primary-400'])
}}
>
{{ $slot }}
</button>

View File

@@ -0,0 +1,3 @@
<div {{ $attributes->class(['flex gap-x-1']) }}>
{{ $slot }}
</div>

View File

@@ -0,0 +1,30 @@
@php
$isAside = $isAside();
@endphp
<x-filament::section
:aside="$isAside"
:collapsed="$isCollapsed()"
:collapsible="$isCollapsible() && (! $isAside)"
:compact="$isCompact()"
:content-before="$isFormBefore()"
:description="$getDescription()"
:footer-actions="$getFooterActions()"
:footer-actions-alignment="$getFooterActionsAlignment()"
:header-actions="$getHeaderActions()"
:heading="$getHeading()"
:icon="$getIcon()"
:icon-color="$getIconColor()"
:icon-size="$getIconSize()"
:persist-collapsed="$shouldPersistCollapsed()"
:attributes="
\Filament\Support\prepare_inherited_attributes($attributes)
->merge([
'id' => $getId(),
], escape: false)
->merge($getExtraAttributes(), escape: false)
->merge($getExtraAlpineAttributes(), escape: false)
"
>
{{ $getChildComponentContainer() }}
</x-filament::section>

View File

@@ -0,0 +1,169 @@
@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::input.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'])
"
>
@if ((! ($isSearchable() || $isMultiple()) && $isNative()))
<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>
@else
<div
x-ignore
@if (FilamentView::hasSpaMode())
ax-load="visible"
@else
ax-load
@endif
ax-load-src="{{ \Filament\Support\Facades\FilamentAsset::getAlpineComponentSrc('select', 'filament/forms') }}"
x-data="selectFormComponent({
canSelectPlaceholder: @js($canSelectPlaceholder),
isHtmlAllowed: @js($isHtmlAllowed()),
getOptionLabelUsing: async () => {
return await $wire.getFormSelectOptionLabel(@js($statePath))
},
getOptionLabelsUsing: async () => {
return await $wire.getFormSelectOptionLabels(@js($statePath))
},
getOptionsUsing: async () => {
return await $wire.getFormSelectOptions(@js($statePath))
},
getSearchResultsUsing: async (search) => {
return await $wire.getFormSelectSearchResults(@js($statePath), search)
},
isAutofocused: @js($isAutofocused()),
isMultiple: @js($isMultiple()),
isSearchable: @js($isSearchable()),
livewireId: @js($this->getId()),
hasDynamicOptions: @js($hasDynamicOptions()),
hasDynamicSearchResults: @js($hasDynamicSearchResults()),
loadingMessage: @js($getLoadingMessage()),
maxItems: @js($getMaxItems()),
maxItemsMessage: @js($getMaxItemsMessage()),
noSearchResultsMessage: @js($getNoSearchResultsMessage()),
options: @js($getOptionsForJs()),
optionsLimit: @js($getOptionsLimit()),
placeholder: @js($getPlaceholder()),
position: @js($getPosition()),
searchDebounce: @js($getSearchDebounce()),
searchingMessage: @js($getSearchingMessage()),
searchPrompt: @js($getSearchPrompt()),
searchableOptionFields: @js($getSearchableOptionFields()),
state: $wire.{{ $applyStateBindingModifiers("\$entangle('{$statePath}')") }},
statePath: @js($statePath),
})"
wire:ignore
x-on:keydown.esc="select.dropdown.isActive && $event.stopPropagation()"
{{
$attributes
->merge($getExtraAlpineAttributes(), escape: false)
->class([
'[&_.choices\_\_inner]:ps-0' => $isPrefixInline && (count($prefixActions) || $prefixIcon || filled($prefixLabel)),
])
}}
>
<select
x-ref="input"
{{
$getExtraInputAttributeBag()
->merge([
'disabled' => $isDisabled,
'id' => $getId(),
'multiple' => $isMultiple(),
], escape: false)
->class([
'h-9 w-full rounded-lg border-none bg-transparent !bg-none',
])
}}
></select>
</div>
@endif
</x-filament::input.wrapper>
</x-dynamic-component>

View File

@@ -0,0 +1,63 @@
@php
use Filament\Support\Enums\VerticalAlignment;
$verticalAlignment = $getVerticalAlignment();
if (! $verticalAlignment instanceof VerticalAlignment) {
$verticalAlignment = filled($verticalAlignment) ? (VerticalAlignment::tryFrom($verticalAlignment) ?? $verticalAlignment) : null;
}
@endphp
<div
{{
$attributes
->merge($getExtraAttributes(), escape: false)
->class([
'fi-fo-split flex gap-6',
match ($getFromBreakpoint()) {
'sm' => 'flex-col sm:flex-row ' . match ($verticalAlignment) {
VerticalAlignment::Center => 'sm:items-center',
VerticalAlignment::End => 'sm:items-end',
default => 'sm:items-start',
},
'md' => 'flex-col md:flex-row ' . match ($verticalAlignment) {
VerticalAlignment::Center => 'md:items-center',
VerticalAlignment::End => 'md:items-end',
default => 'md:items-start',
},
'lg' => 'flex-col lg:flex-row ' . match ($verticalAlignment) {
VerticalAlignment::Center => 'lg:items-center',
VerticalAlignment::End => 'lg:items-end',
default => 'lg:items-start',
},
'xl' => 'flex-col xl:flex-row ' . match ($verticalAlignment) {
VerticalAlignment::Center => 'xl:items-center',
VerticalAlignment::End => 'xl:items-end',
default => 'xl:items-start',
},
'2xl' => 'flex-col 2xl:flex-row ' . match ($verticalAlignment) {
VerticalAlignment::Center => '2xl:items-center',
VerticalAlignment::End => '2xl:items-end',
default => '2xl:items-start',
},
default => match ($verticalAlignment) {
VerticalAlignment::Center => 'items-center',
VerticalAlignment::End => 'items-end',
default => 'items-start',
},
},
])
}}
>
@foreach ($getChildComponentContainers() as $container)
@foreach ($container->getComponents() as $component)
<div
@class([
'w-full flex-1' => $component->canGrow(),
])
>
{{ $component }}
</div>
@endforeach
@endforeach
</div>

View File

@@ -0,0 +1,107 @@
@php
use Filament\Forms\Components\Tabs\Tab;
$isContained = $isContained();
@endphp
<div
wire:ignore.self
x-cloak
x-data="{
tab: @if ($isTabPersisted() && filled($persistenceId = $getId())) $persist(null).as('tabs-{{ $persistenceId }}') @else null @endif,
getTabs: function () {
if (! this.$refs.tabsData) {
return []
}
return JSON.parse(this.$refs.tabsData.value)
},
updateQueryString: function () {
if (! @js($isTabPersistedInQueryString())) {
return
}
const url = new URL(window.location.href)
url.searchParams.set(@js($getTabQueryStringKey()), this.tab)
history.pushState(null, document.title, url.toString())
},
}"
x-init="
$watch('tab', () => updateQueryString())
const tabs = getTabs()
if (! tab || ! tabs.includes(tab)) {
tab = tabs[@js($getActiveTab()) - 1]
}
Livewire.hook('commit', ({ component, commit, succeed, fail, respond }) => {
succeed(({ snapshot, effect }) => {
$nextTick(() => {
if (component.id !== @js($this->getId())) {
return
}
const tabs = getTabs()
if (! tabs.includes(tab)) {
tab = tabs[@js($getActiveTab()) - 1] ?? tab
}
})
})
})
"
{{
$attributes
->merge([
'id' => $getId(),
'wire:key' => "{$this->getId()}.{$getStatePath()}." . \Filament\Forms\Components\Tabs::class . '.container',
], escape: false)
->merge($getExtraAttributes(), escape: false)
->merge($getExtraAlpineAttributes(), escape: false)
->class([
'fi-fo-tabs flex flex-col',
'fi-contained rounded-xl bg-white shadow-sm ring-1 ring-gray-950/5 dark:bg-gray-900 dark:ring-white/10' => $isContained,
])
}}
>
<input
type="hidden"
value="{{
collect($getChildComponentContainer()->getComponents())
->filter(static fn (Tab $tab): bool => $tab->isVisible())
->map(static fn (Tab $tab) => $tab->getId())
->values()
->toJson()
}}"
x-ref="tabsData"
/>
<x-filament::tabs :contained="$isContained" :label="$getLabel()">
@foreach ($getChildComponentContainer()->getComponents() as $tab)
@php
$tabId = $tab->getId();
@endphp
<x-filament::tabs.item
:alpine-active="'tab === \'' . $tabId . '\''"
:badge="$tab->getBadge()"
:badge-color="$tab->getBadgeColor()"
:badge-icon="$tab->getBadgeIcon()"
:badge-icon-position="$tab->getBadgeIconPosition()"
:icon="$tab->getIcon()"
:icon-position="$tab->getIconPosition()"
:x-on:click="'tab = \'' . $tabId . '\''"
>
{{ $tab->getLabel() }}
</x-filament::tabs.item>
@endforeach
</x-filament::tabs>
@foreach ($getChildComponentContainer()->getComponents() as $tab)
{{ $tab }}
@endforeach
</div>

View File

@@ -0,0 +1,34 @@
@php
$id = $getId();
$isContained = $getContainer()->getParentComponent()->isContained();
$activeTabClasses = \Illuminate\Support\Arr::toCssClasses([
'fi-active',
'p-6' => $isContained,
'mt-6' => ! $isContained,
]);
$inactiveTabClasses = 'invisible absolute h-0 overflow-hidden p-0';
@endphp
<div
x-bind:class="{
@js($activeTabClasses): tab === @js($id),
@js($inactiveTabClasses): tab !== @js($id),
}"
x-on:expand="tab = @js($id)"
{{
$attributes
->merge([
'aria-labelledby' => $id,
'id' => $id,
'role' => 'tabpanel',
'tabindex' => '0',
'wire:key' => "{$this->getId()}.{$getStatePath()}." . \Filament\Forms\Components\Tabs\Tab::class . ".tabs.{$id}",
], escape: false)
->merge($getExtraAttributes(), escape: false)
->class(['fi-fo-tabs-tab outline-none'])
}}
>
{{ $getChildComponentContainer() }}
</div>

View File

@@ -0,0 +1,144 @@
@php
use Filament\Support\Facades\FilamentView;
$color = $getColor() ?? 'primary';
$hasInlineLabel = $hasInlineLabel();
$id = $getId();
$isDisabled = $isDisabled();
$isPrefixInline = $isPrefixInline();
$isReorderable = $isReorderable();
$isSuffixInline = $isSuffixInline();
$prefixActions = $getPrefixActions();
$prefixIcon = $getPrefixIcon();
$prefixLabel = $getPrefixLabel();
$statePath = $getStatePath();
$suffixActions = $getSuffixActions();
$suffixIcon = $getSuffixIcon();
$suffixLabel = $getSuffixLabel();
@endphp
<x-dynamic-component
:component="$getFieldWrapperView()"
:field="$field"
:has-inline-label="$hasInlineLabel"
>
<x-slot
name="label"
@class([
'sm:pt-1.5' => $hasInlineLabel,
])
>
{{ $getLabel() }}
</x-slot>
<x-filament::input.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($attributes)
->merge($getExtraAttributes(), escape: false)
->class(['fi-fo-tags-input'])
"
>
<div
@if (FilamentView::hasSpaMode())
ax-load="visible"
@else
ax-load
@endif
ax-load-src="{{ \Filament\Support\Facades\FilamentAsset::getAlpineComponentSrc('tags-input', 'filament/forms') }}"
x-data="tagsInputFormComponent({
state: $wire.{{ $applyStateBindingModifiers("\$entangle('{$statePath}')") }},
splitKeys: @js($getSplitKeys()),
})"
x-ignore
{{ $getExtraAlpineAttributeBag() }}
>
<x-filament::input
autocomplete="off"
:autofocus="$isAutofocused()"
:disabled="$isDisabled"
:id="$id"
:inline-prefix="$isPrefixInline && (count($prefixActions) || $prefixIcon || filled($prefixLabel))"
:inline-suffix="$isSuffixInline && (count($suffixActions) || $suffixIcon || filled($suffixLabel))"
:list="$id . '-suggestions'"
:placeholder="$getPlaceholder()"
type="text"
x-bind="input"
:attributes="\Filament\Support\prepare_inherited_attributes($getExtraInputAttributeBag())"
/>
<datalist id="{{ $id }}-suggestions">
@foreach ($getSuggestions() as $suggestion)
<template
x-bind:key="@js($suggestion)"
x-if="! (state?.includes(@js($suggestion)) ?? true)"
>
<option value="{{ $suggestion }}" />
</template>
@endforeach
</datalist>
<div
@class([
'[&_.fi-badge-delete-button]:hidden' => $isDisabled,
])
>
<div wire:ignore>
<template x-cloak x-if="state?.length">
<div
@if ($isReorderable)
x-on:end.stop="reorderTags($event)"
x-sortable
data-sortable-animation-duration="{{ $getReorderAnimationDuration() }}"
@endif
@class([
'flex w-full flex-wrap gap-1.5 p-2',
'border-t border-t-gray-200 dark:border-t-white/10',
])
>
<template
x-for="(tag, index) in state"
x-bind:key="`${tag}-${index}`"
class="hidden"
>
<x-filament::badge
:color="$color"
:x-bind:x-sortable-item="$isReorderable ? 'index' : null"
:x-sortable-handle="$isReorderable ? '' : null"
@class([
'cursor-move' => $isReorderable,
])
>
{{ $getTagPrefix() }}
<span
x-text="tag"
class="select-none text-start"
></span>
{{ $getTagSuffix() }}
<x-slot
name="deleteButton"
x-on:click="deleteTag(tag)"
></x-slot>
</x-filament::badge>
</template>
</div>
</template>
</div>
</div>
</div>
</x-filament::input.wrapper>
</x-dynamic-component>

View File

@@ -0,0 +1,114 @@
@php
use Filament\Forms\Components\TextInput\Actions\HidePasswordAction;
use Filament\Forms\Components\TextInput\Actions\ShowPasswordAction;
$datalistOptions = $getDatalistOptions();
$extraAlpineAttributes = $getExtraAlpineAttributes();
$hasInlineLabel = $hasInlineLabel();
$id = $getId();
$isConcealed = $isConcealed();
$isDisabled = $isDisabled();
$isPasswordRevealable = $isPasswordRevealable();
$isPrefixInline = $isPrefixInline();
$isSuffixInline = $isSuffixInline();
$mask = $getMask();
$prefixActions = $getPrefixActions();
$prefixIcon = $getPrefixIcon();
$prefixLabel = $getPrefixLabel();
$suffixActions = $getSuffixActions();
$suffixIcon = $getSuffixIcon();
$suffixLabel = $getSuffixLabel();
$statePath = $getStatePath();
if ($isPasswordRevealable) {
$xData = '{ isPasswordRevealed: false }';
} elseif (count($extraAlpineAttributes) || filled($mask)) {
$xData = '{}';
} else {
$xData = null;
}
if ($isPasswordRevealable) {
$type = null;
} elseif (filled($mask)) {
$type = 'text';
} else {
$type = $getType();
}
@endphp
<x-dynamic-component
:component="$getFieldWrapperView()"
:field="$field"
:has-inline-label="$hasInlineLabel"
>
<x-slot
name="label"
@class([
'sm:pt-1.5' => $hasInlineLabel,
])
>
{{ $getLabel() }}
</x-slot>
<x-filament::input.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)"
:x-data="$xData"
:attributes="
\Filament\Support\prepare_inherited_attributes($getExtraAttributeBag())
->class(['fi-fo-text-input overflow-hidden'])
"
>
<x-filament::input
:attributes="
\Filament\Support\prepare_inherited_attributes($getExtraInputAttributeBag())
->merge($extraAlpineAttributes, escape: false)
->merge([
'autocapitalize' => $getAutocapitalize(),
'autocomplete' => $getAutocomplete(),
'autofocus' => $isAutofocused(),
'disabled' => $isDisabled,
'id' => $id,
'inlinePrefix' => $isPrefixInline && (count($prefixActions) || $prefixIcon || filled($prefixLabel)),
'inlineSuffix' => $isSuffixInline && (count($suffixActions) || $suffixIcon || filled($suffixLabel)),
'inputmode' => $getInputMode(),
'list' => $datalistOptions ? $id . '-list' : null,
'max' => (! $isConcealed) ? $getMaxValue() : null,
'maxlength' => (! $isConcealed) ? $getMaxLength() : null,
'min' => (! $isConcealed) ? $getMinValue() : null,
'minlength' => (! $isConcealed) ? $getMinLength() : null,
'placeholder' => $getPlaceholder(),
'readonly' => $isReadOnly(),
'required' => $isRequired() && (! $isConcealed),
'step' => $getStep(),
'type' => $type,
$applyStateBindingModifiers('wire:model') => $statePath,
'x-bind:type' => $isPasswordRevealable ? 'isPasswordRevealed ? \'text\' : \'password\'' : null,
'x-mask' . ($mask instanceof \Filament\Support\RawJs ? ':dynamic' : '') => filled($mask) ? $mask : null,
], escape: false)
->class([
'[&::-ms-reveal]:hidden' => $isPasswordRevealable,
])
"
/>
</x-filament::input.wrapper>
@if ($datalistOptions)
<datalist id="{{ $id }}-list">
@foreach ($datalistOptions as $option)
<option value="{{ $option }}" />
@endforeach
</datalist>
@endif
</x-dynamic-component>

View File

@@ -0,0 +1,78 @@
@php
use Filament\Support\Facades\FilamentView;
$hasInlineLabel = $hasInlineLabel();
$isConcealed = $isConcealed();
$isDisabled = $isDisabled();
$rows = $getRows();
$shouldAutosize = $shouldAutosize();
$statePath = $getStatePath();
$initialHeight = (($rows ?? 2) * 1.5) + 0.75;
@endphp
<x-dynamic-component
:component="$getFieldWrapperView()"
:field="$field"
:has-inline-label="$hasInlineLabel"
>
<x-slot
name="label"
@class([
'sm:pt-1.5' => $hasInlineLabel,
])
>
{{ $getLabel() }}
</x-slot>
<x-filament::input.wrapper
:disabled="$isDisabled"
:valid="! $errors->has($statePath)"
:attributes="
\Filament\Support\prepare_inherited_attributes($getExtraAttributeBag())
->class(['fi-fo-textarea overflow-hidden'])
"
>
<textarea
@if ($shouldAutosize)
@if (FilamentView::hasSpaMode())
ax-load="visible"
@else
ax-load
@endif
ax-load-src="{{ \Filament\Support\Facades\FilamentAsset::getAlpineComponentSrc('textarea', 'filament/forms') }}"
x-data="textareaFormComponent({ initialHeight: @js($initialHeight) })"
x-intersect.once="render()"
x-on:input="render()"
x-on:resize.window="render()"
{{ $getExtraAlpineAttributeBag() }}
@endif
x-ignore
wire:ignore.style.height
{{
$getExtraInputAttributeBag()
->merge([
'autocomplete' => $getAutocomplete(),
'autofocus' => $isAutofocused(),
'cols' => $getCols(),
'disabled' => $isDisabled,
'id' => $getId(),
'maxlength' => (! $isConcealed) ? $getMaxLength() : null,
'minlength' => (! $isConcealed) ? $getMinLength() : null,
'placeholder' => $getPlaceholder(),
'readonly' => $isReadOnly(),
'required' => $isRequired() && (! $isConcealed),
'rows' => $rows,
$applyStateBindingModifiers('wire:model') => $statePath,
], escape: false)
->class([
'block w-full border-none bg-transparent px-3 py-1.5 text-base text-gray-950 placeholder:text-gray-400 focus:ring-0 disabled:text-gray-500 disabled:[-webkit-text-fill-color:theme(colors.gray.500)] disabled:placeholder:[-webkit-text-fill-color:theme(colors.gray.400)] dark:text-white dark:placeholder:text-gray-500 dark:disabled:text-gray-400 dark:disabled:[-webkit-text-fill-color:theme(colors.gray.400)] dark:disabled:placeholder:[-webkit-text-fill-color:theme(colors.gray.500)] sm:text-sm sm:leading-6',
'resize-none' => $shouldAutosize,
])
->style([
"height: {$initialHeight}rem" => $shouldAutosize,
])
}}
></textarea>
</x-filament::input.wrapper>
</x-dynamic-component>

View File

@@ -0,0 +1,61 @@
@php
$hasInlineLabel = $hasInlineLabel();
$id = $getId();
$isDisabled = $isDisabled();
$isMultiple = $isMultiple();
$statePath = $getStatePath();
@endphp
<x-dynamic-component
:component="$getFieldWrapperView()"
:field="$field"
:has-inline-label="$hasInlineLabel"
>
<x-slot
name="label"
@class([
'sm:pt-1.5' => $hasInlineLabel,
])
>
{{ $getLabel() }}
</x-slot>
<x-filament::button.group
:attributes="
\Filament\Support\prepare_inherited_attributes($attributes)
->merge($getExtraAttributes(), escape: false)
->class(['w-max'])
"
>
@foreach ($getOptions() as $value => $label)
@php
$inputId = "{$id}-{$value}";
$shouldOptionBeDisabled = $isDisabled || $isOptionDisabled($value, $label);
@endphp
<input
@disabled($shouldOptionBeDisabled)
id="{{ $inputId }}"
@if (! $isMultiple)
name="{{ $id }}"
@endif
type="{{ $isMultiple ? 'checkbox' : 'radio' }}"
value="{{ $value }}"
wire:loading.attr="disabled"
{{ $applyStateBindingModifiers('wire:model') }}="{{ $statePath }}"
{{ $getExtraInputAttributeBag()->class(['peer pointer-events-none absolute opacity-0']) }}
/>
<x-filament::button
:color="$getColor($value)"
:disabled="$shouldOptionBeDisabled"
:for="$inputId"
grouped
:icon="$getIcon($value)"
tag="label"
>
{{ $label }}
</x-filament::button>
@endforeach
</x-filament::button.group>
</x-dynamic-component>

View File

@@ -0,0 +1,80 @@
@php
$gridDirection = $getGridDirection() ?? 'column';
$hasInlineLabel = $hasInlineLabel();
$id = $getId();
$isDisabled = $isDisabled();
$isInline = $isInline();
$isMultiple = $isMultiple();
$statePath = $getStatePath();
@endphp
<x-dynamic-component
:component="$getFieldWrapperView()"
:field="$field"
:has-inline-label="$hasInlineLabel"
>
<x-slot
name="label"
@class([
'sm:pt-1.5' => $hasInlineLabel,
])
>
{{ $getLabel() }}
</x-slot>
<x-filament::grid
:default="$getColumns('default')"
:sm="$getColumns('sm')"
:md="$getColumns('md')"
:lg="$getColumns('lg')"
:xl="$getColumns('xl')"
:two-xl="$getColumns('2xl')"
:is-grid="! $isInline"
:direction="$gridDirection"
:attributes="
\Filament\Support\prepare_inherited_attributes($attributes)
->merge($getExtraAttributes(), escape: false)
->class([
'fi-fo-radio gap-3',
'-mt-3' => (! $isInline) && ($gridDirection === 'column'),
'flex flex-wrap' => $isInline,
])
"
>
@foreach ($getOptions() as $value => $label)
@php
$inputId = "{$id}-{$value}";
$shouldOptionBeDisabled = $isDisabled || $isOptionDisabled($value, $label);
@endphp
<div
@class([
'break-inside-avoid pt-3' => (! $isInline) && ($gridDirection === 'column'),
])
>
<input
@disabled($shouldOptionBeDisabled)
id="{{ $inputId }}"
@if (! $isMultiple)
name="{{ $id }}"
@endif
type="{{ $isMultiple ? 'checkbox' : 'radio' }}"
value="{{ $value }}"
wire:loading.attr="disabled"
{{ $applyStateBindingModifiers('wire:model') }}="{{ $statePath }}"
{{ $getExtraInputAttributeBag()->class(['peer pointer-events-none absolute opacity-0']) }}
/>
<x-filament::button
:color="$getColor($value)"
:disabled="$shouldOptionBeDisabled"
:for="$inputId"
:icon="$getIcon($value)"
tag="label"
>
{{ $label }}
</x-filament::button>
</div>
@endforeach
</x-filament::grid>
</x-dynamic-component>

View File

@@ -0,0 +1,136 @@
@php
$offColor = $getOffColor() ?? 'gray';
$onColor = $getOnColor() ?? 'primary';
$statePath = $getStatePath();
@endphp
<x-dynamic-component
:component="$getFieldWrapperView()"
:field="$field"
:inline-label-vertical-alignment="\Filament\Support\Enums\VerticalAlignment::Center"
>
@capture($content)
<button
x-data="{
state: $wire.{{ $applyStateBindingModifiers("\$entangle('{$statePath}')") }},
}"
x-bind:aria-checked="state?.toString()"
x-on:click="state = ! state"
x-bind:class="
state
? '{{
\Illuminate\Support\Arr::toCssClasses([
match ($onColor) {
'gray' => 'bg-gray-200 dark:bg-gray-700',
default => 'fi-color-custom bg-custom-600',
},
is_string($onColor) ? "fi-color-{$onColor}" : null,
])
}}'
: '{{
\Illuminate\Support\Arr::toCssClasses([
match ($offColor) {
'gray' => 'bg-gray-200 dark:bg-gray-700',
default => 'fi-color-custom bg-custom-600',
},
is_string($offColor) ? "fi-color-{$offColor}" : null,
])
}}'
"
x-bind:style="
state
? '{{
\Filament\Support\get_color_css_variables(
$onColor,
shades: [600],
alias: 'forms::components.toggle.on',
)
}}'
: '{{
\Filament\Support\get_color_css_variables(
$offColor,
shades: [600],
alias: 'forms::components.toggle.off',
)
}}'
"
{{
$attributes
->merge([
'aria-checked' => 'false',
'autofocus' => $isAutofocused(),
'disabled' => $isDisabled(),
'id' => $getId(),
'role' => 'switch',
'type' => 'button',
'wire:loading.attr' => 'disabled',
'wire:target' => $statePath,
], escape: false)
->merge($getExtraAttributes(), escape: false)
->merge($getExtraAlpineAttributes(), escape: false)
->class(['fi-fo-toggle relative inline-flex h-6 w-11 shrink-0 cursor-pointer rounded-full border-2 border-transparent outline-none transition-colors duration-200 ease-in-out disabled:pointer-events-none disabled:opacity-70'])
}}
>
<span
class="pointer-events-none relative inline-block h-5 w-5 transform rounded-full bg-white shadow ring-0 transition duration-200 ease-in-out"
x-bind:class="{
'translate-x-5 rtl:-translate-x-5': state,
'translate-x-0': ! state,
}"
>
<span
class="absolute inset-0 flex h-full w-full items-center justify-center transition-opacity"
aria-hidden="true"
x-bind:class="{
'opacity-0 ease-out duration-100': state,
'opacity-100 ease-in duration-200': ! state,
}"
>
@if ($hasOffIcon())
<x-filament::icon
:icon="$getOffIcon()"
@class([
'fi-fo-toggle-off-icon h-3 w-3',
match ($offColor) {
'gray' => 'text-gray-400 dark:text-gray-700',
default => 'text-custom-600',
},
])
/>
@endif
</span>
<span
class="absolute inset-0 flex h-full w-full items-center justify-center transition-opacity"
aria-hidden="true"
x-bind:class="{
'opacity-100 ease-in duration-200': state,
'opacity-0 ease-out duration-100': ! state,
}"
>
@if ($hasOnIcon())
<x-filament::icon
:icon="$getOnIcon()"
x-cloak="x-cloak"
@class([
'fi-fo-toggle-on-icon h-3 w-3',
match ($onColor) {
'gray' => 'text-gray-400 dark:text-gray-700',
default => 'text-custom-600',
},
])
/>
@endif
</span>
</span>
</button>
@endcapture
@if ($isInline())
<x-slot name="labelPrefix">
{{ $content() }}
</x-slot>
@else
{{ $content() }}
@endif
</x-dynamic-component>

View File

@@ -0,0 +1,291 @@
@php
$isContained = $isContained();
$statePath = $getStatePath();
@endphp
<div
wire:ignore.self
x-cloak
x-data="{
step: null,
nextStep: function () {
let nextStepIndex = this.getStepIndex(this.step) + 1
if (nextStepIndex >= this.getSteps().length) {
return
}
this.step = this.getSteps()[nextStepIndex]
this.autofocusFields()
this.scrollToTop()
},
previousStep: function () {
let previousStepIndex = this.getStepIndex(this.step) - 1
if (previousStepIndex < 0) {
return
}
this.step = this.getSteps()[previousStepIndex]
this.autofocusFields()
this.scrollToTop()
},
scrollToTop: function () {
this.$nextTick(() =>
this.$root.scrollIntoView({ behavior: 'smooth', block: 'start' }),
)
},
autofocusFields: function () {
$nextTick(() =>
this.$refs[`step-${this.step}`]
.querySelector('[autofocus]')
?.focus(),
)
},
getStepIndex: function (step) {
let index = this.getSteps().findIndex(
(indexedStep) => indexedStep === step,
)
if (index === -1) {
return 0
}
return index
},
getSteps: function () {
return JSON.parse(this.$refs.stepsData.value)
},
isFirstStep: function () {
return this.getStepIndex(this.step) <= 0
},
isLastStep: function () {
return this.getStepIndex(this.step) + 1 >= this.getSteps().length
},
isStepAccessible: function (stepId) {
return (
@js($isSkippable()) || this.getStepIndex(this.step) > this.getStepIndex(stepId)
)
},
updateQueryString: function () {
if (! @js($isStepPersistedInQueryString())) {
return
}
const url = new URL(window.location.href)
url.searchParams.set(@js($getStepQueryStringKey()), this.step)
history.pushState(null, document.title, url.toString())
},
}"
x-init="
$watch('step', () => updateQueryString())
step = getSteps().at({{ $getStartStep() - 1 }})
autofocusFields()
"
x-on:next-wizard-step.window="if ($event.detail.statePath === '{{ $statePath }}') nextStep()"
{{
$attributes
->merge([
'id' => $getId(),
], escape: false)
->merge($getExtraAttributes(), escape: false)
->merge($getExtraAlpineAttributes(), escape: false)
->class([
'fi-fo-wizard',
'fi-contained rounded-xl bg-white shadow-sm ring-1 ring-gray-950/5 dark:bg-gray-900 dark:ring-white/10' => $isContained,
])
}}
>
<input
type="hidden"
value="{{
collect($getChildComponentContainer()->getComponents())
->filter(static fn (\Filament\Forms\Components\Wizard\Step $step): bool => $step->isVisible())
->map(static fn (\Filament\Forms\Components\Wizard\Step $step) => $step->getId())
->values()
->toJson()
}}"
x-ref="stepsData"
/>
<ol
@if (filled($label = $getLabel()))
aria-label="{{ $label }}"
@endif
role="list"
@class([
'fi-fo-wizard-header grid divide-y divide-gray-200 dark:divide-white/5 md:grid-flow-col md:divide-y-0 md:overflow-x-auto',
'border-b border-gray-200 dark:border-white/10' => $isContained,
'rounded-xl bg-white shadow-sm ring-1 ring-gray-950/5 dark:bg-gray-900 dark:ring-white/10' => ! $isContained,
])
>
@foreach ($getChildComponentContainer()->getComponents() as $step)
<li
class="fi-fo-wizard-header-step relative flex"
x-bind:class="{
'fi-active': getStepIndex(step) === {{ $loop->index }},
'fi-completed': getStepIndex(step) > {{ $loop->index }},
}"
>
<button
type="button"
x-bind:aria-current="getStepIndex(step) === {{ $loop->index }} ? 'step' : null"
x-on:click="step = @js($step->getId())"
x-bind:disabled="! isStepAccessible(@js($step->getId()))"
role="step"
class="fi-fo-wizard-header-step-button flex h-full items-center gap-x-4 px-6 py-4 text-start"
>
<div
class="fi-fo-wizard-header-step-icon-ctn flex h-10 w-10 shrink-0 items-center justify-center rounded-full"
x-bind:class="{
'bg-primary-600 dark:bg-primary-500':
getStepIndex(step) > {{ $loop->index }},
'border-2': getStepIndex(step) <= {{ $loop->index }},
'border-primary-600 dark:border-primary-500':
getStepIndex(step) === {{ $loop->index }},
'border-gray-300 dark:border-gray-600':
getStepIndex(step) < {{ $loop->index }},
}"
>
@php
$completedIcon = $step->getCompletedIcon();
@endphp
<x-filament::icon
:alias="filled($completedIcon) ? null : 'forms::components.wizard.completed-step'"
:icon="$completedIcon ?? 'heroicon-o-check'"
x-cloak="x-cloak"
x-show="getStepIndex(step) > {{ $loop->index }}"
class="fi-fo-wizard-header-step-icon h-6 w-6 text-white"
/>
@if (filled($icon = $step->getIcon()))
<x-filament::icon
:icon="$icon"
x-cloak="x-cloak"
x-show="getStepIndex(step) <= {{ $loop->index }}"
class="fi-fo-wizard-header-step-icon h-6 w-6"
x-bind:class="{
'text-gray-500 dark:text-gray-400': getStepIndex(step) !== {{ $loop->index }},
'text-primary-600 dark:text-primary-500': getStepIndex(step) === {{ $loop->index }},
}"
/>
@else
<span
x-show="getStepIndex(step) <= {{ $loop->index }}"
class="fi-fo-wizard-header-step-indicator text-sm font-medium"
x-bind:class="{
'text-gray-500 dark:text-gray-400':
getStepIndex(step) !== {{ $loop->index }},
'text-primary-600 dark:text-primary-500':
getStepIndex(step) === {{ $loop->index }},
}"
>
{{ str_pad($loop->index + 1, 2, '0', STR_PAD_LEFT) }}
</span>
@endif
</div>
<div class="grid justify-items-start md:w-max md:max-w-60">
@if (! $step->isLabelHidden())
<span
class="fi-fo-wizard-header-step-label text-sm font-medium"
x-bind:class="{
'text-gray-500 dark:text-gray-400':
getStepIndex(step) < {{ $loop->index }},
'text-primary-600 dark:text-primary-400':
getStepIndex(step) === {{ $loop->index }},
'text-gray-950 dark:text-white': getStepIndex(step) > {{ $loop->index }},
}"
>
{{ $step->getLabel() }}
</span>
@endif
@if (filled($description = $step->getDescription()))
<span
class="fi-fo-wizard-header-step-description text-start text-sm text-gray-500 dark:text-gray-400"
>
{{ $description }}
</span>
@endif
</div>
</button>
@if (! $loop->last)
<div
aria-hidden="true"
class="fi-fo-wizard-header-step-separator absolute end-0 hidden h-full w-5 md:block"
>
<svg
fill="none"
preserveAspectRatio="none"
viewBox="0 0 22 80"
class="h-full w-full text-gray-200 dark:text-white/5 rtl:rotate-180"
>
<path
d="M0 -2L20 40L0 82"
stroke-linejoin="round"
stroke="currentcolor"
vector-effect="non-scaling-stroke"
></path>
</svg>
</div>
@endif
</li>
@endforeach
</ol>
@foreach ($getChildComponentContainer()->getComponents() as $step)
{{ $step }}
@endforeach
<div
@class([
'flex items-center justify-between gap-x-3',
'px-6 pb-6' => $isContained,
'mt-6' => ! $isContained,
])
>
<span x-cloak x-on:click="previousStep" x-show="! isFirstStep()">
{{ $getAction('previous') }}
</span>
<span x-show="isFirstStep()">
{{ $getCancelAction() }}
</span>
<span
x-cloak
x-on:click="
$wire.dispatchFormEvent(
'wizard::nextStep',
'{{ $statePath }}',
getStepIndex(step),
)
"
x-show="! isLastStep()"
>
{{ $getAction('next') }}
</span>
<span x-show="isLastStep()">
{{ $getSubmitAction() }}
</span>
</div>
</div>

View File

@@ -0,0 +1,40 @@
@php
$id = $getId();
$isContained = $getContainer()->getParentComponent()->isContained();
$activeStepClasses = \Illuminate\Support\Arr::toCssClasses([
'fi-active',
'p-6' => $isContained,
'mt-6' => ! $isContained,
]);
$inactiveStepClasses = 'invisible absolute h-0 overflow-hidden p-0';
@endphp
<div
x-bind:class="{
@js($activeStepClasses): step === @js($id),
@js($inactiveStepClasses): step !== @js($id),
}"
x-on:expand="
if (! isStepAccessible(@js($id))) {
return
}
step = @js($id)
"
x-ref="step-{{ $id }}"
{{
$attributes
->merge([
'aria-labelledby' => $id,
'id' => $id,
'role' => 'tabpanel',
'tabindex' => '0',
], escape: false)
->merge($getExtraAttributes(), escape: false)
->class(['fi-fo-wizard-step outline-none'])
}}
>
{{ $getChildComponentContainer() }}
</div>