feat: add "React (Atom)" to embed generator and rename rest (#20618)
* fix: trigger lingo.dev by removing duplicate value * feat: add support for React Atom embed options * fix: update locator for React code tab in embed code generator E2E tests * fix: update embed tab names to clarify iframe usage * fix failing test * fix: indentation and installation commands for atoms * fix: add note about iframe usage in Preview tab * refactor: replace Cal component with BookerEmbed in EmbedCodes * fix: Add EmbedConfig type import in Embed.tsx - Added missing EmbedConfig type to the existing types import - Resolves TypeScript error "Cannot find name 'EmbedConfig'" * fix: update BookerEmbed usage in EmbedCodes component * fix: enhance BookerEmbed implementation and update installation instructions in EmbedTabs * fix: improve comments in EmbedCodes for clarity on layout options and styling references * fix: streamline BookerEmbed usage in EmbedCodes with improved class handling and layout options * fix: embed type selection * fix: update layout selection in EmbedCodes to use dynamic configuration * fix: update embed configuration to use theme instead of layout * fix: conditionally render embed options based on if we are selecting atom * fix: use simple props instead of embed specific logic to fix the active embed bug * Fix double tabs activeness * Revert isCurrent change --------- Co-authored-by: Hariom Balhara <hariombalhara@gmail.com>
This commit is contained in:
1 parent
f163e4638b
commit
acf3a7c54e
4 files changed
+349
-156
No files matched your search
+122
-104
@@ -37,12 +37,13 @@ import { HorizontalTabs } from "@calcom/ui/components/navigation";
|
||||
import { showToast } from "@calcom/ui/components/toast";
|
||||
|
||||
import { useBookerTime } from "../bookings/Booker/components/hooks/useBookerTime";
|
||||
import { EmbedTabName } from "./lib/EmbedTabs";
|
||||
import { buildCssVarsPerTheme } from "./lib/buildCssVarsPerTheme";
|
||||
import { EmbedTheme } from "./lib/constants";
|
||||
import { getDimension } from "./lib/getDimension";
|
||||
import { useEmbedDialogCtx } from "./lib/hooks/useEmbedDialogCtx";
|
||||
import { useEmbedParams } from "./lib/hooks/useEmbedParams";
|
||||
import type { EmbedTabs, EmbedType, EmbedTypes, PreviewState } from "./types";
|
||||
import type { EmbedTabs, EmbedType, EmbedTypes, PreviewState, EmbedConfig } from "./types";
|
||||
|
||||
type EventType = RouterOutputs["viewer"]["eventTypes"]["get"]["eventType"] | undefined;
|
||||
type EmbedDialogProps = {
|
||||
@@ -932,55 +933,58 @@ const EmbedTypeCodeAndPreviewDialogContent = ({
|
||||
open={isEmbedCustomizationOpen}
|
||||
onOpenChange={() => setIsEmbedCustomizationOpen((val) => !val)}>
|
||||
<CollapsibleContent className="text-sm">
|
||||
<div className={classNames(embedType === "inline" ? "block" : "hidden")}>
|
||||
{/*TODO: Add Auto/Fixed toggle from Figma */}
|
||||
<div className="text-default mb-[9px] text-sm">Window sizing</div>
|
||||
<div className="justify-left mb-6 flex items-center !font-normal ">
|
||||
<div className="mr-[9px]">
|
||||
{/* Conditionally render Window Sizing only if inline embed AND NOT React Atom */}
|
||||
{embedType === "inline" && embedParams.embedTabName !== EmbedTabName.ATOM_REACT && (
|
||||
<div>
|
||||
{/*TODO: Add Auto/Fixed toggle from Figma */}
|
||||
<div className="text-default mb-[9px] text-sm">Window sizing</div>
|
||||
<div className="justify-left mb-6 flex items-center !font-normal ">
|
||||
<div className="mr-[9px]">
|
||||
<TextField
|
||||
labelProps={{ className: "hidden" }}
|
||||
className="focus:ring-offset-0"
|
||||
required
|
||||
value={previewState.inline.width}
|
||||
onChange={(e) => {
|
||||
setPreviewState((previewState) => {
|
||||
const width = e.target.value || "100%";
|
||||
|
||||
return {
|
||||
...previewState,
|
||||
inline: {
|
||||
...previewState.inline,
|
||||
width,
|
||||
},
|
||||
};
|
||||
});
|
||||
}}
|
||||
addOnLeading={<>W</>}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<TextField
|
||||
labelProps={{ className: "hidden" }}
|
||||
className="focus:ring-offset-0"
|
||||
value={previewState.inline.height}
|
||||
required
|
||||
value={previewState.inline.width}
|
||||
onChange={(e) => {
|
||||
setPreviewState((previewState) => {
|
||||
const width = e.target.value || "100%";
|
||||
const height = e.target.value || "100%";
|
||||
|
||||
setPreviewState((previewState) => {
|
||||
return {
|
||||
...previewState,
|
||||
inline: {
|
||||
...previewState.inline,
|
||||
width,
|
||||
height,
|
||||
},
|
||||
};
|
||||
});
|
||||
}}
|
||||
addOnLeading={<>W</>}
|
||||
addOnLeading={<>H</>}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<TextField
|
||||
labelProps={{ className: "hidden" }}
|
||||
className="focus:ring-offset-0"
|
||||
value={previewState.inline.height}
|
||||
required
|
||||
onChange={(e) => {
|
||||
const height = e.target.value || "100%";
|
||||
|
||||
setPreviewState((previewState) => {
|
||||
return {
|
||||
...previewState,
|
||||
inline: {
|
||||
...previewState.inline,
|
||||
height,
|
||||
},
|
||||
};
|
||||
});
|
||||
}}
|
||||
addOnLeading={<>H</>}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
<div
|
||||
className={classNames(
|
||||
"items-center justify-between",
|
||||
@@ -1105,51 +1109,53 @@ const EmbedTypeCodeAndPreviewDialogContent = ({
|
||||
onOpenChange={() => setIsBookingCustomizationOpen((val) => !val)}>
|
||||
<CollapsibleContent>
|
||||
<div className="text-sm">
|
||||
<Label className="mb-6">
|
||||
<div className="mb-2">Theme</div>
|
||||
<Select
|
||||
className="w-full"
|
||||
defaultValue={ThemeOptions[0]}
|
||||
components={{
|
||||
Control: ThemeSelectControl,
|
||||
IndicatorSeparator: () => null,
|
||||
}}
|
||||
onChange={(option) => {
|
||||
if (!option) {
|
||||
return;
|
||||
}
|
||||
setPreviewState((previewState) => {
|
||||
return {
|
||||
...previewState,
|
||||
inline: {
|
||||
...previewState.inline,
|
||||
config: {
|
||||
...(previewState.inline.config ?? {}),
|
||||
theme: option.value,
|
||||
{/* Conditionally render EmbedTheme only if NOT React Atom */}
|
||||
{embedParams.embedTabName !== EmbedTabName.ATOM_REACT && (
|
||||
<Label className="mb-6">
|
||||
<div className="mb-2">EmbedTheme</div>
|
||||
<Select
|
||||
className="w-full"
|
||||
defaultValue={ThemeOptions[0]}
|
||||
components={{
|
||||
Control: ThemeSelectControl,
|
||||
IndicatorSeparator: () => null,
|
||||
}}
|
||||
onChange={(option) => {
|
||||
if (!option) {
|
||||
return;
|
||||
}
|
||||
setPreviewState((previewState) => {
|
||||
// Ensure theme is updated in config for all embed types
|
||||
const newConfig = (currentConfig?: EmbedConfig) => ({
|
||||
...(currentConfig ?? {}),
|
||||
theme: option.value,
|
||||
});
|
||||
return {
|
||||
...previewState,
|
||||
inline: {
|
||||
...previewState.inline,
|
||||
config: newConfig(previewState.inline.config),
|
||||
},
|
||||
},
|
||||
floatingPopup: {
|
||||
...previewState.floatingPopup,
|
||||
config: {
|
||||
...(previewState.floatingPopup.config ?? {}),
|
||||
theme: option.value,
|
||||
floatingPopup: {
|
||||
...previewState.floatingPopup,
|
||||
config: newConfig(previewState.floatingPopup.config),
|
||||
},
|
||||
},
|
||||
elementClick: {
|
||||
...previewState.elementClick,
|
||||
config: {
|
||||
...(previewState.elementClick.config ?? {}),
|
||||
theme: option.value,
|
||||
elementClick: {
|
||||
...previewState.elementClick,
|
||||
config: newConfig(previewState.elementClick.config),
|
||||
},
|
||||
},
|
||||
theme: option.value,
|
||||
};
|
||||
});
|
||||
}}
|
||||
options={ThemeOptions}
|
||||
/>
|
||||
</Label>
|
||||
{!eventTypeHideOptionDisabled ? (
|
||||
// Keep updating top-level theme for preview iframe
|
||||
theme: option.value,
|
||||
};
|
||||
});
|
||||
}}
|
||||
options={ThemeOptions}
|
||||
/>
|
||||
</Label>
|
||||
)}
|
||||
{/* Conditionally render Hide Details Switch only if NOT Atom embed AND not disabled by prop */}
|
||||
{!eventTypeHideOptionDisabled &&
|
||||
embedParams.embedTabName !== EmbedTabName.ATOM_REACT ? (
|
||||
<div className="mb-6 flex items-center justify-start space-x-2 rtl:space-x-reverse">
|
||||
<Switch
|
||||
checked={previewState.hideEventTypeDetails}
|
||||
@@ -1165,31 +1171,33 @@ const EmbedTypeCodeAndPreviewDialogContent = ({
|
||||
<div className="text-default text-sm">{t("hide_eventtype_details")}</div>
|
||||
</div>
|
||||
) : null}
|
||||
{[
|
||||
{ name: "brandColor", title: "light_brand_color" },
|
||||
{ name: "darkBrandColor", title: "dark_brand_color" },
|
||||
// { name: "lightColor", title: "Light Color" },
|
||||
// { name: "lighterColor", title: "Lighter Color" },
|
||||
// { name: "lightestColor", title: "Lightest Color" },
|
||||
// { name: "highlightColor", title: "Highlight Color" },
|
||||
// { name: "medianColor", title: "Median Color" },
|
||||
].map((palette) => (
|
||||
<Label key={palette.name} className="mb-6">
|
||||
<div className="mb-2">{t(palette.title)}</div>
|
||||
<div className="w-full">
|
||||
<ColorPicker
|
||||
popoverAlign="start"
|
||||
container={dialogContentRef?.current ?? undefined}
|
||||
defaultValue={paletteDefaultValue(palette.name)}
|
||||
onChange={(color) => {
|
||||
addToPalette({
|
||||
[palette.name as keyof (typeof previewState)["palette"]]: color,
|
||||
});
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
</Label>
|
||||
))}
|
||||
{/* Conditionally render Brand Colors only if NOT React Atom */}
|
||||
{embedParams.embedTabName !== EmbedTabName.ATOM_REACT &&
|
||||
[
|
||||
{ name: "brandColor", title: "light_brand_color" },
|
||||
{ name: "darkBrandColor", title: "dark_brand_color" },
|
||||
// { name: "lightColor", title: "Light Color" },
|
||||
// { name: "lighterColor", title: "Lighter Color" },
|
||||
// { name: "lightestColor", title: "Lightest Color" },
|
||||
// { name: "highlightColor", title: "Highlight Color" },
|
||||
// { name: "medianColor", title: "Median Color" },
|
||||
].map((palette) => (
|
||||
<Label key={palette.name} className="mb-6">
|
||||
<div className="mb-2">{t(palette.title)}</div>
|
||||
<div className="w-full">
|
||||
<ColorPicker
|
||||
popoverAlign="start"
|
||||
container={dialogContentRef?.current ?? undefined}
|
||||
defaultValue={paletteDefaultValue(palette.name)}
|
||||
onChange={(color) => {
|
||||
addToPalette({
|
||||
[palette.name as keyof (typeof previewState)["palette"]]: color,
|
||||
});
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
</Label>
|
||||
))}
|
||||
<Label className="mb-6">
|
||||
<div className="mb-2">{t("layout")}</div>
|
||||
<Select
|
||||
@@ -1200,16 +1208,26 @@ const EmbedTypeCodeAndPreviewDialogContent = ({
|
||||
return;
|
||||
}
|
||||
setPreviewState((previewState) => {
|
||||
const config = {
|
||||
...(previewState.floatingPopup.config ?? {}),
|
||||
// Ensure layout is updated in config for all embed types
|
||||
const newConfig = (currentConfig?: EmbedConfig) => ({
|
||||
...(currentConfig ?? {}),
|
||||
layout: option.value,
|
||||
};
|
||||
});
|
||||
return {
|
||||
...previewState,
|
||||
inline: {
|
||||
...previewState.inline,
|
||||
config: newConfig(previewState.inline.config),
|
||||
},
|
||||
floatingPopup: {
|
||||
...previewState.floatingPopup,
|
||||
config,
|
||||
config: newConfig(previewState.floatingPopup.config),
|
||||
},
|
||||
elementClick: {
|
||||
...previewState.elementClick,
|
||||
config: newConfig(previewState.elementClick.config),
|
||||
},
|
||||
// Keep updating top-level layout for preview iframe
|
||||
layout: option.value,
|
||||
};
|
||||
});
|
||||
|
||||
@@ -32,23 +32,23 @@ export const Codes = {
|
||||
const namespaceProp = `${namespace ? `namespace="${namespace}"` : ""}`;
|
||||
const argumentForGetCalApi = getArgumentForGetCalApi(namespace);
|
||||
return code`
|
||||
import Cal, { getCalApi } from "@calcom/embed-react";
|
||||
import { useEffect } from "react";
|
||||
export default function MyApp() {
|
||||
useEffect(()=>{
|
||||
(async function () {
|
||||
const cal = await getCalApi(${argumentForGetCalApi ? JSON.stringify(argumentForGetCalApi) : ""});
|
||||
${uiInstructionCode}
|
||||
})();
|
||||
}, [])
|
||||
return <Cal ${namespaceProp}
|
||||
calLink="${calLink}"
|
||||
style={{width:"${width}",height:"${height}",overflow:"scroll"}}
|
||||
config={${JSON.stringify(previewState.config)}}
|
||||
${doWeNeedCalOriginProp(embedCalOrigin) ? ` calOrigin="${embedCalOrigin}"` : ""}
|
||||
${IS_SELF_HOSTED ? `embedJsUrl="${embedLibUrl}"` : ""}
|
||||
/>;
|
||||
};`;
|
||||
import Cal, { getCalApi } from "@calcom/embed-react";
|
||||
import { useEffect } from "react";
|
||||
export default function MyApp() {
|
||||
useEffect(() => {
|
||||
(async function () {
|
||||
const cal = await getCalApi(${argumentForGetCalApi ? JSON.stringify(argumentForGetCalApi) : ""});
|
||||
${uiInstructionCode}
|
||||
})();
|
||||
}, [])
|
||||
return <Cal ${namespaceProp}
|
||||
calLink="${calLink}"
|
||||
style={{width:"${width}",height:"${height}",overflow:"scroll"}}
|
||||
config={${JSON.stringify(previewState.config)}}
|
||||
${doWeNeedCalOriginProp(embedCalOrigin) ? `calOrigin="${embedCalOrigin}"` : ""}
|
||||
${IS_SELF_HOSTED ? `embedJsUrl="${embedLibUrl}"` : ""}
|
||||
/>;
|
||||
};`;
|
||||
},
|
||||
"floating-popup": ({
|
||||
calLink,
|
||||
@@ -70,17 +70,17 @@ export const Codes = {
|
||||
...previewState,
|
||||
});
|
||||
return code`
|
||||
import { getCalApi } from "@calcom/embed-react";
|
||||
import { useEffect } from "react";
|
||||
export default function MyApp() {
|
||||
useEffect(()=>{
|
||||
(async function () {
|
||||
const cal = await getCalApi(${argumentForGetCalApi ? JSON.stringify(argumentForGetCalApi) : ""});
|
||||
${getApiNameForReactSnippet({ mainApiName: "cal" })}("floatingButton", ${floatingButtonArg});
|
||||
${uiInstructionCode}
|
||||
})();
|
||||
}, [])
|
||||
};`;
|
||||
import { getCalApi } from "@calcom/embed-react";
|
||||
import { useEffect } from "react";
|
||||
export default function MyApp() {
|
||||
useEffect(() => {
|
||||
(async function () {
|
||||
const cal = await getCalApi(${argumentForGetCalApi ? JSON.stringify(argumentForGetCalApi) : ""});
|
||||
${getApiNameForReactSnippet({ mainApiName: "cal" })}("floatingButton", ${floatingButtonArg});
|
||||
${uiInstructionCode}
|
||||
})();
|
||||
}, [])
|
||||
};`;
|
||||
},
|
||||
"element-click": ({
|
||||
calLink,
|
||||
@@ -97,21 +97,138 @@ export const Codes = {
|
||||
}) => {
|
||||
const argumentForGetCalApi = getArgumentForGetCalApi(namespace);
|
||||
return code`
|
||||
import { getCalApi } from "@calcom/embed-react";
|
||||
import { useEffect } from "react";
|
||||
export default function MyApp() {
|
||||
useEffect(()=>{
|
||||
(async function () {
|
||||
const cal = await getCalApi(${argumentForGetCalApi ? JSON.stringify(argumentForGetCalApi) : ""});
|
||||
${uiInstructionCode}
|
||||
})();
|
||||
}, [])
|
||||
return <button data-cal-namespace="${namespace}"
|
||||
data-cal-link="${calLink}"
|
||||
${doWeNeedCalOriginProp(embedCalOrigin) ? ` data-cal-origin="${embedCalOrigin}"` : ""}
|
||||
${`data-cal-config='${JSON.stringify(previewState.config)}'`}
|
||||
>Click me</button>;
|
||||
};`;
|
||||
import { getCalApi } from "@calcom/embed-react";
|
||||
import { useEffect } from "react";
|
||||
export default function MyApp() {
|
||||
useEffect(() => {
|
||||
(async function () {
|
||||
const cal = await getCalApi(${argumentForGetCalApi ? JSON.stringify(argumentForGetCalApi) : ""});
|
||||
${uiInstructionCode}
|
||||
})();
|
||||
}, [])
|
||||
return <button data-cal-namespace="${namespace}"
|
||||
data-cal-link="${calLink}"
|
||||
${doWeNeedCalOriginProp(embedCalOrigin) ? `data-cal-origin="${embedCalOrigin}"` : ""}
|
||||
${`data-cal-config='${JSON.stringify(previewState.config)}'`}
|
||||
>Click me</button>;
|
||||
};`;
|
||||
},
|
||||
},
|
||||
"react-atom": {
|
||||
inline: ({
|
||||
calLink,
|
||||
uiInstructionCode,
|
||||
previewState,
|
||||
embedCalOrigin,
|
||||
namespace,
|
||||
}: {
|
||||
calLink: string;
|
||||
uiInstructionCode: string;
|
||||
previewState: PreviewState["inline"];
|
||||
embedCalOrigin: string;
|
||||
namespace: string;
|
||||
}) => {
|
||||
return code`
|
||||
import { BookerEmbed } from "@calcom/atoms";
|
||||
|
||||
// You might need to define or import BookerProps depending on your setup
|
||||
// For example: type BookerProps = { eventTypeSlug: string; calUsername: string; /* other props */ };
|
||||
export default function Booker( props : BookerProps ) {
|
||||
return (
|
||||
<>
|
||||
<BookerEmbed
|
||||
// Use the parsed username and event slug from calLink
|
||||
eventSlug={eventSlug}
|
||||
// layout can be of three types: COLUMN_VIEW, MONTH_VIEW or WEEK_VIEW,
|
||||
// you can choose whichever you prefer
|
||||
view="${previewState.config?.layout || "MONTH_VIEW"}"
|
||||
username={calUsername}
|
||||
customClassNames={{
|
||||
bookerContainer: "border-subtle border",
|
||||
}}
|
||||
onCreateBookingSuccess={() => {
|
||||
console.log("booking created successfully");
|
||||
}}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
};`;
|
||||
},
|
||||
"floating-popup": ({
|
||||
calLink,
|
||||
uiInstructionCode,
|
||||
previewState,
|
||||
embedCalOrigin,
|
||||
namespace,
|
||||
}: {
|
||||
calLink: string;
|
||||
embedCalOrigin: string;
|
||||
uiInstructionCode: string;
|
||||
namespace: string;
|
||||
previewState: PreviewState["floatingPopup"];
|
||||
}) => {
|
||||
return code`
|
||||
import { BookerEmbed } from "@calcom/atoms";
|
||||
|
||||
// You might need to define or import BookerProps depending on your setup
|
||||
export default function Booker( props : BookerProps ) {
|
||||
return (
|
||||
<>
|
||||
<BookerEmbed
|
||||
// Use the parsed username and event slug from calLink
|
||||
eventSlug={eventSlug}
|
||||
// layout can be of three types: COLUMN_VIEW, MONTH_VIEW or WEEK_VIEW,
|
||||
// you can choose whichever you prefer
|
||||
view="${previewState.config?.layout || "MONTH_VIEW"}"
|
||||
username={calUsername}
|
||||
customClassNames={{
|
||||
bookerContainer: "border-subtle border",
|
||||
}}
|
||||
onCreateBookingSuccess={() => {
|
||||
console.log("booking created successfully");
|
||||
}}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
};`;
|
||||
},
|
||||
"element-click": ({
|
||||
calLink,
|
||||
uiInstructionCode,
|
||||
previewState,
|
||||
embedCalOrigin,
|
||||
namespace,
|
||||
}: {
|
||||
calLink: string;
|
||||
uiInstructionCode: string;
|
||||
previewState: PreviewState["elementClick"];
|
||||
embedCalOrigin: string;
|
||||
namespace: string;
|
||||
}) => {
|
||||
return code`
|
||||
import { BookerEmbed } from "@calcom/atoms";
|
||||
|
||||
// You might need to define or import BookerProps depending on your setup
|
||||
export default function Booker( props : BookerProps ) {
|
||||
return (
|
||||
<>
|
||||
<BookerEmbed
|
||||
// Use the parsed username and event slug from calLink
|
||||
eventSlug={eventSlug}
|
||||
// layout can be of three types: COLUMN_VIEW, MONTH_VIEW or WEEK_VIEW,
|
||||
// you can choose whichever you prefer
|
||||
view="${previewState.config?.layout || "MONTH_VIEW"}"
|
||||
username={calUsername}
|
||||
customClassNames={{
|
||||
bookerContainer: "border-subtle border",
|
||||
}}
|
||||
onCreateBookingSuccess={() => {
|
||||
console.log("booking created successfully");
|
||||
}}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
};`;
|
||||
},
|
||||
},
|
||||
HTML: {
|
||||
@@ -176,8 +293,7 @@ export const Codes = {
|
||||
${uiInstructionCode}`;
|
||||
},
|
||||
},
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
} satisfies Record<string, Record<string, (...args: any[]) => string>>;
|
||||
};
|
||||
|
||||
/**
|
||||
* It allows us to show code with certain reusable blocks indented according to the block variable placement
|
||||
|
||||
@@ -15,9 +15,15 @@ import { getApiNameForReactSnippet, getApiNameForVanillaJsSnippet } from "./getA
|
||||
import { getDimension } from "./getDimension";
|
||||
import { useEmbedCalOrigin } from "./hooks";
|
||||
|
||||
export const enum EmbedTabName {
|
||||
HTML = "embed-code",
|
||||
IFRAME_REACT = "embed-react",
|
||||
ATOM_REACT = "embed-atom-react",
|
||||
}
|
||||
|
||||
export const tabs = [
|
||||
{
|
||||
name: "HTML",
|
||||
name: "HTML (iframe)",
|
||||
href: "embedTabName=embed-code",
|
||||
icon: "code" as const,
|
||||
type: "code",
|
||||
@@ -74,7 +80,7 @@ export const tabs = [
|
||||
}),
|
||||
},
|
||||
{
|
||||
name: "React",
|
||||
name: "React (iframe)",
|
||||
href: "embedTabName=embed-react",
|
||||
"data-testid": "react",
|
||||
icon: "code" as const,
|
||||
@@ -104,11 +110,11 @@ export const tabs = [
|
||||
style={{ resize: "none", overflow: "auto" }}
|
||||
value={`/* First make sure that you have installed the package */
|
||||
|
||||
/* If you are using yarn */
|
||||
// yarn add @calcom/embed-react
|
||||
/* If you are using yarn */
|
||||
// yarn add @calcom/embed-react
|
||||
|
||||
/* If you are using npm */
|
||||
// npm install @calcom/embed-react
|
||||
/* If you are using npm */
|
||||
// npm install @calcom/embed-react
|
||||
${getEmbedTypeSpecificString({
|
||||
embedFramework: "react",
|
||||
embedType,
|
||||
@@ -123,6 +129,59 @@ export const tabs = [
|
||||
);
|
||||
}),
|
||||
},
|
||||
{
|
||||
name: "React (Atom)",
|
||||
href: `embedTabName=${EmbedTabName.ATOM_REACT}`,
|
||||
"data-testid": "react-atom",
|
||||
icon: "code" as const,
|
||||
type: "code",
|
||||
Component: forwardRef<
|
||||
HTMLTextAreaElement | HTMLIFrameElement | null,
|
||||
{ embedType: EmbedType; calLink: string; previewState: PreviewState; namespace: string }
|
||||
>(function EmbedReactAtom({ embedType, calLink, previewState, namespace }, ref) {
|
||||
const { t } = useLocale();
|
||||
const embedCalOrigin = useEmbedCalOrigin();
|
||||
|
||||
if (ref instanceof Function || !ref) {
|
||||
return null;
|
||||
}
|
||||
if (ref.current && !(ref.current instanceof HTMLTextAreaElement)) {
|
||||
return null;
|
||||
}
|
||||
return (
|
||||
<>
|
||||
<small className="text-subtle flex py-2">{t("create_update_react_component")}</small>
|
||||
<div className="text-default bg-attention mb-3 rounded-md p-3 text-sm">
|
||||
<strong>Note:</strong> The preview shown in the Preview tab is generated using the iframe
|
||||
approach.
|
||||
</div>
|
||||
<TextArea
|
||||
data-testid={`${EmbedTabName.ATOM_REACT}`}
|
||||
ref={ref as typeof ref & MutableRefObject<HTMLTextAreaElement>}
|
||||
name={`${EmbedTabName.ATOM_REACT}`}
|
||||
className="text-default bg-default h-[calc(100%-50px)] font-mono"
|
||||
readOnly
|
||||
style={{ resize: "none", overflow: "auto" }}
|
||||
value={`/* First make sure that you have installed the package */
|
||||
|
||||
/* If you are using yarn */
|
||||
// yarn add @calcom/atoms
|
||||
|
||||
/* If you are using npm */
|
||||
// npm install @calcom/atoms
|
||||
${getEmbedTypeSpecificString({
|
||||
embedFramework: "react-atom" as EmbedFramework,
|
||||
embedType,
|
||||
calLink,
|
||||
previewState,
|
||||
embedCalOrigin,
|
||||
namespace,
|
||||
})}`}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
}),
|
||||
},
|
||||
{
|
||||
name: "Preview",
|
||||
href: "embedTabName=embed-preview",
|
||||
|
||||
+1
-1
@@ -53,6 +53,6 @@ export type PreviewState = {
|
||||
layout: BookerLayouts;
|
||||
};
|
||||
|
||||
export type EmbedFramework = "react" | "HTML";
|
||||
export type EmbedFramework = "react" | "react-atom" | "HTML";
|
||||
export type EmbedTabs = typeof tabs;
|
||||
export type EmbedTypes = ReturnType<typeof useEmbedTypes>;
|
||||
Reference in new issue
Block a user