|
1 | 1 | 'use client';
|
2 | 2 | import * as React from 'react';
|
3 |
| -import { BaseUIComponentProps } from '../../utils/types'; |
| 3 | +import { BaseUIComponentProps, Orientation } from '../../utils/types'; |
4 | 4 | import { useEventCallback } from '../../utils/useEventCallback';
|
5 | 5 | import { useModernLayoutEffect } from '../../utils/useModernLayoutEffect';
|
6 | 6 | import { useRenderElement } from '../../utils/useRenderElement';
|
7 | 7 | import { CompositeRoot } from '../../composite/root/CompositeRoot';
|
8 | 8 | import { tabsStyleHookMapping } from '../root/styleHooks';
|
9 | 9 | import { useTabsRootContext } from '../root/TabsRootContext';
|
10 |
| -import { type TabsRoot, type TabsOrientation, type TabActivationDirection } from '../root/TabsRoot'; |
| 10 | +import { type TabsRoot, type TabActivationDirection } from '../root/TabsRoot'; |
11 | 11 | import { type TabMetadata } from '../tab/TabsTab';
|
12 | 12 | import { TabsListContext } from './TabsListContext';
|
13 | 13 |
|
14 | 14 | const EMPTY_ARRAY: number[] = [];
|
15 | 15 |
|
16 |
| -function getInset(tab: HTMLElement, tabsList: HTMLElement) { |
17 |
| - const { left: tabLeft, top: tabTop } = tab.getBoundingClientRect(); |
18 |
| - const { left: listLeft, top: listTop } = tabsList.getBoundingClientRect(); |
19 |
| - |
20 |
| - const left = tabLeft - listLeft; |
21 |
| - const top = tabTop - listTop; |
22 |
| - |
23 |
| - return { left, top }; |
24 |
| -} |
25 |
| - |
26 |
| -function useActivationDirectionDetector( |
27 |
| - // the old value |
28 |
| - selectedTabValue: any, |
29 |
| - orientation: TabsOrientation, |
30 |
| - tabsListRef: React.RefObject<HTMLElement | null>, |
31 |
| - getTabElement: (selectedValue: any) => HTMLElement | null, |
32 |
| -): (newValue: any) => TabActivationDirection { |
33 |
| - const previousTabEdge = React.useRef<number | null>(null); |
34 |
| - |
35 |
| - useModernLayoutEffect(() => { |
36 |
| - // Whenever orientation changes, reset the state. |
37 |
| - if (selectedTabValue == null || tabsListRef.current == null) { |
38 |
| - previousTabEdge.current = null; |
39 |
| - return; |
40 |
| - } |
41 |
| - |
42 |
| - const activeTab = getTabElement(selectedTabValue); |
43 |
| - if (activeTab == null) { |
44 |
| - previousTabEdge.current = null; |
45 |
| - return; |
46 |
| - } |
47 |
| - |
48 |
| - const { left, top } = getInset(activeTab, tabsListRef.current); |
49 |
| - previousTabEdge.current = orientation === 'horizontal' ? left : top; |
50 |
| - }, [orientation, getTabElement, tabsListRef, selectedTabValue]); |
51 |
| - |
52 |
| - return React.useCallback( |
53 |
| - (newValue: any) => { |
54 |
| - if (newValue === selectedTabValue) { |
55 |
| - return 'none'; |
56 |
| - } |
57 |
| - |
58 |
| - if (newValue == null) { |
59 |
| - previousTabEdge.current = null; |
60 |
| - return 'none'; |
61 |
| - } |
62 |
| - |
63 |
| - if (newValue != null && tabsListRef.current != null) { |
64 |
| - const selectedTabElement = getTabElement(newValue); |
65 |
| - |
66 |
| - if (selectedTabElement != null) { |
67 |
| - const { left, top } = getInset(selectedTabElement, tabsListRef.current); |
68 |
| - |
69 |
| - if (previousTabEdge.current == null) { |
70 |
| - previousTabEdge.current = orientation === 'horizontal' ? left : top; |
71 |
| - return 'none'; |
72 |
| - } |
73 |
| - |
74 |
| - if (orientation === 'horizontal') { |
75 |
| - if (left < previousTabEdge.current) { |
76 |
| - previousTabEdge.current = left; |
77 |
| - return 'left'; |
78 |
| - } |
79 |
| - if (left > previousTabEdge.current) { |
80 |
| - previousTabEdge.current = left; |
81 |
| - return 'right'; |
82 |
| - } |
83 |
| - } else if (top < previousTabEdge.current) { |
84 |
| - previousTabEdge.current = top; |
85 |
| - return 'up'; |
86 |
| - } else if (top > previousTabEdge.current) { |
87 |
| - previousTabEdge.current = top; |
88 |
| - return 'down'; |
89 |
| - } |
90 |
| - } |
91 |
| - } |
92 |
| - |
93 |
| - return 'none'; |
94 |
| - }, |
95 |
| - [getTabElement, orientation, previousTabEdge, tabsListRef, selectedTabValue], |
96 |
| - ); |
97 |
| -} |
98 |
| - |
99 | 16 | /**
|
100 | 17 | * Groups the individual tab buttons.
|
101 | 18 | * Renders a `<div>` element.
|
@@ -199,10 +116,93 @@ export const TabsList = React.forwardRef(function TabsList(
|
199 | 116 | );
|
200 | 117 | });
|
201 | 118 |
|
| 119 | +function getInset(tab: HTMLElement, tabsList: HTMLElement) { |
| 120 | + const { left: tabLeft, top: tabTop } = tab.getBoundingClientRect(); |
| 121 | + const { left: listLeft, top: listTop } = tabsList.getBoundingClientRect(); |
| 122 | + |
| 123 | + const left = tabLeft - listLeft; |
| 124 | + const top = tabTop - listTop; |
| 125 | + |
| 126 | + return { left, top }; |
| 127 | +} |
| 128 | + |
| 129 | +function useActivationDirectionDetector( |
| 130 | + // the old value |
| 131 | + selectedTabValue: any, |
| 132 | + orientation: Orientation, |
| 133 | + tabsListRef: React.RefObject<HTMLElement | null>, |
| 134 | + getTabElement: (selectedValue: any) => HTMLElement | null, |
| 135 | +): (newValue: any) => TabActivationDirection { |
| 136 | + const previousTabEdge = React.useRef<number | null>(null); |
| 137 | + |
| 138 | + useModernLayoutEffect(() => { |
| 139 | + // Whenever orientation changes, reset the state. |
| 140 | + if (selectedTabValue == null || tabsListRef.current == null) { |
| 141 | + previousTabEdge.current = null; |
| 142 | + return; |
| 143 | + } |
| 144 | + |
| 145 | + const activeTab = getTabElement(selectedTabValue); |
| 146 | + if (activeTab == null) { |
| 147 | + previousTabEdge.current = null; |
| 148 | + return; |
| 149 | + } |
| 150 | + |
| 151 | + const { left, top } = getInset(activeTab, tabsListRef.current); |
| 152 | + previousTabEdge.current = orientation === 'horizontal' ? left : top; |
| 153 | + }, [orientation, getTabElement, tabsListRef, selectedTabValue]); |
| 154 | + |
| 155 | + return React.useCallback( |
| 156 | + (newValue: any) => { |
| 157 | + if (newValue === selectedTabValue) { |
| 158 | + return 'none'; |
| 159 | + } |
| 160 | + |
| 161 | + if (newValue == null) { |
| 162 | + previousTabEdge.current = null; |
| 163 | + return 'none'; |
| 164 | + } |
| 165 | + |
| 166 | + if (newValue != null && tabsListRef.current != null) { |
| 167 | + const selectedTabElement = getTabElement(newValue); |
| 168 | + |
| 169 | + if (selectedTabElement != null) { |
| 170 | + const { left, top } = getInset(selectedTabElement, tabsListRef.current); |
| 171 | + |
| 172 | + if (previousTabEdge.current == null) { |
| 173 | + previousTabEdge.current = orientation === 'horizontal' ? left : top; |
| 174 | + return 'none'; |
| 175 | + } |
| 176 | + |
| 177 | + if (orientation === 'horizontal') { |
| 178 | + if (left < previousTabEdge.current) { |
| 179 | + previousTabEdge.current = left; |
| 180 | + return 'left'; |
| 181 | + } |
| 182 | + if (left > previousTabEdge.current) { |
| 183 | + previousTabEdge.current = left; |
| 184 | + return 'right'; |
| 185 | + } |
| 186 | + } else if (top < previousTabEdge.current) { |
| 187 | + previousTabEdge.current = top; |
| 188 | + return 'up'; |
| 189 | + } else if (top > previousTabEdge.current) { |
| 190 | + previousTabEdge.current = top; |
| 191 | + return 'down'; |
| 192 | + } |
| 193 | + } |
| 194 | + } |
| 195 | + |
| 196 | + return 'none'; |
| 197 | + }, |
| 198 | + [getTabElement, orientation, previousTabEdge, tabsListRef, selectedTabValue], |
| 199 | + ); |
| 200 | +} |
| 201 | + |
202 | 202 | export namespace TabsList {
|
203 |
| - export type State = TabsRoot.State; |
| 203 | + export interface State extends TabsRoot.State {} |
204 | 204 |
|
205 |
| - export interface Props extends BaseUIComponentProps<'div', TabsRoot.State> { |
| 205 | + export interface Props extends BaseUIComponentProps<'div', State> { |
206 | 206 | /**
|
207 | 207 | * Whether to automatically change the active tab on arrow key focus.
|
208 | 208 | * Otherwise, tabs will be activated using Enter or Spacebar key press.
|
|
0 commit comments