Files
supabase/apps/studio/components/layouts/ExplorerLayout/ExplorerLayout.constants.tsx
Saxon FletcherandClaude Opus 5 7880c2f079 fix(studio): explorer chat and notebook layout refinements (#50453)
Five layout fixes across Explorer chat, notebooks, and the sidebar.

### Chat

- **Conversation fade overlapped the scrollbar.** The top and bottom
gradients are positioned against the conversation's padding box, which
includes the scroll container's scrollbar gutter, so `inset-x-0` painted
them over the scrollbar. They now stop at the conversation's content
gutter, which `Conversation` owns for both the content and the fades.
- **Composer background bled past the input's radius.** The form paints
the surface behind the textarea but had no radius of its own, so its
square corners showed outside the `rounded-lg` input. It now shares the
radius.
- **Message parts used two different widths.** Wide parts come down to
`max-w-3xl` so every part shares a column, matching `AssistantQueryCell`
and `AssistantNotebookPreview`. `isWide` / `isWideMessagePart` stay in
place with both widths equal, so a part can diverge again later without
rebuilding the mechanism.

### Notebooks

- **Cell controls sat at the container edge.** Each cell centred itself
at its own max width while the grip and add-cell button stayed at the
far left of the full-width row, leaving a large gap. `SortableSection`
takes a `sectionWidth` and carries its control gutter twice — once as
the controls, once as padding on the other side — so the section stays
centred with its controls immediately beside it. Cell widths are
unchanged (prose `48rem`, query `72rem`); set them equal and the two
cell types' controls line up on their own.

The controls stay in flow rather than floating in an outside gutter, so
on a viewport narrower than the cap the row just fills the space instead
of clipping the controls into the padding.

### Sidebar

- **Search icon didn't line up with the menu row icons.** The row box
already sits flush with the search input's box, so rows moved from
`pl-3` to `pl-2` to put their icons on the same 8px offset the search
icon uses. Spacing between the input and the list now matches the 12px
side padding.

### Testing

`pnpm --filter studio run typecheck`, Prettier, and 378 tests across
`Explorer`, `ProjectHome`, `AIAssistantPanel`, and `ExplorerLayout`
pass. ESLint warning counts are unchanged from master.

These were reasoned from layout rather than checked in a browser, so
they're worth a look on a preview before merge.

<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->

## Summary by CodeRabbit

* **UI Improvements**
* Updated Explorer layouts with flexible, configurable widths for
notebook and query sections.
  * Refined navigation spacing and padding across Explorer views.
* Centered and standardized AI Assistant preview, query, and message
content widths.
  * Improved chat form styling with rounded corners.
* Adjusted conversation spacing and fade overlays to avoid overlapping
the scrollbar.
* Preserved full-width behavior where appropriate while keeping controls
aligned.

* **Tests**
* Updated layout tests to reflect revised width and alignment behavior.

<!-- end of auto-generated comment: release notes by coderabbit.ai -->

---------

Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
2026-09-17 11:39:30 +08:00

79 lines
2.3 KiB
TypeScript

import { motion } from 'framer-motion'
import { MessageSquare, NotebookText } from 'lucide-react'
import { type ComponentType, type PropsWithChildren } from 'react'
import { cn } from 'ui'
import { InnerSideBarFilters, InnerSideBarFilterSearchInput } from 'ui-patterns/InnerSideMenu'
export type ExplorerResourceType = 'notebook' | 'chat'
export const LEVEL_OFFSET = 8
export const LEVEL_TRANSITION = { duration: 0.09, ease: 'easeOut' } as const
export const EXPLORER_SECTIONS: Array<{
type: ExplorerResourceType
label: string
icon: ComponentType<{ size?: number; className?: string }>
searchPlaceholder: string
}> = [
{
type: 'notebook',
label: 'Notebooks',
icon: NotebookText,
searchPlaceholder: 'Search notebooks',
},
{ type: 'chat', label: 'Chats', icon: MessageSquare, searchPlaceholder: 'Search chats' },
]
export const rowClassName = (isActive: boolean) =>
cn(
'group relative flex h-7 w-full items-center gap-2 rounded-md pl-2 pr-2 text-sm',
isActive
? 'bg-selection text-foreground'
: 'text-foreground-light hover:bg-surface-200 hover:text-foreground'
)
export const ExplorerNavResourceWrapper = ({
type,
label,
className,
children,
search,
setSearch,
}: PropsWithChildren<{
type: ExplorerResourceType
label?: string
className?: string
search?: string
setSearch: (value: string) => void
}>) => {
const searchPlaceholder = EXPLORER_SECTIONS.find((x) => x.type === type)?.searchPlaceholder
return (
<motion.div
role="group"
aria-label={label}
initial={{ opacity: 0, x: LEVEL_OFFSET }}
animate={{ opacity: 1, x: 0 }}
exit={{ opacity: 0, x: LEVEL_OFFSET }}
transition={LEVEL_TRANSITION}
className={cn('absolute inset-0 flex flex-col', className)}
>
<div className="px-3 pt-3">
<span id="explorer-sidebar-search-label" className="sr-only">
{searchPlaceholder}
</span>
<InnerSideBarFilters className="w-full gap-0 p-0">
<InnerSideBarFilterSearchInput
name="explorer-sidebar-search"
value={search}
placeholder={searchPlaceholder}
aria-labelledby="explorer-sidebar-search-label"
onChange={(event) => setSearch(event.target.value)}
/>
</InnerSideBarFilters>
</div>
{children}
</motion.div>
)
}