Skip to content

Commit

Permalink
Add Spectrum 2 docs to storybook
Browse files Browse the repository at this point in the history
  • Loading branch information
devongovett committed Jul 29, 2024
1 parent 82adcfc commit 41be55b
Show file tree
Hide file tree
Showing 20 changed files with 579 additions and 13 deletions.
3 changes: 2 additions & 1 deletion .storybook-s2/.parcelrc
Original file line number Diff line number Diff line change
Expand Up @@ -6,12 +6,13 @@
"packages/@react-spectrum/s2/s2wf-icons/*.svg": ["parcel-transformer-icon"],
"packages/*/*/intl/*.json": ["parcel-transformer-intl"],
// Disable PostCSS from running over style macro output
"packages/@react-spectrum/s2/**/*.css": ["@parcel/transformer-css"],
"*.css": ["@parcel/transformer-css"],
"*.{js,mjs,jsm,jsx,es6,cjs,ts,tsx}": [
"@parcel/transformer-js",
"@parcel/transformer-react-refresh-wrap"
],
"*.svg": ["@parcel/transformer-svg-react"],
"*.{mdx,md}": ["parcel-transformer-mdx-storybook"],
"raw:*": ["@parcel/transformer-raw"]
}
}
3 changes: 0 additions & 3 deletions .storybook-s2/Intro.mdx

This file was deleted.

35 changes: 35 additions & 0 deletions .storybook-s2/docs/Icons.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
import icons from '@react-spectrum/s2/s2wf-icons/*.svg';
import { style } from '../../packages/@react-spectrum/s2/style/spectrum-theme' with {type: 'macro'};
import {ActionButton, Text} from '@react-spectrum/s2';
import {P, Code, Pre} from './typography';
import {highlight} from './highlight' with {type: 'macro'};

export function Icons() {
return (
<div className={style({maxWidth: 'lg', marginX: 'auto', fontFamily: 'sans'})}>
<div className={style({marginX: 48})}>
<h1 className={style({fontSize: 'heading-2xl', color: 'heading', marginBottom: 48})}>
Workflow icons
</h1>
<P>Spectrum 2 offers a subset of the icons currently available in React Spectrum v3. These icons can be imported from <Code>@react-spectrum/s2/icons</Code>.</P>
<Pre>{highlight("import Add from '@react-spectrum/s2/icons/Add';")}</Pre>
<P>See below for a full list of available icons. Click to copy import statement.</P>
<div className={style({display: 'flex', flexWrap: 'wrap', gap: 4, marginTop: 48})}>
{Object.keys(icons).map(icon => {
let Icon = icons[icon].default;
let name = icon.replace(/^S2_Icon_(.*?)_2.*/, '$1');
let importName = name.replace(/^(\d)/, '_$1');
return (
<ActionButton
key={icon}
onPress={() => navigator.clipboard.writeText(`import ${importName} from '@react-spectrum/s2/icons/${name}';`)}>
<Icon />
<Text>{name}</Text>
</ActionButton>
);
})}
</div>
</div>
</div>
);
}
269 changes: 269 additions & 0 deletions .storybook-s2/docs/Intro.jsx

Large diffs are not rendered by default.

3 changes: 3 additions & 0 deletions .storybook-s2/docs/Intro.mdx
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
import {Docs} from './Intro.jsx';

<Docs />
3 changes: 3 additions & 0 deletions .storybook-s2/docs/Style Macro.mdx
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
import {StyleMacro} from './StyleMacro.jsx';

<StyleMacro />
132 changes: 132 additions & 0 deletions .storybook-s2/docs/StyleMacro.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,132 @@
import { style } from '../../packages/@react-spectrum/s2/style/spectrum-theme' with {type: 'macro'};
import {Link, LinkButton} from '@react-spectrum/s2';
import {highlight} from './highlight' with {type: 'macro'};
import {H2, H3, H3, P, Pre, Code} from './typography';

