Skip to content
Draft
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
30 changes: 30 additions & 0 deletions packages/gamut-styles/src/variance/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -245,6 +245,21 @@ export const margin = {
mb: { property: 'marginBottom', scale: 'spacing' },
mr: { property: 'marginRight', scale: 'spacing' },
ml: { property: 'marginLeft', scale: 'spacing' },
// Logical properties
mbl: {
property: 'margin',
properties: ['marginBlockStart', 'marginBlockEnd'],
scale: 'spacing',
},
mbls: { property: 'marginBlockStart', scale: 'spacing' },
mble: { property: 'marginBlockEnd', scale: 'spacing' },
mi: {
property: 'margin',
properties: ['marginInlineStart', 'marginInlineEnd'],
scale: 'spacing',
},
mis: { property: 'marginInlineStart', scale: 'spacing' },
mie: { property: 'marginInlineEnd', scale: 'spacing' },
} as const;

export const padding = {
Expand All @@ -263,6 +278,21 @@ export const padding = {
pb: { property: 'paddingBottom', scale: 'spacing' },
pr: { property: 'paddingRight', scale: 'spacing' },
pl: { property: 'paddingLeft', scale: 'spacing' },
// Logical properties
pbl: {
property: 'padding',
properties: ['paddingBlockStart', 'paddingBlockEnd'],
scale: 'spacing',
},
pbls: { property: 'paddingBlockStart', scale: 'spacing' },
pble: { property: 'paddingBlockEnd', scale: 'spacing' },
pi: {
property: 'padding',
properties: ['paddingInlineStart', 'paddingInlineEnd'],
scale: 'spacing',
},
pis: { property: 'paddingInlineStart', scale: 'spacing' },
pie: { property: 'paddingInlineEnd', scale: 'spacing' },
} as const;

export const space = {
Expand Down
21 changes: 19 additions & 2 deletions packages/styleguide/src/lib/Foundations/System/Props/Space.mdx
Original file line number Diff line number Diff line change
@@ -1,16 +1,17 @@
import { Meta } from '@storybook/blocks';
import { Canvas, Meta } from '@storybook/blocks';

import { AboutHeader, TokenTable } from '~styleguide/blocks';

import { defaultColumns, getPropRows } from '../../shared/elements';
import * as SpaceStories from './Space.stories';

export const parameters = {
title: 'Space',
subtitle: 'Props for maintaining specific vertical and horizontal rhythms',
status: 'updating',
};

<Meta title="Foundations/System/Props/Space" />
<Meta of={SpaceStories} />

<AboutHeader {...parameters} />

Expand All @@ -25,4 +26,20 @@ const SpaceExample = styled.div(system.space);
<SpaceExample p={8} my={[16, 24, 32]} />;
```

## Logical Properties

Logical spacing properties like `mbl` (marginBlock) provide RTL-aware spacing that adapts to text direction. The `mbl` prop applies margin to both block start and block end, which in LTR layouts is equivalent to margin-top and margin-bottom.

### `mbl` (marginBlock)

<Canvas of={SpaceStories.MarginBlock} />

### `mi` (marginInline)

The `mi` prop applies margin to both inline start and inline end, which in LTR layouts is equivalent to margin-left and margin-right.

<Canvas of={SpaceStories.MarginInline} />

<Canvas of={SpaceStories.RTLComparison} />

<TokenTable rows={getPropRows('space')} columns={defaultColumns} />
Original file line number Diff line number Diff line change
@@ -0,0 +1,206 @@
import {
Box,
FlexBox,
FormGroup,
Radio,
RadioGroup,
Text,
} from '@codecademy/gamut';
import type { Meta, StoryObj } from '@storybook/react';
import { ChangeEvent, useState } from 'react';

const meta: Meta<typeof Box> = {
title: 'Foundations/System/Props/Space',
component: Box,
};

export default meta;
type Story = StoryObj<typeof Box>;

export const MarginBlock: Story = {
render: () => (
<Box bg="background-selected" p={16}>
<Box bg="primary" color="white" mbl={32} textAlign="center">
This box has <code>mbl={32}</code> (marginBlock), which applies margin
to both block start and block end. In LTR, this is equivalent to
margin-top and margin-bottom.
</Box>
<Box border={1} width={1} />
<Box bg="secondary" color="white" my={32} textAlign="center">
This box uses <code>my={32}</code> for comparison. Notice how the
logical property <code>mbl</code> provides the same visual result but is
RTL-aware.
</Box>
</Box>
),
};

export const MarginInline: Story = {
render: () => (
<Box bg="background-selected" p={16}>
<Box display="flex" gap={16}>
<Box bg="primary" color="white" mi={32} textAlign="center">
This box has <code>mi={32}</code> (marginInline), which applies margin
to both inline start and inline end. In LTR, this is equivalent to
margin-left and margin-right.
</Box>
<Box bg="secondary" color="white" mx={32} textAlign="center">
This box uses <code>mx={32}</code> for comparison. Notice how the
logical property <code>mi</code> provides the same visual result but
is RTL-aware.
</Box>
</Box>
</Box>
),
};

export const RTLComparison: Story = {
render: () => {
const Component = () => {
const [direction, setDirection] = useState<'ltr' | 'rtl'>('ltr');
const [writingMode, setWritingMode] = useState<
'horizontal-tb' | 'vertical-rl' | 'vertical-lr'
>('horizontal-tb');

return (
<Box p={16}>
{/* Controls */}
<FlexBox gap={32} mb={24}>
<Box flex={1}>
<FormGroup htmlFor="direction" label="Text Direction">
<RadioGroup
htmlForPrefix="direction"
name="direction"
onChange={(event: ChangeEvent<HTMLInputElement>) => {
setDirection(event.target.value as 'ltr' | 'rtl');
}}
>
<Radio
checked={direction === 'ltr'}
htmlFor="direction"
label="LTR"
name="direction"
value="ltr"
/>
<Radio
checked={direction === 'rtl'}
htmlFor="direction"
label="RTL"
name="direction"
value="rtl"
/>
</RadioGroup>
</FormGroup>
</Box>
<Box flex={1}>
<FormGroup htmlFor="writing-mode" label="Writing Mode">
<RadioGroup
htmlForPrefix="writing-mode"
name="writing-mode"
onChange={(event: ChangeEvent<HTMLInputElement>) => {
setWritingMode(
event.target.value as
| 'horizontal-tb'
| 'vertical-rl'
| 'vertical-lr'
);
}}
>
<Radio
checked={writingMode === 'horizontal-tb'}
htmlFor="writing-mode"
label="Horizontal"
name="writing-mode"
value="horizontal-tb"
/>
<Radio
checked={writingMode === 'vertical-rl'}
htmlFor="writing-mode"
label="Vertical RL"
name="writing-mode"
value="vertical-rl"
/>
<Radio
checked={writingMode === 'vertical-lr'}
htmlFor="writing-mode"
label="Vertical LR"
name="writing-mode"
value="vertical-lr"
/>
</RadioGroup>
</FormGroup>
</Box>
</FlexBox>

{/* Demo Container */}
<Box>
<Text as="h3" fontSize={18} fontWeight="bold" mb={16}>
{direction === 'ltr' ? 'LTR' : 'RTL'} ({writingMode})
</Text>
<Box bg="background-selected" border={1}>
<FlexBox>
<Box
bg="yellow"
dir={direction}
flex={1}
style={{ writingMode }}
>
<Box
bg="primary"
borderRadius="sm"
color="white"
mis={48}
style={{ blockSize: '100%' }}
textAlign="center"
>
<Text as="strong" display="block" fontSize={16} mb={8}>
marginInlineStart={48}
</Text>
<Text fontSize={14}>Logical property</Text>
<br />
<Text fontSize={14} mt={4}>
Margin on start (adapts to direction & writing mode)
</Text>
</Box>
</Box>
<Box border={1} borderColor="danger" borderWidth={2} />
<Box
bg="yellow"
dir={direction}
flex={1}
style={{ writingMode }}
>
<Box
bg="secondary"
borderRadius="sm"
color="white"
ml={48}
style={{ blockSize: '100%' }}
textAlign="center"
>
<Text as="strong" display="block" fontSize={16} mb={8}>
marginLeft={48}
</Text>
<Text fontSize={14}>Physical property</Text>
<br />
<Text fontSize={14} mt={4}>
Margin on left (always fixed)
</Text>
</Box>
</Box>
</FlexBox>
</Box>
</Box>
<Text color="text-disabled" fontSize={14} textAlign="center">
<Text as="strong" color="primary">
Notice:
</Text>{' '}
<code>marginInlineStart</code> adapts to direction and writing mode,{' '}
<code>marginLeft</code> stays fixed
</Text>
</Box>
);
};
return <Component />;
},
};
Loading