All files / molecules/Accordion/mobile/Components/Context AccordionContext.tsx

81.81% Statements 27/33
50% Branches 10/20
90% Functions 9/10
82.75% Lines 24/29

Press n or j to go to the next uncovered block, b, p or k for the previous block.

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125                                                    7x   7x 7x   7x     7x                       3x                           3x                       53x 53x 53x     53x 37x   5x 5x     5x         53x 44x         131x 53x   53x                                     3x 771x 771x     771x    
import React, { createContext, useContext, useReducer, ReactNode, useEffect } from 'react';
import type {
  AccordionHeaderPosition,
  AccordionIconProps,
  AccordionNativeVariant,
} from '../../../Accordion.types';
 
type Action = { type: 'TOGGLE'; id: string } | { type: 'RESET' };
 
type State = {
  openItems: string[];
};
 
type AccordionContextType = {
  variant: AccordionNativeVariant;
  multiple: boolean;
  headerPosition: AccordionHeaderPosition;
  openItems: string[];
  isFlush: boolean;
  isDisabled: boolean;
  isItemOpen: (id: string) => boolean;
  toggleItem: (id: string) => void;
  icon: AccordionIconProps;
};
 
function reducer(state: State, action: Action & { multiple?: boolean }) {
  switch (action.type) {
    case 'TOGGLE': {
      const { id, multiple = false } = action;
      const isOpen = state.openItems.includes(id);
 
      Iif (isOpen) {
        return { openItems: state.openItems.filter((x) => x !== id) };
      } else {
        return {
          openItems: multiple ? [...state.openItems, id] : [id],
        };
      }
    }
    case 'RESET':
      return { openItems: [] };
    default:
      return state;
  }
}
 
const AccordionContext = createContext<AccordionContextType | undefined>(undefined);
 
type AccordionProviderProps = {
  children: ReactNode;
  iconState: AccordionIconProps;
  multiple?: boolean;
  variant?: AccordionNativeVariant;
  headerPosition?: AccordionHeaderPosition;
  flush?: boolean;
  disabled?: boolean;
  defaultOpenIndex?: number;
  onChange?: (openItems: string[]) => void;
};
 
export const AccordionProvider = ({
  children,
  iconState,
  disabled,
  multiple = false,
  variant = 'primary',
  headerPosition = 'top',
  flush = false,
  defaultOpenIndex,
  onChange,
}: AccordionProviderProps) => {
  // Start with empty state
  const [state, dispatch] = useReducer(reducer, { openItems: [] });
  const isFlush = flush;
  const isDisabled = disabled || false;
 
  // Set default open item after children are rendered
  useEffect(() => {
    if (defaultOpenIndex !== undefined) {
      // Small delay to ensure children are rendered first
      const timer = setTimeout(() => {
        dispatch({ type: 'TOGGLE', id: `item-${defaultOpenIndex}`, multiple });
      }, 0);
 
      return () => clearTimeout(timer);
    }
  }, [defaultOpenIndex, multiple]);
 
  // Call onChange when state changes
  useEffect(() => {
    Iif (onChange) {
      onChange(state.openItems);
    }
  }, [state.openItems, onChange]);
 
  const isItemOpen = (id: string) => state.openItems.includes(id);
  const toggleItem = (id: string) => dispatch({ type: 'TOGGLE', id, multiple });
 
  return (
    <AccordionContext.Provider
      value={{
        variant,
        multiple,
        headerPosition,
        isFlush,
        isDisabled,
        openItems: state.openItems,
        isItemOpen,
        toggleItem,
        icon: iconState,
      }}
    >
      {children}
    </AccordionContext.Provider>
  );
};
 
export const useAccordion = () => {
  const ctx = useContext(AccordionContext);
  Iif (!ctx) {
    throw new Error('useAccordion must be used within an AccordionProvider');
  }
  return ctx;
};