05/04 Update form creation page

This commit is contained in:
2026-05-04 15:35:41 -04:00
parent ca9cdc6d71
commit 756107c174
+90 -22
View File
@@ -36,28 +36,45 @@
width: max-content;
}
/* ── iPad / touch-device fluid grid override ── */
/* ── iPad / touch-device fluid grid override ──
--fg-w is set by ResizeObserver in JS (see bottom of file),
which measures the actual .insp-body clientWidth — no vw/scrollbar ambiguity.
Falls back to a safe 100% - padding calculation if JS hasn't run yet. */
@media (max-width: 1194px) {
.form-grid {
/* Tighter column gap (4px) so empty intermediate columns between fields
don't create a large visual gap — e.g. between rating stars and upload */
--_gap: 4px;
--_cell: calc((min(calc(100vw - 2rem), 900px) - 11 * 4px) / 12);
grid-template-columns: repeat(12, var(--_cell));
grid-auto-rows: minmax(calc(var(--_cell) * 0.72), auto);
gap: 4px var(--_gap);
--fg-gap: 4px;
--fg-w: calc(100% - 2rem); /* JS overrides this with measured px value */
--fg-cell: calc((var(--fg-w) - 11 * var(--fg-gap)) / 12);
grid-template-columns: repeat(12, var(--fg-cell));
grid-auto-rows: minmax(calc(var(--fg-cell) * 0.72), auto);
gap: var(--fg-gap);
width: 100%;
}
.insp-body { padding: 1rem; }
/* Rating stars: scale down to fit compressed grid cells on iPad */
.rating-stars button { font-size: .85rem; }
.rating-stars button { font-size: .9rem; min-height: 36px; min-width: 28px; }
/* Pass/fail pills: allow wrapping on narrow cells */
.pf-group { flex-wrap: wrap !important; gap: .4rem !important; }
.pf-btn { flex-shrink: 0; }
/* Image upload cells: don't clip the chosen-file row */
.upload-zone { overflow: visible; }
/* Touch: larger tap targets for checkboxes and radios */
.form-check-input { width: 1.1em; height: 1.1em; margin-top: .15rem; }
}
.fg-cell {
overflow:hidden; display:flex; flex-direction:column; padding:.18rem .45rem;
/* overflow:visible so content (dropdowns, tall labels) is never silently clipped.
align-content:start keeps items pinned to the top of the cell. */
overflow: visible;
display: flex;
flex-direction: column;
align-content: start;
padding: .18rem .45rem;
/* Min height ensures touch targets are reachable even on very short row spans */
min-height: 36px;
}
.fg-cell .field-lbl {
font-size:.74rem; font-weight:500; color:#64748b;
@@ -119,7 +136,27 @@
}
.upload-clear:hover { background:#fecaca; }
.signature-pad-wrap {
/* ── Pass / Fail pills ── */
.pf-group { display:flex; gap:.5rem; flex-wrap:wrap; }
.pf-btn {
display: flex; align-items: center; gap: .35rem;
cursor: pointer;
padding: .4rem .85rem; border-radius: 20px;
font-size: .84rem; font-weight: 600;
border: 2px solid transparent;
transition: background .15s, color .15s, border-color .15s;
user-select: none;
min-height: 36px; /* 44px touch target via padding — no layout shift */
}
.pf-btn input[type=radio] { display: none; }
/* Pass variants */
.pf-btn.pf-pass { border-color: #16a34a; color: #16a34a; background: transparent; }
.pf-btn.pf-pass.pf-on,
.pf-btn.pf-pass:has(input:checked) { background: #16a34a; color: #fff; }
/* Fail variants */
.pf-btn.pf-fail { border-color: #dc2626; color: #dc2626; background: transparent; }
.pf-btn.pf-fail.pf-on,
.pf-btn.pf-fail:has(input:checked) { background: #dc2626; color: #fff; }
flex:1; min-height:0; border-radius:6px; border:1px solid #e2e8f0;
background:#f8fafc; position:relative; overflow:hidden;
}
@@ -334,20 +371,14 @@
{% elif field.type == 'pass_fail' %}
<label class="field-lbl">{{ field.label }}{% if field.required %}<span class="required-mark"> *</span>{% endif %}</label>
{% set pf_options = field.options if field.options else ['Pass', 'Fail'] %}
<div class="pf-group" style="display:flex;gap:.5rem;flex-wrap:wrap;">
<div class="pf-group">
{% for opt in pf_options %}
{% set is_pass = opt.lower() in ('pass','yes','ok','good') %}
<label class="pf-btn{{ ' active' if saved == opt }}" data-color="{{ '#16a34a' if is_pass else '#dc2626' }}"
style="display:flex;align-items:center;gap:.35rem;cursor:pointer;
padding:.4rem .85rem;border-radius:20px;font-size:.84rem;font-weight:600;
border:2px solid {{ '#16a34a' if is_pass else '#dc2626' }};
color:{{ ('#fff' if saved == opt else ('#16a34a' if is_pass else '#dc2626')) }};
background:{{ (('#16a34a' if is_pass else '#dc2626') if saved == opt else 'transparent') }};
transition:background .15s,color .15s;">
{% set is_pass = opt.lower() in ('pass','yes','ok','good','acceptable','compliant') %}
<label class="pf-btn {{ 'pf-pass' if is_pass else 'pf-fail' }}{{ ' pf-on' if saved == opt }}"
data-fid="{{ fid }}">
<input type="radio" name="field_{{ fid }}" value="{{ opt }}"
{{ 'checked' if saved == opt }}
style="display:none;"
onchange="var g=this.closest('.pf-group');g.querySelectorAll('.pf-btn').forEach(function(l){l.style.background='';l.style.color=l.dataset.color;});this.parentElement.style.background=this.parentElement.dataset.color;this.parentElement.style.color='#fff';">
onchange="pfSelect(this)">
<span>{{ opt }}</span>
</label>
{% endfor %}
@@ -474,6 +505,43 @@
{% block extra_js %}
<script>
// ── Pass / Fail pill toggle ───────────────────────────────────────────────────
// Adds/removes .pf-on class instead of inline styles — works with CSS :has() fallback
function pfSelect(radio) {
const group = radio.closest('.pf-group');
if (!group) return;
group.querySelectorAll('.pf-btn').forEach(function(btn) {
btn.classList.remove('pf-on');
});
if (radio.checked) {
radio.closest('.pf-btn').classList.add('pf-on');
}
}
// ── Fluid grid width — ResizeObserver sets --fg-w on .form-grid ──────────────
// Avoids 100vw (includes scrollbar) and 100% on .form-grid itself (reports 0
// before first paint). Measures .insp-body which is always reliably sized.
(function () {
function applyGridWidth() {
const body = document.querySelector('.insp-body');
const grid = document.querySelector('.form-grid');
if (!body || !grid) return;
// Available width = insp-body inner width minus its horizontal padding (1rem each side on iPad)
const style = getComputedStyle(body);
const padH = parseFloat(style.paddingLeft) + parseFloat(style.paddingRight);
const available = body.clientWidth - padH;
grid.style.setProperty('--fg-w', available + 'px');
}
// Run immediately and on every resize
applyGridWidth();
if (typeof ResizeObserver !== 'undefined') {
new ResizeObserver(applyGridWidth).observe(document.querySelector('.insp-body') || document.body);
} else {
window.addEventListener('resize', applyGridWidth);
}
}());
// ── Rating stars ─────────────────────────────────────────────────────────────
function setRating(btn) {
const group = btn.closest('.rating-stars');