diff --git a/src/app/components/nav/NavGutter.tsx b/src/app/components/nav/NavGutter.tsx new file mode 100644 index 00000000..c0a6e50e --- /dev/null +++ b/src/app/components/nav/NavGutter.tsx @@ -0,0 +1,12 @@ +import React, { ReactNode } from 'react'; +import * as css from './styles.css'; + +/** The fixed-width first column of a navigator row or section header; its content is centred in it. */ +export function NavGutter({ children }: { children: ReactNode }) { + return {children}; +} + +/** One nesting step: everything inside is indented by the same constant. */ +export function NavLevel({ children }: { children: ReactNode }) { + return
{children}
; +} diff --git a/src/app/components/nav/index.ts b/src/app/components/nav/index.ts index 12267446..2ed29601 100644 --- a/src/app/components/nav/index.ts +++ b/src/app/components/nav/index.ts @@ -4,3 +4,4 @@ export * from './NavEmptyLayout'; export * from './NavItem'; export * from './NavItemContent'; export * from './NavItemOptions'; +export * from './NavGutter'; diff --git a/src/app/components/nav/styles.css.ts b/src/app/components/nav/styles.css.ts index 62e3928e..34dc81c0 100644 --- a/src/app/components/nav/styles.css.ts +++ b/src/app/components/nav/styles.css.ts @@ -13,6 +13,29 @@ const SCRIM_HOVER = '14%'; const SCRIM_SELECTED = '22%'; const SCRIM_PRESSED = '48%'; +// The navigator's one column grid. Every row, section header and join row begins with a gutter of +// the same width, with its chevron, room icon or avatar centred in it, so every label starts at +// the same x for its depth; each nesting level (NavLevel) indents by one constant step. +const GUTTER = toRem(32); +const LEVEL_STEP = toRem(16); + +export const NavGutter = style({ + width: GUTTER, + minWidth: GUTTER, + flexShrink: 0, + display: 'flex', + alignItems: 'center', + justifyContent: 'center', +}); + +export const NavLevel = style({ + paddingLeft: LEVEL_STEP, +}); + +// Chevrons and room icons share one resting weight, so a foldout is not outweighed by its rooms. +export const NAV_ICON_REST_OPACITY = 0.6; +export const NAV_ICON_UNREAD_OPACITY = 0.9; + export const NavCategory = style([ DefaultReset, { @@ -22,6 +45,11 @@ export const NavCategory = style([ export const NavCategoryHeader = style({ gap: config.space.S100, + selectors: { + // The header's own side padding would shift its gutter off the rows': the section button + // carries the same left inset as NavItemContent instead. + '&&': { paddingLeft: 0 }, + }, '@media': { // Category labels sit on the scene like the rows do, and need a wash to be read (wide screens; // a phone's list is plain text). diff --git a/src/app/features/estate/EstateNavigator.tsx b/src/app/features/estate/EstateNavigator.tsx index 27fcfbad..8066498d 100644 --- a/src/app/features/estate/EstateNavigator.tsx +++ b/src/app/features/estate/EstateNavigator.tsx @@ -1,16 +1,18 @@ import React, { MouseEventHandler, ReactNode, useCallback, useMemo, useRef } from 'react'; import { useNavigate } from 'react-router-dom'; import { useAtom, useAtomValue, useSetAtom } from 'jotai'; -import { Avatar, Box, Chip, Icon, IconButton, Icons, Spinner, Text, color, config } from 'folds'; +import { Avatar, Box, Chip, Icon, IconButton, Icons, Spinner, Text, color } from 'folds'; import { PageNav, PageNavContent, PageNavHeader } from '../../components/page'; import { NavButton, NavCategory, NavCategoryHeader, + NavGutter, NavItem, NavItemContent, + NavLevel, } from '../../components/nav'; -import { NavAction, NavLabel } from '../../components/nav/styles.css'; +import { NAV_ICON_REST_OPACITY, NavAction, NavLabel } from '../../components/nav/styles.css'; import { UseStateProvider } from '../../components/UseStateProvider'; import { JoinAddressPrompt } from '../../components/join-address-prompt'; import { RoomNavCategoryButton, RoomNavItem } from '../room-nav'; @@ -77,12 +79,14 @@ function JoinRow({ room }: { room: EstateRoom }) { return ( - + - - - + + + + + {room.name} @@ -166,7 +170,7 @@ function Collapsible({ categoryId, label, rooms, trailing, children }: Collapsib {trailing} - {!closed && children} + {!closed && {children}} ); } @@ -206,7 +210,13 @@ function Browse({ tierId, groups }: { tierId: string; groups: EstateGroup[] }) { {`Browse ${count} more ${count === 1 ? 'room' : 'rooms'}`} - {open && groups.map((g) => )} + {open && ( + + {groups.map((g) => ( + + ))} + + )} ); } @@ -235,9 +245,11 @@ function EntryPoints() { navigate(getHomeCreatePath())}> - - - + + + + + Create Room @@ -252,9 +264,15 @@ function EntryPoints() { setOpen(true)}> - - - + + + + + Join with Address diff --git a/src/app/features/room-nav/RoomNavCategoryButton.tsx b/src/app/features/room-nav/RoomNavCategoryButton.tsx index e232b856..74945be3 100644 --- a/src/app/features/room-nav/RoomNavCategoryButton.tsx +++ b/src/app/features/room-nav/RoomNavCategoryButton.tsx @@ -1,6 +1,7 @@ import React from 'react'; import { as, Chip, Icon, Icons, Text } from 'folds'; import classNames from 'classnames'; +import { NavGutter } from '../../components/nav'; import * as css from './styles.css'; export const RoomNavCategoryButton = as<'button', { closed?: boolean }>( @@ -9,17 +10,17 @@ export const RoomNavCategoryButton = as<'button', { closed?: boolean }>( className={classNames(css.CategoryButton, className)} variant="Background" radii="Pill" - before={ + aria-expanded={closed === undefined ? undefined : !closed} + {...props} + ref={ref} + > + - } - aria-expanded={closed === undefined ? undefined : !closed} - {...props} - ref={ref} - > + {children} diff --git a/src/app/features/room-nav/RoomNavItem.tsx b/src/app/features/room-nav/RoomNavItem.tsx index ff23930e..48531a19 100644 --- a/src/app/features/room-nav/RoomNavItem.tsx +++ b/src/app/features/room-nav/RoomNavItem.tsx @@ -20,8 +20,12 @@ import { import { useFocusWithin, useHover } from 'react-aria'; import FocusTrap from 'focus-trap-react'; import { useAtom, useAtomValue } from 'jotai'; -import { NavItem, NavItemContent, NavItemOptions, NavLink } from '../../components/nav'; -import { NavLabel } from '../../components/nav/styles.css'; +import { NavGutter, NavItem, NavItemContent, NavItemOptions, NavLink } from '../../components/nav'; +import { + NAV_ICON_REST_OPACITY, + NAV_ICON_UNREAD_OPACITY, + NavLabel, +} from '../../components/nav/styles.css'; import { UnreadBadge, UnreadBadgeCenter } from '../../components/unread-badge'; import { RoomAvatar, RoomIcon } from '../../components/room-avatar'; import { getDirectRoomAvatarUrl, getRoomAvatarUrl, getStateEvent } from '../../utils/room'; @@ -330,34 +334,36 @@ export function RoomNavItem({ - - {showAvatar ? ( - ( - - {nameInitials(roomName)} - - )} - /> - ) : ( - - )} - + + + {showAvatar ? ( + ( + + {nameInitials(roomName)} + + )} + /> + ) : ( + + )} + +