From 090bc61ea8cb6d7d347f6cf6d7394c792380a67e Mon Sep 17 00:00:00 2001 From: Willson Smith Date: Fri, 22 May 2020 14:23:35 -0400 Subject: [PATCH] Make `primaryAction` accept PrimaryAction | React.ReactNode for custom primary actions --- UNRELEASED.md | 4 ++ src/components/Page/Page.tsx | 4 +- src/components/Page/README.md | 26 ++++++++ .../Page/components/Header/Header.tsx | 66 ++++++++++++------- .../components/Header/tests/Header.test.tsx | 11 ++++ 5 files changed, 87 insertions(+), 24 deletions(-) diff --git a/UNRELEASED.md b/UNRELEASED.md index a2725494e60..79224abee61 100644 --- a/UNRELEASED.md +++ b/UNRELEASED.md @@ -6,6 +6,10 @@ ### Enhancements +- Added `ReactNode` as an accepted prop type to `primaryAction` on the `Page` component ([#3002](https://github.com/Shopify/polaris-react/pull/3002)) +- Added a `fullWidth` prop to `EmptyState` to support full width layout within a content context ([#2992](https://github.com/Shopify/polaris-react/pull/2992)) +- Added an `emptyState` prop to `ResourceList` to support in context empty states in list views ([#2569](https://github.com/Shopify/polaris-react/pull/2569)) + ### Bug fixes ### Documentation diff --git a/src/components/Page/Page.tsx b/src/components/Page/Page.tsx index aeda17e324f..22399cc9afa 100644 --- a/src/components/Page/Page.tsx +++ b/src/components/Page/Page.tsx @@ -16,7 +16,7 @@ import { WithAppProviderProps, } from '../../utilities/with-app-provider'; -import {Header, HeaderProps} from './components'; +import {Header, HeaderProps, isPrimaryAction} from './components'; import styles from './Page.scss'; export interface PageProps extends HeaderProps { @@ -166,7 +166,7 @@ class PageInner extends React.PureComponent { return { title, buttons: transformActions(appBridge, { - primaryAction, + ...(isPrimaryAction(primaryAction) && {primaryAction}), secondaryActions, actionGroups, }), diff --git a/src/components/Page/README.md b/src/components/Page/README.md index 0195356ba14..e8258b2c3bb 100644 --- a/src/components/Page/README.md +++ b/src/components/Page/README.md @@ -248,6 +248,32 @@ Use for building any page on iOS. +### Page with custom primary action + + + +Use to create a custom primary action. + +```jsx + + Save + + } +> +

Page content

+
+``` + ### Page without primary action in header diff --git a/src/components/Page/components/Header/Header.tsx b/src/components/Page/components/Header/Header.tsx index 24a9fafdb4a..7c15d6c8b77 100644 --- a/src/components/Page/components/Header/Header.tsx +++ b/src/components/Page/components/Header/Header.tsx @@ -43,7 +43,7 @@ export interface HeaderProps extends TitleProps { /** Adds a border to the bottom of the page header (stand-alone app use only) */ separator?: boolean; /** Primary page-level action */ - primaryAction?: PrimaryAction; + primaryAction?: PrimaryAction | React.ReactNode; /** Page-level pagination (stand-alone app use only) */ pagination?: PaginationDescriptor; /** Collection of breadcrumbs */ @@ -56,6 +56,12 @@ export interface HeaderProps extends TitleProps { additionalNavigation?: React.ReactNode; } +export function isPrimaryAction( + x: PrimaryAction | React.ReactNode, +): x is PrimaryAction { + return !React.isValidElement(x) && x !== undefined; +} + export function Header({ title, subtitle, @@ -116,28 +122,8 @@ export function Header({ /> ); - const primary = - primaryAction && - (primaryAction.primary === undefined ? true : primaryAction.primary); - const primaryActionMarkup = primaryAction ? ( - ( -
{children}
- )} - > - {buttonsFrom( - shouldShowIconOnly( - newDesignLanguage, - isNavigationCollapsed, - primaryAction, - ), - { - primary, - }, - )} -
+ ) : null; const actionMenuMarkup = @@ -228,6 +214,42 @@ export function Header({ ); } +function PrimaryActionMarkup({ + primaryAction, +}: { + primaryAction: PrimaryAction | React.ReactNode; +}) { + const {isNavigationCollapsed} = useMediaQuery(); + const {newDesignLanguage} = useFeatures(); + let content = primaryAction; + if (isPrimaryAction(primaryAction)) { + const primary = + primaryAction.primary === undefined ? true : primaryAction.primary; + + content = buttonsFrom( + shouldShowIconOnly( + newDesignLanguage, + isNavigationCollapsed, + primaryAction, + ), + { + primary, + }, + ); + } + + return ( + ( +
{children}
+ )} + > + {content} +
+ ); +} + function shouldShowIconOnly( newDesignLanguage: boolean, isMobile: boolean, diff --git a/src/components/Page/components/Header/tests/Header.test.tsx b/src/components/Page/components/Header/tests/Header.test.tsx index e6edb86471d..cf7b5c4ffb3 100644 --- a/src/components/Page/components/Header/tests/Header.test.tsx +++ b/src/components/Page/components/Header/tests/Header.test.tsx @@ -12,6 +12,7 @@ import { } from 'components'; // eslint-disable-next-line no-restricted-imports import {mountWithAppProvider} from 'test-utilities/legacy'; +import {mountWithApp} from 'test-utilities'; import type {LinkAction} from '../../../../../types'; import {Header, HeaderProps} from '../Header'; @@ -98,6 +99,16 @@ describe('
', () => { const expectedButton = buttonsFrom(primaryAction, {primary: false}); expect(header.contains(expectedButton)).toBeTruthy(); }); + + it('renders a `ReactNode`', () => { + const PrimaryAction = () => null; + + const header = mountWithApp( +
} />, + ); + + expect(header).toContainReactComponent(PrimaryAction); + }); }); describe('pagination', () => {