Skip to content

Commit 57a6603

Browse files
Added new options, multiple feature, styling
1 parent 5559387 commit 57a6603

File tree

7 files changed

+215
-33
lines changed

7 files changed

+215
-33
lines changed

README.md

Lines changed: 22 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -18,8 +18,8 @@ composer require codewithdennis/filament-select-tree
1818
## Features
1919
- ✅ Compatible with dark mode
2020
- ✅ Featuring search functionality
21-
- ❌ Multi-select (Coming soon)
22-
- ❌ Relationships
21+
- ✅ Comma seperated multi-select
22+
- ❌ Relationships (Planned)
2323

2424
## Usage
2525

@@ -32,15 +32,34 @@ SelectTree::make('category_id')
3232
->tree(Category::class, 'category_id', 'name', function ($query) {
3333
return $query;
3434
})
35-
35+
3636
// The label 'Category' is assigned to the field.
3737
->label(__('Category'))
38+
39+
// Set a custom placeholder for when no items are selected
40+
->placeholder(__('Your custom placeholder here'))
3841

3942
// Ensures that only leaf nodes can be selected while preventing the selection of groups.
4043
->disabledBranchNode()
4144

4245
// Show the count of children alongside the group's name.
4346
->withCount()
47+
48+
// To disable tags and display a text message instead (e.g., "X items have been selected")
49+
// Tags is always disabled on single select
50+
->disableTags()
51+
52+
// To keep the dropdown open at all times
53+
->alwaysOpen()
54+
55+
// By default, all nodes are independent. Set this to false if you want to display groups when all subnodes are selected.
56+
->showGroupsWhenAllSelected(false)
57+
58+
// By default, the clearable icon is enabled, but you can hide it with:
59+
->clearable(false)
60+
61+
// Enable the option to save multiple values as a string (comma-separated)
62+
->multiple()
4463

4564
// Activates the search functionality for the SelectTree.
4665
->searchable()

resources/css/custom.css

Lines changed: 87 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
outline: 2px solid transparent;
88
outline-offset: 2px;
99
background: transparent;
10+
padding: 8px;
1011
}
1112

