forked from storacha/w3up
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat: Customizable UI components (storacha#208)
Introduces a new package called `@w3ui/react` that uses the headless components to build higher level components with some markup and styling. The markup is not customizable, but @cmunns has been proving out how we can make the styling customizable using CSS variables in storacha/w3ui#140 `@w3ui/react` has 3 components that wrap the underlying react-{keyring|uploader|uploads-list} packages and one that brings them all together into a component that developers should be able to simply drop into their applications and start uploading files. Signed-off-by: Oli Evans <[email protected]> Co-authored-by: Alan Shaw <[email protected]> Co-authored-by: Yusef Napora <[email protected]> Co-authored-by: Nathan Vander Wilt <[email protected]> Co-authored-by: Oli Evans <[email protected]>
- Loading branch information
1 parent
f733227
commit 0a776fe
Showing
30 changed files
with
8,439 additions
and
570 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,7 @@ | ||
# `@w3ui/react` | ||
|
||
## Install | ||
|
||
```sh | ||
npm install @w3ui/react | ||
``` |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,24 @@ | ||
# Logs | ||
logs | ||
*.log | ||
npm-debug.log* | ||
yarn-debug.log* | ||
yarn-error.log* | ||
pnpm-debug.log* | ||
lerna-debug.log* | ||
|
||
node_modules | ||
dist | ||
dist-ssr | ||
*.local | ||
|
||
# Editor directories and files | ||
.vscode/* | ||
!.vscode/extensions.json | ||
.idea | ||
.DS_Store | ||
*.suo | ||
*.ntvs* | ||
*.njsproj | ||
*.sln | ||
*.sw? |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,23 @@ | ||
# W3UI React Playground | ||
|
||
To run this example: | ||
|
||
- Clone the w3ui repository and enter the `w3ui` directory | ||
|
||
```sh | ||
git clone https://github.com/web3-storage/w3ui | ||
cd w3ui | ||
``` | ||
|
||
- Install dependencies and build: | ||
|
||
```sh | ||
pnpm install | ||
``` | ||
|
||
- Change to this example directory and run the example: | ||
|
||
```sh | ||
cd examples/react/playground | ||
pnpm start | ||
``` |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,13 @@ | ||
<!DOCTYPE html> | ||
<html lang="en"> | ||
<head> | ||
<meta charset="UTF-8" /> | ||
<link rel="icon" href="/favicon.ico" /> | ||
<meta name="viewport" content="width=device-width, initial-scale=1.0" /> | ||
<title>W3UI React Playground</title> | ||
</head> | ||
<body style="background-color:#1d2027;"> | ||
<div id="root"></div> | ||
<script type="module" src="/src/index.jsx"></script> | ||
</body> | ||
</html> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,29 @@ | ||
{ | ||
"name": "@w3ui/example-react-playground", | ||
"private": true, | ||
"version": "0.0.0", | ||
"type": "module", | ||
"scripts": { | ||
"start": "vite", | ||
"dev": "vite", | ||
"build": "vite build", | ||
"preview": "vite preview" | ||
}, | ||
"dependencies": { | ||
"@w3ui/react-keyring": "workspace:^", | ||
"@w3ui/react": "workspace:^", | ||
"@w3ui/react-uploader": "workspace:^", | ||
"@w3ui/react-uploads-list": "workspace:^", | ||
"react": "^18.2.0", | ||
"react-dom": "^18.2.0", | ||
"react-scripts": "5.0.1", | ||
"react-syntax-highlighter": "^15.5.0" | ||
}, | ||
"devDependencies": { | ||
"@types/react": "^18.0.26", | ||
"@types/react-dom": "^18.0.9", | ||
"@vitejs/plugin-react": "^3.0.0", | ||
"vite": "^4.0.0" | ||
} | ||
|
||
} |
Binary file not shown.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,18 @@ | ||
import logo from './logo.png' | ||
import ContentPage from './ContentPage' | ||
import { KeyringProvider } from '@w3ui/react-keyring' | ||
|
||
function App () { | ||
return ( | ||
<main> | ||
<header style={{ textAlign: 'center', padding: 10 }}> | ||
<img src={logo} width='250' alt='logo' /> | ||
</header> | ||
<KeyringProvider> | ||
<ContentPage /> | ||
</KeyringProvider> | ||
</main> | ||
) | ||
} | ||
|
||
export default App |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,178 @@ | ||
import React from 'react' | ||
import { UploadsList, Uploader, W3Upload } from '@w3ui/react' | ||
import '@w3ui/react/src/styles/uploader.css' | ||
import { Uploader as UploaderCore, UploaderProvider, useUploader } from '@w3ui/react-uploader' | ||
import { UploadsListProvider } from '@w3ui/react-uploads-list' | ||
import { Prism as SyntaxHighlighter } from 'react-syntax-highlighter' | ||
import { a11yDark } from 'react-syntax-highlighter/dist/esm/styles/prism' | ||
|
||
function NoUIUploadComponent () { | ||
const [{ storedDAGShards }, uploader] = useUploader() | ||
|
||
return ( | ||
<div> | ||
<input type='file' onChange={e => uploader.uploadFile(e.target.files[0])} /> | ||
{storedDAGShards?.map(({ cid, size }) => ( | ||
<p key={cid.toString()}> | ||
{cid.toString()} ({size} bytes) | ||
</p> | ||
))} | ||
</div> | ||
) | ||
} | ||
|
||
function NoUIComponent () { | ||
return ( | ||
<UploaderProvider> | ||
<NoUIUploadComponent /> | ||
</UploaderProvider> | ||
) | ||
} | ||
|
||
function HeadlessUIComponent () { | ||
return ( | ||
<UploaderProvider> | ||
<UploaderCore> | ||
<UploaderCore.Form> | ||
<div> | ||
<label htmlFor='file'>File:</label> | ||
<UploaderCore.Input id='file' /> | ||
</div> | ||
<button type='submit'>Upload</button> | ||
</UploaderCore.Form> | ||
</UploaderCore> | ||
</UploaderProvider> | ||
) | ||
} | ||
|
||
function CustomizableUIComponent () { | ||
return ( | ||
<div> | ||
<UploaderProvider> | ||
<Uploader /> | ||
</UploaderProvider> | ||
<UploadsListProvider> | ||
<UploadsList /> | ||
</UploadsListProvider> | ||
</div> | ||
) | ||
} | ||
|
||
function DropinUIComponent () { | ||
return ( | ||
<W3Upload /> | ||
) | ||
} | ||
|
||
export function ContentPage () { | ||
return ( | ||
<> | ||
<p style={{ color: 'lightgray', padding: '24px 0', fontSize: '1.2rem', fontWeight: '300' }}> | ||
W3UI provides React components at four different levels of | ||
abstraction, each of which builds upon the last. | ||
</p> | ||
|
||
<section> | ||
<h2>1. Drop-in UI</h2> | ||
<p> | ||
Components designed to be dropped into a page with very little customization. | ||
Limited styling customization may be supported: | ||
</p> | ||
<SyntaxHighlighter language='jsx' style={a11yDark}> | ||
{`import { W3Upload } from '@w3ui/react' | ||
function ${DropinUIComponent.name} () { | ||
return ( | ||
<W3Upload /> | ||
) | ||
}`} | ||
</SyntaxHighlighter> | ||
<DropinUIComponent /> | ||
</section> | ||
|
||
<section> | ||
<h2>2. Customizable UI</h2> | ||
<p> | ||
Components that work out of the box, and let you customize some aspects of | ||
markup and most of the styling: | ||
</p> | ||
<SyntaxHighlighter language='jsx' style={a11yDark}> | ||
{`import { W3Upload, UploaderProvider } from '@w3ui/react' | ||
function ${CustomizableUIComponent.name}() { | ||
return ( | ||
<UploaderProvider> | ||
<Uploader /> | ||
</UploaderProvider> | ||
) | ||
} | ||
`} | ||
</SyntaxHighlighter> | ||
<CustomizableUIComponent /> | ||
</section> | ||
|
||
<section> | ||
<h2>3. Headless UI</h2> | ||
<p> | ||
A set of components designed to work together, modeled after HeadlessUI | ||
components like <a href='https://headlessui.com/react/combobox'>Combobox</a>. | ||
These components don't make any markup or styling choices for you: | ||
</p> | ||
<SyntaxHighlighter language='jsx' style={a11yDark}> | ||
{`import { Uploader } from '@w3ui/react-uploader' | ||
function ${HeadlessUIComponent.name}() { | ||
return ( | ||
<Uploader> | ||
<Uploader.Form> | ||
<div> | ||
<label htmlFor='file'>File:</label> | ||
<Uploader.Input id="file" /> | ||
</div> | ||
<button type='submit'>Upload</button> | ||
</Uploader.Form> | ||
</Uploader> | ||
) | ||
}`} | ||
</SyntaxHighlighter> | ||
<HeadlessUIComponent /> | ||
</section> | ||
|
||
<h2>4. No UI</h2> | ||
<p> | ||
Maximum flexibility, React Contexts and hooks only: | ||
</p> | ||
<SyntaxHighlighter language='jsx' style={a11yDark}> | ||
{`import { useUploader, UploaderProvider } from '@w3ui/react-uploader' | ||
function ${NoUIUploadComponent.name}() { | ||
const [{ storedDAGShards }, uploader] = useUploader() | ||
return ( | ||
<div> | ||
<input type="file" onChange={e => uploader.uploadFile(e.target.files[0])} /> | ||
{storedDAGShards?.map(({ cid, size }) => ( | ||
<p key={cid.toString()}> | ||
{cid.toString()} ({size} bytes) | ||
</p> | ||
))} | ||
</div> | ||
) | ||
} | ||
function ${NoUIComponent.name}() { | ||
return ( | ||
<UploaderProvider> | ||
<NoUIUploadComponent /> | ||
</UploaderProvider> | ||
) | ||
} | ||
`} | ||
</SyntaxHighlighter> | ||
<NoUIComponent /> | ||
|
||
</> | ||
) | ||
} | ||
|
||
export default ContentPage |
58 changes: 58 additions & 0 deletions
58
examples/react/playground/src/components/Authenticator.jsx
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,58 @@ | ||
import React, { useState } from 'react' | ||
import { useKeyring } from '@w3ui/react-keyring' | ||
|
||
export default function Authenticator ({ children }) { | ||
const [{ space }, { createSpace, registerSpace, cancelRegisterSpace }] = useKeyring() | ||
const [email, setEmail] = useState('') | ||
const [submitted, setSubmitted] = useState(false) | ||
|
||
if (space?.registered()) { | ||
return children | ||
} | ||
|
||
if (submitted) { | ||
return ( | ||
<div> | ||
<h1 className='near-white'>Verify your email address!</h1> | ||
<p>Click the link in the email we sent to {email} to sign in.</p> | ||
<form onSubmit={e => { e.preventDefault(); cancelRegisterSpace() }}> | ||
<button type='submit' className='ph3 pv2'>Cancel</button> | ||
</form> | ||
</div> | ||
) | ||
} | ||
|
||
const handleRegisterSubmit = async e => { | ||
e.preventDefault() | ||
setSubmitted(true) | ||
try { | ||
await createSpace() | ||
await registerSpace(email) | ||
} catch (err) { | ||
throw new Error('failed to register', { cause: err }) | ||
} finally { | ||
setSubmitted(false) | ||
} | ||
} | ||
|
||
return ( | ||
<form onSubmit={handleRegisterSubmit}> | ||
<div className='mb3'> | ||
<label htmlFor='email' className='db mb2'>Email address:</label> | ||
<input id='email' className='db pa2 w-100' type='email' value={email} onChange={e => setEmail(e.target.value)} required /> | ||
</div> | ||
<button type='submit' className='ph3 pv2' disabled={submitted}>Register</button> | ||
</form> | ||
) | ||
} | ||
|
||
/** | ||
* Wrapping a component with this HoC ensures an identity exists. | ||
*/ | ||
export function withIdentity (Component) { | ||
return props => ( | ||
<Authenticator> | ||
<Component {...props} /> | ||
</Authenticator> | ||
) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,32 @@ | ||
body { | ||
margin: 0; | ||
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen', | ||
'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue', | ||
sans-serif; | ||
-webkit-font-smoothing: antialiased; | ||
-moz-osx-font-smoothing: grayscale; | ||
color: white; | ||
} | ||
|
||
code { | ||
font-family: source-code-pro, Menlo, Monaco, Consolas, 'Courier New', | ||
monospace; | ||
} | ||
|
||
main { | ||
padding: 96px; | ||
max-width: 1024px; | ||
margin: 0 auto; | ||
} | ||
|
||
.title { | ||
display: flex; | ||
justify-content: center; | ||
} | ||
|
||
section::after { | ||
display: block; | ||
text-align: center; | ||
padding: 64px 0; | ||
content: '⁂'; | ||
} |
Oops, something went wrong.