mirror of
https://github.com/makeplane/plane
synced 2024-06-14 14:31:34 +00:00
chore: resolved merge conflicts
This commit is contained in:
commit
af7fc035d1
@ -5,9 +5,11 @@ import { observer } from "mobx-react-lite";
|
|||||||
import Link from "next/link";
|
import Link from "next/link";
|
||||||
import { ExternalLink, FileText, HelpCircle, MoveLeft } from "lucide-react";
|
import { ExternalLink, FileText, HelpCircle, MoveLeft } from "lucide-react";
|
||||||
import { Transition } from "@headlessui/react";
|
import { Transition } from "@headlessui/react";
|
||||||
|
// ui
|
||||||
import { DiscordIcon, GithubIcon, Tooltip } from "@plane/ui";
|
import { DiscordIcon, GithubIcon, Tooltip } from "@plane/ui";
|
||||||
|
// helpers
|
||||||
|
import { WEB_BASE_URL, cn } from "@/helpers/common.helper";
|
||||||
// hooks
|
// hooks
|
||||||
import { WEB_BASE_URL } from "@/helpers/common.helper";
|
|
||||||
import { useTheme } from "@/hooks/store";
|
import { useTheme } from "@/hooks/store";
|
||||||
// assets
|
// assets
|
||||||
import packageJson from "package.json";
|
import packageJson from "package.json";
|
||||||
@ -42,9 +44,12 @@ export const HelpSection: FC = observer(() => {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<div
|
<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 ${
|
className={cn(
|
||||||
isSidebarCollapsed ? "flex-col" : ""
|
"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"}`}>
|
<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}>
|
<Tooltip tooltipContent="Redirect to plane" position="right" className="ml-4" disabled={!isSidebarCollapsed}>
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "admin",
|
"name": "admin",
|
||||||
"version": "0.20.0",
|
"version": "0.21.0",
|
||||||
"private": true,
|
"private": true,
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"dev": "turbo run develop",
|
"dev": "turbo run develop",
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
{
|
{
|
||||||
"name": "plane-api",
|
"name": "plane-api",
|
||||||
"version": "0.20.0"
|
"version": "0.21.0"
|
||||||
}
|
}
|
||||||
|
@ -66,6 +66,7 @@ class CycleSerializer(BaseSerializer):
|
|||||||
"external_source",
|
"external_source",
|
||||||
"external_id",
|
"external_id",
|
||||||
"progress_snapshot",
|
"progress_snapshot",
|
||||||
|
"logo_props",
|
||||||
# meta fields
|
# meta fields
|
||||||
"is_favorite",
|
"is_favorite",
|
||||||
"total_issues",
|
"total_issues",
|
||||||
|
@ -199,6 +199,7 @@ class ModuleSerializer(DynamicBaseSerializer):
|
|||||||
"sort_order",
|
"sort_order",
|
||||||
"external_source",
|
"external_source",
|
||||||
"external_id",
|
"external_id",
|
||||||
|
"logo_props",
|
||||||
# computed fields
|
# computed fields
|
||||||
"is_favorite",
|
"is_favorite",
|
||||||
"total_issues",
|
"total_issues",
|
||||||
|
@ -39,6 +39,7 @@ class PageSerializer(BaseSerializer):
|
|||||||
"created_by",
|
"created_by",
|
||||||
"updated_by",
|
"updated_by",
|
||||||
"view_props",
|
"view_props",
|
||||||
|
"logo_props",
|
||||||
]
|
]
|
||||||
read_only_fields = [
|
read_only_fields = [
|
||||||
"workspace",
|
"workspace",
|
||||||
|
@ -231,6 +231,7 @@ class CycleViewSet(BaseViewSet):
|
|||||||
"external_source",
|
"external_source",
|
||||||
"external_id",
|
"external_id",
|
||||||
"progress_snapshot",
|
"progress_snapshot",
|
||||||
|
"logo_props",
|
||||||
# meta fields
|
# meta fields
|
||||||
"is_favorite",
|
"is_favorite",
|
||||||
"total_issues",
|
"total_issues",
|
||||||
@ -356,6 +357,7 @@ class CycleViewSet(BaseViewSet):
|
|||||||
"external_source",
|
"external_source",
|
||||||
"external_id",
|
"external_id",
|
||||||
"progress_snapshot",
|
"progress_snapshot",
|
||||||
|
"logo_props",
|
||||||
# meta fields
|
# meta fields
|
||||||
"is_favorite",
|
"is_favorite",
|
||||||
"total_issues",
|
"total_issues",
|
||||||
@ -403,6 +405,7 @@ class CycleViewSet(BaseViewSet):
|
|||||||
"external_source",
|
"external_source",
|
||||||
"external_id",
|
"external_id",
|
||||||
"progress_snapshot",
|
"progress_snapshot",
|
||||||
|
"logo_props",
|
||||||
# meta fields
|
# meta fields
|
||||||
"is_favorite",
|
"is_favorite",
|
||||||
"cancelled_issues",
|
"cancelled_issues",
|
||||||
@ -496,6 +499,7 @@ class CycleViewSet(BaseViewSet):
|
|||||||
"external_source",
|
"external_source",
|
||||||
"external_id",
|
"external_id",
|
||||||
"progress_snapshot",
|
"progress_snapshot",
|
||||||
|
"logo_props",
|
||||||
# meta fields
|
# meta fields
|
||||||
"is_favorite",
|
"is_favorite",
|
||||||
"total_issues",
|
"total_issues",
|
||||||
@ -556,6 +560,7 @@ class CycleViewSet(BaseViewSet):
|
|||||||
"external_id",
|
"external_id",
|
||||||
"progress_snapshot",
|
"progress_snapshot",
|
||||||
"sub_issues",
|
"sub_issues",
|
||||||
|
"logo_props",
|
||||||
# meta fields
|
# meta fields
|
||||||
"is_favorite",
|
"is_favorite",
|
||||||
"total_issues",
|
"total_issues",
|
||||||
|
@ -225,6 +225,7 @@ class ModuleViewSet(BaseViewSet):
|
|||||||
"sort_order",
|
"sort_order",
|
||||||
"external_source",
|
"external_source",
|
||||||
"external_id",
|
"external_id",
|
||||||
|
"logo_props",
|
||||||
# computed fields
|
# computed fields
|
||||||
"is_favorite",
|
"is_favorite",
|
||||||
"cancelled_issues",
|
"cancelled_issues",
|
||||||
@ -281,6 +282,7 @@ class ModuleViewSet(BaseViewSet):
|
|||||||
"sort_order",
|
"sort_order",
|
||||||
"external_source",
|
"external_source",
|
||||||
"external_id",
|
"external_id",
|
||||||
|
"logo_props",
|
||||||
# computed fields
|
# computed fields
|
||||||
"total_issues",
|
"total_issues",
|
||||||
"is_favorite",
|
"is_favorite",
|
||||||
@ -465,6 +467,7 @@ class ModuleViewSet(BaseViewSet):
|
|||||||
"sort_order",
|
"sort_order",
|
||||||
"external_source",
|
"external_source",
|
||||||
"external_id",
|
"external_id",
|
||||||
|
"logo_props",
|
||||||
# computed fields
|
# computed fields
|
||||||
"is_favorite",
|
"is_favorite",
|
||||||
"cancelled_issues",
|
"cancelled_issues",
|
||||||
|
@ -13,12 +13,9 @@ class InstanceSerializer(BaseSerializer):
|
|||||||
model = Instance
|
model = Instance
|
||||||
exclude = [
|
exclude = [
|
||||||
"license_key",
|
"license_key",
|
||||||
"api_key",
|
|
||||||
"version",
|
|
||||||
]
|
]
|
||||||
read_only_fields = [
|
read_only_fields = [
|
||||||
"id",
|
"id",
|
||||||
"instance_id",
|
|
||||||
"email",
|
"email",
|
||||||
"last_checked_at",
|
"last_checked_at",
|
||||||
"is_setup_done",
|
"is_setup_done",
|
||||||
|
@ -49,8 +49,8 @@ class Command(BaseCommand):
|
|||||||
instance_name="Plane Community Edition",
|
instance_name="Plane Community Edition",
|
||||||
instance_id=secrets.token_hex(12),
|
instance_id=secrets.token_hex(12),
|
||||||
license_key=None,
|
license_key=None,
|
||||||
api_key=secrets.token_hex(8),
|
current_version=payload.get("version"),
|
||||||
version=payload.get("version"),
|
latest_version=payload.get("version"),
|
||||||
last_checked_at=timezone.now(),
|
last_checked_at=timezone.now(),
|
||||||
user_count=payload.get("user_count", 0),
|
user_count=payload.get("user_count", 0),
|
||||||
)
|
)
|
||||||
|
@ -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",),
|
||||||
|
},
|
||||||
|
),
|
||||||
|
]
|
@ -1,3 +1,6 @@
|
|||||||
|
# Python imports
|
||||||
|
from enum import Enum
|
||||||
|
|
||||||
# Django imports
|
# Django imports
|
||||||
from django.db import models
|
from django.db import models
|
||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
@ -8,15 +11,23 @@ from plane.db.models import BaseModel
|
|||||||
ROLE_CHOICES = ((20, "Admin"),)
|
ROLE_CHOICES = ((20, "Admin"),)
|
||||||
|
|
||||||
|
|
||||||
|
class ProductTypes(Enum):
|
||||||
|
PLANE_CE = "plane-ce"
|
||||||
|
|
||||||
|
|
||||||
class Instance(BaseModel):
|
class Instance(BaseModel):
|
||||||
# General informations
|
# General information
|
||||||
instance_name = models.CharField(max_length=255)
|
instance_name = models.CharField(max_length=255)
|
||||||
whitelist_emails = models.TextField(blank=True, null=True)
|
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)
|
license_key = models.CharField(max_length=256, null=True, blank=True)
|
||||||
api_key = models.CharField(max_length=16)
|
current_version = models.CharField(max_length=10)
|
||||||
version = models.CharField(max_length=10)
|
latest_version = models.CharField(max_length=10, null=True, blank=True)
|
||||||
# Instnace specifics
|
product = models.CharField(
|
||||||
|
max_length=50, default=ProductTypes.PLANE_CE.value
|
||||||
|
)
|
||||||
|
domain = models.TextField(blank=True)
|
||||||
|
# Instance specifics
|
||||||
last_checked_at = models.DateTimeField()
|
last_checked_at = models.DateTimeField()
|
||||||
namespace = models.CharField(max_length=50, blank=True, null=True)
|
namespace = models.CharField(max_length=50, blank=True, null=True)
|
||||||
# telemetry and support
|
# telemetry and support
|
||||||
@ -70,3 +81,20 @@ class InstanceConfiguration(BaseModel):
|
|||||||
verbose_name_plural = "Instance Configurations"
|
verbose_name_plural = "Instance Configurations"
|
||||||
db_table = "instance_configurations"
|
db_table = "instance_configurations"
|
||||||
ordering = ("-created_at",)
|
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",)
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"repository": "https://github.com/makeplane/plane.git",
|
"repository": "https://github.com/makeplane/plane.git",
|
||||||
"version": "0.20.0",
|
"version": "0.21.0",
|
||||||
"license": "AGPL-3.0",
|
"license": "AGPL-3.0",
|
||||||
"private": true,
|
"private": true,
|
||||||
"workspaces": [
|
"workspaces": [
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@plane/constants",
|
"name": "@plane/constants",
|
||||||
"version": "0.20.0",
|
"version": "0.21.0",
|
||||||
"private": true,
|
"private": true,
|
||||||
"main": "./src/index.ts",
|
"main": "./src/index.ts",
|
||||||
"exports": {
|
"exports": {
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@plane/editor-core",
|
"name": "@plane/editor-core",
|
||||||
"version": "0.20.0",
|
"version": "0.21.0",
|
||||||
"description": "Core Editor that powers Plane",
|
"description": "Core Editor that powers Plane",
|
||||||
"private": true,
|
"private": true,
|
||||||
"main": "./dist/index.mjs",
|
"main": "./dist/index.mjs",
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@plane/document-editor",
|
"name": "@plane/document-editor",
|
||||||
"version": "0.20.0",
|
"version": "0.21.0",
|
||||||
"description": "Package that powers Plane's Pages Editor",
|
"description": "Package that powers Plane's Pages Editor",
|
||||||
"main": "./dist/index.mjs",
|
"main": "./dist/index.mjs",
|
||||||
"module": "./dist/index.mjs",
|
"module": "./dist/index.mjs",
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@plane/editor-extensions",
|
"name": "@plane/editor-extensions",
|
||||||
"version": "0.20.0",
|
"version": "0.21.0",
|
||||||
"description": "Package that powers Plane's Editor with extensions",
|
"description": "Package that powers Plane's Editor with extensions",
|
||||||
"private": true,
|
"private": true,
|
||||||
"main": "./dist/index.mjs",
|
"main": "./dist/index.mjs",
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@plane/lite-text-editor",
|
"name": "@plane/lite-text-editor",
|
||||||
"version": "0.20.0",
|
"version": "0.21.0",
|
||||||
"description": "Package that powers Plane's Comment Editor",
|
"description": "Package that powers Plane's Comment Editor",
|
||||||
"private": true,
|
"private": true,
|
||||||
"main": "./dist/index.mjs",
|
"main": "./dist/index.mjs",
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@plane/rich-text-editor",
|
"name": "@plane/rich-text-editor",
|
||||||
"version": "0.20.0",
|
"version": "0.21.0",
|
||||||
"description": "Rich Text Editor that powers Plane",
|
"description": "Rich Text Editor that powers Plane",
|
||||||
"private": true,
|
"private": true,
|
||||||
"main": "./dist/index.mjs",
|
"main": "./dist/index.mjs",
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
{
|
{
|
||||||
"name": "eslint-config-custom",
|
"name": "eslint-config-custom",
|
||||||
"private": true,
|
"private": true,
|
||||||
"version": "0.20.0",
|
"version": "0.21.0",
|
||||||
"main": "index.js",
|
"main": "index.js",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"devDependencies": {},
|
"devDependencies": {},
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "tailwind-config-custom",
|
"name": "tailwind-config-custom",
|
||||||
"version": "0.20.0",
|
"version": "0.21.0",
|
||||||
"description": "common tailwind configuration across monorepo",
|
"description": "common tailwind configuration across monorepo",
|
||||||
"main": "index.js",
|
"main": "index.js",
|
||||||
"private": true,
|
"private": true,
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "tsconfig",
|
"name": "tsconfig",
|
||||||
"version": "0.20.0",
|
"version": "0.21.0",
|
||||||
"private": true,
|
"private": true,
|
||||||
"files": [
|
"files": [
|
||||||
"base.json",
|
"base.json",
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@plane/types",
|
"name": "@plane/types",
|
||||||
"version": "0.20.0",
|
"version": "0.21.0",
|
||||||
"private": true,
|
"private": true,
|
||||||
"main": "./src/index.d.ts"
|
"main": "./src/index.d.ts"
|
||||||
}
|
}
|
||||||
|
12
packages/types/src/common.d.ts
vendored
12
packages/types/src/common.d.ts
vendored
@ -9,3 +9,15 @@ export type TPaginationInfo = {
|
|||||||
per_page?: number;
|
per_page?: number;
|
||||||
total_results: number;
|
total_results: number;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export type TLogoProps = {
|
||||||
|
in_use: "emoji" | "icon";
|
||||||
|
emoji?: {
|
||||||
|
value?: string;
|
||||||
|
url?: string;
|
||||||
|
};
|
||||||
|
icon?: {
|
||||||
|
name?: string;
|
||||||
|
color?: string;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
4
packages/types/src/instance/base.d.ts
vendored
4
packages/types/src/instance/base.d.ts
vendored
@ -19,8 +19,8 @@ export interface IInstance {
|
|||||||
whitelist_emails: string | undefined;
|
whitelist_emails: string | undefined;
|
||||||
instance_id: string | undefined;
|
instance_id: string | undefined;
|
||||||
license_key: string | undefined;
|
license_key: string | undefined;
|
||||||
api_key: string | undefined;
|
current_version: string | undefined;
|
||||||
version: string | undefined;
|
latest_version: string | undefined;
|
||||||
last_checked_at: string | undefined;
|
last_checked_at: string | undefined;
|
||||||
namespace: string | undefined;
|
namespace: string | undefined;
|
||||||
is_telemetry_enabled: boolean;
|
is_telemetry_enabled: boolean;
|
||||||
|
2
packages/types/src/pages.d.ts
vendored
2
packages/types/src/pages.d.ts
vendored
@ -1,3 +1,4 @@
|
|||||||
|
import { TLogoProps } from "./common";
|
||||||
import { EPageAccess } from "./enums";
|
import { EPageAccess } from "./enums";
|
||||||
|
|
||||||
export type TPage = {
|
export type TPage = {
|
||||||
@ -17,6 +18,7 @@ export type TPage = {
|
|||||||
updated_at: Date | undefined;
|
updated_at: Date | undefined;
|
||||||
updated_by: string | undefined;
|
updated_by: string | undefined;
|
||||||
workspace: string | undefined;
|
workspace: string | undefined;
|
||||||
|
logo_props: TLogoProps | undefined;
|
||||||
};
|
};
|
||||||
|
|
||||||
// page filters
|
// page filters
|
||||||
|
15
packages/types/src/project/projects.d.ts
vendored
15
packages/types/src/project/projects.d.ts
vendored
@ -6,21 +6,10 @@ import type {
|
|||||||
IUserMemberLite,
|
IUserMemberLite,
|
||||||
IWorkspace,
|
IWorkspace,
|
||||||
IWorkspaceLite,
|
IWorkspaceLite,
|
||||||
|
TLogoProps,
|
||||||
TStateGroups,
|
TStateGroups,
|
||||||
} from "..";
|
} from "..";
|
||||||
|
|
||||||
export type TProjectLogoProps = {
|
|
||||||
in_use: "emoji" | "icon";
|
|
||||||
emoji?: {
|
|
||||||
value?: string;
|
|
||||||
url?: string;
|
|
||||||
};
|
|
||||||
icon?: {
|
|
||||||
name?: string;
|
|
||||||
color?: string;
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
export interface IProject {
|
export interface IProject {
|
||||||
archive_in: number;
|
archive_in: number;
|
||||||
archived_at: string | null;
|
archived_at: string | null;
|
||||||
@ -46,7 +35,7 @@ export interface IProject {
|
|||||||
is_deployed: boolean;
|
is_deployed: boolean;
|
||||||
is_favorite: boolean;
|
is_favorite: boolean;
|
||||||
is_member: boolean;
|
is_member: boolean;
|
||||||
logo_props: TProjectLogoProps;
|
logo_props: TLogoProps;
|
||||||
member_role: EUserProjectRoles | null;
|
member_role: EUserProjectRoles | null;
|
||||||
members: IProjectMemberLite[];
|
members: IProjectMemberLite[];
|
||||||
name: string;
|
name: string;
|
||||||
|
2
packages/types/src/views.d.ts
vendored
2
packages/types/src/views.d.ts
vendored
@ -1,3 +1,4 @@
|
|||||||
|
import { TLogoProps } from "./common";
|
||||||
import {
|
import {
|
||||||
IIssueDisplayFilterOptions,
|
IIssueDisplayFilterOptions,
|
||||||
IIssueDisplayProperties,
|
IIssueDisplayProperties,
|
||||||
@ -21,4 +22,5 @@ export interface IProjectView {
|
|||||||
query_data: IIssueFilterOptions;
|
query_data: IIssueFilterOptions;
|
||||||
project: string;
|
project: string;
|
||||||
workspace: string;
|
workspace: string;
|
||||||
|
logo_props: TLogoProps | undefined;
|
||||||
}
|
}
|
||||||
|
@ -2,7 +2,7 @@
|
|||||||
"name": "@plane/ui",
|
"name": "@plane/ui",
|
||||||
"description": "UI components shared across multiple apps internally",
|
"description": "UI components shared across multiple apps internally",
|
||||||
"private": true,
|
"private": true,
|
||||||
"version": "0.20.0",
|
"version": "0.21.0",
|
||||||
"main": "./dist/index.js",
|
"main": "./dist/index.js",
|
||||||
"module": "./dist/index.mjs",
|
"module": "./dist/index.mjs",
|
||||||
"types": "./dist/index.d.ts",
|
"types": "./dist/index.d.ts",
|
||||||
@ -26,6 +26,7 @@
|
|||||||
"@popperjs/core": "^2.11.8",
|
"@popperjs/core": "^2.11.8",
|
||||||
"clsx": "^2.0.0",
|
"clsx": "^2.0.0",
|
||||||
"emoji-picker-react": "^4.5.16",
|
"emoji-picker-react": "^4.5.16",
|
||||||
|
"lucide-react": "^0.379.0",
|
||||||
"react-color": "^2.19.3",
|
"react-color": "^2.19.3",
|
||||||
"react-dom": "^18.2.0",
|
"react-dom": "^18.2.0",
|
||||||
"react-popper": "^2.3.0",
|
"react-popper": "^2.3.0",
|
||||||
|
@ -2,7 +2,7 @@ import * as React from "react";
|
|||||||
|
|
||||||
export type TControlLink = React.AnchorHTMLAttributes<HTMLAnchorElement> & {
|
export type TControlLink = React.AnchorHTMLAttributes<HTMLAnchorElement> & {
|
||||||
href: string;
|
href: string;
|
||||||
onClick: () => void;
|
onClick: (event: React.MouseEvent<HTMLAnchorElement>) => void;
|
||||||
children: React.ReactNode;
|
children: React.ReactNode;
|
||||||
target?: string;
|
target?: string;
|
||||||
disabled?: boolean;
|
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;
|
const clickCondition = (event.metaKey || event.ctrlKey) && event.button === LEFT_CLICK_EVENT_CODE;
|
||||||
if (!clickCondition) {
|
if (!clickCondition) {
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
onClick();
|
onClick(event);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -1,14 +1,15 @@
|
|||||||
import React from "react";
|
import React, { forwardRef } from "react";
|
||||||
import { forwardRef } from "react";
|
|
||||||
import { MoreVertical } from "lucide-react";
|
import { MoreVertical } from "lucide-react";
|
||||||
|
// helpers
|
||||||
|
import { cn } from "../helpers";
|
||||||
|
|
||||||
interface IDragHandle {
|
interface IDragHandle {
|
||||||
isDragging: boolean;
|
className?: string;
|
||||||
disabled?: boolean;
|
disabled?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const DragHandle = forwardRef<HTMLButtonElement | null, IDragHandle>((props, ref) => {
|
export const DragHandle = forwardRef<HTMLButtonElement | null, IDragHandle>((props, ref) => {
|
||||||
const { isDragging, disabled = false } = props;
|
const { className, disabled = false } = props;
|
||||||
|
|
||||||
if (disabled) {
|
if (disabled) {
|
||||||
return <div className="w-[14px] h-[18px]" />;
|
return <div className="w-[14px] h-[18px]" />;
|
||||||
@ -17,9 +18,10 @@ export const DragHandle = forwardRef<HTMLButtonElement | null, IDragHandle>((pro
|
|||||||
return (
|
return (
|
||||||
<button
|
<button
|
||||||
type="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 ${
|
className={cn(
|
||||||
isDragging ? "opacity-100" : "opacity-0"
|
"p-0.5 flex flex-shrink-0 rounded bg-custom-background-90 text-custom-sidebar-text-200 cursor-grab",
|
||||||
}`}
|
className
|
||||||
|
)}
|
||||||
onContextMenu={(e) => {
|
onContextMenu={(e) => {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
e.stopPropagation();
|
e.stopPropagation();
|
||||||
|
100
packages/ui/src/emoji/emoji-icon-helper.tsx
Normal file
100
packages/ui/src/emoji/emoji-icon-helper.tsx
Normal 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)}`;
|
||||||
|
};
|
135
packages/ui/src/emoji/emoji-icon-picker-new.tsx
Normal file
135
packages/ui/src/emoji/emoji-icon-picker-new.tsx
Normal 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>
|
||||||
|
);
|
||||||
|
};
|
@ -1,63 +1,23 @@
|
|||||||
import React, { useState } from "react";
|
import React, { useRef, useState } from "react";
|
||||||
import { usePopper } from "react-popper";
|
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 { Popover, Tab } from "@headlessui/react";
|
||||||
import { Placement } from "@popperjs/core";
|
|
||||||
// components
|
// components
|
||||||
import { IconsList } from "./icons-list";
|
import { IconsList } from "./icons-list";
|
||||||
// helpers
|
// helpers
|
||||||
import { cn } from "../../helpers";
|
import { cn } from "../../helpers";
|
||||||
|
// hooks
|
||||||
export enum EmojiIconPickerTypes {
|
import useOutsideClickDetector from "../hooks/use-outside-click-detector";
|
||||||
EMOJI = "emoji",
|
import { EmojiIconPickerTypes, TABS_LIST, TCustomEmojiPicker } from "./emoji-icon-helper";
|
||||||
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",
|
|
||||||
},
|
|
||||||
];
|
|
||||||
|
|
||||||
export const CustomEmojiIconPicker: React.FC<TCustomEmojiPicker> = (props) => {
|
export const CustomEmojiIconPicker: React.FC<TCustomEmojiPicker> = (props) => {
|
||||||
const {
|
const {
|
||||||
|
isOpen,
|
||||||
|
handleToggle,
|
||||||
buttonClassName,
|
buttonClassName,
|
||||||
className,
|
className,
|
||||||
closeOnSelect = true,
|
closeOnSelect = true,
|
||||||
defaultIconColor = "#5f5f5f",
|
defaultIconColor = "#6d7b8a",
|
||||||
defaultOpen = EmojiIconPickerTypes.EMOJI,
|
defaultOpen = EmojiIconPickerTypes.EMOJI,
|
||||||
disabled = false,
|
disabled = false,
|
||||||
dropdownClassName,
|
dropdownClassName,
|
||||||
@ -68,6 +28,7 @@ export const CustomEmojiIconPicker: React.FC<TCustomEmojiPicker> = (props) => {
|
|||||||
theme,
|
theme,
|
||||||
} = props;
|
} = props;
|
||||||
// refs
|
// refs
|
||||||
|
const containerRef = useRef<HTMLDivElement>(null);
|
||||||
const [referenceElement, setReferenceElement] = useState<HTMLButtonElement | null>(null);
|
const [referenceElement, setReferenceElement] = useState<HTMLButtonElement | null>(null);
|
||||||
const [popperElement, setPopperElement] = useState<HTMLDivElement | null>(null);
|
const [popperElement, setPopperElement] = useState<HTMLDivElement | null>(null);
|
||||||
// popper-js
|
// popper-js
|
||||||
@ -83,21 +44,25 @@ export const CustomEmojiIconPicker: React.FC<TCustomEmojiPicker> = (props) => {
|
|||||||
],
|
],
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// close dropdown on outside click
|
||||||
|
useOutsideClickDetector(containerRef, () => handleToggle(false));
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Popover as="div" className={cn("relative", className)}>
|
<Popover as="div" className={cn("relative", className)}>
|
||||||
{({ close }) => (
|
<>
|
||||||
<>
|
<Popover.Button as={React.Fragment}>
|
||||||
<Popover.Button as={React.Fragment}>
|
<button
|
||||||
<button
|
type="button"
|
||||||
type="button"
|
ref={setReferenceElement}
|
||||||
ref={setReferenceElement}
|
className={cn("outline-none", buttonClassName)}
|
||||||
className={cn("outline-none", buttonClassName)}
|
disabled={disabled}
|
||||||
disabled={disabled}
|
onClick={() => handleToggle(!isOpen)}
|
||||||
>
|
>
|
||||||
{label}
|
{label}
|
||||||
</button>
|
</button>
|
||||||
</Popover.Button>
|
</Popover.Button>
|
||||||
<Popover.Panel className="fixed z-10">
|
{isOpen && (
|
||||||
|
<Popover.Panel className="fixed z-10" static>
|
||||||
<div
|
<div
|
||||||
ref={setPopperElement}
|
ref={setPopperElement}
|
||||||
style={styles.popper}
|
style={styles.popper}
|
||||||
@ -108,6 +73,7 @@ export const CustomEmojiIconPicker: React.FC<TCustomEmojiPicker> = (props) => {
|
|||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
<Tab.Group
|
<Tab.Group
|
||||||
|
ref={containerRef}
|
||||||
as="div"
|
as="div"
|
||||||
className="h-full w-full flex flex-col overflow-hidden"
|
className="h-full w-full flex flex-col overflow-hidden"
|
||||||
defaultIndex={TABS_LIST.findIndex((tab) => tab.key === defaultOpen)}
|
defaultIndex={TABS_LIST.findIndex((tab) => tab.key === defaultOpen)}
|
||||||
@ -162,8 +128,8 @@ export const CustomEmojiIconPicker: React.FC<TCustomEmojiPicker> = (props) => {
|
|||||||
</Tab.Group>
|
</Tab.Group>
|
||||||
</div>
|
</div>
|
||||||
</Popover.Panel>
|
</Popover.Panel>
|
||||||
</>
|
)}
|
||||||
)}
|
</>
|
||||||
</Popover>
|
</Popover>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
@ -3,15 +3,11 @@ import React, { useEffect, useState } from "react";
|
|||||||
import { Input } from "../form-fields";
|
import { Input } from "../form-fields";
|
||||||
// helpers
|
// helpers
|
||||||
import { cn } from "../../helpers";
|
import { cn } from "../../helpers";
|
||||||
// constants
|
import { DEFAULT_COLORS, TIconsListProps, adjustColorForContrast } from "./emoji-icon-helper";
|
||||||
|
// icons
|
||||||
import { MATERIAL_ICONS_LIST } from "./icons";
|
import { MATERIAL_ICONS_LIST } from "./icons";
|
||||||
|
import { InfoIcon } from "../icons";
|
||||||
type TIconsListProps = {
|
import { Search } from "lucide-react";
|
||||||
defaultColor: string;
|
|
||||||
onChange: (val: { name: string; color: string }) => void;
|
|
||||||
};
|
|
||||||
|
|
||||||
const DEFAULT_COLORS = ["#ff6b00", "#8cc1ff", "#fcbe1d", "#18904f", "#adf672", "#05c3ff", "#5f5f5f"];
|
|
||||||
|
|
||||||
export const IconsList: React.FC<TIconsListProps> = (props) => {
|
export const IconsList: React.FC<TIconsListProps> = (props) => {
|
||||||
const { defaultColor, onChange } = props;
|
const { defaultColor, onChange } = props;
|
||||||
@ -19,6 +15,8 @@ export const IconsList: React.FC<TIconsListProps> = (props) => {
|
|||||||
const [activeColor, setActiveColor] = useState(defaultColor);
|
const [activeColor, setActiveColor] = useState(defaultColor);
|
||||||
const [showHexInput, setShowHexInput] = useState(false);
|
const [showHexInput, setShowHexInput] = useState(false);
|
||||||
const [hexValue, setHexValue] = useState("");
|
const [hexValue, setHexValue] = useState("");
|
||||||
|
const [isInputFocused, setIsInputFocused] = useState(false);
|
||||||
|
const [query, setQuery] = useState("");
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (DEFAULT_COLORS.includes(defaultColor.toLowerCase())) setShowHexInput(false);
|
if (DEFAULT_COLORS.includes(defaultColor.toLowerCase())) setShowHexInput(false);
|
||||||
@ -28,11 +26,28 @@ export const IconsList: React.FC<TIconsListProps> = (props) => {
|
|||||||
}
|
}
|
||||||
}, [defaultColor]);
|
}, [defaultColor]);
|
||||||
|
|
||||||
|
const filteredArray = MATERIAL_ICONS_LIST.filter((icon) => icon.name.toLowerCase().includes(query.toLowerCase()));
|
||||||
|
|
||||||
return (
|
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 ? (
|
{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
|
<span
|
||||||
className="h-4 w-4 flex-shrink-0 rounded-full mr-1"
|
className="h-4 w-4 flex-shrink-0 rounded-full mr-1"
|
||||||
style={{
|
style={{
|
||||||
@ -47,7 +62,7 @@ export const IconsList: React.FC<TIconsListProps> = (props) => {
|
|||||||
onChange={(e) => {
|
onChange={(e) => {
|
||||||
const value = e.target.value;
|
const value = e.target.value;
|
||||||
setHexValue(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"
|
className="flex-grow pl-0 text-xs text-custom-text-200"
|
||||||
mode="true-transparent"
|
mode="true-transparent"
|
||||||
@ -59,7 +74,7 @@ export const IconsList: React.FC<TIconsListProps> = (props) => {
|
|||||||
<button
|
<button
|
||||||
key={curCol}
|
key={curCol}
|
||||||
type="button"
|
type="button"
|
||||||
className="grid place-items-center"
|
className="grid place-items-center size-5"
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
setActiveColor(curCol);
|
setActiveColor(curCol);
|
||||||
setHexValue(curCol.slice(1, 7));
|
setHexValue(curCol.slice(1, 7));
|
||||||
@ -86,12 +101,16 @@ export const IconsList: React.FC<TIconsListProps> = (props) => {
|
|||||||
)}
|
)}
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
<div className="grid grid-cols-8 gap-2 px-2.5 justify-items-center mt-2">
|
<div className="flex items-center gap-2 w-full pl-4 pr-3 py-1 h-6">
|
||||||
{MATERIAL_ICONS_LIST.map((icon) => (
|
<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
|
<button
|
||||||
key={icon.name}
|
key={icon.name}
|
||||||
type="button"
|
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={() => {
|
onClick={() => {
|
||||||
onChange({
|
onChange({
|
||||||
name: icon.name,
|
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}
|
{icon.name}
|
||||||
</span>
|
</span>
|
||||||
</button>
|
</button>
|
||||||
|
@ -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 = [
|
export const MATERIAL_ICONS_LIST = [
|
||||||
{
|
{
|
||||||
name: "search",
|
name: "search",
|
||||||
@ -603,3 +756,156 @@ export const MATERIAL_ICONS_LIST = [
|
|||||||
name: "skull",
|
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 },
|
||||||
|
];
|
||||||
|
@ -1 +1,4 @@
|
|||||||
|
export * from "./emoji-icon-picker-new";
|
||||||
export * from "./emoji-icon-picker";
|
export * from "./emoji-icon-picker";
|
||||||
|
export * from "./emoji-icon-helper";
|
||||||
|
export * from "./icons";
|
||||||
|
128
packages/ui/src/emoji/lucide-icons-list.tsx
Normal file
128
packages/ui/src/emoji/lucide-icons-list.tsx
Normal 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>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
};
|
@ -3,15 +3,26 @@ import * as React from "react";
|
|||||||
import { cn } from "../../helpers";
|
import { cn } from "../../helpers";
|
||||||
|
|
||||||
export interface CheckboxProps extends React.InputHTMLAttributes<HTMLInputElement> {
|
export interface CheckboxProps extends React.InputHTMLAttributes<HTMLInputElement> {
|
||||||
intermediate?: boolean;
|
containerClassName?: string;
|
||||||
className?: string;
|
iconClassName?: string;
|
||||||
|
indeterminate?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
const Checkbox = React.forwardRef<HTMLInputElement, CheckboxProps>((props, ref) => {
|
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 (
|
return (
|
||||||
<div className={cn("relative w-full flex gap-2", className)}>
|
<div className={cn("relative flex-shrink-0 flex gap-2", containerClassName)}>
|
||||||
<input
|
<input
|
||||||
id={id}
|
id={id}
|
||||||
ref={ref}
|
ref={ref}
|
||||||
@ -19,22 +30,27 @@ const Checkbox = React.forwardRef<HTMLInputElement, CheckboxProps>((props, ref)
|
|||||||
name={name}
|
name={name}
|
||||||
checked={checked}
|
checked={checked}
|
||||||
className={cn(
|
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,
|
"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-border-300 hover:border-custom-border-400 bg-transparent": !disabled,
|
||||||
"border-custom-primary-40 bg-custom-primary-100 hover:bg-custom-primary-200":
|
"border-custom-primary-40 hover:border-custom-primary-40 bg-custom-primary-100 hover:bg-custom-primary-200":
|
||||||
!disabled && (checked || intermediate),
|
!disabled && (checked || indeterminate),
|
||||||
}
|
},
|
||||||
|
className
|
||||||
)}
|
)}
|
||||||
disabled={disabled}
|
disabled={disabled}
|
||||||
{...rest}
|
{...rest}
|
||||||
/>
|
/>
|
||||||
<svg
|
<svg
|
||||||
className={cn("absolute w-4 h-4 p-0.5 pointer-events-none outline-none hidden stroke-white", {
|
className={cn(
|
||||||
block: checked,
|
"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",
|
||||||
"stroke-custom-text-400 opacity-40": disabled,
|
{
|
||||||
})}
|
block: checked,
|
||||||
|
"stroke-custom-text-400 opacity-40": disabled,
|
||||||
|
},
|
||||||
|
iconClassName
|
||||||
|
)}
|
||||||
xmlns="http://www.w3.org/2000/svg"
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
viewBox="0 0 24 24"
|
viewBox="0 0 24 24"
|
||||||
fill="none"
|
fill="none"
|
||||||
@ -46,10 +62,14 @@ const Checkbox = React.forwardRef<HTMLInputElement, CheckboxProps>((props, ref)
|
|||||||
<polyline points="20 6 9 17 4 12" />
|
<polyline points="20 6 9 17 4 12" />
|
||||||
</svg>
|
</svg>
|
||||||
<svg
|
<svg
|
||||||
className={cn("absolute w-4 h-4 p-0.5 pointer-events-none outline-none stroke-white hidden", {
|
className={cn(
|
||||||
"stroke-custom-text-400 opacity-40": disabled,
|
"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",
|
||||||
block: intermediate && !checked,
|
{
|
||||||
})}
|
"stroke-custom-text-400 opacity-40": disabled,
|
||||||
|
block: indeterminate && !checked,
|
||||||
|
},
|
||||||
|
iconClassName
|
||||||
|
)}
|
||||||
xmlns="http://www.w3.org/2000/svg"
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
viewBox="0 0 8 8"
|
viewBox="0 0 8 8"
|
||||||
fill="none"
|
fill="none"
|
||||||
|
@ -19,3 +19,4 @@ export * from "./priority-icon";
|
|||||||
export * from "./related-icon";
|
export * from "./related-icon";
|
||||||
export * from "./side-panel-icon";
|
export * from "./side-panel-icon";
|
||||||
export * from "./transfer-icon";
|
export * from "./transfer-icon";
|
||||||
|
export * from "./info-icon";
|
||||||
|
21
packages/ui/src/icons/info-icon.tsx
Normal file
21
packages/ui/src/icons/info-icon.tsx
Normal 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>
|
||||||
|
);
|
@ -1,11 +1,11 @@
|
|||||||
// helpers
|
|
||||||
import { TProjectLogoProps } from "@plane/types";
|
|
||||||
import { cn } from "@/helpers/common.helper";
|
|
||||||
// types
|
// types
|
||||||
|
import { TLogoProps } from "@plane/types";
|
||||||
|
// helpers
|
||||||
|
import { cn } from "@/helpers/common.helper";
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
className?: string;
|
className?: string;
|
||||||
logo: TProjectLogoProps;
|
logo: TLogoProps;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const ProjectLogo: React.FC<Props> = (props) => {
|
export const ProjectLogo: React.FC<Props> = (props) => {
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "space",
|
"name": "space",
|
||||||
"version": "0.20.0",
|
"version": "0.21.0",
|
||||||
"private": true,
|
"private": true,
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"dev": "turbo run develop",
|
"dev": "turbo run develop",
|
||||||
|
4
space/types/project.d.ts
vendored
4
space/types/project.d.ts
vendored
@ -1,4 +1,4 @@
|
|||||||
import { TProjectLogoProps } from "@plane/types";
|
import { TLogoProps } from "@plane/types";
|
||||||
|
|
||||||
export type TWorkspaceDetails = {
|
export type TWorkspaceDetails = {
|
||||||
name: string;
|
name: string;
|
||||||
@ -19,7 +19,7 @@ export type TProjectDetails = {
|
|||||||
identifier: string;
|
identifier: string;
|
||||||
name: string;
|
name: string;
|
||||||
cover_image: string | undefined;
|
cover_image: string | undefined;
|
||||||
logo_props: TProjectLogoProps;
|
logo_props: TLogoProps;
|
||||||
description: string;
|
description: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -1,10 +1,11 @@
|
|||||||
import { observer } from "mobx-react";
|
import { observer } from "mobx-react";
|
||||||
// hooks
|
|
||||||
// icons
|
// icons
|
||||||
import { Contrast, LayoutGrid, Users } from "lucide-react";
|
import { Contrast, LayoutGrid, Users } from "lucide-react";
|
||||||
|
// components
|
||||||
|
import { Logo } from "@/components/common";
|
||||||
// helpers
|
// helpers
|
||||||
import { ProjectLogo } from "@/components/project";
|
|
||||||
import { truncateText } from "@/helpers/string.helper";
|
import { truncateText } from "@/helpers/string.helper";
|
||||||
|
// hooks
|
||||||
import { useProject } from "@/hooks/store";
|
import { useProject } from "@/hooks/store";
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
@ -29,7 +30,7 @@ export const CustomAnalyticsSidebarProjectsList: React.FC<Props> = observer((pro
|
|||||||
<div key={projectId} className="w-full">
|
<div key={projectId} className="w-full">
|
||||||
<div className="flex items-center gap-1 text-sm">
|
<div className="flex items-center gap-1 text-sm">
|
||||||
<div className="h-6 w-6 grid place-items-center">
|
<div className="h-6 w-6 grid place-items-center">
|
||||||
<ProjectLogo logo={project.logo_props} />
|
<Logo logo={project.logo_props} />
|
||||||
</div>
|
</div>
|
||||||
<h5 className="flex items-center gap-1">
|
<h5 className="flex items-center gap-1">
|
||||||
<p className="break-words">{truncateText(project.name, 20)}</p>
|
<p className="break-words">{truncateText(project.name, 20)}</p>
|
||||||
|
@ -1,13 +1,13 @@
|
|||||||
import { observer } from "mobx-react";
|
import { observer } from "mobx-react";
|
||||||
import { useRouter } from "next/router";
|
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
|
// components
|
||||||
// helpers
|
import { Logo } from "@/components/common";
|
||||||
// constants
|
// 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(() => {
|
export const CustomAnalyticsSidebarHeader = observer(() => {
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
@ -84,7 +84,7 @@ export const CustomAnalyticsSidebarHeader = observer(() => {
|
|||||||
<div className="flex items-center gap-1">
|
<div className="flex items-center gap-1">
|
||||||
{projectDetails && (
|
{projectDetails && (
|
||||||
<span className="h-6 w-6 grid place-items-center flex-shrink-0">
|
<span className="h-6 w-6 grid place-items-center flex-shrink-0">
|
||||||
<ProjectLogo logo={projectDetails.logo_props} />
|
<Logo logo={projectDetails.logo_props} />
|
||||||
</span>
|
</span>
|
||||||
)}
|
)}
|
||||||
<h4 className="break-words font-medium">{projectDetails?.name}</h4>
|
<h4 className="break-words font-medium">{projectDetails?.name}</h4>
|
||||||
|
@ -69,7 +69,7 @@ export const DeleteApiTokenModal: FC<Props> = (props) => {
|
|||||||
<AlertModalCore
|
<AlertModalCore
|
||||||
handleClose={handleClose}
|
handleClose={handleClose}
|
||||||
handleSubmit={handleDeletion}
|
handleSubmit={handleDeletion}
|
||||||
isDeleting={deleteLoading}
|
isSubmitting={deleteLoading}
|
||||||
isOpen={isOpen}
|
isOpen={isOpen}
|
||||||
title="Delete API token"
|
title="Delete API token"
|
||||||
content={
|
content={
|
||||||
|
@ -71,7 +71,7 @@ export const CommandPaletteProjectActions: React.FC<Props> = (props) => {
|
|||||||
onSelect={() => {
|
onSelect={() => {
|
||||||
closePalette();
|
closePalette();
|
||||||
setTrackElement("Command palette");
|
setTrackElement("Command palette");
|
||||||
toggleCreatePageModal(true);
|
toggleCreatePageModal({ isOpen: true });
|
||||||
}}
|
}}
|
||||||
className="focus:outline-none"
|
className="focus:outline-none"
|
||||||
>
|
>
|
||||||
|
@ -50,7 +50,7 @@ export const CommandPalette: FC = observer(() => {
|
|||||||
toggleCreateIssueModal,
|
toggleCreateIssueModal,
|
||||||
isCreateCycleModalOpen,
|
isCreateCycleModalOpen,
|
||||||
toggleCreateCycleModal,
|
toggleCreateCycleModal,
|
||||||
isCreatePageModalOpen,
|
createPageModal,
|
||||||
toggleCreatePageModal,
|
toggleCreatePageModal,
|
||||||
isCreateProjectModalOpen,
|
isCreateProjectModalOpen,
|
||||||
toggleCreateProjectModal,
|
toggleCreateProjectModal,
|
||||||
@ -150,7 +150,7 @@ export const CommandPalette: FC = observer(() => {
|
|||||||
d: {
|
d: {
|
||||||
title: "Create a new page",
|
title: "Create a new page",
|
||||||
description: "Create a new page in the current project",
|
description: "Create a new page in the current project",
|
||||||
action: () => toggleCreatePageModal(true),
|
action: () => toggleCreatePageModal({ isOpen: true }),
|
||||||
},
|
},
|
||||||
m: {
|
m: {
|
||||||
title: "Create a new module",
|
title: "Create a new module",
|
||||||
@ -297,8 +297,9 @@ export const CommandPalette: FC = observer(() => {
|
|||||||
<CreatePageModal
|
<CreatePageModal
|
||||||
workspaceSlug={workspaceSlug.toString()}
|
workspaceSlug={workspaceSlug.toString()}
|
||||||
projectId={projectId.toString()}
|
projectId={projectId.toString()}
|
||||||
isModalOpen={isCreatePageModalOpen}
|
isModalOpen={createPageModal.isOpen}
|
||||||
handleModalClose={() => toggleCreatePageModal(false)}
|
pageAccess={createPageModal.pageAccess}
|
||||||
|
handleModalClose={() => toggleCreatePageModal({ isOpen: false })}
|
||||||
redirectionEnabled
|
redirectionEnabled
|
||||||
/>
|
/>
|
||||||
</>
|
</>
|
||||||
|
@ -3,3 +3,4 @@ export * from "./empty-state";
|
|||||||
export * from "./latest-feature-block";
|
export * from "./latest-feature-block";
|
||||||
export * from "./breadcrumb-link";
|
export * from "./breadcrumb-link";
|
||||||
export * from "./logo-spinner";
|
export * from "./logo-spinner";
|
||||||
|
export * from "./logo";
|
||||||
|
69
web/components/common/logo.tsx
Normal file
69
web/components/common/logo.tsx
Normal 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 <></>;
|
||||||
|
};
|
@ -1,5 +1,6 @@
|
|||||||
export * from "./filters";
|
export * from "./filters";
|
||||||
export * from "./modals";
|
export * from "./modals";
|
||||||
|
export * from "./multiple-select";
|
||||||
export * from "./sidebar";
|
export * from "./sidebar";
|
||||||
export * from "./activity";
|
export * from "./activity";
|
||||||
export * from "./favorite-star";
|
export * from "./favorite-star";
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
import React, { FC } from "react";
|
import React, { FC } from "react";
|
||||||
import Link from "next/link";
|
import { useRouter } from "next/router";
|
||||||
// ui
|
// ui
|
||||||
import { Tooltip } from "@plane/ui";
|
import { ControlLink, Tooltip } from "@plane/ui";
|
||||||
// helpers
|
// helpers
|
||||||
import { cn } from "@/helpers/common.helper";
|
import { cn } from "@/helpers/common.helper";
|
||||||
|
|
||||||
@ -14,6 +14,7 @@ interface IListItemProps {
|
|||||||
actionableItems?: JSX.Element;
|
actionableItems?: JSX.Element;
|
||||||
isMobile?: boolean;
|
isMobile?: boolean;
|
||||||
parentRef: React.RefObject<HTMLDivElement>;
|
parentRef: React.RefObject<HTMLDivElement>;
|
||||||
|
disableLink?: boolean;
|
||||||
className?: string;
|
className?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -27,12 +28,22 @@ export const ListItem: FC<IListItemProps> = (props) => {
|
|||||||
onItemClick,
|
onItemClick,
|
||||||
isMobile = false,
|
isMobile = false,
|
||||||
parentRef,
|
parentRef,
|
||||||
|
disableLink = false,
|
||||||
className = "",
|
className = "",
|
||||||
} = props;
|
} = props;
|
||||||
|
|
||||||
|
// router
|
||||||
|
const router = useRouter();
|
||||||
|
|
||||||
|
// handlers
|
||||||
|
const handleControlLinkClick = (e: React.MouseEvent<HTMLAnchorElement>) => {
|
||||||
|
if (onItemClick) onItemClick(e);
|
||||||
|
else router.push(itemLink);
|
||||||
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div ref={parentRef} className="relative">
|
<div ref={parentRef} className="relative">
|
||||||
<Link href={itemLink} onClick={onItemClick}>
|
<ControlLink href={itemLink} onClick={handleControlLinkClick} disabled={disableLink}>
|
||||||
<div
|
<div
|
||||||
className={cn(
|
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",
|
"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>
|
</div>
|
||||||
<span className="h-6 w-96 flex-shrink-0" />
|
<span className="h-6 w-96 flex-shrink-0" />
|
||||||
</div>
|
</div>
|
||||||
</Link>
|
</ControlLink>
|
||||||
{actionableItems && (
|
{actionableItems && (
|
||||||
<div className="absolute right-5 bottom-4 flex items-center gap-1.5">
|
<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">
|
<div className="relative flex items-center gap-4 sm:w-auto sm:flex-shrink-0 sm:justify-end">
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
import { AlertTriangle, LucideIcon } from "lucide-react";
|
import { AlertTriangle, Info, LucideIcon } from "lucide-react";
|
||||||
// ui
|
// ui
|
||||||
import { Button, TButtonVariant } from "@plane/ui";
|
import { Button, TButtonVariant } from "@plane/ui";
|
||||||
// components
|
// components
|
||||||
@ -6,14 +6,14 @@ import { EModalPosition, EModalWidth, ModalCore } from "@/components/core";
|
|||||||
// helpers
|
// helpers
|
||||||
import { cn } from "@/helpers/common.helper";
|
import { cn } from "@/helpers/common.helper";
|
||||||
|
|
||||||
export type TModalVariant = "danger";
|
export type TModalVariant = "danger" | "primary";
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
content: React.ReactNode | string;
|
content: React.ReactNode | string;
|
||||||
handleClose: () => void;
|
handleClose: () => void;
|
||||||
handleSubmit: () => Promise<void>;
|
handleSubmit: () => Promise<void>;
|
||||||
hideIcon?: boolean;
|
hideIcon?: boolean;
|
||||||
isDeleting: boolean;
|
isSubmitting: boolean;
|
||||||
isOpen: boolean;
|
isOpen: boolean;
|
||||||
position?: EModalPosition;
|
position?: EModalPosition;
|
||||||
primaryButtonText?: {
|
primaryButtonText?: {
|
||||||
@ -28,14 +28,17 @@ type Props = {
|
|||||||
|
|
||||||
const VARIANT_ICONS: Record<TModalVariant, LucideIcon> = {
|
const VARIANT_ICONS: Record<TModalVariant, LucideIcon> = {
|
||||||
danger: AlertTriangle,
|
danger: AlertTriangle,
|
||||||
|
primary: Info,
|
||||||
};
|
};
|
||||||
|
|
||||||
const BUTTON_VARIANTS: Record<TModalVariant, TButtonVariant> = {
|
const BUTTON_VARIANTS: Record<TModalVariant, TButtonVariant> = {
|
||||||
danger: "danger",
|
danger: "danger",
|
||||||
|
primary: "primary",
|
||||||
};
|
};
|
||||||
|
|
||||||
const VARIANT_CLASSES: Record<TModalVariant, string> = {
|
const VARIANT_CLASSES: Record<TModalVariant, string> = {
|
||||||
danger: "bg-red-500/20 text-red-500",
|
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) => {
|
export const AlertModalCore: React.FC<Props> = (props) => {
|
||||||
@ -44,7 +47,7 @@ export const AlertModalCore: React.FC<Props> = (props) => {
|
|||||||
handleClose,
|
handleClose,
|
||||||
handleSubmit,
|
handleSubmit,
|
||||||
hideIcon = false,
|
hideIcon = false,
|
||||||
isDeleting,
|
isSubmitting,
|
||||||
isOpen,
|
isOpen,
|
||||||
position = EModalPosition.CENTER,
|
position = EModalPosition.CENTER,
|
||||||
primaryButtonText = {
|
primaryButtonText = {
|
||||||
@ -81,8 +84,8 @@ export const AlertModalCore: React.FC<Props> = (props) => {
|
|||||||
<Button variant="neutral-primary" size="sm" onClick={handleClose}>
|
<Button variant="neutral-primary" size="sm" onClick={handleClose}>
|
||||||
{secondaryButtonText}
|
{secondaryButtonText}
|
||||||
</Button>
|
</Button>
|
||||||
<Button variant={BUTTON_VARIANTS[variant]} size="sm" tabIndex={1} onClick={handleSubmit} loading={isDeleting}>
|
<Button variant={BUTTON_VARIANTS[variant]} size="sm" tabIndex={1} onClick={handleSubmit} loading={isSubmitting}>
|
||||||
{isDeleting ? primaryButtonText.loading : primaryButtonText.default}
|
{isSubmitting ? primaryButtonText.loading : primaryButtonText.default}
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
</ModalCore>
|
</ModalCore>
|
||||||
|
@ -179,7 +179,9 @@ export const GptAssistantPopover: React.FC<Props> = (props) => {
|
|||||||
return (
|
return (
|
||||||
<Popover as="div" className={`relative w-min text-left`}>
|
<Popover as="div" className={`relative w-min text-left`}>
|
||||||
<Popover.Button as={Fragment}>
|
<Popover.Button as={Fragment}>
|
||||||
<button ref={setReferenceElement}>{button}</button>
|
<button ref={setReferenceElement} className="flex items-center">
|
||||||
|
{button}
|
||||||
|
</button>
|
||||||
</Popover.Button>
|
</Popover.Button>
|
||||||
<Transition
|
<Transition
|
||||||
show={isOpen}
|
show={isOpen}
|
||||||
@ -208,11 +210,7 @@ export const GptAssistantPopover: React.FC<Props> = (props) => {
|
|||||||
{response !== "" && (
|
{response !== "" && (
|
||||||
<div className="page-block-section max-h-[8rem] text-sm">
|
<div className="page-block-section max-h-[8rem] text-sm">
|
||||||
Response:
|
Response:
|
||||||
<RichTextReadOnlyEditor
|
<RichTextReadOnlyEditor initialValue={`<p>${response}</p>`} ref={responseRef} />
|
||||||
initialValue={`<p>${response}</p>`}
|
|
||||||
containerClassName={response ? "-mx-3 -my-3" : ""}
|
|
||||||
ref={responseRef}
|
|
||||||
/>
|
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
{invalidResponse && (
|
{invalidResponse && (
|
||||||
|
36
web/components/core/multiple-select/entity-select-action.tsx
Normal file
36
web/components/core/multiple-select/entity-select-action.tsx
Normal 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
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
};
|
30
web/components/core/multiple-select/group-select-action.tsx
Normal file
30
web/components/core/multiple-select/group-select-action.tsx
Normal 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}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
};
|
3
web/components/core/multiple-select/index.ts
Normal file
3
web/components/core/multiple-select/index.ts
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
export * from "./entity-select-action";
|
||||||
|
export * from "./group-select-action";
|
||||||
|
export * from "./select-group";
|
22
web/components/core/multiple-select/select-group.tsx
Normal file
22
web/components/core/multiple-select/select-group.tsx
Normal 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";
|
@ -56,21 +56,18 @@ const ProgressChart: React.FC<Props> = ({ distribution, startDate, endDate, tota
|
|||||||
dates = eachDayOfInterval({ start, end });
|
dates = eachDayOfInterval({ start, end });
|
||||||
}
|
}
|
||||||
|
|
||||||
const maxDates = 4;
|
if (dates.length === 0) return [];
|
||||||
const totalDates = dates.length;
|
|
||||||
|
|
||||||
if (totalDates <= maxDates) return dates.map((d) => renderFormattedDateWithoutYear(d));
|
const formattedDates = dates.map((d) => renderFormattedDateWithoutYear(d));
|
||||||
else {
|
const firstDate = formattedDates[0];
|
||||||
const interval = Math.ceil(totalDates / maxDates);
|
const lastDate = formattedDates[formattedDates.length - 1];
|
||||||
const limitedDates = [];
|
|
||||||
|
|
||||||
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])))
|
const middleDateIndex = Math.floor(formattedDates.length / 2);
|
||||||
limitedDates.push(renderFormattedDateWithoutYear(dates[totalDates - 1]));
|
const middleDate = formattedDates[middleDateIndex];
|
||||||
|
|
||||||
return limitedDates;
|
return [firstDate, middleDate, lastDate];
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
@ -73,7 +73,7 @@ export const CycleDeleteModal: React.FC<ICycleDelete> = observer((props) => {
|
|||||||
<AlertModalCore
|
<AlertModalCore
|
||||||
handleClose={handleClose}
|
handleClose={handleClose}
|
||||||
handleSubmit={formSubmit}
|
handleSubmit={formSubmit}
|
||||||
isDeleting={loader}
|
isSubmitting={loader}
|
||||||
isOpen={isOpen}
|
isOpen={isOpen}
|
||||||
title="Delete Cycle"
|
title="Delete Cycle"
|
||||||
content={
|
content={
|
||||||
|
@ -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 (
|
return (
|
||||||
<ListItem
|
<ListItem
|
||||||
title={cycleDetails?.name ?? ""}
|
title={cycleDetails?.name ?? ""}
|
||||||
itemLink={`/${workspaceSlug}/projects/${projectId}/cycles/${cycleDetails.id}`}
|
itemLink={`/${workspaceSlug}/projects/${projectId}/cycles/${cycleDetails.id}`}
|
||||||
onItemClick={(e) => {
|
onItemClick={handleItemClick}
|
||||||
if (cycleDetails.archived_at) openCycleOverview(e);
|
|
||||||
}}
|
|
||||||
className={className}
|
className={className}
|
||||||
prependTitleElement={
|
prependTitleElement={
|
||||||
<CircularProgressIndicator size={30} percentage={progress} strokeWidth={3}>
|
<CircularProgressIndicator size={30} percentage={progress} strokeWidth={3}>
|
||||||
|
@ -7,8 +7,8 @@ import { TRecentProjectsWidgetResponse } from "@plane/types";
|
|||||||
// ui
|
// ui
|
||||||
import { Avatar, AvatarGroup } from "@plane/ui";
|
import { Avatar, AvatarGroup } from "@plane/ui";
|
||||||
// components
|
// components
|
||||||
|
import { Logo } from "@/components/common";
|
||||||
import { WidgetLoader, WidgetProps } from "@/components/dashboard/widgets";
|
import { WidgetLoader, WidgetProps } from "@/components/dashboard/widgets";
|
||||||
import { ProjectLogo } from "@/components/project";
|
|
||||||
// constants
|
// constants
|
||||||
import { PROJECT_BACKGROUND_COLORS } from "@/constants/dashboard";
|
import { PROJECT_BACKGROUND_COLORS } from "@/constants/dashboard";
|
||||||
import { EUserWorkspaceRoles } from "@/constants/workspace";
|
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}`}
|
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">
|
<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>
|
</div>
|
||||||
<div className="flex-grow truncate">
|
<div className="flex-grow truncate">
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
import { Fragment, ReactNode, useRef, useState } from "react";
|
import { Fragment, ReactNode, useRef, useState } from "react";
|
||||||
import { useTheme } from "next-themes";
|
import { useTheme } from "next-themes";
|
||||||
import { usePopper } from "react-popper";
|
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";
|
import { Combobox } from "@headlessui/react";
|
||||||
// types
|
// types
|
||||||
import { TIssuePriorities } from "@plane/types";
|
import { TIssuePriorities } from "@plane/types";
|
||||||
@ -26,7 +26,7 @@ type Props = TDropdownProps & {
|
|||||||
highlightUrgent?: boolean;
|
highlightUrgent?: boolean;
|
||||||
onChange: (val: TIssuePriorities) => void;
|
onChange: (val: TIssuePriorities) => void;
|
||||||
onClose?: () => void;
|
onClose?: () => void;
|
||||||
value: TIssuePriorities;
|
value: TIssuePriorities | undefined;
|
||||||
};
|
};
|
||||||
|
|
||||||
type ButtonProps = {
|
type ButtonProps = {
|
||||||
@ -37,7 +37,8 @@ type ButtonProps = {
|
|||||||
hideText?: boolean;
|
hideText?: boolean;
|
||||||
isActive?: boolean;
|
isActive?: boolean;
|
||||||
highlightUrgent: boolean;
|
highlightUrgent: boolean;
|
||||||
priority: TIssuePriorities;
|
placeholder: string;
|
||||||
|
priority: TIssuePriorities | undefined;
|
||||||
showTooltip: boolean;
|
showTooltip: boolean;
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -49,6 +50,7 @@ const BorderButton = (props: ButtonProps) => {
|
|||||||
hideIcon = false,
|
hideIcon = false,
|
||||||
hideText = false,
|
hideText = false,
|
||||||
highlightUrgent,
|
highlightUrgent,
|
||||||
|
placeholder,
|
||||||
priority,
|
priority,
|
||||||
showTooltip,
|
showTooltip,
|
||||||
} = props;
|
} = props;
|
||||||
@ -75,7 +77,7 @@ const BorderButton = (props: ButtonProps) => {
|
|||||||
<div
|
<div
|
||||||
className={cn(
|
className={cn(
|
||||||
"h-full flex items-center gap-1.5 border-[0.5px] rounded text-xs px-2 py-0.5",
|
"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
|
// compact the icons if text is hidden
|
||||||
"px-0.5": hideText,
|
"px-0.5": hideText,
|
||||||
@ -85,30 +87,33 @@ const BorderButton = (props: ButtonProps) => {
|
|||||||
className
|
className
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
{!hideIcon && (
|
{!hideIcon &&
|
||||||
<div
|
(priority ? (
|
||||||
className={cn({
|
<div
|
||||||
// highlight just the icon if text is visible and priority is urgent
|
className={cn({
|
||||||
"bg-red-600 p-1 rounded": priority === "urgent" && !hideText && highlightUrgent,
|
// 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,
|
|
||||||
})}
|
})}
|
||||||
/>
|
>
|
||||||
</div>
|
<PriorityIcon
|
||||||
)}
|
priority={priority}
|
||||||
{!hideText && <span className="flex-grow truncate">{priorityDetails?.title}</span>}
|
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 && (
|
{dropdownArrow && (
|
||||||
<ChevronDown className={cn("h-2.5 w-2.5 flex-shrink-0", dropdownArrowClassName)} aria-hidden="true" />
|
<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,
|
hideIcon = false,
|
||||||
hideText = false,
|
hideText = false,
|
||||||
highlightUrgent,
|
highlightUrgent,
|
||||||
|
placeholder,
|
||||||
priority,
|
priority,
|
||||||
showTooltip,
|
showTooltip,
|
||||||
} = props;
|
} = props;
|
||||||
@ -151,7 +157,7 @@ const BackgroundButton = (props: ButtonProps) => {
|
|||||||
<div
|
<div
|
||||||
className={cn(
|
className={cn(
|
||||||
"h-full flex items-center gap-1.5 rounded text-xs px-2 py-0.5",
|
"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
|
// compact the icons if text is hidden
|
||||||
"px-0.5": hideText,
|
"px-0.5": hideText,
|
||||||
@ -161,30 +167,33 @@ const BackgroundButton = (props: ButtonProps) => {
|
|||||||
className
|
className
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
{!hideIcon && (
|
{!hideIcon &&
|
||||||
<div
|
(priority ? (
|
||||||
className={cn({
|
<div
|
||||||
// highlight just the icon if text is visible and priority is urgent
|
className={cn({
|
||||||
"bg-red-600 p-1 rounded": priority === "urgent" && !hideText && highlightUrgent,
|
// 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,
|
|
||||||
})}
|
})}
|
||||||
/>
|
>
|
||||||
</div>
|
<PriorityIcon
|
||||||
)}
|
priority={priority}
|
||||||
{!hideText && <span className="flex-grow truncate">{priorityDetails?.title}</span>}
|
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 && (
|
{dropdownArrow && (
|
||||||
<ChevronDown className={cn("h-2.5 w-2.5 flex-shrink-0", dropdownArrowClassName)} aria-hidden="true" />
|
<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,
|
hideText = false,
|
||||||
isActive = false,
|
isActive = false,
|
||||||
highlightUrgent,
|
highlightUrgent,
|
||||||
|
placeholder,
|
||||||
priority,
|
priority,
|
||||||
showTooltip,
|
showTooltip,
|
||||||
} = props;
|
} = props;
|
||||||
@ -228,7 +238,7 @@ const TransparentButton = (props: ButtonProps) => {
|
|||||||
<div
|
<div
|
||||||
className={cn(
|
className={cn(
|
||||||
"h-full flex items-center gap-1.5 rounded text-xs px-2 py-0.5 hover:bg-custom-background-80",
|
"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
|
// compact the icons if text is hidden
|
||||||
"px-0.5": hideText,
|
"px-0.5": hideText,
|
||||||
@ -239,30 +249,33 @@ const TransparentButton = (props: ButtonProps) => {
|
|||||||
className
|
className
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
{!hideIcon && (
|
{!hideIcon &&
|
||||||
<div
|
(priority ? (
|
||||||
className={cn({
|
<div
|
||||||
// highlight just the icon if text is visible and priority is urgent
|
className={cn({
|
||||||
"bg-red-600 p-1 rounded": priority === "urgent" && !hideText && highlightUrgent,
|
// 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,
|
|
||||||
})}
|
})}
|
||||||
/>
|
>
|
||||||
</div>
|
<PriorityIcon
|
||||||
)}
|
priority={priority}
|
||||||
{!hideText && <span className="flex-grow truncate">{priorityDetails?.title}</span>}
|
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 && (
|
{dropdownArrow && (
|
||||||
<ChevronDown className={cn("h-2.5 w-2.5 flex-shrink-0", dropdownArrowClassName)} aria-hidden="true" />
|
<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,
|
highlightUrgent = true,
|
||||||
onChange,
|
onChange,
|
||||||
onClose,
|
onClose,
|
||||||
|
placeholder = "Priority",
|
||||||
placement,
|
placement,
|
||||||
showTooltip = false,
|
showTooltip = false,
|
||||||
tabIndex,
|
tabIndex,
|
||||||
@ -400,6 +414,7 @@ export const PriorityDropdown: React.FC<Props> = (props) => {
|
|||||||
dropdownArrow={dropdownArrow && !disabled}
|
dropdownArrow={dropdownArrow && !disabled}
|
||||||
dropdownArrowClassName={dropdownArrowClassName}
|
dropdownArrowClassName={dropdownArrowClassName}
|
||||||
hideIcon={hideIcon}
|
hideIcon={hideIcon}
|
||||||
|
placeholder={placeholder}
|
||||||
showTooltip={showTooltip}
|
showTooltip={showTooltip}
|
||||||
hideText={BUTTON_VARIANTS_WITHOUT_TEXT.includes(buttonVariant)}
|
hideText={BUTTON_VARIANTS_WITHOUT_TEXT.includes(buttonVariant)}
|
||||||
/>
|
/>
|
||||||
|
@ -6,7 +6,7 @@ import { Combobox } from "@headlessui/react";
|
|||||||
// types
|
// types
|
||||||
import { IProject } from "@plane/types";
|
import { IProject } from "@plane/types";
|
||||||
// components
|
// components
|
||||||
import { ProjectLogo } from "@/components/project";
|
import { Logo } from "@/components/common";
|
||||||
// helpers
|
// helpers
|
||||||
import { cn } from "@/helpers/common.helper";
|
import { cn } from "@/helpers/common.helper";
|
||||||
// hooks
|
// hooks
|
||||||
@ -83,7 +83,7 @@ export const ProjectDropdown: React.FC<Props> = observer((props) => {
|
|||||||
<div className="flex items-center gap-2">
|
<div className="flex items-center gap-2">
|
||||||
{projectDetails && (
|
{projectDetails && (
|
||||||
<span className="grid place-items-center flex-shrink-0 h-4 w-4">
|
<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>
|
||||||
)}
|
)}
|
||||||
<span className="flex-grow truncate">{projectDetails?.name}</span>
|
<span className="flex-grow truncate">{projectDetails?.name}</span>
|
||||||
@ -157,7 +157,7 @@ export const ProjectDropdown: React.FC<Props> = observer((props) => {
|
|||||||
>
|
>
|
||||||
{!hideIcon && selectedProject && (
|
{!hideIcon && selectedProject && (
|
||||||
<span className="grid place-items-center flex-shrink-0 h-4 w-4">
|
<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>
|
</span>
|
||||||
)}
|
)}
|
||||||
{BUTTON_VARIANTS_WITH_TEXT.includes(buttonVariant) && (
|
{BUTTON_VARIANTS_WITH_TEXT.includes(buttonVariant) && (
|
||||||
|
@ -24,7 +24,8 @@ type Props = TDropdownProps & {
|
|||||||
onChange: (val: string) => void;
|
onChange: (val: string) => void;
|
||||||
onClose?: () => void;
|
onClose?: () => void;
|
||||||
projectId: string;
|
projectId: string;
|
||||||
value: string;
|
showDefaultState?: boolean;
|
||||||
|
value: string | undefined;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const StateDropdown: React.FC<Props> = observer((props) => {
|
export const StateDropdown: React.FC<Props> = observer((props) => {
|
||||||
@ -42,6 +43,7 @@ export const StateDropdown: React.FC<Props> = observer((props) => {
|
|||||||
onClose,
|
onClose,
|
||||||
placement,
|
placement,
|
||||||
projectId,
|
projectId,
|
||||||
|
showDefaultState = true,
|
||||||
showTooltip = false,
|
showTooltip = false,
|
||||||
tabIndex,
|
tabIndex,
|
||||||
value,
|
value,
|
||||||
@ -72,8 +74,8 @@ export const StateDropdown: React.FC<Props> = observer((props) => {
|
|||||||
const { workspaceSlug } = useAppRouter();
|
const { workspaceSlug } = useAppRouter();
|
||||||
const { fetchProjectStates, getProjectStates, getStateById } = useProjectState();
|
const { fetchProjectStates, getProjectStates, getStateById } = useProjectState();
|
||||||
const statesList = getProjectStates(projectId);
|
const statesList = getProjectStates(projectId);
|
||||||
const defaultStateList = statesList?.find((state) => state.default);
|
const defaultState = statesList?.find((state) => state.default);
|
||||||
const stateValue = value ? value : defaultStateList?.id;
|
const stateValue = value ?? (showDefaultState ? defaultState?.id : undefined);
|
||||||
|
|
||||||
const options = statesList?.map((state) => ({
|
const options = statesList?.map((state) => ({
|
||||||
value: state.id,
|
value: state.id,
|
||||||
@ -170,7 +172,7 @@ export const StateDropdown: React.FC<Props> = observer((props) => {
|
|||||||
{!hideIcon && (
|
{!hideIcon && (
|
||||||
<StateGroupIcon
|
<StateGroupIcon
|
||||||
stateGroup={selectedState?.group ?? "backlog"}
|
stateGroup={selectedState?.group ?? "backlog"}
|
||||||
color={selectedState?.color}
|
color={selectedState?.color ?? "rgba(var(--color-text-300))"}
|
||||||
className="h-3 w-3 flex-shrink-0"
|
className="h-3 w-3 flex-shrink-0"
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
|
@ -1,15 +1,16 @@
|
|||||||
import { observer } from "mobx-react";
|
import { observer } from "mobx-react";
|
||||||
// hooks
|
|
||||||
// components
|
|
||||||
// helpers
|
// helpers
|
||||||
import { cn } from "@/helpers/common.helper";
|
import { cn } from "@/helpers/common.helper";
|
||||||
import { getDate, renderFormattedPayloadDate } from "@/helpers/date-time.helper";
|
import { getDate, renderFormattedPayloadDate } from "@/helpers/date-time.helper";
|
||||||
|
// hooks
|
||||||
import { useIssueDetail } from "@/hooks/store";
|
import { useIssueDetail } from "@/hooks/store";
|
||||||
// types
|
import { TSelectionHelper } from "@/hooks/use-multiple-select";
|
||||||
// constants
|
// constants
|
||||||
import { BLOCK_HEIGHT } from "../constants";
|
import { BLOCK_HEIGHT } from "../constants";
|
||||||
|
// components
|
||||||
import { ChartAddBlock, ChartDraggable } from "../helpers";
|
import { ChartAddBlock, ChartDraggable } from "../helpers";
|
||||||
import { useGanttChart } from "../hooks";
|
import { useGanttChart } from "../hooks";
|
||||||
|
// types
|
||||||
import { IBlockUpdateData, IGanttBlock } from "../types";
|
import { IBlockUpdateData, IGanttBlock } from "../types";
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
@ -21,6 +22,7 @@ type Props = {
|
|||||||
enableBlockMove: boolean;
|
enableBlockMove: boolean;
|
||||||
enableAddBlock: boolean;
|
enableAddBlock: boolean;
|
||||||
ganttContainerRef: React.RefObject<HTMLDivElement>;
|
ganttContainerRef: React.RefObject<HTMLDivElement>;
|
||||||
|
selectionHelpers: TSelectionHelper;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const GanttChartBlock: React.FC<Props> = observer((props) => {
|
export const GanttChartBlock: React.FC<Props> = observer((props) => {
|
||||||
@ -33,6 +35,7 @@ export const GanttChartBlock: React.FC<Props> = observer((props) => {
|
|||||||
enableBlockMove,
|
enableBlockMove,
|
||||||
enableAddBlock,
|
enableAddBlock,
|
||||||
ganttContainerRef,
|
ganttContainerRef,
|
||||||
|
selectionHelpers,
|
||||||
} = props;
|
} = props;
|
||||||
// store hooks
|
// store hooks
|
||||||
const { updateActiveBlockId, isBlockActive } = useGanttChart();
|
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 (
|
return (
|
||||||
<div
|
<div
|
||||||
key={`block-${block.id}`}
|
key={`block-${block.id}`}
|
||||||
@ -80,10 +87,11 @@ export const GanttChartBlock: React.FC<Props> = observer((props) => {
|
|||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
className={cn("relative h-full", {
|
className={cn("relative h-full", {
|
||||||
"bg-custom-background-80": isBlockActive(block.id),
|
"rounded-l border border-r-0 border-custom-primary-70": getIsIssuePeeked(block.data.id),
|
||||||
"rounded-l border border-r-0 border-custom-primary-70 hover:border-custom-primary-70": getIsIssuePeeked(
|
"bg-custom-background-90": isBlockHoveredOn,
|
||||||
block.data.id
|
"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)}
|
onMouseEnter={() => updateActiveBlockId(block.id)}
|
||||||
onMouseLeave={() => updateActiveBlockId(null)}
|
onMouseLeave={() => updateActiveBlockId(null)}
|
||||||
|
@ -1,10 +1,12 @@
|
|||||||
import { FC } from "react";
|
import { FC } from "react";
|
||||||
// components
|
// hooks
|
||||||
import { HEADER_HEIGHT } from "../constants";
|
import { TSelectionHelper } from "@/hooks/use-multiple-select";
|
||||||
import { IBlockUpdateData, IGanttBlock } from "../types";
|
|
||||||
import { GanttChartBlock } from "./block";
|
|
||||||
// types
|
|
||||||
// constants
|
// constants
|
||||||
|
import { HEADER_HEIGHT } from "../constants";
|
||||||
|
// types
|
||||||
|
import { IBlockUpdateData, IGanttBlock } from "../types";
|
||||||
|
// components
|
||||||
|
import { GanttChartBlock } from "./block";
|
||||||
|
|
||||||
export type GanttChartBlocksProps = {
|
export type GanttChartBlocksProps = {
|
||||||
itemsContainerWidth: number;
|
itemsContainerWidth: number;
|
||||||
@ -17,6 +19,7 @@ export type GanttChartBlocksProps = {
|
|||||||
enableAddBlock: boolean;
|
enableAddBlock: boolean;
|
||||||
ganttContainerRef: React.RefObject<HTMLDivElement>;
|
ganttContainerRef: React.RefObject<HTMLDivElement>;
|
||||||
showAllBlocks: boolean;
|
showAllBlocks: boolean;
|
||||||
|
selectionHelpers: TSelectionHelper;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const GanttChartBlocksList: FC<GanttChartBlocksProps> = (props) => {
|
export const GanttChartBlocksList: FC<GanttChartBlocksProps> = (props) => {
|
||||||
@ -31,6 +34,7 @@ export const GanttChartBlocksList: FC<GanttChartBlocksProps> = (props) => {
|
|||||||
enableAddBlock,
|
enableAddBlock,
|
||||||
ganttContainerRef,
|
ganttContainerRef,
|
||||||
showAllBlocks,
|
showAllBlocks,
|
||||||
|
selectionHelpers,
|
||||||
} = props;
|
} = props;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@ -56,6 +60,7 @@ export const GanttChartBlocksList: FC<GanttChartBlocksProps> = (props) => {
|
|||||||
enableBlockMove={enableBlockMove}
|
enableBlockMove={enableBlockMove}
|
||||||
enableAddBlock={enableAddBlock}
|
enableAddBlock={enableAddBlock}
|
||||||
ganttContainerRef={ganttContainerRef}
|
ganttContainerRef={ganttContainerRef}
|
||||||
|
selectionHelpers={selectionHelpers}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
})}
|
})}
|
||||||
|
@ -2,8 +2,8 @@ import { useEffect, useRef } from "react";
|
|||||||
import { combine } from "@atlaskit/pragmatic-drag-and-drop/combine";
|
import { combine } from "@atlaskit/pragmatic-drag-and-drop/combine";
|
||||||
import { autoScrollForElements } from "@atlaskit/pragmatic-drag-and-drop-auto-scroll/element";
|
import { autoScrollForElements } from "@atlaskit/pragmatic-drag-and-drop-auto-scroll/element";
|
||||||
import { observer } from "mobx-react";
|
import { observer } from "mobx-react";
|
||||||
// hooks
|
|
||||||
// components
|
// components
|
||||||
|
import { MultipleSelectGroup } from "@/components/core";
|
||||||
import {
|
import {
|
||||||
BiWeekChartView,
|
BiWeekChartView,
|
||||||
DayChartView,
|
DayChartView,
|
||||||
@ -18,8 +18,12 @@ import {
|
|||||||
WeekChartView,
|
WeekChartView,
|
||||||
YearChartView,
|
YearChartView,
|
||||||
} from "@/components/gantt-chart";
|
} from "@/components/gantt-chart";
|
||||||
|
import { IssueBulkOperationsRoot } from "@/components/issues";
|
||||||
// helpers
|
// helpers
|
||||||
import { cn } from "@/helpers/common.helper";
|
import { cn } from "@/helpers/common.helper";
|
||||||
|
// constants
|
||||||
|
import { GANTT_SELECT_GROUP } from "../constants";
|
||||||
|
// hooks
|
||||||
import { useGanttChart } from "../hooks/use-gantt-chart";
|
import { useGanttChart } from "../hooks/use-gantt-chart";
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
@ -33,6 +37,7 @@ type Props = {
|
|||||||
enableBlockRightResize: boolean;
|
enableBlockRightResize: boolean;
|
||||||
enableReorder: boolean;
|
enableReorder: boolean;
|
||||||
enableAddBlock: boolean;
|
enableAddBlock: boolean;
|
||||||
|
enableSelection: boolean;
|
||||||
itemsContainerWidth: number;
|
itemsContainerWidth: number;
|
||||||
showAllBlocks: boolean;
|
showAllBlocks: boolean;
|
||||||
sidebarToRender: (props: any) => React.ReactNode;
|
sidebarToRender: (props: any) => React.ReactNode;
|
||||||
@ -53,6 +58,7 @@ export const GanttChartMainContent: React.FC<Props> = observer((props) => {
|
|||||||
enableBlockRightResize,
|
enableBlockRightResize,
|
||||||
enableReorder,
|
enableReorder,
|
||||||
enableAddBlock,
|
enableAddBlock,
|
||||||
|
enableSelection,
|
||||||
itemsContainerWidth,
|
itemsContainerWidth,
|
||||||
showAllBlocks,
|
showAllBlocks,
|
||||||
sidebarToRender,
|
sidebarToRender,
|
||||||
@ -107,43 +113,58 @@ export const GanttChartMainContent: React.FC<Props> = observer((props) => {
|
|||||||
const ActiveChartView = CHART_VIEW_COMPONENTS[currentView];
|
const ActiveChartView = CHART_VIEW_COMPONENTS[currentView];
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div
|
<MultipleSelectGroup
|
||||||
// DO NOT REMOVE THE ID
|
containerRef={ganttContainerRef}
|
||||||
id="gantt-container"
|
entities={{
|
||||||
className={cn(
|
[GANTT_SELECT_GROUP]: chartBlocks?.map((block) => block.id) ?? [],
|
||||||
"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
|
{(helpers) => (
|
||||||
blocks={blocks}
|
<>
|
||||||
blockUpdateHandler={blockUpdateHandler}
|
<div
|
||||||
enableReorder={enableReorder}
|
// DO NOT REMOVE THE ID
|
||||||
sidebarToRender={sidebarToRender}
|
id="gantt-container"
|
||||||
title={title}
|
className={cn(
|
||||||
quickAdd={quickAdd}
|
"h-full w-full overflow-auto vertical-scrollbar horizontal-scrollbar scrollbar-lg flex border-t-[0.5px] border-custom-border-200",
|
||||||
/>
|
{
|
||||||
<div className="relative min-h-full h-max flex-shrink-0 flex-grow">
|
"mb-8": bottomSpacing,
|
||||||
<ActiveChartView />
|
}
|
||||||
{currentViewData && (
|
)}
|
||||||
<GanttChartBlocksList
|
ref={ganttContainerRef}
|
||||||
itemsContainerWidth={itemsContainerWidth}
|
onScroll={onScroll}
|
||||||
blocks={chartBlocks}
|
>
|
||||||
blockToRender={blockToRender}
|
<GanttChartSidebar
|
||||||
blockUpdateHandler={blockUpdateHandler}
|
blocks={blocks}
|
||||||
enableBlockLeftResize={enableBlockLeftResize}
|
blockUpdateHandler={blockUpdateHandler}
|
||||||
enableBlockRightResize={enableBlockRightResize}
|
enableReorder={enableReorder}
|
||||||
enableBlockMove={enableBlockMove}
|
enableSelection={enableSelection}
|
||||||
enableAddBlock={enableAddBlock}
|
sidebarToRender={sidebarToRender}
|
||||||
ganttContainerRef={ganttContainerRef}
|
title={title}
|
||||||
showAllBlocks={showAllBlocks}
|
quickAdd={quickAdd}
|
||||||
/>
|
selectionHelpers={helpers}
|
||||||
)}
|
/>
|
||||||
</div>
|
<div className="relative min-h-full h-max flex-shrink-0 flex-grow">
|
||||||
</div>
|
<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>
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
@ -32,6 +32,7 @@ type ChartViewRootProps = {
|
|||||||
enableBlockMove: boolean;
|
enableBlockMove: boolean;
|
||||||
enableReorder: boolean;
|
enableReorder: boolean;
|
||||||
enableAddBlock: boolean;
|
enableAddBlock: boolean;
|
||||||
|
enableSelection: boolean;
|
||||||
bottomSpacing: boolean;
|
bottomSpacing: boolean;
|
||||||
showAllBlocks: boolean;
|
showAllBlocks: boolean;
|
||||||
quickAdd?: React.JSX.Element | undefined;
|
quickAdd?: React.JSX.Element | undefined;
|
||||||
@ -51,6 +52,7 @@ export const ChartViewRoot: FC<ChartViewRootProps> = observer((props) => {
|
|||||||
enableBlockMove,
|
enableBlockMove,
|
||||||
enableReorder,
|
enableReorder,
|
||||||
enableAddBlock,
|
enableAddBlock,
|
||||||
|
enableSelection,
|
||||||
bottomSpacing,
|
bottomSpacing,
|
||||||
showAllBlocks,
|
showAllBlocks,
|
||||||
quickAdd,
|
quickAdd,
|
||||||
@ -184,6 +186,7 @@ export const ChartViewRoot: FC<ChartViewRootProps> = observer((props) => {
|
|||||||
enableBlockRightResize={enableBlockRightResize}
|
enableBlockRightResize={enableBlockRightResize}
|
||||||
enableReorder={enableReorder}
|
enableReorder={enableReorder}
|
||||||
enableAddBlock={enableAddBlock}
|
enableAddBlock={enableAddBlock}
|
||||||
|
enableSelection={enableSelection}
|
||||||
itemsContainerWidth={itemsContainerWidth}
|
itemsContainerWidth={itemsContainerWidth}
|
||||||
showAllBlocks={showAllBlocks}
|
showAllBlocks={showAllBlocks}
|
||||||
sidebarToRender={sidebarToRender}
|
sidebarToRender={sidebarToRender}
|
||||||
|
@ -3,3 +3,5 @@ export const BLOCK_HEIGHT = 44;
|
|||||||
export const HEADER_HEIGHT = 60;
|
export const HEADER_HEIGHT = 60;
|
||||||
|
|
||||||
export const SIDEBAR_WIDTH = 360;
|
export const SIDEBAR_WIDTH = 360;
|
||||||
|
|
||||||
|
export const GANTT_SELECT_GROUP = "gantt-issues";
|
||||||
|
@ -18,6 +18,7 @@ type GanttChartRootProps = {
|
|||||||
enableBlockMove?: boolean;
|
enableBlockMove?: boolean;
|
||||||
enableReorder?: boolean;
|
enableReorder?: boolean;
|
||||||
enableAddBlock?: boolean;
|
enableAddBlock?: boolean;
|
||||||
|
enableSelection?: boolean;
|
||||||
bottomSpacing?: boolean;
|
bottomSpacing?: boolean;
|
||||||
showAllBlocks?: boolean;
|
showAllBlocks?: boolean;
|
||||||
};
|
};
|
||||||
@ -36,6 +37,7 @@ export const GanttChartRoot: FC<GanttChartRootProps> = (props) => {
|
|||||||
enableBlockMove = false,
|
enableBlockMove = false,
|
||||||
enableReorder = false,
|
enableReorder = false,
|
||||||
enableAddBlock = false,
|
enableAddBlock = false,
|
||||||
|
enableSelection = false,
|
||||||
bottomSpacing = false,
|
bottomSpacing = false,
|
||||||
showAllBlocks = false,
|
showAllBlocks = false,
|
||||||
quickAdd,
|
quickAdd,
|
||||||
@ -56,6 +58,7 @@ export const GanttChartRoot: FC<GanttChartRootProps> = (props) => {
|
|||||||
enableBlockMove={enableBlockMove}
|
enableBlockMove={enableBlockMove}
|
||||||
enableReorder={enableReorder}
|
enableReorder={enableReorder}
|
||||||
enableAddBlock={enableAddBlock}
|
enableAddBlock={enableAddBlock}
|
||||||
|
enableSelection={enableSelection}
|
||||||
bottomSpacing={bottomSpacing}
|
bottomSpacing={bottomSpacing}
|
||||||
showAllBlocks={showAllBlocks}
|
showAllBlocks={showAllBlocks}
|
||||||
quickAdd={quickAdd}
|
quickAdd={quickAdd}
|
||||||
|
@ -38,7 +38,7 @@ export const CyclesSidebarBlock: React.FC<Props> = observer((props) => {
|
|||||||
<div
|
<div
|
||||||
id={`sidebar-block-${block.id}`}
|
id={`sidebar-block-${block.id}`}
|
||||||
className={cn("group w-full flex items-center gap-2 pl-2 pr-4", {
|
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={{
|
style={{
|
||||||
height: `${BLOCK_HEIGHT}px`,
|
height: `${BLOCK_HEIGHT}px`,
|
||||||
|
@ -1,63 +1,87 @@
|
|||||||
import React, { MutableRefObject } from "react";
|
import React, { MutableRefObject } from "react";
|
||||||
import { observer } from "mobx-react";
|
import { observer } from "mobx-react";
|
||||||
import { MoreVertical } from "lucide-react";
|
import { MoreVertical } from "lucide-react";
|
||||||
// hooks
|
|
||||||
import { useGanttChart } from "@/components/gantt-chart/hooks";
|
|
||||||
// components
|
// components
|
||||||
|
import { MultipleSelectEntityAction } from "@/components/core";
|
||||||
|
import { useGanttChart } from "@/components/gantt-chart/hooks";
|
||||||
import { IssueGanttSidebarBlock } from "@/components/issues";
|
import { IssueGanttSidebarBlock } from "@/components/issues";
|
||||||
// helpers
|
// helpers
|
||||||
import { cn } from "@/helpers/common.helper";
|
import { cn } from "@/helpers/common.helper";
|
||||||
import { findTotalDaysInRange } from "@/helpers/date-time.helper";
|
import { findTotalDaysInRange } from "@/helpers/date-time.helper";
|
||||||
|
// hooks
|
||||||
import { useIssueDetail } from "@/hooks/store";
|
import { useIssueDetail } from "@/hooks/store";
|
||||||
// types
|
import { TSelectionHelper } from "@/hooks/use-multiple-select";
|
||||||
// constants
|
// constants
|
||||||
import { BLOCK_HEIGHT } from "../../constants";
|
import { BLOCK_HEIGHT, GANTT_SELECT_GROUP } from "../../constants";
|
||||||
|
// types
|
||||||
import { IGanttBlock } from "../../types";
|
import { IGanttBlock } from "../../types";
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
block: IGanttBlock;
|
block: IGanttBlock;
|
||||||
enableReorder: boolean;
|
enableReorder: boolean;
|
||||||
|
enableSelection: boolean;
|
||||||
isDragging: boolean;
|
isDragging: boolean;
|
||||||
dragHandleRef: MutableRefObject<HTMLButtonElement | null>;
|
dragHandleRef: MutableRefObject<HTMLButtonElement | null>;
|
||||||
|
selectionHelpers?: TSelectionHelper;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const IssuesSidebarBlock = observer((props: Props) => {
|
export const IssuesSidebarBlock = observer((props: Props) => {
|
||||||
const { block, enableReorder, isDragging, dragHandleRef } = props;
|
const { block, enableReorder, enableSelection, isDragging, dragHandleRef, selectionHelpers } = props;
|
||||||
// store hooks
|
// store hooks
|
||||||
const { updateActiveBlockId, isBlockActive } = useGanttChart();
|
const { updateActiveBlockId, isBlockActive } = useGanttChart();
|
||||||
const { getIsIssuePeeked } = useIssueDetail();
|
const { getIsIssuePeeked } = useIssueDetail();
|
||||||
|
|
||||||
const duration = findTotalDaysInRange(block.start_date, block.target_date);
|
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 (
|
return (
|
||||||
<div
|
<div
|
||||||
className={cn({
|
className={cn("group/list-block", {
|
||||||
"rounded bg-custom-background-80": isDragging,
|
"rounded bg-custom-background-80": isDragging,
|
||||||
"rounded-l border border-r-0 border-custom-primary-70 hover:border-custom-primary-70": getIsIssuePeeked(
|
"rounded-l border border-r-0 border-custom-primary-70": getIsIssuePeeked(block.data.id),
|
||||||
block.data.id
|
"border border-r-0 border-custom-border-400": isIssueFocused,
|
||||||
),
|
|
||||||
})}
|
})}
|
||||||
onMouseEnter={() => updateActiveBlockId(block.id)}
|
onMouseEnter={() => updateActiveBlockId(block.id)}
|
||||||
onMouseLeave={() => updateActiveBlockId(null)}
|
onMouseLeave={() => updateActiveBlockId(null)}
|
||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
className={cn("group w-full flex items-center gap-2 pl-2 pr-4", {
|
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={{
|
style={{
|
||||||
height: `${BLOCK_HEIGHT}px`,
|
height: `${BLOCK_HEIGHT}px`,
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{enableReorder && (
|
<div className="flex items-center gap-2">
|
||||||
<button
|
{enableReorder && (
|
||||||
type="button"
|
<button
|
||||||
className="flex flex-shrink-0 rounded p-0.5 text-custom-sidebar-text-200 opacity-0 group-hover:opacity-100"
|
type="button"
|
||||||
ref={dragHandleRef}
|
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" />
|
<MoreVertical className="h-3.5 w-3.5" />
|
||||||
</button>
|
<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 h-full flex-grow items-center justify-between gap-2 truncate">
|
||||||
<div className="flex-grow truncate">
|
<div className="flex-grow truncate">
|
||||||
<IssueGanttSidebarBlock issueId={block.data.id} />
|
<IssueGanttSidebarBlock issueId={block.data.id} />
|
||||||
|
@ -1,22 +1,26 @@
|
|||||||
import { MutableRefObject } from "react";
|
import { MutableRefObject } from "react";
|
||||||
// components
|
|
||||||
// ui
|
// ui
|
||||||
import { Loader } from "@plane/ui";
|
import { Loader } from "@plane/ui";
|
||||||
// types
|
// components
|
||||||
import { IGanttBlock, IBlockUpdateData } from "@/components/gantt-chart/types";
|
import { IGanttBlock, IBlockUpdateData } from "@/components/gantt-chart/types";
|
||||||
|
// hooks
|
||||||
|
import { TSelectionHelper } from "@/hooks/use-multiple-select";
|
||||||
import { GanttDnDHOC } from "../gantt-dnd-HOC";
|
import { GanttDnDHOC } from "../gantt-dnd-HOC";
|
||||||
import { handleOrderChange } from "../utils";
|
import { handleOrderChange } from "../utils";
|
||||||
|
// types
|
||||||
import { IssuesSidebarBlock } from "./block";
|
import { IssuesSidebarBlock } from "./block";
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
blockUpdateHandler: (block: any, payload: IBlockUpdateData) => void;
|
blockUpdateHandler: (block: any, payload: IBlockUpdateData) => void;
|
||||||
blocks: IGanttBlock[] | null;
|
blocks: IGanttBlock[] | null;
|
||||||
enableReorder: boolean;
|
enableReorder: boolean;
|
||||||
|
enableSelection: boolean;
|
||||||
showAllBlocks?: boolean;
|
showAllBlocks?: boolean;
|
||||||
|
selectionHelpers?: TSelectionHelper;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const IssueGanttSidebar: React.FC<Props> = (props) => {
|
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 = (
|
const handleOnDrop = (
|
||||||
draggingBlockId: string | undefined,
|
draggingBlockId: string | undefined,
|
||||||
@ -47,8 +51,10 @@ export const IssueGanttSidebar: React.FC<Props> = (props) => {
|
|||||||
<IssuesSidebarBlock
|
<IssuesSidebarBlock
|
||||||
block={block}
|
block={block}
|
||||||
enableReorder={enableReorder}
|
enableReorder={enableReorder}
|
||||||
|
enableSelection={enableSelection}
|
||||||
isDragging={isDragging}
|
isDragging={isDragging}
|
||||||
dragHandleRef={dragHandleRef}
|
dragHandleRef={dragHandleRef}
|
||||||
|
selectionHelpers={selectionHelpers}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
</GanttDnDHOC>
|
</GanttDnDHOC>
|
||||||
|
@ -38,7 +38,7 @@ export const ModulesSidebarBlock: React.FC<Props> = observer((props) => {
|
|||||||
<div
|
<div
|
||||||
id={`sidebar-block-${block.id}`}
|
id={`sidebar-block-${block.id}`}
|
||||||
className={cn("group w-full flex items-center gap-2 pl-2 pr-4", {
|
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={{
|
style={{
|
||||||
height: `${BLOCK_HEIGHT}px`,
|
height: `${BLOCK_HEIGHT}px`,
|
||||||
|
@ -1,19 +1,38 @@
|
|||||||
|
import { observer } from "mobx-react";
|
||||||
// components
|
// components
|
||||||
|
import { MultipleSelectGroupAction } from "@/components/core";
|
||||||
import { IBlockUpdateData, IGanttBlock } from "@/components/gantt-chart";
|
import { IBlockUpdateData, IGanttBlock } from "@/components/gantt-chart";
|
||||||
|
// helpers
|
||||||
|
import { cn } from "@/helpers/common.helper";
|
||||||
|
// hooks
|
||||||
|
import { TSelectionHelper } from "@/hooks/use-multiple-select";
|
||||||
// constants
|
// constants
|
||||||
import { HEADER_HEIGHT, SIDEBAR_WIDTH } from "../constants";
|
import { GANTT_SELECT_GROUP, HEADER_HEIGHT, SIDEBAR_WIDTH } from "../constants";
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
blocks: IGanttBlock[] | null;
|
blocks: IGanttBlock[] | null;
|
||||||
blockUpdateHandler: (block: any, payload: IBlockUpdateData) => void;
|
blockUpdateHandler: (block: any, payload: IBlockUpdateData) => void;
|
||||||
enableReorder: boolean;
|
enableReorder: boolean;
|
||||||
|
enableSelection: boolean;
|
||||||
sidebarToRender: (props: any) => React.ReactNode;
|
sidebarToRender: (props: any) => React.ReactNode;
|
||||||
title: string;
|
title: string;
|
||||||
quickAdd?: React.JSX.Element | undefined;
|
quickAdd?: React.JSX.Element | undefined;
|
||||||
|
selectionHelpers: TSelectionHelper;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const GanttChartSidebar: React.FC<Props> = (props) => {
|
export const GanttChartSidebar: React.FC<Props> = observer((props) => {
|
||||||
const { blocks, blockUpdateHandler, enableReorder, sidebarToRender, title, quickAdd } = props;
|
const {
|
||||||
|
blocks,
|
||||||
|
blockUpdateHandler,
|
||||||
|
enableReorder,
|
||||||
|
enableSelection,
|
||||||
|
sidebarToRender,
|
||||||
|
title,
|
||||||
|
quickAdd,
|
||||||
|
selectionHelpers,
|
||||||
|
} = props;
|
||||||
|
|
||||||
|
const isGroupSelectionEmpty = selectionHelpers.isGroupSelected(GANTT_SELECT_GROUP) === "empty";
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
@ -25,19 +44,39 @@ export const GanttChartSidebar: React.FC<Props> = (props) => {
|
|||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<div
|
<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={{
|
style={{
|
||||||
height: `${HEADER_HEIGHT}px`,
|
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>
|
<h6>Duration</h6>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="min-h-full h-max bg-custom-background-100 overflow-hidden">
|
<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>
|
</div>
|
||||||
{quickAdd ? quickAdd : null}
|
{quickAdd ? quickAdd : null}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
};
|
});
|
||||||
|
@ -10,9 +10,8 @@ import { IIssueDisplayFilterOptions, IIssueDisplayProperties, IIssueFilterOption
|
|||||||
import { Breadcrumbs, Button, ContrastIcon, CustomMenu, Tooltip } from "@plane/ui";
|
import { Breadcrumbs, Button, ContrastIcon, CustomMenu, Tooltip } from "@plane/ui";
|
||||||
// components
|
// components
|
||||||
import { ProjectAnalyticsModal } from "@/components/analytics";
|
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 { DisplayFiltersSelection, FiltersDropdown, FilterSelection, LayoutSelection } from "@/components/issues";
|
||||||
import { ProjectLogo } from "@/components/project";
|
|
||||||
// constants
|
// constants
|
||||||
import { EIssueFilterType, EIssuesStoreType, ISSUE_DISPLAY_FILTERS_BY_LAYOUT } from "@/constants/issue";
|
import { EIssueFilterType, EIssuesStoreType, ISSUE_DISPLAY_FILTERS_BY_LAYOUT } from "@/constants/issue";
|
||||||
import { EUserProjectRoles } from "@/constants/project";
|
import { EUserProjectRoles } from "@/constants/project";
|
||||||
@ -170,7 +169,7 @@ export const CycleIssuesHeader: React.FC = observer(() => {
|
|||||||
icon={
|
icon={
|
||||||
currentProjectDetails && (
|
currentProjectDetails && (
|
||||||
<span className="grid h-4 w-4 flex-shrink-0 place-items-center">
|
<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>
|
</span>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
@ -4,9 +4,8 @@ import { useRouter } from "next/router";
|
|||||||
// ui
|
// ui
|
||||||
import { Breadcrumbs, Button, ContrastIcon } from "@plane/ui";
|
import { Breadcrumbs, Button, ContrastIcon } from "@plane/ui";
|
||||||
// components
|
// components
|
||||||
import { BreadcrumbLink } from "@/components/common";
|
import { BreadcrumbLink, Logo } from "@/components/common";
|
||||||
import { CyclesViewHeader } from "@/components/cycles";
|
import { CyclesViewHeader } from "@/components/cycles";
|
||||||
import { ProjectLogo } from "@/components/project";
|
|
||||||
// constants
|
// constants
|
||||||
import { EUserProjectRoles } from "@/constants/project";
|
import { EUserProjectRoles } from "@/constants/project";
|
||||||
// hooks
|
// hooks
|
||||||
@ -41,7 +40,7 @@ export const CyclesHeader: FC = observer(() => {
|
|||||||
icon={
|
icon={
|
||||||
currentProjectDetails && (
|
currentProjectDetails && (
|
||||||
<span className="grid place-items-center flex-shrink-0 h-4 w-4">
|
<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>
|
</span>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
@ -10,9 +10,8 @@ import { IIssueDisplayFilterOptions, IIssueDisplayProperties, IIssueFilterOption
|
|||||||
import { Breadcrumbs, Button, CustomMenu, DiceIcon, Tooltip } from "@plane/ui";
|
import { Breadcrumbs, Button, CustomMenu, DiceIcon, Tooltip } from "@plane/ui";
|
||||||
// components
|
// components
|
||||||
import { ProjectAnalyticsModal } from "@/components/analytics";
|
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 { DisplayFiltersSelection, FiltersDropdown, FilterSelection, LayoutSelection } from "@/components/issues";
|
||||||
import { ProjectLogo } from "@/components/project";
|
|
||||||
// constants
|
// constants
|
||||||
import { EIssuesStoreType, EIssueFilterType, ISSUE_DISPLAY_FILTERS_BY_LAYOUT } from "@/constants/issue";
|
import { EIssuesStoreType, EIssueFilterType, ISSUE_DISPLAY_FILTERS_BY_LAYOUT } from "@/constants/issue";
|
||||||
import { EUserProjectRoles } from "@/constants/project";
|
import { EUserProjectRoles } from "@/constants/project";
|
||||||
@ -170,7 +169,7 @@ export const ModuleIssuesHeader: React.FC = observer(() => {
|
|||||||
icon={
|
icon={
|
||||||
currentProjectDetails && (
|
currentProjectDetails && (
|
||||||
<span className="grid h-4 w-4 flex-shrink-0 place-items-center">
|
<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>
|
</span>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
@ -3,9 +3,8 @@ import { useRouter } from "next/router";
|
|||||||
// ui
|
// ui
|
||||||
import { Breadcrumbs, Button, DiceIcon } from "@plane/ui";
|
import { Breadcrumbs, Button, DiceIcon } from "@plane/ui";
|
||||||
// components
|
// components
|
||||||
import { BreadcrumbLink } from "@/components/common";
|
import { BreadcrumbLink, Logo } from "@/components/common";
|
||||||
import { ModuleViewHeader } from "@/components/modules";
|
import { ModuleViewHeader } from "@/components/modules";
|
||||||
import { ProjectLogo } from "@/components/project";
|
|
||||||
// constants
|
// constants
|
||||||
import { EUserProjectRoles } from "@/constants/project";
|
import { EUserProjectRoles } from "@/constants/project";
|
||||||
// hooks
|
// hooks
|
||||||
@ -41,7 +40,7 @@ export const ModulesListHeader: React.FC = observer(() => {
|
|||||||
icon={
|
icon={
|
||||||
currentProjectDetails && (
|
currentProjectDetails && (
|
||||||
<span className="grid h-4 w-4 flex-shrink-0 place-items-center">
|
<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>
|
</span>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
@ -1,22 +1,52 @@
|
|||||||
|
import { useState } from "react";
|
||||||
import { observer } from "mobx-react";
|
import { observer } from "mobx-react";
|
||||||
import { useRouter } from "next/router";
|
import { useRouter } from "next/router";
|
||||||
import { FileText } from "lucide-react";
|
import { FileText } from "lucide-react";
|
||||||
|
// types
|
||||||
|
import { TLogoProps } from "@plane/types";
|
||||||
// ui
|
// ui
|
||||||
import { Breadcrumbs, Button } from "@plane/ui";
|
import { Breadcrumbs, Button, EmojiIconPicker, EmojiIconPickerTypes, TOAST_TYPE, setToast } from "@plane/ui";
|
||||||
// components
|
// components
|
||||||
import { BreadcrumbLink } from "@/components/common";
|
import { BreadcrumbLink, Logo } from "@/components/common";
|
||||||
import { ProjectLogo } from "@/components/project";
|
// helper
|
||||||
|
import { convertHexEmojiToDecimal } from "@/helpers/emoji.helper";
|
||||||
// hooks
|
// hooks
|
||||||
import { usePage, useProject } from "@/hooks/store";
|
import { usePage, useProject } from "@/hooks/store";
|
||||||
import { usePlatformOS } from "@/hooks/use-platform-os";
|
import { usePlatformOS } from "@/hooks/use-platform-os";
|
||||||
|
|
||||||
|
export interface IPagesHeaderProps {
|
||||||
|
showButton?: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
export const PageDetailsHeader = observer(() => {
|
export const PageDetailsHeader = observer(() => {
|
||||||
// router
|
// router
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
const { workspaceSlug, pageId } = router.query;
|
const { workspaceSlug, pageId } = router.query;
|
||||||
|
// state
|
||||||
|
const [isOpen, setIsOpen] = useState(false);
|
||||||
// store hooks
|
// store hooks
|
||||||
const { currentProjectDetails } = useProject();
|
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
|
// use platform
|
||||||
const { platform } = usePlatformOS();
|
const { platform } = usePlatformOS();
|
||||||
// derived values
|
// derived values
|
||||||
@ -38,7 +68,7 @@ export const PageDetailsHeader = observer(() => {
|
|||||||
icon={
|
icon={
|
||||||
currentProjectDetails && (
|
currentProjectDetails && (
|
||||||
<span className="grid h-4 w-4 flex-shrink-0 place-items-center">
|
<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>
|
</span>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@ -67,7 +97,49 @@ export const PageDetailsHeader = observer(() => {
|
|||||||
<Breadcrumbs.BreadcrumbItem
|
<Breadcrumbs.BreadcrumbItem
|
||||||
type="text"
|
type="text"
|
||||||
link={
|
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>
|
</Breadcrumbs>
|
||||||
|
@ -1,21 +1,20 @@
|
|||||||
import { observer } from "mobx-react";
|
import { observer } from "mobx-react";
|
||||||
import { useRouter } from "next/router";
|
import { useRouter } from "next/router";
|
||||||
import { FileText } from "lucide-react";
|
import { FileText } from "lucide-react";
|
||||||
// hooks
|
|
||||||
// ui
|
// ui
|
||||||
import { Breadcrumbs, Button } from "@plane/ui";
|
import { Breadcrumbs, Button } from "@plane/ui";
|
||||||
// helpers
|
// helpers
|
||||||
import { BreadcrumbLink } from "@/components/common";
|
import { BreadcrumbLink, Logo } from "@/components/common";
|
||||||
import { ProjectLogo } from "@/components/project";
|
|
||||||
import { EUserProjectRoles } from "@/constants/project";
|
|
||||||
// constants
|
// constants
|
||||||
// components
|
import { EPageAccess } from "@/constants/page";
|
||||||
|
import { EUserProjectRoles } from "@/constants/project";
|
||||||
|
// hooks
|
||||||
import { useCommandPalette, useEventTracker, useProject, useUser } from "@/hooks/store";
|
import { useCommandPalette, useEventTracker, useProject, useUser } from "@/hooks/store";
|
||||||
|
|
||||||
export const PagesHeader = observer(() => {
|
export const PagesHeader = observer(() => {
|
||||||
// router
|
// router
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
const { workspaceSlug } = router.query;
|
const { workspaceSlug, type: pageType } = router.query;
|
||||||
// store hooks
|
// store hooks
|
||||||
const { toggleCreatePageModal } = useCommandPalette();
|
const { toggleCreatePageModal } = useCommandPalette();
|
||||||
const {
|
const {
|
||||||
@ -41,7 +40,7 @@ export const PagesHeader = observer(() => {
|
|||||||
icon={
|
icon={
|
||||||
currentProjectDetails && (
|
currentProjectDetails && (
|
||||||
<span className="grid h-4 w-4 flex-shrink-0 place-items-center">
|
<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>
|
</span>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@ -62,7 +61,10 @@ export const PagesHeader = observer(() => {
|
|||||||
size="sm"
|
size="sm"
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
setTrackElement("Project pages page");
|
setTrackElement("Project pages page");
|
||||||
toggleCreatePageModal(true);
|
toggleCreatePageModal({
|
||||||
|
isOpen: true,
|
||||||
|
pageAccess: pageType === "private" ? EPageAccess.PRIVATE : EPageAccess.PUBLIC,
|
||||||
|
});
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
Add Page
|
Add Page
|
||||||
|
@ -4,8 +4,7 @@ import { useRouter } from "next/router";
|
|||||||
import useSWR from "swr";
|
import useSWR from "swr";
|
||||||
// hooks
|
// hooks
|
||||||
import { ArchiveIcon, Breadcrumbs, LayersIcon } from "@plane/ui";
|
import { ArchiveIcon, Breadcrumbs, LayersIcon } from "@plane/ui";
|
||||||
import { BreadcrumbLink } from "@/components/common";
|
import { BreadcrumbLink, Logo } from "@/components/common";
|
||||||
import { ProjectLogo } from "@/components/project";
|
|
||||||
import { ISSUE_DETAILS } from "@/constants/fetch-keys";
|
import { ISSUE_DETAILS } from "@/constants/fetch-keys";
|
||||||
import { useProject } from "@/hooks/store";
|
import { useProject } from "@/hooks/store";
|
||||||
// components
|
// components
|
||||||
@ -52,7 +51,7 @@ export const ProjectArchivedIssueDetailsHeader: FC = observer(() => {
|
|||||||
icon={
|
icon={
|
||||||
currentProjectDetails && (
|
currentProjectDetails && (
|
||||||
<span className="grid place-items-center flex-shrink-0 h-4 w-4">
|
<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>
|
</span>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
@ -4,8 +4,7 @@ import { useRouter } from "next/router";
|
|||||||
// ui
|
// ui
|
||||||
import { ArchiveIcon, Breadcrumbs, Tooltip } from "@plane/ui";
|
import { ArchiveIcon, Breadcrumbs, Tooltip } from "@plane/ui";
|
||||||
// components
|
// components
|
||||||
import { BreadcrumbLink } from "@/components/common";
|
import { BreadcrumbLink, Logo } from "@/components/common";
|
||||||
import { ProjectLogo } from "@/components/project";
|
|
||||||
// constants
|
// constants
|
||||||
import { PROJECT_ARCHIVES_BREADCRUMB_LIST } from "@/constants/archives";
|
import { PROJECT_ARCHIVES_BREADCRUMB_LIST } from "@/constants/archives";
|
||||||
import { EIssuesStoreType } from "@/constants/issue";
|
import { EIssuesStoreType } from "@/constants/issue";
|
||||||
@ -49,7 +48,7 @@ export const ProjectArchivesHeader: FC = observer(() => {
|
|||||||
icon={
|
icon={
|
||||||
currentProjectDetails && (
|
currentProjectDetails && (
|
||||||
<span className="grid place-items-center flex-shrink-0 h-4 w-4">
|
<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>
|
</span>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
@ -6,9 +6,8 @@ import { IIssueDisplayFilterOptions, IIssueDisplayProperties, IIssueFilterOption
|
|||||||
// ui
|
// ui
|
||||||
import { Breadcrumbs, LayersIcon, Tooltip } from "@plane/ui";
|
import { Breadcrumbs, LayersIcon, Tooltip } from "@plane/ui";
|
||||||
// components
|
// components
|
||||||
import { BreadcrumbLink } from "@/components/common";
|
import { BreadcrumbLink, Logo } from "@/components/common";
|
||||||
import { DisplayFiltersSelection, FiltersDropdown, FilterSelection, LayoutSelection } from "@/components/issues";
|
import { DisplayFiltersSelection, FiltersDropdown, FilterSelection, LayoutSelection } from "@/components/issues";
|
||||||
import { ProjectLogo } from "@/components/project";
|
|
||||||
// constants
|
// constants
|
||||||
import { EIssueFilterType, EIssuesStoreType, ISSUE_DISPLAY_FILTERS_BY_LAYOUT } from "@/constants/issue";
|
import { EIssueFilterType, EIssuesStoreType, ISSUE_DISPLAY_FILTERS_BY_LAYOUT } from "@/constants/issue";
|
||||||
// helpers
|
// helpers
|
||||||
@ -101,7 +100,7 @@ export const ProjectDraftIssueHeader: FC = observer(() => {
|
|||||||
icon={
|
icon={
|
||||||
currentProjectDetails && (
|
currentProjectDetails && (
|
||||||
<span className="grid place-items-center flex-shrink-0 h-4 w-4">
|
<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>
|
</span>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
@ -5,9 +5,8 @@ import { RefreshCcw } from "lucide-react";
|
|||||||
// ui
|
// ui
|
||||||
import { Breadcrumbs, Button, LayersIcon } from "@plane/ui";
|
import { Breadcrumbs, Button, LayersIcon } from "@plane/ui";
|
||||||
// components
|
// components
|
||||||
import { BreadcrumbLink } from "@/components/common";
|
import { BreadcrumbLink, Logo } from "@/components/common";
|
||||||
import { InboxIssueCreateEditModalRoot } from "@/components/inbox";
|
import { InboxIssueCreateEditModalRoot } from "@/components/inbox";
|
||||||
import { ProjectLogo } from "@/components/project";
|
|
||||||
// hooks
|
// hooks
|
||||||
import { useProject, useProjectInbox } from "@/hooks/store";
|
import { useProject, useProjectInbox } from "@/hooks/store";
|
||||||
|
|
||||||
@ -35,7 +34,7 @@ export const ProjectInboxHeader: FC = observer(() => {
|
|||||||
icon={
|
icon={
|
||||||
currentProjectDetails && (
|
currentProjectDetails && (
|
||||||
<span className="grid place-items-center flex-shrink-0 h-4 w-4">
|
<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>
|
</span>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
@ -4,8 +4,7 @@ import { useRouter } from "next/router";
|
|||||||
// hooks
|
// hooks
|
||||||
import { PanelRight } from "lucide-react";
|
import { PanelRight } from "lucide-react";
|
||||||
import { Breadcrumbs, LayersIcon } from "@plane/ui";
|
import { Breadcrumbs, LayersIcon } from "@plane/ui";
|
||||||
import { BreadcrumbLink } from "@/components/common";
|
import { BreadcrumbLink, Logo } from "@/components/common";
|
||||||
import { ProjectLogo } from "@/components/project";
|
|
||||||
import { cn } from "@/helpers/common.helper";
|
import { cn } from "@/helpers/common.helper";
|
||||||
import { useAppTheme, useIssueDetail, useProject } from "@/hooks/store";
|
import { useAppTheme, useIssueDetail, useProject } from "@/hooks/store";
|
||||||
// ui
|
// ui
|
||||||
@ -42,7 +41,7 @@ export const ProjectIssueDetailsHeader: FC = observer(() => {
|
|||||||
icon={
|
icon={
|
||||||
currentProjectDetails && (
|
currentProjectDetails && (
|
||||||
<span className="grid h-4 w-4 flex-shrink-0 place-items-center">
|
<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>
|
</span>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
@ -9,9 +9,8 @@ import { IIssueDisplayFilterOptions, IIssueDisplayProperties, IIssueFilterOption
|
|||||||
import { Breadcrumbs, Button, LayersIcon, Tooltip } from "@plane/ui";
|
import { Breadcrumbs, Button, LayersIcon, Tooltip } from "@plane/ui";
|
||||||
// components
|
// components
|
||||||
import { ProjectAnalyticsModal } from "@/components/analytics";
|
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 { DisplayFiltersSelection, FiltersDropdown, FilterSelection, LayoutSelection } from "@/components/issues";
|
||||||
import { ProjectLogo } from "@/components/project";
|
|
||||||
// constants
|
// constants
|
||||||
import { EIssueFilterType, EIssuesStoreType, ISSUE_DISPLAY_FILTERS_BY_LAYOUT } from "@/constants/issue";
|
import { EIssueFilterType, EIssuesStoreType, ISSUE_DISPLAY_FILTERS_BY_LAYOUT } from "@/constants/issue";
|
||||||
import { EUserProjectRoles } from "@/constants/project";
|
import { EUserProjectRoles } from "@/constants/project";
|
||||||
@ -130,7 +129,7 @@ export const ProjectIssuesHeader: React.FC = observer(() => {
|
|||||||
currentProjectDetails ? (
|
currentProjectDetails ? (
|
||||||
currentProjectDetails && (
|
currentProjectDetails && (
|
||||||
<span className="grid place-items-center flex-shrink-0 h-4 w-4">
|
<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>
|
</span>
|
||||||
)
|
)
|
||||||
) : (
|
) : (
|
||||||
|
@ -5,8 +5,7 @@ import { useRouter } from "next/router";
|
|||||||
import { Settings } from "lucide-react";
|
import { Settings } from "lucide-react";
|
||||||
import { Breadcrumbs, CustomMenu } from "@plane/ui";
|
import { Breadcrumbs, CustomMenu } from "@plane/ui";
|
||||||
// components
|
// components
|
||||||
import { BreadcrumbLink } from "@/components/common";
|
import { BreadcrumbLink, Logo } from "@/components/common";
|
||||||
import { ProjectLogo } from "@/components/project";
|
|
||||||
// constants
|
// constants
|
||||||
import { EUserProjectRoles, PROJECT_SETTINGS_LINKS } from "@/constants/project";
|
import { EUserProjectRoles, PROJECT_SETTINGS_LINKS } from "@/constants/project";
|
||||||
// hooks
|
// hooks
|
||||||
@ -39,7 +38,7 @@ export const ProjectSettingHeader: FC = observer(() => {
|
|||||||
icon={
|
icon={
|
||||||
currentProjectDetails && (
|
currentProjectDetails && (
|
||||||
<span className="grid place-items-center flex-shrink-0 h-4 w-4">
|
<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>
|
</span>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
@ -7,9 +7,8 @@ import { IIssueDisplayFilterOptions, IIssueDisplayProperties, IIssueFilterOption
|
|||||||
// ui
|
// ui
|
||||||
import { Breadcrumbs, Button, CustomMenu, PhotoFilterIcon } from "@plane/ui";
|
import { Breadcrumbs, Button, CustomMenu, PhotoFilterIcon } from "@plane/ui";
|
||||||
// components
|
// components
|
||||||
import { BreadcrumbLink } from "@/components/common";
|
import { BreadcrumbLink, Logo } from "@/components/common";
|
||||||
import { DisplayFiltersSelection, FiltersDropdown, FilterSelection, LayoutSelection } from "@/components/issues";
|
import { DisplayFiltersSelection, FiltersDropdown, FilterSelection, LayoutSelection } from "@/components/issues";
|
||||||
import { ProjectLogo } from "@/components/project";
|
|
||||||
// constants
|
// constants
|
||||||
import { EIssuesStoreType, EIssueFilterType, ISSUE_DISPLAY_FILTERS_BY_LAYOUT } from "@/constants/issue";
|
import { EIssuesStoreType, EIssueFilterType, ISSUE_DISPLAY_FILTERS_BY_LAYOUT } from "@/constants/issue";
|
||||||
import { EUserProjectRoles } from "@/constants/project";
|
import { EUserProjectRoles } from "@/constants/project";
|
||||||
@ -141,7 +140,7 @@ export const ProjectViewIssuesHeader: React.FC = observer(() => {
|
|||||||
icon={
|
icon={
|
||||||
currentProjectDetails && (
|
currentProjectDetails && (
|
||||||
<span className="grid h-4 w-4 flex-shrink-0 place-items-center">
|
<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>
|
</span>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@ -164,7 +163,11 @@ export const ProjectViewIssuesHeader: React.FC = observer(() => {
|
|||||||
<CustomMenu
|
<CustomMenu
|
||||||
label={
|
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)}
|
{viewDetails?.name && truncateText(viewDetails.name, 40)}
|
||||||
</>
|
</>
|
||||||
}
|
}
|
||||||
@ -182,7 +185,11 @@ export const ProjectViewIssuesHeader: React.FC = observer(() => {
|
|||||||
href={`/${workspaceSlug}/projects/${projectId}/views/${viewId}`}
|
href={`/${workspaceSlug}/projects/${projectId}/views/${viewId}`}
|
||||||
className="flex items-center gap-1.5"
|
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)}
|
{truncateText(view.name, 40)}
|
||||||
</Link>
|
</Link>
|
||||||
</CustomMenu.MenuItem>
|
</CustomMenu.MenuItem>
|
||||||
|
@ -1,14 +1,13 @@
|
|||||||
import { observer } from "mobx-react";
|
import { observer } from "mobx-react";
|
||||||
import { useRouter } from "next/router";
|
import { useRouter } from "next/router";
|
||||||
// hooks
|
// ui
|
||||||
// components
|
|
||||||
import { Breadcrumbs, PhotoFilterIcon, Button } from "@plane/ui";
|
import { Breadcrumbs, PhotoFilterIcon, Button } from "@plane/ui";
|
||||||
import { BreadcrumbLink } from "@/components/common";
|
// components
|
||||||
// helpers
|
import { BreadcrumbLink, Logo } from "@/components/common";
|
||||||
import { ProjectLogo } from "@/components/project";
|
|
||||||
import { ViewListHeader } from "@/components/views";
|
import { ViewListHeader } from "@/components/views";
|
||||||
import { EUserProjectRoles } from "@/constants/project";
|
|
||||||
// constants
|
// constants
|
||||||
|
import { EUserProjectRoles } from "@/constants/project";
|
||||||
|
// hooks
|
||||||
import { useCommandPalette, useProject, useUser } from "@/hooks/store";
|
import { useCommandPalette, useProject, useUser } from "@/hooks/store";
|
||||||
|
|
||||||
export const ProjectViewsHeader: React.FC = observer(() => {
|
export const ProjectViewsHeader: React.FC = observer(() => {
|
||||||
@ -40,7 +39,7 @@ export const ProjectViewsHeader: React.FC = observer(() => {
|
|||||||
icon={
|
icon={
|
||||||
currentProjectDetails && (
|
currentProjectDetails && (
|
||||||
<span className="grid h-4 w-4 flex-shrink-0 place-items-center">
|
<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>
|
</span>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
@ -52,7 +52,7 @@ export const InboxIssueActionsHeader: FC<TInboxIssueActionsHeader> = observer((p
|
|||||||
const [declineIssueModal, setDeclineIssueModal] = useState(false);
|
const [declineIssueModal, setDeclineIssueModal] = useState(false);
|
||||||
const [deleteIssueModal, setDeleteIssueModal] = useState(false);
|
const [deleteIssueModal, setDeleteIssueModal] = useState(false);
|
||||||
// store
|
// store
|
||||||
const { currentTab, deleteInboxIssue, inboxIssuesArray } = useProjectInbox();
|
const { currentTab, deleteInboxIssue, inboxIssueIds } = useProjectInbox();
|
||||||
const { data: currentUser } = useUser();
|
const { data: currentUser } = useUser();
|
||||||
const {
|
const {
|
||||||
membership: { currentProjectRole },
|
membership: { currentProjectRole },
|
||||||
@ -76,11 +76,11 @@ export const InboxIssueActionsHeader: FC<TInboxIssueActionsHeader> = observer((p
|
|||||||
|
|
||||||
const redirectIssue = (): string | undefined => {
|
const redirectIssue = (): string | undefined => {
|
||||||
let nextOrPreviousIssueId: string | undefined = undefined;
|
let nextOrPreviousIssueId: string | undefined = undefined;
|
||||||
const currentIssueIndex = inboxIssuesArray.findIndex((i) => i.issue.id === currentInboxIssueId);
|
const currentIssueIndex = inboxIssueIds.findIndex((id) => id === currentInboxIssueId);
|
||||||
if (inboxIssuesArray[currentIssueIndex + 1])
|
if (inboxIssueIds[currentIssueIndex + 1])
|
||||||
nextOrPreviousIssueId = inboxIssuesArray[currentIssueIndex + 1].issue.id;
|
nextOrPreviousIssueId = inboxIssueIds[currentIssueIndex + 1];
|
||||||
else if (inboxIssuesArray[currentIssueIndex - 1])
|
else if (inboxIssueIds[currentIssueIndex - 1])
|
||||||
nextOrPreviousIssueId = inboxIssuesArray[currentIssueIndex - 1].issue.id;
|
nextOrPreviousIssueId = inboxIssueIds[currentIssueIndex - 1];
|
||||||
else nextOrPreviousIssueId = undefined;
|
else nextOrPreviousIssueId = undefined;
|
||||||
return nextOrPreviousIssueId;
|
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(
|
const handleInboxIssueNavigation = useCallback(
|
||||||
(direction: "next" | "prev") => {
|
(direction: "next" | "prev") => {
|
||||||
if (!inboxIssuesArray || !currentInboxIssueId) return;
|
if (!inboxIssueIds || !currentInboxIssueId) return;
|
||||||
const activeElement = document.activeElement as HTMLElement;
|
const activeElement = document.activeElement as HTMLElement;
|
||||||
if (activeElement && (activeElement.classList.contains("tiptap") || activeElement.id === "title-input")) return;
|
if (activeElement && (activeElement.classList.contains("tiptap") || activeElement.id === "title-input")) return;
|
||||||
const nextIssueIndex =
|
const nextIssueIndex =
|
||||||
direction === "next"
|
direction === "next"
|
||||||
? (currentIssueIndex + 1) % inboxIssuesArray.length
|
? (currentIssueIndex + 1) % inboxIssueIds.length
|
||||||
: (currentIssueIndex - 1 + inboxIssuesArray.length) % inboxIssuesArray.length;
|
: (currentIssueIndex - 1 + inboxIssueIds.length) % inboxIssueIds.length;
|
||||||
const nextIssueId = inboxIssuesArray[nextIssueIndex].issue.id;
|
const nextIssueId = inboxIssueIds[nextIssueIndex];
|
||||||
if (!nextIssueId) return;
|
if (!nextIssueId) return;
|
||||||
router.push(`/${workspaceSlug}/projects/${projectId}/inbox?inboxIssueId=${nextIssueId}`);
|
router.push(`/${workspaceSlug}/projects/${projectId}/inbox?inboxIssueId=${nextIssueId}`);
|
||||||
},
|
},
|
||||||
[currentInboxIssueId, currentIssueIndex, inboxIssuesArray, projectId, router, workspaceSlug]
|
[currentInboxIssueId, currentIssueIndex, inboxIssueIds, projectId, router, workspaceSlug]
|
||||||
);
|
);
|
||||||
|
|
||||||
const onKeyDown = useCallback(
|
const onKeyDown = useCallback(
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
import { FC, useState } from "react";
|
import { FC, useEffect, useState } from "react";
|
||||||
import { observer } from "mobx-react";
|
import { observer } from "mobx-react";
|
||||||
|
import { useRouter } from "next/router";
|
||||||
import useSWR from "swr";
|
import useSWR from "swr";
|
||||||
import { InboxIssueActionsHeader, InboxIssueMainContent } from "@/components/inbox";
|
import { InboxIssueActionsHeader, InboxIssueMainContent } from "@/components/inbox";
|
||||||
import { EUserProjectRoles } from "@/constants/project";
|
import { EUserProjectRoles } from "@/constants/project";
|
||||||
@ -15,14 +16,25 @@ type TInboxContentRoot = {
|
|||||||
|
|
||||||
export const InboxContentRoot: FC<TInboxContentRoot> = observer((props) => {
|
export const InboxContentRoot: FC<TInboxContentRoot> = observer((props) => {
|
||||||
const { workspaceSlug, projectId, inboxIssueId, isMobileSidebar, setIsMobileSidebar } = props;
|
const { workspaceSlug, projectId, inboxIssueId, isMobileSidebar, setIsMobileSidebar } = props;
|
||||||
|
/// router
|
||||||
|
const router = useRouter();
|
||||||
// states
|
// states
|
||||||
const [isSubmitting, setIsSubmitting] = useState<"submitting" | "submitted" | "saved">("saved");
|
const [isSubmitting, setIsSubmitting] = useState<"submitting" | "submitted" | "saved">("saved");
|
||||||
// hooks
|
// hooks
|
||||||
const { fetchInboxIssueById, getIssueInboxByIssueId } = useProjectInbox();
|
const { currentTab, fetchInboxIssueById, getIssueInboxByIssueId, getIsIssueAvailable } = useProjectInbox();
|
||||||
const inboxIssue = getIssueInboxByIssueId(inboxIssueId);
|
const inboxIssue = getIssueInboxByIssueId(inboxIssueId);
|
||||||
const {
|
const {
|
||||||
membership: { currentProjectRole },
|
membership: { currentProjectRole },
|
||||||
} = useUser();
|
} = 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(
|
useSWR(
|
||||||
workspaceSlug && projectId && inboxIssueId
|
workspaceSlug && projectId && inboxIssueId
|
||||||
|
@ -36,7 +36,7 @@ export const DeclineIssueModal: React.FC<Props> = (props) => {
|
|||||||
<AlertModalCore
|
<AlertModalCore
|
||||||
handleClose={handleClose}
|
handleClose={handleClose}
|
||||||
handleSubmit={handleDecline}
|
handleSubmit={handleDecline}
|
||||||
isDeleting={isDeclining}
|
isSubmitting={isDeclining}
|
||||||
isOpen={isOpen}
|
isOpen={isOpen}
|
||||||
title="Decline Issue"
|
title="Decline Issue"
|
||||||
content={
|
content={
|
||||||
|
@ -36,7 +36,7 @@ export const DeleteInboxIssueModal: React.FC<Props> = observer(({ isOpen, onClos
|
|||||||
<AlertModalCore
|
<AlertModalCore
|
||||||
handleClose={handleClose}
|
handleClose={handleClose}
|
||||||
handleSubmit={handleDelete}
|
handleSubmit={handleDelete}
|
||||||
isDeleting={isDeleting}
|
isSubmitting={isDeleting}
|
||||||
isOpen={isOpen}
|
isOpen={isOpen}
|
||||||
title="Delete Issue"
|
title="Delete Issue"
|
||||||
content={
|
content={
|
||||||
|
@ -12,31 +12,30 @@ import { renderFormattedDate } from "@/helpers/date-time.helper";
|
|||||||
// hooks
|
// hooks
|
||||||
import { useLabel, useMember, useProjectInbox } from "@/hooks/store";
|
import { useLabel, useMember, useProjectInbox } from "@/hooks/store";
|
||||||
import { usePlatformOS } from "@/hooks/use-platform-os";
|
import { usePlatformOS } from "@/hooks/use-platform-os";
|
||||||
// store
|
|
||||||
import { IInboxIssueStore } from "@/store/inbox/inbox-issue.store";
|
|
||||||
|
|
||||||
type InboxIssueListItemProps = {
|
type InboxIssueListItemProps = {
|
||||||
workspaceSlug: string;
|
workspaceSlug: string;
|
||||||
projectId: string;
|
projectId: string;
|
||||||
projectIdentifier?: string;
|
projectIdentifier?: string;
|
||||||
inboxIssue: IInboxIssueStore;
|
inboxIssueId: string;
|
||||||
setIsMobileSidebar: (value: boolean) => void;
|
setIsMobileSidebar: (value: boolean) => void;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const InboxIssueListItem: FC<InboxIssueListItemProps> = observer((props) => {
|
export const InboxIssueListItem: FC<InboxIssueListItemProps> = observer((props) => {
|
||||||
const { workspaceSlug, projectId, inboxIssue, projectIdentifier, setIsMobileSidebar } = props;
|
const { workspaceSlug, projectId, inboxIssueId, projectIdentifier, setIsMobileSidebar } = props;
|
||||||
// router
|
// router
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
const { inboxIssueId } = router.query;
|
const { inboxIssueId: selectedInboxIssueId } = router.query;
|
||||||
// store
|
// store
|
||||||
const { currentTab } = useProjectInbox();
|
const { currentTab, getIssueInboxByIssueId } = useProjectInbox();
|
||||||
const { projectLabels } = useLabel();
|
const { projectLabels } = useLabel();
|
||||||
const { isMobile } = usePlatformOS();
|
const { isMobile } = usePlatformOS();
|
||||||
const { getUserDetails } = useMember();
|
const { getUserDetails } = useMember();
|
||||||
const issue = inboxIssue.issue;
|
const inboxIssue = getIssueInboxByIssueId(inboxIssueId);
|
||||||
|
const issue = inboxIssue?.issue;
|
||||||
|
|
||||||
const handleIssueRedirection = (event: MouseEvent, currentIssueId: string | undefined) => {
|
const handleIssueRedirection = (event: MouseEvent, currentIssueId: string | undefined) => {
|
||||||
if (inboxIssueId === currentIssueId) event.preventDefault();
|
if (selectedInboxIssueId === currentIssueId) event.preventDefault();
|
||||||
setIsMobileSidebar(false);
|
setIsMobileSidebar(false);
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -55,7 +54,7 @@ export const InboxIssueListItem: FC<InboxIssueListItemProps> = observer((props)
|
|||||||
<div
|
<div
|
||||||
className={cn(
|
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`,
|
`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">
|
<div className="space-y-1">
|
||||||
|
@ -2,30 +2,28 @@ import { FC, Fragment } from "react";
|
|||||||
import { observer } from "mobx-react";
|
import { observer } from "mobx-react";
|
||||||
// components
|
// components
|
||||||
import { InboxIssueListItem } from "@/components/inbox";
|
import { InboxIssueListItem } from "@/components/inbox";
|
||||||
// store
|
|
||||||
import { IInboxIssueStore } from "@/store/inbox/inbox-issue.store";
|
|
||||||
|
|
||||||
export type InboxIssueListProps = {
|
export type InboxIssueListProps = {
|
||||||
workspaceSlug: string;
|
workspaceSlug: string;
|
||||||
projectId: string;
|
projectId: string;
|
||||||
projectIdentifier?: string;
|
projectIdentifier?: string;
|
||||||
inboxIssues: IInboxIssueStore[];
|
inboxIssueIds: string[];
|
||||||
setIsMobileSidebar: (value: boolean) => void;
|
setIsMobileSidebar: (value: boolean) => void;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const InboxIssueList: FC<InboxIssueListProps> = observer((props) => {
|
export const InboxIssueList: FC<InboxIssueListProps> = observer((props) => {
|
||||||
const { workspaceSlug, projectId, projectIdentifier, inboxIssues, setIsMobileSidebar } = props;
|
const { workspaceSlug, projectId, projectIdentifier, inboxIssueIds, setIsMobileSidebar } = props;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
{inboxIssues.map((inboxIssue) => (
|
{inboxIssueIds.map((inboxIssueId) => (
|
||||||
<Fragment key={inboxIssue.id}>
|
<Fragment key={inboxIssueId}>
|
||||||
<InboxIssueListItem
|
<InboxIssueListItem
|
||||||
setIsMobileSidebar={setIsMobileSidebar}
|
setIsMobileSidebar={setIsMobileSidebar}
|
||||||
workspaceSlug={workspaceSlug}
|
workspaceSlug={workspaceSlug}
|
||||||
projectId={projectId}
|
projectId={projectId}
|
||||||
projectIdentifier={projectIdentifier}
|
projectIdentifier={projectIdentifier}
|
||||||
inboxIssue={inboxIssue}
|
inboxIssueId={inboxIssueId}
|
||||||
/>
|
/>
|
||||||
</Fragment>
|
</Fragment>
|
||||||
))}
|
))}
|
||||||
|
@ -44,7 +44,7 @@ export const InboxSidebar: FC<IInboxSidebarProps> = observer((props) => {
|
|||||||
currentTab,
|
currentTab,
|
||||||
handleCurrentTab,
|
handleCurrentTab,
|
||||||
loader,
|
loader,
|
||||||
inboxIssuesArray,
|
inboxIssueIds,
|
||||||
inboxIssuePaginationInfo,
|
inboxIssuePaginationInfo,
|
||||||
fetchInboxPaginationIssues,
|
fetchInboxPaginationIssues,
|
||||||
getAppliedFiltersCount,
|
getAppliedFiltersCount,
|
||||||
@ -56,13 +56,9 @@ export const InboxSidebar: FC<IInboxSidebarProps> = observer((props) => {
|
|||||||
if (!workspaceSlug || !projectId) return;
|
if (!workspaceSlug || !projectId) return;
|
||||||
fetchInboxPaginationIssues(workspaceSlug.toString(), projectId.toString());
|
fetchInboxPaginationIssues(workspaceSlug.toString(), projectId.toString());
|
||||||
}, [workspaceSlug, projectId, fetchInboxPaginationIssues]);
|
}, [workspaceSlug, projectId, fetchInboxPaginationIssues]);
|
||||||
|
|
||||||
// page observer
|
// page observer
|
||||||
useIntersectionObserver({
|
useIntersectionObserver(containerRef, elementRef, fetchNextPages, "20%");
|
||||||
containerRef,
|
|
||||||
elementRef,
|
|
||||||
callback: fetchNextPages,
|
|
||||||
rootMargin: "20%",
|
|
||||||
});
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="bg-custom-background-100 flex-shrink-0 w-full h-full border-r border-custom-border-300 ">
|
<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"
|
className="w-full h-full overflow-hidden overflow-y-auto vertical-scrollbar scrollbar-md"
|
||||||
ref={containerRef}
|
ref={containerRef}
|
||||||
>
|
>
|
||||||
{inboxIssuesArray.length > 0 ? (
|
{inboxIssueIds.length > 0 ? (
|
||||||
<InboxIssueList
|
<InboxIssueList
|
||||||
setIsMobileSidebar={setIsMobileSidebar}
|
setIsMobileSidebar={setIsMobileSidebar}
|
||||||
workspaceSlug={workspaceSlug}
|
workspaceSlug={workspaceSlug}
|
||||||
projectId={projectId}
|
projectId={projectId}
|
||||||
projectIdentifier={currentProjectDetails?.identifier}
|
projectIdentifier={currentProjectDetails?.identifier}
|
||||||
inboxIssues={inboxIssuesArray}
|
inboxIssueIds={inboxIssueIds}
|
||||||
/>
|
/>
|
||||||
) : (
|
) : (
|
||||||
<div className="flex items-center justify-center h-full w-full">
|
<div className="flex items-center justify-center h-full w-full">
|
||||||
@ -130,15 +126,14 @@ export const InboxSidebar: FC<IInboxSidebarProps> = observer((props) => {
|
|||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
{inboxIssuePaginationInfo?.next_page_results && (
|
||||||
<div ref={elementRef}>
|
<div ref={elementRef}>
|
||||||
{inboxIssuePaginationInfo?.next_page_results && (
|
|
||||||
<Loader className="mx-auto w-full space-y-4 py-4 px-2">
|
<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.Item height="64px" width="w-100" />
|
<Loader.Item height="64px" width="w-100" />
|
||||||
</Loader>
|
</Loader>
|
||||||
)}
|
</div>
|
||||||
</div>
|
)}
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
@ -35,7 +35,7 @@ export const IssueAttachmentDeleteModal: FC<Props> = (props) => {
|
|||||||
<AlertModalCore
|
<AlertModalCore
|
||||||
handleClose={handleClose}
|
handleClose={handleClose}
|
||||||
handleSubmit={() => handleDeletion(data.id)}
|
handleSubmit={() => handleDeletion(data.id)}
|
||||||
isDeleting={loader}
|
isSubmitting={loader}
|
||||||
isOpen={isOpen}
|
isOpen={isOpen}
|
||||||
title="Delete attachment"
|
title="Delete attachment"
|
||||||
content={
|
content={
|
||||||
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user