Room management components for Hiyve — manage persistent rooms and discover live rooms in real-time, with fully customizable labels, icons, colors, and styles.
npx @hiyve/cli login
npm install @hiyve/react-rooms
import { RoomsList } from '@hiyve/react-rooms';
import { HiyveProvider } from '@hiyve/react';
function Dashboard() {
return (
<HiyveProvider generateRoomToken={getToken}>
<RoomsList
userId="user-123"
onStartRoom={(room) => navigate(`/room/${room.roomName}`)}
onCreateRoom={(room) => console.log('Created:', room.alias)}
/>
</HiyveProvider>
);
}
| Component | Description |
|---|---|
RoomsList |
Complete room management interface — displays rooms grid with create, edit, and delete |
RoomCard |
Individual room card with start, settings, copy link, and delete actions |
RoomSearchBar |
Search input with sort and filter dropdowns |
CreateRoomDialog |
Dialog form for creating a new room with feature toggles |
RoomSettingsDialog |
Dialog for editing room settings (password, waiting room, offline) |
DeleteConfirmDialog |
Confirmation dialog before deleting a room |
| Component | Description |
|---|---|
RoomDetailHeader |
Banner card with gradient header, room title, category badge, stats row, and action buttons |
| Component | Description |
|---|---|
ActiveRoomsList |
Real-time list of live rooms — connects via SSE, auto-updates as rooms appear/disappear |
ActiveRoomCard |
Individual active room card with live indicator, owner info, and join button |
| Hook | Description |
|---|---|
useRoomFilters |
Client-side search, sort, and filter state management for room arrays |
| Prop | Type | Default | Description |
|---|---|---|---|
userId |
string |
— | Required. User ID for room ownership and API operations |
onStartRoom |
(room: StoredRoom) => void |
— | Called when a room's start/join button is clicked |
onCreateRoom |
(room: StoredRoom) => void |
— | Called after a new room is successfully created |
onDeleteRoom |
(alias: string) => void |
— | Called after a room is successfully deleted |
onSelectRoom |
(room: StoredRoom) => void |
— | Called when a room card is clicked |
labels |
Partial<RoomsLabels> |
— | Combined labels for all sub-components |
icons |
Partial<RoomsIcons> |
— | Combined icons for all sub-components |
colors |
Partial<RoomsColors> |
— | Combined colors for all sub-components |
styles |
Partial<RoomsStyles> |
— | Combined MUI sx styles for all sub-components |
showCreateButton |
boolean |
true |
Whether to show the create room button |
showSearchBar |
boolean |
true |
Whether to show the search/filter bar |
showStaticOption |
boolean |
true |
Whether to show the static room option in the create dialog |
emptyStateContent |
ReactNode |
— | Custom content when there are no rooms |
headerContent |
ReactNode |
— | Custom content in the header area |
onError |
(error: Error) => void |
— | Callback when an error occurs during any operation |
renderProps |
RoomsListRenderProps |
— | Render props for replacing individual layout sections |
The core customization point. Set entity labels once, and all components update their text accordingly.
import { RoomsList } from '@hiyve/react-rooms';
// Rebrand as workspaces
<RoomsList
userId="user-1"
labels={{
entity: {
entity: 'Workspace',
entityPlural: 'Workspaces',
createEntity: 'Create Workspace',
noEntities: 'No workspaces yet',
deleteEntity: 'Delete Workspace',
editEntity: 'Edit Workspace',
searchEntities: 'Search workspaces',
},
}}
onStartRoom={(room) => navigate(`/workspace/${room.roomName}`)}
/>
// Rebrand as sessions
<RoomsList
userId="host-1"
labels={{
entity: {
entity: 'Session',
entityPlural: 'Sessions',
createEntity: 'Schedule Session',
noEntities: 'No sessions scheduled',
},
}}
/>
Every component supports customization through labels, icons, colors, and styles props. Pass a partial object to override only the values you need — unspecified keys use defaults.
import { RoomsList } from '@hiyve/react-rooms';
<RoomsList
userId="user-123"
labels={{
entity: { entity: 'Meeting', entityPlural: 'Meetings' },
card: { startButton: 'Join Now', linkCopied: 'Copied!' },
searchBar: { searchPlaceholder: 'Find meetings...' },
createDialog: { title: 'New Meeting', createButton: 'Schedule' },
settingsDialog: { title: 'Meeting Options', saveButton: 'Update' },
}}
/>
import { RoomsList } from '@hiyve/react-rooms';
// Dark theme
<RoomsList
userId="user-123"
colors={{
card: {
background: '#1e1e1e',
border: '#333',
hoverBackground: '#2a2a2a',
headerBackground: '#252525',
},
}}
/>
import { RoomsList } from '@hiyve/react-rooms';
import { Rocket, Trash2, Cog } from 'lucide-react';
<RoomsList
userId="user-123"
icons={{
card: {
startIcon: <Rocket size={18} />,
deleteIcon: <Trash2 size={18} />,
settingsIcon: <Cog size={18} />,
},
searchBar: {
searchIcon: <Search size={18} />,
},
}}
/>
import { RoomsList } from '@hiyve/react-rooms';
<RoomsList
userId="user-123"
styles={{
card: {
card: { borderRadius: 3, boxShadow: 2 },
header: { py: 1.5 },
actions: { justifyContent: 'flex-start' },
},
}}
/>
import { RoomsList } from '@hiyve/react-rooms';
import { Box, Typography } from '@mui/material';
<RoomsList
userId="user-123"
emptyStateContent={
<Box textAlign="center" py={6}>
<Typography variant="h6">Welcome!</Typography>
<Typography color="text.secondary">
Create your first room to get started.
</Typography>
</Box>
}
/>
Replace individual layout sections while keeping all orchestration logic (data fetching, dialog management, CRUD handlers) intact. Each render function receives the relevant state and action handlers.
| Render Prop | Replaces | Data Provided |
|---|---|---|
renderSearchBar |
Default search/sort/filter bar | Search query, sort/filter state and setters, clearFilters, activeFiltersCount |
renderGrid |
Default card grid | Filtered rooms array, selection state, onStart, onSelect, onSettings, onDelete |
renderCreateButton |
Default "Create Room" button | onCreate handler, entity labels |
renderEmptyState |
Default empty state | onCreate handler, isLoading, entity labels, showCreateButton |
Dialogs (create, settings, delete) are always managed internally — render props only affect layout sections.
import { RoomsList } from '@hiyve/react-rooms';
import { List, ListItem, ListItemText, IconButton } from '@mui/material';
import { PlayArrow, Settings, Delete } from '@mui/icons-material';
<RoomsList
userId="user-123"
onStartRoom={handleStart}
renderProps={{
renderGrid: ({ rooms, onStart, onSelect, onSettings, onDelete }) => (
<List>
{rooms.map((room) => (
<ListItem
key={room.alias}
onClick={() => onSelect(room)}
secondaryAction={
<>
<IconButton onClick={() => onSettings(room)}><Settings /></IconButton>
<IconButton onClick={() => onDelete(room)}><Delete /></IconButton>
</>
}
>
<ListItemText primary={room.alias} secondary={room.roomName} />
<IconButton onClick={() => onStart(room)}><PlayArrow /></IconButton>
</ListItem>
))}
</List>
),
}}
/>
import { Fab } from '@mui/material';
import { Add } from '@mui/icons-material';
<RoomsList
userId="user-123"
renderProps={{
renderCreateButton: ({ onCreate, entityLabels }) => (
<Fab color="primary" onClick={onCreate} sx={{ position: 'fixed', bottom: 24, right: 24 }}>
<Add />
</Fab>
),
}}
/>
import { RoomsList } from '@hiyve/react-rooms';
import { TextField } from '@mui/material';
<RoomsList
userId="user-123"
renderProps={{
renderSearchBar: ({ searchQuery, setSearchQuery, totalRooms }) => (
<TextField
fullWidth
placeholder={`Search ${totalRooms} rooms...`}
value={searchQuery}
onChange={(e) => setSearchQuery(e.target.value)}
/>
),
}}
/>
renderSearchBar ignores the showSearchBar boolean (still gated by rooms.length > 0)renderCreateButton ignores the showCreateButton booleanrenderEmptyState ignores the emptyStateContent ReactNoderenderGrid replaces the entire default gridA banner card for displaying room details with a gradient header, title, optional category badge, stats row, and action buttons. Suitable for room detail pages or dashboards.
import { RoomDetailHeader } from '@hiyve/react-rooms';
import GroupIcon from '@mui/icons-material/Group';
import FolderIcon from '@mui/icons-material/Folder';
import { Button } from '@mui/material';
function RoomDetail({ room }) {
return (
<RoomDetailHeader
room={room}
headerGradient={['#5d6b82', '#3d4a5c']}
categoryBadge={{ label: 'Team' }}
stats={[
{ icon: <GroupIcon fontSize="small" />, label: 'Members', value: '12' },
{ icon: <FolderIcon fontSize="small" />, label: 'Files', value: '45' },
]}
actions={<Button variant="contained">Start</Button>}
/>
);
}
| Prop | Type | Default | Description |
|---|---|---|---|
room |
StoredRoom |
(required) | Room data (displays alias or roomName as title, reads metadata.description) |
headerGradient |
[string, string] |
['#5d6b82', '#3d4a5c'] |
Gradient colors for the header banner [startColor, endColor] |
headerIcon |
ReactNode |
-- | Watermark icon rendered centered in the banner |
categoryBadge |
{ label: string; icon?: ReactNode } |
-- | Category badge shown in the banner |
stats |
RoomDetailStat[] |
-- | Stats to display below the banner |
actions |
ReactNode |
-- | Action buttons rendered in the banner |
isMobile |
boolean |
false |
Whether to use mobile layout (smaller title variant) |
labels |
Partial<RoomDetailHeaderLabels> |
-- | Custom labels |
sx |
SxProps<Theme> |
-- | MUI sx prop |
onError |
(error: Error) => void |
-- | Error callback |
| Property | Type | Description |
|---|---|---|
icon |
ReactNode |
Icon for the stat tile |
label |
string |
Stat label |
value |
string |
Stat value |
Guests can discover and join live rooms in real-time using targeted discovery. Room owners advertise their rooms via @hiyve/cloud and specify which users can see them. Only targeted users receive room events through the SSE stream.
Every advertised room requires a targetUserIds array — there is no broadcast mode. This ensures rooms are only visible to their intended audience.
import { ActiveRoomsList } from '@hiyve/react-rooms';
import { HiyveProvider } from '@hiyve/react';
import { CloudClient } from '@hiyve/cloud';
const cloudClient = new CloudClient({ cloudToken: 'ct_your_token' });
const userId = 'guest-1';
function Lobby() {
const [streamUrl, setStreamUrl] = useState<string>();
useEffect(() => {
cloudClient.getActiveRoomsStreamUrl(userId).then(setStreamUrl);
}, []);
if (!streamUrl) return null;
return (
<HiyveProvider generateRoomToken={getToken}>
<ActiveRoomsList
streamUrl={streamUrl}
onJoinRoom={(room) => store.joinRoom(room.name, userId)}
/>
</HiyveProvider>
);
}
import { CloudClient } from '@hiyve/cloud';
const cloudClient = new CloudClient({ cloudToken: 'ct_your_token' });
// After creating a room, advertise it to specific users
await store.createRoom('Team Standup', userId);
await cloudClient.advertiseRoom({
name: 'Team Standup',
ownerDisplayName: 'Karl',
targetUserIds: ['guest-1', 'guest-2', 'guest-3'],
metadata: { waitingRoom: true },
});
// Update who can see the room (e.g., add a late invitee)
await cloudClient.updateAdvertisedRoom('Team Standup', {
targetUserIds: ['guest-1', 'guest-2', 'guest-3', 'guest-4'],
});
// When leaving, remove from discovery
await store.leaveRoom();
await cloudClient.removeAdvertisedRoom('Team Standup');
Use getActiveRooms for a one-time query instead of a live stream:
// Fetch rooms targeted at a specific user
const rooms = await cloudClient.getActiveRooms(userId);
// Or fetch all rooms for the org (no userId filter)
const allRooms = await cloudClient.getActiveRooms();
| Prop | Type | Default | Description |
|---|---|---|---|
streamUrl |
string |
— | Required. SSE stream URL from await cloudClient.getActiveRoomsStreamUrl(userId) |
onJoinRoom |
(room: ActiveRoom) => void |
— | Called when a room's join button is clicked |
labels |
Partial<ActiveRoomsListLabels> |
— | Labels for the list (title, empty state, search placeholder) |
cardLabels |
Partial<ActiveRoomCardLabels> |
— | Labels passed to each card |
cardColors |
Partial<ActiveRoomCardColors> |
— | Colors passed to each card |
cardStyles |
Partial<ActiveRoomCardStyles> |
— | MUI sx styles passed to each card |
cardIcons |
Partial<ActiveRoomCardIcons> |
— | Icons passed to each card |
showSearch |
boolean |
true |
Whether to show the search bar |
showTitle |
boolean |
true |
Whether to show the title |
emptyStateContent |
ReactNode |
— | Custom content when there are no active rooms |
onError |
(error: Error) => void |
— | Callback when an error occurs |
renderCard |
(room, onJoin) => ReactNode |
— | Custom render function for each card |
| Prop | Type | Default | Description |
|---|---|---|---|
room |
ActiveRoom |
— | Required. The active room to display |
onJoin |
(room: ActiveRoom) => void |
— | Called when the join button is clicked |
labels |
Partial<ActiveRoomCardLabels> |
— | Custom labels (join button, live indicator, hosted-by prefix) |
colors |
Partial<ActiveRoomCardColors> |
— | Custom colors (background, border, live indicator) |
styles |
Partial<ActiveRoomCardStyles> |
— | MUI sx styles for card, content, and actions |
icons |
Partial<ActiveRoomCardIcons> |
— | Custom icons (join button icon) |
onError |
(error: Error) => void |
— | Error callback |
import { ActiveRoomsList } from '@hiyve/react-rooms';
import { Button, Typography, Paper } from '@mui/material';
<ActiveRoomsList
streamUrl={streamUrl}
onJoinRoom={handleJoin}
renderCard={(room, onJoin) => (
<Paper sx={{ p: 2 }}>
<Typography variant="h6">{room.name}</Typography>
<Typography color="text.secondary">
by {room.ownerDisplayName || room.owner}
</Typography>
<Button onClick={() => onJoin?.(room)}>Join</Button>
</Paper>
)}
/>
<ActiveRoomsList
streamUrl={streamUrl}
onJoinRoom={handleJoin}
labels={{
title: 'Live Sessions',
emptyState: 'No sessions running right now',
searchPlaceholder: 'Find a session...',
}}
cardLabels={{
joinButton: 'Enter',
liveIndicator: 'In Progress',
hostedBy: 'Led by',
}}
/>
| Default | camelCase Alias | Merge Function |
|---|---|---|
DEFAULT_ACTIVE_CARD_LABELS |
defaultActiveCardLabels |
mergeActiveCardLabels |
DEFAULT_ACTIVE_CARD_COLORS |
defaultActiveCardColors |
mergeActiveCardColors |
DEFAULT_ACTIVE_CARD_ICONS |
defaultActiveCardIcons |
mergeActiveCardIcons |
DEFAULT_ACTIVE_LIST_LABELS |
defaultActiveListLabels |
mergeActiveListLabels |
All components can be used individually for custom layouts:
import {
RoomCard,
RoomSearchBar,
CreateRoomDialog,
useRoomFilters,
} from '@hiyve/react-rooms';
import { useStoredRooms } from '@hiyve/react';
function CustomRoomGrid() {
const { rooms, fetchStoredRooms, addStoredRoom } = useStoredRooms();
const { searchQuery, setSearchQuery, sortBy, setSortBy,
filterBy, setFilterBy, processedRooms, clearFilters,
activeFiltersCount } = useRoomFilters(rooms);
const [createOpen, setCreateOpen] = useState(false);
useEffect(() => { fetchStoredRooms('user-123'); }, []);
return (
<>
<RoomSearchBar
searchQuery={searchQuery}
sortBy={sortBy}
filterBy={filterBy}
onSearchChange={setSearchQuery}
onSortChange={setSortBy}
onFilterChange={setFilterBy}
onClear={clearFilters}
activeFiltersCount={activeFiltersCount}
/>
{processedRooms.map((room) => (
<RoomCard
key={room.id}
room={room}
onStart={(r) => joinRoom(r.roomName)}
/>
))}
<CreateRoomDialog
open={createOpen}
onClose={() => setCreateOpen(false)}
onSave={async (opts) => {
await addStoredRoom(opts, 'user-123');
setCreateOpen(false);
}}
/>
</>
);
}
All defaults are exported for consumers who want to extend or reference them:
Entity Labels:
| Default | camelCase Alias | Merge Function | Factory |
|---|---|---|---|
DEFAULT_ENTITY_LABELS |
defaultEntityLabels |
mergeEntityLabels |
-- |
Component Labels:
| Default | camelCase Alias | Merge Function | Factory |
|---|---|---|---|
DEFAULT_SEARCH_BAR_LABELS |
defaultSearchBarLabels |
mergeSearchBarLabels |
createDefaultSearchBarLabels |
DEFAULT_CREATE_DIALOG_LABELS |
defaultCreateDialogLabels |
mergeCreateDialogLabels |
createDefaultCreateDialogLabels |
DEFAULT_SETTINGS_DIALOG_LABELS |
defaultSettingsDialogLabels |
mergeSettingsDialogLabels |
createDefaultSettingsDialogLabels |
DEFAULT_CARD_LABELS |
defaultCardLabels |
mergeCardLabels |
createDefaultCardLabels |
Icons and Colors:
| Default | camelCase Alias | Merge Function |
|---|---|---|
DEFAULT_CARD_COLORS |
defaultCardColors |
mergeCardColors |
DEFAULT_CARD_ICONS |
defaultCardIcons |
mergeCardIcons |
DEFAULT_SEARCH_BAR_ICONS |
defaultSearchBarIcons |
mergeSearchBarIcons |
Room Detail Header:
| Default | camelCase Alias | Merge Function |
|---|---|---|
DEFAULT_ROOM_DETAIL_HEADER_LABELS |
defaultRoomDetailHeaderLabels |
mergeRoomDetailHeaderLabels |
Factory functions accept entity labels and generate defaults dynamically -- so createDefaultCardLabels({ entity: 'Workspace', ... }) produces startButton: 'Start Workspace' automatically.
Both SCREAMING_SNAKE_CASE and camelCase aliases reference the same objects. Use whichever naming convention matches your codebase.
Rooms support two join modes:
// Show the static room option in the create dialog
<RoomsList
userId="user-123"
showStaticOption={true}
onStartRoom={(room) => {
if (room.roomLink) {
// Link-based: navigate to join URL
window.location.href = room.roomLink;
} else {
// Static: join by name
joinRoom(room.roomName);
}
}}
/>
Static rooms display a "Static" badge chip, while link-based rooms show a "Link" badge. The "Copy Link" action is only available for link-based rooms.
@hiyve/react (^2.0.0) — components must be rendered inside HiyveProvider@hiyve/core (^1.0.0) — state store types and StoredRoom interface@hiyve/cloud (^1.0.0) — required for active room discovery (CloudClient)@hiyve/rtc-client — WebRTC client (provided by HiyveProvider)@hiyve/utilities (^1.0.0)@mui/material (^5.0.0 || ^6.0.0) and @mui/icons-material@emotion/react (>=11) and @emotion/styled (>=11)react (^18.0.0)Proprietary - Hiyve SDK
@hiyve/react-rooms - Room management components for Hiyve SDK.
Remarks
Provides components for:
All labels are customizable — "Room" can be "Workspace", "Session", "Channel", etc.