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
Publish Fast Tabler Docker image / build-and-push-fast (push) Successful in 1m52s
This commit is contained in:
@@ -436,6 +436,7 @@
|
|||||||
height: 100%;
|
height: 100%;
|
||||||
}
|
}
|
||||||
.header-actions .btn svg { width: 18px; height: 18px; }
|
.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 */
|
/* Ensure dropdowns in header-actions overlay tables/cards correctly but stay below modals */
|
||||||
.header-actions .dropdown-menu,
|
.header-actions .dropdown-menu,
|
||||||
|
|||||||
@@ -602,7 +602,7 @@ function ServerManager() {
|
|||||||
</span>
|
</span>
|
||||||
}
|
}
|
||||||
actions={
|
actions={
|
||||||
<div className="d-flex flex-wrap align-items-center gap-2">
|
<>
|
||||||
<PageHeaderActions
|
<PageHeaderActions
|
||||||
loading={loading}
|
loading={loading}
|
||||||
onRefresh={handleRefreshAll}
|
onRefresh={handleRefreshAll}
|
||||||
@@ -612,33 +612,34 @@ function ServerManager() {
|
|||||||
onSave={handleSaveChanges}
|
onSave={handleSaveChanges}
|
||||||
disableSave={loading}
|
disableSave={loading}
|
||||||
/>
|
/>
|
||||||
<span className="vr d-none d-md-inline align-self-center" style={{ minHeight: '1.5rem' }} />
|
<div className="mt-3 d-flex flex-wrap align-items-center justify-content-end gap-2">
|
||||||
<div className="btn-group" role="group" aria-label="Переключение режимов">
|
<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
|
<button
|
||||||
className={`btn ${activeTab === 'servers' ? 'btn-primary' : 'btn-outline-primary'}`}
|
|
||||||
onClick={() => setActiveTab('servers')}
|
|
||||||
type="button"
|
type="button"
|
||||||
|
className="btn btn-primary"
|
||||||
|
onClick={handleOpenAddServerModal}
|
||||||
>
|
>
|
||||||
<IconServer size={16} className="me-1" />
|
<IconPlus 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>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
<button
|
</>
|
||||||
type="button"
|
|
||||||
className="btn btn-primary"
|
|
||||||
onClick={handleOpenAddServerModal}
|
|
||||||
>
|
|
||||||
<IconPlus size={16} className="me-1" /> Добавить
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
|
|||||||
@@ -3,12 +3,12 @@ import React from 'react'
|
|||||||
/**
|
/**
|
||||||
* Единый заголовок страницы в стиле Tabler UI.
|
* Единый заголовок страницы в стиле Tabler UI.
|
||||||
* Используется на всех страницах для одинакового UX.
|
* Используется на всех страницах для одинакового UX.
|
||||||
* Кнопки (actions) отображаются под названием страницы.
|
* Кнопки (actions) отображаются справа от названия страницы.
|
||||||
* @param {string} title - Заголовок страницы
|
* @param {string} title - Заголовок страницы
|
||||||
* @param {React.ReactNode} [icon] - Иконка слева от заголовка (например, <IconServer />)
|
* @param {React.ReactNode} [icon] - Иконка слева от заголовка (например, <IconServer />)
|
||||||
* @param {string} [pretitle] - Небольшая надпись над заголовком (page-pretitle)
|
* @param {string} [pretitle] - Небольшая надпись над заголовком (page-pretitle)
|
||||||
* @param {React.ReactNode} [meta] - Подзаголовок или мета-информация под заголовком
|
* @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 }) {
|
function PageHeader({ title, icon, pretitle, actions, meta }) {
|
||||||
return (
|
return (
|
||||||
@@ -26,14 +26,12 @@ function PageHeader({ title, icon, pretitle, actions, meta }) {
|
|||||||
<div className="text-muted small mt-1">{meta}</div>
|
<div className="text-muted small mt-1">{meta}</div>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
{actions && (
|
||||||
{actions && (
|
<div className="col-auto ms-auto d-print-none">
|
||||||
<div className="row mt-2">
|
|
||||||
<div className="col d-print-none">
|
|
||||||
{actions}
|
{actions}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
)}
|
||||||
)}
|
</div>
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -59,9 +59,13 @@ function PageHeaderActions({
|
|||||||
{(onImport || onExport) && placeholderBtn(160)}
|
{(onImport || onExport) && placeholderBtn(160)}
|
||||||
{onClear && placeholderBtn(130)}
|
{onClear && placeholderBtn(130)}
|
||||||
{onClearCommunities && placeholderBtn(180)}
|
{onClearCommunities && placeholderBtn(180)}
|
||||||
{onSave && placeholderBtn(140)}
|
|
||||||
{onHistory && placeholderBtn(110)}
|
|
||||||
</div>
|
</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>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -140,33 +144,37 @@ function PageHeaderActions({
|
|||||||
<IconEraser className="me-1" /> Очистить community
|
<IconEraser className="me-1" /> Очистить community
|
||||||
</button>
|
</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>
|
</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 && (
|
{secondaryButtons && (
|
||||||
<div className="btn-group d-inline-flex d-sm-none">
|
<div className="btn-group d-inline-flex d-sm-none">
|
||||||
|
|||||||
Reference in New Issue
Block a user