// Option visuals shared by every RecordPicker configuration: a thumbnail // (image / lucide icon name / initials fallback) and the leading visual of an // option (image, else icon, else color dot, else initials). import { useState } from 'react' import { InitialsAvatar } from '@asteby/metacore-ui/primitives' import { resolveColorCss } from '@asteby/metacore-ui/lib' import { DynamicIcon, isLucideIconName } from './dynamic-icon' import type { ResolvedOption } from './use-options-resolver' /** * Small square thumbnail for an option's `image`. When the option has no image * it falls back to a deterministic initials avatar (shared `InitialsAvatar`) * derived from `name`, so an imageless reference reads as a colored badge rather * than an empty placeholder — and rows/triggers stay aligned. `size` is in * pixels (kept small — 20–24px — so the picker reads as a list, not a gallery). * Inline style for the box dimensions: arbitrary Tailwind classes from a * federated addon don't always survive the host's class scan. */ export function OptionThumb({ image, name, size = 20, }: { image?: string | null name?: string | null size?: number }) { const [broken, setBroken] = useState(false) const box = { width: size, height: size } // Missing or 404 image (bare avatar filenames often 404) → initials, never // an invisible box that looks like "no avatar". if (!image || broken) { return } // A lucide icon name stored where an image url/path is expected (the `icon` // form widget's icon mode) → render the glyph instead of a broken . if (isLucideIconName(image)) { return ( ) } return ( setBroken(true)} /> ) } /** * Leading visual for an option: a photo thumbnail (FK relations with an image), * else a declared icon, else a color dot (enum/status options with a color). * Returns null when the option carries none, so plain text options stay plain. */ export function OptionLead({ option, size = 20, }: { option?: Pick | null size?: number }) { if (!option) return null if (option.image) return if (option.icon) { return ( ) } if (option.color) { return ( ) } // No image/icon/color: an imageless reference option. Show its initials // (shared InitialsAvatar) so it stays visually aligned with the sibling // options that DO carry a thumbnail, rather than a blank gap. if (option.label) return return null }