1 | import * as React from "react";
|
2 | import { createComponent } from "reakit-system/createComponent";
|
3 | import { createHook } from "reakit-system/createHook";
|
4 | import { useForkRef } from "reakit-utils/useForkRef";
|
5 | import {
|
6 | DisclosureContentOptions,
|
7 | DisclosureContentHTMLProps,
|
8 | useDisclosureContent,
|
9 | } from "../Disclosure/DisclosureContent";
|
10 | import {
|
11 | unstable_useId,
|
12 | unstable_IdOptions,
|
13 | unstable_IdHTMLProps,
|
14 | } from "../Id/Id";
|
15 | import { TabStateReturn } from "./TabState";
|
16 | import { TAB_PANEL_KEYS } from "./__keys";
|
17 |
|
18 | export type TabPanelOptions = DisclosureContentOptions &
|
19 | unstable_IdOptions &
|
20 | Pick<
|
21 | TabStateReturn,
|
22 | "selectedId" | "registerPanel" | "unregisterPanel" | "panels" | "items"
|
23 | > & {
|
24 | |
25 |
|
26 |
|
27 | tabId?: string;
|
28 | };
|
29 |
|
30 | export type TabPanelHTMLProps = DisclosureContentHTMLProps &
|
31 | unstable_IdHTMLProps;
|
32 |
|
33 | export type TabPanelProps = TabPanelOptions & TabPanelHTMLProps;
|
34 |
|
35 | function getTabsWithoutPanel(
|
36 | tabs: TabPanelOptions["items"],
|
37 | panels: TabPanelOptions["panels"]
|
38 | ) {
|
39 | const panelsTabIds = panels.map((panel) => panel.groupId).filter(Boolean);
|
40 | return tabs.filter(
|
41 | (item) => panelsTabIds.indexOf(item.id || undefined) === -1
|
42 | );
|
43 | }
|
44 |
|
45 | function getPanelIndex(
|
46 | panels: TabPanelOptions["panels"],
|
47 | panel: typeof panels[number]
|
48 | ) {
|
49 | const panelsWithoutTabId = panels.filter((p) => !p.groupId);
|
50 | return panelsWithoutTabId.indexOf(panel);
|
51 | }
|
52 |
|
53 |
|
54 |
|
55 |
|
56 |
|
57 |
|
58 |
|
59 |
|
60 |
|
61 |
|
62 |
|
63 |
|
64 |
|
65 |
|
66 |
|
67 |
|
68 |
|
69 | function getTabId(options: TabPanelOptions) {
|
70 | const panel = options.panels?.find((p) => p.id === options.id);
|
71 | const tabId = options.tabId || panel?.groupId;
|
72 | if (tabId || !panel || !options.panels || !options.items) {
|
73 | return tabId;
|
74 | }
|
75 | const panelIndex = getPanelIndex(options.panels, panel);
|
76 | const tabsWithoutPanel = getTabsWithoutPanel(options.items, options.panels);
|
77 | return tabsWithoutPanel[panelIndex]?.id || undefined;
|
78 | }
|
79 |
|
80 | export const useTabPanel = createHook<TabPanelOptions, TabPanelHTMLProps>({
|
81 | name: "TabPanel",
|
82 | compose: [unstable_useId, useDisclosureContent],
|
83 | keys: TAB_PANEL_KEYS,
|
84 |
|
85 | useProps(options, { ref: htmlRef, ...htmlProps }) {
|
86 | const ref = React.useRef<HTMLElement>(null);
|
87 | const tabId = getTabId(options);
|
88 | const { id, registerPanel, unregisterPanel } = options;
|
89 |
|
90 | React.useEffect(() => {
|
91 | if (!id) return undefined;
|
92 | registerPanel?.({ id, ref, groupId: tabId });
|
93 | return () => {
|
94 | unregisterPanel?.(id);
|
95 | };
|
96 | }, [tabId, id, registerPanel, unregisterPanel]);
|
97 |
|
98 | return {
|
99 | ref: useForkRef(ref, htmlRef),
|
100 | role: "tabpanel",
|
101 | tabIndex: 0,
|
102 | "aria-labelledby": tabId,
|
103 | ...htmlProps,
|
104 | };
|
105 | },
|
106 |
|
107 | useComposeOptions(options) {
|
108 | const tabId = getTabId(options);
|
109 | return {
|
110 | visible: tabId ? options.selectedId === tabId : false,
|
111 | ...options,
|
112 | };
|
113 | },
|
114 | });
|
115 |
|
116 | export const TabPanel = createComponent({
|
117 | as: "div",
|
118 | useHook: useTabPanel,
|
119 | });
|