From edac9edbcf833fc76d406d225b6cf6abfa2b7f99 Mon Sep 17 00:00:00 2001 From: Vinicius Depizzol Date: Mon, 25 Jul 2022 14:09:14 -0700 Subject: [PATCH] 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 * 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 Co-authored-by: Mike Perrotti --- .changeset/thick-coats-yawn.md | 5 + .../components/Layout/Stack.stories.jsx | 607 ++++++++++++++++++ .../Layout/StackExamples.stories.jsx | 79 +++ .../Layout/StackFeatures.stories.jsx | 151 +++++ src/layout/index.scss | 1 + src/layout/stack.scss | 220 +++++++ src/support/variables/layout.scss | 7 + 7 files changed, 1070 insertions(+) create mode 100644 .changeset/thick-coats-yawn.md create mode 100644 docs/src/stories/components/Layout/Stack.stories.jsx create mode 100644 docs/src/stories/components/Layout/StackExamples.stories.jsx create mode 100644 docs/src/stories/components/Layout/StackFeatures.stories.jsx create mode 100644 src/layout/stack.scss diff --git a/.changeset/thick-coats-yawn.md b/.changeset/thick-coats-yawn.md new file mode 100644 index 00000000..f665865c --- /dev/null +++ b/.changeset/thick-coats-yawn.md @@ -0,0 +1,5 @@ +--- +"@primer/css": minor +--- + +Add Stack component diff --git a/docs/src/stories/components/Layout/Stack.stories.jsx b/docs/src/stories/components/Layout/Stack.stories.jsx new file mode 100644 index 00000000..93203448 --- /dev/null +++ b/docs/src/stories/components/Layout/Stack.stories.jsx @@ -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) + + `, + 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 ( + <> +
+ {children} + + {!children && ( + <> +
1
+ {hasDividers && (
)} +
2
+ {hasDividers && (
)} +
3
+ {hasDividers && (
)} +
4
+ {hasDividers && (
)} +
5
+ {hasDividers && (
)} +
6
+ + )} +
+ + {_debug && ( + <> + + + )} + + ); +}; + +export const Playground = StackTemplate.bind({}) +Playground.args = { + _debug: true, + direction: "block", + gap: "normal", + align: "stretch", +}; \ No newline at end of file diff --git a/docs/src/stories/components/Layout/StackExamples.stories.jsx b/docs/src/stories/components/Layout/StackExamples.stories.jsx new file mode 100644 index 00000000..9d7322ef --- /dev/null +++ b/docs/src/stories/components/Layout/StackExamples.stories.jsx @@ -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: ( + <> + + + + + ) +}; + +export const PageSections = StackTemplate.bind({}) +PageSections.args = { + direction: "block", + gap: "normal", + children: ( + <> +
Section 1
+
Section 2
+
Section 3
+ + ) +}; + +export const Composition = StackTemplate.bind({}) +Composition.args = { + direction: "block", + gap: "normal", + children: ( + <> + + +

Heading

+
Lorem ipsum dolor sit amet avec consequer domulus sit lorem ipsum dolor sit amet.
+ + Inline labels set to wrap + Label 2 + Label 3 + Label 4 + Label 5 + + )} /> + + )} /> +
+ +

Heading

+
Lorem ipsum dolor sit amet avec consequer domulus sit lorem ipsum dolor sit amet.
+ + Inline labels set to wrap + Label 2 + Label 3 + Label 4 + Label 5 + + )} /> + + )} /> + + )} /> + + ) +}; \ No newline at end of file diff --git a/docs/src/stories/components/Layout/StackFeatures.stories.jsx b/docs/src/stories/components/Layout/StackFeatures.stories.jsx new file mode 100644 index 00000000..96158e7e --- /dev/null +++ b/docs/src/stories/components/Layout/StackFeatures.stories.jsx @@ -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: ( + <> +
Item 1
+
Item 2
+
Item 3
+
Item 4
+
Item 5
+ + ) +}; + +export const StackItems = StackTemplate.bind({}) +StackItems.args = { + direction: "inline", + gap: "normal", + wrap: "wrap", + narrow_gap: "condensed", + children: ( + <> +
+
Item 1
+
+
+
Item 2
+
+
+
Item 3
+
+
+
Item 4
+
+
+
Item 5
+
+ + ) +}; + +export const ExpandStackItem = StackTemplate.bind({}) +ExpandStackItem.args = { + direction: "inline", + wrap: "wrap", + gap: "normal", + narrow_gap: "condensed", + children: ( + <> +
+
Item 1 (expand)
+
+
+
Item 2
+
+
+
Item 3
+
+
+
Item 4
+
+
+
Item 5
+
+ + ) +}; + +export const ExpandStackItems = StackTemplate.bind({}) +ExpandStackItems.args = { + direction: "inline", + wrap: "wrap", + gap: "normal", + narrow_gap: "condensed", + children: ( + <> +
+
Item 1 (expand)
+
+
+
Item 2
+
+
+
Item 3
+
+
+
Item 4
+
+
+
Item 5 (expand)
+
+ + ) +}; + +export const ContentOverflow = StackTemplate.bind({}) +ContentOverflow.args = { + direction: "block", + gap: "normal", + narrow_gap: "condensed", + children: ( + <> +
+
+ 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. +
+
+
+
+ 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. +
+
+
+
+ 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. +
+
+
+
+ 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. +
+
+
+
+ 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. +
+
+
+
+ Item 6 +
+
+
+
+ Item 7 +
+
+ + ) +}; diff --git a/src/layout/index.scss b/src/layout/index.scss index 88c26c43..c35797ab 100644 --- a/src/layout/index.scss +++ b/src/layout/index.scss @@ -6,3 +6,4 @@ @import './grid-offset.scss'; @import './layout.scss'; @import './page-layout.scss'; +@import './stack.scss'; diff --git a/src/layout/stack.scss b/src/layout/stack.scss new file mode 100644 index 00000000..752a0b56 --- /dev/null +++ b/src/layout/stack.scss @@ -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'); +} diff --git a/src/support/variables/layout.scss b/src/support/variables/layout.scss index 47dbbf72..15abe59e 100644 --- a/src/support/variables/layout.scss +++ b/src/support/variables/layout.scss @@ -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: