NODEDC_TASKMANAGER/HDESIGN-CODE.md

373 lines
19 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# 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: более светлая версия того же цвета
- правило распространяется на все filled CTA:
- `Добавить`
- `Сохранить`
- `Обновить`
- `Принять`
- `Добавить запрос`
- любые акцентные toolbar-кнопки
- это правило обязательно и для `Внешних контуров`: `Добавить запрос` не может иметь светлый текст на светлом фоне
- 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
- дата
должны быть симметричны верхней
- Для списков карточек `Внешних контуров` используется тот же вертикальный ритм, что и у `Внутреннего контура`:
- контейнер списка не плотнее `space-y-3`
- нельзя лепить карточки вплотную друг к другу
## Dropdown и popup
- Все dropdown/popup приводятся к единому matte glass канону.
- Запрещены:
- квадратные active-box вокруг круглых кнопок
- жёсткие border-outline
- светлый фон, если основной экран тёмный
- Search shell внутри popup должен использовать тот же стиль, что и сам popup.
- Filled CTA внутри popup и модалок подчиняются тому же правилу:
- светлый акцентный фон
- тёмный/чёрный текст
- hover только в более светлый тон того же цвета
### Portal правило
- Если selector/dropdown открывается внутри:
- scroll-контейнера
- detail-pane
- карточки
- properties section
- sidebar
- sticky header
он не должен рендериться inline.
- Такой popup обязан рендериться на верхнем слое через `portal` (`document.body` или эквивалент).
- Inline popup в ограниченном контейнере считается дефектом, потому что даёт:
- клиппинг
- налезание на соседние блоки
- старую “врезанную” верстку
- Для `Свойств` в `Внешних контурах` dropdown по умолчанию открывается вверх:
- `placement="top-start"`
- причина: блок находится близко к `Активности`, popup не должен падать вниз в соседнюю секцию
### Portal anchor snippet
```tsx
{isOpen &&
typeof document !== "undefined" &&
createPortal(
<Combobox.Options className="fixed z-[420]" static>
<div
data-prevent-outside-click
className="nodedc-dropdown-surface nodedc-external-popup-anchor"
>
...
</div>
</Combobox.Options>,
document.body
)}
```
### 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
<Button className="nodedc-external-primary-button">...</Button>
```
```tsx
<IconButton className="nodedc-external-icon-button" ... />
```
```tsx
<div className="nodedc-external-readonly-value">
<SomeIcon className="h-3.5 w-3.5 text-tertiary" />
<span className="text-12 font-medium text-primary">...</span>
</div>
```
```tsx
<div className="nodedc-external-property-row">
<div className="nodedc-external-property-label">
<SomeIcon className="h-4 w-4 flex-shrink-0" />
<span>Label</span>
</div>
<div className="nodedc-external-property-value">
<SomeValue />
</div>
</div>
```
```tsx
<Dropdown
button={
<div className="nodedc-external-property-control text-[13px] font-medium">
<SomeIcon className="h-3.5 w-3.5 flex-shrink-0 text-tertiary" />
<span className="text-primary">Value</span>
</div>
}
/>
```
## Drag and drop
- Drag overlay использует акцентный контур.
- Delete dropzone:
- без красного технического свечения и без red-tinted text/fill
- текст локализован
- акцентный outline обязателен
## Тексты
- Пользовательский UI на русском, если экран русифицирован.
- Не оставлять смешанные подписи вида `Created at / Updated at / Label / State group`, если экран уже на русском.
## Правило внедрения
- Новый экран или popup не стилизуется локально “на глаз”.
- Сначала используется существующий shared-класс или shared-component.
- Если shared-слоя нет, создаётся reusable-класс/компонент и уже через него приводятся все похожие места.
- Цель: не точечная покраска одного окна, а единый системный канон.
- Если блок визуально расходится со стилем системы, не добавлять поверх временную wrapper-заплатку. Нужно либо перевести блок на shared-компонент, либо переверстать локальную структуру под shared-классы.
- Для экранов со вкладками/переключателями нельзя оставлять flash старой верстки. Перед refetch нужно очищать stale store-data и показывать loading shell.
- Если карточки или списки разных модулей должны быть одинаковыми по канону, нельзя лечить это внешней обёрткой. Нужно менять сам внутренний layout item-компонента.
- Для `Внешних контуров` это значит:
- список карточек правится на уровне `list-item.tsx`, а не через внешний wrapper
- gap между карточками должен совпадать с каноном `Внутреннего контура`
- актуальный gap списка на текущем каноне: `space-y-3`
- при tab switch между `Открытые / Закрытые` нельзя полагаться только на route param; нужен локальный `pendingTab`, чтобы stale layout не мелькал до завершения refetch
- toolbar-навигация и inline actions не должны использовать старые квадратные `IconButton` остатки
- свойства `Приоритет / Метки / Статус` не должны рисовать внутренние boxed-chip артефакты
- popup `Приоритет / Метки` не может визуально жить внутри property-row; если он открывается из blur-shell, он обязан уходить в portal и рендериться над секцией
- filled CTA вроде `Добавить запрос` используют `nodedc-external-primary-button` и всегда имеют тёмный текст
- filled CTA используют чёрный/почти-чёрный текст всегда; белый текст на светлом акценте запрещён
- secondary meta-иконки в карточке списка не должны иметь отдельную серую подложку, если по канону это простой inline icon
- empty-state не должен использовать декоративную серую подложку под SVG; media-box прозрачный, SVG выравнивается через `display:flex` и центрирование
- detail-toolbar в карточке запроса использует общий glass-cluster для листания `prev/next`, а сами кнопки внутри кластера — круглые, без квадратной подложки
- `Добавить запрос` в header `Внешних контуров` — это filled accent CTA с тёмным текстом, каноничным радиусом и hover в более светлый тон того же акцента
- global sidebar quick action `Новый рабочий элемент` не показывается на маршруте `external-contours`, потому что этот экран уже имеет собственный primary CTA в header
- active/passive карточки `Внешних контуров` обязаны брать фон только из `--nodedc-card-active-rgb` и `--nodedc-card-passive-rgb`
- header `Внешних контуров` и detail-pane опускаются на единый верхний ритм; нельзя прижимать breadcrumbs, CTA и detail-header к верхней кромке
- popup выбора `Приоритет / Метки` внутри detail view не рендерится inline в property-row; он обязан уходить в `portal`
- секции с dropdown-trigger внутри blur/glass shell обязаны иметь `overflow: visible` и `isolation: isolate`, иначе popup визуально “тонет” внутри блока
- при переключении `Открытые / Закрытые` store обязан очистить stale request list до нового fetch, чтобы пользователь не видел flash старой верстки
- карточка списка `Внешних контуров` правится на уровне `list-item.tsx`, а не внешней обёрткой:
- верхняя и нижняя оси собираются как у карточки `Внутреннего контура`
- gap между карточками совпадает с каноном `Внутреннего контура`
- empty-state иконки без декоративной подложки; если иконка визуально “плывёт”, корректируется сам SVG/media-box
### Внешние контуры: code anchors
- Header CTA:
```tsx
<Button className="nodedc-external-primary-button">...</Button>
```
- Route-aware quick action hide:
```tsx
const pathname = usePathname();
if (pathname?.includes("/external-contours")) return null;
```
- List spacing:
```tsx
<div key={resolvedTab} className="space-y-3">
{filteredRequestIds.map((requestId) => (
<ExternalContoursListItem key={requestId} ... />
))}
</div>
```
- Pending tab anti-flash:
```tsx
const [pendingTab, setPendingTab] = useState<TInboxIssueCurrentTab | null>(null);
const routeTab = (searchParams.get("currentTab") as TInboxIssueCurrentTab | null) ?? currentTab;
const resolvedTab = pendingTab ?? routeTab;
const isTabTransitioning = loader === "init-loading" || pendingTab !== null || routeTab !== currentTab;
```
- Card theme source:
```css
.nodedc-external-card {
background: rgb(var(--nodedc-card-passive-rgb));
}
.nodedc-external-card[data-active="true"] {
background: rgb(var(--nodedc-card-active-rgb));
color: #0b1117;
}
```
- Property popup anchor:
```tsx
<PriorityDropdown
placement="top-start"
buttonContainerClassName="nodedc-external-property-control-shell ..."
button={
<div className="nodedc-external-property-control text-[13px] font-medium">
...
</div>
}
/>
```
- Detail toolbar cluster:
```tsx
<div className="nodedc-external-toolbar-cluster">
<button type="button" className="nodedc-external-icon-button">...</button>
<button type="button" className="nodedc-external-icon-button">...</button>
</div>
```
- Property control:
```tsx
<div className="nodedc-external-property-control text-[13px] font-medium">
<SomeIcon className="h-3.5 w-3.5 flex-shrink-0 text-tertiary" />
<span className="text-primary">...</span>
</div>
```
- Root tab switch without stale flash:
```tsx
const [pendingTab, setPendingTab] = useState<TInboxIssueCurrentTab | null>(null);
const resolvedTab = pendingTab ?? routeTab;
const isTabTransitioning = loader === "init-loading" || pendingTab !== null || routeTab !== currentTab;
if (resolvedTab !== nextTab) {
setPendingTab(nextTab);
void handleCurrentTab(workspaceSlug, projectId, nextTab);
router.push(`...currentTab=${nextTab}`);
}
```
- Store-side tab reset:
```ts
this.requestIds = [];
this.requests = {};
this.loader = "init-loading";
this.currentTab = tab;
```
- Portal popup с фиксированной стратегией:
```tsx
const { styles, attributes } = usePopper(referenceElement, popperElement, {
strategy: "fixed",
placement: placement ?? "bottom-start",
});
```
- Property popup without boxed artifact:
```tsx
<IssueLabelSelect
rootClassName="w-full overflow-visible"
buttonContainerClassName="nodedc-external-property-control-shell h-full w-full overflow-visible"
label={
<div className="nodedc-external-property-control text-[13px] font-medium">
<LabelPropertyIcon className="h-3.5 w-3.5 flex-shrink-0 text-tertiary" />
<span className="truncate text-primary">...</span>
</div>
}
/>
```
- Контейнер секции с trigger:
```tsx
<div className="nodedc-external-section overflow-visible px-4 py-4">
...
</div>
```