Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .changeset/friendly-radios-sparkle.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@shopify/polaris': minor
---

Update `AlphaStack` to be polymorphic, add list reset styles and allow spacing to change based on breakpoint
32 changes: 31 additions & 1 deletion polaris-react/src/components/AlphaStack/AlphaStack.scss
Original file line number Diff line number Diff line change
@@ -1,14 +1,44 @@
@import '../../styles/common';

.AlphaStack {
--pc-stack-spacing-xs: var(--p-space-4);
--pc-stack-spacing-sm: var(--pc-stack-spacing-xs);
--pc-stack-spacing-md: var(--pc-stack-spacing-sm);
--pc-stack-spacing-lg: var(--pc-stack-spacing-md);
--pc-stack-spacing-xl: var(--pc-stack-spacing-lg);
display: flex;
flex-direction: column;
align-items: var(--pc-stack-align);
gap: var(--pc-stack-spacing);
gap: var(--pc-stack-spacing-xs);

@media #{$p-breakpoints-sm-up} {
gap: var(--pc-stack-spacing-sm);
}

@media #{$p-breakpoints-md-up} {
gap: var(--pc-stack-spacing-md);
}

@media #{$p-breakpoints-lg-up} {
gap: var(--pc-stack-spacing-lg);
}

@media #{$p-breakpoints-xl-up} {
gap: var(--pc-stack-spacing-xl);
}

> * {
max-width: 100%;
}
}

.listReset {
list-style-type: none;
margin-block-start: 0;
margin-block-end: 0;
padding-inline-start: 0;
}

