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

View File

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

View File

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

View File

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

View File

@ -21,7 +21,7 @@
</head>
<script type="module" src="./src/preview.ts"></script>
<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>

View File

@ -252,10 +252,6 @@ export const methods = {
ui: function style(uiConfig: UiConfig) {
// TODO: Create automatic logger for all methods. Useful for debugging.
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;
// In case where parent gives instructions before CalComPlan is set.

View File

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

View File

@ -1,11 +1,11 @@
import React, { ComponentProps } from "react";
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 { HorizontalTabItemProps } from "../navigation/tabs/HorizontalTabItem";
const tabs: VerticalTabItemProps[] = [
const tabs: HorizontalTabItemProps[] = [
{
name: "app_store",
href: "/apps",

View File

@ -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 tabs={tabs} sticky />
<VerticalTabs tabNameKey="tabName" tabs={tabs} sticky />
</div>
<div className="block xl:hidden">
<HorizontalTabs tabs={tabs} />
<HorizontalTabs tabNameKey="tabName" tabs={tabs} />
</div>
<main className="w-full max-w-6xl">{children}</main>
</div>

View File

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

View File

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

View File

@ -1,7 +1,7 @@
import noop from "lodash/noop";
import Link from "next/link";
import { useRouter } from "next/router";
import { FC, Fragment, MouseEventHandler } from "react";
import { Fragment, MouseEventHandler } from "react";
// import { ChevronRight } from "react-feather";
import classNames from "@calcom/lib/classNames";
@ -9,12 +9,12 @@ import { useLocale } from "@calcom/lib/hooks/useLocale";
import { SVGComponent } from "@calcom/types/SVGComponent";
import { Icon } from "@calcom/ui/Icon";
export type VerticalTabItemProps = {
export type VerticalTabItemProps<T extends string = "tabName"> = {
name: string;
info?: string;
icon?: SVGComponent;
disabled?: boolean;
children?: VerticalTabItemProps[];
children?: VerticalTabItemProps<T>[];
textClassNames?: string;
className?: string;
isChild?: boolean;
@ -26,32 +26,34 @@ export type VerticalTabItemProps = {
href: string;
tabName?: never;
}
| {
| ({
href?: never;
/** If you want to change the path as per current tab */
tabName: string;
}
} & Partial<Record<T, string>>)
);
const VerticalTabItem: FC<VerticalTabItemProps> = ({
const VerticalTabItem = function <T extends string>({
name,
href,
tabName,
tabNameKey,
info,
isChild,
disableChevron,
...props
}) => {
}: VerticalTabItemProps<T> & {
tabNameKey?: T;
}) {
const router = useRouter();
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 === href;
} else if (tabName) {
newHref = "";
isCurrent = router.query.tabName === tabName;
isCurrent = router.query[_tabNameKey] === tabName;
}
const onClick: MouseEventHandler = tabName
@ -60,7 +62,7 @@ const VerticalTabItem: FC<VerticalTabItemProps> = ({
router.push({
query: {
...router.query,
tabName,
[_tabNameKey]: tabName,
},
});
}
@ -83,6 +85,8 @@ const VerticalTabItem: FC<VerticalTabItemProps> = ({
)}
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>
@ -101,7 +105,7 @@ const VerticalTabItem: FC<VerticalTabItemProps> = ({
</a>
</Link>
{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 VerticalTabItem, { VerticalTabItemProps } from "./VerticalTabItem";
export { VerticalTabItem };
export interface NavTabProps {
tabs: VerticalTabItemProps[];
export interface NavTabProps<T extends string> {
tabs: VerticalTabItemProps<T>[];
children?: React.ReactNode;
className?: string;
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 (
<nav
className={classNames(
@ -26,7 +33,7 @@ const NavTabs: FC<NavTabProps> = ({ tabs, className = "", sticky, ...props }) =>
{sticky && <div className="pt-6" />}
{props.children}
{tabs.map((tab, idx) => (
<VerticalTabItem {...tab} key={idx} />
<VerticalTabItem tabNameKey={_tabNameKey} {...tab} key={idx} />
))}
</nav>
);

762
yarn.lock

File diff suppressed because it is too large Load Diff