Tauri v2 (Rust backend, React/TypeScript frontend). IMAP/POP3/SMTP core, Google OAuth (mail/calendar/contacts sync), CalDAV, local calendar with recurrence, rules engine, contacts, tasks, unified inbox, context menus, drag & drop, snooze, templates, and more.
171 lines
4.6 KiB
TypeScript
171 lines
4.6 KiB
TypeScript
import clsx from 'clsx'
|
|
import { Popover } from './Popover'
|
|
|
|
export function ClassicTabBar({
|
|
tabs,
|
|
active,
|
|
onChange,
|
|
rightSlot,
|
|
}: {
|
|
tabs: { id: string; label: string }[]
|
|
active: string
|
|
onChange: (id: string) => void
|
|
rightSlot?: React.ReactNode
|
|
}) {
|
|
return (
|
|
<div className="flex items-center gap-0.5 bg-[#1e5aa8] px-2 pt-1.5 dark:bg-zinc-900">
|
|
{tabs.map((t) => (
|
|
<button
|
|
key={t.id}
|
|
onClick={() => onChange(t.id)}
|
|
className={clsx(
|
|
'rounded-t-sm px-3 py-1.5 text-[13px] transition',
|
|
active === t.id
|
|
? 'bg-[#f3f2f1] font-semibold text-[#1e5aa8] dark:bg-zinc-800 dark:text-blue-300'
|
|
: 'text-white/90 hover:bg-white/10',
|
|
)}
|
|
>
|
|
{t.label}
|
|
</button>
|
|
))}
|
|
{rightSlot && <div className="ml-auto flex items-center pb-1.5">{rightSlot}</div>}
|
|
</div>
|
|
)
|
|
}
|
|
|
|
export function ClassicRibbonGroup({
|
|
caption,
|
|
children,
|
|
onLauncher,
|
|
}: {
|
|
caption: string
|
|
children: React.ReactNode
|
|
onLauncher?: () => void
|
|
}) {
|
|
return (
|
|
<div className="flex h-[92px] shrink-0 flex-col justify-between border-r border-[#d1d1d1] px-1.5 last:border-r-0 dark:border-zinc-700">
|
|
<div className="flex flex-1 items-start gap-0.5 pt-1">{children}</div>
|
|
<div className="relative flex items-center justify-center pb-0.5">
|
|
<span className="text-[10.5px] text-[#605e5c] dark:text-zinc-400">{caption}</span>
|
|
{onLauncher && (
|
|
<button
|
|
onClick={onLauncher}
|
|
title={`${caption}: weitere Optionen`}
|
|
className="absolute right-0 text-[#605e5c] hover:text-[#1e5aa8] dark:text-zinc-400 dark:hover:text-blue-300"
|
|
>
|
|
<svg width="8" height="8" viewBox="0 0 9 9">
|
|
<path d="M0 9L9 0M9 0H3M9 0V6" stroke="currentColor" strokeWidth="1.2" fill="none" />
|
|
</svg>
|
|
</button>
|
|
)}
|
|
</div>
|
|
</div>
|
|
)
|
|
}
|
|
|
|
export function ClassicButton({
|
|
icon: Icon,
|
|
label,
|
|
onClick,
|
|
disabled,
|
|
active,
|
|
size = 'large',
|
|
title,
|
|
}: {
|
|
icon: React.ComponentType<{ size?: number }>
|
|
label: string
|
|
onClick?: () => void
|
|
disabled?: boolean
|
|
active?: boolean
|
|
size?: 'large' | 'small'
|
|
title?: string
|
|
}) {
|
|
if (size === 'small') {
|
|
return (
|
|
<button
|
|
onClick={onClick}
|
|
disabled={disabled}
|
|
title={title ?? label}
|
|
className={clsx(
|
|
'flex items-center gap-1.5 rounded-sm px-1.5 py-0.5 text-[11.5px] text-[#1f1f1f] transition disabled:cursor-not-allowed disabled:opacity-40 dark:text-zinc-200',
|
|
active ? 'bg-[#cce4f7] dark:bg-blue-500/20' : 'hover:bg-[#e6f2fb] dark:hover:bg-zinc-700/60',
|
|
)}
|
|
>
|
|
<Icon size={14} />
|
|
<span className="whitespace-nowrap">{label}</span>
|
|
</button>
|
|
)
|
|
}
|
|
return (
|
|
<button
|
|
onClick={onClick}
|
|
disabled={disabled}
|
|
title={title ?? label}
|
|
className={clsx(
|
|
'flex w-[64px] shrink-0 flex-col items-center gap-1 rounded-sm px-1 py-1 text-center text-[11px] leading-tight text-[#1f1f1f] transition disabled:cursor-not-allowed disabled:opacity-40 dark:text-zinc-200',
|
|
active ? 'bg-[#cce4f7] dark:bg-blue-500/20' : 'hover:bg-[#e6f2fb] dark:hover:bg-zinc-700/60',
|
|
)}
|
|
>
|
|
<Icon size={24} />
|
|
<span>{label}</span>
|
|
</button>
|
|
)
|
|
}
|
|
|
|
export function ClassicDropdown({
|
|
button,
|
|
children,
|
|
open,
|
|
onOpenChange,
|
|
align = 'left',
|
|
widthClass = 'w-56',
|
|
}: {
|
|
button: React.ReactNode
|
|
children: React.ReactNode
|
|
open: boolean
|
|
onOpenChange: (v: boolean) => void
|
|
align?: 'left' | 'right'
|
|
widthClass?: string
|
|
}) {
|
|
return (
|
|
<Popover
|
|
open={open}
|
|
onOpenChange={onOpenChange}
|
|
trigger={button}
|
|
align={align === 'right' ? 'end' : 'start'}
|
|
panelClassName={clsx(
|
|
'animate-panel-in overflow-hidden rounded-md border border-zinc-300 bg-white py-1 shadow-xl dark:border-zinc-700 dark:bg-zinc-900',
|
|
widthClass,
|
|
)}
|
|
>
|
|
{children}
|
|
</Popover>
|
|
)
|
|
}
|
|
|
|
export function ClassicMenuItem({
|
|
children,
|
|
onClick,
|
|
disabled,
|
|
}: {
|
|
children: React.ReactNode
|
|
onClick?: () => void
|
|
disabled?: boolean
|
|
}) {
|
|
return (
|
|
<button
|
|
onClick={onClick}
|
|
disabled={disabled}
|
|
className="flex w-full items-center gap-2 px-3 py-1.5 text-left text-[12.5px] text-zinc-700 transition hover:bg-[#e6f2fb] disabled:cursor-not-allowed disabled:opacity-40 dark:text-zinc-200 dark:hover:bg-zinc-800"
|
|
>
|
|
{children}
|
|
</button>
|
|
)
|
|
}
|
|
|
|
export function ClassicRibbonBody({ children }: { children: React.ReactNode }) {
|
|
return (
|
|
<div className="flex h-[100px] items-stretch overflow-x-auto bg-[#f3f2f1] px-1.5 dark:bg-zinc-800/60">{children}</div>
|
|
)
|
|
}
|