Standardize modal buttons and delete confirmations

This commit is contained in:
DCCONSTRUCTIONS
2026-05-02 12:57:54 +03:00
parent adad0bd344
commit 17e007f49d
21 changed files with 869 additions and 73 deletions
@@ -0,0 +1,40 @@
{
"id": "accent-contrast",
"name": "NodeDcAccentContrast",
"kind": "utility",
"status": "draft-stable",
"summary": "Shared contrast helper that derives readable text color from a component accent color.",
"sourceRefs": [
{
"project": "nodedc_launcher",
"file": "src/shared/lib/accentContrast.ts",
"exports": [
"getReadableNodedcTextRgb",
"createNodedcAccentStyleVars",
"getNodedcRelativeLuminance"
]
},
{
"project": "nodedc_taskmanager",
"file": "plane-src/packages/utils/src/theme/nodedc-accent.ts",
"exports": [
"getReadableNodedcTextRgb",
"applyNodedcAccent"
]
}
],
"visualContract": {
"darkTextRgb": [11, 17, 23],
"lightTextRgb": [245, 247, 251],
"luminanceThreshold": 0.52,
"cssVars": [
"--nodedc-component-accent-rgb",
"--nodedc-component-on-accent-rgb"
]
},
"rules": [
"Any filled accent button or active accent chip must use the computed on-accent text color.",
"Do not hardcode black or white text on an accent fill unless the accent contrast helper owns that decision.",
"Component-local accent variables are allowed when global --nodedc-accent-rgb is not the intended color for that component."
]
}
+61
View File
@@ -0,0 +1,61 @@
{
"id": "button",
"name": "Button",
"kind": "primitive",
"status": "draft-stable",
"summary": "Canonical text/action button used by launcher stages, admin editors, and modal footers.",
"sourceRefs": [
{
"project": "nodedc_launcher",
"file": "src/shared/ui/Button.tsx",
"exports": ["Button"]
},
{
"project": "nodedc_launcher",
"file": "src/styles/globals.css",
"classes": [
"button",
"button--primary",
"button--secondary",
"button--danger",
"button--ghost",
"button--accent",
"button[data-surface='modal']"
]
},
{
"project": "nodedc_taskmanager",
"file": "plane-src/apps/web/styles/globals.css",
"classes": [
"nodedc-modal-primary-button",
"nodedc-modal-secondary-button",
"nodedc-modal-danger-button"
]
}
],
"api": {
"props": {
"variant": ["primary", "secondary", "danger", "ghost", "accent"],
"surface": ["default", "modal"],
"accentRgb": "optional RGB tuple used by accent variant",
"icon": "optional leading icon"
}
},
"visualContract": {
"height": "2.65rem default, tokens.size.modalButtonHeight on modal surface",
"radius": "control",
"fontWeight": "800",
"outline": "never",
"modalSecondaryDanger": "transparent until hover",
"modalAccent": "filled component accent with computed on-accent text"
},
"rules": [
"Use Button instead of hand-written button classes for text actions.",
"Use IconButton or circle-action-button for icon-only actions.",
"Inside modals, pass surface='modal' on every footer button.",
"Modal Save and modal destructive confirm use variant='accent' with local accentRgb when the app accent differs.",
"Inline modal delete buttons use variant='danger' with surface='modal'; they are transparent until neutral hover.",
"Do not use red filled delete buttons outside NodeDcDeleteModal confirmation.",
"Filled accent variants must use accent-contrast variables for readable text."
]
}
@@ -0,0 +1,74 @@
{
"id": "delete-modal",
"name": "NodeDcDeleteModal",
"kind": "component",
"status": "draft-stable",
"summary": "Canonical dark glass confirmation modal for destructive object deletion.",
"sourceRefs": [
{
"project": "nodedc_launcher",
"file": "src/shared/nodedc-ui/DeleteModal.tsx",
"exports": ["NodeDcDeleteModal"]
},
{
"project": "nodedc_launcher",
"file": "src/styles/globals.css",
"classes": [
"nodedc-delete-modal-layer",
"nodedc-delete-modal",
"nodedc-delete-modal__icon",
"nodedc-delete-modal__foot"
]
},
{
"project": "nodedc_taskmanager",
"file": "plane-src/packages/ui/src/modals/alert-modal.tsx",
"exports": ["AlertModalCore"]
},
{
"project": "nodedc_taskmanager",
"file": "plane-src/apps/web/styles/globals.css",
"classes": [
"nodedc-glass-modal",
"nodedc-modal-alert-icon",
"nodedc-modal-danger-button",
"nodedc-modal-secondary-button"
]
}
],
"anatomy": [
"fixed blurred overlay layer",
"dark glass shell",
"round alert icon",
"title and description copy",
"footer with secondary cancel and destructive confirm"
],
"visualContract": {
"width": "min(34rem, viewport - 2.8rem)",
"radius": "modal",
"background": "transparent black detail glass, not monocolor fill",
"blur": "44px detail glass pseudo-layer blur",
"iconSize": "2.75rem circle",
"iconColor": "component accent, white in launcher",
"buttonHeight": "tokens.size.modalButtonHeight through Button surface='modal'",
"buttonRadius": "control radius",
"cancelButton": "Button variant='secondary' surface='modal'",
"destructiveButton": "Button variant='accent' surface='modal' with computed on-accent text"
},
"rules": [
"Use this component for every delete/destructive removal action.",
"Never delete immediately from a row, footer, dropdown, or editor without this modal.",
"Do not add visible outlines or hard stroke borders to the modal, icon, or buttons.",
"The shell uses nodedc-glass-detail-surface so the panel blurs the actual content under it.",
"The destructive button uses the shared Button accent variant and component-local accent contrast variables, not a hardcoded red fill.",
"Cancel is secondary and stays left of the destructive confirm action.",
"The description must name the object and describe dependent data that will also be removed."
],
"launcherBindings": [
"service showcase deletion",
"client/company deletion",
"client membership removal",
"client group deletion",
"invite deletion"
]
}
+3 -1
View File
@@ -39,9 +39,11 @@
},
"rules": [
"Delete is left of Save inside the right footer action group.",
"Footer buttons use the shared Button primitive with surface='modal'.",
"Inline delete buttons inside edit modals use Button variant='danger' surface='modal', stay transparent until hover, and do not use red danger fills.",
"Save uses Button variant='accent' surface='modal' with local white launcher accent and computed on-accent text.",
"Cancel stays on the left.",
"Close action is circular and transparent until hover.",
"Fields/selects/textareas share the same glass family."
]
}
+4 -5
View File
@@ -12,25 +12,24 @@
{
"project": "nodedc_launcher",
"file": "src/styles/globals.css",
"classes": ["glass-surface", "admin-panel-nav", "admin-panel-content", "service-content-modal"]
"classes": ["glass-surface", "glass-surface--detail", "nodedc-glass-detail-surface", "admin-panel-nav", "admin-panel-content", "service-content-modal"]
},
{
"project": "nodedc_taskmanager",
"file": "plane-src/apps/web/styles/globals.css",
"classes": ["nodedc-dropdown-surface", "nodedc-modal-field", "nodedc-settings-card"]
"classes": ["nodedc-work-item-property-button", "nodedc-settings-card", "nodedc-modal-field", "nodedc-dropdown-surface"]
}
],
"tokens": {
"radius": "card | modal | control depending on scale",
"blur": "panel | modal | dropdown",
"blur": "control | panel | modal | dropdown | detail",
"background": "dark matte glass gradient over rgba black",
"border": "none or transparent soft glass only"
},
"rules": [
"Never use hard outline as the main visual boundary.",
"Use background opacity and blur to separate layers.",
"Use background opacity, pseudo-layer glass, and backdrop blur to separate layers.",
"For major shells use radius.card or radius.modal.",
"For controls inside shells use radius.control."
]
}
+21
View File
@@ -38,12 +38,26 @@
"status": "draft-stable",
"primarySource": "nodedc_launcher"
},
{
"id": "button",
"spec": "components/button.json",
"status": "draft-stable",
"primarySource": "nodedc_launcher",
"taskManagerReference": true
},
{
"id": "glass-panel",
"spec": "components/glass-panel.json",
"status": "draft-stable",
"primarySource": "nodedc_launcher"
},
{
"id": "accent-contrast",
"spec": "components/accent-contrast.json",
"status": "draft-stable",
"primarySource": "nodedc_launcher",
"taskManagerReference": true
},
{
"id": "dropdown-surface",
"spec": "components/dropdown-surface.json",
@@ -102,6 +116,13 @@
"status": "draft-stable",
"primarySource": "nodedc_launcher"
},
{
"id": "delete-modal",
"spec": "components/delete-modal.json",
"status": "draft-stable",
"primarySource": "nodedc_launcher",
"taskManagerReference": true
},
{
"id": "media-source-field",
"spec": "components/media-source-field.json",
+21 -1
View File
@@ -29,7 +29,17 @@
{
"id": "matte-glass",
"severity": "error",
"text": "Popup, dropdown, modal, sidebar, and settings surfaces use dark matte glass with blur, not plain transparency or light foreign backgrounds."
"text": "Popup, dropdown, modal, sidebar, and settings surfaces use dark matte glass with blur, not plain transparency, monocolor fills, or light foreign backgrounds."
},
{
"id": "accent-contrast",
"severity": "error",
"text": "Filled accent controls must use computed on-accent text from the accent-contrast utility."
},
{
"id": "buttons-shared-module",
"severity": "error",
"text": "Text actions use the shared Button primitive with variant and surface props. Modal footer buttons must use surface='modal'."
},
{
"id": "status-pills",
@@ -41,6 +51,16 @@
"severity": "warning",
"text": "Admin data screens use AdminTable anatomy: table-shell, toolbar, editable cells, circular actions, and shared status/date controls."
},
{
"id": "delete-confirmation",
"severity": "error",
"text": "Every destructive delete/removal action must open NodeDcDeleteModal before mutating data."
},
{
"id": "inline-delete-buttons",
"severity": "error",
"text": "Inline delete buttons inside editors and tables stay transparent by default and receive only neutral glass hover; red filled delete buttons are forbidden outside the confirmation modal."
},
{
"id": "taskmanager-calendar",
"severity": "warning",
+23
View File
@@ -7,6 +7,9 @@
"tokensAndCss": [
"src/styles/globals.css"
],
"colorUtilities": [
"src/shared/lib/accentContrast.ts"
],
"topBar": [
"src/widgets/top-bar/TopBar.tsx"
],
@@ -25,6 +28,7 @@
"src/shared/ui/PortalDropdown.tsx",
"src/shared/nodedc-ui/Dropdown.tsx",
"src/shared/nodedc-ui/Select.tsx",
"src/shared/nodedc-ui/DeleteModal.tsx",
"src/shared/nodedc-ui/Calendar.tsx",
"src/shared/nodedc-ui/ProfileMenu.tsx",
"src/shared/nodedc-ui/index.ts"
@@ -61,11 +65,20 @@
"appHeader": [
"plane-src/apps/web/core/components/core/app-header.tsx",
"plane-src/apps/web/ce/components/common/extended-app-header.tsx"
],
"deleteModal": [
"plane-src/packages/ui/src/modals/alert-modal.tsx",
"plane-src/packages/ui/src/modals/modal-core.tsx"
]
}
}
},
"crossProjectMapping": [
{
"component": "button",
"launcher": ["Button", "button--accent", "Button surface='modal'", "ServiceContentModal footer", "EntityModalFoot"],
"taskManager": ["nodedc-modal-primary-button", "nodedc-modal-secondary-button", "nodedc-modal-danger-button"]
},
{
"component": "dropdown-surface",
"launcher": ["NodeDcDropdown", "nodedc-ui-dropdown-surface", "nodedc-dropdown-surface"],
@@ -90,6 +103,16 @@
"component": "admin-table",
"launcher": ["ServicesSection", "ClientsSection", "UsersSection", "GroupsSection"],
"taskManager": ["settings tables and external contour settings classes"]
},
{
"component": "delete-modal",
"launcher": ["NodeDcDeleteModal", "EntityModalFoot deleteConfig", "ServiceContentModal delete confirmation"],
"taskManager": ["AlertModalCore", "ModalCore", "nodedc-glass-modal"]
},
{
"component": "accent-contrast",
"launcher": ["createNodedcAccentStyleVars", "getReadableNodedcTextRgb"],
"taskManager": ["packages/utils/src/theme/nodedc-accent.ts", "--nodedc-on-accent-rgb"]
}
]
}
+4 -1
View File
@@ -3,6 +3,8 @@
--nodedc-card-passive-rgb: 42 43 46;
--nodedc-card-active-rgb: 195 255 102;
--nodedc-on-accent-rgb: 11 17 23;
--nodedc-component-accent-rgb: 247 248 244;
--nodedc-component-on-accent-rgb: 11 17 23;
--nodedc-radius-modal: 1.75rem;
--nodedc-radius-card: 1.35rem;
--nodedc-radius-control: 1.25rem;
@@ -10,6 +12,7 @@
--nodedc-radius-calendar: 1.1rem;
--nodedc-radius-circle: 999px;
--nodedc-toolbar-pill-height: 2.5rem;
--nodedc-modal-button-height: 2.75rem;
--nodedc-control-ring: 2.92rem;
--nodedc-control-inset: 5px;
--nodedc-status-width: 8.65rem;
@@ -20,5 +23,5 @@
--nodedc-dropdown-blur: 44px;
--nodedc-panel-blur: 28px;
--nodedc-modal-blur: 34px;
--nodedc-detail-blur: 44px;
}
+3 -1
View File
@@ -4,6 +4,8 @@
"color": {
"accentRgb": [195, 255, 102],
"onAccentRgb": [11, 17, 23],
"componentAccentRgb": [247, 248, 244],
"componentOnAccentRgb": [11, 17, 23],
"cardPassiveRgb": [42, 43, 46],
"cardActiveRgb": [195, 255, 102],
"surfaceBlack": "rgba(8, 8, 11, 0.9)",
@@ -45,6 +47,7 @@
"dropdown": "44px",
"panel": "28px",
"modal": "34px",
"detail": "44px",
"control": "18px"
},
"shadow": {
@@ -76,4 +79,3 @@
}
}
}