polish: chips bar + appearance menu placement + brand title (v0.2.2)

- appearance menu drops below the active-filter chips row when it is shown,
  and sits 10px further from the right edge
- "Скопировать ссылку" moved to the left of the chips, separated by a divider
- enlarge "Green Media" title so it is flush with the "Адресная программа"
  line below it

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
aaverbitskiy 2026-08-14 16:00:47 +00:00
parent aeb4651f6f
commit 9688ee986d
3 changed files with 33 additions and 21 deletions

View File

@ -1,7 +1,7 @@
{ {
"name": "mapdash-frontend", "name": "mapdash-frontend",
"private": true, "private": true,
"version": "0.2.1", "version": "0.2.2",
"type": "module", "type": "module",
"scripts": { "scripts": {
"dev": "vite", "dev": "vite",

View File

@ -184,8 +184,13 @@ function rebuildDecorMenu(): void {
// closer than 30px to the window edge; overflow scrolls inside. // closer than 30px to the window edge; overflow scrolls inside.
function positionDecorMenu(): void { function positionDecorMenu(): void {
const header = document.querySelector('header'); const header = document.querySelector('header');
const headerBottom = header ? header.getBoundingClientRect().bottom : 0; let refBottom = header ? header.getBoundingClientRect().bottom : 0;
const top = headerBottom + 25; // Drop below the active-filter chips row too, when it is showing.
const chips = el.filterChips;
if (chips && chips.style.display !== 'none' && chips.getClientRects().length) {
refBottom = Math.max(refBottom, chips.getBoundingClientRect().bottom);
}
const top = refBottom + 25;
el.decorMenu.style.top = top + 'px'; el.decorMenu.style.top = top + 'px';
el.decorMenu.style.maxHeight = Math.max(120, window.innerHeight - top - 30) + 'px'; el.decorMenu.style.maxHeight = Math.max(120, window.innerHeight - top - 30) + 'px';
} }
@ -275,6 +280,27 @@ function renderChips(): void {
el.filterChips.innerHTML = ''; el.filterChips.innerHTML = '';
if (!items.length) { el.filterChips.style.display = 'none'; return; } if (!items.length) { el.filterChips.style.display = 'none'; return; }
el.filterChips.style.display = ''; el.filterChips.style.display = '';
// "Скопировать ссылку" — always leftmost, separated from the chips by a rule.
const share = document.createElement('button');
share.type = 'button';
share.className = 'chips-share';
share.textContent = 'Скопировать ссылку';
share.addEventListener('click', () => {
navigator.clipboard
?.writeText(location.href)
.then(() => {
share.textContent = 'Ссылка скопирована';
window.setTimeout(() => { share.textContent = 'Скопировать ссылку'; }, 1600);
})
.catch(() => { /* clipboard unavailable */ });
});
el.filterChips.appendChild(share);
const divider = document.createElement('span');
divider.className = 'chips-divider';
divider.textContent = '|';
el.filterChips.appendChild(divider);
for (const it of items) { for (const it of items) {
const chip = document.createElement('span'); const chip = document.createElement('span');
chip.className = 'chip'; chip.className = 'chip';
@ -294,21 +320,6 @@ function renderChips(): void {
clear.textContent = 'Сбросить всё'; clear.textContent = 'Сбросить всё';
clear.addEventListener('click', resetFilters); clear.addEventListener('click', resetFilters);
el.filterChips.appendChild(clear); el.filterChips.appendChild(clear);
const share = document.createElement('button');
share.type = 'button';
share.className = 'chips-share';
share.textContent = 'Скопировать ссылку';
share.addEventListener('click', () => {
navigator.clipboard
?.writeText(location.href)
.then(() => {
share.textContent = 'Ссылка скопирована';
window.setTimeout(() => { share.textContent = 'Скопировать ссылку'; }, 1600);
})
.catch(() => { /* clipboard unavailable */ });
});
el.filterChips.appendChild(share);
} }
el.emptyResetTimeline.addEventListener('click', resetFilters); el.emptyResetTimeline.addEventListener('click', resetFilters);
el.emptyResetMap.addEventListener('click', resetFilters); el.emptyResetMap.addEventListener('click', resetFilters);

View File

@ -169,7 +169,7 @@ input::placeholder { color: var(--text-muted); }
bottom, level with the date fields / mode switch. */ bottom, level with the date fields / mode switch. */
.hgroup-brand { align-items: stretch; } .hgroup-brand { align-items: stretch; }
.brand { display: flex; flex-direction: column; justify-content: flex-start; line-height: 1.2; } .brand { display: flex; flex-direction: column; justify-content: flex-start; line-height: 1.2; }
.brand-name { font-size: 18px; font-weight: 700; white-space: nowrap; } .brand-name { font-size: 23px; font-weight: 700; white-space: nowrap; }
.brand-sub { font-size: 15px; font-weight: 400; color: var(--text-muted); white-space: nowrap; } .brand-sub { font-size: 15px; font-weight: 400; color: var(--text-muted); white-space: nowrap; }
.field { display: flex; flex-direction: column; gap: var(--size-1); } .field { display: flex; flex-direction: column; gap: var(--size-1); }
@ -258,7 +258,7 @@ input::placeholder { color: var(--text-muted); }
.decor-menu { .decor-menu {
/* Fixed to the viewport: JS sets `top` (25px below the header) and `max-height` /* Fixed to the viewport: JS sets `top` (25px below the header) and `max-height`
(ending 30px above the window bottom) on open, so it never overflows. */ (ending 30px above the window bottom) on open, so it never overflows. */
display: none; position: fixed; right: 18px; display: none; position: fixed; right: 28px;
background: var(--bg); border: 1px solid var(--border); border-radius: var(--radius); background: var(--bg); border: 1px solid var(--border); border-radius: var(--radius);
box-shadow: var(--shadow-md); padding: var(--size-3); z-index: 50; box-shadow: var(--shadow-md); padding: var(--size-3); z-index: 50;
width: max-content; max-width: min(420px, 92vw); overflow-y: auto; width: max-content; max-width: min(420px, 92vw); overflow-y: auto;
@ -596,9 +596,10 @@ html.role-pending .manager-only { display: none !important; }
.chips-share { .chips-share {
border: none; background: transparent; color: var(--accent); cursor: pointer; border: none; background: transparent; color: var(--accent); cursor: pointer;
font-size: 12px; font-weight: 500; padding: 3px 8px; border-radius: var(--radius-sm); font-size: 12px; font-weight: 500; padding: 3px 8px; border-radius: var(--radius-sm);
margin-left: auto; transition: background var(--transition); transition: background var(--transition);
} }
.chips-share:hover { background: var(--accent-weak); } .chips-share:hover { background: var(--accent-weak); }
.chips-divider { color: var(--border-strong); font-weight: 400; user-select: none; }
/* ---- polish: focus rings, animations, scrollbars, tabular figures ---- */ /* ---- polish: focus rings, animations, scrollbars, tabular figures ---- */
button:focus-visible, input[type=text]:focus-visible, input[type=date]:focus-visible, button:focus-visible, input[type=text]:focus-visible, input[type=date]:focus-visible,