@hyphen/hyphen-components 9.0.0 → 9.1.1
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/dist/css/index.css +17 -0
- package/dist/css/index.css.map +1 -1
- package/dist/css/utilities.css +1 -1
- package/dist/css/utilities.css.map +1 -1
- package/dist/hyphen-components.cjs.development.js +363 -143
- package/dist/hyphen-components.cjs.development.js.map +1 -1
- package/dist/hyphen-components.cjs.production.min.js +2 -2
- package/dist/hyphen-components.cjs.production.min.js.map +1 -1
- package/dist/hyphen-components.esm.js +354 -134
- package/dist/hyphen-components.esm.js.map +1 -1
- package/dist/index.d.ts +14 -1
- package/package.json +4 -3
- package/src/components/Sidebar/Sidebar.mdx +18 -0
- package/src/components/Sidebar/Sidebar.module.scss +18 -0
- package/src/components/Sidebar/Sidebar.react17.test.tsx +35 -0
- package/src/components/Sidebar/Sidebar.resize.test.tsx +391 -0
- package/src/components/Sidebar/Sidebar.stories.tsx +36 -0
- package/src/components/Sidebar/Sidebar.test.tsx +46 -0
- package/src/components/Sidebar/Sidebar.tsx +242 -150
- package/src/components/Sidebar/useSidebarResize.ts +222 -0
|
@@ -5,6 +5,7 @@ import React, {
|
|
|
5
5
|
useEffect,
|
|
6
6
|
forwardRef,
|
|
7
7
|
} from 'react';
|
|
8
|
+
import { useId } from '@radix-ui/react-id';
|
|
8
9
|
import { Slot } from '@radix-ui/react-slot';
|
|
9
10
|
import classNames from 'classnames';
|
|
10
11
|
import { Button } from '../Button/Button';
|
|
@@ -14,6 +15,11 @@ import { Box } from '../Box/Box';
|
|
|
14
15
|
import { IconName } from 'src/types';
|
|
15
16
|
import { Icon } from '../Icon/Icon';
|
|
16
17
|
import styles from './Sidebar.module.scss';
|
|
18
|
+
import {
|
|
19
|
+
SidebarResizeContext,
|
|
20
|
+
SidebarResizeProps,
|
|
21
|
+
useSidebarResize,
|
|
22
|
+
} from './useSidebarResize';
|
|
17
23
|
import {
|
|
18
24
|
Tooltip,
|
|
19
25
|
TooltipContent,
|
|
@@ -326,6 +332,14 @@ const SidebarProvider = forwardRef<
|
|
|
326
332
|
) {
|
|
327
333
|
return;
|
|
328
334
|
}
|
|
335
|
+
// Leave Cmd/Ctrl combinations (e.g. Cmd+[ for back) to the browser.
|
|
336
|
+
// AltGraph also sets ctrlKey on Windows and types [ or ] on some layouts.
|
|
337
|
+
if (
|
|
338
|
+
event.metaKey ||
|
|
339
|
+
(event.ctrlKey && !event.getModifierState('AltGraph'))
|
|
340
|
+
) {
|
|
341
|
+
return;
|
|
342
|
+
}
|
|
329
343
|
const shortcutSide =
|
|
330
344
|
event.key === SIDEBAR_KEYBOARD_SHORTCUT_LEFT
|
|
331
345
|
? 'left'
|
|
@@ -369,6 +383,7 @@ const SidebarProvider = forwardRef<
|
|
|
369
383
|
className
|
|
370
384
|
)}
|
|
371
385
|
ref={ref}
|
|
386
|
+
data-sidebar-provider=""
|
|
372
387
|
{...props}
|
|
373
388
|
>
|
|
374
389
|
{children}
|
|
@@ -384,38 +399,76 @@ SidebarProvider.displayName = 'SidebarProvider';
|
|
|
384
399
|
|
|
385
400
|
const Sidebar = React.forwardRef<
|
|
386
401
|
HTMLDivElement,
|
|
387
|
-
React.ComponentProps<'div'> &
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
402
|
+
React.ComponentProps<'div'> &
|
|
403
|
+
SidebarResizeProps & {
|
|
404
|
+
/**
|
|
405
|
+
* Which edge of the viewport the sidebar is attached to.
|
|
406
|
+
*/
|
|
407
|
+
side?: 'left' | 'right';
|
|
408
|
+
/**
|
|
409
|
+
* Collapse behavior when the sidebar is toggled closed: slide fully out
|
|
410
|
+
* of view (`offcanvas`), collapse to a narrow icon strip (`icon`), or
|
|
411
|
+
* stay at full width (`none`).
|
|
412
|
+
*/
|
|
413
|
+
collapsible?: 'offcanvas' | 'icon' | 'none';
|
|
414
|
+
}
|
|
399
415
|
>(
|
|
400
416
|
(
|
|
401
|
-
{
|
|
417
|
+
{
|
|
418
|
+
side = 'left',
|
|
419
|
+
collapsible = 'offcanvas',
|
|
420
|
+
className,
|
|
421
|
+
children,
|
|
422
|
+
resizable,
|
|
423
|
+
defaultWidth,
|
|
424
|
+
minWidth,
|
|
425
|
+
maxWidth,
|
|
426
|
+
widthStorageKey,
|
|
427
|
+
...props
|
|
428
|
+
},
|
|
402
429
|
ref
|
|
403
430
|
) => {
|
|
404
431
|
const { isMobile, state, openMobile, setOpenMobile } = useSidebar(side);
|
|
405
|
-
const
|
|
432
|
+
const rootRef = React.useRef<HTMLDivElement | null>(null);
|
|
433
|
+
const setRootRef = React.useCallback(
|
|
434
|
+
(node: HTMLDivElement | null) => {
|
|
435
|
+
rootRef.current = node;
|
|
436
|
+
if (typeof ref === 'function') ref(node);
|
|
437
|
+
else if (ref) ref.current = node;
|
|
438
|
+
},
|
|
439
|
+
[ref]
|
|
440
|
+
);
|
|
441
|
+
const resize = useSidebarResize({
|
|
442
|
+
resizable,
|
|
443
|
+
defaultWidth,
|
|
444
|
+
minWidth,
|
|
445
|
+
maxWidth,
|
|
446
|
+
widthStorageKey,
|
|
447
|
+
side,
|
|
448
|
+
isMobile,
|
|
449
|
+
expanded: state === 'expanded' || collapsible === 'none',
|
|
450
|
+
rootRef,
|
|
451
|
+
});
|
|
452
|
+
const sidebarWidth = resizable
|
|
453
|
+
? `${resize.width}px`
|
|
454
|
+
: side === 'right'
|
|
455
|
+
? SIDEBAR_RIGHT_WIDTH
|
|
456
|
+
: SIDEBAR_WIDTH;
|
|
406
457
|
|
|
407
458
|
if (isMobile) {
|
|
408
459
|
return (
|
|
409
460
|
<SidebarSideContext.Provider value={side}>
|
|
410
|
-
<
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
461
|
+
<SidebarResizeContext.Provider value={resize}>
|
|
462
|
+
<Drawer
|
|
463
|
+
isOpen={openMobile}
|
|
464
|
+
onDismiss={() => setOpenMobile(false)}
|
|
465
|
+
placement={side}
|
|
466
|
+
>
|
|
467
|
+
<Box data-sidebar="sidebar" data-mobile="true" height="100">
|
|
468
|
+
{children}
|
|
469
|
+
</Box>
|
|
470
|
+
</Drawer>
|
|
471
|
+
</SidebarResizeContext.Provider>
|
|
419
472
|
</SidebarSideContext.Provider>
|
|
420
473
|
);
|
|
421
474
|
}
|
|
@@ -423,97 +476,106 @@ const Sidebar = React.forwardRef<
|
|
|
423
476
|
if (collapsible === 'none') {
|
|
424
477
|
return (
|
|
425
478
|
<SidebarSideContext.Provider value={side}>
|
|
426
|
-
<
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
{
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
479
|
+
<SidebarResizeContext.Provider value={resize}>
|
|
480
|
+
<div
|
|
481
|
+
className={classNames(
|
|
482
|
+
'group display-flex h-100 font-size-xs flex-direction-column background-color-secondary font-color-base',
|
|
483
|
+
className
|
|
484
|
+
)}
|
|
485
|
+
style={
|
|
486
|
+
{
|
|
487
|
+
'--sidebar-width': sidebarWidth,
|
|
488
|
+
width: 'var(--sidebar-width)',
|
|
489
|
+
...(resizable ? { position: 'relative', flexShrink: 0 } : {}),
|
|
490
|
+
} as React.CSSProperties
|
|
491
|
+
}
|
|
492
|
+
ref={setRootRef}
|
|
493
|
+
{...props}
|
|
494
|
+
>
|
|
495
|
+
{children}
|
|
496
|
+
</div>
|
|
497
|
+
</SidebarResizeContext.Provider>
|
|
442
498
|
</SidebarSideContext.Provider>
|
|
443
499
|
);
|
|
444
500
|
}
|
|
445
501
|
|
|
446
502
|
return (
|
|
447
503
|
<SidebarSideContext.Provider value={side}>
|
|
448
|
-
<
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
{
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
<div
|
|
466
|
-
style={{
|
|
467
|
-
animationTimingFunction:
|
|
468
|
-
'var(--sidebar-transition-timing, linear)',
|
|
469
|
-
transitionTimingFunction:
|
|
470
|
-
'var(--sidebar-transition-timing, linear)',
|
|
471
|
-
transitionDuration: 'var(--sidebar-transition-duration, 200ms)',
|
|
472
|
-
animationDuration: 'var(--sidebar-transition-duration, 200ms)',
|
|
473
|
-
transitionProperty: 'width',
|
|
474
|
-
width: getSidebarWidth(state, collapsible),
|
|
475
|
-
height: '100svh',
|
|
476
|
-
}}
|
|
477
|
-
className={classNames('position-relative', className)}
|
|
478
|
-
/>
|
|
479
|
-
<div
|
|
480
|
-
className={classNames(
|
|
481
|
-
'position-absolute display-none display-flex-desktop ',
|
|
482
|
-
className
|
|
483
|
-
)}
|
|
484
|
-
style={{
|
|
485
|
-
...getSidebarOffsetStyles(side, state, collapsible),
|
|
486
|
-
top: '0',
|
|
487
|
-
bottom: '0',
|
|
488
|
-
zIndex: 'var(--size-z-index-drawer)',
|
|
489
|
-
animationTimingFunction:
|
|
490
|
-
'var(--sidebar-transition-timing, linear)',
|
|
491
|
-
transitionTimingFunction:
|
|
492
|
-
'var(--sidebar-transition-timing, linear)',
|
|
493
|
-
transitionDuration: 'var(--sidebar-transition-duration, 200ms)',
|
|
494
|
-
animationDuration: 'var(--sidebar-transition-duration, 200ms)',
|
|
495
|
-
transitionProperty: 'left, right, width',
|
|
496
|
-
width:
|
|
497
|
-
state === 'collapsed' && collapsible === 'icon'
|
|
498
|
-
? 'var(--sidebar-width-icon)'
|
|
499
|
-
: 'var(--sidebar-width)',
|
|
500
|
-
height: '100svh',
|
|
501
|
-
}}
|
|
502
|
-
{...props}
|
|
504
|
+
<SidebarResizeContext.Provider value={resize}>
|
|
505
|
+
<Box
|
|
506
|
+
ref={setRootRef}
|
|
507
|
+
background="primary"
|
|
508
|
+
display={{ base: 'none', desktop: 'block' }}
|
|
509
|
+
color="base"
|
|
510
|
+
fontSize="sm"
|
|
511
|
+
position="relative"
|
|
512
|
+
style={
|
|
513
|
+
{
|
|
514
|
+
'--sidebar-width': sidebarWidth,
|
|
515
|
+
} as React.CSSProperties
|
|
516
|
+
}
|
|
517
|
+
data-state={state}
|
|
518
|
+
data-collapsible={collapsible}
|
|
519
|
+
data-side={side}
|
|
520
|
+
className="group"
|
|
503
521
|
>
|
|
504
522
|
<div
|
|
505
|
-
|
|
523
|
+
style={{
|
|
524
|
+
animationTimingFunction:
|
|
525
|
+
'var(--sidebar-transition-timing, linear)',
|
|
526
|
+
transitionTimingFunction:
|
|
527
|
+
'var(--sidebar-transition-timing, linear)',
|
|
528
|
+
transitionDuration: resize.animate
|
|
529
|
+
? 'var(--sidebar-transition-duration, 200ms)'
|
|
530
|
+
: '0ms',
|
|
531
|
+
animationDuration: 'var(--sidebar-transition-duration, 200ms)',
|
|
532
|
+
transitionProperty: 'width',
|
|
533
|
+
width: getSidebarWidth(state, collapsible),
|
|
534
|
+
height: '100svh',
|
|
535
|
+
}}
|
|
536
|
+
className={classNames('position-relative', className)}
|
|
537
|
+
/>
|
|
538
|
+
<div
|
|
506
539
|
className={classNames(
|
|
507
|
-
'
|
|
508
|
-
|
|
509
|
-
'p-right-lg-desktop': side === 'right',
|
|
510
|
-
}
|
|
540
|
+
'position-absolute display-none display-flex-desktop ',
|
|
541
|
+
className
|
|
511
542
|
)}
|
|
543
|
+
style={{
|
|
544
|
+
...getSidebarOffsetStyles(side, state, collapsible),
|
|
545
|
+
top: '0',
|
|
546
|
+
bottom: '0',
|
|
547
|
+
zIndex: 'var(--size-z-index-drawer)',
|
|
548
|
+
animationTimingFunction:
|
|
549
|
+
'var(--sidebar-transition-timing, linear)',
|
|
550
|
+
transitionTimingFunction:
|
|
551
|
+
'var(--sidebar-transition-timing, linear)',
|
|
552
|
+
transitionDuration: resize.animate
|
|
553
|
+
? 'var(--sidebar-transition-duration, 200ms)'
|
|
554
|
+
: '0ms',
|
|
555
|
+
animationDuration: 'var(--sidebar-transition-duration, 200ms)',
|
|
556
|
+
transitionProperty: 'left, right, width',
|
|
557
|
+
width:
|
|
558
|
+
state === 'collapsed' && collapsible === 'icon'
|
|
559
|
+
? 'var(--sidebar-width-icon)'
|
|
560
|
+
: 'var(--sidebar-width)',
|
|
561
|
+
height: '100svh',
|
|
562
|
+
}}
|
|
563
|
+
{...props}
|
|
512
564
|
>
|
|
513
|
-
|
|
565
|
+
<div
|
|
566
|
+
data-sidebar="sidebar"
|
|
567
|
+
className={classNames(
|
|
568
|
+
'display-flex h-100 w-100 flex-direction-column background-color-secondary font-color-base',
|
|
569
|
+
{
|
|
570
|
+
'p-right-lg-desktop': side === 'right',
|
|
571
|
+
}
|
|
572
|
+
)}
|
|
573
|
+
>
|
|
574
|
+
{children}
|
|
575
|
+
</div>
|
|
514
576
|
</div>
|
|
515
|
-
</
|
|
516
|
-
</
|
|
577
|
+
</Box>
|
|
578
|
+
</SidebarResizeContext.Provider>
|
|
517
579
|
</SidebarSideContext.Provider>
|
|
518
580
|
);
|
|
519
581
|
}
|
|
@@ -860,6 +922,9 @@ const SidebarRail = React.forwardRef<
|
|
|
860
922
|
React.ComponentProps<'button'>
|
|
861
923
|
>(({ className, ...props }, ref) => {
|
|
862
924
|
const { open, toggleSidebar, side } = useSidebar();
|
|
925
|
+
const resize = React.useContext(SidebarResizeContext);
|
|
926
|
+
// React.useId requires React 18; Radix's useId also supports 16.8 and 17.
|
|
927
|
+
const descriptionId = useId();
|
|
863
928
|
const shortcutLabel =
|
|
864
929
|
side === 'left'
|
|
865
930
|
? SIDEBAR_KEYBOARD_SHORTCUT_LEFT
|
|
@@ -870,64 +935,91 @@ const SidebarRail = React.forwardRef<
|
|
|
870
935
|
? 'caret-sm-right'
|
|
871
936
|
: 'caret-sm-left'
|
|
872
937
|
: side === 'right'
|
|
873
|
-
|
|
874
|
-
|
|
938
|
+
? 'caret-sm-left'
|
|
939
|
+
: 'caret-sm-right';
|
|
875
940
|
|
|
876
941
|
return (
|
|
877
|
-
|
|
878
|
-
|
|
879
|
-
|
|
880
|
-
|
|
881
|
-
|
|
882
|
-
|
|
883
|
-
|
|
884
|
-
|
|
885
|
-
|
|
886
|
-
|
|
887
|
-
{
|
|
888
|
-
|
|
889
|
-
|
|
890
|
-
|
|
891
|
-
|
|
892
|
-
|
|
893
|
-
|
|
894
|
-
|
|
895
|
-
|
|
896
|
-
top: '20px',
|
|
897
|
-
bottom: '20px',
|
|
898
|
-
right: side === 'left' ? '-14px' : undefined,
|
|
899
|
-
left: side === 'right' ? '-18px' : undefined,
|
|
900
|
-
width: '10px',
|
|
901
|
-
}}
|
|
902
|
-
type="button"
|
|
903
|
-
{...props}
|
|
904
|
-
>
|
|
905
|
-
<Box
|
|
906
|
-
radius="xl"
|
|
907
|
-
background="primary"
|
|
908
|
-
color="secondary"
|
|
909
|
-
borderWidth="sm"
|
|
910
|
-
padding="xs"
|
|
911
|
-
margin="0"
|
|
912
|
-
shadow="xs"
|
|
913
|
-
width="3xl"
|
|
914
|
-
height="3xl"
|
|
915
|
-
alignItems="center"
|
|
916
|
-
justifyContent="center"
|
|
942
|
+
<>
|
|
943
|
+
<button
|
|
944
|
+
ref={ref}
|
|
945
|
+
data-sidebar="rail"
|
|
946
|
+
data-resizable={resize?.enabled || undefined}
|
|
947
|
+
aria-label={
|
|
948
|
+
resize?.enabled
|
|
949
|
+
? `Resize or toggle ${side} sidebar`
|
|
950
|
+
: 'Toggle Sidebar'
|
|
951
|
+
}
|
|
952
|
+
aria-describedby={resize?.enabled ? descriptionId : undefined}
|
|
953
|
+
tabIndex={resize?.enabled ? 0 : -1}
|
|
954
|
+
{...(resize?.enabled ? resize.railProps : {})}
|
|
955
|
+
onClick={toggleSidebar}
|
|
956
|
+
title={
|
|
957
|
+
resize?.enabled
|
|
958
|
+
? `Drag to resize; click to toggle (${shortcutLabel})`
|
|
959
|
+
: `Toggle Sidebar ${shortcutLabel}`
|
|
960
|
+
}
|
|
917
961
|
className={classNames(
|
|
918
|
-
|
|
962
|
+
styles.rail,
|
|
963
|
+
'hover-show-child background-color-transparent display-flex p-top-5xl p-left-xl p-right-0 justify-content-center position-absolute',
|
|
919
964
|
{
|
|
920
965
|
'cursor-w-resize':
|
|
921
|
-
|
|
966
|
+
!resize?.enabled &&
|
|
967
|
+
((open && side === 'left') || (!open && side === 'right')),
|
|
922
968
|
'cursor-e-resize':
|
|
923
|
-
|
|
969
|
+
!resize?.enabled &&
|
|
970
|
+
((!open && side === 'left') || (open && side === 'right')),
|
|
924
971
|
},
|
|
925
972
|
className
|
|
926
973
|
)}
|
|
974
|
+
style={{
|
|
975
|
+
top: '20px',
|
|
976
|
+
bottom: '20px',
|
|
977
|
+
right: side === 'left' ? '-14px' : undefined,
|
|
978
|
+
left: side === 'right' ? '-18px' : undefined,
|
|
979
|
+
width: '10px',
|
|
980
|
+
...(resize?.enabled
|
|
981
|
+
? { cursor: 'col-resize', touchAction: 'none' }
|
|
982
|
+
: {}),
|
|
983
|
+
}}
|
|
984
|
+
type="button"
|
|
985
|
+
{...props}
|
|
927
986
|
>
|
|
928
|
-
<
|
|
929
|
-
|
|
930
|
-
|
|
987
|
+
<Box
|
|
988
|
+
radius="xl"
|
|
989
|
+
background="primary"
|
|
990
|
+
color="secondary"
|
|
991
|
+
borderWidth="sm"
|
|
992
|
+
padding="xs"
|
|
993
|
+
margin="0"
|
|
994
|
+
shadow="xs"
|
|
995
|
+
width="3xl"
|
|
996
|
+
height="3xl"
|
|
997
|
+
alignItems="center"
|
|
998
|
+
justifyContent="center"
|
|
999
|
+
className={classNames(
|
|
1000
|
+
'hover-child',
|
|
1001
|
+
{
|
|
1002
|
+
'cursor-w-resize':
|
|
1003
|
+
!resize?.enabled &&
|
|
1004
|
+
((open && side === 'left') || (!open && side === 'right')),
|
|
1005
|
+
'cursor-e-resize':
|
|
1006
|
+
!resize?.enabled &&
|
|
1007
|
+
((!open && side === 'left') || (open && side === 'right')),
|
|
1008
|
+
},
|
|
1009
|
+
className
|
|
1010
|
+
)}
|
|
1011
|
+
>
|
|
1012
|
+
<Icon name={caretIcon} />
|
|
1013
|
+
</Box>
|
|
1014
|
+
</button>
|
|
1015
|
+
{resize?.enabled && (
|
|
1016
|
+
<span id={descriptionId} className={styles.resizeDescription}>
|
|
1017
|
+
Width {Math.round(resize.width)} pixels. Use left and right arrows to
|
|
1018
|
+
resize, Home for minimum, End for maximum, and Enter or Space to
|
|
1019
|
+
toggle.
|
|
1020
|
+
</span>
|
|
1021
|
+
)}
|
|
1022
|
+
</>
|
|
931
1023
|
);
|
|
932
1024
|
});
|
|
933
1025
|
SidebarRail.displayName = 'SidebarRail';
|