feat: Homepage mobile UI (#10255)

* * Added hooks for detecting device
* fixed partial header issues

* * mobile UI completed

* config URL changed

* * bug fix

* * bug fix

* * mobile - on tap of app card launch app

* * conflict fix
* removed context menu from app card

* * homepage cy fix
This commit is contained in:
albinAppsmith 2022-02-17 22:08:36 +05:30 committed by GitHub
parent 46810e832a
commit 39e28f4e1b
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
14 changed files with 407 additions and 81 deletions

View File

@ -0,0 +1 @@
<svg fill="none" height="24" viewBox="0 0 24 24" width="24" xmlns="http://www.w3.org/2000/svg"><g stroke="#000" stroke-linecap="round" stroke-linejoin="round" stroke-width="2"><path d="m4 8h16"/><path d="m4 16h16"/></g></svg>

After

Width:  |  Height:  |  Size: 225 B

View File

@ -143,6 +143,7 @@ import EditBoxLineIcon from "remixicon-react/EditBoxLineIcon";
import StarLineIcon from "remixicon-react/StarLineIcon";
import StarFillIcon from "remixicon-react/StarFillIcon";
import Settings2LineIcon from "remixicon-react/Settings2LineIcon";
import HamburgerIcon from "remixicon-react/MenuLineIcon";
import MagicLineIcon from "remixicon-react/MagicLineIcon";
export enum IconSize {
@ -356,6 +357,7 @@ const ICON_LOOKUP = {
warning: <WarningIcon />,
widget: <WidgetIcon />,
workspace: <WorkspaceIcon />,
hamburger: <HamburgerIcon />,
};
export const IconCollection = Object.keys(ICON_LOOKUP);

View File

@ -18,14 +18,16 @@ export enum SearchVariant {
}
export type TextInputProps = CommonComponentProps & {
border?: boolean;
placeholder?: string;
fill?: boolean;
defaultValue?: string;
variant?: SearchVariant;
width?: string;
onChange?: (value: string) => void;
};
const SearchInputWrapper = styled.div`
const SearchInputWrapper = styled.div<{ border?: boolean }>`
& > div {
border: none;
@ -45,6 +47,16 @@ const SearchInputWrapper = styled.div`
& input {
padding: 0 8px;
}
${({ border }) =>
border &&
`
border: 1.2px solid var(--appsmith-search-input-mobile-border-color);
&:active, &:focus, &:hover {
border-color: var(--appsmith-search-input-focus-mobile-border-color);
}
`}
}
`;
@ -82,7 +94,7 @@ const SearchInput = forwardRef(
return props.onChange && props.onChange("");
}, [props]);
return (
<SearchInputWrapper ref={wrapperRef}>
<SearchInputWrapper border={props.border} ref={wrapperRef}>
<TextInput
{...props}
defaultValue={searchValue}
@ -102,7 +114,7 @@ const SearchInput = forwardRef(
</CloseIcon>
) : null
}
width="228px"
width={props.width ? props.width : "228px"}
/>
</SearchInputWrapper>
);

View File

@ -32,3 +32,8 @@ export const getPersistentAppStore = (appId: string, branch?: string) => {
};
export const TOOLTIP_HOVER_ON_DELAY = 1000;
export const MOBILE_MAX_WIDTH = 767;
export const TABLET_MIN_WIDTH = 768;
export const TABLET_MAX_WIDTH = 991;
export const DESKTOP_MIN_WIDTH = 992;

View File

@ -86,6 +86,7 @@ export const Colors = {
LIGHT_GREY2: "#C4C4C4",
Gallery: "#F0F0F0",
GALLERY_1: "#EBEBEB",
GALLERY_2: "#EDEDED",
Galliano: "#E0B30E",
ROYAL_BLUE: "#457AE6",
ALTO2: "#E0DEDE",

View File

@ -21,7 +21,7 @@ import {
getApplicationIcon,
getRandomPaletteColor,
} from "utils/AppsmithUtils";
import { omit } from "lodash";
import { noop, omit } from "lodash";
import Text, { TextType } from "components/ads/Text";
import Button, { Category, Size, IconPositions } from "components/ads/Button";
import Icon, { IconSize } from "components/ads/Icon";
@ -161,6 +161,7 @@ const Wrapper = styled(
props: ICardProps & {
hasReadPermission?: boolean;
backgroundColor: string;
isMobile?: boolean;
},
) => <Card {...omit(props, ["hasReadPermission", "backgroundColor"])} />,
)`
@ -191,6 +192,13 @@ const Wrapper = styled(
}
}
}
${({ isMobile }) =>
isMobile &&
`
width: 100% !important;
height: 126px !important;
`}
`;
const ApplicationImage = styled.div`
@ -283,6 +291,7 @@ type ApplicationCardProps = {
delete?: (applicationId: string) => void;
update?: (id: string, data: UpdateApplicationPayload) => void;
enableImportExport?: boolean;
isMobile?: boolean;
};
const EditButton = styled(Button)`
@ -391,9 +400,10 @@ function GitConnectedBadge() {
);
}
const Container = styled.div`
const Container = styled.div<{ isMobile?: boolean }>`
position: relative;
overflow: visible;
${({ isMobile }) => isMobile && `width: 100%;`}
`;
export function ApplicationCard(props: ApplicationCardProps) {
@ -703,8 +713,15 @@ export function ApplicationCard(props: ApplicationCardProps) {
return editedBy + " edited " + editedOn;
};
const LaunchAppInMobile = () => {
window.location.href = viewApplicationURL;
};
return (
<Container>
<Container
isMobile={props.isMobile}
onClick={props.isMobile ? LaunchAppInMobile : noop}
>
<NameWrapper
className="t--application-card"
hasReadPermission={hasReadPermission}
@ -727,6 +744,7 @@ export function ApplicationCard(props: ApplicationCardProps) {
: "t--application-card-background"
}
hasReadPermission={hasReadPermission}
isMobile={props.isMobile}
key={props.application.id}
>
<CircleAppIcon name={appIcon} size={Size.large} />
@ -746,7 +764,7 @@ export function ApplicationCard(props: ApplicationCardProps) {
appNameText
)}
</AppNameWrapper>
{showOverlay && (
{showOverlay && !props.isMobile && (
<div className="overlay">
<div className="overlay-blur" />
<ApplicationImage className="image-container">
@ -781,7 +799,7 @@ export function ApplicationCard(props: ApplicationCardProps) {
</Wrapper>
<CardFooter>
<ModifiedDataComponent>{editedByText()}</ModifiedDataComponent>
{!!moreActionItems.length && ContextMenu}
{!!moreActionItems.length && !props.isMobile && ContextMenu}
</CardFooter>
</NameWrapper>
{showGitBadge && <GitConnectedBadge />}

View File

@ -95,31 +95,40 @@ import getFeatureFlags from "utils/featureFlags";
import { setIsImportAppViaGitModalOpen } from "actions/gitSyncActions";
import SharedUserList from "pages/common/SharedUserList";
import { getOnboardingOrganisations } from "selectors/onboardingSelectors";
import { useIsMobileDevice } from "utils/hooks/useDeviceDetect";
import { Indices } from "constants/Layers";
import { getAppsmithConfigs } from "@appsmith/configs";
import AnalyticsUtil from "utils/AnalyticsUtil";
const OrgDropDown = styled.div`
const OrgDropDown = styled.div<{ isMobile?: boolean }>`
display: flex;
padding: ${(props) => props.theme.spaces[4]}px
${(props) => props.theme.spaces[4]}px;
padding: ${(props) => (props.isMobile ? `10px 16px` : `10px 10px`)};
font-size: ${(props) => props.theme.fontSizes[1]}px;
justify-content: space-between;
align-items: center;
${({ isMobile }) =>
isMobile &&
`
position: sticky;
top: 0;
background-color: #fff;
z-index: ${Indices.Layer8};
`}
`;
const ApplicationCardsWrapper = styled.div`
const ApplicationCardsWrapper = styled.div<{ isMobile?: boolean }>`
display: flex;
flex-wrap: wrap;
gap: 20px;
gap: ${({ isMobile }) => (isMobile ? 12 : 20)}px;
font-size: ${(props) => props.theme.fontSizes[4]}px;
padding: 10px;
padding: ${({ isMobile }) => (isMobile ? `10px 16px` : `10px`)};
`;
const OrgSection = styled.div`
margin-bottom: 40px;
const OrgSection = styled.div<{ isMobile?: boolean }>`
margin-bottom: ${({ isMobile }) => (isMobile ? `8` : `40`)}px;
`;
const PaddingWrapper = styled.div`
const PaddingWrapper = styled.div<{ isMobile?: boolean }>`
display: flex;
align-items: baseline;
justify-content: center;
@ -184,6 +193,12 @@ const PaddingWrapper = styled.div`
height: ${(props) => props.theme.card.minHeight - 15}px;
}
}
${({ isMobile }) =>
isMobile &&
`
width: 100% !important;
`}
`;
const LeftPaneWrapper = styled.div`
@ -198,7 +213,7 @@ const LeftPaneWrapper = styled.div`
top: ${(props) => props.theme.homePage.header}px;
box-shadow: 1px 0px 0px #ededed;
`;
const ApplicationContainer = styled.div`
const ApplicationContainer = styled.div<{ isMobile?: boolean }>`
height: calc(100vh - ${(props) => props.theme.homePage.search.height - 40}px);
overflow: auto;
padding-right: ${(props) => props.theme.homePage.leftPane.rightMargin}px;
@ -215,6 +230,13 @@ const ApplicationContainer = styled.div`
props.theme.homePage.leftPane.leftPadding}px
);
scroll-behavior: smooth;
${({ isMobile }) =>
isMobile &&
`
margin-left: 0;
width: 100%;
padding: 0;
`}
`;
const ItemWrapper = styled.div`
@ -401,6 +423,7 @@ function LeftPane() {
const isFetchingApplications = useSelector(getIsFetchingApplications);
const { appVersion } = getAppsmithConfigs();
const howMuchTimeBefore = howMuchTimeBeforeText(appVersion.releaseDate);
const isMobile = useIsMobileDevice();
let userOrgs;
if (!isFetchingApplications) {
userOrgs = fetchedUserOrgs;
@ -411,6 +434,8 @@ function LeftPane() {
const location = useLocation();
const urlHash = location.hash.slice(1);
if (isMobile) return null;
return (
<LeftPaneWrapper>
<LeftPaneSection
@ -497,8 +522,8 @@ const CreateNewLabel = styled(Text)`
margin-top: 18px;
`;
const OrgNameElement = styled(Text)`
max-width: 500px;
const OrgNameElement = styled(Text)<{ isMobile?: boolean }>`
max-width: ${({ isMobile }) => (isMobile ? 220 : 500)}px;
${truncateTextUsingEllipsis}
`;
@ -541,6 +566,7 @@ function ApplicationsSection(props: any) {
const userOrgs = useSelector(getUserApplicationsOrgsList);
const creatingApplicationMap = useSelector(getIsCreatingApplication);
const currentUser = useSelector(getCurrentUser);
const isMobile = useIsMobileDevice();
const deleteApplication = (applicationId: string) => {
if (applicationId && applicationId.length > 0) {
dispatch({
@ -612,6 +638,7 @@ function ApplicationsSection(props: any) {
>
<OrgNameElement
className={isFetchingApplications ? BlueprintClasses.SKELETON : ""}
isMobile={isMobile}
type={TextType.H1}
>
{orgName}
@ -674,8 +701,12 @@ function ApplicationsSection(props: any) {
PERMISSION_TYPE.MANAGE_ORGANIZATION,
);
return (
<OrgSection className="t--org-section" key={index}>
<OrgDropDown>
<OrgSection
className="t--org-section"
isMobile={isMobile}
key={index}
>
<OrgDropDown isMobile={isMobile}>
{(currentUser || isFetchingApplications) &&
OrgMenuTarget({
orgName: organization.name,
@ -708,25 +739,28 @@ function ApplicationsSection(props: any) {
!isFetchingApplications && (
<OrgShareUsers>
<SharedUserList orgId={organization.id} />
<FormDialogComponent
Form={OrgInviteUsersForm}
canOutsideClickClose
orgId={organization.id}
title={`Invite Users to ${organization.name}`}
trigger={
<Button
category={Category.tertiary}
icon={"share"}
size={Size.medium}
tag="button"
text={"Share"}
/>
}
/>
{!isMobile && (
<FormDialogComponent
Form={OrgInviteUsersForm}
canOutsideClickClose
orgId={organization.id}
title={`Invite Users to ${organization.name}`}
trigger={
<Button
category={Category.tertiary}
icon={"share"}
size={Size.medium}
tag="button"
text={"Share"}
/>
}
/>
)}
{isPermitted(
organization.userPermissions,
PERMISSION_TYPE.CREATE_APPLICATION,
) &&
!isMobile &&
!isFetchingApplications &&
applications.length !== 0 && (
<Button
@ -757,7 +791,7 @@ function ApplicationsSection(props: any) {
text={"New"}
/>
)}
{(currentUser || isFetchingApplications) && (
{(currentUser || isFetchingApplications) && !isMobile && (
<Menu
className="t--org-name"
cypressSelector="t--org-name"
@ -904,15 +938,16 @@ function ApplicationsSection(props: any) {
</OrgShareUsers>
)}
</OrgDropDown>
<ApplicationCardsWrapper key={organization.id}>
<ApplicationCardsWrapper isMobile={isMobile} key={organization.id}>
{applications.map((application: any) => {
return (
<PaddingWrapper key={application.id}>
<PaddingWrapper isMobile={isMobile} key={application.id}>
<ApplicationCard
application={application}
delete={deleteApplication}
duplicate={duplicateApplicationDispatch}
enableImportExport={enableImportExport}
isMobile={isMobile}
key={application.id}
update={updateApplicationDispatch}
/>
@ -924,32 +959,34 @@ function ApplicationsSection(props: any) {
<NoAppsFoundIcon />
<span>Theres nothing inside this organization</span>
{/* below component is duplicate. This is because of cypress test were failing */}
<Button
className="t--new-button createnew"
icon={"plus"}
isLoading={
creatingApplicationMap &&
creatingApplicationMap[organization.id]
}
onClick={() => {
if (
Object.entries(creatingApplicationMap).length === 0 ||
(creatingApplicationMap &&
!creatingApplicationMap[organization.id])
) {
createNewApplication(
getNextEntityName(
"Untitled application ",
applications.map((el: any) => el.name),
),
organization.id,
);
{!isMobile && (
<Button
className="t--new-button createnew"
icon={"plus"}
isLoading={
creatingApplicationMap &&
creatingApplicationMap[organization.id]
}
}}
size={Size.medium}
tag="button"
text={"New"}
/>
onClick={() => {
if (
Object.entries(creatingApplicationMap).length === 0 ||
(creatingApplicationMap &&
!creatingApplicationMap[organization.id])
) {
createNewApplication(
getNextEntityName(
"Untitled application ",
applications.map((el: any) => el.name),
),
organization.id,
);
}
}}
size={Size.medium}
tag="button"
text={"New"}
/>
)}
</NoAppsFound>
)}
</ApplicationCardsWrapper>
@ -960,7 +997,10 @@ function ApplicationsSection(props: any) {
}
return (
<ApplicationContainer className="t--applications-container">
<ApplicationContainer
className="t--applications-container"
isMobile={isMobile}
>
{organizationsListComponent}
{getFeatureFlags().GIT_IMPORT && <ImportAppViaGitModal />}
</ApplicationContainer>

View File

@ -0,0 +1,165 @@
import React from "react";
import styled from "styled-components";
import { Colors } from "constants/Colors";
import ProfileImage from "pages/common/ProfileImage";
import MenuItem from "components/ads/MenuItem";
import { ADMIN_SETTINGS_CATEGORY_DEFAULT_URL } from "constants/routes";
import {
getOnSelectAction,
DropdownOnSelectActions,
} from "./CustomizedDropdown/dropdownHelpers";
import { ReduxActionTypes } from "constants/ReduxActionConstants";
import { useSelector } from "react-redux";
import { getCurrentUser } from "selectors/usersSelectors";
import {
createMessage,
ADMIN_SETTINGS,
DOCUMENTATION,
} from "@appsmith/constants/messages";
import { getAppsmithConfigs } from "@appsmith/configs";
import { howMuchTimeBeforeText } from "utils/helpers";
type MobileSideBarProps = {
name: string;
isOpen: boolean;
userName?: string;
photoId?: string;
};
const MainContainer = styled.div<{ isOpen: boolean }>`
position: absolute;
top: 49px; /* height of mobile header */
left: ${({ isOpen }) => (isOpen ? `0` : `-100vw`)};
width: 100vw;
height: calc(100vh - 49px);
background-color: ${Colors.WHITE};
transition: left 0.6s ease;
padding: 16px;
`;
const Section = styled.div`
padding: 20px 0;
border-bottom: 1px solid ${Colors.MERCURY};
& > h4 {
color: ${Colors.BLACK};
font-size: 13px;
font-weight: 600;
margin-left: 4px;
}
`;
const ProfileSection = styled(Section)`
display: flex;
align-items: center;
`;
const UserNameSection = styled.div`
display: flex;
flex-direction: column;
justify-content: center;
margin-left: 12px;
`;
const Name = styled.span`
font-weight: 600;
color: ${Colors.BLACK};
font-size: 16px;
`;
const Email = styled.span`
font-size: 14px;
color: ${Colors.GREY_8};
`;
const StyledMenuItem = styled(MenuItem)`
svg {
width: 16px;
height: 16px;
fill: ${Colors.DARK_GRAY};
}
.cs-text {
color: ${Colors.BLACK};
font-size: 16px;
}
`;
const LeftPaneVersionData = styled.div`
display: flex;
justify-content: space-between;
color: #121826;
font-size: 8px;
width: 92%;
margin-top: 8px;
`;
export default function MobileSideBar(props: MobileSideBarProps) {
const user = useSelector(getCurrentUser);
const { appVersion } = getAppsmithConfigs();
const howMuchTimeBefore = howMuchTimeBeforeText(appVersion.releaseDate);
return (
<MainContainer isOpen={props.isOpen}>
<ProfileSection>
<ProfileImage
className="t--profile-menu-icon"
side={52}
source={!!props.photoId ? `/api/v1/assets/${props.photoId}` : ""}
userName={props.name || props.userName}
/>
<UserNameSection>
<Name>{props.name}</Name>
<Email>{props.userName}</Email>
</UserNameSection>
</ProfileSection>
<Section>
<h4>ACCOUNT</h4>
{user?.isSuperUser && user?.isConfigurable && (
<StyledMenuItem
className={`t--admin-settings-menu`}
icon="setting"
onSelect={() => {
getOnSelectAction(DropdownOnSelectActions.REDIRECT, {
path: ADMIN_SETTINGS_CATEGORY_DEFAULT_URL,
});
}}
text={createMessage(ADMIN_SETTINGS)}
/>
)}
<StyledMenuItem
className="t--logout-icon"
icon="logout"
onSelect={() =>
getOnSelectAction(DropdownOnSelectActions.DISPATCH, {
type: ReduxActionTypes.LOGOUT_USER_INIT,
})
}
text="Sign Out"
/>
</Section>
<Section>
<StyledMenuItem
icon="discord"
onSelect={() => {
window.open("https://discord.gg/rBTTVJp", "_blank");
}}
text={"Join our Discord"}
/>
<StyledMenuItem
icon="book"
onSelect={() => {
window.open("https://docs.appsmith.com/", "_blank");
}}
text={createMessage(DOCUMENTATION)}
/>
</Section>
<LeftPaneVersionData>
<span>Appsmith {appVersion.id}</span>
{howMuchTimeBefore !== "" && (
<span>Released {howMuchTimeBefore} ago</span>
)}
</LeftPaneVersionData>
</MainContainer>
);
}

View File

@ -1,4 +1,4 @@
import React from "react";
import React, { useState } from "react";
import { Link, useLocation } from "react-router-dom";
import { connect } from "react-redux";
import { getCurrentUser } from "selectors/usersSelectors";
@ -12,9 +12,16 @@ import Button from "components/editorComponents/Button";
import history from "utils/history";
import ProfileDropdown from "./ProfileDropdown";
import Bell from "notifications/Bell";
import { Colors } from "constants/Colors";
import { useIsMobileDevice } from "utils/hooks/useDeviceDetect";
import { ReactComponent as TwoLineHamburger } from "assets/icons/ads/two-line-hamburger.svg";
import MobileSideBar from "./MobileSidebar";
import { Indices } from "constants/Layers";
import Icon, { IconSize } from "components/ads/Icon";
const StyledPageHeader = styled(StyledHeader)<{
hideShadow?: boolean;
isMobile?: boolean;
showSeparator?: boolean;
}>`
background: white;
@ -23,10 +30,18 @@ const StyledPageHeader = styled(StyledHeader)<{
flex-direction: row;
position: fixed;
top: 0;
z-index: 10;
z-index: ${Indices.Layer9};
box-shadow: ${(props) =>
props.hideShadow ? `none` : `0px 4px 4px rgba(0, 0, 0, 0.05)`};
${(props) => props.showSeparator && sideBorder}
props.hideShadow && !props.isMobile
? `none`
: `0px 4px 4px rgba(0, 0, 0, 0.05)`};
${(props) => props.showSeparator && !props.isMobile && sideBorder}
${({ isMobile }) =>
isMobile &&
`
padding: 0 12px;
padding-left: 10px;
`}
`;
const HeaderSection = styled.div`
@ -49,12 +64,19 @@ const sideBorder = css`
left: ${(props) => props.theme.homePage.sidebar}px;
width: 1px;
height: 100%;
background-color: #ededed;
background-color: ${Colors.GALLERY_2};
}
`;
const StyledDropDownContainer = styled.div``;
const StyledTwoLineHamburger = styled(TwoLineHamburger)`
fill: ${Colors.BLACK};
width: 22px;
height: 22px;
cursor: pointer;
`;
type PageHeaderProps = {
user?: User;
hideShadow?: boolean;
@ -65,6 +87,8 @@ export function PageHeader(props: PageHeaderProps) {
const { user } = props;
const location = useLocation();
const queryParams = new URLSearchParams(location.search);
const isMobile = useIsMobileDevice();
const [isMobileSidebarOpen, setIsMobileSidebarOpen] = useState(false);
let loginUrl = AUTH_LOGIN_URL;
if (queryParams.has("redirectUrl")) {
loginUrl += `?redirectUrl
@ -74,6 +98,7 @@ export function PageHeader(props: PageHeaderProps) {
return (
<StyledPageHeader
hideShadow={props.hideShadow || false}
isMobile={isMobile}
showSeparator={props.showSeparator || false}
>
<HeaderSection>
@ -81,7 +106,7 @@ export function PageHeader(props: PageHeaderProps) {
<AppsmithLogo />
</Link>
</HeaderSection>
{user && (
{user && !isMobile && (
<>
{user.username !== ANONYMOUS_USERNAME && <Bell />}
<StyledDropDownContainer>
@ -103,6 +128,24 @@ export function PageHeader(props: PageHeaderProps) {
</StyledDropDownContainer>
</>
)}
{isMobile && !isMobileSidebarOpen && (
<StyledTwoLineHamburger onClick={() => setIsMobileSidebarOpen(true)} />
)}
{isMobile && isMobileSidebarOpen && (
<Icon
fillColor={Colors.CRUSTA}
name="close-x"
onClick={() => setIsMobileSidebarOpen(false)}
size={IconSize.XXXXL}
/>
)}
{isMobile && (
<MobileSideBar
isOpen={isMobileSidebarOpen}
name="Albin"
userName="albin@appsmith.com"
/>
)}
</StyledPageHeader>
);
}

View File

@ -8,10 +8,11 @@ import ProfileImage from "./ProfileImage";
import ScrollIndicator from "components/ads/ScrollIndicator";
import { OrgUser } from "constants/orgConstants";
import { getUserApplicationsOrgsList } from "selectors/applicationSelectors";
import { useIsMobileDevice } from "utils/hooks/useDeviceDetect";
const UserImageContainer = styled.div`
const UserImageContainer = styled.div<{ isMobile?: boolean }>`
display: flex;
margin-right: 24px;
margin-right: ${({ isMobile }) => (isMobile ? 0 : 24)}px;
.org-share-user-icons {
cursor: default;
@ -67,6 +68,7 @@ export default function SharedUserList(props: any) {
const currentUser = useSelector(getCurrentUser);
const scrollWrapperRef = React.createRef<HTMLUListElement>();
const userOrgs = useSelector(getUserApplicationsOrgsList);
const isMobile = useIsMobileDevice();
const allUsers = useMemo(() => {
const org: any = userOrgs.find((organizationObject: any) => {
const { organization } = organizationObject;
@ -77,7 +79,7 @@ export default function SharedUserList(props: any) {
return userRoles || [];
}, [userOrgs]);
return (
<UserImageContainer>
<UserImageContainer isMobile={isMobile}>
{allUsers.slice(0, 5).map((el: OrgUser) => (
<Popover
boundary="viewport"

View File

@ -8,16 +8,21 @@ import Button, { Size } from "components/ads/Button";
import { useSelector } from "react-redux";
import { getIsFetchingApplications } from "selectors/applicationSelectors";
import { Indices } from "constants/Layers";
import { useIsMobileDevice } from "utils/hooks/useDeviceDetect";
const SubHeaderWrapper = styled.div`
width: 250px;
const SubHeaderWrapper = styled.div<{
isMobile?: boolean;
}>`
width: ${({ isMobile }) => (isMobile ? `100%` : `250px`)};
display: flex;
justify-content: space-between;
position: fixed;
position: ${({ isMobile }) => (isMobile ? `relative` : `fixed`)};
background: ${(props) => props.theme.colors.homepageBackground};
top: 2px;
left: ${(props) => props.theme.homePage.sidebar + 24}px;
z-index: ${Indices.Layer9};
left: ${(props) =>
props.isMobile ? 0 : props.theme.homePage.sidebar + 24}px;
z-index: ${({ isMobile }) => (isMobile ? Indices.Layer8 : Indices.Layer9)};
${({ isMobile }) => isMobile && `padding: 12px 16px;`}
`;
const SearchContainer = styled.div`
flex-grow: 1;
@ -52,6 +57,7 @@ type SubHeaderProps = {
export function ApplicationsSubHeader(props: SubHeaderProps) {
const isFetchingApplications = useSelector(getIsFetchingApplications);
const isMobile = useIsMobileDevice();
const query =
props.search &&
props.search.queryFn &&
@ -61,17 +67,19 @@ export function ApplicationsSubHeader(props: SubHeaderProps) {
);
return (
<SubHeaderWrapper>
<SubHeaderWrapper isMobile={isMobile}>
<SearchContainer>
{props.search && (
<ControlGroup>
<SearchInput
border={isMobile}
cypressSelector={"t--application-search-input"}
defaultValue={props.search.defaultValue}
disabled={isFetchingApplications}
onChange={query || noop}
placeholder={props.search.placeholder}
variant={SearchVariant.BACKGROUND}
width={isMobile ? "100%" : "228px"}
/>
</ControlGroup>
)}

View File

@ -22,6 +22,7 @@
--appsmith-color-black-200 : #E7E7E7;
--appsmith-color-black-100 : #F1F1F1;
--appsmith-color-black-50 : #F8F8F8;
--appsmith-color-black-0 : #FFFFFF;
/* green */
--appsmith-color-green-500 : #03B364;

View File

@ -6,4 +6,8 @@
/* input */
--appsmith-input-focus-border-color: var(--appsmith-color-black-900);
/* search input */
--appsmith-search-input-focus-mobile-border-color: var(--appsmith-color-black-900);
--appsmith-search-input-mobile-border-color: var(--appsmith-color-black-400);
}

View File

@ -0,0 +1,24 @@
import { useMediaQuery } from "react-responsive";
import {
MOBILE_MAX_WIDTH,
TABLET_MIN_WIDTH,
TABLET_MAX_WIDTH,
DESKTOP_MIN_WIDTH,
} from "constants/AppConstants";
export function useIsMobileDevice() {
return useMediaQuery({ maxWidth: MOBILE_MAX_WIDTH });
}
export function useIsTabletDevice() {
return useMediaQuery({
minWidth: TABLET_MIN_WIDTH,
maxWidth: TABLET_MAX_WIDTH,
});
}
export function useIsDesktopDevice() {
return useMediaQuery({
minWidth: DESKTOP_MIN_WIDTH,
});
}