diff --git a/CLAUDE.md b/CLAUDE.md index ecd2029..9c6b740 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -6,6 +6,14 @@ > Prefer `gnexus-ui-kit/vue` components. Do not recreate modal, drawer, tab, toast, form, button, or table markup manually unless the adapter does not expose the needed component yet. +## Demo Synchronization + +`demo/index.html` (vanilla) and `demo/vue.html` (Vue) must always be **pixel-identical**. Any change to one requires an immediate matching change to the other. + +- Section order, navigation items, and `currentSection` observer targets must match exactly. +- If a Vue adapter component cannot render markup identical to the vanilla partial, use raw HTML from the vanilla partial in the Vue demo. Do not force the adapter to match at the cost of visual differences. +- Verify synchronization after every edit by running the Playwright height comparison (`node compare-heights.js`) — it must report 0 differences across all sections. + ## Required Setup (once per app) ```js @@ -66,6 +74,7 @@ | Card / panel | `GnCard` | `title` (slot), `footer` (slot) | | Horizontal card | `GnHorizontalCard` | `image`, `title`, `titleHref`, `icon`, `footer` (slot) | | Login card | `GnLoginCard` | `title`, `logoIcon`, `usernameLabel`, `passwordLabel`, `rememberMe`, `submitText`, `error`, `forgotHref`, `signupHref` | +| User card | `GnUserCard` | `name`, `email`, `role`, `avatar`, `href`, `compact`, `actions` | | Page title bar | `GnPageHeader` | `kicker`, `title`, `subtitle`, `meta`, `actions` (slot) | | Text field | `GnInput` | `v-model`, `label`, `icon`, `state`, `help` | | Textarea | `GnTextarea` | `v-model`, `label`, `state`, `help` | @@ -161,6 +170,34 @@ - `GnCombobox` owns combobox/listbox ARIA and keyboard navigation. - `GnFileUpload` owns preview object URLs and cleans them up on remove/unmount. +## Working on This Repo — Lessons + +These rules exist because previous sessions produced regressions when they were ignored. + +### One change, one verification +Never make three edits and then check the result. After every single change, run `npx gulp build`, open the browser, and confirm the affected component still renders correctly. Catching a bug in the first edit saves more time than catching three stacked bugs in the fourth. + +### Always compare with vanilla HTML before implementing or fixing a Vue component +When adding or fixing a Vue adapter component (e.g., `GnLoginCard`), open `demo/partials/*.html` first. The vanilla HTML is the source of truth for markup structure, CSS classes, icon names, and default prop values. Do not trust the existing Vue component or the existing demo page to be correct. + +### Screenshot every visual change +If a component's appearance changed, take a Playwright screenshot before and after. A diff that looks "slightly off" often hides a real bug (wrong padding, missing border, clipped text). Do not dismiss visual glitches as "probably fine." + +### CSS transitions + Vue render cycle +When a component's visibility is toggled via `v-if` / conditional rendering, the initial render must include the "hidden" state class first, and the "visible" state class must be applied in a `nextTick` + `requestAnimationFrame`. If both states exist in the same render frame, the browser skips the transition entirely. This applies to `GnModal`, `GnDrawer`, and any future overlay component. + +### Validate prop contracts against demo data +If a demo passes `{ label: "Foo" }` and the component expects `{ title: "Foo" }`, the UI will render empty items. Before wiring demo data into a component, read the component's `props` declaration. If they don't match, fix the component fallback (e.g., `item.title || item.label`) or fix the demo data — don't leave it broken. + +### Components with intrinsic max-width do not belong in tight grids +`GnNavList`, `GnSteps`, `GnProgress`, `GnTimeline`, `GnHorizontalCard`, `GnLoginCard`, `GnUsageMeter`, and `GnUserCard` define their own `max-width` in CSS. Putting them inside `display: grid` with `minmax(320px, 1fr)` causes overflow, clipping, or forced squishing. Render them as block-level siblings instead. + +### Do not stack unrelated UI into one demo section +A section titled "Chips & Toolbar" that also contains Pagination creates confusion about component boundaries. Each major component gets its own `demo-section` with its own `h3`. + +### Check event handler syntax in Vue templates +`@click="toastRef.value?.success(...)"` does not work in Vue 3 SFC/inline templates the way it works in plain JS. The template compiler handles ref unwrapping differently from `setup()` return values. If a method call fails silently, extract a helper function in `setup()` and call that instead. + ## Verification Before claiming Vue adapter compatibility in this repo, run: diff --git a/demo/index.html b/demo/index.html index 0e2ed73..71084e2 100644 --- a/demo/index.html +++ b/demo/index.html @@ -186,7 +186,7 @@ diff --git a/demo/partials/cards.html b/demo/partials/cards.html index 91331f1..ae6bb63 100644 --- a/demo/partials/cards.html +++ b/demo/partials/cards.html @@ -308,4 +308,130 @@ @submit="handleLogin" /> +
<article class="card user-card user-card-compact">
+ <div class="user-card-body">
+ <a class="profile-identity" href="#">
+ <span class="identity">
+ <span class="avatar avatar-primary avatar-sm is-online">
+ <img src="..." alt="">
+ </span>
+ <span class="identity-content">
+ <span class="identity-title">Name</span>
+ <span class="identity-meta">email@example.com</span>
+ </span>
+ </span>
+ </a>
+ <div class="user-card-actions">
+ <button class="btn-icon" aria-label="Edit">
+ <i class="ph ph-pencil-simple"></i>
+ </button>
+ </div>
+ </div>
+</article>
+ <article class="card user-card">
+ <div class="user-card-body">
+ <span class="identity">
+ <span class="avatar avatar-primary is-online">
+ <img src="..." alt="">
+ </span>
+ <span class="identity-content">
+ <span class="identity-title">Name</span>
+ <span class="identity-meta">email@example.com</span>
+ </span>
+ </span>
+ <span class="user-card-role">Role</span>
+ <div class="user-card-actions">
+ <button class="btn btn-primary btn-small">View profile</button>
+ <button class="btn btn-accent btn-small">Message</button>
+ </div>
+ </div>
+</article>
+ <GnUserCard
+ name="Eugene Sukhodolskiy"
+ email="goldmikki@gmail.com"
+ role="Lead Frontend Engineer"
+ :avatar="{ src: '...', status: 'online' }"
+ href="/profile"
+ :actions="[
+ { icon: 'ph-user', label: 'View profile', variant: 'primary' },
+ { icon: 'ph-chat-circle', label: 'Message', variant: 'accent' }
+ ]"
+/>
+
+<GnUserCard
+ name="Eugene Sukhodolskiy"
+ email="goldmikki@gmail.com"
+ :avatar="{ src: '...', status: 'online' }"
+ href="/profile"
+ compact
+ :actions="[
+ { icon: 'ph-pencil-simple', label: 'Edit profile' },
+ { icon: 'ph-sign-out', label: 'Logout' }
+ ]"
+/>
+ <footer class="nav-drawer-footer">
+ <a class="profile-identity" href="#" target="_blank" rel="noopener noreferrer">
+ <span class="identity">
+ <span class="avatar avatar-primary avatar-sm is-online">
+ <img src="..." alt="">
+ </span>
+ <span class="identity-content">
+ <span class="identity-title">Name</span>
+ <span class="identity-meta">email@example.com</span>
+ </span>
+ </span>
+ </a>
+ <button class="btn-icon" aria-label="Edit profile">
+ <i class="ph ph-pencil-simple"></i>
+ </button>
+ <button class="btn-icon" aria-label="Logout">
+ <i class="ph ph-sign-out"></i>
+ </button>
+</footer>
+ <GnNavigationShell :items="navItems" brand="Product Console" ...>
+ <template #footer>
+ <a class="profile-identity" href="/profile" target="_blank">
+ <GnIdentity
+ title="Eugene Sukhodolskiy"
+ meta="goldmikki@gmail.com"
+ :avatar="{ src: '...', status: 'online', size: 'sm' }"
+ />
+ </a>
+ <GnIconButton icon="ph-pencil-simple" label="Edit profile" />
+ <GnIconButton icon="ph-sign-out" label="Logout" />
+ </template>
+</GnNavigationShell>
+ Accordion и Disclosure используют native <details> / <summary>, а раскрытие анимируется через Accordion.init().
Check component states, copy examples, keyboard focus, and responsive layout before release.
+Keep component copy short and include code examples for the smallest useful markup.
+Use native disclosure behavior when the component does not need custom animation or async content.
+Use this variant for a single expandable group inside forms or metadata blocks.
+<div class="accordion">
+ <details class="accordion-item" open>
+ <summary class="accordion-summary">
+ <span class="accordion-summary-content">
+ <i class="ph ph-list-checks"></i>
+ Release checklist
+ </span>
+ <i class="ph ph-caret-down accordion-icon"></i>
+ </summary>
+ <div class="accordion-panel">
+ <p>Check component states, copy examples, keyboard focus, and responsive layout.</p>
+ </div>
+ </details>
+</div>
+ <details class="disclosure">
+ <summary class="accordion-summary">
+ <span class="accordion-summary-content">Compact disclosure</span>
+ <i class="ph ph-caret-down accordion-icon"></i>
+ </summary>
+ <div class="accordion-panel">
+ <p>Use this variant for a single expandable group.</p>
+ </div>
+</details>
+ Alerts подходят для заметных системных сообщений внутри контента. Используй alert и один цветовой модификатор.
+<div class="alert alert-primary">Primary message</div>
+<div class="alert alert-success">Operation completed</div>
+<div class="alert alert-warning">Check settings</div>
+<div class="alert alert-error">Action failed</div>
+ Avatar и Identity показывают пользователя, команду, сервис или объект с initials, icon, image и status marker.
+<span class="avatar avatar-secondary is-online">
+ AK
+ <span class="avatar-status"></span>
+</span>
+
+<span class="avatar avatar-outline">
+ <i class="ph ph-user"></i>
+</span>
+
+<span class="avatar">
+ <img src="/assets/imgs/gnexus-mark.svg" alt="GNexus mark">
+</span>
+ <div class="identity">
+ <span class="avatar avatar-secondary is-online">
+ AK
+ <span class="avatar-status"></span>
+ </span>
+ <span class="identity-content">
+ <span class="identity-title">Alex Kim</span>
+ <span class="identity-meta">Product Designer</span>
+ </span>
+</div>
+ <div class="avatar-stack" aria-label="Assigned users">
+ <span class="avatar avatar-sm">AK</span>
+ <span class="avatar avatar-sm avatar-secondary">JS</span>
+ <span class="avatar avatar-sm avatar-warning">MR</span>
+ <span class="avatar-stack-count">+4</span>
+</div>
+ Badges используются для коротких статусов, счётчиков и меток состояния. Цветовой модификатор выбирается по смыслу: success, warning, error, info и так далее.
+<span class="badge badge-success">Online</span>
+<span class="badge badge-warning">Degraded</span>
+<span class="badge badge-error">Offline</span>
+<span class="badge badge-info">Info</span>
+ Кнопки собираются из базового класса .btn, цветового модификатора и опционального размера. Для иконок добавляй .with-icon, а состояние загрузки помечай .loading-state.
+Кнопка копирования на основе .btn-icon. В Vue-адаптере используй GnCopyButton.
+<button class="btn btn-primary">Primary</button>
+<button class="btn btn-secondary with-icon">
+ <i class="ph ph-code"></i>
+ Secondary
+</button>
+<button class="btn btn-warning btn-small">Small</button>
+<button class="btn btn-success with-icon loading-state" disabled>
+ <i class="ph-bold ph-spinner"></i>
+ Loading
+</button>
+ Card подходит для компактных виджетов и контентных блоков. Цветовые состояния карточек задаются модификаторами `card-success`, `card-warning`, `card-error`, `card-info`.
+Карточка для контента, виджетов, статусов.
+Primary status card with a short description.
+Neutral card for a module, widget, or section.
+Warning state for items that need attention.
+Error state for failed actions or blocked flows.
+Informational card for contextual metadata.
+Secondary state for queued or background work.
+Compact horizontal layout for content listings, search results, and entity rows.
+ + Updated 2h ago +<div class="card status-card card-success">
+ <span class="card-title">Status title</span>
+ <div class="card-content">
+ <div class="status-icon-container">
+ <div class="status-icon">
+ <i class="ph ph-check-circle"></i>
+ OK
+ </div>
+ </div>
+ <p class="status-name">Primary status card.</p>
+ </div>
+</div>
+ <div class="card metric-card">
+ <div class="card-content">
+ <div class="metric-card-header">
+ <p class="metric-card-label">Monthly usage</p>
+ <span class="metric-card-icon"><i class="ph ph-chart-line-up"></i></span>
+ </div>
+ <p class="metric-card-value">84.2%</p>
+ <div class="metric-card-meta">
+ <span class="metric-card-delta">+12.8%</span>
+ <span>vs previous period</span>
+ </div>
+ </div>
+</div>
+ <div class="card action-card">
+ <div class="card-content">
+ <span class="action-card-kicker">Quick action</span>
+ <h3 class="action-card-title">Create workflow</h3>
+ <p class="action-card-text">
+ Use action cards for primary next steps and compact empty state actions.
+ </p>
+ <div class="action-card-actions">
+ <button class="btn btn-secondary btn-small">Preview</button>
+ <button class="btn btn-accent btn-small">Create</button>
+ </div>
+ </div>
+</div>
+ <div class="card card-horizontal">
+ <div class="card-media">
+ <img src="/assets/imgs/cover.png" alt="">
+ </div>
+ <div class="card-body">
+ <h3 class="card-title">
+ <a href="/projects/alpha">Project Alpha</a>
+ </h3>
+ <div class="card-content">
+ <p>Description text.</p>
+ </div>
+ <div class="card-footer">
+ <span>Updated 2h ago</span>
+ <span class="badge badge-success">Active</span>
+ </div>
+ </div>
+</div>
+ <GnHorizontalCard
+ image="/assets/imgs/cover.png"
+ title="Project Alpha"
+ title-href="/projects/alpha"
+ icon="ph-arrow-right">
+ <p>Description text.</p>
+ <template #footer>
+ <span>Updated 2h ago</span>
+ <GnBadge variant="success">Active</GnBadge>
+ </template>
+</GnHorizontalCard>
+ <GnLoginCard
+ title="Sign in"
+ username-label="Username"
+ password-label="Password"
+ submit-text="Sign in"
+ remember-me
+ forgot-href="/forgot"
+ signup-href="/signup"
+ @submit="handleLogin"
+/>
+ <article class="card user-card user-card-compact">
+ <div class="user-card-body">
+ <a class="profile-identity" href="#">
+ <span class="identity">
+ <span class="avatar avatar-primary avatar-sm is-online">
+ <img src="..." alt="">
+ </span>
+ <span class="identity-content">
+ <span class="identity-title">Name</span>
+ <span class="identity-meta">email@example.com</span>
+ </span>
+ </span>
+ </a>
+ <div class="user-card-actions">
+ <button class="btn-icon" aria-label="Edit">
+ <i class="ph ph-pencil-simple"></i>
+ </button>
+ </div>
+ </div>
+</article>
+ <article class="card user-card">
+ <div class="user-card-body">
+ <span class="identity">
+ <span class="avatar avatar-primary is-online">
+ <img src="..." alt="">
+ </span>
+ <span class="identity-content">
+ <span class="identity-title">Name</span>
+ <span class="identity-meta">email@example.com</span>
+ </span>
+ </span>
+ <span class="user-card-role">Role</span>
+ <div class="user-card-actions">
+ <button class="btn btn-primary btn-small">View profile</button>
+ <button class="btn btn-accent btn-small">Message</button>
+ </div>
+ </div>
+</article>
+ <GnUserCard
+ name="Eugene Sukhodolskiy"
+ email="goldmikki@gmail.com"
+ role="Lead Frontend Engineer"
+ :avatar="{ src: '...', status: 'online' }"
+ href="/profile"
+ :actions="[
+ { icon: 'ph-user', label: 'View profile', variant: 'primary' },
+ { icon: 'ph-chat-circle', label: 'Message', variant: 'accent' }
+ ]"
+/>
+
+<GnUserCard
+ name="Eugene Sukhodolskiy"
+ email="goldmikki@gmail.com"
+ :avatar="{ src: '...', status: 'online' }"
+ href="/profile"
+ compact
+ :actions="[
+ { icon: 'ph-pencil-simple', label: 'Edit profile' },
+ { icon: 'ph-sign-out', label: 'Logout' }
+ ]"
+/>
+ Chips используются для фильтров, коротких labels, selectable states и removable tokens.
+<div class="chip-group">
+ <span class="chip chip-primary">Primary</span>
+ <span class="chip chip-secondary">Secondary</span>
+ <span class="chip chip-success">Success</span>
+ <span class="chip">
+ <i class="ph ph-funnel"></i>
+ Filtered
+ </span>
+</div>
+ <div class="chip-group" aria-label="Filter chips">
+ <button class="chip chip-selected" type="button" aria-pressed="true">Active</button>
+ <button class="chip" type="button" aria-pressed="false">Queued</button>
+ <button class="chip" type="button" aria-pressed="false">Failed</button>
+</div>
+ <span class="chip">
+ Frontend
+ <button class="chip-remove" type="button" aria-label="Remove Frontend">
+ <i class="ph ph-x"></i>
+ </button>
+</span>
+ Confirm Dialog использует существующий confirmPopup поверх Modal. Он нужен для коротких подтверждений перед опасными или необратимыми действиями.
+confirmPopup(
+ "This action cannot be undone.",
+ () => Toasts.createSuccess("Confirmed", "Action accepted").show(),
+ () => Toasts.createInfo("Canceled", "Action skipped").show()
+);
+ GNexusUIKit.confirmPopup(
+ "Apply this change?",
+ () => console.log("confirmed"),
+ () => console.log("canceled")
+);
+ Toolbar, search field, pagination, empty state и skeleton закрывают типовые состояния таблиц, списков и страниц с данными.
+<div class="toolbar">
+ <div class="toolbar-group">
+ <div>
+ <h3 class="toolbar-title">Projects</h3>
+ <span class="toolbar-meta">24 items</span>
+ </div>
+ </div>
+ <div class="toolbar-group">
+ <div class="input-group input-group-compact search-field">
+ <span class="input-group-addon"><i class="ph ph-magnifying-glass"></i></span>
+ <input class="input-group-input" type="search" placeholder="Search">
+ <button class="input-group-action" type="button" data-input-clear aria-label="Clear search">
+ <i class="ph ph-x"></i>
+ </button>
+ </div>
+ <button class="btn btn-accent btn-small">Create</button>
+ </div>
+</div>
+ <div class="input-group">
+ <span class="input-group-addon">https://</span>
+ <input class="input-group-input" type="text" placeholder="Domain">
+ <button class="input-group-action" type="button">Save</button>
+</div>
+ <nav class="pagination" aria-label="Pagination">
+ <button class="pagination-item" type="button" disabled>Prev</button>
+ <button class="pagination-item pagination-item-active" type="button" aria-current="page">1</button>
+ <button class="pagination-item" type="button">2</button>
+ <span class="pagination-ellipsis">...</span>
+ <button class="pagination-item" type="button">12</button>
+ <button class="pagination-item" type="button">Next</button>
+</nav>
+ <div class="empty-state">
+ <div class="empty-state-icon"><i class="ph ph-package"></i></div>
+ <h3 class="empty-state-title">No results</h3>
+ <p class="empty-state-text">Nothing matched the current filters.</p>
+ <div class="empty-state-actions">
+ <button class="btn btn-secondary btn-small">Reset</button>
+ <button class="btn btn-accent btn-small">Create</button>
+ </div>
+</div>
+ <div class="skeleton-stack" aria-label="Loading preview">
+ <span class="skeleton skeleton-title"></span>
+ <span class="skeleton skeleton-line"></span>
+ <span class="skeleton skeleton-line" style="width: 84%"></span>
+ <span class="skeleton skeleton-block"></span>
+</div>
+ InputPatterns.init();
+
+// Available on the global namespace too:
+GNexusUIKit.InputPatterns.init();
+ Description List подходит для read-only деталей сущности: ID, владельцы, статусы, даты и короткие metadata values.
+@design
+
+ run_1048<dl class="description-list">
+ <div class="description-list-row">
+ <dt class="description-list-term">Project</dt>
+ <dd class="description-list-value">Website Redesign</dd>
+ </div>
+ <div class="description-list-row">
+ <dt class="description-list-term">Status</dt>
+ <dd class="description-list-value">
+ <span class="badge badge-success">Online</span>
+ <span class="description-list-value-muted">All checks passed</span>
+ </dd>
+ </div>
+</dl>
+ <dl class="description-list description-list-compact">
+ <div class="description-list-row">
+ <dt class="description-list-term">Run ID</dt>
+ <dd class="description-list-value"><code class="code">run_1048</code></dd>
+ </div>
+ <div class="description-list-row">
+ <dt class="description-list-term">Runtime</dt>
+ <dd class="description-list-value">18s</dd>
+ </div>
+</dl>
+ Drawer подходит для контекстных деталей, quick edit и длинных вторичных сценариев без ухода со страницы. По API он близок к Modal, но панель закреплена у края экрана.
++ Используй Drawer, когда нужно сохранить контекст основного экрана и раскрыть боковую область с формой, + метаданными или журналом. +
+Drawer.create("details-drawer", {
+ title: "Details",
+ bodyHtml: `
+ <p class="text">Context content, forms, logs, metadata.</p>
+ `,
+ actions: drawer => {
+ const close = document.createElement("button");
+ close.className = "btn btn-primary";
+ close.textContent = "Close";
+ close.addEventListener("click", () => drawer.close());
+ return [close];
+ }
+}).show();
+ Drawer.create("navigation-drawer", {
+ title: "Navigation",
+ position: "left",
+ bodyText: "Left drawer content"
+}).show();
+ Editable String превращает обычный контейнер с текстом в inline-редактор. Компонент возвращает DOM-элемент с API editableString.
+<div class="editable-example">Hello world</div>
+ const editor = editableString(document.querySelector(".editable-example"));
+
+editor.editableString.onChange(component => {
+ console.log(component.value);
+});
+ Формы используют .form-group, .label и .input. Ошибки задаются на label через .error, а пояснение выводится соседним .input-info.
+<div class="form-group">
+ <label class="label error">
+ Project name
+ <i class="ph ph-user"></i>
+ <input class="input" type="text" placeholder="Launch Plan">
+ </label>
+ <div class="input-info">
+ <i class="ph ph-warning-circle"></i>
+ Field cannot be empty
+ </div>
+</div>
+
+<fieldset class="fieldset">
+ <legend class="legend">Fieldset</legend>
+ <label class="label success">
+ Validated
+ <input class="input" type="text" value="Release Plan">
+ </label>
+</fieldset>
+
+<label class="file-upload">
+ Upload file
+ <input type="file">
+</label>
+
+<fieldset class="fieldset">
+ <legend class="legend">Date & Time</legend>
+ <div class="form-grid">
+ <label class="label">
+ Date
+ <i class="ph ph-calendar-blank"></i>
+ <input class="input input-date" type="date" value="2026-04-23" data-date-picker>
+ </label>
+ <label class="label">
+ Time
+ <i class="ph ph-clock"></i>
+ <input class="input input-time" type="time" value="14:30" data-date-picker>
+ </label>
+ <label class="label">
+ Date & time
+ <i class="ph ph-calendar-dots"></i>
+ <input class="input input-datetime" type="datetime-local" value="2026-04-23T14:30" data-date-picker>
+ </label>
+ </div>
+</fieldset>
+
+<div class="file-upload-panel">
+ <form class="file-upload-form" enctype="multipart/form-data">
+ <div class="file-upload-header">
+ <div class="file-upload-heading">
+ <h3 class="file-upload-title">Upload files</h3>
+ <p class="file-upload-description">Attach documents, archives or images.</p>
+ </div>
+ <span class="badge badge-info">Max 12 MB</span>
+ </div>
+
+ <label class="file-upload-dropzone">
+ <span class="file-upload-icon" aria-hidden="true">
+ <i class="ph ph-cloud-arrow-up"></i>
+ </span>
+ <span class="file-upload-body">
+ <span class="file-upload-primary">Choose files</span>
+ <span class="file-upload-secondary">Images get thumbnails, other files show their type</span>
+ </span>
+ <input type="file" name="files" multiple data-file-upload-input>
+ </label>
+
+ <div class="file-upload-preview" data-file-upload-preview hidden></div>
+
+ <div class="file-upload-actions">
+ <button class="btn btn-secondary btn-small" type="reset">Reset</button>
+ <button class="btn btn-accent btn-small" type="submit">Upload</button>
+ </div>
+ </form>
+</div>
+ Списки покрывают обычные перечни, навигацию, definition list и строки с действиями. Для интерактивных пунктов используй `.list-nav`, `.list-action`, `.list-label` и `.list-meta`.
+<ul class="list list-nav">
+ <li class="list-item list-item-active">
+ <button class="list-action" type="button">
+ <span class="list-label">
+ <i class="ph ph-lightbulb"></i>
+ Projects
+ </span>
+ <span class="list-meta">12</span>
+ </button>
+ </li>
+</ul>
+ Modal создаётся из JS через Modals.create. Для обычного текста используй bodyText; HTML-контент передавай явно через bodyHtml или DOM-элемент.
+Modals.create("demo-modal", {
+ title: "Demo modal",
+ bodyText: "Modal content",
+ actions: modal => {
+ const close = document.createElement("button");
+ close.className = "btn btn-primary";
+ close.textContent = "Close";
+ close.addEventListener("click", () => modal.close());
+ return [close];
+ }
+}).show();
+ Page Header собирает заголовок экрана, короткое описание, статусные метки и основные действия.
++ Track active work, review critical states, and open the next action without leaving the page. +
+ +<header class="page-header">
+ <div class="page-header-content">
+ <span class="page-header-kicker">Workspace</span>
+ <h1 class="page-header-title">Projects overview</h1>
+ <p class="page-header-subtitle">
+ Track active work and open the next action without leaving the page.
+ </p>
+ <div class="page-header-meta">
+ <span class="badge badge-success">Healthy</span>
+ <span>Updated 2 min ago</span>
+ </div>
+ </div>
+
+ <div class="page-header-actions">
+ <button class="btn btn-secondary btn-small" type="button">Export</button>
+ <button class="btn btn-accent btn-small" type="button">Create</button>
+ </div>
+</header>
+ <header class="page-header page-header-compact page-header-accent">
+ <div class="page-header-content">
+ <h2 class="page-header-title">Billing settings</h2>
+ <div class="page-header-meta">
+ <span class="badge badge-warning">Pending</span>
+ <span>3 changes waiting</span>
+ </div>
+ </div>
+
+ <div class="page-header-actions">
+ <button class="btn btn-primary btn-small" type="button">Review</button>
+ <button class="btn btn-danger btn-small" type="button">Discard</button>
+ </div>
+</header>
+ Палитра построена вокруг тёмной базы, светлого primary-текста и ярких статусных цветов. Классы bg-* нужны для быстрых цветовых маркеров и демонстрации токенов.
+<div class="color-box bg-primary"></div>
+<div class="color-box bg-secondary"></div>
+<div class="color-box bg-success"></div>
+<div class="color-box bg-warning"></div>
+<div class="color-box bg-error"></div>
+ Progress закрывает загрузку, usage meters и staged progress для операций с понятным состоянием выполнения.
+<div class="progress" role="progressbar" aria-label="Build progress" aria-valuemin="0" aria-valuemax="100" aria-valuenow="64" style="--progress-value: 64%">
+ <div class="progress-header">
+ <span>Build</span>
+ <span class="progress-value">64%</span>
+ </div>
+ <div class="progress-track">
+ <span class="progress-bar"></span>
+ </div>
+</div>
+ <div class="usage-meter">
+ <h3 class="usage-meter-title">
+ Storage
+ <span class="usage-meter-value">78%</span>
+ </h3>
+ <div class="progress progress-success" role="progressbar" aria-valuemin="0" aria-valuemax="100" aria-valuenow="78" style="--progress-value: 78%">
+ <div class="progress-track">
+ <span class="progress-bar"></span>
+ </div>
+ </div>
+ <p class="usage-meter-meta">312 GB used from 400 GB limit.</p>
+</div>
+ <div class="progress-stages" aria-label="Deploy stages">
+ <span class="progress-stage progress-stage-complete">Queued</span>
+ <span class="progress-stage progress-stage-complete">Build</span>
+ <span class="progress-stage progress-stage-current">Deploy</span>
+ <span class="progress-stage">Verify</span>
+</div>
+ Steps описывает wizard-flow, onboarding и последовательные процессы с текущим, завершенным и недоступным шагом.
+<ol class="steps">
+ <li class="step step-complete">
+ <span class="step-marker"><i class="ph ph-check"></i></span>
+ <h3 class="step-title">Account</h3>
+ <p class="step-text">Basic profile data is ready.</p>
+ </li>
+ <li class="step step-current" aria-current="step">
+ <span class="step-marker">2</span>
+ <h3 class="step-title">Confirm</h3>
+ <p class="step-text">Review settings before launch.</p>
+ </li>
+ <li class="step step-disabled">
+ <span class="step-marker">3</span>
+ <h3 class="step-title">Launch</h3>
+ <p class="step-text">Available after confirmation.</p>
+ </li>
+</ol>
+ <ol class="steps steps-vertical">
+ <li class="step step-complete">
+ <span class="step-marker"><i class="ph ph-check"></i></span>
+ <h3 class="step-title">Queued</h3>
+ <p class="step-text">Job accepted and waiting for worker slot.</p>
+ </li>
+ <li class="step step-current" aria-current="step">
+ <span class="step-marker">2</span>
+ <h3 class="step-title">Running</h3>
+ <p class="step-text">Current task is processing input data.</p>
+ </li>
+</ol>
+ Таблицы используют базовый класс .table, semantic table markup и дополнительные элементы: .table-caption, .table-head, .table-body, .table-foot.
+| Project | +Status | +Owner | +Updated | +Actions | +
|---|---|---|---|---|
| Website Redesign | +Online | +@design |
+ 2 min ago | ++ + + | +
| Billing API | +Degraded | +@backend |
+ 1 hour ago | ++ + + | +
| Archive Import | +Offline | +@ops |
+ Yesterday | ++ + + | +
| + | ||||
{{ value }}
+
+ <table class="table">
+ <caption class="table-caption">Projects list</caption>
+ <thead class="table-head">
+ <tr class="table-row">
+ <th scope="col">Project</th>
+ <th scope="col">Status</th>
+ </tr>
+ </thead>
+ <tbody class="table-body">
+ <tr class="table-row">
+ <td>Website Redesign</td>
+ <td><span class="badge badge-success">Online</span></td>
+ </tr>
+ </tbody>
+</table>
+ <table class="table table-compact">
+ <caption class="table-caption">Recent jobs</caption>
+ <thead class="table-head">
+ <tr class="table-row">
+ <th scope="col">Job</th>
+ <th scope="col">State</th>
+ <th scope="col">Run ID</th>
+ </tr>
+ </thead>
+ <tbody class="table-body">
+ <tr class="table-row">
+ <td>Sync catalog</td>
+ <td><span class="badge badge-success">Done</span></td>
+ <td class="table-cell-mono">run_1048</td>
+ </tr>
+ </tbody>
+</table>
+ Tabs переключают связанные панели внутри одного контекста. Компонент поддерживает click, keyboard navigation и ARIA state через Tabs.init().
Overview keeps the primary status, totals, and next actions visible without leaving the current screen.
+Activity contains the latest events, audit notes, and handoff messages for the same record.
+Settings groups secondary options that affect this context but do not need full page navigation.
+Use compact tabs in dense panels, settings pages, and narrow metadata blocks.
+Disabled tabs can stay visible when a feature is unavailable for the current object.
+Billing content is disabled in this example.
+Vertical tabs work well when labels are longer or the panel needs a stable left rail.
+Use them for account details, admin records, or focused configuration groups.
+On smaller screens the tab rail becomes a horizontal scrollable list.
+<div class="tabs" data-tabs>
+ <div class="tabs-list" aria-label="Project sections">
+ <button class="tab tab-active" type="button" aria-controls="panel-overview">Overview</button>
+ <button class="tab" type="button" aria-controls="panel-activity">Activity</button>
+ </div>
+ <div class="tabs-panels">
+ <div class="tab-panel tab-panel-active" id="panel-overview">Overview content</div>
+ <div class="tab-panel" id="panel-activity">Activity content</div>
+ </div>
+</div>
+ Timeline и Activity Log показывают историю событий, audit trail, job updates и системные изменения.
+Production bundle finished without warnings.
+ +Design tokens changed and need one visual pass before release.
+ +Component documentation and demo examples were updated.
+<ol class="timeline">
+ <li class="timeline-item timeline-item-success">
+ <span class="timeline-marker"><i class="ph ph-check-circle"></i></span>
+ <div class="timeline-content">
+ <article class="timeline-card">
+ <div class="timeline-header">
+ <h3 class="timeline-title">Build completed</h3>
+ <time class="timeline-time" datetime="2026-04-11T22:24:00">22:24</time>
+ </div>
+ <p class="timeline-text">Production bundle finished without warnings.</p>
+ </article>
+ </div>
+ </li>
+</ol>
+ <div class="activity-log">
+ <div class="activity-log-row">
+ <time class="activity-log-time" datetime="2026-04-11T22:24:00">22:24</time>
+ <span class="activity-log-title">Build completed</span>
+ <span class="badge badge-success">Done</span>
+ </div>
+</div>
+ Toasts создаются из JS и добавляются в `body` вызовом `.show()`. Для коротких уведомлений доступны методы `createInfo`, `createSuccess`, `createWarning`, `createDanger`.
+Toasts.createSuccess(
+ "Success",
+ "Операция выполнена успешно",
+ { lifetime: 4000, alone: true }
+).show();
+
+Toasts.createDanger("Error", "Произошла ошибка").show();
+ Базовая типографика задаёт размеры заголовков, текстовые состояния и моноширинный код. Используй семантические теги, а служебные классы добавляй для вариаций текста.
+Lead text выделяет вводный абзац, описание страницы или важный контекст перед набором действий.
+Основной текст интерфейса. Используется для контента.
+Вторичный текст, комментарии, подписи.
+Приглушённый текст, подсказки.
+Caption text для технических подписей, footnote и compact metadata.
+Состояния текста: success, warning, danger, info.
+Inline code npm run build и keyboard hint esc.
Интерфейс должен оставаться читаемым даже в плотных технических сценариях.GNexus UI Kit+
<h1 class="contrast">Dashboard</h1>
+<span class="eyebrow">System label</span>
+<p class="text-lead">Вводный текст страницы.</p>
+<p class="text-primary">Основной текст интерфейса.</p>
+<p class="text-muted">Приглушённая подсказка.</p>
+<code class="code">npm run build</code>
+<span class="kbd">esc</span>
+ Utilities дают предсказуемую систему отступов, размеров, раскладки и текстовых правок без создания новых компонентов. Spacing scale используется и в SCSS-переменных, и в utility-классах.
+| Token | Value | Alias | Utility examples |
|---|---|---|---|
$space-0 | 0 | - | m-0 p-0 g-0 |
$space-1 | 5px | $space-xs | mt-1 px-1 |
$space-2 | 8px | $space-sm | mb-2 g-2 |
$space-3 | 12px | - | p-3 gy-3 |
$space-4 | 15px | $space-md | mx-4 gx-4 |
$space-5 | 18px | - | pt-5 pb-5 |
$space-6 | 22px | $space-lg | my-6 g-6 |
$space-8 | 34px | $space-xl | mt-8 p-8 |
$space-10 | 48px | $space-xxl | mb-10 py-10 |
$space-12 | 80px | - | mt-12 pb-12 |
m-* mt-* mr-* mb-* ml-* mx-* my-*p-* pt-* pr-* pb-* pl-* px-* py-*g-* gx-* gy-*row column f-grid grid grid-2 grid-3items-center justify-between justify-endfs-sm text-center text-uppercase text-nowrap<div class="grid-2 g-4 items-center">
+ <div class="p-5">Panel</div>
+ <div class="px-6 py-3 text-right">Actions</div>
+</div>
+