Skip to content

Commit

Permalink
[#779] Add some layout primitives (stack) (#781)
Browse files Browse the repository at this point in the history
feat: Adds a `Stack` layout primitive

#779
  • Loading branch information
planktonic authored Dec 11, 2023
1 parent e9213a1 commit 304140e
Show file tree
Hide file tree
Showing 13 changed files with 292 additions and 3 deletions.
3 changes: 2 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@
### Added

- Badge with Label, added an example showing a text label rendered next to a badge component, to the badge docs.
- A new layout component at `atoms/switcher`, that lays out its children in a horizontal row with consistent spacing between children. The layout switches to a vertical stack once the width of the component passes below a threshold, or the number of children goes over a limit.
- A new layout component at `atoms/stack`, that lays out its children vertically, with consistent spacing between children.

## [[6.0.0]](https://github.com/bitcrowd/bitstyles/releases/tag/v6.0.0) - 2023-06-08

Expand Down Expand Up @@ -46,7 +48,6 @@
- It is now possible to import bitstyles on a per-layer basis, instead of per-module or all at once. It is still possible to override all modules inside each layer in the normal way.
- There are now design tokens as `design-tokens/focus` to describe a consistent `:focus` appearance, that are currently used in `base/anchor/`, `atoms/buttons`, and `atoms/links`.
- Anchor elements and `atoms/link` components now have a disabled state, applied using the `aria-disabled` attribute.
- A new layout component at `atoms/switcher`, that lays out its children in a horizontal row with consistent spacing between children. The layout switches to a vertical stack once the width of the component passes below a threshold, or the number of children goes over a limit.

## [[5.0.0]](https://github.com/bitcrowd/bitstyles/releases/tag/v5.0.0) - 2023-01-03

Expand Down
1 change: 1 addition & 0 deletions scss/bitstyles/atoms/_index.scss
Original file line number Diff line number Diff line change
Expand Up @@ -14,4 +14,5 @@
@forward './link' as link-*;
@forward './skip-link' as skip-link-*;
@forward './switcher' as switcher-*;
@forward './stack' as stack-*;
@forward './topbar' as topbar-*;
4 changes: 2 additions & 2 deletions scss/bitstyles/atoms/button/Button.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { generateLabel } from '../../../../.storybook/helpers';
import { generateButtonLabel } from '../../../../.storybook/helpers';

export default ({
children,
Expand All @@ -16,7 +16,7 @@ export default ({
const button = document.createElement(element);
button.innerHTML =
children ||
generateLabel(
generateButtonLabel(
shapeVariant,
colorVariant,
disabled || ariaDisabled,
Expand Down
50 changes: 50 additions & 0 deletions scss/bitstyles/atoms/stack/Stack.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
import { generateLabel } from '../../../../.storybook/helpers';

const StackItem = ({
children,
backgroundColor = 'var(--bs-color-grayscale-light-2)',
}) => {
const stackItem = document.createElement('div');
stackItem.style.backgroundColor = backgroundColor;
stackItem.style.padding = 'var(--bs-content-padding-base)';
stackItem.style.borderRadius = 'var(--bs-size-s3)';
stackItem.style.minHeight = '6rem';
stackItem.innerHTML = children;
return stackItem;
};

const Stack = ({
length = 3,
classname = [],
sizeVariant = '',
itemColor,
labelPrefix = 'stack',
children = [],
}) => {
const stack = document.createElement('div');
stack.classList.add('a-stack');
if (sizeVariant) {
stack.classList.add(`a-stack--${sizeVariant}`);
}

classname.forEach((cls) => {
stack.classList.add(cls);
});

if (children.length) {
children.forEach((child) => stack.append(child));
} else {
for (let child = 0; child < length; child += 1) {
stack.append(
StackItem({
children: generateLabel([labelPrefix, 'child', child + 1]),
backgroundColor: itemColor,
})
);
}
}

return stack;
};

export { StackItem, Stack };
45 changes: 45 additions & 0 deletions scss/bitstyles/atoms/stack/_index.scss
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
@forward 'settings';
@use './settings';
@use '../../tools/classname';
@use '../../tools/design-token';
@use '../../tools/media-query';

#{classname.get($classname-items: 'stack', $layer: 'atom')} {
display: flex;
flex-direction: column;
justify-content: flex-start;

&:only-child {
height: 100%;
}

> * {
margin-top: 0;
margin-bottom: 0;
}

> * + * {
margin-top: var(design-token.get('stack', 'spacing'));
}
}

@each $breakpoint, $size-variants in settings.$size-variants {
@include media-query.get($breakpoint) {
@each $size-variant-name, $padding in ($size-variants) {
$class: '';
@if $size-variant-name == '' {
$class: 'stack';
} @else {
$class: 'stack--#{$size-variant-name}';
}

#{classname.get($classname-items: $class, $layer: 'atom')} {
/* stylelint-disable max-nesting-depth */
> * + * {
#{design-token.get('stack', 'spacing')}: $padding;
}
/* stylelint-enable max-nesting-depth */
}
}
}
}
11 changes: 11 additions & 0 deletions scss/bitstyles/atoms/stack/_settings.scss
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
@use '../../settings/setup';
@use '../../tools/design-token';

$size-variants: (
'#{setup.$no-media-query}': (
'': var(design-token.get('content', 'padding', 'base')),
),
'm': (
'': var(design-token.get('content', 'padding', 'l')),
),
) !default;
34 changes: 34 additions & 0 deletions scss/bitstyles/atoms/stack/stack.stories.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
import { Stack, StackItem } from './Stack';
import { generateLabel } from '../../../../.storybook/helpers';

export default {
title: 'Atoms/Stack',
component: Stack,
argTypes: {},
};

const Template = (args) => Stack(args);

// ***** Size variants ****************** //

export const Base = Template.bind({});
Base.args = { length: 4 };

export const Nested = () => {
const innerStack = Stack({
length: 2,
itemColor: 'var(--bs-color-grayscale-light-4)',
labelPrefix: 'stack 1 child 3 — stack 2',
});
const children = [
StackItem({ children: generateLabel(['stack 1', 'child 1']) }),
StackItem({ children: generateLabel(['stack 1', 'child 2']) }),
innerStack,
StackItem({ children: generateLabel(['stack 1', 'child 4']) }),
StackItem({ children: generateLabel(['stack 1', 'child 5']) }),
];
const outerStack = Stack({ children });

outerStack.insertBefore(innerStack, outerStack.childNodes[2]);
return outerStack;
};
69 changes: 69 additions & 0 deletions scss/bitstyles/atoms/stack/stack.stories.mdx
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
import { Canvas, Meta, Story } from '@storybook/addon-docs';

<Meta title="Atoms/Stack/Overview" />

<Canvas>
<Story id="atoms-stack--base" />
</Canvas>

# Stack

A layout atom that stacks its children vertically and ensures consistent space between each. This component is responsive, applying larger spacing between children when rendered on larger viewports.

This layout is suitable for large blocks of content, such as the sections in the main content of a page.

The spacing and the breakpoints the component responds to can be [customized](#customization). You can also add extra size variants of the stack, that apply different spacing, but the default configuration provides only one.

Stacks can be nested — children of a stack can themselves be stacks — while the spacing will remain consistent:

<Canvas>
<Story id="atoms-stack--nested" />
</Canvas>

## Customization

The component expects a Sass list of Sass maps, with the keys being the name of the breakpoint (use `setup.$no-media-query` for the base mobile-first styles) and the values being the name of the stack variant. You can change the breakpoint names or add new breakpoints if you want the component to apply different spacing at extra breakpoints (in which case you probably also want to [edit the available `content` padding design tokens](/docs/design-tokens-content--page) available to you, though you can also pass `size` design tokens directly)

```scss
@use '~bitstyles/scss/bitstyles/atoms/stack' with (
$size-variants: (
'#{setup.$no-media-query}': (
'': var(design-token.get('content', 'padding', 'base')),
),
'm': (
'': var(design-token.get('content', 'padding', 'l')),
),
'l': (
'': var(design-token.get('content', 'padding', 'xl')),
// this value of content-padding would need to be added to the content design tokens
),
)
);
```

### Extra size variants

The keys of the spacing values above are deliberately left blank — that results in those spacing values being applied to the base `a-stack` component. If you provide a key, that will be used to create a stack variant:

```scss
@use '~bitstyles/scss/bitstyles/atoms/stack' with (
$size-variants: (
'#{setup.$no-media-query}': (
'': var(design-token.get('content', 'padding', 'base')),
'large': var(design-token.get('content', 'padding', 'l')),
),
)
);
```

Produces CSS similar to the following:

```css
.a-stack > * + * {
margin-top: var(--bs-content-padding-base);
}

.a-stack--large > * + * {
margin-top: var(--bs-content-padding-l);
}
```
26 changes: 26 additions & 0 deletions test/scss/fixtures/bitstyles-overrides.css
Original file line number Diff line number Diff line change
Expand Up @@ -2055,6 +2055,32 @@ table {
--bscpn-switcher-spacing: var(--bscpn-content-padding-l1);
}
}
.bs-at-stack {
display: flex;
flex-direction: column;
justify-content: flex-start;
}
.bs-at-stack:only-child {
height: 100%;
}
.bs-at-stack > * {
margin-bottom: 0;
margin-top: 0;
}
.bs-at-stack > * + * {
margin-top: var(--bscpn-stack-spacing);
}
.bs-at-stack > * + * {
--bscpn-stack-spacing: var(--bscpn-content-padding-base);
}
.bs-at-stack--large > * + * {
--bscpn-stack-spacing: var(--bscpn-content-padding-l);
}
@media screen and (min-width: 30em) {
.bs-at-stack > * + * {
--bscpn-stack-spacing: var(--bscpn-content-padding-l);
}
}
.bs-at-topbar {
left: 0;
padding: 10rem var(--bscpn-size-s1);
Expand Down
23 changes: 23 additions & 0 deletions test/scss/fixtures/bitstyles.css
Original file line number Diff line number Diff line change
Expand Up @@ -2485,6 +2485,29 @@ table {
--bs-switcher-spacing: var(--bs-size-s3);
}
}
.a-stack {
display: flex;
flex-direction: column;
justify-content: flex-start;
}
.a-stack:only-child {
height: 100%;
}
.a-stack > * {
margin-bottom: 0;
margin-top: 0;
}
.a-stack > * + * {
margin-top: var(--bs-stack-spacing);
}
.a-stack > * + * {
--bs-stack-spacing: var(--bs-content-padding-base);
}
@media screen and (min-width: 30em) {
.a-stack > * + * {
--bs-stack-spacing: var(--bs-content-padding-l);
}
}
.a-topbar {
left: 0;
padding: var(--bs-size-s3) var(--bs-size-s1);
Expand Down
9 changes: 9 additions & 0 deletions test/scss/test-use-all.scss
Original file line number Diff line number Diff line change
Expand Up @@ -142,6 +142,15 @@
)
),
$icon-sizes: ('s': 10rem),
$stack-size-variants: (
'no-mq': (
'': var(--bscpn-content-padding-base),
'large': var(--bscpn-content-padding-l),
),
'm': (
'': var(--bscpn-content-padding-l),
)
),
$skip-link-color: #f00,
$switcher-size-variants: (
'no-mq': (
Expand Down
11 changes: 11 additions & 0 deletions test/scss/test-use-each.scss
Original file line number Diff line number Diff line change
Expand Up @@ -251,6 +251,17 @@
),
)
);
@use '../../scss/bitstyles/atoms/stack' with (
$size-variants: (
'no-mq': (
'': var(--bscpn-content-padding-base),
'large': var(--bscpn-content-padding-l),
),
'm': (
'': var(--bscpn-content-padding-l),
),
)
);
@use '../../scss/bitstyles/atoms/topbar' with (
$vertical-padding: 10rem
);
Expand Down
9 changes: 9 additions & 0 deletions test/scss/test-use-layers.scss
Original file line number Diff line number Diff line change
Expand Up @@ -172,6 +172,15 @@
),
),
),
$stack-size-variants: (
'no-mq': (
'': var(--bscpn-content-padding-base),
'large': var(--bscpn-content-padding-l),
),
'm': (
'': var(--bscpn-content-padding-l),
),
),
$topbar-vertical-padding: 10rem
);
@use '../../scss/bitstyles/organisms' with (
Expand Down

0 comments on commit 304140e

Please sign in to comment.