snapshot: preserve dynamic builder and section ordering work

This commit is contained in:
Md Bayazid Bostame
2026-03-27 16:54:11 +01:00
parent fdc27f2123
commit 30877ed8ee
13 changed files with 1391 additions and 365 deletions

View File

@@ -109,14 +109,31 @@ body {
color: #166534;
}
.builder-overview {
.builder-summary-strip {
margin-top: 12px;
display: grid;
grid-template-columns: repeat(4, minmax(0, 1fr));
gap: 12px;
display: flex;
flex-wrap: wrap;
gap: 8px;
}
.builder-summary-pill {
display: inline-flex;
align-items: center;
gap: 6px;
min-height: 34px;
padding: 0 12px;
border: 1px solid #d5dfec;
border-radius: 999px;
background: #f8fbff;
color: #304159;
font-size: 13px;
font-weight: 700;
}
.builder-summary-pill strong {
color: #101c30;
}
.builder-stat-card,
.builder-panel,
.options-panel {
border: 1px solid rgba(201, 212, 226, 0.95);
@@ -125,34 +142,6 @@ body {
box-shadow: 0 10px 24px rgba(15, 23, 42, 0.05);
}
.builder-stat-card {
padding: 14px 16px;
transition: transform 0.2s ease, box-shadow 0.2s ease, border-color 0.2s ease;
}
.builder-stat-card:hover {
transform: translateY(-2px);
border-color: rgba(146, 170, 199, 0.95);
box-shadow: 0 16px 28px rgba(15, 23, 42, 0.08);
}
.builder-stat-label {
display: block;
color: #65758f;
font-size: 12px;
font-weight: 700;
letter-spacing: 0.03em;
text-transform: uppercase;
}
.builder-stat-card strong {
display: block;
margin-top: 6px;
font-size: 28px;
line-height: 1;
color: #101c30;
}
.builder-panel-head h2,
.options-head h2 {
margin: 0;
@@ -371,6 +360,18 @@ body {
gap: 12px;
}
.preview-shell-compact .preview-section {
border-radius: 12px;
}
.preview-shell-compact .preview-section-head {
padding: 10px 12px;
}
.preview-shell-compact .preview-chip-list {
padding: 12px;
}
.preview-section {
border: 1px solid #d7e0ec;
border-radius: 14px;
@@ -500,6 +501,169 @@ body {
justify-content: flex-end;
}
.conditional-rule-grid {
display: grid;
gap: 12px;
}
.conditional-rule-card {
border: 1px solid #d7e0ec;
border-radius: 16px;
background: linear-gradient(180deg, #fbfdff 0%, #ffffff 100%);
padding: 14px;
display: grid;
gap: 12px;
transition: transform 0.2s ease, box-shadow 0.2s ease, border-color 0.2s ease;
}
.conditional-rule-card:hover {
transform: translateY(-1px);
border-color: #bfd0e4;
box-shadow: 0 14px 26px rgba(15, 23, 42, 0.06);
}
.conditional-rule-head {
display: flex;
align-items: flex-start;
justify-content: space-between;
gap: 16px;
padding-bottom: 10px;
border-bottom: 1px solid #e6edf6;
}
.conditional-rule-head-main {
min-width: 0;
}
.conditional-rule-head h3 {
margin: 2px 0 4px;
font-size: 16px;
color: #142033;
}
.conditional-rule-eyebrow {
display: inline-flex;
align-items: center;
min-height: 24px;
padding: 0 9px;
border-radius: 999px;
background: #eef4ff;
color: #214d99;
font-size: 11px;
font-weight: 800;
letter-spacing: 0.04em;
text-transform: uppercase;
}
.conditional-toggle {
display: inline-flex;
align-items: center;
gap: 8px;
color: #5f7089;
font-size: 12px;
font-weight: 800;
white-space: nowrap;
}
.conditional-toggle input[type='checkbox'] {
width: 16px;
height: 16px;
}
.conditional-targets {
display: grid;
gap: 8px;
}
.conditional-target-label {
color: #5f7089;
font-size: 12px;
font-weight: 800;
text-transform: uppercase;
letter-spacing: 0.04em;
}
.conditional-target-chips {
display: flex;
flex-wrap: wrap;
gap: 8px;
}
.conditional-clause-list {
display: grid;
gap: 10px;
}
.conditional-clause-row {
display: grid;
grid-template-columns: 56px minmax(220px, 1.35fr) minmax(180px, 0.8fr) minmax(180px, 0.85fr);
gap: 10px;
align-items: end;
padding: 12px;
border: 1px solid #e5ebf3;
border-radius: 14px;
background: #f8fbff;
}
.conditional-clause-index {
display: inline-flex;
align-items: center;
min-height: 38px;
color: #33506f;
font-size: 12px;
font-weight: 800;
text-transform: uppercase;
letter-spacing: 0.04em;
}
.conditional-clause-control {
display: grid;
gap: 6px;
}
.conditional-clause-control span {
color: #5f7089;
font-size: 12px;
font-weight: 800;
}
.conditional-clause-control select,
.conditional-clause-control input[type='text'] {
width: 100%;
min-height: 40px;
}
.conditional-extra-clause {
border: 1px dashed #d7e0ec;
border-radius: 14px;
background: #fbfdff;
}
.conditional-extra-clause summary {
list-style: none;
cursor: pointer;
padding: 10px 12px;
color: #35506f;
font-size: 12px;
font-weight: 800;
letter-spacing: 0.03em;
text-transform: uppercase;
}
.conditional-extra-clause summary::-webkit-details-marker {
display: none;
}
.conditional-extra-clause[open] summary {
border-bottom: 1px solid #e6edf6;
}
.conditional-extra-clause .conditional-clause-row {
border: 0;
border-radius: 0 0 14px 14px;
background: transparent;
}
.columns {
display: grid;
grid-template-columns: repeat(4, minmax(220px, 1fr));
@@ -728,35 +892,212 @@ body {
justify-content: flex-end;
}
.section-rule-grid {
.builder-entity-form {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(220px, 1fr));
gap: 10px;
gap: 12px;
margin-bottom: 14px;
padding: 14px;
border: 1px solid #d7e0ec;
border-radius: 16px;
background: linear-gradient(180deg, #fbfdff 0%, #ffffff 100%);
}
.section-rule-card {
.builder-entity-head h3,
.builder-group-head h3 {
margin: 0;
font-size: 16px;
color: #142033;
}
.builder-entity-head .mini {
margin-top: 4px;
}
.builder-entity-grid {
display: grid;
grid-template-columns: repeat(3, minmax(0, 1fr));
gap: 12px;
}
.builder-entity-control {
display: grid;
gap: 6px;
}
.builder-entity-control span {
color: #5f7089;
font-size: 12px;
font-weight: 800;
}
.builder-entity-control-narrow {
max-width: 180px;
}
.builder-entity-control-full {
grid-column: 1 / -1;
}
.builder-card-list,
.builder-group-stack {
display: grid;
gap: 12px;
}
.builder-group-card {
border: 1px solid #d7e0ec;
border-radius: 16px;
background: linear-gradient(180deg, #f8fbff 0%, #ffffff 100%);
overflow: hidden;
}
.builder-group-head {
display: flex;
align-items: center;
justify-content: space-between;
gap: 10px;
padding: 12px 14px;
border-bottom: 1px solid #dfe7f1;
background: #f2f7ff;
}
.builder-entity-card {
padding: 14px;
border: 1px solid #e5ebf3;
border-radius: 16px;
background: #ffffff;
display: grid;
gap: 12px;
}
.builder-entity-card-head {
display: flex;
align-items: flex-start;
justify-content: space-between;
gap: 14px;
}
.builder-entity-card-head strong {
color: #142033;
font-size: 15px;
}
.entity-meta {
margin-top: 4px;
color: #64748b;
font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
font-size: 12px;
overflow-wrap: anywhere;
}
.builder-switch,
.builder-switch-inline {
display: inline-flex;
align-items: center;
gap: 8px;
color: #5f7089;
font-size: 12px;
font-weight: 800;
}
.builder-switch-stack {
display: grid;
gap: 8px;
}
.builder-switch input[type='checkbox'],
.builder-switch-inline input[type='checkbox'] {
width: 16px;
height: 16px;
}
.builder-entity-card-actions {
display: flex;
justify-content: flex-end;
}
.builder-empty-state {
padding: 14px;
border: 1px dashed #d7e0ec;
border-radius: 14px;
background: #fbfdff;
color: #64748b;
font-size: 13px;
}
.section-rule-grid {
display: flex;
flex-direction: column;
gap: 10px;
}
.section-rule-grid.drag-over {
outline: 1px dashed #9db4d2;
outline-offset: 6px;
}
.section-rule-card {
display: grid;
grid-template-columns: auto minmax(0, 1fr) auto;
align-items: center;
gap: 12px;
padding: 13px 14px;
border: 1px solid #d6e0ec;
border-radius: 14px;
background: linear-gradient(180deg, #f9fbff, #ffffff);
cursor: move;
transition: transform 0.16s ease, box-shadow 0.16s ease, border-color 0.16s ease;
width: 100%;
}
.section-rule-card:hover {
transform: translateY(-1px);
box-shadow: 0 10px 20px rgba(15, 23, 42, 0.06);
border-color: #b8cae0;
}
.section-rule-card.is-locked {
background: linear-gradient(180deg, #f4f7fb, #fafcff);
}
.section-rule-card.dragging {
opacity: 0.58;
}
.section-rule-card.manual-dragging {
opacity: 0.72;
border-color: #8fb1d8;
box-shadow: 0 16px 30px rgba(15, 23, 42, 0.10);
}
.section-rule-drag {
color: #8aa0be;
font-size: 18px;
letter-spacing: -2px;
user-select: none;
align-self: stretch;
display: inline-flex;
align-items: center;
padding-right: 2px;
cursor: grab;
}
body.builder-dragging,
body.builder-dragging * {
cursor: grabbing !important;
user-select: none !important;
}
.section-rule-copy {
display: grid;
gap: 4px;
min-width: 0;
}
.section-rule-copy strong {
color: #0f172a;
font-size: 14px;
overflow-wrap: anywhere;
}
.section-rule-copy span,
@@ -772,6 +1113,11 @@ body {
gap: 8px;
}
.section-rule-checkbox {
display: inline-flex;
align-items: center;
}
@keyframes builderFadeIn {
from {
opacity: 0;
@@ -821,10 +1167,6 @@ body {
}
@media (max-width: 1220px) {
.builder-overview {
grid-template-columns: repeat(2, minmax(0, 1fr));
}
.builder-rule-layout,
.columns {
grid-template-columns: repeat(2, minmax(220px, 1fr));
@@ -834,7 +1176,8 @@ body {
@media (max-width: 900px) {
.builder-hero,
.builder-panel-head,
.options-head {
.options-head,
.conditional-rule-head {
flex-direction: column;
align-items: flex-start;
}
@@ -846,15 +1189,20 @@ body {
.builder-rule-layout {
grid-template-columns: 1fr;
}
.builder-entity-card-head {
flex-direction: column;
align-items: flex-start;
}
}
@media (max-width: 760px) {
.builder-overview,
.columns {
grid-template-columns: 1fr;
}
.field-rule-row {
.field-rule-row,
.conditional-clause-row {
grid-template-columns: 1fr;
}
@@ -862,7 +1210,26 @@ body {
justify-content: flex-start;
}
.conditional-clause-index {
min-height: auto;
}
.add-option-form {
grid-template-columns: 1fr;
}
.builder-entity-grid {
grid-template-columns: 1fr;
}
.builder-entity-control-narrow,
.builder-entity-control-full {
max-width: none;
grid-column: auto;
}
.builder-group-head {
flex-direction: column;
align-items: flex-start;
}
}

View File

@@ -145,4 +145,95 @@
}
});
}
const sectionRuleGrid = document.getElementById('section-rule-grid');
if (sectionRuleGrid) {
let draggingSectionCard = null;
let manualDraggingSectionCard = null;
function getSectionInsertBeforeNode(mouseY) {
const cards = Array.from(sectionRuleGrid.querySelectorAll('.section-rule-card:not(.dragging)'));
return cards.find((card) => {
const box = card.getBoundingClientRect();
return mouseY < box.top + box.height / 2;
});
}
function getManualSectionInsertBeforeNode(mouseY) {
const cards = Array.from(sectionRuleGrid.querySelectorAll('.section-rule-card:not(.manual-dragging)'));
return cards.find((card) => {
const box = card.getBoundingClientRect();
return mouseY < box.top + box.height / 2;
});
}
function onManualSectionMove(event) {
if (!manualDraggingSectionCard) return;
event.preventDefault();
sectionRuleGrid.classList.add('drag-over');
const beforeNode = getManualSectionInsertBeforeNode(event.clientY);
if (beforeNode) {
sectionRuleGrid.insertBefore(manualDraggingSectionCard, beforeNode);
} else {
sectionRuleGrid.appendChild(manualDraggingSectionCard);
}
}
function endManualSectionDrag() {
if (!manualDraggingSectionCard) return;
manualDraggingSectionCard.classList.remove('manual-dragging');
manualDraggingSectionCard = null;
sectionRuleGrid.classList.remove('drag-over');
document.body.classList.remove('builder-dragging');
document.removeEventListener('mousemove', onManualSectionMove);
document.removeEventListener('mouseup', endManualSectionDrag);
}
sectionRuleGrid.querySelectorAll('.section-rule-card').forEach((card) => {
card.addEventListener('dragstart', (event) => {
draggingSectionCard = card;
card.classList.add('dragging');
event.dataTransfer.effectAllowed = 'move';
event.dataTransfer.setData('text/plain', card.dataset.sectionKey || '');
});
card.addEventListener('dragend', () => {
card.classList.remove('dragging');
sectionRuleGrid.classList.remove('drag-over');
draggingSectionCard = null;
});
const handle = card.querySelector('.section-rule-drag');
if (handle) {
handle.addEventListener('mousedown', (event) => {
event.preventDefault();
manualDraggingSectionCard = card;
card.classList.add('manual-dragging');
sectionRuleGrid.classList.add('drag-over');
document.body.classList.add('builder-dragging');
document.addEventListener('mousemove', onManualSectionMove);
document.addEventListener('mouseup', endManualSectionDrag);
});
}
});
sectionRuleGrid.addEventListener('dragover', (event) => {
event.preventDefault();
sectionRuleGrid.classList.add('drag-over');
if (!draggingSectionCard) return;
const beforeNode = getSectionInsertBeforeNode(event.clientY);
if (beforeNode) {
sectionRuleGrid.insertBefore(draggingSectionCard, beforeNode);
} else {
sectionRuleGrid.appendChild(draggingSectionCard);
}
});
sectionRuleGrid.addEventListener('dragleave', () => {
sectionRuleGrid.classList.remove('drag-over');
});
sectionRuleGrid.addEventListener('drop', (event) => {
event.preventDefault();
sectionRuleGrid.classList.remove('drag-over');
});
}
})();