Simplify board component picker

This commit is contained in:
Mike Cao committed 2026-03-10 16:52:02 -07:00
1 parent 0b7a545df4
commit ad7b64653f
2 files changed
+59 -42

No files matched your search

@@ -24,10 +24,9 @@ import {
isOpenBoardType,
} from '@/lib/boards';
import {
CATEGORIES,
type ComponentDefinition,
type ConfigField,
getComponentsByCategory,
getComponentDefinitions,
} from '../boardComponentRegistry';
import { BoardComponentRenderer } from './BoardComponentRenderer';
@@ -62,7 +61,7 @@ export function BoardComponentSelect({
const [description, setDescription] = useState('');
const allDefinitions = useMemo(
() => CATEGORIES.flatMap(category => getComponentsByCategory(category.key)),
() => getComponentDefinitions().toSorted((a, b) => a.name.localeCompare(b.name)),
[],
);
const activeEntityType = isOpenBoardType(boardType) ? selectedEntityType : boardEntityType;
@@ -189,6 +188,14 @@ export function BoardComponentSelect({
: null;
const canSave = !!selectedDef && isSelectedDefSupported && (!needsWebsite || !!resolvedEntityId);
const availableDefinitions = useMemo(
() =>
allDefinitions.filter(
def =>
isBoardComponentSupported(def.type, activeEntityType) || def.type === selectedDef?.type,
),
[activeEntityType, allDefinitions, selectedDef?.type],
);
return (
<Column gap="4">
@@ -310,48 +317,38 @@ export function BoardComponentSelect({
<Panel maxHeight="100%">
{hasSelectedEntity ? (
<Column gap="1" height="100%" style={{ overflowY: 'auto' }}>
{CATEGORIES.map(category => {
const components = getComponentsByCategory(category.key).filter(def =>
isBoardComponentSupported(def.type, activeEntityType) ||
def.type === selectedDef?.type,
);
if (!components.length) {
return null;
}
{availableDefinitions.map(def => {
const Icon = def.icon;
return (
<Column key={category.key} gap="1" marginBottom="2">
<Text weight="bold">{category.name}</Text>
{components.map(def => (
<Focusable key={def.type}>
<Row
alignItems="center"
paddingX="3"
paddingY="2"
borderRadius
backgroundColor={
selectedDef?.type === def.type ? 'surface-sunken' : undefined
}
hover={{ backgroundColor: 'surface-sunken' }}
style={{ cursor: 'pointer' }}
onClick={() => handleSelectComponent(def)}
<Focusable key={def.type}>
<Row
gap="3"
alignItems="center"
paddingX="3"
paddingY="2"
borderRadius
backgroundColor={
selectedDef?.type === def.type ? 'surface-sunken' : undefined
}
hover={{ backgroundColor: 'surface-sunken' }}
style={{ cursor: 'pointer' }}
onClick={() => handleSelectComponent(def)}
>
<Icon size={16} />
<Column gap="1">
<Text
size="sm"
weight={selectedDef?.type === def.type ? 'bold' : undefined}
>
<Column>
<Text
size="sm"
weight={selectedDef?.type === def.type ? 'bold' : undefined}
>
{def.name}
</Text>
<Text size="xs" color="muted">
{def.description}
</Text>
</Column>
</Row>
</Focusable>
))}
</Column>
{def.name}
</Text>
<Text size="xs" color="muted">
{def.description}
</Text>
</Column>
</Row>
</Focusable>
);
})}
</Column>
@@ -2,6 +2,14 @@ import type { ComponentType } from 'react';
import { TextBlock } from '@/app/(main)/boards/TextBlock';
import { WebsiteChart } from '@/app/(main)/websites/[websiteId]/WebsiteChart';
import { WebsiteMetricsBar } from '@/app/(main)/websites/[websiteId]/WebsiteMetricsBar';
import {
Activity,
Calendar,
ChartPie,
FileText,
Globe,
Sheet,
} from '@/components/icons';
import { EventsChart } from '@/components/metrics/EventsChart';
import { MetricsTable } from '@/components/metrics/MetricsTable';
import { WeeklyTraffic } from '@/components/metrics/WeeklyTraffic';
@@ -20,6 +28,7 @@ export interface ComponentDefinition {
name: string;
description: string;
category: string;
icon: ComponentType<any>;
component: ComponentType<any>;
defaultProps?: Record<string, any>;
configFields?: ConfigField[];
@@ -66,6 +75,7 @@ const componentDefinitions: ComponentDefinition[] = [
name: 'Metrics bar',
description: 'Key metrics: views, visitors, bounces, time on site',
category: 'overview',
icon: ChartPie,
component: WebsiteMetricsBar,
},
{
@@ -73,6 +83,7 @@ const componentDefinitions: ComponentDefinition[] = [
name: 'Website chart',
description: 'Page views and visitors over time',
category: 'overview',
icon: Activity,
component: WebsiteChart,
},
@@ -82,6 +93,7 @@ const componentDefinitions: ComponentDefinition[] = [
name: 'Metrics table',
description: 'Table of metrics by dimension',
category: 'tables',
icon: Sheet,
component: MetricsTable,
defaultProps: { type: 'path', limit: 10 },
configFields: [
@@ -108,6 +120,7 @@ const componentDefinitions: ComponentDefinition[] = [
name: 'World map',
description: 'Geographic distribution of visitors',
category: 'visualization',
icon: Globe,
component: WorldMap,
},
{
@@ -115,6 +128,7 @@ const componentDefinitions: ComponentDefinition[] = [
name: 'Weekly traffic',
description: 'Traffic heatmap by day and hour',
category: 'visualization',
icon: Calendar,
component: WeeklyTraffic,
},
{
@@ -122,6 +136,7 @@ const componentDefinitions: ComponentDefinition[] = [
name: 'Events chart',
description: 'Custom events over time',
category: 'visualization',
icon: ChartPie,
component: EventsChart,
},
@@ -131,6 +146,7 @@ const componentDefinitions: ComponentDefinition[] = [
name: 'Text',
description: 'Free-form text content',
category: 'content',
icon: FileText,
component: TextBlock,
requiresWebsite: false,
defaultProps: { text: '' },
@@ -147,6 +163,10 @@ const componentDefinitions: ComponentDefinition[] = [
const definitionMap = new Map(componentDefinitions.map(def => [def.type, def]));
export function getComponentDefinitions(): ComponentDefinition[] {
return componentDefinitions;
}
export function getComponentDefinition(type: string): ComponentDefinition | undefined {
return definitionMap.get(type);
}