Compare commits

..

No commits in common. "7205fa5775f40bc0286c5077fc562597519dce0f" and "e174b7aafdb20f8cbdbf65a606acb54bd6a7b50e" have entirely different histories.

7 changed files with 76 additions and 150 deletions

View file

@ -8,8 +8,7 @@
"scripts": {
"dev": "vite --host 0.0.0.0",
"preview": "vite preview",
"dist": "vite build",
"count-source-lines": "exec scripts/src-lc.sh"
"dist": "vite build"
},
"keywords": [],
"author": "Rubicon",

View file

@ -1,10 +0,0 @@
#!/bin/sh
# Count the source lines.
find . '(' ! -path "./node_modules/**" ')' \
-and '(' ! -path "./.git/**" ')' \
-and '(' ! -path "./*dist/**" ')' \
-and '(' ! -path "./bun.lockb" ')' \
-and '(' ! -path "./docs/**" ')' \
-type f -print0 \
| wc -l --files0-from=-

View file

@ -7,7 +7,6 @@
width: max-content;
box-shadow: var(--tutu-shadow-e8);
contain: content;
overscroll-behavior: contain;
&.e1 {
box-shadow: var(--tutu-shadow-e9);

View file

@ -1,7 +1,6 @@
import { useWindowSize } from "@solid-primitives/resize-observer";
import { MenuList } from "@suid/material";
import {
batch,
createEffect,
createSignal,
splitProps,
@ -66,39 +65,11 @@ export function createManagedMenuState() {
return !!anchor();
},
anchor: anchor as () => Anchor,
onClose: (event: Event) => {
event.preventDefault();
return setAnchor();
},
onClose: () => setAnchor(),
},
] as const;
}
function animateGrowFromTopLeft(
element: HTMLElement,
opts?: Omit<KeyframeAnimationOptions, "duration">,
) {
const rend = element.getBoundingClientRect();
const overflow = element.style.overflow;
element.style.overflow = "hidden";
const duration = (rend.height / 1600) * 1000;
const animation = element.animate(
{
height: [`${rend.height / 2}px`, `${rend.height}px`],
width: [`${(rend.width / 4) * 3}px`, `${rend.width}px`],
},
{
duration,
...opts,
},
);
animation.addEventListener(
"finish",
() => (element.style.overflow = overflow),
);
return animation;
}
/**
* Material Menu Component. This component is
* implemented with dialog and {@link MenuList} from SUID.
@ -107,16 +78,10 @@ function animateGrowFromTopLeft(
* - Use {@link createManagedMenuState} and you don't need to manage the open and close.
* - Use {@link MenuItem} from SUID as children.
*/
const Menu: Component<MenuProps> = (oprops) => {
const Menu: Component<MenuProps> = (props) => {
let root: HTMLDialogElement;
const windowSize = useWindowSize();
const [props, rest] = splitProps(oprops, [
"open",
"onClose",
"anchor",
"MenuListProps",
"children",
]);
const [, rest] = splitProps(props, ["open", "onClose", "anchor"]);
const [anchorPos, setAnchorPos] = createSignal<{
left?: number;
@ -141,30 +106,16 @@ const Menu: Component<MenuProps> = (oprops) => {
let openAnimationOrigin: "lt" | "rt" = "lt";
const animateOpen = () => {
createEffect(() => {
if (props.open) {
const a = props.anchor();
const { width } = windowSize;
const { left, top, right, e } = a;
const isOpened = root.open;
// There are incomplete animations.
// For `getBoundingClientRect()`, WebKit reports the initial state
// of the element, whilst Firefox reports the final state.
//
// We skip if animations are still on the element
// to avoid the problem on WebKit.
// Here use the final state.
//
// This is a dirty workaround. It's here because the feature is still
// works with it.
// I am curious that why the ones on the other parts are works. (Rubicon)
if (root.getAnimations().length > 0) {
return;
}
if (!root.open) {
root.showModal();
const rend = root.getBoundingClientRect();
const { width } = windowSize;
const { left, top, right, e } = a;
if (left > width / 2) {
openAnimationOrigin = "rt";
setAnchorPos({
@ -172,26 +123,34 @@ const Menu: Component<MenuProps> = (oprops) => {
top,
e,
});
animateGrowFromTopRight(root, { easing: ANIM_CURVE_STD });
} else {
openAnimationOrigin = "lt";
setAnchorPos({ left, top, e });
}
if (!isOpened) {
switch (openAnimationOrigin) {
case "lt":
animateGrowFromTopLeft(root, { easing: ANIM_CURVE_STD });
break;
case "rt":
animateGrowFromTopRight(root, { easing: ANIM_CURVE_STD });
break;
const overflow = root.style.overflow;
root.style.overflow = "hidden";
const duration = (rend.height / 1600) * 1000;
const easing = ANIM_CURVE_STD;
const animation = root.animate(
{
height: [`${rend.height / 2}px`, `${rend.height}px`],
width: [`${(rend.width / 4) * 3}px`, `${rend.width}px`],
},
{
duration,
easing,
},
);
animation.addEventListener(
"finish",
() => (root.style.overflow = overflow),
);
}
} else {
// TODO: update the pos
}
};
createEffect(() => {
if (props.open) {
animateOpen();
} else {
animateClose();
}
@ -226,42 +185,27 @@ const Menu: Component<MenuProps> = (oprops) => {
}
};
const onDialogClick = (
event: MouseEvent & { currentTarget: HTMLDialogElement },
) => {
event.stopPropagation();
if (event.currentTarget !== event.target) return;
if (!event.currentTarget.open) return;
const rect = event.currentTarget.getBoundingClientRect();
const isNotInDialog =
event.clientY < rect.top ||
event.clientY > rect.bottom ||
event.clientX < rect.left ||
event.clientX > rect.right;
if (isNotInDialog) {
return (
<dialog
ref={root!}
onClose={props.onClose}
onClick={(e) => {
if (e.target === root) {
if (props.onClose) {
if (Array.isArray(props.onClose)) {
props.onClose[0](props.onClose[1], event);
props.onClose[0](props.onClose[1], e);
} else {
(
props.onClose as (
event: Event & { currentTarget: HTMLDialogElement },
) => void
)(event);
)(e);
}
}
e.stopPropagation();
}
};
return (
<dialog
ref={root!}
onClose={props.onClose}
onCancel={props.onClose}
onClick={onDialogClick}
class={`Menu e${anchorPos().e || "0"}`}
}}
class={`Menu e${anchorPos().e || 0}`}
style={{
left: px(anchorPos().left),
top: px(anchorPos().top),

View file

@ -4,9 +4,6 @@
z-index: var(--tutu-zidx-nav, auto);
.MuiToolbar-root {
margin-left: var(--safe-area-inset-left);
margin-right: var(--safe-area-inset-right);
>.MuiButtonBase-root {
&:first-child {
margin-left: -0.5em;

View file

@ -15,16 +15,6 @@ dialog.StackedPage {
width: 560px;
max-height: 100vh;
max-height: 100dvh;
/*
* WebKit does not see contain-instric-size as the real element size.
* If the container does not have height, the child element using 100%
* height (usually Scafflod in our case) was have 0px computed height.
*
* This behaviour is different from Firefox. So we need to actually
* define the box height here. (Rubicon)
*/
height: 100vh;
height: 100dvh;
background: none;
display: none;
@ -41,9 +31,10 @@ dialog.StackedPage {
@media (max-width: 560px) {
& {
margin: 0;
width: 100vw;
width: 100dvw;
height: 100vh;
height: 100dvh;
contain-intrinsic-size: 100vw 100vh;
contain-intrinsic-size: 100dvw 100dvh;
}

View file

@ -7,7 +7,11 @@ import {
ListItemText,
MenuItem,
} from "@suid/material";
import { Show, createUniqueId, type ParentComponent } from "solid-js";
import {
Show,
createUniqueId,
type ParentComponent,
} from "solid-js";
import {
Settings as SettingsIcon,
Bookmark as BookmarkIcon,
@ -35,7 +39,9 @@ const ProfileMenuButton: ParentComponent<{
const [open, state] = createManagedMenuState();
const onClick = (event: { currentTarget: HTMLElement }) => {
const onClick = (
event: MouseEvent & { currentTarget: HTMLButtonElement },
) => {
open(event.currentTarget.getBoundingClientRect());
};
@ -48,8 +54,8 @@ const ProfileMenuButton: ParentComponent<{
sx={{ borderRadius: "50%" }}
id={buttonId}
onClick={onClick}
aria-controls={state.open ? menuId : undefined}
aria-expanded={state.open ? "true" : "false"}
aria-controls={open() ? menuId : undefined}
aria-expanded={open() ? "true" : undefined}
>
<Avatar
alt={`${inf()?.displayName}'s avatar`}