build: merge with latest changes update dockerfile to work with pnpm

This commit is contained in:
pablohashescobar 2022-12-02 00:28:31 +05:30
parent 45fe4b89db
commit 7ef9ea07f0
161 changed files with 67 additions and 48 deletions

View file

@ -0,0 +1,59 @@
import { ArrowLeftIcon, HomeIcon } from "@heroicons/react/24/outline";
import Link from "next/link";
import { useRouter } from "next/router";
type BreadcrumbsProps = {
children: any;
};
const Breadcrumbs: React.FC<BreadcrumbsProps> = ({ children }: BreadcrumbsProps) => {
const router = useRouter();
return (
<>
<div className="flex gap-3 ml-1">
<div
className="bg-indigo-50 hover:bg-indigo-100 duration-300 px-3 py-1 rounded-tl-lg rounded-tr-md rounded-br-lg rounded-bl-md skew-x-[-20deg] text-sm text-center grid place-items-center cursor-pointer"
onClick={() => router.back()}
>
<p className="skew-x-[20deg]">
<ArrowLeftIcon className="h-3 w-3" />
</p>
</div>
{children}
</div>
</>
);
};
type BreadcrumbItemProps = {
title: string;
link?: string;
icon?: any;
};
const BreadcrumbItem: React.FC<BreadcrumbItemProps> = ({ title, link, icon }) => {
return (
<>
{link ? (
<Link href={link}>
<a className="bg-indigo-50 hover:bg-indigo-100 duration-300 px-4 py-1 rounded-tl-lg rounded-tr-md rounded-br-lg rounded-bl-md skew-x-[-20deg] text-sm text-center">
<p className={`skew-x-[20deg] ${icon ? "flex items-center gap-2" : ""}`}>
{icon ?? null}
{title}
</p>
</a>
</Link>
) : (
<div className="bg-indigo-50 px-4 py-1 rounded-tl-lg rounded-tr-md rounded-br-lg rounded-bl-md skew-x-[-20deg] text-sm text-center">
<p className={`skew-x-[20deg] ${icon ? "flex items-center gap-2" : ""}`}>
{icon}
{title}
</p>
</div>
)}
</>
);
};
export { Breadcrumbs, BreadcrumbItem };

View file

@ -0,0 +1,64 @@
import React from "react";
type Props = {
onClick?: () => void;
children: React.ReactNode;
type?: "button" | "submit" | "reset";
className?: string;
theme?: "primary" | "secondary" | "danger";
size?: "sm" | "rg" | "md" | "lg";
disabled?: boolean;
};
// commons
import { classNames } from "constants/common";
const Button = React.forwardRef<HTMLButtonElement, Props>(
(
{
children,
onClick,
type = "button",
size = "sm",
className,
theme = "primary",
disabled = false,
},
ref
) => {
return (
<button
ref={ref}
onClick={onClick}
type={type}
disabled={disabled}
className={classNames(
"inline-flex items-center rounded justify-center font-medium",
theme === "primary"
? `${
disabled ? "opacity-70" : "bg-theme hover:bg-indigo-700"
} text-white shadow-sm focus:outline-none focus:ring-2 focus:ring-indigo-500 focus:ring-offset-2 border border-transparent`
: theme === "secondary"
? "border border-gray-300 bg-white"
: `${
disabled ? "opacity-70" : "bg-red-600 hover:bg-red-700"
} text-white shadow-sm focus:outline-none focus:ring-2 focus:ring-red-500 focus:ring-offset-2 border border-transparent`,
size === "sm"
? "p-2 text-xs"
: size === "md"
? "px-3 py-2 text-base"
: size === "lg"
? "px-4 py-2 text-base"
: "px-2.5 py-2 text-sm",
className || ""
)}
>
{children}
</button>
);
}
);
Button.displayName = "Button";
export default Button;

View file

