@shopify/polaris
Version:
Shopify’s product component library
393 lines (358 loc) • 11.4 kB
Markdown
name: Frame
category: Structure
keywords:
- navigation
- nav
- links
- primary navigation
- main navigation
- global
- frame
- sidebar
- side bar
- loading
- top bar
- menu
- toast
fullSizeExamples: true
# Frame
The frame component, while not visible in the user interface itself, provides the structure for any non-embedded application. It wraps the main elements and houses the primary [navigation](https://polaris.shopify.com/components/navigation/navigation), [top bar](https://polaris.shopify.com/components/structure/top-bar), [toast](https://polaris.shopify.com/components/feedback-indicators/toast), and [contextual save bar](https://polaris.shopify.com/components/forms/contextual-save-bar) components.
## Best practices
For the best experience when creating an application frame, use the following components:
- [Top bar](https://polaris.shopify.com/components/structure/top-bar)
- [Navigation](https://polaris.shopify.com/components/navigation/navigation)
- [Contextual save bar](https://polaris.shopify.com/components/forms/contextual-save-bar)
- [Toast](https://polaris.shopify.com/components/feedback-indicators/toast)
- [Loading](https://polaris.shopify.com/components/feedback-indicators/loading)
## Examples
### Frame in a stand-alone application
Use to present the frame structure and all of its elements.
```jsx
function FrameExample() {
const defaultState = useRef({
emailFieldValue: 'dharma@jadedpixel.com',
nameFieldValue: 'Jaded Pixel',
});
const skipToContentRef = useRef(null);
const [toastActive, setToastActive] = useState(false);
const [isLoading, setIsLoading] = useState(false);
const [isDirty, setIsDirty] = useState(false);
const [searchActive, setSearchActive] = useState(false);
const [searchValue, setSearchValue] = useState('');
const [userMenuActive, setUserMenuActive] = useState(false);
const [mobileNavigationActive, setMobileNavigationActive] = useState(false);
const [modalActive, setModalActive] = useState(false);
const [nameFieldValue, setNameFieldValue] = useState(
defaultState.current.nameFieldValue,
);
const [emailFieldValue, setEmailFieldValue] = useState(
defaultState.current.emailFieldValue,
);
const [storeName, setStoreName] = useState(
defaultState.current.nameFieldValue,
);
const [supportSubject, setSupportSubject] = useState('');
const [supportMessage, setSupportMessage] = useState('');
const handleSubjectChange = useCallback(
(value) => setSupportSubject(value),
[],
);
const handleMessageChange = useCallback(
(value) => setSupportMessage(value),
[],
);
const handleDiscard = useCallback(() => {
setEmailFieldValue(defaultState.current.emailFieldValue);
setNameFieldValue(defaultState.current.nameFieldValue);
setIsDirty(false);
}, []);
const handleSave = useCallback(() => {
defaultState.current.nameFieldValue = nameFieldValue;
defaultState.current.emailFieldValue = emailFieldValue;
setIsDirty(false);
setToastActive(true);
setStoreName(defaultState.current.nameFieldValue);
}, [emailFieldValue, nameFieldValue]);
const handleNameFieldChange = useCallback((value) => {
setNameFieldValue(value);
value && setIsDirty(true);
}, []);
const handleEmailFieldChange = useCallback((value) => {
setEmailFieldValue(value);
value && setIsDirty(true);
}, []);
const handleSearchResultsDismiss = useCallback(() => {
setSearchActive(false);
setSearchValue('');
}, []);
const handleSearchFieldChange = useCallback((value) => {
setSearchValue(value);
setSearchActive(value.length > 0);
}, []);
const toggleToastActive = useCallback(
() => setToastActive((toastActive) => !toastActive),
[],
);
const toggleUserMenuActive = useCallback(
() => setUserMenuActive((userMenuActive) => !userMenuActive),
[],
);
const toggleMobileNavigationActive = useCallback(
() =>
setMobileNavigationActive(
(mobileNavigationActive) => !mobileNavigationActive,
),
[],
);
const toggleIsLoading = useCallback(
() => setIsLoading((isLoading) => !isLoading),
[],
);
const toggleModalActive = useCallback(
() => setModalActive((modalActive) => !modalActive),
[],
);
const toastMarkup = toastActive ? (
<Toast onDismiss={toggleToastActive} content="Changes saved" />
) : null;
const userMenuActions = [
{
items: [{content: 'Community forums'}],
},
];
const contextualSaveBarMarkup = isDirty ? (
<ContextualSaveBar
message="Unsaved changes"
saveAction={{
onAction: handleSave,
}}
discardAction={{
onAction: handleDiscard,
}}
/>
) : null;
const userMenuMarkup = (
<TopBar.UserMenu
actions={userMenuActions}
name="Dharma"
detail={storeName}
initials="D"
open={userMenuActive}
onToggle={toggleUserMenuActive}
/>
);
const searchResultsMarkup = (
<Card>
<ActionList
items={[
{content: 'Shopify help center'},
{content: 'Community forums'},
]}
/>
</Card>
);
const searchFieldMarkup = (
<TopBar.SearchField
onChange={handleSearchFieldChange}
value={searchValue}
placeholder="Search"
/>
);
const topBarMarkup = (
<TopBar
showNavigationToggle
userMenu={userMenuMarkup}
searchResultsVisible={searchActive}
searchField={searchFieldMarkup}
searchResults={searchResultsMarkup}
onSearchResultsDismiss={handleSearchResultsDismiss}
onNavigationToggle={toggleMobileNavigationActive}
/>
);
const navigationMarkup = (
<Navigation location="/">
<Navigation.Section
items={[
{
label: 'Back to Shopify',
icon: ArrowLeftMinor,
},
]}
/>
<Navigation.Section
separator
title="Jaded Pixel App"
items={[
{
label: 'Dashboard',
icon: HomeMajorMonotone,
onClick: toggleIsLoading,
},
{
label: 'Jaded Pixel Orders',
icon: OrdersMajorTwotone,
onClick: toggleIsLoading,
},
]}
action={{
icon: ConversationMinor,
accessibilityLabel: 'Contact support',
onClick: toggleModalActive,
}}
/>
</Navigation>
);
const loadingMarkup = isLoading ? <Loading /> : null;
const skipToContentTarget = (
<a id="SkipToContentTarget" ref={skipToContentRef} tabIndex={-1} />
);
const actualPageMarkup = (
<Page title="Account">
<Layout>
{skipToContentTarget}
<Layout.AnnotatedSection
title="Account details"
description="Jaded Pixel will use this as your account information."
>
<Card sectioned>
<FormLayout>
<TextField
label="Full name"
value={nameFieldValue}
onChange={handleNameFieldChange}
/>
<TextField
type="email"
label="Email"
value={emailFieldValue}
onChange={handleEmailFieldChange}
/>
</FormLayout>
</Card>
</Layout.AnnotatedSection>
</Layout>
</Page>
);
const loadingPageMarkup = (
<SkeletonPage>
<Layout>
<Layout.Section>
<Card sectioned>
<TextContainer>
<SkeletonDisplayText size="small" />
<SkeletonBodyText lines={9} />
</TextContainer>
</Card>
</Layout.Section>
</Layout>
</SkeletonPage>
);
const pageMarkup = isLoading ? loadingPageMarkup : actualPageMarkup;
const modalMarkup = (
<Modal
open={modalActive}
onClose={toggleModalActive}
title="Contact support"
primaryAction={{
content: 'Send',
onAction: toggleModalActive,
}}
>
<Modal.Section>
<FormLayout>
<TextField
label="Subject"
value={supportSubject}
onChange={handleSubjectChange}
/>
<TextField
label="Message"
value={supportMessage}
onChange={handleMessageChange}
multiline
/>
</FormLayout>
</Modal.Section>
</Modal>
);
const theme = {
colors: {
topBar: {
background: '#357997',
},
},
logo: {
width: 124,
topBarSource:
'https://cdn.shopify.com/s/files/1/0446/6937/files/jaded-pixel-logo-color.svg?6215648040070010999',
contextualSaveBarSource:
'https://cdn.shopify.com/s/files/1/0446/6937/files/jaded-pixel-logo-gray.svg?6215648040070010999',
url: 'http://jadedpixel.com',
accessibilityLabel: 'Jaded Pixel',
},
};
return (
<div style={{height: '500px'}}>
<AppProvider
theme={theme}
i18n={{
Polaris: {
Avatar: {
label: 'Avatar',
labelWithInitials: 'Avatar with initials {initials}',
},
ContextualSaveBar: {
save: 'Save',
discard: 'Discard',
},
TextField: {
characterCount: '{count} characters',
},
TopBar: {
toggleMenuLabel: 'Toggle menu',
SearchField: {
clearButtonLabel: 'Clear',
search: 'Search',
},
},
Modal: {
iFrameTitle: 'body markup',
},
Frame: {
skipToContent: 'Skip to content',
Navigation: {
closeMobileNavigationLabel: 'Close navigation',
},
},
},
}}
>
<Frame
topBar={topBarMarkup}
navigation={navigationMarkup}
showMobileNavigation={mobileNavigationActive}
onNavigationDismiss={toggleMobileNavigationActive}
skipToContentTarget={skipToContentRef.current}
>
{contextualSaveBarMarkup}
{loadingMarkup}
{pageMarkup}
{toastMarkup}
{modalMarkup}
</Frame>
</AppProvider>
</div>
);
}
```
## Related components
- To display the navigation component on small screens, to provide search and a user menu, or to style the [frame](https://polaris.shopify.com/components/structure/frame) component to reflect an application’s brand, use the [top bar](https://polaris.shopify.com/components/structure/top-bar) component.
- To display the primary navigation within the frame of a non-embedded application, use the [navigation](https://polaris.shopify.com/components/structure/navigation) component.
- To tell merchants their options once they have made changes to a form on the page use the [contextual save bar](https://polaris.shopify.com/components/forms/contextual-save-bar) component.
- To provide quick, at-a-glance feedback on the outcome of an action, use the [toast](https://polaris.shopify.com/components/feedback-indicators/toast) component.
- To indicate to merchants that a page is loading or an upload is processing use the [loading](https://polaris.shopify.com/components/feedback-indicators/loading) component.