All files / src/components DrawerSystem.tsx

93.15% Statements 68/73
45.45% Branches 5/11
80% Functions 4/5
93.15% Lines 68/73

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 1241x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x   1x   1x                             1x 18x 18x   18x 18x 18x 18x 18x 18x 18x 18x   18x     18x   1x                       1x 1x 7x 7x   7x 1x 1x 1x   1x 1x 1x     1x   7x 7x       7x 7x 7x 7x 7x 7x   7x 7x 7x 7x 7x 7x 7x   7x 7x 7x       7x 7x 7x 7x       7x 7x     7x 7x         7x   1x  
import { DRAWER_INDEX } from '@/constants/common';
import { useAppContext } from '@/context';
import { i18n } from '@/helpers/i18n';
import HistoryIcon from '@mui/icons-material/History';
import SearchIcon from '@mui/icons-material/Search';
import SettingsIcon from '@mui/icons-material/Settings';
import { Drawer, Tab, Tabs } from '@mui/material';
import { useState, FC, SyntheticEvent } from 'react';
import DrawerSearchPanel from './DrawerSearchPanel';
import styles from './DrawerSystem.module.scss';
import HistoryPanel from './HistoryPanel';
import SettingsPanel from './SettingsPanel';
 
// TabPanelProps is declared globally in types/props.d.ts
 
/**
 * Renders its children only when the active drawer tab `value` matches this
 * panel's `index`, hiding the panel otherwise. Used to switch between the
 * Search, History, and Settings drawer tabs.
 * @param props - The {@link TabPanelProps}: `children`, the active `value`, and
 *   this panel's `index`.
 * @returns A `tabpanel` element that shows `children` when active, otherwise hidden.
 * @example
 * ```tsx
 * <TabPanel value={activeTab} index={DRAWER_INDEX.SEARCH}>
 *   <DrawerSearchPanel />
 * </TabPanel>
 * // Renders DrawerSearchPanel only when activeTab === DRAWER_INDEX.SEARCH.
 * ```
 * @source
 */
function TabPanel(props: TabPanelProps) {
  const { children, value, index, ...other } = props;
 
  return (
    <div
      role="tabpanel"
      hidden={value !== index}
      id={`drawer-tabpanel-${index}`}
      aria-labelledby={`drawer-tab-${index}`}
      className={styles['drawer-system__tabpanel']}
      {...other}
    >
      {value === index && children}
    </div>
  );
}
 
/**
 * The right-hand slide-out drawer hosting the Search, History, and Settings
 * tabs. Driven by `appContext.drawerTab`: opens when a tab is selected, closes
 * on backdrop click, and renders the matching panel via the local `TabPanel`.
 * @returns The drawer element; an empty (closed) drawer when no tab is active.
 * @example
 * ```tsx
 * // Mounted once near the app root; visibility is controlled via app context.
 * <DrawerSystem />
 * // appContext.setDrawerTab(DRAWER_INDEX.SETTINGS) opens it to the Settings tab.
 * ```
 * @source
 */
const DrawerSystem: FC = () => {
  const appContext = useAppContext();
  const [expandedAccordion, setExpandedAccordion] = useState<string | false>('search-availability');
 
  const handleTabChange = (_event: SyntheticEvent, newValue: DRAWER_INDEX) => {
    appContext.setDrawerTab(newValue);
    // Reset accordion when switching tabs
    if (newValue === DRAWER_INDEX.SEARCH) {
      setExpandedAccordion('search-availability');
    } else if (newValue === DRAWER_INDEX.SETTINGS) {
      setExpandedAccordion('settings-general');
    } else {
      setExpandedAccordion(false);
    }
  };
 
  const handleAccordionChange =
    (panel: string) => (_event: SyntheticEvent, isExpanded: boolean) => {
      setExpandedAccordion(isExpanded ? panel : false);
    };
 
  return (
    <Drawer
      anchor="right"
      open={appContext.drawerTab !== DRAWER_INDEX.CLOSED}
      onClose={() => appContext.setDrawerTab(DRAWER_INDEX.CLOSED)}
      variant="temporary"
    >
      <div className={styles['drawer-container']}>
        {appContext.drawerTab !== DRAWER_INDEX.CLOSED && (
          <Tabs
            value={appContext.drawerTab ?? DRAWER_INDEX.CLOSED}
            onChange={handleTabChange}
            variant="fullWidth"
            className={styles['drawer-tabs']}
          >
            <Tab icon={<SearchIcon />} label={i18n('drawer_tab_search')} iconPosition="start" />
            <Tab icon={<HistoryIcon />} label={i18n('drawer_tab_history')} iconPosition="start" />
            <Tab icon={<SettingsIcon />} label={i18n('drawer_tab_settings')} iconPosition="start" />
          </Tabs>
        )}
 
        <TabPanel value={appContext.drawerTab ?? DRAWER_INDEX.CLOSED} index={DRAWER_INDEX.SEARCH}>
          <DrawerSearchPanel
            expandedAccordion={expandedAccordion}
            onAccordionChange={handleAccordionChange}
          />
        </TabPanel>
 
        <TabPanel value={appContext.drawerTab ?? DRAWER_INDEX.CLOSED} index={DRAWER_INDEX.HISTORY}>
          <HistoryPanel />
        </TabPanel>
 
        <TabPanel value={appContext.drawerTab ?? DRAWER_INDEX.CLOSED} index={DRAWER_INDEX.SETTINGS}>
          <SettingsPanel />
        </TabPanel>
      </div>
    </Drawer>
  );
};
 
export default DrawerSystem;