adf0d17497
publish / version_or_publish (push) Has been cancelled
storybook-build / changes (push) Has been cancelled
storybook-build / :storybook-build (push) Has been cancelled
Sync Gradio Skills to Hugging Face / sync-skills (push) Has been cancelled
functional / changes (push) Has been cancelled
functional / build-frontend (push) Has been cancelled
functional / functional-test-SSR=false (push) Has been cancelled
functional / functional-reload (push) Has been cancelled
js / changes (push) Has been cancelled
js / js-test (push) Has been cancelled
docs-build / changes (push) Has been cancelled
docs-build / docs-build (push) Has been cancelled
docs-build / website-build (push) Has been cancelled
functional / functional-test-SSR=true (push) Has been cancelled
hygiene / hygiene-test (push) Has been cancelled
python / changes (push) Has been cancelled
python / build (push) Has been cancelled
python / test-ubuntu-latest-flaky (push) Has been cancelled
python / test-ubuntu-latest-not-flaky (push) Has been cancelled
python / test-windows-latest-flaky (push) Has been cancelled
python / test-windows-latest-not-flaky (push) Has been cancelled
264 lines
6.0 KiB
Svelte
264 lines
6.0 KiB
Svelte
<script lang="ts">
|
|
import { onMount } from "svelte";
|
|
|
|
let {
|
|
open = $bindable(true),
|
|
width,
|
|
position = $bindable<"left" | "right">("left"),
|
|
elem_classes = [],
|
|
elem_id = "",
|
|
onexpand = () => {},
|
|
oncollapse = () => {}
|
|
}: {
|
|
open?: boolean;
|
|
width: number | string;
|
|
position?: "left" | "right";
|
|
elem_classes?: string[];
|
|
elem_id?: string;
|
|
onexpand?: () => void;
|
|
oncollapse?: () => void;
|
|
} = $props();
|
|
|
|
// Using a temporary variable to animate the sidebar opening at the start
|
|
let mounted = $state(false);
|
|
let _open = $state(false);
|
|
let sidebar_div: HTMLElement;
|
|
let overlap_amount = $state(0);
|
|
|
|
let width_css = $derived(typeof width === "number" ? `${width}px` : width);
|
|
let prefersReducedMotion = $state(false);
|
|
|
|
// Check if the sidebar overlaps with the main content
|
|
function check_overlap(): void {
|
|
if (!sidebar_div.closest(".wrap")) return;
|
|
const parent_rect = sidebar_div.closest(".wrap")?.getBoundingClientRect();
|
|
if (!parent_rect) return;
|
|
const sidebar_rect = sidebar_div.getBoundingClientRect();
|
|
const available_space =
|
|
position === "left"
|
|
? parent_rect.left
|
|
: window.innerWidth - parent_rect.right;
|
|
overlap_amount = Math.max(0, sidebar_rect.width - available_space + 30);
|
|
}
|
|
|
|
onMount(() => {
|
|
sidebar_div.closest(".wrap")?.classList.add("sidebar-parent");
|
|
check_overlap();
|
|
window.addEventListener("resize", check_overlap);
|
|
const update_parent_overlap = (): void => {
|
|
document.documentElement.style.setProperty(
|
|
"--overlap-amount",
|
|
`${overlap_amount}px`
|
|
);
|
|
};
|
|
update_parent_overlap();
|
|
mounted = true;
|
|
const mediaQuery = window.matchMedia("(prefers-reduced-motion: reduce)");
|
|
prefersReducedMotion = mediaQuery.matches;
|
|
const updateMotionPreference = (e: MediaQueryListEvent): void => {
|
|
prefersReducedMotion = e.matches;
|
|
};
|
|
mediaQuery.addEventListener("change", updateMotionPreference);
|
|
return () => {
|
|
window.removeEventListener("resize", check_overlap);
|
|
mediaQuery.removeEventListener("change", updateMotionPreference);
|
|
};
|
|
});
|
|
|
|
// We need to wait for the component to be mounted before we can set the open state
|
|
// so that it animates correctly.
|
|
$effect(() => {
|
|
if (mounted) _open = open;
|
|
});
|
|
|
|
let _elem_classes = $derived(elem_classes?.join(" ") || "");
|
|
</script>
|
|
|
|
<div
|
|
class="sidebar {_elem_classes}"
|
|
id={elem_id}
|
|
class:open={_open}
|
|
class:right={position === "right"}
|
|
class:reduce-motion={prefersReducedMotion}
|
|
bind:this={sidebar_div}
|
|
style="width: {width_css}; {position}: calc({width_css} * -1)"
|
|
>
|
|
<button
|
|
onclick={() => {
|
|
_open = !_open;
|
|
open = _open;
|
|
if (_open) {
|
|
onexpand?.();
|
|
} else {
|
|
oncollapse?.();
|
|
}
|
|
}}
|
|
class="toggle-button"
|
|
aria-label="Toggle Sidebar"
|
|
>
|
|
<div class="chevron">
|
|
<span class="chevron-left"></span>
|
|
</div>
|
|
</button>
|
|
<div class="sidebar-content">
|
|
<slot />
|
|
</div>
|
|
</div>
|
|
|
|
<style>
|
|
/* Mobile styles (≤ 768px) */
|
|
@media (max-width: 768px) {
|
|
.sidebar {
|
|
width: 100vw !important;
|
|
}
|
|
|
|
.sidebar:not(.right) {
|
|
left: -100vw !important;
|
|
}
|
|
|
|
.sidebar.right {
|
|
right: -100vw !important;
|
|
}
|
|
|
|
.sidebar:not(.reduce-motion) {
|
|
transition: transform 0.3s ease-in-out !important;
|
|
}
|
|
|
|
:global(.sidebar-parent) {
|
|
padding-left: 0 !important;
|
|
padding-right: 0 !important;
|
|
}
|
|
|
|
:global(.sidebar-parent:has(.sidebar.open)) {
|
|
padding-left: 0 !important;
|
|
padding-right: 0 !important;
|
|
}
|
|
.sidebar.open {
|
|
z-index: 1001 !important;
|
|
}
|
|
}
|
|
|
|
:global(.sidebar-parent) {
|
|
display: flex !important;
|
|
padding-left: 0;
|
|
padding-right: 0;
|
|
}
|
|
|
|
:global(.sidebar-parent:not(.reduce-motion)) {
|
|
transition:
|
|
padding-left 0.3s ease-in-out,
|
|
padding-right 0.3s ease-in-out;
|
|
}
|
|
|
|
:global(.sidebar-parent:has(.sidebar.open:not(.right))) {
|
|
padding-left: var(--overlap-amount);
|
|
}
|
|
|
|
:global(.sidebar-parent:has(.sidebar.open.right)) {
|
|
padding-right: var(--overlap-amount);
|
|
}
|
|
|
|
.sidebar {
|
|
display: flex;
|
|
flex-direction: column;
|
|
position: fixed;
|
|
top: 0;
|
|
height: 100%;
|
|
background-color: var(--background-fill-secondary);
|
|
transform: translateX(0%);
|
|
z-index: 1000;
|
|
}
|
|
|
|
.sidebar:not(.reduce-motion) {
|
|
transition: transform 0.3s ease-in-out;
|
|
}
|
|
|
|
.sidebar.open:not(.right) {
|
|
transform: translateX(100%);
|
|
box-shadow: var(--size-1) 0 var(--size-1) rgba(0, 0, 0, 0.05);
|
|
}
|
|
|
|
.sidebar.open.right {
|
|
transform: translateX(-100%);
|
|
box-shadow: calc(var(--size-1) * -1) 0 var(--size-1) rgba(0, 0, 0, 0.05);
|
|
}
|
|
|
|
.toggle-button {
|
|
position: absolute;
|
|
top: var(--size-4);
|
|
background: var(--background-fill-secondary);
|
|
border: 1px solid var(--border-color-primary);
|
|
cursor: pointer;
|
|
padding: var(--size-2);
|
|
display: flex;
|
|
align-items: center;
|
|
justify-content: center;
|
|
width: var(--size-7);
|
|
height: var(--size-8);
|
|
z-index: 1001;
|
|
border-radius: 0;
|
|
}
|
|
|
|
.toggle-button:not(.reduce-motion) {
|
|
transition: all 0.3s ease-in-out;
|
|
}
|
|
|
|
.sidebar:not(.right) .toggle-button {
|
|
left: 100%;
|
|
border-radius: 0 var(--size-8) var(--size-8) 0;
|
|
border-left: none;
|
|
}
|
|
|
|
.sidebar.right .toggle-button {
|
|
right: 100%;
|
|
transform: rotate(180deg);
|
|
border-radius: 0 var(--size-8) var(--size-8) 0;
|
|
border-left: none;
|
|
}
|
|
|
|
.open:not(.right) .toggle-button {
|
|
right: 0;
|
|
left: auto;
|
|
transform: rotate(180deg);
|
|
border-radius: 0 var(--size-8) var(--size-8) 0;
|
|
border-left: none;
|
|
border-right: 1px solid var(--border-color-primary);
|
|
}
|
|
|
|
.open.right .toggle-button {
|
|
left: 0;
|
|
right: auto;
|
|
transform: rotate(0deg);
|
|
border-radius: 0 var(--size-8) var(--size-8) 0;
|
|
border-left: none;
|
|
border-right: 1px solid var(--border-color-primary);
|
|
}
|
|
|
|
.chevron {
|
|
position: relative;
|
|
display: flex;
|
|
align-items: center;
|
|
justify-content: center;
|
|
padding-right: 8px;
|
|
}
|
|
|
|
.chevron-left {
|
|
position: relative;
|
|
width: var(--size-3);
|
|
height: var(--size-3);
|
|
border-top: var(--size-0-5) solid var(--body-text-color);
|
|
border-right: var(--size-0-5) solid var(--body-text-color);
|
|
transform: rotate(45deg);
|
|
}
|
|
|
|
.sidebar-content {
|
|
padding: var(--size-5);
|
|
padding-right: var(--size-8);
|
|
overflow-y: auto;
|
|
}
|
|
|
|
.sidebar.right .sidebar-content {
|
|
padding-left: var(--size-8);
|
|
}
|
|
</style>
|