@nex-ui/react
Version:
🎉 A beautiful, modern, and reliable React component library.
239 lines (236 loc) • 8.2 kB
JavaScript
"use client";
import { jsx, jsxs } from 'react/jsx-runtime';
import * as m from 'motion/react-m';
import { useEvent } from '@nex-ui/hooks';
import { ChevronDownOutlined } from '@nex-ui/icons';
import { LazyMotion, AnimatePresence } from 'motion/react';
import { useId, useRef, useMemo } from 'react';
import { useAccordionGroup } from './AccordionContext.mjs';
import { useDefaultProps } from '../utils/useDefaultProps.mjs';
import { useStyles } from '../utils/useStyles.mjs';
import { useSlotClasses } from '../utils/useSlotClasses.mjs';
import { useSlot } from '../utils/useSlot.mjs';
import { ButtonBase } from '../buttonBase/ButtonBase.mjs';
import { motionFeatures } from '../utils/motionFeatures/index.mjs';
import { accordionItemRecipe } from '../../theme/recipes/accordion.mjs';
const contentMotionVariants = {
expanded: {
opacity: 1,
height: 'auto',
transition: {
opacity: {
delay: 0.1
},
ease: 'easeInOut',
duration: 0.2
}
},
collapsed: {
opacity: 0,
height: 0,
transition: {
ease: 'easeInOut',
duration: 0.2
}
}
};
const indicatorMotionVariants = {
expanded: {
rotate: 180,
transition: {
duration: 0.2
}
},
collapsed: {
rotate: 0,
transition: {
duration: 0.2
}
}
};
const slots = [
'root',
'heading',
'trigger',
'content',
'indicator'
];
const useSlotAriaProps = (ownerState)=>{
const { itemKey, expanded, slotProps } = ownerState;
const id = useId();
return useMemo(()=>{
const triggerProps = slotProps?.trigger || {};
const contentProps = slotProps?.content || {};
const indicatorProps = slotProps?.indicator || {};
const triggerId = triggerProps.id ?? id;
const contentId = contentProps.id ?? `panel-${itemKey}-content`;
const trigger = {
id: triggerId,
'aria-expanded': triggerProps['aria-expanded'] ?? expanded,
'aria-controls': triggerProps['aria-controls'] ?? contentId
};
const content = {
id: contentId,
role: contentProps.role ?? 'region',
'aria-labelledby': contentProps['aria-labelledby'] ?? triggerId
};
const indicator = {
'aria-hidden': indicatorProps['aria-hidden'] ?? true
};
return {
trigger,
content,
indicator
};
}, [
expanded,
id,
itemKey,
slotProps?.content,
slotProps?.indicator,
slotProps?.trigger
]);
};
const AccordionItem = (inProps)=>{
const defaultKey = useId();
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 { children, title, slotProps, classNames, 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 ownerState = {
...props,
variant,
itemKey,
expanded,
indicator,
keepMounted,
hideIndicator,
disabled,
motionProps
};
const styles = useStyles({
name: 'AccordionItem',
ownerState,
recipe: accordionItemRecipe
});
const slotClasses = useSlotClasses({
name: 'AccordionItem',
slots,
classNames
});
const slotAriaProps = useSlotAriaProps(ownerState);
const animate = expanded ? 'expanded' : 'collapsed';
// Skip initial animation when first rendering and the item is expanded
const motionInitialRef = useRef(animate);
if (motionInitialRef.current === 'expanded' && !expanded) {
// Restore open animation for subsequent renders
motionInitialRef.current = 'collapsed';
}
const contentMotionProps = keepMounted ? {
animate,
initial: motionInitialRef.current,
variants: contentMotionVariants,
style: {
overflow: 'hidden'
}
} : {
variants: contentMotionVariants,
initial: motionInitialRef.current,
animate: 'expanded',
exit: 'collapsed',
style: {
overflow: 'hidden'
}
};
const [AccordionItemRoot, getAccordionItemRootProps] = useSlot({
elementType: 'div',
externalForwardedProps: remainingProps,
style: styles.root,
classNames: slotClasses.root,
dataAttrs: {
keepMounted,
hideIndicator,
disabled,
state: animate
}
});
const [AccordionItemHeading, getAccordionItemHeadingProps] = useSlot({
elementType: 'h3',
externalSlotProps: slotProps?.heading,
style: styles.heading,
classNames: slotClasses.heading
});
const handleClick = useEvent(()=>{
toggleExpandedKey(itemKey);
});
const [AccordionItemTrigger, getAccordionItemTriggerProps] = useSlot({
elementType: ButtonBase,
externalSlotProps: slotProps?.trigger,
style: styles.trigger,
classNames: slotClasses.trigger,
a11y: slotAriaProps.trigger,
shouldForwardComponent: false,
additionalProps: {
disabled,
onClick: handleClick
}
});
const [AccordionItemContent, getAccordionItemContentProps] = useSlot({
elementType: 'div',
externalSlotProps: slotProps?.content,
style: styles.content,
classNames: slotClasses.content,
a11y: slotAriaProps.content
});
const [AccordionItemIndicator, getAccordionItemIndicatorProps] = useSlot({
elementType: m.span,
externalSlotProps: slotProps?.indicator,
style: styles.indicator,
classNames: slotClasses.indicator,
a11y: slotAriaProps.indicator,
additionalProps: {
animate,
variants: indicatorMotionVariants,
initial: animate,
...indicatorMotionProps
}
});
return /*#__PURE__*/ jsx(LazyMotion, {
features: motionFeatures,
children: /*#__PURE__*/ jsxs(AccordionItemRoot, {
...getAccordionItemRootProps(),
children: [
/*#__PURE__*/ jsx(AccordionItemHeading, {
...getAccordionItemHeadingProps(),
children: /*#__PURE__*/ jsxs(AccordionItemTrigger, {
...getAccordionItemTriggerProps(),
children: [
/*#__PURE__*/ jsx("span", {
children: title
}),
!hideIndicator && /*#__PURE__*/ jsx(AccordionItemIndicator, {
...getAccordionItemIndicatorProps(),
children: indicator ?? /*#__PURE__*/ jsx(ChevronDownOutlined, {})
})
]
})
}),
/*#__PURE__*/ jsx(AnimatePresence, {
children: (keepMounted || expanded) && /*#__PURE__*/ jsx(m.div, {
...contentMotionProps,
...motionProps,
children: /*#__PURE__*/ jsx(AccordionItemContent, {
...getAccordionItemContentProps(),
children: children
})
})
})
]
})
});
};
AccordionItem.displayName = 'AccordionItem';
export { AccordionItem };