1213
.treeselect {
@@ -107,4 +108,90 @@ html.dark .treeselect-list {
107108

108109
html.dark .treeselect-list.treeselect-list--single-select html.dark.treeselect-list__item--single-selected, html.dark .treeselect-list__item--focused, html.dark .treeselect-list__item:hover, html.dark .treeselect-list.treeselect-list--single-select .treeselect-list__item--single-selected, html.dark .treeselect-list__item--focused, html.dark .treeselect-list__item:hover {
109110
background-color: hsla(0, 0%, 100%, .05) !important;
111+
}
112+
113+
html.dark .treeselect-list__item--checked, .treeselect-list__item--checked {
114+
background: transparent;
115+
}
116+
117+
.treeselect-input__tags-element {
118+
--tw-bg-opacity: 1;
119+
--tw-text-opacity: 1;
120+
--tw-ring-offset-shadow: var(--tw-ring-inset) 0 0 0 var(--tw-ring-offset-width) var(--tw-ring-offset-color);
121+
--tw-ring-shadow: var(--tw-ring-inset) 0 0 0 calc(1px + var(--tw-ring-offset-width)) var(--tw-ring-color);
122+
--tw-ring-inset: inset;
123+
--tw-ring-color: rgba(var(--primary-600), 0.1);
124+
align-items: center;
125+
background-color: rgba(var(--primary-50), var(--tw-bg-opacity));
126+
border-radius: 0.375rem;
127+
box-shadow: var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow, 0 0 #0000);
128+
color: rgba(var(--primary-600), var(--tw-text-opacity));
129+
display: inline-flex;
130+
font-size: .75rem;
131+
font-weight: 500;
132+
gap: 0.25rem;
133+
line-height: 1rem;
134+
padding: 0.25rem 0.5rem;
135+
word-break: break-all;
136+
}
137+
138+
html.dark .treeselect-input__tags-element {
139+
--tw-text-opacity: 1;
140+
--tw-ring-color: rgba(var(--primary-400), 0.3);
141+
background-color: rgba(var(--primary-400), .1);
142+
color: rgba(var(--primary-400), var(--tw-text-opacity));
143+
}
144+
145+
.treeselect-list__item-checkbox-container {
146+
border-radius: 0.25rem;
147+
height: 16px;
148+
min-width: 16px;
149+
width: 16px;
150+
}
151+
152+
.treeselect-list__item--checked .treeselect-list__item-checkbox-container, .treeselect-list__item--partial-checked .treeselect-list__item-checkbox-container {
153+
background-color: rgba(var(--primary-600), var(--tw-text-opacity));
154+
}
155+
156+
.treeselect-list__item-checkbox {
157+
transition-duration: 75ms;
158+
background-color: transparent !important;
159+
border: none;
160+
}
161+
162+
.treeselect-list__item-checkbox-container {
163+
background-color: #f8f5f5;
164+
border: none;
165+
}
166+
167+
html.dark .treeselect-list__item-checkbox-container {
168+
border: rgb(255 255 255/var(--tw-text-opacity));
169+
}
170+
171+
html.dark .treeselect-list__item-checkbox-container {
172+
background-color: hsla(0, 0%, 100%, .05);
173+
}
174+
175+
.treeselect-list__item-checkbox-icon {
176+
height: 80%;
177+
left: 0.1rem;
178+
top: 0.1rem;
179+
width: 80%;
180+
}
181+
182+
.treeselect-input__tags-element:hover {
183+
background-color: rgba(0, 0, 0, 0.1);
184+
}
185+
186+
.treeselect-input__tags-element:hover {
187+
background-color: rgba(var(--primary-600), var(--tw-text-opacity));
188+
color: white;
189+
}
190+
191+
.treeselect-input__tags-element:hover .treeselect-input__tags-cross svg {
192+
stroke: rgb(255 255 255/var(--tw-text-opacity));
193+
}
194+
195+
.treeselect-input__tags {
196+
margin-left: 3px;
110197
}

resources/dist/custom.css

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

resources/dist/tree.js

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

resources/js/index.js

Lines changed: 27 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,27 +1,33 @@
11
import Treeselect from 'treeselectjs'
22

33
export default function tree({
4-
state,
5-
name,
6-
options,
7-
searchable,
8-
showCount,
9-
placeholder,
10-
disabledBranchNode,
11-
disabled = false,
12-
isSingleSelect = true,
13-
showTags = false,
14-
clearable = false,
15-
}) {
4+
state,
5+
name,
6+
options,
7+
searchable,
8+
showCount,
9+
placeholder,
10+
disabledBranchNode,
11+
disabled = false,
12+
isSingleSelect = true,
13+
showTags = true,
14+
clearable = true,
15+
isIndependentNodes = true,
16+
alwaysOpen = false
17+
}) {
1618
return {
1719
state,
1820
tree: null,
1921
init() {
22+
const values = this.isSingleSelect
23+
? (this.state !== null ? this.state : '')
24+
: (this.state !== null ? this.state.split(',').map(Number) : '');
25+
2026
this.tree = new Treeselect({
2127
id: `tree-${name}-id`,
2228
ariaLabel: `tree-${name}-label`,
2329
parentHtmlContainer: this.$refs.tree,
24-
value: this.state,
30+
value: values,
2531
options,
2632
searchable,
2733
showCount,
@@ -30,11 +36,17 @@ export default function tree({
3036
disabled,
3137
isSingleSelect,
3238
showTags,
33-
clearable
39+
clearable,
40+
isIndependentNodes,
41+
alwaysOpen,
3442
});
3543

3644
this.tree.srcElement.addEventListener('input', (e) => {
37-
this.state = e.detail;
45+
if (Array.isArray(e.detail)) {
46+
this.state = e.detail.join(",");
47+
} else {
48+
this.state = e.detail;
49+
}
3850
});
3951
}
4052
}

resources/views/select-tree.blade.php

Lines changed: 12 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,17 @@
11
<x-dynamic-component :component="$getFieldWrapperView()" :field="$field">
22
<div
3-
wire:ignore
4-
x-data
5-
x-load-css="[
3+
wire:ignore
4+
x-data
5+
x-load-css="[
66
@js(\Filament\Support\Facades\FilamentAsset::getStyleHref('tree', package: 'codewithdennis/filament-select-tree')),
77
@js(\Filament\Support\Facades\FilamentAsset::getStyleHref('custom', package: 'codewithdennis/filament-select-tree'))
88
]"
99
>
1010
<div
11-
x-ignore
12-
ax-load="visible"
13-
ax-load-src="{{ \Filament\Support\Facades\FilamentAsset::getAlpineComponentSrc('tree', package: 'codewithdennis/filament-select-tree') }}"
14-
x-data="tree({
11+
x-ignore
12+
ax-load="visible"
13+
ax-load-src="{{ \Filament\Support\Facades\FilamentAsset::getAlpineComponentSrc('tree', package: 'codewithdennis/filament-select-tree') }}"
14+
x-data="tree({
1515
name: '{{ $getName() }}',
1616
state: $wire.{{ $applyStateBindingModifiers("\$entangle('{$getStatePath()}')") }},
1717
options: {{ json_encode($getTree()) }},
@@ -20,6 +20,11 @@
2020
placeholder: '{{ $getPlaceholder() }}',
2121
disabledBranchNode: '{{ $getDisabledBranchNode() }}',
2222
disabled: '{{ $isDisabled() }}',
23+
isSingleSelect: '{{ !$getMultiple() }}',
24+
isIndependentNodes: '{{ $getIndependent() }}',
25+
showTags: '{{ $getMultiple() && $getShowTags() }}',
26+
alwaysOpen: '{{ $getAlwaysOpen() }}',
27+
clearable: '{{ $getClearable() }}',
2328
})"
2429
>
2530
<div x-ref="tree"></div>

src/SelectTree.php

Lines changed: 65 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -16,17 +16,16 @@ class SelectTree extends Field
1616
use HasPlaceholder;
1717

1818
protected string $view = 'select-tree::select-tree';
19-
2019
protected bool $withCount = false;
21-
20+
protected bool $multiple = false;
21+
protected bool $alwaysOpen = false;
22+
protected bool $showTags = true;
23+
protected bool $independent = true;
24+
protected bool $clearable = true;
2225
protected bool $disabledBranchNode = false;
23-
2426
protected string $treeModel;
25-
2627
protected string $treeParentKey;
27-
2828
protected string $titleAttribute;
29-
3029
protected ?Closure $modifyQueryUsing;
3130

3231
public function withCount(bool $withCount = true): static
@@ -36,6 +35,41 @@ public function withCount(bool $withCount = true): static
3635
return $this;
3736
}
3837

