Skip to content

Commit 7d4dc53

Browse files
authored
[Feature] Datalists for text inputs and drop PHP 8.1 support (#4)
* Add datalist functionality to text inputs * Fix styling * Remove deprecated config value * wip * wip * wip * wip * wip * wip * wip * wip * wip * wip * Reduce duplicate code * Fix styling * wip --------- Co-authored-by: Baspa <[email protected]>
1 parent 8fc3110 commit 7d4dc53

File tree

8 files changed

+322
-235
lines changed

8 files changed

+322
-235
lines changed

.github/workflows/phpstan.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ jobs:
1616
- name: Setup PHP
1717
uses: shivammathur/setup-php@v2
1818
with:
19-
php-version: '8.1'
19+
php-version: '8.2'
2020
coverage: none
2121

2222
- name: Install composer dependencies

.github/workflows/run-tests.yml

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -13,13 +13,19 @@ jobs:
1313
fail-fast: true
1414
matrix:
1515
os: [ubuntu-latest, windows-latest]
16-
php: [8.2, 8.1]
17-
laravel: [10.*]
16+
php: [8.3, 8.2]
17+
laravel: [10.*, 11.*]
1818
stability: [prefer-lowest, prefer-stable]
1919
include:
2020
- laravel: 10.*
2121
testbench: 8.*
22-
carbon: 2.*
22+
carbon: ^2.67
23+
- laravel: 11.*
24+
testbench: 9.*
25+
carbon: ^2.72.2
26+
exclude:
27+
- laravel: 11.*
28+
php: 8.1
2329

2430
name: P${{ matrix.php }} - L${{ matrix.laravel }} - ${{ matrix.stability }} - ${{ matrix.os }}
2531

composer.json

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -20,19 +20,19 @@
2020
}
2121
],
2222
"require": {
23-
"php": "^8.1",
23+
"php": "^8.2",
2424
"baspa/laravel-timezones": "^1.2",
2525
"filament/filament": "3.3.2",
2626
"saade/filament-adjacency-list": "3.2.1",
2727
"spatie/laravel-package-tools": "^1.15.0",
2828
"staudenmeir/laravel-adjacency-list": "^1.0"
2929
},
3030
"require-dev": {
31-
"laravel/pint": "^1.0",
32-
"nunomaduro/collision": "^7.9",
31+
"laravel/pint": "^1.14",
32+
"nunomaduro/collision": "^8.1.1||^7.10.0",
3333
"nunomaduro/larastan": "^2.0.1",
34-
"orchestra/testbench": "^8.0",
35-
"pestphp/pest": "^2.1",
34+
"orchestra/testbench": "^9.0.0||^8.22.0",
35+
"pestphp/pest": "^2.34",
3636
"pestphp/pest-plugin-arch": "^2.0",
3737
"pestphp/pest-plugin-laravel": "^2.0",
3838
"phpstan/extension-installer": "^1.1",

phpstan.neon.dist

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,5 +10,4 @@ parameters:
1010
tmpDir: build/phpstan
1111
checkOctaneCompatibility: true
1212
checkModelProperties: true
13-
checkMissingIterableValueType: false
1413

src/Concerns/HasDatalist.php

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
<?php
2+
3+
namespace Backstage\Fields\Concerns;
4+
5+
use Filament\Forms\Components\TagsInput;
6+
7+
trait HasDatalist
8+
{
9+
use HasSelectableValues;
10+
11+
public static function addDatalistToInput(mixed $input, mixed $field): mixed
12+
{
13+
return static::addValuesToInput(
14+
input: $input,
15+
field: $field,
16+
type: 'datalistType',
17+
method: 'datalist'
18+
);
19+
}
20+
21+
public static function getDatalistConfig(): array
22+
{
23+
return array_merge(static::getSelectableValuesConfig(), [
24+
'datalistType' => null,
25+
]);
26+
}
27+
28+
public function datalistFormFields(): \Filament\Forms\Components\Fieldset
29+
{
30+
return $this->selectableValuesFormFields(
31+
type: 'datalistType',
32+
label: 'Datalist',
33+
arrayComponent: TagsInput::class
34+
);
35+
}
36+
}

src/Concerns/HasOptions.php

Lines changed: 17 additions & 225 deletions
Original file line numberDiff line numberDiff line change
@@ -2,243 +2,35 @@
22

33
namespace Backstage\Fields\Concerns;
44

5-
use Filament\Forms;
6-
use Filament\Forms\Components\Fieldset;
7-
use Filament\Forms\Components\Grid;
8-
use Filament\Forms\Components\Repeater;
9-
use Illuminate\Support\Facades\Schema;
10-
use Illuminate\Support\Str;
5+
use Filament\Forms\Components\KeyValue;
116

127
trait HasOptions
138
{
9+
use HasSelectableValues;
10+
1411
public static function addOptionsToInput(mixed $input, mixed $field): mixed
1512
{
16-
if (isset($field->config['optionType']) && $field->config['optionType'] === 'relationship') {
17-
$options = [];
18-
19-
foreach ($field->config['relations'] as $relation) {
20-
$resources = config('backstage.fields.selectable_resources');
21-
$resourceClass = collect($resources)->first(function ($resource) use ($relation) {
22-
$res = new $resource;
23-
$model = $res->getModel();
24-
$model = new $model;
25-
26-
if (! isset($relation['resource'])) {
27-
return false;
28-
}
29-
30-
return $model->getTable() === $relation['resource'];
31-
});
32-
33-
if (! $resourceClass) {
34-
continue;
35-
}
36-
37-
$resource = new $resourceClass;
38-
$model = $resource->getModel();
39-
$query = $model::query();
40-
41-
// Apply filters if they exist
42-
if (isset($relation['relationValue_filters'])) {
43-
foreach ($relation['relationValue_filters'] as $filter) {
44-
if (isset($filter['column'], $filter['operator'], $filter['value'])) {
45-
$query->where($filter['column'], $filter['operator'], $filter['value']);
46-
}
47-
}
48-
}
49-
50-
$results = $query->get();
51-
52-
if ($results->isEmpty()) {
53-
continue;
54-
}
55-
56-
$opts = $results->pluck($relation['relationValue'] ?? 'name', $relation['relationKey'])->toArray();
57-
58-
if (count($opts) === 0) {
59-
continue;
60-
}
61-
62-
$options[] = $opts;
63-
}
64-
65-
if (! empty($options)) {
66-
$options = array_merge(...$options);
67-
$input->options($options);
68-
}
69-
}
70-
71-
if (isset($field->config['optionType']) && $field->config['optionType'] === 'array') {
72-
$input->options($field->config['options']);
73-
}
74-
75-
return $input;
13+
return static::addValuesToInput(
14+
input: $input,
15+
field: $field,
16+
type: 'optionType',
17+
method: 'options'
18+
);
7619
}
7720

7821
public static function getOptionsConfig(): array
7922
{
80-
return [
23+
return array_merge(static::getSelectableValuesConfig(), [
8124
'optionType' => null,
82-
'options' => [],
83-
'descriptions' => [],
84-
'relations' => [],
85-
'contentType' => null,
86-
'relationKey' => null,
87-
'relationValue' => null,
88-
];
25+
]);
8926
}
9027

91-
public function optionFormFields(): Fieldset
28+
public function optionFormFields(): \Filament\Forms\Components\Fieldset
9229
{
93-
return Forms\Components\Fieldset::make('Options')
94-
->columnSpanFull()
95-
->label(__('Options'))
96-
->schema([
97-
Forms\Components\Grid::make(2)
98-
->schema([
99-
Forms\Components\Select::make('config.optionType')
100-
->options([
101-
'array' => __('Array'),
102-
'relationship' => __('Relationship'),
103-
])
104-
->searchable()
105-
->live(onBlur: true)
106-
->reactive()
107-
->label(__('Type')),
108-
// Array options
109-
Forms\Components\KeyValue::make('config.options')
110-
->label(__('Options'))
111-
->columnSpanFull()
112-
->visible(fn (Forms\Get $get): bool => $get('config.optionType') == 'array')
113-
->required(fn (Forms\Get $get): bool => $get('config.optionType') == 'array'),
114-
// Relationship options
115-
Repeater::make('config.relations')
116-
->label(__('Relations'))
117-
->schema([
118-
Grid::make()
119-
->columns(2)
120-
->schema([
121-
Forms\Components\Select::make('resource')
122-
->label(__('Resource'))
123-
->searchable()
124-
->preload()
125-
->columnSpanFull()
126-
->live(debounce: 250)
127-
->afterStateUpdated(function (Forms\Set $set, ?string $state) {
128-
$resources = config('backstage.fields.selectable_resources');
129-
$resourceClass = collect($resources)->first(function ($resource) use ($state) {
130-
$res = new $resource;
131-
$model = $res->getModel();
132-
$model = new $model;
133-
134-
return $model->getTable() === $state;
135-
});
136-
137-
if (! $resourceClass) {
138-
return;
139-
}
140-
141-
$resource = new $resourceClass;
142-
$model = $resource->getModel();
143-
$model = new $model;
144-
145-
// Get all column names from the table
146-
$columns = Schema::getColumnListing($model->getTable());
147-
148-
// Create options array with column names
149-
$columnOptions = collect($columns)->mapWithKeys(function ($column) {
150-
return [$column => Str::title($column)];
151-
})->toArray();
152-
153-
$set('relationValue', null);
154-
$set('relationValue_options', $columnOptions);
155-
})
156-
->options(function () {
157-
$resources = config('backstage.fields.selectable_resources');
158-
159-
return collect($resources)->map(function ($resource) {
160-
$res = new $resource;
161-
$model = $res->getModel();
162-
$model = new $model;
163-
164-
return [
165-
$model->getTable() => Str::title($model->getTable()),
166-
];
167-
})
168-
->collapse()
169-
->toArray();
170-
})
171-
->noSearchResultsMessage(__('No types found'))
172-
->required(fn (Forms\Get $get): bool => $get('config.optionType') == 'relationship'),
173-
Forms\Components\Hidden::make('relationKey')
174-
->default('ulid')
175-
->label(__('Key'))
176-
->required(fn (Forms\Get $get): bool => $get('config.optionType') == 'relationship'),
177-
Forms\Components\Repeater::make('relationValue_filters')
178-
->label(__('Filters'))
179-
->visible(fn (Forms\Get $get): bool => ! empty($get('resource')))
180-
->schema([
181-
Forms\Components\Grid::make(3)
182-
->schema([
183-
Forms\Components\Select::make('column')
184-
->options(fn (\Filament\Forms\Get $get) => $get('../../relationValue_options') ?? [
185-
'slug' => __('Slug'),
186-
'name' => __('Name'),
187-
])
188-
->live()
189-
->label(__('Column')),
190-
Forms\Components\Select::make('operator')
191-
->options([
192-
'=' => __('Equal'),
193-
'!=' => __('Not equal'),
194-
'>' => __('Greater than'),
195-
'<' => __('Less than'),
196-
'>=' => __('Greater than or equal to'),
197-
'<=' => __('Less than or equal to'),
198-
'LIKE' => __('Like'),
199-
'NOT LIKE' => __('Not like'),
200-
])
201-
->label(__('Operator')),
202-
Forms\Components\TextInput::make('value')
203-
->datalist(function (Forms\Get $get) {
204-
$resource = $get('../../resource');
205-
$column = $get('column');
206-
207-
if (! $resource || ! $column) {
208-
return [];
209-
}
210-
211-
$resources = config('backstage.fields.selectable_resources');
212-
$resourceClass = collect($resources)->first(function ($r) use ($resource) {
213-
$res = new $r;
214-
$model = $res->getModel();
215-
$model = new $model;
216-
217-
return $model->getTable() === $resource;
218-
});
219-
220-
if (! $resourceClass) {
221-
return [];
222-
}
223-
224-
$resource = new $resourceClass;
225-
$model = $resource->getModel();
226-
227-
return $model::query()
228-
->select($column)
229-
->distinct()
230-
->pluck($column)
231-
->toArray();
232-
})
233-
->label(__('Value')),
234-
]),
235-
])
236-
->columnSpanFull(),
237-
]),
238-
])
239-
->visible(fn (Forms\Get $get): bool => $get('config.optionType') == 'relationship')
240-
->columnSpanFull(),
241-
]),
242-
]);
30+
return $this->selectableValuesFormFields(
31+
type: 'optionType',
32+
label: 'Options',
33+
arrayComponent: KeyValue::class
34+
);
24335
}
24436
}

0 commit comments

Comments
 (0)