Skip to content

Commit aec0dfd

Browse files
authored
feat(ui): use motion for page, tab, switch and press gestures animation (#2406)
1 parent a80daab commit aec0dfd

17 files changed

Lines changed: 1781 additions & 123 deletions

File tree

bun.lock

Lines changed: 1180 additions & 21 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package-lock.json

Lines changed: 70 additions & 3 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -182,6 +182,7 @@
182182
"markdown-it-texmath": "^1.0.0",
183183
"mermaid": "^11.14.0",
184184
"mime-types": "^3.0.2",
185+
"motion": "^12.42.0",
185186
"mustache": "^4.2.0",
186187
"picomatch": "^4.0.4",
187188
"url-parse": "^1.5.10",

src/components/WebComponents/wcPage.js

Lines changed: 62 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import { animate, press } from "motion";
12
import tile from "../tile";
23

34
export default class WCPage extends HTMLElement {
@@ -53,6 +54,22 @@ export default class WCPage extends HTMLElement {
5354
></span>
5455
);
5556

57+
press(this.#leadBtn, (element) => {
58+
if (document.body.classList.contains("no-animation")) return;
59+
animate(
60+
element,
61+
{ scale: 0.85 },
62+
{ type: "spring", stiffness: 400, damping: 20 },
63+
);
64+
return () => {
65+
animate(
66+
element,
67+
{ scale: 1 },
68+
{ type: "spring", stiffness: 400, damping: 20 },
69+
);
70+
};
71+
});
72+
5673
this.#header = tile({
5774
type: "header",
5875
text: title || "Page",
@@ -80,6 +97,29 @@ export default class WCPage extends HTMLElement {
8097

8198
connectedCallback() {
8299
this.classList.remove("hide");
100+
const isPrimary = this.classList.contains("primary");
101+
const isNoTransition = this.classList.contains("no-transition");
102+
103+
if (!isPrimary) {
104+
if (document.body.classList.contains("no-animation")) {
105+
this.style.opacity = "";
106+
} else {
107+
this.style.opacity = "0";
108+
animate(
109+
this,
110+
{
111+
opacity: 1,
112+
},
113+
{
114+
duration: isNoTransition ? 0.08 : 0.14,
115+
ease: "easeOut",
116+
},
117+
).then(() => {
118+
this.style.opacity = "";
119+
});
120+
}
121+
}
122+
83123
if (typeof this.onconnect === "function") this.onconnect();
84124
this.#on.show.forEach((cb) => cb.call(this));
85125
}
@@ -120,12 +160,29 @@ export default class WCPage extends HTMLElement {
120160
}
121161

122162
hide() {
123-
this.classList.add("hide");
124163
if (typeof this.onhide === "function") this.onhide();
125-
setTimeout(() => {
164+
165+
const isPrimary = this.classList.contains("primary");
166+
const isNoTransition = this.classList.contains("no-transition");
167+
168+
if (isPrimary || document.body.classList.contains("no-animation")) {
126169
this.remove();
127170
this.handler.remove();
128-
}, 150);
171+
} else {
172+
animate(
173+
this,
174+
{
175+
opacity: 0,
176+
},
177+
{
178+
duration: isNoTransition ? 0.08 : 0.12,
179+
ease: "easeIn",
180+
},
181+
).then(() => {
182+
this.remove();
183+
this.handler.remove();
184+
});
185+
}
129186
}
130187

131188
get body() {
@@ -239,6 +296,7 @@ class PageHandler {
239296
* Replace current element with a replacement element
240297
*/
241298
replaceEl() {
299+
if (this.$el.classList.contains("primary")) return;
242300
this.$el.off("hide", this.onhide);
243301
if (!this.$el.isConnected || this.$replacement.isConnected) return;
244302
if (typeof this.onReplace === "function") this.onReplace();
@@ -285,7 +343,7 @@ class PageHandler {
285343
*/
286344
function handlePagesForSmoothExperience() {
287345
const $pages = [...tag.getAll("wc-page")];
288-
for (let $page of $pages.slice(0, -1)) {
346+
for (let $page of $pages.slice(0, -2)) {
289347
$page.handler.replaceEl();
290348
}
291349
}

src/components/checkbox/index.js

Lines changed: 51 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import "./styles.scss";
22
import Ref from "html-tag-js/ref";
3+
import { animate } from "motion";
34

45
/**
56
* @typedef {Object} Checkbox
@@ -23,35 +24,81 @@ import Ref from "html-tag-js/ref";
2324
* @param {string} [size] Size of checkbox
2425
* @returns {Checkbox & HTMLLabelElement}
2526
*/
26-
function Checkbox(text, checked, name, id, type, ref, size) {
27+
function Checkbox(text, checked, name, id, type, ref, size, isSwitch) {
2728
if (typeof text === "object") {
28-
({ text, checked, name, id, type, ref, size } = text);
29+
({ text, checked, name, id, type, ref, size, isSwitch } = text);
2930
}
3031

3132
size = size || "1rem";
3233

3334
const $input = ref || Ref();
35+
const $handle = Ref();
3436
const $checkbox = (
35-
<label className="input-checkbox">
37+
<label className={`input-checkbox ${isSwitch ? "switch" : ""}`}>
3638
<input
3739
ref={$input}
3840
checked={checked}
3941
type={type || "checkbox"}
4042
name={name}
4143
id={id}
44+
onchange={handleChange}
4245
/>
43-
<span style={{ height: size, width: size }} className="box"></span>
46+
<span style={{ height: size, width: size }} className="box">
47+
<span ref={$handle} className="handle"></span>
48+
</span>
4449
<span>{text}</span>
4550
</label>
4651
);
4752

53+
function updateToggle(animateToggle = true) {
54+
const isSwitch =
55+
$checkbox.classList.contains("switch") ||
56+
$checkbox.closest(
57+
".detail-settings-list, .main-settings-list, .settings-search-section",
58+
) !== null;
59+
60+
if (isSwitch && $handle.el) {
61+
const isChecked = !!$input.el.checked;
62+
const targetTransform = isChecked
63+
? "translate3d(1.12rem, 0, 0)"
64+
: "translate3d(0, 0, 0)";
65+
66+
if (animateToggle && !document.body.classList.contains("no-animation")) {
67+
animate(
68+
$handle.el,
69+
{
70+
transform: targetTransform,
71+
},
72+
{
73+
type: "spring",
74+
stiffness: 500,
75+
damping: 28,
76+
},
77+
).then(() => {
78+
$handle.el.style.transform = targetTransform;
79+
});
80+
} else {
81+
$handle.el.style.transform = targetTransform;
82+
}
83+
}
84+
}
85+
86+
function handleChange() {
87+
updateToggle(true);
88+
}
89+
90+
requestAnimationFrame(() => {
91+
updateToggle(false);
92+
});
93+
4894
Object.defineProperties($checkbox, {
4995
checked: {
5096
get() {
5197
return !!$input.el.checked;
5298
},
5399
set(value) {
54100
$input.el.checked = value;
101+
updateToggle(true);
55102
},
56103
},
57104
onclick: {

src/components/checkbox/styles.scss

Lines changed: 8 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -15,27 +15,29 @@
1515
border: solid 1px var(--secondary-text-color);
1616
margin: 0 5px;
1717

18-
&::after {
19-
content: '';
18+
.handle {
2019
display: block;
2120
height: 80%;
2221
width: 80%;
2322
background-color: #3399ff;
2423
background-color: var(--button-background-color);
2524
margin: auto;
2625
border-radius: 2px;
26+
transition: opacity 140ms ease;
2727
}
2828
}
2929

3030
input:checked {
31-
&+.box::after {
31+
&+.box .handle {
3232
opacity: 1;
3333
}
3434
}
3535

36-
input:not(:checked) {
37-
&+.box::after {
38-
opacity: 0;
36+
&:not(.switch) {
37+
input:not(:checked) {
38+
&+.box .handle {
39+
opacity: 0;
40+
}
3941
}
4042
}
4143
}

0 commit comments

Comments
 (0)