task detail Labels
This commit is contained in:
+4
-1
@@ -997,7 +997,10 @@
|
|||||||
"attachment": "ضمیمه",
|
"attachment": "ضمیمه",
|
||||||
"date_settings": "تنظیمات تاریخ",
|
"date_settings": "تنظیمات تاریخ",
|
||||||
"description": "توضیحات",
|
"description": "توضیحات",
|
||||||
"description_placeholder": "توضیحات خود را اضافه کنید"
|
"description_placeholder": "توضیحات خود را اضافه کنید",
|
||||||
|
"new_label": "برچسب جدید",
|
||||||
|
"label_title": "عنوان",
|
||||||
|
"choose_custom_color": "انتخاب رنگ دلخواه"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import { Calendar, Paperclip2, Profile2User, Tag, TickSquare, type Icon } from "iconsax-react";
|
import { Calendar, Paperclip2, Profile2User, Tag, TickSquare, type Icon } from "iconsax-react";
|
||||||
import { type FC } from "react";
|
import { type FC, type ReactNode } from "react";
|
||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
import type { TaskDetailTab } from "./types";
|
import type { TaskDetailTab } from "./types";
|
||||||
|
|
||||||
@@ -20,9 +20,10 @@ const TABS: TabItem[] = [
|
|||||||
type Props = {
|
type Props = {
|
||||||
activeTab: TaskDetailTab | null;
|
activeTab: TaskDetailTab | null;
|
||||||
onTabChange: (tab: TaskDetailTab) => void;
|
onTabChange: (tab: TaskDetailTab) => void;
|
||||||
|
labelsPopover?: ReactNode;
|
||||||
};
|
};
|
||||||
|
|
||||||
const TaskDetailActionBar: FC<Props> = ({ activeTab, onTabChange }) => {
|
const TaskDetailActionBar: FC<Props> = ({ activeTab, onTabChange, labelsPopover }) => {
|
||||||
const { t } = useTranslation("global");
|
const { t } = useTranslation("global");
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@@ -30,17 +31,31 @@ const TaskDetailActionBar: FC<Props> = ({ activeTab, onTabChange }) => {
|
|||||||
{TABS.map(({ id, icon: Icon, labelKey }) => {
|
{TABS.map(({ id, icon: Icon, labelKey }) => {
|
||||||
const isActive = activeTab === id;
|
const isActive = activeTab === id;
|
||||||
|
|
||||||
return (
|
const button = (
|
||||||
<button
|
<button
|
||||||
key={id}
|
|
||||||
type="button"
|
type="button"
|
||||||
onClick={() => onTabChange(id)}
|
onClick={() => onTabChange(id)}
|
||||||
className={`flex items-center gap-1.5 border border-white rounded-lg px-3 py-2 text-[11px] cursor-pointer transition-colors ${isActive ? "bg-white text-black" : "bg-white/50 text-[#292D32]"}`}
|
className={`flex items-center gap-1.5 border rounded-lg px-3 py-2 text-[11px] cursor-pointer transition-colors ${isActive ? "bg-[#4A4A4A] text-white border-transparent" : "bg-white/50 text-[#292D32] border-white"}`}
|
||||||
>
|
>
|
||||||
<Icon size={16} color="#292D32" />
|
<Icon size={16} color={isActive ? "#FFFFFF" : "#292D32"} />
|
||||||
<span>{t(labelKey)}</span>
|
<span>{t(labelKey)}</span>
|
||||||
</button>
|
</button>
|
||||||
);
|
);
|
||||||
|
|
||||||
|
if (id === "labels") {
|
||||||
|
return (
|
||||||
|
<div key={id} className="relative">
|
||||||
|
{button}
|
||||||
|
{labelsPopover}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div key={id}>
|
||||||
|
{button}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
})}
|
})}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -0,0 +1,57 @@
|
|||||||
|
import { Add } from "iconsax-react";
|
||||||
|
import { type FC } from "react";
|
||||||
|
import { useTranslation } from "react-i18next";
|
||||||
|
import AvatarImage from "../../../../assets/images/avatar_image.png";
|
||||||
|
import type { TaskLabel } from "./labels/types";
|
||||||
|
|
||||||
|
type Props = {
|
||||||
|
selectedLabels: TaskLabel[];
|
||||||
|
};
|
||||||
|
|
||||||
|
const TaskDetailMetadataPreview: FC<Props> = ({ selectedLabels }) => {
|
||||||
|
const { t } = useTranslation("global");
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="mt-4 grid grid-cols-2 gap-4">
|
||||||
|
<div>
|
||||||
|
<div className="text-xs font-medium mb-2">{t("taskmanager.task_detail.labels")}</div>
|
||||||
|
<div className="flex items-center gap-2 flex-wrap">
|
||||||
|
{selectedLabels.map((label) => (
|
||||||
|
<span
|
||||||
|
key={label.id}
|
||||||
|
className="inline-flex items-center h-7 px-3 rounded-lg text-xs font-medium"
|
||||||
|
style={{ backgroundColor: label.color }}
|
||||||
|
>
|
||||||
|
{label.title}
|
||||||
|
</span>
|
||||||
|
))}
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
className="size-7 rounded-lg border border-[#D0D0D0] bg-white/60 flex items-center justify-center cursor-pointer"
|
||||||
|
aria-label={t("taskmanager.task_detail.new_label")}
|
||||||
|
>
|
||||||
|
<Add size={14} color="#292D32" />
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<div className="text-xs font-medium mb-2">{t("taskmanager.users")}</div>
|
||||||
|
<div className="flex items-center gap-2">
|
||||||
|
<div className="size-8 rounded-full bg-[#D0D0D0] overflow-hidden">
|
||||||
|
<img src={AvatarImage} alt="" className="size-full object-cover" />
|
||||||
|
</div>
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
className="size-7 rounded-full border border-[#D0D0D0] bg-white/60 flex items-center justify-center cursor-pointer"
|
||||||
|
aria-label={t("taskmanager.users")}
|
||||||
|
>
|
||||||
|
<Add size={14} color="#292D32" />
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default TaskDetailMetadataPreview;
|
||||||
@@ -1,9 +1,9 @@
|
|||||||
import { type FC, useEffect, useState } from "react";
|
import { type FC, useEffect, useState } from "react";
|
||||||
import DefaulModal from "../../../../components/DefaulModal";
|
import DefaulModal from "../../../../components/DefaulModal";
|
||||||
import type { Task } from "../../types";
|
import type { Task } from "../../types";
|
||||||
import TaskDetailActionBar from "./TaskDetailActionBar";
|
|
||||||
import TaskDetailDescription from "./TaskDetailDescription";
|
import TaskDetailDescription from "./TaskDetailDescription";
|
||||||
import TaskDetailHeader from "./TaskDetailHeader";
|
import TaskDetailHeader from "./TaskDetailHeader";
|
||||||
|
import TaskDetailToolbar from "./TaskDetailToolbar";
|
||||||
import type { TaskDetailTab } from "./types";
|
import type { TaskDetailTab } from "./types";
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
@@ -24,6 +24,10 @@ const TaskDetailModal: FC<Props> = ({ open, task, statusLabel, onClose }) => {
|
|||||||
}
|
}
|
||||||
}, [open]);
|
}, [open]);
|
||||||
|
|
||||||
|
const handleTabChange = (tab: TaskDetailTab) => {
|
||||||
|
setActiveTab((prev) => (prev === tab ? null : tab));
|
||||||
|
};
|
||||||
|
|
||||||
if (!task) return null;
|
if (!task) return null;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@@ -33,7 +37,7 @@ const TaskDetailModal: FC<Props> = ({ open, task, statusLabel, onClose }) => {
|
|||||||
<h2 className="mt-6 text-sm font-bold">{task.title}</h2>
|
<h2 className="mt-6 text-sm font-bold">{task.title}</h2>
|
||||||
|
|
||||||
<div className="mt-4">
|
<div className="mt-4">
|
||||||
<TaskDetailActionBar activeTab={activeTab} onTabChange={setActiveTab} />
|
<TaskDetailToolbar activeTab={activeTab} onTabChange={handleTabChange} />
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<TaskDetailDescription value={description} onChange={setDescription} />
|
<TaskDetailDescription value={description} onChange={setDescription} />
|
||||||
|
|||||||
@@ -0,0 +1,76 @@
|
|||||||
|
import { type FC, useState } from "react";
|
||||||
|
import TaskDetailActionBar from "./TaskDetailActionBar";
|
||||||
|
import TaskDetailMetadataPreview from "./TaskDetailMetadataPreview";
|
||||||
|
import { DEFAULT_LABELS } from "./labels/constants";
|
||||||
|
import TaskDetailLabelsPopover from "./labels/TaskDetailLabelsPopover";
|
||||||
|
import type { LabelsView, TaskLabel } from "./labels/types";
|
||||||
|
import type { TaskDetailTab } from "./types";
|
||||||
|
|
||||||
|
type Props = {
|
||||||
|
activeTab: TaskDetailTab | null;
|
||||||
|
onTabChange: (tab: TaskDetailTab) => void;
|
||||||
|
};
|
||||||
|
|
||||||
|
const TaskDetailToolbar: FC<Props> = ({ activeTab, onTabChange }) => {
|
||||||
|
const [labelsView, setLabelsView] = useState<LabelsView>("list");
|
||||||
|
const [labels, setLabels] = useState<TaskLabel[]>(DEFAULT_LABELS);
|
||||||
|
const [selectedIds, setSelectedIds] = useState<Set<string>>(new Set(["1"]));
|
||||||
|
|
||||||
|
const selectedLabels = labels.filter((label) => selectedIds.has(label.id));
|
||||||
|
const isLabelsOpen = activeTab === "labels";
|
||||||
|
|
||||||
|
const handleTabChange = (tab: TaskDetailTab) => {
|
||||||
|
if (tab === "labels" && activeTab !== "labels") {
|
||||||
|
setLabelsView("list");
|
||||||
|
}
|
||||||
|
onTabChange(tab);
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleToggle = (id: string) => {
|
||||||
|
setSelectedIds((prev) => {
|
||||||
|
const next = new Set(prev);
|
||||||
|
if (next.has(id)) {
|
||||||
|
next.delete(id);
|
||||||
|
} else {
|
||||||
|
next.add(id);
|
||||||
|
}
|
||||||
|
return next;
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleCreate = (title: string, color: string) => {
|
||||||
|
const newLabel: TaskLabel = {
|
||||||
|
id: crypto.randomUUID(),
|
||||||
|
title,
|
||||||
|
color,
|
||||||
|
};
|
||||||
|
setLabels((prev) => [...prev, newLabel]);
|
||||||
|
setSelectedIds((prev) => new Set([...prev, newLabel.id]));
|
||||||
|
setLabelsView("list");
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="relative">
|
||||||
|
<TaskDetailActionBar
|
||||||
|
activeTab={activeTab}
|
||||||
|
onTabChange={handleTabChange}
|
||||||
|
labelsPopover={
|
||||||
|
isLabelsOpen ? (
|
||||||
|
<TaskDetailLabelsPopover
|
||||||
|
view={labelsView}
|
||||||
|
labels={labels}
|
||||||
|
selectedIds={selectedIds}
|
||||||
|
onViewChange={setLabelsView}
|
||||||
|
onToggle={handleToggle}
|
||||||
|
onCreate={handleCreate}
|
||||||
|
/>
|
||||||
|
) : null
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<TaskDetailMetadataPreview selectedLabels={selectedLabels} />
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default TaskDetailToolbar;
|
||||||
@@ -0,0 +1,29 @@
|
|||||||
|
import { type FC } from "react";
|
||||||
|
import { clx } from "../../../../../helpers/utils";
|
||||||
|
|
||||||
|
type Props = {
|
||||||
|
checked: boolean;
|
||||||
|
onToggle: () => void;
|
||||||
|
ariaLabel: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
const TaskDetailLabelCheckbox: FC<Props> = ({ checked, onToggle, ariaLabel }) => {
|
||||||
|
return (
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
onClick={onToggle}
|
||||||
|
className={clx("shrink-0 size-4 rounded-md border flex items-center justify-center cursor-pointer transition-colors", checked ? "bg-black border-black" : "bg-white/60 border-[#D0D0D0]")}
|
||||||
|
aria-label={ariaLabel}
|
||||||
|
aria-checked={checked}
|
||||||
|
role="checkbox"
|
||||||
|
>
|
||||||
|
{checked && (
|
||||||
|
<svg width="10" height="8" viewBox="0 0 10 8" fill="none" aria-hidden>
|
||||||
|
<path d="M1 4L3.5 6.5L9 1" stroke="white" strokeWidth="1.5" strokeLinecap="round" strokeLinejoin="round" />
|
||||||
|
</svg>
|
||||||
|
)}
|
||||||
|
</button>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default TaskDetailLabelCheckbox;
|
||||||
@@ -0,0 +1,53 @@
|
|||||||
|
import { AddCircle, Edit } from "iconsax-react";
|
||||||
|
import { type FC } from "react";
|
||||||
|
import { useTranslation } from "react-i18next";
|
||||||
|
import Button from "../../../../../components/Button";
|
||||||
|
import TaskDetailLabelCheckbox from "./TaskDetailLabelCheckbox";
|
||||||
|
import type { TaskLabel } from "./types";
|
||||||
|
|
||||||
|
type Props = {
|
||||||
|
labels: TaskLabel[];
|
||||||
|
selectedIds: Set<string>;
|
||||||
|
onToggle: (id: string) => void;
|
||||||
|
onEdit: (label: TaskLabel) => void;
|
||||||
|
onCreateClick: () => void;
|
||||||
|
};
|
||||||
|
|
||||||
|
const TaskDetailLabelsList: FC<Props> = ({ labels, selectedIds, onToggle, onEdit, onCreateClick }) => {
|
||||||
|
const { t } = useTranslation("global");
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="flex flex-col gap-3">
|
||||||
|
<h3 className="text-xs font-bold text-center">{t("taskmanager.task_detail.labels")}</h3>
|
||||||
|
|
||||||
|
<div className="flex flex-col gap-2">
|
||||||
|
{labels.map((label) => {
|
||||||
|
const isSelected = selectedIds.has(label.id);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div key={label.id} className="flex items-center gap-2">
|
||||||
|
<TaskDetailLabelCheckbox checked={isSelected} onToggle={() => onToggle(label.id)} ariaLabel={t("taskmanager.task_detail.labels")} />
|
||||||
|
|
||||||
|
<div className="flex-1 h-5 rounded flex items-center px-3 min-w-0" style={{ backgroundColor: label.color }}>
|
||||||
|
{label.title && <span className="text-xs font-medium truncate">{label.title}</span>}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<Edit onClick={() => onEdit(label)} size={16} color="#0047FF" />
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<Button
|
||||||
|
type="button"
|
||||||
|
onClick={onCreateClick}
|
||||||
|
className="flex items-center justify-center gap-2 bg-white border border-[#D0D0D0] text-[#292D32] h-6 rounded-[6px] text-xs mt-1"
|
||||||
|
>
|
||||||
|
<AddCircle size={14} color="#292D32" />
|
||||||
|
<span className="text-xs">{t("taskmanager.task_detail.new_label")}</span>
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default TaskDetailLabelsList;
|
||||||
@@ -0,0 +1,43 @@
|
|||||||
|
import { type FC } from "react";
|
||||||
|
import TaskDetailLabelsList from "./TaskDetailLabelsList";
|
||||||
|
import TaskDetailNewLabelForm from "./TaskDetailNewLabelForm";
|
||||||
|
import type { LabelsView, TaskLabel } from "./types";
|
||||||
|
|
||||||
|
type Props = {
|
||||||
|
view: LabelsView;
|
||||||
|
labels: TaskLabel[];
|
||||||
|
selectedIds: Set<string>;
|
||||||
|
onViewChange: (view: LabelsView) => void;
|
||||||
|
onToggle: (id: string) => void;
|
||||||
|
onCreate: (title: string, color: string) => void;
|
||||||
|
};
|
||||||
|
|
||||||
|
const TaskDetailLabelsPopover: FC<Props> = ({
|
||||||
|
view,
|
||||||
|
labels,
|
||||||
|
selectedIds,
|
||||||
|
onViewChange,
|
||||||
|
onToggle,
|
||||||
|
onCreate,
|
||||||
|
}) => {
|
||||||
|
return (
|
||||||
|
<div className="absolute top-full right-0 mt-2 z-30 w-[300px] bg-white/90 backdrop-blur-md rounded-2xl p-4 shadow-lg border border-white/80">
|
||||||
|
{view === "list" ? (
|
||||||
|
<TaskDetailLabelsList
|
||||||
|
labels={labels}
|
||||||
|
selectedIds={selectedIds}
|
||||||
|
onToggle={onToggle}
|
||||||
|
onEdit={() => undefined}
|
||||||
|
onCreateClick={() => onViewChange("create")}
|
||||||
|
/>
|
||||||
|
) : (
|
||||||
|
<TaskDetailNewLabelForm
|
||||||
|
onBack={() => onViewChange("list")}
|
||||||
|
onSubmit={onCreate}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default TaskDetailLabelsPopover;
|
||||||
@@ -0,0 +1,95 @@
|
|||||||
|
import { AddCircle, ArrowRight2 } from "iconsax-react";
|
||||||
|
import { type FC, useRef, useState } from "react";
|
||||||
|
import { useTranslation } from "react-i18next";
|
||||||
|
import ColorsImage from "../../../../../assets/images/colors.png";
|
||||||
|
import Button from "../../../../../components/Button";
|
||||||
|
import Input from "../../../../../components/Input";
|
||||||
|
import { clx } from "../../../../../helpers/utils";
|
||||||
|
import { LABEL_COLOR_OPTIONS } from "./constants";
|
||||||
|
|
||||||
|
type Props = {
|
||||||
|
onBack: () => void;
|
||||||
|
onSubmit: (title: string, color: string) => void;
|
||||||
|
};
|
||||||
|
|
||||||
|
const TaskDetailNewLabelForm: FC<Props> = ({ onBack, onSubmit }) => {
|
||||||
|
const { t } = useTranslation("global");
|
||||||
|
const colorInputRef = useRef<HTMLInputElement>(null);
|
||||||
|
const [title, setTitle] = useState("");
|
||||||
|
const [selectedColor, setSelectedColor] = useState<string>(LABEL_COLOR_OPTIONS[1]);
|
||||||
|
|
||||||
|
const handleSubmit = () => {
|
||||||
|
const trimmed = title.trim();
|
||||||
|
if (!trimmed) return;
|
||||||
|
onSubmit(trimmed, selectedColor);
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="flex flex-col gap-4">
|
||||||
|
<div className="flex items-center justify-center relative">
|
||||||
|
<Button
|
||||||
|
type="button"
|
||||||
|
onClick={onBack}
|
||||||
|
className="absolute right-0 size-7 bg-transparent text-inherit w-auto h-auto p-0 border-0 rounded-none shadow-none"
|
||||||
|
aria-label={t("back")}
|
||||||
|
>
|
||||||
|
<ArrowRight2 size={16} color="#292D32" />
|
||||||
|
</Button>
|
||||||
|
<h3 className="text-xs font-bold">{t("taskmanager.task_detail.new_label")}</h3>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<Input
|
||||||
|
label={t("taskmanager.task_detail.label_title")}
|
||||||
|
value={title}
|
||||||
|
onChange={(e) => setTitle(e.target.value)}
|
||||||
|
className="h-8 bg-white/63 border-white"
|
||||||
|
/>
|
||||||
|
|
||||||
|
<Button
|
||||||
|
type="button"
|
||||||
|
onClick={handleSubmit}
|
||||||
|
disabled={!title.trim()}
|
||||||
|
className="flex items-center justify-center gap-2 bg-white border border-[#D0D0D0] text-[#292D32] rounded-[6px] h-7 text-xs disabled:opacity-50"
|
||||||
|
>
|
||||||
|
<AddCircle size={14} color="#292D32" />
|
||||||
|
<span className="text-xs">{t("taskmanager.task_detail.new_label")}</span>
|
||||||
|
</Button>
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<div className="text-xs mb-3">{t("taskmanager.task_detail.choose_custom_color")}</div>
|
||||||
|
|
||||||
|
<div className="flex items-center gap-2 flex-wrap">
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
onClick={() => colorInputRef.current?.click()}
|
||||||
|
className={clx(
|
||||||
|
"size-6 rounded-lg cursor-pointer overflow-hidden",
|
||||||
|
!LABEL_COLOR_OPTIONS.includes(selectedColor as (typeof LABEL_COLOR_OPTIONS)[number]) && "ring-1 ring-[#0047FF] rounded-full",
|
||||||
|
)}
|
||||||
|
aria-label={t("taskmanager.task_detail.choose_custom_color")}
|
||||||
|
>
|
||||||
|
<img src={ColorsImage} alt="colors" className="size-full object-cover" />
|
||||||
|
</button>
|
||||||
|
{LABEL_COLOR_OPTIONS.map((color) => (
|
||||||
|
<button
|
||||||
|
key={color}
|
||||||
|
type="button"
|
||||||
|
onClick={() => setSelectedColor(color)}
|
||||||
|
className={clx(
|
||||||
|
"size-6 rounded-lg cursor-pointer transition-transform",
|
||||||
|
color === "#FFFFFF" && "border border-[#D0D0D0]",
|
||||||
|
selectedColor === color && "ring-2 ring-[#0047FF] ring-offset-1",
|
||||||
|
)}
|
||||||
|
style={{ backgroundColor: color }}
|
||||||
|
aria-label={color}
|
||||||
|
/>
|
||||||
|
))}
|
||||||
|
|
||||||
|
<input ref={colorInputRef} type="color" value={selectedColor} onChange={(e) => setSelectedColor(e.target.value)} className="sr-only" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default TaskDetailNewLabelForm;
|
||||||
@@ -0,0 +1,19 @@
|
|||||||
|
import type { TaskLabel } from "./types";
|
||||||
|
|
||||||
|
export const LABEL_COLOR_OPTIONS = [
|
||||||
|
"#A8E6CF",
|
||||||
|
"#F5A3B8",
|
||||||
|
"#F5C4A3",
|
||||||
|
"#8B9A8B",
|
||||||
|
"#292D32",
|
||||||
|
"#E86A4A",
|
||||||
|
"#FFFFFF",
|
||||||
|
] as const;
|
||||||
|
|
||||||
|
export const DEFAULT_LABELS: TaskLabel[] = [
|
||||||
|
{ id: "1", title: "عنوان", color: "#F9A8BB" },
|
||||||
|
{ id: "2", title: "", color: "#AED9F4" },
|
||||||
|
{ id: "3", title: "", color: "#B2E7CC" },
|
||||||
|
{ id: "4", title: "", color: "#F9E7A8" },
|
||||||
|
{ id: "5", title: "", color: "#F9C8A8" },
|
||||||
|
];
|
||||||
@@ -0,0 +1,7 @@
|
|||||||
|
export type TaskLabel = {
|
||||||
|
id: string;
|
||||||
|
title: string;
|
||||||
|
color: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
export type LabelsView = "list" | "create";
|
||||||
Reference in New Issue
Block a user