diff --git a/packages/chrome/.size-snapshot.json b/packages/chrome/.size-snapshot.json index 5ab60122bb0..0748df6baee 100644 --- a/packages/chrome/.size-snapshot.json +++ b/packages/chrome/.size-snapshot.json @@ -1,20 +1,20 @@ { "index.cjs.js": { - "bundled": 70881, - "minified": 53763, - "gzipped": 10336 + "bundled": 72044, + "minified": 54378, + "gzipped": 10482 }, "index.esm.js": { - "bundled": 65238, - "minified": 48853, - "gzipped": 10022, + "bundled": 66389, + "minified": 49456, + "gzipped": 10163, "treeshaked": { "rollup": { - "code": 38404, + "code": 38823, "import_statements": 676 }, "webpack": { - "code": 43076 + "code": 43530 } } } diff --git a/packages/chrome/demo/sheet.stories.mdx b/packages/chrome/demo/sheet.stories.mdx index b88c9676981..59ada3a86cf 100644 --- a/packages/chrome/demo/sheet.stories.mdx +++ b/packages/chrome/demo/sheet.stories.mdx @@ -40,6 +40,8 @@ import { hasClose: true, hasFooter: true, hasHeader: true, + hasTitle: true, + hasDescription: true, title: TITLE, description: DESCRIPTION, footerItems: FOOTER_ITEMS @@ -52,7 +54,9 @@ import { hasHeader: { name: 'Sheet.Header', table: { category: 'Story' } }, footerItems: { name: 'Sheet.FooterItem[]', table: { category: 'Story' } }, body: { name: 'children', table: { category: 'Sheet.Body' } }, + hasTitle: { name: 'Sheet.Title', table: { category: 'Story' } }, title: { name: 'children', table: { category: 'Sheet.Title' } }, + hasDescription: { name: 'Sheet.Description', table: { category: 'Story' } }, description: { name: 'children', table: { category: 'Sheet.Description' } }, isCompact: { control: 'boolean', table: { category: 'Sheet.Footer' } } }} diff --git a/packages/chrome/demo/stories/SheetStory.tsx b/packages/chrome/demo/stories/SheetStory.tsx index 446325de9d7..00ba11bfefa 100644 --- a/packages/chrome/demo/stories/SheetStory.tsx +++ b/packages/chrome/demo/stories/SheetStory.tsx @@ -23,14 +23,18 @@ interface ISheetComponentProps extends ISheetProps { footerItems: IFooterItem[]; hasHeader: boolean; isCompact: boolean; + hasTitle?: boolean; title: string; + hasDescription?: boolean; description: string; } export const SheetComponent = ({ hasHeader, title, + hasTitle = !!title, description, + hasDescription = !!description, hasBody, body, hasFooter, @@ -43,8 +47,8 @@ export const SheetComponent = ({ {hasHeader && ( - {title} - {description} + {hasTitle && {title}} + {hasDescription && {description}} )} {hasBody ? {body} : body} diff --git a/packages/chrome/src/elements/sheet/Sheet.tsx b/packages/chrome/src/elements/sheet/Sheet.tsx index bae561fcc2d..6270d81bd38 100644 --- a/packages/chrome/src/elements/sheet/Sheet.tsx +++ b/packages/chrome/src/elements/sheet/Sheet.tsx @@ -67,11 +67,20 @@ export const Sheet = React.forwardRef( const sheetRef = useRef(null); const seed = useUIDSeed(); + const [isCloseButtonPresent, setCloseButtonPresent] = useState(false); const [idPrefix] = useState(id || seed(`sheet_${PACKAGE_VERSION}`)); const titleId = `${idPrefix}--title`; const descriptionId = `${idPrefix}--description`; - const sheetContext = useMemo(() => ({ titleId, descriptionId }), [titleId, descriptionId]); + const sheetContext = useMemo( + () => ({ + titleId, + descriptionId, + isCloseButtonPresent, + setCloseButtonPresent + }), + [titleId, descriptionId, isCloseButtonPresent] + ); useFocusableMount({ targetRef: sheetRef, isMounted: isOpen, focusOnMount, restoreFocus }); diff --git a/packages/chrome/src/elements/sheet/components/Close.spec.tsx b/packages/chrome/src/elements/sheet/components/Close.spec.tsx index aaba7c933d5..ae518f58fb9 100644 --- a/packages/chrome/src/elements/sheet/components/Close.spec.tsx +++ b/packages/chrome/src/elements/sheet/components/Close.spec.tsx @@ -10,7 +10,23 @@ import { render, screen } from 'garden-test-utils'; import { SheetClose as Close } from './Close'; +import { useSheetContext } from '../../../utils/useSheetContext'; + +jest.mock('../../../utils/useSheetContext', () => { + const setCloseButtonPresent = jest.fn(); + + return { + useSheetContext: () => ({ + setCloseButtonPresent + }) + }; +}); + describe('Sheet.Close', () => { + beforeEach(() => { + jest.clearAllMocks(); + }); + it('passes ref to underlying DOM element', () => { const ref = React.createRef(); @@ -20,4 +36,18 @@ describe('Sheet.Close', () => { expect(btn).toBe(ref.current); }); + + describe('functionality', () => { + it('calls setCloseButtonPresent when mounting and unmounting', () => { + const { unmount } = render(); + const { setCloseButtonPresent } = useSheetContext(); + + expect(setCloseButtonPresent).toHaveBeenCalledWith(true); + + unmount(); + + expect(setCloseButtonPresent).toHaveBeenCalledWith(false); + expect(setCloseButtonPresent).toHaveBeenCalledTimes(2); + }); + }); }); diff --git a/packages/chrome/src/elements/sheet/components/Close.tsx b/packages/chrome/src/elements/sheet/components/Close.tsx index 3934f3d83ab..613a7e49a6b 100644 --- a/packages/chrome/src/elements/sheet/components/Close.tsx +++ b/packages/chrome/src/elements/sheet/components/Close.tsx @@ -5,13 +5,22 @@ * found at http://www.apache.org/licenses/LICENSE-2.0. */ -import React, { forwardRef, HTMLAttributes } from 'react'; +import React, { forwardRef, HTMLAttributes, useEffect } from 'react'; import XStrokeIcon from '@zendeskgarden/svg-icons/src/16/x-stroke.svg'; import { StyledSheetClose } from '../../../styled'; +import { useSheetContext } from '../../../utils/useSheetContext'; export const SheetClose = forwardRef>( (props, ref) => { + const { setCloseButtonPresent } = useSheetContext(); + + useEffect(() => { + setCloseButtonPresent(true); + + return () => setCloseButtonPresent(false); + }, [setCloseButtonPresent]); + return ( diff --git a/packages/chrome/src/elements/sheet/components/Header.tsx b/packages/chrome/src/elements/sheet/components/Header.tsx index e0bd1ec75b6..6687dcfc045 100644 --- a/packages/chrome/src/elements/sheet/components/Header.tsx +++ b/packages/chrome/src/elements/sheet/components/Header.tsx @@ -6,10 +6,14 @@ */ import React, { forwardRef, HTMLAttributes } from 'react'; + import { StyledSheetHeader } from '../../../styled'; +import { useSheetContext } from '../../../utils/useSheetContext'; export const SheetHeader = forwardRef>((props, ref) => { - return ; + const { isCloseButtonPresent } = useSheetContext(); + + return ; }); SheetHeader.displayName = 'Sheet.Header'; diff --git a/packages/chrome/src/styled/sheet/StyledSheetClose.ts b/packages/chrome/src/styled/sheet/StyledSheetClose.ts index 681ab10ca32..83664f22845 100644 --- a/packages/chrome/src/styled/sheet/StyledSheetClose.ts +++ b/packages/chrome/src/styled/sheet/StyledSheetClose.ts @@ -10,6 +10,12 @@ import { getColor, retrieveComponentStyles, DEFAULT_THEME } from '@zendeskgarden const COMPONENT_ID = 'chrome.sheet_close'; +export const baseMultipliers = { + top: 2.5, + side: 2, + size: 10 +}; + const colorStyles = (props: ThemeProps) => { const backgroundColor = 'primaryHue'; const foregroundColor = 'neutralHue'; @@ -46,8 +52,9 @@ export const StyledSheetClose = styled.button.attrs({ })>` display: flex; position: absolute; - top: ${props => props.theme.space.base * 2.5}px; - ${props => (props.theme.rtl ? 'left' : 'right')}: ${props => `${props.theme.space.base * 2}px`}; + top: ${props => props.theme.space.base * baseMultipliers.top}px; + ${props => (props.theme.rtl ? 'left' : 'right')}: ${props => + `${props.theme.space.base * baseMultipliers.side}px`}; align-items: center; justify-content: center; /* prettier-ignore */ @@ -59,8 +66,8 @@ export const StyledSheetClose = styled.button.attrs({ border-radius: 50%; cursor: pointer; padding: 0; - width: ${props => props.theme.space.base * 10}px; - height: ${props => props.theme.space.base * 10}px; + width: ${props => props.theme.space.base * baseMultipliers.size}px; + height: ${props => props.theme.space.base * baseMultipliers.size}px; overflow: hidden; text-decoration: none; font-size: 0; diff --git a/packages/chrome/src/styled/sheet/StyledSheetHeader.spec.tsx b/packages/chrome/src/styled/sheet/StyledSheetHeader.spec.tsx new file mode 100644 index 00000000000..c8e8465c3d2 --- /dev/null +++ b/packages/chrome/src/styled/sheet/StyledSheetHeader.spec.tsx @@ -0,0 +1,41 @@ +/** + * Copyright Zendesk, Inc. + * + * Use of this source code is governed under the Apache License, Version 2.0 + * found at http://www.apache.org/licenses/LICENSE-2.0. + */ + +import React from 'react'; +import { renderRtl, render, screen } from 'garden-test-utils'; +import { DEFAULT_THEME, getColor } from '@zendeskgarden/react-theming'; + +import { StyledSheetHeader } from './StyledSheetHeader'; + +describe('StyledSheetHeader', () => { + it('renders default styling', () => { + render(Header); + + expect(screen.getByText('Header')).toHaveStyleRule( + 'border-bottom', + `${DEFAULT_THEME.borders.sm} ${getColor('neutralHue', 300, DEFAULT_THEME)}` + ); + }); + + it('renders correctly when button is present', () => { + render(Header); + + expect(screen.getByText('Header')).toHaveStyleRule( + 'padding-right', + `${DEFAULT_THEME.space.base * 14}px` + ); + }); + + it('renders correctly in rtl mode when button is present', () => { + renderRtl(Header); + + expect(screen.getByText('Header')).toHaveStyleRule( + 'padding-left', + `${DEFAULT_THEME.space.base * 14}px` + ); + }); +}); diff --git a/packages/chrome/src/styled/sheet/StyledSheetHeader.ts b/packages/chrome/src/styled/sheet/StyledSheetHeader.ts index a7d8da4d963..225152bb1cd 100644 --- a/packages/chrome/src/styled/sheet/StyledSheetHeader.ts +++ b/packages/chrome/src/styled/sheet/StyledSheetHeader.ts @@ -8,15 +8,28 @@ import styled, { ThemeProps, DefaultTheme } from 'styled-components'; import { getColor, retrieveComponentStyles, DEFAULT_THEME } from '@zendeskgarden/react-theming'; +import { baseMultipliers } from './StyledSheetClose'; + const COMPONENT_ID = 'chrome.sheet_header'; +export interface IStyledSheetHeaderProps { + isCloseButtonPresent?: boolean; +} + export const StyledSheetHeader = styled.header.attrs({ 'data-garden-id': COMPONENT_ID, 'data-garden-version': PACKAGE_VERSION -})>` +})>` border-bottom: ${props => `${props.theme.borders.sm} ${getColor('neutralHue', 300, props.theme)}}`}; padding: ${props => props.theme.space.base * 5}px; + ${props => + props.isCloseButtonPresent && + // the padding size accounts for 40px (10 base units) size of the button, + // 8px additional padding and 8px padding for the button position from a given side. + `padding-${props.theme.rtl ? 'left' : 'right'}: ${ + props.theme.space.base * (baseMultipliers.size + baseMultipliers.side + 2) + }px;`} ${props => retrieveComponentStyles(COMPONENT_ID, props)}; `; diff --git a/packages/chrome/src/utils/useSheetContext.ts b/packages/chrome/src/utils/useSheetContext.ts index 14b152f670e..439fa3e2021 100644 --- a/packages/chrome/src/utils/useSheetContext.ts +++ b/packages/chrome/src/utils/useSheetContext.ts @@ -10,9 +10,14 @@ import { createContext, useContext } from 'react'; export interface ISheetContext { titleId?: string; descriptionId?: string; + isCloseButtonPresent?: boolean; + setCloseButtonPresent: (isPresent: boolean) => void; } -export const SheetContext = createContext({}); +export const SheetContext = createContext({ + // eslint-disable-next-line @typescript-eslint/no-empty-function + setCloseButtonPresent() {} +}); export const useSheetContext = () => { return useContext(SheetContext);