Advertisement
fabiobiondi

QwikUI Tab fix

May 6th, 2023
1,017
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
  1. import {
  2.   $,
  3.   component$,
  4.   createContextId,
  5.   PropFunction,
  6.   QRL,
  7.   Signal,
  8.   Slot,
  9.   useContext,
  10.   useContextProvider,
  11.   useTask$,
  12.   useSignal,
  13.   useVisibleTask$, useId, useStore,
  14. } from '@builder.io/qwik';
  15.  
  16. export type Behavior = 'automatic' | 'manual';
  17.  
  18. interface TabsContext {
  19.   selectedIndex: Signal<number>;
  20.   getNextTabIndex: QRL<() => number>;
  21.   getNextPanelIndex: QRL<() => number>;
  22.   tabsHash: string;
  23.   behavior: Behavior;
  24. }
  25.  
  26. export const tabsContext = createContextId<TabsContext>('tabList');
  27.  
  28. export interface TabsProps {
  29.   behavior?: Behavior;
  30.   class?: string;
  31. }
  32.  
  33. export const Tabs = component$((props: TabsProps) => {
  34.   const behavior = props.behavior ?? 'manual';
  35.   const lastTabIndex = useSignal(0);
  36.   const lastPanelIndex = useSignal(0);
  37.  
  38.   const tabsHash = `${Math.random() * 1000}`;
  39.  
  40.   const getNextTabIndex = $(() => {
  41.     console.log(lastTabIndex.value)
  42.     return lastTabIndex.value++;
  43.   });
  44.  
  45.   const getNextPanelIndex = $(() => {
  46.     return lastPanelIndex.value++;
  47.   });
  48.  
  49.   const selected = useSignal(0);
  50.  
  51.   const contextService: TabsContext = {
  52.     selectedIndex: selected,
  53.     getNextTabIndex,
  54.     getNextPanelIndex,
  55.     tabsHash,
  56.     behavior,
  57.   };
  58.  
  59.   useContextProvider(tabsContext, contextService);
  60.  
  61.   return (
  62.     <div {...props}>
  63.       <Slot />
  64.     </div>
  65.   );
  66. });
  67.  
  68. interface TabListProps {
  69.   labelledBy?: string;
  70.   behavior?: 'automatic' | 'manual';
  71.   class?: string;
  72. }
  73.  
  74. // List of tabs that can be clicked to show different content.
  75. export const TabList = component$((props: TabListProps) => {
  76.   const { labelledBy, ...rest } = props;
  77.   return (
  78.     <div role="tablist" aria-labelledby={labelledBy} {...rest}>
  79.       <Slot />
  80.     </div>
  81.   );
  82. });
  83.  
  84. interface TabProps {
  85.   onClick?: PropFunction<(clicked: number) => void>;
  86.   class?: string;
  87.   selectedClassName?: string;
  88. }
  89.  
  90. // Tab button inside of a tab list
  91. export const Tab = component$(
  92.   ({ selectedClassName, onClick, ...props }: TabProps) => {
  93.     const contextService = useContext(tabsContext);
  94.     const thisTabIndex = useSignal(0);
  95.     /*FIX*/
  96.     const initialized = useSignal(false)
  97.  
  98.     // FIX: from useTask to useVisibleTask
  99.     useVisibleTask$(async () => {
  100.       thisTabIndex.value = await contextService.getNextTabIndex();
  101.       /*FIX*/
  102.       initialized.value = true;
  103.     });
  104.  
  105.  
  106.     const isSelected = () =>
  107.       thisTabIndex.value === contextService.selectedIndex.value;
  108.  
  109.     const selectIfAutomatic = $(() => {
  110.       if (contextService.behavior === 'automatic') {
  111.         contextService.selectedIndex.value = thisTabIndex.value;
  112.       }
  113.     });
  114.  
  115.     return (
  116.       <button
  117.         id={`${contextService.tabsHash}-tab-${thisTabIndex.value}`}
  118.         type="button"
  119.         role="tab"
  120.         onFocus$={selectIfAutomatic}
  121.         onMouseEnter$={selectIfAutomatic}
  122.         aria-selected={isSelected()}
  123.         aria-controls={`tabpanel-${thisTabIndex.value}`}
  124.         class={`${(isSelected() && initialized.value)? `selected ${selectedClassName}` : ''}${
  125.           (props.class) ? ` ${props.class}` : ''
  126.         }`}
  127.         onClick$={$(() => {
  128.           contextService.selectedIndex.value = thisTabIndex.value;
  129.           if (onClick) {
  130.             onClick(thisTabIndex.value);
  131.           }
  132.         })}
  133.       >
  134.         {<Slot/>}
  135.       </button>
  136.     );
  137.   }
  138. );
  139.  
  140. interface TabPanelProps {
  141.   class?: string;
  142. }
  143.  
  144. // Tab Panel implementation
  145. export const TabPanel = component$(({ ...props }: TabPanelProps) => {
  146.   const { class: classNames, ...rest } = props;
  147.   const contextService = useContext(tabsContext);
  148.   const thisPanelIndex = useSignal(0);
  149.  
  150.   // FIX
  151.   const initialized = useSignal(false)
  152.  
  153.  
  154.   const isSelected = () =>
  155.     thisPanelIndex.value === contextService.selectedIndex.value;
  156.   useVisibleTask$(async () => {
  157.     thisPanelIndex.value = await contextService.getNextPanelIndex();
  158.     initialized.value = true;
  159.  
  160.   });
  161.   return (
  162.     <div
  163.       id={`${contextService.tabsHash}-tabpanel-${thisPanelIndex}`}
  164.       role="tabpanel"
  165.       tabIndex={0}
  166.       aria-labelledby={`tab-${thisPanelIndex}`}
  167.       class={`${isSelected() ? 'is-hidden' : ''}${
  168.         classNames ? ` ${classNames}` : ''
  169.       }`}
  170.       style={isSelected() ? 'display: block' : 'display: none'}
  171.       {...rest}
  172.     >
  173.       {/*FIX*/}
  174.       {initialized.value && <Slot/>}
  175.     </div>
  176.   );
  177. });
  178.  
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement