Feat: Support setting custom css variables through embed API. (#8169)
* Add booker border specific custom variables * Support configuring cssVars through embed API * Update apps/web/pages/booking/[uid].tsx Co-authored-by: sean-brydon <55134778+sean-brydon@users.noreply.github.com> --------- Co-authored-by: sean-brydon <55134778+sean-brydon@users.noreply.github.com>
This commit is contained in:
co-authored by
sean-brydon
parent
17f48614ff
commit
0e22e92cab
@@ -191,7 +191,7 @@ const AvailabilityPage = ({ profile, eventType, ...restProps }: Props) => {
|
||||
style={availabilityDatePickerEmbedStyles}
|
||||
className={classNames(
|
||||
isBackgroundTransparent ? "" : "bg-default dark:bg-muted pb-4 md:pb-0",
|
||||
"border-subtle md:rounded-md md:border",
|
||||
"border-booker md:border-booker-width md:rounded-md",
|
||||
isEmbed && "mx-auto"
|
||||
)}>
|
||||
<div className="md:flex">
|
||||
|
||||
@@ -528,8 +528,8 @@ const BookingPage = ({
|
||||
<div
|
||||
className={classNames(
|
||||
"main",
|
||||
isBackgroundTransparent ? "" : "dark:bg-darkgray-100 bg-default dark:border",
|
||||
"border-subtle rounded-md sm:border"
|
||||
isBackgroundTransparent ? "" : "bg-default",
|
||||
"border-booker sm:border-booker-width rounded-md"
|
||||
)}>
|
||||
<div className="sm:flex">
|
||||
{showEventTypeDetails && (
|
||||
|
||||
@@ -122,7 +122,7 @@ export default function User(props: inferSSRProps<typeof getServerSideProps> & E
|
||||
<main
|
||||
className={classNames(
|
||||
shouldAlignCentrally ? "mx-auto" : "",
|
||||
isEmbed ? " border-bookinglightest bg-default rounded-md border" : "",
|
||||
isEmbed ? " border-booker border-booker-width bg-default rounded-md border" : "",
|
||||
"max-w-3xl py-24 px-4"
|
||||
)}>
|
||||
{isSingleUser && ( // When we deal with a single user, not dynamic group
|
||||
|
||||
@@ -5,6 +5,7 @@ import Head from "next/head";
|
||||
import Script from "next/script";
|
||||
|
||||
import "@calcom/embed-core/src/embed-iframe";
|
||||
import { useEmbedUiConfig } from "@calcom/embed-core/src/embed-iframe";
|
||||
import LicenseRequired from "@calcom/features/ee/common/components/LicenseRequired";
|
||||
import { trpc } from "@calcom/trpc/react";
|
||||
|
||||
@@ -27,6 +28,18 @@ const calFont = localFont({
|
||||
function MyApp(props: AppProps) {
|
||||
const { Component, pageProps, err, router } = props;
|
||||
let pageStatus = "200";
|
||||
const { cssVarsPerTheme } = useEmbedUiConfig();
|
||||
const cssVarsStyle = [];
|
||||
if (cssVarsPerTheme) {
|
||||
for (const [themeName, cssVars] of Object.entries(cssVarsPerTheme)) {
|
||||
cssVarsStyle.push(`.${themeName} {`);
|
||||
for (const [cssVarName, value] of Object.entries(cssVars)) {
|
||||
cssVarsStyle.push(`--${cssVarName}: ${value};`);
|
||||
}
|
||||
cssVarsStyle.push(`}`);
|
||||
}
|
||||
}
|
||||
|
||||
if (router.pathname === "/404") {
|
||||
pageStatus = "404";
|
||||
} else if (router.pathname === "/500") {
|
||||
@@ -62,6 +75,12 @@ function MyApp(props: AppProps) {
|
||||
--font-cal: ${calFont.style.fontFamily};
|
||||
}
|
||||
`}</style>
|
||||
|
||||
<style jsx global>
|
||||
{`
|
||||
${cssVarsStyle.join("")}
|
||||
`}
|
||||
</style>
|
||||
<Head>
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1" />
|
||||
</Head>
|
||||
|
||||
@@ -325,7 +325,7 @@ export default function Success(props: SuccessProps) {
|
||||
<div
|
||||
className={classNames(
|
||||
"main inline-block transform overflow-hidden rounded-lg border sm:my-8 sm:max-w-xl",
|
||||
isBackgroundTransparent ? "" : " bg-default dark:bg-muted border-subtle",
|
||||
!isBackgroundTransparent && " bg-default dark:bg-muted border-booker border-booker-width",
|
||||
"px-8 pt-5 pb-4 text-left align-bottom transition-all sm:w-full sm:py-8 sm:align-middle"
|
||||
)}
|
||||
role="dialog"
|
||||
|
||||
@@ -1,7 +1,9 @@
|
||||
@tailwind base;
|
||||
@tailwind components;
|
||||
@tailwind utilities;
|
||||
|
||||
/**
|
||||
* Add css variables here as well for light mode in addition to tailwind.config to avoid FOUC
|
||||
*/
|
||||
:root {
|
||||
/* background */
|
||||
|
||||
@@ -21,6 +23,7 @@
|
||||
--cal-border-emphasis: #9ca3af;
|
||||
--cal-border: #d1d5db;
|
||||
--cal-border-subtle: #e5e7eb;
|
||||
--cal-border-booker: #e5e7eb;
|
||||
--cal-border-muted: #f3f4f6;
|
||||
|
||||
/* Content/Text */
|
||||
@@ -62,6 +65,7 @@
|
||||
--cal-border-emphasis: #575757;
|
||||
--cal-border: #444444;
|
||||
--cal-border-subtle: #2b2b2b;
|
||||
--cal-border-booker: #2b2b2b;
|
||||
--cal-border-muted: #1c1c1c;
|
||||
|
||||
/* Content/Text */
|
||||
|
||||
@@ -120,7 +120,7 @@ function RoutingForm({ form, profile, ...restProps }: inferSSRProps<typeof getSe
|
||||
</Head>
|
||||
<div className={classNames("mx-auto my-0 max-w-3xl", isEmbed ? "" : "md:my-24")}>
|
||||
<div className="w-full max-w-4xl ltr:mr-2 rtl:ml-2">
|
||||
<div className="main border-bookinglightest dark:bg-darkgray-100 sm:border-subtle bg-default mx-0 rounded-md p-4 py-6 sm:-mx-4 sm:px-8 md:border">
|
||||
<div className="main border-booker md:border-booker-width dark:bg-muted bg-default mx-0 rounded-md p-4 py-6 sm:-mx-4 sm:px-8 ">
|
||||
<Toaster position="bottom-right" />
|
||||
|
||||
<form onSubmit={handleOnSubmit}>
|
||||
|
||||
@@ -2,7 +2,7 @@ const plugin = require("tailwindcss/plugin");
|
||||
const { fontFamily } = require("tailwindcss/defaultTheme");
|
||||
|
||||
/** @type {import('tailwindcss').Config} */
|
||||
|
||||
const subtleColor = "#E5E7EB";
|
||||
module.exports = {
|
||||
content: [
|
||||
"./pages/**/*.{js,ts,jsx,tsx}",
|
||||
@@ -58,8 +58,9 @@ module.exports = {
|
||||
borderColor: {
|
||||
emphasis: "var(--cal-border-emphasis, #9CA3AF)",
|
||||
default: "var(--cal-border, #D1D5DB)",
|
||||
subtle: "var(--cal-border-subtle, #E5E7EB)",
|
||||
subtle: `var(--cal-border-subtle, ${subtleColor})`,
|
||||
muted: "var(--cal-border-muted, #F3F4F6)",
|
||||
booker: `var(--cal-border-booker, ${subtleColor})`,
|
||||
},
|
||||
textColor: {
|
||||
emphasis: "var(--cal-text-emphasis, #111827)",
|
||||
@@ -106,6 +107,9 @@ module.exports = {
|
||||
boxShadow: {
|
||||
dropdown: "0px 2px 6px -1px rgba(0, 0, 0, 0.08)",
|
||||
},
|
||||
borderWidth: {
|
||||
"booker-width": "var(--cal-border-booker-width, 1px)",
|
||||
},
|
||||
fontFamily: {
|
||||
cal: ["var(--font-cal)", ...fontFamily.serif],
|
||||
sans: ["var(--font-inter)", ...fontFamily.sans],
|
||||
|
||||
@@ -17,15 +17,15 @@
|
||||
if (!location.search.includes("nonResponsive")) {
|
||||
document.write('<meta name="viewport" content="width=device-width"/>');
|
||||
}
|
||||
(()=> {
|
||||
(() => {
|
||||
const url = new URL(document.URL);
|
||||
// Only run the example specified by only=, avoids distraction and faster to test.
|
||||
const only = window.only = url.searchParams.get("only");
|
||||
const elementIdentifier = only !== "all" ? only.replace("ns:",""): null
|
||||
const only = (window.only = url.searchParams.get("only"));
|
||||
const elementIdentifier = only !== "all" ? only.replace("ns:", "") : null;
|
||||
if (elementIdentifier) {
|
||||
location.hash="#cal-booking-place-" + elementIdentifier + "-iframe"
|
||||
location.hash = "#cal-booking-place-" + elementIdentifier + "-iframe";
|
||||
}
|
||||
})()
|
||||
})();
|
||||
</script>
|
||||
<script>
|
||||
(function (C, A, L) {
|
||||
@@ -70,7 +70,7 @@
|
||||
.inline-embed-container {
|
||||
/* border: 1px solid black; */
|
||||
margin-bottom: 5px;
|
||||
border-bottom: 1px solid
|
||||
border-bottom: 1px solid;
|
||||
}
|
||||
|
||||
.loader {
|
||||
@@ -82,8 +82,10 @@
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<span style="display: block;" ><a href="?nonResponsive">Non responsive version of this page here</a></span>
|
||||
<span style="display: block;" ><a href="?only=prerender-test">Go to Pre-render test page only</a><small></small></span>
|
||||
<span style="display: block"><a href="?nonResponsive">Non responsive version of this page here</a></span>
|
||||
<span style="display: block"
|
||||
><a href="?only=prerender-test">Go to Pre-render test page only</a><small></small
|
||||
></span>
|
||||
<div>
|
||||
<script>
|
||||
if (only === "all" || only === "prerender-test") {
|
||||
@@ -94,28 +96,60 @@
|
||||
as you are reading this text, it would open up super fast[If you are running a production build on
|
||||
local]. Try switching to slow 3G or create a custom Network configuration which is impossibly
|
||||
slow</i
|
||||
>`)
|
||||
>`);
|
||||
}
|
||||
</script>
|
||||
</script>
|
||||
</div>
|
||||
<span style="display: block;" ><a href="?only=all">Render All embeds together</a><small> - It would be slow to load</small></span>
|
||||
<span style="display: block"
|
||||
><a href="?only=all">Render All embeds together</a><small> - It would be slow to load</small></span
|
||||
>
|
||||
<div>
|
||||
<a href="?only=ns:floatingButton">Floating Popup</a>
|
||||
|
||||
<h2>Popup Examples</h2>
|
||||
<button data-cal-namespace="popupAutoTheme" data-cal-link="free">Book with Free User[Auto Theme]</button>
|
||||
<button data-cal-namespace="popupDarkTheme" data-cal-config='{"theme":"dark"}' data-cal-link="free">Book with Free User[Dark Theme]</button>
|
||||
<button data-cal-namespace="popupTeamLinkLightTheme" data-cal-config='{"theme":"light"}' data-cal-link="team/seeded-team/collective-seeded-team-event">Book with Test Team[Light Theme]</button>
|
||||
<button data-cal-namespace="popupTeamLinkDarkTheme" data-cal-config='{"theme":"dark"}' data-cal-link="team/seeded-team/collective-seeded-team-event">Book with Test Team[Dark Theme]</button>
|
||||
<button data-cal-namespace="popupTeamLinksList" data-cal-link="team/seeded-team/">See Team Links [Auto Theme]</button>
|
||||
<button data-cal-namespace="popupAutoTheme" data-cal-link="free">
|
||||
Book with Free User[Auto Theme]
|
||||
</button>
|
||||
<button data-cal-namespace="popupDarkTheme" data-cal-config='{"theme":"dark"}' data-cal-link="free">
|
||||
Book with Free User[Dark Theme]
|
||||
</button>
|
||||
<button
|
||||
data-cal-namespace="popupTeamLinkLightTheme"
|
||||
data-cal-config='{"theme":"light"}'
|
||||
data-cal-link="team/seeded-team/collective-seeded-team-event">
|
||||
Book with Test Team[Light Theme]
|
||||
</button>
|
||||
<button
|
||||
data-cal-namespace="popupTeamLinkDarkTheme"
|
||||
data-cal-config='{"theme":"dark"}'
|
||||
data-cal-link="team/seeded-team/collective-seeded-team-event">
|
||||
Book with Test Team[Dark Theme]
|
||||
</button>
|
||||
<button data-cal-namespace="popupTeamLinksList" data-cal-link="team/seeded-team/">
|
||||
See Team Links [Auto Theme]
|
||||
</button>
|
||||
<script>
|
||||
let popupRescheduleId = new URL(document.URL).searchParams.get("popupRescheduleId") || "qm3kwt3aTnVD7vmP9tiT2f"
|
||||
document.write(`<button data-cal-namespace="popupReschedule" data-cal-link="reschedule/${popupRescheduleId}">Reschedule Event[Auto Theme]</button>`)
|
||||
let popupRescheduleId =
|
||||
new URL(document.URL).searchParams.get("popupRescheduleId") || "qm3kwt3aTnVD7vmP9tiT2f";
|
||||
document.write(
|
||||
`<button data-cal-namespace="popupReschedule" data-cal-link="reschedule/${popupRescheduleId}">Reschedule Event[Auto Theme]</button>`
|
||||
);
|
||||
</script>
|
||||
<button data-cal-namespace="popupPaidEvent" data-cal-link="pro/paid">Book Paid Event [Auto Theme]</button>
|
||||
<button data-cal-namespace="popupHideEventTypeDetails" data-cal-link="free/30min">Book Free Event [Auto Theme][uiConfig.hideEventTypeDetails=true]</button>
|
||||
<button data-cal-namespace="routingFormAuto" data-cal-link="forms/948ae412-d995-4865-875a-48302588de03">Book through Routing Form [Auto Theme]</button>
|
||||
<button data-cal-namespace="routingFormDark" data-cal-config='{"theme":"dark"}' data-cal-link="forms/948ae412-d995-4865-875a-48302588de03">Book through Routing Form [Dark Theme]</button>
|
||||
<button data-cal-namespace="popupPaidEvent" data-cal-link="pro/paid">
|
||||
Book Paid Event [Auto Theme]
|
||||
</button>
|
||||
<button data-cal-namespace="popupHideEventTypeDetails" data-cal-link="free/30min">
|
||||
Book Free Event [Auto Theme][uiConfig.hideEventTypeDetails=true]
|
||||
</button>
|
||||
<button data-cal-namespace="routingFormAuto" data-cal-link="forms/948ae412-d995-4865-875a-48302588de03">
|
||||
Book through Routing Form [Auto Theme]
|
||||
</button>
|
||||
<button
|
||||
data-cal-namespace="routingFormDark"
|
||||
data-cal-config='{"theme":"dark"}'
|
||||
data-cal-link="forms/948ae412-d995-4865-875a-48302588de03">
|
||||
Book through Routing Form [Dark Theme]
|
||||
</button>
|
||||
<!-- <div>
|
||||
<h2>Embed for Pages behind authentication</h2>
|
||||
<button data-cal-namespace="upcomingBookings" data-cal-config='{"theme":"dark"}' data-cal-link="bookings/upcoming">Show Upcoming Bookings</button>
|
||||
@@ -128,15 +162,18 @@
|
||||
<a href="?only=ns:default">[Dark Theme][Guests(janedoe@example.com and test@example.com)]</a>
|
||||
</h3>
|
||||
<i class="last-action"> You would see last Booking page action in my place </i>
|
||||
<div >
|
||||
<div class="place" style="width:100%;"></div>
|
||||
<div>
|
||||
<div class="place" style="width: 100%"></div>
|
||||
<div class="loader" id="cal-booking-loader-"></div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="inline-embed-container" id="cal-booking-place-second">
|
||||
<h3><a href="?only=ns:second">[Custom Styling]</a></h3>
|
||||
<div class="place">
|
||||
<div>If you render booking embed in me, I won't restrict you. The entire page is yours. Content is by default aligned center</div>
|
||||
<div>
|
||||
If you render booking embed in me, I won't restrict you. The entire page is yours. Content is by
|
||||
default aligned center
|
||||
</div>
|
||||
<button
|
||||
onclick="(function () {Cal.ns.second('ui', {styles:{eventTypeListItem:{backgroundColor:'blue'}}})})()">
|
||||
Change <code>eventTypeListItem</code> bg color
|
||||
@@ -144,13 +181,16 @@
|
||||
<button onclick="(function () {Cal.ns.second('ui', {styles:{body:{background:'red'}}})})()">
|
||||
Change <code>body</code> bg color
|
||||
</button>
|
||||
<button onclick="(function () {Cal.ns.second('ui', {styles:{align:'left'}})})()">
|
||||
Align left
|
||||
<button
|
||||
onclick="(function () {Cal.ns.second('ui', {cssVarsPerTheme:{light:{'cal-border-booker':'green', 'cal-border-booker-width':'20px'},dark:{'cal-border-booker':'red', 'cal-border-booker-width':'5px'}}})})()">
|
||||
Change booker border for dark and light themes
|
||||
</button>
|
||||
<button onclick="(function () {Cal.ns.second('ui', {styles:{align:'left'}})})()">Align left</button>
|
||||
<button onclick="(function () {Cal.ns.second('ui', {styles:{align:'center'}})})()">
|
||||
Align Center
|
||||
</button>
|
||||
<button onclick="(function () {Cal.ns.second('ui', {styles:{enabledDateButton: {
|
||||
<button
|
||||
onclick="(function () {Cal.ns.second('ui', {styles:{enabledDateButton: {
|
||||
backgroundColor: generateRandomHexColor(),
|
||||
},
|
||||
disabledDateButton: {
|
||||
@@ -158,7 +198,6 @@
|
||||
},}})})()">
|
||||
Change 'enabledDateButton` and `disabledDateButton` background Color
|
||||
</button>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
<div class="inline-embed-container" id="cal-booking-place-third">
|
||||
@@ -177,28 +216,21 @@
|
||||
|
||||
<div class="inline-embed-container" id="cal-booking-place-fifth">
|
||||
<h3><a href="?only=ns:fifth">[Team Event Test][inline along with some content]</a></h3>
|
||||
<div style="display:flex;align-items: center;">
|
||||
<h4 style="width: 30%">
|
||||
On the right side you can book a team meeting =>
|
||||
</h4>
|
||||
<div style="width: 70%" class="place">
|
||||
</div>
|
||||
<div style="display: flex; align-items: center">
|
||||
<h4 style="width: 30%">On the right side you can book a team meeting =></h4>
|
||||
<div style="width: 70%" class="place"></div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="inline-embed-container" id="cal-booking-place-inline-routing-form">
|
||||
<h3><a href="?only=inline-routing-form">Inline Routing Form</a></h3>
|
||||
<div style="display:flex;align-items: center;">
|
||||
<h4 style="width: 30%">
|
||||
On the right side you can book a team meeting =>
|
||||
</h4>
|
||||
<div style="width: 70%" class="place">
|
||||
</div>
|
||||
<div style="display: flex; align-items: center">
|
||||
<h4 style="width: 30%">On the right side you can book a team meeting =></h4>
|
||||
<div style="width: 70%" class="place"></div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="inline-embed-container" id="cal-booking-place-hideEventTypeDetails">
|
||||
<h3><a href="?only=hideEventTypeDetails">Hide EventType Details Test</a></h3>
|
||||
<div class="place">
|
||||
</div>
|
||||
<div class="place"></div>
|
||||
</div>
|
||||
<script src="./playground.ts"></script>
|
||||
</body>
|
||||
|
||||
@@ -4,14 +4,7 @@ type A = GlobalCalWithoutNs;
|
||||
const Cal = window.Cal as GlobalCal;
|
||||
const callback = function (e) {
|
||||
const detail = e.detail;
|
||||
const namespace = detail.namespace || "default";
|
||||
|
||||
if (detail.type === "linkReady") {
|
||||
document.querySelector(`#cal-booking-place-${namespace} .loader`).remove();
|
||||
} else if (detail.type === "linkFailed") {
|
||||
document.querySelector(`#cal-booking-place-${namespace} .loader`).remove();
|
||||
}
|
||||
document.querySelector(`#cal-booking-place-${namespace} .last-action`).innerHTML = JSON.stringify(e.detail);
|
||||
console.log("Event: ", e.type, detail);
|
||||
};
|
||||
|
||||
const searchParams = new URL(document.URL).searchParams;
|
||||
|
||||
@@ -5,10 +5,14 @@ import { useState, useEffect } from "react";
|
||||
import type { Message } from "./embed";
|
||||
import { sdkActionManager } from "./sdk-event";
|
||||
|
||||
type Theme = "dark" | "light";
|
||||
|
||||
export type UiConfig = {
|
||||
hideEventTypeDetails?: boolean;
|
||||
theme?: "dark" | "light" | "auto";
|
||||
theme?: Theme | "auto";
|
||||
styles?: EmbedStyles & EmbedNonStylesConfig;
|
||||
//TODO: Extract from tailwind the list of all custom variables and support them in auto-completion as well as runtime validation. Followup with listing all variables in Embed Snippet Generator UI.
|
||||
cssVarsPerTheme?: Record<Theme, Record<string, string>>;
|
||||
};
|
||||
|
||||
type SetStyles = React.Dispatch<React.SetStateAction<EmbedStyles>>;
|
||||
@@ -279,6 +283,12 @@ const methods = {
|
||||
log("Method: ui called", uiConfig);
|
||||
const stylesConfig = uiConfig.styles;
|
||||
|
||||
if (stylesConfig) {
|
||||
console.warn(
|
||||
"Cal.com Embed: `styles` prop is deprecated. Use `cssVarsPerTheme` instead to achieve the same effect"
|
||||
);
|
||||
}
|
||||
|
||||
// body can't be styled using React state hook as it is generated by _document.tsx which doesn't support hooks.
|
||||
if (stylesConfig?.body?.background) {
|
||||
document.body.style.background = stylesConfig.body.background as string;
|
||||
|
||||
Reference in New Issue
Block a user