export function StyleMacro() {
return (
<div className={style({maxWidth: 'lg', marginX: 'auto', fontFamily: 'sans', fontSize: 'body-lg'})}>
<header
className={style({
paddingX: 48,
marginBottom: 48
})}>
<h1 className={style({fontSize: 'heading-2xl', color: 'heading'})}>
Style Macro
</h1>
</header>
<main className={style({marginX: 48})}>
<P>The React Spectrum <Code>style</Code> <Link href="https://parceljs.org/features/macros/" target="_blank">macro</Link> generates atomic CSS at build time, which can be applied to any DOM element or Spectrum component. Style properties use Spectrum tokens such as colors, spacing, sizing, and typography, helping you work more quickly with TypeScript autocomplete, reduce the design choices you need to make, and improve consistency between Adobe applications.</P>
<Pre>{highlight(`import {style} from '@react-spectrum/s2/style' with {type: 'macro'};
<div className={style({backgroundColor: 'red-400', color: 'white'})}>
{/* ... */}
</div>`)}</Pre>
<P><strong>Atomic CSS</strong> scales as your application grows because it outputs a separate rule for each CSS property you write, ensuring there is no duplication across your whole app. The above example generates two CSS rules:</P>
<Pre>{highlight(`.bJ { background-color: #ffbcb4 }
.ac { color: #fff }`, 'CSS')}</Pre>
<P>These rules are reused across your app wherever the same values are used, which keeps your bundle size small even as you add features. In addition, you only pay for the values you use – there’s no unnecessary CSS custom properties for colors and other tokens that aren’t used.</P>
<P>The <Code>style</Code> macro <strong>colocates</strong> your styles with your component code, rather than in separate CSS files. Colocation enables you to:</P>
<ul className="sb-unstyled">
<li className={style({lineHeight: 'body', color: 'body', marginTop: 0, marginBottom: 8})}><strong>Develop more efficiently</strong> – No need to switch between multiple files when working on a component, or spend time writing CSS selectors.</li>
<li className={style({lineHeight: 'body', color: 'body', marginY: 0})}><strong>Refactor with confidence</strong> – Changing the styles in a component is guaranteed to never unintentionally affect any other parts of your application. When you delete a component, the corresponding styles are also removed, reducing technical debt.</li>
</ul>
<H2>Values</H2>
<P>The <Code>style</Code> macro supports a constrained set of values for each CSS property, which conform to the Spectrum design system. For example, the <Code>backgroundColor</Code> property supports Spectrum colors, and does not allow arbitrary hex or rgb values by default. This helps make it easier to build consistent UIs that are maintainable over time.</P>
<H3>Colors</H3>
<P>The Spectrum 2 color palette is available across all color properties. The <Code>backgroundColor</Code> property also supports Spectrum’s background layers. In addition, semantic colors such as <Code>accent</Code> and <Code>negative</Code> are available across all properties, and automatically update according to states such as <Code>isHovered</Code> (see <Link href="#runtime-conditions" target="_self">runtime conditions</Link> below).</P>
<H3>Spacing and sizing</H3>
<P>Spacing properties such as <Code>margin</Code> and <Code>padding</Code>, and sizing properties such as <Code>width</Code> and <Code>height</Code> support a limited set of values. The API is represented in pixels, however, only values conforming to a 4px grid are allowed. This helps ensure that spacing and sizing are visually consistent.</P>
<P>Internally, spacing and sizing values are converted from pixels to rems, which scale according to the user’s font size preference. In addition, sizing values are multiplied by 1.25x on touch screen devices to help increase the size of hit targets. Spacing values do not scale and remain fixed.</P>
<H2>Conditional styles</H2>
<P>The <Code>style</Code> macro also supports conditional styles, such as media queries, UI states such as hover and press, and component variants. Conditional values are defined as an object where each key is a condition. This keeps all values for each property together in one place so it is easy to see where overrides are coming from.</P>
<P>This example sets the padding of a div to 8px by default, and 32px at the large media query breakpoint (1024px) defined by Spectrum.</P>
<Pre>{highlight(`<div
className={style({
padding: {
default: 8,
lg: 32
}
})} />`)}</Pre>
<P>Conditions are mutually exclusive, following object property order. The <Code>style</Code> macro uses <Link href="https://developer.mozilla.org/en-US/docs/Learn/CSS/Building_blocks/Cascade_layers" target="_blank">CSS cascade layers</Link> to ensure that there are no specificity issues to worry about. The last matching condition always wins.</P>
<H3>Runtime conditions</H3>
<P>The <Code>style</Code> macro also supports conditions that are resolved in JavaScript at runtime, such as variant props and UI states. When a runtime condition is detected, the <Code>style</Code> macro returns a function that can be called at runtime to determine which styles to apply.</P>
<P>Runtime conditions can be named however you like, and values are defined as an object. This example changes the background color depending on a <Code>variant</Code> prop:</P>
<Pre>{highlight(`let styles = style({
backgroundColor: {
variant: {
primary: 'accent',
secondary: 'neutral'
}
}
});
function MyComponent({variant}) {
return <div className={styles({variant})} />
}`)}</Pre>
<P>Boolean conditions starting with <Code>is</Code> do not need to be nested in an object:</P>
<Pre>{highlight(`let styles = style({
backgroundColor: {
default: 'gray-100',
isSelected: 'gray-900'
}
});
<div className={styles({isSelected: true})} />`)}</Pre>
<P>Runtime conditions also work well with the <Link href="https://react-spectrum.adobe.com/react-aria/styling.html#render-props" target="_blank">render props</Link> in React Aria Components. If you define your styles inline, you’ll even get autocomplete for all of the available conditions.</P>
<Pre>{highlight(`import {Checkbox} from 'react-aria-components';
<Checkbox
className={style({
backgroundColor: {
default: 'gray-100',
isHovered: 'gray-200',
isSelected: 'gray-900'
}
})} />`)}</Pre>
<H3>Nesting conditions</H3>
<P>Conditions can be nested to apply styles when multiple conditions are true. Keep in mind that conditions at the same level are mutually exclusive, with the last matching condition winning. Since only one value can apply at a time, there are no specificity issues to worry about.</P>
<Pre>{highlight(`let styles = style({
backgroundColor: {
default: 'gray-25',
isSelected: {
default: 'neutral',
isEmphasized: 'accent',
forcedColors: 'Highlight',
isDisabled: {
default: 'gray-400',
forcedColors: 'GrayText'
}
}
}
});
<div className={styles({isSelected, isEmphasized, isDisabled})} />`)}</Pre>
<P>The above example has three runtime conditions (<Code>isSelected</Code>, <Code>isEmphasized</Code>, and <Code>isDisabled</Code>), and uses the <Code>forcedColors</Code> condition to apply styles for <Link href="https://developer.mozilla.org/en-US/docs/Web/CSS/@media/forced-colors" target="_blank">Windows High Contrast Mode</Link> (WHCM). The order of precedence follows the order the conditions are defined in the object, with the <Code>isSelected</Code> + <Code>isDisabled</Code> + <Code>forcedColors</Code> state having the highest priority.</P>
<H2>Reusing styles</H2>
<P>Styles can be reused by extracting common properties into objects, and spreading them into <Code>style</Code> calls. These must either be constants (declared with <Code>const</Code>) in the same file, or imported from another file as a macro (<Code>{"with {type: 'macro'}"}</Code>). Properties can be overridden just like normal JS objects – the last value always wins.</P>
<Pre>{highlight(`const horizontalStack = {
display: 'flex',
alignItems: 'center',
columnGap: 8
};
const styles = style({
...horizontalStack,
columnGap: 4
});`)}</Pre>
<P>You can also create custom utilities by defining your own macros. These are normal functions so you can do whatever computations you like to generate styles.</P>
<Pre>{highlight(`// style-utils.ts
export function horizontalStack(gap: number) {
return {
display: 'flex',
alignItems: 'center',
columnGap: gap
};
}`)}</Pre>
<P>Then, import your macro and use it in a component.</P>
<Pre>{highlight(`// component.tsx
import {horizontalStack} from './style-utils' with {type: 'macro'};
const styles = style({
...horizontalStack(4),
backgroundColor: 'base'
});`)}</Pre>
</main>
</div>
)
}
3 changes: 3 additions & 0 deletions .storybook-s2/docs/Workflow Icons.mdx
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
import {Icons} from './Icons.jsx';

