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.
81 lines
2.7 KiB
TypeScript
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,
|
|
},
|
|
}));
|