Skip to content
This repository was archived by the owner on Oct 3, 2020. It is now read-only.

Commit c048e7a

Browse files
committed
feat(playground): setup playground system
All the pages inside the `pages` directory can be used as a playground. We have a custom `_app.tsx` file which ensures that there is a "shell" for each component. If you go to a route that is essentially a folder, you will get a page with a listing of all the examples available. We can do this by parsing the file system tree and display a nested tree on the page for this (sub) section.
1 parent b34483c commit c048e7a

File tree

8 files changed

+665
-5
lines changed

8 files changed

+665
-5
lines changed

package.json

+1-1
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@
1616
"build": "tsdx build --name tailwindui --tsconfig ./tsconfig.tsdx.json",
1717
"test": "tsdx test --passWithNoTests",
1818
"playground": "next dev",
19-
"lint": "tsdx lint",
19+
"lint": "tsdx lint src pages",
2020
"prepare": "npm run build",
2121
"commit": "git-cz",
2222
"semantic-release": "semantic-release"

pages/_app.tsx

+112
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,112 @@
1+
import React from 'react'
2+
import Link from 'next/link'
3+
import Head from 'next/head'
4+
import { useRouter } from 'next/router'
5+
6+
import 'tailwindcss/tailwind.css'
7+
import { ExamplesType } from '../playground-utils/resolve-all-examples'
8+
9+
function MyApp({ Component, pageProps }) {
10+
const router = useRouter()
11+
const { allExamples = [] } = pageProps
12+
13+
return (
14+
<>
15+
<Head>
16+
<meta httpEquiv="X-UA-Compatible" content="IE=edge" />
17+
<meta name="viewport" content="width=device-width, initial-scale=1" />
18+
19+
<link rel="stylesheet" href="https://rsms.me/inter/inter.css" />
20+
21+
<link
22+
rel="icon"
23+
type="image/png"
24+
sizes="32x32"
25+
href="https://tailwindui.com/favicon-32x32.png"
26+
/>
27+
<link
28+
rel="icon"
29+
type="image/png"
30+
sizes="16x16"
31+
href="https://tailwindui.com//favicon-16x16.png"
32+
/>
33+
</Head>
34+
35+
<div className="flex flex-col h-screen font-sans antialiased text-gray-900">
36+
<header className="relative z-10 flex items-center justify-between flex-shrink-0 px-4 py-4 bg-white border-b border-gray-200 sm:px-6 lg:px-8">
37+
<Link href="/">
38+
<a>
39+
<img
40+
className="w-auto h-6"
41+
src="https://tailwindui.com/img/tailwindui-logo.svg"
42+
alt="Tailwind UI"
43+
/>
44+
</a>
45+
</Link>
46+
{allExamples.length > 0 && (
47+
<div>
48+
<select
49+
value={router.asPath}
50+
onChange={event => {
51+
router.push(event.target.value)
52+
}}
53+
className="block w-full py-2 pl-3 pr-10 text-base leading-6 border-gray-300 form-select focus:outline-none focus:shadow-outline-blue focus:border-blue-300 sm:text-sm sm:leading-5"
54+
>
55+
<RecursiveExamplesSelectOptions examples={allExamples} />
56+
</select>
57+
</div>
58+
)}
59+
</header>
60+
61+
<main className="flex-1">
62+
<Component {...pageProps} />
63+
</main>
64+
</div>
65+
</>
66+
)
67+
}
68+
69+
// Ideally you would use <optgroup></optgroup>... However, when you do that, React yells at you:
70+
// <optgroup> cannot appear as a child of <optgroup>
71+
function RecursiveExamplesSelectOptions(props: { examples: ExamplesType[]; level?: number }) {
72+
const { examples = [], level = 0 } = props
73+
74+
return (
75+
<>
76+
{examples.map(example => {
77+
if (example.children) {
78+
return (
79+
<React.Fragment key={example.name + example.path}>
80+
<option value={example.path}>
81+
{'   '.repeat(level)}
82+
{example.name
83+
.split(' ')
84+
.map(v => {
85+
const [first, ...rest] = v.split('')
86+
return `${first.toUpperCase()}${rest.join('')}`
87+
})
88+
.join(' ')}
89+
</option>
90+
<RecursiveExamplesSelectOptions level={level + 1} examples={example.children} />
91+
</React.Fragment>
92+
)
93+
}
94+
95+
return (
96+
<option key={example.path} value={example.path}>
97+
{'   '.repeat(level)}
98+
{example.name
99+
.split(' ')
100+
.map(v => {
101+
const [first, ...rest] = v.split('')
102+
return `${first.toUpperCase()}${rest.join('')}`
103+
})
104+
.join(' ')}
105+
</option>
106+
)
107+
})}
108+
</>
109+
)
110+
}
111+
112+
export default MyApp

pages/_error.tsx

