feat(ui): one CountBadge for all counters; counters on tab corners
MindSift CI & Docker Release / Build, Test in Container & Push (push) Successful in 2m30s

- CountBadge component (accent / muted, inline / corner, 99+, showZero);
  used in the nav bar, task list and list tabs, admin organizations and
  numeric badges of the mobile menu; old badge-count removed
- Counters sit on the top right corner of tabs; "Alle Aufgaben" gets one
  (desktop tab and mobile menu)
- Open/done task counters include tasks in shared lists, matching what
  "Alle Aufgaben" shows (nav counter was too low)
- Theme template derives --accent-dim/--accent-border from the accent
- Docs: ui-patterns counters section

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
sandrodachandClaude Opus 5.5 committed 2026-10-28 22:54:15 +01:00
1 parent a354f3d008
commit a62b204e14
12 files changed
+167 -55

No files matched your search

@@ -14,6 +14,7 @@ import { ListItem, SharedList, ListShare, Team, User, WSMessage } from '../../li
import { api, buildWebSocketURL, triggerHaptic, createRealtimeConnection, RealtimeTransport } from '../../lib/api';
import { UnifiedShareModal } from '../../components/UnifiedShareModal';
import { AvatarStack, AvatarPerson } from '../../components/AvatarStack';
import { CountBadge } from '../../components/CountBadge';
import { useMobileActions } from '../../context/MobileActionContext';
interface SharedListsViewProps {
@@ -842,8 +843,8 @@ export const SharedListsView: React.FC<SharedListsViewProps> = ({ onRefreshGloba
<div className="w-full space-y-3">
{/* Top Header: List Tabs & Actions (Desktop) */}
<div className="hidden sm:flex flex-wrap items-center justify-between gap-2 border-b border-border pb-2 !mt-0">
{/* List Tabs */}
<div className="flex flex-wrap items-center gap-1">
{/* List Tabs (counters sit on the top right corner, hence the extra gap) */}
<div className="flex flex-wrap items-center gap-x-3 gap-y-3 pt-2">
{lists.map((lst) => {
const openCount = deduplicateItems(lst.items || []).filter((i) => !i.isCompleted).length;
const isSelected = activeList?.id === lst.id;
@@ -860,9 +861,6 @@ export const SharedListsView: React.FC<SharedListsViewProps> = ({ onRefreshGloba
>
<span>{lst.icon}</span>
<span>{lst.title}</span>
{openCount > 0 && (
<span className="ml-1 badge-count">{openCount}</span>
)}
<AvatarStack
people={otherMembers(lst)}
size="xxs"
@@ -870,6 +868,7 @@ export const SharedListsView: React.FC<SharedListsViewProps> = ({ onRefreshGloba
className="ml-0.5"
/>
</button>
<CountBadge count={openCount} position="corner" />
{isSelected && lists.length > 1 && (
<button
onClick={() => handleDeleteList(lst.id)}
@@ -27,6 +27,7 @@ import {
import { api, setCurrentSharingMode, triggerHaptic } from '../../lib/api';
import { UserAvatar } from '../../components/UserAvatar';
import { ToggleSwitch } from '../../components/ToggleSwitch';
import { CountBadge } from '../../components/CountBadge';
interface AdminSettingsViewProps {
currentUser: User;
@@ -929,7 +930,7 @@ export const AdminSettingsView: React.FC<AdminSettingsViewProps> = ({
<div className="flex items-center gap-2 min-w-0">
<Building2 className="w-4 h-4 text-accent shrink-0" />
<span className="font-bold text-main truncate">{o.name}</span>
<span className="badge-count">{o.memberCount}</span>
<CountBadge count={o.memberCount} showZero />
</div>
)}
{renamingOrgId !== o.id && (
+11 -6
View File
@@ -32,6 +32,7 @@ import { UnifiedShareModal } from '../../components/UnifiedShareModal';
import { TaskDetailModal } from './TaskDetailModal';
import { ManageColumnsModal, DEFAULT_KANBAN_COLUMNS } from './ManageColumnsModal';
import { AvatarStack, AvatarPerson } from '../../components/AvatarStack';
import { CountBadge } from '../../components/CountBadge';
import { useMobileActions } from '../../context/MobileActionContext';
interface TasksViewProps {
@@ -40,6 +41,7 @@ interface TasksViewProps {
onOpenDocument: (docId: string, blockId?: string) => void;
refreshTrigger?: number;
initialSelectedTaskId?: string | null;
openTasksCount?: number; // all open tasks incl. shared lists (same as the nav bar counter)
}
export const TasksView: React.FC<TasksViewProps> = ({
@@ -48,6 +50,7 @@ export const TasksView: React.FC<TasksViewProps> = ({
onOpenDocument,
refreshTrigger = 0,
initialSelectedTaskId,
openTasksCount,
}) => {
const [viewMode, setViewMode] = useState<'list' | 'kanban'>('list');
const [filter, setFilter] = useState<'open' | 'done' | 'discarded'>('open');
@@ -1036,6 +1039,7 @@ export const TasksView: React.FC<TasksViewProps> = ({
id: 'list-all',
label: 'Alle Aufgaben',
icon: <Layers className="w-4 h-4" />,
badge: openTasksCount || undefined,
active: selectedListId === 'all',
onClick: () => setSelectedListId('all'),
},
@@ -1086,6 +1090,7 @@ export const TasksView: React.FC<TasksViewProps> = ({
safeDiscarded.length,
kanbanTasks.length,
safeTaskLists,
openTasksCount,
currentSelectedList,
isSelectedListReadOnly,
setPrimaryAction,
@@ -1368,17 +1373,18 @@ export const TasksView: React.FC<TasksViewProps> = ({
<div className="w-full space-y-3">
{/* Top Header: Task Lists Tabs & View Mode Switcher (Desktop) */}
<div className="hidden sm:flex flex-wrap items-center justify-between gap-2 border-b border-border pb-2 !mt-0">
{/* Task Lists selector tabs */}
<div className="flex flex-wrap items-center gap-1">
{/* Task Lists selector tabs (counters sit on the top right corner, hence the extra gap) */}
<div className="flex flex-wrap items-center gap-x-3 gap-y-3 pt-2">
<button
onClick={() => setSelectedListId('all')}
className={`h-8 px-3 text-xs font-medium border rounded-btn transition-colors ${
className={`relative h-8 px-3 text-xs font-medium border rounded-btn transition-colors ${
selectedListId === 'all'
? 'bg-panel border-accent text-main'
: 'bg-canvas border-border text-muted hover:text-main'
}`}
>
Alle Aufgaben
<CountBadge count={openTasksCount} position="corner" />
</button>
{safeTaskLists.map((tl) => (
@@ -1394,9 +1400,7 @@ export const TasksView: React.FC<TasksViewProps> = ({
>
<span>{tl.icon}</span>
<span>{tl.title}</span>
{tl.tasksCount !== undefined && tl.tasksCount > 0 && (
<span className="ml-1 badge-count">{tl.tasksCount}</span>
)}
{(() => {
const isShared = (tl.shares && tl.shares.length > 0) || (tl.members && tl.members.length > 1);
if (!isShared) return null;
@@ -1418,6 +1422,7 @@ export const TasksView: React.FC<TasksViewProps> = ({
);
})()}
</button>
<CountBadge count={tl.tasksCount} position="corner" />
{selectedListId === tl.id && (
<button
onClick={() => handleDeleteTaskList(tl.id)}