dialogs: updates to design and mobile tweaks

This commit is contained in:
Hunter Miller 2021-08-23 14:18:07 -05:00
parent 952d5c0c38
commit 1c421963d1
9 changed files with 96 additions and 65 deletions

View File

@ -2,7 +2,7 @@ import React from 'react';
import type * as Polymorphic from '@radix-ui/react-polymorphic';
import classNames from 'classnames';
type ButtonVariant = 'primary' | 'secondary' | 'destructive';
type ButtonVariant = 'primary' | 'secondary' | 'destructive' | 'alt-primary' | 'alt-secondary';
type PolymorphicButton = Polymorphic.ForwardRefComponent<
'button',
@ -12,9 +12,11 @@ type PolymorphicButton = Polymorphic.ForwardRefComponent<
>;
const variants: Record<ButtonVariant, string> = {
primary: 'text-white bg-blue-400',
secondary: 'text-blue-400 bg-blue-100',
destructive: 'text-white bg-red-400'
primary: 'text-white bg-black',
secondary: 'text-black bg-gray-100',
destructive: 'text-white bg-red-400',
'alt-primary': 'text-white bg-blue-400',
'alt-secondary': 'text-blue-400 bg-blue-100'
};
export const Button = React.forwardRef(

View File

@ -163,13 +163,13 @@ export const Nav: FunctionComponent<NavProps> = ({ menu }) => {
<Dialog open={isOpen} onOpenChange={onDialogClose}>
<DialogContent
onOpenAutoFocus={onOpen}
className="fixed top-0 left-[calc(50%)] w-[calc(100%-15px)] max-w-3xl px-4 text-gray-400 -translate-x-1/2 outline-none"
className="fixed bottom-0 sm:top-0 left-[calc(50%-7.5px)] flex flex-col w-[calc(100%-15px)] max-w-3xl px-4 text-gray-400 -translate-x-1/2 outline-none"
role="combobox"
aria-controls="leap-items"
aria-owns="leap-items"
aria-expanded={isOpen}
>
<header ref={dialogNavRef} className="my-6" />
<header ref={dialogNavRef} className="my-6 order-last sm:order-none" />
<div
id="leap-items"
className="grid grid-rows-[fit-content(calc(100vh-7.5rem))] bg-white rounded-3xl overflow-hidden default-ring"

View File

@ -2,7 +2,8 @@ import { chadIsRunning } from '@urbit/api/docket';
import clipboardCopy from 'clipboard-copy';
import React, { useEffect } from 'react';
import { useParams } from 'react-router-dom';
import { PillButton } from '../../components/Button';
import { Button, PillButton } from '../../components/Button';
import { Dialog, DialogClose, DialogContent, DialogTrigger } from '../../components/Dialog';
import { DocketHeader } from '../../components/DocketHeader';
import { ShipName } from '../../components/ShipName';
import { Spinner } from '../../components/Spinner';
@ -49,23 +50,45 @@ export const AppInfo = () => {
<DocketHeader docket={treaty}>
<div className="col-span-2 md:col-span-1 flex items-center space-x-4">
{installed && (
<PillButton as="a" href={getAppHref(treaty.href)} target={treaty.title || '_blank'}>
<PillButton
variant="alt-primary"
as="a"
href={getAppHref(treaty.href)}
target={treaty.title || '_blank'}
>
Open App
</PillButton>
)}
{!installed && (
<PillButton onClick={installApp}>
{installing ? (
<>
<Spinner />
<span className="sr-only">Installing...</span>
</>
) : (
'Get App'
)}
</PillButton>
<Dialog>
<DialogTrigger as={PillButton} variant="alt-primary">
{installing ? (
<>
<Spinner />
<span className="sr-only">Installing...</span>
</>
) : (
'Get App'
)}
</DialogTrigger>
<DialogContent showClose={false} className="max-w-[400px] space-y-6">
<h2 className="h4">Install &ldquo;{treaty.title}&rdquo;</h2>
<p className="text-base tracking-tight pr-6">
This application will be able to view and interact with the contents of your
Urbit. Only install if you trust the developer.
</p>
<div className="flex space-x-6">
<DialogClose as={Button} variant="secondary">
Cancel
</DialogClose>
<DialogClose as={Button} onClick={installApp}>
Get &ldquo;{treaty.title}&rdquo;
</DialogClose>
</div>
</DialogContent>
</Dialog>
)}
<PillButton variant="secondary" onClick={copyApp}>
<PillButton variant="alt-secondary" onClick={copyApp}>
Copy App Link
</PillButton>
</div>

View File

@ -26,14 +26,14 @@ export const Grid: FunctionComponent<GridProps> = ({ match }) => {
return (
<div className="flex flex-col">
<header className="sticky top-0 left-0 z-30 flex justify-center w-full bg-white">
<header className="fixed sm:sticky bottom-0 sm:bottom-auto sm:top-0 left-0 z-30 flex justify-center w-full bg-white">
<Nav menu={match.params.menu} />
</header>
<main className="h-full w-full flex justify-center pt-24 pb-32 relative z-0">
<main className="h-full w-full flex justify-center pt-4 md:pt-16 pb-32 relative z-0">
{!chargesLoaded && <span>Loading...</span>}
{chargesLoaded && (
<div className="grid grid-cols-2 sm:grid-cols-3 md:grid-cols-4 gap-6 px-4 md:px-8 w-full max-w-6xl">
<div className="grid grid-cols-2 sm:grid-cols-3 md:grid-cols-4 gap-4 px-4 md:px-8 w-full max-w-6xl">
{charges &&
map(omit(charges, 'grid'), (charge, desk) => (
<Tile key={desk} charge={charge} desk={desk} />

View File

@ -1,9 +1,7 @@
import create from 'zustand';
import produce from 'immer';
import { useCallback, useEffect } from 'react';
import { omit } from 'lodash-es';
import api from './api';
import { mockAllies, mockCharges, mockTreaties } from './mock-data';
import { mapValues, omit, pick } from 'lodash-es';
import {
Allies,
Charge,
@ -21,8 +19,9 @@ import {
docketInstall,
ChargeUpdate
} from '@urbit/api/docket';
import _ from 'lodash';
import {kilnRevive, kilnSuspend} from '@urbit/api/hood';
import { kilnRevive, kilnSuspend } from '@urbit/api/hood';
import api from './api';
import { mockAllies, mockCharges, mockTreaties } from './mock-data';
const useMockData = import.meta.env.MODE === 'mock';
@ -67,8 +66,10 @@ const useDocketState = create<DocketState>((set, get) => ({
return allies;
},
fetchAllyTreaties: async (ally: string) => {
let treaties = useMockData ? mockTreaties : (await api.scry<TreatyUpdateIni>(scryAllyTreaties(ally))).ini;
treaties = _.mapValues(treaties, normalizeDocket);
let treaties = useMockData
? mockTreaties
: (await api.scry<TreatyUpdateIni>(scryAllyTreaties(ally))).ini;
treaties = mapValues(treaties, normalizeDocket);
set((s) => ({ treaties: { ...s.treaties, ...treaties } }));
return treaties;
},
@ -97,10 +98,9 @@ const useDocketState = create<DocketState>((set, get) => ({
throw new Error('Bad install');
}
if (useMockData) {
set((state) => addCharge(state, desk, {...treaty, chad: { install: null }}));
set((state) => addCharge(state, desk, { ...treaty, chad: { install: null } }));
await new Promise<void>((res) => setTimeout(() => res(), 5000));
set((state) => addCharge(state, desk, {...treaty, chad: { glob: null }}));
set((state) => addCharge(state, desk, { ...treaty, chad: { glob: null } }));
}
return api.poke(docketInstall(ship, desk));
@ -117,7 +117,7 @@ const useDocketState = create<DocketState>((set, get) => ({
});
},
toggleDocket: async (desk: string) => {
if(useMockData) {
if (useMockData) {
set(
produce((draft) => {
const charge = draft.charges[desk];
@ -127,11 +127,11 @@ const useDocketState = create<DocketState>((set, get) => ({
}
const { charges } = get();
const charge = charges[desk];
if(!charge) {
if (!charge) {
return;
}
const suspended = 'suspend' in charge.chad;
if(suspended) {
if (suspended) {
await api.poke(kilnRevive(desk));
} else {
await api.poke(kilnSuspend(desk));
@ -175,15 +175,14 @@ api.subscribe({
path: '/charges',
event: (data: ChargeUpdate) => {
useDocketState.setState((state) => {
if ('add-charge' in data) {
const { desk, charge } = data['add-charge']
return addCharge(state, desk, charge)
const { desk, charge } = data['add-charge'];
return addCharge(state, desk, charge);
}
if ('del-charge' in data) {
const desk = data['del-charge'];
return delCharge(state, desk)
return delCharge(state, desk);
}
return { charges: state.charges };
@ -222,7 +221,7 @@ export function useAllyTreaties(ship: string) {
useCallback(
(s) => {
const charter = s.allies[ship];
return _.pick(s.treaties, ...(charter || []));
return pick(s.treaties, ...(charter || []));
},
[ship]
)
@ -242,6 +241,6 @@ export function useTreaty(host: string, desk: string) {
}
// xx useful for debugging
//window.docket = useDocketState.getState;
// window.docket = useDocketState.getState;
export default useDocketState;

View File

@ -1,6 +1,6 @@
import systemUrl from '../assets/system.png';
import _ from 'lodash';
import _ from 'lodash-es';
import { Allies, Charges, DocketHrefGlob, Treaties, Treaty } from '@urbit/api/docket';
import systemUrl from '../assets/system.png';
export const appMetaData: Pick<Treaty, 'cass' | 'hash' | 'website' | 'license' | 'version'> = {
cass: '~2021.8.11..05.11.10..b721',
@ -127,9 +127,8 @@ export const mockCharges: Charges = _.reduce(
mockTreaties,
(acc, val, key) => {
const [, desk] = key.split('/');
const chad = desk === 'uniswap'
? { install: null } : { glob : null };
if(desk === 'inbox') {
const chad = { glob: null };
if (desk === 'inbox') {
return acc;
}

View File

@ -1,7 +1,7 @@
import React, { useCallback } from 'react';
import { useHistory, useParams } from 'react-router-dom';
import { Button } from '../components/Button';
import { Dialog, DialogContent } from '../components/Dialog';
import { Dialog, DialogClose, DialogContent } from '../components/Dialog';
import useDocketState, { useCharges } from '../state/docket';
export const RemoveApp = () => {
@ -14,19 +14,23 @@ export const RemoveApp = () => {
// TODO: add optimistic updates
const handleRemoveApp = useCallback(() => {
uninstallDocket(desk);
history.push('/');
}, []);
}, [desk]);
return (
<Dialog open onOpenChange={(open) => !open && history.push('/')}>
<DialogContent>
<h1 className="h4 mb-9">Remove &ldquo;{docket?.title || ''}&rdquo;</h1>
<p className="text-base tracking-tight mb-4 pr-6">
Explanatory writing about what data will be kept.
<DialogContent showClose={false} className="max-w-[400px] space-y-6">
<h1 className="h4">Remove &ldquo;{docket?.title || ''}&rdquo;?</h1>
<p className="text-base tracking-tight pr-6">
This will remove the software&apos;s tile from your home screen.
</p>
<Button variant="destructive" onClick={handleRemoveApp}>
Remove
</Button>
<div className="flex space-x-6">
<DialogClose as={Button} variant="secondary">
Cancel
</DialogClose>
<DialogClose as={Button} onClick={handleRemoveApp}>
Remove &ldquo;{docket?.title}&rdquo;
</DialogClose>
</div>
</DialogContent>
</Dialog>
);

View File

@ -1,7 +1,7 @@
import React, { useCallback } from 'react';
import { Redirect, useHistory, useParams } from 'react-router-dom';
import { Button } from '../components/Button';
import { Dialog, DialogContent } from '../components/Dialog';
import { Dialog, DialogClose, DialogContent } from '../components/Dialog';
import useDocketState, { useCharges } from '../state/docket';
export const SuspendApp = () => {
@ -13,7 +13,6 @@ export const SuspendApp = () => {
// TODO: add optimistic updates
const handleSuspendApp = useCallback(() => {
useDocketState.getState().toggleDocket(desk);
history.push('/');
}, [desk]);
if ('suspend' in charge.chad) {
@ -22,14 +21,19 @@ export const SuspendApp = () => {
return (
<Dialog open onOpenChange={(open) => !open && history.push('/')}>
<DialogContent>
<h1 className="h4 mb-9">Suspend &ldquo;{charge?.title || ''}&rdquo;</h1>
<p className="text-base tracking-tight mb-4 pr-6">
<DialogContent showClose={false} className="max-w-[400px] space-y-6">
<h1 className="h4">Suspend &ldquo;{charge?.title || ''}&rdquo;</h1>
<p className="text-base tracking-tight pr-6">
Suspending an app will freeze its current state, and render it unable
</p>
<Button variant="destructive" onClick={handleSuspendApp}>
Suspend
</Button>
<div className="flex space-x-6">
<DialogClose as={Button} variant="secondary">
Cancel
</DialogClose>
<DialogClose as={Button} onClick={handleSuspendApp}>
Suspend &ldquo;{charge?.title}&rdquo;
</DialogClose>
</div>
</DialogContent>
</Dialog>
);

View File

@ -38,7 +38,7 @@ export const Tile: FunctionComponent<TileProps> = ({ charge, desk }) => {
href={active ? link : undefined}
target={desk}
className={classNames(
'group relative font-semibold aspect-w-1 aspect-h-1 rounded-xl default-ring',
'group relative font-semibold aspect-w-1 aspect-h-1 rounded-3xl default-ring',
!active && 'cursor-default'
)}
style={{ backgroundColor: active ? color || 'purple' : suspendColor }}