@ -0,0 +1,163 @@
import React from "react";
// headless ui
import { Listbox, Transition } from "@headlessui/react";
// icons
import { CheckIcon } from "@heroicons/react/20/solid";
import { Props } from "./types";
const CustomListbox: React.FC<Props> = ({
title,
options,
value,
onChange,
multiple,
icon,
width,
footerOption,
optionsFontsize,
className,
label,
}) => {
return (
<Listbox value={value} onChange={onChange} multiple={multiple}>
{({ open }) => (
<>
{label && (
<Listbox.Label>
<div className="text-gray-500 mb-2">{label}</div>
</Listbox.Label>
)}
<div className="relative">
<Listbox.Button
className={`flex items-center gap-1 hover:bg-gray-100 relative border rounded-md shadow-sm cursor-pointer focus:outline-none focus:ring-1 focus:ring-indigo-500 focus:border-indigo-500 sm:text-sm duration-300 ${
width === "sm"
? "w-32"
: width === "md"
? "w-48"
: width === "lg"
? "w-64"
: width === "xl"
? "w-80"
: width === "2xl"
? "w-96"
: width === "w-full"
? "w-full"
: ""
}
${className || "px-2 py-1"}`}
>
{icon ?? null}
<span className="block truncate">
{Array.isArray(value)
? value.map((v) => options?.find((o) => o.value === v)?.display).join(", ") ||
`${title}`
: options?.find((o) => o.value === value)?.display || `${title}`}
</span>
</Listbox.Button>
<Transition
show={open}
as={React.Fragment}
leave="transition ease-in duration-100"
leaveFrom="opacity-100"
leaveTo="opacity-0"
>
<Listbox.Options
className={`absolute mt-1 bg-white shadow-lg max-h-32 overflow-auto ${
width === "sm"
? "w-32"
: width === "md"
? "w-48"
: width === "lg"
? "w-64"
: width === "xl"
? "w-80"
: width === "2xl"
? "w-96"
: width === "w-full"
? "w-full"
: ""
} ${
optionsFontsize === "sm"
? "text-xs"
: optionsFontsize === "md"
? "text-base"
: optionsFontsize === "lg"
? "text-lg"
: optionsFontsize === "xl"
? "text-xl"
: optionsFontsize === "2xl"
? "text-2xl"
: ""
} ${
className || ""
} rounded-md py-1 ring-1 ring-black ring-opacity-5 focus:outline-none z-10`}
>
<div className="p-1">
{options ? (
options.length > 0 ? (
options.map((option) => (
<Listbox.Option
key={option.value}
className={({ active }) =>
`${
active ? "text-white bg-theme" : "text-gray-900"
} cursor-pointer select-none relative p-2 rounded-md`
}
value={option.value}
>
{({ selected, active }) => (
<>
<span
className={`${
selected ||
(Array.isArray(value)
? value.includes(option.value)
: value === option.value)
? "font-semibold"
: "font-normal"
} block truncate`}
>
{option.display}
</span>
{selected ||
(Array.isArray(value)
? value.includes(option.value)
: value === option.value) ? (
<span
className={`absolute inset-y-0 right-0 flex items-center pr-4 ${
active ||
(Array.isArray(value)
? value.includes(option.value)
: value === option.value)
? "text-white"
: "text-indigo-600"
}`}
>
<CheckIcon className="h-5 w-5" aria-hidden="true" />
</span>
) : null}
</>
)}
</Listbox.Option>
))
) : (
<p className="text-sm text-gray-500 text-center">No options</p>
)
) : (
<p className="text-sm text-gray-500 text-center">Loading...</p>
)}
</div>
{footerOption ?? null}
</Listbox.Options>
</Transition>
</div>
</>
)}
</Listbox>
);
};
export default CustomListbox;

13
apps/app/ui/CustomListbox/types.d.ts vendored Normal file
View file

@ -0,0 +1,13 @@
export type Props = {
title: string;
label?: string;
options?: Array<{ display: string; value: any }>;
icon?: JSX.Element;
value: any;
onChange: (value: any) => void;
multiple?: boolean;
width?: "sm" | "md" | "lg" | "xl" | "2xl" | "w-full";
optionsFontsize?: "sm" | "md" | "lg" | "xl" | "2xl";
className?: string;
footerOption?: JSX.Element;
};

