+ TabViewer
This commit is contained in:
parent
9f282388f4
commit
793f051925
|
@ -75,7 +75,7 @@ const TabBar: React.ComponentType<TabBarProps> = React.memo(
|
|||
/**
|
||||
* Stylesheet for the active tab indicator
|
||||
*/
|
||||
const [activeIndicatorStyles, setAactiveIndicatorStyles] = useState<
|
||||
const [activeIndicatorStyles, setActiveIndicatorStyles] = useState<
|
||||
React.CSSProperties
|
||||
>({});
|
||||
|
||||
|
@ -177,15 +177,23 @@ const TabBar: React.ComponentType<TabBarProps> = React.memo(
|
|||
useEffect(() => {
|
||||
if (!activeId) return;
|
||||
const activeItem = items.find(getActiveState);
|
||||
tabEventEmitter.emit(`${id}-change`, activeItem);
|
||||
const setNewId = setActiveId;
|
||||
tabEventEmitter.emit(`${id}-change`, activeItem.id);
|
||||
|
||||
/**
|
||||
* Sends necessary tabbar data to new subscribers.
|
||||
*/
|
||||
const subHandler = () => tabEventEmitter.emit(`${id}-subPub`, activeItem);
|
||||
tabEventEmitter.on(`${id}-sub`, subHandler);
|
||||
|
||||
/**
|
||||
* Programatic change listener.
|
||||
* Used to change both tabbar and tabcontent state at once.
|
||||
*/
|
||||
tabEventEmitter.on(`${id}-update`, setNewId);
|
||||
return () => {
|
||||
tabEventEmitter.off(`${id}-sub`, subHandler);
|
||||
tabEventEmitter.off(`${id}-update`, setNewId);
|
||||
};
|
||||
}, [activeId, getActiveState, id, items]);
|
||||
|
||||
|
@ -231,7 +239,7 @@ const TabBar: React.ComponentType<TabBarProps> = React.memo(
|
|||
width;
|
||||
}
|
||||
|
||||
setAactiveIndicatorStyles({
|
||||
setActiveIndicatorStyles({
|
||||
transform: `scaleX(${width}) translateX(${posX}px)`,
|
||||
});
|
||||
}
|
||||
|
|
|
@ -0,0 +1,44 @@
|
|||
import { TabItem } from "../TabBar/TabBar.props";
|
||||
import { ManagedClasses } from "@microsoft/fast-jss-manager-react";
|
||||
|
||||
/**
|
||||
* Class name contract for the component
|
||||
*/
|
||||
export interface TabViewerClassNameContract {
|
||||
tabViewer: string;
|
||||
tabPanel: string;
|
||||
tabPanel__visible?: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* Object in which specific content is assigned to a tab
|
||||
*/
|
||||
export interface TabPanel {
|
||||
/**
|
||||
* The id of the tab the panel is referring
|
||||
*/
|
||||
id: TabItem["id"];
|
||||
/**
|
||||
* The contents of the panel
|
||||
*/
|
||||
children: React.ReactNode | null;
|
||||
/**
|
||||
* Is triggered if the panel becomes visible
|
||||
*/
|
||||
onLoad?: () => any;
|
||||
}
|
||||
|
||||
/**
|
||||
* Props for the component
|
||||
*/
|
||||
export interface TabViewerProps extends ManagedClasses<TabViewerClassNameContract> {
|
||||
/**
|
||||
* Unique identifier used to establish connection with tabbar
|
||||
*/
|
||||
id: string;
|
||||
|
||||
/**
|
||||
* The list of tab panels
|
||||
*/
|
||||
items: TabPanel[];
|
||||
}
|
|
@ -0,0 +1,85 @@
|
|||
import React, { useState, useEffect, useCallback } from "react";
|
||||
import { TabViewerProps, TabViewerClassNameContract, TabPanel } from "./TabViewer.props";
|
||||
import { DesignSystem } from "@microsoft/fast-components-styles-msft";
|
||||
import manageJss, { ComponentStyles } from "@microsoft/fast-jss-manager-react";
|
||||
import tabEventEmitter from "../TabEvents";
|
||||
import { TabItem } from "../TabBar/TabBar.props";
|
||||
import { classNames } from "@microsoft/fast-web-utilities";
|
||||
|
||||
const styles: ComponentStyles<TabViewerClassNameContract, DesignSystem> = {
|
||||
tabViewer: {},
|
||||
tabPanel: {
|
||||
display: "none",
|
||||
position: "absolute",
|
||||
width: "100%",
|
||||
height: "100%",
|
||||
},
|
||||
tabPanel__visible: {
|
||||
display: "block",
|
||||
},
|
||||
};
|
||||
|
||||
const TabViewer: React.ComponentType<TabViewerProps> = ({
|
||||
managedClasses,
|
||||
items,
|
||||
id,
|
||||
...props
|
||||
}) => {
|
||||
const [activeId, setActiveId] = useState<string>(null);
|
||||
|
||||
/**
|
||||
* Looks up whether the given tab panel is currently visible or not
|
||||
*/
|
||||
const getActiveState = useCallback(
|
||||
(tabItem: TabPanel, index: number): boolean => {
|
||||
if (activeId) return activeId === tabItem.id;
|
||||
return index === 0;
|
||||
},
|
||||
[activeId]
|
||||
);
|
||||
|
||||
/**
|
||||
* Listening to change events from <TabBar/>
|
||||
*/
|
||||
useEffect(() => {
|
||||
// We need to do this in order to cache the function
|
||||
const changeHandler = (id: TabItem["id"]): void => setActiveId(id);
|
||||
|
||||
// Handle initial data
|
||||
tabEventEmitter.once(`${id}-subPub`, changeHandler);
|
||||
// Subscribe to change handler
|
||||
tabEventEmitter.on(`${id}-change`, changeHandler);
|
||||
// Tell tabbar to broadcast initial data
|
||||
tabEventEmitter.emit(`${id}-sub`);
|
||||
|
||||
return () => {
|
||||
tabEventEmitter.off(`${id}-subPub`, changeHandler);
|
||||
tabEventEmitter.off(`${id}-change`, changeHandler);
|
||||
};
|
||||
}, [id]);
|
||||
|
||||
useEffect(() => {
|
||||
if (!activeId) return;
|
||||
|
||||
const func = items.find(getActiveState)["onLoad"];
|
||||
if (typeof func === "function") func();
|
||||
}, [activeId, getActiveState, items]);
|
||||
|
||||
return (
|
||||
<div className={managedClasses.tabViewer}>
|
||||
{items.map((tabItem, i) => (
|
||||
<div
|
||||
key={tabItem.id}
|
||||
className={classNames(managedClasses.tabPanel, [
|
||||
managedClasses.tabPanel__visible,
|
||||
getActiveState(tabItem, i),
|
||||
])}
|
||||
>
|
||||
{tabItem.children}
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default manageJss(styles)(TabViewer);
|
|
@ -17,6 +17,7 @@ import useMotionValueFactory from "./Hooks/useMotionValueFactory";
|
|||
import { useScaleFactor } from "./Hooks/useScaleFactor";
|
||||
import { ToastManager, toast } from "./Toasts/toast";
|
||||
import TabBar from "./Tabs/TabBar/TabBar";
|
||||
import TabViewer from "./Tabs/TabViewer/TabViewer";
|
||||
|
||||
export {
|
||||
Button,
|
||||
|
@ -39,4 +40,5 @@ export {
|
|||
ToastManager,
|
||||
toast,
|
||||
TabBar,
|
||||
TabViewer,
|
||||
};
|
||||
|
|
Loading…
Reference in New Issue