@fluentui/react-northstar
Version:
A themable React component library.
251 lines (248 loc) • 10.2 kB
JavaScript
import _extends from "@babel/runtime/helpers/esm/extends";
import _each from "lodash/each";
import _invoke from "lodash/invoke";
import _without from "lodash/without";
import _includes from "lodash/includes";
import _uniqueId from "lodash/uniqueId";
import { accordionBehavior } from '@fluentui/accessibility';
import * as customPropTypes from '@fluentui/react-proptypes';
import * as PropTypes from 'prop-types';
import * as React from 'react';
import { childrenExist, commonPropTypes, rtlTextContainer, createShorthand, createShorthandFactory } from '../../utils';
import { AccordionTitle } from './AccordionTitle';
import { AccordionContent } from './AccordionContent';
import { ContainerFocusHandler } from '../../utils/accessibility/FocusHandling/FocusContainer';
import { useAutoControlled, useAccessibility, useTelemetry, useFluentContext, useUnhandledProps, getElementType, useStyles } from '@fluentui/react-bindings';
export var accordionClassName = 'ui-accordion';
export var accordionSlotClassNames = {
content: accordionClassName + "__content",
title: accordionClassName + "__title"
};
/**
* An Accordion represents stacked set of content sections, with action elements to toggle the display of these sections.
*
* @accessibility
* Implements [ARIA Accordion](https://www.w3.org/TR/wai-aria-practices-1.1/#accordion) design pattern (keyboard navigation not yet supported).
*/
export var Accordion = /*#__PURE__*/function () {
var Accordion = /*#__PURE__*/React.forwardRef(function (props, ref) {
var context = useFluentContext();
var _useTelemetry = useTelemetry(Accordion.displayName, context.telemetry),
setStart = _useTelemetry.setStart,
setEnd = _useTelemetry.setEnd;
setStart();
var expanded = props.expanded,
exclusive = props.exclusive,
accessibility = props.accessibility,
children = props.children,
className = props.className,
design = props.design,
styles = props.styles,
variables = props.variables,
panels = props.panels,
renderPanelContent = props.renderPanelContent,
renderPanelTitle = props.renderPanelTitle,
alwaysRenderPanels = props.alwaysRenderPanels;
var alwaysActiveIndex = expanded ? 0 : -1;
var _useAutoControlled = useAutoControlled({
defaultValue: props.defaultActiveIndex,
value: props.activeIndex,
initialValue: exclusive ? alwaysActiveIndex : [alwaysActiveIndex]
}),
activeIndex = _useAutoControlled[0],
setActiveIndex = _useAutoControlled[1];
var actionHandlers = {
moveNext: function moveNext(e) {
e.preventDefault();
focusHandler.moveNext();
},
movePrevious: function movePrevious(e) {
e.preventDefault();
focusHandler.movePrevious();
},
moveFirst: function moveFirst(e) {
e.preventDefault();
focusHandler.moveFirst();
},
moveLast: function moveLast(e) {
e.preventDefault();
focusHandler.moveLast();
}
};
var getA11yProps = useAccessibility(accessibility, {
debugName: Accordion.displayName,
actionHandlers: actionHandlers,
rtl: context.rtl
});
var _useStyles = useStyles(Accordion.displayName, {
className: accordionClassName,
mapPropsToInlineStyles: function mapPropsToInlineStyles() {
return {
className: className,
design: design,
styles: styles,
variables: variables
};
},
rtl: context.rtl
}),
classes = _useStyles.classes;
var _React$useState = React.useState(),
focusedIndex = _React$useState[0],
setfocusedIndex = _React$useState[1];
var handleNavigationFocus = function handleNavigationFocus(index) {
setfocusedIndex(index);
};
var getNavigationItemsSize = function getNavigationItemsSize() {
return props.panels.length;
};
var unhandledProps = useUnhandledProps(Accordion.handledProps, props);
var ElementType = getElementType(props);
var focusHandler = new ContainerFocusHandler(getNavigationItemsSize, handleNavigationFocus, true);
var itemRefs = React.useMemo(function () {
return Array.from({
length: panels == null ? void 0 : panels.length
}, function () {
return /*#__PURE__*/React.createRef();
});
},
// As we are using "panels.length" it's fine to have dependency on them
// eslint-disable-next-line react-hooks/exhaustive-deps
[panels == null ? void 0 : panels.length]);
React.useEffect(function () {
var targetComponent = itemRefs[focusedIndex] && itemRefs[focusedIndex].current;
targetComponent && targetComponent.focus();
}, [itemRefs, focusedIndex]);
var defaultAccordionTitleId = React.useMemo(function () {
return _uniqueId('accordion-title-');
}, []);
var defaultAccordionContentId = React.useMemo(function () {
return _uniqueId('accordion-content-');
}, []);
var computeNewIndex = function computeNewIndex(index) {
if (!isIndexActionable(index)) {
return activeIndex;
}
if (exclusive) return index === activeIndex ? -1 : index;
// check to see if index is in array, and remove it, if not then add it
return _includes(activeIndex, index) ? _without(activeIndex, index) : [].concat(activeIndex, [index]);
};
var handleTitleOverrides = function handleTitleOverrides(predefinedProps) {
return {
onClick: function onClick(e, titleProps) {
var index = titleProps.index;
var activeIndex = computeNewIndex(index);
setActiveIndex(activeIndex);
setfocusedIndex(index);
_invoke(props, 'onActiveIndexChange', e, Object.assign({}, props, {
activeIndex: activeIndex
}));
_invoke(predefinedProps, 'onClick', e, titleProps);
_invoke(props, 'onTitleClick', e, titleProps);
},
onFocus: function onFocus(e, titleProps) {
_invoke(predefinedProps, 'onFocus', e, titleProps);
setfocusedIndex(predefinedProps.index);
}
};
};
var isIndexActive = function isIndexActive(index) {
return exclusive ? activeIndex === index : _includes(activeIndex, index);
};
/**
* Checks if panel at index can be actioned upon. Used in the case of expanded accordion,
* when at least a panel needs to stay active. Will return false if expanded prop is true,
* index is active and either it's an exclusive accordion or if there are no other active
* panels open besides this one.
*
* @param index - The index of the panel.
* @returns If the panel can be set active/inactive.
*/
var isIndexActionable = function isIndexActionable(index) {
if (!isIndexActive(index)) {
return true;
}
return !expanded || !exclusive && activeIndex.length > 1;
};
var renderPanels = function renderPanels() {
var children = [];
focusHandler.syncFocusedIndex(focusedIndex);
_each(panels, function (panel, index) {
var content = panel.content,
title = panel.title;
var active = isIndexActive(+index);
var canBeCollapsed = isIndexActionable(+index);
var titleId = title['id'] || "" + defaultAccordionTitleId + index;
var contentId = content['id'] || "" + defaultAccordionContentId + index;
var contentRef = itemRefs[index];
children.push(createShorthand(AccordionTitle, title, {
defaultProps: function defaultProps() {
return {
className: accordionSlotClassNames.title,
active: active,
index: +index,
contentRef: contentRef,
canBeCollapsed: canBeCollapsed,
id: titleId,
accordionContentId: contentId
};
},
overrideProps: handleTitleOverrides,
render: renderPanelTitle
}));
if (alwaysRenderPanels || active) {
children.push(createShorthand(AccordionContent, content, {
defaultProps: function defaultProps() {
return {
className: accordionSlotClassNames.content,
active: active,
id: contentId,
accordionTitleId: titleId
};
},
render: renderPanelContent
}));
}
});
return children;
};
var element = /*#__PURE__*/React.createElement(ElementType, _extends({}, getA11yProps('root', Object.assign({
className: classes.root
}, unhandledProps, {
ref: ref
})), rtlTextContainer.getAttributes({
forElements: [children]
})), childrenExist(children) ? children : renderPanels());
setEnd();
return element;
});
Accordion.displayName = 'Accordion';
Accordion.propTypes = Object.assign({}, commonPropTypes.createCommon({
content: false
}), {
activeIndex: customPropTypes.every([customPropTypes.disallow(['children']), PropTypes.oneOfType([PropTypes.arrayOf(PropTypes.number), PropTypes.number])]),
defaultActiveIndex: customPropTypes.every([customPropTypes.disallow(['children']), PropTypes.oneOfType([PropTypes.arrayOf(PropTypes.number), PropTypes.number])]),
exclusive: PropTypes.bool,
expanded: PropTypes.bool,
onTitleClick: customPropTypes.every([customPropTypes.disallow(['children']), PropTypes.func]),
onActiveIndexChange: PropTypes.func,
panels: customPropTypes.every([customPropTypes.disallow(['children']), PropTypes.arrayOf(PropTypes.shape({
content: customPropTypes.itemShorthand,
title: customPropTypes.itemShorthand
}))]),
renderPanelTitle: PropTypes.func,
renderPanelContent: PropTypes.func
});
Accordion.defaultProps = {
accessibility: accordionBehavior,
as: 'dl'
};
Accordion.handledProps = Object.keys(Accordion.propTypes);
Accordion.Title = AccordionTitle;
Accordion.Content = AccordionContent;
Accordion.create = createShorthandFactory({
Component: Accordion
});
return Accordion;
}();
//# sourceMappingURL=Accordion.js.map