Skip to content

Commit bc916cc

Browse files
committed
feat(tailwindcss-utopia-playground): add tailwinplay like editor with utopia plugin support
1 parent 71dcace commit bc916cc

25 files changed

Lines changed: 1606 additions & 11 deletions

apps/playground/.gitignore

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
2+
3+
# dependencies
4+
/node_modules
5+
/.pnp
6+
.pnp.*
7+
.yarn/*
8+
!.yarn/patches
9+
!.yarn/plugins
10+
!.yarn/releases
11+
!.yarn/versions
12+
13+
# testing
14+
/coverage
15+
16+
# next.js
17+
/.next/
18+
/out/
19+
20+
# production
21+
/build
22+
23+
# misc
24+
.DS_Store
25+
*.pem
26+
27+
# debug
28+
npm-debug.log*
29+
yarn-debug.log*
30+
yarn-error.log*
31+
.pnpm-debug.log*
32+
33+
# env files (can opt-in for committing if needed)
34+
.env*
35+
36+
# vercel
37+
.vercel
38+
39+
# typescript
40+
*.tsbuildinfo
41+
next-env.d.ts

apps/playground/README.md

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
This is a [Next.js](https://nextjs.org) project bootstrapped with [`create-next-app`](https://nextjs.org/docs/app/api-reference/cli/create-next-app).
2+
3+
## Getting Started
4+
5+
First, run the development server:
6+
7+
```bash
8+
npm run dev
9+
# or
10+
yarn dev
11+
# or
12+
pnpm dev
13+
# or
14+
bun dev
15+
```
16+
17+
Open [http://localhost:3000](http://localhost:3000) with your browser to see the result.
18+
19+
You can start editing the page by modifying `app/page.tsx`. The page auto-updates as you edit the file.
20+
21+
This project uses [`next/font`](https://nextjs.org/docs/app/building-your-application/optimizing/fonts) to automatically optimize and load [Geist](https://vercel.com/font), a new font family for Vercel.
22+
23+
## Learn More
24+
25+
To learn more about Next.js, take a look at the following resources:
26+
27+
- [Next.js Documentation](https://nextjs.org/docs) - learn about Next.js features and API.
28+
- [Learn Next.js](https://nextjs.org/learn) - an interactive Next.js tutorial.
29+
30+
You can check out [the Next.js GitHub repository](https://github.com/vercel/next.js) - your feedback and contributions are welcome!
31+
32+
## Deploy on Vercel
33+
34+
The easiest way to deploy your Next.js app is to use the [Vercel Platform](https://vercel.com/new?utm_medium=default-template&filter=next.js&utm_source=create-next-app&utm_campaign=create-next-app-readme) from the creators of Next.js.
35+
36+
Check out our [Next.js deployment documentation](https://nextjs.org/docs/app/building-your-application/deploying) for more details.
Lines changed: 201 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,201 @@
1+
"use client";
2+
3+
/*
4+
const mockModule = { hello: 'world' };
5+
const require = (moduleName) => {
6+
if (moduleName === 'mock-module') return mockModule;
7+
throw new Error(`Module '${moduleName}' not found`);
8+
};
9+
10+
const someVariable = 42;
11+
12+
import tailwindcssUtopia from "https://esm.sh/tailwindcss-utopia@0.0.3";
13+
14+
@type {import('tailwindcss').Config}
15+
16+
export default {
17+
tailwindcssUtopia,
18+
someVariable,
19+
mockModule: require('mock-module'),
20+
};
21+
22+
*/
23+
24+
import { useEffect, useRef, useState } from "react";
25+
import * as monaco from "monaco-editor";
26+
import { Editor as MonacoEditor, loader } from "@monaco-editor/react";
27+
import {
28+
configureMonacoTailwindcss,
29+
MonacoTailwindcss,
30+
TailwindConfig,
31+
tailwindcssData,
32+
} from "monaco-tailwindcss";
33+
import { css, generateSrcDoc, html, js } from "@/lib/utils";
34+
35+
type Refs = Parameters<
36+
NonNullable<React.ComponentProps<typeof MonacoEditor>["onMount"]>
37+
>;
38+
type EditorRef = Refs[0];
39+
type MonacoEditorRef = Refs[1];
40+
41+
loader.config({ monaco });
42+
43+
export default function Editor({
44+
activeTab,
45+
onChange,
46+
}: {
47+
activeTab: "html" | "css" | "config";
48+
onChange: (content: string) => void;
49+
}) {
50+
const editorRef = useRef<EditorRef | null>(null);
51+
const monacoEditorRef = useRef<MonacoEditorRef | null>(null);
52+
53+
const [tabs] = useState(() => ({
54+
html: monaco.editor.createModel(
55+
html`<h1>Heading H1</h1>
56+
<h2>Heading H2</h2>
57+
<h3>Heading H3</h3>
58+
<h4>Heading H4</h4>
59+
<h5>Heading H5</h5>
60+
<h6>Heading H6</h6>
61+
`,
62+
"html",
63+
),
64+
css: monaco.editor.createModel(
65+
css`@tailwind base;
66+
@tailwind components;
67+
@tailwind utilities;
68+
69+
h1,
70+
h2,
71+
h3,
72+
h4,
73+
h5,
74+
h6 {
75+
@apply font-bold;
76+
}
77+
78+
h1 {
79+
@apply ~text-x5;
80+
}
81+
82+
h2 {
83+
@apply ~text-x4;
84+
}
85+
86+
h3 {
87+
@apply ~text-x3;
88+
}
89+
90+
h4 {
91+
@apply ~text-x2;
92+
}
93+
94+
h5 {
95+
@apply ~text-x1;
96+
}
97+
98+
body {
99+
@apply ~text-1;
100+
}
101+
`,
102+
"css",
103+
),
104+
config: monaco.editor.createModel(js`export default {
105+
theme: {},
106+
plugins: [], /* plugins should be empty */
107+
};
108+
`, "typescript"),
109+
}));
110+
111+
const monacoTailwindcssRef = useRef<MonacoTailwindcss>(null);
112+
113+
const handleChange = async () => {
114+
console.log('a');
115+
if (!monacoTailwindcssRef.current) return;
116+
console.log('b');
117+
118+
const css = await monacoTailwindcssRef.current.generateStylesFromContent(
119+
tabs.css.getValue(),
120+
[{ content: tabs.html.getValue() }],
121+
);
122+
const html = tabs.html.getValue();
123+
// const config = tabs.config.getValue();
124+
125+
const content = generateSrcDoc(html, css);
126+
127+
onChange(content);
128+
};
129+
130+
useEffect(() => {
131+
const onResize = () => {
132+
if (!editorRef.current) return;
133+
134+
editorRef.current.layout();
135+
};
136+
137+
window.addEventListener("resize", onResize);
138+
139+
return () => {
140+
window.removeEventListener("resize", onResize);
141+
};
142+
}, []);
143+
144+
useEffect(() => {
145+
tabs["config"].onDidChangeContent(() => {
146+
const code = tabs["config"].getValue();
147+
148+
let evaluatedCode;
149+
150+
const blob = new Blob([code], { type: "application/javascript" });
151+
const url = URL.createObjectURL(blob);
152+
153+
import(/* webpackIgnore: true */ url)
154+
.then((module) => {
155+
evaluatedCode = module.default;
156+
157+
monacoTailwindcssRef.current?.setTailwindConfig(
158+
evaluatedCode as TailwindConfig,
159+
);
160+
})
161+
.catch((error) => {
162+
console.error("Error evaluating code:", error.message);
163+
evaluatedCode = null;
164+
});
165+
});
166+
167+
Object.values(tabs).map((tab) => {
168+
tab.onDidChangeContent(handleChange);
169+
});
170+
}, []);
171+
172+
useEffect(() => {
173+
if (!editorRef.current) return;
174+
175+
editorRef.current.setModel(tabs[activeTab]);
176+
}, [editorRef, tabs, activeTab]);
177+
178+
return (
179+
<MonacoEditor
180+
beforeMount={() => {
181+
monaco.languages.css.cssDefaults.setOptions({
182+
data: {
183+
dataProviders: {
184+
tailwindcssData,
185+
},
186+
},
187+
});
188+
189+
monacoTailwindcssRef.current = configureMonacoTailwindcss(monaco);
190+
}}
191+
onMount={(editor, monaco) => {
192+
editorRef.current = editor;
193+
monacoEditorRef.current = monaco;
194+
195+
editor.setModel(tabs[activeTab]);
196+
197+
handleChange();
198+
}}
199+
/>
200+
);
201+
}