<Icons />
38 changes: 38 additions & 0 deletions .storybook-s2/docs/highlight.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
const treeSitter = require('tree-sitter-highlight');
const tokens = require('@adobe/spectrum-tokens/dist/json/variables.json');

function colorToken(name) {
let token = tokens[name];
return `light-dark(${token.sets.light.value}, ${token.sets.dark.value})`;
}

exports.highlight = function(code, lang = 'JSX') {
this.addAsset({
type: 'css',
content: `
.keyword {
color: ${colorToken('magenta-1000')}
}
.string {
color: ${colorToken('green-1000')}
}
.number {
color: ${colorToken('pink-1000')}
}
.property, .attribute {
color: ${colorToken('indigo-1000')}
}
.function, .tag, .constructor {
color: ${colorToken('red-1000')}
}
.comment {
color: ${colorToken('gray-700')}
}
.variable {
color: ${colorToken('fuchsia-1000')}
}
`
});

return treeSitter.highlight(code, treeSitter.Language[lang]);
}
37 changes: 37 additions & 0 deletions .storybook-s2/docs/typography.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
import { style } from '../../packages/@react-spectrum/s2/style/spectrum-theme' with {type: 'macro'};
import {Link} from '@react-spectrum/s2';

export function H2({children}) {
let id = anchorId(children);
return <h2 className={style({fontSize: 'heading-xl', lineHeight: 'heading', color: 'heading', marginTop: 48, marginBottom: 24})} id={id}>{children}</h2>
}

