with one click
right-panel
Add, modify, or troubleshoot the right sidebar panel on admin pages
Install with Codex or Claude Copy this prompt, paste it into Codex, Claude, or another assistant, and let it review the skill page and install it for you.
Menu
Add, modify, or troubleshoot the right sidebar panel on admin pages
Install with Codex or Claude Copy this prompt, paste it into Codex, Claude, or another assistant, and let it review the skill page and install it for you.
Based on SOC occupation classification
| name | right-panel |
| description | Add, modify, or troubleshoot the right sidebar panel on admin pages |
| metadata | {"audience":"developers","workflow":"feature-development"} |
I help you add, modify, or troubleshoot the right sidebar panel on admin pages. This includes creating panel header and body components, integrating them with the PanelWrapper layout component, and managing open/close state.
The right panel uses a props-based pattern: each page creates its own panel header and body components and passes them directly to PanelWrapper as props. There is no global store, no registration system, and no cross-route shared state. Each route fully owns its own panel content.
panels/tasks.tsx) exports a header component and a body componentpanelHeader and panelBody props to <PanelWrapper>PanelWrapper renders the header in a fixed h-11 border-b zone and the body in a scrollable area belowpanelHeader nor panelBody is provided, PanelWrapper renders just its children (no resizable group)| File | Purpose |
|---|---|
apps/start/src/components/generic/wrapper.tsx | PanelWrapper component (resizable panel layout + mobile Sheet) |
| File | Route | Content |
|---|---|---|
apps/start/src/components/admin/panels/tasks.tsx | /$orgId/tasks (tasks list) | Overview tiles, saved views/releases/priority tabs, view editing Sheet |
apps/start/src/components/admin/panels/task.tsx | /$orgId/tasks/$taskShortId (task detail) | Task metadata sidebar using TaskContentSideContent |
| Page file | Panel components used |
|---|---|
apps/start/src/components/pages/admin/orgid/tasks/index.tsx | TasksPanelHeader, TasksPanelContent |
apps/start/src/components/pages/admin/orgid/tasks/taskId.tsx | TaskPanelHeader, TaskPanelContent |
PanelWrapper is the layout component that pages opt into. It wraps the entire page content including PageHeader so the panel column sits beside everything at full height.
import { PanelWrapper } from "@/components/generic/wrapper";
| Prop | Type | Default | Description |
|---|---|---|---|
children | ReactNode | Required | Main page content (left side) |
isOpen | boolean | Required | Whether the panel is open |
setOpen | (open: boolean) => void | Required | Toggle callback |
panelHeader | ReactNode | — | Fixed header rendered at the top of the panel (h-11, border-b) |
panelBody | ReactNode | — | Body content rendered inside the scrollable panel area |
panelDefaultSize | number | 30 | Default panel width (percentage) |
panelMinSize | number | 15 | Minimum panel width (percentage) |
className | string | — | Classes on ResizablePanelGroup root |
contentClassName | string | — | Classes on the main (left) content panel |
ResizablePanelGroup with the page content on the left and panel on the right, separated by a ResizableHandleSheet overlay for the panelpanelHeader nor panelBody is provided, returns just {children} with no wrapping<SidebarContext.Provider value={{ id: "panel-right", isCollapsed: false }}> so doras-ui SidebarMenu* components work inside the panel"panel-right" entry in the sidebar store on mountWhen panel content is provided, PanelWrapper renders:
panelHeader is provided): h-11 shrink-0 border-b px-3 — height-matched to PageHeader.IdentitypanelBody is provided): flex flex-col gap-2 flex-1 overflow-y-auto p-2 — scrollableCreate apps/start/src/components/admin/panels/<your-panel>.tsx:
"use client";
// Optional: Fixed header component, height-matched to PageHeader.Identity
export function MyPanelHeader() {
return (
<div className="flex items-center gap-2 w-full flex-1 min-w-0">
{/* Avatar, title, action buttons, etc. */}
<span className="text-xs font-medium truncate">Panel Title</span>
</div>
);
}
// Interactive content component (manages its own state, uses context hooks)
export function MyPanelContent() {
// Use context hooks, state, click handlers, etc.
return <div>Panel content here</div>;
}
In the page component:
import { PanelWrapper } from "@/components/generic/wrapper";
import { PageHeader } from "@/components/generic/PageHeader";
import { MyPanelHeader, MyPanelContent } from "@/components/admin/panels/my-panel";
import { useLayoutOrganization } from "@/contexts/ContextOrg";
export default function MyPage() {
const { isProjectPanelOpen, setProjectPanelOpen } = useLayoutOrganization();
return (
<PanelWrapper
isOpen={isProjectPanelOpen}
setOpen={setProjectPanelOpen}
panelHeader={<MyPanelHeader />}
panelBody={<MyPanelContent />}
>
<div className="relative flex flex-col h-full max-h-full">
<PageHeader>
<PageHeader.Identity ... />
<PageHeader.Toolbar
right={
<>
{/* Panel toggle button */}
<Button
variant="accent"
className="gap-2 h-6 w-fit bg-accent border-transparent p-1"
onClick={() => setProjectPanelOpen(!isProjectPanelOpen)}
>
{isProjectPanelOpen ? (
<IconLayoutSidebarRightFilled />
) : (
<IconLayoutSidebarRight />
)}
</Button>
</>
}
/>
</PageHeader>
<div className="flex-1 overflow-y-auto h-full flex flex-col relative">
{/* Page content */}
</div>
</div>
</PanelWrapper>
);
}
Critical: PanelWrapper must wrap the entire page content including PageHeader. The panel column sits beside everything at full height, not below the header.
For panels where users click tiles/tabs to trigger actions (like filtering), the component manages its own useState and calls shared hooks like useTaskViewManager.
export function TasksPanelContent() {
const [editingView, setEditingView] = useState(null);
const { applyFilter, selectView, clearView } = useTaskViewManager(views);
return (
<div className="flex flex-col gap-3 w-full">
{/* Clickable tiles */}
<Tile onClick={() => clearView()}>...</Tile>
{/* Tabs with views/releases/priority */}
<Tabs>...</Tabs>
{/* Editing Sheet */}
<Sheet open={!!editingView} onOpenChange={...}>...</Sheet>
</div>
);
}
For panels that display structured data, compose existing components. The TaskPanelContent delegates to TaskContentSideContent, pulling data from context hooks.
export function TaskPanelContent() {
const { task, setTask } = useLayoutTask();
const { organization, labels, categories, releases } = useLayoutOrganization();
const { tasks, setTasks } = useLayoutTasks();
return (
<TaskContentSideContent
task={task}
labels={labels}
tasks={tasks}
setTasks={setTasks}
setSelectedTask={(t) => t && setTask(t)}
availableUsers={organization?.members.map((m) => m.user) || []}
// ...other props
/>
);
}
SidebarMenu / SidebarMenuItem / SidebarMenuButton from doras-ui work inside the panel because PanelWrapper provides the required SidebarContext.
The panel open/close state is managed externally (not by the panel system itself). Currently, the tasks pages use isProjectPanelOpen / setProjectPanelOpen from useLayoutOrganization(), which is persisted via useStateManagement("isProjectPanelOpen", true, 30000).
When adding a panel to a new page:
isProjectPanelOpen if the page is within an org contextuseStateManagement if it should be independentisOpen / setOpen props to PanelWrapperPanel components are rendered inside the page component tree, so they have access to all context providers that wrap the page. For task pages:
useLayoutData() — account, organizations, wsuseLayoutOrganization() — organization, labels, views, categories, releases, isProjectPanelOpen, setProjectPanelOpenuseLayoutTasks() — tasks, setTasksuseLayoutTask() — task, setTask (only on /$orgId/tasks/$taskShortId)Since panel components are passed as JSX props (<MyPanelContent />), they are instantiated in the page component's render, which means they inherit all context from the page's provider tree. No special mounting considerations are needed.
h-11 border-b — height-matched to PageHeader.Identity for horizontal alignmentpanelHeader and panelBody, not a registration systemPanelWrapper provides SidebarContextIconLayoutSidebarRight / IconLayoutSidebarRightFilled iconspanels/*.tsxUse this skill when:
Tell me:
isProjectPanelOpen or new independent state?Create or update user-facing documentation for Sayr features in apps/marketing
Work with the Sayr edition system (cloud, community, enterprise) including limits, capabilities, and edition-aware code
Add, remove, or modify commands and sub-views in the Cmd+K command palette
Add or modify PageHeader layouts on admin pages, including task view integration and cross-org patterns
Update GitHub pull request title and description with comprehensive summary