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:
parent
35c2f9046a
commit
a3462657db
|
@ -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
|
||||||
|
|
|
@ -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>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
@ -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";
|
||||||
|
|
|
@ -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"
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
|
@ -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>
|
||||||
|
|
|
@ -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.
|
||||||
|
|
|
@ -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;
|
||||||
|
|
|
@ -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",
|
||||||
|
|
|
@ -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>
|
||||||
|
|
|
@ -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,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
|
@ -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>
|
||||||
|
|
|
@ -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 />
|
||||||
))}
|
))}
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
|
|
|
@ -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>
|
||||||
);
|
);
|
||||||
|
|
Loading…
Reference in New Issue
Block a user