View file

@ -0,0 +1,102 @@
// next
import Link from "next/link";
// react
import React from "react";
// icons
import { ChevronRightIcon } from "@heroicons/react/24/outline";
type EmptySpaceProps = {
title: string;
description: string;
children: any;
Icon?: (
props: React.SVGProps<SVGSVGElement> & {
title?: string | undefined;
titleId?: string | undefined;
}
) => JSX.Element;
link?: { text: string; href: string };
};
const EmptySpace: React.FC<EmptySpaceProps> = ({ title, description, children, Icon, link }) => {
return (
<>
<div className="max-w-lg">
{Icon ? (
<div className="mb-4">
<Icon className="h-14 w-14 text-gray-400" />
</div>
) : null}
<h2 className="text-lg font-medium text-gray-900">{title}</h2>
<p className="mt-1 text-sm text-gray-500">{description}</p>
<ul role="list" className="mt-6 divide-y divide-gray-200 border-t border-b border-gray-200">
{children}
</ul>
{link ? (
<div className="mt-6 flex">
<Link href={link.href}>
<a className="text-sm font-medium text-indigo-600 hover:text-indigo-500">
{link.text}
<span aria-hidden="true"> &rarr;</span>
</a>
</Link>
</div>
) : null}
</div>
</>
);
};
type EmptySpaceItemProps = {
title: string;
description?: React.ReactNode | string;
bgColor?: string;
Icon: (
props: React.SVGProps<SVGSVGElement> & {
title?: string | undefined;
titleId?: string | undefined;
}
) => JSX.Element;
action: () => void;
};
const EmptySpaceItem: React.FC<EmptySpaceItemProps> = ({
title,
description,
bgColor = "blue",
Icon,
action,
}) => {
return (
<>
<li className="cursor-pointer" onClick={action}>
<div
className={`group relative flex ${
description ? "items-start" : "items-center"
} space-x-3 py-4`}
>
<div className="flex-shrink-0">
<span
className={`inline-flex items-center justify-center h-10 w-10 rounded-lg bg-theme`}
>
<Icon className="h-6 w-6 text-white" aria-hidden="true" />
</span>
</div>
<div className="min-w-0 flex-1">
<div className="text-sm font-medium text-gray-900">{title}</div>
{description ? <p className="text-sm text-gray-500">{description}</p> : null}
</div>
<div className="flex-shrink-0 self-center">
<ChevronRightIcon
className="h-5 w-5 text-gray-400 group-hover:text-gray-500"
aria-hidden="true"
/>
</div>
</div>
</li>
</>
);
};
export { EmptySpace, EmptySpaceItem };

View file

@ -0,0 +1,40 @@
type HeaderButtonProps = {
Icon: (
props: React.SVGProps<SVGSVGElement> & {
title?: string | undefined;
titleId?: string | undefined;
}
) => JSX.Element;
label: string;
disabled?: boolean;
onClick: () => void;
className?: string;
position?: "normal" | "reverse";
};
const HeaderButton = ({
Icon,
label,
disabled = false,
onClick,
className = "",
position = "normal",
}: HeaderButtonProps) => {
return (
<>
<button
type="button"
className={`bg-theme text-white border border-indigo-600 text-xs flex items-center gap-x-1 p-2 rounded-md font-medium whitespace-nowrap outline-none ${
position === "reverse" && "flex-row-reverse"
} ${className}`}
disabled={disabled}
onClick={onClick}
>
<Icon className="h-4 w-4" />
{label}
</button>
</>
);
};
export default HeaderButton;

View file

