feat: tabs for teams in mobile (#6685)
* fix: padding * feat: add teams tab component * fix: add image to type * fix: current path check * fix: add avatar prop * feat: add teams tab for mobile * fix: padding * fix: background and padding x * fix: empty logic * fix: conditions * fix: bg --------- Co-authored-by: Alan <alannnc@gmail.com>
This commit is contained in:
parent
77d45a6ec2
commit
3b8f436bdf
|
@ -2,7 +2,8 @@ import { useAutoAnimate } from "@formkit/auto-animate/react";
|
|||
import { GetServerSidePropsContext } from "next";
|
||||
import Link from "next/link";
|
||||
import { useRouter } from "next/router";
|
||||
import React, { Fragment, useEffect, useState } from "react";
|
||||
import { FC, useEffect, useState, memo } from "react";
|
||||
import { z } from "zod";
|
||||
|
||||
import {
|
||||
CreateEventTypeButton,
|
||||
|
@ -11,6 +12,8 @@ import {
|
|||
import Shell from "@calcom/features/shell/Shell";
|
||||
import { APP_NAME, CAL_URL, WEBAPP_URL } from "@calcom/lib/constants";
|
||||
import { useLocale } from "@calcom/lib/hooks/useLocale";
|
||||
import useMediaQuery from "@calcom/lib/hooks/useMediaQuery";
|
||||
import { useTypedQuery } from "@calcom/lib/hooks/useTypedQuery";
|
||||
import { RouterOutputs, trpc, TRPCClientError } from "@calcom/trpc/react";
|
||||
import {
|
||||
Badge,
|
||||
|
@ -31,6 +34,7 @@ import {
|
|||
Avatar,
|
||||
AvatarGroup,
|
||||
Tooltip,
|
||||
HorizontalTabs,
|
||||
} from "@calcom/ui";
|
||||
import {
|
||||
FiArrowDown,
|
||||
|
@ -74,6 +78,40 @@ interface EventTypeListProps {
|
|||
types: EventType[];
|
||||
}
|
||||
|
||||
interface MobileTeamsTabProps {
|
||||
eventTypeGroups: EventTypeGroups;
|
||||
}
|
||||
|
||||
const querySchema = z.object({
|
||||
teamId: z.nullable(z.coerce.number()).optional().default(null),
|
||||
});
|
||||
|
||||
const MobileTeamsTab: FC<MobileTeamsTabProps> = (props) => {
|
||||
const { eventTypeGroups } = props;
|
||||
|
||||
const tabs = eventTypeGroups.map((item) => ({
|
||||
name: item.profile.name ?? "",
|
||||
href: item.teamId ? `/event-types?teamId=${item.teamId}` : "/event-types",
|
||||
avatar: item.profile.image ?? `${WEBAPP_URL}/${item.profile.slug}/avatar.png`,
|
||||
}));
|
||||
const { data } = useTypedQuery(querySchema);
|
||||
const events = eventTypeGroups.filter((item) => item.teamId === data.teamId);
|
||||
|
||||
return (
|
||||
<div>
|
||||
<HorizontalTabs tabs={tabs} />
|
||||
{events.length && (
|
||||
<EventTypeList
|
||||
types={events[0].eventTypes}
|
||||
group={events[0]}
|
||||
groupIndex={0}
|
||||
readOnly={events[0].metadata.readOnly}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
const Item = ({ type, group, readOnly }: { type: EventType; group: EventTypeGroup; readOnly: boolean }) => {
|
||||
const { t } = useLocale();
|
||||
|
||||
|
@ -105,7 +143,7 @@ const Item = ({ type, group, readOnly }: { type: EventType; group: EventTypeGrou
|
|||
);
|
||||
};
|
||||
|
||||
const MemoizedItem = React.memo(Item);
|
||||
const MemoizedItem = memo(Item);
|
||||
|
||||
export const EventTypeList = ({ group, groupIndex, readOnly, types }: EventTypeListProps): JSX.Element => {
|
||||
const { t } = useLocale();
|
||||
|
@ -589,6 +627,8 @@ const WithQuery = withQuery(trpc.viewer.eventTypes.getByViewer);
|
|||
const EventTypesPage = () => {
|
||||
const { t } = useLocale();
|
||||
|
||||
const isMobile = useMediaQuery("(max-width: 768px)");
|
||||
|
||||
return (
|
||||
<div>
|
||||
<Shell heading={t("event_types_page_title")} subtitle={t("event_types_page_subtitle")} CTA={<CTA />}>
|
||||
|
@ -596,26 +636,40 @@ const EventTypesPage = () => {
|
|||
customLoader={<SkeletonLoader />}
|
||||
success={({ data }) => (
|
||||
<>
|
||||
{data.eventTypeGroups.map((group, index) => (
|
||||
<Fragment key={group.profile.slug}>
|
||||
{/* hide list heading when there is only one (current user) */}
|
||||
{(data.eventTypeGroups.length !== 1 || group.teamId) && (
|
||||
<EventTypeListHeading
|
||||
profile={group.profile}
|
||||
membershipCount={group.metadata.membershipCount}
|
||||
teamId={group.teamId}
|
||||
/>
|
||||
)}
|
||||
<EventTypeList
|
||||
types={group.eventTypes}
|
||||
group={group}
|
||||
groupIndex={index}
|
||||
readOnly={group.metadata.readOnly}
|
||||
/>
|
||||
</Fragment>
|
||||
))}
|
||||
{data.eventTypeGroups.length > 1 ? (
|
||||
<>
|
||||
{isMobile ? (
|
||||
<MobileTeamsTab eventTypeGroups={data.eventTypeGroups} />
|
||||
) : (
|
||||
data.eventTypeGroups.map((group, index) => (
|
||||
<div className="flex flex-col" key={group.profile.slug}>
|
||||
<EventTypeListHeading
|
||||
profile={group.profile}
|
||||
membershipCount={group.metadata.membershipCount}
|
||||
teamId={group.teamId}
|
||||
/>
|
||||
|
||||
<EventTypeList
|
||||
types={group.eventTypes}
|
||||
group={group}
|
||||
groupIndex={index}
|
||||
readOnly={group.metadata.readOnly}
|
||||
/>
|
||||
</div>
|
||||
))
|
||||
)}
|
||||
</>
|
||||
) : data.eventTypeGroups.length === 1 ? (
|
||||
<EventTypeList
|
||||
types={data.eventTypeGroups[0].eventTypes}
|
||||
group={data.eventTypeGroups[0]}
|
||||
groupIndex={0}
|
||||
readOnly={data.eventTypeGroups[0].metadata.readOnly}
|
||||
/>
|
||||
) : (
|
||||
<CreateFirstEventTypeView />
|
||||
)}
|
||||
|
||||
{data.eventTypeGroups.length === 0 && <CreateFirstEventTypeView />}
|
||||
<EmbedDialog />
|
||||
</>
|
||||
)}
|
||||
|
|
|
@ -846,7 +846,7 @@ function MainContainer({
|
|||
<main className="relative z-0 flex-1 bg-white focus:outline-none">
|
||||
{/* show top navigation for md and smaller (tablet and phones) */}
|
||||
{TopNavContainerProp}
|
||||
<div className="max-w-full px-4 py-8 lg:px-12">
|
||||
<div className="max-w-full px-4 py-4 md:py-8 lg:px-12">
|
||||
<ErrorBoundary>
|
||||
{!props.withoutMain ? <ShellMain {...props}>{props.children}</ShellMain> : props.children}
|
||||
</ErrorBoundary>
|
||||
|
|
|
@ -299,6 +299,7 @@ export const eventTypesRouter = router({
|
|||
profile: {
|
||||
slug: typeof user["username"];
|
||||
name: typeof user["name"];
|
||||
image?: string;
|
||||
};
|
||||
metadata: {
|
||||
membershipCount: number;
|
||||
|
|
|
@ -6,6 +6,7 @@ import classNames from "@calcom/lib/classNames";
|
|||
import { useLocale } from "@calcom/lib/hooks/useLocale";
|
||||
import { SVGComponent } from "@calcom/types/SVGComponent";
|
||||
|
||||
import { Avatar } from "../../avatar";
|
||||
import { SkeletonText } from "../../skeleton";
|
||||
|
||||
export type HorizontalTabItemProps = {
|
||||
|
@ -15,12 +16,14 @@ export type HorizontalTabItemProps = {
|
|||
href: string;
|
||||
linkProps?: Omit<ComponentProps<typeof Link>, "href">;
|
||||
icon?: SVGComponent;
|
||||
avatar?: string;
|
||||
};
|
||||
|
||||
const HorizontalTabItem = function ({ name, href, linkProps, ...props }: HorizontalTabItemProps) {
|
||||
const HorizontalTabItem = function ({ name, href, linkProps, avatar, ...props }: HorizontalTabItemProps) {
|
||||
const { t, isLocaleReady } = useLocale();
|
||||
const { asPath } = useRouter();
|
||||
const isCurrent = asPath.startsWith(href);
|
||||
|
||||
const isCurrent = asPath === href;
|
||||
|
||||
return (
|
||||
<Link
|
||||
|
@ -28,8 +31,8 @@ const HorizontalTabItem = function ({ name, href, linkProps, ...props }: Horizon
|
|||
href={href}
|
||||
{...linkProps}
|
||||
className={classNames(
|
||||
isCurrent ? "bg-gray-200 text-gray-900" : " text-gray-600 hover:bg-gray-100 hover:text-gray-900 ",
|
||||
"inline-flex items-center justify-center whitespace-nowrap rounded-[6px] py-[10px] px-4 text-sm font-medium leading-4 md:mb-0",
|
||||
isCurrent ? "bg-gray-100 text-gray-900" : " text-gray-600 hover:bg-gray-100 hover:text-gray-900 ",
|
||||
"inline-flex items-center justify-center whitespace-nowrap rounded-[6px] py-[10px] px-2 text-sm font-medium leading-4 md:mb-0",
|
||||
props.disabled && "pointer-events-none !opacity-30",
|
||||
props.className
|
||||
)}
|
||||
|
@ -45,7 +48,13 @@ const HorizontalTabItem = function ({ name, href, linkProps, ...props }: Horizon
|
|||
aria-hidden="true"
|
||||
/>
|
||||
)}
|
||||
{isLocaleReady ? t(name) : <SkeletonText className="h-4 w-24" />}
|
||||
{isLocaleReady ? (
|
||||
<div className="flex items-center gap-x-2">
|
||||
{avatar && <Avatar size="sm" imageSrc={avatar} alt="avatar" />} {t(name)}
|
||||
</div>
|
||||
) : (
|
||||
<SkeletonText className="h-4 w-24" />
|
||||
)}
|
||||
</Link>
|
||||
);
|
||||
};
|
||||
|
|
Loading…
Reference in New Issue
Block a user