chore: resolved merge conflicts

This commit is contained in:
guru_sainath 2024-06-04 12:14:26 +05:30
commit af7fc035d1
208 changed files with 3688 additions and 1211 deletions

View File

@ -5,9 +5,11 @@ import { observer } from "mobx-react-lite";
import Link from "next/link";
import { ExternalLink, FileText, HelpCircle, MoveLeft } from "lucide-react";
import { Transition } from "@headlessui/react";
// ui
import { DiscordIcon, GithubIcon, Tooltip } from "@plane/ui";
// helpers
import { WEB_BASE_URL, cn } from "@/helpers/common.helper";
// hooks
import { WEB_BASE_URL } from "@/helpers/common.helper";
import { useTheme } from "@/hooks/store";
// assets
import packageJson from "package.json";
@ -42,9 +44,12 @@ export const HelpSection: FC = observer(() => {
return (
<div
className={`flex w-full items-center justify-between gap-1 self-baseline border-t border-custom-sidebar-border-200 bg-custom-sidebar-background-100 px-4 py-2 ${
isSidebarCollapsed ? "flex-col" : ""
}`}
className={cn(
"flex w-full items-center justify-between gap-1 self-baseline border-t border-custom-border-200 bg-custom-sidebar-background-100 px-4 h-28",
{
"flex-col h-auto py-1.5": isSidebarCollapsed,
}
)}
>
<div className={`flex items-center gap-1 ${isSidebarCollapsed ? "flex-col justify-center" : "w-full"}`}>
<Tooltip tooltipContent="Redirect to plane" position="right" className="ml-4" disabled={!isSidebarCollapsed}>

View File

@ -1,6 +1,6 @@
{
"name": "admin",
"version": "0.20.0",
"version": "0.21.0",
"private": true,
"scripts": {
"dev": "turbo run develop",

View File

@ -1,4 +1,4 @@
{
"name": "plane-api",
"version": "0.20.0"
"version": "0.21.0"
}

View File

@ -66,6 +66,7 @@ class CycleSerializer(BaseSerializer):
"external_source",
"external_id",
"progress_snapshot",
"logo_props",
# meta fields
"is_favorite",
"total_issues",

View File

@ -199,6 +199,7 @@ class ModuleSerializer(DynamicBaseSerializer):
"sort_order",
"external_source",
"external_id",
"logo_props",
# computed fields
"is_favorite",
"total_issues",

View File

@ -39,6 +39,7 @@ class PageSerializer(BaseSerializer):
"created_by",
"updated_by",
"view_props",
"logo_props",
]
read_only_fields = [
"workspace",

View File

@ -231,6 +231,7 @@ class CycleViewSet(BaseViewSet):
"external_source",
"external_id",
"progress_snapshot",
"logo_props",
# meta fields
"is_favorite",
"total_issues",
@ -356,6 +357,7 @@ class CycleViewSet(BaseViewSet):
"external_source",
"external_id",
"progress_snapshot",
"logo_props",
# meta fields
"is_favorite",
"total_issues",
@ -403,6 +405,7 @@ class CycleViewSet(BaseViewSet):
"external_source",
"external_id",
"progress_snapshot",
"logo_props",
# meta fields
"is_favorite",
"cancelled_issues",
@ -496,6 +499,7 @@ class CycleViewSet(BaseViewSet):
"external_source",
"external_id",
"progress_snapshot",
"logo_props",
# meta fields
"is_favorite",
"total_issues",
@ -556,6 +560,7 @@ class CycleViewSet(BaseViewSet):
"external_id",
"progress_snapshot",
"sub_issues",
"logo_props",
# meta fields
"is_favorite",
"total_issues",

View File

@ -225,6 +225,7 @@ class ModuleViewSet(BaseViewSet):
"sort_order",
"external_source",
"external_id",
"logo_props",
# computed fields
"is_favorite",
"cancelled_issues",
@ -281,6 +282,7 @@ class ModuleViewSet(BaseViewSet):
"sort_order",
"external_source",
"external_id",
"logo_props",
# computed fields
"total_issues",
"is_favorite",
@ -465,6 +467,7 @@ class ModuleViewSet(BaseViewSet):
"sort_order",
"external_source",
"external_id",
"logo_props",
# computed fields
"is_favorite",
"cancelled_issues",

View File

@ -13,12 +13,9 @@ class InstanceSerializer(BaseSerializer):
model = Instance
exclude = [
"license_key",
"api_key",
"version",
]
read_only_fields = [
"id",
"instance_id",
"email",
"last_checked_at",
"is_setup_done",

View File

@ -49,8 +49,8 @@ class Command(BaseCommand):
instance_name="Plane Community Edition",
instance_id=secrets.token_hex(12),
license_key=None,
api_key=secrets.token_hex(8),
version=payload.get("version"),
current_version=payload.get("version"),
latest_version=payload.get("version"),
last_checked_at=timezone.now(),
user_count=payload.get("user_count", 0),
)

View File

@ -0,0 +1,106 @@
# Generated by Django 4.2.11 on 2024-05-31 10:46
from django.conf import settings
from django.db import migrations, models
import django.db.models.deletion
import uuid
class Migration(migrations.Migration):
dependencies = [
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
("license", "0001_initial"),
]
operations = [
migrations.AlterField(
model_name="instance",
name="instance_id",
field=models.CharField(max_length=255, unique=True),
),
migrations.RenameField(
model_name="instance",
old_name="version",
new_name="current_version",
),
migrations.RemoveField(
model_name="instance",
name="api_key",
),
migrations.AddField(
model_name="instance",
name="domain",
field=models.TextField(blank=True),
),
migrations.AddField(
model_name="instance",
name="latest_version",
field=models.CharField(blank=True, max_length=10, null=True),
),
migrations.AddField(
model_name="instance",
name="product",
field=models.CharField(default="plane-ce", max_length=50),
),
migrations.CreateModel(
name="ChangeLog",
fields=[
(
"created_at",
models.DateTimeField(
auto_now_add=True, verbose_name="Created At"
),
),
(
"updated_at",
models.DateTimeField(
auto_now=True, verbose_name="Last Modified At"
),
),
(
"id",
models.UUIDField(
db_index=True,
default=uuid.uuid4,
editable=False,
primary_key=True,
serialize=False,
unique=True,
),
),
("title", models.CharField(max_length=100)),
("description", models.TextField(blank=True)),
("version", models.CharField(max_length=100)),
("tags", models.JSONField(default=list)),
("release_date", models.DateTimeField(null=True)),
("is_release_candidate", models.BooleanField(default=False)),
(
"created_by",
models.ForeignKey(
null=True,
on_delete=django.db.models.deletion.SET_NULL,
related_name="%(class)s_created_by",
to=settings.AUTH_USER_MODEL,
verbose_name="Created By",
),
),
(
"updated_by",
models.ForeignKey(
null=True,
on_delete=django.db.models.deletion.SET_NULL,
related_name="%(class)s_updated_by",
to=settings.AUTH_USER_MODEL,
verbose_name="Last Modified By",
),
),
],
options={
"verbose_name": "Change Log",
"verbose_name_plural": "Change Logs",
"db_table": "changelogs",
"ordering": ("-created_at",),
},
),
]

View File

@ -1,3 +1,6 @@
# Python imports
from enum import Enum
# Django imports
from django.db import models
from django.conf import settings
@ -8,15 +11,23 @@ from plane.db.models import BaseModel
ROLE_CHOICES = ((20, "Admin"),)
class ProductTypes(Enum):
PLANE_CE = "plane-ce"
class Instance(BaseModel):
# General informations
# General information
instance_name = models.CharField(max_length=255)
whitelist_emails = models.TextField(blank=True, null=True)
instance_id = models.CharField(max_length=25, unique=True)
instance_id = models.CharField(max_length=255, unique=True)
license_key = models.CharField(max_length=256, null=True, blank=True)
api_key = models.CharField(max_length=16)
version = models.CharField(max_length=10)
# Instnace specifics
current_version = models.CharField(max_length=10)
latest_version = models.CharField(max_length=10, null=True, blank=True)
product = models.CharField(
max_length=50, default=ProductTypes.PLANE_CE.value
)
domain = models.TextField(blank=True)
# Instance specifics
last_checked_at = models.DateTimeField()
namespace = models.CharField(max_length=50, blank=True, null=True)
# telemetry and support
@ -70,3 +81,20 @@ class InstanceConfiguration(BaseModel):
verbose_name_plural = "Instance Configurations"
db_table = "instance_configurations"
ordering = ("-created_at",)
class ChangeLog(BaseModel):
"""Change Log model to store the release changelogs made in the application."""
title = models.CharField(max_length=100)
description = models.TextField(blank=True)
version = models.CharField(max_length=100)
tags = models.JSONField(default=list)
release_date = models.DateTimeField(null=True)
is_release_candidate = models.BooleanField(default=False)
class Meta:
verbose_name = "Change Log"
verbose_name_plural = "Change Logs"
db_table = "changelogs"
ordering = ("-created_at",)

View File

@ -1,6 +1,6 @@
{
"repository": "https://github.com/makeplane/plane.git",
"version": "0.20.0",
"version": "0.21.0",
"license": "AGPL-3.0",
"private": true,
"workspaces": [

View File

@ -1,6 +1,6 @@
{
"name": "@plane/constants",
"version": "0.20.0",
"version": "0.21.0",
"private": true,
"main": "./src/index.ts",
"exports": {

View File

@ -1,6 +1,6 @@
{
"name": "@plane/editor-core",
"version": "0.20.0",
"version": "0.21.0",
"description": "Core Editor that powers Plane",
"private": true,
"main": "./dist/index.mjs",

View File

@ -1,6 +1,6 @@
{
"name": "@plane/document-editor",
"version": "0.20.0",
"version": "0.21.0",
"description": "Package that powers Plane's Pages Editor",
"main": "./dist/index.mjs",
"module": "./dist/index.mjs",

View File

@ -1,6 +1,6 @@
{
"name": "@plane/editor-extensions",
"version": "0.20.0",
"version": "0.21.0",
"description": "Package that powers Plane's Editor with extensions",
"private": true,
"main": "./dist/index.mjs",

View File

@ -1,6 +1,6 @@
{
"name": "@plane/lite-text-editor",
"version": "0.20.0",
"version": "0.21.0",
"description": "Package that powers Plane's Comment Editor",
"private": true,
"main": "./dist/index.mjs",

View File

@ -1,6 +1,6 @@
{
"name": "@plane/rich-text-editor",
"version": "0.20.0",
"version": "0.21.0",
"description": "Rich Text Editor that powers Plane",
"private": true,
"main": "./dist/index.mjs",

View File

@ -1,7 +1,7 @@
{
"name": "eslint-config-custom",
"private": true,
"version": "0.20.0",
"version": "0.21.0",
"main": "index.js",
"license": "MIT",
"devDependencies": {},

View File

@ -1,6 +1,6 @@
{
"name": "tailwind-config-custom",
"version": "0.20.0",
"version": "0.21.0",
"description": "common tailwind configuration across monorepo",
"main": "index.js",
"private": true,

View File

@ -1,6 +1,6 @@
{
"name": "tsconfig",
"version": "0.20.0",
"version": "0.21.0",
"private": true,
"files": [
"base.json",

View File

@ -1,6 +1,6 @@
{
"name": "@plane/types",
"version": "0.20.0",
"version": "0.21.0",
"private": true,
"main": "./src/index.d.ts"
}

View File

@ -9,3 +9,15 @@ export type TPaginationInfo = {
per_page?: number;
total_results: number;
};
export type TLogoProps = {
in_use: "emoji" | "icon";
emoji?: {
value?: string;
url?: string;
};
icon?: {
name?: string;
color?: string;
};
};

View File

@ -19,8 +19,8 @@ export interface IInstance {
whitelist_emails: string | undefined;
instance_id: string | undefined;
license_key: string | undefined;
api_key: string | undefined;
version: string | undefined;
current_version: string | undefined;
latest_version: string | undefined;
last_checked_at: string | undefined;
namespace: string | undefined;
is_telemetry_enabled: boolean;

View File

@ -1,3 +1,4 @@
import { TLogoProps } from "./common";
import { EPageAccess } from "./enums";
export type TPage = {
@ -17,6 +18,7 @@ export type TPage = {
updated_at: Date | undefined;
updated_by: string | undefined;
workspace: string | undefined;
logo_props: TLogoProps | undefined;
};
// page filters

View File

@ -6,21 +6,10 @@ import type {
IUserMemberLite,
IWorkspace,
IWorkspaceLite,
TLogoProps,
TStateGroups,
} from "..";
export type TProjectLogoProps = {
in_use: "emoji" | "icon";
emoji?: {
value?: string;
url?: string;
};
icon?: {
name?: string;
color?: string;
};
};
export interface IProject {
archive_in: number;
archived_at: string | null;
@ -46,7 +35,7 @@ export interface IProject {
is_deployed: boolean;
is_favorite: boolean;
is_member: boolean;
logo_props: TProjectLogoProps;
logo_props: TLogoProps;
member_role: EUserProjectRoles | null;
members: IProjectMemberLite[];
name: string;

View File

@ -1,3 +1,4 @@
import { TLogoProps } from "./common";
import {
IIssueDisplayFilterOptions,
IIssueDisplayProperties,
@ -21,4 +22,5 @@ export interface IProjectView {
query_data: IIssueFilterOptions;
project: string;
workspace: string;
logo_props: TLogoProps | undefined;
}

View File

@ -2,7 +2,7 @@
"name": "@plane/ui",
"description": "UI components shared across multiple apps internally",
"private": true,
"version": "0.20.0",
"version": "0.21.0",
"main": "./dist/index.js",
"module": "./dist/index.mjs",
"types": "./dist/index.d.ts",
@ -26,6 +26,7 @@
"@popperjs/core": "^2.11.8",
"clsx": "^2.0.0",
"emoji-picker-react": "^4.5.16",
"lucide-react": "^0.379.0",
"react-color": "^2.19.3",
"react-dom": "^18.2.0",
"react-popper": "^2.3.0",

View File

@ -2,7 +2,7 @@ import * as React from "react";
export type TControlLink = React.AnchorHTMLAttributes<HTMLAnchorElement> & {
href: string;
onClick: () => void;
onClick: (event: React.MouseEvent<HTMLAnchorElement>) => void;
children: React.ReactNode;
target?: string;
disabled?: boolean;
@ -17,7 +17,7 @@ export const ControlLink = React.forwardRef<HTMLAnchorElement, TControlLink>((pr
const clickCondition = (event.metaKey || event.ctrlKey) && event.button === LEFT_CLICK_EVENT_CODE;
if (!clickCondition) {
event.preventDefault();
onClick();
onClick(event);
}
};

View File

@ -1,14 +1,15 @@
import React from "react";
import { forwardRef } from "react";
import React, { forwardRef } from "react";
import { MoreVertical } from "lucide-react";
// helpers
import { cn } from "../helpers";
interface IDragHandle {
isDragging: boolean;
className?: string;
disabled?: boolean;
}
export const DragHandle = forwardRef<HTMLButtonElement | null, IDragHandle>((props, ref) => {
const { isDragging, disabled = false } = props;
const { className, disabled = false } = props;
if (disabled) {
return <div className="w-[14px] h-[18px]" />;
@ -17,9 +18,10 @@ export const DragHandle = forwardRef<HTMLButtonElement | null, IDragHandle>((pro
return (
<button
type="button"
className={` p-[2px] flex flex-shrink-0 rounded bg-custom-background-90 text-custom-sidebar-text-200 group-hover:opacity-100 cursor-grab ${
isDragging ? "opacity-100" : "opacity-0"
}`}
className={cn(
"p-0.5 flex flex-shrink-0 rounded bg-custom-background-90 text-custom-sidebar-text-200 cursor-grab",
className
)}
onContextMenu={(e) => {
e.preventDefault();
e.stopPropagation();

View File

@ -0,0 +1,100 @@
import { Placement } from "@popperjs/core";
import { EmojiClickData, Theme } from "emoji-picker-react";
export enum EmojiIconPickerTypes {
EMOJI = "emoji",
ICON = "icon",
}
export const TABS_LIST = [
{
key: EmojiIconPickerTypes.EMOJI,
title: "Emojis",
},
{
key: EmojiIconPickerTypes.ICON,
title: "Icons",
},
];
export type TChangeHandlerProps =
| {
type: EmojiIconPickerTypes.EMOJI;
value: EmojiClickData;
}
| {
type: EmojiIconPickerTypes.ICON;
value: {
name: string;
color: string;
};
};
export type TCustomEmojiPicker = {
isOpen: boolean;
handleToggle: (value: boolean) => void;
buttonClassName?: string;
className?: string;
closeOnSelect?: boolean;
defaultIconColor?: string;
defaultOpen?: EmojiIconPickerTypes;
disabled?: boolean;
dropdownClassName?: string;
label: React.ReactNode;
onChange: (value: TChangeHandlerProps) => void;
placement?: Placement;
searchPlaceholder?: string;
theme?: Theme;
iconType?: "material" | "lucide";
};
export const DEFAULT_COLORS = ["#95999f", "#6d7b8a", "#5e6ad2", "#02b5ed", "#02b55c", "#f2be02", "#e57a00", "#f38e82"];
export type TIconsListProps = {
defaultColor: string;
onChange: (val: { name: string; color: string }) => void;
};
/**
* Adjusts the given hex color to ensure it has enough contrast.
* @param {string} hex - The hex color code input by the user.
* @returns {string} - The adjusted hex color code.
*/
export const adjustColorForContrast = (hex: string): string => {
// Ensure hex color is valid
if (!/^#([0-9A-F]{3}){1,2}$/i.test(hex)) {
throw new Error("Invalid hex color code");
}
// Convert hex to RGB
let r = 0,
g = 0,
b = 0;
if (hex.length === 4) {
r = parseInt(hex[1] + hex[1], 16);
g = parseInt(hex[2] + hex[2], 16);
b = parseInt(hex[3] + hex[3], 16);
} else if (hex.length === 7) {
r = parseInt(hex[1] + hex[2], 16);
g = parseInt(hex[3] + hex[4], 16);
b = parseInt(hex[5] + hex[6], 16);
}
// Calculate luminance
const luminance = (0.299 * r + 0.587 * g + 0.114 * b) / 255;
// If the color is too light, darken it
if (luminance > 0.5) {
r = Math.max(0, r - 50);
g = Math.max(0, g - 50);
b = Math.max(0, b - 50);
}
// Convert RGB back to hex
const toHex = (value: number): string => {
const hex = value.toString(16);
return hex.length === 1 ? "0" + hex : hex;
};
return `#${toHex(r)}${toHex(g)}${toHex(b)}`;
};

View File

@ -0,0 +1,135 @@
import React, { useRef, useState } from "react";
import { usePopper } from "react-popper";
import { Popover, Tab } from "@headlessui/react";
import EmojiPicker from "emoji-picker-react";
// helpers
import { cn } from "../../helpers";
// hooks
import useOutsideClickDetector from "../hooks/use-outside-click-detector";
import { LucideIconsList } from "./lucide-icons-list";
// helpers
import { EmojiIconPickerTypes, TABS_LIST, TCustomEmojiPicker } from "./emoji-icon-helper";
export const EmojiIconPicker: React.FC<TCustomEmojiPicker> = (props) => {
const {
isOpen,
handleToggle,
buttonClassName,
className,
closeOnSelect = true,
defaultIconColor = "#6d7b8a",
defaultOpen = EmojiIconPickerTypes.EMOJI,
disabled = false,
dropdownClassName,
label,
onChange,
placement = "bottom-start",
searchPlaceholder = "Search",
theme,
} = props;
// refs
const containerRef = useRef<HTMLDivElement>(null);
const [referenceElement, setReferenceElement] = useState<HTMLButtonElement | null>(null);
const [popperElement, setPopperElement] = useState<HTMLDivElement | null>(null);
// popper-js
const { styles, attributes } = usePopper(referenceElement, popperElement, {
placement,
modifiers: [
{
name: "preventOverflow",
options: {
padding: 20,
},
},
],
});
// close dropdown on outside click
useOutsideClickDetector(containerRef, () => handleToggle(false));
return (
<Popover as="div" className={cn("relative", className)}>
<>
<Popover.Button as={React.Fragment}>
<button
type="button"
ref={setReferenceElement}
className={cn("outline-none", buttonClassName)}
disabled={disabled}
onClick={() => handleToggle(!isOpen)}
>
{label}
</button>
</Popover.Button>
{isOpen && (
<Popover.Panel className="fixed z-10" static>
<div
ref={setPopperElement}
style={styles.popper}
{...attributes.popper}
className={cn(
"w-80 bg-custom-background-100 rounded-md border-[0.5px] border-custom-border-300 overflow-hidden",
dropdownClassName
)}
>
<Tab.Group
ref={containerRef}
as="div"
className="h-full w-full flex flex-col overflow-hidden"
defaultIndex={TABS_LIST.findIndex((tab) => tab.key === defaultOpen)}
>
<Tab.List as="div" className="grid grid-cols-2 gap-1 p-2">
{TABS_LIST.map((tab) => (
<Tab
key={tab.key}
className={({ selected }) =>
cn("py-1 text-sm rounded border border-custom-border-200", {
"bg-custom-background-80": selected,
"hover:bg-custom-background-90 focus:bg-custom-background-90": !selected,
})
}
>
{tab.title}
</Tab>
))}
</Tab.List>
<Tab.Panels as="div" className="h-full w-full overflow-y-auto">
<Tab.Panel>
<EmojiPicker
onEmojiClick={(val) => {
onChange({
type: EmojiIconPickerTypes.EMOJI,
value: val,
});
if (closeOnSelect) close();
}}
height="20rem"
width="100%"
theme={theme}
searchPlaceholder={searchPlaceholder}
previewConfig={{
showPreview: false,
}}
/>
</Tab.Panel>
<Tab.Panel className="h-80 w-full">
<LucideIconsList
defaultColor={defaultIconColor}
onChange={(val) => {
onChange({
type: EmojiIconPickerTypes.ICON,
value: val,
});
if (closeOnSelect) close();
}}
/>
</Tab.Panel>
</Tab.Panels>
</Tab.Group>
</div>
</Popover.Panel>
)}
</>
</Popover>
);
};

View File

@ -1,63 +1,23 @@
import React, { useState } from "react";
import React, { useRef, useState } from "react";
import { usePopper } from "react-popper";
import EmojiPicker, { EmojiClickData, Theme } from "emoji-picker-react";
import EmojiPicker from "emoji-picker-react";
import { Popover, Tab } from "@headlessui/react";
import { Placement } from "@popperjs/core";
// components
import { IconsList } from "./icons-list";
// helpers
import { cn } from "../../helpers";
export enum EmojiIconPickerTypes {
EMOJI = "emoji",
ICON = "icon",
}
type TChangeHandlerProps =
| {
type: EmojiIconPickerTypes.EMOJI;
value: EmojiClickData;
}
| {
type: EmojiIconPickerTypes.ICON;
value: {
name: string;
color: string;
};
};
export type TCustomEmojiPicker = {
buttonClassName?: string;
className?: string;
closeOnSelect?: boolean;
defaultIconColor?: string;
defaultOpen?: EmojiIconPickerTypes;
disabled?: boolean;
dropdownClassName?: string;
label: React.ReactNode;
onChange: (value: TChangeHandlerProps) => void;
placement?: Placement;
searchPlaceholder?: string;
theme?: Theme;
};
const TABS_LIST = [
{
key: EmojiIconPickerTypes.EMOJI,
title: "Emojis",
},
{
key: EmojiIconPickerTypes.ICON,
title: "Icons",
},
];
// hooks
import useOutsideClickDetector from "../hooks/use-outside-click-detector";
import { EmojiIconPickerTypes, TABS_LIST, TCustomEmojiPicker } from "./emoji-icon-helper";
export const CustomEmojiIconPicker: React.FC<TCustomEmojiPicker> = (props) => {
const {
isOpen,
handleToggle,
buttonClassName,
className,
closeOnSelect = true,
defaultIconColor = "#5f5f5f",
defaultIconColor = "#6d7b8a",
defaultOpen = EmojiIconPickerTypes.EMOJI,
disabled = false,
dropdownClassName,
@ -68,6 +28,7 @@ export const CustomEmojiIconPicker: React.FC<TCustomEmojiPicker> = (props) => {
theme,
} = props;
// refs
const containerRef = useRef<HTMLDivElement>(null);
const [referenceElement, setReferenceElement] = useState<HTMLButtonElement | null>(null);
const [popperElement, setPopperElement] = useState<HTMLDivElement | null>(null);
// popper-js
@ -83,21 +44,25 @@ export const CustomEmojiIconPicker: React.FC<TCustomEmojiPicker> = (props) => {
],
});
// close dropdown on outside click
useOutsideClickDetector(containerRef, () => handleToggle(false));
return (
<Popover as="div" className={cn("relative", className)}>
{({ close }) => (
<>
<Popover.Button as={React.Fragment}>
<button
type="button"
ref={setReferenceElement}
className={cn("outline-none", buttonClassName)}
disabled={disabled}
>
{label}
</button>
</Popover.Button>
<Popover.Panel className="fixed z-10">
<>
<Popover.Button as={React.Fragment}>
<button
type="button"
ref={setReferenceElement}
className={cn("outline-none", buttonClassName)}
disabled={disabled}
onClick={() => handleToggle(!isOpen)}
>
{label}
</button>
</Popover.Button>
{isOpen && (
<Popover.Panel className="fixed z-10" static>
<div
ref={setPopperElement}
style={styles.popper}
@ -108,6 +73,7 @@ export const CustomEmojiIconPicker: React.FC<TCustomEmojiPicker> = (props) => {
)}
>
<Tab.Group
ref={containerRef}
as="div"
className="h-full w-full flex flex-col overflow-hidden"
defaultIndex={TABS_LIST.findIndex((tab) => tab.key === defaultOpen)}
@ -162,8 +128,8 @@ export const CustomEmojiIconPicker: React.FC<TCustomEmojiPicker> = (props) => {
</Tab.Group>
</div>
</Popover.Panel>
</>
)}
)}
</>
</Popover>
);
};

View File

@ -3,15 +3,11 @@ import React, { useEffect, useState } from "react";
import { Input } from "../form-fields";
// helpers
import { cn } from "../../helpers";
// constants
import { DEFAULT_COLORS, TIconsListProps, adjustColorForContrast } from "./emoji-icon-helper";
// icons
import { MATERIAL_ICONS_LIST } from "./icons";
type TIconsListProps = {
defaultColor: string;
onChange: (val: { name: string; color: string }) => void;
};
const DEFAULT_COLORS = ["#ff6b00", "#8cc1ff", "#fcbe1d", "#18904f", "#adf672", "#05c3ff", "#5f5f5f"];
import { InfoIcon } from "../icons";
import { Search } from "lucide-react";
export const IconsList: React.FC<TIconsListProps> = (props) => {
const { defaultColor, onChange } = props;
@ -19,6 +15,8 @@ export const IconsList: React.FC<TIconsListProps> = (props) => {
const [activeColor, setActiveColor] = useState(defaultColor);
const [showHexInput, setShowHexInput] = useState(false);
const [hexValue, setHexValue] = useState("");
const [isInputFocused, setIsInputFocused] = useState(false);
const [query, setQuery] = useState("");
useEffect(() => {
if (DEFAULT_COLORS.includes(defaultColor.toLowerCase())) setShowHexInput(false);
@ -28,11 +26,28 @@ export const IconsList: React.FC<TIconsListProps> = (props) => {
}
}, [defaultColor]);
const filteredArray = MATERIAL_ICONS_LIST.filter((icon) => icon.name.toLowerCase().includes(query.toLowerCase()));
return (
<>
<div className="grid grid-cols-8 gap-2 items-center justify-items-center px-2.5 h-9">
<div className="flex items-center px-2 py-[15px] w-full ">
<div
className={`relative flex items-center gap-2 bg-custom-background-90 h-10 rounded-lg w-full px-[30px] border ${isInputFocused ? "border-custom-primary-100" : "border-transparent"}`}
onFocus={() => setIsInputFocused(true)}
onBlur={() => setIsInputFocused(false)}
>
<Search className="absolute left-2.5 bottom-3 h-3.5 w-3.5 text-custom-text-400" />
<Input
placeholder="Search"
value={query}
onChange={(e) => setQuery(e.target.value)}
className="text-[1rem] border-none p-0 h-full w-full "
/>
</div>
</div>
<div className="grid grid-cols-9 gap-2 items-center justify-items-center px-2.5 py-1 h-9">
{showHexInput ? (
<div className="col-span-7 flex items-center gap-1 justify-self-stretch ml-2">
<div className="col-span-8 flex items-center gap-1 justify-self-stretch ml-2">
<span
className="h-4 w-4 flex-shrink-0 rounded-full mr-1"
style={{
@ -47,7 +62,7 @@ export const IconsList: React.FC<TIconsListProps> = (props) => {
onChange={(e) => {
const value = e.target.value;
setHexValue(value);
if (/^[0-9A-Fa-f]{6}$/.test(value)) setActiveColor(`#${value}`);
if (/^[0-9A-Fa-f]{6}$/.test(value)) setActiveColor(adjustColorForContrast(`#${value}`));
}}
className="flex-grow pl-0 text-xs text-custom-text-200"
mode="true-transparent"
@ -59,7 +74,7 @@ export const IconsList: React.FC<TIconsListProps> = (props) => {
<button
key={curCol}
type="button"
className="grid place-items-center"
className="grid place-items-center size-5"
onClick={() => {
setActiveColor(curCol);
setHexValue(curCol.slice(1, 7));
@ -86,12 +101,16 @@ export const IconsList: React.FC<TIconsListProps> = (props) => {
)}
</button>
</div>
<div className="grid grid-cols-8 gap-2 px-2.5 justify-items-center mt-2">
{MATERIAL_ICONS_LIST.map((icon) => (
<div className="flex items-center gap-2 w-full pl-4 pr-3 py-1 h-6">
<InfoIcon className="h-3 w-3" />
<p className="text-xs"> Colors will be adjusted to ensure sufficient contrast.</p>
</div>
<div className="grid grid-cols-8 gap-1 px-2.5 justify-items-center mt-2">
{filteredArray.map((icon) => (
<button
key={icon.name}
type="button"
className="h-6 w-6 select-none text-lg grid place-items-center rounded hover:bg-custom-background-80"
className="h-9 w-9 select-none text-lg grid place-items-center rounded hover:bg-custom-background-80"
onClick={() => {
onChange({
name: icon.name,
@ -99,7 +118,10 @@ export const IconsList: React.FC<TIconsListProps> = (props) => {
});
}}
>
<span style={{ color: activeColor }} className="material-symbols-rounded text-base">
<span
style={{ color: activeColor }}
className="material-symbols-rounded !text-[1.25rem] !leading-[1.25rem]"
>
{icon.name}
</span>
</button>

View File

@ -1,3 +1,156 @@
import {
Activity,
Airplay,
AlertCircle,
AlertOctagon,
AlertTriangle,
AlignCenter,
AlignJustify,
AlignLeft,
AlignRight,
Anchor,
Aperture,
Archive,
ArrowDown,
ArrowLeft,
ArrowRight,
ArrowUp,
AtSign,
Award,
BarChart,
BarChart2,
Battery,
BatteryCharging,
Bell,
BellOff,
Book,
Bookmark,
BookOpen,
Box,
Briefcase,
Calendar,
Camera,
CameraOff,
Cast,
Check,
CheckCircle,
CheckSquare,
ChevronDown,
ChevronLeft,
ChevronRight,
ChevronUp,
Clipboard,
Clock,
Cloud,
CloudDrizzle,
CloudLightning,
CloudOff,
CloudRain,
CloudSnow,
Code,
Codepen,
Codesandbox,
Coffee,
Columns,
Command,
Compass,
Copy,
CornerDownLeft,
CornerDownRight,
CornerLeftDown,
CornerLeftUp,
CornerRightDown,
CornerRightUp,
CornerUpLeft,
CornerUpRight,
Cpu,
CreditCard,
Crop,
Crosshair,
Database,
Delete,
Disc,
Divide,
DivideCircle,
DivideSquare,
DollarSign,
Download,
DownloadCloud,
Dribbble,
Droplet,
Edit,
Edit2,
Edit3,
ExternalLink,
Eye,
EyeOff,
Facebook,
FastForward,
Feather,
Figma,
File,
FileMinus,
FilePlus,
FileText,
Film,
Filter,
Flag,
Folder,
FolderMinus,
FolderPlus,
Framer,
Frown,
Gift,
GitBranch,
GitCommit,
GitMerge,
GitPullRequest,
Github,
Gitlab,
Globe,
Grid,
HardDrive,
Hash,
Headphones,
Heart,
HelpCircle,
Hexagon,
Home,
Image,
Inbox,
Info,
Instagram,
Italic,
Key,
Layers,
Layout,
LifeBuoy,
Link,
Link2,
Linkedin,
List,
Loader,
Lock,
LogIn,
LogOut,
Mail,
Map,
MapPin,
Maximize,
Maximize2,
Meh,
Menu,
MessageCircle,
MessageSquare,
Mic,
MicOff,
Minimize,
Minimize2,
Minus,
MinusCircle,
MinusSquare,
} from "lucide-react";
export const MATERIAL_ICONS_LIST = [
{
name: "search",
@ -603,3 +756,156 @@ export const MATERIAL_ICONS_LIST = [
name: "skull",
},
];
export const LUCIDE_ICONS_LIST = [
{ name: "Activity", element: Activity },
{ name: "Airplay", element: Airplay },
{ name: "AlertCircle", element: AlertCircle },
{ name: "AlertOctagon", element: AlertOctagon },
{ name: "AlertTriangle", element: AlertTriangle },
{ name: "AlignCenter", element: AlignCenter },
{ name: "AlignJustify", element: AlignJustify },
{ name: "AlignLeft", element: AlignLeft },
{ name: "AlignRight", element: AlignRight },
{ name: "Anchor", element: Anchor },
{ name: "Aperture", element: Aperture },
{ name: "Archive", element: Archive },
{ name: "ArrowDown", element: ArrowDown },
{ name: "ArrowLeft", element: ArrowLeft },
{ name: "ArrowRight", element: ArrowRight },
{ name: "ArrowUp", element: ArrowUp },
{ name: "AtSign", element: AtSign },
{ name: "Award", element: Award },
{ name: "BarChart", element: BarChart },
{ name: "BarChart2", element: BarChart2 },
{ name: "Battery", element: Battery },
{ name: "BatteryCharging", element: BatteryCharging },
{ name: "Bell", element: Bell },
{ name: "BellOff", element: BellOff },
{ name: "Book", element: Book },
{ name: "Bookmark", element: Bookmark },
{ name: "BookOpen", element: BookOpen },
{ name: "Box", element: Box },
{ name: "Briefcase", element: Briefcase },
{ name: "Calendar", element: Calendar },
{ name: "Camera", element: Camera },
{ name: "CameraOff", element: CameraOff },
{ name: "Cast", element: Cast },
{ name: "Check", element: Check },
{ name: "CheckCircle", element: CheckCircle },
{ name: "CheckSquare", element: CheckSquare },
{ name: "ChevronDown", element: ChevronDown },
{ name: "ChevronLeft", element: ChevronLeft },
{ name: "ChevronRight", element: ChevronRight },
{ name: "ChevronUp", element: ChevronUp },
{ name: "Clipboard", element: Clipboard },
{ name: "Clock", element: Clock },
{ name: "Cloud", element: Cloud },
{ name: "CloudDrizzle", element: CloudDrizzle },
{ name: "CloudLightning", element: CloudLightning },
{ name: "CloudOff", element: CloudOff },
{ name: "CloudRain", element: CloudRain },
{ name: "CloudSnow", element: CloudSnow },
{ name: "Code", element: Code },
{ name: "Codepen", element: Codepen },
{ name: "Codesandbox", element: Codesandbox },
{ name: "Coffee", element: Coffee },
{ name: "Columns", element: Columns },
{ name: "Command", element: Command },
{ name: "Compass", element: Compass },
{ name: "Copy", element: Copy },
{ name: "CornerDownLeft", element: CornerDownLeft },
{ name: "CornerDownRight", element: CornerDownRight },
{ name: "CornerLeftDown", element: CornerLeftDown },
{ name: "CornerLeftUp", element: CornerLeftUp },
{ name: "CornerRightDown", element: CornerRightDown },
{ name: "CornerRightUp", element: CornerRightUp },
{ name: "CornerUpLeft", element: CornerUpLeft },
{ name: "CornerUpRight", element: CornerUpRight },
{ name: "Cpu", element: Cpu },
{ name: "CreditCard", element: CreditCard },
{ name: "Crop", element: Crop },
{ name: "Crosshair", element: Crosshair },
{ name: "Database", element: Database },
{ name: "Delete", element: Delete },
{ name: "Disc", element: Disc },
{ name: "Divide", element: Divide },
{ name: "DivideCircle", element: DivideCircle },
{ name: "DivideSquare", element: DivideSquare },
{ name: "DollarSign", element: DollarSign },
{ name: "Download", element: Download },
{ name: "DownloadCloud", element: DownloadCloud },
{ name: "Dribbble", element: Dribbble },
{ name: "Droplet", element: Droplet },
{ name: "Edit", element: Edit },
{ name: "Edit2", element: Edit2 },
{ name: "Edit3", element: Edit3 },
{ name: "ExternalLink", element: ExternalLink },
{ name: "Eye", element: Eye },
{ name: "EyeOff", element: EyeOff },
{ name: "Facebook", element: Facebook },
{ name: "FastForward", element: FastForward },
{ name: "Feather", element: Feather },
{ name: "Figma", element: Figma },
{ name: "File", element: File },
{ name: "FileMinus", element: FileMinus },
{ name: "FilePlus", element: FilePlus },
{ name: "FileText", element: FileText },
{ name: "Film", element: Film },
{ name: "Filter", element: Filter },
{ name: "Flag", element: Flag },
{ name: "Folder", element: Folder },
{ name: "FolderMinus", element: FolderMinus },
{ name: "FolderPlus", element: FolderPlus },
{ name: "Framer", element: Framer },
{ name: "Frown", element: Frown },
{ name: "Gift", element: Gift },
{ name: "GitBranch", element: GitBranch },
{ name: "GitCommit", element: GitCommit },
{ name: "GitMerge", element: GitMerge },
{ name: "GitPullRequest", element: GitPullRequest },
{ name: "Github", element: Github },
{ name: "Gitlab", element: Gitlab },
{ name: "Globe", element: Globe },
{ name: "Grid", element: Grid },
{ name: "HardDrive", element: HardDrive },
{ name: "Hash", element: Hash },
{ name: "Headphones", element: Headphones },
{ name: "Heart", element: Heart },
{ name: "HelpCircle", element: HelpCircle },
{ name: "Hexagon", element: Hexagon },
{ name: "Home", element: Home },
{ name: "Image", element: Image },
{ name: "Inbox", element: Inbox },
{ name: "Info", element: Info },
{ name: "Instagram", element: Instagram },
{ name: "Italic", element: Italic },
{ name: "Key", element: Key },
{ name: "Layers", element: Layers },
{ name: "Layout", element: Layout },
{ name: "LifeBuoy", element: LifeBuoy },
{ name: "Link", element: Link },
{ name: "Link2", element: Link2 },
{ name: "Linkedin", element: Linkedin },
{ name: "List", element: List },
{ name: "Loader", element: Loader },
{ name: "Lock", element: Lock },
{ name: "LogIn", element: LogIn },
{ name: "LogOut", element: LogOut },
{ name: "Mail", element: Mail },
{ name: "Map", element: Map },
{ name: "MapPin", element: MapPin },
{ name: "Maximize", element: Maximize },
{ name: "Maximize2", element: Maximize2 },
{ name: "Meh", element: Meh },
{ name: "Menu", element: Menu },
{ name: "MessageCircle", element: MessageCircle },
{ name: "MessageSquare", element: MessageSquare },
{ name: "Mic", element: Mic },
{ name: "MicOff", element: MicOff },
{ name: "Minimize", element: Minimize },
{ name: "Minimize2", element: Minimize2 },
{ name: "Minus", element: Minus },
{ name: "MinusCircle", element: MinusCircle },
{ name: "MinusSquare", element: MinusSquare },
];

View File

@ -1 +1,4 @@
export * from "./emoji-icon-picker-new";
export * from "./emoji-icon-picker";
export * from "./emoji-icon-helper";
export * from "./icons";

View File

@ -0,0 +1,128 @@
import React, { useEffect, useState } from "react";
// components
import { Input } from "../form-fields";
// helpers
import { cn } from "../../helpers";
import { DEFAULT_COLORS, TIconsListProps, adjustColorForContrast } from "./emoji-icon-helper";
// icons
import { InfoIcon } from "../icons";
// constants
import { LUCIDE_ICONS_LIST } from "./icons";
import { Search } from "lucide-react";
export const LucideIconsList: React.FC<TIconsListProps> = (props) => {
const { defaultColor, onChange } = props;
// states
const [activeColor, setActiveColor] = useState(defaultColor);
const [showHexInput, setShowHexInput] = useState(false);
const [hexValue, setHexValue] = useState("");
const [isInputFocused, setIsInputFocused] = useState(false);
const [query, setQuery] = useState("");
useEffect(() => {
if (DEFAULT_COLORS.includes(defaultColor.toLowerCase())) setShowHexInput(false);
else {
setHexValue(defaultColor.slice(1, 7));
setShowHexInput(true);
}
}, [defaultColor]);
const filteredArray = LUCIDE_ICONS_LIST.filter((icon) => icon.name.toLowerCase().includes(query.toLowerCase()));
return (
<>
<div className="flex items-center px-2 py-[15px] w-full ">
<div
className={`relative flex items-center gap-2 bg-custom-background-90 h-10 rounded-lg w-full px-[30px] border ${isInputFocused ? "border-custom-primary-100" : "border-transparent"}`}
onFocus={() => setIsInputFocused(true)}
onBlur={() => setIsInputFocused(false)}
>
<Search className="absolute left-2.5 bottom-3 h-3.5 w-3.5 text-custom-text-400" />
<Input
placeholder="Search"
value={query}
onChange={(e) => setQuery(e.target.value)}
className="text-[1rem] border-none p-0 h-full w-full "
/>
</div>
</div>
<div className="grid grid-cols-9 gap-2 items-center justify-items-center px-2.5 py-1 h-9">
{showHexInput ? (
<div className="col-span-8 flex items-center gap-1 justify-self-stretch ml-2">
<span
className="h-4 w-4 flex-shrink-0 rounded-full mr-1"
style={{
backgroundColor: `#${hexValue}`,
}}
/>
<span className="text-xs text-custom-text-300 flex-shrink-0">HEX</span>
<span className="text-xs text-custom-text-200 flex-shrink-0 -mr-1">#</span>
<Input
type="text"
value={hexValue}
onChange={(e) => {
const value = e.target.value;
setHexValue(value);
if (/^[0-9A-Fa-f]{6}$/.test(value)) setActiveColor(adjustColorForContrast(`#${value}`));
}}
className="flex-grow pl-0 text-xs text-custom-text-200"
mode="true-transparent"
autoFocus
/>
</div>
) : (
DEFAULT_COLORS.map((curCol) => (
<button
key={curCol}
type="button"
className="grid place-items-center size-5"
onClick={() => {
setActiveColor(curCol);
setHexValue(curCol.slice(1, 7));
}}
>
<span className="h-4 w-4 cursor-pointer rounded-full" style={{ backgroundColor: curCol }} />
</button>
))
)}
<button
type="button"
className={cn("grid place-items-center h-4 w-4 rounded-full border border-transparent", {
"border-custom-border-400": !showHexInput,
})}
onClick={() => {
setShowHexInput((prevData) => !prevData);
setHexValue(activeColor.slice(1, 7));
}}
>
{showHexInput ? (
<span className="conical-gradient h-4 w-4 rounded-full" />
) : (
<span className="text-custom-text-300 text-[0.6rem] grid place-items-center">#</span>
)}
</button>
</div>
<div className="flex items-center gap-2 w-full pl-4 pr-3 py-1 h-6">
<InfoIcon className="h-3 w-3" />
<p className="text-xs"> Colors will be adjusted to ensure sufficient contrast.</p>
</div>
<div className="grid grid-cols-8 gap-1 px-2.5 justify-items-center mt-2">
{filteredArray.map((icon) => (
<button
key={icon.name}
type="button"
className="h-9 w-9 select-none text-lg grid place-items-center rounded hover:bg-custom-background-80"
onClick={() => {
onChange({
name: icon.name,
color: activeColor,
});
}}
>
<icon.element style={{ color: activeColor }} className="size-4" />
</button>
))}
</div>
</>
);
};

View File

@ -3,15 +3,26 @@ import * as React from "react";
import { cn } from "../../helpers";
export interface CheckboxProps extends React.InputHTMLAttributes<HTMLInputElement> {
intermediate?: boolean;
className?: string;
containerClassName?: string;
iconClassName?: string;
indeterminate?: boolean;
}
const Checkbox = React.forwardRef<HTMLInputElement, CheckboxProps>((props, ref) => {
const { id, name, checked, intermediate = false, disabled, className = "", ...rest } = props;
const {
id,
name,
checked,
indeterminate = false,
disabled,
containerClassName,
iconClassName,
className,
...rest
} = props;
return (
<div className={cn("relative w-full flex gap-2", className)}>
<div className={cn("relative flex-shrink-0 flex gap-2", containerClassName)}>
<input
id={id}
ref={ref}
@ -19,22 +30,27 @@ const Checkbox = React.forwardRef<HTMLInputElement, CheckboxProps>((props, ref)
name={name}
checked={checked}
className={cn(
"appearance-none shrink-0 w-4 h-4 border rounded-[3px] focus:outline-1 focus:outline-offset-4 focus:outline-custom-primary-50",
"appearance-none shrink-0 size-4 border rounded-[3px] focus:outline-1 focus:outline-offset-4 focus:outline-custom-primary-50 cursor-pointer",
{
"border-custom-border-200 bg-custom-background-80 cursor-not-allowed": disabled,
"cursor-pointer border-custom-border-300 hover:border-custom-border-400 bg-white": !disabled,
"border-custom-primary-40 bg-custom-primary-100 hover:bg-custom-primary-200":
!disabled && (checked || intermediate),
}
"border-custom-border-300 hover:border-custom-border-400 bg-transparent": !disabled,
"border-custom-primary-40 hover:border-custom-primary-40 bg-custom-primary-100 hover:bg-custom-primary-200":
!disabled && (checked || indeterminate),
},
className
)}
disabled={disabled}
{...rest}
/>
<svg
className={cn("absolute w-4 h-4 p-0.5 pointer-events-none outline-none hidden stroke-white", {
block: checked,
"stroke-custom-text-400 opacity-40": disabled,
})}
className={cn(
"absolute top-1/2 left-1/2 -translate-x-1/2 -translate-y-1/2 size-4 p-0.5 pointer-events-none outline-none hidden stroke-white",
{
block: checked,
"stroke-custom-text-400 opacity-40": disabled,
},
iconClassName
)}
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 24 24"
fill="none"
@ -46,10 +62,14 @@ const Checkbox = React.forwardRef<HTMLInputElement, CheckboxProps>((props, ref)
<polyline points="20 6 9 17 4 12" />
</svg>
<svg
className={cn("absolute w-4 h-4 p-0.5 pointer-events-none outline-none stroke-white hidden", {
"stroke-custom-text-400 opacity-40": disabled,
block: intermediate && !checked,
})}
className={cn(
"absolute top-1/2 left-1/2 -translate-x-1/2 -translate-y-1/2 size-4 p-0.5 pointer-events-none outline-none stroke-white hidden",
{
"stroke-custom-text-400 opacity-40": disabled,
block: indeterminate && !checked,
},
iconClassName
)}
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 8 8"
fill="none"

View File

@ -19,3 +19,4 @@ export * from "./priority-icon";
export * from "./related-icon";
export * from "./side-panel-icon";
export * from "./transfer-icon";
export * from "./info-icon";

View File

@ -0,0 +1,21 @@
import * as React from "react";
import { ISvgIcons } from "./type";
export const InfoIcon: React.FC<ISvgIcons> = ({ className = "text-current", ...rest }) => (
<svg
viewBox="0 0 24 24"
className={`${className} stroke-2`}
stroke="currentColor"
fill="none"
stroke-width="2"
stroke-linecap="round"
stroke-linejoin="round"
xmlns="http://www.w3.org/2000/svg"
{...rest}
>
<circle cx="12" cy="12" r="10" />
<path d="M12 16v-4" />
<path d="M12 8h.01" />
</svg>
);

View File

@ -1,11 +1,11 @@
// helpers
import { TProjectLogoProps } from "@plane/types";
import { cn } from "@/helpers/common.helper";
// types
import { TLogoProps } from "@plane/types";
// helpers
import { cn } from "@/helpers/common.helper";
type Props = {
className?: string;
logo: TProjectLogoProps;
logo: TLogoProps;
};
export const ProjectLogo: React.FC<Props> = (props) => {

View File

@ -1,6 +1,6 @@
{
"name": "space",
"version": "0.20.0",
"version": "0.21.0",
"private": true,
"scripts": {
"dev": "turbo run develop",

View File

@ -1,4 +1,4 @@
import { TProjectLogoProps } from "@plane/types";
import { TLogoProps } from "@plane/types";
export type TWorkspaceDetails = {
name: string;
@ -19,7 +19,7 @@ export type TProjectDetails = {
identifier: string;
name: string;
cover_image: string | undefined;
logo_props: TProjectLogoProps;
logo_props: TLogoProps;
description: string;
};

View File

@ -1,10 +1,11 @@
import { observer } from "mobx-react";
// hooks
// icons
import { Contrast, LayoutGrid, Users } from "lucide-react";
// components
import { Logo } from "@/components/common";
// helpers
import { ProjectLogo } from "@/components/project";
import { truncateText } from "@/helpers/string.helper";
// hooks
import { useProject } from "@/hooks/store";
type Props = {
@ -29,7 +30,7 @@ export const CustomAnalyticsSidebarProjectsList: React.FC<Props> = observer((pro
<div key={projectId} className="w-full">
<div className="flex items-center gap-1 text-sm">
<div className="h-6 w-6 grid place-items-center">
<ProjectLogo logo={project.logo_props} />
<Logo logo={project.logo_props} />
</div>
<h5 className="flex items-center gap-1">
<p className="break-words">{truncateText(project.name, 20)}</p>

View File

@ -1,13 +1,13 @@
import { observer } from "mobx-react";
import { useRouter } from "next/router";
// hooks
import { ProjectLogo } from "@/components/project";
import { NETWORK_CHOICES } from "@/constants/project";
import { renderFormattedDate } from "@/helpers/date-time.helper";
import { useCycle, useMember, useModule, useProject } from "@/hooks/store";
// components
// helpers
import { Logo } from "@/components/common";
// constants
import { NETWORK_CHOICES } from "@/constants/project";
// helpers
import { renderFormattedDate } from "@/helpers/date-time.helper";
// hooks
import { useCycle, useMember, useModule, useProject } from "@/hooks/store";
export const CustomAnalyticsSidebarHeader = observer(() => {
const router = useRouter();
@ -84,7 +84,7 @@ export const CustomAnalyticsSidebarHeader = observer(() => {
<div className="flex items-center gap-1">
{projectDetails && (
<span className="h-6 w-6 grid place-items-center flex-shrink-0">
<ProjectLogo logo={projectDetails.logo_props} />
<Logo logo={projectDetails.logo_props} />
</span>
)}
<h4 className="break-words font-medium">{projectDetails?.name}</h4>

View File

@ -69,7 +69,7 @@ export const DeleteApiTokenModal: FC<Props> = (props) => {
<AlertModalCore
handleClose={handleClose}
handleSubmit={handleDeletion}
isDeleting={deleteLoading}
isSubmitting={deleteLoading}
isOpen={isOpen}
title="Delete API token"
content={

View File

@ -71,7 +71,7 @@ export const CommandPaletteProjectActions: React.FC<Props> = (props) => {
onSelect={() => {
closePalette();
setTrackElement("Command palette");
toggleCreatePageModal(true);
toggleCreatePageModal({ isOpen: true });
}}
className="focus:outline-none"
>

View File

@ -50,7 +50,7 @@ export const CommandPalette: FC = observer(() => {
toggleCreateIssueModal,
isCreateCycleModalOpen,
toggleCreateCycleModal,
isCreatePageModalOpen,
createPageModal,
toggleCreatePageModal,
isCreateProjectModalOpen,
toggleCreateProjectModal,
@ -150,7 +150,7 @@ export const CommandPalette: FC = observer(() => {
d: {
title: "Create a new page",
description: "Create a new page in the current project",
action: () => toggleCreatePageModal(true),
action: () => toggleCreatePageModal({ isOpen: true }),
},
m: {
title: "Create a new module",
@ -297,8 +297,9 @@ export const CommandPalette: FC = observer(() => {
<CreatePageModal
workspaceSlug={workspaceSlug.toString()}
projectId={projectId.toString()}
isModalOpen={isCreatePageModalOpen}
handleModalClose={() => toggleCreatePageModal(false)}
isModalOpen={createPageModal.isOpen}
pageAccess={createPageModal.pageAccess}
handleModalClose={() => toggleCreatePageModal({ isOpen: false })}
redirectionEnabled
/>
</>

View File

@ -3,3 +3,4 @@ export * from "./empty-state";
export * from "./latest-feature-block";
export * from "./breadcrumb-link";
export * from "./logo-spinner";
export * from "./logo";

View File

@ -0,0 +1,69 @@
import { FC } from "react";
// emoji-picker-react
import { Emoji } from "emoji-picker-react";
// import { icons } from "lucide-react";
import { TLogoProps } from "@plane/types";
// helpers
import { LUCIDE_ICONS_LIST } from "@plane/ui";
import { emojiCodeToUnicode } from "@/helpers/emoji.helper";
type Props = {
logo: TLogoProps;
size?: number;
type?: "lucide" | "material";
};
export const Logo: FC<Props> = (props) => {
const { logo, size = 16, type = "material" } = props;
// destructuring the logo object
const { in_use, emoji, icon } = logo;
// derived values
const value = in_use === "emoji" ? emoji?.value : icon?.name;
const color = icon?.color;
const lucideIcon = LUCIDE_ICONS_LIST.find((item) => item.name === value);
// if no value, return empty fragment
if (!value) return <></>;
// emoji
if (in_use === "emoji") {
return <Emoji unified={emojiCodeToUnicode(value)} size={size} />;
}
// icon
if (in_use === "icon") {
return (
<>
{type === "lucide" ? (
<>
{lucideIcon && (
<lucideIcon.element
style={{
color: color,
height: size,
width: size,
}}
/>
)}
</>
) : (
<span
className="material-symbols-rounded"
style={{
fontSize: size,
color: color,
scale: "115%",
}}
>
{value}
</span>
)}
</>
);
}
// if no value, return empty fragment
return <></>;
};

View File

@ -1,5 +1,6 @@
export * from "./filters";
export * from "./modals";
export * from "./multiple-select";
export * from "./sidebar";
export * from "./activity";
export * from "./favorite-star";

View File

@ -1,7 +1,7 @@
import React, { FC } from "react";
import Link from "next/link";
import { useRouter } from "next/router";
// ui
import { Tooltip } from "@plane/ui";
import { ControlLink, Tooltip } from "@plane/ui";
// helpers
import { cn } from "@/helpers/common.helper";
@ -14,6 +14,7 @@ interface IListItemProps {
actionableItems?: JSX.Element;
isMobile?: boolean;
parentRef: React.RefObject<HTMLDivElement>;
disableLink?: boolean;
className?: string;
}
@ -27,12 +28,22 @@ export const ListItem: FC<IListItemProps> = (props) => {
onItemClick,
isMobile = false,
parentRef,
disableLink = false,
className = "",
} = props;
// router
const router = useRouter();
// handlers
const handleControlLinkClick = (e: React.MouseEvent<HTMLAnchorElement>) => {
if (onItemClick) onItemClick(e);
else router.push(itemLink);
};
return (
<div ref={parentRef} className="relative">
<Link href={itemLink} onClick={onItemClick}>
<ControlLink href={itemLink} onClick={handleControlLinkClick} disabled={disableLink}>
<div
className={cn(
"group h-24 sm:h-[52px] flex w-full flex-col items-center justify-between gap-3 sm:gap-5 px-6 py-4 sm:py-0 text-sm border-b border-custom-border-200 bg-custom-background-100 hover:bg-custom-background-90 sm:flex-row",
@ -52,7 +63,7 @@ export const ListItem: FC<IListItemProps> = (props) => {
</div>
<span className="h-6 w-96 flex-shrink-0" />
</div>
</Link>
</ControlLink>
{actionableItems && (
<div className="absolute right-5 bottom-4 flex items-center gap-1.5">
<div className="relative flex items-center gap-4 sm:w-auto sm:flex-shrink-0 sm:justify-end">

View File

@ -1,4 +1,4 @@
import { AlertTriangle, LucideIcon } from "lucide-react";
import { AlertTriangle, Info, LucideIcon } from "lucide-react";
// ui
import { Button, TButtonVariant } from "@plane/ui";
// components
@ -6,14 +6,14 @@ import { EModalPosition, EModalWidth, ModalCore } from "@/components/core";
// helpers
import { cn } from "@/helpers/common.helper";
export type TModalVariant = "danger";
export type TModalVariant = "danger" | "primary";
type Props = {
content: React.ReactNode | string;
handleClose: () => void;
handleSubmit: () => Promise<void>;
hideIcon?: boolean;
isDeleting: boolean;
isSubmitting: boolean;
isOpen: boolean;
position?: EModalPosition;
primaryButtonText?: {
@ -28,14 +28,17 @@ type Props = {
const VARIANT_ICONS: Record<TModalVariant, LucideIcon> = {
danger: AlertTriangle,
primary: Info,
};
const BUTTON_VARIANTS: Record<TModalVariant, TButtonVariant> = {
danger: "danger",
primary: "primary",
};
const VARIANT_CLASSES: Record<TModalVariant, string> = {
danger: "bg-red-500/20 text-red-500",
primary: "bg-custom-primary-100/20 text-custom-primary-100",
};
export const AlertModalCore: React.FC<Props> = (props) => {
@ -44,7 +47,7 @@ export const AlertModalCore: React.FC<Props> = (props) => {
handleClose,
handleSubmit,
hideIcon = false,
isDeleting,
isSubmitting,
isOpen,
position = EModalPosition.CENTER,
primaryButtonText = {
@ -81,8 +84,8 @@ export const AlertModalCore: React.FC<Props> = (props) => {
<Button variant="neutral-primary" size="sm" onClick={handleClose}>
{secondaryButtonText}
</Button>
<Button variant={BUTTON_VARIANTS[variant]} size="sm" tabIndex={1} onClick={handleSubmit} loading={isDeleting}>
{isDeleting ? primaryButtonText.loading : primaryButtonText.default}
<Button variant={BUTTON_VARIANTS[variant]} size="sm" tabIndex={1} onClick={handleSubmit} loading={isSubmitting}>
{isSubmitting ? primaryButtonText.loading : primaryButtonText.default}
</Button>
</div>
</ModalCore>

View File

@ -179,7 +179,9 @@ export const GptAssistantPopover: React.FC<Props> = (props) => {
return (
<Popover as="div" className={`relative w-min text-left`}>
<Popover.Button as={Fragment}>
<button ref={setReferenceElement}>{button}</button>
<button ref={setReferenceElement} className="flex items-center">
{button}
</button>
</Popover.Button>
<Transition
show={isOpen}
@ -208,11 +210,7 @@ export const GptAssistantPopover: React.FC<Props> = (props) => {
{response !== "" && (
<div className="page-block-section max-h-[8rem] text-sm">
Response:
<RichTextReadOnlyEditor
initialValue={`<p>${response}</p>`}
containerClassName={response ? "-mx-3 -my-3" : ""}
ref={responseRef}
/>
<RichTextReadOnlyEditor initialValue={`<p>${response}</p>`} ref={responseRef} />
</div>
)}
{invalidResponse && (

View File

@ -0,0 +1,36 @@
// ui
import { Checkbox } from "@plane/ui";
// helpers
import { cn } from "@/helpers/common.helper";
// hooks
import { TSelectionHelper } from "@/hooks/use-multiple-select";
type Props = {
className?: string;
disabled?: boolean;
groupId: string;
id: string;
selectionHelpers: TSelectionHelper;
};
export const MultipleSelectEntityAction: React.FC<Props> = (props) => {
const { className, disabled = false, groupId, id, selectionHelpers } = props;
// derived values
const isSelected = selectionHelpers.getIsEntitySelected(id);
return (
<Checkbox
className={cn("!outline-none size-3.5", className)}
iconClassName="size-3"
onClick={(e) => {
e.stopPropagation();
selectionHelpers.handleEntityClick(e, id, groupId);
}}
checked={isSelected}
data-entity-group-id={groupId}
data-entity-id={id}
disabled={disabled}
readOnly
/>
);
};

View File

@ -0,0 +1,30 @@
// ui
import { Checkbox } from "@plane/ui";
// helpers
import { cn } from "@/helpers/common.helper";
// hooks
import { TSelectionHelper } from "@/hooks/use-multiple-select";
type Props = {
className?: string;
disabled?: boolean;
groupID: string;
selectionHelpers: TSelectionHelper;
};
export const MultipleSelectGroupAction: React.FC<Props> = (props) => {
const { className, disabled = false, groupID, selectionHelpers } = props;
// derived values
const groupSelectionStatus = selectionHelpers.isGroupSelected(groupID);
return (
<Checkbox
className={cn("size-3.5 !outline-none", className)}
iconClassName="size-3"
onClick={() => selectionHelpers.handleGroupClick(groupID)}
checked={groupSelectionStatus === "complete"}
indeterminate={groupSelectionStatus === "partial"}
disabled={disabled}
/>
);
};

View File

@ -0,0 +1,3 @@
export * from "./entity-select-action";
export * from "./group-select-action";
export * from "./select-group";

View File

@ -0,0 +1,22 @@
import { observer } from "mobx-react";
// hooks
import { TSelectionHelper, useMultipleSelect } from "@/hooks/use-multiple-select";
type Props = {
children: (helpers: TSelectionHelper) => React.ReactNode;
containerRef: React.MutableRefObject<HTMLElement | null>;
entities: Record<string, string[]>; // { groupID: entityIds[] }
};
export const MultipleSelectGroup: React.FC<Props> = observer((props) => {
const { children, containerRef, entities } = props;
const helpers = useMultipleSelect({
containerRef,
entities,
});
return <>{children(helpers)}</>;
});
MultipleSelectGroup.displayName = "MultipleSelectGroup";

View File

@ -56,21 +56,18 @@ const ProgressChart: React.FC<Props> = ({ distribution, startDate, endDate, tota
dates = eachDayOfInterval({ start, end });
}
const maxDates = 4;
const totalDates = dates.length;
if (dates.length === 0) return [];
if (totalDates <= maxDates) return dates.map((d) => renderFormattedDateWithoutYear(d));
else {
const interval = Math.ceil(totalDates / maxDates);
const limitedDates = [];
const formattedDates = dates.map((d) => renderFormattedDateWithoutYear(d));
const firstDate = formattedDates[0];
const lastDate = formattedDates[formattedDates.length - 1];
for (let i = 0; i < totalDates; i += interval) limitedDates.push(renderFormattedDateWithoutYear(dates[i]));
if (formattedDates.length <= 2) return [firstDate, lastDate];
if (!limitedDates.includes(renderFormattedDateWithoutYear(dates[totalDates - 1])))
limitedDates.push(renderFormattedDateWithoutYear(dates[totalDates - 1]));
const middleDateIndex = Math.floor(formattedDates.length / 2);
const middleDate = formattedDates[middleDateIndex];
return limitedDates;
}
return [firstDate, middleDate, lastDate];
};
return (

View File

@ -73,7 +73,7 @@ export const CycleDeleteModal: React.FC<ICycleDelete> = observer((props) => {
<AlertModalCore
handleClose={handleClose}
handleSubmit={formSubmit}
isDeleting={loader}
isSubmitting={loader}
isOpen={isOpen}
title="Delete Cycle"
content={

View File

@ -77,13 +77,18 @@ export const CyclesListItem: FC<TCyclesListItem> = observer((props) => {
}
};
// handlers
const handleArchivedCycleClick = (e: MouseEvent<HTMLAnchorElement>) => {
openCycleOverview(e);
};
const handleItemClick = cycleDetails.archived_at ? handleArchivedCycleClick : undefined;
return (
<ListItem
title={cycleDetails?.name ?? ""}
itemLink={`/${workspaceSlug}/projects/${projectId}/cycles/${cycleDetails.id}`}
onItemClick={(e) => {
if (cycleDetails.archived_at) openCycleOverview(e);
}}
onItemClick={handleItemClick}
className={className}
prependTitleElement={
<CircularProgressIndicator size={30} percentage={progress} strokeWidth={3}>

View File

@ -7,8 +7,8 @@ import { TRecentProjectsWidgetResponse } from "@plane/types";
// ui
import { Avatar, AvatarGroup } from "@plane/ui";
// components
import { Logo } from "@/components/common";
import { WidgetLoader, WidgetProps } from "@/components/dashboard/widgets";
import { ProjectLogo } from "@/components/project";
// constants
import { PROJECT_BACKGROUND_COLORS } from "@/constants/dashboard";
import { EUserWorkspaceRoles } from "@/constants/workspace";
@ -38,7 +38,7 @@ const ProjectListItem: React.FC<ProjectListItemProps> = observer((props) => {
className={`grid h-[3.375rem] w-[3.375rem] flex-shrink-0 place-items-center rounded border border-transparent ${randomBgColor}`}
>
<div className="grid h-7 w-7 place-items-center">
<ProjectLogo logo={projectDetails.logo_props} className="text-xl" />
<Logo logo={projectDetails.logo_props} size={20} />
</div>
</div>
<div className="flex-grow truncate">

View File

@ -1,7 +1,7 @@
import { Fragment, ReactNode, useRef, useState } from "react";
import { useTheme } from "next-themes";
import { usePopper } from "react-popper";
import { Check, ChevronDown, Search } from "lucide-react";
import { Check, ChevronDown, Search, SignalHigh } from "lucide-react";
import { Combobox } from "@headlessui/react";
// types
import { TIssuePriorities } from "@plane/types";
@ -26,7 +26,7 @@ type Props = TDropdownProps & {
highlightUrgent?: boolean;
onChange: (val: TIssuePriorities) => void;
onClose?: () => void;
value: TIssuePriorities;
value: TIssuePriorities | undefined;
};
type ButtonProps = {
@ -37,7 +37,8 @@ type ButtonProps = {
hideText?: boolean;
isActive?: boolean;
highlightUrgent: boolean;
priority: TIssuePriorities;
placeholder: string;
priority: TIssuePriorities | undefined;
showTooltip: boolean;
};
@ -49,6 +50,7 @@ const BorderButton = (props: ButtonProps) => {
hideIcon = false,
hideText = false,
highlightUrgent,
placeholder,
priority,
showTooltip,
} = props;
@ -75,7 +77,7 @@ const BorderButton = (props: ButtonProps) => {
<div
className={cn(
"h-full flex items-center gap-1.5 border-[0.5px] rounded text-xs px-2 py-0.5",
priorityClasses[priority],
priorityClasses[priority ?? "none"],
{
// compact the icons if text is hidden
"px-0.5": hideText,
@ -85,30 +87,33 @@ const BorderButton = (props: ButtonProps) => {
className
)}
>
{!hideIcon && (
<div
className={cn({
// highlight just the icon if text is visible and priority is urgent
"bg-red-600 p-1 rounded": priority === "urgent" && !hideText && highlightUrgent,
})}
>
<PriorityIcon
priority={priority}
size={12}
className={cn("flex-shrink-0", {
// increase the icon size if text is hidden
"h-3.5 w-3.5": hideText,
// centre align the icons if text is hidden
"translate-x-[0.0625rem]": hideText && priority === "high",
"translate-x-0.5": hideText && priority === "medium",
"translate-x-1": hideText && priority === "low",
// highlight the icon if priority is urgent
"text-white": priority === "urgent" && highlightUrgent,
{!hideIcon &&
(priority ? (
<div
className={cn({
// highlight just the icon if text is visible and priority is urgent
"bg-red-600 p-1 rounded": priority === "urgent" && !hideText && highlightUrgent,
})}
/>
</div>
)}
{!hideText && <span className="flex-grow truncate">{priorityDetails?.title}</span>}
>
<PriorityIcon
priority={priority}
size={12}
className={cn("flex-shrink-0", {
// increase the icon size if text is hidden
"h-3.5 w-3.5": hideText,
// centre align the icons if text is hidden
"translate-x-[0.0625rem]": hideText && priority === "high",
"translate-x-0.5": hideText && priority === "medium",
"translate-x-1": hideText && priority === "low",
// highlight the icon if priority is urgent
"text-white": priority === "urgent" && highlightUrgent,
})}
/>
</div>
) : (
<SignalHigh className="size-3" />
))}
{!hideText && <span className="flex-grow truncate">{priorityDetails?.title ?? placeholder}</span>}
{dropdownArrow && (
<ChevronDown className={cn("h-2.5 w-2.5 flex-shrink-0", dropdownArrowClassName)} aria-hidden="true" />
)}
@ -125,6 +130,7 @@ const BackgroundButton = (props: ButtonProps) => {
hideIcon = false,
hideText = false,
highlightUrgent,
placeholder,
priority,
showTooltip,
} = props;
@ -151,7 +157,7 @@ const BackgroundButton = (props: ButtonProps) => {
<div
className={cn(
"h-full flex items-center gap-1.5 rounded text-xs px-2 py-0.5",
priorityClasses[priority],
priorityClasses[priority ?? "none"],
{
// compact the icons if text is hidden
"px-0.5": hideText,
@ -161,30 +167,33 @@ const BackgroundButton = (props: ButtonProps) => {
className
)}
>
{!hideIcon && (
<div
className={cn({
// highlight just the icon if text is visible and priority is urgent
"bg-red-600 p-1 rounded": priority === "urgent" && !hideText && highlightUrgent,
})}
>
<PriorityIcon
priority={priority}
size={12}
className={cn("flex-shrink-0", {
// increase the icon size if text is hidden
"h-3.5 w-3.5": hideText,
// centre align the icons if text is hidden
"translate-x-[0.0625rem]": hideText && priority === "high",
"translate-x-0.5": hideText && priority === "medium",
"translate-x-1": hideText && priority === "low",
// highlight the icon if priority is urgent
"text-white": priority === "urgent" && highlightUrgent,
{!hideIcon &&
(priority ? (
<div
className={cn({
// highlight just the icon if text is visible and priority is urgent
"bg-red-600 p-1 rounded": priority === "urgent" && !hideText && highlightUrgent,
})}
/>
</div>
)}
{!hideText && <span className="flex-grow truncate">{priorityDetails?.title}</span>}
>
<PriorityIcon
priority={priority}
size={12}
className={cn("flex-shrink-0", {
// increase the icon size if text is hidden
"h-3.5 w-3.5": hideText,
// centre align the icons if text is hidden
"translate-x-[0.0625rem]": hideText && priority === "high",
"translate-x-0.5": hideText && priority === "medium",
"translate-x-1": hideText && priority === "low",
// highlight the icon if priority is urgent
"text-white": priority === "urgent" && highlightUrgent,
})}
/>
</div>
) : (
<SignalHigh className="size-3" />
))}
{!hideText && <span className="flex-grow truncate">{priorityDetails?.title ?? placeholder}</span>}
{dropdownArrow && (
<ChevronDown className={cn("h-2.5 w-2.5 flex-shrink-0", dropdownArrowClassName)} aria-hidden="true" />
)}
@ -202,6 +211,7 @@ const TransparentButton = (props: ButtonProps) => {
hideText = false,
isActive = false,
highlightUrgent,
placeholder,
priority,
showTooltip,
} = props;
@ -228,7 +238,7 @@ const TransparentButton = (props: ButtonProps) => {
<div
className={cn(
"h-full flex items-center gap-1.5 rounded text-xs px-2 py-0.5 hover:bg-custom-background-80",
priorityClasses[priority],
priorityClasses[priority ?? "none"],
{
// compact the icons if text is hidden
"px-0.5": hideText,
@ -239,30 +249,33 @@ const TransparentButton = (props: ButtonProps) => {
className
)}
>
{!hideIcon && (
<div
className={cn({
// highlight just the icon if text is visible and priority is urgent
"bg-red-600 p-1 rounded": priority === "urgent" && !hideText && highlightUrgent,
})}
>
<PriorityIcon
priority={priority}
size={12}
className={cn("flex-shrink-0", {
// increase the icon size if text is hidden
"h-3.5 w-3.5": hideText,
// centre align the icons if text is hidden
"translate-x-[0.0625rem]": hideText && priority === "high",
"translate-x-0.5": hideText && priority === "medium",
"translate-x-1": hideText && priority === "low",
// highlight the icon if priority is urgent
"text-white": priority === "urgent" && highlightUrgent,
{!hideIcon &&
(priority ? (
<div
className={cn({
// highlight just the icon if text is visible and priority is urgent
"bg-red-600 p-1 rounded": priority === "urgent" && !hideText && highlightUrgent,
})}
/>
</div>
)}
{!hideText && <span className="flex-grow truncate">{priorityDetails?.title}</span>}
>
<PriorityIcon
priority={priority}
size={12}
className={cn("flex-shrink-0", {
// increase the icon size if text is hidden
"h-3.5 w-3.5": hideText,
// centre align the icons if text is hidden
"translate-x-[0.0625rem]": hideText && priority === "high",
"translate-x-0.5": hideText && priority === "medium",
"translate-x-1": hideText && priority === "low",
// highlight the icon if priority is urgent
"text-white": priority === "urgent" && highlightUrgent,
})}
/>
</div>
) : (
<SignalHigh className="size-3" />
))}
{!hideText && <span className="flex-grow truncate">{priorityDetails?.title ?? placeholder}</span>}
{dropdownArrow && (
<ChevronDown className={cn("h-2.5 w-2.5 flex-shrink-0", dropdownArrowClassName)} aria-hidden="true" />
)}
@ -285,6 +298,7 @@ export const PriorityDropdown: React.FC<Props> = (props) => {
highlightUrgent = true,
onChange,
onClose,
placeholder = "Priority",
placement,
showTooltip = false,
tabIndex,
@ -400,6 +414,7 @@ export const PriorityDropdown: React.FC<Props> = (props) => {
dropdownArrow={dropdownArrow && !disabled}
dropdownArrowClassName={dropdownArrowClassName}
hideIcon={hideIcon}
placeholder={placeholder}
showTooltip={showTooltip}
hideText={BUTTON_VARIANTS_WITHOUT_TEXT.includes(buttonVariant)}
/>

View File

@ -6,7 +6,7 @@ import { Combobox } from "@headlessui/react";
// types
import { IProject } from "@plane/types";
// components
import { ProjectLogo } from "@/components/project";
import { Logo } from "@/components/common";
// helpers
import { cn } from "@/helpers/common.helper";
// hooks
@ -83,7 +83,7 @@ export const ProjectDropdown: React.FC<Props> = observer((props) => {
<div className="flex items-center gap-2">
{projectDetails && (
<span className="grid place-items-center flex-shrink-0 h-4 w-4">
<ProjectLogo logo={projectDetails?.logo_props} className="text-sm" />
<Logo logo={projectDetails?.logo_props} size={12} />
</span>
)}
<span className="flex-grow truncate">{projectDetails?.name}</span>
@ -157,7 +157,7 @@ export const ProjectDropdown: React.FC<Props> = observer((props) => {
>
{!hideIcon && selectedProject && (
<span className="grid place-items-center flex-shrink-0 h-4 w-4">
<ProjectLogo logo={selectedProject.logo_props} className="text-sm" />
<Logo logo={selectedProject.logo_props} size={12} />
</span>
)}
{BUTTON_VARIANTS_WITH_TEXT.includes(buttonVariant) && (

View File

@ -24,7 +24,8 @@ type Props = TDropdownProps & {
onChange: (val: string) => void;
onClose?: () => void;
projectId: string;
value: string;
showDefaultState?: boolean;
value: string | undefined;
};
export const StateDropdown: React.FC<Props> = observer((props) => {
@ -42,6 +43,7 @@ export const StateDropdown: React.FC<Props> = observer((props) => {
onClose,
placement,
projectId,
showDefaultState = true,
showTooltip = false,
tabIndex,
value,
@ -72,8 +74,8 @@ export const StateDropdown: React.FC<Props> = observer((props) => {
const { workspaceSlug } = useAppRouter();
const { fetchProjectStates, getProjectStates, getStateById } = useProjectState();
const statesList = getProjectStates(projectId);
const defaultStateList = statesList?.find((state) => state.default);
const stateValue = value ? value : defaultStateList?.id;
const defaultState = statesList?.find((state) => state.default);
const stateValue = value ?? (showDefaultState ? defaultState?.id : undefined);
const options = statesList?.map((state) => ({
value: state.id,
@ -170,7 +172,7 @@ export const StateDropdown: React.FC<Props> = observer((props) => {
{!hideIcon && (
<StateGroupIcon
stateGroup={selectedState?.group ?? "backlog"}
color={selectedState?.color}
color={selectedState?.color ?? "rgba(var(--color-text-300))"}
className="h-3 w-3 flex-shrink-0"
/>
)}

View File

@ -1,15 +1,16 @@
import { observer } from "mobx-react";
// hooks
// components
// helpers
import { cn } from "@/helpers/common.helper";
import { getDate, renderFormattedPayloadDate } from "@/helpers/date-time.helper";
// hooks
import { useIssueDetail } from "@/hooks/store";
// types
import { TSelectionHelper } from "@/hooks/use-multiple-select";
// constants
import { BLOCK_HEIGHT } from "../constants";
// components
import { ChartAddBlock, ChartDraggable } from "../helpers";
import { useGanttChart } from "../hooks";
// types
import { IBlockUpdateData, IGanttBlock } from "../types";
type Props = {
@ -21,6 +22,7 @@ type Props = {
enableBlockMove: boolean;
enableAddBlock: boolean;
ganttContainerRef: React.RefObject<HTMLDivElement>;
selectionHelpers: TSelectionHelper;
};
export const GanttChartBlock: React.FC<Props> = observer((props) => {
@ -33,6 +35,7 @@ export const GanttChartBlock: React.FC<Props> = observer((props) => {
enableBlockMove,
enableAddBlock,
ganttContainerRef,
selectionHelpers,
} = props;
// store hooks
const { updateActiveBlockId, isBlockActive } = useGanttChart();
@ -70,6 +73,10 @@ export const GanttChartBlock: React.FC<Props> = observer((props) => {
});
};
const isBlockSelected = selectionHelpers.getIsEntitySelected(block.id);
const isBlockFocused = selectionHelpers.getIsEntityActive(block.id);
const isBlockHoveredOn = isBlockActive(block.id);
return (
<div
key={`block-${block.id}`}
@ -80,10 +87,11 @@ export const GanttChartBlock: React.FC<Props> = observer((props) => {
>
<div
className={cn("relative h-full", {
"bg-custom-background-80": isBlockActive(block.id),
"rounded-l border border-r-0 border-custom-primary-70 hover:border-custom-primary-70": getIsIssuePeeked(
block.data.id
),
"rounded-l border border-r-0 border-custom-primary-70": getIsIssuePeeked(block.data.id),
"bg-custom-background-90": isBlockHoveredOn,
"bg-custom-primary-100/5 hover:bg-custom-primary-100/10": isBlockSelected,
"bg-custom-primary-100/10": isBlockSelected && isBlockHoveredOn,
"border border-r-0 border-custom-border-400": isBlockFocused,
})}
onMouseEnter={() => updateActiveBlockId(block.id)}
onMouseLeave={() => updateActiveBlockId(null)}

View File

@ -1,10 +1,12 @@
import { FC } from "react";
// components
import { HEADER_HEIGHT } from "../constants";
import { IBlockUpdateData, IGanttBlock } from "../types";
import { GanttChartBlock } from "./block";
// types
// hooks
import { TSelectionHelper } from "@/hooks/use-multiple-select";
// constants
import { HEADER_HEIGHT } from "../constants";
// types
import { IBlockUpdateData, IGanttBlock } from "../types";
// components
import { GanttChartBlock } from "./block";
export type GanttChartBlocksProps = {
itemsContainerWidth: number;
@ -17,6 +19,7 @@ export type GanttChartBlocksProps = {
enableAddBlock: boolean;
ganttContainerRef: React.RefObject<HTMLDivElement>;
showAllBlocks: boolean;
selectionHelpers: TSelectionHelper;
};
export const GanttChartBlocksList: FC<GanttChartBlocksProps> = (props) => {
@ -31,6 +34,7 @@ export const GanttChartBlocksList: FC<GanttChartBlocksProps> = (props) => {
enableAddBlock,
ganttContainerRef,
showAllBlocks,
selectionHelpers,
} = props;
return (
@ -56,6 +60,7 @@ export const GanttChartBlocksList: FC<GanttChartBlocksProps> = (props) => {
enableBlockMove={enableBlockMove}
enableAddBlock={enableAddBlock}
ganttContainerRef={ganttContainerRef}
selectionHelpers={selectionHelpers}
/>
);
})}

View File

@ -2,8 +2,8 @@ import { useEffect, useRef } from "react";
import { combine } from "@atlaskit/pragmatic-drag-and-drop/combine";
import { autoScrollForElements } from "@atlaskit/pragmatic-drag-and-drop-auto-scroll/element";
import { observer } from "mobx-react";
// hooks
// components
import { MultipleSelectGroup } from "@/components/core";
import {
BiWeekChartView,
DayChartView,
@ -18,8 +18,12 @@ import {
WeekChartView,
YearChartView,
} from "@/components/gantt-chart";
import { IssueBulkOperationsRoot } from "@/components/issues";
// helpers
import { cn } from "@/helpers/common.helper";
// constants
import { GANTT_SELECT_GROUP } from "../constants";
// hooks
import { useGanttChart } from "../hooks/use-gantt-chart";
type Props = {
@ -33,6 +37,7 @@ type Props = {
enableBlockRightResize: boolean;
enableReorder: boolean;
enableAddBlock: boolean;
enableSelection: boolean;
itemsContainerWidth: number;
showAllBlocks: boolean;
sidebarToRender: (props: any) => React.ReactNode;
@ -53,6 +58,7 @@ export const GanttChartMainContent: React.FC<Props> = observer((props) => {
enableBlockRightResize,
enableReorder,
enableAddBlock,
enableSelection,
itemsContainerWidth,
showAllBlocks,
sidebarToRender,
@ -107,43 +113,58 @@ export const GanttChartMainContent: React.FC<Props> = observer((props) => {
const ActiveChartView = CHART_VIEW_COMPONENTS[currentView];
return (
<div
// DO NOT REMOVE THE ID
id="gantt-container"
className={cn(
"h-full w-full overflow-auto vertical-scrollbar horizontal-scrollbar scrollbar-lg flex border-t-[0.5px] border-custom-border-200",
{
"mb-8": bottomSpacing,
}
)}
ref={ganttContainerRef}
onScroll={onScroll}
<MultipleSelectGroup
containerRef={ganttContainerRef}
entities={{
[GANTT_SELECT_GROUP]: chartBlocks?.map((block) => block.id) ?? [],
}}
>
<GanttChartSidebar
blocks={blocks}
blockUpdateHandler={blockUpdateHandler}
enableReorder={enableReorder}
sidebarToRender={sidebarToRender}
title={title}
quickAdd={quickAdd}
/>
<div className="relative min-h-full h-max flex-shrink-0 flex-grow">
<ActiveChartView />
{currentViewData && (
<GanttChartBlocksList
itemsContainerWidth={itemsContainerWidth}
blocks={chartBlocks}
blockToRender={blockToRender}
blockUpdateHandler={blockUpdateHandler}
enableBlockLeftResize={enableBlockLeftResize}
enableBlockRightResize={enableBlockRightResize}
enableBlockMove={enableBlockMove}
enableAddBlock={enableAddBlock}
ganttContainerRef={ganttContainerRef}
showAllBlocks={showAllBlocks}
/>
)}
</div>
</div>
{(helpers) => (
<>
<div
// DO NOT REMOVE THE ID
id="gantt-container"
className={cn(
"h-full w-full overflow-auto vertical-scrollbar horizontal-scrollbar scrollbar-lg flex border-t-[0.5px] border-custom-border-200",
{
"mb-8": bottomSpacing,
}
)}
ref={ganttContainerRef}
onScroll={onScroll}
>
<GanttChartSidebar
blocks={blocks}
blockUpdateHandler={blockUpdateHandler}
enableReorder={enableReorder}
enableSelection={enableSelection}
sidebarToRender={sidebarToRender}
title={title}
quickAdd={quickAdd}
selectionHelpers={helpers}
/>
<div className="relative min-h-full h-max flex-shrink-0 flex-grow">
<ActiveChartView />
{currentViewData && (
<GanttChartBlocksList
itemsContainerWidth={itemsContainerWidth}
blocks={chartBlocks}
blockToRender={blockToRender}
blockUpdateHandler={blockUpdateHandler}
enableBlockLeftResize={enableBlockLeftResize}
enableBlockRightResize={enableBlockRightResize}
enableBlockMove={enableBlockMove}
enableAddBlock={enableAddBlock}
ganttContainerRef={ganttContainerRef}
showAllBlocks={showAllBlocks}
selectionHelpers={helpers}
/>
)}
</div>
</div>
<IssueBulkOperationsRoot />
</>
)}
</MultipleSelectGroup>
);
});

View File

@ -32,6 +32,7 @@ type ChartViewRootProps = {
enableBlockMove: boolean;
enableReorder: boolean;
enableAddBlock: boolean;
enableSelection: boolean;
bottomSpacing: boolean;
showAllBlocks: boolean;
quickAdd?: React.JSX.Element | undefined;
@ -51,6 +52,7 @@ export const ChartViewRoot: FC<ChartViewRootProps> = observer((props) => {
enableBlockMove,
enableReorder,
enableAddBlock,
enableSelection,
bottomSpacing,
showAllBlocks,
quickAdd,
@ -184,6 +186,7 @@ export const ChartViewRoot: FC<ChartViewRootProps> = observer((props) => {
enableBlockRightResize={enableBlockRightResize}
enableReorder={enableReorder}
enableAddBlock={enableAddBlock}
enableSelection={enableSelection}
itemsContainerWidth={itemsContainerWidth}
showAllBlocks={showAllBlocks}
sidebarToRender={sidebarToRender}

View File

@ -3,3 +3,5 @@ export const BLOCK_HEIGHT = 44;
export const HEADER_HEIGHT = 60;
export const SIDEBAR_WIDTH = 360;
export const GANTT_SELECT_GROUP = "gantt-issues";

View File

@ -18,6 +18,7 @@ type GanttChartRootProps = {
enableBlockMove?: boolean;
enableReorder?: boolean;
enableAddBlock?: boolean;
enableSelection?: boolean;
bottomSpacing?: boolean;
showAllBlocks?: boolean;
};
@ -36,6 +37,7 @@ export const GanttChartRoot: FC<GanttChartRootProps> = (props) => {
enableBlockMove = false,
enableReorder = false,
enableAddBlock = false,
enableSelection = false,
bottomSpacing = false,
showAllBlocks = false,
quickAdd,
@ -56,6 +58,7 @@ export const GanttChartRoot: FC<GanttChartRootProps> = (props) => {
enableBlockMove={enableBlockMove}
enableReorder={enableReorder}
enableAddBlock={enableAddBlock}
enableSelection={enableSelection}
bottomSpacing={bottomSpacing}
showAllBlocks={showAllBlocks}
quickAdd={quickAdd}

View File

@ -38,7 +38,7 @@ export const CyclesSidebarBlock: React.FC<Props> = observer((props) => {
<div
id={`sidebar-block-${block.id}`}
className={cn("group w-full flex items-center gap-2 pl-2 pr-4", {
"bg-custom-background-80": isBlockActive(block.id),
"bg-custom-background-90": isBlockActive(block.id),
})}
style={{
height: `${BLOCK_HEIGHT}px`,

View File

@ -1,63 +1,87 @@
import React, { MutableRefObject } from "react";
import { observer } from "mobx-react";
import { MoreVertical } from "lucide-react";
// hooks
import { useGanttChart } from "@/components/gantt-chart/hooks";
// components
import { MultipleSelectEntityAction } from "@/components/core";
import { useGanttChart } from "@/components/gantt-chart/hooks";
import { IssueGanttSidebarBlock } from "@/components/issues";
// helpers
import { cn } from "@/helpers/common.helper";
import { findTotalDaysInRange } from "@/helpers/date-time.helper";
// hooks
import { useIssueDetail } from "@/hooks/store";
// types
import { TSelectionHelper } from "@/hooks/use-multiple-select";
// constants
import { BLOCK_HEIGHT } from "../../constants";
import { BLOCK_HEIGHT, GANTT_SELECT_GROUP } from "../../constants";
// types
import { IGanttBlock } from "../../types";
type Props = {
block: IGanttBlock;
enableReorder: boolean;
enableSelection: boolean;
isDragging: boolean;
dragHandleRef: MutableRefObject<HTMLButtonElement | null>;
selectionHelpers?: TSelectionHelper;
};
export const IssuesSidebarBlock = observer((props: Props) => {
const { block, enableReorder, isDragging, dragHandleRef } = props;
const { block, enableReorder, enableSelection, isDragging, dragHandleRef, selectionHelpers } = props;
// store hooks
const { updateActiveBlockId, isBlockActive } = useGanttChart();
const { getIsIssuePeeked } = useIssueDetail();
const duration = findTotalDaysInRange(block.start_date, block.target_date);
const isIssueSelected = selectionHelpers?.getIsEntitySelected(block.id);
const isIssueFocused = selectionHelpers?.getIsEntityActive(block.id);
const isBlockHoveredOn = isBlockActive(block.id);
return (
<div
className={cn({
className={cn("group/list-block", {
"rounded bg-custom-background-80": isDragging,
"rounded-l border border-r-0 border-custom-primary-70 hover:border-custom-primary-70": getIsIssuePeeked(
block.data.id
),
"rounded-l border border-r-0 border-custom-primary-70": getIsIssuePeeked(block.data.id),
"border border-r-0 border-custom-border-400": isIssueFocused,
})}
onMouseEnter={() => updateActiveBlockId(block.id)}
onMouseLeave={() => updateActiveBlockId(null)}
>
<div
className={cn("group w-full flex items-center gap-2 pl-2 pr-4", {
"bg-custom-background-80": isBlockActive(block.id),
"bg-custom-background-90": isBlockHoveredOn,
"bg-custom-primary-100/5 hover:bg-custom-primary-100/10": isIssueSelected,
"bg-custom-primary-100/10": isIssueSelected && isBlockHoveredOn,
})}
style={{
height: `${BLOCK_HEIGHT}px`,
}}
>
{enableReorder && (
<button
type="button"
className="flex flex-shrink-0 rounded p-0.5 text-custom-sidebar-text-200 opacity-0 group-hover:opacity-100"
ref={dragHandleRef}
>
<MoreVertical className="h-3.5 w-3.5" />
<MoreVertical className="-ml-5 h-3.5 w-3.5" />
</button>
)}
<div className="flex items-center gap-2">
{enableReorder && (
<button
type="button"
className="flex flex-shrink-0 rounded p-0.5 text-custom-sidebar-text-200 opacity-0 group-hover:opacity-100"
ref={dragHandleRef}
>
<MoreVertical className="h-3.5 w-3.5" />
<MoreVertical className="-ml-5 h-3.5 w-3.5" />
</button>
)}
{enableSelection && selectionHelpers && (
<MultipleSelectEntityAction
className={cn(
"opacity-0 pointer-events-none group-hover/list-block:opacity-100 group-hover/list-block:pointer-events-auto transition-opacity",
{
"opacity-100 pointer-events-auto": isIssueSelected,
}
)}
groupId={GANTT_SELECT_GROUP}
id={block.id}
selectionHelpers={selectionHelpers}
/>
)}
</div>
<div className="flex h-full flex-grow items-center justify-between gap-2 truncate">
<div className="flex-grow truncate">
<IssueGanttSidebarBlock issueId={block.data.id} />

View File

@ -1,22 +1,26 @@
import { MutableRefObject } from "react";
// components
// ui
import { Loader } from "@plane/ui";
// types
// components
import { IGanttBlock, IBlockUpdateData } from "@/components/gantt-chart/types";
// hooks
import { TSelectionHelper } from "@/hooks/use-multiple-select";
import { GanttDnDHOC } from "../gantt-dnd-HOC";
import { handleOrderChange } from "../utils";
// types
import { IssuesSidebarBlock } from "./block";
type Props = {
blockUpdateHandler: (block: any, payload: IBlockUpdateData) => void;
blocks: IGanttBlock[] | null;
enableReorder: boolean;
enableSelection: boolean;
showAllBlocks?: boolean;
selectionHelpers?: TSelectionHelper;
};
export const IssueGanttSidebar: React.FC<Props> = (props) => {
const { blockUpdateHandler, blocks, enableReorder, showAllBlocks = false } = props;
const { blockUpdateHandler, blocks, enableReorder, enableSelection, showAllBlocks = false, selectionHelpers } = props;
const handleOnDrop = (
draggingBlockId: string | undefined,
@ -47,8 +51,10 @@ export const IssueGanttSidebar: React.FC<Props> = (props) => {
<IssuesSidebarBlock
block={block}
enableReorder={enableReorder}
enableSelection={enableSelection}
isDragging={isDragging}
dragHandleRef={dragHandleRef}
selectionHelpers={selectionHelpers}
/>
)}
</GanttDnDHOC>

View File

@ -38,7 +38,7 @@ export const ModulesSidebarBlock: React.FC<Props> = observer((props) => {
<div
id={`sidebar-block-${block.id}`}
className={cn("group w-full flex items-center gap-2 pl-2 pr-4", {
"bg-custom-background-80": isBlockActive(block.id),
"bg-custom-background-90": isBlockActive(block.id),
})}
style={{
height: `${BLOCK_HEIGHT}px`,

View File

@ -1,19 +1,38 @@
import { observer } from "mobx-react";
// components
import { MultipleSelectGroupAction } from "@/components/core";
import { IBlockUpdateData, IGanttBlock } from "@/components/gantt-chart";
// helpers
import { cn } from "@/helpers/common.helper";
// hooks
import { TSelectionHelper } from "@/hooks/use-multiple-select";
// constants
import { HEADER_HEIGHT, SIDEBAR_WIDTH } from "../constants";
import { GANTT_SELECT_GROUP, HEADER_HEIGHT, SIDEBAR_WIDTH } from "../constants";
type Props = {
blocks: IGanttBlock[] | null;
blockUpdateHandler: (block: any, payload: IBlockUpdateData) => void;
enableReorder: boolean;
enableSelection: boolean;
sidebarToRender: (props: any) => React.ReactNode;
title: string;
quickAdd?: React.JSX.Element | undefined;
selectionHelpers: TSelectionHelper;
};
export const GanttChartSidebar: React.FC<Props> = (props) => {
const { blocks, blockUpdateHandler, enableReorder, sidebarToRender, title, quickAdd } = props;
export const GanttChartSidebar: React.FC<Props> = observer((props) => {
const {
blocks,
blockUpdateHandler,
enableReorder,
enableSelection,
sidebarToRender,
title,
quickAdd,
selectionHelpers,
} = props;
const isGroupSelectionEmpty = selectionHelpers.isGroupSelected(GANTT_SELECT_GROUP) === "empty";
return (
<div
@ -25,19 +44,39 @@ export const GanttChartSidebar: React.FC<Props> = (props) => {
}}
>
<div
className="box-border flex-shrink-0 flex items-end justify-between gap-2 border-b-[0.5px] border-custom-border-200 pb-2 pl-8 pr-4 text-sm font-medium text-custom-text-300 sticky top-0 z-10 bg-custom-background-100"
className="group/list-header box-border flex-shrink-0 flex items-end justify-between gap-2 border-b-[0.5px] border-custom-border-200 pb-2 pl-2 pr-4 text-sm font-medium text-custom-text-300 sticky top-0 z-10 bg-custom-background-100"
style={{
height: `${HEADER_HEIGHT}px`,
}}
>
<h6>{title}</h6>
<div
className={cn("flex items-center gap-2", {
"pl-2": !enableSelection,
})}
>
{enableSelection && (
<div className="flex-shrink-0 flex items-center w-3.5">
<MultipleSelectGroupAction
className={cn(
"size-3.5 opacity-0 pointer-events-none group-hover/list-header:opacity-100 group-hover/list-header:pointer-events-auto !outline-none",
{
"opacity-100 pointer-events-auto": !isGroupSelectionEmpty,
}
)}
groupID={GANTT_SELECT_GROUP}
selectionHelpers={selectionHelpers}
/>
</div>
)}
<h6>{title}</h6>
</div>
<h6>Duration</h6>
</div>
<div className="min-h-full h-max bg-custom-background-100 overflow-hidden">
{sidebarToRender && sidebarToRender({ title, blockUpdateHandler, blocks, enableReorder })}
{sidebarToRender?.({ title, blockUpdateHandler, blocks, enableReorder, enableSelection, selectionHelpers })}
</div>
{quickAdd ? quickAdd : null}
</div>
);
};
});

View File

@ -10,9 +10,8 @@ import { IIssueDisplayFilterOptions, IIssueDisplayProperties, IIssueFilterOption
import { Breadcrumbs, Button, ContrastIcon, CustomMenu, Tooltip } from "@plane/ui";
// components
import { ProjectAnalyticsModal } from "@/components/analytics";
import { BreadcrumbLink } from "@/components/common";
import { BreadcrumbLink, Logo } from "@/components/common";
import { DisplayFiltersSelection, FiltersDropdown, FilterSelection, LayoutSelection } from "@/components/issues";
import { ProjectLogo } from "@/components/project";
// constants
import { EIssueFilterType, EIssuesStoreType, ISSUE_DISPLAY_FILTERS_BY_LAYOUT } from "@/constants/issue";
import { EUserProjectRoles } from "@/constants/project";
@ -170,7 +169,7 @@ export const CycleIssuesHeader: React.FC = observer(() => {
icon={
currentProjectDetails && (
<span className="grid h-4 w-4 flex-shrink-0 place-items-center">
<ProjectLogo logo={currentProjectDetails?.logo_props} className="text-sm" />
<Logo logo={currentProjectDetails?.logo_props} size={16} />
</span>
)
}

View File

@ -4,9 +4,8 @@ import { useRouter } from "next/router";
// ui
import { Breadcrumbs, Button, ContrastIcon } from "@plane/ui";
// components
import { BreadcrumbLink } from "@/components/common";
import { BreadcrumbLink, Logo } from "@/components/common";
import { CyclesViewHeader } from "@/components/cycles";
import { ProjectLogo } from "@/components/project";
// constants
import { EUserProjectRoles } from "@/constants/project";
// hooks
@ -41,7 +40,7 @@ export const CyclesHeader: FC = observer(() => {
icon={
currentProjectDetails && (
<span className="grid place-items-center flex-shrink-0 h-4 w-4">
<ProjectLogo logo={currentProjectDetails?.logo_props} className="text-sm" />
<Logo logo={currentProjectDetails?.logo_props} size={16} />
</span>
)
}

View File

@ -10,9 +10,8 @@ import { IIssueDisplayFilterOptions, IIssueDisplayProperties, IIssueFilterOption
import { Breadcrumbs, Button, CustomMenu, DiceIcon, Tooltip } from "@plane/ui";
// components
import { ProjectAnalyticsModal } from "@/components/analytics";
import { BreadcrumbLink } from "@/components/common";
import { BreadcrumbLink, Logo } from "@/components/common";
import { DisplayFiltersSelection, FiltersDropdown, FilterSelection, LayoutSelection } from "@/components/issues";
import { ProjectLogo } from "@/components/project";
// constants
import { EIssuesStoreType, EIssueFilterType, ISSUE_DISPLAY_FILTERS_BY_LAYOUT } from "@/constants/issue";
import { EUserProjectRoles } from "@/constants/project";
@ -170,7 +169,7 @@ export const ModuleIssuesHeader: React.FC = observer(() => {
icon={
currentProjectDetails && (
<span className="grid h-4 w-4 flex-shrink-0 place-items-center">
<ProjectLogo logo={currentProjectDetails?.logo_props} className="text-sm" />
<Logo logo={currentProjectDetails?.logo_props} size={16} />
</span>
)
}

View File

@ -3,9 +3,8 @@ import { useRouter } from "next/router";
// ui
import { Breadcrumbs, Button, DiceIcon } from "@plane/ui";
// components
import { BreadcrumbLink } from "@/components/common";
import { BreadcrumbLink, Logo } from "@/components/common";
import { ModuleViewHeader } from "@/components/modules";
import { ProjectLogo } from "@/components/project";
// constants
import { EUserProjectRoles } from "@/constants/project";
// hooks
@ -41,7 +40,7 @@ export const ModulesListHeader: React.FC = observer(() => {
icon={
currentProjectDetails && (
<span className="grid h-4 w-4 flex-shrink-0 place-items-center">
<ProjectLogo logo={currentProjectDetails?.logo_props} className="text-sm" />
<Logo logo={currentProjectDetails?.logo_props} size={16} />
</span>
)
}

View File

@ -1,22 +1,52 @@
import { useState } from "react";
import { observer } from "mobx-react";
import { useRouter } from "next/router";
import { FileText } from "lucide-react";
// types
import { TLogoProps } from "@plane/types";
// ui
import { Breadcrumbs, Button } from "@plane/ui";
import { Breadcrumbs, Button, EmojiIconPicker, EmojiIconPickerTypes, TOAST_TYPE, setToast } from "@plane/ui";
// components
import { BreadcrumbLink } from "@/components/common";
import { ProjectLogo } from "@/components/project";
import { BreadcrumbLink, Logo } from "@/components/common";
// helper
import { convertHexEmojiToDecimal } from "@/helpers/emoji.helper";
// hooks
import { usePage, useProject } from "@/hooks/store";
import { usePlatformOS } from "@/hooks/use-platform-os";
export interface IPagesHeaderProps {
showButton?: boolean;
}
export const PageDetailsHeader = observer(() => {
// router
const router = useRouter();
const { workspaceSlug, pageId } = router.query;
// state
const [isOpen, setIsOpen] = useState(false);
// store hooks
const { currentProjectDetails } = useProject();
const { isContentEditable, isSubmitting, name } = usePage(pageId?.toString() ?? "");
const { isContentEditable, isSubmitting, name, logo_props, updatePageLogo } = usePage(pageId?.toString() ?? "");
const handlePageLogoUpdate = async (data: TLogoProps) => {
if (data) {
updatePageLogo(data)
.then(() => {
setToast({
type: TOAST_TYPE.SUCCESS,
title: "Success!",
message: "Logo Updated successfully.",
});
})
.catch(() => {
setToast({
type: TOAST_TYPE.ERROR,
title: "Error!",
message: "Something went wrong. Please try again.",
});
});
}
};
// use platform
const { platform } = usePlatformOS();
// derived values
@ -38,7 +68,7 @@ export const PageDetailsHeader = observer(() => {
icon={
currentProjectDetails && (
<span className="grid h-4 w-4 flex-shrink-0 place-items-center">
<ProjectLogo logo={currentProjectDetails?.logo_props} className="text-sm" />
<Logo logo={currentProjectDetails?.logo_props} size={16} />
</span>
)
}
@ -67,7 +97,49 @@ export const PageDetailsHeader = observer(() => {
<Breadcrumbs.BreadcrumbItem
type="text"
link={
<BreadcrumbLink label={name ?? "Page"} icon={<FileText className="h-4 w-4 text-custom-text-300" />} />
<BreadcrumbLink
label={name ?? "Page"}
icon={
<EmojiIconPicker
isOpen={isOpen}
handleToggle={(val: boolean) => setIsOpen(val)}
className="flex items-center justify-center"
buttonClassName="flex items-center justify-center"
label={
<>
{logo_props?.in_use ? (
<Logo logo={logo_props} size={16} type="lucide" />
) : (
<FileText className="h-4 w-4 text-custom-text-300" />
)}
</>
}
onChange={(val) => {
let logoValue = {};
if (val?.type === "emoji")
logoValue = {
value: convertHexEmojiToDecimal(val.value.unified),
url: val.value.imageUrl,
};
else if (val?.type === "icon") logoValue = val.value;
handlePageLogoUpdate({
in_use: val?.type,
[val?.type]: logoValue,
}).finally(() => setIsOpen(false));
}}
defaultIconColor={
logo_props?.in_use && logo_props.in_use === "icon" ? logo_props?.icon?.color : undefined
}
defaultOpen={
logo_props?.in_use && logo_props?.in_use === "emoji"
? EmojiIconPickerTypes.EMOJI
: EmojiIconPickerTypes.ICON
}
/>
}
/>
}
/>
</Breadcrumbs>

View File

@ -1,21 +1,20 @@
import { observer } from "mobx-react";
import { useRouter } from "next/router";
import { FileText } from "lucide-react";
// hooks
// ui
import { Breadcrumbs, Button } from "@plane/ui";
// helpers
import { BreadcrumbLink } from "@/components/common";
import { ProjectLogo } from "@/components/project";
import { EUserProjectRoles } from "@/constants/project";
import { BreadcrumbLink, Logo } from "@/components/common";
// constants
// components
import { EPageAccess } from "@/constants/page";
import { EUserProjectRoles } from "@/constants/project";
// hooks
import { useCommandPalette, useEventTracker, useProject, useUser } from "@/hooks/store";
export const PagesHeader = observer(() => {
// router
const router = useRouter();
const { workspaceSlug } = router.query;
const { workspaceSlug, type: pageType } = router.query;
// store hooks
const { toggleCreatePageModal } = useCommandPalette();
const {
@ -41,7 +40,7 @@ export const PagesHeader = observer(() => {
icon={
currentProjectDetails && (
<span className="grid h-4 w-4 flex-shrink-0 place-items-center">
<ProjectLogo logo={currentProjectDetails?.logo_props} className="text-sm" />
<Logo logo={currentProjectDetails?.logo_props} size={16} />
</span>
)
}
@ -62,7 +61,10 @@ export const PagesHeader = observer(() => {
size="sm"
onClick={() => {
setTrackElement("Project pages page");
toggleCreatePageModal(true);
toggleCreatePageModal({
isOpen: true,
pageAccess: pageType === "private" ? EPageAccess.PRIVATE : EPageAccess.PUBLIC,
});
}}
>
Add Page

View File

@ -4,8 +4,7 @@ import { useRouter } from "next/router";
import useSWR from "swr";
// hooks
import { ArchiveIcon, Breadcrumbs, LayersIcon } from "@plane/ui";
import { BreadcrumbLink } from "@/components/common";
import { ProjectLogo } from "@/components/project";
import { BreadcrumbLink, Logo } from "@/components/common";
import { ISSUE_DETAILS } from "@/constants/fetch-keys";
import { useProject } from "@/hooks/store";
// components
@ -52,7 +51,7 @@ export const ProjectArchivedIssueDetailsHeader: FC = observer(() => {
icon={
currentProjectDetails && (
<span className="grid place-items-center flex-shrink-0 h-4 w-4">
<ProjectLogo logo={currentProjectDetails?.logo_props} className="text-sm" />
<Logo logo={currentProjectDetails?.logo_props} size={16} />
</span>
)
}

View File

@ -4,8 +4,7 @@ import { useRouter } from "next/router";
// ui
import { ArchiveIcon, Breadcrumbs, Tooltip } from "@plane/ui";
// components
import { BreadcrumbLink } from "@/components/common";
import { ProjectLogo } from "@/components/project";
import { BreadcrumbLink, Logo } from "@/components/common";
// constants
import { PROJECT_ARCHIVES_BREADCRUMB_LIST } from "@/constants/archives";
import { EIssuesStoreType } from "@/constants/issue";
@ -49,7 +48,7 @@ export const ProjectArchivesHeader: FC = observer(() => {
icon={
currentProjectDetails && (
<span className="grid place-items-center flex-shrink-0 h-4 w-4">
<ProjectLogo logo={currentProjectDetails?.logo_props} className="text-sm" />
<Logo logo={currentProjectDetails?.logo_props} size={16} />
</span>
)
}

View File

@ -6,9 +6,8 @@ import { IIssueDisplayFilterOptions, IIssueDisplayProperties, IIssueFilterOption
// ui
import { Breadcrumbs, LayersIcon, Tooltip } from "@plane/ui";
// components
import { BreadcrumbLink } from "@/components/common";
import { BreadcrumbLink, Logo } from "@/components/common";
import { DisplayFiltersSelection, FiltersDropdown, FilterSelection, LayoutSelection } from "@/components/issues";
import { ProjectLogo } from "@/components/project";
// constants
import { EIssueFilterType, EIssuesStoreType, ISSUE_DISPLAY_FILTERS_BY_LAYOUT } from "@/constants/issue";
// helpers
@ -101,7 +100,7 @@ export const ProjectDraftIssueHeader: FC = observer(() => {
icon={
currentProjectDetails && (
<span className="grid place-items-center flex-shrink-0 h-4 w-4">
<ProjectLogo logo={currentProjectDetails?.logo_props} className="text-sm" />
<Logo logo={currentProjectDetails?.logo_props} size={16} />
</span>
)
}

View File

@ -5,9 +5,8 @@ import { RefreshCcw } from "lucide-react";
// ui
import { Breadcrumbs, Button, LayersIcon } from "@plane/ui";
// components
import { BreadcrumbLink } from "@/components/common";
import { BreadcrumbLink, Logo } from "@/components/common";
import { InboxIssueCreateEditModalRoot } from "@/components/inbox";
import { ProjectLogo } from "@/components/project";
// hooks
import { useProject, useProjectInbox } from "@/hooks/store";
@ -35,7 +34,7 @@ export const ProjectInboxHeader: FC = observer(() => {
icon={
currentProjectDetails && (
<span className="grid place-items-center flex-shrink-0 h-4 w-4">
<ProjectLogo logo={currentProjectDetails?.logo_props} className="text-sm" />
<Logo logo={currentProjectDetails?.logo_props} size={16} />
</span>
)
}

View File

@ -4,8 +4,7 @@ import { useRouter } from "next/router";
// hooks
import { PanelRight } from "lucide-react";
import { Breadcrumbs, LayersIcon } from "@plane/ui";
import { BreadcrumbLink } from "@/components/common";
import { ProjectLogo } from "@/components/project";
import { BreadcrumbLink, Logo } from "@/components/common";
import { cn } from "@/helpers/common.helper";
import { useAppTheme, useIssueDetail, useProject } from "@/hooks/store";
// ui
@ -42,7 +41,7 @@ export const ProjectIssueDetailsHeader: FC = observer(() => {
icon={
currentProjectDetails && (
<span className="grid h-4 w-4 flex-shrink-0 place-items-center">
<ProjectLogo logo={currentProjectDetails?.logo_props} className="text-sm" />
<Logo logo={currentProjectDetails?.logo_props} size={16} />
</span>
)
}

View File

@ -9,9 +9,8 @@ import { IIssueDisplayFilterOptions, IIssueDisplayProperties, IIssueFilterOption
import { Breadcrumbs, Button, LayersIcon, Tooltip } from "@plane/ui";
// components
import { ProjectAnalyticsModal } from "@/components/analytics";
import { BreadcrumbLink } from "@/components/common";
import { BreadcrumbLink, Logo } from "@/components/common";
import { DisplayFiltersSelection, FiltersDropdown, FilterSelection, LayoutSelection } from "@/components/issues";
import { ProjectLogo } from "@/components/project";
// constants
import { EIssueFilterType, EIssuesStoreType, ISSUE_DISPLAY_FILTERS_BY_LAYOUT } from "@/constants/issue";
import { EUserProjectRoles } from "@/constants/project";
@ -130,7 +129,7 @@ export const ProjectIssuesHeader: React.FC = observer(() => {
currentProjectDetails ? (
currentProjectDetails && (
<span className="grid place-items-center flex-shrink-0 h-4 w-4">
<ProjectLogo logo={currentProjectDetails?.logo_props} className="text-sm" />
<Logo logo={currentProjectDetails?.logo_props} size={16} />
</span>
)
) : (

View File

@ -5,8 +5,7 @@ import { useRouter } from "next/router";
import { Settings } from "lucide-react";
import { Breadcrumbs, CustomMenu } from "@plane/ui";
// components
import { BreadcrumbLink } from "@/components/common";
import { ProjectLogo } from "@/components/project";
import { BreadcrumbLink, Logo } from "@/components/common";
// constants
import { EUserProjectRoles, PROJECT_SETTINGS_LINKS } from "@/constants/project";
// hooks
@ -39,7 +38,7 @@ export const ProjectSettingHeader: FC = observer(() => {
icon={
currentProjectDetails && (
<span className="grid place-items-center flex-shrink-0 h-4 w-4">
<ProjectLogo logo={currentProjectDetails?.logo_props} className="text-sm" />
<Logo logo={currentProjectDetails?.logo_props} size={16} />
</span>
)
}

View File

@ -7,9 +7,8 @@ import { IIssueDisplayFilterOptions, IIssueDisplayProperties, IIssueFilterOption
// ui
import { Breadcrumbs, Button, CustomMenu, PhotoFilterIcon } from "@plane/ui";
// components
import { BreadcrumbLink } from "@/components/common";
import { BreadcrumbLink, Logo } from "@/components/common";
import { DisplayFiltersSelection, FiltersDropdown, FilterSelection, LayoutSelection } from "@/components/issues";
import { ProjectLogo } from "@/components/project";
// constants
import { EIssuesStoreType, EIssueFilterType, ISSUE_DISPLAY_FILTERS_BY_LAYOUT } from "@/constants/issue";
import { EUserProjectRoles } from "@/constants/project";
@ -141,7 +140,7 @@ export const ProjectViewIssuesHeader: React.FC = observer(() => {
icon={
currentProjectDetails && (
<span className="grid h-4 w-4 flex-shrink-0 place-items-center">
<ProjectLogo logo={currentProjectDetails?.logo_props} className="text-sm" />
<Logo logo={currentProjectDetails?.logo_props} size={16} />
</span>
)
}
@ -164,7 +163,11 @@ export const ProjectViewIssuesHeader: React.FC = observer(() => {
<CustomMenu
label={
<>
<PhotoFilterIcon height={12} width={12} />
{viewDetails?.logo_props?.in_use ? (
<Logo logo={viewDetails.logo_props} size={12} type="lucide" />
) : (
<PhotoFilterIcon height={12} width={12} />
)}
{viewDetails?.name && truncateText(viewDetails.name, 40)}
</>
}
@ -182,7 +185,11 @@ export const ProjectViewIssuesHeader: React.FC = observer(() => {
href={`/${workspaceSlug}/projects/${projectId}/views/${viewId}`}
className="flex items-center gap-1.5"
>
<PhotoFilterIcon height={12} width={12} />
{view?.logo_props?.in_use ? (
<Logo logo={view.logo_props} size={12} type="lucide" />
) : (
<PhotoFilterIcon height={12} width={12} />
)}
{truncateText(view.name, 40)}
</Link>
</CustomMenu.MenuItem>

View File

@ -1,14 +1,13 @@
import { observer } from "mobx-react";
import { useRouter } from "next/router";
// hooks
// components
// ui
import { Breadcrumbs, PhotoFilterIcon, Button } from "@plane/ui";
import { BreadcrumbLink } from "@/components/common";
// helpers
import { ProjectLogo } from "@/components/project";
// components
import { BreadcrumbLink, Logo } from "@/components/common";
import { ViewListHeader } from "@/components/views";
import { EUserProjectRoles } from "@/constants/project";
// constants
import { EUserProjectRoles } from "@/constants/project";
// hooks
import { useCommandPalette, useProject, useUser } from "@/hooks/store";
export const ProjectViewsHeader: React.FC = observer(() => {
@ -40,7 +39,7 @@ export const ProjectViewsHeader: React.FC = observer(() => {
icon={
currentProjectDetails && (
<span className="grid h-4 w-4 flex-shrink-0 place-items-center">
<ProjectLogo logo={currentProjectDetails?.logo_props} className="text-sm" />
<Logo logo={currentProjectDetails?.logo_props} size={16} />
</span>
)
}

View File

@ -52,7 +52,7 @@ export const InboxIssueActionsHeader: FC<TInboxIssueActionsHeader> = observer((p
const [declineIssueModal, setDeclineIssueModal] = useState(false);
const [deleteIssueModal, setDeleteIssueModal] = useState(false);
// store
const { currentTab, deleteInboxIssue, inboxIssuesArray } = useProjectInbox();
const { currentTab, deleteInboxIssue, inboxIssueIds } = useProjectInbox();
const { data: currentUser } = useUser();
const {
membership: { currentProjectRole },
@ -76,11 +76,11 @@ export const InboxIssueActionsHeader: FC<TInboxIssueActionsHeader> = observer((p
const redirectIssue = (): string | undefined => {
let nextOrPreviousIssueId: string | undefined = undefined;
const currentIssueIndex = inboxIssuesArray.findIndex((i) => i.issue.id === currentInboxIssueId);
if (inboxIssuesArray[currentIssueIndex + 1])
nextOrPreviousIssueId = inboxIssuesArray[currentIssueIndex + 1].issue.id;
else if (inboxIssuesArray[currentIssueIndex - 1])
nextOrPreviousIssueId = inboxIssuesArray[currentIssueIndex - 1].issue.id;
const currentIssueIndex = inboxIssueIds.findIndex((id) => id === currentInboxIssueId);
if (inboxIssueIds[currentIssueIndex + 1])
nextOrPreviousIssueId = inboxIssueIds[currentIssueIndex + 1];
else if (inboxIssueIds[currentIssueIndex - 1])
nextOrPreviousIssueId = inboxIssueIds[currentIssueIndex - 1];
else nextOrPreviousIssueId = undefined;
return nextOrPreviousIssueId;
};
@ -134,22 +134,22 @@ export const InboxIssueActionsHeader: FC<TInboxIssueActionsHeader> = observer((p
})
);
const currentIssueIndex = inboxIssuesArray.findIndex((issue) => issue.issue.id === currentInboxIssueId) ?? 0;
const currentIssueIndex = inboxIssueIds.findIndex((issueId) => issueId === currentInboxIssueId) ?? 0;
const handleInboxIssueNavigation = useCallback(
(direction: "next" | "prev") => {
if (!inboxIssuesArray || !currentInboxIssueId) return;
if (!inboxIssueIds || !currentInboxIssueId) return;
const activeElement = document.activeElement as HTMLElement;
if (activeElement && (activeElement.classList.contains("tiptap") || activeElement.id === "title-input")) return;
const nextIssueIndex =
direction === "next"
? (currentIssueIndex + 1) % inboxIssuesArray.length
: (currentIssueIndex - 1 + inboxIssuesArray.length) % inboxIssuesArray.length;
const nextIssueId = inboxIssuesArray[nextIssueIndex].issue.id;
? (currentIssueIndex + 1) % inboxIssueIds.length
: (currentIssueIndex - 1 + inboxIssueIds.length) % inboxIssueIds.length;
const nextIssueId = inboxIssueIds[nextIssueIndex];
if (!nextIssueId) return;
router.push(`/${workspaceSlug}/projects/${projectId}/inbox?inboxIssueId=${nextIssueId}`);
},
[currentInboxIssueId, currentIssueIndex, inboxIssuesArray, projectId, router, workspaceSlug]
[currentInboxIssueId, currentIssueIndex, inboxIssueIds, projectId, router, workspaceSlug]
);
const onKeyDown = useCallback(

View File

@ -1,5 +1,6 @@
import { FC, useState } from "react";
import { FC, useEffect, useState } from "react";
import { observer } from "mobx-react";
import { useRouter } from "next/router";
import useSWR from "swr";
import { InboxIssueActionsHeader, InboxIssueMainContent } from "@/components/inbox";
import { EUserProjectRoles } from "@/constants/project";
@ -15,14 +16,25 @@ type TInboxContentRoot = {
export const InboxContentRoot: FC<TInboxContentRoot> = observer((props) => {
const { workspaceSlug, projectId, inboxIssueId, isMobileSidebar, setIsMobileSidebar } = props;
/// router
const router = useRouter();
// states
const [isSubmitting, setIsSubmitting] = useState<"submitting" | "submitted" | "saved">("saved");
// hooks
const { fetchInboxIssueById, getIssueInboxByIssueId } = useProjectInbox();
const { currentTab, fetchInboxIssueById, getIssueInboxByIssueId, getIsIssueAvailable } = useProjectInbox();
const inboxIssue = getIssueInboxByIssueId(inboxIssueId);
const {
membership: { currentProjectRole },
} = useUser();
// derived values
const isIssueAvailable = getIsIssueAvailable(inboxIssueId?.toString() || "");
useEffect(() => {
if (!isIssueAvailable && inboxIssueId) {
router.replace(`/${workspaceSlug}/projects/${projectId}/inbox?currentTab=${currentTab}`);
}
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [isIssueAvailable]);
useSWR(
workspaceSlug && projectId && inboxIssueId

View File

@ -36,7 +36,7 @@ export const DeclineIssueModal: React.FC<Props> = (props) => {
<AlertModalCore
handleClose={handleClose}
handleSubmit={handleDecline}
isDeleting={isDeclining}
isSubmitting={isDeclining}
isOpen={isOpen}
title="Decline Issue"
content={

View File

@ -36,7 +36,7 @@ export const DeleteInboxIssueModal: React.FC<Props> = observer(({ isOpen, onClos
<AlertModalCore
handleClose={handleClose}
handleSubmit={handleDelete}
isDeleting={isDeleting}
isSubmitting={isDeleting}
isOpen={isOpen}
title="Delete Issue"
content={

View File

@ -12,31 +12,30 @@ import { renderFormattedDate } from "@/helpers/date-time.helper";
// hooks
import { useLabel, useMember, useProjectInbox } from "@/hooks/store";
import { usePlatformOS } from "@/hooks/use-platform-os";
// store
import { IInboxIssueStore } from "@/store/inbox/inbox-issue.store";
type InboxIssueListItemProps = {
workspaceSlug: string;
projectId: string;
projectIdentifier?: string;
inboxIssue: IInboxIssueStore;
inboxIssueId: string;
setIsMobileSidebar: (value: boolean) => void;
};
export const InboxIssueListItem: FC<InboxIssueListItemProps> = observer((props) => {
const { workspaceSlug, projectId, inboxIssue, projectIdentifier, setIsMobileSidebar } = props;
const { workspaceSlug, projectId, inboxIssueId, projectIdentifier, setIsMobileSidebar } = props;
// router
const router = useRouter();
const { inboxIssueId } = router.query;
const { inboxIssueId: selectedInboxIssueId } = router.query;
// store
const { currentTab } = useProjectInbox();
const { currentTab, getIssueInboxByIssueId } = useProjectInbox();
const { projectLabels } = useLabel();
const { isMobile } = usePlatformOS();
const { getUserDetails } = useMember();
const issue = inboxIssue.issue;
const inboxIssue = getIssueInboxByIssueId(inboxIssueId);
const issue = inboxIssue?.issue;
const handleIssueRedirection = (event: MouseEvent, currentIssueId: string | undefined) => {
if (inboxIssueId === currentIssueId) event.preventDefault();
if (selectedInboxIssueId === currentIssueId) event.preventDefault();
setIsMobileSidebar(false);
};
@ -55,7 +54,7 @@ export const InboxIssueListItem: FC<InboxIssueListItemProps> = observer((props)
<div
className={cn(
`flex flex-col gap-2 relative border border-t-transparent border-l-transparent border-r-transparent border-b-custom-border-200 p-4 hover:bg-custom-primary/5 cursor-pointer transition-all`,
{ "border-custom-primary-100 border": inboxIssueId === issue.id }
{ "border-custom-primary-100 border": selectedInboxIssueId === issue.id }
)}
>
<div className="space-y-1">

View File

@ -2,30 +2,28 @@ import { FC, Fragment } from "react";
import { observer } from "mobx-react";
// components
import { InboxIssueListItem } from "@/components/inbox";
// store
import { IInboxIssueStore } from "@/store/inbox/inbox-issue.store";
export type InboxIssueListProps = {
workspaceSlug: string;
projectId: string;
projectIdentifier?: string;
inboxIssues: IInboxIssueStore[];
inboxIssueIds: string[];
setIsMobileSidebar: (value: boolean) => void;
};
export const InboxIssueList: FC<InboxIssueListProps> = observer((props) => {
const { workspaceSlug, projectId, projectIdentifier, inboxIssues, setIsMobileSidebar } = props;
const { workspaceSlug, projectId, projectIdentifier, inboxIssueIds, setIsMobileSidebar } = props;
return (
<>
{inboxIssues.map((inboxIssue) => (
<Fragment key={inboxIssue.id}>
{inboxIssueIds.map((inboxIssueId) => (
<Fragment key={inboxIssueId}>
<InboxIssueListItem
setIsMobileSidebar={setIsMobileSidebar}
workspaceSlug={workspaceSlug}
projectId={projectId}
projectIdentifier={projectIdentifier}
inboxIssue={inboxIssue}
inboxIssueId={inboxIssueId}
/>
</Fragment>
))}

View File

@ -44,7 +44,7 @@ export const InboxSidebar: FC<IInboxSidebarProps> = observer((props) => {
currentTab,
handleCurrentTab,
loader,
inboxIssuesArray,
inboxIssueIds,
inboxIssuePaginationInfo,
fetchInboxPaginationIssues,
getAppliedFiltersCount,
@ -56,13 +56,9 @@ export const InboxSidebar: FC<IInboxSidebarProps> = observer((props) => {
if (!workspaceSlug || !projectId) return;
fetchInboxPaginationIssues(workspaceSlug.toString(), projectId.toString());
}, [workspaceSlug, projectId, fetchInboxPaginationIssues]);
// page observer
useIntersectionObserver({
containerRef,
elementRef,
callback: fetchNextPages,
rootMargin: "20%",
});
useIntersectionObserver(containerRef, elementRef, fetchNextPages, "20%");
return (
<div className="bg-custom-background-100 flex-shrink-0 w-full h-full border-r border-custom-border-300 ">
@ -108,13 +104,13 @@ export const InboxSidebar: FC<IInboxSidebarProps> = observer((props) => {
className="w-full h-full overflow-hidden overflow-y-auto vertical-scrollbar scrollbar-md"
ref={containerRef}
>
{inboxIssuesArray.length > 0 ? (
{inboxIssueIds.length > 0 ? (
<InboxIssueList
setIsMobileSidebar={setIsMobileSidebar}
workspaceSlug={workspaceSlug}
projectId={projectId}
projectIdentifier={currentProjectDetails?.identifier}
inboxIssues={inboxIssuesArray}
inboxIssueIds={inboxIssueIds}
/>
) : (
<div className="flex items-center justify-center h-full w-full">
@ -130,15 +126,14 @@ export const InboxSidebar: FC<IInboxSidebarProps> = observer((props) => {
/>
</div>
)}
<div ref={elementRef}>
{inboxIssuePaginationInfo?.next_page_results && (
{inboxIssuePaginationInfo?.next_page_results && (
<div ref={elementRef}>
<Loader className="mx-auto w-full space-y-4 py-4 px-2">
<Loader.Item height="64px" width="w-100" />
<Loader.Item height="64px" width="w-100" />
</Loader>
)}
</div>
</div>
)}
</div>
)}
</div>

View File

@ -35,7 +35,7 @@ export const IssueAttachmentDeleteModal: FC<Props> = (props) => {
<AlertModalCore
handleClose={handleClose}
handleSubmit={() => handleDeletion(data.id)}
isDeleting={loader}
isSubmitting={loader}
isOpen={isOpen}
title="Delete attachment"
content={

Some files were not shown because too many files have changed in this diff Show More