@ -0,0 +1,54 @@
import React from "react";
// common
import { classNames } from "constants/common";
// types
import { Props } from "./types";
const Input: React.FC<Props> = ({
label,
value,
name,
register,
validations,
error,
mode = "primary",
onChange,
className,
type,
id,
...rest
}) => {
return (
<>
{label && (
<label htmlFor={id} className="text-gray-500 mb-2">
{label}
</label>
)}
<input
type={type}
id={id}
value={value}
{...(register && register(name, validations))}
onChange={(e) => {
register && register(name).onChange(e);
onChange && onChange(e);
}}
className={classNames(
"mt-1 block w-full px-3 py-2 text-base focus:outline-none sm:text-sm rounded-md bg-transparent",
mode === "primary" ? "border border-gray-300 rounded-md" : "",
mode === "transparent"
? "bg-transparent border-none transition-all ring-0 focus:ring-1 focus:ring-indigo-500 rounded"
: "",
error ? "border-red-500" : "",
error && mode === "primary" ? "bg-red-100" : "",
className ?? ""
)}
{...rest}
/>
{error?.message && <div className="text-red-500 text-sm">{error.message}</div>}
</>
);
};
export default Input;

13
apps/app/ui/Input/types.d.ts vendored Normal file
View file

@ -0,0 +1,13 @@
import React from "react";
import type { UseFormRegister, RegisterOptions, FieldError } from "react-hook-form";
export interface Props extends React.ComponentPropsWithoutRef<"input"> {
label?: string;
name: string;
value?: string | number | readonly string[];
mode?: "primary" | "transparent" | "secondary" | "disabled";
register?: UseFormRegister<any>;
validations?: RegisterOptions;
error?: FieldError;
className?: string;
}

118
apps/app/ui/Modal/index.tsx Normal file
View file

@ -0,0 +1,118 @@
import { Fragment, ReactNode } from "react";
// Headless ui imports
import { Dialog, Transition } from "@headlessui/react";
// Design components
import Button from "ui/Button";
// Icons
import { XMarkIcon } from "@heroicons/react/24/outline";
type ModalProps = {
isModal: boolean;
setModal: Function;
size?: "xs" | "rg" | "lg" | "xl";
position?: "top" | "center" | "bottom";
title: string;
children: ReactNode;
buttons?: ReactNode;
onClose?: Function;
closeButton?: string;
continueButton?: string;
};
const Modal = (props: ModalProps) => {
const closeModal = () => {
props.setModal(false);
props.onClose ? props.onClose() : () => {};
};
const width: string =
props.size === "xs"
? "w-4/12"
: props.size === "rg"
? "w-6/12"
: props.size === "lg"
? "w-9/12"
: props.size === "xl"
? "w-full"
: "w-auto";
const position: string =
props.position === "top"
? "content-start justify-items-center"
: props.position === "center"
? "place-items-center"
: props.position === "bottom"
? "content-end justify-items-center"
: "place-items-center";
return (
<>
<Transition appear show={props.isModal} as={Fragment}>
<Dialog as="div" className="relative z-10" onClose={closeModal}>
<Transition.Child
as={Fragment}
enter="ease-out duration-300"
enterFrom="opacity-0"
enterTo="opacity-100"
leave="ease-in duration-200"
leaveFrom="opacity-100"
leaveTo="opacity-0"
>
<div className="fixed inset-0 bg-black bg-opacity-25" />
</Transition.Child>
<div className="fixed inset-0">
<div className={`grid h-full ${position} p-4 text-center`}>
<Transition.Child
as={Fragment}
enter="ease-out duration-300"
enterFrom="opacity-0 scale-95"
enterTo="opacity-100 scale-100"
leave="ease-in duration-200"
leaveFrom="opacity-100 scale-100"
leaveTo="opacity-0 scale-95"
>
<Dialog.Panel
className={`transform rounded-2xl ${width} bg-white p-8 text-left max-h-full shadow-xl transition-all`}
>
<Dialog.Title
as="h3"
className="text-lg font-medium leading-6 text-gray-900 relative"
>
<div
className="absolute top-[-1rem] right-[-1rem] cursor-pointer"
onClick={closeModal}
>
<XMarkIcon className="h-4 w-4" />
</div>
<div>{props.title}</div>
</Dialog.Title>
<div className="mt-2">{props.children}</div>
<div className="mt-4">
<div className={`flex gap-2 justify-end`}>
<Button theme="secondary" onClick={closeModal}>
{props.closeButton}
</Button>
<Button onClick={closeModal}>
{props.continueButton}
</Button>
</div>
</div>
</Dialog.Panel>
</Transition.Child>
</div>
</div>
</Dialog>
</Transition>
</>
);
};
Modal.defaultProps = {
size: "rg",
position: "center",
closeButton: "Close",
continueButton: "Continue",
};
export default Modal;

