diff --git a/src/renderer/components/Editor/Editor.tsx b/src/renderer/components/Editor/Editor.tsx index b6d44db..c9b1891 100644 --- a/src/renderer/components/Editor/Editor.tsx +++ b/src/renderer/components/Editor/Editor.tsx @@ -14,7 +14,7 @@ import { TagInput } from '../TagInput'; import { ChatPanel } from '../ChatPanel'; import { ImportAnalysisView } from '../ImportAnalysisView'; import { MetadataDiffPanel } from '../MetadataDiffPanel'; -import { AutoSaveManager } from '../../utils'; +import { AutoSaveManager, getContrastColor } from '../../utils'; import { parseMacros, getMacro } from '../../macros/registry'; import { InsertModal } from '../InsertModal'; import { AISuggestionsModal, AISuggestions } from '../AISuggestionsModal/AISuggestionsModal'; @@ -2070,23 +2070,6 @@ interface TagDataWithColor { color?: string; } -// Get contrasting text color for background -const getContrastColor = (hex: string): string => { - const color = hex.replace('#', ''); - let r: number, g: number, b: number; - if (color.length === 3) { - r = parseInt(color[0] + color[0], 16); - g = parseInt(color[1] + color[1], 16); - b = parseInt(color[2] + color[2], 16); - } else { - r = parseInt(color.substring(0, 2), 16); - g = parseInt(color.substring(2, 4), 16); - b = parseInt(color.substring(4, 6), 16); - } - const luminance = (0.299 * r + 0.587 * g + 0.114 * b) / 255; - return luminance > 0.5 ? '#000000' : '#ffffff'; -}; - const Dashboard: React.FC = () => { const { posts, media } = useAppStore(); const [stats, setStats] = useState(null); diff --git a/src/renderer/components/Sidebar/Sidebar.tsx b/src/renderer/components/Sidebar/Sidebar.tsx index 63149a2..3211589 100644 --- a/src/renderer/components/Sidebar/Sidebar.tsx +++ b/src/renderer/components/Sidebar/Sidebar.tsx @@ -1,7 +1,7 @@ import React, { useState, useEffect, useCallback, useMemo, useRef } from 'react'; import { useAppStore, PostData, MediaData } from '../../store'; import { showToast } from '../Toast'; -import { groupPostsByStatus } from '../../utils'; +import { getContrastColor, groupPostsByStatus } from '../../utils'; import type { ChatConversation, ImportDefinitionData } from '../../types/electron'; import './Sidebar.css'; @@ -22,23 +22,6 @@ interface TagData { color?: string; } -// Get contrasting text color for background -const getContrastColor = (hex: string): string => { - const color = hex.replace('#', ''); - let r: number, g: number, b: number; - if (color.length === 3) { - r = parseInt(color[0] + color[0], 16); - g = parseInt(color[1] + color[1], 16); - b = parseInt(color[2] + color[2], 16); - } else { - r = parseInt(color.substring(0, 2), 16); - g = parseInt(color.substring(2, 4), 16); - b = parseInt(color.substring(4, 6), 16); - } - const luminance = (0.299 * r + 0.587 * g + 0.114 * b) / 255; - return luminance > 0.5 ? '#000000' : '#ffffff'; -}; - const formatDate = (dateString: string) => { const date = new Date(dateString); return date.toLocaleDateString('en-US', { month: 'short', day: 'numeric', year: 'numeric' }); diff --git a/src/renderer/components/TagInput/TagInput.tsx b/src/renderer/components/TagInput/TagInput.tsx index f08c758..690e7f0 100644 --- a/src/renderer/components/TagInput/TagInput.tsx +++ b/src/renderer/components/TagInput/TagInput.tsx @@ -1,5 +1,6 @@ import React, { useState, useEffect, useRef, useCallback } from 'react'; import { showToast } from '../Toast'; +import { getContrastColor } from '../../utils/color'; import './TagInput.css'; interface TagData { @@ -19,23 +20,6 @@ interface TagInputProps { disabled?: boolean; } -// Get contrasting text color for background -const getContrastColor = (hex: string): string => { - const color = hex.replace('#', ''); - let r: number, g: number, b: number; - if (color.length === 3) { - r = parseInt(color[0] + color[0], 16); - g = parseInt(color[1] + color[1], 16); - b = parseInt(color[2] + color[2], 16); - } else { - r = parseInt(color.substring(0, 2), 16); - g = parseInt(color.substring(2, 4), 16); - b = parseInt(color.substring(4, 6), 16); - } - const luminance = (0.299 * r + 0.587 * g + 0.114 * b) / 255; - return luminance > 0.5 ? '#000000' : '#ffffff'; -}; - export const TagInput: React.FC = ({ value, onChange, diff --git a/src/renderer/components/TagsView/TagsView.tsx b/src/renderer/components/TagsView/TagsView.tsx index bc58747..155aa41 100644 --- a/src/renderer/components/TagsView/TagsView.tsx +++ b/src/renderer/components/TagsView/TagsView.tsx @@ -1,6 +1,7 @@ import React, { useState, useEffect, useCallback } from 'react'; import { useAppStore } from '../../store'; import { showToast } from '../Toast'; +import { getContrastColor } from '../../utils/color'; import './TagsView.css'; // Types @@ -30,29 +31,6 @@ export const scrollToTagsSection = (category: TagsCategory) => { } }; -// Get contrasting text color for background -const getContrastColor = (hex: string): string => { - // Remove # if present - const color = hex.replace('#', ''); - - // Parse hex to RGB - let r: number, g: number, b: number; - if (color.length === 3) { - r = parseInt(color[0] + color[0], 16); - g = parseInt(color[1] + color[1], 16); - b = parseInt(color[2] + color[2], 16); - } else { - r = parseInt(color.substring(0, 2), 16); - g = parseInt(color.substring(2, 4), 16); - b = parseInt(color.substring(4, 6), 16); - } - - // Calculate relative luminance - const luminance = (0.299 * r + 0.587 * g + 0.114 * b) / 255; - - return luminance > 0.5 ? '#000000' : '#ffffff'; -}; - // Color picker presets const COLOR_PRESETS = [ '#ef4444', '#f97316', '#f59e0b', '#eab308', '#84cc16', diff --git a/src/renderer/utils/color.ts b/src/renderer/utils/color.ts new file mode 100644 index 0000000..e1107f2 --- /dev/null +++ b/src/renderer/utils/color.ts @@ -0,0 +1,20 @@ +export const getContrastColor = (hex: string): string => { + const color = hex.replace('#', ''); + + let r: number; + let g: number; + let b: number; + + if (color.length === 3) { + r = parseInt(color[0] + color[0], 16); + g = parseInt(color[1] + color[1], 16); + b = parseInt(color[2] + color[2], 16); + } else { + r = parseInt(color.substring(0, 2), 16); + g = parseInt(color.substring(2, 4), 16); + b = parseInt(color.substring(4, 6), 16); + } + + const luminance = (0.299 * r + 0.587 * g + 0.114 * b) / 255; + return luminance > 0.5 ? '#000000' : '#ffffff'; +}; \ No newline at end of file diff --git a/src/renderer/utils/index.ts b/src/renderer/utils/index.ts index 09131ce..b232a49 100644 --- a/src/renderer/utils/index.ts +++ b/src/renderer/utils/index.ts @@ -1,3 +1,4 @@ export { AutoSaveManager, type AutoSaveConfig } from './autoSave'; +export { getContrastColor } from './color'; export { unescapeMacroSyntax } from './markdownEscape'; export { groupPostsByStatus, type GroupedPosts, type PostStatus } from './postGrouping'; diff --git a/tests/renderer/utils/color.test.ts b/tests/renderer/utils/color.test.ts new file mode 100644 index 0000000..b899653 --- /dev/null +++ b/tests/renderer/utils/color.test.ts @@ -0,0 +1,19 @@ +import { describe, it, expect } from 'vitest'; +import { getContrastColor } from '../../../src/renderer/utils/color'; + +describe('getContrastColor', () => { + it('should return black text for light 6-char colors', () => { + expect(getContrastColor('#ffffff')).toBe('#000000'); + expect(getContrastColor('fef3c7')).toBe('#000000'); + }); + + it('should return white text for dark 6-char colors', () => { + expect(getContrastColor('#000000')).toBe('#ffffff'); + expect(getContrastColor('1f2937')).toBe('#ffffff'); + }); + + it('should support 3-char hex colors', () => { + expect(getContrastColor('#abc')).toBe('#000000'); + expect(getContrastColor('333')).toBe('#ffffff'); + }); +}); \ No newline at end of file