feat: floating glass navigation and action bars in modals
Replace inline Save/Discard/Reset buttons with sticky glass-bubble pills that float at the bottom of scrollable modal content. Make SpaceSettings tab sidebar sticky with glass material. Convert RoleEditView "Back to roles" into a sticky glass pill at the top. UserSettings Log Out + Save always visible in a glass pill with separator. Also includes floating position hook and popover/tooltip improvements from prior work.
This commit is contained in:
@@ -196,20 +196,24 @@ function StreamingLimitsPanel() {
|
||||
<div className="p-2 bg-status-online/10 border border-status-online/30 rounded text-status-online text-sm">Settings saved</div>
|
||||
)}
|
||||
{hasChanges && (
|
||||
<div className="flex items-center gap-2">
|
||||
<button
|
||||
onClick={handleSave}
|
||||
disabled={saving}
|
||||
className="px-4 py-1.5 bg-accent-primary hover:bg-accent-primary/80 text-white text-sm font-medium rounded transition-colors disabled:opacity-50"
|
||||
>
|
||||
{saving ? 'Saving...' : 'Save'}
|
||||
</button>
|
||||
<button
|
||||
onClick={handleReset}
|
||||
className="px-4 py-1.5 text-sm text-txt-tertiary hover:text-txt-secondary transition-colors"
|
||||
>
|
||||
Reset
|
||||
</button>
|
||||
<div className="sticky bottom-0 z-10 pointer-events-none">
|
||||
<div className="flex justify-center pt-3 pb-1">
|
||||
<div className="glass-bubble rounded-full px-4 py-2 flex items-center gap-2 animate-slide-up pointer-events-auto">
|
||||
<button
|
||||
onClick={handleReset}
|
||||
className="px-3 py-1 text-sm text-txt-tertiary hover:text-txt-secondary transition-colors"
|
||||
>
|
||||
Reset
|
||||
</button>
|
||||
<button
|
||||
onClick={handleSave}
|
||||
disabled={saving}
|
||||
className="px-3 py-1.5 bg-accent-primary hover:bg-accent-primary/80 text-white text-sm font-medium rounded-full transition-colors disabled:opacity-50"
|
||||
>
|
||||
{saving ? 'Saving...' : 'Save'}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
@@ -331,20 +335,24 @@ function DiscoveryPanel({ spaceId }: { spaceId: string }) {
|
||||
<div className="p-2 bg-status-online/10 border border-status-online/30 rounded text-status-online text-sm">Settings saved</div>
|
||||
)}
|
||||
{hasChanges && (
|
||||
<div className="flex items-center gap-2">
|
||||
<button
|
||||
onClick={handleSave}
|
||||
disabled={saving}
|
||||
className="px-4 py-1.5 bg-accent-primary hover:bg-accent-primary/80 text-white text-sm font-medium rounded transition-colors disabled:opacity-50"
|
||||
>
|
||||
{saving ? 'Saving...' : 'Save'}
|
||||
</button>
|
||||
<button
|
||||
onClick={handleReset}
|
||||
className="px-4 py-1.5 text-sm text-txt-tertiary hover:text-txt-secondary transition-colors"
|
||||
>
|
||||
Reset
|
||||
</button>
|
||||
<div className="sticky bottom-0 z-10 pointer-events-none">
|
||||
<div className="flex justify-center pt-3 pb-1">
|
||||
<div className="glass-bubble rounded-full px-4 py-2 flex items-center gap-2 animate-slide-up pointer-events-auto">
|
||||
<button
|
||||
onClick={handleReset}
|
||||
className="px-3 py-1 text-sm text-txt-tertiary hover:text-txt-secondary transition-colors"
|
||||
>
|
||||
Reset
|
||||
</button>
|
||||
<button
|
||||
onClick={handleSave}
|
||||
disabled={saving}
|
||||
className="px-3 py-1.5 bg-accent-primary hover:bg-accent-primary/80 text-white text-sm font-medium rounded-full transition-colors disabled:opacity-50"
|
||||
>
|
||||
{saving ? 'Saving...' : 'Save'}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
@@ -479,7 +487,7 @@ export function SpaceSettingsModal() {
|
||||
if (!space || !currentSpaceId) return null;
|
||||
|
||||
const tabClass = (t: typeof tab) =>
|
||||
`w-full text-left px-3 py-1.5 rounded text-sm transition-colors ${
|
||||
`w-full text-left px-2.5 py-1.5 rounded text-sm transition-colors ${
|
||||
tab === t ? 'bg-interactive-selected text-txt-primary' : 'text-txt-tertiary hover:text-txt-secondary hover:bg-interactive-hover'
|
||||
}`;
|
||||
|
||||
@@ -487,28 +495,30 @@ export function SpaceSettingsModal() {
|
||||
<Modal isOpen={isOpen} onClose={closeModal} title="Space Settings" maxWidth="max-w-xl">
|
||||
<div className="flex gap-4">
|
||||
{/* Tabs */}
|
||||
<div className="w-32 flex-shrink-0 space-y-1">
|
||||
<button onClick={() => setTab('overview')} className={tabClass('overview')}>
|
||||
Overview
|
||||
</button>
|
||||
{canManageSpace && (
|
||||
<button onClick={() => setTab('discovery')} className={tabClass('discovery')}>
|
||||
Discovery
|
||||
<div className="w-32 flex-shrink-0 sticky top-0 self-start z-10">
|
||||
<div className="glass-bubble rounded-lg p-1.5 space-y-0.5">
|
||||
<button onClick={() => setTab('overview')} className={tabClass('overview')}>
|
||||
Overview
|
||||
</button>
|
||||
)}
|
||||
<button onClick={() => setTab('members')} className={tabClass('members')}>
|
||||
Members
|
||||
</button>
|
||||
{canManageRoles && (
|
||||
<button onClick={() => setTab('roles')} className={tabClass('roles')}>
|
||||
Roles
|
||||
{canManageSpace && (
|
||||
<button onClick={() => setTab('discovery')} className={tabClass('discovery')}>
|
||||
Discovery
|
||||
</button>
|
||||
)}
|
||||
<button onClick={() => setTab('members')} className={tabClass('members')}>
|
||||
Members
|
||||
</button>
|
||||
)}
|
||||
{isAdmin && (
|
||||
<button onClick={() => setTab('streaming')} className={tabClass('streaming')}>
|
||||
Streaming
|
||||
</button>
|
||||
)}
|
||||
{canManageRoles && (
|
||||
<button onClick={() => setTab('roles')} className={tabClass('roles')}>
|
||||
Roles
|
||||
</button>
|
||||
)}
|
||||
{isAdmin && (
|
||||
<button onClick={() => setTab('streaming')} className={tabClass('streaming')}>
|
||||
Streaming
|
||||
</button>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Content */}
|
||||
|
||||
@@ -172,20 +172,25 @@ export function UserSettingsModal() {
|
||||
{/* Connected Instances */}
|
||||
<ConnectedInstances />
|
||||
|
||||
<div className="flex items-center justify-between pt-2">
|
||||
<button
|
||||
onClick={handleLogout}
|
||||
className="px-4 py-2 text-sm text-txt-danger hover:bg-accent-rose/10 rounded transition-colors"
|
||||
>
|
||||
Log Out
|
||||
</button>
|
||||
<button
|
||||
onClick={handleSave}
|
||||
disabled={isLoading}
|
||||
className="px-4 py-2 bg-accent-primary hover:bg-accent-primary/80 text-white text-sm font-medium rounded transition-colors disabled:opacity-50"
|
||||
>
|
||||
{isLoading ? 'Saving...' : 'Save Changes'}
|
||||
</button>
|
||||
<div className="sticky bottom-0 z-10 pointer-events-none">
|
||||
<div className="flex justify-center pt-3 pb-1">
|
||||
<div className="glass-bubble rounded-full px-3 py-2 flex items-center gap-3 pointer-events-auto">
|
||||
<button
|
||||
onClick={handleLogout}
|
||||
className="px-3 py-1 text-sm text-txt-danger hover:bg-accent-rose/10 rounded-full transition-colors"
|
||||
>
|
||||
Log Out
|
||||
</button>
|
||||
<div className="w-px h-5 bg-white/10" />
|
||||
<button
|
||||
onClick={handleSave}
|
||||
disabled={isLoading}
|
||||
className="px-3 py-1.5 bg-accent-primary hover:bg-accent-primary/80 text-white text-sm font-medium rounded-full transition-colors disabled:opacity-50"
|
||||
>
|
||||
{isLoading ? 'Saving...' : 'Save Changes'}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</Modal>
|
||||
|
||||
@@ -229,20 +229,24 @@ export function OverviewPanel({ spaceId }: OverviewPanelProps) {
|
||||
<div className="p-2 bg-status-online/10 border border-status-online/30 rounded text-status-online text-sm">Settings saved</div>
|
||||
)}
|
||||
{canManageSpace && hasChanges && (
|
||||
<div className="flex items-center gap-2">
|
||||
<button
|
||||
onClick={handleSave}
|
||||
disabled={saving || uploadingIcon || !spaceName.trim()}
|
||||
className="px-4 py-1.5 bg-accent-primary hover:bg-accent-primary/80 text-white text-sm font-medium rounded transition-colors disabled:opacity-50"
|
||||
>
|
||||
{saving ? 'Saving...' : 'Save'}
|
||||
</button>
|
||||
<button
|
||||
onClick={handleDiscard}
|
||||
className="px-4 py-1.5 text-sm text-txt-tertiary hover:text-txt-secondary transition-colors"
|
||||
>
|
||||
Discard
|
||||
</button>
|
||||
<div className="sticky bottom-0 z-10 pointer-events-none">
|
||||
<div className="flex justify-center pt-3 pb-1">
|
||||
<div className="glass-bubble rounded-full px-4 py-2 flex items-center gap-2 animate-slide-up pointer-events-auto">
|
||||
<button
|
||||
onClick={handleDiscard}
|
||||
className="px-3 py-1 text-sm text-txt-tertiary hover:text-txt-secondary transition-colors"
|
||||
>
|
||||
Discard
|
||||
</button>
|
||||
<button
|
||||
onClick={handleSave}
|
||||
disabled={saving || uploadingIcon || !spaceName.trim()}
|
||||
className="px-3 py-1.5 bg-accent-primary hover:bg-accent-primary/80 text-white text-sm font-medium rounded-full transition-colors disabled:opacity-50"
|
||||
>
|
||||
{saving ? 'Saving...' : 'Save'}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
|
||||
@@ -242,15 +242,17 @@ function RoleEditView({ role, spaceId, onBack, onDeleted }: RoleEditViewProps) {
|
||||
return (
|
||||
<div className="space-y-4">
|
||||
{/* Back button */}
|
||||
<button
|
||||
onClick={onBack}
|
||||
className="flex items-center gap-1 text-sm text-txt-tertiary hover:text-txt-secondary transition-colors"
|
||||
>
|
||||
<svg className="w-4 h-4" fill="none" viewBox="0 0 24 24" stroke="currentColor" strokeWidth={2}>
|
||||
<path strokeLinecap="round" strokeLinejoin="round" d="M15 19l-7-7 7-7" />
|
||||
</svg>
|
||||
Back to roles
|
||||
</button>
|
||||
<div className="sticky top-0 z-10 pointer-events-none pb-3">
|
||||
<button
|
||||
onClick={onBack}
|
||||
className="glass-bubble rounded-full px-3 py-1.5 flex items-center gap-1 text-sm text-txt-tertiary hover:text-txt-secondary transition-colors pointer-events-auto"
|
||||
>
|
||||
<svg className="w-4 h-4" fill="none" viewBox="0 0 24 24" stroke="currentColor" strokeWidth={2}>
|
||||
<path strokeLinecap="round" strokeLinejoin="round" d="M15 19l-7-7 7-7" />
|
||||
</svg>
|
||||
Back to roles
|
||||
</button>
|
||||
</div>
|
||||
|
||||
{/* Role Name (not editable for @everyone) */}
|
||||
{!isEveryone && (
|
||||
@@ -362,20 +364,24 @@ function RoleEditView({ role, spaceId, onBack, onDeleted }: RoleEditViewProps) {
|
||||
<div className="p-2 bg-status-online/10 border border-status-online/30 rounded text-status-online text-sm">Role saved</div>
|
||||
)}
|
||||
{hasChanges && (
|
||||
<div className="flex items-center gap-2">
|
||||
<button
|
||||
onClick={handleSave}
|
||||
disabled={saving || (!isEveryone && !draftName.trim())}
|
||||
className="px-4 py-1.5 bg-accent-primary hover:bg-accent-primary/80 text-white text-sm font-medium rounded transition-colors disabled:opacity-50"
|
||||
>
|
||||
{saving ? 'Saving...' : 'Save'}
|
||||
</button>
|
||||
<button
|
||||
onClick={handleDiscard}
|
||||
className="px-4 py-1.5 text-sm text-txt-tertiary hover:text-txt-secondary transition-colors"
|
||||
>
|
||||
Discard
|
||||
</button>
|
||||
<div className="sticky bottom-0 z-10 pointer-events-none">
|
||||
<div className="flex justify-center pt-3 pb-1">
|
||||
<div className="glass-bubble rounded-full px-4 py-2 flex items-center gap-2 animate-slide-up pointer-events-auto">
|
||||
<button
|
||||
onClick={handleDiscard}
|
||||
className="px-3 py-1 text-sm text-txt-tertiary hover:text-txt-secondary transition-colors"
|
||||
>
|
||||
Discard
|
||||
</button>
|
||||
<button
|
||||
onClick={handleSave}
|
||||
disabled={saving || (!isEveryone && !draftName.trim())}
|
||||
className="px-3 py-1.5 bg-accent-primary hover:bg-accent-primary/80 text-white text-sm font-medium rounded-full transition-colors disabled:opacity-50"
|
||||
>
|
||||
{saving ? 'Saving...' : 'Save'}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user