Skip to content

Commit 2656e53

Browse files
committed
Tighten up the types and deduplicate CSS Var generation code
1 parent c7a3747 commit 2656e53

File tree

1 file changed

+109
-40
lines changed
  • polaris-react/src/utilities

1 file changed

+109
-40
lines changed

polaris-react/src/utilities/css.ts

Lines changed: 109 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -12,9 +12,14 @@ export type ResponsivePropObject<T = string> = {
1212

1313
export type ResponsiveProp<T = string> = T | ResponsivePropObject<T>;
1414

15-
export type ResponsiveValue<T = string> = undefined | ResponsiveProp<T>;
15+
export type PolarisCSSCustomPropertyName = `${`--p-` | `--pc-`}${string}`;
16+
export type PolarisCSSVar = `var(${PolarisCSSCustomPropertyName})`;
1617

17-
type ResponsiveVariables<T> = {
18+
type ResponsiveCSSCustomProperties = {
19+
[Breakpoint in `${string}-${BreakpointsAlias}`]?: PolarisCSSVar;
20+
};
21+
22+
type ResponsiveValues<T> = {
1823
[Breakpoint in `${string}-${BreakpointsAlias}`]?: T;
1924
};
2025

@@ -36,6 +41,41 @@ export function sanitizeCustomProperties(
3641
return nonNullValues.length ? Object.fromEntries(nonNullValues) : undefined;
3742
}
3843

44+
export function createPolarisCSSVar<T extends string | number = string>(
45+
tokenSubgroup: string,
46+
tokenValue: T,
47+
): PolarisCSSVar {
48+
// For backwards compatibility with `Grid` and `Grid.Cell`, accept already
49+
// formed var()'s using either polaris or polaris component custom properties.
50+
if (typeof tokenValue === 'string' && tokenValue.startsWith('var(')) {
51+
if (
52+
!tokenValue.startsWith(`var(--p-${tokenSubgroup}-`) &&
53+
!tokenValue.startsWith(`var(--pc-${tokenSubgroup}-`)
54+
) {
55+
throw new Error(
56+
`"${tokenValue}" is not from the ${tokenSubgroup} token group.`,
57+
);
58+
}
59+
60+
return tokenValue as PolarisCSSVar;
61+
}
62+
63+
// NOTE: All our token values today are either strings or numbers, so
64+
// stringifying them here works. But if we ever have anything more complex
65+
// (such as an object) this may generate invalid token names.
66+
return `var(--p-${tokenSubgroup}-${tokenValue})`;
67+
}
68+
69+
export function createPolarisCSSCustomProperty(
70+
componentName: string,
71+
componentProp: string,
72+
breakpointAlias?: BreakpointsAlias,
73+
): PolarisCSSCustomPropertyName {
74+
return `--pc-${componentName}-${componentProp}${
75+
breakpointAlias ? `-${breakpointAlias}` : ''
76+
}`;
77+
}
78+
3979
/**
4080
* Given params like so:
4181
* (
@@ -53,59 +93,88 @@ export function sanitizeCustomProperties(
5393
* '--pc-button-padding-lg': 'var(--p-spacing-6)'
5494
* }
5595
*
96+
* NOTE: Also supports legacy / deprecated values which are a complete CSS
97+
* variable declaration (primarily for `Grid`):
98+
* (
99+
* 'grid',
100+
* 'gap',
101+
* 'spacing',
102+
* {
103+
* sm: "var(--p-spacing-4)",
104+
* lg: "var(--p-spacing-6)"
105+
* }
106+
* )
107+
*
56108
*/
57-
export function getResponsiveProps<T = string>(
109+
export function getResponsiveProps<T extends string | number = string>(
58110
componentName: string,
59111
componentProp: string,
60112
tokenSubgroup: string,
61113
responsiveProp?: ResponsiveProp<T>,
62-
): ResponsiveVariables<T> {
63-
if (!responsiveProp) return {};
64-
65-
let result: ResponsivePropConfig;
66-
67-
if (!isObject(responsiveProp)) {
68-
result = {
69-
[breakpointsAliases[0]]: `var(--p-${tokenSubgroup}-${responsiveProp})`,
70-
};
71-
} else {
72-
result = Object.fromEntries(
73-
Object.entries(responsiveProp).map(([breakpointAlias, aliasOrScale]) => [
74-
breakpointAlias,
75-
`var(--p-${tokenSubgroup}-${aliasOrScale})`,
114+
): ResponsiveCSSCustomProperties {
115+
// "falsey" values are valid except `null` or `undefined`
116+
if (responsiveProp == null) return {};
117+
118+
if (isObject(responsiveProp)) {
119+
return Object.fromEntries(
120+
(
121+
Object.entries(responsiveProp).filter(
122+
([, aliasOrScale]) => aliasOrScale != null,
123+
// Use 'Required' here because .filter() doesn't type narrow
124+
) as Entries<Required<typeof responsiveProp>>
125+
).map(([breakpointAlias, aliasOrScale]) => [
126+
createPolarisCSSCustomProperty(
127+
componentName,
128+
componentProp,
129+
breakpointAlias,
130+
),
131+
createPolarisCSSVar(tokenSubgroup, aliasOrScale),
76132
]),
77133
);
78134
}
79135

80-
// Prefix each responsive key with the correct token name
81-
return Object.fromEntries(
82-
Object.entries(result).map(([breakpointAlias, value]) => [
83-
`--pc-${componentName}-${componentProp}-${breakpointAlias}`,
84-
value,
85-
]),
86-
) as unknown as ResponsiveVariables<T>;
136+
return {
137+
[createPolarisCSSCustomProperty(
138+
componentName,
139+
componentProp,
140+
breakpointsAliases[0],
141+
)]: createPolarisCSSVar(tokenSubgroup, responsiveProp as T),
142+
};
87143
}
88144

89-
export function getResponsiveValue<T = string>(
145+
export function getResponsiveValue<T extends string | number = string>(
90146
componentName: string,
91147
componentProp: string,
92-
responsiveProp?: ResponsiveValue<T>,
93-
): ResponsiveVariables<T> {
94-
if (!responsiveProp) return {};
95-
96-
if (!isObject(responsiveProp)) {
97-
return {
98-
[`--pc-${componentName}-${componentProp}-${breakpointsAliases[0]}`]:
99-
responsiveProp,
100-
} as ResponsiveVariables<T>;
148+
responsiveProp?: ResponsiveProp<T>,
149+
): ResponsiveValues<T> {
150+
// "falsey" values are valid except `null` or `undefined`
151+
if (responsiveProp == null) return {};
152+
153+
if (isObject(responsiveProp)) {
154+
return Object.fromEntries(
155+
(
156+
Object.entries(responsiveProp).filter(
157+
([, responsiveValue]) => responsiveValue != null,
158+
// Use 'Required' here because .filter() doesn't type narrow
159+
) as Entries<Required<typeof responsiveProp>>
160+
).map(([breakpointAlias, responsiveValue]) => [
161+
createPolarisCSSCustomProperty(
162+
componentName,
163+
componentProp,
164+
breakpointAlias,
165+
),
166+
responsiveValue,
167+
]),
168+
);
101169
}
102170

103-
return Object.fromEntries(
104-
Object.entries(responsiveProp).map(([breakpointAlias, responsiveValue]) => [
105-
`--pc-${componentName}-${componentProp}-${breakpointAlias}`,
106-
responsiveValue,
107-
]),
108-
);
171+
return {
172+
[createPolarisCSSCustomProperty(
173+
componentName,
174+
componentProp,
175+
breakpointsAliases[0],
176+
)]: responsiveProp as T,
177+
};
109178
}
110179

111180
export function mapResponsivePropValues<Input, Output>(

0 commit comments

Comments
 (0)