Skip to content

Commit 259cbd5

Browse files
committed
Toggle button webcomponent
1 parent 0eefa53 commit 259cbd5

File tree

3 files changed

+217
-10
lines changed

3 files changed

+217
-10
lines changed

ts/WoltLabSuite/WebComponent/index.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,8 +12,9 @@ import "./fa-metadata.js";
1212
import "./fa-brand.ts";
1313
import "./fa-icon.ts";
1414
import "./woltlab-core-date-time.ts";
15-
import "./woltlab-core-file-upload.ts"
15+
import "./woltlab-core-file-upload.ts";
1616
import "./woltlab-core-loading-indicator.ts";
1717
import "./woltlab-core-notice.ts";
1818
import "./woltlab-core-pagination.ts";
1919
import "./woltlab-core-reaction-summary.ts";
20+
import "./woltlab-core-toggle-button.ts";
Lines changed: 153 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,153 @@
1+
/**
2+
* `<woltlab-core-toggle-button>` creates a toggle button.
3+
* Usage: `<woltlab-core-toggle-button name="foo" checked></woltlab-core-toggle-button>`
4+
*
5+
* @author Marcel Werk
6+
* @copyright 2001-2024 WoltLab GmbH
7+
* @license GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
8+
* @since 6.2
9+
*/
10+
11+
{
12+
class WoltlabCoreToggleButtonElement extends HTMLElement {
13+
static get observedAttributes() {
14+
return ["checked"];
15+
}
16+
17+
constructor() {
18+
super();
19+
20+
const shadow = this.attachShadow({ mode: "open" });
21+
const style = document.createElement("style");
22+
style.textContent = `
23+
:host {
24+
display: inline-flex;
25+
vertical-align: middle;
26+
}
27+
28+
[part="track"] {
29+
position: relative;
30+
border-radius: 14px;
31+
width: 40px;
32+
height: 24px;
33+
cursor: pointer;
34+
background-color: var(--wcfSidebarDimmedText);
35+
transition: 0.4s;
36+
}
37+
38+
:host([checked]) [part="track"] {
39+
background-color: var(--wcfStatusSuccessText);
40+
}
41+
42+
[part="slider"] {
43+
background-color: white;
44+
border-radius: 50%;
45+
position: absolute;
46+
top: 2px;
47+
bottom: 2px;
48+
left: 0;
49+
align-items: center;
50+
display: flex;
51+
transition: 0.4s;
52+
transform: translateX(2px);
53+
}
54+
55+
:host([checked]) [part="slider"] {
56+
transform: translateX(18px);
57+
}
58+
59+
::slotted(fa-icon) {
60+
color: var(--wcfSidebarDimmedText);
61+
}
62+
63+
:host([checked]) ::slotted(fa-icon) {
64+
color: var(--wcfStatusSuccessText);
65+
}
66+
`;
67+
68+
const track = document.createElement("div");
69+
track.setAttribute("part", "track");
70+
const slider = document.createElement("span");
71+
slider.setAttribute("part", "slider");
72+
track.append(slider);
73+
const iconSlot = document.createElement("slot");
74+
iconSlot.name = "icon";
75+
slider.append(iconSlot);
76+
const checkboxSlot = document.createElement("slot");
77+
checkboxSlot.name = "checkbox";
78+
shadow.append(style, track, checkboxSlot);
79+
80+
this.addEventListener("click", (event) => {
81+
event.stopPropagation();
82+
event.preventDefault();
83+
this.toggle();
84+
});
85+
this.addEventListener("keydown", (event) => {
86+
if (event.key === "Enter" || event.key === " ") {
87+
event.preventDefault();
88+
this.toggle();
89+
}
90+
});
91+
}
92+
93+
connectedCallback() {
94+
this.innerHTML = "";
95+
96+
this.#renderCheckbox();
97+
this.#renderIcon();
98+
99+
this.setAttribute("role", "switch");
100+
this.setAttribute("tabindex", "0");
101+
}
102+
103+
#renderCheckbox(): void {
104+
if (!this.hasAttribute("name")) {
105+
return;
106+
}
107+
108+
const checkbox = document.createElement("input");
109+
checkbox.type = "checkbox";
110+
checkbox.name = this.getAttribute("name")!;
111+
checkbox.value = this.hasAttribute("value") ? this.getAttribute("value")! : "1";
112+
checkbox.checked = this.checked;
113+
checkbox.hidden = true;
114+
checkbox.slot = "checkbox";
115+
this.append(checkbox);
116+
}
117+
118+
#renderIcon(): void {
119+
const icon = document.createElement("fa-icon");
120+
icon.setIcon(this.checked ? "check" : "xmark");
121+
icon.slot = "icon";
122+
this.append(icon);
123+
}
124+
125+
get checked(): boolean {
126+
return this.hasAttribute("checked");
127+
}
128+
129+
set checked(value: boolean) {
130+
this.toggleAttribute("checked", value);
131+
this.querySelector("fa-icon")?.setIcon(value ? "check" : "xmark");
132+
}
133+
134+
toggle(): void {
135+
this.checked = !this.checked;
136+
}
137+
138+
attributeChangedCallback(name: string) {
139+
if (name === "checked") {
140+
this.setAttribute("aria-checked", this.checked.toString());
141+
this.dispatchEvent(
142+
new CustomEvent("change", {
143+
detail: {
144+
checked: this.checked,
145+
},
146+
}),
147+
);
148+
}
149+
}
150+
}
151+
152+
window.customElements.define("woltlab-core-toggle-button", WoltlabCoreToggleButtonElement);
153+
}

0 commit comments

Comments
 (0)