Skip to content

Commit

Permalink
feat: 使用hooks封装chrome-tabs
Browse files Browse the repository at this point in the history
  • Loading branch information
pansinm committed Jun 28, 2021
1 parent a73874f commit de2b45e
Show file tree
Hide file tree
Showing 6 changed files with 164 additions and 137 deletions.
32 changes: 23 additions & 9 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,15 +3,29 @@
## Usage

```js
import ChromeTabs from "@sinm/react-chrome-tabs";
import { useChromeTabs } from "@sinm/react-chrome-tabs";

function YourComponent() {
const [tabs, setTabs] = useState({ id: "id", title: "title" });
return <ChromeTabs onChange={(newTabs, reason) => {
if (reason.type === 'close') {
// ...
}
setTabs(newTabs);
}}>
function Example() {
const [tabs, setTabs] = useState([]);
const { ChromeTabs, addTab, updateTab, removeTab } = useChromeTabs({
onTabActivated: (tabId) => {
console.log('active:', tabId);
},
onTabReorder: (tabId, fromIndex, toIndex) => {},
onTabClosed: (tabId) => {
},
});
return (
<div>
<ChromeTabs />
<button
onClick={() =>
addTab({ id: `id-${Date.now()}`, title: `页签`, favicon: false })
}
>
添加
</button>
</div>
);
}
```
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@sinm/react-chrome-tabs",
"version": "1.0.2",
"version": "1.1.0",
"description": "Chrome-style tabs in HTML/CSS/JS",
"main": "dist/index.js",
"files": [
Expand Down
2 changes: 1 addition & 1 deletion src/chrome-tabs.ts
Original file line number Diff line number Diff line change
Expand Up @@ -246,8 +246,8 @@ class ChromeTabs {

setTabCloseEventListener(tabEl: HTMLElement) {
tabEl.querySelector(".chrome-tab-close")!.addEventListener("click", (_) => {
this.removeTab(tabEl);
this.emit("tabClose", { tabEl });
// this.removeTab(tabEl);
});
}

Expand Down
109 changes: 109 additions & 0 deletions src/hooks.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,109 @@
import React, {
CSSProperties,
forwardRef,
useCallback,
useEffect,
useMemo,
useRef,
} from "react";
import ChromeTabsClz, { TabProperties } from "./chrome-tabs";

type Listeners = {
onTabActivated: (tabId: string) => void;
onTabClosed: (tabId: string) => void;
onTabReorder: (tabId: string, fromIdex: number, toIndex: number) => void;
};

const ChromeTabsWrapper = forwardRef<HTMLDivElement, any>((props, ref) => {
return (
<div
ref={ref}
className="chrome-tabs"
style={{ "--tab-content-margin": "9px" } as CSSProperties}
>
<div className="chrome-tabs-content"></div>
</div>
);
});

export function useChromeTabs(listeners: Listeners) {
const ref = useRef<HTMLDivElement>(null);
const chromeTabsRef = useRef<ChromeTabsClz | null>(null);
const listenersRef = useRef<Listeners>(listeners);

useCallback(() => {
listenersRef.current = { ...listeners };
}, [listeners.onTabActivated, listeners.onTabClosed, listeners.onTabReorder]);

useEffect(() => {
const chromeTabs = new ChromeTabsClz();
chromeTabsRef.current = chromeTabs;
chromeTabs.init(ref.current as HTMLDivElement);
chromeTabs.el.addEventListener("activeTabChange", ({ detail }: any) => {
const tabEle = detail.tabEl as HTMLDivElement;
const tabId = tabEle.getAttribute(
"data-tab-id"
) as string;
listenersRef.current.onTabActivated(tabId);
});

chromeTabs.el.addEventListener("tabClose", ({ detail }: any) => {
const tabEle = detail.tabEl as HTMLDivElement;
const tabId = tabEle.getAttribute(
"data-tab-id"
) as string;
listenersRef.current.onTabClosed(tabId);
});

chromeTabs.el.addEventListener("tabReorder", ({ detail }: any) => {
const { tabEl: tabEle, originIndex, destinationIndex } = detail;
const tabId = tabEle.getAttribute(
"data-tab-id"
) as string;
listenersRef.current.onTabReorder(tabId, originIndex, destinationIndex);
});
}, []);

const addTab = useCallback((tab: TabProperties) => {
chromeTabsRef.current?.addTab(tab);
}, []);

const removeTab = useCallback((tabId: string) => {
const ele = ref.current?.querySelector(
`[data-tab-id="${tabId}"]`
) as HTMLDivElement;
if (ele) {
chromeTabsRef.current?.removeTab(ele);
}
}, []);

const activeTab = useCallback((tabId: string) => {
const ele = ref.current?.querySelector(
`[data-tab-id="${tabId}"]`
) as HTMLDivElement;
if (ele) {
chromeTabsRef.current?.setCurrentTab(ele);
}
}, []);

const updateTab = useCallback((tabId: string, tab: TabProperties) => {
const ele = ref.current?.querySelector(
`[data-tab-id="${tabId}"]`
) as HTMLDivElement;
if (ele) {
chromeTabsRef.current?.updateTab(ele, { ...tab, id: tabId });
}
}, []);

const ChromeTabs = useCallback(() => {
return <ChromeTabsWrapper ref={ref} />;
}, []);

return {
ChromeTabs,
addTab,
updateTab,
removeTab,
activeTab
};
}
123 changes: 1 addition & 122 deletions src/index.tsx
Original file line number Diff line number Diff line change
@@ -1,122 +1 @@
import React, {
CSSProperties,
memo,
useCallback,
useEffect,
useMemo,
useRef,
} from "react";
import ChromeTabsJS, { TabProperties } from "./chrome-tabs";

export interface ChromeTabsProps {
tabs: TabProperties[];
style?: CSSProperties;
onTabsChange(
tabs: TabProperties[],
detail: {
reason: "close" | "reorder" | "active";
tab: TabProperties;
originIndex?: number;
destIndex?: number;
}
): void;
}

export const ChromeTabs = memo<ChromeTabsProps>((props) => {
const ref = useRef<HTMLDivElement>(null);
const tabsRef = useRef<ChromeTabsJS>();
const onChangeRef = useRef(props.onTabsChange);

const map = useMemo(() => {
return new Map<HTMLElement, TabProperties>();
}, []);

useEffect(() => {
onChangeRef.current = props.onTabsChange;
}, [props.onTabsChange]);

const updateTabs = useCallback((tabs: TabProperties[]) => {
const chromeTabs = tabsRef.current!;
const tabEls = chromeTabs.tabEls;

tabEls.forEach((ele, index) => {
const tab = tabs[index];
if (tab) {
chromeTabs.updateTab(ele, tab);
map.set(ele, tab);
if (tab.active) {
chromeTabs.setCurrentTab(ele);
}
} else {
chromeTabs.removeTab(ele);
map.delete(ele);
}
});

// console.log(tabs);
tabs.slice(tabEls.length).forEach((tab) => {
const ele = chromeTabs.addTab(tab, { background: true });
map.set(ele, tab);
if (tab.active) {
chromeTabs.setCurrentTab(ele);
}
});
}, []);

useEffect(() => {
const chromeTabs = new ChromeTabsJS();
chromeTabs.init(ref.current!);
tabsRef.current = chromeTabs;
chromeTabs.el.addEventListener("activeTabChange", ({ detail }: any) => {
const tabEl = detail.tabEl;
const tabEls = [...chromeTabs.tabEls];
const newTabs = tabEls
.map((el) => {
return { ...map.get(el)!, active: el === tabEl }!;
})
.filter((tab) => !!tab);
onChangeRef.current(newTabs, { reason: "active", tab: map.get(tabEl)! });
});
// chromeTabs.el.addEventListener("tabClick", ({ detail }: any) => {
// const tabEl = detail.tabEl;
// const tabEls = [...chromeTabs.tabEls];
// const newTabs = tabEls
// .map((el) => {
// return { ...map.get(el)!, active: el === tabEl }!;
// })
// .filter((tab) => !!tab);
// onChangeRef.current(newTabs, { reason: "active", tab: map.get(tabEl)! });
// });

chromeTabs.el.addEventListener("tabClose", ({ detail }: any) => {
const tabEl = detail.tabEl;
const tabEls = [...chromeTabs.tabEls];
const index = tabEls.indexOf(tabEl);
tabEls.splice(index, 1);
const newTabs = tabEls.map((el) => map.get(el)!).filter((tab) => !!tab);
onChangeRef.current(newTabs, { reason: "close", tab: map.get(tabEl)! });
// props.onTabsChange([...props.tabs])
});

chromeTabs.el.addEventListener("tabReorder", ({ detail }: any) => {
const tabEl = detail.tabEl;
const tabEls = [...chromeTabs.tabEls];
const newTabs = tabEls.filter((tab) => !!tab);
onChangeRef.current(newTabs, { reason: "reorder", tab: map.get(tabEl)! });
});
}, []);

useEffect(() => {
updateTabs(props.tabs);
}, [props.tabs]);

return (
<div
ref={ref}
className="chrome-tabs"
style={{ "--tab-content-margin": "9px" } as CSSProperties}
>
<div className="chrome-tabs-content"></div>
</div>
);
});
export { useChromeTabs } from './hooks'
33 changes: 29 additions & 4 deletions stories/ChromeTabs.stories.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,15 +2,40 @@ import React, { useState } from "react";
import { Story, Meta } from "@storybook/react";
import "../css/chrome-tabs.css";

import { ChromeTabs, ChromeTabsProps } from "../src/index";
import { useChromeTabs } from "../src/index";

function Example() {
const [tabs, setTabs] = useState([]);
const { ChromeTabs, addTab, updateTab, removeTab } = useChromeTabs({
onTabActivated: (tabId) => {
console.log('active:', tabId);
},
onTabReorder: (tabId, fromIndex, toIndex) => {},
onTabClosed: (tabId) => {

},
});
return (
<div>
<ChromeTabs />
<button
onClick={() =>
addTab({ id: `id-${Date.now()}`, title: `页签`, favicon: false })
}
>
添加
</button>
</div>
);
}

export default {
title: "Example/ChromeTabs",
component: ChromeTabs,
component: Example,
} as Meta;

const Template: Story<ChromeTabsProps> = (args: ChromeTabsProps) => {
return <ChromeTabs {...args} />;
const Template: Story<ChromeTabsProps> = (args) => {
return <Example {...args} />;
};

export const ChromeTab = Template.bind({});
Expand Down

0 comments on commit de2b45e

Please sign in to comment.