From 34bccf6bf9e7c69becd0b54f0b2041f8518db35e Mon Sep 17 00:00:00 2001 From: Tymek Date: Sat, 14 Feb 2026 18:04:25 +0100 Subject: [PATCH 1/4] Add channel type selecor --- .../create-room/CreateRoomVoiceSelector.tsx | 68 +++++++++++++++++++ src/app/features/create-room/CreateRoom.tsx | 44 +++++++----- 2 files changed, 95 insertions(+), 17 deletions(-) create mode 100644 src/app/components/create-room/CreateRoomVoiceSelector.tsx diff --git a/src/app/components/create-room/CreateRoomVoiceSelector.tsx b/src/app/components/create-room/CreateRoomVoiceSelector.tsx new file mode 100644 index 00000000..9ed2a0f8 --- /dev/null +++ b/src/app/components/create-room/CreateRoomVoiceSelector.tsx @@ -0,0 +1,68 @@ +import React from 'react'; +import { Box, Text, Icon, Icons, config, IconSrc } from 'folds'; +import { SequenceCard } from '../sequence-card'; +import { SettingTile } from '../setting-tile'; + +export enum CreateRoomVoice { + TextRoom = 'text', + VoiceRoom = 'voice', +} +type CreateRoomVoiceSelectorProps = { + value?: CreateRoomVoice; + onSelect: (value: CreateRoomVoice) => void; + disabled?: boolean; + getIcon: (kind: CreateRoomVoice) => IconSrc; +}; +export function CreateRoomVoiceSelector({ + value, + onSelect, + disabled, + getIcon, +}: CreateRoomVoiceSelectorProps) { + return ( + + onSelect(CreateRoomVoice.TextRoom)} + disabled={disabled} + > + } + after={value === CreateRoomVoice.TextRoom && } + > + Text + + Send text messages, videos and GIFs. + + + + onSelect(CreateRoomVoice.VoiceRoom)} + disabled={disabled} + > + } + after={value === CreateRoomVoice.VoiceRoom && } + > + Voice + + A room optimized for voice calls. + + + + + ); +} diff --git a/src/app/features/create-room/CreateRoom.tsx b/src/app/features/create-room/CreateRoom.tsx index ef8e67be..6cf9f708 100644 --- a/src/app/features/create-room/CreateRoom.tsx +++ b/src/app/features/create-room/CreateRoom.tsx @@ -40,6 +40,10 @@ import { } from '../../components/create-room'; import { RoomType, StateEvent } from '../../../types/matrix/room'; import { IPowerLevels } from '../../hooks/usePowerLevels'; +import { + CreateRoomVoice, + CreateRoomVoiceSelector, +} from '../../components/create-room/CreateRoomVoiceSelector'; const getCreateRoomKindToIcon = (kind: CreateRoomKind) => { if (kind === CreateRoomKind.Private) return Icons.HashLock; @@ -47,12 +51,23 @@ const getCreateRoomKindToIcon = (kind: CreateRoomKind) => { return Icons.HashGlobe; }; +const getCreateRoomVoiceToIcon = (kind: CreateRoomVoice) => { + if (kind === CreateRoomVoice.VoiceRoom) return Icons.VolumeHigh; + return Icons.Hash; +}; + type CreateRoomFormProps = { defaultKind?: CreateRoomKind; + defaultVoice?: CreateRoomVoice; space?: Room; onCreate?: (roomId: string) => void; }; -export function CreateRoomForm({ defaultKind, space, onCreate }: CreateRoomFormProps) { +export function CreateRoomForm({ + defaultKind, + defaultVoice, + space, + onCreate, +}: CreateRoomFormProps) { const mx = useMatrixClient(); const alive = useAlive(); @@ -66,6 +81,7 @@ export function CreateRoomForm({ defaultKind, space, onCreate }: CreateRoomFormP const allowRestricted = space && restrictedSupported(selectedRoomVersion); + const [voice, setVoice] = useState(defaultVoice ?? CreateRoomVoice.TextRoom); const [kind, setKind] = useState( defaultKind ?? allowRestricted ? CreateRoomKind.Restricted : CreateRoomKind.Private ); @@ -74,7 +90,6 @@ export function CreateRoomForm({ defaultKind, space, onCreate }: CreateRoomFormP useAdditionalCreators(); const [federation, setFederation] = useState(true); const [encryption, setEncryption] = useState(false); - const [callRoom, setCallRoom] = useState(false); const [knock, setKnock] = useState(false); const [advance, setAdvance] = useState(false); @@ -123,7 +138,7 @@ export function CreateRoomForm({ defaultKind, space, onCreate }: CreateRoomFormP const powerOverrides: IPowerLevels = { events: {}, }; - if (callRoom) { + if (voice === CreateRoomVoice.VoiceRoom) { roomType = RoomType.Call; powerOverrides.events![StateEvent.GroupCallMemberPrefix] = 0; } @@ -150,6 +165,15 @@ export function CreateRoomForm({ defaultKind, space, onCreate }: CreateRoomFormP return ( + + Type + + Access - - - } - /> - {kind === CreateRoomKind.Public && } From 31e4a02d27a1da66fb5672a7e608cd41367c65d3 Mon Sep 17 00:00:00 2001 From: Tymek Date: Sat, 14 Feb 2026 18:14:39 +0100 Subject: [PATCH 2/4] Add option for voice rooms, which for now sets the default selected option in the creation modal --- src/app/features/create-room/CreateRoomModal.tsx | 5 +++-- src/app/features/lobby/SpaceItem.tsx | 16 +++++++++++++--- src/app/state/createRoomModal.ts | 2 ++ src/app/state/hooks/createRoomModal.ts | 7 ++++--- 4 files changed, 22 insertions(+), 8 deletions(-) diff --git a/src/app/features/create-room/CreateRoomModal.tsx b/src/app/features/create-room/CreateRoomModal.tsx index c9919ba9..2f23c4e9 100644 --- a/src/app/features/create-room/CreateRoomModal.tsx +++ b/src/app/features/create-room/CreateRoomModal.tsx @@ -28,7 +28,8 @@ type CreateRoomModalProps = { state: CreateRoomModalState; }; function CreateRoomModal({ state }: CreateRoomModalProps) { - const { spaceId } = state; + const { spaceId, voice } = state; + console.log({ voice }); const closeDialog = useCloseCreateRoomModal(); const allJoinedRooms = useAllJoinedRoomsSet(); @@ -74,7 +75,7 @@ function CreateRoomModal({ state }: CreateRoomModalProps) { direction="Column" gap="500" > - + diff --git a/src/app/features/lobby/SpaceItem.tsx b/src/app/features/lobby/SpaceItem.tsx index 64a97900..fbc906b0 100644 --- a/src/app/features/lobby/SpaceItem.tsx +++ b/src/app/features/lobby/SpaceItem.tsx @@ -36,6 +36,7 @@ import { useMediaAuthentication } from '../../hooks/useMediaAuthentication'; import { useOpenCreateRoomModal } from '../../state/hooks/createRoomModal'; import { useOpenCreateSpaceModal } from '../../state/hooks/createSpaceModal'; import { AddExistingModal } from '../add-existing'; +import { CreateRoomVoice } from '../../components/create-room/CreateRoomVoiceSelector'; function SpaceProfileLoading() { return ( @@ -249,8 +250,8 @@ function AddRoomButton({ item }: { item: HierarchyItem }) { setCords(evt.currentTarget.getBoundingClientRect()); }; - const handleCreateRoom = () => { - openCreateRoomModal(item.roomId); + const handleCreateRoom = (voice?: CreateRoomVoice) => { + openCreateRoomModal(item.roomId, voice); setCords(undefined); }; @@ -281,10 +282,19 @@ function AddRoomButton({ item }: { item: HierarchyItem }) { radii="300" variant="Primary" fill="None" - onClick={handleCreateRoom} + onClick={() => handleCreateRoom(CreateRoomVoice.TextRoom)} > New Room + handleCreateRoom(CreateRoomVoice.VoiceRoom)} + > + New Voice Room + Existing Room diff --git a/src/app/state/createRoomModal.ts b/src/app/state/createRoomModal.ts index 81af5d5b..69a79f27 100644 --- a/src/app/state/createRoomModal.ts +++ b/src/app/state/createRoomModal.ts @@ -1,7 +1,9 @@ import { atom } from 'jotai'; +import { CreateRoomVoice } from '../components/create-room/CreateRoomVoiceSelector'; export type CreateRoomModalState = { spaceId?: string; + voice?: CreateRoomVoice; }; export const createRoomModalAtom = atom(undefined); diff --git a/src/app/state/hooks/createRoomModal.ts b/src/app/state/hooks/createRoomModal.ts index 15db7289..7e6151ff 100644 --- a/src/app/state/hooks/createRoomModal.ts +++ b/src/app/state/hooks/createRoomModal.ts @@ -1,6 +1,7 @@ import { useCallback } from 'react'; import { useAtomValue, useSetAtom } from 'jotai'; import { createRoomModalAtom, CreateRoomModalState } from '../createRoomModal'; +import { CreateRoomVoice } from '../../components/create-room/CreateRoomVoiceSelector'; export const useCreateRoomModalState = (): CreateRoomModalState | undefined => { const data = useAtomValue(createRoomModalAtom); @@ -19,13 +20,13 @@ export const useCloseCreateRoomModal = (): CloseCallback => { return close; }; -type OpenCallback = (space?: string) => void; +type OpenCallback = (space?: string, voice?: CreateRoomVoice) => void; export const useOpenCreateRoomModal = (): OpenCallback => { const setSettings = useSetAtom(createRoomModalAtom); const open: OpenCallback = useCallback( - (spaceId) => { - setSettings({ spaceId }); + (spaceId, voice) => { + setSettings({ spaceId, voice }); }, [setSettings] ); From 2a10347515b3d51fa81a683d79e0170e18fbbf9c Mon Sep 17 00:00:00 2001 From: Tymek Date: Sat, 14 Feb 2026 19:23:05 +0100 Subject: [PATCH 3/4] Add proper support for room selection from the enu --- src/app/features/create-room/CreateRoom.tsx | 34 +++++++++++-------- .../features/create-room/CreateRoomModal.tsx | 3 +- 2 files changed, 21 insertions(+), 16 deletions(-) diff --git a/src/app/features/create-room/CreateRoom.tsx b/src/app/features/create-room/CreateRoom.tsx index 6cf9f708..d607640e 100644 --- a/src/app/features/create-room/CreateRoom.tsx +++ b/src/app/features/create-room/CreateRoom.tsx @@ -45,10 +45,12 @@ import { CreateRoomVoiceSelector, } from '../../components/create-room/CreateRoomVoiceSelector'; -const getCreateRoomKindToIcon = (kind: CreateRoomKind) => { - if (kind === CreateRoomKind.Private) return Icons.HashLock; - if (kind === CreateRoomKind.Restricted) return Icons.Hash; - return Icons.HashGlobe; +const getCreateRoomKindToIcon = (kind: CreateRoomKind, voice?: CreateRoomVoice) => { + const isVoiceRoom = voice === CreateRoomVoice.VoiceRoom; + // TODO: Add VoiceLock and VoiceGlobe icons + if (kind === CreateRoomKind.Private) return isVoiceRoom ? Icons.Lock : Icons.HashLock; + if (kind === CreateRoomKind.Restricted) return isVoiceRoom ? Icons.VolumeHigh : Icons.Hash; + return isVoiceRoom ? Icons.VolumeHigh : Icons.HashGlobe; }; const getCreateRoomVoiceToIcon = (kind: CreateRoomVoice) => { @@ -165,15 +167,17 @@ export function CreateRoomForm({ return ( - - Type - - + {!space && ( + + Type + + + )} Access getCreateRoomKindToIcon(roomKind, voice)} /> Name } + before={} name="nameInput" autoFocus size="500" diff --git a/src/app/features/create-room/CreateRoomModal.tsx b/src/app/features/create-room/CreateRoomModal.tsx index 2f23c4e9..8b74eafb 100644 --- a/src/app/features/create-room/CreateRoomModal.tsx +++ b/src/app/features/create-room/CreateRoomModal.tsx @@ -23,6 +23,7 @@ import { } from '../../state/hooks/createRoomModal'; import { CreateRoomModalState } from '../../state/createRoomModal'; import { stopPropagation } from '../../utils/keyboard'; +import { CreateRoomVoice } from '../../components/create-room/CreateRoomVoiceSelector'; type CreateRoomModalProps = { state: CreateRoomModalState; @@ -58,7 +59,7 @@ function CreateRoomModal({ state }: CreateRoomModalProps) { }} > - New Room + New {voice === CreateRoomVoice.VoiceRoom && 'Voice '}Room From ee6875b95c9af8fb3e09969af7315cc7dbc50827 Mon Sep 17 00:00:00 2001 From: Tymek Date: Sun, 15 Feb 2026 01:31:30 +0100 Subject: [PATCH 4/4] Move enums to `types.ts` and change icons selection to use `getRoomIconSrc` --- .../create-room/CreateRoomKindSelector.tsx | 6 +----- .../create-room/CreateRoomVoiceSelector.tsx | 5 +---- src/app/components/create-room/index.ts | 1 + src/app/components/create-room/types.ts | 10 ++++++++++ src/app/components/create-room/utils.ts | 2 +- src/app/features/create-room/CreateRoom.tsx | 19 ++++++++++--------- .../features/create-room/CreateRoomModal.tsx | 3 +-- src/app/features/lobby/SpaceItem.tsx | 2 +- src/app/state/createRoomModal.ts | 2 +- src/app/state/hooks/createRoomModal.ts | 2 +- 10 files changed, 28 insertions(+), 24 deletions(-) create mode 100644 src/app/components/create-room/types.ts diff --git a/src/app/components/create-room/CreateRoomKindSelector.tsx b/src/app/components/create-room/CreateRoomKindSelector.tsx index 096954fb..ebc55e7f 100644 --- a/src/app/components/create-room/CreateRoomKindSelector.tsx +++ b/src/app/components/create-room/CreateRoomKindSelector.tsx @@ -2,12 +2,8 @@ import React from 'react'; import { Box, Text, Icon, Icons, config, IconSrc } from 'folds'; import { SequenceCard } from '../sequence-card'; import { SettingTile } from '../setting-tile'; +import { CreateRoomKind } from './types'; -export enum CreateRoomKind { - Private = 'private', - Restricted = 'restricted', - Public = 'public', -} type CreateRoomKindSelectorProps = { value?: CreateRoomKind; onSelect: (value: CreateRoomKind) => void; diff --git a/src/app/components/create-room/CreateRoomVoiceSelector.tsx b/src/app/components/create-room/CreateRoomVoiceSelector.tsx index 9ed2a0f8..f3862db2 100644 --- a/src/app/components/create-room/CreateRoomVoiceSelector.tsx +++ b/src/app/components/create-room/CreateRoomVoiceSelector.tsx @@ -2,11 +2,8 @@ import React from 'react'; import { Box, Text, Icon, Icons, config, IconSrc } from 'folds'; import { SequenceCard } from '../sequence-card'; import { SettingTile } from '../setting-tile'; +import { CreateRoomVoice } from './types'; -export enum CreateRoomVoice { - TextRoom = 'text', - VoiceRoom = 'voice', -} type CreateRoomVoiceSelectorProps = { value?: CreateRoomVoice; onSelect: (value: CreateRoomVoice) => void; diff --git a/src/app/components/create-room/index.ts b/src/app/components/create-room/index.ts index ffd9cb3d..a9c603b2 100644 --- a/src/app/components/create-room/index.ts +++ b/src/app/components/create-room/index.ts @@ -3,3 +3,4 @@ export * from './CreateRoomAliasInput'; export * from './RoomVersionSelector'; export * from './utils'; export * from './AdditionalCreatorInput'; +export * from './types'; diff --git a/src/app/components/create-room/types.ts b/src/app/components/create-room/types.ts new file mode 100644 index 00000000..e35af47a --- /dev/null +++ b/src/app/components/create-room/types.ts @@ -0,0 +1,10 @@ +export enum CreateRoomVoice { + TextRoom = 'text', + VoiceRoom = 'voice', +} + +export enum CreateRoomKind { + Private = 'private', + Restricted = 'restricted', + Public = 'public', +} diff --git a/src/app/components/create-room/utils.ts b/src/app/components/create-room/utils.ts index f10adf21..3d702f2a 100644 --- a/src/app/components/create-room/utils.ts +++ b/src/app/components/create-room/utils.ts @@ -7,11 +7,11 @@ import { Room, } from 'matrix-js-sdk'; import { RoomJoinRulesEventContent } from 'matrix-js-sdk/lib/types'; -import { CreateRoomKind } from './CreateRoomKindSelector'; import { RoomType, StateEvent } from '../../../types/matrix/room'; import { getViaServers } from '../../plugins/via-servers'; import { getMxIdServer } from '../../utils/matrix'; import { IPowerLevels } from '../../hooks/usePowerLevels'; +import { CreateRoomKind } from './types'; export const createRoomCreationContent = ( type: RoomType | undefined, diff --git a/src/app/features/create-room/CreateRoom.tsx b/src/app/features/create-room/CreateRoom.tsx index d607640e..9cc8bee8 100644 --- a/src/app/features/create-room/CreateRoom.tsx +++ b/src/app/features/create-room/CreateRoom.tsx @@ -1,5 +1,5 @@ import React, { FormEventHandler, useCallback, useEffect, useState } from 'react'; -import { MatrixError, Room } from 'matrix-js-sdk'; +import { MatrixError, Room, JoinRule } from 'matrix-js-sdk'; import { Box, Button, @@ -37,20 +37,21 @@ import { CreateRoomKindSelector, RoomVersionSelector, useAdditionalCreators, + CreateRoomVoice, } from '../../components/create-room'; import { RoomType, StateEvent } from '../../../types/matrix/room'; import { IPowerLevels } from '../../hooks/usePowerLevels'; -import { - CreateRoomVoice, - CreateRoomVoiceSelector, -} from '../../components/create-room/CreateRoomVoiceSelector'; +import { CreateRoomVoiceSelector } from '../../components/create-room/CreateRoomVoiceSelector'; +import { getRoomIconSrc } from '../../utils/room'; const getCreateRoomKindToIcon = (kind: CreateRoomKind, voice?: CreateRoomVoice) => { const isVoiceRoom = voice === CreateRoomVoice.VoiceRoom; - // TODO: Add VoiceLock and VoiceGlobe icons - if (kind === CreateRoomKind.Private) return isVoiceRoom ? Icons.Lock : Icons.HashLock; - if (kind === CreateRoomKind.Restricted) return isVoiceRoom ? Icons.VolumeHigh : Icons.Hash; - return isVoiceRoom ? Icons.VolumeHigh : Icons.HashGlobe; + + let joinRule: JoinRule = JoinRule.Public; + if (kind === CreateRoomKind.Restricted) joinRule = JoinRule.Restricted; + if (kind === CreateRoomKind.Private) joinRule = JoinRule.Knock; + + return getRoomIconSrc(Icons, isVoiceRoom ? RoomType.Call : undefined, joinRule); }; const getCreateRoomVoiceToIcon = (kind: CreateRoomVoice) => { diff --git a/src/app/features/create-room/CreateRoomModal.tsx b/src/app/features/create-room/CreateRoomModal.tsx index 8b74eafb..5fa9b6ca 100644 --- a/src/app/features/create-room/CreateRoomModal.tsx +++ b/src/app/features/create-room/CreateRoomModal.tsx @@ -23,14 +23,13 @@ import { } from '../../state/hooks/createRoomModal'; import { CreateRoomModalState } from '../../state/createRoomModal'; import { stopPropagation } from '../../utils/keyboard'; -import { CreateRoomVoice } from '../../components/create-room/CreateRoomVoiceSelector'; +import { CreateRoomVoice } from '../../components/create-room/types'; type CreateRoomModalProps = { state: CreateRoomModalState; }; function CreateRoomModal({ state }: CreateRoomModalProps) { const { spaceId, voice } = state; - console.log({ voice }); const closeDialog = useCloseCreateRoomModal(); const allJoinedRooms = useAllJoinedRoomsSet(); diff --git a/src/app/features/lobby/SpaceItem.tsx b/src/app/features/lobby/SpaceItem.tsx index fbc906b0..3dfbbdea 100644 --- a/src/app/features/lobby/SpaceItem.tsx +++ b/src/app/features/lobby/SpaceItem.tsx @@ -36,7 +36,7 @@ import { useMediaAuthentication } from '../../hooks/useMediaAuthentication'; import { useOpenCreateRoomModal } from '../../state/hooks/createRoomModal'; import { useOpenCreateSpaceModal } from '../../state/hooks/createSpaceModal'; import { AddExistingModal } from '../add-existing'; -import { CreateRoomVoice } from '../../components/create-room/CreateRoomVoiceSelector'; +import { CreateRoomVoice } from '../../components/create-room/types'; function SpaceProfileLoading() { return ( diff --git a/src/app/state/createRoomModal.ts b/src/app/state/createRoomModal.ts index 69a79f27..3d1a6803 100644 --- a/src/app/state/createRoomModal.ts +++ b/src/app/state/createRoomModal.ts @@ -1,5 +1,5 @@ import { atom } from 'jotai'; -import { CreateRoomVoice } from '../components/create-room/CreateRoomVoiceSelector'; +import { CreateRoomVoice } from '../components/create-room/types'; export type CreateRoomModalState = { spaceId?: string; diff --git a/src/app/state/hooks/createRoomModal.ts b/src/app/state/hooks/createRoomModal.ts index 7e6151ff..dfb15017 100644 --- a/src/app/state/hooks/createRoomModal.ts +++ b/src/app/state/hooks/createRoomModal.ts @@ -1,7 +1,7 @@ import { useCallback } from 'react'; import { useAtomValue, useSetAtom } from 'jotai'; import { createRoomModalAtom, CreateRoomModalState } from '../createRoomModal'; -import { CreateRoomVoice } from '../../components/create-room/CreateRoomVoiceSelector'; +import { CreateRoomVoice } from '../../components/create-room/types'; export const useCreateRoomModalState = (): CreateRoomModalState | undefined => { const data = useAtomValue(createRoomModalAtom);