@hyphen/hyphen-components 5.2.1 → 5.3.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.
@@ -19,6 +19,7 @@ import {
19
19
  SidebarMenuAction,
20
20
  SidebarMenuBadge,
21
21
  SidebarTrigger,
22
+ useSidebar,
22
23
  } from './Sidebar';
23
24
  import { allModes } from '../../modes';
24
25
  import { Card } from '../Card/Card';
@@ -78,53 +79,48 @@ const data = {
78
79
  name: 'Evil Corp.',
79
80
  },
80
81
  ],
81
- navMain: [
82
+ items: [
82
83
  {
83
84
  title: 'Dashboard',
84
85
  url: '#',
85
- icon: 'dashboard',
86
+ icon: 'dashboard' as IconName,
86
87
  isActive: true,
87
88
  },
89
+ {
90
+ title: 'Deploy',
91
+ url: '#',
92
+ icon: 'logo-deploy' as IconName,
93
+ },
88
94
  {
89
95
  title: 'Teams',
90
96
  url: '#',
91
- icon: 'users',
97
+ icon: 'users' as IconName,
92
98
  },
93
99
  {
94
100
  title: 'Link',
95
101
  url: '#',
96
- icon: 'logo-link',
102
+ icon: 'logo-link' as IconName,
97
103
  },
98
104
  {
99
105
  title: 'ENV',
100
106
  url: '#',
101
- icon: 'logo-env',
107
+ icon: 'logo-env' as IconName,
102
108
  },
103
109
  {
104
110
  title: 'Toggle',
105
111
  url: '#',
106
- icon: 'logo-toggle',
107
- items: [
108
- {
109
- title: 'Feature Toggles',
110
- url: '#',
111
- },
112
- {
113
- title: 'Segments',
114
- url: '#',
115
- },
116
- ],
112
+ icon: 'logo-toggle' as IconName,
117
113
  },
118
114
  {
119
115
  title: 'Integrations',
120
116
  url: '#',
121
- icon: 'stack',
117
+ icon: 'stack' as IconName,
122
118
  count: 23,
123
119
  },
124
120
  {
125
121
  title: 'Settings',
126
122
  url: '#',
127
- icon: 'settings',
123
+ icon: 'settings' as IconName,
128
124
  items: [
129
125
  {
130
126
  title: 'General',
@@ -162,223 +158,61 @@ const data = {
162
158
 
163
159
  // type Story = StoryObj<typeof Sidebar>;
164
160
 
161
+ function getCookieValue(name: string): string | null {
162
+ const match = document.cookie.match(
163
+ new RegExp(
164
+ `(?:^|; )${name.replace(/([.$?*|{}()[\]\\/+^])/g, '\\$1')}=([^;]*)`
165
+ )
166
+ );
167
+ return match ? decodeURIComponent(match[1]) : null;
168
+ }
169
+
165
170
  export const SidebarExample = () => {
166
171
  const [activeTeam, setActiveTeam] = React.useState(data.teams[0]);
167
172
  const isMobile = useIsMobile();
173
+
174
+ const startOpen = getCookieValue('sidebar_expanded') || 'true';
175
+
168
176
  return (
169
177
  <ResponsiveProvider>
170
- <SidebarProvider>
171
- <Sidebar side="left" collapsible="offcanvas">
172
- <SidebarHeader>
173
- <SidebarMenu>
174
- <SidebarMenuItem>
175
- <DropdownMenu>
176
- <DropdownMenuTrigger asChild>
177
- <SidebarMenuButton>
178
- <Box
179
- flex="auto"
180
- direction="row"
181
- gap="sm"
182
- alignItems="center"
183
- >
184
- <Box
185
- background="black"
186
- borderColor="subtle"
187
- borderWidth="sm"
188
- width="4xl"
189
- height="4xl"
190
- alignItems="center"
191
- justifyContent="center"
192
- radius="md"
193
- ></Box>
194
- <span className="font-weight-semibold">
195
- {activeTeam.name}
196
- </span>
197
- </Box>
198
- <Icon name="caret-up-down" />
199
- </SidebarMenuButton>
200
- </DropdownMenuTrigger>
201
- <DropdownMenuContent
202
- align="start"
203
- side="bottom"
204
- sideOffset={4}
205
- >
206
- <DropdownMenuLabel className="text-xs text-muted-foreground">
207
- Organizations
208
- </DropdownMenuLabel>
209
- {data.teams.map((team, index) => (
210
- <DropdownMenuItem
211
- key={team.name}
212
- onClick={() => setActiveTeam(team)}
213
- >
214
- {team.name}
215
- <DropdownMenuShortcut>
216
- ⌘{index + 1}
217
- </DropdownMenuShortcut>
218
- </DropdownMenuItem>
219
- ))}
220
- <DropdownMenuSeparator />
221
- <DropdownMenuItem>
222
- <a
223
- href="https://ux.hyphen.ai"
224
- className="display-flex flex-direction-row g-sm align-items-center"
225
- >
226
- <Icon name="add" color="tertiary" />
227
- <span>Create Organization</span>
228
- </a>
229
- </DropdownMenuItem>
230
- </DropdownMenuContent>
231
- </DropdownMenu>
232
- </SidebarMenuItem>
233
- </SidebarMenu>
234
- </SidebarHeader>
178
+ <SidebarProvider defaultOpen={startOpen === 'true'}>
179
+ <Sidebar side="left" collapsible="icon">
180
+ <NavHeader activeTeam={activeTeam} setActiveTeam={setActiveTeam} />
235
181
  <SidebarContent>
236
- <SidebarGroup>
237
- <SidebarGroupLabel>Platform</SidebarGroupLabel>
238
- <SidebarMenu>
239
- {data.navMain.map((item) =>
240
- item.items ? (
241
- <Collapsible
242
- key={item.title}
243
- className="group/collapsible"
244
- asChild
245
- >
246
- <SidebarMenuItem>
247
- <CollapsibleTrigger asChild>
248
- <SidebarMenuButton>
249
- <Icon
250
- name={item.icon as IconName}
251
- color="tertiary"
252
- size="lg"
253
- />
254
- {item.title}
255
- <Icon
256
- name="caret-sm-right"
257
- className="m-left-auto transform data-[state=open]:rotate-90"
258
- />
259
- </SidebarMenuButton>
260
- </CollapsibleTrigger>
261
- <CollapsibleContent>
262
- <SidebarMenuSub>
263
- {item.items?.map((subItem) => (
264
- <SidebarMenuSubItem key={subItem.title}>
265
- <SidebarMenuSubButton asChild>
266
- <a href={subItem.url}>
267
- <span>{subItem.title}</span>
268
- </a>
269
- </SidebarMenuSubButton>
270
- </SidebarMenuSubItem>
271
- ))}
272
- </SidebarMenuSub>
273
- </CollapsibleContent>
274
- </SidebarMenuItem>
275
- </Collapsible>
276
- ) : (
277
- <SidebarMenuItem key={item.title}>
278
- <SidebarMenuButton asChild isActive={item.isActive}>
279
- <a href={item.url}>
280
- <Icon
281
- name={item.icon as IconName}
282
- color="tertiary"
283
- size="lg"
284
- />
285
- <span>{item.title}</span>
286
- </a>
287
- </SidebarMenuButton>
288
- {item.count && (
289
- <SidebarMenuBadge>{item.count}</SidebarMenuBadge>
290
- )}
291
- </SidebarMenuItem>
292
- )
293
- )}
294
- </SidebarMenu>
295
- </SidebarGroup>
296
- <SidebarGroup>
297
- <SidebarGroupLabel>Favorites</SidebarGroupLabel>
298
- <SidebarMenu>
299
- {data.favorites.map((item) => (
300
- <SidebarMenuItem key={item.name}>
301
- <SidebarMenuButton asChild>
302
- <a href={item.url}>
303
- <Icon name={item.icon as IconName} color="tertiary" />
304
- <span>{item.name}</span>
305
- </a>
306
- </SidebarMenuButton>
307
- <DropdownMenu>
308
- <DropdownMenuTrigger asChild>
309
- <SidebarMenuAction>
310
- <Icon name="dots" />
311
- <span className="sr-only">More</span>
312
- </SidebarMenuAction>
313
- </DropdownMenuTrigger>
314
- <DropdownMenuContent side="bottom" align="end">
315
- <DropdownMenuItem>
316
- <a href="https://ux.hyphen.ai">View</a>
317
- </DropdownMenuItem>
318
- <DropdownMenuItem>
319
- <a href="https://ux.hyphen.ai">Share</a>
320
- </DropdownMenuItem>
321
- <DropdownMenuSeparator />
322
- <DropdownMenuItem>
323
- <a href="https://ux.hyphen.ai">Remove</a>
324
- </DropdownMenuItem>
325
- </DropdownMenuContent>
326
- </DropdownMenu>
327
- </SidebarMenuItem>
328
- ))}
329
- </SidebarMenu>
330
- </SidebarGroup>
182
+ <NavMain items={data.items} />
183
+ <NavFavorites favorites={data.favorites} />
331
184
  </SidebarContent>
185
+ <NavFooter />
186
+ <SidebarRail />
187
+ </Sidebar>
188
+ <SidebarInset>
189
+ {isMobile && <SidebarTrigger />}
190
+ <Card height="100" padding="2xl">
191
+ content
192
+ </Card>
193
+ </SidebarInset>
194
+ </SidebarProvider>
195
+ </ResponsiveProvider>
196
+ );
197
+ };
198
+
199
+ export const SidebarCollapsed = () => {
200
+ const [activeTeam, setActiveTeam] = React.useState(data.teams[0]);
201
+ const isMobile = useIsMobile();
202
+
203
+ const startOpen = getCookieValue('false');
204
+
205
+ return (
206
+ <ResponsiveProvider>
207
+ <SidebarProvider defaultOpen={startOpen === 'true'}>
208
+ <Sidebar side="left" collapsible="icon">
209
+ <NavHeader activeTeam={activeTeam} setActiveTeam={setActiveTeam} />
210
+ <SidebarContent>
211
+ <NavMain items={data.items} />
212
+ <NavFavorites favorites={data.favorites} />
213
+ </SidebarContent>
214
+ <NavFooter />
332
215
  <SidebarRail />
333
- <SidebarFooter>
334
- <SidebarMenu>
335
- <SidebarMenuItem>
336
- <DropdownMenu>
337
- <DropdownMenuTrigger asChild>
338
- <SidebarMenuButton>
339
- <Box flex="auto" direction="column" gap="2xs">
340
- <span className="font-weight-semibold">
341
- {data.user.name}
342
- </span>
343
- <span className="truncate font-size-xs font-color-secondary">
344
- {data.user.email}
345
- </span>
346
- </Box>
347
- <Icon name="caret-up-down" />
348
- </SidebarMenuButton>
349
- </DropdownMenuTrigger>
350
- <DropdownMenuContent side="bottom" align="end" sideOffset={4}>
351
- <DropdownMenuLabel>
352
- <Box flex="auto" direction="column" gap="2xs">
353
- <span className="font-weight-semibold">
354
- {data.user.name}
355
- </span>
356
- <span className="truncate font-size-xs font-color-secondary">
357
- {data.user.email}
358
- </span>
359
- </Box>
360
- </DropdownMenuLabel>
361
- <DropdownMenuSeparator />
362
- <DropdownMenuGroup>
363
- <DropdownMenuItem>
364
- <Icon name="user" color="tertiary" />
365
- Profile
366
- </DropdownMenuItem>
367
- <DropdownMenuItem>
368
- <Icon name="alarm" color="tertiary" />
369
- Notifications{' '}
370
- </DropdownMenuItem>
371
- </DropdownMenuGroup>
372
- <DropdownMenuSeparator />
373
- <DropdownMenuItem>
374
- <Icon name="logout" color="tertiary" />
375
- Log out
376
- </DropdownMenuItem>
377
- </DropdownMenuContent>
378
- </DropdownMenu>
379
- </SidebarMenuItem>
380
- </SidebarMenu>
381
- </SidebarFooter>
382
216
  </Sidebar>
383
217
  <SidebarInset>
384
218
  {isMobile && <SidebarTrigger />}
@@ -390,3 +224,289 @@ export const SidebarExample = () => {
390
224
  </ResponsiveProvider>
391
225
  );
392
226
  };
227
+
228
+ interface NavItem {
229
+ title: string;
230
+ url: string;
231
+ icon?: IconName;
232
+ isActive?: boolean;
233
+ count?: number;
234
+ items?: {
235
+ title: string;
236
+ url: string;
237
+ }[];
238
+ }
239
+
240
+ const NavHeader = ({
241
+ activeTeam,
242
+ setActiveTeam,
243
+ }: {
244
+ activeTeam: any;
245
+ setActiveTeam: (team: any) => void;
246
+ }) => {
247
+ const { state } = useSidebar();
248
+
249
+ return (
250
+ <SidebarHeader>
251
+ <SidebarMenu>
252
+ <SidebarMenuItem>
253
+ <DropdownMenu>
254
+ <DropdownMenuTrigger asChild>
255
+ <SidebarMenuButton style={{ padding: 'var(--size-spacing-xs)' }}>
256
+ <Box flex="auto" direction="row" gap="sm" alignItems="center">
257
+ <Box
258
+ background="black"
259
+ borderColor="subtle"
260
+ borderWidth="sm"
261
+ width="24px"
262
+ height="24px"
263
+ minWidth="24px"
264
+ minHeight="24px"
265
+ alignItems="center"
266
+ justifyContent="center"
267
+ radius="sm"
268
+ color="white"
269
+ fontSize="xs"
270
+ fontWeight="bold"
271
+ >
272
+ AC
273
+ </Box>
274
+ <span
275
+ className="font-weight-semibold"
276
+ style={{ whiteSpace: 'nowrap' }}
277
+ >
278
+ {activeTeam.name}
279
+ </span>
280
+ </Box>
281
+ <Icon
282
+ name="caret-up-down"
283
+ className="group-data-collapsible-icon-hidden"
284
+ />
285
+ </SidebarMenuButton>
286
+ </DropdownMenuTrigger>
287
+ <DropdownMenuContent
288
+ align="start"
289
+ side={state === 'expanded' ? 'bottom' : 'right'}
290
+ sideOffset={4}
291
+ >
292
+ <DropdownMenuLabel className="text-xs text-muted-foreground">
293
+ Organizations
294
+ </DropdownMenuLabel>
295
+ {data.teams.map((team, index) => (
296
+ <DropdownMenuItem
297
+ key={`team.name-${index}`}
298
+ onClick={() => setActiveTeam(team)}
299
+ >
300
+ {team.name}
301
+ <DropdownMenuShortcut>⌘{index + 1}</DropdownMenuShortcut>
302
+ </DropdownMenuItem>
303
+ ))}
304
+ <DropdownMenuSeparator />
305
+ <DropdownMenuItem>
306
+ <a
307
+ href="https://ux.hyphen.ai"
308
+ className="display-flex flex-direction-row g-sm align-items-center"
309
+ >
310
+ <Icon name="add" color="tertiary" />
311
+ <span>Create Organization</span>
312
+ </a>
313
+ </DropdownMenuItem>
314
+ </DropdownMenuContent>
315
+ </DropdownMenu>
316
+ </SidebarMenuItem>
317
+ </SidebarMenu>
318
+ </SidebarHeader>
319
+ );
320
+ };
321
+
322
+ const NavMain = ({ items }: { items: NavItem[] }) => {
323
+ const { state } = useSidebar();
324
+
325
+ return (
326
+ <SidebarGroup>
327
+ <SidebarGroupLabel>Platform</SidebarGroupLabel>
328
+ <SidebarMenu>
329
+ {items.map((item, idx) =>
330
+ item.items && state === 'expanded' ? (
331
+ <Collapsible
332
+ key={`${item.title}-${idx}`}
333
+ className="group/collapsible"
334
+ asChild
335
+ >
336
+ <SidebarMenuItem>
337
+ <CollapsibleTrigger asChild>
338
+ <SidebarMenuButton tooltip={item.title}>
339
+ <Icon
340
+ name={item.icon as IconName}
341
+ color="tertiary"
342
+ size="lg"
343
+ />
344
+ {item.title}
345
+ <Icon
346
+ name="caret-sm-right"
347
+ className="m-left-auto transform data-[state=open]:rotate-90"
348
+ />
349
+ </SidebarMenuButton>
350
+ </CollapsibleTrigger>
351
+ <CollapsibleContent className="test">
352
+ <SidebarMenuSub>
353
+ {item.items?.map((subItem, idx) => (
354
+ <SidebarMenuSubItem key={`${subItem.title}-${idx}`}>
355
+ <SidebarMenuSubButton asChild>
356
+ <a href={subItem.url}>
357
+ <span>{subItem.title}</span>
358
+ </a>
359
+ </SidebarMenuSubButton>
360
+ </SidebarMenuSubItem>
361
+ ))}
362
+ </SidebarMenuSub>
363
+ </CollapsibleContent>
364
+ </SidebarMenuItem>
365
+ </Collapsible>
366
+ ) : item.items && state === 'collapsed' ? (
367
+ <DropdownMenu>
368
+ <DropdownMenuTrigger asChild>
369
+ <SidebarMenuButton tooltip={item.title}>
370
+ <Icon
371
+ name={item.icon as IconName}
372
+ color="tertiary"
373
+ size="lg"
374
+ />
375
+ {item.title}
376
+ <Icon
377
+ name="caret-sm-right"
378
+ className="m-left-auto transform data-[state=open]:rotate-90"
379
+ />
380
+ </SidebarMenuButton>
381
+ </DropdownMenuTrigger>
382
+ <DropdownMenuContent side="right" align="start" sideOffset={4}>
383
+ {item.items.map((subItem, idx) => (
384
+ <DropdownMenuItem key={`${subItem.title}-${idx}`}>
385
+ <a href={subItem.url}>
386
+ <span>{subItem.title}</span>
387
+ </a>
388
+ </DropdownMenuItem>
389
+ ))}
390
+ </DropdownMenuContent>
391
+ </DropdownMenu>
392
+ ) : (
393
+ <SidebarMenuItem key={item.title}>
394
+ <SidebarMenuButton
395
+ asChild
396
+ isActive={item.isActive}
397
+ tooltip={item.title}
398
+ >
399
+ <a href={item.url}>
400
+ <Icon
401
+ name={item.icon as IconName}
402
+ color="tertiary"
403
+ size="lg"
404
+ />
405
+ <span>{item.title}</span>
406
+ </a>
407
+ </SidebarMenuButton>
408
+ {item.count && <SidebarMenuBadge>{item.count}</SidebarMenuBadge>}
409
+ </SidebarMenuItem>
410
+ )
411
+ )}
412
+ </SidebarMenu>
413
+ </SidebarGroup>
414
+ );
415
+ };
416
+
417
+ const NavFooter = () => {
418
+ const { state } = useSidebar();
419
+ return (
420
+ <SidebarFooter>
421
+ <SidebarMenu>
422
+ <SidebarMenuItem>
423
+ <DropdownMenu>
424
+ <DropdownMenuTrigger asChild>
425
+ <SidebarMenuButton tooltip="Your Profile">
426
+ <Icon name="user" size="lg" color="tertiary" />
427
+ <Box flex="auto" direction="column" gap="2xs">
428
+ <span className="font-weight-semibold">{data.user.name}</span>
429
+ <span className="truncate font-size-xs font-color-secondary">
430
+ {data.user.email}
431
+ </span>
432
+ </Box>
433
+ <Icon
434
+ name="caret-up-down"
435
+ className="group-data-collapsible-icon-hidden"
436
+ />
437
+ </SidebarMenuButton>
438
+ </DropdownMenuTrigger>
439
+ <DropdownMenuContent
440
+ side={state === 'expanded' ? 'top' : 'right'}
441
+ align="end"
442
+ sideOffset={4}
443
+ >
444
+ <DropdownMenuLabel>
445
+ <Box flex="auto" direction="column" gap="2xs">
446
+ <span className="font-weight-semibold">{data.user.name}</span>
447
+ <span className="truncate font-size-xs font-color-secondary">
448
+ {data.user.email}
449
+ </span>
450
+ </Box>
451
+ </DropdownMenuLabel>
452
+ <DropdownMenuSeparator />
453
+ <DropdownMenuGroup>
454
+ <DropdownMenuItem>
455
+ <Icon name="user" color="tertiary" />
456
+ Profile
457
+ </DropdownMenuItem>
458
+ <DropdownMenuItem>
459
+ <Icon name="alarm" color="tertiary" />
460
+ Notifications{' '}
461
+ </DropdownMenuItem>
462
+ </DropdownMenuGroup>
463
+ <DropdownMenuSeparator />
464
+ <DropdownMenuItem>
465
+ <Icon name="logout" color="tertiary" />
466
+ Log out
467
+ </DropdownMenuItem>
468
+ </DropdownMenuContent>
469
+ </DropdownMenu>
470
+ </SidebarMenuItem>
471
+ </SidebarMenu>
472
+ </SidebarFooter>
473
+ );
474
+ };
475
+
476
+ const NavFavorites = ({ favorites }: { favorites: typeof data.favorites }) => (
477
+ <SidebarGroup>
478
+ <SidebarGroupLabel>Favorites</SidebarGroupLabel>
479
+ <SidebarMenu>
480
+ {favorites.map((item, idx) => (
481
+ <SidebarMenuItem key={`${item.name}-${idx}`}>
482
+ <SidebarMenuButton asChild tooltip={item.name}>
483
+ <a href={item.url}>
484
+ <Icon name={item.icon as IconName} color="tertiary" size="lg" />
485
+ <span>{item.name}</span>
486
+ </a>
487
+ </SidebarMenuButton>
488
+ <DropdownMenu>
489
+ <DropdownMenuTrigger asChild>
490
+ <SidebarMenuAction className="group-data-collapsible-icon-hidden">
491
+ <Icon name="dots" />
492
+ <span className="sr-only">More</span>
493
+ </SidebarMenuAction>
494
+ </DropdownMenuTrigger>
495
+ <DropdownMenuContent side="bottom" align="end">
496
+ <DropdownMenuItem>
497
+ <a href="https://ux.hyphen.ai">View</a>
498
+ </DropdownMenuItem>
499
+ <DropdownMenuItem>
500
+ <a href="https://ux.hyphen.ai">Share</a>
501
+ </DropdownMenuItem>
502
+ <DropdownMenuSeparator />
503
+ <DropdownMenuItem>
504
+ <a href="https://ux.hyphen.ai">Remove</a>
505
+ </DropdownMenuItem>
506
+ </DropdownMenuContent>
507
+ </DropdownMenu>
508
+ </SidebarMenuItem>
509
+ ))}
510
+ </SidebarMenu>
511
+ </SidebarGroup>
512
+ );