Checkpoint
This commit is contained in:
148
src/components/Layout/Split.tsx
Normal file
148
src/components/Layout/Split.tsx
Normal file
@@ -0,0 +1,148 @@
|
||||
import React from 'react'
|
||||
|
||||
export type AccentKey =
|
||||
| 'primary'
|
||||
| 'warning'
|
||||
| 'info'
|
||||
| 'success'
|
||||
| 'danger'
|
||||
| 'secondary'
|
||||
| 'green'
|
||||
| 'pink'
|
||||
| 'yellow'
|
||||
| 'light'
|
||||
| 'dark'
|
||||
|
||||
export interface PanelProps extends React.HTMLAttributes<HTMLDivElement> {
|
||||
size?: number
|
||||
variant?: 'border' | 'solid'
|
||||
accent?: AccentKey
|
||||
children?: React.ReactNode
|
||||
}
|
||||
|
||||
const Panel: React.FC<PanelProps> = ({ children }) => <>{children}</>
|
||||
Panel.displayName = 'SplitPanel'
|
||||
|
||||
interface SplitProps {
|
||||
direction?: 'horizontal' | 'vertical'
|
||||
gutter?: string | number
|
||||
className?: string
|
||||
// allow any children but we will look for Panel elements
|
||||
children?: React.ReactNode
|
||||
variant?: 'border' | 'solid'
|
||||
accent?: AccentKey
|
||||
}
|
||||
|
||||
const Split: React.FC<SplitProps> & { Panel: React.FC<PanelProps> } = ({
|
||||
direction = 'horizontal',
|
||||
gutter = 20,
|
||||
className = '',
|
||||
children,
|
||||
variant: splitVariant,
|
||||
accent: splitAccent,
|
||||
}) => {
|
||||
const childrenArray = React.Children.toArray(children)
|
||||
if (childrenArray.length < 1) {
|
||||
throw new Error('Split requires at least one child Panel')
|
||||
}
|
||||
|
||||
const gutterVal = typeof gutter === 'number' ? `${gutter}px` : gutter
|
||||
const containerStyle: React.CSSProperties = { ['--split-gutter' as any]: gutterVal }
|
||||
|
||||
const dirClass = direction === 'vertical' ? 'split--vertical' : 'split--horizontal'
|
||||
|
||||
// detect Panel children and extract sizes and passthrough props
|
||||
const sizes: (number | null)[] = childrenArray.map((c) => {
|
||||
if (React.isValidElement(c) && (c.type as any).displayName === 'SplitPanel') {
|
||||
const p = c as React.ReactElement<PanelProps>
|
||||
return typeof p.props.size === 'number' ? p.props.size : 1
|
||||
}
|
||||
return null
|
||||
})
|
||||
|
||||
const allPanels = sizes.every((s) => s != null)
|
||||
|
||||
// compute flex styles if panels provided sizes
|
||||
|
||||
return (
|
||||
<div className={`split ${dirClass} ${className}`.trim()} style={containerStyle}>
|
||||
{childrenArray.map((child, idx) => {
|
||||
const style: React.CSSProperties = {}
|
||||
let extraClass = ''
|
||||
let extraStyle: React.CSSProperties | undefined
|
||||
|
||||
if (React.isValidElement(child) && (child.type as any).displayName === 'SplitPanel') {
|
||||
const p = child as React.ReactElement<PanelProps>
|
||||
extraClass = p.props.className || ''
|
||||
extraStyle = p.props.style
|
||||
// inherit variant/accent from parent if panel doesn't specify one
|
||||
if (!p.props.variant && splitVariant) {
|
||||
// handled below
|
||||
}
|
||||
if (!p.props.accent && splitAccent) {
|
||||
// handled below
|
||||
}
|
||||
}
|
||||
|
||||
if (allPanels) {
|
||||
const unit = (sizes[idx] || 0) as number
|
||||
style.flex = `${unit} 1 0`
|
||||
}
|
||||
|
||||
const mergedStyle = extraStyle ? { ...style, ...extraStyle } : style
|
||||
const childElem = React.isValidElement(child) ? (child as React.ReactElement<PanelProps>) : null
|
||||
const variant = childElem ? childElem.props.variant ?? splitVariant : splitVariant
|
||||
// Determine accent: a panel with its own `accent` always uses it.
|
||||
// Otherwise, if the parent `Split` has an `accent`, only the first
|
||||
// panel (top-left decoration) and the last panel (bottom-right)
|
||||
// receive the inherited accent. No other panels inherit the split
|
||||
// accent.
|
||||
const panelHasOwnAccent = !!(childElem && childElem.props.accent)
|
||||
let accent: AccentKey | undefined = undefined
|
||||
let accentTL = false
|
||||
let accentBR = false
|
||||
const lastIndex = childrenArray.length - 1
|
||||
const isFirst = idx === 0
|
||||
const isLast = idx === lastIndex
|
||||
|
||||
if (panelHasOwnAccent) {
|
||||
// panel-specified accent: show both decorations
|
||||
accent = childElem!.props.accent as AccentKey
|
||||
accentTL = true
|
||||
accentBR = true
|
||||
} else if (splitAccent) {
|
||||
// inherited accent from Split: only first gets top-left, last gets bottom-right
|
||||
if (isFirst) {
|
||||
accent = splitAccent
|
||||
accentTL = true
|
||||
}
|
||||
if (isLast) {
|
||||
accent = splitAccent
|
||||
accentBR = true
|
||||
}
|
||||
}
|
||||
let variantClass = ''
|
||||
if (variant === 'border') variantClass = 'pane--border'
|
||||
else if (variant === 'solid') variantClass = 'pane--solid'
|
||||
|
||||
const paneClass = `pane ${variantClass} ${extraClass} ${accentTL ? 'pane--accent--tl' : ''} ${accentBR ? 'pane--accent--br' : ''}`.trim()
|
||||
|
||||
// attach accent color as CSS var for the mixin to pick up
|
||||
const finalStyle = { ...mergedStyle }
|
||||
if (accent) {
|
||||
;(finalStyle as any)['--pane-accent-color'] = `var(--color-variant-${accent})`
|
||||
}
|
||||
|
||||
return (
|
||||
<div className={paneClass} key={idx} style={finalStyle}>
|
||||
{child}
|
||||
</div>
|
||||
)
|
||||
})}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
Split.Panel = Panel
|
||||
export { Split }
|
||||
export default Split
|
||||
Reference in New Issue
Block a user