UNPKG

@fluent-windows/core

Version:

React components that inspired by Microsoft's Fluent Design System.

176 lines (164 loc) 6.1 kB
function _extends() { _extends = Object.assign || function (target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i]; for (var key in source) { if (Object.prototype.hasOwnProperty.call(source, key)) { target[key] = source[key]; } } } return target; }; return _extends.apply(this, arguments); } import * as React from 'react'; import classNames from 'classnames'; import { createUseStyles } from '@fluent-windows/styles'; import { useReveal, useHover, usePopper } from '@fluent-windows/hooks'; import { ArrowDownSLine as ArrowDownSLineIcon, ArrowRightSLine as ArrowRightSLineIcon } from '@fluent-windows/icons'; import Item from '../Item'; import Transition from '../Transition'; import Box from '../Box'; import { styles } from './ItemGroup.styled'; import { ItemGroupPropTypes } from './ItemGroup.type'; import { NavigationContext } from '../Navigation/Navigation'; import { ListContext } from '../List/List'; export const name = 'ItemGroup'; const useStyles = createUseStyles(styles, { name }); const ItemGroup = React.forwardRef((props, ref) => { const { as = 'div', className: classNameProp, level = 1, children, title, prefix, shrink = 'expand', ...rest } = props; const { value: activeID, expanded, reveal: navigationReveal, acrylic, horizontal } = React.useContext(NavigationContext); const { reveal: listReveal } = React.useContext(ListContext); const reveal = navigationReveal || listReveal; // handle active item const childIds = React.useMemo(() => React.Children.map(children, child => child.props.value), [children]); const isActiveGroup = React.useMemo(() => { if (activeID) { const target = childIds.find(v => v === activeID); if (target !== undefined) { return true; } } return false; }, [activeID, childIds]); // handle click status (shrink expand) const [clickStatus, setOpen] = React.useState(isActiveGroup); const handleOpen = React.useCallback(() => { if (expanded === true) setOpen(v => !v); }, [expanded]); // If expanded is false, set clickStatus to false const openRecords = React.useRef(false); React.useEffect(() => { if (expanded === true) { openRecords.current = isActiveGroup || clickStatus; } }, [clickStatus, isActiveGroup]); // eslint-disable-line React.useEffect(() => { if (expanded === false) { setOpen(false); } if (expanded === true) { setOpen(openRecords.current); } }, [expanded, isActiveGroup]); // handle hover status (shrink float) const [hoverStatus, bindHover] = useHover(); const [referenceRef, popperRef] = usePopper({ placement: horizontal && level === 1 ? 'bottom-start' : 'right-start', eventsEnabled: true, positionFixed: true, modifiers: { preventOverflow: { enabled: true, priority: ['right', 'bottom'], boundariesElement: 'viewport' }, flip: { enabled: true } } }); // handle Reveal Effects const [RevealWrapper] = useReveal(66); const titleElement = React.useMemo(() => reveal ? React.createElement(RevealWrapper, null, React.createElement(Item, { prefix: prefix }, title)) : React.createElement(Item, { prefix: prefix }, title), [reveal, prefix, title]); const childElements = React.useMemo(() => reveal ? React.Children.map(children, (child, i) => { if (child.type) { if (child.type.displayName === 'FItem') { return React.createElement(RevealWrapper, { key: i }, child); } else if (child.type.displayName === `F${name}`) { return React.cloneElement(child, { level: level + 1 }); } } return child; }) : React.Children.map(children, child => { if (child.type && child.type.displayName === `F${name}`) { return React.cloneElement(child, { level: level + 1 }); } return child; }), [reveal, children, level]); const isFloat = shrink === 'float' || horizontal; const classes = useStyles(props); const titleClassName = classNames(classes.titleRoot, { [classes.titleActive]: isActiveGroup, [classes.titleFloatAndHorizontal]: isFloat && horizontal }); const titlePrefixClassName = classNames(classes.titlePrefix, { [classes.titlePrefixNotFloatOpen]: !isFloat && clickStatus, [classes.titlePrefixNotFloatClose]: !isFloat && !clickStatus, [classes.titlePrefixHorizontal]: horizontal, [classes.titlePrefixExpanded]: !horizontal && expanded, [classes.titlePrefixAcrylic]: acrylic }); const className = classNames(classes.root, { [classes.level]: !isFloat, [classes.float]: isFloat }); return React.createElement(Box, _extends({ className: classNameProp, ref: ref, as: as }, rest), shrink === 'expand' && !horizontal && React.createElement(React.Fragment, null, React.createElement("div", { className: titleClassName, ref: referenceRef, onClick: handleOpen }, titleElement, React.createElement("div", { className: titlePrefixClassName }, React.createElement(ArrowDownSLineIcon, null))), React.createElement(Transition, { visible: clickStatus, type: "collapse" }, React.createElement(Box, { className: className, acrylic: acrylic && horizontal }, childElements))), isFloat && React.createElement("div", _extends({ className: titleClassName, ref: referenceRef }, bindHover), titleElement, React.createElement("div", { className: titlePrefixClassName }, horizontal ? level === 1 ? React.createElement(ArrowDownSLineIcon, null) : React.createElement(ArrowRightSLineIcon, null) : React.createElement(ArrowRightSLineIcon, null)), React.createElement(Transition, { visible: hoverStatus, type: "grow", wrapper: false }, React.createElement(Box, { className: className, ref: popperRef, acrylic: acrylic && isFloat }, childElements)))); }); ItemGroup.displayName = `F${name}`; ItemGroup.propTypes = ItemGroupPropTypes; ItemGroup.defaultProps = { level: 1 }; export default ItemGroup;