@fluent-windows/core
Version:
React components that inspired by Microsoft's Fluent Design System.
176 lines (164 loc) • 6.1 kB
JavaScript
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;