fix(picker): Pi family component cards — full illustrations + PRO badge

The registry's Pi Zero/1/2/3/4/5 component entries rendered the live
velxio-raspberry-pi-* element clipped to a sliver and carried no PRO
marker. Reuse the board illustrations keyed by tagName (Zero/1/2
intentionally share the Pi 3 art) and show the shared gold PRO pill on
any component whose id is a pro board kind (Pi Linux family + STM32).
This commit is contained in:
David Montero 2026-07-21 00:19:01 +02:00
parent 1ce0bad5c8
commit a000fba154
1 changed files with 47 additions and 24 deletions

View File

@ -343,17 +343,53 @@ const PASSIVE_TAGS = new Set([
'wokwi-inductor', 'wokwi-inductor',
]); ]);
// Static illustrations for the Pi Linux family. A live velxio-raspberry-pi-*
// custom element at natural size + CSS scale keeps its unscaled layout box,
// so the 100px thumbnail clips it to a sliver — images render fully instead.
// Keyed by tagName because the registry's Pi Zero/1/2 entries deliberately
// reuse the Pi 3 board art.
const PI_BOARD_ART: Record<string, string> = {
'velxio-raspberry-pi-3': raspberryPi3Svg,
'velxio-raspberry-pi-4': raspberryPi4Png,
'velxio-raspberry-pi-5': raspberryPi5Png,
};
/** Gold PRO pill shown on cards for paid-gated boards (Pi Linux + STM32). */
const ProBadge: React.FC = () => (
<span
title="Pro feature — paid plan or Velxio Desktop"
style={{
position: 'absolute',
top: 8,
right: 8,
zIndex: 1,
padding: '3px 10px',
borderRadius: 999,
fontSize: 11,
fontWeight: 700,
letterSpacing: 0.6,
color: '#1a1205',
background: 'linear-gradient(180deg,#ffd566,#f5a623)',
boxShadow: '0 1px 4px rgba(0,0,0,0.35)',
}}
>
PRO
</span>
);
const ComponentCard: React.FC<ComponentCardProps> = ({ component, onSelect }) => { const ComponentCard: React.FC<ComponentCardProps> = ({ component, onSelect }) => {
const thumbnailRef = React.useRef<HTMLDivElement>(null); const thumbnailRef = React.useRef<HTMLDivElement>(null);
const usePresetSvg = const usePresetSvg =
PASSIVE_TAGS.has(component.tagName) && PASSIVE_TAGS.has(component.tagName) &&
typeof component.thumbnail === 'string' && typeof component.thumbnail === 'string' &&
component.thumbnail.trim().startsWith('<svg'); component.thumbnail.trim().startsWith('<svg');
const boardArt = PI_BOARD_ART[component.tagName];
// Render actual web component as thumbnail // Render actual web component as thumbnail
React.useEffect(() => { React.useEffect(() => {
if (!thumbnailRef.current) return; if (!thumbnailRef.current) return;
if (usePresetSvg) return; // SVG is rendered via dangerouslySetInnerHTML below if (usePresetSvg) return; // SVG is rendered via dangerouslySetInnerHTML below
if (boardArt) return; // static illustration rendered below
// Create the actual wokwi element // Create the actual wokwi element
const element = document.createElement(component.tagName); const element = document.createElement(component.tagName);
@ -397,12 +433,19 @@ const ComponentCard: React.FC<ComponentCardProps> = ({ component, onSelect }) =>
thumbnailRef.current.innerHTML = ''; thumbnailRef.current.innerHTML = '';
} }
}; };
}, [component.tagName, component.defaultValues, usePresetSvg]); }, [component.tagName, component.defaultValues, usePresetSvg, boardArt]);
return ( return (
<button className="component-card" onClick={onSelect}> <button className="component-card" onClick={onSelect} style={{ position: 'relative' }}>
{isProBoardKind(component.id) && <ProBadge />}
<div className="card-thumbnail"> <div className="card-thumbnail">
{usePresetSvg ? ( {boardArt ? (
<img
src={boardArt}
alt={component.name}
style={{ maxWidth: '100%', maxHeight: '100%', objectFit: 'contain' }}
/>
) : usePresetSvg ? (
<div <div
className="component-preview" className="component-preview"
dangerouslySetInnerHTML={{ __html: component.thumbnail }} dangerouslySetInnerHTML={{ __html: component.thumbnail }}
@ -517,27 +560,7 @@ const BoardCard: React.FC<BoardCardProps> = ({ kind, onSelect }) => {
return ( return (
<button className="component-card" onClick={onSelect} style={{ position: 'relative' }}> <button className="component-card" onClick={onSelect} style={{ position: 'relative' }}>
{isProBoardKind(kind) && ( {isProBoardKind(kind) && <ProBadge />}
<span
title="Pro feature — paid plan or Velxio Desktop"
style={{
position: 'absolute',
top: 8,
right: 8,
zIndex: 1,
padding: '3px 10px',
borderRadius: 999,
fontSize: 11,
fontWeight: 700,
letterSpacing: 0.6,
color: '#1a1205',
background: 'linear-gradient(180deg,#ffd566,#f5a623)',
boxShadow: '0 1px 4px rgba(0,0,0,0.35)',
}}
>
PRO
</span>
)}
<div className="card-thumbnail"> <div className="card-thumbnail">
{reactThumbnail ? reactThumbnail : <div ref={thumbnailRef} className="component-preview" />} {reactThumbnail ? reactThumbnail : <div ref={thumbnailRef} className="component-preview" />}
</div> </div>