UNPKG

now-design-organisms

Version:

Organism components for the Now Design System

654 lines (618 loc) 21 kB
import React, { useState } from 'react'; import LeftPanel from './LeftPanel'; import { MetalcloudMeltingFurnaceLine, SystemAddFill, ArrowsArrowDownSLine, WeatherSunLine, UserLine, SettingsLine, FileTextLine, HomeLine, ChartLine, NotificationLine, ShieldLine, DatabaseLine, CloudLine, ToolsLine, AlertLine, CheckLine } from 'now-design-icons'; export default { title: 'Organisms/LeftPanel', component: LeftPanel, parameters: { layout: 'fullscreen', docs: { description: { component: ` # LeftPanel Organism Component A comprehensive left panel navigation organism that provides a complete layout for filtering and navigation using the AccordionSelectableListContainer. ## Features - **Organism-Level Component**: Combines multiple molecules for complex UI patterns - **Clean API**: Minimal props focused on functionality - **Flexible Styling**: All layout and visual properties via \`style\` prop - **Responsive Design**: Mobile-first approach with breakpoints - **Theme Integration**: Uses design tokens for consistent theming - **Accessibility**: Proper focus states and semantic markup - **Developer Friendly**: Helpful warnings for missing data ## Usage \`\`\`jsx import { LeftPanel } from 'now-design-organisms'; <LeftPanel items={items} onItemSelect={(itemId, parentAccordionId, selectedItemLabel) => { console.log('Selected:', itemId, parentAccordionId, selectedItemLabel); }} selectedItem="melting-furnace" style={{ width: '280px', backgroundColor: '#ffffff' }} /> \`\`\` ` } } }, argTypes: { items: { control: { type: 'object' }, description: 'Array of accordion configurations (required)', table: { type: { summary: 'Array<AccordionConfig>' }, defaultValue: { summary: '[]' }, category: 'Data' } }, onItemSelect: { action: 'itemSelected', description: 'Enhanced callback when any item is selected. Receives: (itemId, parentAccordionId, selectedItemLabel)', table: { type: { summary: 'function' }, category: 'Events' } }, selectedItem: { control: { type: 'text' }, description: 'Selected item ID to highlight on initial render', table: { type: { summary: 'string' }, category: 'Data' } }, logo: { control: { type: 'object' }, description: 'Custom logo component or element to replace LogoMetalCloud', table: { type: { summary: 'ReactElement|Component' }, category: 'Customization' } }, logoSize: { control: { type: 'object' }, description: 'Logo dimensions {width, height}', table: { type: { summary: '{width: number, height: number}' }, defaultValue: { summary: '{width: 64, height: 32}' }, category: 'Customization' } }, onLogoClick: { action: 'logoClicked', description: 'Callback when logo is clicked', table: { type: { summary: 'function' }, category: 'Events' } }, contentClassName: { control: { type: 'text' }, description: 'Additional CSS classes for content container', table: { type: { summary: 'string' }, defaultValue: { summary: "''" }, category: 'Styling' } }, contentStyle: { control: { type: 'object' }, description: 'Additional inline styles for content container', table: { type: { summary: 'object' }, defaultValue: { summary: '{}' }, category: 'Styling' } }, className: { control: { type: 'text' }, description: 'Additional CSS classes for the main container', table: { type: { summary: 'string' }, defaultValue: { summary: "''" }, category: 'Styling' } }, style: { control: { type: 'object' }, description: 'Additional inline styles (includes layout, visual, and positioning styles)', table: { type: { summary: 'object' }, defaultValue: { summary: '{}' }, category: 'Styling' } } } }; // Sample accordion data for stories const sampleAccordionData = [ { id: 'production', triggerLabel: 'Production', triggerIcon: MetalcloudMeltingFurnaceLine, items: [ { id: 'melting-furnace', label: 'Melting Furnace', icon: MetalcloudMeltingFurnaceLine }, { id: 'casting-line', label: 'Casting Line', icon: SystemAddFill }, { id: 'quality-check', label: 'Quality Check', icon: WeatherSunLine } ] }, { id: 'management', triggerLabel: 'Management', triggerIcon: UserLine, items: [ { id: 'user-management', label: 'User Management', icon: UserLine }, { id: 'settings', label: 'Settings', icon: SettingsLine }, { id: 'reports', label: 'Reports', icon: FileTextLine } ] }, { id: 'system', triggerLabel: 'System', triggerIcon: SystemAddFill, items: [ { id: 'configuration', label: 'Configuration', icon: SystemAddFill }, { id: 'maintenance', label: 'Maintenance', icon: MetalcloudMeltingFurnaceLine }, { id: 'backup', label: 'Backup', icon: WeatherSunLine } ] } ]; const Template = (args) => ( <div style={{ display: 'flex', height: '100vh' }}> <LeftPanel {...args} /> <div style={{ flex: 1, padding: '20px', backgroundColor: '#f5f5f5' }}> <h2>Main Content Area</h2> <p>This is the main content area that would be displayed when items are selected from the left panel.</p> </div> </div> ); // Default story with basic configuration export const Default = Template.bind({}); Default.args = { items: sampleAccordionData, style: { width: '280px', backgroundColor: '#ffffff', borderRight: '1px solid #e0e0e0' } }; // Compact version export const Compact = Template.bind({}); Compact.args = { items: sampleAccordionData, logoSize: { width: 48, height: 24 }, style: { width: '220px', backgroundColor: '#f8f9fa', borderRight: '1px solid #dee2e6' } }; // Wide version export const Wide = Template.bind({}); Wide.args = { items: sampleAccordionData, logoSize: { width: 80, height: 40 }, style: { width: '320px', backgroundColor: '#ffffff', borderRight: '1px solid #e0e0e0' } }; // With default selection export const WithDefaultSelection = Template.bind({}); WithDefaultSelection.args = { items: sampleAccordionData, selectedItem: 'melting-furnace', style: { width: '280px', backgroundColor: '#ffffff', borderRight: '1px solid #e0e0e0' } }; // Interactive with state management export const Interactive = () => { const [selectedItemId, setSelectedItemId] = useState(null); const [selectedAccordionId, setSelectedAccordionId] = useState(null); const [selectedItemLabel, setSelectedItemLabel] = useState(null); const handleItemSelect = (itemId, parentAccordionId, selectedItemLabel) => { setSelectedItemId(itemId); setSelectedAccordionId(parentAccordionId); setSelectedItemLabel(selectedItemLabel); }; return ( <div style={{ display: 'flex', height: '100vh' }}> <LeftPanel items={sampleAccordionData} onItemSelect={handleItemSelect} selectedItem="melting-furnace" style={{ width: '280px', backgroundColor: '#ffffff', borderRight: '1px solid #e0e0e0' }} /> <div style={{ flex: 1, padding: '20px', backgroundColor: '#f5f5f5' }}> <h2>Interactive Example</h2> <p>This example shows the left panel with interactive state management.</p> <div style={{ marginTop: '20px', padding: '16px', backgroundColor: '#ffffff', borderRadius: '8px' }}> <h3>Selection Details:</h3> <p><strong>Selected Item ID:</strong> {selectedItemId || 'None'}</p> <p><strong>Parent Accordion ID:</strong> {selectedAccordionId || 'None'}</p> <p><strong>Selected Item Label:</strong> {selectedItemLabel || 'None'}</p> </div> </div> </div> ); }; // Complex data structure export const ComplexData = () => { const complexAccordionData = [ { id: 'manufacturing', triggerLabel: 'Manufacturing', triggerIcon: MetalcloudMeltingFurnaceLine, items: [ { id: 'furnace-1', label: 'Furnace Line 1', icon: MetalcloudMeltingFurnaceLine }, { id: 'furnace-2', label: 'Furnace Line 2', icon: MetalcloudMeltingFurnaceLine }, { id: 'casting-1', label: 'Casting Unit 1', icon: SystemAddFill }, { id: 'casting-2', label: 'Casting Unit 2', icon: SystemAddFill }, { id: 'quality-1', label: 'Quality Station 1', icon: WeatherSunLine }, { id: 'quality-2', label: 'Quality Station 2', icon: WeatherSunLine } ] }, { id: 'operations', triggerLabel: 'Operations', triggerIcon: UserLine, items: [ { id: 'users', label: 'User Management', icon: UserLine }, { id: 'roles', label: 'Role Management', icon: UserLine }, { id: 'permissions', label: 'Permissions', icon: SettingsLine }, { id: 'audit', label: 'Audit Logs', icon: FileTextLine } ] }, { id: 'analytics', triggerLabel: 'Analytics', triggerIcon: ChartLine, items: [ { id: 'reports', label: 'Reports', icon: FileTextLine }, { id: 'dashboards', label: 'Dashboards', icon: ChartLine }, { id: 'metrics', label: 'Metrics', icon: WeatherSunLine }, { id: 'alerts', label: 'Alerts', icon: AlertLine } ] }, { id: 'system', triggerLabel: 'System', triggerIcon: SystemAddFill, items: [ { id: 'config', label: 'Configuration', icon: SystemAddFill }, { id: 'maintenance', label: 'Maintenance', icon: ToolsLine }, { id: 'backup', label: 'Backup & Restore', icon: DatabaseLine }, { id: 'updates', label: 'System Updates', icon: CloudLine }, { id: 'logs', label: 'System Logs', icon: FileTextLine }, { id: 'security', label: 'Security', icon: ShieldLine } ] } ]; return ( <div style={{ display: 'flex', height: '100vh' }}> <LeftPanel items={complexAccordionData} selectedItem="furnace-1" style={{ width: '300px', backgroundColor: '#ffffff', borderRight: '1px solid #e0e0e0' }} /> <div style={{ flex: 1, padding: '20px', backgroundColor: '#f5f5f5' }}> <h2>Complex Navigation Example</h2> <p>This example shows a more complex navigation structure with multiple accordions and items.</p> <p>The data structure includes 4 accordions with 6 items each, demonstrating the component's ability to handle complex navigation hierarchies.</p> </div> </div> ); }; // Custom styling examples export const CustomStyling = () => { return ( <div style={{ display: 'flex', height: '100vh' }}> <LeftPanel items={sampleAccordionData} style={{ width: '280px', backgroundColor: '#2c3e50', borderRight: '2px solid #34495e', borderRadius: '0 8px 8px 0', boxShadow: '2px 0 10px rgba(0,0,0,0.1)', padding: '16px' }} contentStyle={{ backgroundColor: 'transparent' }} /> <div style={{ flex: 1, padding: '20px', backgroundColor: '#ecf0f1' }}> <h2>Custom Styled Panel</h2> <p>This example shows the left panel with custom styling including:</p> <ul> <li>Dark background color (#2c3e50)</li> <li>Custom border radius</li> <li>Box shadow for depth</li> <li>Custom padding</li> </ul> </div> </div> ); }; // With disabled items export const WithDisabledItems = () => { const accordionDataWithDisabled = [ { id: 'production', triggerLabel: 'Production', triggerIcon: MetalcloudMeltingFurnaceLine, items: [ { id: 'melting-furnace', label: 'Melting Furnace', icon: MetalcloudMeltingFurnaceLine }, { id: 'casting-line', label: 'Casting Line (Disabled)', icon: SystemAddFill, disabled: true }, { id: 'quality-check', label: 'Quality Check', icon: WeatherSunLine } ] }, { id: 'management', triggerLabel: 'Management', triggerIcon: UserLine, items: [ { id: 'user-management', label: 'User Management', icon: UserLine }, { id: 'settings', label: 'Settings (Disabled)', icon: SettingsLine, disabled: true }, { id: 'reports', label: 'Reports', icon: FileTextLine } ] } ]; return ( <div style={{ display: 'flex', height: '100vh' }}> <LeftPanel items={accordionDataWithDisabled} style={{ width: '280px', backgroundColor: '#ffffff', borderRight: '1px solid #e0e0e0' }} /> <div style={{ flex: 1, padding: '20px', backgroundColor: '#f5f5f5' }}> <h2>Panel with Disabled Items</h2> <p>This example shows the left panel with some disabled items that cannot be selected.</p> <p>Disabled items are visually distinct and non-interactive.</p> </div> </div> ); }; // With custom logo component (FIXED) export const WithCustomLogo = () => { const CustomLogo = ({ width, height }) => ( <div style={{ width: width, height: height, backgroundColor: '#3498db', borderRadius: '8px', display: 'flex', alignItems: 'center', justifyContent: 'center', color: 'white', fontSize: '12px', fontWeight: 'bold', fontFamily: 'Arial, sans-serif' }}> CUSTOM </div> ); return ( <div style={{ display: 'flex', height: '100vh' }}> <LeftPanel items={sampleAccordionData} logo={CustomLogo} logoSize={{ width: 80, height: 40 }} onLogoClick={() => alert('Custom logo clicked!')} style={{ width: '280px', backgroundColor: '#ffffff', borderRight: '1px solid #e0e0e0' }} /> <div style={{ flex: 1, padding: '20px', backgroundColor: '#f5f5f5' }}> <h2>Custom Logo Example</h2> <p>This example shows the left panel with a custom logo component instead of the default LogoMetalCloud.</p> <p>The custom logo is clickable and demonstrates the flexibility of the logo prop.</p> </div> </div> ); }; // With logo click handler export const WithLogoClick = () => { const [logoClickCount, setLogoClickCount] = useState(0); const handleLogoClick = () => { setLogoClickCount(prev => prev + 1); }; return ( <div style={{ display: 'flex', height: '100vh' }}> <LeftPanel items={sampleAccordionData} onLogoClick={handleLogoClick} style={{ width: '280px', backgroundColor: '#ffffff', borderRight: '1px solid #e0e0e0' }} /> <div style={{ flex: 1, padding: '20px', backgroundColor: '#f5f5f5' }}> <h2>Logo Click Handler</h2> <p>This example demonstrates the logo click functionality.</p> <div style={{ marginTop: '20px', padding: '16px', backgroundColor: '#ffffff', borderRadius: '8px' }}> <p><strong>Logo Click Count:</strong> {logoClickCount}</p> <p>Click the logo in the left panel to increment the counter.</p> </div> </div> </div> ); }; // Empty state (no accordion data) export const EmptyState = () => { return ( <div style={{ display: 'flex', height: '100vh' }}> <LeftPanel items={[]} style={{ width: '280px', backgroundColor: '#ffffff', borderRight: '1px solid #e0e0e0' }} /> <div style={{ flex: 1, padding: '20px', backgroundColor: '#f5f5f5' }}> <h2>Empty State</h2> <p>This example shows the left panel with no accordion data.</p> <p>Check the browser console for the warning message about empty items.</p> </div> </div> ); }; // Responsive example with font family export const Responsive = () => { return ( <div style={{ display: 'flex', height: '100vh' }}> <LeftPanel items={sampleAccordionData} style={{ width: '280px', minWidth: '200px', maxWidth: '320px', backgroundColor: '#ffffff', borderRight: '1px solid #e0e0e0', fontFamily: 'Oxanium, Arial, sans-serif' }} /> <div style={{ flex: 1, padding: '20px', backgroundColor: '#f5f5f5', fontFamily: 'Oxanium, Arial, sans-serif' }}> <h2>Responsive Panel</h2> <p>This example shows a responsive left panel with minimum and maximum width constraints.</p> <p>Try resizing the browser window to see how the panel adapts.</p> <p>Font family is set to Oxanium for consistent theming.</p> </div> </div> ); }; // With content styling export const WithContentStyling = () => { return ( <div style={{ display: 'flex', height: '100vh' }}> <LeftPanel items={sampleAccordionData} contentClassName="custom-content" contentStyle={{ backgroundColor: '#f8f9fa', padding: '8px', borderRadius: '4px' }} style={{ width: '280px', backgroundColor: '#ffffff', borderRight: '1px solid #e0e0e0', padding: '16px' }} /> <div style={{ flex: 1, padding: '20px', backgroundColor: '#f5f5f5' }}> <h2>Content Styling Example</h2> <p>This example demonstrates custom styling for the content container.</p> <p>The content area has a light background, custom padding, and border radius.</p> </div> </div> ); }; // Interactive testing with actions export const InteractiveTesting = () => { const [selectedItemId, setSelectedItemId] = useState(null); const [selectedAccordionId, setSelectedAccordionId] = useState(null); const [selectedItemLabel, setSelectedItemLabel] = useState(null); const [logoClickCount, setLogoClickCount] = useState(0); const [currentData, setCurrentData] = useState(sampleAccordionData); const handleItemSelect = (itemId, parentAccordionId, selectedItemLabel) => { setSelectedItemId(itemId); setSelectedAccordionId(parentAccordionId); setSelectedItemLabel(selectedItemLabel); }; const handleLogoClick = () => { setLogoClickCount(prev => prev + 1); }; const toggleData = () => { setCurrentData(currentData === sampleAccordionData ? [] : sampleAccordionData); }; const resetSelection = () => { setSelectedItemId(null); setSelectedAccordionId(null); setSelectedItemLabel(null); }; return ( <div style={{ display: 'flex', height: '100vh' }}> <LeftPanel items={currentData} onItemSelect={handleItemSelect} onLogoClick={handleLogoClick} style={{ width: '280px', backgroundColor: '#ffffff', borderRight: '1px solid #e0e0e0', fontFamily: 'Oxanium, Arial, sans-serif' }} /> <div style={{ flex: 1, padding: '20px', backgroundColor: '#f5f5f5', fontFamily: 'Oxanium, Arial, sans-serif' }}> <h2>Interactive Testing Panel</h2> <p>Test all interactions and responsive behavior here.</p> <div style={{ marginTop: '20px', padding: '16px', backgroundColor: '#ffffff', borderRadius: '8px' }}> <h3>Selection Details:</h3> <p><strong>Selected Item ID:</strong> {selectedItemId || 'None'}</p> <p><strong>Parent Accordion ID:</strong> {selectedAccordionId || 'None'}</p> <p><strong>Selected Item Label:</strong> {selectedItemLabel || 'None'}</p> <p><strong>Logo Click Count:</strong> {logoClickCount}</p> </div> <div style={{ marginTop: '20px', padding: '16px', backgroundColor: '#e8f5e8', borderRadius: '8px' }}> <h3>Test Controls:</h3> <button onClick={toggleData} style={{ marginRight: '10px', padding: '8px 16px', backgroundColor: '#007bff', color: 'white', border: 'none', borderRadius: '4px', cursor: 'pointer' }} > {currentData.length > 0 ? 'Clear Data' : 'Load Data'} </button> <button onClick={resetSelection} style={{ padding: '8px 16px', backgroundColor: '#6c757d', color: 'white', border: 'none', borderRadius: '4px', cursor: 'pointer' }} > Reset Selection </button> </div> <div style={{ marginTop: '20px', padding: '16px', backgroundColor: '#fff3cd', borderRadius: '8px' }}> <h3>Responsive Testing:</h3> <p>• Try resizing the browser window</p> <p>• Test on different screen sizes</p> <p>• Check font rendering with Oxanium</p> <p>• Verify all interactions work properly</p> </div> </div> </div> ); };