@gadagi/ui-navigation 1.0.0
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.
- package/README.md +479 -0
- package/package.json +43 -0
- package/src/NavItem.tsx +41 -0
- package/src/Navigation.tsx +55 -0
- package/src/index.ts +2 -0
package/README.md
ADDED
|
@@ -0,0 +1,479 @@
|
|
|
1
|
+
# @gadagi/ui-navigation - Navigation Component
|
|
2
|
+
|
|
3
|
+
A comprehensive sidebar navigation component for the Gadagi platform micro-frontend architecture.
|
|
4
|
+
|
|
5
|
+
## Overview
|
|
6
|
+
|
|
7
|
+
This package provides a flexible navigation sidebar with collapsible sections, badges, icons, and responsive design for all Gadagi micro-frontends.
|
|
8
|
+
|
|
9
|
+
## Installation
|
|
10
|
+
|
|
11
|
+
```bash
|
|
12
|
+
npm install @gadagi/ui-navigation
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
## Peer Dependencies
|
|
16
|
+
|
|
17
|
+
```bash
|
|
18
|
+
npm install react react-dom react-router-dom @gadagi/design-system @gadagi/types
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
## Features
|
|
22
|
+
|
|
23
|
+
- 🧠**Hierarchical Navigation** - Multi-level menu support
|
|
24
|
+
- 🎯 **Active States** - Automatic active route highlighting
|
|
25
|
+
- 📊 **Badges** - Notification badges and counters
|
|
26
|
+
- 🎨 **Icons** - Icon support for navigation items
|
|
27
|
+
- 📱 **Responsive** - Collapsible sidebar for mobile
|
|
28
|
+
- ♿ **Accessible** - Full keyboard navigation support
|
|
29
|
+
|
|
30
|
+
## Quick Start
|
|
31
|
+
|
|
32
|
+
```tsx
|
|
33
|
+
import { Navigation } from '@gadagi/ui-navigation';
|
|
34
|
+
import { ThemeProvider } from '@gadagi/design-system';
|
|
35
|
+
|
|
36
|
+
function App() {
|
|
37
|
+
const navigationItems = [
|
|
38
|
+
{
|
|
39
|
+
id: 'dashboard',
|
|
40
|
+
label: 'Dashboard',
|
|
41
|
+
path: '/dashboard',
|
|
42
|
+
icon: <DashboardIcon />
|
|
43
|
+
},
|
|
44
|
+
{
|
|
45
|
+
id: 'users',
|
|
46
|
+
label: 'Users',
|
|
47
|
+
path: '/users',
|
|
48
|
+
badge: 5
|
|
49
|
+
}
|
|
50
|
+
];
|
|
51
|
+
|
|
52
|
+
return (
|
|
53
|
+
<ThemeProvider>
|
|
54
|
+
<Navigation
|
|
55
|
+
items={navigationItems}
|
|
56
|
+
collapsed={false}
|
|
57
|
+
onToggle={setCollapsed}
|
|
58
|
+
/>
|
|
59
|
+
</ThemeProvider>
|
|
60
|
+
);
|
|
61
|
+
}
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
## Components
|
|
65
|
+
|
|
66
|
+
### Navigation
|
|
67
|
+
|
|
68
|
+
The main navigation sidebar component.
|
|
69
|
+
|
|
70
|
+
```tsx
|
|
71
|
+
import { Navigation } from '@gadagi/ui-navigation';
|
|
72
|
+
|
|
73
|
+
<Navigation
|
|
74
|
+
items={navigationItems}
|
|
75
|
+
collapsed={false}
|
|
76
|
+
onToggle={handleToggle}
|
|
77
|
+
activeItem="dashboard"
|
|
78
|
+
/>
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
**Props:**
|
|
82
|
+
- `items: NavItem[]` - Navigation items array
|
|
83
|
+
- `collapsed?: boolean` - Sidebar collapsed state (default: false)
|
|
84
|
+
- `onToggle?: (collapsed: boolean) => void` - Toggle callback
|
|
85
|
+
- `activeItem?: string` - Currently active item ID
|
|
86
|
+
- `className?: string` - Additional CSS classes
|
|
87
|
+
- `width?: string` - Sidebar width when expanded (default: "250px")
|
|
88
|
+
- `collapsedWidth?: string` - Sidebar width when collapsed (default: "60px")
|
|
89
|
+
|
|
90
|
+
### NavItem
|
|
91
|
+
|
|
92
|
+
Individual navigation item component.
|
|
93
|
+
|
|
94
|
+
```tsx
|
|
95
|
+
import { NavItem } from '@gadagi/ui-navigation';
|
|
96
|
+
|
|
97
|
+
<NavItem
|
|
98
|
+
item={{
|
|
99
|
+
id: 'dashboard',
|
|
100
|
+
label: 'Dashboard',
|
|
101
|
+
path: '/dashboard',
|
|
102
|
+
icon: <DashboardIcon />,
|
|
103
|
+
badge: 3
|
|
104
|
+
}}
|
|
105
|
+
collapsed={false}
|
|
106
|
+
isActive={true}
|
|
107
|
+
/>
|
|
108
|
+
```
|
|
109
|
+
|
|
110
|
+
**Props:**
|
|
111
|
+
- `item: NavItem` - Navigation item data
|
|
112
|
+
- `collapsed?: boolean` - Collapsed state
|
|
113
|
+
- `isActive?: boolean` - Active state
|
|
114
|
+
- `onClick?: (item: NavItem) => void` - Click handler
|
|
115
|
+
|
|
116
|
+
## Navigation Item Structure
|
|
117
|
+
|
|
118
|
+
```typescript
|
|
119
|
+
interface NavItem {
|
|
120
|
+
id: string;
|
|
121
|
+
label: string;
|
|
122
|
+
path: string;
|
|
123
|
+
icon?: React.ReactNode;
|
|
124
|
+
badge?: number;
|
|
125
|
+
children?: NavItem[];
|
|
126
|
+
disabled?: boolean;
|
|
127
|
+
external?: boolean;
|
|
128
|
+
}
|
|
129
|
+
```
|
|
130
|
+
|
|
131
|
+
## Usage Examples
|
|
132
|
+
|
|
133
|
+
### Basic Navigation
|
|
134
|
+
|
|
135
|
+
```tsx
|
|
136
|
+
import { Navigation } from '@gadagi/ui-navigation';
|
|
137
|
+
|
|
138
|
+
const basicItems = [
|
|
139
|
+
{ id: 'home', label: 'Home', path: '/' },
|
|
140
|
+
{ id: 'about', label: 'About', path: '/about' },
|
|
141
|
+
{ id: 'contact', label: 'Contact', path: '/contact' }
|
|
142
|
+
];
|
|
143
|
+
|
|
144
|
+
function BasicNav() {
|
|
145
|
+
return (
|
|
146
|
+
<Navigation items={basicItems} />
|
|
147
|
+
);
|
|
148
|
+
}
|
|
149
|
+
```
|
|
150
|
+
|
|
151
|
+
### Navigation with Icons
|
|
152
|
+
|
|
153
|
+
```tsx
|
|
154
|
+
import { Navigation } from '@gadagi/ui-navigation';
|
|
155
|
+
|
|
156
|
+
const iconItems = [
|
|
157
|
+
{
|
|
158
|
+
id: 'dashboard',
|
|
159
|
+
label: 'Dashboard',
|
|
160
|
+
path: '/dashboard',
|
|
161
|
+
icon: <DashboardIcon />
|
|
162
|
+
},
|
|
163
|
+
{
|
|
164
|
+
id: 'users',
|
|
165
|
+
label: 'Users',
|
|
166
|
+
path: '/users',
|
|
167
|
+
icon: <UsersIcon />
|
|
168
|
+
},
|
|
169
|
+
{
|
|
170
|
+
id: 'settings',
|
|
171
|
+
label: 'Settings',
|
|
172
|
+
path: '/settings',
|
|
173
|
+
icon: <SettingsIcon />
|
|
174
|
+
}
|
|
175
|
+
];
|
|
176
|
+
|
|
177
|
+
function IconNav() {
|
|
178
|
+
return (
|
|
179
|
+
<Navigation items={iconItems} />
|
|
180
|
+
);
|
|
181
|
+
}
|
|
182
|
+
```
|
|
183
|
+
|
|
184
|
+
### Navigation with Badges
|
|
185
|
+
|
|
186
|
+
```tsx
|
|
187
|
+
const badgeItems = [
|
|
188
|
+
{
|
|
189
|
+
id: 'notifications',
|
|
190
|
+
label: 'Notifications',
|
|
191
|
+
path: '/notifications',
|
|
192
|
+
icon: <BellIcon />,
|
|
193
|
+
badge: 12
|
|
194
|
+
},
|
|
195
|
+
{
|
|
196
|
+
id: 'messages',
|
|
197
|
+
label: 'Messages',
|
|
198
|
+
path: '/messages',
|
|
199
|
+
icon: <MessageIcon />,
|
|
200
|
+
badge: 5
|
|
201
|
+
},
|
|
202
|
+
{
|
|
203
|
+
id: 'tasks',
|
|
204
|
+
label: 'Tasks',
|
|
205
|
+
path: '/tasks',
|
|
206
|
+
icon: <TaskIcon />,
|
|
207
|
+
badge: 0
|
|
208
|
+
}
|
|
209
|
+
];
|
|
210
|
+
|
|
211
|
+
function BadgeNav() {
|
|
212
|
+
return (
|
|
213
|
+
<Navigation items={badgeItems} />
|
|
214
|
+
);
|
|
215
|
+
}
|
|
216
|
+
```
|
|
217
|
+
|
|
218
|
+
### Hierarchical Navigation
|
|
219
|
+
|
|
220
|
+
```tsx
|
|
221
|
+
const hierarchicalItems = [
|
|
222
|
+
{
|
|
223
|
+
id: 'admin',
|
|
224
|
+
label: 'Administration',
|
|
225
|
+
icon: <AdminIcon />,
|
|
226
|
+
children: [
|
|
227
|
+
{
|
|
228
|
+
id: 'users',
|
|
229
|
+
label: 'User Management',
|
|
230
|
+
path: '/admin/users'
|
|
231
|
+
},
|
|
232
|
+
{
|
|
233
|
+
id: 'roles',
|
|
234
|
+
label: 'Role Management',
|
|
235
|
+
path: '/admin/roles'
|
|
236
|
+
},
|
|
237
|
+
{
|
|
238
|
+
id: 'permissions',
|
|
239
|
+
label: 'Permissions',
|
|
240
|
+
path: '/admin/permissions'
|
|
241
|
+
}
|
|
242
|
+
]
|
|
243
|
+
},
|
|
244
|
+
{
|
|
245
|
+
id: 'reports',
|
|
246
|
+
label: 'Reports',
|
|
247
|
+
icon: <ReportIcon />,
|
|
248
|
+
children: [
|
|
249
|
+
{
|
|
250
|
+
id: 'sales',
|
|
251
|
+
label: 'Sales Reports',
|
|
252
|
+
path: '/reports/sales'
|
|
253
|
+
},
|
|
254
|
+
{
|
|
255
|
+
id: 'analytics',
|
|
256
|
+
label: 'Analytics',
|
|
257
|
+
path: '/reports/analytics'
|
|
258
|
+
}
|
|
259
|
+
]
|
|
260
|
+
}
|
|
261
|
+
];
|
|
262
|
+
|
|
263
|
+
function HierarchicalNav() {
|
|
264
|
+
return (
|
|
265
|
+
<Navigation items={hierarchicalItems} />
|
|
266
|
+
);
|
|
267
|
+
}
|
|
268
|
+
```
|
|
269
|
+
|
|
270
|
+
### Collapsible Navigation
|
|
271
|
+
|
|
272
|
+
```tsx
|
|
273
|
+
function CollapsibleNav() {
|
|
274
|
+
const [collapsed, setCollapsed] = useState(false);
|
|
275
|
+
|
|
276
|
+
return (
|
|
277
|
+
<div style={{ display: 'flex', height: '100vh' }}>
|
|
278
|
+
<Navigation
|
|
279
|
+
items={navigationItems}
|
|
280
|
+
collapsed={collapsed}
|
|
281
|
+
onToggle={setCollapsed}
|
|
282
|
+
/>
|
|
283
|
+
<main style={{ flex: 1, padding: '20px' }}>
|
|
284
|
+
<button onClick={() => setCollapsed(!collapsed)}>
|
|
285
|
+
Toggle Sidebar
|
|
286
|
+
</button>
|
|
287
|
+
{/* Main content */}
|
|
288
|
+
</main>
|
|
289
|
+
</div>
|
|
290
|
+
);
|
|
291
|
+
}
|
|
292
|
+
```
|
|
293
|
+
|
|
294
|
+
### Integration with React Router
|
|
295
|
+
|
|
296
|
+
```tsx
|
|
297
|
+
import { Navigation } from '@gadagi/ui-navigation';
|
|
298
|
+
import { useLocation, useNavigate } from 'react-router-dom';
|
|
299
|
+
|
|
300
|
+
function RouterNav() {
|
|
301
|
+
const location = useLocation();
|
|
302
|
+
const navigate = useNavigate();
|
|
303
|
+
|
|
304
|
+
const navigationItems = [
|
|
305
|
+
{ id: 'dashboard', label: 'Dashboard', path: '/dashboard' },
|
|
306
|
+
{ id: 'users', label: 'Users', path: '/users' },
|
|
307
|
+
{ id: 'reports', label: 'Reports', path: '/reports' }
|
|
308
|
+
];
|
|
309
|
+
|
|
310
|
+
const getActiveItem = () => {
|
|
311
|
+
const currentPath = location.pathname;
|
|
312
|
+
return navigationItems.find(item => item.path === currentPath)?.id;
|
|
313
|
+
};
|
|
314
|
+
|
|
315
|
+
const handleItemClick = (item: NavItem) => {
|
|
316
|
+
navigate(item.path);
|
|
317
|
+
};
|
|
318
|
+
|
|
319
|
+
return (
|
|
320
|
+
<Navigation
|
|
321
|
+
items={navigationItems}
|
|
322
|
+
activeItem={getActiveItem()}
|
|
323
|
+
onItemClick={handleItemClick}
|
|
324
|
+
/>
|
|
325
|
+
);
|
|
326
|
+
}
|
|
327
|
+
```
|
|
328
|
+
|
|
329
|
+
## Customization
|
|
330
|
+
|
|
331
|
+
### Custom Styling
|
|
332
|
+
|
|
333
|
+
```tsx
|
|
334
|
+
<Navigation
|
|
335
|
+
items={navigationItems}
|
|
336
|
+
className="custom-navigation"
|
|
337
|
+
width="300px"
|
|
338
|
+
collapsedWidth="80px"
|
|
339
|
+
/>
|
|
340
|
+
```
|
|
341
|
+
|
|
342
|
+
```css
|
|
343
|
+
.custom-navigation {
|
|
344
|
+
background: linear-gradient(180deg, #2c3e50 0%, #34495e 100%);
|
|
345
|
+
border-right: 2px solid #3498db;
|
|
346
|
+
}
|
|
347
|
+
|
|
348
|
+
.custom-navigation .nav-item {
|
|
349
|
+
border-radius: 8px;
|
|
350
|
+
margin: 4px 8px;
|
|
351
|
+
}
|
|
352
|
+
|
|
353
|
+
.custom-navigation .nav-item.active {
|
|
354
|
+
background: #3498db;
|
|
355
|
+
color: white;
|
|
356
|
+
}
|
|
357
|
+
```
|
|
358
|
+
|
|
359
|
+
### Custom Icons
|
|
360
|
+
|
|
361
|
+
```tsx
|
|
362
|
+
import { Navigation } from '@gadagi/ui-navigation';
|
|
363
|
+
|
|
364
|
+
const customItems = [
|
|
365
|
+
{
|
|
366
|
+
id: 'home',
|
|
367
|
+
label: 'Home',
|
|
368
|
+
path: '/',
|
|
369
|
+
icon: (
|
|
370
|
+
<svg width="20" height="20" viewBox="0 0 24 24">
|
|
371
|
+
<path d="M10 20v-6h4v6h5v-8h3L12 3 2 12h3v8z"/>
|
|
372
|
+
</svg>
|
|
373
|
+
)
|
|
374
|
+
}
|
|
375
|
+
];
|
|
376
|
+
|
|
377
|
+
function CustomIconNav() {
|
|
378
|
+
return (
|
|
379
|
+
<Navigation items={customItems} />
|
|
380
|
+
);
|
|
381
|
+
}
|
|
382
|
+
```
|
|
383
|
+
|
|
384
|
+
### Custom Badge Styling
|
|
385
|
+
|
|
386
|
+
```css
|
|
387
|
+
.nav-badge {
|
|
388
|
+
background: #e74c3c;
|
|
389
|
+
color: white;
|
|
390
|
+
border-radius: 12px;
|
|
391
|
+
padding: 2px 6px;
|
|
392
|
+
font-size: 12px;
|
|
393
|
+
font-weight: bold;
|
|
394
|
+
}
|
|
395
|
+
|
|
396
|
+
.nav-badge.zero {
|
|
397
|
+
display: none;
|
|
398
|
+
}
|
|
399
|
+
```
|
|
400
|
+
|
|
401
|
+
## Accessibility Features
|
|
402
|
+
|
|
403
|
+
- **Keyboard Navigation**: Full tab and arrow key support
|
|
404
|
+
- **Screen Readers**: Proper ARIA labels and roles
|
|
405
|
+
- **Focus Management**: Logical focus flow
|
|
406
|
+
- **High Contrast**: WCAG compliant color combinations
|
|
407
|
+
- **Reduced Motion**: Respects user's motion preferences
|
|
408
|
+
|
|
409
|
+
## TypeScript Support
|
|
410
|
+
|
|
411
|
+
Full TypeScript support with proper interfaces:
|
|
412
|
+
|
|
413
|
+
```tsx
|
|
414
|
+
import { NavigationProps, NavItem } from '@gadagi/ui-navigation';
|
|
415
|
+
|
|
416
|
+
const items: NavItem[] = [
|
|
417
|
+
{
|
|
418
|
+
id: 'dashboard',
|
|
419
|
+
label: 'Dashboard',
|
|
420
|
+
path: '/dashboard',
|
|
421
|
+
icon: <DashboardIcon />,
|
|
422
|
+
badge: 5
|
|
423
|
+
}
|
|
424
|
+
];
|
|
425
|
+
|
|
426
|
+
const navProps: NavigationProps = {
|
|
427
|
+
items,
|
|
428
|
+
collapsed: false,
|
|
429
|
+
onToggle: (collapsed: boolean) => console.log('Collapsed:', collapsed)
|
|
430
|
+
};
|
|
431
|
+
```
|
|
432
|
+
|
|
433
|
+
## Responsive Design
|
|
434
|
+
|
|
435
|
+
The navigation automatically adapts to different screen sizes:
|
|
436
|
+
|
|
437
|
+
- **Desktop**: Full sidebar with all features
|
|
438
|
+
- **Tablet**: Compact mode with icon-only options
|
|
439
|
+
- **Mobile**: Overlay navigation with hamburger toggle
|
|
440
|
+
|
|
441
|
+
```tsx
|
|
442
|
+
// Responsive behavior is automatic
|
|
443
|
+
<Navigation items={navigationItems} />
|
|
444
|
+
```
|
|
445
|
+
|
|
446
|
+
## Performance Optimization
|
|
447
|
+
|
|
448
|
+
- **Virtual Scrolling**: For large navigation trees
|
|
449
|
+
- **Lazy Loading**: Load icons and badges on demand
|
|
450
|
+
- **Memoization**: Prevent unnecessary re-renders
|
|
451
|
+
- **Tree Shaking**: Only import used components
|
|
452
|
+
|
|
453
|
+
## Development
|
|
454
|
+
|
|
455
|
+
```bash
|
|
456
|
+
# Install dependencies
|
|
457
|
+
npm install
|
|
458
|
+
|
|
459
|
+
# Start development
|
|
460
|
+
npm run dev
|
|
461
|
+
|
|
462
|
+
# Build for production
|
|
463
|
+
npm run build
|
|
464
|
+
|
|
465
|
+
# Run tests
|
|
466
|
+
npm test
|
|
467
|
+
```
|
|
468
|
+
|
|
469
|
+
## Contributing
|
|
470
|
+
|
|
471
|
+
1. Fork the repository
|
|
472
|
+
2. Create a feature branch
|
|
473
|
+
3. Make your changes
|
|
474
|
+
4. Add tests for new functionality
|
|
475
|
+
5. Submit a pull request
|
|
476
|
+
|
|
477
|
+
## License
|
|
478
|
+
|
|
479
|
+
MIT © Gadagi Team
|
package/package.json
ADDED
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@gadagi/ui-navigation",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "Sidebar navigation for gadagi micro-frontends",
|
|
5
|
+
"main": "src/index.ts",
|
|
6
|
+
"module": "src/index.ts",
|
|
7
|
+
"types": "src/index.ts",
|
|
8
|
+
"exports": {
|
|
9
|
+
".": {
|
|
10
|
+
"types": "./src/index.ts",
|
|
11
|
+
"default": "./src/index.ts"
|
|
12
|
+
}
|
|
13
|
+
},
|
|
14
|
+
"files": ["src"],
|
|
15
|
+
"scripts": {
|
|
16
|
+
"build": "webpack --mode production && tsc -p tsconfig.build.json --emitDeclarationOnly",
|
|
17
|
+
"dev": "webpack --mode development --watch"
|
|
18
|
+
},
|
|
19
|
+
"publishConfig": {
|
|
20
|
+
"access": "public"
|
|
21
|
+
},
|
|
22
|
+
"peerDependencies": {
|
|
23
|
+
"react": "^19.0.0",
|
|
24
|
+
"react-dom": "^19.0.0",
|
|
25
|
+
"react-router-dom": "^6.0.0"
|
|
26
|
+
},
|
|
27
|
+
"dependencies": {
|
|
28
|
+
"@gadagi/types": "^1.0.1",
|
|
29
|
+
"@gadagi/design-system": "^1.0.1"
|
|
30
|
+
},
|
|
31
|
+
"devDependencies": {
|
|
32
|
+
"@types/react": "^19.0.0",
|
|
33
|
+
"@types/react-dom": "^19.0.0",
|
|
34
|
+
"css-loader": "^7.0.0",
|
|
35
|
+
"react": "^19.0.0",
|
|
36
|
+
"react-dom": "^19.0.0",
|
|
37
|
+
"style-loader": "^4.0.0",
|
|
38
|
+
"ts-loader": "^9.0.0",
|
|
39
|
+
"typescript": "^5.0.0",
|
|
40
|
+
"webpack": "^5.0.0",
|
|
41
|
+
"webpack-cli": "^5.0.0"
|
|
42
|
+
}
|
|
43
|
+
}
|
package/src/NavItem.tsx
ADDED
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import { NavLink } from 'react-router-dom';
|
|
3
|
+
import { NavItem as NavItemType } from '@gadagi/types';
|
|
4
|
+
import { colors, typography, Badge } from '@gadagi/design-system';
|
|
5
|
+
|
|
6
|
+
interface NavItemProps {
|
|
7
|
+
item: NavItemType;
|
|
8
|
+
collapsed?: boolean;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export const NavItem: React.FC<NavItemProps> = ({ item, collapsed }) => {
|
|
12
|
+
return (
|
|
13
|
+
<NavLink
|
|
14
|
+
to={item.path}
|
|
15
|
+
style={({ isActive }) => ({
|
|
16
|
+
display: 'flex',
|
|
17
|
+
alignItems: 'center',
|
|
18
|
+
gap: '10px',
|
|
19
|
+
padding: collapsed ? '10px 12px' : '10px 16px',
|
|
20
|
+
borderRadius: '6px',
|
|
21
|
+
textDecoration: 'none',
|
|
22
|
+
fontSize: typography.fontSize.sm,
|
|
23
|
+
fontWeight: typography.fontWeight.medium,
|
|
24
|
+
color: isActive ? colors.primary[600] : colors.neutral[600],
|
|
25
|
+
background: isActive ? colors.primary[50] : 'transparent',
|
|
26
|
+
transition: 'background 0.15s, color 0.15s',
|
|
27
|
+
justifyContent: collapsed ? 'center' : undefined,
|
|
28
|
+
})}
|
|
29
|
+
>
|
|
30
|
+
{item.icon && <span style={{ fontSize: '16px', flexShrink: 0 }}>{item.icon}</span>}
|
|
31
|
+
{!collapsed && (
|
|
32
|
+
<>
|
|
33
|
+
<span style={{ flex: 1 }}>{item.label}</span>
|
|
34
|
+
{item.badge !== undefined && item.badge > 0 && (
|
|
35
|
+
<Badge variant="info">{item.badge}</Badge>
|
|
36
|
+
)}
|
|
37
|
+
</>
|
|
38
|
+
)}
|
|
39
|
+
</NavLink>
|
|
40
|
+
);
|
|
41
|
+
};
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
import React, { useState } from 'react';
|
|
2
|
+
import { NavigationConfig } from '@gadagi/types';
|
|
3
|
+
import { colors, typography } from '@gadagi/design-system';
|
|
4
|
+
import { NavItem } from './NavItem';
|
|
5
|
+
|
|
6
|
+
interface NavigationProps {
|
|
7
|
+
config: NavigationConfig;
|
|
8
|
+
defaultCollapsed?: boolean;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export const Navigation: React.FC<NavigationProps> = ({
|
|
12
|
+
config,
|
|
13
|
+
defaultCollapsed = false,
|
|
14
|
+
}) => {
|
|
15
|
+
const [collapsed, setCollapsed] = useState(defaultCollapsed);
|
|
16
|
+
|
|
17
|
+
return (
|
|
18
|
+
<nav
|
|
19
|
+
style={{
|
|
20
|
+
width: collapsed ? '56px' : '240px',
|
|
21
|
+
minHeight: '100vh',
|
|
22
|
+
background: '#fff',
|
|
23
|
+
borderRight: `1px solid ${colors.neutral[200]}`,
|
|
24
|
+
padding: '12px 8px',
|
|
25
|
+
display: 'flex',
|
|
26
|
+
flexDirection: 'column',
|
|
27
|
+
gap: '2px',
|
|
28
|
+
transition: 'width 0.2s ease',
|
|
29
|
+
overflowX: 'hidden',
|
|
30
|
+
}}
|
|
31
|
+
>
|
|
32
|
+
<button
|
|
33
|
+
onClick={() => setCollapsed(c => !c)}
|
|
34
|
+
style={{
|
|
35
|
+
alignSelf: collapsed ? 'center' : 'flex-end',
|
|
36
|
+
background: 'transparent',
|
|
37
|
+
border: 'none',
|
|
38
|
+
cursor: 'pointer',
|
|
39
|
+
padding: '6px',
|
|
40
|
+
color: colors.neutral[400],
|
|
41
|
+
fontSize: '18px',
|
|
42
|
+
lineHeight: 1,
|
|
43
|
+
marginBottom: '8px',
|
|
44
|
+
}}
|
|
45
|
+
title={collapsed ? 'Expand' : 'Collapse'}
|
|
46
|
+
>
|
|
47
|
+
{collapsed ? '->' : '<-'}
|
|
48
|
+
</button>
|
|
49
|
+
|
|
50
|
+
{config.items.map(item => (
|
|
51
|
+
<NavItem key={item.id} item={item} collapsed={collapsed} />
|
|
52
|
+
))}
|
|
53
|
+
</nav>
|
|
54
|
+
);
|
|
55
|
+
};
|
package/src/index.ts
ADDED