@fjell/core 4.4.6 → 4.4.12

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (41) hide show
  1. package/README.md +227 -0
  2. package/dist/cjs/index.js +5 -0
  3. package/dist/cjs/index.js.map +1 -1
  4. package/dist/cjs/item/IQUtils.js +20 -1
  5. package/dist/cjs/item/IQUtils.js.map +1 -1
  6. package/dist/cjs/item/IUtils.js.map +1 -1
  7. package/dist/cjs/key/KUtils.js +100 -1
  8. package/dist/cjs/key/KUtils.js.map +1 -1
  9. package/dist/esm/index.js +1 -1
  10. package/dist/esm/item/IQUtils.js +2 -2
  11. package/dist/esm/item/IQUtils.js.map +1 -1
  12. package/dist/esm/item/IUtils.js.map +1 -1
  13. package/dist/esm/key/KUtils.js +96 -2
  14. package/dist/esm/key/KUtils.js.map +1 -1
  15. package/dist/index.cjs +120 -2
  16. package/dist/index.cjs.map +1 -1
  17. package/dist/key/KUtils.d.ts +7 -2
  18. package/dist/keys.d.ts +3 -3
  19. package/docs/README.md +53 -0
  20. package/docs/index.html +18 -0
  21. package/docs/package.json +35 -0
  22. package/docs/public/README.md +227 -0
  23. package/docs/public/api.md +230 -0
  24. package/docs/public/basic-usage.ts +293 -0
  25. package/docs/public/examples-README.md +147 -0
  26. package/docs/public/fjell-icon.svg +1 -0
  27. package/docs/public/package.json +56 -0
  28. package/docs/public/pano.png +0 -0
  29. package/docs/src/App.css +1178 -0
  30. package/docs/src/App.tsx +684 -0
  31. package/docs/src/index.css +38 -0
  32. package/docs/src/main.tsx +10 -0
  33. package/docs/tsconfig.node.json +14 -0
  34. package/docs/vitest.config.ts +14 -0
  35. package/examples/README.md +147 -0
  36. package/examples/basic-usage.ts +293 -0
  37. package/package.json +10 -9
  38. package/src/item/IQUtils.ts +3 -3
  39. package/src/item/IUtils.ts +1 -1
  40. package/src/key/KUtils.ts +114 -6
  41. package/src/keys.ts +3 -3
