From 3783a156c146dadbe465ddbae62ce8c1ddb3ef9e Mon Sep 17 00:00:00 2001 From: thislight Date: Wed, 9 Oct 2024 23:04:20 +0800 Subject: [PATCH 1/3] BottomSheet: more animation --- src/material/BottomSheet.module.css | 8 ++- src/material/BottomSheet.tsx | 93 +++++++++++++++++++++++++---- 2 files changed, 90 insertions(+), 11 deletions(-) diff --git a/src/material/BottomSheet.module.css b/src/material/BottomSheet.module.css index 8f716e9..410858b 100644 --- a/src/material/BottomSheet.module.css +++ b/src/material/BottomSheet.module.css @@ -43,7 +43,7 @@ &.animated { position: absolute; - transform: none; + transform: translateY(-50%); overflow: hidden; will-change: width, height, top, left; @@ -54,6 +54,12 @@ & * { overflow: hidden; } + + @media (max-width: 560px) { + & { + transform: none; + } + } } &.bottom { diff --git a/src/material/BottomSheet.tsx b/src/material/BottomSheet.tsx index 92eabc9..690db27 100644 --- a/src/material/BottomSheet.tsx +++ b/src/material/BottomSheet.tsx @@ -36,7 +36,7 @@ function composeAnimationFrame( }; } -const MOVE_SPEED = 1400; // 1400px/s, bottom sheet is big and a bit heavier than small papers +const MOVE_SPEED = 1200; const BottomSheet: ParentComponent = (props) => { let element: HTMLDialogElement; @@ -62,13 +62,17 @@ const BottomSheet: ParentComponent = (props) => { }); const onClose = () => { - hero()!.style.visibility = 'unset' + const srcElement = hero(); + if (srcElement) { + srcElement.style.visibility = "unset"; + } + element.close(); setHero(); }; const animatedClose = () => { - const srcElement = hero() + const srcElement = hero(); const endRect = srcElement?.getBoundingClientRect(); if (endRect) { const startRect = element.getBoundingClientRect(); @@ -76,19 +80,88 @@ const BottomSheet: ParentComponent = (props) => { animation.addEventListener("finish", onClose); animation.addEventListener("cancel", onClose); } else { - element.close(); - setHero(); + if (window.innerWidth > 560 && !props.bottomUp) { + onClose(); + return; + } + const animation = props.bottomUp + ? animateSlideInFromBottom(element, true) + : animateSlideInFromRight(element, true); + animation.addEventListener("finish", onClose); + animation.addEventListener("cancel", onClose); } }; const animatedOpen = () => { element.showModal(); - const srcElement = hero() + const srcElement = hero(); const startRect = srcElement?.getBoundingClientRect(); - if (!startRect) return; - srcElement!.style.visibility = 'hidden' - const endRect = element.getBoundingClientRect(); - animateHero(startRect, endRect, element); + if (startRect) { + srcElement!.style.visibility = "hidden"; + const endRect = element.getBoundingClientRect(); + animateHero(startRect, endRect, element); + } else if (props.bottomUp) { + animateSlideInFromBottom(element); + } else if (window.innerWidth <= 560) { + animateSlideInFromRight(element); + } + }; + + const animateSlideInFromRight = (element: HTMLElement, reserve?: boolean) => { + const rect = element.getBoundingClientRect(); + const easing = "cubic-bezier(0.4, 0, 0.2, 1)"; + element.classList.add(styles.animated); + const distance = Math.abs(rect.left - window.innerWidth); + const duration = (distance / MOVE_SPEED) * 1000; + + animation = element.animate( + { + top: [rect.top, rect.top], + left: reserve + ? [`${rect.left}px`, `${window.innerWidth}px`] + : [`${window.innerWidth}px`, `${rect.left}px`], + width: [rect.width, rect.width], + height: [rect.height, rect.height], + }, + { easing, duration }, + ); + const onAnimationEnd = () => { + element.classList.remove(styles.animated); + animation = undefined; + }; + animation.addEventListener("cancel", onAnimationEnd); + animation.addEventListener("finish", onAnimationEnd); + return animation; + }; + + const animateSlideInFromBottom = ( + element: HTMLElement, + reserve?: boolean, + ) => { + const rect = element.getBoundingClientRect(); + const easing = "cubic-bezier(0.4, 0, 0.2, 1)"; + element.classList.add(styles.animated); + const distance = Math.abs(rect.top - window.innerHeight); + const duration = (distance / MOVE_SPEED) * 1000; + + animation = element.animate( + { + left: [rect.left, rect.left], + top: reserve + ? [`${rect.top}px`, `${window.innerHeight}px`] + : [`${window.innerHeight}px`, `${rect.top}px`], + width: [rect.width, rect.width], + height: [rect.height, rect.height], + }, + { easing, duration }, + ); + const onAnimationEnd = () => { + element.classList.remove(styles.animated); + animation = undefined; + }; + animation.addEventListener("cancel", onAnimationEnd); + animation.addEventListener("finish", onAnimationEnd); + return animation; }; const animateHero = ( From 233134c25f958ce47f9c32df56a9f605c6896211 Mon Sep 17 00:00:00 2001 From: thislight Date: Wed, 9 Oct 2024 23:27:20 +0800 Subject: [PATCH 2/3] Settings: move lang and region to separate route --- src/App.tsx | 4 ++ src/settings/{ChooseLang.tsx => Language.tsx} | 39 +++++++++------- src/settings/Motions.tsx | 4 +- src/settings/{ChooseRegion.tsx => Region.tsx} | 45 ++++++++++--------- src/settings/Settings.tsx | 25 +---------- 5 files changed, 55 insertions(+), 62 deletions(-) rename src/settings/{ChooseLang.tsx => Language.tsx} (73%) rename src/settings/{ChooseRegion.tsx => Region.tsx} (66%) diff --git a/src/App.tsx b/src/App.tsx index 69249b3..cd6fe4f 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -26,6 +26,8 @@ const TimelineHome = lazy(() => import("./timelines/Home.js")); const Settings = lazy(() => import("./settings/Settings.js")); const TootBottomSheet = lazy(() => import("./timelines/TootBottomSheet.js")); const MotionSettings = lazy(() => import("./settings/Motions.js")); +const LanguageSettings = lazy(() => import("./settings/Language.js")); +const RegionSettings = lazy(() => import("./settings/Region.jsx")); const Routing: Component = () => { return ( @@ -34,6 +36,8 @@ const Routing: Component = () => { + + diff --git a/src/settings/ChooseLang.tsx b/src/settings/Language.tsx similarity index 73% rename from src/settings/ChooseLang.tsx rename to src/settings/Language.tsx index ac03c7b..f04d4b1 100644 --- a/src/settings/ChooseLang.tsx +++ b/src/settings/Language.tsx @@ -13,7 +13,7 @@ import { Switch, Toolbar, } from "@suid/material"; -import { Close as CloseIcon } from "@suid/icons-material"; +import { ArrowBack } from "@suid/icons-material"; import iso639_1 from "iso-639-1"; import { autoMatchLangTag, @@ -22,14 +22,12 @@ import { } from "../platform/i18n"; import { Title } from "../material/typography"; import type { Template } from "@solid-primitives/i18n"; +import { useStore } from "@nanostores/solid"; +import { $settings } from "./stores"; +import { useNavigate } from "@solidjs/router"; -type ChooseLangProps = { - code?: string; - onCodeChange: (ncode?: string) => void; - onClose?: JSX.EventHandlerUnion; -}; - -const ChooseLang: Component = (props) => { +const ChooseLang: Component = () => { + const navigate = useNavigate() const [t] = createTranslator( () => import("./i18n/lang-names.json"), (code) => @@ -39,6 +37,9 @@ const ChooseLang: Component = (props) => { }; }>, ); + const settings = useStore($settings) + + const code = () => settings().language const unsupportedLangCodes = createMemo(() => { return iso639_1.getAllCodes().filter((x) => !["zh", "en"].includes(x)); @@ -46,6 +47,10 @@ const ChooseLang: Component = (props) => { const matchedLangCode = createMemo(() => autoMatchLangTag()); + const onCodeChange = (code?: string) => { + $settings.setKey("language", code) + } + return ( = (props) => { variant="dense" sx={{ paddingTop: "var(--safe-area-inset-top, 0px)" }} > - - + + {t("Choose Language")} @@ -69,7 +74,7 @@ const ChooseLang: Component = (props) => { > { - props.onCodeChange(props.code ? undefined : matchedLangCode()); + onCodeChange(code() ? undefined : matchedLangCode()); }} > @@ -78,20 +83,20 @@ const ChooseLang: Component = (props) => { })} - + {t("Supported")}}> - {(code) => ( + {(c) => ( - {t(`lang.${code}`)} + {t(`lang.${c}`)} diff --git a/src/settings/Motions.tsx b/src/settings/Motions.tsx index 2f111fd..a90bd44 100644 --- a/src/settings/Motions.tsx +++ b/src/settings/Motions.tsx @@ -14,7 +14,7 @@ import { } from "@suid/material"; import { Title } from "../material/typography"; import { useNavigate } from "@solidjs/router"; -import { Close as CloseIcon } from "@suid/icons-material"; +import { ArrowBack } from "@suid/icons-material"; import { createTranslator } from "../platform/i18n"; import { useStore } from "@nanostores/solid"; import { $settings } from "./stores"; @@ -37,7 +37,7 @@ const Motions: Component = () => { sx={{ paddingTop: "var(--safe-area-inset-top, 0px)" }} > - + {t("motions")} diff --git a/src/settings/ChooseRegion.tsx b/src/settings/Region.tsx similarity index 66% rename from src/settings/ChooseRegion.tsx rename to src/settings/Region.tsx index 77a969e..1ae1a62 100644 --- a/src/settings/ChooseRegion.tsx +++ b/src/settings/Region.tsx @@ -12,8 +12,7 @@ import { Switch, Toolbar, } from "@suid/material"; -import { Close as CloseIcon } from "@suid/icons-material"; -import iso639_1 from "iso-639-1"; +import { ArrowBack } from "@suid/icons-material"; import { autoMatchRegion, createTranslator, @@ -21,14 +20,12 @@ import { } from "../platform/i18n"; import { Title } from "../material/typography"; import type { Template } from "@solid-primitives/i18n"; +import { useNavigate } from "@solidjs/router"; +import { $settings } from "./stores"; +import { useStore } from "@nanostores/solid"; -type ChooseRegionProps = { - code?: string; - onCodeChange: (ncode?: string) => void; - onClose?: JSX.EventHandlerUnion; -}; - -const ChooseRegion: Component = (props) => { +const ChooseRegion: Component = () => { + const navigate = useNavigate(); const [t] = createTranslator( () => import("./i18n/lang-names.json"), (code) => @@ -39,12 +36,16 @@ const ChooseRegion: Component = (props) => { }>, ); - const unsupportedLangCodes = createMemo(() => { - return iso639_1.getAllCodes().filter((x) => !["zh", "en"].includes(x)); - }); + const settings = useStore($settings); + + const region = () => settings().region; const matchedRegionCode = createMemo(() => autoMatchRegion()); + const onCodeChange = (code?: string) => { + $settings.setKey("region", code); + }; + return ( = (props) => { variant="dense" sx={{ paddingTop: "var(--safe-area-inset-top, 0px)" }} > - - + + {t("Choose Language")} @@ -68,16 +69,17 @@ const ChooseRegion: Component = (props) => { > { - props.onCodeChange(props.code ? undefined : matchedRegionCode()); + onCodeChange(region() ? undefined : matchedRegionCode()); }} > {t("region.auto", { - detected: t(`region.${matchedRegionCode()}`) ?? matchedRegionCode(), + detected: + t(`region.${matchedRegionCode()}`) ?? matchedRegionCode(), })} - + @@ -85,13 +87,16 @@ const ChooseRegion: Component = (props) => { {(code) => ( {t(`region.${code}`)} diff --git a/src/settings/Settings.tsx b/src/settings/Settings.tsx index e27443a..826fea9 100644 --- a/src/settings/Settings.tsx +++ b/src/settings/Settings.tsx @@ -41,15 +41,10 @@ import { autoMatchLangTag, autoMatchRegion, createTranslator, - SUPPORTED_LANGS, - SUPPORTED_REGIONS, useDateFnLocale, } from "../platform/i18n.jsx"; import { type Template } from "@solid-primitives/i18n"; import BottomSheet from "../material/BottomSheet.jsx"; -import ChooseLang from "./ChooseLang.jsx"; -import ChooseRegion from "./ChooseRegion.jsx"; -import { Portal } from "solid-js/web"; type Strings = { ["lang.auto"]: Template<{ detected: string }>; @@ -69,8 +64,6 @@ const Settings: ParentComponent = (props) => { needRefresh: [needRefresh], } = useRegisterSW(); const dateFnLocale = useDateFnLocale(); - const [langPickerOpen, setLangPickerOpen] = createSignal(false); - const [regionPickerOpen, setRegionPickerOpen] = createSignal(false); const [profiles] = useSignedInProfiles(); @@ -175,7 +168,7 @@ const Settings: ParentComponent = (props) => {
  • {t("This Application")} - + @@ -192,15 +185,8 @@ const Settings: ParentComponent = (props) => { {t("Language")} - - $settings.setKey("language", nval)} - onClose={[setLangPickerOpen, false]} - /> - - + @@ -217,13 +203,6 @@ const Settings: ParentComponent = (props) => { {t("Region")} - - $settings.setKey("region", nval)} - onClose={[setRegionPickerOpen, false]} - /> - From 27342936f0eb31367c1d82cbecc780b6e55b5172 Mon Sep 17 00:00:00 2001 From: thislight Date: Wed, 9 Oct 2024 23:27:47 +0800 Subject: [PATCH 3/3] BottomSheet: set body overflow:hidden in animation --- src/material/BottomSheet.tsx | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/material/BottomSheet.tsx b/src/material/BottomSheet.tsx index 690db27..e753e20 100644 --- a/src/material/BottomSheet.tsx +++ b/src/material/BottomSheet.tsx @@ -111,6 +111,8 @@ const BottomSheet: ParentComponent = (props) => { const rect = element.getBoundingClientRect(); const easing = "cubic-bezier(0.4, 0, 0.2, 1)"; element.classList.add(styles.animated); + const oldOverflow = document.body.style.overflow; + document.body.style.overflow = "hidden"; const distance = Math.abs(rect.left - window.innerWidth); const duration = (distance / MOVE_SPEED) * 1000; @@ -127,6 +129,7 @@ const BottomSheet: ParentComponent = (props) => { ); const onAnimationEnd = () => { element.classList.remove(styles.animated); + document.body.style.overflow = oldOverflow; animation = undefined; }; animation.addEventListener("cancel", onAnimationEnd); @@ -141,6 +144,8 @@ const BottomSheet: ParentComponent = (props) => { const rect = element.getBoundingClientRect(); const easing = "cubic-bezier(0.4, 0, 0.2, 1)"; element.classList.add(styles.animated); + const oldOverflow = document.body.style.overflow; + document.body.style.overflow = "hidden"; const distance = Math.abs(rect.top - window.innerHeight); const duration = (distance / MOVE_SPEED) * 1000; @@ -157,6 +162,7 @@ const BottomSheet: ParentComponent = (props) => { ); const onAnimationEnd = () => { element.classList.remove(styles.animated); + document.body.style.overflow = oldOverflow; animation = undefined; }; animation.addEventListener("cancel", onAnimationEnd);