diff --git a/app/client/packages/design-system/widgets/src/components/Sheet/src/Sheet.tsx b/app/client/packages/design-system/widgets/src/components/Sheet/src/Sheet.tsx index 3b5261f223a8..1b19dc7ca10b 100644 --- a/app/client/packages/design-system/widgets/src/components/Sheet/src/Sheet.tsx +++ b/app/client/packages/design-system/widgets/src/components/Sheet/src/Sheet.tsx @@ -15,7 +15,9 @@ export function _Sheet(props: SheetProps, ref: Ref) { children, className, isOpen, + onEnter, onEntered, + onExit, onExited, onOpenChange, position = "start", @@ -31,7 +33,9 @@ export function _Sheet(props: SheetProps, ref: Ref) { void; onEntered?: () => void; + onExit?: () => void; onExited?: () => void; } diff --git a/app/client/packages/design-system/widgets/src/components/Sidebar/src/Sidebar.tsx b/app/client/packages/design-system/widgets/src/components/Sidebar/src/Sidebar.tsx index c8b231cef3cb..60b628f8daec 100644 --- a/app/client/packages/design-system/widgets/src/components/Sidebar/src/Sidebar.tsx +++ b/app/client/packages/design-system/widgets/src/components/Sidebar/src/Sidebar.tsx @@ -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) => { 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(); + const onEnter = () => { + setIsAnimating(true); + onEnterProp?.(); + }; + + const onEntered = () => { + setIsAnimating(false); + onEnteredProp?.(); + }; + + const onExit = () => { + setIsAnimating(true); + onExitProp?.(); + }; + + const onExited = () => { + setIsAnimating(false); + onExitedProp?.(); + }; + + const content = ( + + {typeof children === "function" + ? children({ isAnimating, state }) + : children} + + ); + if (collapsible === "none") { return (
- {children} + {content}
); } @@ -33,38 +68,52 @@ const _Sidebar = (props: SidebarProps, ref: Ref) => { return ( setState(isOpen ? "expanded" : "collapsed")} position={side} > - {children} + {content} ); } return ( -
-
-
-
{children}
+
+
+
+
{content}
+
-
+ ); }; diff --git a/app/client/packages/design-system/widgets/src/components/Sidebar/src/SidebarContent.tsx b/app/client/packages/design-system/widgets/src/components/Sidebar/src/SidebarContent.tsx index bb90417a91b1..f72ee85dc5c9 100644 --- a/app/client/packages/design-system/widgets/src/components/Sidebar/src/SidebarContent.tsx +++ b/app/client/packages/design-system/widgets/src/components/Sidebar/src/SidebarContent.tsx @@ -1,13 +1,24 @@ 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, ) => { - const { className, ...rest } = props; + const { children, className, title, ...rest } = props; + const { isMobile, setState, state } = useSidebar(); return (
+ > + + + {Boolean(title) && {title}} + {!isMobile && ( +
); }; diff --git a/app/client/packages/design-system/widgets/src/components/Sidebar/src/SidebarProvider.tsx b/app/client/packages/design-system/widgets/src/components/Sidebar/src/SidebarProvider.tsx index 35d688f23837..16dcc26a5a85 100644 --- a/app/client/packages/design-system/widgets/src/components/Sidebar/src/SidebarProvider.tsx +++ b/app/client/packages/design-system/widgets/src/components/Sidebar/src/SidebarProvider.tsx @@ -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, @@ -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(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() { @@ -60,17 +64,14 @@ export const _SidebarProvider = ( [toggleSidebar, isMobile], ); - const state = open ? "expanded" : "collapsed"; - const contextValue = React.useMemo( () => ({ state, - open, - setOpen, + setState, isMobile, toggleSidebar, }), - [state, open, setOpen, isMobile, toggleSidebar], + [state, setState, isMobile, toggleSidebar], ); return ( diff --git a/app/client/packages/design-system/widgets/src/components/Sidebar/src/styles.module.css b/app/client/packages/design-system/widgets/src/components/Sidebar/src/styles.module.css index e3fe71fce134..91c640d923b5 100644 --- a/app/client/packages/design-system/widgets/src/components/Sidebar/src/styles.module.css +++ b/app/client/packages/design-system/widgets/src/components/Sidebar/src/styles.module.css @@ -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 { @@ -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) { @@ -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 @@ -166,6 +185,13 @@ height: 100%; } +.sidebarContentInner { + flex-grow: 1; + overflow-y: auto; + overflow-x: hidden; + width: 100%; +} + /** *----------------------------------------------------- * SIDEBAR INSET @@ -201,3 +227,12 @@ margin-left: 0; } } + +/** + *----------------------------------------------------- + * SIDEBAR HEADER + *----------------------------------------------------- + */ +.sidebarHeader { + border-bottom: var(--border-width-1) solid var(--color-bd-elevation-1); +} diff --git a/app/client/packages/design-system/widgets/src/components/Sidebar/src/types.ts b/app/client/packages/design-system/widgets/src/components/Sidebar/src/types.ts index 1d4e130126e5..00f84b13757a 100644 --- a/app/client/packages/design-system/widgets/src/components/Sidebar/src/types.ts +++ b/app/client/packages/design-system/widgets/src/components/Sidebar/src/types.ts @@ -1,28 +1,37 @@ import type { ReactNode } from "react"; export interface SidebarContextType { - state: "expanded" | "collapsed"; - open: boolean; - setOpen: (open: boolean) => void; + state: SidebarState; + setState: (state: SidebarState) => void; isMobile: boolean; toggleSidebar: () => void; } export interface SidebarProviderProps { - defaultOpen?: boolean; - isOpen?: boolean; - onOpen?: (open: boolean) => void; + defaultState?: SidebarState; + state?: SidebarState; + onStateChange?: (state: SidebarState) => void; children: ReactNode; className?: string; style?: React.CSSProperties; } +export type SidebarState = "collapsed" | "expanded" | "full-width"; + export interface SidebarProps { side?: "start" | "end"; variant?: "sidebar" | "floating" | "inset"; collapsible?: "offcanvas" | "icon" | "none"; + onEnter?: () => void; + onExit?: () => void; onEntered?: () => void; onExited?: () => void; - children: ReactNode; + children: + | React.ReactNode + | ((props: { + isAnimating: boolean; + state: SidebarState; + }) => React.ReactNode); className?: string; + title?: string; } diff --git a/app/client/packages/design-system/widgets/src/components/Sidebar/stories/Sidebar.stories.tsx b/app/client/packages/design-system/widgets/src/components/Sidebar/stories/Sidebar.stories.tsx index 0aa33cfc529e..b460c3c4740e 100644 --- a/app/client/packages/design-system/widgets/src/components/Sidebar/stories/Sidebar.stories.tsx +++ b/app/client/packages/design-system/widgets/src/components/Sidebar/stories/Sidebar.stories.tsx @@ -2,7 +2,6 @@ import React from "react"; import type { Meta, StoryObj } from "@storybook/react"; import { Sidebar, - SidebarContent, SidebarTrigger, SidebarProvider, SidebarInset, @@ -23,6 +22,7 @@ const meta: Meta = { }, }, args: { + title: "Sidebar", side: "start", variant: "sidebar", }, @@ -34,9 +34,7 @@ const meta: Meta = { }} > - - - + @@ -123,3 +121,46 @@ export const VariantInset: Story = { ), }; + +export const WithRenderProps: Story = { + render: (args) => ( + + + {({ isAnimating, state }) => ( + + + Sidebar State + + + + + + {state} + + {isAnimating ? "(Animating)" : ""} + + + + + + )} + + + + + + + + ), +};