Skip to content
This repository was archived by the owner on Mar 18, 2025. It is now read-only.

Commit ee88223

Browse files
committed
Add switch-toggle component
1 parent 2547a71 commit ee88223

File tree

8 files changed

+740
-1
lines changed

8 files changed

+740
-1
lines changed

config/form-components.php

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,11 @@
6363
'view' => 'form-components::components.choice.checkbox-or-radio',
6464
],
6565

66+
'switch-toggle' => [
67+
'class' => Components\Choice\SwitchToggle::class,
68+
'view' => 'form-components::components.choice.switch-toggle',
69+
],
70+
6671
'select' => [
6772
'class' => Components\Inputs\Select::class,
6873
'view' => 'form-components::components.inputs.select',

resources/lang/en/messages.php

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,4 +10,5 @@
1010
'custom_select_empty_text' => 'No options available...',
1111
'date_picker_placeholder' => 'Y-m-d',
1212
'timezone_select_placeholder' => 'Select a timezone',
13+
'switch_button_label' => 'Turn on',
1314
];

resources/sass/form-components.scss

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,3 +9,4 @@
99
@import 'utils/addon';
1010
@import 'utils/flatpickr';
1111
@import 'utils/files';
12+
@import 'utils/switch';

resources/sass/utils/_switch.scss

Lines changed: 182 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,182 @@
1+
.switch-toggle {
2+
@apply relative
3+
flex-shrink-0
4+
inline-flex
5+
rounded-full
6+
cursor-pointer
7+
focus:outline-none
8+
focus:ring-2
9+
focus:ring-offset-2
10+
focus:ring-primary-500;
11+
}
12+
13+
.switch-toggle-simple {
14+
@apply h-6
15+
w-11
16+
border-2
17+
border-transparent
18+
transition-colors
19+
ease-in-out
20+
duration-200
21+
bg-blue-gray-200;
22+
23+
&.pressed {
24+
@apply bg-primary-600;
25+
}
26+
}
27+
28+
.switch-toggle-short {
29+
@apply items-center
30+
justify-center
31+
h-5
32+
w-10;
33+
}
34+
35+
.switch-toggle-button {
36+
@apply inline-block
37+
h-5
38+
w-5
39+
rounded-full
40+
bg-white
41+
shadow
42+
transform
43+
ring-0
44+
transition
45+
ease-in-out
46+
duration-200
47+
translate-x-0;
48+
49+
&.pressed {
50+
@apply translate-x-5;
51+
}
52+
}
53+
54+
.switch-toggle-short-bg {
55+
@apply absolute
56+
h-4
57+
w-9
58+
mx-auto
59+
rounded-full
60+
transition-colors
61+
ease-in-out
62+
duration-200
63+
bg-gray-200;
64+
65+
&.pressed {
66+
@apply bg-primary-600;
67+
}
68+
}
69+
70+
.switch-toggle-short-button {
71+
@apply absolute
72+
left-0
73+
inline-block
74+
h-5
75+
w-5
76+
border
77+
border-blue-gray-200
78+
rounded-full
79+
bg-white
80+
shadow
81+
transform
82+
ring-0
83+
transition-transform
84+
ease-in-out
85+
duration-200
86+
translate-x-0;
87+
88+
&.pressed {
89+
@apply translate-x-5;
90+
}
91+
}
92+
93+
.switch-toggle {
94+
&.disabled,
95+
&[disabled] {
96+
@apply cursor-not-allowed;
97+
98+
.switch-toggle-simple {
99+
@apply opacity-75;
100+
}
101+
}
102+
}
103+
104+
.switch-toggle {
105+
&.disabled,
106+
&[disabled] {
107+
@apply cursor-not-allowed;
108+
109+
&.switch-toggle-simple,
110+
.switch-toggle-short-bg {
111+
@apply bg-cool-gray-200;
112+
}
113+
114+
&.switch-toggle-simple.pressed ,
115+
.switch-toggle-short-bg.pressed {
116+
@apply bg-primary-400 opacity-75;
117+
}
118+
119+
.switch-toggle-button,
120+
.switch-toggle-short-button {
121+
@apply bg-blue-gray-100;
122+
}
123+
}
124+
}
125+
126+
@responsive {
127+
.switch-toggle-simple {
128+
&.switch-toggle--sm {
129+
@apply h-5 w-10;
130+
131+
.switch-toggle-button {
132+
@apply h-4 w-4;
133+
}
134+
135+
.switch-toggle-button.pressed {
136+
@apply translate-x-5;
137+
}
138+
}
139+
140+
&.switch-toggle--base {
141+
@apply h-6 w-11;
142+
143+
.switch-toggle-button {
144+
@apply h-5 w-5;
145+
}
146+
147+
.switch-toggle-button.pressed {
148+
@apply translate-x-5;
149+
}
150+
}
151+
152+
&.switch-toggle--lg {
153+
@apply h-8 w-14;
154+
155+
.switch-toggle-button {
156+
@apply h-7 w-7;
157+
}
158+
159+
.switch-toggle-button.pressed {
160+
@apply translate-x-6;
161+
}
162+
}
163+
}
164+
165+
.switch-toggle-short {
166+
&.switch-toggle--lg {
167+
@apply h-7 w-12;
168+
169+
.switch-toggle-short-bg {
170+
@apply h-5 w-11;
171+
}
172+
173+
.switch-toggle-short-button {
174+
@apply h-6 w-6;
175+
176+
&.pressed {
177+
@apply translate-x-6;
178+
}
179+
}
180+
}
181+
}
182+
}
Lines changed: 91 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,91 @@
1+
<div x-data="{
2+
onValue: {{ json_encode($onValue) }},
3+
offValue: {{ json_encode($offValue) }},
4+
@if ($attributes->whereStartsWith('wire:model')->first())
5+
value: @entangle($attributes->wire('model')),
6+
@else
7+
value: {{ json_encode($value) }},
8+
@endif
9+
get isPressed() {
10+
return this.value === this.onValue;
11+
},
12+
toggle() {
13+
this.value = this.isPressed ? this.offValue : this.onValue;
14+
},
15+
}"
16+
wire:ignore.self
17+
class="{{ $getContainerClass() }}"
18+
>
19+
@if ($label && $labelPosition === 'left')
20+
<span x-on:click="$refs.button.click(); $refs.button.focus();"
21+
class="flex-grow switch-toggle-label form-label"
22+
id="{{ $labelId() }}"
23+
>
24+
{{ $label }}
25+
</span>
26+
@endif
27+
28+
<button x-bind:aria-pressed="JSON.stringify(isPressed)"
29+
x-on:click="toggle()"
30+
x-ref="button"
31+
x-cloak
32+
type="button"
33+
@if ($id) id="{{ $id }}" @endif
34+
@if ($label) aria-labelledby="{{ $labelId() }}" @endif
35+
{{ $attributes->except(['type', 'wire:model', 'wire:model.defer'])->merge(['class' => $buttonClass()]) }}
36+
x-bind:class="{ 'pressed': isPressed }"
37+
@if ($disabled) disabled @endif
38+
>
39+
<span class="sr-only">{{ __($buttonLabel) }}</span>
40+
41+
@if ($short)
42+
<span aria-hidden="true"
43+
class="switch-toggle-short-bg"
44+
x-bind:class="{ 'pressed': isPressed }"
45+
>
46+
</span>
47+
48+
<span aria-hidden="true"
49+
class="switch-toggle-short-button"
50+
x-bind:class="{ 'pressed': isPressed }"
51+
>
52+
</span>
53+
@else
54+
<span aria-hidden="true"
55+
class="switch-toggle-button"
56+
x-bind:class="{ 'pressed': isPressed }"
57+
>
58+
@if ($offIcon)
59+
<span class="absolute inset-0 h-full w-full flex items-center justify-center transition-opacity"
60+
x-bind:class="{ 'opacity-0 ease-out duration-100': isPressed, 'opacity-100 ease-in duration-200': ! isPressed }"
61+
aria-hidden="true"
62+
>
63+
{{ $offIcon }}
64+
</span>
65+
@endif
66+
67+
@if ($onIcon)
68+
<span class="absolute inset-0 h-full w-full flex items-center justify-center transition-opacity"
69+
x-bind:class="{ 'opacity-100 ease-in duration-200': isPressed, 'opacity-0 ease-out duration-100': ! isPressed }"
70+
aria-hidden="true"
71+
>
72+
{{ $onIcon }}
73+
</span>
74+
@endif
75+
</span>
76+
@endif
77+
</button>
78+
79+
@if ($label && $labelPosition === 'right')
80+
<span x-on:click="$refs.button.click(); $refs.button.focus()"
81+
class="ml-3 switch-toggle-label form-label"
82+
id="{{ $labelId() }}"
83+
>
84+
{{ $label }}
85+
</span>
86+
@endif
87+
88+
@if ($name)
89+
<input type="hidden" name="{{ $name }}" x-bind:value="JSON.stringify(value)" />
90+
@endif
91+
</div>

