From 9920b86356aecedbfee480c78f6051c69e15fe33 Mon Sep 17 00:00:00 2001 From: Kenny Lin Date: Fri, 21 Nov 2025 15:47:14 -0500 Subject: [PATCH 01/11] 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/11] 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/11] 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 efd5af854b0bd8c79fd9489932d1d01b84dec882 Mon Sep 17 00:00:00 2001 From: Jake Hiller Date: Wed, 11 Feb 2026 15:56:51 -0500 Subject: [PATCH 04/11] Kl gmt 1510 logical border (#3264) * 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 * feat(Icon): :sparkles: Add Live Learning Icon * chore(release): publish - @codecademy/gamut@68.0.1 - @codecademy/gamut-icons@9.55.0 - @codecademy/gamut-kit@0.6.580 --------- Co-authored-by: Kenny Lin Co-authored-by: Hailey Co-authored-by: codecademydev --- packages/gamut-styles/src/GamutProvider.tsx | 1 + packages/gamut-styles/src/variance/config.ts | 190 +++++++++++++++--- 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 +-- 9 files changed, 392 insertions(+), 78 deletions(-) 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-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/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'], }, ]} /> From 09e0652346be4802cfa2322e2f0e3d87e02d34e3 Mon Sep 17 00:00:00 2001 From: Hailey Date: Wed, 4 Feb 2026 15:10:32 -0500 Subject: [PATCH 05/11] feat(Icon): :sparkles: Add Live Learning Icon --- .../src/svg/regular/live-learning-icon.svg | 11 +++++++++++ 1 file changed, 11 insertions(+) create mode 100644 packages/gamut-icons/src/svg/regular/live-learning-icon.svg 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 From 83ebf1b8c4439e4f0a6df1c1e724e6ff14525cb3 Mon Sep 17 00:00:00 2001 From: codecademydev Date: Wed, 4 Feb 2026 20:12:54 +0000 Subject: [PATCH 06/11] chore(release): publish - @codecademy/gamut@68.0.1 - @codecademy/gamut-icons@9.55.0 - @codecademy/gamut-kit@0.6.580 --- packages/gamut-icons/CHANGELOG.md | 6 ++++++ packages/gamut-icons/package.json | 2 +- packages/gamut-kit/CHANGELOG.md | 4 ++++ packages/gamut-kit/package.json | 6 +++--- packages/gamut/CHANGELOG.md | 4 ++++ packages/gamut/package.json | 4 ++-- yarn.lock | 10 +++++----- 7 files changed, 25 insertions(+), 11 deletions(-) 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-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/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 diff --git a/packages/gamut/package.json b/packages/gamut/package.json index d8d6ed45dbf..dda7b640ec6 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.0", + "version": "68.0.1", "author": "Codecademy Engineering ", "dependencies": { - "@codecademy/gamut-icons": "9.54.2", + "@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 62399d5d1f5..942a41c4529 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1616,7 +1616,7 @@ __metadata: languageName: node linkType: hard -"@codecademy/gamut-icons@npm:9.54.2, @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: @@ -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.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" @@ -1703,11 +1703,11 @@ __metadata: languageName: unknown linkType: soft -"@codecademy/gamut@npm:68.0.0, @codecademy/gamut@workspace:packages/gamut": +"@codecademy/gamut@npm:68.0.1, @codecademy/gamut@workspace:packages/gamut": version: 0.0.0-use.local resolution: "@codecademy/gamut@workspace:packages/gamut" dependencies: - "@codecademy/gamut-icons": "npm:9.54.2" + "@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" From 1683d7cea0059311382e59a22bf3127b4101ef8c Mon Sep 17 00:00:00 2001 From: Kenny Lin Date: Wed, 11 Feb 2026 16:16:08 -0500 Subject: [PATCH 07/11] resolve merge conflict --- .cursor/rules/figma-rules.mdc | 160 +++++++ .eslintignore | 2 + .eslintrc.js | 2 + .github/copilot-instructions.md | 0 .github/instructions/figma.instructions.md | 189 +++++++++ .prettierignore | 1 + figma.config.json | 7 + packages/code-connect/Atoms/Anchor.figma.tsx | 42 ++ packages/code-connect/Atoms/Badge.figma.tsx | 36 ++ .../Atoms/Buttons/CTAButton.figma.tsx | 31 ++ .../Atoms/Buttons/FillButton.figma.tsx | 41 ++ .../Atoms/Buttons/IconButton.figma.tsx | 30 ++ .../Atoms/Buttons/StrokeButton.figma.tsx | 41 ++ .../Atoms/Buttons/TextButton.figma.tsx | 36 ++ packages/code-connect/Atoms/Card.figma.tsx | 39 ++ packages/code-connect/Atoms/Drawer.figma.tsx | 28 ++ .../Atoms/FeatureShimmer.figma.tsx | 22 + .../code-connect/Atoms/FormGroup.figma.tsx | 60 +++ .../Atoms/FormInputs/Checkbox.figma.tsx | 35 ++ .../Atoms/FormInputs/Input.figma.tsx | 124 ++++++ .../Atoms/FormInputs/Radio.figma.tsx | 64 +++ .../Atoms/FormInputs/Select.figma.tsx | 51 +++ .../Atoms/FormInputs/SelectDropdown.figma.tsx | 39 ++ .../Atoms/FormInputs/TextArea.figma.tsx | 55 +++ .../Atoms/Loaders/Shimmer.figma.tsx | 20 + .../Atoms/Loaders/Spinner.figma.tsx | 20 + .../code-connect/Atoms/ProgressBar.figma.tsx | 47 +++ .../Atoms/RadialProgress.figma.tsx | 60 +++ .../Atoms/SkipToContent.figma.tsx | 22 + .../Atoms/Tag/TagNavigation.figma.tsx | 31 ++ .../Atoms/Tag/TagReadOnly.figma.tsx | 31 ++ .../Atoms/Tag/TagSelection.figma.tsx | 31 ++ .../Atoms/Tag/TagSuggestion.figma.tsx | 31 ++ packages/code-connect/Atoms/Toggle.figma.tsx | 28 ++ .../code-connect/Molecules/Alert.figma.tsx | 52 +++ .../Molecules/Breadcrumbs.figma.tsx | 163 +++++++ .../Molecules/Coachmark.figma.tsx | 54 +++ .../Molecules/Disclosure.figma.tsx | 80 ++++ .../code-connect/Molecules/Flyout.figma.tsx | 41 ++ .../Molecules/Menu/Menu.figma.tsx | 30 ++ .../Molecules/Menu/MenuItemFixed.figma.tsx | 30 ++ .../Molecules/Menu/MenuItemPopover.figma.tsx | 30 ++ .../Molecules/Menu/MenuSeparator.figma.tsx | 19 + .../Molecules/Models/Dialog.figma.tsx | 48 +++ .../Molecules/Models/Modal.figma.tsx | 48 +++ .../Molecules/Pagination.figma.tsx | 29 ++ .../code-connect/Molecules/Popover.figma.tsx | 48 +++ .../code-connect/Molecules/Tabs/Tab.figma.tsx | 42 ++ .../Molecules/Tabs/Tabs.figma.tsx | 26 ++ .../Molecules/Tips/InfoTip.figma.tsx | 39 ++ .../Molecules/Tips/PreviewTip.figma.tsx | 42 ++ .../Molecules/Tips/ToolTip.figma.tsx | 31 ++ .../Molecules/Toasts/Toast.figma.tsx | 39 ++ .../Molecules/Toasts/Toaster.figma.tsx | 396 ++++++++++++++++++ .../code-connect/Molecules/Video.figma.tsx | 22 + .../DataTable/DataTableBasic.figma.tsx | 57 +++ .../code-connect/Organisms/GridForm.figma.tsx | 138 ++++++ .../Organisms/List/List.figma.tsx | 32 ++ .../Organisms/List/ListRow.figma.tsx | 65 +++ packages/styleguide/.storybook/preview.ts | 6 +- .../styleguide/src/lib/Atoms/Badge/Badge.mdx | 2 +- packages/styleguide/src/lib/Meta/About.mdx | 2 + .../styleguide/src/lib/Meta/MCP/About.mdx | 29 ++ .../src/lib/Meta/MCP/Code Connect.mdx | 38 ++ .../styleguide/src/lib/Meta/MCP/Figma MCP.mdx | 89 ++++ .../src/lib/Molecules/Alert/Alert.mdx | 2 +- .../src/static/mcp/component_playground.png | Bin 0 -> 59747 bytes .../mcp/component_with_code_connect.png | Bin 0 -> 55279 bytes tsconfig.base.json | 2 +- 69 files changed, 3223 insertions(+), 4 deletions(-) create mode 100644 .cursor/rules/figma-rules.mdc create mode 100644 .github/copilot-instructions.md create mode 100644 .github/instructions/figma.instructions.md create mode 100644 figma.config.json create mode 100644 packages/code-connect/Atoms/Anchor.figma.tsx create mode 100644 packages/code-connect/Atoms/Badge.figma.tsx create mode 100644 packages/code-connect/Atoms/Buttons/CTAButton.figma.tsx create mode 100644 packages/code-connect/Atoms/Buttons/FillButton.figma.tsx create mode 100644 packages/code-connect/Atoms/Buttons/IconButton.figma.tsx create mode 100644 packages/code-connect/Atoms/Buttons/StrokeButton.figma.tsx create mode 100644 packages/code-connect/Atoms/Buttons/TextButton.figma.tsx create mode 100644 packages/code-connect/Atoms/Card.figma.tsx create mode 100644 packages/code-connect/Atoms/Drawer.figma.tsx create mode 100644 packages/code-connect/Atoms/FeatureShimmer.figma.tsx create mode 100644 packages/code-connect/Atoms/FormGroup.figma.tsx create mode 100644 packages/code-connect/Atoms/FormInputs/Checkbox.figma.tsx create mode 100644 packages/code-connect/Atoms/FormInputs/Input.figma.tsx create mode 100644 packages/code-connect/Atoms/FormInputs/Radio.figma.tsx create mode 100644 packages/code-connect/Atoms/FormInputs/Select.figma.tsx create mode 100644 packages/code-connect/Atoms/FormInputs/SelectDropdown.figma.tsx create mode 100644 packages/code-connect/Atoms/FormInputs/TextArea.figma.tsx create mode 100644 packages/code-connect/Atoms/Loaders/Shimmer.figma.tsx create mode 100644 packages/code-connect/Atoms/Loaders/Spinner.figma.tsx create mode 100644 packages/code-connect/Atoms/ProgressBar.figma.tsx create mode 100644 packages/code-connect/Atoms/RadialProgress.figma.tsx create mode 100644 packages/code-connect/Atoms/SkipToContent.figma.tsx create mode 100644 packages/code-connect/Atoms/Tag/TagNavigation.figma.tsx create mode 100644 packages/code-connect/Atoms/Tag/TagReadOnly.figma.tsx create mode 100644 packages/code-connect/Atoms/Tag/TagSelection.figma.tsx create mode 100644 packages/code-connect/Atoms/Tag/TagSuggestion.figma.tsx create mode 100644 packages/code-connect/Atoms/Toggle.figma.tsx create mode 100644 packages/code-connect/Molecules/Alert.figma.tsx create mode 100644 packages/code-connect/Molecules/Breadcrumbs.figma.tsx create mode 100644 packages/code-connect/Molecules/Coachmark.figma.tsx create mode 100644 packages/code-connect/Molecules/Disclosure.figma.tsx create mode 100644 packages/code-connect/Molecules/Flyout.figma.tsx create mode 100644 packages/code-connect/Molecules/Menu/Menu.figma.tsx create mode 100644 packages/code-connect/Molecules/Menu/MenuItemFixed.figma.tsx create mode 100644 packages/code-connect/Molecules/Menu/MenuItemPopover.figma.tsx create mode 100644 packages/code-connect/Molecules/Menu/MenuSeparator.figma.tsx create mode 100644 packages/code-connect/Molecules/Models/Dialog.figma.tsx create mode 100644 packages/code-connect/Molecules/Models/Modal.figma.tsx create mode 100644 packages/code-connect/Molecules/Pagination.figma.tsx create mode 100644 packages/code-connect/Molecules/Popover.figma.tsx create mode 100644 packages/code-connect/Molecules/Tabs/Tab.figma.tsx create mode 100644 packages/code-connect/Molecules/Tabs/Tabs.figma.tsx create mode 100644 packages/code-connect/Molecules/Tips/InfoTip.figma.tsx create mode 100644 packages/code-connect/Molecules/Tips/PreviewTip.figma.tsx create mode 100644 packages/code-connect/Molecules/Tips/ToolTip.figma.tsx create mode 100644 packages/code-connect/Molecules/Toasts/Toast.figma.tsx create mode 100644 packages/code-connect/Molecules/Toasts/Toaster.figma.tsx create mode 100644 packages/code-connect/Molecules/Video.figma.tsx create mode 100644 packages/code-connect/Organisms/DataTable/DataTableBasic.figma.tsx create mode 100644 packages/code-connect/Organisms/GridForm.figma.tsx create mode 100644 packages/code-connect/Organisms/List/List.figma.tsx create mode 100644 packages/code-connect/Organisms/List/ListRow.figma.tsx create mode 100644 packages/styleguide/src/lib/Meta/MCP/About.mdx create mode 100644 packages/styleguide/src/lib/Meta/MCP/Code Connect.mdx create mode 100644 packages/styleguide/src/lib/Meta/MCP/Figma MCP.mdx create mode 100644 packages/styleguide/src/static/mcp/component_playground.png create mode 100644 packages/styleguide/src/static/mcp/component_with_code_connect.png diff --git a/.cursor/rules/figma-rules.mdc b/.cursor/rules/figma-rules.mdc new file mode 100644 index 00000000000..45b5e626df0 --- /dev/null +++ b/.cursor/rules/figma-rules.mdc @@ -0,0 +1,160 @@ +--- +description: Figma Dev Mode MCP rules +alwaysApply: true +--- + +# Figma Dev Mode MCP Rules + +You are an expert developer working with the Codecademy Gamut design system and Figma Dev Mode MCP integration. + +When generating code from Figma designs, follow these rules: + +## MANDATORY Pre-Generation Steps + +**BEFORE generating ANY code from Figma, you MUST:** + +1. **Inspect the Figma layer hierarchy**: + + - Call `get_metadata` WITH the nodeId to get parent component info + - Call `get_metadata` WITHOUT nodeId (empty string) to attempt to get CHILD layers + - **IMPORTANT**: Current limitation - `get_metadata` may not return nested child layer names (icons, nested components, etc.) + - **If child layer names are not available from tooling:** + - Analyze the screenshot and make your best guess about which icons/nested components are used + - Look for visual clues (user icon, gear icon, etc.) and match them to likely Gamut icon names + - Search the codebase to verify the icon exists (e.g., check for `PersonIcon`, `GearIcon`, etc. in `/packages/gamut-icons/src`) + - Generate the code using your best guess + - **AFTER generating the code**, ask the user to confirm the icons are correct + - Example: "I've generated the code using PersonIcon and GearIcon based on what I see in the screenshot. Can you confirm these are the correct icons from the Figma layers?" + - Map icon layer names to components in the codebase (e.g., "Regular/Interface/PersonIcon" → `PersonIcon`, "Mini/MiniCheckCircleIcon" → `MiniCheckCircleIcon`) + +2. **Read token files** (use read_file tool on ALL of these): + + - `/packages/gamut-styles/src/variables/spacing.ts` + - `/packages/gamut-styles/src/variables/colors.ts` + - `/packages/gamut-styles/src/variables/typography.ts` + - `/packages/gamut-styles/src/variables/borderRadii.ts` + +3. **Search for existing components** (use codebase_search): + + - Check if similar component exists in `/packages/gamut/src` + - If exists, extend it instead of creating new one + +4. **Understand the design system patterns**: + - Read example components like Badge, Tag, or Button + - Follow variance, system props, and styledOptions patterns + +## Asset Management + +- The Figma Dev Mode MCP Server provides an assets endpoint which can serve image and SVG assets +- **IMPORTANT**: do NOT use or create placeholders if a localhost source is provided + +## Component Usage + +- **IMPORTANT**: Always use components from `/packages` whenever possible +- Check if the Figma component name matches a Gamut component name and use that component +- **IMPORTANT**: All patterns should come from `/packages/gamut-patterns` - use the design's metadata to match the Figma component name to the pattern component +- **IMPORTANT**: All illustrations should come from `/packages/gamut-illustrations` - use the design's metadata to match the Figma component name to the illustration component +- **IMPORTANT**: All icons should come from `/packages/gamut-icons`: + - Try to get icon layer names from Figma metadata + - If layer names are not available, make your best guess based on the screenshot and verify the icon exists in the codebase + - Generate the code with your best guess, then confirm with the user after + - Map icon layer names to Gamut components (e.g., "Regular/Interface/PersonIcon" → `PersonIcon` from `@codecademy/gamut-icons`) + +## Styling Guidelines - STRICT RULES + +### ❌ NEVER Do This: + +```tsx +// NEVER use hardcoded hex colors +color: '#ffffff', +backgroundColor: '#000000', + +// NEVER use CSS properties not in system props +backdropFilter: 'blur(1px)', + +// NEVER use inline styles +style={{ fontSize: 14 }} +``` + +### ✅ ALWAYS Do This: + +```tsx +// ALWAYS use spacing tokens (4, 8, 12, 16, 24, 32, 40, 48, 64, 96) +height: 24, // from spacing.ts +width: 64, // from spacing.ts + +// ALWAYS use fontSize tokens (14, 16, 18, 20, 22, 26, 34, 44, 64) +fontSize: 14, // from typography.ts + +// ALWAYS use borderRadii tokens (none, sm, md, lg, xl, full) +borderRadius: 'full', // from borderRadii.ts + +// ALWAYS use semantic color tokens +bg: 'background', +color: 'text', +borderColor: 'border', + +// ALWAYS use system props via system.css or styled components +system.css({ + bg: 'black', + color: 'white', + borderRadius: 'full', +}) +``` + +### Token Mapping Rules: + +1. **Spacing/Sizing**: Map Figma values to closest token from `spacing.ts` + + - 4px, 8px, 12px, 16px, 24px, 32px, 40px, 48px, 64px, 96px + +2. **Colors**: Use semantic tokens OR core colors from `colors.ts` + + - Semantic: `background`, `text`, `border`, `text-secondary`, etc. + - Core: `navy`, `white`, `black`, `blue`, `green`, `red`, `yellow`, etc. + - For Background component only: use color names (navy, white, etc.) + +3. **Border Radius**: Use tokens from `borderRadii.ts` + + - none (0px), sm (2px), md (4px), lg (8px), xl (16px), full (999px) + +4. **Typography**: Use tokens from `typography.ts` + + - fontFamily: `accent`, `base`, `monospace`, `system` + - fontSize: 14, 16, 18, 20, 22, 26, 34, 44, 64 + - fontWeight: 400, 700 or `base`, `title` + - lineHeight: `base` (1.5), `title` (1.2), `spacedTitle` (1.3) + +5. **If no exact match**: Document in code comment why custom value needed + +### Emotion & CSS-in-JS Patterns: + +- **IMPORTANT**: Do not use inline styles +- Use `styled` from `@emotion/styled` +- Use `system.css()` for style objects +- Use `styledOptions` for styled component options +- Compose system props with `variance.compose()` + +## Accessibility & Best Practices + +- **IMPORTANT**: Follow WCAG requirements for accessibility +- Always follow best practices from `/packages/styleguide/src/lib/Meta/Best Practices.mdx` + +## Implementation + +- Use the CodeConnect implementation when available +- Generate clean, maintainable React code using TypeScript +- Follow the existing Gamut patterns and conventions + +## Post-Generation Validation + +After generating code, verify: + +- [ ] No hardcoded hex colors (`#` in color values) +- [ ] No hardcoded pixel strings (`'24px'` format) +- [ ] All spacing values match tokens from spacing.ts +- [ ] All colors use semantic tokens or theme colors +- [ ] All border radius uses borderRadii tokens +- [ ] Component follows Gamut patterns (variance, system props, styledOptions) +- [ ] No inline styles +- [ ] Uses emotion styled components diff --git a/.eslintignore b/.eslintignore index 9152f931279..2b0150ad6de 100644 --- a/.eslintignore +++ b/.eslintignore @@ -8,4 +8,6 @@ docs **/tmp packages/gamut-icons/src/icons packages/gamut-patterns/src/patterns +**/code-connect/** +packages/code-connect .nx diff --git a/.eslintrc.js b/.eslintrc.js index f79525bf490..6aac8591e81 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -8,6 +8,8 @@ module.exports = { plugins: ['eslint-plugin-gamut'], + ignorePatterns: ['packages/code-connect/**/*'], + rules: { 'gamut/prefer-themed': 'error', 'gamut/no-css-standalone': 'error', diff --git a/.github/copilot-instructions.md b/.github/copilot-instructions.md new file mode 100644 index 00000000000..e69de29bb2d diff --git a/.github/instructions/figma.instructions.md b/.github/instructions/figma.instructions.md new file mode 100644 index 00000000000..d316f6a15c7 --- /dev/null +++ b/.github/instructions/figma.instructions.md @@ -0,0 +1,189 @@ +--- +applyTo: '**' +priority: high +--- + +# Figma Dev Mode MCP Rules + +You are an expert developer working with the Codecademy Gamut design system and Figma Dev Mode MCP integration. + +When generating code from Figma designs, follow these rules: + +## WORKFLOW TRIGGER - CRITICAL + +**When you see ANY of these patterns:** + +- Figma URL mentioned (figma.com/design/...) +- nodeId provided +- "generate from figma" or similar requests +- MCP Figma server is being used + +## MANDATORY Pre-Generation Steps - MUST BE COMPLETED IN ORDER + +**Step 1: Figma Inspection (REQUIRED)** + +- [ ] Call `get_metadata` WITH nodeId +- [ ] Call `get_metadata` WITHOUT nodeId +- **IMPORTANT**: Current limitation - `get_metadata` may not return nested child layer names (icons, nested components, etc.) + - **If child layer names are not available from tooling:** + - Analyze the screenshot and make your best guess about which icons/nested components are used + - Look for visual clues (user icon, gear icon, etc.) and match them to likely Gamut icon names + - Search the codebase to verify the icon exists (e.g., check for `PersonIcon`, `GearIcon`, etc. in `/packages/gamut-icons/src`) + - Generate the code using your best guess + - **AFTER generating the code**, ask the user to confirm the icons are correct + - Example: "I've generated the code using PersonIcon and GearIcon based on what I see in the screenshot. Can you confirm these are the correct icons from the Figma layers?" + - Map icon layer names to components in the codebase (e.g., "Regular/Interface/PersonIcon" → `PersonIcon`, "Mini/MiniCheckCircleIcon" → `MiniCheckCircleIcon`) +- [ ] Call `get_image` to get screenshot FOR CONFIRMATION ONLY +- [ ] **Validate that screenshot matches metadata understanding** + +**Step 1.5: Metadata Analysis (NEW - REQUIRED)** + +- [ ] **PRIMARY SOURCE**: Use Figma metadata to determine: + - Component name and type + - Layer structure and nested components + - Any CodeConnect implementations mentioned +- [ ] **SECONDARY SOURCE**: Use screenshot only to: + - Confirm visual styling details + - Identify icons if not available in metadata + - Validate layout and spacing +- [ ] **If metadata says "Badge" but screenshot looks different, trust the metadata** + +**Step 2: Token Analysis (REQUIRED)** + +- [ ] Read `/packages/gamut-styles/src/variables/spacing.ts` +- [ ] Read `/packages/gamut-styles/src/variables/colors.ts` +- [ ] Read `/packages/gamut-styles/src/variables/typography.ts` +- [ ] Read `/packages/gamut-styles/src/variables/borderRadii.ts` + +**Step 3: Component Research (REQUIRED)** + +- [ ] Search for existing components in `/packages/gamut/src` +- [ ] Read existing component if found +- [ ] Determine if extending existing vs creating new + +**Step 4: Generate Code** + +- [ ] Use existing components when possible +- [ ] Follow token mapping rules strictly +- [ ] Validate against post-generation checklist + +## Asset Management + +- The Figma Dev Mode MCP Server provides an assets endpoint which can serve image and SVG assets +- **IMPORTANT**: do NOT use or create placeholders if a localhost source is provided + +## Component Usage + +- **IMPORTANT**: Always use components from `/packages` whenever possible +- Check if the Figma component name matches a Gamut component name and use that component +- **IMPORTANT**: All patterns should come from `/packages/gamut-patterns` - use the design's metadata to match the Figma component name to the pattern component +- **IMPORTANT**: All illustrations should come from `/packages/gamut-illustrations` - use the design's metadata to match the Figma component name to the illustration component +- **IMPORTANT**: All icons should come from `/packages/gamut-icons`: + - Try to get icon layer names from Figma metadata + - If layer names are not available, make your best guess based on the screenshot and verify the icon exists in the codebase + - Generate the code with your best guess, then confirm with the user after + - Map icon layer names to Gamut components (e.g., "Regular/Interface/PersonIcon" → `PersonIcon` from `@codecademy/gamut-icons`) + +## Styling Guidelines - STRICT RULES + +### ❌ NEVER Do This: + +```tsx +// NEVER use hardcoded pixel values +height: 24, +width: '64px', +fontSize: 14, + +// NEVER use hardcoded hex colors +color: '#ffffff', +backgroundColor: '#000000', + +// NEVER use CSS properties not in system props +backdropFilter: 'blur(1px)', + +// NEVER use inline styles +style={{ fontSize: 14 }} +``` + +### ✅ ALWAYS Do This: + +```tsx +// ALWAYS use spacing tokens (4, 8, 12, 16, 24, 32, 40, 48, 64, 96) +height: 24, // from spacing.ts +width: 64, // from spacing.ts + +// ALWAYS use fontSize tokens (14, 16, 18, 20, 22, 26, 34, 44, 64) +fontSize: 14, // from typography.ts + +// ALWAYS use borderRadii tokens (none, sm, md, lg, xl, full) +borderRadius: 'full', // from borderRadii.ts + +// ALWAYS use semantic color tokens +bg: 'background', +color: 'text', +borderColor: 'border', + +// ALWAYS use system props via system.css or styled components +system.css({ + bg: 'black', + color: 'white', + borderRadius: 'full', +}) +``` + +### Token Mapping Rules: + +1. **Spacing/Sizing**: Map Figma values to closest token from `spacing.ts` + + - 4px, 8px, 12px, 16px, 24px, 32px, 40px, 48px, 64px, 96px + +2. **Colors**: Use semantic tokens OR core colors from `colors.ts` + + - Semantic: `background`, `text`, `border`, `text-secondary`, etc. + - Core: `navy`, `white`, `black`, `blue`, `green`, `red`, `yellow`, etc. + - For Background component only: use color names (navy, white, etc.) + +3. **Border Radius**: Use tokens from `borderRadii.ts` + + - none (0px), sm (2px), md (4px), lg (8px), xl (16px), full (999px) + +4. **Typography**: Use tokens from `typography.ts` + + - fontFamily: `accent`, `base`, `monospace`, `system` + - fontSize: 14, 16, 18, 20, 22, 26, 34, 44, 64 + - fontWeight: 400, 700 or `base`, `title` + - lineHeight: `base` (1.5), `title` (1.2), `spacedTitle` (1.3) + +5. **If no exact match**: Document in code comment why custom value needed + +### Emotion & CSS-in-JS Patterns: + +- **IMPORTANT**: Do not use inline styles +- Use `styled` from `@emotion/styled` +- Use `system.css()` for style objects +- Use `styledOptions` for styled component options +- Compose system props with `variance.compose()` + +## Accessibility & Best Practices + +- **IMPORTANT**: Follow WCAG requirements for accessibility +- Always follow best practices from `/packages/styleguide/src/lib/Meta/Best Practices.mdx` + +## Implementation + +- Use the CodeConnect implementation when available +- Generate clean, maintainable React code using TypeScript +- Follow the existing Gamut patterns and conventions + +## Post-Generation Validation + +After generating code, verify: + +- [ ] No hardcoded hex colors (`#` in color values) +- [ ] No hardcoded pixel strings (`'24px'` format) +- [ ] All spacing values match tokens from spacing.ts +- [ ] All colors use semantic tokens or theme colors +- [ ] All border radius uses borderRadii tokens +- [ ] Component follows Gamut patterns (variance, system props, styledOptions) +- [ ] No inline styles +- [ ] Uses emotion styled components diff --git a/.prettierignore b/.prettierignore index 291938f37e6..af4f080d196 100644 --- a/.prettierignore +++ b/.prettierignore @@ -5,6 +5,7 @@ node_modules packages/gamut-icons/src/icons packages/gamut-styles/**/*.d.ts packages/styleguide/stories/Core/Atoms/Markdown/*.md +packages/code-connect/**/* /.nx/cache /.nx/workspace-data diff --git a/figma.config.json b/figma.config.json new file mode 100644 index 00000000000..6bd64f2bf52 --- /dev/null +++ b/figma.config.json @@ -0,0 +1,7 @@ +{ + "codeConnect": { + "include": ["packages/code-connect/**/*.{tsx,jsx}"], + "label": "React", + "interactiveSetupFigmaFileUrl": "https://www.figma.com/design/ReGfRNillGABAj5SlITalN/%F0%9F%93%90-Gamut?node-id=23-5&p=f&m=dev" + } +} diff --git a/packages/code-connect/Atoms/Anchor.figma.tsx b/packages/code-connect/Atoms/Anchor.figma.tsx new file mode 100644 index 00000000000..d9433f40900 --- /dev/null +++ b/packages/code-connect/Atoms/Anchor.figma.tsx @@ -0,0 +1,42 @@ +import React from 'react'; +import { Anchor } from '@codecademy/gamut'; +import figma from '@figma/code-connect'; + +/** + * -- This file was auto-generated by Code Connect -- + * `props` includes a mapping from Figma properties and variants to + * suggested values. You should update this to match the props of your + * code component, and update the `example` function to return the + * code example you'd like to see in Figma + */ + +figma.connect( + Anchor, + 'https://www.figma.com/design/ReGfRNillGABAj5SlITalN/%F0%9F%93%90-Gamut?node-id=19220%3A21856', + { + props: { + children: figma.string('✏️ label'), + icon: figma.boolean('👁 leading icon', { + true: figma.instance('↳ leading icon'), + false: figma.boolean('👁 trailing icon', { + true: figma.instance('↳ trailing icon'), + false: undefined, + }), + }), + iconPosition: figma.boolean('👁 leading icon', { + true: 'left', + false: figma.boolean('👁 trailing icon', { + true: 'right', + false: undefined, + }), + }), + variant: figma.enum('variant', { + Inline: 'inline', + Interface: 'interface', + Standard: 'standard', + 'Standard-secondary': 'standard-secondary', + }), + }, + example: ({ children, ...props }) => {children}, + } +); diff --git a/packages/code-connect/Atoms/Badge.figma.tsx b/packages/code-connect/Atoms/Badge.figma.tsx new file mode 100644 index 00000000000..94800ac0f40 --- /dev/null +++ b/packages/code-connect/Atoms/Badge.figma.tsx @@ -0,0 +1,36 @@ +import React from 'react'; +import { Badge } from '@codecademy/gamut'; +import figma from '@figma/code-connect'; + +/** + * -- This file was auto-generated by Code Connect -- + * `props` includes a mapping from Figma properties and variants to + * suggested values. You should update this to match the props of your + * code component, and update the `example` function to return the + * code example you'd like to see in Figma + */ + +figma.connect( + Badge, + 'https://www.figma.com/design/ReGfRNillGABAj5SlITalN/%F0%9F%93%90-Gamut?node-id=8200%3A8349', + { + props: { + children: figma.string('✏️ label'), + icon: figma.instance('↳ icon'), + variant: figma.enum('variant', { + primary: 'primary', + secondary: 'secondary', + tertiary: 'tertiary', + tertiaryFill: 'tertiaryFill', + accent: 'accent', + }), + size: figma.enum('size', { + base: 'base', + sm: 'sm', + }), + }, + example: ({ children, ...props }: any) => ( + {children} + ), + } +); diff --git a/packages/code-connect/Atoms/Buttons/CTAButton.figma.tsx b/packages/code-connect/Atoms/Buttons/CTAButton.figma.tsx new file mode 100644 index 00000000000..f1d1208d72c --- /dev/null +++ b/packages/code-connect/Atoms/Buttons/CTAButton.figma.tsx @@ -0,0 +1,31 @@ +import React from 'react'; +import { CTAButton } from '@codecademy/gamut'; +import figma from '@figma/code-connect'; + +/** + * -- This file was auto-generated by Code Connect -- + * `props` includes a mapping from Figma properties and variants to + * suggested values. You should update this to match the props of your + * code component, and update the `example` function to return the + * code example you'd like to see in Figma + */ + +figma.connect( + CTAButton, + 'https://www.figma.com/design/ReGfRNillGABAj5SlITalN/%F0%9F%93%90-Gamut?node-id=1615%3A1914', + { + props: { + children: figma.string('✏️ label'), + icon: figma.boolean('👁 leading icon', { + true: figma.instance('↳ leading icon'), + false: figma.boolean('👁 trailing icon', { + true: figma.instance('↳ trailing icon'), + false: undefined, + }), + }), + }, + example: ({ children, ...props }) => ( + {children} + ), + } +); diff --git a/packages/code-connect/Atoms/Buttons/FillButton.figma.tsx b/packages/code-connect/Atoms/Buttons/FillButton.figma.tsx new file mode 100644 index 00000000000..84f22e13cfd --- /dev/null +++ b/packages/code-connect/Atoms/Buttons/FillButton.figma.tsx @@ -0,0 +1,41 @@ +import React from 'react'; +import { FillButton } from '@codecademy/gamut'; +import figma from '@figma/code-connect'; + +/** + * -- This file was auto-generated by Code Connect -- + * `props` includes a mapping from Figma properties and variants to + * suggested values. You should update this to match the props of your + * code component, and update the `example` function to return the + * code example you'd like to see in Figma + */ + +figma.connect( + FillButton, + 'https://www.figma.com/design/ReGfRNillGABAj5SlITalN/%F0%9F%93%90-Gamut?node-id=1106%3A7', + { + props: { + icon: figma.boolean('👁 leading icon', { + true: figma.instance('↳ leading icon'), + false: figma.boolean('👁 trailing icon', { + true: figma.instance('↳ trailing icon'), + false: undefined, + }), + }), + children: figma.string('✏️ label'), + variant: figma.enum('variant', { + primary: 'primary', + secondary: 'secondary', + danger: 'danger', + }), + size: figma.enum('size', { + normal: 'normal', + small: 'small', + large: 'large', + }), + }, + example: ({ children, ...props }) => ( + {children} + ), + } +); diff --git a/packages/code-connect/Atoms/Buttons/IconButton.figma.tsx b/packages/code-connect/Atoms/Buttons/IconButton.figma.tsx new file mode 100644 index 00000000000..65898b34504 --- /dev/null +++ b/packages/code-connect/Atoms/Buttons/IconButton.figma.tsx @@ -0,0 +1,30 @@ +import React from 'react'; +import { IconButton } from '@codecademy/gamut'; +import figma from '@figma/code-connect'; + +/** + * -- This file was auto-generated by Code Connect -- + * `props` includes a mapping from Figma properties and variants to + * suggested values. You should update this to match the props of your + * code component, and update the `example` function to return the + * code example you'd like to see in Figma + */ + +figma.connect( + IconButton, + 'https://www.figma.com/design/ReGfRNillGABAj5SlITalN/%F0%9F%93%90-Gamut?node-id=1106%3A90', + { + props: { + icon: figma.instance('icon'), + size: figma.enum('size', { + normal: 'normal', + small: 'small', + large: 'large', + }), + toolTipInfo: figma.nestedProps('tooltip', { + tip: figma.textContent('✏️ tooltip'), + }), + }, + example: (props) => , + } +); diff --git a/packages/code-connect/Atoms/Buttons/StrokeButton.figma.tsx b/packages/code-connect/Atoms/Buttons/StrokeButton.figma.tsx new file mode 100644 index 00000000000..0d637bc317b --- /dev/null +++ b/packages/code-connect/Atoms/Buttons/StrokeButton.figma.tsx @@ -0,0 +1,41 @@ +import React from 'react'; +import { StrokeButton } from '@codecademy/gamut'; +import figma from '@figma/code-connect'; + +/** + * -- This file was auto-generated by Code Connect -- + * `props` includes a mapping from Figma properties and variants to + * suggested values. You should update this to match the props of your + * code component, and update the `example` function to return the + * code example you'd like to see in Figma + */ + +figma.connect( + StrokeButton, + 'https://www.figma.com/design/ReGfRNillGABAj5SlITalN/%F0%9F%93%90-Gamut?node-id=1106%3A48', + { + props: { + children: figma.string('✏️ label'), + icon: figma.boolean('👁 leading icon', { + true: figma.instance('↳ leading icon'), + false: figma.boolean('👁 trailing icon', { + true: figma.instance('↳ trailing icon'), + false: undefined, + }), + }), + variant: figma.enum('variant', { + primary: 'primary', + secondary: 'secondary', + danger: 'danger', + }), + size: figma.enum('size', { + normal: 'normal', + small: 'small', + large: 'large', + }), + }, + example: ({ children, ...props }) => ( + {children} + ), + } +); diff --git a/packages/code-connect/Atoms/Buttons/TextButton.figma.tsx b/packages/code-connect/Atoms/Buttons/TextButton.figma.tsx new file mode 100644 index 00000000000..9bdc3a7e077 --- /dev/null +++ b/packages/code-connect/Atoms/Buttons/TextButton.figma.tsx @@ -0,0 +1,36 @@ +import React from 'react'; +import { TextButton } from '@codecademy/gamut'; +import figma from '@figma/code-connect'; + +/** + * -- This file was auto-generated by Code Connect -- + * `props` includes a mapping from Figma properties and variants to + * suggested values. You should update this to match the props of your + * code component, and update the `example` function to return the + * code example you'd like to see in Figma + */ + +figma.connect( + TextButton, + 'https://www.figma.com/design/ReGfRNillGABAj5SlITalN/%F0%9F%93%90-Gamut?node-id=1106%3A69', + { + props: { + children: figma.string('✏️ label'), + icon: figma.boolean('👁 leading icon', { + true: figma.instance('↳ leading icon'), + false: figma.boolean('👁 trailing icon', { + true: figma.instance('↳ trailing icon'), + false: undefined, + }), + }), + size: figma.enum('size', { + small: 'small', + normal: 'normal', + large: 'large', + }), + }, + example: ({ children, ...props }) => ( + {children} + ), + } +); diff --git a/packages/code-connect/Atoms/Card.figma.tsx b/packages/code-connect/Atoms/Card.figma.tsx new file mode 100644 index 00000000000..a574e45ff0f --- /dev/null +++ b/packages/code-connect/Atoms/Card.figma.tsx @@ -0,0 +1,39 @@ +import React from 'react'; +import { Card } from '@codecademy/gamut'; +import figma from '@figma/code-connect'; + +/** + * -- This file was auto-generated by Code Connect -- + * `props` includes a mapping from Figma properties and variants to + * suggested values. You should update this to match the props of your + * code component, and update the `example` function to return the + * code example you'd like to see in Figma + */ + +figma.connect( + Card, + 'https://www.figma.com/design/ReGfRNillGABAj5SlITalN/%F0%9F%93%90-Gamut?node-id=20111%3A63391', + { + props: { + variant: figma.enum('variant', { + Default: 'default', + White: 'white', + Yellow: 'yellow', + Beige: 'beige', + Navy: 'navy', + Hyper: 'hyper', + }), + shadow: figma.enum('shadow', { + 'pattern-right': 'patternRight', + 'pattern-left': 'patternLeft', + outline: 'outline', + none: 'none', + }), + isInteractive: figma.boolean('isInteractive'), + children: figma.children('.Card Content'), + }, + example: ({ children, ...props }) => ( + {children} + ), + } +); diff --git a/packages/code-connect/Atoms/Drawer.figma.tsx b/packages/code-connect/Atoms/Drawer.figma.tsx new file mode 100644 index 00000000000..70cb5025503 --- /dev/null +++ b/packages/code-connect/Atoms/Drawer.figma.tsx @@ -0,0 +1,28 @@ +import { Drawer } from '@codecademy/gamut'; +import figma from '@figma/code-connect'; + +/** + * -- This file was auto-generated by Code Connect -- + * `props` includes a mapping from Figma properties and variants to + * suggested values. You should update this to match the props of your + * code component, and update the `example` function to return the + * code example you'd like to see in Figma + */ + +figma.connect( + Drawer, + 'https://www.figma.com/design/ReGfRNillGABAj5SlITalN/%F0%9F%93%90-Gamut?node-id=115820-91241', + { + props: { + alignContentContainer: figma.enum('alignContentContainer', { + left: 'left', + right: 'right', + }), + expanded: true, + children: figma.textContent('text'), + }, + example: ({ children, ...props }) => ( + {children} + ), + } +); diff --git a/packages/code-connect/Atoms/FeatureShimmer.figma.tsx b/packages/code-connect/Atoms/FeatureShimmer.figma.tsx new file mode 100644 index 00000000000..192c8768a1e --- /dev/null +++ b/packages/code-connect/Atoms/FeatureShimmer.figma.tsx @@ -0,0 +1,22 @@ +import React from 'react'; +import { FeatureShimmer } from '@codecademy/gamut'; +import figma from '@figma/code-connect'; + +/** + * -- This file was auto-generated by Code Connect -- + * `props` includes a mapping from Figma properties and variants to + * suggested values. You should update this to match the props of your + * code component, and update the `example` function to return the + * code example you'd like to see in Figma + */ + +figma.connect( + FeatureShimmer, + 'https://www.figma.com/design/ReGfRNillGABAj5SlITalN/%F0%9F%93%90-Gamut?node-id=61568%3A44253', + { + props: { + children: '{children}', + }, + example: (props) => {props.children}, + } +); diff --git a/packages/code-connect/Atoms/FormGroup.figma.tsx b/packages/code-connect/Atoms/FormGroup.figma.tsx new file mode 100644 index 00000000000..6f3a7b3e92c --- /dev/null +++ b/packages/code-connect/Atoms/FormGroup.figma.tsx @@ -0,0 +1,60 @@ +import React from 'react'; +import { FormGroup } from '@codecademy/gamut'; +import figma from '@figma/code-connect'; + +/** + * -- This file was auto-generated by Code Connect -- + * `props` includes a mapping from Figma properties and variants to + * suggested values. You should update this to match the props of your + * code component, and update the `example` function to return the + * code example you'd like to see in Figma + */ + +figma.connect( + FormGroup, + 'https://www.figma.com/design/ReGfRNillGABAj5SlITalN/%F0%9F%93%90-Gamut?node-id=47031%3A18018', + { + props: { + label: figma.string('✏️ label'), + infoTip: figma.boolean('infoTip'), + required: figma.boolean('required'), + labelSize: figma.enum('size', { + small: 'small', + large: 'large', + }), + infoTipData: figma.nestedProps('infotip', { + emphasis: figma.enum('emphasis', { low: 'low', high: 'high' }), + disabled: figma.enum('state', { + default: false, + hover: false, + active: false, + focus: false, + disabled: true, + }), + }), + description: figma.boolean('description', { + true: figma.textContent('description'), + }), + toolTipInfo: figma.nestedProps('.infotip-alignment', { + alignment: figma.enum('alignment', { + 'top-left': 'top-left', + 'top-right': 'top-right', + 'bottom-left': 'bottom-left', + 'bottom-right': 'bottom-right', + }), + info: figma.textContent('✏️ tip'), + }), + disabled: figma.boolean('disabled'), + }, + example: (props) => ( + + ), + } +); diff --git a/packages/code-connect/Atoms/FormInputs/Checkbox.figma.tsx b/packages/code-connect/Atoms/FormInputs/Checkbox.figma.tsx new file mode 100644 index 00000000000..1be7cb6c9f5 --- /dev/null +++ b/packages/code-connect/Atoms/FormInputs/Checkbox.figma.tsx @@ -0,0 +1,35 @@ +import React from 'react'; +import { Checkbox } from '@codecademy/gamut'; +import figma from '@figma/code-connect'; + +/** + * -- This file was auto-generated by Code Connect -- + * `props` includes a mapping from Figma properties and variants to + * suggested values. You should update this to match the props of your + * code component, and update the `example` function to return the + * code example you'd like to see in Figma + */ + +figma.connect( + Checkbox, + 'https://www.figma.com/design/ReGfRNillGABAj5SlITalN/%F0%9F%93%90-Gamut?node-id=1199%3A270', + { + props: { + label: figma.string('✏️ label'), + checked: figma.enum('checked', { + true: 'true', + false: 'false', + Indeterminate: 'false', + }), + indeterminate: figma.enum('checked', { + true: 'false', + false: 'false', + Indeterminate: 'true', + }), + ariaLabel: figma.string('✏️ label'), + }, + example: ({ ariaLabel, ...props }) => ( + + ), + } +); diff --git a/packages/code-connect/Atoms/FormInputs/Input.figma.tsx b/packages/code-connect/Atoms/FormInputs/Input.figma.tsx new file mode 100644 index 00000000000..af178e9c102 --- /dev/null +++ b/packages/code-connect/Atoms/FormInputs/Input.figma.tsx @@ -0,0 +1,124 @@ +import React from 'react'; +import { Input } from '@codecademy/gamut'; +import figma from '@figma/code-connect'; + +/** + * -- This file was auto-generated by Code Connect -- + * `props` includes a mapping from Figma properties and variants to + * suggested values. You should update this to match the props of your + * code component, and update the `example` function to return the + * code example you'd like to see in Figma + */ + +figma.connect( + Input, + 'https://www.figma.com/design/ReGfRNillGABAj5SlITalN/%F0%9F%93%90-Gamut?node-id=1189%3A590', + { + props: { + type: figma.enum('type', { + text: 'text', + number: 'number', + file: 'file', + }), + size: figma.enum('size', { + base: 'base', + small: 'small', + }), + error: figma.enum('state', { + error: true, + 'error + hover': true, + 'error + focus': true, + 'filled + error': true, + 'filled + error + hover': true, + 'filled + error + focus': true, + }), + valid: figma.enum('state', { + valid: true, + 'valid + hover': true, + 'valid + focus': true, + 'filled + valid': true, + 'filled + valid + focus': true, + 'filled + valid + hover': true, + }), + disabled: figma.enum('state', { + disabled: true, + 'filled + disabled': true, + }), + placeholder: figma.boolean('placeholder', { + true: figma.enum('state', { + enabled: figma.enum('type', { + text: figma.textContent('placeholder'), + number: figma.textContent('placeholder'), + }), + hover: figma.enum('type', { + text: figma.textContent('placeholder'), + number: figma.textContent('placeholder'), + }), + focus: figma.enum('type', { + text: figma.textContent('placeholder'), + number: figma.textContent('placeholder'), + }), + error: figma.enum('type', { + text: figma.textContent('placeholder'), + number: figma.textContent('placeholder'), + }), + 'error + hover': figma.enum('type', { + text: figma.textContent('placeholder'), + number: figma.textContent('placeholder'), + }), + 'error + focus': figma.enum('type', { + text: figma.textContent('placeholder'), + number: figma.textContent('placeholder'), + }), + disabled: figma.enum('type', { + text: figma.textContent('placeholder'), + number: figma.textContent('placeholder'), + }), + }), + }), + defaultValue: figma.enum('state', { + filled: figma.enum('type', { + text: figma.string('input'), + number: figma.string('number'), + }), + 'filled + hover': figma.enum('type', { + text: figma.string('input'), + number: figma.string('number'), + }), + 'filled + focus': figma.enum('type', { + text: figma.string('input'), + number: figma.string('number'), + }), + 'filled + valid': figma.enum('type', { + text: figma.string('input'), + number: figma.string('number'), + }), + 'filled + valid + hover': figma.enum('type', { + text: figma.string('input'), + number: figma.string('number'), + }), + 'filled + valid + focus': figma.enum('type', { + text: figma.string('input'), + number: figma.string('number'), + }), + 'filled + error': figma.enum('type', { + text: figma.string('input'), + number: figma.string('number'), + }), + 'filled + error + hover': figma.enum('type', { + text: figma.string('input'), + number: figma.string('number'), + }), + 'filled + error + focus': figma.enum('type', { + text: figma.string('input'), + number: figma.string('number'), + }), + 'filled + disabled': figma.enum('type', { + text: figma.string('input'), + number: figma.string('number'), + }), + }), + }, + example: (props) => , + } +); diff --git a/packages/code-connect/Atoms/FormInputs/Radio.figma.tsx b/packages/code-connect/Atoms/FormInputs/Radio.figma.tsx new file mode 100644 index 00000000000..49e1609899d --- /dev/null +++ b/packages/code-connect/Atoms/FormInputs/Radio.figma.tsx @@ -0,0 +1,64 @@ +import React from 'react'; +import { Radio } from '@codecademy/gamut'; +import figma from '@figma/code-connect'; + +/** + * -- This file was auto-generated by Code Connect -- + * `props` includes a mapping from Figma properties and variants to + * suggested values. You should update this to match the props of your + * code component, and update the `example` function to return the + * code example you'd like to see in Figma + */ + +figma.connect( + Radio, + 'https://www.figma.com/design/ReGfRNillGABAj5SlITalN/%F0%9F%93%90-Gamut?node-id=1191%3A1622', + { + props: { + label: figma.string('✏️ label'), + checked: figma.boolean('selected'), + disabled: figma.enum('state', { + enabled: false, + hover: false, + active: false, + focus: false, + error: false, + disabled: true, + }), + error: figma.enum('state', { + enabled: false, + hover: false, + active: false, + focus: false, + error: true, + disabled: false, + }), + hasInfotip: figma.boolean('infoTip', { + true: true, + false: false, + }), + hasEmphasis: figma.nestedProps('infotip', { + emphasis: figma.enum('emphasis', { low: 'low', high: 'high' }), + }), + toolTipInfo: figma.nestedProps('.infotip-alignment', { + alignment: figma.enum('alignment', { + 'top-left': 'top-left', + 'top-right': 'top-right', + 'bottom-left': 'bottom-left', + 'bottom-right': 'bottom-right', + }), + info: figma.string('✏️ info tip'), + }), + }, + example: (props) => ( + + ), + } +); diff --git a/packages/code-connect/Atoms/FormInputs/Select.figma.tsx b/packages/code-connect/Atoms/FormInputs/Select.figma.tsx new file mode 100644 index 00000000000..fdd2485c26b --- /dev/null +++ b/packages/code-connect/Atoms/FormInputs/Select.figma.tsx @@ -0,0 +1,51 @@ +import React from 'react'; +import { Select } from '@codecademy/gamut'; +import figma from '@figma/code-connect'; + +/** + * -- This file was auto-generated by Code Connect -- + * `props` includes a mapping from Figma properties and variants to + * suggested values. You should update this to match the props of your + * code component, and update the `example` function to return the + * code example you'd like to see in Figma + */ + +figma.connect( + Select, + 'https://www.figma.com/design/ReGfRNillGABAj5SlITalN/%F0%9F%93%90-Gamut?node-id=70050%3A14802', + { + props: { + sizeVariant: figma.enum('sizeVariant', { + base: 'base', + small: 'small', + }), + disabled: figma.enum('state', { + Enabled: false, + Hover: false, + Focus: false, + Active: false, + Filled: false, + 'Filled + Hover': false, + 'Filled + Focus': false, + 'Filled + Active': false, + Error: false, + Disabled: true, + }), + error: figma.enum('state', { + Enabled: false, + Hover: false, + Focus: false, + Active: false, + Filled: false, + 'Filled + Hover': false, + 'Filled + Focus': false, + 'Filled + Active': false, + Error: true, + Disabled: false, + }), + options: figma.children('option-*'), + defaultValue: figma.string('selection'), + }, + example: (props) =>