mirror of
https://github.com/lumehq/lume.git
synced 2025-04-08 11:58:22 +02:00
feat: add window titlebar
This commit is contained in:
parent
e9d845cf25
commit
7c4ec71089
@ -81,7 +81,6 @@
|
||||
"react-router-dom": "^6.20.1",
|
||||
"react-string-replace": "^1.1.1",
|
||||
"sonner": "^1.2.4",
|
||||
"tauri-controls": "github:reyamir/tauri-controls",
|
||||
"tippy.js": "^6.3.7",
|
||||
"tiptap-markdown": "^0.8.8",
|
||||
"virtua": "^0.17.5",
|
||||
|
26
pnpm-lock.yaml
generated
26
pnpm-lock.yaml
generated
@ -194,9 +194,6 @@ dependencies:
|
||||
sonner:
|
||||
specifier: ^1.2.4
|
||||
version: 1.2.4(react-dom@18.2.0)(react@18.2.0)
|
||||
tauri-controls:
|
||||
specifier: github:reyamir/tauri-controls
|
||||
version: github.com/reyamir/tauri-controls/079b1262f9b75c84b97613174836101b01d61c37(@tauri-apps/api@2.0.0-alpha.11)(@tauri-apps/plugin-os@2.0.0-alpha.4)(clsx@2.0.0)(react-dom@18.2.0)(react@18.2.0)(tailwind-merge@1.14.0)
|
||||
tippy.js:
|
||||
specifier: ^6.3.7
|
||||
version: 6.3.7
|
||||
@ -3135,6 +3132,7 @@ packages:
|
||||
/clsx@2.0.0:
|
||||
resolution: {integrity: sha512-rQ1+kcj+ttHG0MKVGBUXwayCCF1oh39BF5COIpRzuCEv8Mwjv0XucrI2ExNTOn9IlLifGClWQcU9BrZORvtw6Q==}
|
||||
engines: {node: '>=6'}
|
||||
dev: true
|
||||
|
||||
/color-convert@1.9.3:
|
||||
resolution: {integrity: sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==}
|
||||
@ -5689,6 +5687,7 @@ packages:
|
||||
|
||||
/tailwind-merge@1.14.0:
|
||||
resolution: {integrity: sha512-3mFKyCo/MBcgyOTlrY8T7odzZFx+w+qKSMAmdFzRvqBfLlSigU6TZnlFHK0lkMwj9Bj8OYU+9yW9lmGuS0QEnQ==}
|
||||
dev: true
|
||||
|
||||
/tailwind-scrollbar@3.0.5(tailwindcss@3.3.6):
|
||||
resolution: {integrity: sha512-0ZwxTivevqq9BY9fRP9zDjHl7Tu+J5giBGbln+0O1R/7nHtBUKnjQcA1aTIhK7Oyjp6Uc/Dj6/dn8Dq58k5Uww==}
|
||||
@ -6214,24 +6213,3 @@ packages:
|
||||
react: 18.2.0
|
||||
use-sync-external-store: 1.2.0(react@18.2.0)
|
||||
dev: false
|
||||
|
||||
github.com/reyamir/tauri-controls/079b1262f9b75c84b97613174836101b01d61c37(@tauri-apps/api@2.0.0-alpha.11)(@tauri-apps/plugin-os@2.0.0-alpha.4)(clsx@2.0.0)(react-dom@18.2.0)(react@18.2.0)(tailwind-merge@1.14.0):
|
||||
resolution: {tarball: https://codeload.github.com/reyamir/tauri-controls/tar.gz/079b1262f9b75c84b97613174836101b01d61c37}
|
||||
id: github.com/reyamir/tauri-controls/079b1262f9b75c84b97613174836101b01d61c37
|
||||
name: tauri-controls
|
||||
version: 0.3.0
|
||||
peerDependencies:
|
||||
'@tauri-apps/api': ^2.0.0-alpha.11
|
||||
'@tauri-apps/plugin-os': ^2.0.0-alpha.3
|
||||
clsx: ^2.0.0
|
||||
react: ^18.2.0
|
||||
react-dom: ^18.2.0
|
||||
tailwind-merge: ^1.14.0
|
||||
dependencies:
|
||||
'@tauri-apps/api': 2.0.0-alpha.11
|
||||
'@tauri-apps/plugin-os': 2.0.0-alpha.4
|
||||
clsx: 2.0.0
|
||||
react: 18.2.0
|
||||
react-dom: 18.2.0(react@18.2.0)
|
||||
tailwind-merge: 1.14.0
|
||||
dev: false
|
||||
|
@ -1,10 +1,10 @@
|
||||
import { Outlet, ScrollRestoration } from 'react-router-dom';
|
||||
import { twMerge } from 'tailwind-merge';
|
||||
import { WindowTitlebar } from 'tauri-controls';
|
||||
|
||||
import { useArk } from '@libs/ark';
|
||||
|
||||
import { Navigation } from '@shared/navigation';
|
||||
import { WindowTitleBar } from '@shared/titlebar';
|
||||
|
||||
export function AppLayout() {
|
||||
const { ark } = useArk();
|
||||
@ -17,7 +17,7 @@ export function AppLayout() {
|
||||
)}
|
||||
>
|
||||
{ark.platform !== 'macos' ? (
|
||||
<WindowTitlebar />
|
||||
<WindowTitleBar platform={ark.platform} />
|
||||
) : (
|
||||
<div data-tauri-drag-region className="h-9" />
|
||||
)}
|
||||
|
@ -1,15 +1,16 @@
|
||||
import { Outlet, ScrollRestoration } from 'react-router-dom';
|
||||
import { WindowTitlebar } from 'tauri-controls';
|
||||
|
||||
import { useArk } from '@libs/ark';
|
||||
|
||||
import { WindowTitleBar } from '@shared/titlebar';
|
||||
|
||||
export function AuthLayout() {
|
||||
const { ark } = useArk();
|
||||
|
||||
return (
|
||||
<div className="flex h-screen w-screen flex-col">
|
||||
{ark.platform !== 'macos' ? (
|
||||
<WindowTitlebar />
|
||||
<WindowTitleBar platform={ark.platform} />
|
||||
) : (
|
||||
<div data-tauri-drag-region className="h-9" />
|
||||
)}
|
||||
|
@ -1,10 +1,10 @@
|
||||
import { Link, NavLink, Outlet, useLocation } from 'react-router-dom';
|
||||
import { twMerge } from 'tailwind-merge';
|
||||
import { WindowTitlebar } from 'tauri-controls';
|
||||
|
||||
import { useArk } from '@libs/ark';
|
||||
|
||||
import { ArrowLeftIcon } from '@shared/icons';
|
||||
import { WindowTitleBar } from '@shared/titlebar';
|
||||
|
||||
export function NewLayout() {
|
||||
const { ark } = useArk();
|
||||
@ -13,7 +13,7 @@ export function NewLayout() {
|
||||
return (
|
||||
<div className="flex h-screen w-screen flex-col bg-neutral-50 dark:bg-neutral-950">
|
||||
{ark.platform !== 'macos' ? (
|
||||
<WindowTitlebar />
|
||||
<WindowTitleBar platform={ark.platform} />
|
||||
) : (
|
||||
<div data-tauri-drag-region className="h-9 shrink-0" />
|
||||
)}
|
||||
|
@ -1,15 +1,16 @@
|
||||
import { Outlet, ScrollRestoration } from 'react-router-dom';
|
||||
import { WindowTitlebar } from 'tauri-controls';
|
||||
|
||||
import { useArk } from '@libs/ark';
|
||||
|
||||
import { WindowTitleBar } from '@shared/titlebar';
|
||||
|
||||
export function NoteLayout() {
|
||||
const { ark } = useArk();
|
||||
|
||||
return (
|
||||
<div className="flex h-screen w-screen flex-col bg-neutral-50 dark:bg-neutral-950">
|
||||
{ark.platform !== 'macos' ? (
|
||||
<WindowTitlebar />
|
||||
<WindowTitleBar platform={ark.platform} />
|
||||
) : (
|
||||
<div data-tauri-drag-region className="h-9" />
|
||||
)}
|
||||
|
@ -1,6 +1,5 @@
|
||||
import { NavLink, Outlet, ScrollRestoration, useNavigate } from 'react-router-dom';
|
||||
import { twMerge } from 'tailwind-merge';
|
||||
import { WindowTitlebar } from 'tauri-controls';
|
||||
|
||||
import { useArk } from '@libs/ark';
|
||||
|
||||
@ -12,6 +11,7 @@ import {
|
||||
SettingsIcon,
|
||||
UserIcon,
|
||||
} from '@shared/icons';
|
||||
import { WindowTitleBar } from '@shared/titlebar';
|
||||
|
||||
export function SettingsLayout() {
|
||||
const { ark } = useArk();
|
||||
@ -20,7 +20,7 @@ export function SettingsLayout() {
|
||||
return (
|
||||
<div className="flex h-screen w-screen flex-col bg-neutral-50 dark:bg-neutral-950">
|
||||
{ark.platform !== 'macos' ? (
|
||||
<WindowTitlebar />
|
||||
<WindowTitleBar platform={ark.platform} />
|
||||
) : (
|
||||
<div data-tauri-drag-region className="h-9" />
|
||||
)}
|
||||
|
20
src/shared/titlebar/components/button.tsx
Normal file
20
src/shared/titlebar/components/button.tsx
Normal file
@ -0,0 +1,20 @@
|
||||
import type { ButtonHTMLAttributes } from 'react';
|
||||
import { twMerge } from 'tailwind-merge';
|
||||
|
||||
export function WindowButton({
|
||||
className,
|
||||
children,
|
||||
...props
|
||||
}: ButtonHTMLAttributes<HTMLButtonElement>) {
|
||||
return (
|
||||
<button
|
||||
className={twMerge(
|
||||
'inline-flex cursor-default items-center justify-center',
|
||||
className
|
||||
)}
|
||||
{...props}
|
||||
>
|
||||
{children}
|
||||
</button>
|
||||
);
|
||||
}
|
140
src/shared/titlebar/components/icons.tsx
Normal file
140
src/shared/titlebar/components/icons.tsx
Normal file
@ -0,0 +1,140 @@
|
||||
import type { SVGProps } from 'react';
|
||||
|
||||
export const WindowIcons = {
|
||||
minimizeWin: (props: JSX.IntrinsicAttributes & SVGProps<SVGSVGElement>) => (
|
||||
<svg
|
||||
width="10"
|
||||
height="1"
|
||||
viewBox="0 0 10 1"
|
||||
fill="none"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
{...props}
|
||||
>
|
||||
<path
|
||||
d="M0.498047 1.00098C0.429688 1.00098 0.364583 0.987956 0.302734 0.961914C0.244141 0.935872 0.192057 0.900065 0.146484 0.854492C0.100911 0.808919 0.0651042 0.756836 0.0390625 0.698242C0.0130208 0.636393 0 0.571289 0 0.50293C0 0.43457 0.0130208 0.371094 0.0390625 0.3125C0.0651042 0.250651 0.100911 0.19694 0.146484 0.151367C0.192057 0.102539 0.244141 0.0651042 0.302734 0.0390625C0.364583 0.0130208 0.429688 0 0.498047 0H9.50195C9.57031 0 9.63379 0.0130208 9.69238 0.0390625C9.75423 0.0651042 9.80794 0.102539 9.85352 0.151367C9.89909 0.19694 9.9349 0.250651 9.96094 0.3125C9.98698 0.371094 10 0.43457 10 0.50293C10 0.571289 9.98698 0.636393 9.96094 0.698242C9.9349 0.756836 9.89909 0.808919 9.85352 0.854492C9.80794 0.900065 9.75423 0.935872 9.69238 0.961914C9.63379 0.987956 9.57031 1.00098 9.50195 1.00098H0.498047Z"
|
||||
fill="currentColor"
|
||||
fillOpacity="0.8956"
|
||||
/>
|
||||
</svg>
|
||||
),
|
||||
maximizeWin: (props: JSX.IntrinsicAttributes & SVGProps<SVGSVGElement>) => (
|
||||
<svg
|
||||
width="10"
|
||||
height="10"
|
||||
viewBox="0 0 10 10"
|
||||
fill="none"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
{...props}
|
||||
>
|
||||
<path
|
||||
d="M1.47461 10.001C1.2793 10.001 1.09212 9.96191 0.913086 9.88379C0.734049 9.80241 0.576172 9.69499 0.439453 9.56152C0.30599 9.4248 0.198568 9.26693 0.117188 9.08789C0.0390625 8.90885 0 8.72168 0 8.52637V1.47559C0 1.28027 0.0390625 1.0931 0.117188 0.914062C0.198568 0.735026 0.30599 0.578776 0.439453 0.445312C0.576172 0.308594 0.734049 0.201172 0.913086 0.123047C1.09212 0.0416667 1.2793 0.000976562 1.47461 0.000976562H8.52539C8.7207 0.000976562 8.90788 0.0416667 9.08691 0.123047C9.26595 0.201172 9.4222 0.308594 9.55566 0.445312C9.69238 0.578776 9.7998 0.735026 9.87793 0.914062C9.95931 1.0931 10 1.28027 10 1.47559V8.52637C10 8.72168 9.95931 8.90885 9.87793 9.08789C9.7998 9.26693 9.69238 9.4248 9.55566 9.56152C9.4222 9.69499 9.26595 9.80241 9.08691 9.88379C8.90788 9.96191 8.7207 10.001 8.52539 10.001H1.47461ZM8.50098 9C8.56934 9 8.63281 8.98698 8.69141 8.96094C8.75326 8.9349 8.80697 8.89909 8.85254 8.85352C8.89811 8.80794 8.93392 8.75586 8.95996 8.69727C8.986 8.63542 8.99902 8.57031 8.99902 8.50195V1.5C8.99902 1.43164 8.986 1.36816 8.95996 1.30957C8.93392 1.24772 8.89811 1.19401 8.85254 1.14844C8.80697 1.10286 8.75326 1.06706 8.69141 1.04102C8.63281 1.01497 8.56934 1.00195 8.50098 1.00195H1.49902C1.43066 1.00195 1.36556 1.01497 1.30371 1.04102C1.24512 1.06706 1.19303 1.10286 1.14746 1.14844C1.10189 1.19401 1.06608 1.24772 1.04004 1.30957C1.014 1.36816 1.00098 1.43164 1.00098 1.5V8.50195C1.00098 8.57031 1.014 8.63542 1.04004 8.69727C1.06608 8.75586 1.10189 8.80794 1.14746 8.85352C1.19303 8.89909 1.24512 8.9349 1.30371 8.96094C1.36556 8.98698 1.43066 9 1.49902 9H8.50098Z"
|
||||
fill="currentColor"
|
||||
fillOpacity="0.8956"
|
||||
/>
|
||||
</svg>
|
||||
),
|
||||
maximizeRestoreWin: (props: JSX.IntrinsicAttributes & SVGProps<SVGSVGElement>) => (
|
||||
<svg
|
||||
width="10"
|
||||
height="11"
|
||||
viewBox="0 0 10 11"
|
||||
fill="none"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
{...props}
|
||||
>
|
||||
<path
|
||||
d="M8.99902 2.98096C8.99902 2.71077 8.94531 2.45687 8.83789 2.21924C8.73047 1.97835 8.58398 1.77002 8.39844 1.59424C8.21615 1.4152 8.00293 1.27523 7.75879 1.17432C7.5179 1.07015 7.264 1.01807 6.99707 1.01807H2.08496C2.13704 0.868327 2.21029 0.731608 2.30469 0.60791C2.39909 0.484212 2.50814 0.378418 2.63184 0.290527C2.75553 0.202637 2.89062 0.135905 3.03711 0.090332C3.18685 0.0415039 3.34147 0.0170898 3.50098 0.0170898H6.99707C7.41048 0.0170898 7.79948 0.0968424 8.16406 0.256348C8.52865 0.412598 8.84603 0.625814 9.11621 0.895996C9.38965 1.16618 9.60449 1.48356 9.76074 1.84814C9.92025 2.21273 10 2.60173 10 3.01514V6.51611C10 6.67562 9.97559 6.83024 9.92676 6.97998C9.88118 7.12646 9.81445 7.26156 9.72656 7.38525C9.63867 7.50895 9.53288 7.618 9.40918 7.7124C9.28548 7.8068 9.14876 7.88005 8.99902 7.93213V2.98096ZM1.47461 10.0171C1.2793 10.0171 1.09212 9.97803 0.913086 9.8999C0.734049 9.81852 0.576172 9.7111 0.439453 9.57764C0.30599 9.44092 0.198568 9.28304 0.117188 9.104C0.0390625 8.92497 0 8.73779 0 8.54248V3.49365C0 3.29508 0.0390625 3.10791 0.117188 2.93213C0.198568 2.75309 0.30599 2.59684 0.439453 2.46338C0.576172 2.32666 0.732422 2.21924 0.908203 2.14111C1.08724 2.05973 1.27604 2.01904 1.47461 2.01904H6.52344C6.72201 2.01904 6.91081 2.05973 7.08984 2.14111C7.26888 2.21924 7.42513 2.32503 7.55859 2.4585C7.69206 2.59196 7.79785 2.74821 7.87598 2.92725C7.95736 3.10628 7.99805 3.29508 7.99805 3.49365V8.54248C7.99805 8.74105 7.95736 8.92985 7.87598 9.10889C7.79785 9.28467 7.69043 9.44092 7.55371 9.57764C7.42025 9.7111 7.264 9.81852 7.08496 9.8999C6.90918 9.97803 6.72201 10.0171 6.52344 10.0171H1.47461ZM6.49902 9.01611C6.56738 9.01611 6.63086 9.00309 6.68945 8.97705C6.7513 8.95101 6.80501 8.9152 6.85059 8.86963C6.89941 8.82406 6.93685 8.77197 6.96289 8.71338C6.98893 8.65153 7.00195 8.58643 7.00195 8.51807V3.51807C7.00195 3.44971 6.98893 3.3846 6.96289 3.32275C6.93685 3.2609 6.90104 3.20719 6.85547 3.16162C6.8099 3.11605 6.75618 3.08024 6.69434 3.0542C6.63249 3.02816 6.56738 3.01514 6.49902 3.01514H1.49902C1.43066 3.01514 1.36556 3.02816 1.30371 3.0542C1.24512 3.08024 1.19303 3.11768 1.14746 3.1665C1.10189 3.21208 1.06608 3.26579 1.04004 3.32764C1.014 3.38623 1.00098 3.44971 1.00098 3.51807V8.51807C1.00098 8.58643 1.014 8.65153 1.04004 8.71338C1.06608 8.77197 1.10189 8.82406 1.14746 8.86963C1.19303 8.9152 1.24512 8.95101 1.30371 8.97705C1.36556 9.00309 1.43066 9.01611 1.49902 9.01611H6.49902Z"
|
||||
fill="currentColor"
|
||||
fillOpacity="0.8956"
|
||||
/>
|
||||
</svg>
|
||||
),
|
||||
closeWin: (props: JSX.IntrinsicAttributes & SVGProps<SVGSVGElement>) => (
|
||||
<svg
|
||||
width="10"
|
||||
height="10"
|
||||
viewBox="0 0 10 10"
|
||||
fill="none"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
{...props}
|
||||
>
|
||||
<path
|
||||
d="M5 5.70898L0.854492 9.85449C0.756836 9.95215 0.639648 10.001 0.50293 10.001C0.359701 10.001 0.239258 9.95378 0.141602 9.85938C0.0472005 9.76172 0 9.64128 0 9.49805C0 9.36133 0.0488281 9.24414 0.146484 9.14648L4.29199 5.00098L0.146484 0.855469C0.0488281 0.757812 0 0.638997 0 0.499023C0 0.430664 0.0130208 0.36556 0.0390625 0.303711C0.0651042 0.241862 0.100911 0.189779 0.146484 0.147461C0.192057 0.101888 0.245768 0.0660807 0.307617 0.0400391C0.369466 0.0139974 0.43457 0.000976562 0.50293 0.000976562C0.639648 0.000976562 0.756836 0.0498047 0.854492 0.147461L5 4.29297L9.14551 0.147461C9.24316 0.0498047 9.36198 0.000976562 9.50195 0.000976562C9.57031 0.000976562 9.63379 0.0139974 9.69238 0.0400391C9.75423 0.0660807 9.80794 0.101888 9.85352 0.147461C9.89909 0.193034 9.9349 0.246745 9.96094 0.308594C9.98698 0.367188 10 0.430664 10 0.499023C10 0.638997 9.95117 0.757812 9.85352 0.855469L5.70801 5.00098L9.85352 9.14648C9.95117 9.24414 10 9.36133 10 9.49805C10 9.56641 9.98698 9.63151 9.96094 9.69336C9.9349 9.75521 9.89909 9.80892 9.85352 9.85449C9.8112 9.90007 9.75911 9.93587 9.69727 9.96191C9.63542 9.98796 9.57031 10.001 9.50195 10.001C9.36198 10.001 9.24316 9.95215 9.14551 9.85449L5 5.70898Z"
|
||||
fill="currentColor"
|
||||
fillOpacity="0.8956"
|
||||
/>
|
||||
</svg>
|
||||
),
|
||||
closeMac: (props: JSX.IntrinsicAttributes & SVGProps<SVGSVGElement>) => (
|
||||
<svg
|
||||
width="6"
|
||||
height="6"
|
||||
viewBox="0 0 16 18"
|
||||
fill="none"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
{...props}
|
||||
>
|
||||
<path
|
||||
d="M15.7522 4.44381L11.1543 9.04165L15.7494 13.6368C16.0898 13.9771 16.078 14.5407 15.724 14.8947L13.8907 16.728C13.5358 17.0829 12.9731 17.0938 12.6328 16.7534L8.03766 12.1583L3.44437 16.7507C3.10402 17.091 2.54132 17.0801 2.18645 16.7253L0.273257 14.8121C-0.0807018 14.4572 -0.0925004 13.8945 0.247845 13.5542L4.84024 8.96087L0.32499 4.44653C-0.0153555 4.10619 -0.00355681 3.54258 0.350402 3.18862L2.18373 1.35529C2.53859 1.00042 3.1013 0.989533 3.44164 1.32988L7.95689 5.84422L12.5556 1.24638C12.8951 0.906035 13.4587 0.917833 13.8126 1.27179L15.7267 3.18589C16.0807 3.53985 16.0925 4.10346 15.7522 4.44381Z"
|
||||
fill="currentColor"
|
||||
/>
|
||||
</svg>
|
||||
),
|
||||
minMac: (props: JSX.IntrinsicAttributes & SVGProps<SVGSVGElement>) => (
|
||||
<svg
|
||||
width="8"
|
||||
height="8"
|
||||
viewBox="0 0 17 6"
|
||||
fill="none"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
{...props}
|
||||
>
|
||||
<g clipPath="url(#clip0_20_2051)">
|
||||
<path
|
||||
fillRule="evenodd"
|
||||
clipRule="evenodd"
|
||||
d="M1.47211 1.18042H15.4197C15.8052 1.18042 16.1179 1.50551 16.1179 1.90769V3.73242C16.1179 4.13387 15.8052 4.80006 15.4197 4.80006H1.47211C1.08665 4.80006 0.773926 4.47497 0.773926 4.07278V1.90769C0.773926 1.50551 1.08665 1.18042 1.47211 1.18042Z"
|
||||
fill="currentColor"
|
||||
/>
|
||||
</g>
|
||||
</svg>
|
||||
),
|
||||
fullMac: (props: JSX.IntrinsicAttributes & SVGProps<SVGSVGElement>) => (
|
||||
<svg
|
||||
width="6"
|
||||
height="6"
|
||||
viewBox="0 0 15 15"
|
||||
fill="none"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
{...props}
|
||||
>
|
||||
<g clipPath="url(#clip0_20_2057)">
|
||||
<path
|
||||
fillRule="evenodd"
|
||||
clipRule="evenodd"
|
||||
d="M3.53068 0.433838L15.0933 12.0409C15.0933 12.0409 15.0658 5.35028 15.0658 4.01784C15.0658 1.32095 14.1813 0.433838 11.5378 0.433838C10.6462 0.433838 3.53068 0.433838 3.53068 0.433838ZM12.4409 15.5378L0.87735 3.93073C0.87735 3.93073 0.905794 10.6214 0.905794 11.9538C0.905794 14.6507 1.79024 15.5378 4.43291 15.5378C5.32535 15.5378 12.4409 15.5378 12.4409 15.5378Z"
|
||||
fill="currentColor"
|
||||
/>
|
||||
</g>
|
||||
</svg>
|
||||
),
|
||||
plusMac: (props: JSX.IntrinsicAttributes & SVGProps<SVGSVGElement>) => (
|
||||
<svg
|
||||
width="8"
|
||||
height="8"
|
||||
viewBox="0 0 17 16"
|
||||
fill="none"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
{...props}
|
||||
>
|
||||
<g clipPath="url(#clip0_20_2053)">
|
||||
<path
|
||||
fillRule="evenodd"
|
||||
clipRule="evenodd"
|
||||
d="M15.5308 9.80147H10.3199V15.0095C10.3199 15.3949 9.9941 15.7076 9.59265 15.7076H7.51555C7.11337 15.7076 6.78828 15.3949 6.78828 15.0095V9.80147H1.58319C1.19774 9.80147 0.88501 9.47638 0.88501 9.07419V6.90619C0.88501 6.50401 1.19774 6.17892 1.58319 6.17892H6.78828V1.06183C6.78828 0.676375 7.11337 0.363647 7.51555 0.363647H9.59265C9.9941 0.363647 10.3199 0.676375 10.3199 1.06183V6.17892H15.5308C15.9163 6.17892 16.229 6.50401 16.229 6.90619V9.07419C16.229 9.47638 15.9163 9.80147 15.5308 9.80147Z"
|
||||
fill="currentColor"
|
||||
/>
|
||||
</g>
|
||||
</svg>
|
||||
),
|
||||
};
|
113
src/shared/titlebar/context.tsx
Normal file
113
src/shared/titlebar/context.tsx
Normal file
@ -0,0 +1,113 @@
|
||||
import { type Window, getCurrent } from '@tauri-apps/api/window';
|
||||
import { type } from '@tauri-apps/plugin-os';
|
||||
import React, { createContext, useCallback, useEffect, useState } from 'react';
|
||||
|
||||
interface AppWindowContextType {
|
||||
appWindow: Window | null;
|
||||
isWindowMaximized: boolean;
|
||||
minimizeWindow: () => Promise<void>;
|
||||
maximizeWindow: () => Promise<void>;
|
||||
fullscreenWindow: () => Promise<void>;
|
||||
closeWindow: () => Promise<void>;
|
||||
}
|
||||
|
||||
export const AppWindowContext = createContext<AppWindowContextType>({
|
||||
appWindow: null,
|
||||
isWindowMaximized: false,
|
||||
minimizeWindow: () => Promise.resolve(),
|
||||
maximizeWindow: () => Promise.resolve(),
|
||||
fullscreenWindow: () => Promise.resolve(),
|
||||
closeWindow: () => Promise.resolve(),
|
||||
});
|
||||
|
||||
interface AppWindowProviderProps {
|
||||
children: React.ReactNode;
|
||||
}
|
||||
|
||||
export const AppWindowProvider: React.FC<AppWindowProviderProps> = ({ children }) => {
|
||||
const [appWindow, setAppWindow] = useState<Window | null>(null);
|
||||
const [isWindowMaximized, setIsWindowMaximized] = useState(false);
|
||||
|
||||
useEffect(() => {
|
||||
const window = getCurrent();
|
||||
setAppWindow(window);
|
||||
}, []);
|
||||
|
||||
const updateIsWindowMaximized = useCallback(async () => {
|
||||
if (appWindow) {
|
||||
const _isWindowMaximized = await appWindow.isMaximized();
|
||||
setIsWindowMaximized(_isWindowMaximized);
|
||||
}
|
||||
}, [appWindow]);
|
||||
|
||||
useEffect(() => {
|
||||
let unlisten: () => void = () => {};
|
||||
|
||||
async function getOsType() {
|
||||
const osname = await type();
|
||||
|
||||
if (osname !== 'macos') {
|
||||
updateIsWindowMaximized();
|
||||
|
||||
const listen = async () => {
|
||||
if (appWindow) {
|
||||
unlisten = await appWindow.onResized(() => {
|
||||
updateIsWindowMaximized();
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
listen();
|
||||
}
|
||||
}
|
||||
|
||||
getOsType();
|
||||
|
||||
// Cleanup the listener when the component unmounts
|
||||
return () => unlisten && unlisten();
|
||||
}, [appWindow, updateIsWindowMaximized]);
|
||||
|
||||
const minimizeWindow = async () => {
|
||||
if (appWindow) {
|
||||
await appWindow.minimize();
|
||||
}
|
||||
};
|
||||
|
||||
const maximizeWindow = async () => {
|
||||
if (appWindow) {
|
||||
await appWindow.toggleMaximize();
|
||||
}
|
||||
};
|
||||
|
||||
const fullscreenWindow = async () => {
|
||||
if (appWindow) {
|
||||
const fullscreen = await appWindow.isFullscreen();
|
||||
if (fullscreen) {
|
||||
await appWindow.setFullscreen(false);
|
||||
} else {
|
||||
await appWindow.setFullscreen(true);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const closeWindow = async () => {
|
||||
if (appWindow) {
|
||||
await appWindow.close();
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<AppWindowContext.Provider
|
||||
value={{
|
||||
appWindow,
|
||||
isWindowMaximized,
|
||||
minimizeWindow,
|
||||
maximizeWindow,
|
||||
fullscreenWindow,
|
||||
closeWindow,
|
||||
}}
|
||||
>
|
||||
{children}
|
||||
</AppWindowContext.Provider>
|
||||
);
|
||||
};
|
39
src/shared/titlebar/controls/gnome.tsx
Normal file
39
src/shared/titlebar/controls/gnome.tsx
Normal file
@ -0,0 +1,39 @@
|
||||
import { HTMLProps, useContext } from 'react';
|
||||
import { twMerge } from 'tailwind-merge';
|
||||
|
||||
import { AppWindowContext, WindowButton, WindowIcons } from '@shared/titlebar';
|
||||
|
||||
export function Gnome({ className, ...props }: HTMLProps<HTMLDivElement>) {
|
||||
const { isWindowMaximized, minimizeWindow, maximizeWindow, closeWindow } =
|
||||
useContext(AppWindowContext);
|
||||
|
||||
return (
|
||||
<div
|
||||
className={twMerge('mr-[10px] h-auto items-center space-x-[13px]', className)}
|
||||
{...props}
|
||||
>
|
||||
<WindowButton
|
||||
onClick={minimizeWindow}
|
||||
className="m-0 aspect-square h-6 w-6 cursor-default rounded-full bg-[#dadada] p-0 text-[#3d3d3d] hover:bg-[#d1d1d1] active:bg-[#bfbfbf] dark:bg-[#373737] dark:text-white dark:hover:bg-[#424242] dark:active:bg-[#565656]"
|
||||
>
|
||||
<WindowIcons.minimizeWin className="h-[9px] w-[9px]" />
|
||||
</WindowButton>
|
||||
<WindowButton
|
||||
onClick={maximizeWindow}
|
||||
className="m-0 aspect-square h-6 w-6 cursor-default rounded-full bg-[#dadada] p-0 text-[#3d3d3d] hover:bg-[#d1d1d1] active:bg-[#bfbfbf] dark:bg-[#373737] dark:text-white dark:hover:bg-[#424242] dark:active:bg-[#565656]"
|
||||
>
|
||||
{!isWindowMaximized ? (
|
||||
<WindowIcons.maximizeWin className="h-2 w-2" />
|
||||
) : (
|
||||
<WindowIcons.maximizeRestoreWin className="h-[9px] w-[9px]" />
|
||||
)}
|
||||
</WindowButton>
|
||||
<WindowButton
|
||||
onClick={closeWindow}
|
||||
className="m-0 aspect-square h-6 w-6 cursor-default rounded-full bg-[#dadada] p-0 text-[#3d3d3d] hover:bg-[#d1d1d1] active:bg-[#bfbfbf] dark:bg-[#373737] dark:text-white dark:hover:bg-[#424242] dark:active:bg-[#565656]"
|
||||
>
|
||||
<WindowIcons.closeWin className="h-2 w-2" />
|
||||
</WindowButton>
|
||||
</div>
|
||||
);
|
||||
}
|
74
src/shared/titlebar/controls/macos.tsx
Normal file
74
src/shared/titlebar/controls/macos.tsx
Normal file
@ -0,0 +1,74 @@
|
||||
import { HTMLProps, useContext, useEffect, useState } from 'react';
|
||||
import { twMerge } from 'tailwind-merge';
|
||||
|
||||
import { AppWindowContext, WindowButton, WindowIcons } from '@shared/titlebar';
|
||||
|
||||
export function MacOS({ className, ...props }: HTMLProps<HTMLDivElement>) {
|
||||
const { minimizeWindow, maximizeWindow, fullscreenWindow, closeWindow } =
|
||||
useContext(AppWindowContext);
|
||||
|
||||
const [isAltKeyPressed, setIsAltKeyPressed] = useState(false);
|
||||
const [isHovering, setIsHovering] = useState(false);
|
||||
|
||||
const last = isAltKeyPressed ? <WindowIcons.plusMac /> : <WindowIcons.fullMac />;
|
||||
const key = 'Alt';
|
||||
|
||||
const handleMouseEnter = () => {
|
||||
setIsHovering(true);
|
||||
};
|
||||
|
||||
const handleMouseLeave = () => {
|
||||
setIsHovering(false);
|
||||
};
|
||||
|
||||
const handleAltKeyDown = (e: KeyboardEvent) => {
|
||||
if (e.key === key) {
|
||||
setIsAltKeyPressed(true);
|
||||
}
|
||||
};
|
||||
|
||||
const handleAltKeyUp = (e: KeyboardEvent) => {
|
||||
if (e.key === key) {
|
||||
setIsAltKeyPressed(false);
|
||||
}
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
// Attach event listeners when the component mounts
|
||||
window.addEventListener('keydown', handleAltKeyDown);
|
||||
window.addEventListener('keyup', handleAltKeyUp);
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<div
|
||||
className={twMerge(
|
||||
'space-x-2 px-3 text-black active:text-black dark:text-black',
|
||||
className
|
||||
)}
|
||||
onMouseEnter={handleMouseEnter}
|
||||
onMouseLeave={handleMouseLeave}
|
||||
{...props}
|
||||
>
|
||||
<WindowButton
|
||||
onClick={closeWindow}
|
||||
className="aspect-square h-3 w-3 cursor-default content-center items-center justify-center self-center rounded-full border border-black/[.12] bg-[#ff544d] text-center text-black/60 hover:bg-[#ff544d] active:bg-[#bf403a] active:text-black/60 dark:border-none"
|
||||
>
|
||||
{isHovering && <WindowIcons.closeMac />}
|
||||
</WindowButton>
|
||||
<WindowButton
|
||||
onClick={minimizeWindow}
|
||||
className="aspect-square h-3 w-3 cursor-default content-center items-center justify-center self-center rounded-full border border-black/[.12] bg-[#ffbd2e] text-center text-black/60 hover:bg-[#ffbd2e] active:bg-[#bf9122] active:text-black/60 dark:border-none"
|
||||
>
|
||||
{isHovering && <WindowIcons.minMac />}
|
||||
</WindowButton>
|
||||
<WindowButton
|
||||
// onKeyDown={handleAltKeyDown}
|
||||
// onKeyUp={handleAltKeyUp}
|
||||
onClick={isAltKeyPressed ? maximizeWindow : fullscreenWindow}
|
||||
className="aspect-square h-3 w-3 cursor-default content-center items-center justify-center self-center rounded-full border border-black/[.12] bg-[#28c93f] text-center text-black/60 hover:bg-[#28c93f] active:bg-[#1e9930] active:text-black/60 dark:border-none"
|
||||
>
|
||||
{isHovering && last}
|
||||
</WindowButton>
|
||||
</div>
|
||||
);
|
||||
}
|
40
src/shared/titlebar/controls/windows.tsx
Normal file
40
src/shared/titlebar/controls/windows.tsx
Normal file
@ -0,0 +1,40 @@
|
||||
import { HTMLProps, useContext } from 'react';
|
||||
import { twMerge } from 'tailwind-merge';
|
||||
|
||||
import { AppWindowContext, WindowButton, WindowIcons } from '@shared/titlebar';
|
||||
|
||||
export function Windows({ className, ...props }: HTMLProps<HTMLDivElement>) {
|
||||
const { isWindowMaximized, minimizeWindow, maximizeWindow, closeWindow } =
|
||||
useContext(AppWindowContext);
|
||||
|
||||
return (
|
||||
<div className={twMerge('h-8', className)} {...props}>
|
||||
<WindowButton
|
||||
onClick={minimizeWindow}
|
||||
className="max-h-8 w-[46px] cursor-default rounded-none bg-transparent text-black/90 hover:bg-black/[.05] active:bg-black/[.03] dark:text-white dark:hover:bg-white/[.06] dark:active:bg-white/[.04]"
|
||||
>
|
||||
<WindowIcons.minimizeWin />
|
||||
</WindowButton>
|
||||
<WindowButton
|
||||
onClick={maximizeWindow}
|
||||
className={twMerge(
|
||||
'max-h-8 w-[46px] cursor-default rounded-none bg-transparent',
|
||||
'text-black/90 hover:bg-black/[.05] active:bg-black/[.03] dark:text-white dark:hover:bg-white/[.06] dark:active:bg-white/[.04]'
|
||||
// !isMaximizable && "text-white/[.36]",
|
||||
)}
|
||||
>
|
||||
{!isWindowMaximized ? (
|
||||
<WindowIcons.maximizeWin />
|
||||
) : (
|
||||
<WindowIcons.maximizeRestoreWin />
|
||||
)}
|
||||
</WindowButton>
|
||||
<WindowButton
|
||||
onClick={closeWindow}
|
||||
className="max-h-8 w-[46px] cursor-default rounded-none bg-transparent text-black/90 hover:bg-[#c42b1c] hover:text-white active:bg-[#c42b1c]/90 dark:text-white"
|
||||
>
|
||||
<WindowIcons.closeWin />
|
||||
</WindowButton>
|
||||
</div>
|
||||
);
|
||||
}
|
7
src/shared/titlebar/index.ts
Normal file
7
src/shared/titlebar/index.ts
Normal file
@ -0,0 +1,7 @@
|
||||
export * from './context';
|
||||
export * from './components/button';
|
||||
export * from './components/icons';
|
||||
export * from './controls/gnome';
|
||||
export * from './controls/windows';
|
||||
export * from './controls/macos';
|
||||
export * from './titleBar';
|
29
src/shared/titlebar/titleBar.tsx
Normal file
29
src/shared/titlebar/titleBar.tsx
Normal file
@ -0,0 +1,29 @@
|
||||
import { Platform } from '@tauri-apps/plugin-os';
|
||||
|
||||
import { AppWindowProvider, Gnome, MacOS, Windows } from '@shared/titlebar';
|
||||
|
||||
export function WindowTitleBar({ platform }: { platform: Platform }) {
|
||||
const ControlsComponent = () => {
|
||||
switch (platform) {
|
||||
case 'windows':
|
||||
return <Windows className="ml-auto flex" />;
|
||||
case 'macos':
|
||||
return <MacOS className="ml-0 flex" />;
|
||||
case 'linux':
|
||||
return <Gnome className="ml-auto flex" />;
|
||||
default:
|
||||
return <Windows className="ml-auto flex" />;
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<AppWindowProvider>
|
||||
<div
|
||||
data-tauri-drag-region
|
||||
className="bg-background flex select-none flex-row overflow-hidden"
|
||||
>
|
||||
<ControlsComponent />
|
||||
</div>
|
||||
</AppWindowProvider>
|
||||
);
|
||||
}
|
@ -8,8 +8,7 @@ import { useArk } from '@libs/ark';
|
||||
|
||||
import { ArrowRightCircleIcon, LoaderIcon } from '@shared/icons';
|
||||
import { MemoizedArticleNote } from '@shared/notes';
|
||||
import { TitleBar } from '@shared/titleBar';
|
||||
import { WidgetWrapper } from '@shared/widgets';
|
||||
import { TitleBar, WidgetWrapper } from '@shared/widgets';
|
||||
|
||||
import { FETCH_LIMIT } from '@utils/constants';
|
||||
import { Widget } from '@utils/types';
|
||||
|
@ -7,8 +7,7 @@ import { useArk } from '@libs/ark';
|
||||
|
||||
import { ArrowRightCircleIcon, LoaderIcon } from '@shared/icons';
|
||||
import { MemoizedFileNote } from '@shared/notes';
|
||||
import { TitleBar } from '@shared/titleBar';
|
||||
import { WidgetWrapper } from '@shared/widgets';
|
||||
import { TitleBar, WidgetWrapper } from '@shared/widgets';
|
||||
|
||||
import { FETCH_LIMIT } from '@utils/constants';
|
||||
import { Widget } from '@utils/types';
|
||||
|
@ -12,8 +12,7 @@ import {
|
||||
NoteSkeleton,
|
||||
UnknownNote,
|
||||
} from '@shared/notes';
|
||||
import { TitleBar } from '@shared/titleBar';
|
||||
import { WidgetWrapper } from '@shared/widgets';
|
||||
import { TitleBar, WidgetWrapper } from '@shared/widgets';
|
||||
|
||||
import { FETCH_LIMIT } from '@utils/constants';
|
||||
import { Widget } from '@utils/types';
|
||||
|
@ -7,8 +7,7 @@ import { useArk } from '@libs/ark';
|
||||
|
||||
import { ArrowRightCircleIcon, LoaderIcon } from '@shared/icons';
|
||||
import { MemoizedRepost, MemoizedTextNote, UnknownNote } from '@shared/notes';
|
||||
import { TitleBar } from '@shared/titleBar';
|
||||
import { WidgetWrapper } from '@shared/widgets';
|
||||
import { TitleBar, WidgetWrapper } from '@shared/widgets';
|
||||
|
||||
import { FETCH_LIMIT } from '@utils/constants';
|
||||
import { Widget } from '@utils/types';
|
||||
|
@ -16,3 +16,5 @@ export * from './other/widgetList';
|
||||
export * from './other/addGroupFeeds';
|
||||
export * from './other/addHashtagFeeds';
|
||||
export * from './other/userProfile';
|
||||
export * from './titleBar';
|
||||
export * from './other/nostrBandUserProfile';
|
||||
|
@ -12,8 +12,7 @@ import {
|
||||
NoteSkeleton,
|
||||
UnknownNote,
|
||||
} from '@shared/notes';
|
||||
import { TitleBar } from '@shared/titleBar';
|
||||
import { LiveUpdater, WidgetWrapper } from '@shared/widgets';
|
||||
import { LiveUpdater, TitleBar, WidgetWrapper } from '@shared/widgets';
|
||||
|
||||
import { FETCH_LIMIT } from '@utils/constants';
|
||||
|
||||
|
@ -2,12 +2,12 @@ import { useQuery } from '@tanstack/react-query';
|
||||
import { VList } from 'virtua';
|
||||
|
||||
import { LoaderIcon } from '@shared/icons';
|
||||
import { TitleBar } from '@shared/titleBar';
|
||||
import { WidgetWrapper } from '@shared/widgets';
|
||||
import {
|
||||
NostrBandUserProfile,
|
||||
type Profile,
|
||||
} from '@shared/widgets/other/nostrBandUserProfile';
|
||||
TitleBar,
|
||||
WidgetWrapper,
|
||||
} from '@shared/widgets';
|
||||
|
||||
import { Widget } from '@utils/types';
|
||||
|
||||
|
@ -4,8 +4,7 @@ import { VList } from 'virtua';
|
||||
|
||||
import { LoaderIcon } from '@shared/icons';
|
||||
import { MemoizedTextNote } from '@shared/notes';
|
||||
import { TitleBar } from '@shared/titleBar';
|
||||
import { WidgetWrapper } from '@shared/widgets';
|
||||
import { TitleBar, WidgetWrapper } from '@shared/widgets';
|
||||
|
||||
import { Widget } from '@utils/types';
|
||||
|
||||
|
@ -7,8 +7,7 @@ import { useArk } from '@libs/ark';
|
||||
|
||||
import { ArrowRightCircleIcon, LoaderIcon } from '@shared/icons';
|
||||
import { MemoizedNotifyNote, NoteSkeleton } from '@shared/notes';
|
||||
import { TitleBar } from '@shared/titleBar';
|
||||
import { WidgetWrapper } from '@shared/widgets';
|
||||
import { TitleBar, WidgetWrapper } from '@shared/widgets';
|
||||
|
||||
import { FETCH_LIMIT } from '@utils/constants';
|
||||
import { sendNativeNotification } from '@utils/notification';
|
||||
|
@ -1,6 +1,5 @@
|
||||
import { ArticleIcon, MediaIcon, PlusIcon } from '@shared/icons';
|
||||
import { TitleBar } from '@shared/titleBar';
|
||||
import { AddGroupFeeds, AddHashtagFeeds, WidgetWrapper } from '@shared/widgets';
|
||||
import { AddGroupFeeds, AddHashtagFeeds, TitleBar, WidgetWrapper } from '@shared/widgets';
|
||||
|
||||
import { TOPICS, WIDGET_KIND } from '@utils/constants';
|
||||
import { useWidget } from '@utils/hooks/useWidget';
|
||||
|
@ -14,9 +14,8 @@ import {
|
||||
NoteReplyForm,
|
||||
} from '@shared/notes';
|
||||
import { ReplyList } from '@shared/notes/replies/list';
|
||||
import { TitleBar } from '@shared/titleBar';
|
||||
import { User } from '@shared/user';
|
||||
import { WidgetWrapper } from '@shared/widgets';
|
||||
import { TitleBar, WidgetWrapper } from '@shared/widgets';
|
||||
|
||||
import { useEvent } from '@utils/hooks/useEvent';
|
||||
import { Widget } from '@utils/types';
|
||||
|
@ -12,8 +12,7 @@ import {
|
||||
NoteSkeleton,
|
||||
UnknownNote,
|
||||
} from '@shared/notes';
|
||||
import { TitleBar } from '@shared/titleBar';
|
||||
import { WidgetWrapper } from '@shared/widgets';
|
||||
import { TitleBar, WidgetWrapper } from '@shared/widgets';
|
||||
|
||||
import { FETCH_LIMIT } from '@utils/constants';
|
||||
import { Widget } from '@utils/types';
|
||||
|
@ -12,8 +12,7 @@ import {
|
||||
NoteSkeleton,
|
||||
UnknownNote,
|
||||
} from '@shared/notes';
|
||||
import { TitleBar } from '@shared/titleBar';
|
||||
import { UserProfile, WidgetWrapper } from '@shared/widgets';
|
||||
import { TitleBar, UserProfile, WidgetWrapper } from '@shared/widgets';
|
||||
|
||||
import { FETCH_LIMIT } from '@utils/constants';
|
||||
import { Widget } from '@utils/types';
|
||||
|
Loading…
x
Reference in New Issue
Block a user