|
1 | 1 | import React from 'react' |
2 | 2 | import {useStickyPaneHeight} from './useStickyPaneHeight' |
| 3 | +import {useHorizontalResize} from './useHorizontalResize' |
3 | 4 | import Box from '../Box' |
4 | 5 | import {isResponsiveValue, ResponsiveValue, useResponsiveValue} from '../hooks/useResponsiveValue' |
| 6 | +import {useRefObjectAsForwardedRef} from '../hooks/useRefObjectAsForwardedRef' |
5 | 7 | import {BetterSystemStyleObject, merge, SxProp} from '../sx' |
| 8 | +import {Theme} from '../ThemeProvider' |
6 | 9 |
|
7 | 10 | const REGION_ORDER = { |
8 | 11 | header: 0, |
@@ -105,6 +108,10 @@ Root.displayName = 'PageLayout' |
105 | 108 |
|
106 | 109 | type DividerProps = { |
107 | 110 | variant?: 'none' | 'line' | 'filled' | ResponsiveValue<'none' | 'line' | 'filled'> |
| 111 | + canResize?: boolean |
| 112 | + isResizing?: boolean |
| 113 | + onClick?: (e: React.MouseEvent) => void |
| 114 | + onMouseDown?: (e: React.MouseEvent) => void |
108 | 115 | } & SxProp |
109 | 116 |
|
110 | 117 | const horizontalDividerVariants = { |
@@ -177,18 +184,65 @@ const verticalDividerVariants = { |
177 | 184 | } |
178 | 185 | } |
179 | 186 |
|
180 | | -const VerticalDivider: React.FC<React.PropsWithChildren<DividerProps>> = ({variant = 'none', sx = {}}) => { |
| 187 | +const VerticalDivider: React.FC<React.PropsWithChildren<DividerProps>> = ({ |
| 188 | + variant = 'none', |
| 189 | + canResize, |
| 190 | + isResizing, |
| 191 | + onClick, |
| 192 | + onMouseDown, |
| 193 | + sx = {} |
| 194 | +}) => { |
181 | 195 | const responsiveVariant = useResponsiveValue(variant, 'none') |
182 | 196 | return ( |
183 | 197 | <Box |
184 | 198 | sx={merge<BetterSystemStyleObject>( |
185 | 199 | { |
186 | 200 | height: '100%', |
| 201 | + position: 'relative', |
187 | 202 | ...verticalDividerVariants[responsiveVariant] |
188 | 203 | }, |
189 | 204 | sx |
190 | 205 | )} |
191 | | - /> |
| 206 | + > |
| 207 | + {canResize && ( |
| 208 | + <Box |
| 209 | + onMouseDown={onMouseDown} |
| 210 | + onClick={onClick} |
| 211 | + sx={{ |
| 212 | + width: '16px', |
| 213 | + height: '100%', |
| 214 | + display: 'flex', |
| 215 | + justifyContent: 'center', |
| 216 | + position: 'absolute', |
| 217 | + transform: 'translateX(50%)', |
| 218 | + right: 0, |
| 219 | + opacity: isResizing ? 1 : 0, |
| 220 | + cursor: 'col-resize', |
| 221 | + '&:hover': { |
| 222 | + animation: isResizing ? 'none' : 'resizer-appear 80ms 300ms both', |
| 223 | + |
| 224 | + '@keyframes resizer-appear': { |
| 225 | + from: { |
| 226 | + opacity: 0 |
| 227 | + }, |
| 228 | + |
| 229 | + to: { |
| 230 | + opacity: 1 |
| 231 | + } |
| 232 | + } |
| 233 | + } |
| 234 | + }} |
| 235 | + > |
| 236 | + <Box |
| 237 | + sx={{ |
| 238 | + backgroundColor: 'accent.fg', |
| 239 | + width: '1px', |
| 240 | + height: '100%' |
| 241 | + }} |
| 242 | + /> |
| 243 | + </Box> |
| 244 | + )} |
| 245 | + </Box> |
192 | 246 | ) |
193 | 247 | } |
194 | 248 |
|
@@ -370,6 +424,8 @@ export type PageLayoutPaneProps = { |
370 | 424 | */ |
371 | 425 | positionWhenNarrow?: 'inherit' | keyof typeof panePositions |
372 | 426 | width?: keyof typeof paneWidths |
| 427 | + canResizePane?: boolean |
| 428 | + paneWidthStorageKey?: string |
373 | 429 | padding?: keyof typeof SPACING_MAP |
374 | 430 | divider?: 'none' | 'line' | ResponsiveValue<'none' | 'line', 'none' | 'line' | 'filled'> |
375 | 431 | /** |
@@ -410,6 +466,8 @@ const Pane = React.forwardRef<HTMLDivElement, React.PropsWithChildren<PageLayout |
410 | 466 | positionWhenNarrow = 'inherit', |
411 | 467 | width = 'medium', |
412 | 468 | padding = 'none', |
| 469 | + canResizePane = false, |
| 470 | + paneWidthStorageKey = 'paneWidth', |
413 | 471 | divider: responsiveDivider = 'none', |
414 | 472 | dividerWhenNarrow = 'inherit', |
415 | 473 | sticky = false, |
@@ -448,6 +506,15 @@ const Pane = React.forwardRef<HTMLDivElement, React.PropsWithChildren<PageLayout |
448 | 506 | } |
449 | 507 | }, [sticky, enableStickyPane, disableStickyPane, offsetHeader]) |
450 | 508 |
|
| 509 | + const paneRef = React.useRef<HTMLDivElement>(null) |
| 510 | + useRefObjectAsForwardedRef(forwardRef, paneRef) |
| 511 | + |
| 512 | + const {onMouseDown, onClick, isResizing, paneWidth} = useHorizontalResize( |
| 513 | + canResizePane, |
| 514 | + paneRef, |
| 515 | + paneWidthStorageKey |
| 516 | + ) |
| 517 | + |
451 | 518 | return ( |
452 | 519 | <Box |
453 | 520 | // eslint-disable-next-line @typescript-eslint/no-explicit-any |
@@ -493,10 +560,27 @@ const Pane = React.forwardRef<HTMLDivElement, React.PropsWithChildren<PageLayout |
493 | 560 | /> |
494 | 561 | <VerticalDivider |
495 | 562 | variant={{narrow: 'none', regular: dividerVariant}} |
| 563 | + canResize={canResizePane} |
| 564 | + isResizing={isResizing} |
| 565 | + onClick={onClick} |
| 566 | + onMouseDown={onMouseDown} |
496 | 567 | sx={{[position === 'end' ? 'marginRight' : 'marginLeft']: SPACING_MAP[columnGap]}} |
497 | 568 | /> |
498 | | - |
499 | | - <Box ref={forwardRef} sx={{width: paneWidths[width], padding: SPACING_MAP[padding], overflow: 'auto'}}> |
| 569 | + <Box |
| 570 | + ref={paneRef} |
| 571 | + sx={(theme: Theme) => ({ |
| 572 | + padding: SPACING_MAP[padding], |
| 573 | + overflow: 'auto', |
| 574 | + ...(paneWidth |
| 575 | + ? { |
| 576 | + width: `clamp(256px, ${paneWidth}px, 100vw - 511px)`, |
| 577 | + [`@media screen and (min-width: ${theme.breakpoints[3]})`]: { |
| 578 | + width: `clamp(256px, ${paneWidth}px, 100vw - 959px)` |
| 579 | + } |
| 580 | + } |
| 581 | + : {width: paneWidths[width]}) |
| 582 | + })} |
| 583 | + > |
500 | 584 | {children} |
501 | 585 | </Box> |
502 | 586 | </Box> |
|
0 commit comments