From 6f8c8c054f3c42fa27abdd875f8f527d6d28567f Mon Sep 17 00:00:00 2001 From: os-zhuang Date: Wed, 10 Jun 2026 20:27:40 +0500 Subject: [PATCH] =?UTF-8?q?fix(ux):=20P1=20=E2=80=94=20tab=20bar=20label?= =?UTF-8?q?=20clipping=20+=20home=20greeting=20&=20AI=20entry?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Tab bar: CJK labels were clipped at the bottom edge without a safe-area inset (web / no-home-indicator devices). Reserve a floor of bottom padding and pin the label lineHeight so descenders always clear the edge — the labels were collapsing to a 1px line otherwise. Home: replace the static "Dashboard / Welcome back" header with a time-of-day greeting + the signed-in user's first name, surface a prominent AI Assistant entry card (previously buried two levels deep under More), and group the dashboards under a section heading. All strings are i18n'd (en/zh/ar `home.*`). Verified in the browser: labels render full-height, greeting + AI card + grouped dashboards display in Chinese. Co-Authored-By: Claude Opus 4.8 (1M context) --- app/(tabs)/_layout.tsx | 10 +++++++ app/(tabs)/index.tsx | 64 ++++++++++++++++++++++++++++++++++++------ locales/ar.json | 12 ++++++++ locales/en.json | 12 ++++++++ locales/zh.json | 12 ++++++++ 5 files changed, 101 insertions(+), 9 deletions(-) diff --git a/app/(tabs)/_layout.tsx b/app/(tabs)/_layout.tsx index 6b53afe..8e8403a 100644 --- a/app/(tabs)/_layout.tsx +++ b/app/(tabs)/_layout.tsx @@ -1,5 +1,6 @@ import { Tabs } from "expo-router"; import { useTranslation } from "react-i18next"; +import { useSafeAreaInsets } from "react-native-safe-area-context"; import { Home, Search, @@ -10,6 +11,11 @@ import { export default function TabLayout() { const { t } = useTranslation(); + const insets = useSafeAreaInsets(); + // Without a bottom safe-area inset (web, or a device with no home indicator) + // the labels sit flush against the bottom edge and get clipped. Reserve a + // floor of bottom padding so the descenders always clear the edge. + const bottomPad = Math.max(insets.bottom, 12); return ( diff --git a/app/(tabs)/index.tsx b/app/(tabs)/index.tsx index 092f272..5a3de4c 100644 --- a/app/(tabs)/index.tsx +++ b/app/(tabs)/index.tsx @@ -1,14 +1,29 @@ import { View, Text, ScrollView, RefreshControl } from "react-native"; import { SafeAreaView } from "react-native-safe-area-context"; -import { LayoutDashboard, ChevronRight, Inbox, AlertCircle } from "lucide-react-native"; +import { + LayoutDashboard, + ChevronRight, + Inbox, + AlertCircle, + Sparkles, +} from "lucide-react-native"; import { useClient } from "@objectstack/client-react"; import { useRouter } from "expo-router"; +import { useTranslation } from "react-i18next"; import { useCallback, useEffect, useState } from "react"; +import { authClient } from "~/lib/auth-client"; import { PressableCard } from "~/components/ui/PressableCard"; import { EmptyState } from "~/components/ui/EmptyState"; import { ListSkeleton } from "~/components/ui/ListSkeleton"; import { useApps } from "~/hooks/useApps"; +/** Pick a time-of-day greeting i18n key from the local hour. */ +function greetingKey(hour: number): "greetingMorning" | "greetingAfternoon" | "greetingEvening" { + if (hour < 12) return "greetingMorning"; + if (hour < 18) return "greetingAfternoon"; + return "greetingEvening"; +} + interface DashboardEntry { /** App name — the route segment dashboards open under. */ appId: string; @@ -27,8 +42,14 @@ interface DashboardEntry { export default function HomeScreen() { const client = useClient(); const router = useRouter(); + const { t } = useTranslation(); + const { data: session } = authClient.useSession(); const { apps, isLoading: appsLoading, refetch: refetchApps } = useApps(); + const firstName = (session?.user?.name ?? "").trim().split(/\s+/)[0]; + const greeting = t(`home.${greetingKey(new Date().getHours())}`); + const heading = firstName ? `${greeting}, ${firstName}` : greeting; + const [dashboards, setDashboards] = useState([]); const [isLoading, setIsLoading] = useState(true); const [isRefreshing, setIsRefreshing] = useState(false); @@ -100,35 +121,60 @@ export default function HomeScreen() { } > - Dashboard + {heading} - Welcome back. Here's your overview. + {t("home.subtitle")} + {/* AI Assistant quick entry — surfaces the assistant on the home screen + instead of burying it two levels deep under More. */} + router.push("/ai")} + accessibilityRole="button" + accessibilityLabel={t("home.assistantTitle")} + > + + + + + + {t("home.assistantTitle")} + + + {t("home.assistantSubtitle")} + + + + + {loading ? ( ) : error ? ( - + void fetchDashboards()} /> ) : dashboards.length === 0 ? ( - + ) : ( + + {t("home.dashboardsTitle")} + {dashboards.map((d) => (