From cf59e47359818f53c7e3af818b2d1af5d2f00578 Mon Sep 17 00:00:00 2001 From: Thunnini Date: Thu, 10 Oct 2024 19:11:47 +0900 Subject: [PATCH] =?UTF-8?q?=ED=97=A4=EB=8D=94=EC=97=90=20=EC=82=AC?= =?UTF-8?q?=EC=9D=B4=EB=93=9C=ED=8C=A8=EB=84=90=EC=9D=84=20=EC=82=AC?= =?UTF-8?q?=EC=9A=A9=ED=95=B4=EB=B3=B4=EB=9D=BC=EB=8A=94=20=EB=B0=B0?= =?UTF-8?q?=EB=84=88=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/extension/src/layouts/header/header.tsx | 27 +- apps/extension/src/layouts/header/types.ts | 11 + .../pages/main/components/menu-bar/index.tsx | 101 +++- apps/extension/src/pages/main/index.tsx | 38 +- .../src/pages/main/layouts/header.tsx | 568 ++++++++++-------- .../pages/main/new-side-panel-header-top.tsx | 245 ++++++++ .../src/pages/setting/general/index.tsx | 10 +- apps/extension/src/stores/ui-config/index.ts | 43 +- 8 files changed, 769 insertions(+), 274 deletions(-) create mode 100644 apps/extension/src/pages/main/new-side-panel-header-top.tsx diff --git a/apps/extension/src/layouts/header/header.tsx b/apps/extension/src/layouts/header/header.tsx index 345f9f6019..9ccd566f28 100644 --- a/apps/extension/src/layouts/header/header.tsx +++ b/apps/extension/src/layouts/header/header.tsx @@ -29,7 +29,9 @@ export const HeaderHeight = "3.75rem"; const Styles = { Container: styled.div``, - HeaderContainer: styled.div` + HeaderContainer: styled.div<{ + fixedTopHeight?: string; + }>` height: ${HeaderHeight}; background: ${(props) => @@ -50,7 +52,7 @@ const Styles = { : ColorPalette["gray-10"]}; position: fixed; - top: 0; + top: ${(props) => props.fixedTopHeight ?? "0"}; left: 0; right: 0; @@ -108,6 +110,7 @@ const Styles = { displayFlex: boolean; fixedHeight: boolean; fixedMinHeight: boolean; + fixedTopHeight?: string; }>` ${({ displayFlex }) => { if (displayFlex) { @@ -119,7 +122,12 @@ const Styles = { return css``; }} - padding-top: ${HeaderHeight}; + padding-top: ${(props) => { + if (!props.fixedTopHeight) { + return HeaderHeight; + } + return `calc(${HeaderHeight} + ${props.fixedTopHeight})`; + }}; padding-bottom: ${({ bottomPadding }) => bottomPadding}; ${({ @@ -183,6 +191,8 @@ export const HeaderLayout: FunctionComponent< isNotReady, additionalPaddingBottom, headerContainerStyle, + + fixedTop, }) => { const [height, setHeight] = React.useState(() => pxToRem(600)); const lastSetHeight = useRef(-1); @@ -226,7 +236,15 @@ export const HeaderLayout: FunctionComponent< return ( - + {fixedTop ? ( +
+ {fixedTop.element} +
+ ) : null} + {left && !isNotReady ? ( {left} ) : null} @@ -254,6 +272,7 @@ export const HeaderLayout: FunctionComponent< fixedHeight={fixedHeight || false} fixedMinHeight={fixedMinHeight || false} bottomPadding={bottomPadding} + fixedTopHeight={fixedTop?.height} > {children} diff --git a/apps/extension/src/layouts/header/types.ts b/apps/extension/src/layouts/header/types.ts index 534b09cf88..d976ad69bb 100644 --- a/apps/extension/src/layouts/header/types.ts +++ b/apps/extension/src/layouts/header/types.ts @@ -22,4 +22,15 @@ export interface HeaderProps { isNotReady?: boolean; headerContainerStyle?: React.CSSProperties; + + // MainHeaderLayout에서만 테스트해봄 + // 다른 props 옵션과 섞였을때 잘 작동되는지는 모름 + // 그냥 MainHeaderLayout 전용이라고 생각할 것. + fixedTop?: { + // rem이여야 잘 작동될 확률이 높음. + // px 등은 테스트 안해봄 될수도 있고 안될수도 있음. + // 되도록 rem을 사용할 것 + height: string; + element: React.ReactElement; + }; } diff --git a/apps/extension/src/pages/main/components/menu-bar/index.tsx b/apps/extension/src/pages/main/components/menu-bar/index.tsx index e7573f9208..81a7f44403 100644 --- a/apps/extension/src/pages/main/components/menu-bar/index.tsx +++ b/apps/extension/src/pages/main/components/menu-bar/index.tsx @@ -8,7 +8,13 @@ import { useNavigate } from "react-router"; import { Gutter } from "../../../../components/gutter"; import { observer } from "mobx-react-lite"; import { useStore } from "../../../../stores"; -import { Button2, H3, H5, Subtitle4 } from "../../../../components/typography"; +import { + Button2, + Caption1, + H3, + H5, + Subtitle4, +} from "../../../../components/typography"; import { XAxis } from "../../../../components/axis"; import { Bleed } from "../../../../components/bleed"; import { FormattedMessage } from "react-intl"; @@ -39,8 +45,10 @@ const Styles = { export const MenuBar: FunctionComponent<{ close: () => void; -}> = observer(({ close }) => { - const { analyticsStore, keyRingStore } = useStore(); + + showSidePanelRecommendationTooltip?: boolean; +}> = observer(({ close, showSidePanelRecommendationTooltip }) => { + const { analyticsStore, keyRingStore, uiConfigStore } = useStore(); const location = useLocation(); @@ -220,10 +228,17 @@ export const MenuBar: FunctionComponent<{ { - toggleSidePanelMode(!sidePanelEnabled, (res) => - setSidePanelEnabled(res) - ); + toggleSidePanelMode(!sidePanelEnabled, (res) => { + setSidePanelEnabled(res); + + if (res) { + uiConfigStore.setShowNewSidePanelHeaderTop(false); + } + }); }} + showSidePanelRecommendationTooltip={ + showSidePanelRecommendationTooltip + } isSelected={sidePanelEnabled} isSidePanel={true} img={ @@ -263,9 +278,13 @@ export const MenuBar: FunctionComponent<{ { - toggleSidePanelMode(!sidePanelEnabled, (res) => - setSidePanelEnabled(res) - ); + toggleSidePanelMode(!sidePanelEnabled, (res) => { + setSidePanelEnabled(res); + + if (res) { + uiConfigStore.setShowNewSidePanelHeaderTop(false); + } + }); }} isSidePanel={false} isSelected={!sidePanelEnabled} @@ -382,7 +401,16 @@ const PanelModeItem: FunctionComponent<{ isSidePanel: boolean; img: React.ReactElement; text: React.ReactElement; -}> = ({ isSelected, onClick, isSidePanel, text, img }) => { + + showSidePanelRecommendationTooltip?: boolean; +}> = ({ + isSelected, + onClick, + isSidePanel, + text, + img, + showSidePanelRecommendationTooltip, +}) => { const theme = useTheme(); return ( @@ -423,6 +451,59 @@ const PanelModeItem: FunctionComponent<{ } }} > + {showSidePanelRecommendationTooltip ? ( +
+ + + + Try the new mode ✨ + + +
+ + + +
+
+
+ ) : null} {isSidePanel ? ( (null); + return ( - + { + if (isNotReady) { + return; + } + + if (uiConfigStore.showNewSidePanelHeaderTop) { + return { + height: "3rem", + element: ( + { + uiConfigStore.setShowNewSidePanelHeaderTop(false); + + if (mainHeaderLayoutRef.current) { + mainHeaderLayoutRef.current.setShowSidePanelRecommendationTooltip( + true + ); + mainHeaderLayoutRef.current.openSideMenu(); + } + }} + onCloseClick={() => { + uiConfigStore.setShowNewSidePanelHeaderTop(false); + }} + /> + ), + }; + } + })()} + > {/* side panel에서만 보여준다. 보여주는 로직은 isRefreshButtonVisible를 다루는 useEffect를 참고. refresh button이 로딩중이면 모조건 보여준다. */} void; + openSideMenu: () => void; + closeSideMenu: () => void; + + setShowSidePanelRecommendationTooltip: (value: boolean) => void; +} + +export const MainHeaderLayout = observer< PropsWithChildren< Pick< HeaderProps, @@ -48,290 +57,341 @@ export const MainHeaderLayout: FunctionComponent< | "additionalPaddingBottom" | "onSubmit" | "headerContainerStyle" + | "fixedTop" > - > -> = observer((props) => { - const { children, ...otherProps } = props; + >, + MainHeaderLayoutRef +>( + (props, ref) => { + const { children, ...otherProps } = props; + + const { + keyRingStore, + uiConfigStore, + chainStore, + accountStore, + queriesStore, + } = useStore(); + + const icnsPrimaryName = (() => { + if ( + uiConfigStore.icnsInfo && + chainStore.hasChain(uiConfigStore.icnsInfo.chainId) + ) { + const queries = queriesStore.get(uiConfigStore.icnsInfo.chainId); + const icnsQuery = queries.icns.queryICNSNames.getQueryContract( + uiConfigStore.icnsInfo.resolverContractAddress, + accountStore.getAccount(uiConfigStore.icnsInfo.chainId).bech32Address + ); + + return icnsQuery.primaryName.split(".")[0]; + } + })(); + + const theme = useTheme(); + const intl = useIntl(); - const { - keyRingStore, - uiConfigStore, - chainStore, - accountStore, - queriesStore, - } = useStore(); + const [currentChainIdForEVM, setCurrentChainIdForEVM] = React.useState< + string | undefined + >(); + const [activeTabOrigin, setActiveTabOrigin] = React.useState< + string | undefined + >(); + useEffect(() => { + const updateCurrentChainIdForEVM = async () => { + const activeTabOrigin = await getActiveTabOrigin(); + + if (activeTabOrigin) { + const msg = new GetCurrentChainIdForEVMMsg(activeTabOrigin); + const newCurrentChainIdForEVM = + await new InExtensionMessageRequester().sendMessage( + BACKGROUND_PORT, + msg + ); + setCurrentChainIdForEVM(newCurrentChainIdForEVM); + setActiveTabOrigin(activeTabOrigin); + } else { + setCurrentChainIdForEVM(undefined); + setActiveTabOrigin(undefined); + } + }; - const icnsPrimaryName = (() => { - if ( - uiConfigStore.icnsInfo && - chainStore.hasChain(uiConfigStore.icnsInfo.chainId) - ) { - const queries = queriesStore.get(uiConfigStore.icnsInfo.chainId); - const icnsQuery = queries.icns.queryICNSNames.getQueryContract( - uiConfigStore.icnsInfo.resolverContractAddress, - accountStore.getAccount(uiConfigStore.icnsInfo.chainId).bech32Address - ); + browser.tabs.onActivated.addListener(updateCurrentChainIdForEVM); + updateCurrentChainIdForEVM(); + // Update current chain id for EVM every second. + // TODO: Make it sync with `chainChanged` event. + const intervalId = setInterval(updateCurrentChainIdForEVM, 1000); + + return () => { + browser.tabs.onActivated.removeListener(updateCurrentChainIdForEVM); + clearInterval(intervalId); + }; + }, []); + const [isHoveredCurrenctChainIcon, setIsHoveredCurrenctChainIcon] = + React.useState(false); + const [isOpenCurrentChainDropdown, setIsOpenCurrentChainDropdown] = + React.useState(false); + const evmChainInfos = chainStore.chainInfos.filter((chainInfo) => + chainStore.isEvmChain(chainInfo.chainId) + ); - return icnsQuery.primaryName.split(".")[0]; - } - })(); + const [ + showSidePanelRecommendationTooltip, + setShowSidePanelRecommendationTooltip, + ] = React.useState(false); - const theme = useTheme(); - const intl = useIntl(); + const [isOpenMenu, setIsOpenMenu] = React.useState(false); - const [currentChainIdForEVM, setCurrentChainIdForEVM] = React.useState< - string | undefined - >(); - const [activeTabOrigin, setActiveTabOrigin] = React.useState< - string | undefined - >(); - useEffect(() => { - const updateCurrentChainIdForEVM = async () => { - const activeTabOrigin = await getActiveTabOrigin(); + const openMenu = () => { + setIsOpenMenu(true); - if (activeTabOrigin) { - const msg = new GetCurrentChainIdForEVMMsg(activeTabOrigin); - const newCurrentChainIdForEVM = - await new InExtensionMessageRequester().sendMessage( - BACKGROUND_PORT, - msg - ); - setCurrentChainIdForEVM(newCurrentChainIdForEVM); - setActiveTabOrigin(activeTabOrigin); - } else { - setCurrentChainIdForEVM(undefined); - setActiveTabOrigin(undefined); + if ( + uiConfigStore.newChainSuggestionConfig.newSuggestionChains.length > 0 + ) { + uiConfigStore.newChainSuggestionConfig.turnOffSuggestionChains( + ...uiConfigStore.newChainSuggestionConfig.newSuggestionChains + ); } }; - browser.tabs.onActivated.addListener(updateCurrentChainIdForEVM); - updateCurrentChainIdForEVM(); - // Update current chain id for EVM every second. - // TODO: Make it sync with `chainChanged` event. - const intervalId = setInterval(updateCurrentChainIdForEVM, 1000); - - return () => { - browser.tabs.onActivated.removeListener(updateCurrentChainIdForEVM); - clearInterval(intervalId); + const closeMenu = () => { + setIsOpenMenu(false); }; - }, []); - const [isHoveredCurrenctChainIcon, setIsHoveredCurrenctChainIcon] = - React.useState(false); - const [isOpenCurrentChainDropdown, setIsOpenCurrentChainDropdown] = - React.useState(false); - const evmChainInfos = chainStore.chainInfos.filter((chainInfo) => - chainStore.isEvmChain(chainInfo.chainId) - ); - const [isOpenMenu, setIsOpenMenu] = React.useState(false); + const openMenuRef = useRef(openMenu); + openMenuRef.current = openMenu; + const closeMenuRef = useRef(closeMenu); + closeMenuRef.current = closeMenu; - const openMenu = () => { - setIsOpenMenu(true); + useImperativeHandle( + ref, + () => ({ + toggleSideMenu: () => { + if (isOpenMenu) { + closeMenuRef.current(); + } else { + openMenuRef.current(); + } + }, + openSideMenu: () => { + openMenuRef.current(); + }, + closeSideMenu: () => { + closeMenuRef.current(); + }, + setShowSidePanelRecommendationTooltip: (value: boolean) => { + setShowSidePanelRecommendationTooltip(value); + }, + }), + [isOpenMenu] + ); - if (uiConfigStore.newChainSuggestionConfig.newSuggestionChains.length > 0) { - uiConfigStore.newChainSuggestionConfig.turnOffSuggestionChains( - ...uiConfigStore.newChainSuggestionConfig.newSuggestionChains - ); - } - }; + return ( + { + const name = keyRingStore.selectedKeyInfo?.name || "Keplr Account"; - return ( - { - const name = keyRingStore.selectedKeyInfo?.name || "Keplr Account"; + if (icnsPrimaryName !== "") { + return ( + + {name} - if (icnsPrimaryName !== "") { - return ( - - {name} + + ICNS : {icnsPrimaryName} + + } + > + icns-icon + + + ); + } + return name; + })()} + left={ + + {/* 일종의 padding left인데 cursor를 가지게 하면서 밑에서 tooltip도 함께 사용하기 위해서 다른 Box로 분리되어있음 */} + + - ICNS : {icnsPrimaryName} - + + + + { + return chainStore.getChain(chain).chainName; + }) + .join(", "), + }} + /> + + + + + + + +