export function H3({children}) {
let id = anchorId(children);
return <h3 className={style({fontSize: 'heading', lineHeight: 'heading', color: 'heading', marginTop: 32, marginBottom: 16})} id={anchorId(children)}>{children}</h3>
}

export function H4({children}) {
let id = anchorId(children);
return <h4 className={style({fontSize: 'heading-sm', lineHeight: 'heading', color: 'heading', marginTop: 32, marginBottom: 8})} id={anchorId(children)}>{children}</h4>
}

export function P({children}) {
return <p className={style({fontSize: 'body-lg', lineHeight: 'body', color: 'body', marginTop: 0, marginBottom: 24})}>{children}</p>
}

export function Code({children}) {
return <code className={style({fontFamily: 'code', fontSize: 'code-sm', backgroundColor: 'layer-1', paddingX: 4, borderWidth: 1, borderColor: 'gray-100', borderStyle: 'solid', borderRadius: 'sm'})}>{children}</code>;
}

export function Pre({children}) {
return (
<pre className={'sb-unstyled ' + style({padding: 32, marginY: 32, backgroundColor: 'layer-1', borderRadius: 'xl', fontFamily: 'code', fontSize: 'code-sm', lineHeight: 'code'})}>
<code dangerouslySetInnerHTML={{__html: children}} />
</pre>
);
}

function anchorId(children) {
return children.replace(/\s/g, '-').replace(/[^a-zA-Z0-9-_]/g, '').toLowerCase();
}
Binary file not shown.
2 changes: 1 addition & 1 deletion .storybook-s2/main.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ import type { StorybookConfig } from "@storybook/types";

const config: StorybookConfig = {
stories: [
// './Intro.mdx',
'./docs/*.mdx',
"../packages/@react-spectrum/s2/stories/*.stories.@(js|jsx|mjs|ts|tsx)",
],
addons: [
Expand Down
9 changes: 9 additions & 0 deletions .storybook-s2/preview-head.html
Original file line number Diff line number Diff line change
Expand Up @@ -13,3 +13,12 @@
h=d.documentElement,t=setTimeout(function(){h.className=h.className.replace(/\bwf-loading\b/g,"")+" wf-inactive";},config.scriptTimeout),tk=d.createElement("script"),f=false,s=d.getElementsByTagName("script")[0],a;h.className+=" wf-loading";tk.src='https://use.typekit.net/'+config.kitId+'.js';tk.async=true;tk.onload=tk.onreadystatechange=function(){a=this.readyState;if(f||a&&a!="complete"&&a!="loaded")return;f=true;clearTimeout(t);try{Typekit.load(config)}catch(e){}};s.parentNode.insertBefore(tk,s)
})(document);
</script>
<style>
@font-face {
font-family: "Source Code Pro";
src: url("https://use.typekit.net/af/398a64/00000000000000007735dc06/30/l?primer=7cdcb44be4a7db8877ffa5c0007b8dd865b3bbc383831fe2ea177f62257a9191&fvd=n4&v=3") format("woff2"),url("https://use.typekit.net/af/398a64/00000000000000007735dc06/30/d?primer=7cdcb44be4a7db8877ffa5c0007b8dd865b3bbc383831fe2ea177f62257a9191&fvd=n4&v=3") format("woff"),url("https://use.typekit.net/af/398a64/00000000000000007735dc06/30/a?primer=7cdcb44be4a7db8877ffa5c0007b8dd865b3bbc383831fe2ea177f62257a9191&fvd=n4&v=3") format("opentype");
font-display: swap;
font-style: normal;
font-weight: 400;
}
</style>
3 changes: 2 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -250,7 +250,8 @@
"packages/@react-spectrum/style-macro-s1/**",
"packages/@react-spectrum/tree/**",
"packages/@react-spectrum/color/src/*.tsx",
"packages/@react-spectrum/s2/**"
"packages/@react-spectrum/s2/**",
".storybook-s2/**"
]
},
"drafts": {
Expand Down
4 changes: 2 additions & 2 deletions packages/@react-spectrum/s2/spectrum-illustrations/Cloud.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading

0 comments on commit 41be55b

Please sign in to comment.