Skip to content

Commit

Permalink
feat: add popup and perf improvements
Browse files Browse the repository at this point in the history
  • Loading branch information
icfor committed Mar 5, 2024
1 parent 9be84ed commit 96776e6
Show file tree
Hide file tree
Showing 17 changed files with 304 additions and 94 deletions.
9 changes: 6 additions & 3 deletions src/app/layout.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import { ToastContainer } from "react-toastify";
import "react-toastify/dist/ReactToastify.css";

import BaseWrapper from "@/features/core/components/base-wrapper";
import { CoreProvider } from "@/features/core/context/provider";
import { StakingProvider } from "@/features/staking/context/provider";
import {
dashboardUrl,
Expand All @@ -31,9 +32,11 @@ export default function RootLayout({
<html lang="en">
<body>
<AbstraxionProvider config={abstraxionConfig}>
<StakingProvider>
<BaseWrapper>{children}</BaseWrapper>
</StakingProvider>
<CoreProvider>
<StakingProvider>
<BaseWrapper>{children}</BaseWrapper>
</StakingProvider>
</CoreProvider>
</AbstraxionProvider>
<ToastContainer closeOnClick />
</body>
Expand Down
60 changes: 29 additions & 31 deletions src/features/core/components/base.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,9 @@ import { toast } from "react-toastify";

import { clipboard } from "@/features/staking/lib/core/icons";

import { useCore } from "../context/hooks";
import { setPopupOpenId } from "../context/reducer";

type TypographyProps = PropsWithChildren & {
className?: string;
};
Expand Down Expand Up @@ -41,10 +44,7 @@ type NavLinkProps = {
};

export const NavLink = ({ children, href }: NavLinkProps) => (
<Link
className="text-left font-[14px] font-normal leading-[24px] underline"
href={href}
>
<Link className="text-left font-normal leading-[24px] underline" href={href}>
{children}
</Link>
);
Expand All @@ -58,7 +58,7 @@ export const ButtonPill = ({ className, ...props }: ButtonPillProps) => (
<button
{...props}
className={[
"cursor-pointer rounded-full bg-bg-550 px-[8px] py-[4px] text-white hover:bg-bg-600 disabled:cursor-not-allowed disabled:bg-bg-400 disabled:text-typo-150",
"cursor-pointer rounded-full bg-bg-550 px-[8px] py-[4px] text-white hover:bg-bg-600 disabled:cursor-not-allowed disabled:bg-bg-600 disabled:text-typo-150",
className,
].join(" ")}
/>
Expand Down Expand Up @@ -94,7 +94,7 @@ export const InputBox = ({ error, ...props }: InputProps) => (
error ? "border-danger" : "border-white",
].join(" ")}
/>
<span className="absolute bottom-0 right-[12px] top-0 flex h-full items-center text-[24px] text-[48px] text-typo-300">
<span className="absolute bottom-0 right-[12px] top-0 flex h-full items-center text-[48px] text-typo-300">
XION
</span>
</span>
Expand Down Expand Up @@ -149,58 +149,56 @@ type FloatingDropdownProps = {
children: ReactNode;
className?: string;
id: string;
isOpen: boolean;
modalClass?: string;
offset?: number;
placement?: "bottom-end" | "bottom-start" | "bottom" | "right" | "top";
setIsOpen: (isOpen: boolean) => void;
Trigger: FC<{ id?: string }>;
};

// @TODO: Implement for settings
// eslint-disable-next-line @typescript-eslint/no-unused-vars
const FloatingDropdown = ({
export const FloatingDropdown = ({
children,
className,
id,
isOpen,
modalClass,
offset,
placement,
setIsOpen,
Trigger,
}: FloatingDropdownProps) => {
const [anchor, setAnchor] = useState<HTMLButtonElement | null>(null);
const { core } = useCore();
const isOpen = core.state.popupOpenId === id;

return (
<div className={[className].join(" ")}>
<button
onClick={(e) => {
e.stopPropagation();
setIsOpen?.(!isOpen);
core.dispatch(setPopupOpenId(id));
}}
ref={setAnchor}
>
<Trigger id={id} />
</button>
<ClickAwayListener
onClickAway={() => {
if (isOpen) {
setIsOpen?.(false);
}
}}
>
<BasePopup
anchor={anchor}
className={[modalClass].join(" ")}
offset={offset}
open={isOpen}
placement={placement}
withTransition
{isOpen && (
<ClickAwayListener
onClickAway={() => {
if (isOpen) {
core.dispatch(setPopupOpenId(null));
}
}}
>
{children}
</BasePopup>
</ClickAwayListener>
<BasePopup
anchor={anchor}
className={[modalClass].join(" ")}
offset={offset}
open={isOpen}
placement={placement}
withTransition
>
{children}
</BasePopup>
</ClickAwayListener>
)}
</div>
);
};
19 changes: 19 additions & 0 deletions src/features/core/context/hooks.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
import { useContext, useRef } from "react";

import { CoreContext } from "./state";
import type { CoreContextType } from "./state";

export const useCore = () => {
const coreRef = useRef<CoreContextType>({} as CoreContextType);
const core = useContext(CoreContext);

// It is important to not override the `current` object reference so it
// doesn't trigger more hooks than it should if it is added as a hook
// dependency
coreRef.current.state = core.state;
coreRef.current.dispatch = core.dispatch;

return {
core: coreRef.current,
};
};
17 changes: 17 additions & 0 deletions src/features/core/context/provider.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
"use client";

import type { PropsWithChildren } from "react";
import { useReducer } from "react";

import { reducer } from "./reducer";
import { CoreContext, defaultState } from "./state";

export const CoreProvider = ({ children }: PropsWithChildren) => {
const [state, dispatch] = useReducer(reducer, defaultState);

return (
<CoreContext.Provider value={{ dispatch, state }}>
{children}
</CoreContext.Provider>
);
};
45 changes: 45 additions & 0 deletions src/features/core/context/reducer.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
import type { CoreState } from "./state";

export type CoreAction =
| {
content: CoreState["isLoadingBlocking"];
type: "SET_IS_LOADING_BLOCKING";
}
| {
content: CoreState["popupOpenId"];
type: "SET_POPUP_OPEN_ID";
};

type Content<T extends CoreAction["type"]> = Extract<
CoreAction,
{ type: T }
>["content"];

export const setPopupOpenId = (
content: Content<"SET_POPUP_OPEN_ID">,
): CoreAction => ({
content,
type: "SET_POPUP_OPEN_ID",
});

export const setIsLoadingBlocking = (
content: Content<"SET_IS_LOADING_BLOCKING">,
): CoreAction => ({
content,
type: "SET_IS_LOADING_BLOCKING",
});

export const reducer = (state: CoreState, action: CoreAction) => {
switch (action.type) {
case "SET_POPUP_OPEN_ID":
return { ...state, popupOpenId: action.content };

case "SET_IS_LOADING_BLOCKING":
return { ...state, isLoadingBlocking: action.content };

default:
action satisfies never;

return state;
}
};
24 changes: 24 additions & 0 deletions src/features/core/context/state.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
import type { Dispatch } from "react";
import { createContext } from "react";

import type { CoreAction } from "./reducer";

export type CoreState = {
isLoadingBlocking: boolean;
popupOpenId: null | string;
};

export type CoreContextType = {
dispatch: Dispatch<CoreAction>;
state: CoreState;
};

export const defaultState: CoreState = {
isLoadingBlocking: false,
popupOpenId: null,
};

export const CoreContext = createContext<CoreContextType>({
dispatch: () => null,
state: defaultState,
});
Loading

0 comments on commit 96776e6

Please sign in to comment.