go.chromium.org/luci@v0.0.0-20240309015107-7cdc2e660f33/milo/ui/src/generic_libs/components/routed_tabs/reducer.tsx (about)

     1  // Copyright 2023 The LUCI Authors.
     2  //
     3  // Licensed under the Apache License, Version 2.0 (the "License");
     4  // you may not use this file except in compliance with the License.
     5  // You may obtain a copy of the License at
     6  //
     7  //      http://www.apache.org/licenses/LICENSE-2.0
     8  //
     9  // Unless required by applicable law or agreed to in writing, software
    10  // distributed under the License is distributed on an "AS IS" BASIS,
    11  // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    12  // See the License for the specific language governing permissions and
    13  // limitations under the License.
    14  
    15  export interface RoutedTabsState {
    16    readonly activeTab: {
    17      readonly id: string;
    18      readonly hookRef: unknown;
    19    } | null;
    20  }
    21  
    22  export type Action = {
    23    readonly type: 'activateTab' | 'deactivateTab';
    24    /**
    25     * The ID of the tab. Used to inform `<RoutedTabs />` which tab should be
    26     * highlighted.
    27     */
    28    readonly id: string;
    29    /**
    30     * A reference that uniquely identifies a `useTabId` call in a component.
    31     *
    32     * For a given `<RoutedTabs />`, only one `useTabId` call can exist in its
    33     * descendants at any time.
    34     */
    35    readonly hookRef: unknown;
    36  };
    37  
    38  export function reducer(
    39    state: RoutedTabsState,
    40    action: Action,
    41  ): RoutedTabsState {
    42    switch (action.type) {
    43      case 'activateTab': {
    44        if (
    45          state.activeTab !== null &&
    46          state.activeTab.hookRef !== action.hookRef
    47        ) {
    48          throw new Error(
    49            `cannot activate a tab when there's already an active tab; ` +
    50              `active: ${state.activeTab.id}}; ` +
    51              `activating: ${action.id}`,
    52          );
    53        }
    54        return { activeTab: { id: action.id, hookRef: action.hookRef } };
    55      }
    56      case 'deactivateTab': {
    57        if (state.activeTab?.hookRef !== action.hookRef) {
    58          throw new Error(
    59            `cannot deactivate an inactive tab; ` +
    60              `active: ${state.activeTab?.id ?? null}}; ` +
    61              `deactivating: ${action.id}`,
    62          );
    63        }
    64        return { activeTab: null };
    65      }
    66    }
    67  }