From 5bf6cf42ede973b699441b46ff8f5b0fbc964dd9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E2=80=9Cnora?= Date: Tue, 4 Feb 2025 21:41:12 -0500 Subject: [PATCH 1/5] chore(side-panel): add zhoosh --- .changeset/itchy-teachers-dance.md | 5 + .../paste-codemods/tools/.cache/mappings.json | 1 + .../components/side-panel/package.json | 2 + .../components/side-panel/src/SidePanel.tsx | 224 ++++++++++++------ .../side-panel/src/SidePanelFooter.tsx | 2 +- .../side-panel/src/SidePanelHeader.tsx | 10 +- .../src/SidePanelPushContentWrapper.tsx | 2 +- .../components/side-panel/src/hooks.ts | 16 ++ .../components/side-panel/src/index.tsx | 2 + .../components/side-panel/src/types.ts | 39 +++ .../side-panel/stories/index.stories.tsx | 156 ++++++++++++ yarn.lock | 2 + 12 files changed, 383 insertions(+), 78 deletions(-) create mode 100644 .changeset/itchy-teachers-dance.md create mode 100644 packages/paste-core/components/side-panel/src/hooks.ts diff --git a/.changeset/itchy-teachers-dance.md b/.changeset/itchy-teachers-dance.md new file mode 100644 index 0000000000..d82766d12e --- /dev/null +++ b/.changeset/itchy-teachers-dance.md @@ -0,0 +1,5 @@ +--- +"@twilio-paste/side-panel": minor +--- + +[Side Panel] Update mobile styles, add useSidePanelState hook, animation fixes diff --git a/packages/paste-codemods/tools/.cache/mappings.json b/packages/paste-codemods/tools/.cache/mappings.json index 02e39e8b8b..f720188cbe 100644 --- a/packages/paste-codemods/tools/.cache/mappings.json +++ b/packages/paste-codemods/tools/.cache/mappings.json @@ -263,6 +263,7 @@ "SidePanelHeader": "@twilio-paste/core/side-panel", "SidePanelHeaderActions": "@twilio-paste/core/side-panel", "SidePanelPushContentWrapper": "@twilio-paste/core/side-panel", + "useSidePanelState": "@twilio-paste/core/side-panel", "Sidebar": "@twilio-paste/core/sidebar", "SidebarBetaBadge": "@twilio-paste/core/sidebar", "SidebarBody": "@twilio-paste/core/sidebar", diff --git a/packages/paste-core/components/side-panel/package.json b/packages/paste-core/components/side-panel/package.json index 48acaad5e3..8e93de05fc 100644 --- a/packages/paste-core/components/side-panel/package.json +++ b/packages/paste-core/components/side-panel/package.json @@ -34,6 +34,7 @@ "@twilio-paste/customization": "^8.1.1", "@twilio-paste/design-tokens": "^10.3.0", "@twilio-paste/icons": "^12.4.0", + "@twilio-paste/modal-dialog-primitive": "^2.0.1", "@twilio-paste/spinner": "^14.1.2", "@twilio-paste/stack": "^8.1.0", "@twilio-paste/style-props": "^9.1.1", @@ -57,6 +58,7 @@ "@twilio-paste/customization": "^8.1.1", "@twilio-paste/design-tokens": "^10.7.0", "@twilio-paste/icons": "^12.7.0", + "@twilio-paste/modal-dialog-primitive": "^2.0.1", "@twilio-paste/spinner": "^14.1.2", "@twilio-paste/stack": "^8.1.0", "@twilio-paste/style-props": "^9.1.1", diff --git a/packages/paste-core/components/side-panel/src/SidePanel.tsx b/packages/paste-core/components/side-panel/src/SidePanel.tsx index 6d33b5aed9..26925d3d02 100644 --- a/packages/paste-core/components/side-panel/src/SidePanel.tsx +++ b/packages/paste-core/components/side-panel/src/SidePanel.tsx @@ -1,12 +1,41 @@ import { animated, useTransition } from "@twilio-paste/animation-library"; -import { Box, safelySpreadBoxProps } from "@twilio-paste/box"; +import { Box, getCustomElementStyles, safelySpreadBoxProps } from "@twilio-paste/box"; import type { BoxProps } from "@twilio-paste/box"; +import { ModalDialogPrimitiveContent, ModalDialogPrimitiveOverlay } from "@twilio-paste/modal-dialog-primitive"; +import { css, styled } from "@twilio-paste/styling-library"; +import { pasteBaseStyles, useTheme } from "@twilio-paste/theme"; import { useMergeRefs, useWindowSize } from "@twilio-paste/utils"; import * as React from "react"; import { SidePanelContext } from "./SidePanelContext"; import type { SidePanelProps } from "./types"; +const SidePanelMobileOverlay = animated( + // @ts-expect-error the styled div color prop from emotion is clashing with our color style prop in BoxProps + styled(ModalDialogPrimitiveOverlay)( + css({ + backgroundColor: "colorBackgroundOverlay", + position: "fixed", + top: 0, + right: 0, + bottom: 0, + left: 0, + width: "100%", + zIndex: "zIndex80", + }), + /* + * import Paste Theme Based Styles due to portal positioning. + * reach portal is a sibling to the main app, so you are now + * no longer a child of the theme provider. We need to re-set + * some of the base styles that we rely on inheriting from + * such as font-family and line-height so that compositions + * of paste components in the side panel are styled correctly. + */ + pasteBaseStyles, + getCustomElementStyles, + ), +); + const StyledSidePanelWrapper = React.forwardRef((props, ref) => ( ((props paddingRight={["space0", "space40", "space40"]} width={["100%", "size40", "size40"]} height={props.height} + boxSizing="content-box" /> )); @@ -31,87 +61,147 @@ const config = { friction: 20, }; -const transitionStyles = { - from: { opacity: 0, transform: "translateX(100%)" }, - enter: { opacity: 1, transform: "translateX(0%)" }, - leave: { opacity: 0, transform: "translateX(100%)" }, - config, -}; - -const mobileTransitionStyles = { - from: { opacity: 0, transform: "translateY(100%)" }, - enter: { opacity: 1, transform: "translateY(0%)" }, - leave: { opacity: 0, transform: "translateY(100%)" }, - config, -}; - -const SidePanel = React.forwardRef( - ({ element = "SIDE_PANEL", label, children, ...props }, ref) => { - const { sidePanelId, isOpen } = React.useContext(SidePanelContext); - - const { breakpointIndex } = useWindowSize(); - - const transitions = - breakpointIndex === 0 ? useTransition(isOpen, mobileTransitionStyles) : useTransition(isOpen, transitionStyles); - - const screenSize = window.innerHeight; +interface SidePanelContentsProps extends SidePanelProps { + sidePanelId: string; + styles: any; +} +const SidePanelContents = React.forwardRef( + ({ label, element, sidePanelId, styles, children, ...props }, ref) => { + // Get the offset of the side panel from the top of the viewport const sidePanelRef = React.useRef(null); const mergedSidePanelRef = useMergeRefs(sidePanelRef, ref) as React.RefObject; - + const screenSize = window.innerHeight; const [offsetY, setOffsetY] = React.useState(0); - - // Get the offset of the side panel from the top of the viewport React.useEffect(() => { const boundingClientRect = sidePanelRef?.current?.getBoundingClientRect(); setOffsetY(boundingClientRect?.y || 0); }, []); + return ( + + + + {children} + + + + ); + }, +); +SidePanelContents.displayName = "SidePanelContents"; + +const SidePanel = React.forwardRef( + ({ element = "SIDE_PANEL", label, children, ...props }, ref) => { + const theme = useTheme(); + const { sidePanelId, isOpen } = React.useContext(SidePanelContext); + // Determine whether this is the initial render in order to block enter animations + const [isFirstRender, setIsFirstRender] = React.useState(true); + React.useEffect(() => { + if (isFirstRender) { + setIsFirstRender(false); + } + }, [isFirstRender]); + + // Define transition styles for both breakpoints + const transitionStyles = { + from: isFirstRender ? undefined : { opacity: 0, width: "0px" }, + enter: { opacity: 1, width: "400px" }, + leave: { opacity: 0, width: "0px" }, + config, + }; + const mobileTransitionStyles = { + from: isFirstRender ? undefined : { opacity: 0, transform: "translateY(100%)" }, + enter: { opacity: 1, transform: "translateY(0%)" }, + leave: { opacity: 0, transform: "translateY(100%)" }, + config, + }; + + // Set mobile or desktop transitions based on breakpointIndex + const { breakpointIndex } = useWindowSize(); + const desktopTransitions = useTransition(isOpen, transitionStyles); + const mobileTransitions = useTransition(isOpen, mobileTransitionStyles); + const transitions = React.useMemo(() => { + if (breakpointIndex === 0) return mobileTransitions; + return desktopTransitions; + }, [breakpointIndex, desktopTransitions, mobileTransitions]); + + // console.log("breakpointIndex", breakpointIndex); + return ( <> {transitions( (styles, item) => - item && ( - - - - {children} - - - - ), + {children} + + + ) : ( + + {children} + + )), )} ); diff --git a/packages/paste-core/components/side-panel/src/SidePanelFooter.tsx b/packages/paste-core/components/side-panel/src/SidePanelFooter.tsx index 248ecee702..ebb51ded1a 100644 --- a/packages/paste-core/components/side-panel/src/SidePanelFooter.tsx +++ b/packages/paste-core/components/side-panel/src/SidePanelFooter.tsx @@ -11,7 +11,7 @@ const SidePanelFooter = React.forwardRef( paddingX={variant === "chat" ? "space50" : "space70"} paddingBottom="space50" paddingTop={variant === "chat" ? "space0" : "space50"} - boxShadow={variant === "chat" ? "none" : "shadow"} + boxShadow={variant === "chat" ? "none" : "shadowElevationTop05"} marginBottom="spaceNegative70" zIndex="zIndex20" display="flex" diff --git a/packages/paste-core/components/side-panel/src/SidePanelHeader.tsx b/packages/paste-core/components/side-panel/src/SidePanelHeader.tsx index d92a6b2528..6c325cfabc 100644 --- a/packages/paste-core/components/side-panel/src/SidePanelHeader.tsx +++ b/packages/paste-core/components/side-panel/src/SidePanelHeader.tsx @@ -4,15 +4,7 @@ import { CloseIcon } from "@twilio-paste/icons/esm/CloseIcon"; import * as React from "react"; import { SidePanelContext } from "./SidePanelContext"; -import type { SidePanelHeaderProps } from "./types"; - -type SidePanelCloseButtonProps = { - setIsOpen: React.Dispatch>; - i18nCloseSidePanelTitle: string; - sidePanelId: string; - isOpen: boolean; - element: string; -}; +import type { SidePanelCloseButtonProps, SidePanelHeaderProps } from "./types"; const SidePanelCloseButton: React.FC> = ({ setIsOpen, diff --git a/packages/paste-core/components/side-panel/src/SidePanelPushContentWrapper.tsx b/packages/paste-core/components/side-panel/src/SidePanelPushContentWrapper.tsx index a1d5da4220..51408d8f00 100644 --- a/packages/paste-core/components/side-panel/src/SidePanelPushContentWrapper.tsx +++ b/packages/paste-core/components/side-panel/src/SidePanelPushContentWrapper.tsx @@ -34,7 +34,7 @@ export const SidePanelPushContentWrapper = React.forwardRef { + const [isOpen, setIsOpen] = React.useState(open); + + const toggleSidePanel = (): void => { + setIsOpen(!isOpen); + }; + + return { + sidePanel: { isOpen, setIsOpen }, + toggleSidePanel, + }; +}; diff --git a/packages/paste-core/components/side-panel/src/index.tsx b/packages/paste-core/components/side-panel/src/index.tsx index dd054b94f5..5399001a1a 100644 --- a/packages/paste-core/components/side-panel/src/index.tsx +++ b/packages/paste-core/components/side-panel/src/index.tsx @@ -17,5 +17,7 @@ export type { SidePanelContainerProps, SidePanelBodyProps, SidePanelFooterProps, + SidePanelStateReturn, } from "./types"; export { SidePanelContext } from "./SidePanelContext"; +export { useSidePanelState } from "./hooks"; diff --git a/packages/paste-core/components/side-panel/src/types.ts b/packages/paste-core/components/side-panel/src/types.ts index a2387f9baf..08a14f8bd2 100644 --- a/packages/paste-core/components/side-panel/src/types.ts +++ b/packages/paste-core/components/side-panel/src/types.ts @@ -175,3 +175,42 @@ export interface SidePanelContextProps { i18nCloseSidePanelTitle: string; i18nOpenSidePanelTitle: string; } + +export interface SidePanelStateReturn { + sidePanel: { + /** + * State for the Side Panel. Determines whether the Side Panel is open or closed. + * + * @type {boolean} + * @default false + * @memberof SidePanelStateReturn + */ + isOpen: boolean; + /** + * Sets the state of the Side Panel between open and closed. + * + * @type {React.Dispatch>} + * @memberof SidePanelStateReturn + */ + setIsOpen: React.Dispatch>; + }; + /** + * Toggles the Side Panel between open and closed states. Apply to the `onClick` of the component that triggers the Side Panel. + * + * @type {() => void} + * @memberof SidePanelStateReturn + */ + toggleSidePanel: () => void; +} + +export interface UseSidePanelStateProps { + open?: boolean; +} + +export type SidePanelCloseButtonProps = { + setIsOpen: React.Dispatch>; + i18nCloseSidePanelTitle: string; + sidePanelId: string; + isOpen: boolean; + element: string; +}; diff --git a/packages/paste-core/components/side-panel/stories/index.stories.tsx b/packages/paste-core/components/side-panel/stories/index.stories.tsx index f40485c961..767a86f141 100644 --- a/packages/paste-core/components/side-panel/stories/index.stories.tsx +++ b/packages/paste-core/components/side-panel/stories/index.stories.tsx @@ -28,6 +28,7 @@ import { SidePanelHeader, SidePanelHeaderActions, SidePanelPushContentWrapper, + useSidePanelState, } from "../src"; import { MessagingInsightsContent } from "./components/MessagingInsightsContent"; import { SidePanelWithAIContent } from "./components/SidePanelWithAIContent"; @@ -75,6 +76,39 @@ Default.parameters = { }, }; +export const NoContent: StoryFn = () => { + const { sidePanel, toggleSidePanel } = useSidePanelState({ open: true }); + return ( + + + + open sesame + + + + headercontent + + + ); +}; +NoContent.parameters = { + padding: false, + a11y: { + config: { + rules: [ + { + /* + * Using position="relative" on SidePanel causes it to overflow other themes in stacked and side-by-side views, and therefore fail color contrast checks based on SidePanelBody's content. + * The DefaultVRT test below serves to test color contrast on the Side Panel component without this issue causing false failures. + */ + id: "color-contrast", + selector: "*:not(*)", + }, + ], + }, + }, +}; + export const Basic: StoryFn = () => { const [isOpen, setIsOpen] = React.useState(true); const sidePanelId = useUID(); @@ -115,6 +149,47 @@ Basic.parameters = { }, }; +export const AIMobile: StoryFn = () => { + const [isOpen, setIsOpen] = React.useState(true); + const sidePanelId = useUID(); + const topbarSkipLinkID = useUID(); + const mainContentSkipLinkID = useUID(); + return ( + <> + + + + + + + + Toggle Side Panel + + + + + + ); +}; +AIMobile.parameters = { + viewport: { defaultViewport: "iphonex" }, + padding: false, + a11y: { + config: { + rules: [ + { + /* + * Using position="relative" on SidePanel causes it to overflow other themes in stacked and side-by-side views, and therefore fail color contrast checks based on SidePanelBody's content. + * The DefaultVRT test below serves to test color contrast on the Side Panel component without this issue causing false failures. + */ + id: "color-contrast", + selector: "*:not(*)", + }, + ], + }, + }, +}; + export const AI: StoryFn = () => { const [isOpen, setIsOpen] = React.useState(true); const sidePanelId = useUID(); @@ -150,6 +225,44 @@ AI.parameters = { }, }; +export const FilterMobile: StoryFn = () => { + const [isOpen, setIsOpen] = React.useState(true); + const sidePanelId = useUID(); + return ( + + + + + + More filters + + 2 + + + + + + ); +}; +FilterMobile.parameters = { + viewport: { defaultViewport: "iphonex" }, + padding: false, + a11y: { + config: { + rules: [ + { + /* + * Using position="relative" on SidePanel causes it to overflow other themes in stacked and side-by-side views, and therefore fail color contrast checks based on SidePanelBody's content. + * The DefaultVRT test below serves to test color contrast on the Side Panel component without this issue causing false failures. + */ + id: "color-contrast", + selector: "*:not(*)", + }, + ], + }, + }, +}; + export const Filter: StoryFn = () => { const [isOpen, setIsOpen] = React.useState(true); const sidePanelId = useUID(); @@ -374,6 +487,49 @@ Composed.parameters = { }, }; +export const ComposedMobile: StoryFn = () => { + const [isOpen, setIsOpen] = React.useState(true); + + const topbarSkipLinkID = useUID(); + const mainContentSkipLinkID = useUID(); + + return ( + + {/* Sidebar can be placed anywhere - position fixed */} + + + + + + + + {/* Side Panel can be placed anywhere - position fixed */} + + + + + ); +}; + +ComposedMobile.parameters = { + viewport: { defaultViewport: "iphonex" }, + padding: false, + a11y: { + config: { + rules: [ + { + /* + * Using position="relative" on SidePanel causes it to overflow other themes in stacked and side-by-side views, and therefore fail color contrast checks based on SidePanelBody's content. + * The DefaultVRT test below serves to test color contrast on the Side Panel component without this issue causing false failures. + */ + id: "color-contrast", + selector: "*:not(*)", + }, + ], + }, + }, +}; + export const Customized: StoryFn = () => { const [isOpen, setIsOpen] = React.useState(true); const label = useUID(); diff --git a/yarn.lock b/yarn.lock index e6e52febbb..bbb6292c91 100644 --- a/yarn.lock +++ b/yarn.lock @@ -14852,6 +14852,7 @@ __metadata: "@twilio-paste/customization": ^8.1.1 "@twilio-paste/design-tokens": ^10.7.0 "@twilio-paste/icons": ^12.7.0 + "@twilio-paste/modal-dialog-primitive": ^2.0.1 "@twilio-paste/spinner": ^14.1.2 "@twilio-paste/stack": ^8.1.0 "@twilio-paste/style-props": ^9.1.1 @@ -14876,6 +14877,7 @@ __metadata: "@twilio-paste/customization": ^8.1.1 "@twilio-paste/design-tokens": ^10.3.0 "@twilio-paste/icons": ^12.4.0 + "@twilio-paste/modal-dialog-primitive": ^2.0.1 "@twilio-paste/spinner": ^14.1.2 "@twilio-paste/stack": ^8.1.0 "@twilio-paste/style-props": ^9.1.1 From b4ba31b248c53857efdbc006134d9038e683ddfc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E2=80=9Cnora?= Date: Tue, 4 Feb 2025 21:41:34 -0500 Subject: [PATCH 2/5] docs(side-panel): more examples --- .../shortcodes/live-preview/index.tsx | 3 + .../src/pages/components/paragraph/api.mdx | 2 +- .../src/pages/components/side-panel/index.mdx | 123 ++++++++++++++++-- 3 files changed, 114 insertions(+), 14 deletions(-) diff --git a/packages/paste-website/src/components/shortcodes/live-preview/index.tsx b/packages/paste-website/src/components/shortcodes/live-preview/index.tsx index db3dfa6f10..9b6f284ab7 100644 --- a/packages/paste-website/src/components/shortcodes/live-preview/index.tsx +++ b/packages/paste-website/src/components/shortcodes/live-preview/index.tsx @@ -22,6 +22,7 @@ interface LivePreviewProps { disabled?: boolean; noInline?: boolean; showOverflow?: boolean; + height?: string; } const LivePreview: React.FC> = ({ @@ -31,6 +32,7 @@ const LivePreview: React.FC> = ({ noInline = false, showOverflow = false, scope, + height = "unset", }) => { const [viewCode, setViewCode] = React.useState(false); const id = useUID(); @@ -72,6 +74,7 @@ const LivePreview: React.FC> = ({ borderTopRightRadius="borderRadius20" position="relative" overflow={overflow} + height={height} > diff --git a/packages/paste-website/src/pages/components/paragraph/api.mdx b/packages/paste-website/src/pages/components/paragraph/api.mdx index 6e54b7df80..24fdfbc544 100644 --- a/packages/paste-website/src/pages/components/paragraph/api.mdx +++ b/packages/paste-website/src/pages/components/paragraph/api.mdx @@ -31,7 +31,7 @@ export const getStaticProps = async () => { pageHeaderData: { categoryRoute: SidebarCategoryRoutes.COMPONENTS, githubUrl: '/?path=/story/components-alert--neutral', - storybookUrl: 'https://github.com/twilio-labs/paste/tree/main/packages/paste-core/components/alert', + storybookUrl: '/?path=/story/components-paragraph--default', }, }, }; diff --git a/packages/paste-website/src/pages/components/side-panel/index.mdx b/packages/paste-website/src/pages/components/side-panel/index.mdx index c7e3c5c5c3..66f57be80b 100644 --- a/packages/paste-website/src/pages/components/side-panel/index.mdx +++ b/packages/paste-website/src/pages/components/side-panel/index.mdx @@ -8,6 +8,9 @@ export const meta = { import {Anchor} from '@twilio-paste/anchor'; import {Callout, CalloutHeading, CalloutText} from '@twilio-paste/callout'; +import {SidePanel, SidePanelContainer, SidePanelButton, SidePanelPushContentWrapper, SidePanelHeader, SidePanelBody, useSidePanelState} from '@twilio-paste/side-panel'; +import {Heading} from '@twilio-paste/heading'; +import {Separator} from '@twilio-paste/separator'; import {SidebarCategoryRoutes} from '../../../constants'; import { @@ -54,19 +57,21 @@ export const SidePanelExample = (): React.ReactNode => { - Heading + + + Assistant + - - Side Panel content goes here. - + Footer content goes here. @@ -86,6 +91,8 @@ export const SidePanelExample = (): React.ReactNode => { Side Panel is a container that pushes the main page content when open. It's important for page content to be responsive when using a Side Panel so that the opening and closing of the panel doesn't cause the page to jump or shift. At mobile breakpoints, the Side Panel overlays the page content and takes up the full width of the viewport. +Side Panel is primarily used within [AI experiences](/experiences/artificial-intelligence) and on pages using the [filter pattern](/patterns/filter) when there are too many filter options to display on the page. + Only use one Side Panel on a page @@ -127,19 +134,21 @@ export const SidePanelExample = (): React.ReactNode => { - Heading + + + Assistant + - - Side Panel content goes here. - + Footer content goes here. @@ -153,6 +162,49 @@ export const SidePanelExample = (): React.ReactNode => { }`} /> +### Side Panel with Footer + +Use the `default` variant of SidePanelFooter when you need to add actions to the bottom of the Side Panel. Use the `chat` variant of Side Panel Footer for AI use cases. + + { + const [isOpen, setIsOpen] = React.useState(true); + const sidePanelId = useUID(); + return ( + + + + + More filters + + + + + Side Panel content goes here. + + + Footer content goes here. + + + + + + More filters + + 2 + + + + + + ) +}`} +/> + ### Internationalization To internationalize Side Panel, simply pass different text as children to the Side Panel components. The only exceptions are the close button in the SidePanelHeader and the SidePanelButton/SidePanelBadgeButton. To change the buttons' accessible label text, use the `i18nCloseSidePanelTitle` and `i18nOpenSidePanel` props on the `SidePanelContainer`. @@ -167,9 +219,9 @@ export const SidePanelExample = (): React.ReactNode => { const sidePanelId = useUID(); return ( - + - Título + Título Side Panel content goes here. @@ -177,7 +229,7 @@ export const SidePanelExample = (): React.ReactNode => { - Probar Panel Lateral + Probar Panel Lateral @@ -185,6 +237,51 @@ export const SidePanelExample = (): React.ReactNode => { }`} /> +### Using the state hook + +Side Panel comes with the option of using a hook to manage the open and close state of the panel. The `useSidePanelState` hook returns an object to spread onto SidePanelContainer and a function to pass to the `onClick` of SidePanelButton. + + + {`const SidePanelExample = () => { + const { sidePanel, toggleSidePanel } = useSidePanelState({}); + return ( + + + + Toggle Side Panel + + + + + + Assistant + + + + + Side Panel content goes here. + + + + ) + } + render()`} + + ## Composition notes The Side Panel comes with some smaller components that can be used to compose a Side Panel to your application's needs. All of the following components should be used inside of a `SidePanelContainer`, with `SidePanel` and `SidePanelPushContentWrapper` being its direct children. The Side Panel Container controls the positioning of the Side Panel with relation to the page content. From a6e467a01fb5994f98f34031afebcbd693dbad8c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E2=80=9Cnora?= Date: Tue, 4 Feb 2025 21:50:24 -0500 Subject: [PATCH 3/5] chore: things i forgot --- .changeset/itchy-teachers-dance.md | 1 + .changeset/lazy-months-roll.md | 5 +++++ packages/paste-core/components/side-panel/src/SidePanel.tsx | 2 -- 3 files changed, 6 insertions(+), 2 deletions(-) create mode 100644 .changeset/lazy-months-roll.md diff --git a/.changeset/itchy-teachers-dance.md b/.changeset/itchy-teachers-dance.md index d82766d12e..616c58a27a 100644 --- a/.changeset/itchy-teachers-dance.md +++ b/.changeset/itchy-teachers-dance.md @@ -1,5 +1,6 @@ --- "@twilio-paste/side-panel": minor +"@twilio-paste/core": minor --- [Side Panel] Update mobile styles, add useSidePanelState hook, animation fixes diff --git a/.changeset/lazy-months-roll.md b/.changeset/lazy-months-roll.md new file mode 100644 index 0000000000..5ac37df623 --- /dev/null +++ b/.changeset/lazy-months-roll.md @@ -0,0 +1,5 @@ +--- +"@twilio-paste/codemods": minor +--- + +[Codemods] new export from Side Panel: useSidePanelState() diff --git a/packages/paste-core/components/side-panel/src/SidePanel.tsx b/packages/paste-core/components/side-panel/src/SidePanel.tsx index 26925d3d02..cb206cc9f7 100644 --- a/packages/paste-core/components/side-panel/src/SidePanel.tsx +++ b/packages/paste-core/components/side-panel/src/SidePanel.tsx @@ -166,8 +166,6 @@ const SidePanel = React.forwardRef( return desktopTransitions; }, [breakpointIndex, desktopTransitions, mobileTransitions]); - // console.log("breakpointIndex", breakpointIndex); - return ( <> {transitions( From c5ff4e561fda89a96af61435c644fec3101ea24d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E2=80=9Cnora?= Date: Fri, 7 Feb 2025 11:25:09 -0500 Subject: [PATCH 4/5] chore: update state return and only use modal on mobile --- .../components/side-panel/src/SidePanel.tsx | 12 +++++-- .../components/side-panel/src/hooks.ts | 8 ++--- .../components/side-panel/src/types.ts | 31 +++++++------------ .../side-panel/stories/index.stories.tsx | 6 ++-- .../src/pages/components/side-panel/index.mdx | 6 ++-- 5 files changed, 28 insertions(+), 35 deletions(-) diff --git a/packages/paste-core/components/side-panel/src/SidePanel.tsx b/packages/paste-core/components/side-panel/src/SidePanel.tsx index cb206cc9f7..a9b545ca9d 100644 --- a/packages/paste-core/components/side-panel/src/SidePanel.tsx +++ b/packages/paste-core/components/side-panel/src/SidePanel.tsx @@ -64,10 +64,16 @@ const config = { interface SidePanelContentsProps extends SidePanelProps { sidePanelId: string; styles: any; + isMobile: boolean; } +const getAs = (isMobile: boolean): any => { + if (isMobile) return ModalDialogPrimitiveContent as any; + return "div"; +}; + const SidePanelContents = React.forwardRef( - ({ label, element, sidePanelId, styles, children, ...props }, ref) => { + ({ label, element, sidePanelId, styles, isMobile, children, ...props }, ref) => { // Get the offset of the side panel from the top of the viewport const sidePanelRef = React.useRef(null); const mergedSidePanelRef = useMergeRefs(sidePanelRef, ref) as React.RefObject; @@ -83,7 +89,7 @@ const SidePanelContents = React.forwardRef( sidePanelId={sidePanelId} styles={styles} label={label} + isMobile ref={ref} > {children} @@ -195,6 +202,7 @@ const SidePanel = React.forwardRef( sidePanelId={sidePanelId} styles={styles} label={label} + isMobile={false} ref={ref} > {children} diff --git a/packages/paste-core/components/side-panel/src/hooks.ts b/packages/paste-core/components/side-panel/src/hooks.ts index ed5db684c9..d5de73345f 100644 --- a/packages/paste-core/components/side-panel/src/hooks.ts +++ b/packages/paste-core/components/side-panel/src/hooks.ts @@ -5,12 +5,8 @@ import type { SidePanelStateReturn, UseSidePanelStateProps } from "./types"; export const useSidePanelState = ({ open = false }: UseSidePanelStateProps = {}): SidePanelStateReturn => { const [isOpen, setIsOpen] = React.useState(open); - const toggleSidePanel = (): void => { - setIsOpen(!isOpen); - }; - return { - sidePanel: { isOpen, setIsOpen }, - toggleSidePanel, + isOpen, + setIsOpen, }; }; diff --git a/packages/paste-core/components/side-panel/src/types.ts b/packages/paste-core/components/side-panel/src/types.ts index 08a14f8bd2..054160258a 100644 --- a/packages/paste-core/components/side-panel/src/types.ts +++ b/packages/paste-core/components/side-panel/src/types.ts @@ -177,30 +177,21 @@ export interface SidePanelContextProps { } export interface SidePanelStateReturn { - sidePanel: { - /** - * State for the Side Panel. Determines whether the Side Panel is open or closed. - * - * @type {boolean} - * @default false - * @memberof SidePanelStateReturn - */ - isOpen: boolean; - /** - * Sets the state of the Side Panel between open and closed. - * - * @type {React.Dispatch>} - * @memberof SidePanelStateReturn - */ - setIsOpen: React.Dispatch>; - }; /** - * Toggles the Side Panel between open and closed states. Apply to the `onClick` of the component that triggers the Side Panel. + * State for the Side Panel. Determines whether the Side Panel is open or closed. * - * @type {() => void} + * @type {boolean} + * @default false + * @memberof SidePanelStateReturn + */ + isOpen: boolean; + /** + * Sets the state of the Side Panel between open and closed. + * + * @type {React.Dispatch>} * @memberof SidePanelStateReturn */ - toggleSidePanel: () => void; + setIsOpen: React.Dispatch>; } export interface UseSidePanelStateProps { diff --git a/packages/paste-core/components/side-panel/stories/index.stories.tsx b/packages/paste-core/components/side-panel/stories/index.stories.tsx index 767a86f141..7488d359d6 100644 --- a/packages/paste-core/components/side-panel/stories/index.stories.tsx +++ b/packages/paste-core/components/side-panel/stories/index.stories.tsx @@ -77,13 +77,11 @@ Default.parameters = { }; export const NoContent: StoryFn = () => { - const { sidePanel, toggleSidePanel } = useSidePanelState({ open: true }); + const sidePanel = useSidePanelState({ open: true }); return ( - - open sesame - + open sesame headercontent diff --git a/packages/paste-website/src/pages/components/side-panel/index.mdx b/packages/paste-website/src/pages/components/side-panel/index.mdx index 66f57be80b..0934464950 100644 --- a/packages/paste-website/src/pages/components/side-panel/index.mdx +++ b/packages/paste-website/src/pages/components/side-panel/index.mdx @@ -239,7 +239,7 @@ export const SidePanelExample = (): React.ReactNode => { ### Using the state hook -Side Panel comes with the option of using a hook to manage the open and close state of the panel. The `useSidePanelState` hook returns an object to spread onto SidePanelContainer and a function to pass to the `onClick` of SidePanelButton. +Side Panel comes with the option of using a hook to manage the open and close state of the panel. The `useSidePanelState` hook returns an object to spread onto SidePanelContainer. To change the default state of the Side Panel from closed to open, pass `open: true` to the hook. {`const SidePanelExample = () => { - const { sidePanel, toggleSidePanel } = useSidePanelState({}); + const sidePanel = useSidePanelState({}); return ( - + Toggle Side Panel From 861c23f5b334b588e7cc9291f8d2b3fc1dd7501b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E2=80=9Cnora?= Date: Fri, 7 Feb 2025 11:48:59 -0500 Subject: [PATCH 5/5] chore: save some lines --- .../paste-core/components/side-panel/src/SidePanel.tsx | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/packages/paste-core/components/side-panel/src/SidePanel.tsx b/packages/paste-core/components/side-panel/src/SidePanel.tsx index a9b545ca9d..93b8949f89 100644 --- a/packages/paste-core/components/side-panel/src/SidePanel.tsx +++ b/packages/paste-core/components/side-panel/src/SidePanel.tsx @@ -67,11 +67,6 @@ interface SidePanelContentsProps extends SidePanelProps { isMobile: boolean; } -const getAs = (isMobile: boolean): any => { - if (isMobile) return ModalDialogPrimitiveContent as any; - return "div"; -}; - const SidePanelContents = React.forwardRef( ({ label, element, sidePanelId, styles, isMobile, children, ...props }, ref) => { // Get the offset of the side panel from the top of the viewport @@ -89,7 +84,7 @@ const SidePanelContents = React.forwardRef