feat(window): maximize toggle + drag-zone fix + minWidth bump
- Средняя кнопка тайтлбара теперь toggle maximize/restore (была hide-to-tray, но иконка Square вводила в заблуждение — выглядит как нативная maximize). Double-click по тайтлбару тоже работает. - Иконка свапается Square ↔ Copy в зависимости от max-state, aria-label локализован (titlebar.maximize_aria / restore_aria). - Новый IPC: toggleMaximizeMain, isMaximizedMain (invoke), evtMaximizeChanged (event main → renderer на maximize/unmaximize). - Фикс drag-зоны: titlebar-nodrag перенесён с обёртки правого кластера на сами кнопки. Из-за flex-1 basis-0 пустое место слева от кнопок раньше было no-drag — окно нельзя было ухватить рядом. - minWidth/minHeight окна 900x600 → 1100x700, чтобы Tailwind lg: всегда срабатывал (4 hero-stat в один ряд, heatmap без скролла). - CLAUDE.md: контекст проекта для будущих сессий Claude Code (стек, архитектура, команды, релиз, тех. долг, чего не делать).
This commit is contained in:
@@ -1,4 +1,5 @@
|
||||
import { Minus, X, Square, Menu } from 'lucide-react'
|
||||
import { Minus, X, Square, Copy, Menu } from 'lucide-react'
|
||||
import { useEffect, useState } from 'react'
|
||||
import { useT } from '../i18n'
|
||||
|
||||
type Props = {
|
||||
@@ -10,8 +11,29 @@ export function Titlebar({ title, onMenuClick }: Props): JSX.Element {
|
||||
const { t } = useT()
|
||||
const effectiveTitle = title ?? t('titlebar.app_title')
|
||||
|
||||
// Локально отслеживаем maximize-state, чтобы свапать иконку (квадрат ↔
|
||||
// «двойной квадрат», как в нативной винде). Стартовое значение спрашиваем
|
||||
// у main; дальше подписываемся на evtMaximizeChanged.
|
||||
const [maximized, setMaximized] = useState(false)
|
||||
useEffect(() => {
|
||||
void window.api.isMaximizedMain().then(setMaximized)
|
||||
const unsub = window.api.onMaximizeChanged((v) => setMaximized(v))
|
||||
return unsub
|
||||
}, [])
|
||||
|
||||
// Double-click по тайтлбару — стандартный Windows-жест для toggle maximize.
|
||||
// Игнорируем клики по элементам с no-drag (кнопки/меню) — у них своя логика.
|
||||
function onDoubleClick(e: React.MouseEvent<HTMLDivElement>): void {
|
||||
const target = e.target as HTMLElement
|
||||
if (target.closest('.titlebar-nodrag')) return
|
||||
window.api.toggleMaximizeMain()
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="titlebar-drag relative h-10 px-2 sm:px-3 flex items-center justify-between vibrancy hairline-b">
|
||||
<div
|
||||
onDoubleClick={onDoubleClick}
|
||||
className="titlebar-drag relative h-10 px-2 sm:px-3 flex items-center justify-between vibrancy hairline-b"
|
||||
>
|
||||
<div className="flex items-center gap-1 min-w-0 flex-1 basis-0">
|
||||
{onMenuClick && (
|
||||
<button
|
||||
@@ -28,7 +50,10 @@ export function Titlebar({ title, onMenuClick }: Props): JSX.Element {
|
||||
{effectiveTitle}
|
||||
</div>
|
||||
|
||||
<div className="titlebar-nodrag flex items-center justify-end gap-0.5 min-w-0 flex-1 basis-0">
|
||||
{/* no-drag навешен на сами кнопки, не на обёртку: иначе из-за
|
||||
flex-1 basis-0 весь кластер (включая пустое место слева от кнопок)
|
||||
становится no-drag, и окно нельзя ухватить рядом с кнопками. */}
|
||||
<div className="flex items-center justify-end gap-0.5 min-w-0 flex-1 basis-0">
|
||||
<WinBtn
|
||||
onClick={() => window.api.minimizeMain()}
|
||||
label={t('titlebar.minimize_aria')}
|
||||
@@ -36,10 +61,18 @@ export function Titlebar({ title, onMenuClick }: Props): JSX.Element {
|
||||
<Minus size={13} strokeWidth={2} />
|
||||
</WinBtn>
|
||||
<WinBtn
|
||||
onClick={() => window.api.hideMain()}
|
||||
label={t('titlebar.tray_aria')}
|
||||
onClick={() => window.api.toggleMaximizeMain()}
|
||||
label={
|
||||
maximized
|
||||
? t('titlebar.restore_aria')
|
||||
: t('titlebar.maximize_aria')
|
||||
}
|
||||
>
|
||||
<Square size={11} strokeWidth={2} />
|
||||
{maximized ? (
|
||||
<Copy size={11} strokeWidth={2} />
|
||||
) : (
|
||||
<Square size={11} strokeWidth={2} />
|
||||
)}
|
||||
</WinBtn>
|
||||
<WinBtn
|
||||
onClick={() => window.api.closeMain()}
|
||||
@@ -69,7 +102,7 @@ function WinBtn({
|
||||
onClick={onClick}
|
||||
aria-label={label}
|
||||
className={[
|
||||
'w-9 h-7 grid place-items-center rounded-md transition-colors text-text/55',
|
||||
'titlebar-nodrag w-9 h-7 grid place-items-center rounded-md transition-colors text-text/55',
|
||||
danger
|
||||
? 'hover:bg-destructive hover:text-white'
|
||||
: 'hover:bg-text/[0.08] hover:text-text'
|
||||
|
||||
@@ -21,6 +21,8 @@ export const ru: Dict = {
|
||||
'sidebar.status_tracking': 'Активность отслеживается',
|
||||
'titlebar.menu_aria': 'Меню',
|
||||
'titlebar.minimize_aria': 'Свернуть',
|
||||
'titlebar.maximize_aria': 'Развернуть',
|
||||
'titlebar.restore_aria': 'Восстановить размер',
|
||||
'titlebar.tray_aria': 'В трей',
|
||||
'titlebar.close_aria': 'Закрыть',
|
||||
'titlebar.app_title': 'Exercise Reminder',
|
||||
@@ -265,6 +267,8 @@ export const en: Dict = {
|
||||
'sidebar.status_tracking': 'Activity tracking is on',
|
||||
'titlebar.menu_aria': 'Menu',
|
||||
'titlebar.minimize_aria': 'Minimize',
|
||||
'titlebar.maximize_aria': 'Maximize',
|
||||
'titlebar.restore_aria': 'Restore size',
|
||||
'titlebar.tray_aria': 'To tray',
|
||||
'titlebar.close_aria': 'Close',
|
||||
'titlebar.app_title': 'Exercise Reminder',
|
||||
|
||||
Reference in New Issue
Block a user