Skip to content

Commit 1775ff2

Browse files
authored
RAC + Spectrum + Tailwind example (#4819)
* initialize example * add to verdaccio * typo * temporarily enable build comment * temp remove main filter * add readme * update config * add more examples * improve default focus ring * simplify examples * split out into preset * add font weights * add letterSpacing and lineHeight * add transition timings and durations * improve sentiment example * improve select box example * improve select box example * improve examples * add sizes * fix size values * fix transition values * update break points * improve examples * add dropShadow to preset * add Theme switcher * dark mode fixes * clean up * fix spacing/sizing * spacing * cleanup * add default blue * add opacity * fix theme switcher margin * add rac plugin * update sentiment example to style w/ attributes * add plugin to config * cleanup attributes in plugin * cleanup plugin * add more color variants * formatting * update README * cleanup * use custom selector for dark mode * revert CI changes * cleanup
1 parent 66f9638 commit 1775ff2

File tree

15 files changed

+872
-0
lines changed

15 files changed

+872
-0
lines changed

.circleci/comment.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,7 @@ async function run() {
5151
[CRA Test App](https://reactspectrum.blob.core.windows.net/reactspectrum/${process.env.CIRCLE_SHA1}/verdaccio/build/index.html)
5252
[NextJS Test App](https://reactspectrum.blob.core.windows.net/reactspectrum/${process.env.CIRCLE_SHA1}/verdaccio/next/index.html)
5353
[RAC Tailwind Example](https://reactspectrum.blob.core.windows.net/reactspectrum/${process.env.CIRCLE_SHA1}/verdaccio/rac-tailwind/index.html)
54+
[RAC Spectrum + Tailwind Example](https://reactspectrum.blob.core.windows.net/reactspectrum/${process.env.CIRCLE_SHA1}/verdaccio/rac-tailwind/index.html)
5455
[CRA Test App Size](https://reactspectrum.blob.core.windows.net/reactspectrum/${process.env.CIRCLE_SHA1}/verdaccio/publish-stats/build-stats.txt)
5556
[NextJS App Size](https://reactspectrum.blob.core.windows.net/reactspectrum/${process.env.CIRCLE_SHA1}/verdaccio/publish-stats/next-build-stats.txt)
5657
[Publish stats](https://reactspectrum.blob.core.windows.net/reactspectrum/${process.env.CIRCLE_SHA1}/verdaccio/publish-stats/publish.json)
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
{
2+
"extends": "@parcel/config-default",
3+
"resolvers": ["@parcel/resolver-glob", "..."],
4+
"transformers": {
5+
"../../packages/*/*/intl/*.json": ["parcel-transformer-intl"]
6+
}
7+
}
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
{
2+
"plugins": {
3+
"tailwindcss": {}
4+
}
5+
}
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
# React Aria Components + Spectrum + Tailwind CSS
2+
3+
A Tailwind CSS template for creating React Aria Components using common Spectrum styles.
4+
5+
## Overview
6+
7+
### Includes
8+
9+
- `Theme`: a preset that includes Spectrum values for colors, scale and more
10+
- `Plugin`: for easier styling based on React Aria Component's selectors for states
11+
12+
### When to use this
13+
14+
- You need a component that matches an ARIA pattern, but doesn't exist in React Spectrum
15+
- You are prototyping a component that might be a good fit for a future addition to React Spectrum, but isn't available yet
16+
17+
### When not to use this
18+
19+
- There is an existing React Spectrum component that matches your needs
20+
- You are trying to override Spectrum guidelines or create a slight variation of an existing React Spectrum component
21+
22+
### Note
23+
24+
This is not a library you can install and consume, but rather a template that you can copy into your own project and maintain yourself. Keep in mind that you are still responsible for ensuring the experiences you create are accessible and meet Spectrum guidelines.
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
{
2+
"name": "rac-spectrum-tailwind-example",
3+
"private": true,
4+
"scripts": {
5+
"start": "parcel src/index.html",
6+
"build": "parcel build src/index.html",
7+
"install-17": "yarn add -W react@^17 react-dom@^17"
8+
},
9+
"dependencies": {
10+
"@adobe/react-spectrum": "^3.28.0",
11+
"@spectrum-icons/illustrations": "^3.6.3",
12+
"@spectrum-icons/workflow": "^4.2.2",
13+
"parcel": "^2.9.1",
14+
"postcss": "^8.2.1",
15+
"react": "^18.2.0",
16+
"react-aria-components": "^1.0.0-alpha.4",
17+
"react-dom": "^18.2.0",
18+
"react-stately": "^3.23.0",
19+
"tailwindcss": "^3.3.0",
20+
"tailwindcss-animate": "^1.0.5"
21+
},
22+
"devDependencies": {
23+
"process": "^0.11.10"
24+
}
25+
}
Lines changed: 109 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,109 @@
1+
import { useState } from "react";
2+
import { defaultTheme, Provider } from "@adobe/react-spectrum";
3+
import { Label, Radio, RadioGroup } from "react-aria-components";
4+
import User from "@spectrum-icons/workflow/User";
5+
import UserGroup from "@spectrum-icons/workflow/UserGroup";
6+
import Building from "@spectrum-icons/workflow/Building";
7+
import CheckmarkCircle from "@spectrum-icons/workflow/CheckmarkCircle";
8+
import ThemeSwitcher from "./ThemeSwitcher";
9+
10+
export function App() {
11+
let [colorScheme, setColorScheme] = useState(undefined);
12+
13+
return (
14+
<Provider theme={defaultTheme} colorScheme={colorScheme}>
15+
<ThemeSwitcher setColorScheme={setColorScheme} />
16+
<div className="grid justify-center grid-cols-1 gap-160 auto-rows-fr">
17+
<SelectBoxExample />
18+
<SentimentRatingGroup />
19+
</div>
20+
</Provider>
21+
);
22+
}
23+
24+
function SelectBoxExample() {
25+
return (
26+
<RadioGroup
27+
className="flex flex-col space-y-2 text-center"
28+
defaultValue="Team"
29+
>
30+
<Label className="text-xl font-semibold">Select Boxes</Label>
31+
<div className="flex justify-center">
32+
<SelectBox
33+
name="Individual"
34+
icon={<User size="XL" />}
35+
description="For 1 person"
36+
/>
37+
<SelectBox
38+
name="Team"
39+
icon={<UserGroup size="XL" />}
40+
description="For teams of 9 or less"
41+
/>
42+
<SelectBox
43+
name="Enterprise"
44+
icon={<Building size="XL" />}
45+
description="For of 10 or more"
46+
/>
47+
</div>
48+
</RadioGroup>
49+
);
50+
}
51+
52+
function SelectBox({ name, icon, description }) {
53+
return (
54+
<Radio
55+
value={name}
56+
className={({ isFocusVisible, isSelected, isPressed }) => `
57+
flex justify-center p-160 m-160 h-2000 w-2000 focus:outline-none border rounded
58+
${isFocusVisible ? "ring-half ring-offset-0" : ""}
59+
${isSelected ? "bg-accent-100 border-accent-700" : ""}
60+
${isPressed && !isSelected ? "bg-gray-200" : ""}
61+
${!isSelected && !isPressed ? "bg-white dark:bg-black" : ""}
62+
`}
63+
>
64+
{({ isSelected }) => (
65+
<div className="relative flex flex-col items-center justify-center w-full h-full gap-150">
66+
{isSelected && (
67+
<div className="absolute top-0 left-0 text-accent-800 -mt-125 -ml-75">
68+
<CheckmarkCircle size="S" />
69+
</div>
70+
)}
71+
{icon && <div className="text-gray-500">{icon}</div>}
72+
<div>
73+
<div className={`font-semibold`}>{name}</div>
74+
{description && <div className="text-sm">{description}</div>}
75+
</div>
76+
</div>
77+
)}
78+
</Radio>
79+
);
80+
}
81+
82+
function SentimentRatingGroup() {
83+
let ratings = ["0", "1", "2", "3", "4", "5", "6", "7", "8", "9"];
84+
return (
85+
<RadioGroup className="flex flex-col m-auto space-y-10 text-center">
86+
<Label className="text-xl font-semibold">Sentiment Rating</Label>
87+
<div className="flex justify-between">
88+
<span>Least Likely</span>
89+
<span>Most Likely</span>
90+
</div>
91+
<div className="flex justify-evenly">
92+
{ratings.map((rating) => (
93+
<SentimentRating key={rating} rating={rating} />
94+
))}
95+
</div>
96+
</RadioGroup>
97+
);
98+
}
99+
100+
function SentimentRating({ rating }) {
101+
return (
102+
<Radio
103+
value={rating}
104+
className="flex items-center justify-center bg-white border rounded-full p-160 m-75 h-200 w-200 focus:outline-none focus-visible:ring dark:bg-black selected:bg-accent-800 dark:selected:bg-accent-800 selected:border-accent-800 selected:text-white pressed:bg-gray-200 dark:pressed:bg-gray-200 hovered:border-gray-300"
105+
>
106+
{rating}
107+
</Radio>
108+
);
109+
}
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
import { useProvider, ActionButton } from "@adobe/react-spectrum";
2+
import Moon from "@spectrum-icons/workflow/Moon";
3+
import Light from "@spectrum-icons/workflow/Light";
4+
5+
export default function ThemeSwitcher({ setColorScheme }) {
6+
let { colorScheme } = useProvider();
7+
let label =
8+
colorScheme === "dark" ? "Switch to light theme" : "Switch to dark theme";
9+
let otherScheme = colorScheme === "light" ? "dark" : "light";
10+
11+
return (
12+
<div className="float-right m-50">
13+
<ActionButton
14+
aria-label={label}
15+
onPress={() => setColorScheme(otherScheme)}
16+
>
17+
{colorScheme === "dark" ? <Light /> : <Moon />}
18+
</ActionButton>
19+
</div>
20+
);
21+
}
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
<!doctype html>
2+
<html>
3+
<head>
4+
<meta charset="utf-8">
5+
<title>React Aria Components + Spectrum + Tailwind</title>
6+
<meta name="viewport" content="width=device-width, initial-scale=1" />
7+
<link rel="stylesheet" href="style.css">
8+
</head>
9+
<body class="bg-white text-gray-800">
10+
<div id="root"></div>
11+
<script type="module" src="index.js"></script>
12+
</body>
13+
</html>
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
import { createRoot } from "react-dom/client";
2+
import { App } from './App';
3+
4+
let root = createRoot(document.getElementById('root'));
5+
root.render(<App />);
6+
Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
import plugin from "tailwindcss/plugin";
2+
3+
const attributes = {
4+
boolean: {
5+
data: [
6+
"hovered",
7+
"focused",
8+
"focus-visible",
9+
"pressed",
10+
"disabled",
11+
"drop-target",
12+
"dragging",
13+
"empty",
14+
"allows-removing",
15+
"placeholder",
16+
"selected",
17+
"indeterminate",
18+
"readonly",
19+
"required",
20+
"entering",
21+
"exiting",
22+
"open",
23+
"unavailable",
24+
],
25+
aria: ["sort", "invalid", "current"],
26+
},
27+
enum: {
28+
data: {
29+
"validation-state": ["invalid", "valid"],
30+
placement: ["left", "right", "top", "bottom"],
31+
type: ["literal", "year", "month", "day"],
32+
layout: ["grid", "stack"],
33+
},
34+
aria: {
35+
orientation: ["horizontal", "vertical"],
36+
},
37+
},
38+
};
39+
40+
module.exports = plugin(({ addVariant }) => {
41+
Object.keys(attributes.boolean).forEach((attributePrefix) => {
42+
attributes.boolean[attributePrefix].forEach((attributeName) => {
43+
let selector = `&[${attributePrefix}-${attributeName}]`;
44+
addVariant(attributeName, selector);
45+
});
46+
});
47+
Object.keys(attributes.enum).forEach((attributePrefix) => {
48+
Object.keys(attributes.enum[attributePrefix]).forEach((attributeName) => {
49+
attributes.enum[attributePrefix][attributeName].forEach(
50+
(attributeValue) => {
51+
let variantName = `${attributeName}-${attributeValue}`;
52+
let selector = `&[${attributePrefix}-${attributeName}]="${attributeValue}"`;
53+
addVariant(variantName, selector);
54+
}
55+
);
56+
});
57+
});
58+
});

0 commit comments

Comments
 (0)