diff --git a/.changeset/yellow-geese-sneeze.md b/.changeset/yellow-geese-sneeze.md new file mode 100644 index 0000000000..6abda8edd9 --- /dev/null +++ b/.changeset/yellow-geese-sneeze.md @@ -0,0 +1,6 @@ +--- +"@twilio-paste/side-panel": patch +"@twilio-paste/core": patch +--- + +[Side Panel] Fix positioning of close button when no header actions are being used by adding justifyContent="space-between" diff --git a/cypress/integration/sitemap-vrt/constants.ts b/cypress/integration/sitemap-vrt/constants.ts index 151730c856..49e88dd27e 100644 --- a/cypress/integration/sitemap-vrt/constants.ts +++ b/cypress/integration/sitemap-vrt/constants.ts @@ -186,6 +186,9 @@ export const SITEMAP = [ "/components/sidebar-navigation/", "/components/sidebar-navigation/api", "/components/sidebar-navigation/changelog", + "/components/side-panel/", + "/components/side-panel/api", + "/components/side-panel/changelog", "/components/stack/", "/components/stack/api", "/components/stack/changelog", diff --git a/packages/paste-core/components/side-panel/CHANGELOG.md b/packages/paste-core/components/side-panel/CHANGELOG.md new file mode 100644 index 0000000000..e69de29bb2 diff --git a/packages/paste-core/components/side-panel/src/SidePanelHeader.tsx b/packages/paste-core/components/side-panel/src/SidePanelHeader.tsx index 1f19ed7bee..1ffba020b1 100644 --- a/packages/paste-core/components/side-panel/src/SidePanelHeader.tsx +++ b/packages/paste-core/components/side-panel/src/SidePanelHeader.tsx @@ -56,6 +56,7 @@ const SidePanelHeader = React.forwardRef( display="flex" columnGap="space30" alignItems="center" + justifyContent="space-between" {...safelySpreadBoxProps(props)} ref={ref} element={element} diff --git a/packages/paste-core/components/side-panel/stories/components/MessagingInsightsContent.tsx b/packages/paste-core/components/side-panel/stories/components/MessagingInsightsContent.tsx index b30a9c09ef..5e3215f650 100644 --- a/packages/paste-core/components/side-panel/stories/components/MessagingInsightsContent.tsx +++ b/packages/paste-core/components/side-panel/stories/components/MessagingInsightsContent.tsx @@ -2,7 +2,6 @@ /* eslint-disable react/jsx-max-depth */ // eslint-disable-next-line eslint-comments/disable-enable-pair /* eslint-disable import/no-extraneous-dependencies */ -import { Badge } from "@twilio-paste/badge"; import { Box } from "@twilio-paste/box"; import { Button } from "@twilio-paste/button"; import { Card } from "@twilio-paste/card"; diff --git a/packages/paste-core/components/side-panel/stories/components/SidebarWithContent.tsx b/packages/paste-core/components/side-panel/stories/components/SidebarWithContent.tsx index 9119903746..4502099ed8 100644 --- a/packages/paste-core/components/side-panel/stories/components/SidebarWithContent.tsx +++ b/packages/paste-core/components/side-panel/stories/components/SidebarWithContent.tsx @@ -22,7 +22,7 @@ export const SidebarWithContent: React.FC< return ( - + Explore products 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 15210c3434..7978428fe1 100644 --- a/packages/paste-core/components/side-panel/stories/index.stories.tsx +++ b/packages/paste-core/components/side-panel/stories/index.stories.tsx @@ -57,19 +57,78 @@ Default.parameters = { export const Basic = (): React.ReactNode => { const [isOpen, setIsOpen] = React.useState(true); const sidePanelId = useUID(); + const topbarSkipLinkID = useUID(); + const mainContentSkipLinkID = useUID(); return ( - - - - Toggle Side Panel - - + <> + + + + + + + + Toggle Side Panel + + + + + ); }; Basic.parameters = { padding: false, }; +export const I18n = (): React.ReactNode => { + const [isOpen, setIsOpen] = React.useState(true); + + const topbarSkipLinkID = useUID(); + const mainContentSkipLinkID = useUID(); + const sidePanelId = useUID(); + return ( + + + + + + + + Probar Panel Lateral + + + + + + Título + + + + + + + + + + ); +}; + +I18n.parameters = { + padding: false, +}; + export const ContentDemo = (): React.ReactNode => { const [isOpen, setIsOpen] = React.useState(true); @@ -148,7 +207,7 @@ export const Composed = (): React.ReactNode => { {/* Sidebar can be placed anywhere - position fixed */} - + diff --git a/packages/paste-website/src/pages/components/side-modal/index.mdx b/packages/paste-website/src/pages/components/side-modal/index.mdx index 5071df70f2..55246c82a3 100644 --- a/packages/paste-website/src/pages/components/side-modal/index.mdx +++ b/packages/paste-website/src/pages/components/side-modal/index.mdx @@ -100,6 +100,13 @@ Side Modal and non-modal dialogs follow these accessibility guidelines: - A Side Modal is a focus trap, and focus is placed inside it when it's shown. - A Side Modal must be triggered by an explicit user action, e.g. clicking a button. +### Side Panel vs. Side Modal + +[Side Panel](/components/side-panel) and Side Modal are both used to display additional content on the side of the main page content. Side Panel is used for content that is not blocking and can be interacted with while the main page content is still visible. Side Panels are designed to remain open while the user completes other tasks in the main content of the page. Side Modals typically need to be closed before the user returns to their main task as the Modal overlays part of the page. + +Side Modals are a focus trap and can't be tabbed out of, while the content of Side Panels can be tabbed through and then tabbed out of to return to the main page content. + + ## Examples ### Basic Side Modal diff --git a/packages/paste-website/src/pages/components/side-panel/api.mdx b/packages/paste-website/src/pages/components/side-panel/api.mdx new file mode 100644 index 0000000000..cdda14550d --- /dev/null +++ b/packages/paste-website/src/pages/components/side-panel/api.mdx @@ -0,0 +1,84 @@ +export const meta = { + title: 'Side Panel - API', + package: '@twilio-paste/side-panel', + description: + 'Side Panel is a container that pushes the main page content when open.', + slug: '/components/side-panel/api', +}; + +import Changelog from '@twilio-paste/side-panel/CHANGELOG.md'; // I don't know why this is needed but if you remove it the page fails to render +import packageJson from '@twilio-paste/side-panel/package.json'; + +import {SidebarCategoryRoutes} from '../../../constants'; +import ComponentPageLayout from '../../../layouts/ComponentPageLayout'; +import {getFeature, getNavigationData, getComponentApi} from '../../../utils/api'; + +export default ComponentPageLayout; + +export const getStaticProps = async () => { + const navigationData = await getNavigationData(); + const feature = await getFeature('Side Panel'); + const {componentApi, componentApiTocData} = getComponentApi('@twilio-paste/side-panel'); + return { + props: { + data: { + ...packageJson, + ...feature, + }, + componentApi, + mdxHeadings: [...mdxHeadings, ...componentApiTocData], + navigationData, + pageHeaderData: { + categoryRoute: SidebarCategoryRoutes.COMPONENTS, + githubUrl: 'https://github.com/twilio-labs/paste/tree/main/packages/paste-core/components/side-panel', + storybookUrl: '/?path=/story/components-side-panel--default', + }, + }, + }; +}; + +## Installation + +```bash +yarn add @twilio-paste/side-panel - or - yarn add @twilio-paste/core +``` + +## Usage + +```jsx +import { + SidePanelContainer, + SidePanel, + SidePanelHeader, + SidePanelHeaderActions, + SidePanelBody, + SidePanelPushContentWrapper, + SidePanelButton, +} from '@twilio-paste/core/side-panel'; + +const SideModalExample: React.FC = () => { + const [isOpen, setIsOpen] = React.useState(true); + return ( + + + + Heading goes here. + + Actions go here. + + + + Side Panel content goes here. + + + + Toggle Side Panel + + + ); +}; +``` + +## Props + + diff --git a/packages/paste-website/src/pages/components/side-panel/changelog.mdx b/packages/paste-website/src/pages/components/side-panel/changelog.mdx new file mode 100644 index 0000000000..a9b113756a --- /dev/null +++ b/packages/paste-website/src/pages/components/side-panel/changelog.mdx @@ -0,0 +1,38 @@ +export const meta = { + title: 'Side Panel - Changelog', + package: '@twilio-paste/side-panel', + description: + 'Side Panel is a container that pushes the main page content when open.', + slug: '/components/side-panel/changelog', +}; + +import Changelog from '@twilio-paste/side-panel/CHANGELOG.md'; +import packageJson from '@twilio-paste/side-panel/package.json'; + +import {SidebarCategoryRoutes} from '../../../constants'; +import ComponentPageLayout from '../../../layouts/ComponentPageLayout'; +import {getFeature, getNavigationData} from '../../../utils/api'; + +export default ComponentPageLayout; + +export const getStaticProps = async () => { + const navigationData = await getNavigationData(); + const feature = await getFeature('Side Panel'); + return { + props: { + data: { + ...packageJson, + ...feature, + }, + navigationData, + mdxHeadings, + pageHeaderData: { + categoryRoute: SidebarCategoryRoutes.COMPONENTS, + githubUrl: 'https://github.com/twilio-labs/paste/tree/main/packages/paste-core/components/side-panel', + storybookUrl: '/?path=/story/components-side-panel--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 new file mode 100644 index 0000000000..c8e2f0240e --- /dev/null +++ b/packages/paste-website/src/pages/components/side-panel/index.mdx @@ -0,0 +1,200 @@ +export const meta = { + title: 'Side Panel', + package: '@twilio-paste/side-panel', + description: + 'Side Panel is a container that pushes the main page content when open.', + slug: '/components/side-panel/', +}; + +import {Anchor} from '@twilio-paste/anchor'; +import {Callout, CalloutHeading, CalloutText} from '@twilio-paste/callout'; + +import {SidebarCategoryRoutes} from '../../../constants'; +import { + defaultExample, + footerExample, + sideModalButtonExample, + hookExample, +} from '../../../component-examples/SideModalExamples'; +import packageJson from '@twilio-paste/side-panel/package.json'; +import ComponentPageLayout from '../../../layouts/ComponentPageLayout'; +import {getFeature, getNavigationData} from '../../../utils/api'; + +export default ComponentPageLayout; + +export const getStaticProps = async () => { + const navigationData = await getNavigationData(); + const feature = await getFeature('Side Panel'); + return { + props: { + data: { + ...packageJson, + ...feature, + }, + navigationData, + mdxHeadings, + pageHeaderData: { + categoryRoute: SidebarCategoryRoutes.COMPONENTS, + githubUrl: 'https://github.com/twilio-labs/paste/tree/main/packages/paste-core/components/side-panel', + storybookUrl: '/?path=/story/components-side-panel--default', + }, + }, + }; +}; + + { + const [isOpen, setIsOpen] = React.useState(true); + const sidePanelId = useUID(); + return ( + + + + Heading + + + + + + + Side Panel content goes here. + + + + + Toggle Side Panel + + + + )`} +/> + +## Guidelines + +### About Side Panel + +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. + + + Only use one Side Panel on a page + + We currently only support having one Side Panel open on a page. If you have questions, please post{' '} + + a GitHub Discussion + + . + + + +### Accessibility + +- There must be an accessible aria label. Pass the descriptive label to `SidePanel` using the `label` prop. +- The close button in the Side Panel Header as well as the Side Panel Button / Side Panel Badge Button all use `aria-controls` and `aria-expanded` to indicate the connection to and the state of the Side Panel. +- Focus is placed on the close button when the Side Panel is opened, but the Side Panel is not a focus trap. Users can tab through the Side Panel content and then tab out of the Side Panel to the main page content without closing the Side Panel. + +### Side Panel vs. Side Modal + +Side Panel and [Side Modal](/components/side-modal) are both used to display additional content on the side of the main page content. Side Panel is used for content that is not blocking and can be interacted with while the main page content is still visible. Side Panels are designed to remain open while the user completes other tasks in the main content of the page. Side Modals typically need to be closed before the user returns to their main task as the Modal overlays part of the page. + +Side Modals are a focus trap and can't be tabbed out of, while the content of Side Panels can be tabbed through and then tabbed out of to return to the main page content. + +## Examples + +### Basic Side Panel + +Pass the `isOpen` and `setIsOpen` props to the `SidePanelContainer` component to control the open and close state of the Side Panel. + + { + const [isOpen, setIsOpen] = React.useState(true); + const sidePanelId = useUID(); + return ( + + + + Heading + + + + + + + Side Panel content goes here. + + + + + Toggle Side Panel + + + + )`} +/> + +### 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`. + + { + const [isOpen, setIsOpen] = React.useState(true); + const sidePanelId = useUID(); + return ( + + + + Título + + + ... + + + + + Probar Panel Lateral + + + + )`} +/> + +## 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. + +### Side Panel + +The Side Panel contains all the various elements of the component. + +#### Side Panel Header + +The Side Panel Header is a container for the descriptive title of the panel and sometimes an associated icon, in addition to any action buttons used in the panel. It also contains the close button. + +##### Side Panel Header Actions + +The Side Panel Header Actions component is a container for action buttons that are used in the Side Panel Header. Common actions include a `MoreIcon` for additional menu options. + +#### Side Panel Body + +The Side Panel Body is a container for the main content of the Side Panel. This is where the majority of the content will be placed. + +### Side Panel Push Content Wrapper + +The Side Panel Push Content Wrapper is a container for the main page content that is pushed to the side when the Side Panel is open. This wrapper is used to control the positioning of the main page content when the Side Panel is open. Ensuring that the content of this component is fully responsive is important to prevent the page from jumping or shifting when the Side Panel is opened or closed. + +#### Side Panel Button (or Side Panel Badge Button) + +In order to ensure accessibility, use one of the buttons exported from the Side Panel package as the trigger for the Side Panel. The Side Panel Button is a button that opens the Side Panel when clicked. \ No newline at end of file