АРХ - МЕЖПРОЕКТНАЯ КОММУНИКАЦИЯ: фундамент редизайна и фиксация UI-техдолга

This commit is contained in:
DCCONSTRUCTIONS
2026-04-22 14:30:51 +03:00
parent ecb31a78f9
commit 3f6219fc50
21 changed files with 385 additions and 61 deletions
@@ -116,7 +116,7 @@ const ProjectsToolbarMenu = observer(function ProjectsToolbarMenu() {
>
<span
className={`nodedc-toolbar-icon-active-dot ${
pathname.includes("/projects/") ? "bg-[rgb(var(--nodedc-accent-rgb))] text-[#0b1117]" : ""
pathname.includes("/projects/") ? "bg-[rgb(var(--nodedc-accent-rgb))] text-[rgb(var(--nodedc-on-accent-rgb))]" : ""
}`}
>
<ProjectIcon className="size-4" />
+43 -3
View File
@@ -36,10 +36,50 @@ import "@fontsource/ibm-plex-mono";
const APP_TITLE = "NODE.DC | Self-hosted task management workspace.";
const DARK_TEXT_RGB = [11, 17, 23] as const;
const LIGHT_TEXT_RGB = [245, 247, 251] as const;
const formatRgbTuple = (rgb: readonly number[]) => rgb.join(" ");
const formatCssRgb = (rgb: readonly number[]) => `rgb(${rgb.join(" ")})`;
const blendRgb = (rgb: readonly number[], target: number, ratio: number) =>
rgb.map((channel) => Math.round(channel * (1 - ratio) + target * ratio)) as [number, number, number];
const toRelativeLuminance = (rgb: readonly number[]) => {
const [r, g, b] = rgb.map((channel) => {
const normalized = channel / 255;
return normalized <= 0.03928 ? normalized / 12.92 : ((normalized + 0.055) / 1.055) ** 2.4;
});
return 0.2126 * r + 0.7152 * g + 0.0722 * b;
};
const getReadableTextRgb = (rgb: readonly number[]) => (toRelativeLuminance(rgb) > 0.52 ? DARK_TEXT_RGB : LIGHT_TEXT_RGB);
const accentRgb = designConfig.nodedc.accent_rgb as [number, number, number];
const activeCardRgb = designConfig.nodedc.active_card_rgb as [number, number, number];
const passiveCardRgb = designConfig.nodedc.passive_card_rgb as [number, number, number];
const accentHoverRgb = blendRgb(accentRgb, 255, 0.18);
const accentActiveRgb = blendRgb(accentRgb, 0, 0.1);
const onAccentRgb = getReadableTextRgb(accentRgb);
const onActiveCardRgb = getReadableTextRgb(activeCardRgb);
const onPassiveCardRgb = getReadableTextRgb(passiveCardRgb);
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(" "),
"--nodedc-accent-rgb": formatRgbTuple(accentRgb),
"--nodedc-card-passive-rgb": formatRgbTuple(passiveCardRgb),
"--nodedc-card-active-rgb": formatRgbTuple(activeCardRgb),
"--nodedc-on-accent-rgb": formatRgbTuple(onAccentRgb),
"--nodedc-on-card-active-rgb": formatRgbTuple(onActiveCardRgb),
"--nodedc-on-card-passive-rgb": formatRgbTuple(onPassiveCardRgb),
"--brand-default": formatCssRgb(accentRgb),
"--brand-300": formatCssRgb(blendRgb(accentRgb, 255, 0.35)),
"--brand-700": formatCssRgb(blendRgb(accentRgb, 0, 0.25)),
"--bg-accent-primary": formatCssRgb(accentRgb),
"--bg-accent-primary-hover": formatCssRgb(accentHoverRgb),
"--bg-accent-primary-active": formatCssRgb(accentActiveRgb),
"--txt-on-color": formatCssRgb(onAccentRgb),
"--txt-icon-on-color": formatCssRgb(onAccentRgb),
} as CSSProperties;
export const links: LinksFunction = () => [
@@ -122,12 +122,14 @@ export const ExternalContoursBoardItem = observer(function ExternalContoursBoard
const sourceStateIds = useMemo(() => targetOptions?.states?.map((state) => state.id) ?? [], [targetOptions?.states]);
const selectedState = canEditTargetIssue ? getStateById(issue.state_id) : sourceStateMap[issue.state_id ?? ""];
const projectStateIds = issue.project_id ? getProjectStateIds(issue.project_id) : [];
const foregroundClasses = isActive ? "text-[#111111]" : "text-white";
const foregroundClasses = isActive ? "text-[rgb(var(--nodedc-on-card-active-rgb))]" : "text-white";
const subtleTextClasses = isActive ? "text-[#2F4721]" : "text-[#B3B3B8]";
const pillBackgroundClasses =
isActive ? "bg-black/10 text-[#111111]" : "bg-[rgb(var(--nodedc-card-passive-rgb))] text-white";
isActive
? "bg-black/10 text-[rgb(var(--nodedc-on-card-active-rgb))]"
: "bg-[rgb(var(--nodedc-card-passive-rgb))] text-white";
const iconBubbleClasses = isActive ? "bg-black text-[rgb(var(--nodedc-card-active-rgb))]" : "bg-[#111214] text-white";
const statusIconColor = selectedState?.color ?? (isActive ? "#111111" : "var(--text-color-primary)");
const statusIconColor = selectedState?.color ?? (isActive ? "rgb(var(--nodedc-on-card-active-rgb))" : "var(--text-color-primary)");
const dueDateLabel = issue.target_date ? renderFormattedDate(issue.target_date, "d MMM, yyyy") : t("common.none");
if (!issue) return null;
@@ -75,7 +75,10 @@ export const ExternalContoursListItem = observer(function ExternalContoursListIt
/>
<div className="min-w-0">
<div
className={cn("truncate text-[15px] leading-5 font-semibold", isActive ? "text-[#0b1117]" : "text-primary")}
className={cn(
"truncate text-[15px] leading-5 font-semibold",
isActive ? "text-[rgb(var(--nodedc-on-card-active-rgb))]" : "text-primary"
)}
>
{requesterName}
</div>
@@ -83,7 +86,12 @@ export const ExternalContoursListItem = observer(function ExternalContoursListIt
</div>
<div className="flex shrink-0 items-center gap-2">
<div className={cn("text-11 font-medium", isActive ? "text-[#17212b]/70" : "text-tertiary")}>
<div
className={cn(
"text-11 font-medium",
isActive ? "text-[rgb(var(--nodedc-on-card-active-rgb))]/70" : "text-tertiary"
)}
>
{issue.project_detail?.identifier || "REQ"}-{issue.sequence_id}
</div>
<div className="flex items-center gap-2">
@@ -97,7 +105,12 @@ export const ExternalContoursListItem = observer(function ExternalContoursListIt
</div>
</div>
<div className={cn("truncate pl-10 text-[12px] font-medium leading-4", isActive ? "text-[#17212b]/75" : "text-secondary")}>
<div
className={cn(
"truncate pl-10 text-[12px] font-medium leading-4",
isActive ? "text-[rgb(var(--nodedc-on-card-active-rgb))]/75" : "text-secondary"
)}
>
{contourName}
</div>
</div>
@@ -106,7 +119,7 @@ export const ExternalContoursListItem = observer(function ExternalContoursListIt
<h3
className={cn(
"line-clamp-3 w-full max-w-[18.5rem] text-center text-16 leading-7 font-semibold",
isActive ? "text-[#0b1117]" : "text-primary"
isActive ? "text-[rgb(var(--nodedc-on-card-active-rgb))]" : "text-primary"
)}
>
{issue.name}
@@ -141,7 +154,7 @@ export const ExternalContoursListItem = observer(function ExternalContoursListIt
<div
className={cn(
"rounded-full px-3 py-1.5 text-12",
isActive ? "bg-black/10 text-[#17212b]/80" : "bg-white/6 text-secondary"
isActive ? "bg-black/10 text-[rgb(var(--nodedc-on-card-active-rgb))]/80" : "bg-white/6 text-secondary"
)}
>
{renderFormattedDate(lastUpdatedAt ?? "")}
@@ -73,7 +73,7 @@ export const FilterDisplayProperties = observer(function FilterDisplayProperties
type="button"
className={`rounded-full border-0 px-3 py-1.5 text-12 transition-all ${
displayProperties?.[displayProperty.key]
? "bg-[rgb(var(--nodedc-accent-rgb))] text-[#0b1117]"
? "bg-[rgb(var(--nodedc-accent-rgb))] text-[rgb(var(--nodedc-on-accent-rgb))]"
: "bg-white/5 text-secondary hover:bg-white/8"
}`}
onClick={() =>
@@ -29,7 +29,7 @@ export function FilterOption(props: Props) {
<div
className={`grid h-4 w-4 flex-shrink-0 place-items-center border-0 ${
isChecked
? "bg-[rgb(var(--nodedc-accent-rgb))] text-[#0b1117]"
? "bg-[rgb(var(--nodedc-accent-rgb))] text-[rgb(var(--nodedc-on-accent-rgb))]"
: "bg-white/6 text-transparent"
} rounded-full`}
>
@@ -48,7 +48,9 @@ export function LayoutSelection(props: Props) {
layout={layout.key}
size={14}
strokeWidth={2}
className={`size-3.5 ${selectedLayout == layout.key ? "text-[#0b1117]" : "text-secondary"}`}
className={`size-3.5 ${
selectedLayout == layout.key ? "text-[rgb(var(--nodedc-on-accent-rgb))]" : "text-secondary"
}`}
/>
</span>
</button>
@@ -27,7 +27,7 @@ export function MobileLayoutSelection({
<SelectionDropdown
menuButton={
<div className="nodedc-toolbar-pill relative flex items-center gap-2 px-3">
<span className="nodedc-toolbar-icon-active-dot bg-[rgb(var(--nodedc-accent-rgb))] text-[#0b1117]">
<span className="nodedc-toolbar-icon-active-dot bg-[rgb(var(--nodedc-accent-rgb))] text-[rgb(var(--nodedc-on-accent-rgb))]">
{activeLayout && (
<IssueLayoutIcon layout={activeLayout} size={14} strokeWidth={2} className="h-3.5 w-3.5" />
)}
@@ -75,7 +75,8 @@ export const InternalContourKanbanCard = observer(function InternalContourKanban
const selectedState = getStateById(issue.state_id);
const projectStateIds = issue.project_id ? getProjectStateIds(issue.project_id) : [];
const { pillBackgroundClasses, iconBubbleClasses } = getNodedcWorkItemCardAppearance(isActive);
const statusIconColor = selectedState?.color ?? (isActive ? "#111111" : "var(--text-color-primary)");
const statusIconColor =
selectedState?.color ?? (isActive ? "rgb(var(--nodedc-on-card-active-rgb))" : "var(--text-color-primary)");
const creatorName = creatorDetails?.display_name ?? t("common.none");
const dueDateLabel = issue.target_date ? renderFormattedDate(issue.target_date, "d MMM, yyyy") : t("common.none");
@@ -23,11 +23,13 @@ type TNodedcWorkItemCardProps = {
export const getNodedcWorkItemCardAppearance = (isActive: boolean) => ({
surfaceClassName: isActive
? "bg-[rgb(var(--nodedc-card-active-rgb))] text-[#111111]"
? "bg-[rgb(var(--nodedc-card-active-rgb))] text-[rgb(var(--nodedc-on-card-active-rgb))]"
: "bg-[rgb(var(--nodedc-card-passive-rgb))] text-white",
foregroundClasses: isActive ? "text-[#111111]" : "text-white",
foregroundClasses: isActive ? "text-[rgb(var(--nodedc-on-card-active-rgb))]" : "text-[rgb(var(--nodedc-on-card-passive-rgb))]",
subtleTextClasses: isActive ? "text-[#2F4721]" : "text-[#B3B3B8]",
pillBackgroundClasses: isActive ? "bg-black/10 text-[#111111]" : "bg-[rgb(var(--nodedc-card-passive-rgb))] text-white",
pillBackgroundClasses: isActive
? "bg-black/10 text-[rgb(var(--nodedc-on-card-active-rgb))]"
: "bg-[rgb(var(--nodedc-card-passive-rgb))] text-[rgb(var(--nodedc-on-card-passive-rgb))]",
iconBubbleClasses: isActive ? "bg-black text-[rgb(var(--nodedc-card-active-rgb))]" : "bg-[#111214] text-white",
});
@@ -435,7 +435,7 @@ export function ProjectDetailsForm(props: IProjectDetailsForm) {
type="submit"
loading={isLoading}
disabled={!isAdmin}
className="nodedc-settings-save-button min-w-[11.5rem] !text-[#0b1117] hover:!text-[#0b1117]"
className="nodedc-settings-save-button min-w-[11.5rem]"
>
{isLoading ? t("updating") : t("common.update_project")}
</Button>
@@ -57,7 +57,7 @@ export const ProjectFeatureUpdate = observer(function ProjectFeatureUpdate(props
<Link
href={`/${workspaceSlug}/projects/${projectId}/issues`}
onClick={onClose}
className="nodedc-modal-primary-button inline-flex min-w-[10.5rem] items-center justify-center !text-[#0b1117] hover:!text-[#0b1117]"
className="nodedc-modal-primary-button inline-flex min-w-[10.5rem] items-center justify-center"
tabIndex={2}
>
{t("open_project")}
+36 -25
View File
@@ -28,11 +28,19 @@
--editor-colors-dark-blue-background: #c9dafb;
--editor-colors-purple-background: #e3d8fd;
--nodedc-accent-rgb: 51 163 255;
--nodedc-on-accent-rgb: 245 247 251;
--nodedc-card-passive-rgb: 42 43 46;
--nodedc-on-card-passive-rgb: 245 247 251;
--nodedc-card-active-rgb: 195 255 102;
--nodedc-on-card-active-rgb: 11 17 23;
--brand-default: rgb(var(--nodedc-accent-rgb));
--brand-300: color-mix(in srgb, rgb(var(--nodedc-accent-rgb)) 35%, white);
--brand-300: color-mix(in srgb, rgb(var(--nodedc-accent-rgb)) 65%, white);
--brand-700: color-mix(in srgb, rgb(var(--nodedc-accent-rgb)) 75%, black);
--bg-accent-primary: rgb(var(--nodedc-accent-rgb));
--bg-accent-primary-hover: color-mix(in srgb, rgb(var(--nodedc-accent-rgb)) 82%, white);
--bg-accent-primary-active: color-mix(in srgb, rgb(var(--nodedc-accent-rgb)) 90%, black);
--txt-on-color: rgb(var(--nodedc-on-accent-rgb));
--txt-icon-on-color: rgb(var(--nodedc-on-accent-rgb));
/* end background colors */
}
/* background colors */
@@ -507,7 +515,7 @@
.nodedc-toolbar-icon-button[data-active="true"] .nodedc-toolbar-icon-active-dot {
background: rgb(var(--nodedc-accent-rgb));
color: #0b1117;
color: rgb(var(--nodedc-on-accent-rgb));
}
.nodedc-toolbar-pill {
@@ -553,7 +561,7 @@
min-height: 2.5rem;
padding-inline: 1.55rem;
background: rgb(var(--nodedc-accent-rgb)) !important;
color: #0b1117 !important;
color: rgb(var(--nodedc-on-accent-rgb)) !important;
}
.nodedc-toolbar-primary-wide {
@@ -587,7 +595,7 @@
box-shadow: none !important;
border-radius: 1.25rem !important;
background: rgb(var(--nodedc-accent-rgb)) !important;
color: #0b1117 !important;
color: rgb(var(--nodedc-on-accent-rgb)) !important;
padding-inline: 1.25rem !important;
}
@@ -596,14 +604,17 @@
}
.nodedc-modal-primary-button,
.nodedc-modal-primary-button *,
.nodedc-modal-danger-button,
.nodedc-modal-danger-button *,
.nodedc-modal-primary-button *,
.nodedc-modal-danger-button * {
color: rgb(var(--nodedc-on-accent-rgb)) !important;
}
.nodedc-settings-primary-button,
.nodedc-settings-primary-button *,
.nodedc-settings-save-button,
.nodedc-settings-save-button * {
color: #0b1117 !important;
color: rgb(var(--nodedc-on-card-active-rgb)) !important;
}
.nodedc-modal-danger-button {
@@ -613,7 +624,7 @@
box-shadow: none !important;
border-radius: 1.25rem !important;
background: rgb(var(--nodedc-accent-rgb)) !important;
color: #0b1117 !important;
color: rgb(var(--nodedc-on-accent-rgb)) !important;
padding-inline: 1.25rem !important;
}
@@ -627,7 +638,7 @@
outline: none !important;
box-shadow: none !important;
background: rgb(var(--nodedc-accent-rgb)) !important;
color: #0b1117 !important;
color: rgb(var(--nodedc-on-accent-rgb)) !important;
}
.nodedc-glass-modal button.bg-danger-primary:hover,
@@ -637,7 +648,7 @@
.nodedc-glass-modal button.bg-danger-primary *,
.nodedc-glass-modal button.border-danger-strong * {
color: #0b1117 !important;
color: rgb(var(--nodedc-on-accent-rgb)) !important;
}
.nodedc-modal-chip {
@@ -761,13 +772,13 @@
box-shadow: none !important;
border-radius: 1.25rem !important;
background: rgb(var(--nodedc-card-active-rgb)) !important;
color: #0b1117 !important;
color: rgb(var(--nodedc-on-card-active-rgb)) !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;
color: rgb(var(--nodedc-on-card-active-rgb)) !important;
}
.nodedc-settings-save-button {
@@ -777,13 +788,13 @@
box-shadow: none !important;
border-radius: 1.25rem !important;
background: rgb(var(--nodedc-card-active-rgb)) !important;
color: #0b1117 !important;
color: rgb(var(--nodedc-on-card-active-rgb)) !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;
color: rgb(var(--nodedc-on-card-active-rgb)) !important;
}
.nodedc-overlay-button {
@@ -1014,12 +1025,12 @@
box-shadow: none !important;
border-radius: 1.25rem !important;
background: rgb(var(--nodedc-card-active-rgb)) !important;
color: #0b1117 !important;
color: rgb(var(--nodedc-on-card-active-rgb)) !important;
}
.nodedc-auth-primary-button:hover {
background: color-mix(in srgb, rgb(var(--nodedc-card-active-rgb)) 82%, white) !important;
color: #0b1117 !important;
color: rgb(var(--nodedc-on-card-active-rgb)) !important;
}
.nodedc-error-shell {
@@ -1055,13 +1066,13 @@
box-shadow: none !important;
border-radius: 1.2rem !important;
background: rgb(var(--nodedc-card-active-rgb)) !important;
color: #0b1117 !important;
color: rgb(var(--nodedc-on-card-active-rgb)) !important;
padding-inline: 1.35rem !important;
}
.nodedc-error-primary:hover {
background: color-mix(in srgb, rgb(var(--nodedc-card-active-rgb)) 82%, white) !important;
color: #0b1117 !important;
color: rgb(var(--nodedc-on-card-active-rgb)) !important;
}
.nodedc-empty-state-primary {
@@ -1071,13 +1082,13 @@
box-shadow: none !important;
border-radius: 1.2rem !important;
background: rgb(var(--nodedc-card-active-rgb)) !important;
color: #0b1117 !important;
color: rgb(var(--nodedc-on-card-active-rgb)) !important;
padding-inline: 1.35rem !important;
}
.nodedc-empty-state-primary:hover {
background: color-mix(in srgb, rgb(var(--nodedc-card-active-rgb)) 82%, white) !important;
color: #0b1117 !important;
color: rgb(var(--nodedc-on-card-active-rgb)) !important;
}
.nodedc-empty-state-secondary {
@@ -1143,14 +1154,14 @@
background:
linear-gradient(180deg, rgba(255, 255, 255, 0.024) 0%, rgba(255, 255, 255, 0.008) 100%),
rgb(var(--nodedc-card-active-rgb)) !important;
color: #0b1117 !important;
color: rgb(var(--nodedc-on-card-active-rgb)) !important;
box-shadow:
inset 0 0 0 1px rgba(var(--nodedc-accent-rgb), 0.32),
0 12px 32px rgba(0, 0, 0, 0.16) !important;
}
.nodedc-external-card[data-active="true"] .text-primary {
color: #0b1117 !important;
color: rgb(var(--nodedc-on-card-active-rgb)) !important;
}
.nodedc-external-card[data-active="true"] .text-secondary,
@@ -1278,19 +1289,19 @@
box-shadow: none !important;
border-radius: 1.35rem !important;
background: rgb(var(--nodedc-card-active-rgb)) !important;
color: #0b1117 !important;
color: rgb(var(--nodedc-on-card-active-rgb)) !important;
padding-inline: 1.6rem !important;
font-weight: 600 !important;
}
.nodedc-external-primary-button,
.nodedc-external-primary-button * {
color: #0b1117 !important;
color: rgb(var(--nodedc-on-card-active-rgb)) !important;
}
.nodedc-external-primary-button:hover {
background: color-mix(in srgb, rgb(var(--nodedc-card-active-rgb)) 82%, white) !important;
color: #0b1117 !important;
color: rgb(var(--nodedc-on-card-active-rgb)) !important;
}
.nodedc-external-empty-state {