Skip to content

Commit

Permalink
chore: eject @w3ui/react from w3console (storacha#383)
Browse files Browse the repository at this point in the history
We don't have the bandwidth to support @w3ui/react anymore, so migrate
w3console to use the "headless" components directly.

Importantly, this means we can use idiomatic TailwindCSS across the
board rather than the hybrid raw-CSS/Tailwind approach we had been
using.

A few years ago I would have considered the CSS changes here a step
backwards, moving from semantic classes to Tailwind utility classes
sprinkled everywhere. While there may be some opportunities for
CSS-class-based abstraction in a few places, Tailwind's docs make the
case for being extremely conservative with this type of
componentization:

https://tailwindcss.com/docs/reusing-styles

I have created a `w3ui-button` class, but it probably needs to be pared
down - evidence for this can be found in the authenticator's "Register"
button in which I needed to copy most of the styles from the
`w3ui-button` class to customize it the way I wanted. I also created an
`authenticator` class, but it may be a better idea to create a React
component for this.

I have this configured to merge to `feat/pre-paging` for now to make
this PR cleaner, but won't merge it until that hits `main` and this gets
automatically retargetted.
  • Loading branch information
travis authored Feb 24, 2023
1 parent 5cb7792 commit 8d2316a
Show file tree
Hide file tree
Showing 18 changed files with 679 additions and 51 deletions.
4 changes: 3 additions & 1 deletion packages/w3ui/examples/react/w3console/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -10,13 +10,15 @@
"preview": "vite preview"
},
"dependencies": {
"@headlessui/react": "^1.7.7",
"@heroicons/react": "^2.0.13",
"@ipld/car": "^5.1.0",
"@ipld/dag-ucan": "^3.2.0",
"@w3ui/keyring-core": "workspace:^",
"@w3ui/react": "workspace:^",
"@w3ui/react-keyring": "workspace:^",
"@w3ui/react-uploader": "workspace:^",
"@w3ui/react-uploads-list": "workspace:^",
"@w3ui/uploader-core": "workspace:^",
"blueimp-md5": "^2.19.0",
"preact": "^10.11.3"
},
Expand Down
35 changes: 16 additions & 19 deletions packages/w3ui/examples/react/w3console/src/app.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,30 +2,27 @@ import type { ChangeEvent } from 'react'
import type { Space } from '@w3ui/keyring-core'

import { useEffect, useState } from 'react'
import {
Authenticator,
Uploader,
UploadsList,
W3APIProvider,
SpaceFinder,
SpaceCreator,
} from '@w3ui/react'
import { DIDKey } from '@ucanto/interface'
import { useKeyring } from '@w3ui/react-keyring'
import { useUploadsList } from '@w3ui/react-uploads-list'
import { ShareIcon } from '@heroicons/react/20/solid'
import md5 from 'blueimp-md5'
import '@w3ui/react/src/styles/all.css'
import { SpaceShare } from './share'
import { DIDKey } from '@ucanto/interface'
import { Authenticator } from './components/Authenticator'
import { Uploader } from './components/Uploader'
import { UploadsList } from './components/UploadsList'
import { W3APIProvider } from './components/W3API'
import { SpaceFinder } from './components/SpaceFinder'
import { SpaceCreator } from './components/SpaceCreator'

