theatre/theatre/studio/src/panels/OutlinePanel/BaseItem.tsx
2023-05-04 10:39:19 +02:00

191 lines
4.1 KiB
TypeScript

import type {VoidFn} from '@theatre/shared/utils/types'
import React from 'react'
import styled, {css} from 'styled-components'
import noop from '@theatre/shared/utils/noop'
import {pointerEventsAutoInNormalMode} from '@theatre/studio/css'
import {ChevronDown, Package} from '@theatre/studio/uiComponents/icons'
export const Container = styled.li`
margin: 0;
padding: 0;
list-style: none;
display: flex;
justify-content: flex-start;
flex-direction: column;
align-items: flex-start;
`
export const BaseHeader = styled.div``
const Header = styled(BaseHeader)`
position: relative;
margin-top: 2px;
margin-bottom: 2px;
margin-left: calc(4px + var(--depth) * 16px);
padding-left: 4px;
padding-right: 8px;
gap: 4px;
height: 21px;
line-height: 0;
box-sizing: border-box;
display: flex;
flex-wrap: nowrap;
align-items: center;
pointer-events: none;
white-space: nowrap;
font-family: 'OPS-Cubic';
border-radius: 2px;
/*box-shadow: 0 3px 4px -1px rgba(0, 0, 0, 0.48);*/
color: rgba(0, 146, 255, 0.9);
background: rgba(255, 255, 255, 0.65);
backdrop-filter: blur(14px);
border-bottom: 1px solid rgba(255, 255, 255, 0.08);
&.descendant-is-selected {
background: rgba(255, 255, 255, 0.7);
}
${pointerEventsAutoInNormalMode};
&:not(.not-selectable):not(.selected):hover {
background: rgba(59, 63, 69, 0.9);
border-bottom: 1px solid rgba(255, 255, 255, 0.24);
}
&:not(.not-selectable):not(.selected):active {
background: rgba(82, 88, 96, 0.9);
border-bottom: 1px solid rgba(255, 255, 255, 0.24);
}
&.selected {
background: rgba(255, 255, 255, 0.7);
border-bottom: 1px solid rgba(255, 255, 255, 0.08);
}
@supports not (backdrop-filter: blur()) {
background: rgba(40, 43, 47, 0.95);
}
`
export const outlineItemFont = css`
font-weight: 500;
font-size: 11px;
& {
}
`
const Head_Label = styled.span`
${outlineItemFont};
${pointerEventsAutoInNormalMode};
position: relative;
// Compensate for border bottom
top: 0.5px;
display: flex;
height: 20px;
align-items: center;
box-sizing: border-box;
`
const Head_IconContainer = styled.div`
font-weight: 500;
display: flex;
justify-content: center;
align-items: center;
position: relative;
opacity: 0.99;
`
const Head_Icon_WithDescendants = styled.span`
font-size: 9px;
position: relative;
display: block;
transition: transform 0.1s ease-out;
&:hover {
transform: rotate(-20deg);
}
${Container}.collapsed & {
transform: rotate(-90deg);
&:hover {
transform: rotate(-70deg);
}
}
`
const ChildrenContainer = styled.ul`
margin: 0;
padding: 0;
list-style: none;
${Container}.collapsed & {
display: none;
}
`
type SelectionStatus =
| 'not-selectable'
| 'not-selected'
| 'selected'
| 'descendant-is-selected'
const BaseItem: React.FC<{
label: React.ReactNode
select?: VoidFn
depth: number
selectionStatus: SelectionStatus
labelDecoration?: React.ReactNode
collapsed?: boolean
setIsCollapsed?: (v: boolean) => void
}> = ({
label,
children,
depth,
select,
selectionStatus,
labelDecoration,
collapsed = false,
setIsCollapsed,
}) => {
const canContainChildren = children !== undefined
return (
<Container
style={
/* @ts-ignore */
{'--depth': depth}
}
className={collapsed ? 'collapsed' : ''}
>
<Header className={selectionStatus} onClick={select ?? noop} data-header>
<Head_IconContainer>
{canContainChildren ? (
<Head_Icon_WithDescendants
onClick={(evt) => {
evt.stopPropagation()
evt.preventDefault()
setIsCollapsed?.(!collapsed)
}}
>
<ChevronDown />
</Head_Icon_WithDescendants>
) : (
<Package />
)}
</Head_IconContainer>
<Head_Label>
<span>{label}</span>
</Head_Label>
{labelDecoration}
</Header>
{canContainChildren && <ChildrenContainer>{children}</ChildrenContainer>}
</Container>
)
}
export default BaseItem