UI - МЕЖПРОЕКТНАЯ КОММУНИКАЦИЯ: полировка внешних контуров и дизайн-кода

This commit is contained in:
DCCONSTRUCTIONS
2026-04-20 14:37:14 +03:00
parent 9553c81752
commit 1cf05aad72
6 changed files with 194 additions and 94 deletions
+66 -4
View File
@@ -52,6 +52,7 @@
- `Принять`
- `Добавить запрос`
- любые акцентные toolbar-кнопки
- это правило обязательно и для `Внешних контуров`: `Добавить запрос` не может иметь светлый текст на светлом фоне
- Save/update button:
- если это зафиксированный green CTA, текст должен быть контрастным и читаемым
- hover осветляет текущий тон, а не уходит в синий
@@ -91,6 +92,9 @@
- assignee bubbles
- дата
должны быть симметричны верхней
- Для списков карточек `Внешних контуров` используется тот же вертикальный ритм, что и у `Внутреннего контура`:
- контейнер списка не плотнее `space-y-3`
- нельзя лепить карточки вплотную друг к другу
## Dropdown и popup
- Все dropdown/popup приводятся к единому matte glass канону.
@@ -118,6 +122,9 @@
- клиппинг
- налезание на соседние блоки
- старую “врезанную” верстку
- Для `Свойств` в `Внешних контурах` dropdown по умолчанию открывается вверх:
- `placement="top-start"`
- причина: блок находится близко к `Активности`, popup не должен падать вниз в соседнюю секцию
### Portal anchor snippet
```tsx
@@ -222,12 +229,17 @@
- Для `Внешних контуров` это значит:
- список карточек правится на уровне `list-item.tsx`, а не через внешний wrapper
- gap между карточками должен совпадать с каноном `Внутреннего контура`
- актуальный gap списка на текущем каноне: `space-y-2`
- актуальный 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 в более светлый тон того же акцента
- popup выбора `Приоритет / Метки` внутри detail view не рендерится inline в property-row; он обязан уходить в `portal`
- секции с dropdown-trigger внутри blur/glass shell обязаны иметь `overflow: visible` и `isolation: isolate`, иначе popup визуально “тонет” внутри блока
- при переключении `Открытые / Закрытые` store обязан очистить stale request list до нового fetch, чтобы пользователь не видел flash старой верстки
@@ -244,13 +256,42 @@
- List spacing:
```tsx
<div key={routeTab} className="space-y-2">
<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;
```
- 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">
@@ -261,8 +302,15 @@
- Root tab switch without stale flash:
```tsx
void handleCurrentTab(workspaceSlug, projectId, nextTab);
router.push(`...currentTab=${nextTab}`);
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:
@@ -281,6 +329,20 @@ const { styles, attributes } = usePopper(referenceElement, popperElement, {
});
```
- 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">