View file

@ -0,0 +1,151 @@
import React, { useState } from "react";
// headless ui
import { Transition, Combobox } from "@headlessui/react";
// common
import { classNames } from "constants/common";
// types
import type { Props } from "./types";
const SearchListbox: React.FC<Props> = ({
title,
options,
onChange,
value,
multiple: canSelectMultiple,
icon,
width = "sm",
optionsFontsize,
buttonClassName,
optionsClassName,
}) => {
const [query, setQuery] = useState("");
const filteredOptions =
query === ""
? options
: options?.filter((option) => option.display.toLowerCase().includes(query.toLowerCase()));
const props: any = {
value,
onChange,
};
if (canSelectMultiple) {
props.value = props.value ?? [];
props.onChange = (value: string[]) => {
onChange(value);
};
props.multiple = true;
}
return (
<Combobox as="div" {...props} className="flex-shrink-0">
{({ open }: any) => (
<>
<Combobox.Label className="sr-only"> {title} </Combobox.Label>
<div className="relative">
<Combobox.Button
className={`flex items-center gap-1 hover:bg-gray-100 relative border rounded-md shadow-sm px-2 py-1 cursor-pointer focus:outline-none focus:ring-1 focus:ring-indigo-500 focus:border-indigo-500 sm:text-sm duration-300 ${
width === "sm"
? "w-32"
: width === "md"
? "w-48"
: width === "lg"
? "w-64"
: width === "xl"
? "w-80"
: width === "2xl"
? "w-96"
: ""
} ${buttonClassName || ""}`}
>
{icon ?? null}
<span
className={classNames(
value === null || value === undefined ? "" : "text-gray-900",
"hidden truncate sm:ml-2 sm:block"
)}
>
{Array.isArray(value)
? value
.map((v) => options?.find((option) => option.value === v)?.display)
.join(", ") || title
: options?.find((option) => option.value === value)?.display || title}
</span>
</Combobox.Button>
<Transition
show={open}
as={React.Fragment}
leave="transition ease-in duration-100"
leaveFrom="opacity-100"
leaveTo="opacity-0"
>
<Combobox.Options
className={`absolute mt-1 bg-white shadow-lg rounded-md py-1 ring-1 ring-black ring-opacity-5 focus:outline-none max-h-32 overflow-auto z-10 ${
width === "xs"
? "w-20"
: width === "sm"
? "w-32"
: width === "md"
? "w-48"
: width === "lg"
? "w-64"
: width === "xl"
? "w-80"
: width === "2xl"
? "w-96"
: ""
}} ${
optionsFontsize === "sm"
? "text-xs"
: optionsFontsize === "md"
? "text-base"
: optionsFontsize === "lg"
? "text-lg"
: optionsFontsize === "xl"
? "text-xl"
: optionsFontsize === "2xl"
? "text-2xl"
: ""
} ${optionsClassName || ""}`}
>
<Combobox.Input
className="w-full bg-transparent border-b p-2 mb-1 focus:outline-none sm:text-sm"
onChange={(event) => setQuery(event.target.value)}
placeholder="Search"
displayValue={(assigned: any) => assigned?.name}
/>
<div className="p-1">
{filteredOptions ? (
filteredOptions.length > 0 ? (
filteredOptions.map((option) => (
<Combobox.Option
key={option.value}
className={({ active }) =>
`${
active ? "text-white bg-theme" : "text-gray-900"
} cursor-pointer select-none truncate font-medium relative p-2 rounded-md`
}
value={option.value}
>
{option.element ?? option.display}
</Combobox.Option>
))
) : (
<p className="text-sm text-gray-500">No {title.toLowerCase()} found</p>
)
) : (
<p className="text-sm text-gray-500">Loading...</p>
)}
</div>
</Combobox.Options>
</Transition>
</div>
</>
)}
</Combobox>
);
};
export default SearchListbox;

