Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
26 changes: 16 additions & 10 deletions apps/web/components/dashboard/unified-mailbox-nav.tsx
Original file line number Diff line number Diff line change
@@ -1,21 +1,21 @@
"use client";

import Link from "next/link";
import { useParams, usePathname } from "next/navigation";
import { cn } from "@/lib/utils";
import type { IdentityEntity, MailboxEntity } from "@db";
import type { MailboxKind } from "@schema";
import {
Inbox,
Send,
FileText,
Archive,
Ban,
Trash2,
FileText,
Folder,
Inbox,
Plus,
Send,
Trash2,
} from "lucide-react";
import { IdentityEntity, MailboxEntity } from "@db";
import { FetchIdentityMailboxListResult } from "@/lib/actions/mailbox";
import { MailboxKind } from "@schema";
import Link from "next/link";
import { useParams, usePathname } from "next/navigation";
import type { FetchIdentityMailboxListResult } from "@/lib/actions/mailbox";
import { cn } from "@/lib/utils";

type Mailbox = {
slug: string | null;
Expand Down Expand Up @@ -118,6 +118,12 @@ export function UnifiedMailboxNav({
isActive && "bg-sidebar-accent text-sidebar-accent-foreground",
)}
>
{m.mailbox.color ? (
<div
className="h-2.5 w-2.5 shrink-0 rounded-full"
style={{ backgroundColor: m.mailbox.color }}
/>
) : null}
<Icon className="h-4 w-4 shrink-0" />
<span className="min-w-0 truncate">
{m.mailbox.kind === "custom"
Expand Down
78 changes: 74 additions & 4 deletions apps/web/components/mailbox/default/add-new-folder.tsx
Original file line number Diff line number Diff line change
@@ -1,13 +1,75 @@
import { ActionIcon, ColorSwatch, Modal, Tooltip } from "@mantine/core";
import { useDisclosure } from "@mantine/hooks";
import { Modal, ActionIcon } from "@mantine/core";
import { Plus } from "lucide-react";
import { Plus, X } from "lucide-react";
import * as React from "react";
import { ReusableForm } from "@/components/common/reusable-form";
import { useMailboxOptions } from "@/hooks/use-mailbox-options";
import {
addNewMailboxFolder,
FetchIdentityMailboxListResult,
type FetchIdentityMailboxListResult,
} from "@/lib/actions/mailbox";
import { useMailboxOptions } from "@/hooks/use-mailbox-options";

const FOLDER_COLORS = [
"#e03131",
"#e8590c",
"#f08c00",
"#2f9e44",
"#1971c2",
"#6741d9",
"#c2255c",
"#868e96",
];

function ColorPickerField({ name }: { name: string }) {
const [selected, setSelected] = React.useState<string | null>(null);

return (
<div>
<input type="hidden" name={name} value={selected ?? ""} />
<div className="flex items-center gap-2 flex-wrap">
{FOLDER_COLORS.map((color) => (
<button
key={color}
type="button"
onClick={() => setSelected(color === selected ? null : color)}
className="p-0 m-0 bg-transparent border-0 rounded-full"
style={{ lineHeight: 0 }}
>
<Tooltip label={color} withArrow>
<ColorSwatch
color={color}
size={20}
withShadow
className={`cursor-pointer transition-all ${
selected === color
? "ring-2 ring-offset-1 ring-[color:var(--color-brand-500)]"
: ""
}`}
style={{
transform: selected === color ? "scale(1.1)" : "scale(1)",
}}
/>
</Tooltip>
</button>
))}
{selected && (
<button
type="button"
onClick={() => setSelected(null)}
className="p-0 m-0 bg-transparent border-0 rounded-full"
style={{ lineHeight: 0 }}
>
<Tooltip label="No color" withArrow>
<ActionIcon size={20} variant="default" radius="xl">
<X className="h-3 w-3" />
</ActionIcon>
</Tooltip>
</button>
)}
</div>
</div>
);
}

export default function AddNewFolder({
mailboxes,
Expand Down Expand Up @@ -53,6 +115,14 @@ export default function AddNewFolder({
},
},
},
{
name: "color",
label: "Color (Optional)",
kind: "custom" as const,
component: ColorPickerField,
wrapperClasses: "col-span-12",
props: {},
},
];

return (
Expand Down
112 changes: 112 additions & 0 deletions apps/web/components/mailbox/default/mailbox-color-picker.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,112 @@
"use client";

import { ActionIcon, ColorSwatch, Popover, Tooltip } from "@mantine/core";
import { Paintbrush, X } from "lucide-react";
import { useState, useTransition } from "react";
import { updateMailboxColor } from "@/lib/actions/mailbox";

const MAILBOX_COLORS = [
"#e03131",
"#e8590c",
"#f08c00",
"#2f9e44",
"#1971c2",
"#6741d9",
"#c2255c",
"#868e96",
];

export function MailboxColorPicker({
mailboxId,
currentColor,
}: {
mailboxId: string;
currentColor: string | null;
}) {
const [opened, setOpened] = useState(false);
const [pending, startTransition] = useTransition();

const handleSelect = (color: string | null) => {
startTransition(async () => {
await updateMailboxColor(mailboxId, color);
setOpened(false);
});
};

return (
<Popover
opened={opened}
onChange={setOpened}
position="bottom"
withArrow
shadow="md"
>
<Popover.Target>
<ActionIcon
size={16}
variant="subtle"
onClick={(e: React.MouseEvent) => {
e.preventDefault();
e.stopPropagation();
setOpened((o) => !o);
}}
aria-label="Set mailbox color"
className="opacity-0 group-hover:opacity-100 transition-opacity"
>
{currentColor ? (
<ColorSwatch color={currentColor} size={12} />
) : (
<Paintbrush className="h-3 w-3" />
)}
</ActionIcon>
</Popover.Target>

<Popover.Dropdown>
<div className="flex flex-wrap gap-2 p-1 max-w-[160px]">
{MAILBOX_COLORS.map((color) => (
<button
key={color}
type="button"
onClick={() => handleSelect(color)}
disabled={pending}
className="p-0 m-0 bg-transparent border-0 rounded-full"
style={{ lineHeight: 0 }}
>
<Tooltip label={color} withArrow>
<ColorSwatch
color={color}
size={20}
withShadow
className={`cursor-pointer transition-all ${
currentColor === color
? "ring-2 ring-offset-1 ring-[color:var(--color-brand-500)]"
: ""
}`}
style={{
transform:
currentColor === color ? "scale(1.1)" : "scale(1)",
}}
/>
</Tooltip>
</button>
))}
{currentColor && (
<button
type="button"
onClick={() => handleSelect(null)}
disabled={pending}
className="p-0 m-0 bg-transparent border-0 rounded-full"
style={{ lineHeight: 0 }}
>
<Tooltip label="Remove color" withArrow>
<ActionIcon size={20} variant="default" radius="xl">
<X className="h-3 w-3" />
</ActionIcon>
</Tooltip>
</button>
)}
</div>
</Popover.Dropdown>
</Popover>
);
}
62 changes: 24 additions & 38 deletions apps/web/components/mailbox/default/mailbox-nav.tsx
Original file line number Diff line number Diff line change
@@ -1,35 +1,20 @@
"use client";

import Link from "next/link";
import { useParams, usePathname } from "next/navigation";
import { Badge } from "@/components/ui/badge";
import { cn } from "@/lib/utils";
import type { MailboxEntity } from "@db";
import type { MailboxKind } from "@schema";
import {
Inbox,
Send,
FileText,
Archive,
Ban,
Trash2,
FileText,
Folder,
Plus,
Inbox,
Send,
Trash2,
} from "lucide-react";
import { MailboxEntity } from "@db";

type Mailbox = {
slug: string | null;
kind:
| "inbox"
| "sent"
| "drafts"
| "archive"
| "spam"
| "trash"
| "outbox"
| "custom";
name?: string | null;
unreadCount?: number | null;
};
import Link from "next/link";
import { useParams, usePathname } from "next/navigation";
import { cn } from "@/lib/utils";
import { MailboxColorPicker } from "./mailbox-color-picker";

export function MailboxNav({
mailboxes,
Expand All @@ -43,17 +28,17 @@ export function MailboxNav({
const pathname = usePathname();
const params = useParams() as { mailboxSlug?: string };

const systemOrder: Mailbox["kind"][] = [
const systemOrder: MailboxKind[] = [
"inbox",
"starred" as any, // if you add later
"drafts",
"sent",
"archive",
"spam",
"trash",
].filter(Boolean) as Mailbox["kind"][];
].filter(Boolean) as MailboxKind[];

const iconFor: Record<Mailbox["kind"], React.ElementType> = {
const iconFor: Record<MailboxKind, React.ElementType> = {
inbox: Inbox,
sent: Send,
drafts: FileText,
Expand All @@ -71,7 +56,7 @@ export function MailboxNav({

const custom = mailboxes.filter((m) => m.kind === "custom");

const Item = ({ m }: { m: Mailbox }) => {
const Item = ({ m }: { m: MailboxEntity }) => {
const Icon = iconFor[m.kind] ?? Folder;
const slug = m.slug ?? "inbox";
const href = `/mail/${identityPublicId}/${slug}`;
Expand All @@ -88,18 +73,19 @@ export function MailboxNav({
isActive && "bg-sidebar-accent text-sidebar-accent-foreground",
)}
>
{m.color ? (
<div
className="h-2.5 w-2.5 shrink-0 rounded-full"
style={{ backgroundColor: m.color }}
/>
) : null}
<Icon className="h-4 w-4 shrink-0" />
<span className="min-w-0 truncate">
{m.kind === "custom" ? (m.name ?? "Label") : titleFor(m.kind)}
</span>
{m.unreadCount ? (
<Badge
variant={isActive ? "secondary" : "outline"}
className="ml-auto"
>
{m.unreadCount}
</Badge>
) : null}
<span className="ml-auto">
<MailboxColorPicker mailboxId={m.id} currentColor={m.color} />
</span>
</Link>
);
};
Expand All @@ -115,7 +101,7 @@ export function MailboxNav({
);
}

function titleFor(kind: Mailbox["kind"]) {
function titleFor(kind: MailboxKind) {
switch (kind) {
case "inbox":
return "Inbox";
Expand Down
Loading