Skip to content

Commit 28eb788

Browse files
committed
initial stab at extracting types from style macro
1 parent 8a1f97e commit 28eb788

File tree

5 files changed

+234
-5
lines changed

5 files changed

+234
-5
lines changed

packages/@react-spectrum/s2/style/index.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ import {Inset, fontRelative as internalFontRelative, size as internalSize, space
1515
import type {MacroContext} from '@parcel/macros';
1616
import {StyleString} from './types';
1717

18-
export {baseColor, color, edgeToText, lightDark, linearGradient, colorMix, style} from './spectrum-theme';
18+
export {baseColor, color, edgeToText, lightDark, linearGradient, colorMix, style, themeConfig} from './spectrum-theme';
1919
export type {StyleString} from './types';
2020

2121
// Wrap these functions in arbitrary value syntax when called from the outside.
@@ -81,7 +81,7 @@ const iconSizes = {
8181

8282
export function iconStyle(this: MacroContext | void, options: IconStyle): StyleString<Exclude<keyof IconStyle, 'color' | 'size'>> {
8383
let {size = 'M', color, ...styles} = options;
84-
84+
8585
if (color) {
8686
styles['--iconPrimary'] = {
8787
type: 'fill',

packages/@react-spectrum/s2/style/spectrum-theme.ts

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -535,7 +535,7 @@ const minFontSize = 10;
535535
const maxFontSize = 32;
536536
const lineHeightCalc = `round(1em * (${minFontScale} + (1 - ((min(${maxFontSize}, ${fontSizeCalc}) - ${minFontSize})) / ${maxFontSize - minFontSize}) * ${(maxFontScale - minFontScale).toFixed(2)}), 2px)`;
537537

538-
export const style = createTheme({
538+
export const themeConfig = {
539539
properties: {
540540
// colors
541541
color: new SpectrumColorProperty('color', {
@@ -1044,4 +1044,6 @@ export const style = createTheme({
10441044
xl: `@media (min-width: ${pxToRem(1280)})`,
10451045
'2xl': `@media (min-width: ${pxToRem(1536)})`
10461046
}
1047-
});
1047+
} as const;
1048+
1049+
export const style = createTheme(themeConfig);
Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,76 @@
1+
import {Layout} from '../../src/Layout';
2+
import {InlineAlert, Heading, Content, Link} from '@react-spectrum/s2';
3+
import {S2Colors} from '../../src/S2Colors';
4+
import {S2Typography} from '../../src/S2Typography';
5+
import {StyleMacroProperties} from '../../src/types';
6+
import {getPropertyDefinitions} from '../../src/styleProperties';
7+
export default Layout;
8+
9+
export const section = 'Guides';
10+
export const tags = ['style', 'macro', 'spectrum', 'custom', 'values', 'reference'];
11+
export const description = 'Reference table for the `style` macro';
12+
13+
# Style Macro Reference
14+
15+
Reference table for the properties and values supported by React Spectrum `style` macro.
16+
17+
## Colors
18+
19+
All Spectrum 2 color tokens are available across color properties (e.g., `backgroundColor`, `color`, `borderColor`).
20+
21+
<S2Colors />
22+
23+
## Spacing
24+
25+
Spacing props like `margin` and `padding` accept values on a **4px grid**. These are specified in `px` and get converted to `rem`. In addition to numbers, these named options are available:
26+
27+
- `edge-to-text` – default spacing between the edge of a control and its text. Relative to control height.
28+
- `pill` – default spacing between the edge of a pill-shaped control and its text. Relative to control height.
29+
- `text-to-control` – default spacing between text and a control (e.g., label and input). Scales with font size.
30+
- `text-to-visual` – default spacing between text and a visual element (e.g., icon). Scales with font size.
31+
32+
## Sizing
33+
34+
Size props like `width` and `height` accept arbitrary pixel values. Values are converted to `rem` and multiplied by 1.25x on touch devices to increase hit targets.
35+
36+
### top
37+
38+
<StyleMacroProperties properties={getPropertyDefinitions(['top'])} />
39+
40+
## Text
41+
42+
// TODO: edit copy
43+
Spectrum 2 typography is applied via the `font` shorthand, which sets `fontFamily`, `fontSize`, `fontWeight`, `lineHeight`, and `color`. You can override any of these individually.
44+
45+
```tsx
46+
<main>
47+
<h1 className={style({font: 'heading-xl'})}>Heading</h1>
48+
<p className={style({font: 'body'})}>Body</p>
49+
<ul className={style({font: 'body-sm', fontWeight: 'bold'})}>
50+
<li>List item</li>
51+
</ul>
52+
</main>
53+
```
54+
55+
Type scales include: UI, Body, Heading, Title, Detail, and Code. Each scale has a default and additional t-shirt sizes (e.g., `ui-sm`, `heading-2xl`, `code-xl`).
56+
57+
<S2Typography />
58+
59+
60+
## Effects
61+
62+
63+
## Layout
64+
65+
### display
66+
67+
<StyleMacroProperties properties={getPropertyDefinitions(['display'])} />
68+
69+
70+
## Misc
71+
72+
73+
## Shorthands
74+
75+
76+
## Conditions
Lines changed: 83 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,83 @@
1+
/*
2+
* Copyright 2025 Adobe. All rights reserved.
3+
* This file is licensed to you under the Apache License, Version 2.0 (the "License");
4+
* you may not use this file except in compliance with the License. You may obtain a copy
5+
* of the License at http://www.apache.org/licenses/LICENSE-2.0
6+
*
7+
* Unless required by applicable law or agreed to in writing, software distributed under
8+
* the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS
9+
* OF ANY KIND, either express or implied. See the License for the specific language
10+
* governing permissions and limitations under the License.
11+
*/
12+
13+
interface StyleMacroPropertyDefinition {
14+
type: 'mapped' | 'percentage' | 'sizing' | 'arbitrary',
15+
values: string[],
16+
additionalTypes?: string[]
17+
}
18+
19+
// Properties that use PercentageProperty (accept LengthPercentage in addition to mapped values)
20+
const percentageProperties = new Set([
21+
'top', 'left', 'bottom', 'right',
22+
'insetStart', 'insetEnd',
23+
'marginTop', 'marginBottom', 'marginStart', 'marginEnd',
24+
'paddingTop', 'paddingBottom', 'paddingStart', 'paddingEnd',
25+
'textIndent', 'translateX', 'translateY'
26+
]);
27+
28+
// Properties that use SizingProperty (accept number and LengthPercentage in addition to mapped values)
29+
const sizingProperties = new Set([
30+
'width', 'height', 'minWidth', 'minHeight', 'maxWidth', 'maxHeight',
31+
'flexBasis', 'containIntrinsicWidth', 'containIntrinsicHeight'
32+
]);
33+
34+
// Manually defined property values from spectrum-theme.ts
35+
// These are extracted from the theme definition
36+
const propertyValues: {[key: string]: string[]} = {
37+
display: ['block', 'inline-block', 'inline', 'flex', 'inline-flex', 'grid', 'inline-grid', 'contents', 'list-item', 'none'],
38+
top: ['0', '2', '4', '8', '12', '16', '20', '24', '28', '32', '36', '40', '44', '48', '56', '64', '80', '96', '-2', '-4', '-8', '-12', '-16', '-20', '-24', '-28', '-32', '-36', '-40', '-44', '-48', '-56', '-64', '-80', '-96', 'auto', 'full'],
39+
position: ['absolute', 'fixed', 'relative', 'sticky', 'static'],
40+
width: ['auto', 'full', 'min', 'max', 'fit', 'screen'],
41+
height: ['auto', 'full', 'min', 'max', 'fit', 'screen'],
42+
minWidth: ['auto', 'full', 'min', 'max', 'fit', 'screen'],
43+
minHeight: ['auto', 'full', 'min', 'max', 'fit', 'screen'],
44+
maxWidth: ['auto', 'full', 'min', 'max', 'fit', 'screen', 'none'],
45+
maxHeight: ['auto', 'full', 'min', 'max', 'fit', 'screen', 'none'],
46+
flexDirection: ['row', 'column', 'row-reverse', 'column-reverse'],
47+
justifyContent: ['normal', 'start', 'end', 'center', 'space-between', 'space-around', 'space-evenly', 'stretch'],
48+
alignItems: ['start', 'end', 'center', 'baseline', 'stretch'],
49+
gap: ['0', '2', '4', '8', '12', '16', '20', '24', '28', '32', '36', '40', '44', '48', '56', '64', '80', '96', 'text-to-control', 'text-to-visual', 'edge-to-text', 'pill']
50+
};
51+
52+
export function getPropertyDefinition(propertyName: string): StyleMacroPropertyDefinition {
53+
const values = propertyValues[propertyName] || [];
54+
55+
if (sizingProperties.has(propertyName)) {
56+
return {
57+
type: 'sizing',
58+
values,
59+
additionalTypes: ['number', 'LengthPercentage']
60+
};
61+
}
62+
63+
if (percentageProperties.has(propertyName)) {
64+
return {
65+
type: 'percentage',
66+
values,
67+
additionalTypes: ['LengthPercentage']
68+
};
69+
}
70+
71+
return {
72+
type: 'mapped',
73+
values
74+
};
75+
}
76+
77+
export function getPropertyDefinitions(propertyNames: string[]): {[key: string]: StyleMacroPropertyDefinition} {
78+
const result: {[key: string]: StyleMacroPropertyDefinition} = {};
79+
for (const name of propertyNames) {
80+
result[name] = getPropertyDefinition(name);
81+
}
82+
return result;
83+
}

packages/dev/s2-docs/src/types.tsx

Lines changed: 69 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -47,7 +47,7 @@ export type TKeyof = {type: 'keyof', keyof: TType};
4747
export type TLink = {type: 'link', id: string};
4848
export type TReference = {type: 'reference', local: string, imported: string, specifier: string};
4949
export type TMapped = {type: 'mapped', readonly: boolean | '+' | '-', typeParameter: TTypeParameter, typeAnnotation: TType};
50-
export type TType =
50+
export type TType =
5151
| TKeyword
5252
| TIdentifier
5353
| TString
@@ -694,3 +694,71 @@ function TemplateLiteral({elements}: TTemplate) {
694694
</>
695695
);
696696
}
697+
698+
interface StyleMacroPropertyDefinition {
699+
type: 'mapped' | 'percentage' | 'sizing' | 'arbitrary',
700+
values: string[],
701+
additionalTypes?: string[]
702+
}
703+
704+
interface StyleMacroPropertiesProps {
705+
properties: {[propertyName: string]: StyleMacroPropertyDefinition}
706+
}
707+
708+
export function StyleMacroProperties({properties}: StyleMacroPropertiesProps) {
709+
let propertyNames = Object.keys(properties);
710+
711+
return (
712+
<Table>
713+
<TableHeader>
714+
<tr>
715+
<TableColumn>Property</TableColumn>
716+
<TableColumn>Values</TableColumn>
717+
</tr>
718+
</TableHeader>
719+
<TableBody>
720+
{propertyNames.map((propertyName, index) => {
721+
let propDef = properties[propertyName];
722+
let values = propDef.values;
723+
724+
return (
725+
<TableRow key={index}>
726+
<TableCell role="rowheader">
727+
<code className={codeStyle}>
728+
<span className={codeStyles.attribute}>{propertyName}</span>
729+
</code>
730+
</TableCell>
731+
<TableCell>
732+
<code className={codeStyle}>
733+
{values.map((value, i) => (
734+
<React.Fragment key={i}>
735+
{i > 0 && <Punctuation>{' | '}</Punctuation>}
736+
<span className={codeStyles.string}>'{value}'</span>
737+
</React.Fragment>
738+
))}
739+
{propDef.additionalTypes && propDef.additionalTypes.map((typeName, i) => (
740+
<React.Fragment key={`type-${i}`}>
741+
<Punctuation>{' | '}</Punctuation>
742+
<TypePopover name={typeName}>
743+
{typeName === 'LengthPercentage' && (
744+
<p className={style({font: 'body'})}>
745+
A CSS length value with percentage or viewport units. Examples: <code className={codeStyle}>'50%'</code>, <code className={codeStyle}>'100vw'</code>, <code className={codeStyle}>'50vh'</code>
746+
</p>
747+
)}
748+
{typeName === 'number' && (
749+
<p className={style({font: 'body'})}>
750+
A numeric value in pixels. Will be converted to rem and scaled on touch devices.
751+
</p>
752+
)}
753+
</TypePopover>
754+
</React.Fragment>
755+
))}
756+
</code>
757+
</TableCell>
758+
</TableRow>
759+
);
760+
})}
761+
</TableBody>
762+
</Table>
763+
);
764+
}

0 commit comments

Comments
 (0)