feat(db): mesh data model — meshes, members, invites, audit log
- pgSchema "mesh" with 4 tables isolating the peer mesh domain - Enums: visibility, transport, tier, role - audit_log is metadata-only (E2E encryption enforced at broker/client) - Cascade on mesh delete, soft-delete via archivedAt/revokedAt Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
122
packages/ui/mobile/src/components/theme.tsx
Normal file
122
packages/ui/mobile/src/components/theme.tsx
Normal file
@@ -0,0 +1,122 @@
|
||||
import { oklch, formatHex } from "culori";
|
||||
import { memo } from "react";
|
||||
import { FlatList, View } from "react-native";
|
||||
|
||||
import { useTranslation } from "@turbostarter/i18n";
|
||||
import { cn, ThemeColor, ThemeMode, themes } from "@turbostarter/ui";
|
||||
|
||||
import { Button } from "./button";
|
||||
import { Icons } from "./icons";
|
||||
import { Label } from "./label";
|
||||
import { Text } from "./text";
|
||||
|
||||
import type { ThemeConfig } from "@turbostarter/ui";
|
||||
|
||||
interface ThemeCustomizerProps {
|
||||
readonly config: ThemeConfig;
|
||||
readonly onChange: (config: ThemeConfig) => void;
|
||||
readonly resolvedTheme: Exclude<ThemeMode, "system">;
|
||||
}
|
||||
|
||||
export const MODE_ICONS = {
|
||||
[ThemeMode.LIGHT]: Icons.Sun,
|
||||
[ThemeMode.DARK]: Icons.Moon,
|
||||
[ThemeMode.SYSTEM]: Icons.SunMoon,
|
||||
} as const;
|
||||
|
||||
export const ThemeCustomizer = memo<ThemeCustomizerProps>(
|
||||
({ config, onChange, resolvedTheme }) => {
|
||||
const { t } = useTranslation("common");
|
||||
|
||||
return (
|
||||
<View className="mt-2 flex-1 items-center gap-4">
|
||||
<View className="w-full gap-1.5">
|
||||
<Label nativeID="color" className="text-xs">
|
||||
{t("theme.color.label")}
|
||||
</Label>
|
||||
<FlatList
|
||||
bounces={false}
|
||||
showsVerticalScrollIndicator={false}
|
||||
numColumns={3}
|
||||
data={Object.values(ThemeColor)}
|
||||
columnWrapperClassName="gap-2"
|
||||
contentContainerClassName="gap-2"
|
||||
renderItem={({ item }) => {
|
||||
const [l, c, h, alpha] = themes[item][resolvedTheme].primary;
|
||||
return (
|
||||
<Button
|
||||
variant="outline"
|
||||
key={item}
|
||||
onPress={() => onChange({ ...config, color: item })}
|
||||
hitSlop={2}
|
||||
className={cn(
|
||||
"grow basis-24 flex-row justify-start gap-2.5 px-3",
|
||||
config.color === item &&
|
||||
"border-primary dark:border-primary border-2",
|
||||
)}
|
||||
>
|
||||
<View
|
||||
className="flex size-4.5 shrink-0 items-center justify-center rounded-full"
|
||||
style={{
|
||||
backgroundColor: formatHex(
|
||||
oklch({
|
||||
mode: "oklch",
|
||||
l,
|
||||
c,
|
||||
h,
|
||||
alpha,
|
||||
}),
|
||||
),
|
||||
}}
|
||||
/>
|
||||
<Text className="capitalize">{t(`theme.color.${item}`)}</Text>
|
||||
</Button>
|
||||
);
|
||||
}}
|
||||
/>
|
||||
</View>
|
||||
<View className="w-full gap-1.5">
|
||||
<Label nativeID="mode" className="text-xs">
|
||||
{t("theme.mode.label")}
|
||||
</Label>
|
||||
<FlatList
|
||||
bounces={false}
|
||||
showsVerticalScrollIndicator={false}
|
||||
numColumns={3}
|
||||
data={Object.values(ThemeMode)}
|
||||
columnWrapperClassName="gap-2"
|
||||
contentContainerClassName="gap-2"
|
||||
renderItem={({ item }) => {
|
||||
const isActive = config.mode === item;
|
||||
const Icon = MODE_ICONS[item];
|
||||
|
||||
return (
|
||||
<Button
|
||||
variant="outline"
|
||||
key={item}
|
||||
onPress={() => onChange({ ...config, mode: item })}
|
||||
hitSlop={2}
|
||||
className={cn(
|
||||
"grow basis-24 flex-row justify-start gap-2 px-3 capitalize",
|
||||
isActive && "border-primary dark:border-primary border-2",
|
||||
)}
|
||||
>
|
||||
<Icon
|
||||
className="text-foreground shrink-0"
|
||||
width={18}
|
||||
height={18}
|
||||
/>
|
||||
<Text className="text-sm capitalize">
|
||||
{t(`theme.mode.${item}`)}
|
||||
</Text>
|
||||
</Button>
|
||||
);
|
||||
}}
|
||||
/>
|
||||
</View>
|
||||
</View>
|
||||
);
|
||||
},
|
||||
);
|
||||
|
||||
ThemeCustomizer.displayName = "ThemeCustomizer";
|
||||
Reference in New Issue
Block a user