mirror of
https://github.com/karl0ss/homepage.git
synced 2025-05-09 08:29:04 +01:00
Feature: tabbed layouts (#1981)
This commit is contained in:
parent
768107cde8
commit
2d8160512f
24
src/components/tab.jsx
Normal file
24
src/components/tab.jsx
Normal file
@ -0,0 +1,24 @@
|
||||
import { useContext } from "react";
|
||||
import classNames from "classnames";
|
||||
|
||||
import { TabContext } from "utils/contexts/tab";
|
||||
|
||||
export default function Tab({ tab }) {
|
||||
const { activeTab, setActiveTab } = useContext(TabContext);
|
||||
|
||||
return (
|
||||
<li key={tab} role="presentation"
|
||||
className={classNames(
|
||||
"text-theme-700 dark:text-theme-200 relative h-8 w-full rounded-md flex m-1",
|
||||
)}>
|
||||
<button id={`${tab}-tab`} type="button" role="tab"
|
||||
aria-controls={`#${tab}`} aria-selected={activeTab === tab ? "true" : "false"}
|
||||
className={classNames(
|
||||
"h-full w-full rounded-md",
|
||||
activeTab === tab ? "bg-theme-300/20 dark:bg-white/10" : "hover:bg-theme-100/20 dark:hover:bg-white/5",
|
||||
)}
|
||||
onClick={() => { setActiveTab(tab); window.location.hash = `#${tab}`; }}
|
||||
>{tab}</button>
|
||||
</li>
|
||||
);
|
||||
}
|
@ -11,6 +11,7 @@ import nextI18nextConfig from "../../next-i18next.config";
|
||||
import { ColorProvider } from "utils/contexts/color";
|
||||
import { ThemeProvider } from "utils/contexts/theme";
|
||||
import { SettingsProvider } from "utils/contexts/settings";
|
||||
import { TabProvider } from "utils/contexts/tab";
|
||||
|
||||
function MyApp({ Component, pageProps }) {
|
||||
return (
|
||||
@ -26,7 +27,9 @@ function MyApp({ Component, pageProps }) {
|
||||
<ColorProvider>
|
||||
<ThemeProvider>
|
||||
<SettingsProvider>
|
||||
<Component {...pageProps} />
|
||||
<TabProvider>
|
||||
<Component {...pageProps} />
|
||||
</TabProvider>
|
||||
</SettingsProvider>
|
||||
</ThemeProvider>
|
||||
</ColorProvider>
|
||||
|
@ -7,7 +7,9 @@ import { useTranslation } from "next-i18next";
|
||||
import { useEffect, useContext, useState, useMemo } from "react";
|
||||
import { BiError } from "react-icons/bi";
|
||||
import { serverSideTranslations } from "next-i18next/serverSideTranslations";
|
||||
import { useRouter } from "next/router";
|
||||
|
||||
import Tab from "components/tab";
|
||||
import FileContent from "components/filecontent";
|
||||
import ServicesGroup from "components/services/group";
|
||||
import BookmarksGroup from "components/bookmarks/group";
|
||||
@ -19,6 +21,7 @@ import { getSettings } from "utils/config/config";
|
||||
import { ColorContext } from "utils/contexts/color";
|
||||
import { ThemeContext } from "utils/contexts/theme";
|
||||
import { SettingsContext } from "utils/contexts/settings";
|
||||
import { TabContext } from "utils/contexts/tab";
|
||||
import { bookmarksResponse, servicesResponse, widgetsResponse } from "utils/config/api-response";
|
||||
import ErrorBoundary from "components/errorboundry";
|
||||
import themes from "utils/styles/themes";
|
||||
@ -169,6 +172,8 @@ function Home({ initialSettings }) {
|
||||
const { theme, setTheme } = useContext(ThemeContext);
|
||||
const { color, setColor } = useContext(ColorContext);
|
||||
const { settings, setSettings } = useContext(SettingsContext);
|
||||
const { activeTab, setActiveTab } = useContext(TabContext);
|
||||
const { asPath } = useRouter();
|
||||
|
||||
useEffect(() => {
|
||||
setSettings(initialSettings);
|
||||
@ -231,18 +236,51 @@ function Home({ initialSettings }) {
|
||||
}
|
||||
})
|
||||
|
||||
const servicesAndBookmarksGroups = useMemo(() => {
|
||||
const layoutGroups = settings.layout ? Object.keys(settings.layout).map(
|
||||
(groupName) => services?.find(g => g.name === groupName) ?? bookmarks?.find(b => b.name === groupName)
|
||||
).filter(g => g) : [];
|
||||
const tabs = useMemo( () => [
|
||||
...new Set(
|
||||
Object.keys(settings.layout ?? {}).map(
|
||||
(groupName) => settings.layout[groupName]?.tab
|
||||
).filter(group => group)
|
||||
)
|
||||
], [settings.layout]);
|
||||
|
||||
const serviceGroups = services?.filter(group => settings.layout?.[group.name] === undefined);
|
||||
const bookmarkGroups = bookmarks.filter(group => settings.layout?.[group.name] === undefined);
|
||||
if (!activeTab) {
|
||||
const initialTab = decodeURI(asPath.substring(asPath.indexOf("#") + 1));
|
||||
if (initialTab !== '/') {
|
||||
setActiveTab(initialTab)
|
||||
} else {
|
||||
setActiveTab(tabs['0'] ?? false)
|
||||
}
|
||||
}
|
||||
|
||||
const servicesAndBookmarksGroups = useMemo(() => {
|
||||
const tabGroupFilter = g => g && [activeTab, undefined].includes(settings.layout?.[g.name]?.tab);
|
||||
const undefinedGroupFilter = g => settings.layout?.[g.name] === undefined;
|
||||
|
||||
const layoutGroups = Object.keys(settings.layout ?? {}).map(
|
||||
(groupName) => services?.find(g => g.name === groupName) ?? bookmarks?.find(b => b.name === groupName)
|
||||
).filter(tabGroupFilter);
|
||||
|
||||
if (!settings.layout || !layoutGroups) {
|
||||
// wait for settings to populate, otherwise all the widgets will be requested initially even if we are on a single tab
|
||||
return <div />;
|
||||
}
|
||||
|
||||
const serviceGroups = services?.filter(tabGroupFilter).filter(undefinedGroupFilter);
|
||||
const bookmarkGroups = bookmarks.filter(tabGroupFilter).filter(undefinedGroupFilter);
|
||||
|
||||
return <>
|
||||
{tabs.length > 0 && <div key="tabs" id="tabs" className="p-4 sm:p-8 sm:pt-4 sm:pb-0">
|
||||
<ul className={classNames(
|
||||
"sm:flex rounded-md bg-theme-100/20 dark:bg-white/5",
|
||||
settings.cardBlur !== undefined && `backdrop-blur${settings.cardBlur.length ? '-': "" }${settings.cardBlur}`
|
||||
)} id="myTab" data-tabs-toggle="#myTabContent" role="tablist" >
|
||||
{tabs.map(tab => <Tab key={tab} tab={tab} />)}
|
||||
</ul>
|
||||
</div>}
|
||||
{layoutGroups.length > 0 && <div key="layoutGroups" id="layout-groups" className="flex flex-wrap p-4 sm:p-8 sm:pt-4 items-start pb-2">
|
||||
{layoutGroups.map((group) => (
|
||||
group.services ?
|
||||
group.services ?
|
||||
(<ServicesGroup
|
||||
key={group.name}
|
||||
group={group.name}
|
||||
@ -284,11 +322,14 @@ function Home({ initialSettings }) {
|
||||
</div>}
|
||||
</>
|
||||
}, [
|
||||
tabs,
|
||||
activeTab,
|
||||
services,
|
||||
bookmarks,
|
||||
settings.layout,
|
||||
settings.fiveColumns,
|
||||
settings.disableCollapse
|
||||
settings.disableCollapse,
|
||||
settings.cardBlur
|
||||
]);
|
||||
|
||||
return (
|
||||
|
15
src/utils/contexts/tab.jsx
Normal file
15
src/utils/contexts/tab.jsx
Normal file
@ -0,0 +1,15 @@
|
||||
import { createContext, useState, useMemo } from "react";
|
||||
|
||||
export const TabContext = createContext();
|
||||
|
||||
export function TabProvider({ initialTab, children }) {
|
||||
const [activeTab, setActiveTab] = useState(false);
|
||||
|
||||
if (initialTab) {
|
||||
setActiveTab(initialTab);
|
||||
}
|
||||
|
||||
const value = useMemo(() => ({ activeTab, setActiveTab }), [activeTab]);
|
||||
|
||||
return <TabContext.Provider value={value}>{children}</TabContext.Provider>;
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user