Styling tweaks to inputs and Select (+ TimezoneSelect) (#2453)

Co-authored-by: Peer Richelsen <peeroke@gmail.com>
Co-authored-by: Bailey Pumfleet <pumfleet@hey.com>
This commit is contained in:
Alex van Andel 2022-04-14 15:58:23 +01:00 committed by GitHub
parent d91f667d0c
commit 5fdc5078cc
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
24 changed files with 265 additions and 561 deletions

View file

@ -68,7 +68,7 @@ const DestinationCalendarSelector = ({
placeholder={!hidePlaceholder ? `${t("select_destination_calendar")}:` : undefined}
options={options}
isSearchable={false}
className="focus:ring-primary-500 focus:border-primary-500 mt-1 mb-2 block w-full min-w-0 flex-1 rounded-none rounded-r-md border-gray-300 sm:text-sm"
className="mt-1 mb-2 block w-full min-w-0 flex-1 rounded-none rounded-r-md border-gray-300 sm:text-sm"
onChange={(option) => {
setSelectedOption(option);
if (!option) {

View file

@ -60,7 +60,19 @@ export function NewScheduleButton({ name = "new-schedule" }: { name?: string })
createMutation.mutate(values);
}}>
<div className="mt-3 space-y-4">
<TextField placeholder={t("default_schedule_name")} label={t("name")} {...register("name")} />
<label htmlFor="label" className="block text-sm font-medium text-gray-700">
{t("name")}
</label>
<div className="mt-1">
<input
type="text"
id="name"
required
className="block w-full rounded-sm border-gray-300 text-sm shadow-sm"
placeholder={t("default_schedule_name")}
{...register("name")}
/>
</div>
</div>
<div className="mt-8 flex flex-row-reverse gap-x-2">
<Button type="submit" loading={createMutation.isLoading}>

View file

@ -1,12 +1,13 @@
import { EventTypeCustomInput, EventTypeCustomInputType } from "@prisma/client";
import React, { FC } from "react";
import { Controller, SubmitHandler, useForm, useWatch } from "react-hook-form";
import Select from "react-select";
import Button from "@calcom/ui/Button";
import { useLocale } from "@lib/hooks/useLocale";
import Select from "@components/ui/form/Select";
interface OptionTypeBase {
label: string;
value: EventTypeCustomInputType;
@ -55,7 +56,7 @@ const CustomInputTypeForm: FC<Props> = (props) => {
defaultValue={selectedInputOption}
options={inputOptions}
isSearchable={false}
className="focus:border-primary-500 focus:ring-primary-500 mt-1 mb-2 block w-full min-w-0 flex-1 rounded-none rounded-r-md border-gray-300 sm:text-sm"
className="mt-1 mb-2 block w-full min-w-0 flex-1 sm:text-sm"
onChange={(option) => option && field.onChange(option.value)}
value={selectedInputOption}
onBlur={field.onBlur}
@ -73,7 +74,7 @@ const CustomInputTypeForm: FC<Props> = (props) => {
type="text"
id="label"
required
className="focus:border-primary-500 focus:ring-primary-500 block w-full rounded-sm border-gray-300 text-sm shadow-sm"
className="block w-full rounded-sm border-gray-300 text-sm shadow-sm"
defaultValue={selectedCustomInput?.label}
{...register("label", { required: true })}
/>
@ -89,7 +90,7 @@ const CustomInputTypeForm: FC<Props> = (props) => {
<input
type="text"
id="placeholder"
className="focus:border-primary-500 focus:ring-primary-500 block w-full rounded-sm border-gray-300 text-sm shadow-sm"
className="block w-full rounded-sm border-gray-300 text-sm shadow-sm"
defaultValue={selectedCustomInput?.placeholder}
{...register("placeholder")}
/>

View file

@ -74,7 +74,7 @@ const ChangePasswordSection = () => {
name="current_password"
id="current_password"
required
className="focus:border-brand block w-full rounded-sm border-gray-300 shadow-sm focus:ring-black sm:text-sm"
className="block w-full rounded-sm border-gray-300 shadow-sm sm:text-sm"
placeholder={t("your_old_password")}
/>
</div>
@ -91,7 +91,7 @@ const ChangePasswordSection = () => {
value={newPassword}
required
onInput={(e) => setNewPassword(e.currentTarget.value)}
className="focus:border-brand block w-full rounded-sm border-gray-300 shadow-sm focus:ring-black sm:text-sm"
className="block w-full rounded-sm border-gray-300 shadow-sm sm:text-sm"
placeholder={t("super_secure_new_password")}
/>
</div>

View file

@ -70,7 +70,7 @@ const DisableTwoFactorAuthModal = ({ onDisable, onCancel }: DisableTwoFactorAuth
required
value={password}
onInput={(e) => setPassword(e.currentTarget.value)}
className="block w-full rounded-sm border-gray-300 shadow-sm focus:border-neutral-900 focus:ring-neutral-900 sm:text-sm"
className="block w-full rounded-sm border-gray-300 shadow-sm sm:text-sm"
/>
</div>

View file

@ -139,7 +139,7 @@ const EnableTwoFactorModal = ({ onEnable, onCancel }: EnableTwoFactorModalProps)
required
value={password}
onInput={(e) => setPassword(e.currentTarget.value)}
className="block w-full rounded-sm border-gray-300 shadow-sm focus:border-neutral-900 focus:ring-neutral-900 sm:text-sm"
className="block w-full rounded-sm border-gray-300 shadow-sm sm:text-sm"
/>
</div>
@ -172,7 +172,7 @@ const EnableTwoFactorModal = ({ onEnable, onCancel }: EnableTwoFactorModalProps)
minLength={6}
inputMode="numeric"
onInput={(e) => setTotpCode(e.currentTarget.value)}
className="block w-full rounded-sm border-gray-300 shadow-sm focus:border-neutral-900 focus:ring-neutral-900 sm:text-sm"
className="block w-full rounded-sm border-gray-300 shadow-sm sm:text-sm"
/>
</div>

View file

@ -1,6 +1,6 @@
import { MembershipRole } from "@prisma/client";
import { useState } from "react";
import React, { SyntheticEvent } from "react";
import React, { SyntheticEvent, useEffect } from "react";
import { useLocale } from "@calcom/lib/hooks/useLocale";
import Button from "@calcom/ui/Button";
@ -8,6 +8,14 @@ import Button from "@calcom/ui/Button";
import { trpc } from "@lib/trpc";
import ModalContainer from "@components/ui/ModalContainer";
import Select from "@components/ui/form/Select";
type MembershipRoleOption = {
value: MembershipRole;
label?: string;
};
const options: MembershipRoleOption[] = [{ value: "MEMBER" }, { value: "ADMIN" }];
export default function MemberChangeRoleModal(props: {
isOpen: boolean;
@ -16,7 +24,16 @@ export default function MemberChangeRoleModal(props: {
initialRole: MembershipRole;
onExit: () => void;
}) {
const [role, setRole] = useState(props.initialRole || MembershipRole.MEMBER);
useEffect(() => {
options.forEach((option, i) => {
options[i].label = t(option.value.toLowerCase());
});
// eslint-disable-next-line react-hooks/exhaustive-deps
}, []);
const [role, setRole] = useState(
options.find((option) => option.value === props.initialRole || MembershipRole.MEMBER)!
);
const [errorMessage, setErrorMessage] = useState("");
const { t } = useLocale();
const utils = trpc.useContext();
@ -37,7 +54,7 @@ export default function MemberChangeRoleModal(props: {
changeRoleMutation.mutate({
teamId: props.teamId,
memberId: props.memberId,
role,
role: role.value,
});
}
@ -56,17 +73,16 @@ export default function MemberChangeRoleModal(props: {
<label className="mb-2 block text-sm font-medium tracking-wide text-gray-700" htmlFor="role">
{t("role")}
</label>
<select
{/*<option value="OWNER">{t("owner")}</option> - needs dialog to confirm change of ownership */}
<Select
isSearchable={false}
options={options}
value={role}
onChange={(e) => setRole(e.target.value as MembershipRole)}
onChange={(option) => option && setRole(option)}
id="role"
className="focus:border-brand mt-1 block w-full rounded-md border-gray-300 shadow-sm focus:ring-black sm:text-sm">
<option value="MEMBER">{t("member")}</option>
<option value="ADMIN">{t("admin")}</option>
{/*<option value="OWNER">{t("owner")}</option> - needs dialog to confirm change of ownership */}
</select>
className="mt-1 block w-full rounded-md border-gray-300 shadow-sm sm:text-sm"
/>
</div>
{errorMessage && (
<p className="text-sm text-red-700">
<span className="font-bold">Error: </span>

View file

@ -1,8 +1,7 @@
import { UserIcon } from "@heroicons/react/outline";
import { InformationCircleIcon } from "@heroicons/react/solid";
import { MembershipRole } from "@prisma/client";
import { useState } from "react";
import React, { SyntheticEvent } from "react";
import React, { useState, useEffect, SyntheticEvent } from "react";
import Button from "@calcom/ui/Button";
import { Dialog, DialogContent, DialogFooter, DialogHeader } from "@calcom/ui/Dialog";
@ -12,17 +11,33 @@ import { useLocale } from "@lib/hooks/useLocale";
import { TeamWithMembers } from "@lib/queries/teams";
import { trpc } from "@lib/trpc";
import Select from "@components/ui/form/Select";
type MemberInvitationModalProps = {
isOpen: boolean;
team: TeamWithMembers | null;
onExit: () => void;
};
type MembershipRoleOption = {
value: MembershipRole;
label?: string;
};
const options: MembershipRoleOption[] = [{ value: "MEMBER" }, { value: "ADMIN" }];
export default function MemberInvitationModal(props: MemberInvitationModalProps) {
const [errorMessage, setErrorMessage] = useState("");
const { t, i18n } = useLocale();
const utils = trpc.useContext();
useEffect(() => {
options.forEach((option, i) => {
options[i].label = t(option.value.toLowerCase());
});
// eslint-disable-next-line react-hooks/exhaustive-deps
}, []);
const inviteMemberMutation = trpc.useMutation("viewer.teams.inviteMember", {
async onSuccess() {
await utils.invalidateQueries(["viewer.teams.get"]);
@ -83,12 +98,12 @@ export default function MemberInvitationModal(props: MemberInvitationModalProps)
<label className="mb-1 block text-sm font-medium tracking-wide text-gray-700" htmlFor="role">
{t("role")}
</label>
<select
<Select
options={options}
id="role"
className="focus:border-brand mt-1 block w-full rounded-sm border-gray-300 shadow-sm focus:ring-black sm:text-sm">
<option value="MEMBER">{t("member")}</option>
<option value="ADMIN">{t("admin")}</option>
</select>
name="role"
className="mt-1 block w-full rounded-sm border-gray-300 shadow-sm sm:text-sm"
/>
</div>
<div className="relative flex items-start">
<div className="flex h-5 items-center">
@ -97,7 +112,7 @@ export default function MemberInvitationModal(props: MemberInvitationModalProps)
name="sendInviteEmail"
defaultChecked
id="sendInviteEmail"
className="focus:border-brand rounded-sm border-gray-300 text-black shadow-sm focus:ring-black sm:text-sm"
className="rounded-sm border-gray-300 text-black shadow-sm sm:text-sm"
/>
</div>
<div className="text-sm ltr:ml-2 rtl:mr-2">

View file

@ -63,7 +63,7 @@ export default function TeamCreate(props: Props) {
id="name"
placeholder="Acme Inc."
required
className="mt-1 block w-full rounded-sm border border-gray-300 px-3 py-2 shadow-sm focus:border-neutral-500 focus:outline-none focus:ring-neutral-500 sm:text-sm"
className="mt-1 block w-full rounded-sm border border-gray-300 px-3 py-2 shadow-sm sm:text-sm"
/>
</div>
{errorMessage && <Alert severity="error" title={errorMessage} />}

View file

@ -112,7 +112,7 @@ export default function TeamSettings(props: Props) {
id="name"
placeholder={t("your_team_name")}
required
className="mt-1 block w-full rounded-sm border border-gray-300 px-3 py-2 shadow-sm focus:border-neutral-800 focus:outline-none focus:ring-neutral-800 sm:text-sm"
className="mt-1 block w-full rounded-sm border border-gray-300 px-3 py-2 shadow-sm sm:text-sm"
defaultValue={team?.name as string}
/>
}
@ -131,7 +131,7 @@ export default function TeamSettings(props: Props) {
name="about"
rows={3}
defaultValue={team?.bio as string}
className="mt-1 block w-full rounded-sm border-gray-300 shadow-sm focus:border-neutral-800 focus:ring-neutral-800 sm:text-sm"></textarea>
className="mt-1 block w-full rounded-sm border-gray-300 shadow-sm sm:text-sm"></textarea>
<p className="mt-2 text-sm text-gray-500">{t("team_description")}</p>
</>
}
@ -151,7 +151,7 @@ export default function TeamSettings(props: Props) {
name="avatar"
id="avatar"
placeholder="URL"
className="mt-1 block w-full rounded-sm border border-gray-300 px-3 py-2 shadow-sm focus:border-neutral-800 focus:outline-none focus:ring-neutral-800 sm:text-sm"
className="mt-1 block w-full rounded-sm border border-gray-300 px-3 py-2 shadow-sm sm:text-sm"
defaultValue={team?.logo ?? undefined}
/>
<ImageUploader

View file

@ -1,156 +0,0 @@
import { TrashIcon } from "@heroicons/react/outline";
import { Availability } from "@prisma/client";
import dayjs from "dayjs";
import timezone from "dayjs/plugin/timezone";
import utc from "dayjs/plugin/utc";
import React, { useEffect, useState } from "react";
import TimezoneSelect, { ITimezoneOption } from "react-timezone-select";
import Button from "@calcom/ui/Button";
import { useLocale } from "@lib/hooks/useLocale";
import { WeekdaySelect } from "./WeekdaySelect";
import SetTimesModal from "./modal/SetTimesModal";
dayjs.extend(utc);
dayjs.extend(timezone);
type AvailabilityInput = Pick<Availability, "days" | "startTime" | "endTime">;
type Props = {
timeZone: string;
availability: Availability[];
setTimeZone: (timeZone: string) => void;
setAvailability: (schedule: {
openingHours: AvailabilityInput[];
dateOverrides: AvailabilityInput[];
}) => void;
};
/**
* @deprecated
*/
export const Scheduler = ({ availability, setAvailability, timeZone, setTimeZone }: Props) => {
const { t, i18n } = useLocale();
const [editSchedule, setEditSchedule] = useState(-1);
const [openingHours, setOpeningHours] = useState<Availability[]>([]);
useEffect(() => {
setOpeningHours(availability.filter((item: Availability) => item.days.length !== 0));
}, []);
useEffect(() => {
setAvailability({ openingHours, dateOverrides: [] });
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [openingHours]);
const addNewSchedule = () => setEditSchedule(openingHours.length);
const applyEditSchedule = (changed: Availability) => {
// new entry
if (!changed.days) {
changed.days = [1, 2, 3, 4, 5]; // Mon - Fri
setOpeningHours(openingHours.concat(changed));
} else {
// update
const replaceWith = { ...openingHours[editSchedule], ...changed };
openingHours.splice(editSchedule, 1, replaceWith);
setOpeningHours([...openingHours]);
}
};
const removeScheduleAt = (toRemove: number) => {
openingHours.splice(toRemove, 1);
setOpeningHours([...openingHours]);
};
const OpeningHours = ({ idx, item }: { idx: number; item: Availability }) => (
<li className="flex justify-between border-b py-2">
<div className="flex flex-col space-y-4 lg:inline-flex">
<WeekdaySelect defaultValue={item.days} onSelect={(selected: number[]) => (item.days = selected)} />
<button
className="rounded-sm bg-neutral-100 px-3 py-2 text-sm"
type="button"
onClick={() => setEditSchedule(idx)}>
{item.startTime.toLocaleTimeString(i18n.language, {
hour: "numeric",
minute: "2-digit",
timeZone: "UTC",
})}
&nbsp;{t("until")}&nbsp;
{item.endTime.toLocaleTimeString(i18n.language, {
hour: "numeric",
minute: "2-digit",
timeZone: "UTC",
})}
</button>
</div>
<button
type="button"
onClick={() => removeScheduleAt(idx)}
className="btn-sm ml-1 bg-transparent px-2 py-1">
<TrashIcon className="-mt-1 inline h-5 w-5 text-gray-400" />
</button>
</li>
);
return (
<div>
<div className="flex">
<div className="w-full">
<div>
<label htmlFor="timeZone" className="block text-sm font-medium text-gray-700">
{t("timezone")}
</label>
<div className="mt-1">
<TimezoneSelect
id="timeZone"
value={timeZone}
onChange={(tz: ITimezoneOption) => setTimeZone(tz.value)}
className="focus:border-brand mt-1 block w-full rounded-md border-gray-300 shadow-sm focus:ring-black sm:text-sm"
/>
</div>
</div>
<ul>
{openingHours.map((item, idx) => (
<OpeningHours key={idx} idx={idx} item={item} />
))}
</ul>
<Button type="button" onClick={addNewSchedule} className="mt-2" color="secondary" size="sm">
{t("add_another")}
</Button>
</div>
</div>
{editSchedule >= 0 && (
<SetTimesModal
isOpen={true}
startTime={
openingHours[editSchedule]
? new Date(openingHours[editSchedule].startTime).getUTCHours() * 60 +
new Date(openingHours[editSchedule].startTime).getUTCMinutes()
: 540
}
endTime={
openingHours[editSchedule]
? new Date(openingHours[editSchedule].endTime).getUTCHours() * 60 +
new Date(openingHours[editSchedule].endTime).getUTCMinutes()
: 1020
}
onChange={(times: { startTime: number; endTime: number }) =>
applyEditSchedule({
...(openingHours[editSchedule] || {}),
startTime: new Date(
new Date().setUTCHours(Math.floor(times.startTime / 60), times.startTime % 60, 0, 0)
),
endTime: new Date(
new Date().setUTCHours(Math.floor(times.endTime / 60), times.endTime % 60, 0, 0)
),
})
}
onExit={() => setEditSchedule(-1)}
/>
)}
</div>
);
};

View file

@ -87,7 +87,7 @@ const ColorPicker = (props: ColorPickerProps) => {
</div>
)}
<HexColorInput
className="ml-1 block w-full rounded-sm border border-gray-300 px-3 py-2 shadow-sm focus:border-neutral-800 focus:outline-none focus:ring-neutral-800 sm:text-sm"
className="ml-1 block w-full rounded-sm border border-gray-300 px-3 py-2 shadow-sm sm:text-sm"
color={color}
onChange={(val) => {
setColor(val);

View file

@ -14,7 +14,7 @@ type Props = {
export const DateRangePicker = ({ startDate, endDate, onDatesChange }: Props) => {
return (
<PrimitiveDateRangePicker
className="focus:border-primary-500 focus:ring-primary-500 rounded-sm border-gray-300 sm:text-sm"
className="rounded-sm border-gray-300 sm:text-sm"
clearIcon={null}
calendarIcon={<CalendarIcon className="h-5 w-5 text-gray-500" />}
rangeDivider={<ArrowRightIcon className="h-4 w-4 text-gray-400 ltr:mr-2 rtl:ml-2" />}

View file

@ -22,7 +22,7 @@ const MinutesField = forwardRef<HTMLInputElement, Props>(({ label, ...rest }, re
ref={ref}
type="number"
className={classNames(
"focus:border-primary-500 focus:ring-primary-500 block w-full rounded-sm border-gray-300 pl-2 pr-12 sm:text-sm",
"block w-full rounded-sm border-gray-300 pl-2 pr-12 sm:text-sm",
rest.className
)}
/>

View file

@ -1,13 +1,32 @@
import React from "react";
import ReactSelect, { components, GroupBase, Props } from "react-select";
import ReactSelect, { components, GroupBase, Props, InputProps } from "react-select";
import classNames from "@lib/classNames";
export type SelectProps<
Option,
IsMulti extends boolean = false,
Group extends GroupBase<Option> = GroupBase<Option>
> = Props<Option, IsMulti, Group>;
export const InputComponent = <Option, IsMulti extends boolean, Group extends GroupBase<Option>>({
inputClassName,
...props
}: InputProps<Option, IsMulti, Group>) => {
return (
<components.Input
// disables our default form focus hightlight on the react-select input element
inputClassName={classNames("focus:ring-0 focus:ring-offset-0", inputClassName)}
{...props}
/>
);
};
function Select<
Option,
IsMulti extends boolean = false,
Group extends GroupBase<Option> = GroupBase<Option>
>({ className, ...props }: Props<Option, IsMulti, Group>) {
>({ className, ...props }: SelectProps<Option, IsMulti, Group>) {
return (
<ReactSelect
theme={(theme) => ({
@ -15,26 +34,28 @@ function Select<
borderRadius: 2,
colors: {
...theme.colors,
primary: "rgba(17, 17, 17, var(--tw-bg-opacity))",
primary: "var(--brand-color)",
primary50: "rgba(209 , 213, 219, var(--tw-bg-opacity))",
primary25: "rgba(244, 245, 246, var(--tw-bg-opacity))",
},
})}
styles={{
option: (base, state) => ({
...base,
option: (provided, state) => ({
...provided,
color: state.isSelected ? "var(--brand-text-color)" : "black",
":active": {
backgroundColor: state.isSelected ? "" : "rgba(17, 17, 17, var(--tw-bg-opacity))",
color: "#ffffff",
backgroundColor: state.isSelected ? "" : "var(--brand-color)",
color: "var(--brand-text-color)",
},
}),
}}
components={{
...components,
IndicatorSeparator: () => null,
Input: InputComponent,
}}
className={classNames("focus:border-primary-500 text-sm shadow-sm", className)}
className={classNames("text-sm shadow-sm", className)}
{...props}
/>
);

View file

@ -0,0 +1,43 @@
import classNames from "classnames";
import { components } from "react-select";
import BaseSelect, { ITimezone, Props as SelectProps } from "react-timezone-select";
import { InputComponent } from "@components/ui/form/Select";
function TimezoneSelect({ className, ...props }: SelectProps) {
return (
<BaseSelect
theme={(theme) => ({
...theme,
borderRadius: 2,
colors: {
...theme.colors,
primary: "var(--brand-color)",
primary50: "rgba(209 , 213, 219, var(--tw-bg-opacity))",
primary25: "rgba(244, 245, 246, var(--tw-bg-opacity))",
},
})}
styles={{
option: (provided, state) => ({
...provided,
color: state.isSelected ? "var(--brand-text-color)" : "black",
":active": {
backgroundColor: state.isSelected ? "" : "var(--brand-color)",
color: "var(--brand-text-color)",
},
}),
}}
components={{
...components,
IndicatorSeparator: () => null,
Input: InputComponent,
}}
className={classNames("text-sm shadow-sm", className)}
{...props}
/>
);
}
export default TimezoneSelect;
export type { ITimezone };

View file

@ -1,222 +0,0 @@
import { ClockIcon } from "@heroicons/react/outline";
import dayjs from "dayjs";
import customParseFormat from "dayjs/plugin/customParseFormat";
import { useRef, useState } from "react";
import { useLocale } from "@calcom/lib/hooks/useLocale";
import showToast from "@calcom/lib/notification";
import Button from "@calcom/ui/Button";
import { Dialog, DialogContent, DialogFooter } from "@calcom/ui/Dialog";
dayjs.extend(customParseFormat);
interface SetTimesModalProps {
isOpen: boolean;
startTime: number;
endTime: number;
onChange: (times: { startTime: number; endTime: number }) => void;
onExit: (...p: unknown[]) => void;
}
export default function SetTimesModal(props: SetTimesModalProps) {
const { t } = useLocale();
const [startHours, startMinutes] = [Math.floor(props.startTime / 60), props.startTime % 60];
const [endHours, endMinutes] = [Math.floor(props.endTime / 60), props.endTime % 60];
const startHoursRef = useRef<HTMLInputElement>(null!);
const startMinsRef = useRef<HTMLInputElement>(null!);
const endHoursRef = useRef<HTMLInputElement>(null!);
const endMinsRef = useRef<HTMLInputElement>(null!);
const [endMinuteDisable, setEndMinuteDisable] = useState(false);
const [maximumStartTime, setMaximumStartTime] = useState({ hour: endHours, minute: 59 });
const [minimumEndTime, setMinimumEndTime] = useState({ hour: startHours, minute: 59 });
const STEP = 15;
const isValidTime = (startTime: number, endTime: number) => {
if (new Date(startTime) > new Date(endTime)) {
showToast(t("error_end_time_before_start_time"), "error");
return false;
}
if (endTime > 1440) {
showToast(t("error_end_time_next_day"), "error");
return false;
}
return true;
};
// compute dynamic range for minimum and maximum allowed hours/minutes.
const setEdgeTimes = (
(step) =>
(
startHoursRef: React.MutableRefObject<HTMLInputElement>,
startMinsRef: React.MutableRefObject<HTMLInputElement>,
endHoursRef: React.MutableRefObject<HTMLInputElement>,
endMinsRef: React.MutableRefObject<HTMLInputElement>
) => {
//parse all the refs
const startHour = parseInt(startHoursRef.current.value);
let startMinute = parseInt(startMinsRef.current.value);
const endHour = parseInt(endHoursRef.current.value);
let endMinute = parseInt(endMinsRef.current.value);
//convert to dayjs object
const startTime = dayjs(`${startHour}:${startMinute}`, "hh:mm");
const endTime = dayjs(`${endHour}:${endMinute}`, "hh:mm");
//compute minimin and maximum allowed
const maximumStartTime = endTime.subtract(step, "minute");
const maximumStartHour = maximumStartTime.hour();
const maximumStartMinute = startHour === endHour ? maximumStartTime.minute() : 59;
const minimumEndTime = startTime.add(step, "minute");
const minimumEndHour = minimumEndTime.hour();
const minimumEndMinute = startHour === endHour ? minimumEndTime.minute() : 0;
//check allow min/max minutes when the end/start hour matches
if (startHoursRef.current.value === endHoursRef.current.value) {
if (parseInt(startMinsRef.current.value) >= maximumStartMinute)
startMinsRef.current.value = maximumStartMinute.toString();
if (parseInt(endMinsRef.current.value) <= minimumEndMinute)
endMinsRef.current.value = minimumEndMinute.toString();
}
//save into state
setMaximumStartTime({ hour: maximumStartHour, minute: maximumStartMinute });
setMinimumEndTime({ hour: minimumEndHour, minute: minimumEndMinute });
}
)(STEP);
return (
<Dialog open={props.isOpen} onOpenChange={props.onExit}>
<DialogContent>
<div className="mb-4 sm:flex sm:items-start">
<div className="mx-auto flex h-12 w-12 flex-shrink-0 items-center justify-center rounded-full bg-blue-100 sm:mx-0 sm:h-10 sm:w-10">
<ClockIcon className="h-6 w-6 text-black" />
</div>
<div className="mt-3 text-center sm:mt-0 sm:ml-4 sm:text-left">
<h3 className="text-lg font-medium leading-6 text-gray-900" id="modal-title">
{t("change_bookings_availability")}
</h3>
<div>
<p className="text-sm text-gray-500">{t("set_work_schedule")}</p>
</div>
</div>
</div>
<div className="mb-4 flex">
<label className="block w-1/4 pt-2 text-sm font-medium text-gray-700">{t("start_time")}</label>
<div className="w-1/6">
<label htmlFor="startHours" className="sr-only">
{t("hours")}
</label>
<input
ref={startHoursRef}
type="number"
min="0"
max={maximumStartTime.hour}
minLength={2}
name="hours"
id="startHours"
className="focus:border-brand block w-full rounded-md border-gray-300 shadow-sm focus:ring-black sm:text-sm"
placeholder="9"
defaultValue={startHours}
onChange={() => setEdgeTimes(startHoursRef, startMinsRef, endHoursRef, endMinsRef)}
/>
</div>
<span className="mx-2 pt-1">:</span>
<div className="w-1/6">
<label htmlFor="startMinutes" className="sr-only">
{t("minutes")}
</label>
<input
ref={startMinsRef}
type="number"
min="0"
max={maximumStartTime.minute}
step={STEP}
maxLength={2}
name="minutes"
id="startMinutes"
className="focus:border-brand block w-full rounded-md border-gray-300 shadow-sm focus:ring-black sm:text-sm"
placeholder="30"
defaultValue={startMinutes}
onChange={() => setEdgeTimes(startHoursRef, startMinsRef, endHoursRef, endMinsRef)}
/>
</div>
</div>
<div className="flex">
<label className="block w-1/4 pt-2 text-sm font-medium text-gray-700">{t("end_time")}</label>
<div className="w-1/6">
<label htmlFor="endHours" className="sr-only">
{t("hours")}
</label>
<input
ref={endHoursRef}
type="number"
min={minimumEndTime.hour}
max="24"
maxLength={2}
name="hours"
id="endHours"
className="focus:border-brand block w-full rounded-md border-gray-300 shadow-sm focus:ring-black sm:text-sm"
placeholder="17"
defaultValue={endHours}
onChange={(e) => {
if (endHoursRef.current.value === "24") endMinsRef.current.value = "0";
setEdgeTimes(startHoursRef, startMinsRef, endHoursRef, endMinsRef);
setEndMinuteDisable(endHoursRef.current.value === "24");
}}
/>
</div>
<span className="mx-2 pt-1">:</span>
<div className="w-1/6">
<label htmlFor="endMinutes" className="sr-only">
{t("minutes")}
</label>
<input
ref={endMinsRef}
type="number"
min={minimumEndTime.minute}
max="59"
maxLength={2}
step={STEP}
name="minutes"
id="endMinutes"
className="focus:border-brand block w-full rounded-md border-gray-300 shadow-sm focus:ring-black sm:text-sm"
placeholder="30"
defaultValue={endMinutes}
disabled={endMinuteDisable}
onChange={() => setEdgeTimes(startHoursRef, startMinsRef, endHoursRef, endMinsRef)}
/>
</div>
</div>
<DialogFooter>
<Button
onClick={(event) => {
event.preventDefault();
const enteredStartHours = parseInt(startHoursRef.current.value);
const enteredStartMins = parseInt(startMinsRef.current.value);
const enteredEndHours = parseInt(endHoursRef.current.value);
const enteredEndMins = parseInt(endMinsRef.current.value);
if (
isValidTime(enteredStartHours * 60 + enteredStartMins, enteredEndHours * 60 + enteredEndMins)
) {
props.onChange({
startTime: enteredStartHours * 60 + enteredStartMins,
endTime: enteredEndHours * 60 + enteredEndMins,
});
props.onExit(0);
}
}}
type="submit">
{t("save")}
</Button>
<Button onClick={props.onExit} type="button" color="secondary" className="ltr:mr-2">
{t("cancel")}
</Button>
</DialogFooter>
</DialogContent>
</Dialog>
);
}

View file

@ -60,8 +60,7 @@ export default function TeamAvailabilityModal(props: Props) {
id="timeZone"
value={selectedTimeZone}
onChange={(timezone) => setSelectedTimeZone(timezone.value)}
classNamePrefix="react-select"
className="react-select-container mt-1 block w-full rounded-sm border border-gray-300 shadow-sm focus:border-neutral-800 focus:ring-neutral-800 sm:text-sm"
className="mt-1 block w-full rounded-sm border border-gray-300 shadow-sm sm:text-sm"
/>
</div>
<div>
@ -73,8 +72,7 @@ export default function TeamAvailabilityModal(props: Props) {
{ value: 60, label: "60 minutes" },
]}
isSearchable={false}
classNamePrefix="react-select"
className="react-select-container focus:ring-primary-500 focus:border-primary-500 block w-full min-w-0 flex-1 rounded-sm border border-gray-300 sm:text-sm"
className="block w-full min-w-0 flex-1 rounded-sm border border-gray-300 sm:text-sm"
value={{ value: frequency, label: `${frequency} minutes` }}
onChange={(newFrequency) => setFrequency(newFrequency?.value ?? 30)}
/>

View file

@ -80,7 +80,7 @@ export default function TeamAvailabilityScreen(props: Props) {
value={selectedTimeZone}
onChange={(timezone) => setSelectedTimeZone(timezone.value)}
classNamePrefix="react-select"
className="react-select-container w-full rounded-sm border border-gray-300 shadow-sm focus:border-neutral-800 focus:ring-neutral-800 sm:text-sm"
className="react-select-container w-full rounded-sm border border-gray-300 shadow-sm sm:text-sm"
/>
</div>
<div className="hidden sm:block">
@ -92,8 +92,7 @@ export default function TeamAvailabilityScreen(props: Props) {
{ value: 60, label: "60 minutes" },
]}
isSearchable={false}
classNamePrefix="react-select"
className="react-select-container focus:ring-primary-500 focus:border-primary-500 block w-full min-w-0 flex-1 rounded-sm border border-gray-300 sm:text-sm"
className="block w-full min-w-0 flex-1 rounded-sm border border-gray-300 sm:text-sm"
value={{ value: frequency, label: `${frequency} minutes` }}
onChange={(newFrequency) => setFrequency(newFrequency?.value ?? 30)}
/>

View file

@ -51,7 +51,7 @@ function IframeEmbedContainer() {
<div className="text-right">
<input
id="iframe"
className="focus:border-brand px-2 py-1 text-sm text-gray-500 focus:ring-black"
className="px-2 py-1 text-sm text-gray-500 "
placeholder={t("loading")}
defaultValue={iframeTemplate}
readOnly
@ -76,7 +76,7 @@ function IframeEmbedContainer() {
<div>
<input
id="fullscreen"
className="focus:border-brand px-2 py-1 text-sm text-gray-500 focus:ring-black"
className="px-2 py-1 text-sm text-gray-500 "
placeholder={t("loading")}
defaultValue={htmlTemplate}
readOnly

View file

@ -2,7 +2,6 @@ import { BadgeCheckIcon } from "@heroicons/react/solid";
import { useRouter } from "next/router";
import { useState } from "react";
import { Controller, useForm } from "react-hook-form";
import TimezoneSelect from "react-timezone-select";
import { DEFAULT_SCHEDULE, availabilityAsString } from "@calcom/lib/availability";
import { useLocale } from "@calcom/lib/hooks/useLocale";
@ -18,6 +17,7 @@ import { inferQueryOutput, trpc } from "@lib/trpc";
import Shell from "@components/Shell";
import Schedule from "@components/availability/Schedule";
import EditableHeading from "@components/ui/EditableHeading";
import TimezoneSelect from "@components/ui/form/TimezoneSelect";
export function AvailabilityForm(props: inferQueryOutput<"viewer.availability.schedule">) {
const { t } = useLocale();
@ -99,7 +99,7 @@ export function AvailabilityForm(props: inferQueryOutput<"viewer.availability.sc
render={({ field: { onChange, value } }) => (
<TimezoneSelect
value={value}
className="focus:border-brand mt-1 block w-full rounded-md border-gray-300 shadow-sm focus:ring-black sm:text-sm"
className="focus:border-brand mt-1 block w-full rounded-md border-gray-300 shadow-sm sm:text-sm"
onChange={(timezone) => onChange(timezone.value)}
/>
)}

View file

@ -25,7 +25,6 @@ import { useRouter } from "next/router";
import React, { useEffect, useState } from "react";
import { Controller, Noop, useForm, UseFormReturn } from "react-hook-form";
import { FormattedNumber, IntlProvider } from "react-intl";
import Select, { Props as SelectProps } from "react-select";
import { JSONObject } from "superjson/dist/types";
import { z } from "zod";
@ -62,6 +61,7 @@ import CheckboxField from "@components/ui/form/CheckboxField";
import CheckedSelect from "@components/ui/form/CheckedSelect";
import { DateRangePicker } from "@components/ui/form/DateRangePicker";
import MinutesField from "@components/ui/form/MinutesField";
import Select, { SelectProps } from "@components/ui/form/Select";
import * as RadioArea from "@components/ui/form/radio-area";
import WebhookListContainer from "@components/webhook/WebhookListContainer";
@ -122,7 +122,7 @@ const SuccessRedirectEdit = <T extends UseFormReturn<any, any>>({
}}
readOnly={proUpgradeRequired}
type="url"
className="focus:border-primary-500 focus:ring-primary-500 block w-full rounded-sm border-gray-300 shadow-sm sm:text-sm"
className=" block w-full rounded-sm border-gray-300 shadow-sm sm:text-sm"
placeholder={t("external_redirect_url")}
defaultValue={eventType.successRedirectUrl || ""}
{...formMethods.register("successRedirectUrl")}
@ -172,11 +172,7 @@ const AvailabilitySelect = ({
options={options}
isSearchable={false}
onChange={props.onChange}
classNamePrefix="react-select"
className={classNames(
"react-select-container focus:border-primary-500 focus:ring-primary-500 block w-full min-w-0 flex-1 rounded-sm border border-gray-300 sm:text-sm",
className
)}
className={classNames("block w-full min-w-0 flex-1 rounded-sm sm:text-sm", className)}
value={value}
/>
);
@ -356,7 +352,7 @@ const EventTypePage = (props: inferSSRProps<typeof getServerSideProps>) => {
{...locationFormMethods.register("locationAddress")}
id="address"
required
className="focus:border-primary-500 focus:ring-primary-500 block w-full rounded-sm border-gray-300 text-sm shadow-sm"
className=" block w-full rounded-sm border-gray-300 text-sm shadow-sm"
defaultValue={
formMethods
.getValues("locations")
@ -378,7 +374,7 @@ const EventTypePage = (props: inferSSRProps<typeof getServerSideProps>) => {
{...locationFormMethods.register("locationLink")}
id="address"
required
className="focus:border-primary-500 focus:ring-primary-500 block w-full rounded-sm border-gray-300 shadow-sm sm:text-sm"
className=" block w-full rounded-sm border-gray-300 shadow-sm sm:text-sm"
defaultValue={
formMethods.getValues("locations").find((location) => location.type === LocationType.Link)
?.link
@ -524,8 +520,7 @@ const EventTypePage = (props: inferSSRProps<typeof getServerSideProps>) => {
<Select
options={locationOptions}
isSearchable={false}
classNamePrefix="react-select"
className="react-select-container focus:border-primary-500 focus:ring-primary-500 block w-full min-w-0 flex-1 rounded-sm border border-gray-300 sm:text-sm"
className=" block w-full min-w-0 flex-1 rounded-sm sm:text-sm"
onChange={(e) => {
if (e?.value) {
const newLocationType: LocationType = e.value;
@ -958,7 +953,7 @@ const EventTypePage = (props: inferSSRProps<typeof getServerSideProps>) => {
id="slug"
aria-labelledby="slug-label"
required
className="focus:border-primary-500 focus:ring-primary-500 block w-full min-w-0 flex-1 rounded-none rounded-r-sm border-gray-300 sm:text-sm"
className=" block w-full min-w-0 flex-1 rounded-none rounded-r-sm border-gray-300 sm:text-sm"
defaultValue={eventType.slug}
{...formMethods.register("slug", {
setValueAs: (v) => slugify(v),
@ -1024,7 +1019,7 @@ const EventTypePage = (props: inferSSRProps<typeof getServerSideProps>) => {
<div className="w-full">
<textarea
id="description"
className="focus:border-primary-500 focus:ring-primary-500 block w-full rounded-sm border-gray-300 text-sm shadow-sm"
className=" block w-full rounded-sm border-gray-300 text-sm shadow-sm"
placeholder={t("quick_video_meeting")}
{...formMethods.register("description")}
defaultValue={asStringOrUndefined(eventType.description)}></textarea>
@ -1043,20 +1038,18 @@ const EventTypePage = (props: inferSSRProps<typeof getServerSideProps>) => {
{t("availability")} <InfoBadge content={t("you_can_manage_your_schedules")} />
</label>
</div>
<div className="w-full">
<Controller
name="schedule"
control={formMethods.control}
render={({ field }) => (
<AvailabilitySelect
value={field.value}
onBlur={field.onBlur}
name={field.name}
onChange={(selected) => field.onChange(selected?.value || null)}
/>
)}
/>
</div>
<Controller
name="schedule"
control={formMethods.control}
render={({ field }) => (
<AvailabilitySelect
value={field.value}
onBlur={field.onBlur}
name={field.name}
onChange={(selected) => field.onChange(selected?.value || null)}
/>
)}
/>
</div>
</div>
@ -1177,7 +1170,7 @@ const EventTypePage = (props: inferSSRProps<typeof getServerSideProps>) => {
<div className="relative mt-1 rounded-sm shadow-sm">
<input
type="text"
className="focus:border-primary-500 focus:ring-primary-500 block w-full rounded-sm border-gray-300 text-sm shadow-sm"
className=" block w-full rounded-sm border-gray-300 text-sm shadow-sm"
placeholder={t("meeting_with_user")}
defaultValue={eventType.eventName || ""}
{...formMethods.register("eventName")}
@ -1199,7 +1192,7 @@ const EventTypePage = (props: inferSSRProps<typeof getServerSideProps>) => {
{
<input
type="text"
className="focus:border-primary-500 focus:ring-primary-500 block w-full rounded-sm border-gray-300 text-sm shadow-sm"
className=" block w-full rounded-sm border-gray-300 text-sm shadow-sm"
placeholder={t("Example: 0x71c7656ec7ab88b098defb751b7401b5f6d8976f")}
defaultValue={(eventType.metadata.smartContractAddress || "") as string}
{...formMethods.register("smartContractAddress")}
@ -1362,45 +1355,40 @@ const EventTypePage = (props: inferSSRProps<typeof getServerSideProps>) => {
{t("slot_interval")}
</label>
</div>
<div className="w-full">
<div className="relative mt-1 rounded-sm shadow-sm">
<Controller
name="slotInterval"
control={formMethods.control}
render={() => {
const slotIntervalOptions = [
{
label: t("slot_interval_default"),
value: -1,
},
...[5, 10, 15, 20, 30, 45, 60].map((minutes) => ({
label: minutes + " " + t("minutes"),
value: minutes,
})),
];
return (
<Select
isSearchable={false}
classNamePrefix="react-select"
className="react-select-container focus:border-primary-500 focus:ring-primary-500 block w-full min-w-0 flex-1 rounded-sm border border-gray-300 sm:text-sm"
onChange={(val) => {
formMethods.setValue(
"slotInterval",
val && (val.value || 0) > 0 ? val.value : null
);
}}
defaultValue={
slotIntervalOptions.find(
(option) => option.value === eventType.slotInterval
) || slotIntervalOptions[0]
}
options={slotIntervalOptions}
/>
);
}}
/>
</div>
</div>
<Controller
name="slotInterval"
control={formMethods.control}
render={() => {
const slotIntervalOptions = [
{
label: t("slot_interval_default"),
value: -1,
},
...[5, 10, 15, 20, 30, 45, 60].map((minutes) => ({
label: minutes + " " + t("minutes"),
value: minutes,
})),
];
return (
<Select
isSearchable={false}
className="block w-full min-w-0 flex-1 rounded-sm sm:text-sm"
onChange={(val) => {
formMethods.setValue(
"slotInterval",
val && (val.value || 0) > 0 ? val.value : null
);
}}
defaultValue={
slotIntervalOptions.find(
(option) => option.value === eventType.slotInterval
) || slotIntervalOptions[0]
}
options={slotIntervalOptions}
/>
);
}}
/>
</div>
<hr className="my-2 border-neutral-200" />
@ -1436,14 +1424,14 @@ const EventTypePage = (props: inferSSRProps<typeof getServerSideProps>) => {
<div className="inline-flex">
<input
type="number"
className="focus:border-primary-500 focus:ring-primary-500 block w-12 rounded-sm border-gray-300 shadow-sm [appearance:textfield] ltr:mr-2 rtl:ml-2 sm:text-sm"
className="block w-12 rounded-sm border-gray-300 shadow-sm [appearance:textfield] ltr:mr-2 rtl:ml-2 sm:text-sm"
placeholder="30"
{...formMethods.register("periodDays", { valueAsNumber: true })}
defaultValue={eventType.periodDays || 30}
/>
<select
id=""
className="focus:border-primary-500 focus:ring-primary-500 block w-full rounded-sm border-gray-300 py-2 pl-3 pr-10 text-base focus:outline-none sm:text-sm"
className=" block w-full rounded-sm border-gray-300 py-2 pl-3 pr-10 text-base focus:outline-none sm:text-sm"
{...formMethods.register("periodCountCalendarDays")}
defaultValue={eventType.periodCountCalendarDays ? "1" : "0"}>
<option value="1">{t("calendar_days")}</option>
@ -1514,8 +1502,7 @@ const EventTypePage = (props: inferSSRProps<typeof getServerSideProps>) => {
return (
<Select
isSearchable={false}
classNamePrefix="react-select"
className="react-select-container focus:border-primary-500 focus:ring-primary-500 block w-full min-w-0 flex-1 rounded-sm border border-gray-300 sm:text-sm"
className=" block w-full min-w-0 flex-1 rounded-sm sm:text-sm"
onChange={(val) => {
if (val) onChange(val.value);
}}
@ -1553,8 +1540,7 @@ const EventTypePage = (props: inferSSRProps<typeof getServerSideProps>) => {
return (
<Select
isSearchable={false}
classNamePrefix="react-select"
className="react-select-container focus:border-primary-500 focus:ring-primary-500 block w-full min-w-0 flex-1 rounded-sm border border-gray-300 sm:text-sm"
className=" block w-full min-w-0 flex-1 rounded-sm sm:text-sm"
onChange={(val) => {
if (val) onChange(val.value);
}}
@ -1602,7 +1588,7 @@ const EventTypePage = (props: inferSSRProps<typeof getServerSideProps>) => {
id="requirePayment"
name="requirePayment"
type="checkbox"
className="text-primary-600 focus:ring-primary-500 h-4 w-4 rounded border-gray-300"
className="text-primary-600 h-4 w-4 rounded border-gray-300"
defaultChecked={requirePayment}
/>
</div>
@ -1639,7 +1625,7 @@ const EventTypePage = (props: inferSSRProps<typeof getServerSideProps>) => {
min="0.5"
type="number"
required
className="focus:border-primary-500 focus:ring-primary-500 block w-full rounded-sm border-gray-300 pl-2 pr-12 sm:text-sm"
className=" block w-full rounded-sm border-gray-300 pl-2 pr-12 sm:text-sm"
placeholder="Price"
onChange={(e) => {
field.onChange(e.target.valueAsNumber * 100);
@ -1781,8 +1767,7 @@ const EventTypePage = (props: inferSSRProps<typeof getServerSideProps>) => {
defaultValue={selectedLocation}
options={locationOptions}
isSearchable={false}
classNamePrefix="react-select"
className="react-select-container focus:border-primary-500 focus:ring-primary-500 my-4 block w-full min-w-0 flex-1 rounded-sm border border-gray-300 sm:text-sm"
className=" my-4 block w-full min-w-0 flex-1 rounded-sm border border-gray-300 sm:text-sm"
onChange={(val) => {
if (val) {
locationFormMethods.setValue("locationType", val.value);

View file

@ -4,8 +4,6 @@ import { GetServerSidePropsContext } from "next";
import { signOut } from "next-auth/react";
import { useRouter } from "next/router";
import { ComponentProps, FormEvent, RefObject, useEffect, useMemo, useRef, useState } from "react";
import Select from "react-select";
import TimezoneSelect, { ITimezone } from "react-timezone-select";
import showToast from "@calcom/lib/notification";
import { Alert } from "@calcom/ui/Alert";
@ -31,6 +29,8 @@ import Avatar from "@components/ui/Avatar";
import Badge from "@components/ui/Badge";
import InfoBadge from "@components/ui/InfoBadge";
import ColorPicker from "@components/ui/colorpicker";
import Select from "@components/ui/form/Select";
import TimezoneSelect, { ITimezone } from "@components/ui/form/TimezoneSelect";
import { UpgradeToProDialog } from "../../components/UpgradeToProDialog";
@ -225,7 +225,7 @@ function SettingsView(props: ComponentProps<typeof Settings> & { localeProp: str
autoComplete="given-name"
placeholder={t("your_name")}
required
className="mt-1 block w-full rounded-sm border border-gray-300 px-3 py-2 shadow-sm focus:border-neutral-800 focus:outline-none focus:ring-neutral-800 sm:text-sm"
className="mt-1 block w-full rounded-sm border border-gray-300 px-3 py-2 shadow-sm sm:text-sm"
defaultValue={props.user.name || undefined}
/>
</div>
@ -241,7 +241,7 @@ function SettingsView(props: ComponentProps<typeof Settings> & { localeProp: str
name="email"
id="email"
placeholder={t("your_email")}
className="mt-1 block w-full rounded-sm border-gray-300 shadow-sm focus:border-neutral-800 focus:ring-neutral-800 sm:text-sm"
className="mt-1 block w-full rounded-sm border-gray-300 shadow-sm sm:text-sm"
defaultValue={props.user.email}
/>
<p className="mt-2 text-sm text-gray-500" id="email-description">
@ -262,7 +262,7 @@ function SettingsView(props: ComponentProps<typeof Settings> & { localeProp: str
placeholder={t("little_something_about")}
rows={3}
defaultValue={props.user.bio || undefined}
className="mt-1 block w-full rounded-sm border-gray-300 shadow-sm focus:border-neutral-800 focus:ring-neutral-800 sm:text-sm"></textarea>
className="mt-1 block w-full rounded-sm border-gray-300 shadow-sm sm:text-sm"></textarea>
</div>
</div>
<div>
@ -314,8 +314,7 @@ function SettingsView(props: ComponentProps<typeof Settings> & { localeProp: str
id="languageSelect"
value={selectedLanguage || props.localeProp}
onChange={(v) => v && setSelectedLanguage(v)}
classNamePrefix="react-select"
className="react-select-container mt-1 block w-full rounded-sm border border-gray-300 capitalize shadow-sm focus:border-neutral-800 focus:ring-neutral-800 sm:text-sm"
className="mt-1 block w-full rounded-sm capitalize shadow-sm sm:text-sm"
options={localeOptions}
/>
</div>
@ -329,8 +328,7 @@ function SettingsView(props: ComponentProps<typeof Settings> & { localeProp: str
id="timeZone"
value={selectedTimeZone}
onChange={(v) => v && setSelectedTimeZone(v)}
classNamePrefix="react-select"
className="react-select-container mt-1 block w-full rounded-sm border border-gray-300 shadow-sm focus:border-neutral-800 focus:ring-neutral-800 sm:text-sm"
className="mt-1 block w-full rounded-sm shadow-sm sm:text-sm"
/>
</div>
</div>
@ -343,8 +341,7 @@ function SettingsView(props: ComponentProps<typeof Settings> & { localeProp: str
id="timeFormatSelect"
value={selectedTimeFormat || props.user.timeFormat}
onChange={(v) => v && setSelectedTimeFormat(v)}
classNamePrefix="react-select"
className="react-select-container mt-1 block w-full rounded-sm border border-gray-300 capitalize shadow-sm focus:border-neutral-800 focus:ring-neutral-800 sm:text-sm"
className="mt-1 block w-full rounded-sm capitalize shadow-sm sm:text-sm"
options={timeFormatOptions}
/>
</div>
@ -358,8 +355,7 @@ function SettingsView(props: ComponentProps<typeof Settings> & { localeProp: str
id="weekStart"
value={selectedWeekStartDay}
onChange={(v) => v && setSelectedWeekStartDay(v)}
classNamePrefix="react-select"
className="react-select-container mt-1 block w-full rounded-sm border border-gray-300 capitalize shadow-sm focus:border-neutral-800 focus:ring-neutral-800 sm:text-sm"
className="mt-1 block w-full rounded-sm capitalize shadow-sm sm:text-sm"
options={[
{ value: "Sunday", label: nameOfDay(props.localeProp, 0) },
{ value: "Monday", label: nameOfDay(props.localeProp, 1) },
@ -375,7 +371,7 @@ function SettingsView(props: ComponentProps<typeof Settings> & { localeProp: str
type="checkbox"
ref={allowDynamicGroupBookingRef}
defaultChecked={props.user.allowDynamicBooking || false}
className="h-4 w-4 rounded-sm border-gray-300 text-neutral-900 focus:ring-neutral-800"
className="h-4 w-4 rounded-sm border-gray-300 text-neutral-900 "
/>
</div>
<div className="text-sm ltr:ml-3 rtl:mr-3">
@ -397,7 +393,7 @@ function SettingsView(props: ComponentProps<typeof Settings> & { localeProp: str
defaultValue={selectedTheme || themeOptions[0]}
value={selectedTheme || themeOptions[0]}
onChange={(v) => v && setSelectedTheme(v)}
className="| { value: string } mt-1 block w-full rounded-sm border-gray-300 shadow-sm focus:border-neutral-800 focus:ring-neutral-800 sm:text-sm"
className="mt-1 block w-full rounded-sm shadow-sm sm:text-sm"
options={themeOptions}
/>
</div>
@ -409,7 +405,7 @@ function SettingsView(props: ComponentProps<typeof Settings> & { localeProp: str
type="checkbox"
onChange={(e) => setSelectedTheme(e.target.checked ? undefined : themeOptions[0])}
checked={!selectedTheme}
className="h-4 w-4 rounded-sm border-gray-300 text-neutral-900 focus:ring-neutral-800"
className="h-4 w-4 rounded-sm border-gray-300 text-neutral-900 "
/>
</div>
<div className="text-sm ltr:ml-3 rtl:mr-3">
@ -420,19 +416,18 @@ function SettingsView(props: ComponentProps<typeof Settings> & { localeProp: str
</div>
</div>
<div className="block rtl:space-x-reverse sm:flex sm:space-x-2">
<div className="mb-6 w-full sm:w-1/2">
<div className="mb-2 sm:w-1/2">
<label htmlFor="brandColor" className="block text-sm font-medium text-gray-700">
{t("light_brand_color")}
</label>
<ColorPicker defaultValue={props.user.brandColor} onChange={setBrandColor} />
</div>
<div className="mb-6 w-full sm:w-1/2">
<div className="mb-2 sm:w-1/2">
<label htmlFor="darkBrandColor" className="block text-sm font-medium text-gray-700">
{t("dark_brand_color")}
</label>
<ColorPicker defaultValue={props.user.darkBrandColor} onChange={setDarkBrandColor} />
</div>
<hr className="mt-6" />
</div>
<div>
<div className="relative flex items-start">

View file

@ -9,6 +9,31 @@
--brand-text-color-dark-mode: #292929;
}
/*
* Override the default tailwindcss-forms styling (default is: 'colors.blue.600')
* @see: https://github.com/tailwindlabs/tailwindcss-forms/issues/14#issuecomment-1005376006
*/
[type='text']:focus,
[type='email']:focus,
[type='url']:focus,
[type='password']:focus,
[type='number']:focus,
[type='date']:focus,
[type='datetime-local']:focus,
[type='month']:focus,
[type='search']:focus,
[type='tel']:focus,
[type='checkbox']:focus,
[type='radio']:focus,
[type='time']:focus,
[type='week']:focus,
[multiple]:focus,
textarea:focus,
select:focus {
--tw-ring-color: var(--brand-color);
border-color: var(--brand-color);
}
button[role="switch"][data-state="checked"] {
@apply bg-gray-900;
}
@ -36,7 +61,7 @@ button[role="switch"][data-state="checked"] span {
.react-daterange-picker > .react-daterange-picker__wrapper input {
/* Makes sure the on-focus behaviour is like Cal.com's */
@apply focus:border-primary-500 focus:ring-primary-500 my-0.5 h-auto rounded-sm py-0.5;
@apply my-0.5 h-auto rounded-sm py-0.5;
}
/* note(PeerRich): TODO move @layer components into proper React Components: <Button color="primary" size="xs" /> */
@ -192,7 +217,7 @@ button[role="switch"][data-state="checked"] span {
}
.react-multi-email > [type="text"] {
@apply focus:border-brand block w-full rounded-md border-gray-300 shadow-sm focus:ring-black dark:border-gray-900 dark:bg-gray-700 dark:text-white sm:text-sm;
@apply block w-full rounded-md border-gray-300 shadow-sm dark:border-gray-900 dark:bg-gray-700 dark:text-white sm:text-sm;
}
.react-multi-email [data-tag] {
@ -238,34 +263,6 @@ button[role="switch"][data-state="checked"] span {
cursor: pointer;
}
/* !important to override react-select */
.react-select__value-container {
border: 0 !important;
}
.react-select__input input {
border: 0 !important;
box-shadow: none !important;
}
.react-select__option--is-focused {
background: #000 !important;
color: #fff !important;
}
.react-select__control {
border: 2px solid transparent !important;
box-shadow: none !important;
}
.react-select__control.react-select__control--is-focused {
border: 2px solid #000 !important;
border-color: #000 !important;
box-shadow: none !important;
margin: -1px;
padding: 1px;
}
/* !important to override react-dates */
.DateRangePickerInput__withBorder {
border: 0 !important;