BillTracker/client/components/brand/Brand.tsx

53 lines
1.4 KiB
TypeScript
Raw Normal View History

import { type ComponentProps } from 'react';
import { cn } from '@/lib/utils';
import { BRAND, BRAND_GLYPHS, type BrandGlyphName } from '@/lib/brand';
import { useTheme } from '@/contexts/ThemeContext';
type ImgProps = Omit<ComponentProps<'img'>, 'src' | 'alt'>;
export function BrandMark({ className, ...props }: ImgProps) {
const { resolvedTheme } = useTheme();
const src = resolvedTheme === 'light' ? BRAND.assets.logoLight : BRAND.assets.logo;
return (
<img
src={src}
alt={BRAND.name}
className={cn('object-contain', className)}
{...props}
/>
);
}
export function BrandWordmark({ compact = false, className }: { compact?: boolean; className?: string }) {
return (
<span className={cn('font-semibold tracking-tight text-foreground', className)}>
{compact ? BRAND.compactName : BRAND.name}
</span>
);
}
export function BrandGlyph({
name,
className,
}: {
name: BrandGlyphName;
className?: string;
}) {
const glyph = BRAND_GLYPHS[name];
return (
<img
src={glyph.asset}
alt={glyph.label}
width={40}
height={40}
draggable={false}
// Fail soft: if the asset can't load, hide rather than show a broken image —
// the adjacent title/label carries the meaning.
onError={(e) => { e.currentTarget.style.display = 'none'; }}
className={cn('inline-block h-10 w-10 shrink-0 select-none rounded-xl object-contain shadow-sm', className)}
/>
);
}