From 7eb5767f3545f6841952d1f7ff0fd96b4a4a96fb Mon Sep 17 00:00:00 2001 From: Shea Duma Date: Sun, 3 May 2026 23:14:16 +0300 Subject: [PATCH 01/10] initial modifiable version --- src/app/components/page/style.css.ts | 3 - src/app/pages/client/direct/Direct.tsx | 155 +++++----- src/app/pages/client/explore/Explore.tsx | 178 ++++++----- src/app/pages/client/home/Home.tsx | 254 ++++++++-------- src/app/pages/client/inbox/Inbox.tsx | 83 +++--- .../client/sidebar/SidebarResizer.css.ts | 20 ++ .../pages/client/sidebar/SidebarResizer.tsx | 49 +++ src/app/pages/client/space/Space.tsx | 280 +++++++++--------- src/app/state/settings.ts | 2 + 9 files changed, 571 insertions(+), 453 deletions(-) create mode 100644 src/app/pages/client/sidebar/SidebarResizer.css.ts create mode 100644 src/app/pages/client/sidebar/SidebarResizer.tsx diff --git a/src/app/components/page/style.css.ts b/src/app/components/page/style.css.ts index d35fa651d..6d9343c7e 100644 --- a/src/app/components/page/style.css.ts +++ b/src/app/components/page/style.css.ts @@ -14,9 +14,6 @@ export const PageNav = recipe({ }, }, }, - defaultVariants: { - size: '400', - }, }); export type PageNavVariants = RecipeVariants; diff --git a/src/app/pages/client/direct/Direct.tsx b/src/app/pages/client/direct/Direct.tsx index e84e04daa..6a3f7c3c5 100644 --- a/src/app/pages/client/direct/Direct.tsx +++ b/src/app/pages/client/direct/Direct.tsx @@ -53,6 +53,7 @@ import { } from '$hooks/useRoomsNotificationPreferences'; import { useDirectCreateSelected } from '$hooks/router/useDirectSelected'; import { useDirectRooms } from './useDirectRooms'; +import { SidebarResizer } from '$pages/client/sidebar/SidebarResizer'; type DirectMenuProps = { requestClose: () => void; @@ -179,6 +180,7 @@ export function Direct() { const roomToUnread = useAtomValue(roomToUnreadAtom); const navigate = useNavigate(); const [customDMCards] = useSetting(settingsAtom, 'customDMCards'); + const [roomSidebarWidth] = useSetting(settingsAtom, 'roomSidebarWidth'); const createDirectSelected = useDirectCreateSelected(); @@ -238,80 +240,85 @@ export function Direct() { ); return ( - - - {noRoomToDisplay ? ( - - ) : ( - - - - - navigate(getDirectCreatePath())}> - - - - - - - - Create Chat - - - - - - - - - - - Chats - - -
- {virtualizer.getVirtualItems().map((vItem) => { - const roomId = sortedDirects[vItem.index]; - if (!roomId) return null; - const room = mx.getRoom(roomId); - if (!room) return null; - const selected = selectedRoomId === roomId; - - return ( - +
+ + + {noRoomToDisplay ? ( + + ) : ( + + + + + navigate(getDirectCreatePath())}> + + + + + + + + Create Chat + + + + + + + + + + - - - ); - })} -
- - - - )} - + Chats + + +
+ {virtualizer.getVirtualItems().map((vItem) => { + const roomId = sortedDirects[vItem.index]; + if (!roomId) return null; + const room = mx.getRoom(roomId); + if (!room) return null; + const selected = selectedRoomId === roomId; + + return ( + + + + ); + })} +
+ + + + )} + +
+ + ); } diff --git a/src/app/pages/client/explore/Explore.tsx b/src/app/pages/client/explore/Explore.tsx index cf4f69297..aa843575a 100644 --- a/src/app/pages/client/explore/Explore.tsx +++ b/src/app/pages/client/explore/Explore.tsx @@ -18,6 +18,7 @@ import { Text, color, config, + toRem, } from 'folds'; import { NavCategory, NavCategoryHeader, NavItem, NavItemContent, NavLink } from '$components/nav'; import { getExploreFeaturedPath, getExploreServerPath } from '$pages/pathUtils'; @@ -29,6 +30,9 @@ import { AsyncStatus, useAsyncCallback } from '$hooks/useAsyncCallback'; import { useNavToActivePathMapper } from '$hooks/useNavToActivePathMapper'; import { PageNav, PageNavContent, PageNavHeader } from '$components/page'; import { stopPropagation } from '$utils/keyboard'; +import { SidebarResizer } from '$pages/client/sidebar/SidebarResizer'; +import { settingsAtom } from '$state/settings'; +import { useSetting } from '$state/hooks/settings'; export function AddServer() { const mx = useMatrixClient(); @@ -159,101 +163,111 @@ export function Explore() { const featuredSelected = useExploreFeaturedSelected(); const selectedServer = useExploreServer(); + const [roomSidebarWidth] = useSetting(settingsAtom, 'roomSidebarWidth'); return ( - - - - - - Explore Community - - - - - - - - - - - - - - - - - - Featured - - - - - - - {userServer && ( - - - - - - - - - - {userServer} - - - - - - - )} - - {servers.length > 0 && ( - - - - Servers + <> +
+ + + + + + Explore Community - - {servers.map((server) => ( - - + + + + + + + + + - + - {server} + Featured - ))} - - )} - - - - - - + {userServer && ( + + + + + + + + + + {userServer} + + + + + + + )} + + {servers.length > 0 && ( + + + + Servers + + + {servers.map((server) => ( + + + + + + + + + + {server} + + + + + + + ))} + + )} + + + + + + +
+ + ); } diff --git a/src/app/pages/client/home/Home.tsx b/src/app/pages/client/home/Home.tsx index afd4de936..e30af0320 100644 --- a/src/app/pages/client/home/Home.tsx +++ b/src/app/pages/client/home/Home.tsx @@ -62,6 +62,7 @@ import { import { UseStateProvider } from '$components/UseStateProvider'; import { JoinAddressPrompt } from '$components/join-address-prompt'; import { useHomeRooms } from './useHomeRooms'; +import { SidebarResizer } from '$pages/client/sidebar/SidebarResizer'; type HomeMenuProps = { requestClose: () => void; @@ -200,6 +201,8 @@ export function Home() { const roomToUnread = useAtomValue(roomToUnreadAtom); const navigate = useNavigate(); + const [roomSidebarWidth] = useSetting(settingsAtom, 'roomSidebarWidth'); + const selectedRoomId = useSelectedRoom(); const createRoomSelected = useHomeCreateSelected(); const searchSelected = useHomeSearchSelected(); @@ -234,131 +237,136 @@ export function Home() { ); return ( - - - {noRoomToDisplay ? ( - - ) : ( - - - - - navigate(getHomeCreatePath())}> - - - - - - - - Create Room - - - - - - - - {(open, setOpen) => ( - <> - - setOpen(true)}> - - - - - - - - Join with Address - - + <> +
+ + + {noRoomToDisplay ? ( + + ) : ( + + + + + navigate(getHomeCreatePath())}> + + + + + + + + Create Room + - - - - {open && ( - setOpen(false)} - onOpen={(roomIdOrAlias, viaServers, eventId) => { - setOpen(false); - const path = getHomeRoomPath(roomIdOrAlias, eventId); - navigate( - viaServers - ? withSearchParam(path, { - viaServers: encodeSearchParamValueArray(viaServers), - }) - : path - ); - }} - /> + + + + + + {(open, setOpen) => ( + <> + + setOpen(true)}> + + + + + + + + Join with Address + + + + + + + {open && ( + setOpen(false)} + onOpen={(roomIdOrAlias, viaServers, eventId) => { + setOpen(false); + const path = getHomeRoomPath(roomIdOrAlias, eventId); + navigate( + viaServers + ? withSearchParam(path, { + viaServers: encodeSearchParamValueArray(viaServers), + }) + : path + ); + }} + /> + )} + )} - - )} - - - - - - - - - - - Message Search - - - - - - - - - - - Rooms - - -
- {virtualizer.getVirtualItems().map((vItem) => { - const roomId = sortedRooms[vItem.index]; - if (!roomId) return null; - const room = mx.getRoom(roomId); - if (!room) return null; - const selected = selectedRoomId === roomId; - - return ( - + + + + + + + + + + Message Search + + + + + + + + + + - - - ); - })} -
-
- -
- )} -
+ Rooms + + +
+ {virtualizer.getVirtualItems().map((vItem) => { + const roomId = sortedRooms[vItem.index]; + if (!roomId) return null; + const room = mx.getRoom(roomId); + if (!room) return null; + const selected = selectedRoomId === roomId; + + return ( + + + + ); + })} +
+ + + + )} + +
+ + ); } diff --git a/src/app/pages/client/inbox/Inbox.tsx b/src/app/pages/client/inbox/Inbox.tsx index 661435513..5d982a39a 100644 --- a/src/app/pages/client/inbox/Inbox.tsx +++ b/src/app/pages/client/inbox/Inbox.tsx @@ -1,4 +1,4 @@ -import { Avatar, Box, Icon, Icons, Text } from 'folds'; +import { Avatar, Box, Icon, Icons, Text, toRem } from 'folds'; import { useAtomValue } from 'jotai'; import { NavCategory, NavItem, NavItemContent, NavLink } from '$components/nav'; import { getInboxInvitesPath, getInboxNotificationsPath } from '$pages/pathUtils'; @@ -7,6 +7,9 @@ import { UnreadBadge } from '$components/unread-badge'; import { allInvitesAtom } from '$state/room-list/inviteList'; import { useNavToActivePathMapper } from '$hooks/useNavToActivePathMapper'; import { PageNav, PageNavContent, PageNavHeader } from '$components/page'; +import { SidebarResizer } from '$pages/client/sidebar/SidebarResizer'; +import { useSetting } from '$state/hooks/settings'; +import { settingsAtom } from '$state/settings'; function InvitesNavItem() { const invitesSelected = useInboxInvitesSelected(); @@ -43,41 +46,51 @@ export function Inbox() { useNavToActivePathMapper('inbox'); const notificationsSelected = useInboxNotificationsSelected(); + const [roomSidebarWidth] = useSetting(settingsAtom, 'roomSidebarWidth'); return ( - - - - - - Inbox - - - - + <> +
+ + + + + + Inbox + + + + - - - - - - - - - - - - - Notifications - - - - - - - - - - - + + + + + + + + + + + + + Notifications + + + + + + + + + + + +
+ + ); } diff --git a/src/app/pages/client/sidebar/SidebarResizer.css.ts b/src/app/pages/client/sidebar/SidebarResizer.css.ts new file mode 100644 index 000000000..fe0a79eda --- /dev/null +++ b/src/app/pages/client/sidebar/SidebarResizer.css.ts @@ -0,0 +1,20 @@ +import { style } from '@vanilla-extract/css'; +import { color } from 'folds'; + +export const SidebarResizer = style({ + width: '4px', + backgroundColor: 'inherit', + transition: '0.2s', + selectors: { + '&:hover': { + height: '100%', + zIndex: '100', + boxShadow: `0px 0px 32px 8px ${color.Primary.Container}`, + }, + }, +}); +export const SideBarResizerAnimation = style({ + width: '100%', + backgroundColor: color.Primary.Container, + transition: '0.2s', +}); diff --git a/src/app/pages/client/sidebar/SidebarResizer.tsx b/src/app/pages/client/sidebar/SidebarResizer.tsx new file mode 100644 index 000000000..23b26e7e5 --- /dev/null +++ b/src/app/pages/client/sidebar/SidebarResizer.tsx @@ -0,0 +1,49 @@ +import { Box } from 'folds'; +import * as css from '$pages/client/sidebar/SidebarResizer.css'; +import type { SetStateAction } from 'react'; +import React, { useCallback, useEffect, useState } from 'react'; +import { settingsAtom } from '$state/settings'; +import { useSetting } from '$state/hooks/settings'; + +export function SidebarResizer() { + const [roomSidebarWidth, setRoomSidebarWidth] = useSetting(settingsAtom, 'roomSidebarWidth'); + + const [isPointerOver, setIsPointerOver] = useState(false); + const [oldX, setOldX] = useState(0); + const [newX, setNewX] = useState(0); + + useEffect(() => { + const change = oldX - newX; + if (change) setRoomSidebarWidth(Math.max(roomSidebarWidth - change, 0)); + // The disable is because the position should only update whenever the new one is updated + // oxlint-disable-next-line eslint-plugin-react-hooks/exhaustive-deps + }, [newX]); + const onMouseUp = useCallback((e: { clientX: SetStateAction }) => { + setNewX(e.clientX); + window.removeEventListener('pointerup', onMouseUp); + }, []); + + const onMouseDown = useCallback( + (e: React.PointerEvent) => { + e.preventDefault(); + setOldX(e.clientX); + window.addEventListener('pointerup', onMouseUp); + }, + [onMouseUp] + ); + + return ( + setIsPointerOver(true)} + onPointerLeave={() => setIsPointerOver(false)} + onPointerDown={onMouseDown} + onPointerUp={onMouseUp} + > + + + ); +} diff --git a/src/app/pages/client/space/Space.tsx b/src/app/pages/client/space/Space.tsx index c4994d829..ba391fd0c 100644 --- a/src/app/pages/client/space/Space.tsx +++ b/src/app/pages/client/space/Space.tsx @@ -80,6 +80,7 @@ import { lastVisitedRoomIdAtom } from '$state/room/lastRoom'; import { SwipeableOverlayWrapper } from '$components/SwipeableOverlayWrapper'; import { useCallEmbed } from '$hooks/useCallEmbed'; import { createDebugLogger } from '$utils/debugLogger'; +import { SidebarResizer } from '$pages/client/sidebar/SidebarResizer'; const debugLog = createDebugLogger('Space'); @@ -389,6 +390,8 @@ export function Space() { const allJoinedRooms = useMemo(() => new Set(allRooms), [allRooms]); const notificationPreferences = useRoomsNotificationPreferencesContext(); + const [roomSidebarWidth] = useSetting(settingsAtom, 'roomSidebarWidth'); + const tombstoneEvent = useStateEvent(space, EventType.RoomTombstone); const selectedRoomId = useSelectedRoom(); const lobbySelected = useSpaceLobbySelected(spaceIdOrAlias); @@ -711,142 +714,147 @@ export function Space() { }, [lastRoomId, spaceIdOrAlias, mx, navigate]); return ( - - - - - - {tombstoneEvent && ( - - )} - - - - - - - - - - - Lobby - - - - - - - - - - - - - - - - Message Search - - - - - - - - - {virtualizedItems.map((vItem) => { - const hierarchyItem = hierarchy[vItem.index]; - if (!hierarchyItem) return null; - const { roomId, depth: itemDepth } = hierarchyItem; - const depth = itemDepth ?? 0; - const room = mx.getRoom(roomId); - const renderDepth = room?.isSpaceRoom() ? depth - 2 : depth - 1; - if (!room) return null; - if (depth === subspaceHierarchyLimit && room.isSpaceRoom()) { - return ( - -
- -
-
- ); - } - - const paddingTop = getCategoryPadding(depth); - const paddingLeft = `calc(${renderDepth} * ${config.space.S400})`; - - if (room.isSpaceRoom()) { - const categoryId = makeNavCategoryId(space.roomId, roomId); - const closedViaCategory = getInClosedCategories(space.roomId, roomId); - - return ( - -
- - +
+ + + + + + {tombstoneEvent && ( + + )} + + + + + + + + + + + Lobby + + + + + + + + + + + + + + + + Message Search + + + + + + + + + {virtualizedItems.map((vItem) => { + const hierarchyItem = hierarchy[vItem.index]; + if (!hierarchyItem) return null; + const { roomId, depth: itemDepth } = hierarchyItem; + const depth = itemDepth ?? 0; + const room = mx.getRoom(roomId); + const renderDepth = room?.isSpaceRoom() ? depth - 2 : depth - 1; + if (!room) return null; + if (depth === subspaceHierarchyLimit && room.isSpaceRoom()) { + return ( + +
- {roomId === space.roomId ? 'Rooms' : room?.name} - - -
-
- ); - } - - return ( - -
- -
-
- ); - })} - {getConnectorSVG(hierarchy, virtualizedItems)} -
-
-
-
-
+ +
+ + ); + } + + const paddingTop = getCategoryPadding(depth); + const paddingLeft = `calc(${renderDepth} * ${config.space.S400})`; + + if (room.isSpaceRoom()) { + const categoryId = makeNavCategoryId(space.roomId, roomId); + const closedViaCategory = getInClosedCategories(space.roomId, roomId); + + return ( + +
+ + + {roomId === space.roomId ? 'Rooms' : room?.name} + + +
+
+ ); + } + + return ( + +
+ +
+
+ ); + })} + {getConnectorSVG(hierarchy, virtualizedItems)} + + + + + +
+ + ); } diff --git a/src/app/state/settings.ts b/src/app/state/settings.ts index 83532c673..369816115 100644 --- a/src/app/state/settings.ts +++ b/src/app/state/settings.ts @@ -118,6 +118,7 @@ export interface Settings { mentionInReplies: boolean; showPersonaSetting: boolean; closeFoldersByDefault: boolean; + roomSidebarWidth: number; // furry stuff renderAnimals: boolean; @@ -219,6 +220,7 @@ const defaultSettings: Settings = { mentionInReplies: true, showPersonaSetting: false, closeFoldersByDefault: false, + roomSidebarWidth: 400, // furry stuff renderAnimals: true, From 4c93f0bd3e10d8fe7268512bd54f50d3cfc113c8 Mon Sep 17 00:00:00 2001 From: Shea Duma Date: Mon, 4 May 2026 07:59:26 +0300 Subject: [PATCH 02/10] continuous moves --- src/app/components/nav/styles.css.ts | 1 + src/app/pages/client/direct/Direct.tsx | 5 +- src/app/pages/client/explore/Explore.tsx | 5 +- src/app/pages/client/home/Home.tsx | 5 +- src/app/pages/client/inbox/Inbox.tsx | 6 ++- .../client/sidebar/SidebarResizer.css.ts | 13 +++-- .../pages/client/sidebar/SidebarResizer.tsx | 48 +++++++++++++------ src/app/pages/client/space/Space.tsx | 7 ++- 8 files changed, 58 insertions(+), 32 deletions(-) diff --git a/src/app/components/nav/styles.css.ts b/src/app/components/nav/styles.css.ts index f72d3fe38..5c9b70335 100644 --- a/src/app/components/nav/styles.css.ts +++ b/src/app/components/nav/styles.css.ts @@ -9,6 +9,7 @@ export const NavCategory = style([ DefaultReset, { position: 'relative', + overflow: 'scroll', }, ]); diff --git a/src/app/pages/client/direct/Direct.tsx b/src/app/pages/client/direct/Direct.tsx index 6a3f7c3c5..809e50c33 100644 --- a/src/app/pages/client/direct/Direct.tsx +++ b/src/app/pages/client/direct/Direct.tsx @@ -181,6 +181,7 @@ export function Direct() { const navigate = useNavigate(); const [customDMCards] = useSetting(settingsAtom, 'customDMCards'); const [roomSidebarWidth] = useSetting(settingsAtom, 'roomSidebarWidth'); + const [curWidth, setCurWidth] = useState(roomSidebarWidth); const createDirectSelected = useDirectCreateSelected(); @@ -241,7 +242,7 @@ export function Direct() { return ( <> -
+
{noRoomToDisplay ? ( @@ -318,7 +319,7 @@ export function Direct() { )}
- + ); } diff --git a/src/app/pages/client/explore/Explore.tsx b/src/app/pages/client/explore/Explore.tsx index aa843575a..bf4cd4d96 100644 --- a/src/app/pages/client/explore/Explore.tsx +++ b/src/app/pages/client/explore/Explore.tsx @@ -164,9 +164,10 @@ export function Explore() { const selectedServer = useExploreServer(); const [roomSidebarWidth] = useSetting(settingsAtom, 'roomSidebarWidth'); + const [curWidth, setCurWidth] = useState(roomSidebarWidth); return ( <> -
+
@@ -267,7 +268,7 @@ export function Explore() {
- + ); } diff --git a/src/app/pages/client/home/Home.tsx b/src/app/pages/client/home/Home.tsx index e30af0320..8dbab40c1 100644 --- a/src/app/pages/client/home/Home.tsx +++ b/src/app/pages/client/home/Home.tsx @@ -202,6 +202,7 @@ export function Home() { const navigate = useNavigate(); const [roomSidebarWidth] = useSetting(settingsAtom, 'roomSidebarWidth'); + const [curWidth, setCurWidth] = useState(roomSidebarWidth); const selectedRoomId = useSelectedRoom(); const createRoomSelected = useHomeCreateSelected(); @@ -238,7 +239,7 @@ export function Home() { return ( <> -
+
{noRoomToDisplay ? ( @@ -366,7 +367,7 @@ export function Home() { )}
- + ); } diff --git a/src/app/pages/client/inbox/Inbox.tsx b/src/app/pages/client/inbox/Inbox.tsx index 5d982a39a..eb91682ac 100644 --- a/src/app/pages/client/inbox/Inbox.tsx +++ b/src/app/pages/client/inbox/Inbox.tsx @@ -10,6 +10,7 @@ import { PageNav, PageNavContent, PageNavHeader } from '$components/page'; import { SidebarResizer } from '$pages/client/sidebar/SidebarResizer'; import { useSetting } from '$state/hooks/settings'; import { settingsAtom } from '$state/settings'; +import { useState } from 'react'; function InvitesNavItem() { const invitesSelected = useInboxInvitesSelected(); @@ -47,9 +48,10 @@ export function Inbox() { const notificationsSelected = useInboxNotificationsSelected(); const [roomSidebarWidth] = useSetting(settingsAtom, 'roomSidebarWidth'); + const [curWidth, setCurWidth] = useState(roomSidebarWidth); return ( <> -
+
@@ -90,7 +92,7 @@ export function Inbox() {
- + ); } diff --git a/src/app/pages/client/sidebar/SidebarResizer.css.ts b/src/app/pages/client/sidebar/SidebarResizer.css.ts index fe0a79eda..a2ac8d150 100644 --- a/src/app/pages/client/sidebar/SidebarResizer.css.ts +++ b/src/app/pages/client/sidebar/SidebarResizer.css.ts @@ -5,13 +5,12 @@ export const SidebarResizer = style({ width: '4px', backgroundColor: 'inherit', transition: '0.2s', - selectors: { - '&:hover': { - height: '100%', - zIndex: '100', - boxShadow: `0px 0px 32px 8px ${color.Primary.Container}`, - }, - }, + ':hover': {}, +}); +export const SidebarResizerHover = style({ + height: '100%', + zIndex: '100', + boxShadow: `0px 0px 32px 8px ${color.Primary.Container}`, }); export const SideBarResizerAnimation = style({ width: '100%', diff --git a/src/app/pages/client/sidebar/SidebarResizer.tsx b/src/app/pages/client/sidebar/SidebarResizer.tsx index 23b26e7e5..d1ecdb9c8 100644 --- a/src/app/pages/client/sidebar/SidebarResizer.tsx +++ b/src/app/pages/client/sidebar/SidebarResizer.tsx @@ -1,49 +1,67 @@ +// The disable is because the position should only update whenever the new one is updated +// oxlint-disable eslint-plugin-react-hooks/exhaustive-deps import { Box } from 'folds'; import * as css from '$pages/client/sidebar/SidebarResizer.css'; -import type { SetStateAction } from 'react'; +import type { Dispatch, SetStateAction } from 'react'; import React, { useCallback, useEffect, useState } from 'react'; import { settingsAtom } from '$state/settings'; import { useSetting } from '$state/hooks/settings'; -export function SidebarResizer() { +export function SidebarResizer( + { setCurWidth }: { setCurWidth?: Dispatch> } +) { const [roomSidebarWidth, setRoomSidebarWidth] = useSetting(settingsAtom, 'roomSidebarWidth'); const [isPointerOver, setIsPointerOver] = useState(false); + const [isPointerDown, setIsPointerDown] = useState(false); const [oldX, setOldX] = useState(0); + const [interimX, setInterimX] = useState(0); const [newX, setNewX] = useState(0); useEffect(() => { const change = oldX - newX; - if (change) setRoomSidebarWidth(Math.max(roomSidebarWidth - change, 0)); - // The disable is because the position should only update whenever the new one is updated - // oxlint-disable-next-line eslint-plugin-react-hooks/exhaustive-deps + if (change) setRoomSidebarWidth(Math.min(Math.max(roomSidebarWidth - change, 0), 1200)); }, [newX]); - const onMouseUp = useCallback((e: { clientX: SetStateAction }) => { + + useEffect(() => { + const change = oldX - interimX; + if (change && setCurWidth) setCurWidth(Math.min(Math.max(roomSidebarWidth - change, 0), 1200)); + }, [interimX]); + + const onPointerMove = useCallback((e: PointerEvent) => { + e.preventDefault(); + setInterimX(e.clientX); + }, []); + const onPointerUp = useCallback((e: PointerEvent) => { + e.preventDefault(); setNewX(e.clientX); - window.removeEventListener('pointerup', onMouseUp); + setIsPointerDown(false); + window.removeEventListener('pointerup', onPointerUp); + window.removeEventListener('pointermove', onPointerMove); }, []); - const onMouseDown = useCallback( + const onPointerDown = useCallback( (e: React.PointerEvent) => { e.preventDefault(); setOldX(e.clientX); - window.addEventListener('pointerup', onMouseUp); + setIsPointerDown(true); + window.addEventListener('pointerup', onPointerUp); + window.addEventListener('pointermove', onPointerMove); }, - [onMouseUp] + [onPointerUp, onPointerMove] ); return ( setIsPointerOver(true)} onPointerLeave={() => setIsPointerOver(false)} - onPointerDown={onMouseDown} - onPointerUp={onMouseUp} + onPointerDown={onPointerDown} > ); -} +} \ No newline at end of file diff --git a/src/app/pages/client/space/Space.tsx b/src/app/pages/client/space/Space.tsx index ba391fd0c..500185038 100644 --- a/src/app/pages/client/space/Space.tsx +++ b/src/app/pages/client/space/Space.tsx @@ -391,6 +391,7 @@ export function Space() { const notificationPreferences = useRoomsNotificationPreferencesContext(); const [roomSidebarWidth] = useSetting(settingsAtom, 'roomSidebarWidth'); + const [curWidth, setCurWidth] = useState(roomSidebarWidth); const tombstoneEvent = useStateEvent(space, EventType.RoomTombstone); const selectedRoomId = useSelectedRoom(); @@ -400,6 +401,8 @@ export function Space() { const [closedCategories, setClosedCategories] = useAtom(useClosedNavCategoriesAtom()); + useEffect(() => {setCurWidth(roomSidebarWidth)}, [roomSidebarWidth]); + const getRoom = useCallback( (rId: string): Room | undefined => { if (allJoinedRooms.has(rId)) { @@ -715,7 +718,7 @@ export function Space() { return ( <> -
+
@@ -854,7 +857,7 @@ export function Space() {
- + ); } From 5ce2981cae7daeea38939f8c6e67a37353b2e05b Mon Sep 17 00:00:00 2001 From: Shea Duma Date: Mon, 4 May 2026 21:18:12 +0300 Subject: [PATCH 03/10] fixed scrolling --- src/app/components/page/style.css.ts | 6 ++++++ src/app/pages/client/direct/Direct.tsx | 7 ++++--- src/app/pages/client/explore/Explore.tsx | 7 ++++--- src/app/pages/client/home/Home.tsx | 7 ++++--- src/app/pages/client/inbox/Inbox.tsx | 7 ++++--- src/app/pages/client/sidebar/SidebarResizer.tsx | 12 +++++++----- src/app/pages/client/space/Space.tsx | 10 ++++++---- 7 files changed, 35 insertions(+), 21 deletions(-) diff --git a/src/app/components/page/style.css.ts b/src/app/components/page/style.css.ts index 6d9343c7e..8a5472c48 100644 --- a/src/app/components/page/style.css.ts +++ b/src/app/components/page/style.css.ts @@ -6,6 +6,9 @@ import { DefaultReset, color, config, toRem } from 'folds'; export const PageNav = recipe({ variants: { size: { + '100%': { + width: '100%', + }, '400': { width: toRem(256), }, @@ -14,6 +17,9 @@ export const PageNav = recipe({ }, }, }, + defaultVariants: { + size: '100%', + }, }); export type PageNavVariants = RecipeVariants; diff --git a/src/app/pages/client/direct/Direct.tsx b/src/app/pages/client/direct/Direct.tsx index 809e50c33..5b15af7b1 100644 --- a/src/app/pages/client/direct/Direct.tsx +++ b/src/app/pages/client/direct/Direct.tsx @@ -54,6 +54,7 @@ import { import { useDirectCreateSelected } from '$hooks/router/useDirectSelected'; import { useDirectRooms } from './useDirectRooms'; import { SidebarResizer } from '$pages/client/sidebar/SidebarResizer'; +import { mobileOrTablet } from '$utils/user-agent'; type DirectMenuProps = { requestClose: () => void; @@ -242,7 +243,7 @@ export function Direct() { return ( <> -
+ {noRoomToDisplay ? ( @@ -318,8 +319,8 @@ export function Direct() { )} -
- + + {!mobileOrTablet() && } ); } diff --git a/src/app/pages/client/explore/Explore.tsx b/src/app/pages/client/explore/Explore.tsx index bf4cd4d96..fdea832cf 100644 --- a/src/app/pages/client/explore/Explore.tsx +++ b/src/app/pages/client/explore/Explore.tsx @@ -33,6 +33,7 @@ import { stopPropagation } from '$utils/keyboard'; import { SidebarResizer } from '$pages/client/sidebar/SidebarResizer'; import { settingsAtom } from '$state/settings'; import { useSetting } from '$state/hooks/settings'; +import { mobileOrTablet } from '$utils/user-agent'; export function AddServer() { const mx = useMatrixClient(); @@ -167,7 +168,7 @@ export function Explore() { const [curWidth, setCurWidth] = useState(roomSidebarWidth); return ( <> -
+ @@ -267,8 +268,8 @@ export function Explore() { -
- + + {!mobileOrTablet() && } ); } diff --git a/src/app/pages/client/home/Home.tsx b/src/app/pages/client/home/Home.tsx index 8dbab40c1..cabb3fb3f 100644 --- a/src/app/pages/client/home/Home.tsx +++ b/src/app/pages/client/home/Home.tsx @@ -63,6 +63,7 @@ import { UseStateProvider } from '$components/UseStateProvider'; import { JoinAddressPrompt } from '$components/join-address-prompt'; import { useHomeRooms } from './useHomeRooms'; import { SidebarResizer } from '$pages/client/sidebar/SidebarResizer'; +import { mobileOrTablet } from '$utils/user-agent'; type HomeMenuProps = { requestClose: () => void; @@ -239,7 +240,7 @@ export function Home() { return ( <> -
+ {noRoomToDisplay ? ( @@ -366,8 +367,8 @@ export function Home() { )} -
- + + {!mobileOrTablet() && } ); } diff --git a/src/app/pages/client/inbox/Inbox.tsx b/src/app/pages/client/inbox/Inbox.tsx index eb91682ac..02f89d72c 100644 --- a/src/app/pages/client/inbox/Inbox.tsx +++ b/src/app/pages/client/inbox/Inbox.tsx @@ -11,6 +11,7 @@ import { SidebarResizer } from '$pages/client/sidebar/SidebarResizer'; import { useSetting } from '$state/hooks/settings'; import { settingsAtom } from '$state/settings'; import { useState } from 'react'; +import { mobileOrTablet } from '$utils/user-agent'; function InvitesNavItem() { const invitesSelected = useInboxInvitesSelected(); @@ -51,7 +52,7 @@ export function Inbox() { const [curWidth, setCurWidth] = useState(roomSidebarWidth); return ( <> -
+ @@ -91,8 +92,8 @@ export function Inbox() { -
- + + {!mobileOrTablet() && } ); } diff --git a/src/app/pages/client/sidebar/SidebarResizer.tsx b/src/app/pages/client/sidebar/SidebarResizer.tsx index d1ecdb9c8..e8673c5f8 100644 --- a/src/app/pages/client/sidebar/SidebarResizer.tsx +++ b/src/app/pages/client/sidebar/SidebarResizer.tsx @@ -7,9 +7,11 @@ import React, { useCallback, useEffect, useState } from 'react'; import { settingsAtom } from '$state/settings'; import { useSetting } from '$state/hooks/settings'; -export function SidebarResizer( - { setCurWidth }: { setCurWidth?: Dispatch> } -) { +export function SidebarResizer({ + setCurWidth, +}: { + setCurWidth?: Dispatch>; +}) { const [roomSidebarWidth, setRoomSidebarWidth] = useSetting(settingsAtom, 'roomSidebarWidth'); const [isPointerOver, setIsPointerOver] = useState(false); @@ -30,7 +32,7 @@ export function SidebarResizer( const onPointerMove = useCallback((e: PointerEvent) => { e.preventDefault(); - setInterimX(e.clientX); + setInterimX(e.clientX); }, []); const onPointerUp = useCallback((e: PointerEvent) => { e.preventDefault(); @@ -64,4 +66,4 @@ export function SidebarResizer( /> ); -} \ No newline at end of file +} diff --git a/src/app/pages/client/space/Space.tsx b/src/app/pages/client/space/Space.tsx index 500185038..aba33de98 100644 --- a/src/app/pages/client/space/Space.tsx +++ b/src/app/pages/client/space/Space.tsx @@ -401,7 +401,9 @@ export function Space() { const [closedCategories, setClosedCategories] = useAtom(useClosedNavCategoriesAtom()); - useEffect(() => {setCurWidth(roomSidebarWidth)}, [roomSidebarWidth]); + useEffect(() => { + setCurWidth(roomSidebarWidth); + }, [roomSidebarWidth]); const getRoom = useCallback( (rId: string): Room | undefined => { @@ -718,7 +720,7 @@ export function Space() { return ( <> -
+ @@ -856,8 +858,8 @@ export function Space() { -
- + + {!mobileOrTablet() && } ); } From 115f1f7c38c2ce32244131fe0f0c3b4559a39b34 Mon Sep 17 00:00:00 2001 From: Shea Duma Date: Thu, 7 May 2026 16:18:46 +0300 Subject: [PATCH 04/10] pre-merge --- .../features/settings/cosmetics/Cosmetics.tsx | 80 ++++++++++++++++++- src/app/hooks/useShowRoomIcon.ts | 26 ++++++ src/app/pages/client/home/Home.tsx | 1 + src/app/state/settings.ts | 8 ++ 4 files changed, 114 insertions(+), 1 deletion(-) create mode 100644 src/app/hooks/useShowRoomIcon.ts diff --git a/src/app/features/settings/cosmetics/Cosmetics.tsx b/src/app/features/settings/cosmetics/Cosmetics.tsx index 1681774a2..f053016da 100644 --- a/src/app/features/settings/cosmetics/Cosmetics.tsx +++ b/src/app/features/settings/cosmetics/Cosmetics.tsx @@ -18,7 +18,7 @@ import FocusTrap from 'focus-trap-react'; import { PageContent } from '$components/page'; import { SequenceCard } from '$components/sequence-card'; import { useSetting } from '$state/hooks/settings'; -import type { JumboEmojiSize } from '$state/settings'; +import type { JumboEmojiSize, ShowRoomIcon } from '$state/settings'; import { settingsAtom } from '$state/settings'; import { SettingTile } from '$components/setting-tile'; import { stopPropagation } from '$utils/keyboard'; @@ -26,6 +26,7 @@ import { SequenceCardStyle } from '$features/settings/styles.css'; import { SettingsSectionPage } from '../SettingsSectionPage'; import { Appearance } from './Themes'; import { LanguageSpecificPronouns } from './LanguageSpecificPronouns'; +import { useShowRoomIcon } from '$hooks/useShowRoomIcon'; const emojiSizeItems = [ { id: 'none', name: 'None (Same size as text)' }, @@ -104,6 +105,75 @@ function SelectJumboEmojiSize() { ); } +function SelectShowRoomIcon() { + const [menuCords, setMenuCords] = useState(); + const [showRoomIcon, setShowRoomIconItems] = useSetting(settingsAtom, 'showRoomIcon'); + const showRoomIconItems = useShowRoomIcon(); + + const handleMenu: MouseEventHandler = (evt) => { + setMenuCords(evt.currentTarget.getBoundingClientRect()); + }; + + const handleSelect = (iconStyle: ShowRoomIcon) => { + setShowRoomIconItems(iconStyle); + setMenuCords(undefined); + }; + + return ( + <> + + setMenuCords(undefined), + clickOutsideDeactivates: true, + isKeyForward: (evt: KeyboardEvent) => + evt.key === 'ArrowDown' || evt.key === 'ArrowRight', + isKeyBackward: (evt: KeyboardEvent) => + evt.key === 'ArrowUp' || evt.key === 'ArrowLeft', + escapeDeactivates: stopPropagation, + }} + > + + + {showRoomIconItems.map((item: any) => ( + handleSelect(item.layout)} + > + {item.name} + + ))} + + + + } + /> + + ); +} + function JumboEmoji() { return ( @@ -214,6 +284,14 @@ function IdentityCosmetics() { after={} /> + + + } + /> + + useMemo( + () => [ + { + layout: ShowRoomIcon.Always, + name: 'Always', + }, + { + layout: ShowRoomIcon.Smart, + name: 'Smart', + }, + { + layout: ShowRoomIcon.Never, + name: 'Never', + }, + ], + [] + ); diff --git a/src/app/pages/client/home/Home.tsx b/src/app/pages/client/home/Home.tsx index cabb3fb3f..46aa74fa7 100644 --- a/src/app/pages/client/home/Home.tsx +++ b/src/app/pages/client/home/Home.tsx @@ -352,6 +352,7 @@ export function Home() { - - - } - /> - + = (evt) => { + const val = evt.target.value; + setInputValue(val); + + const parsed = parseInt(val, 10); + if (!Number.isNaN(parsed)) { + setRoomSidebarWidth(parsed); + } + }; + + const handleKeyDown: KeyboardEventHandler = (evt) => { + if (isKeyHotkey('escape', evt)) { + evt.stopPropagation(); + setInputValue(roomSidebarWidth.toString()); + (evt.target as HTMLInputElement).blur(); + } + + if (isKeyHotkey('enter', evt)) { + (evt.target as HTMLInputElement).blur(); + } + }; + + return ( + + ); +} + +function SelectShowRoomIcon() { + const [menuCords, setMenuCords] = useState(); + const [showRoomIcon, setShowRoomIcon] = useSetting(settingsAtom, 'showRoomIcon'); + const showRoomIconItems = useShowRoomIcon(); + + const handleMenu: MouseEventHandler = (evt) => { + setMenuCords(evt.currentTarget.getBoundingClientRect()); + }; + + const handleSelect = (position: ShowRoomIcon) => { + setShowRoomIcon(position); + setMenuCords(undefined); + }; + + return ( + <> + + setMenuCords(undefined), + clickOutsideDeactivates: true, + isKeyForward: (evt: KeyboardEvent) => + evt.key === 'ArrowDown' || evt.key === 'ArrowRight', + isKeyBackward: (evt: KeyboardEvent) => + evt.key === 'ArrowUp' || evt.key === 'ArrowLeft', + escapeDeactivates: stopPropagation, + }} + > + + + {showRoomIconItems.map((item) => ( + handleSelect(item.layout)} + > + {item.name} + + ))} + + + + } + /> + + ); +} export function Appearance({ onThemeBrowserOpenChange, }: { @@ -448,6 +578,24 @@ export function Appearance({ after={} /> + + + } + /> + + + + } + /> + )} diff --git a/src/app/pages/client/direct/Direct.tsx b/src/app/pages/client/direct/Direct.tsx index 5b15af7b1..b65edaeaf 100644 --- a/src/app/pages/client/direct/Direct.tsx +++ b/src/app/pages/client/direct/Direct.tsx @@ -184,6 +184,10 @@ export function Direct() { const [roomSidebarWidth] = useSetting(settingsAtom, 'roomSidebarWidth'); const [curWidth, setCurWidth] = useState(roomSidebarWidth); + useEffect(() => { + setCurWidth(roomSidebarWidth); + }, [roomSidebarWidth]); + const createDirectSelected = useDirectCreateSelected(); const selectedRoomId = useSelectedRoom(); diff --git a/src/app/pages/client/explore/Explore.tsx b/src/app/pages/client/explore/Explore.tsx index 3691f0636..aa70dedf5 100644 --- a/src/app/pages/client/explore/Explore.tsx +++ b/src/app/pages/client/explore/Explore.tsx @@ -1,5 +1,5 @@ import type { FormEventHandler } from 'react'; -import { useCallback, useRef, useState } from 'react'; +import { useCallback, useEffect, useRef, useState } from 'react'; import { useNavigate } from 'react-router-dom'; import FocusTrap from 'focus-trap-react'; import { @@ -166,6 +166,10 @@ export function Explore() { const [roomSidebarWidth] = useSetting(settingsAtom, 'roomSidebarWidth'); const [curWidth, setCurWidth] = useState(roomSidebarWidth); + + useEffect(() => { + setCurWidth(roomSidebarWidth); + }, [roomSidebarWidth]); return ( <> diff --git a/src/app/pages/client/home/Home.tsx b/src/app/pages/client/home/Home.tsx index 46aa74fa7..7a707a238 100644 --- a/src/app/pages/client/home/Home.tsx +++ b/src/app/pages/client/home/Home.tsx @@ -1,5 +1,5 @@ import type { MouseEventHandler } from 'react'; -import { forwardRef, useMemo, useRef, useState } from 'react'; +import { forwardRef, useEffect, useMemo, useRef, useState } from 'react'; import { useNavigate } from 'react-router-dom'; import type { RectCords } from 'folds'; import { @@ -54,7 +54,7 @@ import { markAsRead } from '$utils/notifications'; import { useClosedNavCategoriesAtom } from '$state/hooks/closedNavCategories'; import { stopPropagation } from '$utils/keyboard'; import { useSetting } from '$state/hooks/settings'; -import { settingsAtom } from '$state/settings'; +import { settingsAtom, ShowRoomIcon } from '$state/settings'; import { getRoomNotificationMode, useRoomsNotificationPreferencesContext, @@ -205,6 +205,16 @@ export function Home() { const [roomSidebarWidth] = useSetting(settingsAtom, 'roomSidebarWidth'); const [curWidth, setCurWidth] = useState(roomSidebarWidth); + const [showRoomIcon] = useSetting(settingsAtom, 'showRoomIcon'); + const showIcons = () => { + if (showRoomIcon === ShowRoomIcon.Always) return true; + if (showRoomIcon === ShowRoomIcon.Never) return false; + return curWidth < 96; + }; + useEffect(() => { + setCurWidth(roomSidebarWidth); + }, [roomSidebarWidth]); + const selectedRoomId = useSelectedRoom(); const createRoomSelected = useHomeCreateSelected(); const searchSelected = useHomeSearchSelected(); @@ -352,7 +362,7 @@ export function Home() { { + setCurWidth(roomSidebarWidth); + }, [roomSidebarWidth]); return ( <> diff --git a/src/app/pages/client/space/Space.tsx b/src/app/pages/client/space/Space.tsx index aba33de98..3911d4c0e 100644 --- a/src/app/pages/client/space/Space.tsx +++ b/src/app/pages/client/space/Space.tsx @@ -62,7 +62,7 @@ import { stopPropagation } from '$utils/keyboard'; import { getMatrixToRoom } from '$plugins/matrix-to'; import { getViaServers } from '$plugins/via-servers'; import { useSetting } from '$state/hooks/settings'; -import { settingsAtom } from '$state/settings'; +import { settingsAtom, ShowRoomIcon } from '$state/settings'; import { getRoomNotificationMode, useRoomsNotificationPreferencesContext, @@ -393,6 +393,16 @@ export function Space() { const [roomSidebarWidth] = useSetting(settingsAtom, 'roomSidebarWidth'); const [curWidth, setCurWidth] = useState(roomSidebarWidth); + const [showRoomIcon] = useSetting(settingsAtom, 'showRoomIcon'); + const showIcons = () => { + if (showRoomIcon === ShowRoomIcon.Always) return true; + if (showRoomIcon === ShowRoomIcon.Never) return false; + return curWidth < 96; + }; + useEffect(() => { + setCurWidth(roomSidebarWidth); + }, [roomSidebarWidth]); + const tombstoneEvent = useStateEvent(space, EventType.RoomTombstone); const selectedRoomId = useSelectedRoom(); const lobbySelected = useSpaceLobbySelected(spaceIdOrAlias); @@ -401,10 +411,6 @@ export function Space() { const [closedCategories, setClosedCategories] = useAtom(useClosedNavCategoriesAtom()); - useEffect(() => { - setCurWidth(roomSidebarWidth); - }, [roomSidebarWidth]); - const getRoom = useCallback( (rId: string): Room | undefined => { if (allJoinedRooms.has(rId)) { @@ -840,7 +846,7 @@ export function Space() { Date: Thu, 7 May 2026 18:27:55 +0300 Subject: [PATCH 06/10] fix weird test issue --- src/app/features/settings/cosmetics/Cosmetics.tsx | 3 +-- src/app/features/settings/cosmetics/Themes.tsx | 2 +- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/src/app/features/settings/cosmetics/Cosmetics.tsx b/src/app/features/settings/cosmetics/Cosmetics.tsx index 41dc7ecb2..49ab59374 100644 --- a/src/app/features/settings/cosmetics/Cosmetics.tsx +++ b/src/app/features/settings/cosmetics/Cosmetics.tsx @@ -104,7 +104,7 @@ function SelectJumboEmojiSize() { ); } -export const profileCardRenderItems: { id: RenderUserCardsMode; name: string }[] = [ +const profileCardRenderItems: { id: RenderUserCardsMode; name: string }[] = [ { id: 'both', name: 'Light & dark' }, { id: 'light', name: 'Light only' }, { id: 'dark', name: 'Dark only' }, @@ -290,7 +290,6 @@ function IdentityCosmetics() { after={} /> - = (evt) => { const val = evt.target.value; From cac58ac478cbf914a576edbcfd4e8d98b6c732f1 Mon Sep 17 00:00:00 2001 From: Shea Duma Date: Fri, 8 May 2026 07:01:56 +0300 Subject: [PATCH 07/10] added more items --- src/app/features/room/CallChatView.tsx | 94 +++-- src/app/features/room/MembersDrawer.tsx | 376 ++++++++++-------- src/app/features/room/RoomView.tsx | 10 +- src/app/features/room/ThreadBrowser.tsx | 244 ++++++------ src/app/features/room/ThreadDrawer.css.ts | 3 +- src/app/features/room/ThreadDrawer.tsx | 364 +++++++++-------- .../features/settings/cosmetics/Themes.tsx | 141 ++++++- src/app/features/widgets/WidgetsDrawer.css.ts | 5 +- src/app/features/widgets/WidgetsDrawer.tsx | 171 ++++---- src/app/hooks/usePanelSizes.ts | 37 ++ src/app/pages/client/direct/Direct.tsx | 10 +- src/app/pages/client/explore/Explore.tsx | 10 +- src/app/pages/client/home/Home.tsx | 10 +- src/app/pages/client/inbox/Inbox.tsx | 10 +- .../client/sidebar/SidebarResizer.css.ts | 4 +- .../pages/client/sidebar/SidebarResizer.tsx | 30 +- src/app/pages/client/space/Space.tsx | 10 +- src/app/state/settings.ts | 15 +- 18 files changed, 942 insertions(+), 602 deletions(-) create mode 100644 src/app/hooks/usePanelSizes.ts diff --git a/src/app/features/room/CallChatView.tsx b/src/app/features/room/CallChatView.tsx index 3acc8c562..d979a144c 100644 --- a/src/app/features/room/CallChatView.tsx +++ b/src/app/features/room/CallChatView.tsx @@ -1,10 +1,15 @@ import { useSetAtom } from 'jotai'; import { useParams } from 'react-router-dom'; -import { Box, Text, TooltipProvider, Tooltip, Icon, Icons, IconButton, toRem } from 'folds'; +import { Box, Text, TooltipProvider, Tooltip, Icon, Icons, IconButton } from 'folds'; import { Page, PageHeader } from '../../components/page'; import { callChatAtom } from '../../state/callEmbed'; import { RoomView } from './RoomView'; import { ScreenSize, useScreenSizeContext } from '../../hooks/useScreenSize'; +import { SidebarResizer } from '$pages/client/sidebar/SidebarResizer'; +import { useSetting } from '$state/hooks/settings'; +import { settingsAtom } from '$state/settings'; +import { mobileOrTablet } from '$utils/user-agent'; +import { useState, useEffect } from 'react'; export function CallChatView() { const { eventId } = useParams(); @@ -13,44 +18,59 @@ export function CallChatView() { const handleClose = () => setChat(false); + const [threadSidebarWidth, setThreadSidebarWidth] = useSetting(settingsAtom, 'vcmsgSidebarWidth'); + const [curWidth, setCurWidth] = useState(threadSidebarWidth); + useEffect(() => { + setCurWidth(threadSidebarWidth); + }, [threadSidebarWidth]); return ( - - - - - - Chat - - - - - Close - - } - > - {(triggerRef) => ( - - - - )} - + <> + {!mobileOrTablet() && ( + + )} + + + + + + Chat + + + + + Close + + } + > + {(triggerRef) => ( + + + + )} + + + + + - - - - - + + ); } diff --git a/src/app/features/room/MembersDrawer.tsx b/src/app/features/room/MembersDrawer.tsx index f751bcf31..82e831c41 100644 --- a/src/app/features/room/MembersDrawer.tsx +++ b/src/app/features/room/MembersDrawer.tsx @@ -1,5 +1,5 @@ import type { ChangeEventHandler, MouseEventHandler } from 'react'; -import { useCallback, useMemo, useRef, useState } from 'react'; +import { useCallback, useEffect, useMemo, useRef, useState } from 'react'; import type { RectCords } from 'folds'; import { Avatar, @@ -56,6 +56,8 @@ import { useRoomCreators } from '$hooks/useRoomCreators'; import { useSableCosmetics } from '$hooks/useSableCosmetics'; import { formatCompactNumber } from '$utils/formatCompactNumber'; import * as css from './MembersDrawer.css'; +import { SidebarResizer } from '$pages/client/sidebar/SidebarResizer'; +import { mobileOrTablet } from '$utils/user-agent'; type MemberDrawerHeaderProps = { room: Room; @@ -286,200 +288,224 @@ export function MembersDrawer({ room, members }: MembersDrawerProps) { openUserRoomProfile(room.roomId, space?.roomId, userId, cords, 'Left'); }; + const [memberSidebarWidth, setMemberSidebarWidth] = useSetting( + settingsAtom, + 'memberSidebarWidth' + ); + const [curWidth, setCurWidth] = useState(memberSidebarWidth); + useEffect(() => { + setCurWidth(memberSidebarWidth); + }, [memberSidebarWidth]); return ( - - - - - - - - - {(anchor: RectCords | undefined, setAnchor) => ( - setAnchor(undefined)} - /> - } - > - - setAnchor( - evt.currentTarget.getBoundingClientRect() - )) as MouseEventHandler + <> + {!mobileOrTablet() && ( + + )} + + + + + + + + + {(anchor: RectCords | undefined, setAnchor) => ( + setAnchor(undefined)} + /> } - variant="Background" - size="400" - radii="300" - before={} > - {membershipFilter.name} - - - )} - - - {(anchor: RectCords | undefined, setAnchor) => ( - setAnchor(undefined)} - /> - } - > - - setAnchor( - evt.currentTarget.getBoundingClientRect() - )) as MouseEventHandler + + setAnchor( + evt.currentTarget.getBoundingClientRect() + )) as MouseEventHandler + } + variant="Background" + size="400" + radii="300" + before={} + > + {membershipFilter.name} + + + )} + + + {(anchor: RectCords | undefined, setAnchor) => ( + setAnchor(undefined)} + /> } - variant="Background" - size="400" - radii="300" - after={} > - {memberSort.name} - - - )} - - - - } - after={ - result && ( - 0 ? 'Success' : 'Critical'} - size="400" - radii="Pill" - aria-pressed - onClick={() => { - if (searchInputRef.current) { - searchInputRef.current.value = ''; - searchInputRef.current.focus(); + + setAnchor( + evt.currentTarget.getBoundingClientRect() + )) as MouseEventHandler } - resetSearch(); - }} - after={} - > - {`${result.items.length || 'No'} ${ - result.items.length === 1 ? 'Result' : 'Results' - }`} - - ) - } - /> + variant="Background" + size="400" + radii="300" + after={} + > + {memberSort.name} + + + )} + + + + } + after={ + result && ( + 0 ? 'Success' : 'Critical'} + size="400" + radii="Pill" + aria-pressed + onClick={() => { + if (searchInputRef.current) { + searchInputRef.current.value = ''; + searchInputRef.current.focus(); + } + resetSearch(); + }} + after={} + > + {`${result.items.length || 'No'} ${ + result.items.length === 1 ? 'Result' : 'Results' + }`} + + ) + } + /> + - - - virtualizer.scrollToOffset(0)} - variant="Surface" - radii="Pill" - outlined - size="300" - aria-label="Scroll to Top" - > - - - + + virtualizer.scrollToOffset(0)} + variant="Surface" + radii="Pill" + outlined + size="300" + aria-label="Scroll to Top" + > + + + - {!fetchingMembers && !result && processMembers.length === 0 && ( - - {`No "${membershipFilter.name}" Members`} - - )} + {!fetchingMembers && !result && processMembers.length === 0 && ( + + {`No "${membershipFilter.name}" Members`} + + )} + + +
+ {virtualizer.getVirtualItems().map((vItem) => { + const tagOrMember = PLTagOrRoomMember[vItem.index]; + if (!tagOrMember) return null; + if (!('userId' in tagOrMember)) { + return ( + + {tagOrMember.name} + + ); + } - -
- {virtualizer.getVirtualItems().map((vItem) => { - const tagOrMember = PLTagOrRoomMember[vItem.index]; - if (!tagOrMember) return null; - if (!('userId' in tagOrMember)) { return ( - - {tagOrMember.name} - + receipt.userId === tagOrMember.userId + )} + /> +
); - } + })} +
+
- return ( -
- receipt.userId === tagOrMember.userId - )} - /> -
- ); - })} -
+ {fetchingMembers && ( + + + + )}
- - {fetchingMembers && ( - - - - )} - - + + - + ); } diff --git a/src/app/features/room/RoomView.tsx b/src/app/features/room/RoomView.tsx index 90afe3498..c8af24c2a 100644 --- a/src/app/features/room/RoomView.tsx +++ b/src/app/features/room/RoomView.tsx @@ -1,7 +1,7 @@ import { useCallback, useRef, useState } from 'react'; import { useAtomValue } from 'jotai'; import { Transforms } from 'slate'; -import { Box, Text, config, toRem } from 'folds'; +import { Box, Text, config } from 'folds'; import { EventType } from '$types/matrix-sdk'; import { ReactEditor } from 'slate-react'; import { isKeyHotkey } from 'is-hotkey'; @@ -143,11 +143,9 @@ export function RoomView({ eventId }: { eventId?: string }) { {(onBack) => ( diff --git a/src/app/features/room/ThreadBrowser.tsx b/src/app/features/room/ThreadBrowser.tsx index f9891eeda..391d0514e 100644 --- a/src/app/features/room/ThreadBrowser.tsx +++ b/src/app/features/room/ThreadBrowser.tsx @@ -13,6 +13,7 @@ import { Avatar, config, Chip, + toRem, } from 'folds'; import type { EventTimelineSet, MatrixEvent, Room, Thread } from '$types/matrix-sdk'; import { NotificationCountType, RoomEvent, ThreadEvent } from '$types/matrix-sdk'; @@ -52,6 +53,8 @@ import { import { UnreadBadge, UnreadBadgeCenter } from '$components/unread-badge'; import { EncryptedContent } from './message'; import * as css from './ThreadDrawer.css'; +import { SidebarResizer } from '$pages/client/sidebar/SidebarResizer'; +import { mobileOrTablet } from '$utils/user-agent'; type ThreadPreviewProps = { room: Room; @@ -436,130 +439,149 @@ export function ThreadBrowser({ room, onOpenThread, onClose, overlay }: ThreadBr setQuery(e.target.value); }; + const [threadSidebarWidth, setThreadSidebarWidth] = useSetting( + settingsAtom, + 'threadSidebarWidth' + ); + const [curWidth, setCurWidth] = useState(threadSidebarWidth); + useEffect(() => { + setCurWidth(threadSidebarWidth); + }, [threadSidebarWidth]); return ( - -
- - - - Threads - - - - - - - -
- + <> + {!mobileOrTablet() && ( + + )} - } - after={ - query ? ( - { - setQuery(''); - searchRef.current?.focus(); - }} - aria-label="Clear search" - > - - - ) : undefined - } - /> - +
+ + + + Threads + + + + + + + +
- - - {(() => { - if (threads.length === 0 && loadingMore) - return ( - - - - ); - if (threads.length === 0) - return ( - - - - {lowerQuery ? 'No threads match your search.' : 'No threads yet.'} - - - ); - return ( - <> - } + after={ + query ? ( + { + setQuery(''); + searchRef.current?.focus(); + }} + aria-label="Clear search" > - {threads.map((thread: Thread) => ( - - ))} - - {loadingMore && ( + + + ) : undefined + } + /> + + + + + {(() => { + if (threads.length === 0 && loadingMore) + return ( - )} - - ); - })()} - + ); + if (threads.length === 0) + return ( + + + + {lowerQuery ? 'No threads match your search.' : 'No threads yet.'} + + + ); + return ( + <> + + {threads.map((thread: Thread) => ( + + ))} + + {loadingMore && ( + + + + )} + + ); + })()} + +
-
+ ); } diff --git a/src/app/features/room/ThreadDrawer.css.ts b/src/app/features/room/ThreadDrawer.css.ts index 4b2d9c1a1..d44d1b103 100644 --- a/src/app/features/room/ThreadDrawer.css.ts +++ b/src/app/features/room/ThreadDrawer.css.ts @@ -1,8 +1,7 @@ import { style, globalStyle } from '@vanilla-extract/css'; -import { config, color, toRem } from 'folds'; +import { config, color } from 'folds'; export const ThreadDrawer = style({ - width: toRem(440), height: '100%', display: 'flex', flexDirection: 'column', diff --git a/src/app/features/room/ThreadDrawer.tsx b/src/app/features/room/ThreadDrawer.tsx index 39cc66728..dba1067ac 100644 --- a/src/app/features/room/ThreadDrawer.tsx +++ b/src/app/features/room/ThreadDrawer.tsx @@ -1,6 +1,18 @@ import type { MouseEventHandler } from 'react'; import { useCallback, useEffect, useMemo, useRef, useState } from 'react'; -import { Box, Header, Icon, IconButton, Icons, Scroll, Spinner, Text, config } from 'folds'; +import { + Box, + Header, + Icon, + IconButton, + Icons, + Scroll, + Spinner, + Text, + color, + config, + toRem, +} from 'folds'; import type { IEvent, Room } from '$types/matrix-sdk'; import { Direction, @@ -63,6 +75,8 @@ import { useTimelineEventRenderer } from '$hooks/timeline/useTimelineEventRender import { RoomInput } from './RoomInput'; import { RoomViewFollowing, RoomViewFollowingPlaceholder } from './RoomViewFollowing'; import * as css from './ThreadDrawer.css'; +import { SidebarResizer } from '$pages/client/sidebar/SidebarResizer'; +import { mobileOrTablet } from '$utils/user-agent'; /** * Resolve the list of reply events to show in the thread drawer. @@ -726,172 +740,206 @@ export function ThreadDrawer({ room, threadRootId, onClose, overlay }: ThreadDra ); const latestThreadEventId = processedEvents.at(-1)?.id ?? rootEvent?.getId(); + const [threadSidebarWidth, setThreadSidebarWidth] = useSetting( + settingsAtom, + 'threadSidebarWidth' + ); + const [curWidth, setCurWidth] = useState(threadSidebarWidth); + useEffect(() => { + setCurWidth(threadSidebarWidth); + }, [threadSidebarWidth]); + + const [threadRootHeight, setThreadRootHeight] = useSetting(settingsAtom, 'threadRootHeight'); + const [curHeight, setCurHeight] = useState(threadRootHeight); + useEffect(() => { + setCurHeight(threadRootHeight); + }, [threadRootHeight]); return ( - - {/* Header */} -
- - - - Thread - - - - + {!mobileOrTablet() && ( + + )} + + {/* Header */} +
+ + + + Thread + + + + + + + +
+ + {/* Thread root message */} + {rootEvent && ( + <> + + + {renderMatrixEvent( + rootEvent.getType(), + typeof rootEvent.getStateKey() === 'string', + rootEvent.getId()!, + rootEvent, + processedEvents.find((e) => e.id === threadRootId)?.itemIndex ?? 0, + thread?.timelineSet ?? room.getUnfilteredTimelineSet(), + false + )} + + + +
+ + )} + {/* Replies */} + + - - - -
- - {/* Thread root message */} - {rootEvent && ( - - - {renderMatrixEvent( - rootEvent.getType(), - typeof rootEvent.getStateKey() === 'string', - rootEvent.getId()!, - rootEvent, - processedEvents.find((e) => e.id === threadRootId)?.itemIndex ?? 0, - thread?.timelineSet ?? room.getUnfilteredTimelineSet(), - false - )} - - - )} - - {/* Replies */} - - - {(() => { - if (isThreadLoading) - return ( - - - - ); - if (processedReplies.length === 0) - return ( - - - - No replies yet. Start the thread below! - - - ); - return ( - <> - {loadingOlderReplies && ( + {(() => { + if (isThreadLoading) + return ( - + - )} - {/* Reply count label inside scroll area */} - - - {processedReplies.length} {processedReplies.length === 1 ? 'reply' : 'replies'} - - - - {processedReplies.map((e) => - renderMatrixEvent( - e.mEvent.getType(), - typeof e.mEvent.getStateKey() === 'string', - e.id, - e.mEvent, - e.itemIndex, - e.timelineSet, - e.collapsed - ) + ); + if (processedReplies.length === 0) + return ( + + + + No replies yet. Start the thread below! + + + ); + return ( + <> + {loadingOlderReplies && ( + + + )} - - - ); - })()} - - + {/* Reply count label inside scroll area */} + + + {processedReplies.length}{' '} + {processedReplies.length === 1 ? 'reply' : 'replies'} + + + + {processedReplies.map((e) => + renderMatrixEvent( + e.mEvent.getType(), + typeof e.mEvent.getStateKey() === 'string', + e.id, + e.mEvent, + e.itemIndex, + e.timelineSet, + e.collapsed + ) + )} + + + ); + })()} + +
- {/* Thread input */} - -
- -
- {hideReads ? ( - - ) : ( - - )} + {/* Thread input */} + +
+ +
+ {hideReads ? ( + + ) : ( + + )} +
- + ); } diff --git a/src/app/features/settings/cosmetics/Themes.tsx b/src/app/features/settings/cosmetics/Themes.tsx index 64c4c749a..14767f079 100644 --- a/src/app/features/settings/cosmetics/Themes.tsx +++ b/src/app/features/settings/cosmetics/Themes.tsx @@ -36,6 +36,8 @@ import { ThemeAppearanceSection } from './ThemeAppearanceSection'; import { stopPropagation } from '$utils/keyboard'; import FocusTrap from 'focus-trap-react'; import { useShowRoomIcon } from '$hooks/useShowRoomIcon'; +import type { PanelSizetItem} from '$hooks/usePanelSizes'; +import { usePanelSizeItems } from '$hooks/usePanelSizes'; function makeArboriumThemeOptions(kind?: 'light' | 'dark') { const themes = kind @@ -379,9 +381,89 @@ function PageZoomInput() { ); } -function RoomSidebarWidth() { - const [roomSidebarWidth, setRoomSidebarWidth] = useSetting(settingsAtom, 'roomSidebarWidth'); - const [inputValue, setInputValue] = useState(roomSidebarWidth?.toString()); +function PanelSelector({ + sidebarSelector, + setSidebarSelector, +}: { + sidebarSelector: string; + setSidebarSelector: (arg0: string) => void; +}) { + const [menuCords, setMenuCords] = useState(); + const panelSizeItems = usePanelSizeItems(); + + const handleMenu: MouseEventHandler = (evt) => { + setMenuCords(evt.currentTarget.getBoundingClientRect()); + }; + + const handleSelect = (position: PanelSizetItem) => { + setSidebarSelector(position.layout); + setMenuCords(undefined); + }; + + return ( + <> + + setMenuCords(undefined), + clickOutsideDeactivates: true, + isKeyForward: (evt: KeyboardEvent) => + evt.key === 'ArrowDown' || evt.key === 'ArrowRight', + isKeyBackward: (evt: KeyboardEvent) => + evt.key === 'ArrowUp' || evt.key === 'ArrowLeft', + escapeDeactivates: stopPropagation, + }} + > + + + {panelSizeItems.map((item) => ( + handleSelect(item)} + > + {item.name} + + ))} + + + + } + /> + + ); +} +function SidebarWidth({ + sidebarWidth, + setSidebarWidth, + sidebarSelector, +}: { + sidebarWidth: number; + setSidebarWidth: (arg0: number) => void; + sidebarSelector: string; +}) { + const [inputValue, setInputValue] = useState(sidebarWidth?.toString()); const handleChange: ChangeEventHandler = (evt) => { const val = evt.target.value; @@ -389,14 +471,14 @@ function RoomSidebarWidth() { const parsed = parseInt(val, 10); if (!Number.isNaN(parsed)) { - setRoomSidebarWidth(parsed); + setSidebarWidth(parsed); } }; const handleKeyDown: KeyboardEventHandler = (evt) => { if (isKeyHotkey('escape', evt)) { evt.stopPropagation(); - setInputValue(roomSidebarWidth.toString()); + setInputValue(sidebarWidth.toString()); (evt.target as HTMLInputElement).blur(); } @@ -406,9 +488,10 @@ function RoomSidebarWidth() { }; return ( + <> + {sidebarSelector} + ); } @@ -495,6 +580,22 @@ export function Appearance({ }: { onThemeBrowserOpenChange?: (open: boolean) => void; } = {}) { + const [roomSidebarWidth, setRoomSidebarWidth] = useSetting(settingsAtom, 'roomSidebarWidth'); + const [memberSidebarWidth, setMemberSidebarWidth] = useSetting( + settingsAtom, + 'memberSidebarWidth' + ); + const [threadSidebarWidth, setThreadSidebarWidth] = useSetting( + settingsAtom, + 'threadSidebarWidth' + ); + const [threadRootHeight, setThreadRootHeight] = useSetting(settingsAtom, 'threadRootHeight'); + const [vcmsgSidebarWidth, setvcmsgSidebarWidth] = useSetting(settingsAtom, 'vcmsgSidebarWidth'); + const [widgetSidebarWidth, setWidgetSidebarWidth] = useSetting( + settingsAtom, + 'widgetSidebarWidth' + ); + const [sidebarSelector, setSidebarSelector] = useState('roomSidebarWidth'); const [twitterEmoji, setTwitterEmoji] = useSetting(settingsAtom, 'twitterEmoji'); const [customDMCards, setCustomDMCards] = useSetting(settingsAtom, 'customDMCards'); const [showEasterEggs, setShowEasterEggs] = useSetting(settingsAtom, 'showEasterEggs'); @@ -581,19 +682,31 @@ export function Appearance({ } + title="Show Room Icons" + focusId="show-room-icons" + description="When do you want to show the specific room icons in the sidebar as opposed to the default room icons?" + after={} /> } + title="Room Sidebar Width" + focusId="room-sidebar-width" + description="The width of the sidebar, it can be changed either here numerically or by hovering and dragging the lighting bar" + after={ + <> + + + + } /> diff --git a/src/app/features/widgets/WidgetsDrawer.css.ts b/src/app/features/widgets/WidgetsDrawer.css.ts index 5687b57ad..f08b53e46 100644 --- a/src/app/features/widgets/WidgetsDrawer.css.ts +++ b/src/app/features/widgets/WidgetsDrawer.css.ts @@ -1,10 +1,9 @@ import { style } from '@vanilla-extract/css'; -import { config, toRem } from 'folds'; +import { config } from 'folds'; export const WidgetsDrawer = style({ - width: toRem(420), maxWidth: '100vw', - minWidth: '20vw', + minWidth: '9vw', }); export const WidgetsDrawerHeader = style({ diff --git a/src/app/features/widgets/WidgetsDrawer.tsx b/src/app/features/widgets/WidgetsDrawer.tsx index b135b3720..106ff6f51 100644 --- a/src/app/features/widgets/WidgetsDrawer.tsx +++ b/src/app/features/widgets/WidgetsDrawer.tsx @@ -1,5 +1,5 @@ import type { FormEventHandler, MouseEventHandler } from 'react'; -import { useState } from 'react'; +import { useEffect, useState } from 'react'; import { Box, Header, @@ -15,13 +15,14 @@ import { config, Button, Line, + toRem, } from 'folds'; import type { Room } from '$types/matrix-sdk'; import { useMatrixClient } from '$hooks/useMatrixClient'; import type { RoomWidget } from '$hooks/useRoomWidgets'; import { useRoomWidgets, enrichWidgetUrl } from '$hooks/useRoomWidgets'; -import { useSetSetting } from '$state/hooks/settings'; +import { useSetSetting, useSetting } from '$state/hooks/settings'; import { settingsAtom } from '$state/settings'; import { usePowerLevelsContext } from '$hooks/usePowerLevels'; import { useRoomCreators } from '$hooks/useRoomCreators'; @@ -32,6 +33,8 @@ import { WidgetIframe } from './WidgetIframe'; import * as css from './WidgetsDrawer.css'; import { IntegrationManager } from './IntegrationManager'; import { CustomStateEvent } from '$types/matrix/room'; +import { SidebarResizer } from '$pages/client/sidebar/SidebarResizer'; +import { mobileOrTablet } from '$utils/user-agent'; type WidgetsDrawerHeaderProps = { activeWidget: RoomWidget | null; @@ -229,6 +232,15 @@ export function WidgetsDrawer({ room }: WidgetsDrawerProps) { const permissions = useRoomPermissions(creators, powerLevels); const canManageWidgets = permissions.stateEvent(CustomStateEvent.RoomWidget, mx.getSafeUserId()); + const [widgetSidebarWidth, setWidgetSidebarWidth] = useSetting( + settingsAtom, + 'widgetSidebarWidth' + ); + const [curWidth, setCurWidth] = useState(widgetSidebarWidth); + useEffect(() => { + setCurWidth(widgetSidebarWidth); + }, [widgetSidebarWidth]); + const handleRemoveWidget = async (widget: RoomWidget) => { try { await mx.sendStateEvent(room.roomId, CustomStateEvent.RoomWidget, {}, widget.id); @@ -243,74 +255,93 @@ export function WidgetsDrawer({ room }: WidgetsDrawerProps) { const handleBack = () => setActiveWidget(null); return ( - - - {activeWidget ? ( - - - - ) : ( - - - {widgets.length === 0 && !showAddForm && ( - - - No widgets in this room. - - - )} - {widgets.map((widget) => ( - - ))} - {canManageWidgets && ( - <> - - {showAddForm ? ( - setShowAddForm(false)} /> - ) : ( - - - - - )} - - )} - - + <> + {!mobileOrTablet() && ( + )} - setShowIntegrationManager(false)} - /> - + + + {activeWidget ? ( + + + + ) : ( + + + {widgets.length === 0 && !showAddForm && ( + + + No widgets in this room. + + + )} + {widgets.map((widget) => ( + + ))} + {canManageWidgets && ( + <> + + {showAddForm ? ( + setShowAddForm(false)} /> + ) : ( + + + + + )} + + )} + + + )} + setShowIntegrationManager(false)} + /> + + ); } diff --git a/src/app/hooks/usePanelSizes.ts b/src/app/hooks/usePanelSizes.ts new file mode 100644 index 000000000..19a30b1b3 --- /dev/null +++ b/src/app/hooks/usePanelSizes.ts @@ -0,0 +1,37 @@ +import { useMemo } from 'react'; + +export type PanelSizetItem = { + layout: string; + name: string; +}; + +export const usePanelSizeItems = (): PanelSizetItem[] => + useMemo( + () => [ + { + layout: 'roomSidebarWidth', + name: 'Room Panel Width', + }, + { + layout: 'memberSidebarWidth', + name: 'Member Panel Width', + }, + { + layout: 'threadSidebarWidth', + name: 'Thread Panel Width', + }, + { + layout: 'threadRootHeight', + name: 'Thread Root Height', + }, + { + layout: 'vcmsgSidebarWidth', + name: 'VoiceCall Msg Panel Width', + }, + { + layout: 'widgetSidebarWidth', + name: 'Widget Panel Width', + }, + ], + [] + ); diff --git a/src/app/pages/client/direct/Direct.tsx b/src/app/pages/client/direct/Direct.tsx index b65edaeaf..4ad59ac15 100644 --- a/src/app/pages/client/direct/Direct.tsx +++ b/src/app/pages/client/direct/Direct.tsx @@ -181,7 +181,7 @@ export function Direct() { const roomToUnread = useAtomValue(roomToUnreadAtom); const navigate = useNavigate(); const [customDMCards] = useSetting(settingsAtom, 'customDMCards'); - const [roomSidebarWidth] = useSetting(settingsAtom, 'roomSidebarWidth'); + const [roomSidebarWidth, setRoomSidebarWidth] = useSetting(settingsAtom, 'roomSidebarWidth'); const [curWidth, setCurWidth] = useState(roomSidebarWidth); useEffect(() => { @@ -324,7 +324,13 @@ export function Direct() { )} - {!mobileOrTablet() && } + {!mobileOrTablet() && ( + + )} ); } diff --git a/src/app/pages/client/explore/Explore.tsx b/src/app/pages/client/explore/Explore.tsx index aa70dedf5..eab6c4edd 100644 --- a/src/app/pages/client/explore/Explore.tsx +++ b/src/app/pages/client/explore/Explore.tsx @@ -164,7 +164,7 @@ export function Explore() { const featuredSelected = useExploreFeaturedSelected(); const selectedServer = useExploreServer(); - const [roomSidebarWidth] = useSetting(settingsAtom, 'roomSidebarWidth'); + const [roomSidebarWidth, setRoomSidebarWidth] = useSetting(settingsAtom, 'roomSidebarWidth'); const [curWidth, setCurWidth] = useState(roomSidebarWidth); useEffect(() => { @@ -273,7 +273,13 @@ export function Explore() { - {!mobileOrTablet() && } + {!mobileOrTablet() && ( + + )} ); } diff --git a/src/app/pages/client/home/Home.tsx b/src/app/pages/client/home/Home.tsx index 7a707a238..53a5f40df 100644 --- a/src/app/pages/client/home/Home.tsx +++ b/src/app/pages/client/home/Home.tsx @@ -202,7 +202,7 @@ export function Home() { const roomToUnread = useAtomValue(roomToUnreadAtom); const navigate = useNavigate(); - const [roomSidebarWidth] = useSetting(settingsAtom, 'roomSidebarWidth'); + const [roomSidebarWidth, setRoomSidebarWidth] = useSetting(settingsAtom, 'roomSidebarWidth'); const [curWidth, setCurWidth] = useState(roomSidebarWidth); const [showRoomIcon] = useSetting(settingsAtom, 'showRoomIcon'); @@ -379,7 +379,13 @@ export function Home() { )} - {!mobileOrTablet() && } + {!mobileOrTablet() && ( + + )} ); } diff --git a/src/app/pages/client/inbox/Inbox.tsx b/src/app/pages/client/inbox/Inbox.tsx index adac4bb9d..44401ed50 100644 --- a/src/app/pages/client/inbox/Inbox.tsx +++ b/src/app/pages/client/inbox/Inbox.tsx @@ -48,7 +48,7 @@ export function Inbox() { useNavToActivePathMapper('inbox'); const notificationsSelected = useInboxNotificationsSelected(); - const [roomSidebarWidth] = useSetting(settingsAtom, 'roomSidebarWidth'); + const [roomSidebarWidth, setRoomSidebarWidth] = useSetting(settingsAtom, 'roomSidebarWidth'); const [curWidth, setCurWidth] = useState(roomSidebarWidth); useEffect(() => { @@ -97,7 +97,13 @@ export function Inbox() { - {!mobileOrTablet() && } + {!mobileOrTablet() && ( + + )} ); } diff --git a/src/app/pages/client/sidebar/SidebarResizer.css.ts b/src/app/pages/client/sidebar/SidebarResizer.css.ts index a2ac8d150..4f270bf09 100644 --- a/src/app/pages/client/sidebar/SidebarResizer.css.ts +++ b/src/app/pages/client/sidebar/SidebarResizer.css.ts @@ -10,10 +10,10 @@ export const SidebarResizer = style({ export const SidebarResizerHover = style({ height: '100%', zIndex: '100', - boxShadow: `0px 0px 32px 8px ${color.Primary.Container}`, + boxShadow: `0px 0px 32px 8px ${color.Primary.Main}`, }); export const SideBarResizerAnimation = style({ width: '100%', - backgroundColor: color.Primary.Container, + backgroundColor: color.Primary.Main, transition: '0.2s', }); diff --git a/src/app/pages/client/sidebar/SidebarResizer.tsx b/src/app/pages/client/sidebar/SidebarResizer.tsx index e8673c5f8..89927bd02 100644 --- a/src/app/pages/client/sidebar/SidebarResizer.tsx +++ b/src/app/pages/client/sidebar/SidebarResizer.tsx @@ -4,16 +4,20 @@ import { Box } from 'folds'; import * as css from '$pages/client/sidebar/SidebarResizer.css'; import type { Dispatch, SetStateAction } from 'react'; import React, { useCallback, useEffect, useState } from 'react'; -import { settingsAtom } from '$state/settings'; -import { useSetting } from '$state/hooks/settings'; export function SidebarResizer({ + sidebarWidth, + setSidebarWidth, setCurWidth, + rightSided, + topSided, }: { + sidebarWidth: number; + setSidebarWidth: (arg0: number) => void; setCurWidth?: Dispatch>; + rightSided?: boolean; + topSided?: boolean; }) { - const [roomSidebarWidth, setRoomSidebarWidth] = useSetting(settingsAtom, 'roomSidebarWidth'); - const [isPointerOver, setIsPointerOver] = useState(false); const [isPointerDown, setIsPointerDown] = useState(false); const [oldX, setOldX] = useState(0); @@ -21,22 +25,22 @@ export function SidebarResizer({ const [newX, setNewX] = useState(0); useEffect(() => { - const change = oldX - newX; - if (change) setRoomSidebarWidth(Math.min(Math.max(roomSidebarWidth - change, 0), 1200)); + const change = rightSided ? -(oldX - newX) : oldX - newX; + if (change) setSidebarWidth(Math.min(Math.max(sidebarWidth - change, 0), 1200)); }, [newX]); useEffect(() => { - const change = oldX - interimX; - if (change && setCurWidth) setCurWidth(Math.min(Math.max(roomSidebarWidth - change, 0), 1200)); + const change = rightSided ? -(oldX - interimX) : oldX - interimX; + if (change && setCurWidth) setCurWidth(Math.min(Math.max(sidebarWidth - change, 0), 1200)); }, [interimX]); const onPointerMove = useCallback((e: PointerEvent) => { e.preventDefault(); - setInterimX(e.clientX); + setInterimX(topSided ? e.clientY : e.clientX); }, []); const onPointerUp = useCallback((e: PointerEvent) => { e.preventDefault(); - setNewX(e.clientX); + setNewX(topSided ? e.clientY : e.clientX); setIsPointerDown(false); window.removeEventListener('pointerup', onPointerUp); window.removeEventListener('pointermove', onPointerMove); @@ -45,7 +49,7 @@ export function SidebarResizer({ const onPointerDown = useCallback( (e: React.PointerEvent) => { e.preventDefault(); - setOldX(e.clientX); + setOldX(topSided ? e.clientY : e.clientX); setIsPointerDown(true); window.addEventListener('pointerup', onPointerUp); window.addEventListener('pointermove', onPointerMove); @@ -59,6 +63,10 @@ export function SidebarResizer({ onPointerEnter={() => setIsPointerOver(true)} onPointerLeave={() => setIsPointerOver(false)} onPointerDown={onPointerDown} + style={{ + width: topSided ? '100%' : '4px', + height: topSided ? '4px' : '100%', + }} > new Set(allRooms), [allRooms]); const notificationPreferences = useRoomsNotificationPreferencesContext(); - const [roomSidebarWidth] = useSetting(settingsAtom, 'roomSidebarWidth'); + const [roomSidebarWidth, setRoomSidebarWidth] = useSetting(settingsAtom, 'roomSidebarWidth'); const [curWidth, setCurWidth] = useState(roomSidebarWidth); const [showRoomIcon] = useSetting(settingsAtom, 'showRoomIcon'); @@ -865,7 +865,13 @@ export function Space() { - {!mobileOrTablet() && } + {!mobileOrTablet() && ( + + )} ); } diff --git a/src/app/state/settings.ts b/src/app/state/settings.ts index 1ac682685..390b4e83b 100644 --- a/src/app/state/settings.ts +++ b/src/app/state/settings.ts @@ -155,8 +155,13 @@ export interface Settings { mentionInReplies: boolean; showPersonaSetting: boolean; closeFoldersByDefault: boolean; - roomSidebarWidth: number; showRoomIcon: ShowRoomIcon; + roomSidebarWidth: number; + memberSidebarWidth: number; + threadSidebarWidth: number; + threadRootHeight: number; + vcmsgSidebarWidth: number; + widgetSidebarWidth: number; // furry stuff renderAnimals: boolean; @@ -276,9 +281,13 @@ export const defaultSettings: Settings = { mentionInReplies: true, showPersonaSetting: false, closeFoldersByDefault: false, - roomSidebarWidth: 256, showRoomIcon: ShowRoomIcon.Smart, - + roomSidebarWidth: 256, + memberSidebarWidth: 262, + threadSidebarWidth: 440, + threadRootHeight: 220, + vcmsgSidebarWidth: 399, + widgetSidebarWidth: 420, // furry stuff renderAnimals: true, From 1fd0981231589d90c2e7197e64e60459023a09f6 Mon Sep 17 00:00:00 2001 From: Shea Duma Date: Fri, 8 May 2026 07:30:44 +0300 Subject: [PATCH 08/10] added a unique selector for all the panels in the settings --- .../features/settings/cosmetics/Themes.tsx | 111 +++++++++++------- 1 file changed, 66 insertions(+), 45 deletions(-) diff --git a/src/app/features/settings/cosmetics/Themes.tsx b/src/app/features/settings/cosmetics/Themes.tsx index 14767f079..25cc2612a 100644 --- a/src/app/features/settings/cosmetics/Themes.tsx +++ b/src/app/features/settings/cosmetics/Themes.tsx @@ -1,5 +1,5 @@ import type { ChangeEventHandler, KeyboardEventHandler } from 'react'; -import { type MouseEventHandler, useState } from 'react'; +import { type MouseEventHandler, useEffect, useMemo, useState } from 'react'; import { Box, Button, @@ -36,7 +36,7 @@ import { ThemeAppearanceSection } from './ThemeAppearanceSection'; import { stopPropagation } from '$utils/keyboard'; import FocusTrap from 'focus-trap-react'; import { useShowRoomIcon } from '$hooks/useShowRoomIcon'; -import type { PanelSizetItem} from '$hooks/usePanelSizes'; +import type { PanelSizetItem } from '$hooks/usePanelSizes'; import { usePanelSizeItems } from '$hooks/usePanelSizes'; function makeArboriumThemeOptions(kind?: 'light' | 'dark') { @@ -454,16 +454,59 @@ function PanelSelector({ ); } -function SidebarWidth({ - sidebarWidth, - setSidebarWidth, - sidebarSelector, -}: { - sidebarWidth: number; - setSidebarWidth: (arg0: number) => void; - sidebarSelector: string; -}) { - const [inputValue, setInputValue] = useState(sidebarWidth?.toString()); +function SidebarWidth({ sidebarSelector }: { sidebarSelector: string }) { + const [roomSidebarWidth, setRoomSidebarWidth] = useSetting(settingsAtom, 'roomSidebarWidth'); + const [memberSidebarWidth, setMemberSidebarWidth] = useSetting( + settingsAtom, + 'memberSidebarWidth' + ); + const [threadSidebarWidth, setThreadSidebarWidth] = useSetting( + settingsAtom, + 'threadSidebarWidth' + ); + const [threadRootHeight, setThreadRootHeight] = useSetting(settingsAtom, 'threadRootHeight'); + const [vcmsgSidebarWidth, setvcmsgSidebarWidth] = useSetting(settingsAtom, 'vcmsgSidebarWidth'); + const [widgetSidebarWidth, setWidgetSidebarWidth] = useSetting( + settingsAtom, + 'widgetSidebarWidth' + ); + + // Yandere style code but it works and is as straight forward as can be :shrug: + const getCurValue = useMemo(() => { + if (sidebarSelector === 'roomSidebarWidth') return roomSidebarWidth; + if (sidebarSelector === 'memberSidebarWidth') return memberSidebarWidth; + if (sidebarSelector === 'threadSidebarWidth') return threadSidebarWidth; + if (sidebarSelector === 'threadRootHeight') return threadRootHeight; + if (sidebarSelector === 'vcmsgSidebarWidth') return vcmsgSidebarWidth; + if (sidebarSelector === 'widgetSidebarWidth') return widgetSidebarWidth; + return undefined; + }, [ + sidebarSelector, + roomSidebarWidth, + memberSidebarWidth, + threadSidebarWidth, + threadRootHeight, + vcmsgSidebarWidth, + widgetSidebarWidth, + ]); + const [curValue, setCurValue] = useState(getCurValue); + const setValue = (value: number) => { + if (sidebarSelector === 'roomSidebarWidth') setRoomSidebarWidth(value); + if (sidebarSelector === 'memberSidebarWidth') setMemberSidebarWidth(value); + if (sidebarSelector === 'threadSidebarWidth') setThreadSidebarWidth(value); + if (sidebarSelector === 'threadRootHeight') setThreadRootHeight(value); + if (sidebarSelector === 'vcmsgSidebarWidth') setvcmsgSidebarWidth(value); + if (sidebarSelector === 'widgetSidebarWidth') setWidgetSidebarWidth(value); + }; + + useEffect(() => { + setInputValue(curValue?.toString()); + }, [curValue]); + useEffect(() => { + setCurValue(getCurValue); + }, [getCurValue]); + + const [inputValue, setInputValue] = useState(curValue?.toString()); const handleChange: ChangeEventHandler = (evt) => { const val = evt.target.value; @@ -471,14 +514,14 @@ function SidebarWidth({ const parsed = parseInt(val, 10); if (!Number.isNaN(parsed)) { - setSidebarWidth(parsed); + setValue(parsed); } }; const handleKeyDown: KeyboardEventHandler = (evt) => { if (isKeyHotkey('escape', evt)) { evt.stopPropagation(); - setInputValue(sidebarWidth.toString()); + setInputValue(curValue?.toString()); (evt.target as HTMLInputElement).blur(); } @@ -488,10 +531,9 @@ function SidebarWidth({ }; return ( - <> - {sidebarSelector} - ); } @@ -580,21 +620,6 @@ export function Appearance({ }: { onThemeBrowserOpenChange?: (open: boolean) => void; } = {}) { - const [roomSidebarWidth, setRoomSidebarWidth] = useSetting(settingsAtom, 'roomSidebarWidth'); - const [memberSidebarWidth, setMemberSidebarWidth] = useSetting( - settingsAtom, - 'memberSidebarWidth' - ); - const [threadSidebarWidth, setThreadSidebarWidth] = useSetting( - settingsAtom, - 'threadSidebarWidth' - ); - const [threadRootHeight, setThreadRootHeight] = useSetting(settingsAtom, 'threadRootHeight'); - const [vcmsgSidebarWidth, setvcmsgSidebarWidth] = useSetting(settingsAtom, 'vcmsgSidebarWidth'); - const [widgetSidebarWidth, setWidgetSidebarWidth] = useSetting( - settingsAtom, - 'widgetSidebarWidth' - ); const [sidebarSelector, setSidebarSelector] = useState('roomSidebarWidth'); const [twitterEmoji, setTwitterEmoji] = useSetting(settingsAtom, 'twitterEmoji'); const [customDMCards, setCustomDMCards] = useSetting(settingsAtom, 'customDMCards'); @@ -691,20 +716,16 @@ export function Appearance({ - - + + } /> From e563d527400560baa6ab07e0cbbbc445e55cdf24 Mon Sep 17 00:00:00 2001 From: Shea Duma Date: Fri, 8 May 2026 07:50:24 +0300 Subject: [PATCH 09/10] better compliance to landscape view limitations --- src/app/pages/client/direct/Direct.tsx | 9 ++++++++- src/app/pages/client/explore/Explore.tsx | 9 ++++++++- src/app/pages/client/home/Home.tsx | 9 ++++++++- src/app/pages/client/inbox/Inbox.tsx | 9 ++++++++- src/app/pages/client/space/Space.tsx | 9 ++++++++- 5 files changed, 40 insertions(+), 5 deletions(-) diff --git a/src/app/pages/client/direct/Direct.tsx b/src/app/pages/client/direct/Direct.tsx index 4ad59ac15..57749ae55 100644 --- a/src/app/pages/client/direct/Direct.tsx +++ b/src/app/pages/client/direct/Direct.tsx @@ -55,6 +55,7 @@ import { useDirectCreateSelected } from '$hooks/router/useDirectSelected'; import { useDirectRooms } from './useDirectRooms'; import { SidebarResizer } from '$pages/client/sidebar/SidebarResizer'; import { mobileOrTablet } from '$utils/user-agent'; +import { useScreenSizeContext, ScreenSize } from '$hooks/useScreenSize'; type DirectMenuProps = { requestClose: () => void; @@ -245,9 +246,15 @@ export function Direct() { closedCategories.has(categoryId) ); + const screenSize = useScreenSizeContext(); + return ( <> - + {noRoomToDisplay ? ( diff --git a/src/app/pages/client/explore/Explore.tsx b/src/app/pages/client/explore/Explore.tsx index eab6c4edd..904d7251e 100644 --- a/src/app/pages/client/explore/Explore.tsx +++ b/src/app/pages/client/explore/Explore.tsx @@ -34,6 +34,7 @@ import { settingsAtom } from '$state/settings'; import { useSetting } from '$state/hooks/settings'; import { mobileOrTablet } from '$utils/user-agent'; import { getMxIdServer } from '$utils/mxIdHelper'; +import { useScreenSizeContext, ScreenSize } from '$hooks/useScreenSize'; export function AddServer() { const mx = useMatrixClient(); @@ -170,9 +171,15 @@ export function Explore() { useEffect(() => { setCurWidth(roomSidebarWidth); }, [roomSidebarWidth]); + const screenSize = useScreenSizeContext(); + return ( <> - + diff --git a/src/app/pages/client/home/Home.tsx b/src/app/pages/client/home/Home.tsx index 53a5f40df..f25b984b7 100644 --- a/src/app/pages/client/home/Home.tsx +++ b/src/app/pages/client/home/Home.tsx @@ -64,6 +64,7 @@ import { JoinAddressPrompt } from '$components/join-address-prompt'; import { useHomeRooms } from './useHomeRooms'; import { SidebarResizer } from '$pages/client/sidebar/SidebarResizer'; import { mobileOrTablet } from '$utils/user-agent'; +import { ScreenSize, useScreenSizeContext } from '$hooks/useScreenSize'; type HomeMenuProps = { requestClose: () => void; @@ -248,9 +249,15 @@ export function Home() { closedCategories.has(categoryId) ); + const screenSize = useScreenSizeContext(); + return ( <> - + {noRoomToDisplay ? ( diff --git a/src/app/pages/client/inbox/Inbox.tsx b/src/app/pages/client/inbox/Inbox.tsx index 44401ed50..8b343527c 100644 --- a/src/app/pages/client/inbox/Inbox.tsx +++ b/src/app/pages/client/inbox/Inbox.tsx @@ -12,6 +12,7 @@ import { useSetting } from '$state/hooks/settings'; import { settingsAtom } from '$state/settings'; import { useEffect, useState } from 'react'; import { mobileOrTablet } from '$utils/user-agent'; +import { ScreenSize, useScreenSizeContext } from '$hooks/useScreenSize'; function InvitesNavItem() { const invitesSelected = useInboxInvitesSelected(); @@ -54,9 +55,15 @@ export function Inbox() { useEffect(() => { setCurWidth(roomSidebarWidth); }, [roomSidebarWidth]); + const screenSize = useScreenSizeContext(); + return ( <> - + diff --git a/src/app/pages/client/space/Space.tsx b/src/app/pages/client/space/Space.tsx index 49ec7454b..eb501e5ff 100644 --- a/src/app/pages/client/space/Space.tsx +++ b/src/app/pages/client/space/Space.tsx @@ -81,6 +81,7 @@ import { SwipeableOverlayWrapper } from '$components/SwipeableOverlayWrapper'; import { useCallEmbed } from '$hooks/useCallEmbed'; import { createDebugLogger } from '$utils/debugLogger'; import { SidebarResizer } from '$pages/client/sidebar/SidebarResizer'; +import { ScreenSize, useScreenSizeContext } from '$hooks/useScreenSize'; const debugLog = createDebugLogger('Space'); @@ -724,9 +725,15 @@ export function Space() { } }, [lastRoomId, spaceIdOrAlias, mx, navigate]); + const screenSize = useScreenSizeContext(); + return ( <> - + From 1e2351d06928b9a6b67e8ad3afda0aff293c55a5 Mon Sep 17 00:00:00 2001 From: Shea Duma Date: Fri, 8 May 2026 08:00:32 +0300 Subject: [PATCH 10/10] small forgotten change from testing --- src/app/features/room/RoomView.tsx | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/src/app/features/room/RoomView.tsx b/src/app/features/room/RoomView.tsx index c8af24c2a..a5329bed4 100644 --- a/src/app/features/room/RoomView.tsx +++ b/src/app/features/room/RoomView.tsx @@ -141,12 +141,7 @@ export function RoomView({ eventId }: { eventId?: string }) { return ( {(onBack) => ( - + {showCallView && (