UI - МЕЖПРОЕКТНАЯ КОММУНИКАЦИЯ: настройки проекта, модалки и drag-and-drop канон
This commit is contained in:
parent
bd7a7d96fe
commit
df68c96795
|
|
@ -0,0 +1,111 @@
|
|||
# HDESIGN CODE
|
||||
|
||||
Документ фиксирует канон интерфейса NODE.DC, чтобы не обсуждать одни и те же правила повторно.
|
||||
|
||||
## Источник цветов
|
||||
- Основной runtime-конфиг цветов: [design.config.json](/Users/dcconstructions/Downloads/mnt/data/dc_taskmanager/NODEDC_TASKMANAGER/design.config.json)
|
||||
- Рабочая web-копия: [plane-src/apps/web/design.config.json](/Users/dcconstructions/Downloads/mnt/data/dc_taskmanager/NODEDC_TASKMANAGER/plane-src/apps/web/design.config.json)
|
||||
- В рантайме используются CSS variables:
|
||||
- `--nodedc-accent-rgb`
|
||||
- `--nodedc-card-passive-rgb`
|
||||
- `--nodedc-card-active-rgb`
|
||||
|
||||
## Цветовые правила
|
||||
- `accent_rgb`: акцентный цвет интерфейса.
|
||||
- `passive_card_rgb`: пассивные карточки.
|
||||
- `active_card_rgb`: активные карточки и primary CTA в зелёной теме.
|
||||
- Primary/active элементы используют акцентный или `active_card_rgb`.
|
||||
- Secondary элементы не должны иметь ярких outline и цветных рамок без явной причины.
|
||||
|
||||
## Радиусы
|
||||
- Главные модалки и большие surface-контейнеры: `1.75rem`
|
||||
- Стандартные glass-карточки и settings-карточки: `1.35rem`
|
||||
- Поля ввода, селекты, secondary/primary кнопки, chip-кнопки: `1.25rem`
|
||||
- Малые круглые action-кнопки: `999px` или полный круг при квадратной коробке
|
||||
|
||||
## Outline и рамки
|
||||
- Внешние outline у контролов запрещены.
|
||||
- Синий browser outline должен быть снят и заменён на нормальный hover/focus surface.
|
||||
- Если нужен контур, он должен быть частью дизайна:
|
||||
- мягкий glass border
|
||||
- акцентный border для drag/drop или active-state
|
||||
- Красные технические outline, debug-рамки и случайные browser-box shadow запрещены.
|
||||
|
||||
## Glass и фон
|
||||
- Popup, dropdown, modal, sidebar overlays и settings-карточки используют matte black glass.
|
||||
- База:
|
||||
- тёмный полупрозрачный фон
|
||||
- `backdrop-filter: blur(...)`
|
||||
- мягкая стеклянная граница
|
||||
- Popup не должны выглядеть просто прозрачными. Если blur не читается, проблема в слое рендера, а не в одном `rgba`.
|
||||
|
||||
## Кнопки
|
||||
- Все кнопки без жёсткого outline.
|
||||
- Primary button:
|
||||
- фон: акцентный или `active_card_rgb`
|
||||
- текст: чёрный или очень тёмный, если фон светлый
|
||||
- hover: более светлая версия того же цвета
|
||||
- Save/update button:
|
||||
- если это зафиксированный green CTA, текст должен быть контрастным и читаемым
|
||||
- hover осветляет текущий тон, а не уходит в синий
|
||||
- Secondary button:
|
||||
- тёмный glass фон
|
||||
- без border-outline
|
||||
- hover немного светлее базового surface
|
||||
- Danger button:
|
||||
- без кислотно-красных рамок
|
||||
- мягкий danger surface
|
||||
|
||||
## Поля и селекты
|
||||
- Все поля ввода, textarea, select, chip-select:
|
||||
- скруглённые
|
||||
- без внешних outline
|
||||
- glass background
|
||||
- единая вертикальная высота для одного класса контролов
|
||||
- Placeholder и label должны быть читаемы и не прилипать к краям.
|
||||
|
||||
## Toolbar и верхние панели
|
||||
- Элементы верхней панели центрируются по одной горизонтальной оси.
|
||||
- Активный layout/tool mode выделяется кругом акцентного цвета, не квадратной плашкой.
|
||||
- Кнопки `Отображение`, `Аналитика`, `Добавить рабочий элемент`:
|
||||
- одинаковая высота
|
||||
- каноничные радиусы
|
||||
- нормальные горизонтальные paddings, чтобы текст не лип к краям
|
||||
|
||||
## Карточки
|
||||
- Внутренние карточки строятся по симметричным верхним и нижним padding.
|
||||
- Верхняя ось:
|
||||
- аватар
|
||||
- имя
|
||||
- вторичная строка
|
||||
- action-circle справа
|
||||
должны сидеть на согласованной геометрии
|
||||
- Нижняя ось:
|
||||
- assignee bubbles
|
||||
- дата
|
||||
должны быть симметричны верхней
|
||||
|
||||
## Dropdown и popup
|
||||
- Все dropdown/popup приводятся к единому matte glass канону.
|
||||
- Запрещены:
|
||||
- квадратные active-box вокруг круглых кнопок
|
||||
- жёсткие border-outline
|
||||
- светлый фон, если основной экран тёмный
|
||||
- Search shell внутри popup должен использовать тот же стиль, что и сам popup.
|
||||
|
||||
## Drag and drop
|
||||
- Drag overlay использует акцентный контур.
|
||||
- Delete dropzone:
|
||||
- без красного технического свечения
|
||||
- текст локализован
|
||||
- акцентный outline допустим
|
||||
|
||||
## Тексты
|
||||
- Пользовательский UI на русском, если экран русифицирован.
|
||||
- Не оставлять смешанные подписи вида `Created at / Updated at / Label / State group`, если экран уже на русском.
|
||||
|
||||
## Правило внедрения
|
||||
- Новый экран или popup не стилизуется локально “на глаз”.
|
||||
- Сначала используется существующий shared-класс или shared-component.
|
||||
- Если shared-слоя нет, создаётся reusable-класс/компонент и уже через него приводятся все похожие места.
|
||||
- Цель: не точечная покраска одного окна, а единый системный канон.
|
||||
|
|
@ -1,7 +0,0 @@
|
|||
{
|
||||
"nodedc": {
|
||||
"accent_rgb": [195, 255, 102],
|
||||
"passive_card_rgb": [42, 43, 46],
|
||||
"active_card_rgb": [195, 255, 102]
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1 @@
|
|||
plane-src/apps/web/design.config.json
|
||||
|
|
@ -4,7 +4,7 @@
|
|||
* See the LICENSE file for details.
|
||||
*/
|
||||
|
||||
import type { ReactNode } from "react";
|
||||
import type { CSSProperties, ReactNode } from "react";
|
||||
import Script from "next/script";
|
||||
import { Links, Meta, Outlet, Scripts } from "react-router";
|
||||
import type { LinksFunction } from "react-router";
|
||||
|
|
@ -22,6 +22,7 @@ import icon512 from "@/app/assets/icons/icon-512x512.png?url";
|
|||
import ogImage from "@/app/assets/og-image.png?url";
|
||||
import globalStyles from "@/styles/globals.css?url";
|
||||
import type { Route } from "./+types/root";
|
||||
import designConfig from "../design.config.json";
|
||||
// components
|
||||
import { LogoSpinner } from "@/components/common/logo-spinner";
|
||||
// local
|
||||
|
|
@ -35,6 +36,12 @@ import "@fontsource/ibm-plex-mono";
|
|||
|
||||
const APP_TITLE = "NODE.DC | Self-hosted task management workspace.";
|
||||
|
||||
const designConfigStyle = {
|
||||
"--nodedc-accent-rgb": designConfig.nodedc.accent_rgb.join(" "),
|
||||
"--nodedc-card-passive-rgb": designConfig.nodedc.passive_card_rgb.join(" "),
|
||||
"--nodedc-card-active-rgb": designConfig.nodedc.active_card_rgb.join(" "),
|
||||
} as CSSProperties;
|
||||
|
||||
export const links: LinksFunction = () => [
|
||||
{ rel: "icon", type: "image/png", sizes: "32x32", href: favicon32 },
|
||||
{ rel: "icon", type: "image/png", sizes: "16x16", href: favicon16 },
|
||||
|
|
@ -58,7 +65,7 @@ export function Layout({ children }: { children: ReactNode }) {
|
|||
const isSessionRecorderEnabled = parseInt(process.env.VITE_ENABLE_SESSION_RECORDER || "0");
|
||||
|
||||
return (
|
||||
<html lang="en" suppressHydrationWarning>
|
||||
<html lang="en" suppressHydrationWarning style={designConfigStyle}>
|
||||
<head>
|
||||
<meta charSet="utf-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||
|
|
|
|||
|
|
@ -24,7 +24,7 @@ export const EstimateListItemButtons = observer(function EstimateListItemButtons
|
|||
return (
|
||||
<div className="relative flex items-center gap-1">
|
||||
<button
|
||||
className="relative flex h-6 w-6 flex-shrink-0 cursor-pointer items-center justify-center overflow-hidden rounded-sm transition-colors hover:bg-layer-1"
|
||||
className="nodedc-settings-secondary-button relative flex h-10 w-10 min-h-0 min-w-0 flex-shrink-0 cursor-pointer items-center justify-center overflow-hidden rounded-[1rem] px-0 transition-colors"
|
||||
onClick={() => onDeleteClick && onDeleteClick(estimateId)}
|
||||
data-ph-element={PROJECT_SETTINGS_TRACKER_ELEMENTS.ESTIMATES_LIST_ITEM}
|
||||
>
|
||||
|
|
|
|||
|
|
@ -120,7 +120,7 @@ export const IssuesHeader = observer(function IssuesHeader() {
|
|||
<Button
|
||||
variant="primary"
|
||||
size="lg"
|
||||
className="nodedc-toolbar-primary"
|
||||
className="nodedc-toolbar-primary nodedc-toolbar-primary-wide"
|
||||
onClick={() => {
|
||||
toggleCreateIssueModal(true, EIssuesStoreType.PROJECT);
|
||||
}}
|
||||
|
|
|
|||
|
|
@ -24,6 +24,8 @@ function ProjectAttributes(props: Props) {
|
|||
const { t } = useTranslation();
|
||||
const { control } = useFormContext<IProject>();
|
||||
const { getIndex } = getTabIndex(ETabIndices.PROJECT_CREATE, isMobile);
|
||||
const projectAttributeChipClassName =
|
||||
"nodedc-modal-chip !h-10 !rounded-[1.25rem] !px-4 !py-2 !text-13";
|
||||
return (
|
||||
<div className="flex flex-wrap items-center gap-2">
|
||||
<Controller
|
||||
|
|
@ -50,8 +52,8 @@ function ProjectAttributes(props: Props) {
|
|||
</div>
|
||||
}
|
||||
placement="bottom-start"
|
||||
className="h-full"
|
||||
buttonClassName="h-full"
|
||||
className="h-10"
|
||||
buttonClassName={projectAttributeChipClassName}
|
||||
noChevron
|
||||
tabIndex={getIndex("network")}
|
||||
>
|
||||
|
|
@ -84,6 +86,9 @@ function ProjectAttributes(props: Props) {
|
|||
placeholder={t("lead")}
|
||||
multiple={false}
|
||||
buttonVariant="border-with-text"
|
||||
buttonClassName={projectAttributeChipClassName}
|
||||
buttonContainerClassName="!h-10"
|
||||
showUserDetails
|
||||
tabIndex={getIndex("lead")}
|
||||
/>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -167,8 +167,8 @@ export const CreateProjectForm = observer(function CreateProjectForm(props: TCre
|
|||
<FormProvider {...methods}>
|
||||
<ProjectCreateHeader handleClose={handleClose} isMobile={isMobile} />
|
||||
|
||||
<form onSubmit={handleSubmit(onSubmit)} className="px-3">
|
||||
<div className="mt-9 space-y-6 pb-5">
|
||||
<form onSubmit={handleSubmit(onSubmit)} className="px-6">
|
||||
<div className="mt-10 space-y-6 pb-6">
|
||||
<ProjectCommonAttributes
|
||||
setValue={setValue}
|
||||
isMobile={isMobile}
|
||||
|
|
|
|||
|
|
@ -21,6 +21,7 @@ import { Button, getButtonStyling } from "@plane/propel/button";
|
|||
import { TOAST_TYPE, setToast } from "@plane/propel/toast";
|
||||
import { EFileAssetType } from "@plane/types";
|
||||
import { Input, Loader } from "@plane/ui";
|
||||
import { cn } from "@plane/utils";
|
||||
// helpers
|
||||
import { STATIC_COVER_IMAGES, getCoverImageDisplayURL } from "@/helpers/cover-image.helper";
|
||||
// hooks
|
||||
|
|
@ -40,6 +41,7 @@ type Props = {
|
|||
value: string | null;
|
||||
control: Control<any>;
|
||||
onChange: (data: string) => void;
|
||||
buttonClassName?: string;
|
||||
disabled?: boolean;
|
||||
tabIndex?: number;
|
||||
isProfileCover?: boolean;
|
||||
|
|
@ -50,7 +52,17 @@ type Props = {
|
|||
const fileService = new FileService();
|
||||
|
||||
export const ImagePickerPopover = observer(function ImagePickerPopover(props: Props) {
|
||||
const { label, value, control, onChange, disabled = false, tabIndex, isProfileCover = false, projectId } = props;
|
||||
const {
|
||||
label,
|
||||
value,
|
||||
control,
|
||||
onChange,
|
||||
buttonClassName = "",
|
||||
disabled = false,
|
||||
tabIndex,
|
||||
isProfileCover = false,
|
||||
projectId,
|
||||
} = props;
|
||||
const { t } = useTranslation();
|
||||
// states
|
||||
const [image, setImage] = useState<File | null>(null);
|
||||
|
|
@ -191,7 +203,11 @@ export const ImagePickerPopover = observer(function ImagePickerPopover(props: Pr
|
|||
|
||||
return (
|
||||
<Popover className="relative z-19" ref={ref} tabIndex={tabIndex} onKeyDown={handleKeyDown}>
|
||||
<Popover.Button className={getButtonStyling("secondary", "sm")} onClick={handleOnClick} disabled={disabled}>
|
||||
<Popover.Button
|
||||
className={cn(getButtonStyling("secondary", "sm"), buttonClassName)}
|
||||
onClick={handleOnClick}
|
||||
disabled={disabled}
|
||||
>
|
||||
{label}
|
||||
</Popover.Button>
|
||||
|
||||
|
|
|
|||
|
|
@ -9,6 +9,7 @@ import { observer } from "mobx-react";
|
|||
import useSWR from "swr";
|
||||
// plane imports
|
||||
import { useTranslation } from "@plane/i18n";
|
||||
import { Button } from "@plane/propel/button";
|
||||
// components
|
||||
import { SettingsBoxedControlItem } from "@/components/settings/boxed-control-item";
|
||||
import { SettingsHeading } from "@/components/settings/heading";
|
||||
|
|
@ -74,7 +75,7 @@ export const EstimateRoot = observer(function EstimateRoot(props: TEstimateRoot)
|
|||
/>
|
||||
{/* active estimates section */}
|
||||
<div className="mt-12 flex flex-col gap-y-4">
|
||||
<SettingsHeading title="Estimates list" variant="h6" />
|
||||
<SettingsHeading title={t("project_settings.estimates.list_heading")} variant="h6" />
|
||||
<EstimateList
|
||||
estimateIds={[currentActiveEstimateId]}
|
||||
isAdmin={isAdmin}
|
||||
|
|
@ -91,12 +92,15 @@ export const EstimateRoot = observer(function EstimateRoot(props: TEstimateRoot)
|
|||
assetClassName="size-20"
|
||||
title={t("settings_empty_state.estimates.title")}
|
||||
description={t("settings_empty_state.estimates.description")}
|
||||
actions={[
|
||||
{
|
||||
label: t("settings_empty_state.estimates.cta_primary"),
|
||||
onClick: () => setIsEstimateCreateModalOpen(true),
|
||||
},
|
||||
]}
|
||||
customButton={
|
||||
<Button
|
||||
variant="ghost"
|
||||
className="nodedc-settings-primary-button"
|
||||
onClick={() => setIsEstimateCreateModalOpen(true)}
|
||||
>
|
||||
{t("settings_empty_state.estimates.cta_primary")}
|
||||
</Button>
|
||||
}
|
||||
align="start"
|
||||
rootClassName="py-20"
|
||||
/>
|
||||
|
|
@ -105,11 +109,10 @@ export const EstimateRoot = observer(function EstimateRoot(props: TEstimateRoot)
|
|||
{archivedEstimateIds && archivedEstimateIds.length > 0 && (
|
||||
<div className="mt-12 flex flex-col gap-y-4">
|
||||
<SettingsHeading
|
||||
title="Archived estimates"
|
||||
title={t("project_settings.estimates.archived_heading")}
|
||||
description={
|
||||
<>
|
||||
Estimates have gone through a change, these are the estimates you had in your older versions which
|
||||
were not in use. Read more about them
|
||||
{t("project_settings.estimates.archived_description")}
|
||||
<a
|
||||
href={"https://docs.plane.so/core-concepts/projects/run-project#estimate"}
|
||||
target="_blank"
|
||||
|
|
|
|||
|
|
@ -74,16 +74,16 @@ export const DeleteInboxIssueModal = observer(function DeleteInboxIssueModal({
|
|||
isSubmitting={isDeleting}
|
||||
isOpen={isOpen}
|
||||
title={t("inbox_issue.modals.delete.title")}
|
||||
// TODO: Need to translate the confirmation message
|
||||
content={
|
||||
<>
|
||||
Are you sure you want to delete work item{" "}
|
||||
<span className="font-medium break-words text-primary">
|
||||
{projectDetails?.identifier}-{data?.sequence_id}
|
||||
</span>
|
||||
{""}? The work item will only be deleted from the intake and this action cannot be undone.
|
||||
</>
|
||||
t("inbox_issue.modals.delete.content", {
|
||||
value: `${projectDetails?.identifier}-${data?.sequence_id}`,
|
||||
})
|
||||
}
|
||||
primaryButtonText={{
|
||||
loading: t("deleting"),
|
||||
default: t("delete"),
|
||||
}}
|
||||
secondaryButtonText={t("cancel")}
|
||||
/>
|
||||
);
|
||||
});
|
||||
|
|
|
|||
|
|
@ -119,14 +119,17 @@ export const DeleteIssueModal = observer(function DeleteIssueModal(props: Props)
|
|||
title={t("entity.delete.label", { entity: isEpic ? t("common.epic") : t("common.work_item") })}
|
||||
content={
|
||||
<>
|
||||
{/* TODO: Translate here */}
|
||||
{`Are you sure you want to delete ${isEpic ? "epic" : "work item"} `}
|
||||
<span className="font-medium break-words text-primary">
|
||||
{projectDetails?.identifier}-{issue?.sequence_id}
|
||||
</span>
|
||||
{` ? All of the data related to the ${isEpic ? "epic" : "work item"} will be permanently removed. This action cannot be undone.`}
|
||||
{t("entity.delete.confirmation", {
|
||||
entity: isEpic ? t("common.epic") : t("common.work_item"),
|
||||
identifier: `${projectDetails?.identifier}-${issue?.sequence_id}`,
|
||||
})}
|
||||
</>
|
||||
}
|
||||
primaryButtonText={{
|
||||
loading: t("deleting"),
|
||||
default: t("delete"),
|
||||
}}
|
||||
secondaryButtonText={t("cancel")}
|
||||
/>
|
||||
);
|
||||
});
|
||||
|
|
|
|||
|
|
@ -128,7 +128,7 @@ export const HeaderFilters = observer(function HeaderFilters(props: Props) {
|
|||
</FiltersDropdown>
|
||||
{canUserCreateIssue ? (
|
||||
<Button
|
||||
className="nodedc-toolbar-pill hidden md:inline-flex"
|
||||
className="nodedc-toolbar-pill nodedc-toolbar-pill-wide hidden md:inline-flex"
|
||||
onClick={() => setAnalyticsModal(true)}
|
||||
variant="secondary"
|
||||
size="lg"
|
||||
|
|
|
|||
|
|
@ -61,7 +61,7 @@ export function FiltersDropdown(props: Props) {
|
|||
variant="secondary"
|
||||
prependIcon={icon}
|
||||
tabIndex={tabIndex}
|
||||
className="nodedc-toolbar-pill relative"
|
||||
className="nodedc-toolbar-pill nodedc-toolbar-pill-wide relative"
|
||||
data-active={open}
|
||||
size="lg"
|
||||
>
|
||||
|
|
@ -81,7 +81,7 @@ export function FiltersDropdown(props: Props) {
|
|||
ref={setReferenceElement}
|
||||
variant="secondary"
|
||||
tabIndex={tabIndex}
|
||||
className="nodedc-toolbar-pill"
|
||||
className="nodedc-toolbar-pill nodedc-toolbar-pill-wide"
|
||||
size="lg"
|
||||
>
|
||||
{miniIcon || title}
|
||||
|
|
|
|||
|
|
@ -51,10 +51,10 @@ export function GroupDragOverlay(props: Props) {
|
|||
<div
|
||||
ref={messageContainerRef}
|
||||
className={cn(
|
||||
`absolute top-0 left-0 h-full w-full items-center rounded-sm bg-layer-1/85 text-13 font-medium text-tertiary ${dragColumnOrientation}`,
|
||||
`absolute top-0 left-0 h-full w-full items-center rounded-[1.5rem] bg-layer-1/85 text-13 font-medium text-tertiary ${dragColumnOrientation}`,
|
||||
{
|
||||
"z-2 flex flex-col border-[1px] border-strong": shouldOverlayBeVisible,
|
||||
"bg-danger-subtle": workflowDisabledSource && isDropDisabled,
|
||||
"z-2 flex flex-col border border-[rgb(var(--nodedc-accent-rgb))]/55 bg-black/35": shouldOverlayBeVisible,
|
||||
"bg-black/40": workflowDisabledSource && isDropDisabled,
|
||||
},
|
||||
{ hidden: !shouldOverlayBeVisible }
|
||||
)}
|
||||
|
|
@ -67,14 +67,13 @@ export function GroupDragOverlay(props: Props) {
|
|||
/>
|
||||
) : (
|
||||
<div
|
||||
className={cn("my-8 flex flex-col items-center rounded-sm p-3", {
|
||||
"text-secondary": shouldOverlayBeVisible,
|
||||
"text-danger-secondary": isDropDisabled,
|
||||
})}
|
||||
className={cn(
|
||||
"my-8 flex max-w-[20rem] flex-col items-center rounded-[1.25rem] px-4 py-3 text-center text-secondary"
|
||||
)}
|
||||
>
|
||||
{dropErrorMessage ? (
|
||||
<div className="flex items-center">
|
||||
<AlertCircle width={13} height={13} />
|
||||
<AlertCircle width={13} height={13} className="text-[rgb(var(--nodedc-accent-rgb))]" />
|
||||
<span>{dropErrorMessage}</span>
|
||||
</div>
|
||||
) : (
|
||||
|
|
@ -84,7 +83,7 @@ export function GroupDragOverlay(props: Props) {
|
|||
{t("issue.layouts.ordered_by_label")} <span className="font-semibold">{t(readableOrderBy)}</span>.
|
||||
</span>
|
||||
)}
|
||||
<span>{t("entity.drop_here_to_move", { entity: isEpic ? "epic" : "work item" })}</span>
|
||||
<span>{t("entity.drop_here_to_move", { entity: t(isEpic ? "common.epic" : "common.work_item") })}</span>
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -12,6 +12,7 @@ import { autoScrollForElements } from "@atlaskit/pragmatic-drag-and-drop-auto-sc
|
|||
import { observer } from "mobx-react";
|
||||
import { useParams } from "next/navigation";
|
||||
import { EIssueFilterType, EUserPermissions, EUserPermissionsLevel } from "@plane/constants";
|
||||
import { useTranslation } from "@plane/i18n";
|
||||
import type { EIssuesStoreType } from "@plane/types";
|
||||
import { EIssueServiceType, EIssueLayoutTypes } from "@plane/types";
|
||||
//hooks
|
||||
|
|
@ -89,6 +90,7 @@ export const BaseKanBanRoot = observer(function BaseKanBanRoot(props: IBaseKanBa
|
|||
const [isDragOverDelete, setIsDragOverDelete] = useState(false);
|
||||
|
||||
const { isDragging } = useKanbanView();
|
||||
const { t } = useTranslation();
|
||||
|
||||
const displayFilters = issuesFilter?.issueFilters?.displayFilters;
|
||||
const displayProperties = issuesFilter?.issueFilters?.displayProperties;
|
||||
|
|
@ -249,17 +251,17 @@ export const BaseKanBanRoot = observer(function BaseKanBanRoot(props: IBaseKanBa
|
|||
<div
|
||||
className={`fixed left-1/2 -translate-x-1/2 ${
|
||||
isDragging ? "z-40" : ""
|
||||
} top-3 mx-3 flex w-72 items-center justify-center`}
|
||||
} top-3 mx-3 flex w-[min(40rem,calc(100vw-2rem))] items-center justify-center`}
|
||||
ref={deleteAreaRef}
|
||||
>
|
||||
<div
|
||||
className={`${
|
||||
isDragging ? `opacity-100` : `opacity-0`
|
||||
} flex w-full items-center justify-center rounded-sm border-2 border-danger-strong/20 bg-surface-1 px-3 py-5 text-11 font-medium text-danger-primary italic ${
|
||||
isDragOverDelete ? "bg-danger-primary blur-2xl" : ""
|
||||
} nodedc-glass-popup-surface flex w-full items-center justify-center rounded-[1.5rem] border-2 border-[rgb(var(--nodedc-accent-rgb))]/80 px-6 py-4 text-center text-[13px] font-medium text-secondary ring-1 ring-[rgb(var(--nodedc-accent-rgb))]/20 ${
|
||||
isDragOverDelete ? "border-[rgb(var(--nodedc-accent-rgb))] bg-white/6 ring-[rgb(var(--nodedc-accent-rgb))]/35" : ""
|
||||
} transition duration-300`}
|
||||
>
|
||||
Drop here to delete the work item.
|
||||
{t("entity.drop_here_to_delete", { entity: t("common.work_item") })}
|
||||
</div>
|
||||
</div>
|
||||
<IssueLayoutHOC layout={EIssueLayoutTypes.KANBAN}>
|
||||
|
|
|
|||
|
|
@ -415,9 +415,9 @@ export const CreateUpdateIssueModalBase = observer(function CreateUpdateIssueMod
|
|||
return (
|
||||
<ModalCore
|
||||
isOpen={isOpen}
|
||||
position={EModalPosition.TOP}
|
||||
position={EModalPosition.CENTER}
|
||||
width={isDuplicateModalOpen ? EModalWidth.VIXL : EModalWidth.XXXXL}
|
||||
className="rounded-lg !bg-transparent shadow-none transition-[width] ease-linear"
|
||||
className="transition-[width] ease-linear"
|
||||
>
|
||||
{withDraftIssueWrapper ? (
|
||||
<DraftIssueLayout {...commonIssueModalProps} changesMade={changesMade} onChange={handleFormChange} />
|
||||
|
|
|
|||
|
|
@ -154,7 +154,7 @@ export const IssueDescriptionEditor = observer(function IssueDescriptionEditor(p
|
|||
};
|
||||
|
||||
return (
|
||||
<div className="relative rounded-lg border-[0.5px] border-subtle-1 bg-layer-2">
|
||||
<div className="nodedc-modal-editor relative min-h-[180px]">
|
||||
{descriptionHtmlData === undefined || !projectId ? (
|
||||
<Loader className="max-h-64 min-h-[120px] space-y-2 overflow-hidden rounded-md border border-subtle p-3 py-2 pt-3">
|
||||
<Loader.Item width="100%" height="26px" />
|
||||
|
|
@ -203,7 +203,7 @@ export const IssueDescriptionEditor = observer(function IssueDescriptionEditor(p
|
|||
project_id: projectId?.toString() ?? "",
|
||||
})
|
||||
}
|
||||
containerClassName="pt-3 min-h-[120px]"
|
||||
containerClassName="min-h-[180px] !border-none !bg-transparent !p-0"
|
||||
uploadFile={async (blockId, file) => {
|
||||
try {
|
||||
const { asset_id } = await uploadEditorAsset({
|
||||
|
|
@ -243,11 +243,11 @@ export const IssueDescriptionEditor = observer(function IssueDescriptionEditor(p
|
|||
/>
|
||||
)}
|
||||
/>
|
||||
<div className="border-0.5 z-10 flex items-center justify-end gap-2 p-3">
|
||||
<div className="z-10 flex items-center justify-end gap-2 px-4 pb-4">
|
||||
{issueName && issueName.trim() !== "" && config?.has_llm_configured && (
|
||||
<button
|
||||
type="button"
|
||||
className={`flex items-center gap-1 rounded-sm bg-surface-2 px-1.5 py-1 text-caption-sm-regular hover:bg-layer-1 ${
|
||||
className={`flex items-center gap-1 rounded-[1rem] bg-white/6 px-2.5 py-1.5 text-caption-sm-regular text-secondary transition-colors hover:bg-white/10 ${
|
||||
iAmFeelingLucky ? "cursor-wait" : ""
|
||||
}`}
|
||||
onClick={handleAutoGenerateDescription}
|
||||
|
|
|
|||
|
|
@ -72,7 +72,7 @@ export const IssueTitleInput = observer(function IssueTitleInput(props: TIssueTi
|
|||
ref={issueTitleRef || ref}
|
||||
hasError={Boolean(errors.name)}
|
||||
placeholder={t("title")}
|
||||
className="w-full text-body-sm-regular"
|
||||
className="nodedc-modal-input w-full !px-4 !py-3 !text-[15px]"
|
||||
autoFocus
|
||||
tabIndex={getIndex("name")}
|
||||
/>
|
||||
|
|
|
|||
|
|
@ -376,15 +376,15 @@ export const IssueFormRoot = observer(function IssueFormRoot(props: IssueFormPro
|
|||
|
||||
return (
|
||||
<FormProvider {...methods}>
|
||||
<div className="flex gap-2 bg-transparent">
|
||||
<div className="w-full rounded-lg">
|
||||
<div className="nodedc-work-item-modal flex gap-3 bg-transparent">
|
||||
<div className="w-full">
|
||||
<form
|
||||
ref={formRef}
|
||||
onSubmit={handleSubmit((data) => handleFormSubmit(data))}
|
||||
className="flex w-full flex-col"
|
||||
>
|
||||
<div className="rounded-t-lg bg-surface-1 p-5">
|
||||
<h3 className="pb-2 text-h4-medium text-secondary">{modalTitle}</h3>
|
||||
<div className="px-6 pt-6 pb-2">
|
||||
<h3 className="pb-2 text-18 font-medium text-secondary">{modalTitle}</h3>
|
||||
<div className="flex items-center justify-between pt-2 pb-4">
|
||||
<div className="flex items-center gap-x-1">
|
||||
<IssueProjectSelect
|
||||
|
|
@ -452,7 +452,7 @@ export const IssueFormRoot = observer(function IssueFormRoot(props: IssueFormPro
|
|||
</div>
|
||||
<div
|
||||
className={cn(
|
||||
"space-y-3 bg-surface-1 pb-4",
|
||||
"space-y-4 bg-transparent pb-4",
|
||||
activeAdditionalPropertiesLength > 4 &&
|
||||
"vertical-scrollbar scrollbar-sm max-h-[45vh] overflow-hidden overflow-y-auto"
|
||||
)}
|
||||
|
|
@ -486,12 +486,7 @@ export const IssueFormRoot = observer(function IssueFormRoot(props: IssueFormPro
|
|||
workspaceSlug={workspaceSlug?.toString()}
|
||||
/>
|
||||
</div>
|
||||
<div
|
||||
className={cn(
|
||||
"rounded-b-lg border-t-[0.5px] border-subtle bg-surface-1 px-4 py-3",
|
||||
activeAdditionalPropertiesLength > 0 && "shadow-raised-100"
|
||||
)}
|
||||
>
|
||||
<div className={cn("border-t border-subtle/70 bg-transparent px-6 py-4")}>
|
||||
<div className="pb-3">
|
||||
<IssueDefaultProperties
|
||||
control={control}
|
||||
|
|
@ -509,7 +504,7 @@ export const IssueFormRoot = observer(function IssueFormRoot(props: IssueFormPro
|
|||
</div>
|
||||
{showActionButtons && (
|
||||
<div
|
||||
className="flex items-center justify-end gap-4 border-t-[0.5px] border-subtle pt-6 pb-3"
|
||||
className="flex items-center justify-end gap-4 border-t border-subtle/70 pt-6 pb-2"
|
||||
tabIndex={getIndex("create_more")}
|
||||
>
|
||||
{!data?.id && (
|
||||
|
|
@ -530,6 +525,7 @@ export const IssueFormRoot = observer(function IssueFormRoot(props: IssueFormPro
|
|||
<Button
|
||||
variant="secondary"
|
||||
size="lg"
|
||||
className="nodedc-modal-secondary-button min-w-[8.25rem]"
|
||||
onClick={() => {
|
||||
if (editorRef.current?.isEditorReadyToDiscard()) {
|
||||
onClose();
|
||||
|
|
@ -553,6 +549,10 @@ export const IssueFormRoot = observer(function IssueFormRoot(props: IssueFormPro
|
|||
ref={submitBtnRef}
|
||||
loading={isSubmitting}
|
||||
disabled={isDisabled}
|
||||
className={cn(
|
||||
"min-w-[8.25rem]",
|
||||
moveToIssue ? "nodedc-modal-secondary-button" : "nodedc-modal-primary-button"
|
||||
)}
|
||||
>
|
||||
{isSubmitting ? primaryButtonText.loading : primaryButtonText.default}
|
||||
</Button>
|
||||
|
|
@ -566,6 +566,7 @@ export const IssueFormRoot = observer(function IssueFormRoot(props: IssueFormPro
|
|||
onClick={handleMoveToProjects}
|
||||
disabled={isMoving}
|
||||
size="lg"
|
||||
className="nodedc-modal-primary-button min-w-[8.25rem]"
|
||||
>
|
||||
{t("add_to_project")}
|
||||
</Button>
|
||||
|
|
@ -579,7 +580,7 @@ export const IssueFormRoot = observer(function IssueFormRoot(props: IssueFormPro
|
|||
{shouldRenderDuplicateModal && (
|
||||
<div
|
||||
ref={modalContainerRef}
|
||||
className="shadow-xl bg-pi-50 relative flex flex-col gap-2.5 rounded-lg px-3 py-4"
|
||||
className="nodedc-glass-surface relative flex flex-col gap-2.5 rounded-[1.75rem] px-4 py-4"
|
||||
style={{ maxHeight: formRef?.current?.offsetHeight ? `${formRef.current.offsetHeight}px` : "436px" }}
|
||||
>
|
||||
<DuplicateModalRoot
|
||||
|
|
|
|||
|
|
@ -161,7 +161,7 @@ export const CreateUpdateLabelInline = observer(
|
|||
<>
|
||||
<div
|
||||
ref={ref}
|
||||
className={`flex w-full scroll-m-8 items-center gap-2 bg-surface-1 ${labelForm ? "" : "hidden"}`}
|
||||
className={`flex w-full scroll-m-8 items-center gap-2 ${labelForm ? "" : "hidden"}`}
|
||||
>
|
||||
<div className="flex-shrink-0">
|
||||
<Popover className="relative z-10 flex h-full w-full items-center justify-center">
|
||||
|
|
@ -229,21 +229,22 @@ export const CreateUpdateLabelInline = observer(
|
|||
ref={ref}
|
||||
hasError={Boolean(errors.name)}
|
||||
placeholder={t("project_settings.labels.label_title")}
|
||||
className="w-full"
|
||||
className="nodedc-settings-input w-full"
|
||||
/>
|
||||
)}
|
||||
/>
|
||||
</div>
|
||||
<Button variant="secondary" onClick={() => handleClose()}>
|
||||
<Button variant="ghost" onClick={() => handleClose()} className="nodedc-settings-secondary-button">
|
||||
{t("cancel")}
|
||||
</Button>
|
||||
<Button
|
||||
variant="primary"
|
||||
variant="ghost"
|
||||
onClick={(e) => {
|
||||
e.preventDefault();
|
||||
handleSubmit(handleFormSubmit)();
|
||||
}}
|
||||
loading={isSubmitting}
|
||||
className="nodedc-settings-primary-button"
|
||||
>
|
||||
{isUpdating ? (isSubmitting ? t("updating") : t("update")) : isSubmitting ? t("adding") : t("add")}
|
||||
</Button>
|
||||
|
|
|
|||
|
|
@ -33,7 +33,7 @@ export function LabelDragPreview(props: LabelDragPreviewProps) {
|
|||
const { label, isGroup } = props;
|
||||
|
||||
return (
|
||||
<div className="border-[1px] border-subtle bg-surface-1 py-3 pr-4 pl-2">
|
||||
<div className="nodedc-settings-card rounded-[1.25rem] py-3 pr-4 pl-2">
|
||||
<LabelName name={label.name} color={label.color} isGroup={isGroup} />
|
||||
</div>
|
||||
);
|
||||
|
|
|
|||
|
|
@ -78,13 +78,11 @@ export const ProjectSettingLabelGroup = observer(function ProjectSettingLabelGro
|
|||
<LabelDndHOC label={label} isGroup isChild={false} isLastChild={isLastChild} onDrop={onDrop}>
|
||||
{(isDragging, isDroppingInLabel, dragHandleRef) => (
|
||||
<div
|
||||
className={`rounded-sm ${isDroppingInLabel ? "border-[2px] border-accent-strong" : "border-[1.5px] border-transparent"}`}
|
||||
className={`rounded-[1.25rem] ${isDroppingInLabel ? "border-[2px] border-accent-strong" : "border-[1.5px] border-transparent"}`}
|
||||
>
|
||||
<Disclosure
|
||||
as="div"
|
||||
className={`rounded-sm text-primary ${
|
||||
!isDroppingInLabel ? "border-[0.5px] border-subtle" : ""
|
||||
} ${isDragging ? "bg-layer-1" : "bg-surface-1"}`}
|
||||
className={`nodedc-settings-card rounded-[1.25rem] text-primary ${isDragging ? "bg-layer-1" : "bg-surface-1"}`}
|
||||
defaultOpen
|
||||
>
|
||||
{({ open }) => (
|
||||
|
|
|
|||
|
|
@ -87,12 +87,12 @@ export function ProjectSettingLabelItem(props: Props) {
|
|||
<LabelDndHOC label={label} isGroup={false} isChild={isChild} isLastChild={isLastChild} onDrop={onDrop}>
|
||||
{(isDragging, isDroppingInLabel, dragHandleRef) => (
|
||||
<div
|
||||
className={`rounded-sm ${isDroppingInLabel ? "border-[2px] border-accent-strong" : "border-[1.5px] border-transparent"}`}
|
||||
className={`rounded-[1.25rem] ${isDroppingInLabel ? "border-[2px] border-accent-strong" : "border-[1.5px] border-transparent"}`}
|
||||
>
|
||||
<div
|
||||
className={`group relative flex items-center justify-between gap-2 space-y-3 rounded-sm px-1 py-3 ${
|
||||
isDroppingInLabel ? "" : "border-[0.5px] border-subtle"
|
||||
} ${isDragging || isParentDragging ? "bg-layer-1" : "bg-surface-1"}`}
|
||||
className={`nodedc-settings-card group relative flex items-center justify-between gap-2 space-y-3 rounded-[1.25rem] px-3 py-3 ${
|
||||
isDragging || isParentDragging ? "bg-layer-1" : "bg-surface-1"
|
||||
}`}
|
||||
>
|
||||
{isEditLabelForm ? (
|
||||
<CreateUpdateLabelInline
|
||||
|
|
|
|||
|
|
@ -85,7 +85,7 @@ export const ProjectSettingsLabelList = observer(function ProjectSettingsLabelLi
|
|||
description={t("project_settings.labels.description")}
|
||||
control={
|
||||
isEditable && (
|
||||
<Button variant="primary" size="lg" onClick={newLabel}>
|
||||
<Button variant="ghost" size="lg" onClick={newLabel} className="nodedc-settings-primary-button min-w-[11rem]">
|
||||
{t("common.add_label")}
|
||||
</Button>
|
||||
)
|
||||
|
|
@ -93,7 +93,7 @@ export const ProjectSettingsLabelList = observer(function ProjectSettingsLabelLi
|
|||
/>
|
||||
<div className="mt-6 w-full">
|
||||
{showLabelForm && (
|
||||
<div className="my-2 w-full rounded-sm border border-subtle px-3.5 py-2">
|
||||
<div className="nodedc-settings-card my-2 w-full px-4 py-3">
|
||||
<CreateUpdateLabelInline
|
||||
labelForm={showLabelForm}
|
||||
setLabelForm={setLabelForm}
|
||||
|
|
@ -114,14 +114,11 @@ export const ProjectSettingsLabelList = observer(function ProjectSettingsLabelLi
|
|||
assetClassName="size-20"
|
||||
title={t("settings_empty_state.labels.title")}
|
||||
description={t("settings_empty_state.labels.description")}
|
||||
actions={[
|
||||
{
|
||||
label: t("settings_empty_state.labels.cta_primary"),
|
||||
onClick: () => {
|
||||
newLabel();
|
||||
},
|
||||
},
|
||||
]}
|
||||
customButton={
|
||||
<Button variant="ghost" className="nodedc-settings-primary-button" onClick={newLabel}>
|
||||
{t("settings_empty_state.labels.cta_primary")}
|
||||
</Button>
|
||||
}
|
||||
align="start"
|
||||
rootClassName="py-20"
|
||||
/>
|
||||
|
|
|
|||
|
|
@ -22,7 +22,7 @@ type TStateForm = {
|
|||
function PopoverButton({ color }: { color?: string }) {
|
||||
return (
|
||||
<div
|
||||
className="group inline-flex h-5 w-5 items-center rounded-sm text-14 font-medium transition-all focus:outline-none"
|
||||
className="group inline-flex h-5 w-5 items-center rounded-full text-14 font-medium transition-all focus:outline-none"
|
||||
style={{
|
||||
backgroundColor: color ?? "black",
|
||||
}}
|
||||
|
|
@ -65,7 +65,7 @@ export function StateForm(props: TStateForm) {
|
|||
};
|
||||
|
||||
return (
|
||||
<div className="relative flex space-x-2 rounded-sm bg-surface-1 p-3">
|
||||
<div className="nodedc-settings-card relative flex space-x-2 rounded-[1.25rem] p-3">
|
||||
{/* color */}
|
||||
<div className="mt-2 h-full flex-shrink-0">
|
||||
<Popover button={<PopoverButton color={formData?.color} />} panelClassName="mt-4 -ml-3">
|
||||
|
|
@ -83,7 +83,7 @@ export function StateForm(props: TStateForm) {
|
|||
value={formData?.name}
|
||||
onChange={(e) => handleFormData("name", e.target.value)}
|
||||
hasError={(errors && Boolean(errors.name)) || false}
|
||||
className="w-full"
|
||||
className="nodedc-settings-input w-full"
|
||||
maxLength={100}
|
||||
autoFocus
|
||||
/>
|
||||
|
|
@ -96,14 +96,27 @@ export function StateForm(props: TStateForm) {
|
|||
value={formData?.description}
|
||||
onChange={(e) => handleFormData("description", e.target.value)}
|
||||
hasError={(errors && Boolean(errors.description)) || false}
|
||||
className="min-h-14 w-full resize-none text-13"
|
||||
className="nodedc-settings-input min-h-14 w-full resize-none text-13"
|
||||
/>
|
||||
|
||||
<div className="flex items-center space-x-2">
|
||||
<Button onClick={formSubmit} variant="primary" size="lg" disabled={buttonDisabled}>
|
||||
<Button
|
||||
onClick={formSubmit}
|
||||
variant="ghost"
|
||||
size="lg"
|
||||
disabled={buttonDisabled}
|
||||
className="nodedc-settings-primary-button"
|
||||
>
|
||||
{buttonTitle}
|
||||
</Button>
|
||||
<Button type="button" variant="secondary" size="lg" disabled={buttonDisabled} onClick={onCancel}>
|
||||
<Button
|
||||
type="button"
|
||||
variant="ghost"
|
||||
size="lg"
|
||||
disabled={buttonDisabled}
|
||||
onClick={onCancel}
|
||||
className="nodedc-settings-secondary-button"
|
||||
>
|
||||
{t("cancel")}
|
||||
</Button>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -57,7 +57,7 @@ export const GroupItem = observer(function GroupItem(props: TGroupItem) {
|
|||
|
||||
return (
|
||||
<div
|
||||
className={cn("space-y-1 rounded-sm border border-subtle bg-surface-2 p-2 transition-all", groupItemClassName)}
|
||||
className={cn("nodedc-settings-card space-y-1 rounded-[1.35rem] p-3 transition-all", groupItemClassName)}
|
||||
ref={dropElementRef}
|
||||
>
|
||||
<div className="flex items-center justify-between gap-2">
|
||||
|
|
@ -68,6 +68,7 @@ export const GroupItem = observer(function GroupItem(props: TGroupItem) {
|
|||
<div
|
||||
className={cn(
|
||||
"flex h-5 w-5 flex-shrink-0 items-center justify-center overflow-hidden rounded-sm transition-all",
|
||||
"rounded-full",
|
||||
{
|
||||
"rotate-0": currentStateExpanded,
|
||||
"-rotate-90": !currentStateExpanded,
|
||||
|
|
@ -76,7 +77,7 @@ export const GroupItem = observer(function GroupItem(props: TGroupItem) {
|
|||
>
|
||||
<ChevronDownIcon className="h-4 w-4" />
|
||||
</div>
|
||||
<div className="flex h-6 w-6 flex-shrink-0 items-center justify-center overflow-hidden rounded-sm">
|
||||
<div className="flex h-6 w-6 flex-shrink-0 items-center justify-center overflow-hidden rounded-full">
|
||||
<StateGroupIcon stateGroup={groupKey} size={EIconSize.XL} />
|
||||
</div>
|
||||
<div className="px-1 text-14 font-medium text-secondary">{translatedGroupKey}</div>
|
||||
|
|
@ -86,6 +87,7 @@ export const GroupItem = observer(function GroupItem(props: TGroupItem) {
|
|||
data-ph-element={STATE_TRACKER_ELEMENTS.STATE_GROUP_ADD_BUTTON}
|
||||
className={cn(
|
||||
"flex h-6 w-6 flex-shrink-0 cursor-pointer items-center justify-center overflow-hidden rounded-sm text-accent-primary/80 transition-colors hover:bg-layer-1 hover:text-accent-primary",
|
||||
"rounded-full hover:bg-white/6",
|
||||
(!isEditable || createState) && "cursor-not-allowed text-placeholder hover:text-placeholder"
|
||||
)}
|
||||
onClick={() => {
|
||||
|
|
|
|||
|
|
@ -135,7 +135,7 @@ export const StateItem = observer(function StateItem(props: TStateItem) {
|
|||
<div
|
||||
ref={draggableElementRef}
|
||||
className={cn(
|
||||
"group relative rounded-sm border border-subtle bg-surface-1 px-3.5 py-3",
|
||||
"nodedc-settings-field group relative rounded-[1.2rem] px-3.5 py-3",
|
||||
isDragging ? `opacity-50` : `opacity-100`,
|
||||
totalStates === 1 ? `cursor-auto` : `cursor-grab`,
|
||||
stateItemClassName
|
||||
|
|
|
|||
|
|
@ -66,7 +66,12 @@ export function CreateProjectModal(props: Props) {
|
|||
});
|
||||
|
||||
return (
|
||||
<ModalCore isOpen={isOpen} position={EModalPosition.TOP} width={EModalWidth.XXXXL}>
|
||||
<ModalCore
|
||||
isOpen={isOpen}
|
||||
position={EModalPosition.CENTER}
|
||||
width={EModalWidth.XXXXL}
|
||||
className="overflow-hidden"
|
||||
>
|
||||
{currentStep === EProjectCreationSteps.CREATE_PROJECT && (
|
||||
<CreateProjectForm
|
||||
setToFavorite={setToFavorite}
|
||||
|
|
|
|||
|
|
@ -79,7 +79,7 @@ function ProjectCommonAttributes(props: Props) {
|
|||
onChange={handleNameChange(onChange)}
|
||||
hasError={Boolean(errors.name)}
|
||||
placeholder={t("project_name")}
|
||||
className="focus:border-blue-400 w-full"
|
||||
className="nodedc-modal-input w-full text-13"
|
||||
tabIndex={getIndex("name")}
|
||||
/>
|
||||
)}
|
||||
|
|
@ -113,7 +113,7 @@ function ProjectCommonAttributes(props: Props) {
|
|||
onChange={handleIdentifierChange(onChange)}
|
||||
hasError={Boolean(errors.identifier)}
|
||||
placeholder={t("project_id")}
|
||||
className={cn("focus:border-blue-400 w-full pr-7 text-11", {
|
||||
className={cn("nodedc-modal-input w-full pr-7 text-12", {
|
||||
uppercase: value,
|
||||
})}
|
||||
tabIndex={getIndex("identifier")}
|
||||
|
|
@ -144,7 +144,7 @@ function ProjectCommonAttributes(props: Props) {
|
|||
onChange(e);
|
||||
handleFormOnChange?.();
|
||||
}}
|
||||
className="focus:border-blue-400 !h-24 text-13"
|
||||
className="nodedc-modal-input !h-24 text-13"
|
||||
hasError={Boolean(errors?.description)}
|
||||
tabIndex={getIndex("description")}
|
||||
/>
|
||||
|
|
|
|||
|
|
@ -48,11 +48,11 @@ function ProjectCreateHeader(props: Props) {
|
|||
const { getIndex } = getTabIndex(ETabIndices.PROJECT_CREATE, isMobile);
|
||||
|
||||
return (
|
||||
<div className="group relative h-44 w-full rounded-lg">
|
||||
<div className="group relative h-44 w-full rounded-[1.75rem]">
|
||||
<CoverImage
|
||||
src={coverImage}
|
||||
alt={t("project_cover_image_alt")}
|
||||
className="absolute top-0 left-0 h-full w-full rounded-lg"
|
||||
className="absolute top-0 left-0 h-full w-full rounded-[1.75rem]"
|
||||
/>
|
||||
{showActionButtons && (
|
||||
<div className="absolute top-2.5 left-2.5">
|
||||
|
|
@ -60,8 +60,13 @@ function ProjectCreateHeader(props: Props) {
|
|||
</div>
|
||||
)}
|
||||
{isClosable && (
|
||||
<div className="absolute top-2 right-2 p-2">
|
||||
<button type="button" onClick={handleClose} tabIndex={getIndex("close")}>
|
||||
<div className="absolute top-3 right-3">
|
||||
<button
|
||||
type="button"
|
||||
onClick={handleClose}
|
||||
tabIndex={getIndex("close")}
|
||||
className="grid h-10 w-10 place-items-center rounded-[1.25rem] bg-black/32 text-on-color outline-none backdrop-blur-md transition-colors hover:bg-black/48"
|
||||
>
|
||||
<CloseIcon className="h-5 w-5 text-on-color" />
|
||||
</button>
|
||||
</div>
|
||||
|
|
@ -79,6 +84,7 @@ function ProjectCreateHeader(props: Props) {
|
|||
}}
|
||||
control={control}
|
||||
value={value ?? null}
|
||||
buttonClassName="nodedc-overlay-button"
|
||||
tabIndex={getIndex("cover_image")}
|
||||
/>
|
||||
)}
|
||||
|
|
@ -96,7 +102,7 @@ function ProjectCreateHeader(props: Props) {
|
|||
className="flex items-center justify-center"
|
||||
buttonClassName="flex items-center justify-center"
|
||||
label={
|
||||
<span className="grid h-11 w-11 place-items-center rounded-md border border-subtle bg-layer-2">
|
||||
<span className="grid h-11 w-11 place-items-center rounded-[1.2rem] bg-black/36 shadow-[0_10px_24px_rgba(0,0,0,0.22)] backdrop-blur-md">
|
||||
<Logo logo={value} size={20} />
|
||||
</span>
|
||||
}
|
||||
|
|
|
|||
|
|
@ -30,10 +30,23 @@ function ProjectCreateButtons(props: Props) {
|
|||
|
||||
return (
|
||||
<div className="flex justify-end gap-2 border-t border-subtle py-4">
|
||||
<Button variant="secondary" size="lg" onClick={handleClose} tabIndex={getIndex("cancel")}>
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="lg"
|
||||
onClick={handleClose}
|
||||
tabIndex={getIndex("cancel")}
|
||||
className="nodedc-modal-secondary-button min-w-[8.75rem]"
|
||||
>
|
||||
{t("common.cancel")}
|
||||
</Button>
|
||||
<Button variant="primary" size="lg" type="submit" loading={isSubmitting} tabIndex={getIndex("submit")}>
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="lg"
|
||||
type="submit"
|
||||
loading={isSubmitting}
|
||||
tabIndex={getIndex("submit")}
|
||||
className="nodedc-modal-primary-button min-w-[10.5rem]"
|
||||
>
|
||||
{isSubmitting ? t("creating") : t("create_project")}
|
||||
</Button>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -7,6 +7,7 @@
|
|||
import { useState } from "react";
|
||||
import { observer } from "mobx-react";
|
||||
// plane imports
|
||||
import { useTranslation } from "@plane/i18n";
|
||||
import { Button } from "@plane/propel/button";
|
||||
import { ChevronDownIcon } from "@plane/propel/icons";
|
||||
import { EUserProjectRoles, EUserWorkspaceRoles } from "@plane/types";
|
||||
|
|
@ -52,11 +53,12 @@ const RoleFilterGroup = observer(function RoleFilterGroup({
|
|||
const [isExpanded, setIsExpanded] = useState(true);
|
||||
const appliedFiltersCount = appliedFilters?.length ?? 0;
|
||||
const roleOptions = memberType === "project" ? PROJECT_ROLE_OPTIONS : WORKSPACE_ROLE_OPTIONS;
|
||||
const { t } = useTranslation();
|
||||
|
||||
return (
|
||||
<div className="space-y-2">
|
||||
<FilterHeader
|
||||
title={`Roles${appliedFiltersCount > 0 ? ` (${appliedFiltersCount})` : ""}`}
|
||||
title={`${t("roles")}${appliedFiltersCount > 0 ? ` (${appliedFiltersCount})` : ""}`}
|
||||
isPreviewEnabled={isExpanded}
|
||||
handleIsPreviewEnabled={() => setIsExpanded(!isExpanded)}
|
||||
/>
|
||||
|
|
@ -96,13 +98,14 @@ export const MemberListFiltersDropdown = observer(function MemberListFiltersDrop
|
|||
const { appliedFilters, handleUpdate, memberType } = props;
|
||||
|
||||
const appliedFiltersCount = appliedFilters?.length ?? 0;
|
||||
const { t } = useTranslation();
|
||||
|
||||
return (
|
||||
<CustomMenu
|
||||
customButton={
|
||||
<div className="relative">
|
||||
<Button variant="secondary" size="lg" className="flex items-center gap-2">
|
||||
<span>Filters</span>
|
||||
<Button variant="ghost" size="lg" className="nodedc-settings-secondary-button flex items-center gap-2">
|
||||
<span>{t("filters")}</span>
|
||||
<ChevronDownIcon className="h-3 w-3" />
|
||||
</Button>
|
||||
{appliedFiltersCount > 0 && (
|
||||
|
|
|
|||
|
|
@ -255,6 +255,7 @@ export function ProjectDetailsForm(props: IProjectDetailsForm) {
|
|||
control={control}
|
||||
onChange={onChange}
|
||||
value={value ?? null}
|
||||
buttonClassName="nodedc-overlay-button"
|
||||
disabled={!isAdmin}
|
||||
projectId={project.id}
|
||||
/>
|
||||
|
|
@ -286,7 +287,7 @@ export function ProjectDetailsForm(props: IProjectDetailsForm) {
|
|||
value={value}
|
||||
onChange={onChange}
|
||||
hasError={Boolean(errors.name)}
|
||||
className="rounded-md !p-3 font-medium"
|
||||
className="nodedc-settings-input !px-4 !py-3 font-medium"
|
||||
placeholder={t("common.project_name")}
|
||||
disabled={!isAdmin}
|
||||
/>
|
||||
|
|
@ -306,7 +307,7 @@ export function ProjectDetailsForm(props: IProjectDetailsForm) {
|
|||
value={value}
|
||||
placeholder={t("project_description_placeholder")}
|
||||
onChange={onChange}
|
||||
className="min-h-[102px] text-13 font-medium"
|
||||
className="nodedc-settings-input min-h-[102px] !rounded-[1.35rem] text-13 font-medium"
|
||||
hasError={Boolean(errors?.description)}
|
||||
disabled={!isAdmin}
|
||||
/>
|
||||
|
|
@ -342,7 +343,7 @@ export function ProjectDetailsForm(props: IProjectDetailsForm) {
|
|||
ref={ref}
|
||||
hasError={Boolean(errors.identifier)}
|
||||
placeholder={t("project_settings.general.enter_project_id")}
|
||||
className="w-full font-medium"
|
||||
className="nodedc-settings-input w-full pr-10 font-medium"
|
||||
disabled={!isAdmin}
|
||||
/>
|
||||
)}
|
||||
|
|
@ -383,7 +384,7 @@ export function ProjectDetailsForm(props: IProjectDetailsForm) {
|
|||
)}
|
||||
</div>
|
||||
}
|
||||
buttonClassName="!border-subtle !shadow-none font-medium rounded-md"
|
||||
buttonClassName="nodedc-settings-select !h-12 font-medium"
|
||||
input
|
||||
disabled={!isAdmin}
|
||||
// optionsClassName="w-full"
|
||||
|
|
@ -418,7 +419,7 @@ export function ProjectDetailsForm(props: IProjectDetailsForm) {
|
|||
onChange(value);
|
||||
}}
|
||||
error={Boolean(errors.timezone)}
|
||||
buttonClassName="border-none"
|
||||
buttonClassName="nodedc-settings-select !h-12 !border-0"
|
||||
disabled={!isAdmin}
|
||||
/>
|
||||
</>
|
||||
|
|
@ -429,7 +430,14 @@ export function ProjectDetailsForm(props: IProjectDetailsForm) {
|
|||
</div>
|
||||
<div className="flex items-center justify-between py-2">
|
||||
<>
|
||||
<Button variant="primary" size="lg" type="submit" loading={isLoading} disabled={!isAdmin}>
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="lg"
|
||||
type="submit"
|
||||
loading={isLoading}
|
||||
disabled={!isAdmin}
|
||||
className="nodedc-settings-save-button min-w-[11.5rem]"
|
||||
>
|
||||
{isLoading ? t("updating") : t("common.update_project")}
|
||||
</Button>
|
||||
<span className="text-13 text-placeholder italic">
|
||||
|
|
|
|||
|
|
@ -84,11 +84,11 @@ export const ProjectMemberList = observer(function ProjectMemberList(props: TPro
|
|||
<div className="flex items-center justify-between gap-4 overflow-x-hidden border-b border-subtle py-2">
|
||||
<div className="text-14 font-semibold">{t("common.members")}</div>
|
||||
<div className="flex items-center gap-2">
|
||||
<div className="flex items-center justify-start gap-1.5 rounded-md border border-subtle bg-surface-1 px-2 py-1">
|
||||
<div className="nodedc-settings-field flex min-h-[2.75rem] items-center justify-start gap-1.5 px-3">
|
||||
<SearchIcon className="h-3.5 w-3.5" />
|
||||
<input
|
||||
className="w-full max-w-[234px] border-none bg-transparent text-13 placeholder:text-placeholder focus:outline-none"
|
||||
placeholder="Search"
|
||||
placeholder={t("search")}
|
||||
value={searchQuery}
|
||||
autoFocus
|
||||
onChange={(e) => setSearchQuery(e.target.value)}
|
||||
|
|
@ -101,11 +101,12 @@ export const ProjectMemberList = observer(function ProjectMemberList(props: TPro
|
|||
/>
|
||||
{isAdmin && (
|
||||
<Button
|
||||
variant="primary"
|
||||
variant="ghost"
|
||||
onClick={() => {
|
||||
setInviteModal(true);
|
||||
}}
|
||||
data-ph-element={MEMBER_TRACKER_ELEMENTS.HEADER_ADD_BUTTON}
|
||||
className="nodedc-settings-primary-button min-w-[11rem]"
|
||||
>
|
||||
{t("add_member")}
|
||||
</Button>
|
||||
|
|
|
|||
|
|
@ -8,6 +8,7 @@ import React from "react";
|
|||
import { observer } from "mobx-react";
|
||||
import { useParams } from "next/navigation";
|
||||
import { Ban } from "lucide-react";
|
||||
import { useTranslation } from "@plane/i18n";
|
||||
import { EUserProjectRoles } from "@plane/types";
|
||||
// plane ui
|
||||
import { Avatar, CustomSearchSelect } from "@plane/ui";
|
||||
|
|
@ -24,6 +25,7 @@ type Props = {
|
|||
|
||||
export const MemberSelect = observer(function MemberSelect(props: Props) {
|
||||
const { value, onChange, isDisabled = false } = props;
|
||||
const { t } = useTranslation();
|
||||
// router
|
||||
const { projectId } = useParams();
|
||||
// store hooks
|
||||
|
|
@ -72,12 +74,12 @@ export const MemberSelect = observer(function MemberSelect(props: Props) {
|
|||
) : (
|
||||
<div className="flex items-center gap-2">
|
||||
<Ban className="h-3.5 w-3.5 rotate-90 text-placeholder" />
|
||||
<span className="text-13 text-placeholder">None</span>
|
||||
<span className="text-13 text-placeholder">{t("none")}</span>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
}
|
||||
buttonClassName="!px-3 !py-2 bg-surface-1"
|
||||
buttonClassName="nodedc-settings-select !w-full !justify-between !px-4 !py-3"
|
||||
options={
|
||||
options &&
|
||||
options && [
|
||||
|
|
@ -88,7 +90,7 @@ export const MemberSelect = observer(function MemberSelect(props: Props) {
|
|||
content: (
|
||||
<div className="flex items-center gap-2">
|
||||
<Ban className="h-3.5 w-3.5 rotate-90 text-placeholder" />
|
||||
<span className="py-0.5 text-13 text-placeholder">None</span>
|
||||
<span className="py-0.5 text-13 text-placeholder">{t("none")}</span>
|
||||
</div>
|
||||
),
|
||||
},
|
||||
|
|
|
|||
|
|
@ -8,7 +8,7 @@ import { observer } from "mobx-react";
|
|||
import Link from "next/link";
|
||||
import { useTranslation } from "@plane/i18n";
|
||||
// ui
|
||||
import { Button, getButtonStyling } from "@plane/propel/button";
|
||||
import { Button } from "@plane/propel/button";
|
||||
import { Logo } from "@plane/propel/emoji-icon-picker";
|
||||
import { Row } from "@plane/ui";
|
||||
// components
|
||||
|
|
@ -35,7 +35,7 @@ export const ProjectFeatureUpdate = observer(function ProjectFeatureUpdate(props
|
|||
|
||||
return (
|
||||
<>
|
||||
<Row className="py-6">
|
||||
<Row className="px-6 py-6">
|
||||
<ProjectFeaturesList workspaceSlug={workspaceSlug} projectId={projectId} isAdmin />
|
||||
</Row>
|
||||
<div className="mt-4 flex items-center justify-between gap-2 border-t border-subtle px-6 py-4">
|
||||
|
|
@ -45,13 +45,19 @@ export const ProjectFeatureUpdate = observer(function ProjectFeatureUpdate(props
|
|||
{t("created").toLowerCase()}.
|
||||
</div>
|
||||
<div className="flex gap-2">
|
||||
<Button variant="secondary" size="lg" onClick={onClose} tabIndex={1}>
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="lg"
|
||||
onClick={onClose}
|
||||
tabIndex={1}
|
||||
className="nodedc-modal-secondary-button min-w-[8.75rem]"
|
||||
>
|
||||
{t("close")}
|
||||
</Button>
|
||||
<Link
|
||||
href={`/${workspaceSlug}/projects/${projectId}/issues`}
|
||||
onClick={onClose}
|
||||
className={getButtonStyling("primary", "lg")}
|
||||
className="nodedc-modal-primary-button inline-flex min-w-[10.5rem] items-center justify-center"
|
||||
tabIndex={2}
|
||||
>
|
||||
{t("open_project")}
|
||||
|
|
|
|||
|
|
@ -36,9 +36,9 @@ type TDefaultSettingItemProps = {
|
|||
|
||||
function DefaultSettingItem({ title, description, children }: TDefaultSettingItemProps) {
|
||||
return (
|
||||
<div className="flex items-center justify-between gap-x-2">
|
||||
<div className="flex flex-col gap-0.5">
|
||||
<h4 className="text-13 font-medium">{title}</h4>
|
||||
<div className="nodedc-settings-card flex items-center justify-between gap-x-6 px-5 py-4">
|
||||
<div className="flex flex-col gap-1">
|
||||
<h4 className="text-13 font-medium text-primary">{title}</h4>
|
||||
<p className="text-11 text-tertiary">{description}</p>
|
||||
</div>
|
||||
<div className="w-full max-w-48 sm:max-w-64">{children}</div>
|
||||
|
|
@ -139,7 +139,10 @@ export const ProjectSettingsMemberDefaults = observer(function ProjectSettingsMe
|
|||
|
||||
return (
|
||||
<div className="my-6 flex flex-col gap-y-6">
|
||||
<DefaultSettingItem title="Project Lead" description="Select the project lead for the project.">
|
||||
<DefaultSettingItem
|
||||
title={t("project_settings.members.project_lead")}
|
||||
description={t("project_settings.members.project_lead_description")}
|
||||
>
|
||||
{currentProjectDetails ? (
|
||||
<Controller
|
||||
control={control}
|
||||
|
|
@ -160,7 +163,10 @@ export const ProjectSettingsMemberDefaults = observer(function ProjectSettingsMe
|
|||
</Loader>
|
||||
)}
|
||||
</DefaultSettingItem>
|
||||
<DefaultSettingItem title="Default Assignee" description="Select the default assignee for the project.">
|
||||
<DefaultSettingItem
|
||||
title={t("project_settings.members.default_assignee")}
|
||||
description={t("project_settings.members.default_assignee_description")}
|
||||
>
|
||||
{currentProjectDetails ? (
|
||||
<Controller
|
||||
control={control}
|
||||
|
|
@ -183,8 +189,8 @@ export const ProjectSettingsMemberDefaults = observer(function ProjectSettingsMe
|
|||
</DefaultSettingItem>
|
||||
{currentProjectDetails && (
|
||||
<DefaultSettingItem
|
||||
title="Guest access"
|
||||
description="This will allow guests to have view access to all the project work items."
|
||||
title={t("project_settings.members.guest_super_permissions.title")}
|
||||
description={t("project_settings.members.guest_super_permissions.sub_heading")}
|
||||
>
|
||||
<div className="flex items-center justify-end">
|
||||
<ToggleSwitch
|
||||
|
|
|
|||
|
|
@ -55,28 +55,29 @@ export const GeneralProjectSettingsControlSection = observer(function GeneralPro
|
|||
isOpen={Boolean(selectProject)}
|
||||
onClose={() => setSelectedProject(null)}
|
||||
/>
|
||||
<div className="rounded-lg border border-subtle bg-layer-2">
|
||||
{/* Project Selector */}
|
||||
<div className="flex flex-col gap-1.5">
|
||||
<SettingsBoxedControlItem
|
||||
className="rounded-b-none border-0 border-b"
|
||||
title={t("archive")}
|
||||
description={t("project_settings_control.archive_description")}
|
||||
control={
|
||||
<Button variant="secondary" onClick={() => setArchiveProject(true)}>
|
||||
<Button
|
||||
variant="ghost"
|
||||
onClick={() => setArchiveProject(true)}
|
||||
className="nodedc-settings-secondary-button"
|
||||
>
|
||||
{t("archive")}
|
||||
</Button>
|
||||
}
|
||||
/>
|
||||
{/* Format Selector */}
|
||||
<SettingsBoxedControlItem
|
||||
className="rounded-t-none border-0"
|
||||
title={t("delete")}
|
||||
description={t("project_settings_control.delete_description")}
|
||||
control={
|
||||
<Button
|
||||
variant="error-outline"
|
||||
variant="ghost"
|
||||
onClick={() => setSelectedProject(currentProjectDetails.id ?? null)}
|
||||
data-ph-element={PROJECT_TRACKER_ELEMENTS.DELETE_PROJECT_BUTTON}
|
||||
className="nodedc-settings-danger-button"
|
||||
>
|
||||
{t("delete")}
|
||||
</Button>
|
||||
|
|
|
|||
|
|
@ -20,7 +20,7 @@ export function SettingsBoxedControlItem(props: Props) {
|
|||
return (
|
||||
<div
|
||||
className={cn(
|
||||
"flex w-full flex-col items-start gap-4 rounded-lg border border-subtle bg-layer-2 px-4 py-3 md:flex-row md:items-center md:justify-between md:gap-8",
|
||||
"nodedc-settings-card flex w-full flex-col items-start gap-4 px-5 py-4 md:flex-row md:items-center md:justify-between md:gap-8",
|
||||
className
|
||||
)}
|
||||
>
|
||||
|
|
|
|||
|
|
@ -0,0 +1,7 @@
|
|||
{
|
||||
"nodedc": {
|
||||
"accent_rgb": [195, 255, 102],
|
||||
"passive_card_rgb": [42, 43, 46],
|
||||
"active_card_rgb": [195, 255, 102]
|
||||
}
|
||||
}
|
||||
|
|
@ -1,102 +1,33 @@
|
|||
/**
|
||||
* Copyright 2018 Google Inc. All Rights Reserved.
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
/*
|
||||
* Temporary cleanup service worker.
|
||||
*
|
||||
* We intentionally stop intercepting requests, clear legacy caches, and
|
||||
* unregister ourselves. This prevents stale HTML/chunk bundles from surviving
|
||||
* fresh web deployments and causing client-side hydration/runtime crashes.
|
||||
*/
|
||||
|
||||
// If the loader is already loaded, just stop.
|
||||
if (!self.define) {
|
||||
let registry = {};
|
||||
self.addEventListener("install", (event) => {
|
||||
event.waitUntil(self.skipWaiting());
|
||||
});
|
||||
|
||||
// Used for `eval` and `importScripts` where we can't get script URL by other means.
|
||||
// In both cases, it's safe to use a global var because those functions are synchronous.
|
||||
let nextDefineUri;
|
||||
self.addEventListener("activate", (event) => {
|
||||
event.waitUntil(
|
||||
(async () => {
|
||||
const cacheKeys = await caches.keys();
|
||||
await Promise.all(cacheKeys.map((cacheKey) => caches.delete(cacheKey)));
|
||||
|
||||
const singleRequire = (uri, parentUri) => {
|
||||
uri = new URL(uri + ".js", parentUri).href;
|
||||
return (
|
||||
registry[uri] ||
|
||||
new Promise((resolve) => {
|
||||
if ("document" in self) {
|
||||
const script = document.createElement("script");
|
||||
script.src = uri;
|
||||
script.onload = resolve;
|
||||
document.head.appendChild(script);
|
||||
} else {
|
||||
nextDefineUri = uri;
|
||||
importScripts(uri);
|
||||
resolve();
|
||||
}
|
||||
}).then(() => {
|
||||
let promise = registry[uri];
|
||||
if (!promise) {
|
||||
throw new Error(`Module ${uri} didn’t register its module`);
|
||||
}
|
||||
return promise;
|
||||
})
|
||||
const clients = await self.clients.matchAll({ type: "window", includeUncontrolled: true });
|
||||
await Promise.all(
|
||||
clients.map((client) =>
|
||||
"navigate" in client && typeof client.navigate === "function" ? client.navigate(client.url) : Promise.resolve()
|
||||
)
|
||||
);
|
||||
};
|
||||
|
||||
self.define = (depsNames, factory) => {
|
||||
const uri = nextDefineUri || ("document" in self ? document.currentScript.src : "") || location.href;
|
||||
if (registry[uri]) {
|
||||
// Module is already loading or loaded.
|
||||
return;
|
||||
}
|
||||
let exports = {};
|
||||
const require = (depUri) => singleRequire(depUri, uri);
|
||||
const specialDeps = {
|
||||
module: { uri },
|
||||
exports,
|
||||
require,
|
||||
};
|
||||
registry[uri] = Promise.all(depsNames.map((depName) => specialDeps[depName] || require(depName))).then((deps) => {
|
||||
factory(...deps);
|
||||
return exports;
|
||||
});
|
||||
};
|
||||
}
|
||||
define(["./workbox-9f2f79cf"], function (workbox) {
|
||||
"use strict";
|
||||
|
||||
importScripts();
|
||||
self.skipWaiting();
|
||||
workbox.clientsClaim();
|
||||
workbox.registerRoute(
|
||||
"/",
|
||||
new workbox.NetworkFirst({
|
||||
cacheName: "start-url",
|
||||
plugins: [
|
||||
{
|
||||
cacheWillUpdate: async ({ request, response, event, state }) => {
|
||||
if (response && response.type === "opaqueredirect") {
|
||||
return new Response(response.body, {
|
||||
status: 200,
|
||||
statusText: "OK",
|
||||
headers: response.headers,
|
||||
});
|
||||
}
|
||||
return response;
|
||||
},
|
||||
},
|
||||
],
|
||||
}),
|
||||
"GET"
|
||||
);
|
||||
workbox.registerRoute(
|
||||
/.*/i,
|
||||
new workbox.NetworkOnly({
|
||||
cacheName: "dev",
|
||||
plugins: [],
|
||||
}),
|
||||
"GET"
|
||||
await self.registration.unregister();
|
||||
})()
|
||||
);
|
||||
});
|
||||
//# sourceMappingURL=sw.js.map
|
||||
|
||||
self.addEventListener("fetch", () => {
|
||||
// Intentionally empty: do not cache or intercept runtime requests.
|
||||
});
|
||||
|
|
|
|||
|
|
@ -30,6 +30,9 @@
|
|||
--nodedc-accent-rgb: 51 163 255;
|
||||
--nodedc-card-passive-rgb: 42 43 46;
|
||||
--nodedc-card-active-rgb: 195 255 102;
|
||||
--brand-default: rgb(var(--nodedc-accent-rgb));
|
||||
--brand-300: color-mix(in srgb, rgb(var(--nodedc-accent-rgb)) 35%, white);
|
||||
--brand-700: color-mix(in srgb, rgb(var(--nodedc-accent-rgb)) 75%, black);
|
||||
/* end background colors */
|
||||
}
|
||||
/* background colors */
|
||||
|
|
@ -419,7 +422,7 @@
|
|||
box-shadow: none !important;
|
||||
border-radius: 999px !important;
|
||||
min-height: 2.5rem;
|
||||
padding-inline: 1rem;
|
||||
padding-inline: 1.35rem;
|
||||
background: rgba(18, 18, 22, 0.94) !important;
|
||||
color: var(--text-color-primary) !important;
|
||||
transition:
|
||||
|
|
@ -428,6 +431,11 @@
|
|||
transform 160ms ease;
|
||||
}
|
||||
|
||||
.nodedc-toolbar-pill-wide {
|
||||
min-width: 9.75rem;
|
||||
padding-inline: 1.75rem;
|
||||
}
|
||||
|
||||
.nodedc-toolbar-pill:hover {
|
||||
background: rgba(24, 24, 29, 0.96) !important;
|
||||
}
|
||||
|
|
@ -442,13 +450,261 @@
|
|||
box-shadow: none !important;
|
||||
border-radius: 999px !important;
|
||||
min-height: 2.5rem;
|
||||
padding-inline: 1rem;
|
||||
padding-inline: 1.55rem;
|
||||
background: rgb(var(--nodedc-accent-rgb)) !important;
|
||||
color: #0b1117 !important;
|
||||
}
|
||||
|
||||
.nodedc-toolbar-primary-wide {
|
||||
min-width: 13rem;
|
||||
padding-inline: 2rem;
|
||||
}
|
||||
|
||||
.nodedc-toolbar-primary:hover {
|
||||
background: rgba(var(--nodedc-accent-rgb), 0.92) !important;
|
||||
background: color-mix(in srgb, rgb(var(--nodedc-accent-rgb)) 82%, white) !important;
|
||||
}
|
||||
|
||||
.nodedc-modal-secondary-button {
|
||||
min-height: 2.75rem;
|
||||
border: 0 !important;
|
||||
outline: none !important;
|
||||
box-shadow: none !important;
|
||||
border-radius: 1.25rem !important;
|
||||
background: rgba(255, 255, 255, 0.06) !important;
|
||||
color: var(--text-color-primary) !important;
|
||||
padding-inline: 1.25rem !important;
|
||||
}
|
||||
|
||||
.nodedc-modal-secondary-button:hover {
|
||||
background: rgba(255, 255, 255, 0.1) !important;
|
||||
}
|
||||
|
||||
.nodedc-modal-primary-button {
|
||||
min-height: 2.75rem;
|
||||
border: 0 !important;
|
||||
outline: none !important;
|
||||
box-shadow: none !important;
|
||||
border-radius: 1.25rem !important;
|
||||
background: rgb(var(--nodedc-card-active-rgb)) !important;
|
||||
color: #0b1117 !important;
|
||||
padding-inline: 1.25rem !important;
|
||||
}
|
||||
|
||||
.nodedc-modal-primary-button:hover {
|
||||
background: color-mix(in srgb, rgb(var(--nodedc-card-active-rgb)) 82%, white) !important;
|
||||
}
|
||||
|
||||
.nodedc-modal-danger-button {
|
||||
min-height: 2.75rem;
|
||||
border: 0 !important;
|
||||
outline: none !important;
|
||||
box-shadow: none !important;
|
||||
border-radius: 1.25rem !important;
|
||||
padding-inline: 1.25rem !important;
|
||||
}
|
||||
|
||||
.nodedc-modal-chip {
|
||||
min-height: 2.5rem;
|
||||
border: 0 !important;
|
||||
outline: none !important;
|
||||
box-shadow: none !important;
|
||||
border-radius: 1.25rem !important;
|
||||
background:
|
||||
linear-gradient(180deg, rgba(255, 255, 255, 0.03) 0%, rgba(255, 255, 255, 0.012) 100%),
|
||||
rgba(255, 255, 255, 0.028) !important;
|
||||
color: var(--text-color-secondary) !important;
|
||||
padding-inline: 1rem !important;
|
||||
}
|
||||
|
||||
.nodedc-modal-chip:hover {
|
||||
background:
|
||||
linear-gradient(180deg, rgba(255, 255, 255, 0.038) 0%, rgba(255, 255, 255, 0.016) 100%),
|
||||
rgba(255, 255, 255, 0.04) !important;
|
||||
}
|
||||
|
||||
.nodedc-settings-card {
|
||||
background:
|
||||
linear-gradient(180deg, rgba(255, 255, 255, 0.026) 0%, rgba(255, 255, 255, 0.01) 100%),
|
||||
rgba(255, 255, 255, 0.032);
|
||||
border: 0 !important;
|
||||
outline: none !important;
|
||||
box-shadow: none !important;
|
||||
border-radius: 1.35rem !important;
|
||||
backdrop-filter: blur(18px);
|
||||
-webkit-backdrop-filter: blur(18px);
|
||||
}
|
||||
|
||||
.nodedc-settings-field {
|
||||
background:
|
||||
linear-gradient(180deg, rgba(255, 255, 255, 0.028) 0%, rgba(255, 255, 255, 0.012) 100%),
|
||||
rgba(255, 255, 255, 0.03) !important;
|
||||
border: 0 !important;
|
||||
outline: none !important;
|
||||
box-shadow: none !important;
|
||||
border-radius: 1.25rem !important;
|
||||
-webkit-backdrop-filter: blur(18px);
|
||||
backdrop-filter: blur(18px);
|
||||
transition: background 160ms ease;
|
||||
}
|
||||
|
||||
.nodedc-settings-field:hover,
|
||||
.nodedc-settings-field:focus-within {
|
||||
background:
|
||||
linear-gradient(180deg, rgba(255, 255, 255, 0.038) 0%, rgba(255, 255, 255, 0.016) 100%),
|
||||
rgba(255, 255, 255, 0.042) !important;
|
||||
}
|
||||
|
||||
.nodedc-settings-input {
|
||||
background:
|
||||
linear-gradient(180deg, rgba(255, 255, 255, 0.028) 0%, rgba(255, 255, 255, 0.012) 100%),
|
||||
rgba(255, 255, 255, 0.03) !important;
|
||||
border: 0 !important;
|
||||
outline: none !important;
|
||||
box-shadow: none !important;
|
||||
border-radius: 1.25rem !important;
|
||||
min-height: 3rem;
|
||||
color: var(--text-color-primary) !important;
|
||||
font-size: 0.875rem !important;
|
||||
-webkit-backdrop-filter: blur(18px);
|
||||
backdrop-filter: blur(18px);
|
||||
}
|
||||
|
||||
.nodedc-settings-input:focus,
|
||||
.nodedc-settings-input:focus-visible {
|
||||
outline: none !important;
|
||||
box-shadow: none !important;
|
||||
}
|
||||
|
||||
.nodedc-settings-select {
|
||||
min-height: 3rem !important;
|
||||
border: 0 !important;
|
||||
outline: none !important;
|
||||
box-shadow: none !important;
|
||||
border-radius: 1.25rem !important;
|
||||
background:
|
||||
linear-gradient(180deg, rgba(255, 255, 255, 0.028) 0%, rgba(255, 255, 255, 0.012) 100%),
|
||||
rgba(255, 255, 255, 0.03) !important;
|
||||
color: var(--text-color-primary) !important;
|
||||
-webkit-backdrop-filter: blur(18px);
|
||||
backdrop-filter: blur(18px);
|
||||
}
|
||||
|
||||
.nodedc-settings-select:hover,
|
||||
.nodedc-settings-select:focus-visible {
|
||||
background:
|
||||
linear-gradient(180deg, rgba(255, 255, 255, 0.038) 0%, rgba(255, 255, 255, 0.016) 100%),
|
||||
rgba(255, 255, 255, 0.042) !important;
|
||||
}
|
||||
|
||||
.nodedc-settings-chip {
|
||||
min-height: 2.75rem;
|
||||
border: 0 !important;
|
||||
outline: none !important;
|
||||
box-shadow: none !important;
|
||||
border-radius: 1.25rem !important;
|
||||
background:
|
||||
linear-gradient(180deg, rgba(255, 255, 255, 0.028) 0%, rgba(255, 255, 255, 0.012) 100%),
|
||||
rgba(255, 255, 255, 0.03) !important;
|
||||
color: var(--text-color-primary) !important;
|
||||
padding-inline: 1rem !important;
|
||||
-webkit-backdrop-filter: blur(18px);
|
||||
backdrop-filter: blur(18px);
|
||||
}
|
||||
|
||||
.nodedc-settings-chip:hover {
|
||||
background:
|
||||
linear-gradient(180deg, rgba(255, 255, 255, 0.038) 0%, rgba(255, 255, 255, 0.016) 100%),
|
||||
rgba(255, 255, 255, 0.042) !important;
|
||||
}
|
||||
|
||||
.nodedc-settings-primary-button {
|
||||
min-height: 2.75rem;
|
||||
border: 0 !important;
|
||||
outline: none !important;
|
||||
box-shadow: none !important;
|
||||
border-radius: 1.25rem !important;
|
||||
background: rgb(var(--nodedc-card-active-rgb)) !important;
|
||||
color: #0b1117 !important;
|
||||
padding-inline: 1.35rem !important;
|
||||
}
|
||||
|
||||
.nodedc-settings-primary-button:hover {
|
||||
background: color-mix(in srgb, rgb(var(--nodedc-card-active-rgb)) 82%, white) !important;
|
||||
color: #0b1117 !important;
|
||||
}
|
||||
|
||||
.nodedc-settings-save-button {
|
||||
min-height: 2.75rem;
|
||||
border: 0 !important;
|
||||
outline: none !important;
|
||||
box-shadow: none !important;
|
||||
border-radius: 1.25rem !important;
|
||||
background: rgb(var(--nodedc-card-active-rgb)) !important;
|
||||
color: #0b1117 !important;
|
||||
padding-inline: 1.45rem !important;
|
||||
}
|
||||
|
||||
.nodedc-settings-save-button:hover {
|
||||
background: color-mix(in srgb, rgb(var(--nodedc-card-active-rgb)) 82%, white) !important;
|
||||
color: #0b1117 !important;
|
||||
}
|
||||
|
||||
.nodedc-overlay-button {
|
||||
min-height: 2.5rem;
|
||||
border: 0 !important;
|
||||
outline: none !important;
|
||||
box-shadow: none !important;
|
||||
border-radius: 1.05rem !important;
|
||||
background:
|
||||
linear-gradient(180deg, rgba(255, 255, 255, 0.05) 0%, rgba(255, 255, 255, 0.018) 100%),
|
||||
rgba(8, 8, 10, 0.58) !important;
|
||||
color: #f5f7fb !important;
|
||||
padding-inline: 1rem !important;
|
||||
-webkit-backdrop-filter: blur(18px);
|
||||
backdrop-filter: blur(18px);
|
||||
transition:
|
||||
background 120ms ease,
|
||||
color 120ms ease,
|
||||
transform 120ms ease;
|
||||
}
|
||||
|
||||
.nodedc-overlay-button:hover {
|
||||
background:
|
||||
linear-gradient(180deg, rgba(255, 255, 255, 0.075) 0%, rgba(255, 255, 255, 0.03) 100%),
|
||||
rgba(8, 8, 10, 0.72) !important;
|
||||
color: #ffffff !important;
|
||||
}
|
||||
|
||||
.nodedc-settings-secondary-button {
|
||||
min-height: 2.75rem;
|
||||
border: 0 !important;
|
||||
outline: none !important;
|
||||
box-shadow: none !important;
|
||||
border-radius: 1.25rem !important;
|
||||
background: rgba(255, 255, 255, 0.06) !important;
|
||||
color: var(--text-color-primary) !important;
|
||||
padding-inline: 1.25rem !important;
|
||||
}
|
||||
|
||||
.nodedc-settings-secondary-button:hover {
|
||||
background: rgba(255, 255, 255, 0.1) !important;
|
||||
color: var(--text-color-primary) !important;
|
||||
}
|
||||
|
||||
.nodedc-settings-danger-button {
|
||||
min-height: 2.75rem;
|
||||
border: 0 !important;
|
||||
outline: none !important;
|
||||
box-shadow: none !important;
|
||||
border-radius: 1.25rem !important;
|
||||
background: rgba(255, 82, 82, 0.14) !important;
|
||||
color: rgb(255, 141, 141) !important;
|
||||
padding-inline: 1.25rem !important;
|
||||
}
|
||||
|
||||
.nodedc-settings-danger-button:hover {
|
||||
background: rgba(255, 82, 82, 0.2) !important;
|
||||
color: rgb(255, 162, 162) !important;
|
||||
}
|
||||
|
||||
.nodedc-toolbar-filter-toggle {
|
||||
|
|
|
|||
|
|
@ -916,8 +916,11 @@ export default {
|
|||
priority: "{entity} Priority",
|
||||
all: "All {entity}",
|
||||
drop_here_to_move: "Drop here to move the {entity}",
|
||||
drop_here_to_delete: "Drop here to delete the {entity}",
|
||||
delete: {
|
||||
label: "Delete {entity}",
|
||||
confirmation:
|
||||
"Are you sure you want to delete {entity} {identifier}? All related data will be permanently removed and this action cannot be undone.",
|
||||
success: "{entity} deleted successfully",
|
||||
failed: "{entity} delete failed",
|
||||
},
|
||||
|
|
@ -1865,7 +1868,9 @@ export default {
|
|||
members: {
|
||||
label: "Members",
|
||||
project_lead: "Project lead",
|
||||
project_lead_description: "Choose the project lead.",
|
||||
default_assignee: "Default assignee",
|
||||
default_assignee_description: "Choose the default assignee for the project.",
|
||||
guest_super_permissions: {
|
||||
title: "Grant view access to all work items for guest users:",
|
||||
sub_heading: "This will allow guests to have view access to all the project work items.",
|
||||
|
|
@ -1908,6 +1913,9 @@ export default {
|
|||
label: "Estimates",
|
||||
title: "Enable estimates for my project",
|
||||
enable_description: "They help you in communicating complexity and workload of the team.",
|
||||
list_heading: "Estimate list",
|
||||
archived_heading: "Archived estimates",
|
||||
archived_description: "These are estimates from earlier project versions that are not currently in use. Read more",
|
||||
no_estimate: "No estimate",
|
||||
new: "New estimate system",
|
||||
create: {
|
||||
|
|
|
|||
|
|
@ -1072,8 +1072,11 @@ export default {
|
|||
priority: "Приоритет {entity}",
|
||||
all: "Все {entity}",
|
||||
drop_here_to_move: "Переместите {entity} сюда",
|
||||
drop_here_to_delete: "Перетащите сюда, чтобы удалить {entity}",
|
||||
delete: {
|
||||
label: "Удалить {entity}",
|
||||
confirmation:
|
||||
"Вы уверены, что хотите удалить {entity} {identifier}? Все связанные данные будут удалены без возможности восстановления.",
|
||||
success: "{entity} успешно удалён",
|
||||
failed: "Ошибка удаления {entity}",
|
||||
},
|
||||
|
|
@ -2024,7 +2027,9 @@ export default {
|
|||
members: {
|
||||
label: "Участники",
|
||||
project_lead: "Руководитель проекта",
|
||||
project_lead_description: "Выберите руководителя проекта.",
|
||||
default_assignee: "Ответственный по умолчанию",
|
||||
default_assignee_description: "Выберите ответственного по умолчанию для проекта.",
|
||||
guest_super_permissions: {
|
||||
title: "Дать гостям доступ на просмотр всех рабочих элементов:",
|
||||
sub_heading: "Гости смогут просматривать все рабочие элементы проекта",
|
||||
|
|
@ -2067,6 +2072,9 @@ export default {
|
|||
label: "Оценки",
|
||||
title: "Включить оценки для моего проекта",
|
||||
enable_description: "Они помогают вам в общении о сложности и рабочей нагрузке команды.",
|
||||
list_heading: "Список оценок",
|
||||
archived_heading: "Архивные оценки",
|
||||
archived_description: "Это оценки из предыдущих версий проекта, которые сейчас не используются. Подробнее о них",
|
||||
no_estimate: "Без оценки",
|
||||
new: "Новая система оценок",
|
||||
create: {
|
||||
|
|
|
|||
|
|
@ -202,10 +202,10 @@ function CustomMenu(props: ICustomMenuDropdownProps) {
|
|||
className={cn(
|
||||
"nodedc-glass-modal nodedc-glass-popup-surface my-1 min-w-[13rem] overflow-y-auto rounded-[1.25rem] border-0 px-2 py-2.5 text-12 whitespace-nowrap shadow-none outline-none",
|
||||
{
|
||||
"max-h-60": maxHeight === "lg",
|
||||
"max-h-48": maxHeight === "md",
|
||||
"max-h-36": maxHeight === "rg",
|
||||
"max-h-28": maxHeight === "sm",
|
||||
"max-h-[min(85vh,40rem)]": maxHeight === "lg",
|
||||
"max-h-[min(70vh,24rem)]": maxHeight === "md",
|
||||
"max-h-[min(55vh,18rem)]": maxHeight === "rg",
|
||||
"max-h-[min(40vh,12rem)]": maxHeight === "sm",
|
||||
},
|
||||
optionsClassName
|
||||
)}
|
||||
|
|
|
|||
|
|
@ -122,7 +122,7 @@ function CustomSelect(props: ICustomSelectProps) {
|
|||
<Combobox.Options data-prevent-outside-click>
|
||||
<div
|
||||
className={cn(
|
||||
"z-30 my-1 min-w-48 overflow-y-scroll rounded-md border-[0.5px] border-subtle-1 bg-surface-1 px-2 py-2.5 text-11 whitespace-nowrap focus:outline-none",
|
||||
"nodedc-dropdown-surface z-30 my-1 min-w-48 overflow-y-scroll whitespace-nowrap focus:outline-none",
|
||||
optionsClassName
|
||||
)}
|
||||
ref={setPopperElement}
|
||||
|
|
@ -166,9 +166,9 @@ function Option(props: ICustomSelectItemProps) {
|
|||
value={value}
|
||||
className={({ active }) =>
|
||||
cn(
|
||||
"flex cursor-pointer items-center justify-between gap-2 truncate rounded-sm px-1 py-1.5 text-secondary select-none",
|
||||
"nodedc-dropdown-option cursor-pointer text-secondary",
|
||||
{
|
||||
"bg-layer-transparent-hover": active,
|
||||
"bg-white/6": active,
|
||||
},
|
||||
className
|
||||
)
|
||||
|
|
|
|||
|
|
@ -75,8 +75,14 @@ export function AlertModalCore(props: Props) {
|
|||
const Icon = VARIANT_ICONS[variant];
|
||||
|
||||
return (
|
||||
<ModalCore isOpen={isOpen} handleClose={handleClose} position={position} width={width}>
|
||||
<div className="flex flex-col items-center gap-4 p-5 sm:flex-row sm:items-start">
|
||||
<ModalCore
|
||||
isOpen={isOpen}
|
||||
handleClose={handleClose}
|
||||
position={position}
|
||||
width={width}
|
||||
className="transition-[width] ease-linear"
|
||||
>
|
||||
<div className="flex flex-col items-center gap-4 px-6 pt-6 pb-5 sm:flex-row sm:items-start">
|
||||
{!hideIcon && (
|
||||
<span
|
||||
className={cn(
|
||||
|
|
@ -88,15 +94,24 @@ export function AlertModalCore(props: Props) {
|
|||
</span>
|
||||
)}
|
||||
<div className="text-center sm:text-left">
|
||||
<h3 className="text-16 font-medium">{title}</h3>
|
||||
<h3 className="text-18 font-medium text-secondary">{title}</h3>
|
||||
<p className="mt-1 text-13 text-secondary">{content}</p>
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex flex-col-reverse gap-2 border-t-[0.5px] border-subtle px-5 py-4 sm:flex-row sm:justify-end">
|
||||
<Button variant="secondary" onClick={handleClose}>
|
||||
<div className="flex flex-col-reverse gap-3 border-t border-subtle/70 px-6 py-4 sm:flex-row sm:justify-end">
|
||||
<Button variant="secondary" onClick={handleClose} className="nodedc-modal-secondary-button min-w-[8.25rem]">
|
||||
{secondaryButtonText}
|
||||
</Button>
|
||||
<Button variant={BUTTON_VARIANTS[variant]} tabIndex={1} onClick={handleSubmit} loading={isSubmitting}>
|
||||
<Button
|
||||
variant={BUTTON_VARIANTS[variant]}
|
||||
tabIndex={1}
|
||||
onClick={handleSubmit}
|
||||
loading={isSubmitting}
|
||||
className={cn("min-w-[8.25rem]", {
|
||||
"nodedc-modal-danger-button": variant === "danger",
|
||||
"nodedc-modal-primary-button": variant === "primary",
|
||||
})}
|
||||
>
|
||||
{isSubmitting ? primaryButtonText.loading : primaryButtonText.default}
|
||||
</Button>
|
||||
</div>
|
||||
|
|
|
|||
Loading…
Reference in New Issue