Skip to content
This repository was archived by the owner on Jul 31, 2026. It is now read-only.
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions src/components/WorkspaceSelector/OrgList.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,7 @@ export function OrgList({
}, [selectedOrg, orgs])

return (
<div className="border-neutral-softest flex w-1/3 flex-col border-r">
<div className="border-neutral-softest flex h-full w-full flex-col @[640px]:w-1/3 @[640px]:border-r">
<SearchBox
inputRef={inputRef}
placeholder="Search organizations..."
Expand All @@ -69,7 +69,7 @@ export function OrgList({
onSelect={() => setSelectedOrg(org)}
value={`org-${org.slug}`}
className={cn(
'hover:!bg-accent/40 flex max-w-lg cursor-pointer flex-row gap-3 rounded-none p-4 text-base',
'hover:!bg-accent/40 flex cursor-pointer flex-row gap-3 rounded-none p-4 text-base @[640px]:max-w-lg',
!showRecents &&
selectedOrg?.slug === org.slug &&
'bg-accent/40 text-accent-foreground font-semibold'
Expand Down
20 changes: 8 additions & 12 deletions src/components/WorkspaceSelector/WorkspaceList.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -30,35 +30,34 @@ export function WorkspaceList({
const [search, setSearch] = useState('')
const inputRef = useRef<HTMLInputElement>(null)
const [filteredWorkspaces, setFilteredWorkspaces] = useState<Workspace[]>(
selectedOrg?.workspaces || []
selectedOrg.workspaces

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just sense-checking this doesn't crash anything when someone goes to a non-workspace page. Always a bit cautious when we remove guarding logic

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's true, if there are no orgs at all, this check can fail. Since I'm there anyway, I'll just refactor this part as it's a bit spaghetti right now.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

(We wouldn't be rendering this component if there are no orgs, which is why this is fine)

)

useEffect(() => {
setFilteredWorkspaces(
selectedOrg?.workspaces.filter((workspace) =>
selectedOrg.workspaces.filter((workspace) =>
filterWorkspaceFunc(workspace, search)
)
)
}, [search, selectedOrg?.workspaces, filterWorkspaceFunc])
}, [search, selectedOrg.workspaces, filterWorkspaceFunc])

useEffect(() => {
if (selectedWorkspace && virtuoso.current) {
const index = selectedOrg?.workspaces.findIndex(
const index = selectedOrg.workspaces.findIndex(
(workspace) => workspace.slug === selectedWorkspace.slug
)

// wait for the ref to update
setTimeout(() => {
virtuoso.current?.scrollToIndex({
index,
behavior: selectedOrg?.workspaces.length < 10 ? 'smooth' : 'auto',
behavior: selectedOrg.workspaces.length < 10 ? 'smooth' : 'auto',
})
}, 100)
}
}, [selectedWorkspace, selectedOrg?.workspaces])
}, [selectedWorkspace, selectedOrg.workspaces])

return (
<div className="flex w-2/3 flex-col">
<div className="flex h-full w-full flex-col @[640px]:w-2/3">
<SearchBox
inputRef={inputRef}
placeholder="Search workspaces..."
Expand All @@ -83,10 +82,7 @@ export function WorkspaceList({
workspace={workspace}
selectedOrg={selectedOrg}
handleSelect={handleSelect}
isSelected={
selectedOrg.id === selectedOrg.id &&
selectedWorkspace?.id === workspace.id
}
isSelected={selectedWorkspace?.id === workspace.id}
/>
)}
/>
Expand Down
99 changes: 73 additions & 26 deletions src/components/WorkspaceSelector/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import { Stack } from '../Stack'
import { CreateOrg } from './CreateOrg'
import { Heading } from '../Heading'
import { GlobalWorkspaceSelectorProps } from '@/types'
import { cn } from '@/lib/utils'

export interface Org {
id: string
Expand Down Expand Up @@ -225,10 +226,14 @@ export function WorkspaceSelector({
)
}, [startTransition])

const backToOrgSelector = React.useCallback(() => {
setSelectedOrg(null)
}, [])

return (
<div
ref={containerRef}
style={{ height }}
style={{ height, containerType: 'inline-size' }}
className="workspace-selector border-neutral-softest flex w-full flex-grow overflow-hidden rounded-md border"
>
{createOrgViewOpen ? (
Expand Down Expand Up @@ -261,7 +266,7 @@ export function WorkspaceSelector({
style={{
viewTransitionName: isTransitioning ? 'workspace-content' : '',
}}
className="flex w-full"
className="flex w-full flex-col @[640px]:flex-row"
>
<WorkspaceViewContents
orgs={orgs}
Expand All @@ -277,6 +282,7 @@ export function WorkspaceSelector({
handleSelectOrg={handleSelectOrg}
filterOrgFunc={filterOrgFunc}
filterWorkspaceFunc={filterWorkspaceFunc}
backToOrgSelector={backToOrgSelector}
/>
</div>
)}
Expand All @@ -298,6 +304,7 @@ interface WorkspaceViewContentsProps {
handleSelectOrg: (org: Org) => void
filterOrgFunc: (org: Org, search: string) => boolean
filterWorkspaceFunc: (workspace: Workspace, search: string) => boolean
backToOrgSelector: () => void
}

function WorkspaceViewContents({
Expand All @@ -314,14 +321,16 @@ function WorkspaceViewContents({
handleSelectOrg,
filterOrgFunc,
filterWorkspaceFunc,
backToOrgSelector,
}: WorkspaceViewContentsProps) {
const showRecentsView = React.useMemo(
() => recents.length > 0 && showRecents,
[recents, showRecents]
)
return (
<>
<div className="bg-surface-primary border-neutral-softest flex h-full flex-1/3 flex-col items-center justify-center border-r">
{/* Wide container layout (>= 640px) - Left sidebar with title */}
<div className="bg-surface-primary border-neutral-softest hidden h-full flex-1/3 flex-col items-center justify-center border-r @[640px]:flex">
<div className="flex h-full max-w-80 flex-col items-center justify-center px-8 text-center">
<Stack align="center" gap={4}>
<div className="flex h-16 w-16 items-center justify-center">
Expand All @@ -339,8 +348,15 @@ function WorkspaceViewContents({
</div>
</div>

<div className="w-full flex-2/3">
<Command shouldFilter={false}>
{/* Narrow container header (< 640px) */}
<div className="border-neutral-softest flex w-full items-center gap-3 border-b px-4 py-3 @[640px]:hidden">
<Logo variant="icon" className="size-8" />
<Heading variant="xs">Select workspace</Heading>
</div>

{/* Main content area */}
<div className="flex h-full w-full flex-1 flex-col overflow-hidden @[640px]:flex-2/3">
<Command shouldFilter={false} className="h-full w-full">
{showRecentsView ? (
<div className="flex w-full flex-grow flex-row">
<OrgList
Expand All @@ -363,27 +379,58 @@ function WorkspaceViewContents({
/>
</div>
) : orgs.length > 0 ? (
<div className="flex h-full flex-row">
<OrgList
orgs={orgs}
selectedOrg={selectedOrg}
setSelectedOrg={handleSelectOrg}
onSelectRecent={() => setShowRecents(true)}
showRecents={showRecents}
enableRecents={recents.length > 0}
filterOrgFunc={(org, search) => filterOrgFunc(org, search)}
/>
<WorkspaceList
selectedOrg={selectedOrg!}
handleCreateViewOpen={handleCreateWorkspaceViewOpen}
handleSelect={(org, workspace) =>
handleSelect(org, workspace, false)
}
selectedWorkspace={selectedWorkspace}
filterWorkspaceFunc={(workspace, search) =>
filterWorkspaceFunc(workspace, search)
}
/>
<div className="flex h-full w-full flex-1 flex-col @[640px]:flex-row">
{/* Back button - narrow only, when org selected */}
<div
className={cn(
'border-neutral-softest flex-shrink-0 border-b p-3 @[640px]:hidden',
selectedOrg ? 'flex' : 'hidden'
)}
>
<button
onClick={backToOrgSelector}
className="text-body-sm text-link-primary hover:text-link-secondary flex items-center gap-2"
>
<span>←</span>
<span>Back to organizations</span>
</button>
</div>

{/* OrgList - always in wide, only when no org selected in narrow */}
<div
className={cn(
selectedOrg ? 'hidden @[640px]:contents' : 'contents'
)}
>
<OrgList
orgs={orgs}
selectedOrg={selectedOrg}
setSelectedOrg={handleSelectOrg}
onSelectRecent={() => setShowRecents(true)}
showRecents={showRecents}
enableRecents={recents.length > 0}
filterOrgFunc={(org, search) => filterOrgFunc(org, search)}
/>
</div>

{/* WorkspaceList - always in wide, only when org selected in narrow */}
<div
className={cn(
selectedOrg ? 'contents' : 'hidden @[640px]:contents'
)}
>
<WorkspaceList
selectedOrg={selectedOrg ?? orgs[0]}
handleCreateViewOpen={handleCreateWorkspaceViewOpen}
handleSelect={(org, workspace) =>
handleSelect(org, workspace, false)
}
selectedWorkspace={selectedWorkspace}
filterWorkspaceFunc={(workspace, search) =>
filterWorkspaceFunc(workspace, search)
}
/>
</div>
</div>
) : (
<CommandEmpty
Expand Down