Correct Hub and Engine component geometry
This commit is contained in:
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@nodedc/tokens",
|
||||
"version": "0.1.0",
|
||||
"version": "0.2.0",
|
||||
"type": "module",
|
||||
"files": ["tokens.json", "tokens.css", "themes.css"],
|
||||
"exports": {
|
||||
|
||||
@@ -16,6 +16,14 @@
|
||||
--nodedc-header-logo-width: 7.25rem;
|
||||
--nodedc-header-logo-height: 1.79rem;
|
||||
--nodedc-inspector-width: 24.375rem;
|
||||
--nodedc-inspector-inner-width: 20.625rem;
|
||||
--nodedc-inspector-label-width: 9.625rem;
|
||||
--nodedc-inspector-control-width: 10.125rem;
|
||||
--nodedc-inspector-control-gap: 0.875rem;
|
||||
--nodedc-inspector-section-radius: 0.75rem;
|
||||
--nodedc-inspector-button-radius: 1rem;
|
||||
--nodedc-select-toggle-width: 2.875rem;
|
||||
--nodedc-select-gap: 0.5rem;
|
||||
|
||||
--nodedc-space-1: 0.25rem;
|
||||
--nodedc-space-2: 0.5rem;
|
||||
@@ -53,4 +61,3 @@
|
||||
--nodedc-font-weight-medium: 620;
|
||||
--nodedc-font-weight-strong: 760;
|
||||
}
|
||||
|
||||
|
||||
@@ -26,7 +26,13 @@
|
||||
"headerPill": { "$type": "dimension", "$value": "55.2px" },
|
||||
"logoWidth": { "$type": "dimension", "$value": "116px" },
|
||||
"logoHeight": { "$type": "dimension", "$value": "28.64px" },
|
||||
"inspectorWidth": { "$type": "dimension", "$value": "390px" }
|
||||
"inspectorWidth": { "$type": "dimension", "$value": "390px" },
|
||||
"inspectorInnerWidth": { "$type": "dimension", "$value": "330px" },
|
||||
"inspectorLabelWidth": { "$type": "dimension", "$value": "154px" },
|
||||
"inspectorControlWidth": { "$type": "dimension", "$value": "162px" },
|
||||
"inspectorControlGap": { "$type": "dimension", "$value": "14px" },
|
||||
"selectToggleWidth": { "$type": "dimension", "$value": "46px" },
|
||||
"selectGap": { "$type": "dimension", "$value": "8px" }
|
||||
},
|
||||
"space": {
|
||||
"1": { "$type": "dimension", "$value": "4px" },
|
||||
@@ -58,4 +64,3 @@
|
||||
"toast": { "$type": "number", "$value": 950 }
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@nodedc/ui-core",
|
||||
"version": "0.1.0",
|
||||
"version": "0.2.0",
|
||||
"type": "module",
|
||||
"files": ["dist", "styles.css"],
|
||||
"main": "./dist/index.js",
|
||||
@@ -17,6 +17,6 @@
|
||||
"typecheck": "tsc -p tsconfig.json --noEmit"
|
||||
},
|
||||
"dependencies": {
|
||||
"@nodedc/tokens": "0.1.0"
|
||||
"@nodedc/tokens": "0.2.0"
|
||||
}
|
||||
}
|
||||
|
||||
+441
-27
@@ -15,13 +15,13 @@
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
:where(.nodedc-button, .nodedc-icon-button, .nodedc-checker, .nodedc-select-trigger, .nodedc-dropdown-option, .nodedc-segmented__item, .nodedc-header__nav-item, .nodedc-inspector__section-trigger, .nodedc-window__close) {
|
||||
: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) {
|
||||
border: 0;
|
||||
outline: 0;
|
||||
font: inherit;
|
||||
}
|
||||
|
||||
:where(.nodedc-button, .nodedc-icon-button, .nodedc-checker, .nodedc-select-trigger, .nodedc-dropdown-option, .nodedc-segmented__item, .nodedc-header__nav-item, .nodedc-inspector__section-trigger, .nodedc-window__close):focus-visible {
|
||||
: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);
|
||||
}
|
||||
@@ -126,6 +126,14 @@
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.nodedc-button[data-shape="pill"] {
|
||||
border-radius: var(--nodedc-radius-circle);
|
||||
}
|
||||
|
||||
.nodedc-button[data-shape="rounded"] {
|
||||
border-radius: var(--nodedc-inspector-button-radius);
|
||||
}
|
||||
|
||||
.nodedc-button[data-variant="primary"],
|
||||
.nodedc-button[data-variant="accent"] {
|
||||
--nodedc-button-bg: rgb(var(--nodedc-accent-rgb));
|
||||
@@ -313,7 +321,6 @@ textarea.nodedc-field__control {
|
||||
}
|
||||
|
||||
.nodedc-checker__indicator {
|
||||
position: relative;
|
||||
width: 1.625rem;
|
||||
height: 1.625rem;
|
||||
border-radius: var(--nodedc-radius-circle);
|
||||
@@ -321,29 +328,11 @@ textarea.nodedc-field__control {
|
||||
box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.3), inset 0 0 0 1px rgba(255, 255, 255, 0.035);
|
||||
}
|
||||
|
||||
.nodedc-checker__indicator::after {
|
||||
position: absolute;
|
||||
inset: 50% auto auto 50%;
|
||||
width: 0.375rem;
|
||||
height: 0.375rem;
|
||||
border-radius: var(--nodedc-radius-circle);
|
||||
background: rgb(var(--nodedc-on-accent-rgb));
|
||||
content: "";
|
||||
opacity: 0;
|
||||
transform: translate(-50%, -50%) scale(0.4);
|
||||
transition: opacity var(--nodedc-duration-fast) ease, transform var(--nodedc-duration-fast) ease;
|
||||
}
|
||||
|
||||
.nodedc-checker[aria-checked="true"] .nodedc-checker__indicator {
|
||||
background: rgb(var(--nodedc-accent-rgb));
|
||||
box-shadow: none;
|
||||
}
|
||||
|
||||
.nodedc-checker[aria-checked="true"] .nodedc-checker__indicator::after {
|
||||
opacity: 1;
|
||||
transform: translate(-50%, -50%) scale(1);
|
||||
}
|
||||
|
||||
.nodedc-range {
|
||||
--nodedc-range-progress: 50%;
|
||||
position: relative;
|
||||
@@ -601,6 +590,59 @@ textarea.nodedc-field__control {
|
||||
box-shadow: var(--nodedc-glass-control-shadow);
|
||||
}
|
||||
|
||||
.nodedc-header__profile .nodedc-icon-button {
|
||||
width: var(--nodedc-header-row-height);
|
||||
height: var(--nodedc-header-row-height);
|
||||
flex-basis: var(--nodedc-header-row-height);
|
||||
background: transparent;
|
||||
box-shadow: none;
|
||||
}
|
||||
|
||||
.nodedc-header__profile-button {
|
||||
display: inline-flex;
|
||||
min-height: calc(var(--nodedc-header-pill-height) - 0.64rem);
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
border: 0;
|
||||
border-radius: var(--nodedc-radius-circle);
|
||||
outline: 0;
|
||||
background: transparent;
|
||||
color: var(--nodedc-text-secondary);
|
||||
padding: 0.2rem 1.2rem;
|
||||
font: inherit;
|
||||
font-size: var(--nodedc-font-size-sm);
|
||||
font-weight: 700;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.nodedc-header__profile-button:hover,
|
||||
.nodedc-header__profile-button:focus-visible {
|
||||
background: var(--nodedc-glass-control-hover);
|
||||
color: var(--nodedc-text-primary);
|
||||
}
|
||||
|
||||
.nodedc-header__avatar {
|
||||
display: grid;
|
||||
width: var(--nodedc-header-row-height);
|
||||
height: var(--nodedc-header-row-height);
|
||||
flex: 0 0 var(--nodedc-header-row-height);
|
||||
place-items: center;
|
||||
overflow: hidden;
|
||||
border-radius: var(--nodedc-radius-circle);
|
||||
background:
|
||||
radial-gradient(circle at 70% 20%, rgba(255, 255, 255, 0.72), transparent 24%),
|
||||
linear-gradient(135deg, rgb(166, 194, 109), rgb(142, 123, 139));
|
||||
color: rgba(8, 8, 10, 0.96);
|
||||
font-size: 0.72rem;
|
||||
font-weight: 800;
|
||||
}
|
||||
|
||||
.nodedc-header__avatar img {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
object-fit: cover;
|
||||
}
|
||||
|
||||
.nodedc-header__workspace {
|
||||
display: inline-grid;
|
||||
width: var(--nodedc-header-row-height);
|
||||
@@ -649,10 +691,276 @@ textarea.nodedc-field__control {
|
||||
color: var(--nodedc-glass-control-active-text);
|
||||
}
|
||||
|
||||
.nodedc-admin-panel {
|
||||
position: relative;
|
||||
display: grid;
|
||||
width: min(22rem, calc(100vw - 2.5rem));
|
||||
height: 100%;
|
||||
min-height: 0;
|
||||
grid-template-rows: auto auto minmax(0, 1fr) auto;
|
||||
gap: 1.05rem;
|
||||
overflow: hidden;
|
||||
border: 0;
|
||||
border-radius: var(--nodedc-radius-card);
|
||||
background:
|
||||
linear-gradient(180deg, rgba(255, 255, 255, 0.035), rgba(255, 255, 255, 0.012)),
|
||||
rgba(10, 10, 13, 0.9);
|
||||
color: var(--nodedc-text-primary);
|
||||
padding: 1.1rem;
|
||||
box-shadow: 0 34px 110px rgba(0, 0, 0, 0.52);
|
||||
backdrop-filter: blur(28px);
|
||||
-webkit-backdrop-filter: blur(28px);
|
||||
animation: nodedc-admin-panel-in 420ms cubic-bezier(0.22, 1, 0.36, 1) both;
|
||||
}
|
||||
|
||||
@keyframes nodedc-admin-panel-in {
|
||||
from { opacity: 0; transform: translateX(-1.6rem); }
|
||||
to { opacity: 1; transform: translateX(0); }
|
||||
}
|
||||
|
||||
.nodedc-admin-panel__head {
|
||||
display: flex;
|
||||
align-items: flex-start;
|
||||
justify-content: space-between;
|
||||
gap: 3.05rem;
|
||||
}
|
||||
|
||||
.nodedc-admin-panel__head p,
|
||||
.nodedc-admin-panel__head h2 {
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.nodedc-admin-panel__head p {
|
||||
color: var(--nodedc-text-muted);
|
||||
font-size: 0.68rem;
|
||||
font-weight: 850;
|
||||
letter-spacing: 0.04em;
|
||||
text-transform: uppercase;
|
||||
}
|
||||
|
||||
.nodedc-admin-panel__head h2 {
|
||||
margin-top: 0.55rem;
|
||||
font-size: 1.15rem;
|
||||
font-weight: 780;
|
||||
line-height: 1.1;
|
||||
}
|
||||
|
||||
.nodedc-admin-panel__close {
|
||||
position: absolute;
|
||||
top: 5px;
|
||||
right: 5px;
|
||||
display: grid;
|
||||
width: 2.92rem;
|
||||
height: 2.92rem;
|
||||
place-items: center;
|
||||
border: 1px solid rgba(255, 255, 255, 0.22);
|
||||
border-radius: var(--nodedc-radius-circle);
|
||||
outline: 0;
|
||||
background: transparent;
|
||||
color: var(--nodedc-text-secondary);
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.nodedc-admin-panel__close:hover {
|
||||
border-color: rgba(255, 255, 255, 0.3);
|
||||
background: var(--nodedc-glass-control-hover);
|
||||
color: var(--nodedc-text-primary);
|
||||
}
|
||||
|
||||
.nodedc-admin-panel__contexts,
|
||||
.nodedc-admin-panel__context-group {
|
||||
display: grid;
|
||||
gap: 0.48rem;
|
||||
}
|
||||
|
||||
.nodedc-admin-panel__context-group {
|
||||
gap: 0.28rem;
|
||||
}
|
||||
|
||||
.nodedc-admin-panel__group-label {
|
||||
padding-inline: 0.35rem;
|
||||
color: var(--nodedc-text-muted);
|
||||
font-size: 0.68rem;
|
||||
font-weight: 850;
|
||||
letter-spacing: 0.12em;
|
||||
text-transform: uppercase;
|
||||
}
|
||||
|
||||
.nodedc-admin-context {
|
||||
display: grid;
|
||||
width: calc(100% + 2.2rem);
|
||||
min-height: 3.55rem;
|
||||
margin-inline: -1.1rem;
|
||||
grid-template-columns: minmax(0, 1fr);
|
||||
overflow: hidden;
|
||||
border-radius: var(--nodedc-radius-circle);
|
||||
background: rgba(255, 255, 255, 0.04);
|
||||
color: var(--nodedc-text-secondary);
|
||||
opacity: 0.72;
|
||||
}
|
||||
|
||||
.nodedc-admin-context[data-active="true"] {
|
||||
background: rgba(74, 74, 74, 0.5);
|
||||
color: var(--nodedc-text-primary);
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
.nodedc-admin-context--split {
|
||||
grid-template-columns: minmax(0, 1fr) 2.92rem;
|
||||
}
|
||||
|
||||
.nodedc-admin-context__main,
|
||||
.nodedc-admin-context__toggle {
|
||||
border: 0;
|
||||
outline: 0;
|
||||
background: transparent;
|
||||
color: inherit;
|
||||
font: inherit;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.nodedc-admin-context__main {
|
||||
display: flex;
|
||||
min-width: 0;
|
||||
min-height: 3.55rem;
|
||||
align-items: center;
|
||||
gap: 0.65rem;
|
||||
padding: 5px 0 5px 5px;
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
.nodedc-admin-context__toggle {
|
||||
display: grid;
|
||||
width: 2.92rem;
|
||||
place-items: center;
|
||||
border-radius: var(--nodedc-radius-circle);
|
||||
color: var(--nodedc-text-muted);
|
||||
}
|
||||
|
||||
.nodedc-admin-context__toggle:hover {
|
||||
background: var(--nodedc-glass-control-hover);
|
||||
color: var(--nodedc-text-primary);
|
||||
}
|
||||
|
||||
.nodedc-admin-context__icon,
|
||||
.nodedc-admin-panel__nav-icon {
|
||||
display: grid;
|
||||
width: 2.92rem;
|
||||
height: 2.92rem;
|
||||
flex: 0 0 2.92rem;
|
||||
place-items: center;
|
||||
border-radius: var(--nodedc-radius-circle);
|
||||
background: rgba(255, 255, 255, 0.06);
|
||||
color: var(--nodedc-text-secondary);
|
||||
}
|
||||
|
||||
.nodedc-admin-context__body {
|
||||
display: grid;
|
||||
min-width: 0;
|
||||
gap: 0.12rem;
|
||||
}
|
||||
|
||||
.nodedc-admin-context__body strong,
|
||||
.nodedc-admin-context__body small {
|
||||
min-width: 0;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.nodedc-admin-context__body strong {
|
||||
font-size: 0.92rem;
|
||||
font-weight: 800;
|
||||
}
|
||||
|
||||
.nodedc-admin-context__body small {
|
||||
color: var(--nodedc-text-muted);
|
||||
font-size: 0.72rem;
|
||||
font-weight: 750;
|
||||
}
|
||||
|
||||
.nodedc-admin-panel__nav {
|
||||
display: grid;
|
||||
width: calc(100% + 2.2rem);
|
||||
min-height: 0;
|
||||
margin-inline: -1.1rem;
|
||||
align-content: start;
|
||||
gap: 0.22rem;
|
||||
overflow-y: auto;
|
||||
scrollbar-width: none;
|
||||
}
|
||||
|
||||
.nodedc-admin-panel__nav::-webkit-scrollbar {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.nodedc-admin-panel__nav-item,
|
||||
.nodedc-admin-panel__footer {
|
||||
position: relative;
|
||||
display: flex;
|
||||
width: 100%;
|
||||
min-height: 3.55rem;
|
||||
align-items: center;
|
||||
gap: 0.86rem;
|
||||
border: 0;
|
||||
border-radius: var(--nodedc-radius-circle);
|
||||
background: rgba(255, 255, 255, 0.04);
|
||||
color: var(--nodedc-text-secondary);
|
||||
padding: 5px 0.9rem 5px calc(5px + 2.92rem + 0.86rem);
|
||||
font: inherit;
|
||||
font-size: 0.91rem;
|
||||
font-weight: 570;
|
||||
text-align: left;
|
||||
opacity: 0.66;
|
||||
}
|
||||
|
||||
.nodedc-admin-panel__nav-item {
|
||||
cursor: pointer;
|
||||
transition: background 160ms ease, color 160ms ease, opacity 160ms ease, transform 160ms ease;
|
||||
}
|
||||
|
||||
.nodedc-admin-panel__nav-item:hover {
|
||||
background: rgba(255, 255, 255, 0.085);
|
||||
color: var(--nodedc-text-primary);
|
||||
opacity: 0.92;
|
||||
transform: translateY(-1px);
|
||||
}
|
||||
|
||||
.nodedc-admin-panel__nav-item[data-active="true"] {
|
||||
background: rgba(255, 255, 255, 0.115);
|
||||
color: var(--nodedc-text-primary);
|
||||
opacity: 1;
|
||||
box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.048), 0 10px 22px rgba(0, 0, 0, 0.12);
|
||||
}
|
||||
|
||||
.nodedc-admin-panel__nav-icon {
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
left: 5px;
|
||||
background: rgba(255, 255, 255, 0.045);
|
||||
transform: translateY(-50%);
|
||||
}
|
||||
|
||||
.nodedc-admin-panel__nav-item[data-active="true"] .nodedc-admin-panel__nav-icon {
|
||||
background: rgba(247, 248, 244, 0.96);
|
||||
color: rgb(var(--nodedc-on-accent-rgb));
|
||||
}
|
||||
|
||||
.nodedc-admin-panel__footer {
|
||||
width: calc(100% + 2.2rem);
|
||||
margin-inline: -1.1rem;
|
||||
opacity: 0.82;
|
||||
}
|
||||
|
||||
.nodedc-dropdown-anchor {
|
||||
display: inline-flex;
|
||||
}
|
||||
|
||||
.nodedc-dropdown-anchor.nodedc-select-anchor {
|
||||
display: block;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.nodedc-dropdown-surface {
|
||||
position: fixed;
|
||||
z-index: var(--nodedc-layer-popover);
|
||||
@@ -756,6 +1064,83 @@ textarea.nodedc-field__control {
|
||||
line-height: 1.25;
|
||||
}
|
||||
|
||||
.nodedc-select {
|
||||
position: relative;
|
||||
width: 100%;
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
.nodedc-select__control {
|
||||
display: grid;
|
||||
width: 100%;
|
||||
grid-template-columns: minmax(0, 1fr) var(--nodedc-select-toggle-width);
|
||||
gap: var(--nodedc-select-gap);
|
||||
}
|
||||
|
||||
.nodedc-select__value {
|
||||
display: flex;
|
||||
height: var(--nodedc-control-height);
|
||||
min-width: 0;
|
||||
align-items: center;
|
||||
gap: var(--nodedc-space-2);
|
||||
overflow: hidden;
|
||||
border: 0;
|
||||
border-radius: var(--nodedc-radius-control-compact);
|
||||
background: var(--nodedc-field-bg);
|
||||
color: var(--nodedc-text-primary);
|
||||
padding: 0 0.95rem;
|
||||
box-shadow: var(--nodedc-glass-control-shadow);
|
||||
font-size: var(--nodedc-font-size-md);
|
||||
font-weight: var(--nodedc-font-weight-medium);
|
||||
line-height: 1;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.nodedc-select__value > span:last-child {
|
||||
min-width: 0;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.nodedc-select__toggle {
|
||||
display: inline-grid;
|
||||
width: var(--nodedc-select-toggle-width);
|
||||
height: var(--nodedc-control-height);
|
||||
place-items: center;
|
||||
border: 0;
|
||||
border-radius: var(--nodedc-radius-control-compact);
|
||||
outline: 0;
|
||||
background: var(--nodedc-glass-control-bg);
|
||||
color: var(--nodedc-text-primary);
|
||||
padding: 0;
|
||||
box-shadow: var(--nodedc-glass-control-shadow);
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.nodedc-select__toggle:hover:not(:disabled),
|
||||
.nodedc-select__toggle[aria-expanded="true"] {
|
||||
background: var(--nodedc-glass-control-hover);
|
||||
}
|
||||
|
||||
.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);
|
||||
}
|
||||
|
||||
.nodedc-dropdown-surface.nodedc-select__menu {
|
||||
gap: 0.125rem;
|
||||
border-radius: var(--nodedc-radius-control);
|
||||
background: var(--nodedc-field-bg);
|
||||
padding: 0.4375rem;
|
||||
}
|
||||
|
||||
.nodedc-select__menu .nodedc-dropdown-option {
|
||||
min-height: 2.625rem;
|
||||
border-radius: var(--nodedc-radius-control-compact);
|
||||
}
|
||||
|
||||
.nodedc-select-trigger {
|
||||
display: grid;
|
||||
width: 100%;
|
||||
@@ -799,6 +1184,10 @@ textarea.nodedc-field__control {
|
||||
transform: translateY(2px) rotate(225deg);
|
||||
}
|
||||
|
||||
.nodedc-select__toggle[aria-expanded="true"] .nodedc-select-trigger__chevron {
|
||||
transform: translateY(2px) rotate(225deg);
|
||||
}
|
||||
|
||||
.nodedc-overlay {
|
||||
position: fixed;
|
||||
z-index: var(--nodedc-layer-overlay);
|
||||
@@ -815,7 +1204,11 @@ textarea.nodedc-field__control {
|
||||
|
||||
.nodedc-overlay[data-placement="end"] {
|
||||
place-items: end;
|
||||
padding-top: 5.75rem;
|
||||
padding: 5.75rem 1.5rem 1.5rem;
|
||||
background: transparent;
|
||||
backdrop-filter: none;
|
||||
-webkit-backdrop-filter: none;
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
@keyframes nodedc-overlay-in {
|
||||
@@ -853,6 +1246,20 @@ textarea.nodedc-field__control {
|
||||
width: min(var(--nodedc-inspector-width), calc(100vw - 3rem));
|
||||
max-height: calc(100vh - 7.25rem);
|
||||
border-radius: var(--nodedc-radius-panel);
|
||||
pointer-events: auto;
|
||||
}
|
||||
|
||||
.nodedc-window[data-placement="end"] .nodedc-window__head {
|
||||
min-height: 4.875rem;
|
||||
padding: 1.125rem 0.75rem 0.875rem 1.625rem;
|
||||
}
|
||||
|
||||
.nodedc-window[data-placement="end"] .nodedc-window__body {
|
||||
padding: 0 0.75rem 1.125rem;
|
||||
}
|
||||
|
||||
.nodedc-window[data-placement="end"] .nodedc-window__close {
|
||||
border-radius: var(--nodedc-inspector-button-radius);
|
||||
}
|
||||
|
||||
@keyframes nodedc-window-in {
|
||||
@@ -998,6 +1405,7 @@ textarea.nodedc-field__control {
|
||||
|
||||
.nodedc-inspector {
|
||||
display: grid;
|
||||
width: min(var(--nodedc-inspector-inner-width), 100%);
|
||||
gap: 1px;
|
||||
}
|
||||
|
||||
@@ -1018,7 +1426,7 @@ textarea.nodedc-field__control {
|
||||
min-height: 3.125rem;
|
||||
align-content: center;
|
||||
gap: 0.2rem;
|
||||
border-radius: var(--nodedc-radius-control-compact);
|
||||
border-radius: var(--nodedc-inspector-section-radius);
|
||||
background: var(--nodedc-glass-control-bg);
|
||||
color: var(--nodedc-text-secondary);
|
||||
padding: 0.55rem 0.75rem;
|
||||
@@ -1033,7 +1441,8 @@ textarea.nodedc-field__control {
|
||||
color: var(--nodedc-text-primary);
|
||||
}
|
||||
|
||||
.nodedc-inspector__section-trigger[data-active="true"] {
|
||||
.nodedc-inspector__section-trigger[data-active="true"],
|
||||
.nodedc-inspector__section-trigger[data-tone="accent"] {
|
||||
background: rgb(var(--nodedc-accent-rgb));
|
||||
color: rgb(var(--nodedc-on-accent-rgb));
|
||||
}
|
||||
@@ -1066,10 +1475,11 @@ textarea.nodedc-field__control {
|
||||
|
||||
.nodedc-control-row {
|
||||
display: grid;
|
||||
width: min(var(--nodedc-inspector-inner-width), 100%);
|
||||
min-height: var(--nodedc-control-height);
|
||||
grid-template-columns: minmax(7rem, 1fr) minmax(10rem, 1.1fr);
|
||||
grid-template-columns: var(--nodedc-inspector-label-width) var(--nodedc-inspector-control-width);
|
||||
align-items: center;
|
||||
gap: var(--nodedc-space-3);
|
||||
gap: var(--nodedc-inspector-control-gap);
|
||||
}
|
||||
|
||||
.nodedc-control-row[data-layout="stack"] {
|
||||
@@ -1081,7 +1491,7 @@ textarea.nodedc-field__control {
|
||||
.nodedc-control-row__label {
|
||||
color: var(--nodedc-text-secondary);
|
||||
font-size: var(--nodedc-font-size-sm);
|
||||
font-weight: var(--nodedc-font-weight-medium);
|
||||
font-weight: var(--nodedc-font-weight-regular);
|
||||
line-height: 1.3;
|
||||
}
|
||||
|
||||
@@ -1129,6 +1539,10 @@ textarea.nodedc-field__control {
|
||||
padding: 0.875rem;
|
||||
}
|
||||
|
||||
.nodedc-window[data-placement="end"] .nodedc-window__body {
|
||||
padding-inline: 0.75rem;
|
||||
}
|
||||
|
||||
.nodedc-control-row {
|
||||
grid-template-columns: 1fr;
|
||||
align-items: stretch;
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@nodedc/ui-dom",
|
||||
"version": "0.1.0",
|
||||
"version": "0.2.0",
|
||||
"type": "module",
|
||||
"files": ["dist"],
|
||||
"main": "./dist/index.js",
|
||||
@@ -16,6 +16,6 @@
|
||||
"typecheck": "tsc -p tsconfig.json --noEmit"
|
||||
},
|
||||
"dependencies": {
|
||||
"@nodedc/ui-core": "0.1.0"
|
||||
"@nodedc/ui-core": "0.2.0"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@nodedc/ui-react",
|
||||
"version": "0.1.0",
|
||||
"version": "0.2.0",
|
||||
"type": "module",
|
||||
"files": ["dist"],
|
||||
"main": "./dist/index.js",
|
||||
@@ -16,7 +16,7 @@
|
||||
"typecheck": "tsc -p tsconfig.json --noEmit"
|
||||
},
|
||||
"dependencies": {
|
||||
"@nodedc/ui-core": "0.1.0"
|
||||
"@nodedc/ui-core": "0.2.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"react": ">=18",
|
||||
|
||||
@@ -0,0 +1,102 @@
|
||||
import type { HTMLAttributes, ReactNode } from "react";
|
||||
import { cn } from "./cn";
|
||||
|
||||
export interface AdminNavigationContext {
|
||||
id: string;
|
||||
label: string;
|
||||
description?: string;
|
||||
groupLabel?: string;
|
||||
icon?: ReactNode;
|
||||
active?: boolean;
|
||||
onSelect?: () => void;
|
||||
onToggle?: () => void;
|
||||
}
|
||||
|
||||
export interface AdminNavigationItem {
|
||||
id: string;
|
||||
label: string;
|
||||
icon?: ReactNode;
|
||||
}
|
||||
|
||||
export interface AdminNavigationPanelProps extends Omit<HTMLAttributes<HTMLElement>, "title"> {
|
||||
eyebrow?: string;
|
||||
title: ReactNode;
|
||||
contexts?: AdminNavigationContext[];
|
||||
items: AdminNavigationItem[];
|
||||
activeId?: string;
|
||||
footer?: ReactNode;
|
||||
closeLabel?: string;
|
||||
onClose: () => void;
|
||||
onItemChange: (id: string) => void;
|
||||
}
|
||||
|
||||
export function AdminNavigationPanel({
|
||||
eyebrow = "NODE.DC",
|
||||
title,
|
||||
contexts = [],
|
||||
items,
|
||||
activeId,
|
||||
footer,
|
||||
closeLabel = "Закрыть администрирование",
|
||||
onClose,
|
||||
onItemChange,
|
||||
className,
|
||||
...props
|
||||
}: AdminNavigationPanelProps) {
|
||||
return (
|
||||
<aside className={cn("nodedc-admin-panel", className)} {...props}>
|
||||
<header className="nodedc-admin-panel__head">
|
||||
<div>
|
||||
<p>{eyebrow}</p>
|
||||
<h2>{title}</h2>
|
||||
</div>
|
||||
<button type="button" className="nodedc-admin-panel__close" aria-label={closeLabel} onClick={onClose}>
|
||||
<span className="nodedc-close-mark" aria-hidden="true" />
|
||||
</button>
|
||||
</header>
|
||||
|
||||
<div className="nodedc-admin-panel__contexts">
|
||||
{contexts.map((context) => (
|
||||
<div className="nodedc-admin-panel__context-group" key={context.id}>
|
||||
{context.groupLabel ? <span className="nodedc-admin-panel__group-label">{context.groupLabel}</span> : null}
|
||||
<div
|
||||
className={cn("nodedc-admin-context", context.onToggle && "nodedc-admin-context--split")}
|
||||
data-active={context.active ? "true" : undefined}
|
||||
>
|
||||
<button type="button" className="nodedc-admin-context__main" onClick={context.onSelect}>
|
||||
<span className="nodedc-admin-context__icon" aria-hidden="true">{context.icon}</span>
|
||||
<span className="nodedc-admin-context__body">
|
||||
<strong>{context.label}</strong>
|
||||
{context.description ? <small>{context.description}</small> : null}
|
||||
</span>
|
||||
</button>
|
||||
{context.onToggle ? (
|
||||
<button type="button" className="nodedc-admin-context__toggle" aria-label={`Выбрать: ${context.label}`} onClick={context.onToggle}>
|
||||
<span className="nodedc-select-trigger__chevron" aria-hidden="true" />
|
||||
</button>
|
||||
) : null}
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
|
||||
<nav className="nodedc-admin-panel__nav" aria-label="Разделы администрирования">
|
||||
{items.map((item) => (
|
||||
<button
|
||||
key={item.id}
|
||||
type="button"
|
||||
className="nodedc-admin-panel__nav-item"
|
||||
data-active={activeId === item.id ? "true" : undefined}
|
||||
onClick={() => onItemChange(item.id)}
|
||||
>
|
||||
<span className="nodedc-admin-panel__nav-icon" aria-hidden="true">{item.icon}</span>
|
||||
<span>{item.label}</span>
|
||||
</button>
|
||||
))}
|
||||
</nav>
|
||||
|
||||
{footer ? <footer className="nodedc-admin-panel__footer">{footer}</footer> : null}
|
||||
</aside>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import type { HTMLAttributes, ReactNode } from "react";
|
||||
import type { ButtonHTMLAttributes, HTMLAttributes, ReactNode } from "react";
|
||||
import { cn } from "./cn";
|
||||
|
||||
export interface AppHeaderProps extends HTMLAttributes<HTMLElement> {
|
||||
@@ -41,6 +41,23 @@ export function HeaderProfile({ children, className, ...props }: HTMLAttributes<
|
||||
return <div className={cn("nodedc-header__profile", className)} {...props}>{children}</div>;
|
||||
}
|
||||
|
||||
export function HeaderProfileButton({ className, children, type = "button", ...props }: ButtonHTMLAttributes<HTMLButtonElement>) {
|
||||
return <button type={type} className={cn("nodedc-header__profile-button", className)} {...props}>{children}</button>;
|
||||
}
|
||||
|
||||
export interface HeaderAvatarProps extends HTMLAttributes<HTMLSpanElement> {
|
||||
label: string;
|
||||
imageUrl?: string;
|
||||
}
|
||||
|
||||
export function HeaderAvatar({ label, imageUrl, className, ...props }: HeaderAvatarProps) {
|
||||
return (
|
||||
<span className={cn("nodedc-header__avatar", className)} title={label} {...props}>
|
||||
{imageUrl ? <img src={imageUrl} alt="" /> : label.slice(0, 2).toUpperCase()}
|
||||
</span>
|
||||
);
|
||||
}
|
||||
|
||||
export interface HeaderWorkspaceProps extends HTMLAttributes<HTMLSpanElement> {
|
||||
label: string;
|
||||
imageUrl?: string;
|
||||
@@ -53,4 +70,3 @@ export function HeaderWorkspace({ label, imageUrl, className, ...props }: Header
|
||||
</span>
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -4,11 +4,13 @@ import { cn } from "./cn";
|
||||
|
||||
export type ButtonVariant = "primary" | "secondary" | "ghost" | "danger" | "accent";
|
||||
export type ButtonSize = "default" | "compact";
|
||||
export type ButtonShape = "default" | "pill" | "rounded";
|
||||
|
||||
export interface ButtonProps extends ButtonHTMLAttributes<HTMLButtonElement> {
|
||||
variant?: ButtonVariant;
|
||||
size?: ButtonSize;
|
||||
width?: "auto" | "full";
|
||||
shape?: ButtonShape;
|
||||
accent?: RgbTuple;
|
||||
icon?: ReactNode;
|
||||
}
|
||||
@@ -17,6 +19,7 @@ export const Button = forwardRef<HTMLButtonElement, ButtonProps>(function Button
|
||||
variant = "secondary",
|
||||
size = "default",
|
||||
width = "auto",
|
||||
shape = "default",
|
||||
accent,
|
||||
icon,
|
||||
className,
|
||||
@@ -34,6 +37,7 @@ export const Button = forwardRef<HTMLButtonElement, ButtonProps>(function Button
|
||||
data-variant={variant}
|
||||
data-size={size === "default" ? undefined : size}
|
||||
data-width={width === "auto" ? undefined : width}
|
||||
data-shape={shape === "default" ? undefined : shape}
|
||||
style={accentStyle ? { ...accentStyle, ...style } : style}
|
||||
{...props}
|
||||
>
|
||||
|
||||
@@ -8,6 +8,7 @@ export interface InspectorSectionSpec {
|
||||
group?: string;
|
||||
content: ReactNode;
|
||||
disabled?: boolean;
|
||||
tone?: "default" | "accent";
|
||||
}
|
||||
|
||||
export interface InspectorProps {
|
||||
@@ -64,6 +65,7 @@ export function Inspector({
|
||||
className="nodedc-inspector__section-trigger"
|
||||
data-open={isOpen ? "true" : undefined}
|
||||
data-active={activeId === section.id ? "true" : undefined}
|
||||
data-tone={section.tone === "accent" ? "accent" : undefined}
|
||||
aria-expanded={isOpen}
|
||||
disabled={section.disabled}
|
||||
onClick={() => toggle(section.id)}
|
||||
@@ -98,4 +100,3 @@ export function ControlRow({ label, children, layout = "inline", className }: Co
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -11,6 +11,8 @@ export interface SelectOption<T extends string> {
|
||||
disabled?: boolean;
|
||||
}
|
||||
|
||||
export type SelectVariant = "integrated" | "split";
|
||||
|
||||
export interface SelectProps<T extends string> {
|
||||
value: T;
|
||||
options: Array<SelectOption<T>>;
|
||||
@@ -23,6 +25,7 @@ export interface SelectProps<T extends string> {
|
||||
minMenuWidth?: number;
|
||||
menuWidth?: number | "anchor";
|
||||
disabled?: boolean;
|
||||
variant?: SelectVariant;
|
||||
className?: string;
|
||||
triggerClassName?: string;
|
||||
menuClassName?: string;
|
||||
@@ -40,6 +43,7 @@ export function Select<T extends string>({
|
||||
minMenuWidth = 180,
|
||||
menuWidth = "anchor",
|
||||
disabled = false,
|
||||
variant = "integrated",
|
||||
className,
|
||||
triggerClassName,
|
||||
menuClassName,
|
||||
@@ -54,13 +58,13 @@ export function Select<T extends string>({
|
||||
|
||||
return (
|
||||
<Dropdown
|
||||
className={className}
|
||||
className={cn("nodedc-select-anchor", className)}
|
||||
placement={placement}
|
||||
minWidth={minMenuWidth}
|
||||
width={menuWidth}
|
||||
disabled={disabled}
|
||||
surfaceRole="listbox"
|
||||
surfaceClassName={menuClassName}
|
||||
surfaceClassName={cn(variant === "split" && "nodedc-select__menu", menuClassName)}
|
||||
trigger={({ open, toggle, setTriggerRef, surfaceId }) => {
|
||||
const handleKeyDown = (event: KeyboardEvent<HTMLButtonElement>) => {
|
||||
if (event.key === "ArrowDown" || event.key === "Enter" || event.key === " ") {
|
||||
@@ -68,6 +72,32 @@ export function Select<T extends string>({
|
||||
if (!open) toggle();
|
||||
}
|
||||
};
|
||||
if (variant === "split") {
|
||||
return (
|
||||
<div className={cn("nodedc-select", triggerClassName)}>
|
||||
<div className="nodedc-select__control">
|
||||
<div ref={setTriggerRef} className="nodedc-select__value">
|
||||
{selected?.icon ? <span className="nodedc-select__value-icon">{selected.icon}</span> : null}
|
||||
<span>{selected?.label ?? "—"}</span>
|
||||
</div>
|
||||
<button
|
||||
type="button"
|
||||
className="nodedc-select__toggle"
|
||||
aria-label={label}
|
||||
aria-haspopup="listbox"
|
||||
aria-controls={surfaceId}
|
||||
aria-expanded={open}
|
||||
disabled={disabled}
|
||||
onClick={toggle}
|
||||
onKeyDown={handleKeyDown}
|
||||
>
|
||||
<span className="nodedc-select-trigger__chevron" aria-hidden="true" />
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<button
|
||||
ref={setTriggerRef}
|
||||
@@ -126,4 +156,3 @@ export function Select<T extends string>({
|
||||
</Dropdown>
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -33,6 +33,7 @@ export interface WindowProps extends Omit<HTMLAttributes<HTMLDivElement>, "title
|
||||
closeOnBackdrop?: boolean;
|
||||
closeOnEscape?: boolean;
|
||||
lockBodyScroll?: boolean;
|
||||
trapFocus?: boolean;
|
||||
onClose: () => void;
|
||||
}
|
||||
|
||||
@@ -45,9 +46,10 @@ export function Window({
|
||||
size = "md",
|
||||
placement = "center",
|
||||
closeLabel = "Закрыть",
|
||||
closeOnBackdrop = true,
|
||||
closeOnBackdrop,
|
||||
closeOnEscape = true,
|
||||
lockBodyScroll = true,
|
||||
lockBodyScroll,
|
||||
trapFocus,
|
||||
onClose,
|
||||
className,
|
||||
...props
|
||||
@@ -55,12 +57,14 @@ export function Window({
|
||||
const titleId = useId();
|
||||
const descriptionId = useId();
|
||||
const dialogRef = useRef<HTMLDivElement>(null);
|
||||
const shouldLockBodyScroll = lockBodyScroll ?? placement === "center";
|
||||
const shouldTrapFocus = trapFocus ?? placement === "center";
|
||||
|
||||
useEffect(() => {
|
||||
if (!open || typeof document === "undefined") return;
|
||||
const previousActiveElement = document.activeElement instanceof HTMLElement ? document.activeElement : null;
|
||||
const previousOverflow = document.body.style.overflow;
|
||||
if (lockBodyScroll) document.body.style.overflow = "hidden";
|
||||
if (shouldLockBodyScroll) document.body.style.overflow = "hidden";
|
||||
|
||||
const frame = window.requestAnimationFrame(() => {
|
||||
const firstFocusable = dialogRef.current?.querySelector<HTMLElement>(focusableSelector);
|
||||
@@ -73,7 +77,7 @@ export function Window({
|
||||
onClose();
|
||||
return;
|
||||
}
|
||||
if (event.key !== "Tab" || !dialogRef.current) return;
|
||||
if (event.key !== "Tab" || !shouldTrapFocus || !dialogRef.current) return;
|
||||
const focusable = Array.from(dialogRef.current.querySelectorAll<HTMLElement>(focusableSelector));
|
||||
if (focusable.length === 0) {
|
||||
event.preventDefault();
|
||||
@@ -95,15 +99,17 @@ export function Window({
|
||||
return () => {
|
||||
window.cancelAnimationFrame(frame);
|
||||
document.removeEventListener("keydown", handleKeyDown);
|
||||
if (lockBodyScroll) document.body.style.overflow = previousOverflow;
|
||||
if (shouldLockBodyScroll) document.body.style.overflow = previousOverflow;
|
||||
previousActiveElement?.focus();
|
||||
};
|
||||
}, [closeOnEscape, lockBodyScroll, onClose, open]);
|
||||
}, [closeOnEscape, onClose, open, shouldLockBodyScroll, shouldTrapFocus]);
|
||||
|
||||
if (!open || typeof document === "undefined") return null;
|
||||
|
||||
const allowBackdropClose = closeOnBackdrop ?? placement === "center";
|
||||
|
||||
const handleBackdropPointerDown = (event: PointerEvent<HTMLDivElement>) => {
|
||||
if (closeOnBackdrop && event.target === event.currentTarget) onClose();
|
||||
if (allowBackdropClose && event.target === event.currentTarget) onClose();
|
||||
};
|
||||
|
||||
return createPortal(
|
||||
@@ -114,7 +120,7 @@ export function Window({
|
||||
data-size={size === "md" ? undefined : size}
|
||||
data-placement={placement}
|
||||
role="dialog"
|
||||
aria-modal="true"
|
||||
aria-modal={placement === "center" ? "true" : undefined}
|
||||
aria-labelledby={titleId}
|
||||
aria-describedby={subtitle ? descriptionId : undefined}
|
||||
tabIndex={-1}
|
||||
@@ -140,4 +146,3 @@ export function Window({
|
||||
export function WindowFooterActions({ children, className, ...props }: HTMLAttributes<HTMLDivElement>) {
|
||||
return <div className={cn("nodedc-window__footer-actions", className)} {...props}>{children}</div>;
|
||||
}
|
||||
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
export * from "./AppHeader";
|
||||
export * from "./AdminNavigationPanel";
|
||||
export * from "./Button";
|
||||
export * from "./Checker";
|
||||
export * from "./ConfirmationModal";
|
||||
@@ -11,4 +12,3 @@ export * from "./SegmentedControl";
|
||||
export * from "./Select";
|
||||
export * from "./StatusBadge";
|
||||
export * from "./Window";
|
||||
|
||||
|
||||
Reference in New Issue
Block a user