From 78b5063259d553c20c8643e00fe55c0cb118730a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E2=80=9CNaeel=E2=80=9D?= Date: Fri, 31 Jul 2026 12:52:02 +0400 Subject: [PATCH] =?UTF-8?q?v1.2.7:=20Phase=200=20=E2=80=94=20CSS=20foundat?= =?UTF-8?q?ion=20+=20icons=20+=20snackbar?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit style.css: +93 lines — sidebar, view, tabs, toolbar, field (label on top), skeleton, snackbar, combobox classes. Font scale 13/14/16. icons.js (NEW): 10 inline-SVG Lucide icons (play, edit, trash, check, x, chevronDown, search, plus, rotateCw, cloud). stroke=currentColor. snackbar.js (NEW): showSnackbar(msg, type). bottom-right, 4s/8s auto-hide, stacking max 4, accessibility role=status aria-live=polite. index.html: include icons.js before utils.js --- site/app.py | 2 +- site/static/js/icons.js | 16 +++++++ site/static/js/snackbar.js | 30 ++++++++++++ site/static/style.css | 93 ++++++++++++++++++++++++++++++++++++++ site/templates/index.html | 1 + 5 files changed, 141 insertions(+), 1 deletion(-) create mode 100644 site/static/js/icons.js create mode 100644 site/static/js/snackbar.js diff --git a/site/app.py b/site/app.py index d92fd99..1453307 100644 --- a/site/app.py +++ b/site/app.py @@ -19,7 +19,7 @@ from routes.api_scenario_run import bp_run as api_scenario_run_bp from routes.api_scenario_defs import bp_defs as api_scenario_defs_bp # Версия — меняется при КАЖДОМ изменении кода. Показывается в топбаре UI. -VERSION = "1.2.6" +VERSION = "1.2.7" app = Flask(__name__, template_folder="templates", static_folder="static") app.config["NUBES_API_ENDPOINT"] = os.getenv("NUBES_API_ENDPOINT", "https://lk-api-gateway-test.ngcloud.ru/api/v1/svc") diff --git a/site/static/js/icons.js b/site/static/js/icons.js new file mode 100644 index 0000000..314accf --- /dev/null +++ b/site/static/js/icons.js @@ -0,0 +1,16 @@ +// icons.js — 10 inline-SVG иконок (Lucide, MIT) +// stroke="currentColor" — цвет берётся из CSS +// Размер: 20x20, stroke-width: 2 + +const ICONS = { + play: '', + edit: '', + trash: '', + check: '', + xIcon: '', + chevronDown: '', + search: '', + plus: '', + rotateCw: '', + cloud: '', +}; diff --git a/site/static/js/snackbar.js b/site/static/js/snackbar.js new file mode 100644 index 0000000..b6599d9 --- /dev/null +++ b/site/static/js/snackbar.js @@ -0,0 +1,30 @@ +// snackbar.js — лёгкие уведомления (без зависимостей) +// showSnackbar(msg, type='info') — type: info, success, error + +function showSnackbar(msg, type) { + type = type || 'info'; + let container = document.getElementById('snackbar-container'); + if (!container) { + container = document.createElement('div'); + container.id = 'snackbar-container'; + container.className = 'snackbar-container'; + container.setAttribute('role', 'status'); + container.setAttribute('aria-live', 'polite'); + document.body.appendChild(container); + } + const el = document.createElement('div'); + el.className = 'snackbar ' + type; + el.textContent = msg; + el.onclick = function() { el.remove(); }; + container.appendChild(el); + + // Limit stacking + const all = container.querySelectorAll('.snackbar'); + if (all.length > 4) all[0].remove(); + + // Auto-hide: info/success 4s, error 8s + const delay = (type === 'error') ? 8000 : 4000; + setTimeout(function() { + if (el.parentNode) el.remove(); + }, delay); +} diff --git a/site/static/style.css b/site/static/style.css index 56d2711..e4d2b20 100644 --- a/site/static/style.css +++ b/site/static/style.css @@ -150,3 +150,96 @@ tr:hover td { color: #fff; border-color: var(--destructive); } + +/* === Phase 0: Redesign Foundation === */ + +/* ── Sidebar ── */ +.sidebar { + position: fixed; top: 0; left: 0; bottom: 0; + width: 192px; background: var(--background); + border-right: 1px solid var(--brand-gray); + display: flex; flex-direction: column; z-index: 100; + transition: width .2s; +} +.sidebar.collapsed { width: 56px; } +.sidebar-top { + height: 80px; border-top: 4px solid var(--brand-primary); + display: flex; align-items: center; padding: 0 16px; +} +.sidebar-nav { flex: 1; overflow-y: auto; padding: 8px 0; } +.sidebar-item { + display: flex; align-items: center; gap: 12px; + padding: 10px 16px; font-size: 14px; color: var(--foreground); + cursor: pointer; transition: background .15s; + border: none; background: none; width: 100%; text-align: left; +} +.sidebar-item:hover { background: var(--brand-grey-light); } +.sidebar-item.active { background: #dbeafe; font-weight: 600; } +.sidebar-item svg { width: 20px; height: 20px; flex-shrink: 0; } +.sidebar-bottom { padding: 8px 0; border-top: 1px solid var(--brand-gray); } + +/* ── Views ── */ +.view { margin-left: 192px; padding: 16px 20px; min-height: 100vh; } +.view.hidden { display: none; } +.view-header { font-size: 20px; font-weight: 700; margin-bottom: 16px; } + +/* ── Tabs ── */ +.tabs { display: flex; gap: 0; border-bottom: 2px solid var(--brand-gray); margin-bottom: 12px; } +.tab { + padding: 8px 16px; font-size: 14px; color: var(--muted); + cursor: pointer; border: none; background: none; + border-bottom: 2px solid transparent; margin-bottom: -2px; + transition: color .15s, border-color .15s; +} +.tab:hover { color: var(--foreground); } +.tab.active { color: var(--brand-primary); border-bottom-color: var(--brand-primary); font-weight: 600; } + +/* ── Toolbar ── */ +.toolbar { display: flex; gap: 6px; flex-wrap: wrap; padding: 8px 0; border-top: 1px solid var(--brand-gray); border-bottom: 1px solid var(--brand-gray); margin: 8px 0; } + +/* ── Field (label on top) ── */ +.field { margin-bottom: 10px; } +.field label { display: block; font-size: 13px; font-weight: 500; margin-bottom: 4px; color: var(--foreground); } +.field label.required::after { content: " *"; color: var(--destructive); } +.field input, .field select, .field textarea { + width: 100%; padding: 8px 10px; font-size: 14px; + border: 1px solid var(--brand-gray); border-radius: var(--radius-sm); + background: var(--background); +} +.field input:focus, .field select:focus { + outline: none; border-color: var(--brand-primary); + box-shadow: 0 0 0 2px rgba(37,99,235,.15); +} + +/* ── Skeleton ── */ +.skeleton { + background: linear-gradient(90deg, #e5e7eb 25%, #f3f4f6 50%, #e5e7eb 75%); + background-size: 200% 100%; animation: skeleton-shimmer 1.5s infinite; + border-radius: 4px; +} +@keyframes skeleton-shimmer { 0% { background-position: 200% 0; } 100% { background-position: -200% 0; } } +.skeleton-line { height: 14px; margin-bottom: 8px; } +.skeleton-line:last-child { width: 60%; } + +/* ── Snackbar ── */ +.snackbar-container { position: fixed; bottom: 16px; right: 16px; z-index: 10001; display: flex; flex-direction: column; gap: 8px; } +.snackbar { + padding: 10px 16px; border-radius: var(--radius-sm); font-size: 13px; + color: #fff; box-shadow: 0 2px 8px rgba(0,0,0,.2); + animation: snackbar-in .3s ease; max-width: 360px; +} +.snackbar.info { background: var(--brand-primary); } +.snackbar.success { background: var(--green); } +.snackbar.error { background: var(--destructive); } +@keyframes snackbar-in { from { opacity: 0; transform: translateY(10px); } to { opacity: 1; transform: translateY(0); } } + +/* ── Combobox ── */ +.combobox { position: relative; } +.combobox-input { width: 100%; padding: 6px 10px; font-size: 13px; border: 1px solid var(--brand-gray); border-radius: var(--radius-sm); } +.combobox-input:focus { outline: none; border-color: var(--brand-primary); } +.combobox-list { position: absolute; top: 100%; left: 0; right: 0; max-height: 200px; overflow-y: auto; background: var(--background); border: 1px solid var(--brand-gray); border-radius: 0 0 var(--radius-sm) var(--radius-sm); z-index: 10; } +.combobox-item { padding: 6px 10px; font-size: 13px; cursor: pointer; } +.combobox-item:hover { background: var(--brand-grey-light); } + +/* ── Body for sidebar ── */ +body.has-sidebar { padding-left: 0; } diff --git a/site/templates/index.html b/site/templates/index.html index 32955ab..7d12457 100644 --- a/site/templates/index.html +++ b/site/templates/index.html @@ -151,6 +151,7 @@ window.APP = { firstServiceId: {{ (config.service_ids[0] if config.service_ids else 1) | tojson }} }; +