diff --git a/client/package.json b/client/package.json
index cfb07597e..5766e56c5 100644
--- a/client/package.json
+++ b/client/package.json
@@ -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",
diff --git a/client/src/app/App.tsx b/client/src/app/App.tsx
index eab6b3ef7..0c3ffa3f7 100644
--- a/client/src/app/App.tsx
+++ b/client/src/app/App.tsx
@@ -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 }) {
-
-
-
-
-
-
+
+
+
+
diff --git a/client/src/components/dashboard/DashboardHeader.tsx b/client/src/components/dashboard/DashboardHeader.tsx
index 0d74554d6..8234b03df 100644
--- a/client/src/components/dashboard/DashboardHeader.tsx
+++ b/client/src/components/dashboard/DashboardHeader.tsx
@@ -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 = [
-
+
Activity
- ,
-
+ ,
+
Calendar
- ,
-
+ ,
+
Your Profiles
- ,
-
+ ,
+
Global Activity
- ,
+ ,
];
- return (
-
- {navItems}
-
- );
+ return {navItems};
}
diff --git a/client/src/components/game/GPTHeader.tsx b/client/src/components/game/GPTHeader.tsx
index 3cd47761f..de6b19ff4 100644
--- a/client/src/components/game/GPTHeader.tsx
+++ b/client/src/components/game/GPTHeader.tsx
@@ -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 = [
-
+
Activity
- ,
-
+ ,
+
Songs
- ,
-
+ ,
+
Leaderboards
- ,
- ,
+
Quests
- ,
+ ,
];
if (settings?.preferences.developerMode) {
navItems.push(
-
+
Developer Info
-
+
);
}
- return (
-
- {navItems}
-
- );
+ return {navItems};
}
diff --git a/client/src/components/layout/LayoutHeaderContainer.tsx b/client/src/components/layout/LayoutHeaderContainer.tsx
index 9a44fe821..011788b14 100644
--- a/client/src/components/layout/LayoutHeaderContainer.tsx
+++ b/client/src/components/layout/LayoutHeaderContainer.tsx
@@ -20,7 +20,7 @@ export default function UserHeaderContainer({
- {footer}
+ {footer}
diff --git a/client/src/components/layout/misc/MaterialThemeProvider.tsx b/client/src/components/layout/misc/MaterialThemeProvider.tsx
deleted file mode 100644
index 0a01486fc..000000000
--- a/client/src/components/layout/misc/MaterialThemeProvider.tsx
+++ /dev/null
@@ -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
{children};
-}
diff --git a/client/src/components/layout/screens/SplashScreen.tsx b/client/src/components/layout/screens/SplashScreen.tsx
index 6fe1e1f3b..c4a1c9d3a 100644
--- a/client/src/components/layout/screens/SplashScreen.tsx
+++ b/client/src/components/layout/screens/SplashScreen.tsx
@@ -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"
>
- {!broke &&
}
+ {!broke &&
}
{broke &&
{broke}
}
);
diff --git a/client/src/components/nav/NavItem.tsx b/client/src/components/nav/NavItem.tsx
deleted file mode 100644
index 875c4a6c5..000000000
--- a/client/src/components/nav/NavItem.tsx
+++ /dev/null
@@ -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;
- 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.
- {children}}
- LinkComponent={Link}
- to={to}
- style={{ opacity: 1 }}
- />
- );
-}
diff --git a/client/src/components/nav/Navbar.tsx b/client/src/components/nav/Navbar.tsx
index 6483bd9af..32a465ace 100644
--- a/client/src/components/nav/Navbar.tsx
+++ b/client/src/components/nav/Navbar.tsx
@@ -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 {
+ to: string;
+ children: string;
+ /**
+ * An array of other pathnames that should trigger an active item
+ */
+ otherMatchingPaths?: Array;
+}
- if (e.props.otherMatchingPaths) {
- return [basePath, ...e.props.otherMatchingPaths];
- }
- return [basePath];
- });
+export type NavbarItem = ReactElement;
+export interface NavbarProps {
+ children: ReactElement | ReactElement[];
+}
+
+export default function Navbar({ children }: NavbarProps) {
const location = useLocation();
- const value = useMemo(() => {
- const loc = location.pathname.split(/([#?]|\/$)/u)[0];
+ const navRef = useRef(null);
+ const itemsListRef = useRef(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) && (
+ <>
+
+
+ >
+ )}
+ >
+ );
+
+ 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 = (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 (
-
- {children}
-
+
+
+ {scrollButtons}
+
);
}
+
+/**
+ * Exposes a React Router Link component accepting otherMatchingPaths
+ */
+export const NavbarItem = React.forwardRef(
+ /* 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) =>
+);
+
+Navbar.Item = NavbarItem;
diff --git a/client/src/components/user/UGPTHeader.tsx b/client/src/components/user/UGPTHeader.tsx
index a6b9f3b68..5bec4ae30 100644
--- a/client/src/components/user/UGPTHeader.tsx
+++ b/client/src/components/user/UGPTHeader.tsx
@@ -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 = [
-
+
Overview & Activity
- ,
-
+ ,
+
Scores
- ,
-
+ ,
+
Folders
- ,
-
+ ,
+
Sessions
- ,
+ ,
];
const utilsName = GetGPTUtilsName(game, playtype, isRequestedUser);
if (utilsName) {
navItems.push(
-
+
{utilsName}
-
+
);
}
if (isRequestedUser) {
navItems.push(
-
+
Rivals
- ,
-
+ ,
+
Goals & Quests
-
+
);
}
navItems.push(
-
+
Leaderboard
-
+
);
if (isRequestedUser) {
navItems.push(
-
+
Settings
-
+
);
}
diff --git a/client/src/components/user/UserHeader.tsx b/client/src/components/user/UserHeader.tsx
index 420605961..de85f911c 100644
--- a/client/src/components/user/UserHeader.tsx
+++ b/client/src/components/user/UserHeader.tsx
@@ -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 = [
-
+
Overview
- ,
-
+ ,
+
Games
- ,
+ ,
];
if (isRequestedUser) {
navItems.push(
-
+
Service Integrations
-
+
);
navItems.push(
-
+
Imports
-
+
);
// 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(
-
+
Invites
-
+
);
}
navItems.push(
-
+
Profile Settings
-
+
);
}
- return (
-
- {navItems}
-
- );
+ return {navItems};
}
function StatusComponent({ reqUser }: { reqUser: UserDocument }) {
diff --git a/client/src/components/util/Loading.tsx b/client/src/components/util/Loading.tsx
index 59916236a..1d525685e 100644
--- a/client/src/components/util/Loading.tsx
+++ b/client/src/components/util/Loading.tsx
@@ -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 (
-
+
);
}
diff --git a/client/src/styles/bootstrap/_index.scss b/client/src/styles/bootstrap/_index.scss
index 7969f0098..243086911 100644
--- a/client/src/styles/bootstrap/_index.scss
+++ b/client/src/styles/bootstrap/_index.scss
@@ -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";
diff --git a/client/src/styles/bootstrap/customised-bootstrap/maps.scss b/client/src/styles/bootstrap/customised-bootstrap/maps.scss
index 41348d52f..cb91b5dcd 100644
--- a/client/src/styles/bootstrap/customised-bootstrap/maps.scss
+++ b/client/src/styles/bootstrap/customised-bootstrap/maps.scss
@@ -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,
),
),
diff --git a/client/src/styles/components/_index.scss b/client/src/styles/components/_index.scss
index e72660f29..194f7798c 100644
--- a/client/src/styles/components/_index.scss
+++ b/client/src/styles/components/_index.scss
@@ -3,4 +3,5 @@
@import "folder-minimap";
@import "table";
@import "timeline";
-@import "tooltip";
\ No newline at end of file
+@import "tooltip";
+@import "navbar";
diff --git a/client/src/styles/components/navbar.scss b/client/src/styles/components/navbar.scss
new file mode 100644
index 000000000..52ec7b5d4
--- /dev/null
+++ b/client/src/styles/components/navbar.scss
@@ -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;
+ }
+}
diff --git a/client/src/styles/globals.scss b/client/src/styles/globals.scss
index 41a3256dc..fe1a39f73 100644
--- a/client/src/styles/globals.scss
+++ b/client/src/styles/globals.scss
@@ -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);
}
diff --git a/client/src/styles/legacy/tachi-custom/_custom.scss b/client/src/styles/legacy/tachi-custom/_custom.scss
index 3c54962e7..5fa4d51c7 100644
--- a/client/src/styles/legacy/tachi-custom/_custom.scss
+++ b/client/src/styles/legacy/tachi-custom/_custom.scss
@@ -40,10 +40,3 @@
transition: opacity 0.3s;
opacity: 0;
}
-
-a:where(.MuiTab-root) {
- @extend .transition-color;
- &:hover {
- color: var(--#{$prefix}link-hover-color)
- }
-}
\ No newline at end of file
diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml
index 8c2e68829..d370b3077 100644
--- a/pnpm-lock.yaml
+++ b/pnpm-lock.yaml
@@ -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