diff --git a/.changeset/forty-insects-eat.md b/.changeset/forty-insects-eat.md new file mode 100644 index 0000000000..90d5486d27 --- /dev/null +++ b/.changeset/forty-insects-eat.md @@ -0,0 +1,6 @@ +--- +'@twilio-paste/button': patch +'@twilio-paste/core': patch +--- + +[Button] allow passing all style props to Button with variant="reset", effectively making it a Button primitive. diff --git a/.changeset/fresh-grapes-drop.md b/.changeset/fresh-grapes-drop.md new file mode 100644 index 0000000000..ec59ecc4b6 --- /dev/null +++ b/.changeset/fresh-grapes-drop.md @@ -0,0 +1,6 @@ +--- +'@twilio-paste/sidebar': minor +'@twilio-paste/core': minor +--- + +[Sidebar] add SidebarNavigation and all relevant sub-components diff --git a/.changeset/moody-deers-obey.md b/.changeset/moody-deers-obey.md new file mode 100644 index 0000000000..8a8097f945 --- /dev/null +++ b/.changeset/moody-deers-obey.md @@ -0,0 +1,6 @@ +--- +'@twilio-paste/core': patch +'@twilio-paste/design-tokens': patch +--- + +[Design tokens] update the `shadowFocusInverseInset` token in the `twilio` and `twilio-dark` themes diff --git a/.changeset/new-wolves-crash.md b/.changeset/new-wolves-crash.md new file mode 100644 index 0000000000..f0ecb5b019 --- /dev/null +++ b/.changeset/new-wolves-crash.md @@ -0,0 +1,6 @@ +--- +'@twilio-paste/codemods': patch +'@twilio-paste/core': patch +--- + +[Codemods] add SidebarNavigation components diff --git a/.changeset/tiny-poems-smash.md b/.changeset/tiny-poems-smash.md new file mode 100644 index 0000000000..7a80e4fad0 --- /dev/null +++ b/.changeset/tiny-poems-smash.md @@ -0,0 +1,5 @@ +--- +'@twilio-paste/icons': patch +--- + +[Icons] apply `flex-shrink: 0` to IconWrapper by default, as we never want icons to be compressed unexpectedly diff --git a/packages/paste-codemods/tools/.cache/mappings.json b/packages/paste-codemods/tools/.cache/mappings.json index 71870d22d3..1c3d9cccfa 100644 --- a/packages/paste-codemods/tools/.cache/mappings.json +++ b/packages/paste-codemods/tools/.cache/mappings.json @@ -226,8 +226,15 @@ "SidebarHeader": "@twilio-paste/core/sidebar", "SidebarHeaderIconButton": "@twilio-paste/core/sidebar", "SidebarHeaderLabel": "@twilio-paste/core/sidebar", + "SidebarNavigation": "@twilio-paste/core/sidebar", + "SidebarNavigationDisclosure": "@twilio-paste/core/sidebar", + "SidebarNavigationDisclosureContent": "@twilio-paste/core/sidebar", + "SidebarNavigationDisclosureHeading": "@twilio-paste/core/sidebar", + "SidebarNavigationDisclosureHeadingWrapper": "@twilio-paste/core/sidebar", + "SidebarNavigationItem": "@twilio-paste/core/sidebar", "SidebarOverlayContentWrapper": "@twilio-paste/core/sidebar", "SidebarPushContentWrapper": "@twilio-paste/core/sidebar", + "useSidebarNavigationDisclosureState": "@twilio-paste/core/sidebar", "SkeletonLoader": "@twilio-paste/core/skeleton-loader", "Spinner": "@twilio-paste/core/spinner", "StatusBadge": "@twilio-paste/core/status", diff --git a/packages/paste-core/components/button/src/ResetButton.tsx b/packages/paste-core/components/button/src/ResetButton.tsx index 4e9e3d7276..df24363fe3 100644 --- a/packages/paste-core/components/button/src/ResetButton.tsx +++ b/packages/paste-core/components/button/src/ResetButton.tsx @@ -1,6 +1,6 @@ import * as React from 'react'; import type {BoxStyleProps} from '@twilio-paste/box'; -import {Box, safelySpreadBoxProps} from '@twilio-paste/box'; +import {Box} from '@twilio-paste/box'; import merge from 'deepmerge'; import {SizeStyles, BaseStyles} from './styles'; @@ -29,9 +29,9 @@ const ResetButton = React.forwardRef( ); } diff --git a/packages/paste-core/components/button/src/index.tsx b/packages/paste-core/components/button/src/index.tsx index 3aef4a3bab..0395ce6d61 100644 --- a/packages/paste-core/components/button/src/index.tsx +++ b/packages/paste-core/components/button/src/index.tsx @@ -153,6 +153,7 @@ const ButtonContents: React.FC> = ( justifyContent={buttonVariantHasBoundingBox ? null : 'center'} columnGap="space20" alignItems="center" + width="100%" > {children} diff --git a/packages/paste-core/components/sidebar/__tests__/sidebarNavigation.spec.tsx b/packages/paste-core/components/sidebar/__tests__/sidebarNavigation.spec.tsx new file mode 100644 index 0000000000..376c31f297 --- /dev/null +++ b/packages/paste-core/components/sidebar/__tests__/sidebarNavigation.spec.tsx @@ -0,0 +1,330 @@ +import * as React from 'react'; +import {render, screen, fireEvent} from '@testing-library/react'; +import {Theme} from '@twilio-paste/theme'; +import {CustomizationProvider} from '@twilio-paste/customization'; +import {Box} from '@twilio-paste/box'; +import {Button} from '@twilio-paste/button'; +import {ProductFlexIcon} from '@twilio-paste/icons/esm/ProductFlexIcon'; +import {Menu, MenuButton, MenuItem, MenuSeparator, useMenuState} from '@twilio-paste/menu'; +import {ProductContactCenterTasksIcon} from '@twilio-paste/icons/esm/ProductContactCenterTasksIcon'; +import {MoreIcon} from '@twilio-paste/icons/esm/MoreIcon'; + +import { + Sidebar, + SidebarHeader, + SidebarHeaderLabel, + SidebarHeaderIconButton, + SidebarCollapseButton, + SidebarCollapseButtonWrapper, + SidebarPushContentWrapper, + SidebarBetaBadge, + SidebarNavigation, + SidebarNavigationDisclosure, + SidebarNavigationDisclosureHeadingWrapper, + SidebarNavigationDisclosureHeading, + SidebarNavigationDisclosureContent, + SidebarNavigationItem, +} from '../src'; + +const MockPushSidebarWithNavigation = ({ + collapsed, + onClick, +}: { + collapsed: boolean; + onClick: () => void; +}): JSX.Element => { + const menu = useMenuState(); + + /* eslint-disable react/jsx-max-depth */ + return ( + + + + + + + Twilio Flex + + + } + > + This item closes the sidebar + + } + > + Go to Google.com + + + + + + Heading + + + Beta + + + + + + Settings + + + Has a link + + + Destructive link + + + Extensions + + + + Keyboard shortcuts + + + + + + + + + + Heading + + Beta + + + Navigation Item + Navigation Item + Navigation Item + + + Navigation Item + + Navigation Item + + Navigation Item + + + + + + + + + {/* Must wrap content area */} + + + + + ); + /* eslint-enable react/jsx-max-depth */ +}; + +describe('SidebarNavigation', () => { + it('renders collapsed SidebarNavigationItems correctly', () => { + const onClick: jest.Mock = jest.fn(() => {}); + + render(); + const wrapper = screen.getByTestId('nav-wrapper'); + const buttonIcon = screen.getByTestId('nav-item-button'); + const anchorIcon = screen.getByTestId('nav-item-anchor'); + const disclosure = screen.getByTestId('nav-item-disclosure'); + + /* + * Check that the sidebar has the correct number of children, including the disclosure + * We check this so that in the future we don't remove the hidden disclosure + * on collapsed, because if we do that then we need to manage the state of the + * disclosure in the collapsed state + */ + expect(wrapper.children).toHaveLength(3); + + // Button works + expect(buttonIcon).toBeInTheDocument(); + fireEvent.click(buttonIcon); + expect(onClick).toBeCalledTimes(1); + // N.B: 'getByText' fails when element is missing so we use 'queryByText' here + const buttonByText = screen.queryByText('This item closes the sidebar'); + expect(buttonByText).not.toBeInTheDocument(); + + // Anchor works + expect(anchorIcon).toHaveAttribute('href'); + + // Disclosure works + expect(disclosure).toBeInTheDocument(); + expect(disclosure).not.toBeVisible(); + }); + + it('renders expanded SidebarNavigationItems correctly', () => { + const onClick: jest.Mock = jest.fn(() => {}); + + render(); + const wrapper = screen.getByTestId('nav-wrapper'); + const disclosure = screen.getByTestId('nav-item-disclosure'); + const disclosureHeading = screen.getByTestId('nav-item-disclosure-heading'); + const disclosureContent = screen.getByTestId('nav-item-disclosure-content'); + + /* + * Check that the sidebar has the correct number of children, including the disclosure + * We check this so that in the future we don't remove the hidden disclosure + * on collapsed, because if we do that then we need to manage the state of the + * disclosure in the collapsed state + */ + expect(wrapper.children).toHaveLength(3); + + // SidebarNavigationItem text is rendered + const buttonByText = screen.getByText('This item closes the sidebar'); + expect(buttonByText).toBeInTheDocument(); + const anchorByText = screen.getByText('Go to Google.com'); + expect(anchorByText).toBeInTheDocument(); + + // Disclosure is visible + expect(disclosure).toBeInTheDocument(); + expect(disclosure).toHaveStyleRule('display', 'block'); + // Disclosure is closed + expect(disclosureHeading).toHaveAttribute('aria-expanded', 'false'); + expect(disclosureContent.getAttribute('id')).toEqual(disclosureHeading.getAttribute('aria-controls')); + expect(disclosureContent).not.toBeVisible(); + // Disclosure is open + fireEvent.click(disclosureHeading); + expect(disclosureHeading).toHaveAttribute('aria-expanded', 'true'); + expect(disclosureContent).toBeVisible(); + }); + + /** + * Customization + */ + describe('Customization', () => { + it('should work with default element values', () => { + render( + + + + + AnchorItem Selected + + + AnchorItem + + {}} selected> + ButtonItem Selected + + {}}>ButtonItem + + + Heading + + + {}}>Content Button Item + + + + + + ); + const nav = screen.getByTestId('nav-wrapper'); + const disclosure = screen.getByTestId('disclosure'); + const disclosureHeading = screen.getByText('Heading'); + const disclosureHeadingWrapper = disclosureHeading.parentElement; + const disclosureContent = screen.getByTestId('disclosure-content'); + const buttonItem = screen.getByText('ButtonItem').parentElement?.parentElement; + const anchorItem = screen.getByText('AnchorItem').parentElement?.parentElement; + + expect(nav).toHaveStyleRule('margin', '2.25rem'); + expect(disclosure).toHaveStyleRule('margin', '2.25rem'); + expect(disclosureHeadingWrapper).toHaveStyleRule('margin', '2.25rem'); + expect(disclosureHeading).toHaveStyleRule('margin', '2.25rem'); + expect(disclosureContent).toHaveStyleRule('margin', '2.25rem'); + expect(buttonItem).toHaveStyleRule('margin', '2.25rem'); + expect(anchorItem).toHaveStyleRule('margin', '2.25rem'); + }); + + it('should work with custom element values', () => { + render( + + + + + AnchorItem Selected + + + AnchorItem + + {}} selected> + ButtonItem Selected + + {}}> + ButtonItem + + + + + Heading + + + + {}}> + Content Button Item + + + + + + + ); + const nav = screen.getByTestId('nav-wrapper'); + const disclosure = screen.getByTestId('disclosure'); + const disclosureHeading = screen.getByText('Heading'); + const disclosureHeadingWrapper = disclosureHeading.parentElement; + const disclosureContent = screen.getByTestId('disclosure-content'); + const buttonItem = screen.getByText('ButtonItem').parentElement?.parentElement; + const anchorItem = screen.getByText('AnchorItem').parentElement?.parentElement; + + expect(nav).toHaveStyleRule('margin', '2.25rem'); + expect(disclosure).toHaveStyleRule('margin', '2.25rem'); + expect(disclosureHeadingWrapper).toHaveStyleRule('margin', '2.25rem'); + expect(disclosureHeading).toHaveStyleRule('margin', '2.25rem'); + expect(disclosureContent).toHaveStyleRule('margin', '2.25rem'); + expect(buttonItem).toHaveStyleRule('margin', '2.25rem'); + expect(anchorItem).toHaveStyleRule('margin', '2.25rem'); + }); + }); +}); diff --git a/packages/paste-core/components/sidebar/package.json b/packages/paste-core/components/sidebar/package.json index 60e3bc70be..c448d2fb40 100644 --- a/packages/paste-core/components/sidebar/package.json +++ b/packages/paste-core/components/sidebar/package.json @@ -32,7 +32,9 @@ "@twilio-paste/color-contrast-utils": "^4.0.0", "@twilio-paste/customization": "^7.0.0", "@twilio-paste/design-tokens": "^9.0.0", + "@twilio-paste/disclosure-primitive": "^1.0.0", "@twilio-paste/icons": "^11.0.0", + "@twilio-paste/reakit-library": "^1.0.0", "@twilio-paste/screen-reader-only": "^12.0.0", "@twilio-paste/spinner": "^13.0.0", "@twilio-paste/stack": "^7.0.0", @@ -40,6 +42,7 @@ "@twilio-paste/styling-library": "^2.0.0", "@twilio-paste/text": "^9.0.0", "@twilio-paste/theme": "^10.0.0", + "@twilio-paste/truncate": "^13.0.0", "@twilio-paste/types": "^5.0.0", "@twilio-paste/uid-library": "^1.0.0", "@twilio-paste/utils": "^4.0.0", @@ -58,7 +61,9 @@ "@twilio-paste/color-contrast-utils": "^4.0.0", "@twilio-paste/customization": "^7.0.0", "@twilio-paste/design-tokens": "^9.2.0", + "@twilio-paste/disclosure-primitive": "^1.0.0", "@twilio-paste/icons": "^11.1.0", + "@twilio-paste/reakit-library": "^1.0.0", "@twilio-paste/screen-reader-only": "^12.0.0", "@twilio-paste/spinner": "^13.0.0", "@twilio-paste/stack": "^7.0.0", @@ -66,6 +71,7 @@ "@twilio-paste/styling-library": "^2.0.2", "@twilio-paste/text": "^9.0.1", "@twilio-paste/theme": "^10.0.2", + "@twilio-paste/truncate": "^13.0.0", "@twilio-paste/types": "^5.0.0", "@twilio-paste/uid-library": "^1.0.0", "@twilio-paste/utils": "^4.0.0", diff --git a/packages/paste-core/components/sidebar/src/Sidebar.tsx b/packages/paste-core/components/sidebar/src/Sidebar.tsx index a4386a5d1a..62f3e83dcd 100644 --- a/packages/paste-core/components/sidebar/src/Sidebar.tsx +++ b/packages/paste-core/components/sidebar/src/Sidebar.tsx @@ -13,7 +13,9 @@ const StyledSidebar = React.forwardRef((props, ref) => { children: React.ReactNode; diff --git a/packages/paste-core/components/sidebar/src/SidebarPushContentWrapper.tsx b/packages/paste-core/components/sidebar/src/content-wrappers/SidebarPushContentWrapper.tsx similarity index 97% rename from packages/paste-core/components/sidebar/src/SidebarPushContentWrapper.tsx rename to packages/paste-core/components/sidebar/src/content-wrappers/SidebarPushContentWrapper.tsx index d926db1783..84df715250 100644 --- a/packages/paste-core/components/sidebar/src/SidebarPushContentWrapper.tsx +++ b/packages/paste-core/components/sidebar/src/content-wrappers/SidebarPushContentWrapper.tsx @@ -5,7 +5,7 @@ import {useSpring, animated} from '@twilio-paste/animation-library'; import {useTheme} from '@twilio-paste/theme'; import {useWindowSize} from '@twilio-paste/utils'; -import type {Variants} from './types'; +import type {Variants} from '../types'; const StyledContentWrapper = React.forwardRef((props, ref) => ( diff --git a/packages/paste-core/components/sidebar/src/SidebarCollapseButton.tsx b/packages/paste-core/components/sidebar/src/footer/SidebarCollapseButton.tsx similarity index 97% rename from packages/paste-core/components/sidebar/src/SidebarCollapseButton.tsx rename to packages/paste-core/components/sidebar/src/footer/SidebarCollapseButton.tsx index 13f8bbe494..1f8aac54eb 100644 --- a/packages/paste-core/components/sidebar/src/SidebarCollapseButton.tsx +++ b/packages/paste-core/components/sidebar/src/footer/SidebarCollapseButton.tsx @@ -7,7 +7,7 @@ import {ScreenReaderOnly} from '@twilio-paste/screen-reader-only'; import {ChevronDoubleLeftIcon} from '@twilio-paste/icons/esm/ChevronDoubleLeftIcon'; import {ChevronDoubleRightIcon} from '@twilio-paste/icons/esm/ChevronDoubleRightIcon'; -import {SidebarContext} from './SidebarContext'; +import {SidebarContext} from '../SidebarContext'; export interface SidebarCollapseButtonProps extends React.HTMLAttributes { i18nCollapseLabel: string; diff --git a/packages/paste-core/components/sidebar/src/SidebarCollapseButtonWrapper.tsx b/packages/paste-core/components/sidebar/src/footer/SidebarCollapseButtonWrapper.tsx similarity index 66% rename from packages/paste-core/components/sidebar/src/SidebarCollapseButtonWrapper.tsx rename to packages/paste-core/components/sidebar/src/footer/SidebarCollapseButtonWrapper.tsx index 0cb89d3326..ddac601620 100644 --- a/packages/paste-core/components/sidebar/src/SidebarCollapseButtonWrapper.tsx +++ b/packages/paste-core/components/sidebar/src/footer/SidebarCollapseButtonWrapper.tsx @@ -2,7 +2,7 @@ import * as React from 'react'; import {Box, safelySpreadBoxProps} from '@twilio-paste/box'; import type {BoxProps} from '@twilio-paste/box'; -import {SidebarContext} from './SidebarContext'; +import {SidebarContext} from '../SidebarContext'; export const SidebarCollapseButtonWrapper = React.forwardRef( ({element = 'SIDEBAR_COLLAPSE_BUTTON_WRAPPER', ...props}, ref) => { @@ -10,10 +10,14 @@ export const SidebarCollapseButtonWrapper = React.forwardRef diff --git a/packages/paste-core/components/sidebar/src/SidebarHeader.tsx b/packages/paste-core/components/sidebar/src/header/SidebarHeader.tsx similarity index 98% rename from packages/paste-core/components/sidebar/src/SidebarHeader.tsx rename to packages/paste-core/components/sidebar/src/header/SidebarHeader.tsx index b2df2bc720..0cbb8cdc19 100644 --- a/packages/paste-core/components/sidebar/src/SidebarHeader.tsx +++ b/packages/paste-core/components/sidebar/src/header/SidebarHeader.tsx @@ -24,6 +24,7 @@ export const SidebarHeader = React.forwardRef diff --git a/packages/paste-core/components/sidebar/src/SidebarHeaderIconButton.tsx b/packages/paste-core/components/sidebar/src/header/SidebarHeaderIconButton.tsx similarity index 74% rename from packages/paste-core/components/sidebar/src/SidebarHeaderIconButton.tsx rename to packages/paste-core/components/sidebar/src/header/SidebarHeaderIconButton.tsx index f9c16c224d..6b542d8183 100644 --- a/packages/paste-core/components/sidebar/src/SidebarHeaderIconButton.tsx +++ b/packages/paste-core/components/sidebar/src/header/SidebarHeaderIconButton.tsx @@ -2,13 +2,9 @@ import * as React from 'react'; import PropTypes from 'prop-types'; import {Button} from '@twilio-paste/button'; import type {ButtonProps} from '@twilio-paste/button'; -import type {BoxProps} from '@twilio-paste/box'; -export interface SidebarHeaderIconButtonProps extends React.HTMLAttributes { +export interface SidebarHeaderIconButtonProps extends Omit { children: NonNullable; - tabIndex?: ButtonProps['tabIndex']; - onClick?: () => void; - element?: BoxProps['element']; } export const SidebarHeaderIconButton = React.forwardRef( diff --git a/packages/paste-core/components/sidebar/src/SidebarHeaderLabel.tsx b/packages/paste-core/components/sidebar/src/header/SidebarHeaderLabel.tsx similarity index 95% rename from packages/paste-core/components/sidebar/src/SidebarHeaderLabel.tsx rename to packages/paste-core/components/sidebar/src/header/SidebarHeaderLabel.tsx index 0998818648..a7d8d37d44 100644 --- a/packages/paste-core/components/sidebar/src/SidebarHeaderLabel.tsx +++ b/packages/paste-core/components/sidebar/src/header/SidebarHeaderLabel.tsx @@ -3,7 +3,7 @@ import PropTypes from 'prop-types'; import {Box, safelySpreadBoxProps} from '@twilio-paste/box'; import type {BoxProps} from '@twilio-paste/box'; -import {SidebarContext} from './SidebarContext'; +import {SidebarContext} from '../SidebarContext'; export interface SidebarHeaderLabelProps extends React.HTMLAttributes { children: React.ReactNode; diff --git a/packages/paste-core/components/sidebar/src/index.tsx b/packages/paste-core/components/sidebar/src/index.tsx index 43eb7f368e..9309081122 100644 --- a/packages/paste-core/components/sidebar/src/index.tsx +++ b/packages/paste-core/components/sidebar/src/index.tsx @@ -1,10 +1,21 @@ +/* Wrapper */ export * from './Sidebar'; -export * from './SidebarPushContentWrapper'; -export * from './SidebarOverlayContentWrapper'; -export * from './SidebarCollapseButton'; -export * from './SidebarCollapseButtonWrapper'; export * from './SidebarContext'; -export * from './SidebarHeader'; -export * from './SidebarHeaderLabel'; -export * from './SidebarHeaderIconButton'; +export * from './content-wrappers/SidebarPushContentWrapper'; +export * from './content-wrappers/SidebarOverlayContentWrapper'; +/* Header */ +export * from './header/SidebarHeader'; +export * from './header/SidebarHeaderLabel'; +export * from './header/SidebarHeaderIconButton'; +/* Footer */ +export * from './footer/SidebarCollapseButtonWrapper'; +export * from './footer/SidebarCollapseButton'; +/* Navigation */ +export * from './navigation/SidebarNavigation'; +export * from './navigation/SidebarNavigationDisclosure'; +export * from './navigation/SidebarNavigationDisclosureHeadingWrapper'; +export * from './navigation/SidebarNavigationDisclosureHeading'; +export * from './navigation/SidebarNavigationDisclosureContent'; +export * from './navigation/SidebarNavigationItem'; +/* Misc */ export * from './SidebarBetaBadge'; diff --git a/packages/paste-core/components/sidebar/src/navigation/SidebarNavigation.tsx b/packages/paste-core/components/sidebar/src/navigation/SidebarNavigation.tsx new file mode 100644 index 0000000000..4f098f3093 --- /dev/null +++ b/packages/paste-core/components/sidebar/src/navigation/SidebarNavigation.tsx @@ -0,0 +1,84 @@ +import * as React from 'react'; +import {Box, safelySpreadBoxProps} from '@twilio-paste/box'; +import type {BoxProps} from '@twilio-paste/box'; +import {styled, css} from '@twilio-paste/styling-library'; +import type {ThemeShape} from '@twilio-paste/theme'; + +/** + * This wrapper applies styles that customize the scrollbar and its track, + * as well as using a gradient to fade out the top and bottom of the sidebar + * only when they are scrolled out of view + */ +const SidebarNavigationWrapper = styled.div(({theme}: {theme: ThemeShape}) => { + const {colorBackgroundInverse, colorBackgroundInverseStronger} = theme.backgroundColors; + + return css({ + overflowScrolling: 'touch', + // CSS magic to apply shadow on top and bottom of container when they scroll + background: `linear-gradient( + ${colorBackgroundInverse} 30%, + rgba(255, 255, 255, 0) + ) center top, + linear-gradient( + rgba(255, 255, 255, 0), + ${colorBackgroundInverse} 70% + ) center bottom, + radial-gradient( + farthest-side at 50% 0, + rgba(0, 0, 0, 0.5), + rgba(0, 0, 0, 0) + ) center top, + radial-gradient( + farthest-side at 50% 100%, + rgba(0, 0, 0, 0.5), + rgba(0, 0, 0, 0) + ) center bottom`, + backgroundRepeat: 'no-repeat', + backgroundSize: '100% 40px, 100% 40px, 100% 14px, 100% 14px', + backgroundAttachment: `local, local, scroll, scroll`, + // Scrollbar + '::-webkit-scrollbar': { + width: '10px', + }, + // Track + '::-webkit-scrollbar-track': { + background: colorBackgroundInverse, + }, + // Handle + '::-webkit-scrollbar-thumb': { + background: colorBackgroundInverseStronger, + borderRadius: '10px', + }, + // Handle on hover + '::-webkit-scrollbar-thumb:hover': { + background: colorBackgroundInverseStronger, + }, + }); +}); + +export interface SidebarNavigationProps extends React.HTMLAttributes { + children: React.ReactNode; + element?: BoxProps['element']; +} + +export const SidebarNavigation = React.forwardRef( + ({element = 'SIDEBAR_NAVIGATION', children, ...props}, ref) => { + return ( + + {children} + + ); + } +); +SidebarNavigation.displayName = 'SidebarNavigation'; diff --git a/packages/paste-core/components/sidebar/src/navigation/SidebarNavigationDisclosure.tsx b/packages/paste-core/components/sidebar/src/navigation/SidebarNavigationDisclosure.tsx new file mode 100644 index 0000000000..dbd7450c1e --- /dev/null +++ b/packages/paste-core/components/sidebar/src/navigation/SidebarNavigationDisclosure.tsx @@ -0,0 +1,55 @@ +import * as React from 'react'; +import PropTypes from 'prop-types'; +import {useDisclosurePrimitiveState} from '@twilio-paste/disclosure-primitive'; +import {Box, safelySpreadBoxProps} from '@twilio-paste/box'; +import type {BoxProps} from '@twilio-paste/box'; +import type {DisclosurePrimitiveInitialState} from '@twilio-paste/disclosure-primitive'; + +import {SidebarContext} from '../SidebarContext'; +import {SidebarNavigationDisclosureContext} from './SidebarNavigationDisclosureContext'; +import type {SidebarNavigationDisclosureInitialState, SidebarNavigationDisclosureStateReturn} from './types'; + +export interface DisclosureProps extends DisclosurePrimitiveInitialState { + children: NonNullable; + state?: SidebarNavigationDisclosureStateReturn; + element?: BoxProps['element']; +} + +const SidebarNavigationDisclosure = React.forwardRef( + ({children, element = 'SIDEBAR_NAVIGATION_DISCLOSURE', state, ...props}, ref) => { + const {collapsed} = React.useContext(SidebarContext); + // We check context to see if this disclosure is nested + const {disclosure: parentDisclosure} = React.useContext(SidebarNavigationDisclosureContext); + // Set the disclosure state to provide into this component's context + const disclosure = state || useDisclosurePrimitiveState({animated: false, ...props}); + + const disclosureContext = { + disclosure, + nested: parentDisclosure != null, + }; + + return ( + + + {children} + + + ); + } +); +SidebarNavigationDisclosure.displayName = 'SidebarNavigationDisclosure'; +SidebarNavigationDisclosure.propTypes = { + children: PropTypes.node.isRequired, + element: PropTypes.string, +}; + +export {SidebarNavigationDisclosure}; + +export {useDisclosurePrimitiveState as useSidebarNavigationDisclosureState}; +export type {SidebarNavigationDisclosureInitialState, SidebarNavigationDisclosureStateReturn}; diff --git a/packages/paste-core/components/sidebar/src/navigation/SidebarNavigationDisclosureContent.tsx b/packages/paste-core/components/sidebar/src/navigation/SidebarNavigationDisclosureContent.tsx new file mode 100644 index 0000000000..5298b24404 --- /dev/null +++ b/packages/paste-core/components/sidebar/src/navigation/SidebarNavigationDisclosureContent.tsx @@ -0,0 +1,43 @@ +import * as React from 'react'; +import PropTypes from 'prop-types'; +import {Box, safelySpreadBoxProps} from '@twilio-paste/box'; +import {DisclosurePrimitiveContent} from '@twilio-paste/disclosure-primitive'; +import type {BoxProps, BoxStyleProps} from '@twilio-paste/box'; +import type {DisclosurePrimitiveContentProps} from '@twilio-paste/disclosure-primitive'; + +import {SidebarNavigationDisclosureContext} from './SidebarNavigationDisclosureContext'; + +export interface SidebarNavigationDisclosureContentProps + extends Omit { + children: NonNullable; + element?: BoxProps['element']; +} + +const SidebarNavigationDisclosureContent = React.forwardRef( + ({children, element = 'SIDEBAR_NAVIGATION_DISCLOSURE_CONTENT', ...props}, ref) => { + const {disclosure} = React.useContext(SidebarNavigationDisclosureContext); + + return ( + + {children} + + ); + } +); + +SidebarNavigationDisclosureContent.displayName = 'SidebarNavigationDisclosureContent'; +SidebarNavigationDisclosureContent.propTypes = { + children: PropTypes.node.isRequired, + element: PropTypes.string, +}; + +export {SidebarNavigationDisclosureContent}; diff --git a/packages/paste-core/components/sidebar/src/navigation/SidebarNavigationDisclosureContext.tsx b/packages/paste-core/components/sidebar/src/navigation/SidebarNavigationDisclosureContext.tsx new file mode 100644 index 0000000000..bfea8a4b5d --- /dev/null +++ b/packages/paste-core/components/sidebar/src/navigation/SidebarNavigationDisclosureContext.tsx @@ -0,0 +1,11 @@ +import * as React from 'react'; +import type {DisclosurePrimitveStateReturn} from '@twilio-paste/disclosure-primitive'; + +export interface SidebarNavigationDisclosureContextProps { + disclosure: DisclosurePrimitveStateReturn; + nested: boolean; +} + +const SidebarNavigationDisclosureContext = React.createContext({} as any); + +export {SidebarNavigationDisclosureContext}; diff --git a/packages/paste-core/components/sidebar/src/navigation/SidebarNavigationDisclosureHeading.tsx b/packages/paste-core/components/sidebar/src/navigation/SidebarNavigationDisclosureHeading.tsx new file mode 100644 index 0000000000..80000e98cd --- /dev/null +++ b/packages/paste-core/components/sidebar/src/navigation/SidebarNavigationDisclosureHeading.tsx @@ -0,0 +1,73 @@ +import * as React from 'react'; +import PropTypes from 'prop-types'; +import {Box, safelySpreadBoxProps} from '@twilio-paste/box'; +import {ChevronDisclosureIcon} from '@twilio-paste/icons/esm/ChevronDisclosureIcon'; +import {DisclosurePrimitive} from '@twilio-paste/disclosure-primitive'; +import type {BoxProps} from '@twilio-paste/box'; +import {useTheme} from '@twilio-paste/theme'; + +import {SidebarNavigationDisclosureContext} from './SidebarNavigationDisclosureContext'; +import { + sidebarNavigationLabelStyles, + sidebarNavigationLabelNestedStyles, + sidebarNavigationLabelSelectedStyles, +} from './styles'; + +export interface SidebarNavigationDisclosureHeadingProps extends React.ComponentPropsWithRef<'div'> { + children: NonNullable; + element?: BoxProps['element']; + selected?: boolean; +} + +const StyledDisclosureHeading = React.forwardRef( + ({children, element = 'SIDEBAR_NAVIGATION_DISCLOSURE_HEADING', selected, ...props}, ref) => { + const [shouldIconMove, setShouldIconMove] = React.useState(false); + const {nested} = React.useContext(SidebarNavigationDisclosureContext); + const isExpanded = props['aria-expanded']; + const theme = useTheme(); + + return ( + setShouldIconMove(true)} + onMouseLeave={() => setShouldIconMove(false)} + {...(nested ? sidebarNavigationLabelNestedStyles : sidebarNavigationLabelStyles)} + {...(selected && sidebarNavigationLabelSelectedStyles)} + > + + + + {children} + + ); + } +); +StyledDisclosureHeading.displayName = 'StyledDisclosureHeading'; + +const SidebarNavigationDisclosureHeading = React.forwardRef( + (props, ref) => { + const {disclosure} = React.useContext(SidebarNavigationDisclosureContext); + return ; + } +); +SidebarNavigationDisclosureHeading.displayName = 'SidebarNavigationDisclosureHeading'; +SidebarNavigationDisclosureHeading.propTypes = { + children: PropTypes.node, + element: PropTypes.string, + selected: PropTypes.bool, +}; + +export {SidebarNavigationDisclosureHeading}; diff --git a/packages/paste-core/components/sidebar/src/navigation/SidebarNavigationDisclosureHeadingWrapper.tsx b/packages/paste-core/components/sidebar/src/navigation/SidebarNavigationDisclosureHeadingWrapper.tsx new file mode 100644 index 0000000000..18c633f9a9 --- /dev/null +++ b/packages/paste-core/components/sidebar/src/navigation/SidebarNavigationDisclosureHeadingWrapper.tsx @@ -0,0 +1,27 @@ +import * as React from 'react'; +import {Box, safelySpreadBoxProps} from '@twilio-paste/box'; +import type {BoxProps} from '@twilio-paste/box'; + +export interface SidebarNavigationDisclosureHeadingWrapperProps extends React.ComponentPropsWithRef<'div'> { + children: React.ReactNode; + element?: BoxProps['element']; +} + +export const SidebarNavigationDisclosureHeadingWrapper = React.forwardRef< + HTMLDivElement, + SidebarNavigationDisclosureHeadingWrapperProps +>(({element = 'SIDEBAR_NAVIGATION_DISCLOSURE_HEADING_WRAPPER', ...props}, ref) => { + return ( + + ); +}); + +SidebarNavigationDisclosureHeadingWrapper.displayName = 'SidebarNavigationDisclosureHeadingWrapper'; diff --git a/packages/paste-core/components/sidebar/src/navigation/SidebarNavigationItem.tsx b/packages/paste-core/components/sidebar/src/navigation/SidebarNavigationItem.tsx new file mode 100644 index 0000000000..9c0c51bb46 --- /dev/null +++ b/packages/paste-core/components/sidebar/src/navigation/SidebarNavigationItem.tsx @@ -0,0 +1,74 @@ +import * as React from 'react'; +import PropTypes from 'prop-types'; +import {Box, safelySpreadBoxProps} from '@twilio-paste/box'; +import type {BoxProps} from '@twilio-paste/box'; +import {useTheme} from '@twilio-paste/theme'; +import {Button} from '@twilio-paste/button'; +import {Truncate} from '@twilio-paste/truncate'; +import type {ButtonProps} from '@twilio-paste/button'; + +import {SidebarContext} from '../SidebarContext'; +import {SidebarNavigationDisclosureContext} from './SidebarNavigationDisclosureContext'; +import { + sidebarNavigationItemStyles, + sidebarNavigationItemNestedStyles, + sidebarNavigationItemSelectedStyles, + sidebarNavigationItemCollapsedStyles, +} from './styles'; + +export interface SidebarNavigationItemProps extends React.HTMLAttributes { + as?: ButtonProps['as']; + href?: ButtonProps['href']; + onClick?: ButtonProps['onClick']; + i18nExternalLinkLabel?: ButtonProps['i18nExternalLinkLabel']; + children: string; + element?: BoxProps['element']; + selected?: boolean; + icon?: React.ReactNode; +} + +const SidebarNavigationItem = React.forwardRef( + ({element = 'SIDEBAR_NAVIGATION_ITEM', selected, children, icon, ...props}, ref) => { + const theme = useTheme(); + const {collapsed} = React.useContext(SidebarContext); + const {disclosure} = React.useContext(SidebarNavigationDisclosureContext); + // If there is any disclosure context, that indicates that this component is nested + const isNested = disclosure != null; + + const styles = React.useMemo( + () => ({ + ...(isNested ? sidebarNavigationItemNestedStyles(theme) : sidebarNavigationItemStyles), + ...(collapsed && sidebarNavigationItemCollapsedStyles), + ...(selected && sidebarNavigationItemSelectedStyles), + display: collapsed && !icon ? 'none' : 'flex', + }), + [theme, isNested, selected, collapsed, icon] + ); + + return ( + + ); + } +); +SidebarNavigationItem.displayName = 'SidebarNavigationItem'; +SidebarNavigationItem.propTypes = { + children: PropTypes.string.isRequired, + element: PropTypes.string, + selected: PropTypes.bool, +}; + +export {SidebarNavigationItem}; diff --git a/packages/paste-core/components/sidebar/src/navigation/styles.tsx b/packages/paste-core/components/sidebar/src/navigation/styles.tsx new file mode 100644 index 0000000000..dee7873ea6 --- /dev/null +++ b/packages/paste-core/components/sidebar/src/navigation/styles.tsx @@ -0,0 +1,88 @@ +import type {BoxProps} from '@twilio-paste/box'; +import type {ThemeShape} from '@twilio-paste/theme'; + +/* + * Disclosure Heading styles + */ +// Base disclosure heading styles +export const sidebarNavigationLabelStyles: BoxProps = { + fontStyle: 'normal', + fontWeight: 'fontWeightNormal', + fontSize: 'fontSize30', + lineHeight: 'lineHeight20', + color: 'colorTextInverseWeak', + transition: 'all 120ms ease', + borderRadius: 'borderRadius20', + cursor: 'pointer', + display: 'flex', + flexGrow: 1, + columnGap: 'space20', + outline: 'none', + paddingY: 'space30', + position: 'relative', + role: 'button', + zIndex: 'zIndex10', + _focus: { + boxShadow: 'shadowFocusInverseInset', + }, +}; + +// Nested disclosure heading styles +export const sidebarNavigationLabelNestedStyles: BoxProps = { + ...sidebarNavigationLabelStyles, + paddingY: 'space20', + fontSize: 'fontSize20', +}; + +// Selected disclosure heading styles +export const sidebarNavigationLabelSelectedStyles: BoxProps = { + fontWeight: 'fontWeightSemibold', + color: 'colorTextInverse', +}; + +/* + * Item styles + */ +// Base item styles +export const sidebarNavigationItemStyles: BoxProps = { + ...sidebarNavigationLabelStyles, + background: 'none', + border: 'none', + outline: 'none', + whiteSpace: 'nowrap', + textOverflow: 'ellipsis', + overflow: 'hidden', + paddingLeft: 'space30', + paddingRight: 'space30', + marginBottom: 'space30', + _hover: { + textDecoration: 'underline', + }, +}; + +// Nested item styles +export const sidebarNavigationItemNestedStyles: (theme: ThemeShape) => BoxProps = (theme) => ({ + ...sidebarNavigationItemStyles, + paddingY: 'space20', + fontSize: 'fontSize20', + flexGrow: 1, + marginLeft: 'spaceNegative60', + width: `calc(100% + ${theme.space.space60})`, + paddingLeft: 'space60', +}); + +// Collapsed item styles +export const sidebarNavigationItemCollapsedStyles: BoxProps = { + color: 'colorTextIconInverse', + _hover: { + background: 'none', + color: 'colorTextInverse', + }, +}; + +// Selected item styles +export const sidebarNavigationItemSelectedStyles: BoxProps = { + ...sidebarNavigationLabelSelectedStyles, + backgroundColor: 'colorBackgroundInverseStrong', + color: 'colorTextInverse', +}; diff --git a/packages/paste-core/components/sidebar/src/navigation/types.ts b/packages/paste-core/components/sidebar/src/navigation/types.ts new file mode 100644 index 0000000000..2ca85872cf --- /dev/null +++ b/packages/paste-core/components/sidebar/src/navigation/types.ts @@ -0,0 +1,6 @@ +import type {DisclosurePrimitiveInitialState, DisclosurePrimitveStateReturn} from '@twilio-paste/disclosure-primitive'; + +export type { + DisclosurePrimitiveInitialState as SidebarNavigationDisclosureInitialState, + DisclosurePrimitveStateReturn as SidebarNavigationDisclosureStateReturn, +}; diff --git a/packages/paste-core/components/sidebar/stories/customization.stories.tsx b/packages/paste-core/components/sidebar/stories/customization.stories.tsx index 3311753a6f..24b195a815 100644 --- a/packages/paste-core/components/sidebar/stories/customization.stories.tsx +++ b/packages/paste-core/components/sidebar/stories/customization.stories.tsx @@ -1,6 +1,5 @@ import * as React from 'react'; import {Button} from '@twilio-paste/button'; -import {Stack} from '@twilio-paste/stack'; import {Box} from '@twilio-paste/box'; import {useTheme} from '@twilio-paste/theme'; import {CustomizationProvider} from '@twilio-paste/customization'; @@ -19,6 +18,7 @@ import { SidebarPushContentWrapper, SidebarOverlayContentWrapper, SidebarBetaBadge, + SidebarNavigation, } from '../src'; // eslint-disable-next-line import/no-default-export @@ -48,11 +48,11 @@ export const WithDefaultElementName: StoryFn = (_args, {parameters: {isTestEnvir SIDEBAR_HEADER_LABEL: { fontWeight: 'fontWeightNormal', }, - SIDEBAR_COLLAPSE_BUTTON: { - padding: 'space40', - }, SIDEBAR_COLLAPSE_BUTTON_WRAPPER: { - padding: 'space40', + boxShadow: 'shadowBorder', + }, + SIDEBAR_COLLAPSE_BUTTON: { + padding: 'space60', }, SIDEBAR_PUSH_CONTENT_WRAPPER: { backgroundColor: 'colorBackground', @@ -61,27 +61,30 @@ export const WithDefaultElementName: StoryFn = (_args, {parameters: {isTestEnvir SIDEBAR_BETA_BADGE: { padding: 'space40', }, + SIDEBAR_NAVIGATION: { + background: 'none', + }, }} > {/* Can be placed anywhere - position fixed */} - - - - - - Twilio Flex - + + + + + Twilio Flex + + Beta - - setPushSidebarCollapsed(!pushSidebarCollapsed)} - i18nCollapseLabel="Close sidebar" - i18nExpandLabel="Open sidebar" - /> - - + + + setPushSidebarCollapsed(!pushSidebarCollapsed)} + i18nCollapseLabel="Close sidebar" + i18nExpandLabel="Open sidebar" + /> + {/* Must wrap content area */} @@ -120,11 +123,11 @@ export const WithCustomElementName: StoryFn = (_args, {parameters: {isTestEnviro SIDECAR_HEADER_LABEL: { padding: 'space40', }, - SIDECAR_COLLAPSE_BUTTON: { - padding: 'space40', - }, SIDECAR_COLLAPSE_BUTTON_WRAPPER: { - padding: 'space40', + boxShadow: 'shadowBorder', + }, + SIDECAR_COLLAPSE_BUTTON: { + padding: 'space60', }, SIDECAR_OVERLAY_WRAPPER: { backgroundColor: 'colorBackground', @@ -133,30 +136,33 @@ export const WithCustomElementName: StoryFn = (_args, {parameters: {isTestEnviro SIDECAR_BETA_BADGE: { padding: 'space40', }, + SIDECAR_NAVIGATION: { + background: 'none', + }, }} > {/* Can be placed anywhere - position fixed */} - - - - - - Twilio Flex - + + + + + Twilio Flex + + Beta - - setPushSidebarCollapsed(!pushSidebarCollapsed)} - i18nCollapseLabel="Close sidebar" - i18nExpandLabel="Open sidebar" - /> - - + + + setPushSidebarCollapsed(!pushSidebarCollapsed)} + i18nCollapseLabel="Close sidebar" + i18nExpandLabel="Open sidebar" + /> + {/* Must wrap content area */} diff --git a/packages/paste-core/components/sidebar/stories/navigation.stories.tsx b/packages/paste-core/components/sidebar/stories/navigation.stories.tsx new file mode 100644 index 0000000000..2fbe1576fc --- /dev/null +++ b/packages/paste-core/components/sidebar/stories/navigation.stories.tsx @@ -0,0 +1,369 @@ +import * as React from 'react'; +import {Button} from '@twilio-paste/button'; +import {Box} from '@twilio-paste/box'; +import type {StoryFn} from '@storybook/react'; +import {ProductFlexIcon} from '@twilio-paste/icons/esm/ProductFlexIcon'; +// ONLY for storybook stacked view not to complain on duplicates. aria-label should be carefully selected strings +import {useUID} from '@twilio-paste/uid-library'; +// import {Tabs, TabList, Tab, TabPanels, TabPanel} from '@twilio-paste/tabs'; +import {Menu, MenuButton, MenuItem, MenuSeparator, useMenuState} from '@twilio-paste/menu'; +import {ProductContactCenterTasksIcon} from '@twilio-paste/icons/esm/ProductContactCenterTasksIcon'; +import {MoreIcon} from '@twilio-paste/icons/esm/MoreIcon'; + +import { + Sidebar, + SidebarHeader, + SidebarHeaderLabel, + SidebarHeaderIconButton, + SidebarCollapseButton, + SidebarCollapseButtonWrapper, + SidebarPushContentWrapper, + SidebarBetaBadge, + SidebarNavigation, + SidebarNavigationDisclosure, + SidebarNavigationDisclosureHeadingWrapper, + SidebarNavigationDisclosureHeading, + SidebarNavigationDisclosureContent, + SidebarNavigationItem, + useSidebarNavigationDisclosureState, +} from '../src'; +import type {SidebarNavigationDisclosureInitialState} from '../src'; + +// eslint-disable-next-line import/no-default-export +export default { + title: 'Components/Sidebar/Navigation', +}; + +const onClick = (): void => {}; + +export const Default: StoryFn = () => { + const id = useUID(); + const menu = useMenuState(); + const [pushSidebarCollapsed, setPushSidebarCollapsed] = React.useState(false); + + /* eslint-disable react/jsx-max-depth */ + return ( + + + {/* Header */} + + + + + Twilio Flex + + {/* Nav */} + + { + setPushSidebarCollapsed(!pushSidebarCollapsed); + }} + icon={} + > + This item closes the sidebar + + } + > + Go to Google.com + + + + + + Heading + + + Beta + + + + + + Settings + + + Has a link + + + Destructive link + + + Extensions + + + + Keyboard shortcuts + + + + + + + + + + Heading + + Beta + + + Navigation Item + Navigation Item + Navigation Item + + + { + setPushSidebarCollapsed(!pushSidebarCollapsed); + }} + > + This item closes the sidebar + + + Go to Google.com + + Navigation Item + + + + Navigation Item + Navigation Item + Navigation Item + Navigation Item + Navigation Item + Navigation Item + Navigation Item + Navigation Item + Navigation Item + Navigation Item + Navigation Item + Navigation Item + Navigation Item + Navigation Item + + + setPushSidebarCollapsed(!pushSidebarCollapsed)} + i18nCollapseLabel="Close sidebar" + i18nExpandLabel="Open sidebar" + /> + + + + {/* Must wrap content area */} + + + + + ); + /* eslint-enable react/jsx-max-depth */ +}; +Default.parameters = { + padding: false, +}; + +export const Compact: StoryFn = () => { + const id = useUID(); + const menu = useMenuState(); + const [pushSidebarCollapsed, setPushSidebarCollapsed] = React.useState(true); + + return ( + /* eslint-disable react/jsx-max-depth */ + + + + + + + Twilio Flex + + + { + setPushSidebarCollapsed(!pushSidebarCollapsed); + }} + icon={} + > + This item closes the sidebar + + } + > + Go to Google.com + + + + + + Heading + + + Beta + + + + + + Settings + + + Has a link + + + Destructive link + + + Extensions + + + + Keyboard shortcuts + + + + + + + + + + Heading + + Beta + + + Navigation Item + Navigation Item + Navigation Item + + + { + setPushSidebarCollapsed(!pushSidebarCollapsed); + }} + > + Navigation Item + + + Navigation Item + + Navigation Item + + + + + setPushSidebarCollapsed(!pushSidebarCollapsed)} + i18nCollapseLabel="Close sidebar" + i18nExpandLabel="Open sidebar" + /> + + + + {/* Must wrap content area */} + + + + + ); + /* eslint-enable react/jsx-max-depth */ +}; +Compact.parameters = { + padding: false, +}; + +interface UseDelayedDisclosureStateProps extends SidebarNavigationDisclosureInitialState { + delay: number; +} +const useDelayedDisclosureState = ({delay, ...initialState}: UseDelayedDisclosureStateProps): DisclosureStateReturn => { + const disclosure = useSidebarNavigationDisclosureState(initialState); + const [transitioning, setTransitioning] = React.useState(false); + return { + ...disclosure, + transitioning, + toggle: () => { + setTransitioning(true); + setTimeout(() => { + disclosure.toggle(); + setTransitioning(false); + }, delay); + }, + }; +}; + +export const StateHookDisclosure: StoryFn = () => { + const id = useUID(); + const [pushSidebarCollapsed, setPushSidebarCollapsed] = React.useState(false); + + // Custom state hook to control state of disclosure + const {transitioning, ...disclosure} = useDelayedDisclosureState({ + delay: 1000, + }); + const clickableHeading = disclosure.visible ? 'Hide with delay' : 'Show with delay'; + + return ( + /* eslint-disable react/jsx-max-depth */ + + + + + + + Twilio Console + + + + + + + {transitioning ? 'Please wait...' : clickableHeading} + + + + { + setPushSidebarCollapsed(!pushSidebarCollapsed); + }} + > + Navigation Item + + + Go to google.com + + Navigation Item + + + + + setPushSidebarCollapsed(!pushSidebarCollapsed)} + i18nCollapseLabel="Close sidebar" + i18nExpandLabel="Open sidebar" + /> + + + + {/* Must wrap content area */} + + + + + ); + /* eslint-enable react/jsx-max-depth */ +}; +StateHookDisclosure.parameters = { + padding: false, +}; diff --git a/packages/paste-core/components/sidebar/stories/overlay.stories.tsx b/packages/paste-core/components/sidebar/stories/overlay.stories.tsx index 4e277121d6..da1e27b673 100644 --- a/packages/paste-core/components/sidebar/stories/overlay.stories.tsx +++ b/packages/paste-core/components/sidebar/stories/overlay.stories.tsx @@ -1,6 +1,5 @@ import * as React from 'react'; import {Button} from '@twilio-paste/button'; -import {Stack} from '@twilio-paste/stack'; import {Box} from '@twilio-paste/box'; import type {StoryFn} from '@storybook/react'; import {ProductFlexIcon} from '@twilio-paste/icons/esm/ProductFlexIcon'; @@ -16,6 +15,7 @@ import { SidebarCollapseButtonWrapper, SidebarOverlayContentWrapper, SidebarBetaBadge, + SidebarNavigation, } from '../src'; // eslint-disable-next-line import/no-default-export @@ -31,22 +31,22 @@ export const Default: StoryFn = () => { {/* Can be placed anywhere - position fixed */} - - - - - - Twilio Flex - + + + + + Twilio Flex + + Beta - - setOverlaySidebarExpanded(!overlaySidebarExpanded)} - i18nCollapseLabel="Close sidebar" - i18nExpandLabel="Open sidebar" - /> - - + + + setOverlaySidebarExpanded(!overlaySidebarExpanded)} + i18nCollapseLabel="Close sidebar" + i18nExpandLabel="Open sidebar" + /> + {/* Must wrap content area */} @@ -70,22 +70,22 @@ export const Compact: StoryFn = () => { {/* Can be placed anywhere - position fixed */} - - - - - - Twilio Flex - + + + + + Twilio Flex + + Beta - - setOverlaySidebarExpanded(!overlaySidebarExpanded)} - i18nCollapseLabel="Close sidebar" - i18nExpandLabel="Open sidebar" - /> - - + + + setOverlaySidebarExpanded(!overlaySidebarExpanded)} + i18nCollapseLabel="Close sidebar" + i18nExpandLabel="Open sidebar" + /> + {/* Must wrap content area */} diff --git a/packages/paste-core/components/sidebar/stories/push.stories.tsx b/packages/paste-core/components/sidebar/stories/push.stories.tsx index 737b21ab20..ee4e8f4ddd 100644 --- a/packages/paste-core/components/sidebar/stories/push.stories.tsx +++ b/packages/paste-core/components/sidebar/stories/push.stories.tsx @@ -1,6 +1,5 @@ import * as React from 'react'; import {Button} from '@twilio-paste/button'; -import {Stack} from '@twilio-paste/stack'; import {Box} from '@twilio-paste/box'; import type {StoryFn} from '@storybook/react'; import {ProductFlexIcon} from '@twilio-paste/icons/esm/ProductFlexIcon'; @@ -16,6 +15,7 @@ import { SidebarCollapseButtonWrapper, SidebarPushContentWrapper, SidebarBetaBadge, + SidebarNavigation, } from '../src'; // eslint-disable-next-line import/no-default-export @@ -31,22 +31,22 @@ export const Default: StoryFn = () => { {/* Can be placed anywhere - position fixed */} - - - - - - Twilio Flex - + + + + + Twilio Flex + + Beta - - setPushSidebarCollapsed(!pushSidebarCollapsed)} - i18nCollapseLabel="Close sidebar" - i18nExpandLabel="Open sidebar" - /> - - + + + setPushSidebarCollapsed(!pushSidebarCollapsed)} + i18nCollapseLabel="Close sidebar" + i18nExpandLabel="Open sidebar" + /> + {/* Must wrap content area */} @@ -70,22 +70,22 @@ export const Compact: StoryFn = () => { {/* Can be placed anywhere - position fixed */} - - - - - - Twilio Flex - + + + + + Twilio Flex + + Beta - - setPushSidebarCollapsed(!pushSidebarCollapsed)} - i18nCollapseLabel="Close sidebar" - i18nExpandLabel="Open sidebar" - /> - - + + + setPushSidebarCollapsed(!pushSidebarCollapsed)} + i18nCollapseLabel="Close sidebar" + i18nExpandLabel="Open sidebar" + /> + {/* Must wrap content area */} diff --git a/packages/paste-design-tokens/__tests__/__snapshots__/index.test.tsx.snap b/packages/paste-design-tokens/__tests__/__snapshots__/index.test.tsx.snap index 85940f6bc9..77212a7100 100644 --- a/packages/paste-design-tokens/__tests__/__snapshots__/index.test.tsx.snap +++ b/packages/paste-design-tokens/__tests__/__snapshots__/index.test.tsx.snap @@ -1386,7 +1386,7 @@ exports[`Design Tokens matches the Twilio Dark theme 1`] = ` \\"shadow-card\\": \\"0 2px 4px 0 rgba(0, 0, 0, 0.4)\\", \\"shadow-border-error-weaker\\": \\"0 0 0 1px #ad1111\\", \\"shadow-border-inverse-strongest\\": \\"0 0 0 1px #ffffff\\", - \\"shadow-focus-inverse-inset\\": \\"inset 0 0 0 2px rgba(255, 255, 255, 0.4)\\", + \\"shadow-focus-inverse-inset\\": \\"0 0 0 1px #ffffff, inset 0 0 0 3px rgba(255, 255, 255, 0.2)\\", \\"shadow-border-user\\": \\"0 0 0 1px #5817bd\\", \\"shadow-border-neutral-weaker\\": \\"0 0 0 1px #043cb5\\", \\"font-family-text\\": \\"'TwilioSansText', 'Inter var experimental', 'Inter var', -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen-Sans, Ubuntu, Cantarell, 'Helvetica Neue', sans-serif\\", @@ -1790,7 +1790,7 @@ exports[`Design Tokens matches the Twilio theme 1`] = ` \\"shadow-card\\": \\"0 2px 8px 0 rgba(18, 28, 45, 0.1)\\", \\"shadow-border-error-weaker\\": \\"0 0 0 1px #fccfcf\\", \\"shadow-border-inverse-strongest\\": \\"0 0 0 1px #ffffff\\", - \\"shadow-focus-inverse-inset\\": \\"inset 0 0 0 2px rgba(255, 255, 255, 0.4)\\", + \\"shadow-focus-inverse-inset\\": \\"0 0 0 1px #ffffff, inset 0 0 0 3px rgba(255, 255, 255, 0.2)\\", \\"shadow-border-user\\": \\"0 0 0 1px #e7dcfa\\", \\"shadow-border-neutral-weaker\\": \\"0 0 0 1px #cce4ff\\", \\"font-family-text\\": \\"'TwilioSansText', 'Inter var experimental', 'Inter var', -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen-Sans, Ubuntu, Cantarell, 'Helvetica Neue', sans-serif\\", diff --git a/packages/paste-design-tokens/tokens/themes/twilio-dark/global/box-shadow.yml b/packages/paste-design-tokens/tokens/themes/twilio-dark/global/box-shadow.yml index 02f6b97dc2..8eaf7fb929 100644 --- a/packages/paste-design-tokens/tokens/themes/twilio-dark/global/box-shadow.yml +++ b/packages/paste-design-tokens/tokens/themes/twilio-dark/global/box-shadow.yml @@ -19,6 +19,9 @@ props: shadow-focus-inset: value: "inset {!offset-0} {!offset-0} {!offset-0} 1px {!palette-gray-0}, inset {!offset-0} {!offset-0} {!offset-0} 3px {!palette-gray-0-transparent-20}" comment: Shadow for inset focus on elements, such as DataGrid cells. + shadow-focus-inverse-inset: + value: "{!offset-0} {!offset-0} {!offset-0} 1px {!palette-gray-0}, inset {!offset-0} {!offset-0} {!offset-0} 3px rgba(255, 255, 255, 0.2)" + comment: Shadow for inset focus ring on interactive elements on inverse backgrounds. shadow-border-primary-weaker: value: "{!offset-0} {!offset-0} {!offset-0} 1px {!palette-blue-70}" comment: "Weaker shadow border for primary interactions." diff --git a/packages/paste-design-tokens/tokens/themes/twilio/global/box-shadow.yml b/packages/paste-design-tokens/tokens/themes/twilio/global/box-shadow.yml index cffa45c928..432ccd2210 100644 --- a/packages/paste-design-tokens/tokens/themes/twilio/global/box-shadow.yml +++ b/packages/paste-design-tokens/tokens/themes/twilio/global/box-shadow.yml @@ -19,6 +19,9 @@ props: shadow-focus-inset: value: "inset {!offset-0} {!offset-0} {!offset-0} 1px {!palette-blue-55}, inset {!offset-0} {!offset-0} {!offset-0} 3px {!palette-blue-20}" comment: Shadow for inset focus on elements, such as DataGrid cells. + shadow-focus-inverse-inset: + value: "{!offset-0} {!offset-0} {!offset-0} 1px {!palette-gray-0}, inset {!offset-0} {!offset-0} {!offset-0} 3px rgba(255, 255, 255, 0.2)" + comment: Shadow for inset focus ring on interactive elements on inverse backgrounds. shadow-border-primary: value: "{!offset-0} {!offset-0} {!offset-0} 1px {!palette-blue-55}" comment: Default shadow border for primary interactions. diff --git a/packages/paste-icons/src/helpers/IconWrapper.tsx b/packages/paste-icons/src/helpers/IconWrapper.tsx index b16a772a21..9f21b03843 100644 --- a/packages/paste-icons/src/helpers/IconWrapper.tsx +++ b/packages/paste-icons/src/helpers/IconWrapper.tsx @@ -18,6 +18,7 @@ const IconWrapper = React.forwardRef( color={color} size={size} ref={ref} + flexShrink={0} /> ) ); diff --git a/packages/paste-website/src/pages/components/multiselect-combobox/index.mdx b/packages/paste-website/src/pages/components/multiselect-combobox/index.mdx index 250e587b27..2fc2cec4b6 100644 --- a/packages/paste-website/src/pages/components/multiselect-combobox/index.mdx +++ b/packages/paste-website/src/pages/components/multiselect-combobox/index.mdx @@ -46,7 +46,7 @@ export default DefaultLayout; export const getStaticProps = async () => { const navigationData = await getNavigationData(); - const feature = await getFeature('MultiselectCombobox'); + const feature = await getFeature('Multiselect Combobox'); return { props: { data: { diff --git a/yarn.lock b/yarn.lock index 342d03eb61..cd53542635 100644 --- a/yarn.lock +++ b/yarn.lock @@ -12852,7 +12852,9 @@ __metadata: "@twilio-paste/color-contrast-utils": ^4.0.0 "@twilio-paste/customization": ^7.0.0 "@twilio-paste/design-tokens": ^9.2.0 + "@twilio-paste/disclosure-primitive": ^1.0.0 "@twilio-paste/icons": ^11.1.0 + "@twilio-paste/reakit-library": ^1.0.0 "@twilio-paste/screen-reader-only": ^12.0.0 "@twilio-paste/spinner": ^13.0.0 "@twilio-paste/stack": ^7.0.0 @@ -12860,6 +12862,7 @@ __metadata: "@twilio-paste/styling-library": ^2.0.2 "@twilio-paste/text": ^9.0.1 "@twilio-paste/theme": ^10.0.2 + "@twilio-paste/truncate": ^13.0.0 "@twilio-paste/types": ^5.0.0 "@twilio-paste/uid-library": ^1.0.0 "@twilio-paste/utils": ^4.0.0 @@ -12877,7 +12880,9 @@ __metadata: "@twilio-paste/color-contrast-utils": ^4.0.0 "@twilio-paste/customization": ^7.0.0 "@twilio-paste/design-tokens": ^9.0.0 + "@twilio-paste/disclosure-primitive": ^1.0.0 "@twilio-paste/icons": ^11.0.0 + "@twilio-paste/reakit-library": ^1.0.0 "@twilio-paste/screen-reader-only": ^12.0.0 "@twilio-paste/spinner": ^13.0.0 "@twilio-paste/stack": ^7.0.0 @@ -12885,6 +12890,7 @@ __metadata: "@twilio-paste/styling-library": ^2.0.0 "@twilio-paste/text": ^9.0.0 "@twilio-paste/theme": ^10.0.0 + "@twilio-paste/truncate": ^13.0.0 "@twilio-paste/types": ^5.0.0 "@twilio-paste/uid-library": ^1.0.0 "@twilio-paste/utils": ^4.0.0