mirror of
https://github.com/zkldi/Tachi.git
synced 2026-10-10 16:28:17 +03:00
feat: replace MUI tabs with custom implementation (#1030)
* feat: custom navbar implementation * remove mui/material * add key navigation and improve visuals also allow for handling of cases that likely won't happen anyway --------- Co-authored-by: saintnoodle <hi@noodle.moe>
This commit is contained in:
co-authored by
saintnoodle
parent
0bb76fb338
commit
1dec6d34c6
@@ -27,13 +27,10 @@
|
||||
},
|
||||
"dependencies": {
|
||||
"@babel/core": "7.1.0",
|
||||
"@emotion/react": "^11.10.5",
|
||||
"@emotion/styled": "^11.10.5",
|
||||
"@fortawesome/fontawesome-free": "5.13.0",
|
||||
"@fullcalendar/core": "^6.0.0",
|
||||
"@fullcalendar/daygrid": "^6.0.0",
|
||||
"@fullcalendar/react": "^6.0.0",
|
||||
"@mui/material": "^5.11.1",
|
||||
"@nivo/bar": "0.79.1",
|
||||
"@nivo/colors": "0.79.1",
|
||||
"@nivo/core": "0.79.0",
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
import { MaterialThemeProvider } from "components/layout/misc/MaterialThemeProvider";
|
||||
import { LoadingScreen } from "components/layout/screens/LoadingScreen";
|
||||
import { AllLUGPTStatsContextProvider } from "context/AllLUGPTStatsContext";
|
||||
import { BannedContextProvider } from "context/BannedContext";
|
||||
@@ -29,12 +28,10 @@ export default function App({ basename }: { basename: string }) {
|
||||
<UserSettingsContextProvider>
|
||||
<AllLUGPTStatsContextProvider>
|
||||
<BrowserRouter basename={basename}>
|
||||
<MaterialThemeProvider>
|
||||
<Toaster />
|
||||
<SubheaderContextProvider>
|
||||
<Routes />
|
||||
</SubheaderContextProvider>
|
||||
</MaterialThemeProvider>
|
||||
<Toaster />
|
||||
<SubheaderContextProvider>
|
||||
<Routes />
|
||||
</SubheaderContextProvider>
|
||||
</BrowserRouter>
|
||||
</AllLUGPTStatsContextProvider>
|
||||
</UserSettingsContextProvider>
|
||||
|
||||
@@ -1,26 +1,21 @@
|
||||
import Navbar from "components/nav/Navbar";
|
||||
import NavItem from "components/nav/NavItem";
|
||||
import React from "react";
|
||||
import Navbar from "components/nav/Navbar";
|
||||
|
||||
export function DashboardHeader() {
|
||||
const navItems = [
|
||||
<NavItem key="activity" to="/">
|
||||
<Navbar.Item key="activity" to="/">
|
||||
Activity
|
||||
</NavItem>,
|
||||
<NavItem key="activity" to="/calendar">
|
||||
</Navbar.Item>,
|
||||
<Navbar.Item key="calendar" to="/calendar">
|
||||
Calendar
|
||||
</NavItem>,
|
||||
<NavItem key="profiles" to="/profiles">
|
||||
</Navbar.Item>,
|
||||
<Navbar.Item key="profiles" to="/profiles">
|
||||
Your Profiles
|
||||
</NavItem>,
|
||||
<NavItem key="profiles" to="/global-activity">
|
||||
</Navbar.Item>,
|
||||
<Navbar.Item key="global-activity" to="/global-activity">
|
||||
Global Activity
|
||||
</NavItem>,
|
||||
</Navbar.Item>,
|
||||
];
|
||||
|
||||
return (
|
||||
<div className="align-items-center mb-0">
|
||||
<Navbar>{navItems}</Navbar>
|
||||
</div>
|
||||
);
|
||||
return <Navbar>{navItems}</Navbar>;
|
||||
}
|
||||
|
||||
@@ -1,41 +1,36 @@
|
||||
import Navbar from "components/nav/Navbar";
|
||||
import NavItem from "components/nav/NavItem";
|
||||
import { UserSettingsContext } from "context/UserSettingsContext";
|
||||
import React, { useContext } from "react";
|
||||
import { UserSettingsContext } from "context/UserSettingsContext";
|
||||
import Navbar from "components/nav/Navbar";
|
||||
|
||||
export function GPTBottomNav({ baseUrl }: { baseUrl: string }) {
|
||||
const { settings } = useContext(UserSettingsContext);
|
||||
|
||||
const navItems = [
|
||||
<NavItem key="activity" to={`${baseUrl}/`}>
|
||||
<Navbar.Item key="activity" to={`${baseUrl}/`}>
|
||||
Activity
|
||||
</NavItem>,
|
||||
<NavItem key="songs" to={`${baseUrl}/songs`}>
|
||||
</Navbar.Item>,
|
||||
<Navbar.Item key="songs" to={`${baseUrl}/songs`}>
|
||||
Songs
|
||||
</NavItem>,
|
||||
<NavItem key="leaderboards" to={`${baseUrl}/leaderboards`}>
|
||||
</Navbar.Item>,
|
||||
<Navbar.Item key="leaderboards" to={`${baseUrl}/leaderboards`}>
|
||||
Leaderboards
|
||||
</NavItem>,
|
||||
<NavItem
|
||||
</Navbar.Item>,
|
||||
<Navbar.Item
|
||||
key="quests"
|
||||
to={`${baseUrl}/quests`}
|
||||
otherMatchingPaths={[`${baseUrl}/questline`]}
|
||||
>
|
||||
Quests
|
||||
</NavItem>,
|
||||
</Navbar.Item>,
|
||||
];
|
||||
|
||||
if (settings?.preferences.developerMode) {
|
||||
navItems.push(
|
||||
<NavItem key="dev-info" to={`${baseUrl}/dev-info`}>
|
||||
<Navbar.Item key="dev-info" to={`${baseUrl}/dev-info`}>
|
||||
Developer Info
|
||||
</NavItem>
|
||||
</Navbar.Item>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="align-items-center mb-0">
|
||||
<Navbar>{navItems}</Navbar>
|
||||
</div>
|
||||
);
|
||||
return <Navbar>{navItems}</Navbar>;
|
||||
}
|
||||
|
||||
@@ -20,7 +20,7 @@ export default function UserHeaderContainer({
|
||||
<div className="card-body">
|
||||
<div className="row align-items-center">{children}</div>
|
||||
</div>
|
||||
<div className="card-footer pb-0 pt-0">{footer}</div>
|
||||
<div className="card-footer p-0">{footer}</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className="col-12">
|
||||
|
||||
@@ -1,30 +0,0 @@
|
||||
import { ThemeProvider, createTheme } from "@mui/material/styles";
|
||||
|
||||
import { ColourConfig } from "lib/config";
|
||||
import React from "react";
|
||||
import { JustChildren } from "types/react";
|
||||
|
||||
const theme = createTheme({
|
||||
typography: {
|
||||
fontFamily: "Poppins",
|
||||
},
|
||||
palette: {
|
||||
secondary: {
|
||||
main: ColourConfig.primary,
|
||||
},
|
||||
primary: {
|
||||
main: ColourConfig.primary,
|
||||
},
|
||||
},
|
||||
components: {
|
||||
MuiButtonBase: {
|
||||
defaultProps: {
|
||||
disableRipple: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
export function MaterialThemeProvider({ children }: JustChildren) {
|
||||
return <ThemeProvider theme={theme}>{children}</ThemeProvider>;
|
||||
}
|
||||
@@ -1,5 +1,5 @@
|
||||
import { CircularProgress } from "@mui/material";
|
||||
import React from "react";
|
||||
import Loading from "components/util/Loading";
|
||||
import SplashImage from "../misc/SplashImage";
|
||||
|
||||
export function SplashScreen({ broke }: { broke: string }) {
|
||||
@@ -9,7 +9,7 @@ export function SplashScreen({ broke }: { broke: string }) {
|
||||
className="bg-body position-fixed inset-0 d-flex flex-column justify-content-center align-items-center"
|
||||
>
|
||||
<SplashImage />
|
||||
{!broke && <CircularProgress />}
|
||||
{!broke && <Loading />}
|
||||
{broke && <p className="mt-4">{broke}</p>}
|
||||
</div>
|
||||
);
|
||||
|
||||
@@ -1,23 +0,0 @@
|
||||
import { Tab } from "@mui/material";
|
||||
import React from "react";
|
||||
import { Link } from "react-router-dom";
|
||||
|
||||
export default function NavItem({
|
||||
to,
|
||||
children,
|
||||
}: {
|
||||
to: string;
|
||||
otherMatchingPaths?: Array<string>;
|
||||
children: string;
|
||||
}) {
|
||||
return (
|
||||
// @ts-expect-error Faulty types from MUI. The to={to} here is necessary
|
||||
// and works perfectly fine, but the MUI types disagree that it exists.
|
||||
<Tab
|
||||
label={<span className="mx-4">{children}</span>}
|
||||
LinkComponent={Link}
|
||||
to={to}
|
||||
style={{ opacity: 1 }}
|
||||
/>
|
||||
);
|
||||
}
|
||||
@@ -1,52 +1,353 @@
|
||||
import Tabs from "@mui/material/Tabs";
|
||||
import React, { useMemo } from "react";
|
||||
import { useLocation } from "react-router-dom";
|
||||
import Icon from "components/util/Icon";
|
||||
import { debounce } from "lodash";
|
||||
import React, {
|
||||
cloneElement,
|
||||
KeyboardEventHandler,
|
||||
ReactElement,
|
||||
useEffect,
|
||||
useMemo,
|
||||
useRef,
|
||||
useState,
|
||||
} from "react";
|
||||
import { useLocation, Link, LinkProps } from "react-router-dom";
|
||||
|
||||
export default function Navbar({ children }: { children: JSX.Element[] }) {
|
||||
const links = children
|
||||
.filter((e) => e.props.to)
|
||||
.map((e) => {
|
||||
const basePath =
|
||||
e.props.to[e.props.to.length - 1] === "/"
|
||||
? e.props.to.substring(0, e.props.to.length - 1)
|
||||
: e.props.to;
|
||||
export interface NavbarItemProps extends LinkProps<HTMLAnchorElement> {
|
||||
to: string;
|
||||
children: string;
|
||||
/**
|
||||
* An array of other pathnames that should trigger an active item
|
||||
*/
|
||||
otherMatchingPaths?: Array<string>;
|
||||
}
|
||||
|
||||
if (e.props.otherMatchingPaths) {
|
||||
return [basePath, ...e.props.otherMatchingPaths];
|
||||
}
|
||||
return [basePath];
|
||||
});
|
||||
export type NavbarItem = ReactElement<NavbarItemProps>;
|
||||
|
||||
export interface NavbarProps {
|
||||
children: ReactElement<NavbarItemProps> | ReactElement<NavbarItemProps>[];
|
||||
}
|
||||
|
||||
export default function Navbar({ children }: NavbarProps) {
|
||||
const location = useLocation();
|
||||
|
||||
const value = useMemo(() => {
|
||||
const loc = location.pathname.split(/([#?]|\/$)/u)[0];
|
||||
const navRef = useRef<HTMLDivElement>(null);
|
||||
const itemsListRef = useRef<HTMLDivElement>(null);
|
||||
|
||||
let lidx = 0;
|
||||
const [mounted, setMounted] = useState(false);
|
||||
const [indicatorStyle, setIndicatorStyle] = useState({
|
||||
left: 0,
|
||||
width: 0,
|
||||
});
|
||||
const [showScrollLeft, setShowScrollLeft] = useState(false);
|
||||
const [showScrollRight, setShowScrollRight] = useState(false);
|
||||
|
||||
const links = useMemo(
|
||||
() =>
|
||||
Array.isArray(children)
|
||||
? children.map((el) => {
|
||||
const to = el.props.to;
|
||||
if (!to) {
|
||||
return [];
|
||||
}
|
||||
const basePath =
|
||||
to[to.length - 1] === "/" ? to.substring(0, to.length - 1) : to;
|
||||
|
||||
if (el.props.otherMatchingPaths) {
|
||||
return [basePath, ...el.props.otherMatchingPaths];
|
||||
}
|
||||
return [basePath];
|
||||
})
|
||||
: children.props.to
|
||||
? [[children.props.to]]
|
||||
: [[]],
|
||||
[children]
|
||||
);
|
||||
|
||||
const activeLocIndex = useMemo(() => {
|
||||
const loc = location.pathname.split(/([#?]|\/$)/u)[0];
|
||||
let locIndex = 0;
|
||||
for (let i = 0; i < links.length; i++) {
|
||||
const matchingPaths = links[i];
|
||||
|
||||
if (matchingPaths.some((path) => loc.startsWith(path))) {
|
||||
lidx = i;
|
||||
locIndex = i;
|
||||
// break; THIS IS DELIBERATE TO AVOID / matching everything
|
||||
}
|
||||
}
|
||||
return locIndex;
|
||||
}, [links, location]);
|
||||
|
||||
return lidx;
|
||||
}, [location]);
|
||||
const items = React.Children.map(children, (child, index) => {
|
||||
if (!React.isValidElement(child)) {
|
||||
return null;
|
||||
}
|
||||
if (process.env.NODE_ENV === "development") {
|
||||
if (child.type === React.Fragment) {
|
||||
console.error("The navbar doesn't accept React fragments; Pass an array instead");
|
||||
}
|
||||
}
|
||||
return cloneElement(child, {
|
||||
key: child.key || index,
|
||||
"aria-selected": index === activeLocIndex,
|
||||
role: "tab",
|
||||
draggable: false,
|
||||
className: "tachi-navbar-item",
|
||||
});
|
||||
});
|
||||
|
||||
const scrollButtons = (
|
||||
<>
|
||||
{(showScrollLeft || showScrollRight) && (
|
||||
<>
|
||||
<button
|
||||
aria-label="Scroll Navbar Left"
|
||||
aria-hidden
|
||||
hidden={!showScrollLeft}
|
||||
tabIndex={-1}
|
||||
className="tachi-navbar-button-left"
|
||||
onClick={() => handleScrollButton()}
|
||||
>
|
||||
<Icon type="chevron-left" />
|
||||
</button>
|
||||
<button
|
||||
aria-label="Scroll Navbar Right"
|
||||
aria-hidden
|
||||
hidden={!showScrollRight}
|
||||
tabIndex={-1}
|
||||
className="tachi-navbar-button-right"
|
||||
onClick={() => handleScrollButton(true)}
|
||||
>
|
||||
<Icon type="chevron-right" />
|
||||
</button>
|
||||
</>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
|
||||
const nav = navRef.current;
|
||||
const itemsList = itemsListRef.current;
|
||||
const activeElement = itemsList?.children[activeLocIndex];
|
||||
|
||||
const updateIndicator = () => {
|
||||
if (nav && activeElement) {
|
||||
const navRect = nav.getBoundingClientRect();
|
||||
const activeElementRect = activeElement.getBoundingClientRect();
|
||||
|
||||
setIndicatorStyle({
|
||||
left: activeElementRect.left - navRect.left + nav.scrollLeft,
|
||||
width: activeElementRect.width,
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
const scrollActiveElementIntoView = debounce(
|
||||
() => {
|
||||
if (nav && itemsList && activeElement) {
|
||||
if (itemsList.clientWidth > nav.clientWidth) {
|
||||
const childRect = activeElement.getBoundingClientRect();
|
||||
const navRect = nav.getBoundingClientRect();
|
||||
|
||||
const left = childRect.left - navRect.left + nav.scrollLeft;
|
||||
const right = left + childRect.width;
|
||||
|
||||
if (left <= nav.scrollLeft || right >= nav.scrollLeft + nav.clientWidth) {
|
||||
// ScrollIntoView is seemingly broken in Chromium so we will do it ourselves
|
||||
requestAnimationFrame(() => {
|
||||
if (left < nav.scrollLeft) {
|
||||
nav.scrollTo({ left });
|
||||
} else if (right > nav.scrollLeft + nav.clientWidth) {
|
||||
nav.scrollTo({ left: right - nav.clientWidth });
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
125,
|
||||
{ trailing: true }
|
||||
);
|
||||
|
||||
const handleScrollButton = (right = false) => {
|
||||
if (nav && itemsList) {
|
||||
const scrollSize = Array.from(itemsList.children).reduceRight(
|
||||
(accumulator: number, item, index) => {
|
||||
if (accumulator + item.clientWidth > nav.clientWidth) {
|
||||
if (index === 0) {
|
||||
return nav.clientWidth;
|
||||
}
|
||||
}
|
||||
return accumulator + item.clientWidth;
|
||||
},
|
||||
0
|
||||
);
|
||||
nav.scrollTo({
|
||||
left: (nav.scrollLeft += right ? scrollSize : -scrollSize),
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
const handleKeyDown: KeyboardEventHandler<HTMLDivElement> = (event) => {
|
||||
type Tab = HTMLAnchorElement | null;
|
||||
|
||||
if (!itemsList) {
|
||||
return;
|
||||
}
|
||||
|
||||
const currentFocus = itemsList.ownerDocument.activeElement;
|
||||
|
||||
if (!currentFocus) {
|
||||
return;
|
||||
}
|
||||
|
||||
const previous = currentFocus.previousElementSibling as Tab;
|
||||
const next = currentFocus.nextElementSibling as Tab;
|
||||
const first = itemsList.firstElementChild as Tab;
|
||||
const last = itemsList.lastElementChild as Tab;
|
||||
|
||||
switch (event.key) {
|
||||
case "ArrowLeft":
|
||||
event.preventDefault();
|
||||
if (currentFocus === itemsList.firstElementChild) {
|
||||
last?.focus();
|
||||
} else {
|
||||
previous?.focus();
|
||||
}
|
||||
break;
|
||||
case "ArrowRight":
|
||||
event.preventDefault();
|
||||
if (currentFocus === itemsList.lastElementChild) {
|
||||
first?.focus();
|
||||
} else {
|
||||
next?.focus();
|
||||
}
|
||||
break;
|
||||
case "Home":
|
||||
event.preventDefault();
|
||||
first?.focus();
|
||||
break;
|
||||
case "End":
|
||||
event.preventDefault();
|
||||
last?.focus();
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
setMounted(true);
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
updateIndicator();
|
||||
scrollActiveElementIntoView();
|
||||
}, [nav, itemsList, activeElement]);
|
||||
|
||||
useEffect(() => {
|
||||
if (!nav || !itemsList) {
|
||||
return;
|
||||
}
|
||||
|
||||
const debouncedUpdateIndicator = debounce(updateIndicator, 75, { trailing: true });
|
||||
|
||||
const debounceIntersectOptions = [125, { leading: true }] as const;
|
||||
const handleFirstIntersect: IntersectionObserverCallback = debounce(
|
||||
(entries) => setShowScrollLeft(!entries[0].isIntersecting),
|
||||
...debounceIntersectOptions
|
||||
);
|
||||
const handleLastIntersect: IntersectionObserverCallback = debounce(
|
||||
(entries) => setShowScrollRight(!entries[0].isIntersecting),
|
||||
...debounceIntersectOptions
|
||||
);
|
||||
|
||||
const observeChildren = () => {
|
||||
if (
|
||||
itemsList.children.length > 1 &&
|
||||
itemsList.firstElementChild &&
|
||||
itemsList.lastElementChild
|
||||
) {
|
||||
firstObserver.observe(itemsList.firstElementChild);
|
||||
lastObserver.observe(itemsList.lastElementChild);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* handles observations whenever mutations may happen
|
||||
*
|
||||
* I'm not convinced we need this but the previously used mui navbar uses a mutation observer
|
||||
* so i thought it would be safe to do the same in case there's a chance a conditional item changes during render
|
||||
*/
|
||||
const handleMutation: MutationCallback = (mutations) => {
|
||||
firstObserver.disconnect();
|
||||
lastObserver.disconnect();
|
||||
|
||||
mutations.forEach((mutation) => {
|
||||
mutation.addedNodes.forEach((node) => {
|
||||
resizeObserver.observe(node as Element);
|
||||
});
|
||||
mutation.removedNodes.forEach((node) => {
|
||||
resizeObserver.unobserve(node as Element);
|
||||
});
|
||||
});
|
||||
|
||||
updateIndicator();
|
||||
observeChildren();
|
||||
};
|
||||
|
||||
const mutationObserver = new MutationObserver(handleMutation);
|
||||
mutationObserver.observe(itemsList, { childList: true });
|
||||
|
||||
const resizeObserver = new ResizeObserver(() => {
|
||||
debouncedUpdateIndicator();
|
||||
scrollActiveElementIntoView();
|
||||
});
|
||||
Array.from(itemsList.children).forEach((item) => resizeObserver.observe(item));
|
||||
resizeObserver.observe(nav);
|
||||
|
||||
const observerOptions = {
|
||||
root: nav,
|
||||
threshold: 0.75,
|
||||
};
|
||||
const firstObserver = new IntersectionObserver(handleFirstIntersect, observerOptions);
|
||||
const lastObserver = new IntersectionObserver(handleLastIntersect, observerOptions);
|
||||
observeChildren();
|
||||
|
||||
return () => {
|
||||
mutationObserver.disconnect();
|
||||
resizeObserver.disconnect();
|
||||
firstObserver.disconnect();
|
||||
lastObserver.disconnect();
|
||||
};
|
||||
}, [activeElement, nav, itemsList]);
|
||||
|
||||
return (
|
||||
<Tabs
|
||||
sx={{
|
||||
width: "100%",
|
||||
borderRadius: ".42rem",
|
||||
}}
|
||||
variant="scrollable"
|
||||
scrollButtons="auto"
|
||||
value={value}
|
||||
allowScrollButtonsMobile
|
||||
>
|
||||
{children}
|
||||
</Tabs>
|
||||
<div className="d-flex position-relative rounded overflow-hidden px-1">
|
||||
<nav className="tachi-navbar" ref={navRef}>
|
||||
<div
|
||||
ref={itemsListRef}
|
||||
onKeyDown={handleKeyDown}
|
||||
role="tablist"
|
||||
className="hstack gap-4 flex-grow-1 justify-content-around "
|
||||
>
|
||||
{items}
|
||||
</div>
|
||||
<div
|
||||
className="tachi-navbar-indicator"
|
||||
style={indicatorStyle}
|
||||
hidden={!mounted || indicatorStyle.width === 0}
|
||||
/>
|
||||
</nav>
|
||||
{scrollButtons}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Exposes a React Router Link component accepting otherMatchingPaths
|
||||
*/
|
||||
export const NavbarItem = React.forwardRef<HTMLAnchorElement, NavbarItemProps>(
|
||||
/* we don't want to pass otherMatchingPaths to the Link component
|
||||
it is still seen by the navbar and processed accordingly */
|
||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||
({ otherMatchingPaths, ...restProps }, ref) => <Link ref={ref} {...restProps} />
|
||||
);
|
||||
|
||||
Navbar.Item = NavbarItem;
|
||||
|
||||
@@ -3,7 +3,6 @@ import { APIFetchV1 } from "util/api";
|
||||
import { HumaniseError } from "util/humanise-error";
|
||||
import { SendErrorToast, SendSuccessToast } from "util/toaster";
|
||||
import Navbar from "components/nav/Navbar";
|
||||
import NavItem from "components/nav/NavItem";
|
||||
import MiniTable from "components/tables/components/MiniTable";
|
||||
import Divider from "components/util/Divider";
|
||||
import React, { useContext } from "react";
|
||||
@@ -204,52 +203,52 @@ export function UGPTBottomNav({
|
||||
playtype: Playtype;
|
||||
}) {
|
||||
const navItems = [
|
||||
<NavItem key="overview" to={`${baseUrl}/`}>
|
||||
<Navbar.Item key="overview" to={`${baseUrl}/`}>
|
||||
Overview & Activity
|
||||
</NavItem>,
|
||||
<NavItem key="scores" to={`${baseUrl}/scores`}>
|
||||
</Navbar.Item>,
|
||||
<Navbar.Item key="scores" to={`${baseUrl}/scores`}>
|
||||
Scores
|
||||
</NavItem>,
|
||||
<NavItem key="folders" to={`${baseUrl}/folders`}>
|
||||
</Navbar.Item>,
|
||||
<Navbar.Item key="folders" to={`${baseUrl}/folders`}>
|
||||
Folders
|
||||
</NavItem>,
|
||||
<NavItem key="sessions" to={`${baseUrl}/sessions`}>
|
||||
</Navbar.Item>,
|
||||
<Navbar.Item key="sessions" to={`${baseUrl}/sessions`}>
|
||||
Sessions
|
||||
</NavItem>,
|
||||
</Navbar.Item>,
|
||||
];
|
||||
|
||||
const utilsName = GetGPTUtilsName(game, playtype, isRequestedUser);
|
||||
|
||||
if (utilsName) {
|
||||
navItems.push(
|
||||
<NavItem key="targets" to={`${baseUrl}/utils`}>
|
||||
<Navbar.Item key="utils" to={`${baseUrl}/utils`}>
|
||||
{utilsName}
|
||||
</NavItem>
|
||||
</Navbar.Item>
|
||||
);
|
||||
}
|
||||
|
||||
if (isRequestedUser) {
|
||||
navItems.push(
|
||||
<NavItem key="rivals" to={`${baseUrl}/rivals`}>
|
||||
<Navbar.Item key="rivals" to={`${baseUrl}/rivals`}>
|
||||
Rivals
|
||||
</NavItem>,
|
||||
<NavItem key="targets" to={`${baseUrl}/targets`}>
|
||||
</Navbar.Item>,
|
||||
<Navbar.Item key="targets" to={`${baseUrl}/targets`}>
|
||||
Goals & Quests
|
||||
</NavItem>
|
||||
</Navbar.Item>
|
||||
);
|
||||
}
|
||||
|
||||
navItems.push(
|
||||
<NavItem key="leaderboard" to={`${baseUrl}/leaderboard`}>
|
||||
<Navbar.Item key="leaderboard" to={`${baseUrl}/leaderboard`}>
|
||||
Leaderboard
|
||||
</NavItem>
|
||||
</Navbar.Item>
|
||||
);
|
||||
|
||||
if (isRequestedUser) {
|
||||
navItems.push(
|
||||
<NavItem key="settings" to={`${baseUrl}/settings`}>
|
||||
<Navbar.Item key="settings" to={`${baseUrl}/settings`}>
|
||||
Settings
|
||||
</NavItem>
|
||||
</Navbar.Item>
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
import { APIFetchV1 } from "util/api";
|
||||
import { FormatDate } from "util/time";
|
||||
import Navbar from "components/nav/Navbar";
|
||||
import NavItem from "components/nav/NavItem";
|
||||
import Divider from "components/util/Divider";
|
||||
import ExternalLink from "components/util/ExternalLink";
|
||||
import Icon from "components/util/Icon";
|
||||
@@ -133,24 +132,24 @@ export function UserBottomNav({ baseUrl, reqUser }: { baseUrl: string; reqUser:
|
||||
const isRequestedUser = !!(user && user.id === reqUser.id);
|
||||
|
||||
const navItems = [
|
||||
<NavItem key="about" to={`${baseUrl}/`}>
|
||||
<Navbar.Item key="about" to={`${baseUrl}/`}>
|
||||
Overview
|
||||
</NavItem>,
|
||||
<NavItem key="games" to={`${baseUrl}/games`}>
|
||||
</Navbar.Item>,
|
||||
<Navbar.Item key="games" to={`${baseUrl}/games`}>
|
||||
Games
|
||||
</NavItem>,
|
||||
</Navbar.Item>,
|
||||
];
|
||||
|
||||
if (isRequestedUser) {
|
||||
navItems.push(
|
||||
<NavItem key="integrations" to={`${baseUrl}/integrations`}>
|
||||
<Navbar.Item key="integrations" to={`${baseUrl}/integrations`}>
|
||||
Service Integrations
|
||||
</NavItem>
|
||||
</Navbar.Item>
|
||||
);
|
||||
navItems.push(
|
||||
<NavItem key="imports" to={`${baseUrl}/imports`}>
|
||||
<Navbar.Item key="imports" to={`${baseUrl}/imports`}>
|
||||
Imports
|
||||
</NavItem>
|
||||
</Navbar.Item>
|
||||
);
|
||||
|
||||
// If mandates login, assume that we also use invite codes.
|
||||
@@ -158,24 +157,20 @@ export function UserBottomNav({ baseUrl, reqUser }: { baseUrl: string; reqUser:
|
||||
// querying the server, but I just don't care.
|
||||
if (ClientConfig.MANDATE_LOGIN) {
|
||||
navItems.push(
|
||||
<NavItem key="invites" to={`${baseUrl}/invites`}>
|
||||
<Navbar.Item key="invites" to={`${baseUrl}/invites`}>
|
||||
Invites
|
||||
</NavItem>
|
||||
</Navbar.Item>
|
||||
);
|
||||
}
|
||||
|
||||
navItems.push(
|
||||
<NavItem key="settings" to={`${baseUrl}/settings`}>
|
||||
<Navbar.Item key="settings" to={`${baseUrl}/settings`}>
|
||||
Profile Settings
|
||||
</NavItem>
|
||||
</Navbar.Item>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="mx-n9 align-items-center mb-0">
|
||||
<Navbar>{navItems}</Navbar>
|
||||
</div>
|
||||
);
|
||||
return <Navbar>{navItems}</Navbar>;
|
||||
}
|
||||
|
||||
function StatusComponent({ reqUser }: { reqUser: UserDocument }) {
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
import CircularProgress from "@mui/material/CircularProgress";
|
||||
import React, { CSSProperties } from "react";
|
||||
import Spinner from "react-bootstrap/Spinner";
|
||||
|
||||
export default function Loading({ style }: { style?: CSSProperties }) {
|
||||
return (
|
||||
<div className="d-flex justify-content-center align-items-center w-100" style={style}>
|
||||
<CircularProgress />
|
||||
<Spinner variant="primary" />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -32,6 +32,7 @@
|
||||
@import "bootstrap-scss/pagination";
|
||||
@import "bootstrap-scss/modal";
|
||||
@import "bootstrap-scss/close";
|
||||
@import "bootstrap-scss/spinners";
|
||||
@import "customised-bootstrap";
|
||||
|
||||
@import "bootstrap-scss/utilities/api";
|
||||
|
||||
@@ -20,8 +20,11 @@ $utilities: map-merge(
|
||||
50: 50%,
|
||||
//75: 75%,
|
||||
100: 100%,
|
||||
//"half": 50%,
|
||||
"full": 100%,
|
||||
//half: 50%,
|
||||
full: 100%,
|
||||
min: min-content,
|
||||
fit: fit-content,
|
||||
max: max-content,
|
||||
auto: auto,
|
||||
),
|
||||
),
|
||||
@@ -43,8 +46,11 @@ $utilities: map-merge(
|
||||
//50: 50%,
|
||||
//75: 75%,
|
||||
100: 100%,
|
||||
//"half": 50%,
|
||||
"full": 100%,
|
||||
//half: 50%,
|
||||
full: 100%,
|
||||
min: min-content,
|
||||
fit: fit-content,
|
||||
max: max-content,
|
||||
auto: auto,
|
||||
),
|
||||
),
|
||||
@@ -161,7 +167,7 @@ $utilities: map-merge(
|
||||
"sm": var(--#{$prefix}box-shadow-sm),
|
||||
"md": var(--#{$prefix}box-shadow),
|
||||
"lg": var(--#{$prefix}box-shadow-lg),
|
||||
//"inset": var(--#{$prefix}box-shadow-inset),
|
||||
"inset": var(--#{$prefix}box-shadow-inset),
|
||||
"none": none,
|
||||
),
|
||||
),
|
||||
|
||||
@@ -3,4 +3,5 @@
|
||||
@import "folder-minimap";
|
||||
@import "table";
|
||||
@import "timeline";
|
||||
@import "tooltip";
|
||||
@import "tooltip";
|
||||
@import "navbar";
|
||||
|
||||
@@ -0,0 +1,70 @@
|
||||
.tachi-navbar {
|
||||
display: inline-flex;
|
||||
position: relative;
|
||||
outline: none;
|
||||
width: 100%;
|
||||
background-color: var(--bs-secondary-bg);
|
||||
overflow-x: auto;
|
||||
overflow-y: hidden;
|
||||
scroll-behavior: smooth;
|
||||
@extend .scrollbar-hide;
|
||||
|
||||
&-item {
|
||||
padding: 1rem 2rem;
|
||||
outline: none;
|
||||
text-decoration: none;
|
||||
text-wrap: nowrap;
|
||||
user-select: none;
|
||||
z-index: 20;
|
||||
&:focus-visible {
|
||||
text-decoration: underline;
|
||||
@extend .text-primary;
|
||||
}
|
||||
}
|
||||
|
||||
&-button {
|
||||
%navbar-button {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
border: none;
|
||||
height: 100%;
|
||||
color: rgba(var(--bs-body-color-rgb), 0.8);
|
||||
transition: opacity, ease-in-out, $transition-duration;
|
||||
z-index: 40;
|
||||
@extend .animate-fade-in;
|
||||
&:hover,
|
||||
&:focus {
|
||||
color: rgba(var(--bs-body-color-rgb), 1);
|
||||
}
|
||||
}
|
||||
|
||||
$navbar-button-gradient: rgba(var(--bs-secondary-bg-rgb), 0) 0%,
|
||||
rgba(var(--bs-secondary-bg-rgb), 1) 60%;
|
||||
|
||||
&-left {
|
||||
@extend %navbar-button;
|
||||
left: 0;
|
||||
padding: 0 0.85rem 0 0.65rem;
|
||||
background: linear-gradient(270deg, $navbar-button-gradient);
|
||||
}
|
||||
|
||||
&-right {
|
||||
@extend %navbar-button;
|
||||
right: 0;
|
||||
padding: 0 0.65rem 0 0.85rem;
|
||||
background: linear-gradient(90deg, $navbar-button-gradient);
|
||||
}
|
||||
}
|
||||
|
||||
&-indicator {
|
||||
position: absolute;
|
||||
bottom: 2px;
|
||||
height: 2px;
|
||||
border-radius: var(--bs-border-radius);
|
||||
background-color: var(--bs-primary);
|
||||
transition-property: left, width;
|
||||
transition-duration: $transition-duration-lg;
|
||||
transition-timing-function: ease-in-out;
|
||||
z-index: 10;
|
||||
}
|
||||
}
|
||||
@@ -1,23 +1,27 @@
|
||||
.nav-link {
|
||||
@extend .rounded;
|
||||
@extend .rounded;
|
||||
}
|
||||
|
||||
.credits-easter-egg {
|
||||
transition: text-decoration-color 120ms ease-in-out;
|
||||
text-decoration: underline;
|
||||
text-decoration-color: transparent;
|
||||
&:hover {
|
||||
text-decoration-color: var(--#{$prefix}body-color);
|
||||
}
|
||||
transition: text-decoration-color 120ms ease-in-out;
|
||||
text-decoration: underline;
|
||||
text-decoration-color: transparent;
|
||||
&:hover {
|
||||
text-decoration-color: var(--#{$prefix}body-color);
|
||||
}
|
||||
}
|
||||
|
||||
a {
|
||||
@extend .transition-color;
|
||||
}
|
||||
|
||||
body,
|
||||
header {
|
||||
@extend .transition-bg;
|
||||
@extend .transition-color;
|
||||
@extend .transition-bg;
|
||||
@extend .transition-color;
|
||||
}
|
||||
|
||||
code {
|
||||
padding: #{$kbd-padding-y} #{$kbd-padding-x};
|
||||
border-radius: var(--#{$prefix}border-radius-sm);
|
||||
padding: #{$kbd-padding-y} #{$kbd-padding-x};
|
||||
border-radius: var(--#{$prefix}border-radius-sm);
|
||||
}
|
||||
|
||||
@@ -40,10 +40,3 @@
|
||||
transition: opacity 0.3s;
|
||||
opacity: 0;
|
||||
}
|
||||
|
||||
a:where(.MuiTab-root) {
|
||||
@extend .transition-color;
|
||||
&:hover {
|
||||
color: var(--#{$prefix}link-hover-color)
|
||||
}
|
||||
}
|
||||
Generated
-213
@@ -170,12 +170,6 @@ importers:
|
||||
'@babel/core':
|
||||
specifier: 7.1.0
|
||||
version: 7.1.0
|
||||
'@emotion/react':
|
||||
specifier: ^11.10.5
|
||||
version: 11.10.5(@babel/core@7.1.0)(@types/react@17.0.13)(react@17.0.2)
|
||||
'@emotion/styled':
|
||||
specifier: ^11.10.5
|
||||
version: 11.10.5(@babel/core@7.1.0)(@emotion/react@11.10.5)(@types/react@17.0.13)(react@17.0.2)
|
||||
'@fortawesome/fontawesome-free':
|
||||
specifier: 5.13.0
|
||||
version: 5.13.0
|
||||
@@ -188,9 +182,6 @@ importers:
|
||||
'@fullcalendar/react':
|
||||
specifier: ^6.0.0
|
||||
version: 6.0.0(@fullcalendar/core@6.0.0)(react-dom@17.0.2)(react@17.0.2)
|
||||
'@mui/material':
|
||||
specifier: ^5.11.1
|
||||
version: 5.11.1(@emotion/react@11.10.5)(@emotion/styled@11.10.5)(@types/react@17.0.13)(react-dom@17.0.2)(react@17.0.2)
|
||||
'@nivo/bar':
|
||||
specifier: 0.79.1
|
||||
version: 0.79.1(@nivo/core@0.79.0)(prop-types@15.6.0)(react-dom@17.0.2)(react@17.0.2)
|
||||
@@ -2310,12 +2301,6 @@ packages:
|
||||
resolution: {integrity: sha512-14FtKiHhy2QoPIzdTcvh//8OyBlknNs2nXRwIhG904opCby3l+9Xaf/wuPvICBF0rc1ZCNBd3nKe9cd2mecVkQ==}
|
||||
dev: false
|
||||
|
||||
/@emotion/is-prop-valid@1.2.0:
|
||||
resolution: {integrity: sha512-3aDpDprjM0AwaxGE09bOPkNxHpBd+kA6jty3RnaEXdweX1DF1U3VQpPYb0g1IStAuK7SVQ1cy+bNBBKp4W3Fjg==}
|
||||
dependencies:
|
||||
'@emotion/memoize': 0.8.0
|
||||
dev: false
|
||||
|
||||
/@emotion/memoize@0.8.0:
|
||||
resolution: {integrity: sha512-G/YwXTkv7Den9mXDO7AhLWkE3q+I92B+VqAE+dYG4NGPaHZGvt3G8Q0p9vmE+sq7rTGphUbAvmQ9YpbfMQGGlA==}
|
||||
dev: false
|
||||
@@ -2359,31 +2344,6 @@ packages:
|
||||
resolution: {integrity: sha512-zxRBwl93sHMsOj4zs+OslQKg/uhF38MB+OMKoCrVuS0nyTkqnau+BM3WGEoOptg9Oz45T/aIGs1qbVAsEFo3nA==}
|
||||
dev: false
|
||||
|
||||
/@emotion/styled@11.10.5(@babel/core@7.1.0)(@emotion/react@11.10.5)(@types/react@17.0.13)(react@17.0.2):
|
||||
resolution: {integrity: sha512-8EP6dD7dMkdku2foLoruPCNkRevzdcBaY6q0l0OsbyJK+x8D9HWjX27ARiSIKNF634hY9Zdoedh8bJCiva8yZw==}
|
||||
peerDependencies:
|
||||
'@babel/core': ^7.0.0
|
||||
'@emotion/react': ^11.0.0-rc.0
|
||||
'@types/react': '*'
|
||||
react: '>=16.8.0'
|
||||
peerDependenciesMeta:
|
||||
'@babel/core':
|
||||
optional: true
|
||||
'@types/react':
|
||||
optional: true
|
||||
dependencies:
|
||||
'@babel/core': 7.1.0
|
||||
'@babel/runtime': 7.20.7
|
||||
'@emotion/babel-plugin': 11.10.5(@babel/core@7.1.0)
|
||||
'@emotion/is-prop-valid': 1.2.0
|
||||
'@emotion/react': 11.10.5(@babel/core@7.1.0)(@types/react@17.0.13)(react@17.0.2)
|
||||
'@emotion/serialize': 1.1.1
|
||||
'@emotion/use-insertion-effect-with-fallbacks': 1.0.0(react@17.0.2)
|
||||
'@emotion/utils': 1.2.0
|
||||
'@types/react': 17.0.13
|
||||
react: 17.0.2
|
||||
dev: false
|
||||
|
||||
/@emotion/unitless@0.8.0:
|
||||
resolution: {integrity: sha512-VINS5vEYAscRl2ZUDiT3uMPlrFQupiKgHz5AA4bCH1miKBg4qtwkim1qPmJj/4WG6TreYMY111rEFsjupcOKHw==}
|
||||
dev: false
|
||||
@@ -2630,164 +2590,6 @@ packages:
|
||||
dev: false
|
||||
optional: true
|
||||
|
||||
/@mui/base@5.0.0-alpha.111(@types/react@17.0.13)(react-dom@17.0.2)(react@17.0.2):
|
||||
resolution: {integrity: sha512-2wfIPpl97S4dPzD0QOM3UIzQ/EuXCYQvHmXxTpfKxev/cfkzOe7Ik/McoYUBbtM1bSOqH3W276R/L2LF9cyXqQ==}
|
||||
engines: {node: '>=12.0.0'}
|
||||
peerDependencies:
|
||||
'@types/react': ^17.0.0 || ^18.0.0
|
||||
react: ^17.0.0 || ^18.0.0
|
||||
react-dom: ^17.0.0 || ^18.0.0
|
||||
peerDependenciesMeta:
|
||||
'@types/react':
|
||||
optional: true
|
||||
dependencies:
|
||||
'@babel/runtime': 7.20.7
|
||||
'@emotion/is-prop-valid': 1.2.0
|
||||
'@mui/types': 7.2.3(@types/react@17.0.13)
|
||||
'@mui/utils': 5.11.1(react@17.0.2)
|
||||
'@popperjs/core': 2.11.6
|
||||
'@types/react': 17.0.13
|
||||
clsx: 1.2.1
|
||||
prop-types: 15.8.1
|
||||
react: 17.0.2
|
||||
react-dom: 17.0.2(react@17.0.2)
|
||||
react-is: 18.2.0
|
||||
dev: false
|
||||
|
||||
/@mui/core-downloads-tracker@5.11.1:
|
||||
resolution: {integrity: sha512-QVqVNlZ2K+LqUDE5kFgYd0r4KekR/dv2cNYbAutQWbfOA8VPVUVrDz0ELrEcoe8TjM/CwnsmGvaDh/YSNl/ALA==}
|
||||
dev: false
|
||||
|
||||
/@mui/material@5.11.1(@emotion/react@11.10.5)(@emotion/styled@11.10.5)(@types/react@17.0.13)(react-dom@17.0.2)(react@17.0.2):
|
||||
resolution: {integrity: sha512-yaZiXvcrl2vgUK+VO24780BWRgwdAMmAyuMVZnRTts1Yu0tWd6PjIYq2ZtaOlpj6/LbaSS+Q2kSfxYnDQ20CEQ==}
|
||||
engines: {node: '>=12.0.0'}
|
||||
peerDependencies:
|
||||
'@emotion/react': ^11.5.0
|
||||
'@emotion/styled': ^11.3.0
|
||||
'@types/react': ^17.0.0 || ^18.0.0
|
||||
react: ^17.0.0 || ^18.0.0
|
||||
react-dom: ^17.0.0 || ^18.0.0
|
||||
peerDependenciesMeta:
|
||||
'@emotion/react':
|
||||
optional: true
|
||||
'@emotion/styled':
|
||||
optional: true
|
||||
'@types/react':
|
||||
optional: true
|
||||
dependencies:
|
||||
'@babel/runtime': 7.20.7
|
||||
'@emotion/react': 11.10.5(@babel/core@7.1.0)(@types/react@17.0.13)(react@17.0.2)
|
||||
'@emotion/styled': 11.10.5(@babel/core@7.1.0)(@emotion/react@11.10.5)(@types/react@17.0.13)(react@17.0.2)
|
||||
'@mui/base': 5.0.0-alpha.111(@types/react@17.0.13)(react-dom@17.0.2)(react@17.0.2)
|
||||
'@mui/core-downloads-tracker': 5.11.1
|
||||
'@mui/system': 5.11.1(@emotion/react@11.10.5)(@emotion/styled@11.10.5)(@types/react@17.0.13)(react@17.0.2)
|
||||
'@mui/types': 7.2.3(@types/react@17.0.13)
|
||||
'@mui/utils': 5.11.1(react@17.0.2)
|
||||
'@types/react': 17.0.13
|
||||
'@types/react-transition-group': 4.4.5
|
||||
clsx: 1.2.1
|
||||
csstype: 3.1.1
|
||||
prop-types: 15.8.1
|
||||
react: 17.0.2
|
||||
react-dom: 17.0.2(react@17.0.2)
|
||||
react-is: 18.2.0
|
||||
react-transition-group: 4.4.5(react-dom@17.0.2)(react@17.0.2)
|
||||
dev: false
|
||||
|
||||
/@mui/private-theming@5.11.1(@types/react@17.0.13)(react@17.0.2):
|
||||
resolution: {integrity: sha512-nnHg7kA5RwFRhy0wiDYe59sLCVGORpPypL1JcEdhv0+N0Zbmc2E/y4z2zqMRZ62MAEscpro7cQbvv244ThA84A==}
|
||||
engines: {node: '>=12.0.0'}
|
||||
peerDependencies:
|
||||
'@types/react': ^17.0.0 || ^18.0.0
|
||||
react: ^17.0.0 || ^18.0.0
|
||||
peerDependenciesMeta:
|
||||
'@types/react':
|
||||
optional: true
|
||||
dependencies:
|
||||
'@babel/runtime': 7.22.6
|
||||
'@mui/utils': 5.11.1(react@17.0.2)
|
||||
'@types/react': 17.0.13
|
||||
prop-types: 15.8.1
|
||||
react: 17.0.2
|
||||
dev: false
|
||||
|
||||
/@mui/styled-engine@5.11.0(@emotion/react@11.10.5)(@emotion/styled@11.10.5)(react@17.0.2):
|
||||
resolution: {integrity: sha512-AF06K60Zc58qf0f7X+Y/QjaHaZq16znliLnGc9iVrV/+s8Ln/FCoeNuFvhlCbZZQ5WQcJvcy59zp0nXrklGGPQ==}
|
||||
engines: {node: '>=12.0.0'}
|
||||
peerDependencies:
|
||||
'@emotion/react': ^11.4.1
|
||||
'@emotion/styled': ^11.3.0
|
||||
react: ^17.0.0 || ^18.0.0
|
||||
peerDependenciesMeta:
|
||||
'@emotion/react':
|
||||
optional: true
|
||||
'@emotion/styled':
|
||||
optional: true
|
||||
dependencies:
|
||||
'@babel/runtime': 7.22.6
|
||||
'@emotion/cache': 11.10.5
|
||||
'@emotion/react': 11.10.5(@babel/core@7.1.0)(@types/react@17.0.13)(react@17.0.2)
|
||||
'@emotion/styled': 11.10.5(@babel/core@7.1.0)(@emotion/react@11.10.5)(@types/react@17.0.13)(react@17.0.2)
|
||||
csstype: 3.1.1
|
||||
prop-types: 15.8.1
|
||||
react: 17.0.2
|
||||
dev: false
|
||||
|
||||
/@mui/system@5.11.1(@emotion/react@11.10.5)(@emotion/styled@11.10.5)(@types/react@17.0.13)(react@17.0.2):
|
||||
resolution: {integrity: sha512-BEA2S0hay8n8CcZftkeAVsi0nsb5ZjdnZRCahv5lX7QJYwDjO4ucJ6lnvxHe2v/9Te1LLjTO7ojxu/qM6CE5Cg==}
|
||||
engines: {node: '>=12.0.0'}
|
||||
peerDependencies:
|
||||
'@emotion/react': ^11.5.0
|
||||
'@emotion/styled': ^11.3.0
|
||||
'@types/react': ^17.0.0 || ^18.0.0
|
||||
react: ^17.0.0 || ^18.0.0
|
||||
peerDependenciesMeta:
|
||||
'@emotion/react':
|
||||
optional: true
|
||||
'@emotion/styled':
|
||||
optional: true
|
||||
'@types/react':
|
||||
optional: true
|
||||
dependencies:
|
||||
'@babel/runtime': 7.20.7
|
||||
'@emotion/react': 11.10.5(@babel/core@7.1.0)(@types/react@17.0.13)(react@17.0.2)
|
||||
'@emotion/styled': 11.10.5(@babel/core@7.1.0)(@emotion/react@11.10.5)(@types/react@17.0.13)(react@17.0.2)
|
||||
'@mui/private-theming': 5.11.1(@types/react@17.0.13)(react@17.0.2)
|
||||
'@mui/styled-engine': 5.11.0(@emotion/react@11.10.5)(@emotion/styled@11.10.5)(react@17.0.2)
|
||||
'@mui/types': 7.2.3(@types/react@17.0.13)
|
||||
'@mui/utils': 5.11.1(react@17.0.2)
|
||||
'@types/react': 17.0.13
|
||||
clsx: 1.2.1
|
||||
csstype: 3.1.1
|
||||
prop-types: 15.8.1
|
||||
react: 17.0.2
|
||||
dev: false
|
||||
|
||||
/@mui/types@7.2.3(@types/react@17.0.13):
|
||||
resolution: {integrity: sha512-tZ+CQggbe9Ol7e/Fs5RcKwg/woU+o8DCtOnccX6KmbBc7YrfqMYEYuaIcXHuhpT880QwNkZZ3wQwvtlDFA2yOw==}
|
||||
peerDependencies:
|
||||
'@types/react': '*'
|
||||
peerDependenciesMeta:
|
||||
'@types/react':
|
||||
optional: true
|
||||
dependencies:
|
||||
'@types/react': 17.0.13
|
||||
dev: false
|
||||
|
||||
/@mui/utils@5.11.1(react@17.0.2):
|
||||
resolution: {integrity: sha512-lMAPgIJoil8V9ZxsMbEflMsvZmWcHbRVMc4JDY9jPO9V4welpF43h/O267b1RqlcRnC5MEbVQV605GYkTZY29Q==}
|
||||
engines: {node: '>=12.0.0'}
|
||||
peerDependencies:
|
||||
react: ^17.0.0 || ^18.0.0
|
||||
dependencies:
|
||||
'@babel/runtime': 7.20.7
|
||||
'@types/prop-types': 15.7.5
|
||||
'@types/react-is': 17.0.3
|
||||
prop-types: 15.8.1
|
||||
react: 17.0.2
|
||||
react-is: 18.2.0
|
||||
dev: false
|
||||
|
||||
/@nivo/annotations@0.79.1(@nivo/core@0.79.0)(prop-types@15.6.0)(react-dom@17.0.2)(react@17.0.2):
|
||||
resolution: {integrity: sha512-lYso9Luu0maSDtIufwvyVt2+Wue7R9Fh3CIjuRDmNR72UjAgAVEcCar27Fy865UXGsj2hRJZ7KY/1s6kT3gu/w==}
|
||||
peerDependencies:
|
||||
@@ -3620,12 +3422,6 @@ packages:
|
||||
'@types/react': 17.0.13
|
||||
dev: false
|
||||
|
||||
/@types/react-is@17.0.3:
|
||||
resolution: {integrity: sha512-aBTIWg1emtu95bLTLx0cpkxwGW3ueZv71nE2YFBpL8k/z5czEW8yYpOo8Dp+UUAFAtKwNaOsh/ioSeQnWlZcfw==}
|
||||
dependencies:
|
||||
'@types/react': 17.0.13
|
||||
dev: false
|
||||
|
||||
/@types/react-router-bootstrap@0.26.1:
|
||||
resolution: {integrity: sha512-EdgZn/PmSA8+I0RW6WyDUgBvkVXc8AmbbgOJ8WJfec1R/V06X++OrIOzwzdgYl4g3uXvEB6oEIxzcjx1/8Opqw==}
|
||||
dependencies:
|
||||
@@ -4594,11 +4390,6 @@ packages:
|
||||
engines: {node: '>=0.8'}
|
||||
dev: false
|
||||
|
||||
/clsx@1.2.1:
|
||||
resolution: {integrity: sha512-EcR6r5a8bj6pu3ycsa/E/cKVGuTgZJZdsyUYHOksG/UHIiKfjxzRxYJpyVBwYaQeOvghal9fcc4PidlgzugAQg==}
|
||||
engines: {node: '>=6'}
|
||||
dev: false
|
||||
|
||||
/cluster-key-slot@1.1.0:
|
||||
resolution: {integrity: sha512-2Nii8p3RwAPiFwsnZvukotvow2rIHM+yQ6ZcBXGHdniadkYGZYiGmkHJIbZPIV9nfv7m/U1IPMVVcAhoWFeklw==}
|
||||
engines: {node: '>=0.10.0'}
|
||||
@@ -9066,10 +8857,6 @@ packages:
|
||||
resolution: {integrity: sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w==}
|
||||
dev: false
|
||||
|
||||
/react-is@18.2.0:
|
||||
resolution: {integrity: sha512-xWGDIW6x921xtzPkhiULtthJHoJvBbF3q26fzloPCK0hsvxtPVelvftw3zjbHWSkR2km9Z+4uxbDDK/6Zw9B8w==}
|
||||
dev: false
|
||||
|
||||
/react-lifecycles-compat@3.0.4:
|
||||
resolution: {integrity: sha512-fBASbA6LnOU9dOU2eW7aQ8xmYBSXUIWr+UmF9b1efZBazGNO+rcXT/icdKnYm2pTwcRylVUYwW7H1PHfLekVzA==}
|
||||
dev: false
|
||||
|
||||
Reference in New Issue
Block a user