mobile: consistent dark theme as desktop

This commit is contained in:
talksik
2026-04-30 17:46:09 -07:00
parent 424f27737b
commit 13d3df2b51
8 changed files with 74 additions and 45 deletions
+1 -1
View File
@@ -9,7 +9,7 @@ const config: ExpoConfig = {
orientation: "portrait",
icon: "./assets/icon.png",
scheme: "flowy",
userInterfaceStyle: "automatic",
userInterfaceStyle: "dark",
newArchEnabled: true,
splash: {
image: "./assets/icon.png",
+1 -1
View File
@@ -31,7 +31,7 @@ export default function App() {
<RootNavigator />
</NavigationContainer>
<Toaster />
<StatusBar style="auto" />
<StatusBar style="light" />
</SafeAreaProvider>
</PusherProvider>
</QueryClientProvider>
File diff suppressed because one or more lines are too long
@@ -0,0 +1,10 @@
import { View } from "react-native";
/**
* Inset hairline separator for edge-to-edge list rows. Pass to a FlatList
* via `ItemSeparatorComponent` so it renders only between rows, not after
* the last one. Mirrors desktop's <Separator className="mx-4" /> pattern.
*/
export function ListSeparator() {
return <View className="bg-border mx-4 h-px" />;
}
@@ -13,6 +13,8 @@ import { useNetworks } from "@/hooks/use-networks";
import { useAuthStore } from "@/stores/auth-store";
import { toUserMessage } from "@/lib/errors";
import type { RootStackScreenProps } from "@/navigation/types";
import { FlowyLogo } from "@/components/FlowyLogo";
import { ListSeparator } from "@/components/ListSeparator";
import { Drawer } from "./Drawer";
export function NetworkListScreen({
@@ -48,7 +50,7 @@ export function NetworkListScreen({
{initials}
</Text>
</Pressable>
<Text className="text-foreground text-base font-semibold">Flowy</Text>
<FlowyLogo />
<View className="w-9" />
</View>
@@ -71,10 +73,10 @@ export function NetworkListScreen({
<FlatList
data={data}
keyExtractor={(item) => item.id}
contentContainerClassName="p-4 gap-2"
refreshControl={
<RefreshControl refreshing={refreshing} onRefresh={onRefresh} />
}
ItemSeparatorComponent={ListSeparator}
renderItem={({ item }) => (
<NetworkCard
network={item}
@@ -106,10 +108,10 @@ function NetworkCard({
return (
<Pressable
onPress={onPress}
className="bg-card border-border active:bg-accent rounded-lg border px-4 py-4 flex-row items-center justify-between"
className="active:bg-accent flex-row items-center justify-between px-4 py-4"
>
<View className="flex-1">
<Text className="text-card-foreground text-base font-semibold">
<Text className="text-foreground text-base font-semibold">
{network.name}
</Text>
<Text className="text-muted-foreground text-sm">
@@ -96,10 +96,7 @@ export const StreamCard = memo(function StreamCard({
<Pressable
onPress={onPress}
android_ripple={{ color: "rgba(0,0,0,0.05)" }}
className={cn(
"bg-card border-b px-3.5 py-3 flex-row items-center gap-3 active:bg-accent",
isUnseen ? "border-primary" : "border-border",
)}
className="bg-card flex-row items-center gap-3 px-4 py-3 active:bg-accent"
>
<View
className={cn(
@@ -6,6 +6,7 @@ import {
View,
} from "react-native";
import { SafeAreaView } from "react-native-safe-area-context";
import { ListSeparator } from "@/components/ListSeparator";
import { toUserMessage } from "@/lib/errors";
import { particlePath } from "@/lib/particle-path";
import { useNetwork } from "@/hooks/use-networks";
@@ -43,7 +44,7 @@ export function StreamListScreen({
<FlatList
data={streams}
keyExtractor={(s) => s.id}
contentContainerClassName=""
ItemSeparatorComponent={ListSeparator}
renderItem={({ item }) => (
<StreamCard
particle={item}
+33 -34
View File
@@ -1,45 +1,44 @@
/** @type {import('tailwindcss').Config} */
// Tokens mirror js/desktop/src/styles/globals.css. Desktop authors values in
// OKLCH; React Native's color parser is not guaranteed to handle oklch(), so
// we ship hex equivalents here and keep the OKLCH source in comments.
// Tokens mirror js/desktop/src/styles/globals.css `.dark` palette. Desktop
// authors values in OKLCH; React Native's color parser is not guaranteed to
// handle oklch(), so we ship hex/rgba equivalents here and keep the OKLCH
// source in comments.
//
// Names match desktop 1:1 components written against `bg-background`,
// `text-foreground`, etc. behave identically.
// Mobile is dark-only by design — names match desktop 1:1 so components
// written against `bg-background`, `text-foreground`, etc. behave identically
// to the desktop app in dark mode.
module.exports = {
content: ["./index.ts", "./src/**/*.{ts,tsx}"],
presets: [require("nativewind/preset")],
darkMode: "class",
theme: {
extend: {
colors: {
// ---- Light tokens (desktop :root) ----
background: { DEFAULT: "#ffffff", dark: "#252525" }, // oklch(1 0 0) / oklch(0.145 0 0)
foreground: { DEFAULT: "#252525", dark: "#fafafa" }, // oklch(0.145 0 0) / oklch(0.985 0 0)
card: { DEFAULT: "#ffffff", dark: "#363636" }, // oklch(1 0 0) / oklch(0.205 0 0)
"card-foreground": { DEFAULT: "#252525", dark: "#fafafa" },
popover: { DEFAULT: "#ffffff", dark: "#363636" },
"popover-foreground": { DEFAULT: "#252525", dark: "#fafafa" },
primary: { DEFAULT: "#363636", dark: "#ebebeb" }, // oklch(0.205 0 0) / oklch(0.922 0 0)
"primary-foreground": { DEFAULT: "#fafafa", dark: "#363636" },
secondary: { DEFAULT: "#f4f4f4", dark: "#454545" }, // oklch(0.97 0 0) / oklch(0.269 0 0)
"secondary-foreground": { DEFAULT: "#363636", dark: "#fafafa" },
muted: { DEFAULT: "#f4f4f4", dark: "#454545" },
"muted-foreground": { DEFAULT: "#878787", dark: "#a6a6a6" }, // oklch(0.556 0 0) / oklch(0.708 0 0)
accent: { DEFAULT: "#f4f4f4", dark: "#454545" },
"accent-foreground": { DEFAULT: "#363636", dark: "#fafafa" },
destructive: { DEFAULT: "#dc2626", dark: "#ef4444" }, // oklch(0.577 0.245 27.325) / oklch(0.704 0.191 22.216)
border: { DEFAULT: "#dcdcdc", dark: "rgba(255,255,255,0.1)" },
input: { DEFAULT: "#dcdcdc", dark: "rgba(255,255,255,0.15)" },
ring: { DEFAULT: "#a6a6a6", dark: "#878787" }, // oklch(0.708 0 0) / oklch(0.556 0 0)
// Sidebar (drawer) tokens — desktop also defines these
sidebar: { DEFAULT: "#fafafa", dark: "#363636" },
"sidebar-foreground": { DEFAULT: "#252525", dark: "#fafafa" },
"sidebar-primary": { DEFAULT: "#363636", dark: "#6366f1" }, // chart-1 dark = oklch(0.488 0.243 264.376)
"sidebar-primary-foreground": { DEFAULT: "#fafafa", dark: "#fafafa" },
"sidebar-accent": { DEFAULT: "#f4f4f4", dark: "#454545" },
"sidebar-accent-foreground": { DEFAULT: "#363636", dark: "#fafafa" },
"sidebar-border": { DEFAULT: "#dcdcdc", dark: "rgba(255,255,255,0.1)" },
"sidebar-ring": { DEFAULT: "#a6a6a6", dark: "#878787" },
background: "#252525", // oklch(0.145 0 0)
foreground: "#fafafa", // oklch(0.985 0 0)
card: "#363636", // oklch(0.205 0 0)
"card-foreground": "#fafafa",
popover: "#363636",
"popover-foreground": "#fafafa",
primary: "#ebebeb", // oklch(0.922 0 0)
"primary-foreground": "#363636",
secondary: "#454545", // oklch(0.269 0 0)
"secondary-foreground": "#fafafa",
muted: "#454545",
"muted-foreground": "#a6a6a6", // oklch(0.708 0 0)
accent: "#454545",
"accent-foreground": "#fafafa",
destructive: "#ef4444", // oklch(0.704 0.191 22.216)
border: "rgba(255,255,255,0.1)", // oklch(1 0 0 / 10%)
input: "rgba(255,255,255,0.15)", // oklch(1 0 0 / 15%)
ring: "#878787", // oklch(0.556 0 0)
sidebar: "#363636",
"sidebar-foreground": "#fafafa",
"sidebar-primary": "#6366f1", // chart-1 dark = oklch(0.488 0.243 264.376)
"sidebar-primary-foreground": "#fafafa",
"sidebar-accent": "#454545",
"sidebar-accent-foreground": "#fafafa",
"sidebar-border": "rgba(255,255,255,0.1)",
"sidebar-ring": "#878787",
},
borderRadius: {
lg: "10px", // --radius: 0.625rem