Skip to content

Layout structural — Constructor și agenți

Sistemul canonic de compoziție pentru paginile GDS. Contractul executabil este în @gstack/gds-core (layout-system.ts); coordonatele persistate rămân în celule de 8px.

Referințele manuale din design/ folosesc consecvent:

  • rail lateral de 236–250px (GLog, GDS Portal, GPay);
  • padding de conținut de 24–34px;
  • gutter între carduri de 14–16px;
  • conținut centrat cu o margine dreaptă comună;
  • KPI pe 3–4 coloane egale;
  • rând principal/secundar de aproximativ 1.5fr 1fr sau 1.55fr 1fr.

Pe grila Constructor de 8px acestea devin: rail implicit 30 celule (240px), padding 4 celule (32px), gutter 2 celule (16px) și ritm vertical 2 celule (16px).

Artboard-ul desktop implicit este 1920×1080px = 240×135 celule.

  • fără sidenav: conținut col:4..236, lățime 232 celule;
  • cu rail implicit: chrome col:30..240, iar conținutul col:34..236, lățime 202;
  • sidenav tip top-bar: conținutul începe sub marginea sa inferioară;
  • chrome (topbar, brand-bar, site-nav, site-footer) ocupă lățimea disponibilă lângă rail, fără padding-ul paginii.

Template adaptiv: o pagină, layout-uri autonome

Section titled “Template adaptiv: o pagină, layout-uri autonome”

Un template web adaptiv păstrează o singură GdsPage și aceleași blocuri/date:

  • Desktop 1920×1080: block.layout și page.sidenavLayout;
  • Phone 430×932: block.layouts.phone și page.sidenavLayouts.phone;
  • Tablet 820×1180: block.layouts.tablet și page.sidenavLayouts.tablet;
  • Foldable 720×840: block.layouts.foldable și page.sidenavLayouts.foldable.

Frame-ul selectat este UI/editViewport și nu schimbă artboard-ul din project.meta. project.screens este suprafața paralelă Flutter/mobile, nu o variantă responsive a unei pagini web.

La prima deschidere a unui frame îngust, Constructor seed-uiește pozițiile lipsă din Desktop; apoi fiecare viewport se editează independent. Compoziția păstrează aceeași ordine informațională: Desktop folosește perechi 8+4 / 6+6, Tablet reduce densitatea și stivuiește panourile secundare, iar Phone folosește o singură coloană. Sidenav-ul devine icon rail pe tablet/foldable și top app bar + sheet pe phone.

Agent / autosuggest (concurrent): desktop + tablet + phone se tratează împreună pe aceeași GdsPage (aceleași id-uri, date partajate). După plasarea desktop, layouts.tablet și layouts.phone se autorizează / se seed-uiesc în același ciclu (RULE.ADAPTIVE_VIEWPORTS, RULE.MULTI_VIEWPORT_CONCURRENT). Frame-ul din Editor este doar UI — nu schimbă artboard-ul din project.meta.

Aria de conținut are 12 coloane și gutter de 2 celule. Preseturile sunt:

  • 12 — lățime completă;
  • 8 + 4 — vizual principal + panou secundar;
  • 6 + 6 — două panouri echilibrate;
  • 4 + 4 + 4 — trei panouri;
  • 3 + 3 + 3 + 3 — patru carduri compacte.

Un rând normal trebuie să umple aria până la marginea dreaptă. Excepțiile sunt componentele constrânse (form/login/profile), centrate, widget-urile compacte solo (wallet-card, card, toggle-card, progress-*, deadline-list, donut-chart — span 4 centrat, RULE.COMPACT_CARD_WIDTH), și componentele flotante (toast/chat/drawer).

Lățime consistentă (RULE.CONTENT_WIDTH_CONSISTENCY):

  • pagini listă (Utilizatori, Alerte, Jurnal): benzi solo full-role = full content width, aceeași muchie stângă;
  • pagină settings-only (Configurare / retenție): formular constrained centrat = OK intenționat;
  • pagină Profil: user-profile span 6 centrat + wallet-card span 4 centrat — nu wallet full-bleed;
  • interzis: tabel full-width + formular-insulă centrat pe aceeași pagină — separă Utilizatori vs Configurare (RULE.NO_CROSS_CONCERN_STACK);
  • interzis: card / wallet / progress solo întinse pe toată aria de conținut (RULE.COMPACT_CARD_WIDTH).
  • chrome complet: topbar, brand-bar, site-nav, site-footer, header, bottom-menu;
  • conținut complet: page-header, kpi-row, table, log-journal, section-header, hero, alerte, search/tabs/filtre și benzile CTA;
  • 8 coloane: grafice line/bar/hbar, liste/tabele secundare ample, panouri log/config;
  • 4 coloane (combinabile; centrate când sunt solo): donut, deadline, wallet, progres compact, card, toggle-card;
  • 4 coloane (combinabile; pot umple lățimea când sunt solo): activity (feed);
  • 6 coloane centrat: form, auth, profile, receipt, method-list, FAQ;
  • flotant: chat, toast, drawer.

