feat: rebuild SpaceSettings with expanded layout and grouped sidebar

This commit is contained in:
Jannis Braun
2026-03-22 02:19:15 +01:00
parent d54de16ec8
commit b3cb84c6a2
@@ -266,11 +266,13 @@ function JoinRequestsSection({ spaceId }: { spaceId: string }) {
export function SpaceSettingsModal() { export function SpaceSettingsModal() {
const activeModal = useUIStore((s) => s.activeModal); const activeModal = useUIStore((s) => s.activeModal);
const closeModal = useUIStore((s) => s.closeModal); const closeModal = useUIStore((s) => s.closeModal);
const isMobile = useUIStore((s) => s.isMobile);
const currentSpaceId = useSpaceStore((s) => s.currentSpaceId); const currentSpaceId = useSpaceStore((s) => s.currentSpaceId);
const spaces = useSpaceStore((s) => s.spaces); const spaces = useSpaceStore((s) => s.spaces);
const spacePermissions = useSpaceStore((s) => s.spacePermissions); const spacePermissions = useSpaceStore((s) => s.spacePermissions);
const [tab, setTab] = useState<'overview' | 'discovery' | 'members' | 'roles' | 'bans'>('overview'); const [tab, setTab] = useState<'overview' | 'discovery' | 'members' | 'roles' | 'bans'>('overview');
const [mobileView, setMobileView] = useState<'tabs' | 'content'>('tabs');
const isOpen = activeModal === 'spaceSettings'; const isOpen = activeModal === 'spaceSettings';
const space = spaces.find(s => s.id === currentSpaceId); const space = spaces.find(s => s.id === currentSpaceId);
@@ -282,48 +284,114 @@ export function SpaceSettingsModal() {
if (!space || !currentSpaceId) return null; if (!space || !currentSpaceId) return null;
const tabClass = (t: typeof tab) => const tabClass = (t: typeof tab) =>
`w-full text-left px-2.5 py-1.5 rounded text-sm transition-colors ${ `w-full text-left px-3 py-2 rounded-md text-sm transition-colors ${
tab === t ? 'bg-interactive-selected text-txt-primary' : 'text-txt-tertiary hover:text-txt-secondary hover:bg-interactive-hover' tab === t ? 'bg-interactive-selected text-txt-primary font-medium' : 'text-txt-tertiary hover:text-txt-secondary hover:bg-interactive-hover'
}`; }`;
const handleTabClick = (t: typeof tab) => {
setTab(t);
if (isMobile) setMobileView('content');
};
return ( return (
<Modal isOpen={isOpen} onClose={closeModal} title="Space Settings" maxWidth="max-w-2xl" mobileStyle="fullscreen"> <Modal isOpen={isOpen} onClose={closeModal} size="settings" mobileStyle="fullscreen">
<div className="flex gap-4 h-[min(520px,70vh)]"> <div className="flex h-full">
{/* Tabs */} {/* Desktop Sidebar */}
<div className="w-32 flex-shrink-0 self-start z-10"> <div className="hidden md:flex w-52 flex-shrink-0 flex-col p-4 gap-3">
<div className="glass-bubble rounded-lg p-1.5 space-y-0.5"> {/* Space card */}
<button onClick={() => setTab('overview')} className={tabClass('overview')}> <div className="glass-bubble rounded-lg p-3 flex items-center gap-3">
Overview <Avatar
</button> src={space.icon}
name={space.name}
size={36}
userId={space.id}
/>
<div className="min-w-0">
<div className="text-sm font-medium text-txt-primary truncate">{space.name}</div>
</div>
</div>
{/* Nav list */}
<div className="glass-bubble rounded-lg p-2 flex-1 flex flex-col">
<div className="text-[10px] font-semibold text-txt-tertiary uppercase tracking-wider px-3 py-1">General</div>
<button onClick={() => handleTabClick('overview')} className={tabClass('overview')}>Overview</button>
{canManageSpace && ( {canManageSpace && (
<button onClick={() => setTab('discovery')} className={tabClass('discovery')}> <button onClick={() => handleTabClick('discovery')} className={tabClass('discovery')}>Discovery</button>
Discovery
</button>
)} )}
<button onClick={() => setTab('members')} className={tabClass('members')}>
Members <div className="border-t border-white/[0.04] my-2 mx-2" />
</button> <div className="text-[10px] font-semibold text-txt-tertiary uppercase tracking-wider px-3 py-1">Management</div>
<button onClick={() => handleTabClick('members')} className={tabClass('members')}>Members</button>
{canManageRoles && ( {canManageRoles && (
<button onClick={() => setTab('roles')} className={tabClass('roles')}> <button onClick={() => handleTabClick('roles')} className={tabClass('roles')}>Roles</button>
Roles
</button>
)} )}
{canBanMembers && ( {canBanMembers && (
<button onClick={() => setTab('bans')} className={tabClass('bans')}> <button onClick={() => handleTabClick('bans')} className={tabClass('bans')}>Bans</button>
Bans
</button>
)} )}
</div> </div>
</div> </div>
{/* Content */} {/* Mobile: Tab list */}
<div className="flex-1 min-w-0 overflow-y-auto scrollbar-thin"> {isMobile && mobileView === 'tabs' && (
{tab === 'overview' && <OverviewPanel spaceId={currentSpaceId} />} <div className="flex-1 overflow-y-auto p-4 space-y-3">
{tab === 'discovery' && canManageSpace && <DiscoveryPanel spaceId={currentSpaceId} />} {/* Mobile space card */}
{tab === 'members' && <MembersPanel spaceId={currentSpaceId} />} <div className="glass-bubble rounded-lg p-3 flex items-center gap-3">
{tab === 'roles' && canManageRoles && <RolesPanel spaceId={currentSpaceId} />} <Avatar
{tab === 'bans' && canBanMembers && <BansPanel spaceId={currentSpaceId} />} src={space.icon}
</div> name={space.name}
size={36}
userId={space.id}
/>
<div className="min-w-0">
<div className="text-sm font-medium text-txt-primary truncate">{space.name}</div>
</div>
</div>
<div className="glass-bubble rounded-lg p-2 space-y-0.5">
<div className="text-[10px] font-semibold text-txt-tertiary uppercase tracking-wider px-3 py-1">General</div>
<button onClick={() => handleTabClick('overview')} className={tabClass('overview')}>Overview</button>
{canManageSpace && (
<button onClick={() => handleTabClick('discovery')} className={tabClass('discovery')}>Discovery</button>
)}
<div className="border-t border-white/[0.04] my-2 mx-2" />
<div className="text-[10px] font-semibold text-txt-tertiary uppercase tracking-wider px-3 py-1">Management</div>
<button onClick={() => handleTabClick('members')} className={tabClass('members')}>Members</button>
{canManageRoles && (
<button onClick={() => handleTabClick('roles')} className={tabClass('roles')}>Roles</button>
)}
{canBanMembers && (
<button onClick={() => handleTabClick('bans')} className={tabClass('bans')}>Bans</button>
)}
</div>
</div>
)}
{/* Content area (desktop always, mobile only when viewing content) */}
{(!isMobile || mobileView === 'content') && (
<div className="flex-1 min-w-0 overflow-y-auto scrollbar-thin py-6">
<div className="px-6 max-w-[640px]">
{/* Mobile back button */}
{isMobile && (
<button
onClick={() => setMobileView('tabs')}
className="flex items-center gap-1.5 text-txt-tertiary hover:text-txt-secondary mb-4 text-sm"
aria-label="Back to space settings menu"
>
<svg width="16" height="16" viewBox="0 0 24 24" fill="currentColor">
<path d="M20 11H7.83l5.59-5.59L12 4l-8 8 8 8 1.41-1.41L7.83 13H20v-2z" />
</svg>
Space Settings
</button>
)}
{tab === 'overview' && <OverviewPanel spaceId={currentSpaceId} />}
{tab === 'discovery' && canManageSpace && <DiscoveryPanel spaceId={currentSpaceId} />}
{tab === 'members' && <MembersPanel spaceId={currentSpaceId} />}
{tab === 'roles' && canManageRoles && <RolesPanel spaceId={currentSpaceId} />}
{tab === 'bans' && canBanMembers && <BansPanel spaceId={currentSpaceId} />}
</div>
</div>
)}
</div> </div>
</Modal> </Modal>
); );