diff --git a/src/components/Dropdown.tsx b/src/components/Dropdown.tsx index ffca3f46e5247bceb00d7f963fc631947aa8ed35..b2056dd5e9142a2a0cb6db167564cdd4c0c0a797 100644 --- a/src/components/Dropdown.tsx +++ b/src/components/Dropdown.tsx @@ -25,7 +25,7 @@ const Dropdown = ({ placeholder=" " // so the label stays outside disableAnimation disableSelectorIconRotation - inputProps={{ classNames: baseInputClassNames, variant: "bordered" }} // use the same props as BaseInput + inputProps={{ classNames: baseInputClassNames(false) }} // use the same props as BaseInput selectorIcon={<Icon name="dropdown" />} isClearable={false} // so the "cross" button does not show isReadOnly // so the input is not searchable diff --git a/src/components/TextInput.tsx b/src/components/TextInput.tsx index 97166e4cf7c66831a0059accbc903a4b71237694..31d27e15bca737533513954da52528912907a5d6 100644 --- a/src/components/TextInput.tsx +++ b/src/components/TextInput.tsx @@ -6,9 +6,10 @@ type TextInputProps = { endContent?: ReactNode; // such as units (as text element e.g. <span>) isClearable?: boolean; value: string; - onValueChange: Dispatch<SetStateAction<string>>; + onValueChange?: Dispatch<SetStateAction<string>>; isInvalid?: boolean; errorMessage?: string; + isDisabled?: boolean; }; const TextInput = ({ @@ -19,6 +20,7 @@ const TextInput = ({ onValueChange, isInvalid = false, errorMessage = "", + isDisabled = false, }: TextInputProps) => { return ( <BaseInput @@ -30,6 +32,7 @@ const TextInput = ({ onValueChange={onValueChange} isInvalid={isInvalid} errorMessage={errorMessage} + isDisabled={isDisabled} /> ); }; diff --git a/src/components/utils/baseComponents.tsx b/src/components/utils/baseComponents.tsx index a6d62d59ccbe8566eb93514dc02cc8a8e57a5ee9..61d002df2986572b49b2a88aac18b85273b84710 100644 --- a/src/components/utils/baseComponents.tsx +++ b/src/components/utils/baseComponents.tsx @@ -1,5 +1,6 @@ import { Input } from "@nextui-org/react"; import { Dispatch, ReactNode, SetStateAction } from "react"; +import { InputSlots } from "@nextui-org/theme"; type BaseInputProps = { // basic info @@ -9,24 +10,40 @@ type BaseInputProps = { placeholder: string; isClearable?: boolean; value?: string; - onValueChange: Dispatch<SetStateAction<string>>; + onValueChange?: Dispatch<SetStateAction<string>>; isInvalid: boolean; errorMessage: string; + // interaction + isDisabled: boolean; }; -// No we cannot do "[&>svg]:" + className because Tailwind does not support dynamic class names -const clearButtonSvgClass = - "[&>svg]:w-[42px] [&>svg]:h-[42px] [&>svg]:mt-[-6px] [&>svg]:ml-[-5px]"; +type baseInputClassNamesObj = { [key in InputSlots]: string }; -export const baseInputClassNames = { - label: "!text-foreground-body", - mainWrapper: "text-grey dark:text-mediumGrey", - inputWrapper: "bg-baseWhite dark:bg-mediumGrey rounded-[4px] px-2", - input: "!text-foreground-body bg-default", - clearButton: - clearButtonSvgClass + - " " + - "bg-mediumGrey text-baseWhite dark:bg-baseWhite dark:text-mediumGrey w-[30px] h-[30px] p-0 !opacity-100", +export const baseInputClassNames = (isDisabled: boolean) => { + // No we cannot do "[&>svg]:" + className because Tailwind does not support dynamic class names + const clearButtonSvgClass = + "[&>svg]:w-[42px] [&>svg]:h-[42px] [&>svg]:mt-[-6px] [&>svg]:ml-[-5px]"; + + const defaultClass = { + label: "!text-foreground-body", + input: "!text-foreground-body bg-default", + inputWrapper: "px-2 rounded-[4px] bg-baseWhite dark:bg-mediumGrey", + mainWrapper: "text-grey dark:text-mediumGrey", // this is the border color (use text-*) + clearButton: + clearButtonSvgClass + + " " + + "bg-mediumGrey text-baseWhite dark:bg-baseWhite dark:text-mediumGrey w-[30px] h-[30px] p-0 !opacity-100", + } as baseInputClassNamesObj; + + const disabledClass = { + base: "opacity-100 pointer-events-auto cursor-not-allowed", + label: "!text-foreground-body", + input: "!text-foreground-body bg-default", + inputWrapper: "px-2 rounded-[4px] bg-lightGrey dark:bg-darkModeBlue", + mainWrapper: "text-mediumGrey dark:text-darkModeBlue", + } as baseInputClassNamesObj; + + return isDisabled ? disabledClass : defaultClass; }; export const BaseInput = ({ @@ -38,6 +55,7 @@ export const BaseInput = ({ onValueChange, isInvalid, errorMessage, + isDisabled, }: BaseInputProps) => { return ( <Input @@ -50,10 +68,11 @@ export const BaseInput = ({ onValueChange={onValueChange} isInvalid={isInvalid} errorMessage={errorMessage} + isDisabled={isDisabled} // appearance labelPlacement="outside" variant="bordered" - classNames={baseInputClassNames} + classNames={baseInputClassNames(isDisabled)} /> ); }; diff --git a/src/index.css b/src/index.css index 16a15f482165a16507c297960203a5d5fcdc7332..876d6a0c7db242412402ef989b5d1acdb722037e 100644 --- a/src/index.css +++ b/src/index.css @@ -2701,6 +2701,11 @@ video { color: hsl(var(--nextui-warning) / 0.8); } +.text-mediumGrey { + --tw-text-opacity: 1; + color: rgb(49 49 49 / var(--tw-text-opacity)); +} + .underline { text-decoration-line: underline; } @@ -3205,7 +3210,7 @@ video { .dark,[data-theme="dark"] { color-scheme: dark; - --nextui-background: 240 100% 7%; + --nextui-background: 240 25% 13%; --nextui-foreground-50: 240 6% 10%; --nextui-foreground-100: 240 4% 16%; --nextui-foreground-200: 240 5% 26%; @@ -4480,6 +4485,11 @@ video { opacity: 1; } +.focus\:text-baseBlack:focus { + --tw-text-opacity: 1; + color: rgb(0 0 0 / var(--tw-text-opacity)); +} + .focus\:underline:focus { text-decoration-line: underline; } @@ -4509,6 +4519,16 @@ video { outline-color: hsl(var(--nextui-focus) / var(--nextui-focus-opacity, 1)); } +.active\:text-primary:active { + --tw-text-opacity: 1; + color: hsl(var(--nextui-primary) / var(--nextui-primary-opacity, var(--tw-text-opacity))); +} + +.active\:\!text-primary:active { + --tw-text-opacity: 1 !important; + color: hsl(var(--nextui-primary) / var(--nextui-primary-opacity, var(--tw-text-opacity))) !important; +} + .active\:underline:active { text-decoration-line: underline; } @@ -5047,6 +5067,11 @@ video { color: hsl(var(--nextui-secondary) / var(--nextui-secondary-opacity, var(--tw-text-opacity))); } +.data-\[active\=false\]\:text-grey[data-active=false] { + --tw-text-opacity: 1; + color: rgb(119 119 119 / var(--tw-text-opacity)); +} + .data-\[disabled\=true\]\:opacity-30[data-disabled=true] { opacity: 0.3; } @@ -6205,6 +6230,11 @@ video { background-color: hsl(var(--nextui-content2) / var(--nextui-content2-opacity, var(--tw-bg-opacity))); } +:is(.dark .dark\:bg-darkModeBlue) { + --tw-bg-opacity: 1; + background-color: rgb(0 0 34 / var(--tw-bg-opacity)); +} + :is(.dark .dark\:bg-default) { --tw-bg-opacity: 1; background-color: hsl(var(--nextui-default) / var(--nextui-default-opacity, var(--tw-bg-opacity))); @@ -6235,6 +6265,11 @@ video { color: hsl(var(--nextui-warning) / var(--nextui-warning-opacity, var(--tw-text-opacity))); } +:is(.dark .dark\:text-darkModeBlue) { + --tw-text-opacity: 1; + color: rgb(0 0 34 / var(--tw-text-opacity)); +} + :is(.dark .dark\:placeholder\:text-success)::-moz-placeholder { --tw-text-opacity: 1; color: hsl(var(--nextui-success) / var(--nextui-success-opacity, var(--tw-text-opacity))); diff --git a/src/stories/TextInput.stories.tsx b/src/stories/TextInput.stories.tsx index d3ece2232f92fd24bf975dbe1f49f2aee23a4641..d949d8b171b8a1d81052a4136735ed7941901fa8 100644 --- a/src/stories/TextInput.stories.tsx +++ b/src/stories/TextInput.stories.tsx @@ -43,3 +43,13 @@ export const IsClearable = () => { /> ); }; + +export const Disabled = () => { + return ( + <TextInput + label="You can't change this!" + isDisabled={true} + value="*Evil laughter*" + /> + ); +}; diff --git a/tailwind.config.js b/tailwind.config.js index dc91dc347c4e40c93cd4fd7a8c23b83b11407ee1..fb61e45644763fe6994166019a011a5d8efff565 100644 --- a/tailwind.config.js +++ b/tailwind.config.js @@ -61,7 +61,7 @@ export default { }, dark: { colors: { - background: palette.darkModeBlue, + background: palette.darkBlue, foreground: { heading: palette.baseWhite, heading2: palette.lightBlue,