UNPKG

react95

Version:

Refreshed Windows95 UI components for modern web apps - React95

238 lines (224 loc) 7.4 kB
import React__default, { forwardRef, useCallback } from 'react'; import styled, { css } from 'styled-components'; import useControlledOrUncontrolled from '../common/hooks/useControlledOrUncontrolled.mjs'; import { LabelText, StyledLabel } from '../common/SwitchBase.mjs'; const Text = styled(LabelText)` white-space: nowrap; `; const focusedElementStyles = css` :focus { outline: none; } ${({ $disabled }) => !$disabled ? css` cursor: pointer; :focus { ${Text} { background: ${({ theme }) => theme.hoverBackground}; color: ${({ theme }) => theme.materialTextInvert}; outline: 2px dotted ${({ theme }) => theme.focusSecondary}; } } ` : `cursor: default;`} `; const TreeWrapper = styled.ul` position: relative; isolation: isolate; ${({ isRootLevel }) => isRootLevel && css` &:before { content: ''; position: absolute; top: 20px; bottom: 0; left: 5.5px; width: 1px; border-left: 2px dashed ${({ theme }) => theme.borderDark}; } `} ul { padding-left: 19.5px; } li { position: relative; &:before { content: ''; position: absolute; top: 17.5px; left: 5.5px; width: 22px; border-top: 2px dashed ${({ theme }) => theme.borderDark}; font-size: 12px; } } `; const TreeItem = styled.li` position: relative; padding-left: ${({ hasItems }) => !hasItems ? "13px" : "0"}; ${({ isRootLevel }) => !isRootLevel ? css` &:last-child { &:after { content: ''; position: absolute; z-index: 1; top: 19.5px; bottom: 0; left: 1.5px; width: 10px; background: ${({ theme }) => theme.material}; } } ` : css` &:last-child { &:after { content: ''; position: absolute; top: 19.5px; left: 1px; bottom: 0; width: 10px; background: ${({ theme }) => theme.material}; } } `} & > details > ul { &:after { content: ''; position: absolute; top: -18px; bottom: 0; left: 25px; border-left: 2px dashed ${({ theme }) => theme.borderDark}; } } `; const Details = styled.details` position: relative; z-index: 2; &[open] > summary:before { content: '-'; } `; const Summary = styled.summary` position: relative; z-index: 1; display: inline-flex; align-items: center; color: ${({ theme }) => theme.materialText}; user-select: none; padding-left: 18px; ${focusedElementStyles}; &::-webkit-details-marker { display: none; } &:before { content: '+'; position: absolute; left: 0; display: block; width: 8px; height: 9px; border: 2px solid #808080; padding-left: 1px; background-color: #fff; line-height: 8px; text-align: center; } `; const TitleWithIcon = styled(StyledLabel)` position: relative; z-index: 1; background: none; border: 0; font-family: inherit; padding-top: 8px; padding-bottom: 8px; margin: 0; ${focusedElementStyles}; `; const Icon = styled.span` display: flex; align-items: center; justify-content: center; width: 16px; height: 16px; margin-right: 6px; `; function toggleItem(state, id) { return state.includes(id) ? state.filter((item) => item !== id) : [...state, id]; } function preventDefault(event) { event.preventDefault(); } function TreeBranch({ className, disabled, expanded, innerRef, level, select, selected, style, tree = [] }) { const isRootLevel = level === 0; const renderLeaf = useCallback((item) => { var _a, _b; const hasItems = Boolean(item.items && item.items.length > 0); const isMenuShown = expanded.includes(item.id); const isNodeDisabled = (_a = disabled || item.disabled) !== null && _a !== void 0 ? _a : false; const onClickSummary = !isNodeDisabled ? (event) => select(event, item) : preventDefault; const onClickLeaf = !isNodeDisabled ? (event) => select(event, item) : preventDefault; const isSelected = selected === item.id; const icon = React__default.createElement(Icon, { "aria-hidden": true }, item.icon); return React__default.createElement(TreeItem, { key: item.label, isRootLevel, role: "treeitem", "aria-expanded": isMenuShown, "aria-selected": isSelected, hasItems }, !hasItems ? React__default.createElement( TitleWithIcon, { as: "button", "$disabled": isNodeDisabled, onClick: onClickLeaf }, icon, React__default.createElement(Text, null, item.label) ) : React__default.createElement( Details, { open: isMenuShown }, React__default.createElement( Summary, { onClick: onClickSummary, "$disabled": isNodeDisabled }, React__default.createElement( TitleWithIcon, { "$disabled": isNodeDisabled }, icon, React__default.createElement(Text, null, item.label) ) ), isMenuShown && React__default.createElement(TreeBranch, { className, disabled: isNodeDisabled, expanded, level: level + 1, select, selected, style, tree: (_b = item.items) !== null && _b !== void 0 ? _b : [] }) )); }, [className, disabled, expanded, isRootLevel, level, select, selected, style]); return React__default.createElement(TreeWrapper, { className: isRootLevel ? className : void 0, style: isRootLevel ? style : void 0, ref: isRootLevel ? innerRef : void 0, role: isRootLevel ? "tree" : "group", isRootLevel }, tree.map(renderLeaf)); } function TreeInner({ className, defaultExpanded = [], defaultSelected, disabled = false, expanded, onNodeSelect, onNodeToggle, selected, style, tree = [] }, ref) { const [expandedInternal, setExpandedInternal] = useControlledOrUncontrolled({ defaultValue: defaultExpanded, onChange: onNodeToggle, onChangePropName: "onNodeToggle", value: expanded, valuePropName: "expanded" }); const [selectedInternal, setSelectedInternal] = useControlledOrUncontrolled({ defaultValue: defaultSelected, onChange: onNodeSelect, onChangePropName: "onNodeSelect", value: selected, valuePropName: "selected" }); const toggleMenu = useCallback((event, id) => { if (onNodeToggle) { const newState = toggleItem(expandedInternal, id); onNodeToggle(event, newState); } setExpandedInternal((previouslyExpandedIds) => toggleItem(previouslyExpandedIds, id)); }, [expandedInternal, onNodeToggle, setExpandedInternal]); const select = useCallback((event, id) => { setSelectedInternal(id); if (onNodeSelect) { onNodeSelect(event, id); } }, [onNodeSelect, setSelectedInternal]); const handleSelect = useCallback((event, item) => { event.preventDefault(); select(event, item.id); if (item.items && item.items.length) { toggleMenu(event, item.id); } }, [select, toggleMenu]); return React__default.createElement(TreeBranch, { className, disabled, expanded: expandedInternal, level: 0, innerRef: ref, select: handleSelect, selected: selectedInternal, style, tree }); } const TreeView = forwardRef(TreeInner); TreeView.displayName = "TreeView"; export { TreeView };