149 lines
4.8 KiB
TypeScript
149 lines
4.8 KiB
TypeScript
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
|