feat(channels): pager into the chip row, Blocked as its own tab
Address manager UAT: - The DataTable controls row (status chips + pager) now portals into the fixed band via a new controlsInBand prop, so it stays put above the sticky header instead of scrolling away — and the pager fills the empty space next to the status chips, on one row. - The tab bar is always fixed chrome now (not just on Subscriptions), so it doesn't jump when switching tabs; on Subscriptions the stats/intro/ tags ride with it. - Blocked channels moves off the bottom of the Subscriptions list into its own 'Blocked' tab (with an empty state), alongside Subscriptions and Discover. Verified with a 90-channel test account: everything above the rows stays fixed while the rows scroll under the sticky header. Suite 15/15, tsc + knip clean.
This commit is contained in:
@@ -3,6 +3,7 @@ import { useTranslation } from "react-i18next";
|
||||
import { ArrowDown, ArrowUp, ChevronLeft, ChevronRight, ListFilter, X } from "lucide-react";
|
||||
import { useDismiss } from "../lib/useDismiss";
|
||||
import { useScrollFade } from "../lib/useScrollFade";
|
||||
import { PageToolbar } from "./PageShell";
|
||||
|
||||
// A reusable, client-side data table: per-column sort + filter (in the header) + pagination,
|
||||
// with its sort/filter/page state optionally persisted to localStorage so a reload (F5) keeps
|
||||
@@ -84,6 +85,8 @@ export default function DataTable<T>({
|
||||
rowClassName,
|
||||
controlsPosition = "bottom",
|
||||
controlsLeading,
|
||||
controlsInBand = false,
|
||||
controlsBandClassName,
|
||||
externalFilter,
|
||||
resetFiltersToken,
|
||||
}: {
|
||||
@@ -97,6 +100,11 @@ export default function DataTable<T>({
|
||||
rowClassName?: (row: T) => string;
|
||||
controlsPosition?: "top" | "bottom" | "both";
|
||||
controlsLeading?: ReactNode;
|
||||
// Render the controls row (controlsLeading + pager) into the shell's fixed toolbar band instead
|
||||
// of inline, so it stays put above the sticky header while the rows scroll. `controlsBandClassName`
|
||||
// aligns it with the page's fixed chrome (e.g. the same max-width wrapper).
|
||||
controlsInBand?: boolean;
|
||||
controlsBandClassName?: string;
|
||||
// Set a column's filter from outside (e.g. "focus this channel" deep-links here). Applied
|
||||
// whenever its value changes; the user can still clear it from the header afterwards.
|
||||
externalFilter?: { key: string; value: string } | null;
|
||||
@@ -344,9 +352,18 @@ export default function DataTable<T>({
|
||||
</div>
|
||||
) : null;
|
||||
|
||||
const topControls =
|
||||
controlsInBand && controls ? (
|
||||
<PageToolbar>
|
||||
<div className={controlsBandClassName}>{controls}</div>
|
||||
</PageToolbar>
|
||||
) : (
|
||||
controls
|
||||
);
|
||||
|
||||
return (
|
||||
<div>
|
||||
{(controlsPosition === "top" || controlsPosition === "both") && controls}
|
||||
{(controlsPosition === "top" || controlsPosition === "both") && topControls}
|
||||
{/* Wide screens: table. The wrapper isn't clipped so header filter popovers can overflow. */}
|
||||
<div className="hidden md:block">
|
||||
<table className="w-full border-collapse text-sm">
|
||||
|
||||
Reference in New Issue
Block a user