diff --git a/src/renderer/components/settings/components/SettingRow.tsx b/src/renderer/components/settings/components/SettingRow.tsx index d0353e34..a9a4ec3c 100644 --- a/src/renderer/components/settings/components/SettingRow.tsx +++ b/src/renderer/components/settings/components/SettingRow.tsx @@ -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)' }} > -
-
- {label} -
- {description && ( -
- {description} +
+ {icon ? ( +
+ {icon}
- )} + ) : null} +
+
+ {label} +
+ {description && ( +
+ {description} +
+ )} +
{children}
diff --git a/src/renderer/components/settings/components/SettingsSectionHeader.tsx b/src/renderer/components/settings/components/SettingsSectionHeader.tsx index 67887fd9..e94a0616 100644 --- a/src/renderer/components/settings/components/SettingsSectionHeader.tsx +++ b/src/renderer/components/settings/components/SettingsSectionHeader.tsx @@ -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 (

+ {icon} {title}

); diff --git a/src/renderer/components/settings/sections/NotificationsSection.tsx b/src/renderer/components/settings/sections/NotificationsSection.tsx index b75c4986..898af92a 100644 --- a/src/renderer/components/settings/sections/NotificationsSection.tsx +++ b/src/renderer/components/settings/sections/NotificationsSection.tsx @@ -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 (
{/* Task Completion Notifications */} - + } + />
{/* Notification Settings */} - + } /> } > - + } + > onNotificationToggle('soundEnabled', v)} @@ -129,6 +151,7 @@ export const NotificationsSection = ({ } > } > } > } > } > } >
{isSnoozed ? ( @@ -208,12 +236,17 @@ export const NotificationsSection = ({ {/* Task Status Change Notifications — grouped section */}
-
-
- Task status change notifications +
+
+
-
- Show native OS notifications when a task's status changes +
+
+ Task status change notifications +
+
+ Show native OS notifications when a task's status changes +
@@ -282,7 +315,7 @@ export const NotificationsSection = ({ onRemoveTrigger={onRemoveTrigger} /> - + } />

Notifications from these repositories will be ignored

diff --git a/src/renderer/components/team/TeamDetailView.tsx b/src/renderer/components/team/TeamDetailView.tsx index 42f36a8d..fd65d402 100644 --- a/src/renderer/components/team/TeamDetailView.tsx +++ b/src/renderer/components/team/TeamDetailView.tsx @@ -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) => { diff --git a/src/renderer/constants/teamColors.ts b/src/renderer/constants/teamColors.ts index 6b55b3ac..e37ff909 100644 --- a/src/renderer/constants/teamColors.ts +++ b/src/renderer/constants/teamColors.ts @@ -89,9 +89,9 @@ const TEAMMATE_COLORS: Record = { /** 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', }, diff --git a/src/renderer/index.css b/src/renderer/index.css index c49636dc..d93b5d46 100644 --- a/src/renderer/index.css +++ b/src/renderer/index.css @@ -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%;