14
apps/app/ui/SearchListbox/types.d.ts vendored Normal file
View file

@ -0,0 +1,14 @@
type Value = any;
export type Props = {
title: string;
multiple?: boolean;
options?: Array<{ display: string; element?: JSX.Element; value: Value }>;
onChange: (value: Value) => void;
value: Value;
icon?: JSX.Element;
buttonClassName?: string;
optionsClassName?: string;
width?: "xs" | "sm" | "md" | "lg" | "xl" | "2xl";
optionsFontsize?: "sm" | "md" | "lg" | "xl" | "2xl";
};

View file

@ -0,0 +1,43 @@
import React from "react";
// types
import { Props } from "./types";
const Select: React.FC<Props> = ({
id,
label,
value,
className,
name,
register,
disabled,
validations,
error,
options,
}) => {
return (
<>
{label && (
<label htmlFor={id} className="text-gray-500 mb-2">
{label}
</label>
)}
<select
id={id}
name={name}
value={value}
{...(register && register(name, validations))}
disabled={disabled}
className="mt-1 block w-full px-3 py-2 text-base border border-gray-300 focus:outline-none focus:ring-indigo-500 focus:border-indigo-500 sm:text-sm rounded-md bg-transparent"
>
{options.map((option, index) => (
<option value={option.value} key={index}>
{option.label}
</option>
))}
</select>
{error?.message && <div className="text-red-500 text-sm">{error.message}</div>}
</>
);
};
export default Select;

19
apps/app/ui/Select/types.d.ts vendored Normal file
View file

@ -0,0 +1,19 @@
import type {
UseFormRegister,
RegisterOptions,
FieldError,
} from "react-hook-form";
export type Props = {
label?: string;
id: string;
name: string;
value?: string | number | readonly string[];
className?: string;
register?: UseFormRegister<any>;
disabled?: boolean;
validations?: RegisterOptions;
error?: FieldError;
autoComplete?: "on" | "off";
options: { label: string; value: any }[];
};

View file

@ -0,0 +1,25 @@
const Spinner: React.FC = () => {
return (
<div role="status">
<svg
aria-hidden="true"
className="mr-2 w-8 h-8 text-gray-200 animate-spin dark:text-gray-600 fill-blue-600"
viewBox="0 0 100 101"
fill="none"
xmlns="http://www.w3.org/2000/svg"
>
<path
d="M100 50.5908C100 78.2051 77.6142 100.591 50 100.591C22.3858 100.591 0 78.2051 0 50.5908C0 22.9766 22.3858 0.59082 50 0.59082C77.6142 0.59082 100 22.9766 100 50.5908ZM9.08144 50.5908C9.08144 73.1895 27.4013 91.5094 50 91.5094C72.5987 91.5094 90.9186 73.1895 90.9186 50.5908C90.9186 27.9921 72.5987 9.67226 50 9.67226C27.4013 9.67226 9.08144 27.9921 9.08144 50.5908Z"
fill="currentColor"
></path>
<path
d="M93.9676 39.0409C96.393 38.4038 97.8624 35.9116 97.0079 33.5539C95.2932 28.8227 92.871 24.3692 89.8167 20.348C85.8452 15.1192 80.8826 10.7238 75.2124 7.41289C69.5422 4.10194 63.2754 1.94025 56.7698 1.05124C51.7666 0.367541 46.6976 0.446843 41.7345 1.27873C39.2613 1.69328 37.813 4.19778 38.4501 6.62326C39.0873 9.04874 41.5694 10.4717 44.0505 10.1071C47.8511 9.54855 51.7191 9.52689 55.5402 10.0491C60.8642 10.7766 65.9928 12.5457 70.6331 15.2552C75.2735 17.9648 79.3347 21.5619 82.5849 25.841C84.9175 28.9121 86.7997 32.2913 88.1811 35.8758C89.083 38.2158 91.5421 39.6781 93.9676 39.0409Z"
fill="currentFill"
></path>
</svg>
<span className="sr-only">Loading...</span>
</div>
);
};
export default Spinner;

