base
This commit is contained in:
@@ -0,0 +1,96 @@
|
||||
/**
|
||||
* Copyright (c) 2023-present Plane Software, Inc. and contributors
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
* See the LICENSE file for details.
|
||||
*/
|
||||
|
||||
import * as React from "react";
|
||||
// helpers
|
||||
import { cn } from "../utils";
|
||||
|
||||
export interface CheckboxProps extends React.InputHTMLAttributes<HTMLInputElement> {
|
||||
containerClassName?: string;
|
||||
iconClassName?: string;
|
||||
indeterminate?: boolean;
|
||||
}
|
||||
|
||||
const Checkbox = React.forwardRef(function Checkbox(props: CheckboxProps, ref: React.ForwardedRef<HTMLInputElement>) {
|
||||
const {
|
||||
id,
|
||||
name,
|
||||
checked,
|
||||
indeterminate = false,
|
||||
disabled,
|
||||
containerClassName,
|
||||
iconClassName,
|
||||
className,
|
||||
...rest
|
||||
} = props;
|
||||
|
||||
return (
|
||||
<div className={cn("relative flex flex-shrink-0 gap-2", containerClassName)}>
|
||||
<input
|
||||
id={id}
|
||||
ref={ref}
|
||||
type="checkbox"
|
||||
name={name}
|
||||
checked={checked}
|
||||
className={cn(
|
||||
"size-4 shrink-0 cursor-pointer appearance-none rounded-[3px] border focus:outline-1 focus:outline-offset-4 focus:outline-accent-strong",
|
||||
{
|
||||
"cursor-not-allowed border-subtle bg-layer-1": disabled,
|
||||
"border-strong bg-transparent hover:border-strong-1": !disabled,
|
||||
"border-accent-strong-40 hover:border-accent-strong-40 bg-accent-primary hover:bg-accent-primary/80":
|
||||
!disabled && (checked || indeterminate),
|
||||
|
||||
"border-none": checked,
|
||||
},
|
||||
className
|
||||
)}
|
||||
disabled={disabled}
|
||||
{...rest}
|
||||
/>
|
||||
<svg
|
||||
className={cn(
|
||||
"pointer-events-none absolute top-1/2 left-1/2 hidden size-4 -translate-x-1/2 -translate-y-1/2 p-0.5 text-on-color outline-none",
|
||||
{
|
||||
block: checked,
|
||||
"text-placeholder opacity-40": disabled,
|
||||
},
|
||||
iconClassName
|
||||
)}
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
viewBox="0 0 24 24"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
strokeWidth="3"
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
>
|
||||
<polyline points="20 6 9 17 4 12" />
|
||||
</svg>
|
||||
<svg
|
||||
className={cn(
|
||||
"pointer-events-none absolute top-1/2 left-1/2 hidden size-4 -translate-x-1/2 -translate-y-1/2 stroke-white p-0.5 outline-none",
|
||||
{
|
||||
"stroke-placeholder opacity-40": disabled,
|
||||
block: indeterminate && !checked,
|
||||
},
|
||||
iconClassName
|
||||
)}
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
viewBox="0 0 8 8"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
strokeWidth="3"
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
>
|
||||
<path d="M5.75 4H2.25" strokeWidth="1.5" strokeLinecap="round" strokeLinejoin="round" />
|
||||
</svg>
|
||||
</div>
|
||||
);
|
||||
});
|
||||
Checkbox.displayName = "form-checkbox-field";
|
||||
|
||||
export { Checkbox };
|
||||
@@ -0,0 +1,12 @@
|
||||
/**
|
||||
* Copyright (c) 2023-present Plane Software, Inc. and contributors
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
* See the LICENSE file for details.
|
||||
*/
|
||||
|
||||
export * from "./input";
|
||||
export * from "./textarea";
|
||||
export * from "./input-color-picker";
|
||||
export * from "./checkbox";
|
||||
export * from "./root";
|
||||
export * from "./password";
|
||||
@@ -0,0 +1,111 @@
|
||||
/**
|
||||
* Copyright (c) 2023-present Plane Software, Inc. and contributors
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
* See the LICENSE file for details.
|
||||
*/
|
||||
|
||||
import { Popover, Transition } from "@headlessui/react";
|
||||
import * as React from "react";
|
||||
import * as ColorPicker from "react-color";
|
||||
import type { ColorResult } from "react-color";
|
||||
import { usePopper } from "react-popper";
|
||||
// helpers
|
||||
import { Button } from "../button";
|
||||
import { cn } from "../utils";
|
||||
// components
|
||||
import { Input } from "./input";
|
||||
|
||||
export interface InputColorPickerProps {
|
||||
hasError: boolean;
|
||||
value: string | undefined;
|
||||
onChange: (value: string) => void;
|
||||
name: string;
|
||||
className?: string;
|
||||
style?: React.CSSProperties;
|
||||
placeholder: string;
|
||||
}
|
||||
|
||||
export function InputColorPicker(props: InputColorPickerProps) {
|
||||
const { value, hasError, onChange, name, className, style, placeholder } = props;
|
||||
|
||||
const [referenceElement, setReferenceElement] = React.useState<HTMLButtonElement | null>(null);
|
||||
const [popperElement, setPopperElement] = React.useState<HTMLDivElement | null>(null);
|
||||
|
||||
const { styles, attributes } = usePopper(referenceElement, popperElement, {
|
||||
placement: "auto",
|
||||
});
|
||||
|
||||
const handleColorChange = (newColor: ColorResult) => {
|
||||
const { hex } = newColor;
|
||||
onChange(hex);
|
||||
};
|
||||
|
||||
const handleInputChange = (e: React.ChangeEvent<HTMLInputElement>) => {
|
||||
onChange(e.target.value);
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="relative">
|
||||
<Input
|
||||
id={name}
|
||||
name={name}
|
||||
type="text"
|
||||
value={value}
|
||||
onChange={handleInputChange}
|
||||
hasError={hasError}
|
||||
placeholder={placeholder}
|
||||
className={cn("border-[0.5px] border-subtle", className)}
|
||||
style={style}
|
||||
/>
|
||||
|
||||
<Popover as="div" className="absolute top-1/2 right-1 z-10 -translate-y-1/2">
|
||||
{() => (
|
||||
<>
|
||||
<Popover.Button as={React.Fragment}>
|
||||
<Button ref={setReferenceElement} variant="neutral-primary" className="border-none !bg-transparent">
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
width="14"
|
||||
height="14"
|
||||
viewBox="0 0 24 24"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
strokeWidth="2"
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
className="lucide lucide-palette"
|
||||
>
|
||||
<circle cx="13.5" cy="6.5" r=".5" />
|
||||
<circle cx="17.5" cy="10.5" r=".5" />
|
||||
<circle cx="8.5" cy="7.5" r=".5" />
|
||||
<circle cx="6.5" cy="12.5" r=".5" />
|
||||
<path d="M12 2C6.5 2 2 6.5 2 12s4.5 10 10 10c.926 0 1.648-.746 1.648-1.688 0-.437-.18-.835-.437-1.125-.29-.289-.438-.652-.438-1.125a1.64 1.64 0 0 1 1.668-1.668h1.996c3.051 0 5.555-2.503 5.555-5.554C21.965 6.012 17.461 2 12 2z" />
|
||||
</svg>
|
||||
</Button>
|
||||
</Popover.Button>
|
||||
<Transition
|
||||
as={React.Fragment}
|
||||
enter="transition ease-out duration-200"
|
||||
enterFrom="opacity-0 translate-y-1"
|
||||
enterTo="opacity-100 translate-y-0"
|
||||
leave="transition ease-in duration-150"
|
||||
leaveFrom="opacity-100 translate-y-0"
|
||||
leaveTo="opacity-0 translate-y-1"
|
||||
>
|
||||
<Popover.Panel>
|
||||
<div
|
||||
className="z-10 overflow-hidden rounded-sm border border-subtle bg-surface-1 shadow-raised-200"
|
||||
ref={setPopperElement}
|
||||
style={styles.popper}
|
||||
{...attributes.popper}
|
||||
>
|
||||
<ColorPicker.SketchPicker color={value} onChange={handleColorChange} />
|
||||
</div>
|
||||
</Popover.Panel>
|
||||
</Transition>
|
||||
</>
|
||||
)}
|
||||
</Popover>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,59 @@
|
||||
/**
|
||||
* Copyright (c) 2023-present Plane Software, Inc. and contributors
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
* See the LICENSE file for details.
|
||||
*/
|
||||
|
||||
import * as React from "react";
|
||||
// helpers
|
||||
import { cn } from "../utils";
|
||||
|
||||
export interface InputProps extends React.InputHTMLAttributes<HTMLInputElement> {
|
||||
mode?: "primary" | "transparent" | "true-transparent";
|
||||
inputSize?: "xs" | "sm" | "md";
|
||||
hasError?: boolean;
|
||||
className?: string;
|
||||
}
|
||||
|
||||
const Input = React.forwardRef(function Input(props: InputProps, ref: React.ForwardedRef<HTMLInputElement>) {
|
||||
const {
|
||||
id,
|
||||
type,
|
||||
name,
|
||||
mode = "primary",
|
||||
inputSize = "sm",
|
||||
hasError = false,
|
||||
className = "",
|
||||
autoComplete = "off",
|
||||
...rest
|
||||
} = props;
|
||||
|
||||
return (
|
||||
<input
|
||||
id={id}
|
||||
ref={ref}
|
||||
type={type}
|
||||
name={name}
|
||||
className={cn(
|
||||
"placeholder-tertiary block rounded-md border-subtle-1 bg-layer-2 text-13 focus:outline-none",
|
||||
{
|
||||
"rounded-md border-[0.5px]": mode === "primary",
|
||||
"rounded-sm border-none bg-transparent ring-0 transition-all focus:ring-1 focus:ring-accent-strong":
|
||||
mode === "transparent",
|
||||
"rounded-sm border-none bg-transparent ring-0": mode === "true-transparent",
|
||||
"border-danger-strong": hasError,
|
||||
"px-1.5 py-1": inputSize === "xs",
|
||||
"px-3 py-2": inputSize === "sm",
|
||||
"p-3": inputSize === "md",
|
||||
},
|
||||
className
|
||||
)}
|
||||
autoComplete={autoComplete}
|
||||
{...rest}
|
||||
/>
|
||||
);
|
||||
});
|
||||
|
||||
Input.displayName = "form-input-field";
|
||||
|
||||
export { Input };
|
||||
@@ -0,0 +1,71 @@
|
||||
/**
|
||||
* Copyright (c) 2023-present Plane Software, Inc. and contributors
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
* See the LICENSE file for details.
|
||||
*/
|
||||
|
||||
import { E_PASSWORD_STRENGTH } from "@plane/constants";
|
||||
|
||||
export interface StrengthInfo {
|
||||
message: string;
|
||||
textColor: string;
|
||||
activeFragments: number;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get strength information including message, color, and active fragments
|
||||
*/
|
||||
export const getStrengthInfo = (strength: E_PASSWORD_STRENGTH): StrengthInfo => {
|
||||
switch (strength) {
|
||||
case E_PASSWORD_STRENGTH.EMPTY:
|
||||
return {
|
||||
message: "Please enter your password",
|
||||
textColor: "text-primary",
|
||||
activeFragments: 0,
|
||||
};
|
||||
case E_PASSWORD_STRENGTH.LENGTH_NOT_VALID:
|
||||
return {
|
||||
message: "Password is too short",
|
||||
textColor: "text-danger-primary",
|
||||
activeFragments: 1,
|
||||
};
|
||||
case E_PASSWORD_STRENGTH.STRENGTH_NOT_VALID:
|
||||
return {
|
||||
message: "Password does not meet security requirements",
|
||||
textColor: "text-orange-500",
|
||||
activeFragments: 2,
|
||||
};
|
||||
case E_PASSWORD_STRENGTH.STRENGTH_VALID:
|
||||
return {
|
||||
message: "Password meets security requirements",
|
||||
textColor: "text-success-primary",
|
||||
activeFragments: 3,
|
||||
};
|
||||
default:
|
||||
return {
|
||||
message: "Please enter your password",
|
||||
textColor: "text-primary",
|
||||
activeFragments: 0,
|
||||
};
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Get fragment color based on position and active state
|
||||
*/
|
||||
export const getFragmentColor = (fragmentIndex: number, activeFragments: number): string => {
|
||||
if (fragmentIndex >= activeFragments) {
|
||||
return "bg-layer-1";
|
||||
}
|
||||
|
||||
switch (activeFragments) {
|
||||
case 1:
|
||||
return "bg-danger-primary";
|
||||
case 2:
|
||||
return "bg-orange-500";
|
||||
case 3:
|
||||
return "bg-success-primary";
|
||||
default:
|
||||
return "bg-layer-1";
|
||||
}
|
||||
};
|
||||
@@ -0,0 +1,9 @@
|
||||
/**
|
||||
* Copyright (c) 2023-present Plane Software, Inc. and contributors
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
* See the LICENSE file for details.
|
||||
*/
|
||||
|
||||
export * from "./indicator";
|
||||
export * from "./helper";
|
||||
export * from "./password-input";
|
||||
@@ -0,0 +1,81 @@
|
||||
/**
|
||||
* Copyright (c) 2023-present Plane Software, Inc. and contributors
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
* See the LICENSE file for details.
|
||||
*/
|
||||
|
||||
import { CircleCheck } from "lucide-react";
|
||||
import React from "react";
|
||||
import { E_PASSWORD_STRENGTH } from "@plane/constants";
|
||||
import { cn, getPasswordStrength, getPasswordCriteria } from "@plane/utils";
|
||||
import { getStrengthInfo, getFragmentColor } from "./helper";
|
||||
|
||||
export interface PasswordStrengthIndicatorProps {
|
||||
password: string;
|
||||
showCriteria?: boolean;
|
||||
isFocused?: boolean;
|
||||
}
|
||||
|
||||
export function PasswordStrengthIndicator({
|
||||
password,
|
||||
showCriteria = true,
|
||||
isFocused = false,
|
||||
}: PasswordStrengthIndicatorProps) {
|
||||
const strength = getPasswordStrength(password);
|
||||
const criteria = getPasswordCriteria(password);
|
||||
const strengthInfo = getStrengthInfo(strength);
|
||||
|
||||
const isPasswordMeterVisible = isFocused ? true : strength === E_PASSWORD_STRENGTH.STRENGTH_VALID ? false : true;
|
||||
|
||||
if ((!password && !showCriteria) || !isPasswordMeterVisible) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<div className={cn("space-y-3")}>
|
||||
{/* Strength Indicator */}
|
||||
<div className="space-y-2">
|
||||
<div className="flex w-full gap-1 transition-all duration-300 ease-linear">
|
||||
{[0, 1, 2].map((fragmentIndex) => (
|
||||
<div
|
||||
key={fragmentIndex}
|
||||
className={cn(
|
||||
"h-1 flex-1 rounded-xs transition-all duration-300 ease-in-out",
|
||||
getFragmentColor(fragmentIndex, strengthInfo.activeFragments)
|
||||
)}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
|
||||
{/* Strength Message */}
|
||||
{password && <p className={cn("!text-13 font-medium", strengthInfo.textColor)}>{strengthInfo.message}</p>}
|
||||
</div>
|
||||
|
||||
{/* Criteria list */}
|
||||
{showCriteria && (
|
||||
<div className="flex flex-wrap gap-2">
|
||||
{criteria.map((criterion) => (
|
||||
<div key={criterion.key} className="flex items-center gap-1.5">
|
||||
<div className="flex items-center justify-center p-0.5">
|
||||
<CircleCheck
|
||||
className={cn("h-3 w-3 flex-shrink-0", {
|
||||
"text-success-primary": criterion.isValid,
|
||||
"text-primary": !criterion.isValid,
|
||||
})}
|
||||
/>
|
||||
</div>
|
||||
<span
|
||||
className={cn("!text-11", {
|
||||
"text-success-primary": criterion.isValid,
|
||||
"text-primary": !criterion.isValid,
|
||||
})}
|
||||
>
|
||||
{criterion.label}
|
||||
</span>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,78 @@
|
||||
/**
|
||||
* Copyright (c) 2023-present Plane Software, Inc. and contributors
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
* See the LICENSE file for details.
|
||||
*/
|
||||
|
||||
import { Eye, EyeClosed } from "lucide-react";
|
||||
import { useState } from "react";
|
||||
import { Tooltip } from "@plane/propel/tooltip";
|
||||
import { cn } from "@plane/utils";
|
||||
|
||||
type TPasswordInputProps = {
|
||||
id: string;
|
||||
value: string;
|
||||
onChange: (value: string) => void;
|
||||
placeholder?: string;
|
||||
className?: string;
|
||||
showToggle?: boolean;
|
||||
error?: boolean;
|
||||
autoComplete?: React.HTMLInputAutoCompleteAttribute;
|
||||
};
|
||||
|
||||
export function PasswordInput({
|
||||
id,
|
||||
value,
|
||||
onChange,
|
||||
placeholder = "Enter your password",
|
||||
className,
|
||||
showToggle = true,
|
||||
error = false,
|
||||
autoComplete = "off",
|
||||
}: TPasswordInputProps) {
|
||||
const [showPassword, setShowPassword] = useState(false);
|
||||
return (
|
||||
<div className="relative">
|
||||
<input
|
||||
id={id}
|
||||
type={showPassword ? "text" : "password"}
|
||||
value={value}
|
||||
onChange={(e) => onChange(e.target.value)}
|
||||
className={cn(
|
||||
"w-full rounded-md border bg-surface-1 px-3 py-2 pr-10 text-secondary transition-all duration-200 placeholder:text-placeholder focus:border-transparent focus:ring-2 focus:ring-accent-strong focus:outline-none",
|
||||
{
|
||||
"border-strong": !error,
|
||||
"border-danger-strong": error,
|
||||
},
|
||||
className
|
||||
)}
|
||||
placeholder={placeholder}
|
||||
autoComplete={autoComplete}
|
||||
/>
|
||||
{showToggle && (
|
||||
<Tooltip tooltipContent={showPassword ? "Hide password" : "Show password"} position="top">
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => setShowPassword(!showPassword)}
|
||||
className="absolute inset-y-0 right-0 flex items-center pr-3 text-secondary transition-colors duration-200 hover:text-primary"
|
||||
>
|
||||
<div className="relative h-4 w-4">
|
||||
<Eye
|
||||
className={cn(
|
||||
"absolute inset-0 h-4 w-4 transition-all duration-300 ease-in-out",
|
||||
showPassword ? "scale-75 rotate-12 opacity-0" : "scale-100 rotate-0 opacity-100"
|
||||
)}
|
||||
/>
|
||||
<EyeClosed
|
||||
className={cn(
|
||||
"absolute inset-0 h-4 w-4 transition-all duration-300 ease-in-out",
|
||||
showPassword ? "scale-100 rotate-0 opacity-100" : "scale-75 -rotate-12 opacity-0"
|
||||
)}
|
||||
/>
|
||||
</div>
|
||||
</button>
|
||||
</Tooltip>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,68 @@
|
||||
/**
|
||||
* Copyright (c) 2023-present Plane Software, Inc. and contributors
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
* See the LICENSE file for details.
|
||||
*/
|
||||
|
||||
import React from "react";
|
||||
import { cn } from "@plane/utils";
|
||||
|
||||
// Reusable Label Component
|
||||
interface LabelProps {
|
||||
htmlFor: string;
|
||||
children: React.ReactNode;
|
||||
className?: string;
|
||||
}
|
||||
|
||||
export function Label({ htmlFor, children, className }: LabelProps) {
|
||||
return (
|
||||
<label htmlFor={htmlFor} className={cn("block text-13 font-medium text-primary", className)}>
|
||||
{children}
|
||||
</label>
|
||||
);
|
||||
}
|
||||
|
||||
// Reusable Form Field Component
|
||||
interface FormFieldProps {
|
||||
label: string;
|
||||
htmlFor: string;
|
||||
children: React.ReactNode;
|
||||
className?: string;
|
||||
optional?: boolean;
|
||||
}
|
||||
|
||||
export function FormField({ label, htmlFor, children, className, optional = false }: FormFieldProps) {
|
||||
return (
|
||||
<div className={cn("flex flex-col gap-1.5", className)}>
|
||||
<Label htmlFor={htmlFor}>
|
||||
{label}
|
||||
{optional && <span className="text-13 text-placeholder"> (optional)</span>}
|
||||
</Label>
|
||||
{children}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
// Reusable Validation Message Component
|
||||
interface ValidationMessageProps {
|
||||
type: "error" | "success";
|
||||
message: string;
|
||||
className?: string;
|
||||
}
|
||||
|
||||
export function ValidationMessage({ type, message, className }: ValidationMessageProps) {
|
||||
return (
|
||||
<p
|
||||
className={cn(
|
||||
"text-13",
|
||||
{
|
||||
"text-danger-primary": type === "error",
|
||||
"text-success-primary": type === "success",
|
||||
},
|
||||
className
|
||||
)}
|
||||
>
|
||||
{message}
|
||||
</p>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,67 @@
|
||||
/**
|
||||
* Copyright (c) 2023-present Plane Software, Inc. and contributors
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
* See the LICENSE file for details.
|
||||
*/
|
||||
|
||||
import React, { useRef } from "react";
|
||||
// helpers
|
||||
import { useAutoResizeTextArea } from "../hooks/use-auto-resize-textarea";
|
||||
import { cn } from "../utils";
|
||||
// hooks
|
||||
|
||||
export interface TextAreaProps extends React.TextareaHTMLAttributes<HTMLTextAreaElement> {
|
||||
mode?: "primary" | "transparent" | "true-transparent";
|
||||
textAreaSize?: "xs" | "sm" | "md";
|
||||
hasError?: boolean;
|
||||
className?: string;
|
||||
}
|
||||
|
||||
const TextArea = React.forwardRef(function TextArea(
|
||||
props: TextAreaProps,
|
||||
ref: React.ForwardedRef<HTMLTextAreaElement>
|
||||
) {
|
||||
const {
|
||||
id,
|
||||
name,
|
||||
value = "",
|
||||
mode = "primary",
|
||||
textAreaSize = "sm",
|
||||
hasError = false,
|
||||
className = "",
|
||||
...rest
|
||||
} = props;
|
||||
// refs
|
||||
const textAreaRef = useRef<any>(ref);
|
||||
// auto re-size
|
||||
useAutoResizeTextArea(textAreaRef, value);
|
||||
|
||||
return (
|
||||
<textarea
|
||||
id={id}
|
||||
name={name}
|
||||
ref={textAreaRef}
|
||||
value={value}
|
||||
className={cn(
|
||||
"no-scrollbar w-full bg-layer-2 placeholder-(--text-color-placeholder) outline-none",
|
||||
{
|
||||
"rounded-md border-[0.5px] border-subtle-1": mode === "primary",
|
||||
"focus:ring-theme rounded-sm border-none bg-transparent ring-0 transition-all focus:ring-1":
|
||||
mode === "transparent",
|
||||
"rounded-sm border-none bg-transparent ring-0": mode === "true-transparent",
|
||||
"px-1.5 py-1": textAreaSize === "xs",
|
||||
"px-3 py-2": textAreaSize === "sm",
|
||||
"p-3": textAreaSize === "md",
|
||||
"border-danger-strong": hasError,
|
||||
"bg-danger-subtle": hasError && mode === "primary",
|
||||
},
|
||||
className
|
||||
)}
|
||||
{...rest}
|
||||
/>
|
||||
);
|
||||
});
|
||||
|
||||
TextArea.displayName = "TextArea";
|
||||
|
||||
export { TextArea };
|
||||
Reference in New Issue
Block a user