mirror of
https://github.com/getpaseo/paseo.git
synced 2026-11-08 01:14:52 +00:00
* fix(app): wrap sidebar empty-state actions so longer labels stay inside the card The "Add project" / "Import session" row in the sidebar's empty state is a non-wrapping flex row. With longer translations (e.g. Russian) the second button overflows the card at the default 320px sidebar width. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * test(app): cover sidebar empty-state actions staying inside the card Renders the empty state in a real browser at the default sidebar width and checks both actions' bounds against the card, for Russian (fails without the wrap) and English. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * fix(app): keep long sidebar actions inside narrow cards Wrapping the row still lets a single translated action exceed its available width. Bound each empty-state action to the row and allow its label to shrink and wrap. Co-authored-by: Vyacheslav Scherbinin <gentslava@mail.ru> --------- Co-authored-by: Vyacheslav Scherbinin <gentslava@mail.ru> Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com> Co-authored-by: paseo-bot[bot] <266920839+paseo-bot[bot]@users.noreply.github.com>
142 lines
3.9 KiB
TypeScript
142 lines
3.9 KiB
TypeScript
import { type ReactNode, useCallback } from "react";
|
|
import { Import, Plus } from "lucide-react-native";
|
|
import { useTranslation } from "react-i18next";
|
|
import { Text, View } from "react-native";
|
|
import { StyleSheet } from "react-native-unistyles";
|
|
import { Button } from "@/components/ui/button";
|
|
import { useSidebarViewStore } from "@/stores/sidebar-view-store";
|
|
|
|
function SidebarEmptyStateCard({
|
|
testID,
|
|
title,
|
|
description,
|
|
children,
|
|
}: {
|
|
testID: string;
|
|
title: string;
|
|
description: string;
|
|
children: ReactNode;
|
|
}) {
|
|
return (
|
|
<View style={styles.card} testID={testID}>
|
|
<View style={styles.copy}>
|
|
<Text style={styles.title}>{title}</Text>
|
|
<Text style={styles.description}>{description}</Text>
|
|
</View>
|
|
<View style={styles.actions}>{children}</View>
|
|
</View>
|
|
);
|
|
}
|
|
|
|
/**
|
|
* The two things the sidebar's workspace list says when it has no rows.
|
|
*
|
|
* Both are bodies, not screens: a list mode renders one where its groups would go, inside its own
|
|
* scroll and below the section header it already owns. The header carries the display menu's
|
|
* trigger, and an empty state that replaces the list rather than its body takes the header with
|
|
* it — which is how filtering the last row away used to close the menu you were filtering from.
|
|
*/
|
|
export function SidebarFilterEmptyState() {
|
|
const { t } = useTranslation();
|
|
const clearLabelFilter = useSidebarViewStore((state) => state.clearLabelFilter);
|
|
const clearProjectFilters = useSidebarViewStore((state) => state.clearProjectFilters);
|
|
// Clears every filter that can empty the list, not just the one that did. The card names no
|
|
// filter, so a Clear that undid only one of two active filters would leave it on screen looking
|
|
// like it had failed.
|
|
const clearFilters = useCallback(() => {
|
|
clearLabelFilter();
|
|
clearProjectFilters();
|
|
}, [clearLabelFilter, clearProjectFilters]);
|
|
|
|
return (
|
|
<SidebarEmptyStateCard
|
|
testID="sidebar-filter-empty-state"
|
|
title={t("sidebar.filterEmpty.title")}
|
|
description={t("sidebar.filterEmpty.description")}
|
|
>
|
|
<Button
|
|
variant="secondary"
|
|
size="xs"
|
|
style={styles.action}
|
|
textStyle={styles.actionText}
|
|
onPress={clearFilters}
|
|
>
|
|
{t("sidebar.filterEmpty.clear")}
|
|
</Button>
|
|
</SidebarEmptyStateCard>
|
|
);
|
|
}
|
|
|
|
export function SidebarProjectEmptyState({
|
|
onAddProject,
|
|
onImportSession,
|
|
}: {
|
|
onAddProject?: () => void;
|
|
onImportSession?: () => void;
|
|
}) {
|
|
const { t } = useTranslation();
|
|
|
|
return (
|
|
<SidebarEmptyStateCard
|
|
testID="sidebar-project-empty-state"
|
|
title={t("sidebar.project.empty.title")}
|
|
description={t("sidebar.project.empty.description")}
|
|
>
|
|
<Button
|
|
variant="secondary"
|
|
size="xs"
|
|
leftIcon={Plus}
|
|
style={styles.action}
|
|
textStyle={styles.actionText}
|
|
onPress={onAddProject}
|
|
>
|
|
{t("sidebar.actions.addProject")}
|
|
</Button>
|
|
<Button
|
|
variant="outline"
|
|
size="xs"
|
|
leftIcon={Import}
|
|
style={styles.action}
|
|
textStyle={styles.actionText}
|
|
onPress={onImportSession}
|
|
>
|
|
{t("importSession.title")}
|
|
</Button>
|
|
</SidebarEmptyStateCard>
|
|
);
|
|
}
|
|
|
|
const styles = StyleSheet.create((theme) => ({
|
|
card: {
|
|
marginTop: theme.spacing[3],
|
|
padding: theme.spacing[4],
|
|
borderWidth: 1,
|
|
borderColor: theme.colors.border,
|
|
borderRadius: theme.borderRadius.lg,
|
|
},
|
|
copy: {
|
|
gap: theme.spacing[1],
|
|
},
|
|
actions: {
|
|
flexDirection: "row",
|
|
flexWrap: "wrap",
|
|
gap: theme.spacing[2],
|
|
marginTop: theme.spacing[4],
|
|
},
|
|
action: {
|
|
maxWidth: "100%",
|
|
},
|
|
actionText: {
|
|
flexShrink: 1,
|
|
},
|
|
title: {
|
|
color: theme.colors.foreground,
|
|
fontSize: theme.fontSize.sm,
|
|
fontWeight: theme.fontWeight.normal,
|
|
},
|
|
description: {
|
|
color: theme.colors.foregroundMuted,
|
|
fontSize: theme.fontSize.sm,
|
|
},
|
|
}));
|