From eb885d1b6331505724cb5053262db1293605c6c3 Mon Sep 17 00:00:00 2001 From: Nathan Kanigsberg Date: Tue, 30 May 2023 12:54:57 -0400 Subject: [PATCH 1/2] overhaul AutoLayout Alignment property (incl. stories) --- .../auto-layout-alignment.stories.tsx | 356 +++++++++--------- .../auto-layout/auto-layout.styles.ts | 32 +- .../components/auto-layout/auto-layout.tsx | 4 +- .../auto-layout/auto-layout.types.ts | 60 ++- 4 files changed, 265 insertions(+), 187 deletions(-) diff --git a/library/core-components/components/auto-layout/auto-layout-alignment.stories.tsx b/library/core-components/components/auto-layout/auto-layout-alignment.stories.tsx index 187fe5ea..e4ea5a58 100644 --- a/library/core-components/components/auto-layout/auto-layout-alignment.stories.tsx +++ b/library/core-components/components/auto-layout/auto-layout-alignment.stories.tsx @@ -1,9 +1,35 @@ -import React, { ComponentProps } from 'react'; -import { Meta } from '@storybook/react'; +import React from 'react'; +import { Meta, StoryObj } from '@storybook/react'; import { BADGE } from '@geometricpanda/storybook-addon-badges'; import { RadiusAutoLayout } from './auto-layout'; -import { Stories, Title, Description } from '@storybook/addon-docs'; +import { AutoLayoutExtendedProps } from './auto-layout.types'; +const alignmentOptions = [ + 'topLeft', + 'topCenter', + 'topRight', + 'left', + 'center', + 'right', + 'bottomLeft', + 'bottomCenter', + 'bottomRight', +] as const; + +/** + * The RadiusAutoLayout component duplicates the behaviour of Figma AutoLayout's + * Alignment property. This deviates from how CSS flexbox works in some cases - + * for example when the `direction` is changed, the alignment of the children + * remains the same instead of being dependent on the flex-direction. Also, when + * the `space` property is set to `auto`, this is equivalent to writing + * `justify-content: space-between`, which evenly spaces children regardless of + * direction. + * + * Please see the examples of the different behaviours below. + * + * ## Resources + * [How Figma Alignment Works](https://help.figma.com/hc/en-us/articles/360040451373-Explore-auto-layout-properties#alignment) + */ const meta: Meta = { component: RadiusAutoLayout, title: 'Component Development Kit / Auto Layout / Alignment', @@ -15,194 +41,180 @@ const meta: Meta = { patch: process.env.COMPONENT_VERSION?.[2], }, badges: [BADGE.BETA], - docs: { - page: () => ( - <> - Alignment - - - - ), + controls: { + // only show controls relevant to this story + include: ['alignment', 'direction', 'space'], }, }, -}; - -export default meta; -// type Story = StoryObj; -// TODO: apply `Story` type to all stories - causes issues due to parent and children args not existing in original component - -const ThreeBoxesTemplateAlignmentHor = { - render: (args: { - parent: ComponentProps; - children: ComponentProps; - }) => ( - - - - - - - - ), -}; - -export const AlignmentTop = { - ...ThreeBoxesTemplateAlignmentHor, - args: { - parent: { - direction: 'horizontal', - space: 'auto', - alignment: 'top', + argTypes: { + alignment: { + options: alignmentOptions, }, - children: { - width: 100, + direction: { + options: ['horizontal', 'vertical'], }, - }, - parameters: { - controls: { - disable: true, + space: { + options: { + auto: 'auto', + fixed: { css: '10px' }, + }, + table: { defaultValue: { summary: '10px' } }, }, }, -}; - -export const AlignmentCenter = { - ...ThreeBoxesTemplateAlignmentHor, args: { - parent: { - direction: 'horizontal', - space: 'auto', - alignment: 'center', - }, - children: { - width: 100, - }, - }, - parameters: { - controls: { - disable: true, - }, + alignment: 'topLeft', + direction: 'vertical', }, }; -export const AlignmentBottom = { - ...ThreeBoxesTemplateAlignmentHor, - args: { - parent: { - direction: 'horizontal', - space: 'auto', - alignment: 'bottom', - }, - children: { - width: 100, - }, - }, - parameters: { - controls: { - disable: true, - }, - sidebar: { disabled: true }, - }, +export default meta; +type Story = StoryObj; + +const PADDING = 24; +const CIRCLE_SIZE = 12; + +/** A circle to show the alignment of the child AutoLayouts */ +const Circle = ({ + top, + left, + bottom, + right, +}: { + top?: number | string; + left?: number | string; + bottom?: number | string; + right?: number | string; +}) => { + return ( +
+ ); }; -const ThreeBoxesTemplateAlignmentVert = { - render: (args: { - parent: ComponentProps; - children: ComponentProps; - }) => ( - +/** The positions of each of the circles */ +const circles = [ + { top: PADDING, left: PADDING }, + { top: PADDING, left: `calc(50% - ${CIRCLE_SIZE / 2}px)` }, + { top: PADDING, right: PADDING }, + { top: `calc(50% - ${CIRCLE_SIZE / 2}px)`, left: PADDING }, + { + top: `calc(50% - ${CIRCLE_SIZE / 2}px)`, + left: `calc(50% - ${CIRCLE_SIZE / 2}px)`, + }, + { top: `calc(50% - ${CIRCLE_SIZE / 2}px)`, right: PADDING }, + { bottom: PADDING, left: PADDING }, + { bottom: PADDING, left: `calc(50% - ${CIRCLE_SIZE / 2}px)` }, + { bottom: PADDING, right: PADDING }, +]; + +const AlignmentDemo = ({ + alignment, + direction, + space, +}: { + alignment: AutoLayoutExtendedProps['alignment']; + direction: AutoLayoutExtendedProps['direction']; + space: AutoLayoutExtendedProps['space']; +}) => { + return ( + + - - - - + width={direction === 'vertical' ? '113px' : '12px'} + height={direction === 'vertical' ? '12px' : '113px'} + fill={{ css: '#F7856E' }} + style={{ zIndex: 1 }} + /> + + {circles.map((positionProps) => ( + + ))} + ); +}; + +export const Alignment: Story = { + // @ts-expect-error - bug with `args` type inference due to polymorphism + render: ({ alignment, direction, space }: AutoLayoutExtendedProps) => ( + ), }; -export const AlignmentLeft = { - ...ThreeBoxesTemplateAlignmentVert, - args: { - parent: { - direction: 'vertical', - space: 'auto', - alignment: 'left', - }, - children: { - width: 100, - }, - }, - parameters: { - controls: { - disable: true, - }, - }, +const GridTemplate = ({ direction, space }: AutoLayoutExtendedProps) => { + return ( +
+ {alignmentOptions.map((alignmentOption) => ( +
+

Alignment {alignmentOption}

+ +
+ ))} +
+ ); }; -export const AlignmentCenterVertically = { - ...ThreeBoxesTemplateAlignmentVert, - args: { - parent: { - direction: 'vertical', - space: 'auto', - alignment: 'center', - }, - children: { - width: 100, - }, - }, - parameters: { - controls: { - disable: true, - }, - }, +export const VerticalWithFixedSpacing: Story = { + render: () => , }; -export const AlignmentRight = { - ...ThreeBoxesTemplateAlignmentVert, - args: { - parent: { - direction: 'vertical', - space: 'auto', - alignment: 'right', - }, - }, - parameters: { - name: 'Right', - Title: 'Right', - namespace: 'Right', - controls: { - disable: true, - }, - sidebar: { disabled: true }, - }, +export const HorizontalWithFixedSpacing: Story = { + render: () => , +}; + +export const VerticalWithAutoSpacing: Story = { + render: () => , +}; + +export const HorizontalWithAutoSpacing: Story = { + render: () => , }; diff --git a/library/core-components/components/auto-layout/auto-layout.styles.ts b/library/core-components/components/auto-layout/auto-layout.styles.ts index 1ccbbf19..d271d0b4 100644 --- a/library/core-components/components/auto-layout/auto-layout.styles.ts +++ b/library/core-components/components/auto-layout/auto-layout.styles.ts @@ -2,7 +2,7 @@ import { CSSExpression } from '@rangle/radius-foundations'; import { renderCSSProp, css, RequireAndPick } from '../../utils'; import { - mapAlignments, + flexLayouts, mapStrokeAlign, AutolayoutSize, Size, @@ -64,6 +64,25 @@ const getShadow = ( return shadows.length ? shadows.join(', ') : undefined; }; +/** + * Function that returns the `align-items` and `justify-content` properties + * given the alignment and direction props. This allows us to map the Figma + * alignment prop to the correct flexbox properties. + */ +const getAlignment = ( + alignment: StyleProps['alignment'], + direction: StyleProps['direction'] +) => { + // TODO: We need a computed flex-direction here in cases where the direction + // is provided by token or CSS prop. + if (direction !== 'horizontal' && direction !== 'vertical') return ''; + + const { alignItems, justifyContent } = flexLayouts[direction][alignment]; + + return `align-items: ${alignItems}; + justify-content: ${justifyContent};`; +}; + /** When no item spacing is specified, figma defaults to 10px */ const FIGMA_DEFAULT_SPACING = '10px'; @@ -73,7 +92,6 @@ const FIGMA_DEFAULT_SPACING = '10px'; */ export type StyleProps = Pick< AutoLayoutExtendedProps, - | 'direction' | 'space' | 'padding' | 'opacity' @@ -99,6 +117,7 @@ export type StyleProps = Pick< RequireAndPick< AutoLayoutExtendedProps, | 'alignment' + | 'direction' | 'width' | 'height' | 'clippedContent' @@ -148,7 +167,7 @@ export const useStyles = ({ direction, //flex-direction space, // is either auto or number or zero clippedContent, //overflow:hidden - alignment, //align-items + alignment, width, //width height, //height padding, //padding @@ -185,12 +204,15 @@ export const useStyles = ({ : renderCSSProp(direction)}; margin: 0; box-sizing: ${mapStrokeAlign[strokeAlign || 'inside']}; - align-items: ${mapAlignments[alignment]}; + + ${getAlignment(alignment, direction)} + width: ${getSize(width)}; height: ${getSize(height)}; - ${isParent ? 'position: relative;' : ''} ${absolutePosition ? 'position: absolute;' : ''} + // note the justify-content property set by the space property will + // override the value set by the getAlignment function above ${space === 'auto' ? 'justify-content: space-between;' : `gap: ${renderCSSProp(space ?? { css: FIGMA_DEFAULT_SPACING })};`}; diff --git a/library/core-components/components/auto-layout/auto-layout.tsx b/library/core-components/components/auto-layout/auto-layout.tsx index d8174a0a..c2a62f64 100644 --- a/library/core-components/components/auto-layout/auto-layout.tsx +++ b/library/core-components/components/auto-layout/auto-layout.tsx @@ -37,10 +37,10 @@ export const RadiusAutoLayout: AutoLayoutComponent = forwardRef( className, isParent = false, absolutePosition = false, - direction, + direction = 'horizontal', space, clippedContent = false, - alignment = 'top', + alignment = 'topLeft', width = 'hug-contents', height = 'hug-contents', padding, diff --git a/library/core-components/components/auto-layout/auto-layout.types.ts b/library/core-components/components/auto-layout/auto-layout.types.ts index 1a92ff11..3a45d3e1 100644 --- a/library/core-components/components/auto-layout/auto-layout.types.ts +++ b/library/core-components/components/auto-layout/auto-layout.types.ts @@ -4,14 +4,58 @@ import { } from '@rangle/radius-foundations/generated/design-tokens.types'; import { PolymorphicComponentPropWithRef } from '../../utils'; -export const mapAlignments = { - top: 'flex-start', - center: 'center', - bottom: 'flex-end', - left: 'flex-start', - right: 'flex-end', +type FlexAlignment = { + alignItems: string; + justifyContent: string; }; +type FlexDirectionLayout = { + topLeft: FlexAlignment; + topCenter: FlexAlignment; + topRight: FlexAlignment; + left: FlexAlignment; + center: FlexAlignment; + right: FlexAlignment; + bottomLeft: FlexAlignment; + bottomCenter: FlexAlignment; + bottomRight: FlexAlignment; +}; + +/** + * A map of alignments to flexbox alignment properties, corresponding to Figma's + * AutoLayout alignment options. + * + * Note: `auto` spacing alignment names are slightly different and may need to + * be added at a later date. + */ +export const flexLayouts: Record< + 'horizontal' | 'vertical', + FlexDirectionLayout +> = { + horizontal: { + topLeft: { alignItems: 'flex-start', justifyContent: 'flex-start' }, + topCenter: { alignItems: 'flex-start', justifyContent: 'center' }, + topRight: { alignItems: 'flex-start', justifyContent: 'flex-end' }, + left: { alignItems: 'center', justifyContent: 'flex-start' }, + center: { alignItems: 'center', justifyContent: 'center' }, + right: { alignItems: 'center', justifyContent: 'flex-end' }, + bottomLeft: { alignItems: 'flex-end', justifyContent: 'flex-start' }, + bottomCenter: { alignItems: 'flex-end', justifyContent: 'center' }, + bottomRight: { alignItems: 'flex-end', justifyContent: 'flex-end' }, + }, + vertical: { + topLeft: { alignItems: 'flex-start', justifyContent: 'flex-start' }, + topCenter: { alignItems: 'center', justifyContent: 'flex-start' }, + topRight: { alignItems: 'flex-end', justifyContent: 'flex-start' }, + left: { alignItems: 'flex-start', justifyContent: 'center' }, + center: { alignItems: 'center', justifyContent: 'center' }, + right: { alignItems: 'flex-end', justifyContent: 'center' }, + bottomLeft: { alignItems: 'flex-start', justifyContent: 'flex-end' }, + bottomCenter: { alignItems: 'center', justifyContent: 'flex-end' }, + bottomRight: { alignItems: 'flex-end', justifyContent: 'flex-end' }, + }, +} as const; + export const mapStrokeAlign = { inside: 'border-box', outside: 'content-box', @@ -77,8 +121,8 @@ export type AutoLayoutExtendedProps = { space?: CSSProp<'spacing'> | 'auto'; // auto = justify-content: space-between; /** Whether the content should be clipped or not, uses overflow: hidden */ clippedContent?: boolean; - /** The alignment of the layout, uses align-items */ - alignment?: keyof typeof mapAlignments; + /** The alignment of the layout, uses a combination of `align-items` and `justify-content` */ + alignment?: keyof FlexDirectionLayout; /** The width of the layout, can be number, percentage, hug-contents (auto) or fill-parent (100%) */ width?: AutolayoutSize; /** The height of the layout, can be number, percentage, hug-contents (auto) or fill-parent (100%) */ From 05b2c34fee8792e2ee441a78ee57c0416f5cfa90 Mon Sep 17 00:00:00 2001 From: Nathan Kanigsberg Date: Tue, 6 Jun 2023 11:29:13 -0400 Subject: [PATCH 2/2] clean up, add link, change design to squares --- .../auto-layout-alignment.stories.tsx | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/library/core-components/components/auto-layout/auto-layout-alignment.stories.tsx b/library/core-components/components/auto-layout/auto-layout-alignment.stories.tsx index e4ea5a58..d2079f74 100644 --- a/library/core-components/components/auto-layout/auto-layout-alignment.stories.tsx +++ b/library/core-components/components/auto-layout/auto-layout-alignment.stories.tsx @@ -29,6 +29,8 @@ const alignmentOptions = [ * * ## Resources * [How Figma Alignment Works](https://help.figma.com/hc/en-us/articles/360040451373-Explore-auto-layout-properties#alignment) + * + * [RadiusAutoLayout Figma Specs](https://www.figma.com/file/ODAUZaQxH8oH2GI0A9MAVb/Radius-Booster---Auto-Layout?type=design&node-id=1302-3736&t=Fh2ap7gIybG92aBU-0) */ const meta: Meta = { component: RadiusAutoLayout, @@ -63,7 +65,7 @@ const meta: Meta = { }, args: { alignment: 'topLeft', - direction: 'vertical', + direction: 'horizontal', }, }; @@ -140,20 +142,20 @@ const AlignmentDemo = ({ isParent >