UNPKG

@nex-ui/react

Version:

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

257 lines (254 loc) • 8.32 kB
"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 };