From 39e28f4e1bc41620958b7e7f45751cfe10f1cd0d Mon Sep 17 00:00:00 2001 From: albinAppsmith <87797149+albinAppsmith@users.noreply.github.com> Date: Thu, 17 Feb 2022 22:08:36 +0530 Subject: [PATCH] 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 --- .../assets/icons/ads/two-line-hamburger.svg | 1 + app/client/src/components/ads/Icon.tsx | 2 + app/client/src/components/ads/SearchInput.tsx | 18 +- app/client/src/constants/AppConstants.ts | 5 + app/client/src/constants/Colors.tsx | 1 + .../pages/Applications/ApplicationCard.tsx | 28 ++- app/client/src/pages/Applications/index.tsx | 156 +++++++++++------ app/client/src/pages/common/MobileSidebar.tsx | 165 ++++++++++++++++++ app/client/src/pages/common/PageHeader.tsx | 55 +++++- .../src/pages/common/SharedUserList.tsx | 8 +- app/client/src/pages/common/SubHeader.tsx | 20 ++- app/client/src/theme/colors.css | 1 + app/client/src/theme/defaultTheme.css | 4 + app/client/src/utils/hooks/useDeviceDetect.ts | 24 +++ 14 files changed, 407 insertions(+), 81 deletions(-) create mode 100644 app/client/src/assets/icons/ads/two-line-hamburger.svg create mode 100644 app/client/src/pages/common/MobileSidebar.tsx create mode 100644 app/client/src/utils/hooks/useDeviceDetect.ts diff --git a/app/client/src/assets/icons/ads/two-line-hamburger.svg b/app/client/src/assets/icons/ads/two-line-hamburger.svg new file mode 100644 index 0000000000..b613452efc --- /dev/null +++ b/app/client/src/assets/icons/ads/two-line-hamburger.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/app/client/src/components/ads/Icon.tsx b/app/client/src/components/ads/Icon.tsx index 2f5197f0e1..af35937949 100644 --- a/app/client/src/components/ads/Icon.tsx +++ b/app/client/src/components/ads/Icon.tsx @@ -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: , widget: , workspace: , + hamburger: , }; export const IconCollection = Object.keys(ICON_LOOKUP); diff --git a/app/client/src/components/ads/SearchInput.tsx b/app/client/src/components/ads/SearchInput.tsx index a52b0561c3..44a3f1d6dc 100644 --- a/app/client/src/components/ads/SearchInput.tsx +++ b/app/client/src/components/ads/SearchInput.tsx @@ -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 ( - + ) : null } - width="228px" + width={props.width ? props.width : "228px"} /> ); diff --git a/app/client/src/constants/AppConstants.ts b/app/client/src/constants/AppConstants.ts index e75a8c8fe9..1065e1ab5a 100644 --- a/app/client/src/constants/AppConstants.ts +++ b/app/client/src/constants/AppConstants.ts @@ -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; diff --git a/app/client/src/constants/Colors.tsx b/app/client/src/constants/Colors.tsx index 00bead750a..8b2c1d78c3 100644 --- a/app/client/src/constants/Colors.tsx +++ b/app/client/src/constants/Colors.tsx @@ -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", diff --git a/app/client/src/pages/Applications/ApplicationCard.tsx b/app/client/src/pages/Applications/ApplicationCard.tsx index 09bff99d18..7e76ff99af 100644 --- a/app/client/src/pages/Applications/ApplicationCard.tsx +++ b/app/client/src/pages/Applications/ApplicationCard.tsx @@ -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; }, ) => , )` @@ -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 ( - + @@ -746,7 +764,7 @@ export function ApplicationCard(props: ApplicationCardProps) { appNameText )} - {showOverlay && ( + {showOverlay && !props.isMobile && (
@@ -781,7 +799,7 @@ export function ApplicationCard(props: ApplicationCardProps) { {editedByText()} - {!!moreActionItems.length && ContextMenu} + {!!moreActionItems.length && !props.isMobile && ContextMenu} {showGitBadge && } diff --git a/app/client/src/pages/Applications/index.tsx b/app/client/src/pages/Applications/index.tsx index fd84040843..f99073bf06 100644 --- a/app/client/src/pages/Applications/index.tsx +++ b/app/client/src/pages/Applications/index.tsx @@ -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 ( ` + 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) { > {orgName} @@ -674,8 +701,12 @@ function ApplicationsSection(props: any) { PERMISSION_TYPE.MANAGE_ORGANIZATION, ); return ( - - + + {(currentUser || isFetchingApplications) && OrgMenuTarget({ orgName: organization.name, @@ -708,25 +739,28 @@ function ApplicationsSection(props: any) { !isFetchingApplications && ( - - } - /> + {!isMobile && ( + + } + /> + )} {isPermitted( organization.userPermissions, PERMISSION_TYPE.CREATE_APPLICATION, ) && + !isMobile && !isFetchingApplications && applications.length !== 0 && (