beUI Pro 移动端已上线:50 个 React Native 区块和页面
Notification Center
Notification inboxes with category tabs, search, expanding details, animated row menus, and host-controlled read, snooze, and archive actions.
Notification dropdown
notification-dropdown.tsxA notification dropdown that unfolds from the trigger corner, with compact rows, category filters, bulk read actions, and accessible row removal.
"use client";
import { Download, RefreshCw, RotateCcw } from "lucide-react";
import { useState } from "react";
import { Button } from "@/components/motion/button";
import {
NotificationDropdown,
type NotificationItem,
} from "@/components/premium/notification-center/notification-dropdown";
const filters = [
{ id: "action", label: "Needs action" },
{ id: "reviews", label: "Reviews" },
{ id: "system", label: "System" },
];
const initialItems: NotificationItem[] = [
{
id: "review",
title: "Review requested",
description: "Maya shared the revised navigation for your feedback.",
time: "4m ago",
category: "reviews",
read: false,
avatarUrl:
"https://images.unsplash.com/photo-1494790108377-be9c29b29330?auto=format&fit=crop&fm=webp&q=80&w=96",
actionLabel: "Review",
},
{
id: "export",
title: "Your export is ready",
description: "The latest project files are ready to download.",
time: "8m ago",
category: "system",
read: false,
icon: <Download className="size-4" />,
},
{
id: "invite",
title: "Invitation needs approval",
description: "Jordan requested access to the design workspace.",
time: "12m ago",
category: "action",
read: false,
avatarUrl:
"https://images.unsplash.com/photo-1534528741775-53994a69daeb?auto=format&fit=crop&fm=webp&q=80&w=96",
actionLabel: "View request",
},
{
id: "comment",
title: "New comment on your draft",
description: "Alex left a note on the account menu.",
time: "26m ago",
category: "reviews",
read: false,
avatarUrl:
"https://images.unsplash.com/photo-1500648767791-00dcc994a43e?auto=format&fit=crop&fm=webp&q=80&w=96",
},
{
id: "sync",
title: "Files are up to date",
description: "All changes have been synced to your workspace.",
time: "1h ago",
category: "system",
read: true,
icon: <RefreshCw className="size-4" />,
},
{
id: "access",
title: "Access request approved",
description: "Sam can now view the project brief.",
time: "Yesterday",
category: "action",
read: true,
avatarUrl:
"https://images.unsplash.com/photo-1506794778202-cad84cf45f1d?auto=format&fit=crop&fm=webp&q=80&w=96",
},
];
export function NotificationDropdownPreview() {
const [items, setItems] = useState(initialItems);
const [message, setMessage] = useState("");
return (
<div className="flex h-144 max-h-[calc(100dvh-2rem)] w-full flex-col items-center justify-start gap-4 p-6">
<div className="flex w-full max-w-md items-center justify-between gap-3">
<Button
tabIndex={0}
variant="ghost"
className="h-9 rounded-full px-3 text-xs focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring"
onClick={() => {
setItems(initialItems);
setMessage("");
}}
>
<RotateCcw aria-hidden className="size-3" /> Reset demo
</Button>
<NotificationDropdown
items={items}
filters={filters}
onReadChange={(item, read) =>
setItems((current) =>
current.map((entry) =>
entry.id === item.id ? { ...entry, read } : entry,
),
)
}
onMarkAllRead={() =>
setItems((current) =>
current.map((item) => ({ ...item, read: true })),
)
}
onSnooze={(item) => {
setItems((current) =>
current.filter((entry) => entry.id !== item.id),
);
setMessage(
`Snoozed: ${item.title}. Demo only; no reminder scheduled.`,
);
}}
onArchive={(item) => {
setItems((current) =>
current.filter((entry) => entry.id !== item.id),
);
setMessage(`Archived: ${item.title}`);
}}
onSelect={(item) => {
setItems((current) =>
current.map((entry) =>
entry.id === item.id ? { ...entry, read: true } : entry,
),
);
setMessage(`Opened: ${item.title}`);
}}
/>
</div>
<output className="max-w-sm text-center text-xs text-muted-foreground">
{message}
</output>
</div>
);
}
安装
Notification panel
notification-panel.tsxA full inbox panel with searchable grouped updates, unread and category filters, morphing inline details, and bulk read actions.
Notifications
4 unread updates
Review requested. Today. Unread.
4m agoMaya shared the revised navigation for your feedback.
Your export is ready. Today. Unread.
8m agoThe latest project files are ready to download.
Invitation needs approval. Today. Unread.
12m agoJordan requested access to the design workspace.
New comment on your draft. Today. Unread.
26m agoAlex left a note on the account menu.
Files are up to date. Today. Read.
1h agoAll changes have been synced to your workspace.
Access request approved. Earlier. Read.
YesterdaySam can now view the project brief.
"use client";
import { Download, RefreshCw, RotateCcw } from "lucide-react";
import { useState } from "react";
import { Button } from "@/components/motion/button";
import { MorphicTooltip } from "@/components/premium/morphic-tooltip";
import {
type NotificationItem,
NotificationPanel,
} from "@/components/premium/notification-center/notification-panel";
const panelFilters = [
{ id: "action", label: "Needs action" },
{ id: "reviews", label: "Reviews" },
{ id: "system", label: "System" },
];
const baseItems: NotificationItem[] = [
{
id: "review",
title: "Review requested",
description: "Maya shared the revised navigation for your feedback.",
time: "4m ago",
category: "reviews",
read: false,
avatarUrl:
"https://images.unsplash.com/photo-1494790108377-be9c29b29330?auto=format&fit=crop&fm=webp&q=80&w=96",
actionLabel: "Review",
},
{
id: "export",
title: "Your export is ready",
description: "The latest project files are ready to download.",
time: "8m ago",
category: "system",
read: false,
icon: <Download className="size-4" />,
},
{
id: "invite",
title: "Invitation needs approval",
description: "Jordan requested access to the design workspace.",
time: "12m ago",
category: "action",
read: false,
avatarUrl:
"https://images.unsplash.com/photo-1534528741775-53994a69daeb?auto=format&fit=crop&fm=webp&q=80&w=96",
actionLabel: "View request",
},
{
id: "comment",
title: "New comment on your draft",
description: "Alex left a note on the account menu.",
time: "26m ago",
category: "reviews",
read: false,
avatarUrl:
"https://images.unsplash.com/photo-1500648767791-00dcc994a43e?auto=format&fit=crop&fm=webp&q=80&w=96",
},
{
id: "sync",
title: "Files are up to date",
description: "All changes have been synced to your workspace.",
time: "1h ago",
category: "system",
read: true,
icon: <RefreshCw className="size-4" />,
},
{
id: "access",
title: "Access request approved",
description: "Sam can now view the project brief.",
time: "Yesterday",
category: "action",
read: true,
avatarUrl:
"https://images.unsplash.com/photo-1506794778202-cad84cf45f1d?auto=format&fit=crop&fm=webp&q=80&w=96",
},
];
const details: Record<string, string> = {
review:
"The navigation draft now includes a simpler account menu and a clearer mobile layout. Maya is looking for feedback on the labels and the order of the main destinations.",
export:
"Your export includes the latest design files and project assets. It’s ready to share with your team or save a copy for your next handoff.",
invite:
"Jordan requested view access to the design workspace. Review the request before granting access to the project files and team discussions.",
comment:
"Alex suggested keeping the account actions together and making the switch-workspace option easier to find. The comment is attached to the latest draft.",
sync: "All project files are synced. Everyone in the workspace can now see the latest changes without refreshing their local copies.",
access:
"Sam’s access request has been approved. They can now read the project brief and follow updates shared with the team.",
};
const initialItems: NotificationItem[] = baseItems.map((item) => ({
...item,
group: item.time === "Yesterday" ? "Earlier" : "Today",
details: details[item.id],
}));
export function NotificationPanelPreview() {
const [items, setItems] = useState(initialItems);
const [message, setMessage] = useState("");
const [query, setQuery] = useState("");
function remove(item: NotificationItem, action: string) {
setItems((current) => current.filter((entry) => entry.id !== item.id));
setMessage(`${action}: ${item.title}`);
}
return (
<div className="flex w-full justify-center p-3 sm:p-6">
<NotificationPanel
items={items}
query={query}
onQueryChange={setQuery}
filters={[{ id: "unread", label: "Unread" }, ...panelFilters]}
headerActions={
<MorphicTooltip content="Reset demo">
<Button
tabIndex={0}
variant="ghost"
size="icon"
className="size-9 rounded-full focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring"
aria-label="Reset demo"
onClick={() => {
setItems(initialItems);
setMessage("");
setQuery("");
}}
>
<RotateCcw aria-hidden className="size-3.5" />
</Button>
</MorphicTooltip>
}
onReadChange={(item, read) =>
setItems((current) =>
current.map((entry) =>
entry.id === item.id ? { ...entry, read } : entry,
),
)
}
onMarkAllRead={() => {
setItems((current) =>
current.map((item) => ({ ...item, read: true })),
);
setMessage("All updates marked as read.");
}}
onSnooze={(item) => remove(item, "Snoozed in this demo")}
onArchive={(item) => remove(item, "Archived")}
onSelect={(item) => {
setItems((current) =>
current.map((entry) =>
entry.id === item.id ? { ...entry, read: true } : entry,
),
);
setMessage(`Opened: ${item.title}`);
}}
footer={
<div className="flex flex-wrap items-center justify-between gap-2">
<output>{message || "Updates across your workspace"}</output>
<span className="shrink-0 tabular-nums">
{items.length} updates
</span>
</div>
}
/>
</div>
);
}