@nex-ui/react
Version:
🎉 A beautiful, modern, and reliable React component library.
257 lines (254 loc) • 8.32 kB
JavaScript
"use client";
import { jsx, jsxs } from 'react/jsx-runtime';
import * as m from 'motion/react-m';
import { useFocusVisible, useEvent } from '@nex-ui/hooks';
import { ChevronDownOutlined } from '@nex-ui/icons';
import { LazyMotion, domAnimation, AnimatePresence } from 'motion/react';
import { nex } from '@nex-ui/styled';
import { mergeRefs } from '@nex-ui/utils';
import { useId, useRef, useEffect } from 'react';
import { useAccordionGroup } from './AccordionContext.mjs';
import { useDefaultProps } from '../utils/useDefaultProps.mjs';
import { useStyles } from '../utils/useStyles.mjs';
import { useSlotProps } from '../utils/useSlotProps.mjs';
import { useNexUI } from '../provider/Context.mjs';
import { composeClasses } from '../utils/composeClasses.mjs';
import { accordionItemRecipe } from '../../theme/recipes/accordion.mjs';
import { getUtilityClass } from '../utils/getUtilityClass.mjs';
const contentMotionVariants = {
expanded: {
opacity: 1,
height: 'auto',
transition: {
opacity: {
delay: 0.1
},
ease: 'easeInOut',
duration: 0.2
}
},
unexpanded: {
opacity: 0,
height: 0,
overflow: 'hidden',
transition: {
ease: 'easeInOut',
duration: 0.2
}
}
};
const indicatorMotionVariants = {
expanded: {
rotate: 180,
transition: {
duration: 0.2
}
},
unexpanded: {
rotate: 0,
transition: {
duration: 0.2
}
}
};
const useSlotClasses = (ownerState)=>{
const { prefix } = useNexUI();
const accordionItemRoot = `${prefix}-accordion-item`;
const { hideIndicator, keepMounted, disabled, classes } = ownerState;
const slots = {
root: [
'root',
hideIndicator && 'hide-indicator',
keepMounted && 'keep-mounted',
disabled && 'disabled'
],
heading: [
'heading'
],
trigger: [
'trigger'
],
content: [
'content'
],
indicator: [
'indicator'
]
};
const composedClasses = composeClasses(slots, getUtilityClass(accordionItemRoot), classes);
return composedClasses;
};
const useSlotAriaProps = (ownerState)=>{
const { itemKey, disabled, expanded, slotProps } = ownerState;
const triggerProps = slotProps?.trigger || {};
const triggerId = useId();
const contentId = `panel-${itemKey}-content`;
let trigger = {
type: 'button',
disabled,
id: triggerId,
tabIndex: disabled ? -1 : 0,
'aria-expanded': expanded,
'aria-controls': contentId,
'aria-disabled': disabled || undefined
};
if (triggerProps.as && triggerProps.as !== 'button') {
trigger = {
...trigger,
role: 'button'
};
}
const content = {
role: 'region',
'aria-labelledby': triggerId,
id: contentId
};
return {
trigger,
content
};
};
const AccordionItem = (inProps)=>{
const defaultKey = useId();
const firstRender = useRef(true);
const triggerRef = useRef(null);
const props = useDefaultProps({
name: 'AccordionItem',
props: inProps
});
const { variant, toggleExpandedKey, expandedKeys, disabledKeys, disabled: defaultDisabled, indicator: defaultIndicator, motionProps: defaultMotionProps, keepMounted: defaultKeepMounted, hideIndicator: defaultHideIndicator, indicatorMotionProps: defaultIndicatorMotionProps } = useAccordionGroup();
const { ref, children, title, slotProps, as = 'div', indicatorMotionProps = defaultIndicatorMotionProps, motionProps = defaultMotionProps, hideIndicator = defaultHideIndicator, keepMounted = defaultKeepMounted, indicator = defaultIndicator, itemKey = defaultKey, disabled = disabledKeys.includes(itemKey) || defaultDisabled, ...remainingProps } = props;
const expanded = expandedKeys.includes(itemKey);
const [focusVisible] = useFocusVisible({
ref: triggerRef
});
const ownerState = {
...props,
variant,
itemKey,
expanded,
indicator,
keepMounted,
hideIndicator,
disabled,
motionProps
};
const styles = useStyles({
name: 'AccordionItem',
ownerState,
recipe: accordionItemRecipe
});
const onClick = useEvent(()=>{
toggleExpandedKey(itemKey);
});
const onKeyUp = useEvent((event)=>{
// Keyboard accessibility for non interactive elements
if (focusVisible && slotProps?.trigger?.as && slotProps?.trigger?.as !== 'button' && (event.code === 'Space' || event.code === 'Enter')) {
event.currentTarget.click();
}
});
const classes = useSlotClasses(ownerState);
const slotAriaProps = useSlotAriaProps(ownerState);
const animate = expanded ? 'expanded' : 'unexpanded';
const contentMotionProps = keepMounted ? {
animate,
initial: firstRender.current && animate,
variants: contentMotionVariants
} : {
variants: contentMotionVariants,
initial: firstRender.current && animate,
animate: 'expanded',
exit: 'unexpanded'
};
const rootProps = useSlotProps({
ownerState,
externalForwardedProps: remainingProps,
sx: styles.root,
classNames: classes.root,
additionalProps: {
ref,
as
}
});
const headingProps = useSlotProps({
ownerState,
externalSlotProps: slotProps?.heading,
sx: styles.heading,
classNames: classes.heading
});
const triggerProps = useSlotProps({
ownerState,
externalSlotProps: {
...slotProps?.trigger,
ref: mergeRefs(triggerRef, slotProps?.trigger?.ref)
},
sx: styles.trigger,
classNames: classes.trigger,
additionalProps: {
onClick,
onKeyUp,
...slotAriaProps.trigger
}
});
const contentProps = useSlotProps({
ownerState,
externalSlotProps: slotProps?.content,
sx: styles.content,
classNames: classes.content,
additionalProps: slotAriaProps.content
});
const indicatorProps = useSlotProps({
ownerState,
externalSlotProps: slotProps?.indicator,
sx: styles.indicator,
classNames: classes.indicator,
additionalProps: {
size: 'sm',
as: m.span,
animate,
variants: indicatorMotionVariants,
...indicatorMotionProps
}
});
useEffect(()=>{
if (firstRender.current) {
firstRender.current = false;
}
}, []);
return /*#__PURE__*/ jsx(LazyMotion, {
features: domAnimation,
children: /*#__PURE__*/ jsxs(nex.div, {
...rootProps,
children: [
/*#__PURE__*/ jsx(nex.h3, {
...headingProps,
children: /*#__PURE__*/ jsxs(nex.button, {
...triggerProps,
children: [
/*#__PURE__*/ jsx("span", {
children: title
}),
!hideIndicator && /*#__PURE__*/ jsx(nex.span, {
...indicatorProps,
children: indicator ?? /*#__PURE__*/ jsx(ChevronDownOutlined, {})
})
]
})
}),
/*#__PURE__*/ jsx(AnimatePresence, {
children: (keepMounted || expanded) && // @ts-ignore
/*#__PURE__*/ jsx(m.div, {
...contentMotionProps,
...motionProps,
children: /*#__PURE__*/ jsx(nex.div, {
...contentProps,
children: children
})
})
})
]
})
});
};
AccordionItem.displayName = 'AccordionItem';
export { AccordionItem };