refactor(PageHeaderActions): restructure actions layout and enhance button visibility for improved user interaction
Publish Fast Tabler Docker image / build-and-push-fast (push) Successful in 1m52s

This commit is contained in:
2026-02-16 22:45:06 +07:00
parent 0935eacd3f
commit 1f49b02ba0
4 changed files with 67 additions and 59 deletions
+1
View File
@@ -436,6 +436,7 @@
height: 100%;
}
.header-actions .btn svg { width: 18px; height: 18px; }
.header-actions .header-actions-right { flex-shrink: 0; }
/* Ensure dropdowns in header-actions overlay tables/cards correctly but stay below modals */
.header-actions .dropdown-menu,
+24 -23
View File
@@ -602,7 +602,7 @@ function ServerManager() {
</span>
}
actions={
<div className="d-flex flex-wrap align-items-center gap-2">
<>
<PageHeaderActions
loading={loading}
onRefresh={handleRefreshAll}
@@ -612,33 +612,34 @@ function ServerManager() {
onSave={handleSaveChanges}
disableSave={loading}
/>
<span className="vr d-none d-md-inline align-self-center" style={{ minHeight: '1.5rem' }} />
<div className="btn-group" role="group" aria-label="Переключение режимов">
<div className="mt-3 d-flex flex-wrap align-items-center justify-content-end gap-2">
<div className="btn-group" role="group" aria-label="Переключение режимов">
<button
className={`btn ${activeTab === 'servers' ? 'btn-primary' : 'btn-outline-primary'}`}
onClick={() => setActiveTab('servers')}
type="button"
>
<IconServer size={16} className="me-1" />
Серверы
</button>
<button
className={`btn ${activeTab === 'connections' ? 'btn-primary' : 'btn-outline-primary'}`}
onClick={() => setActiveTab('connections')}
type="button"
>
<IconGitBranch size={16} className="me-1" />
Связи
</button>
</div>
<button
className={`btn ${activeTab === 'servers' ? 'btn-primary' : 'btn-outline-primary'}`}
onClick={() => setActiveTab('servers')}
type="button"
className="btn btn-primary"
onClick={handleOpenAddServerModal}
>
<IconServer size={16} className="me-1" />
Серверы
</button>
<button
className={`btn ${activeTab === 'connections' ? 'btn-primary' : 'btn-outline-primary'}`}
onClick={() => setActiveTab('connections')}
type="button"
>
<IconGitBranch size={16} className="me-1" />
Связи
<IconPlus size={16} className="me-1" /> Добавить
</button>
</div>
<button
type="button"
className="btn btn-primary"
onClick={handleOpenAddServerModal}
>
<IconPlus size={16} className="me-1" /> Добавить
</button>
</div>
</>
}
/>
+6 -8
View File
@@ -3,12 +3,12 @@ import React from 'react'
/**
* Единый заголовок страницы в стиле Tabler UI.
* Используется на всех страницах для одинакового UX.
* Кнопки (actions) отображаются под названием страницы.
* Кнопки (actions) отображаются справа от названия страницы.
* @param {string} title - Заголовок страницы
* @param {React.ReactNode} [icon] - Иконка слева от заголовка (например, <IconServer />)
* @param {string} [pretitle] - Небольшая надпись над заголовком (page-pretitle)
* @param {React.ReactNode} [meta] - Подзаголовок или мета-информация под заголовком
* @param {React.ReactNode} [actions] - Кнопки/действия под заголовком (btn-list, btn-group)
* @param {React.ReactNode} [actions] - Кнопки/действия справа в шапке (btn-list, btn-group)
*/
function PageHeader({ title, icon, pretitle, actions, meta }) {
return (
@@ -26,14 +26,12 @@ function PageHeader({ title, icon, pretitle, actions, meta }) {
<div className="text-muted small mt-1">{meta}</div>
)}
</div>
</div>
{actions && (
<div className="row mt-2">
<div className="col d-print-none">
{actions && (
<div className="col-auto ms-auto d-print-none">
{actions}
</div>
</div>
)}
)}
</div>
</div>
)
}
+36 -28
View File
@@ -59,9 +59,13 @@ function PageHeaderActions({
{(onImport || onExport) && placeholderBtn(160)}
{onClear && placeholderBtn(130)}
{onClearCommunities && placeholderBtn(180)}
{onSave && placeholderBtn(140)}
{onHistory && placeholderBtn(110)}
</div>
{(onSave || onHistory) && (
<div className="header-actions-right ms-auto d-flex align-items-center gap-2">
{onSave && placeholderBtn(140)}
{onHistory && placeholderBtn(110)}
</div>
)}
</div>
);
}
@@ -140,33 +144,37 @@ function PageHeaderActions({
<IconEraser className="me-1" /> Очистить community
</button>
)}
{onSave && (
<button className="btn btn-primary" type="button" onClick={onSave} disabled={disableSave} title="Сохранить">
{loading ? (
<>
<span className="spinner-border spinner-border-sm me-2" role="status"></span>
Сохранение...
</>
) : (
<>
<IconDeviceFloppy className="me-1" /> Сохранить
</>
)}
</button>
)}
{onHistory && (
<div className="btn-group">
<button className="btn btn-outline-secondary" type="button" onClick={onHistory} title="История версий">История</button>
{lastModified && (
<button className="btn btn-outline-secondary" type="button" onClick={onHistory} title={`Последние изменения: ${formatDateTimeWithRelative(lastModified)}`}>
<IconClock className="me-1" />
<span className="d-none d-sm-inline">{formatDateTimeWithRelative(lastModified)}</span>
<span className="d-inline d-sm-none">{formatDateTimeWithRelative(lastModified)}</span>
</button>
)}
</div>
)}
</div>
{(onSave || onHistory) && (
<div className="header-actions-right ms-auto d-flex align-items-center gap-2">
{onSave && (
<button className="btn btn-primary" type="button" onClick={onSave} disabled={disableSave} title="Сохранить">
{loading ? (
<>
<span className="spinner-border spinner-border-sm me-2" role="status"></span>
Сохранение...
</>
) : (
<>
<IconDeviceFloppy className="me-1" /> Сохранить
</>
)}
</button>
)}
{onHistory && (
<div className="btn-group d-none d-sm-inline-flex">
<button className="btn btn-outline-secondary" type="button" onClick={onHistory} title="История версий">История</button>
{lastModified && (
<button className="btn btn-outline-secondary" type="button" onClick={onHistory} title={`Последние изменения: ${formatDateTimeWithRelative(lastModified)}`}>
<IconClock className="me-1" />
<span className="d-none d-sm-inline">{formatDateTimeWithRelative(lastModified)}</span>
<span className="d-inline d-sm-none">{formatDateTimeWithRelative(lastModified)}</span>
</button>
)}
</div>
)}
</div>
)}
{/* Компактное меню для маленьких экранов */}
{secondaryButtons && (
<div className="btn-group d-inline-flex d-sm-none">