From 6a517e9089c5d17e9acf8d1dc39fba0f7681c939 Mon Sep 17 00:00:00 2001 From: Chloe Rice Date: Fri, 25 Aug 2023 16:22:36 -0400 Subject: [PATCH] [ContextualSaveBar] Add support for custom saveAction markup --- .changeset/healthy-toads-rollerblade.md | 5 + .../ContextualSaveBar.module.scss | 10 +- .../ContextualSaveBar/ContextualSaveBar.tsx | 52 +++-- .../src/components/Page/Page.stories.tsx | 184 +++++++++++++++- polaris-react/src/utilities/frame/index.ts | 1 + polaris-react/src/utilities/frame/types.ts | 4 +- .../page-with-custom-primary-action.tsx | 199 +++++++++++++++++- 7 files changed, 414 insertions(+), 41 deletions(-) create mode 100644 .changeset/healthy-toads-rollerblade.md diff --git a/.changeset/healthy-toads-rollerblade.md b/.changeset/healthy-toads-rollerblade.md new file mode 100644 index 00000000000..baa86a1d133 --- /dev/null +++ b/.changeset/healthy-toads-rollerblade.md @@ -0,0 +1,5 @@ +--- +'@shopify/polaris': minor +--- + +Added support for setting custom markup on the `ContextualSaveBar` `saveAction` prop diff --git a/polaris-react/src/components/Frame/components/ContextualSaveBar/ContextualSaveBar.module.scss b/polaris-react/src/components/Frame/components/ContextualSaveBar/ContextualSaveBar.module.scss index 62b7c2a663f..b92ee7d83d5 100644 --- a/polaris-react/src/components/Frame/components/ContextualSaveBar/ContextualSaveBar.module.scss +++ b/polaris-react/src/components/Frame/components/ContextualSaveBar/ContextualSaveBar.module.scss @@ -1,12 +1,6 @@ @import '../../../../styles/common'; .ContextualSaveBar { - /* stylelint-disable -- polaris: Used to apply dark theme to action buttons */ - --p-color-bg-surface: var(--p-color-bg-inverse); - --p-color-text: var(--p-color-text-inverse); - --p-color-bg-surface-hover: var(--p-color-bg-fill-inverse-hover); - --p-color-bg-surface-secondary-active: var(--p-color-bg-fill-inverse-active); - /* stylelint-enable */ display: flex; height: $top-bar-height; background: var(--p-color-bg-inverse); @@ -95,8 +89,8 @@ --pc-button-bg_pressed: rgba(247, 247, 247, 1); --pc-button-bg_disabled: rgba(255, 255, 255, 0.2); --pc-button-box-shadow: 0 1px 0 0 rgba(255, 255, 255, 0.48) inset, - -1px 0 0 0 rgba(255, 255, 255, 0.20) inset, - 1px 0 0 0 rgba(255, 255, 255, 0.20) inset, + -1px 0 0 0 rgba(255, 255, 255, 0.2) inset, + 1px 0 0 0 rgba(255, 255, 255, 0.2) inset, 0 -1.5px 0 0 rgba(0, 0, 0, 0.25) inset; --pc-button-box-shadow_active: 0px 2px 1px 0px rgba(26, 26, 26, 0.2) inset, 1px 0px 1px 0px rgba(26, 26, 26, 0.12) inset, diff --git a/polaris-react/src/components/Frame/components/ContextualSaveBar/ContextualSaveBar.tsx b/polaris-react/src/components/Frame/components/ContextualSaveBar/ContextualSaveBar.tsx index 0ed2fbcf44f..fe0276c320f 100644 --- a/polaris-react/src/components/Frame/components/ContextualSaveBar/ContextualSaveBar.tsx +++ b/polaris-react/src/components/Frame/components/ContextualSaveBar/ContextualSaveBar.tsx @@ -3,16 +3,19 @@ import {AlertTriangleIcon} from '@shopify/polaris-icons'; import {Button} from '../../../Button'; import {Image} from '../../../Image'; -// eslint-disable-next-line import/no-deprecated -import {LegacyStack} from '../../../LegacyStack'; +import {InlineStack} from '../../../InlineStack'; import {Text} from '../../../Text'; import {Icon} from '../../../Icon'; import {classNames} from '../../../../utilities/css'; +import type { + ContextualSaveBarProps, + ContextualSaveBarAction, +} from '../../../../utilities/frame'; import {useFrame} from '../../../../utilities/frame'; -import type {ContextualSaveBarProps} from '../../../../utilities/frame'; import {getWidth} from '../../../../utilities/get-width'; import {useI18n} from '../../../../utilities/i18n'; import {useToggle} from '../../../../utilities/use-toggle'; +import {isInterface} from '../../../../utilities/is-interface'; import {DiscardConfirmationModal} from './components'; import styles from './ContextualSaveBar.module.scss'; @@ -77,25 +80,34 @@ export function ContextualSaveBar({ ); + let saveActionMarkup; + const saveActionContent = - saveAction && saveAction.content + saveAction && 'content' in saveAction ? saveAction.content : i18n.translate('Polaris.ContextualSaveBar.save'); - const saveActionMarkup = saveAction && ( - - ); + if (saveAction && isInterface(saveAction)) { + const {url, loading, disabled, onAction} = + saveAction as ContextualSaveBarAction; + + saveActionMarkup = ( + + ); + } else { + saveActionMarkup = saveAction; + } const width = getWidth(logo, 104); @@ -134,11 +146,11 @@ export function ContextualSaveBar({ )}
- + {secondaryMenu} {discardActionMarkup} {saveActionMarkup} - +
diff --git a/polaris-react/src/components/Page/Page.stories.tsx b/polaris-react/src/components/Page/Page.stories.tsx index de4048e0085..4cf0d79ab9c 100644 --- a/polaris-react/src/components/Page/Page.stories.tsx +++ b/polaris-react/src/components/Page/Page.stories.tsx @@ -1,4 +1,4 @@ -import React from 'react'; +import React, {useState, useRef} from 'react'; import type {ComponentMeta} from '@storybook/react'; import { DeleteIcon, @@ -7,6 +7,7 @@ import { ExternalIcon, ViewIcon, MenuVerticalIcon, + ChevronDownIcon, } from '@shopify/polaris-icons'; import { Badge, @@ -15,6 +16,13 @@ import { LegacyStack, Page, PageActions, + Popover, + ActionList, + ButtonGroup, + TextField, + FormLayout, + ContextualSaveBar, + Frame, } from '@shopify/polaris'; export default { @@ -493,3 +501,177 @@ export function WithContentAfterTitleAndSubtitle() { ); } + +export function WithSplitSaveAction() { + const initialState = { + title: 'Jar With Lock-Lid', + description: '', + isDraft: false, + }; + + const [active, setActive] = React.useState(false); + const [title, setTitle] = useState('Jar With Lock-Lid'); + const [description, setDescription] = useState(''); + const [isDirty, setIsDirty] = useState(false); + const [isDraft, setIsDraft] = useState(false); + + const savedEditHistory = useRef< + { + title: string; + description: string; + isDraft: boolean; + }[] + >([]); + + const handleChange = (name: string) => (value: string) => { + switch (name) { + case 'title': + handleDirtyState(name, value, title); + setTitle(value); + break; + case 'description': + handleDirtyState(name, value, description); + setDescription(value); + break; + default: + null; + } + }; + + const handleDirtyState = ( + name: string, + newValue: string, + currentValue: string, + ) => { + if ( + (newValue !== initialState[name] && !isDirty) || + newValue !== currentValue + ) { + setIsDirty(true); + } else { + setIsDirty(false); + } + }; + + const handleDiscard = () => { + const previousState: { + title: string; + description: string; + isDraft: boolean; + } = savedEditHistory.current.pop() ?? initialState; + + setTitle(previousState.title); + setDescription(previousState.description); + setIsDraft(previousState.isDraft); + setIsDirty(false); + }; + + const splitButton = ( + + + + setActive(true)} + icon={ChevronDownIcon} + accessibilityLabel="Other save actions" + /> + } + autofocusTarget="first-node" + onClose={() => setActive(false)} + zIndexOverride={514} + > + { + setIsDraft(true); + savedEditHistory.current.push({ + title, + description, + isDraft: true, + }); + }, + }, + ]} + onActionAnyItem={() => setIsDirty(false)} + /> + + + ); + + const saveBar = isDirty ? ( + + ) : null; + + console.log(savedEditHistory); + + return ( + + {saveBar} + + {isDraft ? 'Draft' : 'Active'} + + } + secondaryActions={[ + {content: 'Duplicate'}, + {content: 'View on your store'}, + ]} + pagination={{ + hasPrevious: true, + hasNext: true, + }} + > + + + + + + + + + ); +} diff --git a/polaris-react/src/utilities/frame/index.ts b/polaris-react/src/utilities/frame/index.ts index e567a36650f..a71a1a73312 100644 --- a/polaris-react/src/utilities/frame/index.ts +++ b/polaris-react/src/utilities/frame/index.ts @@ -3,6 +3,7 @@ export * from './hooks'; export * from './context'; export type { + ContextualSaveBarAction, ContextualSaveBarProps, ToastProps, ToastID, diff --git a/polaris-react/src/utilities/frame/types.ts b/polaris-react/src/utilities/frame/types.ts index 8af08faa5de..ff4e2b9a99a 100644 --- a/polaris-react/src/utilities/frame/types.ts +++ b/polaris-react/src/utilities/frame/types.ts @@ -13,7 +13,7 @@ export interface Logo { width?: number; } -interface ContextualSaveBarAction { +export interface ContextualSaveBarAction { /** A destination to link to */ url?: string; /** Content the action displays */ @@ -40,7 +40,7 @@ export interface ContextualSaveBarProps { /** Accepts a string of content that will be rendered to the left of the actions */ message?: string; /** Save or commit contextual save bar action with text defaulting to 'Save' */ - saveAction?: ContextualSaveBarAction; + saveAction?: ContextualSaveBarAction | React.JSX.Element; /** Discard or cancel contextual save bar action with text defaulting to 'Discard' */ discardAction?: ContextualSaveBarCombinedActionProps; /** Remove the normal max-width on the contextual save bar */ diff --git a/polaris.shopify.com/pages/examples/page-with-custom-primary-action.tsx b/polaris.shopify.com/pages/examples/page-with-custom-primary-action.tsx index ef1816ff8e9..1aee7beac87 100644 --- a/polaris.shopify.com/pages/examples/page-with-custom-primary-action.tsx +++ b/polaris.shopify.com/pages/examples/page-with-custom-primary-action.tsx @@ -1,18 +1,197 @@ -import {Page, Button, LegacyCard} from '@shopify/polaris'; -import React from 'react'; +import { + Page, + Badge, + LegacyCard, + TextField, + Button, + ButtonGroup, + Popover, + Frame, + ContextualSaveBar, + FormLayout, + ActionList, +} from '@shopify/polaris'; +import React, {useState, useRef} from 'react'; import {withPolarisExample} from '../../src/components/PolarisExampleWrapper'; +import {ChevronDownIcon} from '@shopify/polaris-icons'; function PageExample() { + interface FormState { + title: string; + description: string; + isDraft: boolean; + } + + const initialState: FormState = { + title: 'Jar With Lock-Lid', + description: '', + isDraft: false, + }; + + const [active, setActive] = React.useState(false); + const [title, setTitle] = useState('Jar With Lock-Lid'); + const [description, setDescription] = useState(''); + const [isDirty, setIsDirty] = useState(false); + const [isDraft, setIsDraft] = useState(false); + + const savedEditHistory = useRef< + { + title: string; + description: string; + isDraft: boolean; + }[] + >([]); + + const handleChange = (name: string) => (value: string) => { + switch (name) { + case 'title': + handleDirtyState(name, value, title); + setTitle(value); + break; + case 'description': + handleDirtyState(name, value, description); + setDescription(value); + break; + default: + null; + } + }; + + const handleDirtyState = ( + name: keyof FormState, + newValue: string, + currentValue: string, + ) => { + if ( + (newValue !== initialState[name] && !isDirty) || + newValue !== currentValue + ) { + setIsDirty(true); + } else { + setIsDirty(false); + } + }; + + const handleDiscard = () => { + const previousState: { + title: string; + description: string; + isDraft: boolean; + } = savedEditHistory.current.pop() ?? initialState; + + setTitle(previousState?.title); + setDescription(previousState?.description); + setIsDraft(previousState?.isDraft); + setIsDirty(false); + }; + + const splitButton = ( + + + + setActive(true)} + icon={ChevronDownIcon} + accessibilityLabel="Other save actions" + /> + } + autofocusTarget="first-node" + onClose={() => setActive(false)} + zIndexOverride={514} + > + { + setIsDraft(true); + savedEditHistory.current.push({ + title, + description, + isDraft: true, + }); + }, + }, + ]} + onActionAnyItem={() => setIsDirty(false)} + /> + + + ); + + const saveBar = isDirty ? ( + + ) : null; + + console.log(savedEditHistory); + return ( - Save} + - -

Credit card information

-
-
+ {saveBar} + + {isDraft ? 'Draft' : 'Active'} + + } + secondaryActions={[ + {content: 'Duplicate'}, + {content: 'View on your store'}, + ]} + pagination={{ + hasPrevious: true, + hasNext: true, + }} + > + + + + + + + + ); }