diff --git a/src/design-system/composites/DataTable/DataTable.module.css b/src/design-system/composites/DataTable/DataTable.module.css index 8182f97..edcdfbb 100644 --- a/src/design-system/composites/DataTable/DataTable.module.css +++ b/src/design-system/composites/DataTable/DataTable.module.css @@ -12,6 +12,23 @@ box-shadow: none; } +.fillHeight { + display: flex; + flex-direction: column; + flex: 1; + min-height: 0; +} + +.fillHeight .scroll { + flex: 1; + min-height: 0; + overflow-y: auto; +} + +.fillHeight .footer { + flex-shrink: 0; +} + .scroll { overflow-x: auto; } @@ -35,6 +52,9 @@ background: var(--bg-raised); border-bottom: 1px solid var(--border); transition: color 0.12s; + position: sticky; + top: 0; + z-index: 1; } .th.sortable { diff --git a/src/design-system/composites/DataTable/DataTable.tsx b/src/design-system/composites/DataTable/DataTable.tsx index 37403a9..7d51825 100644 --- a/src/design-system/composites/DataTable/DataTable.tsx +++ b/src/design-system/composites/DataTable/DataTable.tsx @@ -24,6 +24,7 @@ export function DataTable({ rowAccent, expandedContent, flush = false, + fillHeight = false, onSortChange, }: DataTableProps) { const [sortKey, setSortKey] = useState(null) @@ -81,7 +82,7 @@ export function DataTable({ })) return ( -
+
diff --git a/src/design-system/composites/DataTable/types.ts b/src/design-system/composites/DataTable/types.ts index efdaa42..c780cda 100644 --- a/src/design-system/composites/DataTable/types.ts +++ b/src/design-system/composites/DataTable/types.ts @@ -20,6 +20,10 @@ export interface DataTableProps { expandedContent?: (row: T) => ReactNode | null /** Strip border, radius, and shadow so the table sits flush inside a parent container. */ flush?: boolean + /** Make the table fill remaining vertical space in a flex parent. + * The table body scrolls while the header stays sticky and the + * pagination footer stays pinned at the bottom. */ + fillHeight?: boolean /** Controlled sort: called when the user clicks a sortable column header. * When provided, the component skips client-side sorting — the caller is * responsible for providing `data` in the desired order. */ diff --git a/src/design-system/layout/Sidebar/Sidebar.tsx b/src/design-system/layout/Sidebar/Sidebar.tsx index c9458ef..00c2e7f 100644 --- a/src/design-system/layout/Sidebar/Sidebar.tsx +++ b/src/design-system/layout/Sidebar/Sidebar.tsx @@ -34,6 +34,7 @@ export interface SidebarAgent { interface SidebarProps { apps: SidebarApp[] className?: string + onNavigate?: (path: string) => void } // ── Helpers ────────────────────────────────────────────────────────────────── @@ -246,7 +247,7 @@ function StarredGroup({ // ── Sidebar ────────────────────────────────────────────────────────────────── -export function Sidebar({ apps, className }: SidebarProps) { +export function Sidebar({ apps, className, onNavigate }: SidebarProps) { const [search, setSearch] = useState('') const [appsCollapsed, _setAppsCollapsed] = useState(() => localStorage.getItem('cameleer:sidebar:apps-collapsed') === 'true') const [agentsCollapsed, _setAgentsCollapsed] = useState(() => localStorage.getItem('cameleer:sidebar:agents-collapsed') === 'true') @@ -275,7 +276,8 @@ export function Sidebar({ apps, className }: SidebarProps) { return next }) } - const navigate = useNavigate() + const routerNavigate = useNavigate() + const nav = onNavigate ?? routerNavigate const location = useLocation() const { starredIds, isStarred, toggleStar } = useStarred() @@ -330,7 +332,7 @@ export function Sidebar({ apps, className }: SidebarProps) { return (