Simplify board component picker
This commit is contained in:
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);
|
||||
}
|
||||
|
||||
Reference in new issue
Block a user