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:
+4
-1
@@ -46,4 +46,7 @@ next-env.d.ts
|
|||||||
/data/
|
/data/
|
||||||
|
|
||||||
# Local private data
|
# Local private data
|
||||||
/local-data/
|
/local-data/
|
||||||
|
|
||||||
|
# Sibling repos
|
||||||
|
/repos/
|
||||||
@@ -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.
|
||||||
@@ -733,9 +733,9 @@ export default function LoginPage() {
|
|||||||
)}
|
)}
|
||||||
</Button>
|
</Button>
|
||||||
) : oauthDiscoveryDone ? (
|
) : oauthDiscoveryDone ? (
|
||||||
<div className="p-3.5 bg-amber-500/10 border border-amber-500/20 rounded-xl flex items-start gap-2">
|
<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-amber-700 dark:text-amber-400 flex-shrink-0 mt-0.5" />
|
<AlertCircle className="w-4 h-4 text-warning flex-shrink-0 mt-0.5" />
|
||||||
<p className="text-sm text-amber-700 dark:text-amber-400">
|
<p className="text-sm text-warning">
|
||||||
{t("error.oauth_discovery_failed")}
|
{t("error.oauth_discovery_failed")}
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
@@ -941,9 +941,9 @@ export default function LoginPage() {
|
|||||||
)}
|
)}
|
||||||
|
|
||||||
{oauthEnabled && oauthDiscoveryDone && !oauthMetadata && (
|
{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">
|
<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-amber-700 dark:text-amber-400 flex-shrink-0 mt-0.5" />
|
<AlertCircle className="w-4 h-4 text-warning flex-shrink-0 mt-0.5" />
|
||||||
<p className="text-sm text-amber-700 dark:text-amber-400">
|
<p className="text-sm text-warning">
|
||||||
{t("error.oauth_discovery_failed")}
|
{t("error.oauth_discovery_failed")}
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -69,6 +69,7 @@ interface TabDef {
|
|||||||
label: string;
|
label: string;
|
||||||
icon: LucideIcon;
|
icon: LucideIcon;
|
||||||
group: TabGroup;
|
group: TabGroup;
|
||||||
|
experimental?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
const tabIcons: Record<Tab, LucideIcon> = {
|
const tabIcons: Record<Tab, LucideIcon> = {
|
||||||
@@ -162,8 +163,8 @@ export default function SettingsPage() {
|
|||||||
{ id: 'contacts', label: t('tabs.contacts'), icon: tabIcons.contacts, group: 'apps' },
|
{ 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 }] : []),
|
...(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('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('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 }] : []),
|
...(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' },
|
{ 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">
|
<span className="flex items-center gap-3">
|
||||||
<Icon className="w-4 h-4 text-muted-foreground" />
|
<Icon className="w-4 h-4 text-muted-foreground" />
|
||||||
{tab.label}
|
{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>
|
</span>
|
||||||
<ChevronRight className="w-4 h-4 text-muted-foreground" />
|
<ChevronRight className="w-4 h-4 text-muted-foreground" />
|
||||||
</button>
|
</button>
|
||||||
@@ -396,6 +402,11 @@ export default function SettingsPage() {
|
|||||||
activeTab === tab.id ? 'text-accent-foreground' : 'text-muted-foreground'
|
activeTab === tab.id ? 'text-accent-foreground' : 'text-muted-foreground'
|
||||||
)} />
|
)} />
|
||||||
{tab.label}
|
{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>
|
</button>
|
||||||
);
|
);
|
||||||
})}
|
})}
|
||||||
|
|||||||
+7
-7
@@ -105,18 +105,18 @@ export default function AdminDashboardPage() {
|
|||||||
<div className="max-w-3xl space-y-8">
|
<div className="max-w-3xl space-y-8">
|
||||||
{/* Warnings */}
|
{/* Warnings */}
|
||||||
{warnings.map((msg, i) => (
|
{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">
|
<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-amber-600 dark:text-amber-400 mt-0.5 shrink-0" />
|
<AlertTriangle className="w-5 h-5 text-warning mt-0.5 shrink-0" />
|
||||||
<p className="text-sm text-amber-700 dark:text-amber-300">{msg}</p>
|
<p className="text-sm text-warning">{msg}</p>
|
||||||
</div>
|
</div>
|
||||||
))}
|
))}
|
||||||
|
|
||||||
{status && !status.lastLogin && (
|
{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">
|
<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-amber-600 dark:text-amber-400 mt-0.5 shrink-0" />
|
<AlertTriangle className="w-5 h-5 text-warning mt-0.5 shrink-0" />
|
||||||
<div>
|
<div>
|
||||||
<p className="text-sm font-medium text-amber-800 dark:text-amber-200">First login detected</p>
|
<p className="text-sm font-medium text-warning">First login detected</p>
|
||||||
<p className="text-sm text-amber-700 dark:text-amber-300 mt-0.5">
|
<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.
|
Remember to remove ADMIN_PASSWORD from your .env file now that the hash is stored securely.
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -252,8 +252,8 @@ export default function AdminPluginsPage() {
|
|||||||
</div>
|
</div>
|
||||||
{plugin.permissions.length > 0 && (
|
{plugin.permissions.length > 0 && (
|
||||||
<div className="flex items-center gap-1 mt-1">
|
<div className="flex items-center gap-1 mt-1">
|
||||||
<AlertTriangle className="w-3 h-3 text-amber-500" />
|
<AlertTriangle className="w-3 h-3 text-warning" />
|
||||||
<span className="text-xs text-amber-600 dark:text-amber-400">
|
<span className="text-xs text-warning">
|
||||||
Permissions: {plugin.permissions.join(', ')}
|
Permissions: {plugin.permissions.join(', ')}
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
+99
-47
@@ -23,6 +23,39 @@
|
|||||||
--color-popover: #ffffff;
|
--color-popover: #ffffff;
|
||||||
--color-popover-foreground: #0f172a;
|
--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 */
|
/* Settings variables */
|
||||||
--font-size-base: 16px;
|
--font-size-base: 16px;
|
||||||
--list-item-height: 48px;
|
--list-item-height: 48px;
|
||||||
@@ -54,6 +87,39 @@
|
|||||||
--color-destructive-foreground: #fafafa;
|
--color-destructive-foreground: #fafafa;
|
||||||
--color-popover: #1c1c1c;
|
--color-popover: #1c1c1c;
|
||||||
--color-popover-foreground: #fafafa;
|
--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 {
|
@theme inline {
|
||||||
@@ -74,6 +140,27 @@
|
|||||||
--color-destructive-foreground: var(--color-destructive-foreground);
|
--color-destructive-foreground: var(--color-destructive-foreground);
|
||||||
--color-popover: var(--color-popover);
|
--color-popover: var(--color-popover);
|
||||||
--color-popover-foreground: var(--color-popover-foreground);
|
--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 {
|
.email-content blockquote {
|
||||||
border-left: 3px solid #d1d5db;
|
border-left: 3px solid var(--color-border);
|
||||||
padding-left: 1rem;
|
padding-left: 1rem;
|
||||||
margin: 1rem 0;
|
margin: 1rem 0;
|
||||||
color: #4b5563;
|
color: var(--color-muted-foreground);
|
||||||
font-style: italic;
|
font-style: italic;
|
||||||
}
|
}
|
||||||
|
|
||||||
.dark .email-content blockquote {
|
|
||||||
border-left-color: #4b5563;
|
|
||||||
color: #9ca3af;
|
|
||||||
}
|
|
||||||
|
|
||||||
.email-content pre {
|
.email-content pre {
|
||||||
background-color: #f5f5f7;
|
background-color: var(--color-muted);
|
||||||
border: 1px solid #e5e7eb;
|
border: 1px solid var(--color-border);
|
||||||
border-radius: 0.375rem;
|
border-radius: 0.375rem;
|
||||||
padding: 1rem;
|
padding: 1rem;
|
||||||
font-family: "SF Mono", Monaco, Menlo, Consolas, monospace;
|
font-family: "SF Mono", Monaco, Menlo, Consolas, monospace;
|
||||||
@@ -186,23 +268,13 @@ body {
|
|||||||
margin: 1rem 0;
|
margin: 1rem 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
.dark .email-content pre {
|
|
||||||
background-color: #1f2937;
|
|
||||||
border-color: #374151;
|
|
||||||
}
|
|
||||||
|
|
||||||
.email-content code {
|
.email-content code {
|
||||||
background-color: #f3f4f6;
|
background-color: var(--color-muted);
|
||||||
padding: 0.125rem 0.375rem;
|
padding: 0.125rem 0.375rem;
|
||||||
border-radius: 0.25rem;
|
border-radius: 0.25rem;
|
||||||
font-family: "SF Mono", Monaco, Menlo, Consolas, monospace;
|
font-family: "SF Mono", Monaco, Menlo, Consolas, monospace;
|
||||||
font-size: 0.875rem;
|
font-size: 0.875rem;
|
||||||
color: #dc2626;
|
color: var(--color-destructive);
|
||||||
}
|
|
||||||
|
|
||||||
.dark .email-content code {
|
|
||||||
background-color: #374151;
|
|
||||||
color: #f87171;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.email-content h1,
|
.email-content h1,
|
||||||
@@ -282,26 +354,15 @@ body {
|
|||||||
.email-content table[border="1"] th,
|
.email-content table[border="1"] th,
|
||||||
.email-content table[border="1"] td {
|
.email-content table[border="1"] td {
|
||||||
padding: 0.625rem;
|
padding: 0.625rem;
|
||||||
border: 1px solid #e5e7eb;
|
border: 1px solid var(--color-border);
|
||||||
text-align: left;
|
text-align: left;
|
||||||
}
|
}
|
||||||
|
|
||||||
.email-content table.data-table th,
|
.email-content table.data-table th,
|
||||||
.email-content table[border="1"] th {
|
.email-content table[border="1"] th {
|
||||||
background-color: #f9fafb;
|
background-color: var(--color-muted);
|
||||||
font-weight: 600;
|
font-weight: 600;
|
||||||
color: #374151;
|
color: var(--color-foreground);
|
||||||
}
|
|
||||||
|
|
||||||
.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;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Reset styles for layout tables (commonly used in HTML emails) */
|
/* Reset styles for layout tables (commonly used in HTML emails) */
|
||||||
@@ -323,28 +384,19 @@ body {
|
|||||||
|
|
||||||
.email-content hr {
|
.email-content hr {
|
||||||
border: none;
|
border: none;
|
||||||
border-top: 1px solid #e5e7eb;
|
border-top: 1px solid var(--color-border);
|
||||||
margin: 1.5rem 0;
|
margin: 1.5rem 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
.dark .email-content hr {
|
|
||||||
border-top-color: #374151;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Email thread quoted text */
|
/* Email thread quoted text */
|
||||||
.email-content .quoted-text {
|
.email-content .quoted-text {
|
||||||
border-left: 3px solid #d1d5db;
|
border-left: 3px solid var(--color-border);
|
||||||
padding-left: 1rem;
|
padding-left: 1rem;
|
||||||
margin: 1rem 0;
|
margin: 1rem 0;
|
||||||
color: #4b5563;
|
color: var(--color-muted-foreground);
|
||||||
opacity: 0.8;
|
opacity: 0.8;
|
||||||
}
|
}
|
||||||
|
|
||||||
.dark .email-content .quoted-text {
|
|
||||||
border-left-color: #4b5563;
|
|
||||||
color: #9ca3af;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Toast animations */
|
/* Toast animations */
|
||||||
@keyframes toast-enter {
|
@keyframes toast-enter {
|
||||||
from {
|
from {
|
||||||
|
|||||||
@@ -182,7 +182,7 @@ export function CalendarDayView({
|
|||||||
className={cn(
|
className={cn(
|
||||||
"flex-shrink-0 w-3.5 h-3.5 rounded-full border flex items-center justify-center",
|
"flex-shrink-0 w-3.5 h-3.5 rounded-full border flex items-center justify-center",
|
||||||
isCompleted
|
isCompleted
|
||||||
? "bg-green-500 border-green-500 text-white"
|
? "bg-success border-success text-success-foreground"
|
||||||
: "border-muted-foreground/40 hover:border-primary"
|
: "border-muted-foreground/40 hover:border-primary"
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
@@ -288,8 +288,8 @@ export function CalendarDayView({
|
|||||||
style={{ top: (nowMinutes / 60) * HOUR_HEIGHT }}
|
style={{ top: (nowMinutes / 60) * HOUR_HEIGHT }}
|
||||||
>
|
>
|
||||||
<div className="flex items-center">
|
<div className="flex items-center">
|
||||||
<div className="w-2.5 h-2.5 rounded-full bg-red-500 -ml-1" />
|
<div className="w-2.5 h-2.5 rounded-full bg-destructive -ml-1" />
|
||||||
<div className="flex-1 h-px bg-red-500" />
|
<div className="flex-1 h-px bg-destructive" />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|||||||
@@ -252,7 +252,7 @@ export function CalendarWeekView({
|
|||||||
>
|
>
|
||||||
<span className={cn(
|
<span className={cn(
|
||||||
"w-2.5 h-2.5 rounded-full border flex-shrink-0 flex items-center justify-center",
|
"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" />}
|
{isCompleted && <Check className="h-2 w-2 text-white" />}
|
||||||
</span>
|
</span>
|
||||||
@@ -402,8 +402,8 @@ export function CalendarWeekView({
|
|||||||
style={{ top: (nowMinutes / 60) * HOUR_HEIGHT }}
|
style={{ top: (nowMinutes / 60) * HOUR_HEIGHT }}
|
||||||
>
|
>
|
||||||
<div className="flex items-center">
|
<div className="flex items-center">
|
||||||
<div className="w-2 h-2 rounded-full bg-red-500 -ml-1" />
|
<div className="w-2 h-2 rounded-full bg-destructive -ml-1" />
|
||||||
<div className="flex-1 h-px bg-red-500" />
|
<div className="flex-1 h-px bg-destructive" />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|||||||
@@ -297,7 +297,7 @@ export function EventDetailPopover({
|
|||||||
<p className="text-xs text-muted-foreground mt-0.5 pl-[18px]">
|
<p className="text-xs text-muted-foreground mt-0.5 pl-[18px]">
|
||||||
{calendar.name}
|
{calendar.name}
|
||||||
{event.status === "tentative" && (
|
{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")}
|
{t("detail.tentative")}
|
||||||
</span>
|
</span>
|
||||||
)}
|
)}
|
||||||
@@ -507,8 +507,8 @@ export function EventDetailPopover({
|
|||||||
onClick={() => onRsvp("accepted")}
|
onClick={() => onRsvp("accepted")}
|
||||||
className={
|
className={
|
||||||
userCurrentStatus === "accepted"
|
userCurrentStatus === "accepted"
|
||||||
? "bg-green-600 hover:bg-green-700 text-white dark:bg-green-500 dark:hover:bg-green-600"
|
? "bg-success hover:bg-success/80 text-success-foreground"
|
||||||
: "text-green-600 dark:text-green-400 border-green-300 dark:border-green-700 hover:bg-green-50 dark:hover:bg-green-950"
|
: "text-success border-success/30 hover:bg-success/10"
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
{userCurrentStatus === "accepted" && <Check className="w-3.5 h-3.5 mr-1" />}
|
{userCurrentStatus === "accepted" && <Check className="w-3.5 h-3.5 mr-1" />}
|
||||||
@@ -520,8 +520,8 @@ export function EventDetailPopover({
|
|||||||
onClick={() => onRsvp("tentative")}
|
onClick={() => onRsvp("tentative")}
|
||||||
className={
|
className={
|
||||||
userCurrentStatus === "tentative"
|
userCurrentStatus === "tentative"
|
||||||
? "bg-amber-600 hover:bg-amber-700 text-white dark:bg-amber-500 dark:hover:bg-amber-600"
|
? "bg-warning hover:bg-warning/80 text-warning-foreground"
|
||||||
: "border border-amber-500 text-amber-600 hover:bg-amber-50 dark:text-amber-400 dark:hover:bg-amber-950"
|
: "border border-warning/30 text-warning hover:bg-warning/10"
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
{userCurrentStatus === "tentative" && <Check className="w-3.5 h-3.5 mr-1" />}
|
{userCurrentStatus === "tentative" && <Check className="w-3.5 h-3.5 mr-1" />}
|
||||||
@@ -533,8 +533,8 @@ export function EventDetailPopover({
|
|||||||
onClick={() => onRsvp("declined")}
|
onClick={() => onRsvp("declined")}
|
||||||
className={
|
className={
|
||||||
userCurrentStatus === "declined"
|
userCurrentStatus === "declined"
|
||||||
? "bg-red-600 hover:bg-red-700 text-white dark:bg-red-500 dark:hover:bg-red-600"
|
? "bg-destructive hover:bg-destructive/80 text-destructive-foreground"
|
||||||
: "text-red-600 hover:bg-red-50 dark:text-red-400 dark:hover:bg-red-950"
|
: "text-destructive hover:bg-destructive/10"
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
{userCurrentStatus === "declined" && <Check className="w-3.5 h-3.5 mr-1" />}
|
{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">
|
<div className="px-4 py-2.5 border-t border-border flex items-center gap-1.5">
|
||||||
{showDeleteConfirm ? (
|
{showDeleteConfirm ? (
|
||||||
<div className="flex items-center gap-2 w-full">
|
<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")}
|
{t("form.delete_confirm")}
|
||||||
</span>
|
</span>
|
||||||
<Button
|
<Button
|
||||||
@@ -611,9 +611,9 @@ function ParticipantStatusBadge({
|
|||||||
t: ReturnType<typeof useTranslations>;
|
t: ReturnType<typeof useTranslations>;
|
||||||
}) {
|
}) {
|
||||||
const colors: Record<string, string> = {
|
const colors: Record<string, string> = {
|
||||||
accepted: "text-green-600 dark:text-green-400",
|
accepted: "text-success",
|
||||||
declined: "text-red-600 dark:text-red-400",
|
declined: "text-destructive",
|
||||||
tentative: "text-amber-600 dark:text-amber-400",
|
tentative: "text-warning",
|
||||||
"needs-action": "text-muted-foreground",
|
"needs-action": "text-muted-foreground",
|
||||||
};
|
};
|
||||||
const labels: Record<string, string> = {
|
const labels: Record<string, string> = {
|
||||||
|
|||||||
@@ -530,8 +530,8 @@ export function EventModal({
|
|||||||
variant={userCurrentStatus === "accepted" ? "default" : "outline"}
|
variant={userCurrentStatus === "accepted" ? "default" : "outline"}
|
||||||
onClick={() => handleRsvp("accepted")}
|
onClick={() => handleRsvp("accepted")}
|
||||||
className={userCurrentStatus === "accepted"
|
className={userCurrentStatus === "accepted"
|
||||||
? "bg-green-600 hover:bg-green-700 text-white dark:bg-green-500 dark:hover:bg-green-600"
|
? "bg-success hover:bg-success/80 text-success-foreground"
|
||||||
: "text-green-600 dark:text-green-400 border-green-300 dark:border-green-700 hover:bg-green-50 dark:hover:bg-green-950"}
|
: "text-success border-success/30 hover:bg-success/10"}
|
||||||
>
|
>
|
||||||
{userCurrentStatus === "accepted" && <Check className="w-4 h-4 mr-1" />}
|
{userCurrentStatus === "accepted" && <Check className="w-4 h-4 mr-1" />}
|
||||||
{t("participants.accepted")}
|
{t("participants.accepted")}
|
||||||
@@ -541,8 +541,8 @@ export function EventModal({
|
|||||||
variant={userCurrentStatus === "tentative" ? "default" : "outline"}
|
variant={userCurrentStatus === "tentative" ? "default" : "outline"}
|
||||||
onClick={() => handleRsvp("tentative")}
|
onClick={() => handleRsvp("tentative")}
|
||||||
className={userCurrentStatus === "tentative"
|
className={userCurrentStatus === "tentative"
|
||||||
? "bg-amber-600 hover:bg-amber-700 text-white dark:bg-amber-500 dark:hover:bg-amber-600"
|
? "bg-warning hover:bg-warning/80 text-warning-foreground"
|
||||||
: "border border-amber-500 text-amber-600 hover:bg-amber-50 dark:text-amber-400 dark:hover:bg-amber-950"}
|
: "border border-warning/30 text-warning hover:bg-warning/10"}
|
||||||
>
|
>
|
||||||
{userCurrentStatus === "tentative" && <Check className="w-4 h-4 mr-1" />}
|
{userCurrentStatus === "tentative" && <Check className="w-4 h-4 mr-1" />}
|
||||||
{t("participants.tentative")}
|
{t("participants.tentative")}
|
||||||
@@ -552,8 +552,8 @@ export function EventModal({
|
|||||||
variant={userCurrentStatus === "declined" ? "default" : "ghost"}
|
variant={userCurrentStatus === "declined" ? "default" : "ghost"}
|
||||||
onClick={() => handleRsvp("declined")}
|
onClick={() => handleRsvp("declined")}
|
||||||
className={userCurrentStatus === "declined"
|
className={userCurrentStatus === "declined"
|
||||||
? "bg-red-600 hover:bg-red-700 text-white dark:bg-red-500 dark:hover:bg-red-600"
|
? "bg-destructive hover:bg-destructive/80 text-destructive-foreground"
|
||||||
: "text-red-600 hover:bg-red-50 dark:text-red-400 dark:hover:bg-red-950"}
|
: "text-destructive hover:bg-destructive/10"}
|
||||||
>
|
>
|
||||||
{userCurrentStatus === "declined" && <Check className="w-4 h-4 mr-1" />}
|
{userCurrentStatus === "declined" && <Check className="w-4 h-4 mr-1" />}
|
||||||
{t("participants.declined")}
|
{t("participants.declined")}
|
||||||
@@ -701,8 +701,8 @@ export function EventModal({
|
|||||||
{onDelete && (
|
{onDelete && (
|
||||||
showDeleteConfirm ? (
|
showDeleteConfirm ? (
|
||||||
<div className="flex items-center gap-2">
|
<div className="flex items-center gap-2">
|
||||||
<span className="text-sm text-red-600 dark:text-red-400">{t("form.delete_confirm")}</span>
|
<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-red-600 dark:text-red-400 border-red-300 dark:border-red-700">
|
<Button variant="outline" size="sm" onClick={() => { onDelete(event.id, hasParticipants || undefined); onClose(); }} className="text-destructive border-destructive/30">
|
||||||
{t("events.delete")}
|
{t("events.delete")}
|
||||||
</Button>
|
</Button>
|
||||||
<Button variant="ghost" size="sm" onClick={() => setShowDeleteConfirm(false)}>
|
<Button variant="ghost" size="sm" onClick={() => setShowDeleteConfirm(false)}>
|
||||||
@@ -710,7 +710,7 @@ export function EventModal({
|
|||||||
</Button>
|
</Button>
|
||||||
</div>
|
</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" />
|
<Trash2 className="w-4 h-4 mr-1" />
|
||||||
{t("events.delete")}
|
{t("events.delete")}
|
||||||
</Button>
|
</Button>
|
||||||
@@ -997,9 +997,9 @@ function StatusBadge({ status, isOrganizer, t }: {
|
|||||||
return <span className="text-xs text-primary">{t("participants.organizer")}</span>;
|
return <span className="text-xs text-primary">{t("participants.organizer")}</span>;
|
||||||
}
|
}
|
||||||
const colors: Record<string, string> = {
|
const colors: Record<string, string> = {
|
||||||
accepted: "text-green-600 dark:text-green-400",
|
accepted: "text-success",
|
||||||
declined: "text-red-600 dark:text-red-400",
|
declined: "text-destructive",
|
||||||
tentative: "text-amber-600 dark:text-amber-400",
|
tentative: "text-warning",
|
||||||
"needs-action": "text-muted-foreground",
|
"needs-action": "text-muted-foreground",
|
||||||
};
|
};
|
||||||
const labels: Record<string, string> = {
|
const labels: Record<string, string> = {
|
||||||
|
|||||||
@@ -349,7 +349,7 @@ export function ICalImportModal({ calendars, client, onClose }: ICalImportModalP
|
|||||||
)}
|
)}
|
||||||
|
|
||||||
{error && (
|
{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}
|
{error}
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|||||||
@@ -204,7 +204,7 @@ export function TaskListView({
|
|||||||
className={cn(
|
className={cn(
|
||||||
"mt-0.5 flex-shrink-0 w-5 h-5 rounded-full border-2 flex items-center justify-center transition-colors",
|
"mt-0.5 flex-shrink-0 w-5 h-5 rounded-full border-2 flex items-center justify-center transition-colors",
|
||||||
isCompleted
|
isCompleted
|
||||||
? "bg-green-500 border-green-500 text-white"
|
? "bg-success border-success text-success-foreground"
|
||||||
: "border-muted-foreground/40 hover:border-primary"
|
: "border-muted-foreground/40 hover:border-primary"
|
||||||
)}
|
)}
|
||||||
aria-label={isCompleted ? t("tasks.mark_incomplete") : t("tasks.mark_complete")}
|
aria-label={isCompleted ? t("tasks.mark_incomplete") : t("tasks.mark_complete")}
|
||||||
|
|||||||
@@ -52,7 +52,7 @@ describe('ContactListItem', () => {
|
|||||||
it('applies selected styling', () => {
|
it('applies selected styling', () => {
|
||||||
const { container } = render(<ContactListItem contact={contact} {...baseProps} isSelected={true} />);
|
const { container } = render(<ContactListItem contact={contact} {...baseProps} isSelected={true} />);
|
||||||
const div = container.firstElementChild;
|
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', () => {
|
it('shows email as display name when no name exists', () => {
|
||||||
|
|||||||
@@ -205,7 +205,7 @@ export function ContactImportDialog({
|
|||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
{isDupe && (
|
{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")}
|
{t("import.duplicate")}
|
||||||
</span>
|
</span>
|
||||||
)}
|
)}
|
||||||
|
|||||||
@@ -59,9 +59,9 @@ export function ContactListItem({ contact, isSelected, isChecked, hasSelection,
|
|||||||
className={cn(
|
className={cn(
|
||||||
"w-full flex items-center cursor-pointer select-none transition-all duration-200 border-b border-border",
|
"w-full flex items-center cursor-pointer select-none transition-all duration-200 border-b border-border",
|
||||||
isSelected
|
isSelected
|
||||||
? "bg-blue-200 dark:bg-blue-900/50 shadow-sm"
|
? "bg-selection shadow-sm"
|
||||||
: "bg-background hover:bg-muted hover: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)' }}
|
style={{ gap: 'var(--density-item-gap)', paddingInline: '16px', paddingBlock: 'var(--density-item-py)' }}
|
||||||
>
|
>
|
||||||
|
|||||||
@@ -171,11 +171,11 @@ function getParticipationLabel(
|
|||||||
function getParticipationTone(status: string | null): string {
|
function getParticipationTone(status: string | null): string {
|
||||||
switch (status) {
|
switch (status) {
|
||||||
case 'accepted':
|
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':
|
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':
|
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:
|
default:
|
||||||
return 'bg-muted text-muted-foreground';
|
return 'bg-muted text-muted-foreground';
|
||||||
}
|
}
|
||||||
@@ -731,9 +731,9 @@ export function CalendarInvitationBanner({ email }: CalendarInvitationBannerProp
|
|||||||
|
|
||||||
if (state === 'error') {
|
if (state === 'error') {
|
||||||
return (
|
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">
|
<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-red-600 dark:text-red-400 flex-shrink-0" />
|
<AlertCircle className="w-4 h-4 text-destructive flex-shrink-0" />
|
||||||
<span className="text-sm text-red-600 dark:text-red-400">{t('parse_error')}</span>
|
<span className="text-sm text-destructive">{t('parse_error')}</span>
|
||||||
</div>
|
</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 justify-between px-4 py-2.5 bg-muted/30 border-b border-border">
|
||||||
<div className="flex items-center gap-2 min-w-0">
|
<div className="flex items-center gap-2 min-w-0">
|
||||||
{isCancellation ? (
|
{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" />
|
<Calendar className="w-4 h-4 text-primary flex-shrink-0" />
|
||||||
)}
|
)}
|
||||||
@@ -808,19 +808,19 @@ export function CalendarInvitationBanner({ email }: CalendarInvitationBannerProp
|
|||||||
<span className={cn(
|
<span className={cn(
|
||||||
"rounded-full px-2 py-0.5 text-[11px] font-medium",
|
"rounded-full px-2 py-0.5 text-[11px] font-medium",
|
||||||
parsedEvent.status === 'cancelled'
|
parsedEvent.status === 'cancelled'
|
||||||
? "bg-red-100 text-red-700 dark:bg-red-900/30 dark:text-red-400"
|
? "bg-destructive/15 text-destructive"
|
||||||
: "bg-amber-100 text-amber-700 dark:bg-amber-900/30 dark:text-amber-400"
|
: "bg-warning/15 text-warning"
|
||||||
)}>
|
)}>
|
||||||
{t(`event_status_${parsedEvent.status}`)}
|
{t(`event_status_${parsedEvent.status}`)}
|
||||||
</span>
|
</span>
|
||||||
)}
|
)}
|
||||||
{existingEvent && (
|
{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')}
|
{t('already_in_calendar')}
|
||||||
</span>
|
</span>
|
||||||
)}
|
)}
|
||||||
{userIsOrganizer && (
|
{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')}
|
{t('organizer_role')}
|
||||||
</span>
|
</span>
|
||||||
)}
|
)}
|
||||||
@@ -833,7 +833,7 @@ export function CalendarInvitationBanner({ email }: CalendarInvitationBannerProp
|
|||||||
</span>
|
</span>
|
||||||
)}
|
)}
|
||||||
{actionFeedback && (
|
{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}
|
{actionFeedback}
|
||||||
</span>
|
</span>
|
||||||
)}
|
)}
|
||||||
@@ -877,8 +877,8 @@ export function CalendarInvitationBanner({ email }: CalendarInvitationBannerProp
|
|||||||
<div className={cn(
|
<div className={cn(
|
||||||
'flex items-start gap-1.5 text-xs rounded-md px-3 py-2',
|
'flex items-start gap-1.5 text-xs rounded-md px-3 py-2',
|
||||||
trustAssessment.level === 'warning'
|
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-destructive/10 text-destructive border border-destructive/20'
|
||||||
: '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-warning/10 text-warning border border-warning/20'
|
||||||
)}>
|
)}>
|
||||||
<AlertCircle className="w-3.5 h-3.5 mt-0.5 flex-shrink-0" />
|
<AlertCircle className="w-3.5 h-3.5 mt-0.5 flex-shrink-0" />
|
||||||
<span>{trustMessage}</span>
|
<span>{trustMessage}</span>
|
||||||
@@ -887,7 +887,7 @@ export function CalendarInvitationBanner({ email }: CalendarInvitationBannerProp
|
|||||||
|
|
||||||
{/* Action error */}
|
{/* Action error */}
|
||||||
{actionError && (
|
{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" />
|
<AlertCircle className="w-3.5 h-3.5 mt-0.5 flex-shrink-0" />
|
||||||
<span>{actionError}</span>
|
<span>{actionError}</span>
|
||||||
</div>
|
</div>
|
||||||
@@ -905,8 +905,8 @@ export function CalendarInvitationBanner({ email }: CalendarInvitationBannerProp
|
|||||||
className={cn(
|
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",
|
"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'
|
currentRsvp === 'accepted'
|
||||||
? "bg-green-100 dark:bg-green-900/30 text-green-700 dark:text-green-400 border-green-200 dark:border-green-800"
|
? "bg-success/15 text-success border-success/20"
|
||||||
: "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"
|
: "text-muted-foreground hover:text-success border-border hover:border-success/30 hover:bg-success/10"
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
<Check className="w-3.5 h-3.5" />
|
<Check className="w-3.5 h-3.5" />
|
||||||
@@ -919,8 +919,8 @@ export function CalendarInvitationBanner({ email }: CalendarInvitationBannerProp
|
|||||||
className={cn(
|
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",
|
"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'
|
currentRsvp === 'tentative'
|
||||||
? "bg-amber-100 dark:bg-amber-900/30 text-amber-700 dark:text-amber-400 border-amber-200 dark:border-amber-800"
|
? "bg-warning/15 text-warning border-warning/20"
|
||||||
: "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"
|
: "text-muted-foreground hover:text-warning border-border hover:border-warning/30 hover:bg-warning/10"
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
<HelpCircle className="w-3.5 h-3.5" />
|
<HelpCircle className="w-3.5 h-3.5" />
|
||||||
@@ -933,8 +933,8 @@ export function CalendarInvitationBanner({ email }: CalendarInvitationBannerProp
|
|||||||
className={cn(
|
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",
|
"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'
|
currentRsvp === 'declined'
|
||||||
? "bg-red-100 dark:bg-red-900/30 text-red-700 dark:text-red-400 border-red-200 dark:border-red-800"
|
? "bg-destructive/15 text-destructive border-destructive/20"
|
||||||
: "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"
|
: "text-muted-foreground hover:text-destructive border-border hover:border-destructive/30 hover:bg-destructive/10"
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
<X className="w-3.5 h-3.5" />
|
<X className="w-3.5 h-3.5" />
|
||||||
|
|||||||
@@ -87,15 +87,15 @@ export function EmailListItem({ email, selected, onClick, onContextMenu, onToggl
|
|||||||
// Apply color tag as background, with selected and unread states
|
// Apply color tag as background, with selected and unread states
|
||||||
colorTag ? colorTag : (
|
colorTag ? colorTag : (
|
||||||
selected
|
selected
|
||||||
? "bg-blue-200 dark:bg-blue-900/50"
|
? "bg-selection"
|
||||||
: "bg-background"
|
: "bg-background"
|
||||||
),
|
),
|
||||||
selected && !colorTag && "shadow-sm",
|
selected && !colorTag && "shadow-sm",
|
||||||
!colorTag && !selected && "hover:bg-muted hover:shadow-sm",
|
!colorTag && !selected && "hover:bg-muted hover:shadow-sm",
|
||||||
colorTag && "hover:brightness-95 dark:hover:brightness-110",
|
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
|
// 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
|
// Drag state visual feedback
|
||||||
isDragging && "opacity-50 scale-[0.98] ring-2 ring-primary/30",
|
isDragging && "opacity-50 scale-[0.98] ring-2 ring-primary/30",
|
||||||
// Long press visual feedback
|
// Long press visual feedback
|
||||||
@@ -140,7 +140,7 @@ export function EmailListItem({ email, selected, onClick, onContextMenu, onToggl
|
|||||||
{/* Unread indicator */}
|
{/* Unread indicator */}
|
||||||
{isUnread && (
|
{isUnread && (
|
||||||
<div className="absolute left-1 top-1/2 -translate-y-1/2">
|
<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>
|
</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" />
|
<Star className="w-3.5 h-3.5 fill-amber-400 text-amber-400" />
|
||||||
)}
|
)}
|
||||||
{isImportant && (
|
{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
|
Important
|
||||||
</span>
|
</span>
|
||||||
)}
|
)}
|
||||||
|
|||||||
@@ -3541,7 +3541,7 @@ export function EmailViewer({
|
|||||||
) : null;
|
) : null;
|
||||||
})()}
|
})()}
|
||||||
{isImportant && (
|
{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')}
|
{t('important')}
|
||||||
</span>
|
</span>
|
||||||
)}
|
)}
|
||||||
@@ -4372,9 +4372,9 @@ export function EmailViewer({
|
|||||||
|
|
||||||
{/* Draft Banner */}
|
{/* Draft Banner */}
|
||||||
{isDraft && (
|
{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="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" />
|
<File className="w-4 h-4" />
|
||||||
<span className="text-sm font-medium">{t('draft_banner')}</span>
|
<span className="text-sm font-medium">{t('draft_banner')}</span>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -83,10 +83,10 @@ export function SmimeStatusBanner({ status, onUnlockKey, className }: SmimeStatu
|
|||||||
if (items.length === 0) return null;
|
if (items.length === 0) return null;
|
||||||
|
|
||||||
const variantStyles = {
|
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',
|
success: 'bg-success/10 text-success border-success/30',
|
||||||
warning: 'bg-yellow-50 dark:bg-yellow-950/30 text-yellow-700 dark:text-yellow-400 border-yellow-200 dark:border-yellow-800',
|
warning: 'bg-warning/10 text-warning border-warning/30',
|
||||||
error: 'bg-red-50 dark:bg-red-950/30 text-red-700 dark:text-red-400 border-red-200 dark:border-red-800',
|
error: 'bg-destructive/10 text-destructive border-destructive/30',
|
||||||
info: 'bg-blue-50 dark:bg-blue-950/30 text-blue-700 dark:text-blue-400 border-blue-200 dark:border-blue-800',
|
info: 'bg-info/10 text-info border-info/30',
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
|||||||
@@ -85,9 +85,9 @@ export function ThreadEmailItem({
|
|||||||
"pl-12 pr-4",
|
"pl-12 pr-4",
|
||||||
"border-l-2 border-l-transparent",
|
"border-l-2 border-l-transparent",
|
||||||
selected
|
selected
|
||||||
? "bg-blue-200 dark:bg-blue-900/50 border-l-primary"
|
? "bg-selection border-l-primary"
|
||||||
: "hover:bg-muted/50",
|
: "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",
|
!isLast && "border-b border-border/30",
|
||||||
isChecked && "ring-2 ring-primary/20 bg-accent/40",
|
isChecked && "ring-2 ring-primary/20 bg-accent/40",
|
||||||
isDragging && "opacity-50 scale-[0.98] ring-2 ring-primary/30",
|
isDragging && "opacity-50 scale-[0.98] ring-2 ring-primary/30",
|
||||||
@@ -121,7 +121,7 @@ export function ThreadEmailItem({
|
|||||||
{/* Unread indicator */}
|
{/* Unread indicator */}
|
||||||
{isUnread && (
|
{isUnread && (
|
||||||
<div className="absolute left-7 top-1/2 -translate-y-1/2">
|
<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>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
|
|||||||
@@ -156,7 +156,7 @@ const SingleEmailItem = React.forwardRef<HTMLDivElement, SingleEmailItemProps>(
|
|||||||
|
|
||||||
{isUnread && (
|
{isUnread && (
|
||||||
<div className="absolute left-1 top-1/2 -translate-y-1/2">
|
<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>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
@@ -456,7 +456,7 @@ export const ThreadListItem = React.forwardRef<HTMLDivElement, ThreadListItemPro
|
|||||||
|
|
||||||
{hasUnread && (
|
{hasUnread && (
|
||||||
<div className="absolute left-1 top-1/2 -translate-y-1/2">
|
<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>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
|
|||||||
@@ -94,7 +94,7 @@ export function EmailViewerErrorFallback({ resetError, t }: FallbackProps) {
|
|||||||
export function ComposerErrorFallback({ resetError, t }: FallbackProps) {
|
export function ComposerErrorFallback({ resetError, t }: FallbackProps) {
|
||||||
return (
|
return (
|
||||||
<div className="flex flex-col h-full bg-background border rounded-lg items-center justify-center p-8">
|
<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">
|
<p className="text-sm text-muted-foreground text-center mb-4">
|
||||||
{t("composer_error")}
|
{t("composer_error")}
|
||||||
</p>
|
</p>
|
||||||
|
|||||||
@@ -299,7 +299,7 @@ export function FilterRuleModal({
|
|||||||
type="button"
|
type="button"
|
||||||
onClick={() => removeCondition(index)}
|
onClick={() => removeCondition(index)}
|
||||||
disabled={conditions.length <= 1}
|
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")}
|
aria-label={t("delete_rule")}
|
||||||
>
|
>
|
||||||
<Trash2 className="w-4 h-4" />
|
<Trash2 className="w-4 h-4" />
|
||||||
@@ -387,7 +387,7 @@ export function FilterRuleModal({
|
|||||||
type="button"
|
type="button"
|
||||||
onClick={() => removeAction(index)}
|
onClick={() => removeAction(index)}
|
||||||
disabled={actions.length <= 1}
|
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")}
|
aria-label={t("delete_rule")}
|
||||||
>
|
>
|
||||||
<Trash2 className="w-4 h-4" />
|
<Trash2 className="w-4 h-4" />
|
||||||
|
|||||||
@@ -93,7 +93,7 @@ export function SieveEditorModal({
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="px-6 py-4 flex-1 overflow-hidden flex flex-col space-y-4">
|
<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" />
|
<AlertTriangle className="w-4 h-4 mt-0.5 flex-shrink-0" />
|
||||||
<p>{t("warning")}</p>
|
<p>{t("warning")}</p>
|
||||||
</div>
|
</div>
|
||||||
@@ -131,8 +131,8 @@ export function SieveEditorModal({
|
|||||||
<div
|
<div
|
||||||
className={`flex items-start gap-2 p-3 rounded-md text-sm ${
|
className={`flex items-start gap-2 p-3 rounded-md text-sm ${
|
||||||
validationResult.isValid
|
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-success/10 border border-success/20 text-success"
|
||||||
: "bg-red-50 dark:bg-red-900/20 border border-red-200 dark:border-red-800 text-red-700 dark:text-red-400"
|
: "bg-destructive/10 border border-destructive/20 text-destructive"
|
||||||
}`}
|
}`}
|
||||||
>
|
>
|
||||||
{validationResult.isValid ? (
|
{validationResult.isValid ? (
|
||||||
@@ -157,7 +157,7 @@ export function SieveEditorModal({
|
|||||||
)}
|
)}
|
||||||
|
|
||||||
{showSaveWarning && (
|
{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" />
|
<AlertTriangle className="w-4 h-4 mt-0.5 flex-shrink-0" />
|
||||||
<p>{t("save_warning")}</p>
|
<p>{t("save_warning")}</p>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -73,10 +73,10 @@ function StorageQuotaCircle({ quota, usagePercent }: { quota: { used: number; to
|
|||||||
|
|
||||||
const free = quota.total - quota.used;
|
const free = quota.total - quota.used;
|
||||||
const strokeColor = usagePercent > 90
|
const strokeColor = usagePercent > 90
|
||||||
? "stroke-red-500 dark:stroke-red-400"
|
? "stroke-destructive"
|
||||||
: usagePercent > 70
|
: usagePercent > 70
|
||||||
? "stroke-amber-500 dark:stroke-amber-400"
|
? "stroke-warning"
|
||||||
: "stroke-green-500 dark:stroke-green-400";
|
: "stroke-success";
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="relative">
|
<div className="relative">
|
||||||
@@ -123,10 +123,10 @@ function StorageQuotaCircle({ quota, usagePercent }: { quota: { used: number; to
|
|||||||
className={cn(
|
className={cn(
|
||||||
"h-1.5 rounded-full transition-all",
|
"h-1.5 rounded-full transition-all",
|
||||||
usagePercent > 90
|
usagePercent > 90
|
||||||
? "bg-red-500 dark:bg-red-400"
|
? "bg-destructive"
|
||||||
: usagePercent > 70
|
: usagePercent > 70
|
||||||
? "bg-amber-500 dark:bg-amber-400"
|
? "bg-warning"
|
||||||
: "bg-green-500 dark:bg-green-400"
|
: "bg-success"
|
||||||
)}
|
)}
|
||||||
style={{ width: `${usagePercent}%` }}
|
style={{ width: `${usagePercent}%` }}
|
||||||
/>
|
/>
|
||||||
|
|||||||
@@ -68,7 +68,7 @@ export function PluginsSettings() {
|
|||||||
};
|
};
|
||||||
|
|
||||||
return (
|
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 */}
|
{/* Plugin List */}
|
||||||
{plugins.length === 0 ? (
|
{plugins.length === 0 ? (
|
||||||
<div className="flex flex-col items-center justify-center py-12 text-center">
|
<div className="flex flex-col items-center justify-center py-12 text-center">
|
||||||
|
|||||||
@@ -1,16 +1,29 @@
|
|||||||
import { ReactNode } from 'react';
|
import { ReactNode } from 'react';
|
||||||
import { Lock } from 'lucide-react';
|
import { Lock, FlaskConical } from 'lucide-react';
|
||||||
import { cn } from '@/lib/utils';
|
import { cn } from '@/lib/utils';
|
||||||
|
|
||||||
interface SettingsSectionProps {
|
interface SettingsSectionProps {
|
||||||
title: string;
|
title: string;
|
||||||
description?: string;
|
description?: string;
|
||||||
children: ReactNode;
|
children: ReactNode;
|
||||||
|
experimental?: boolean;
|
||||||
|
experimentalDescription?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function SettingsSection({ title, description, children }: SettingsSectionProps) {
|
export function SettingsSection({ title, description, children, experimental, experimentalDescription }: SettingsSectionProps) {
|
||||||
return (
|
return (
|
||||||
<div className="space-y-4">
|
<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>
|
<div>
|
||||||
<h3 className="text-lg font-medium text-foreground">{title}</h3>
|
<h3 className="text-lg font-medium text-foreground">{title}</h3>
|
||||||
{description && (
|
{description && (
|
||||||
|
|||||||
@@ -69,7 +69,8 @@ export function ThemesSettings() {
|
|||||||
};
|
};
|
||||||
|
|
||||||
return (
|
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 */}
|
{/* Theme Grid */}
|
||||||
<div className="grid grid-cols-2 sm:grid-cols-3 gap-3">
|
<div className="grid grid-cols-2 sm:grid-cols-3 gap-3">
|
||||||
{/* Default theme card */}
|
{/* Default theme card */}
|
||||||
|
|||||||
@@ -240,7 +240,7 @@ export function VacationSettings() {
|
|||||||
{validationWarnings.length > 0 && (
|
{validationWarnings.length > 0 && (
|
||||||
<div className="space-y-2">
|
<div className="space-y-2">
|
||||||
{validationWarnings.map((warning, i) => (
|
{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" />
|
<AlertTriangle className="w-4 h-4 mt-0.5 flex-shrink-0" />
|
||||||
<span>{warning}</span>
|
<span>{warning}</span>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -36,17 +36,17 @@ const icons = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const iconContainerStyles = {
|
const iconContainerStyles = {
|
||||||
success: "bg-emerald-500 text-white",
|
success: "bg-success text-success-foreground",
|
||||||
error: "bg-red-500 text-white",
|
error: "bg-destructive text-destructive-foreground",
|
||||||
info: "bg-blue-500 text-white",
|
info: "bg-info text-info-foreground",
|
||||||
warning: "bg-amber-500 text-white",
|
warning: "bg-warning text-warning-foreground",
|
||||||
};
|
};
|
||||||
|
|
||||||
const progressBarStyles = {
|
const progressBarStyles = {
|
||||||
success: "bg-emerald-500",
|
success: "bg-success",
|
||||||
error: "bg-red-500",
|
error: "bg-destructive",
|
||||||
info: "bg-blue-500",
|
info: "bg-info",
|
||||||
warning: "bg-amber-500",
|
warning: "bg-warning",
|
||||||
};
|
};
|
||||||
|
|
||||||
export function ToastItem({ toast, onClose }: ToastProps) {
|
export function ToastItem({ toast, onClose }: ToastProps) {
|
||||||
|
|||||||
@@ -1,5 +1,87 @@
|
|||||||
import type { InstalledTheme } from './plugin-types';
|
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 = `
|
const nordCSS = `
|
||||||
:root {
|
:root {
|
||||||
--color-border: #d8dee9;
|
--color-border: #d8dee9;
|
||||||
@@ -19,6 +101,27 @@ const nordCSS = `
|
|||||||
--color-destructive-foreground: #eceff4;
|
--color-destructive-foreground: #eceff4;
|
||||||
--color-popover: #eceff4;
|
--color-popover: #eceff4;
|
||||||
--color-popover-foreground: #2e3440;
|
--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 {
|
.dark {
|
||||||
--color-border: #3b4252;
|
--color-border: #3b4252;
|
||||||
@@ -38,6 +141,27 @@ const nordCSS = `
|
|||||||
--color-destructive-foreground: #eceff4;
|
--color-destructive-foreground: #eceff4;
|
||||||
--color-popover: #3b4252;
|
--color-popover: #3b4252;
|
||||||
--color-popover-foreground: #eceff4;
|
--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 = `
|
const catppuccinCSS = `
|
||||||
@@ -59,6 +183,27 @@ const catppuccinCSS = `
|
|||||||
--color-destructive-foreground: #eff1f5;
|
--color-destructive-foreground: #eff1f5;
|
||||||
--color-popover: #eff1f5;
|
--color-popover: #eff1f5;
|
||||||
--color-popover-foreground: #4c4f69;
|
--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 {
|
.dark {
|
||||||
--color-border: #45475a;
|
--color-border: #45475a;
|
||||||
@@ -78,6 +223,27 @@ const catppuccinCSS = `
|
|||||||
--color-destructive-foreground: #1e1e2e;
|
--color-destructive-foreground: #1e1e2e;
|
||||||
--color-popover: #313244;
|
--color-popover: #313244;
|
||||||
--color-popover-foreground: #cdd6f4;
|
--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 = `
|
const solarizedCSS = `
|
||||||
@@ -99,6 +265,27 @@ const solarizedCSS = `
|
|||||||
--color-destructive-foreground: #fdf6e3;
|
--color-destructive-foreground: #fdf6e3;
|
||||||
--color-popover: #fdf6e3;
|
--color-popover: #fdf6e3;
|
||||||
--color-popover-foreground: #657b83;
|
--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 {
|
.dark {
|
||||||
--color-border: #073642;
|
--color-border: #073642;
|
||||||
@@ -118,9 +305,41 @@ const solarizedCSS = `
|
|||||||
--color-destructive-foreground: #fdf6e3;
|
--color-destructive-foreground: #fdf6e3;
|
||||||
--color-popover: #073642;
|
--color-popover: #073642;
|
||||||
--color-popover-foreground: #93a1a1;
|
--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[] = [
|
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',
|
id: 'builtin-nord',
|
||||||
name: 'Nord',
|
name: 'Nord',
|
||||||
|
|||||||
@@ -154,10 +154,10 @@ export function getSecurityStatus(result?: string): {
|
|||||||
case 'neutral':
|
case 'neutral':
|
||||||
case 'temperror':
|
case 'temperror':
|
||||||
return {
|
return {
|
||||||
color: 'text-amber-700 dark:text-amber-400',
|
color: 'text-warning',
|
||||||
icon: 'alert',
|
icon: 'alert',
|
||||||
bgColor: 'bg-gray-50 dark:bg-gray-800',
|
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:
|
default:
|
||||||
return {
|
return {
|
||||||
|
|||||||
Reference in New Issue
Block a user