Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(ThemeContextProvider): expose TextPreset size/lineHeight tokens as css vars #1178

Merged
merged 9 commits into from
Jul 30, 2024
60 changes: 60 additions & 0 deletions src/__private_stories__/text-preset-vars-story.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
import * as React from 'react';
import {Stack, Text} from '..';
import {vars} from '../skins/skin-contract.css';

export default {
title: 'Private/TextPresets CSS vars',
};

const TextWrapper = ({
weight,
size,
lineHeight,
children,
}: {
weight?: string;
size?: string;
lineHeight?: string;
children: React.ReactNode;
}) => {
return (
<Text>
<span style={{fontWeight: weight, fontSize: size, lineHeight}}>{children}</span>
marcoskolodny marked this conversation as resolved.
Show resolved Hide resolved
</Text>
);
};

export const Default: StoryComponent = () => {
return (
<Stack space={8} dataAttributes={{testid: 'story'}}>
<TextWrapper weight={vars.textPresets.cardTitle.weight}>cardTitle</TextWrapper>
<TextWrapper weight={vars.textPresets.button.weight}>button</TextWrapper>
<TextWrapper weight={vars.textPresets.link.weight}>link</TextWrapper>
<TextWrapper weight={vars.textPresets.title1.weight}>title1</TextWrapper>
<TextWrapper
weight={vars.textPresets.title2.weight}
size={vars.textPresets.title2.size}
lineHeight={vars.textPresets.title2.lineHeight}
>
title2
</TextWrapper>
<TextWrapper weight={vars.textPresets.indicator.weight}>indicator</TextWrapper>
<TextWrapper
weight={vars.textPresets.tabsLabel.weight}
size={vars.textPresets.tabsLabel.size}
lineHeight={vars.textPresets.tabsLabel.lineHeight}
>
tabsLabel
</TextWrapper>
<TextWrapper weight={vars.textPresets.navigationBar.weight}>navigationBar</TextWrapper>
<TextWrapper weight={vars.textPresets.text5.weight}>text5</TextWrapper>
<TextWrapper weight={vars.textPresets.text6.weight}>text6</TextWrapper>
<TextWrapper weight={vars.textPresets.text7.weight}>text7</TextWrapper>
<TextWrapper weight={vars.textPresets.text8.weight}>text8</TextWrapper>
<TextWrapper weight={vars.textPresets.text9.weight}>text9</TextWrapper>
<TextWrapper weight={vars.textPresets.text10.weight}>text10</TextWrapper>
</Stack>
);
};

Default.storyName = 'TextPresets CSS vars';
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
import {openStoryPage, screen} from '../test-utils';

const SKINS = ['Movistar', 'Vivo-new', 'O2-new'] as const;
Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Tested only in these skins because I don't see any value in adding all the possible ones here

const DEVICES = ['MOBILE_IOS', 'DESKTOP'] as const;

const getCases = () => {
const cases = [];
for (const skin of SKINS) {
for (const device of DEVICES) {
cases.push([skin, device]);
}
}
return cases;
};

