mirror of
https://github.com/urbit/shrub.git
synced 2025-01-05 11:09:30 +03:00
dialogs: updates to design and mobile tweaks
This commit is contained in:
parent
952d5c0c38
commit
1c421963d1
@ -2,7 +2,7 @@ import React from 'react';
|
|||||||
import type * as Polymorphic from '@radix-ui/react-polymorphic';
|
import type * as Polymorphic from '@radix-ui/react-polymorphic';
|
||||||
import classNames from 'classnames';
|
import classNames from 'classnames';
|
||||||
|
|
||||||
type ButtonVariant = 'primary' | 'secondary' | 'destructive';
|
type ButtonVariant = 'primary' | 'secondary' | 'destructive' | 'alt-primary' | 'alt-secondary';
|
||||||
|
|
||||||
type PolymorphicButton = Polymorphic.ForwardRefComponent<
|
type PolymorphicButton = Polymorphic.ForwardRefComponent<
|
||||||
'button',
|
'button',
|
||||||
@ -12,9 +12,11 @@ type PolymorphicButton = Polymorphic.ForwardRefComponent<
|
|||||||
>;
|
>;
|
||||||
|
|
||||||
const variants: Record<ButtonVariant, string> = {
|
const variants: Record<ButtonVariant, string> = {
|
||||||
primary: 'text-white bg-blue-400',
|
primary: 'text-white bg-black',
|
||||||
secondary: 'text-blue-400 bg-blue-100',
|
secondary: 'text-black bg-gray-100',
|
||||||
destructive: 'text-white bg-red-400'
|
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(
|
export const Button = React.forwardRef(
|
||||||
|
@ -163,13 +163,13 @@ export const Nav: FunctionComponent<NavProps> = ({ menu }) => {
|
|||||||
<Dialog open={isOpen} onOpenChange={onDialogClose}>
|
<Dialog open={isOpen} onOpenChange={onDialogClose}>
|
||||||
<DialogContent
|
<DialogContent
|
||||||
onOpenAutoFocus={onOpen}
|
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"
|
role="combobox"
|
||||||
aria-controls="leap-items"
|
aria-controls="leap-items"
|
||||||
aria-owns="leap-items"
|
aria-owns="leap-items"
|
||||||
aria-expanded={isOpen}
|
aria-expanded={isOpen}
|
||||||
>
|
>
|
||||||
<header ref={dialogNavRef} className="my-6" />
|
<header ref={dialogNavRef} className="my-6 order-last sm:order-none" />
|
||||||
<div
|
<div
|
||||||
id="leap-items"
|
id="leap-items"
|
||||||
className="grid grid-rows-[fit-content(calc(100vh-7.5rem))] bg-white rounded-3xl overflow-hidden default-ring"
|
className="grid grid-rows-[fit-content(calc(100vh-7.5rem))] bg-white rounded-3xl overflow-hidden default-ring"
|
||||||
|
@ -2,7 +2,8 @@ import { chadIsRunning } from '@urbit/api/docket';
|
|||||||
import clipboardCopy from 'clipboard-copy';
|
import clipboardCopy from 'clipboard-copy';
|
||||||
import React, { useEffect } from 'react';
|
import React, { useEffect } from 'react';
|
||||||
import { useParams } from 'react-router-dom';
|
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 { DocketHeader } from '../../components/DocketHeader';
|
||||||
import { ShipName } from '../../components/ShipName';
|
import { ShipName } from '../../components/ShipName';
|
||||||
import { Spinner } from '../../components/Spinner';
|
import { Spinner } from '../../components/Spinner';
|
||||||
@ -49,23 +50,45 @@ export const AppInfo = () => {
|
|||||||
<DocketHeader docket={treaty}>
|
<DocketHeader docket={treaty}>
|
||||||
<div className="col-span-2 md:col-span-1 flex items-center space-x-4">
|
<div className="col-span-2 md:col-span-1 flex items-center space-x-4">
|
||||||
{installed && (
|
{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
|
Open App
|
||||||
</PillButton>
|
</PillButton>
|
||||||
)}
|
)}
|
||||||
{!installed && (
|
{!installed && (
|
||||||
<PillButton onClick={installApp}>
|
<Dialog>
|
||||||
{installing ? (
|
<DialogTrigger as={PillButton} variant="alt-primary">
|
||||||
<>
|
{installing ? (
|
||||||
<Spinner />
|
<>
|
||||||
<span className="sr-only">Installing...</span>
|
<Spinner />
|
||||||
</>
|
<span className="sr-only">Installing...</span>
|
||||||
) : (
|
</>
|
||||||
'Get App'
|
) : (
|
||||||
)}
|
'Get App'
|
||||||
</PillButton>
|
)}
|
||||||
|
</DialogTrigger>
|
||||||
|
<DialogContent showClose={false} className="max-w-[400px] space-y-6">
|
||||||
|
<h2 className="h4">Install “{treaty.title}”</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 “{treaty.title}”
|
||||||
|
</DialogClose>
|
||||||
|
</div>
|
||||||
|
</DialogContent>
|
||||||
|
</Dialog>
|
||||||
)}
|
)}
|
||||||
<PillButton variant="secondary" onClick={copyApp}>
|
<PillButton variant="alt-secondary" onClick={copyApp}>
|
||||||
Copy App Link
|
Copy App Link
|
||||||
</PillButton>
|
</PillButton>
|
||||||
</div>
|
</div>
|
||||||
|
@ -26,14 +26,14 @@ export const Grid: FunctionComponent<GridProps> = ({ match }) => {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="flex flex-col">
|
<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} />
|
<Nav menu={match.params.menu} />
|
||||||
</header>
|
</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 && <span>Loading...</span>}
|
||||||
{chargesLoaded && (
|
{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 &&
|
{charges &&
|
||||||
map(omit(charges, 'grid'), (charge, desk) => (
|
map(omit(charges, 'grid'), (charge, desk) => (
|
||||||
<Tile key={desk} charge={charge} desk={desk} />
|
<Tile key={desk} charge={charge} desk={desk} />
|
||||||
|
@ -1,9 +1,7 @@
|
|||||||
import create from 'zustand';
|
import create from 'zustand';
|
||||||
import produce from 'immer';
|
import produce from 'immer';
|
||||||
import { useCallback, useEffect } from 'react';
|
import { useCallback, useEffect } from 'react';
|
||||||
import { omit } from 'lodash-es';
|
import { mapValues, omit, pick } from 'lodash-es';
|
||||||
import api from './api';
|
|
||||||
import { mockAllies, mockCharges, mockTreaties } from './mock-data';
|
|
||||||
import {
|
import {
|
||||||
Allies,
|
Allies,
|
||||||
Charge,
|
Charge,
|
||||||
@ -21,8 +19,9 @@ import {
|
|||||||
docketInstall,
|
docketInstall,
|
||||||
ChargeUpdate
|
ChargeUpdate
|
||||||
} from '@urbit/api/docket';
|
} 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';
|
const useMockData = import.meta.env.MODE === 'mock';
|
||||||
|
|
||||||
@ -67,8 +66,10 @@ const useDocketState = create<DocketState>((set, get) => ({
|
|||||||
return allies;
|
return allies;
|
||||||
},
|
},
|
||||||
fetchAllyTreaties: async (ally: string) => {
|
fetchAllyTreaties: async (ally: string) => {
|
||||||
let treaties = useMockData ? mockTreaties : (await api.scry<TreatyUpdateIni>(scryAllyTreaties(ally))).ini;
|
let treaties = useMockData
|
||||||
treaties = _.mapValues(treaties, normalizeDocket);
|
? mockTreaties
|
||||||
|
: (await api.scry<TreatyUpdateIni>(scryAllyTreaties(ally))).ini;
|
||||||
|
treaties = mapValues(treaties, normalizeDocket);
|
||||||
set((s) => ({ treaties: { ...s.treaties, ...treaties } }));
|
set((s) => ({ treaties: { ...s.treaties, ...treaties } }));
|
||||||
return treaties;
|
return treaties;
|
||||||
},
|
},
|
||||||
@ -97,10 +98,9 @@ const useDocketState = create<DocketState>((set, get) => ({
|
|||||||
throw new Error('Bad install');
|
throw new Error('Bad install');
|
||||||
}
|
}
|
||||||
if (useMockData) {
|
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));
|
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));
|
return api.poke(docketInstall(ship, desk));
|
||||||
@ -117,7 +117,7 @@ const useDocketState = create<DocketState>((set, get) => ({
|
|||||||
});
|
});
|
||||||
},
|
},
|
||||||
toggleDocket: async (desk: string) => {
|
toggleDocket: async (desk: string) => {
|
||||||
if(useMockData) {
|
if (useMockData) {
|
||||||
set(
|
set(
|
||||||
produce((draft) => {
|
produce((draft) => {
|
||||||
const charge = draft.charges[desk];
|
const charge = draft.charges[desk];
|
||||||
@ -127,11 +127,11 @@ const useDocketState = create<DocketState>((set, get) => ({
|
|||||||
}
|
}
|
||||||
const { charges } = get();
|
const { charges } = get();
|
||||||
const charge = charges[desk];
|
const charge = charges[desk];
|
||||||
if(!charge) {
|
if (!charge) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const suspended = 'suspend' in charge.chad;
|
const suspended = 'suspend' in charge.chad;
|
||||||
if(suspended) {
|
if (suspended) {
|
||||||
await api.poke(kilnRevive(desk));
|
await api.poke(kilnRevive(desk));
|
||||||
} else {
|
} else {
|
||||||
await api.poke(kilnSuspend(desk));
|
await api.poke(kilnSuspend(desk));
|
||||||
@ -175,15 +175,14 @@ api.subscribe({
|
|||||||
path: '/charges',
|
path: '/charges',
|
||||||
event: (data: ChargeUpdate) => {
|
event: (data: ChargeUpdate) => {
|
||||||
useDocketState.setState((state) => {
|
useDocketState.setState((state) => {
|
||||||
|
|
||||||
if ('add-charge' in data) {
|
if ('add-charge' in data) {
|
||||||
const { desk, charge } = data['add-charge']
|
const { desk, charge } = data['add-charge'];
|
||||||
return addCharge(state, desk, charge)
|
return addCharge(state, desk, charge);
|
||||||
}
|
}
|
||||||
|
|
||||||
if ('del-charge' in data) {
|
if ('del-charge' in data) {
|
||||||
const desk = data['del-charge'];
|
const desk = data['del-charge'];
|
||||||
return delCharge(state, desk)
|
return delCharge(state, desk);
|
||||||
}
|
}
|
||||||
|
|
||||||
return { charges: state.charges };
|
return { charges: state.charges };
|
||||||
@ -222,7 +221,7 @@ export function useAllyTreaties(ship: string) {
|
|||||||
useCallback(
|
useCallback(
|
||||||
(s) => {
|
(s) => {
|
||||||
const charter = s.allies[ship];
|
const charter = s.allies[ship];
|
||||||
return _.pick(s.treaties, ...(charter || []));
|
return pick(s.treaties, ...(charter || []));
|
||||||
},
|
},
|
||||||
[ship]
|
[ship]
|
||||||
)
|
)
|
||||||
@ -242,6 +241,6 @@ export function useTreaty(host: string, desk: string) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// xx useful for debugging
|
// xx useful for debugging
|
||||||
//window.docket = useDocketState.getState;
|
// window.docket = useDocketState.getState;
|
||||||
|
|
||||||
export default useDocketState;
|
export default useDocketState;
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
import systemUrl from '../assets/system.png';
|
import _ from 'lodash-es';
|
||||||
import _ from 'lodash';
|
|
||||||
import { Allies, Charges, DocketHrefGlob, Treaties, Treaty } from '@urbit/api/docket';
|
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'> = {
|
export const appMetaData: Pick<Treaty, 'cass' | 'hash' | 'website' | 'license' | 'version'> = {
|
||||||
cass: '~2021.8.11..05.11.10..b721',
|
cass: '~2021.8.11..05.11.10..b721',
|
||||||
@ -127,9 +127,8 @@ export const mockCharges: Charges = _.reduce(
|
|||||||
mockTreaties,
|
mockTreaties,
|
||||||
(acc, val, key) => {
|
(acc, val, key) => {
|
||||||
const [, desk] = key.split('/');
|
const [, desk] = key.split('/');
|
||||||
const chad = desk === 'uniswap'
|
const chad = { glob: null };
|
||||||
? { install: null } : { glob : null };
|
if (desk === 'inbox') {
|
||||||
if(desk === 'inbox') {
|
|
||||||
return acc;
|
return acc;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
import React, { useCallback } from 'react';
|
import React, { useCallback } from 'react';
|
||||||
import { useHistory, useParams } from 'react-router-dom';
|
import { useHistory, useParams } from 'react-router-dom';
|
||||||
import { Button } from '../components/Button';
|
import { Button } from '../components/Button';
|
||||||
import { Dialog, DialogContent } from '../components/Dialog';
|
import { Dialog, DialogClose, DialogContent } from '../components/Dialog';
|
||||||
import useDocketState, { useCharges } from '../state/docket';
|
import useDocketState, { useCharges } from '../state/docket';
|
||||||
|
|
||||||
export const RemoveApp = () => {
|
export const RemoveApp = () => {
|
||||||
@ -14,19 +14,23 @@ export const RemoveApp = () => {
|
|||||||
// TODO: add optimistic updates
|
// TODO: add optimistic updates
|
||||||
const handleRemoveApp = useCallback(() => {
|
const handleRemoveApp = useCallback(() => {
|
||||||
uninstallDocket(desk);
|
uninstallDocket(desk);
|
||||||
history.push('/');
|
}, [desk]);
|
||||||
}, []);
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Dialog open onOpenChange={(open) => !open && history.push('/')}>
|
<Dialog open onOpenChange={(open) => !open && history.push('/')}>
|
||||||
<DialogContent>
|
<DialogContent showClose={false} className="max-w-[400px] space-y-6">
|
||||||
<h1 className="h4 mb-9">Remove “{docket?.title || ''}”</h1>
|
<h1 className="h4">Remove “{docket?.title || ''}”?</h1>
|
||||||
<p className="text-base tracking-tight mb-4 pr-6">
|
<p className="text-base tracking-tight pr-6">
|
||||||
Explanatory writing about what data will be kept.
|
This will remove the software's tile from your home screen.
|
||||||
</p>
|
</p>
|
||||||
<Button variant="destructive" onClick={handleRemoveApp}>
|
<div className="flex space-x-6">
|
||||||
Remove
|
<DialogClose as={Button} variant="secondary">
|
||||||
</Button>
|
Cancel
|
||||||
|
</DialogClose>
|
||||||
|
<DialogClose as={Button} onClick={handleRemoveApp}>
|
||||||
|
Remove “{docket?.title}”
|
||||||
|
</DialogClose>
|
||||||
|
</div>
|
||||||
</DialogContent>
|
</DialogContent>
|
||||||
</Dialog>
|
</Dialog>
|
||||||
);
|
);
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
import React, { useCallback } from 'react';
|
import React, { useCallback } from 'react';
|
||||||
import { Redirect, useHistory, useParams } from 'react-router-dom';
|
import { Redirect, useHistory, useParams } from 'react-router-dom';
|
||||||
import { Button } from '../components/Button';
|
import { Button } from '../components/Button';
|
||||||
import { Dialog, DialogContent } from '../components/Dialog';
|
import { Dialog, DialogClose, DialogContent } from '../components/Dialog';
|
||||||
import useDocketState, { useCharges } from '../state/docket';
|
import useDocketState, { useCharges } from '../state/docket';
|
||||||
|
|
||||||
export const SuspendApp = () => {
|
export const SuspendApp = () => {
|
||||||
@ -13,7 +13,6 @@ export const SuspendApp = () => {
|
|||||||
// TODO: add optimistic updates
|
// TODO: add optimistic updates
|
||||||
const handleSuspendApp = useCallback(() => {
|
const handleSuspendApp = useCallback(() => {
|
||||||
useDocketState.getState().toggleDocket(desk);
|
useDocketState.getState().toggleDocket(desk);
|
||||||
history.push('/');
|
|
||||||
}, [desk]);
|
}, [desk]);
|
||||||
|
|
||||||
if ('suspend' in charge.chad) {
|
if ('suspend' in charge.chad) {
|
||||||
@ -22,14 +21,19 @@ export const SuspendApp = () => {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<Dialog open onOpenChange={(open) => !open && history.push('/')}>
|
<Dialog open onOpenChange={(open) => !open && history.push('/')}>
|
||||||
<DialogContent>
|
<DialogContent showClose={false} className="max-w-[400px] space-y-6">
|
||||||
<h1 className="h4 mb-9">Suspend “{charge?.title || ''}”</h1>
|
<h1 className="h4">Suspend “{charge?.title || ''}”</h1>
|
||||||
<p className="text-base tracking-tight mb-4 pr-6">
|
<p className="text-base tracking-tight pr-6">
|
||||||
Suspending an app will freeze its current state, and render it unable
|
Suspending an app will freeze its current state, and render it unable
|
||||||
</p>
|
</p>
|
||||||
<Button variant="destructive" onClick={handleSuspendApp}>
|
<div className="flex space-x-6">
|
||||||
Suspend
|
<DialogClose as={Button} variant="secondary">
|
||||||
</Button>
|
Cancel
|
||||||
|
</DialogClose>
|
||||||
|
<DialogClose as={Button} onClick={handleSuspendApp}>
|
||||||
|
Suspend “{charge?.title}”
|
||||||
|
</DialogClose>
|
||||||
|
</div>
|
||||||
</DialogContent>
|
</DialogContent>
|
||||||
</Dialog>
|
</Dialog>
|
||||||
);
|
);
|
||||||
|
@ -38,7 +38,7 @@ export const Tile: FunctionComponent<TileProps> = ({ charge, desk }) => {
|
|||||||
href={active ? link : undefined}
|
href={active ? link : undefined}
|
||||||
target={desk}
|
target={desk}
|
||||||
className={classNames(
|
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'
|
!active && 'cursor-default'
|
||||||
)}
|
)}
|
||||||
style={{ backgroundColor: active ? color || 'purple' : suspendColor }}
|
style={{ backgroundColor: active ? color || 'purple' : suspendColor }}
|
||||||
|
Loading…
Reference in New Issue
Block a user