Skip to content
Snippets Groups Projects
Unverified Commit f810e2fa authored by Gustavo Saiani's avatar Gustavo Saiani Committed by GitHub
Browse files

AppBar: switch between homepage link and sidebar buttons on hover (#21724)

parent ee8d71da
Branches
Tags
No related merge requests found
......@@ -258,6 +258,9 @@ export const ICON_PATHS: Record<string, any> = {
viewBox: "0 0 36 33",
},
},
home: {
path: "M16.2 0 1 12.8 4.2 32h8v-8.8a4 4 0 1 1 8 0V32h8l3.2-19.2L16.2 0Z",
},
hourglass: {
path:
"M3 1.838C3 .823 3.617 0 4.378 0H27.35c.761 0 1.379.823 1.379 1.838 0 1.015-.618 1.837-1.379 1.837h-.143a28.303 28.303 0 0 1-.25 3.17c-.237 1.641-.695 3.295-1.654 4.574-1.157 1.543-2.537 2.456-3.52 3.108h-.001l-.214.142c-.53.354-.839.58-1.028.783-.138.15-.138.205-.137.24v.005c0 .009-.002.014-.003.018v.002l-.002.003c0 .008.008.016.052.058l.002.002c.14.134.32.254.664.481l.403.269c1.154.776 2.72 1.971 4.291 4.563.764 1.26 1.212 3.171 1.41 4.777a15.138 15.138 0 0 1 .119 2.455h.011c.761 0 1.379.822 1.379 1.837 0 1.015-.618 1.838-1.379 1.838H4.38C3.616 32 3 31.177 3 30.162c0-1.015.617-1.837 1.378-1.837h.012a15.146 15.146 0 0 1 .118-2.454c.199-1.607.647-3.519 1.41-4.778 1.57-2.591 3.137-3.787 4.292-4.563.155-.105.288-.193.403-.269.343-.227.524-.347.664-.481l.002-.002c.043-.042.052-.05.051-.058l-.001-.005c-.001-.004-.003-.01-.003-.018v-.005c0-.035.001-.09-.138-.24-.189-.204-.497-.43-1.028-.783l-.214-.142c-.984-.652-2.364-1.565-3.52-3.107-.96-1.28-1.418-2.934-1.655-4.575a28.31 28.31 0 0 1-.25-3.17h-.143C3.618 3.675 3 2.853 3 1.838zm4.28 1.837h17.168c-.03.973-.094 1.908-.22 2.775-.212 1.468-.58 2.582-1.131 3.316-.862 1.15-1.88 1.825-2.881 2.491l-.178.118c-.494.33-1.075.724-1.52 1.203-.487.526-.873 1.218-.873 2.119 0 .898.367 1.561.894 2.069.34.327.804.632 1.193.888l.248.163c.93.626 2.167 1.551 3.472 3.705.457.753.847 2.196 1.032 3.687.09.72.123 1.387.104 1.907a6.013 6.013 0 0 1-.011.209H7.15a6.192 6.192 0 0 1-.011-.21c-.019-.52.014-1.186.103-1.906.185-1.49.576-2.934 1.032-3.687 1.305-2.154 2.543-3.08 3.473-3.705.077-.052.16-.106.248-.163.388-.256.853-.561 1.192-.888.527-.508.894-1.17.894-2.07 0-.9-.385-1.592-.873-2.118-.444-.48-1.026-.874-1.52-1.203l-.177-.118c-1.002-.666-2.019-1.342-2.881-2.491-.55-.734-.92-1.848-1.132-3.316a25.21 25.21 0 0 1-.22-2.775zm2.152 3.682h12.421c0 2.948-6.132 6.04-6.132 6.04s-6.29-3.38-6.29-6.04zm-.919 19.59h14.702c0-.7-.744-1.323-1.764-1.848-3.56-1.834-8.014-1.823-11.48.182-.856.495-1.458 1.059-1.458 1.665zm7.35-5.17a1.378 1.378 0 1 0 0-2.757 1.378 1.378 0 0 0 0 2.757z",
......@@ -447,15 +450,15 @@ export const ICON_PATHS: Record<string, any> = {
"M27.922 5.804c-5.157 0-10.667-3.294-13.46-5.804C10.69 3.16 6.118 5.333 1.57 5.804.314 5.804 0 7.029 0 7.529 0 16.314 2.04 29.333 14.62 32 27.52 29.172 29.73 11.892 28.856 7.059c-.144-.797-.677-1.255-.934-1.255z",
sidebar_closed: {
svg: `
<path d="M11.3814 11.0592C11.005 11.0592 10.6999 10.7541 10.6999 10.3777C10.6999 10.0014 11.005 9.69627 11.3814 9.69627H17.0053L14.6921 7.38309C14.426 7.11695 14.426 6.68546 14.6921 6.41933C14.9583 6.15319 15.3898 6.15319 15.6559 6.41933L19.1324 9.89587C19.2642 10.0276 19.3363 10.2076 19.3318 10.394C19.3274 10.5803 19.2469 10.7566 19.109 10.882L15.6324 14.0425C15.3539 14.2957 14.9229 14.2751 14.6698 13.9967C14.4166 13.7182 14.4371 13.2872 14.7156 13.034L16.8878 11.0592H11.3814Z"/>
<path fill-rule="evenodd" clip-rule="evenodd" d="M4 0C1.79086 0 0 1.79086 0 4V16C0 18.2091 1.79086 20 4 20H20C22.2091 20 24 18.2091 24 16V4C24 1.79086 22.2091 0 20 0H4ZM20 1H7V19H20C21.6569 19 23 17.6569 23 16V4C23 2.34315 21.6569 1 20 1ZM4 19H6V1H4C2.34315 1 1 2.34315 1 4V16C1 17.6569 2.34315 19 4 19Z" />
<svg width="32" height="32" fill="none" xmlns="http://www.w3.org/2000/svg">
<path fill-rule="evenodd" clip-rule="evenodd" d="M23.167 6H8a4 4 0 0 0-4 4v13a4 4 0 0 0 4 4h15.167a4 4 0 0 0 4-4V10a4 4 0 0 0-4-4ZM6 10a2 2 0 0 1 2-2h2v17H8a2 2 0 0 1-2-2V10Zm6 15h11.167a2 2 0 0 0 2-2V10a2 2 0 0 0-2-2H12v17Z" fill="#509EE3"/>
</svg>
`,
},
sidebar_open: {
svg: `
<path d="M18.6506 11.0592C19.0269 11.0592 19.332 10.7541 19.332 10.3777C19.332 10.0014 19.0269 9.69627 18.6506 9.69627H13.0267L15.3398 7.38309C15.606 7.11695 15.606 6.68546 15.3398 6.41933C15.0737 6.15319 14.6422 6.15319 14.3761 6.41933L10.8995 9.89587C10.7678 10.0276 10.6957 10.2076 10.7001 10.394C10.7046 10.5803 10.7851 10.7566 10.923 10.882L14.3995 14.0425C14.678 14.2957 15.109 14.2751 15.3622 13.9967C15.6154 13.7182 15.5949 13.2872 15.3164 13.034L13.1441 11.0592H18.6506Z" />
<path fill-rule="evenodd" clip-rule="evenodd" d="M4 0C1.79086 0 0 1.79086 0 4V16C0 18.2091 1.79086 20 4 20H20C22.2091 20 24 18.2091 24 16V4C24 1.79086 22.2091 0 20 0H4ZM20 1H7V19H20C21.6569 19 23 17.6569 23 16V4C23 2.34315 21.6569 1 20 1ZM4 19H6V1H4C2.34315 1 1 2.34315 1 4V16C1 17.6569 2.34315 19 4 19Z" />
`,
<path fill-rule="evenodd" clip-rule="evenodd" d="M23.167 6H8a4 4 0 0 0-4 4v13a4 4 0 0 0 4 4h15.167a4 4 0 0 0 4-4V10a4 4 0 0 0-4-4ZM6 10a2 2 0 0 1 2-2h5v17H8a2 2 0 0 1-2-2V10Zm9 15h8.167a2 2 0 0 0 2-2V10a2 2 0 0 0-2-2H15v17Z" fill="#509EE3"/><path opacity=".34" fill="#509EE3" d="M6 8h7v17H6z"/>`,
},
slack_colorized: {
img: "app/assets/img/slack.png",
......
......@@ -50,7 +50,7 @@ export const SearchInputContainer = styled.div<{ isActive: boolean }>`
${props =>
props.isActive &&
css`
width: 95%;
width: 100%;
${activeInputCSS};
`}
}
......@@ -81,6 +81,7 @@ export const SearchInput = styled.input<{ isActive: boolean }>`
${breakpointMaxSmall} {
width: 0;
padding: 0;
${props =>
props.isActive &&
......@@ -96,9 +97,14 @@ const ICON_MARGIN = "10px";
export const SearchIcon = styled(Icon)<{ isActive: boolean }>`
${breakpointMaxSmall} {
margin-left: ${props => (props.isActive ? ICON_MARGIN : "3px")};
margin-right: ${props => (props.isActive ? ICON_MARGIN : 0)};
transition: margin 0.3s;
${props =>
props.isActive &&
css`
margin-left: ${ICON_MARGIN};
margin-right: ${ICON_MARGIN};
`}
}
${breakpointMinSmall} {
......@@ -106,7 +112,7 @@ export const SearchIcon = styled(Icon)<{ isActive: boolean }>`
}
`;
export const ClearIconButton = styled.button`
export const CloseSearchButton = styled.button`
display: flex;
align-items: center;
justify-content: center;
......@@ -117,6 +123,10 @@ export const ClearIconButton = styled.button`
color: ${color("text-light")};
cursor: pointer;
&:hover {
color: ${color("text-medium")};
}
`;
export const SearchResultsFloatingContainer = styled.div`
......
import React, { useEffect, useCallback, useRef, useState } from "react";
import React, {
MouseEvent,
useEffect,
useCallback,
useRef,
useState,
} from "react";
import { t } from "ttag";
import { Location, LocationDescriptorObject } from "history";
......@@ -16,7 +22,7 @@ import RecentsList from "./RecentsList";
import {
SearchInputContainer,
SearchIcon,
ClearIconButton,
CloseSearchButton,
SearchInput,
SearchResultsFloatingContainer,
SearchResultsContainer,
......@@ -73,10 +79,6 @@ function SearchBar({
setSearchText(e.target.value);
}, []);
const onClear = useCallback(e => {
setSearchText("");
}, []);
useOnClickOutside(container, setInactive);
useKeyboardShortcut("Escape", setInactive);
......@@ -138,6 +140,14 @@ function SearchBar({
const hasSearchText = searchText.trim().length > 0;
const handleClickOnClose = useCallback(
(e: MouseEvent) => {
e.stopPropagation();
setInactive();
},
[setInactive],
);
return (
<div ref={container}>
<SearchInputContainer isActive={isActive} onClick={onInputContainerClick}>
......@@ -151,10 +161,10 @@ function SearchBar({
onKeyPress={handleInputKeyPress}
ref={searchInput}
/>
{isSmallScreen() && hasSearchText && (
<ClearIconButton onClick={onClear}>
{isSmallScreen() && isActive && (
<CloseSearchButton onClick={handleClickOnClose}>
<Icon name="close" />
</ClearIconButton>
</CloseSearchButton>
)}
</SearchInputContainer>
{isActive && MetabaseSettings.searchTypeaheadEnabled() && (
......
......@@ -23,29 +23,103 @@ export const AppBarRoot = styled.header`
z-index: 4;
`;
export const RowLeft = styled.div`
display: flex;
flex-direction: row;
align-items: center;
`;
export const RowRight = styled(RowLeft)`
flex: 1;
justify-content: flex-end;
`;
export const LogoLink = styled(Link)`
cursor: pointer;
display: flex;
align-items: center;
justify-content: center;
border-radius: 6px;
left: 0;
padding: ${space(1)};
padding-left: ${space(2)};
margin-left: ${space(2)};
position: absolute;
transition: opacity 0.3s;
&:hover {
background-color: ${color("bg-light")};
}
${breakpointMaxSmall} {
margin-left: ${space(1)};
}
`;
export const SidebarButtonContainer = styled.div`
left: 23px;
opacity: 0;
position: absolute;
top: 4px;
transition: opacity 0.3s;
${breakpointMaxSmall} {
left: 5px;
}
`;
export interface RowLeftProps {
isSearchActive: boolean;
}
export const RowLeft = styled.div<RowLeftProps>`
display: flex;
height: 100%;
flex-direction: row;
align-items: center;
width: 30%;
&:hover {
${LogoLink} {
opacity: 0;
pointer-events: none;
}
${SidebarButtonContainer} {
opacity: 1;
}
}
${breakpointMaxSmall} {
width: ${props => (props.isSearchActive ? "80px" : "calc(100% - 60px);")};
${LogoLink} {
opacity: 0;
pointer-events: none;
}
${SidebarButtonContainer} {
opacity: 1;
}
}
`;
export const RowMiddle = styled.div`
display: none;
justify-content: center;
width: 80px;
${breakpointMaxSmall} {
display: flex;
}
${LogoLink} {
position: relative;
padding-left: 8px;
margin-left: 0;
}
`;
export const RowRight = styled.div`
display: flex;
height: 100%;
flex-direction: row;
align-items: center;
width: 30%;
justify-content: flex-end;
${breakpointMaxSmall} {
width: calc(100% - 60px);
}
`;
export const SearchBarContainer = styled.div`
......@@ -65,6 +139,6 @@ export const SearchBarContent = styled.div`
${breakpointMinSmall} {
position: relative;
width: 500px;
width: 460px;
}
`;
......@@ -20,7 +20,9 @@ import {
SearchBarContainer,
SearchBarContent,
RowLeft,
RowMiddle,
RowRight,
SidebarButtonContainer,
} from "./AppBar.styled";
type Props = {
......@@ -32,6 +34,14 @@ type Props = {
onChangeLocation: (nextLocation: LocationDescriptorObject) => void;
};
function HomepageLink({ handleClick }: { handleClick: () => void }) {
return (
<LogoLink to="/" onClick={handleClick} data-metabase-event="Navbar;Logo">
<LogoIcon size={24} />
</LogoLink>
);
}
function AppBar({
isSidebarOpen,
location,
......@@ -69,23 +79,22 @@ function AppBar({
return (
<AppBarRoot>
<RowLeft>
<LogoLink
to="/"
onClick={onLogoClick}
data-metabase-event="Navbar;Logo"
>
<LogoIcon size={24} />
</LogoLink>
{!isSearchActive && (
<Tooltip tooltip={sidebarButtonTooltip}>
<RowLeft isSearchActive={isSearchActive}>
<HomepageLink handleClick={onLogoClick} />
<SidebarButtonContainer>
<Tooltip tooltip={sidebarButtonTooltip} isEnabled={!isSmallScreen()}>
<SidebarButton
isSidebarOpen={isSidebarOpen}
onClick={onToggleSidebarClick}
/>
</Tooltip>
)}
</SidebarButtonContainer>
</RowLeft>
{!isSearchActive && (
<RowMiddle>
<HomepageLink handleClick={onLogoClick} />
</RowMiddle>
)}
<RowRight>
<SearchBarContainer>
<SearchBarContent>
......
......@@ -107,6 +107,10 @@ export const ProfileLinkContainer = styled.div<{ isOpen: boolean }>`
color: ${color("text-light")};
`;
export const HomePageLink = styled(SidebarLink)`
padding-left: 12px;
`;
export const BrowseLink = styled(SidebarLink)`
padding-left: 14px;
`;
......
......@@ -125,7 +125,7 @@ function MainNavbarContainer({
if (pathname.startsWith("/question") || pathname.startsWith("/model")) {
return { type: "card", id: Urls.extractEntityId(slug) };
}
return { type: "unknown", url: pathname };
return { type: "non-entity", url: pathname };
}, [location, params]);
const collectionTree = useMemo<CollectionTreeItem[]>(() => {
......
......@@ -3,6 +3,7 @@ import { t } from "ttag";
import { BookmarksType, Collection, User } from "metabase-types/api";
import Link from "metabase/core/components/Link";
import { IconProps } from "metabase/components/Icon";
import { Tree } from "metabase/components/tree";
import TippyPopoverWithTrigger from "metabase/components/PopoverWithTrigger/TippyPopoverWithTrigger";
......@@ -25,6 +26,7 @@ import {
CollectionsMoreIconContainer,
CollectionsMoreIcon,
CollectionMenuList,
HomePageLink,
ProfileLinkContainer,
SidebarContentRoot,
SidebarHeading,
......@@ -73,7 +75,7 @@ function MainNavbarView({
handleCloseNavbar,
handleLogout,
}: Props) {
const isMiscLinkSelected = selectedItem.type === "unknown";
const isNonEntityLinkSelected = selectedItem.type === "non-entity";
const isCollectionSelected =
selectedItem.type === "collection" && selectedItem.id !== "users";
......@@ -86,18 +88,32 @@ function MainNavbarView({
return (
<SidebarContentRoot>
<div>
<SidebarSection>
<ul>
<HomePageLink
isSelected={isNonEntityLinkSelected && selectedItem.url === "/"}
icon="home"
onClick={onItemSelect}
url="/"
>
{t`Home`}
</HomePageLink>
</ul>
</SidebarSection>
{bookmarks.length > 0 && (
<SidebarSection>
<BookmarkList
bookmarks={bookmarks}
selectedItem={
selectedItem.type !== "unknown" ? selectedItem : undefined
selectedItem.type !== "non-entity" ? selectedItem : undefined
}
onSelect={onItemSelect}
reorderBookmarks={reorderBookmarks}
/>
</SidebarSection>
)}
<SidebarSection>
<CollectionSectionHeading currentUser={currentUser} />
<Tree
......@@ -118,7 +134,8 @@ function MainNavbarView({
icon="database"
url={BROWSE_URL}
isSelected={
isMiscLinkSelected && selectedItem.url.startsWith(BROWSE_URL)
isNonEntityLinkSelected &&
selectedItem.url.startsWith(BROWSE_URL)
}
onClick={onItemSelect}
data-metabase-event="NavBar;Data Browse"
......@@ -130,7 +147,7 @@ function MainNavbarView({
icon="add"
url={ADD_YOUR_OWN_DATA_URL}
isSelected={
isMiscLinkSelected &&
isNonEntityLinkSelected &&
selectedItem.url.startsWith(ADD_YOUR_OWN_DATA_URL)
}
onClick={onItemSelect}
......
......@@ -4,7 +4,7 @@ export type SelectedEntityItem = {
};
export type SelectedNonEntityItem = {
type: "unknown";
type: "non-entity";
url: string;
};
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment