# Phase 1: Foundation **Estimated Time:** 1-2 hours **Complexity:** Low **Prerequisites:** None ## Overview Set up the basic directory structure, TypeScript interfaces, and a minimal React component that can be registered with SidebarModel. By the end of this phase, the panel should mount in the sidebar showing placeholder content. --- ## Goals - ✅ Create directory structure for new React component - ✅ Define TypeScript interfaces for component data - ✅ Create minimal ComponentsPanel React component - ✅ Register component with SidebarModel - ✅ Port base CSS styles to SCSS module - ✅ Verify panel mounts without errors --- ## Step 1: Create Directory Structure ### 1.1 Create Main Directory ```bash mkdir -p packages/noodl-editor/src/editor/src/views/panels/ComponentsPanel cd packages/noodl-editor/src/editor/src/views/panels/ComponentsPanel ``` ### 1.2 Create Subdirectories ```bash mkdir components mkdir hooks ``` ### Final Structure ``` ComponentsPanel/ ├── components/ # UI components ├── hooks/ # React hooks ├── ComponentsPanel.tsx ├── ComponentsPanel.module.scss ├── types.ts └── index.ts ``` --- ## Step 2: Define TypeScript Interfaces ### 2.1 Create `types.ts` Create comprehensive type definitions: ```typescript import { ComponentModel } from '@noodl-models/componentmodel'; import { ComponentsPanelFolder } from '../componentspanel/ComponentsPanelFolder'; /** * Props accepted by ComponentsPanel component */ export interface ComponentsPanelProps { /** Current node graph editor instance */ nodeGraphEditor?: TSFixme; /** Lock to a specific sheet */ lockCurrentSheetName?: string; /** Show the sheet section */ showSheetList: boolean; /** List of sheets we want to hide */ hideSheets?: string[]; /** Change the title of the component header */ componentTitle?: string; } /** * Data for rendering a component item */ export interface ComponentItemData { type: 'component'; component: ComponentModel; folder: ComponentsPanelFolder; name: string; fullName: string; isSelected: boolean; isRoot: boolean; isPage: boolean; isCloudFunction: boolean; isVisual: boolean; canBecomeRoot: boolean; hasWarnings: boolean; // Future: migration status for TASK-004 // migrationStatus?: 'needs-review' | 'ai-migrated' | 'auto' | 'manually-fixed'; } /** * Data for rendering a folder item */ export interface FolderItemData { type: 'folder'; folder: ComponentsPanelFolder; name: string; path: string; isOpen: boolean; isSelected: boolean; isRoot: boolean; isPage: boolean; isCloudFunction: boolean; isVisual: boolean; isComponentFolder: boolean; // Folder that also has a component canBecomeRoot: boolean; hasWarnings: boolean; children: TreeNode[]; } /** * Tree node can be either component or folder */ export type TreeNode = ComponentItemData | FolderItemData; /** * Sheet/tab information */ export interface SheetData { name: string; displayName: string; folder: ComponentsPanelFolder; isDefault: boolean; isSelected: boolean; } /** * Context menu item configuration */ export interface ContextMenuItem { icon?: string; label: string; onClick: () => void; type?: 'divider'; } ``` --- ## Step 3: Create Base Component ### 3.1 Create `ComponentsPanel.tsx` Start with a minimal shell: ```typescript /** * ComponentsPanel * * Modern React implementation of the components sidebar panel. * Displays project component hierarchy with folders, allows drag-drop reorganization, * and provides context menus for component/folder operations. */ import React from 'react'; import css from './ComponentsPanel.module.scss'; import { ComponentsPanelProps } from './types'; export function ComponentsPanel(props: ComponentsPanelProps) { const { nodeGraphEditor, showSheetList = true, hideSheets = [], componentTitle = 'Components', lockCurrentSheetName } = props; return (
{componentTitle}
{showSheetList && (
Sheets
{/* Sheet tabs will go here */}
Default
)}
Components
{/* Placeholder content */}
📁 Folder 1
📄 Component 1
📄 Component 2
); } ``` --- ## Step 4: Create Base Styles ### 4.1 Create `ComponentsPanel.module.scss` Port essential styles from the legacy CSS: ```scss /** * ComponentsPanel Styles * Ported from legacy componentspanel.css */ .ComponentsPanel { display: flex; flex-direction: column; height: 100%; background-color: var(--theme-color-bg-1); color: var(--theme-color-fg-default); overflow: hidden; } /* Header sections */ .Header, .SheetsHeader, .ComponentsHeader { display: flex; align-items: center; justify-content: space-between; padding: 8px 10px; font: 11px var(--font-family-bold); color: var(--theme-color-fg-default); background-color: var(--theme-color-bg-2); border-bottom: 1px solid var(--theme-color-border-default); } .Title { flex: 1; text-transform: uppercase; letter-spacing: 0.5px; } .AddButton { width: 20px; height: 20px; display: flex; align-items: center; justify-content: center; background: transparent; border: none; color: var(--theme-color-fg-default); cursor: pointer; border-radius: 3px; transition: background-color 0.15s ease; &:hover { background-color: var(--theme-color-bg-3); } } .AddIcon { font-size: 14px; font-weight: bold; } /* Sheets section */ .SheetsSection { border-bottom: 1px solid var(--theme-color-border-default); } .SheetsList { max-height: 250px; overflow-y: auto; overflow-x: hidden; } .SheetItem { padding: 8px 10px 8px 30px; font: 11px var(--font-family-regular); cursor: pointer; transition: background-color 0.15s ease; &:hover { background-color: var(--theme-color-bg-3); } } /* Components list */ .ComponentsScroller { flex: 1; overflow-y: auto; overflow-x: hidden; min-height: 0; } .ComponentsList { padding: 4px 0; } /* Placeholder items (temporary for Phase 1) */ .PlaceholderItem { padding: 8px 10px 8px 23px; font: 11px var(--font-family-regular); color: var(--theme-color-fg-default); cursor: pointer; transition: background-color 0.15s ease; &:hover { background-color: var(--theme-color-bg-3); } } /* Custom scrollbar */ .ComponentsScroller::-webkit-scrollbar { width: 8px; } .ComponentsScroller::-webkit-scrollbar-track { background: var(--theme-color-bg-1); } .ComponentsScroller::-webkit-scrollbar-thumb { background: var(--theme-color-bg-4); border-radius: 4px; &:hover { background: var(--theme-color-fg-muted); } } ``` --- ## Step 5: Create Export File ### 5.1 Create `index.ts` ```typescript export { ComponentsPanel } from './ComponentsPanel'; export * from './types'; ``` --- ## Step 6: Register with SidebarModel ### 6.1 Update `router.setup.ts` Find the existing ComponentsPanel registration and update it: **Before:** ```typescript const ComponentsPanel = require('./views/panels/componentspanel/ComponentsPanel').ComponentsPanelView; ``` **After:** ```typescript import { ComponentsPanel } from './views/panels/ComponentsPanel'; ``` **Registration (should already exist, just verify):** ```typescript SidebarModel.instance.register({ id: 'components', name: 'Components', order: 1, icon: IconName.Components, onOpen: (args) => { const panel = new ComponentsPanel({ nodeGraphEditor: args.context.nodeGraphEditor, showSheetList: true, hideSheets: ['__cloud__'] }); panel.render(); return panel.el; } }); ``` **Update to:** ```typescript SidebarModel.instance.register({ id: 'components', name: 'Components', order: 1, icon: IconName.Components, panel: ComponentsPanel, panelProps: { nodeGraphEditor: undefined, // Will be set by SidePanel showSheetList: true, hideSheets: ['__cloud__'] } }); ``` **Note:** Check how `SidebarModel` handles React components. You may need to look at how `SearchPanel.tsx` or other React panels are registered. --- ## Step 7: Testing ### 7.1 Build and Run ```bash npm run dev ``` ### 7.2 Verification Checklist - [ ] No TypeScript compilation errors - [ ] Application starts without errors - [ ] Clicking "Components" icon in sidebar shows panel - [ ] Panel displays with header "Components" - [ ] "+" button appears in header - [ ] Placeholder items are visible - [ ] If `showSheetList` is true, "Sheets" section appears - [ ] No console errors or warnings - [ ] Styles look consistent with other sidebar panels ### 7.3 Test Edge Cases - [ ] Panel resizes correctly with window - [ ] Scrollbar appears if content overflows - [ ] Panel switches correctly with other sidebar panels --- ## Common Issues & Solutions ### Issue: Panel doesn't appear **Solution:** Check that `SidebarModel` registration is correct. Look at how other React panels like `SearchPanel` are registered. ### Issue: Styles not applying **Solution:** Verify CSS module import path is correct and webpack is configured to handle `.module.scss` files. ### Issue: TypeScript errors with ComponentModel **Solution:** Ensure all `@noodl-models` imports are available. Check `tsconfig.json` paths. ### Issue: "nodeGraphEditor" prop undefined **Solution:** `SidePanel` should inject this. Check that prop passing matches other panels. --- ## Reference Files **Legacy Implementation:** - `packages/noodl-editor/src/editor/src/views/panels/componentspanel/ComponentsPanel.ts` - `packages/noodl-editor/src/editor/src/templates/componentspanel.html` - `packages/noodl-editor/src/editor/src/styles/componentspanel.css` **React Panel Examples:** - `packages/noodl-editor/src/editor/src/views/panels/SearchPanel/SearchPanel.tsx` - `packages/noodl-editor/src/editor/src/views/VersionControlPanel/VersionControlPanel.tsx` **SidebarModel:** - `packages/noodl-editor/src/editor/src/models/sidebar/sidebarmodel.tsx` --- ## Success Criteria ✅ **Phase 1 is complete when:** 1. New directory structure exists 2. TypeScript types are defined 3. ComponentsPanel React component renders 4. Component is registered with SidebarModel 5. Panel appears when clicking Components icon 6. Placeholder content is visible 7. No console errors 8. All TypeScript compiles without errors --- ## Next Phase **Phase 2: Tree Rendering** - Connect to ProjectModel and render actual component tree structure.