apps/playground/app/favicon.ico

25.3 KB
Binary file not shown.

apps/playground/app/globals.css

Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,72 @@
1+
@tailwind base;
2+
@tailwind components;
3+
@tailwind utilities;
4+
5+
body {
6+
font-family: Arial, Helvetica, sans-serif;
7+
}
8+
9+
@layer base {
10+
:root {
11+
--background: 0 0% 100%;
12+
--foreground: 0 0% 3.9%;
13+
--card: 0 0% 100%;
14+
--card-foreground: 0 0% 3.9%;
15+
--popover: 0 0% 100%;
16+
--popover-foreground: 0 0% 3.9%;
17+
--primary: 0 0% 9%;
18+
--primary-foreground: 0 0% 98%;
19+
--secondary: 0 0% 96.1%;
20+
--secondary-foreground: 0 0% 9%;
21+
--muted: 0 0% 96.1%;
22+
--muted-foreground: 0 0% 45.1%;
23+
--accent: 0 0% 96.1%;
24+
--accent-foreground: 0 0% 9%;
25+
--destructive: 0 84.2% 60.2%;
26+
--destructive-foreground: 0 0% 98%;
27+
--border: 0 0% 89.8%;
28+
--input: 0 0% 89.8%;
29+
--ring: 0 0% 3.9%;
30+
--chart-1: 12 76% 61%;
31+
--chart-2: 173 58% 39%;
32+
--chart-3: 197 37% 24%;
33+
--chart-4: 43 74% 66%;
34+
--chart-5: 27 87% 67%;
35+
--radius: 0.5rem;
36+
}
37+
.dark {
38+
--background: 0 0% 3.9%;
39+
--foreground: 0 0% 98%;
40+
--card: 0 0% 3.9%;
41+
--card-foreground: 0 0% 98%;
42+
--popover: 0 0% 3.9%;
43+
--popover-foreground: 0 0% 98%;
44+
--primary: 0 0% 98%;
45+
--primary-foreground: 0 0% 9%;
46+
--secondary: 0 0% 14.9%;
47+
--secondary-foreground: 0 0% 98%;
48+
--muted: 0 0% 14.9%;
49+
--muted-foreground: 0 0% 63.9%;
50+
--accent: 0 0% 14.9%;
51+
--accent-foreground: 0 0% 98%;
52+
--destructive: 0 62.8% 30.6%;
53+
--destructive-foreground: 0 0% 98%;
54+
--border: 0 0% 14.9%;
55+
--input: 0 0% 14.9%;
56+
--ring: 0 0% 83.1%;
57+
--chart-1: 220 70% 50%;
58+
--chart-2: 160 60% 45%;
59+
--chart-3: 30 80% 55%;
60+
--chart-4: 280 65% 60%;
61+
--chart-5: 340 75% 55%;
62+
}
63+
}
64+
65+
@layer base {
66+
* {
67+
@apply border-border;
68+
}
69+
body {
70+
@apply bg-background text-foreground;
71+
}
72+
}

apps/playground/app/layout.tsx

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
import type { Metadata } from "next";
2+
import { Geist, Geist_Mono } from "next/font/google";
3+
import "./globals.css";
4+
5+
const geistSans = Geist({
6+
variable: "--font-geist-sans",
7+
subsets: ["latin"],
8+
});
9+
10+
const geistMono = Geist_Mono({
11+
variable: "--font-geist-mono",
12+
subsets: ["latin"],
13+
});
14+
15+
export const metadata: Metadata = {
16+
title: "Create Next App",
17+
description: "Generated by create next app",
18+
};
19+
20+
export default function RootLayout({
21+
children,
22+
}: Readonly<{
23+
children: React.ReactNode;
24+
}>) {
25+
return (
26+
<html lang="en">
27+
<body
28+
className={`${geistSans.variable} ${geistMono.variable} antialiased`}
29+
>
30+
{children}
31+
</body>
32+
</html>
33+
);
34+
}

0 commit comments

Comments
 (0)