rework/lightweight #12

Merged
GauthierWebDev merged 106 commits from rework/lightweight into main 2025-04-21 16:27:38 +00:00
3 changed files with 91 additions and 37 deletions
Showing only changes of commit 88012308d0 - Show all commits

View File

@ -102,10 +102,7 @@ export const Highlight: ParentComponent<Props> = (_props) => {
)} )}
<pre <pre
class={clsx( class={clsx("not-prose h-full w-full prism-code flex", languageClass())}
"not-prose w-full prism-code flex overflow-x-auto",
languageClass(),
)}
> >
<code <code
class={clsx("leading-6", props.withLineNumbers ? "px-4" : "pr-4")} class={clsx("leading-6", props.withLineNumbers ? "px-4" : "pr-4")}

View File

@ -48,10 +48,40 @@ export function SmoothScroll(props: SmoothScrollProps) {
return regex.test(navigator.userAgent); return regex.test(navigator.userAgent);
}; };
const isElementScrollable = (element: HTMLElement) => {
if (!element) return false;
return element.scrollHeight > element.clientHeight;
};
const findScrollableParent = (element: HTMLElement) => {
let currentElement: HTMLElement | null = element;
while (currentElement) {
if (isElementScrollable(currentElement)) {
return currentElement;
}
currentElement = currentElement.parentElement;
}
return null;
};
const handleWheel = (event: WheelEvent) => { const handleWheel = (event: WheelEvent) => {
if (isMobile()) return; if (isMobile()) return;
const hoveredElement = document.elementFromPoint(
event.clientX,
event.clientY,
) as HTMLElement;
if (findScrollableParent(hoveredElement)) {
if (animationFrameId !== null) cancelAnimationFrame(animationFrameId);
return;
}
event.preventDefault(); event.preventDefault();
event.stopPropagation();
if (isScrolling()) { if (isScrolling()) {
if (animationFrameId !== null) { if (animationFrameId !== null) {
cancelAnimationFrame(animationFrameId); cancelAnimationFrame(animationFrameId);

View File

@ -38,6 +38,9 @@ type SnippetProps = {
}; };
export function Snippet(props: SnippetProps) { export function Snippet(props: SnippetProps) {
let tabs: HTMLDivElement | undefined;
let nav: HTMLDivElement | undefined;
const [selectedTab, setSelectedTab] = createSignal<SnippetTab | CommonTab>( const [selectedTab, setSelectedTab] = createSignal<SnippetTab | CommonTab>(
props.snippets[0], props.snippets[0],
); );
@ -49,6 +52,27 @@ export function Snippet(props: SnippetProps) {
const selectTab = (name: string) => { const selectTab = (name: string) => {
const tab = props.snippets.find((tab) => tab.name === name); const tab = props.snippets.find((tab) => tab.name === name);
if (tab) setSelectedTab(tab); if (tab) setSelectedTab(tab);
if (!tabs || !nav) return;
const navWidth = nav.offsetWidth || 0;
const tabsWidth = tabs.scrollWidth;
if (tabsWidth > navWidth) {
const tabElement: HTMLDivElement | null = tabs.querySelector(
`div[data-tab="${name}"]`,
);
if (!tabElement) return;
const tabOffsetLeft = tabElement.offsetLeft;
const tabWidth = tabElement.offsetWidth;
const scrollLeft = Math.max(
0,
tabOffsetLeft - navWidth / 2 + tabWidth / 2,
);
nav.scrollTo({ left: scrollLeft, behavior: "smooth" });
}
}; };
const canBeSelected = (tab: SnippetTab | CommonTab) => { const canBeSelected = (tab: SnippetTab | CommonTab) => {
@ -68,48 +92,51 @@ export function Snippet(props: SnippetProps) {
<div class="pt-4 pl-4"> <div class="pt-4 pl-4">
<TrafficLightsIcon class="h-2.5 w-auto stroke-slate-500/30" /> <TrafficLightsIcon class="h-2.5 w-auto stroke-slate-500/30" />
<div class="mt-4 flex space-x-2 text-xs overflow-x-auto"> <nav ref={nav} class="overflow-x-auto">
<For each={props.snippets}> <div ref={tabs} class="mt-4 flex space-x-2 text-xs w-max mb-2">
{(tab) => ( <For each={props.snippets}>
<div {(tab) => (
class={clsx( <div
"flex h-6 rounded-full", data-tab={tab.name}
{ "cursor-pointer": canBeSelected(tab) && !isActive(tab) },
isActive(tab)
? clsx(
"bg-linear-to-r from-violet-400/30 via-violet-400 to-violet-400/30 p-px font-medium",
props.dark ? "text-violet-300" : "text-violet-600",
)
: props.dark
? "text-slate-400"
: "text-slate-500",
)}
>
<button
type="button"
class={clsx( class={clsx(
"flex items-center rounded-full px-2.5", "flex h-6 rounded-full",
isActive(tab) && { { "cursor-pointer": canBeSelected(tab) && !isActive(tab) },
"bg-slate-800": props.dark, isActive(tab)
"bg-violet-100": !props.dark, ? clsx(
}, "bg-linear-to-r from-violet-400/30 via-violet-400 to-violet-400/30 p-px font-medium",
props.dark ? "text-violet-300" : "text-violet-600",
)
: props.dark
? "text-slate-400"
: "text-slate-500",
)} )}
disabled={!canBeSelected(tab)}
onClick={() => selectTab(tab.name)}
> >
{tab.name} <button
</button> type="button"
</div> class={clsx(
)} "flex items-center rounded-full px-2.5",
</For> isActive(tab) && {
</div> "bg-slate-800": props.dark,
"bg-violet-100": !props.dark,
},
)}
disabled={!canBeSelected(tab)}
onClick={() => selectTab(tab.name)}
>
{tab.name}
</button>
</div>
)}
</For>
</div>
</nav>
{selectedTab() && ( {selectedTab() && (
<div class="mt-6"> <div class="mt-6">
{selectedTab().code && ( {selectedTab().code && (
<Highlight <Highlight
class={clsx( class={clsx(
"!pt-0 !px-1 max-h-96 overflow-auto", "!pt-0 !px-1 max-h-96 overflow-auto mb-2",
props.dark && "dark text-white", props.dark && "dark text-white",
)} )}
language={(selectedTab() as SnippetTab).codeLanguage} language={(selectedTab() as SnippetTab).codeLanguage}