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.
This commit is contained in:
Linus Rath
2026-03-25 23:10:47 +01:00
parent f64306be5e
commit b5172802bb
33 changed files with 481 additions and 166 deletions
+4 -1
View File
@@ -46,4 +46,7 @@ next-env.d.ts
/data/
# Local private data
/local-data/
/local-data/
# Sibling repos
/repos/
+16
View File
@@ -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.
+6 -6
View File
@@ -733,9 +733,9 @@ export default function LoginPage() {
)}
</Button>
) : oauthDiscoveryDone ? (
<div className="p-3.5 bg-amber-500/10 border border-amber-500/20 rounded-xl flex items-start gap-2">
<AlertCircle className="w-4 h-4 text-amber-700 dark:text-amber-400 flex-shrink-0 mt-0.5" />
<p className="text-sm text-amber-700 dark:text-amber-400">
<div className="p-3.5 bg-warning/10 border border-warning/20 rounded-xl flex items-start gap-2">
<AlertCircle className="w-4 h-4 text-warning flex-shrink-0 mt-0.5" />
<p className="text-sm text-warning">
{t("error.oauth_discovery_failed")}
</p>
</div>
@@ -941,9 +941,9 @@ export default function LoginPage() {
)}
{oauthEnabled && oauthDiscoveryDone && !oauthMetadata && (
<div className="mt-2 p-3 bg-amber-500/10 border border-amber-500/20 rounded-xl flex items-start gap-2">
<AlertCircle className="w-4 h-4 text-amber-700 dark:text-amber-400 flex-shrink-0 mt-0.5" />
<p className="text-sm text-amber-700 dark:text-amber-400">
<div className="mt-2 p-3 bg-warning/10 border border-warning/20 rounded-xl flex items-start gap-2">
<AlertCircle className="w-4 h-4 text-warning flex-shrink-0 mt-0.5" />
<p className="text-sm text-warning">
{t("error.oauth_discovery_failed")}
</p>
</div>
+13 -2
View File
@@ -69,6 +69,7 @@ interface TabDef {
label: string;
icon: LucideIcon;
group: TabGroup;
experimental?: boolean;
}
const tabIcons: Record<Tab, LucideIcon> = {
@@ -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() {
<span className="flex items-center gap-3">
<Icon className="w-4 h-4 text-muted-foreground" />
{tab.label}
{tab.experimental && (
<span className="text-[10px] font-medium px-1.5 py-0.5 rounded-full bg-warning/15 text-warning">
Experimental
</span>
)}
</span>
<ChevronRight className="w-4 h-4 text-muted-foreground" />
</button>
@@ -396,6 +402,11 @@ export default function SettingsPage() {
activeTab === tab.id ? 'text-accent-foreground' : 'text-muted-foreground'
)} />
{tab.label}
{tab.experimental && (
<span className="ml-auto text-[10px] font-medium px-1.5 py-0.5 rounded-full bg-warning/15 text-warning shrink-0">
Experimental
</span>
)}
</button>
);
})}
+7 -7
View File
@@ -105,18 +105,18 @@ export default function AdminDashboardPage() {
<div className="max-w-3xl space-y-8">
{/* Warnings */}
{warnings.map((msg, i) => (
<div key={i} className="flex items-start gap-3 rounded-lg border border-amber-200 bg-amber-50 dark:border-amber-900 dark:bg-amber-950/30 p-4">
<AlertTriangle className="w-5 h-5 text-amber-600 dark:text-amber-400 mt-0.5 shrink-0" />
<p className="text-sm text-amber-700 dark:text-amber-300">{msg}</p>
<div key={i} className="flex items-start gap-3 rounded-lg border border-warning/20 bg-warning/10 p-4">
<AlertTriangle className="w-5 h-5 text-warning mt-0.5 shrink-0" />
<p className="text-sm text-warning">{msg}</p>
</div>
))}
{status && !status.lastLogin && (
<div className="flex items-start gap-3 rounded-lg border border-amber-200 bg-amber-50 dark:border-amber-900 dark:bg-amber-950/30 p-4">
<AlertTriangle className="w-5 h-5 text-amber-600 dark:text-amber-400 mt-0.5 shrink-0" />
<div className="flex items-start gap-3 rounded-lg border border-warning/20 bg-warning/10 p-4">
<AlertTriangle className="w-5 h-5 text-warning mt-0.5 shrink-0" />
<div>
<p className="text-sm font-medium text-amber-800 dark:text-amber-200">First login detected</p>
<p className="text-sm text-amber-700 dark:text-amber-300 mt-0.5">
<p className="text-sm font-medium text-warning">First login detected</p>
<p className="text-sm text-warning/80 mt-0.5">
Remember to remove ADMIN_PASSWORD from your .env file now that the hash is stored securely.
</p>
</div>
+2 -2
View File
@@ -252,8 +252,8 @@ export default function AdminPluginsPage() {
</div>
{plugin.permissions.length > 0 && (
<div className="flex items-center gap-1 mt-1">
<AlertTriangle className="w-3 h-3 text-amber-500" />
<span className="text-xs text-amber-600 dark:text-amber-400">
<AlertTriangle className="w-3 h-3 text-warning" />
<span className="text-xs text-warning">
Permissions: {plugin.permissions.join(', ')}
</span>
</div>
+99 -47
View File
@@ -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 {
+3 -3
View File
@@ -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 }}
>
<div className="flex items-center">
<div className="w-2.5 h-2.5 rounded-full bg-red-500 -ml-1" />
<div className="flex-1 h-px bg-red-500" />
<div className="w-2.5 h-2.5 rounded-full bg-destructive -ml-1" />
<div className="flex-1 h-px bg-destructive" />
</div>
</div>
)}
+3 -3
View File
@@ -252,7 +252,7 @@ export function CalendarWeekView({
>
<span className={cn(
"w-2.5 h-2.5 rounded-full border flex-shrink-0 flex items-center justify-center",
isCompleted ? "bg-green-500 border-green-500" : "border-current"
isCompleted ? "bg-success border-success" : "border-current"
)}>
{isCompleted && <Check className="h-2 w-2 text-white" />}
</span>
@@ -402,8 +402,8 @@ export function CalendarWeekView({
style={{ top: (nowMinutes / 60) * HOUR_HEIGHT }}
>
<div className="flex items-center">
<div className="w-2 h-2 rounded-full bg-red-500 -ml-1" />
<div className="flex-1 h-px bg-red-500" />
<div className="w-2 h-2 rounded-full bg-destructive -ml-1" />
<div className="flex-1 h-px bg-destructive" />
</div>
</div>
)}
+11 -11
View File
@@ -297,7 +297,7 @@ export function EventDetailPopover({
<p className="text-xs text-muted-foreground mt-0.5 pl-[18px]">
{calendar.name}
{event.status === "tentative" && (
<span className="ml-2 inline-flex items-center px-1.5 py-0.5 rounded text-[10px] font-medium bg-amber-100 text-amber-800 dark:bg-amber-900/30 dark:text-amber-400">
<span className="ml-2 inline-flex items-center px-1.5 py-0.5 rounded text-[10px] font-medium bg-warning/15 text-warning">
{t("detail.tentative")}
</span>
)}
@@ -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" && <Check className="w-3.5 h-3.5 mr-1" />}
@@ -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" && <Check className="w-3.5 h-3.5 mr-1" />}
@@ -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" && <Check className="w-3.5 h-3.5 mr-1" />}
@@ -548,7 +548,7 @@ export function EventDetailPopover({
<div className="px-4 py-2.5 border-t border-border flex items-center gap-1.5">
{showDeleteConfirm ? (
<div className="flex items-center gap-2 w-full">
<span className="text-sm text-red-600 dark:text-red-400 flex-1">
<span className="text-sm text-destructive flex-1">
{t("form.delete_confirm")}
</span>
<Button
@@ -611,9 +611,9 @@ function ParticipantStatusBadge({
t: ReturnType<typeof useTranslations>;
}) {
const colors: Record<string, string> = {
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<string, string> = {
+12 -12
View File
@@ -530,8 +530,8 @@ export function EventModal({
variant={userCurrentStatus === "accepted" ? "default" : "outline"}
onClick={() => handleRsvp("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" && <Check className="w-4 h-4 mr-1" />}
{t("participants.accepted")}
@@ -541,8 +541,8 @@ export function EventModal({
variant={userCurrentStatus === "tentative" ? "default" : "outline"}
onClick={() => handleRsvp("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" && <Check className="w-4 h-4 mr-1" />}
{t("participants.tentative")}
@@ -552,8 +552,8 @@ export function EventModal({
variant={userCurrentStatus === "declined" ? "default" : "ghost"}
onClick={() => handleRsvp("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" && <Check className="w-4 h-4 mr-1" />}
{t("participants.declined")}
@@ -701,8 +701,8 @@ export function EventModal({
{onDelete && (
showDeleteConfirm ? (
<div className="flex items-center gap-2">
<span className="text-sm text-red-600 dark:text-red-400">{t("form.delete_confirm")}</span>
<Button variant="outline" size="sm" onClick={() => { onDelete(event.id, hasParticipants || undefined); onClose(); }} className="text-red-600 dark:text-red-400 border-red-300 dark:border-red-700">
<span className="text-sm text-destructive">{t("form.delete_confirm")}</span>
<Button variant="outline" size="sm" onClick={() => { onDelete(event.id, hasParticipants || undefined); onClose(); }} className="text-destructive border-destructive/30">
{t("events.delete")}
</Button>
<Button variant="ghost" size="sm" onClick={() => setShowDeleteConfirm(false)}>
@@ -710,7 +710,7 @@ export function EventModal({
</Button>
</div>
) : (
<Button variant="ghost" size="sm" onClick={() => setShowDeleteConfirm(true)} className="text-red-600 dark:text-red-400">
<Button variant="ghost" size="sm" onClick={() => setShowDeleteConfirm(true)} className="text-destructive">
<Trash2 className="w-4 h-4 mr-1" />
{t("events.delete")}
</Button>
@@ -997,9 +997,9 @@ function StatusBadge({ status, isOrganizer, t }: {
return <span className="text-xs text-primary">{t("participants.organizer")}</span>;
}
const colors: Record<string, string> = {
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<string, string> = {
+1 -1
View File
@@ -349,7 +349,7 @@ export function ICalImportModal({ calendars, client, onClose }: ICalImportModalP
)}
{error && (
<div className="text-sm text-red-600 dark:text-red-400 bg-red-50 dark:bg-red-950/30 rounded-md px-3 py-2">
<div className="text-sm text-destructive bg-destructive/10 rounded-md px-3 py-2">
{error}
</div>
)}
+1 -1
View File
@@ -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")}
@@ -52,7 +52,7 @@ describe('ContactListItem', () => {
it('applies selected styling', () => {
const { container } = render(<ContactListItem contact={contact} {...baseProps} isSelected={true} />);
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', () => {
@@ -205,7 +205,7 @@ export function ContactImportDialog({
)}
</div>
{isDupe && (
<span className="text-xs px-1.5 py-0.5 rounded bg-amber-100 dark:bg-amber-900 text-amber-700 dark:text-amber-400 flex-shrink-0">
<span className="text-xs px-1.5 py-0.5 rounded bg-warning/15 text-warning flex-shrink-0">
{t("import.duplicate")}
</span>
)}
+2 -2
View File
@@ -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)' }}
>
+21 -21
View File
@@ -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 (
<div className="rounded-lg border border-red-200 dark:border-red-900/50 bg-red-50/50 dark:bg-red-950/20 px-4 py-3 flex items-center gap-2.5">
<AlertCircle className="w-4 h-4 text-red-600 dark:text-red-400 flex-shrink-0" />
<span className="text-sm text-red-600 dark:text-red-400">{t('parse_error')}</span>
<div className="rounded-lg border border-destructive/20 bg-destructive/10 px-4 py-3 flex items-center gap-2.5">
<AlertCircle className="w-4 h-4 text-destructive flex-shrink-0" />
<span className="text-sm text-destructive">{t('parse_error')}</span>
</div>
);
}
@@ -744,7 +744,7 @@ export function CalendarInvitationBanner({ email }: CalendarInvitationBannerProp
<div className="flex items-center justify-between px-4 py-2.5 bg-muted/30 border-b border-border">
<div className="flex items-center gap-2 min-w-0">
{isCancellation ? (
<CalendarX className="w-4 h-4 text-red-600 dark:text-red-400 flex-shrink-0" />
<CalendarX className="w-4 h-4 text-destructive flex-shrink-0" />
) : (
<Calendar className="w-4 h-4 text-primary flex-shrink-0" />
)}
@@ -808,19 +808,19 @@ export function CalendarInvitationBanner({ email }: CalendarInvitationBannerProp
<span className={cn(
"rounded-full px-2 py-0.5 text-[11px] font-medium",
parsedEvent.status === 'cancelled'
? "bg-red-100 text-red-700 dark:bg-red-900/30 dark:text-red-400"
: "bg-amber-100 text-amber-700 dark:bg-amber-900/30 dark:text-amber-400"
? "bg-destructive/15 text-destructive"
: "bg-warning/15 text-warning"
)}>
{t(`event_status_${parsedEvent.status}`)}
</span>
)}
{existingEvent && (
<span className="rounded-full bg-green-100 px-2 py-0.5 text-[11px] font-medium text-green-700 dark:bg-green-900/30 dark:text-green-400">
<span className="rounded-full bg-success/15 px-2 py-0.5 text-[11px] font-medium text-success">
{t('already_in_calendar')}
</span>
)}
{userIsOrganizer && (
<span className="rounded-full bg-blue-100 px-2 py-0.5 text-[11px] font-medium text-blue-700 dark:bg-blue-900/30 dark:text-blue-400">
<span className="rounded-full bg-info/15 px-2 py-0.5 text-[11px] font-medium text-info">
{t('organizer_role')}
</span>
)}
@@ -833,7 +833,7 @@ export function CalendarInvitationBanner({ email }: CalendarInvitationBannerProp
</span>
)}
{actionFeedback && (
<span className="rounded-full bg-green-100 px-2 py-0.5 text-[11px] font-medium text-green-700 dark:bg-green-900/30 dark:text-green-400">
<span className="rounded-full bg-success/15 px-2 py-0.5 text-[11px] font-medium text-success">
{actionFeedback}
</span>
)}
@@ -877,8 +877,8 @@ export function CalendarInvitationBanner({ email }: CalendarInvitationBannerProp
<div className={cn(
'flex items-start gap-1.5 text-xs rounded-md px-3 py-2',
trustAssessment.level === 'warning'
? 'bg-red-50 dark:bg-red-950/20 text-red-700 dark:text-red-400 border border-red-200 dark:border-red-900/50'
: 'bg-amber-50 dark:bg-amber-950/20 text-amber-700 dark:text-amber-400 border border-amber-200 dark:border-amber-900/50'
? 'bg-destructive/10 text-destructive border border-destructive/20'
: 'bg-warning/10 text-warning border border-warning/20'
)}>
<AlertCircle className="w-3.5 h-3.5 mt-0.5 flex-shrink-0" />
<span>{trustMessage}</span>
@@ -887,7 +887,7 @@ export function CalendarInvitationBanner({ email }: CalendarInvitationBannerProp
{/* Action error */}
{actionError && (
<div className="flex items-start gap-1.5 text-xs text-red-600 dark:text-red-400 rounded-md px-3 py-2 bg-red-50 dark:bg-red-950/20 border border-red-200 dark:border-red-900/50">
<div className="flex items-start gap-1.5 text-xs text-destructive rounded-md px-3 py-2 bg-destructive/10 border border-destructive/20">
<AlertCircle className="w-3.5 h-3.5 mt-0.5 flex-shrink-0" />
<span>{actionError}</span>
</div>
@@ -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"
)}
>
<Check className="w-3.5 h-3.5" />
@@ -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"
)}
>
<HelpCircle className="w-3.5 h-3.5" />
@@ -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"
)}
>
<X className="w-3.5 h-3.5" />
+5 -5
View File
@@ -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 && (
<div className="absolute left-1 top-1/2 -translate-y-1/2">
<Circle className="w-2 h-2 fill-blue-600 text-blue-600 dark:fill-blue-400 dark:text-blue-400" />
<Circle className="w-2 h-2 fill-unread text-unread" />
</div>
)}
@@ -172,7 +172,7 @@ export function EmailListItem({ email, selected, onClick, onContextMenu, onToggl
<Star className="w-3.5 h-3.5 fill-amber-400 text-amber-400" />
)}
{isImportant && (
<span className="px-1.5 py-0.5 text-xs bg-amber-100 dark:bg-amber-900/30 text-amber-700 dark:text-amber-400 rounded font-medium">
<span className="px-1.5 py-0.5 text-xs bg-warning/15 text-warning dark:text-warning rounded font-medium">
Important
</span>
)}
+3 -3
View File
@@ -3541,7 +3541,7 @@ export function EmailViewer({
) : null;
})()}
{isImportant && (
<span className="px-1.5 lg:px-2 py-0.5 bg-amber-50 dark:bg-amber-900/30 text-amber-700 dark:text-amber-400 rounded-full text-xs font-medium whitespace-nowrap flex-shrink-0 self-center">
<span className="px-1.5 lg:px-2 py-0.5 bg-warning/15 text-warning rounded-full text-xs font-medium whitespace-nowrap flex-shrink-0 self-center">
{t('important')}
</span>
)}
@@ -4372,9 +4372,9 @@ export function EmailViewer({
{/* Draft Banner */}
{isDraft && (
<div className="border-b border-border bg-amber-50 dark:bg-amber-950/30">
<div className="border-b border-border bg-warning/10">
<div className="max-w-4xl mx-auto px-6 py-2.5 flex items-center justify-between">
<div className="flex items-center gap-2 text-amber-700 dark:text-amber-400">
<div className="flex items-center gap-2 text-warning">
<File className="w-4 h-4" />
<span className="text-sm font-medium">{t('draft_banner')}</span>
</div>
+4 -4
View File
@@ -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 (
+3 -3
View File
@@ -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 && (
<div className="absolute left-7 top-1/2 -translate-y-1/2">
<Circle className="w-1.5 h-1.5 fill-blue-600 text-blue-600 dark:fill-blue-400 dark:text-blue-400" />
<Circle className="w-1.5 h-1.5 fill-unread text-unread" />
</div>
)}
+2 -2
View File
@@ -156,7 +156,7 @@ const SingleEmailItem = React.forwardRef<HTMLDivElement, SingleEmailItemProps>(
{isUnread && (
<div className="absolute left-1 top-1/2 -translate-y-1/2">
<Circle className="w-2 h-2 fill-blue-600 text-blue-600 dark:fill-blue-400 dark:text-blue-400" />
<Circle className="w-2 h-2 fill-unread text-unread" />
</div>
)}
@@ -456,7 +456,7 @@ export const ThreadListItem = React.forwardRef<HTMLDivElement, ThreadListItemPro
{hasUnread && (
<div className="absolute left-1 top-1/2 -translate-y-1/2">
<Circle className="w-2 h-2 fill-blue-600 text-blue-600 dark:fill-blue-400 dark:text-blue-400" />
<Circle className="w-2 h-2 fill-unread text-unread" />
</div>
)}
+1 -1
View File
@@ -94,7 +94,7 @@ export function EmailViewerErrorFallback({ resetError, t }: FallbackProps) {
export function ComposerErrorFallback({ resetError, t }: FallbackProps) {
return (
<div className="flex flex-col h-full bg-background border rounded-lg items-center justify-center p-8">
<AlertCircle className="w-10 h-10 text-amber-500 mb-3" />
<AlertCircle className="w-10 h-10 text-warning mb-3" />
<p className="text-sm text-muted-foreground text-center mb-4">
{t("composer_error")}
</p>
+2 -2
View File
@@ -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")}
>
<Trash2 className="w-4 h-4" />
@@ -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")}
>
<Trash2 className="w-4 h-4" />
+4 -4
View File
@@ -93,7 +93,7 @@ export function SieveEditorModal({
</div>
<div className="px-6 py-4 flex-1 overflow-hidden flex flex-col space-y-4">
<div className="flex items-start gap-2 p-3 rounded-md bg-amber-50 dark:bg-amber-900/20 border border-amber-200 dark:border-amber-800 text-sm text-amber-700 dark:text-amber-400">
<div className="flex items-start gap-2 p-3 rounded-md bg-warning/10 border border-warning/20 text-sm text-warning">
<AlertTriangle className="w-4 h-4 mt-0.5 flex-shrink-0" />
<p>{t("warning")}</p>
</div>
@@ -131,8 +131,8 @@ export function SieveEditorModal({
<div
className={`flex items-start gap-2 p-3 rounded-md text-sm ${
validationResult.isValid
? "bg-green-50 dark:bg-green-900/20 border border-green-200 dark:border-green-800 text-green-700 dark:text-green-400"
: "bg-red-50 dark:bg-red-900/20 border border-red-200 dark:border-red-800 text-red-700 dark:text-red-400"
? "bg-success/10 border border-success/20 text-success"
: "bg-destructive/10 border border-destructive/20 text-destructive"
}`}
>
{validationResult.isValid ? (
@@ -157,7 +157,7 @@ export function SieveEditorModal({
)}
{showSaveWarning && (
<div className="flex items-start gap-2 p-3 rounded-md bg-amber-50 dark:bg-amber-900/20 border border-amber-200 dark:border-amber-800 text-sm text-amber-700 dark:text-amber-400">
<div className="flex items-start gap-2 p-3 rounded-md bg-warning/10 border border-warning/20 text-sm text-warning">
<AlertTriangle className="w-4 h-4 mt-0.5 flex-shrink-0" />
<p>{t("save_warning")}</p>
</div>
+6 -6
View File
@@ -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 (
<div className="relative">
@@ -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}%` }}
/>
+1 -1
View File
@@ -68,7 +68,7 @@ export function PluginsSettings() {
};
return (
<SettingsSection title="Plugins" description="Manage installed plugins. Upload plugin .zip files to add new functionality.">
<SettingsSection title="Plugins" description="Manage installed plugins. Upload plugin .zip files to add new functionality." experimental experimentalDescription="Plugins is an experimental feature. The plugin API is not yet stable and may change between releases, which could break existing plugins. Plugins run in a sandboxed environment but have access to your data within the application. Only install plugins from sources you trust.">
{/* Plugin List */}
{plugins.length === 0 ? (
<div className="flex flex-col items-center justify-center py-12 text-center">
+15 -2
View File
@@ -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 (
<div className="space-y-4">
{experimental && (
<div className="flex gap-3 rounded-lg border border-amber-300 bg-amber-50 p-3 dark:border-amber-700 dark:bg-amber-950/30">
<FlaskConical className="w-5 h-5 text-amber-600 dark:text-amber-400 mt-0.5 shrink-0" />
<div>
<p className="text-sm font-medium text-amber-800 dark:text-amber-300">Experimental Feature</p>
{experimentalDescription && (
<p className="text-xs text-amber-700 dark:text-amber-400/80 mt-1">{experimentalDescription}</p>
)}
</div>
</div>
)}
<div>
<h3 className="text-lg font-medium text-foreground">{title}</h3>
{description && (
+2 -1
View File
@@ -69,7 +69,8 @@ export function ThemesSettings() {
};
return (
<SettingsSection title="Themes" description="Customize the appearance with color themes. Upload .zip theme files or activate built-in presets.">
<SettingsSection title="Themes" description="Customize the appearance with color themes. Upload .zip theme files or activate built-in presets." experimental experimentalDescription="Themes is an experimental feature. Custom themes may not cover all UI elements, and theme formats could change in future updates. Built-in presets are stable, but uploaded themes may require updates after application upgrades.">
{/* Theme Grid */}
<div className="grid grid-cols-2 sm:grid-cols-3 gap-3">
{/* Default theme card */}
+1 -1
View File
@@ -240,7 +240,7 @@ export function VacationSettings() {
{validationWarnings.length > 0 && (
<div className="space-y-2">
{validationWarnings.map((warning, i) => (
<div key={i} className="flex items-start gap-2 text-sm text-amber-700 dark:text-amber-400">
<div key={i} className="flex items-start gap-2 text-sm text-warning">
<AlertTriangle className="w-4 h-4 mt-0.5 flex-shrink-0" />
<span>{warning}</span>
</div>
+8 -8
View File
@@ -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) {
+219
View File
@@ -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',
+2 -2
View File
@@ -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 {