View file

@ -0,0 +1,73 @@
import React, { useState, useRef } from "react";
// commons
import { classNames } from "constants/common";
// hooks
import useAutosizeTextArea from "lib/hooks/useAutosizeTextArea";
// types
import { Props } from "./types";
const TextArea: React.FC<Props> = ({
id,
label,
className,
value,
placeholder,
name,
register,
mode = "primary",
rows,
cols,
disabled,
error,
validations,
onChange,
...rest
}) => {
const [textareaValue, setTextareaValue] = useState(value ?? "");
const textAreaRef = useRef<any>(null);
useAutosizeTextArea(textAreaRef.current, textareaValue);
return (
<>
{label && (
<label htmlFor={id} className="text-gray-500 mb-2">
{label}
</label>
)}
<textarea
id={id}
placeholder={placeholder}
value={value}
rows={rows}
cols={cols}
disabled={disabled}
{...(register && register(name, validations))}
ref={(e) => {
textAreaRef.current = e;
if (register) register(name).ref(e);
}}
onChange={(e) => {
register && register(name).onChange(e);
onChange && onChange(e);
setTextareaValue(e.target.value);
}}
className={classNames(
"w-full outline-none px-3 py-2 bg-transparent",
mode === "primary" ? "border border-gray-300 rounded-md" : "",
mode === "transparent"
? "bg-transparent border-none transition-all ring-0 focus:ring-1 focus:ring-indigo-600 rounded"
: "",
error ? "border-red-500" : "",
error && mode === "primary" ? "bg-red-100" : "",
className ?? ""
)}
{...rest}
></textarea>
{error?.message && <div className="text-red-500 text-sm">{error.message}</div>}
</>
);
};
export default TextArea;

12
apps/app/ui/TextArea/types.d.ts vendored Normal file
View file

@ -0,0 +1,12 @@
import React from "react";
import type { UseFormRegister, RegisterOptions, FieldError } from "react-hook-form";
export interface Props extends React.ComponentPropsWithoutRef<"textarea"> {
label?: string;
value?: string | number | readonly string[];
name: string;
register?: UseFormRegister<any>;
mode?: "primary" | "transparent" | "secondary" | "disabled";
validations?: RegisterOptions;
error?: FieldError;
}

View file

@ -0,0 +1,41 @@
import React from "react";
type Props = {
children: React.ReactNode;
content: React.ReactNode;
position?: "top" | "bottom" | "left" | "right";
};
const Tooltip: React.FC<Props> = ({ children, content, position = "top" }) => {
return (
<div className="relative group">
<div
className={`fixed pointer-events-none transition-opacity opacity-0 group-hover:opacity-100 bg-black text-white px-3 py-1 rounded ${
position === "right"
? "left-14"
: position === "left"
? "right-14"
: position === "top"
? "bottom-14"
: "top-14"
}`}
>
<p className="truncate text-xs">{content}</p>
<span
className={`absolute w-2 h-2 bg-black ${
position === "top"
? "top-full left-1/2 transform -translate-y-1/2 -translate-x-1/2 rotate-45"
: position === "bottom"
? "bottom-full left-1/2 transform translate-y-1/2 -translate-x-1/2 rotate-45"
: position === "left"
? "left-full top-1/2 transform translate-x-1/2 -translate-y-1/2 rotate-45"
: "right-full top-1/2 transform translate-x-1/2 -translate-y-1/2 rotate-45"
}`}
></span>
</div>
{children}
</div>
);
};
export default Tooltip;

11
apps/app/ui/index.ts Normal file
View file

@ -0,0 +1,11 @@
export { default as Button } from "./Button";
export { default as Input } from "./Input";
export { default as Select } from "./Select";
export { default as TextArea } from "./TextArea";
export { default as CustomListbox } from "./CustomListbox";
export { default as Spinner } from "./Spinner";
export { default as Tooltip } from "./Tooltip";
export { default as SearchListbox } from "./SearchListbox";
export { default as HeaderButton } from "./HeaderButton";
export * from "./Breadcrumbs";
export * from "./EmptySpace";