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:
parent
46810e832a
commit
39e28f4e1b
1
app/client/src/assets/icons/ads/two-line-hamburger.svg
Normal file
1
app/client/src/assets/icons/ads/two-line-hamburger.svg
Normal 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 |
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
);
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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",
|
||||
|
|
|
|||
|
|
@ -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 />}
|
||||
|
|
|
|||
|
|
@ -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>There’s 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>
|
||||
|
|
|
|||
165
app/client/src/pages/common/MobileSidebar.tsx
Normal file
165
app/client/src/pages/common/MobileSidebar.tsx
Normal 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>
|
||||
);
|
||||
}
|
||||
|
|
@ -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>
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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"
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
)}
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
||||
}
|
||||
|
|
|
|||
24
app/client/src/utils/hooks/useDeviceDetect.ts
Normal file
24
app/client/src/utils/hooks/useDeviceDetect.ts
Normal 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,
|
||||
});
|
||||
}
|
||||
Loading…
Reference in New Issue
Block a user