@@ -0,0 +1,684 @@
1
+ import React, { useEffect, useState } from 'react'
2
+ import ReactMarkdown from 'react-markdown'
3
+ import { Prism as SyntaxHighlighter } from 'react-syntax-highlighter'
4
+ import { oneLight } from 'react-syntax-highlighter/dist/esm/styles/prism'
5
+ import remarkGfm from 'remark-gfm'
6
+ import './App.css'
7
+
8
+ interface DocumentSection {
9
+ id: string;
10
+ title: string;
11
+ subtitle: string;
12
+ file: string;
13
+ content?: string;
14
+ }
15
+
16
+ const documentSections: DocumentSection[] = [
17
+ { id: 'overview', title: 'Foundation', subtitle: 'Core concepts & philosophy', file: '/core/README.md' },
18
+ { id: 'getting-started', title: 'Getting Started', subtitle: 'Your first steps with Fjell Core', file: '/core/README.md' },
19
+ { id: 'examples', title: 'Examples', subtitle: 'Code examples & usage patterns', file: '/core/examples-README.md' },
20
+ { id: 'api', title: 'API Reference', subtitle: 'Complete API documentation', file: '/core/api.md' }
21
+ ];
22
+
23
+ const App: React.FC = () => {
24
+ const [currentSection, setCurrentSection] = useState('overview')
25
+ const [documents, setDocuments] = useState<{ [key: string]: string }>({})
26
+ const [loading, setLoading] = useState(true)
27
+ const [sidebarOpen, setSidebarOpen] = useState(false)
28
+ const [fullscreenImage, setFullscreenImage] = useState<string | null>(null)
29
+ const [version, setVersion] = useState<string>('4.4.7')
30
+
31
+ useEffect(() => {
32
+ const loadDocuments = async () => {
33
+ const loadedDocs: { [key: string]: string } = {}
34
+
35
+ for (const section of documentSections) {
36
+ try {
37
+ const response = await fetch(section.file)
38
+
39
+ if (response.ok) {
40
+ loadedDocs[section.id] = await response.text()
41
+ } else {
42
+ // Fallback content for missing files
43
+ loadedDocs[section.id] = getFallbackContent(section.id)
44
+ }
45
+ } catch (error) {
46
+ console.error(`Error loading ${section.file}:`, error)
47
+ loadedDocs[section.id] = getFallbackContent(section.id)
48
+ }
49
+ }
50
+
51
+ setDocuments(loadedDocs)
52
+ setLoading(false)
53
+ }
54
+
55
+ const loadVersion = async () => {
56
+ try {
57
+ const response = await fetch('/core/package.json')
58
+ if (response.ok) {
59
+ const packageData = await response.json()
60
+ setVersion(packageData.version)
61
+ } else {
62
+ setVersion('4.4.7') // Fallback version
63
+ }
64
+ } catch (error) {
65
+ console.error('Error loading version:', error)
66
+ setVersion('4.4.7') // Fallback version
67
+ }
68
+ }
69
+
70
+ loadDocuments()
71
+ loadVersion()
72
+ }, [])
73
+
74
+ const getFallbackContent = (sectionId: string): string => {
75
+ switch (sectionId) {
76
+ case 'overview':
77
+ return `# Fjell Core
78
+
79
+ Core Item and Key Framework for Fjell - The foundational library that provides
80
+ essential item management, key utilities, and service coordination for the entire
81
+ Fjell ecosystem.
82
+
83
+ ## Installation
84
+
85
+ \`\`\`bash
86
+ npm install @fjell/core
87
+ \`\`\`
88
+
89
+ ## Quick Start
90
+
91
+ \`\`\`typescript
92
+ import { IFactory, KUtils, AItemService } from '@fjell/core'
93
+
94
+ // Create items with the factory
95
+ const item = IFactory.create('user', { id: '123', name: 'John' })
96
+
97
+ // Use key utilities for key management
98
+ const key = KUtils.generateKey(['user', '123'])
99
+
100
+ // Service coordination
101
+ const service = new AItemService()
102
+ \`\`\`
103
+
104
+ ## Core Features
105
+
106
+ ### Item Factory (IFactory)
107
+ - **Type-safe item creation**: Create strongly-typed items with validation
108
+ - **Flexible configuration**: Support for various item types and schemas
109
+ - **Integration ready**: Works seamlessly with other Fjell libraries
110
+
111
+ ### Key Utilities (KUtils)
112
+ - **Key generation**: Create hierarchical keys for item identification
113
+ - **Key parsing**: Extract components from complex key structures
114
+ - **Key validation**: Ensure key integrity across the system
115
+
116
+ ### Service Layer (AItemService)
117
+ - **Abstract service base**: Foundation for building domain services
118
+ - **Lifecycle management**: Handle service initialization and cleanup
119
+ - **Event coordination**: Built-in event handling capabilities
120
+
121
+ ### Query Support (IQFactory, IQUtils)
122
+ - **Query building**: Construct complex queries for item retrieval
123
+ - **Query optimization**: Efficient query execution strategies
124
+ - **Result processing**: Transform and filter query results
125
+
126
+ ## Architecture Philosophy
127
+
128
+ Fjell Core is designed around the principle of **progressive enhancement**:
129
+
130
+ 1. **Start Simple**: Basic item and key operations require minimal setup
131
+ 2. **Scale Gradually**: Add complexity only when needed
132
+ 3. **Maintain Consistency**: Common patterns across all Fjell libraries
133
+ 4. **Enable Integration**: Designed to work with the entire Fjell ecosystem
134
+
135
+ ## Integration with Fjell Ecosystem
136
+
137
+ Fjell Core serves as the foundation for:
138
+
139
+ - **@fjell/registry**: Service location and dependency injection
140
+ - **@fjell/cache**: High-performance caching solutions
141
+ - **@fjell/lib**: Database abstraction layers
142
+ - **@fjell/providers**: React component state management
143
+ - **@fjell/client-api**: HTTP client utilities
144
+
145
+ ## Type Safety
146
+
147
+ Built with TypeScript-first design:
148
+
149
+ \`\`\`typescript
150
+ // Strongly typed item creation
151
+ interface UserItem {
152
+ id: string
153
+ name: string
154
+ email: string
155
+ }
156
+
157
+ const user = IFactory.create<UserItem>('user', {
158
+ id: '123',
159
+ name: 'John Doe',
160
+ email: 'john@example.com'
161
+ })
162
+
163
+ // Type-safe key operations
164
+ const userKey = KUtils.createKey(['users', user.id])
165
+ const [collection, id] = KUtils.parseKey(userKey)
166
+ \`\`\`
167
+
168
+ ## Performance Characteristics
169
+
170
+ - **Minimal Runtime Overhead**: Core operations are highly optimized
171
+ - **Memory Efficient**: Smart object pooling and reuse strategies
172
+ - **Lazy Loading**: Components load only when needed
173
+ - **Tree Shaking**: Only bundle what you use
174
+
175
+ ## Testing Support
176
+
177
+ Comprehensive testing utilities:
178
+
179
+ \`\`\`typescript
180
+ import { TestUtils } from '@fjell/core/testing'
181
+
182
+ // Mock factories for testing
183
+ const mockFactory = TestUtils.createMockFactory()
184
+ const testItem = mockFactory.create('test', { id: 'test-123' })
185
+
186
+ // Assertion helpers
187
+ expect(TestUtils.isValidKey(key)).toBe(true)
188
+ expect(TestUtils.getKeyComponents(key)).toEqual(['users', '123'])
189
+ \`\`\`
190
+
191
+ ## Error Handling
192
+
193
+ Robust error handling with descriptive messages:
194
+
195
+ \`\`\`typescript
196
+ try {
197
+ const item = IFactory.create('invalid-type', data)
198
+ } catch (error) {
199
+ if (error instanceof CoreValidationError) {
200
+ console.log('Validation failed:', error.details)
201
+ }
202
+ }
203
+ \`\`\`
204
+
205
+ ## Getting Help
206
+
207
+ - **GitHub Issues**: Report bugs and request features
208
+ - **Documentation**: Comprehensive guides and API reference
209
+ - **Examples**: Real-world usage patterns and best practices
210
+ - **Community**: Join discussions and get support
211
+
212
+ Start building with Fjell Core today and experience the power of a well-designed
213
+ foundational framework that grows with your application needs.`
214
+
215
+ case 'getting-started':
216
+ return `# Getting Started with Fjell Core
217
+
218
+ Welcome to Fjell Core! This guide will help you get up and running quickly.
219
+
220
+ ## Installation
221
+
222
+ \`\`\`bash
223
+ # Using npm
224
+ npm install @fjell/core
225
+
226
+ # Using yarn
227
+ yarn add @fjell/core
228
+
229
+ # Using pnpm
230
+ pnpm add @fjell/core
231
+ \`\`\`
232
+
233
+ ## Basic Usage
234
+
235
+ ### 1. Item Creation with IFactory
236
+
237
+ \`\`\`typescript
238
+ import { IFactory } from '@fjell/core'
239
+
240
+ // Define your item type
241
+ interface User {
242
+ id: string
243
+ name: string
244
+ email: string
245
+ }
246
+
247
+ // Create an item
248
+ const user = IFactory.create<User>('user', {
249
+ id: 'user-123',
250
+ name: 'John Doe',
251
+ email: 'john.doe@example.com'
252
+ })
253
+
254
+ console.log(user) // Strongly typed User item
255
+ \`\`\`
256
+
257
+ ### 2. Key Management with KUtils
258
+
259
+ \`\`\`typescript
260
+ import { KUtils } from '@fjell/core'
261
+
262
+ // Generate hierarchical keys
263
+ const userKey = KUtils.generateKey(['users', 'user-123'])
264
+ const profileKey = KUtils.generateKey(['users', 'user-123', 'profile'])
265
+
266
+ // Parse keys back to components
267
+ const components = KUtils.parseKey(userKey)
268
+ console.log(components) // ['users', 'user-123']
269
+
270
+ // Validate keys
271
+ const isValid = KUtils.isValidKey(userKey)
272
+ console.log(isValid) // true
273
+ \`\`\`
274
+
275
+ ### 3. Service Layer with AItemService
276
+
277
+ \`\`\`typescript
278
+ import { AItemService } from '@fjell/core'
279
+
280
+ class UserService extends AItemService {
281
+ async createUser(userData: Partial<User>): Promise<User> {
282
+ // Your business logic here
283
+ const user = IFactory.create<User>('user', {
284
+ id: this.generateId(),
285
+ ...userData
286
+ })
287
+
288
+ // Use the service's built-in methods
289
+ await this.validate(user)
290
+ return this.save(user)
291
+ }
292
+
293
+ private generateId(): string {
294
+ return \`user-\${Date.now()}-\${Math.random().toString(36).substr(2, 9)}\`;
295
+ }
296
+ }
297
+
298
+ // Use the service
299
+ const userService = new UserService()
300
+ const newUser = await userService.createUser({
301
+ name: 'Jane Smith',
302
+ email: 'jane@example.com'
303
+ })
304
+ \`\`\`
305
+
306
+ ### 4. Query Building with IQFactory
307
+
308
+ \`\`\`typescript
309
+ import { IQFactory, IQUtils } from '@fjell/core'
310
+
311
+ // Build queries for item retrieval
312
+ const query = IQFactory.create('user')
313
+ .where('status', 'active')
314
+ .where('role', 'admin')
315
+ .limit(10)
316
+ .orderBy('createdAt', 'desc')
317
+
318
+ // Execute queries (integration with your data layer)
319
+ const results = await IQUtils.execute(query)
320
+ console.log(results) // Array of matching User items
321
+ \`\`\`
322
+
323
+ ## Next Steps
324
+
325
+ 1. **Explore Examples**: Check out our comprehensive examples
326
+ 2. **API Reference**: Review the complete API documentation
327
+ 3. **Integration**: Learn how to integrate with other Fjell libraries
328
+ 4. **Testing**: Set up testing with Fjell Core utilities
329
+
330
+ Ready to build something amazing with Fjell Core!`
331
+
332
+ case 'examples':
333
+ return `# Fjell Core Examples
334
+
335
+ Examples are loading from the examples directory...
336
+
337
+ If you're seeing this message, there may be an issue loading the examples content.
338
+ Please check the console for any errors.`
339
+
340
+ case 'api':
341
+ return `# Fjell Core API Reference
342
+
343
+ Complete API documentation for all Fjell Core modules.
344
+
345
+ ## IFactory
346
+
347
+ Factory for creating strongly-typed items.
348
+
349
+ ### Methods
350
+
351
+ #### \`create<T>(type: string, data: Partial<T>): T\`
352
+ Creates a new item of the specified type.
353
+
354
+ **Parameters:**
355
+ - \`type\`: String identifier for the item type
356
+ - \`data\`: Partial item data to initialize the item
357
+
358
+ **Returns:** Fully constructed item of type T
359
+
360
+ **Example:**
361
+ \`\`\`typescript
362
+ const user = IFactory.create<User>('user', {
363
+ id: 'user-123',
364
+ name: 'John Doe'
365
+ })
366
+ \`\`\`
367
+
368
+ ## KUtils
369
+
370
+ Utilities for key generation and manipulation.
371
+
372
+ ### Methods
373
+
374
+ #### \`generateKey(components: string[]): string\`
375
+ Generates a hierarchical key from components.
376
+
377
+ **Parameters:**
378
+ - \`components\`: Array of string components
379
+
380
+ **Returns:** Generated key string
381
+
382
+ **Example:**
383
+ \`\`\`typescript
384
+ const key = KUtils.generateKey(['users', 'user-123', 'profile'])
385
+ // Returns: "users:user-123:profile"
386
+ \`\`\`
387
+
388
+ #### \`parseKey(key: string): string[]\`
389
+ Parses a key back into its components.
390
+
391
+ #### \`isValidKey(key: string): boolean\`
392
+ Validates whether a key is properly formatted.
393
+
394
+ ## AItemService
395
+
396
+ Abstract base class for building domain services.
397
+
398
+ ### Methods
399
+
400
+ #### \`validate<T>(item: T): Promise<void>\`
401
+ Validates an item. Override in subclasses.
402
+
403
+ #### \`save<T>(item: T): Promise<T>\`
404
+ Saves an item. Override in subclasses.
405
+
406
+ #### \`findById<T>(id: string): Promise<T | null>\`
407
+ Finds an item by ID. Override in subclasses.
408
+
409
+ ## IQFactory
410
+
411
+ Factory for building queries.
412
+
413
+ ### Methods
414
+
415
+ #### \`create(type: string): QueryBuilder\`
416
+ Creates a new query builder for the specified type.
417
+
418
+ This API reference provides complete coverage of all Fjell Core functionality.`
419
+
420
+ default:
421
+ return `# ${sectionId}
422
+
423
+ Documentation for this section is being prepared.
424
+
425
+ Please check back soon for comprehensive content.`
426
+ }
427
+ }
428
+
429
+ const toggleSidebar = () => {
430
+ setSidebarOpen(!sidebarOpen)
431
+ }
432
+
433
+ const currentSectionData = documentSections.find(section => section.id === currentSection)
434
+ const currentContent = documents[currentSection]
435
+
436
+ if (loading) {
437
+ return (
438
+ <div className="loading">
439
+ <div className="spinner"></div>
440
+ Loading documentation...
441
+ </div>
442
+ )
443
+ }
444
+
445
+ return (
446
+ <div className="app">
447
+ {/* Header */}
448
+ <header className="header">
449
+ <div className="header-container">
450
+ <div className="brand">
451
+ <h1 className="brand-title">
452
+ <span className="brand-fjell">Fjell</span>
453
+ <span className="brand-core">Core</span>
454
+ </h1>
455
+ <p className="brand-tagline">Core Item and Key Framework for Fjell</p>
456
+ </div>
457
+
458
+ <div className="header-actions">
459
+ <span className="version-badge">v{version}</span>
460
+ <a
461
+ href="https://github.com/getfjell/fjell-core"
462
+ className="header-link"
463
+ target="_blank"
464
+ rel="noopener noreferrer"
465
+ >
466
+ View Source
467
+ </a>
468
+ <a
469
+ href="https://www.npmjs.com/package/@fjell/core"
470
+ className="header-link"
471
+ target="_blank"
472
+ rel="noopener noreferrer"
473
+ >
474
+ Install Package
475
+ </a>
476
+ </div>
477
+
478
+ <button
479
+ className="menu-toggle"
480
+ onClick={toggleSidebar}
481
+ aria-label="Toggle navigation"
482
+ >
483
+ <span className="menu-line"></span>
484
+ <span className="menu-line"></span>
485
+ <span className="menu-line"></span>
486
+ </button>
487
+ </div>
488
+ </header>
489
+
490
+ {/* Layout */}
491
+ <div className="layout">
492
+ {/* Sidebar */}
493
+ <nav className={`sidebar ${sidebarOpen ? 'sidebar-open' : ''}`}>
494
+ <div className="sidebar-content">
495
+ <div className="sidebar-header">
496
+ <h2 className="sidebar-title">Documentation</h2>
497
+ <p className="sidebar-subtitle">Core concepts & philosophy</p>
498
+ </div>
499
+
500
+ <div className="nav-menu">
501
+ {documentSections.map((section) => (
502
+ <button
503
+ key={section.id}
504
+ className={`nav-item ${currentSection === section.id ? 'nav-item-active' : ''}`}
505
+ onClick={() => {
506
+ setCurrentSection(section.id)
507
+ setSidebarOpen(false)
508
+ }}
509
+ >
510
+ <span className="nav-item-title">{section.title}</span>
511
+ <span className="nav-item-subtitle">{section.subtitle}</span>
512
+ </button>
513
+ ))}
514
+ </div>
515
+ </div>
516
+ </nav>
517
+
518
+ {/* Main Content */}
519
+ <main className="main">
520
+ <div className="main-content">
521
+ {currentSectionData && (
522
+ <div className="section-header">
523
+ <h1 className="section-title">{currentSectionData.title}</h1>
524
+ <p className="section-subtitle">{currentSectionData.subtitle}</p>
525
+ </div>
526
+ )}
527
+
528
+ <div className="content">
529
+ <div className="markdown-content">
530
+ <ReactMarkdown
531
+ remarkPlugins={[remarkGfm]}
532
+ components={{
533
+ code({ inline, className, children, ...props }: any) {
534
+ const match = /language-(\w+)/.exec(className || '')
535
+ return !inline && match ? (
536
+ <SyntaxHighlighter
537
+ style={oneLight as any}
538
+ language={match[1]}
539
+ PreTag="div"
540
+ {...props}
541
+ >
542
+ {String(children).replace(/\n$/, '')}
543
+ </SyntaxHighlighter>
544
+ ) : (
545
+ <code className={className} {...props}>
546
+ {children}
547
+ </code>
548
+ )
549
+ },
550
+ img({ src, alt, ...props }) {
551
+ return (
552
+ <img
553
+ src={src}
554
+ alt={alt}
555
+ {...props}
556
+ onClick={() => src && setFullscreenImage(src)}
557
+ style={{ cursor: 'pointer' }}
558
+ />
559
+ )
560
+ },
561
+ a({ href, children, ...props }) {
562
+ // Handle links to .ts files specially - load and display code
563
+ if (href && href.endsWith('.ts')) {
564
+ return (
565
+ <button
566
+ className="code-example-link"
567
+ onClick={async () => {
568
+ try {
569
+ const response = await fetch(href.startsWith('/') ? `/core${href}` : `/core/${href}`)
570
+ if (response.ok) {
571
+ const code = await response.text()
572
+ // Create a modal or section to display the code
573
+ const codeSection = document.createElement('div')
574
+ codeSection.className = 'code-example-modal'
575
+ codeSection.innerHTML = `
576
+ <div class="code-example-content">
577
+ <div class="code-example-header">
578
+ <h3>${href}</h3>
579
+ <button onclick="this.parentElement.parentElement.parentElement.remove()">×</button>
580
+ </div>
581
+ <pre><code class="language-typescript">${code}</code></pre>
582
+ </div>
583
+ `
584
+ document.body.appendChild(codeSection)
585
+ } else {
586
+ console.error('Failed to load code example:', href)
587
+ }
588
+ } catch (error) {
589
+ console.error('Error loading code example:', error)
590
+ }
591
+ }}
592
+ style={{
593
+ background: 'none',
594
+ border: 'none',
595
+ color: '#0066cc',
596
+ textDecoration: 'underline',
597
+ cursor: 'pointer',
598
+ padding: 0,
599
+ font: 'inherit'
600
+ }}
601
+ {...props}
602
+ >
603
+ {children}
604
+ </button>
605
+ )
606
+ }
607
+
608
+ // Regular links
609
+ return (
610
+ <a href={href} {...props}>
611
+ {children}
612
+ </a>
613
+ )
614
+ }
615
+ }}
616
+ >
617
+ {currentContent || 'Loading content...'}
618
+ </ReactMarkdown>
619
+ </div>
620
+
621
+ {/* Navigation suggestions */}
622
+ <div className="nav-suggestions">
623
+ {documentSections.map((section) => {
624
+ if (section.id === currentSection) return null
625
+ return (
626
+ <button
627
+ key={section.id}
628
+ className="nav-suggestion"
629
+ onClick={() => setCurrentSection(section.id)}
630
+ >
631
+ <span className="nav-suggestion-label">Next</span>
632
+ <span className="nav-suggestion-title">{section.title}</span>
633
+ </button>
634
+ )
635
+ }).filter(Boolean).slice(0, 1)}
636
+ </div>
637
+ </div>
638
+ </div>
639
+ </main>
640
+ </div>
641
+
642
+ <footer className="footer">
643
+ <div className="footer-container">
644
+ <div className="footer-content">
645
+ <p className="footer-text">
646
+ Crafted with intention for the Fjell ecosystem
647
+ </p>
648
+ <p className="footer-license">
649
+ Licensed under Apache-2.0 &nbsp;•&nbsp; 2024
650
+ </p>
651
+ </div>
652
+ </div>
653
+ </footer>
654
+
655
+ {/* Fullscreen Image Modal */}
656
+ {fullscreenImage && (
657
+ <div className="fullscreen-modal" onClick={() => setFullscreenImage(null)}>
658
+ <div className="fullscreen-content">
659
+ <img
660
+ src={fullscreenImage}
661
+ alt="Fullscreen view"
662
+ className="fullscreen-image"
663
+ />
664
+ <button
665
+ className="close-button"
666
+ onClick={() => setFullscreenImage(null)}
667
+ aria-label="Close fullscreen view"
668
+ >
669
+ ×
670
+ </button>
671
+ </div>
672
+ </div>
673
+ )}
674
+
675
+ {/* Mobile overlay */}
676
+ <div
677
+ className={`mobile-overlay ${sidebarOpen ? 'mobile-overlay-visible' : ''}`}
678
+ onClick={() => setSidebarOpen(false)}
679
+ />
680
+ </div>
681
+ )
682
+ }
683
+
684
+ export default App