Feature: tabbed layouts (#1981)

pull/1989/head
Denis Papec 1 year ago committed by GitHub
parent 768107cde8
commit 2d8160512f
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23

@ -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 { ColorProvider } from "utils/contexts/color";
import { ThemeProvider } from "utils/contexts/theme"; import { ThemeProvider } from "utils/contexts/theme";
import { SettingsProvider } from "utils/contexts/settings"; import { SettingsProvider } from "utils/contexts/settings";
import { TabProvider } from "utils/contexts/tab";
function MyApp({ Component, pageProps }) { function MyApp({ Component, pageProps }) {
return ( return (
@ -26,7 +27,9 @@ function MyApp({ Component, pageProps }) {
<ColorProvider> <ColorProvider>
<ThemeProvider> <ThemeProvider>
<SettingsProvider> <SettingsProvider>
<TabProvider>
<Component {...pageProps} /> <Component {...pageProps} />
</TabProvider>
</SettingsProvider> </SettingsProvider>
</ThemeProvider> </ThemeProvider>
</ColorProvider> </ColorProvider>

@ -7,7 +7,9 @@ import { useTranslation } from "next-i18next";
import { useEffect, useContext, useState, useMemo } from "react"; import { useEffect, useContext, useState, useMemo } from "react";
import { BiError } from "react-icons/bi"; import { BiError } from "react-icons/bi";
import { serverSideTranslations } from "next-i18next/serverSideTranslations"; import { serverSideTranslations } from "next-i18next/serverSideTranslations";
import { useRouter } from "next/router";
import Tab from "components/tab";
import FileContent from "components/filecontent"; import FileContent from "components/filecontent";
import ServicesGroup from "components/services/group"; import ServicesGroup from "components/services/group";
import BookmarksGroup from "components/bookmarks/group"; import BookmarksGroup from "components/bookmarks/group";
@ -19,6 +21,7 @@ import { getSettings } from "utils/config/config";
import { ColorContext } from "utils/contexts/color"; import { ColorContext } from "utils/contexts/color";
import { ThemeContext } from "utils/contexts/theme"; import { ThemeContext } from "utils/contexts/theme";
import { SettingsContext } from "utils/contexts/settings"; import { SettingsContext } from "utils/contexts/settings";
import { TabContext } from "utils/contexts/tab";
import { bookmarksResponse, servicesResponse, widgetsResponse } from "utils/config/api-response"; import { bookmarksResponse, servicesResponse, widgetsResponse } from "utils/config/api-response";
import ErrorBoundary from "components/errorboundry"; import ErrorBoundary from "components/errorboundry";
import themes from "utils/styles/themes"; import themes from "utils/styles/themes";
@ -169,6 +172,8 @@ function Home({ initialSettings }) {
const { theme, setTheme } = useContext(ThemeContext); const { theme, setTheme } = useContext(ThemeContext);
const { color, setColor } = useContext(ColorContext); const { color, setColor } = useContext(ColorContext);
const { settings, setSettings } = useContext(SettingsContext); const { settings, setSettings } = useContext(SettingsContext);
const { activeTab, setActiveTab } = useContext(TabContext);
const { asPath } = useRouter();
useEffect(() => { useEffect(() => {
setSettings(initialSettings); setSettings(initialSettings);
@ -231,15 +236,48 @@ function Home({ initialSettings }) {
} }
}) })
const tabs = useMemo( () => [
...new Set(
Object.keys(settings.layout ?? {}).map(
(groupName) => settings.layout[groupName]?.tab
).filter(group => group)
)
], [settings.layout]);
if (!activeTab) {
const initialTab = decodeURI(asPath.substring(asPath.indexOf("#") + 1));
if (initialTab !== '/') {
setActiveTab(initialTab)
} else {
setActiveTab(tabs['0'] ?? false)
}
}
const servicesAndBookmarksGroups = useMemo(() => { const servicesAndBookmarksGroups = useMemo(() => {
const layoutGroups = settings.layout ? Object.keys(settings.layout).map( 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) (groupName) => services?.find(g => g.name === groupName) ?? bookmarks?.find(b => b.name === groupName)
).filter(g => g) : []; ).filter(tabGroupFilter);
const serviceGroups = services?.filter(group => settings.layout?.[group.name] === undefined); if (!settings.layout || !layoutGroups) {
const bookmarkGroups = bookmarks.filter(group => settings.layout?.[group.name] === undefined); // 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 <> 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.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) => ( {layoutGroups.map((group) => (
group.services ? group.services ?
@ -284,11 +322,14 @@ function Home({ initialSettings }) {
</div>} </div>}
</> </>
}, [ }, [
tabs,
activeTab,
services, services,
bookmarks, bookmarks,
settings.layout, settings.layout,
settings.fiveColumns, settings.fiveColumns,
settings.disableCollapse settings.disableCollapse,
settings.cardBlur
]); ]);
return ( return (

@ -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…
Cancel
Save