grid: tweaking focus styles first pass

This commit is contained in:
Hunter Miller 2021-08-30 15:28:43 -05:00
parent 5de3919e8c
commit 2ff5a39bf7
23 changed files with 74387 additions and 27701 deletions

2
.gitignore vendored
View File

@ -78,3 +78,5 @@ pkg/interface/link-webext/web-ext-artifacts
# Logs
*.log
.vercel

File diff suppressed because it is too large Load Diff

View File

@ -35,8 +35,11 @@ export const AppLink = <T extends DocketWithDesk>({
}: AppLinkProps<T>) => {
const linkTo = to?.(app);
const linkClassnames = classNames(
'flex items-center default-ring ring-offset-2 rounded-lg',
selected && 'ring-4',
'flex items-center default-ring rounded-lg',
size === 'default' && 'ring-offset-2',
size !== 'xs' && 'p-2',
size === 'xs' && 'p-1',
selected && 'bg-blue-200',
className
);
const link = (children: ReactNode) =>

View File

@ -11,7 +11,7 @@ type AppListProps<T extends DocketWithDesk> = {
matchAgainst?: MatchItem;
onClick?: (e: MouseEvent<HTMLAnchorElement>, app: T) => void;
listClass?: string;
} & Omit<AppLinkProps<T>, 'app' | 'onClick'>;
} & Omit<AppLinkProps, 'app' | 'onClick'>;
export function appMatches(target: DocketWithDesk, match?: MatchItem): boolean {
if (!match) {
@ -27,7 +27,7 @@ export const AppList = <T extends DocketWithDesk>({
labelledBy,
matchAgainst,
onClick,
listClass = 'space-y-8',
listClass,
size = 'default',
...props
}: AppListProps<T>) => {
@ -37,9 +37,9 @@ export const AppList = <T extends DocketWithDesk>({
return (
<ul
className={classNames(
size === 'default' && 'space-y-8',
size === 'small' && 'space-y-4',
size === 'xs' && 'space-y-2',
size === 'default' && 'space-y-4',
size !== 'xs' && '-mx-2',
size === 'xs' && '-mx-1',
listClass
)}
aria-labelledby={labelledBy}

View File

@ -23,8 +23,9 @@ export const ProviderLink = ({
<Link
to={(to && to(provider)) || `/leap/search/${provider.shipName}/apps`}
className={classNames(
'flex items-center space-x-3 default-ring ring-offset-2 rounded-lg',
selected && 'ring-4',
'flex items-center p-2 space-x-3 default-ring rounded-lg',
!small && 'ring-offset-2',
selected && 'bg-blue-200',
className
)}
{...props}

View File

@ -39,7 +39,7 @@ export const ProviderList = ({
return (
<ul
className={classNames(small ? 'space-y-4' : 'space-y-8', listClass)}
className={classNames('-mx-2', !small && 'space-y-4', listClass)}
aria-labelledby={labelledBy}
>
{providers.map((p) => (

View File

@ -1,14 +1,7 @@
import React, { useEffect } from 'react';
import { useLeapStore } from './Nav';
import React from 'react';
import helpAndSupport from '../assets/help-and-support.svg';
export const Help = () => {
const select = useLeapStore((state) => state.select);
useEffect(() => {
select('Help and Support');
}, []);
return (
<div className="flex flex-col items-center px-4 py-8 md:px-8 md:py-16 space-y-8 md:space-y-16">
<img className="w-52 h-auto" src={helpAndSupport} alt="" />

View File

@ -34,10 +34,10 @@ export function createPreviousPath(current: string): string {
type LeapProps = {
menu: MenuState;
dropdown: string;
showClose: boolean;
navOpen: boolean;
} & HTMLAttributes<HTMLDivElement>;
export const Leap = React.forwardRef(({ menu, dropdown, showClose, className }: LeapProps, ref) => {
export const Leap = React.forwardRef(({ menu, dropdown, navOpen, className }: LeapProps, ref) => {
const { push } = useHistory();
const match = useRouteMatch<{ menu?: MenuState; query?: string; desk?: string }>(
`/leap/${menu}/:query?/(apps)?/:desk?`
@ -196,47 +196,51 @@ export const Leap = React.forwardRef(({ menu, dropdown, showClose, className }:
);
return (
<form
className={classNames(
'relative z-50 flex items-center w-full px-2 rounded-full bg-white default-ring focus-within:ring-4',
className
)}
onSubmit={onSubmit}
>
<label
htmlFor="leap"
<div className="relative z-50 w-full">
<form
className={classNames(
'inline-block flex-none p-2 h4 text-blue-400',
!selection && 'sr-only'
'flex items-center h-full w-full px-2 rounded-full bg-white default-ring focus-within:ring-2',
navOpen && menu !== 'search' && 'opacity-80',
!navOpen ? 'bg-gray-100' : '',
className
)}
onSubmit={onSubmit}
>
{selection || 'Search Landscape'}
</label>
<input
id="leap"
type="text"
ref={inputRef}
placeholder={selection ? '' : 'Search Landscape'}
className="flex-1 w-full h-full px-2 h4 rounded-full bg-transparent outline-none"
value={rawInput}
onClick={toggleSearch}
onFocus={onFocus}
onChange={onChange}
onKeyDown={onKeyDown}
aria-autocomplete="both"
aria-controls={dropdown}
aria-activedescendant={selectedMatch?.display || selectedMatch?.value}
/>
{showClose && (
<label
htmlFor="leap"
className={classNames(
'inline-block flex-none p-2 h4 text-blue-400',
!selection && 'sr-only'
)}
>
{selection || 'Search Landscape'}
</label>
<input
id="leap"
type="text"
ref={inputRef}
placeholder={selection ? '' : 'Search Landscape'}
className="flex-1 w-full px-2 h4 rounded-full bg-transparent outline-none"
value={rawInput}
onClick={toggleSearch}
onFocus={onFocus}
onChange={onChange}
onKeyDown={onKeyDown}
aria-autocomplete="both"
aria-controls={dropdown}
aria-activedescendant={selectedMatch?.display || selectedMatch?.value}
/>
</form>
{navOpen && (
<Link
to="/"
className="circle-button w-8 h-8 text-gray-400 bg-gray-100 default-ring"
className="absolute top-1/2 right-2 flex-none circle-button w-8 h-8 text-gray-400 bg-gray-100 default-ring -translate-y-1/2"
onClick={() => select(null)}
>
<Cross className="w-3 h-3 fill-current" />
<span className="sr-only">Close</span>
</Link>
)}
</form>
</div>
);
});

View File

@ -142,17 +142,12 @@ export const Nav: FunctionComponent<NavProps> = ({ menu }) => {
<SystemMenu
open={systemMenuOpen}
setOpen={setSystemMenuOpen}
showOverlay={!isOpen}
menu={menuState}
navOpen={isOpen}
className={classNames('relative z-50 flex-none', eitherOpen ? 'bg-white' : 'bg-gray-100')}
/>
<NotificationsLink isOpen={isOpen} />
<Leap
ref={inputRef}
menu={menuState}
dropdown="leap-items"
showClose={isOpen}
className={classNames('flex-1 max-w-[600px]', !isOpen ? 'bg-gray-100' : '')}
/>
<NotificationsLink menu={menuState} navOpen={isOpen} />
<Leap ref={inputRef} menu={menuState} dropdown="leap-items" navOpen={isOpen} />
</Portal.Root>
<div
ref={navRef}
@ -169,7 +164,7 @@ export const Nav: FunctionComponent<NavProps> = ({ menu }) => {
<DialogContent
onOpenAutoFocus={onOpen}
onInteractOutside={disableCloseWhenDropdownOpen}
className="fixed bottom-0 sm:top-0 scroll-left-50 flex flex-col scroll-full-width max-w-3xl px-4 pb-4 text-gray-400 -translate-x-1/2 outline-none"
className="fixed bottom-0 sm:top-0 scroll-left-50 flex flex-col scroll-full-width max-w-3xl px-4 sm:pb-4 text-gray-400 -translate-x-1/2 outline-none"
role="combobox"
aria-controls="leap-items"
aria-owns="leap-items"
@ -178,7 +173,7 @@ export const Nav: FunctionComponent<NavProps> = ({ menu }) => {
<header ref={dialogNavRef} className="my-6 order-last sm:order-none" />
<div
id="leap-items"
className="grid grid-rows-[fit-content(100vh)] bg-white rounded-3xl overflow-hidden default-ring"
className="grid grid-rows-[fit-content(100vh)] bg-white rounded-3xl overflow-hidden default-ring focus-visible:ring-2"
tabIndex={0}
role="listbox"
>

View File

@ -1,6 +1,5 @@
import React, { useEffect } from 'react';
import React from 'react';
import { Link } from 'react-router-dom';
import { useLeapStore } from './Nav';
import { Button } from '../components/Button';
import { Notification } from '../state/hark-types';
import { BasicNotification } from './notifications/BasicNotification';
@ -27,12 +26,12 @@ const Empty = () => (
);
export const Notifications = () => {
const select = useLeapStore((s) => s.select);
// const select = useLeapStore((s) => s.select);
const { notifications, systemNotifications, hasAnyNotifications } = useNotifications();
useEffect(() => {
select('Notifications');
}, []);
// useEffect(() => {
// select('Notifications');
// }, []);
return (
<div className="grid grid-rows-[auto,1fr] h-full p-4 md:p-8 overflow-hidden">

View File

@ -4,6 +4,7 @@ import { Link, LinkProps } from 'react-router-dom';
import { Bullet } from '../components/icons/Bullet';
import { Notification } from '../state/hark-types';
import { useNotifications } from '../state/notifications';
import { MenuState } from './Nav';
type NotificationsState = 'empty' | 'unread' | 'attention-needed';
@ -24,10 +25,11 @@ function getNotificationsState(
}
type NotificationsLinkProps = Omit<LinkProps<HTMLAnchorElement>, 'to'> & {
isOpen: boolean;
menu: MenuState;
navOpen: boolean;
};
export const NotificationsLink = ({ isOpen }: NotificationsLinkProps) => {
export const NotificationsLink = ({ navOpen, menu }: NotificationsLinkProps) => {
const { notifications, systemNotifications } = useNotifications();
const state = getNotificationsState(notifications, systemNotifications);
@ -35,10 +37,11 @@ export const NotificationsLink = ({ isOpen }: NotificationsLinkProps) => {
<Link
to="/leap/notifications"
className={classNames(
'relative z-50 flex-none circle-button h4',
isOpen && 'text-opacity-60',
state === 'empty' && !isOpen && 'text-gray-400 bg-gray-100',
state === 'empty' && isOpen && 'text-gray-400 bg-white',
'relative z-50 flex-none circle-button h4 default-ring',
navOpen && 'text-opacity-60',
navOpen && menu !== 'notifications' && 'opacity-80',
state === 'empty' && !navOpen && 'text-gray-400 bg-gray-100',
state === 'empty' && navOpen && 'text-gray-400 bg-white',
state === 'unread' && 'bg-blue-400 text-white',
state === 'attention-needed' && 'text-white bg-orange-500'
)}

View File

@ -4,15 +4,17 @@ import clipboardCopy from 'clipboard-copy';
import React, { HTMLAttributes, useCallback, useState } from 'react';
import { Link } from 'react-router-dom';
import { Adjust } from '../components/icons/Adjust';
import { disableDefault } from '../state/util';
import { disableDefault, handleDropdownLink } from '../state/util';
import { MenuState } from './Nav';
type SystemMenuProps = HTMLAttributes<HTMLButtonElement> & {
open: boolean;
setOpen: (open: boolean) => void;
showOverlay?: boolean;
menu: MenuState;
navOpen: boolean;
};
export const SystemMenu = ({ open, setOpen, className, showOverlay = false }: SystemMenuProps) => {
export const SystemMenu = ({ open, setOpen, className, menu, navOpen }: SystemMenuProps) => {
const [copied, setCopied] = useState(false);
const copyHash = useCallback((event: Event) => {
@ -30,7 +32,12 @@ export const SystemMenu = ({ open, setOpen, className, showOverlay = false }: Sy
<>
<DropdownMenu.Root open={open} onOpenChange={(isOpen) => setOpen(isOpen)}>
<DropdownMenu.Trigger
className={classNames('circle-button default-ring', open && 'text-gray-300', className)}
className={classNames(
'circle-button default-ring',
open && 'text-gray-300',
navOpen && menu !== 'system-preferences' && menu !== 'help-and-support' && 'opacity-80',
className
)}
>
<Adjust className="w-6 h-6 fill-current" />
<span className="sr-only">System Menu</span>
@ -39,17 +46,14 @@ export const SystemMenu = ({ open, setOpen, className, showOverlay = false }: Sy
<DropdownMenu.Content
onCloseAutoFocus={disableDefault}
sideOffset={12}
className="dropdown min-w-64 p-6 font-semibold text-gray-500 bg-white"
className="dropdown min-w-64 p-4 font-semibold text-gray-500 bg-white"
>
<DropdownMenu.Group className="space-y-6">
<DropdownMenu.Group>
<DropdownMenu.Item
as={Link}
to="/leap/system-preferences"
className="flex items-center space-x-2 default-ring ring-offset-2 rounded"
onSelect={(e) => {
e.preventDefault();
setTimeout(() => setOpen(false), 0);
}}
className="flex items-center p-2 mb-2 space-x-2 focus:bg-blue-200 focus:outline-none rounded"
onSelect={handleDropdownLink(setOpen)}
>
<span className="w-5 h-5 bg-gray-100 rounded-full" />
<span className="h4">System Preferences</span>
@ -57,19 +61,15 @@ export const SystemMenu = ({ open, setOpen, className, showOverlay = false }: Sy
<DropdownMenu.Item
as={Link}
to="/leap/help-and-support"
className="flex items-center space-x-2 default-ring ring-offset-2 rounded"
onSelect={(e) => {
e.stopPropagation();
e.preventDefault();
setTimeout(() => setOpen(false), 0);
}}
className="flex items-center p-2 mb-2 space-x-2 focus:bg-blue-200 focus:outline-none rounded"
onSelect={handleDropdownLink(setOpen)}
>
<span className="w-5 h-5 bg-gray-100 rounded-full" />
<span className="h4">Help and Support</span>
</DropdownMenu.Item>
<DropdownMenu.Item
as="button"
className="inline-flex items-center py-2 px-3 h4 text-black bg-gray-100 rounded default-ring"
className="inline-flex items-center py-2 px-3 m-2 h4 text-black bg-gray-100 rounded focus:bg-blue-200 focus:outline-none"
onSelect={copyHash}
>
<span className="sr-only">Base Hash</span>
@ -82,7 +82,7 @@ export const SystemMenu = ({ open, setOpen, className, showOverlay = false }: Sy
</DropdownMenu.Content>
</DropdownMenu.Root>
{showOverlay && open && (
{!navOpen && open && (
<div className="fixed z-30 right-0 bottom-0 w-screen h-screen bg-black opacity-30" />
)}
</>

View File

@ -1,20 +1,14 @@
import React, { useCallback, useEffect } from 'react';
import React, { useCallback } from 'react';
import { Link, Route, RouteComponentProps, Switch, useRouteMatch } from 'react-router-dom';
import classNames from 'classnames';
import { useLeapStore } from './Nav';
import { NotificationPrefs } from './preferences/NotificationPrefs';
import { SystemUpdatePrefs } from './preferences/SystemUpdatePrefs';
import notificationsSVG from '../assets/notifications.svg';
import systemUpdatesSVG from '../assets/system-updates.svg';
export const SystemPreferences = ({ match }: RouteComponentProps<{ submenu: string }>) => {
const select = useLeapStore((state) => state.select);
const subMatch = useRouteMatch<{ submenu: string }>(`${match.url}/:submenu`);
useEffect(() => {
select('System Preferences');
}, []);
const matchSub = useCallback(
(target: string) => {
if (!subMatch && target === 'notifications') {

View File

@ -1,16 +1,10 @@
import React, { useEffect } from 'react';
import React from 'react';
import { Setting } from '../../components/Setting';
import { useLeapStore } from '../Nav';
import { usePreferencesStore } from './usePreferencesStore';
export const NotificationPrefs = () => {
const select = useLeapStore((s) => s.select);
const { doNotDisturb, mentions, toggleDoNotDisturb, toggleMentions } = usePreferencesStore();
useEffect(() => {
select('System Preferences: Notifications');
}, []);
return (
<>
<h2 className="h3 mb-7">Notifications</h2>

View File

@ -4,20 +4,14 @@ import { Setting } from '../../components/Setting';
import { ShipName } from '../../components/ShipName';
import { Spinner } from '../../components/Spinner';
import { useAsyncCall } from '../../logic/useAsyncCall';
import { useLeapStore } from '../Nav';
import { usePreferencesStore } from './usePreferencesStore';
export const SystemUpdatePrefs = () => {
const select = useLeapStore((s) => s.select);
const { otasEnabled, otaSource, toggleOTAs, setOTASource } = usePreferencesStore();
const [source, setSource] = useState(otaSource);
const sourceDirty = source !== otaSource;
const { status: sourceStatus, call: setOTA } = useAsyncCall(setOTASource);
useEffect(() => {
select('System Preferences: Updates');
}, []);
useEffect(() => {
setSource(otaSource);
}, [otaSource]);

View File

@ -91,7 +91,7 @@ export const Home = () => {
return (
<div className="h-full p-4 md:p-8 font-semibold leading-tight text-black overflow-y-auto">
<h2 id="recent-apps" className="mb-6 h4 text-gray-500">
<h2 id="recent-apps" className="mb-4 h4 text-gray-500">
Recent Apps
</h2>
{recentApps.length === 0 && (
@ -116,7 +116,7 @@ export const Home = () => {
/>
)}
<hr className="-mx-4 my-6 md:-mx-8 md:my-9" />
<h2 id="recent-devs" className="mb-6 h4 text-gray-500">
<h2 id="recent-devs" className="mb-4 h4 text-gray-500">
Recent Developers
</h2>
{recentDevs.length === 0 && (

View File

@ -1,11 +1,5 @@
import { DocketHref } from '@urbit/api/docket';
export function makeKeyFn(key: string) {
return (childKeys: string[] = []) => {
return [key].concat(childKeys);
};
}
export const useMockData = import.meta.env.MODE === 'mock';
export async function fakeRequest<T>(data: T, time = 300): Promise<T> {
@ -24,6 +18,11 @@ export function disableDefault<T extends Event>(e: T): void {
e.preventDefault();
}
export function disableDefault<T extends Event>(e: T): void {
e.preventDefault();
// hack until radix-ui fixes this behavior
export function handleDropdownLink(setOpen: (open: boolean) => void): (e: Event) => void {
return (e: Event) => {
e.stopPropagation();
e.preventDefault();
setTimeout(() => setOpen(false), 15);
};
}

View File

@ -15,5 +15,5 @@
}
.default-ring {
@apply focus:ring-4 ring-blue-400 ring-opacity-80 focus:outline-none;
}
@apply focus-visible:ring-2 ring-blue-400 ring-opacity-80 focus-visible:outline-none;
}

View File

@ -41,7 +41,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-3xl default-ring overflow-hidden',
'group relative font-semibold aspect-w-1 aspect-h-1 rounded-3xl default-ring focus-visible:ring-4 overflow-hidden',
!active && 'cursor-default'
)}
style={{ backgroundColor: active ? color || 'purple' : suspendColor }}

View File

@ -4,7 +4,7 @@ import * as DropdownMenu from '@radix-ui/react-dropdown-menu';
import classNames from 'classnames';
import { Link } from 'react-router-dom';
import useDocketState from '../state/docket';
import { disableDefault } from '../state/util';
import { disableDefault, handleDropdownLink } from '../state/util';
export interface TileMenuProps {
desk: string;
@ -41,10 +41,7 @@ export const TileMenu = ({ desk, active, menuColor, lightText, className }: Tile
const [open, setOpen] = useState(false);
const toggleDocket = useDocketState((s) => s.toggleDocket);
const menuBg = { backgroundColor: menuColor };
const linkOnSelect = useCallback((e: Event) => {
e.preventDefault();
setTimeout(() => setOpen(false), 15);
}, []);
const linkOnSelect = useCallback(handleDropdownLink(setOpen), []);
return (
<DropdownMenu.Root open={open} onOpenChange={(isOpen) => setOpen(isOpen)}>

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff