Refine glass material and environment controls

This commit is contained in:
DCCONSTRUCTIONS
2026-07-10 21:05:04 +03:00
parent 4c74c22a6e
commit 0e030136bb
9 changed files with 350 additions and 112 deletions
+44 -22
View File
@@ -14,25 +14,32 @@
--nodedc-text-muted: rgba(247, 248, 244, 0.48);
--nodedc-text-disabled: rgba(247, 248, 244, 0.34);
--nodedc-glass-panel-bg: rgba(28, 28, 30, 0.72);
--nodedc-glass-panel-bg-strong: rgba(18, 18, 21, 0.88);
--nodedc-glass-panel-bg-soft: rgba(42, 42, 46, 0.5);
/* Runtime material controls override only these five values. */
--nodedc-panel-material-rgb: 22 22 26;
--nodedc-panel-material-opacity: 0.68;
--nodedc-panel-material-strong-opacity: 0.8;
--nodedc-panel-material-soft-opacity: 0.48;
--nodedc-field-material-rgb: 16 16 22;
--nodedc-field-material-opacity: 0.78;
--nodedc-glass-panel-bg: rgb(var(--nodedc-panel-material-rgb) / var(--nodedc-panel-material-opacity));
--nodedc-glass-panel-bg-strong: rgb(var(--nodedc-panel-material-rgb) / var(--nodedc-panel-material-strong-opacity));
--nodedc-glass-panel-bg-soft: rgb(var(--nodedc-panel-material-rgb) / var(--nodedc-panel-material-soft-opacity));
--nodedc-header-pill-bg: rgba(64, 64, 64, 0.48);
--nodedc-glass-panel-surface: linear-gradient(180deg, rgba(255, 255, 255, 0.055), rgba(255, 255, 255, 0.014)), var(--nodedc-glass-panel-bg);
--nodedc-glass-panel-surface-strong: linear-gradient(180deg, rgba(255, 255, 255, 0.068), rgba(255, 255, 255, 0.018)), var(--nodedc-glass-panel-bg-strong);
--nodedc-glass-panel-surface: radial-gradient(circle at 18% 0%, rgba(255, 255, 255, 0.075), transparent 38%), linear-gradient(180deg, rgba(255, 255, 255, 0.06), rgba(255, 255, 255, 0.012)), var(--nodedc-glass-panel-bg);
--nodedc-glass-panel-surface-strong: radial-gradient(circle at 16% 0%, rgba(255, 255, 255, 0.09), transparent 36%), linear-gradient(180deg, rgba(255, 255, 255, 0.075), rgba(255, 255, 255, 0.016)), var(--nodedc-glass-panel-bg-strong);
--nodedc-glass-control-bg: rgba(110, 110, 110, 0.07);
--nodedc-glass-control-hover: rgba(129, 129, 129, 0.13);
--nodedc-glass-control-active: rgba(255, 255, 255, 0.92);
--nodedc-glass-control-active-text: rgba(8, 8, 10, 0.96);
--nodedc-field-bg: rgba(20, 20, 27, 0.96);
--nodedc-field-bg: linear-gradient(180deg, rgba(255, 255, 255, 0.022), transparent), rgb(var(--nodedc-field-material-rgb) / var(--nodedc-field-material-opacity));
--nodedc-overlay-bg: rgba(0, 0, 0, 0.46);
--nodedc-glass-rim: linear-gradient(180deg, rgba(255, 255, 255, 0.09), rgba(255, 255, 255, 0.026));
--nodedc-glass-rim: linear-gradient(180deg, rgba(255, 255, 255, 0.18), rgba(255, 255, 255, 0.035));
--nodedc-glass-control-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.025);
--nodedc-glass-panel-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.075), 0 22px 72px rgba(0, 0, 0, 0.42);
--nodedc-glass-panel-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.11), 0 28px 90px rgba(0, 0, 0, 0.38);
--nodedc-glass-dropdown-shadow: 0 22px 60px rgba(0, 0, 0, 0.42), 0 6px 18px rgba(0, 0, 0, 0.2);
--nodedc-focus-surface: rgba(var(--nodedc-accent-rgb), 0.14);
--nodedc-focus-surface: rgb(var(--nodedc-accent-rgb) / 0.14);
--nodedc-glass-outline: rgba(255, 255, 255, 0.11);
--nodedc-app-panel-surface: linear-gradient(180deg, rgba(255, 255, 255, 0.035), rgba(255, 255, 255, 0.012)), rgba(10, 10, 13, 0.9);
--nodedc-app-panel-surface: var(--nodedc-glass-panel-surface-strong);
--nodedc-app-panel-shadow: 0 34px 110px rgba(0, 0, 0, 0.42);
--nodedc-app-panel-shadow-strong: 0 34px 110px rgba(0, 0, 0, 0.52);
--nodedc-panel-action-border: rgba(255, 255, 255, 0.22);
@@ -44,6 +51,10 @@
--nodedc-panel-active-icon-bg: rgba(247, 248, 244, 0.96);
--nodedc-panel-active-icon-color: rgb(var(--nodedc-on-accent-rgb));
--nodedc-scrollbar-thumb: rgba(255, 255, 255, 0.15);
--nodedc-primary-action-bg: rgb(var(--nodedc-accent-rgb));
--nodedc-primary-action-hover: color-mix(in srgb, rgb(var(--nodedc-accent-rgb)) 84%, white);
--nodedc-primary-action-color: rgb(var(--nodedc-on-accent-rgb));
--nodedc-primary-action-shadow: 0 12px 28px rgb(var(--nodedc-accent-rgb) / 0.2);
}
[data-nodedc-theme="light"] {
@@ -61,25 +72,32 @@
--nodedc-text-muted: rgba(18, 20, 22, 0.48);
--nodedc-text-disabled: rgba(18, 20, 22, 0.32);
--nodedc-glass-panel-bg: rgba(255, 255, 255, 0.48);
--nodedc-glass-panel-bg-strong: rgba(255, 255, 255, 0.72);
--nodedc-glass-panel-bg-soft: rgba(255, 255, 255, 0.34);
/* The light values follow the SEO launcher material: white glass, soft depth. */
--nodedc-panel-material-rgb: 255 255 255;
--nodedc-panel-material-opacity: 0.62;
--nodedc-panel-material-strong-opacity: 0.8;
--nodedc-panel-material-soft-opacity: 0.46;
--nodedc-field-material-rgb: 255 255 255;
--nodedc-field-material-opacity: 0.72;
--nodedc-glass-panel-bg: rgb(var(--nodedc-panel-material-rgb) / var(--nodedc-panel-material-opacity));
--nodedc-glass-panel-bg-strong: rgb(var(--nodedc-panel-material-rgb) / var(--nodedc-panel-material-strong-opacity));
--nodedc-glass-panel-bg-soft: rgb(var(--nodedc-panel-material-rgb) / var(--nodedc-panel-material-soft-opacity));
--nodedc-header-pill-bg: rgba(255, 255, 255, 0.56);
--nodedc-glass-panel-surface: linear-gradient(180deg, rgba(255, 255, 255, 0.58), rgba(255, 255, 255, 0.22)), var(--nodedc-glass-panel-bg);
--nodedc-glass-panel-surface-strong: linear-gradient(180deg, rgba(255, 255, 255, 0.76), rgba(255, 255, 255, 0.34)), var(--nodedc-glass-panel-bg-strong);
--nodedc-glass-control-bg: linear-gradient(180deg, rgba(255, 255, 255, 0.54), rgba(255, 255, 255, 0.22)), rgba(255, 255, 255, 0.38);
--nodedc-glass-control-hover: linear-gradient(180deg, rgba(255, 255, 255, 0.72), rgba(255, 255, 255, 0.34)), rgba(255, 255, 255, 0.54);
--nodedc-glass-panel-surface: radial-gradient(circle at 18% 0%, rgba(255, 255, 255, 0.92), transparent 45%), linear-gradient(180deg, rgba(255, 255, 255, 0.76), rgba(255, 255, 255, 0.26)), var(--nodedc-glass-panel-bg);
--nodedc-glass-panel-surface-strong: radial-gradient(circle at 18% 0%, rgba(255, 255, 255, 0.98), transparent 45%), linear-gradient(180deg, rgba(255, 255, 255, 0.9), rgba(255, 255, 255, 0.36)), var(--nodedc-glass-panel-bg-strong);
--nodedc-glass-control-bg: linear-gradient(180deg, rgba(255, 255, 255, 0.88), rgba(255, 255, 255, 0.46)), rgba(255, 255, 255, 0.7);
--nodedc-glass-control-hover: linear-gradient(180deg, rgba(255, 255, 255, 0.98), rgba(255, 255, 255, 0.58)), rgba(255, 255, 255, 0.84);
--nodedc-glass-control-active: rgba(32, 32, 34, 0.96);
--nodedc-glass-control-active-text: rgba(247, 248, 244, 0.96);
--nodedc-field-bg: rgba(255, 255, 255, 0.58);
--nodedc-field-bg: linear-gradient(180deg, rgba(255, 255, 255, 0.78), rgba(255, 255, 255, 0.3)), rgb(var(--nodedc-field-material-rgb) / var(--nodedc-field-material-opacity));
--nodedc-overlay-bg: rgba(24, 32, 29, 0.22);
--nodedc-glass-rim: linear-gradient(180deg, rgba(255, 255, 255, 0.9), rgba(24, 32, 29, 0.1));
--nodedc-glass-control-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.68);
--nodedc-glass-rim: linear-gradient(180deg, rgba(255, 255, 255, 0.98), rgba(24, 32, 29, 0.1));
--nodedc-glass-control-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.88), 0 10px 22px rgba(24, 32, 29, 0.08);
--nodedc-glass-panel-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.72), 0 22px 76px rgba(24, 32, 29, 0.16);
--nodedc-glass-dropdown-shadow: 0 22px 60px rgba(24, 32, 29, 0.2), 0 6px 18px rgba(24, 32, 29, 0.1);
--nodedc-focus-surface: rgba(var(--nodedc-accent-rgb), 0.1);
--nodedc-focus-surface: rgb(var(--nodedc-accent-rgb) / 0.1);
--nodedc-glass-outline: rgba(24, 32, 29, 0.12);
--nodedc-app-panel-surface: linear-gradient(180deg, rgba(255, 255, 255, 0.58) 0%, rgba(255, 255, 255, 0.22) 100%), rgba(255, 255, 255, 0.48);
--nodedc-app-panel-surface: var(--nodedc-glass-panel-surface-strong);
--nodedc-app-panel-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.72), 0 22px 76px rgba(24, 32, 29, 0.16);
--nodedc-app-panel-shadow-strong: inset 0 1px 0 rgba(255, 255, 255, 0.72), 0 24px 82px rgba(24, 32, 29, 0.2);
--nodedc-panel-action-border: rgba(24, 32, 29, 0.12);
@@ -91,4 +109,8 @@
--nodedc-panel-active-icon-bg: rgba(32, 32, 34, 0.96);
--nodedc-panel-active-icon-color: rgba(247, 248, 244, 0.96);
--nodedc-scrollbar-thumb: rgba(24, 32, 29, 0.22);
--nodedc-primary-action-bg: rgba(24, 24, 24, 0.94);
--nodedc-primary-action-hover: rgba(8, 8, 10, 0.98);
--nodedc-primary-action-color: rgba(247, 248, 244, 0.96);
--nodedc-primary-action-shadow: none;
}
+50 -6
View File
@@ -2,23 +2,57 @@ import { createAccentVariables, type RgbTuple } from "./accent.js";
export type NodedcTheme = "dark" | "light";
export interface NodedcMaterial {
/** Base fill used by glass panels, application windows and modals. */
panel?: RgbTuple;
/** 01 alpha for the base glass panel. */
panelOpacity?: number;
/** Base fill used by text, range and selection fields. */
field?: RgbTuple;
/** 01 alpha for field fill. */
fieldOpacity?: number;
}
export interface ApplyThemeOptions {
theme?: NodedcTheme;
accent?: RgbTuple;
material?: NodedcMaterial;
}
function formatRgb([red, green, blue]: RgbTuple): string {
return `${red} ${green} ${blue}`;
}
function clampOpacity(value: number): number {
return Math.max(0, Math.min(1, value));
}
export function applyNodedcTheme(
target: HTMLElement,
{ theme = "dark", accent }: ApplyThemeOptions = {},
{ theme = "dark", accent, material }: ApplyThemeOptions = {},
): void {
target.dataset.nodedcTheme = theme;
target.dataset.nodedcUi = "true";
if (!accent) return;
const variables = createAccentVariables(accent);
Object.entries(variables).forEach(([property, value]) => {
target.style.setProperty(property, value);
});
if (accent) {
const variables = createAccentVariables(accent);
Object.entries(variables).forEach(([property, value]) => {
target.style.setProperty(property, value);
});
}
if (!material) return;
if (material.panel) target.style.setProperty("--nodedc-panel-material-rgb", formatRgb(material.panel));
if (typeof material.panelOpacity === "number" && Number.isFinite(material.panelOpacity)) {
const opacity = clampOpacity(material.panelOpacity);
target.style.setProperty("--nodedc-panel-material-opacity", String(opacity));
target.style.setProperty("--nodedc-panel-material-strong-opacity", String(clampOpacity(opacity + 0.12)));
target.style.setProperty("--nodedc-panel-material-soft-opacity", String(clampOpacity(Math.max(0.12, opacity - 0.2))));
}
if (material.field) target.style.setProperty("--nodedc-field-material-rgb", formatRgb(material.field));
if (typeof material.fieldOpacity === "number" && Number.isFinite(material.fieldOpacity)) {
target.style.setProperty("--nodedc-field-material-opacity", String(clampOpacity(material.fieldOpacity)));
}
}
export function clearNodedcAccent(target: HTMLElement): void {
@@ -26,3 +60,13 @@ export function clearNodedcAccent(target: HTMLElement): void {
target.style.removeProperty("--nodedc-on-accent-rgb");
}
export function clearNodedcMaterial(target: HTMLElement): void {
[
"--nodedc-panel-material-rgb",
"--nodedc-panel-material-opacity",
"--nodedc-panel-material-strong-opacity",
"--nodedc-panel-material-soft-opacity",
"--nodedc-field-material-rgb",
"--nodedc-field-material-opacity",
].forEach((property) => target.style.removeProperty(property));
}
+74 -34
View File
@@ -23,7 +23,7 @@
:where(.nodedc-button, .nodedc-icon-button, .nodedc-checker, .nodedc-select-trigger, .nodedc-select__toggle, .nodedc-dropdown-option, .nodedc-segmented__item, .nodedc-header__nav-item, .nodedc-inspector__section-trigger, .nodedc-window__close):focus-visible {
background-color: var(--nodedc-focus-surface);
box-shadow: var(--nodedc-glass-control-shadow), inset 0 0 0 1px rgba(var(--nodedc-accent-rgb), 0.22);
box-shadow: var(--nodedc-glass-control-shadow), inset 0 0 0 1px rgb(var(--nodedc-accent-rgb) / 0.22);
}
.nodedc-material-rim {
@@ -54,8 +54,8 @@
background: var(--nodedc-glass-panel-surface);
color: var(--nodedc-text-primary);
box-shadow: var(--nodedc-glass-panel-shadow);
backdrop-filter: blur(var(--nodedc-blur-panel)) saturate(1.24) brightness(1.04);
-webkit-backdrop-filter: blur(var(--nodedc-blur-panel)) saturate(1.24) brightness(1.04);
backdrop-filter: blur(var(--nodedc-blur-panel)) saturate(1.28) brightness(1.05);
-webkit-backdrop-filter: blur(var(--nodedc-blur-panel)) saturate(1.28) brightness(1.05);
}
.nodedc-glass[data-tone="strong"] {
@@ -136,12 +136,24 @@
.nodedc-button[data-variant="primary"],
.nodedc-button[data-variant="accent"] {
--nodedc-button-bg: rgb(var(--nodedc-accent-rgb));
--nodedc-button-color: rgb(var(--nodedc-on-accent-rgb));
box-shadow: none;
}
.nodedc-button[data-variant="primary"]:hover:not(:disabled),
.nodedc-button[data-variant="primary"] {
--nodedc-button-bg: var(--nodedc-primary-action-bg);
--nodedc-button-color: var(--nodedc-primary-action-color);
box-shadow: var(--nodedc-primary-action-shadow);
}
.nodedc-button[data-variant="accent"] {
--nodedc-button-bg: rgb(var(--nodedc-accent-rgb));
--nodedc-button-color: rgb(var(--nodedc-on-accent-rgb));
}
.nodedc-button[data-variant="primary"]:hover:not(:disabled) {
background: var(--nodedc-primary-action-hover);
}
.nodedc-button[data-variant="accent"]:hover:not(:disabled) {
background: color-mix(in srgb, rgb(var(--nodedc-accent-rgb)) 84%, white);
}
@@ -159,7 +171,7 @@
}
.nodedc-button[data-variant="danger"]:hover:not(:disabled) {
background: rgba(var(--nodedc-danger-rgb), 0.12);
background: rgb(var(--nodedc-danger-rgb) / 0.12);
}
.nodedc-button:disabled,
@@ -279,7 +291,7 @@
.nodedc-field__control:focus-visible {
background: var(--nodedc-focus-surface);
box-shadow: var(--nodedc-glass-control-shadow), inset 0 0 0 1px rgba(var(--nodedc-accent-rgb), 0.22);
box-shadow: var(--nodedc-glass-control-shadow), inset 0 0 0 1px rgb(var(--nodedc-accent-rgb) / 0.22);
}
textarea.nodedc-field__control {
@@ -366,8 +378,8 @@ textarea.nodedc-field__control {
align-items: center;
justify-content: center;
border-radius: var(--nodedc-radius-circle);
background: rgba(255, 255, 255, 0.92);
color: rgba(8, 8, 10, 0.96);
background: var(--nodedc-primary-action-bg);
color: var(--nodedc-primary-action-color);
padding: 0 1rem;
font-size: 0.78rem;
font-weight: 850;
@@ -376,7 +388,7 @@ textarea.nodedc-field__control {
}
.nodedc-media-file__button:hover {
background: #fff;
background: var(--nodedc-primary-action-hover);
}
.nodedc-media-file__name {
@@ -433,9 +445,9 @@ textarea.nodedc-field__control {
}
.nodedc-media-source-button[data-active="true"] {
border-color: rgba(255, 255, 255, 0.92);
background: rgba(255, 255, 255, 0.92);
color: rgba(8, 8, 10, 0.96);
border-color: var(--nodedc-primary-action-bg);
background: var(--nodedc-primary-action-bg);
color: var(--nodedc-primary-action-color);
}
.nodedc-media-preview {
@@ -744,7 +756,7 @@ textarea.nodedc-field__control {
.nodedc-color-field__picker[data-open="true"],
.nodedc-color-field__picker:focus-visible {
box-shadow: 0 0 0 2px rgba(var(--nodedc-accent-rgb), 0.3), 0 0 0 3px var(--nodedc-canvas);
box-shadow: 0 0 0 2px rgb(var(--nodedc-accent-rgb) / 0.3), 0 0 0 3px var(--nodedc-canvas);
}
.nodedc-color-field__picker:disabled {
@@ -780,7 +792,7 @@ textarea.nodedc-field__control {
}
.nodedc-color-palette__spectrum:focus-visible {
box-shadow: 0 0 0 2px rgba(var(--nodedc-accent-rgb), 0.28);
box-shadow: 0 0 0 2px rgb(var(--nodedc-accent-rgb) / 0.28);
}
.nodedc-color-palette__marker {
@@ -881,22 +893,22 @@ textarea.nodedc-field__control {
}
.nodedc-status[data-tone="success"] {
background: rgba(var(--nodedc-success-rgb), 0.1);
background: rgb(var(--nodedc-success-rgb) / 0.1);
color: color-mix(in srgb, rgb(var(--nodedc-success-rgb)) 74%, var(--nodedc-text-primary));
}
.nodedc-status[data-tone="warning"] {
background: rgba(var(--nodedc-warning-rgb), 0.1);
background: rgb(var(--nodedc-warning-rgb) / 0.1);
color: color-mix(in srgb, rgb(var(--nodedc-warning-rgb)) 74%, var(--nodedc-text-primary));
}
.nodedc-status[data-tone="danger"] {
background: rgba(var(--nodedc-danger-rgb), 0.1);
background: rgb(var(--nodedc-danger-rgb) / 0.1);
color: color-mix(in srgb, rgb(var(--nodedc-danger-rgb)) 74%, var(--nodedc-text-primary));
}
.nodedc-status[data-tone="accent"] {
background: rgba(var(--nodedc-accent-rgb), 0.1);
background: rgb(var(--nodedc-accent-rgb) / 0.1);
color: color-mix(in srgb, rgb(var(--nodedc-accent-rgb)) 76%, var(--nodedc-text-primary));
}
@@ -1682,7 +1694,7 @@ textarea.nodedc-field__control {
}
.nodedc-dropdown-option[data-selected="true"] {
background: rgba(var(--nodedc-accent-rgb), 0.14);
background: rgb(var(--nodedc-accent-rgb) / 0.14);
}
.nodedc-dropdown-option__icon,
@@ -1773,7 +1785,7 @@ textarea.nodedc-field__control {
.nodedc-select__toggle:focus-visible {
background: var(--nodedc-focus-surface);
box-shadow: var(--nodedc-glass-control-shadow), inset 0 0 0 1px rgba(var(--nodedc-accent-rgb), 0.22);
box-shadow: var(--nodedc-glass-control-shadow), inset 0 0 0 1px rgb(var(--nodedc-accent-rgb) / 0.22);
}
.nodedc-dropdown-surface.nodedc-select__menu {
@@ -1876,8 +1888,8 @@ textarea.nodedc-field__control {
background: var(--nodedc-glass-panel-surface-strong);
color: var(--nodedc-text-primary);
box-shadow: var(--nodedc-glass-panel-shadow);
backdrop-filter: blur(var(--nodedc-blur-modal)) saturate(1.35) brightness(1.04);
-webkit-backdrop-filter: blur(var(--nodedc-blur-modal)) saturate(1.35) brightness(1.04);
backdrop-filter: blur(var(--nodedc-blur-modal)) saturate(1.38) brightness(1.06);
-webkit-backdrop-filter: blur(var(--nodedc-blur-modal)) saturate(1.38) brightness(1.06);
animation: nodedc-window-in var(--nodedc-duration-normal) var(--nodedc-ease-standard);
}
@@ -1922,11 +1934,11 @@ textarea.nodedc-field__control {
.nodedc-window__head {
display: grid;
min-height: 4.75rem;
min-height: 4.25rem;
grid-template-columns: minmax(0, 1fr) var(--nodedc-icon-button-size);
align-items: center;
gap: var(--nodedc-space-3);
padding: 1rem 1rem 0.75rem 1.5rem;
padding: 0.8rem 0.9rem 0.55rem 1.15rem;
}
.nodedc-window__titles {
@@ -2005,7 +2017,7 @@ textarea.nodedc-field__control {
.nodedc-window__body {
min-height: 0;
overflow: auto;
padding: 0.75rem 1.5rem 1.5rem;
padding: 0.5rem 1.15rem 0.9rem;
scrollbar-width: thin;
scrollbar-color: rgba(255, 255, 255, 0.14) transparent;
}
@@ -2015,7 +2027,7 @@ textarea.nodedc-field__control {
align-items: center;
justify-content: space-between;
gap: var(--nodedc-space-3);
padding: 0.9rem 1.5rem 1.25rem;
padding: 0.65rem 1.15rem 0.9rem;
}
.nodedc-window__footer-actions {
@@ -2026,7 +2038,7 @@ textarea.nodedc-field__control {
}
.nodedc-window.nodedc-share-access-window {
width: min(37.5rem, calc(100vw - 2rem));
width: min(35rem, calc(100vw - 2rem));
}
@media (min-width: 641px) {
@@ -2038,7 +2050,7 @@ textarea.nodedc-field__control {
.nodedc-share-access {
display: grid;
gap: 1rem;
gap: 0.65rem;
}
.nodedc-share-access__members,
@@ -2052,7 +2064,7 @@ textarea.nodedc-field__control {
.nodedc-share-access__members {
position: relative;
z-index: 20;
gap: 0.62rem;
gap: 0.38rem;
}
.nodedc-share-access__section-label {
@@ -2203,9 +2215,9 @@ textarea.nodedc-field__control {
.nodedc-share-access__invite {
display: grid;
grid-template-columns: minmax(0, 1fr) 11rem;
grid-template-columns: minmax(0, 1fr) 9.5rem;
align-items: end;
gap: 0.75rem;
gap: 0.55rem;
}
.nodedc-share-access__readonly {
@@ -2244,7 +2256,7 @@ textarea.nodedc-field__control {
height: 2.75rem;
place-items: center;
border-radius: var(--nodedc-radius-control-compact);
background: rgba(var(--nodedc-danger-rgb), 0.1);
background: rgb(var(--nodedc-danger-rgb) / 0.1);
color: rgb(var(--nodedc-danger-rgb));
}
@@ -2335,6 +2347,11 @@ textarea.nodedc-field__control {
gap: var(--nodedc-inspector-control-gap);
}
.nodedc-control-row[data-action="true"] {
grid-template-columns: minmax(6.4rem, 1fr) minmax(0, 1fr) var(--nodedc-control-height);
gap: var(--nodedc-space-2);
}
.nodedc-control-row[data-layout="stack"] {
grid-template-columns: 1fr;
align-items: stretch;
@@ -2352,6 +2369,12 @@ textarea.nodedc-field__control {
min-width: 0;
}
.nodedc-control-row__action {
display: grid;
min-width: 0;
place-items: center;
}
.nodedc-empty-state {
display: grid;
min-height: 12rem;
@@ -2494,6 +2517,23 @@ textarea.nodedc-field__control {
gap: var(--nodedc-space-2);
}
.nodedc-control-row[data-action="true"] {
grid-template-columns: minmax(0, 1fr) var(--nodedc-control-height);
}
.nodedc-control-row[data-action="true"] .nodedc-control-row__label {
grid-column: 1 / -1;
}
.nodedc-control-row[data-action="true"] .nodedc-control-row__control {
grid-column: 1;
}
.nodedc-control-row[data-action="true"] .nodedc-control-row__action {
grid-column: 2;
grid-row: 2;
}
.nodedc-media-control {
grid-template-columns: minmax(0, 1fr) auto;
border-radius: var(--nodedc-radius-card);
+1 -2
View File
@@ -59,7 +59,7 @@ export function ConfirmationModal({
<span />
<WindowFooterActions>
<Button disabled={pending} onClick={onClose}>{cancelLabel}</Button>
<Button variant={danger ? "danger" : "accent"} disabled={pending} onClick={handleConfirm}>
<Button variant={danger ? "danger" : "primary"} disabled={pending} onClick={handleConfirm}>
{pending ? pendingLabel : confirmLabel}
</Button>
</WindowFooterActions>
@@ -73,4 +73,3 @@ export function ConfirmationModal({
</Window>
);
}
+9 -2
View File
@@ -88,15 +88,22 @@ export function Inspector({
export interface ControlRowProps {
label: ReactNode;
children: ReactNode;
/** Optional trailing action, for example a per-control save-to-layout affordance. */
action?: ReactNode;
layout?: "inline" | "stack";
className?: string;
}
export function ControlRow({ label, children, layout = "inline", className }: ControlRowProps) {
export function ControlRow({ label, children, action, layout = "inline", className }: ControlRowProps) {
return (
<div className={cn("nodedc-control-row", className)} data-layout={layout === "inline" ? undefined : layout}>
<div
className={cn("nodedc-control-row", className)}
data-layout={layout === "inline" ? undefined : layout}
data-action={action ? "true" : undefined}
>
<div className="nodedc-control-row__label">{label}</div>
<div className="nodedc-control-row__control">{children}</div>
{action ? <div className="nodedc-control-row__action">{action}</div> : null}
</div>
);
}
+6 -3
View File
@@ -4,6 +4,8 @@ import { Dropdown } from "./Dropdown.js";
export interface RangeControlProps extends Omit<InputHTMLAttributes<HTMLInputElement>, "type" | "value" | "onChange"> {
label: string;
/** Keeps the accessible name while letting a parent ControlRow render the visible label. */
showLabel?: boolean;
value: number;
min: number;
max: number;
@@ -13,6 +15,7 @@ export interface RangeControlProps extends Omit<InputHTMLAttributes<HTMLInputEle
export function RangeControl({
label,
showLabel = true,
value,
min,
max,
@@ -26,7 +29,7 @@ export function RangeControl({
const progress = Math.max(0, Math.min(100, ((value - min) / (safeMax - min)) * 100));
const style = { "--nodedc-range-progress": `${progress}%` } as CSSProperties;
return (
<label className={cn("nodedc-range", className)} style={style}>
<label className={cn("nodedc-range", className)} data-label-hidden={showLabel ? undefined : "true"} style={style}>
<input
type="range"
value={value}
@@ -37,10 +40,10 @@ export function RangeControl({
onChange={(event) => onChange(Number(event.target.value))}
{...props}
/>
<span className="nodedc-range__label">{label}</span>
{showLabel ? <span className="nodedc-range__label">{label}</span> : null}
<span className="nodedc-range__value">{formatValue(value)}</span>
<span className="nodedc-range__fill-text" aria-hidden="true">
<span className="nodedc-range__label">{label}</span>
{showLabel ? <span className="nodedc-range__label">{label}</span> : null}
<span className="nodedc-range__value">{formatValue(value)}</span>
</span>
</label>
+2 -2
View File
@@ -97,7 +97,7 @@ export function ShareAccessModal<Role extends string>({
<WindowFooterActions>
<Button onClick={onClose} disabled={pending}>Отменить</Button>
{canManage ? (
<Button variant="accent" onClick={() => void onInvite()} disabled={pending || !inviteEmail.trim()}>
<Button variant="primary" onClick={() => void onInvite()} disabled={pending || !inviteEmail.trim()}>
{pending ? "Сохранение..." : "Поделиться"}
</Button>
) : null}
@@ -233,7 +233,7 @@ export function ShareLinkModal({
<>
<Button onClick={onClose}>Закрыть</Button>
<WindowFooterActions>
<Button variant="accent" icon={<Icon name={copied ? "check" : "copy"} />} onClick={() => void copy()} disabled={!link}>
<Button variant="primary" icon={<Icon name={copied ? "check" : "copy"} />} onClick={() => void copy()} disabled={!link}>
{copied ? "Скопировано" : copyLabel}
</Button>
</WindowFooterActions>