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)}
+
+ )}
+ />
+ ) : (
+
+ )}
+
+