Skip to content

Commit

Permalink
chore: add sheet + sidebar (#37408)
Browse files Browse the repository at this point in the history
/ok-to-test tags="@tag.Anvil"

<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->
## Summary by CodeRabbit

## Release Notes

- **New Features**
- Introduced a new `Sheet` component for modal-like overlays, enhancing
content display.
- Added a `Sidebar` component for improved navigation and organization
within the design system.

- **Improvements**
- Enhanced styling and animations for the `Sheet` and `Sidebar`
components, providing a more interactive user experience.
  - Updated dependencies to improve performance and accessibility.

- **Documentation**
- Added Storybook configurations for `Sheet` and `Sidebar` components,
showcasing their functionality and usage.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->

<!-- This is an auto-generated comment: Cypress test results  -->
> [!TIP]
> 🟢 🟢 🟢 All cypress tests have passed! 🎉 🎉 🎉
> Workflow run:
<https://github.com/appsmithorg/appsmith/actions/runs/11855312043>
> Commit: f8f4d2c
> <a
href="https://internal.appsmith.com/app/cypress-dashboard/rundetails-65890b3c81d7400d08fa9ee5?branch=master&workflowId=11855312043&attempt=1"
target="_blank">Cypress dashboard</a>.
> Tags: `@tag.Anvil`
> Spec:
> <hr>Fri, 15 Nov 2024 11:40:45 UTC
<!-- end of auto-generated comment: Cypress test results  -->
  • Loading branch information
jsartisan authored Nov 15, 2024
1 parent 7f2c4c6 commit aa8b153
Show file tree
Hide file tree
Showing 25 changed files with 946 additions and 5 deletions.
2 changes: 2 additions & 0 deletions app/client/packages/design-system/widgets/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -29,11 +29,13 @@
"react-aria-components": "^1.2.1",
"react-markdown": "^9.0.1",
"react-syntax-highlighter": "^15.5.0",
"react-transition-group": "^4.4.5",
"remark-gfm": "^4.0.0"
},
"devDependencies": {
"@types/fs-extra": "^11.0.4",
"@types/react-syntax-highlighter": "^15.5.13",
"@types/react-transition-group": "^4.4.11",
"eslint-plugin-storybook": "^0.6.10"
},
"peerDependencies": {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export * from "./src";
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
import clsx from "clsx";
import React, { forwardRef, type Ref, useRef } from "react";
import {
Modal as HeadlessModal,
Dialog as HeadlessDialog,
ModalOverlay as HeadlessModalOverlay,
} from "react-aria-components";
import { CSSTransition } from "react-transition-group";

import styles from "./styles.module.css";
import type { SheetProps } from "./types";

export function _Sheet(props: SheetProps, ref: Ref<HTMLDivElement>) {
const {
children,
className,
isOpen,
onEntered,
onExited,
onOpenChange,
position = "start",
...rest
} = props;
const root = document.body.querySelector(
"[data-theme-provider]",
) as HTMLButtonElement;

const overlayRef = useRef<HTMLDivElement>();

return (
<CSSTransition
in={isOpen}
nodeRef={overlayRef}
onEntered={onEntered}
onExited={onExited}
timeout={300}
unmountOnExit
>
<HeadlessModalOverlay
UNSTABLE_portalContainer={root}
className={clsx(styles.overlay, className)}
isDismissable
isOpen={isOpen}
onOpenChange={onOpenChange}
// @ts-expect-error TS is unable to infer the correct type for the render prop
ref={overlayRef}
>
<HeadlessModal
className={clsx(styles.sheet, styles[position])}
ref={ref}
{...rest}
>
<HeadlessDialog aria-label="Sheet" className={styles.dialog}>
{children}
</HeadlessDialog>
</HeadlessModal>
</HeadlessModalOverlay>
</CSSTransition>
);
}

export const Sheet = forwardRef(_Sheet);
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
export { Sheet } from "./Sheet";
export { DialogTrigger as SheetTrigger } from "react-aria-components";
Original file line number Diff line number Diff line change
@@ -0,0 +1,117 @@
.sheet {
position: fixed;
background: var(--color-bg-elevation-3);
width: 80%;
}

.overlay {
display: flex;
align-items: center;
justify-content: center;
background: var(--color-bg-neutral-opacity);
z-index: var(--z-index-99);
contain: strict;
position: fixed;
top: 0;
left: 0;
height: 100%;
width: 100%;
}

.dialog {
height: 100%;
}

.overlay[data-entering] {
animation: fade-in 0.3s ease-in-out;
}

.overlay[data-exiting] {
animation: fade-out 0.3s ease-in-out;
}

@keyframes fade-in {
from {
opacity: 0;
}
to {
opacity: 1;
}
}

@keyframes fade-out {
from {
opacity: 1;
}
to {
opacity: 0;
}
}

.start {
top: 0;
left: 0;
bottom: 0;
max-width: 90%;
height: 100%;
}

.start[data-entering] {
animation: slide-in-start 0.3s cubic-bezier(0.4, 0, 0.2, 1);
}

.start[data-exiting] {
animation: slide-out-start 0.3s cubic-bezier(0.4, 0, 0.2, 1);
}

.end {
top: 0;
right: 0;
bottom: 0;
max-width: 90%;
height: 100%;
}

.end[data-entering] {
animation: slide-in-end 0.3s cubic-bezier(0.4, 0, 0.2, 1);
}

.end[data-exiting] {
animation: slide-out-end 0.3s cubic-bezier(0.4, 0, 0.2, 1);
}

@keyframes slide-in-start {
from {
transform: translateX(-100%);
}
to {
transform: translateX(0);
}
}

@keyframes slide-out-start {
from {
transform: translateX(0);
}
to {
transform: translateX(-100%);
}
}

@keyframes slide-in-end {
from {
transform: translateX(100%);
}
to {
transform: translateX(0);
}
}

@keyframes slide-out-end {
from {
transform: translateX(0);
}
to {
transform: translateX(100%);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
import type { ModalOverlayProps as HeadlessModalOverlayProps } from "react-aria-components";

export interface SheetProps
extends Omit<HeadlessModalOverlayProps, "position"> {
/**
* The position from which the sheet slides in
* @default 'start'
*/
position?: "start" | "end";
onEntered?: () => void;
onExited?: () => void;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
import React from "react";
import type { Meta, StoryObj } from "@storybook/react";
import { Sheet } from "../index";
import { SimpleSheet } from "./SimpleSheet";

const meta: Meta<typeof Sheet> = {
title: "WDS/Widgets/Sheet",
component: Sheet,
render: (args) => <SimpleSheet {...args} />,
};

export default meta;
type Story = StoryObj<typeof Sheet>;

// Default story with left position (start)
export const Default: Story = {
args: {
position: "start",
},
};

// Right position (end)
export const RightPositioned: Story = {
args: {
position: "end",
},
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
import React, { useState } from "react";
import { Button } from "../../Button";
import { Sheet } from "../index";
import type { SheetProps } from "../src/types";

export const SimpleSheet = (props: Omit<SheetProps, "children">) => {
const [isOpen, setIsOpen] = useState(props.isOpen);

return (
<>
<Button onPress={() => setIsOpen(!Boolean(isOpen))}>Sheet trigger</Button>
<Sheet {...props} isOpen={isOpen} onOpenChange={setIsOpen}>
<div style={{ padding: "1rem" } as React.CSSProperties}>
<h3>Sheet Content</h3>
<p>This is an example of sheet content.</p>
</div>
</Sheet>
</>
);
};
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export * from "./src";
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
import clsx from "clsx";
import * as React from "react";
import { type Ref, useRef } from "react";
import { Sheet } from "../../Sheet";
import { useSidebar } from "./use-sidebar";
import styles from "./styles.module.css";
import type { SidebarProps } from "./types";
import { CSSTransition } from "react-transition-group";

const _Sidebar = (props: SidebarProps, ref: Ref<HTMLDivElement>) => {
const {
children,
className,
collapsible = "offcanvas",
onEntered,
onExited,
side = "start",
variant = "sidebar",
...rest
} = props;
const { isMobile, setOpen, state } = useSidebar();
const sidebarRef = useRef<HTMLDivElement>();

if (collapsible === "none") {
return (
<div className={clsx(className)} ref={ref} {...props}>
{children}
</div>
);
}

if (Boolean(isMobile)) {
return (
<Sheet
isOpen={state === "expanded"}
onEntered={onEntered}
onExited={onExited}
onOpenChange={setOpen}
position={side}
>
{children}
</Sheet>
);
}

return (
<CSSTransition
in={state === "expanded"}
nodeRef={sidebarRef}
onEntered={onEntered}
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}
>
<div className={styles.fakeSidebar} />
<div className={clsx(styles.sidebar, className)} ref={ref} {...rest}>
<div className={styles.sidebarContainer}>{children}</div>
</div>
</div>
</CSSTransition>
);
};

export const Sidebar = React.forwardRef(_Sidebar);
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
import clsx from "clsx";
import React, { type ComponentProps, type Ref } from "react";

import styles from "./styles.module.css";

const _SidebarContent = (
props: ComponentProps<"div">,
ref: Ref<HTMLDivElement>,
) => {
const { className, ...rest } = props;

return (
<div
className={clsx(styles.sidebarContent, className)}
data-sidebar="content"
ref={ref}
{...rest}
/>
);
};

export const SidebarContent = React.forwardRef(_SidebarContent);
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
import clsx from "clsx";
import * as React from "react";
import type { ComponentProps, Ref } from "react";

import styles from "./styles.module.css";

export const _SidebarInset = (
props: ComponentProps<"main">,
ref: Ref<HTMLDivElement>,
) => {
const { className, ...rest } = props;

return (
<main
className={clsx(styles.sidebarInset, className)}
ref={ref}
{...rest}
/>
);
};

export const SidebarInset = React.forwardRef(_SidebarInset);
Loading

0 comments on commit aa8b153

Please sign in to comment.