diff --git a/.gitignore b/.gitignore index 76d65f17..162a9448 100644 --- a/.gitignore +++ b/.gitignore @@ -46,4 +46,7 @@ next-env.d.ts /data/ # Local private data -/local-data/ \ No newline at end of file +/local-data/ + +# Sibling repos +/repos/ \ No newline at end of file diff --git a/COMMIT_MSG.txt b/COMMIT_MSG.txt new file mode 100644 index 00000000..b2214fdc --- /dev/null +++ b/COMMIT_MSG.txt @@ -0,0 +1,16 @@ +refactor: update styling and color classes across components + +- Changed error message styling in ICalImportModal to use new color classes. +- Updated task completion styling in TaskListView to use new success color classes. +- Modified selected styling in ContactListItem tests to reflect new background class. +- Adjusted duplicate warning styling in ContactImportDialog to use new warning color classes. +- Refactored background and border colors in CalendarInvitationBanner for various statuses. +- Updated email list item and viewer components to use new warning and success color classes. +- Refined styling for unread indicators in email components. +- Enhanced error fallback styling in error components to use new warning color classes. +- Updated filter rule modal button hover styles to use new destructive color classes. +- Added experimental feature descriptions in Plugins and Themes settings. +- Refined vacation settings validation warning styling to use new warning color classes. +- Updated toast component styles to use new color classes for different states. +- Introduced a new built-in theme 'Qui' with specific color variables. +- Adjusted email security status colors to use new warning color classes. diff --git a/app/[locale]/login/page.tsx b/app/[locale]/login/page.tsx index 53123ee7..31ec1213 100644 --- a/app/[locale]/login/page.tsx +++ b/app/[locale]/login/page.tsx @@ -733,9 +733,9 @@ export default function LoginPage() { )} ) : oauthDiscoveryDone ? ( -
- -

+

+ +

{t("error.oauth_discovery_failed")}