function SpaceRegistrar(): JSX.Element {
function SpaceRegistrar (): JSX.Element {
const [, { registerSpace }] = useKeyring()
const [email, setEmail] = useState('')
const [submitted, setSubmitted] = useState(false)
function resetForm(): void {
function resetForm (): void {
setEmail('')
}
async function onSubmit(e: React.FormEvent<HTMLFormElement>): Promise<void> {
async function onSubmit (e: React.FormEvent<HTMLFormElement>): Promise<void> {
e.preventDefault()
setSubmitted(true)
try {
Expand Down Expand Up @@ -87,7 +84,7 @@ interface SpaceSectionProps {
share: boolean
}

function SpaceSection(props: SpaceSectionProps): JSX.Element {
function SpaceSection (props: SpaceSectionProps): JSX.Element {
const { viewSpace, share, setShare } = props
const [{ space }] = useKeyring()
const [, { reload }] = useUploadsList()
Expand Down Expand Up @@ -149,7 +146,7 @@ function SpaceSection(props: SpaceSectionProps): JSX.Element {
)
}

function SpaceSelector(props: any): JSX.Element {
function SpaceSelector (props: any): JSX.Element {
const { selected, setSelected, spaces } = props
return (
<div>
Expand All @@ -167,7 +164,7 @@ function SpaceSelector(props: any): JSX.Element {
)
}

export function Logo(): JSX.Element {
export function Logo (): JSX.Element {
return (
<h1 className='font-bold flex flex-row justify-center items-center gap-2'>
<svg
Expand All @@ -190,11 +187,11 @@ export function Logo(): JSX.Element {
)
}

export function Layout(): JsxElement {
export function Layout (): JsxElement {
const [share, setShare] = useState(false)
const [{ space, spaces }, { setCurrentSpace }] = useKeyring()

function viewSpace(did: DIDKey): void {
function viewSpace (did: DIDKey): void {
setShare(false)
void setCurrentSpace(did)
}
Expand Down Expand Up @@ -223,7 +220,7 @@ export function Layout(): JsxElement {
)
}

export function App(): JSX.Element {
export function App (): JSX.Element {
return (
<W3APIProvider uploadsListPageSize={20}>
<Authenticator className='h-full'>
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
import React from 'react'
import {
Authenticator as AuthCore,
useAuthenticator
} from '@w3ui/react-keyring'

export function AuthenticationForm (): JSX.Element {
const [{ submitted }] = useAuthenticator()

return (
<div className='authenticator'>
<AuthCore.Form className='bg-gradient-to-br from-blue-500 to-cyan-500 rounded-xl shadow-md px-10 pt-14 pb-8'>
<div>
<label className='block mb-2 uppercase text-white/80 text-xs font-semibold tracking-wider m-1 font-mono' htmlFor='authenticator-email'>Email</label>
<AuthCore.EmailInput className='block rounded-md p-2 w-80 bg-white shadow-md' id='authenticator-email' required />
</div>
<button
className='mt-2 bg-white/0 w-full hover:bg-blue-800 rounded-md w-full text-sm font-medium text-white py-2 px-8 transition-colors ease-in'
type='submit'
disabled={submitted}
>
Register
</button>
</AuthCore.Form>
</div>
)
}

export function AuthenticationSubmitted (): JSX.Element {
const [{ email }] = useAuthenticator()

return (
<div className='authenticator'>
<div className='bg-gray-400 px-24 py-16 rounded-md'>
<h1 className='text-xl'>Verify your email address!</h1>
<p className='pt-2 pb-4'>
Click the link in the email we sent to {email} to sign in.
</p>
<AuthCore.CancelButton className='w3ui-button w-full'>
Cancel
</AuthCore.CancelButton>
</div>
</div>
)
}

export function AuthenticationEnsurer ({
children
}: {
children: JSX.Element | JSX.Element[]
}): JSX.Element {
const [{ spaces, submitted }] = useAuthenticator()
const registered = Boolean(spaces.some((s) => s.registered()))
if (registered) {
return <>{children}</>
}
if (submitted) {
return <AuthenticationSubmitted />
}
return <AuthenticationForm />
}

interface AuthenticatorProps {
children: JSX.Element | JSX.Element[]
className?: string
}

export function Authenticator ({
children,
className = ''
}: AuthenticatorProps): JSX.Element {
return (
<AuthCore as='div' className={className}>
<AuthenticationEnsurer>{children}</AuthenticationEnsurer>
</AuthCore>
)
}

/**
* Wrapping a component with this HoC ensures an identity exists.
*/
export function withIdentity<C extends React.JSXElementConstructor<P>, P> (
Component: C
) {
return (props: any) => (
<Authenticator>
<Component {...props} />
</Authenticator>
)
}
104 changes: 104 additions & 0 deletions packages/w3ui/examples/react/w3console/src/components/SpaceCreator.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,104 @@
import type { ChangeEvent } from 'react'

import React, { useState } from 'react'
import { useKeyring } from '@w3ui/react-keyring'
import { ArrowPathIcon } from '@heroicons/react/20/solid'

export function SpaceCreatorCreating (): JSX.Element {
return (
<div className='flex flex-col items-center space-y-4'>
<h5>Creating Space...</h5>
<ArrowPathIcon className='animate-spin w-6' />
</div>
)
}

interface SpaceCreatorProps {
className?: string
}

export function SpaceCreator ({
className = ''
}: SpaceCreatorProps): JSX.Element {
const [, { createSpace, registerSpace }] = useKeyring()
const [creating, setCreating] = useState(false)
const [submitted, setSubmitted] = useState(false)
const [email, setEmail] = useState('')
const [name, setName] = useState('')

function resetForm (): void {
setEmail('')
setName('')
}

async function onSubmit (e: React.FormEvent<HTMLFormElement>): Promise<void> {
e.preventDefault()
setSubmitted(true)
try {
await createSpace(name)
// ignore this because the Space UI should handle helping the user recover
// from space registration failure
void registerSpace(email)
} catch (error) {
/* eslint-disable no-console */
console.error(error)
/* eslint-enable no-console */
throw new Error('failed to register', { cause: error })
} finally {
resetForm()
setSubmitted(false)
}
}
/* eslint-disable no-nested-ternary */
return (
<div className={`${className}`}>
{creating
? (
submitted
? (
<SpaceCreatorCreating />
)
: (
<form
className='flex flex-col space-y-2'
onSubmit={(e: React.FormEvent<HTMLFormElement>) => {
void onSubmit(e)
}}
>
<input
className='text-black py-1 px-2 rounded'
type='email'
placeholder='Email'
value={email}
onChange={(e: ChangeEvent<HTMLInputElement>) => {
setEmail(e.target.value)
}}
/>
<input
className='text-black py-1 px-2 rounded'
placeholder='Name'
value={name}
onChange={(e: ChangeEvent<HTMLInputElement>) => {
setName(e.target.value)
}}
/>
<input
type='submit'
className='w3ui-button'
value='Create'
/>
</form>
)
)
: (
<button
className='w3ui-button py-2'
onClick={() => { setCreating(true) }}
>
Add Space
</button>
)}
</div>
)
/* eslint-enable no-nested-ternary */
}
Loading

0 comments on commit 8d2316a

Please sign in to comment.