Refine server picker toggle and bump Harbor versions
All checks were successful
Build and Deploy Gateway / build-and-push (push) Successful in 15s
Build and Deploy Gateway / deploy (push) Successful in 7s

This commit is contained in:
2026-07-12 14:31:34 +03:00
parent d9745e9aed
commit fca3c0b705
4 changed files with 215 additions and 113 deletions

View File

@@ -1,6 +1,6 @@
export const HARBOR_VERSIONS = Object.freeze({ export const HARBOR_VERSIONS = Object.freeze({
macClient: '0.7.13', macClient: '0.7.14',
gatewayClient: '0.7.13', gatewayClient: '0.7.14',
gatewayBackend: '0.7.1', gatewayBackend: '0.7.1',
}); });

View File

@@ -181,7 +181,6 @@ export function ServerPicker({
/> />
)); ));
if (!advanced) {
const simpleServers = [ const simpleServers = [
...(selected ? [selected] : []), ...(selected ? [selected] : []),
...servers.filter(({ id }) => id !== selectedServerId), ...servers.filter(({ id }) => id !== selectedServerId),
@@ -190,12 +189,24 @@ export function ServerPicker({
return <section className="client-servers is-scalable" aria-label="Выберите сервер"> return <section className="client-servers is-scalable" aria-label="Выберите сервер">
{prompt && <span className="client-server-prompt">Выберите сервер</span>} {prompt && <span className="client-server-prompt">Выберите сервер</span>}
<button <button
className="client-server-mode-toggle" className={`client-server-mode-toggle${advanced ? ' is-open' : ''}`}
type="button" type="button"
aria-expanded="false" aria-expanded={advanced}
onClick={() => setAdvanced(true)} aria-label={advanced ? 'Скрыть поиск и фильтры' : 'Показать поиск и фильтры'}
>Поиск и фильтры</button> onClick={() => setAdvanced((current) => !current)}
<div className={`client-server-scroll${leaving ? ' is-leaving' : ''}`} key={`${serverKey}:${revealVersion}`}> >
<span>Поиск и фильтры</span>
<svg viewBox="0 0 12 8" aria-hidden="true"><path d="m1 1 5 5 5-5" /></svg>
</button>
<div className="client-server-mode-panels">
<div
className={`client-server-mode-panel is-simple${advanced ? '' : ' is-open'}`}
aria-hidden={advanced}
inert={advanced ? true : undefined}
>
<div className="client-server-mode-panel-inner">
<div className={`client-server-scroll${leaving ? ' is-leaving' : ''}`} key={`simple:${serverKey}:${revealVersion}`}>
<div className="client-server-grid"> <div className="client-server-grid">
{simpleServers.map((server, index) => <ServerRow {simpleServers.map((server, index) => <ServerRow
key={server.id} key={server.id}
@@ -206,23 +217,19 @@ export function ServerPicker({
onSelect={select} onSelect={select}
/>)} />)}
</div> </div>
{servers.length > simpleServers.length && <button {servers.length > simpleServers.length && <p className="client-server-overflow-note">
className="client-server-mode-toggle" Ещё {servers.length - simpleServers.length} доступны через поиск
type="button" </p>}
onClick={() => setAdvanced(true)} </div>
>Ещё {servers.length - simpleServers.length} открыть поиск</button>} </div>
</div> </div>
</section>;
}
return <section className="client-servers is-scalable" aria-label="Выберите сервер"> <div
{prompt && <span className="client-server-prompt">Выберите сервер</span>} className={`client-server-mode-panel is-advanced${advanced ? ' is-open' : ''}`}
<button aria-hidden={!advanced}
className="client-server-mode-toggle" inert={!advanced ? true : undefined}
type="button" >
aria-expanded="true" <div className="client-server-mode-panel-inner">
onClick={() => setAdvanced(false)}
>Простой список</button>
<div className="client-server-tools"> <div className="client-server-tools">
<input <input
type="search" type="search"
@@ -272,7 +279,7 @@ export function ServerPicker({
/>} />}
</div> </div>
<div className={`client-server-scroll${leaving ? ' is-leaving' : ''}`} key={`${serverKey}:${revealVersion}`}> <div className={`client-server-scroll${leaving ? ' is-leaving' : ''}`} key={`advanced:${serverKey}:${revealVersion}`}>
{!visible.length && <p className="client-server-empty">Серверы не найдены</p>} {!visible.length && <p className="client-server-empty">Серверы не найдены</p>}
{grouped ? groupServers(visible).map(([group, items]) => { {grouped ? groupServers(visible).map(([group, items]) => {
const isCollapsed = collapsed.includes(group); const isCollapsed = collapsed.includes(group);
@@ -294,5 +301,8 @@ export function ServerPicker({
<button className="client-server-more" type="button" disabled={page + 1 === pageCount} onClick={() => setPage((current) => current + 1)}>Дальше</button> <button className="client-server-more" type="button" disabled={page + 1 === pageCount} onClick={() => setPage((current) => current + 1)}>Дальше</button>
</nav>} </nav>}
</div> </div>
</div>
</div>
</div>
</section>; </section>;
} }

View File

@@ -2708,7 +2708,10 @@ p {
.client-server-mode-toggle { .client-server-mode-toggle {
min-height: 32px; min-height: 32px;
display: block; display: flex;
align-items: center;
justify-content: center;
gap: 7px;
margin: 0 auto 8px; margin: 0 auto 8px;
padding: 5px 8px; padding: 5px 8px;
border: 0; border: 0;
@@ -2716,14 +2719,94 @@ p {
color: var(--client-muted); color: var(--client-muted);
font: 700 9px/1.2 'JetBrains Mono', 'SF Mono', ui-monospace, Menlo, monospace; font: 700 9px/1.2 'JetBrains Mono', 'SF Mono', ui-monospace, Menlo, monospace;
cursor: pointer; cursor: pointer;
transition: color 220ms ease, text-shadow 320ms ease;
} }
.client-server-mode-toggle:hover { .client-server-mode-toggle:hover,
.client-server-mode-toggle.is-open {
color: var(--client-accent); color: var(--client-accent);
text-shadow: 0 0 10px color-mix(in oklch, var(--client-accent) 38%, transparent);
} }
.client-server-scroll > .client-server-mode-toggle { .client-server-mode-toggle svg {
margin-top: 8px; width: 10px;
fill: none;
stroke: currentColor;
stroke-width: 1.5;
transition: transform 480ms cubic-bezier(0.16, 1, 0.3, 1);
}
.client-server-mode-toggle.is-open svg {
transform: rotate(180deg);
}
.client-server-mode-panels {
display: grid;
}
.client-server-mode-panel {
display: grid;
grid-template-rows: 0fr;
opacity: 0;
visibility: hidden;
filter: blur(7px);
transform: translateY(-10px);
transition:
grid-template-rows 620ms cubic-bezier(0.16, 1, 0.3, 1),
opacity 260ms ease,
filter 420ms ease,
transform 520ms cubic-bezier(0.16, 1, 0.3, 1),
visibility 0s 620ms;
}
.client-server-mode-panel.is-open {
grid-template-rows: 1fr;
opacity: 1;
visibility: visible;
filter: blur(0);
transform: translateY(0);
transition-delay: 0s;
}
.client-server-mode-panel-inner {
min-height: 0;
overflow: hidden;
}
.client-server-mode-panel.is-advanced .client-server-tools,
.client-server-mode-panel.is-advanced .client-server-pinned,
.client-server-mode-panel.is-advanced .client-server-scroll {
opacity: 0;
filter: blur(5px);
transform: translateY(-8px);
transition: opacity 300ms ease, filter 440ms ease, transform 520ms cubic-bezier(0.16, 1, 0.3, 1);
}
.client-server-mode-panel.is-advanced.is-open .client-server-tools,
.client-server-mode-panel.is-advanced.is-open .client-server-pinned,
.client-server-mode-panel.is-advanced.is-open .client-server-scroll {
opacity: 1;
filter: blur(0);
transform: translateY(0);
}
.client-server-mode-panel.is-advanced.is-open .client-server-tools {
transition-delay: 90ms;
}
.client-server-mode-panel.is-advanced.is-open .client-server-pinned {
transition-delay: 150ms;
}
.client-server-mode-panel.is-advanced.is-open .client-server-scroll {
transition-delay: 210ms;
}
.client-server-overflow-note {
margin: 10px 0 0;
color: var(--client-muted);
font-size: 9px;
text-align: center;
} }
.client-server-tools input { .client-server-tools input {
@@ -3398,6 +3481,11 @@ p {
.client-server, .client-server,
.client-server-auto, .client-server-auto,
.client-server-mode-toggle, .client-server-mode-toggle,
.client-server-mode-toggle svg,
.client-server-mode-panel,
.client-server-mode-panel .client-server-tools,
.client-server-mode-panel .client-server-pinned,
.client-server-mode-panel .client-server-scroll,
.client-server-favorite, .client-server-favorite,
.client-server-group-toggle, .client-server-group-toggle,
.client-server-more, .client-server-more,

View File

@@ -50,8 +50,12 @@ test('server picker keeps health manual and the rendered result window bounded',
test('server picker starts simple and reveals advanced controls on demand', () => { test('server picker starts simple and reveals advanced controls on demand', () => {
assert.match(picker, /const \[advanced, setAdvanced\] = useState\(false\)/); assert.match(picker, /const \[advanced, setAdvanced\] = useState\(false\)/);
assert.match(picker, /if \(!advanced\)/); assert.match(picker, /aria-expanded={advanced}/);
assert.match(picker, />Поиск и фильтры<\/button>/); assert.match(picker, /setAdvanced\(\(current\) => !current\)/);
assert.match(picker, />Простой список<\/button>/); assert.match(picker, /<span>Поиск и фильтры<\/span>/);
assert.match(picker, /client-server-mode-panel is-simple/);
assert.match(picker, /client-server-mode-panel is-advanced/);
assert.match(picker, /inert={advanced \? true : undefined}/);
assert.match(picker, /inert={!advanced \? true : undefined}/);
assert.match(picker, /\.slice\(0, SERVER_RESULT_WINDOW\)/); assert.match(picker, /\.slice\(0, SERVER_RESULT_WINDOW\)/);
}); });