feat: enhance settings UI with optional icons and add kanban focus animation
- Updated SettingRow and SettingsSectionHeader components to support optional icons, improving visual clarity and user experience. - Introduced a new CSS animation for kanban card focus, enhancing the visual feedback when tasks are interacted with. - Adjusted NotificationsSection to utilize the new icon feature in headers and rows, providing a more engaging settings interface. - Refined team detail view to incorporate the kanban focus animation for better task visibility during interactions.
This commit is contained in:
parent
3381dad0da
commit
d4688825fd
6 changed files with 124 additions and 28 deletions
|
|
@ -1,17 +1,19 @@
|
|||
/**
|
||||
* SettingRow - Setting row component for consistent layout.
|
||||
* Linear-style clean row without icons.
|
||||
* Linear-style clean row with optional icon.
|
||||
*/
|
||||
|
||||
interface SettingRowProps {
|
||||
readonly label: string;
|
||||
readonly description?: string;
|
||||
readonly icon?: React.ReactNode;
|
||||
readonly children: React.ReactNode;
|
||||
}
|
||||
|
||||
export const SettingRow = ({
|
||||
label,
|
||||
description,
|
||||
icon,
|
||||
children,
|
||||
}: SettingRowProps): React.JSX.Element => {
|
||||
return (
|
||||
|
|
@ -19,15 +21,22 @@ export const SettingRow = ({
|
|||
className="flex items-center justify-between border-b py-3"
|
||||
style={{ borderColor: 'var(--color-border-subtle)' }}
|
||||
>
|
||||
<div>
|
||||
<div className="text-sm font-medium" style={{ color: 'var(--color-text)' }}>
|
||||
{label}
|
||||
</div>
|
||||
{description && (
|
||||
<div className="text-xs" style={{ color: 'var(--color-text-muted)' }}>
|
||||
{description}
|
||||
<div className="flex items-start gap-2.5">
|
||||
{icon ? (
|
||||
<div className="mt-0.5 shrink-0" style={{ color: 'var(--color-text-muted)' }}>
|
||||
{icon}
|
||||
</div>
|
||||
)}
|
||||
) : null}
|
||||
<div>
|
||||
<div className="text-sm font-medium" style={{ color: 'var(--color-text)' }}>
|
||||
{label}
|
||||
</div>
|
||||
{description && (
|
||||
<div className="text-xs" style={{ color: 'var(--color-text-muted)' }}>
|
||||
{description}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
<div className="shrink-0">{children}</div>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -1,18 +1,23 @@
|
|||
/**
|
||||
* SettingsSectionHeader - Section header component.
|
||||
* Linear-style subtle label.
|
||||
* Linear-style subtle label with optional icon.
|
||||
*/
|
||||
|
||||
interface SettingsSectionHeaderProps {
|
||||
readonly title: string;
|
||||
readonly icon?: React.ReactNode;
|
||||
}
|
||||
|
||||
export const SettingsSectionHeader = ({ title }: SettingsSectionHeaderProps): React.JSX.Element => {
|
||||
export const SettingsSectionHeader = ({
|
||||
title,
|
||||
icon,
|
||||
}: SettingsSectionHeaderProps): React.JSX.Element => {
|
||||
return (
|
||||
<h3
|
||||
className="mb-2 mt-6 text-xs font-medium uppercase tracking-widest first:mt-0"
|
||||
className="mb-2 mt-6 flex items-center gap-1.5 text-xs font-medium uppercase tracking-widest first:mt-0"
|
||||
style={{ color: 'var(--color-text-muted)' }}
|
||||
>
|
||||
{icon}
|
||||
{title}
|
||||
</h3>
|
||||
);
|
||||
|
|
|
|||
|
|
@ -7,7 +7,21 @@ import {
|
|||
RepositoryDropdown,
|
||||
SelectedRepositoryItem,
|
||||
} from '@renderer/components/common/RepositoryDropdown';
|
||||
import { ExternalLink } from 'lucide-react';
|
||||
import {
|
||||
AlertTriangle,
|
||||
ArrowRightLeft,
|
||||
Bell,
|
||||
BellRing,
|
||||
Clock,
|
||||
ExternalLink,
|
||||
EyeOff,
|
||||
HelpCircle,
|
||||
Inbox,
|
||||
Mail,
|
||||
MessageSquare,
|
||||
PartyPopper,
|
||||
Volume2,
|
||||
} from 'lucide-react';
|
||||
|
||||
import { SettingRow, SettingsSectionHeader, SettingsSelect, SettingsToggle } from '../components';
|
||||
import { NotificationTriggerSettings } from '../NotificationTriggerSettings';
|
||||
|
|
@ -80,7 +94,10 @@ export const NotificationsSection = ({
|
|||
return (
|
||||
<div>
|
||||
{/* Task Completion Notifications */}
|
||||
<SettingsSectionHeader title="Task Completion Notifications" />
|
||||
<SettingsSectionHeader
|
||||
title="Task Completion Notifications"
|
||||
icon={<PartyPopper className="size-3.5" />}
|
||||
/>
|
||||
<div
|
||||
className="mb-4 rounded-lg border p-4"
|
||||
style={{
|
||||
|
|
@ -108,10 +125,11 @@ export const NotificationsSection = ({
|
|||
</div>
|
||||
|
||||
{/* Notification Settings */}
|
||||
<SettingsSectionHeader title="Notification Settings" />
|
||||
<SettingsSectionHeader title="Notification Settings" icon={<Bell className="size-3.5" />} />
|
||||
<SettingRow
|
||||
label="Enable System Notifications"
|
||||
description="Show system notifications for errors and events"
|
||||
icon={<BellRing className="size-4" />}
|
||||
>
|
||||
<SettingsToggle
|
||||
enabled={safeConfig.notifications.enabled}
|
||||
|
|
@ -119,7 +137,11 @@ export const NotificationsSection = ({
|
|||
disabled={saving}
|
||||
/>
|
||||
</SettingRow>
|
||||
<SettingRow label="Play sound" description="Play a sound when notifications appear">
|
||||
<SettingRow
|
||||
label="Play sound"
|
||||
description="Play a sound when notifications appear"
|
||||
icon={<Volume2 className="size-4" />}
|
||||
>
|
||||
<SettingsToggle
|
||||
enabled={safeConfig.notifications.soundEnabled}
|
||||
onChange={(v) => onNotificationToggle('soundEnabled', v)}
|
||||
|
|
@ -129,6 +151,7 @@ export const NotificationsSection = ({
|
|||
<SettingRow
|
||||
label="Include subagent errors"
|
||||
description="Detect and notify about errors in subagent sessions"
|
||||
icon={<AlertTriangle className="size-4" />}
|
||||
>
|
||||
<SettingsToggle
|
||||
enabled={safeConfig.notifications.includeSubagentErrors}
|
||||
|
|
@ -139,6 +162,7 @@ export const NotificationsSection = ({
|
|||
<SettingRow
|
||||
label="Lead inbox notifications"
|
||||
description="Notify when teammates send messages to the team lead"
|
||||
icon={<Inbox className="size-4" />}
|
||||
>
|
||||
<SettingsToggle
|
||||
enabled={safeConfig.notifications.notifyOnLeadInbox}
|
||||
|
|
@ -149,6 +173,7 @@ export const NotificationsSection = ({
|
|||
<SettingRow
|
||||
label="User inbox notifications"
|
||||
description="Notify when teammates send messages to you"
|
||||
icon={<Mail className="size-4" />}
|
||||
>
|
||||
<SettingsToggle
|
||||
enabled={safeConfig.notifications.notifyOnUserInbox}
|
||||
|
|
@ -159,6 +184,7 @@ export const NotificationsSection = ({
|
|||
<SettingRow
|
||||
label="Task clarification notifications"
|
||||
description="Show native OS notifications when a task needs your input"
|
||||
icon={<HelpCircle className="size-4" />}
|
||||
>
|
||||
<SettingsToggle
|
||||
enabled={safeConfig.notifications.notifyOnClarifications}
|
||||
|
|
@ -169,6 +195,7 @@ export const NotificationsSection = ({
|
|||
<SettingRow
|
||||
label="Task comment notifications"
|
||||
description="Show native OS notifications when agents comment on tasks"
|
||||
icon={<MessageSquare className="size-4" />}
|
||||
>
|
||||
<SettingsToggle
|
||||
enabled={safeConfig.notifications.notifyOnTaskComments}
|
||||
|
|
@ -183,6 +210,7 @@ export const NotificationsSection = ({
|
|||
? `Snoozed until ${new Date(safeConfig.notifications.snoozedUntil!).toLocaleTimeString()}`
|
||||
: 'Temporarily pause notifications'
|
||||
}
|
||||
icon={<Clock className="size-4" />}
|
||||
>
|
||||
<div className="flex items-center gap-2">
|
||||
{isSnoozed ? (
|
||||
|
|
@ -208,12 +236,17 @@ export const NotificationsSection = ({
|
|||
{/* Task Status Change Notifications — grouped section */}
|
||||
<div className="border-b py-3" style={{ borderColor: 'var(--color-border-subtle)' }}>
|
||||
<div className="flex items-center justify-between">
|
||||
<div>
|
||||
<div className="text-sm font-medium" style={{ color: 'var(--color-text)' }}>
|
||||
Task status change notifications
|
||||
<div className="flex items-start gap-2.5">
|
||||
<div className="mt-0.5 shrink-0" style={{ color: 'var(--color-text-muted)' }}>
|
||||
<ArrowRightLeft className="size-4" />
|
||||
</div>
|
||||
<div className="text-xs" style={{ color: 'var(--color-text-muted)' }}>
|
||||
Show native OS notifications when a task's status changes
|
||||
<div>
|
||||
<div className="text-sm font-medium" style={{ color: 'var(--color-text)' }}>
|
||||
Task status change notifications
|
||||
</div>
|
||||
<div className="text-xs" style={{ color: 'var(--color-text-muted)' }}>
|
||||
Show native OS notifications when a task's status changes
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className="shrink-0">
|
||||
|
|
@ -282,7 +315,7 @@ export const NotificationsSection = ({
|
|||
onRemoveTrigger={onRemoveTrigger}
|
||||
/>
|
||||
|
||||
<SettingsSectionHeader title="Ignored Repositories" />
|
||||
<SettingsSectionHeader title="Ignored Repositories" icon={<EyeOff className="size-3.5" />} />
|
||||
<p className="mb-3 text-xs" style={{ color: 'var(--color-text-muted)' }}>
|
||||
Notifications from these repositories will be ignored
|
||||
</p>
|
||||
|
|
|
|||
|
|
@ -1581,8 +1581,14 @@ export const TeamDetailView = ({ teamName }: TeamDetailViewProps): React.JSX.Ele
|
|||
const el = document.querySelector(`[data-task-id="${taskId}"]`);
|
||||
if (el) {
|
||||
el.scrollIntoView({ behavior: 'smooth', block: 'nearest' });
|
||||
el.classList.add('ring-2', 'ring-blue-400/50');
|
||||
setTimeout(() => el.classList.remove('ring-2', 'ring-blue-400/50'), 1500);
|
||||
el.classList.remove('kanban-card-focus-pulse');
|
||||
void (el as HTMLElement).offsetWidth;
|
||||
el.classList.add('kanban-card-focus-pulse');
|
||||
el.addEventListener(
|
||||
'animationend',
|
||||
() => el.classList.remove('kanban-card-focus-pulse'),
|
||||
{ once: true }
|
||||
);
|
||||
}
|
||||
}}
|
||||
onTaskClick={(task) => setSelectedTask(task)}
|
||||
|
|
@ -1919,8 +1925,14 @@ export const TeamDetailView = ({ teamName }: TeamDetailViewProps): React.JSX.Ele
|
|||
const el = document.querySelector(`[data-task-id="${taskId}"]`);
|
||||
if (el) {
|
||||
el.scrollIntoView({ behavior: 'smooth', block: 'nearest' });
|
||||
el.classList.add('ring-2', 'ring-blue-400/50');
|
||||
setTimeout(() => el.classList.remove('ring-2', 'ring-blue-400/50'), 1500);
|
||||
el.classList.remove('kanban-card-focus-pulse');
|
||||
void (el as HTMLElement).offsetWidth;
|
||||
el.classList.add('kanban-card-focus-pulse');
|
||||
el.addEventListener(
|
||||
'animationend',
|
||||
() => el.classList.remove('kanban-card-focus-pulse'),
|
||||
{ once: true }
|
||||
);
|
||||
}
|
||||
}}
|
||||
onOwnerChange={(taskId, owner) => {
|
||||
|
|
|
|||
|
|
@ -89,9 +89,9 @@ const TEAMMATE_COLORS: Record<string, TeamColorSet> = {
|
|||
/** Reserved for the human user — never assigned to team members. */
|
||||
user: {
|
||||
border: '#f5f5f4',
|
||||
borderLight: '#a8a29e',
|
||||
borderLight: '#78716c',
|
||||
badge: 'rgba(245, 245, 244, 0.12)',
|
||||
badgeLight: 'rgba(120, 113, 108, 0.14)',
|
||||
badgeLight: 'rgba(87, 83, 78, 0.18)',
|
||||
text: '#d6d3d1',
|
||||
textLight: '#44403c',
|
||||
},
|
||||
|
|
|
|||
|
|
@ -295,6 +295,43 @@
|
|||
}
|
||||
}
|
||||
|
||||
@keyframes kanban-focus-pulse {
|
||||
0% {
|
||||
box-shadow: 0 0 0 0 rgba(96, 165, 250, 0.6);
|
||||
background-color: rgba(96, 165, 250, 0.15);
|
||||
}
|
||||
14% {
|
||||
box-shadow: 0 0 16px 6px rgba(96, 165, 250, 0.5);
|
||||
background-color: rgba(96, 165, 250, 0.12);
|
||||
}
|
||||
28% {
|
||||
box-shadow: 0 0 0 0 rgba(96, 165, 250, 0.1);
|
||||
background-color: rgba(96, 165, 250, 0.02);
|
||||
}
|
||||
42% {
|
||||
box-shadow: 0 0 14px 5px rgba(96, 165, 250, 0.45);
|
||||
background-color: rgba(96, 165, 250, 0.1);
|
||||
}
|
||||
56% {
|
||||
box-shadow: 0 0 0 0 rgba(96, 165, 250, 0.1);
|
||||
background-color: rgba(96, 165, 250, 0.02);
|
||||
}
|
||||
70% {
|
||||
box-shadow: 0 0 10px 4px rgba(96, 165, 250, 0.3);
|
||||
background-color: rgba(96, 165, 250, 0.06);
|
||||
}
|
||||
100% {
|
||||
box-shadow: 0 0 0 0 rgba(96, 165, 250, 0);
|
||||
background-color: transparent;
|
||||
}
|
||||
}
|
||||
|
||||
.kanban-card-focus-pulse {
|
||||
animation: kanban-focus-pulse 2.2s ease-out;
|
||||
outline: 2px solid rgba(96, 165, 250, 0.6);
|
||||
outline-offset: -1px;
|
||||
}
|
||||
|
||||
.kanban-grid-resize-handle-n,
|
||||
.kanban-grid-resize-handle-s {
|
||||
left: 50%;
|
||||
|
|
|
|||
Loading…
Reference in a new issue