feat: more atoms styles (#22976)

* feat: availability override classnames

* feat: availability time picker styles

* feat: calendar settings headers styles

* feat: destination and connected calendars

* feat: date override title,description,button

* feat: selected calendars list

* example styles

* docs

* chanegest

* refactor: change import path to relative

* fix: make all classnames optional

* docs: time picker docs

---------

Co-authored-by: Somay Chauhan <somaychauhan98@gmail.com>
This commit is contained in:
Lauris Skraucis
2025-08-12 21:35:06 +09:00
committed by GitHub
co-authored by Somay Chauhan
parent 6c6cf0433a
commit abe92cd693
13 changed files with 355 additions and 51 deletions
+5
View File
@@ -0,0 +1,5 @@
---
"@calcom/atoms": minor
---
feat: style calendar settings and availability overrides
+39 -6
View File
@@ -125,12 +125,45 @@ Along with the props, Availability settings atom accepts custom styles via the *
| formClassName | Form which contains the days and toggles |
| timezoneSelectClassName | Adds styling to the timezone select component |
| subtitlesClassName | Styles the subtitle |
| scheduleContainer | Styles the entire schedule component |
| scheduleDay | Adds styling to just the day of a particular schedule |
| dayRanges | Adds styling to day ranges |
| timeRanges | Time ranges in the availability settings can be customized |
| labelAndSwitchContainer | Adds styling to label and switches |
| overridesModalClassNames | Adds styling to the date overrides modal |
| scheduleClassNames | An object for granular styling of schedule components (see detailed table below) |
| dateOverrideClassNames | An object for granular styling of date override components (see detailed table below) |
### scheduleClassNames Object Structure
The `scheduleClassNames` prop accepts an object with the following structure for granular styling of schedule components:
| Property Path | Description |
|:--------------|:------------|
| `schedule` | Styles the entire schedule component |
| `scheduleContainer` | Styles the schedule container |
| `scheduleDay` | Adds styling to just the day of a particular schedule |
| `dayRanges` | Adds styling to day ranges |
| `timeRangeField` | Styles the time range input fields |
| `labelAndSwitchContainer` | Adds styling to label and switches |
| `timePicker` | An object for granular styling of time picker dropdown components (see nested table below) |
#### timePicker Object Structure (nested within scheduleClassNames)
The `timePicker` prop accepts an object with the following structure for granular styling of time picker dropdown components. This applies to the time selection dropdowns (e.g., [ 9:00 ]) that users can click to open a dropdown with available times or click to manually enter a time:
| Property Path | Description |
|:--------------|:------------|
| `container` | Styles the main time picker container |
| `valueContainer` | Styles the container that holds the selected value |
| `value` | Styles the displayed selected time value |
| `input` | Styles the input field for manual time entry |
| `dropdown` | Styles the dropdown menu with time options |
### dateOverrideClassNames Object Structure
The `dateOverrideClassNames` prop accepts an object with the following structure for granular styling of date override components:
| Property Path | Description |
|:--------------|:------------|
| `container` | Styles the main container |
| `title` | Styles the title|
| `description` | Styles the description |
| `button` | Styles the button to add date override |
<Info>Please ensure all custom classnames are valid [Tailwind CSS](https://tailwindcss.com/) classnames.</Info>
+31
View File
@@ -47,6 +47,37 @@ Along with the props, calendar settings atom accepts custom styles via the **cla
| calendarSettingsCustomClassnames | Adds styling to the entire calendar settings atom |
| destinationCalendarSettingsCustomClassnames | Adds styling only to the destination calendar container |
| selectedCalendarSettingsCustomClassnames | Adds styling only to the selected calendar container |
| selectedCalendarSettingsClassNames | An object for granular styling of selected calendars component (see detailed table below) |
| destinationCalendarSettingsClassNames | An object for granular styling of destination calendar component (see detailed table below) |
### selectedCalendarSettingsClassNames Object Structure
The `selectedCalendarSettingsClassNames` prop accepts an object with the following nested structure for granular styling of the selected calendars component:
| Property Path | Description |
|:--------------|:------------|
| `container` | Styles the main container of the selected calendars section |
| `header.container` | Styles the header container |
| `header.title` | Styles the header title |
| `header.description` | Styles the header description |
| `selectedCalendarsListClassNames.container` | Styles the container that holds all selected calendar items |
| `selectedCalendarsListClassNames.selectedCalendar.container` | Styles each individual calendar item container |
| `selectedCalendarsListClassNames.selectedCalendar.header.container` | Styles the header section of each calendar item |
| `selectedCalendarsListClassNames.selectedCalendar.header.title` | Styles the title of each calendar item |
| `selectedCalendarsListClassNames.selectedCalendar.header.description` | Styles the description of each calendar item |
| `selectedCalendarsListClassNames.selectedCalendar.body.container` | Styles the body section of each calendar item |
| `selectedCalendarsListClassNames.selectedCalendar.body.description` | Styles the body description of each calendar item |
| `noSelectedCalendarsMessage` | Styles the message shown when no calendars are connected |
### destinationCalendarSettingsClassNames Object Structure
The `destinationCalendarSettingsClassNames` prop accepts an object with the following nested structure for granular styling of the destination calendar component:
| Property Path | Description |
|:--------------|:------------|
| `container` | Styles the main container of the destination calendar section |
| `header.title` | Styles the header title text |
| `header.description` | Styles the header description text |
<p></p>
Additionally, if you wish to select either the Destination Calendar or the Selected Calendar, we also provide atoms specifically designed for this purpose.
@@ -36,6 +36,14 @@ export type ScheduleLabelsType = {
deleteTime: string;
};
export type SelectInnerClassNames = {
control?: string;
singleValue?: string;
valueContainer?: string;
input?: string;
menu?: string;
};
export type FieldPathByValue<TFieldValues extends FieldValues, TValue> = {
[Key in FieldPath<TFieldValues>]: FieldPathValue<TFieldValues, Key> extends TValue ? Key : never;
}[FieldPath<TFieldValues>];
@@ -105,6 +113,7 @@ export const ScheduleDay = <TFieldValues extends FieldValues>({
classNames={{
dayRanges: classNames?.dayRanges,
timeRangeField: classNames?.timeRangeField,
timePicker: classNames?.timePicker,
}}
/>
{!disabled && <div className="block">{CopyButton}</div>}
@@ -237,7 +246,7 @@ export const DayRanges = <TFieldValues extends FieldValues>({
disabled?: boolean;
labels?: ScheduleLabelsType;
userTimeFormat: number | null;
classNames?: Pick<scheduleClassNames, "dayRanges" | "timeRangeField">;
classNames?: Pick<scheduleClassNames, "dayRanges" | "timeRangeField" | "timePicker">;
}) => {
const { t } = useLocale();
const { getValues } = useFormContext();
@@ -260,6 +269,7 @@ export const DayRanges = <TFieldValues extends FieldValues>({
<TimeRangeField
className={classNames?.timeRangeField}
userTimeFormat={userTimeFormat}
timePickerClassNames={classNames?.timePicker}
{...field}
/>
)}
@@ -335,11 +345,27 @@ const TimeRangeField = ({
onChange,
disabled,
userTimeFormat,
timePickerClassNames,
}: {
className?: string;
disabled?: boolean;
userTimeFormat: number | null;
timePickerClassNames?: {
container?: string;
value?: string;
valueContainer?: string;
input?: string;
dropdown?: string;
};
} & ControllerRenderProps) => {
const innerClassNames: SelectInnerClassNames = {
control: timePickerClassNames?.container,
singleValue: timePickerClassNames?.value,
valueContainer: timePickerClassNames?.valueContainer,
input: timePickerClassNames?.input,
menu: timePickerClassNames?.dropdown,
};
// this is a controlled component anyway given it uses LazySelect, so keep it RHF agnostic.
return (
<div className={cn("flex flex-row gap-2 sm:gap-3", className)}>
@@ -349,6 +375,7 @@ const TimeRangeField = ({
isDisabled={disabled}
value={value.start}
menuPlacement="bottom"
innerClassNames={innerClassNames}
onChange={(option) => {
const newStart = new Date(option?.value as number);
if (newStart >= new Date(value.end)) {
@@ -367,6 +394,7 @@ const TimeRangeField = ({
isDisabled={disabled}
value={value.end}
min={value.start}
innerClassNames={innerClassNames}
menuPlacement="bottom"
onChange={(option) => {
onChange({ ...value, end: new Date(option?.value as number) });
@@ -388,6 +416,7 @@ const LazySelect = ({
min?: ConfigType;
max?: ConfigType;
userTimeFormat: number | null;
innerClassNames?: SelectInnerClassNames;
}) => {
// Lazy-loaded options, otherwise adding a field has a noticeable redraw delay.
const { options, filter } = useOptions(userTimeFormat);
@@ -68,6 +68,12 @@ export type CustomClassNames = {
subtitlesClassName?: string;
scheduleClassNames?: scheduleClassNames;
overridesModalClassNames?: string;
dateOverrideClassNames?: {
container?: string;
title?: string;
description?: string;
button?: string;
};
hiddenSwitchClassname?: {
container?: string;
thumb?: string;
@@ -180,6 +186,7 @@ const DateOverride = ({
travelSchedules,
weekStart,
overridesModalClassNames,
classNames,
handleSubmit,
}: {
workingHours: WorkingHours[];
@@ -187,6 +194,12 @@ const DateOverride = ({
travelSchedules?: RouterOutputs["viewer"]["travelSchedules"]["get"];
weekStart: 0 | 1 | 2 | 3 | 4 | 5 | 6;
overridesModalClassNames?: string;
classNames?: {
container?: string;
title?: string;
description?: string;
button?: string;
};
handleSubmit: (data: AvailabilityFormValues) => Promise<void>;
}) => {
const { append, replace, fields } = useFieldArray<AvailabilityFormValues, "dateOverrides">({
@@ -202,8 +215,8 @@ const DateOverride = ({
};
return (
<div className="p-6">
<h3 className="text-emphasis font-medium leading-6">
<div className={cn("p-6", classNames?.container)}>
<h3 className={cn("text-emphasis font-medium leading-6", classNames?.title)}>
{t("date_overrides")}{" "}
<Tooltip content={t("date_overrides_info")}>
<span className="inline-block align-middle">
@@ -211,7 +224,9 @@ const DateOverride = ({
</span>
</Tooltip>
</h3>
<p className="text-subtle mb-4 text-sm">{t("date_overrides_subtitle")}</p>
<p className={cn("text-subtle mb-4 text-sm", classNames?.description)}>
{t("date_overrides_subtitle")}
</p>
<div className="space-y-2">
<DateOverrideList
excludedDates={excludedDates}
@@ -235,7 +250,11 @@ const DateOverride = ({
userTimeFormat={userTimeFormat}
weekStart={weekStart}
Trigger={
<Button color="secondary" StartIcon="plus" data-testid="add-override">
<Button
className={classNames?.button}
color="secondary"
StartIcon="plus"
data-testid="add-override">
{t("add_an_override")}
</Button>
}
@@ -318,24 +337,27 @@ export const AvailabilitySettings = forwardRef<AvailabilitySettingsFormRef, Avai
const callbacksRef = useRef<{ onSuccess?: () => void; onError?: (error: Error) => void }>({});
const handleFormSubmit = useCallback((customCallbacks?: { onSuccess?: () => void; onError?: (error: Error) => void }) => {
if (customCallbacks) {
callbacksRef.current = customCallbacks;
}
const handleFormSubmit = useCallback(
(customCallbacks?: { onSuccess?: () => void; onError?: (error: Error) => void }) => {
if (customCallbacks) {
callbacksRef.current = customCallbacks;
}
if (saveButtonRef.current) {
saveButtonRef.current.click();
} else {
form.handleSubmit(async (data) => {
try {
await handleSubmit(data);
callbacksRef.current?.onSuccess?.();
} catch (error) {
callbacksRef.current?.onError?.(error as Error);
}
})();
}
}, [form, handleSubmit]);
if (saveButtonRef.current) {
saveButtonRef.current.click();
} else {
form.handleSubmit(async (data) => {
try {
await handleSubmit(data);
callbacksRef.current?.onSuccess?.();
} catch (error) {
callbacksRef.current?.onError?.(error as Error);
}
})();
}
},
[form, handleSubmit]
);
const validateForm = useCallback(async () => {
const isValid = await form.trigger();
@@ -661,6 +683,7 @@ export const AvailabilitySettings = forwardRef<AvailabilitySettingsFormRef, Avai
) as 0 | 1 | 2 | 3 | 4 | 5 | 6
}
overridesModalClassNames={customClassNames?.overridesModalClassNames}
classNames={customClassNames?.dateOverrideClassNames}
/>
)}
</div>
@@ -28,6 +28,13 @@ export type scheduleClassNames = {
timeRangeField?: string;
labelAndSwitchContainer?: string;
scheduleContainer?: string;
timePicker?: {
container?: string;
valueContainer?: string;
value?: string;
input?: string;
dropdown?: string;
};
};
export type AvailabilityFormValidationResult = {
@@ -1,12 +1,18 @@
import type { DestinationCalendarClassNames } from "../../destination-calendar/DestinationCalendar";
import { DestinationCalendarSettingsPlatformWrapper } from "../../destination-calendar/index";
import { SelectedCalendarsSettingsPlatformWrapper } from "../../selected-calendars/index";
import type { CalendarRedirectUrls } from "../../selected-calendars/wrappers/SelectedCalendarsSettingsPlatformWrapper";
import type {
CalendarRedirectUrls,
SelectedCalendarsClassNames,
} from "../../selected-calendars/wrappers/SelectedCalendarsSettingsPlatformWrapper";
type CalendarSettingsPlatformWrapperProps = {
classNames?: {
calendarSettingsCustomClassnames?: string;
destinationCalendarSettingsCustomClassnames?: string;
selectedCalendarSettingsCustomClassnames?: string;
selectedCalendarSettingsClassNames?: SelectedCalendarsClassNames;
destinationCalendarSettingsClassNames?: DestinationCalendarClassNames;
};
calendarRedirectUrls?: CalendarRedirectUrls;
allowDelete?: boolean;
@@ -24,6 +30,7 @@ export const CalendarSettingsPlatformWrapper = ({
<DestinationCalendarSettingsPlatformWrapper
statusLoader={<></>}
classNames={classNames?.destinationCalendarSettingsCustomClassnames}
classNamesObject={classNames?.destinationCalendarSettingsClassNames}
isDryRun={isDryRun}
/>
<SelectedCalendarsSettingsPlatformWrapper
@@ -31,6 +38,7 @@ export const CalendarSettingsPlatformWrapper = ({
calendarRedirectUrls={calendarRedirectUrls}
allowDelete={allowDelete}
isDryRun={isDryRun}
classNamesObject={classNames?.selectedCalendarSettingsClassNames}
/>
</div>
);
@@ -5,12 +5,29 @@ import { cn } from "../src/lib/utils";
import type { DestinationCalendarProps } from "./DestinationCalendarSelector";
import { DestinationCalendarSelector } from "./DestinationCalendarSelector";
export const DestinationCalendarSettings = (props: DestinationCalendarProps & { classNames?: string }) => {
type DestinationHeaderClassnames = {
container?: string;
title?: string;
description?: string;
};
export type DestinationCalendarClassNames = {
container?: string;
header?: DestinationHeaderClassnames;
};
export const DestinationCalendarSettings = (
props: DestinationCalendarProps & { classNames?: string; classNamesObject?: DestinationCalendarClassNames }
) => {
const { t } = useLocale();
return (
<div className={cn("border-subtle mb-6 mt-8 rounded-lg border", props?.classNames)}>
<DestinationCalendarSettingsHeading />
<div
className={cn(
"border-subtle mb-6 mt-8 rounded-lg border",
props?.classNames || props?.classNamesObject?.container
)}>
<DestinationCalendarSettingsHeading classNames={props.classNamesObject?.header} />
<div className="border-t">
<div className="border-subtle flex w-full flex-col space-y-3 border-y-0 p-6">
<div>
@@ -23,15 +40,17 @@ export const DestinationCalendarSettings = (props: DestinationCalendarProps & {
);
};
const DestinationCalendarSettingsHeading = () => {
const DestinationCalendarSettingsHeading = ({ classNames }: { classNames?: DestinationHeaderClassnames }) => {
const { t } = useLocale();
return (
<div className="p-6">
<h2 className="text-emphasis mb-1 text-base font-bold leading-5 tracking-wide">
<div className={cn("p-6", classNames?.container)}>
<h2 className={cn("text-emphasis mb-1 text-base font-bold leading-5 tracking-wide", classNames?.title)}>
{t("add_to_calendar")}
</h2>
<p className="text-subtle text-sm leading-tight">{t("add_to_calendar_description")}</p>
<p className={cn("text-subtle text-sm leading-tight", classNames?.description)}>
{t("add_to_calendar_description")}
</p>
</div>
);
};
@@ -1,15 +1,18 @@
import { useUpdateDestinationCalendars } from "../../hooks/calendars/useUpdateDestinationCalendars";
import { useConnectedCalendars } from "../../hooks/useConnectedCalendars";
import { AtomsWrapper } from "../../src/components/atoms-wrapper";
import type { DestinationCalendarClassNames } from "../DestinationCalendar";
import { DestinationCalendarSettings } from "../DestinationCalendar";
export const DestinationCalendarSettingsPlatformWrapper = ({
statusLoader,
classNames = "mx-5",
classNamesObject,
isDryRun = false,
}: {
statusLoader?: JSX.Element;
classNames?: string;
classNamesObject?: DestinationCalendarClassNames;
isDryRun?: boolean;
}) => {
const calendars = useConnectedCalendars({});
@@ -35,6 +38,7 @@ export const DestinationCalendarSettingsPlatformWrapper = ({
<AtomsWrapper>
<DestinationCalendarSettings
classNames={classNames}
classNamesObject={classNamesObject}
connectedCalendars={calendars.data.connectedCalendars}
destinationCalendar={calendars.data.destinationCalendar}
value={calendars.data.destinationCalendar.externalId}
@@ -4,6 +4,7 @@ import type { ICalendarSwitchProps } from "@calcom/features/calendars/CalendarSw
import { useLocale } from "@calcom/lib/hooks/useLocale";
import type { CALENDARS } from "@calcom/platform-constants";
import { QueryCell } from "@calcom/trpc/components/QueryCell";
import cn from "@calcom/ui/classNames";
import { Alert } from "@calcom/ui/components/alert";
import { AppListCard } from "@calcom/ui/components/app-list-card";
import type { ButtonProps } from "@calcom/ui/components/button";
@@ -29,8 +30,34 @@ export type CalendarRedirectUrls = {
outlook?: string;
};
export type SelectedCalendarsClassNames = {
container?: string;
header?: {
container?: string;
title?: string;
description?: string;
};
selectedCalendarsListClassNames?: {
container?: string;
selectedCalendar?: {
container?: string;
header?: {
container?: string;
title?: string;
description?: string;
};
body?: {
container?: string;
description?: string;
};
};
};
noSelectedCalendarsMessage?: string;
};
type SelectedCalendarsSettingsPlatformWrapperProps = {
classNames?: string;
classNamesObject?: SelectedCalendarsClassNames;
calendarRedirectUrls?: CalendarRedirectUrls;
allowDelete?: boolean;
isDryRun?: boolean;
@@ -41,6 +68,7 @@ export const SelectedCalendarsSettingsPlatformWrapper = ({
calendarRedirectUrls,
allowDelete,
isDryRun,
classNamesObject,
}: SelectedCalendarsSettingsPlatformWrapperProps) => {
const { t } = useLocale();
const query = useConnectedCalendars({});
@@ -55,23 +83,33 @@ export const SelectedCalendarsSettingsPlatformWrapper = ({
if (!data.connectedCalendars.length) {
return (
<SelectedCalendarsSettings classNames={classNames}>
<SelectedCalendarsSettings classNames={classNamesObject?.container || classNames}>
<SelectedCalendarsSettingsHeading
classNames={classNamesObject?.header}
calendarRedirectUrls={calendarRedirectUrls}
isDryRun={isDryRun}
/>
<h1 className="px-6 py-4 text-base leading-5">No connected calendars found.</h1>
<h1
className={cn(
"px-6 py-4 text-base leading-5",
classNamesObject?.noSelectedCalendarsMessage
)}>
No connected calendars found.
</h1>
</SelectedCalendarsSettings>
);
}
return (
<SelectedCalendarsSettings classNames={classNames}>
<SelectedCalendarsSettings classNames={classNamesObject?.container || classNames}>
<SelectedCalendarsSettingsHeading
classNames={classNamesObject?.header}
calendarRedirectUrls={calendarRedirectUrls}
isDryRun={isDryRun}
/>
<List noBorderTreatment className="p-6 pt-2">
<List
noBorderTreatment
className={classNamesObject?.selectedCalendarsListClassNames?.container || "p-6 pt-2"}>
{data.connectedCalendars.map((connectedCalendar) => {
if (!!connectedCalendar.calendars && connectedCalendar.calendars.length > 0) {
return (
@@ -84,7 +122,18 @@ export const SelectedCalendarsSettingsPlatformWrapper = ({
description={
connectedCalendar.primary?.email ?? connectedCalendar.integration.description
}
className="border-subtle mt-4 rounded-lg border"
classNameObject={{
container: cn(
"border-subtle mt-4 rounded-lg border",
classNamesObject?.selectedCalendarsListClassNames?.selectedCalendar?.container
),
title:
classNamesObject?.selectedCalendarsListClassNames?.selectedCalendar?.header
?.title,
description:
classNamesObject?.selectedCalendarsListClassNames?.selectedCalendar?.header
?.description,
}}
actions={
<div className="flex w-32 justify-end">
{allowDelete && !connectedCalendar.delegationCredentialId && (
@@ -98,8 +147,20 @@ export const SelectedCalendarsSettingsPlatformWrapper = ({
)}
</div>
}>
<div className="border-subtle border-t">
<p className="text-subtle px-5 pt-4 text-sm">{t("toggle_calendars_conflict")}</p>
<div
className={cn(
"border-subtle border-t",
classNamesObject?.selectedCalendarsListClassNames?.selectedCalendar?.body
?.container
)}>
<p
className={cn(
"text-subtle px-5 pt-4 text-sm",
classNamesObject?.selectedCalendarsListClassNames?.selectedCalendar?.body
?.description
)}>
{t("toggle_calendars_conflict")}
</p>
<ul className="space-y-4 px-5 py-4">
{connectedCalendar.calendars?.map((cal) => {
return (
@@ -159,18 +220,28 @@ export const SelectedCalendarsSettingsPlatformWrapper = ({
const SelectedCalendarsSettingsHeading = ({
calendarRedirectUrls,
isDryRun,
classNames,
}: {
calendarRedirectUrls?: CalendarRedirectUrls;
isDryRun?: boolean;
classNames?: {
container?: string;
title?: string;
description?: string;
};
}) => {
const { t } = useLocale();
return (
<div className="border-subtle border-b p-6">
<div className={cn("border-subtle border-b p-6", classNames?.container)}>
<div className="flex items-center justify-between">
<div>
<h4 className="text-emphasis text-base font-semibold leading-5">{t("check_for_conflicts")}</h4>
<p className="text-default text-sm leading-tight">{t("select_calendars")}</p>
<h4 className={cn("text-emphasis text-base font-semibold leading-5", classNames?.title)}>
{t("check_for_conflicts")}
</h4>
<p className={cn("text-default text-sm leading-tight", classNames?.description)}>
{t("select_calendars")}
</p>
</div>
<div className="flex flex-col xl:flex-row xl:space-x-5">
<div className="flex items-center">
@@ -1,6 +1,6 @@
import { Navbar } from "@/components/Navbar";
import { Inter } from "next/font/google";
import { useRef, useCallback, useState } from "react";
import { useRef, useCallback } from "react";
import type { AvailabilitySettingsFormRef } from "@calcom/atoms";
import { AvailabilitySettings } from "@calcom/atoms";
@@ -57,6 +57,27 @@ export default function Availability(props: { calUsername: string; calEmail: str
ctaClassName: "border p-4 rounded-md",
editableHeadingClassName: "underline font-semibold",
hiddenSwitchClassname: { thumb: "bg-red-500" },
scheduleClassNames: {
schedule: "bg-blue-50 border-2 border-blue-200 rounded-lg p-4",
scheduleDay: "bg-green-50 border border-green-300 rounded-md mb-2",
dayRanges: "bg-yellow-50 p-3 rounded border-l-4 border-yellow-400",
timeRangeField: "!text-2xl bg-red-50 border-2 border-red-300 rounded-xl px-4 py-2",
labelAndSwitchContainer: "bg-purple-50 border border-purple-200 rounded p-2",
scheduleContainer: "bg-gray-100 border-4 border-gray-400 rounded-2xl shadow-lg",
timePicker: {
container: "!bg-blue-900",
valueContainer: "!bg-pink-500",
value: "!bg-yellow-500",
input: "!bg-green-500",
dropdown: "!bg-cyan-500",
},
},
dateOverrideClassNames: {
container: "p-4 bg-gray-900 rounded-md",
title: "text-red-500 font-bold",
description: "text-white",
button: "text-black",
},
}}
onFormStateChange={handleFormStateChange}
onUpdateSuccess={() => {
@@ -10,7 +10,43 @@ export default function Calendars(props: { calUsername: string; calEmail: string
<main className={`flex min-h-screen flex-col ${inter.className}`}>
<Navbar username={props.calUsername} />
<div>
<CalendarSettings allowDelete={true} />
<CalendarSettings
allowDelete={true}
classNames={{
destinationCalendarSettingsClassNames: {
container: "bg-red-200",
header: {
container: "bg-gray-200",
title: "text-green-500",
description: "text-red-500",
},
},
selectedCalendarSettingsClassNames: {
container: "mx-5 mb-6",
header: {
container: "bg-gray-200 rounded-md",
title: "text-green-500",
description: "text-red-500",
},
noSelectedCalendarsMessage: "text-blue-500",
selectedCalendarsListClassNames: {
container: "bg-yellow-100",
selectedCalendar: {
container: "bg-yellow-200",
header: {
container: "bg-yellow-500",
title: "text-green-500",
description: "text-red-500",
},
body: {
container: "bg-yellow-500",
description: "text-red-500",
},
},
},
},
}}
/>
</div>
</main>
);
@@ -22,6 +22,12 @@ type ShouldHighlight =
slug?: never;
};
export type AppCardClassNames = {
container: string;
title?: string;
description?: string;
};
export type AppListCardProps = {
logo?: string;
title: string;
@@ -33,6 +39,7 @@ export type AppListCardProps = {
children?: ReactNode;
credentialOwner?: CredentialOwner;
className?: string;
classNameObject?: AppCardClassNames;
} & ShouldHighlight;
export const AppListCard = (props: AppListCardProps & { highlight?: boolean }) => {
@@ -48,11 +55,16 @@ export const AppListCard = (props: AppListCardProps & { highlight?: boolean }) =
children,
credentialOwner,
className,
classNameObject,
highlight,
} = props;
return (
<div className={classNames(highlight && "dark:bg-muted bg-yellow-100", className)}>
<div
className={classNames(
highlight && "dark:bg-muted bg-yellow-100",
className || classNameObject?.container
)}>
<div className="flex items-start gap-x-3 px-4 py-4 sm:px-6">
{logo ? (
<img
@@ -63,13 +75,18 @@ export const AppListCard = (props: AppListCardProps & { highlight?: boolean }) =
) : null}
<div className="flex min-w-0 grow flex-col gap-y-1">
<div className="flex items-center gap-x-2">
<h3 className="text-emphasis truncate text-sm font-semibold">{title}</h3>
<h3
className={classNames("text-emphasis truncate text-sm font-semibold", classNameObject?.title)}>
{title}
</h3>
<div className="flex flex-shrink-0 items-center gap-x-2">
{isDefault && <Badge variant="green">{t("default")}</Badge>}
{isTemplate && <Badge variant="red">Template</Badge>}
</div>
</div>
<ListItemText component="p" className="whitespace-normal break-words">
<ListItemText
component="p"
className={classNames("whitespace-normal break-words", classNameObject?.description)}>
{description}
</ListItemText>
{invalidCredential && (