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:
co-authored by
Somay Chauhan
parent
6c6cf0433a
commit
abe92cd693
@@ -0,0 +1,5 @@
|
||||
---
|
||||
"@calcom/atoms": minor
|
||||
---
|
||||
|
||||
feat: style calendar settings and availability overrides
|
||||
@@ -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>
|
||||
|
||||
|
||||
@@ -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 = {
|
||||
|
||||
+9
-1
@@ -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>
|
||||
);
|
||||
};
|
||||
|
||||
+4
@@ -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}
|
||||
|
||||
+81
-10
@@ -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 && (
|
||||
|
||||
Reference in New Issue
Block a user