├── .all-contributorsrc
├── .github
└── workflows
│ └── nodejs.yml
├── .gitignore
├── LICENSE
├── README.md
├── applescript.sh
├── images
├── banner.png
└── icon.png
├── jest.config.js
├── manifest.json
├── modd.conf
├── package.json
├── src
├── @types
│ ├── core.d.ts
│ ├── figma.d.ts
│ └── tailwind.d.ts
├── App.tsx
├── __tests__
│ ├── colors.test.ts
│ └── config.test.ts
├── code.ts
├── components
│ ├── ColorView.tsx
│ ├── FileUpload.tsx
│ └── Footer.tsx
├── core
│ ├── colors.ts
│ ├── config.ts
│ └── fonts.ts
├── generated
│ └── tw.min.json
├── models
│ └── Root.ts
├── resources
│ ├── css
│ │ └── ui.css
│ └── data
│ │ └── .gitkeep
└── ui.html
├── tailwind.config.js
├── tsconfig.json
├── webpack.config.js
└── yarn.lock
/.all-contributorsrc:
--------------------------------------------------------------------------------
1 | {
2 | "files": [
3 | "README.md"
4 | ],
5 | "imageSize": 100,
6 | "commit": false,
7 | "contributors": [
8 | {
9 | "login": "KA95DEV",
10 | "name": "KA95DEV",
11 | "avatar_url": "https://avatars1.githubusercontent.com/u/32483834?v=4",
12 | "profile": "https://github.com/KA95DEV",
13 | "contributions": [
14 | "code"
15 | ]
16 | },
17 | {
18 | "login": "leinardi",
19 | "name": "Roberto Leinardi",
20 | "avatar_url": "https://avatars2.githubusercontent.com/u/273338?v=4",
21 | "profile": "https://github.com/leinardi",
22 | "contributions": [
23 | "ideas",
24 | "code"
25 | ]
26 | }
27 | ],
28 | "contributorsPerLine": 7,
29 | "projectName": "tailwindcss-figma-plugin",
30 | "projectOwner": "impulse",
31 | "repoType": "github",
32 | "repoHost": "https://github.com",
33 | "skipCi": true
34 | }
35 |
--------------------------------------------------------------------------------
/.github/workflows/nodejs.yml:
--------------------------------------------------------------------------------
1 | name: Figma Plugin CI
2 |
3 | on:
4 | push:
5 | # Run against main branch only.
6 | branches: [main]
7 | pull_request:
8 | # Include events emitted on code change only.
9 | types: [opened, synchronize]
10 |
11 | jobs:
12 | build:
13 | runs-on: ubuntu-latest
14 |
15 | strategy:
16 | matrix:
17 | node-version: [14.x]
18 |
19 | steps:
20 | - uses: actions/checkout@v2
21 | - name: Use Node.js ${{ matrix.node-version }}
22 | uses: actions/setup-node@v1
23 | with:
24 | node-version: ${{ matrix.node-version }}
25 | - name: yarn install, build, and test
26 | run: |
27 | yarn install
28 | yarn build
29 | yarn test
30 | env:
31 | CI: true
32 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | .DS_Store
2 | node_modules/
3 | dist/
4 | /coverage.data
5 | /coverage/
6 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2019 ecklf
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Tailwind CSS Figma Plugin
2 |
3 |  [](https://github.com/impulse/tailwindcss-figma-plugin/blob/master/LICENSE) 
4 |
5 |
6 |
7 | [](#contributors-)
8 |
9 |
10 |
11 | Making your life with Tailwind CSS and Figma easier.
12 |
13 | 
14 |
15 | ## Usage
16 |
17 | 1. Generate a resolved TailwindCSS config (make sure dependencies are installed)
18 |
19 | ```sh
20 | $ cd yourtailwindproject/
21 | $ npx tailwind.json
22 | ```
23 |
24 | 2. Drag the generated `tailwind.json` in the plugin.
25 | 3. Done!
26 |
27 | ## Setup
28 |
29 | This Plugin will only work when running within Figma since it relies on its API.
30 |
31 | Go to the Plugins tab and add this projects `manifest.json`. For more info read the [Figma docs](https://www.figma.com/plugin-docs/intro/)
32 |
33 | 1. Then, install your dependencies:
34 |
35 | ```sh
36 | $ yarn install
37 | ```
38 |
39 | 2. The config offers either a dev or build script
40 |
41 | ```sh
42 | $ yarn dev
43 | $ yarn build
44 | ```
45 |
46 | ## Hot Reloading (macOS only)
47 |
48 | Not the smoothest experience, but way better than needing to press a hotkey. In case you use the Figma Beta change App name in `applescript.sh`.
49 |
50 | ```sh
51 | $ brew install modd
52 | $ cd tailwindcss-figma-plugin/
53 | $ modd
54 | ```
55 |
56 | ## License
57 |
58 | MIT
59 |
60 | ## Contributors ✨
61 |
62 | Thanks goes to these wonderful people ([emoji key](https://allcontributors.org/docs/en/emoji-key)):
63 |
64 |
65 |
66 |
67 |
73 |
74 |
75 |
76 |
77 |
78 |
79 | This project follows the [all-contributors](https://github.com/all-contributors/all-contributors) specification. Contributions of any kind welcome!
80 |
--------------------------------------------------------------------------------
/applescript.sh:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env bash
2 |
3 | osascript <<'EOF'
4 | tell application "Figma" to activate
5 | tell application "System Events" to tell process "Figma"
6 | keystroke "p" using {command down, option down}
7 | end tell
8 | EOF
--------------------------------------------------------------------------------
/images/banner.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ecklf/tailwindcss-figma-plugin/114e846993c33f714e0dd4699af7b67fc36d635b/images/banner.png
--------------------------------------------------------------------------------
/images/icon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ecklf/tailwindcss-figma-plugin/114e846993c33f714e0dd4699af7b67fc36d635b/images/icon.png
--------------------------------------------------------------------------------
/jest.config.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | preset: 'ts-jest',
3 | testEnvironment: 'node',
4 | };
--------------------------------------------------------------------------------
/manifest.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "Tailwind CSS",
3 | "id": "738806869514947558",
4 | "api": "1.0.0",
5 | "main": "dist/code.js",
6 | "ui": "dist/ui.html"
7 | }
8 |
--------------------------------------------------------------------------------
/modd.conf:
--------------------------------------------------------------------------------
1 | ** !dist/** !lib/** !node_modules/** {
2 | prep: yarn build:dev
3 | prep: ./applescript.sh
4 | }
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "tailwindcss",
3 | "version": "2.1.0",
4 | "description": "Make your life with Tailwind CSS and Figma easier",
5 | "main": "code.ts",
6 | "typings": "./src/@types",
7 | "scripts": {
8 | "test": "jest",
9 | "test:watch": "jest --watchAll",
10 | "coverage": "jest --coverage",
11 | "dev": "webpack --mode=development --watch",
12 | "build:dev": "webpack --mode=development",
13 | "build": "webpack --mode=production"
14 | },
15 | "author": "impulse",
16 | "license": "MIT",
17 | "dependencies": {
18 | "autoprefixer": "^10.3.4",
19 | "classnames": "^2.2.6",
20 | "css-loader": "^3.6.0",
21 | "html-webpack-inline-source-plugin": "0.0.10",
22 | "html-webpack-plugin": "^3.2.0",
23 | "lodash": "^4.17.20",
24 | "mobx": "^6.0.4",
25 | "mobx-react-lite": "^3.1.6",
26 | "mobx-state-tree": "^4.0.2",
27 | "parse-color": "^1.0.0",
28 | "postcss-loader": "^4.1.0",
29 | "react": "^17.0.1",
30 | "react-dom": "^17.0.1",
31 | "react-dropzone": "^11.2.4",
32 | "style-loader": "^1.2.1",
33 | "tailwindcss": "^2.2.15",
34 | "ts-loader": "^7.0.5",
35 | "typescript": "^3.9.6",
36 | "url-loader": "^4.1.0",
37 | "webpack": "^4.43.0",
38 | "webpack-cli": "^3.3.12"
39 | },
40 | "devDependencies": {
41 | "@fullhuman/postcss-purgecss": "4.0.3",
42 | "@tailwindcss/forms": "^0.3.3",
43 | "@types/jest": "27.0.1",
44 | "@types/node": "14.0.14",
45 | "@types/react": "^17.0.0",
46 | "@types/react-dom": "^17.0.0",
47 | "cssnano": "^5.0.8",
48 | "jest": "26.1.0",
49 | "postcss": "^8.3.6",
50 | "ts-jest": "26.1.1",
51 | "webpack-dev-server": "3.11.0"
52 | }
53 | }
54 |
--------------------------------------------------------------------------------
/src/@types/core.d.ts:
--------------------------------------------------------------------------------
1 | interface PluginMessage {
2 | type: string;
3 | payload: object;
4 | }
5 |
6 | interface TailwindColor {
7 | name: string;
8 | value: RGBA;
9 | }
10 | interface AddColorsPayload {
11 | prefix: string;
12 | config: Array;
13 | overrideStyles: boolean;
14 | addSpaces: boolean;
15 | }
16 |
--------------------------------------------------------------------------------
/src/@types/figma.d.ts:
--------------------------------------------------------------------------------
1 | // Global variable with Figma's plugin API.
2 | declare const figma: PluginAPI;
3 | declare const __html__: string;
4 |
5 | interface PluginAPI {
6 | readonly apiVersion: "1.0.0";
7 | readonly command: string;
8 | readonly root: DocumentNode;
9 | readonly viewport: ViewportAPI;
10 | closePlugin(message?: string): void;
11 |
12 | showUI(html: string, options?: ShowUIOptions): void;
13 | readonly ui: UIAPI;
14 |
15 | readonly clientStorage: ClientStorageAPI;
16 |
17 | getNodeById(id: string): BaseNode | null;
18 | getStyleById(id: string): BaseStyle | null;
19 |
20 | currentPage: PageNode;
21 |
22 | readonly mixed: symbol;
23 |
24 | createRectangle(): RectangleNode;
25 | createLine(): LineNode;
26 | createEllipse(): EllipseNode;
27 | createPolygon(): PolygonNode;
28 | createStar(): StarNode;
29 | createVector(): VectorNode;
30 | createText(): TextNode;
31 | createBooleanOperation(): BooleanOperationNode;
32 | createFrame(): FrameNode;
33 | createComponent(): ComponentNode;
34 | createPage(): PageNode;
35 | createSlice(): SliceNode;
36 |
37 | createPaintStyle(): PaintStyle;
38 | createTextStyle(): TextStyle;
39 | createEffectStyle(): EffectStyle;
40 | createGridStyle(): GridStyle;
41 |
42 | getLocalPaintStyles(): PaintStyle[];
43 |
44 | importComponentByKeyAsync(key: string): Promise;
45 | importStyleByKeyAsync(key: string): Promise;
46 |
47 | listAvailableFontsAsync(): Promise;
48 | loadFontAsync(fontName: FontName): Promise;
49 | readonly hasMissingFont: boolean;
50 |
51 | createNodeFromSvg(svg: string): FrameNode;
52 |
53 | createImage(data: Uint8Array): Image;
54 | getImageByHash(hash: string): Image;
55 |
56 | group(
57 | nodes: ReadonlyArray,
58 | parent: BaseNode & ChildrenMixin,
59 | index?: number
60 | ): FrameNode;
61 | flatten(
62 | nodes: ReadonlyArray,
63 | parent?: BaseNode & ChildrenMixin,
64 | index?: number
65 | ): VectorNode;
66 | }
67 |
68 | interface ClientStorageAPI {
69 | getAsync(key: string): Promise;
70 | setAsync(key: string, value: any): Promise;
71 | }
72 |
73 | type ShowUIOptions = {
74 | visible?: boolean;
75 | width?: number;
76 | height?: number;
77 | };
78 |
79 | type UIPostMessageOptions = {
80 | targetOrigin?: string;
81 | };
82 |
83 | type OnMessageProperties = {
84 | sourceOrigin: string;
85 | };
86 |
87 | interface UIAPI {
88 | show(): void;
89 | hide(): void;
90 | resize(width: number, height: number): void;
91 | close(): void;
92 |
93 | postMessage(pluginMessage: any, options?: UIPostMessageOptions): void;
94 | onmessage:
95 | | ((pluginMessage: any, props: OnMessageProperties) => void)
96 | | undefined;
97 | }
98 |
99 | interface ViewportAPI {
100 | center: { x: number; y: number };
101 | zoom: number;
102 | scrollAndZoomIntoView(nodes: ReadonlyArray);
103 | }
104 |
105 | ////////////////////////////////////////////////////////////////////////////////
106 | // Datatypes
107 |
108 | type Transform = [[number, number, number], [number, number, number]];
109 |
110 | interface Vector {
111 | readonly x: number;
112 | readonly y: number;
113 | }
114 |
115 | interface RGB {
116 | readonly r: number;
117 | readonly g: number;
118 | readonly b: number;
119 | }
120 |
121 | interface RGBA {
122 | readonly r: number;
123 | readonly g: number;
124 | readonly b: number;
125 | readonly a: number;
126 | }
127 |
128 | interface FontName {
129 | readonly family: string;
130 | readonly style: string;
131 | }
132 |
133 | type TextCase = "ORIGINAL" | "UPPER" | "LOWER" | "TITLE";
134 |
135 | type TextDecoration = "NONE" | "UNDERLINE" | "STRIKETHROUGH";
136 |
137 | interface ArcData {
138 | readonly startingAngle: number;
139 | readonly endingAngle: number;
140 | readonly innerRadius: number;
141 | }
142 |
143 | interface ShadowEffect {
144 | readonly type: "DROP_SHADOW" | "INNER_SHADOW";
145 | readonly color: RGBA;
146 | readonly offset: Vector;
147 | readonly radius: number;
148 | readonly visible: boolean;
149 | readonly blendMode: BlendMode;
150 | }
151 |
152 | interface BlurEffect {
153 | readonly type: "LAYER_BLUR" | "BACKGROUND_BLUR";
154 | readonly radius: number;
155 | readonly visible: boolean;
156 | }
157 |
158 | type Effect = ShadowEffect | BlurEffect;
159 |
160 | type ConstraintType = "MIN" | "CENTER" | "MAX" | "STRETCH" | "SCALE";
161 |
162 | interface Constraints {
163 | readonly horizontal: ConstraintType;
164 | readonly vertical: ConstraintType;
165 | }
166 |
167 | interface ColorStop {
168 | readonly position: number;
169 | readonly color: RGBA;
170 | }
171 |
172 | interface ImageFilters {
173 | exposure?: number;
174 | contrast?: number;
175 | saturation?: number;
176 | temperature?: number;
177 | tint?: number;
178 | highlights?: number;
179 | shadows?: number;
180 | }
181 |
182 | interface SolidPaint {
183 | readonly type: "SOLID";
184 | readonly color: RGB;
185 |
186 | readonly visible?: boolean;
187 | readonly opacity?: number;
188 | readonly blendMode?: BlendMode;
189 | }
190 |
191 | interface GradientPaint {
192 | readonly type:
193 | | "GRADIENT_LINEAR"
194 | | "GRADIENT_RADIAL"
195 | | "GRADIENT_ANGULAR"
196 | | "GRADIENT_DIAMOND";
197 | readonly gradientTransform: Transform;
198 | readonly gradientStops: ReadonlyArray;
199 |
200 | readonly visible?: boolean;
201 | readonly opacity?: number;
202 | readonly blendMode?: BlendMode;
203 | }
204 |
205 | interface ImagePaint {
206 | readonly type: "IMAGE";
207 | readonly scaleMode: "FILL" | "FIT" | "CROP" | "TILE";
208 | readonly imageHash: string | null;
209 | readonly imageTransform?: Transform; // setting for "CROP"
210 | readonly scalingFactor?: number; // setting for "TILE"
211 | readonly filters?: ImageFilters;
212 |
213 | readonly visible?: boolean;
214 | readonly opacity?: number;
215 | readonly blendMode?: BlendMode;
216 | }
217 |
218 | type Paint = SolidPaint | GradientPaint | ImagePaint;
219 |
220 | interface Guide {
221 | readonly axis: "X" | "Y";
222 | readonly offset: number;
223 | }
224 |
225 | interface RowsColsLayoutGrid {
226 | readonly pattern: "ROWS" | "COLUMNS";
227 | readonly alignment: "MIN" | "MAX" | "STRETCH" | "CENTER";
228 | readonly gutterSize: number;
229 |
230 | readonly count: number; // Infinity when "Auto" is set in the UI
231 | readonly sectionSize?: number; // Not set for alignment: "STRETCH"
232 | readonly offset?: number; // Not set for alignment: "CENTER"
233 |
234 | readonly visible?: boolean;
235 | readonly color?: RGBA;
236 | }
237 |
238 | interface GridLayoutGrid {
239 | readonly pattern: "GRID";
240 | readonly sectionSize: number;
241 |
242 | readonly visible?: boolean;
243 | readonly color?: RGBA;
244 | }
245 |
246 | type LayoutGrid = RowsColsLayoutGrid | GridLayoutGrid;
247 |
248 | interface ExportSettingsConstraints {
249 | type: "SCALE" | "WIDTH" | "HEIGHT";
250 | value: number;
251 | }
252 |
253 | interface ExportSettingsImage {
254 | format: "JPG" | "PNG";
255 | contentsOnly?: boolean; // defaults to true
256 | suffix?: string;
257 | constraint?: ExportSettingsConstraints;
258 | }
259 |
260 | interface ExportSettingsSVG {
261 | format: "SVG";
262 | contentsOnly?: boolean; // defaults to true
263 | suffix?: string;
264 | svgOutlineText?: boolean; // defaults to true
265 | svgIdAttribute?: boolean; // defaults to false
266 | svgSimplifyStroke?: boolean; // defaults to true
267 | }
268 |
269 | interface ExportSettingsPDF {
270 | format: "PDF";
271 | contentsOnly?: boolean; // defaults to true
272 | suffix?: string;
273 | }
274 |
275 | type ExportSettings =
276 | | ExportSettingsImage
277 | | ExportSettingsSVG
278 | | ExportSettingsPDF;
279 |
280 | type WindingRule = "NONZERO" | "EVENODD";
281 |
282 | interface VectorVertex {
283 | readonly x: number;
284 | readonly y: number;
285 | readonly strokeCap?: StrokeCap;
286 | readonly strokeJoin?: StrokeJoin;
287 | readonly cornerRadius?: number;
288 | readonly handleMirroring?: HandleMirroring;
289 | }
290 |
291 | interface VectorSegment {
292 | readonly start: number;
293 | readonly end: number;
294 | readonly tangentStart?: Vector; // Defaults to { x: 0, y: 0 }
295 | readonly tangentEnd?: Vector; // Defaults to { x: 0, y: 0 }
296 | }
297 |
298 | interface VectorRegion {
299 | readonly windingRule: WindingRule;
300 | readonly loops: ReadonlyArray>;
301 | }
302 |
303 | interface VectorNetwork {
304 | readonly vertices: ReadonlyArray;
305 | readonly segments: ReadonlyArray;
306 | readonly regions?: ReadonlyArray; // Defaults to []
307 | }
308 |
309 | interface VectorPath {
310 | readonly windingRule: WindingRule | "NONE";
311 | readonly data: string;
312 | }
313 |
314 | type VectorPaths = ReadonlyArray;
315 |
316 | type LetterSpacing = {
317 | readonly value: number;
318 | readonly unit: "PIXELS" | "PERCENT";
319 | };
320 |
321 | type LineHeight =
322 | | {
323 | readonly value: number;
324 | readonly unit: "PIXELS" | "PERCENT";
325 | }
326 | | {
327 | readonly unit: "AUTO";
328 | };
329 |
330 | type BlendMode =
331 | | "PASS_THROUGH"
332 | | "NORMAL"
333 | | "DARKEN"
334 | | "MULTIPLY"
335 | | "LINEAR_BURN"
336 | | "COLOR_BURN"
337 | | "LIGHTEN"
338 | | "SCREEN"
339 | | "LINEAR_DODGE"
340 | | "COLOR_DODGE"
341 | | "OVERLAY"
342 | | "SOFT_LIGHT"
343 | | "HARD_LIGHT"
344 | | "DIFFERENCE"
345 | | "EXCLUSION"
346 | | "HUE"
347 | | "SATURATION"
348 | | "COLOR"
349 | | "LUMINOSITY";
350 |
351 | interface Font {
352 | fontName: FontName;
353 | }
354 |
355 | ////////////////////////////////////////////////////////////////////////////////
356 | // Mixins
357 |
358 | interface BaseNodeMixin {
359 | readonly id: string;
360 | readonly parent: (BaseNode & ChildrenMixin) | null;
361 | name: string; // Note: setting this also sets \`autoRename\` to false on TextNodes
362 | readonly removed: boolean;
363 | toString(): string;
364 | remove(): void;
365 |
366 | getPluginData(key: string): string;
367 | setPluginData(key: string, value: string): void;
368 |
369 | // Namespace is a string that must be at least 3 alphanumeric characters, and should
370 | // be a name related to your plugin. Other plugins will be able to read this data.
371 | getSharedPluginData(namespace: string, key: string): string;
372 | setSharedPluginData(namespace: string, key: string, value: string): void;
373 | }
374 |
375 | interface SceneNodeMixin {
376 | visible: boolean;
377 | locked: boolean;
378 | }
379 |
380 | interface ChildrenMixin {
381 | readonly children: ReadonlyArray;
382 |
383 | appendChild(child: BaseNode): void;
384 | insertChild(index: number, child: BaseNode): void;
385 |
386 | findAll(callback?: (node: BaseNode) => boolean): ReadonlyArray;
387 | findOne(callback: (node: BaseNode) => boolean): BaseNode | null;
388 | }
389 |
390 | interface ConstraintMixin {
391 | constraints: Constraints;
392 | }
393 |
394 | interface LayoutMixin {
395 | readonly absoluteTransform: Transform;
396 | relativeTransform: Transform;
397 | x: number;
398 | y: number;
399 | rotation: number; // In degrees
400 |
401 | readonly width: number;
402 | readonly height: number;
403 |
404 | resize(width: number, height: number): void;
405 | resizeWithoutConstraints(width: number, height: number): void;
406 | }
407 |
408 | interface BlendMixin {
409 | opacity: number;
410 | blendMode: BlendMode;
411 | isMask: boolean;
412 | effects: ReadonlyArray;
413 | effectStyleId: string;
414 | }
415 |
416 | interface FrameMixin {
417 | backgrounds: ReadonlyArray;
418 | layoutGrids: ReadonlyArray;
419 | clipsContent: boolean;
420 | guides: ReadonlyArray;
421 | gridStyleId: string;
422 | backgroundStyleId: string;
423 | }
424 |
425 | type StrokeCap =
426 | | "NONE"
427 | | "ROUND"
428 | | "SQUARE"
429 | | "ARROW_LINES"
430 | | "ARROW_EQUILATERAL";
431 | type StrokeJoin = "MITER" | "BEVEL" | "ROUND";
432 | type HandleMirroring = "NONE" | "ANGLE" | "ANGLE_AND_LENGTH";
433 |
434 | interface GeometryMixin {
435 | fills: ReadonlyArray | symbol;
436 | strokes: ReadonlyArray;
437 | strokeWeight: number;
438 | strokeAlign: "CENTER" | "INSIDE" | "OUTSIDE";
439 | strokeCap: StrokeCap | symbol;
440 | strokeJoin: StrokeJoin | symbol;
441 | dashPattern: ReadonlyArray;
442 | fillStyleId: string | symbol;
443 | strokeStyleId: string;
444 | }
445 |
446 | interface CornerMixin {
447 | cornerRadius: number | symbol;
448 | cornerSmoothing: number;
449 | }
450 |
451 | interface ExportMixin {
452 | exportSettings: ExportSettings[];
453 | exportAsync(settings?: ExportSettings): Promise; // Defaults to PNG format
454 | }
455 |
456 | interface DefaultShapeMixin
457 | extends BaseNodeMixin,
458 | SceneNodeMixin,
459 | BlendMixin,
460 | GeometryMixin,
461 | LayoutMixin,
462 | ExportMixin {}
463 |
464 | interface DefaultContainerMixin
465 | extends BaseNodeMixin,
466 | SceneNodeMixin,
467 | ChildrenMixin,
468 | FrameMixin,
469 | BlendMixin,
470 | ConstraintMixin,
471 | LayoutMixin,
472 | ExportMixin {}
473 |
474 | ////////////////////////////////////////////////////////////////////////////////
475 | // Nodes
476 |
477 | interface DocumentNode extends BaseNodeMixin, ChildrenMixin {
478 | readonly type: "DOCUMENT";
479 | }
480 |
481 | interface PageNode extends BaseNodeMixin, ChildrenMixin, ExportMixin {
482 | readonly type: "PAGE";
483 | clone(): PageNode;
484 |
485 | guides: ReadonlyArray;
486 | selection: ReadonlyArray;
487 | }
488 |
489 | interface FrameNode extends DefaultContainerMixin {
490 | readonly type: "FRAME" | "GROUP";
491 | clone(): FrameNode;
492 | }
493 |
494 | interface SliceNode
495 | extends BaseNodeMixin,
496 | SceneNodeMixin,
497 | LayoutMixin,
498 | ExportMixin {
499 | readonly type: "SLICE";
500 | clone(): SliceNode;
501 | }
502 |
503 | interface RectangleNode
504 | extends DefaultShapeMixin,
505 | ConstraintMixin,
506 | CornerMixin {
507 | readonly type: "RECTANGLE";
508 | clone(): RectangleNode;
509 | topLeftRadius: number;
510 | topRightRadius: number;
511 | bottomLeftRadius: number;
512 | bottomRightRadius: number;
513 | }
514 |
515 | interface LineNode extends DefaultShapeMixin, ConstraintMixin {
516 | readonly type: "LINE";
517 | clone(): LineNode;
518 | }
519 |
520 | interface EllipseNode extends DefaultShapeMixin, ConstraintMixin, CornerMixin {
521 | readonly type: "ELLIPSE";
522 | clone(): EllipseNode;
523 | arcData: ArcData;
524 | }
525 |
526 | interface PolygonNode extends DefaultShapeMixin, ConstraintMixin, CornerMixin {
527 | readonly type: "POLYGON";
528 | clone(): PolygonNode;
529 | pointCount: number;
530 | }
531 |
532 | interface StarNode extends DefaultShapeMixin, ConstraintMixin, CornerMixin {
533 | readonly type: "STAR";
534 | clone(): StarNode;
535 | pointCount: number;
536 | innerRadius: number;
537 | }
538 |
539 | interface VectorNode extends DefaultShapeMixin, ConstraintMixin, CornerMixin {
540 | readonly type: "VECTOR";
541 | clone(): VectorNode;
542 | vectorNetwork: VectorNetwork;
543 | vectorPaths: VectorPaths;
544 | handleMirroring: HandleMirroring | symbol;
545 | }
546 |
547 | interface TextNode extends DefaultShapeMixin, ConstraintMixin {
548 | readonly type: "TEXT";
549 | clone(): TextNode;
550 | characters: string;
551 | readonly hasMissingFont: boolean;
552 | textAlignHorizontal: "LEFT" | "CENTER" | "RIGHT" | "JUSTIFIED";
553 | textAlignVertical: "TOP" | "CENTER" | "BOTTOM";
554 | textAutoResize: "NONE" | "WIDTH_AND_HEIGHT" | "HEIGHT";
555 | paragraphIndent: number;
556 | paragraphSpacing: number;
557 | autoRename: boolean;
558 |
559 | textStyleId: string | symbol;
560 | fontSize: number | symbol;
561 | fontName: FontName | symbol;
562 | textCase: TextCase | symbol;
563 | textDecoration: TextDecoration | symbol;
564 | letterSpacing: LetterSpacing | symbol;
565 | lineHeight: LineHeight | symbol;
566 |
567 | getRangeFontSize(start: number, end: number): number | symbol;
568 | setRangeFontSize(start: number, end: number, value: number): void;
569 | getRangeFontName(start: number, end: number): FontName | symbol;
570 | setRangeFontName(start: number, end: number, value: FontName): void;
571 | getRangeTextCase(start: number, end: number): TextCase | symbol;
572 | setRangeTextCase(start: number, end: number, value: TextCase): void;
573 | getRangeTextDecoration(start: number, end: number): TextDecoration | symbol;
574 | setRangeTextDecoration(
575 | start: number,
576 | end: number,
577 | value: TextDecoration
578 | ): void;
579 | getRangeLetterSpacing(start: number, end: number): LetterSpacing | symbol;
580 | setRangeLetterSpacing(start: number, end: number, value: LetterSpacing): void;
581 | getRangeLineHeight(start: number, end: number): LineHeight | symbol;
582 | setRangeLineHeight(start: number, end: number, value: LineHeight): void;
583 | getRangeFills(start: number, end: number): Paint[] | symbol;
584 | setRangeFills(start: number, end: number, value: Paint[]): void;
585 | getRangeTextStyleId(start: number, end: number): string | symbol;
586 | setRangeTextStyleId(start: number, end: number, value: string): void;
587 | getRangeFillStyleId(start: number, end: number): string | symbol;
588 | setRangeFillStyleId(start: number, end: number, value: string): void;
589 | }
590 |
591 | interface ComponentNode extends DefaultContainerMixin {
592 | readonly type: "COMPONENT";
593 | clone(): ComponentNode;
594 |
595 | createInstance(): InstanceNode;
596 | description: string;
597 | readonly remote: boolean;
598 | readonly key: string; // The key to use with "importComponentByKeyAsync"
599 | }
600 |
601 | interface InstanceNode extends DefaultContainerMixin {
602 | readonly type: "INSTANCE";
603 | clone(): InstanceNode;
604 | masterComponent: ComponentNode;
605 | }
606 |
607 | interface BooleanOperationNode
608 | extends DefaultShapeMixin,
609 | ChildrenMixin,
610 | CornerMixin {
611 | readonly type: "BOOLEAN_OPERATION";
612 | clone(): BooleanOperationNode;
613 | booleanOperation: "UNION" | "INTERSECT" | "SUBTRACT" | "EXCLUDE";
614 | }
615 |
616 | type BaseNode = DocumentNode | PageNode | SceneNode;
617 |
618 | type SceneNode =
619 | | SliceNode
620 | | FrameNode
621 | | ComponentNode
622 | | InstanceNode
623 | | BooleanOperationNode
624 | | VectorNode
625 | | StarNode
626 | | LineNode
627 | | EllipseNode
628 | | PolygonNode
629 | | RectangleNode
630 | | TextNode;
631 |
632 | type NodeType =
633 | | "DOCUMENT"
634 | | "PAGE"
635 | | "SLICE"
636 | | "FRAME"
637 | | "GROUP"
638 | | "COMPONENT"
639 | | "INSTANCE"
640 | | "BOOLEAN_OPERATION"
641 | | "VECTOR"
642 | | "STAR"
643 | | "LINE"
644 | | "ELLIPSE"
645 | | "POLYGON"
646 | | "RECTANGLE"
647 | | "TEXT";
648 |
649 | ////////////////////////////////////////////////////////////////////////////////
650 | // Styles
651 | type StyleType = "PAINT" | "TEXT" | "EFFECT" | "GRID";
652 |
653 | interface BaseStyle {
654 | readonly id: string;
655 | readonly type: StyleType;
656 | name: string;
657 | description: string;
658 | remote: boolean;
659 | readonly key: string; // The key to use with "importStyleByKeyAsync"
660 | remove(): void;
661 | }
662 |
663 | interface PaintStyle extends BaseStyle {
664 | type: "PAINT";
665 | paints: ReadonlyArray;
666 | }
667 |
668 | interface TextStyle extends BaseStyle {
669 | type: "TEXT";
670 | fontSize: number;
671 | textDecoration: TextDecoration;
672 | fontName: FontName;
673 | letterSpacing: LetterSpacing;
674 | lineHeight: LineHeight;
675 | paragraphIndent: number;
676 | paragraphSpacing: number;
677 | textCase: TextCase;
678 | }
679 |
680 | interface EffectStyle extends BaseStyle {
681 | type: "EFFECT";
682 | effects: ReadonlyArray;
683 | }
684 |
685 | interface GridStyle extends BaseStyle {
686 | type: "GRID";
687 | layoutGrids: ReadonlyArray;
688 | }
689 |
690 | ////////////////////////////////////////////////////////////////////////////////
691 | // Other
692 |
693 | interface Image {
694 | readonly hash: string;
695 | getBytesAsync(): Promise;
696 | }
697 |
--------------------------------------------------------------------------------
/src/@types/tailwind.d.ts:
--------------------------------------------------------------------------------
1 | export interface TailwindConfig {
2 | theme: Theme;
3 | variants: { [K in keyof Theme]?: string[] };
4 | purge?: any[];
5 | plugins: any[];
6 | target?: string;
7 | prefix?: string;
8 | important?: boolean;
9 | separator?: string;
10 | corePlugins?: string[];
11 | darkMode: boolean;
12 | presets: any[];
13 | variantOrder: string[];
14 | }
15 | export interface Theme {
16 | extend?: Theme;
17 | screens?: KeyConfig;
18 | colors?: KeyConfig;
19 | spacing?: KeyConfig;
20 | backgroundColor?: KeyConfig;
21 | backgroundOpacity?: KeyConfig;
22 | backgroundPosition?: KeyConfig;
23 | backgroundSize?: KeyConfig;
24 | borderColor?: KeyConfig;
25 | borderOpacity?: KeyConfig;
26 | borderRadius?: KeyConfig;
27 | borderWidth?: KeyConfig;
28 | boxShadow?: KeyConfig;
29 | container?: KeyConfig;
30 | cursor?: KeyConfig;
31 | divideColor?: KeyConfig;
32 | divideOpacity?: KeyConfig;
33 | divideWidth?: KeyConfig;
34 | fill?: KeyConfig;
35 | flex?: KeyConfig;
36 | flexGrow?: KeyConfig;
37 | flexShrink?: KeyConfig;
38 | fontFamily?: KeyConfig;
39 | fontSize?: KeyConfig;
40 | fontWeight?: KeyConfig;
41 | height?: KeyConfig;
42 | inset?: KeyConfig;
43 | letterSpacing?: KeyConfig;
44 | lineHeight?: KeyConfig;
45 | listStyleType?: KeyConfig;
46 | margin?: KeyConfig;
47 | maxHeight?: KeyConfig;
48 | maxWidth?: KeyConfig;
49 | minHeight?: KeyConfig;
50 | minWidth?: KeyConfig;
51 | objectPosition?: KeyConfig;
52 | opacity?: KeyConfig;
53 | order?: KeyConfig;
54 | padding?: KeyConfig;
55 | placeholderColor?: KeyConfig;
56 | placeholderOpacity?: KeyConfig;
57 | space?: KeyConfig;
58 | stroke?: KeyConfig;
59 | strokeWidth?: KeyConfig;
60 | textColor?: KeyConfig;
61 | textOpacity?: KeyConfig;
62 | width?: KeyConfig;
63 | zIndex?: KeyConfig;
64 | gap?: KeyConfig;
65 | gridTemplateColumns?: KeyConfig;
66 | gridColumn?: KeyConfig;
67 | gridColumnStart?: KeyConfig;
68 | gridColumnEnd?: KeyConfig;
69 | gridTemplateRows?: KeyConfig;
70 | gridRow?: KeyConfig;
71 | gridRowStart?: KeyConfig;
72 | gridRowEnd?: KeyConfig;
73 | transformOrigin?: KeyConfig;
74 | scale?: KeyConfig;
75 | rotate?: KeyConfig;
76 | translate?: KeyConfig;
77 | skew?: KeyConfig;
78 | transitionProperty?: KeyConfig;
79 | transitionTimingFunction?: KeyConfig;
80 | transitionDuration?: KeyConfig;
81 | transitionDelay?: KeyConfig;
82 | }
83 | export interface KeyConfig {
84 | [key: string]: string | { [key: string]: any };
85 | }
86 |
--------------------------------------------------------------------------------
/src/App.tsx:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import ReactDOM from "react-dom";
3 | import ColorView from "./components/ColorView";
4 | import FileUpload from "./components/FileUpload";
5 | import Footer from "./components/Footer";
6 | import { Provider, rootStore } from "./models/Root";
7 | import "./resources/css/ui.css";
8 |
9 | interface Props {}
10 |
11 | const App = (props: Props) => {
12 | return (
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 | );
25 | };
26 |
27 | ReactDOM.render(, document.getElementById("react-page"));
28 |
--------------------------------------------------------------------------------
/src/__tests__/colors.test.ts:
--------------------------------------------------------------------------------
1 | import * as colors from "../core/colors";
2 | import { fetchConfigColors } from "../core/config";
3 | import defaultConfig from "../generated/tw.min.json";
4 |
5 | let fetchedColors = fetchConfigColors(defaultConfig);
6 |
7 | describe("When addColors is called", () => {
8 | it("should return true", () => {
9 | const handleSolidColorMock = jest.spyOn(colors, "handleSolidColor");
10 | handleSolidColorMock.mockImplementation(() => "figma api");
11 | expect(
12 | colors.addColors(
13 | {
14 | prefix: "",
15 | config: fetchedColors,
16 | overrideStyles: false,
17 | addSpaces: false,
18 | },
19 | []
20 | )
21 | ).toBe(true);
22 | // Would not be called if Figma API returns error
23 | expect(handleSolidColorMock).toHaveBeenCalledTimes(fetchedColors.length);
24 | });
25 | });
26 |
--------------------------------------------------------------------------------
/src/__tests__/config.test.ts:
--------------------------------------------------------------------------------
1 | import { convertConfigColor, fetchConfigColors } from "../core/config";
2 | import defaultConfig from "../generated/tw.min.json";
3 |
4 | const colors = {
5 | transparent: "transparent",
6 | hex6: "#E53E3E",
7 | hex8: "#E53E3E80",
8 | rgb: "rgb(56,161,105)",
9 | rgba: "rgba(49,151,149, 0.5)",
10 | hsl: "hsl(170, 45%, 45%)",
11 | hsla: "hsla(170, 45%, 45%, 0.5)",
12 | };
13 |
14 | describe("fetchConfigColor", () => {
15 | describe("reading a valid config", () => {
16 | it("should return an object with color name and value", () => {
17 | const fetchedColors = fetchConfigColors(defaultConfig);
18 | expect(fetchedColors[0]).toHaveProperty("name");
19 | expect(fetchedColors[0]).toHaveProperty("value");
20 | });
21 | });
22 | });
23 |
24 | describe("convertConfigColor", () => {
25 | describe("Passing a valid config color", () => {
26 | let currentColor: RGBA;
27 | it("should return object from transparent string", () => {
28 | currentColor = convertConfigColor(colors["transparent"]);
29 | // Check if valid object
30 | expect(currentColor).toHaveProperty("r");
31 | expect(currentColor).toHaveProperty("g");
32 | expect(currentColor).toHaveProperty("b");
33 | expect(currentColor).toHaveProperty("a");
34 | // Check if valid values
35 | expect(currentColor["r"]).toBeGreaterThanOrEqual(0);
36 | expect(currentColor["r"]).toBeLessThanOrEqual(1);
37 | expect(currentColor["g"]).toBeGreaterThanOrEqual(0);
38 | expect(currentColor["g"]).toBeLessThanOrEqual(1);
39 | expect(currentColor["b"]).toBeGreaterThanOrEqual(0);
40 | expect(currentColor["b"]).toBeLessThanOrEqual(1);
41 | expect(currentColor["a"]).toBeGreaterThanOrEqual(0);
42 | expect(currentColor["a"]).toBeLessThanOrEqual(1);
43 | });
44 |
45 | it("should return object from hex6", () => {
46 | currentColor = convertConfigColor(colors["hex6"]);
47 | // Check if valid object
48 | expect(currentColor).toHaveProperty("r");
49 | expect(currentColor).toHaveProperty("g");
50 | expect(currentColor).toHaveProperty("b");
51 | expect(currentColor).toHaveProperty("a");
52 | // Check if valid values
53 | expect(currentColor["r"]).toBeGreaterThanOrEqual(0);
54 | expect(currentColor["r"]).toBeLessThanOrEqual(1);
55 | expect(currentColor["g"]).toBeGreaterThanOrEqual(0);
56 | expect(currentColor["g"]).toBeLessThanOrEqual(1);
57 | expect(currentColor["b"]).toBeGreaterThanOrEqual(0);
58 | expect(currentColor["b"]).toBeLessThanOrEqual(1);
59 | expect(currentColor["a"]).toBeGreaterThanOrEqual(0);
60 | expect(currentColor["a"]).toBeLessThanOrEqual(1);
61 | });
62 |
63 | it("should return object from hex8", () => {
64 | currentColor = convertConfigColor(colors["hex8"]);
65 | // Check if valid object
66 | expect(currentColor).toHaveProperty("r");
67 | expect(currentColor).toHaveProperty("g");
68 | expect(currentColor).toHaveProperty("b");
69 | expect(currentColor).toHaveProperty("a");
70 | // Check if valid values
71 | expect(currentColor["r"]).toBeGreaterThanOrEqual(0);
72 | expect(currentColor["r"]).toBeLessThanOrEqual(1);
73 | expect(currentColor["g"]).toBeGreaterThanOrEqual(0);
74 | expect(currentColor["g"]).toBeLessThanOrEqual(1);
75 | expect(currentColor["b"]).toBeGreaterThanOrEqual(0);
76 | expect(currentColor["b"]).toBeLessThanOrEqual(1);
77 | expect(currentColor["a"]).toBeGreaterThanOrEqual(0);
78 | expect(currentColor["a"]).toBeLessThanOrEqual(1);
79 | });
80 |
81 | it("should return object from rgb", () => {
82 | currentColor = convertConfigColor(colors["rgb"]);
83 | // Check if valid object
84 | expect(currentColor).toHaveProperty("r");
85 | expect(currentColor).toHaveProperty("g");
86 | expect(currentColor).toHaveProperty("b");
87 | expect(currentColor).toHaveProperty("a");
88 | // Check if valid values
89 | expect(currentColor["r"]).toBeGreaterThanOrEqual(0);
90 | expect(currentColor["r"]).toBeLessThanOrEqual(1);
91 | expect(currentColor["g"]).toBeGreaterThanOrEqual(0);
92 | expect(currentColor["g"]).toBeLessThanOrEqual(1);
93 | expect(currentColor["b"]).toBeGreaterThanOrEqual(0);
94 | expect(currentColor["b"]).toBeLessThanOrEqual(1);
95 | expect(currentColor["a"]).toBeGreaterThanOrEqual(0);
96 | expect(currentColor["a"]).toBeLessThanOrEqual(1);
97 | });
98 |
99 | it("should return object from rgba", () => {
100 | currentColor = convertConfigColor(colors["rgba"]);
101 | // Check if valid object
102 | expect(currentColor).toHaveProperty("r");
103 | expect(currentColor).toHaveProperty("g");
104 | expect(currentColor).toHaveProperty("b");
105 | expect(currentColor).toHaveProperty("a");
106 | // Check if valid values
107 | expect(currentColor["r"]).toBeGreaterThanOrEqual(0);
108 | expect(currentColor["r"]).toBeLessThanOrEqual(1);
109 | expect(currentColor["g"]).toBeGreaterThanOrEqual(0);
110 | expect(currentColor["g"]).toBeLessThanOrEqual(1);
111 | expect(currentColor["b"]).toBeGreaterThanOrEqual(0);
112 | expect(currentColor["b"]).toBeLessThanOrEqual(1);
113 | expect(currentColor["a"]).toBeGreaterThanOrEqual(0);
114 | expect(currentColor["a"]).toBeLessThanOrEqual(1);
115 | });
116 |
117 | it("should return object from hsl", () => {
118 | currentColor = convertConfigColor(colors["hsl"]);
119 | // Check if valid object
120 | expect(currentColor).toHaveProperty("r");
121 | expect(currentColor).toHaveProperty("g");
122 | expect(currentColor).toHaveProperty("b");
123 | expect(currentColor).toHaveProperty("a");
124 | // Check if valid values
125 | expect(currentColor["r"]).toBeGreaterThanOrEqual(0);
126 | expect(currentColor["r"]).toBeLessThanOrEqual(1);
127 | expect(currentColor["g"]).toBeGreaterThanOrEqual(0);
128 | expect(currentColor["g"]).toBeLessThanOrEqual(1);
129 | expect(currentColor["b"]).toBeGreaterThanOrEqual(0);
130 | expect(currentColor["b"]).toBeLessThanOrEqual(1);
131 | expect(currentColor["a"]).toBeGreaterThanOrEqual(0);
132 | expect(currentColor["a"]).toBeLessThanOrEqual(1);
133 | });
134 |
135 | it("should return object from hsla", () => {
136 | currentColor = convertConfigColor(colors["hsla"]);
137 | // Check if valid object
138 | expect(currentColor).toHaveProperty("r");
139 | expect(currentColor).toHaveProperty("g");
140 | expect(currentColor).toHaveProperty("b");
141 | expect(currentColor).toHaveProperty("a");
142 | // Check if valid values
143 | expect(currentColor["r"]).toBeGreaterThanOrEqual(0);
144 | expect(currentColor["r"]).toBeLessThanOrEqual(1);
145 | expect(currentColor["g"]).toBeGreaterThanOrEqual(0);
146 | expect(currentColor["g"]).toBeLessThanOrEqual(1);
147 | expect(currentColor["b"]).toBeGreaterThanOrEqual(0);
148 | expect(currentColor["b"]).toBeLessThanOrEqual(1);
149 | expect(currentColor["a"]).toBeGreaterThanOrEqual(0);
150 | expect(currentColor["a"]).toBeLessThanOrEqual(1);
151 | });
152 | });
153 | });
154 |
--------------------------------------------------------------------------------
/src/code.ts:
--------------------------------------------------------------------------------
1 | import { addColors } from "./core/colors";
2 |
3 | let windowSize = {
4 | width: 300,
5 | height: 290,
6 | };
7 | figma.showUI(__html__, windowSize);
8 |
9 | interface AddColorsMessage {
10 | type: "ADD_COLORS";
11 | payload: AddColorsPayload;
12 | }
13 |
14 | let styles: Array = [];
15 |
16 | styles = figma.getLocalPaintStyles().map((style) => style);
17 |
18 | export type PluginMessage = AddColorsMessage;
19 |
20 | figma.ui.onmessage = (pluginMessage: PluginMessage) => {
21 | const { type, payload } = pluginMessage;
22 | if (type === "ADD_COLORS") {
23 | console.log("adding color");
24 | addColors(payload, styles);
25 | }
26 | figma.closePlugin();
27 | };
28 |
--------------------------------------------------------------------------------
/src/components/ColorView.tsx:
--------------------------------------------------------------------------------
1 | import cx from "classnames";
2 | import { observer } from "mobx-react-lite";
3 | import React, { useState } from "react";
4 | import { useMst } from "../models/Root";
5 |
6 | interface Props {}
7 |
8 | const ColorView = observer(({}: Props) => {
9 | const {
10 | addColorStyles,
11 | loadedTailwindConfig,
12 | loadDefaultStub,
13 | addSpaces,
14 | setAddSpaces,
15 | overrideStyles,
16 | setOverrideStyles,
17 | } = useMst();
18 | const [prefix, setPrefix] = useState("");
19 |
20 | const hasAvailableColorStyles = () => {
21 | return (
22 | loadedTailwindConfig.theme.colors || loadedTailwindConfig.theme.extend
23 | );
24 | };
25 |
26 | return (
27 |
28 |
29 | setPrefix(e.target.value)}
33 | className="block w-full mt-2 border-gray-300 rounded-md shadow-sm focus:border-teal-300 focus:ring focus:ring-teal-200 focus:ring-opacity-50"
34 | placeholder="Style name prefix (optional)"
35 | />
36 |
37 |
38 |
39 |
52 |
53 |
65 |
66 |
67 |
68 |
83 |
92 |
93 |
94 | );
95 | });
96 |
97 | export default ColorView;
98 |
--------------------------------------------------------------------------------
/src/components/FileUpload.tsx:
--------------------------------------------------------------------------------
1 | import { observer } from "mobx-react-lite";
2 | import React, { useCallback } from "react";
3 | import { useDropzone } from "react-dropzone";
4 | import { useMst } from "../models/Root";
5 |
6 | interface Props {}
7 |
8 | const FileUpload = observer(({}: Props) => {
9 | const { configName, errorMessage, readFile } = useMst();
10 |
11 | const onDrop = useCallback((acceptedFiles) => {
12 | acceptedFiles.forEach((file) => readFile(acceptedFiles[0].name, file));
13 | }, []);
14 | const { getRootProps, getInputProps, isDragActive } = useDropzone({ onDrop });
15 |
16 | return (
17 |
28 |
33 |
34 |
35 | {isDragActive
36 | ? "Drag your file here 🎉"
37 | : errorMessage ?? configName ?? "Import tailwind.json"}
38 |
39 |
40 |
41 | );
42 | });
43 |
44 | export default FileUpload;
45 |
--------------------------------------------------------------------------------
/src/components/Footer.tsx:
--------------------------------------------------------------------------------
1 | import React from "react";
2 |
3 | interface Props {}
4 |
5 | const Footer = ({}: Props) => {
6 | return (
7 |
68 | );
69 | };
70 |
71 | export default Footer;
72 |
--------------------------------------------------------------------------------
/src/core/colors.ts:
--------------------------------------------------------------------------------
1 | export const addColors = (payload: AddColorsPayload, styles: Array): boolean => {
2 | let payloadStyles: Array = payload.config;
3 | const overrideStyles: boolean = payload.overrideStyles;
4 | const addSpaces: boolean = payload.addSpaces;
5 | const prefix = payload.prefix !== "" ? addSpaces ? `${payload.prefix} / ` : `${payload.prefix}/` : "";
6 | try {
7 | if (styles.length > 0) {
8 | if (overrideStyles)
9 | styles.forEach(style => {
10 | for (const payloadStyle of payloadStyles) {
11 | const styleName = addSpaces ? payloadStyle.name.split('/').join(' / ') : payloadStyle.name;
12 | const payloadStyleName = `${prefix}${styleName}`;
13 |
14 | if (style.name == payloadStyleName) {
15 | handleSolidColor(style.name, payloadStyle.value, false, style);
16 | const index = payloadStyles.indexOf(payloadStyle, 0);
17 | payloadStyles.splice(index, 1);
18 | }
19 | }
20 | });
21 | createSolidColors(prefix, payloadStyles, addSpaces);
22 | } else {
23 | createSolidColors(prefix, payloadStyles, addSpaces);
24 | }
25 | return true;
26 | } catch (error) {
27 | alert(error);
28 | return false;
29 | }
30 | };
31 |
32 | export const createSolidColors = (prefix: String, payloadStyles: Array, addSpaces: boolean) => {
33 | for (const { name, value } of payloadStyles) {
34 | const styleName = addSpaces ? name.split('/').join(' / ') : name;
35 | const styleNameWithPrefix = `${prefix}${styleName}`;
36 | handleSolidColor(styleNameWithPrefix, value);
37 | }
38 | }
39 |
40 | export const handleSolidColor = (name: string, color: RGBA, create: boolean = true, paintStyle?: PaintStyle) => {
41 | const style = create ? figma.createPaintStyle() : paintStyle;
42 |
43 | if (create) {
44 | figma.createPaintStyle;
45 | style.name = name;
46 | }
47 |
48 | const { r, g, b, a } = color;
49 |
50 | const rgbColor: RGB = { r, g, b };
51 | const alpha: number = a;
52 |
53 | const solidPaint: SolidPaint = {
54 | type: "SOLID",
55 | color: rgbColor,
56 | opacity: alpha
57 | };
58 |
59 | style.paints = [solidPaint];
60 | };
61 |
--------------------------------------------------------------------------------
/src/core/config.ts:
--------------------------------------------------------------------------------
1 | const parse = require("parse-color");
2 |
3 | const looseJsonParse = (obj) => {
4 | return Function('"use strict";return (' + obj + ")")();
5 | };
6 |
7 | export const parseConfig = (config: string): object => {
8 | try {
9 | // Remove any whitespace
10 | config = config.replace(/\s/g, "");
11 | // Remove anything before module.exports and module.exports itself
12 | config = config.replace(/^(.*)(module.exports=?)/gi, "");
13 | // Remove semicolon after brace
14 | config = config.replace("};", "}");
15 | // Remove plugins section
16 | config = config.replace(/,plugins(.*?)(?!.*\])(?=})/gi, "");
17 | return looseJsonParse(config);
18 | } catch (error) {
19 | console.error(error);
20 | throw new Error(error);
21 | }
22 | };
23 |
24 | export const convertConfigColor = (color: string): RGBA => {
25 | if (color === "transparent") {
26 | color = "rgba(0, 0, 0, 0.0)";
27 | }
28 | const { rgba } = parse(color);
29 |
30 | const alpha = color.length < 8 ? rgba[3] : rgba[3] / 255;
31 | return {
32 | r: rgba[0] / 255,
33 | g: rgba[1] / 255,
34 | b: rgba[2] / 255,
35 | a: alpha,
36 | };
37 | };
38 |
39 | export const fetchConfigColors = (config): Array => {
40 | let foundColors: Array = [];
41 |
42 | if (config.theme) {
43 | const { theme } = config;
44 | let mergedColors = {};
45 |
46 | if (theme.colors) {
47 | mergedColors = { ...mergedColors, ...theme.colors };
48 | }
49 |
50 | if (theme.extend && theme.extend.colors) {
51 | mergedColors = { ...mergedColors, ...theme.extend.colors };
52 | }
53 |
54 | // Looping through all overwritten and extended colors
55 | for (const color of Object.keys(mergedColors)) {
56 | const colorData = mergedColors[color];
57 | // Determine if the current color has nested shades
58 | if (typeof colorData === "object") {
59 | for (const colorShade of Object.keys(colorData)) {
60 | foundColors.push({
61 | name: `${color}/${colorShade}`,
62 | value: convertConfigColor(colorData[colorShade]),
63 | });
64 | }
65 | } else {
66 | if (colorData === "currentColor") {
67 | continue;
68 | }
69 | foundColors.push({
70 | name: color,
71 | value: convertConfigColor(mergedColors[color]),
72 | });
73 | }
74 | }
75 | }
76 | return foundColors;
77 | };
78 |
--------------------------------------------------------------------------------
/src/core/fonts.ts:
--------------------------------------------------------------------------------
1 | export interface TailwindFont {
2 | family: string;
3 | style: string;
4 | }
5 |
6 | export const createFontStyle = async (payload: TailwindFont) => {
7 | try {
8 | const { family, style } = payload;
9 | const desiredFont: FontName = { family, style };
10 | const fontData = await figma.listAvailableFontsAsync();
11 | const availableStyles = fontData
12 | .filter(font => font.fontName.family === family)
13 | .map(font => {
14 | return font.fontName.style;
15 | });
16 | await figma.loadFontAsync(desiredFont);
17 | const fontStyle = figma.createTextStyle();
18 | fontStyle.name = "text-xs";
19 | fontStyle.fontName = desiredFont;
20 | } catch (error) {
21 | alert(error);
22 | }
23 | };
24 |
--------------------------------------------------------------------------------
/src/generated/tw.min.json:
--------------------------------------------------------------------------------
1 | {"theme":{"typography":{"DEFAULT":{"css":[{"color":"#374151","maxWidth":"65ch","[class~=\"lead\"]":{"color":"#4b5563"},"a":{"color":"#111827","textDecoration":"underline","fontWeight":"500"},"strong":{"color":"#111827","fontWeight":"600"},"ol":{"counterReset":"list-counter"},"ol > li":{"position":"relative","counterIncrement":"list-counter"},"ol > li::before":{"content":"counter(list-counter) \".\"","position":"absolute","fontWeight":"400","color":"#6b7280"},"ul > li":{"position":"relative"},"ul > li::before":{"content":"\"\"","position":"absolute","backgroundColor":"#d1d5db","borderRadius":"50%"},"hr":{"borderColor":"#e5e7eb","borderTopWidth":1},"blockquote":{"fontWeight":"500","fontStyle":"italic","color":"#111827","borderLeftWidth":"0.25rem","borderLeftColor":"#e5e7eb","quotes":"\"\\201C\"\"\\201D\"\"\\2018\"\"\\2019\""},"blockquote p:first-of-type::before":{"content":"open-quote"},"blockquote p:last-of-type::after":{"content":"close-quote"},"h1":{"color":"#111827","fontWeight":"800"},"h2":{"color":"#111827","fontWeight":"700"},"h3":{"color":"#111827","fontWeight":"600"},"h4":{"color":"#111827","fontWeight":"600"},"figure figcaption":{"color":"#6b7280"},"code":{"color":"#111827","fontWeight":"600"},"code::before":{"content":"\"`\""},"code::after":{"content":"\"`\""},"a code":{"color":"#111827"},"pre":{"color":"#e5e7eb","backgroundColor":"#1f2937","overflowX":"auto"},"pre code":{"backgroundColor":"transparent","borderWidth":"0","borderRadius":"0","padding":"0","fontWeight":"400","color":"inherit","fontSize":"inherit","fontFamily":"inherit","lineHeight":"inherit"},"pre code::before":{"content":"\"\""},"pre code::after":{"content":"\"\""},"table":{"width":"100%","tableLayout":"auto","textAlign":"left","marginTop":"2em","marginBottom":"2em"},"thead":{"color":"#111827","fontWeight":"600","borderBottomWidth":"1px","borderBottomColor":"#d1d5db"},"thead th":{"verticalAlign":"bottom"},"tbody tr":{"borderBottomWidth":"1px","borderBottomColor":"#e5e7eb"},"tbody tr:last-child":{"borderBottomWidth":"0"},"tbody td":{"verticalAlign":"top"}},{"fontSize":"1rem","lineHeight":"1.75","p":{"marginTop":"1.25em","marginBottom":"1.25em"},"[class~=\"lead\"]":{"fontSize":"1.25em","lineHeight":"1.6","marginTop":"1.2em","marginBottom":"1.2em"},"blockquote":{"marginTop":"1.6em","marginBottom":"1.6em","paddingLeft":"1em"},"h1":{"fontSize":"2.25em","marginTop":"0","marginBottom":"0.8888889em","lineHeight":"1.1111111"},"h2":{"fontSize":"1.5em","marginTop":"2em","marginBottom":"1em","lineHeight":"1.3333333"},"h3":{"fontSize":"1.25em","marginTop":"1.6em","marginBottom":"0.6em","lineHeight":"1.6"},"h4":{"marginTop":"1.5em","marginBottom":"0.5em","lineHeight":"1.5"},"img":{"marginTop":"2em","marginBottom":"2em"},"video":{"marginTop":"2em","marginBottom":"2em"},"figure":{"marginTop":"2em","marginBottom":"2em"},"figure > *":{"marginTop":"0","marginBottom":"0"},"figure figcaption":{"fontSize":"0.875em","lineHeight":"1.4285714","marginTop":"0.8571429em"},"code":{"fontSize":"0.875em"},"h2 code":{"fontSize":"0.875em"},"h3 code":{"fontSize":"0.9em"},"pre":{"fontSize":"0.875em","lineHeight":"1.7142857","marginTop":"1.7142857em","marginBottom":"1.7142857em","borderRadius":"0.375rem","paddingTop":"0.8571429em","paddingRight":"1.1428571em","paddingBottom":"0.8571429em","paddingLeft":"1.1428571em"},"ol":{"marginTop":"1.25em","marginBottom":"1.25em"},"ul":{"marginTop":"1.25em","marginBottom":"1.25em"},"li":{"marginTop":"0.5em","marginBottom":"0.5em"},"ol > li":{"paddingLeft":"1.75em"},"ol > li::before":{"left":"0"},"ul > li":{"paddingLeft":"1.75em"},"ul > li::before":{"width":"0.375em","height":"0.375em","top":"calc(0.875em - 0.1875em)","left":"0.25em"},"> ul > li p":{"marginTop":"0.75em","marginBottom":"0.75em"},"> ul > li > *:first-child":{"marginTop":"1.25em"},"> ul > li > *:last-child":{"marginBottom":"1.25em"},"> ol > li > *:first-child":{"marginTop":"1.25em"},"> ol > li > *:last-child":{"marginBottom":"1.25em"},"ul ul, ul ol, ol ul, ol ol":{"marginTop":"0.75em","marginBottom":"0.75em"},"hr":{"marginTop":"3em","marginBottom":"3em"},"hr + *":{"marginTop":"0"},"h2 + *":{"marginTop":"0"},"h3 + *":{"marginTop":"0"},"h4 + *":{"marginTop":"0"},"table":{"fontSize":"0.875em","lineHeight":"1.7142857"},"thead th":{"paddingRight":"0.5714286em","paddingBottom":"0.5714286em","paddingLeft":"0.5714286em"},"thead th:first-child":{"paddingLeft":"0"},"thead th:last-child":{"paddingRight":"0"},"tbody td":{"paddingTop":"0.5714286em","paddingRight":"0.5714286em","paddingBottom":"0.5714286em","paddingLeft":"0.5714286em"},"tbody td:first-child":{"paddingLeft":"0"},"tbody td:last-child":{"paddingRight":"0"}},{"> :first-child":{"marginTop":"0"},"> :last-child":{"marginBottom":"0"}}]},"sm":{"css":[{"fontSize":"0.875rem","lineHeight":"1.7142857","p":{"marginTop":"1.1428571em","marginBottom":"1.1428571em"},"[class~=\"lead\"]":{"fontSize":"1.2857143em","lineHeight":"1.5555556","marginTop":"0.8888889em","marginBottom":"0.8888889em"},"blockquote":{"marginTop":"1.3333333em","marginBottom":"1.3333333em","paddingLeft":"1.1111111em"},"h1":{"fontSize":"2.1428571em","marginTop":"0","marginBottom":"0.8em","lineHeight":"1.2"},"h2":{"fontSize":"1.4285714em","marginTop":"1.6em","marginBottom":"0.8em","lineHeight":"1.4"},"h3":{"fontSize":"1.2857143em","marginTop":"1.5555556em","marginBottom":"0.4444444em","lineHeight":"1.5555556"},"h4":{"marginTop":"1.4285714em","marginBottom":"0.5714286em","lineHeight":"1.4285714"},"img":{"marginTop":"1.7142857em","marginBottom":"1.7142857em"},"video":{"marginTop":"1.7142857em","marginBottom":"1.7142857em"},"figure":{"marginTop":"1.7142857em","marginBottom":"1.7142857em"},"figure > *":{"marginTop":"0","marginBottom":"0"},"figure figcaption":{"fontSize":"0.8571429em","lineHeight":"1.3333333","marginTop":"0.6666667em"},"code":{"fontSize":"0.8571429em"},"h2 code":{"fontSize":"0.9em"},"h3 code":{"fontSize":"0.8888889em"},"pre":{"fontSize":"0.8571429em","lineHeight":"1.6666667","marginTop":"1.6666667em","marginBottom":"1.6666667em","borderRadius":"0.25rem","paddingTop":"0.6666667em","paddingRight":"1em","paddingBottom":"0.6666667em","paddingLeft":"1em"},"ol":{"marginTop":"1.1428571em","marginBottom":"1.1428571em"},"ul":{"marginTop":"1.1428571em","marginBottom":"1.1428571em"},"li":{"marginTop":"0.2857143em","marginBottom":"0.2857143em"},"ol > li":{"paddingLeft":"1.5714286em"},"ol > li::before":{"left":"0"},"ul > li":{"paddingLeft":"1.5714286em"},"ul > li::before":{"height":"0.3571429em","width":"0.3571429em","top":"calc(0.8571429em - 0.1785714em)","left":"0.2142857em"},"> ul > li p":{"marginTop":"0.5714286em","marginBottom":"0.5714286em"},"> ul > li > *:first-child":{"marginTop":"1.1428571em"},"> ul > li > *:last-child":{"marginBottom":"1.1428571em"},"> ol > li > *:first-child":{"marginTop":"1.1428571em"},"> ol > li > *:last-child":{"marginBottom":"1.1428571em"},"ul ul, ul ol, ol ul, ol ol":{"marginTop":"0.5714286em","marginBottom":"0.5714286em"},"hr":{"marginTop":"2.8571429em","marginBottom":"2.8571429em"},"hr + *":{"marginTop":"0"},"h2 + *":{"marginTop":"0"},"h3 + *":{"marginTop":"0"},"h4 + *":{"marginTop":"0"},"table":{"fontSize":"0.8571429em","lineHeight":"1.5"},"thead th":{"paddingRight":"1em","paddingBottom":"0.6666667em","paddingLeft":"1em"},"thead th:first-child":{"paddingLeft":"0"},"thead th:last-child":{"paddingRight":"0"},"tbody td":{"paddingTop":"0.6666667em","paddingRight":"1em","paddingBottom":"0.6666667em","paddingLeft":"1em"},"tbody td:first-child":{"paddingLeft":"0"},"tbody td:last-child":{"paddingRight":"0"}},{"> :first-child":{"marginTop":"0"},"> :last-child":{"marginBottom":"0"}}]},"lg":{"css":[{"fontSize":"1.125rem","lineHeight":"1.7777778","p":{"marginTop":"1.3333333em","marginBottom":"1.3333333em"},"[class~=\"lead\"]":{"fontSize":"1.2222222em","lineHeight":"1.4545455","marginTop":"1.0909091em","marginBottom":"1.0909091em"},"blockquote":{"marginTop":"1.6666667em","marginBottom":"1.6666667em","paddingLeft":"1em"},"h1":{"fontSize":"2.6666667em","marginTop":"0","marginBottom":"0.8333333em","lineHeight":"1"},"h2":{"fontSize":"1.6666667em","marginTop":"1.8666667em","marginBottom":"1.0666667em","lineHeight":"1.3333333"},"h3":{"fontSize":"1.3333333em","marginTop":"1.6666667em","marginBottom":"0.6666667em","lineHeight":"1.5"},"h4":{"marginTop":"1.7777778em","marginBottom":"0.4444444em","lineHeight":"1.5555556"},"img":{"marginTop":"1.7777778em","marginBottom":"1.7777778em"},"video":{"marginTop":"1.7777778em","marginBottom":"1.7777778em"},"figure":{"marginTop":"1.7777778em","marginBottom":"1.7777778em"},"figure > *":{"marginTop":"0","marginBottom":"0"},"figure figcaption":{"fontSize":"0.8888889em","lineHeight":"1.5","marginTop":"1em"},"code":{"fontSize":"0.8888889em"},"h2 code":{"fontSize":"0.8666667em"},"h3 code":{"fontSize":"0.875em"},"pre":{"fontSize":"0.8888889em","lineHeight":"1.75","marginTop":"2em","marginBottom":"2em","borderRadius":"0.375rem","paddingTop":"1em","paddingRight":"1.5em","paddingBottom":"1em","paddingLeft":"1.5em"},"ol":{"marginTop":"1.3333333em","marginBottom":"1.3333333em"},"ul":{"marginTop":"1.3333333em","marginBottom":"1.3333333em"},"li":{"marginTop":"0.6666667em","marginBottom":"0.6666667em"},"ol > li":{"paddingLeft":"1.6666667em"},"ol > li::before":{"left":"0"},"ul > li":{"paddingLeft":"1.6666667em"},"ul > li::before":{"width":"0.3333333em","height":"0.3333333em","top":"calc(0.8888889em - 0.1666667em)","left":"0.2222222em"},"> ul > li p":{"marginTop":"0.8888889em","marginBottom":"0.8888889em"},"> ul > li > *:first-child":{"marginTop":"1.3333333em"},"> ul > li > *:last-child":{"marginBottom":"1.3333333em"},"> ol > li > *:first-child":{"marginTop":"1.3333333em"},"> ol > li > *:last-child":{"marginBottom":"1.3333333em"},"ul ul, ul ol, ol ul, ol ol":{"marginTop":"0.8888889em","marginBottom":"0.8888889em"},"hr":{"marginTop":"3.1111111em","marginBottom":"3.1111111em"},"hr + *":{"marginTop":"0"},"h2 + *":{"marginTop":"0"},"h3 + *":{"marginTop":"0"},"h4 + *":{"marginTop":"0"},"table":{"fontSize":"0.8888889em","lineHeight":"1.5"},"thead th":{"paddingRight":"0.75em","paddingBottom":"0.75em","paddingLeft":"0.75em"},"thead th:first-child":{"paddingLeft":"0"},"thead th:last-child":{"paddingRight":"0"},"tbody td":{"paddingTop":"0.75em","paddingRight":"0.75em","paddingBottom":"0.75em","paddingLeft":"0.75em"},"tbody td:first-child":{"paddingLeft":"0"},"tbody td:last-child":{"paddingRight":"0"}},{"> :first-child":{"marginTop":"0"},"> :last-child":{"marginBottom":"0"}}]},"xl":{"css":[{"fontSize":"1.25rem","lineHeight":"1.8","p":{"marginTop":"1.2em","marginBottom":"1.2em"},"[class~=\"lead\"]":{"fontSize":"1.2em","lineHeight":"1.5","marginTop":"1em","marginBottom":"1em"},"blockquote":{"marginTop":"1.6em","marginBottom":"1.6em","paddingLeft":"1.0666667em"},"h1":{"fontSize":"2.8em","marginTop":"0","marginBottom":"0.8571429em","lineHeight":"1"},"h2":{"fontSize":"1.8em","marginTop":"1.5555556em","marginBottom":"0.8888889em","lineHeight":"1.1111111"},"h3":{"fontSize":"1.5em","marginTop":"1.6em","marginBottom":"0.6666667em","lineHeight":"1.3333333"},"h4":{"marginTop":"1.8em","marginBottom":"0.6em","lineHeight":"1.6"},"img":{"marginTop":"2em","marginBottom":"2em"},"video":{"marginTop":"2em","marginBottom":"2em"},"figure":{"marginTop":"2em","marginBottom":"2em"},"figure > *":{"marginTop":"0","marginBottom":"0"},"figure figcaption":{"fontSize":"0.9em","lineHeight":"1.5555556","marginTop":"1em"},"code":{"fontSize":"0.9em"},"h2 code":{"fontSize":"0.8611111em"},"h3 code":{"fontSize":"0.9em"},"pre":{"fontSize":"0.9em","lineHeight":"1.7777778","marginTop":"2em","marginBottom":"2em","borderRadius":"0.5rem","paddingTop":"1.1111111em","paddingRight":"1.3333333em","paddingBottom":"1.1111111em","paddingLeft":"1.3333333em"},"ol":{"marginTop":"1.2em","marginBottom":"1.2em"},"ul":{"marginTop":"1.2em","marginBottom":"1.2em"},"li":{"marginTop":"0.6em","marginBottom":"0.6em"},"ol > li":{"paddingLeft":"1.8em"},"ol > li::before":{"left":"0"},"ul > li":{"paddingLeft":"1.8em"},"ul > li::before":{"width":"0.35em","height":"0.35em","top":"calc(0.9em - 0.175em)","left":"0.25em"},"> ul > li p":{"marginTop":"0.8em","marginBottom":"0.8em"},"> ul > li > *:first-child":{"marginTop":"1.2em"},"> ul > li > *:last-child":{"marginBottom":"1.2em"},"> ol > li > *:first-child":{"marginTop":"1.2em"},"> ol > li > *:last-child":{"marginBottom":"1.2em"},"ul ul, ul ol, ol ul, ol ol":{"marginTop":"0.8em","marginBottom":"0.8em"},"hr":{"marginTop":"2.8em","marginBottom":"2.8em"},"hr + *":{"marginTop":"0"},"h2 + *":{"marginTop":"0"},"h3 + *":{"marginTop":"0"},"h4 + *":{"marginTop":"0"},"table":{"fontSize":"0.9em","lineHeight":"1.5555556"},"thead th":{"paddingRight":"0.6666667em","paddingBottom":"0.8888889em","paddingLeft":"0.6666667em"},"thead th:first-child":{"paddingLeft":"0"},"thead th:last-child":{"paddingRight":"0"},"tbody td":{"paddingTop":"0.8888889em","paddingRight":"0.6666667em","paddingBottom":"0.8888889em","paddingLeft":"0.6666667em"},"tbody td:first-child":{"paddingLeft":"0"},"tbody td:last-child":{"paddingRight":"0"}},{"> :first-child":{"marginTop":"0"},"> :last-child":{"marginBottom":"0"}}]},"2xl":{"css":[{"fontSize":"1.5rem","lineHeight":"1.6666667","p":{"marginTop":"1.3333333em","marginBottom":"1.3333333em"},"[class~=\"lead\"]":{"fontSize":"1.25em","lineHeight":"1.4666667","marginTop":"1.0666667em","marginBottom":"1.0666667em"},"blockquote":{"marginTop":"1.7777778em","marginBottom":"1.7777778em","paddingLeft":"1.1111111em"},"h1":{"fontSize":"2.6666667em","marginTop":"0","marginBottom":"0.875em","lineHeight":"1"},"h2":{"fontSize":"2em","marginTop":"1.5em","marginBottom":"0.8333333em","lineHeight":"1.0833333"},"h3":{"fontSize":"1.5em","marginTop":"1.5555556em","marginBottom":"0.6666667em","lineHeight":"1.2222222"},"h4":{"marginTop":"1.6666667em","marginBottom":"0.6666667em","lineHeight":"1.5"},"img":{"marginTop":"2em","marginBottom":"2em"},"video":{"marginTop":"2em","marginBottom":"2em"},"figure":{"marginTop":"2em","marginBottom":"2em"},"figure > *":{"marginTop":"0","marginBottom":"0"},"figure figcaption":{"fontSize":"0.8333333em","lineHeight":"1.6","marginTop":"1em"},"code":{"fontSize":"0.8333333em"},"h2 code":{"fontSize":"0.875em"},"h3 code":{"fontSize":"0.8888889em"},"pre":{"fontSize":"0.8333333em","lineHeight":"1.8","marginTop":"2em","marginBottom":"2em","borderRadius":"0.5rem","paddingTop":"1.2em","paddingRight":"1.6em","paddingBottom":"1.2em","paddingLeft":"1.6em"},"ol":{"marginTop":"1.3333333em","marginBottom":"1.3333333em"},"ul":{"marginTop":"1.3333333em","marginBottom":"1.3333333em"},"li":{"marginTop":"0.5em","marginBottom":"0.5em"},"ol > li":{"paddingLeft":"1.6666667em"},"ol > li::before":{"left":"0"},"ul > li":{"paddingLeft":"1.6666667em"},"ul > li::before":{"width":"0.3333333em","height":"0.3333333em","top":"calc(0.8333333em - 0.1666667em)","left":"0.25em"},"> ul > li p":{"marginTop":"0.8333333em","marginBottom":"0.8333333em"},"> ul > li > *:first-child":{"marginTop":"1.3333333em"},"> ul > li > *:last-child":{"marginBottom":"1.3333333em"},"> ol > li > *:first-child":{"marginTop":"1.3333333em"},"> ol > li > *:last-child":{"marginBottom":"1.3333333em"},"ul ul, ul ol, ol ul, ol ol":{"marginTop":"0.6666667em","marginBottom":"0.6666667em"},"hr":{"marginTop":"3em","marginBottom":"3em"},"hr + *":{"marginTop":"0"},"h2 + *":{"marginTop":"0"},"h3 + *":{"marginTop":"0"},"h4 + *":{"marginTop":"0"},"table":{"fontSize":"0.8333333em","lineHeight":"1.4"},"thead th":{"paddingRight":"0.6em","paddingBottom":"0.8em","paddingLeft":"0.6em"},"thead th:first-child":{"paddingLeft":"0"},"thead th:last-child":{"paddingRight":"0"},"tbody td":{"paddingTop":"0.8em","paddingRight":"0.6em","paddingBottom":"0.8em","paddingLeft":"0.6em"},"tbody td:first-child":{"paddingLeft":"0"},"tbody td:last-child":{"paddingRight":"0"}},{"> :first-child":{"marginTop":"0"},"> :last-child":{"marginBottom":"0"}}]},"red":{"css":[{"a":{"color":"#dc2626"},"a code":{"color":"#dc2626"}}]},"yellow":{"css":[{"a":{"color":"#d97706"},"a code":{"color":"#d97706"}}]},"green":{"css":[{"a":{"color":"#059669"},"a code":{"color":"#059669"}}]},"blue":{"css":[{"a":{"color":"#2563eb"},"a code":{"color":"#2563eb"}}]},"indigo":{"css":[{"a":{"color":"#4f46e5"},"a code":{"color":"#4f46e5"}}]},"purple":{"css":[{"a":{"color":"#7c3aed"},"a code":{"color":"#7c3aed"}}]},"pink":{"css":[{"a":{"color":"#db2777"},"a code":{"color":"#db2777"}}]}},"aspectRatio":{"1":"1","2":"2","3":"3","4":"4","5":"5","6":"6","7":"7","8":"8","9":"9","10":"10","11":"11","12":"12","13":"13","14":"14","15":"15","16":"16"},"screens":{"sm":"640px","md":"768px","lg":"1024px","xl":"1280px","2xl":"1536px"},"colors":{"transparent":"transparent","current":"currentColor","black":"#000","white":"#fff","gray":{"50":"#f9fafb","100":"#f3f4f6","200":"#e5e7eb","300":"#d1d5db","400":"#9ca3af","500":"#6b7280","600":"#4b5563","700":"#374151","800":"#1f2937","900":"#111827"},"red":{"50":"#fef2f2","100":"#fee2e2","200":"#fecaca","300":"#fca5a5","400":"#f87171","500":"#ef4444","600":"#dc2626","700":"#b91c1c","800":"#991b1b","900":"#7f1d1d"},"yellow":{"50":"#fffbeb","100":"#fef3c7","200":"#fde68a","300":"#fcd34d","400":"#fbbf24","500":"#f59e0b","600":"#d97706","700":"#b45309","800":"#92400e","900":"#78350f"},"green":{"50":"#ecfdf5","100":"#d1fae5","200":"#a7f3d0","300":"#6ee7b7","400":"#34d399","500":"#10b981","600":"#059669","700":"#047857","800":"#065f46","900":"#064e3b"},"blue":{"50":"#eff6ff","100":"#dbeafe","200":"#bfdbfe","300":"#93c5fd","400":"#60a5fa","500":"#3b82f6","600":"#2563eb","700":"#1d4ed8","800":"#1e40af","900":"#1e3a8a"},"indigo":{"50":"#eef2ff","100":"#e0e7ff","200":"#c7d2fe","300":"#a5b4fc","400":"#818cf8","500":"#6366f1","600":"#4f46e5","700":"#4338ca","800":"#3730a3","900":"#312e81"},"purple":{"50":"#f5f3ff","100":"#ede9fe","200":"#ddd6fe","300":"#c4b5fd","400":"#a78bfa","500":"#8b5cf6","600":"#7c3aed","700":"#6d28d9","800":"#5b21b6","900":"#4c1d95"},"pink":{"50":"#fdf2f8","100":"#fce7f3","200":"#fbcfe8","300":"#f9a8d4","400":"#f472b6","500":"#ec4899","600":"#db2777","700":"#be185d","800":"#9d174d","900":"#831843"}},"spacing":{"0":"0px","1":"0.25rem","2":"0.5rem","3":"0.75rem","4":"1rem","5":"1.25rem","6":"1.5rem","7":"1.75rem","8":"2rem","9":"2.25rem","10":"2.5rem","11":"2.75rem","12":"3rem","14":"3.5rem","16":"4rem","20":"5rem","24":"6rem","28":"7rem","32":"8rem","36":"9rem","40":"10rem","44":"11rem","48":"12rem","52":"13rem","56":"14rem","60":"15rem","64":"16rem","72":"18rem","80":"20rem","96":"24rem","px":"1px","0.5":"0.125rem","1.5":"0.375rem","2.5":"0.625rem","3.5":"0.875rem"},"animation":{"none":"none","spin":"spin 1s linear infinite","ping":"ping 1s cubic-bezier(0, 0, 0.2, 1) infinite","pulse":"pulse 2s cubic-bezier(0.4, 0, 0.6, 1) infinite","bounce":"bounce 1s infinite"},"backgroundColor":{"transparent":"transparent","current":"currentColor","black":"#000","white":"#fff","gray":{"50":"#f9fafb","100":"#f3f4f6","200":"#e5e7eb","300":"#d1d5db","400":"#9ca3af","500":"#6b7280","600":"#4b5563","700":"#374151","800":"#1f2937","900":"#111827"},"red":{"50":"#fef2f2","100":"#fee2e2","200":"#fecaca","300":"#fca5a5","400":"#f87171","500":"#ef4444","600":"#dc2626","700":"#b91c1c","800":"#991b1b","900":"#7f1d1d"},"yellow":{"50":"#fffbeb","100":"#fef3c7","200":"#fde68a","300":"#fcd34d","400":"#fbbf24","500":"#f59e0b","600":"#d97706","700":"#b45309","800":"#92400e","900":"#78350f"},"green":{"50":"#ecfdf5","100":"#d1fae5","200":"#a7f3d0","300":"#6ee7b7","400":"#34d399","500":"#10b981","600":"#059669","700":"#047857","800":"#065f46","900":"#064e3b"},"blue":{"50":"#eff6ff","100":"#dbeafe","200":"#bfdbfe","300":"#93c5fd","400":"#60a5fa","500":"#3b82f6","600":"#2563eb","700":"#1d4ed8","800":"#1e40af","900":"#1e3a8a"},"indigo":{"50":"#eef2ff","100":"#e0e7ff","200":"#c7d2fe","300":"#a5b4fc","400":"#818cf8","500":"#6366f1","600":"#4f46e5","700":"#4338ca","800":"#3730a3","900":"#312e81"},"purple":{"50":"#f5f3ff","100":"#ede9fe","200":"#ddd6fe","300":"#c4b5fd","400":"#a78bfa","500":"#8b5cf6","600":"#7c3aed","700":"#6d28d9","800":"#5b21b6","900":"#4c1d95"},"pink":{"50":"#fdf2f8","100":"#fce7f3","200":"#fbcfe8","300":"#f9a8d4","400":"#f472b6","500":"#ec4899","600":"#db2777","700":"#be185d","800":"#9d174d","900":"#831843"}},"backgroundImage":{"none":"none","gradient-to-t":"linear-gradient(to top, var(--tw-gradient-stops))","gradient-to-tr":"linear-gradient(to top right, var(--tw-gradient-stops))","gradient-to-r":"linear-gradient(to right, var(--tw-gradient-stops))","gradient-to-br":"linear-gradient(to bottom right, var(--tw-gradient-stops))","gradient-to-b":"linear-gradient(to bottom, var(--tw-gradient-stops))","gradient-to-bl":"linear-gradient(to bottom left, var(--tw-gradient-stops))","gradient-to-l":"linear-gradient(to left, var(--tw-gradient-stops))","gradient-to-tl":"linear-gradient(to top left, var(--tw-gradient-stops))"},"backgroundOpacity":{"0":"0","5":"0.05","10":"0.1","20":"0.2","25":"0.25","30":"0.3","40":"0.4","50":"0.5","60":"0.6","70":"0.7","75":"0.75","80":"0.8","90":"0.9","95":"0.95","100":"1"},"backgroundPosition":{"bottom":"bottom","center":"center","left":"left","left-bottom":"left bottom","left-top":"left top","right":"right","right-bottom":"right bottom","right-top":"right top","top":"top"},"backgroundSize":{"auto":"auto","cover":"cover","contain":"contain"},"borderColor":{"transparent":"transparent","current":"currentColor","black":"#000","white":"#fff","gray":{"50":"#f9fafb","100":"#f3f4f6","200":"#e5e7eb","300":"#d1d5db","400":"#9ca3af","500":"#6b7280","600":"#4b5563","700":"#374151","800":"#1f2937","900":"#111827"},"red":{"50":"#fef2f2","100":"#fee2e2","200":"#fecaca","300":"#fca5a5","400":"#f87171","500":"#ef4444","600":"#dc2626","700":"#b91c1c","800":"#991b1b","900":"#7f1d1d"},"yellow":{"50":"#fffbeb","100":"#fef3c7","200":"#fde68a","300":"#fcd34d","400":"#fbbf24","500":"#f59e0b","600":"#d97706","700":"#b45309","800":"#92400e","900":"#78350f"},"green":{"50":"#ecfdf5","100":"#d1fae5","200":"#a7f3d0","300":"#6ee7b7","400":"#34d399","500":"#10b981","600":"#059669","700":"#047857","800":"#065f46","900":"#064e3b"},"blue":{"50":"#eff6ff","100":"#dbeafe","200":"#bfdbfe","300":"#93c5fd","400":"#60a5fa","500":"#3b82f6","600":"#2563eb","700":"#1d4ed8","800":"#1e40af","900":"#1e3a8a"},"indigo":{"50":"#eef2ff","100":"#e0e7ff","200":"#c7d2fe","300":"#a5b4fc","400":"#818cf8","500":"#6366f1","600":"#4f46e5","700":"#4338ca","800":"#3730a3","900":"#312e81"},"purple":{"50":"#f5f3ff","100":"#ede9fe","200":"#ddd6fe","300":"#c4b5fd","400":"#a78bfa","500":"#8b5cf6","600":"#7c3aed","700":"#6d28d9","800":"#5b21b6","900":"#4c1d95"},"pink":{"50":"#fdf2f8","100":"#fce7f3","200":"#fbcfe8","300":"#f9a8d4","400":"#f472b6","500":"#ec4899","600":"#db2777","700":"#be185d","800":"#9d174d","900":"#831843"},"DEFAULT":"#e5e7eb"},"borderOpacity":{"0":"0","5":"0.05","10":"0.1","20":"0.2","25":"0.25","30":"0.3","40":"0.4","50":"0.5","60":"0.6","70":"0.7","75":"0.75","80":"0.8","90":"0.9","95":"0.95","100":"1"},"borderRadius":{"none":"0px","sm":"0.125rem","DEFAULT":"0.25rem","md":"0.375rem","lg":"0.5rem","xl":"0.75rem","2xl":"1rem","3xl":"1.5rem","full":"9999px"},"borderWidth":{"0":"0px","2":"2px","4":"4px","8":"8px","DEFAULT":"1px"},"boxShadow":{"sm":"0 1px 2px 0 rgba(0, 0, 0, 0.05)","DEFAULT":"0 1px 3px 0 rgba(0, 0, 0, 0.1), 0 1px 2px 0 rgba(0, 0, 0, 0.06)","md":"0 4px 6px -1px rgba(0, 0, 0, 0.1), 0 2px 4px -1px rgba(0, 0, 0, 0.06)","lg":"0 10px 15px -3px rgba(0, 0, 0, 0.1), 0 4px 6px -2px rgba(0, 0, 0, 0.05)","xl":"0 20px 25px -5px rgba(0, 0, 0, 0.1), 0 10px 10px -5px rgba(0, 0, 0, 0.04)","2xl":"0 25px 50px -12px rgba(0, 0, 0, 0.25)","inner":"inset 0 2px 4px 0 rgba(0, 0, 0, 0.06)","none":"none"},"container":{},"cursor":{"auto":"auto","default":"default","pointer":"pointer","wait":"wait","text":"text","move":"move","not-allowed":"not-allowed"},"divideColor":{"transparent":"transparent","current":"currentColor","black":"#000","white":"#fff","gray":{"50":"#f9fafb","100":"#f3f4f6","200":"#e5e7eb","300":"#d1d5db","400":"#9ca3af","500":"#6b7280","600":"#4b5563","700":"#374151","800":"#1f2937","900":"#111827"},"red":{"50":"#fef2f2","100":"#fee2e2","200":"#fecaca","300":"#fca5a5","400":"#f87171","500":"#ef4444","600":"#dc2626","700":"#b91c1c","800":"#991b1b","900":"#7f1d1d"},"yellow":{"50":"#fffbeb","100":"#fef3c7","200":"#fde68a","300":"#fcd34d","400":"#fbbf24","500":"#f59e0b","600":"#d97706","700":"#b45309","800":"#92400e","900":"#78350f"},"green":{"50":"#ecfdf5","100":"#d1fae5","200":"#a7f3d0","300":"#6ee7b7","400":"#34d399","500":"#10b981","600":"#059669","700":"#047857","800":"#065f46","900":"#064e3b"},"blue":{"50":"#eff6ff","100":"#dbeafe","200":"#bfdbfe","300":"#93c5fd","400":"#60a5fa","500":"#3b82f6","600":"#2563eb","700":"#1d4ed8","800":"#1e40af","900":"#1e3a8a"},"indigo":{"50":"#eef2ff","100":"#e0e7ff","200":"#c7d2fe","300":"#a5b4fc","400":"#818cf8","500":"#6366f1","600":"#4f46e5","700":"#4338ca","800":"#3730a3","900":"#312e81"},"purple":{"50":"#f5f3ff","100":"#ede9fe","200":"#ddd6fe","300":"#c4b5fd","400":"#a78bfa","500":"#8b5cf6","600":"#7c3aed","700":"#6d28d9","800":"#5b21b6","900":"#4c1d95"},"pink":{"50":"#fdf2f8","100":"#fce7f3","200":"#fbcfe8","300":"#f9a8d4","400":"#f472b6","500":"#ec4899","600":"#db2777","700":"#be185d","800":"#9d174d","900":"#831843"},"DEFAULT":"#e5e7eb"},"divideOpacity":{"0":"0","5":"0.05","10":"0.1","20":"0.2","25":"0.25","30":"0.3","40":"0.4","50":"0.5","60":"0.6","70":"0.7","75":"0.75","80":"0.8","90":"0.9","95":"0.95","100":"1"},"divideWidth":{"0":"0px","2":"2px","4":"4px","8":"8px","DEFAULT":"1px"},"fill":{"current":"currentColor"},"flex":{"1":"1 1 0%","auto":"1 1 auto","initial":"0 1 auto","none":"none"},"flexGrow":{"0":"0","DEFAULT":"1"},"flexShrink":{"0":"0","DEFAULT":"1"},"fontFamily":{"sans":["ui-sans-serif","system-ui","-apple-system","BlinkMacSystemFont","\"Segoe UI\"","Roboto","\"Helvetica Neue\"","Arial","\"Noto Sans\"","sans-serif","\"Apple Color Emoji\"","\"Segoe UI Emoji\"","\"Segoe UI Symbol\"","\"Noto Color Emoji\""],"serif":["ui-serif","Georgia","Cambria","\"Times New Roman\"","Times","serif"],"mono":["ui-monospace","SFMono-Regular","Menlo","Monaco","Consolas","\"Liberation Mono\"","\"Courier New\"","monospace"]},"fontSize":{"xs":["0.75rem",{"lineHeight":"1rem"}],"sm":["0.875rem",{"lineHeight":"1.25rem"}],"base":["1rem",{"lineHeight":"1.5rem"}],"lg":["1.125rem",{"lineHeight":"1.75rem"}],"xl":["1.25rem",{"lineHeight":"1.75rem"}],"2xl":["1.5rem",{"lineHeight":"2rem"}],"3xl":["1.875rem",{"lineHeight":"2.25rem"}],"4xl":["2.25rem",{"lineHeight":"2.5rem"}],"5xl":["3rem",{"lineHeight":"1"}],"6xl":["3.75rem",{"lineHeight":"1"}],"7xl":["4.5rem",{"lineHeight":"1"}],"8xl":["6rem",{"lineHeight":"1"}],"9xl":["8rem",{"lineHeight":"1"}]},"fontWeight":{"thin":"100","extralight":"200","light":"300","normal":"400","medium":"500","semibold":"600","bold":"700","extrabold":"800","black":"900"},"gap":{"0":"0px","1":"0.25rem","2":"0.5rem","3":"0.75rem","4":"1rem","5":"1.25rem","6":"1.5rem","7":"1.75rem","8":"2rem","9":"2.25rem","10":"2.5rem","11":"2.75rem","12":"3rem","14":"3.5rem","16":"4rem","20":"5rem","24":"6rem","28":"7rem","32":"8rem","36":"9rem","40":"10rem","44":"11rem","48":"12rem","52":"13rem","56":"14rem","60":"15rem","64":"16rem","72":"18rem","80":"20rem","96":"24rem","px":"1px","0.5":"0.125rem","1.5":"0.375rem","2.5":"0.625rem","3.5":"0.875rem"},"gradientColorStops":{"transparent":"transparent","current":"currentColor","black":"#000","white":"#fff","gray":{"50":"#f9fafb","100":"#f3f4f6","200":"#e5e7eb","300":"#d1d5db","400":"#9ca3af","500":"#6b7280","600":"#4b5563","700":"#374151","800":"#1f2937","900":"#111827"},"red":{"50":"#fef2f2","100":"#fee2e2","200":"#fecaca","300":"#fca5a5","400":"#f87171","500":"#ef4444","600":"#dc2626","700":"#b91c1c","800":"#991b1b","900":"#7f1d1d"},"yellow":{"50":"#fffbeb","100":"#fef3c7","200":"#fde68a","300":"#fcd34d","400":"#fbbf24","500":"#f59e0b","600":"#d97706","700":"#b45309","800":"#92400e","900":"#78350f"},"green":{"50":"#ecfdf5","100":"#d1fae5","200":"#a7f3d0","300":"#6ee7b7","400":"#34d399","500":"#10b981","600":"#059669","700":"#047857","800":"#065f46","900":"#064e3b"},"blue":{"50":"#eff6ff","100":"#dbeafe","200":"#bfdbfe","300":"#93c5fd","400":"#60a5fa","500":"#3b82f6","600":"#2563eb","700":"#1d4ed8","800":"#1e40af","900":"#1e3a8a"},"indigo":{"50":"#eef2ff","100":"#e0e7ff","200":"#c7d2fe","300":"#a5b4fc","400":"#818cf8","500":"#6366f1","600":"#4f46e5","700":"#4338ca","800":"#3730a3","900":"#312e81"},"purple":{"50":"#f5f3ff","100":"#ede9fe","200":"#ddd6fe","300":"#c4b5fd","400":"#a78bfa","500":"#8b5cf6","600":"#7c3aed","700":"#6d28d9","800":"#5b21b6","900":"#4c1d95"},"pink":{"50":"#fdf2f8","100":"#fce7f3","200":"#fbcfe8","300":"#f9a8d4","400":"#f472b6","500":"#ec4899","600":"#db2777","700":"#be185d","800":"#9d174d","900":"#831843"}},"gridAutoColumns":{"auto":"auto","min":"min-content","max":"max-content","fr":"minmax(0, 1fr)"},"gridAutoRows":{"auto":"auto","min":"min-content","max":"max-content","fr":"minmax(0, 1fr)"},"gridColumn":{"auto":"auto","span-1":"span 1 / span 1","span-2":"span 2 / span 2","span-3":"span 3 / span 3","span-4":"span 4 / span 4","span-5":"span 5 / span 5","span-6":"span 6 / span 6","span-7":"span 7 / span 7","span-8":"span 8 / span 8","span-9":"span 9 / span 9","span-10":"span 10 / span 10","span-11":"span 11 / span 11","span-12":"span 12 / span 12","span-full":"1 / -1"},"gridColumnEnd":{"1":"1","2":"2","3":"3","4":"4","5":"5","6":"6","7":"7","8":"8","9":"9","10":"10","11":"11","12":"12","13":"13","auto":"auto"},"gridColumnStart":{"1":"1","2":"2","3":"3","4":"4","5":"5","6":"6","7":"7","8":"8","9":"9","10":"10","11":"11","12":"12","13":"13","auto":"auto"},"gridRow":{"auto":"auto","span-1":"span 1 / span 1","span-2":"span 2 / span 2","span-3":"span 3 / span 3","span-4":"span 4 / span 4","span-5":"span 5 / span 5","span-6":"span 6 / span 6","span-full":"1 / -1"},"gridRowStart":{"1":"1","2":"2","3":"3","4":"4","5":"5","6":"6","7":"7","auto":"auto"},"gridRowEnd":{"1":"1","2":"2","3":"3","4":"4","5":"5","6":"6","7":"7","auto":"auto"},"transformOrigin":{"center":"center","top":"top","top-right":"top right","right":"right","bottom-right":"bottom right","bottom":"bottom","bottom-left":"bottom left","left":"left","top-left":"top left"},"gridTemplateColumns":{"1":"repeat(1, minmax(0, 1fr))","2":"repeat(2, minmax(0, 1fr))","3":"repeat(3, minmax(0, 1fr))","4":"repeat(4, minmax(0, 1fr))","5":"repeat(5, minmax(0, 1fr))","6":"repeat(6, minmax(0, 1fr))","7":"repeat(7, minmax(0, 1fr))","8":"repeat(8, minmax(0, 1fr))","9":"repeat(9, minmax(0, 1fr))","10":"repeat(10, minmax(0, 1fr))","11":"repeat(11, minmax(0, 1fr))","12":"repeat(12, minmax(0, 1fr))","none":"none"},"gridTemplateRows":{"1":"repeat(1, minmax(0, 1fr))","2":"repeat(2, minmax(0, 1fr))","3":"repeat(3, minmax(0, 1fr))","4":"repeat(4, minmax(0, 1fr))","5":"repeat(5, minmax(0, 1fr))","6":"repeat(6, minmax(0, 1fr))","none":"none"},"height":{"0":"0px","1":"0.25rem","2":"0.5rem","3":"0.75rem","4":"1rem","5":"1.25rem","6":"1.5rem","7":"1.75rem","8":"2rem","9":"2.25rem","10":"2.5rem","11":"2.75rem","12":"3rem","14":"3.5rem","16":"4rem","20":"5rem","24":"6rem","28":"7rem","32":"8rem","36":"9rem","40":"10rem","44":"11rem","48":"12rem","52":"13rem","56":"14rem","60":"15rem","64":"16rem","72":"18rem","80":"20rem","96":"24rem","auto":"auto","px":"1px","0.5":"0.125rem","1.5":"0.375rem","2.5":"0.625rem","3.5":"0.875rem","1/2":"50%","1/3":"33.333333%","2/3":"66.666667%","1/4":"25%","2/4":"50%","3/4":"75%","1/5":"20%","2/5":"40%","3/5":"60%","4/5":"80%","1/6":"16.666667%","2/6":"33.333333%","3/6":"50%","4/6":"66.666667%","5/6":"83.333333%","full":"100%","screen":"100vh"},"inset":{"0":"0px","1":"0.25rem","2":"0.5rem","3":"0.75rem","4":"1rem","5":"1.25rem","6":"1.5rem","7":"1.75rem","8":"2rem","9":"2.25rem","10":"2.5rem","11":"2.75rem","12":"3rem","14":"3.5rem","16":"4rem","20":"5rem","24":"6rem","28":"7rem","32":"8rem","36":"9rem","40":"10rem","44":"11rem","48":"12rem","52":"13rem","56":"14rem","60":"15rem","64":"16rem","72":"18rem","80":"20rem","96":"24rem","auto":"auto","px":"1px","0.5":"0.125rem","1.5":"0.375rem","2.5":"0.625rem","3.5":"0.875rem","-0":"0px","-1":"-0.25rem","-2":"-0.5rem","-3":"-0.75rem","-4":"-1rem","-5":"-1.25rem","-6":"-1.5rem","-7":"-1.75rem","-8":"-2rem","-9":"-2.25rem","-10":"-2.5rem","-11":"-2.75rem","-12":"-3rem","-14":"-3.5rem","-16":"-4rem","-20":"-5rem","-24":"-6rem","-28":"-7rem","-32":"-8rem","-36":"-9rem","-40":"-10rem","-44":"-11rem","-48":"-12rem","-52":"-13rem","-56":"-14rem","-60":"-15rem","-64":"-16rem","-72":"-18rem","-80":"-20rem","-96":"-24rem","-px":"-1px","-0.5":"-0.125rem","-1.5":"-0.375rem","-2.5":"-0.625rem","-3.5":"-0.875rem","1/2":"50%","1/3":"33.333333%","2/3":"66.666667%","1/4":"25%","2/4":"50%","3/4":"75%","full":"100%","-1/2":"-50%","-1/3":"-33.333333%","-2/3":"-66.666667%","-1/4":"-25%","-2/4":"-50%","-3/4":"-75%","-full":"-100%"},"keyframes":{"spin":{"to":{"transform":"rotate(360deg)"}},"ping":{"75%, 100%":{"transform":"scale(2)","opacity":"0"}},"pulse":{"50%":{"opacity":".5"}},"bounce":{"0%, 100%":{"transform":"translateY(-25%)","animationTimingFunction":"cubic-bezier(0.8,0,1,1)"},"50%":{"transform":"none","animationTimingFunction":"cubic-bezier(0,0,0.2,1)"}}},"letterSpacing":{"tighter":"-0.05em","tight":"-0.025em","normal":"0em","wide":"0.025em","wider":"0.05em","widest":"0.1em"},"lineHeight":{"3":".75rem","4":"1rem","5":"1.25rem","6":"1.5rem","7":"1.75rem","8":"2rem","9":"2.25rem","10":"2.5rem","none":"1","tight":"1.25","snug":"1.375","normal":"1.5","relaxed":"1.625","loose":"2"},"listStyleType":{"none":"none","disc":"disc","decimal":"decimal"},"margin":{"0":"0px","1":"0.25rem","2":"0.5rem","3":"0.75rem","4":"1rem","5":"1.25rem","6":"1.5rem","7":"1.75rem","8":"2rem","9":"2.25rem","10":"2.5rem","11":"2.75rem","12":"3rem","14":"3.5rem","16":"4rem","20":"5rem","24":"6rem","28":"7rem","32":"8rem","36":"9rem","40":"10rem","44":"11rem","48":"12rem","52":"13rem","56":"14rem","60":"15rem","64":"16rem","72":"18rem","80":"20rem","96":"24rem","auto":"auto","px":"1px","0.5":"0.125rem","1.5":"0.375rem","2.5":"0.625rem","3.5":"0.875rem","-0":"0px","-1":"-0.25rem","-2":"-0.5rem","-3":"-0.75rem","-4":"-1rem","-5":"-1.25rem","-6":"-1.5rem","-7":"-1.75rem","-8":"-2rem","-9":"-2.25rem","-10":"-2.5rem","-11":"-2.75rem","-12":"-3rem","-14":"-3.5rem","-16":"-4rem","-20":"-5rem","-24":"-6rem","-28":"-7rem","-32":"-8rem","-36":"-9rem","-40":"-10rem","-44":"-11rem","-48":"-12rem","-52":"-13rem","-56":"-14rem","-60":"-15rem","-64":"-16rem","-72":"-18rem","-80":"-20rem","-96":"-24rem","-px":"-1px","-0.5":"-0.125rem","-1.5":"-0.375rem","-2.5":"-0.625rem","-3.5":"-0.875rem"},"maxHeight":{"0":"0px","1":"0.25rem","2":"0.5rem","3":"0.75rem","4":"1rem","5":"1.25rem","6":"1.5rem","7":"1.75rem","8":"2rem","9":"2.25rem","10":"2.5rem","11":"2.75rem","12":"3rem","14":"3.5rem","16":"4rem","20":"5rem","24":"6rem","28":"7rem","32":"8rem","36":"9rem","40":"10rem","44":"11rem","48":"12rem","52":"13rem","56":"14rem","60":"15rem","64":"16rem","72":"18rem","80":"20rem","96":"24rem","px":"1px","0.5":"0.125rem","1.5":"0.375rem","2.5":"0.625rem","3.5":"0.875rem","full":"100%","screen":"100vh"},"maxWidth":{"0":"0rem","none":"none","xs":"20rem","sm":"24rem","md":"28rem","lg":"32rem","xl":"36rem","2xl":"42rem","3xl":"48rem","4xl":"56rem","5xl":"64rem","6xl":"72rem","7xl":"80rem","full":"100%","min":"min-content","max":"max-content","prose":"65ch","screen-sm":"640px","screen-md":"768px","screen-lg":"1024px","screen-xl":"1280px","screen-2xl":"1536px"},"minHeight":{"0":"0px","full":"100%","screen":"100vh"},"minWidth":{"0":"0px","full":"100%","min":"min-content","max":"max-content"},"objectPosition":{"bottom":"bottom","center":"center","left":"left","left-bottom":"left bottom","left-top":"left top","right":"right","right-bottom":"right bottom","right-top":"right top","top":"top"},"opacity":{"0":"0","5":"0.05","10":"0.1","20":"0.2","25":"0.25","30":"0.3","40":"0.4","50":"0.5","60":"0.6","70":"0.7","75":"0.75","80":"0.8","90":"0.9","95":"0.95","100":"1"},"order":{"1":"1","2":"2","3":"3","4":"4","5":"5","6":"6","7":"7","8":"8","9":"9","10":"10","11":"11","12":"12","first":"-9999","last":"9999","none":"0"},"outline":{"none":["2px solid transparent","2px"],"white":["2px dotted white","2px"],"black":["2px dotted black","2px"]},"padding":{"0":"0px","1":"0.25rem","2":"0.5rem","3":"0.75rem","4":"1rem","5":"1.25rem","6":"1.5rem","7":"1.75rem","8":"2rem","9":"2.25rem","10":"2.5rem","11":"2.75rem","12":"3rem","14":"3.5rem","16":"4rem","20":"5rem","24":"6rem","28":"7rem","32":"8rem","36":"9rem","40":"10rem","44":"11rem","48":"12rem","52":"13rem","56":"14rem","60":"15rem","64":"16rem","72":"18rem","80":"20rem","96":"24rem","px":"1px","0.5":"0.125rem","1.5":"0.375rem","2.5":"0.625rem","3.5":"0.875rem"},"placeholderColor":{"transparent":"transparent","current":"currentColor","black":"#000","white":"#fff","gray":{"50":"#f9fafb","100":"#f3f4f6","200":"#e5e7eb","300":"#d1d5db","400":"#9ca3af","500":"#6b7280","600":"#4b5563","700":"#374151","800":"#1f2937","900":"#111827"},"red":{"50":"#fef2f2","100":"#fee2e2","200":"#fecaca","300":"#fca5a5","400":"#f87171","500":"#ef4444","600":"#dc2626","700":"#b91c1c","800":"#991b1b","900":"#7f1d1d"},"yellow":{"50":"#fffbeb","100":"#fef3c7","200":"#fde68a","300":"#fcd34d","400":"#fbbf24","500":"#f59e0b","600":"#d97706","700":"#b45309","800":"#92400e","900":"#78350f"},"green":{"50":"#ecfdf5","100":"#d1fae5","200":"#a7f3d0","300":"#6ee7b7","400":"#34d399","500":"#10b981","600":"#059669","700":"#047857","800":"#065f46","900":"#064e3b"},"blue":{"50":"#eff6ff","100":"#dbeafe","200":"#bfdbfe","300":"#93c5fd","400":"#60a5fa","500":"#3b82f6","600":"#2563eb","700":"#1d4ed8","800":"#1e40af","900":"#1e3a8a"},"indigo":{"50":"#eef2ff","100":"#e0e7ff","200":"#c7d2fe","300":"#a5b4fc","400":"#818cf8","500":"#6366f1","600":"#4f46e5","700":"#4338ca","800":"#3730a3","900":"#312e81"},"purple":{"50":"#f5f3ff","100":"#ede9fe","200":"#ddd6fe","300":"#c4b5fd","400":"#a78bfa","500":"#8b5cf6","600":"#7c3aed","700":"#6d28d9","800":"#5b21b6","900":"#4c1d95"},"pink":{"50":"#fdf2f8","100":"#fce7f3","200":"#fbcfe8","300":"#f9a8d4","400":"#f472b6","500":"#ec4899","600":"#db2777","700":"#be185d","800":"#9d174d","900":"#831843"}},"placeholderOpacity":{"0":"0","5":"0.05","10":"0.1","20":"0.2","25":"0.25","30":"0.3","40":"0.4","50":"0.5","60":"0.6","70":"0.7","75":"0.75","80":"0.8","90":"0.9","95":"0.95","100":"1"},"ringColor":{"DEFAULT":"#3b82f6","transparent":"transparent","current":"currentColor","black":"#000","white":"#fff","gray":{"50":"#f9fafb","100":"#f3f4f6","200":"#e5e7eb","300":"#d1d5db","400":"#9ca3af","500":"#6b7280","600":"#4b5563","700":"#374151","800":"#1f2937","900":"#111827"},"red":{"50":"#fef2f2","100":"#fee2e2","200":"#fecaca","300":"#fca5a5","400":"#f87171","500":"#ef4444","600":"#dc2626","700":"#b91c1c","800":"#991b1b","900":"#7f1d1d"},"yellow":{"50":"#fffbeb","100":"#fef3c7","200":"#fde68a","300":"#fcd34d","400":"#fbbf24","500":"#f59e0b","600":"#d97706","700":"#b45309","800":"#92400e","900":"#78350f"},"green":{"50":"#ecfdf5","100":"#d1fae5","200":"#a7f3d0","300":"#6ee7b7","400":"#34d399","500":"#10b981","600":"#059669","700":"#047857","800":"#065f46","900":"#064e3b"},"blue":{"50":"#eff6ff","100":"#dbeafe","200":"#bfdbfe","300":"#93c5fd","400":"#60a5fa","500":"#3b82f6","600":"#2563eb","700":"#1d4ed8","800":"#1e40af","900":"#1e3a8a"},"indigo":{"50":"#eef2ff","100":"#e0e7ff","200":"#c7d2fe","300":"#a5b4fc","400":"#818cf8","500":"#6366f1","600":"#4f46e5","700":"#4338ca","800":"#3730a3","900":"#312e81"},"purple":{"50":"#f5f3ff","100":"#ede9fe","200":"#ddd6fe","300":"#c4b5fd","400":"#a78bfa","500":"#8b5cf6","600":"#7c3aed","700":"#6d28d9","800":"#5b21b6","900":"#4c1d95"},"pink":{"50":"#fdf2f8","100":"#fce7f3","200":"#fbcfe8","300":"#f9a8d4","400":"#f472b6","500":"#ec4899","600":"#db2777","700":"#be185d","800":"#9d174d","900":"#831843"}},"ringOffsetColor":{"transparent":"transparent","current":"currentColor","black":"#000","white":"#fff","gray":{"50":"#f9fafb","100":"#f3f4f6","200":"#e5e7eb","300":"#d1d5db","400":"#9ca3af","500":"#6b7280","600":"#4b5563","700":"#374151","800":"#1f2937","900":"#111827"},"red":{"50":"#fef2f2","100":"#fee2e2","200":"#fecaca","300":"#fca5a5","400":"#f87171","500":"#ef4444","600":"#dc2626","700":"#b91c1c","800":"#991b1b","900":"#7f1d1d"},"yellow":{"50":"#fffbeb","100":"#fef3c7","200":"#fde68a","300":"#fcd34d","400":"#fbbf24","500":"#f59e0b","600":"#d97706","700":"#b45309","800":"#92400e","900":"#78350f"},"green":{"50":"#ecfdf5","100":"#d1fae5","200":"#a7f3d0","300":"#6ee7b7","400":"#34d399","500":"#10b981","600":"#059669","700":"#047857","800":"#065f46","900":"#064e3b"},"blue":{"50":"#eff6ff","100":"#dbeafe","200":"#bfdbfe","300":"#93c5fd","400":"#60a5fa","500":"#3b82f6","600":"#2563eb","700":"#1d4ed8","800":"#1e40af","900":"#1e3a8a"},"indigo":{"50":"#eef2ff","100":"#e0e7ff","200":"#c7d2fe","300":"#a5b4fc","400":"#818cf8","500":"#6366f1","600":"#4f46e5","700":"#4338ca","800":"#3730a3","900":"#312e81"},"purple":{"50":"#f5f3ff","100":"#ede9fe","200":"#ddd6fe","300":"#c4b5fd","400":"#a78bfa","500":"#8b5cf6","600":"#7c3aed","700":"#6d28d9","800":"#5b21b6","900":"#4c1d95"},"pink":{"50":"#fdf2f8","100":"#fce7f3","200":"#fbcfe8","300":"#f9a8d4","400":"#f472b6","500":"#ec4899","600":"#db2777","700":"#be185d","800":"#9d174d","900":"#831843"}},"ringOffsetWidth":{"0":"0px","1":"1px","2":"2px","4":"4px","8":"8px"},"ringOpacity":{"0":"0","5":"0.05","10":"0.1","20":"0.2","25":"0.25","30":"0.3","40":"0.4","50":"0.5","60":"0.6","70":"0.7","75":"0.75","80":"0.8","90":"0.9","95":"0.95","100":"1","DEFAULT":"0.5"},"ringWidth":{"0":"0px","1":"1px","2":"2px","4":"4px","8":"8px","DEFAULT":"3px"},"rotate":{"0":"0deg","1":"1deg","2":"2deg","3":"3deg","6":"6deg","12":"12deg","45":"45deg","90":"90deg","180":"180deg","-180":"-180deg","-90":"-90deg","-45":"-45deg","-12":"-12deg","-6":"-6deg","-3":"-3deg","-2":"-2deg","-1":"-1deg"},"scale":{"0":"0","50":".5","75":".75","90":".9","95":".95","100":"1","105":"1.05","110":"1.1","125":"1.25","150":"1.5"},"skew":{"0":"0deg","1":"1deg","2":"2deg","3":"3deg","6":"6deg","12":"12deg","-12":"-12deg","-6":"-6deg","-3":"-3deg","-2":"-2deg","-1":"-1deg"},"space":{"0":"0px","1":"0.25rem","2":"0.5rem","3":"0.75rem","4":"1rem","5":"1.25rem","6":"1.5rem","7":"1.75rem","8":"2rem","9":"2.25rem","10":"2.5rem","11":"2.75rem","12":"3rem","14":"3.5rem","16":"4rem","20":"5rem","24":"6rem","28":"7rem","32":"8rem","36":"9rem","40":"10rem","44":"11rem","48":"12rem","52":"13rem","56":"14rem","60":"15rem","64":"16rem","72":"18rem","80":"20rem","96":"24rem","px":"1px","0.5":"0.125rem","1.5":"0.375rem","2.5":"0.625rem","3.5":"0.875rem","-0":"0px","-1":"-0.25rem","-2":"-0.5rem","-3":"-0.75rem","-4":"-1rem","-5":"-1.25rem","-6":"-1.5rem","-7":"-1.75rem","-8":"-2rem","-9":"-2.25rem","-10":"-2.5rem","-11":"-2.75rem","-12":"-3rem","-14":"-3.5rem","-16":"-4rem","-20":"-5rem","-24":"-6rem","-28":"-7rem","-32":"-8rem","-36":"-9rem","-40":"-10rem","-44":"-11rem","-48":"-12rem","-52":"-13rem","-56":"-14rem","-60":"-15rem","-64":"-16rem","-72":"-18rem","-80":"-20rem","-96":"-24rem","-px":"-1px","-0.5":"-0.125rem","-1.5":"-0.375rem","-2.5":"-0.625rem","-3.5":"-0.875rem"},"stroke":{"current":"currentColor"},"strokeWidth":{"0":"0","1":"1","2":"2"},"textColor":{"transparent":"transparent","current":"currentColor","black":"#000","white":"#fff","gray":{"50":"#f9fafb","100":"#f3f4f6","200":"#e5e7eb","300":"#d1d5db","400":"#9ca3af","500":"#6b7280","600":"#4b5563","700":"#374151","800":"#1f2937","900":"#111827"},"red":{"50":"#fef2f2","100":"#fee2e2","200":"#fecaca","300":"#fca5a5","400":"#f87171","500":"#ef4444","600":"#dc2626","700":"#b91c1c","800":"#991b1b","900":"#7f1d1d"},"yellow":{"50":"#fffbeb","100":"#fef3c7","200":"#fde68a","300":"#fcd34d","400":"#fbbf24","500":"#f59e0b","600":"#d97706","700":"#b45309","800":"#92400e","900":"#78350f"},"green":{"50":"#ecfdf5","100":"#d1fae5","200":"#a7f3d0","300":"#6ee7b7","400":"#34d399","500":"#10b981","600":"#059669","700":"#047857","800":"#065f46","900":"#064e3b"},"blue":{"50":"#eff6ff","100":"#dbeafe","200":"#bfdbfe","300":"#93c5fd","400":"#60a5fa","500":"#3b82f6","600":"#2563eb","700":"#1d4ed8","800":"#1e40af","900":"#1e3a8a"},"indigo":{"50":"#eef2ff","100":"#e0e7ff","200":"#c7d2fe","300":"#a5b4fc","400":"#818cf8","500":"#6366f1","600":"#4f46e5","700":"#4338ca","800":"#3730a3","900":"#312e81"},"purple":{"50":"#f5f3ff","100":"#ede9fe","200":"#ddd6fe","300":"#c4b5fd","400":"#a78bfa","500":"#8b5cf6","600":"#7c3aed","700":"#6d28d9","800":"#5b21b6","900":"#4c1d95"},"pink":{"50":"#fdf2f8","100":"#fce7f3","200":"#fbcfe8","300":"#f9a8d4","400":"#f472b6","500":"#ec4899","600":"#db2777","700":"#be185d","800":"#9d174d","900":"#831843"}},"textOpacity":{"0":"0","5":"0.05","10":"0.1","20":"0.2","25":"0.25","30":"0.3","40":"0.4","50":"0.5","60":"0.6","70":"0.7","75":"0.75","80":"0.8","90":"0.9","95":"0.95","100":"1"},"transitionDuration":{"75":"75ms","100":"100ms","150":"150ms","200":"200ms","300":"300ms","500":"500ms","700":"700ms","1000":"1000ms","DEFAULT":"150ms"},"transitionDelay":{"75":"75ms","100":"100ms","150":"150ms","200":"200ms","300":"300ms","500":"500ms","700":"700ms","1000":"1000ms"},"transitionProperty":{"none":"none","all":"all","DEFAULT":"background-color, border-color, color, fill, stroke, opacity, box-shadow, transform","colors":"background-color, border-color, color, fill, stroke","opacity":"opacity","shadow":"box-shadow","transform":"transform"},"transitionTimingFunction":{"DEFAULT":"cubic-bezier(0.4, 0, 0.2, 1)","linear":"linear","in":"cubic-bezier(0.4, 0, 1, 1)","out":"cubic-bezier(0, 0, 0.2, 1)","in-out":"cubic-bezier(0.4, 0, 0.2, 1)"},"translate":{"0":"0px","1":"0.25rem","2":"0.5rem","3":"0.75rem","4":"1rem","5":"1.25rem","6":"1.5rem","7":"1.75rem","8":"2rem","9":"2.25rem","10":"2.5rem","11":"2.75rem","12":"3rem","14":"3.5rem","16":"4rem","20":"5rem","24":"6rem","28":"7rem","32":"8rem","36":"9rem","40":"10rem","44":"11rem","48":"12rem","52":"13rem","56":"14rem","60":"15rem","64":"16rem","72":"18rem","80":"20rem","96":"24rem","px":"1px","0.5":"0.125rem","1.5":"0.375rem","2.5":"0.625rem","3.5":"0.875rem","-0":"0px","-1":"-0.25rem","-2":"-0.5rem","-3":"-0.75rem","-4":"-1rem","-5":"-1.25rem","-6":"-1.5rem","-7":"-1.75rem","-8":"-2rem","-9":"-2.25rem","-10":"-2.5rem","-11":"-2.75rem","-12":"-3rem","-14":"-3.5rem","-16":"-4rem","-20":"-5rem","-24":"-6rem","-28":"-7rem","-32":"-8rem","-36":"-9rem","-40":"-10rem","-44":"-11rem","-48":"-12rem","-52":"-13rem","-56":"-14rem","-60":"-15rem","-64":"-16rem","-72":"-18rem","-80":"-20rem","-96":"-24rem","-px":"-1px","-0.5":"-0.125rem","-1.5":"-0.375rem","-2.5":"-0.625rem","-3.5":"-0.875rem","1/2":"50%","1/3":"33.333333%","2/3":"66.666667%","1/4":"25%","2/4":"50%","3/4":"75%","full":"100%","-1/2":"-50%","-1/3":"-33.333333%","-2/3":"-66.666667%","-1/4":"-25%","-2/4":"-50%","-3/4":"-75%","-full":"-100%"},"width":{"0":"0px","1":"0.25rem","2":"0.5rem","3":"0.75rem","4":"1rem","5":"1.25rem","6":"1.5rem","7":"1.75rem","8":"2rem","9":"2.25rem","10":"2.5rem","11":"2.75rem","12":"3rem","14":"3.5rem","16":"4rem","20":"5rem","24":"6rem","28":"7rem","32":"8rem","36":"9rem","40":"10rem","44":"11rem","48":"12rem","52":"13rem","56":"14rem","60":"15rem","64":"16rem","72":"18rem","80":"20rem","96":"24rem","auto":"auto","px":"1px","0.5":"0.125rem","1.5":"0.375rem","2.5":"0.625rem","3.5":"0.875rem","1/2":"50%","1/3":"33.333333%","2/3":"66.666667%","1/4":"25%","2/4":"50%","3/4":"75%","1/5":"20%","2/5":"40%","3/5":"60%","4/5":"80%","1/6":"16.666667%","2/6":"33.333333%","3/6":"50%","4/6":"66.666667%","5/6":"83.333333%","1/12":"8.333333%","2/12":"16.666667%","3/12":"25%","4/12":"33.333333%","5/12":"41.666667%","6/12":"50%","7/12":"58.333333%","8/12":"66.666667%","9/12":"75%","10/12":"83.333333%","11/12":"91.666667%","full":"100%","screen":"100vw","min":"min-content","max":"max-content"},"zIndex":{"0":"0","10":"10","20":"20","30":"30","40":"40","50":"50","auto":"auto"}},"variants":{"accessibility":["responsive","focus-within","focus"],"alignContent":["responsive"],"alignItems":["responsive"],"alignSelf":["responsive"],"animation":["responsive"],"appearance":["responsive"],"backgroundAttachment":["responsive"],"backgroundClip":["responsive"],"backgroundColor":["responsive","dark","group-hover","focus-within","hover","focus"],"backgroundImage":["responsive"],"backgroundOpacity":["responsive","group-hover","focus-within","hover","focus"],"backgroundPosition":["responsive"],"backgroundRepeat":["responsive"],"backgroundSize":["responsive"],"borderCollapse":["responsive"],"borderColor":["responsive","dark","group-hover","focus-within","hover","focus"],"borderOpacity":["responsive","group-hover","focus-within","hover","focus"],"borderRadius":["responsive"],"borderStyle":["responsive"],"borderWidth":["responsive"],"boxShadow":["responsive","group-hover","focus-within","hover","focus"],"boxSizing":["responsive"],"clear":["responsive"],"container":["responsive"],"cursor":["responsive"],"display":["responsive"],"divideColor":["responsive","dark"],"divideOpacity":["responsive"],"divideStyle":["responsive"],"divideWidth":["responsive"],"fill":["responsive"],"flex":["responsive"],"flexDirection":["responsive"],"flexGrow":["responsive"],"flexShrink":["responsive"],"flexWrap":["responsive"],"float":["responsive"],"fontFamily":["responsive"],"fontSize":["responsive"],"fontSmoothing":["responsive"],"fontStyle":["responsive"],"fontVariantNumeric":["responsive"],"fontWeight":["responsive"],"gap":["responsive"],"gradientColorStops":["responsive","dark","hover","focus"],"gridAutoColumns":["responsive"],"gridAutoFlow":["responsive"],"gridAutoRows":["responsive"],"gridColumn":["responsive"],"gridColumnEnd":["responsive"],"gridColumnStart":["responsive"],"gridRow":["responsive"],"gridRowEnd":["responsive"],"gridRowStart":["responsive"],"gridTemplateColumns":["responsive"],"gridTemplateRows":["responsive"],"height":["responsive"],"inset":["responsive"],"justifyContent":["responsive"],"justifyItems":["responsive"],"justifySelf":["responsive"],"letterSpacing":["responsive"],"lineHeight":["responsive"],"listStylePosition":["responsive"],"listStyleType":["responsive"],"margin":["responsive"],"maxHeight":["responsive"],"maxWidth":["responsive"],"minHeight":["responsive"],"minWidth":["responsive"],"objectFit":["responsive"],"objectPosition":["responsive"],"opacity":["responsive","group-hover","focus-within","hover","focus"],"order":["responsive"],"outline":["responsive","focus-within","focus"],"overflow":["responsive"],"overscrollBehavior":["responsive"],"padding":["responsive"],"placeContent":["responsive"],"placeItems":["responsive"],"placeSelf":["responsive"],"placeholderColor":["responsive","dark","focus"],"placeholderOpacity":["responsive","focus"],"pointerEvents":["responsive"],"position":["responsive"],"resize":["responsive"],"ringColor":["responsive","dark","focus-within","focus"],"ringOffsetColor":["responsive","dark","focus-within","focus"],"ringOffsetWidth":["responsive","focus-within","focus"],"ringOpacity":["responsive","focus-within","focus"],"ringWidth":["responsive","focus-within","focus"],"rotate":["responsive","hover","focus"],"scale":["responsive","hover","focus"],"skew":["responsive","hover","focus"],"space":["responsive"],"stroke":["responsive"],"strokeWidth":["responsive"],"tableLayout":["responsive"],"textAlign":["responsive"],"textColor":["responsive","dark","group-hover","focus-within","hover","focus"],"textDecoration":["responsive","group-hover","focus-within","hover","focus"],"textOpacity":["responsive","group-hover","focus-within","hover","focus"],"textOverflow":["responsive"],"textTransform":["responsive"],"transform":["responsive"],"transformOrigin":["responsive"],"transitionDelay":["responsive"],"transitionDuration":["responsive"],"transitionProperty":["responsive"],"transitionTimingFunction":["responsive"],"translate":["responsive","hover","focus"],"userSelect":["responsive"],"verticalAlign":["responsive"],"visibility":["responsive"],"whitespace":["responsive"],"width":["responsive"],"wordBreak":["responsive"],"zIndex":["responsive","focus-within","focus"],"aspectRatio":["responsive"],"typography":["responsive"]},"corePlugins":["preflight","container","space","divideWidth","divideColor","divideStyle","divideOpacity","accessibility","appearance","backgroundAttachment","backgroundClip","backgroundColor","backgroundImage","gradientColorStops","backgroundOpacity","backgroundPosition","backgroundRepeat","backgroundSize","borderCollapse","borderColor","borderOpacity","borderRadius","borderStyle","borderWidth","boxSizing","cursor","display","flexDirection","flexWrap","placeItems","placeContent","placeSelf","alignItems","alignContent","alignSelf","justifyItems","justifyContent","justifySelf","flex","flexGrow","flexShrink","order","float","clear","fontFamily","fontWeight","height","fontSize","lineHeight","listStylePosition","listStyleType","margin","maxHeight","maxWidth","minHeight","minWidth","objectFit","objectPosition","opacity","outline","overflow","overscrollBehavior","padding","placeholderColor","placeholderOpacity","pointerEvents","position","inset","resize","boxShadow","ringWidth","ringOffsetColor","ringOffsetWidth","ringColor","ringOpacity","fill","stroke","strokeWidth","tableLayout","textAlign","textColor","textOpacity","textOverflow","fontStyle","textTransform","textDecoration","fontSmoothing","fontVariantNumeric","letterSpacing","userSelect","verticalAlign","visibility","whitespace","wordBreak","width","zIndex","gap","gridAutoFlow","gridTemplateColumns","gridAutoColumns","gridColumn","gridColumnStart","gridColumnEnd","gridTemplateRows","gridAutoRows","gridRow","gridRowStart","gridRowEnd","transform","transformOrigin","scale","rotate","translate","skew","transitionProperty","transitionTimingFunction","transitionDuration","transitionDelay","animation"],"plugins":[{},null,{"config":{"theme":{"aspectRatio":{"1":"1","2":"2","3":"3","4":"4","5":"5","6":"6","7":"7","8":"8","9":"9","10":"10","11":"11","12":"12","13":"13","14":"14","15":"15","16":"16"}},"variants":{"aspectRatio":["responsive"]}}},{}],"purge":[],"darkMode":false,"presets":[],"variantOrder":["first","last","odd","even","visited","checked","group-hover","group-focus","focus-within","hover","focus","focus-visible","active","disabled"],"prefix":"","important":false,"separator":":"}
--------------------------------------------------------------------------------
/src/models/Root.ts:
--------------------------------------------------------------------------------
1 | import "mobx-react-lite/batchingForReactDom";
2 | import { Instance, onSnapshot, types } from "mobx-state-tree";
3 | import { createContext, useContext } from "react";
4 | import { TailwindConfig } from "../@types/tailwind";
5 | import { PluginMessage } from "../code";
6 | import { fetchConfigColors } from "../core/config";
7 | import defaultConfig from "../generated/tw.min.json";
8 |
9 | const RootModel = types
10 | .model({
11 | twPrefix: types.optional(types.string, ""),
12 | addSpaces: types.optional(types.boolean, false),
13 | overrideStyles: types.optional(types.boolean, false),
14 | configName: types.optional(types.maybeNull(types.string), null),
15 | errorMessage: types.optional(types.maybeNull(types.string), null),
16 | })
17 | .volatile(() => {
18 | const reader = new FileReader();
19 | const config: TailwindConfig = {
20 | theme: {},
21 | variants: {},
22 | plugins: [],
23 | darkMode: false,
24 | presets: [],
25 | variantOrder: [
26 | "first",
27 | "last",
28 | "odd",
29 | "even",
30 | "visited",
31 | "checked",
32 | "group-hover",
33 | "group-focus",
34 | "focus-within",
35 | "hover",
36 | "focus",
37 | "focus-visible",
38 | "active",
39 | "disabled",
40 | ],
41 | };
42 | return {
43 | reader,
44 | config,
45 | };
46 | })
47 | .views((self) => ({
48 | get loadedTailwindConfig(): TailwindConfig {
49 | return self.config;
50 | },
51 | }))
52 | .actions((self) => ({
53 | handleReadAbort: () => {
54 | alert("Read was aborted");
55 | },
56 | handleReadError: () => {
57 | alert("Read failed");
58 | },
59 | setAddSpaces: (value: boolean) => {
60 | self.addSpaces = value;
61 | },
62 | setOverrideStyles: (value: boolean) => {
63 | self.overrideStyles = value;
64 | },
65 | setTwPrefix: (prefix: string) => {
66 | self.twPrefix = prefix;
67 | },
68 | setConfig: (config: TailwindConfig) => {
69 | self.config = config;
70 | },
71 | setConfigName: (name: string | null) => {
72 | self.configName = name;
73 | },
74 | setErrorMessage: (msg: string | null) => {
75 | self.errorMessage = msg;
76 | },
77 | sendPluginMessage: (pluginMessage: PluginMessage) => {
78 | parent.postMessage(
79 | {
80 | pluginMessage,
81 | },
82 | "*"
83 | );
84 | },
85 | }))
86 | .actions((self) => ({
87 | afterCreate: () => {
88 | self.reader.onabort = (e) => self.handleReadAbort;
89 | self.reader.onerror = (e) => self.handleReadError;
90 | self.reader.onload = () => {
91 | const binaryString = self.reader.result as string;
92 | try {
93 | self.setConfig(JSON.parse(binaryString));
94 | } catch (error) {
95 | new Error(error);
96 | self.setErrorMessage("Parsing failed!");
97 | }
98 | };
99 | },
100 | readFile: (fileName: string, file: File) => {
101 | self.setErrorMessage(null);
102 | self.setConfigName(fileName);
103 | self.reader.readAsBinaryString(file);
104 | },
105 | loadDefaultStub: () => {
106 | self.setErrorMessage(null);
107 | self.setConfigName("Default Config");
108 | self.setConfig(defaultConfig);
109 | },
110 | addColorStyles: (prefix: string) => {
111 | self.sendPluginMessage({
112 | type: "ADD_COLORS",
113 | payload: {
114 | prefix: prefix,
115 | config: fetchConfigColors(self.config),
116 | overrideStyles: self.overrideStyles,
117 | addSpaces: self.addSpaces,
118 | },
119 | });
120 | },
121 | }));
122 |
123 | export const rootStore = RootModel.create({});
124 |
125 | onSnapshot(rootStore, (snapshot) => console.log("Snapshot: ", snapshot));
126 |
127 | export type RootInstance = Instance;
128 | const RootStoreContext = createContext(null);
129 |
130 | export const Provider = RootStoreContext.Provider;
131 | export function useMst() {
132 | const store = useContext(RootStoreContext);
133 | if (store === null) {
134 | throw new Error("Store cannot be null, please add a context provider");
135 | }
136 | return store as RootInstance;
137 | }
138 |
--------------------------------------------------------------------------------
/src/resources/css/ui.css:
--------------------------------------------------------------------------------
1 | @tailwind base;
2 |
3 | @tailwind components;
4 |
5 | .link {
6 | transition: all 0.27s cubic-bezier(0.3, 0.1, 0.58, 1) 0s;
7 | }
8 |
9 | button {
10 | @apply outline-none;
11 | }
12 |
13 | button:focus {
14 | @apply outline-none;
15 | }
16 |
17 | button {
18 | transition: all 0.27s cubic-bezier(0.3, 0.1, 0.58, 1) 0s;
19 | }
20 |
21 | @tailwind utilities;
22 |
--------------------------------------------------------------------------------
/src/resources/data/.gitkeep:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ecklf/tailwindcss-figma-plugin/114e846993c33f714e0dd4699af7b67fc36d635b/src/resources/data/.gitkeep
--------------------------------------------------------------------------------
/src/ui.html:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/tailwind.config.js:
--------------------------------------------------------------------------------
1 | const colors = require("tailwindcss/colors");
2 |
3 | module.exports = {
4 | mode: "jit",
5 | purge: ["./src/**/*.{js,ts,tsx,html}"],
6 | darkMode: false, // or 'media' or 'class'
7 | theme: {
8 | extend: {
9 | colors: {
10 | teal: colors.teal,
11 | },
12 | },
13 | },
14 | variants: {
15 | extend: {},
16 | },
17 | plugins: [require("@tailwindcss/forms")],
18 | };
19 |
--------------------------------------------------------------------------------
/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "lib": ["dom", "es2016", "es2017.object"],
4 | "rootDir": "src",
5 | "outDir": "dist",
6 | "target": "es6",
7 | "jsx": "react",
8 | "allowSyntheticDefaultImports": true,
9 | "module": "commonjs",
10 | "resolveJsonModule": true,
11 | "esModuleInterop": true,
12 | "moduleResolution": "node"
13 | }
14 | }
15 |
--------------------------------------------------------------------------------
/webpack.config.js:
--------------------------------------------------------------------------------
1 | const HtmlWebpackInlineSourcePlugin = require("html-webpack-inline-source-plugin");
2 | const HtmlWebpackPlugin = require("html-webpack-plugin");
3 | const path = require("path");
4 | const trimEnd = require("lodash/trimEnd");
5 |
6 | module.exports = (env, argv) => ({
7 | mode: argv.mode === "production" ? "production" : "development",
8 |
9 | // This is necessary because Figma's 'eval' works differently than normal eval
10 | devtool: argv.mode === "production" ? false : "inline-source-map",
11 |
12 | entry: {
13 | ui: "./src/App.tsx", // The entry point for your UI code
14 | code: "./src/code.ts", // The entry point for your plugin code
15 | },
16 |
17 | module: {
18 | rules: [
19 | // Converts TypeScript code to JavaScript
20 | { test: /\.tsx?$/, use: "ts-loader", exclude: /node_modules/ },
21 | // Enables including CSS by doing "import './file.css'" in your TypeScript code
22 | {
23 | test: /\.css$/,
24 | use: [
25 | "style-loader",
26 | { loader: "css-loader", options: { importLoaders: 1 } },
27 | {
28 | loader: "postcss-loader",
29 | options: {
30 | postcssOptions: {
31 | plugins: [
32 | require("tailwindcss")("./tailwind.config.js"),
33 | require("autoprefixer"),
34 | require("cssnano"),
35 | ],
36 | },
37 | },
38 | },
39 | ],
40 | },
41 | // Allows you to use "<%= require('./file.svg') %>" in your HTML code to get a data URI
42 | {
43 | test: /\.(png|jpg|gif|webp|svg|zip)$/,
44 | loader: [{ loader: "url-loader" }],
45 | },
46 | ],
47 | },
48 |
49 | // Webpack tries these extensions for you if you omit the extension like "import './file'"
50 | resolve: { extensions: [".ts", ".tsx", ".js"] },
51 |
52 | output: {
53 | filename: "[name].js",
54 | path: path.resolve(__dirname, "dist"), // Compile into a folder called "dist"
55 | },
56 |
57 | // Tells Webpack to generate "ui.html" and to inline "ui.ts" into it
58 | plugins: [
59 | new HtmlWebpackPlugin({
60 | template: "./src/ui.html",
61 | filename: "ui.html",
62 | inlineSource: ".(js)$",
63 | chunks: ["ui"],
64 | }),
65 | new HtmlWebpackInlineSourcePlugin(),
66 | ],
67 | });
68 |
--------------------------------------------------------------------------------