Rename fold/unfold to collapse/expand across panel code
Aligns terminology with standard UI conventions. Renames props, state, handlers, aria-labels, test descriptions, and the test file. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -336,8 +336,8 @@ export function App() {
|
||||
uploadAndCacheSource={uploadAndCacheSource}
|
||||
refreshCache={refreshCache}
|
||||
panelRole="pinned"
|
||||
isFolded={false}
|
||||
onToggleFold={() => {}}
|
||||
isCollapsed={false}
|
||||
onToggleCollapse={() => {}}
|
||||
onPin={() => {}}
|
||||
onUnpin={sidePanel.unpin}
|
||||
showPinButton={false}
|
||||
@@ -355,8 +355,8 @@ export function App() {
|
||||
uploadAndCacheSource={uploadAndCacheSource}
|
||||
refreshCache={refreshCache}
|
||||
panelRole="browse"
|
||||
isFolded={sidePanel.isRightPanelFolded}
|
||||
onToggleFold={sidePanel.toggleFold}
|
||||
isCollapsed={sidePanel.isRightPanelCollapsed}
|
||||
onToggleCollapse={sidePanel.toggleCollapse}
|
||||
onPin={sidePanel.togglePin}
|
||||
onUnpin={() => {}}
|
||||
showPinButton={sidePanel.isWideDesktop && !!selectedCreature}
|
||||
@@ -371,7 +371,7 @@ export function App() {
|
||||
|
||||
<BulkImportToasts
|
||||
state={bulkImport.state}
|
||||
visible={!sidePanel.bulkImportMode || sidePanel.isRightPanelFolded}
|
||||
visible={!sidePanel.bulkImportMode || sidePanel.isRightPanelCollapsed}
|
||||
onReset={bulkImport.reset}
|
||||
/>
|
||||
|
||||
|
||||
@@ -45,8 +45,8 @@ interface PanelProps {
|
||||
creatureId?: CreatureId | null;
|
||||
creature?: Creature | null;
|
||||
panelRole?: "browse" | "pinned";
|
||||
isFolded?: boolean;
|
||||
onToggleFold?: () => void;
|
||||
isCollapsed?: boolean;
|
||||
onToggleCollapse?: () => void;
|
||||
onPin?: () => void;
|
||||
onUnpin?: () => void;
|
||||
showPinButton?: boolean;
|
||||
@@ -64,8 +64,8 @@ function renderPanel(overrides: PanelProps = {}) {
|
||||
uploadAndCacheSource: vi.fn(),
|
||||
refreshCache: vi.fn(),
|
||||
panelRole: "browse" as const,
|
||||
isFolded: false,
|
||||
onToggleFold: vi.fn(),
|
||||
isCollapsed: false,
|
||||
onToggleCollapse: vi.fn(),
|
||||
onPin: vi.fn(),
|
||||
onUnpin: vi.fn(),
|
||||
showPinButton: false,
|
||||
@@ -78,18 +78,18 @@ function renderPanel(overrides: PanelProps = {}) {
|
||||
return props;
|
||||
}
|
||||
|
||||
describe("Stat Block Panel Fold/Unfold and Pin", () => {
|
||||
describe("Stat Block Panel Collapse/Expand and Pin", () => {
|
||||
beforeEach(() => {
|
||||
mockMatchMedia(true); // desktop by default
|
||||
});
|
||||
|
||||
afterEach(cleanup);
|
||||
|
||||
describe("US1: Fold and Unfold", () => {
|
||||
it("shows fold button instead of close button on desktop", () => {
|
||||
describe("US1: Collapse and Expand", () => {
|
||||
it("shows collapse button instead of close button on desktop", () => {
|
||||
renderPanel();
|
||||
expect(
|
||||
screen.getByRole("button", { name: "Fold stat block panel" }),
|
||||
screen.getByRole("button", { name: "Collapse stat block panel" }),
|
||||
).toBeInTheDocument();
|
||||
expect(
|
||||
screen.queryByRole("button", { name: /close/i }),
|
||||
@@ -101,42 +101,42 @@ describe("Stat Block Panel Fold/Unfold and Pin", () => {
|
||||
expect(screen.queryByText("Stat Block")).not.toBeInTheDocument();
|
||||
});
|
||||
|
||||
it("renders folded tab with creature name when isFolded is true", () => {
|
||||
renderPanel({ isFolded: true });
|
||||
it("renders collapsed tab with creature name when isCollapsed is true", () => {
|
||||
renderPanel({ isCollapsed: true });
|
||||
expect(screen.getByText("Goblin")).toBeInTheDocument();
|
||||
expect(
|
||||
screen.getByRole("button", { name: "Unfold stat block panel" }),
|
||||
screen.getByRole("button", { name: "Expand stat block panel" }),
|
||||
).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it("calls onToggleFold when fold button is clicked", () => {
|
||||
it("calls onToggleCollapse when collapse button is clicked", () => {
|
||||
const props = renderPanel();
|
||||
fireEvent.click(
|
||||
screen.getByRole("button", { name: "Fold stat block panel" }),
|
||||
screen.getByRole("button", { name: "Collapse stat block panel" }),
|
||||
);
|
||||
expect(props.onToggleFold).toHaveBeenCalledTimes(1);
|
||||
expect(props.onToggleCollapse).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
|
||||
it("calls onToggleFold when folded tab is clicked", () => {
|
||||
const props = renderPanel({ isFolded: true });
|
||||
it("calls onToggleCollapse when collapsed tab is clicked", () => {
|
||||
const props = renderPanel({ isCollapsed: true });
|
||||
fireEvent.click(
|
||||
screen.getByRole("button", { name: "Unfold stat block panel" }),
|
||||
screen.getByRole("button", { name: "Expand stat block panel" }),
|
||||
);
|
||||
expect(props.onToggleFold).toHaveBeenCalledTimes(1);
|
||||
expect(props.onToggleCollapse).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
|
||||
it("applies translate-x class when folded (right side)", () => {
|
||||
renderPanel({ isFolded: true, side: "right" });
|
||||
it("applies translate-x class when collapsed (right side)", () => {
|
||||
renderPanel({ isCollapsed: true, side: "right" });
|
||||
const panel = screen
|
||||
.getByRole("button", { name: "Unfold stat block panel" })
|
||||
.getByRole("button", { name: "Expand stat block panel" })
|
||||
.closest("div");
|
||||
expect(panel?.className).toContain("translate-x-[calc(100%-40px)]");
|
||||
});
|
||||
|
||||
it("applies translate-x-0 when expanded", () => {
|
||||
renderPanel({ isFolded: false });
|
||||
renderPanel({ isCollapsed: false });
|
||||
const foldBtn = screen.getByRole("button", {
|
||||
name: "Fold stat block panel",
|
||||
name: "Collapse stat block panel",
|
||||
});
|
||||
const panel = foldBtn.closest("div.fixed") as HTMLElement;
|
||||
expect(panel?.className).toContain("translate-x-0");
|
||||
@@ -148,12 +148,12 @@ describe("Stat Block Panel Fold/Unfold and Pin", () => {
|
||||
mockMatchMedia(false); // mobile
|
||||
});
|
||||
|
||||
it("shows fold button instead of X close button on mobile drawer", () => {
|
||||
it("shows collapse button instead of X close button on mobile drawer", () => {
|
||||
renderPanel();
|
||||
expect(
|
||||
screen.getByRole("button", { name: "Fold stat block panel" }),
|
||||
screen.getByRole("button", { name: "Collapse stat block panel" }),
|
||||
).toBeInTheDocument();
|
||||
// No X close icon button — only backdrop dismiss and fold toggle
|
||||
// No X close icon button — only backdrop dismiss and collapse toggle
|
||||
const buttons = screen.getAllByRole("button");
|
||||
const buttonLabels = buttons.map((b) => b.getAttribute("aria-label"));
|
||||
expect(buttonLabels).not.toContain("Close");
|
||||
@@ -175,8 +175,8 @@ describe("Stat Block Panel Fold/Unfold and Pin", () => {
|
||||
uploadAndCacheSource={vi.fn()}
|
||||
refreshCache={vi.fn()}
|
||||
panelRole="pinned"
|
||||
isFolded={false}
|
||||
onToggleFold={vi.fn()}
|
||||
isCollapsed={false}
|
||||
onToggleCollapse={vi.fn()}
|
||||
onPin={vi.fn()}
|
||||
onUnpin={vi.fn()}
|
||||
showPinButton={false}
|
||||
@@ -235,7 +235,7 @@ describe("Stat Block Panel Fold/Unfold and Pin", () => {
|
||||
it("positions browse panel on the right side", () => {
|
||||
renderPanel({ panelRole: "browse", side: "right" });
|
||||
const foldBtn = screen.getByRole("button", {
|
||||
name: "Fold stat block panel",
|
||||
name: "Collapse stat block panel",
|
||||
});
|
||||
const panel = foldBtn.closest("div.fixed") as HTMLElement;
|
||||
expect(panel?.className).toContain("right-0");
|
||||
@@ -243,16 +243,16 @@ describe("Stat Block Panel Fold/Unfold and Pin", () => {
|
||||
});
|
||||
});
|
||||
|
||||
describe("US3: Fold independence with pinned panel", () => {
|
||||
it("pinned panel has no fold button", () => {
|
||||
describe("US3: Collapse independence with pinned panel", () => {
|
||||
it("pinned panel has no collapse button", () => {
|
||||
renderPanel({ panelRole: "pinned", side: "left" });
|
||||
expect(
|
||||
screen.queryByRole("button", { name: /fold/i }),
|
||||
screen.queryByRole("button", { name: /collapse/i }),
|
||||
).not.toBeInTheDocument();
|
||||
});
|
||||
|
||||
it("pinned panel is always expanded (no translate offset)", () => {
|
||||
renderPanel({ panelRole: "pinned", side: "left", isFolded: false });
|
||||
renderPanel({ panelRole: "pinned", side: "left", isCollapsed: false });
|
||||
const unpinBtn = screen.getByRole("button", {
|
||||
name: "Unpin creature",
|
||||
});
|
||||
@@ -22,8 +22,8 @@ interface StatBlockPanelProps {
|
||||
) => Promise<void>;
|
||||
refreshCache: () => Promise<void>;
|
||||
panelRole: "browse" | "pinned";
|
||||
isFolded: boolean;
|
||||
onToggleFold: () => void;
|
||||
isCollapsed: boolean;
|
||||
onToggleCollapse: () => void;
|
||||
onPin: () => void;
|
||||
onUnpin: () => void;
|
||||
showPinButton: boolean;
|
||||
@@ -42,23 +42,23 @@ function extractSourceCode(cId: CreatureId): string {
|
||||
return cId.slice(0, colonIndex).toUpperCase();
|
||||
}
|
||||
|
||||
function FoldedTab({
|
||||
function CollapsedTab({
|
||||
creatureName,
|
||||
side,
|
||||
onToggleFold,
|
||||
onToggleCollapse,
|
||||
}: {
|
||||
creatureName: string;
|
||||
side: "left" | "right";
|
||||
onToggleFold: () => void;
|
||||
onToggleCollapse: () => void;
|
||||
}) {
|
||||
return (
|
||||
<button
|
||||
type="button"
|
||||
onClick={onToggleFold}
|
||||
onClick={onToggleCollapse}
|
||||
className={`flex h-full w-[40px] cursor-pointer items-center justify-center text-muted-foreground hover:text-hover-neutral ${
|
||||
side === "right" ? "self-start" : "self-end"
|
||||
}`}
|
||||
aria-label="Unfold stat block panel"
|
||||
aria-label="Expand stat block panel"
|
||||
>
|
||||
<span className="writing-vertical-rl text-sm font-medium">
|
||||
{creatureName}
|
||||
@@ -70,13 +70,13 @@ function FoldedTab({
|
||||
function PanelHeader({
|
||||
panelRole,
|
||||
showPinButton,
|
||||
onToggleFold,
|
||||
onToggleCollapse,
|
||||
onPin,
|
||||
onUnpin,
|
||||
}: {
|
||||
panelRole: "browse" | "pinned";
|
||||
showPinButton: boolean;
|
||||
onToggleFold: () => void;
|
||||
onToggleCollapse: () => void;
|
||||
onPin: () => void;
|
||||
onUnpin: () => void;
|
||||
}) {
|
||||
@@ -87,9 +87,9 @@ function PanelHeader({
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="icon-sm"
|
||||
onClick={onToggleFold}
|
||||
onClick={onToggleCollapse}
|
||||
className="text-muted-foreground"
|
||||
aria-label="Fold stat block panel"
|
||||
aria-label="Collapse stat block panel"
|
||||
>
|
||||
<PanelRightClose className="h-4 w-4" />
|
||||
</Button>
|
||||
@@ -124,48 +124,48 @@ function PanelHeader({
|
||||
}
|
||||
|
||||
function DesktopPanel({
|
||||
isFolded,
|
||||
isCollapsed,
|
||||
side,
|
||||
creatureName,
|
||||
panelRole,
|
||||
showPinButton,
|
||||
onToggleFold,
|
||||
onToggleCollapse,
|
||||
onPin,
|
||||
onUnpin,
|
||||
children,
|
||||
}: {
|
||||
isFolded: boolean;
|
||||
isCollapsed: boolean;
|
||||
side: "left" | "right";
|
||||
creatureName: string;
|
||||
panelRole: "browse" | "pinned";
|
||||
showPinButton: boolean;
|
||||
onToggleFold: () => void;
|
||||
onToggleCollapse: () => void;
|
||||
onPin: () => void;
|
||||
onUnpin: () => void;
|
||||
children: ReactNode;
|
||||
}) {
|
||||
const sideClasses = side === "left" ? "left-0 border-r" : "right-0 border-l";
|
||||
const foldedTranslate =
|
||||
const collapsedTranslate =
|
||||
side === "right"
|
||||
? "translate-x-[calc(100%-40px)]"
|
||||
: "translate-x-[calc(-100%+40px)]";
|
||||
|
||||
return (
|
||||
<div
|
||||
className={`fixed top-0 bottom-0 flex w-[400px] flex-col border-border bg-card transition-slide-panel ${sideClasses} ${isFolded ? foldedTranslate : "translate-x-0"}`}
|
||||
className={`fixed top-0 bottom-0 flex w-[400px] flex-col border-border bg-card transition-slide-panel ${sideClasses} ${isCollapsed ? collapsedTranslate : "translate-x-0"}`}
|
||||
>
|
||||
{isFolded ? (
|
||||
<FoldedTab
|
||||
{isCollapsed ? (
|
||||
<CollapsedTab
|
||||
creatureName={creatureName}
|
||||
side={side}
|
||||
onToggleFold={onToggleFold}
|
||||
onToggleCollapse={onToggleCollapse}
|
||||
/>
|
||||
) : (
|
||||
<>
|
||||
<PanelHeader
|
||||
panelRole={panelRole}
|
||||
showPinButton={showPinButton}
|
||||
onToggleFold={onToggleFold}
|
||||
onToggleCollapse={onToggleCollapse}
|
||||
onPin={onPin}
|
||||
onUnpin={onUnpin}
|
||||
/>
|
||||
@@ -206,7 +206,7 @@ function MobileDrawer({
|
||||
size="icon-sm"
|
||||
onClick={onDismiss}
|
||||
className="text-muted-foreground"
|
||||
aria-label="Fold stat block panel"
|
||||
aria-label="Collapse stat block panel"
|
||||
>
|
||||
<PanelRightClose className="h-4 w-4" />
|
||||
</Button>
|
||||
@@ -227,8 +227,8 @@ export function StatBlockPanel({
|
||||
uploadAndCacheSource,
|
||||
refreshCache,
|
||||
panelRole,
|
||||
isFolded,
|
||||
onToggleFold,
|
||||
isCollapsed,
|
||||
onToggleCollapse,
|
||||
onPin,
|
||||
onUnpin,
|
||||
showPinButton,
|
||||
@@ -341,12 +341,12 @@ export function StatBlockPanel({
|
||||
if (isDesktop) {
|
||||
return (
|
||||
<DesktopPanel
|
||||
isFolded={isFolded}
|
||||
isCollapsed={isCollapsed}
|
||||
side={side}
|
||||
creatureName={creatureName}
|
||||
panelRole={panelRole}
|
||||
showPinButton={showPinButton}
|
||||
onToggleFold={onToggleFold}
|
||||
onToggleCollapse={onToggleCollapse}
|
||||
onPin={onPin}
|
||||
onUnpin={onUnpin}
|
||||
>
|
||||
|
||||
@@ -12,7 +12,7 @@ interface SidePanelState {
|
||||
selectedCreatureId: CreatureId | null;
|
||||
bulkImportMode: boolean;
|
||||
sourceManagerMode: boolean;
|
||||
isRightPanelFolded: boolean;
|
||||
isRightPanelCollapsed: boolean;
|
||||
pinnedCreatureId: CreatureId | null;
|
||||
isWideDesktop: boolean;
|
||||
}
|
||||
@@ -22,14 +22,14 @@ interface SidePanelActions {
|
||||
showBulkImport: () => void;
|
||||
showSourceManager: () => void;
|
||||
dismissPanel: () => void;
|
||||
toggleFold: () => void;
|
||||
toggleCollapse: () => void;
|
||||
togglePin: () => void;
|
||||
unpin: () => void;
|
||||
}
|
||||
|
||||
export function useSidePanelState(): SidePanelState & SidePanelActions {
|
||||
const [panelView, setPanelView] = useState<PanelView>({ mode: "closed" });
|
||||
const [isRightPanelFolded, setIsRightPanelFolded] = useState(false);
|
||||
const [isRightPanelCollapsed, setIsRightPanelCollapsed] = useState(false);
|
||||
const [pinnedCreatureId, setPinnedCreatureId] = useState<CreatureId | null>(
|
||||
null,
|
||||
);
|
||||
@@ -49,25 +49,25 @@ export function useSidePanelState(): SidePanelState & SidePanelActions {
|
||||
|
||||
const showCreature = useCallback((creatureId: CreatureId) => {
|
||||
setPanelView({ mode: "creature", creatureId });
|
||||
setIsRightPanelFolded(false);
|
||||
setIsRightPanelCollapsed(false);
|
||||
}, []);
|
||||
|
||||
const showBulkImport = useCallback(() => {
|
||||
setPanelView({ mode: "bulk-import" });
|
||||
setIsRightPanelFolded(false);
|
||||
setIsRightPanelCollapsed(false);
|
||||
}, []);
|
||||
|
||||
const showSourceManager = useCallback(() => {
|
||||
setPanelView({ mode: "source-manager" });
|
||||
setIsRightPanelFolded(false);
|
||||
setIsRightPanelCollapsed(false);
|
||||
}, []);
|
||||
|
||||
const dismissPanel = useCallback(() => {
|
||||
setPanelView({ mode: "closed" });
|
||||
}, []);
|
||||
|
||||
const toggleFold = useCallback(() => {
|
||||
setIsRightPanelFolded((f) => !f);
|
||||
const toggleCollapse = useCallback(() => {
|
||||
setIsRightPanelCollapsed((f) => !f);
|
||||
}, []);
|
||||
|
||||
const togglePin = useCallback(() => {
|
||||
@@ -87,14 +87,14 @@ export function useSidePanelState(): SidePanelState & SidePanelActions {
|
||||
selectedCreatureId,
|
||||
bulkImportMode: panelView.mode === "bulk-import",
|
||||
sourceManagerMode: panelView.mode === "source-manager",
|
||||
isRightPanelFolded,
|
||||
isRightPanelCollapsed,
|
||||
pinnedCreatureId,
|
||||
isWideDesktop,
|
||||
showCreature,
|
||||
showBulkImport,
|
||||
showSourceManager,
|
||||
dismissPanel,
|
||||
toggleFold,
|
||||
toggleCollapse,
|
||||
togglePin,
|
||||
unpin,
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user