Maparea exhaustivă este expusă prin blockLayoutIntent(kind).

Fiecare pagină web are două sloturi obligatorii înaintea conținutului:

  1. structură opțională (sidenav, doar pentru shell-uri de aplicație);
  2. chrome de deschidere: exact unul dintre topbar, site-nav, brand-bar;
  3. titlu vizibil: page-header (app), hero (landing), sau auth-gate (login — fără page-header);
  4. KPI;
  5. vizualul sau tabelul principal;
  6. panouri secundare din același job;
  7. chrome de închidere opțional (site-footer).

sidenav? → topbar(breadcrumb produs / pagina curentă) → page-header(titlu = item sidenav activ) → conținut → site-footer?

  • Utilizatori: topbar → page-header „Utilizatori” + actions[{Creează}] → table (title: '', fără toolbarActionLabel) → site-footer? (RULE.LIST_CTA_ON_PAGE_HEADER)
  • Configurare: topbar → page-header „Configurare” → form constrained → site-footer?
  • Nu amesteca tabelul de utilizatori cu formularul de retenție pe un singur board.

Referințe: GLog, GPay Core, Service Provider și Payment Provider au rail-ul de 236–242px, apoi o bară de 58px cu breadcrumb, iar în main primul element este titlul paginii cu subtitlu.

site-nav|brand-bar → hero|page-header → conținut → site-footer?

hero poate înlocui page-header pe pagina de landing deoarece conține titlul dominant, dar nu înlocuiește navigarea de sus. Referințele GPay Hub și Portal Cetățean au header-ul sticky înaintea hero-ului.

Ordinea sloturilor este semantică, nu doar o sugestie de coordonate:

  • topbar|site-nav|brand-bar este reparat primul, chiar dacă modelul îl emite târziu;
  • site-footer este reparat ultimul;
  • fără chrome de deschidere, primul bloc de conținut primește un inset sigur de 2 celule (16px), astfel încât titlul/hero-ul să nu fie lipit sau tăiat la marginea board-ului;
  • engine-ul nu inventează breadcrumb-uri sau titluri. Lipsa sloturilor de conținut produce feedback corectiv către agent, care trebuie să le adauge explicit.

Un artboard nu afișează niciodată un scrollbar în interiorul unui bloc. Dacă conținutul e mai înalt decât cadrul, cadrul crește — nu apare derulare:

  • fiecare .gds-block-frame și rădăcina lui de conținut sunt overflow-y:hidden (GDS_ARTBOARD_NO_SCROLL_CSS), pe canvas, în Preview și în export;
  • înălțimea e podelită la naturalBlockRowSpan(kind, data, {span|widthPx}), deci un rowSpan prea mic venit de la agent este ridicat (resolveRowSpan în repairGdsLayout / packGdsBlock), iar la viewport îngust reflowFreeformLayouts refolosește aceeași podea pentru blocurile sensibile la lățime (hero, card, cta-banner, cta-card).

Rămân derulabile, intenționat: orizontal .gds-table-wrap (coloane late pe telefon), .gds-tabs, .gds-drawer-panel-tabs, .gds-filter-chips-row segmentat; vertical doar scroller-ele interne, mai adânci decât rădăcina blocului — .gds-nav-panel (meniu sidenav), .gds-log-scroll, .gds-chat-body, .gds-drawer-panel-body.

  • gdsContentArea(...) — calculează limitele după artboard și sidenav;
  • gdsSpanRect(area, span, startColumn) — convertește span-ul în celule;
  • blockLayoutIntent(kind) — span și rol implicit pentru orice BlockKind;
  • packGdsBlock(...) — snap, row packing și reparare overlap/overflow;
  • validateGdsLayout(...) — detectează overlap, overflow, nealiniere și spațiu mort.

Agentul poate trimite layout:{span:8,row:"next"}. Dreptunghiurile brute {col,row,colSpan,rowSpan} sunt acceptate ca sugestii, apoi sunt aliniate și reparate. Drag-and-drop-ul uman rămâne WYSIWYG și nu trece prin acest resolver.