Compare commits

..

3 Commits

Author SHA1 Message Date
pablohashescobar 1604f4585d dev: create project settings 2023-10-12 13:51:32 +05:30
pablohashescobar 03f9ca45d8 dev: create model for project setting 2023-10-11 19:24:45 +05:30
Anmol Singh Bhatia 58ea4d6ec9 chore: integrate popper js (#2398)
* chore: react-popper-js added

* chore: integrate popper js in issue properties dropdown

* chore: integrate popper js in custom menu component

* chore: integrate popper js in custom select component

* chore: integrate popper js in custom search select component

* chore: popper js placement type added

* chore: popper js placement type added
2023-10-11 12:05:53 +05:30
55 changed files with 862 additions and 552 deletions
+3 -1
View File
@@ -11,7 +11,9 @@ from .workspace import (
)
from .project import (
ProjectSerializer,
ProjectSettingSerializer,
ProjectDetailSerializer,
ProjectSettingDetailSerializer,
ProjectMemberSerializer,
ProjectMemberInviteSerializer,
ProjectIdentifierSerializer,
@@ -20,7 +22,7 @@ from .project import (
ProjectMemberLiteSerializer,
ProjectDeployBoardSerializer,
ProjectMemberAdminSerializer,
ProjectPublicMemberSerializer
ProjectPublicMemberSerializer,
)
from .state import StateSerializer, StateLiteSerializer
from .view import GlobalViewSerializer, IssueViewSerializer, IssueViewFavoriteSerializer
+26 -4
View File
@@ -10,6 +10,7 @@ from plane.api.serializers.workspace import WorkSpaceSerializer, WorkspaceLiteSe
from plane.api.serializers.user import UserLiteSerializer, UserAdminLiteSerializer
from plane.db.models import (
Project,
ProjectSetting,
ProjectMember,
ProjectMemberInvite,
ProjectIdentifier,
@@ -96,8 +97,6 @@ class ProjectLiteSerializer(BaseSerializer):
class ProjectDetailSerializer(BaseSerializer):
workspace = WorkSpaceSerializer(read_only=True)
default_assignee = UserLiteSerializer(read_only=True)
project_lead = UserLiteSerializer(read_only=True)
is_favorite = serializers.BooleanField(read_only=True)
total_members = serializers.IntegerField(read_only=True)
total_cycles = serializers.IntegerField(read_only=True)
@@ -178,12 +177,12 @@ class ProjectDeployBoardSerializer(BaseSerializer):
fields = "__all__"
read_only_fields = [
"workspace",
"project", "anchor",
"project",
"anchor",
]
class ProjectPublicMemberSerializer(BaseSerializer):
class Meta:
model = ProjectPublicMember
fields = "__all__"
@@ -192,3 +191,26 @@ class ProjectPublicMemberSerializer(BaseSerializer):
"project",
"member",
]
class ProjectSettingSerializer(BaseSerializer):
class Meta:
model = ProjectSetting
fields = "__all__"
read_only_fields = [
"workspace",
"project",
]
class ProjectSettingDetailSerializer(BaseSerializer):
default_assignee = UserLiteSerializer(read_only=True)
project_lead = UserLiteSerializer(read_only=True)
class Meta:
model = ProjectSetting
fields = "__all__"
read_only_fields = [
"workspace",
"project",
]
+21
View File
@@ -59,6 +59,7 @@ from plane.api.views import (
## End File Assets
# Projects
ProjectViewSet,
ProjectSettingViewSet,
InviteProjectEndpoint,
ProjectMemberViewSet,
ProjectMemberEndpoint,
@@ -481,6 +482,26 @@ urlpatterns = [
),
name="project",
),
path(
"workspaces/<str:slug>/projects/<uuid:project_id>/settings/",
ProjectSettingViewSet.as_view(
{
"get": "list",
"post": "create",
}
),
name="project",
),
path(
"workspaces/<str:slug>/projects/<uuid:project_id>/settings/",
ProjectSettingViewSet.as_view(
{
"get": "retrieve",
"patch": "partial_update",
}
),
name="project",
),
path(
"workspaces/<str:slug>/project-identifiers/",
ProjectIdentifierEndpoint.as_view(),
+1
View File
@@ -1,5 +1,6 @@
from .project import (
ProjectViewSet,
ProjectSettingViewSet,
ProjectMemberViewSet,
UserProjectInvitationsViewset,
InviteProjectEndpoint,
+4 -4
View File
@@ -9,7 +9,7 @@ from sentry_sdk import capture_exception
# Module imports
from .base import BaseViewSet, BaseAPIView
from plane.api.permissions import ProjectEntityPermission
from plane.db.models import Project, Estimate, EstimatePoint
from plane.db.models import ProjectSetting, Estimate, EstimatePoint
from plane.api.serializers import (
EstimateSerializer,
EstimatePointSerializer,
@@ -24,10 +24,10 @@ class ProjectEstimatePointEndpoint(BaseAPIView):
def get(self, request, slug, project_id):
try:
project = Project.objects.get(workspace__slug=slug, pk=project_id)
if project.estimate_id is not None:
project_setting = ProjectSetting.objects.get(workspace__slug=slug, pk=project_id)
if project_setting.estimate_id is not None:
estimate_points = EstimatePoint.objects.filter(
estimate_id=project.estimate_id,
estimate_id=project_setting.estimate_id,
project_id=project_id,
workspace__slug=slug,
)
+25 -32
View File
@@ -63,6 +63,7 @@ from plane.api.permissions import (
)
from plane.db.models import (
Project,
ProjectSetting,
Issue,
IssueActivity,
IssueComment,
@@ -111,6 +112,26 @@ class IssueViewSet(BaseViewSet):
def perform_create(self, serializer):
serializer.save(project_id=self.kwargs.get("project_id"))
def perform_update(self, serializer):
requested_data = json.dumps(self.request.data, cls=DjangoJSONEncoder)
current_instance = (
self.get_queryset().filter(pk=self.kwargs.get("pk", None)).first()
)
if current_instance is not None:
issue_activity.delay(
type="issue.activity.updated",
requested_data=requested_data,
actor_id=str(self.request.user.id),
issue_id=str(self.kwargs.get("pk", None)),
project_id=str(self.kwargs.get("project_id", None)),
current_instance=json.dumps(
IssueSerializer(current_instance).data, cls=DjangoJSONEncoder
),
epoch=int(timezone.now().timestamp())
)
return super().perform_update(serializer)
def perform_destroy(self, instance):
current_instance = (
self.get_queryset().filter(pk=self.kwargs.get("pk", None)).first()
@@ -273,13 +294,14 @@ class IssueViewSet(BaseViewSet):
def create(self, request, slug, project_id):
try:
project = Project.objects.get(pk=project_id)
project_setting = ProjectSetting.objects.get(workspace__slug=slug, project_id=project_id)
serializer = IssueCreateSerializer(
data=request.data,
context={
"project_id": project_id,
"workspace_id": project.workspace_id,
"default_assignee_id": project.default_assignee_id,
"default_assignee_id": project_setting.default_assignee_id,
},
)
@@ -320,35 +342,6 @@ class IssueViewSet(BaseViewSet):
{"error": "Issue Does not exist"}, status=status.HTTP_404_NOT_FOUND
)
def partial_update(self, request, slug, project_id, pk):
try:
issue = Issue.objects.get(workspace__slug=slug, project_id=project_id, pk=pk)
requested_data = json.dumps(request.data, cls=DjangoJSONEncoder)
current_instance = json.dumps(
IssueSerializer(issue).data, cls=DjangoJSONEncoder
)
serializer = IssueSerializer(issue, data=request.data, partial=True)
if serializer.is_valid():
serializer.save()
# Track this data
issue_activity.delay(
type="issue.activity.updated",
requested_data=requested_data,
actor_id=str(request.user.id),
issue_id=str(pk),
project_id=str(project_id),
current_instance=json.dumps(
IssueSerializer(current_instance).data, cls=DjangoJSONEncoder
),
epoch=int(timezone.now().timestamp())
)
return Response(serializer.data, status=status.HTTP_200_OK)
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
except Issue.DoesNotExist:
return Response({"error": "Isssue does not exists"}, status=status.HTTP_404_NOT_FOUND)
except Exception as e:
capture_exception(e)
return Response({"error": "Something went wrong please try again later"}, status=status.HTTP_400_BAD_REQUEST)
class UserWorkSpaceIssues(BaseAPIView):
@method_decorator(gzip_page)
@@ -2574,13 +2567,13 @@ class IssueDraftViewSet(BaseViewSet):
def create(self, request, slug, project_id):
try:
project = Project.objects.get(pk=project_id)
project_setting = ProjectSetting.objects.get(workspace__slug=slug, project_id=project_id)
serializer = IssueCreateSerializer(
data=request.data,
context={
"project_id": project_id,
"workspace_id": project.workspace_id,
"default_assignee_id": project.default_assignee_id,
"default_assignee_id": project_setting.default_assignee_id,
},
)
+84 -7
View File
@@ -29,11 +29,12 @@ from sentry_sdk import capture_exception
from .base import BaseViewSet, BaseAPIView
from plane.api.serializers import (
ProjectSerializer,
ProjectSettingDetailSerializer,
ProjectSettingSerializer,
ProjectMemberSerializer,
ProjectDetailSerializer,
ProjectMemberInviteSerializer,
ProjectFavoriteSerializer,
IssueLiteSerializer,
ProjectDeployBoardSerializer,
ProjectMemberAdminSerializer,
)
@@ -67,6 +68,7 @@ from plane.db.models import (
ModuleMember,
Inbox,
ProjectDeployBoard,
ProjectSetting,
)
from plane.bgtasks.project_invitation_task import project_invitation
@@ -98,7 +100,7 @@ class ProjectViewSet(BaseViewSet):
.filter(workspace__slug=self.kwargs.get("slug"))
.filter(Q(project_projectmember__member=self.request.user) | Q(network=2))
.select_related(
"workspace", "workspace__owner", "default_assignee", "project_lead"
"workspace", "workspace__owner",
)
.annotate(is_favorite=Exists(subquery))
.annotate(
@@ -210,20 +212,29 @@ class ProjectViewSet(BaseViewSet):
if serializer.is_valid():
serializer.save()
project_lead = request.data.get("project_lead", None)
# Create Project Setting
_ = ProjectSetting.objects.create(
project_id=serializer.data["id"],
project_lead_id=request.data.get("project_lead", None)
)
# Add the user as Administrator to the project
project_member = ProjectMember.objects.create(
project_id=serializer.data["id"], member=request.user, role=20
)
if serializer.data["project_lead"] is not None and str(
serializer.data["project_lead"]
if project_lead is not None and str(
project_lead
) != str(request.user.id):
ProjectMember.objects.create(
project_id=serializer.data["id"],
member_id=serializer.data["project_lead"],
member_id=project_lead,
role=20,
)
# Default states
states = [
{
@@ -307,7 +318,7 @@ class ProjectViewSet(BaseViewSet):
status=status.HTTP_410_GONE,
)
except Exception as e:
capture_exception(e)
print(e)
return Response(
{"error": "Something went wrong please try again later"},
status=status.HTTP_400_BAD_REQUEST,
@@ -973,7 +984,7 @@ class ProjectFavoritesViewSet(BaseViewSet):
.filter(workspace__slug=self.kwargs.get("slug"))
.filter(user=self.request.user)
.select_related(
"project", "project__project_lead", "project__default_assignee"
"project",
)
.select_related("workspace", "workspace__owner")
)
@@ -1246,3 +1257,69 @@ class ProjectPublicCoverImagesEndpoint(BaseAPIView):
except Exception as e:
capture_exception(e)
return Response([], status=status.HTTP_200_OK)
class ProjectSettingViewSet(BaseViewSet):
model = ProjectSetting
permission_classes = [
ProjectBasePermission,
]
def get_serializer_class(self, *args, **kwargs):
if self.action in ["create", "partial_update"]:
return ProjectSettingSerializer
return ProjectSettingDetailSerializer
def get_queryset(self):
return (
super()
.get_queryset()
.filter(
workspace__slug=self.kwargs.get("slug"),
project_id=self.kwargs.get("project_id"),
)
)
def perform_create(self, serializer):
serializer.save(project_id=self.kwargs.get("project_id"))
def list(self, request, slug, project_id):
try:
project_setting = self.get_queryset().first()
if project_setting is not None:
serializer = ProjectSettingDetailSerializer(project_setting)
return Response(serializer.data, status=status.HTTP_200_OK)
return Response(
{"error": "Project setting does not exists"},
status=status.HTTP_404_NOT_FOUND,
)
except Exception as e:
capture_exception(e)
return Response(
{"error": "Something went wrong please try again later"},
status=status.HTTP_400_BAD_REQUEST,
)
def partial_update(self, request, slug, project_id):
try:
project_setting = self.get_queryset().first()
# Check if it is None
if project_setting is not None:
serializer = ProjectSettingSerializer(
project_setting, data=request.data, partial=True
)
if serializer.is_valid():
return Response(serializer.data, status=status.HTTP_200_OK)
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
return Response(
{"error": "Project setting does not exists"},
status=status.HTTP_404_NOT_FOUND,
)
except Exception as e:
capture_exception(e)
return Response(
{"error": "Something went wrong please try again later"},
status=status.HTTP_400_BAD_REQUEST,
)
@@ -12,7 +12,7 @@ from celery import shared_task
from sentry_sdk import capture_exception
# Module imports
from plane.db.models import Issue, Project, State
from plane.db.models import Issue, ProjectSetting, State
from plane.bgtasks.issue_activites_task import issue_activity
@@ -25,11 +25,11 @@ def archive_and_close_old_issues():
def archive_old_issues():
try:
# Get all the projects whose archive_in is greater than 0
projects = Project.objects.filter(archive_in__gt=0)
project_settings = ProjectSetting.objects.filter(archive_in__gt=0)
for project in projects:
project_id = project.id
archive_in = project.archive_in
for project_setting in project_settings:
project_id = project_setting.project_id
archive_in = project_setting.archive_in
# Get all the issues whose updated_at in less that the archive_in month
issues = Issue.issue_objects.filter(
@@ -75,7 +75,7 @@ def archive_old_issues():
issue_activity.delay(
type="issue.activity.updated",
requested_data=json.dumps({"archived_at": str(archive_at)}),
actor_id=str(project.created_by_id),
actor_id=str(project_setting.created_by_id),
issue_id=issue.id,
project_id=project_id,
current_instance=None,
@@ -95,13 +95,13 @@ def archive_old_issues():
def close_old_issues():
try:
# Get all the projects whose close_in is greater than 0
projects = Project.objects.filter(close_in__gt=0).select_related(
"default_state"
project_settings = ProjectSetting.objects.filter(close_in__gt=0).select_related(
"close_state"
)
for project in projects:
project_id = project.id
close_in = project.close_in
for project_setting in project_settings:
project_id = project_setting.project_id
close_in = project_setting.close_in
# Get all the issues whose updated_at in less that the close_in month
issues = Issue.issue_objects.filter(
@@ -130,10 +130,10 @@ def close_old_issues():
# Check if Issues
if issues:
if project.default_state is None:
if project_setting.close_state is None:
close_state = State.objects.filter(group="cancelled").first()
else:
close_state = project.default_state
close_state = project_setting.close_state
issues_to_update = []
for issue in issues:
@@ -147,7 +147,7 @@ def close_old_issues():
issue_activity.delay(
type="issue.activity.updated",
requested_data=json.dumps({"closed_to": str(issue.state_id)}),
actor_id=str(project.created_by_id),
actor_id=str(project_setting.created_by_id),
issue_id=issue.id,
project_id=project_id,
current_instance=None,
@@ -0,0 +1,124 @@
# Generated by Django 4.2.3 on 2023-10-11 13:34
import uuid
from django.db import migrations, models
import django.db.models.deletion
from django.conf import settings
def create_project_settings(apps, schema_editor):
Project = apps.get_model("db", "Project")
ProjectSetting = apps.get_model("db", "ProjectSetting")
ProjectSetting.objects.bulk_create(
[
ProjectSetting(
project=project,
workspace_id=project.workspace_id,
archive_in=project.archive_in,
close_in=project.close_in,
cycle_view=project.cycle_view,
default_assignee=project.default_assignee,
default_state=project.default_state,
estimate=project.estimate,
inbox_view=project.inbox_view,
issue_views_view=project.issue_views_view,
module_view=project.module_view,
page_view=project.page_view,
project_lead=project.project_lead,
)
for project in Project.objects.all()
],
batch_size=1000,
)
class Migration(migrations.Migration):
dependencies = [
('db', '0045_issueactivity_epoch_workspacemember_issue_props_and_more'),
]
operations = [
migrations.CreateModel(
name='ProjectSetting',
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)),
('module_view', models.BooleanField(default=True)),
('cycle_view', models.BooleanField(default=True)),
('issue_views_view', models.BooleanField(default=True)),
('page_view', models.BooleanField(default=True)),
('inbox_view', models.BooleanField(default=False)),
('archive_in', models.IntegerField(default=0, validators=[django.core.validators.MinValueValidator(0), django.core.validators.MaxValueValidator(12)])),
('close_in', models.IntegerField(default=0, validators=[django.core.validators.MinValueValidator(0), django.core.validators.MaxValueValidator(12)])),
('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')),
('default_assignee', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='default_assignee', to=settings.AUTH_USER_MODEL)),
('default_state', models.ForeignKey(null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='default_state', to='db.state')),
('estimate', models.ForeignKey(null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='projects', to='db.estimate')),
('project', models.OneToOneField(on_delete=django.db.models.deletion.CASCADE, related_name='project_settings', to='db.project')),
('project_lead', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='project_lead', to=settings.AUTH_USER_MODEL)),
('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')),
('workspace', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='project_settings', to='db.workspace')),
],
options={
'verbose_name': 'Project Settings',
'verbose_name_plural': 'Project Settings',
'db_table': 'project_settings',
'ordering': ('-created_at',),
},
),
migrations.RunPython(create_project_settings),
migrations.RemoveField(
model_name='project',
name='archive_in',
),
migrations.RemoveField(
model_name='project',
name='close_in',
),
migrations.RemoveField(
model_name='project',
name='cycle_view',
),
migrations.RemoveField(
model_name='project',
name='default_assignee',
),
migrations.RemoveField(
model_name='project',
name='default_state',
),
migrations.RemoveField(
model_name='project',
name='description_html',
),
migrations.RemoveField(
model_name='project',
name='description_text',
),
migrations.RemoveField(
model_name='project',
name='estimate',
),
migrations.RemoveField(
model_name='project',
name='inbox_view',
),
migrations.RemoveField(
model_name='project',
name='issue_views_view',
),
migrations.RemoveField(
model_name='project',
name='module_view',
),
migrations.RemoveField(
model_name='project',
name='page_view',
),
migrations.RemoveField(
model_name='project',
name='project_lead',
),
]
@@ -0,0 +1,24 @@
# Generated by Django 4.2.3 on 2023-10-12 07:13
from django.conf import settings
from django.db import migrations, models
import django.db.models.deletion
class Migration(migrations.Migration):
dependencies = [
('db', '0046_auto_20231011_1334'),
]
operations = [
migrations.RemoveField(
model_name='projectsetting',
name='default_state',
),
migrations.AddField(
model_name='projectsetting',
name='close_state',
field=models.ForeignKey(null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='close_state', to='db.state'),
),
]
+1
View File
@@ -13,6 +13,7 @@ from .workspace import (
from .project import (
Project,
ProjectSetting,
ProjectMember,
ProjectBaseModel,
ProjectMemberInvite,
+86 -38
View File
@@ -38,7 +38,7 @@ def get_default_props():
},
"display_filters": {
"group_by": None,
"order_by": '-created_at',
"order_by": "-created_at",
"type": None,
"sub_issue": True,
"show_empty_groups": True,
@@ -56,12 +56,6 @@ class Project(BaseModel):
NETWORK_CHOICES = ((0, "Secret"), (2, "Public"))
name = models.CharField(max_length=255, verbose_name="Project Name")
description = models.TextField(verbose_name="Project Description", blank=True)
description_text = models.JSONField(
verbose_name="Project Description RT", blank=True, null=True
)
description_html = models.JSONField(
verbose_name="Project Description HTML", blank=True, null=True
)
network = models.PositiveSmallIntegerField(default=2, choices=NETWORK_CHOICES)
workspace = models.ForeignKey(
"db.WorkSpace", on_delete=models.CASCADE, related_name="workspace_project"
@@ -70,40 +64,40 @@ class Project(BaseModel):
max_length=12,
verbose_name="Project Identifier",
)
default_assignee = models.ForeignKey(
settings.AUTH_USER_MODEL,
on_delete=models.CASCADE,
related_name="default_assignee",
null=True,
blank=True,
)
project_lead = models.ForeignKey(
settings.AUTH_USER_MODEL,
on_delete=models.CASCADE,
related_name="project_lead",
null=True,
blank=True,
)
# default_assignee = models.ForeignKey(
# settings.AUTH_USER_MODEL,
# on_delete=models.CASCADE,
# related_name="default_assignee",
# null=True,
# blank=True,
# )
# project_lead = models.ForeignKey(
# settings.AUTH_USER_MODEL,
# on_delete=models.CASCADE,
# related_name="project_lead",
# null=True,
# blank=True,
# )
emoji = models.CharField(max_length=255, null=True, blank=True)
icon_prop = models.JSONField(null=True)
module_view = models.BooleanField(default=True)
cycle_view = models.BooleanField(default=True)
issue_views_view = models.BooleanField(default=True)
page_view = models.BooleanField(default=True)
inbox_view = models.BooleanField(default=False)
# module_view = models.BooleanField(default=True)
# cycle_view = models.BooleanField(default=True)
# issue_views_view = models.BooleanField(default=True)
# page_view = models.BooleanField(default=True)
# inbox_view = models.BooleanField(default=False)
cover_image = models.URLField(blank=True, null=True, max_length=800)
estimate = models.ForeignKey(
"db.Estimate", on_delete=models.SET_NULL, related_name="projects", null=True
)
archive_in = models.IntegerField(
default=0, validators=[MinValueValidator(0), MaxValueValidator(12)]
)
close_in = models.IntegerField(
default=0, validators=[MinValueValidator(0), MaxValueValidator(12)]
)
default_state = models.ForeignKey(
"db.State", on_delete=models.SET_NULL, null=True, related_name="default_state"
)
# estimate = models.ForeignKey(
# "db.Estimate", on_delete=models.SET_NULL, related_name="projects", null=True
# )
# archive_in = models.IntegerField(
# default=0, validators=[MinValueValidator(0), MaxValueValidator(12)]
# )
# close_in = models.IntegerField(
# default=0, validators=[MinValueValidator(0), MaxValueValidator(12)]
# )
# default_state = models.ForeignKey(
# "db.State", on_delete=models.SET_NULL, null=True, related_name="default_state"
# )
def __str__(self):
"""Return name of the project"""
@@ -121,6 +115,60 @@ class Project(BaseModel):
return super().save(*args, **kwargs)
class ProjectSetting(BaseModel):
workspace = models.ForeignKey(
"db.Workspace", on_delete=models.CASCADE, related_name="project_settings"
)
project = models.OneToOneField(
"db.Project", on_delete=models.CASCADE, related_name="project_settings"
)
default_assignee = models.ForeignKey(
settings.AUTH_USER_MODEL,
on_delete=models.CASCADE,
related_name="default_assignee",
null=True,
blank=True,
)
project_lead = models.ForeignKey(
settings.AUTH_USER_MODEL,
on_delete=models.CASCADE,
related_name="project_lead",
null=True,
blank=True,
)
module_view = models.BooleanField(default=True)
cycle_view = models.BooleanField(default=True)
issue_views_view = models.BooleanField(default=True)
page_view = models.BooleanField(default=True)
inbox_view = models.BooleanField(default=False)
estimate = models.ForeignKey(
"db.Estimate", on_delete=models.SET_NULL, related_name="projects", null=True
)
archive_in = models.IntegerField(
default=0, validators=[MinValueValidator(0), MaxValueValidator(12)]
)
close_in = models.IntegerField(
default=0, validators=[MinValueValidator(0), MaxValueValidator(12)]
)
close_state = models.ForeignKey(
"db.State", on_delete=models.SET_NULL, null=True, related_name="close_state"
)
def save(self, *args, **kwargs):
self.workspace = self.project.workspace
super(ProjectSetting, self).save(*args, **kwargs)
def __str__(self):
"""Return name of the project"""
return f"{self.project.name} <{self.workspace.name}>"
class Meta:
verbose_name = "Project Settings"
verbose_name_plural = "Project Settings"
db_table = "project_settings"
ordering = ("-created_at",)
class ProjectBaseModel(BaseModel):
project = models.ForeignKey(
Project, on_delete=models.CASCADE, related_name="project_%(class)s"
@@ -74,7 +74,6 @@ export const AutoArchiveAutomation: React.FC<Props> = ({
handleChange({ archive_in: val });
}}
input
verticalPosition="bottom"
width="w-full"
disabled={disabled}
>
@@ -100,7 +100,6 @@ export const ThemeSwitch: React.FC<Props> = observer(
}}
input
width="w-full"
position="right"
>
{THEMES_OBJ.map(({ value, label, type, icon }) => (
<CustomSelect.Option key={value} value={{ value, type }}>
@@ -255,15 +255,11 @@ export const SingleBoard: React.FC<Props> = (props) => {
!isDraftIssuesPage && (
<CustomMenu
customButton={
<button
type="button"
className="flex items-center gap-2 font-medium text-custom-primary outline-none whitespace-nowrap"
>
<div className="flex items-center gap-2 font-medium text-custom-primary outline-none whitespace-nowrap">
<PlusIcon className="h-4 w-4" />
Add Issue
</button>
</div>
}
position="left"
noBorder
>
<CustomMenu.MenuItem
@@ -369,12 +369,12 @@ export const SingleBoardIssue: React.FC<Props> = ({
{type && !isNotAllowed && (
<CustomMenu
customButton={
<button
<div
className="flex w-full cursor-pointer items-center justify-between gap-1 rounded p-1 text-left text-xs duration-300 hover:bg-custom-background-80"
onClick={() => setIsMenuActive(!isMenuActive)}
>
<EllipsisHorizontalIcon className="h-4 w-4" />
</button>
</div>
}
>
<CustomMenu.MenuItem
@@ -272,7 +272,6 @@ export const SingleList: React.FC<Props> = (props) => {
<PlusIcon className="h-4 w-4" />
</div>
}
position="right"
noBorder
>
<CustomMenu.MenuItem onClick={() => setIsCreateIssueFormOpen(true)}>
@@ -255,7 +255,6 @@ export const SpreadsheetView: React.FC<Props> = ({
<CustomMenu
customButtonClassName="!w-full"
className="!w-full"
position="left"
customButton={
<div
className={`relative group flex items-center justify-between gap-1.5 cursor-pointer text-sm text-custom-text-200 hover:text-custom-text-100 w-full py-3 px-2 ${
@@ -641,16 +640,11 @@ export const SpreadsheetView: React.FC<Props> = ({
<CustomMenu
className="sticky left-0 z-10"
customButton={
<button
className="flex gap-1.5 items-center text-custom-primary-100 pl-4 py-2.5 text-sm sticky left-0 z-[1] border-custom-border-200 w-full"
type="button"
>
<div className="flex gap-1.5 items-center text-custom-primary-100 pl-4 py-2.5 text-sm sticky left-0 z-[1] border-custom-border-200 w-full">
<PlusIcon className="h-4 w-4" />
New Issue
</button>
</div>
}
position="left"
verticalPosition="top"
optionsClassName="left-5 !w-36"
noBorder
>
@@ -92,7 +92,6 @@ export const SelectRepository: React.FC<Props> = ({
)}
</>
}
position="right"
optionsClassName="w-full"
/>
);
@@ -143,7 +143,6 @@ export const JiraGetImportDetail: React.FC = () => {
)}
</span>
}
verticalPosition="top"
>
{projects && projects.length > 0 ? (
projects.map((project) => (
+1 -1
View File
@@ -158,7 +158,7 @@ export const IssueMainContent: React.FC<Props> = ({
</a>
</Link>
<CustomMenu position="left" ellipsis optionsClassName="px-1.5">
<CustomMenu ellipsis optionsClassName="px-1.5">
{siblingIssuesList ? (
siblingIssuesList.length > 0 ? (
<>
@@ -93,7 +93,6 @@ export const PeekOverviewHeader: React.FC<Props> = ({
<Icon iconName={peekModes.find((m) => m.key === mode)?.icon ?? ""} />
</button>
}
position="left"
>
{peekModes.map((mode) => (
<CustomSelect.Option key={mode.key} value={mode.key}>
@@ -33,7 +33,6 @@ export const IssueEstimateSelect: React.FC<Props> = ({ value, onChange }) => {
</div>
}
onChange={onChange}
position="right"
width="w-full min-w-[8rem]"
noChevron
>
@@ -91,7 +91,6 @@ export const SidebarCycleSelect: React.FC<Props> = ({
: handleCycleChange(incompleteCycles?.find((c) => c.id === value) as ICycle);
}}
width="w-full"
position="right"
maxHeight="rg"
disabled={disabled}
>
@@ -20,17 +20,14 @@ export const SidebarEstimateSelect: React.FC<Props> = ({ value, onChange, disabl
<CustomSelect
value={value}
customButton={
<button
type="button"
className="flex items-center gap-1.5 !text-sm bg-custom-background-80 rounded px-2.5 py-0.5"
>
<div className="flex items-center gap-1.5 !text-sm bg-custom-background-80 rounded px-2.5 py-0.5">
<PlayIcon
className={`h-4 w-4 -rotate-90 ${
value !== null ? "text-custom-text-100" : "text-custom-text-200"
}`}
/>
{estimatePoints?.find((e) => e.key === value)?.value ?? "No estimate"}
</button>
</div>
}
onChange={onChange}
disabled={disabled}
@@ -87,7 +87,6 @@ export const SidebarModuleSelect: React.FC<Props> = ({
: handleModuleChange(modules?.find((m) => m.id === value) as IModule);
}}
width="w-full"
position="right"
maxHeight="rg"
disabled={disabled}
>
@@ -18,8 +18,7 @@ type Props = {
export const SidebarPrioritySelect: React.FC<Props> = ({ value, onChange, disabled = false }) => (
<CustomSelect
customButton={
<button
type="button"
<div
className={`flex items-center gap-1.5 text-left text-xs capitalize rounded px-2.5 py-0.5 ${
value === "urgent"
? "border-red-500/20 bg-red-500/20 text-red-500"
@@ -36,7 +35,7 @@ export const SidebarPrioritySelect: React.FC<Props> = ({ value, onChange, disabl
<PriorityIcon priority={value} className="!text-sm" />
</span>
<span>{value ?? "None"}</span>
</button>
</div>
}
value={value}
onChange={onChange}
@@ -39,7 +39,7 @@ export const SidebarStateSelect: React.FC<Props> = ({ value, onChange, disabled
return (
<CustomSelect
customButton={
<button type="button" className="bg-custom-background-80 text-xs rounded px-2.5 py-0.5">
<div className="bg-custom-background-80 text-xs rounded px-2.5 py-0.5">
{selectedState ? (
<div className="flex items-center gap-1.5 text-left text-custom-text-100">
<StateGroupIcon stateGroup={selectedState.group} color={selectedState.color} />
@@ -53,12 +53,11 @@ export const SidebarStateSelect: React.FC<Props> = ({ value, onChange, disabled
) : (
"None"
)}
</button>
</div>
}
value={value}
onChange={onChange}
optionsClassName="w-min"
position="left"
disabled={disabled}
>
{states ? (
@@ -247,7 +247,6 @@ export const SubIssuesRoot: React.FC<ISubIssuesRoot> = ({ parentIssue, user }) =
</>
}
buttonClassName="whitespace-nowrap"
position="left"
noBorder
noChevron
>
@@ -283,7 +282,6 @@ export const SubIssuesRoot: React.FC<ISubIssuesRoot> = ({ parentIssue, user }) =
</>
}
buttonClassName="whitespace-nowrap"
position="left"
noBorder
noChevron
>
@@ -113,10 +113,8 @@ export const ViewAssigneeSelect: React.FC<Props> = ({
{...(customButton ? { customButton: assigneeLabel } : { label: assigneeLabel })}
multiple
noChevron
position={position}
disabled={isNotAllowed}
onOpen={() => setFetchAssignees(true)}
selfPositioned={selfPositioned}
width="w-full min-w-[12rem]"
/>
);
@@ -74,8 +74,6 @@ export const ViewEstimateSelect: React.FC<Props> = ({
maxHeight="md"
noChevron
disabled={isNotAllowed}
position={position}
selfPositioned={selfPositioned}
width="w-full min-w-[8rem]"
>
<CustomSelect.Option value={null}>
@@ -146,9 +146,7 @@ export const ViewLabelSelect: React.FC<Props> = ({
{...(customButton ? { customButton: labelsLabel } : { label: labelsLabel })}
multiple
noChevron
position={position}
disabled={isNotAllowed}
selfPositioned={selfPositioned}
footerOption={footerOption}
width="w-full min-w-[12rem]"
/>
@@ -59,8 +59,7 @@ export const ViewPrioritySelect: React.FC<Props> = ({
}}
maxHeight="md"
customButton={
<button
type="button"
<div
className={`grid place-items-center rounded ${
isNotAllowed ? "cursor-not-allowed" : "cursor-pointer"
} ${noBorder ? "" : "h-6 w-6 border shadow-sm"} ${
@@ -94,12 +93,10 @@ export const ViewPrioritySelect: React.FC<Props> = ({
{noBorder ? capitalizeFirstLetter(issue.priority ?? "None") : ""}
</span>
</Tooltip>
</button>
</div>
}
noChevron
disabled={isNotAllowed}
position={position}
selfPositioned={selfPositioned}
>
{PRIORITIES?.map((priority) => (
<CustomSelect.Option key={priority} value={priority} className="capitalize">
@@ -65,7 +65,6 @@ export const SidebarLeadSelect: React.FC<Props> = ({ value, onChange }) => {
}
options={options}
maxHeight="md"
position="right"
onChange={onChange}
/>
</div>
@@ -64,7 +64,6 @@ export const SidebarMembersSelect: React.FC<Props> = ({ value, onChange }) => {
options={options}
onChange={onChange}
maxHeight="md"
position="right"
multiple
/>
</div>
@@ -214,12 +214,9 @@ export const NotificationCard: React.FC<NotificationCardProps> = (props) => {
e.stopPropagation();
}}
customButton={
<button
type="button"
className="text-sm flex w-full items-center gap-x-2 bg-custom-background-80 hover:bg-custom-background-100 p-0.5 rounded"
>
<div className="text-sm flex w-full items-center gap-x-2 bg-custom-background-80 hover:bg-custom-background-100 p-0.5 rounded">
<Icon iconName="schedule" className="h-5 w-5 text-custom-text-300" />
</button>
</div>
}
optionsClassName="!z-20"
>
@@ -167,7 +167,6 @@ export const UserDetails: React.FC<Props> = ({ user }) => {
}
input
width="w-full"
verticalPosition="top"
>
{USER_ROLES.map((item) => (
<CustomSelect.Option key={item.value} value={item.value}>
@@ -197,7 +196,6 @@ export const UserDetails: React.FC<Props> = ({ user }) => {
}
options={timeZoneOptions}
onChange={onChange}
verticalPosition="top"
optionsClassName="w-full"
input
/>
+2 -2
View File
@@ -384,12 +384,12 @@ export const SinglePageBlock: React.FC<Props> = ({
</button>
<CustomMenu
customButton={
<button
<div
className="flex w-full cursor-pointer items-center justify-between gap-1 rounded px-2.5 py-1 text-left text-xs duration-300 hover:bg-custom-background-90"
onClick={() => setIsMenuActive(!isMenuActive)}
>
<BoltIcon className="h-4.5 w-3.5" />
</button>
</div>
}
>
{block.issue ? (
@@ -418,7 +418,6 @@ export const CreateProjectModal: React.FC<Props> = ({
)}
</div>
}
verticalPosition="top"
noChevron
/>
);
+39 -33
View File
@@ -1,13 +1,13 @@
import React, { useRef, useState } from "react";
import React, { useState } from "react";
import useSWR from "swr";
import { useRouter } from "next/router";
// react-popper
import { usePopper } from "react-popper";
// services
import issuesService from "services/issues.service";
// hooks
import useDynamicDropdownPosition from "hooks/use-dynamic-dropdown";
// headless ui
import { Combobox } from "@headlessui/react";
// component
@@ -19,6 +19,7 @@ import { PlusIcon } from "lucide-react";
// types
import { Tooltip } from "components/ui";
import { ICurrentUserResponse, IIssueLabels } from "types";
import { Placement } from "@popperjs/core";
// constants
import { PROJECT_ISSUE_LABELS } from "constants/fetch-keys";
@@ -31,6 +32,7 @@ type Props = {
buttonClassName?: string;
optionsClassName?: string;
maxRender?: number;
placement?: Placement;
hideDropdownArrow?: boolean;
disabled?: boolean;
user: ICurrentUserResponse | undefined;
@@ -45,21 +47,25 @@ export const LabelSelect: React.FC<Props> = ({
buttonClassName = "",
optionsClassName = "",
maxRender = 2,
placement,
hideDropdownArrow = false,
disabled = false,
user,
}) => {
const [query, setQuery] = useState("");
const [isOpen, setIsOpen] = useState(false);
const [fetchStates, setFetchStates] = useState(false);
const [referenceElement, setReferenceElement] = useState<HTMLButtonElement | null>(null);
const [popperElement, setPopperElement] = useState<HTMLDivElement | null>(null);
const [labelModal, setLabelModal] = useState(false);
const router = useRouter();
const { workspaceSlug } = router.query;
const dropdownBtn = useRef<any>(null);
const dropdownOptions = useRef<any>(null);
const { styles, attributes } = usePopper(referenceElement, popperElement, {
placement: placement ?? "bottom-start",
});
const { data: issueLabels } = useSWR<IIssueLabels[]>(
projectId && fetchStates ? PROJECT_ISSUE_LABELS(projectId) : null,
@@ -131,8 +137,6 @@ export const LabelSelect: React.FC<Props> = ({
</div>
);
useDynamicDropdownPosition(isOpen, () => setIsOpen(false), dropdownBtn, dropdownOptions);
const footerOption = (
<button
type="button"
@@ -165,33 +169,35 @@ export const LabelSelect: React.FC<Props> = ({
multiple
>
{({ open }: { open: boolean }) => {
if (open) {
if (!isOpen) setIsOpen(true);
setFetchStates(true);
} else if (isOpen) setIsOpen(false);
if (open) setFetchStates(true);
return (
<>
<Combobox.Button
ref={dropdownBtn}
type="button"
className={`flex items-center justify-between gap-1 w-full text-xs ${
disabled
? "cursor-not-allowed text-custom-text-200"
: value.length <= maxRender
? "cursor-pointer"
: "cursor-pointer hover:bg-custom-background-80"
} ${buttonClassName}`}
>
{label}
{!hideDropdownArrow && !disabled && (
<ChevronDownIcon className="h-3 w-3" aria-hidden="true" />
)}
<Combobox.Button as={React.Fragment}>
<button
ref={setReferenceElement}
type="button"
className={`flex items-center justify-between gap-1 w-full text-xs ${
disabled
? "cursor-not-allowed text-custom-text-200"
: value.length <= maxRender
? "cursor-pointer"
: "cursor-pointer hover:bg-custom-background-80"
} ${buttonClassName}`}
>
{label}
{!hideDropdownArrow && !disabled && (
<ChevronDownIcon className="h-3 w-3" aria-hidden="true" />
)}
</button>
</Combobox.Button>
<div className={`${open ? "fixed z-20 top-0 left-0 h-full w-full cursor-auto" : ""}`}>
<Combobox.Options
ref={dropdownOptions}
className={`absolute z-10 border border-custom-border-300 px-2 py-2.5 rounded bg-custom-background-100 text-xs shadow-lg focus:outline-none w-48 whitespace-nowrap mt-1 ${optionsClassName}`}
<Combobox.Options>
<div
className={`z-10 border border-custom-border-300 px-2 py-2.5 rounded bg-custom-background-100 text-xs shadow-custom-shadow-rg focus:outline-none w-48 whitespace-nowrap my-1 ${optionsClassName}`}
ref={setPopperElement}
style={styles.popper}
{...attributes.popper}
>
<div className="flex w-full items-center justify-start rounded border border-custom-border-200 bg-custom-background-90 px-2">
<MagnifyingGlassIcon className="h-3.5 w-3.5 text-custom-text-300" />
@@ -234,8 +240,8 @@ export const LabelSelect: React.FC<Props> = ({
)}
</div>
{footerOption}
</Combobox.Options>
</div>
</div>
</Combobox.Options>
</>
);
}}
-1
View File
@@ -77,7 +77,6 @@ export const MemberSelect: React.FC<Props> = ({ value, onChange, isDisabled = fa
]
}
maxHeight="md"
position="right"
width="w-full"
onChange={onChange}
disabled={isDisabled}
+36 -30
View File
@@ -1,9 +1,10 @@
import React, { useRef, useState } from "react";
import React, { useState } from "react";
import { useRouter } from "next/router";
// react-popper
import { usePopper } from "react-popper";
// hooks
import useDynamicDropdownPosition from "hooks/use-dynamic-dropdown";
import useProjectMembers from "hooks/use-project-members";
import useWorkspaceMembers from "hooks/use-workspace-members";
// headless ui
@@ -15,6 +16,7 @@ import { ChevronDownIcon } from "@heroicons/react/20/solid";
import { CheckIcon, MagnifyingGlassIcon } from "@heroicons/react/24/outline";
// types
import { IUser } from "types";
import { Placement } from "@popperjs/core";
type Props = {
value: string | string[];
@@ -25,6 +27,7 @@ type Props = {
className?: string;
buttonClassName?: string;
optionsClassName?: string;
placement?: Placement;
hideDropdownArrow?: boolean;
disabled?: boolean;
};
@@ -38,18 +41,22 @@ export const MembersSelect: React.FC<Props> = ({
className = "",
buttonClassName = "",
optionsClassName = "",
placement,
hideDropdownArrow = false,
disabled = false,
}) => {
const [query, setQuery] = useState("");
const [isOpen, setIsOpen] = useState(false);
const [fetchStates, setFetchStates] = useState(false);
const [referenceElement, setReferenceElement] = useState<HTMLButtonElement | null>(null);
const [popperElement, setPopperElement] = useState<HTMLDivElement | null>(null);
const router = useRouter();
const { workspaceSlug } = router.query;
const dropdownBtn = useRef<any>(null);
const dropdownOptions = useRef<any>(null);
const { styles, attributes } = usePopper(referenceElement, popperElement, {
placement: placement ?? "bottom-start",
});
const { members } = useProjectMembers(
workspaceSlug?.toString(),
@@ -105,8 +112,6 @@ export const MembersSelect: React.FC<Props> = ({
</Tooltip>
);
useDynamicDropdownPosition(isOpen, () => setIsOpen(false), dropdownBtn, dropdownOptions);
return (
<Combobox
as="div"
@@ -117,31 +122,32 @@ export const MembersSelect: React.FC<Props> = ({
multiple
>
{({ open }: { open: boolean }) => {
if (open) {
if (!isOpen) setIsOpen(true);
setFetchStates(true);
} else if (isOpen) setIsOpen(false);
if (open) setFetchStates(true);
return (
<>
<Combobox.Button
ref={dropdownBtn}
type="button"
className={`flex items-center justify-between gap-1 w-full text-xs ${
disabled
? "cursor-not-allowed text-custom-text-200"
: "cursor-pointer hover:bg-custom-background-80"
} ${buttonClassName}`}
>
{label}
{!hideDropdownArrow && !disabled && (
<ChevronDownIcon className="h-3 w-3" aria-hidden="true" />
)}
<Combobox.Button as={React.Fragment}>
<button
ref={setReferenceElement}
type="button"
className={`flex items-center justify-between gap-1 w-full text-xs ${
disabled
? "cursor-not-allowed text-custom-text-200"
: "cursor-pointer hover:bg-custom-background-80"
} ${buttonClassName}`}
>
{label}
{!hideDropdownArrow && !disabled && (
<ChevronDownIcon className="h-3 w-3" aria-hidden="true" />
)}
</button>
</Combobox.Button>
<div className={`${open ? "fixed z-20 top-0 left-0 h-full w-full cursor-auto" : ""}`}>
<Combobox.Options
ref={dropdownOptions}
className={`absolute z-10 border border-custom-border-300 px-2 py-2.5 rounded bg-custom-background-100 text-xs shadow-lg focus:outline-none w-48 whitespace-nowrap mt-1 ${optionsClassName}`}
<Combobox.Options>
<div
className={`z-10 border border-custom-border-300 px-2 py-2.5 rounded bg-custom-background-100 text-xs shadow-custom-shadow-rg focus:outline-none w-48 whitespace-nowrap my-1 ${optionsClassName}`}
ref={setPopperElement}
style={styles.popper}
{...attributes.popper}
>
<div className="flex w-full items-center justify-start rounded border border-custom-border-200 bg-custom-background-90 px-2">
<MagnifyingGlassIcon className="h-3.5 w-3.5 text-custom-text-300" />
@@ -183,8 +189,8 @@ export const MembersSelect: React.FC<Props> = ({
<p className="text-center text-custom-text-200">Loading...</p>
)}
</div>
</Combobox.Options>
</div>
</div>
</Combobox.Options>
</>
);
}}
+77 -79
View File
@@ -1,7 +1,7 @@
import React, { useRef, useState } from "react";
import React, { useState } from "react";
// hooks
import useDynamicDropdownPosition from "hooks/use-dynamic-dropdown";
// react-popper
import { usePopper } from "react-popper";
// headless ui
import { Combobox } from "@headlessui/react";
// icons
@@ -12,6 +12,7 @@ import { PriorityIcon } from "components/icons";
import { Tooltip } from "components/ui";
// types
import { TIssuePriorities } from "types";
import { Placement } from "@popperjs/core";
// constants
import { PRIORITIES } from "constants/project";
@@ -21,6 +22,7 @@ type Props = {
className?: string;
buttonClassName?: string;
optionsClassName?: string;
placement?: Placement;
hideDropdownArrow?: boolean;
disabled?: boolean;
};
@@ -31,14 +33,18 @@ export const PrioritySelect: React.FC<Props> = ({
className = "",
buttonClassName = "",
optionsClassName = "",
placement,
hideDropdownArrow = false,
disabled = false,
}) => {
const [query, setQuery] = useState("");
const [isOpen, setIsOpen] = useState(false);
const dropdownBtn = useRef<any>(null);
const dropdownOptions = useRef<any>(null);
const [referenceElement, setReferenceElement] = useState<HTMLButtonElement | null>(null);
const [popperElement, setPopperElement] = useState<HTMLDivElement | null>(null);
const { styles, attributes } = usePopper(referenceElement, popperElement, {
placement: placement ?? "bottom-start",
});
const options = PRIORITIES?.map((priority) => ({
value: priority,
@@ -87,8 +93,6 @@ export const PrioritySelect: React.FC<Props> = ({
</Tooltip>
);
useDynamicDropdownPosition(isOpen, () => setIsOpen(false), dropdownBtn, dropdownOptions);
return (
<Combobox
as="div"
@@ -97,77 +101,71 @@ export const PrioritySelect: React.FC<Props> = ({
onChange={onChange}
disabled={disabled}
>
{({ open }: { open: boolean }) => {
if (open) {
if (!isOpen) setIsOpen(true);
} else if (isOpen) setIsOpen(false);
return (
<>
<Combobox.Button
ref={dropdownBtn}
type="button"
className={`flex items-center justify-between gap-1 w-full text-xs ${
disabled
? "cursor-not-allowed text-custom-text-200"
: "cursor-pointer hover:bg-custom-background-80"
} ${buttonClassName}`}
>
{label}
{!hideDropdownArrow && !disabled && (
<ChevronDownIcon className="h-3 w-3" aria-hidden="true" />
)}
</Combobox.Button>
<div className={`${open ? "fixed z-20 top-0 left-0 h-full w-full cursor-auto" : ""}`}>
<Combobox.Options
ref={dropdownOptions}
className={`absolute z-10 border border-custom-border-300 px-2 py-2.5 rounded bg-custom-background-100 text-xs shadow-lg focus:outline-none w-48 whitespace-nowrap mt-1 ${optionsClassName}`}
>
<div className="flex w-full items-center justify-start rounded border border-custom-border-200 bg-custom-background-90 px-2">
<MagnifyingGlassIcon className="h-3.5 w-3.5 text-custom-text-300" />
<Combobox.Input
className="w-full bg-transparent py-1 px-2 text-xs text-custom-text-200 placeholder:text-custom-text-400 focus:outline-none"
value={query}
onChange={(e) => setQuery(e.target.value)}
placeholder="Search"
displayValue={(assigned: any) => assigned?.name}
/>
</div>
<div className={`mt-2 space-y-1 max-h-48 overflow-y-scroll`}>
{filteredOptions ? (
filteredOptions.length > 0 ? (
filteredOptions.map((option) => (
<Combobox.Option
key={option.value}
value={option.value}
className={({ active, selected }) =>
`flex items-center justify-between gap-2 cursor-pointer select-none truncate rounded px-1 py-1.5 ${
active && !selected ? "bg-custom-background-80" : ""
} ${selected ? "text-custom-text-100" : "text-custom-text-200"}`
}
>
{({ selected }) => (
<>
{option.content}
{selected && <CheckIcon className={`h-3.5 w-3.5`} />}
</>
)}
</Combobox.Option>
))
) : (
<span className="flex items-center gap-2 p-1">
<p className="text-left text-custom-text-200 ">No matching results</p>
</span>
)
) : (
<p className="text-center text-custom-text-200">Loading...</p>
)}
</div>
</Combobox.Options>
</div>
</>
);
}}
<Combobox.Button as={React.Fragment}>
<button
ref={setReferenceElement}
type="button"
className={`flex items-center justify-between gap-1 w-full text-xs ${
disabled
? "cursor-not-allowed text-custom-text-200"
: "cursor-pointer hover:bg-custom-background-80"
} ${buttonClassName}`}
>
{label}
{!hideDropdownArrow && !disabled && (
<ChevronDownIcon className="h-3 w-3" aria-hidden="true" />
)}
</button>
</Combobox.Button>
<Combobox.Options>
<div
className={`z-10 border border-custom-border-300 px-2 py-2.5 rounded bg-custom-background-100 text-xs shadow-custom-shadow-rg focus:outline-none w-48 whitespace-nowrap my-1 ${optionsClassName}`}
ref={setPopperElement}
style={styles.popper}
{...attributes.popper}
>
<div className="flex w-full items-center justify-start rounded border border-custom-border-200 bg-custom-background-90 px-2">
<MagnifyingGlassIcon className="h-3.5 w-3.5 text-custom-text-300" />
<Combobox.Input
className="w-full bg-transparent py-1 px-2 text-xs text-custom-text-200 placeholder:text-custom-text-400 focus:outline-none"
value={query}
onChange={(e) => setQuery(e.target.value)}
placeholder="Search"
displayValue={(assigned: any) => assigned?.name}
/>
</div>
<div className={`mt-2 space-y-1 max-h-48 overflow-y-scroll`}>
{filteredOptions ? (
filteredOptions.length > 0 ? (
filteredOptions.map((option) => (
<Combobox.Option
key={option.value}
value={option.value}
className={({ active, selected }) =>
`flex items-center justify-between gap-2 cursor-pointer select-none truncate rounded px-1 py-1.5 ${
active && !selected ? "bg-custom-background-80" : ""
} ${selected ? "text-custom-text-100" : "text-custom-text-200"}`
}
>
{({ selected }) => (
<>
{option.content}
{selected && <CheckIcon className={`h-3.5 w-3.5`} />}
</>
)}
</Combobox.Option>
))
) : (
<span className="flex items-center gap-2 p-1">
<p className="text-left text-custom-text-200 ">No matching results</p>
</span>
)
) : (
<p className="text-center text-custom-text-200">Loading...</p>
)}
</div>
</div>
</Combobox.Options>
</Combobox>
);
};
@@ -230,7 +230,6 @@ const SendProjectInvitationModal: React.FC<Props> = (props) => {
onChange(val);
}}
options={options}
position="left"
width="w-full min-w-[12rem]"
/>
)}
@@ -252,12 +251,12 @@ const SendProjectInvitationModal: React.FC<Props> = (props) => {
<CustomSelect
{...field}
customButton={
<button className="flex w-full items-center justify-between gap-1 rounded-md border border-custom-border-200 shadow-sm duration-300 text-custom-text-200 hover:text-custom-text-100 hover:bg-custom-background-80 focus:outline-none px-3 py-2.5 text-sm text-left">
<div className="flex w-full items-center justify-between gap-1 rounded-md border border-custom-border-200 shadow-sm duration-300 text-custom-text-200 hover:text-custom-text-100 hover:bg-custom-background-80 focus:outline-none px-3 py-2.5 text-sm text-left">
<span className="capitalize">
{field.value ? ROLE[field.value] : "Select role"}
</span>
<ChevronDownIcon className="h-3 w-3" aria-hidden="true" />
</button>
</div>
}
input
width="w-full"
+36 -31
View File
@@ -1,11 +1,11 @@
import React, { useRef, useState } from "react";
import React, { useState } from "react";
import { useRouter } from "next/router";
import useSWR from "swr";
// hooks
import useDynamicDropdownPosition from "hooks/use-dynamic-dropdown";
// react-popper
import { usePopper } from "react-popper";
// services
import stateService from "services/state.service";
// headless ui
@@ -16,6 +16,7 @@ import { CheckIcon, MagnifyingGlassIcon } from "@heroicons/react/24/outline";
import { StateGroupIcon } from "components/icons";
// types
import { Tooltip } from "components/ui";
import { Placement } from "@popperjs/core";
// constants
import { IState } from "types";
import { STATES_LIST } from "constants/fetch-keys";
@@ -29,6 +30,7 @@ type Props = {
className?: string;
buttonClassName?: string;
optionsClassName?: string;
placement?: Placement;
hideDropdownArrow?: boolean;
disabled?: boolean;
};
@@ -40,17 +42,21 @@ export const StateSelect: React.FC<Props> = ({
className = "",
buttonClassName = "",
optionsClassName = "",
placement,
hideDropdownArrow = false,
disabled = false,
}) => {
const [query, setQuery] = useState("");
const [isOpen, setIsOpen] = useState(false);
const dropdownBtn = useRef<any>(null);
const dropdownOptions = useRef<any>(null);
const [referenceElement, setReferenceElement] = useState<HTMLButtonElement | null>(null);
const [popperElement, setPopperElement] = useState<HTMLDivElement | null>(null);
const [fetchStates, setFetchStates] = useState<boolean>(false);
const { styles, attributes } = usePopper(referenceElement, popperElement, {
placement: placement ?? "bottom-start",
});
const router = useRouter();
const { workspaceSlug } = router.query;
@@ -90,8 +96,6 @@ export const StateSelect: React.FC<Props> = ({
</Tooltip>
);
useDynamicDropdownPosition(isOpen, () => setIsOpen(false), dropdownBtn, dropdownOptions);
return (
<Combobox
as="div"
@@ -103,31 +107,32 @@ export const StateSelect: React.FC<Props> = ({
disabled={disabled}
>
{({ open }: { open: boolean }) => {
if (open) {
if (!isOpen) setIsOpen(true);
setFetchStates(true);
} else if (isOpen) setIsOpen(false);
if (open) setFetchStates(true);
return (
<>
<Combobox.Button
ref={dropdownBtn}
type="button"
className={`flex items-center justify-between gap-1 w-full text-xs px-2.5 py-1 rounded-md shadow-sm border border-custom-border-300 duration-300 focus:outline-none ${
disabled
? "cursor-not-allowed text-custom-text-200"
: "cursor-pointer hover:bg-custom-background-80"
} ${buttonClassName}`}
>
{label}
{!hideDropdownArrow && !disabled && (
<ChevronDownIcon className="h-3 w-3" aria-hidden="true" />
)}
<Combobox.Button as={React.Fragment}>
<button
ref={setReferenceElement}
type="button"
className={`flex items-center justify-between gap-1 w-full text-xs px-2.5 py-1 rounded-md shadow-sm border border-custom-border-300 duration-300 focus:outline-none ${
disabled
? "cursor-not-allowed text-custom-text-200"
: "cursor-pointer hover:bg-custom-background-80"
} ${buttonClassName}`}
>
{label}
{!hideDropdownArrow && !disabled && (
<ChevronDownIcon className="h-3 w-3" aria-hidden="true" />
)}
</button>
</Combobox.Button>
<div className={`${open ? "fixed z-20 top-0 left-0 h-full w-full cursor-auto" : ""}`}>
<Combobox.Options
ref={dropdownOptions}
className={`absolute z-10 border border-custom-border-300 px-2 py-2.5 rounded bg-custom-background-100 text-xs shadow-lg focus:outline-none w-48 whitespace-nowrap mt-1 ${optionsClassName}`}
<Combobox.Options>
<div
className={`z-10 border border-custom-border-300 px-2 py-2.5 rounded bg-custom-background-100 text-xs shadow-custom-shadow-rg focus:outline-none w-48 whitespace-nowrap my-1 ${optionsClassName}`}
ref={setPopperElement}
style={styles.popper}
{...attributes.popper}
>
<div className="flex w-full items-center justify-start rounded border border-custom-border-200 bg-custom-background-90 px-2">
<MagnifyingGlassIcon className="h-3.5 w-3.5 text-custom-text-300" />
@@ -169,8 +174,8 @@ export const StateSelect: React.FC<Props> = ({
<p className="text-center text-custom-text-200">Loading...</p>
)}
</div>
</Combobox.Options>
</div>
</div>
</Combobox.Options>
</>
);
}}
+98 -89
View File
@@ -1,9 +1,11 @@
import React from "react";
import React, { useState } from "react";
import Link from "next/link";
// react-poppper
import { usePopper } from "react-popper";
// headless ui
import { Menu, Transition } from "@headlessui/react";
import { Menu } from "@headlessui/react";
// ui
import { DropdownProps } from "components/ui";
// icons
@@ -20,6 +22,7 @@ export type CustomMenuProps = DropdownProps & {
const CustomMenu = ({
buttonClassName = "",
customButtonClassName = "",
placement,
children,
className = "",
customButton,
@@ -30,100 +33,106 @@ const CustomMenu = ({
noBorder = false,
noChevron = false,
optionsClassName = "",
position = "right",
selfPositioned = false,
verticalEllipsis = false,
verticalPosition = "bottom",
width = "auto",
menuButtonOnClick,
}: CustomMenuProps) => (
<Menu as="div" className={`${selfPositioned ? "" : "relative"} w-min text-left ${className}`}>
{({ open }) => (
<>
{customButton ? (
<Menu.Button
as="button"
type="button"
onClick={menuButtonOnClick}
className={customButtonClassName}
disabled={disabled}
>
{customButton}
</Menu.Button>
) : (
<>
{ellipsis || verticalEllipsis ? (
<Menu.Button
}: CustomMenuProps) => {
const [referenceElement, setReferenceElement] = useState<HTMLButtonElement | null>(null);
const [popperElement, setPopperElement] = useState<HTMLDivElement | null>(null);
const { styles, attributes } = usePopper(referenceElement, popperElement, {
placement: placement ?? "bottom-start",
});
return (
<Menu as="div" className={`relative w-min text-left ${className}`}>
{({ open }) => (
<>
{customButton ? (
<Menu.Button as={React.Fragment}>
<button
ref={setReferenceElement}
type="button"
onClick={menuButtonOnClick}
disabled={disabled}
className={`relative grid place-items-center rounded p-1 text-custom-text-200 hover:text-custom-text-100 outline-none ${
disabled ? "cursor-not-allowed" : "cursor-pointer hover:bg-custom-background-80"
} ${buttonClassName}`}
className={customButtonClassName}
>
<MoreHorizOutlined
fontSize="small"
className={verticalEllipsis ? "rotate-90" : ""}
/>
</Menu.Button>
) : (
<Menu.Button
type="button"
className={`flex items-center justify-between gap-1 rounded-md px-2.5 py-1 text-xs whitespace-nowrap duration-300 ${
open ? "bg-custom-background-90 text-custom-text-100" : "text-custom-text-200"
} ${
noBorder ? "" : "border border-custom-border-300 shadow-sm focus:outline-none"
} ${
disabled
? "cursor-not-allowed text-custom-text-200"
: "cursor-pointer hover:bg-custom-background-80"
} ${buttonClassName}`}
>
{label}
{!noChevron && (
<ExpandMoreOutlined
sx={{
fontSize: 14,
}}
aria-hidden="true"
/>
)}
</Menu.Button>
)}
</>
)}
<Transition
as={React.Fragment}
enter="transition ease-out duration-100"
enterFrom="transform opacity-0 scale-95"
enterTo="transform opacity-100 scale-100"
leave="transition ease-in duration-75"
leaveFrom="transform opacity-100 scale-100"
leaveTo="transform opacity-0 scale-95"
>
<Menu.Items
className={`absolute z-10 overflow-y-scroll whitespace-nowrap rounded-md border border-custom-border-300 p-1 text-xs shadow-lg focus:outline-none bg-custom-background-90 ${
position === "left" ? "left-0 origin-top-left" : "right-0 origin-top-right"
} ${verticalPosition === "top" ? "bottom-full mb-1" : "mt-1"} ${
maxHeight === "lg"
? "max-h-60"
: maxHeight === "md"
? "max-h-48"
: maxHeight === "rg"
? "max-h-36"
: maxHeight === "sm"
? "max-h-28"
: ""
} ${width === "auto" ? "min-w-[8rem] whitespace-nowrap" : width} ${optionsClassName}`}
>
<div className="py-1">{children}</div>
{customButton}
</button>
</Menu.Button>
) : (
<>
{ellipsis || verticalEllipsis ? (
<Menu.Button as={React.Fragment}>
<button
ref={setReferenceElement}
type="button"
onClick={menuButtonOnClick}
disabled={disabled}
className={`relative grid place-items-center rounded p-1 text-custom-text-200 hover:text-custom-text-100 outline-none ${
disabled
? "cursor-not-allowed"
: "cursor-pointer hover:bg-custom-background-80"
} ${buttonClassName}`}
>
<MoreHorizOutlined
fontSize="small"
className={verticalEllipsis ? "rotate-90" : ""}
/>
</button>
</Menu.Button>
) : (
<Menu.Button as={React.Fragment}>
<button
ref={setReferenceElement}
type="button"
className={`flex items-center justify-between gap-1 rounded-md px-2.5 py-1 text-xs whitespace-nowrap duration-300 ${
open ? "bg-custom-background-90 text-custom-text-100" : "text-custom-text-200"
} ${
noBorder ? "" : "border border-custom-border-300 shadow-sm focus:outline-none"
} ${
disabled
? "cursor-not-allowed text-custom-text-200"
: "cursor-pointer hover:bg-custom-background-80"
} ${buttonClassName}`}
>
{label}
{!noChevron && (
<ExpandMoreOutlined
sx={{
fontSize: 14,
}}
aria-hidden="true"
/>
)}
</button>
</Menu.Button>
)}
</>
)}
<Menu.Items>
<div
className={`z-10 overflow-y-scroll whitespace-nowrap rounded-md border border-custom-border-300 p-1 text-xs shadow-custom-shadow-rg focus:outline-none bg-custom-background-90 my-1 ${
maxHeight === "lg"
? "max-h-60"
: maxHeight === "md"
? "max-h-48"
: maxHeight === "rg"
? "max-h-36"
: maxHeight === "sm"
? "max-h-28"
: ""
} ${width === "auto" ? "min-w-[8rem] whitespace-nowrap" : width} ${optionsClassName}`}
ref={setPopperElement}
style={styles.popper}
{...attributes.popper}
>
<div className="py-1">{children}</div>
</div>
</Menu.Items>
</Transition>
</>
)}
</Menu>
);
</>
)}
</Menu>
);
};
type MenuItemProps = {
children: React.ReactNode;
@@ -1,7 +1,9 @@
import React, { useState } from "react";
// react-poppper
import { usePopper } from "react-popper";
// headless ui
import { Combobox, Transition } from "@headlessui/react";
import { Combobox } from "@headlessui/react";
// icons
import { ChevronDownIcon } from "@heroicons/react/20/solid";
import { CheckIcon, MagnifyingGlassIcon } from "@heroicons/react/24/outline";
@@ -27,9 +29,11 @@ export type CustomSearchSelectProps = DropdownProps & {
);
export const CustomSearchSelect = ({
customButtonClassName = "",
buttonClassName = "",
className = "",
customButton,
placement,
disabled = false,
footerOption,
input = false,
@@ -41,14 +45,18 @@ export const CustomSearchSelect = ({
options,
onOpen,
optionsClassName = "",
position = "left",
selfPositioned = false,
value,
verticalPosition = "bottom",
width = "auto",
}: CustomSearchSelectProps) => {
const [query, setQuery] = useState("");
const [referenceElement, setReferenceElement] = useState<HTMLButtonElement | null>(null);
const [popperElement, setPopperElement] = useState<HTMLDivElement | null>(null);
const { styles, attributes } = usePopper(referenceElement, popperElement, {
placement: placement ?? "bottom-start",
});
const filteredOptions =
query === ""
? options
@@ -63,51 +71,54 @@ export const CustomSearchSelect = ({
if (multiple) props.multiple = true;
return (
<Combobox
as="div"
className={`${selfPositioned ? "" : "relative"} flex-shrink-0 text-left ${className}`}
{...props}
>
<Combobox as="div" className={`relative flex-shrink-0 text-left ${className}`} {...props}>
{({ open }: { open: boolean }) => {
if (open && onOpen) onOpen();
return (
<>
{customButton ? (
<Combobox.Button as="div">{customButton}</Combobox.Button>
<Combobox.Button as={React.Fragment}>
<button
ref={setReferenceElement}
type="button"
className={`flex items-center justify-between gap-1 w-full text-xs ${
disabled
? "cursor-not-allowed text-custom-text-200"
: "cursor-pointer hover:bg-custom-background-80"
} ${customButtonClassName}`}
>
{customButton}
</button>
</Combobox.Button>
) : (
<Combobox.Button
type="button"
className={`flex items-center justify-between gap-1 w-full rounded-md shadow-sm border border-custom-border-300 duration-300 focus:outline-none ${
input ? "px-3 py-2 text-sm" : "px-2.5 py-1 text-xs"
} ${
disabled
? "cursor-not-allowed text-custom-text-200"
: "cursor-pointer hover:bg-custom-background-80"
} ${buttonClassName}`}
>
{label}
{!noChevron && !disabled && (
<ChevronDownIcon className="h-3 w-3" aria-hidden="true" />
)}
<Combobox.Button as={React.Fragment}>
<button
ref={setReferenceElement}
type="button"
className={`flex items-center justify-between gap-1 w-full rounded-md border border-custom-border-300 shadow-sm duration-300 focus:outline-none ${
input ? "px-3 py-2 text-sm" : "px-2.5 py-1 text-xs"
} ${
disabled
? "cursor-not-allowed text-custom-text-200"
: "cursor-pointer hover:bg-custom-background-80"
} ${buttonClassName}`}
>
{label}
{!noChevron && !disabled && (
<ChevronDownIcon className="h-3 w-3" aria-hidden="true" />
)}
</button>
</Combobox.Button>
)}
<Transition
show={open}
as={React.Fragment}
enter="transition ease-out duration-200"
enterFrom="opacity-0 translate-y-1"
enterTo="opacity-100 translate-y-0"
leave="transition ease-in duration-150"
leaveFrom="opacity-100 translate-y-0"
leaveTo="opacity-0 translate-y-1"
>
<Combobox.Options
className={`absolute z-10 min-w-[10rem] border border-custom-border-300 p-2 rounded-md bg-custom-background-90 text-xs shadow-lg focus:outline-none ${
position === "left" ? "left-0 origin-top-left" : "right-0 origin-top-right"
} ${verticalPosition === "top" ? "bottom-full mb-1" : "mt-1"} ${
<Combobox.Options as={React.Fragment}>
<div
className={`z-10 min-w-[10rem] border border-custom-border-300 p-2 rounded-md bg-custom-background-90 text-xs shadow-custom-shadow-rg focus:outline-none my-1 ${
width === "auto" ? "min-w-[8rem] whitespace-nowrap" : width
} ${optionsClassName}`}
ref={setPopperElement}
style={styles.popper}
{...attributes.popper}
>
<div className="flex w-full items-center justify-start rounded-sm border-[0.6px] border-custom-border-200 bg-custom-background-90 px-2">
<MagnifyingGlassIcon className="h-3 w-3 text-custom-text-200" />
@@ -176,8 +187,8 @@ export const CustomSearchSelect = ({
)}
</div>
{footerOption}
</Combobox.Options>
</Transition>
</div>
</Combobox.Options>
</>
);
}}
+78 -59
View File
@@ -1,7 +1,9 @@
import React from "react";
import React, { useState } from "react";
// react-popper
import { usePopper } from "react-popper";
// headless ui
import { Listbox, Transition } from "@headlessui/react";
import { Listbox } from "@headlessui/react";
// icons
import { ChevronDownIcon } from "@heroicons/react/20/solid";
import { CheckIcon } from "@heroicons/react/24/outline";
@@ -15,7 +17,9 @@ export type CustomSelectProps = DropdownProps & {
};
const CustomSelect = ({
customButtonClassName = "",
buttonClassName = "",
placement,
children,
className = "",
customButton,
@@ -26,68 +30,83 @@ const CustomSelect = ({
noChevron = false,
onChange,
optionsClassName = "",
position = "left",
selfPositioned = false,
value,
verticalPosition = "bottom",
width = "auto",
}: CustomSelectProps) => (
<Listbox
as="div"
value={value}
onChange={onChange}
className={`${selfPositioned ? "" : "relative"} flex-shrink-0 text-left ${className}`}
disabled={disabled}
>
<>
{customButton ? (
<Listbox.Button as={React.Fragment}>{customButton}</Listbox.Button>
) : (
<Listbox.Button
type="button"
className={`flex items-center justify-between gap-1 w-full rounded-md border border-custom-border-300 shadow-sm duration-300 focus:outline-none ${
input ? "px-3 py-2 text-sm" : "px-2.5 py-1 text-xs"
} ${
disabled
? "cursor-not-allowed text-custom-text-200"
: "cursor-pointer hover:bg-custom-background-80"
} ${buttonClassName}`}
>
{label}
{!noChevron && !disabled && <ChevronDownIcon className="h-3 w-3" aria-hidden="true" />}
</Listbox.Button>
)}
</>
}: CustomSelectProps) => {
const [referenceElement, setReferenceElement] = useState<HTMLButtonElement | null>(null);
const [popperElement, setPopperElement] = useState<HTMLDivElement | null>(null);
<Transition
as={React.Fragment}
enter="transition ease-out duration-100"
enterFrom="transform opacity-0 scale-95"
enterTo="transform opacity-100 scale-100"
leave="transition ease-in duration-75"
leaveFrom="transform opacity-100 scale-100"
leaveTo="transform opacity-0 scale-95"
const { styles, attributes } = usePopper(referenceElement, popperElement, {
placement: placement ?? "bottom-start",
});
return (
<Listbox
as="div"
value={value}
onChange={onChange}
className={`relative flex-shrink-0 text-left ${className}`}
disabled={disabled}
>
<Listbox.Options
className={`absolute z-10 border border-custom-border-300 mt-1 origin-top-right overflow-y-auto rounded-md bg-custom-background-90 text-xs shadow-lg focus:outline-none ${
position === "left" ? "left-0 origin-top-left" : "right-0 origin-top-right"
} ${verticalPosition === "top" ? "bottom-full mb-1" : "mt-1"} ${
maxHeight === "lg"
? "max-h-60"
: maxHeight === "md"
? "max-h-48"
: maxHeight === "rg"
? "max-h-36"
: maxHeight === "sm"
? "max-h-28"
: ""
} ${width === "auto" ? "min-w-[8rem] whitespace-nowrap" : width} ${optionsClassName}`}
>
<div className="space-y-1 p-2">{children}</div>
<>
{customButton ? (
<Listbox.Button as={React.Fragment}>
<button
ref={setReferenceElement}
type="button"
className={`flex items-center justify-between gap-1 w-full text-xs ${
disabled
? "cursor-not-allowed text-custom-text-200"
: "cursor-pointer hover:bg-custom-background-80"
} ${customButtonClassName}`}
>
{customButton}
</button>
</Listbox.Button>
) : (
<Listbox.Button as={React.Fragment}>
<button
ref={setReferenceElement}
type="button"
className={`flex items-center justify-between gap-1 w-full rounded-md border border-custom-border-300 shadow-sm duration-300 focus:outline-none ${
input ? "px-3 py-2 text-sm" : "px-2.5 py-1 text-xs"
} ${
disabled
? "cursor-not-allowed text-custom-text-200"
: "cursor-pointer hover:bg-custom-background-80"
} ${buttonClassName}`}
>
{label}
{!noChevron && !disabled && (
<ChevronDownIcon className="h-3 w-3" aria-hidden="true" />
)}
</button>
</Listbox.Button>
)}
</>
<Listbox.Options>
<div
className={`z-10 border border-custom-border-300 overflow-y-auto rounded-md bg-custom-background-90 text-xs shadow-custom-shadow-rg focus:outline-none my-1 ${
maxHeight === "lg"
? "max-h-60"
: maxHeight === "md"
? "max-h-48"
: maxHeight === "rg"
? "max-h-36"
: maxHeight === "sm"
? "max-h-28"
: ""
} ${width === "auto" ? "min-w-[8rem] whitespace-nowrap" : width} ${optionsClassName}`}
ref={setPopperElement}
style={styles.popper}
{...attributes.popper}
>
<div className="space-y-1 p-2">{children}</div>
</div>
</Listbox.Options>
</Transition>
</Listbox>
);
</Listbox>
);
};
type OptionProps = {
children: React.ReactNode;
+4 -3
View File
@@ -1,4 +1,7 @@
import { Placement } from "@popperjs/core";
export type DropdownProps = {
customButtonClassName?: string;
buttonClassName?: string;
customButtonClassName?: string;
className?: string;
@@ -10,8 +13,6 @@ export type DropdownProps = {
noChevron?: boolean;
onOpen?: () => void;
optionsClassName?: string;
position?: "right" | "left";
selfPositioned?: boolean;
verticalPosition?: "top" | "bottom";
width?: "auto" | string;
placement?: Placement;
};
+2
View File
@@ -25,6 +25,7 @@
"@nivo/line": "0.80.0",
"@nivo/pie": "0.80.0",
"@nivo/scatterplot": "0.80.0",
"@popperjs/core": "^2.11.8",
"@sentry/nextjs": "^7.36.0",
"@tiptap/extension-code-block-lowlight": "^2.0.4",
"@tiptap/extension-color": "^2.0.4",
@@ -72,6 +73,7 @@
"react-hook-form": "^7.38.0",
"react-markdown": "^8.0.7",
"react-moveable": "^0.54.1",
"react-popper": "^2.3.0",
"sharp": "^0.32.1",
"sonner": "^0.6.2",
"swr": "^2.1.3",
@@ -298,8 +298,6 @@ const Profile: NextPage = () => {
buttonClassName={errors.role ? "border-red-500 bg-red-500/10" : ""}
width="w-full"
input
verticalPosition="top"
position="right"
>
{USER_ROLES.map((item) => (
<CustomSelect.Option key={item.value} value={item.value}>
@@ -362,7 +360,6 @@ const Profile: NextPage = () => {
}
options={timeZoneOptions}
onChange={onChange}
verticalPosition="top"
optionsClassName="w-full"
input
/>
@@ -427,13 +427,10 @@ const SinglePage: NextPage = () => {
)}
<CustomSearchSelect
customButton={
<button
type="button"
className="flex items-center gap-1 rounded-sm bg-custom-background-80 p-1.5 text-xs"
>
<div className="flex items-center gap-1 rounded-sm bg-custom-background-80 p-1.5 text-xs">
<PlusIcon className="h-3.5 w-3.5" />
{pageDetails.labels.length <= 0 && <span>Add Label</span>}
</button>
</div>
}
value={pageDetails.labels}
footerOption={
@@ -412,7 +412,7 @@ const MembersSettings: NextPage = () => {
)}
<CustomSelect
customButton={
<button className="flex item-center gap-1">
<div className="flex item-center gap-1">
<span
className={`flex items-center text-sm font-medium ${
member.memberId !== user?.id ? "" : "text-custom-sidebar-text-400"
@@ -423,7 +423,7 @@ const MembersSettings: NextPage = () => {
{member.memberId !== user?.id && (
<Icon iconName="expand_more" className="text-lg font-medium" />
)}
</button>
</div>
}
value={member.role}
onChange={(value: 5 | 10 | 15 | 20 | undefined) => {
@@ -455,7 +455,6 @@ const MembersSettings: NextPage = () => {
});
});
}}
position="right"
disabled={
member.memberId === user?.id ||
!member.member ||
@@ -269,7 +269,7 @@ const MembersSettings: NextPage = () => {
)}
<CustomSelect
customButton={
<button className="flex item-center gap-1">
<div className="flex item-center gap-1">
<span
className={`flex items-center text-sm font-medium ${
member.memberId !== user?.id ? "" : "text-custom-sidebar-text-400"
@@ -280,7 +280,7 @@ const MembersSettings: NextPage = () => {
{member.memberId !== user?.id && (
<Icon iconName="expand_more" className="text-lg font-medium" />
)}
</button>
</div>
}
value={member.role}
onChange={(value: 5 | 10 | 15 | 20 | undefined) => {
@@ -307,7 +307,6 @@ const MembersSettings: NextPage = () => {
});
});
}}
position="right"
disabled={
member.memberId === currentUser?.member.id ||
!member.status ||
+1 -8
View File
@@ -6628,20 +6628,13 @@ prosemirror-menu@^1.2.1:
prosemirror-history "^1.0.0"
prosemirror-state "^1.0.0"
prosemirror-model@^1.0.0, prosemirror-model@^1.16.0, prosemirror-model@^1.18.1, prosemirror-model@^1.8.1:
prosemirror-model@1.18.1, prosemirror-model@^1.0.0, prosemirror-model@^1.16.0, prosemirror-model@^1.18.1, prosemirror-model@^1.19.0, prosemirror-model@^1.8.1:
version "1.18.1"
resolved "https://registry.yarnpkg.com/prosemirror-model/-/prosemirror-model-1.18.1.tgz#1d5d6b6de7b983ee67a479dc607165fdef3935bd"
integrity sha512-IxSVBKAEMjD7s3n8cgtwMlxAXZrC7Mlag7zYsAKDndAqnDScvSmp/UdnRTV/B33lTCVU3CCm7dyAn/rVVD0mcw==
dependencies:
orderedmap "^2.0.0"
prosemirror-model@^1.19.0:
version "1.19.3"
resolved "https://registry.yarnpkg.com/prosemirror-model/-/prosemirror-model-1.19.3.tgz#f0d55285487fefd962d0ac695f716f4ec6705006"
integrity sha512-tgSnwN7BS7/UM0sSARcW+IQryx2vODKX4MI7xpqY2X+iaepJdKBPc7I4aACIsDV/LTaTjt12Z56MhDr9LsyuZQ==
dependencies:
orderedmap "^2.0.0"
prosemirror-schema-basic@^1.2.0:
version "1.2.2"
resolved "https://registry.yarnpkg.com/prosemirror-schema-basic/-/prosemirror-schema-basic-1.2.2.tgz#6695f5175e4628aab179bf62e5568628b9cfe6c7"