Add monorepo support to project (#682)
* feat: add monorepo support * fix: correct relatedProjects structure in vercel.json * update github actions with monorepo support * removed legacy dep * moved jsr config into web package
This commit is contained in:
@@ -0,0 +1,219 @@
|
||||
import * as React from "react";
|
||||
import { cn } from "@core/utils/cn.ts";
|
||||
import { cva, type VariantProps } from "class-variance-authority";
|
||||
import { Check, Copy, Eye, EyeOff, type LucideIcon, X } from "lucide-react";
|
||||
import { useCopyToClipboard } from "@core/hooks/useCopyToClipboard.ts";
|
||||
import { usePasswordVisibilityToggle } from "@core/hooks/usePasswordVisibilityToggle.ts";
|
||||
import { useTranslation } from "react-i18next";
|
||||
|
||||
const cnInvalidBase = "border-2 border-red-500 dark:border-red-500";
|
||||
const cnDirtyBase = "border-2 border-sky-500 dark:border-sky-500";
|
||||
|
||||
const inputVariants = cva(
|
||||
"flex h-10 w-full rounded-md border border-slate-300 bg-transparent py-2 px-3 text-sm placeholder:text-slate-400 focus:outline-none focus:ring-1 focus:ring-slate-400 disabled:cursor-not-allowed disabled:opacity-50 dark:border-slate-500 dark:bg-transparet dark:text-slate-100 dark:focus:ring-slate-400 dark:focus:ring-offset-slate-600",
|
||||
{
|
||||
variants: {
|
||||
variant: {
|
||||
default: "border-slate-300 dark:border-slate-500",
|
||||
invalid: `${cnInvalidBase} focus:ring-red-500 dark:focus:ring-red-500`,
|
||||
dirty: `${cnDirtyBase} focus:ring-sky-500 dark:focus:ring-sky-500`,
|
||||
},
|
||||
},
|
||||
defaultVariants: {
|
||||
variant: "default",
|
||||
},
|
||||
},
|
||||
);
|
||||
|
||||
type InputActionType = {
|
||||
id: string;
|
||||
icon: LucideIcon;
|
||||
onClick: (e: React.MouseEvent<HTMLButtonElement>) => void;
|
||||
ariaLabel: string;
|
||||
tooltip?: string;
|
||||
condition?: boolean;
|
||||
};
|
||||
|
||||
export interface InputProps
|
||||
extends
|
||||
Omit<React.InputHTMLAttributes<HTMLInputElement>, "prefix" | "suffix">,
|
||||
VariantProps<typeof inputVariants> {
|
||||
prefix?: React.ReactNode;
|
||||
suffix?: React.ReactNode;
|
||||
showPasswordToggle?: boolean;
|
||||
showCopyButton?: boolean;
|
||||
showClearButton?: boolean;
|
||||
containerClassName?: string;
|
||||
}
|
||||
|
||||
const Input = React.forwardRef<HTMLInputElement, InputProps>(
|
||||
(
|
||||
{
|
||||
className,
|
||||
containerClassName,
|
||||
variant,
|
||||
disabled,
|
||||
type = "text",
|
||||
prefix,
|
||||
suffix,
|
||||
showPasswordToggle,
|
||||
showCopyButton,
|
||||
showClearButton,
|
||||
value,
|
||||
onChange,
|
||||
...props
|
||||
},
|
||||
ref,
|
||||
) => {
|
||||
const { isVisible, toggleVisibility } = usePasswordVisibilityToggle();
|
||||
const { copy, isCopied } = useCopyToClipboard({ timeout: 1500 });
|
||||
const { t } = useTranslation("ui");
|
||||
|
||||
const potentialActions: InputActionType[] = [
|
||||
{
|
||||
id: "clear-input",
|
||||
icon: X,
|
||||
onClick: (e) => {
|
||||
e.stopPropagation();
|
||||
if (onChange) {
|
||||
const event = {
|
||||
target: { value: "" },
|
||||
currentTarget: { value: "" },
|
||||
} as React.ChangeEvent<HTMLInputElement>;
|
||||
onChange(event);
|
||||
}
|
||||
if (ref && typeof ref !== "function" && ref.current) {
|
||||
ref.current.focus();
|
||||
}
|
||||
},
|
||||
ariaLabel: t("clearInput.label"),
|
||||
tooltip: t("clearInput.label"),
|
||||
condition: !!showClearButton && !!value,
|
||||
},
|
||||
{
|
||||
id: "toggle-visibility",
|
||||
icon: isVisible ? EyeOff : Eye,
|
||||
onClick: (e) => {
|
||||
e.stopPropagation();
|
||||
toggleVisibility();
|
||||
},
|
||||
ariaLabel: isVisible
|
||||
? t("notifications.hidePassword.label")
|
||||
: t("notifications.showPassword.label"),
|
||||
tooltip: isVisible
|
||||
? t("notifications.hidePassword.label")
|
||||
: t("notifications.showPassword.label"),
|
||||
condition: !!showPasswordToggle && type === "password",
|
||||
},
|
||||
{
|
||||
id: "copy-value",
|
||||
icon: isCopied ? Check : Copy,
|
||||
onClick: (e) => {
|
||||
e.stopPropagation();
|
||||
if (value !== undefined && value !== null) {
|
||||
copy(String(value));
|
||||
}
|
||||
},
|
||||
ariaLabel: isCopied
|
||||
? t("notifications.copied.label")
|
||||
: t("notifications.copyToClipboard.label"),
|
||||
tooltip: isCopied
|
||||
? t("notifications.copied.label")
|
||||
: t("notifications.copyToClipboard.label"),
|
||||
condition: !!showCopyButton,
|
||||
},
|
||||
];
|
||||
|
||||
const actions = potentialActions.filter((action) => action.condition);
|
||||
|
||||
const inputType = showPasswordToggle
|
||||
? (isVisible ? "text" : "password")
|
||||
: type;
|
||||
|
||||
const hasPrefix = !!prefix;
|
||||
const hasSuffix = !!suffix;
|
||||
const hasActions = actions.length > 0;
|
||||
|
||||
const inputClassName = cn(
|
||||
inputVariants({ variant }),
|
||||
hasActions && !hasSuffix && "pr-10",
|
||||
hasPrefix && "rounded-l-none",
|
||||
className,
|
||||
);
|
||||
|
||||
const extrasClassName = cn([
|
||||
variant === "invalid" && `${cnInvalidBase} border-l-0`,
|
||||
variant === "dirty" && `${cnDirtyBase} border-l-0`,
|
||||
]);
|
||||
|
||||
return (
|
||||
<div
|
||||
className={cn("relative flex w-full items-stretch", containerClassName)}
|
||||
>
|
||||
{prefix && (
|
||||
<span className="inline-flex items-center rounded-l-md border border-r-0 border-slate-300 bg-slate-100/80 px-3 text-sm text-slate-600 dark:border-slate-700 dark:bg-slate-200 dark:text-slate-700">
|
||||
{prefix}
|
||||
</span>
|
||||
)}
|
||||
|
||||
<input
|
||||
type={inputType === "password" && isVisible ? "text" : inputType}
|
||||
className={inputClassName}
|
||||
ref={ref}
|
||||
value={value}
|
||||
onChange={onChange}
|
||||
disabled={disabled}
|
||||
{...props}
|
||||
/>
|
||||
|
||||
<div className="absolute right-0 top-0 flex h-full items-stretch">
|
||||
{suffix && (
|
||||
<span
|
||||
className={cn(
|
||||
"inline-flex items-center border border-l-0 border-slate-300 bg-slate-100/80 px-3 text-sm text-slate-600 dark:border-slate-700 dark:bg-slate-700 dark:text-slate-300",
|
||||
extrasClassName,
|
||||
!hasActions && "rounded-r-md",
|
||||
)}
|
||||
>
|
||||
{suffix}
|
||||
</span>
|
||||
)}
|
||||
|
||||
{hasActions && (
|
||||
<div
|
||||
className={cn(
|
||||
"flex items-center divide-x divide-slate-300 border border-l-0 border-slate-300 dark:divide-slate-700 dark:border-slate-500",
|
||||
extrasClassName,
|
||||
disabled &&
|
||||
"border-slate-200 dark:border-slate-700 divide-slate-200",
|
||||
!hasSuffix && "rounded-r-md",
|
||||
"bg-white dark:bg-slate-800",
|
||||
)}
|
||||
>
|
||||
{actions.map((action) => (
|
||||
<button
|
||||
key={action.id}
|
||||
type="button"
|
||||
className={cn(
|
||||
"inline-flex h-full items-center justify-center px-2.5 text-slate-500 hover:bg-slate-100 hover:text-slate-700 focus:outline-none focus:ring-1 focus:ring-slate-400 focus:ring-offset-0 dark:text-slate-400 dark:hover:bg-slate-700 dark:hover:text-slate-200 dark:focus:ring-slate-500 last:hover:rounded-r-md last:dark:hover:rounded-r-md",
|
||||
disabled && "text-slate-300 dark:text-slate-600",
|
||||
action.id === "copy-value" && isCopied &&
|
||||
"text-green-600 dark:text-green-500",
|
||||
)}
|
||||
onClick={action.onClick}
|
||||
aria-label={action.ariaLabel}
|
||||
title={action.tooltip || action.ariaLabel}
|
||||
>
|
||||
<action.icon size={18} aria-hidden="true" />
|
||||
</button>
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
},
|
||||
);
|
||||
Input.displayName = "Input";
|
||||
|
||||
export { Input, inputVariants };
|
||||
Reference in New Issue
Block a user