rsuite
Version:
A suite of react components
103 lines • 3.17 kB
JavaScript
'use client';
// Headless Disclosure
// Ref: https://w3c.github.io/aria-practices/#disclosure
import React, { useMemo, useReducer, useRef, useCallback, useContext } from 'react';
import DisclosureContext, { DisclosureActionTypes } from "./DisclosureContext.js";
import DisclosureButton from "./DisclosureButton.js";
import DisclosureContent from "./DisclosureContent.js";
import useClickOutside from "../hooks/useClickOutside.js";
const initialDisclosureState = {
open: false
};
function disclosureReducer(state, action) {
switch (action.type) {
case DisclosureActionTypes.Show:
return {
...state,
open: true
};
case DisclosureActionTypes.Hide:
return {
...state,
open: false
};
}
return state;
}
const Disclosure = /*#__PURE__*/React.memo(props => {
const {
children,
open: openProp,
defaultOpen = false,
hideOnClickOutside = false,
onToggle,
trigger = ['click']
} = props;
const parentDisclosure = useContext(DisclosureContext);
const [{
open: openState
}, dispatch] = useReducer(disclosureReducer, {
...initialDisclosureState,
open: defaultOpen
});
const containerElementRef = useRef(null);
const open = openProp !== null && openProp !== void 0 ? openProp : openState;
useClickOutside({
enabled: hideOnClickOutside,
isOutside: event => {
var _containerElementRef$;
return !((_containerElementRef$ = containerElementRef.current) !== null && _containerElementRef$ !== void 0 && _containerElementRef$.contains(event.target));
},
handle: () => dispatch({
type: DisclosureActionTypes.Hide
})
});
const onMouseEnter = useCallback(event => {
if (!open) {
dispatch({
type: DisclosureActionTypes.Show
});
onToggle === null || onToggle === void 0 || onToggle(true, event);
}
}, [open, dispatch, onToggle]);
const onMouseLeave = useCallback(event => {
if (open) {
dispatch({
type: DisclosureActionTypes.Hide
});
onToggle === null || onToggle === void 0 || onToggle(false, event);
}
}, [open, dispatch, onToggle]);
const contextValue = useMemo(() => {
const cascadeDispatch = action => {
const result = dispatch(action);
if ('cascade' in action) {
parentDisclosure === null || parentDisclosure === void 0 || parentDisclosure[1](action);
}
return result;
};
return [{
open
}, cascadeDispatch, {
onToggle,
trigger
}];
}, [parentDisclosure, open, dispatch, onToggle, trigger]);
const renderProps = useMemo(() => {
const renderProps = {
open
};
if (trigger.includes('hover')) {
renderProps.onMouseEnter = onMouseEnter;
renderProps.onMouseLeave = onMouseLeave;
}
return renderProps;
}, [open, trigger, onMouseEnter, onMouseLeave]);
return /*#__PURE__*/React.createElement(DisclosureContext.Provider, {
value: contextValue
}, children(renderProps, containerElementRef));
});
Disclosure.displayName = 'Disclosure';
Disclosure.Button = DisclosureButton;
Disclosure.Content = DisclosureContent;
export default Disclosure;