38+
public function clearable(bool $clearable = true): static
39+
{
40+
$this->clearable = $clearable;
41+
42+
return $this;
43+
}
44+
45+
public function independent(bool $independent = true): static
46+
{
47+
$this->independent = $independent;
48+
49+
return $this;
50+
}
51+
52+
public function showTags(bool $showTags = true): static
53+
{
54+
$this->showTags = $showTags;
55+
56+
return $this;
57+
}
58+
59+
public function alwaysOpen(bool $alwaysOpen = true): static
60+
{
61+
$this->alwaysOpen = $alwaysOpen;
62+
63+
return $this;
64+
}
65+
66+
public function multiple(bool $multiple = true): static
67+
{
68+
$this->multiple = $multiple;
69+
70+
return $this;
71+
}
72+
3973
public function disabledBranchNode(bool $disabledBranchNode = true): static
4074
{
4175
$this->disabledBranchNode = $disabledBranchNode;
@@ -48,11 +82,36 @@ public function getTree(): Collection
4882
return $this->evaluate($this->buildTree());
4983
}
5084

85+
public function getIndependent(): bool
86+
{
87+
return $this->evaluate($this->independent);
88+
}
89+
5190
public function getWithCount(): bool
5291
{
5392
return $this->evaluate($this->withCount);
5493
}
5594

95+
public function getMultiple(): bool
96+
{
97+
return $this->evaluate($this->multiple);
98+
}
99+
100+
public function getClearable(): bool
101+
{
102+
return $this->evaluate($this->clearable);
103+
}
104+
105+
public function getAlwaysOpen(): bool
106+
{
107+
return $this->evaluate($this->alwaysOpen);
108+
}
109+
110+
public function getShowTags(): bool
111+
{
112+
return $this->evaluate($this->showTags);
113+
}
114+
56115
public function getDisabledBranchNode(): bool
57116
{
58117
return $this->evaluate($this->disabledBranchNode);

0 commit comments

Comments
 (0)