* feat: add mobile navigation with responsive layout and theme controls * feat: prevent workspace owners from removing themselves and update workspace dropdown styling * feat: add useClickOutside hook and event listener utilities for improved UI interactions * chore: improve mobile UI styling * refactor: improve UI layout and buttons in boards/members views feat: adjust /members layout * fix: standardize layout containers and improve mobile navigation styling * feat: update card view layout * feat: add responsive layout to board view * feat: readjust boards page layout * feat: enhance table view on mobile for members page * feat: extend mobile support with sliding side panels * fix: prevent two panels from being open at the same time * feat: close panels when clicking outside * fix: adjust font-sizing for import source --------- Co-authored-by: Henry <henry_ball@hotmail.co.uk>
107 lines
3.5 KiB
TypeScript
107 lines
3.5 KiB
TypeScript
import React, { forwardRef, useState } from "react";
|
|
import ContentEditable from "react-contenteditable";
|
|
import { HiOutlineEye, HiOutlineEyeSlash } from "react-icons/hi2";
|
|
import { twMerge } from "tailwind-merge";
|
|
|
|
interface InputProps extends React.InputHTMLAttributes<HTMLInputElement> {
|
|
contentEditable?: boolean;
|
|
prefix?: string;
|
|
iconRight?: React.ReactNode;
|
|
minHeight?: number;
|
|
value?: string;
|
|
errorMessage?: string;
|
|
className?: string;
|
|
onChange?: (
|
|
e: React.ChangeEvent<HTMLInputElement | HTMLTextAreaElement>,
|
|
) => void;
|
|
onKeyDown?: (e: React.KeyboardEvent) => void;
|
|
type?: string;
|
|
}
|
|
|
|
const Input = forwardRef<HTMLInputElement, InputProps>(
|
|
(
|
|
{
|
|
contentEditable,
|
|
errorMessage,
|
|
prefix,
|
|
value,
|
|
onChange,
|
|
onKeyDown,
|
|
iconRight,
|
|
className,
|
|
type = "text",
|
|
...props
|
|
},
|
|
ref,
|
|
) => {
|
|
const [showPassword, setShowPassword] = useState(false);
|
|
|
|
if (contentEditable) {
|
|
return (
|
|
<ContentEditable
|
|
placeholder={props.placeholder}
|
|
html={value ?? ""}
|
|
onChange={onChange}
|
|
onKeyDown={onKeyDown}
|
|
className={twMerge(
|
|
"block min-h-[70px] w-full cursor-text overflow-y-auto rounded-md border-0 bg-dark-300 bg-white/5 px-3 py-1.5 text-light-900 shadow-sm ring-1 ring-inset ring-light-600 focus:ring-2 focus:ring-inset focus:ring-light-600 focus-visible:outline-none dark:text-dark-1000 dark:ring-dark-700 dark:focus:ring-dark-700 sm:text-sm sm:leading-6",
|
|
className && className,
|
|
)}
|
|
/>
|
|
);
|
|
}
|
|
|
|
return (
|
|
<div className="flex w-full flex-col gap-1">
|
|
<div className="relative flex">
|
|
{prefix && (
|
|
<div className="flex shrink-0 items-center rounded-l-md border border-r-0 border-light-600 px-3 text-sm dark:border-dark-700 dark:text-dark-1000 sm:text-sm/6">
|
|
{prefix}
|
|
</div>
|
|
)}
|
|
<input
|
|
ref={ref}
|
|
value={value}
|
|
onChange={onChange}
|
|
type={type === "password" && showPassword ? "text" : type}
|
|
className={twMerge(
|
|
"block w-full rounded-md border-0 bg-dark-300 bg-white/5 py-1.5 text-sm shadow-sm ring-1 ring-inset ring-light-600 placeholder:text-dark-800 focus:ring-2 focus:ring-inset focus:ring-light-700 dark:text-dark-1000 dark:ring-dark-700 dark:focus:ring-dark-700 sm:leading-6",
|
|
prefix && "rounded-l-none",
|
|
type === "password" && "pr-8",
|
|
className && className,
|
|
)}
|
|
onKeyDown={onKeyDown}
|
|
{...props}
|
|
/>
|
|
{type === "password" && (
|
|
<button
|
|
type="button"
|
|
className="absolute right-3 top-1/2 -translate-y-1/2 text-xs text-light-900 dark:text-dark-900"
|
|
tabIndex={-1}
|
|
onClick={() => setShowPassword((v) => !v)}
|
|
>
|
|
{showPassword ? (
|
|
<HiOutlineEyeSlash className="h-4 w-4" />
|
|
) : (
|
|
<HiOutlineEye className="h-4 w-4" />
|
|
)}
|
|
</button>
|
|
)}
|
|
{iconRight && type !== "password" && (
|
|
<div className="absolute right-3 top-1/2 -translate-y-1/2">
|
|
{iconRight}
|
|
</div>
|
|
)}
|
|
</div>
|
|
{errorMessage && (
|
|
<div className="text-xs text-red-500">{errorMessage}</div>
|
|
)}
|
|
</div>
|
|
);
|
|
},
|
|
);
|
|
|
|
Input.displayName = "Input";
|
|
|
|
export default Input;
|