Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,9 @@ export function _Sheet(props: SheetProps, ref: Ref<HTMLDivElement>) {
children,
className,
isOpen,
onEnter,
onEntered,
onExit,
onExited,
onOpenChange,
position = "start",
Expand All @@ -31,7 +33,9 @@ export function _Sheet(props: SheetProps, ref: Ref<HTMLDivElement>) {
<CSSTransition
in={isOpen}
nodeRef={overlayRef}
onEnter={onEnter}
onEntered={onEntered}
onExit={onExit}
onExited={onExited}
timeout={300}
unmountOnExit
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@ export interface SheetProps
* @default 'start'
*/
position?: "start" | "end";
onEnter?: () => void;
onEntered?: () => void;
onExit?: () => void;
onExited?: () => void;
}
Original file line number Diff line number Diff line change
@@ -1,30 +1,65 @@
import clsx from "clsx";
import * as React from "react";
import { type Ref, useRef } from "react";
import { type Ref, useRef, useState } from "react";
import { CSSTransition } from "react-transition-group";

import { Sheet } from "../../Sheet";
import { useSidebar } from "./use-sidebar";
import styles from "./styles.module.css";
import { useSidebar } from "./use-sidebar";
import type { SidebarProps } from "./types";
import { CSSTransition } from "react-transition-group";

import { SidebarContent } from "./SidebarContent";

const _Sidebar = (props: SidebarProps, ref: Ref<HTMLDivElement>) => {
const {
children,
className,
collapsible = "offcanvas",
onEntered,
onExited,
onEnter: onEnterProp,
onEntered: onEnteredProp,
onExit: onExitProp,
onExited: onExitedProp,
side = "start",
title,
variant = "sidebar",
...rest
} = props;
const { isMobile, setOpen, state } = useSidebar();
const [isAnimating, setIsAnimating] = useState(false);
const { isMobile, setState, state } = useSidebar();
const sidebarRef = useRef<HTMLDivElement>();

const onEnter = () => {
setIsAnimating(true);
onEnterProp?.();
};

const onEntered = () => {
setIsAnimating(false);
onEnteredProp?.();
};

const onExit = () => {
setIsAnimating(true);
onExitProp?.();
};

const onExited = () => {
setIsAnimating(false);
onExitedProp?.();
};

const content = (
<SidebarContent title={title}>
{typeof children === "function"
? children({ isAnimating, state })
: children}
</SidebarContent>
);

if (collapsible === "none") {
return (
<div className={clsx(className)} ref={ref} {...props}>
{children}
{content}
</div>
);
}
Expand All @@ -33,38 +68,52 @@ const _Sidebar = (props: SidebarProps, ref: Ref<HTMLDivElement>) => {
return (
<Sheet
isOpen={state === "expanded"}
onEnter={onEnter}
onEntered={onEntered}
onExit={onExit}
onExited={onExited}
onOpenChange={setOpen}
onOpenChange={(isOpen) => setState(isOpen ? "expanded" : "collapsed")}
position={side}
>
{children}
{content}
</Sheet>
);
}

return (
<CSSTransition
in={state === "expanded"}
in={state === "full-width"}
nodeRef={sidebarRef}
onEnter={onEnter}
onEntered={onEntered}
onExit={onExit}
onExited={onExited}
timeout={300}
>
<div
className={clsx(styles.mainSidebar)}
data-collapsible={state === "collapsed" ? collapsible : ""}
data-side={side}
data-state={state}
data-variant={variant}
// @ts-expect-error TS is unable to infer the correct type for the render prop
ref={sidebarRef}
<CSSTransition
in={state === "expanded"}
nodeRef={sidebarRef}
onEnter={onEnter}
onEntered={onEntered}
onExit={onExit}
onExited={onExited}
timeout={300}
>
<div className={styles.fakeSidebar} />
<div className={clsx(styles.sidebar, className)} ref={ref} {...rest}>
<div className={styles.sidebarContainer}>{children}</div>
<div
className={clsx(styles.mainSidebar)}
data-collapsible={state === "collapsed" ? collapsible : ""}
data-side={side}
data-state={state}
data-variant={variant}
// @ts-expect-error TS is unable to infer the correct type for the render prop
ref={sidebarRef}
>
<div className={styles.fakeSidebar} />
<div className={clsx(styles.sidebar, className)} ref={ref} {...rest}>
<div className={styles.sidebarContainer}>{content}</div>
</div>
</div>
</div>
</CSSTransition>
</CSSTransition>
);
};
Expand Down
Original file line number Diff line number Diff line change
@@ -1,21 +1,59 @@
import clsx from "clsx";
import React, { type ComponentProps, type Ref } from "react";
import React, { type Ref } from "react";

import { Flex } from "../../Flex";
import { Text } from "../../Text";
import { Button } from "../../Button";
import styles from "./styles.module.css";
import { useSidebar } from "./use-sidebar";

interface SidebarContentProps {
title?: string;
className?: string;
children: React.ReactNode;
}

const _SidebarContent = (
props: ComponentProps<"div">,
props: SidebarContentProps,
ref: Ref<HTMLDivElement>,
) => {
const { className, ...rest } = props;
const { children, className, title, ...rest } = props;
const { isMobile, setState, state } = useSidebar();

return (
<div
className={clsx(styles.sidebarContent, className)}
data-sidebar="content"
ref={ref}
{...rest}
/>
>
<Flex direction="column" height="100%" isInner>
<Flex
alignItems="center"
className={styles.sidebarHeader}
isInner
justifyContent="space-between"
padding="spacing-2"
>
{Boolean(title) && <Text lineClamp={1}>{title}</Text>}
{!isMobile && (
<Button
color="neutral"
icon={
state === "full-width"
? "arrows-diagonal-minimize"
: "arrows-diagonal-2"
}
onPress={() =>
setState(state === "full-width" ? "expanded" : "full-width")
}
variant="ghost"
/>
)}
</Flex>
<div className={styles.sidebarContentInner}>{children}</div>
</Flex>
</div>
);
};

Expand Down
Original file line number Diff line number Diff line change
@@ -1,11 +1,15 @@
import clsx from "clsx";
import React, { type Ref, useCallback, useState } from "react";

import type {
SidebarContextType,
SidebarProviderProps,
SidebarState,
} from "./types";
import styles from "./styles.module.css";
import { SidebarContext } from "./context";
import { useIsMobile } from "./use-mobile";
import { SIDEBAR_CONSTANTS } from "./constants";
import type { SidebarContextType, SidebarProviderProps } from "./types";

export const _SidebarProvider = (
props: SidebarProviderProps,
Expand All @@ -14,32 +18,32 @@ export const _SidebarProvider = (
const {
children,
className,
defaultOpen = true,
isOpen: openProp,
onOpen: setOpenProp,
defaultState = "expanded",
onStateChange: setStateProp,
state: stateProp,
style,
...rest
} = props;
const isMobile = useIsMobile();

const [_open, _setOpen] = useState(defaultOpen);
const open = openProp ?? _open;
const setOpen = useCallback(
(value: boolean | ((value: boolean) => boolean)) => {
const openState = typeof value === "function" ? value(open) : value;
const [_state, _setState] = useState<SidebarState>(defaultState);
const state = stateProp ?? _state;
const setState = useCallback(
(value: SidebarState | ((value: SidebarState) => SidebarState)) => {
const computedState = typeof value === "function" ? value(state) : value;

if (setOpenProp) {
setOpenProp(openState);
if (setStateProp) {
setStateProp(computedState);
} else {
_setOpen(openState);
_setState(computedState);
}
},
[setOpenProp, open],
[setStateProp, state],
);

const toggleSidebar = React.useCallback(() => {
return isMobile ? setOpen((open) => !open) : setOpen((open) => !open);
}, [isMobile, setOpen]);
return state === "collapsed" ? setState("expanded") : setState("collapsed");
}, [setState, state]);

React.useEffect(
function handleKeyboardShortcuts() {
Expand All @@ -60,17 +64,14 @@ export const _SidebarProvider = (
[toggleSidebar, isMobile],
);

const state = open ? "expanded" : "collapsed";

const contextValue = React.useMemo<SidebarContextType>(
() => ({
state,
open,
setOpen,
setState,
isMobile,
toggleSidebar,
}),
[state, open, setOpen, isMobile, toggleSidebar],
[state, setState, isMobile, toggleSidebar],
);

return (
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@
height: 100%;
width: var(--sidebar-width);
background-color: transparent;
transition: width 300ms linear;
transition: width 300ms ease-in-out;
}

.mainSidebar[data-side="right"] .fakeSidebar {
Expand Down Expand Up @@ -76,9 +76,14 @@
height: 100%;
width: var(--sidebar-width);
transition:
left 300ms linear,
right 300ms linear,
width 300ms linear;
left 300ms ease-in-out,
right 300ms ease-in-out,
width 300ms ease-in-out;
background-color: var(--color-bg-elevation-2);
}

[data-state="full-width"] .sidebar {
width: 100%;
}

@container (min-width: 768px) {
Expand Down Expand Up @@ -131,6 +136,20 @@
border-inline-start: var(--border-width-1) solid var(--color-bd-elevation-1);
}

.mainSidebar[data-state="full-width"][data-side="start"]:is(
[data-variant="sidebar"]
)
.sidebar {
border-inline-end: none;
}

.mainSidebar[data-state="full-width"][data-side="end"]:is(
[data-variant="sidebar"]
)
.sidebar {
border-inline-start: none;
}

/**
*-----------------------------------------------------
* SIDEBAR CONTAINER
Expand Down Expand Up @@ -166,6 +185,13 @@
height: 100%;
}

.sidebarContentInner {
flex-grow: 1;
overflow-y: auto;
overflow-x: hidden;
width: 100%;
}

/**
*-----------------------------------------------------
* SIDEBAR INSET
Expand Down Expand Up @@ -201,3 +227,12 @@
margin-left: 0;
}
}

/**
*-----------------------------------------------------
* SIDEBAR HEADER
*-----------------------------------------------------
*/
.sidebarHeader {
border-bottom: var(--border-width-1) solid var(--color-bd-elevation-1);
}
Loading