From 9920b86356aecedbfee480c78f6051c69e15fe33 Mon Sep 17 00:00:00 2001 From: Kenny Lin Date: Fri, 21 Nov 2025 15:47:14 -0500 Subject: [PATCH 01/15] denoting main branch --- packages/styleguide/src/lib/Foundations/System/Props.mdx | 2 ++ 1 file changed, 2 insertions(+) diff --git a/packages/styleguide/src/lib/Foundations/System/Props.mdx b/packages/styleguide/src/lib/Foundations/System/Props.mdx index dc8bb6ce53d..f7fb5868717 100644 --- a/packages/styleguide/src/lib/Foundations/System/Props.mdx +++ b/packages/styleguide/src/lib/Foundations/System/Props.mdx @@ -19,6 +19,8 @@ export const parameters = { +// main branch for RTL support tokens + We provide a set of out of style functions out of the box through `@codecademy/gamut-styles` that are standardized throughout all of our components. These props are strongly typed and can be included as necessary on any styled component. System props have a few facets that are important to note: From eb724b0dd320ace09d26e608ef18b965d1349178 Mon Sep 17 00:00:00 2001 From: Kenny Lin Date: Mon, 24 Nov 2025 15:17:24 -0500 Subject: [PATCH 02/15] docs: update props for more modular organization (#3217) * re-orged Props.mdx files into separate pages * updated MDN links * update properties column with working links and styling * updated statuses for pages that will receive new logical properties * formatted * fix Responsive Properities story * applied Cass's feedback --- .../components/Elements/Markdown.tsx | 1 - .../src/lib/Foundations/System/About.mdx | 2 +- .../src/lib/Foundations/System/Props.mdx | 232 ------------------ .../lib/Foundations/System/Props/About.mdx | 81 ++++++ .../Foundations/System/Props/Background.mdx | 30 +++ .../lib/Foundations/System/Props/Border.mdx | 33 +++ .../lib/Foundations/System/Props/Color.mdx | 28 +++ .../src/lib/Foundations/System/Props/Flex.mdx | 28 +++ .../src/lib/Foundations/System/Props/Grid.mdx | 31 +++ .../lib/Foundations/System/Props/Layout.mdx | 34 +++ .../src/lib/Foundations/System/Props/List.mdx | 38 +++ .../Foundations/System/Props/Positioning.mdx | 29 +++ .../lib/Foundations/System/Props/Shadow.mdx | 31 +++ .../lib/Foundations/System/Props/Space.mdx | 28 +++ .../Foundations/System/Props/Typography.mdx | 28 +++ .../ResponsiveProperties.mdx | 6 +- .../ResponsiveProperties.stories.tsx | 1 + .../src/lib/Foundations/shared/elements.tsx | 20 +- 18 files changed, 440 insertions(+), 241 deletions(-) delete mode 100644 packages/styleguide/src/lib/Foundations/System/Props.mdx create mode 100644 packages/styleguide/src/lib/Foundations/System/Props/About.mdx create mode 100644 packages/styleguide/src/lib/Foundations/System/Props/Background.mdx create mode 100644 packages/styleguide/src/lib/Foundations/System/Props/Border.mdx create mode 100644 packages/styleguide/src/lib/Foundations/System/Props/Color.mdx create mode 100644 packages/styleguide/src/lib/Foundations/System/Props/Flex.mdx create mode 100644 packages/styleguide/src/lib/Foundations/System/Props/Grid.mdx create mode 100644 packages/styleguide/src/lib/Foundations/System/Props/Layout.mdx create mode 100644 packages/styleguide/src/lib/Foundations/System/Props/List.mdx create mode 100644 packages/styleguide/src/lib/Foundations/System/Props/Positioning.mdx create mode 100644 packages/styleguide/src/lib/Foundations/System/Props/Shadow.mdx create mode 100644 packages/styleguide/src/lib/Foundations/System/Props/Space.mdx create mode 100644 packages/styleguide/src/lib/Foundations/System/Props/Typography.mdx diff --git a/packages/styleguide/.storybook/components/Elements/Markdown.tsx b/packages/styleguide/.storybook/components/Elements/Markdown.tsx index b1c75f2f356..e26f71e4466 100644 --- a/packages/styleguide/.storybook/components/Elements/Markdown.tsx +++ b/packages/styleguide/.storybook/components/Elements/Markdown.tsx @@ -43,7 +43,6 @@ export const Code = styled.code` color: ${themed('colors.navy-700')}; background-color: ${themed('colors.gray-100')}; display: inline-block; - overflow-x: scroll; ::-webkit-scrollbar { width: 4px; diff --git a/packages/styleguide/src/lib/Foundations/System/About.mdx b/packages/styleguide/src/lib/Foundations/System/About.mdx index 3735ec08601..effca42ebc0 100644 --- a/packages/styleguide/src/lib/Foundations/System/About.mdx +++ b/packages/styleguide/src/lib/Foundations/System/About.mdx @@ -7,7 +7,7 @@ import { } from '~styleguide/blocks'; import { parameters as composeParameters } from './Compose.mdx'; -import { parameters as propsParameters } from './Props.mdx'; +import { parameters as propsParameters } from './Props/About.mdx'; import { parameters as responsivePropertiesParameters } from './ResponsiveProperties/ResponsiveProperties.mdx'; import { parameters as variantsParameters } from './Variants.mdx'; diff --git a/packages/styleguide/src/lib/Foundations/System/Props.mdx b/packages/styleguide/src/lib/Foundations/System/Props.mdx deleted file mode 100644 index f7fb5868717..00000000000 --- a/packages/styleguide/src/lib/Foundations/System/Props.mdx +++ /dev/null @@ -1,232 +0,0 @@ -import { Meta } from '@storybook/blocks'; - -import { AboutHeader, TokenTable } from '~styleguide/blocks'; - -import { defaultColumns, getPropRows } from '../shared/elements'; - -export const parameters = { - title: 'Props', - subtitle: - 'Reusable CSS-in-JS props with predictable behaviors and a consistent API for responsive CSS.', - source: { - repo: 'gamut-styles', - githubLink: - 'https://github.com/Codecademy/gamut/blob/af5be6e39cccca5d5d8a1f811c77a7a0b618c914/packages/gamut-styles/src/variance/config.ts#L11', - }, -}; - - - - - -// main branch for RTL support tokens - -We provide a set of out of style functions out of the box through `@codecademy/gamut-styles` that are standardized throughout all of our components. These props are strongly typed and can be included as necessary on any styled component. - -System props have a few facets that are important to note: - -- They can some times represent multiple properties. -- They may be restricted to specific token scales but will always have access to global css values like `initial` and `none`. -- They may have a function that transforms the given value into a standardized value (e.g. `width={.5}` => `width: 50%`) - -We've grouped these into a few main groups that affect simliar behaviors: `layout`, `space`, `color`, `border`, `background`, `typography`, `positioning`, `grid`, `flex`, `shadow`. - -You may import these groups directly from `gamut-styles`. - -```tsx -import { variance } from '@codecademy/variance'; -import { system } from '@codecademy/gamut-styles'; - -const Box = styled.div(variance.compose(system.layout, system.positioning)); - -; -``` - -Each system prop has 3 important features: - -- `properties`: Any number of CSS Properties this prop is responsible for. -- `scale`: A set of values that determines valid inputs for each prop (e.g. a scale of `colors` will restrict to only theme colors). These are generally aliases for more verbose or opaque css properties allowing you to specify a human readable name in your props. If a prop doesn't have a scale that means it accepts all valid CSSType values as props, however if it does have a scale it will only accept global values and keys of the provided scale. -- `transform`: A function that changes the prop / scale value prior to adding it to the stylehseet. This allows us to add / change units for properties like `width` and `height`. Or ensure extra defaults or fallbacks are added dynamically. - -
- -## Layout - -Props for handling dimensions and other layout specific properties. - -```tsx -import styled from '@emotion/styled'; -import { system } from '@codecademy/gamut-styles'; - -const Box = styled.div(system.layout); - -; -``` - - - -## Space - -Props for maintaining specific vertical and horizontal rhythms - -```tsx -import styled from '@emotion/styled'; -import { system } from '@codecademy/gamut-styles'; - -const Spacer = styled.div(system.space); - -; -``` - - - -## Typography - -Props for text manipulation - -```tsx -import styled from '@emotion/styled'; -import { system } from '@codecademy/gamut-styles'; - -const Text = styled.p(system.typography); - -; -``` - - - -## Color - -Specific color properties - -```tsx -import styled from '@emotion/styled'; -import { system } from '@codecademy/gamut-styles'; - -const Background = styled.div(system.color); - -; -``` - - - -## Border - -Border styles - -```tsx -import styled from '@emotion/styled'; -import { system } from '@codecademy/gamut-styles'; - -const Box = styled.div(system.border); - -; -``` - - - -## Flex - -Flex specific properties - -```tsx -import styled from '@emotion/styled'; -import { system } from '@codecademy/gamut-styles'; - -const FlexBox = styled.div(system.flex); - -; -``` - - - -## Grid - -Grid specific properties - -```tsx -import styled from '@emotion/styled'; -import { system } from '@codecademy/gamut-styles'; - -const GridBox = styled.div(system.grid); - -; -``` - - - -## Background - -Props for background manipulation (sizing / repitition / images), for background color see `colors`. - -```tsx -import styled from '@emotion/styled'; -import { system } from '@codecademy/gamut-styles'; -import myBg from './myBg.png'; - -const Box = styled.div(system.background); - -; -``` - - - -## Positioning - -Props that affect stacking and position contexts. Like `top`, `position` and `opacity`. - -```tsx -import styled from '@emotion/styled'; -import { system } from '@codecademy/gamut-styles'; - -const Box = styled.div(system.positioning); - -; -``` - - - -## Shadow - -Props for box and text shadows. - -```tsx -import styled from '@emotion/styled'; -import { system } from '@codecademy/gamut-styles'; - -const Box = styled.div(system.shadow); - -; -``` - - - -## List - -Props for adjusting list styles when rendering a component as a `ul` or `ol` - -```tsx -import styled from '@emotion/styled'; -import { system } from '@codecademy/gamut-styles'; - -const Box = styled.div(system.list); - - - a list item -; -``` - - diff --git a/packages/styleguide/src/lib/Foundations/System/Props/About.mdx b/packages/styleguide/src/lib/Foundations/System/Props/About.mdx new file mode 100644 index 00000000000..97ca50cd4b0 --- /dev/null +++ b/packages/styleguide/src/lib/Foundations/System/Props/About.mdx @@ -0,0 +1,81 @@ +import { Meta } from '@storybook/blocks'; + +import { + AboutHeader, + addParentPath, + TableOfContents, +} from '~styleguide/blocks'; + +import { parameters as backgroundParameters } from './Background.mdx'; +import { parameters as borderParameters } from './Border.mdx'; +import { parameters as colorParameters } from './Color.mdx'; +import { parameters as flexParameters } from './Flex.mdx'; +import { parameters as gridParameters } from './Grid.mdx'; +import { parameters as layoutParameters } from './Layout.mdx'; +import { parameters as listParameters } from './List.mdx'; +import { parameters as positioningParameters } from './Positioning.mdx'; +import { parameters as shadowParameters } from './Shadow.mdx'; +import { parameters as spaceParameters } from './Space.mdx'; +import { parameters as typographyParameters } from './Typography.mdx'; + +export const parameters = { + id: 'Foundations/System/Props', + title: 'Props', + subtitle: + 'Reusable CSS-in-JS props with predictable behaviors and a consistent API for responsive CSS.', + status: 'current', + source: { + repo: 'gamut-styles', + githubLink: + 'https://github.com/Codecademy/gamut/blob/main/packages/gamut-styles/src/variance/config.ts', + }, +}; + + + + + +We provide a set of out of style functions out of the box through `@codecademy/gamut-styles` that are standardized throughout all of our components. These props are strongly typed and can be included as necessary on any styled component. + +System props have a few facets that are important to note: + +- They can some times represent multiple properties. +- They may be restricted to specific token scales but will always have access to global css values like `initial` and `none`. +- They may have a function that transforms the given value into a standardized value (e.g. `width={.5}` => `width: 50%`) + +We've grouped these into a few main groups that affect simliar behaviors: `layout`, `space`, `color`, `border`, `background`, `typography`, `positioning`, `grid`, `flex`, `shadow`. + +You may import these groups directly from `gamut-styles`. + +```tsx +import { variance } from '@codecademy/variance'; +import { system } from '@codecademy/gamut-styles'; + +const ExampleContainer = styled.div( + variance.compose(system.layout, system.positioning) +); + +; +``` + +Each system prop has 3 important features: + +- `properties`: Any number of CSS Properties this prop is responsible for. +- `scale`: A set of values that determines valid inputs for each prop based on the selected theme and that theme's typing (e.g. if the `lxStudio` theme is being used, a scale of `colors` will restrict to the `lxStudio` theme's colors). These are generally aliases for more verbose or opaque CSS properties allowing you to specify a human readable name in your props. If a prop doesn't have a scale that means it accepts all valid `CSSType` values as props, however if it does have a scale it will only accept global values and keys of the provided scale. +- `transform`: A function that changes the prop / scale value prior to adding it to the stylehseet. This allows us to add / change units for properties like `width` and `height`. Or ensure extra defaults or fallbacks are added dynamically. + + diff --git a/packages/styleguide/src/lib/Foundations/System/Props/Background.mdx b/packages/styleguide/src/lib/Foundations/System/Props/Background.mdx new file mode 100644 index 00000000000..e5c34129cf8 --- /dev/null +++ b/packages/styleguide/src/lib/Foundations/System/Props/Background.mdx @@ -0,0 +1,30 @@ +import { Meta } from '@storybook/blocks'; + +import { AboutHeader, TokenTable } from '~styleguide/blocks'; + +import { defaultColumns, getPropRows } from '../../shared/elements'; + +export const parameters = { + title: 'Background', + subtitle: + 'Props for background manipulation (sizing / repitition / images), for background color see `colors`.', + status: 'current', +}; + + + + + +Background props control how background images and patterns are displayed on elements. These properties give you control over image sizing, positioning, and repetition behavior. For solid background colors, use the color props which connect to the theme's color palette. + +```tsx +import styled from '@emotion/styled'; +import { system } from '@codecademy/gamut-styles'; +import myBg from './myBg.png'; + +const BackgroundExample = styled.div(system.background); + +; +``` + + diff --git a/packages/styleguide/src/lib/Foundations/System/Props/Border.mdx b/packages/styleguide/src/lib/Foundations/System/Props/Border.mdx new file mode 100644 index 00000000000..554cafe2139 --- /dev/null +++ b/packages/styleguide/src/lib/Foundations/System/Props/Border.mdx @@ -0,0 +1,33 @@ +import { Meta } from '@storybook/blocks'; + +import { AboutHeader, TokenTable } from '~styleguide/blocks'; + +import { defaultColumns, getPropRows } from '../../shared/elements'; + +export const parameters = { + title: 'Border', + subtitle: 'Border styles', + status: 'updating', +}; + + + + + +Border props enable you to add and style borders on any side of an element. These properties support directional borders (top, right, bottom, left) as well as convenient shorthands for horizontal and vertical borders. Border radius values connect to the theme's `borderRadii` scale for consistent corner rounding. + +```tsx +import styled from '@emotion/styled'; +import { system } from '@codecademy/gamut-styles'; + +const BorderExample = styled.div(system.border); + +; +``` + + diff --git a/packages/styleguide/src/lib/Foundations/System/Props/Color.mdx b/packages/styleguide/src/lib/Foundations/System/Props/Color.mdx new file mode 100644 index 00000000000..0f2e7cb15e1 --- /dev/null +++ b/packages/styleguide/src/lib/Foundations/System/Props/Color.mdx @@ -0,0 +1,28 @@ +import { Meta } from '@storybook/blocks'; + +import { AboutHeader, TokenTable } from '~styleguide/blocks'; + +import { defaultColumns, getPropRows } from '../../shared/elements'; + +export const parameters = { + title: 'Color', + subtitle: 'Specific color properties', + status: 'current', +}; + + + + + +Color props control the foreground, background, and border colors of elements. All color values are restricted to your theme's color palette, ensuring consistent color usage throughout your application. The `bg` shorthand provides a convenient way to set background colors quickly. + +```tsx +import styled from '@emotion/styled'; +import { system } from '@codecademy/gamut-styles'; + +const ColorExample = styled.div(system.color); + +; +``` + + diff --git a/packages/styleguide/src/lib/Foundations/System/Props/Flex.mdx b/packages/styleguide/src/lib/Foundations/System/Props/Flex.mdx new file mode 100644 index 00000000000..85f515de38c --- /dev/null +++ b/packages/styleguide/src/lib/Foundations/System/Props/Flex.mdx @@ -0,0 +1,28 @@ +import { Meta } from '@storybook/blocks'; + +import { AboutHeader, TokenTable } from '~styleguide/blocks'; + +import { defaultColumns, getPropRows } from '../../shared/elements'; + +export const parameters = { + title: 'Flex', + subtitle: 'Flex specific properties', + status: 'current', +}; + + + + + +Flex props provide complete control over flexbox layouts, from container behavior to individual flex item properties. These properties make it easy to create flexible, responsive layouts with proper alignment and distribution of child elements. Use these on flex containers to control their children or on flex items to control their own behavior. + +```tsx +import styled from '@emotion/styled'; +import { system } from '@codecademy/gamut-styles'; + +const FlexExample = styled.div(system.flex); + +; +``` + + diff --git a/packages/styleguide/src/lib/Foundations/System/Props/Grid.mdx b/packages/styleguide/src/lib/Foundations/System/Props/Grid.mdx new file mode 100644 index 00000000000..39d17e1b3c0 --- /dev/null +++ b/packages/styleguide/src/lib/Foundations/System/Props/Grid.mdx @@ -0,0 +1,31 @@ +import { Meta } from '@storybook/blocks'; + +import { AboutHeader, TokenTable } from '~styleguide/blocks'; + +import { defaultColumns, getPropRows } from '../../shared/elements'; + +export const parameters = { + title: 'Grid', + subtitle: 'Grid specific properties', + status: 'current', +}; + + + + + +Grid props give you powerful control over CSS Grid layouts. Define grid templates, control auto-placement behavior, and set gaps between grid items. These properties make it straightforward to create complex, responsive grid layouts with precise control over both container and item positioning. + +```tsx +import styled from '@emotion/styled'; +import { system } from '@codecademy/gamut-styles'; + +const GridExample = styled.div(system.grid); + +; +``` + + diff --git a/packages/styleguide/src/lib/Foundations/System/Props/Layout.mdx b/packages/styleguide/src/lib/Foundations/System/Props/Layout.mdx new file mode 100644 index 00000000000..8c687a4dee5 --- /dev/null +++ b/packages/styleguide/src/lib/Foundations/System/Props/Layout.mdx @@ -0,0 +1,34 @@ +import { Meta } from '@storybook/blocks'; + +import { AboutHeader, TokenTable } from '~styleguide/blocks'; + +import { defaultColumns, getPropRows } from '../../shared/elements'; + +export const parameters = { + title: 'Layout', + subtitle: + 'Props for handling dimensions and other layout specific properties.', + status: 'updating', +}; + + + + + +Layout props control the visual structure and dimensions of elements. These properties determine how components take up space, their display behavior, and how they align within their containers. Use these props to set widths, heights, overflow behavior, and container types for responsive layouts. + +```tsx +import styled from '@emotion/styled'; +import { system } from '@codecademy/gamut-styles'; + +const LayoutExample = styled.div(system.layout); + +; +``` + + diff --git a/packages/styleguide/src/lib/Foundations/System/Props/List.mdx b/packages/styleguide/src/lib/Foundations/System/Props/List.mdx new file mode 100644 index 00000000000..74398b42e07 --- /dev/null +++ b/packages/styleguide/src/lib/Foundations/System/Props/List.mdx @@ -0,0 +1,38 @@ +import { Meta } from '@storybook/blocks'; + +import { AboutHeader, LinkTo, TokenTable } from '~styleguide/blocks'; + +import { defaultColumns, getPropRows } from '../../shared/elements'; + +export const parameters = { + title: 'List', + subtitle: + 'Props for adjusting list styles when rendering a component as a `ul` or `ol`', + status: 'current', +}; + + + + + +List props control the appearance of ordered and unordered lists when components are rendered as `ul` or `ol` elements. These properties let you customize bullet styles, list positioning, and even use custom images as list markers, giving you full control over list presentation. + +For more advanced list features, refer to the List component. + +```tsx +import styled from '@emotion/styled'; +import { system } from '@codecademy/gamut-styles'; + +const ListExample = styled.div(system.list); + + + a list item +; +``` + + diff --git a/packages/styleguide/src/lib/Foundations/System/Props/Positioning.mdx b/packages/styleguide/src/lib/Foundations/System/Props/Positioning.mdx new file mode 100644 index 00000000000..fb916b46286 --- /dev/null +++ b/packages/styleguide/src/lib/Foundations/System/Props/Positioning.mdx @@ -0,0 +1,29 @@ +import { Meta } from '@storybook/blocks'; + +import { AboutHeader, TokenTable } from '~styleguide/blocks'; + +import { defaultColumns, getPropRows } from '../../shared/elements'; + +export const parameters = { + title: 'Positioning', + subtitle: + 'Props that affect stacking and position contexts. Like `top`, `position` and `opacity`.', + status: 'updating', +}; + + + + + +Positioning props control how elements are positioned within their parent containers and manage their stacking order. Use these properties to create fixed headers, absolute overlays, sticky navigation, and control layering with z-index. The `inset` shorthand provides a convenient way to set all four position values at once. + +```tsx +import styled from '@emotion/styled'; +import { system } from '@codecademy/gamut-styles'; + +const PositioningExample = styled.div(system.positioning); + +; +``` + + diff --git a/packages/styleguide/src/lib/Foundations/System/Props/Shadow.mdx b/packages/styleguide/src/lib/Foundations/System/Props/Shadow.mdx new file mode 100644 index 00000000000..60ee41e8f09 --- /dev/null +++ b/packages/styleguide/src/lib/Foundations/System/Props/Shadow.mdx @@ -0,0 +1,31 @@ +import { Meta } from '@storybook/blocks'; + +import { AboutHeader, TokenTable } from '~styleguide/blocks'; + +import { defaultColumns, getPropRows } from '../../shared/elements'; + +export const parameters = { + title: 'Shadow', + subtitle: 'Props for box and text shadows.', + status: 'current', +}; + + + + + +Shadow props add depth and visual interest to your components through box and text shadows. These properties accept standard CSS shadow syntax, allowing you to create subtle elevation effects or dramatic visual depth. Use shadows consistently to establish visual hierarchy in your interface. + +```tsx +import styled from '@emotion/styled'; +import { system } from '@codecademy/gamut-styles'; + +const ShadowExample = styled.div(system.shadow); + +; +``` + + diff --git a/packages/styleguide/src/lib/Foundations/System/Props/Space.mdx b/packages/styleguide/src/lib/Foundations/System/Props/Space.mdx new file mode 100644 index 00000000000..0afcbb48e6a --- /dev/null +++ b/packages/styleguide/src/lib/Foundations/System/Props/Space.mdx @@ -0,0 +1,28 @@ +import { Meta } from '@storybook/blocks'; + +import { AboutHeader, TokenTable } from '~styleguide/blocks'; + +import { defaultColumns, getPropRows } from '../../shared/elements'; + +export const parameters = { + title: 'Space', + subtitle: 'Props for maintaining specific vertical and horizontal rhythms', + status: 'updating', +}; + + + + + +Space props provide a consistent way to apply margin and padding throughout your application. All spacing values reference the theme's spacing scale, ensuring visual consistency and making it easy to create responsive spacing patterns using array syntax. + +```tsx +import styled from '@emotion/styled'; +import { system } from '@codecademy/gamut-styles'; + +const SpaceExample = styled.div(system.space); + +; +``` + + diff --git a/packages/styleguide/src/lib/Foundations/System/Props/Typography.mdx b/packages/styleguide/src/lib/Foundations/System/Props/Typography.mdx new file mode 100644 index 00000000000..a63af6ab7bf --- /dev/null +++ b/packages/styleguide/src/lib/Foundations/System/Props/Typography.mdx @@ -0,0 +1,28 @@ +import { Meta } from '@storybook/blocks'; + +import { AboutHeader, TokenTable } from '~styleguide/blocks'; + +import { defaultColumns, getPropRows } from '../../shared/elements'; + +export const parameters = { + title: 'Typography', + subtitle: 'Props for text manipulation', + status: 'current', +}; + + + + + +Typography props give you fine-grained control over text styling and appearance. These properties connect to the theme's typography scales for font families, sizes, weights, and line heights, making it simple to maintain typographic consistency across your application while allowing for custom text transformations and decorations. + +```tsx +import styled from '@emotion/styled'; +import { system } from '@codecademy/gamut-styles'; + +const TextExample = styled.p(system.typography); + +; +``` + + diff --git a/packages/styleguide/src/lib/Foundations/System/ResponsiveProperties/ResponsiveProperties.mdx b/packages/styleguide/src/lib/Foundations/System/ResponsiveProperties/ResponsiveProperties.mdx index 32ef96b22b2..03a9f18b744 100644 --- a/packages/styleguide/src/lib/Foundations/System/ResponsiveProperties/ResponsiveProperties.mdx +++ b/packages/styleguide/src/lib/Foundations/System/ResponsiveProperties/ResponsiveProperties.mdx @@ -6,17 +6,17 @@ import { breakpoint } from '../../shared/elements'; import * as ResponsivePropertiesStories from './ResponsiveProperties.stories'; export const parameters = { - title: 'Responsive Properties', + title: 'Responsive properties', subtitle: 'All system props accept a syntax to generate responsive styles on a per prop basis', source: { repo: 'variance', githubLink: - 'https://github.com/Codecademy/gamut/blob/cass-gm-842/packages/variance/src/utils/responsive.ts', + 'https://github.com/Codecademy/gamut/blob/main/packages/gamut-styles/src/variables/responsive.ts', }, }; - + diff --git a/packages/styleguide/src/lib/Foundations/System/ResponsiveProperties/ResponsiveProperties.stories.tsx b/packages/styleguide/src/lib/Foundations/System/ResponsiveProperties/ResponsiveProperties.stories.tsx index c7b51fc8085..cc8e3a88bd0 100644 --- a/packages/styleguide/src/lib/Foundations/System/ResponsiveProperties/ResponsiveProperties.stories.tsx +++ b/packages/styleguide/src/lib/Foundations/System/ResponsiveProperties/ResponsiveProperties.stories.tsx @@ -2,6 +2,7 @@ import { Box, FlexBox } from '@codecademy/gamut'; import type { Meta, StoryObj } from '@storybook/react'; const meta: Meta = { + title: 'Foundations/System/Responsive properties', component: FlexBox, args: { center: true, diff --git a/packages/styleguide/src/lib/Foundations/shared/elements.tsx b/packages/styleguide/src/lib/Foundations/shared/elements.tsx index d01149e99d5..a6b0cf500cf 100644 --- a/packages/styleguide/src/lib/Foundations/shared/elements.tsx +++ b/packages/styleguide/src/lib/Foundations/shared/elements.tsx @@ -2,22 +2,31 @@ import { Anchor, Box } from '@codecademy/gamut'; import { Background, coreSwatches, + css, lxStudioColors, theme, trueColors, } from '@codecademy/gamut-styles'; // eslint-disable-next-line gamut/import-paths import * as ALL_PROPS from '@codecademy/gamut-styles/src/variance/config'; +import styled from '@emotion/styled'; import kebabCase from 'lodash/kebabCase'; import { Code, ColorScale, LinkTo, TokenTable } from '~styleguide/blocks'; import { applyCorrectNotation } from './applyCorrectNotation'; +const AnchorCode = styled(Code)( + css({ + textDecoration: 'underline', + mx: 4, + }) +); + export const PROP_COLUMN = { key: 'key', name: 'Prop', - size: 'md', + size: 'lg', render: ({ id }: any) => {id}, }; @@ -416,11 +425,14 @@ const PROPERTIES_COLUMN = { }) => properties.map((property) => ( - {kebabCase(property)} + {kebabCase(property)} )), }; @@ -430,7 +442,7 @@ const SCALE_COLUMN = { name: 'Scale', size: 'lg', render: ({ scale }: { scale: string }) => ( - {scale} + {scale} ), }; From 567a6aeffb3e8628db4b5c9a37dab965b716d969 Mon Sep 17 00:00:00 2001 From: Kenny Lin Date: Fri, 6 Feb 2026 10:55:02 -0500 Subject: [PATCH 03/15] feat: Updates to ThemeProvider, tokens, and transform to allow Logical vs Physical prop resolution (#3234) * working PoC * fix build and format * lint fixes * some more refactoring * fix existing test failures * add test for getPropertyMode * updated gamutprovider to include useLogicalProperties * fix failiing tests * more test fixes * formatted * add logicalprops switcher to toolbar * updated shorthand in margin related CSS properties * fix linting issue re: physical * update docs to show logical prop updates to margin related props * updated padding too * updated Usage Guide and clean up * add new file to explain logical and physical properties * update docs for readibility * formatted and cleaned up * fix tests and edit MockGamutProvider to use useLogicalProperties * temp fix for test failure * removed useLogicalProperties from contextValue b.c. it's not used anymore * added toolbar button for direction * address Cass's feedback --- packages/gamut-styles/src/GamutProvider.tsx | 19 ++- .../src/__tests__/GamutProvider.test.tsx | 2 +- packages/gamut-styles/src/variance/config.ts | 98 ++++++++++-- packages/gamut-tests/src/index.tsx | 16 +- .../ConnectedNestedCheckboxes.test.tsx | 46 +++--- .../__tests__/utils.test.tsx | 65 ++++---- .../GridFormNestedCheckboxInput.test.tsx | 46 +++--- .../gamut/src/List/__tests__/List.test.tsx | 15 +- .../components/Elements/DocsContainer.tsx | 4 + packages/styleguide/.storybook/preview.ts | 26 ++++ .../.storybook/theming/GamutThemeProvider.tsx | 12 +- .../lib/Foundations/System/Props/Space.mdx | 22 ++- .../System/Props/Space.stories.tsx | 48 ++++++ .../src/lib/Foundations/shared/elements.tsx | 78 +++++++--- packages/styleguide/src/lib/Meta/About.mdx | 4 +- .../Logical and physical CSS properties.mdx | 139 ++++++++++++++++++ .../styleguide/src/lib/Meta/Usage Guide.mdx | 7 +- .../styleguide/src/static/meta/toolbar.png | Bin 4868 -> 4465 bytes packages/variance/src/core.ts | 45 +++++- .../getPropertyMode/getPropertyMode.test.ts | 13 ++ .../src/getPropertyMode/getPropertyMode.ts | 7 + .../variance/src/getPropertyMode/index.ts | 1 + packages/variance/src/index.ts | 1 + packages/variance/src/types/config.ts | 33 ++++- packages/variance/src/types/properties.ts | 12 ++ packages/variance/src/utils/propNames.ts | 33 +++-- 26 files changed, 658 insertions(+), 134 deletions(-) create mode 100644 packages/styleguide/src/lib/Foundations/System/Props/Space.stories.tsx create mode 100644 packages/styleguide/src/lib/Meta/Logical and physical CSS properties.mdx create mode 100644 packages/variance/src/getPropertyMode/getPropertyMode.test.ts create mode 100644 packages/variance/src/getPropertyMode/getPropertyMode.ts create mode 100644 packages/variance/src/getPropertyMode/index.ts diff --git a/packages/gamut-styles/src/GamutProvider.tsx b/packages/gamut-styles/src/GamutProvider.tsx index 98b4bc84202..4bb266f0724 100644 --- a/packages/gamut-styles/src/GamutProvider.tsx +++ b/packages/gamut-styles/src/GamutProvider.tsx @@ -27,6 +27,10 @@ export interface GamutProviderProps { * Pass a nonce to the cache to prevent CSP errors */ nonce?: string; + /** + * Whether to use logical properties for the theme + */ + useLogicalProperties?: boolean; } export const GamutContext = React.createContext<{ @@ -47,6 +51,7 @@ export const GamutProvider: React.FC = ({ useGlobals = true, useCache = true, nonce, + useLogicalProperties = true, }) => { const { hasGlobals, hasCache } = useContext(GamutContext); const shouldCreateCache = useCache && !hasCache; @@ -71,12 +76,20 @@ export const GamutProvider: React.FC = ({ ); + // Merge useLogicalProperties into theme so variance can access it via props.theme + const themeWithLogicalProperties = { + ...theme, + useLogicalProperties, + }; + if (activeCache.current) { return ( {globals} - {children} + + {children} + ); @@ -85,7 +98,9 @@ export const GamutProvider: React.FC = ({ return ( {globals} - {children} + + {children} + ); }; diff --git a/packages/gamut-styles/src/__tests__/GamutProvider.test.tsx b/packages/gamut-styles/src/__tests__/GamutProvider.test.tsx index e32357589e1..7086e7f0fbf 100644 --- a/packages/gamut-styles/src/__tests__/GamutProvider.test.tsx +++ b/packages/gamut-styles/src/__tests__/GamutProvider.test.tsx @@ -56,7 +56,7 @@ describe(GamutProvider, () => { ), }); - screen.getByText(JSON.stringify(theme)); + screen.getByText(JSON.stringify({ ...theme, useLogicalProperties: true })); }); it('it can have another GamutProvider as a child with creating multiple caches or globals', () => { renderView({ diff --git a/packages/gamut-styles/src/variance/config.ts b/packages/gamut-styles/src/variance/config.ts index 4ba51362f45..d4514ecd18b 100644 --- a/packages/gamut-styles/src/variance/config.ts +++ b/packages/gamut-styles/src/variance/config.ts @@ -1,4 +1,4 @@ -import { transformSize } from '@codecademy/variance'; +import { getPropertyMode, transformSize } from '@codecademy/variance'; export const color = { color: { property: 'color', scale: 'colors' }, @@ -233,36 +233,108 @@ export const margin = { m: { property: 'margin', scale: 'spacing' }, mx: { property: 'margin', - properties: ['marginLeft', 'marginRight'], + properties: { + physical: ['marginLeft', 'marginRight'], + logical: ['marginInlineStart', 'marginInlineEnd'], + }, + resolveProperty: getPropertyMode, scale: 'spacing', }, my: { property: 'margin', - properties: ['marginTop', 'marginBottom'], + properties: { + physical: ['marginTop', 'marginBottom'], + logical: ['marginBlockStart', 'marginBlockEnd'], + }, + resolveProperty: getPropertyMode, scale: 'spacing', }, - mt: { property: 'marginTop', scale: 'spacing' }, - mb: { property: 'marginBottom', scale: 'spacing' }, - mr: { property: 'marginRight', scale: 'spacing' }, - ml: { property: 'marginLeft', scale: 'spacing' }, + mt: { + property: { + physical: 'marginTop', + logical: 'marginBlockStart', + }, + scale: 'spacing', + resolveProperty: getPropertyMode, + }, + mb: { + property: { + physical: 'marginBottom', + logical: 'marginBlockEnd', + }, + scale: 'spacing', + resolveProperty: getPropertyMode, + }, + mr: { + property: { + physical: 'marginRight', + logical: 'marginInlineEnd', + }, + scale: 'spacing', + resolveProperty: getPropertyMode, + }, + ml: { + property: { + physical: 'marginLeft', + logical: 'marginInlineStart', + }, + scale: 'spacing', + resolveProperty: getPropertyMode, + }, } as const; export const padding = { p: { property: 'padding', scale: 'spacing' }, px: { property: 'padding', - properties: ['paddingLeft', 'paddingRight'], + properties: { + physical: ['paddingLeft', 'paddingRight'], + logical: ['paddingInlineStart', 'paddingInlineEnd'], + }, scale: 'spacing', + resolveProperty: getPropertyMode, }, py: { property: 'padding', - properties: ['paddingTop', 'paddingBottom'], + properties: { + physical: ['paddingTop', 'paddingBottom'], + logical: ['paddingBlockStart', 'paddingBlockEnd'], + }, + scale: 'spacing', + resolveProperty: getPropertyMode, + }, + pt: { + property: { + physical: 'paddingTop', + logical: 'paddingBlockStart', + }, + scale: 'spacing', + resolveProperty: getPropertyMode, + }, + pb: { + property: { + physical: 'paddingBottom', + logical: 'paddingBlockEnd', + }, + scale: 'spacing', + resolveProperty: getPropertyMode, + }, + pr: { + property: { + physical: 'paddingRight', + logical: 'paddingInlineEnd', + }, + scale: 'spacing', + resolveProperty: getPropertyMode, + }, + pl: { + property: { + physical: 'paddingLeft', + logical: 'paddingInlineStart', + }, scale: 'spacing', + resolveProperty: getPropertyMode, }, - pt: { property: 'paddingTop', scale: 'spacing' }, - pb: { property: 'paddingBottom', scale: 'spacing' }, - pr: { property: 'paddingRight', scale: 'spacing' }, - pl: { property: 'paddingLeft', scale: 'spacing' }, } as const; export const space = { diff --git a/packages/gamut-tests/src/index.tsx b/packages/gamut-tests/src/index.tsx index 72005789548..700132e1cd4 100644 --- a/packages/gamut-tests/src/index.tsx +++ b/packages/gamut-tests/src/index.tsx @@ -6,13 +6,19 @@ import { import overArgs from 'lodash/overArgs'; import * as React from 'react'; -// See https://www.notion.so/codecademy/Frontend-Unit-Tests-1cbf4e078a6647559b4583dfb6d3cb18 for more info +// See https://skillsoftdev.atlassian.net/wiki/spaces/779a16d9c7ea452eab11b39cbbe771ce/pages/4441315387/Frontend+Unit+Tests for more info -export const MockGamutProvider: React.FC<{ children?: React.ReactNode }> = ({ - children, -}) => { +export const MockGamutProvider: React.FC<{ + children?: React.ReactNode; + useLogicalProperties?: boolean; +}> = ({ children, useLogicalProperties }) => { return ( - + {children} ); diff --git a/packages/gamut/src/ConnectedForm/ConnectedInputs/ConnectedNestedCheckboxes/__tests__/ConnectedNestedCheckboxes.test.tsx b/packages/gamut/src/ConnectedForm/ConnectedInputs/ConnectedNestedCheckboxes/__tests__/ConnectedNestedCheckboxes.test.tsx index 95301bddba8..ec472292184 100644 --- a/packages/gamut/src/ConnectedForm/ConnectedInputs/ConnectedNestedCheckboxes/__tests__/ConnectedNestedCheckboxes.test.tsx +++ b/packages/gamut/src/ConnectedForm/ConnectedInputs/ConnectedNestedCheckboxes/__tests__/ConnectedNestedCheckboxes.test.tsx @@ -1,6 +1,6 @@ -import { setupRtl } from '@codecademy/gamut-tests'; +import { MockGamutProvider, setupRtl } from '@codecademy/gamut-tests'; import { fireEvent } from '@testing-library/dom'; -import { act } from '@testing-library/react'; +import { act, render, screen } from '@testing-library/react'; import userEvent from '@testing-library/user-event'; import { ConnectedForm, ConnectedFormGroup, SubmitButton } from '../../..'; @@ -90,21 +90,33 @@ describe('ConnectedNestedCheckboxes', () => { view.getByLabelText('Fastify'); }); - it('should render checkboxes with proper indentation levels', () => { - const { view } = renderView(); - - const frontendCheckbox = view - .getByLabelText('Frontend Technologies') - .closest('li'); - const reactCheckbox = view.getByLabelText('React').closest('li'); - const nodeCheckbox = view.getByLabelText('Node.js').closest('li'); - const expressCheckbox = view.getByLabelText('Express.js').closest('li'); - - expect(frontendCheckbox).toHaveStyle({ marginLeft: '0' }); - expect(reactCheckbox).toHaveStyle({ marginLeft: '1.5rem' }); - expect(nodeCheckbox).toHaveStyle({ marginLeft: '1.5rem' }); - expect(expressCheckbox).toHaveStyle({ marginLeft: '3rem' }); - }); + it.each([ + { useLogicalProperties: true, marginLeft: 'marginInlineStart' }, + { useLogicalProperties: false, marginLeft: 'marginLeft' }, + ])( + 'should render checkboxes with proper indentation levels (useLogicalProperties: $useLogicalProperties)', + ({ useLogicalProperties, marginLeft }) => { + render( + + + + ); + + const frontendCheckbox = screen + .getByLabelText('Frontend Technologies') + .closest('li'); + const reactCheckbox = screen.getByLabelText('React').closest('li'); + const nodeCheckbox = screen.getByLabelText('Node.js').closest('li'); + const expressCheckbox = screen + .getByLabelText('Express.js') + .closest('li'); + + expect(frontendCheckbox).toHaveStyle({ [marginLeft]: '0' }); + expect(reactCheckbox).toHaveStyle({ [marginLeft]: '1.5rem' }); + expect(nodeCheckbox).toHaveStyle({ [marginLeft]: '1.5rem' }); + expect(expressCheckbox).toHaveStyle({ [marginLeft]: '3rem' }); + } + ); it('should render with unique IDs for each checkbox', () => { const { view } = renderView(); diff --git a/packages/gamut/src/ConnectedForm/ConnectedInputs/ConnectedNestedCheckboxes/__tests__/utils.test.tsx b/packages/gamut/src/ConnectedForm/ConnectedInputs/ConnectedNestedCheckboxes/__tests__/utils.test.tsx index 8f9259fb9c4..a616094c737 100644 --- a/packages/gamut/src/ConnectedForm/ConnectedInputs/ConnectedNestedCheckboxes/__tests__/utils.test.tsx +++ b/packages/gamut/src/ConnectedForm/ConnectedInputs/ConnectedNestedCheckboxes/__tests__/utils.test.tsx @@ -1,3 +1,4 @@ +import { MockGamutProvider } from '@codecademy/gamut-tests'; import { render } from '@testing-library/react'; import { @@ -475,7 +476,7 @@ describe('ConnectedNestedCheckboxes utils', () => { const mockOnBlur = jest.fn(); it('should render a checked checkbox with correct props', () => { - const state = { checked: true }; + const state = { checked: true, indeterminate: false }; const result = renderCheckbox({ option: mockOption, @@ -522,7 +523,7 @@ describe('ConnectedNestedCheckboxes utils', () => { }); it('should render an unchecked checkbox with correct props', () => { - const state = { checked: false }; + const state = { checked: false, indeterminate: false }; const result = renderCheckbox({ option: mockOption, @@ -544,29 +545,39 @@ describe('ConnectedNestedCheckboxes utils', () => { expect(checkbox).toHaveAttribute('aria-checked', 'false'); }); - it('should apply correct margin based on level', () => { - const state = { checked: false }; - - const result = renderCheckbox({ - option: { ...mockOption, level: 2 }, - state, - name: 'test', - isRequired: false, - isDisabled: false, - onBlur: mockOnBlur, - onChange: mockOnChange, - ref: mockRef, - flatOptions: [{ ...mockOption, level: 2 }], - }); + it.each([ + { useLogicalProperties: true, marginLeft: 'marginInlineStart' }, + { useLogicalProperties: false, marginLeft: 'marginLeft' }, + ])( + 'should apply correct margin based on level (useLogicalProperties: $useLogicalProperties)', + ({ useLogicalProperties, marginLeft }) => { + const state = { checked: false, indeterminate: false }; + + const result = renderCheckbox({ + option: { ...mockOption, level: 2 }, + state, + name: 'test', + isRequired: false, + isDisabled: false, + onBlur: mockOnBlur, + onChange: mockOnChange, + ref: mockRef, + flatOptions: [{ ...mockOption, level: 2 }], + }); - const { container } = render(result); - const listItem = container.querySelector('li'); + const { container } = render( + + {result} + + ); + const listItem = container.querySelector('li'); - expect(listItem).toHaveStyle({ marginLeft: '48px' }); // 2 * 24px - }); + expect(listItem).toHaveStyle({ [marginLeft]: '3rem' }); // 24px * 2 = 48px = 3rem + } + ); it('should handle disabled state', () => { - const state = { checked: false }; + const state = { checked: false, indeterminate: false }; const result = renderCheckbox({ option: { ...mockOption, disabled: true }, @@ -587,7 +598,7 @@ describe('ConnectedNestedCheckboxes utils', () => { }); it('should handle error state', () => { - const state = { checked: false }; + const state = { checked: false, indeterminate: false }; const result = renderCheckbox({ option: mockOption, @@ -609,7 +620,7 @@ describe('ConnectedNestedCheckboxes utils', () => { }); it('should use custom aria-label when provided', () => { - const state = { checked: false }; + const state = { checked: false, indeterminate: false }; const optionWithAriaLabel = { ...mockOption, 'aria-label': 'Custom aria label', @@ -634,7 +645,7 @@ describe('ConnectedNestedCheckboxes utils', () => { }); it('should fallback to label text for aria-label when label is string', () => { - const state = { checked: false }; + const state = { checked: false, indeterminate: false }; const result = renderCheckbox({ option: mockOption, @@ -655,7 +666,7 @@ describe('ConnectedNestedCheckboxes utils', () => { }); it('should use default aria-label when label is not string', () => { - const state = { checked: false }; + const state = { checked: false, indeterminate: false }; const optionWithElementLabel = { ...mockOption, label: Element Label, @@ -680,7 +691,7 @@ describe('ConnectedNestedCheckboxes utils', () => { }); it('should generate aria-controls with all nested descendants', () => { - const state = { checked: false }; + const state = { checked: false, indeterminate: false }; const flatOptions = [ { value: 'parent', @@ -737,7 +748,7 @@ describe('ConnectedNestedCheckboxes utils', () => { }); it('should not have aria-controls for leaf nodes', () => { - const state = { checked: false }; + const state = { checked: false, indeterminate: false }; const flatOptions = [ { value: 'leaf', diff --git a/packages/gamut/src/GridForm/GridFormInputGroup/GridFormNestedCheckboxInput/__tests__/GridFormNestedCheckboxInput.test.tsx b/packages/gamut/src/GridForm/GridFormInputGroup/GridFormNestedCheckboxInput/__tests__/GridFormNestedCheckboxInput.test.tsx index 7c92c4bd4b7..93ba0585de9 100644 --- a/packages/gamut/src/GridForm/GridFormInputGroup/GridFormNestedCheckboxInput/__tests__/GridFormNestedCheckboxInput.test.tsx +++ b/packages/gamut/src/GridForm/GridFormInputGroup/GridFormNestedCheckboxInput/__tests__/GridFormNestedCheckboxInput.test.tsx @@ -1,6 +1,6 @@ -import { setupRtl } from '@codecademy/gamut-tests'; +import { MockGamutProvider, setupRtl } from '@codecademy/gamut-tests'; import { fireEvent } from '@testing-library/dom'; -import { act } from '@testing-library/react'; +import { act, render, screen } from '@testing-library/react'; import userEvent from '@testing-library/user-event'; import { GridForm } from '../../../GridForm'; @@ -89,21 +89,33 @@ describe('GridFormNestedCheckboxInput', () => { view.getByLabelText('Fastify'); }); - it('should render checkboxes with proper indentation levels', () => { - const { view } = renderView(); - - const frontendCheckbox = view - .getByLabelText('Frontend Technologies') - .closest('li'); - const reactCheckbox = view.getByLabelText('React').closest('li'); - const nodeCheckbox = view.getByLabelText('Node.js').closest('li'); - const expressCheckbox = view.getByLabelText('Express.js').closest('li'); - - expect(frontendCheckbox).toHaveStyle({ marginLeft: '0' }); - expect(reactCheckbox).toHaveStyle({ marginLeft: '1.5rem' }); - expect(nodeCheckbox).toHaveStyle({ marginLeft: '1.5rem' }); - expect(expressCheckbox).toHaveStyle({ marginLeft: '3rem' }); - }); + it.each([ + { useLogicalProperties: true, marginLeft: 'marginInlineStart' }, + { useLogicalProperties: false, marginLeft: 'marginLeft' }, + ])( + 'should render checkboxes with proper indentation levels (useLogicalProperties: $useLogicalProperties)', + ({ useLogicalProperties, marginLeft }) => { + render( + + + + ); + + const frontendCheckbox = screen + .getByLabelText('Frontend Technologies') + .closest('li'); + const reactCheckbox = screen.getByLabelText('React').closest('li'); + const nodeCheckbox = screen.getByLabelText('Node.js').closest('li'); + const expressCheckbox = screen + .getByLabelText('Express.js') + .closest('li'); + + expect(frontendCheckbox).toHaveStyle({ [marginLeft]: '0' }); + expect(reactCheckbox).toHaveStyle({ [marginLeft]: '1.5rem' }); + expect(nodeCheckbox).toHaveStyle({ [marginLeft]: '1.5rem' }); + expect(expressCheckbox).toHaveStyle({ [marginLeft]: '3rem' }); + } + ); it('should render with unique IDs for each checkbox', () => { const { view } = renderView(); diff --git a/packages/gamut/src/List/__tests__/List.test.tsx b/packages/gamut/src/List/__tests__/List.test.tsx index 71f2e500f36..a25cb5ed4a2 100644 --- a/packages/gamut/src/List/__tests__/List.test.tsx +++ b/packages/gamut/src/List/__tests__/List.test.tsx @@ -48,15 +48,22 @@ describe('List', () => { expect(rowEl).toHaveStyle({ columnGap: theme.spacing[40] }); }); + // Note: Only testing one mode here since variant() caches styles after first render. it('configures columns with the correct variants', () => { - const { view } = renderView(); + const useLogicalProperties = true; + const paddingLeft = useLogicalProperties + ? 'paddingInlineStart' + : 'paddingLeft'; + const paddingRight = useLogicalProperties + ? 'paddingInlineEnd' + : 'paddingRight'; + const { view } = renderView(); const colEl = view.getByText('Hello'); expect(colEl).not.toHaveStyle({ py: 16 }); - expect(colEl).toHaveStyle({ paddingLeft: theme.spacing[8] }); - expect(colEl).toHaveStyle({ paddingRight: theme.spacing[8] }); - + expect(colEl).toHaveStyle({ [paddingLeft]: theme.spacing[8] }); + expect(colEl).toHaveStyle({ [paddingRight]: theme.spacing[8] }); expect(colEl).not.toHaveStyle({ position: 'sticky' }); }); diff --git a/packages/styleguide/.storybook/components/Elements/DocsContainer.tsx b/packages/styleguide/.storybook/components/Elements/DocsContainer.tsx index 525723c2a4e..0c6a47dd78d 100644 --- a/packages/styleguide/.storybook/components/Elements/DocsContainer.tsx +++ b/packages/styleguide/.storybook/components/Elements/DocsContainer.tsx @@ -52,6 +52,9 @@ export const DocsContainer: React.FC<{ const globalTheme = (context as any).store.userGlobals?.globals?.theme || 'core'; + const globalLogicalProps = (context as any).store.userGlobals?.globals + ?.logicalProps; + const useLogicalProperties = globalLogicalProps !== 'false'; const { currentTheme } = useMemo(() => { const findThemeStory: keyof typeof themeSpecificStories | undefined = @@ -77,6 +80,7 @@ export const DocsContainer: React.FC<{ cache={createEmotionCache({ speedy: false })} // This is typed to the CoreTheme in theme.d.ts theme={currentTheme as unknown as CoreTheme} + useLogicalProperties={useLogicalProperties} > diff --git a/packages/styleguide/.storybook/preview.ts b/packages/styleguide/.storybook/preview.ts index 6dcd4a6f339..3f1d8b2ec2a 100644 --- a/packages/styleguide/.storybook/preview.ts +++ b/packages/styleguide/.storybook/preview.ts @@ -49,6 +49,7 @@ const preview: Preview = { 'ESLint rules', 'Contributing', 'FAQs', + 'Logical and physical CSS properties', 'Stories', ], 'Foundations', @@ -163,6 +164,31 @@ export const globalTypes = { showName: true, }, }, + logicalProps: { + name: 'LogicalProps', + description: 'Toggle between logical and physical CSS properties', + defaultValue: 'true', + toolbar: { + icon: 'transfer', + items: [ + { value: 'true', title: 'Logical' }, + { value: 'false', title: 'Physical' }, + ], + showName: true, + }, + }, + direction: { + name: 'Direction', + description: 'Text direction for the page', + defaultValue: 'ltr', + toolbar: { + items: [ + { value: 'ltr', icon: 'arrowright', title: 'Left-To-Right' }, + { value: 'rtl', icon: 'arrowleft', title: 'Right-To-Left' }, + ], + showName: true, + }, + }, }; export const decorators = [withEmotion]; diff --git a/packages/styleguide/.storybook/theming/GamutThemeProvider.tsx b/packages/styleguide/.storybook/theming/GamutThemeProvider.tsx index fec952647b8..2bbb8949a27 100644 --- a/packages/styleguide/.storybook/theming/GamutThemeProvider.tsx +++ b/packages/styleguide/.storybook/theming/GamutThemeProvider.tsx @@ -34,12 +34,16 @@ type GlobalsContext = { globals: { colorMode: 'light' | 'dark'; theme: keyof typeof themeMap; + logicalProps: 'true' | 'false'; + direction: 'ltr' | 'rtl'; }; }; export const withEmotion = (Story: any, context: GlobalsContext) => { const colorMode = context.globals.colorMode ?? 'light'; const selectedTheme = context.globals.theme; + const useLogicalProperties = context.globals.logicalProps !== 'false'; + const direction = context.globals.direction ?? 'ltr'; const background = corePalette[themeBackground[colorMode]]; const storyRef = useRef(null); const currentTheme = themeMap[selectedTheme]; @@ -57,12 +61,14 @@ export const withEmotion = (Story: any, context: GlobalsContext) => { {Story()} @@ -72,11 +78,15 @@ export const withEmotion = (Story: any, context: GlobalsContext) => { // Wrap all stories in minimal provider return ( - + {Story()} diff --git a/packages/styleguide/src/lib/Foundations/System/Props/Space.mdx b/packages/styleguide/src/lib/Foundations/System/Props/Space.mdx index 0afcbb48e6a..2f5da1611df 100644 --- a/packages/styleguide/src/lib/Foundations/System/Props/Space.mdx +++ b/packages/styleguide/src/lib/Foundations/System/Props/Space.mdx @@ -1,8 +1,9 @@ -import { Meta } from '@storybook/blocks'; +import { Canvas, Meta } from '@storybook/blocks'; -import { AboutHeader, TokenTable } from '~styleguide/blocks'; +import { AboutHeader, Callout, TokenTable } from '~styleguide/blocks'; import { defaultColumns, getPropRows } from '../../shared/elements'; +import * as SpaceStories from './Space.stories'; export const parameters = { title: 'Space', @@ -10,7 +11,7 @@ export const parameters = { status: 'updating', }; - + @@ -25,4 +26,19 @@ const SpaceExample = styled.div(system.space); ; ``` +These space props support both physical and logical CSS properties and will render the appropriate properties based on `useLogicalProperties`'s value passed into the `` at the root of your application. + + + You can use the LogicalProps button in the toolbar to + switch between modes. + + } +/> + + + + + diff --git a/packages/styleguide/src/lib/Foundations/System/Props/Space.stories.tsx b/packages/styleguide/src/lib/Foundations/System/Props/Space.stories.tsx new file mode 100644 index 00000000000..d94e10f0a0b --- /dev/null +++ b/packages/styleguide/src/lib/Foundations/System/Props/Space.stories.tsx @@ -0,0 +1,48 @@ +import { Box, Markdown } from '@codecademy/gamut'; +import type { Meta, StoryObj } from '@storybook/react'; + +const meta: Meta = { + title: 'Foundations/System/Props/Space', + component: Box, +}; + +export default meta; +type Story = StoryObj; + +export const MarginExample: Story = { + render: () => ( + + This box has{' '} + Inspect + the example to see what CSS properties are rendered. + + ), +}; + +export const PaddingExample: Story = { + render: () => ( + + + This box has{' '} + {' '} + Inspect the example to see what CSS properties are rendered. + + + ), +}; diff --git a/packages/styleguide/src/lib/Foundations/shared/elements.tsx b/packages/styleguide/src/lib/Foundations/shared/elements.tsx index a6b0cf500cf..4d345df50be 100644 --- a/packages/styleguide/src/lib/Foundations/shared/elements.tsx +++ b/packages/styleguide/src/lib/Foundations/shared/elements.tsx @@ -9,6 +9,7 @@ import { } from '@codecademy/gamut-styles'; // eslint-disable-next-line gamut/import-paths import * as ALL_PROPS from '@codecademy/gamut-styles/src/variance/config'; +import { useTheme } from '@emotion/react'; import styled from '@emotion/styled'; import kebabCase from 'lodash/kebabCase'; @@ -412,29 +413,61 @@ export const DarkModeTable = () => ( ); /* eslint-disable gamut/import-paths */ +const PropertiesRenderer = ({ + property, + properties, + resolveProperty, +}: { + property: string | { physical: string; logical: string }; + properties?: string[] | { physical: string[]; logical: string[] }; + resolveProperty?: (useLogicalProperties: boolean) => 'logical' | 'physical'; +}) => { + const currentTheme = useTheme() as { useLogicalProperties?: boolean }; + const useLogicalProperties = currentTheme?.useLogicalProperties ?? true; + + const mode = resolveProperty + ? resolveProperty(useLogicalProperties) + : 'physical'; + + const resolvedProperty = + typeof property === 'string' ? property : property[mode]; + + let resolvedProperties: string[]; + if (!properties) { + resolvedProperties = [resolvedProperty]; + } else if (Array.isArray(properties)) { + resolvedProperties = properties; + } else { + resolvedProperties = properties[mode]; + } + + return ( + <> + {resolvedProperties.map((prop) => ( + + {kebabCase(prop)} + + ))} + + ); +}; + const PROPERTIES_COLUMN = { key: 'properties', name: 'Properties', size: 'xl', - render: ({ - property, - properties = [property], - }: { - property: string; - properties: string[]; - }) => - properties.map((property) => ( - - {kebabCase(property)} - - )), + render: (props: { + property: string | { physical: string; logical: string }; + properties?: string[] | { physical: string[]; logical: string[] }; + resolveProperty?: (useLogicalProperties: boolean) => 'logical' | 'physical'; + }) => , }; const SCALE_COLUMN = { @@ -450,7 +483,12 @@ const TRANSFORM_COLUMN = { key: 'transform', name: 'Transform', size: 'fill', - render: ({ transform }: any) => transform && {transform?.name}, + render: ({ transform, resolveProperty }: any) => ( + <> + {transform && {transform?.name}} + {resolveProperty && {resolveProperty?.name}} + + ), }; export const defaultColumns = [ diff --git a/packages/styleguide/src/lib/Meta/About.mdx b/packages/styleguide/src/lib/Meta/About.mdx index 6239464d45c..b159c4fafff 100644 --- a/packages/styleguide/src/lib/Meta/About.mdx +++ b/packages/styleguide/src/lib/Meta/About.mdx @@ -13,6 +13,7 @@ import { parameters as deepControlsParameters } from './Deep Controls Add-On.mdx import { parameters as eslintRulesParameters } from './ESLint rules.mdx'; import { parameters as faqsParameters } from './FAQs.mdx'; import { parameters as installationParameters } from './Installation.mdx'; +import { parameters as logicalPhysicalParameters } from './Logical and physical CSS properties.mdx'; import { parameters as storiesParameters } from './Stories.mdx'; import { parameters as usageGuideParameters } from './Usage Guide.mdx'; @@ -34,9 +35,10 @@ export const parameters = { deepControlsParameters, eslintRulesParameters, faqsParameters, + installationParameters, + logicalPhysicalParameters, storiesParameters, brandParameters, - installationParameters, usageGuideParameters, ])} /> diff --git a/packages/styleguide/src/lib/Meta/Logical and physical CSS properties.mdx b/packages/styleguide/src/lib/Meta/Logical and physical CSS properties.mdx new file mode 100644 index 00000000000..0ed385fcf5d --- /dev/null +++ b/packages/styleguide/src/lib/Meta/Logical and physical CSS properties.mdx @@ -0,0 +1,139 @@ +import { Meta } from '@storybook/blocks'; + +import { + AboutHeader, + Callout, + Code, + ImageWrapper, + TokenTable, +} from '~styleguide/blocks'; + +export const parameters = { + id: 'Meta/Logical and physical CSS properties', + title: 'Logical and physical CSS properties', + subtitle: + 'Understanding CSS logical and physical properties and how Gamut supports both modes.', + status: 'static', +}; + + + + + +## What are CSS logical properties? + +CSS logical properties are a modern approach to styling that adapts to the writing mode and text direction of your content, rather than being tied to physical screen directions. More information can be found on [MDN: CSS Logical Properties](https://developer.mozilla.org/en-US/docs/Web/CSS/CSS_logical_properties_and_values) + +### Physical Properties (Traditional) + +Physical properties reference the physical dimensions of the viewport. For example: + +- `margin-left`, `margin-right`, `margin-top`, `margin-bottom` +- `padding-left`, `padding-right`, `padding-top`, `padding-bottom` + +These work well for left-to-right (LTR) languages but require manual overrides for right-to-left (RTL) languages like Arabic or Hebrew. + +### Logical Properties (Modern) + +Logical properties reference the flow of content: + +- **Inline axis** (text direction): `margin-inline-start`, `margin-inline-end` +- **Block axis** (reading direction): `margin-block-start`, `margin-block-end` + +## Using `useLogicalProperties` in Gamut + +Gamut supports both physical and logical CSS properties through the `useLogicalProperties` prop on `GamutProvider`. This allows you to choose which mode your application uses. By default, `useLogicalProperties` is set to `true`, meaning Gamut will use logical CSS properties. If you want to use physical CSS properties, you have to set `useLogicalProperties` to `false`. + +### Affected Props + +When `useLogicalProperties` is enabled, Gamut replaces physical CSS properties with their logical equivalents. This applies to both **base properties** (like `marginLeft`, `width`) and **shorthand props** (like `mx`, `py`). + +The table below shows a few examples — this is not a comprehensive list. + + {prop}, + }, + { + key: 'physical', + name: 'Physical CSS', + size: 'xl', + render: ({ physical }) => + physical.map((p) => ( + <> + {p}{' '} + + )), + }, + { + key: 'logical', + name: 'Logical CSS', + size: 'xl', + render: ({ logical }) => + logical.map((l) => ( + <> + {l}{' '} + + )), + }, + ]} + rows={[ + { + prop: 'marginLeft', + physical: ['margin-left'], + logical: ['margin-inline-start'], + }, + { + prop: 'mx', + physical: ['margin-left', 'margin-right'], + logical: ['margin-inline-start', 'margin-inline-end'], + }, + { + prop: 'paddingTop', + physical: ['padding-top'], + logical: ['padding-block-start'], + }, + { + prop: 'py', + physical: ['padding-top', 'padding-bottom'], + logical: ['padding-block-start', 'padding-block-end'], + }, + { + prop: 'width', + physical: ['width'], + logical: ['inline-size'], + }, + { + prop: 'height', + physical: ['height'], + logical: ['block-size'], + }, + ]} +/> + + + Props like m and p (which set all four sides at + once) are not affected by this setting, as the CSS margin and{' '} + padding shorthands work identically in both modes. + + } +/> + +## Previewing in Storybook + +You can toggle between logical and physical properties in Storybook using the **LogicalProps** toolbar button: + + + +This allows you to preview how components render with either property mode without changing any code. diff --git a/packages/styleguide/src/lib/Meta/Usage Guide.mdx b/packages/styleguide/src/lib/Meta/Usage Guide.mdx index fd7d01107fa..d8298ee7632 100644 --- a/packages/styleguide/src/lib/Meta/Usage Guide.mdx +++ b/packages/styleguide/src/lib/Meta/Usage Guide.mdx @@ -39,7 +39,8 @@ On each page is a toolbar located on the top: 1. Grid (the 2x2 collection of squares) - applies a grid to the preview 2. Color mode selector (the circle icon) - toggles between light and dark mode for rendered code examples 3. Theme switcher (the paintbrush icon) - switches between different design system themes (Core, Admin, LX Studio, Percipio) -4. Outline (dotted square) - applies outlines to elements in the rendered code examples +4. LogicalProps (the two arrows icon) - toggles between physical and logical CSS properties +5. Outline (dotted square) - applies outlines to elements in the rendered code examples ### Theme Switcher @@ -58,6 +59,10 @@ Available themes: The theme switcher works in combination with the color mode selector, so you can test both light and dark variants of each theme. +### LogicalProps + +The LogicalProps button (two arrows icon) provides a menu to select between Logical and Physical CSS properties. + ### Showing code On the bottom right of each canvas (a rendered code example) is a button to show its code: diff --git a/packages/styleguide/src/static/meta/toolbar.png b/packages/styleguide/src/static/meta/toolbar.png index a94fdacf0f9dc8da1f16fd81b2b8d902edba8fc9..d5c8cb35b327ed86e2577190ebb25062312db768 100644 GIT binary patch delta 4008 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 + props !== undefined && + !isArray(props) && + 'physical' in props && + 'logical' in props; + return { ...config, prop, @@ -135,18 +145,45 @@ export const variance = { return styles; } + const useLogicalProperties = + (props.theme as { useLogicalProperties?: boolean }) + ?.useLogicalProperties ?? true; + + let resolvedProperties: readonly ( + | string + | { physical: string; logical: string } + )[]; + if (isDirectionalProperties(configProperties)) { + const mode = resolveProperty + ? resolveProperty(useLogicalProperties) + : useLogicalProperties + ? 'logical' + : 'physical'; + resolvedProperties = configProperties[mode]; + } else { + resolvedProperties = configProperties ?? [property]; + } + // for each property look up the scale value from theme if passed and apply any // final transforms to the value - properties.forEach((property) => { + resolvedProperties.forEach((property) => { + let resolvedProperty: string; + if (resolveProperty && typeof property === 'object') { + const mode = resolveProperty(useLogicalProperties); + resolvedProperty = property[mode]; + } else { + resolvedProperty = property as string; + } + let styleValue: ReturnType = intermediateValue; if (useTransform && !isUndefined(styleValue)) { - styleValue = transform(styleValue, property, props); + styleValue = transform(styleValue, resolvedProperty, props); } switch (typeof styleValue) { case 'number': case 'string': - return (styles[property] = styleValue); + return (styles[resolvedProperty] = styleValue); case 'object': return Object.assign(styles, styleValue); default: diff --git a/packages/variance/src/getPropertyMode/getPropertyMode.test.ts b/packages/variance/src/getPropertyMode/getPropertyMode.test.ts new file mode 100644 index 00000000000..ddb8720b241 --- /dev/null +++ b/packages/variance/src/getPropertyMode/getPropertyMode.test.ts @@ -0,0 +1,13 @@ +import { getPropertyMode } from './getPropertyMode'; + +describe('getPropertyMode', () => { + it.each([ + { useLogicalProperties: true, expected: 'logical' }, + { useLogicalProperties: false, expected: 'physical' }, + ])( + 'returns "$expected" when useLogicalProperties is $useLogicalProperties', + ({ useLogicalProperties, expected }) => { + expect(getPropertyMode(useLogicalProperties)).toBe(expected); + } + ); +}); diff --git a/packages/variance/src/getPropertyMode/getPropertyMode.ts b/packages/variance/src/getPropertyMode/getPropertyMode.ts new file mode 100644 index 00000000000..6b2507fc993 --- /dev/null +++ b/packages/variance/src/getPropertyMode/getPropertyMode.ts @@ -0,0 +1,7 @@ +import { PropertyMode } from '../types/properties'; + +export const getPropertyMode = ( + useLogicalProperties: boolean +): PropertyMode => { + return useLogicalProperties ? 'logical' : 'physical'; +}; diff --git a/packages/variance/src/getPropertyMode/index.ts b/packages/variance/src/getPropertyMode/index.ts new file mode 100644 index 00000000000..e85e49cd2a8 --- /dev/null +++ b/packages/variance/src/getPropertyMode/index.ts @@ -0,0 +1 @@ +export * from './getPropertyMode'; diff --git a/packages/variance/src/index.ts b/packages/variance/src/index.ts index 35fb9eb488b..6c1279e8061 100644 --- a/packages/variance/src/index.ts +++ b/packages/variance/src/index.ts @@ -3,3 +3,4 @@ export * from './createTheme'; export * from './types/props'; export * from './transforms'; export * from './scales/createScale'; +export * from './getPropertyMode'; diff --git a/packages/variance/src/types/config.ts b/packages/variance/src/types/config.ts index 860fe1b5b68..c4dc0be88eb 100644 --- a/packages/variance/src/types/config.ts +++ b/packages/variance/src/types/config.ts @@ -1,6 +1,12 @@ import { Theme } from '@emotion/react'; -import { DefaultCSSPropertyValue, PropertyTypes } from './properties'; +import { + DefaultCSSPropertyValue, + DirectionalProperties, + DirectionalProperty, + PropertyMode, + PropertyTypes, +} from './properties'; import { AbstractProps, CSSObject, @@ -14,9 +20,11 @@ import { AllUnionKeys, Key, KeyFromUnion } from './utils'; export type MapScale = Record; export type ArrayScale = readonly (string | number)[] & { length: 0 }; +export type PropertyValue = keyof PropertyTypes | DirectionalProperty; + export interface BaseProperty { - property: keyof PropertyTypes; - properties?: readonly (keyof PropertyTypes)[]; + property: PropertyValue; + properties?: readonly PropertyValue[] | DirectionalProperties; } export interface Prop extends BaseProperty { @@ -26,6 +34,7 @@ export interface Prop extends BaseProperty { prop?: string, props?: AbstractProps ) => string | number | CSSObject; + resolveProperty?: (useLogicalProperties: boolean) => PropertyMode; } export interface AbstractPropTransformer extends Prop { @@ -47,14 +56,24 @@ export type PropertyValues< All extends true ? never : object | any[] >; +// Extract a single property key from PropertyValue for type inference +// Uses 'physical' for directional properties (both physical/logical have same value types) +type BasePropertyKey

= P extends DirectionalProperty ? P['physical'] : P; + export type ScaleValue = Config['scale'] extends keyof Theme - ? keyof Theme[Config['scale']] | PropertyValues + ? + | keyof Theme[Config['scale']] + | PropertyValues> : Config['scale'] extends MapScale - ? keyof Config['scale'] | PropertyValues + ? + | keyof Config['scale'] + | PropertyValues> : Config['scale'] extends ArrayScale - ? Config['scale'][number] | PropertyValues - : PropertyValues; + ? + | Config['scale'][number] + | PropertyValues> + : PropertyValues, true>; export type Scale = ResponsiveProp< ScaleValue | ((theme: Theme) => ScaleValue) diff --git a/packages/variance/src/types/properties.ts b/packages/variance/src/types/properties.ts index f307cb60d2c..61131257ff5 100644 --- a/packages/variance/src/types/properties.ts +++ b/packages/variance/src/types/properties.ts @@ -47,3 +47,15 @@ export interface VendorPropertyTypes export interface CSSPropertyTypes extends PropertyTypes, VendorPropertyTypes {} + +export type PropertyMode = 'logical' | 'physical'; + +export interface DirectionalProperty { + physical: keyof PropertyTypes; + logical: keyof PropertyTypes; +} + +export interface DirectionalProperties { + physical: readonly (keyof PropertyTypes)[]; + logical: readonly (keyof PropertyTypes)[]; +} diff --git a/packages/variance/src/utils/propNames.ts b/packages/variance/src/utils/propNames.ts index f4f75913cc8..3450bdb4f13 100644 --- a/packages/variance/src/utils/propNames.ts +++ b/packages/variance/src/utils/propNames.ts @@ -1,4 +1,5 @@ -import { BaseProperty } from '../types/config'; +import { BaseProperty, PropertyValue } from '../types/config'; +import { DirectionalProperties } from '../types/properties'; const SHORTHAND_PROPERTIES = [ 'border', @@ -36,6 +37,19 @@ const compare = (a: number, b: number) => { return SORT.EQUAL; }; +const isShorthand = (prop: PropertyValue): boolean => + typeof prop === 'string' && SHORTHAND_PROPERTIES.includes(prop); + +const getShorthandIndex = (prop: PropertyValue): number => + typeof prop === 'string' ? SHORTHAND_PROPERTIES.indexOf(prop) : -1; + +const getPropertiesCount = (properties: BaseProperty['properties']): number => { + if (!properties) return 0; + if (Array.isArray(properties)) return properties.length; + // DirectionalProperties object - using physical array length as representative, since the length for logical is the same + return (properties as DirectionalProperties).physical?.length ?? 0; +}; + /** * Orders all properties by the most dependent props * @param config @@ -44,21 +58,18 @@ export const orderPropNames = (config: Record) => Object.keys(config).sort((a, b) => { const { [a]: aConf, [b]: bConf } = config; - const { property: aProp, properties: aProperties = [] } = aConf; - const { property: bProp, properties: bProperties = [] } = bConf; + const { property: aProp, properties: aProperties } = aConf; + const { property: bProp, properties: bProperties } = bConf; - const aIsShorthand = SHORTHAND_PROPERTIES.includes(aProp); - const bIsShorthand = SHORTHAND_PROPERTIES.includes(bProp); + const aIsShorthand = isShorthand(aProp); + const bIsShorthand = isShorthand(bProp); if (aIsShorthand && bIsShorthand) { - const aNum = aProperties.length; - const bNum = bProperties.length; + const aNum = getPropertiesCount(aProperties); + const bNum = getPropertiesCount(bProperties); if (aProp !== bProp) { - return compare( - SHORTHAND_PROPERTIES.indexOf(aProp), - SHORTHAND_PROPERTIES.indexOf(bProp) - ); + return compare(getShorthandIndex(aProp), getShorthandIndex(bProp)); } if (aProp === bProp) { From e852afa6b0f70eb0e3a696c2b0fde7be0047bd5b Mon Sep 17 00:00:00 2001 From: Kenny Lin Date: Fri, 6 Feb 2026 14:54:21 -0500 Subject: [PATCH 04/15] feat: Add border related logical props (#3257) * working PoC * fix build and format * lint fixes * some more refactoring * fix existing test failures * add test for getPropertyMode * updated gamutprovider to include useLogicalProperties * fix failiing tests * more test fixes * formatted * add logicalprops switcher to toolbar * updated shorthand in margin related CSS properties * fix linting issue re: physical * update docs to show logical prop updates to margin related props * updated padding too * updated Usage Guide and clean up * add new file to explain logical and physical properties * update docs for readibility * formatted and cleaned up * fix tests and edit MockGamutProvider to use useLogicalProperties * temp fix for test failure * start on border related logical props * updated more border props * formatted * added border color props * grammar * formatted --- packages/gamut-icons/CHANGELOG.md | 6 - packages/gamut-icons/package.json | 2 +- .../src/svg/regular/live-learning-icon.svg | 11 - packages/gamut-kit/CHANGELOG.md | 4 - packages/gamut-kit/package.json | 6 +- packages/gamut-styles/src/GamutProvider.tsx | 1 + packages/gamut-styles/src/variance/config.ts | 190 +++++++++++++++--- packages/gamut/CHANGELOG.md | 4 - packages/gamut/package.json | 4 +- packages/styleguide/.storybook/preview.ts | 12 -- .../.storybook/theming/GamutThemeProvider.tsx | 4 - .../lib/Foundations/System/Props/Border.mdx | 26 ++- .../System/Props/Border.stories.tsx | 138 +++++++++++++ .../lib/Foundations/System/Props/Color.mdx | 20 +- .../System/Props/Color.stories.tsx | 47 +++++ .../Logical and physical CSS properties.mdx | 32 +-- yarn.lock | 10 +- 17 files changed, 403 insertions(+), 114 deletions(-) delete mode 100644 packages/gamut-icons/src/svg/regular/live-learning-icon.svg create mode 100644 packages/styleguide/src/lib/Foundations/System/Props/Border.stories.tsx create mode 100644 packages/styleguide/src/lib/Foundations/System/Props/Color.stories.tsx diff --git a/packages/gamut-icons/CHANGELOG.md b/packages/gamut-icons/CHANGELOG.md index 87d68aff6a4..60f2574890d 100644 --- a/packages/gamut-icons/CHANGELOG.md +++ b/packages/gamut-icons/CHANGELOG.md @@ -3,12 +3,6 @@ All notable changes to this project will be documented in this file. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. -## [9.55.0](https://github.com/Codecademy/gamut/compare/@codecademy/gamut-icons@9.54.2...@codecademy/gamut-icons@9.55.0) (2026-02-04) - -### Features - -- **Icon:** :sparkles: Add Live Learning Icon ([7dfbf29](https://github.com/Codecademy/gamut/commit/7dfbf292b80a3eb242d6eb66ede31e91b110d307)) - ### [9.54.2](https://github.com/Codecademy/gamut/compare/@codecademy/gamut-icons@9.54.1...@codecademy/gamut-icons@9.54.2) (2026-01-28) **Note:** Version bump only for package @codecademy/gamut-icons diff --git a/packages/gamut-icons/package.json b/packages/gamut-icons/package.json index 48935e6ce58..c7a2639cbf6 100644 --- a/packages/gamut-icons/package.json +++ b/packages/gamut-icons/package.json @@ -1,7 +1,7 @@ { "name": "@codecademy/gamut-icons", "description": "Icon library for codecademy.com", - "version": "9.55.0", + "version": "9.54.2", "author": "Codecademy ", "dependencies": { "@codecademy/gamut-styles": "17.11.2", diff --git a/packages/gamut-icons/src/svg/regular/live-learning-icon.svg b/packages/gamut-icons/src/svg/regular/live-learning-icon.svg deleted file mode 100644 index ff98991b87b..00000000000 --- a/packages/gamut-icons/src/svg/regular/live-learning-icon.svg +++ /dev/null @@ -1,11 +0,0 @@ - - - Online Class Student Streamline Icon: https://streamlinehq.com - - - - - - - - \ No newline at end of file diff --git a/packages/gamut-kit/CHANGELOG.md b/packages/gamut-kit/CHANGELOG.md index 03ecd257ae3..e5a60325d51 100644 --- a/packages/gamut-kit/CHANGELOG.md +++ b/packages/gamut-kit/CHANGELOG.md @@ -3,10 +3,6 @@ All notable changes to this project will be documented in this file. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. -### [0.6.580](https://github.com/Codecademy/gamut/compare/@codecademy/gamut-kit@0.6.579...@codecademy/gamut-kit@0.6.580) (2026-02-04) - -**Note:** Version bump only for package @codecademy/gamut-kit - ### [0.6.579](https://github.com/Codecademy/gamut/compare/@codecademy/gamut-kit@0.6.578...@codecademy/gamut-kit@0.6.579) (2026-01-29) **Note:** Version bump only for package @codecademy/gamut-kit diff --git a/packages/gamut-kit/package.json b/packages/gamut-kit/package.json index 90fc80f3dd4..e5cb992cfdc 100644 --- a/packages/gamut-kit/package.json +++ b/packages/gamut-kit/package.json @@ -1,11 +1,11 @@ { "name": "@codecademy/gamut-kit", "description": "Styleguide & Component library for Codecademy", - "version": "0.6.580", + "version": "0.6.579", "author": "Codecademy Engineering ", "dependencies": { - "@codecademy/gamut": "68.0.1", - "@codecademy/gamut-icons": "9.55.0", + "@codecademy/gamut": "68.0.0", + "@codecademy/gamut-icons": "9.54.2", "@codecademy/gamut-illustrations": "0.58.2", "@codecademy/gamut-patterns": "0.10.21", "@codecademy/gamut-styles": "17.11.2", diff --git a/packages/gamut-styles/src/GamutProvider.tsx b/packages/gamut-styles/src/GamutProvider.tsx index 4bb266f0724..4cc22d73f1a 100644 --- a/packages/gamut-styles/src/GamutProvider.tsx +++ b/packages/gamut-styles/src/GamutProvider.tsx @@ -65,6 +65,7 @@ export const GamutProvider: React.FC = ({ const contextValue = { hasGlobals: shouldInsertGlobals, hasCache: shouldCreateCache, + useLogicalProperties, }; const globals = shouldInsertGlobals && ( diff --git a/packages/gamut-styles/src/variance/config.ts b/packages/gamut-styles/src/variance/config.ts index d4514ecd18b..e179fdf74a8 100644 --- a/packages/gamut-styles/src/variance/config.ts +++ b/packages/gamut-styles/src/variance/config.ts @@ -7,18 +7,42 @@ export const color = { borderColor: { property: 'borderColor', scale: 'colors' }, borderColorX: { property: 'borderColor', - properties: ['borderLeftColor', 'borderRightColor'], + properties: { + physical: ['borderLeftColor', 'borderRightColor'], + logical: ['borderInlineStartColor', 'borderInlineEndColor'], + }, + resolveProperty: getPropertyMode, scale: 'colors', }, borderColorY: { property: 'borderColor', - properties: ['borderTopColor', 'borderBottomColor'], + properties: { + physical: ['borderTopColor', 'borderBottomColor'], + logical: ['borderBlockStartColor', 'borderBlockEndColor'], + }, + resolveProperty: getPropertyMode, + scale: 'colors', + }, + borderColorLeft: { + property: 'borderLeftColor', + resolveProperty: getPropertyMode, + scale: 'colors', + }, + borderColorRight: { + property: 'borderRightColor', + resolveProperty: getPropertyMode, + scale: 'colors', + }, + borderColorTop: { + property: 'borderTopColor', + resolveProperty: getPropertyMode, + scale: 'colors', + }, + borderColorBottom: { + property: 'borderBottomColor', + resolveProperty: getPropertyMode, scale: 'colors', }, - borderColorLeft: { property: 'borderLeftColor', scale: 'colors' }, - borderColorRight: { property: 'borderRightColor', scale: 'colors' }, - borderColorTop: { property: 'borderTopColor', scale: 'colors' }, - borderColorBottom: { property: 'borderBottomColor', scale: 'colors' }, } as const; export const border = { @@ -26,84 +50,186 @@ export const border = { border: { property: 'border', scale: 'borders' }, borderX: { property: 'border', - properties: ['borderLeft', 'borderRight'], + properties: { + physical: ['borderLeft', 'borderRight'], + logical: ['borderInlineStart', 'borderInlineEnd'], + }, + resolveProperty: getPropertyMode, scale: 'borders', }, borderY: { property: 'border', - properties: ['borderTop', 'borderBottom'], + properties: { + physical: ['borderTop', 'borderBottom'], + logical: ['borderBlockStart', 'borderBlockEnd'], + }, + resolveProperty: getPropertyMode, + scale: 'borders', + }, + borderTop: { + property: { physical: 'borderTop', logical: 'borderBlockStart' }, + resolveProperty: getPropertyMode, + scale: 'borders', + }, + borderRight: { + property: { physical: 'borderRight', logical: 'borderInlineEnd' }, + resolveProperty: getPropertyMode, + scale: 'borders', + }, + borderBottom: { + property: { physical: 'borderBottom', logical: 'borderBlockEnd' }, + resolveProperty: getPropertyMode, + scale: 'borders', + }, + borderLeft: { + property: { physical: 'borderLeft', logical: 'borderInlineStart' }, + resolveProperty: getPropertyMode, scale: 'borders', }, - borderTop: { property: 'borderTop', scale: 'borders' }, - borderRight: { property: 'borderRight', scale: 'borders' }, - borderBottom: { property: 'borderBottom', scale: 'borders' }, - borderLeft: { property: 'borderLeft', scale: 'borders' }, // Width borderWidth: { property: 'borderWidth' }, borderWidthX: { property: 'borderWidth', - properties: ['borderLeftWidth', 'borderRightWidth'], + properties: { + physical: ['borderLeftWidth', 'borderRightWidth'], + logical: ['borderInlineStartWidth', 'borderInlineEndWidth'], + }, + resolveProperty: getPropertyMode, }, borderWidthY: { property: 'borderWidth', - properties: ['borderTopWidth', 'borderBottomWidth'], + properties: { + physical: ['borderTopWidth', 'borderBottomWidth'], + logical: ['borderBlockStartWidth', 'borderBlockEndWidth'], + }, + resolveProperty: getPropertyMode, + }, + borderWidthLeft: { + property: { + physical: 'borderLeftWidth', + logical: 'borderInlineStartWidth', + }, + resolveProperty: getPropertyMode, + }, + borderWidthRight: { + property: { physical: 'borderRightWidth', logical: 'borderInlineEndWidth' }, + resolveProperty: getPropertyMode, + }, + borderWidthTop: { + property: { physical: 'borderTopWidth', logical: 'borderBlockStartWidth' }, + resolveProperty: getPropertyMode, + }, + borderWidthBottom: { + property: { physical: 'borderBottomWidth', logical: 'borderBlockEndWidth' }, + resolveProperty: getPropertyMode, }, - borderWidthLeft: { property: 'borderLeftWidth' }, - borderWidthRight: { property: 'borderRightWidth' }, - borderWidthTop: { property: 'borderTopWidth' }, - borderWidthBottom: { property: 'borderBottomWidth' }, // Radius borderRadius: { property: 'borderRadius', scale: 'borderRadii' }, borderRadiusLeft: { property: 'borderRadius', - properties: ['borderTopLeftRadius', 'borderBottomLeftRadius'], + properties: { + physical: ['borderTopLeftRadius', 'borderBottomLeftRadius'], + logical: ['borderStartStartRadius', 'borderEndStartRadius'], + }, + resolveProperty: getPropertyMode, scale: 'borderRadii', }, borderRadiusTop: { property: 'borderRadius', - properties: ['borderTopLeftRadius', 'borderTopRightRadius'], + properties: { + physical: ['borderTopLeftRadius', 'borderTopRightRadius'], + logical: ['borderStartStartRadius', 'borderStartEndRadius'], + }, + resolveProperty: getPropertyMode, scale: 'borderRadii', }, borderRadiusBottom: { property: 'borderRadius', - properties: ['borderBottomLeftRadius', 'borderBottomRightRadius'], + properties: { + physical: ['borderBottomLeftRadius', 'borderBottomRightRadius'], + logical: ['borderEndStartRadius', 'borderEndEndRadius'], + }, + resolveProperty: getPropertyMode, scale: 'borderRadii', }, borderRadiusRight: { property: 'borderRadius', - properties: ['borderTopRightRadius', 'borderBottomRightRadius'], + properties: { + physical: ['borderTopRightRadius', 'borderBottomRightRadius'], + logical: ['borderStartEndRadius', 'borderEndEndRadius'], + }, + resolveProperty: getPropertyMode, scale: 'borderRadii', }, borderRadiusTopLeft: { - property: 'borderTopLeftRadius', + property: { + physical: 'borderTopLeftRadius', + logical: 'borderStartStartRadius', + }, + resolveProperty: getPropertyMode, scale: 'borderRadii', }, borderRadiusTopRight: { - property: 'borderTopRightRadius', + property: { + physical: 'borderTopRightRadius', + logical: 'borderStartEndRadius', + }, + resolveProperty: getPropertyMode, scale: 'borderRadii', }, borderRadiusBottomRight: { - property: 'borderBottomRightRadius', + property: { + physical: 'borderBottomRightRadius', + logical: 'borderEndEndRadius', + }, + resolveProperty: getPropertyMode, scale: 'borderRadii', }, borderRadiusBottomLeft: { - property: 'borderBottomLeftRadius', + property: { + physical: 'borderBottomLeftRadius', + logical: 'borderEndStartRadius', + }, + resolveProperty: getPropertyMode, scale: 'borderRadii', }, // Style borderStyle: { property: 'borderStyle' }, borderStyleX: { property: 'borderStyle', - properties: ['borderLeftStyle', 'borderRightStyle'], + properties: { + physical: ['borderLeftStyle', 'borderRightStyle'], + logical: ['borderInlineStartStyle', 'borderInlineEndStyle'], + }, + resolveProperty: getPropertyMode, }, borderStyleY: { property: 'borderStyle', - properties: ['borderTopStyle', 'borderBottomStyle'], + properties: { + physical: ['borderTopStyle', 'borderBottomStyle'], + logical: ['borderBlockStartStyle', 'borderBlockEndStyle'], + }, + resolveProperty: getPropertyMode, + }, + borderStyleLeft: { + property: { + physical: 'borderLeftStyle', + logical: 'borderInlineStartStyle', + }, + resolveProperty: getPropertyMode, + }, + borderStyleRight: { + property: { physical: 'borderRightStyle', logical: 'borderInlineEndStyle' }, + resolveProperty: getPropertyMode, + }, + borderStyleTop: { + property: { physical: 'borderTopStyle', logical: 'borderBlockStartStyle' }, + resolveProperty: getPropertyMode, + }, + borderStyleBottom: { + property: { physical: 'borderBottomStyle', logical: 'borderBlockEndStyle' }, + resolveProperty: getPropertyMode, }, - borderStyleLeft: { property: 'borderLeftStyle' }, - borderStyleRight: { property: 'borderRightStyle' }, - borderStyleTop: { property: 'borderTopStyle' }, - borderStyleBottom: { property: 'borderBottomStyle' }, } as const; const selfAlignments = { diff --git a/packages/gamut/CHANGELOG.md b/packages/gamut/CHANGELOG.md index ee0990f24f0..c9284863f7c 100644 --- a/packages/gamut/CHANGELOG.md +++ b/packages/gamut/CHANGELOG.md @@ -3,10 +3,6 @@ All notable changes to this project will be documented in this file. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. -### [68.0.1](https://github.com/Codecademy/gamut/compare/@codecademy/gamut@68.0.0...@codecademy/gamut@68.0.1) (2026-02-04) - -**Note:** Version bump only for package @codecademy/gamut - ## [68.0.0](https://github.com/Codecademy/gamut/compare/@codecademy/gamut@67.6.5...@codecademy/gamut@68.0.0) (2026-01-29) ### ⚠ BREAKING CHANGES diff --git a/packages/gamut/package.json b/packages/gamut/package.json index dda7b640ec6..d8d6ed45dbf 100644 --- a/packages/gamut/package.json +++ b/packages/gamut/package.json @@ -1,10 +1,10 @@ { "name": "@codecademy/gamut", "description": "Styleguide & Component library for Codecademy", - "version": "68.0.1", + "version": "68.0.0", "author": "Codecademy Engineering ", "dependencies": { - "@codecademy/gamut-icons": "9.55.0", + "@codecademy/gamut-icons": "9.54.2", "@codecademy/gamut-illustrations": "0.58.2", "@codecademy/gamut-patterns": "0.10.21", "@codecademy/gamut-styles": "17.11.2", diff --git a/packages/styleguide/.storybook/preview.ts b/packages/styleguide/.storybook/preview.ts index 3f1d8b2ec2a..c1bc2b1da6e 100644 --- a/packages/styleguide/.storybook/preview.ts +++ b/packages/styleguide/.storybook/preview.ts @@ -177,18 +177,6 @@ export const globalTypes = { showName: true, }, }, - direction: { - name: 'Direction', - description: 'Text direction for the page', - defaultValue: 'ltr', - toolbar: { - items: [ - { value: 'ltr', icon: 'arrowright', title: 'Left-To-Right' }, - { value: 'rtl', icon: 'arrowleft', title: 'Right-To-Left' }, - ], - showName: true, - }, - }, }; export const decorators = [withEmotion]; diff --git a/packages/styleguide/.storybook/theming/GamutThemeProvider.tsx b/packages/styleguide/.storybook/theming/GamutThemeProvider.tsx index 2bbb8949a27..0e54d335318 100644 --- a/packages/styleguide/.storybook/theming/GamutThemeProvider.tsx +++ b/packages/styleguide/.storybook/theming/GamutThemeProvider.tsx @@ -35,7 +35,6 @@ type GlobalsContext = { colorMode: 'light' | 'dark'; theme: keyof typeof themeMap; logicalProps: 'true' | 'false'; - direction: 'ltr' | 'rtl'; }; }; @@ -43,7 +42,6 @@ export const withEmotion = (Story: any, context: GlobalsContext) => { const colorMode = context.globals.colorMode ?? 'light'; const selectedTheme = context.globals.theme; const useLogicalProperties = context.globals.logicalProps !== 'false'; - const direction = context.globals.direction ?? 'ltr'; const background = corePalette[themeBackground[colorMode]]; const storyRef = useRef(null); const currentTheme = themeMap[selectedTheme]; @@ -68,7 +66,6 @@ export const withEmotion = (Story: any, context: GlobalsContext) => { alwaysSetVariables bg={themeBackground[colorMode]} ref={storyRef} - dir={direction} > {Story()} @@ -86,7 +83,6 @@ export const withEmotion = (Story: any, context: GlobalsContext) => { alwaysSetVariables bg={themeBackground[colorMode]} ref={storyRef} - dir={direction} > {Story()} diff --git a/packages/styleguide/src/lib/Foundations/System/Props/Border.mdx b/packages/styleguide/src/lib/Foundations/System/Props/Border.mdx index 554cafe2139..99479956fe8 100644 --- a/packages/styleguide/src/lib/Foundations/System/Props/Border.mdx +++ b/packages/styleguide/src/lib/Foundations/System/Props/Border.mdx @@ -1,8 +1,9 @@ -import { Meta } from '@storybook/blocks'; +import { Canvas, Meta } from '@storybook/blocks'; -import { AboutHeader, TokenTable } from '~styleguide/blocks'; +import { AboutHeader, Callout, TokenTable } from '~styleguide/blocks'; import { defaultColumns, getPropRows } from '../../shared/elements'; +import * as BorderStories from './Border.stories'; export const parameters = { title: 'Border', @@ -10,7 +11,7 @@ export const parameters = { status: 'updating', }; - + @@ -30,4 +31,23 @@ const BorderExample = styled.div(system.border); />; ``` +These border props support both physical and logical CSS properties and will render the appropriate properties based on `useLogicalProperties`'s value passed into the `` at the root of your application. + + + You can use the LogicalProps button in the toolbar to + switch between modes. + + } +/> + + + + + + + + + diff --git a/packages/styleguide/src/lib/Foundations/System/Props/Border.stories.tsx b/packages/styleguide/src/lib/Foundations/System/Props/Border.stories.tsx new file mode 100644 index 00000000000..1bd76b57482 --- /dev/null +++ b/packages/styleguide/src/lib/Foundations/System/Props/Border.stories.tsx @@ -0,0 +1,138 @@ +import { Box, FlexBox, Markdown } from '@codecademy/gamut'; +import type { Meta, StoryObj } from '@storybook/react'; + +const meta: Meta = { + title: 'Foundations/System/Props/Border', + component: Box, +}; + +export default meta; +type Story = StoryObj; + +export const DirectionalBorderExample: Story = { + render: () => ( + + + This box has Inspect + the example to see what CSS properties are rendered based on the logical + properties mode. + + + This box has {' '} + Inspect the example to see what CSS properties are rendered based on the + logical properties mode. + + + ), +}; + +export const BorderWidthExample: Story = { + render: () => ( + + + This box has{' '} + {' '} + Inspect the example to see what CSS properties are rendered based on the + logical properties mode. + + + This box has{' '} + {' '} + Inspect the example to see what CSS properties are rendered based on the + logical properties mode. + + + ), +}; + +export const BorderRadiusExample: Story = { + render: () => ( + + + This box has{' '} + {' '} + Inspect the example to see what CSS properties are rendered based on the + logical properties mode. + + + This box has{' '} + {' '} + Inspect the example to see what CSS properties are rendered based on the + logical properties mode. + + + ), +}; + +export const BorderStyleExample: Story = { + render: () => ( + + + This box has{' '} + {' '} + Inspect the example to see what CSS properties are rendered based on the + logical properties mode. + + + This box has{' '} + {' '} + Inspect the example to see what CSS properties are rendered based on the + logical properties mode. + + + ), +}; diff --git a/packages/styleguide/src/lib/Foundations/System/Props/Color.mdx b/packages/styleguide/src/lib/Foundations/System/Props/Color.mdx index 0f2e7cb15e1..d220fb9fc82 100644 --- a/packages/styleguide/src/lib/Foundations/System/Props/Color.mdx +++ b/packages/styleguide/src/lib/Foundations/System/Props/Color.mdx @@ -1,8 +1,9 @@ -import { Meta } from '@storybook/blocks'; +import { Canvas, Meta } from '@storybook/blocks'; -import { AboutHeader, TokenTable } from '~styleguide/blocks'; +import { AboutHeader, Callout, TokenTable } from '~styleguide/blocks'; import { defaultColumns, getPropRows } from '../../shared/elements'; +import * as ColorStories from './Color.stories'; export const parameters = { title: 'Color', @@ -10,7 +11,7 @@ export const parameters = { status: 'current', }; - + @@ -25,4 +26,17 @@ const ColorExample = styled.div(system.color); ; ``` +These color props support both physical and logical CSS properties and will render the appropriate properties based on `useLogicalProperties`'s value passed into the `` at the root of your application. + + + You can use the LogicalProps button in the toolbar to + switch between modes. + + } +/> + + + diff --git a/packages/styleguide/src/lib/Foundations/System/Props/Color.stories.tsx b/packages/styleguide/src/lib/Foundations/System/Props/Color.stories.tsx new file mode 100644 index 00000000000..9f403933298 --- /dev/null +++ b/packages/styleguide/src/lib/Foundations/System/Props/Color.stories.tsx @@ -0,0 +1,47 @@ +import { Box, FlexBox, Markdown } from '@codecademy/gamut'; +import type { Meta, StoryObj } from '@storybook/react'; + +const meta: Meta = { + title: 'Foundations/System/Props/Color', + component: Box, +}; + +export default meta; +type Story = StoryObj; + +export const BorderColorExample: Story = { + render: () => ( + + + This box has{' '} + {' '} + Inspect the example to see what CSS properties are rendered based on the + logical properties mode. + + + This box has{' '} + {' '} + Inspect the example to see what CSS properties are rendered based on the + logical properties mode. + + + ), +}; diff --git a/packages/styleguide/src/lib/Meta/Logical and physical CSS properties.mdx b/packages/styleguide/src/lib/Meta/Logical and physical CSS properties.mdx index 0ed385fcf5d..cac1e10d7c8 100644 --- a/packages/styleguide/src/lib/Meta/Logical and physical CSS properties.mdx +++ b/packages/styleguide/src/lib/Meta/Logical and physical CSS properties.mdx @@ -22,7 +22,7 @@ export const parameters = { ## What are CSS logical properties? -CSS logical properties are a modern approach to styling that adapts to the writing mode and text direction of your content, rather than being tied to physical screen directions. More information can be found on [MDN: CSS Logical Properties](https://developer.mozilla.org/en-US/docs/Web/CSS/CSS_logical_properties_and_values) +CSS logical properties are a modern approach to styling that adapts to the writing mode and text direction of your content, rather than being tied to physical screen directions. More information can be found on[MDN: CSS Logical Properties](https://developer.mozilla.org/en-US/docs/Web/CSS/CSS_logical_properties_and_values) ### Physical Properties (Traditional) @@ -46,9 +46,7 @@ Gamut supports both physical and logical CSS properties through the `useLogicalP ### Affected Props -When `useLogicalProperties` is enabled, Gamut replaces physical CSS properties with their logical equivalents. This applies to both **base properties** (like `marginLeft`, `width`) and **shorthand props** (like `mx`, `py`). - -The table below shows a few examples — this is not a comprehensive list. +Here are some examples of how physical and logical properties are affected by the `useLogicalProperties` prop: physical.map((p) => ( @@ -72,7 +70,7 @@ The table below shows a few examples — this is not a comprehensive list. }, { key: 'logical', - name: 'Logical CSS', + name: 'Logical', size: 'xl', render: ({ logical }) => logical.map((l) => ( @@ -83,35 +81,21 @@ The table below shows a few examples — this is not a comprehensive list. }, ]} rows={[ - { - prop: 'marginLeft', - physical: ['margin-left'], - logical: ['margin-inline-start'], - }, { prop: 'mx', physical: ['margin-left', 'margin-right'], logical: ['margin-inline-start', 'margin-inline-end'], }, - { - prop: 'paddingTop', - physical: ['padding-top'], - logical: ['padding-block-start'], - }, + { prop: 'mt', physical: ['margin-top'], logical: ['margin-block-start'] }, { prop: 'py', physical: ['padding-top', 'padding-bottom'], logical: ['padding-block-start', 'padding-block-end'], }, { - prop: 'width', - physical: ['width'], - logical: ['inline-size'], - }, - { - prop: 'height', - physical: ['height'], - logical: ['block-size'], + prop: 'pb', + physical: ['padding-bottom'], + logical: ['padding-block-end'], }, ]} /> diff --git a/yarn.lock b/yarn.lock index 942a41c4529..62399d5d1f5 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1616,7 +1616,7 @@ __metadata: languageName: node linkType: hard -"@codecademy/gamut-icons@npm:9.55.0, @codecademy/gamut-icons@workspace:packages/gamut-icons": +"@codecademy/gamut-icons@npm:9.54.2, @codecademy/gamut-icons@workspace:packages/gamut-icons": version: 0.0.0-use.local resolution: "@codecademy/gamut-icons@workspace:packages/gamut-icons" dependencies: @@ -1648,8 +1648,8 @@ __metadata: version: 0.0.0-use.local resolution: "@codecademy/gamut-kit@workspace:packages/gamut-kit" dependencies: - "@codecademy/gamut": "npm:68.0.1" - "@codecademy/gamut-icons": "npm:9.55.0" + "@codecademy/gamut": "npm:68.0.0" + "@codecademy/gamut-icons": "npm:9.54.2" "@codecademy/gamut-illustrations": "npm:0.58.2" "@codecademy/gamut-patterns": "npm:0.10.21" "@codecademy/gamut-styles": "npm:17.11.2" @@ -1703,11 +1703,11 @@ __metadata: languageName: unknown linkType: soft -"@codecademy/gamut@npm:68.0.1, @codecademy/gamut@workspace:packages/gamut": +"@codecademy/gamut@npm:68.0.0, @codecademy/gamut@workspace:packages/gamut": version: 0.0.0-use.local resolution: "@codecademy/gamut@workspace:packages/gamut" dependencies: - "@codecademy/gamut-icons": "npm:9.55.0" + "@codecademy/gamut-icons": "npm:9.54.2" "@codecademy/gamut-illustrations": "npm:0.58.2" "@codecademy/gamut-patterns": "npm:0.10.21" "@codecademy/gamut-styles": "npm:17.11.2" From b9bba726e7debc90c62b644a93ddefdd9e2d2914 Mon Sep 17 00:00:00 2001 From: Kenny Lin Date: Fri, 6 Feb 2026 17:06:15 -0500 Subject: [PATCH 05/15] restored deleted svg --- packages/gamut-icons/CHANGELOG.md | 6 ++++++ packages/gamut-icons/package.json | 2 +- .../src/svg/regular/live-learning-icon.svg | 11 +++++++++++ yarn.lock | 17 ++++++++++++++++- 4 files changed, 34 insertions(+), 2 deletions(-) create mode 100644 packages/gamut-icons/src/svg/regular/live-learning-icon.svg diff --git a/packages/gamut-icons/CHANGELOG.md b/packages/gamut-icons/CHANGELOG.md index 60f2574890d..87d68aff6a4 100644 --- a/packages/gamut-icons/CHANGELOG.md +++ b/packages/gamut-icons/CHANGELOG.md @@ -3,6 +3,12 @@ All notable changes to this project will be documented in this file. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. +## [9.55.0](https://github.com/Codecademy/gamut/compare/@codecademy/gamut-icons@9.54.2...@codecademy/gamut-icons@9.55.0) (2026-02-04) + +### Features + +- **Icon:** :sparkles: Add Live Learning Icon ([7dfbf29](https://github.com/Codecademy/gamut/commit/7dfbf292b80a3eb242d6eb66ede31e91b110d307)) + ### [9.54.2](https://github.com/Codecademy/gamut/compare/@codecademy/gamut-icons@9.54.1...@codecademy/gamut-icons@9.54.2) (2026-01-28) **Note:** Version bump only for package @codecademy/gamut-icons diff --git a/packages/gamut-icons/package.json b/packages/gamut-icons/package.json index c7a2639cbf6..48935e6ce58 100644 --- a/packages/gamut-icons/package.json +++ b/packages/gamut-icons/package.json @@ -1,7 +1,7 @@ { "name": "@codecademy/gamut-icons", "description": "Icon library for codecademy.com", - "version": "9.54.2", + "version": "9.55.0", "author": "Codecademy ", "dependencies": { "@codecademy/gamut-styles": "17.11.2", diff --git a/packages/gamut-icons/src/svg/regular/live-learning-icon.svg b/packages/gamut-icons/src/svg/regular/live-learning-icon.svg new file mode 100644 index 00000000000..ff98991b87b --- /dev/null +++ b/packages/gamut-icons/src/svg/regular/live-learning-icon.svg @@ -0,0 +1,11 @@ + + + Online Class Student Streamline Icon: https://streamlinehq.com + + + + + + + + \ No newline at end of file diff --git a/yarn.lock b/yarn.lock index 62399d5d1f5..2e8aa929991 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1616,7 +1616,22 @@ __metadata: languageName: node linkType: hard -"@codecademy/gamut-icons@npm:9.54.2, @codecademy/gamut-icons@workspace:packages/gamut-icons": +"@codecademy/gamut-icons@npm:9.54.2": + version: 9.54.2 + resolution: "@codecademy/gamut-icons@npm:9.54.2" + dependencies: + "@codecademy/gamut-styles": "npm:17.11.2" + "@codecademy/variance": "npm:0.25.2" + peerDependencies: + "@emotion/react": ^11.4.0 + "@emotion/styled": ^11.3.0 + lodash: ^4.17.23 + react: ^17.0.2 || ^18.2.0 + checksum: 10c0/80f10b2261c4434a9c09fa9175a8107f9b505b0e7799d689d906cd7db6014260e4753353cac3f430ca98665b321fda93755ec451666e60771e1f44e03d68c178 + languageName: node + linkType: hard + +"@codecademy/gamut-icons@workspace:packages/gamut-icons": version: 0.0.0-use.local resolution: "@codecademy/gamut-icons@workspace:packages/gamut-icons" dependencies: From d8c16347b5233d7c84a17ee4c72afbfef94ad62e Mon Sep 17 00:00:00 2001 From: Kenny Lin Date: Fri, 6 Feb 2026 17:07:25 -0500 Subject: [PATCH 06/15] restore other deleted code --- packages/styleguide/.storybook/preview.ts | 12 ++++++++++++ .../.storybook/theming/GamutThemeProvider.tsx | 4 ++++ 2 files changed, 16 insertions(+) diff --git a/packages/styleguide/.storybook/preview.ts b/packages/styleguide/.storybook/preview.ts index c1bc2b1da6e..3f1d8b2ec2a 100644 --- a/packages/styleguide/.storybook/preview.ts +++ b/packages/styleguide/.storybook/preview.ts @@ -177,6 +177,18 @@ export const globalTypes = { showName: true, }, }, + direction: { + name: 'Direction', + description: 'Text direction for the page', + defaultValue: 'ltr', + toolbar: { + items: [ + { value: 'ltr', icon: 'arrowright', title: 'Left-To-Right' }, + { value: 'rtl', icon: 'arrowleft', title: 'Right-To-Left' }, + ], + showName: true, + }, + }, }; export const decorators = [withEmotion]; diff --git a/packages/styleguide/.storybook/theming/GamutThemeProvider.tsx b/packages/styleguide/.storybook/theming/GamutThemeProvider.tsx index 0e54d335318..2bbb8949a27 100644 --- a/packages/styleguide/.storybook/theming/GamutThemeProvider.tsx +++ b/packages/styleguide/.storybook/theming/GamutThemeProvider.tsx @@ -35,6 +35,7 @@ type GlobalsContext = { colorMode: 'light' | 'dark'; theme: keyof typeof themeMap; logicalProps: 'true' | 'false'; + direction: 'ltr' | 'rtl'; }; }; @@ -42,6 +43,7 @@ export const withEmotion = (Story: any, context: GlobalsContext) => { const colorMode = context.globals.colorMode ?? 'light'; const selectedTheme = context.globals.theme; const useLogicalProperties = context.globals.logicalProps !== 'false'; + const direction = context.globals.direction ?? 'ltr'; const background = corePalette[themeBackground[colorMode]]; const storyRef = useRef(null); const currentTheme = themeMap[selectedTheme]; @@ -66,6 +68,7 @@ export const withEmotion = (Story: any, context: GlobalsContext) => { alwaysSetVariables bg={themeBackground[colorMode]} ref={storyRef} + dir={direction} > {Story()} @@ -83,6 +86,7 @@ export const withEmotion = (Story: any, context: GlobalsContext) => { alwaysSetVariables bg={themeBackground[colorMode]} ref={storyRef} + dir={direction} > {Story()} From a91ba1abcd74bb222d79a91ca91ac331151f6fc2 Mon Sep 17 00:00:00 2001 From: Kenny Lin Date: Mon, 9 Feb 2026 09:23:20 -0500 Subject: [PATCH 07/15] more restoring --- packages/gamut-kit/CHANGELOG.md | 4 +++ packages/gamut-kit/package.json | 6 ++--- yarn.lock | 44 ++++++++++++++++++++++++++++++--- 3 files changed, 47 insertions(+), 7 deletions(-) diff --git a/packages/gamut-kit/CHANGELOG.md b/packages/gamut-kit/CHANGELOG.md index e5a60325d51..03ecd257ae3 100644 --- a/packages/gamut-kit/CHANGELOG.md +++ b/packages/gamut-kit/CHANGELOG.md @@ -3,6 +3,10 @@ All notable changes to this project will be documented in this file. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. +### [0.6.580](https://github.com/Codecademy/gamut/compare/@codecademy/gamut-kit@0.6.579...@codecademy/gamut-kit@0.6.580) (2026-02-04) + +**Note:** Version bump only for package @codecademy/gamut-kit + ### [0.6.579](https://github.com/Codecademy/gamut/compare/@codecademy/gamut-kit@0.6.578...@codecademy/gamut-kit@0.6.579) (2026-01-29) **Note:** Version bump only for package @codecademy/gamut-kit diff --git a/packages/gamut-kit/package.json b/packages/gamut-kit/package.json index e5cb992cfdc..90fc80f3dd4 100644 --- a/packages/gamut-kit/package.json +++ b/packages/gamut-kit/package.json @@ -1,11 +1,11 @@ { "name": "@codecademy/gamut-kit", "description": "Styleguide & Component library for Codecademy", - "version": "0.6.579", + "version": "0.6.580", "author": "Codecademy Engineering ", "dependencies": { - "@codecademy/gamut": "68.0.0", - "@codecademy/gamut-icons": "9.54.2", + "@codecademy/gamut": "68.0.1", + "@codecademy/gamut-icons": "9.55.0", "@codecademy/gamut-illustrations": "0.58.2", "@codecademy/gamut-patterns": "0.10.21", "@codecademy/gamut-styles": "17.11.2", diff --git a/yarn.lock b/yarn.lock index 2e8aa929991..782422eba8d 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1631,7 +1631,7 @@ __metadata: languageName: node linkType: hard -"@codecademy/gamut-icons@workspace:packages/gamut-icons": +"@codecademy/gamut-icons@npm:9.55.0, @codecademy/gamut-icons@workspace:packages/gamut-icons": version: 0.0.0-use.local resolution: "@codecademy/gamut-icons@workspace:packages/gamut-icons" dependencies: @@ -1663,8 +1663,8 @@ __metadata: version: 0.0.0-use.local resolution: "@codecademy/gamut-kit@workspace:packages/gamut-kit" dependencies: - "@codecademy/gamut": "npm:68.0.0" - "@codecademy/gamut-icons": "npm:9.54.2" + "@codecademy/gamut": "npm:68.0.1" + "@codecademy/gamut-icons": "npm:9.55.0" "@codecademy/gamut-illustrations": "npm:0.58.2" "@codecademy/gamut-patterns": "npm:0.10.21" "@codecademy/gamut-styles": "npm:17.11.2" @@ -1718,7 +1718,43 @@ __metadata: languageName: unknown linkType: soft -"@codecademy/gamut@npm:68.0.0, @codecademy/gamut@workspace:packages/gamut": +"@codecademy/gamut@npm:68.0.1": + version: 68.0.1 + resolution: "@codecademy/gamut@npm:68.0.1" + dependencies: + "@codecademy/gamut-icons": "npm:9.55.0" + "@codecademy/gamut-illustrations": "npm:0.58.2" + "@codecademy/gamut-patterns": "npm:0.10.21" + "@codecademy/gamut-styles": "npm:17.11.2" + "@codecademy/variance": "npm:0.25.2" + "@types/marked": "npm:^4.0.8" + "@vidstack/react": "npm:^1.12.12" + classnames: "npm:^2.2.5" + framer-motion: "npm:^11.18.0" + html-to-react: "npm:^1.6.0" + invariant: "npm:^2.2.4" + lodash: "npm:^4.17.23" + marked: "npm:^4.3.0" + polished: "npm:^4.1.2" + react-aria-components: "npm:1.8.0" + react-aria-tabpanel: "npm:^4.4.0" + react-focus-on: "npm:^3.10.0" + react-hook-form: "npm:^7.65.0" + react-player: "npm:^2.16.0" + react-select: "npm:^5.2.2" + react-truncate-markup: "npm:^5.1.2" + react-use: "npm:^15.3.8" + sanitize-markdown: "npm:^2.6.7" + peerDependencies: + "@emotion/react": ^11.4.0 + "@emotion/styled": ^11.3.0 + react: ^17.0.2 || ^18.2.0 + react-dom: ^17.0.2 || ^18.2.0 + checksum: 10c0/5639b3edbaa0efe55ae4d1ee6a2b7e89b98a5181b69ff39260604429e9feb74ef90b8582e3de7c69ff7ff43eaa7c4b25730ad55d8422883e9bd47fa52c8d74b9 + languageName: node + linkType: hard + +"@codecademy/gamut@workspace:packages/gamut": version: 0.0.0-use.local resolution: "@codecademy/gamut@workspace:packages/gamut" dependencies: From dd765c9004f820d29f0671fa5af25543078dcec5 Mon Sep 17 00:00:00 2001 From: Kenny Lin Date: Mon, 9 Feb 2026 10:57:39 -0500 Subject: [PATCH 08/15] branch fixes and more test coverage --- .../components/Scales/ColorScale.tsx | 2 +- .../styleguide/src/lib/Foundations/Layout.mdx | 2 +- .../styleguide/src/lib/Meta/ESLint rules.mdx | 2 +- .../Component guidelines/Alerts.mdx | 2 +- .../integration/__tests__/variance.test.ts | 95 +++++++++++++++++++ .../src/utils/__tests__/propNames.test.ts | 25 +++++ 6 files changed, 124 insertions(+), 4 deletions(-) diff --git a/packages/styleguide/.storybook/components/Scales/ColorScale.tsx b/packages/styleguide/.storybook/components/Scales/ColorScale.tsx index ce56ca46d46..2737bdbea98 100644 --- a/packages/styleguide/.storybook/components/Scales/ColorScale.tsx +++ b/packages/styleguide/.storybook/components/Scales/ColorScale.tsx @@ -1,7 +1,7 @@ import * as React from 'react'; import { themed } from '@codecademy/gamut-styles'; import styled from '@emotion/styled'; -import { Box, GridBox } from '@codecademy/gamut/src'; +import { Box, GridBox } from '@codecademy/gamut'; const HexCode = styled.span` font-family: ${themed('fontFamily.monospace')}; diff --git a/packages/styleguide/src/lib/Foundations/Layout.mdx b/packages/styleguide/src/lib/Foundations/Layout.mdx index bb4460d2589..96739a065ad 100644 --- a/packages/styleguide/src/lib/Foundations/Layout.mdx +++ b/packages/styleguide/src/lib/Foundations/Layout.mdx @@ -1,4 +1,4 @@ -import { Box } from '@codecademy/gamut/src'; +import { Box } from '@codecademy/gamut'; import { Meta } from '@storybook/blocks'; import { ComponentHeader, LinkTo, TokenTable } from '~styleguide/blocks'; diff --git a/packages/styleguide/src/lib/Meta/ESLint rules.mdx b/packages/styleguide/src/lib/Meta/ESLint rules.mdx index c459e5960bb..d6b875ac2c5 100644 --- a/packages/styleguide/src/lib/Meta/ESLint rules.mdx +++ b/packages/styleguide/src/lib/Meta/ESLint rules.mdx @@ -155,7 +155,7 @@ Ensures Gamut import statements use proper module paths. This rule enforces thre import { FillButton } from '@codecademy/gamut'; // Don't append /src -import { FillButton } from '@codecademy/gamut/src'; +import { FillButton } from '@codecademy/gamut'; // Don't append /dist import { theme } from '@codecademy/gamut-styles/dist'; diff --git a/packages/styleguide/src/lib/UX Writing/Component guidelines/Alerts.mdx b/packages/styleguide/src/lib/UX Writing/Component guidelines/Alerts.mdx index 4d265f1b7d0..ffd8dc0f494 100644 --- a/packages/styleguide/src/lib/UX Writing/Component guidelines/Alerts.mdx +++ b/packages/styleguide/src/lib/UX Writing/Component guidelines/Alerts.mdx @@ -1,4 +1,4 @@ -import { Alert, Text } from '@codecademy/gamut/src'; +import { Alert, Text } from '@codecademy/gamut'; import { Meta } from '@storybook/blocks'; import { AboutHeader, LinkTo } from '~styleguide/blocks'; diff --git a/packages/variance/integration/__tests__/variance.test.ts b/packages/variance/integration/__tests__/variance.test.ts index e7cfbed6cb7..176ba59354a 100644 --- a/packages/variance/integration/__tests__/variance.test.ts +++ b/packages/variance/integration/__tests__/variance.test.ts @@ -545,6 +545,101 @@ describe('variants', () => { }); }); +describe('logical properties', () => { + const getPropertyMode = (useLogicalProperties: boolean) => + useLogicalProperties ? 'logical' : 'physical'; + + it('uses logical properties when useLogicalProperties is true (default)', () => { + const parser = variance.create({ + marginLeft: { + property: { + physical: 'marginLeft', + logical: 'marginInlineStart', + }, + resolveProperty: getPropertyMode, + }, + }); + + const result = parser({ + marginLeft: '10px', + theme: { ...theme, useLogicalProperties: true }, + }); + + expect(result).toEqual({ marginInlineStart: '10px' }); + }); + + it('uses physical properties when useLogicalProperties is false', () => { + const parser = variance.create({ + marginLeft: { + property: { + physical: 'marginLeft', + logical: 'marginInlineStart', + }, + resolveProperty: getPropertyMode, + }, + }); + + const result = parser({ + marginLeft: '10px', + theme: { ...theme, useLogicalProperties: false }, + }); + + expect(result).toEqual({ marginLeft: '10px' }); + }); + + it('defaults to logical properties when useLogicalProperties is not set', () => { + const parser = variance.create({ + width: { + property: { + physical: 'width', + logical: 'inlineSize', + }, + resolveProperty: getPropertyMode, + }, + }); + + const result = parser({ + width: '100px', + theme, + }); + + expect(result).toEqual({ inlineSize: '100px' }); + }); + + it('handles directional properties array with resolveProperty', () => { + const parser = variance.create({ + mx: { + property: 'margin', + properties: { + physical: ['marginLeft', 'marginRight'], + logical: ['marginInlineStart', 'marginInlineEnd'], + }, + resolveProperty: getPropertyMode, + }, + }); + + const logicalResult = parser({ + mx: '20px', + theme: { ...theme, useLogicalProperties: true }, + }); + + expect(logicalResult).toEqual({ + marginInlineStart: '20px', + marginInlineEnd: '20px', + }); + + const physicalResult = parser({ + mx: '20px', + theme: { ...theme, useLogicalProperties: false }, + }); + + expect(physicalResult).toEqual({ + marginLeft: '20px', + marginRight: '20px', + }); + }); +}); + describe('states', () => { const marginTransform = jest.fn(); diff --git a/packages/variance/src/utils/__tests__/propNames.test.ts b/packages/variance/src/utils/__tests__/propNames.test.ts index 32d64884036..7044593b931 100644 --- a/packages/variance/src/utils/__tests__/propNames.test.ts +++ b/packages/variance/src/utils/__tests__/propNames.test.ts @@ -81,6 +81,31 @@ describe('orderPropNames', () => { expect(result).toEqual(['margin', 'marginX', 'marginY', 'marginLeft']); }); + + it('handles DirectionalProperties objects with physical/logical arrays', () => { + const result = orderPropNames({ + mx: { + property: 'margin', + properties: { + physical: ['marginLeft', 'marginRight'], + logical: ['marginInlineStart', 'marginInlineEnd'], + }, + }, + my: { + property: 'margin', + properties: { + physical: ['marginTop', 'marginBottom'], + logical: ['marginBlockStart', 'marginBlockEnd'], + }, + }, + marginLeft: { + property: 'marginLeft', + }, + }); + + expect(result).toEqual(['mx', 'my', 'marginLeft']); + }); + it('orders all props', () => { const result = orderPropNames(testConfig); From b56b873c36668f0eadcd207382426866039284e1 Mon Sep 17 00:00:00 2001 From: Kenny Lin Date: Mon, 9 Feb 2026 11:47:32 -0500 Subject: [PATCH 09/15] restore changelog --- packages/gamut/CHANGELOG.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/packages/gamut/CHANGELOG.md b/packages/gamut/CHANGELOG.md index c9284863f7c..ee0990f24f0 100644 --- a/packages/gamut/CHANGELOG.md +++ b/packages/gamut/CHANGELOG.md @@ -3,6 +3,10 @@ All notable changes to this project will be documented in this file. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. +### [68.0.1](https://github.com/Codecademy/gamut/compare/@codecademy/gamut@68.0.0...@codecademy/gamut@68.0.1) (2026-02-04) + +**Note:** Version bump only for package @codecademy/gamut + ## [68.0.0](https://github.com/Codecademy/gamut/compare/@codecademy/gamut@67.6.5...@codecademy/gamut@68.0.0) (2026-01-29) ### ⚠ BREAKING CHANGES From f3d0b83257f99ace37c5c34eff399dce7e5025c1 Mon Sep 17 00:00:00 2001 From: Kenny Lin Date: Mon, 9 Feb 2026 16:41:12 -0500 Subject: [PATCH 10/15] fix imports --- packages/styleguide/src/lib/Meta/ESLint rules.mdx | 2 +- .../styleguide/src/lib/Typography/Text/Text.stories.tsx | 8 ++++---- packages/styleguide/src/lib/Typography/Text/tables.tsx | 4 ++-- 3 files changed, 7 insertions(+), 7 deletions(-) diff --git a/packages/styleguide/src/lib/Meta/ESLint rules.mdx b/packages/styleguide/src/lib/Meta/ESLint rules.mdx index d6b875ac2c5..c459e5960bb 100644 --- a/packages/styleguide/src/lib/Meta/ESLint rules.mdx +++ b/packages/styleguide/src/lib/Meta/ESLint rules.mdx @@ -155,7 +155,7 @@ Ensures Gamut import statements use proper module paths. This rule enforces thre import { FillButton } from '@codecademy/gamut'; // Don't append /src -import { FillButton } from '@codecademy/gamut'; +import { FillButton } from '@codecademy/gamut/src'; // Don't append /dist import { theme } from '@codecademy/gamut-styles/dist'; diff --git a/packages/styleguide/src/lib/Typography/Text/Text.stories.tsx b/packages/styleguide/src/lib/Typography/Text/Text.stories.tsx index 5dd0589bd64..0be504ace02 100644 --- a/packages/styleguide/src/lib/Typography/Text/Text.stories.tsx +++ b/packages/styleguide/src/lib/Typography/Text/Text.stories.tsx @@ -1,12 +1,12 @@ import { Box, Column, LayoutGrid, Text } from '@codecademy/gamut'; -// eslint-disable-next-line gamut/import-paths +import type { Meta, StoryObj } from '@storybook/react'; +import { Fragment } from 'react'; + import { typographyElementVariants, typographyStyleVariants, typographyUtilities, -} from '@codecademy/gamut/src/Typography/variants'; -import type { Meta, StoryObj } from '@storybook/react'; -import { Fragment } from 'react'; +} from '../../../../../gamut/src/Typography/variants'; const meta: Meta = { component: Text, diff --git a/packages/styleguide/src/lib/Typography/Text/tables.tsx b/packages/styleguide/src/lib/Typography/Text/tables.tsx index 18470aa04b2..a757d515ace 100644 --- a/packages/styleguide/src/lib/Typography/Text/tables.tsx +++ b/packages/styleguide/src/lib/Typography/Text/tables.tsx @@ -1,8 +1,8 @@ import { Column, LayoutGrid, Text } from '@codecademy/gamut'; -// eslint-disable-next-line gamut/import-paths -import { typographyElementVariants } from '@codecademy/gamut/src/Typography/variants'; import { Fragment } from 'react'; +import { typographyElementVariants } from '../../../../../gamut/src/Typography/variants'; + export const Elements: React.FC = () => ( {Object.keys(typographyElementVariants).map((tag) => ( From 29d536e8bb9c3c81c4a5323c86375954c9ff5437 Mon Sep 17 00:00:00 2001 From: Kenny Lin Date: Tue, 10 Feb 2026 16:50:53 -0500 Subject: [PATCH 11/15] revert to only testing logical props --- .../ConnectedNestedCheckboxes.test.tsx | 42 ++++++---------- .../__tests__/utils.test.tsx | 48 ++++++++----------- .../GridFormNestedCheckboxInput.test.tsx | 46 ++++++++---------- .../gamut/src/List/__tests__/List.test.tsx | 13 +---- 4 files changed, 56 insertions(+), 93 deletions(-) diff --git a/packages/gamut/src/ConnectedForm/ConnectedInputs/ConnectedNestedCheckboxes/__tests__/ConnectedNestedCheckboxes.test.tsx b/packages/gamut/src/ConnectedForm/ConnectedInputs/ConnectedNestedCheckboxes/__tests__/ConnectedNestedCheckboxes.test.tsx index ec472292184..117f2743534 100644 --- a/packages/gamut/src/ConnectedForm/ConnectedInputs/ConnectedNestedCheckboxes/__tests__/ConnectedNestedCheckboxes.test.tsx +++ b/packages/gamut/src/ConnectedForm/ConnectedInputs/ConnectedNestedCheckboxes/__tests__/ConnectedNestedCheckboxes.test.tsx @@ -90,33 +90,21 @@ describe('ConnectedNestedCheckboxes', () => { view.getByLabelText('Fastify'); }); - it.each([ - { useLogicalProperties: true, marginLeft: 'marginInlineStart' }, - { useLogicalProperties: false, marginLeft: 'marginLeft' }, - ])( - 'should render checkboxes with proper indentation levels (useLogicalProperties: $useLogicalProperties)', - ({ useLogicalProperties, marginLeft }) => { - render( - - - - ); - - const frontendCheckbox = screen - .getByLabelText('Frontend Technologies') - .closest('li'); - const reactCheckbox = screen.getByLabelText('React').closest('li'); - const nodeCheckbox = screen.getByLabelText('Node.js').closest('li'); - const expressCheckbox = screen - .getByLabelText('Express.js') - .closest('li'); - - expect(frontendCheckbox).toHaveStyle({ [marginLeft]: '0' }); - expect(reactCheckbox).toHaveStyle({ [marginLeft]: '1.5rem' }); - expect(nodeCheckbox).toHaveStyle({ [marginLeft]: '1.5rem' }); - expect(expressCheckbox).toHaveStyle({ [marginLeft]: '3rem' }); - } - ); + it('should render checkboxes with proper indentation levels ', () => { + render(); + + const frontendCheckbox = screen + .getByLabelText('Frontend Technologies') + .closest('li'); + const reactCheckbox = screen.getByLabelText('React').closest('li'); + const nodeCheckbox = screen.getByLabelText('Node.js').closest('li'); + const expressCheckbox = screen.getByLabelText('Express.js').closest('li'); + + expect(frontendCheckbox).toHaveStyle({ marginInlineStart: 0 }); + expect(reactCheckbox).toHaveStyle({ marginInlineStart: '24px' }); + expect(nodeCheckbox).toHaveStyle({ marginInlineStart: '24px' }); + expect(expressCheckbox).toHaveStyle({ marginInlineStart: '48px' }); + }); it('should render with unique IDs for each checkbox', () => { const { view } = renderView(); diff --git a/packages/gamut/src/ConnectedForm/ConnectedInputs/ConnectedNestedCheckboxes/__tests__/utils.test.tsx b/packages/gamut/src/ConnectedForm/ConnectedInputs/ConnectedNestedCheckboxes/__tests__/utils.test.tsx index a616094c737..276bcb0133d 100644 --- a/packages/gamut/src/ConnectedForm/ConnectedInputs/ConnectedNestedCheckboxes/__tests__/utils.test.tsx +++ b/packages/gamut/src/ConnectedForm/ConnectedInputs/ConnectedNestedCheckboxes/__tests__/utils.test.tsx @@ -545,36 +545,28 @@ describe('ConnectedNestedCheckboxes utils', () => { expect(checkbox).toHaveAttribute('aria-checked', 'false'); }); - it.each([ - { useLogicalProperties: true, marginLeft: 'marginInlineStart' }, - { useLogicalProperties: false, marginLeft: 'marginLeft' }, - ])( - 'should apply correct margin based on level (useLogicalProperties: $useLogicalProperties)', - ({ useLogicalProperties, marginLeft }) => { - const state = { checked: false, indeterminate: false }; - - const result = renderCheckbox({ - option: { ...mockOption, level: 2 }, - state, - name: 'test', - isRequired: false, - isDisabled: false, - onBlur: mockOnBlur, - onChange: mockOnChange, - ref: mockRef, - flatOptions: [{ ...mockOption, level: 2 }], - }); + it('should apply correct margin based on level', () => { + const state = { checked: false, indeterminate: false }; + + const result = renderCheckbox({ + option: { ...mockOption, level: 2 }, + state, + name: 'test', + isRequired: false, + isDisabled: false, + onBlur: mockOnBlur, + onChange: mockOnChange, + ref: mockRef, + flatOptions: [{ ...mockOption, level: 2 }], + }); - const { container } = render( - - {result} - - ); - const listItem = container.querySelector('li'); + const { container } = render( + {result} + ); + const listItem = container.querySelector('li'); - expect(listItem).toHaveStyle({ [marginLeft]: '3rem' }); // 24px * 2 = 48px = 3rem - } - ); + expect(listItem).toHaveStyle({ marginInlineStart: '48px' }); // 24px * 2 = 48px + }); it('should handle disabled state', () => { const state = { checked: false, indeterminate: false }; diff --git a/packages/gamut/src/GridForm/GridFormInputGroup/GridFormNestedCheckboxInput/__tests__/GridFormNestedCheckboxInput.test.tsx b/packages/gamut/src/GridForm/GridFormInputGroup/GridFormNestedCheckboxInput/__tests__/GridFormNestedCheckboxInput.test.tsx index 93ba0585de9..80bbca2e02b 100644 --- a/packages/gamut/src/GridForm/GridFormInputGroup/GridFormNestedCheckboxInput/__tests__/GridFormNestedCheckboxInput.test.tsx +++ b/packages/gamut/src/GridForm/GridFormInputGroup/GridFormNestedCheckboxInput/__tests__/GridFormNestedCheckboxInput.test.tsx @@ -89,33 +89,25 @@ describe('GridFormNestedCheckboxInput', () => { view.getByLabelText('Fastify'); }); - it.each([ - { useLogicalProperties: true, marginLeft: 'marginInlineStart' }, - { useLogicalProperties: false, marginLeft: 'marginLeft' }, - ])( - 'should render checkboxes with proper indentation levels (useLogicalProperties: $useLogicalProperties)', - ({ useLogicalProperties, marginLeft }) => { - render( - - - - ); - - const frontendCheckbox = screen - .getByLabelText('Frontend Technologies') - .closest('li'); - const reactCheckbox = screen.getByLabelText('React').closest('li'); - const nodeCheckbox = screen.getByLabelText('Node.js').closest('li'); - const expressCheckbox = screen - .getByLabelText('Express.js') - .closest('li'); - - expect(frontendCheckbox).toHaveStyle({ [marginLeft]: '0' }); - expect(reactCheckbox).toHaveStyle({ [marginLeft]: '1.5rem' }); - expect(nodeCheckbox).toHaveStyle({ [marginLeft]: '1.5rem' }); - expect(expressCheckbox).toHaveStyle({ [marginLeft]: '3rem' }); - } - ); + it('should render checkboxes with proper indentation levels', () => { + render( + + + + ); + + const frontendCheckbox = screen + .getByLabelText('Frontend Technologies') + .closest('li'); + const reactCheckbox = screen.getByLabelText('React').closest('li'); + const nodeCheckbox = screen.getByLabelText('Node.js').closest('li'); + const expressCheckbox = screen.getByLabelText('Express.js').closest('li'); + + expect(frontendCheckbox).toHaveStyle({ marginInlineStart: 0 }); + expect(reactCheckbox).toHaveStyle({ marginInlineStart: '24px' }); + expect(nodeCheckbox).toHaveStyle({ marginInlineStart: '24px' }); + expect(expressCheckbox).toHaveStyle({ marginInlineStart: '48px' }); + }); it('should render with unique IDs for each checkbox', () => { const { view } = renderView(); diff --git a/packages/gamut/src/List/__tests__/List.test.tsx b/packages/gamut/src/List/__tests__/List.test.tsx index a25cb5ed4a2..d1d419eb060 100644 --- a/packages/gamut/src/List/__tests__/List.test.tsx +++ b/packages/gamut/src/List/__tests__/List.test.tsx @@ -48,22 +48,13 @@ describe('List', () => { expect(rowEl).toHaveStyle({ columnGap: theme.spacing[40] }); }); - // Note: Only testing one mode here since variant() caches styles after first render. it('configures columns with the correct variants', () => { - const useLogicalProperties = true; - const paddingLeft = useLogicalProperties - ? 'paddingInlineStart' - : 'paddingLeft'; - const paddingRight = useLogicalProperties - ? 'paddingInlineEnd' - : 'paddingRight'; - const { view } = renderView(); const colEl = view.getByText('Hello'); expect(colEl).not.toHaveStyle({ py: 16 }); - expect(colEl).toHaveStyle({ [paddingLeft]: theme.spacing[8] }); - expect(colEl).toHaveStyle({ [paddingRight]: theme.spacing[8] }); + expect(colEl).toHaveStyle({ paddingInlineStart: theme.spacing[8] }); + expect(colEl).toHaveStyle({ paddingInlineEnd: theme.spacing[8] }); expect(colEl).not.toHaveStyle({ position: 'sticky' }); }); From 534cfdc465610a3a9cce1f426f95a44e133c07c4 Mon Sep 17 00:00:00 2001 From: Kenny Lin Date: Tue, 10 Feb 2026 16:52:51 -0500 Subject: [PATCH 12/15] linting --- .../__tests__/ConnectedNestedCheckboxes.test.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/gamut/src/ConnectedForm/ConnectedInputs/ConnectedNestedCheckboxes/__tests__/ConnectedNestedCheckboxes.test.tsx b/packages/gamut/src/ConnectedForm/ConnectedInputs/ConnectedNestedCheckboxes/__tests__/ConnectedNestedCheckboxes.test.tsx index 117f2743534..00478ea725e 100644 --- a/packages/gamut/src/ConnectedForm/ConnectedInputs/ConnectedNestedCheckboxes/__tests__/ConnectedNestedCheckboxes.test.tsx +++ b/packages/gamut/src/ConnectedForm/ConnectedInputs/ConnectedNestedCheckboxes/__tests__/ConnectedNestedCheckboxes.test.tsx @@ -1,4 +1,4 @@ -import { MockGamutProvider, setupRtl } from '@codecademy/gamut-tests'; +import { setupRtl } from '@codecademy/gamut-tests'; import { fireEvent } from '@testing-library/dom'; import { act, render, screen } from '@testing-library/react'; import userEvent from '@testing-library/user-event'; From c5fbed08b6ae6224a9412d9e429df3f2fd1b2ae3 Mon Sep 17 00:00:00 2001 From: Kenny Lin Date: Wed, 11 Feb 2026 11:05:53 -0500 Subject: [PATCH 13/15] revert back to using renderView --- .../ConnectedNestedCheckboxes.test.tsx | 20 ++++++++-------- .../__tests__/utils.test.tsx | 6 ++--- .../GridFormNestedCheckboxInput.test.tsx | 24 ++++++++----------- 3 files changed, 22 insertions(+), 28 deletions(-) diff --git a/packages/gamut/src/ConnectedForm/ConnectedInputs/ConnectedNestedCheckboxes/__tests__/ConnectedNestedCheckboxes.test.tsx b/packages/gamut/src/ConnectedForm/ConnectedInputs/ConnectedNestedCheckboxes/__tests__/ConnectedNestedCheckboxes.test.tsx index 00478ea725e..0bff6d9164e 100644 --- a/packages/gamut/src/ConnectedForm/ConnectedInputs/ConnectedNestedCheckboxes/__tests__/ConnectedNestedCheckboxes.test.tsx +++ b/packages/gamut/src/ConnectedForm/ConnectedInputs/ConnectedNestedCheckboxes/__tests__/ConnectedNestedCheckboxes.test.tsx @@ -1,6 +1,6 @@ import { setupRtl } from '@codecademy/gamut-tests'; import { fireEvent } from '@testing-library/dom'; -import { act, render, screen } from '@testing-library/react'; +import { act } from '@testing-library/react'; import userEvent from '@testing-library/user-event'; import { ConnectedForm, ConnectedFormGroup, SubmitButton } from '../../..'; @@ -90,20 +90,20 @@ describe('ConnectedNestedCheckboxes', () => { view.getByLabelText('Fastify'); }); - it('should render checkboxes with proper indentation levels ', () => { - render(); + it('should render checkboxes with proper indentation levels', () => { + const { view } = renderView(); - const frontendCheckbox = screen + const frontendCheckbox = view .getByLabelText('Frontend Technologies') .closest('li'); - const reactCheckbox = screen.getByLabelText('React').closest('li'); - const nodeCheckbox = screen.getByLabelText('Node.js').closest('li'); - const expressCheckbox = screen.getByLabelText('Express.js').closest('li'); + const reactCheckbox = view.getByLabelText('React').closest('li'); + const nodeCheckbox = view.getByLabelText('Node.js').closest('li'); + const expressCheckbox = view.getByLabelText('Express.js').closest('li'); expect(frontendCheckbox).toHaveStyle({ marginInlineStart: 0 }); - expect(reactCheckbox).toHaveStyle({ marginInlineStart: '24px' }); - expect(nodeCheckbox).toHaveStyle({ marginInlineStart: '24px' }); - expect(expressCheckbox).toHaveStyle({ marginInlineStart: '48px' }); + expect(reactCheckbox).toHaveStyle({ marginInlineStart: '1.5rem' }); + expect(nodeCheckbox).toHaveStyle({ marginInlineStart: '1.5rem' }); + expect(expressCheckbox).toHaveStyle({ marginInlineStart: '3rem' }); }); it('should render with unique IDs for each checkbox', () => { diff --git a/packages/gamut/src/ConnectedForm/ConnectedInputs/ConnectedNestedCheckboxes/__tests__/utils.test.tsx b/packages/gamut/src/ConnectedForm/ConnectedInputs/ConnectedNestedCheckboxes/__tests__/utils.test.tsx index 276bcb0133d..1d940a75650 100644 --- a/packages/gamut/src/ConnectedForm/ConnectedInputs/ConnectedNestedCheckboxes/__tests__/utils.test.tsx +++ b/packages/gamut/src/ConnectedForm/ConnectedInputs/ConnectedNestedCheckboxes/__tests__/utils.test.tsx @@ -560,12 +560,10 @@ describe('ConnectedNestedCheckboxes utils', () => { flatOptions: [{ ...mockOption, level: 2 }], }); - const { container } = render( - {result} - ); + const { container } = render(result); const listItem = container.querySelector('li'); - expect(listItem).toHaveStyle({ marginInlineStart: '48px' }); // 24px * 2 = 48px + expect(listItem).toHaveStyle({ marginInlineStart: '48px' }); // 2 * 24px }); it('should handle disabled state', () => { diff --git a/packages/gamut/src/GridForm/GridFormInputGroup/GridFormNestedCheckboxInput/__tests__/GridFormNestedCheckboxInput.test.tsx b/packages/gamut/src/GridForm/GridFormInputGroup/GridFormNestedCheckboxInput/__tests__/GridFormNestedCheckboxInput.test.tsx index 80bbca2e02b..af7647b8505 100644 --- a/packages/gamut/src/GridForm/GridFormInputGroup/GridFormNestedCheckboxInput/__tests__/GridFormNestedCheckboxInput.test.tsx +++ b/packages/gamut/src/GridForm/GridFormInputGroup/GridFormNestedCheckboxInput/__tests__/GridFormNestedCheckboxInput.test.tsx @@ -1,6 +1,6 @@ -import { MockGamutProvider, setupRtl } from '@codecademy/gamut-tests'; +import { setupRtl } from '@codecademy/gamut-tests'; import { fireEvent } from '@testing-library/dom'; -import { act, render, screen } from '@testing-library/react'; +import { act } from '@testing-library/react'; import userEvent from '@testing-library/user-event'; import { GridForm } from '../../../GridForm'; @@ -90,23 +90,19 @@ describe('GridFormNestedCheckboxInput', () => { }); it('should render checkboxes with proper indentation levels', () => { - render( - - - - ); + const { view } = renderView(); - const frontendCheckbox = screen + const frontendCheckbox = view .getByLabelText('Frontend Technologies') .closest('li'); - const reactCheckbox = screen.getByLabelText('React').closest('li'); - const nodeCheckbox = screen.getByLabelText('Node.js').closest('li'); - const expressCheckbox = screen.getByLabelText('Express.js').closest('li'); + const reactCheckbox = view.getByLabelText('React').closest('li'); + const nodeCheckbox = view.getByLabelText('Node.js').closest('li'); + const expressCheckbox = view.getByLabelText('Express.js').closest('li'); expect(frontendCheckbox).toHaveStyle({ marginInlineStart: 0 }); - expect(reactCheckbox).toHaveStyle({ marginInlineStart: '24px' }); - expect(nodeCheckbox).toHaveStyle({ marginInlineStart: '24px' }); - expect(expressCheckbox).toHaveStyle({ marginInlineStart: '48px' }); + expect(reactCheckbox).toHaveStyle({ marginInlineStart: '1.5rem' }); + expect(nodeCheckbox).toHaveStyle({ marginInlineStart: '1.5rem' }); + expect(expressCheckbox).toHaveStyle({ marginInlineStart: '3rem' }); }); it('should render with unique IDs for each checkbox', () => { From df84731cb6d18096b21c4e431320746ed35bffc4 Mon Sep 17 00:00:00 2001 From: Kenny Lin Date: Wed, 11 Feb 2026 11:13:51 -0500 Subject: [PATCH 14/15] linted --- .../ConnectedNestedCheckboxes/__tests__/utils.test.tsx | 1 - 1 file changed, 1 deletion(-) diff --git a/packages/gamut/src/ConnectedForm/ConnectedInputs/ConnectedNestedCheckboxes/__tests__/utils.test.tsx b/packages/gamut/src/ConnectedForm/ConnectedInputs/ConnectedNestedCheckboxes/__tests__/utils.test.tsx index 1d940a75650..156e5533f38 100644 --- a/packages/gamut/src/ConnectedForm/ConnectedInputs/ConnectedNestedCheckboxes/__tests__/utils.test.tsx +++ b/packages/gamut/src/ConnectedForm/ConnectedInputs/ConnectedNestedCheckboxes/__tests__/utils.test.tsx @@ -1,4 +1,3 @@ -import { MockGamutProvider } from '@codecademy/gamut-tests'; import { render } from '@testing-library/react'; import { From 0fd4406833ce2bf4faf46d286edc0400fbfb2744 Mon Sep 17 00:00:00 2001 From: Kenny Lin Date: Wed, 11 Feb 2026 11:23:27 -0500 Subject: [PATCH 15/15] revert some testing changes --- .../__tests__/utils.test.tsx | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/packages/gamut/src/ConnectedForm/ConnectedInputs/ConnectedNestedCheckboxes/__tests__/utils.test.tsx b/packages/gamut/src/ConnectedForm/ConnectedInputs/ConnectedNestedCheckboxes/__tests__/utils.test.tsx index 156e5533f38..227d2240091 100644 --- a/packages/gamut/src/ConnectedForm/ConnectedInputs/ConnectedNestedCheckboxes/__tests__/utils.test.tsx +++ b/packages/gamut/src/ConnectedForm/ConnectedInputs/ConnectedNestedCheckboxes/__tests__/utils.test.tsx @@ -475,7 +475,7 @@ describe('ConnectedNestedCheckboxes utils', () => { const mockOnBlur = jest.fn(); it('should render a checked checkbox with correct props', () => { - const state = { checked: true, indeterminate: false }; + const state = { checked: true }; const result = renderCheckbox({ option: mockOption, @@ -545,7 +545,7 @@ describe('ConnectedNestedCheckboxes utils', () => { }); it('should apply correct margin based on level', () => { - const state = { checked: false, indeterminate: false }; + const state = { checked: false }; const result = renderCheckbox({ option: { ...mockOption, level: 2 }, @@ -566,7 +566,7 @@ describe('ConnectedNestedCheckboxes utils', () => { }); it('should handle disabled state', () => { - const state = { checked: false, indeterminate: false }; + const state = { checked: false }; const result = renderCheckbox({ option: { ...mockOption, disabled: true }, @@ -587,7 +587,7 @@ describe('ConnectedNestedCheckboxes utils', () => { }); it('should handle error state', () => { - const state = { checked: false, indeterminate: false }; + const state = { checked: false }; const result = renderCheckbox({ option: mockOption, @@ -609,7 +609,7 @@ describe('ConnectedNestedCheckboxes utils', () => { }); it('should use custom aria-label when provided', () => { - const state = { checked: false, indeterminate: false }; + const state = { checked: false }; const optionWithAriaLabel = { ...mockOption, 'aria-label': 'Custom aria label', @@ -634,7 +634,7 @@ describe('ConnectedNestedCheckboxes utils', () => { }); it('should fallback to label text for aria-label when label is string', () => { - const state = { checked: false, indeterminate: false }; + const state = { checked: false }; const result = renderCheckbox({ option: mockOption, @@ -655,7 +655,7 @@ describe('ConnectedNestedCheckboxes utils', () => { }); it('should use default aria-label when label is not string', () => { - const state = { checked: false, indeterminate: false }; + const state = { checked: false }; const optionWithElementLabel = { ...mockOption, label: Element Label, @@ -680,7 +680,7 @@ describe('ConnectedNestedCheckboxes utils', () => { }); it('should generate aria-controls with all nested descendants', () => { - const state = { checked: false, indeterminate: false }; + const state = { checked: false }; const flatOptions = [ { value: 'parent', @@ -737,7 +737,7 @@ describe('ConnectedNestedCheckboxes utils', () => { }); it('should not have aria-controls for leaf nodes', () => { - const state = { checked: false, indeterminate: false }; + const state = { checked: false }; const flatOptions = [ { value: 'leaf',