diff --git a/PROJECT_NOTES.md b/PROJECT_NOTES.md index 2c3fb7d..52c1d0a 100644 --- a/PROJECT_NOTES.md +++ b/PROJECT_NOTES.md @@ -4,7 +4,7 @@ ## Цель -Вынести UI веб-панели умного дома из `webclient` в отдельный UI-kit с простой сборкой для SCSS, HTML из частей и небольшого количества browser JS. Нужна отдельная страница демонстрации и документации компонентов. +Вынести UI из `webclient` в отдельный универсальный UI-kit с простой сборкой для SCSS, HTML из частей и небольшого количества browser JS. Нужна отдельная страница демонстрации и документации компонентов. ## Что есть сейчас @@ -118,13 +118,83 @@ - `gulpfile.js` - сборка SCSS, JS, HTML partials, static assets и BrowserSync server. - `src/scss/kit.scss` - основной SCSS entry UI-kit. - `src/scss/components/*.scss` - перенесенные стили UI-компонентов. -- `src/js/index.js` - browser entry, экспортирует UI API в `window` и в bundle global `GNexusUIKit`. +- `src/js/index.js` - browser entry, экспортирует UI API в `window` и `window.GNexusUIKit`. - `src/js/components/*.js` - перенесенные JS-компоненты UI-kit. - `demo/index.html` - демонстрационная страница. - `demo/partials/*.html` - HTML partials, перенесенные из бывших PHP demo partials. - `public/assets` - исходные статичные assets для demo/build. - `dist` - генерируемый результат сборки, игнорируется git. +## Текущее состояние UI-kit + +Дизайн теперь универсальный, без доменной привязки к умному дому. Визуальный стиль: dark cyber/terminal с легкой примесью Tokyo Night, IBM Plex Mono, Phosphor Icons, прямые формы, толстые акцентные бордеры, яркие status colors. После Tokyo Night pass на плотных светлых state-заливках используется темный `$color-black` текст для контраста. + +Favicon и карточечный image asset заменены на нейтральный `public/assets/imgs/gnexus-mark.svg`; старый `sh-icon.png` удален. + +Документированные секции demo: + +- Typography +- Palette +- Buttons +- Forms +- Editable String +- Navigation & Overlays +- Lists +- Badges +- Alerts +- Tables +- Data Patterns +- Toasts +- Cards +- Modals + +Новые компоненты после базового scaffold: + +- `src/scss/components/_navigation-overlays.scss` +- `src/js/components/overlays.js` +- `demo/partials/navigation-overlays.html` +- `src/scss/components/_input-group.scss` +- `src/scss/components/_data-patterns.scss` +- `src/js/components/input-patterns.js` +- `demo/partials/data-patterns.html` + +`Navigation & Overlays` включает tabs, dropdown, tooltip, popover. `Data Patterns` включает toolbar, search field, input group, pagination, empty state, skeleton. + +Публичный JS API: + +- `GNexusUIKit.Helper` +- `GNexusUIKit.Toasts` +- `GNexusUIKit.Modals` +- `GNexusUIKit.advancedSelect` +- `GNexusUIKit.editableString` +- `GNexusUIKit.confirmPopup` +- `GNexusUIKit.Overlays` +- `GNexusUIKit.InputPatterns` + +`Overlays.init()` и `InputPatterns.init()` запускаются автоматически на `DOMContentLoaded`. + +## Запланированные компоненты + +Ближайшие полезные additions: + +- Page Header: заголовок страницы, subtitle, actions, status. +- Key-Value / Description List: компактный read-only блок для деталей сущности. +- Progress: progress bar, usage meter, staged progress. +- Stepper / Steps: пошаговые сценарии и wizard-flow. +- Chip / Tag: selectable/removable метки для фильтров и labels. +- Avatar / Identity: initials, icon avatar, image avatar, status marker. +- Timeline / Activity Log: история событий, audit log, jobs. +- Accordion / Disclosure: раскрываемые группы настроек и деталей. +- Drawer / Side Panel: контекстные детали и quick edit без полной модалки. +- Confirm Dialog docs: `confirm-popup.js` уже есть, но его нужно вывести в demo/docs. + +Технический backlog: + +- Мигрировать Sass с `@import` на `@use`. +- Заменить deprecated Sass functions на `sass:map` и `sass:color`. +- Пересмотреть Gulp-зависимости или заменить сборку на более свежий простой pipeline. +- Добавить отдельную demo-секцию для loader/progress states. + Проверенные команды: - `npm install` - установлены зависимости. diff --git a/README.md b/README.md index 7da49b4..dc13375 100644 --- a/README.md +++ b/README.md @@ -92,6 +92,30 @@ GNexusUIKit.Toasts.createInfo("Info", "Message").show(); ``` +## JS API + +Bundle публикует компоненты и helper-модули двумя способами: + +```js +Toasts.createSuccess("Success", "Saved").show(); +GNexusUIKit.Toasts.createInfo("Info", "Message").show(); +``` + +Текущий публичный namespace: + +```text +GNexusUIKit.Helper +GNexusUIKit.Toasts +GNexusUIKit.Modals +GNexusUIKit.advancedSelect +GNexusUIKit.editableString +GNexusUIKit.confirmPopup +GNexusUIKit.Overlays +GNexusUIKit.InputPatterns +``` + +`Overlays.init()` и `InputPatterns.init()` запускаются автоматически на `DOMContentLoaded`. + ## Компоненты Сейчас в demo/docs представлены: @@ -106,12 +130,37 @@ - Badges - Alerts - Tables +- Data Patterns - Toasts - Cards - Modals Каждая секция demo содержит визуальный пример, короткое описание и копируемый блок кода. +### Navigation & Overlays + +Секция включает: + +- Tabs +- Dropdown +- Tooltip +- Popover + +JS-модуль: `src/js/components/overlays.js`. + +### Data Patterns + +Секция включает: + +- Toolbar +- Search Field +- Input Group +- Pagination +- Empty State +- Skeleton + +JS-модуль: `src/js/components/input-patterns.js` добавляет поведение для `data-input-clear`. + ## SCSS Базовые файлы: @@ -148,3 +197,25 @@ - Sass сейчас использует старый `@import`. Сборка проходит, но Dart Sass выводит deprecation warnings. Позже стоит мигрировать на `@use`. - `webclient/` оставлен как источник старого клиента и игнорируется git. - `.codex` является локальным служебным файлом и не должен попадать в коммиты. + +## Запланировано + +Ближайшие полезные компоненты: + +- Page Header: заголовок страницы, subtitle, actions, status. +- Key-Value / Description List: компактный read-only блок для деталей сущности. +- Progress: progress bar, usage meter, staged progress. +- Stepper / Steps: пошаговые сценарии и wizard-flow. +- Chip / Tag: selectable/removable метки для фильтров и labels. +- Avatar / Identity: initials, icon avatar, image avatar, status marker. +- Timeline / Activity Log: история событий, audit log, jobs. +- Accordion / Disclosure: раскрываемые группы настроек и деталей. +- Drawer / Side Panel: контекстные детали и quick edit без полной модалки. +- Confirm Dialog docs: `confirm-popup.js` уже есть в JS, но его нужно вынести в demo/docs. + +Технические задачи: + +- Мигрировать Sass с `@import` на `@use`. +- Заменить deprecated Sass functions на `sass:map` и `sass:color`. +- Пересмотреть Gulp-зависимости или заменить сборку на более свежий простой pipeline. +- Добавить отдельную demo-секцию для loader/progress states. diff --git a/demo/index.html b/demo/index.html index 2e15e0d..0823764 100644 --- a/demo/index.html +++ b/demo/index.html @@ -73,6 +73,11 @@
  • + + Data Patterns + +
  • +
  • Toasts @@ -102,6 +107,7 @@ @@include("partials/badges.html") @@include("partials/alerts.html") @@include("partials/tables.html") + @@include("partials/data-patterns.html") @@include("partials/toasts.html") @@include("partials/cards.html") @@include("partials/modals.html") diff --git a/demo/partials/data-patterns.html b/demo/partials/data-patterns.html new file mode 100644 index 0000000..77d26fb --- /dev/null +++ b/demo/partials/data-patterns.html @@ -0,0 +1,181 @@ +
    +

    Data Patterns

    +

    + Toolbar, search field, pagination, empty state и skeleton закрывают типовые состояния + таблиц, списков и страниц с данными. +

    + +
    +
    +
    +
    +

    Projects

    + 24 items +
    +
    + +
    +
    + + + + + +
    + + + +
    +
    +
    + +
    +
    + https:// + + +
    +
    + +
    + +
    + +
    +
    +
    + +
    +

    No results

    +

    + Nothing matched the current filters. Reset the query or create a new item. +

    +
    + + +
    +
    +
    + +
    +
    + + + + + +
    +
    + +
    +
    + Toolbar HTML + +
    +
    <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>
    +
    + +
    +
    + Input Group HTML + +
    +
    <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>
    +
    + +
    +
    + Pagination HTML + +
    +
    <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>
    +
    + +
    +
    + Empty State HTML + +
    +
    <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>
    +
    + +
    +
    + Skeleton HTML + +
    +
    <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>
    +
    + +
    +
    + Input Patterns JS + +
    +
    InputPatterns.init();
    +
    +// Available on the global namespace too:
    +GNexusUIKit.InputPatterns.init();
    +
    +
    diff --git a/src/js/components/input-patterns.js b/src/js/components/input-patterns.js new file mode 100644 index 0000000..037b68d --- /dev/null +++ b/src/js/components/input-patterns.js @@ -0,0 +1,34 @@ +let initialized = false; + +function init(root = document) { + if(root === document && initialized) { + return; + } + + root.addEventListener("click", event => { + const clearButton = event.target.closest("[data-input-clear]"); + + if(!clearButton) { + return; + } + + const group = clearButton.closest(".input-group"); + const input = group?.querySelector("input, textarea"); + + if(!input) { + return; + } + + input.value = ""; + input.dispatchEvent(new Event("input", { bubbles: true })); + input.focus(); + }); + + if(root === document) { + initialized = true; + } +} + +export default { + init +}; diff --git a/src/js/index.js b/src/js/index.js index b4ac6a2..d5793b2 100644 --- a/src/js/index.js +++ b/src/js/index.js @@ -5,6 +5,7 @@ import editableString from "./components/editable-string.js"; import confirmPopup from "./components/confirm-popup.js"; import Overlays from "./components/overlays.js"; +import InputPatterns from "./components/input-patterns.js"; import demoNavigation from "./demo-navigation.js"; import codeExamples from "./code-examples.js"; @@ -15,13 +16,16 @@ advancedSelect, editableString, confirmPopup, - Overlays + Overlays, + InputPatterns }; +window.GNexusUIKit = api; Object.assign(window, api); document.addEventListener("DOMContentLoaded", () => { Overlays.init(); + InputPatterns.init(); demoNavigation(); codeExamples(); }); @@ -33,7 +37,8 @@ advancedSelect, editableString, confirmPopup, - Overlays + Overlays, + InputPatterns }; export default api; diff --git a/src/scss/components/_data-patterns.scss b/src/scss/components/_data-patterns.scss new file mode 100644 index 0000000..7a22a9b --- /dev/null +++ b/src/scss/components/_data-patterns.scss @@ -0,0 +1,196 @@ +.toolbar { + display: flex; + flex-wrap: wrap; + align-items: center; + justify-content: space-between; + gap: $space-3; + width: 100%; + padding: $space-3; + border: $border-width-base solid $border-color-muted; + border-left-width: $border-width-accent; + background: $surface-panel-muted; + + .toolbar-group { + display: flex; + flex-wrap: wrap; + align-items: center; + gap: $space-2; + min-width: 0; + } + + .toolbar-title { + margin: 0; + font-size: $font-size-lg; + font-weight: $font-weight-bold; + line-height: $line-height-base; + text-transform: uppercase; + } + + .toolbar-meta { + color: $color-text-dark; + font-size: $font-size-sm; + } +} + +.pagination { + display: flex; + flex-wrap: wrap; + align-items: center; + gap: $space-2; + + .pagination-item { + display: inline-flex; + align-items: center; + justify-content: center; + min-width: $control-height-sm; + height: $control-height-sm; + padding: 0 $space-3; + border: $border-width-base solid $border-color-muted; + color: $color-text-medium; + background: $surface-panel-muted; + font-family: $font-family-base; + font-size: $font-size-sm; + font-weight: $font-weight-semibold; + line-height: $line-height-base; + cursor: pointer; + transition-duration: $motion-base; + transition-timing-function: $motion-ease; + transition-property: color, background, border-color, opacity; + + &:focus-visible { + @include focus_ring; + } + + @include hover_touch { + color: $color-black; + background: $color-secondary; + border-color: $color-secondary; + } + + &.pagination-item-active, + &[aria-current="page"] { + color: $color-black; + background: $color-primary; + border-color: $color-primary; + } + + &:disabled, + &.pagination-item-disabled { + color: $color-text-dark; + background: $surface-panel; + border-color: $border-color-muted; + cursor: not-allowed; + opacity: 0.72; + } + } + + .pagination-ellipsis { + color: $color-text-dark; + padding: 0 $space-1; + } +} + +.empty-state { + max-width: 560px; + padding: $space-6; + border: $border-width-base solid $border-color-muted; + border-left-width: $border-width-accent; + background: $surface-panel-muted; + + .empty-state-icon { + display: inline-flex; + align-items: center; + justify-content: center; + width: $control-height-lg; + height: $control-height-lg; + margin-bottom: $space-4; + color: $color-black; + background: $color-primary; + font-size: $icon-size-lg; + } + + .empty-state-title { + margin: 0 0 $space-2; + font-size: $font-size-xl; + font-weight: $font-weight-bold; + text-transform: uppercase; + } + + .empty-state-text { + max-width: 440px; + margin: 0 0 $space-5; + color: $color-text-medium; + line-height: $line-height-relaxed; + } + + .empty-state-actions { + display: flex; + flex-wrap: wrap; + gap: $space-2; + } + + &.empty-state-error { + border-color: $color-error; + + .empty-state-icon { + background: $color-error; + } + } +} + +.skeleton { + display: block; + position: relative; + overflow: hidden; + background: $surface-panel-strong; + + &::after { + content: ""; + position: absolute; + inset: 0; + transform: translateX(-100%); + background: linear-gradient( + 90deg, + transparent, + rgba($color-text-light, 0.12), + transparent + ); + animation: skeleton_shimmer 1.6s infinite; + } + + &.skeleton-line { + width: 100%; + height: 14px; + } + + &.skeleton-title { + width: 60%; + height: 22px; + } + + &.skeleton-block { + width: 100%; + height: 120px; + } + + &.skeleton-square { + width: $control-height-lg; + height: $control-height-lg; + } +} + +.skeleton-stack { + display: flex; + flex-direction: column; + gap: $space-3; + max-width: 520px; + padding: $space-4; + border: $border-width-base solid $border-color-muted; + background: $surface-panel-muted; +} + +@keyframes skeleton_shimmer { + 100% { + transform: translateX(100%); + } +} diff --git a/src/scss/components/_input-group.scss b/src/scss/components/_input-group.scss new file mode 100644 index 0000000..038b58b --- /dev/null +++ b/src/scss/components/_input-group.scss @@ -0,0 +1,92 @@ +.input-group { + display: flex; + align-items: stretch; + width: 100%; + max-width: 600px; + min-height: $control-height-lg; + border: $border-width-base solid $color-text-light; + border-bottom-width: $border-width-accent; + background: $surface-panel-muted; + transition-duration: $motion-base; + transition-timing-function: $motion-ease; + transition-property: border-color, background; + + &:focus-within { + @include focus_ring; + border-color: $color-secondary; + background: transparent; + } + + .input-group-addon, + .input-group-action { + display: inline-flex; + align-items: center; + justify-content: center; + min-width: $control-height-lg; + padding: 0 $space-3; + color: $color-text-medium; + background: rgba($color-text-light, 0.045); + border: 0; + font-family: $font-family-base; + font-size: $font-size-sm; + font-weight: $font-weight-semibold; + text-transform: uppercase; + } + + .input-group-action { + color: $color-primary; + cursor: pointer; + transition-duration: $motion-base; + transition-timing-function: $motion-ease; + transition-property: color, background; + + @include hover_touch { + color: $color-black; + background: $color-warning; + } + } + + .input-group-input { + flex: 1 1 auto; + min-width: 0; + border: 0; + padding: $space-3 $space-4; + color: $color-text-light; + background: transparent; + font-family: $font-family-base; + font-size: $font-size-base; + font-weight: $font-weight-medium; + letter-spacing: $letter-spacing-wide; + + &:focus { + outline: none; + } + + &::placeholder { + color: $color-text-dark; + } + } + + .ph, + .ph-bold { + font-size: $icon-size-md; + } + + &.input-group-compact { + min-height: $control-height-md; + + .input-group-addon, + .input-group-action { + min-width: $control-height-md; + } + + .input-group-input { + padding: $space-2 $space-3; + font-size: $font-size-sm; + } + } +} + +.search-field { + max-width: 420px; +} diff --git a/src/scss/kit.scss b/src/scss/kit.scss index c0eef79..8561172 100644 --- a/src/scss/kit.scss +++ b/src/scss/kit.scss @@ -8,9 +8,11 @@ @import "components/loader"; @import "components/buttons"; @import "components/forms"; +@import "components/input-group"; @import "components/lists"; @import "components/badges"; @import "components/tables"; +@import "components/data-patterns"; @import "components/toasts"; @import "components/cards"; @import "components/modals";