import type { SxProps, Theme } from '@mui/material' /** * Design height of the group header (Figma "Group layers header", 48px). * Grouped layer-row headers use it as their sticky `top` offset so they pin * just below their group's header while the panel scrolls. */ export const LEGEND_GROUP_HEADER_HEIGHT = 48 export const styles = { group: { display: 'flex', flexDirection: 'column', '& + &': { borderTop: '1px solid', borderColor: 'divider', }, }, // The original stays in place, dimmed, while its overlay clone is dragged. groupDragging: { opacity: 0.4, }, // Figma: 48px tall, 16px left inset (matches the row's own left inset), // 8px gap to the label, actions right-aligned with ~8px edge inset (the // small icon-button padding brings the glyphs to the design's 16px optical // inset), hairline divider below. Sticky: pins to the top of the panel's // scroll container. header: { display: 'flex', alignItems: 'center', gap: 1, padding: ({ spacing }) => spacing(1.5, 2, 1.5, 2), minHeight: LEGEND_GROUP_HEADER_HEIGHT, borderBottom: '1px solid', borderColor: 'divider', position: 'sticky', top: 0, // Above the sticky row headers (2), which in turn sit above MUI's // z-index-1 form internals in row content. zIndex: 3, // Distinct from the row header / panel body (both `background.paper`) so // the group header visually separates from the rest. backgroundColor: 'background.default', // Hover-fade group actions (widgets-v2 Wrapper parity). Scoped to the // header itself (not the group root) so hovering a member row's body // below never lights up the group's own actions/handle — only hovering // the header (title/handle/actions) does. A control carrying `.active` // (hidden group) stays visible. Touch devices always show them. The // collapse chevron sits outside the fade group. Each non-active child // also collapses to zero width on its own (not just the whole actions // slot) so a sibling's `.active` pin (e.g. the eye on a mixed-visibility // group) doesn't leave a same-sized invisible hole where the still-faded // ⋮ menu would've been. '& .PsLegend-groupFade > *': { opacity: 1, transition: ({ transitions }: Theme) => transitions.create(['opacity', 'width'], { duration: transitions.duration.standard, easing: transitions.easing.easeInOut, }), '@media (hover: hover)': { opacity: 0, }, '&.active': { opacity: 1, }, }, // Faded actions don't reserve flex space on fine-pointer devices — the // label grows to the trailing edge. Hover/focus-within restores the slot // so the label reflows with the buttons; a dirty `.active` child keeps // the slot open even outside hover. '@media (hover: hover)': { '& .PsLegend-groupFade > *:not(.active)': { width: 0, minWidth: 0, // border-box IconButtons keep their padding inside `width` — zero it // too, otherwise the fixed 2px+2px padding survives as a tiny but // needless residual gap. padding: 0, overflow: 'hidden', pointerEvents: 'none', }, '& .PsLegend-groupActions:not(:has(.active))': { width: 0, minWidth: 0, overflow: 'hidden', // Cancel the header gap so nothing is left behind the label. marginInlineStart: ({ spacing }: Theme) => `calc(-1 * ${spacing(1)})`, pointerEvents: 'none', }, }, '&:hover .PsLegend-groupFade > *, &:focus-within .PsLegend-groupFade > *': { opacity: 1, width: 'auto', minWidth: 0, padding: '2px', overflow: 'visible', pointerEvents: 'auto', }, '&:hover .PsLegend-groupActions, &:focus-within .PsLegend-groupActions': { width: 'auto', minWidth: 0, overflow: 'visible', marginInlineStart: 0, pointerEvents: 'auto', }, // Drag handle (Legend.Sortable): absolutely positioned (no reserved flex // slot, so it never shifts the title/icon); the button inside fades in // on hover/focus and is always visible on touch devices. '& .PsLegend-dragHandle-group > *': { opacity: 0, transition: ({ transitions }: Theme) => transitions.create('opacity', { duration: transitions.duration.standard, easing: transitions.easing.easeInOut, }), '@media (hover: none)': { opacity: 1, }, }, '&:hover .PsLegend-dragHandle-group > *, &:focus-within .PsLegend-dragHandle-group > *': { opacity: 1, }, }, // Accordion click target: everything in the header but the actions box. // Non-native button (role='button'), so it carries its own focus ring. titleArea: { flex: 1, minWidth: 0, display: 'flex', alignItems: 'center', gap: 1, cursor: 'pointer', userSelect: 'none', '&:focus-visible': { outline: '2px solid', outlineColor: 'primary.main', outlineOffset: -2, }, }, groupIcon: { display: 'flex', color: 'text.secondary', }, // No `flex:1` here — the label sizes to its own text (shrinking to // ellipsis when too long) so the chevron sits right after it, instead of // being dragged to the trailing edge of `titleArea` (which keeps its own // `flex:1` to fill the header up to the actions). label: { minWidth: 0, overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap', }, // Informative collapse indicator beside the title (the whole title area is // the button). Points down when expanded, right when collapsed. Touch // devices don't get it — the title row is the affordance there. chevron: { fontSize: 16, color: 'text.secondary', flexShrink: 0, transition: ({ transitions }: Theme) => transitions.create('transform', { duration: transitions.duration.shorter, }), '@media (hover: none)': { display: 'none', }, }, chevronCollapsed: { transform: 'rotate(-90deg)', }, // Icon buttons match the row header's 24×24 (2px padding + 20px icon). actions: { display: 'flex', alignItems: 'center', gap: 0.25, flexShrink: 0, '& .MuiIconButton-root': { padding: '2px', }, }, } satisfies Record>