test.each(getCases())('TextPresets as CSS vars in %s (%s)', async (skin, device) => {
await openStoryPage({
id: 'private-textpresets-css-vars--default',
skin: skin as (typeof SKINS)[number],
device: device as (typeof DEVICES)[number],
});

const story = await screen.findByTestId('story');
expect(await story.screenshot()).toMatchImageSnapshot();
});
4 changes: 2 additions & 2 deletions src/skins/skin-contract.css.ts
Original file line number Diff line number Diff line change
Expand Up @@ -181,9 +181,9 @@ const textPresets = {
button: {weight: ''},
link: {weight: ''},
title1: {weight: ''},
title2: {weight: ''},
title2: {weight: '', size: '', lineHeight: ''},
indicator: {weight: ''},
tabsLabel: {weight: ''},
tabsLabel: {weight: '', size: '', lineHeight: ''},
navigationBar: {weight: ''},
text5: {weight: ''},
text6: {weight: ''},
Expand Down
44 changes: 22 additions & 22 deletions src/skins/types/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,33 +11,33 @@ export type GetKnownSkin = (variant?: SkinVariant) => KnownSkin;

export type FontWeight = 'light' | 'regular' | 'medium' | 'bold';

type TextWeightTokenConfig<PossibleFontWeights = FontWeight> = {
weight: PossibleFontWeights;
size?: never;
lineHeight?: never;
};

type TextTokenConfig<PossibleFontWeights = FontWeight> = {
weight: PossibleFontWeights;
size: {mobile: number; desktop: number};
lineHeight: {mobile: number; desktop: number};
};

export type TextPresetsConfig = {
cardTitle: TextTokenConfig;
button: TextTokenConfig<'regular' | 'medium'>;
link: TextTokenConfig<'regular' | 'medium'>;
title1: TextTokenConfig<'regular' | 'medium'>;
title2: {
weight: FontWeight;
size: {mobile: number; desktop: number};
lineHeight: {mobile: number; desktop: number};
};
indicator: TextTokenConfig<'regular' | 'medium'>;
tabsLabel: {
weight: FontWeight;
size: {mobile: number; desktop: number};
lineHeight: {mobile: number; desktop: number};
};
navigationBar: TextTokenConfig;
text5: TextTokenConfig;
text6: TextTokenConfig;
text7: TextTokenConfig;
text8: TextTokenConfig;
text9: TextTokenConfig;
text10: TextTokenConfig;
cardTitle: TextWeightTokenConfig;
button: TextWeightTokenConfig<'regular' | 'medium'>;
link: TextWeightTokenConfig<'regular' | 'medium'>;
title1: TextWeightTokenConfig<'regular' | 'medium'>;
title2: TextTokenConfig;
indicator: TextWeightTokenConfig<'regular' | 'medium'>;
tabsLabel: TextTokenConfig;
navigationBar: TextWeightTokenConfig;
text5: TextWeightTokenConfig;
text6: TextWeightTokenConfig;
text7: TextWeightTokenConfig;
text8: TextWeightTokenConfig;
text9: TextWeightTokenConfig;
text10: TextWeightTokenConfig;
};

type PartialTextPresetsConfig = Partial<TextPresetsConfig>;
Expand Down
68 changes: 58 additions & 10 deletions src/theme-context-provider.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,8 @@ import {isClientSide} from './utils/environment';
import {PACKAGE_VERSION} from './package-version';
import {SnackbarRoot} from './snackbar-context';
import {mapToWeight} from './text';
import * as mq from './media-queries.css';
import * as styles from './theme-context.css';

import type {Colors, TextPresetsConfig} from './skins/types';
import type {Theme, ThemeConfig} from './theme';
Expand Down Expand Up @@ -110,6 +112,14 @@ const SetupStackingContext = () => {
return <div ref={ref} style={{display: 'none'}} />;
};

type TextPresetsVars = {
[key in keyof TextPresetsConfig]: {
weight: string;
size: string;
lineHeight: string;
};
};

const ThemeContextProvider: React.FC<Props> = ({theme, children, as, withoutStyles = false}) => {
const nextAriaId = React.useRef(1);
const getAriaId = React.useCallback((): string => `aria-id-hook-${nextAriaId.current++}`, []);
Expand Down Expand Up @@ -180,28 +190,49 @@ const ThemeContextProvider: React.FC<Props> = ({theme, children, as, withoutStyl
[colors]
);

// TODO: create CSS vars for size and lineHeight (https://jira.tid.es/browse/WEB-1929)
const textPresetsVars = React.useMemo(() => {
// Get an object mapping textPresets tokens to objects containing the token's weight
// For example, {title1: {weight: '700'}}
const tokenValues = Object.entries(contextTheme.textPresets).map(([token, config]) => {
// Map light/regular/medium/bold to valid css fontWeight values
return {[token]: {weight: String(mapToWeight[config.weight])}};
return {
[token]: {
weight: String(mapToWeight[config.weight]),
...(config.size && {size: `${config.size.desktop}px`}),
...(config.lineHeight && {lineHeight: `${config.lineHeight.desktop}px`}),
},
};
});

const textPresetsVars = Object.assign({}, ...tokenValues) as {
[key in keyof TextPresetsConfig]: {weight: string};
};
return Object.assign({}, ...tokenValues) as TextPresetsVars;
}, [contextTheme]);

return textPresetsVars;
const textPresetsResponsiveVars = React.useMemo(() => {
const tokenValues = Object.entries(contextTheme.textPresets).map(([token, config]) => {
return {
[token]: {
weight: String(mapToWeight[config.weight]),
// Use mobile values for size/lineHeight
...(config.size && {size: `${config.size.mobile}px`}),
...(config.lineHeight && {lineHeight: `${config.lineHeight.mobile}px`}),
},
};
});

return Object.assign({}, ...tokenValues) as TextPresetsVars;
}, [contextTheme]);

const themeVars = assignInlineVars(vars, {
const themeVarsValues = {
textPresets: textPresetsVars,
colors,
rawColors,
borderRadii: theme.skin.borderRadii ?? defaultBorderRadiiConfig,
});
};

const responsiveThemeVarsValues = {
...themeVarsValues,
marcoskolodny marked this conversation as resolved.
Show resolved Hide resolved
textPresets: textPresetsResponsiveVars,
};

return (
<>
Expand All @@ -222,8 +253,18 @@ const ThemeContextProvider: React.FC<Props> = ({theme, children, as, withoutStyl
{
style: {
isolation: 'isolate',
...(withoutStyles ? {} : themeVars),
...assignInlineVars(
styles.themeVarsContract,
themeVarsValues
),
...assignInlineVars(
styles.responsiveThemeVarsContract,
responsiveThemeVarsValues
),
},
className: withoutStyles
? undefined
: styles.themeVars,
},
children
)
Expand All @@ -232,7 +273,14 @@ const ThemeContextProvider: React.FC<Props> = ({theme, children, as, withoutStyl
{!withoutStyles &&
(process.env.NODE_ENV !== 'test' ||
process.env.SSR_TEST) && (
<style>{`:root {${themeVars}}`}</style>
<style>
{`
:root {${assignInlineVars(vars, themeVarsValues)}}
@media ${mq.tabletOrSmaller} {
:root {${assignInlineVars(vars, responsiveThemeVarsValues)}}
}
`}
</style>
)}
{children}
</>
Expand Down
16 changes: 16 additions & 0 deletions src/theme-context.css.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
import {assignVars, createThemeContract, style} from '@vanilla-extract/css';
import {vars as skinVars} from './skins/skin-contract.css';
import * as mq from './media-queries.css';

export const themeVarsContract = createThemeContract(skinVars);
export const responsiveThemeVarsContract = createThemeContract(skinVars);

export const themeVars = style({
vars: assignVars(skinVars, themeVarsContract),

'@media': {
[mq.tabletOrSmaller]: {
vars: assignVars(skinVars, responsiveThemeVarsContract),
},
},
});
Loading