forked from github/plane
style: ui improvement (#1806)
* style: kanban board theming * style: assignee ui revamp * style: kanban board header, label , priority , state and avatar ui revamp * style: kanban card state dropdown * style: sidebar profile dropdown * style: sidebar dropdown icon * style: sidebar workspace dropdown * style: assignee select component * fix: state select * style: consistent app header button
This commit is contained in:
parent
289e81d6eb
commit
5c964d144a
@ -155,7 +155,7 @@ export const IssuesFilterView: React.FC = () => {
|
||||
{({ open }) => (
|
||||
<>
|
||||
<Popover.Button
|
||||
className={`group flex items-center gap-2 rounded-md border border-custom-sidebar-border-200 bg-transparent px-3 py-1.5 text-xs hover:bg-custom-sidebar-background-90 hover:text-custom-sidebar-text-100 focus:outline-none duration-300 ${
|
||||
className={`group flex items-center gap-2 rounded-md border border-custom-border-200 px-3 py-1.5 text-xs hover:bg-custom-sidebar-background-90 hover:text-custom-sidebar-text-100 focus:outline-none duration-300 ${
|
||||
open
|
||||
? "bg-custom-sidebar-background-90 text-custom-sidebar-text-100"
|
||||
: "text-custom-sidebar-text-200"
|
||||
|
@ -10,7 +10,7 @@ import projectService from "services/project.service";
|
||||
// hooks
|
||||
import useProjects from "hooks/use-projects";
|
||||
// component
|
||||
import { Avatar } from "components/ui";
|
||||
import { Avatar, Icon } from "components/ui";
|
||||
// icons
|
||||
import { ArrowsPointingInIcon, ArrowsPointingOutIcon, PlusIcon } from "@heroicons/react/24/outline";
|
||||
import { getPriorityIcon, getStateGroupIcon } from "components/icons";
|
||||
@ -140,7 +140,7 @@ export const BoardHeader: React.FC<Props> = ({
|
||||
>
|
||||
<div className={`flex items-center ${isCollapsed ? "gap-1" : "flex-col gap-2"}`}>
|
||||
<div
|
||||
className={`flex cursor-pointer items-center gap-x-3 max-w-[316px] ${
|
||||
className={`flex cursor-pointer items-center gap-x-2 max-w-[316px] ${
|
||||
!isCollapsed ? "mb-2 flex-col gap-y-2 py-2" : ""
|
||||
}`}
|
||||
>
|
||||
@ -155,11 +155,7 @@ export const BoardHeader: React.FC<Props> = ({
|
||||
>
|
||||
{getGroupTitle()}
|
||||
</h2>
|
||||
<span
|
||||
className={`${
|
||||
isCollapsed ? "ml-0.5" : ""
|
||||
} min-w-[2.5rem] rounded-full bg-custom-background-80 py-1 text-center text-xs`}
|
||||
>
|
||||
<span className={`${isCollapsed ? "ml-0.5" : ""} py-1 text-center text-sm`}>
|
||||
{groupedIssues?.[groupTitle].length ?? 0}
|
||||
</span>
|
||||
</div>
|
||||
@ -174,9 +170,12 @@ export const BoardHeader: React.FC<Props> = ({
|
||||
}}
|
||||
>
|
||||
{isCollapsed ? (
|
||||
<ArrowsPointingInIcon className="h-4 w-4" />
|
||||
<Icon
|
||||
iconName="close_fullscreen"
|
||||
className="text-base font-medium text-custom-text-900"
|
||||
/>
|
||||
) : (
|
||||
<ArrowsPointingOutIcon className="h-4 w-4" />
|
||||
<Icon iconName="open_in_full" className="text-base font-medium text-custom-text-900" />
|
||||
)}
|
||||
</button>
|
||||
{!disableUserActions && selectedGroup !== "created_by" && (
|
||||
|
@ -232,7 +232,7 @@ export const SingleBoardIssue: React.FC<Props> = ({
|
||||
</a>
|
||||
</ContextMenu>
|
||||
<div
|
||||
className={`mb-3 rounded bg-custom-background-90 shadow ${
|
||||
className={`mb-3 rounded bg-custom-background-100 shadow ${
|
||||
snapshot.isDragging ? "border-2 border-custom-primary shadow-lg" : ""
|
||||
}`}
|
||||
ref={provided.innerRef}
|
||||
@ -301,10 +301,10 @@ export const SingleBoardIssue: React.FC<Props> = ({
|
||||
{issue.project_detail.identifier}-{issue.sequence_id}
|
||||
</div>
|
||||
)}
|
||||
<h5 className="text-sm break-words line-clamp-3">{issue.name}</h5>
|
||||
<h5 className="text-sm break-words line-clamp-2">{issue.name}</h5>
|
||||
</a>
|
||||
</Link>
|
||||
<div className="relative mt-2.5 flex flex-wrap items-center gap-2 text-xs">
|
||||
<div className="mt-2.5 flex overflow-x-scroll items-center gap-2 text-xs">
|
||||
{properties.priority && (
|
||||
<ViewPrioritySelect
|
||||
issue={issue}
|
||||
@ -347,6 +347,7 @@ export const SingleBoardIssue: React.FC<Props> = ({
|
||||
issue={issue}
|
||||
partialUpdateIssue={partialUpdateIssue}
|
||||
isNotAllowed={isNotAllowed}
|
||||
customButton
|
||||
user={user}
|
||||
selfPositioned
|
||||
/>
|
||||
|
@ -18,7 +18,7 @@ export const ViewIssueLabel: React.FC<Props> = ({ issue, maxRender = 1 }) => (
|
||||
{issue.label_details.map((label, index) => (
|
||||
<div
|
||||
key={label.id}
|
||||
className="flex cursor-default items-center rounded-md border border-custom-border-300 px-2.5 py-1 text-xs shadow-sm"
|
||||
className="flex cursor-default items-center flex-shrink-0 rounded-md border border-custom-border-300 px-2.5 py-1 text-xs shadow-sm"
|
||||
>
|
||||
<Tooltip position="top" tooltipHeading="Label" tooltipContent={label.name}>
|
||||
<div className="flex items-center gap-1.5 text-custom-text-200">
|
||||
@ -35,7 +35,7 @@ export const ViewIssueLabel: React.FC<Props> = ({ issue, maxRender = 1 }) => (
|
||||
))}
|
||||
</>
|
||||
) : (
|
||||
<div className="flex cursor-default items-center rounded-md border border-custom-border-300 px-2.5 py-1 text-xs shadow-sm">
|
||||
<div className="flex cursor-default items-center flex-shrink-0 rounded-md border border-custom-border-300 px-2.5 py-1 text-xs shadow-sm">
|
||||
<Tooltip
|
||||
position="top"
|
||||
tooltipHeading="Labels"
|
||||
|
@ -120,7 +120,7 @@ export const MyIssuesViewOptions: React.FC = () => {
|
||||
{({ open }) => (
|
||||
<>
|
||||
<Popover.Button
|
||||
className={`group flex items-center gap-2 rounded-md border border-custom-sidebar-border-200 bg-transparent px-3 py-1.5 text-xs hover:bg-custom-sidebar-background-90 hover:text-custom-sidebar-text-100 focus:outline-none duration-300 ${
|
||||
className={`group flex items-center gap-2 rounded-md border border-custom-border-200 bg-transparent px-3 py-1.5 text-xs hover:bg-custom-sidebar-background-90 hover:text-custom-sidebar-text-100 focus:outline-none duration-300 ${
|
||||
open
|
||||
? "bg-custom-sidebar-background-90 text-custom-sidebar-text-100"
|
||||
: "text-custom-sidebar-text-200"
|
||||
|
@ -8,7 +8,7 @@ import useSWR from "swr";
|
||||
import projectService from "services/project.service";
|
||||
import trackEventServices from "services/track-event.service";
|
||||
// ui
|
||||
import { AssigneesList, Avatar, CustomSearchSelect, Tooltip } from "components/ui";
|
||||
import { AssigneesList, Avatar, CustomSearchSelect, Icon, Tooltip } from "components/ui";
|
||||
// icons
|
||||
import { UserGroupIcon } from "@heroicons/react/24/outline";
|
||||
// types
|
||||
@ -73,11 +73,11 @@ export const ViewAssigneeSelect: React.FC<Props> = ({
|
||||
>
|
||||
{issue.assignees && issue.assignees.length > 0 && Array.isArray(issue.assignees) ? (
|
||||
<div className="-my-0.5 flex items-center justify-center gap-2">
|
||||
<AssigneesList userIds={issue.assignees} length={5} showLength={true} />
|
||||
<AssigneesList userIds={issue.assignees} length={3} showLength={true} />
|
||||
</div>
|
||||
) : (
|
||||
<div className="flex items-center justify-center gap-2">
|
||||
<UserGroupIcon className="h-4 w-4 text-custom-text-200" />
|
||||
<div className="flex items-center justify-center gap-2 px-1.5 py-1 rounded shadow-sm border border-custom-border-300">
|
||||
<Icon iconName="person" className="text-sm !leading-4" />
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
@ -87,6 +87,7 @@ export const ViewAssigneeSelect: React.FC<Props> = ({
|
||||
return (
|
||||
<CustomSearchSelect
|
||||
value={issue.assignees}
|
||||
buttonClassName="!p-0"
|
||||
onChange={(data: any) => {
|
||||
const newData = issue.assignees ?? [];
|
||||
|
||||
|
@ -67,14 +67,8 @@ export const ViewPrioritySelect: React.FC<Props> = ({
|
||||
noBorder
|
||||
? ""
|
||||
: issue.priority === "urgent"
|
||||
? "border-red-500/20 bg-red-500/20"
|
||||
: issue.priority === "high"
|
||||
? "border-orange-500/20 bg-orange-500/20"
|
||||
: issue.priority === "medium"
|
||||
? "border-yellow-500/20 bg-yellow-500/20"
|
||||
: issue.priority === "low"
|
||||
? "border-green-500/20 bg-green-500/20"
|
||||
: "border-custom-border-200 bg-custom-background-80"
|
||||
? "border-red-500/20 bg-red-500"
|
||||
: "border-custom-border-300 bg-custom-background-100"
|
||||
} items-center`}
|
||||
>
|
||||
<Tooltip
|
||||
@ -87,7 +81,7 @@ export const ViewPrioritySelect: React.FC<Props> = ({
|
||||
issue.priority && issue.priority !== "" ? issue.priority ?? "" : "None",
|
||||
`text-sm ${
|
||||
issue.priority === "urgent"
|
||||
? "text-red-500"
|
||||
? "text-white"
|
||||
: issue.priority === "high"
|
||||
? "text-orange-500"
|
||||
: issue.priority === "medium"
|
||||
|
@ -74,9 +74,9 @@ export const ViewStateSelect: React.FC<Props> = ({
|
||||
position={tooltipPosition}
|
||||
>
|
||||
<div className="flex items-center cursor-pointer w-full gap-2 text-custom-text-200">
|
||||
<span className="h-4 w-4">
|
||||
<span className="h-3.5 w-3.5">
|
||||
{selectedOption &&
|
||||
getStateGroupIcon(selectedOption.group, "16", "16", selectedOption.color)}
|
||||
getStateGroupIcon(selectedOption.group, "14", "14", selectedOption.color)}
|
||||
</span>
|
||||
<span className="truncate">{selectedOption?.name ?? "State"}</span>
|
||||
</div>
|
||||
@ -131,6 +131,7 @@ export const ViewStateSelect: React.FC<Props> = ({
|
||||
disabled={isNotAllowed}
|
||||
onOpen={() => setFetchStates(true)}
|
||||
noChevron
|
||||
selfPositioned={selfPositioned}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
@ -146,7 +146,7 @@ export const ProfileIssuesViewOptions: React.FC = () => {
|
||||
{({ open }) => (
|
||||
<>
|
||||
<Popover.Button
|
||||
className={`group flex items-center gap-2 rounded-md border border-custom-sidebar-border-200 bg-transparent px-3 py-1.5 text-xs hover:bg-custom-sidebar-background-90 hover:text-custom-sidebar-text-100 focus:outline-none duration-300 ${
|
||||
className={`group flex items-center gap-2 rounded-md border border-custom-border-200 bg-transparent px-3 py-1.5 text-xs hover:bg-custom-sidebar-background-90 hover:text-custom-sidebar-text-100 focus:outline-none duration-300 ${
|
||||
open
|
||||
? "bg-custom-sidebar-background-90 text-custom-sidebar-text-100"
|
||||
: "text-custom-sidebar-text-200"
|
||||
|
@ -3,6 +3,8 @@ import { useRouter } from "next/router";
|
||||
|
||||
import useSWR from "swr";
|
||||
|
||||
// component
|
||||
import { Icon } from "components/ui";
|
||||
// services
|
||||
import workspaceService from "services/workspace.service";
|
||||
// icons
|
||||
@ -23,12 +25,14 @@ type AvatarProps = {
|
||||
export const Avatar: React.FC<AvatarProps> = ({
|
||||
user,
|
||||
index,
|
||||
height = "20px",
|
||||
width = "20px",
|
||||
height = "24px",
|
||||
width = "24px",
|
||||
fontSize = "12px",
|
||||
}) => (
|
||||
<div
|
||||
className={`relative rounded-full ${index && index !== 0 ? "-ml-3.5" : ""}`}
|
||||
className={`relative rounded border-[0.5px] ${
|
||||
index && index !== 0 ? "-ml-3.5 border-custom-border-200" : "border-transparent"
|
||||
}`}
|
||||
style={{
|
||||
height: height,
|
||||
width: width,
|
||||
@ -36,8 +40,8 @@ export const Avatar: React.FC<AvatarProps> = ({
|
||||
>
|
||||
{user && user.avatar && user.avatar !== "" ? (
|
||||
<div
|
||||
className={`rounded-full border-2 ${
|
||||
index ? "border-custom-border-200 bg-custom-background-80" : "border-transparent"
|
||||
className={`rounded border-[0.5px] ${
|
||||
index ? "border-custom-border-200 bg-custom-background-100" : "border-transparent"
|
||||
}`}
|
||||
style={{
|
||||
height: height,
|
||||
@ -46,13 +50,13 @@ export const Avatar: React.FC<AvatarProps> = ({
|
||||
>
|
||||
<img
|
||||
src={user.avatar}
|
||||
className="absolute top-0 left-0 h-full w-full object-cover rounded-full"
|
||||
className="absolute top-0 left-0 h-full w-full object-cover rounded"
|
||||
alt={user.display_name}
|
||||
/>
|
||||
</div>
|
||||
) : (
|
||||
<div
|
||||
className="grid place-items-center rounded-full border-2 border-custom-border-200 bg-gray-700 text-xs capitalize text-white"
|
||||
className="grid place-items-center text-xs capitalize text-white rounded bg-gray-700 border-[0.5px] border-custom-border-200"
|
||||
style={{
|
||||
height: height,
|
||||
width: width,
|
||||
@ -75,7 +79,7 @@ type AsigneesListProps = {
|
||||
export const AssigneesList: React.FC<AsigneesListProps> = ({
|
||||
users,
|
||||
userIds,
|
||||
length = 5,
|
||||
length = 3,
|
||||
showLength = true,
|
||||
}) => {
|
||||
const router = useRouter();
|
||||
@ -88,7 +92,7 @@ export const AssigneesList: React.FC<AsigneesListProps> = ({
|
||||
|
||||
if ((users && users.length === 0) || (userIds && userIds.length === 0))
|
||||
return (
|
||||
<div className="h-5 w-5 rounded-full border-2 border-white bg-custom-background-80">
|
||||
<div className="h-5 w-5 rounded border-[0.5px] border-custom-border-200 bg-custom-background-80">
|
||||
<Image src={User} height="100%" width="100%" className="rounded-full" alt="No user" />
|
||||
</div>
|
||||
);
|
||||
@ -100,7 +104,14 @@ export const AssigneesList: React.FC<AsigneesListProps> = ({
|
||||
{users.slice(0, length).map((user, index) => (
|
||||
<Avatar key={user?.id} user={user} index={index} />
|
||||
))}
|
||||
{users.length > length ? <span>+{users.length - length}</span> : null}
|
||||
{users.length > length ? (
|
||||
<div className="-ml-3.5 relative h-6 w-6 rounded">
|
||||
<div className="grid place-items-center rounded bg-custom-background-80 text-xs capitalize h-6 w-6 text-custom-text-200 border-[0.5px] border-custom-border-300">
|
||||
<Icon iconName="add" className="text-xs !leading-3 -mr-0.5" />
|
||||
{users.length - length}
|
||||
</div>
|
||||
</div>
|
||||
) : null}
|
||||
</>
|
||||
)}
|
||||
{userIds && (
|
||||
@ -112,7 +123,12 @@ export const AssigneesList: React.FC<AsigneesListProps> = ({
|
||||
})}
|
||||
{showLength ? (
|
||||
userIds.length > length ? (
|
||||
<span>+{userIds.length - length}</span>
|
||||
<div className="-ml-3.5 relative h-6 w-6 rounded">
|
||||
<div className="flex items-center rounded bg-custom-background-80 text-xs capitalize h-6 w-6 text-custom-text-200 border-[0.5px] border-custom-border-300">
|
||||
<Icon iconName="add" className="text-xs !leading-3 -mr-0.5" />
|
||||
{userIds.length - length}
|
||||
</div>
|
||||
</div>
|
||||
) : null
|
||||
) : (
|
||||
""
|
||||
|
@ -146,13 +146,12 @@ export const WorkspaceSidebarDropdown = () => {
|
||||
>
|
||||
<Menu.Items
|
||||
className="fixed left-4 z-20 mt-1 flex flex-col w-full max-w-[17rem] origin-top-left rounded-md
|
||||
border border-custom-sidebar-border-200 bg-custom-sidebar-background-90 shadow-lg outline-none"
|
||||
border border-custom-sidebar-border-200 bg-custom-sidebar-background-100 shadow-lg outline-none"
|
||||
>
|
||||
<div className="flex flex-col items-start justify-start gap-3 p-3">
|
||||
<div className="text-sm text-custom-sidebar-text-200">{user?.display_name}</div>
|
||||
<span className="text-sm font-semibold text-custom-sidebar-text-200">Workspace</span>
|
||||
<span className="text-sm font-medium text-custom-sidebar-text-200">Workspace</span>
|
||||
{workspaces ? (
|
||||
<div className="flex h-full w-full flex-col items-start justify-start gap-3.5">
|
||||
<div className="flex h-full w-full flex-col items-start justify-start gap-1.5">
|
||||
{workspaces.length > 0 ? (
|
||||
workspaces.map((workspace) => (
|
||||
<Menu.Item key={workspace.id}>
|
||||
@ -160,7 +159,7 @@ export const WorkspaceSidebarDropdown = () => {
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => handleWorkspaceNavigation(workspace)}
|
||||
className="flex w-full items-center justify-between gap-1 rounded-md text-sm text-custom-sidebar-text-100"
|
||||
className="flex w-full items-center justify-between gap-1 p-1 rounded-md text-sm text-custom-sidebar-text-100 hover:bg-custom-sidebar-background-80"
|
||||
>
|
||||
<div className="flex items-center justify-start gap-2.5">
|
||||
<span className="relative flex h-6 w-6 items-center justify-center rounded bg-gray-700 p-2 text-xs uppercase text-white">
|
||||
@ -186,9 +185,7 @@ export const WorkspaceSidebarDropdown = () => {
|
||||
<span className="p-1">
|
||||
<CheckIcon
|
||||
className={`h-3 w-3.5 text-custom-sidebar-text-100 ${
|
||||
active || workspace.id === activeWorkspace?.id
|
||||
? "opacity-100"
|
||||
: "opacity-0"
|
||||
workspace.id === activeWorkspace?.id ? "opacity-100" : "opacity-0"
|
||||
}`}
|
||||
/>
|
||||
</span>
|
||||
@ -205,9 +202,9 @@ export const WorkspaceSidebarDropdown = () => {
|
||||
onClick={() => {
|
||||
router.push("/create-workspace");
|
||||
}}
|
||||
className="flex w-full items-center gap-1 text-sm text-custom-sidebar-text-200"
|
||||
className="flex w-full items-center gap-2 px-2 py-1 text-sm text-custom-sidebar-text-200 hover:bg-custom-sidebar-background-80"
|
||||
>
|
||||
<PlusIcon className="h-3 w-3" />
|
||||
<PlusIcon className="h-4 w-4" />
|
||||
Create Workspace
|
||||
</Menu.Item>
|
||||
</div>
|
||||
@ -263,16 +260,17 @@ export const WorkspaceSidebarDropdown = () => {
|
||||
leaveTo="transform opacity-0 scale-95"
|
||||
>
|
||||
<Menu.Items
|
||||
className="absolute left-0 z-20 mt-1.5 flex flex-col w-52 origin-top-left rounded-md
|
||||
border border-custom-sidebar-border-200 bg-custom-sidebar-background-90 p-2 divide-y divide-custom-sidebar-border-200 shadow-lg text-xs outline-none"
|
||||
className="absolute left-0 z-20 mt-1.5 flex flex-col w-52 origin-top-left rounded-md
|
||||
border border-custom-sidebar-border-200 bg-custom-sidebar-background-100 px-1 py-2 divide-y divide-custom-sidebar-border-200 shadow-lg text-xs outline-none"
|
||||
>
|
||||
<div className="flex flex-col space-y-2 pb-2">
|
||||
<div className="flex flex-col gap-2.5 pb-2">
|
||||
<span className="px-2 text-custom-sidebar-text-200">{user?.email}</span>
|
||||
{profileLinks(workspaceSlug?.toString() ?? "", user?.id ?? "").map(
|
||||
(link, index) => (
|
||||
<Menu.Item key={index} as="button" type="button">
|
||||
<Link href={link.link}>
|
||||
<a className="flex w-full items-center gap-2 rounded px-2 py-1 hover:bg-custom-sidebar-background-80">
|
||||
<Icon iconName={link.icon} className="!text-base" />
|
||||
<Icon iconName={link.icon} className="!text-lg !leading-5" />
|
||||
{link.name}
|
||||
</a>
|
||||
</Link>
|
||||
@ -287,7 +285,7 @@ export const WorkspaceSidebarDropdown = () => {
|
||||
className="flex w-full items-center gap-2 rounded px-2 py-1 hover:bg-custom-sidebar-background-80"
|
||||
onClick={handleSignOut}
|
||||
>
|
||||
<Icon iconName="logout" className="!text-base" />
|
||||
<Icon iconName="logout" className="!text-lg !leading-5" />
|
||||
Sign out
|
||||
</Menu.Item>
|
||||
</div>
|
||||
|
@ -63,7 +63,7 @@ const ProjectIssues: NextPage = () => {
|
||||
<IssuesFilterView />
|
||||
<SecondaryButton
|
||||
onClick={() => setAnalyticsModal(true)}
|
||||
className="!py-1.5 rounded-md font-normal text-custom-sidebar-text-200 border-custom-sidebar-border-200 hover:text-custom-text-100 hover:bg-custom-sidebar-background-90"
|
||||
className="!py-1.5 rounded-md font-normal text-custom-sidebar-text-200 border-custom-border-200 hover:text-custom-text-100 hover:bg-custom-sidebar-background-90"
|
||||
outline
|
||||
>
|
||||
Analytics
|
||||
@ -72,7 +72,7 @@ const ProjectIssues: NextPage = () => {
|
||||
<Link href={`/${workspaceSlug}/projects/${projectId}/inbox/${inboxList?.[0]?.id}`}>
|
||||
<a>
|
||||
<SecondaryButton
|
||||
className="relative !py-1.5 rounded-md font-normal text-custom-sidebar-text-200 border-custom-sidebar-border-200 hover:text-custom-text-100 hover:bg-custom-sidebar-background-90"
|
||||
className="relative !py-1.5 rounded-md font-normal text-custom-sidebar-text-200 border-custom-border-200 hover:text-custom-text-100 hover:bg-custom-sidebar-background-90"
|
||||
outline
|
||||
>
|
||||
<span>Inbox</span>
|
||||
@ -97,6 +97,7 @@ const ProjectIssues: NextPage = () => {
|
||||
</PrimaryButton>
|
||||
</div>
|
||||
}
|
||||
bg="secondary"
|
||||
>
|
||||
<AnalyticsProjectModal isOpen={analyticsModal} onClose={() => setAnalyticsModal(false)} />
|
||||
<div className="h-full w-full flex flex-col">
|
||||
|
Loading…
Reference in New Issue
Block a user