1
1
mirror of https://github.com/primer/css.git synced 2024-12-26 15:43:59 +03:00

Add Stack component (#2152)

* Add Stack component

* Stylelint auto-fixes

* Ongoing Stack component implementation

* Add Stack dividers, responsive props, examples

* Implement align, alignWrap, and spread props

* Code cleanup

* Wrap responsive props under narrow+regular queries

* Fix structure comment

* Address rendering bugs, add docs

* typo

* Cleanup

* typo

* Please stylelint

* Update docs/src/stories/components/Layout/Stack.stories.jsx

Co-authored-by: Mike Perrotti <mperrotti@github.com>

* Simplify jsx

* Simplify Stack CSS, add Stack-item

* Add Stack feature stories

* Stylelint auto-fixes

* Remove `none` from dividerAriaRole property

* add wide viewport support

* Stylelint auto-fixes

* Add changeset

* Hide custom gap support

* Use Sass comments instead

Co-authored-by: Actions Auto Build <actions@github.com>
Co-authored-by: Mike Perrotti <mperrotti@github.com>
This commit is contained in:
Vinicius Depizzol 2022-07-25 14:09:14 -07:00 committed by GitHub
parent a52afe7e7d
commit edac9edbcf
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
7 changed files with 1070 additions and 0 deletions

View File

@ -0,0 +1,5 @@
---
"@primer/css": minor
---
Add Stack component

View File

@ -0,0 +1,607 @@
import React from 'react'
import clsx from 'clsx'
export default {
title: 'Components/Layout/Stack',
excludeStories: ['StackTemplate'],
argTypes: {
// Debug
_debug: {
control: {
type: 'boolean',
},
table: {
category: 'Debug'
},
},
_height: {
control: {
type: 'number',
},
table: {
category: 'Debug'
},
},
_width: {
control: {
type: 'number',
},
table: {
category: 'Debug'
},
},
// Direction
direction: {
options: ['inline', 'block'],
control: {
type: 'inline-radio',
},
description: 'Sets how elements inside `Stack` are placed, either horizontally (`inline`) or vertically (`block`). This property follows the writing mode.',
table: {
category: 'Properties',
defaultValue: {
summary: 'block',
}
}
},
// Gap
gap: {
options: ['none', 'condensed', 'normal', 'spacious'],
control: {
type: 'inline-radio',
},
description: `Sets the spacing gap between items. All sizes are rendered in \`rem\` units.
- \`none\`: 0
- \`condensed\`: \`var(--primer-stack-gap-condensed, 8px)\`,
- \`normal\`: \`var(--primer-stack-gap-normal, 16px)\` (default)
- \`spacious\`: \`var(--primer-stack-gap-spacious, 24px)\` (on regular viewports, otherwise it appears as \`normal\` on narrow viewports)
<!-- - \`custom\`: set a custom size. When using with a framework such as ViewComponent or React, a custom value can be passed directly to the property. -->
`,
table: {
category: 'Properties',
defaultValue: {
summary: 'normal',
}
}
},
// gap_custom: {
// control: {
// type: 'text'
// },
// description: 'A custom value to `gap`. Refer to [Primer Primitives](https://primer.style/primitives/spacing) for other spacing tokens. Example: `var(--base-size-12, 12px)`.',
// table: {
// category: 'Properties',
// },
// },
// Align
align: {
options: ['stretch', 'start', 'center', 'end', 'baseline'],
control: {
type: 'inline-radio'
},
description: `Sets the alignment between items in the cross-axis of the specified direction. For example:
- If \`direction\` is set to \`block\` (stacks vertically), it controls the horizontal alignment (left, center, right).
- If \`direction\` is set to \`inline\` (stacks horizontally), it controls the vertical alignment (top, center, bottom).
This property behavior is equivalent to the \`align-items\` Flexbox property.`,
table: {
category: 'Properties',
defaultValue: {
summary: 'stretch',
}
}
},
// Align wrap
alignWrap: {
options: ['start', 'center', 'end', 'distribute', 'distributeEvenly'],
control: {
type: 'inline-radio'
},
description: 'Sets how stack lines are distributed, if they `wrap` into multiple lines. This has equivalent behavior to the `align-content` Flexbox property.',
table: {
category: 'Properties',
defaultValue: {
summary: 'start',
}
}
},
// Spread
spread: {
options: ['start', 'center', 'end', 'distribute', 'distributeEvenly'],
control: {
type: 'inline-radio',
},
description: 'Sets how items will be distributed in the stacking direction.',
table: {
category: 'Properties',
defaultValue: {
summary: 'start',
},
},
},
// Wrap
wrap: {
options: ['wrap', 'nowrap'],
control: {
type: 'inline-radio'
},
description: 'Sets whether items are forced onto one line or can wrap onto multiple lines.',
table: {
category: 'Properties',
defaultValue: {
summary: 'nowrap',
}
}
},
// Divider
showDividers: {
control: {
type: 'boolean'
},
description: `Whether a divider between items is shown or not.
_Note: the presence of a divider duplicates the \`gap\` between items._`,
table: {
category: 'Properties',
defaultValue: {
summary: 'false',
}
}
},
dividerAriaRole: {
options: ['presentation', 'separator'],
control: {
type: 'inline-radio'
},
description: 'Sets which ARIA role will be used for the divider.',
table: {
category: 'Properties',
defaultValue: {
summary: 'presentation',
}
}
},
// Responsive properties / narrow
narrow_direction: {
options: ['inherit', 'inline', 'block'],
control: {
type: 'inline-radio',
},
description: 'Override `direction` on narrow viewports',
table: {
category: 'Narrow viewport properties',
defaultValue: {
summary: 'inherit'
}
},
},
narrow_gap: {
options: ['inherit', 'none', 'condensed', 'normal'],
control: {
type: 'inline-radio',
},
description: 'Override `gap` on narrow viewports',
table: {
category: 'Narrow viewport properties',
defaultValue: {
summary: 'inherit'
}
},
},
// narrow_gap_custom: {
// control: {
// type: 'text'
// },
// description: 'Override a custom value for `gap` for narrow viewports',
// table: {
// category: 'Narrow viewport properties'
// },
// },
narrow_align: {
options: ['inherit', 'stretch', 'start', 'center', 'end', 'baseline'],
control: {
type: 'inline-radio'
},
table: {
category: 'Narrow viewport properties',
defaultValue: {
summary: 'inherit',
}
},
},
narrow_alignWrap: {
options: ['inherit', 'start', 'center', 'end', 'distribute', 'distributeEvenly'],
control: {
type: 'inline-radio'
},
table: {
category: 'Narrow viewport properties',
defaultValue: {
summary: 'inherit',
}
},
},
narrow_spread: {
options: ['inherit', 'start', 'center', 'end', 'distribute', 'distributeEvenly'],
control: {
type: 'inline-radio',
},
table: {
category: 'Narrow viewport properties',
defaultValue: {
summary: 'inherit'
}
},
},
narrow_wrap: {
options: ['inherit', 'wrap', 'nowrap'],
control: {
type: 'inline-radio'
},
table: {
category: 'Narrow viewport properties',
defaultValue: {
summary: 'inherit'
}
},
},
narrow_showDividers: {
control: {
type: 'boolean'
},
table: {
category: 'Narrow viewport properties',
defaultValue: {
summary: 'inherit'
}
},
},
// Responsive properties / wide
wide_direction: {
options: ['inherit', 'inline', 'block'],
control: {
type: 'inline-radio',
},
description: 'Override `direction` on wide viewports',
table: {
category: 'wide viewport properties',
defaultValue: {
summary: 'inherit'
}
},
},
wide_gap: {
options: ['inherit', 'none', 'condensed', 'normal', 'spacious'],
control: {
type: 'inline-radio',
},
description: 'Override `gap` on wide viewports',
table: {
category: 'wide viewport properties',
defaultValue: {
summary: 'inherit'
}
},
},
// wide_gap_custom: {
// control: {
// type: 'text'
// },
// description: 'Override a custom value for `gap` for wide viewports',
// table: {
// category: 'wide viewport properties'
// },
// },
wide_align: {
options: ['inherit', 'stretch', 'start', 'center', 'end', 'baseline'],
control: {
type: 'inline-radio'
},
table: {
category: 'wide viewport properties',
defaultValue: {
summary: 'inherit',
}
},
},
wide_alignWrap: {
options: ['inherit', 'start', 'center', 'end', 'distribute', 'distributeEvenly'],
control: {
type: 'inline-radio'
},
table: {
category: 'wide viewport properties',
defaultValue: {
summary: 'inherit',
}
},
},
wide_spread: {
options: ['inherit', 'start', 'center', 'end', 'distribute', 'distributeEvenly'],
control: {
type: 'inline-radio',
},
table: {
category: 'wide viewport properties',
defaultValue: {
summary: 'inherit'
}
},
},
wide_wrap: {
options: ['inherit', 'wrap', 'nowrap'],
control: {
type: 'inline-radio'
},
table: {
category: 'wide viewport properties',
defaultValue: {
summary: 'inherit'
}
},
},
wide_showDividers: {
control: {
type: 'boolean'
},
table: {
category: 'wide viewport properties',
defaultValue: {
summary: 'inherit'
}
},
},
// Children
children: {
description: 'A slot for children elements.',
table: {
category: 'HTML'
}
}
},
};
export const StackTemplate = ({
_debug,
_height,
_width,
direction,
gap,
//gap_custom,
align,
alignWrap,
spread,
wrap,
showDividers,
dividerAriaRole,
narrow_direction,
narrow_gap,
//narrow_gap_custom,
narrow_align,
narrow_alignWrap,
narrow_spread,
narrow_wrap,
narrow_showDividers,
wide_direction,
wide_gap,
//wide_gap_custom,
wide_align,
wide_alignWrap,
wide_spread,
wide_wrap,
wide_showDividers,
children
}) => {
let custom_styles = {};
// Default values
direction = direction ?? 'block';
gap = gap ?? 'normal';
alignWrap = alignWrap ?? 'start';
dividerAriaRole = dividerAriaRole ?? 'presentation';
// Default narrow values
narrow_direction = narrow_direction ?? 'inherit';
narrow_gap = narrow_gap ?? 'inherit';
narrow_align = narrow_align ?? 'inherit';
narrow_alignWrap = narrow_alignWrap ?? 'inherit';
narrow_spread = narrow_spread ?? 'inherit';
narrow_wrap = narrow_wrap ?? 'inherit';
narrow_showDividers = narrow_showDividers ?? 'inherit';
// Default wide values
wide_direction = wide_direction ?? 'inherit';
wide_gap = wide_gap ?? 'inherit';
wide_align = wide_align ?? 'inherit';
wide_alignWrap = wide_alignWrap ?? 'inherit';
wide_spread = wide_spread ?? 'inherit';
wide_wrap = wide_wrap ?? 'inherit';
wide_showDividers = wide_showDividers ?? 'inherit';
// Custom gap - not available
// if (gap === 'custom') {
// custom_styles['--Stack-gap-whenRegular'] = gap_custom;
// }
// if (narrow_gap === 'custom') {
// custom_styles['--Stack-gap-whenNarrow'] = narrow_gap_custom;
// }
// if (wide_gap === 'custom') {
// custom_styles['--Stack-gap-whenWide'] = wide_gap_custom;
// }
// Null value for states that don't require a modifier class
align = align === 'stretch' ? null : align;
alignWrap = alignWrap === 'start' ? null : alignWrap;
spread = spread === 'start' ? null : spread;
wrap = wrap === 'nowrap' ? null : wrap;
gap = gap === 'normal' ? null : gap;
// Null value for inherit responsive values
narrow_direction = narrow_direction === 'inherit' ? direction : narrow_direction;
narrow_gap = narrow_gap === 'inherit' ? gap : narrow_gap;
narrow_align = narrow_align === 'inherit' ? align : narrow_align;
narrow_alignWrap = narrow_alignWrap === 'inherit' ? alignWrap : narrow_alignWrap;
narrow_spread = narrow_spread === 'inherit' ? spread : narrow_spread;
narrow_wrap = narrow_wrap === 'inherit' ? wrap : narrow_wrap;
narrow_showDividers = narrow_showDividers === 'inherit' ? showDividers : narrow_showDividers;
wide_direction = wide_direction === 'inherit' ? null : wide_direction;
wide_gap = wide_gap === 'inherit' ? null : wide_gap;
wide_align = wide_align === 'inherit' ? null : wide_align;
wide_alignWrap = wide_alignWrap === 'inherit' ? null : wide_alignWrap;
wide_spread = wide_spread === 'inherit' ? null : wide_spread;
wide_wrap = wide_wrap === 'inherit' ? null : wide_wrap;
wide_showDividers = wide_showDividers === 'inherit' ? null : wide_showDividers;
// Dividers logic
showDividers = wrap === 'wrap' ? false : showDividers;
narrow_showDividers = narrow_wrap === 'wrap' ? false : narrow_showDividers;
wide_showDividers = wide_wrap === 'wrap' ? false : wide_showDividers;
const hasDividers = showDividers || narrow_showDividers || wide_showDividers;
return (
<>
<div
className={clsx(
'Stack',
direction && `Stack--dir-${direction}-whenRegular`,
narrow_direction && 'Stack--dir-' + `${narrow_direction}-whenNarrow`,
wide_direction && 'Stack--dir-' + `${wide_direction}-whenWide`,
gap && 'Stack--gap-' + `${gap}-whenRegular`,
narrow_gap && 'Stack--gap-' + `${narrow_gap}-whenNarrow`,
wide_gap && 'Stack--gap-' + `${wide_gap}-whenWide`,
align && 'Stack--align-' + `${align}-whenRegular`,
narrow_align && 'Stack--align-' + `${narrow_align}-whenNarrow`,
wide_align && 'Stack--align-' + `${wide_align}-whenWide`,
alignWrap && 'Stack--alignWrap-' + `${alignWrap}-whenRegular`,
narrow_alignWrap && 'Stack--alignWrap-' + `${narrow_alignWrap}-whenNarrow`,
wide_alignWrap && 'Stack--alignWrap-' + `${wide_alignWrap}-whenWide`,
spread && 'Stack--spread-' + `${spread}-whenRegular`,
narrow_spread && 'Stack--spread-' + `${narrow_spread}-whenNarrow`,
wide_spread && 'Stack--spread-' + `${wide_spread}-whenWide`,
wrap && 'Stack--' + `${wrap}-whenRegular`,
narrow_wrap && 'Stack--' + `${narrow_wrap}-whenNarrow`,
wide_wrap && 'Stack--' + `${wide_wrap}-whenWide`,
showDividers && 'Stack--showDividers-whenRegular',
narrow_showDividers && 'Stack--showDividers-whenNarrow',
wide_showDividers && 'Stack--showDividers-whenWide',
)}
//style={custom_styles}
>
{children}
{!children && (
<>
<div className="Stack-item _debug _debug-item-1">1</div>
{hasDividers && ( <hr className="Stack-divider" role={dividerAriaRole} /> )}
<div className="Stack-item _debug _debug-item-2">2</div>
{hasDividers && ( <hr className="Stack-divider" role={dividerAriaRole} /> )}
<div className="Stack-item _debug _debug-item-3">3</div>
{hasDividers && ( <hr className="Stack-divider" role={dividerAriaRole} /> )}
<div className="Stack-item _debug _debug-item-4">4</div>
{hasDividers && ( <hr className="Stack-divider" role={dividerAriaRole} /> )}
<div className="Stack-item _debug _debug-item-5">5</div>
{hasDividers && ( <hr className="Stack-divider" role={dividerAriaRole} /> )}
<div className="Stack-item _debug _debug-item-6">6</div>
</>
)}
</div>
{_debug && (
<>
<style type="text/css">{`
.Stack {
background: beige;
${_height ? 'height: '+ _height + 'px;': ''}
${_width ? 'width: '+ _width + 'px;': ''}
}
.Stack ._debug {
padding: 8px;
border-radius: 6px;
}
.Stack ._debug-item-1 {
background: lightblue;
min-inline-size: 6ch;
font-size: 1rem;
}
.Stack ._debug-item-2 {
background: coral;
min-inline-size: 9ch;
font-size: 1.25rem;
}
.Stack ._debug-item-3 {
background: darkseagreen;
inline-size: 8ch;
font-size: 1rem;
}
.Stack ._debug-item-4 {
background: khaki;
inline-size: 7ch;
font-size: 1.25rem;
}
.Stack ._debug-item-5 {
background: lightpink;
inline-size: 10ch;
font-size: 1rem;
}
.Stack ._debug-item-6 {
background: lightsalmon;
inline-size: 6ch;
font-size: 1.25rem;
}
`}</style>
</>
)}
</>
);
};
export const Playground = StackTemplate.bind({})
Playground.args = {
_debug: true,
direction: "block",
gap: "normal",
align: "stretch",
};

View File

@ -0,0 +1,79 @@
import React from 'react'
import {
StackTemplate
} from './Stack.stories'
export default {
title: 'Components/Layout/Stack/Examples'
}
export const ButtonInlineStack = StackTemplate.bind({})
ButtonInlineStack.args = {
direction: "inline",
gap: "condensed",
children: (
<>
<button class="btn">Button</button>
<button class="btn">Button</button>
<button class="btn">Button</button>
</>
)
};
export const PageSections = StackTemplate.bind({})
PageSections.args = {
direction: "block",
gap: "normal",
children: (
<>
<div className="Box p-2" style={{minHeight: '20ch'}}>Section 1</div>
<div className="Box p-2" style={{minHeight: '12ch'}}>Section 2</div>
<div className="Box p-2" style={{minHeight: '16ch'}}>Section 3</div>
</>
)
};
export const Composition = StackTemplate.bind({})
Composition.args = {
direction: "block",
gap: "normal",
children: (
<>
<StackTemplate divider={true} children={(
<>
<StackTemplate gap="condensed" children={(
<>
<h4>Heading</h4>
<div>Lorem ipsum dolor sit amet avec consequer domulus sit lorem ipsum dolor sit amet.</div>
<StackTemplate direction="inline" wrap="wrap" gap="condensed" children={(
<>
<span class="Label">Inline labels set to wrap</span>
<span class="Label">Label 2</span>
<span class="Label">Label 3</span>
<span class="Label">Label 4</span>
<span class="Label">Label 5</span>
</>
)} />
</>
)} />
<hr className="Stack-divider" />
<StackTemplate gap="condensed" children={(
<>
<h4>Heading</h4>
<div>Lorem ipsum dolor sit amet avec consequer domulus sit lorem ipsum dolor sit amet.</div>
<StackTemplate direction="inline" wrap="wrap" gap="condensed" children={(
<>
<span class="Label">Inline labels set to wrap</span>
<span class="Label">Label 2</span>
<span class="Label">Label 3</span>
<span class="Label">Label 4</span>
<span class="Label">Label 5</span>
</>
)} />
</>
)} />
</>
)} />
</>
)
};

View File

@ -0,0 +1,151 @@
import React from 'react'
import {
StackTemplate
} from './Stack.stories'
export default {
title: 'Components/Layout/Stack/Features'
}
export const DirectChildren = StackTemplate.bind({})
DirectChildren.args = {
direction: "block",
gap: "normal",
narrow_gap: "condensed",
children: (
<>
<div style={{backgroundColor: 'lightpink', padding: '16px'}}>Item 1</div>
<div style={{backgroundColor: 'orange', padding: '16px'}}>Item 2</div>
<div style={{backgroundColor: 'lightblue', padding: '16px'}}>Item 3</div>
<div style={{backgroundColor: 'darkseagreen', padding: '16px'}}>Item 4</div>
<div style={{backgroundColor: 'khaki', padding: '16px'}}>Item 5</div>
</>
)
};
export const StackItems = StackTemplate.bind({})
StackItems.args = {
direction: "inline",
gap: "normal",
wrap: "wrap",
narrow_gap: "condensed",
children: (
<>
<div className="Stack-item">
<div style={{backgroundColor: 'lightpink', padding: '16px'}}>Item 1</div>
</div>
<div className="Stack-item">
<div style={{backgroundColor: 'orange', padding: '16px'}}>Item 2</div>
</div>
<div className="Stack-item">
<div style={{backgroundColor: 'lightblue', padding: '16px'}}>Item 3</div>
</div>
<div className="Stack-item">
<div style={{backgroundColor: 'darkseagreen', padding: '16px'}}>Item 4</div>
</div>
<div className="Stack-item">
<div style={{backgroundColor: 'khaki', padding: '16px'}}>Item 5</div>
</div>
</>
)
};
export const ExpandStackItem = StackTemplate.bind({})
ExpandStackItem.args = {
direction: "inline",
wrap: "wrap",
gap: "normal",
narrow_gap: "condensed",
children: (
<>
<div className="Stack-item Stack-item--expand-whenRegular Stack-item--expand-whenNarrow">
<div style={{backgroundColor: 'lightpink', padding: '16px'}}>Item 1 (expand)</div>
</div>
<div className="Stack-item">
<div style={{backgroundColor: 'orange', padding: '16px'}}>Item 2</div>
</div>
<div className="Stack-item">
<div style={{backgroundColor: 'lightblue', padding: '16px'}}>Item 3</div>
</div>
<div className="Stack-item">
<div style={{backgroundColor: 'darkseagreen', padding: '16px'}}>Item 4</div>
</div>
<div className="Stack-item">
<div style={{backgroundColor: 'khaki', padding: '16px'}}>Item 5</div>
</div>
</>
)
};
export const ExpandStackItems = StackTemplate.bind({})
ExpandStackItems.args = {
direction: "inline",
wrap: "wrap",
gap: "normal",
narrow_gap: "condensed",
children: (
<>
<div className="Stack-item Stack-item--expand-whenRegular Stack-item--expand-whenNarrow">
<div style={{backgroundColor: 'lightpink', padding: '16px'}}>Item 1 (expand)</div>
</div>
<div className="Stack-item">
<div style={{backgroundColor: 'orange', padding: '16px'}}>Item 2</div>
</div>
<div className="Stack-item">
<div style={{backgroundColor: 'lightblue', padding: '16px'}}>Item 3</div>
</div>
<div className="Stack-item">
<div style={{backgroundColor: 'darkseagreen', padding: '16px'}}>Item 4</div>
</div>
<div className="Stack-item Stack-item--expand-whenRegular Stack-item--expand-whenNarrow">
<div style={{backgroundColor: 'khaki', padding: '16px'}}>Item 5 (expand)</div>
</div>
</>
)
};
export const ContentOverflow = StackTemplate.bind({})
ContentOverflow.args = {
direction: "block",
gap: "normal",
narrow_gap: "condensed",
children: (
<>
<div className="Stack-item">
<div style={{backgroundColor: 'lightpink', padding: '16px'}}>
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Vestibulum id faucibus sapien. Nullam gravida tortor eget lacinia volutpat. Vestibulum efficitur lorem non ex ultricies accumsan. Vestibulum porttitor nunc non tellus auctor rutrum. Morbi nec augue turpis. Vestibulum pulvinar semper risus id fermentum. Nulla egestas metus id nulla ullamcorper ullamcorper nec in urna. Duis tincidunt finibus quam, quis pretium odio pulvinar nec. Nullam malesuada sodales ligula. Nunc varius arcu et tellus condimentum interdum.
</div>
</div>
<div className="Stack-item">
<div style={{backgroundColor: 'orange', padding: '16px'}}>
Ut eu ligula tellus. Integer efficitur sit amet lorem nec hendrerit. In hac habitasse platea dictumst. Donec aliquam posuere leo iaculis efficitur. Curabitur malesuada placerat est, sit amet fermentum quam dignissim congue. Etiam interdum, leo sit amet molestie ornare, nisi ipsum cursus odio, a auctor turpis dolor in justo. Nulla molestie dolor sit amet lectus faucibus gravida. Maecenas ac ornare magna, in ultricies lectus. Nam venenatis porta pellentesque. Nullam odio nisl, accumsan id rutrum in, ultricies ac purus. Donec nec blandit risus.
</div>
</div>
<div className="Stack-item">
<div style={{backgroundColor: 'lightblue', padding: '16px'}}>
Ut cursus elit leo. Curabitur in lorem eget sem ullamcorper tempor. Lorem ipsum dolor sit amet, consectetur adipiscing elit. Praesent convallis vulputate turpis, fringilla congue felis laoreet tempus. Fusce tincidunt sodales lacus eu volutpat. Duis a semper neque. Aenean fermentum bibendum lectus, et aliquam ligula elementum vitae. Cras tempor lacus ipsum, vel sagittis sem laoreet et. Nulla vel tortor metus. Nullam euismod, dui non vulputate pulvinar, nisl sem malesuada dolor, vel malesuada nibh dui nec arcu. Nunc accumsan justo purus, non sodales massa blandit mattis. Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos. Aenean vel mollis orci, at consequat ante. Curabitur pharetra euismod condimentum. Pellentesque mauris lacus, ultricies sit amet lacus a, congue scelerisque dui.
</div>
</div>
<div className="Stack-item">
<div style={{backgroundColor: 'darkseagreen', padding: '16px', textOverflow: 'ellipsis', whiteSpace: 'nowrap', overflow: 'hidden'}}>
Vestibulum faucibus luctus bibendum. Nunc sodales interdum dapibus. Nulla aliquet nibh ac est pellentesque eleifend. Praesent ultricies eu nunc vel dignissim. Cras tempus porttitor arcu porttitor condimentum. Nulla imperdiet turpis nec tincidunt luctus. Curabitur in congue urna. Nulla vehicula nunc hendrerit, tristique est porta, tristique erat. Duis vel neque sed eros eleifend feugiat non eget metus. Curabitur elit nunc, condimentum et mollis vitae, vestibulum et lorem. Duis luctus tortor eu vulputate volutpat. Fusce egestas volutpat ante et lobortis. Integer scelerisque neque vitae lorem pellentesque elementum. Sed fringilla lacus at hendrerit tincidunt. Vestibulum pretium, odio nec commodo imperdiet, nibh eros viverra mauris, eget pellentesque est nulla sit amet nunc. Cras mi metus, tristique et tincidunt at, tristique sed dolor.
</div>
</div>
<div className="Stack-item Stack-item--expand-whenRegular Stack-item--expand-whenNarrow">
<div style={{backgroundColor: 'khaki', padding: '16px'}}>
In tempus, urna eu egestas convallis, nunc nisi faucibus lectus, et tempor felis nisi nec nunc. Morbi porttitor libero ac ipsum efficitur ullamcorper. Praesent eget velit volutpat, gravida odio vel, aliquet lectus. Sed a lorem imperdiet, tempor ligula eget, rutrum lorem. Nulla magna purus, iaculis sit amet volutpat et, varius sed orci. Curabitur eu purus quis mi dictum faucibus sit amet in lectus. Nam egestas quis felis sit amet finibus. In congue elementum lorem, id molestie neque commodo nec. Maecenas vitae accumsan orci. Aenean imperdiet et tellus et tincidunt. Donec non porta justo, sit amet laoreet risus. Mauris in bibendum tellus, at lobortis tortor.
</div>
</div>
<div className="Stack-item">
<div style={{backgroundColor: 'coral', padding: '16px'}}>
Item 6
</div>
</div>
<div className="Stack-item">
<div style={{backgroundColor: 'pink', padding: '16px'}}>
Item 7
</div>
</div>
</>
)
};

View File

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

220
src/layout/stack.scss Normal file
View File

@ -0,0 +1,220 @@
// Stack layout helper component
.Stack {
// A Stack component lays elements horizontally or vertically on the page.
//
// Stack is a simple abstraction of CSS' Flexbox. Use it to structure elements
// that are visually grouped, following the same direction.
//
// Markup structure
// ================
//
// .Stack
// &.Stack--dir-[ inline | block ]-[ whenNarrow | whenRegular | whenWide ]
// &.Stack--gap-[ none | condensed | normal | spacious ]-[ whenNarrow | whenRegular | whenWide ]
// &.Stack--align-[ start | center | end | baseline ]-[ whenNarrow | whenRegular | whenWide ]
// &.Stack--alignWrap-[ start | center | end | distribute | distributeEvenly ]-[ whenNarrow | whenRegular | whenWide ]
// &.Stack--spread-[ start | center | end | distribute | distributeEvenly ]-[ whenNarrow | whenRegular | whenWide ]
// &.Stack--wrap-[ whenNarrow | whenRegular | whenWide ]
// &.Stack--nowrap-[ whenNarrow | whenRegular | whenWide ]
// &.Stack--showDividers-[ whenNarrow | whenRegular | whenWide ]
//
// .Stack-divider
// .Stack-item
// &.Stack-item--expand-[ whenNarrow | whenRegular | whenWide ]
// &.Stack-item--keepSize-[ whenNarrow | whenRegular | whenWide ]
$Stack-gap-default: var(--primer-stack-gap-normal, 16px);
--Stack-gap-whenRegular: #{$Stack-gap-default};
--Stack-gap-whenNarrow: #{$Stack-gap-default};
--Stack-gap-whenWide: var(--Stack-gap-whenRegular);
--Stack-divider-color: var(--color-border-default);
display: flex;
flex-flow: column;
align-items: stretch;
align-content: start;
gap: var(--Stack-gap-whenRegular);
@media ($viewport-narrow) {
gap: var(--Stack-gap-whenNarrow);
}
@media ($viewport-wide) {
gap: var(--Stack-gap-whenWide);
}
}
@mixin Stack--modifiers($viewportRange: '') {
// direction
.Stack--dir-inline#{$viewportRange} {
flex-flow: row;
}
.Stack--dir-block#{$viewportRange} {
flex-flow: column;
}
// gap
.Stack--gap-none#{$viewportRange} {
--Stack-gap#{$viewportRange}: 0;
}
.Stack--gap-condensed#{$viewportRange} {
--Stack-gap#{$viewportRange}: var(--primer-stack-gap-condensed, 8px);
}
.Stack--gap-normal#{$viewportRange} {
--Stack-gap#{$viewportRange}: var(--primer-stack-gap-normal, 16px);
}
// There's no .Stack--gap-spacious-whenNarrow
// Narrow viewports render `spacious` gap as `normal`
@if $viewportRange != '-whenNarrow' {
.Stack--gap-spacious#{$viewportRange} {
--Stack-gap#{$viewportRange}: var(--primer-stack-gap-spacious, 24px);
}
}
// align
.Stack--align-start#{$viewportRange} {
align-items: flex-start;
}
.Stack--align-center#{$viewportRange} {
align-items: center;
}
.Stack--align-end#{$viewportRange} {
align-items: flex-end;
}
.Stack--align-baseline#{$viewportRange} {
align-items: baseline;
}
// alignWrap
.Stack--alignWrap-start#{$viewportRange} {
align-content: flex-start;
}
.Stack--alignWrap-center#{$viewportRange} {
align-content: center;
}
.Stack--alignWrap-end#{$viewportRange} {
align-content: flex-end;
}
.Stack--alignWrap-distribute#{$viewportRange} {
align-content: space-between;
}
.Stack--alignWrap-distributeEvenly#{$viewportRange} {
align-content: space-evenly;
}
// spread
.Stack--spread-start#{$viewportRange} {
justify-content: flex-start;
}
.Stack--spread-center#{$viewportRange} {
justify-content: center;
}
.Stack--spread-end#{$viewportRange} {
justify-content: flex-end;
}
.Stack--spread-distribute#{$viewportRange} {
justify-content: space-between;
}
.Stack--spread-distributeEvenly#{$viewportRange} {
justify-content: space-evenly;
}
// wrap
.Stack--wrap#{$viewportRange} {
flex-wrap: wrap;
}
.Stack--nowrap#{$viewportRange} {
flex-wrap: nowrap;
}
// showDividers
.Stack--showDividers#{$viewportRange} > .Stack-divider,
.Stack--showDividers#{$viewportRange} > .Stack-item > .Stack-divider {
display: block;
}
:not(.Stack--dir-inline#{$viewportRange}) > .Stack-divider,
:not(.Stack--dir-inline#{$viewportRange}) > .Stack-item > .Stack-divider {
border-block-end: var(--primer-borderWidth-thin, 1px) solid var(--Stack-divider-color);
inline-size: auto;
block-size: 0;
}
.Stack--dir-inline#{$viewportRange} > .Stack-divider,
.Stack--dir-inline#{$viewportRange} > .Stack-item > .Stack-divider {
border-inline-end: var(--primer-borderWidth-thin, 1px) solid var(--Stack-divider-color);
inline-size: 0;
block-size: auto;
}
}
// Stack-divider
.Stack-divider {
display: none;
padding: 0;
margin: 0;
border: 0;
align-self: stretch;
}
// Stack-item
.Stack-item {
flex: 0 1 auto;
min-inline-size: 0;
}
@mixin Stack-item--modifiers($viewportRange: '') {
.Stack-item--expand#{$viewportRange} {
flex-grow: 1;
}
.Stack-item--keepSize#{$viewportRange} {
flex-shrink: 0;
}
}
// Responsive composition
@media ($viewport-narrow) {
@include Stack--modifiers('-whenNarrow');
@include Stack-item--modifiers('-whenNarrow');
}
@media ($viewport-regular) {
@include Stack--modifiers('-whenRegular');
@include Stack-item--modifiers('-whenRegular');
}
@media ($viewport-wide) {
@include Stack--modifiers('-whenWide');
@include Stack-item--modifiers('-whenWide');
}

View File

@ -146,6 +146,13 @@ $breakpoints: (
xl: $width-xl
) !default;
// Viewport ranges
// Soon to be provided by Primer Primitives directly
// https://github.com/primer/primitives/blob/main/tokens/functional/size/viewport.json
$viewport-narrow: 'max-width: #{$width-md - 0.02px}' !default;
$viewport-regular: 'min-width: #{$width-md}' !default;
$viewport-wide: 'min-width: 1400px' !default;
// This map in the form (breakpoint: variant) is used to iterate over
// breakpoints and create both responsive and non-responsive classes in one
// loop: