Tabs: fix misplaced indicator

This commit is contained in:
thislight 2025-01-02 17:12:48 +08:00
parent 66b593add8
commit 0eef74f25f
No known key found for this signature in database
GPG key ID: FCFE5192241CCD4E
4 changed files with 58 additions and 67 deletions

23
src/material/Tab.css Normal file
View file

@ -0,0 +1,23 @@
.Tab {
cursor: pointer;
background: none;
border: none;
height: 100%;
max-width: min(calc(100% - 56px), 264px);
padding: 10px 24px;
font-size: 0.8135rem;
font-weight: 600;
text-transform: uppercase;
transition: color 120ms var(--tutu-anim-curve-std);
}
.MuiToolbar-root .Tab {
color: rgba(255, 255, 255, 0.7);
&:hover,
&:focus,
&.focus,
&.Tabs-focus {
color: white;
}
}

View file

@ -1,26 +1,18 @@
import { import {
Component,
createEffect, createEffect,
splitProps, splitProps,
type JSX, type JSX,
type ParentComponent, type ParentComponent,
} from "solid-js"; } from "solid-js";
import { css } from "solid-styled";
import { useTabListContext } from "./Tabs"; import { useTabListContext } from "./Tabs";
import "./Tab.css";
const Tab: ParentComponent< const Tab: ParentComponent<
{ {
focus?: boolean; focus?: boolean;
large?: boolean;
} & JSX.ButtonHTMLAttributes<HTMLButtonElement> } & JSX.ButtonHTMLAttributes<HTMLButtonElement>
> = (props) => { > = (props) => {
const [managed, rest] = splitProps(props, [ const [managed, rest] = splitProps(props, ["focus", "type", "role", "ref"]);
"focus",
"large",
"type",
"role",
"ref",
]);
let self: HTMLButtonElement; let self: HTMLButtonElement;
const { const {
focusOn: [, setFocusOn], focusOn: [, setFocusOn],
@ -35,32 +27,7 @@ const Tab: ParentComponent<
} }
return managed.focus; return managed.focus;
}); });
css`
.tab {
cursor: pointer;
background: none;
border: none;
min-width: ${managed.large ? "160px" : "72px"};
height: 48px;
max-width: min(calc(100% - 56px), 264px);
padding: 10px 24px;
font-size: 0.8135rem;
font-weight: 600;
text-transform: uppercase;
transition: color 120ms var(--tutu-anim-curve-std);
}
:global(.MuiToolbar-root) .tab {
color: rgba(255, 255, 255, 0.7);
&:hover,
&:focus,
&.focus,
&:global(.tablist-focus) {
color: white;
}
}
`;
return ( return (
<button <button
ref={(x) => { ref={(x) => {
@ -68,7 +35,7 @@ const Tab: ParentComponent<
(managed.ref as (e: HTMLButtonElement) => void)?.(x); (managed.ref as (e: HTMLButtonElement) => void)?.(x);
}} }}
type={managed.type ?? "button"} type={managed.type ?? "button"}
classList={{ tab: true, focus: managed.focus }} classList={{ Tab: true, focus: managed.focus }}
role={managed.role ?? "tab"} role={managed.role ?? "tab"}
{...rest} {...rest}
> >

21
src/material/Tabs.css Normal file
View file

@ -0,0 +1,21 @@
.Tabs {
width: 100%;
position: relative;
white-space: nowrap;
overflow-x: auto;
align-self: stretch;
&::after {
transition:
left var(--tabs-indkt-movspeed-offset, 0) var(--tutu-anim-curve-std),
width var(--tabs-indkt-movspeed-width, 0) var(--tutu-anim-curve-std);
position: absolute;
content: "";
display: block;
background-color: white;
height: 2px;
width: var(--tabs-indkt-width, 0);
left: var(--tabs-indkt-offset, 0);
bottom: 0;
}
}

View file

@ -1,14 +1,12 @@
import { import {
ParentComponent, ParentComponent,
createContext, createContext,
createEffect,
createMemo,
createRenderEffect, createRenderEffect,
createSignal, createSignal,
useContext, useContext,
type Signal, type Signal,
} from "solid-js"; } from "solid-js";
import { css } from "solid-styled"; import "./Tabs.css"
const TabListContext = /* @__PURE__ */ createContext<{ const TabListContext = /* @__PURE__ */ createContext<{
focusOn: Signal<HTMLElement[]>; focusOn: Signal<HTMLElement[]>;
@ -24,7 +22,7 @@ export function useTabListContext() {
const ANIM_SPEED = 160 / 110; // 160px/110ms const ANIM_SPEED = 160 / 110; // 160px/110ms
const TABLIST_FOCUS_CLASS = "tablist-focus"; const TABS_FOCUS_CLASS = "Tabs-focus";
const Tabs: ParentComponent<{ const Tabs: ParentComponent<{
offset?: number; offset?: number;
@ -37,11 +35,11 @@ const Tabs: ParentComponent<{
const current = focusOn(); const current = focusOn();
if (lastFocusElement) { if (lastFocusElement) {
for (const e of lastFocusElement) { for (const e of lastFocusElement) {
e.classList.remove(TABLIST_FOCUS_CLASS); e.classList.remove(TABS_FOCUS_CLASS);
} }
} }
for (const e of current) { for (const e of current) {
e.classList.add("tablist-focus"); e.classList.add(TABS_FOCUS_CLASS);
} }
return current; return current;
}); });
@ -109,7 +107,7 @@ const Tabs: ParentComponent<{
return ["0px", "0px", "110ms", "110ms"] as const; return ["0px", "0px", "110ms", "110ms"] as const;
} }
const rect = focusBoundingClientRect(); const rect = focusBoundingClientRect();
const rootRect = self.getBoundingClientRect(); const rootRect = self!.getBoundingClientRect();
const left = rect.x - rootRect.x; const left = rect.x - rootRect.x;
const width = rect.width; const width = rect.width;
const [prevEl, nextEl] = focusSiblings(); const [prevEl, nextEl] = focusSiblings();
@ -130,32 +128,14 @@ const Tabs: ParentComponent<{
return result; return result;
}; };
css`
.tablist {
width: 100%;
position: relative;
white-space: nowrap;
overflow-x: auto;
&::after {
transition:
left ${indicator()[2]} var(--tutu-anim-curve-std),
width ${indicator()[3]} var(--tutu-anim-curve-std);
position: absolute;
content: "";
display: block;
background-color: white;
height: 2px;
width: ${indicator()[1]};
left: ${indicator()[0]};
bottom: 0;
}
}
`;
return ( return (
<TabListContext.Provider value={{ focusOn: [focusOn, setFocusOn] }}> <TabListContext.Provider value={{ focusOn: [focusOn, setFocusOn] }}>
<div ref={self!} class="tablist" role="tablist"> <div ref={self!} class="Tabs" style={{
"--tabs-indkt-width": indicator()[1],
"--tabs-indkt-offset": indicator()[0],
"--tabs-indkt-movspeed-offset": indicator()[2],
"--tabs-indkt-movspeed-width": indicator()[3]
}} role="tablist">
{props.children} {props.children}
</div> </div>
</TabListContext.Provider> </TabListContext.Provider>