Files
paseo/packages/app/src/components/sidebar/workspace-trailing/index.tsx
T
Mohamed Boudra a361d1cb2f fix(app): polish workspace navigation surfaces
Use mobile action sheets and long-press file menus while preserving provider context across sheet portals. Keep sidebar and file-pane glyphs on shared visual rails, avoid scrollbar layout shifts, and hide keyboard affordances where shortcuts are unavailable.
2026-08-03 21:40:21 +02:00

81 lines
2.7 KiB
TypeScript

import { Text } from "react-native";
import { StyleSheet } from "react-native-unistyles";
import { DiffStat } from "@/components/diff-stat";
import type { SidebarWorkspaceEntry } from "@/hooks/use-sidebar-workspaces-list";
import { useAppSettings } from "@/hooks/use-settings";
import type { SidebarWorkspaceTrailing } from "@/hooks/use-settings";
import { useCompactTimeAgo } from "@/hooks/use-compact-time-ago";
export type { SidebarWorkspaceTrailing };
/**
* The slot to the right of a workspace title. Three renderers behind one preference, so
* every row renderer asks the same question and the kebab overlay geometry stays identical
* no matter which one is showing.
*
* "none" exists because the slot is the only thing competing with the title for width, and
* a user who never reads the diff would rather have the characters.
*/
export function useSidebarWorkspaceTrailing(): SidebarWorkspaceTrailing {
const {
settings: { sidebarWorkspaceTrailing },
} = useAppSettings();
return sidebarWorkspaceTrailing;
}
/** Whether the slot has anything to draw for this workspace under the current preference. */
export function hasSidebarWorkspaceTrailing({
workspace,
trailing,
}: {
workspace: SidebarWorkspaceEntry;
trailing: SidebarWorkspaceTrailing;
}): boolean {
if (trailing === "diff") return workspace.diffStat !== null;
if (trailing === "timestamp") return workspace.statusEnteredAt !== null;
return false;
}
export function SidebarWorkspaceTrailingContent({
workspace,
trailing,
}: {
workspace: SidebarWorkspaceEntry;
trailing: SidebarWorkspaceTrailing;
}) {
if (trailing === "diff" && workspace.diffStat) {
return (
<DiffStat additions={workspace.diffStat.additions} deletions={workspace.diffStat.deletions} />
);
}
if (trailing === "timestamp" && workspace.statusEnteredAt) {
return <WorkspaceTimestamp enteredAt={workspace.statusEnteredAt} />;
}
return null;
}
/**
* Its own component so the clock stops here. `useCompactTimeAgo` holds state, and state
* re-renders the component that owns it — keeping that component down to a single `<Text>` is
* what stops a minute tick from reaching the row, the list, or the diff stat next door.
*/
function WorkspaceTimestamp({ enteredAt }: { enteredAt: Date }) {
const label = useCompactTimeAgo(enteredAt);
return (
<Text style={styles.timestamp} numberOfLines={1} testID="sidebar-workspace-timestamp">
{label}
</Text>
);
}
const styles = StyleSheet.create((theme) => ({
timestamp: {
height: 20,
lineHeight: 20,
color: theme.colors.foregroundMuted,
fontSize: theme.fontSize.xs,
fontWeight: theme.fontWeight.normal,
flexShrink: 0,
},
}));