diff --git a/src/components/Checkbox.tsx b/src/components/Checkbox.tsx index a743ecfbf5debb3a310ed8b56424811afd4d3d37..78f350520d3ad4c78df2fb3f787274971a7920b3 100644 --- a/src/components/Checkbox.tsx +++ b/src/components/Checkbox.tsx @@ -3,6 +3,7 @@ import { Dispatch, SetStateAction } from "react"; import { clsx } from "clsx"; import Icon from "./Icon.tsx"; import { formFieldBg, cursorPointer } from "./utils/classes.ts"; +import Typography from "./Typography.tsx"; type CheckboxProps = { isDisabled?: boolean; @@ -10,6 +11,8 @@ type CheckboxProps = { isSelected: boolean; setIsSelected?: Dispatch<SetStateAction<boolean>>; label?: string; + labelClass?: string; + labelCustomColor?: boolean; }; const Checkbox = ({ @@ -18,6 +21,8 @@ const Checkbox = ({ isSelected, setIsSelected, label, + labelClass = "", + labelCustomColor = false, }: CheckboxProps) => { return ( <NextCheckbox @@ -39,7 +44,14 @@ const Checkbox = ({ ), }} > - {label} + {label && ( + <Typography + text={label} + variant="paragraph" + customClass={labelClass} + customColor={labelCustomColor} + /> + )} </NextCheckbox> ); }; diff --git a/src/components/DateInput.tsx b/src/components/DateInput.tsx index feced40620458d18a265fc4511ae432ac37f6d36..24f913d3758099008ec173d859b3db98be703e5d 100644 --- a/src/components/DateInput.tsx +++ b/src/components/DateInput.tsx @@ -15,6 +15,8 @@ type DateInputProps = { isRequired?: boolean; label: string; labelPlacement?: LabelPlacement; + labelClass?: string; + labelCustomColor?: boolean; isRange?: boolean; value?: DateNull; startDate?: DateNull; @@ -31,6 +33,8 @@ const DateInput = ({ isRequired = false, label, labelPlacement = "outside", + labelClass = "", + labelCustomColor = false, value, startDate, endDate, @@ -66,6 +70,8 @@ const DateInput = ({ isRequired={isRequired} label={label} labelPlacement={labelPlacement} + labelClass={labelClass} + labelCustomColor={labelCustomColor} endContent={ <Icon name="datepicker" diff --git a/src/components/Dropdown.tsx b/src/components/Dropdown.tsx index a0e4796e15bbb711b34b3a92f584e532071cf820..2dd1cc193bfbe3fb270eaaef3afd2d4bc6946bf6 100644 --- a/src/components/Dropdown.tsx +++ b/src/components/Dropdown.tsx @@ -4,10 +4,15 @@ import { clsx } from "clsx"; import { baseInputClassNames } from "./utils/baseComponents.tsx"; import Icon from "./Icon.tsx"; import { textColor } from "./utils/classes.ts"; +import Typography from "./Typography.tsx"; type DropdownProps = { isRequired?: boolean; + isDisabled?: boolean; label?: string; + labelPlacement?: "outside" | "outside-left"; + labelClass?: string; + labelCustomColor?: boolean; valueOptions: { value: string; label: string }[]; value?: string; onValueChange: Dispatch<SetStateAction<string | undefined>>; @@ -15,7 +20,11 @@ type DropdownProps = { const Dropdown = ({ isRequired = false, + isDisabled = false, label, + labelPlacement = "outside", + labelClass = "", + labelCustomColor = false, valueOptions, value, onValueChange, @@ -27,7 +36,16 @@ const Dropdown = ({ return ( <Autocomplete isRequired={isRequired} - label={label} + label={ + label && ( + <Typography + text={label} + variant="paragraph" + customClass={labelClass} + customColor={labelCustomColor} + /> + ) + } selectedKey={value} onSelectionChange={(key) => onValueChange(key as string)} labelPlacement="outside" @@ -35,7 +53,7 @@ const Dropdown = ({ disableAnimation disableSelectorIconRotation inputProps={{ - classNames: baseInputClassNames(false), + classNames: baseInputClassNames(isDisabled, labelPlacement), variant: "bordered", isRequired: isRequired, }} // use the same props as BaseInput diff --git a/src/components/TextArea.tsx b/src/components/TextArea.tsx index 41708b6522fc598a0ae6c75fecc52c153796f848..e5169ecf672895fd9c7204b781b766f94c631473 100644 --- a/src/components/TextArea.tsx +++ b/src/components/TextArea.tsx @@ -3,12 +3,15 @@ import { Textarea } from "@nextui-org/react"; import { TextInputProps } from "./TextInput"; import { formFieldBg, cursorPointer } from "./utils/classes.ts"; import { LabelPlacement } from "./utils/baseComponents.tsx"; +import Typography from "./Typography.tsx"; interface TextAreaProps extends Omit<TextInputProps, OmittedTextInputProps> { minRows?: number; maxRows?: number; disableAutosize?: boolean; labelPlacement?: LabelPlacement; + labelClass?: string; + labelCustomColor?: boolean; } type OmittedTextInputProps = "isClearable" | "type" | "inputMode"; @@ -35,24 +38,23 @@ const classNames = ( return { base: clsx( - labelPlacement === "outside-left" - ? "flex flex-row justify-between items-start" - : "", + { + "flex flex-row justify-between items-start": + labelPlacement === "outside-left", + }, cursorPointer(isDisabled) ), - label: clsx( - "!text-foreground-body ml-[1px]", - labelPlacement === "outside-left" ? "w-1/3" : "" - ), + label: clsx("!text-foreground-body ml-[1px]", { + "w-1/3": labelPlacement === "outside-left", + }), inputWrapper: clsx( inputWrapperClass(isDisabled), "px-2 rounded-[4px]", formFieldBg(isDisabled) ), - input: clsx( - "!text-foreground-body bg-default", - disableAutosize ? "resize-y" : "" - ), + input: clsx("!text-foreground-body bg-default", { + "resize-y": disableAutosize, + }), }; }; @@ -70,13 +72,24 @@ const TextArea = ({ errorMessage, disableAutosize = false, labelPlacement = "outside", + labelClass = "", + labelCustomColor = false, }: TextAreaProps) => { return ( <Textarea isRequired={isRequired} isDisabled={isDisabled} isReadOnly={isReadOnly} - label={label} + label={ + label && ( + <Typography + text={label} + variant="paragraph" + customClass={labelClass} + customColor={labelCustomColor} + /> + ) + } minRows={minRows} maxRows={maxRows} maxLength={maxLength} diff --git a/src/components/TextInput.tsx b/src/components/TextInput.tsx index 0db64624ce36b81636116bc66016e6dabbe2f654..43c3891c68a323d89a7e33267f1ae354e52b38ee 100644 --- a/src/components/TextInput.tsx +++ b/src/components/TextInput.tsx @@ -5,6 +5,8 @@ type TextInputProps = { isRequired?: boolean; label?: string; labelPlacement?: LabelPlacement; + labelClass?: string; + labelCustomColor?: boolean; endContent?: ReactNode; // such as units (as text element e.g. <span>) isClearable?: boolean; value: string; @@ -37,6 +39,8 @@ const TextInput = ({ isRequired = false, label = "", labelPlacement = "outside", + labelClass = "", + labelCustomColor = false, endContent, isClearable = false, value, @@ -54,6 +58,8 @@ const TextInput = ({ isRequired={isRequired} label={label} labelPlacement={labelPlacement} + labelClass={labelClass} + labelCustomColor={labelCustomColor} endContent={endContent} placeholder=" " isClearable={isClearable} diff --git a/src/components/Toggle.tsx b/src/components/Toggle.tsx index 2890d3e45c9bb6430e249b97623e684011b528b8..20e6cf520b3071bb284a9e43ddd23a4ed2b6423d 100644 --- a/src/components/Toggle.tsx +++ b/src/components/Toggle.tsx @@ -2,6 +2,7 @@ import { Dispatch, SetStateAction } from "react"; import { Switch } from "@nextui-org/react"; import { clsx } from "clsx"; import { cursorPointer } from "./utils/classes.ts"; +import Typography from "./Typography.tsx"; type ToggleProps = { isDisabled?: boolean; @@ -9,6 +10,8 @@ type ToggleProps = { setIsSelected?: Dispatch<SetStateAction<boolean>>; label?: string; labelPlacement?: "right" | "left"; + labelClass?: string; + labelCustomColor?: boolean; width?: "fit" | "full"; }; @@ -18,6 +21,8 @@ const Toggle = ({ setIsSelected, label, labelPlacement = "right", + labelClass = "", + labelCustomColor = false, width = "fit", }: ToggleProps) => { const selectedClass = (isSelected: boolean) => @@ -29,7 +34,14 @@ const Toggle = ({ width === "fit" ? "w-fit" : "w-full" )} > - {labelPlacement === "left" && <div className="mr-2">{label}</div>} + {labelPlacement === "left" && label && ( + <Typography + variant="paragraph" + customClass={clsx("mr-2", labelClass)} + text={label} + customColor={labelCustomColor} + /> + )} <Switch isDisabled={isDisabled} // Does not support "isRequired": https://github.com/nextui-org/nextui/issues/1610 @@ -43,7 +55,14 @@ const Toggle = ({ "h-[8px] w-[25px] p-0 overflow-visible bg-lightGrey dark:bg-mediumGrey", }} /> - {labelPlacement === "right" && label} + {labelPlacement === "right" && label && ( + <Typography + variant="paragraph" + text={label} + customClass={labelClass} + customColor={labelCustomColor} + /> + )} </div> ); }; diff --git a/src/components/Typography.tsx b/src/components/Typography.tsx index 53835edcf34565bc51fd771447d1ca59bcd3a17d..ffc157f203e1f4dc1b823bf88e7be9893045cccf 100644 --- a/src/components/Typography.tsx +++ b/src/components/Typography.tsx @@ -32,6 +32,7 @@ const Typography = ({ customColor = false, customClass = "", }: TypographyProps) => { + // font-body is Inter, font-heading is Montserrat const font = { overtitle: "text-[16px] font-heading font-bold", title: "text-[70px] font-heading font-bold", diff --git a/src/components/utils/baseComponents.tsx b/src/components/utils/baseComponents.tsx index 39764bbbabf609e7304d660b822edb60348d32c4..463c61dbbcbdb8fec6597d24c93721fd9a0ce09e 100644 --- a/src/components/utils/baseComponents.tsx +++ b/src/components/utils/baseComponents.tsx @@ -2,6 +2,7 @@ import { Input } from "@nextui-org/react"; import { Dispatch, ReactNode, SetStateAction } from "react"; import { InputSlots } from "@nextui-org/theme"; import { clsx } from "clsx"; +import Typography from "src/components/Typography.tsx"; import { formFieldBg, cursorPointer } from "./classes.ts"; export type LabelPlacement = "outside" | "outside-left"; @@ -10,6 +11,8 @@ type BaseInputProps = { // basic info label: string; labelPlacement: LabelPlacement; + labelClass: string; + labelCustomColor: boolean; endContent: ReactNode; // such as units or icon // value and validation placeholder: string; @@ -45,7 +48,8 @@ type BaseInputProps = { type baseInputClassNamesObj = { [key in InputSlots]: string }; export const baseInputClassNames = ( - isDisabled: boolean + isDisabled: boolean, + labelPlacement: LabelPlacement ): baseInputClassNamesObj => { // this is the border color (use text-*) const mainWrapperClass = (isDisabled: boolean) => @@ -54,24 +58,29 @@ export const baseInputClassNames = ( : "text-grey dark:text-mediumGrey hover:text-baseBlack focus-within:!text-primary"; const clearButtonClass = (isDisabled: boolean) => { + if (isDisabled) return ""; + // we cannot do "[&>svg]:" + className because Tailwind does not support dynamic class names const clearButtonSvgClass: string = "[&>svg]:w-[30px] [&>svg]:h-[30px] [&>svg]:mt-[-4px] [&>svg]:ml-[-4px]"; const clearButtonBaseClass: string = "bg-mediumGrey text-baseWhite dark:bg-baseWhite dark:text-mediumGrey w-[22px] h-[22px] p-0 !opacity-100"; - - return isDisabled ? "" : clsx(clearButtonSvgClass, clearButtonBaseClass); + return clsx(clearButtonSvgClass, clearButtonBaseClass); }; return { base: clsx("flex flex-row justify-between", cursorPointer(isDisabled)), - label: "!text-foreground-body", input: "!text-foreground-body bg-default", inputWrapper: clsx( "px-2 rounded-[4px] min-h-[33px] max-h-[33px]", formFieldBg(isDisabled) ), - mainWrapper: clsx("w-2/3", mainWrapperClass(isDisabled)), + mainWrapper: clsx( + { + "w-2/3": labelPlacement === "outside-left", + }, + mainWrapperClass(isDisabled) + ), clearButton: clearButtonClass(isDisabled), } as baseInputClassNamesObj; }; @@ -79,6 +88,8 @@ export const baseInputClassNames = ( export const BaseInput = ({ label, labelPlacement = "outside", + labelClass, + labelCustomColor, endContent, placeholder, isClearable = false, @@ -95,7 +106,14 @@ export const BaseInput = ({ }: BaseInputProps) => { return ( <Input - label={label} + label={ + <Typography + text={label} + variant="paragraph" + customClass={labelClass} + customColor={labelCustomColor} + /> + } endContent={endContent} placeholder={placeholder} value={value} @@ -112,7 +130,7 @@ export const BaseInput = ({ // appearance labelPlacement={labelPlacement} variant="bordered" - classNames={baseInputClassNames(isDisabled)} + classNames={baseInputClassNames(isDisabled, labelPlacement)} /> ); };