Simplifies Vertical and Horizontal tabs

This commit is contained in:
zomars
2022-09-19 00:58:28 +01:00
parent 25711ef18e
commit 588748d78e
9 changed files with 134 additions and 239 deletions
+25 -47
View File
@@ -8,8 +8,7 @@ import { useLocale } from "@calcom/lib/hooks/useLocale";
import { Dialog, DialogClose, DialogContent } from "@calcom/ui/Dialog";
import { Icon } from "@calcom/ui/Icon";
import { InputLeading, Label, TextArea, TextField } from "@calcom/ui/form/fields";
import { HorizontalTabs, showToast } from "@calcom/ui/v2";
import { Button, Switch } from "@calcom/ui/v2";
import { Button, HorizontalTabs, showToast, Switch } from "@calcom/ui/v2";
import { EMBED_LIB_URL, WEBAPP_URL } from "@lib/config/constants";
@@ -452,7 +451,7 @@ const embeds: {
const tabs = [
{
name: "HTML",
embedTabName: "embed-code",
href: "embedTabName=embed-code",
icon: Icon.FiCode,
type: "code",
Component: forwardRef<
@@ -503,7 +502,7 @@ ${getEmbedTypeSpecificString({ embedFramework: "HTML", embedType, calLink, previ
},
{
name: "React",
embedTabName: "embed-react",
href: "embedTabName=embed-react",
icon: Icon.FiCode,
type: "code",
Component: forwardRef<
@@ -543,7 +542,7 @@ ${getEmbedTypeSpecificString({ embedFramework: "react", embedType, calLink, prev
},
{
name: "Preview",
embedTabName: "embed-preview",
href: "embedTabName=embed-preview",
icon: Icon.FiEye,
type: "iframe",
Component: forwardRef<
@@ -606,12 +605,9 @@ const ChooseEmbedTypesDialogContent = () => {
key={index}
data-testid={embed.type}
onClick={() => {
router.push({
query: {
...router.query,
embedType: embed.type,
},
});
const newQuery = new URLSearchParams(router.asPath);
newQuery.set("embedType", embed.type);
router.push(`${router.asPath.split("?")[0]}?${newQuery.toString()}`);
}}>
<div className="order-none box-border flex-none rounded-sm border border-solid bg-white">
{embed.illustration}
@@ -635,6 +631,13 @@ const EmbedTypeCodeAndPreviewDialogContent = ({
const { t } = useLocale();
const router = useRouter();
const iframeRef = useRef<HTMLIFrameElement>(null);
const s = (href: string) => {
const searchParams = new URLSearchParams(router.asPath);
const [a, b] = href.split("=");
searchParams.set(a, b);
return `${router.asPath.split("?")[0]}?${searchParams.toString()}`;
};
const parsedTabs = tabs.map((t) => ({ ...t, href: s(t.href) }));
const embedCodeRefs: Record<typeof tabs[0]["name"], RefObject<HTMLTextAreaElement>> = {};
tabs
.filter((tab) => tab.type === "code")
@@ -676,16 +679,6 @@ const EmbedTypeCodeAndPreviewDialogContent = ({
});
};
// Use embed-code as default tab
if (!router.query.embedTabName) {
router.query.embedTabName = "embed-code";
router.push({
query: {
...router.query,
},
});
}
if (!embed || !embedUrl) {
close();
return null;
@@ -785,14 +778,10 @@ const EmbedTypeCodeAndPreviewDialogContent = ({
<h3 className="mb-2 flex text-xl font-bold leading-6 text-gray-900" id="modal-title">
<button
onClick={() => {
const newQuery = { ...router.query };
delete newQuery.embedType;
delete newQuery.embedTabName;
router.push({
query: {
...newQuery,
},
});
const newQuery = new URLSearchParams(router.asPath);
newQuery.delete("embedType");
newQuery.delete("embedTabName");
router.push(`${router.asPath.split("?")[0]}?${newQuery.toString()}`);
}}>
<Icon.FiArrowLeft className="mr-4 w-4" />
</button>
@@ -1041,14 +1030,12 @@ const EmbedTypeCodeAndPreviewDialogContent = ({
</div>
</div>
<div className="flex w-2/3 flex-col p-6">
<HorizontalTabs tabNameKey="embedTabName" data-testid="embed-tabs" tabs={tabs} />
<HorizontalTabs data-testid="embed-tabs" tabs={parsedTabs} linkProps={{ shallow: true }} />
{tabs.map((tab) => {
return (
<div
key={tab.embedTabName}
className={classNames(
router.query.embedTabName === tab.embedTabName ? "flex flex-grow flex-col" : "hidden"
)}>
key={tab.href}
className={classNames(router.asPath === tab.href ? "flex flex-grow flex-col" : "hidden")}>
<div className="flex h-[55vh] flex-grow flex-col">
{tab.type === "code" ? (
<tab.Component
@@ -1133,19 +1120,10 @@ export const EmbedButton = <T extends React.ElementType>({
const router = useRouter();
className = classNames(className, "hidden lg:inline-flex");
const openEmbedModal = () => {
const query = {
...router.query,
dialog: "embed",
embedUrl,
};
router.push(
{
pathname: router.pathname,
query,
},
undefined,
{ shallow: true }
);
const searchParams = new URLSearchParams(router.asPath);
searchParams.set("dialog", "embed");
searchParams.set("embedUrl", embedUrl);
router.push(`${router.asPath.split("?")[0]}?${searchParams.toString()}`, undefined, { shallow: true });
};
const Component = as ?? Button;
@@ -1,6 +1,6 @@
import { useRouter } from "next/router";
import { TFunction } from "next-i18next";
import { EventTypeSetupInfered, FormValues } from "pages/v2/event-types/[type]";
import { useEffect, useMemo, useState } from "react";
import { useMemo, useState } from "react";
import { Loader } from "react-feather";
import { UseFormReturn } from "react-hook-form";
@@ -9,20 +9,18 @@ import { CAL_URL } from "@calcom/lib/constants";
import { useLocale } from "@calcom/lib/hooks/useLocale";
import { HttpError } from "@calcom/lib/http-error";
import { trpc, TRPCClientError } from "@calcom/trpc/react";
import ConfirmationDialogContent from "@calcom/ui/ConfirmationDialogContent";
import { Icon } from "@calcom/ui/Icon";
import {
Button,
ButtonGroup,
Tooltip,
showToast,
VerticalTabItemProps,
VerticalTabs,
HorizontalTabs,
Switch,
Label,
HorizontalTabItemProps,
showToast,
Switch,
Tooltip,
VerticalTabs,
} from "@calcom/ui/v2";
import ConfirmationDialogContent from "@calcom/ui/v2/core/ConfirmationDialogContent";
import { Dialog } from "@calcom/ui/v2/core/Dialog";
import Dropdown, {
DropdownMenuContent,
@@ -46,6 +44,59 @@ type Props = {
formMethods: UseFormReturn<FormValues>;
};
function getNavigation(props: {
t: TFunction;
eventType: Props["eventType"];
enabledAppsNumber: number;
enabledWorkflowsNumber: number;
}) {
const { eventType, t, enabledAppsNumber, enabledWorkflowsNumber } = props;
return [
{
name: "event_setup_tab_title",
href: `/event-types/${eventType.id}?tabName=setup`,
icon: Icon.FiLink,
info: `${eventType.length} Mins`, // TODO: Get this from props
},
{
name: "availability",
href: `/event-types/${eventType.id}?tabName=availability`,
icon: Icon.FiCalendar,
info: `Working Hours`, // TODO: Get this from props
},
{
name: "event_limit_tab_title",
href: `/event-types/${eventType.id}?tabName=limits`,
icon: Icon.FiClock,
info: `event_limit_tab_description`,
},
{
name: "event_advanced_tab_title",
href: `/event-types/${eventType.id}?tabName=advanced`,
icon: Icon.FiSliders,
info: `event_advanced_tab_description`,
},
{
name: "recurring",
href: `/event-types/${eventType.id}?tabName=recurring`,
icon: Icon.FiRepeat,
info: `recurring_event_tab_description`,
},
{
name: "apps",
href: `/event-types/${eventType.id}?tabName=apps`,
icon: Icon.FiGrid,
info: `${enabledAppsNumber} ${t("active")}`,
},
{
name: "workflows",
href: `/event-types/${eventType.id}?tabName=workflows`,
icon: Icon.FiZap,
info: `${enabledWorkflowsNumber} ${t("active")}`,
},
];
}
function EventTypeSingleLayout({
children,
eventType,
@@ -57,7 +108,6 @@ function EventTypeSingleLayout({
formMethods,
}: Props) {
const utils = trpc.useContext();
const router = useRouter();
const { t } = useLocale();
const [deleteDialogOpen, setDeleteDialogOpen] = useState(false);
@@ -82,73 +132,17 @@ function EventTypeSingleLayout({
// Define tab navigation here
const EventTypeTabs = useMemo(() => {
const navigation: (VerticalTabItemProps & HorizontalTabItemProps)[] = [
{
name: "event_setup_tab_title",
tabName: "setup",
icon: Icon.FiLink,
info: `${eventType.length} Mins`, // TODO: Get this from props
},
{
name: "availability",
tabName: "availability",
icon: Icon.FiCalendar,
info: `Working Hours`, // TODO: Get this from props
},
{
name: "event_limit_tab_title",
tabName: "limits",
icon: Icon.FiClock,
info: `event_limit_tab_description`,
},
{
name: "event_advanced_tab_title",
tabName: "advanced",
icon: Icon.FiSliders,
info: `event_advanced_tab_description`,
},
{
name: "recurring",
tabName: "recurring",
icon: Icon.FiRepeat,
info: `recurring_event_tab_description`,
},
{
name: "apps",
tabName: "apps",
icon: Icon.FiGrid,
info: `${enabledAppsNumber} ${t("active")}`,
},
{
name: "workflows",
tabName: "workflows",
icon: Icon.FiZap,
info: `${enabledWorkflowsNumber} ${t("active")}`,
},
];
const navigation = getNavigation({ t, eventType, enabledAppsNumber, enabledWorkflowsNumber });
// If there is a team put this navigation item within the tabs
if (team)
navigation.splice(2, 0, {
name: "scheduling_type",
tabName: "team",
href: `team`,
icon: Icon.FiUsers,
info: eventType.schedulingType === "COLLECTIVE" ? "collective" : "round_robin",
});
return navigation;
}, [eventType, enabledAppsNumber, team]);
useEffect(() => {
// Default to the first in the list
if (!router.query.tabName) {
router.push({
query: {
...router.query,
tabName: EventTypeTabs[0].tabName,
},
});
}
}, [EventTypeTabs, router]);
}, [t, eventType, enabledAppsNumber, enabledWorkflowsNumber, team]);
const permalink = `${CAL_URL}/${team ? `team/${team.slug}` : eventType.users[0].username}/${
eventType.slug
@@ -265,10 +259,15 @@ function EventTypeSingleLayout({
<ClientSuspense fallback={<Loader />}>
<div className="-mt-2 flex flex-col xl:flex-row xl:space-x-8">
<div className="hidden xl:block">
<VerticalTabs className="primary-navigation" tabs={EventTypeTabs} sticky />
<VerticalTabs
className="primary-navigation"
tabs={EventTypeTabs}
sticky
linkProps={{ shallow: true }}
/>
</div>
<div className="p-2 md:mx-0 md:p-0 xl:hidden">
<HorizontalTabs tabNameKey="tabName" tabs={EventTypeTabs} />
<HorizontalTabs tabs={EventTypeTabs} linkProps={{ shallow: true }} />
</div>
<div className="w-full ltr:mr-2 rtl:ml-2">
<div
@@ -292,7 +291,7 @@ function EventTypeSingleLayout({
e.preventDefault();
deleteMutation.mutate({ id: eventType.id });
}}>
{t("delete_event_type_description") as string}
{t("delete_event_type_description")}
</ConfirmationDialogContent>
</Dialog>
<EmbedDialog />
+2 -2
View File
@@ -16,10 +16,10 @@ import { v5 as uuidv5 } from "uuid";
import { z } from "zod";
import { SelectGifInput } from "@calcom/app-store/giphy/components";
import { getEventLocationType, EventLocationType } from "@calcom/app-store/locations";
import { EventLocationType, getEventLocationType } from "@calcom/app-store/locations";
import { StripeData } from "@calcom/app-store/stripepayment/lib/server";
import getApps, { getLocationOptions } from "@calcom/app-store/utils";
import { LocationObject, LocationType } from "@calcom/core/location";
import { LocationObject } from "@calcom/core/location";
import { parseRecurringEvent } from "@calcom/lib";
import { CAL_URL, WEBAPP_URL } from "@calcom/lib/constants";
import { useLocale } from "@calcom/lib/hooks/useLocale";
@@ -45,10 +45,10 @@ export default function BookingLayout({
<Shell {...rest}>
<div className="flex flex-col sm:space-x-2 xl:flex-row">
<div className="hidden xl:block">
<VerticalTabs tabNameKey="tabName" tabs={tabs} sticky />
<VerticalTabs tabs={tabs} sticky />
</div>
<div className="block xl:hidden">
<HorizontalTabs tabNameKey="tabName" tabs={tabs} />
<HorizontalTabs tabs={tabs} />
</div>
<main className="w-full max-w-6xl">{children}</main>
</div>
@@ -49,10 +49,10 @@ export default function InstalledAppsLayout({
<Shell {...rest}>
<div className="flex flex-col p-2 md:p-0 xl:flex-row">
<div className="hidden xl:block">
<VerticalTabs tabs={actualTabs} sticky />
<VerticalTabs tabs={actualTabs} sticky linkProps={{ shallow: true }} />
</div>
<div className="block xl:hidden">
<HorizontalTabs tabNameKey="tabName" tabs={actualTabs} />
<HorizontalTabs tabs={actualTabs} linkProps={{ shallow: true }} />
</div>
<main className="w-full xl:mx-5 xl:w-4/5 xl:pr-5">{children}</main>
</div>
@@ -1,65 +1,28 @@
import noop from "lodash/noop";
import Link from "next/link";
import { useRouter } from "next/router";
import { MouseEventHandler } from "react";
import { ComponentProps } from "react";
import classNames from "@calcom/lib/classNames";
import { useLocale } from "@calcom/lib/hooks/useLocale";
import { SkeletonText } from "../../skeleton";
export type HorizontalTabItemProps<T extends string = "tabName"> = {
export type HorizontalTabItemProps = {
name: string;
disabled?: boolean;
className?: string;
} & (
| {
href: string;
}
| ({
href?: never;
} & Partial<Record<T, string>>)
);
href: string;
linkProps?: Omit<ComponentProps<typeof Link>, "href">;
};
const HorizontalTabItem = function <T extends string>({
name,
href,
tabNameKey,
...props
}: HorizontalTabItemProps<T> & {
tabNameKey?: T;
}) {
const router = useRouter();
const HorizontalTabItem = function ({ name, href, linkProps, ...props }: HorizontalTabItemProps) {
const { t, isLocaleReady } = useLocale();
let newHref = "";
let isCurrent;
const _tabNameKey = tabNameKey || "tabName";
const tabName = props[tabNameKey as keyof typeof props];
if (href) {
newHref = href;
isCurrent = router.asPath.startsWith(href);
} else if (tabName) {
newHref = "";
isCurrent = router.query[_tabNameKey] === tabName;
}
const onClick: MouseEventHandler = tabName
? (e) => {
e.preventDefault();
router.push({
query: {
...router.query,
[_tabNameKey]: tabName,
},
});
}
: noop;
const { asPath } = useRouter();
const isCurrent = asPath.startsWith(href);
return (
<Link key={name} href={props.disabled ? "#" : newHref}>
<Link key={name} href={href} {...linkProps}>
<a
onClick={onClick}
className={classNames(
isCurrent ? "bg-gray-200 text-gray-900" : " text-gray-600 hover:bg-gray-100 hover:text-gray-900 ",
"mb-2 inline-flex items-center justify-center whitespace-nowrap rounded-md py-[10px] px-4 text-sm font-medium leading-4 md:mb-0",
@@ -2,13 +2,12 @@ import HorizontalTabItem, { HorizontalTabItemProps } from "./HorizontalTabItem";
export { HorizontalTabItem };
export interface NavTabProps<T extends string> {
tabs: HorizontalTabItemProps<T>[];
tabNameKey?: T;
export interface NavTabProps {
tabs: HorizontalTabItemProps[];
linkProps?: HorizontalTabItemProps["linkProps"];
}
const HorizontalTabs = function <T extends string>({ tabs, tabNameKey, ...props }: NavTabProps<T>) {
const _tabNameKey = tabNameKey || "tabName";
const HorizontalTabs = function ({ tabs, linkProps, ...props }: NavTabProps) {
return (
<div className="-mx-6 mb-2 w-[calc(100%+40px)]">
<nav
@@ -16,7 +15,7 @@ const HorizontalTabs = function <T extends string>({ tabs, tabNameKey, ...props
aria-label="Tabs"
{...props}>
{tabs.map((tab, idx) => (
<HorizontalTabItem tabNameKey={_tabNameKey} {...tab} key={idx} />
<HorizontalTabItem {...tab} key={idx} {...linkProps} />
))}
</nav>
</div>
@@ -1,80 +1,46 @@
import noop from "lodash/noop";
import Link from "next/link";
import { useRouter } from "next/router";
import { Fragment, MouseEventHandler } from "react";
import { ComponentProps, Fragment } from "react";
// import { ChevronRight } from "react-feather";
import classNames from "@calcom/lib/classNames";
import { useLocale } from "@calcom/lib/hooks/useLocale";
import { SVGComponent } from "@calcom/types/SVGComponent";
import { Icon } from "@calcom/ui/Icon";
export type VerticalTabItemProps<T extends string = "tabName"> = {
export type VerticalTabItemProps = {
name: string;
info?: string;
icon?: SVGComponent;
disabled?: boolean;
children?: VerticalTabItemProps<T>[];
children?: VerticalTabItemProps[];
textClassNames?: string;
className?: string;
isChild?: boolean;
hidden?: boolean;
disableChevron?: boolean;
} & (
| {
/** If you want to change query param tabName as per current tab */
href: string;
tabName?: never;
}
| ({
href?: never;
} & Partial<Record<T, string>>)
);
href: string;
linkProps?: Omit<ComponentProps<typeof Link>, "href">;
};
const VerticalTabItem = function <T extends string>({
const VerticalTabItem = function ({
name,
href,
tabNameKey,
info,
isChild,
disableChevron,
linkProps,
...props
}: VerticalTabItemProps<T> & {
tabNameKey?: T;
}) {
const router = useRouter();
}: VerticalTabItemProps) {
const { t } = useLocale();
let newHref = "";
let isCurrent;
const tabName = props[tabNameKey as keyof typeof props] as string;
const _tabNameKey = tabNameKey || "tabName";
if (href) {
newHref = href;
isCurrent = router.asPath.startsWith(href);
} else if (tabName) {
newHref = "";
isCurrent = router.query[_tabNameKey] === tabName;
}
const onClick: MouseEventHandler = tabName
? (e) => {
e.preventDefault();
router.push({
query: {
...router.query,
[_tabNameKey]: tabName,
},
});
}
: noop;
const { asPath } = useRouter();
const isCurrent = asPath.startsWith(href);
return (
<Fragment key={name}>
{!props.hidden && (
<>
<Link key={name} href={props.disabled ? "#" : newHref}>
<Link key={name} href={href} {...linkProps}>
<a
onClick={onClick}
className={classNames(
props.textClassNames || "text-sm font-medium leading-none text-gray-600",
"group flex w-64 flex-row items-center rounded-md px-3 py-[10px] hover:bg-gray-100 group-hover:text-gray-700 [&[aria-current='page']]:bg-gray-200 [&[aria-current='page']]:text-gray-900",
@@ -86,8 +52,6 @@ const VerticalTabItem = function <T extends string>({
data-testid={`vertical-tab-${name}`}
aria-current={isCurrent ? "page" : undefined}>
{props.icon && (
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
//@ts-ignore
<props.icon className="mr-[10px] h-[16px] w-[16px] self-start stroke-[2px] md:mt-0" />
)}
<div>
@@ -106,7 +70,7 @@ const VerticalTabItem = function <T extends string>({
</a>
</Link>
{props.children?.map((child) => (
<VerticalTabItem tabNameKey={tabNameKey} key={child.name} {...child} isChild />
<VerticalTabItem key={child.name} {...child} isChild />
))}
</>
)}
@@ -4,23 +4,15 @@ import VerticalTabItem, { VerticalTabItemProps } from "./VerticalTabItem";
export { VerticalTabItem };
export interface NavTabProps<T extends string> {
tabs: VerticalTabItemProps<T>[];
export interface NavTabProps {
tabs: VerticalTabItemProps[];
children?: React.ReactNode;
className?: string;
sticky?: boolean;
tabNameKey?: T;
linkProps?: VerticalTabItemProps["linkProps"];
}
const NavTabs = function <T extends string>({
tabs,
tabNameKey,
className = "",
sticky,
...props
}: NavTabProps<T>) {
const _tabNameKey = tabNameKey || "tabName";
const NavTabs = function ({ tabs, className = "", sticky, linkProps, ...props }: NavTabProps) {
return (
<nav
className={classNames(
@@ -33,7 +25,7 @@ const NavTabs = function <T extends string>({
{sticky && <div className="pt-6" />}
{props.children}
{tabs.map((tab, idx) => (
<VerticalTabItem tabNameKey={_tabNameKey} {...tab} key={idx} />
<VerticalTabItem {...tab} key={idx} linkProps={linkProps} />
))}
</nav>
);