feat: add window titlebar

This commit is contained in:
reya 2023-12-11 08:56:00 +07:00
parent e9d845cf25
commit 7c4ec71089
29 changed files with 492 additions and 60 deletions

View File

@ -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
View File

@ -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

View File

@ -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" />
)}

View File

@ -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" />
)}

View File

@ -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" />
)}

View File

@ -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" />
)}

View File

@ -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" />
)}

View 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>
);
}

View 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>
),
};

View 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>
);
};

View 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>
);
}

View 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>
);
}

View 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>
);
}

View 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';

View 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>
);
}

View File

@ -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';

View File

@ -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';

View File

@ -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';

View File

@ -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';

View File

@ -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';

View File

@ -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';

View File

@ -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';

View File

@ -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';

View File

@ -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';

View File

@ -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';

View File

@ -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';

View File

@ -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';

View File

@ -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';