From 53b1c59302f764ff2b26a86ef68aa89e27bb88fb Mon Sep 17 00:00:00 2001 From: Nate Kelley Date: Wed, 13 Aug 2025 10:34:23 -0600 Subject: [PATCH] Update PRD --- .../ui/layouts/AppSplitter2/README.md | 492 ++++++++++++++++++ apps/web-tss/tsconfig.storybook.json | 32 -- apps/web/.storybook/main.ts | 2 +- 3 files changed, 493 insertions(+), 33 deletions(-) create mode 100644 apps/web-tss/src/components/ui/layouts/AppSplitter2/README.md delete mode 100644 apps/web-tss/tsconfig.storybook.json diff --git a/apps/web-tss/src/components/ui/layouts/AppSplitter2/README.md b/apps/web-tss/src/components/ui/layouts/AppSplitter2/README.md new file mode 100644 index 000000000..fee19b7d5 --- /dev/null +++ b/apps/web-tss/src/components/ui/layouts/AppSplitter2/README.md @@ -0,0 +1,492 @@ +# Panel Resize Component PRD + +> **Last Updated**: January 2025 +> **Version**: 1.0.0 +> **Status**: Draft + +## Overview + +This PRD defines a flexible panel resize system consisting of three main components: `PanelGroup`, `Panel`, and `Splitter`. The system allows users to create resizable panel layouts with configurable constraints and behaviors. + +## Component Architecture + +### 1. PanelGroup Component + +The `PanelGroup` is the parent container that manages the layout and resize behavior of its child panels. + +#### Props + +```typescript +interface PanelGroupProps { + // Core Props + children: React.ReactNode; // Must contain Panel components + defaultLayout?: number[]; // Array of initial sizes for each panel (percentages or pixels) + + // Resize Configuration + allowResize?: boolean; // Default: true. Enables/disables resize functionality + split?: 'vertical' | 'horizontal'; // Default: 'vertical'. Direction of split + + // Size Constraints + minSizes?: (number | string)[]; // Min width/height for each panel (e.g., [100, '20%', 200]) + maxSizes?: (number | string)[]; // Max width/height for each panel (e.g., [500, '80%', 400]) + + // Preservation Settings + preservedPanels?: number[]; // Array of panel indices that maintain pixel precision on window resize + + // Splitter Configuration + hideSplitter?: boolean; // Default: false. Hides splitter but maintains resize functionality if allowResize is true + splitterSize?: number; // Default: 4. Width/height of splitter in pixels + + // Styling + className?: string; // CSS class for the container + style?: React.CSSProperties; // Inline styles for the container + + // Callbacks + onResize?: (sizes: number[]) => void; // Called when panels are resized + onResizeStart?: () => void; // Called when resize starts + onResizeEnd?: (sizes: number[]) => void; // Called when resize ends +} +``` + +#### Behavior + +1. **Layout Management**: + - Manages the overall layout direction (vertical/horizontal) + - Calculates and maintains panel sizes + - Handles window resize events + +2. **Size Calculation**: + - Converts between pixels and percentages + - Enforces min/max constraints + - Distributes available space among panels + +3. **Preservation Logic**: + - Preserved panels maintain their pixel size during window resize + - Non-preserved panels use flex-grow to fill remaining space + - When resizing manually, all panels respect their constraints + +### 2. Panel Component + +The `Panel` component represents an individual resizable panel within the PanelGroup. + +#### Props + +```typescript +interface PanelProps { + // Core Props + children: React.ReactNode; // Panel content + id?: string; // Optional unique identifier + + // Size Configuration + defaultSize?: number | string; // Initial size (overrides PanelGroup defaultLayout) + minSize?: number | string; // Min width/height (overrides PanelGroup minSizes) + maxSize?: number | string; // Max width/height (overrides PanelGroup maxSizes) + + // Behavior + collapsible?: boolean; // Default: false. Allows panel to collapse to 0 + resizable?: boolean; // Default: true. Can be resized (overrides group setting) + + // Styling + className?: string; // CSS class for the panel + style?: React.CSSProperties; // Inline styles for the panel + + // Callbacks + onResize?: (size: number) => void; // Called when this panel is resized + onCollapse?: () => void; // Called when panel collapses + onExpand?: () => void; // Called when panel expands from collapsed state +} +``` + +#### Behavior + +1. **Size Management**: + - Communicates with PanelGroup for size coordination + - Maintains its own size state + - Applies flex properties based on preservation settings + +2. **Content Handling**: + - Provides overflow handling (scroll, hidden, etc.) + - Manages content visibility during collapse/expand + +### 3. Splitter Component + +The `Splitter` component is the draggable divider between panels. + +#### Props + +```typescript +interface SplitterProps { + // Core Props + orientation: 'vertical' | 'horizontal'; // Inherited from PanelGroup + index: number; // Position index between panels + + // Behavior + disabled?: boolean; // Disables drag functionality + + // Styling + className?: string; // CSS class for the splitter + style?: React.CSSProperties; // Inline styles for the splitter + hoverClassName?: string; // Class applied on hover + activeClassName?: string; // Class applied during drag + + // Visual + showHandle?: boolean; // Default: true. Shows drag handle indicator + handleClassName?: string; // Class for the handle element +} +``` + +#### Behavior + +1. **Drag Handling**: + - Captures mouse/touch events for dragging + - Calculates new panel sizes during drag + - Respects min/max constraints of adjacent panels + +2. **Visual Feedback**: + - Changes cursor on hover + - Applies active styles during drag + - Optional handle indicator for better affordance + +## Implementation Details + +### Resize Callbacks + +The PanelGroup component provides three callback props for monitoring resize events: + +1. **`onResizeStart`**: + - Fired when user begins dragging a splitter + - Called once at the start of the resize operation + - No parameters passed + - Use cases: Show resize indicators, pause animations, track analytics + +2. **`onResize`**: + - Fired continuously during resize operation + - Called on every frame update while dragging + - Parameters: `sizes: number[]` - Current sizes of all panels in pixels + - Use cases: Live preview, real-time updates, size validation + +3. **`onResizeEnd`**: + - Fired when user releases the splitter + - Called once at the end of the resize operation + - Parameters: `sizes: number[]` - Final sizes of all panels in pixels + - Use cases: Save layout, trigger reflow, update persistent storage + +Example usage: + +```tsx + { + console.log('Resize started'); + setIsResizing(true); + }} + onResize={(sizes) => { + console.log('Current sizes:', sizes); + // Could update a preview or validate sizes + }} + onResizeEnd={(sizes) => { + console.log('Final sizes:', sizes); + setIsResizing(false); + // Save to localStorage or API + localStorage.setItem('panelSizes', JSON.stringify(sizes)); + }} +> + Content 1 + Content 2 + Content 3 + +``` + +### Layout Structure + +```tsx + + + {/* Left panel content */} + + + {/* Automatically inserted by PanelGroup */} + + + {/* Center panel content - flexible */} + + + {/* Automatically inserted by PanelGroup */} + + + {/* Right panel content - preserved on window resize */} + + +``` + +### Size Preservation Example + +When `preservedPanels={[0, 2]}` is set: +- Panels at index 0 and 2 maintain pixel sizes on window resize +- Panel at index 1 uses `flex: 1` to fill remaining space +- During manual resize, all panels follow normal resize behavior + +### Responsive Behavior + +1. **Window Resize**: + - Preserved panels: maintain pixel size + - Non-preserved panels: adjust proportionally + - All panels respect min/max constraints + +2. **Manual Resize**: + - User drags splitter to resize adjacent panels + - Constraints are enforced in real-time + - Smooth animation during resize + +### CSS Structure + +```css +/* PanelGroup */ +.panel-group { + display: flex; + flex-direction: row; /* or column for horizontal split */ + width: 100%; + height: 100%; +} + +/* Panel - Preserved */ +.panel-preserved { + flex: 0 0 auto; + width: 300px; /* Specific pixel value */ +} + +/* Panel - Flexible */ +.panel-flexible { + flex: 1 1 auto; + min-width: 0; /* Allows shrinking below content size */ +} + +/* Splitter */ +.splitter { + flex: 0 0 auto; + width: 4px; /* or height for horizontal */ + cursor: col-resize; /* or row-resize */ + user-select: none; +} + +.splitter:hover { + background-color: rgba(0, 0, 0, 0.1); +} + +.splitter.active { + background-color: rgba(0, 0, 0, 0.2); +} +``` + +## Advanced Features + +### 1. Nested Panel Groups + +Panel components can contain nested PanelGroups for complex layouts: + +```tsx + + + + {/* Top left */} + {/* Bottom left */} + + + {/* Right panel */} + +``` + +### 2. Controlled Mode + +PanelGroup can operate in controlled mode: + +```tsx +const [sizes, setSizes] = useState([30, 70]); + + + {/* panels */} + +``` + +### 3. Persistence + +Panel sizes can be persisted to localStorage: + +```tsx + + {/* panels */} + +``` + +### 4. Keyboard Navigation + +- Arrow keys: Fine-tune panel sizes when splitter is focused +- Tab: Navigate between splitters +- Enter/Space: Toggle panel collapse (if collapsible) + +## Accessibility + +1. **ARIA Attributes**: + - `role="separator"` on splitters + - `aria-orientation` matching split direction + - `aria-valuemin`, `aria-valuemax`, `aria-valuenow` for size + +2. **Keyboard Support**: + - Full keyboard navigation + - Focus indicators on splitters + - Announce size changes to screen readers + +3. **Touch Support**: + - Touch-friendly splitter targets + - Smooth touch-based resizing + - Gesture support for mobile devices + +## Performance Considerations + +1. **Debounced Callbacks**: + - Resize callbacks are debounced during drag + - Final callback on resize end + +2. **RAF Integration**: + - Use requestAnimationFrame for smooth resizing + - Batch DOM updates + +3. **Memoization**: + - Memoize child components to prevent unnecessary re-renders + - Use React.memo for Panel components + +## Error Handling + +1. **Invalid Configurations**: + - Warn if min > max sizes + - Handle missing Panel children gracefully + - Fallback to equal distribution if defaultLayout is invalid + +2. **Constraint Violations**: + - Prevent panels from shrinking below minimum + - Prevent panels from growing beyond maximum + - Redistribute space when constraints conflict + +## Browser Support + +- Modern browsers with flexbox support +- IE11 with polyfills +- Mobile browsers with touch event support + +## Future Enhancements + +1. **Animation API**: + - Smooth transitions when programmatically changing sizes + - Collapse/expand animations + +2. **Snap Points**: + - Define specific sizes panels snap to during resize + - Magnetic behavior near common sizes (25%, 50%, etc.) + +3. **Auto-Save Layouts**: + - Built-in persistence layer + - Multiple saved layout presets + +4. **Responsive Breakpoints**: + - Different layouts for different screen sizes + - Auto-collapse panels on mobile + +## Success Metrics + +1. **Performance**: + - 60fps during resize operations + - < 100ms initial render time + - < 16ms per resize frame + +2. **Usability**: + - Intuitive drag behavior + - Clear visual feedback + - Accessible to all users + +3. **Developer Experience**: + - Simple API + - Comprehensive TypeScript support + - Minimal configuration required + +## Example Implementations + +### Basic Two-Panel Layout + +```tsx + + + + + + + + +``` + +### Complex Dashboard Layout + +```tsx + + + + + + + + + + + + + + + +``` + +### Collapsible Sidebar + +```tsx + + + + + + + + +``` + +## Testing Strategy + +1. **Unit Tests**: + - Size calculation logic + - Constraint enforcement + - Event handling + +2. **Integration Tests**: + - Resize behavior + - Keyboard navigation + - Touch events + +3. **Visual Regression**: + - Screenshot tests for different layouts + - Animation smoothness + - Responsive behavior + +## Conclusion + +This panel resize system provides a flexible, accessible, and performant solution for creating resizable layouts in React applications. The component API is designed to be intuitive while offering advanced features for complex use cases. \ No newline at end of file diff --git a/apps/web-tss/tsconfig.storybook.json b/apps/web-tss/tsconfig.storybook.json deleted file mode 100644 index 10d525991..000000000 --- a/apps/web-tss/tsconfig.storybook.json +++ /dev/null @@ -1,32 +0,0 @@ -{ - "$schema": "https://json.schemastore.org/tsconfig", - "include": [ - "src/**/*.stories.ts", - "src/**/*.stories.tsx" - ], - "exclude": ["node_modules", "dist"], - "compilerOptions": { - "target": "ES2022", - "jsx": "react-jsx", - "module": "ESNext", - "lib": ["ES2022", "DOM", "DOM.Iterable"], - "types": ["vite/client"], - - "moduleResolution": "bundler", - "allowImportingTsExtensions": true, - "verbatimModuleSyntax": false, - "noEmit": true, - - "skipLibCheck": true, - "strict": true, - "noUnusedParameters": false, - "noFallthroughCasesInSwitch": true, - "noUncheckedSideEffectImports": true, - "esModuleInterop": true, - "baseUrl": ".", - "paths": { - "@/*": ["./src/*"], - "react": ["./node_modules/@types/react"] - } - } - } \ No newline at end of file diff --git a/apps/web/.storybook/main.ts b/apps/web/.storybook/main.ts index 6e1110599..137481de6 100644 --- a/apps/web/.storybook/main.ts +++ b/apps/web/.storybook/main.ts @@ -5,7 +5,7 @@ import type { StorybookConfig } from '@storybook/nextjs'; const require = createRequire(import.meta.url); const config: StorybookConfig = { - stories: ['../src/**/*.mdx', '../src/**/*.stories.@(js|jsx|mjs|ts|tsx)'], + stories: ['../src/**/*.stories.@(js|jsx|mjs|ts|tsx)'], addons: [getAbsolutePath('@chromatic-com/storybook'), getAbsolutePath('@storybook/addon-docs')], framework: { name: getAbsolutePath('@storybook/nextjs'),