1
1
mirror of https://github.com/primer/css.git synced 2024-12-23 14:13:14 +03:00

PageLayout component / Layout beta + storybook (#1737)

* storybook for layout alpha

* the beginning of layout beta

* simplify spacing structure and other things

this is a reminder that all the abstraction can happen in the viewComponent;
there's no need to flood the css with constricted behaviors

* add rowGap & dividers, clean spacing props

* content-width support, sticky pane

* cleanup props

* add preset property

* !default

* component name & settings example

* typo, cleaner header & footer template

* fix preset options

* mobile-friendly responsive behavior

* ongoing flowVertical breakpoint behavior

* finish panePosition + responsive pos & dividers

* panePosition fix

* add splitAsPage, finish responsive divider

* Layout beta ongoing updates (#1779)

- simplify component with better names
- rename it to `PageLayout`
- stylelint cleanup
- finalize responsive variants
- finalize variant-specific region dividers

* add minimum 320px viewport

* fix layout alpha, add layout patterns

* cleanup bg colors, stylint pass

* enable debug bg colors

* colorful regions by default for dubugging

* "Needless disable for primer/no-undefined-vars"

* Stylelint auto-fixes

* line breaks a EOF

* introduce page layout behavior as a sb helper

* children props in specialized components

* cleanup

* typo

* discussions responsive temporary example

* layout alpha descriptions

* pageLayout prop descriptions

* cleanup

* fix conflict

* copy

* responsiveVariant storybook description

* 0 padding on fullscreen storybook layout

* has__divider for boolean props

* cleanup, copy

* cleanup

* cleanup

* cleanup chained selections/descendants

* Stylelint auto-fixes

* inherit values for responsive divider props

* consolidate modifier names for responsive props

Changelog:

### CSS classes
- `PageLayout--variant-stackRegions` → `PageLayout--responsive-stackRegions`
- `PageLayout--variant-separateRegions` → `PageLayout--responsive-separateRegions`
- `PageLayout--variant-stackRegions-panePos-*` →`PageLayout--responsive-panePos-*`
- `PageLayout--variant-separateRegions-primary-*` →`PageLayout--responsive-primary-*`
- `PageLayout-region--hasDivider-*` → `PageLayout-region--dividerNarrow-*`

### Prop names
- `responsivePrimaryRegion` → `primaryRegion`
- `paneResponsivePosition` → `panePositionNarrow`
- `paneResponsiveDivider` → `paneDividerNarrow`
- `headerResponsiveDivider`→ `headerDividerNarrow`
- `footerResponsiveDivider`→ `footerDividerNarrow`

### Args
- `*DividerNarrow` props have new `inherit` value by default

* Create lemon-games-swim.md

* Update lemon-games-swim.md

* cleanup, header+footer dividers as local modifiers

* hasFooterDivider instead of footerDivider

* fix primaryRegion selector

Co-authored-by: Actions Auto Build <actions@github.com>
Co-authored-by: Katie Langerman <langermank@github.com>
This commit is contained in:
Vinicius Depizzol 2021-12-21 12:51:59 -08:00 committed by GitHub
parent 995d790d73
commit 6b4089d8b3
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
15 changed files with 2091 additions and 9 deletions

View File

@ -0,0 +1,5 @@
---
"@primer/css": minor
---
Adds new PageLayout component CSS with Storybook documentation

View File

@ -22,6 +22,10 @@
margin: -1rem;
}
.sb-main-fullscreen .theme-wrap .story-wrap {
padding: 0;
}
.sb-main-padded div:not(.theme-wrap) > [data-dark-theme] {
margin: -1rem;
padding: 1rem;

View File

@ -7,6 +7,13 @@ import renderToHTML from '../src/stories/helpers/code-snippet-html-helper'
const customViewports = {
minXS: {
name: 'XS (min)',
styles: {
width: '320px',
height: '100%'
}
},
medXS: {
name: 'XS (med)',
styles: {
width: '375px',
height: '100%'

View File

@ -0,0 +1,257 @@
import React from 'react'
import clsx from 'clsx'
import {DividerTemplate} from './ActionListDivider.stories'
import {ListItemTemplate} from './ActionListItem.stories'
import {ListTemplate} from './ActionList.stories'
export default {
title: 'Components/ActionList/Examples'
}
export const RepoSettings = ListTemplate.bind({})
RepoSettings.storyName = 'Repository settings';
RepoSettings.args = {
...ListTemplate.args,
...ListItemTemplate.args,
ariaLabel: 'Main menu description',
role: 'menu',
showDividers: false,
children: (
<>
<ListItemTemplate
text="General"
href="#content"
leadingVisualSize="ActionList-content--visual16"
leadingVisual={`<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16" width="16" height="16"><path fill-rule="evenodd" d="M7.429 1.525a6.593 6.593 0 011.142 0c.036.003.108.036.137.146l.289 1.105c.147.56.55.967.997 1.189.174.086.341.183.501.29.417.278.97.423 1.53.27l1.102-.303c.11-.03.175.016.195.046.219.31.41.641.573.989.014.031.022.11-.059.19l-.815.806c-.411.406-.562.957-.53 1.456a4.588 4.588 0 010 .582c-.032.499.119 1.05.53 1.456l.815.806c.08.08.073.159.059.19a6.494 6.494 0 01-.573.99c-.02.029-.086.074-.195.045l-1.103-.303c-.559-.153-1.112-.008-1.529.27-.16.107-.327.204-.5.29-.449.222-.851.628-.998 1.189l-.289 1.105c-.029.11-.101.143-.137.146a6.613 6.613 0 01-1.142 0c-.036-.003-.108-.037-.137-.146l-.289-1.105c-.147-.56-.55-.967-.997-1.189a4.502 4.502 0 01-.501-.29c-.417-.278-.97-.423-1.53-.27l-1.102.303c-.11.03-.175-.016-.195-.046a6.492 6.492 0 01-.573-.989c-.014-.031-.022-.11.059-.19l.815-.806c.411-.406.562-.957.53-1.456a4.587 4.587 0 010-.582c.032-.499-.119-1.05-.53-1.456l-.815-.806c-.08-.08-.073-.159-.059-.19a6.44 6.44 0 01.573-.99c.02-.029.086-.075.195-.045l1.103.303c.559.153 1.112.008 1.529-.27.16-.107.327-.204.5-.29.449-.222.851-.628.998-1.189l.289-1.105c.029-.11.101-.143.137-.146zM8 0c-.236 0-.47.01-.701.03-.743.065-1.29.615-1.458 1.261l-.29 1.106c-.017.066-.078.158-.211.224a5.994 5.994 0 00-.668.386c-.123.082-.233.09-.3.071L3.27 2.776c-.644-.177-1.392.02-1.82.63a7.977 7.977 0 00-.704 1.217c-.315.675-.111 1.422.363 1.891l.815.806c.05.048.098.147.088.294a6.084 6.084 0 000 .772c.01.147-.038.246-.088.294l-.815.806c-.474.469-.678 1.216-.363 1.891.2.428.436.835.704 1.218.428.609 1.176.806 1.82.63l1.103-.303c.066-.019.176-.011.299.071.213.143.436.272.668.386.133.066.194.158.212.224l.289 1.106c.169.646.715 1.196 1.458 1.26a8.094 8.094 0 001.402 0c.743-.064 1.29-.614 1.458-1.26l.29-1.106c.017-.066.078-.158.211-.224a5.98 5.98 0 00.668-.386c.123-.082.233-.09.3-.071l1.102.302c.644.177 1.392-.02 1.82-.63.268-.382.505-.789.704-1.217.315-.675.111-1.422-.364-1.891l-.814-.806c-.05-.048-.098-.147-.088-.294a6.1 6.1 0 000-.772c-.01-.147.039-.246.088-.294l.814-.806c.475-.469.679-1.216.364-1.891a7.992 7.992 0 00-.704-1.218c-.428-.609-1.176-.806-1.82-.63l-1.103.303c-.066.019-.176.011-.299-.071a5.991 5.991 0 00-.668-.386c-.133-.066-.194-.158-.212-.224L10.16 1.29C9.99.645 9.444.095 8.701.031A8.094 8.094 0 008 0zm1.5 8a1.5 1.5 0 11-3 0 1.5 1.5 0 013 0zM11 8a3 3 0 11-6 0 3 3 0 016 0z"></path></svg>`}
/>
<DividerTemplate />
<DividerTemplate title="Access" id="group-id-1" />
<ListItemTemplate
containsSubItem
children={
<ListTemplate
subGroup
role="menu"
ariaLabelledBy="group-id-1"
ariaLabel="Sub nav decription"
children={
<>
<ListItemTemplate
text="Collaborators"
href="/"
leadingVisualSize="ActionList-content--visual16"
leadingVisual={`<svg aria-hidden="true" height="16" viewBox="0 0 16 16" version="1.1" width="16" data-view-component="true" class="octicon octicon-people">
<path fill-rule="evenodd" d="M5.5 3.5a2 2 0 100 4 2 2 0 000-4zM2 5.5a3.5 3.5 0 115.898 2.549 5.507 5.507 0 013.034 4.084.75.75 0 11-1.482.235 4.001 4.001 0 00-7.9 0 .75.75 0 01-1.482-.236A5.507 5.507 0 013.102 8.05 3.49 3.49 0 012 5.5zM11 4a.75.75 0 100 1.5 1.5 1.5 0 01.666 2.844.75.75 0 00-.416.672v.352a.75.75 0 00.574.73c1.2.289 2.162 1.2 2.522 2.372a.75.75 0 101.434-.44 5.01 5.01 0 00-2.56-3.012A3 3 0 0011 4z"></path>
</svg>`}
/>
<ListItemTemplate
containsSubItem
text="Moderation options"
// href="/"
collapsible
leadingVisualSize="ActionList-content--visual16"
leadingVisual={`<svg aria-hidden="true" height="16" viewBox="0 0 16 16" version="1.1" width="16" data-view-component="true" class="octicon octicon-comment-discussion">
<path fill-rule="evenodd" d="M1.5 2.75a.25.25 0 01.25-.25h8.5a.25.25 0 01.25.25v5.5a.25.25 0 01-.25.25h-3.5a.75.75 0 00-.53.22L3.5 11.44V9.25a.75.75 0 00-.75-.75h-1a.25.25 0 01-.25-.25v-5.5zM1.75 1A1.75 1.75 0 000 2.75v5.5C0 9.216.784 10 1.75 10H2v1.543a1.457 1.457 0 002.487 1.03L7.061 10h3.189A1.75 1.75 0 0012 8.25v-5.5A1.75 1.75 0 0010.25 1h-8.5zM14.5 4.75a.25.25 0 00-.25-.25h-.5a.75.75 0 110-1.5h.5c.966 0 1.75.784 1.75 1.75v5.5A1.75 1.75 0 0114.25 12H14v1.543a1.457 1.457 0 01-2.487 1.03L9.22 12.28a.75.75 0 111.06-1.06l2.22 2.22v-2.19a.75.75 0 01.75-.75h1a.25.25 0 00.25-.25v-5.5z"></path>
</svg>`}
children={
<ListTemplate
containsSubItem
role="menu"
subGroup
ariaLabel="Sub nav decription"
children={
<>
<ListItemTemplate
subItem
text="Interaction limits"
href="/"
ariaCurrent="page"
/>
<ListItemTemplate
subItem
text="Blocked users"
href="/"
/>
<ListItemTemplate
subItem
text="Code review limits"
href="/"
/>
<ListItemTemplate
subItem
text="Reported content"
href="/"
/>
</>
}
/>
}
/>
</>
}
/>
}
/>
<DividerTemplate />
<DividerTemplate title="Code and automation" id="group-id-2" />
<ListItemTemplate
containsSubItem
children={
<ListTemplate
subGroup
role="menu"
ariaLabel="Sub nav decription"
ariaLabelledBy="group-id-2"
children={
<>
<ListItemTemplate
text="Branches"
href="/"
leadingVisualSize="ActionList-content--visual16"
leadingVisual={`<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16" width="16" height="16"><path fill-rule="evenodd" d="M11.75 2.5a.75.75 0 100 1.5.75.75 0 000-1.5zm-2.25.75a2.25 2.25 0 113 2.122V6A2.5 2.5 0 0110 8.5H6a1 1 0 00-1 1v1.128a2.251 2.251 0 11-1.5 0V5.372a2.25 2.25 0 111.5 0v1.836A2.492 2.492 0 016 7h4a1 1 0 001-1v-.628A2.25 2.25 0 019.5 3.25zM4.25 12a.75.75 0 100 1.5.75.75 0 000-1.5zM3.5 3.25a.75.75 0 111.5 0 .75.75 0 01-1.5 0z"></path></svg>`}
/>
<ListItemTemplate
text="Actions"
href="/"
leadingVisualSize="ActionList-content--visual16"
leadingVisual={`<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16" width="16" height="16"><path fill-rule="evenodd" d="M1.5 8a6.5 6.5 0 1113 0 6.5 6.5 0 01-13 0zM8 0a8 8 0 100 16A8 8 0 008 0zM6.379 5.227A.25.25 0 006 5.442v5.117a.25.25 0 00.379.214l4.264-2.559a.25.25 0 000-.428L6.379 5.227z"></path></svg>`}
/>
<ListItemTemplate
text="Webhooks"
href="/"
leadingVisualSize="ActionList-content--visual16"
leadingVisual={`<svg aria-hidden="true" height="16" viewBox="0 0 16 16" version="1.1" width="16" data-view-component="true" class="octicon octicon-broadcast">
<path fill-rule="evenodd" d="M3.267 1.457c.3.286.312.76.026 1.06A6.475 6.475 0 001.5 7a6.472 6.472 0 001.793 4.483.75.75 0 01-1.086 1.034 8.89 8.89 0 01-.276-.304l.569-.49-.569.49A7.971 7.971 0 010 7c0-2.139.84-4.083 2.207-5.517a.75.75 0 011.06-.026zm9.466 0a.75.75 0 011.06.026A7.975 7.975 0 0116 7c0 2.139-.84 4.083-2.207 5.517a.75.75 0 11-1.086-1.034A6.475 6.475 0 0014.5 7a6.475 6.475 0 00-1.793-4.483.75.75 0 01.026-1.06zM8.75 8.582a1.75 1.75 0 10-1.5 0v5.668a.75.75 0 001.5 0V8.582zM5.331 4.736a.75.75 0 10-1.143-.972A4.983 4.983 0 003 7c0 1.227.443 2.352 1.177 3.222a.75.75 0 001.146-.967A3.483 3.483 0 014.5 7c0-.864.312-1.654.831-2.264zm6.492-.958a.75.75 0 00-1.146.967c.514.61.823 1.395.823 2.255 0 .86-.31 1.646-.823 2.255a.75.75 0 101.146.967A4.983 4.983 0 0013 7a4.983 4.983 0 00-1.177-3.222z"></path>
</svg>`}
/>
<ListItemTemplate
text="Environments"
href="/"
leadingVisualSize="ActionList-content--visual16"
leadingVisual={`<svg aria-hidden="true" height="16" viewBox="0 0 16 16" version="1.1" width="16" data-view-component="true" class="octicon octicon-server">
<path fill-rule="evenodd" d="M1.75 1A1.75 1.75 0 000 2.75v4c0 .372.116.717.314 1a1.742 1.742 0 00-.314 1v4c0 .966.784 1.75 1.75 1.75h12.5A1.75 1.75 0 0016 12.75v-4c0-.372-.116-.717-.314-1 .198-.283.314-.628.314-1v-4A1.75 1.75 0 0014.25 1H1.75zm0 7.5a.25.25 0 00-.25.25v4c0 .138.112.25.25.25h12.5a.25.25 0 00.25-.25v-4a.25.25 0 00-.25-.25H1.75zM1.5 2.75a.25.25 0 01.25-.25h12.5a.25.25 0 01.25.25v4a.25.25 0 01-.25.25H1.75a.25.25 0 01-.25-.25v-4zm5.5 2A.75.75 0 017.75 4h4.5a.75.75 0 010 1.5h-4.5A.75.75 0 017 4.75zM7.75 10a.75.75 0 000 1.5h4.5a.75.75 0 000-1.5h-4.5zM3 4.75A.75.75 0 013.75 4h.5a.75.75 0 010 1.5h-.5A.75.75 0 013 4.75zM3.75 10a.75.75 0 000 1.5h.5a.75.75 0 000-1.5h-.5z"></path>
</svg>`}
/>
<ListItemTemplate
text="Pages"
href="/"
leadingVisualSize="ActionList-content--visual16"
leadingVisual={`<svg aria-hidden="true" height="16" viewBox="0 0 16 16" version="1.1" width="16" data-view-component="true" class="octicon octicon-file">
<path fill-rule="evenodd" d="M3.75 1.5a.25.25 0 00-.25.25v11.5c0 .138.112.25.25.25h8.5a.25.25 0 00.25-.25V6H9.75A1.75 1.75 0 018 4.25V1.5H3.75zm5.75.56v2.19c0 .138.112.25.25.25h2.19L9.5 2.06zM2 1.75C2 .784 2.784 0 3.75 0h5.086c.464 0 .909.184 1.237.513l3.414 3.414c.329.328.513.773.513 1.237v8.086A1.75 1.75 0 0112.25 15h-8.5A1.75 1.75 0 012 13.25V1.75z"></path>
</svg>`}
/>
</>
}
/>
}
/>
<DividerTemplate />
<DividerTemplate title="Security" id="group-id-3" />
<ListItemTemplate
containsSubItem
children={
<ListTemplate
subGroup
role="menu"
ariaLabel="Sub nav decription"
ariaLabelledBy="group-id-3"
children={
<>
<ListItemTemplate
text="Code security and analysis"
href="/"
leadingVisualSize="ActionList-content--visual16"
leadingVisual={`<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16" width="16" height="16"><path fill-rule="evenodd" d="M8.533.133a1.75 1.75 0 00-1.066 0l-5.25 1.68A1.75 1.75 0 001 3.48V7c0 1.566.32 3.182 1.303 4.682.983 1.498 2.585 2.813 5.032 3.855a1.7 1.7 0 001.33 0c2.447-1.042 4.049-2.357 5.032-3.855C14.68 10.182 15 8.566 15 7V3.48a1.75 1.75 0 00-1.217-1.667L8.533.133zm-.61 1.429a.25.25 0 01.153 0l5.25 1.68a.25.25 0 01.174.238V7c0 1.358-.275 2.666-1.057 3.86-.784 1.194-2.121 2.34-4.366 3.297a.2.2 0 01-.154 0c-2.245-.956-3.582-2.104-4.366-3.298C2.775 9.666 2.5 8.36 2.5 7V3.48a.25.25 0 01.174-.237l5.25-1.68zM9.5 6.5a1.5 1.5 0 01-.75 1.3v2.45a.75.75 0 01-1.5 0V7.8A1.5 1.5 0 119.5 6.5z"></path></svg>`}
/>
<ListItemTemplate
text="Deploy keys"
href="/"
leadingVisualSize="ActionList-content--visual16"
leadingVisual={`<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16" width="16" height="16"><path fill-rule="evenodd" d="M6.5 5.5a4 4 0 112.731 3.795.75.75 0 00-.768.18L7.44 10.5H6.25a.75.75 0 00-.75.75v1.19l-.06.06H4.25a.75.75 0 00-.75.75v1.19l-.06.06H1.75a.25.25 0 01-.25-.25v-1.69l5.024-5.023a.75.75 0 00.181-.768A3.995 3.995 0 016.5 5.5zm4-5.5a5.5 5.5 0 00-5.348 6.788L.22 11.72a.75.75 0 00-.22.53v2C0 15.216.784 16 1.75 16h2a.75.75 0 00.53-.22l.5-.5a.75.75 0 00.22-.53V14h.75a.75.75 0 00.53-.22l.5-.5a.75.75 0 00.22-.53V12h.75a.75.75 0 00.53-.22l.932-.932A5.5 5.5 0 1010.5 0zm.5 6a1 1 0 100-2 1 1 0 000 2z"></path></svg>`}
/>
<ListItemTemplate
text="Secrets"
href="/"
leadingVisualSize="ActionList-content--visual16"
leadingVisual={`<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16" width="16" height="16"><path fill-rule="evenodd" d="M0 2.75A2.75 2.75 0 012.75 0h10.5A2.75 2.75 0 0116 2.75v10.5A2.75 2.75 0 0113.25 16H2.75A2.75 2.75 0 010 13.25V2.75zM2.75 1.5c-.69 0-1.25.56-1.25 1.25v10.5c0 .69.56 1.25 1.25 1.25h10.5c.69 0 1.25-.56 1.25-1.25V2.75c0-.69-.56-1.25-1.25-1.25H2.75z"></path><path d="M8 4a.75.75 0 01.75.75V6.7l1.69-.975a.75.75 0 01.75 1.3L9.5 8l1.69.976a.75.75 0 01-.75 1.298L8.75 9.3v1.951a.75.75 0 01-1.5 0V9.299l-1.69.976a.75.75 0 01-.75-1.3L6.5 8l-1.69-.975a.75.75 0 01.75-1.3l1.69.976V4.75A.75.75 0 018 4z"></path></svg>`}
/>
</>
}
/>
}
/>
</>
)
}
RepoSettings.decorators = [
Story => (
<nav>
<Story />
</nav>
)
]
export const DiscussionsPane = ListTemplate.bind({})
DiscussionsPane.storyName = 'Discussions pane';
DiscussionsPane.args = {
...ListTemplate.args,
...ListItemTemplate.args,
ariaLabel: 'Categories',
role: 'menu',
showDividers: false,
children: (
<>
<ListItemTemplate
text="All discussions"
href="/"
ariaCurrent={true}
leadingVisualSize="ActionList-content--visual16"
leadingVisual={`<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16" width="16" height="16"><path fill-rule="evenodd" d="M1.5 2.75a.25.25 0 01.25-.25h8.5a.25.25 0 01.25.25v5.5a.25.25 0 01-.25.25h-3.5a.75.75 0 00-.53.22L3.5 11.44V9.25a.75.75 0 00-.75-.75h-1a.25.25 0 01-.25-.25v-5.5zM1.75 1A1.75 1.75 0 000 2.75v5.5C0 9.216.784 10 1.75 10H2v1.543a1.457 1.457 0 002.487 1.03L7.061 10h3.189A1.75 1.75 0 0012 8.25v-5.5A1.75 1.75 0 0010.25 1h-8.5zM14.5 4.75a.25.25 0 00-.25-.25h-.5a.75.75 0 110-1.5h.5c.966 0 1.75.784 1.75 1.75v5.5A1.75 1.75 0 0114.25 12H14v1.543a1.457 1.457 0 01-2.487 1.03L9.22 12.28a.75.75 0 111.06-1.06l2.22 2.22v-2.19a.75.75 0 01.75-.75h1a.25.25 0 00.25-.25v-5.5z"></path></svg>`}
/>
<DividerTemplate />
<ListItemTemplate
text="Announcements"
href="/"
leadingVisualSize="ActionList-content--visual16"
leadingVisual={`🔔`}
/>
<ListItemTemplate
text="General"
href="/"
leadingVisualSize="ActionList-content--visual16"
leadingVisual={`💬`}
/>
<ListItemTemplate
text="Ideas"
href="/"
leadingVisualSize="ActionList-content--visual16"
leadingVisual={`🏮`}
/>
<ListItemTemplate
text="Questions and answers"
href="/"
leadingVisualSize="ActionList-content--visual16"
leadingVisual={`🌻`}
/>
<ListItemTemplate
text="Show and tell"
href="/"
leadingVisualSize="ActionList-content--visual16"
leadingVisual={`👋`}
/>
<ListItemTemplate
text="Polls"
href="/"
leadingVisualSize="ActionList-content--visual16"
leadingVisual={`🍒`}
/>
</>
)
};
DiscussionsPane.decorators = [
Story => (
<nav>
<Story />
</nav>
)
]

View File

@ -152,7 +152,7 @@ NavWithSubItemsLeadingVisual16px.args = {
children={
<ListTemplate
subGroup
ariaLabel="Sub nav descrioption"
ariaLabel="Sub nav decription"
children={
<>
<ListItemTemplate subItem text="Sub Nav Item" href="/" listSemantic ariaLevel="2" />
@ -236,7 +236,7 @@ NavWithSubItemsLeadingVisual20px.args = {
children={
<ListTemplate
subGroup
ariaLabel="Sub nav descrioption"
ariaLabel="Sub nav decription"
children={
<>
<ListItemTemplate subItem text="Sub Nav Item" href="/" listSemantic ariaLevel="2" />
@ -328,7 +328,7 @@ NavWithSubItemsLeadingVisual24px.args = {
children={
<ListTemplate
subGroup
ariaLabel="Sub nav descrioption"
ariaLabel="Sub nav decription"
children={
<>
<ListItemTemplate subItem text="Sub Nav Item" href="/" listSemantic ariaLevel="2" />
@ -671,7 +671,7 @@ NavWithSubItemsLeadingVisual16pxSubSections.args = {
<ListTemplate
subGroup
ariaLabelledBy="group-id-1"
ariaLabel="Sub nav descrioption"
ariaLabel="Sub nav decription"
children={
<>
<ListItemTemplate
@ -701,7 +701,7 @@ NavWithSubItemsLeadingVisual16pxSubSections.args = {
containsSubItem
containsActiveSubItem
subGroup
ariaLabel="Sub nav descrioption"
ariaLabel="Sub nav decription"
children={
<>
<ListItemTemplate
@ -740,7 +740,7 @@ NavWithSubItemsLeadingVisual16pxSubSections.args = {
children={
<ListTemplate
subGroup
ariaLabel="Sub nav descrioption"
ariaLabel="Sub nav decription"
ariaLabelledBy="group-id-2"
children={
<>

View File

@ -0,0 +1,204 @@
import React from 'react'
import clsx from 'clsx'
export default {
title: 'Components/Layout/Alpha',
excludeStories: ['LayoutAlphaTemplate'],
argTypes: {
container: {
control: { type: 'select' },
options: ['fluid', 'md', 'lg', 'xl'],
control: {
type: 'inline-radio'
},
description: 'Wrapper around the entire component to define an optional maximum width.',
table: {
category: 'CSS'
}
},
hasDivider: {
control: { type: 'boolean' },
description: 'Whether to show a pane line divider.',
table: {
category: 'CSS'
}
},
gutter: {
options: ['default', 'none', 'condensed', 'spacious'],
control: {
type: 'inline-radio'
},
description: 'Sets the gap between columns.',
table: {
category: 'CSS'
}
},
sidebarPosition: {
options: ['start', 'end'],
control: {
type: 'inline-radio'
},
description: 'Sets the position of the sidebar.',
table: {
category: 'CSS'
}
},
sidebarWidth: {
options: ['default', 'narrow', 'wide'],
control: {
type: 'inline-radio'
},
description: 'Sets the width of the sidebar.',
table: {
category: 'CSS'
}
},
mainWidth: {
options: ['fluid', 'md', 'lg', 'xl'],
control: {
type: 'inline-radio'
},
description: 'Sets the width of the main content area.',
table: {
category: 'CSS'
}
},
flowRowUntil: {
options: ['sm', 'md', 'lg'],
control: {
type: 'inline-radio',
},
description: 'Sets the maximum breakpoint at which the layout will flow as row.',
table: {
category: 'CSS'
}
},
mainChildren: {
description: 'creates a slot for main children',
table: {
category: 'HTML'
}
},
sidebarChildren: {
description: 'creates a slot for sidebar children',
table: {
category: 'HTML'
}
},
}
}
// build every component case here in the template (private api)
export const LayoutAlphaTemplate = ({
container,
hasDivider,
gutter,
sidebarPosition,
sidebarWidth,
mainWidth,
flowRowUntil,
mainChildren,
sidebarChildren
}) => {
// Default values
container = container ?? 'xl';
hasDivider = hasDivider ?? false;
gutter = gutter ?? 'default';
sidebarPosition = sidebarPosition ?? 'end';
sidebarWidth = sidebarWidth ?? 'default';
mainWidth = mainWidth ?? 'full';
flowRowUntil = flowRowUntil ?? 'md';
// Leave `null` values for states that don't require a modifier class
container = (container === 'full') ? null : container;
hasDivider = (hasDivider === false) ? null : hasDivider;
gutter = (gutter === 'default') ? null : gutter;
sidebarWidth = (sidebarWidth === 'default') ? null : sidebarWidth;
mainWidth = (mainWidth === 'full') ? null : mainWidth;
flowRowUntil = (flowRowUntil === 'sm') ? null : flowRowUntil;
return (
<div
// use clsx for multiple classnames
className={clsx(
'Layout',
container && 'container-' + `${container}`,
gutter && 'Layout--gutter-' + `${gutter}`,
sidebarPosition && 'Layout--sidebarPosition-' + `${sidebarPosition}`,
sidebarWidth && 'Layout--sidebar-' + `${sidebarWidth}`,
hasDivider && 'Layout--divided',
flowRowUntil && '' + 'Layout--flowRow-until-' + `${flowRowUntil}`
)}
// use undefined for values that shouldn't be set if false
aria-hidden={hasDivider ? 'true' : undefined}
>
{/* use {children} for wrapper component templates */}
<>
<div className="Layout-main">
{mainWidth ? (
<>
<div className={'Layout-main-centered-' + mainWidth}>
<div className={clsx( mainWidth && 'container-' + mainWidth)}>
{mainChildren}
</div>
</div>
</>
) : (
<>
{mainChildren}
</>
)}
</div>
<div className="Layout-divider"></div>
<div className="Layout-sidebar">{sidebarChildren}</div>
</>
</div>
);
};
const sidebarPlaceholder =
<>
<div style={
{
width: '100%',
height: '100%',
backgroundColor: '#DDF4FF',
border: '1px solid #80CCFF',
padding: '16px',
borderRadius: '6px'
}
}>
sidebar
</div>
</>;
const mainPlaceholder =
<>
<div style={
{
width: '100%',
height: '100%',
backgroundColor: '#FFEFF7',
border: '1px solid #FFADDA',
padding: '16px',
borderRadius: '6px'
}
}>
main
</div>
</>;
// create a "playground" demo page that may set some defaults and allow story to access component controls
export const Playground = LayoutAlphaTemplate.bind({})
Playground.args = {
container: 'full',
hasDivider: false,
gutter: 'default',
sidebarPosition: 'end',
sidebarWidth: 'default',
mainWidth: 'full',
flowRowUntil: 'md',
mainChildren: mainPlaceholder,
sidebarChildren: sidebarPlaceholder
}

View File

@ -0,0 +1,502 @@
import React from 'react'
import clsx from 'clsx'
import {NavWithSubItems} from '../ActionList/ActionListPatterns.stories'
import PageLayoutBehavior from '../../helpers/pageLayoutBehavior.jsx'
export default {
title: 'Components/Layout/Beta',
excludeStories: ['LayoutTemplate'],
argTypes: {
// Debug
_debug: {
control: 'boolean',
description: 'Show background colors in regions for debugging',
},
// Structure
wrapperSizing: {
options: ['fluid', 'md', 'lg', 'xl'],
control: {
type: 'inline-radio',
labels: ['fluid', 'md', 'lg', 'xl']
},
description: 'Define the maximum width of the component. `fluid` sets it to full-width. Other values center `Layout` horizontally. Refer to [container utilities](https://primer.style/css/objects/grid#containers) for reference.',
table: {
category: 'Structure'
}
},
outerSpacing: {
options: ['none', 'normal', 'condensed'],
control: {
type: 'inline-radio'
},
description: 'Sets wrapper margins surrounding the component to distance itself from the viewport edges. `normal` sets the margin to 16px, and to 24px on `lg` breakpoints and above. `condensed` keeps the margin at 16px. `none` sets the margin to 0.',
table: {
category: 'Structure'
}
},
innerSpacing: {
options: ['none', 'normal', 'condensed'],
control: {
type: 'inline-radio'
},
description: 'Sets padding to regions individually. `normal` sets padding to 16px, with the `content` region getting 24px horizontal padding on `lg` breakpoints and above. `condensed` keeps the padding always at `16px`. `none` sets the padding to 0.',
table: {
category: 'Structure'
}
},
columnGap: {
options: ['none', 'normal', 'condensed'],
control: {
type: 'inline-radio'
},
description: 'Sets the gap between columns to distance them from each other. `normal` sets the gap to 16px, and to 24px on `lg` breakpoints and above. `condensed` keeps the gap always at 16px. `none` sets the gap to 0.',
table: {
category: 'Structure'
}
},
rowGap: {
options: ['none', 'normal', 'condensed'],
control: {
type: 'inline-radio'
},
description: 'Sets the gap below the header and above the footer. `normal` sets the gap to 16px, and to 24px on `lg` breakpoints and above. `condensed` keeps the gap always at 16px. `none` sets the gap to 0.',
table: {
category: 'Structure'
}
},
// Responsive
responsiveVariant: {
options: ['stackRegions', 'separateRegions'],
control: {
type: 'inline-radio'
},
description: '`responsiveVariant` defines how the layout component adapts to smaller viewports. `stackRegions` presents the content in a vertical flow, with `pane` and `content` vertically arranged. `separateRegions` presents `pane` and `content` as different pages on smaller viewports. Change the preview size from the toolbar to test it.',
table: {
category: 'Responsive'
}
},
primaryRegion: {
options: ['content', 'pane'],
control: {
type: 'inline-radio'
},
description: 'When `responsiveVariant` is set to `separateRegions`, defines which region appears first on small viewports. `content` is default.',
table: {
category: 'Responsive'
}
},
// Pane
paneWidth: {
options: ['default', 'narrow', 'wide'],
control: {
type: 'inline-radio'
},
description: 'Defines the width of the pane',
table: {
category: 'Pane'
}
},
panePosition: {
options: ['start', 'end'],
control: {
type: 'inline-radio'
},
description: 'Defines the position of the pane. `start` renders the pane on the left, and `end` renders it on the right.',
table: {
category: 'Pane',
}
},
panePositionNarrow: {
options: ['inherit', 'start', 'end'],
control: {
type: 'inline-radio',
},
description: 'If `responsiveVariant` is set to `stackRegions`, defines the position of the pane in narrow viewports. `start` puts the pane above `content`, and `end` puts it below `content`. `inherit` uses the same value from `panePosition`.',
table: {
category: 'Pane'
}
},
hasPaneDivider: {
control: { type: 'boolean' },
description: 'Whether to show a pane line divider.',
table: {
category: 'Pane'
}
},
paneDividerNarrow: {
options: ['inherit', 'none', 'line', 'filled'],
control: {
type: 'inline-radio'
},
description: 'Whether to show a divider between `pane` and `content` regions if `responsiveVariant` is set to `stackRegions`. `line` shows a single line. `filled` shows a thicker mobile-frienldy divider.',
table: {
category: 'Pane'
}
},
paneIsSticky: {
control: { type: 'boolean' },
description: 'Whether to make the pane sticky.',
table: {
category: 'Pane'
}
},
// Content
contentWidth: {
options: ['fluid', 'sm', 'md', 'lg', 'xl'],
control: {
type: 'inline-radio'
},
description: 'Defines the maximum width of the content region. `fluid` sets it to full-width. Other values follow container widths from `sm` to `xl`. With smaller widths, the content region will try to stay centered to the viewport area.',
table: {
category: 'Content'
}
},
// Header
hasHeader: {
control: { type: 'boolean' },
table: {
category: 'Header'
}
},
hasHeaderDivider: {
control: { type: 'boolean' },
description: 'Whether to show a header divider.',
table: {
category: 'Header'
}
},
headerDividerNarrow: {
options: ['inherit', 'none', 'line', 'filled'],
control: {
type: 'inline-radio'
},
description: 'Defines how the `header` divider should look on narrow viewports. `inherit` renders a `line` if `hasHeaderDivider` is true. `filled` shows a thicker mobile-friendly divider.',
table: {
category: 'Header'
}
},
// Footer
hasFooter: {
control: { type: 'boolean' },
table: {
category: 'Footer'
}
},
hasFooterDivider: {
control: { type: 'boolean' },
description: 'Whether to show a footer divider.',
table: {
category: 'Footer'
}
},
footerDividerNarrow: {
options: ['inherit', 'none', 'line', 'filled'],
control: {
type: 'inline-radio'
},
description: 'Whether to show a divider above the `footer` region on narrow viewports. `line` shows a single line. `filled` shows a thicker mobile-frienldy divider.',
table: {
category: 'Footer'
}
},
// HTML
headerChildren: {
description: 'creates a slot for header children',
table: {
category: 'HTML'
}
},
contentChildren: {
description: 'creates a slot for content children',
table: {
category: 'HTML'
}
},
paneChildren: {
description: 'creates a slot for pane children',
table: {
category: 'HTML'
}
},
footerChildren: {
description: 'creates a slot for footer children',
table: {
category: 'HTML'
}
}
}
}
const layoutClassName = 'PageLayout';
// build every component case here in the template (private api)
export const LayoutTemplate = ({
// Debug
_debug,
// Wrapper
wrapperSizing,
// Spacing and borders
outerSpacing,
innerSpacing,
columnGap,
rowGap,
// Pane
paneWidth,
panePosition,
panePositionNarrow,
hasPaneDivider,
paneDividerNarrow,
paneIsSticky,
// Header
hasHeader,
hasHeaderDivider,
headerDividerNarrow,
// Footer
hasFooter,
hasFooterDivider,
footerDividerNarrow,
// Content
contentWidth,
// Responsive
responsiveVariant,
primaryRegion,
// Children
headerChildren,
contentChildren,
paneChildren,
footerChildren
}) => {
const containerClass = {
'full': '',
'md': 'container-md',
'lg': 'container-lg',
'xl': 'container-xl'
};
// Default values
wrapperSizing = wrapperSizing ?? 'xl';
outerSpacing = outerSpacing ?? 'normal';
innerSpacing = innerSpacing ?? 'none';
columnGap = columnGap ?? 'normal';
rowGap = rowGap ?? 'normal';
panePosition = panePosition ?? 'end';
panePositionNarrow = panePositionNarrow ?? 'inherit';
responsiveVariant = responsiveVariant ?? 'stackRegions';
primaryRegion = primaryRegion ?? 'content';
// Leave `null` values for states that don't require a modifier class
outerSpacing = (outerSpacing === 'none') ? null : outerSpacing;
innerSpacing = (innerSpacing === 'none') ? null : innerSpacing;
paneWidth = (paneWidth === 'default') ? null : paneWidth;
contentWidth = (contentWidth === 'fluid') ? null : contentWidth;
headerDividerNarrow = (headerDividerNarrow === 'none') ? null : headerDividerNarrow;
footerDividerNarrow = (footerDividerNarrow === 'none') ? null : footerDividerNarrow;
// Inherit value for responsive props
panePositionNarrow = (panePositionNarrow === 'inherit') ? panePosition : panePositionNarrow;
if (hasPaneDivider) {
paneDividerNarrow = (paneDividerNarrow === 'inherit') ? 'line' : paneDividerNarrow;
} else {
paneDividerNarrow = null;
}
if (hasHeaderDivider) {
headerDividerNarrow = (headerDividerNarrow === 'inherit') ? 'line' : headerDividerNarrow;
} else {
headerDividerNarrow = null;
}
if (hasFooterDivider) {
footerDividerNarrow = (footerDividerNarrow === 'inherit') ? 'line' : footerDividerNarrow;
} else {
footerDividerNarrow = null;
}
PageLayoutBehavior();
return (
<>
<div
className={clsx(
layoutClassName,
outerSpacing && layoutClassName + '--outerSpacing-' + `${outerSpacing}`,
innerSpacing && layoutClassName + '--innerSpacing-' + `${innerSpacing}`,
columnGap && layoutClassName + '--columnGap-' + `${columnGap}`,
rowGap && layoutClassName + '--rowGap-' + `${rowGap}`,
paneWidth && layoutClassName + '--paneWidth-' + `${paneWidth}`,
panePosition && layoutClassName + '--panePos-' + `${panePosition}`,
hasPaneDivider && layoutClassName + '--hasPaneDivider',
paneIsSticky && layoutClassName + '--isPaneSticky',
layoutClassName + '--responsive-' + `${responsiveVariant}`,
responsiveVariant === 'separateRegions' && layoutClassName + '--responsive-primary-' + `${primaryRegion}`,
responsiveVariant === 'stackRegions' && panePositionNarrow && layoutClassName + '--responsive-panePos-' + `${panePositionNarrow}`,
)}
>
<div className={clsx(
layoutClassName + '-wrapper',
wrapperSizing && containerClass[wrapperSizing]
)}>
{/* Header */}
{hasHeader &&
<div className={clsx(
layoutClassName + '-region',
layoutClassName + '-header',
hasHeaderDivider && layoutClassName + '-header--hasDivider',
headerDividerNarrow && layoutClassName + '-region--dividerNarrow-' + headerDividerNarrow + '-after'
)}>
{headerChildren}
</div>
}
<div className={clsx(
layoutClassName + '-columns'
)}>
{/* pane if rendered first */}
{panePosition === 'start' &&
<div className={clsx(
layoutClassName + '-region',
layoutClassName + '-pane',
paneDividerNarrow && layoutClassName + '-region--dividerNarrow-' + paneDividerNarrow + (panePositionNarrow === 'start' ? '-after' : '-before')
)}>
{paneChildren}
</div>
}
{/* content */}
<div className={clsx(
layoutClassName + '-region',
layoutClassName + '-content'
)}>
{contentWidth ? (
<>
<div className={layoutClassName + '-content-centered-' + contentWidth}>
<div className={'container-' + contentWidth}>
{contentChildren}
</div>
</div>
</>
) : (
<>
{contentChildren}
</>
)}
</div>
{/* pane if rendered last */}
{panePosition === 'end' &&
<div className={clsx(
layoutClassName + '-region',
layoutClassName + '-pane',
paneDividerNarrow && layoutClassName + '-region--dividerNarrow-' + paneDividerNarrow + (panePositionNarrow === 'start' ? '-after' : '-before')
)}>
{paneChildren}
</div>}
</div>
{/* footer */}
{hasFooter && <div className={clsx(
layoutClassName + '-region',
layoutClassName + '-footer',
hasFooterDivider && layoutClassName + '-footer--hasDivider',
footerDividerNarrow && layoutClassName + '-region--dividerNarrow-' + footerDividerNarrow + '-before'
)}>{footerChildren}</div>}
</div>
{/* debug */}
{_debug &&
<style type='text/css'>{`
.PageLayout-header {
background: lightpink;
}
.PageLayout-content {
background: rgb(255, 197, 253);
}
.PageLayout-pane {
background: rgb(215, 255, 233);
}
.PageLayout-footer {
background: lightyellow;
}
`}</style>
}
</div>
</>
);
};
export const Playground = LayoutTemplate.bind({});
Playground.storyName = 'Playground';
Playground.parameters = {
layout: 'fullscreen',
};
Playground.args = {
_debug: true,
wrapperSizing: 'xl',
outerSpacing: 'normal',
innerSpacing: 'none',
columnGap: 'normal',
rowGap: 'normal',
responsiveVariant: 'stackRegions',
primaryRegion: 'content',
paneWidth: 'default',
panePosition: 'end',
panePositionNarrow: 'inherit',
hasPaneDivider: false,
paneDividerNarrow: 'inherit',
paneIsSticky: false,
contentWidth: 'fluid',
hasHeader: true,
hasHeaderDivider: false,
headerDividerNarrow: 'inherit',
hasFooter: true,
hasFooterDivider: false,
footerDividerNarrow: 'inherit',
contentChildren: 'content',
paneChildren: 'pane',
headerChildren: 'header',
footerChildren: 'footer'
}

View File

@ -0,0 +1,167 @@
import React from 'react'
import clsx from 'clsx'
import { PageLayoutTemplate } from './PageLayout.stories'
import {SplitPageLayoutTemplate} from './SplitPageLayout.stories'
import {RepoSettings, DiscussionsPane} from '../ActionList/ActionListExamples.stories'
import {LayoutAlphaTemplate} from './LayoutAlpha.stories'
export default {
title: 'Components/Layout/Beta/Examples'
}
export const Settings = SplitPageLayoutTemplate.bind({});
Settings.storyName = 'Settings';
Settings.parameters = {
layout: 'fullscreen',
};
Settings.args = {
// Structure
innerSpacing: 'normal',
// Responsive
primaryRegion: 'pane',
// Pane
paneWidth: 'wide',
paneIsSticky: true,
// Content
contentWidth: 'md',
paneChildren: (
<>
<h2 className="h3 ml-2 mr-2">Repository settings</h2>
<div className="ml-n2 mr-n2">
<RepoSettings {...RepoSettings.args} />
</div>
</>
),
contentChildren: (
<>
<h3 className="f3 text-normal">General</h3>
<div className="Box mt-3 p-3" style={{minHeight: '200px'}}> </div>
<div className="Box mt-3 p-3" style={{minHeight: '400px'}}> </div>
<div className="Box mt-3 p-3" style={{minHeight: '600px'}}> </div>
</>
)
};
export const Discussions = PageLayoutTemplate.bind({});
Discussions.parameters = {
layout: 'fullscreen',
};
Discussions.storyName = 'Discussions';
Discussions.args = {
responsiveVariant: 'separateRegions',
panePosition: 'start',
paneWidth: 'narrow',
hasHeader: true,
headerChildren: (
<>
<div class="d-block d-md-none">
<div className="d-flex flex-items-center width-full flex-wrap" style={{gap: '16px'}}>
<h2 className="h3 ml-2 mr-2 flex-1">All discussions<span class="no-wrap">
{/*
I'm using a `no-wrap` class between the heading and the `triangle-down`
octicon to make sure it won't be ever rendered as a typographic widow
*/}
</span><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16" width="16" height="16"><path d="M4.427 7.427l3.396 3.396a.25.25 0 00.354 0l3.396-3.396A.25.25 0 0011.396 7H4.604a.25.25 0 00-.177.427z"></path></svg>
</h2>
<button class="btn btn-primary">New</button>
<button class="btn btn-octicon" style={{margin: 0, border: '1px solid var(--color-border-default)', width: '32px', height: '32px'}}>
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16" width="16" height="16"><path d="M8 9a1.5 1.5 0 100-3 1.5 1.5 0 000 3zM1.5 9a1.5 1.5 0 100-3 1.5 1.5 0 000 3zm13 0a1.5 1.5 0 100-3 1.5 1.5 0 000 3z"></path></svg>
</button>
</div>
<div className="mt-3">
<input className="form-control flex-1 width-full" placeholder="Search discussions" />
</div>
</div>
<div className="hide-md hide-sm">
<div className="d-flex flex-items-center width-full flex-wrap" style={{gap: '8px'}}>
<input className="form-control flex-1" placeholder="Search discussions" />
<div class="BtnGroup d-block">
<button class="BtnGroup-item btn" type="button">New</button>
<button class="BtnGroup-item btn" type="button">Top</button>
</div>
<button class="btn">Labels</button>
<button class="btn btn-primary">New discussion</button>
</div>
</div>
</>
),
paneChildren: (
<>
<div className="ml-n2 mr-n2 mt-n2 mb-n2">
<DiscussionsPane {...DiscussionsPane.args} />
</div>
</>
),
contentChildren: (
<>
<LayoutAlphaTemplate
container='xl'
sidebarPosition='end'
sidebarWidth='narrow'
flowRowUntil='lg'
mainChildren={
<>
<div className="Box p-3" style={{minHeight: '800px'}}> </div>
</>
}
sidebarChildren={
<>
<div className="Box p-3" style={{minHeight: '200px'}}> </div>
<div className="Box mt-3 p-3" style={{minHeight: '200px'}}> </div>
</>
}
/>
</>
)
};
export const IssueDetail = PageLayoutTemplate.bind({});
IssueDetail.storyName = 'Issue detail';
IssueDetail.parameters = {
layout: 'fullscreen',
};
IssueDetail.args = {
panePosition: 'end',
paneWidth: 'default',
hasHeader: true,
hasHeaderDivider: true,
headerDividerNarrow: 'filled',
paneDividerNarrow: 'filled',
headerChildren: (
<>
<h2 className="f2">
Traverse does not calculate scope in object deconstructor
<span className="f2-light color-fg-muted"> #14024</span>
</h2>
<div className="d-flex flex-items-center flex-wrap mt-2" style={{gap: '8px'}}>
<span title="Status: Open" class="State State--open">
<svg height="16" class="octicon octicon-issue-opened" viewBox="0 0 16 16" version="1.1" width="16" aria-hidden="true"><path d="M8 9.5a1.5 1.5 0 100-3 1.5 1.5 0 000 3z"></path><path fill-rule="evenodd" d="M8 0a8 8 0 100 16A8 8 0 008 0zM1.5 8a6.5 6.5 0 1113 0 6.5 6.5 0 01-13 0z"></path></svg> Open
</span>
<span className="color-fg-muted"><strong class="color-fg-default">monalisa</strong> opened this issue 6 days ago</span>
</div>
</>
),
contentChildren: (
<>
<div className="Box p-3" style={{minHeight: '1200px'}}> </div>
</>
),
paneChildren: (
<>
<div className="Box p-3" style={{minHeight: '140px'}}> </div>
<div className="Box p-3 mt-3" style={{minHeight: '200px'}}> </div>
<div className="Box p-3 mt-3" style={{minHeight: '140px'}}> </div>
</>
)
};

View File

@ -0,0 +1,342 @@
// create a "playground" demo page that may set some defaults and allow story to access component controls
import React from 'react'
import clsx from 'clsx'
import {LayoutTemplate} from './LayoutBeta.stories'
export default {
title: 'Components/Layout/Beta/PageLayout',
excludeStories: ['PageLayoutTemplate'],
argTypes: {
// Debug
_debug: {
control: 'boolean',
description: 'Show background colors in regions for debugging',
},
// Structure
wrapperSizing: {
options: ['fluid', 'md', 'lg', 'xl'],
control: {
type: 'inline-radio',
labels: ['fluid', 'md', 'lg', 'xl']
},
description: 'Define the maximum width of the component. `fluid` sets it to full-width. Other values center `Layout` horizontally. Refer to [container utilities](https://primer.style/css/objects/grid#containers) for reference.',
table: {
category: 'Structure'
}
},
outerSpacing: {
options: ['normal', 'condensed'],
control: {
type: 'inline-radio'
},
description: 'Sets wrapper margins surrounding the component to distance itself from the viewport edges. `normal` sets the margin to 16px, and to 24px on `lg` breakpoints and above. `condensed` keeps the margin at 16px.',
table: {
category: 'Structure'
}
},
columnGap: {
options: ['normal', 'condensed'],
control: {
type: 'inline-radio'
},
description: 'Sets the gap between columns to distance them from each other. `normal` sets the gap to 16px, and to 24px on `lg` breakpoints and above. `condensed` keeps the gap always at 16px.',
table: {
category: 'Structure'
}
},
rowGap: {
options: ['normal', 'condensed'],
control: {
type: 'inline-radio'
},
description: 'Sets the gap below the header and above the footer. `normal` sets the gap to 16px, and to 24px on `lg` breakpoints and above. `condensed` keeps the gap always at 16px.',
table: {
category: 'Structure'
}
},
// Responsive
responsiveVariant: {
options: ['stackRegions', 'separateRegions'],
control: {
type: 'inline-radio'
},
description: '`responsiveVariant` defines how the layout component adapts to smaller viewports. `stackRegions` presents the content in a vertical flow, with `pane` and `content` vertically arranged. `separateRegions` presents `pane` and `content` as different pages on smaller viewports. Change the preview size from the toolbar to test it.',
table: {
category: 'Responsive'
}
},
primaryRegion: {
options: ['content', 'pane'],
control: {
type: 'inline-radio'
},
description: 'When `responsiveVariant` is set to `separateRegions`, defines which region appears first on small viewports. `content` is default.',
table: {
category: 'Responsive'
}
},
// Pane
panePosition: {
options: ['start', 'end'],
control: {
type: 'inline-radio'
},
description: 'Defines the position of the pane. `start` renders the pane on the left, and `end` renders it on the right.',
table: {
category: 'Pane',
}
},
panePositionNarrow: {
options: ['inherit', 'start', 'end'],
control: {
type: 'inline-radio',
},
description: 'If `responsiveVariant` is set to `stackRegions`, defines the position of the pane in narrow viewports. `start` puts the pane above `content`, and `end` puts it below `content`. `inherit` uses the same value from `panePosition`.',
table: {
category: 'Pane'
}
},
paneWidth: {
options: ['default', 'narrow', 'wide'],
control: {
type: 'inline-radio'
},
description: 'Defines the width of the pane',
table: {
category: 'Pane'
}
},
hasPaneDivider: {
control: { type: 'boolean' },
description: 'Whether to show a pane line divider.',
table: {
category: 'Pane'
}
},
paneDividerNarrow: {
options: ['inherit', 'none', 'line', 'filled'],
control: {
type: 'inline-radio'
},
description: 'Whether to show a divider between `pane` and `content` regions if `responsiveVariant` is set to `stackRegions`. `line` shows a single line. `filled` shows a thicker mobile-frienldy divider.',
table: {
category: 'Pane'
}
},
// Content
contentWidth: {
options: ['fluid', 'sm', 'md', 'lg', 'xl'],
control: {
type: 'inline-radio'
},
description: 'Defines the maximum width of the content region. `fluid` sets it to full-width. Other values follow container widths from `sm` to `xl`. With smaller widths, the content region will try to stay centered to the viewport area.',
table: {
category: 'Content'
}
},
// Header
hasHeader: {
control: { type: 'boolean' },
table: {
category: 'Header'
}
},
hasHeaderDivider: {
control: { type: 'boolean' },
description: 'Whether to show a header divider.',
table: {
category: 'Header'
}
},
headerDividerNarrow: {
options: ['inherit', 'none', 'line', 'filled'],
control: {
type: 'inline-radio'
},
description: 'Defines how the `header` divider should look on narrow viewports. `inherit` renders a `line` if `hasHeaderDivider` is true. `filled` shows a thicker mobile-friendly divider.',
table: {
category: 'Header'
}
},
// Footer
hasFooter: {
control: { type: 'boolean' },
table: {
category: 'Footer'
}
},
hasFooterDivider: {
control: { type: 'boolean' },
description: 'Whether to show a footer divider.',
table: {
category: 'Footer'
}
},
footerDividerNarrow: {
options: ['inherit', 'none', 'line', 'filled'],
control: {
type: 'inline-radio'
},
description: 'Whether to show a divider above the `footer` region on narrow viewports. `line` shows a single line. `filled` shows a thicker mobile-frienldy divider.',
table: {
category: 'Footer'
}
},
// HTML
headerChildren: {
description: 'creates a slot for header children',
table: {
category: 'HTML'
}
},
contentChildren: {
description: 'creates a slot for content children',
table: {
category: 'HTML'
}
},
paneChildren: {
description: 'creates a slot for pane children',
table: {
category: 'HTML'
}
},
footerChildren: {
description: 'creates a slot for footer children',
table: {
category: 'HTML'
}
}
},
};
export const PageLayoutTemplate = ({
_debug,
wrapperSizing,
outerSpacing,
columnGap,
rowGap,
responsiveVariant,
primaryRegion,
paneWidth,
panePosition,
panePositionNarrow,
hasPaneDivider,
paneDividerNarrow,
contentWidth,
hasHeader,
hasHeaderDivider,
headerDividerNarrow,
hasFooter,
hasFooterDivider,
footerDividerNarrow,
contentChildren,
paneChildren,
headerChildren,
footerChildren
}) => {
return (
<>
<LayoutTemplate
_debug={_debug}
wrapperSizing={wrapperSizing}
outerSpacing={outerSpacing}
innerSpacing='none'
columnGap={columnGap}
rowGap={rowGap}
responsiveVariant={responsiveVariant}
primaryRegion={primaryRegion}
paneWidth={paneWidth}
panePosition={panePosition}
panePositionNarrow={panePositionNarrow}
hasPaneDivider={hasPaneDivider}
paneDividerNarrow={paneDividerNarrow}
contentWidth={contentWidth}
hasHeader={hasHeader}
hasHeaderDivider={hasHeaderDivider}
headerDividerNarrow={headerDividerNarrow}
hasFooter={hasFooter}
hasFooterDivider={hasFooterDivider}
footerDividerNarrow={footerDividerNarrow}
contentChildren={contentChildren}
paneChildren={paneChildren}
headerChildren={headerChildren}
footerChildren={footerChildren}
/>
</>
);
};
export const Playground = PageLayoutTemplate.bind({});
Playground.storyName = 'Playground';
Playground.parameters = {
layout: 'fullscreen',
};
Playground.args = {
_debug: true,
// Structure
wrapperSizing: 'xl',
outerSpacing: 'normal',
columnGap: 'normal',
rowGap: 'normal',
// Responsive
responsiveVariant: 'stackRegions',
primaryRegion: 'content',
// Pane
panePosition: 'end',
panePositionNarrow: 'inherit',
paneWidth: 'default',
hasPaneDivider: false,
paneDividerNarrow: 'inherit',
// Content
contentWidth: 'fluid',
// Header
hasHeader: false,
hasHeaderDivider: false,
headerDividerNarrow: 'inherit',
// Footer
hasFooter: false,
hasFooterDivider: false,
footerDividerNarrow: 'inherit',
contentChildren: 'content',
paneChildren: 'pane',
headerChildren: 'header',
footerChildren: 'footer'
}

View File

@ -0,0 +1,143 @@
// create a "playground" demo page that may set some defaults and allow story to access component controls
import React from 'react'
import clsx from 'clsx'
import {LayoutTemplate} from './LayoutBeta.stories'
import {NavWithSubItems} from '../ActionList/ActionListPatterns.stories'
export default {
title: 'Components/Layout/Beta/SplitPageLayout',
excludeStories: ['SplitPageLayoutTemplate'],
argTypes: {
// Structure
innerSpacing: {
options: ['normal', 'condensed'],
control: {
type: 'inline-radio'
},
description: 'Sets padding to regions individually. `normal` sets padding to 16px, with the `content` region getting 24px horizontal padding on `lg` breakpoints and above. `condensed` keeps the padding always at `16px`.',
table: {
category: 'Structure'
}
},
primaryRegion: {
options: ['content', 'pane'],
control: {
type: 'inline-radio'
},
description: 'Defines which region appears first on small viewports. `content` is default.',
table: {
category: 'Responsive'
}
},
// Pane
paneWidth: {
options: ['default', 'narrow', 'wide'],
control: {
type: 'inline-radio'
},
description: 'Defines the width of the pane.',
table: {
category: 'Pane'
}
},
// Content
contentWidth: {
options: ['fluid', 'sm', 'md', 'lg', 'xl'],
control: {
type: 'inline-radio'
},
description: 'Defines the maximum width of the content region. `fluid` sets it to full-width. Other values follow container widths from `sm` to `xl`. With smaller widths, the content region will try to stay centered to the viewport area.',
table: {
category: 'Content'
}
},
// HTML
contentChildren: {
description: 'creates a slot for content children',
table: {
category: 'HTML'
}
},
paneChildren: {
description: 'creates a slot for pane children',
table: {
category: 'HTML'
}
}
}
};
export const SplitPageLayoutTemplate = ({
_debug,
innerSpacing,
primaryRegion,
paneWidth,
paneIsSticky,
contentWidth,
contentChildren,
paneChildren
}) => {
return (
<>
<LayoutTemplate
_debug={_debug}
wrapperSizing='full'
outerSpacing='none'
innerSpacing={innerSpacing}
columnGap='none'
rowGap='none'
responsiveVariant='separateRegions'
primaryRegion={primaryRegion}
paneWidth={paneWidth}
paneIsSticky={paneIsSticky}
panePosition='start'
hasPaneDivider={true}
contentWidth={contentWidth}
hasHeader={false}
hasFooter={false}
contentChildren={contentChildren}
paneChildren={paneChildren}
/>
</>
);
};
export const Playground = SplitPageLayoutTemplate.bind({});
Playground.storyName = 'Playground';
Playground.parameters = {
layout: 'fullscreen',
};
Playground.args = {
_debug: true,
// Structure
innerSpacing: 'normal',
// Responsive
primaryRegion: 'content',
// Pane
paneWidth: 'wide',
// Content
contentWidth: 'fluid',
// Children
contentChildren: 'content',
paneChildren: 'pane'
};

View File

@ -0,0 +1,45 @@
import React from 'react'
export default function PageLayoutBehavior() {
const pageLayoutSelector = '.PageLayout.PageLayout--responsive-separateRegions';
const primaryRegionSelector = 'PageLayout--responsive-primary';
const detectPageLayoutHash = () => {
const pageLayout = document.querySelector(pageLayoutSelector);
let dest;
if (location.hash === '') {
dest = pageLayout.getAttribute('data-primary-region');
} else if (location.hash === '#pane') {
dest = 'pane';
} else if (location.hash === '#content') {
dest = 'content';
} else {
return;
}
pageLayout.setAttribute('data-current-region', dest);
if (dest === 'pane') {
pageLayout.classList.replace(primaryRegionSelector + '-content', primaryRegionSelector + '-pane');
} else {
pageLayout.classList.replace(primaryRegionSelector + '-pane', primaryRegionSelector + '-content');
}
};
window.addEventListener("hashchange", () => {
detectPageLayoutHash();
});
document.addEventListener('DOMContentLoaded', (event) => {
const pageLayout = document.querySelector(pageLayoutSelector);
const primaryRegion = pageLayout.classList.contains(primaryRegionSelector + '-pane') ? 'pane' : 'content';
if (pageLayout.getAttribute('data-primary-region') === null) {
pageLayout.setAttribute('data-primary-region', primaryRegion);
}
detectPageLayoutHash();
});
}

View File

@ -4,3 +4,4 @@
@import './grid.scss';
@import './grid-offset.scss';
@import './layout.scss';
@import './page-layout.scss';

View File

@ -1,4 +1,4 @@
// Layout mixins
// Layout alpha mixins
@mixin flow-as-row {
grid-auto-flow: row;
@ -77,3 +77,29 @@
}
}
}
// Layout beta mixins
// responsive region dividers
@mixin Layout-line-divider {
position: absolute;
left: calc(var(--Layout-outer-spacing-x) * -1);
display: block;
width: calc(100% + (var(--Layout-outer-spacing-x) * 2));
height: 1px;
content: '';
background-color: $Layout-divider-color;
}
@mixin Layout-filled-divider {
position: absolute;
bottom: calc(#{$spacer-2} * -1); // -8px
left: calc(var(--Layout-outer-spacing-x) * -1);
display: block;
width: calc(100% + (var(--Layout-outer-spacing-x) * 2));
height: #{$spacer-2}; // 8px
content: '';
background-color: var(--color-canvas-inset);
box-shadow: inset 0 1px $Layout-divider-color, inset 0 -1px $Layout-divider-color;
}

380
src/layout/page-layout.scss Normal file
View File

@ -0,0 +1,380 @@
// stylelint-disable max-nesting-depth
// stylelint-disable selector-max-specificity
// stylelint-disable no-duplicate-selectors
$Layout-divider-color: var(--color-border-default) !default;
$Layout-responsive-variant-max-breakpoint: 'md' !default;
:root {
--Layout-pane-width: #{map-get($sidebar-width, 'sm')};
--Layout-content-width: 100%;
--Layout-template-columns: 1fr var(--Layout-pane-width);
--Layout-template-areas: 'content pane';
--Layout-column-gap: #{$spacer-3};
--Layout-row-gap: #{$spacer-3};
// the `px` unit is mandatory for `calc()` execution. See https://stackoverflow.com/a/32518348
// stylelint-disable length-zero-no-unit
--Layout-outer-spacing-x: 0px; // wrapper margin x
--Layout-outer-spacing-y: 0px; // wrapper margin y
--Layout-inner-spacing-min: 0px; // default region padding
--Layout-inner-spacing-max: 0px; // relaxed content horizontal padding
// stylelint-enable length-zero-no-unit
}
.PageLayout {
// stylelint-disable-next-line primer/spacing
margin: var(--Layout-outer-spacing-y) var(--Layout-outer-spacing-x);
// multi-column desktop-friendly layout
@include breakpoint($Layout-responsive-variant-max-breakpoint) {
// Set a `content` region width, to work with loading states when
// `pane` is not yet loaded. See https://github.com/primer/css/pull/1818
$Layout-content-full-width: minmax(0, calc(100% - var(--Layout-pane-width) - var(--Layout-column-gap)));
&.PageLayout--panePos-start {
--Layout-template-columns: var(--Layout-pane-width) #{$Layout-content-full-width};
--Layout-template-areas: 'pane content';
}
&.PageLayout--panePos-end {
--Layout-template-columns: #{$Layout-content-full-width} var(--Layout-pane-width);
--Layout-template-areas: 'content pane';
}
// header divider
.PageLayout-header--hasDivider {
// stylelint-disable-next-line primer/spacing
padding-bottom: max(var(--Layout-row-gap), var(--Layout-inner-spacing-min));
// stylelint-disable-next-line primer/borders
border-bottom: $border-width solid $Layout-divider-color;
}
// footer divider
.PageLayout-footer--hasDivider {
// stylelint-disable-next-line primer/spacing
padding-top: max(var(--Layout-row-gap), var(--Layout-inner-spacing-min));
// stylelint-disable-next-line primer/borders
border-top: $border-width solid $Layout-divider-color;
}
// pane divider
&.PageLayout--hasPaneDivider {
&.PageLayout--panePos-start {
.PageLayout-pane {
// stylelint-disable-next-line primer/borders
border-right: $border-width solid $Layout-divider-color;
}
&:not(.PageLayout--columnGap-none) {
.PageLayout-pane {
// stylelint-disable-next-line primer/spacing
padding-right: calc(var(--Layout-column-gap) - #{$border-width});
// stylelint-disable-next-line primer/spacing
margin-right: calc(var(--Layout-column-gap) * -1);
}
.PageLayout-content {
// stylelint-disable-next-line primer/spacing
margin-left: var(--Layout-column-gap);
}
}
}
&.PageLayout--panePos-end {
.PageLayout-pane {
// stylelint-disable-next-line primer/borders
border-left: $border-width solid $Layout-divider-color;
}
&:not(.PageLayout--columnGap-none) {
.PageLayout-pane {
// stylelint-disable-next-line primer/spacing
padding-left: calc(var(--Layout-column-gap) - #{$border-width});
// stylelint-disable-next-line primer/spacing
margin-left: calc(var(--Layout-column-gap) * -1);
}
.PageLayout-content {
// stylelint-disable-next-line primer/spacing
margin-right: var(--Layout-column-gap);
}
}
}
}
// sticky pane
&.PageLayout--isPaneSticky {
.PageLayout-pane {
position: sticky;
top: 0;
max-height: 100vh;
overflow: auto;
}
}
// content width
[class^='PageLayout-content-centered-'] {
max-width: calc(var(--Layout-content-width) + var(--Layout-pane-width) + var(--Layout-column-gap));
margin-right: auto;
margin-left: auto;
}
&.PageLayout--hasPaneDivider {
[class^='PageLayout-content-centered-'] {
max-width: calc(var(--Layout-content-width) + var(--Layout-pane-width) + (var(--Layout-column-gap) * 2));
}
}
&.PageLayout--panePos-start {
[class^='PageLayout-content-centered-'] > [class^='container-'] {
margin-left: 0;
}
}
&.PageLayout--panePos-end {
[class^='PageLayout-content-centered-'] > [class^='container-'] {
margin-right: 0;
}
}
@each $breakpoint in map-keys($breakpoints) {
.PageLayout-content-centered-#{$breakpoint} {
--Layout-content-width: #{map-get($breakpoints, $breakpoint)};
}
}
// pane width
@each $breakpoint in map-keys($sidebar-width) {
@include breakpoint($breakpoint) {
--Layout-pane-width: #{map-get($sidebar-width, $breakpoint)};
}
}
&.PageLayout--paneWidth-narrow {
@each $breakpoint in map-keys($sidebar-narrow-width) {
@include breakpoint($breakpoint) {
--Layout-pane-width: #{map-get($sidebar-narrow-width, $breakpoint)};
}
}
}
&.PageLayout--paneWidth-wide {
@each $breakpoint in map-keys($sidebar-wide-width) {
@include breakpoint($breakpoint) {
--Layout-pane-width: #{map-get($sidebar-wide-width, $breakpoint)};
}
}
}
}
// responsive behaviors on narrow viewports
@media (max-width: #{map-get($breakpoints, $Layout-responsive-variant-max-breakpoint) - 0.02px}) {
// variant: stackRegions
&.PageLayout--responsive-stackRegions {
--Layout-template-columns: 1fr;
// responsive-panePos: end (default)
--Layout-template-areas: 'content' 'pane';
// responsive-panePos: start
&.PageLayout--responsive-panePos-start {
--Layout-template-areas: 'pane' 'content';
}
}
// variant: separateRegions
&.PageLayout--responsive-separateRegions {
--Layout-template-columns: 1fr;
--Layout-template-areas: 'content';
&.PageLayout--responsive-primary-content {
--Layout-template-areas: 'content';
.PageLayout-pane {
display: none;
}
}
&.PageLayout--responsive-primary-pane {
--Layout-template-areas: 'pane';
.PageLayout-content {
display: none;
}
}
}
// region dividers on narrow viewports
.PageLayout-region--dividerNarrow-line-before {
position: relative;
// stylelint-disable-next-line primer/spacing
margin-top: var(--Layout-row-gap);
&::before {
@include Layout-line-divider;
top: calc(#{$border-width * -1} - var(--Layout-row-gap));
}
}
.PageLayout-region--dividerNarrow-line-after {
position: relative;
// stylelint-disable-next-line primer/spacing
margin-bottom: var(--Layout-row-gap);
&::after {
@include Layout-line-divider;
bottom: calc(#{$border-width * -1} - var(--Layout-row-gap));
}
}
.PageLayout-region--dividerNarrow-filled-before {
position: relative;
// stylelint-disable-next-line primer/spacing
margin-top: calc(#{$spacer-2} + var(--Layout-row-gap));
&::after {
@include Layout-filled-divider;
top: calc(#{$spacer-2 * -1} - var(--Layout-row-gap));
}
}
.PageLayout-region--dividerNarrow-filled-after {
position: relative;
// stylelint-disable-next-line primer/spacing
margin-bottom: calc(#{$spacer-2} + var(--Layout-row-gap));
&::before {
@include Layout-filled-divider;
bottom: calc(#{$spacer-2 * -1} - var(--Layout-row-gap));
}
}
}
}
.PageLayout-wrapper {
display: grid;
grid: auto-flow / 1fr;
row-gap: var(--Layout-row-gap);
}
.PageLayout-columns {
display: grid;
column-gap: var(--Layout-column-gap);
row-gap: var(--Layout-row-gap);
grid-template-columns: var(--Layout-template-columns);
grid-template-rows: 1fr;
grid-template-areas: var(--Layout-template-areas);
}
// outer spacing
.PageLayout--outerSpacing-normal {
--Layout-outer-spacing-x: #{$spacer-3};
--Layout-outer-spacing-y: #{$spacer-3};
@include breakpoint(lg) {
--Layout-outer-spacing-x: #{$spacer-4};
--Layout-outer-spacing-y: #{$spacer-4};
}
}
.PageLayout--outerSpacing-condensed {
--Layout-outer-spacing-x: #{$spacer-3};
--Layout-outer-spacing-y: #{$spacer-3};
}
// inner spacing
.PageLayout--innerSpacing-normal {
--Layout-inner-spacing-min: #{$spacer-3};
--Layout-inner-spacing-max: #{$spacer-3};
@include breakpoint(lg) {
--Layout-inner-spacing-max: #{$spacer-4};
}
}
.PageLayout--innerSpacing-condensed {
--Layout-inner-spacing-min: #{$spacer-3};
--Layout-inner-spacing-max: #{$spacer-3};
}
// column gap
.PageLayout--columnGap-normal {
--Layout-column-gap: #{$spacer-3};
@include breakpoint(lg) {
--Layout-column-gap: #{$spacer-4};
}
}
.PageLayout--columnGap-condensed {
--Layout-column-gap: #{$spacer-3};
}
.PageLayout--columnGap-none {
// the `px` unit is mandatory for `calc()` execution. See https://stackoverflow.com/a/32518348
// stylelint-disable-next-line length-zero-no-unit
--Layout-column-gap: 0px;
}
// row gap
.PageLayout--rowGap-normal {
--Layout-row-gap: #{$spacer-3};
@include breakpoint(lg) {
--Layout-row-gap: #{$spacer-4};
}
}
.PageLayout--rowGap-none {
// the `px` unit is mandatory for `calc()` execution. See https://stackoverflow.com/a/32518348
// stylelint-disable length-zero-no-unit
--Layout-row-gap: 0px;
}
.PageLayout--rowGap-condensed {
--Layout-row-gap: #{$spacer-3};
}
// regions
.PageLayout-header,
.PageLayout-content,
.PageLayout-pane,
.PageLayout-footer {
// stylelint-disable-next-line primer/spacing
padding: var(--Layout-inner-spacing-min);
}
.PageLayout-content {
// stylelint-disable primer/spacing
padding-right: var(--Layout-inner-spacing-max);
padding-left: var(--Layout-inner-spacing-max);
// stylelint-enable primer/spacing
grid-area: content;
}
.PageLayout-pane {
grid-area: pane;
}

View File

@ -178,9 +178,8 @@ $sidebar-narrow-width: (
) !default;
$sidebar-wide-width: (
md: 296px,
lg: 320px,
xl: 344px
xl: 336px
) !default;
$gutter: (