6d2711e36e
Default to beenvoice.soconnor.dev with server settings hidden behind Advanced; add Entities tab with clients/businesses, invoice creation, UI fixes for dashboard layout, date fields, FAB position, and card-matched button radius. Co-authored-by: Cursor <cursoragent@cursor.com>
111 lines
2.4 KiB
TypeScript
111 lines
2.4 KiB
TypeScript
import { Image } from "expo-image";
|
||
import { StyleSheet, Text, View, type ViewStyle } from "react-native";
|
||
|
||
import { useAppTheme } from "@/contexts/ThemeContext";
|
||
import { fonts } from "@/constants/theme";
|
||
|
||
const markSource = require("@/assets/images/icon.png");
|
||
|
||
type LogoSize = "xs" | "sm" | "md" | "lg";
|
||
|
||
const widths: Record<LogoSize, number> = {
|
||
xs: 104,
|
||
sm: 140,
|
||
md: 180,
|
||
lg: 220,
|
||
};
|
||
|
||
type LogoProps = {
|
||
size?: LogoSize;
|
||
style?: ViewStyle;
|
||
/** Force the light wordmark for dark backgrounds (e.g. status bar chrome). */
|
||
onDark?: boolean;
|
||
};
|
||
|
||
/** Full beenvoice wordmark from web `public/beenvoice-logo.png` */
|
||
export function Logo({ size = "md", style, onDark }: LogoProps) {
|
||
const { isDark } = useAppTheme();
|
||
const width = widths[size];
|
||
const height = width * (436 / 2970);
|
||
const useDarkAsset = onDark ?? isDark;
|
||
|
||
return (
|
||
<View style={[styles.row, styles.noShrink, style]}>
|
||
<Image
|
||
source={
|
||
useDarkAsset
|
||
? require("@/assets/images/beenvoice-logo-dark.png")
|
||
: require("@/assets/images/beenvoice-logo.png")
|
||
}
|
||
style={{ width, height }}
|
||
contentFit="contain"
|
||
/>
|
||
</View>
|
||
);
|
||
}
|
||
|
||
/** Square dollar mark from Icon Composer export (1024×1024 PNG). */
|
||
export function LogoMark({
|
||
size = 32,
|
||
style,
|
||
}: {
|
||
size?: number;
|
||
style?: ViewStyle;
|
||
}) {
|
||
const fromStyle =
|
||
typeof style?.width === "number"
|
||
? style.width
|
||
: typeof style?.height === "number"
|
||
? style.height
|
||
: undefined;
|
||
const dimension = fromStyle ?? size;
|
||
|
||
return (
|
||
<View
|
||
style={[
|
||
styles.markBox,
|
||
{ width: dimension, height: dimension, aspectRatio: 1 },
|
||
style,
|
||
]}
|
||
>
|
||
<Image
|
||
source={markSource}
|
||
style={{ width: dimension, height: dimension }}
|
||
contentFit="contain"
|
||
/>
|
||
</View>
|
||
);
|
||
}
|
||
|
||
export function HeadingText({
|
||
children,
|
||
style,
|
||
}: {
|
||
children: React.ReactNode;
|
||
style?: object;
|
||
}) {
|
||
const { colors } = useAppTheme();
|
||
|
||
return (
|
||
<Text style={[styles.heading, { color: colors.foreground }, style]}>{children}</Text>
|
||
);
|
||
}
|
||
|
||
const styles = StyleSheet.create({
|
||
row: {
|
||
flexDirection: "row",
|
||
alignItems: "center",
|
||
},
|
||
noShrink: {
|
||
flexShrink: 0,
|
||
},
|
||
markBox: {
|
||
flexShrink: 0,
|
||
alignItems: "center",
|
||
justifyContent: "center",
|
||
},
|
||
heading: {
|
||
fontFamily: fonts.heading,
|
||
},
|
||
});
|