İçeriğe geç
BigBang

Sık açılanlar

    ↑↓Gezin↵AçEscKapat
    EN

    Şablonlar

    Bildirim merkezi

    Üst çubuktaki zil düğmesi; okunmamış sayısı, Tümü ve Okunmamış ayrımı, okundu işaretleme ve boş durumla açılır panel.

    Bildirim merkezi, kullanıcıya gelen olayları sayfadan ayrılmadan gösterir. Zil düğmesi okunmamış sayısını taşır; panel bir Popover içindedir ve dışarı tıklayınca ya da Escape ile kapanır. Anlık sonuçlar için Toast, kalıcı geçmiş için bu panel kullanılır (Bildirimler).

    Zile basın. Bir satıra basınca o bildirim okunur ve sayı düşer; “Okunmamış” seçimiyle yalnız yenileri görün; “Tümünü okundu işaretle” ile hepsini kapatın. Sayfa başlığındaki marka ve mod seçimi önizlemeyi de değiştirir.

    Canlı örnek, StorybookStorybook'ta aç
    Canlı örnek, StorybookStorybook'ta aç
    Canlı örnek, StorybookStorybook'ta aç
    Canlı örnek, StorybookStorybook'ta aç
    Terminal window
    pnpm add @bigbang/ui
    Veri Tür Not
    Bildirimler { id, icon, title, meta, unread }[] Yeniden eskiye sıralı gelir; satıra basmak unread’i kapatır.
    state 'some' | 'empty' empty boş durumla başlar.
    defaultOpen boolean Panel açık başlar.

    AppShell şablonu bu bileşeni üst çubukta arama düğmesiyle hesap menüsü arasına koyar.

    import { Badge, Button, EmptyState, Icon, IconButton, Inline, Link, ListItem, Popover, SegmentedControl, Stack, VisuallyHidden } from '@bigbang/ui';
    import type { IconName } from '@bigbang/ui';
    import { useState } from 'react';
    import type { Lang } from '../../patterns/lang';
    const copy = {
    tr: {
    title: 'Bildirimler',
    trigger: (unread: number) => (unread > 0 ? `Bildirimler, ${unread} okunmamış` : 'Bildirimler'),
    show: 'Göster',
    all: 'Tümü',
    unreadOnly: 'Okunmamış',
    unread: 'Okunmamış',
    markAll: 'Tümünü okundu işaretle',
    markedAll: 'Tüm bildirimler okundu olarak işaretlendi.',
    seeAll: 'Tüm bildirimler',
    emptyTitle: 'Yeni bildirim yok',
    emptyText: 'Size atanan görevler ve ekipten gelen yanıtlar burada görünür.',
    unreadEmptyTitle: 'Okunmamış bildirim yok',
    unreadEmptyText: 'Hepsini okudunuz. Eski bildirimler "Tümü" altında.',
    items: [
    { icon: 'user', title: 'Kullanıcı B sizi Proje 1 ekibine ekledi', meta: '5 dk önce' },
    { icon: 'pencil', title: 'Kayıt 12 üzerinde yeni yorum var', meta: '1 saat önce' },
    { icon: 'clock', title: 'Görev 3 için son gün yarın', meta: '3 saat önce' },
    { icon: 'circle-check', title: 'Dışa aktarma tamamlandı', meta: 'Dün' },
    { icon: 'settings', title: 'Çalışma alanı ayarları güncellendi', meta: '2 gün önce' },
    ],
    },
    en: {
    title: 'Notifications',
    trigger: (unread: number) => (unread > 0 ? `Notifications, ${unread} unread` : 'Notifications'),
    show: 'Show',
    all: 'All',
    unreadOnly: 'Unread',
    unread: 'Unread',
    markAll: 'Mark all as read',
    markedAll: 'All notifications marked as read.',
    seeAll: 'All notifications',
    emptyTitle: 'No new notifications',
    emptyText: 'Tasks assigned to you and replies from your team show up here.',
    unreadEmptyTitle: 'No unread notifications',
    unreadEmptyText: 'You have read them all. Older ones are under "All".',
    items: [
    { icon: 'user', title: 'User B added you to the Project 1 team', meta: '5 min ago' },
    { icon: 'pencil', title: 'New comment on Record 12', meta: '1 hour ago' },
    { icon: 'clock', title: 'Task 3 is due tomorrow', meta: '3 hours ago' },
    { icon: 'circle-check', title: 'Export finished', meta: 'Yesterday' },
    { icon: 'settings', title: 'Workspace settings updated', meta: '2 days ago' },
    ],
    },
    };
    type Item = { id: number; icon: IconName; title: string; meta: string; unread: boolean };
    export type NotificationsCentreProps = {
    lang?: Lang;
    /** `empty` starts with no notifications. */
    state?: 'some' | 'empty';
    /** Opens the panel on first render. */
    defaultOpen?: boolean;
    };
    /**
    * The notification bell of the app header: an unread count on the button and a Popover with an
    * All / Unread switch, a list where pressing a row marks it read, "Mark all as read" and an empty state.
    */
    export function NotificationsCentre({ lang = 'tr', state = 'some', defaultOpen = false }: NotificationsCentreProps) {
    const t = copy[lang];
    const [items, setItems] = useState<Item[]>(() =>
    state === 'empty' ? [] : t.items.map((item, index) => ({ ...item, id: index, icon: item.icon as IconName, unread: index < 3 })),
    );
    const [filter, setFilter] = useState('all');
    const [announcement, setAnnouncement] = useState('');
    const unread = items.filter((item) => item.unread).length;
    const shown = filter === 'unread' ? items.filter((item) => item.unread) : items;
    const markRead = (id: number) => setItems((list) => list.map((item) => (item.id === id ? { ...item, unread: false } : item)));
    const markAll = () => {
    setItems((list) => list.map((item) => ({ ...item, unread: false })));
    setAnnouncement(t.markedAll);
    };
    return (
    <span className="relative inline-flex">
    <Popover
    title={t.title}
    side="bottom"
    align="end"
    size="lg"
    defaultOpen={defaultOpen}
    onOpenChange={(open) => {
    if (!open) setAnnouncement('');
    }}
    trigger={<IconButton icon="bell" label={t.trigger(unread)} variant="tertiary" size="sm" tooltip={false} />}
    >
    <Stack gap="md">
    {items.length > 0 ? (
    <SegmentedControl
    label={t.show}
    size="sm"
    fullWidth
    value={filter}
    onValueChange={setFilter}
    items={[
    { value: 'all', label: t.all },
    { value: 'unread', label: unread > 0 ? `${t.unreadOnly} (${unread})` : t.unreadOnly },
    ]}
    />
    ) : null}
    {shown.length > 0 ? (
    <ListItem.List divided animate aria-label={t.title} className="-mx-sm max-h-80 overflow-y-auto">
    {shown.map((item) => (
    <ListItem
    key={item.id}
    size="sm"
    render={<button type="button" />}
    onClick={() => markRead(item.id)}
    leading={<Icon name={item.icon} tone={item.unread ? 'brand' : 'subtle'} />}
    title={
    <>
    {/* The space sits outside the hidden span so the accessible name keeps it. */}
    {item.unread ? (
    <>
    <VisuallyHidden>{`${t.unread}:`}</VisuallyHidden>{' '}
    </>
    ) : null}
    <span className={item.unread ? 'font-semibold' : undefined}>{item.title}</span>
    </>
    }
    subtitle={item.meta}
    trailing={item.unread ? <span aria-hidden="true" className="block size-2 rounded-full bg-status-brand-emphasis" /> : null}
    />
    ))}
    </ListItem.List>
    ) : (
    <EmptyState
    size="sm"
    headingLevel={3}
    icon={items.length === 0 ? 'bell' : 'circle-check'}
    title={items.length === 0 ? t.emptyTitle : t.unreadEmptyTitle}
    description={items.length === 0 ? t.emptyText : t.unreadEmptyText}
    />
    )}
    <Inline justify="between" align="center" gap="sm" wrap>
    {items.length > 0 ? (
    <Button variant="tertiary" size="sm" startIcon={<Icon name="check" />} onClick={markAll} disabled={unread === 0} focusableWhenDisabled>
    {t.markAll}
    </Button>
    ) : (
    <span />
    )}
    <Link href="#" size="sm">
    {t.seeAll}
    </Link>
    </Inline>
    <VisuallyHidden role="status">{announcement}</VisuallyHidden>
    </Stack>
    </Popover>
    {unread > 0 ? (
    // The count is part of the button's name; the badge only shows it.
    <Badge aria-hidden="true" tone="brand" variant="bold" size="sm" className="pointer-events-none absolute -end-xs -top-xs min-w-5 justify-center">
    {unread}
    </Badge>
    ) : null}
    </span>
    );
    }
    1. Zil IconButton ve üzerinde okunmamış sayısını gösteren kalın Badge.
    2. Popover (size="lg", align="end"), başlık “Bildirimler”.
    3. SegmentedControl “Göster”: Tümü / Okunmamış (sayıyla).
    4. Düğme satırlarından ListItem listesi: ikon, başlık, zaman; okunmamışlar kalın, sonda bir nokta.
    5. Liste boşsa h3 başlıklı küçük EmptyState.
    6. Alt satır: “Tümünü okundu işaretle” ve “Tüm bildirimler” Link’i.

    Panel motion.popover ile zilin altından solup ölçeklenerek açılır. Liste satırları motion.listItem ile kısa aralıklarla girer. Azaltılmış harekette ölçek ve kayma kalkar, yalnızca kısa solma kalır.

    • Zilin adı sayıyı içerir: “Bildirimler, 3 okunmamış”. Rozet aria-hidden’dır; sayı iki kez okunmaz ve yalnız renkle verilmez.
    • Panel başlığıyla adlandırılmış bir dialog’dur; açılınca odak ilk denetime gider, Escape kapatıp odağı zile döndürür.
    • Okunmamış satırların adı görünmez “Okunmamış:” ön ekiyle başlar; kalın yazı ve nokta tek başına anlam taşımaz.
    • “Tümünü okundu işaretle” sonucu panel içindeki kibar durum bölgesinde okunur. Okunmamış kalmayınca düğme devre dışı ama odaklanabilir kalır.
    • Boş durum başlığı h3’tür; sayfanın h1’i ve panelin başlığı üstündedir.

    Popover, IconButton, Badge, SegmentedControl, ListItem, EmptyState, Button, Link, VisuallyHidden. Kalıplar: Bildirimler, Boş durumlar.