feat(ui): one CountBadge for all counters; counters on tab corners
MindSift CI & Docker Release / Build, Test in Container & Push (push) Successful in 2m30s
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:
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 && (
|
||||
|
||||
@@ -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)}
|
||||
|
||||
Reference in new issue
Block a user