feat: v2 embed (#4477)

* Add support for custom tabNameKey and use embedTabName as the key for embed to avoid conflict with event-types tabName property

* Fix tests

* Code cleanup

* feat: v2 embed

* fix: button black default, reuse horitzontalTabs v2

* fix: remove comment, remove linkProps from NavTabs v2

* fix: height: 98% to avoid overflow

* fix: add embed to event type detail page

* fix: add also tabNames embed-code embed-react

* fix: add tabNames w empty divs

* Update Embed component as per V2

Co-authored-by: Hariom Balhara <hariombalhara@gmail.com>
Co-authored-by: kodiakhq[bot] <49736102+kodiakhq[bot]@users.noreply.github.com>
Co-authored-by: Agusti Fernandez Pardo <git@agusti.me>
Co-authored-by: Alex van Andel <me@alexvanandel.com>
This commit is contained in:
Agusti Fernandez Pardo 2022-09-15 07:34:11 +02:00 committed by GitHub
parent 35c2f9046a
commit a3462657db
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
14 changed files with 857 additions and 143 deletions

View File

@ -5,15 +5,14 @@ import { createRef, forwardRef, MutableRefObject, RefObject, useRef, useState }
import { components, ControlProps } from "react-select"; import { components, ControlProps } from "react-select";
import { useLocale } from "@calcom/lib/hooks/useLocale"; import { useLocale } from "@calcom/lib/hooks/useLocale";
import showToast from "@calcom/lib/notification";
import { Dialog, DialogClose, DialogContent } from "@calcom/ui/Dialog"; import { Dialog, DialogClose, DialogContent } from "@calcom/ui/Dialog";
import { Icon } from "@calcom/ui/Icon"; import { Icon } from "@calcom/ui/Icon";
import { InputLeading, Label, TextArea, TextField } from "@calcom/ui/form/fields"; 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, Switch } from "@calcom/ui/v2";
import { EMBED_LIB_URL, WEBAPP_URL } from "@lib/config/constants"; import { EMBED_LIB_URL, WEBAPP_URL } from "@lib/config/constants";
import NavTabs from "@components/NavTabs";
import ColorPicker from "@components/ui/colorpicker"; import ColorPicker from "@components/ui/colorpicker";
import Select from "@components/ui/form/Select"; import Select from "@components/ui/form/Select";
@ -38,7 +37,7 @@ type PreviewState = {
brandColor: string; brandColor: string;
}; };
}; };
const queryParamsForDialog = ["embedType", "tabName", "embedUrl"]; const queryParamsForDialog = ["embedType", "embedTabName", "embedUrl"];
const getDimension = (dimension: string) => { const getDimension = (dimension: string) => {
if (dimension.match(/^\d+$/)) { if (dimension.match(/^\d+$/)) {
@ -453,7 +452,7 @@ const embeds: {
const tabs = [ const tabs = [
{ {
name: "HTML", name: "HTML",
tabName: "embed-code", embedTabName: "embed-code",
icon: Icon.FiCode, icon: Icon.FiCode,
type: "code", type: "code",
Component: forwardRef< Component: forwardRef<
@ -504,7 +503,7 @@ ${getEmbedTypeSpecificString({ embedFramework: "HTML", embedType, calLink, previ
}, },
{ {
name: "React", name: "React",
tabName: "embed-react", embedTabName: "embed-react",
icon: Icon.FiCode, icon: Icon.FiCode,
type: "code", type: "code",
Component: forwardRef< Component: forwardRef<
@ -544,7 +543,7 @@ ${getEmbedTypeSpecificString({ embedFramework: "react", embedType, calLink, prev
}, },
{ {
name: "Preview", name: "Preview",
tabName: "embed-preview", embedTabName: "embed-preview",
icon: Icon.FiEye, icon: Icon.FiEye,
type: "iframe", type: "iframe",
Component: forwardRef< Component: forwardRef<
@ -561,7 +560,7 @@ ${getEmbedTypeSpecificString({ embedFramework: "react", embedType, calLink, prev
<iframe <iframe
ref={ref as typeof ref & MutableRefObject<HTMLIFrameElement>} ref={ref as typeof ref & MutableRefObject<HTMLIFrameElement>}
data-testid="embed-preview" data-testid="embed-preview"
className="border-1 h-[75vh] border" className="border-1 h-[100vh] border"
width="100%" width="100%"
height="100%" height="100%"
src={`${WEBAPP_URL}/embed/preview.html?embedType=${embedType}&calLink=${calLink}`} src={`${WEBAPP_URL}/embed/preview.html?embedType=${embedType}&calLink=${calLink}`}
@ -678,8 +677,8 @@ const EmbedTypeCodeAndPreviewDialogContent = ({
}; };
// Use embed-code as default tab // Use embed-code as default tab
if (!router.query.tabName) { if (!router.query.embedTabName) {
router.query.tabName = "embed-code"; router.query.embedTabName = "embed-code";
router.push({ router.push({
query: { query: {
...router.query, ...router.query,
@ -780,7 +779,7 @@ const EmbedTypeCodeAndPreviewDialogContent = ({
]; ];
return ( return (
<DialogContent size="xl"> <DialogContent size="lg">
<div className="flex"> <div className="flex">
<div className="flex w-1/3 flex-col bg-white p-6"> <div className="flex w-1/3 flex-col bg-white p-6">
<h3 className="mb-2 flex text-xl font-bold leading-6 text-gray-900" id="modal-title"> <h3 className="mb-2 flex text-xl font-bold leading-6 text-gray-900" id="modal-title">
@ -788,7 +787,7 @@ const EmbedTypeCodeAndPreviewDialogContent = ({
onClick={() => { onClick={() => {
const newQuery = { ...router.query }; const newQuery = { ...router.query };
delete newQuery.embedType; delete newQuery.embedType;
delete newQuery.tabName; delete newQuery.embedTabName;
router.push({ router.push({
query: { query: {
...newQuery, ...newQuery,
@ -871,9 +870,9 @@ const EmbedTypeCodeAndPreviewDialogContent = ({
<div <div
className={classNames( className={classNames(
"mt-4 items-center justify-between", "mt-4 items-center justify-between",
embedType === "floating-popup" ? "flex" : "hidden" embedType === "floating-popup" ? "" : "hidden"
)}> )}>
<div className="text-sm">Button Text</div> <div className="mb-2 text-sm">Button Text</div>
{/* Default Values should come from preview iframe */} {/* Default Values should come from preview iframe */}
<TextField <TextField
name="buttonText" name="buttonText"
@ -895,10 +894,9 @@ const EmbedTypeCodeAndPreviewDialogContent = ({
</div> </div>
<div <div
className={classNames( className={classNames(
"mt-4 flex items-center justify-between", "mt-4 flex items-center justify-start",
embedType === "floating-popup" ? "flex" : "hidden" embedType === "floating-popup" ? "space-x-2" : "hidden"
)}> )}>
<div className="text-sm">Display Calendar Icon Button</div>
<Switch <Switch
defaultChecked={true} defaultChecked={true}
onCheckedChange={(checked) => { onCheckedChange={(checked) => {
@ -913,13 +911,14 @@ const EmbedTypeCodeAndPreviewDialogContent = ({
}); });
}} }}
/> />
<div className="text-sm">Display Calendar Icon Button</div>
</div> </div>
<div <div
className={classNames( className={classNames(
"mt-4 flex items-center justify-between", "mt-4 items-center justify-between",
embedType === "floating-popup" ? "flex" : "hidden" embedType === "floating-popup" ? "" : "hidden"
)}> )}>
<div>Position of Button</div> <div className="mb-2">Position of Button</div>
<Select <Select
onChange={(position) => { onChange={(position) => {
setPreviewState((previewState) => { setPreviewState((previewState) => {
@ -936,13 +935,9 @@ const EmbedTypeCodeAndPreviewDialogContent = ({
options={FloatingPopupPositionOptions} options={FloatingPopupPositionOptions}
/> />
</div> </div>
<div <div className={classNames("mt-4", embedType === "floating-popup" ? "" : "hidden")}>
className={classNames(
"mt-4 flex items-center justify-between",
embedType === "floating-popup" ? "flex" : "hidden"
)}>
<div>Button Color</div> <div>Button Color</div>
<div className="w-36"> <div className="w-full">
<ColorPicker <ColorPicker
defaultValue="#000000" defaultValue="#000000"
onChange={(color) => { onChange={(color) => {
@ -959,13 +954,9 @@ const EmbedTypeCodeAndPreviewDialogContent = ({
/> />
</div> </div>
</div> </div>
<div <div className={classNames("mt-4", embedType === "floating-popup" ? "" : "hidden")}>
className={classNames(
"mt-4 flex items-center justify-between",
embedType === "floating-popup" ? "flex" : "hidden"
)}>
<div>Text Color</div> <div>Text Color</div>
<div className="w-36"> <div className="w-full">
<ColorPicker <ColorPicker
defaultValue="#000000" defaultValue="#000000"
onChange={(color) => { onChange={(color) => {
@ -1000,10 +991,10 @@ const EmbedTypeCodeAndPreviewDialogContent = ({
</CollapsibleTrigger> </CollapsibleTrigger>
<CollapsibleContent> <CollapsibleContent>
<div className="mt-6 text-sm"> <div className="mt-6 text-sm">
<Label className="flex items-center justify-between"> <Label className="">
<div>Theme</div> <div className="mb-2">Theme</div>
<Select <Select
className="w-36" className="w-full"
defaultValue={ThemeOptions[0]} defaultValue={ThemeOptions[0]}
components={{ components={{
Control: ThemeSelectControl, Control: ThemeSelectControl,
@ -1030,9 +1021,9 @@ const EmbedTypeCodeAndPreviewDialogContent = ({
// { name: "highlightColor", title: "Highlight Color" }, // { name: "highlightColor", title: "Highlight Color" },
// { name: "medianColor", title: "Median Color" }, // { name: "medianColor", title: "Median Color" },
].map((palette) => ( ].map((palette) => (
<Label key={palette.name} className="flex items-center justify-between"> <Label key={palette.name} className="pb-4">
<div>{palette.title}</div> <div className="mb-2 pt-2">{palette.title}</div>
<div className="w-36"> <div className="w-full">
<ColorPicker <ColorPicker
defaultValue="#000000" defaultValue="#000000"
onChange={(color) => { onChange={(color) => {
@ -1049,33 +1040,33 @@ const EmbedTypeCodeAndPreviewDialogContent = ({
</Collapsible> </Collapsible>
</div> </div>
</div> </div>
<div className="w-2/3 bg-gray-50 p-6"> <div className="flex w-2/3 flex-col p-6">
<NavTabs data-testid="embed-tabs" tabs={tabs} linkProps={{ shallow: true }} /> <HorizontalTabs tabNameKey="embedTabName" data-testid="embed-tabs" tabs={tabs} />
{tabs.map((tab) => { {tabs.map((tab) => {
return ( return (
<div <div
key={tab.tabName} key={tab.embedTabName}
className={classNames(router.query.tabName === tab.tabName ? "block" : "hidden")}> className={classNames(
<div> router.query.embedTabName === tab.embedTabName ? "flex flex-grow flex-col" : "hidden"
<div className={classNames(tab.type === "code" ? "h-[75vh]" : "")}> )}>
{tab.type === "code" ? ( <div className="flex h-[55vh] flex-grow flex-col">
<tab.Component {tab.type === "code" ? (
embedType={embedType} <tab.Component
calLink={calLink} embedType={embedType}
previewState={previewState} calLink={calLink}
ref={refOfEmbedCodesRefs.current[tab.name]} previewState={previewState}
/> ref={refOfEmbedCodesRefs.current[tab.name]}
) : ( />
<tab.Component ) : (
embedType={embedType} <tab.Component
calLink={calLink} embedType={embedType}
previewState={previewState} calLink={calLink}
ref={iframeRef} previewState={previewState}
/> ref={iframeRef}
)} />
</div> )}
<div className={router.query.tabName == "embed-preview" ? "block" : "hidden"} />
</div> </div>
<div className={router.query.embedTabName == "embed-preview" ? "block" : "hidden"} />
<div className="mt-8 flex flex-row-reverse gap-x-2"> <div className="mt-8 flex flex-row-reverse gap-x-2">
{tab.type === "code" ? ( {tab.type === "code" ? (
<Button <Button

View File

@ -21,6 +21,7 @@ import {
HorizontalTabs, HorizontalTabs,
Switch, Switch,
Label, Label,
HorizontalTabItemProps,
} from "@calcom/ui/v2"; } from "@calcom/ui/v2";
import { Dialog } from "@calcom/ui/v2/core/Dialog"; import { Dialog } from "@calcom/ui/v2/core/Dialog";
import Dropdown, { import Dropdown, {
@ -32,6 +33,7 @@ import Shell from "@calcom/ui/v2/core/Shell";
import VerticalDivider from "@calcom/ui/v2/core/VerticalDivider"; import VerticalDivider from "@calcom/ui/v2/core/VerticalDivider";
import { ClientSuspense } from "@components/ClientSuspense"; import { ClientSuspense } from "@components/ClientSuspense";
import { EmbedButton, EmbedDialog } from "@components/Embed";
type Props = { type Props = {
children: React.ReactNode; children: React.ReactNode;
@ -80,7 +82,7 @@ function EventTypeSingleLayout({
// Define tab navigation here // Define tab navigation here
const EventTypeTabs = useMemo(() => { const EventTypeTabs = useMemo(() => {
const navigation = [ const navigation: (VerticalTabItemProps & HorizontalTabItemProps)[] = [
{ {
name: "event_setup_tab_title", name: "event_setup_tab_title",
tabName: "setup", tabName: "setup",
@ -123,7 +125,7 @@ function EventTypeSingleLayout({
icon: Icon.FiZap, icon: Icon.FiZap,
info: `${enabledWorkflowsNumber} ${t("active")}`, info: `${enabledWorkflowsNumber} ${t("active")}`,
}, },
] as VerticalTabItemProps[]; ];
// If there is a team put this navigation item within the tabs // If there is a team put this navigation item within the tabs
if (team) if (team)
@ -152,6 +154,8 @@ function EventTypeSingleLayout({
eventType.slug eventType.slug
}`; }`;
const embedLink = `${team ? `team/${team.slug}` : eventType.users[0].username}/${eventType.slug}`;
return ( return (
<Shell <Shell
backPath="/event-types" backPath="/event-types"
@ -199,8 +203,12 @@ function EventTypeSingleLayout({
showToast("Link copied!", "success"); showToast("Link copied!", "success");
}} }}
/> />
{/* TODO: Implement embed here @hariom */} <EmbedButton
{/* <Button color="secondary" size="icon" StartIcon={Icon.FiCode} /> */} embedUrl={encodeURIComponent(embedLink)}
StartIcon={Icon.FiCode}
color="secondary"
size="icon"
/>
<Button <Button
color="secondary" color="secondary"
size="icon" size="icon"
@ -256,7 +264,7 @@ function EventTypeSingleLayout({
<VerticalTabs tabs={EventTypeTabs} sticky /> <VerticalTabs tabs={EventTypeTabs} sticky />
</div> </div>
<div className="p-2 md:mx-0 md:p-0 xl:hidden"> <div className="p-2 md:mx-0 md:p-0 xl:hidden">
<HorizontalTabs tabs={EventTypeTabs} /> <HorizontalTabs tabNameKey="tabName" tabs={EventTypeTabs} />
</div> </div>
<div className="w-full ltr:mr-2 rtl:ml-2"> <div className="w-full ltr:mr-2 rtl:ml-2">
<div <div
@ -283,6 +291,7 @@ function EventTypeSingleLayout({
{t("delete_event_type_description") as string} {t("delete_event_type_description") as string}
</ConfirmationDialogContent> </ConfirmationDialogContent>
</Dialog> </Dialog>
<EmbedDialog />
</Shell> </Shell>
); );
} }

View File

@ -1,3 +1,4 @@
/* eslint-disable @typescript-eslint/no-empty-function */
import autoAnimate from "@formkit/auto-animate"; import autoAnimate from "@formkit/auto-animate";
import { EventTypeCustomInput, PeriodType, Prisma, SchedulingType } from "@prisma/client"; import { EventTypeCustomInput, PeriodType, Prisma, SchedulingType } from "@prisma/client";
import { GetServerSidePropsContext } from "next"; import { GetServerSidePropsContext } from "next";

View File

@ -60,7 +60,7 @@ async function expectToBeNavigatingToEmbedCodeAndPreviewDialog(
url.searchParams.get("dialog") === "embed" && url.searchParams.get("dialog") === "embed" &&
url.searchParams.get("embedUrl") === embedUrl && url.searchParams.get("embedUrl") === embedUrl &&
url.searchParams.get("embedType") === embedType && url.searchParams.get("embedType") === embedType &&
url.searchParams.get("tabName") === "embed-code" url.searchParams.get("embedTabName") === "embed-code"
); );
}, },
}); });

View File

@ -21,7 +21,7 @@
</head> </head>
<script type="module" src="./src/preview.ts"></script> <script type="module" src="./src/preview.ts"></script>
<body> <body>
<div id="my-embed" style="width: 100%; height: 100%; overflow: scroll"></div> <div id="my-embed" style="width: 100%; height: 90%; overflow: scroll"></div>
<script type="module"> <script type="module">
</script> </script>

View File

@ -252,10 +252,6 @@ export const methods = {
ui: function style(uiConfig: UiConfig) { ui: function style(uiConfig: UiConfig) {
// TODO: Create automatic logger for all methods. Useful for debugging. // TODO: Create automatic logger for all methods. Useful for debugging.
log("Method: ui called", uiConfig); log("Method: ui called", uiConfig);
if (window.CalComPlan && window.CalComPlan !== "PRO") {
log(`Upgrade to PRO for "ui" instruction to work`, window.CalComPlan);
return;
}
const stylesConfig = uiConfig.styles; const stylesConfig = uiConfig.styles;
// In case where parent gives instructions before CalComPlan is set. // In case where parent gives instructions before CalComPlan is set.

View File

@ -276,8 +276,8 @@ export class Cal {
hideButtonIcon = false, hideButtonIcon = false,
attributes, attributes,
buttonPosition = "bottom-right", buttonPosition = "bottom-right",
buttonColor = "rgb(255, 202, 0)", buttonColor = "rgb(0, 0, 0)",
buttonTextColor = "rgb(20, 30, 47)", buttonTextColor = "rgb(255, 255, 255)",
}: { }: {
calLink: string; calLink: string;
buttonText?: string; buttonText?: string;

View File

@ -1,11 +1,11 @@
import React, { ComponentProps } from "react"; import React, { ComponentProps } from "react";
import HorizontalTabs from "@calcom/ui/v2/core/navigation/tabs/HorizontalTabs"; import HorizontalTabs from "@calcom/ui/v2/core/navigation/tabs/HorizontalTabs";
import type { VerticalTabItemProps } from "@calcom/ui/v2/core/navigation/tabs/VerticalTabItem";
import Shell from "../Shell"; import Shell from "../Shell";
import { HorizontalTabItemProps } from "../navigation/tabs/HorizontalTabItem";
const tabs: VerticalTabItemProps[] = [ const tabs: HorizontalTabItemProps[] = [
{ {
name: "app_store", name: "app_store",
href: "/apps", href: "/apps",

View File

@ -45,10 +45,10 @@ export default function BookingLayout({
<Shell {...rest}> <Shell {...rest}>
<div className="flex flex-col sm:space-x-2 xl:flex-row"> <div className="flex flex-col sm:space-x-2 xl:flex-row">
<div className="hidden xl:block"> <div className="hidden xl:block">
<VerticalTabs tabs={tabs} sticky /> <VerticalTabs tabNameKey="tabName" tabs={tabs} sticky />
</div> </div>
<div className="block xl:hidden"> <div className="block xl:hidden">
<HorizontalTabs tabs={tabs} /> <HorizontalTabs tabNameKey="tabName" tabs={tabs} />
</div> </div>
<main className="w-full max-w-6xl">{children}</main> <main className="w-full max-w-6xl">{children}</main>
</div> </div>

View File

@ -6,34 +6,40 @@ import { MouseEventHandler } from "react";
import classNames from "@calcom/lib/classNames"; import classNames from "@calcom/lib/classNames";
import { useLocale } from "@calcom/lib/hooks/useLocale"; import { useLocale } from "@calcom/lib/hooks/useLocale";
export type HorizontalTabItemProps = { export type HorizontalTabItemProps<T extends string = "tabName"> = {
name: string; name: string;
disabled?: boolean; disabled?: boolean;
className?: string; className?: string;
} & ( } & (
| { | {
/** If you want to change query param tabName as per current tab */
href: string; href: string;
tabName?: never;
} }
| { | ({
href?: never; href?: never;
/** If you want to change the path as per current tab */ } & Partial<Record<T, string>>)
tabName: string;
}
); );
const HorizontalTabItem = ({ name, href, tabName, ...props }: HorizontalTabItemProps) => { const HorizontalTabItem = function <T extends string>({
name,
href,
tabNameKey,
...props
}: HorizontalTabItemProps<T> & {
tabNameKey?: T;
}) {
const router = useRouter(); const router = useRouter();
const { t } = useLocale(); const { t } = useLocale();
let newHref = ""; let newHref = "";
let isCurrent; let isCurrent;
const _tabNameKey = tabNameKey || "tabName";
const tabName = props[tabNameKey as keyof typeof props];
if (href) { if (href) {
newHref = href; newHref = href;
isCurrent = router.asPath === href; isCurrent = router.asPath === href;
} else if (tabName) { } else if (tabName) {
newHref = ""; newHref = "";
isCurrent = router.query.tabName === tabName; isCurrent = router.query[_tabNameKey] === tabName;
} }
const onClick: MouseEventHandler = tabName const onClick: MouseEventHandler = tabName
@ -42,7 +48,7 @@ const HorizontalTabItem = ({ name, href, tabName, ...props }: HorizontalTabItemP
router.push({ router.push({
query: { query: {
...router.query, ...router.query,
tabName, [_tabNameKey]: tabName,
}, },
}); });
} }

View File

@ -1,14 +1,14 @@
import { FC } from "react";
import HorizontalTabItem, { HorizontalTabItemProps } from "./HorizontalTabItem"; import HorizontalTabItem, { HorizontalTabItemProps } from "./HorizontalTabItem";
export { HorizontalTabItem }; export { HorizontalTabItem };
export interface NavTabProps { export interface NavTabProps<T extends string> {
tabs: HorizontalTabItemProps[]; tabs: HorizontalTabItemProps<T>[];
tabNameKey?: T;
} }
const HorizontalTabs: FC<NavTabProps> = ({ tabs, ...props }) => { const HorizontalTabs = function <T extends string>({ tabs, tabNameKey, ...props }: NavTabProps<T>) {
const _tabNameKey = tabNameKey || "tabName";
return ( return (
<div className="-mx-6 mb-2 w-[calc(100%+40px)]"> <div className="-mx-6 mb-2 w-[calc(100%+40px)]">
<nav <nav
@ -16,7 +16,7 @@ const HorizontalTabs: FC<NavTabProps> = ({ tabs, ...props }) => {
aria-label="Tabs" aria-label="Tabs"
{...props}> {...props}>
{tabs.map((tab, idx) => ( {tabs.map((tab, idx) => (
<HorizontalTabItem {...tab} key={idx} /> <HorizontalTabItem tabNameKey={_tabNameKey} {...tab} key={idx} />
))} ))}
</nav> </nav>
</div> </div>

View File

@ -1,7 +1,7 @@
import noop from "lodash/noop"; import noop from "lodash/noop";
import Link from "next/link"; import Link from "next/link";
import { useRouter } from "next/router"; import { useRouter } from "next/router";
import { FC, Fragment, MouseEventHandler } from "react"; import { Fragment, MouseEventHandler } from "react";
// import { ChevronRight } from "react-feather"; // import { ChevronRight } from "react-feather";
import classNames from "@calcom/lib/classNames"; import classNames from "@calcom/lib/classNames";
@ -9,12 +9,12 @@ import { useLocale } from "@calcom/lib/hooks/useLocale";
import { SVGComponent } from "@calcom/types/SVGComponent"; import { SVGComponent } from "@calcom/types/SVGComponent";
import { Icon } from "@calcom/ui/Icon"; import { Icon } from "@calcom/ui/Icon";
export type VerticalTabItemProps = { export type VerticalTabItemProps<T extends string = "tabName"> = {
name: string; name: string;
info?: string; info?: string;
icon?: SVGComponent; icon?: SVGComponent;
disabled?: boolean; disabled?: boolean;
children?: VerticalTabItemProps[]; children?: VerticalTabItemProps<T>[];
textClassNames?: string; textClassNames?: string;
className?: string; className?: string;
isChild?: boolean; isChild?: boolean;
@ -26,32 +26,34 @@ export type VerticalTabItemProps = {
href: string; href: string;
tabName?: never; tabName?: never;
} }
| { | ({
href?: never; href?: never;
/** If you want to change the path as per current tab */ } & Partial<Record<T, string>>)
tabName: string;
}
); );
const VerticalTabItem: FC<VerticalTabItemProps> = ({ const VerticalTabItem = function <T extends string>({
name, name,
href, href,
tabName, tabNameKey,
info, info,
isChild, isChild,
disableChevron, disableChevron,
...props ...props
}) => { }: VerticalTabItemProps<T> & {
tabNameKey?: T;
}) {
const router = useRouter(); const router = useRouter();
const { t } = useLocale(); const { t } = useLocale();
let newHref = ""; let newHref = "";
let isCurrent; let isCurrent;
const tabName = props[tabNameKey as keyof typeof props] as string;
const _tabNameKey = tabNameKey || "tabName";
if (href) { if (href) {
newHref = href; newHref = href;
isCurrent = router.asPath === href; isCurrent = router.asPath === href;
} else if (tabName) { } else if (tabName) {
newHref = ""; newHref = "";
isCurrent = router.query.tabName === tabName; isCurrent = router.query[_tabNameKey] === tabName;
} }
const onClick: MouseEventHandler = tabName const onClick: MouseEventHandler = tabName
@ -60,7 +62,7 @@ const VerticalTabItem: FC<VerticalTabItemProps> = ({
router.push({ router.push({
query: { query: {
...router.query, ...router.query,
tabName, [_tabNameKey]: tabName,
}, },
}); });
} }
@ -83,6 +85,8 @@ const VerticalTabItem: FC<VerticalTabItemProps> = ({
)} )}
aria-current={isCurrent ? "page" : undefined}> aria-current={isCurrent ? "page" : undefined}>
{props.icon && ( {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" /> <props.icon className="mr-[10px] h-[16px] w-[16px] self-start stroke-[2px] md:mt-0" />
)} )}
<div> <div>
@ -101,7 +105,7 @@ const VerticalTabItem: FC<VerticalTabItemProps> = ({
</a> </a>
</Link> </Link>
{props.children?.map((child) => ( {props.children?.map((child) => (
<VerticalTabItem key={child.name} {...child} isChild /> <VerticalTabItem tabNameKey={tabNameKey} key={child.name} {...child} isChild />
))} ))}
</> </>
)} )}

View File

@ -1,19 +1,26 @@
import { FC } from "react";
import { classNames } from "@calcom/lib"; import { classNames } from "@calcom/lib";
import VerticalTabItem, { VerticalTabItemProps } from "./VerticalTabItem"; import VerticalTabItem, { VerticalTabItemProps } from "./VerticalTabItem";
export { VerticalTabItem }; export { VerticalTabItem };
export interface NavTabProps { export interface NavTabProps<T extends string> {
tabs: VerticalTabItemProps[]; tabs: VerticalTabItemProps<T>[];
children?: React.ReactNode; children?: React.ReactNode;
className?: string; className?: string;
sticky?: boolean; sticky?: boolean;
tabNameKey?: T;
} }
const NavTabs: FC<NavTabProps> = ({ tabs, className = "", sticky, ...props }) => { const NavTabs = function <T extends string>({
tabs,
tabNameKey,
className = "",
sticky,
...props
}: NavTabProps<T>) {
const _tabNameKey = tabNameKey || "tabName";
return ( return (
<nav <nav
className={classNames( className={classNames(
@ -26,7 +33,7 @@ const NavTabs: FC<NavTabProps> = ({ tabs, className = "", sticky, ...props }) =>
{sticky && <div className="pt-6" />} {sticky && <div className="pt-6" />}
{props.children} {props.children}
{tabs.map((tab, idx) => ( {tabs.map((tab, idx) => (
<VerticalTabItem {...tab} key={idx} /> <VerticalTabItem tabNameKey={_tabNameKey} {...tab} key={idx} />
))} ))}
</nav> </nav>
); );

762
yarn.lock

File diff suppressed because it is too large Load Diff