From 9e8381e0323b053c629707eac896bbaeb18cc32e Mon Sep 17 00:00:00 2001 From: David Montero Crespo Date: Wed, 22 Jul 2026 20:39:54 +0200 Subject: [PATCH] feat(picker): online-only COMPONENT ad cards + overlay datasheet seam MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Components that only exist in the hosted editor now advertise themselves in the picker exactly like the online-only boards do: ONLINE_ONLY_COMPONENT_ADS (same module as the board ads) renders an ONLINE-badged card that links to velxio.com, auto-hidden in any build whose ComponentRegistry has the real component (the hosted overlay merges it in — no per-build switches). First entries: the M5Stack Chain RGB/Mono 8x8 matrices. componentDocs gains registerComponentDoc(id, raw) so an overlay can supply the hover datasheet for components it injects at runtime. --- .../src/components/ComponentPickerModal.tsx | 44 ++++++++++++++++++- frontend/src/components/componentDocs.ts | 12 +++++ frontend/src/lib/onlineOnlyBoards.ts | 34 ++++++++++++++ 3 files changed, 89 insertions(+), 1 deletion(-) diff --git a/frontend/src/components/ComponentPickerModal.tsx b/frontend/src/components/ComponentPickerModal.tsx index 36708ad0..73070515 100644 --- a/frontend/src/components/ComponentPickerModal.tsx +++ b/frontend/src/components/ComponentPickerModal.tsx @@ -35,8 +35,10 @@ import { BOARD_KIND_LABELS } from '../types/board'; import { isProBoardKind } from '../lib/proBoardGate'; import { ONLINE_ONLY_BOARD_ADS, + ONLINE_ONLY_COMPONENT_ADS, ONLINE_EDITOR_URL, type OnlineOnlyBoardAd, + type OnlineOnlyComponentAd, } from '../lib/onlineOnlyBoards'; import raspberryPi3Svg from '../assets/Raspberry_Pi_3_illustration.svg'; import raspberryPi4Png from '../assets/raspberry-pi-4-board.png'; @@ -203,6 +205,20 @@ export const ComponentPickerModal: React.FC = ({ return components; }, [searchQuery, selectedCategory, registry, isLoading]); + // Online-only component ads: shown where the real component would sit, and + // hidden automatically in any build whose registry has the real component + // (the hosted overlay merges it in) — same contract as VISIBLE_BOARD_ADS. + const visibleComponentAds = useMemo(() => { + if (isLoading) return []; + const q = searchQuery.toLowerCase(); + return ONLINE_ONLY_COMPONENT_ADS.filter( + (ad) => + !registry.getById(ad.id) && + (selectedCategory === 'all' || ad.category === selectedCategory) && + (!q || ad.label.toLowerCase().includes(q)), + ); + }, [registry, isLoading, searchQuery, selectedCategory]); + // Get available categories const categories = useMemo(() => { if (isLoading) return []; @@ -357,7 +373,7 @@ export const ComponentPickerModal: React.FC = ({

{t('editor.componentPicker.loading')}

- ) : filteredComponents.length === 0 ? ( + ) : filteredComponents.length === 0 && visibleComponentAds.length === 0 ? (

{t('editor.componentPicker.noResults')}

{searchQuery && ( @@ -393,6 +409,10 @@ export const ComponentPickerModal: React.FC = ({ /> )) )} + {!isLoading && + visibleComponentAds.map((ad) => ( + + ))}
@@ -764,6 +784,28 @@ const OnlineBadge: React.FC = () => ( ); +/** Advertisement card for a component only available in the hosted editor. */ +const OnlineOnlyComponentCard: React.FC<{ ad: OnlineOnlyComponentAd }> = ({ ad }) => ( + +); + /** Advertisement card for a board only available in the hosted editor. */ const OnlineOnlyBoardCard: React.FC<{ ad: OnlineOnlyBoardAd }> = ({ ad }) => (