Skip to content

Commit 05ecb0f

Browse files
committed
Building a tri-state switch / toggle in Alpine.js.
1 parent 67accf2 commit 05ecb0f

File tree

3 files changed

+222
-0
lines changed

3 files changed

+222
-0
lines changed

README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ with.
1010

1111
## My JavaScript Demos - I Love JavaScript!
1212

13+
* [Creating A Tri-State Switch In Alpine.js](https://bennadel.github.io/JavaScript-Demos/demos/tri-state-toggle)
1314
* [Highlighting Dynamic Parts Of A Pretty-Printed JSON Value](https://bennadel.github.io/JavaScript-Demos/demos/highlighting-json)
1415
* [Exploring Randomness In JavaScript](https://bennadel.github.io/JavaScript-Demos/demos/web-crypto-rand)
1516
* [Color Palette Utility In Alpine.js](https://bennadel.github.io/JavaScript-Demos/demos/color-palette)

demos/tri-state-toggle/index.htm

Lines changed: 145 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,145 @@
1+
<!doctype html>
2+
<html lang="en">
3+
<head>
4+
<meta charset="utf-8" />
5+
<meta name="viewport" content="width=device-width, initial-scale=1" />
6+
<title>
7+
Creating A Tri-State Switch In Alpine.js
8+
</title>
9+
<link rel="stylesheet" type="text/css" href="./main.css" />
10+
</head>
11+
<body>
12+
13+
<h1>
14+
Creating A Tri-State Switch In Alpine.js
15+
</h1>
16+
17+
<div x-data="Demo">
18+
<p>
19+
<button
20+
@click="setMode( 'None' )"
21+
:class="{ active: ( selected === 'None' ) }">
22+
None
23+
</button>
24+
<button
25+
@click="setMode( 'Some' )"
26+
:class="{ active: ( selected === 'Some' ) }">
27+
Some
28+
</button>
29+
<button
30+
@click="setMode( 'All' )"
31+
:class="{ active: ( selected === 'All' ) }">
32+
All
33+
</button>
34+
</p>
35+
36+
<!--
37+
The x-effect directive works by re-evaluating the overall expression every
38+
time one of the embedded dependencies changes. As such, any time either the
39+
"options" or the "selected" parent state is updated, the tri-switch
40+
component's reconcile() method will be invoked and the updated state will be
41+
passed-in, allowing the internal tri-switch state to be mapped from the parent
42+
scope state (ie, one-way data binding).
43+
-->
44+
<div
45+
x-data="TriSwitch()"
46+
x-effect="reconcile({
47+
states: options,
48+
state: selected
49+
})"
50+
@click="cycleMode()"
51+
class="tri-switch"
52+
:class="{
53+
on: ( phase === 'on' ),
54+
off: ( phase === 'off' ),
55+
partial: ( phase === 'partial' )
56+
}">
57+
<div class="tri-switch__track">
58+
<div class="tri-switch__thumb"></div>
59+
</div>
60+
</div>
61+
62+
</div>
63+
64+
<script type="text/javascript" src="../../vendor/alpine/3.13.5/alpine.3.13.5.min.js" defer></script>
65+
<script type="text/javascript">
66+
67+
function Demo() {
68+
69+
return {
70+
options: [ "None", "Some", "All" ],
71+
selected: "Some",
72+
73+
// Public methods.
74+
cycleMode,
75+
setMode,
76+
};
77+
78+
// ---
79+
// PUBLIC METHODS.
80+
// ---
81+
82+
/**
83+
* I cycle to the next selectable mode.
84+
*/
85+
function cycleMode() {
86+
87+
var selectedIndex = this.options.indexOf( this.selected );
88+
89+
// If the NEXT index is undefined, circle back to the front of the modes.
90+
if ( this.options[ ++selectedIndex ] === undefined ) {
91+
92+
selectedIndex = 0;
93+
94+
}
95+
96+
this.selected = this.options[ selectedIndex ];
97+
98+
}
99+
100+
/**
101+
* I set the selected mode.
102+
*/
103+
function setMode( newMode ) {
104+
105+
this.selected = newMode;
106+
107+
}
108+
109+
}
110+
111+
// --------------------------------------------------------------------------- //
112+
// --------------------------------------------------------------------------- //
113+
114+
function TriSwitch() {
115+
116+
return {
117+
phases: [ "off", "partial", "on" ],
118+
phase: "off",
119+
120+
// Public methods.
121+
reconcile,
122+
};
123+
124+
// ---
125+
// PUBLIC METHODS.
126+
// ---
127+
128+
/**
129+
* I reconcile the parent scope state with the local input bindings.
130+
*/
131+
function reconcile( inputs ) {
132+
133+
this.phase = (
134+
this.phases[ inputs.states.indexOf( inputs.state ) ] ||
135+
this.phases[ 0 ]
136+
);
137+
138+
}
139+
140+
}
141+
142+
</script>
143+
144+
</body>
145+
</html>

demos/tri-state-toggle/main.css

Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,76 @@
1+
2+
html {
3+
box-sizing: border-box ;
4+
}
5+
html *,
6+
html *:before,
7+
html *:after {
8+
box-sizing: inherit ;
9+
}
10+
11+
body {
12+
font-family: monospace ;
13+
font-size: 18px ;
14+
line-height: 1.4 ;
15+
}
16+
17+
button {
18+
font-family: inherit ;
19+
font-size: inherit ;
20+
line-height: inherit ;
21+
}
22+
23+
button.active {
24+
outline: 2px solid deeppink ;
25+
outline-offset: 2px ;
26+
}
27+
28+
.tri-switch {
29+
color: deeppink ;
30+
user-select: none ;
31+
}
32+
.tri-switch__track {
33+
background-color: #f0f0f0 ;
34+
border: 1px solid #767676 ;
35+
border-radius: 24px ;
36+
height: 24px ;
37+
position: relative ;
38+
width: 44px ;
39+
}
40+
.tri-switch__thumb {
41+
background-color: #ffffff ;
42+
border: 1px solid #cccccc ;
43+
border-radius: 24px ;
44+
height: 16px ;
45+
left: 0 ;
46+
position: absolute ;
47+
top: 3px ;
48+
width: 16px ;
49+
}
50+
51+
.tri-switch__track::before {
52+
background-color: currentColor ;
53+
border-radius: 24px ;
54+
content: "" ;
55+
inset: 0 ;
56+
position: absolute ;
57+
}
58+
.tri-switch.off .tri-switch__track::before {
59+
opacity: 0.0 ;
60+
}
61+
.tri-switch.partial .tri-switch__track::before {
62+
opacity: 0.5 ;
63+
}
64+
.tri-switch.on .tri-switch__track::before {
65+
opacity: 1.0 ;
66+
}
67+
68+
.tri-switch.off .tri-switch__thumb {
69+
transform: translate3d( 3px, 0, 0 ) ;
70+
}
71+
.tri-switch.partial .tri-switch__thumb {
72+
transform: translate3d( 13px, 0, 0 ) ;
73+
}
74+
.tri-switch.on .tri-switch__thumb {
75+
transform: translate3d( 21px, 0, 0 ) ;
76+
}

0 commit comments

Comments
 (0)