@kolkrabbi/kol-framework 0.3.4 → 0.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/kol-framework.css +3 -362
- package/package.json +2 -2
- package/src/ThemeToggle.jsx +55 -12
package/kol-framework.css
CHANGED
|
@@ -233,28 +233,7 @@ code, pre {
|
|
|
233
233
|
.kol-asset-grid-4col { grid-template-columns: 1fr; }
|
|
234
234
|
}
|
|
235
235
|
|
|
236
|
-
/*
|
|
237
|
-
Wrappers inlined in MoodTile.jsx. CSS retains descendant img positioning
|
|
238
|
-
(cover-crop the photo) + overlay logo sizing. */
|
|
239
|
-
|
|
240
|
-
.kol-mood-tile-frame img {
|
|
241
|
-
position: absolute;
|
|
242
|
-
inset: 0;
|
|
243
|
-
width: 100%;
|
|
244
|
-
height: 100%;
|
|
245
|
-
object-fit: cover;
|
|
246
|
-
}
|
|
247
|
-
|
|
248
|
-
.kol-mood-tile-overlay .kol-brand-logo {
|
|
249
|
-
width: 32%;
|
|
250
|
-
max-width: 180px;
|
|
251
|
-
display: inline-flex;
|
|
252
|
-
}
|
|
253
|
-
|
|
254
|
-
.kol-mood-tile-overlay svg {
|
|
255
|
-
width: 100%;
|
|
256
|
-
height: auto;
|
|
257
|
-
}
|
|
236
|
+
/* .kol-mood-tile-* relocated to kol-theme/kol-components-styleguide.css 2026-07-10 */
|
|
258
237
|
|
|
259
238
|
/* ───── Swatch / Ramp (color specimens) ───── */
|
|
260
239
|
|
|
@@ -684,324 +663,7 @@ code, pre {
|
|
|
684
663
|
/* ───── Foundations · typography specimens ─────
|
|
685
664
|
Wrappers fully inlined in TypographySections.jsx. */
|
|
686
665
|
|
|
687
|
-
/*
|
|
688
|
-
|
|
689
|
-
.kol-combo-frame {
|
|
690
|
-
border-radius: 4px;
|
|
691
|
-
overflow: hidden;
|
|
692
|
-
transition: background-color 400ms ease, border-color 400ms ease;
|
|
693
|
-
}
|
|
694
|
-
|
|
695
|
-
.kol-combo-slab {
|
|
696
|
-
padding: 16px;
|
|
697
|
-
display: flex;
|
|
698
|
-
flex-direction: column;
|
|
699
|
-
justify-content: space-between;
|
|
700
|
-
min-height: 0;
|
|
701
|
-
transition: background-color 400ms ease, color 400ms ease;
|
|
702
|
-
}
|
|
703
|
-
|
|
704
|
-
.kol-combo-slab--end { justify-content: flex-end; }
|
|
705
|
-
.kol-combo-slab--between { justify-content: space-between; }
|
|
706
|
-
|
|
707
|
-
.kol-combo-label {
|
|
708
|
-
font-family: 'Right Grotesk Compact', sans-serif;
|
|
709
|
-
font-size: 12px;
|
|
710
|
-
font-weight: 500;
|
|
711
|
-
line-height: 100%;
|
|
712
|
-
letter-spacing: 0.08em;
|
|
713
|
-
text-transform: uppercase;
|
|
714
|
-
}
|
|
715
|
-
|
|
716
|
-
.kol-combo-number {
|
|
717
|
-
font-family: 'Right Grotesk', sans-serif;
|
|
718
|
-
font-size: 16px;
|
|
719
|
-
font-weight: 700;
|
|
720
|
-
line-height: 100%;
|
|
721
|
-
letter-spacing: 0.02em;
|
|
722
|
-
}
|
|
723
|
-
|
|
724
|
-
.kol-combo-stage--fill {
|
|
725
|
-
height: 100%;
|
|
726
|
-
min-height: 360px;
|
|
727
|
-
width: 100%;
|
|
728
|
-
}
|
|
729
|
-
|
|
730
|
-
.kol-combo-logo {
|
|
731
|
-
display: inline-flex;
|
|
732
|
-
color: currentColor;
|
|
733
|
-
}
|
|
734
|
-
|
|
735
|
-
/* 60 / 30 / 10 */
|
|
736
|
-
.kol-combo-stage--ratio { display: flex; }
|
|
737
|
-
.kol-combo-stage--ratio > :nth-child(1) { flex: 1; }
|
|
738
|
-
.kol-combo-stage--ratio > :nth-child(2) { flex: 3; }
|
|
739
|
-
.kol-combo-stage--ratio > :nth-child(3) { flex: 6; }
|
|
740
|
-
|
|
741
|
-
/* Tower */
|
|
742
|
-
.kol-combo-stage--tower {
|
|
743
|
-
display: flex;
|
|
744
|
-
flex-direction: column;
|
|
745
|
-
height: 100%;
|
|
746
|
-
width: 100%;
|
|
747
|
-
}
|
|
748
|
-
.kol-combo-stage--tower > * { flex: 1; }
|
|
749
|
-
|
|
750
|
-
/* Quad split */
|
|
751
|
-
.kol-combo-stage--quad { display: flex; }
|
|
752
|
-
.kol-combo-stage--quad > * { flex: 1; }
|
|
753
|
-
|
|
754
|
-
.kol-combo-quad-col {
|
|
755
|
-
display: flex;
|
|
756
|
-
flex-direction: column;
|
|
757
|
-
}
|
|
758
|
-
.kol-combo-quad-col > * { flex: 1; }
|
|
759
|
-
|
|
760
|
-
/* Card row */
|
|
761
|
-
.kol-combo-stage--card-row {
|
|
762
|
-
display: grid;
|
|
763
|
-
grid-template-columns: repeat(4, 1fr);
|
|
764
|
-
gap: 16px;
|
|
765
|
-
height: 100%;
|
|
766
|
-
min-height: 360px;
|
|
767
|
-
width: 100%;
|
|
768
|
-
}
|
|
769
|
-
|
|
770
|
-
.kol-combo-card {
|
|
771
|
-
flex: 1;
|
|
772
|
-
min-height: 240px;
|
|
773
|
-
padding: 20px;
|
|
774
|
-
display: flex;
|
|
775
|
-
flex-direction: column;
|
|
776
|
-
transition: background-color 400ms ease, color 400ms ease;
|
|
777
|
-
}
|
|
778
|
-
|
|
779
|
-
.kol-combo-card--end { justify-content: flex-end; }
|
|
780
|
-
.kol-combo-card--between { justify-content: space-between; }
|
|
781
|
-
|
|
782
|
-
/* Stripe row */
|
|
783
|
-
.kol-combo-stage--stripe-row {
|
|
784
|
-
display: flex;
|
|
785
|
-
flex-direction: column;
|
|
786
|
-
gap: 16px;
|
|
787
|
-
height: 100%;
|
|
788
|
-
min-height: 360px;
|
|
789
|
-
width: 100%;
|
|
790
|
-
}
|
|
791
|
-
|
|
792
|
-
.kol-combo-stripe-row {
|
|
793
|
-
display: flex;
|
|
794
|
-
flex: 1;
|
|
795
|
-
align-items: stretch;
|
|
796
|
-
width: 100%;
|
|
797
|
-
}
|
|
798
|
-
|
|
799
|
-
.kol-combo-stripe-bar {
|
|
800
|
-
display: flex;
|
|
801
|
-
flex: 1;
|
|
802
|
-
position: relative;
|
|
803
|
-
width: 100%;
|
|
804
|
-
height: 100%;
|
|
805
|
-
border-radius: 4px;
|
|
806
|
-
overflow: hidden;
|
|
807
|
-
}
|
|
808
|
-
|
|
809
|
-
.kol-combo-stripe-seg { transition: background-color 400ms ease; }
|
|
810
|
-
.kol-combo-stripe-seg--6 { flex: 6; }
|
|
811
|
-
.kol-combo-stripe-seg--3 { flex: 3; }
|
|
812
|
-
.kol-combo-stripe-seg--1 { flex: 1; }
|
|
813
|
-
|
|
814
|
-
.kol-combo-stripe-group { display: flex; }
|
|
815
|
-
.kol-combo-stripe-group--3 { flex: 3; }
|
|
816
|
-
.kol-combo-stripe-group--1 { flex: 1; }
|
|
817
|
-
|
|
818
|
-
.kol-combo-stripe-neutral {
|
|
819
|
-
border-top: 1px solid var(--stripe-rule, transparent);
|
|
820
|
-
border-bottom: 1px solid var(--stripe-rule, transparent);
|
|
821
|
-
}
|
|
822
|
-
|
|
823
|
-
.kol-combo-stripe-accent-border {
|
|
824
|
-
border: 1.73px solid var(--stripe-accent, transparent);
|
|
825
|
-
}
|
|
826
|
-
|
|
827
|
-
.kol-combo-stripe-method {
|
|
828
|
-
position: absolute;
|
|
829
|
-
top: 12px;
|
|
830
|
-
left: 12px;
|
|
831
|
-
font-family: 'Right Grotesk Compact', sans-serif;
|
|
832
|
-
font-size: 12px;
|
|
833
|
-
font-weight: 500;
|
|
834
|
-
letter-spacing: 0.08em;
|
|
835
|
-
line-height: 100%;
|
|
836
|
-
text-transform: uppercase;
|
|
837
|
-
color: white;
|
|
838
|
-
pointer-events: none;
|
|
839
|
-
}
|
|
840
|
-
|
|
841
|
-
/* Applied card */
|
|
842
|
-
.kol-combo-stage--applied {
|
|
843
|
-
display: flex;
|
|
844
|
-
height: 100%;
|
|
845
|
-
min-height: 360px;
|
|
846
|
-
width: 100%;
|
|
847
|
-
}
|
|
848
|
-
|
|
849
|
-
.kol-combo-applied-plate {
|
|
850
|
-
flex: 2;
|
|
851
|
-
padding: 32px;
|
|
852
|
-
display: flex;
|
|
853
|
-
flex-direction: column;
|
|
854
|
-
justify-content: space-between;
|
|
855
|
-
transition: background-color 400ms ease, color 400ms ease;
|
|
856
|
-
}
|
|
857
|
-
|
|
858
|
-
.kol-combo-applied-col {
|
|
859
|
-
flex: 1;
|
|
860
|
-
display: flex;
|
|
861
|
-
flex-direction: column;
|
|
862
|
-
}
|
|
863
|
-
|
|
864
|
-
.kol-combo-applied-surface { flex: 3; }
|
|
865
|
-
.kol-combo-applied-band--lg { flex: 2; }
|
|
866
|
-
.kol-combo-applied-band--sm { flex: 1; }
|
|
867
|
-
.kol-combo-applied-band { transition: background-color 400ms ease; }
|
|
868
|
-
|
|
869
|
-
.kol-combo-applied-accent-strip {
|
|
870
|
-
display: flex;
|
|
871
|
-
gap: 8px;
|
|
872
|
-
}
|
|
873
|
-
|
|
874
|
-
.kol-combo-applied-accent-chip {
|
|
875
|
-
width: 20px;
|
|
876
|
-
height: 20px;
|
|
877
|
-
border-radius: 2px;
|
|
878
|
-
transition: background-color 400ms ease;
|
|
879
|
-
}
|
|
880
|
-
|
|
881
|
-
/* ───── Foundations · combo lab ───── */
|
|
882
|
-
|
|
883
|
-
.kol-combo-lab {
|
|
884
|
-
display: flex;
|
|
885
|
-
flex-direction: column;
|
|
886
|
-
flex: 1;
|
|
887
|
-
padding: 24px;
|
|
888
|
-
border: 1px solid var(--kol-fg-08);
|
|
889
|
-
border-radius: 4px;
|
|
890
|
-
background: var(--kol-fg-02);
|
|
891
|
-
}
|
|
892
|
-
|
|
893
|
-
.kol-combo-controls { margin-bottom: 24px; }
|
|
894
|
-
|
|
895
|
-
.kol-combo-stage-wrap {
|
|
896
|
-
flex: 1;
|
|
897
|
-
margin-bottom: 24px;
|
|
898
|
-
padding: 32px;
|
|
899
|
-
background: var(--kol-fg-02);
|
|
900
|
-
border-radius: 4px;
|
|
901
|
-
min-height: 440px;
|
|
902
|
-
display: flex;
|
|
903
|
-
align-items: stretch;
|
|
904
|
-
justify-content: center;
|
|
905
|
-
}
|
|
906
|
-
|
|
907
|
-
.kol-combo-stage-anim {
|
|
908
|
-
width: 100%;
|
|
909
|
-
height: 100%;
|
|
910
|
-
animation: kol-combo-fade 260ms ease;
|
|
911
|
-
display: flex;
|
|
912
|
-
align-items: stretch;
|
|
913
|
-
}
|
|
914
|
-
|
|
915
|
-
.kol-combo-stage-anim > * { width: 100%; height: 100%; }
|
|
916
|
-
|
|
917
|
-
/* In fullbleed mode, let the lab fill the section */
|
|
918
|
-
.kol-page--fullbleed .kol-combo-lab { flex: 1; }
|
|
919
|
-
.kol-page--fullbleed .kol-combo-stage-wrap { min-height: 520px; }
|
|
920
|
-
|
|
921
|
-
@keyframes kol-combo-fade {
|
|
922
|
-
from { opacity: 0.2; transform: translateY(6px); }
|
|
923
|
-
to { opacity: 1; transform: none; }
|
|
924
|
-
}
|
|
925
|
-
|
|
926
|
-
.kol-combo-readout {
|
|
927
|
-
display: grid;
|
|
928
|
-
grid-template-columns: repeat(auto-fit, minmax(220px, 1fr));
|
|
929
|
-
gap: 8px 32px;
|
|
930
|
-
padding-top: 16px;
|
|
931
|
-
border-top: 1px solid var(--kol-fg-08);
|
|
932
|
-
}
|
|
933
|
-
|
|
934
|
-
/* Combo Lab — control rows (Layout / Palette / Logo selectors) */
|
|
935
|
-
.kol-combo-row {
|
|
936
|
-
display: flex;
|
|
937
|
-
align-items: center;
|
|
938
|
-
gap: 16px;
|
|
939
|
-
flex-wrap: wrap;
|
|
940
|
-
margin-bottom: 12px;
|
|
941
|
-
}
|
|
942
|
-
|
|
943
|
-
.kol-combo-row-label { min-width: 72px; }
|
|
944
|
-
|
|
945
|
-
.kol-combo-row-controls {
|
|
946
|
-
display: flex;
|
|
947
|
-
flex-wrap: wrap;
|
|
948
|
-
gap: 6px;
|
|
949
|
-
}
|
|
950
|
-
|
|
951
|
-
.kol-combo-footer {
|
|
952
|
-
display: flex;
|
|
953
|
-
align-items: center;
|
|
954
|
-
gap: 16px;
|
|
955
|
-
margin-top: 8px;
|
|
956
|
-
}
|
|
957
|
-
|
|
958
|
-
.kol-combo-footer-desc { flex: 1; }
|
|
959
|
-
|
|
960
|
-
.kol-combo-randomize {
|
|
961
|
-
padding: 8px 16px;
|
|
962
|
-
background: transparent;
|
|
963
|
-
color: var(--kol-fg-80);
|
|
964
|
-
border: 1px solid var(--kol-fg-16);
|
|
965
|
-
border-radius: 4px;
|
|
966
|
-
font-family: var(--kol-font-family-mono, monospace);
|
|
967
|
-
font-weight: 600;
|
|
968
|
-
cursor: pointer;
|
|
969
|
-
transition: color 150ms ease, border-color 150ms ease, background 150ms ease;
|
|
970
|
-
}
|
|
971
|
-
|
|
972
|
-
.kol-combo-randomize:hover {
|
|
973
|
-
color: var(--kol-surface-on-primary);
|
|
974
|
-
border-color: var(--kol-fg-48);
|
|
975
|
-
background: var(--kol-fg-04);
|
|
976
|
-
}
|
|
977
|
-
|
|
978
|
-
/* Combo Lab — live palette readout rows */
|
|
979
|
-
.kol-combo-swatch-row {
|
|
980
|
-
display: flex;
|
|
981
|
-
align-items: center;
|
|
982
|
-
gap: 12px;
|
|
983
|
-
padding: 6px 0;
|
|
984
|
-
}
|
|
985
|
-
|
|
986
|
-
.kol-combo-swatch-chip {
|
|
987
|
-
width: 24px;
|
|
988
|
-
height: 24px;
|
|
989
|
-
flex-shrink: 0;
|
|
990
|
-
border-radius: 3px;
|
|
991
|
-
transition: background 400ms ease;
|
|
992
|
-
}
|
|
993
|
-
|
|
994
|
-
.kol-combo-swatch-label {
|
|
995
|
-
min-width: 88px;
|
|
996
|
-
font-weight: 600;
|
|
997
|
-
}
|
|
998
|
-
|
|
999
|
-
.kol-combo-swatch-hex {
|
|
1000
|
-
font-family: var(--kol-font-family-mono, monospace);
|
|
1001
|
-
text-transform: uppercase;
|
|
1002
|
-
}
|
|
1003
|
-
|
|
1004
|
-
.kol-combo-summary { margin-top: 16px; }
|
|
666
|
+
/* .kol-combo-* (+ @keyframes kol-combo-fade) relocated to kol-theme/kol-components-styleguide.css 2026-07-10 */
|
|
1005
667
|
|
|
1006
668
|
/* ═════════════════════════════════════════════════════════════════════
|
|
1007
669
|
SpectrumGrid — matrix color-token grid (9 ramps × 10 stops)
|
|
@@ -1113,28 +775,7 @@ code, pre {
|
|
|
1113
775
|
}
|
|
1114
776
|
|
|
1115
777
|
|
|
1116
|
-
/*
|
|
1117
|
-
Color Anatomy — token composition samples
|
|
1118
|
-
|
|
1119
|
-
Wrappers inlined in ColorAnatomy.jsx; figure margin via Preflight.
|
|
1120
|
-
CSS retains the descendant typography for figcaption + inline <code>.
|
|
1121
|
-
═════════════════════════════════════════════════════════════════════ */
|
|
1122
|
-
|
|
1123
|
-
.kol-anatomy-sample figcaption {
|
|
1124
|
-
margin-top: 12px;
|
|
1125
|
-
font-size: 12px;
|
|
1126
|
-
color: var(--kol-fg-48);
|
|
1127
|
-
font-style: italic;
|
|
1128
|
-
}
|
|
1129
|
-
|
|
1130
|
-
.kol-anatomy-sample code {
|
|
1131
|
-
font-family: var(--kol-font-family-mono, monospace);
|
|
1132
|
-
font-size: 11px;
|
|
1133
|
-
background: color-mix(in srgb, currentColor 18%, transparent);
|
|
1134
|
-
padding: 1px 4px;
|
|
1135
|
-
border-radius: 3px;
|
|
1136
|
-
opacity: 1;
|
|
1137
|
-
}
|
|
778
|
+
/* .kol-anatomy-sample-* relocated to kol-theme/kol-components-styleguide.css 2026-07-10 */
|
|
1138
779
|
|
|
1139
780
|
|
|
1140
781
|
/* ═════════════════════════════════════════════════════════════════════
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@kolkrabbi/kol-framework",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.4.0",
|
|
4
4
|
"description": "KOL app shell — sidenav, layout, theme toggle, footer, heroes + brand color layer. SideNav takes nav data as props so the tree stays app-local.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"type": "module",
|
|
@@ -27,7 +27,7 @@
|
|
|
27
27
|
"app-shell"
|
|
28
28
|
],
|
|
29
29
|
"dependencies": {
|
|
30
|
-
"@kolkrabbi/kol-component": "0.
|
|
30
|
+
"@kolkrabbi/kol-component": "0.9.0",
|
|
31
31
|
"@kolkrabbi/kol-icons": "0.5.0"
|
|
32
32
|
},
|
|
33
33
|
"peerDependencies": {
|
package/src/ThemeToggle.jsx
CHANGED
|
@@ -3,9 +3,24 @@ import { Icon } from '@kolkrabbi/kol-icons'
|
|
|
3
3
|
|
|
4
4
|
const STORAGE_KEY = 'kol-theme'
|
|
5
5
|
|
|
6
|
+
/* An explicit choice is an app-set data-theme attribute or a saved user
|
|
7
|
+
* toggle. Absent both, the DS is light-first and follows the system
|
|
8
|
+
* (prefers-color-scheme) — matching the theme CSS, which only darkens via
|
|
9
|
+
* explicit [data-theme="dark"] or the media query. */
|
|
10
|
+
function getExplicitTheme() {
|
|
11
|
+
const attr = document.documentElement.dataset.theme
|
|
12
|
+
if (attr === 'light' || attr === 'dark') return attr
|
|
13
|
+
try {
|
|
14
|
+
const saved = localStorage.getItem(STORAGE_KEY)
|
|
15
|
+
if (saved === 'light' || saved === 'dark') return saved
|
|
16
|
+
} catch { /* storage blocked */ }
|
|
17
|
+
return null
|
|
18
|
+
}
|
|
19
|
+
|
|
6
20
|
function getInitialTheme() {
|
|
7
|
-
if (typeof document === 'undefined') return '
|
|
8
|
-
return
|
|
21
|
+
if (typeof document === 'undefined') return 'light'
|
|
22
|
+
return getExplicitTheme()
|
|
23
|
+
?? (window.matchMedia?.('(prefers-color-scheme: dark)')?.matches ? 'dark' : 'light')
|
|
9
24
|
}
|
|
10
25
|
|
|
11
26
|
/**
|
|
@@ -22,22 +37,50 @@ function getInitialTheme() {
|
|
|
22
37
|
* bg, no hover bg). For sidenav rows where the row should read
|
|
23
38
|
* as plain text + icon, not a button.
|
|
24
39
|
*
|
|
25
|
-
* Self-contained
|
|
26
|
-
*
|
|
27
|
-
*
|
|
28
|
-
*
|
|
40
|
+
* Self-contained but never imposing: initial state is the app-set
|
|
41
|
+
* `data-theme` attribute, else the user's saved toggle (`kol-theme`), else
|
|
42
|
+
* the system preference. `data-theme` + localStorage are written ONLY on a
|
|
43
|
+
* user toggle — mounting the component leaves the DOM untouched, so an
|
|
44
|
+
* undecided page keeps following prefers-color-scheme via the theme CSS.
|
|
45
|
+
* The icon-swap animation slides the `mode-toggle-01` / `mode-toggle-02`
|
|
46
|
+
* pair (v1 set) horizontally — two different halves of the split circle,
|
|
47
|
+
* so the slide is a visible flip.
|
|
29
48
|
*/
|
|
30
49
|
export default function ThemeToggle({ variant = 'icon', className = '' }) {
|
|
31
50
|
const [theme, setTheme] = useState(getInitialTheme)
|
|
32
51
|
|
|
33
52
|
useEffect(() => {
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
53
|
+
/* Re-stamp a previously saved user choice so persistence works in apps
|
|
54
|
+
* with no theme boot script — but only when the app itself hasn't set
|
|
55
|
+
* data-theme (an app-set attribute always wins over storage). */
|
|
56
|
+
const attr = document.documentElement.dataset.theme
|
|
57
|
+
let saved = null
|
|
58
|
+
try { saved = localStorage.getItem(STORAGE_KEY) } catch { /* storage blocked */ }
|
|
59
|
+
if (attr !== 'light' && attr !== 'dark') {
|
|
60
|
+
if (saved === 'light' || saved === 'dark') {
|
|
61
|
+
document.documentElement.dataset.theme = saved
|
|
62
|
+
return
|
|
63
|
+
}
|
|
64
|
+
/* No explicit choice anywhere — CSS follows the system on its own;
|
|
65
|
+
* this listener only keeps the toggle's icon in sync with it. */
|
|
66
|
+
const mq = window.matchMedia?.('(prefers-color-scheme: dark)')
|
|
67
|
+
if (!mq?.addEventListener) return
|
|
68
|
+
const onChange = (e) => {
|
|
69
|
+
try { if (localStorage.getItem(STORAGE_KEY)) return } catch { /* storage blocked */ }
|
|
70
|
+
setTheme(e.matches ? 'dark' : 'light')
|
|
71
|
+
}
|
|
72
|
+
mq.addEventListener('change', onChange)
|
|
73
|
+
return () => mq.removeEventListener('change', onChange)
|
|
74
|
+
}
|
|
75
|
+
}, [])
|
|
37
76
|
|
|
38
77
|
const isDark = theme === 'dark'
|
|
39
78
|
const next = isDark ? 'light' : 'dark'
|
|
40
|
-
const handleToggle = () =>
|
|
79
|
+
const handleToggle = () => {
|
|
80
|
+
setTheme(next)
|
|
81
|
+
document.documentElement.dataset.theme = next
|
|
82
|
+
try { localStorage.setItem(STORAGE_KEY, next) } catch { /* storage blocked */ }
|
|
83
|
+
}
|
|
41
84
|
|
|
42
85
|
const iconSwap = (size) => (
|
|
43
86
|
<span
|
|
@@ -49,8 +92,8 @@ export default function ThemeToggle({ variant = 'icon', className = '' }) {
|
|
|
49
92
|
className="flex transition-transform duration-500 ease-in-out"
|
|
50
93
|
style={{ width: size * 2, transform: isDark ? 'translateX(0)' : `translateX(-${size}px)` }}
|
|
51
94
|
>
|
|
52
|
-
<Icon name="
|
|
53
|
-
<Icon name="
|
|
95
|
+
<Icon name="mode-toggle-01" size={size} />
|
|
96
|
+
<Icon name="mode-toggle-02" size={size} />
|
|
54
97
|
</span>
|
|
55
98
|
</span>
|
|
56
99
|
)
|