diff --git a/HDESIGN-CODE.md b/HDESIGN-CODE.md index 6961496..368a29d 100644 --- a/HDESIGN-CODE.md +++ b/HDESIGN-CODE.md @@ -43,8 +43,15 @@ - Все кнопки без жёсткого outline. - Primary button: - фон: акцентный или `active_card_rgb` - - текст: чёрный или очень тёмный, если фон светлый + - текст: всегда чёрный или очень тёмный, если фон светлый - hover: более светлая версия того же цвета + - правило распространяется на все filled CTA: + - `Добавить` + - `Сохранить` + - `Обновить` + - `Принять` + - `Добавить запрос` + - любые акцентные toolbar-кнопки - Save/update button: - если это зафиксированный green CTA, текст должен быть контрастным и читаемым - hover осветляет текущий тон, а не уходит в синий @@ -93,6 +100,70 @@ - светлый фон, если основной экран тёмный - Search shell внутри popup должен использовать тот же стиль, что и сам popup. +### Reusable классы +- Accent CTA: + - `.nodedc-external-primary-button` + - текст внутри всегда `#0b1117` +- Secondary action: + - `.nodedc-external-action-button` +- Secondary icon action: + - `.nodedc-external-icon-button` +- Readonly property/control surface: + - `.nodedc-external-readonly-value` + - `.nodedc-modal-field` +- External property rows: + - `.nodedc-external-property-row` + - `.nodedc-external-property-label` + - `.nodedc-external-property-value` + - `.nodedc-external-property-control` +- Dropdown shell: + - `.nodedc-dropdown-surface` + - `.nodedc-dropdown-search` + - `.nodedc-dropdown-option` +- External contour card/shell: + - `.nodedc-external-card` + - `.nodedc-external-section` + - `.nodedc-external-content-shell` + +### Anchor snippets +```tsx + +``` + +```tsx + +``` + +```tsx +
+ + ... +
+``` + +```tsx +
+
+ + Label +
+
+ +
+
+``` + +```tsx + + + Value + + } +/> +``` + ## Drag and drop - Drag overlay использует акцентный контур. - Delete dropzone: @@ -109,3 +180,6 @@ - Сначала используется существующий shared-класс или shared-component. - Если shared-слоя нет, создаётся reusable-класс/компонент и уже через него приводятся все похожие места. - Цель: не точечная покраска одного окна, а единый системный канон. +- Если блок визуально расходится со стилем системы, не добавлять поверх временную wrapper-заплатку. Нужно либо перевести блок на shared-компонент, либо переверстать локальную структуру под shared-классы. +- Для экранов со вкладками/переключателями нельзя оставлять flash старой верстки. Перед refetch нужно очищать stale store-data и показывать loading shell. +- Если карточки или списки разных модулей должны быть одинаковыми по канону, нельзя лечить это внешней обёрткой. Нужно менять сам внутренний layout item-компонента. diff --git a/plane-src/apps/web/ce/components/projects/external-contours/empty-state.tsx b/plane-src/apps/web/ce/components/projects/external-contours/empty-state.tsx new file mode 100644 index 0000000..5419286 --- /dev/null +++ b/plane-src/apps/web/ce/components/projects/external-contours/empty-state.tsx @@ -0,0 +1,30 @@ +/** + * Copyright (c) 2023-present Plane Software, Inc. and contributors + * SPDX-License-Identifier: AGPL-3.0-only + * See the LICENSE file for details. + */ + +import { Layers3 } from "lucide-react"; +import { cn } from "@plane/utils"; + +type Props = { + title: string; + description?: string; + compact?: boolean; +}; + +export const ExternalContoursEmptyState = (props: Props) => { + const { title, description, compact = false } = props; + + return ( +
+
+ +
+
+

{title}

+ {description &&

{description}

} +
+
+ ); +}; diff --git a/plane-src/apps/web/ce/components/projects/external-contours/header.tsx b/plane-src/apps/web/ce/components/projects/external-contours/header.tsx index 5afc342..cf49ca1 100644 --- a/plane-src/apps/web/ce/components/projects/external-contours/header.tsx +++ b/plane-src/apps/web/ce/components/projects/external-contours/header.tsx @@ -69,7 +69,12 @@ export const ProjectExternalContoursHeader = observer(function ProjectExternalCo modalState={createIssueModal} handleModalClose={() => setCreateIssueModal(false)} /> - diff --git a/plane-src/apps/web/ce/components/projects/external-contours/issue-header.tsx b/plane-src/apps/web/ce/components/projects/external-contours/issue-header.tsx index 9b3584b..5868ee0 100644 --- a/plane-src/apps/web/ce/components/projects/external-contours/issue-header.tsx +++ b/plane-src/apps/web/ce/components/projects/external-contours/issue-header.tsx @@ -8,7 +8,6 @@ import { useCallback, useEffect, useState } from "react"; import { observer } from "mobx-react"; import { PanelLeft } from "lucide-react"; import { useTranslation } from "@plane/i18n"; -import { Badge } from "@plane/propel/badge"; import { Button } from "@plane/propel/button"; import { IconButton } from "@plane/propel/icon-button"; import { CheckCircleFilledIcon, ChevronDownIcon, ChevronUpIcon, CloseCircleFilledIcon, LinkIcon, NewTabIcon } from "@plane/propel/icons"; @@ -150,8 +149,22 @@ export const ExternalContoursIssueActionsHeader = observer(function ExternalCont
- redirectToRelativeIssue("prev")} /> - redirectToRelativeIssue("next")} /> + redirectToRelativeIssue("prev")} + className="nodedc-external-icon-button" + /> + redirectToRelativeIssue("next")} + className="nodedc-external-icon-button" + />
@@ -169,7 +182,9 @@ export const ExternalContoursIssueActionsHeader = observer(function ExternalCont )} {isSourceAccepted && ( - {t("external_contours_page.traceability.source_decision_accepted")} +
+ {t("external_contours_page.traceability.source_decision_accepted")} +
)} {isDropdownOpen && ( - +
-
+
setQuery(event.target.value)} placeholder={t("search")} displayValue={(assigned: any) => assigned?.name} onKeyDown={searchInputKeyDown} />
-
+
{labelsList && filteredOptions ? ( filteredOptions.length > 0 ? ( filteredOptions.map((label) => { @@ -223,14 +227,16 @@ export const WorkItemLabelSelectBase = observer(function WorkItemLabelSelectBase - `${ - active ? "bg-layer-1" : "" - } group flex w-full cursor-pointer items-center gap-2 truncate rounded-sm px-1 py-1.5 text-secondary select-none` + cn( + "nodedc-dropdown-option cursor-pointer", + active ? "bg-white/6" : "", + "text-secondary" + ) } value={label.id} > {({ selected }) => ( -
+
{label.name}
-
+
@@ -249,7 +255,7 @@ export const WorkItemLabelSelectBase = observer(function WorkItemLabelSelectBase ); } else return ( -
+
{label.name}
@@ -258,14 +264,16 @@ export const WorkItemLabelSelectBase = observer(function WorkItemLabelSelectBase - `${ - active ? "bg-layer-1" : "" - } group flex min-w-[14rem] cursor-pointer items-center gap-2 truncate rounded-sm px-1 py-1.5 text-secondary select-none` + cn( + "nodedc-dropdown-option min-w-[14rem] cursor-pointer", + active ? "bg-white/6" : "", + "text-secondary" + ) } value={child.id} > {({ selected }) => ( -
+
{child.name}
-
+
@@ -294,12 +302,11 @@ export const WorkItemLabelSelectBase = observer(function WorkItemLabelSelectBase if (!query.length) return; handleAddLabel(query); }} - className={`text-left text-secondary ${query.length ? "cursor-pointer" : "cursor-default"}`} + className={`rounded-[0.9rem] px-2 py-2 text-left text-secondary ${query.length ? "cursor-pointer hover:bg-white/6" : "cursor-default"}`} > - {/* TODO: translate here */} {query.length ? ( <> - + Add "{query}" to labels + + {t("label.create.type")} "{query}" ) : ( t("label.create.type") diff --git a/plane-src/apps/web/core/store/external-contours/project-external-contours.store.ts b/plane-src/apps/web/core/store/external-contours/project-external-contours.store.ts index c53b624..822139f 100644 --- a/plane-src/apps/web/core/store/external-contours/project-external-contours.store.ts +++ b/plane-src/apps/web/core/store/external-contours/project-external-contours.store.ts @@ -178,6 +178,10 @@ export class ProjectExternalContoursStore implements IProjectExternalContoursSto }; handleCurrentTab = async (workspaceSlug: string, projectId: string, tab: TInboxIssueCurrentTab) => { + this.requestIds = []; + this.requests = {}; + this.error = undefined; + this.loader = "init-loading"; this.currentTab = tab; await this.fetchRequests(workspaceSlug, projectId, tab); }; diff --git a/plane-src/apps/web/styles/globals.css b/plane-src/apps/web/styles/globals.css index e5091a4..1db6f26 100644 --- a/plane-src/apps/web/styles/globals.css +++ b/plane-src/apps/web/styles/globals.css @@ -1108,6 +1108,25 @@ background: rgba(255, 255, 255, 0.1) !important; } + .nodedc-external-icon-button { + display: grid !important; + place-items: center !important; + width: 2.5rem !important; + min-width: 2.5rem !important; + height: 2.5rem !important; + border: 0 !important; + outline: none !important; + box-shadow: none !important; + border-radius: 999px !important; + background: rgba(255, 255, 255, 0.06) !important; + color: var(--text-color-primary) !important; + padding: 0 !important; + } + + .nodedc-external-icon-button:hover { + background: rgba(255, 255, 255, 0.1) !important; + } + .nodedc-external-primary-button { min-height: 2.75rem; border: 0 !important; @@ -1116,11 +1135,119 @@ border-radius: 1.25rem !important; background: rgb(var(--nodedc-card-active-rgb)) !important; color: #0b1117 !important; - padding-inline: 1.2rem !important; + padding-inline: 1.45rem !important; + font-weight: 600 !important; } .nodedc-external-primary-button:hover { background: color-mix(in srgb, rgb(var(--nodedc-card-active-rgb)) 82%, white) !important; color: #0b1117 !important; } + + .nodedc-external-empty-state { + display: flex; + width: 100%; + flex-direction: column; + align-items: center; + justify-content: center; + gap: 1rem; + text-align: center; + } + + .nodedc-external-empty-media { + display: grid; + place-items: center; + width: 6.25rem; + height: 6.25rem; + border-radius: 1.85rem; + background: + linear-gradient(180deg, rgba(255, 255, 255, 0.032) 0%, rgba(255, 255, 255, 0.014) 100%), + rgba(255, 255, 255, 0.028); + box-shadow: + 0 16px 40px rgba(0, 0, 0, 0.14), + inset 0 1px 0 rgba(255, 255, 255, 0.018); + } + + .nodedc-external-empty-media svg { + color: rgba(255, 255, 255, 0.22); + } + + .nodedc-external-readonly-value { + display: flex; + min-height: 2.5rem; + width: 100%; + align-items: center; + gap: 0.6rem; + 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-primary) !important; + padding: 0.65rem 0.95rem !important; + } + + .nodedc-external-property-row { + display: flex; + align-items: center; + gap: 1rem; + border: 0 !important; + outline: none !important; + box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.018) !important; + border-radius: 1.6rem !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; + -webkit-backdrop-filter: blur(22px); + backdrop-filter: blur(22px); + padding: 0.9rem 1rem !important; + } + + .nodedc-external-property-label { + display: flex; + width: 40%; + flex-shrink: 0; + align-items: center; + gap: 0.5rem; + font-size: 0.8125rem !important; + color: var(--text-color-tertiary) !important; + } + + .nodedc-external-property-value { + display: flex; + min-height: 2.5rem; + width: 100%; + align-items: center; + gap: 0.6rem; + border: 0 !important; + outline: none !important; + box-shadow: none !important; + border-radius: 999px !important; + background: transparent !important; + color: var(--text-color-primary) !important; + padding: 0.1rem 0.1rem !important; + } + + .nodedc-external-property-control { + display: flex; + min-height: 2.5rem; + width: 100%; + align-items: center; + gap: 0.6rem; + border: 0 !important; + outline: none !important; + box-shadow: none !important; + border-radius: 999px !important; + background: transparent !important; + color: var(--text-color-primary) !important; + padding: 0.1rem 0.1rem !important; + transition: background 160ms ease; + } + + .nodedc-external-property-control:hover, + .nodedc-external-property-control:focus-within { + background: rgba(255, 255, 255, 0.04) !important; + } } diff --git a/plane-src/packages/i18n/src/locales/en/translations.ts b/plane-src/packages/i18n/src/locales/en/translations.ts index 974a18e..c18a5cc 100644 --- a/plane-src/packages/i18n/src/locales/en/translations.ts +++ b/plane-src/packages/i18n/src/locales/en/translations.ts @@ -306,6 +306,7 @@ export default { description: "This screen will host the cross-project request form, the sent requests list, and status pills for each routed task.", detail_title: "Select a request to view its details.", + detail_description: "Choose a request from the left to open its details, properties, and routing context.", open_title: "No open requests", open_description: "Sent and active cross-contour requests will appear here.", closed_title: "No closed requests", diff --git a/plane-src/packages/i18n/src/locales/ru/translations.ts b/plane-src/packages/i18n/src/locales/ru/translations.ts index 55ea8f6..b5b2202 100644 --- a/plane-src/packages/i18n/src/locales/ru/translations.ts +++ b/plane-src/packages/i18n/src/locales/ru/translations.ts @@ -463,6 +463,7 @@ export default { description: "Здесь появятся форма отправки задачи в другой проект, список отправленных запросов и их статусные маркеры.", detail_title: "Выберите запрос для просмотра деталей.", + detail_description: "Выберите запрос слева, чтобы открыть детали, свойства и маршрут межконтурной работы.", open_title: "Нет открытых запросов", open_description: "Здесь будут отображаться отправленные и активные межконтурные запросы.", closed_title: "Нет закрытых запросов",