UNPKG

@nex-ui/react

Version:

🎉 A beautiful, modern, and reliable React component library.

239 lines (236 loc) • 8.2 kB
"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 };