src/Components/Choice/Checkbox.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ public function __construct(
1717
public mixed $value = null,
1818
public null|string $label = null,
1919
public null|string $description = '',
20-
public bool $checked = false
20+
public bool $checked = false,
2121
) {
2222
$this->id = $this->id ?? $this->name;
2323
$this->checked = (bool) old($this->name, $this->checked);
Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace Rawilk\FormComponents\Components\Choice;
6+
7+
use Illuminate\Support\Str;
8+
use Rawilk\FormComponents\Components\BladeComponent;
9+
use Rawilk\FormComponents\Concerns\HandlesValidationErrors;
10+
11+
class SwitchToggle extends BladeComponent
12+
{
13+
use HandlesValidationErrors;
14+
15+
protected static array $assets = ['alpine'];
16+
17+
private string $labelId;
18+
19+
public function __construct(
20+
public null|string $name = null,
21+
public null|string $id = null,
22+
public mixed $value = false,
23+
public mixed $onValue = true,
24+
public mixed $offValue = false,
25+
public null|string $containerClass = null,
26+
public bool $short = false,
27+
public null|string $label = null,
28+
public string $labelPosition = 'right',
29+
public null|string $offIcon = null, // doesn't apply to short mode
30+
public null|string $onIcon = null, // doesn't apply to short mode
31+
public null|string $buttonLabel = 'form-components::messages.switch_button_label',
32+
public null|string $size = null,
33+
public bool $disabled = false,
34+
) {
35+
$this->id = $this->id ?? $this->name;
36+
$this->labelId = $this->id ?? Str::random(8);
37+
}
38+
39+
public function labelId(): string
40+
{
41+
return "{$this->labelId}-label";
42+
}
43+
44+
public function buttonClass(): string
45+
{
46+
return collect([
47+
'switch-toggle',
48+
$this->short ? 'switch-toggle-short' : 'switch-toggle-simple',
49+
$this->size ? "switch-toggle--{$this->size}" : null,
50+
$this->disabled ? 'disabled' : null,
51+
])->filter()->implode(' ');
52+
}
53+
54+
public function getContainerClass(): string
55+
{
56+
return collect([
57+
'flex',
58+
'items-center',
59+
$this->labelPosition === 'left' ? 'justify-between' : null,
60+
$this->containerClass,
61+
])->filter()->implode(' ');
62+
}
63+
}

0 commit comments

Comments
 (0)