.fullWidth {
> * {
width: 100%;
Expand Down
11 changes: 11 additions & 0 deletions polaris-react/src/components/AlphaStack/AlphaStack.stories.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -60,3 +60,14 @@ export function FullWidthChildren() {
</AlphaStack>
);
}

export function ResponsiveSpacing() {
return (
<AlphaStack spacing={{xs: '4', md: '10'}}>
<Badge>Paid</Badge>
<Badge>Processing</Badge>
<Badge>Fulfilled</Badge>
<Badge>Completed</Badge>
</AlphaStack>
);
}
32 changes: 24 additions & 8 deletions polaris-react/src/components/AlphaStack/AlphaStack.tsx
Original file line number Diff line number Diff line change
@@ -1,24 +1,36 @@
import React from 'react';
import React, {createElement} from 'react';
import type {SpacingSpaceScale} from '@shopify/polaris-tokens';

import {classNames} from '../../utilities/css';
import {
classNames,
sanitizeCustomProperties,
getResponsiveProps,
} from '../../utilities/css';
import type {ResponsiveProp} from '../../utilities/css';

import styles from './AlphaStack.scss';

type Align = 'start' | 'end' | 'center';

type Element = 'div' | 'ul' | 'ol' | 'fieldset';

type Spacing = ResponsiveProp<SpacingSpaceScale>;

export interface AlphaStackProps {
/** HTML Element type */
as?: Element;
/** Elements to display inside stack */
children?: React.ReactNode;
/** Adjust vertical alignment of elements */
align?: Align;
/** Toggle elements to be full width */
fullWidth?: boolean;
/** Adjust spacing between elements */
spacing?: SpacingSpaceScale;
spacing?: Spacing;
}

export const AlphaStack = ({
as = 'div',
children,
align = 'start',
fullWidth,
Expand All @@ -27,16 +39,20 @@ export const AlphaStack = ({
const className = classNames(
styles.AlphaStack,
fullWidth && styles.fullWidth,
as === 'ul' && styles.listReset,
);

const style = {
'--pc-stack-align': align ? `${align}` : '',
...(spacing ? {'--pc-stack-spacing': `var(--p-space-${spacing})`} : {}),
...getResponsiveProps('stack', 'spacing', 'space', spacing),
} as React.CSSProperties;

return (
<div className={className} style={style}>
{children}
</div>
return createElement(
as,
{
className,
style: sanitizeCustomProperties(style),
},
children,
);
};
25 changes: 19 additions & 6 deletions polaris-react/src/components/AlphaStack/tests/AlphaStack.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -17,10 +17,10 @@ describe('<AlphaStack />', () => {
const stack = mountWithApp(<AlphaStack>{children}</AlphaStack>);

expect(stack).toContainReactComponent('div', {
style: {
style: expect.objectContaining({
'--pc-stack-align': 'start',
'--pc-stack-spacing': 'var(--p-space-4)',
} as React.CSSProperties,
'--pc-stack-spacing-xs': 'var(--p-space-4)',
}) as React.CSSProperties,
});
});

Expand All @@ -32,10 +32,23 @@ describe('<AlphaStack />', () => {
);

expect(stack).toContainReactComponent('div', {
style: {
style: expect.objectContaining({
'--pc-stack-align': 'center',
'--pc-stack-spacing': 'var(--p-space-10)',
} as React.CSSProperties,
'--pc-stack-spacing-xs': 'var(--p-space-10)',
}) as React.CSSProperties,
});
});

it('accepts spacing based on breakpoints', () => {
const stack = mountWithApp(
<AlphaStack spacing={{xs: '2', md: '8'}}>{children}</AlphaStack>,
);

expect(stack).toContainReactComponent('div', {
style: expect.objectContaining({
'--pc-stack-spacing-md': 'var(--p-space-8)',
'--pc-stack-spacing-xs': 'var(--p-space-2)',
}) as React.CSSProperties,
});
});
});
32 changes: 32 additions & 0 deletions polaris-react/src/utilities/css.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,13 @@
import type {BreakpointsAlias} from '@shopify/polaris-tokens';

type Falsy = boolean | undefined | null | 0;

export type ResponsiveProp<T> =
| T
| {
[Breakpoint in BreakpointsAlias]?: T;
};
Comment on lines +5 to +9
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Wondering if this should be exported from tokens instead of this css utils file? Thoughts on moving it?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@aaronccasanova would be great to get your thoughts on this


export function classNames(...classes: (string | Falsy)[]) {
return classes.filter(Boolean).join(' ');
}
Expand All @@ -17,3 +25,27 @@ export function sanitizeCustomProperties(

return nonNullValues.length ? Object.fromEntries(nonNullValues) : undefined;
}

export function getResponsiveProps(
componentName: string,
componentProp: string,
tokenSubgroup: string,
responsiveProp:
| string
| {
[Breakpoint in BreakpointsAlias]?: string;
},
) {
if (typeof responsiveProp === 'string') {
return {
[`--pc-${componentName}-${componentProp}-xs`]: `var(--p-${tokenSubgroup}-${responsiveProp})`,
};
}

return Object.fromEntries(
Object.entries(responsiveProp).map(([breakpointAlias, aliasOrScale]) => [
`--pc-${componentName}-${componentProp}-${breakpointAlias}`,
`var(--p-${tokenSubgroup}-${aliasOrScale})`,
]),
);
}
19 changes: 18 additions & 1 deletion polaris-react/src/utilities/tests/css.test.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import {classNames, variationName} from '../css';
import {classNames, variationName, getResponsiveProps} from '../css';

describe('classNames', () => {
it('returns a single string from multiple classes', () => {
Expand All @@ -24,3 +24,20 @@ describe('variationName', () => {
expect(variationName('size', 'medium')).toBe('sizeMedium');
});
});

describe('getResponsiveProps', () => {
it('takes a string and returns the custom property', () => {
expect(getResponsiveProps('stack', 'space', 'space', '4')).toMatchObject({
'--pc-stack-space-xs': 'var(--p-space-4)',
});
});

it('takes an object with a breakpoint and value and returns the property for each breakpoint', () => {
expect(
getResponsiveProps('stack', 'space', 'space', {xs: '2', md: '8'}),
).toMatchObject({
'--pc-stack-space-xs': 'var(--p-space-2)',
'--pc-stack-space-md': 'var(--p-space-8)',
});
});
});