feat(webmail): the new interface - logo, dark and light themes, command menu
A mark (a lambda on an ultramarine tile with the flap of an envelope), the favicon, home-screen icons and an install manifest. Every colour is a variable defined once per theme, and the Tailwind scales the screens already use read them, so dark (the default) and light are a change of values and not of classes. Geist is served by the app itself. Ctrl+K opens a command menu for folders, actions, accounts and appearance, and typing something that matches no command searches the mail; c writes a message and / goes to the search field. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
6b97807856
commit
e667843b59
38 files changed
+1161
-316
No files matched your search
@@ -75,11 +75,11 @@ interface Props {
|
||||
}
|
||||
|
||||
const commandClass =
|
||||
"inline-flex h-10 min-w-10 items-center justify-center gap-1.5 rounded-lg px-2.5 text-[13px] text-slate-200 transition-colors hover:bg-white/[0.07] focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-indigo-400 md:px-3";
|
||||
"inline-flex h-10 min-w-10 items-center justify-center gap-1.5 rounded-lg px-2.5 text-[13px] text-slate-200 transition-colors hover:bg-ink/[0.07] focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-indigo-400 md:px-3";
|
||||
const infoLinkClass =
|
||||
"rounded-md px-1.5 py-1 text-[12.5px] font-medium text-indigo-300 transition-colors hover:bg-white/[0.07] focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-indigo-400";
|
||||
"rounded-md px-1.5 py-1 text-[12.5px] font-medium text-indigo-300 transition-colors hover:bg-ink/[0.07] focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-indigo-400";
|
||||
const replyButtonClass =
|
||||
"inline-flex h-10 items-center gap-2 rounded-xl px-4 text-[13.5px] text-slate-200 shadow-[inset_0_0_0_1px_rgba(233,233,237,0.18)] transition-colors hover:bg-white/[0.07] focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-indigo-400";
|
||||
"inline-flex h-10 items-center gap-2 rounded-xl px-4 text-[13.5px] text-slate-200 shadow-[inset_0_0_0_1px_rgb(var(--ink)/0.18)] transition-colors hover:bg-ink/[0.07] focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-indigo-400";
|
||||
|
||||
/** The name and address in a header like `Jenny <jenny@x.test>`. */
|
||||
function splitSender(from: string): { name: string; address: string } {
|
||||
@@ -306,7 +306,7 @@ export function ThreadReader(props: Props) {
|
||||
aria-label={thread.subject}
|
||||
>
|
||||
{/* The commands that apply to the whole conversation. */}
|
||||
<div className="flex flex-wrap items-center gap-0.5 px-2 py-1.5 shadow-[inset_0_-1px_0_0_rgba(233,233,237,0.09)] sm:px-4">
|
||||
<div className="flex flex-wrap items-center gap-0.5 px-2 py-1.5 shadow-[inset_0_-1px_0_0_rgb(var(--ink)/0.09)] sm:px-4">
|
||||
<button type="button" onClick={props.onClose} className={`${commandClass} md:hidden`} aria-label={t("mail.backToList")}>
|
||||
<ArrowLeft className="h-4 w-4" />
|
||||
</button>
|
||||
@@ -320,7 +320,7 @@ export function ThreadReader(props: Props) {
|
||||
<span className="hidden sm:inline">{t("mail.replyAll")}</span>
|
||||
</button>
|
||||
)}
|
||||
<span className="mx-1 hidden h-5 w-px bg-white/10 sm:block" />
|
||||
<span className="mx-1 hidden h-5 w-px bg-ink/10 sm:block" />
|
||||
<button type="button" onClick={() => void deleteThread()} disabled={busy || inThisFolder.length === 0} className={commandClass}>
|
||||
<Trash2 className="h-4 w-4" />
|
||||
<span className="hidden lg:inline">{props.deleteIsPermanent ? t("mail.deleteForever") : t("common.delete")}</span>
|
||||
@@ -339,14 +339,14 @@ export function ThreadReader(props: Props) {
|
||||
<ChevronDown className="h-3 w-3" />
|
||||
</button>
|
||||
{moveOpen && (
|
||||
<div role="menu" className="absolute left-0 top-full z-30 mt-1 min-w-[180px] rounded-xl bg-dark-panel p-1.5 shadow-edge ring-1 ring-white/10">
|
||||
<div role="menu" className="absolute left-0 top-full z-30 mt-1 min-w-[180px] rounded-xl bg-dark-panel p-1.5 shadow-edge ring-1 ring-ink/10">
|
||||
{props.moveTargets.map((target) => (
|
||||
<button
|
||||
key={target.name}
|
||||
role="menuitem"
|
||||
type="button"
|
||||
onClick={() => void moveThread(target.name)}
|
||||
className="flex w-full items-center rounded-lg px-2.5 py-2 text-left text-[13px] text-slate-200 hover:bg-white/[0.07]"
|
||||
className="flex w-full items-center rounded-lg px-2.5 py-2 text-left text-[13px] text-slate-200 hover:bg-ink/[0.07]"
|
||||
>
|
||||
{target.label}
|
||||
</button>
|
||||
@@ -422,7 +422,7 @@ export function ThreadReader(props: Props) {
|
||||
address={info.sender_address}
|
||||
initials={initialsOf(displayName, info.sender_address)}
|
||||
size={isOpen ? 40 : 30}
|
||||
className="bg-indigo-900 text-indigo-300 shadow-[inset_0_0_0_1px_#5d5294]"
|
||||
className="bg-indigo-900 text-indigo-300 shadow-[inset_0_0_0_1px_rgb(var(--a-700))]"
|
||||
/>
|
||||
<span className="flex min-w-0 flex-1 flex-col">
|
||||
<span className="flex items-baseline gap-2">
|
||||
@@ -430,7 +430,7 @@ export function ThreadReader(props: Props) {
|
||||
{displayName}
|
||||
</span>
|
||||
{isOpen && !info.own && sender.address && (
|
||||
<span className="min-w-0 truncate text-[12.5px] text-slate-400"><{sender.address}></span>
|
||||
<span className="hidden min-w-0 truncate text-[12.5px] text-slate-400 sm:inline"><{sender.address}></span>
|
||||
)}
|
||||
</span>
|
||||
{!isOpen && (
|
||||
@@ -477,7 +477,7 @@ export function ThreadReader(props: Props) {
|
||||
className={`inline-flex items-center gap-1 rounded-full px-2 py-0.5 text-[11.5px] font-medium transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-indigo-400 ${
|
||||
trustedHere
|
||||
? "bg-indigo-500/20 text-indigo-200 hover:bg-indigo-500/30"
|
||||
: "text-slate-400 shadow-[inset_0_0_0_1px_rgba(233,233,237,0.18)] hover:bg-white/[0.07] hover:text-slate-200"
|
||||
: "text-slate-400 shadow-[inset_0_0_0_1px_rgb(var(--ink)/0.18)] hover:bg-ink/[0.07] hover:text-slate-200"
|
||||
}`}
|
||||
>
|
||||
<ShieldCheck className="h-3 w-3" aria-hidden />
|
||||
@@ -486,7 +486,7 @@ export function ThreadReader(props: Props) {
|
||||
)}
|
||||
<Link
|
||||
href={`/user/compose?mode=forward&subject=${encodeURIComponent(loaded.subject)}`}
|
||||
className="ml-auto inline-flex items-center gap-1 rounded-md px-1.5 py-1 text-indigo-300 hover:bg-white/[0.07]"
|
||||
className="ml-auto inline-flex items-center gap-1 rounded-md px-1.5 py-1 text-indigo-300 hover:bg-ink/[0.07]"
|
||||
>
|
||||
<Forward className="h-3.5 w-3.5" aria-hidden />
|
||||
{t("mail.forward")}
|
||||
|
||||
Reference in new issue
Block a user