+56
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
import * as React from 'react'
2+
import { NextApiRequest } from 'next'
3+
import Error from 'next/error'
4+
import Head from 'next/head'
5+
import Link from 'next/link'
6+
7+
import { ExamplesType, resolveAllExamples } from '../playground-utils/resolve-all-examples'
8+
9+
export async function getServerSideProps({ req }: { req: NextApiRequest }) {
10+
return {
11+
props: {
12+
allExamples: await resolveAllExamples('pages'),
13+
examples: await resolveAllExamples('pages', ...req.url.split('/').slice(1)),
14+
},
15+
}
16+
}
17+
18+
export default function Page(props: { examples: false | ExamplesType[] }) {
19+
if (props.examples === false) {
20+
return <Error statusCode={404} />
21+
}
22+
23+
return (
24+
<>
25+
<Head>
26+
<title>Examples</title>
27+
</Head>
28+
29+
<div className="container my-24">
30+
<div className="prose">
31+
<h2>Examples</h2>
32+
<Examples examples={props.examples} />
33+
</div>
34+
</div>
35+
</>
36+
)
37+
}
38+
39+
export function Examples(props: { examples: ExamplesType[] }) {
40+
return (
41+
<ul>
42+
{props.examples.map(example => (
43+
<li key={example.path}>
44+
{example.children ? (
45+
<h3 className="text-xl capitalize">{example.name}</h3>
46+
) : (
47+
<Link href={example.path}>
48+
<a className="capitalize">{example.name}</a>
49+
</Link>
50+
)}
51+
{example.children && <Examples examples={example.children} />}
52+
</li>
53+
))}
54+
</ul>
55+
)
56+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,98 @@
1+
import React, { useState } from 'react'
2+
import Head from 'next/head'
3+
import { Transition } from '@tailwindui/react'
4+
5+
export default function Home() {
6+
return (
7+
<>
8+
<Head>
9+
<title>Transition Component - Playground</title>
10+
</Head>
11+
12+
<div className="flex items-center justify-center w-screen h-screen p-12 bg-gray-50">
13+
<Dropdown />
14+
</div>
15+
</>
16+
)
17+
}
18+
19+
function Dropdown() {
20+
const [isOpen, setIsOpen] = useState(false)
21+
22+
return (
23+
<div className="relative inline-block text-left">
24+
<div>
25+
<span className="rounded-md shadow-sm">
26+
<button
27+
type="button"
28+
className="inline-flex justify-center w-full px-4 py-2 text-sm font-medium leading-5 text-gray-700 transition duration-150 ease-in-out bg-white border border-gray-300 rounded-md hover:text-gray-500 focus:outline-none focus:border-blue-300 focus:shadow-outline-blue active:bg-gray-50 active:text-gray-800"
29+
id="options-menu"
30+
aria-haspopup="true"
31+
aria-expanded={isOpen}
32+
onClick={() => setIsOpen(v => !v)}
33+
>
34+
Options
35+
<svg className="w-5 h-5 ml-2 -mr-1" viewBox="0 0 20 20" fill="currentColor">
36+
<path
37+
fillRule="evenodd"
38+
d="M5.293 7.293a1 1 0 011.414 0L10 10.586l3.293-3.293a1 1 0 111.414 1.414l-4 4a1 1 0 01-1.414 0l-4-4a1 1 0 010-1.414z"
39+
clipRule="evenodd"
40+
/>
41+
</svg>
42+
</button>
43+
</span>
44+
</div>
45+
46+
<Transition
47+
show={isOpen}
48+
enter="transition ease-out duration-75"
49+
enterFrom="transform opacity-0 scale-95"
50+
enterTo="transform opacity-100 scale-100"
51+
leave="transition ease-in duration-150"
52+
leaveFrom="transform opacity-100 scale-100"
53+
leaveTo="transform opacity-0 scale-95"
54+
className="absolute right-0 w-56 mt-2 origin-top-right rounded-md shadow-lg"
55+
>
56+
<div className="bg-white rounded-md shadow-xs">
57+
<div
58+
className="py-1"
59+
role="menu"
60+
aria-orientation="vertical"
61+
aria-labelledby="options-menu"
62+
>
63+
<a
64+
href="/"
65+
className="block px-4 py-2 text-sm leading-5 text-gray-700 hover:bg-gray-100 hover:text-gray-900 focus:outline-none focus:bg-gray-100 focus:text-gray-900"
66+
role="menuitem"
67+
>
68+
Account settings
69+
</a>
70+
<a
71+
href="/"
72+
className="block px-4 py-2 text-sm leading-5 text-gray-700 hover:bg-gray-100 hover:text-gray-900 focus:outline-none focus:bg-gray-100 focus:text-gray-900"
73+
role="menuitem"
74+
>
75+
Support
76+
</a>
77+
<a
78+
href="/"
79+
className="block px-4 py-2 text-sm leading-5 text-gray-700 hover:bg-gray-100 hover:text-gray-900 focus:outline-none focus:bg-gray-100 focus:text-gray-900"
80+
role="menuitem"
81+
>
82+
License
83+
</a>
84+
<form method="POST" action="#">
85+
<button
86+
type="submit"
87+
className="block w-full px-4 py-2 text-sm leading-5 text-left text-gray-700 hover:bg-gray-100 hover:text-gray-900 focus:outline-none focus:bg-gray-100 focus:text-gray-900"
88+
role="menuitem"
89+
>
90+
Sign out
91+
</button>
92+
</form>
93+
</div>
94+
</div>
95+
</Transition>
96+
</div>
97+
)
98+
}

0 commit comments

Comments
 (0)