@@ -941,9 +941,9 @@ export default function LoginPage() { )} {oauthEnabled && oauthDiscoveryDone && !oauthMetadata && ( -
- -

+

+ +

{t("error.oauth_discovery_failed")}

diff --git a/app/[locale]/settings/page.tsx b/app/[locale]/settings/page.tsx index 11fd0c3a..28aaee0c 100644 --- a/app/[locale]/settings/page.tsx +++ b/app/[locale]/settings/page.tsx @@ -69,6 +69,7 @@ interface TabDef { label: string; icon: LucideIcon; group: TabGroup; + experimental?: boolean; } const tabIcons: Record = { @@ -162,8 +163,8 @@ export default function SettingsPage() { { id: 'contacts', label: t('tabs.contacts'), icon: tabIcons.contacts, group: 'apps' }, ...(supportsFiles ? [{ id: 'files' as Tab, label: t('tabs.files'), icon: tabIcons.files, group: 'apps' as TabGroup }] : []), ...(isFeatureEnabled('sidebarAppsEnabled') ? [{ id: 'sidebar_apps' as Tab, label: t('tabs.sidebar_apps'), icon: tabIcons.sidebar_apps, group: 'apps' as TabGroup }] : []), - ...(isFeatureEnabled('themesEnabled') ? [{ id: 'themes' as Tab, label: 'Themes', icon: tabIcons.themes, group: 'system' as TabGroup }] : []), - ...(isFeatureEnabled('pluginsEnabled') ? [{ id: 'plugins' as Tab, label: 'Plugins', icon: tabIcons.plugins, group: 'system' as TabGroup }] : []), + ...(isFeatureEnabled('themesEnabled') ? [{ id: 'themes' as Tab, label: 'Themes', icon: tabIcons.themes, group: 'system' as TabGroup, experimental: true }] : []), + ...(isFeatureEnabled('pluginsEnabled') ? [{ id: 'plugins' as Tab, label: 'Plugins', icon: tabIcons.plugins, group: 'system' as TabGroup, experimental: true }] : []), { id: 'advanced', label: t('tabs.advanced'), icon: tabIcons.advanced, group: 'system' }, ]; @@ -290,6 +291,11 @@ export default function SettingsPage() { {tab.label} + {tab.experimental && ( + + Experimental + + )} @@ -396,6 +402,11 @@ export default function SettingsPage() { activeTab === tab.id ? 'text-accent-foreground' : 'text-muted-foreground' )} /> {tab.label} + {tab.experimental && ( + + Experimental + + )} ); })} diff --git a/app/admin/page.tsx b/app/admin/page.tsx index 79b5359b..5b6c0538 100644 --- a/app/admin/page.tsx +++ b/app/admin/page.tsx @@ -105,18 +105,18 @@ export default function AdminDashboardPage() {
{/* Warnings */} {warnings.map((msg, i) => ( -
- -

{msg}

+
+ +

{msg}

))} {status && !status.lastLogin && ( -
- +
+
-

First login detected

-

+

First login detected

+

Remember to remove ADMIN_PASSWORD from your .env file now that the hash is stored securely.

diff --git a/app/admin/plugins/page.tsx b/app/admin/plugins/page.tsx index ace58673..db3a5523 100644 --- a/app/admin/plugins/page.tsx +++ b/app/admin/plugins/page.tsx @@ -252,8 +252,8 @@ export default function AdminPluginsPage() {
{plugin.permissions.length > 0 && (
- - + + Permissions: {plugin.permissions.join(', ')}
diff --git a/app/globals.css b/app/globals.css index f8acf899..d0ac0078 100644 --- a/app/globals.css +++ b/app/globals.css @@ -23,6 +23,39 @@ --color-popover: #ffffff; --color-popover-foreground: #0f172a; + /* Sidebar */ + --color-sidebar: #f8fafc; + --color-sidebar-foreground: #0f172a; + --color-sidebar-border: #e2e8f0; + --color-sidebar-accent: #f1f5f9; + --color-sidebar-accent-foreground: #0f172a; + + /* Card */ + --color-card: #ffffff; + --color-card-foreground: #0f172a; + + /* Semantic status colors */ + --color-success: #22c55e; + --color-success-foreground: #ffffff; + --color-warning: #eab308; + --color-warning-foreground: #ffffff; + --color-info: #3b82f6; + --color-info-foreground: #ffffff; + + /* Selection states */ + --color-selection: #dbeafe; + --color-selection-foreground: #1e40af; + + /* Unread indicator */ + --color-unread: #3b82f6; + + /* Chart colors */ + --color-chart-1: #3b82f6; + --color-chart-2: #22c55e; + --color-chart-3: #f59e0b; + --color-chart-4: #ef4444; + --color-chart-5: #8b5cf6; + /* Settings variables */ --font-size-base: 16px; --list-item-height: 48px; @@ -54,6 +87,39 @@ --color-destructive-foreground: #fafafa; --color-popover: #1c1c1c; --color-popover-foreground: #fafafa; + + /* Sidebar */ + --color-sidebar: #0a0a0a; + --color-sidebar-foreground: #fafafa; + --color-sidebar-border: #262626; + --color-sidebar-accent: #1a1a1a; + --color-sidebar-accent-foreground: #fafafa; + + /* Card */ + --color-card: #141414; + --color-card-foreground: #fafafa; + + /* Semantic status colors */ + --color-success: #16a34a; + --color-success-foreground: #ffffff; + --color-warning: #ca8a04; + --color-warning-foreground: #ffffff; + --color-info: #60a5fa; + --color-info-foreground: #ffffff; + + /* Selection states */ + --color-selection: rgba(59, 130, 246, 0.25); + --color-selection-foreground: #93c5fd; + + /* Unread indicator */ + --color-unread: #60a5fa; + + /* Chart colors */ + --color-chart-1: #60a5fa; + --color-chart-2: #4ade80; + --color-chart-3: #fbbf24; + --color-chart-4: #f87171; + --color-chart-5: #a78bfa; } @theme inline { @@ -74,6 +140,27 @@ --color-destructive-foreground: var(--color-destructive-foreground); --color-popover: var(--color-popover); --color-popover-foreground: var(--color-popover-foreground); + --color-sidebar: var(--color-sidebar); + --color-sidebar-foreground: var(--color-sidebar-foreground); + --color-sidebar-border: var(--color-sidebar-border); + --color-sidebar-accent: var(--color-sidebar-accent); + --color-sidebar-accent-foreground: var(--color-sidebar-accent-foreground); + --color-card: var(--color-card); + --color-card-foreground: var(--color-card-foreground); + --color-success: var(--color-success); + --color-success-foreground: var(--color-success-foreground); + --color-warning: var(--color-warning); + --color-warning-foreground: var(--color-warning-foreground); + --color-info: var(--color-info); + --color-info-foreground: var(--color-info-foreground); + --color-selection: var(--color-selection); + --color-selection-foreground: var(--color-selection-foreground); + --color-unread: var(--color-unread); + --color-chart-1: var(--color-chart-1); + --color-chart-2: var(--color-chart-2); + --color-chart-3: var(--color-chart-3); + --color-chart-4: var(--color-chart-4); + --color-chart-5: var(--color-chart-5); } * { @@ -163,21 +250,16 @@ body { } .email-content blockquote { - border-left: 3px solid #d1d5db; + border-left: 3px solid var(--color-border); padding-left: 1rem; margin: 1rem 0; - color: #4b5563; + color: var(--color-muted-foreground); font-style: italic; } -.dark .email-content blockquote { - border-left-color: #4b5563; - color: #9ca3af; -} - .email-content pre { - background-color: #f5f5f7; - border: 1px solid #e5e7eb; + background-color: var(--color-muted); + border: 1px solid var(--color-border); border-radius: 0.375rem; padding: 1rem; font-family: "SF Mono", Monaco, Menlo, Consolas, monospace; @@ -186,23 +268,13 @@ body { margin: 1rem 0; } -.dark .email-content pre { - background-color: #1f2937; - border-color: #374151; -} - .email-content code { - background-color: #f3f4f6; + background-color: var(--color-muted); padding: 0.125rem 0.375rem; border-radius: 0.25rem; font-family: "SF Mono", Monaco, Menlo, Consolas, monospace; font-size: 0.875rem; - color: #dc2626; -} - -.dark .email-content code { - background-color: #374151; - color: #f87171; + color: var(--color-destructive); } .email-content h1, @@ -282,26 +354,15 @@ body { .email-content table[border="1"] th, .email-content table[border="1"] td { padding: 0.625rem; - border: 1px solid #e5e7eb; + border: 1px solid var(--color-border); text-align: left; } .email-content table.data-table th, .email-content table[border="1"] th { - background-color: #f9fafb; + background-color: var(--color-muted); font-weight: 600; - color: #374151; -} - -.dark .email-content table.data-table th, -.dark .email-content table[border="1"] th { - background-color: #1f2937; - color: #d1d5db; -} - -.dark .email-content table.data-table td, -.dark .email-content table[border="1"] td { - border-color: #374151; + color: var(--color-foreground); } /* Reset styles for layout tables (commonly used in HTML emails) */ @@ -323,28 +384,19 @@ body { .email-content hr { border: none; - border-top: 1px solid #e5e7eb; + border-top: 1px solid var(--color-border); margin: 1.5rem 0; } -.dark .email-content hr { - border-top-color: #374151; -} - /* Email thread quoted text */ .email-content .quoted-text { - border-left: 3px solid #d1d5db; + border-left: 3px solid var(--color-border); padding-left: 1rem; margin: 1rem 0; - color: #4b5563; + color: var(--color-muted-foreground); opacity: 0.8; } -.dark .email-content .quoted-text { - border-left-color: #4b5563; - color: #9ca3af; -} - /* Toast animations */ @keyframes toast-enter { from { diff --git a/components/calendar/calendar-day-view.tsx b/components/calendar/calendar-day-view.tsx index 87a98321..55feb06d 100644 --- a/components/calendar/calendar-day-view.tsx +++ b/components/calendar/calendar-day-view.tsx @@ -182,7 +182,7 @@ export function CalendarDayView({ className={cn( "flex-shrink-0 w-3.5 h-3.5 rounded-full border flex items-center justify-center", isCompleted - ? "bg-green-500 border-green-500 text-white" + ? "bg-success border-success text-success-foreground" : "border-muted-foreground/40 hover:border-primary" )} > @@ -288,8 +288,8 @@ export function CalendarDayView({ style={{ top: (nowMinutes / 60) * HOUR_HEIGHT }} >
-
-
+
+
)} diff --git a/components/calendar/calendar-week-view.tsx b/components/calendar/calendar-week-view.tsx index 0817e758..cede7e4a 100644 --- a/components/calendar/calendar-week-view.tsx +++ b/components/calendar/calendar-week-view.tsx @@ -252,7 +252,7 @@ export function CalendarWeekView({ > {isCompleted && } @@ -402,8 +402,8 @@ export function CalendarWeekView({ style={{ top: (nowMinutes / 60) * HOUR_HEIGHT }} >
-
-
+
+
)} diff --git a/components/calendar/event-detail-popover.tsx b/components/calendar/event-detail-popover.tsx index 7db26f89..0611e983 100644 --- a/components/calendar/event-detail-popover.tsx +++ b/components/calendar/event-detail-popover.tsx @@ -297,7 +297,7 @@ export function EventDetailPopover({

{calendar.name} {event.status === "tentative" && ( - + {t("detail.tentative")} )} @@ -507,8 +507,8 @@ export function EventDetailPopover({ onClick={() => onRsvp("accepted")} className={ userCurrentStatus === "accepted" - ? "bg-green-600 hover:bg-green-700 text-white dark:bg-green-500 dark:hover:bg-green-600" - : "text-green-600 dark:text-green-400 border-green-300 dark:border-green-700 hover:bg-green-50 dark:hover:bg-green-950" + ? "bg-success hover:bg-success/80 text-success-foreground" + : "text-success border-success/30 hover:bg-success/10" } > {userCurrentStatus === "accepted" && } @@ -520,8 +520,8 @@ export function EventDetailPopover({ onClick={() => onRsvp("tentative")} className={ userCurrentStatus === "tentative" - ? "bg-amber-600 hover:bg-amber-700 text-white dark:bg-amber-500 dark:hover:bg-amber-600" - : "border border-amber-500 text-amber-600 hover:bg-amber-50 dark:text-amber-400 dark:hover:bg-amber-950" + ? "bg-warning hover:bg-warning/80 text-warning-foreground" + : "border border-warning/30 text-warning hover:bg-warning/10" } > {userCurrentStatus === "tentative" && } @@ -533,8 +533,8 @@ export function EventDetailPopover({ onClick={() => onRsvp("declined")} className={ userCurrentStatus === "declined" - ? "bg-red-600 hover:bg-red-700 text-white dark:bg-red-500 dark:hover:bg-red-600" - : "text-red-600 hover:bg-red-50 dark:text-red-400 dark:hover:bg-red-950" + ? "bg-destructive hover:bg-destructive/80 text-destructive-foreground" + : "text-destructive hover:bg-destructive/10" } > {userCurrentStatus === "declined" && } @@ -548,7 +548,7 @@ export function EventDetailPopover({

{showDeleteConfirm ? (
- + {t("form.delete_confirm")}
) : ( - @@ -997,9 +997,9 @@ function StatusBadge({ status, isOrganizer, t }: { return {t("participants.organizer")}; } const colors: Record = { - accepted: "text-green-600 dark:text-green-400", - declined: "text-red-600 dark:text-red-400", - tentative: "text-amber-600 dark:text-amber-400", + accepted: "text-success", + declined: "text-destructive", + tentative: "text-warning", "needs-action": "text-muted-foreground", }; const labels: Record = { diff --git a/components/calendar/ical-import-modal.tsx b/components/calendar/ical-import-modal.tsx index 2e9657d4..ddf6d5ea 100644 --- a/components/calendar/ical-import-modal.tsx +++ b/components/calendar/ical-import-modal.tsx @@ -349,7 +349,7 @@ export function ICalImportModal({ calendars, client, onClose }: ICalImportModalP )} {error && ( -
+
{error}
)} diff --git a/components/calendar/task-list-view.tsx b/components/calendar/task-list-view.tsx index c1a79e00..9b9fbd08 100644 --- a/components/calendar/task-list-view.tsx +++ b/components/calendar/task-list-view.tsx @@ -204,7 +204,7 @@ export function TaskListView({ className={cn( "mt-0.5 flex-shrink-0 w-5 h-5 rounded-full border-2 flex items-center justify-center transition-colors", isCompleted - ? "bg-green-500 border-green-500 text-white" + ? "bg-success border-success text-success-foreground" : "border-muted-foreground/40 hover:border-primary" )} aria-label={isCompleted ? t("tasks.mark_incomplete") : t("tasks.mark_complete")} diff --git a/components/contacts/__tests__/contact-list-item.test.tsx b/components/contacts/__tests__/contact-list-item.test.tsx index 252e9888..d4556026 100644 --- a/components/contacts/__tests__/contact-list-item.test.tsx +++ b/components/contacts/__tests__/contact-list-item.test.tsx @@ -52,7 +52,7 @@ describe('ContactListItem', () => { it('applies selected styling', () => { const { container } = render(); const div = container.firstElementChild; - expect(div?.className).toContain('bg-blue-200'); + expect(div?.className).toContain('bg-selection'); }); it('shows email as display name when no name exists', () => { diff --git a/components/contacts/contact-import-dialog.tsx b/components/contacts/contact-import-dialog.tsx index 372e1b49..c399b0fc 100644 --- a/components/contacts/contact-import-dialog.tsx +++ b/components/contacts/contact-import-dialog.tsx @@ -205,7 +205,7 @@ export function ContactImportDialog({ )}
{isDupe && ( - + {t("import.duplicate")} )} diff --git a/components/contacts/contact-list-item.tsx b/components/contacts/contact-list-item.tsx index 2b78a634..0f330e65 100644 --- a/components/contacts/contact-list-item.tsx +++ b/components/contacts/contact-list-item.tsx @@ -59,9 +59,9 @@ export function ContactListItem({ contact, isSelected, isChecked, hasSelection, className={cn( "w-full flex items-center cursor-pointer select-none transition-all duration-200 border-b border-border", isSelected - ? "bg-blue-200 dark:bg-blue-900/50 shadow-sm" + ? "bg-selection shadow-sm" : "bg-background hover:bg-muted hover:shadow-sm", - isChecked && !isSelected && "ring-2 ring-primary/20 bg-blue-100 dark:bg-blue-900/30", + isChecked && !isSelected && "ring-2 ring-primary/20 bg-selection/60", )} style={{ gap: 'var(--density-item-gap)', paddingInline: '16px', paddingBlock: 'var(--density-item-py)' }} > diff --git a/components/email/calendar-invitation-banner.tsx b/components/email/calendar-invitation-banner.tsx index 43ac924d..dd398658 100644 --- a/components/email/calendar-invitation-banner.tsx +++ b/components/email/calendar-invitation-banner.tsx @@ -171,11 +171,11 @@ function getParticipationLabel( function getParticipationTone(status: string | null): string { switch (status) { case 'accepted': - return 'bg-green-100 text-green-700 dark:bg-green-900/30 dark:text-green-400'; + return 'bg-success/15 text-success'; case 'tentative': - return 'bg-amber-100 text-amber-700 dark:bg-amber-900/30 dark:text-amber-400'; + return 'bg-warning/15 text-warning'; case 'declined': - return 'bg-red-100 text-red-700 dark:bg-red-900/30 dark:text-red-400'; + return 'bg-destructive/15 text-destructive'; default: return 'bg-muted text-muted-foreground'; } @@ -731,9 +731,9 @@ export function CalendarInvitationBanner({ email }: CalendarInvitationBannerProp if (state === 'error') { return ( -
- - {t('parse_error')} +
+ + {t('parse_error')}
); } @@ -744,7 +744,7 @@ export function CalendarInvitationBanner({ email }: CalendarInvitationBannerProp
{isCancellation ? ( - + ) : ( )} @@ -808,19 +808,19 @@ export function CalendarInvitationBanner({ email }: CalendarInvitationBannerProp {t(`event_status_${parsedEvent.status}`)} )} {existingEvent && ( - + {t('already_in_calendar')} )} {userIsOrganizer && ( - + {t('organizer_role')} )} @@ -833,7 +833,7 @@ export function CalendarInvitationBanner({ email }: CalendarInvitationBannerProp )} {actionFeedback && ( - + {actionFeedback} )} @@ -877,8 +877,8 @@ export function CalendarInvitationBanner({ email }: CalendarInvitationBannerProp
{trustMessage} @@ -887,7 +887,7 @@ export function CalendarInvitationBanner({ email }: CalendarInvitationBannerProp {/* Action error */} {actionError && ( -
+
{actionError}
@@ -905,8 +905,8 @@ export function CalendarInvitationBanner({ email }: CalendarInvitationBannerProp className={cn( "inline-flex items-center gap-1.5 text-sm font-medium px-3 py-1.5 rounded-md transition-colors min-h-[36px] disabled:opacity-50 border", currentRsvp === 'accepted' - ? "bg-green-100 dark:bg-green-900/30 text-green-700 dark:text-green-400 border-green-200 dark:border-green-800" - : "text-muted-foreground hover:text-green-700 dark:hover:text-green-400 border-border hover:border-green-300 dark:hover:border-green-800 hover:bg-green-50 dark:hover:bg-green-950/20" + ? "bg-success/15 text-success border-success/20" + : "text-muted-foreground hover:text-success border-border hover:border-success/30 hover:bg-success/10" )} > @@ -919,8 +919,8 @@ export function CalendarInvitationBanner({ email }: CalendarInvitationBannerProp className={cn( "inline-flex items-center gap-1.5 text-sm font-medium px-3 py-1.5 rounded-md transition-colors min-h-[36px] disabled:opacity-50 border", currentRsvp === 'tentative' - ? "bg-amber-100 dark:bg-amber-900/30 text-amber-700 dark:text-amber-400 border-amber-200 dark:border-amber-800" - : "text-muted-foreground hover:text-amber-700 dark:hover:text-amber-400 border-border hover:border-amber-300 dark:hover:border-amber-800 hover:bg-amber-50 dark:hover:bg-amber-950/20" + ? "bg-warning/15 text-warning border-warning/20" + : "text-muted-foreground hover:text-warning border-border hover:border-warning/30 hover:bg-warning/10" )} > @@ -933,8 +933,8 @@ export function CalendarInvitationBanner({ email }: CalendarInvitationBannerProp className={cn( "inline-flex items-center gap-1.5 text-sm font-medium px-3 py-1.5 rounded-md transition-colors min-h-[36px] disabled:opacity-50 border", currentRsvp === 'declined' - ? "bg-red-100 dark:bg-red-900/30 text-red-700 dark:text-red-400 border-red-200 dark:border-red-800" - : "text-muted-foreground hover:text-red-700 dark:hover:text-red-400 border-border hover:border-red-300 dark:hover:border-red-800 hover:bg-red-50 dark:hover:bg-red-950/20" + ? "bg-destructive/15 text-destructive border-destructive/20" + : "text-muted-foreground hover:text-destructive border-border hover:border-destructive/30 hover:bg-destructive/10" )} > diff --git a/components/email/email-list-item.tsx b/components/email/email-list-item.tsx index 0ddded47..4858ef25 100644 --- a/components/email/email-list-item.tsx +++ b/components/email/email-list-item.tsx @@ -87,15 +87,15 @@ export function EmailListItem({ email, selected, onClick, onContextMenu, onToggl // Apply color tag as background, with selected and unread states colorTag ? colorTag : ( selected - ? "bg-blue-200 dark:bg-blue-900/50" + ? "bg-selection" : "bg-background" ), selected && !colorTag && "shadow-sm", !colorTag && !selected && "hover:bg-muted hover:shadow-sm", colorTag && "hover:brightness-95 dark:hover:brightness-110", - isUnread && !selected && !colorTag && "bg-amber-50 dark:bg-amber-900/20", + isUnread && !selected && !colorTag && "bg-warning/10", // Add visual feedback for checked state - isChecked && "ring-2 ring-primary/20 bg-blue-100 dark:bg-blue-900/30", + isChecked && "ring-2 ring-primary/20 bg-selection/60", // Drag state visual feedback isDragging && "opacity-50 scale-[0.98] ring-2 ring-primary/30", // Long press visual feedback @@ -140,7 +140,7 @@ export function EmailListItem({ email, selected, onClick, onContextMenu, onToggl {/* Unread indicator */} {isUnread && (
- +
)} @@ -172,7 +172,7 @@ export function EmailListItem({ email, selected, onClick, onContextMenu, onToggl )} {isImportant && ( - + Important )} diff --git a/components/email/email-viewer.tsx b/components/email/email-viewer.tsx index 4a14d1a0..0beae7dc 100644 --- a/components/email/email-viewer.tsx +++ b/components/email/email-viewer.tsx @@ -3541,7 +3541,7 @@ export function EmailViewer({ ) : null; })()} {isImportant && ( - + {t('important')} )} @@ -4372,9 +4372,9 @@ export function EmailViewer({ {/* Draft Banner */} {isDraft && ( -
+
-
+
{t('draft_banner')}
diff --git a/components/email/smime-status-banner.tsx b/components/email/smime-status-banner.tsx index d8ca2210..010c45f4 100644 --- a/components/email/smime-status-banner.tsx +++ b/components/email/smime-status-banner.tsx @@ -83,10 +83,10 @@ export function SmimeStatusBanner({ status, onUnlockKey, className }: SmimeStatu if (items.length === 0) return null; const variantStyles = { - success: 'bg-green-50 dark:bg-green-950/30 text-green-700 dark:text-green-400 border-green-200 dark:border-green-800', - warning: 'bg-yellow-50 dark:bg-yellow-950/30 text-yellow-700 dark:text-yellow-400 border-yellow-200 dark:border-yellow-800', - error: 'bg-red-50 dark:bg-red-950/30 text-red-700 dark:text-red-400 border-red-200 dark:border-red-800', - info: 'bg-blue-50 dark:bg-blue-950/30 text-blue-700 dark:text-blue-400 border-blue-200 dark:border-blue-800', + success: 'bg-success/10 text-success border-success/30', + warning: 'bg-warning/10 text-warning border-warning/30', + error: 'bg-destructive/10 text-destructive border-destructive/30', + info: 'bg-info/10 text-info border-info/30', }; return ( diff --git a/components/email/thread-email-item.tsx b/components/email/thread-email-item.tsx index 4c927028..05933a3d 100644 --- a/components/email/thread-email-item.tsx +++ b/components/email/thread-email-item.tsx @@ -85,9 +85,9 @@ export function ThreadEmailItem({ "pl-12 pr-4", "border-l-2 border-l-transparent", selected - ? "bg-blue-200 dark:bg-blue-900/50 border-l-primary" + ? "bg-selection border-l-primary" : "hover:bg-muted/50", - isUnread && !selected && "bg-amber-50 dark:bg-amber-900/20", + isUnread && !selected && "bg-warning/10", !isLast && "border-b border-border/30", isChecked && "ring-2 ring-primary/20 bg-accent/40", isDragging && "opacity-50 scale-[0.98] ring-2 ring-primary/30", @@ -121,7 +121,7 @@ export function ThreadEmailItem({ {/* Unread indicator */} {isUnread && (
- +
)} diff --git a/components/email/thread-list-item.tsx b/components/email/thread-list-item.tsx index b2e5d13b..87a3b332 100644 --- a/components/email/thread-list-item.tsx +++ b/components/email/thread-list-item.tsx @@ -156,7 +156,7 @@ const SingleEmailItem = React.forwardRef( {isUnread && (
- +
)} @@ -456,7 +456,7 @@ export const ThreadListItem = React.forwardRef - +
)} diff --git a/components/error/error-fallbacks.tsx b/components/error/error-fallbacks.tsx index 045fb9b7..1c1dc5ba 100644 --- a/components/error/error-fallbacks.tsx +++ b/components/error/error-fallbacks.tsx @@ -94,7 +94,7 @@ export function EmailViewerErrorFallback({ resetError, t }: FallbackProps) { export function ComposerErrorFallback({ resetError, t }: FallbackProps) { return (
- +

{t("composer_error")}

diff --git a/components/filters/filter-rule-modal.tsx b/components/filters/filter-rule-modal.tsx index 081f536b..9bd5ed36 100644 --- a/components/filters/filter-rule-modal.tsx +++ b/components/filters/filter-rule-modal.tsx @@ -299,7 +299,7 @@ export function FilterRuleModal({ type="button" onClick={() => removeCondition(index)} disabled={conditions.length <= 1} - className="p-1.5 rounded hover:bg-muted text-muted-foreground hover:text-red-600 dark:hover:text-red-400 transition-colors disabled:opacity-30 disabled:pointer-events-none" + className="p-1.5 rounded hover:bg-muted text-muted-foreground hover:text-destructive transition-colors disabled:opacity-30 disabled:pointer-events-none" aria-label={t("delete_rule")} > @@ -387,7 +387,7 @@ export function FilterRuleModal({ type="button" onClick={() => removeAction(index)} disabled={actions.length <= 1} - className="p-1.5 rounded hover:bg-muted text-muted-foreground hover:text-red-600 dark:hover:text-red-400 transition-colors disabled:opacity-30 disabled:pointer-events-none" + className="p-1.5 rounded hover:bg-muted text-muted-foreground hover:text-destructive transition-colors disabled:opacity-30 disabled:pointer-events-none" aria-label={t("delete_rule")} > diff --git a/components/filters/sieve-editor-modal.tsx b/components/filters/sieve-editor-modal.tsx index 19655814..12652844 100644 --- a/components/filters/sieve-editor-modal.tsx +++ b/components/filters/sieve-editor-modal.tsx @@ -93,7 +93,7 @@ export function SieveEditorModal({
-
+

{t("warning")}

@@ -131,8 +131,8 @@ export function SieveEditorModal({
{validationResult.isValid ? ( @@ -157,7 +157,7 @@ export function SieveEditorModal({ )} {showSaveWarning && ( -
+

{t("save_warning")}

diff --git a/components/layout/navigation-rail.tsx b/components/layout/navigation-rail.tsx index 4b421cfd..9251a20b 100644 --- a/components/layout/navigation-rail.tsx +++ b/components/layout/navigation-rail.tsx @@ -73,10 +73,10 @@ function StorageQuotaCircle({ quota, usagePercent }: { quota: { used: number; to const free = quota.total - quota.used; const strokeColor = usagePercent > 90 - ? "stroke-red-500 dark:stroke-red-400" + ? "stroke-destructive" : usagePercent > 70 - ? "stroke-amber-500 dark:stroke-amber-400" - : "stroke-green-500 dark:stroke-green-400"; + ? "stroke-warning" + : "stroke-success"; return (
@@ -123,10 +123,10 @@ function StorageQuotaCircle({ quota, usagePercent }: { quota: { used: number; to className={cn( "h-1.5 rounded-full transition-all", usagePercent > 90 - ? "bg-red-500 dark:bg-red-400" + ? "bg-destructive" : usagePercent > 70 - ? "bg-amber-500 dark:bg-amber-400" - : "bg-green-500 dark:bg-green-400" + ? "bg-warning" + : "bg-success" )} style={{ width: `${usagePercent}%` }} /> diff --git a/components/settings/plugins-settings.tsx b/components/settings/plugins-settings.tsx index ec9541ea..86c19afc 100644 --- a/components/settings/plugins-settings.tsx +++ b/components/settings/plugins-settings.tsx @@ -68,7 +68,7 @@ export function PluginsSettings() { }; return ( - + {/* Plugin List */} {plugins.length === 0 ? (
diff --git a/components/settings/settings-section.tsx b/components/settings/settings-section.tsx index 63b456b4..c55a3f0a 100644 --- a/components/settings/settings-section.tsx +++ b/components/settings/settings-section.tsx @@ -1,16 +1,29 @@ import { ReactNode } from 'react'; -import { Lock } from 'lucide-react'; +import { Lock, FlaskConical } from 'lucide-react'; import { cn } from '@/lib/utils'; interface SettingsSectionProps { title: string; description?: string; children: ReactNode; + experimental?: boolean; + experimentalDescription?: string; } -export function SettingsSection({ title, description, children }: SettingsSectionProps) { +export function SettingsSection({ title, description, children, experimental, experimentalDescription }: SettingsSectionProps) { return (
+ {experimental && ( +
+ +
+

Experimental Feature

+ {experimentalDescription && ( +

{experimentalDescription}

+ )} +
+
+ )}

{title}

{description && ( diff --git a/components/settings/themes-settings.tsx b/components/settings/themes-settings.tsx index ebd9e733..e97b95c7 100644 --- a/components/settings/themes-settings.tsx +++ b/components/settings/themes-settings.tsx @@ -69,7 +69,8 @@ export function ThemesSettings() { }; return ( - + + {/* Theme Grid */}
{/* Default theme card */} diff --git a/components/settings/vacation-settings.tsx b/components/settings/vacation-settings.tsx index 1da917e0..15feb26b 100644 --- a/components/settings/vacation-settings.tsx +++ b/components/settings/vacation-settings.tsx @@ -240,7 +240,7 @@ export function VacationSettings() { {validationWarnings.length > 0 && (
{validationWarnings.map((warning, i) => ( -
+
{warning}
diff --git a/components/ui/toast.tsx b/components/ui/toast.tsx index 85ed88c6..c7d940be 100644 --- a/components/ui/toast.tsx +++ b/components/ui/toast.tsx @@ -36,17 +36,17 @@ const icons = { }; const iconContainerStyles = { - success: "bg-emerald-500 text-white", - error: "bg-red-500 text-white", - info: "bg-blue-500 text-white", - warning: "bg-amber-500 text-white", + success: "bg-success text-success-foreground", + error: "bg-destructive text-destructive-foreground", + info: "bg-info text-info-foreground", + warning: "bg-warning text-warning-foreground", }; const progressBarStyles = { - success: "bg-emerald-500", - error: "bg-red-500", - info: "bg-blue-500", - warning: "bg-amber-500", + success: "bg-success", + error: "bg-destructive", + info: "bg-info", + warning: "bg-warning", }; export function ToastItem({ toast, onClose }: ToastProps) { diff --git a/lib/builtin-themes.ts b/lib/builtin-themes.ts index 7ed880b6..05326cf7 100644 --- a/lib/builtin-themes.ts +++ b/lib/builtin-themes.ts @@ -1,5 +1,87 @@ import type { InstalledTheme } from './plugin-types'; +const quiCSS = ` +:root { + --color-border: #e4e4e7; + --color-input: #e4e4e7; + --color-ring: #3b82f6; + --color-background: #ffffff; + --color-foreground: #09090b; + --color-primary: #3b82f6; + --color-primary-foreground: #ffffff; + --color-secondary: #f4f4f5; + --color-secondary-foreground: #18181b; + --color-muted: #f4f4f5; + --color-muted-foreground: #71717a; + --color-accent: #dbeafe; + --color-accent-foreground: #1d4ed8; + --color-destructive: #ef4444; + --color-destructive-foreground: #ffffff; + --color-popover: #ffffff; + --color-popover-foreground: #09090b; + --color-sidebar: #fafafa; + --color-sidebar-foreground: #09090b; + --color-sidebar-border: #e4e4e7; + --color-sidebar-accent: #f4f4f5; + --color-sidebar-accent-foreground: #18181b; + --color-card: #ffffff; + --color-card-foreground: #09090b; + --color-success: #22c55e; + --color-success-foreground: #ffffff; + --color-warning: #eab308; + --color-warning-foreground: #ffffff; + --color-info: #3b82f6; + --color-info-foreground: #ffffff; + --color-selection: #dbeafe; + --color-selection-foreground: #1d4ed8; + --color-unread: #3b82f6; + --color-chart-1: #3b82f6; + --color-chart-2: #22c55e; + --color-chart-3: #f59e0b; + --color-chart-4: #ef4444; + --color-chart-5: #8b5cf6; +} +.dark { + --color-border: #27272a; + --color-input: #27272a; + --color-ring: #3b82f6; + --color-background: #09090b; + --color-foreground: #fafafa; + --color-primary: #3b82f6; + --color-primary-foreground: #ffffff; + --color-secondary: #18181b; + --color-secondary-foreground: #fafafa; + --color-muted: #18181b; + --color-muted-foreground: #a1a1aa; + --color-accent: #172554; + --color-accent-foreground: #93c5fd; + --color-destructive: #ef4444; + --color-destructive-foreground: #fafafa; + --color-popover: #18181b; + --color-popover-foreground: #fafafa; + --color-sidebar: #09090b; + --color-sidebar-foreground: #fafafa; + --color-sidebar-border: #27272a; + --color-sidebar-accent: #18181b; + --color-sidebar-accent-foreground: #fafafa; + --color-card: #141414; + --color-card-foreground: #fafafa; + --color-success: #16a34a; + --color-success-foreground: #ffffff; + --color-warning: #ca8a04; + --color-warning-foreground: #ffffff; + --color-info: #60a5fa; + --color-info-foreground: #ffffff; + --color-selection: rgba(59, 130, 246, 0.25); + --color-selection-foreground: #93c5fd; + --color-unread: #60a5fa; + --color-chart-1: #60a5fa; + --color-chart-2: #4ade80; + --color-chart-3: #fbbf24; + --color-chart-4: #f87171; + --color-chart-5: #a78bfa; +}`; + const nordCSS = ` :root { --color-border: #d8dee9; @@ -19,6 +101,27 @@ const nordCSS = ` --color-destructive-foreground: #eceff4; --color-popover: #eceff4; --color-popover-foreground: #2e3440; + --color-sidebar: #e5e9f0; + --color-sidebar-foreground: #2e3440; + --color-sidebar-border: #d8dee9; + --color-sidebar-accent: #d8dee9; + --color-sidebar-accent-foreground: #2e3440; + --color-card: #eceff4; + --color-card-foreground: #2e3440; + --color-success: #a3be8c; + --color-success-foreground: #2e3440; + --color-warning: #ebcb8b; + --color-warning-foreground: #2e3440; + --color-info: #81a1c1; + --color-info-foreground: #eceff4; + --color-selection: #d8dee9; + --color-selection-foreground: #5e81ac; + --color-unread: #5e81ac; + --color-chart-1: #5e81ac; + --color-chart-2: #a3be8c; + --color-chart-3: #ebcb8b; + --color-chart-4: #bf616a; + --color-chart-5: #b48ead; } .dark { --color-border: #3b4252; @@ -38,6 +141,27 @@ const nordCSS = ` --color-destructive-foreground: #eceff4; --color-popover: #3b4252; --color-popover-foreground: #eceff4; + --color-sidebar: #2e3440; + --color-sidebar-foreground: #eceff4; + --color-sidebar-border: #3b4252; + --color-sidebar-accent: #3b4252; + --color-sidebar-accent-foreground: #eceff4; + --color-card: #3b4252; + --color-card-foreground: #eceff4; + --color-success: #a3be8c; + --color-success-foreground: #2e3440; + --color-warning: #ebcb8b; + --color-warning-foreground: #2e3440; + --color-info: #88c0d0; + --color-info-foreground: #2e3440; + --color-selection: rgba(136, 192, 208, 0.2); + --color-selection-foreground: #88c0d0; + --color-unread: #88c0d0; + --color-chart-1: #88c0d0; + --color-chart-2: #a3be8c; + --color-chart-3: #ebcb8b; + --color-chart-4: #bf616a; + --color-chart-5: #b48ead; }`; const catppuccinCSS = ` @@ -59,6 +183,27 @@ const catppuccinCSS = ` --color-destructive-foreground: #eff1f5; --color-popover: #eff1f5; --color-popover-foreground: #4c4f69; + --color-sidebar: #e6e9ef; + --color-sidebar-foreground: #4c4f69; + --color-sidebar-border: #ccd0da; + --color-sidebar-accent: #dce0e8; + --color-sidebar-accent-foreground: #4c4f69; + --color-card: #eff1f5; + --color-card-foreground: #4c4f69; + --color-success: #40a02b; + --color-success-foreground: #eff1f5; + --color-warning: #df8e1d; + --color-warning-foreground: #eff1f5; + --color-info: #1e66f5; + --color-info-foreground: #eff1f5; + --color-selection: #dce0e8; + --color-selection-foreground: #8839ef; + --color-unread: #8839ef; + --color-chart-1: #8839ef; + --color-chart-2: #40a02b; + --color-chart-3: #df8e1d; + --color-chart-4: #d20f39; + --color-chart-5: #1e66f5; } .dark { --color-border: #45475a; @@ -78,6 +223,27 @@ const catppuccinCSS = ` --color-destructive-foreground: #1e1e2e; --color-popover: #313244; --color-popover-foreground: #cdd6f4; + --color-sidebar: #1e1e2e; + --color-sidebar-foreground: #cdd6f4; + --color-sidebar-border: #45475a; + --color-sidebar-accent: #313244; + --color-sidebar-accent-foreground: #cdd6f4; + --color-card: #313244; + --color-card-foreground: #cdd6f4; + --color-success: #a6e3a1; + --color-success-foreground: #1e1e2e; + --color-warning: #f9e2af; + --color-warning-foreground: #1e1e2e; + --color-info: #89b4fa; + --color-info-foreground: #1e1e2e; + --color-selection: rgba(203, 166, 247, 0.2); + --color-selection-foreground: #cba6f7; + --color-unread: #cba6f7; + --color-chart-1: #cba6f7; + --color-chart-2: #a6e3a1; + --color-chart-3: #f9e2af; + --color-chart-4: #f38ba8; + --color-chart-5: #89b4fa; }`; const solarizedCSS = ` @@ -99,6 +265,27 @@ const solarizedCSS = ` --color-destructive-foreground: #fdf6e3; --color-popover: #fdf6e3; --color-popover-foreground: #657b83; + --color-sidebar: #eee8d5; + --color-sidebar-foreground: #657b83; + --color-sidebar-border: #eee8d5; + --color-sidebar-accent: #eee8d5; + --color-sidebar-accent-foreground: #586e75; + --color-card: #fdf6e3; + --color-card-foreground: #657b83; + --color-success: #859900; + --color-success-foreground: #fdf6e3; + --color-warning: #b58900; + --color-warning-foreground: #fdf6e3; + --color-info: #268bd2; + --color-info-foreground: #fdf6e3; + --color-selection: #eee8d5; + --color-selection-foreground: #268bd2; + --color-unread: #268bd2; + --color-chart-1: #268bd2; + --color-chart-2: #859900; + --color-chart-3: #b58900; + --color-chart-4: #dc322f; + --color-chart-5: #6c71c4; } .dark { --color-border: #073642; @@ -118,9 +305,41 @@ const solarizedCSS = ` --color-destructive-foreground: #fdf6e3; --color-popover: #073642; --color-popover-foreground: #93a1a1; + --color-sidebar: #002b36; + --color-sidebar-foreground: #839496; + --color-sidebar-border: #073642; + --color-sidebar-accent: #073642; + --color-sidebar-accent-foreground: #93a1a1; + --color-card: #073642; + --color-card-foreground: #93a1a1; + --color-success: #859900; + --color-success-foreground: #002b36; + --color-warning: #b58900; + --color-warning-foreground: #002b36; + --color-info: #268bd2; + --color-info-foreground: #002b36; + --color-selection: rgba(38, 139, 210, 0.2); + --color-selection-foreground: #268bd2; + --color-unread: #268bd2; + --color-chart-1: #268bd2; + --color-chart-2: #859900; + --color-chart-3: #b58900; + --color-chart-4: #dc322f; + --color-chart-5: #6c71c4; }`; export const BUILTIN_THEMES: InstalledTheme[] = [ + { + id: 'builtin-qui', + name: 'Qui', + version: '1.0.0', + author: 'Built-in', + description: 'Clean, modern zinc-and-blue theme inspired by autobrr/qui', + css: quiCSS, + variants: ['light', 'dark'], + enabled: true, + builtIn: true, + }, { id: 'builtin-nord', name: 'Nord', diff --git a/lib/email-headers.ts b/lib/email-headers.ts index d652cd2f..652803df 100644 --- a/lib/email-headers.ts +++ b/lib/email-headers.ts @@ -154,10 +154,10 @@ export function getSecurityStatus(result?: string): { case 'neutral': case 'temperror': return { - color: 'text-amber-700 dark:text-amber-400', + color: 'text-warning', icon: 'alert', bgColor: 'bg-gray-50 dark:bg-gray-800', - borderColor: 'border-l-4 border-amber-600 dark:border-amber-500' + borderColor: 'border-l-4 border-warning' }; default: return {