Skip to content

Commit 5e9df85

Browse files
Anant Tripathiclaude
authored andcommitted
Add dark/light mode toggle to header
Sun/moon icon button in the header switches between light and dark themes. Dark mode uses data-theme="dark" on the root div with full CSS overrides for all panels, inputs, diff output, and diff2html add/remove line colors. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
1 parent d61b512 commit 5e9df85

2 files changed

Lines changed: 312 additions & 1 deletion

File tree

src/App.css

Lines changed: 287 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,9 @@ body {
3030
max-width: 1600px;
3131
margin: 0 auto;
3232
padding: 16px 0;
33+
display: flex;
34+
align-items: center;
35+
justify-content: space-between;
3336
}
3437

3538
.header-brand {
@@ -519,3 +522,287 @@ body {
519522
border-top: 1px solid #e2e8f0;
520523
background: #ffffff;
521524
}
525+
526+
/* ── Theme Toggle Button ── */
527+
.theme-toggle {
528+
width: 36px;
529+
height: 36px;
530+
display: flex;
531+
align-items: center;
532+
justify-content: center;
533+
border-radius: 8px;
534+
border: 1px solid rgba(255,255,255,0.15);
535+
background: rgba(255,255,255,0.08);
536+
color: #a5b4fc;
537+
cursor: pointer;
538+
flex-shrink: 0;
539+
transition: background 0.15s, color 0.15s;
540+
}
541+
542+
.theme-toggle:hover {
543+
background: rgba(255,255,255,0.16);
544+
color: #ffffff;
545+
}
546+
547+
.theme-toggle svg {
548+
width: 18px;
549+
height: 18px;
550+
}
551+
552+
/* ── Dark Mode ── */
553+
[data-theme="dark"] body,
554+
[data-theme="dark"] {
555+
background: #0d0d1a;
556+
color: #e2e8f0;
557+
}
558+
559+
[data-theme="dark"] .app-header {
560+
background: #13112b;
561+
border-bottom-color: #4f46e5;
562+
}
563+
564+
[data-theme="dark"] .input-section {
565+
background: #1a1829;
566+
border-color: #2a2740;
567+
}
568+
569+
[data-theme="dark"] .pane-header {
570+
background: #13112b;
571+
border-bottom-color: #2a2740;
572+
}
573+
574+
[data-theme="dark"] .pane-label {
575+
color: #94a3b8;
576+
}
577+
578+
[data-theme="dark"] .tab-group {
579+
background: #2a2740;
580+
}
581+
582+
[data-theme="dark"] .tab-btn {
583+
color: #94a3b8;
584+
}
585+
586+
[data-theme="dark"] .tab-btn:hover {
587+
color: #e2e8f0;
588+
}
589+
590+
[data-theme="dark"] .tab-btn.active {
591+
background: #1a1829;
592+
color: #e2e8f0;
593+
box-shadow: 0 1px 2px rgba(0,0,0,0.4);
594+
}
595+
596+
[data-theme="dark"] .code-textarea {
597+
background: #1a1829;
598+
color: #e2e8f0;
599+
}
600+
601+
[data-theme="dark"] .code-textarea::placeholder {
602+
color: #4a4a6a;
603+
}
604+
605+
[data-theme="dark"] .input-pane + .swap-col + .input-pane {
606+
border-left-color: #2a2740;
607+
}
608+
609+
[data-theme="dark"] .swap-col {
610+
background: #13112b;
611+
border-color: #2a2740;
612+
}
613+
614+
[data-theme="dark"] .swap-btn {
615+
background: #1a1829;
616+
border-color: #2a2740;
617+
color: #94a3b8;
618+
}
619+
620+
[data-theme="dark"] .swap-btn:hover {
621+
background: #4f46e5;
622+
border-color: #4f46e5;
623+
color: #ffffff;
624+
}
625+
626+
[data-theme="dark"] .drop-zone {
627+
background: #13112b;
628+
border-color: #2a2740;
629+
}
630+
631+
[data-theme="dark"] .drop-zone:hover:not(.has-file) {
632+
background: #1e1b3d;
633+
border-color: #6366f1;
634+
}
635+
636+
[data-theme="dark"] .drop-zone.dragging {
637+
background: #1e1b3d;
638+
border-color: #4f46e5;
639+
}
640+
641+
[data-theme="dark"] .drop-zone.has-file {
642+
background: #1a1829;
643+
border-color: #2a2740;
644+
}
645+
646+
[data-theme="dark"] .upload-icon {
647+
color: #4a4a6a;
648+
}
649+
650+
[data-theme="dark"] .drop-text {
651+
color: #94a3b8;
652+
}
653+
654+
[data-theme="dark"] .drop-hint {
655+
color: #4a4a6a;
656+
}
657+
658+
[data-theme="dark"] .file-name {
659+
color: #e2e8f0;
660+
}
661+
662+
[data-theme="dark"] .file-btn {
663+
background: #1a1829;
664+
border-color: #2a2740;
665+
color: #94a3b8;
666+
}
667+
668+
[data-theme="dark"] .file-btn:hover {
669+
background: #2a2740;
670+
color: #e2e8f0;
671+
}
672+
673+
[data-theme="dark"] .file-btn-remove {
674+
color: #f87171;
675+
border-color: #3d1a1a;
676+
}
677+
678+
[data-theme="dark"] .file-btn-remove:hover {
679+
background: #2d1a1a;
680+
color: #fca5a5;
681+
}
682+
683+
[data-theme="dark"] .controls-bar {
684+
background: #1a1829;
685+
border-color: #2a2740;
686+
}
687+
688+
[data-theme="dark"] .btn-ghost {
689+
color: #94a3b8;
690+
border-color: #2a2740;
691+
}
692+
693+
[data-theme="dark"] .btn-ghost:hover:not(:disabled) {
694+
background: #2a2740;
695+
color: #e2e8f0;
696+
}
697+
698+
[data-theme="dark"] .stats-badge {
699+
background: #13112b;
700+
border-color: #2a2740;
701+
}
702+
703+
[data-theme="dark"] .stat-label {
704+
color: #4a4a6a;
705+
}
706+
707+
[data-theme="dark"] .view-toggle {
708+
background: #13112b;
709+
}
710+
711+
[data-theme="dark"] .toggle-btn {
712+
color: #94a3b8;
713+
}
714+
715+
[data-theme="dark"] .toggle-btn:hover {
716+
color: #e2e8f0;
717+
}
718+
719+
[data-theme="dark"] .toggle-btn.active {
720+
background: #2a2740;
721+
color: #818cf8;
722+
box-shadow: 0 1px 3px rgba(0,0,0,0.5);
723+
}
724+
725+
[data-theme="dark"] .empty-state {
726+
background: #1a1829;
727+
border-color: #2a2740;
728+
}
729+
730+
[data-theme="dark"] .empty-icon {
731+
color: #2a2740;
732+
}
733+
734+
[data-theme="dark"] .empty-text {
735+
color: #4a4a6a;
736+
}
737+
738+
[data-theme="dark"] .diff-output {
739+
background: #1a1829;
740+
border-color: #2a2740;
741+
}
742+
743+
[data-theme="dark"] .diff-output .d2h-file-header {
744+
background: #13112b;
745+
border-bottom-color: #2a2740;
746+
color: #94a3b8;
747+
}
748+
749+
[data-theme="dark"] .diff-output .d2h-file-name {
750+
color: #a5b4fc;
751+
}
752+
753+
[data-theme="dark"] .diff-output .d2h-diff-table {
754+
background: #1a1829;
755+
color: #e2e8f0;
756+
}
757+
758+
[data-theme="dark"] .diff-output .d2h-code-linenumber,
759+
[data-theme="dark"] .diff-output .d2h-code-side-linenumber {
760+
background: #13112b !important;
761+
color: #4a4a6a !important;
762+
border-color: #2a2740 !important;
763+
}
764+
765+
[data-theme="dark"] .diff-output .d2h-code-line,
766+
[data-theme="dark"] .diff-output .d2h-code-side-line {
767+
background: #1a1829;
768+
color: #e2e8f0;
769+
}
770+
771+
[data-theme="dark"] .diff-output .d2h-ins,
772+
[data-theme="dark"] .diff-output .d2h-ins td {
773+
background: #0d2b1a !important;
774+
color: #86efac !important;
775+
}
776+
777+
[data-theme="dark"] .diff-output .d2h-ins .d2h-code-linenumber,
778+
[data-theme="dark"] .diff-output .d2h-ins .d2h-code-side-linenumber {
779+
background: #0a2015 !important;
780+
color: #4ade80 !important;
781+
}
782+
783+
[data-theme="dark"] .diff-output .d2h-del,
784+
[data-theme="dark"] .diff-output .d2h-del td {
785+
background: #2d0d0d !important;
786+
color: #fca5a5 !important;
787+
}
788+
789+
[data-theme="dark"] .diff-output .d2h-del .d2h-code-linenumber,
790+
[data-theme="dark"] .diff-output .d2h-del .d2h-code-side-linenumber {
791+
background: #250a0a !important;
792+
color: #f87171 !important;
793+
}
794+
795+
[data-theme="dark"] .diff-output .d2h-info {
796+
background: #13112b !important;
797+
color: #6366f1 !important;
798+
}
799+
800+
[data-theme="dark"] .diff-output .d2h-emptyplaceholder {
801+
background: #13112b !important;
802+
}
803+
804+
[data-theme="dark"] .app-footer {
805+
background: #1a1829;
806+
border-top-color: #2a2740;
807+
color: #4a4a6a;
808+
}

src/App.jsx

Lines changed: 25 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ export default function App() {
1212
const [viewType, setViewType] = useState('side-by-side')
1313
const [compared, setCompared] = useState(false)
1414
const [diffSnapshot, setDiffSnapshot] = useState(null)
15+
const [dark, setDark] = useState(false)
1516

1617
const isEmpty = left.trim() === '' && right.trim() === ''
1718

@@ -67,7 +68,7 @@ export default function App() {
6768
}
6869

6970
return (
70-
<div className="app">
71+
<div className="app" data-theme={dark ? 'dark' : 'light'}>
7172
<header className="app-header">
7273
<div className="header-inner">
7374
<div className="header-brand">
@@ -79,6 +80,29 @@ export default function App() {
7980
<p className="header-subtitle">Code &amp; text differentiator</p>
8081
</div>
8182
</div>
83+
<button
84+
className="theme-toggle"
85+
onClick={() => setDark(d => !d)}
86+
title={dark ? 'Switch to light mode' : 'Switch to dark mode'}
87+
>
88+
{dark ? (
89+
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2">
90+
<circle cx="12" cy="12" r="5" />
91+
<line x1="12" y1="1" x2="12" y2="3" />
92+
<line x1="12" y1="21" x2="12" y2="23" />
93+
<line x1="4.22" y1="4.22" x2="5.64" y2="5.64" />
94+
<line x1="18.36" y1="18.36" x2="19.78" y2="19.78" />
95+
<line x1="1" y1="12" x2="3" y2="12" />
96+
<line x1="21" y1="12" x2="23" y2="12" />
97+
<line x1="4.22" y1="19.78" x2="5.64" y2="18.36" />
98+
<line x1="18.36" y1="5.64" x2="19.78" y2="4.22" />
99+
</svg>
100+
) : (
101+
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2">
102+
<path d="M21 12.79A9 9 0 1 1 11.21 3 7 7 0 0 0 21 12.79z" />
103+
</svg>
104+
)}
105+
</button>
82106
</div>
83107
</header>
84108

0 commit comments

Comments
 (0)