feat: Add action‑item support to mobile “More” page and added navigation to refer page (#26431)
* Add action‑item support to mobile “More” page and hide arrow on copy‑link button * updated code * fix code * updated code * remove unused import and formatting --------- Co-authored-by: Dhairyashil <dhairyashil10101010@gmail.com>
This commit is contained in:
co-authored by
Dhairyashil
parent
0beadfa80b
commit
0b0a5478fb
@@ -6,6 +6,7 @@ import {
|
||||
useOrgBranding,
|
||||
type OrganizationBranding,
|
||||
} from "@calcom/features/ee/organizations/context/provider";
|
||||
import { useMobileMoreItems } from "./useMobileMoreItems";
|
||||
import { useIsStandalone } from "@calcom/lib/hooks/useIsStandalone";
|
||||
import classNames from "@calcom/ui/classNames";
|
||||
import { useHasPaidPlan } from "@calcom/web/modules/billing/hooks/useHasPaidPlan";
|
||||
@@ -14,7 +15,11 @@ import UnconfirmedBookingBadge from "../../bookings/components/UnconfirmedBookin
|
||||
import { KBarTrigger } from "../Kbar";
|
||||
import { TeamInviteBadge } from "../TeamInviteBadge";
|
||||
import type { NavigationItemType } from "./NavigationItem";
|
||||
import { NavigationItem, MobileNavigationItem, MobileNavigationMoreItem } from "./NavigationItem";
|
||||
import {
|
||||
NavigationItem,
|
||||
MobileNavigationItem,
|
||||
MobileNavigationMoreItem,
|
||||
} from "./NavigationItem";
|
||||
|
||||
export const MORE_SEPARATOR_NAME = "more";
|
||||
|
||||
@@ -63,7 +68,10 @@ const getNavigationItems = (
|
||||
moreOnMobile: true,
|
||||
isCurrent: ({ pathname: path, item }) => {
|
||||
// During Server rendering path is /v2/apps but on client it becomes /apps(weird..)
|
||||
return (path?.startsWith(item.href) ?? false) && !(path?.includes("routing-forms/") ?? false);
|
||||
return (
|
||||
(path?.startsWith(item.href) ?? false) &&
|
||||
!(path?.includes("routing-forms/") ?? false)
|
||||
);
|
||||
},
|
||||
child: [
|
||||
{
|
||||
@@ -109,7 +117,8 @@ const getNavigationItems = (
|
||||
name: "insights",
|
||||
href: "/insights",
|
||||
icon: "chart-bar",
|
||||
isCurrent: ({ pathname: path, item }) => path?.startsWith(item.href) ?? false,
|
||||
isCurrent: ({ pathname: path, item }) =>
|
||||
path?.startsWith(item.href) ?? false,
|
||||
moreOnMobile: true,
|
||||
child: hasAllInsightsAccess
|
||||
? [
|
||||
@@ -121,18 +130,21 @@ const getNavigationItems = (
|
||||
{
|
||||
name: "routing",
|
||||
href: "/insights/routing",
|
||||
isCurrent: ({ pathname: path }) => path?.startsWith("/insights/routing") ?? false,
|
||||
isCurrent: ({ pathname: path }) =>
|
||||
path?.startsWith("/insights/routing") ?? false,
|
||||
},
|
||||
{
|
||||
name: "router_position",
|
||||
href: "/insights/router-position",
|
||||
isCurrent: ({ pathname: path }) => path?.startsWith("/insights/router-position") ?? false,
|
||||
isCurrent: ({ pathname: path }) =>
|
||||
path?.startsWith("/insights/router-position") ?? false,
|
||||
},
|
||||
{
|
||||
name: "call_history",
|
||||
href: "/insights/call-history",
|
||||
// icon: "phone",
|
||||
isCurrent: ({ pathname: path }) => path?.startsWith("/insights/call-history") ?? false,
|
||||
isCurrent: ({ pathname: path }) =>
|
||||
path?.startsWith("/insights/call-history") ?? false,
|
||||
},
|
||||
]
|
||||
: [
|
||||
@@ -205,19 +217,34 @@ const useNavigationItems = (isPlatformNavigation = false) => {
|
||||
? getNavigationItems(orgBranding, hasAllInsightsAccess)
|
||||
: platformNavigationItems;
|
||||
|
||||
const desktopNavigationItems = items.filter((item) => item.name !== MORE_SEPARATOR_NAME);
|
||||
const desktopNavigationItems = items.filter(
|
||||
(item) => item.name !== MORE_SEPARATOR_NAME
|
||||
);
|
||||
const mobileNavigationBottomItems = items.filter(
|
||||
(item) => (!item.moreOnMobile && !item.onlyDesktop) || item.name === MORE_SEPARATOR_NAME
|
||||
(item) =>
|
||||
(!item.moreOnMobile && !item.onlyDesktop) ||
|
||||
item.name === MORE_SEPARATOR_NAME
|
||||
);
|
||||
const mobileNavigationMoreItems = items.filter(
|
||||
(item) => item.moreOnMobile && !item.onlyDesktop && item.name !== MORE_SEPARATOR_NAME
|
||||
(item) =>
|
||||
item.moreOnMobile &&
|
||||
!item.onlyDesktop &&
|
||||
item.name !== MORE_SEPARATOR_NAME
|
||||
);
|
||||
|
||||
return { desktopNavigationItems, mobileNavigationBottomItems, mobileNavigationMoreItems };
|
||||
return {
|
||||
desktopNavigationItems,
|
||||
mobileNavigationBottomItems,
|
||||
mobileNavigationMoreItems,
|
||||
};
|
||||
}, [hasPaidPlan, isPending, isPlatformNavigation, orgBranding]);
|
||||
};
|
||||
|
||||
export const Navigation = ({ isPlatformNavigation = false }: { isPlatformNavigation?: boolean }) => {
|
||||
export const Navigation = ({
|
||||
isPlatformNavigation = false,
|
||||
}: {
|
||||
isPlatformNavigation?: boolean;
|
||||
}) => {
|
||||
const { desktopNavigationItems } = useNavigationItems(isPlatformNavigation);
|
||||
|
||||
return (
|
||||
@@ -243,9 +270,14 @@ export function MobileNavigationContainer({
|
||||
return <MobileNavigation isPlatformNavigation={isPlatformNavigation} />;
|
||||
}
|
||||
|
||||
const MobileNavigation = ({ isPlatformNavigation = false }: { isPlatformNavigation?: boolean }) => {
|
||||
const MobileNavigation = ({
|
||||
isPlatformNavigation = false,
|
||||
}: {
|
||||
isPlatformNavigation?: boolean;
|
||||
}) => {
|
||||
const isEmbed = useIsEmbed();
|
||||
const { mobileNavigationBottomItems } = useNavigationItems(isPlatformNavigation);
|
||||
const { mobileNavigationBottomItems } =
|
||||
useNavigationItems(isPlatformNavigation);
|
||||
|
||||
return (
|
||||
<>
|
||||
@@ -253,7 +285,8 @@ const MobileNavigation = ({ isPlatformNavigation = false }: { isPlatformNavigati
|
||||
className={classNames(
|
||||
"pwa:pb-[max(0.25rem,env(safe-area-inset-bottom))] pwa:-mx-2 bg-cal-muted/40 border-subtle fixed bottom-0 left-0 z-30 flex w-full border-t px-1 shadow backdrop-blur-md md:hidden",
|
||||
isEmbed && "hidden"
|
||||
)}>
|
||||
)}
|
||||
>
|
||||
{mobileNavigationBottomItems.map((item) => (
|
||||
<MobileNavigationItem key={item.name} item={item} />
|
||||
))}
|
||||
@@ -266,10 +299,13 @@ const MobileNavigation = ({ isPlatformNavigation = false }: { isPlatformNavigati
|
||||
|
||||
export const MobileNavigationMoreItems = () => {
|
||||
const { mobileNavigationMoreItems } = useNavigationItems();
|
||||
const bottomItems = useMobileMoreItems();
|
||||
|
||||
const allItems = [...mobileNavigationMoreItems, ...bottomItems];
|
||||
|
||||
return (
|
||||
<ul className="border-subtle mt-2 rounded-md border">
|
||||
{mobileNavigationMoreItems.map((item) => (
|
||||
{allItems.map((item) => (
|
||||
<MobileNavigationMoreItem key={item.name} item={item} />
|
||||
))}
|
||||
</ul>
|
||||
|
||||
@@ -27,10 +27,7 @@ const usePersistedExpansionState = (itemName: string) => {
|
||||
|
||||
const setPersistedExpansion = (expanded: boolean) => {
|
||||
setIsExpanded(expanded);
|
||||
sessionStorage.setItem(
|
||||
`nav-expansion-${itemName}`,
|
||||
JSON.stringify(expanded)
|
||||
);
|
||||
sessionStorage.setItem(`nav-expansion-${itemName}`, JSON.stringify(expanded));
|
||||
};
|
||||
|
||||
return [isExpanded, setPersistedExpansion] as const;
|
||||
@@ -67,16 +64,8 @@ export type NavigationItemType = {
|
||||
}) => boolean;
|
||||
};
|
||||
|
||||
const defaultIsCurrent: NavigationItemType["isCurrent"] = ({
|
||||
isChild,
|
||||
item,
|
||||
pathname,
|
||||
}) => {
|
||||
return isChild
|
||||
? item.href === pathname
|
||||
: item.href
|
||||
? pathname?.startsWith(item.href) ?? false
|
||||
: false;
|
||||
const defaultIsCurrent: NavigationItemType["isCurrent"] = ({ isChild, item, pathname }) => {
|
||||
return isChild ? item.href === pathname : item.href ? (pathname?.startsWith(item.href) ?? false) : false;
|
||||
};
|
||||
|
||||
export const NavigationItem: React.FC<{
|
||||
@@ -87,12 +76,9 @@ export const NavigationItem: React.FC<{
|
||||
const { item, isChild } = props;
|
||||
const { t, isLocaleReady } = useLocale();
|
||||
const pathname = usePathname();
|
||||
const isCurrent: NavigationItemType["isCurrent"] =
|
||||
item.isCurrent || defaultIsCurrent;
|
||||
const isCurrent: NavigationItemType["isCurrent"] = item.isCurrent || defaultIsCurrent;
|
||||
const current = isCurrent({ isChild: !!isChild, item, pathname });
|
||||
const shouldDisplayNavigationItem = useShouldDisplayNavigationItem(
|
||||
props.item
|
||||
);
|
||||
const shouldDisplayNavigationItem = useShouldDisplayNavigationItem(props.item);
|
||||
const [isExpanded, setIsExpanded] = usePersistedExpansionState(item.name);
|
||||
|
||||
const isTablet = useMediaQuery("(max-width: 1024px)");
|
||||
@@ -102,12 +88,8 @@ export const NavigationItem: React.FC<{
|
||||
|
||||
const hasChildren = item.child && item.child.length > 0;
|
||||
const hasActiveChild =
|
||||
hasChildren &&
|
||||
item.child?.some((child) =>
|
||||
isCurrent({ isChild: true, item: child, pathname })
|
||||
);
|
||||
const shouldShowChildren =
|
||||
isExpanded || hasActiveChild || isCurrent({ pathname, isChild, item });
|
||||
hasChildren && item.child?.some((child) => isCurrent({ isChild: true, item: child, pathname }));
|
||||
const shouldShowChildren = isExpanded || hasActiveChild || isCurrent({ pathname, isChild, item });
|
||||
const shouldShowChevron = hasChildren && !hasActiveChild;
|
||||
const isParentNavigationItem = hasChildren && !isChild;
|
||||
|
||||
@@ -151,8 +133,7 @@ export const NavigationItem: React.FC<{
|
||||
childIsCurrent
|
||||
? "bg-emphasis text-white"
|
||||
: "hover:bg-emphasis text-mute hover:text-emphasis"
|
||||
)}
|
||||
>
|
||||
)}>
|
||||
{t(childItem.name)}
|
||||
</Link>
|
||||
);
|
||||
@@ -163,8 +144,7 @@ export const NavigationItem: React.FC<{
|
||||
t(item.name)
|
||||
)
|
||||
}
|
||||
className="lg:hidden"
|
||||
>
|
||||
className="lg:hidden">
|
||||
<button
|
||||
data-test-id={item.name}
|
||||
aria-label={t(item.name)}
|
||||
@@ -185,8 +165,7 @@ export const NavigationItem: React.FC<{
|
||||
isLocaleReady
|
||||
? "hover:bg-subtle todesktop:[&[aria-current='page']]:bg-emphasis todesktop:hover:bg-transparent hover:text-emphasis"
|
||||
: ""
|
||||
)}
|
||||
>
|
||||
)}>
|
||||
{item.icon && (
|
||||
<div className="relative">
|
||||
<Icon
|
||||
@@ -208,8 +187,7 @@ export const NavigationItem: React.FC<{
|
||||
{isLocaleReady ? (
|
||||
<span
|
||||
className="hidden w-full justify-between truncate text-ellipsis lg:flex"
|
||||
data-testid={`${item.name}-test`}
|
||||
>
|
||||
data-testid={`${item.name}-test`}>
|
||||
{t(item.name)}
|
||||
{item.badge && item.badge}
|
||||
</span>
|
||||
@@ -239,17 +217,14 @@ export const NavigationItem: React.FC<{
|
||||
: `[&[aria-current='page']]:bg-emphasis`,
|
||||
isChild
|
||||
? `[&[aria-current='page']]:text-emphasis [&[aria-current='page']]:bg-emphasis hidden h-8 pl-16 lg:flex lg:pl-11 ${
|
||||
props.index === 0
|
||||
? "mt-0"
|
||||
: "mt-1 hover:mt-1 [&[aria-current='page']]:mt-1"
|
||||
props.index === 0 ? "mt-0" : "mt-1 hover:mt-1 [&[aria-current='page']]:mt-1"
|
||||
}`
|
||||
: "[&[aria-current='page']]:text-emphasis mt-0.5 text-sm md:justify-center lg:justify-start",
|
||||
isLocaleReady
|
||||
? "hover:bg-subtle todesktop:[&[aria-current='page']]:bg-emphasis todesktop:hover:bg-transparent hover:text-emphasis"
|
||||
: ""
|
||||
)}
|
||||
aria-current={current ? "page" : undefined}
|
||||
>
|
||||
aria-current={current ? "page" : undefined}>
|
||||
{item.icon && (
|
||||
<Icon
|
||||
name={item.isLoading ? "rotate-cw" : item.icon}
|
||||
@@ -264,8 +239,7 @@ export const NavigationItem: React.FC<{
|
||||
{isLocaleReady ? (
|
||||
<span
|
||||
className="hidden w-full justify-between truncate text-ellipsis lg:flex"
|
||||
data-testid={`${item.name}-test`}
|
||||
>
|
||||
data-testid={`${item.name}-test`}>
|
||||
{t(item.name)}
|
||||
{item.badge && item.badge}
|
||||
</span>
|
||||
@@ -290,6 +264,7 @@ export const NavigationItem: React.FC<{
|
||||
</div>
|
||||
)}
|
||||
</Fragment>
|
||||
|
||||
);
|
||||
};
|
||||
|
||||
@@ -300,12 +275,9 @@ export const MobileNavigationItem: React.FC<{
|
||||
const { item, isChild } = props;
|
||||
const pathname = usePathname();
|
||||
const { t, isLocaleReady } = useLocale();
|
||||
const isCurrent: NavigationItemType["isCurrent"] =
|
||||
item.isCurrent || defaultIsCurrent;
|
||||
const isCurrent: NavigationItemType["isCurrent"] = item.isCurrent || defaultIsCurrent;
|
||||
const current = isCurrent({ isChild: !!isChild, item, pathname });
|
||||
const shouldDisplayNavigationItem = useShouldDisplayNavigationItem(
|
||||
props.item
|
||||
);
|
||||
const shouldDisplayNavigationItem = useShouldDisplayNavigationItem(props.item);
|
||||
|
||||
if (!shouldDisplayNavigationItem) return null;
|
||||
return (
|
||||
@@ -314,8 +286,7 @@ export const MobileNavigationItem: React.FC<{
|
||||
href={item.href}
|
||||
target={item.target}
|
||||
className="[&[aria-current='page']]:text-emphasis hover:text-default text-muted bg-transparent! relative my-2 min-w-0 flex-1 overflow-hidden rounded-md p-1 text-center text-xs font-medium focus:z-10 sm:text-sm"
|
||||
aria-current={current ? "page" : undefined}
|
||||
>
|
||||
aria-current={current ? "page" : undefined}>
|
||||
{item.badge && <div className="absolute right-1 top-1">{item.badge}</div>}
|
||||
{item.icon && (
|
||||
<Icon
|
||||
@@ -325,11 +296,7 @@ export const MobileNavigationItem: React.FC<{
|
||||
aria-current={current ? "page" : undefined}
|
||||
/>
|
||||
)}
|
||||
{isLocaleReady ? (
|
||||
<span className="block truncate">{t(item.name)}</span>
|
||||
) : (
|
||||
<SkeletonText />
|
||||
)}
|
||||
{isLocaleReady ? <span className="block truncate">{t(item.name)}</span> : <SkeletonText />}
|
||||
</Link>
|
||||
);
|
||||
};
|
||||
@@ -340,14 +307,25 @@ export const MobileNavigationMoreItem: React.FC<{
|
||||
}> = (props) => {
|
||||
const { item } = props;
|
||||
const { t, isLocaleReady } = useLocale();
|
||||
const shouldDisplayNavigationItem = useShouldDisplayNavigationItem(
|
||||
props.item
|
||||
);
|
||||
const shouldDisplayNavigationItem = useShouldDisplayNavigationItem(props.item);
|
||||
const [isExpanded, setIsExpanded] = usePersistedExpansionState(item.name);
|
||||
|
||||
if (!shouldDisplayNavigationItem) return null;
|
||||
|
||||
const hasChildren = item.child && item.child.length > 0;
|
||||
const isActionItem = !item.href && item.onClick;
|
||||
|
||||
const itemContent = (
|
||||
<>
|
||||
<span className="text-default flex items-center font-semibold ">
|
||||
{item.icon && (
|
||||
<Icon name={item.icon} className="h-5 w-5 shrink-0 ltr:mr-3 rtl:ml-3" aria-hidden="true" />
|
||||
)}
|
||||
{isLocaleReady ? t(item.name) : <SkeletonText />}
|
||||
</span>
|
||||
{!isActionItem && <Icon name="arrow-right" className="text-subtle h-5 w-5" />}
|
||||
</>
|
||||
);
|
||||
|
||||
return (
|
||||
<li className="border-subtle border-b last:border-b-0" key={item.name}>
|
||||
@@ -355,22 +333,14 @@ export const MobileNavigationMoreItem: React.FC<{
|
||||
<>
|
||||
<button
|
||||
onClick={() => setIsExpanded(!isExpanded)}
|
||||
className="hover:bg-subtle flex w-full items-center justify-between p-5 text-left transition"
|
||||
>
|
||||
className="hover:bg-subtle flex w-full items-center justify-between p-5 text-left transition">
|
||||
<span className="text-default flex items-center font-semibold">
|
||||
{item.icon && (
|
||||
<Icon
|
||||
name={item.icon}
|
||||
className="h-5 w-5 shrink-0 ltr:mr-3 rtl:ml-3"
|
||||
aria-hidden="true"
|
||||
/>
|
||||
<Icon name={item.icon} className="h-5 w-5 shrink-0 ltr:mr-3 rtl:ml-3" aria-hidden="true" />
|
||||
)}
|
||||
{isLocaleReady ? t(item.name) : <SkeletonText />}
|
||||
</span>
|
||||
<Icon
|
||||
name={isExpanded ? "chevron-up" : "chevron-down"}
|
||||
className="text-subtle h-5 w-5"
|
||||
/>
|
||||
<Icon name={isExpanded ? "chevron-up" : "chevron-down"} className="text-subtle h-5 w-5" />
|
||||
</button>
|
||||
<div
|
||||
className={classNames(
|
||||
@@ -396,23 +366,20 @@ export const MobileNavigationMoreItem: React.FC<{
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</>
|
||||
) : isActionItem ? (
|
||||
<button
|
||||
onClick={item.onClick}
|
||||
className="hover:bg-subtle flex w-full items-center justify-between p-5 text-left transition">
|
||||
{itemContent}
|
||||
</button>
|
||||
) : (
|
||||
<Link
|
||||
href={item.href}
|
||||
className="hover:bg-subtle flex items-center justify-between p-5 transition"
|
||||
>
|
||||
<span className="text-default flex items-center font-semibold ">
|
||||
{item.icon && (
|
||||
<Icon
|
||||
name={item.icon}
|
||||
className="h-5 w-5 shrink-0 ltr:mr-3 rtl:ml-3"
|
||||
aria-hidden="true"
|
||||
/>
|
||||
)}
|
||||
{isLocaleReady ? t(item.name) : <SkeletonText />}
|
||||
</span>
|
||||
<Icon name="arrow-right" className="text-subtle h-5 w-5" />
|
||||
target={item.target}
|
||||
className="hover:bg-subtle flex items-center justify-between p-5 transition">
|
||||
{itemContent}
|
||||
</Link>
|
||||
)}
|
||||
</li>
|
||||
|
||||
@@ -0,0 +1,23 @@
|
||||
import { getBookerBaseUrlSync } from "@calcom/features/ee/organizations/lib/getBookerBaseUrlSync";
|
||||
import { useBottomNavItems } from "../useBottomNavItems";
|
||||
import { UserPermissionRole } from "@calcom/prisma/enums";
|
||||
import type { NavigationItemType } from "./NavigationItem";
|
||||
import { useSession } from "next-auth/react";
|
||||
|
||||
export function useMobileMoreItems(): NavigationItemType[] {
|
||||
const { data: session } = useSession();
|
||||
const user = session?.user;
|
||||
const isAdmin = user?.role === UserPermissionRole.ADMIN;
|
||||
const publicPageUrl = `${getBookerBaseUrlSync(user?.org?.slug ?? null)}/${user?.orgAwareUsername ?? user?.username}`;
|
||||
|
||||
const bottomNavItems = useBottomNavItems({
|
||||
publicPageUrl,
|
||||
isAdmin,
|
||||
user,
|
||||
});
|
||||
|
||||
const filteredBottomNavItems = bottomNavItems.filter(
|
||||
(item: NavigationItemType) => item.name !== "settings"
|
||||
);
|
||||
return filteredBottomNavItems;
|
||||
}
|
||||
Reference in New Issue
Block a user