From d67f5c15aed09cceec2a070444fc9765d2a1202b Mon Sep 17 00:00:00 2001 From: Sean Oliver <882952+seanoliver@users.noreply.github.com> Date: Mon, 22 Sep 2025 16:07:56 -0700 Subject: [PATCH] feat: add view system and UI polish for table templates (2 of 4) - Refactor TableTemplateSelector to use view configuration pattern - Add viewConfig system for managing different UI states - Create separate view components (InitialView, CategoryView, ResultsView) - Improve UI polish with better styling and transitions - Add ViewMode enum and ViewState interface for state management --- .../TableQuickstart/TableTemplateSelector.tsx | 161 +++++++++--------- .../components/CategoryView.tsx | 42 +++++ .../components/InitialView.tsx | 87 ++++++++++ .../components/ResultsView.tsx | 31 ++++ .../components/SharedComponents.tsx | 110 ++++++++++++ .../TableQuickstart/viewConfig.tsx | 47 +++++ 6 files changed, 398 insertions(+), 80 deletions(-) create mode 100644 apps/studio/components/interfaces/TableGridEditor/SidePanelEditor/TableEditor/TableQuickstart/components/CategoryView.tsx create mode 100644 apps/studio/components/interfaces/TableGridEditor/SidePanelEditor/TableEditor/TableQuickstart/components/InitialView.tsx create mode 100644 apps/studio/components/interfaces/TableGridEditor/SidePanelEditor/TableEditor/TableQuickstart/components/ResultsView.tsx create mode 100644 apps/studio/components/interfaces/TableGridEditor/SidePanelEditor/TableEditor/TableQuickstart/components/SharedComponents.tsx create mode 100644 apps/studio/components/interfaces/TableGridEditor/SidePanelEditor/TableEditor/TableQuickstart/viewConfig.tsx diff --git a/apps/studio/components/interfaces/TableGridEditor/SidePanelEditor/TableEditor/TableQuickstart/TableTemplateSelector.tsx b/apps/studio/components/interfaces/TableGridEditor/SidePanelEditor/TableEditor/TableQuickstart/TableTemplateSelector.tsx index 2efb9c871af..86fc3feeca8 100644 --- a/apps/studio/components/interfaces/TableGridEditor/SidePanelEditor/TableEditor/TableQuickstart/TableTemplateSelector.tsx +++ b/apps/studio/components/interfaces/TableGridEditor/SidePanelEditor/TableEditor/TableQuickstart/TableTemplateSelector.tsx @@ -1,9 +1,12 @@ -import { useState, useCallback, useEffect, useMemo } from 'react' +import { useState, useCallback, useMemo } from 'react' import { toast } from 'sonner' -import { Button, cn } from 'ui' import { tableTemplates } from './templates' -import { QuickstartVariant } from './types' +import { QuickstartVariant, ViewMode } from './types' import { convertTableSuggestionToTableField } from './utils' +import { InitialView } from './components/InitialView' +import { CategoryView } from './components/CategoryView' +import { ResultsView } from './components/ResultsView' +import { createViewConfig, type ViewKey } from './viewConfig' import type { TableSuggestion } from './types' import type { TableField } from '../TableEditor.types' @@ -16,20 +19,40 @@ interface TableTemplateSelectorProps { const SUCCESS_MESSAGE_DURATION_MS = 3000 +interface ViewState { + mode: ViewMode + selectedCategory: string | null + selectedTemplate: TableSuggestion | null + generatedTables: TableSuggestion[] + error: string | null + isLoading: boolean +} + +const initialViewState: ViewState = { + mode: ViewMode.INITIAL, + selectedCategory: null, + selectedTemplate: null, + generatedTables: [], + error: null, + isLoading: false, +} + export const TableTemplateSelector = ({ - variant: _variant, + variant, onSelectTemplate, onDismiss, disabled, }: TableTemplateSelectorProps) => { - const [activeCategory, setActiveCategory] = useState(null) // null => All - const [selectedTemplate, setSelectedTemplate] = useState(null) + const [viewState, setViewState] = useState(initialViewState) + + // Memoize view configuration + const viewConfig = useMemo(() => createViewConfig(), []) const handleSelectTemplate = useCallback( (template: TableSuggestion) => { const tableField = convertTableSuggestionToTableField(template) onSelectTemplate(tableField) - setSelectedTemplate(template) + setViewState((prev) => ({ ...prev, selectedTemplate: template })) toast.success( `${template.tableName} template applied. You can add or modify the fields below.`, { @@ -40,82 +63,60 @@ export const TableTemplateSelector = ({ [onSelectTemplate] ) - const categories = useMemo(() => Object.keys(tableTemplates), []) + const handleReset = useCallback(() => { + setViewState(initialViewState) + }, []) - useEffect(() => { - if (activeCategory === null && categories.length > 0) { - setActiveCategory(categories[0]) + // Handler collection for views + const handlers = useMemo( + () => ({ + onCategorySelect: (category: string) => { + setViewState((prev) => ({ + ...prev, + mode: ViewMode.CATEGORY_SELECTED, + selectedCategory: category, + })) + }, + onBack: handleReset, + onSelectTemplate: handleSelectTemplate, + }), + [handleReset, handleSelectTemplate] + ) + + // Get the appropriate view component + const getView = (): JSX.Element | null => { + const { mode, selectedCategory } = viewState + + // Determine the view key based on mode + let viewKey: ViewKey = mode + + // Get the view configuration + const config = viewConfig.get(viewKey) + if (!config) return null + + // Check if view should render + if (config.shouldRender && !config.shouldRender(viewState)) { + return null } - }, [categories, activeCategory]) - const displayed = useMemo( - () => (activeCategory ? tableTemplates[activeCategory] || [] : []), - [activeCategory] - ) + // Get templates for category view + const templates = selectedCategory ? tableTemplates[selectedCategory] ?? [] : [] - return ( -
-
-
-

Start faster with a table template

-

- Save time by starting from a ready-made table schema. -

-
- {onDismiss && ( - - )} -
+ // Render the view with all necessary props + return config.render({ + // Components + InitialView, + CategoryView, + ResultsView, + // Props + variant, + disabled, + onDismiss, + state: viewState, + templates, + handlers, + }) + } -
- {categories.map((category) => ( - - ))} -
- -
- {displayed.map((t) => ( - - ))} -
-
- ) + return getView() } diff --git a/apps/studio/components/interfaces/TableGridEditor/SidePanelEditor/TableEditor/TableQuickstart/components/CategoryView.tsx b/apps/studio/components/interfaces/TableGridEditor/SidePanelEditor/TableEditor/TableQuickstart/components/CategoryView.tsx new file mode 100644 index 00000000000..a6ca5a133e5 --- /dev/null +++ b/apps/studio/components/interfaces/TableGridEditor/SidePanelEditor/TableEditor/TableQuickstart/components/CategoryView.tsx @@ -0,0 +1,42 @@ +import { memo } from 'react' +import type { TableSuggestion } from '../types' +import { BackButton, TemplateList } from './SharedComponents' + +interface CategoryViewProps { + category: string + templates: TableSuggestion[] + selectedTemplate: TableSuggestion | null + onBack: () => void + onSelectTemplate: (template: TableSuggestion) => void +} + +export const CategoryView = memo( + ({ category, templates, selectedTemplate, onBack, onSelectTemplate }: CategoryViewProps) => { + if (templates.length === 0) { + return ( +
+ +

+ No templates available for this category. +

+
+ ) + } + + return ( +
+
+ + {category} +
+ +
+ ) + } +) + +CategoryView.displayName = 'CategoryView' diff --git a/apps/studio/components/interfaces/TableGridEditor/SidePanelEditor/TableEditor/TableQuickstart/components/InitialView.tsx b/apps/studio/components/interfaces/TableGridEditor/SidePanelEditor/TableEditor/TableQuickstart/components/InitialView.tsx new file mode 100644 index 00000000000..7d87b6f3f84 --- /dev/null +++ b/apps/studio/components/interfaces/TableGridEditor/SidePanelEditor/TableEditor/TableQuickstart/components/InitialView.tsx @@ -0,0 +1,87 @@ +import { memo } from 'react' +import { Wand2, ChevronDown, Database } from 'lucide-react' +import { + DropdownMenu, + DropdownMenuContent, + DropdownMenuItem, + DropdownMenuSeparator, + DropdownMenuTrigger, + Button, +} from 'ui' +import { QuickstartVariant } from '../types' +import { tableTemplates } from '../templates' +import { AI_QUICK_IDEAS } from '../constants' +import { SelectorHeader } from './SharedComponents' + +interface InitialViewProps { + variant: Exclude + disabled?: boolean + onDismiss?: () => void + onCategorySelect: (category: string) => void + onAISelect: () => void + onQuickIdea: (idea: string) => void +} + +export const InitialView = memo( + ({ + variant, + disabled, + onDismiss, + onCategorySelect, + onAISelect, + onQuickIdea, + }: InitialViewProps) => { + const isAI = variant === QuickstartVariant.AI + const icon = isAI ? : + const label = isAI ? 'Generate with AI' : 'Select from templates' + + return ( +
+ + + + + + + {isAI ? ( + <> + + + +
Quick ideas
+ {AI_QUICK_IDEAS.map((example) => ( + onQuickIdea(example)}> + {example} + + ))} + + ) : ( + Object.keys(tableTemplates).map((category) => ( + onCategorySelect(category)}> + + )) + )} +
+
+
+ ) + } +) + +InitialView.displayName = 'InitialView' diff --git a/apps/studio/components/interfaces/TableGridEditor/SidePanelEditor/TableEditor/TableQuickstart/components/ResultsView.tsx b/apps/studio/components/interfaces/TableGridEditor/SidePanelEditor/TableEditor/TableQuickstart/components/ResultsView.tsx new file mode 100644 index 00000000000..c71b2154f3e --- /dev/null +++ b/apps/studio/components/interfaces/TableGridEditor/SidePanelEditor/TableEditor/TableQuickstart/components/ResultsView.tsx @@ -0,0 +1,31 @@ +import { memo } from 'react' +import type { TableSuggestion } from '../types' +import { BackButton, TemplateList } from './SharedComponents' + +interface ResultsViewProps { + tables: TableSuggestion[] + selectedTemplate: TableSuggestion | null + title: string + onBack: () => void + onSelectTemplate: (template: TableSuggestion) => void +} + +export const ResultsView = memo( + ({ tables, selectedTemplate, title, onBack, onSelectTemplate }: ResultsViewProps) => ( +
+
+ + {title} +
+
+ +
+
+ ) +) + +ResultsView.displayName = 'ResultsView' diff --git a/apps/studio/components/interfaces/TableGridEditor/SidePanelEditor/TableEditor/TableQuickstart/components/SharedComponents.tsx b/apps/studio/components/interfaces/TableGridEditor/SidePanelEditor/TableEditor/TableQuickstart/components/SharedComponents.tsx new file mode 100644 index 00000000000..a9d0b1323b3 --- /dev/null +++ b/apps/studio/components/interfaces/TableGridEditor/SidePanelEditor/TableEditor/TableQuickstart/components/SharedComponents.tsx @@ -0,0 +1,110 @@ +import { memo } from 'react' +import { ArrowLeft, Columns3, X } from 'lucide-react' +import { Button, cn } from 'ui' +import type { TableSuggestion } from '../types' + +export const BackButton = memo( + ({ + onClick, + disabled = false, + label = 'Back', + }: { + onClick: () => void + disabled?: boolean + label?: string + }) => ( + + ) +) +BackButton.displayName = 'BackButton' + +export const SelectorHeader = memo(({ onDismiss }: { onDismiss?: () => void }) => ( +
+ + {onDismiss && ( + + )} +
+)) +SelectorHeader.displayName = 'SelectorHeader' + +export const TemplateItem = memo( + ({ + template, + isSelected, + onClick, + }: { + template: TableSuggestion + isSelected: boolean + onClick: () => void + }) => ( + + ) +) +TemplateItem.displayName = 'TemplateItem' + +export const TemplateList = memo( + ({ + templates, + selectedTemplate, + onSelectTemplate, + }: { + templates: TableSuggestion[] + selectedTemplate: TableSuggestion | null + onSelectTemplate: (template: TableSuggestion) => void + }) => ( +
+ {templates.map((template) => ( + onSelectTemplate(template)} + /> + ))} +
+ ) +) +TemplateList.displayName = 'TemplateList' diff --git a/apps/studio/components/interfaces/TableGridEditor/SidePanelEditor/TableEditor/TableQuickstart/viewConfig.tsx b/apps/studio/components/interfaces/TableGridEditor/SidePanelEditor/TableEditor/TableQuickstart/viewConfig.tsx new file mode 100644 index 00000000000..1136693930a --- /dev/null +++ b/apps/studio/components/interfaces/TableGridEditor/SidePanelEditor/TableEditor/TableQuickstart/viewConfig.tsx @@ -0,0 +1,47 @@ +import { QuickstartVariant, ViewMode } from './types' + +export type ViewConfig = { + variant: QuickstartVariant + mode: ViewMode + render: (props: any) => JSX.Element | null + shouldRender?: (state: any) => boolean +} + +export type ViewKey = ViewMode.INITIAL | ViewMode.CATEGORY_SELECTED + +// Create a view configuration map for the template system +export const createViewConfig = (): Map => { + const config = new Map() + + // Initial view - category selection + config.set(ViewMode.INITIAL, { + variant: QuickstartVariant.TEMPLATES, + mode: ViewMode.INITIAL, + render: ({ InitialView, variant, disabled, onDismiss, handlers }) => ( + + ), + }) + + // Category selected - show templates + config.set(ViewMode.CATEGORY_SELECTED, { + variant: QuickstartVariant.TEMPLATES, + mode: ViewMode.CATEGORY_SELECTED, + render: ({ CategoryView, state, templates, handlers }) => ( + + ), + shouldRender: (state) => !!state.selectedCategory, + }) + + return config +}