* use the dropdown initializer in post editor options * use dropdown initializer in comment subs dropdown * use Dropdown component in listings * show the dropdown button when it's focused * update storybook notes and components * add cypress tests for post options dropdown * add comment subscription tests * make sure page is properly initialized before setting button functionality * add jsdoc comments, props types, rely on optional chaining * Update app/javascript/crayons/Dropdown/__stories__/dropdowns.md Co-authored-by: rhymes <github@rhymes.dev> * Update app/javascript/crayons/Dropdown/__stories__/dropdowns.md Co-authored-by: rhymes <github@rhymes.dev> Co-authored-by: rhymes <github@rhymes.dev>
91 lines
2.7 KiB
JavaScript
91 lines
2.7 KiB
JavaScript
import { h } from 'preact';
|
|
import { useLayoutEffect, useState } from 'preact/hooks';
|
|
import PropTypes from 'prop-types';
|
|
import { defaultChildrenPropTypes } from '../../common-prop-types/default-children-prop-types';
|
|
import { initializeDropdown } from '@utilities/dropdownUtils';
|
|
|
|
/**
|
|
* A component to render a dropdown with the provided children.
|
|
* This component handles the attachment of all open/close click events and listeners.
|
|
*
|
|
* @param {Object} props
|
|
* @param {Array} props.children Children to be rendered inside the dropdown, passed via composition
|
|
* @param {String} props.className Optional string of classnames to be applied to the dropdown (e.g for positioning)
|
|
* @param {String} props.triggerButtonId The ID of the button element which should open and close the dropdown
|
|
* @param {String} props.dropdownContentId The ID to be applied to the dropdown itself
|
|
* @param {String} props.dropdownContentCloseButtonId An optional ID for any button inside the dropdown content itself which should close it
|
|
* @param {Function} props.onOpen Optional callback for any side-effects needed when the dropdown opens
|
|
* @param {Function} props.onClose Optional callback for any side-effects needed when the dropdown closes
|
|
*
|
|
* @example
|
|
* <div>
|
|
* <button id="toggle-dropdown-button">Toggle dropdown</button>
|
|
* <Dropdown
|
|
* className="right-4 left-4"
|
|
* triggerButtonId="toggle-dropdown-button"
|
|
* dropdownContentId="dropdown-content"
|
|
* >
|
|
* {dropdownInnerContent}
|
|
* </Dropdown>
|
|
* </div>
|
|
*/
|
|
export const Dropdown = ({
|
|
children,
|
|
className,
|
|
triggerButtonId,
|
|
dropdownContentId,
|
|
dropdownContentCloseButtonId,
|
|
onOpen = () => {},
|
|
onClose = () => {},
|
|
...restOfProps
|
|
}) => {
|
|
const [isInitialized, setIsInitialized] = useState(false);
|
|
useLayoutEffect(() => {
|
|
if (!isInitialized) {
|
|
initializeDropdown({
|
|
triggerElementId: triggerButtonId,
|
|
dropdownContentId,
|
|
dropdownContentCloseButtonId,
|
|
onOpen,
|
|
onClose,
|
|
});
|
|
|
|
setIsInitialized(true);
|
|
}
|
|
}, [
|
|
dropdownContentId,
|
|
triggerButtonId,
|
|
dropdownContentCloseButtonId,
|
|
isInitialized,
|
|
onOpen,
|
|
onClose,
|
|
]);
|
|
|
|
return (
|
|
<div
|
|
id={dropdownContentId}
|
|
className={`crayons-dropdown${
|
|
className && className.length > 0 ? ` ${className}` : ''
|
|
}`}
|
|
{...restOfProps}
|
|
>
|
|
{children}
|
|
</div>
|
|
);
|
|
};
|
|
|
|
Dropdown.defaultProps = {
|
|
className: undefined,
|
|
};
|
|
|
|
Dropdown.displayName = 'Dropdown';
|
|
|
|
Dropdown.propTypes = {
|
|
children: defaultChildrenPropTypes.isRequired,
|
|
className: PropTypes.string,
|
|
triggerButtonId: PropTypes.string.isRequired,
|
|
dropdownContentId: PropTypes.string.isRequired,
|
|
dropdownContentCloseButtonId: PropTypes.string,
|
|
onOpen: PropTypes.func,
|
|
onClose: PropTypes.func,
|
|
};
|