paperclip-theme 0.2.0 → 0.2.2
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/dist/constants.d.ts.map +1 -1
- package/dist/constants.js +42 -2
- package/dist/constants.js.map +1 -1
- package/dist/ui/index.js +214 -31
- package/dist/ui/index.js.map +2 -2
- package/package.json +1 -1
- package/src/constants.ts +42 -2
- package/src/ui/index.tsx +237 -30
package/src/ui/index.tsx
CHANGED
|
@@ -25,6 +25,38 @@ const CARD_WIDTH = 192;
|
|
|
25
25
|
const CARD_GAP = 10;
|
|
26
26
|
const SWATCH_TOKENS = ["--background", "--primary", "--accent", "--chart-1", "--destructive"];
|
|
27
27
|
|
|
28
|
+
const FONT_OPTIONS_SANS = [
|
|
29
|
+
{ label: "System Default", value: "-apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif" },
|
|
30
|
+
{ label: "Inter", value: "'Inter', -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif" },
|
|
31
|
+
{ label: "Geist", value: "'Geist', -apple-system, BlinkMacSystemFont, sans-serif" },
|
|
32
|
+
{ label: "Manrope", value: "'Manrope', -apple-system, BlinkMacSystemFont, sans-serif" },
|
|
33
|
+
{ label: "Work Sans", value: "'Work Sans', -apple-system, BlinkMacSystemFont, sans-serif" },
|
|
34
|
+
{ label: "DM Sans", value: "'DM Sans', -apple-system, BlinkMacSystemFont, sans-serif" },
|
|
35
|
+
{ label: "Figtree", value: "'Figtree', -apple-system, BlinkMacSystemFont, sans-serif" },
|
|
36
|
+
{ label: "Outfit", value: "'Outfit', -apple-system, BlinkMacSystemFont, sans-serif" },
|
|
37
|
+
{ label: "Nunito", value: "'Nunito', -apple-system, BlinkMacSystemFont, sans-serif" },
|
|
38
|
+
{ label: "Rubik", value: "'Rubik', -apple-system, BlinkMacSystemFont, sans-serif" },
|
|
39
|
+
{ label: "Lato", value: "'Lato', -apple-system, BlinkMacSystemFont, sans-serif" },
|
|
40
|
+
{ label: "IBM Plex Sans", value: "'IBM Plex Sans', -apple-system, BlinkMacSystemFont, sans-serif" },
|
|
41
|
+
{ label: "Source Sans 3", value: "'Source Sans 3', 'Source Sans Pro', -apple-system, sans-serif" },
|
|
42
|
+
{ label: "Plus Jakarta Sans", value: "'Plus Jakarta Sans', -apple-system, BlinkMacSystemFont, sans-serif" },
|
|
43
|
+
{ label: "Noto Sans", value: "'Noto Sans', -apple-system, BlinkMacSystemFont, sans-serif" },
|
|
44
|
+
{ label: "Custom", value: "__custom__" },
|
|
45
|
+
];
|
|
46
|
+
|
|
47
|
+
const FONT_OPTIONS_MONO = [
|
|
48
|
+
{ label: "System Default", value: "'SF Mono', Consolas, 'Liberation Mono', Menlo, monospace" },
|
|
49
|
+
{ label: "JetBrains Mono", value: "'JetBrains Mono', 'Fira Code', Consolas, monospace" },
|
|
50
|
+
{ label: "Fira Code", value: "'Fira Code', 'JetBrains Mono', Consolas, monospace" },
|
|
51
|
+
{ label: "Cascadia Code", value: "'Cascadia Code', 'Fira Code', Consolas, monospace" },
|
|
52
|
+
{ label: "Commit Mono", value: "'Commit Mono', 'JetBrains Mono', Consolas, monospace" },
|
|
53
|
+
{ label: "IBM Plex Mono", value: "'IBM Plex Mono', Consolas, monospace" },
|
|
54
|
+
{ label: "Source Code Pro", value: "'Source Code Pro', Consolas, monospace" },
|
|
55
|
+
{ label: "Roboto Mono", value: "'Roboto Mono', Consolas, monospace" },
|
|
56
|
+
{ label: "Geist Mono", value: "'Geist Mono', Consolas, monospace" },
|
|
57
|
+
{ label: "Custom", value: "__custom__" },
|
|
58
|
+
];
|
|
59
|
+
|
|
28
60
|
/* ─── CSS injection engine ───────────────────────────────────────── */
|
|
29
61
|
|
|
30
62
|
const STYLE_ELEMENT_ID = "blazo-theme-overrides";
|
|
@@ -385,6 +417,43 @@ const styles = {
|
|
|
385
417
|
background: "var(--accent)",
|
|
386
418
|
flexShrink: 0,
|
|
387
419
|
}),
|
|
420
|
+
stickyBar: {
|
|
421
|
+
position: "sticky" as const,
|
|
422
|
+
top: 0,
|
|
423
|
+
zIndex: 50,
|
|
424
|
+
display: "flex",
|
|
425
|
+
alignItems: "center",
|
|
426
|
+
justifyContent: "space-between",
|
|
427
|
+
gap: 12,
|
|
428
|
+
padding: "10px 16px",
|
|
429
|
+
background: "var(--card)",
|
|
430
|
+
borderBottom: "1px solid var(--border)",
|
|
431
|
+
borderRadius: 8,
|
|
432
|
+
marginBottom: -8,
|
|
433
|
+
},
|
|
434
|
+
stickyThemeInfo: {
|
|
435
|
+
display: "flex",
|
|
436
|
+
flexDirection: "column" as const,
|
|
437
|
+
gap: 1,
|
|
438
|
+
minWidth: 0,
|
|
439
|
+
},
|
|
440
|
+
stickyThemeName: {
|
|
441
|
+
fontSize: 13,
|
|
442
|
+
fontWeight: 600,
|
|
443
|
+
color: "var(--foreground)",
|
|
444
|
+
whiteSpace: "nowrap" as const,
|
|
445
|
+
overflow: "hidden" as const,
|
|
446
|
+
textOverflow: "ellipsis" as const,
|
|
447
|
+
},
|
|
448
|
+
stickyStatus: {
|
|
449
|
+
fontSize: 11,
|
|
450
|
+
color: "var(--muted-foreground)",
|
|
451
|
+
},
|
|
452
|
+
stickyBtns: {
|
|
453
|
+
display: "flex",
|
|
454
|
+
gap: 8,
|
|
455
|
+
flexShrink: 0,
|
|
456
|
+
},
|
|
388
457
|
actions: {
|
|
389
458
|
display: "flex",
|
|
390
459
|
gap: 10,
|
|
@@ -420,6 +489,41 @@ const styles = {
|
|
|
420
489
|
alignSelf: "center" as const,
|
|
421
490
|
transition: "opacity 300ms ease",
|
|
422
491
|
},
|
|
492
|
+
fontRow: {
|
|
493
|
+
display: "flex",
|
|
494
|
+
flexDirection: "column" as const,
|
|
495
|
+
gap: 4,
|
|
496
|
+
padding: "6px 0",
|
|
497
|
+
},
|
|
498
|
+
fontLabel: {
|
|
499
|
+
fontSize: 13,
|
|
500
|
+
fontWeight: 400,
|
|
501
|
+
color: "var(--foreground)",
|
|
502
|
+
},
|
|
503
|
+
fontSelect: {
|
|
504
|
+
padding: "7px 10px",
|
|
505
|
+
borderRadius: 6,
|
|
506
|
+
border: "1.5px solid var(--border)",
|
|
507
|
+
background: "var(--muted)",
|
|
508
|
+
color: "var(--foreground)",
|
|
509
|
+
fontSize: 13,
|
|
510
|
+
cursor: "pointer",
|
|
511
|
+
outline: "none",
|
|
512
|
+
width: "100%",
|
|
513
|
+
},
|
|
514
|
+
fontCustomInput: {
|
|
515
|
+
marginTop: 6,
|
|
516
|
+
padding: "7px 10px",
|
|
517
|
+
borderRadius: 6,
|
|
518
|
+
border: "1.5px solid var(--border)",
|
|
519
|
+
background: "var(--muted)",
|
|
520
|
+
color: "var(--foreground)",
|
|
521
|
+
fontSize: 12,
|
|
522
|
+
fontFamily: "var(--font-mono, monospace)",
|
|
523
|
+
outline: "none",
|
|
524
|
+
width: "100%",
|
|
525
|
+
boxSizing: "border-box" as const,
|
|
526
|
+
},
|
|
423
527
|
overlay: {
|
|
424
528
|
position: "fixed" as const,
|
|
425
529
|
inset: 0,
|
|
@@ -683,6 +787,69 @@ function TokenRow({
|
|
|
683
787
|
);
|
|
684
788
|
}
|
|
685
789
|
|
|
790
|
+
/* ─── Font Row ───────────────────────────────────────────────────── */
|
|
791
|
+
|
|
792
|
+
function FontRow({
|
|
793
|
+
label,
|
|
794
|
+
token,
|
|
795
|
+
value,
|
|
796
|
+
options,
|
|
797
|
+
onChange,
|
|
798
|
+
}: {
|
|
799
|
+
label: string;
|
|
800
|
+
token: string;
|
|
801
|
+
value: string;
|
|
802
|
+
options: { label: string; value: string }[];
|
|
803
|
+
onChange: (token: string, value: string) => void;
|
|
804
|
+
}) {
|
|
805
|
+
const knownOption = options.find((o) => o.value !== "__custom__" && o.value === value);
|
|
806
|
+
const isCustom = !knownOption && value !== "";
|
|
807
|
+
const selectValue = isCustom ? "__custom__" : (value || options[0]!.value);
|
|
808
|
+
const [showCustom, setShowCustom] = useState(isCustom);
|
|
809
|
+
|
|
810
|
+
return (
|
|
811
|
+
<div style={styles.fontRow}>
|
|
812
|
+
<span style={styles.fontLabel}>{label}</span>
|
|
813
|
+
<select
|
|
814
|
+
style={styles.fontSelect}
|
|
815
|
+
value={selectValue}
|
|
816
|
+
onChange={(e) => {
|
|
817
|
+
if (e.target.value === "__custom__") {
|
|
818
|
+
setShowCustom(true);
|
|
819
|
+
} else {
|
|
820
|
+
setShowCustom(false);
|
|
821
|
+
onChange(token, e.target.value);
|
|
822
|
+
}
|
|
823
|
+
}}
|
|
824
|
+
>
|
|
825
|
+
{options.map((o) => (
|
|
826
|
+
<option key={o.value} value={o.value}>
|
|
827
|
+
{o.label}
|
|
828
|
+
</option>
|
|
829
|
+
))}
|
|
830
|
+
</select>
|
|
831
|
+
{showCustom && (
|
|
832
|
+
<input
|
|
833
|
+
type="text"
|
|
834
|
+
placeholder="e.g. 'Roboto', sans-serif"
|
|
835
|
+
defaultValue={isCustom ? value : ""}
|
|
836
|
+
style={styles.fontCustomInput}
|
|
837
|
+
onBlur={(e) => {
|
|
838
|
+
const v = e.target.value.trim();
|
|
839
|
+
if (v) onChange(token, v);
|
|
840
|
+
}}
|
|
841
|
+
onKeyDown={(e) => {
|
|
842
|
+
if (e.key === "Enter") {
|
|
843
|
+
const v = (e.target as HTMLInputElement).value.trim();
|
|
844
|
+
if (v) onChange(token, v);
|
|
845
|
+
}
|
|
846
|
+
}}
|
|
847
|
+
/>
|
|
848
|
+
)}
|
|
849
|
+
</div>
|
|
850
|
+
);
|
|
851
|
+
}
|
|
852
|
+
|
|
686
853
|
/* ─── Main Settings Page Component ───────────────────────────────── */
|
|
687
854
|
|
|
688
855
|
export function ThemeSettingsPage() {
|
|
@@ -840,6 +1007,52 @@ export function ThemeSettingsPage() {
|
|
|
840
1007
|
|
|
841
1008
|
return (
|
|
842
1009
|
<div style={styles.root}>
|
|
1010
|
+
{/* Sticky Save Bar */}
|
|
1011
|
+
<div style={styles.stickyBar}>
|
|
1012
|
+
<div style={styles.stickyThemeInfo}>
|
|
1013
|
+
<span style={styles.stickyThemeName}>
|
|
1014
|
+
{localTheme ? localTheme.name : "No theme selected"}
|
|
1015
|
+
</span>
|
|
1016
|
+
<span style={styles.stickyStatus}>
|
|
1017
|
+
{saving
|
|
1018
|
+
? "Saving\u2026"
|
|
1019
|
+
: savedAt
|
|
1020
|
+
? `Saved at ${savedAt}`
|
|
1021
|
+
: hasUnsaved
|
|
1022
|
+
? "Unsaved changes"
|
|
1023
|
+
: "Saved"}
|
|
1024
|
+
</span>
|
|
1025
|
+
</div>
|
|
1026
|
+
<div style={styles.stickyBtns}>
|
|
1027
|
+
<button
|
|
1028
|
+
type="button"
|
|
1029
|
+
style={{
|
|
1030
|
+
...styles.btnSecondary,
|
|
1031
|
+
padding: "7px 14px",
|
|
1032
|
+
fontSize: 12,
|
|
1033
|
+
}}
|
|
1034
|
+
onClick={handleReset}
|
|
1035
|
+
disabled={saving}
|
|
1036
|
+
>
|
|
1037
|
+
Reset
|
|
1038
|
+
</button>
|
|
1039
|
+
<button
|
|
1040
|
+
type="button"
|
|
1041
|
+
style={{
|
|
1042
|
+
...styles.btnPrimary,
|
|
1043
|
+
padding: "7px 14px",
|
|
1044
|
+
fontSize: 12,
|
|
1045
|
+
opacity: saving || !hasUnsaved ? 0.45 : 1,
|
|
1046
|
+
pointerEvents: saving || !hasUnsaved ? "none" : "auto",
|
|
1047
|
+
}}
|
|
1048
|
+
onClick={handleSave}
|
|
1049
|
+
disabled={saving || !hasUnsaved}
|
|
1050
|
+
>
|
|
1051
|
+
Save Theme
|
|
1052
|
+
</button>
|
|
1053
|
+
</div>
|
|
1054
|
+
</div>
|
|
1055
|
+
|
|
843
1056
|
{/* Header */}
|
|
844
1057
|
<div style={styles.header}>
|
|
845
1058
|
<h2 style={styles.title}>Theme</h2>
|
|
@@ -911,6 +1124,30 @@ export function ThemeSettingsPage() {
|
|
|
911
1124
|
</div>
|
|
912
1125
|
)}
|
|
913
1126
|
|
|
1127
|
+
{/* Typography */}
|
|
1128
|
+
{localTheme && (
|
|
1129
|
+
<div style={styles.section}>
|
|
1130
|
+
<p style={styles.sectionLabel}>Typography</p>
|
|
1131
|
+
<p style={{ ...styles.subtitle, marginTop: -8 }}>
|
|
1132
|
+
Choose fonts for the UI. Select a preset or enter a custom CSS font stack.
|
|
1133
|
+
</p>
|
|
1134
|
+
<FontRow
|
|
1135
|
+
label="Sans-serif (UI font)"
|
|
1136
|
+
token="--font-sans"
|
|
1137
|
+
value={localTheme.tokens["--font-sans"] ?? ""}
|
|
1138
|
+
options={FONT_OPTIONS_SANS}
|
|
1139
|
+
onChange={updateToken}
|
|
1140
|
+
/>
|
|
1141
|
+
<FontRow
|
|
1142
|
+
label="Monospace (code font)"
|
|
1143
|
+
token="--font-mono"
|
|
1144
|
+
value={localTheme.tokens["--font-mono"] ?? ""}
|
|
1145
|
+
options={FONT_OPTIONS_MONO}
|
|
1146
|
+
onChange={updateToken}
|
|
1147
|
+
/>
|
|
1148
|
+
</div>
|
|
1149
|
+
)}
|
|
1150
|
+
|
|
914
1151
|
{/* Radius */}
|
|
915
1152
|
{localTheme && (
|
|
916
1153
|
<div style={styles.section}>
|
|
@@ -931,36 +1168,6 @@ export function ThemeSettingsPage() {
|
|
|
931
1168
|
</div>
|
|
932
1169
|
)}
|
|
933
1170
|
|
|
934
|
-
{/* Actions */}
|
|
935
|
-
<div style={styles.actions}>
|
|
936
|
-
<button
|
|
937
|
-
type="button"
|
|
938
|
-
style={{
|
|
939
|
-
...styles.btnPrimary,
|
|
940
|
-
opacity: saving || !hasUnsaved ? 0.5 : 1,
|
|
941
|
-
pointerEvents: saving || !hasUnsaved ? "none" : "auto",
|
|
942
|
-
}}
|
|
943
|
-
onClick={handleSave}
|
|
944
|
-
disabled={saving || !hasUnsaved}
|
|
945
|
-
>
|
|
946
|
-
{saving ? "Saving\u2026" : "Save Theme"}
|
|
947
|
-
</button>
|
|
948
|
-
<button
|
|
949
|
-
type="button"
|
|
950
|
-
style={styles.btnSecondary}
|
|
951
|
-
onClick={handleReset}
|
|
952
|
-
disabled={saving}
|
|
953
|
-
>
|
|
954
|
-
Reset to Default
|
|
955
|
-
</button>
|
|
956
|
-
{savedAt && (
|
|
957
|
-
<span style={styles.saved}>Saved at {savedAt}</span>
|
|
958
|
-
)}
|
|
959
|
-
{hasUnsaved && !savedAt && (
|
|
960
|
-
<span style={{ ...styles.saved, color: "var(--chart-1)" }}>Unsaved changes</span>
|
|
961
|
-
)}
|
|
962
|
-
</div>
|
|
963
|
-
|
|
964
1171
|
{/* Import / Export */}
|
|
965
1172
|
<div style={styles.section}>
|
|
966
1173
|
<p style={styles.sectionLabel}>Share</p>
|