forked from github/plane
fix: consistent dropdowns, refactor: ui components (#286)
This commit is contained in:
parent
ec37bb9d23
commit
667dafbda4
@ -1,18 +1,17 @@
|
|||||||
import { useState, FC, Fragment } from "react";
|
import { useState, FC, Fragment } from "react";
|
||||||
|
|
||||||
import Image from "next/image";
|
|
||||||
import { useRouter } from "next/router";
|
import { useRouter } from "next/router";
|
||||||
|
|
||||||
import useSWR from "swr";
|
import useSWR from "swr";
|
||||||
|
|
||||||
// headless ui
|
// headless ui
|
||||||
import { Transition, Combobox } from "@headlessui/react";
|
import { Transition, Combobox } from "@headlessui/react";
|
||||||
|
// services
|
||||||
|
import projectServices from "services/project.service";
|
||||||
|
// ui
|
||||||
|
import { Avatar } from "components/ui";
|
||||||
// icons
|
// icons
|
||||||
import { UserIcon } from "@heroicons/react/24/outline";
|
import { UserIcon } from "@heroicons/react/24/outline";
|
||||||
// service
|
|
||||||
import projectServices from "services/project.service";
|
|
||||||
// types
|
|
||||||
import type { IProjectMember } from "types";
|
|
||||||
// fetch keys
|
// fetch keys
|
||||||
import { PROJECT_MEMBERS } from "constants/fetch-keys";
|
import { PROJECT_MEMBERS } from "constants/fetch-keys";
|
||||||
|
|
||||||
@ -22,35 +21,6 @@ export type IssueAssigneeSelectProps = {
|
|||||||
onChange: (value: string[]) => void;
|
onChange: (value: string[]) => void;
|
||||||
};
|
};
|
||||||
|
|
||||||
type AssigneeAvatarProps = {
|
|
||||||
user: IProjectMember | undefined;
|
|
||||||
};
|
|
||||||
|
|
||||||
export const AssigneeAvatar: FC<AssigneeAvatarProps> = ({ user }) => {
|
|
||||||
if (!user) return <></>;
|
|
||||||
|
|
||||||
if (user.member.avatar && user.member.avatar !== "") {
|
|
||||||
return (
|
|
||||||
<div className="relative h-4 w-4">
|
|
||||||
<Image
|
|
||||||
src={user.member.avatar}
|
|
||||||
alt="avatar"
|
|
||||||
className="rounded-full"
|
|
||||||
layout="fill"
|
|
||||||
objectFit="cover"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
} else
|
|
||||||
return (
|
|
||||||
<div className="grid h-4 w-4 flex-shrink-0 place-items-center rounded-full bg-gray-700 capitalize text-white">
|
|
||||||
{user.member.first_name && user.member.first_name !== ""
|
|
||||||
? user.member.first_name.charAt(0)
|
|
||||||
: user.member.email.charAt(0)}
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
export const IssueAssigneeSelect: FC<IssueAssigneeSelectProps> = ({
|
export const IssueAssigneeSelect: FC<IssueAssigneeSelectProps> = ({
|
||||||
projectId,
|
projectId,
|
||||||
value = [],
|
value = [],
|
||||||
@ -136,14 +106,14 @@ export const IssueAssigneeSelect: FC<IssueAssigneeSelectProps> = ({
|
|||||||
className={({ active, selected }) =>
|
className={({ active, selected }) =>
|
||||||
`${active ? "bg-indigo-50" : ""} ${
|
`${active ? "bg-indigo-50" : ""} ${
|
||||||
selected ? "bg-indigo-50 font-medium" : ""
|
selected ? "bg-indigo-50 font-medium" : ""
|
||||||
} flex cursor-pointer select-none items-center gap-2 truncate p-2 text-gray-900`
|
} flex cursor-pointer select-none items-center gap-2 truncate px-2 py-1 text-gray-900`
|
||||||
}
|
}
|
||||||
value={option.value}
|
value={option.value}
|
||||||
>
|
>
|
||||||
{people && (
|
{people && (
|
||||||
<>
|
<>
|
||||||
<AssigneeAvatar
|
<Avatar
|
||||||
user={people?.find((p) => p.member.id === option.value)}
|
user={people?.find((p) => p.member.id === option.value)?.member}
|
||||||
/>
|
/>
|
||||||
{option.display}
|
{option.display}
|
||||||
</>
|
</>
|
||||||
|
@ -97,8 +97,8 @@ export const SidebarAssigneeSelect: React.FC<Props> = ({ control, submitChanges,
|
|||||||
<Listbox.Option
|
<Listbox.Option
|
||||||
key={option.member.id}
|
key={option.member.id}
|
||||||
className={({ active, selected }) =>
|
className={({ active, selected }) =>
|
||||||
`${
|
`${active || selected ? "bg-indigo-50" : ""} ${
|
||||||
active || selected ? "bg-indigo-50" : ""
|
selected ? "font-medium" : ""
|
||||||
} flex cursor-pointer select-none items-center gap-2 truncate p-2 text-gray-900`
|
} flex cursor-pointer select-none items-center gap-2 truncate p-2 text-gray-900`
|
||||||
}
|
}
|
||||||
value={option.member.id}
|
value={option.member.id}
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
import React from "react";
|
import React from "react";
|
||||||
|
|
||||||
// ui
|
// ui
|
||||||
import { Listbox, Transition } from "@headlessui/react";
|
import { CustomSelect } from "components/ui";
|
||||||
// icons
|
// icons
|
||||||
import { getPriorityIcon } from "components/icons/priority-icon";
|
import { getPriorityIcon } from "components/icons/priority-icon";
|
||||||
// types
|
// types
|
||||||
@ -22,67 +22,43 @@ export const ViewPrioritySelect: React.FC<Props> = ({
|
|||||||
position = "right",
|
position = "right",
|
||||||
isNotAllowed,
|
isNotAllowed,
|
||||||
}) => (
|
}) => (
|
||||||
<Listbox
|
<CustomSelect
|
||||||
as="div"
|
label={
|
||||||
value={issue.priority}
|
<span>
|
||||||
|
{getPriorityIcon(
|
||||||
|
issue.priority && issue.priority !== "" ? issue.priority ?? "" : "None",
|
||||||
|
"text-sm"
|
||||||
|
)}
|
||||||
|
</span>
|
||||||
|
}
|
||||||
|
value={issue.state}
|
||||||
onChange={(data: string) => {
|
onChange={(data: string) => {
|
||||||
partialUpdateIssue({ priority: data });
|
partialUpdateIssue({ priority: data });
|
||||||
}}
|
}}
|
||||||
className="group relative flex-shrink-0"
|
maxHeight="md"
|
||||||
|
buttonClassName={`flex ${
|
||||||
|
isNotAllowed ? "cursor-not-allowed" : "cursor-pointer"
|
||||||
|
} items-center gap-x-2 rounded px-2 py-0.5 capitalize shadow-sm focus:border-indigo-500 focus:outline-none focus:ring-1 focus:ring-indigo-500 ${
|
||||||
|
issue.priority === "urgent"
|
||||||
|
? "bg-red-100 text-red-600 hover:bg-red-100"
|
||||||
|
: issue.priority === "high"
|
||||||
|
? "bg-orange-100 text-orange-500 hover:bg-orange-100"
|
||||||
|
: issue.priority === "medium"
|
||||||
|
? "bg-yellow-100 text-yellow-500 hover:bg-yellow-100"
|
||||||
|
: issue.priority === "low"
|
||||||
|
? "bg-green-100 text-green-500 hover:bg-green-100"
|
||||||
|
: "bg-gray-100"
|
||||||
|
} border-none`}
|
||||||
|
noChevron
|
||||||
disabled={isNotAllowed}
|
disabled={isNotAllowed}
|
||||||
>
|
>
|
||||||
{({ open }) => (
|
{PRIORITIES?.map((priority) => (
|
||||||
<div>
|
<CustomSelect.Option key={priority} value={priority} className="capitalize">
|
||||||
<Listbox.Button
|
<>
|
||||||
className={`flex ${
|
{getPriorityIcon(priority, "text-sm")}
|
||||||
isNotAllowed ? "cursor-not-allowed" : "cursor-pointer"
|
{priority ?? "None"}
|
||||||
} items-center gap-x-2 rounded px-2 py-0.5 capitalize shadow-sm focus:border-indigo-500 focus:outline-none focus:ring-1 focus:ring-indigo-500 ${
|
</>
|
||||||
issue.priority === "urgent"
|
</CustomSelect.Option>
|
||||||
? "bg-red-100 text-red-600"
|
))}
|
||||||
: issue.priority === "high"
|
</CustomSelect>
|
||||||
? "bg-orange-100 text-orange-500"
|
|
||||||
: issue.priority === "medium"
|
|
||||||
? "bg-yellow-100 text-yellow-500"
|
|
||||||
: issue.priority === "low"
|
|
||||||
? "bg-green-100 text-green-500"
|
|
||||||
: "bg-gray-100"
|
|
||||||
}`}
|
|
||||||
>
|
|
||||||
{getPriorityIcon(
|
|
||||||
issue.priority && issue.priority !== "" ? issue.priority ?? "" : "None",
|
|
||||||
"text-sm"
|
|
||||||
)}
|
|
||||||
</Listbox.Button>
|
|
||||||
|
|
||||||
<Transition
|
|
||||||
show={open}
|
|
||||||
as={React.Fragment}
|
|
||||||
leave="transition ease-in duration-100"
|
|
||||||
leaveFrom="opacity-100"
|
|
||||||
leaveTo="opacity-0"
|
|
||||||
>
|
|
||||||
<Listbox.Options
|
|
||||||
className={`absolute z-10 mt-1 max-h-48 w-36 overflow-auto rounded-md bg-white py-1 text-xs shadow-lg ring-1 ring-black ring-opacity-5 focus:outline-none ${
|
|
||||||
position === "left" ? "left-0" : "right-0"
|
|
||||||
}`}
|
|
||||||
>
|
|
||||||
{PRIORITIES?.map((priority) => (
|
|
||||||
<Listbox.Option
|
|
||||||
key={priority}
|
|
||||||
className={({ active, selected }) =>
|
|
||||||
`${active || selected ? "bg-indigo-50" : ""} ${
|
|
||||||
selected ? "font-medium" : ""
|
|
||||||
} flex cursor-pointer select-none items-center gap-x-2 px-3 py-2 capitalize`
|
|
||||||
}
|
|
||||||
value={priority}
|
|
||||||
>
|
|
||||||
{getPriorityIcon(priority, "text-sm")}
|
|
||||||
{priority ?? "None"}
|
|
||||||
</Listbox.Option>
|
|
||||||
))}
|
|
||||||
</Listbox.Options>
|
|
||||||
</Transition>
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
</Listbox>
|
|
||||||
);
|
);
|
||||||
|
@ -24,7 +24,7 @@ type Props = {
|
|||||||
export const ViewStateSelect: React.FC<Props> = ({
|
export const ViewStateSelect: React.FC<Props> = ({
|
||||||
issue,
|
issue,
|
||||||
partialUpdateIssue,
|
partialUpdateIssue,
|
||||||
position,
|
position = "right",
|
||||||
isNotAllowed,
|
isNotAllowed,
|
||||||
}) => {
|
}) => {
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
|
@ -4,8 +4,7 @@ import useToast from "hooks/use-toast";
|
|||||||
import workspaceService from "services/workspace.service";
|
import workspaceService from "services/workspace.service";
|
||||||
import { IUser } from "types";
|
import { IUser } from "types";
|
||||||
// ui components
|
// ui components
|
||||||
import MultiInput from "components/ui/multi-input";
|
import { MultiInput, OutlineButton } from "components/ui";
|
||||||
import OutlineButton from "components/ui/outline-button";
|
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
setStep: React.Dispatch<React.SetStateAction<number>>;
|
setStep: React.Dispatch<React.SetStateAction<number>>;
|
||||||
|
@ -2,8 +2,19 @@ import React from "react";
|
|||||||
|
|
||||||
// headless ui
|
// headless ui
|
||||||
import { Listbox, Transition } from "@headlessui/react";
|
import { Listbox, Transition } from "@headlessui/react";
|
||||||
// types
|
|
||||||
import { Props } from "./types";
|
type Props = {
|
||||||
|
title?: string;
|
||||||
|
label?: string;
|
||||||
|
options?: Array<{ display: string; value: any; color?: string; icon?: JSX.Element }>;
|
||||||
|
icon?: JSX.Element;
|
||||||
|
value: any;
|
||||||
|
onChange: (value: any) => void;
|
||||||
|
multiple?: boolean;
|
||||||
|
optionsFontsize?: "sm" | "md" | "lg" | "xl" | "2xl";
|
||||||
|
className?: string;
|
||||||
|
footerOption?: JSX.Element;
|
||||||
|
};
|
||||||
|
|
||||||
export const CustomListbox: React.FC<Props> = ({
|
export const CustomListbox: React.FC<Props> = ({
|
||||||
title = "",
|
title = "",
|
12
apps/app/components/ui/custom-listbox/types.d.ts
vendored
12
apps/app/components/ui/custom-listbox/types.d.ts
vendored
@ -1,12 +0,0 @@
|
|||||||
export type Props = {
|
|
||||||
title?: string;
|
|
||||||
label?: string;
|
|
||||||
options?: Array<{ display: string; value: any; color?: string; icon?: JSX.Element }>;
|
|
||||||
icon?: JSX.Element;
|
|
||||||
value: any;
|
|
||||||
onChange: (value: any) => void;
|
|
||||||
multiple?: boolean;
|
|
||||||
optionsFontsize?: "sm" | "md" | "lg" | "xl" | "2xl";
|
|
||||||
className?: string;
|
|
||||||
footerOption?: JSX.Element;
|
|
||||||
};
|
|
@ -5,9 +5,25 @@ import Link from "next/link";
|
|||||||
import { Menu, Transition } from "@headlessui/react";
|
import { Menu, Transition } from "@headlessui/react";
|
||||||
// icons
|
// icons
|
||||||
import { ChevronDownIcon, EllipsisHorizontalIcon } from "@heroicons/react/24/outline";
|
import { ChevronDownIcon, EllipsisHorizontalIcon } from "@heroicons/react/24/outline";
|
||||||
// types
|
|
||||||
import { MenuItemProps, Props } from "./types";
|
type Props = {
|
||||||
// constants
|
children: React.ReactNode;
|
||||||
|
label?: string | JSX.Element;
|
||||||
|
className?: string;
|
||||||
|
ellipsis?: boolean;
|
||||||
|
width?: "sm" | "md" | "lg" | "xl" | "auto";
|
||||||
|
textAlignment?: "left" | "center" | "right";
|
||||||
|
noBorder?: boolean;
|
||||||
|
optionsPosition?: "left" | "right";
|
||||||
|
};
|
||||||
|
|
||||||
|
type MenuItemProps = {
|
||||||
|
children: JSX.Element | string;
|
||||||
|
renderAs?: "button" | "a";
|
||||||
|
href?: string;
|
||||||
|
onClick?: () => void;
|
||||||
|
className?: string;
|
||||||
|
};
|
||||||
|
|
||||||
const CustomMenu = ({
|
const CustomMenu = ({
|
||||||
children,
|
children,
|
18
apps/app/components/ui/custom-menu/types.d.ts
vendored
18
apps/app/components/ui/custom-menu/types.d.ts
vendored
@ -1,18 +0,0 @@
|
|||||||
export type Props = {
|
|
||||||
children: React.ReactNode;
|
|
||||||
label?: string | JSX.Element;
|
|
||||||
className?: string;
|
|
||||||
ellipsis?: boolean;
|
|
||||||
width?: "sm" | "md" | "lg" | "xl" | "auto";
|
|
||||||
textAlignment?: "left" | "center" | "right";
|
|
||||||
noBorder?: boolean;
|
|
||||||
optionsPosition?: "left" | "right";
|
|
||||||
};
|
|
||||||
|
|
||||||
export type MenuItemProps = {
|
|
||||||
children: JSX.Element | string;
|
|
||||||
renderAs?: "button" | "a";
|
|
||||||
href?: string;
|
|
||||||
onClick?: () => void;
|
|
||||||
className?: string;
|
|
||||||
};
|
|
@ -14,6 +14,7 @@ type CustomSelectProps = {
|
|||||||
width?: "auto" | string;
|
width?: "auto" | string;
|
||||||
input?: boolean;
|
input?: boolean;
|
||||||
noChevron?: boolean;
|
noChevron?: boolean;
|
||||||
|
buttonClassName?: string;
|
||||||
disabled?: boolean;
|
disabled?: boolean;
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -27,6 +28,7 @@ const CustomSelect = ({
|
|||||||
width = "auto",
|
width = "auto",
|
||||||
input = false,
|
input = false,
|
||||||
noChevron = false,
|
noChevron = false,
|
||||||
|
buttonClassName = "",
|
||||||
disabled = false,
|
disabled = false,
|
||||||
}: CustomSelectProps) => (
|
}: CustomSelectProps) => (
|
||||||
<Listbox
|
<Listbox
|
||||||
@ -38,7 +40,7 @@ const CustomSelect = ({
|
|||||||
>
|
>
|
||||||
<div>
|
<div>
|
||||||
<Listbox.Button
|
<Listbox.Button
|
||||||
className={`flex w-full ${
|
className={`${buttonClassName} flex w-full ${
|
||||||
disabled ? "cursor-not-allowed" : "cursor-pointer hover:bg-gray-100"
|
disabled ? "cursor-not-allowed" : "cursor-pointer hover:bg-gray-100"
|
||||||
} items-center justify-between gap-1 rounded-md border shadow-sm duration-300 focus:border-indigo-500 focus:outline-none focus:ring-1 focus:ring-indigo-500 ${
|
} items-center justify-between gap-1 rounded-md border shadow-sm duration-300 focus:border-indigo-500 focus:outline-none focus:ring-1 focus:ring-indigo-500 ${
|
||||||
input ? "border-gray-300 px-3 py-2 text-sm" : "px-2 py-1 text-xs"
|
input ? "border-gray-300 px-3 py-2 text-sm" : "px-2 py-1 text-xs"
|
||||||
@ -95,8 +97,8 @@ const Option: React.FC<OptionProps> = ({ children, value, className }) => (
|
|||||||
<Listbox.Option
|
<Listbox.Option
|
||||||
value={value}
|
value={value}
|
||||||
className={({ active, selected }) =>
|
className={({ active, selected }) =>
|
||||||
`${selected ? "bg-indigo-50 font-medium" : ""} ${
|
`${active || selected ? "bg-indigo-50" : ""} ${
|
||||||
active ? "bg-indigo-50" : ""
|
selected ? "font-medium" : ""
|
||||||
} relative flex cursor-pointer select-none items-center gap-2 truncate p-2 text-gray-900 ${className}`
|
} relative flex cursor-pointer select-none items-center gap-2 truncate p-2 text-gray-900 ${className}`
|
||||||
}
|
}
|
||||||
>
|
>
|
@ -1,15 +1,17 @@
|
|||||||
|
export * from "./input";
|
||||||
|
export * from "./text-area";
|
||||||
|
export * from "./avatar";
|
||||||
export * from "./button";
|
export * from "./button";
|
||||||
export * from "./custom-listbox";
|
export * from "./custom-listbox";
|
||||||
export * from "./custom-menu";
|
export * from "./custom-menu";
|
||||||
export * from "./custom-select";
|
export * from "./custom-select";
|
||||||
|
export * from "./datepicker";
|
||||||
export * from "./empty-space";
|
export * from "./empty-space";
|
||||||
export * from "./header-button";
|
export * from "./header-button";
|
||||||
export * from "./input";
|
|
||||||
export * from "./loader";
|
export * from "./loader";
|
||||||
|
export * from "./multi-input";
|
||||||
export * from "./outline-button";
|
export * from "./outline-button";
|
||||||
|
export * from "./progress-bar";
|
||||||
export * from "./select";
|
export * from "./select";
|
||||||
export * from "./spinner";
|
export * from "./spinner";
|
||||||
export * from "./text-area";
|
|
||||||
export * from "./avatar";
|
|
||||||
export * from "./datepicker";
|
|
||||||
export * from "./tooltip";
|
export * from "./tooltip";
|
||||||
|
@ -1,116 +0,0 @@
|
|||||||
import { Fragment, ReactNode } from "react";
|
|
||||||
// Headless ui imports
|
|
||||||
import { Dialog, Transition } from "@headlessui/react";
|
|
||||||
// Design components
|
|
||||||
import { XMarkIcon } from "@heroicons/react/24/outline";
|
|
||||||
import { Button } from "components/ui";
|
|
||||||
// Icons
|
|
||||||
|
|
||||||
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} max-h-full bg-white p-8 text-left shadow-xl transition-all`}
|
|
||||||
>
|
|
||||||
<Dialog.Title
|
|
||||||
as="h3"
|
|
||||||
className="relative text-lg font-medium leading-6 text-gray-900"
|
|
||||||
>
|
|
||||||
<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 justify-end gap-2`}>
|
|
||||||
<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;
|
|
@ -8,7 +8,7 @@ const isEmailValid = (email: string) =>
|
|||||||
/^(([^<>()[\]\\.,;:\s@"]+(\.[^<>()[\]\\.,;:\s@"]+)*)|(".+"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/
|
/^(([^<>()[\]\\.,;:\s@"]+(\.[^<>()[\]\\.,;:\s@"]+)*)|(".+"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/
|
||||||
);
|
);
|
||||||
|
|
||||||
const MultiInput = ({ label, name, placeholder, setValue, watch }: any) => {
|
export const MultiInput = ({ label, name, placeholder, setValue, watch }: any) => {
|
||||||
const handleKeyDown = (e: any) => {
|
const handleKeyDown = (e: any) => {
|
||||||
if (e.key !== "Enter") return;
|
if (e.key !== "Enter") return;
|
||||||
const value = e.target.value;
|
const value = e.target.value;
|
||||||
@ -72,5 +72,3 @@ const MultiInput = ({ label, name, placeholder, setValue, watch }: any) => {
|
|||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
export default MultiInput;
|
|
@ -10,7 +10,7 @@ type Props = {
|
|||||||
disabled?: boolean;
|
disabled?: boolean;
|
||||||
};
|
};
|
||||||
|
|
||||||
const OutlineButton = React.forwardRef<HTMLButtonElement, Props>(
|
export const OutlineButton = React.forwardRef<HTMLButtonElement, Props>(
|
||||||
(
|
(
|
||||||
{
|
{
|
||||||
children,
|
children,
|
||||||
@ -58,5 +58,3 @@ const OutlineButton = React.forwardRef<HTMLButtonElement, Props>(
|
|||||||
);
|
);
|
||||||
|
|
||||||
OutlineButton.displayName = "Button";
|
OutlineButton.displayName = "Button";
|
||||||
|
|
||||||
export default OutlineButton;
|
|
@ -9,7 +9,7 @@ type Props = {
|
|||||||
inactiveStrokeColor?: string;
|
inactiveStrokeColor?: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
const ProgressBar: React.FC<Props> = ({
|
export const ProgressBar: React.FC<Props> = ({
|
||||||
maxValue = 0,
|
maxValue = 0,
|
||||||
value = 0,
|
value = 0,
|
||||||
radius = 8,
|
radius = 8,
|
||||||
@ -67,4 +67,3 @@ const ProgressBar: React.FC<Props> = ({
|
|||||||
</svg>
|
</svg>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
export default ProgressBar;
|
|
60
apps/app/components/ui/select.tsx
Normal file
60
apps/app/components/ui/select.tsx
Normal file
@ -0,0 +1,60 @@
|
|||||||
|
import React from "react";
|
||||||
|
|
||||||
|
// react-hook-form
|
||||||
|
import { RegisterOptions, UseFormRegister } from "react-hook-form";
|
||||||
|
|
||||||
|
type Props = {
|
||||||
|
label?: string;
|
||||||
|
id: string;
|
||||||
|
name: string;
|
||||||
|
value?: string | number | readonly string[];
|
||||||
|
className?: string;
|
||||||
|
register?: UseFormRegister<any>;
|
||||||
|
disabled?: boolean;
|
||||||
|
validations?: RegisterOptions;
|
||||||
|
error?: any;
|
||||||
|
autoComplete?: "on" | "off";
|
||||||
|
options: { label: string; value: any }[];
|
||||||
|
size?: "rg" | "lg";
|
||||||
|
fullWidth?: boolean;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const Select: React.FC<Props> = ({
|
||||||
|
id,
|
||||||
|
label,
|
||||||
|
value,
|
||||||
|
className = "",
|
||||||
|
name,
|
||||||
|
register,
|
||||||
|
disabled,
|
||||||
|
validations,
|
||||||
|
error,
|
||||||
|
options,
|
||||||
|
size = "rg",
|
||||||
|
fullWidth = true,
|
||||||
|
}) => (
|
||||||
|
<>
|
||||||
|
{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 text-base border border-gray-300 focus:outline-none focus:ring-indigo-500 focus:border-indigo-500 sm:text-sm rounded-md bg-transparent ${
|
||||||
|
fullWidth ? "w-full" : ""
|
||||||
|
} ${size === "rg" ? "px-3 py-2" : size === "lg" ? "p-3" : ""} ${className}`}
|
||||||
|
>
|
||||||
|
{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>}
|
||||||
|
</>
|
||||||
|
);
|
@ -1,47 +0,0 @@
|
|||||||
import React from "react";
|
|
||||||
// types
|
|
||||||
import { Props } from "./types";
|
|
||||||
|
|
||||||
export const Select: React.FC<Props> = ({
|
|
||||||
id,
|
|
||||||
label,
|
|
||||||
value,
|
|
||||||
className = "",
|
|
||||||
name,
|
|
||||||
register,
|
|
||||||
disabled,
|
|
||||||
validations,
|
|
||||||
error,
|
|
||||||
options,
|
|
||||||
size = "rg",
|
|
||||||
fullWidth = true,
|
|
||||||
}) => (
|
|
||||||
<>
|
|
||||||
{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 text-base border border-gray-300 focus:outline-none focus:ring-indigo-500 focus:border-indigo-500 sm:text-sm rounded-md bg-transparent ${
|
|
||||||
fullWidth ? "w-full" : ""
|
|
||||||
} ${
|
|
||||||
size === "rg" ? "px-3 py-2" : size === "lg" ? "p-3" : ""
|
|
||||||
} ${className}`}
|
|
||||||
>
|
|
||||||
{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>
|
|
||||||
)}
|
|
||||||
</>
|
|
||||||
);
|
|
17
apps/app/components/ui/select/types.d.ts
vendored
17
apps/app/components/ui/select/types.d.ts
vendored
@ -1,17 +0,0 @@
|
|||||||
import type { UseFormRegister, RegisterOptions } 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?: any;
|
|
||||||
autoComplete?: "on" | "off";
|
|
||||||
options: { label: string; value: any }[];
|
|
||||||
size?: "rg" | "lg";
|
|
||||||
fullWidth?: boolean;
|
|
||||||
};
|
|
@ -20,9 +20,8 @@ import EmojiIconPicker from "components/emoji-icon-picker";
|
|||||||
// hooks
|
// hooks
|
||||||
import useToast from "hooks/use-toast";
|
import useToast from "hooks/use-toast";
|
||||||
// ui
|
// ui
|
||||||
import { Button, Input, TextArea, Loader, CustomSelect } from "components/ui";
|
import { Button, Input, TextArea, Loader, CustomSelect, OutlineButton } from "components/ui";
|
||||||
import { BreadcrumbItem, Breadcrumbs } from "components/breadcrumbs";
|
import { BreadcrumbItem, Breadcrumbs } from "components/breadcrumbs";
|
||||||
import OutlineButton from "components/ui/outline-button";
|
|
||||||
// helpers
|
// helpers
|
||||||
import { debounce } from "helpers/common.helper";
|
import { debounce } from "helpers/common.helper";
|
||||||
// types
|
// types
|
||||||
|
@ -24,9 +24,8 @@ import useToast from "hooks/use-toast";
|
|||||||
import { ImageUploadModal } from "components/core";
|
import { ImageUploadModal } from "components/core";
|
||||||
import ConfirmWorkspaceDeletion from "components/workspace/confirm-workspace-deletion";
|
import ConfirmWorkspaceDeletion from "components/workspace/confirm-workspace-deletion";
|
||||||
// ui
|
// ui
|
||||||
import { Spinner, Button, Input, CustomSelect } from "components/ui";
|
import { Spinner, Button, Input, CustomSelect, OutlineButton } from "components/ui";
|
||||||
import { BreadcrumbItem, Breadcrumbs } from "components/breadcrumbs";
|
import { BreadcrumbItem, Breadcrumbs } from "components/breadcrumbs";
|
||||||
import OutlineButton from "components/ui/outline-button";
|
|
||||||
// helpers
|
// helpers
|
||||||
import { copyTextToClipboard } from "helpers/string.helper";
|
import { copyTextToClipboard } from "helpers/string.helper";
|
||||||
// types
|
// types
|
||||||
|
Loading…
Reference in New Issue
Block a user