@orz/ui 0.0.0-stage → 0.1.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/LICENSE +29 -0
- package/README.md +541 -2
- package/dist/components/avatar.d.ts +14 -0
- package/dist/components/avatar.d.ts.map +1 -0
- package/dist/components/avatar.js +23 -0
- package/dist/components/avatar.js.map +1 -0
- package/dist/components/badge.d.ts +10 -0
- package/dist/components/badge.d.ts.map +1 -0
- package/dist/components/badge.js +26 -0
- package/dist/components/badge.js.map +1 -0
- package/dist/components/button.d.ts +11 -0
- package/dist/components/button.d.ts.map +1 -0
- package/dist/components/button.js +40 -0
- package/dist/components/button.js.map +1 -0
- package/dist/components/card.d.ts +12 -0
- package/dist/components/card.d.ts.map +1 -0
- package/dist/components/card.js +25 -0
- package/dist/components/card.js.map +1 -0
- package/dist/components/color-picker.d.ts +58 -0
- package/dist/components/color-picker.d.ts.map +1 -0
- package/dist/components/color-picker.js +950 -0
- package/dist/components/color-picker.js.map +1 -0
- package/dist/components/confirm-dialog.d.ts +26 -0
- package/dist/components/confirm-dialog.d.ts.map +1 -0
- package/dist/components/confirm-dialog.js +18 -0
- package/dist/components/confirm-dialog.js.map +1 -0
- package/dist/components/dialog.d.ts +26 -0
- package/dist/components/dialog.d.ts.map +1 -0
- package/dist/components/dialog.js +149 -0
- package/dist/components/dialog.js.map +1 -0
- package/dist/components/dropdown-menu.d.ts +30 -0
- package/dist/components/dropdown-menu.d.ts.map +1 -0
- package/dist/components/dropdown-menu.js +100 -0
- package/dist/components/dropdown-menu.js.map +1 -0
- package/dist/components/filter-menu.d.ts +98 -0
- package/dist/components/filter-menu.d.ts.map +1 -0
- package/dist/components/filter-menu.js +745 -0
- package/dist/components/filter-menu.js.map +1 -0
- package/dist/components/highlighted-input.d.ts +40 -0
- package/dist/components/highlighted-input.d.ts.map +1 -0
- package/dist/components/highlighted-input.js +95 -0
- package/dist/components/highlighted-input.js.map +1 -0
- package/dist/components/hover-card.d.ts +7 -0
- package/dist/components/hover-card.d.ts.map +1 -0
- package/dist/components/hover-card.js +22 -0
- package/dist/components/hover-card.js.map +1 -0
- package/dist/components/input.d.ts +4 -0
- package/dist/components/input.d.ts.map +1 -0
- package/dist/components/input.js +7 -0
- package/dist/components/input.js.map +1 -0
- package/dist/components/label.d.ts +5 -0
- package/dist/components/label.d.ts.map +1 -0
- package/dist/components/label.js +8 -0
- package/dist/components/label.js.map +1 -0
- package/dist/components/link-chip.d.ts +67 -0
- package/dist/components/link-chip.d.ts.map +1 -0
- package/dist/components/link-chip.js +71 -0
- package/dist/components/link-chip.js.map +1 -0
- package/dist/components/load-failure.d.ts +24 -0
- package/dist/components/load-failure.d.ts.map +1 -0
- package/dist/components/load-failure.js +27 -0
- package/dist/components/load-failure.js.map +1 -0
- package/dist/components/load-more.d.ts +15 -0
- package/dist/components/load-more.d.ts.map +1 -0
- package/dist/components/load-more.js +25 -0
- package/dist/components/load-more.js.map +1 -0
- package/dist/components/media-controls.d.ts +50 -0
- package/dist/components/media-controls.d.ts.map +1 -0
- package/dist/components/media-controls.js +71 -0
- package/dist/components/media-controls.js.map +1 -0
- package/dist/components/popover.d.ts +9 -0
- package/dist/components/popover.d.ts.map +1 -0
- package/dist/components/popover.js +82 -0
- package/dist/components/popover.js.map +1 -0
- package/dist/components/query-box.d.ts +78 -0
- package/dist/components/query-box.d.ts.map +1 -0
- package/dist/components/query-box.js +332 -0
- package/dist/components/query-box.js.map +1 -0
- package/dist/components/query-highlight.d.ts +14 -0
- package/dist/components/query-highlight.d.ts.map +1 -0
- package/dist/components/query-highlight.js +132 -0
- package/dist/components/query-highlight.js.map +1 -0
- package/dist/components/select.d.ts +19 -0
- package/dist/components/select.d.ts.map +1 -0
- package/dist/components/select.js +38 -0
- package/dist/components/select.js.map +1 -0
- package/dist/components/separator.d.ts +5 -0
- package/dist/components/separator.d.ts.map +1 -0
- package/dist/components/separator.js +9 -0
- package/dist/components/separator.js.map +1 -0
- package/dist/components/sheet.d.ts +16 -0
- package/dist/components/sheet.d.ts.map +1 -0
- package/dist/components/sheet.js +41 -0
- package/dist/components/sheet.js.map +1 -0
- package/dist/components/skeleton.d.ts +3 -0
- package/dist/components/skeleton.d.ts.map +1 -0
- package/dist/components/skeleton.js +7 -0
- package/dist/components/skeleton.js.map +1 -0
- package/dist/components/slider.d.ts +8 -0
- package/dist/components/slider.d.ts.map +1 -0
- package/dist/components/slider.js +14 -0
- package/dist/components/slider.js.map +1 -0
- package/dist/components/sonner.d.ts +4 -0
- package/dist/components/sonner.d.ts.map +1 -0
- package/dist/components/sonner.js +26 -0
- package/dist/components/sonner.js.map +1 -0
- package/dist/components/switch.d.ts +5 -0
- package/dist/components/switch.d.ts.map +1 -0
- package/dist/components/switch.js +8 -0
- package/dist/components/switch.js.map +1 -0
- package/dist/components/table.d.ts +11 -0
- package/dist/components/table.d.ts.map +1 -0
- package/dist/components/table.js +28 -0
- package/dist/components/table.js.map +1 -0
- package/dist/components/tabs.d.ts +8 -0
- package/dist/components/tabs.d.ts.map +1 -0
- package/dist/components/tabs.js +18 -0
- package/dist/components/tabs.js.map +1 -0
- package/dist/components/textarea.d.ts +4 -0
- package/dist/components/textarea.d.ts.map +1 -0
- package/dist/components/textarea.js +7 -0
- package/dist/components/textarea.js.map +1 -0
- package/dist/components/theme-editor/draft.d.ts +235 -0
- package/dist/components/theme-editor/draft.d.ts.map +1 -0
- package/dist/components/theme-editor/draft.js +638 -0
- package/dist/components/theme-editor/draft.js.map +1 -0
- package/dist/components/theme-editor/export-panel.d.ts +14 -0
- package/dist/components/theme-editor/export-panel.d.ts.map +1 -0
- package/dist/components/theme-editor/export-panel.js +39 -0
- package/dist/components/theme-editor/export-panel.js.map +1 -0
- package/dist/components/theme-editor/form.d.ts +69 -0
- package/dist/components/theme-editor/form.d.ts.map +1 -0
- package/dist/components/theme-editor/form.js +350 -0
- package/dist/components/theme-editor/form.js.map +1 -0
- package/dist/components/theme-editor/import-panel.d.ts +21 -0
- package/dist/components/theme-editor/import-panel.d.ts.map +1 -0
- package/dist/components/theme-editor/import-panel.js +117 -0
- package/dist/components/theme-editor/import-panel.js.map +1 -0
- package/dist/components/theme-editor/theme-list.d.ts +23 -0
- package/dist/components/theme-editor/theme-list.d.ts.map +1 -0
- package/dist/components/theme-editor/theme-list.js +42 -0
- package/dist/components/theme-editor/theme-list.js.map +1 -0
- package/dist/components/theme-editor/token-list.d.ts +30 -0
- package/dist/components/theme-editor/token-list.d.ts.map +1 -0
- package/dist/components/theme-editor/token-list.js +124 -0
- package/dist/components/theme-editor/token-list.js.map +1 -0
- package/dist/components/theme-editor/tokens.d.ts +37 -0
- package/dist/components/theme-editor/tokens.d.ts.map +1 -0
- package/dist/components/theme-editor/tokens.js +155 -0
- package/dist/components/theme-editor/tokens.js.map +1 -0
- package/dist/components/theme-editor.d.ts +26 -0
- package/dist/components/theme-editor.d.ts.map +1 -0
- package/dist/components/theme-editor.js +467 -0
- package/dist/components/theme-editor.js.map +1 -0
- package/dist/components/theme-menu.d.ts +28 -0
- package/dist/components/theme-menu.d.ts.map +1 -0
- package/dist/components/theme-menu.js +308 -0
- package/dist/components/theme-menu.js.map +1 -0
- package/dist/components/tooltip.d.ts +11 -0
- package/dist/components/tooltip.d.ts.map +1 -0
- package/dist/components/tooltip.js +18 -0
- package/dist/components/tooltip.js.map +1 -0
- package/dist/components/unsaved-changes-dialog.d.ts +39 -0
- package/dist/components/unsaved-changes-dialog.d.ts.map +1 -0
- package/dist/components/unsaved-changes-dialog.js +46 -0
- package/dist/components/unsaved-changes-dialog.js.map +1 -0
- package/dist/hooks/scroll-insets.d.ts +89 -0
- package/dist/hooks/scroll-insets.d.ts.map +1 -0
- package/dist/hooks/scroll-insets.js +265 -0
- package/dist/hooks/scroll-insets.js.map +1 -0
- package/dist/hooks/use-element-height.d.ts +11 -0
- package/dist/hooks/use-element-height.d.ts.map +1 -0
- package/dist/hooks/use-element-height.js +33 -0
- package/dist/hooks/use-element-height.js.map +1 -0
- package/dist/hooks/use-media-query.d.ts +22 -0
- package/dist/hooks/use-media-query.d.ts.map +1 -0
- package/dist/hooks/use-media-query.js +38 -0
- package/dist/hooks/use-media-query.js.map +1 -0
- package/dist/hooks/use-overlay-scrollbars.d.ts +31 -0
- package/dist/hooks/use-overlay-scrollbars.d.ts.map +1 -0
- package/dist/hooks/use-overlay-scrollbars.js +80 -0
- package/dist/hooks/use-overlay-scrollbars.js.map +1 -0
- package/dist/hooks/use-paged-append.d.ts +7 -0
- package/dist/hooks/use-paged-append.d.ts.map +1 -0
- package/dist/hooks/use-paged-append.js +53 -0
- package/dist/hooks/use-paged-append.js.map +1 -0
- package/dist/hooks/use-query-completion.d.ts +27 -0
- package/dist/hooks/use-query-completion.d.ts.map +1 -0
- package/dist/hooks/use-query-completion.js +100 -0
- package/dist/hooks/use-query-completion.js.map +1 -0
- package/dist/hooks/use-slash-shortcut.d.ts +9 -0
- package/dist/hooks/use-slash-shortcut.d.ts.map +1 -0
- package/dist/hooks/use-slash-shortcut.js +38 -0
- package/dist/hooks/use-slash-shortcut.js.map +1 -0
- package/dist/hooks/viewport-chrome.d.ts +50 -0
- package/dist/hooks/viewport-chrome.d.ts.map +1 -0
- package/dist/hooks/viewport-chrome.js +170 -0
- package/dist/hooks/viewport-chrome.js.map +1 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +2 -0
- package/dist/index.js.map +1 -0
- package/dist/lib/cell-width-table.d.ts +3 -0
- package/dist/lib/cell-width-table.d.ts.map +1 -0
- package/dist/lib/cell-width-table.js +6 -0
- package/dist/lib/cell-width-table.js.map +1 -0
- package/dist/lib/cell-width.d.ts +7 -0
- package/dist/lib/cell-width.d.ts.map +1 -0
- package/dist/lib/cell-width.js +50 -0
- package/dist/lib/cell-width.js.map +1 -0
- package/dist/lib/clipboard.d.ts +8 -0
- package/dist/lib/clipboard.d.ts.map +1 -0
- package/dist/lib/clipboard.js +19 -0
- package/dist/lib/clipboard.js.map +1 -0
- package/dist/lib/color.d.ts +40 -0
- package/dist/lib/color.d.ts.map +1 -0
- package/dist/lib/color.js +177 -0
- package/dist/lib/color.js.map +1 -0
- package/dist/lib/scroll-map.d.ts +20 -0
- package/dist/lib/scroll-map.d.ts.map +1 -0
- package/dist/lib/scroll-map.js +46 -0
- package/dist/lib/scroll-map.js.map +1 -0
- package/dist/lib/text-metrics.d.ts +33 -0
- package/dist/lib/text-metrics.d.ts.map +1 -0
- package/dist/lib/text-metrics.js +89 -0
- package/dist/lib/text-metrics.js.map +1 -0
- package/dist/lib/theme-file.d.ts +51 -0
- package/dist/lib/theme-file.d.ts.map +1 -0
- package/dist/lib/theme-file.js +511 -0
- package/dist/lib/theme-file.js.map +1 -0
- package/dist/lib/unsaved-guard.d.ts +33 -0
- package/dist/lib/unsaved-guard.d.ts.map +1 -0
- package/dist/lib/unsaved-guard.js +37 -0
- package/dist/lib/unsaved-guard.js.map +1 -0
- package/dist/lib/utils.d.ts +3 -0
- package/dist/lib/utils.d.ts.map +1 -0
- package/dist/lib/utils.js +6 -0
- package/dist/lib/utils.js.map +1 -0
- package/dist/theme/custom.d.ts +8 -0
- package/dist/theme/custom.d.ts.map +1 -0
- package/dist/theme/custom.js +56 -0
- package/dist/theme/custom.js.map +1 -0
- package/dist/theme/index.d.ts +6 -0
- package/dist/theme/index.d.ts.map +1 -0
- package/dist/theme/index.js +5 -0
- package/dist/theme/index.js.map +1 -0
- package/dist/theme/paint-guard.d.ts +20 -0
- package/dist/theme/paint-guard.d.ts.map +1 -0
- package/dist/theme/paint-guard.js +120 -0
- package/dist/theme/paint-guard.js.map +1 -0
- package/dist/theme/picks.d.ts +22 -0
- package/dist/theme/picks.d.ts.map +1 -0
- package/dist/theme/picks.js +43 -0
- package/dist/theme/picks.js.map +1 -0
- package/dist/theme/pref.d.ts +39 -0
- package/dist/theme/pref.d.ts.map +1 -0
- package/dist/theme/pref.js +129 -0
- package/dist/theme/pref.js.map +1 -0
- package/dist/theme/provider.d.ts +41 -0
- package/dist/theme/provider.d.ts.map +1 -0
- package/dist/theme/provider.js +103 -0
- package/dist/theme/provider.js.map +1 -0
- package/dist/theme/store.d.ts +104 -0
- package/dist/theme/store.d.ts.map +1 -0
- package/dist/theme/store.js +509 -0
- package/dist/theme/store.js.map +1 -0
- package/package.json +94 -3
- package/styles.css +79 -0
- package/theme.css +186 -0
- package/vendor/shadcn-tailwind.css +664 -0
|
@@ -0,0 +1,155 @@
|
|
|
1
|
+
const pair = (surface, surfaceLabel, text, textLabel) => ({
|
|
2
|
+
kind: "pair",
|
|
3
|
+
surface: { name: surface, label: surfaceLabel },
|
|
4
|
+
text: { name: text, label: textLabel },
|
|
5
|
+
});
|
|
6
|
+
const single = (name, label) => ({
|
|
7
|
+
kind: "single",
|
|
8
|
+
token: { name, label },
|
|
9
|
+
});
|
|
10
|
+
const ANSI = [
|
|
11
|
+
["black", "Black"],
|
|
12
|
+
["red", "Red"],
|
|
13
|
+
["green", "Green"],
|
|
14
|
+
["yellow", "Yellow"],
|
|
15
|
+
["blue", "Blue"],
|
|
16
|
+
["magenta", "Magenta"],
|
|
17
|
+
["cyan", "Cyan"],
|
|
18
|
+
["white", "White"],
|
|
19
|
+
];
|
|
20
|
+
export const UI_TOKEN_GROUPS = [
|
|
21
|
+
{
|
|
22
|
+
id: "surfaces",
|
|
23
|
+
label: "Surfaces",
|
|
24
|
+
rows: [
|
|
25
|
+
pair("background", "Background", "foreground", "Text"),
|
|
26
|
+
pair("card", "Card", "card-foreground", "Card text"),
|
|
27
|
+
pair("popover", "Popover", "popover-foreground", "Popover text"),
|
|
28
|
+
],
|
|
29
|
+
},
|
|
30
|
+
{
|
|
31
|
+
id: "actions",
|
|
32
|
+
label: "Actions",
|
|
33
|
+
rows: [
|
|
34
|
+
pair("primary", "Primary", "primary-foreground", "On primary"),
|
|
35
|
+
pair("secondary", "Secondary", "secondary-foreground", "On secondary"),
|
|
36
|
+
pair("accent", "Accent", "accent-foreground", "On accent"),
|
|
37
|
+
pair("muted", "Muted", "muted-foreground", "Muted text"),
|
|
38
|
+
single("destructive", "Destructive"),
|
|
39
|
+
],
|
|
40
|
+
},
|
|
41
|
+
{
|
|
42
|
+
id: "lines",
|
|
43
|
+
label: "Lines & focus",
|
|
44
|
+
rows: [
|
|
45
|
+
single("border", "Border"),
|
|
46
|
+
single("input", "Input border"),
|
|
47
|
+
single("ring", "Focus ring"),
|
|
48
|
+
],
|
|
49
|
+
},
|
|
50
|
+
{
|
|
51
|
+
id: "charts",
|
|
52
|
+
label: "Charts",
|
|
53
|
+
rows: [
|
|
54
|
+
{
|
|
55
|
+
kind: "strip",
|
|
56
|
+
tokens: [1, 2, 3, 4, 5].map((n) => ({
|
|
57
|
+
name: `chart-${n}`,
|
|
58
|
+
label: `Chart ${n}`,
|
|
59
|
+
})),
|
|
60
|
+
},
|
|
61
|
+
],
|
|
62
|
+
},
|
|
63
|
+
{
|
|
64
|
+
id: "sidebar",
|
|
65
|
+
label: "Sidebar",
|
|
66
|
+
rows: [
|
|
67
|
+
pair("sidebar", "Sidebar", "sidebar-foreground", "Sidebar text"),
|
|
68
|
+
pair("sidebar-primary", "Sidebar primary", "sidebar-primary-foreground", "On sidebar primary"),
|
|
69
|
+
pair("sidebar-accent", "Sidebar accent", "sidebar-accent-foreground", "On sidebar accent"),
|
|
70
|
+
single("sidebar-border", "Sidebar border"),
|
|
71
|
+
single("sidebar-ring", "Sidebar focus ring"),
|
|
72
|
+
],
|
|
73
|
+
},
|
|
74
|
+
{
|
|
75
|
+
id: "diff",
|
|
76
|
+
label: "Diff",
|
|
77
|
+
rows: [
|
|
78
|
+
single("diff-addition", "Addition"),
|
|
79
|
+
single("diff-deletion", "Deletion"),
|
|
80
|
+
],
|
|
81
|
+
},
|
|
82
|
+
];
|
|
83
|
+
export const TERMINAL_TOKEN_GROUPS = [
|
|
84
|
+
{
|
|
85
|
+
id: "terminal-text",
|
|
86
|
+
label: "Background & text",
|
|
87
|
+
rows: [
|
|
88
|
+
pair("terminal-background", "Background", "terminal-foreground", "Text"),
|
|
89
|
+
],
|
|
90
|
+
},
|
|
91
|
+
{
|
|
92
|
+
id: "ansi",
|
|
93
|
+
label: "ANSI colours",
|
|
94
|
+
rows: [
|
|
95
|
+
{
|
|
96
|
+
kind: "strip",
|
|
97
|
+
tokens: ANSI.map(([id, label]) => ({
|
|
98
|
+
name: `ansi-${id}`,
|
|
99
|
+
label,
|
|
100
|
+
})),
|
|
101
|
+
},
|
|
102
|
+
{
|
|
103
|
+
kind: "strip",
|
|
104
|
+
tokens: ANSI.map(([id, label]) => ({
|
|
105
|
+
name: `ansi-bright-${id}`,
|
|
106
|
+
label,
|
|
107
|
+
})),
|
|
108
|
+
},
|
|
109
|
+
],
|
|
110
|
+
},
|
|
111
|
+
{
|
|
112
|
+
id: "cursor",
|
|
113
|
+
label: "Cursor & selection",
|
|
114
|
+
rows: [
|
|
115
|
+
single("terminal-cursor", "Cursor"),
|
|
116
|
+
single("terminal-selection", "Selection"),
|
|
117
|
+
],
|
|
118
|
+
},
|
|
119
|
+
];
|
|
120
|
+
/** The token names a row or group holds, in display order. */
|
|
121
|
+
export function rowTokens(row) {
|
|
122
|
+
if (row.kind === "pair")
|
|
123
|
+
return [row.surface, row.text];
|
|
124
|
+
if (row.kind === "single")
|
|
125
|
+
return [row.token];
|
|
126
|
+
return row.tokens;
|
|
127
|
+
}
|
|
128
|
+
export function groupTokens(group) {
|
|
129
|
+
return group.rows.flatMap((row) => rowTokens(row).map((t) => t.name));
|
|
130
|
+
}
|
|
131
|
+
/**
|
|
132
|
+
* The surface a single token is drawn on, for its popover's contrast ratio.
|
|
133
|
+
* Pair members are absent: their row shows the pair's ratio.
|
|
134
|
+
*/
|
|
135
|
+
export const UI_CONTRAST_WITH = {
|
|
136
|
+
destructive: "background",
|
|
137
|
+
border: "background",
|
|
138
|
+
input: "background",
|
|
139
|
+
ring: "background",
|
|
140
|
+
"chart-1": "background",
|
|
141
|
+
"chart-2": "background",
|
|
142
|
+
"chart-3": "background",
|
|
143
|
+
"chart-4": "background",
|
|
144
|
+
"chart-5": "background",
|
|
145
|
+
"diff-addition": "background",
|
|
146
|
+
"diff-deletion": "background",
|
|
147
|
+
"sidebar-border": "sidebar",
|
|
148
|
+
"sidebar-ring": "sidebar",
|
|
149
|
+
};
|
|
150
|
+
export const TERMINAL_CONTRAST_WITH = Object.fromEntries([
|
|
151
|
+
...ANSI.flatMap(([id]) => [`ansi-${id}`, `ansi-bright-${id}`]),
|
|
152
|
+
"terminal-cursor",
|
|
153
|
+
"terminal-selection",
|
|
154
|
+
].map((name) => [name, "terminal-background"]));
|
|
155
|
+
//# sourceMappingURL=tokens.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"tokens.js","sourceRoot":"","sources":["../../../src/components/theme-editor/tokens.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
export type CustomThemeKind = "ui" | "terminal";
|
|
2
|
+
export interface ThemeEditorProps {
|
|
3
|
+
open: boolean;
|
|
4
|
+
onOpenChange(open: boolean): void;
|
|
5
|
+
/**
|
|
6
|
+
* The custom theme to open, as `ThemeMenu`'s `onCustomize(id?)` passes it. Absent, an id the
|
|
7
|
+
* store does not hold, or a theme of a kind this store cannot show: a new theme. Read when
|
|
8
|
+
* `open` turns true; changing it while open selects that theme (with the unsaved-changes
|
|
9
|
+
* prompt).
|
|
10
|
+
*/
|
|
11
|
+
themeId?: string;
|
|
12
|
+
/**
|
|
13
|
+
* The kind of a new theme, as `ThemeMenu`'s `onCustomize(id, slot)` passes its slot. Unset, or a
|
|
14
|
+
* slot the store lacks: "ui" when the store has a UI slot, else "terminal". Read when `open`
|
|
15
|
+
* turns true, as `themeId` is.
|
|
16
|
+
*/
|
|
17
|
+
kind?: CustomThemeKind;
|
|
18
|
+
}
|
|
19
|
+
/**
|
|
20
|
+
* The custom theme editor: a panel docked to the right that edits the
|
|
21
|
+
* store's custom themes while the page previews the draft. A store with a
|
|
22
|
+
* UI slot edits UI themes, one with a terminal slot terminal themes, and one
|
|
23
|
+
* with both, both kinds.
|
|
24
|
+
*/
|
|
25
|
+
export declare function ThemeEditor(props: ThemeEditorProps): import("react").JSX.Element;
|
|
26
|
+
//# sourceMappingURL=theme-editor.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"theme-editor.d.ts","sourceRoot":"","sources":["../../src/components/theme-editor.tsx"],"names":[],"mappings":"AA6DA,MAAM,MAAM,eAAe,GAAG,IAAI,GAAG,UAAU,CAAC;AAEhD,MAAM,WAAW,gBAAgB;IAC/B,IAAI,EAAE,OAAO,CAAC;IACd,YAAY,CAAC,IAAI,EAAE,OAAO,GAAG,IAAI,CAAC;IAClC;;;;;OAKG;IACH,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB;;;;OAIG;IACH,IAAI,CAAC,EAAE,eAAe,CAAC;CACxB;AAeD;;;;;GAKG;AACH,wBAAgB,WAAW,CAAC,KAAK,EAAE,gBAAgB,+BA4ClD"}
|
|
@@ -0,0 +1,467 @@
|
|
|
1
|
+
import { Fragment as _Fragment, jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { DEFAULT_VARIANT, isCustomTerminalTheme, } from "@orz/themes";
|
|
3
|
+
import { XIcon } from "lucide-react";
|
|
4
|
+
import { useEffect, useLayoutEffect, useMemo, useRef, useState, } from "react";
|
|
5
|
+
import { serializeThemeFile } from "../lib/theme-file.js";
|
|
6
|
+
import { useDirtySource } from "../lib/unsaved-guard.js";
|
|
7
|
+
import { useTerminalTheme, useTheme, useThemeSlots, } from "../theme/index.js";
|
|
8
|
+
import { Button } from "./button.js";
|
|
9
|
+
import { ConfirmDialog } from "./confirm-dialog.js";
|
|
10
|
+
import { Sheet, SheetContent, SheetDescription, SheetFooter, SheetHeader, SheetTitle, } from "./sheet.js";
|
|
11
|
+
import { duplicate, newTheme, newThemeId, sameTerminal, useThemeDraft, } from "./theme-editor/draft.js";
|
|
12
|
+
import { ALL_THEMES_FILE_NAME, ExportPanel, themeFileName, } from "./theme-editor/export-panel.js";
|
|
13
|
+
import { TerminalThemeForm, ThemeForm, } from "./theme-editor/form.js";
|
|
14
|
+
import { ImportPanel } from "./theme-editor/import-panel.js";
|
|
15
|
+
import { displayLabel, ThemeList } from "./theme-editor/theme-list.js";
|
|
16
|
+
import { UnsavedChangesDialog } from "./unsaved-changes-dialog.js";
|
|
17
|
+
const FORM = { kind: "form" };
|
|
18
|
+
const SCHEMES = ["light", "dark"];
|
|
19
|
+
const other = (scheme) => scheme === "light" ? "dark" : "light";
|
|
20
|
+
const kindOf = (custom) => isCustomTerminalTheme(custom) ? "terminal" : "ui";
|
|
21
|
+
/**
|
|
22
|
+
* The custom theme editor: a panel docked to the right that edits the
|
|
23
|
+
* store's custom themes while the page previews the draft. A store with a
|
|
24
|
+
* UI slot edits UI themes, one with a terminal slot terminal themes, and one
|
|
25
|
+
* with both, both kinds.
|
|
26
|
+
*/
|
|
27
|
+
export function ThemeEditor(props) {
|
|
28
|
+
// Esc, the outside click and ✕ all arrive here; the session decides
|
|
29
|
+
// whether to ask first.
|
|
30
|
+
const requestClose = useRef(() => props.onOpenChange(false));
|
|
31
|
+
// The sheet keeps its content through the exit animation, so a reopening
|
|
32
|
+
// in that window would otherwise get the closing session back.
|
|
33
|
+
const [opening, setOpening] = useState({ open: props.open, count: 0 });
|
|
34
|
+
if (opening.open !== props.open) {
|
|
35
|
+
setOpening({
|
|
36
|
+
open: props.open,
|
|
37
|
+
count: opening.count + (props.open ? 1 : 0),
|
|
38
|
+
});
|
|
39
|
+
}
|
|
40
|
+
return (_jsx(Sheet, { open: props.open, onOpenChange: (open) => {
|
|
41
|
+
if (!open)
|
|
42
|
+
requestClose.current();
|
|
43
|
+
}, children: _jsx(SheetContent, { side: "right", overlay: "clear", showCloseButton: false, className: "gap-0 data-[side=right]:w-full data-[side=right]:sm:max-w-[460px]", onEscapeKeyDown: (event) => {
|
|
44
|
+
// An Esc that clears the colour filter or ends an IME composition is not a close.
|
|
45
|
+
const { target } = event;
|
|
46
|
+
if (event.isComposing ||
|
|
47
|
+
(target instanceof Element && target.closest("[data-keeps-escape]"))) {
|
|
48
|
+
event.preventDefault();
|
|
49
|
+
}
|
|
50
|
+
}, children: _jsx(EditorSession, { ...props, requestClose: requestClose }, opening.count) }) }));
|
|
51
|
+
}
|
|
52
|
+
/** One opening of the editor; mounted while the sheet is open. */
|
|
53
|
+
function EditorSession({ open, themeId, kind, onOpenChange, requestClose, }) {
|
|
54
|
+
const ui = useTheme();
|
|
55
|
+
const terminal = useTerminalTheme();
|
|
56
|
+
const slots = useThemeSlots();
|
|
57
|
+
const { customThemes: everyCustom, pref, setPref, preview } = ui;
|
|
58
|
+
// Without a UI slot useTheme() answers a fallback list.
|
|
59
|
+
const themes = slots.ui ? ui.themes : NO_THEMES;
|
|
60
|
+
const offered = (k) => slots[k];
|
|
61
|
+
const newKind = kind !== undefined && offered(kind) ? kind : slots.ui ? "ui" : "terminal";
|
|
62
|
+
// Writes go through the whole stored list, so a kind this store cannot
|
|
63
|
+
// show is written back untouched.
|
|
64
|
+
const customs = useMemo(() => everyCustom.filter((c) => slots[kindOf(c)]), [everyCustom, slots]);
|
|
65
|
+
const customIds = useMemo(() => new Set(everyCustom.map((c) => c.id)), [everyCustom]);
|
|
66
|
+
const baseThemes = useMemo(() => themes.filter((t) => !customIds.has(t.id)), [themes, customIds]);
|
|
67
|
+
const palettes = useMemo(() => (slots.terminal ? terminal.themes : themes).filter((t) => !customIds.has(t.id)), [slots.terminal, terminal.themes, themes, customIds]);
|
|
68
|
+
// Custom themes are never bases, so the draft composes against the base
|
|
69
|
+
// lists alone.
|
|
70
|
+
const composeList = useMemo(() => [...baseThemes, ...palettes], [baseThemes, palettes]);
|
|
71
|
+
// Names are unique across both kinds and both slots' base themes, so the
|
|
72
|
+
// two pickers never show two rows with one name.
|
|
73
|
+
const labels = useMemo(() => [
|
|
74
|
+
...everyCustom.map((c) => c.label),
|
|
75
|
+
...composeList.map((t) => t.label),
|
|
76
|
+
], [everyCustom, composeList]);
|
|
77
|
+
const start = (id, kindForNew = newKind) => {
|
|
78
|
+
const custom = id === undefined ? undefined : customs.find((c) => c.id === id);
|
|
79
|
+
if (custom !== undefined)
|
|
80
|
+
return { draft: custom, saved: custom };
|
|
81
|
+
// The committed picks, not what the page shows: that may be this
|
|
82
|
+
// session's preview.
|
|
83
|
+
const committed = themes.find((t) => t.id === pref.ui?.theme) ?? ui.theme;
|
|
84
|
+
if (kindForNew === "terminal") {
|
|
85
|
+
const pick = pref.terminal;
|
|
86
|
+
const picked = pick && terminal.themes.find((t) => t.id === pick.theme);
|
|
87
|
+
const shown = picked !== undefined
|
|
88
|
+
? {
|
|
89
|
+
theme: picked,
|
|
90
|
+
variant: pick?.variant ?? DEFAULT_VARIANT,
|
|
91
|
+
following: false,
|
|
92
|
+
}
|
|
93
|
+
: slots.ui
|
|
94
|
+
? {
|
|
95
|
+
theme: committed,
|
|
96
|
+
variant: pref.ui?.variant ?? DEFAULT_VARIANT,
|
|
97
|
+
following: true,
|
|
98
|
+
}
|
|
99
|
+
: {
|
|
100
|
+
theme: terminal.theme,
|
|
101
|
+
variant: terminal.variant,
|
|
102
|
+
following: false,
|
|
103
|
+
};
|
|
104
|
+
const draft = newTheme({
|
|
105
|
+
kind: "terminal",
|
|
106
|
+
pref,
|
|
107
|
+
terminal: shown,
|
|
108
|
+
baseThemes,
|
|
109
|
+
customs: everyCustom,
|
|
110
|
+
labels,
|
|
111
|
+
});
|
|
112
|
+
return { draft, saved: null };
|
|
113
|
+
}
|
|
114
|
+
const draft = newTheme({
|
|
115
|
+
pref,
|
|
116
|
+
resolved: {
|
|
117
|
+
theme: committed,
|
|
118
|
+
variant: pref.ui?.variant ?? DEFAULT_VARIANT,
|
|
119
|
+
},
|
|
120
|
+
baseThemes,
|
|
121
|
+
customs: everyCustom,
|
|
122
|
+
labels,
|
|
123
|
+
});
|
|
124
|
+
return { draft, saved: null };
|
|
125
|
+
};
|
|
126
|
+
const [initial] = useState(() => start(themeId));
|
|
127
|
+
const { state, draft, dirty, composed, dispatch } = useThemeDraft(initial, composeList);
|
|
128
|
+
const terminalDraft = isCustomTerminalTheme(draft) ? draft : null;
|
|
129
|
+
const uiDraft = isCustomTerminalTheme(draft) ? null : draft;
|
|
130
|
+
const composedUi = terminalDraft ? null : composed;
|
|
131
|
+
const composedTerminal = terminalDraft
|
|
132
|
+
? composed
|
|
133
|
+
: null;
|
|
134
|
+
const shownScheme = (custom) => isCustomTerminalTheme(custom) || !slots.ui
|
|
135
|
+
? terminal.colorScheme
|
|
136
|
+
: ui.colorScheme;
|
|
137
|
+
const firstScheme = (custom) => {
|
|
138
|
+
const scheme = shownScheme(custom);
|
|
139
|
+
return custom[scheme]
|
|
140
|
+
? scheme
|
|
141
|
+
: (SCHEMES.find((s) => custom[s] !== undefined) ?? scheme);
|
|
142
|
+
};
|
|
143
|
+
const [tab, setTab] = useState(() => firstScheme(initial.draft));
|
|
144
|
+
// The side the page shows: the last Light or Dark tab.
|
|
145
|
+
const [uiScheme, setUiScheme] = useState(() => firstScheme(initial.draft));
|
|
146
|
+
const viewFor = (custom, side) => ({
|
|
147
|
+
split: isCustomTerminalTheme(custom) || !sameTerminal(custom),
|
|
148
|
+
side: custom[side] ? side : other(side),
|
|
149
|
+
});
|
|
150
|
+
const [terminalView, setTerminalView] = useState(() => viewFor(initial.draft, firstScheme(initial.draft)));
|
|
151
|
+
const [panel, setPanel] = useState(FORM);
|
|
152
|
+
const [formKey, setFormKey] = useState(0);
|
|
153
|
+
const [prompt, setPrompt] = useState(null);
|
|
154
|
+
const [confirmingDelete, setConfirmingDelete] = useState(false);
|
|
155
|
+
const [saveError, setSaveError] = useState(null);
|
|
156
|
+
const [importError, setImportError] = useState(null);
|
|
157
|
+
const both = draft.light !== undefined && draft.dark !== undefined;
|
|
158
|
+
const shared = uiDraft !== null && both && !terminalView.split && sameTerminal(uiDraft);
|
|
159
|
+
// The colours each side last showed, so a side whose base disappears can
|
|
160
|
+
// still be kept as fully custom.
|
|
161
|
+
const lastShown = useRef({});
|
|
162
|
+
const lastShownTerminal = useRef({});
|
|
163
|
+
useEffect(() => {
|
|
164
|
+
for (const scheme of SCHEMES) {
|
|
165
|
+
const palette = composedUi?.[scheme];
|
|
166
|
+
if (palette)
|
|
167
|
+
lastShown.current[scheme] = palette;
|
|
168
|
+
const terminalPalette = composedTerminal?.[scheme];
|
|
169
|
+
if (terminalPalette) {
|
|
170
|
+
lastShownTerminal.current[scheme] = terminalPalette.terminal;
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
}, [composedUi, composedTerminal]);
|
|
174
|
+
// A drag repaints at most once a frame.
|
|
175
|
+
const [pending, setPending] = useState(null);
|
|
176
|
+
const pendingNext = useRef(null);
|
|
177
|
+
const frame = useRef(0);
|
|
178
|
+
const queuePreview = (next) => {
|
|
179
|
+
pendingNext.current = next;
|
|
180
|
+
if (next === null) {
|
|
181
|
+
cancelAnimationFrame(frame.current);
|
|
182
|
+
frame.current = 0;
|
|
183
|
+
setPending(null);
|
|
184
|
+
return;
|
|
185
|
+
}
|
|
186
|
+
if (frame.current !== 0)
|
|
187
|
+
return;
|
|
188
|
+
frame.current = requestAnimationFrame(() => {
|
|
189
|
+
frame.current = 0;
|
|
190
|
+
setPending(pendingNext.current);
|
|
191
|
+
});
|
|
192
|
+
};
|
|
193
|
+
const shownTheme = useMemo(() => {
|
|
194
|
+
if (composedUi === null)
|
|
195
|
+
return null;
|
|
196
|
+
const scheme = composedUi[uiScheme] ? uiScheme : other(uiScheme);
|
|
197
|
+
const palette = composedUi[scheme];
|
|
198
|
+
if (palette === undefined)
|
|
199
|
+
return null;
|
|
200
|
+
let { tokens, terminal: terminalTokens } = palette;
|
|
201
|
+
// On the Terminal tab the page keeps its UI side and shows the terminal
|
|
202
|
+
// being edited.
|
|
203
|
+
const edited = tab === "terminal" && both && !shared ? terminalView.side : scheme;
|
|
204
|
+
const editedPalette = composedUi[edited];
|
|
205
|
+
if (editedPalette)
|
|
206
|
+
terminalTokens = editedPalette.terminal;
|
|
207
|
+
if (pending?.kind === "ui" && pending.scheme === scheme) {
|
|
208
|
+
tokens = { ...tokens, [pending.name]: pending.value };
|
|
209
|
+
}
|
|
210
|
+
else if (pending?.kind === "terminal") {
|
|
211
|
+
terminalTokens = { ...terminalTokens, [pending.name]: pending.value };
|
|
212
|
+
}
|
|
213
|
+
return {
|
|
214
|
+
theme: {
|
|
215
|
+
...composedUi,
|
|
216
|
+
[scheme]: { ...palette, tokens, terminal: terminalTokens },
|
|
217
|
+
},
|
|
218
|
+
scheme,
|
|
219
|
+
};
|
|
220
|
+
}, [composedUi, uiScheme, tab, both, shared, terminalView.side, pending]);
|
|
221
|
+
// A terminal theme previews through the terminal slot alone, on the side
|
|
222
|
+
// of the active tab (or the one it has).
|
|
223
|
+
const shownTerminal = useMemo(() => {
|
|
224
|
+
if (composedTerminal === null)
|
|
225
|
+
return null;
|
|
226
|
+
const scheme = composedTerminal[uiScheme] ? uiScheme : other(uiScheme);
|
|
227
|
+
const palette = composedTerminal[scheme];
|
|
228
|
+
if (palette === undefined)
|
|
229
|
+
return null;
|
|
230
|
+
if (pending?.kind !== "terminal") {
|
|
231
|
+
return { theme: composedTerminal, scheme };
|
|
232
|
+
}
|
|
233
|
+
return {
|
|
234
|
+
theme: {
|
|
235
|
+
...composedTerminal,
|
|
236
|
+
[scheme]: {
|
|
237
|
+
terminal: { ...palette.terminal, [pending.name]: pending.value },
|
|
238
|
+
},
|
|
239
|
+
},
|
|
240
|
+
scheme,
|
|
241
|
+
};
|
|
242
|
+
}, [composedTerminal, uiScheme, pending]);
|
|
243
|
+
// Set once the editor hands back control, so a re-render on the way out
|
|
244
|
+
// (Save re-composes the lists) does not preview again.
|
|
245
|
+
const closing = useRef(false);
|
|
246
|
+
useEffect(() => {
|
|
247
|
+
if (closing.current)
|
|
248
|
+
return;
|
|
249
|
+
if (open && shownTerminal !== null) {
|
|
250
|
+
preview({
|
|
251
|
+
terminal: { theme: shownTerminal.theme, mode: shownTerminal.scheme },
|
|
252
|
+
});
|
|
253
|
+
return;
|
|
254
|
+
}
|
|
255
|
+
if (!open || shownTheme === null) {
|
|
256
|
+
preview(null);
|
|
257
|
+
return;
|
|
258
|
+
}
|
|
259
|
+
preview({
|
|
260
|
+
ui: { theme: shownTheme.theme, mode: shownTheme.scheme },
|
|
261
|
+
// Shows the edited terminal even when the user picked a separate one.
|
|
262
|
+
...(tab === "terminal" && slots.terminal
|
|
263
|
+
? { terminal: "follow" }
|
|
264
|
+
: {}),
|
|
265
|
+
});
|
|
266
|
+
}, [open, shownTheme, shownTerminal, tab, slots.terminal, preview]);
|
|
267
|
+
useEffect(() => () => {
|
|
268
|
+
cancelAnimationFrame(frame.current);
|
|
269
|
+
preview(null);
|
|
270
|
+
}, [preview]);
|
|
271
|
+
useDirtySource(() => dirty);
|
|
272
|
+
const finish = () => {
|
|
273
|
+
closing.current = true;
|
|
274
|
+
preview(null);
|
|
275
|
+
onOpenChange(false);
|
|
276
|
+
};
|
|
277
|
+
const guarded = (proceed) => {
|
|
278
|
+
if (dirty)
|
|
279
|
+
setPrompt({ proceed });
|
|
280
|
+
else
|
|
281
|
+
proceed();
|
|
282
|
+
};
|
|
283
|
+
const close = () => guarded(finish);
|
|
284
|
+
useEffect(() => {
|
|
285
|
+
requestClose.current = close;
|
|
286
|
+
});
|
|
287
|
+
const load = (next) => {
|
|
288
|
+
dispatch({ type: "load", ...next });
|
|
289
|
+
lastShown.current = {};
|
|
290
|
+
lastShownTerminal.current = {};
|
|
291
|
+
queuePreview(null);
|
|
292
|
+
setPanel(FORM);
|
|
293
|
+
setSaveError(null);
|
|
294
|
+
// A terminal theme has no Terminal tab.
|
|
295
|
+
if (isCustomTerminalTheme(next.draft) && tab === "terminal") {
|
|
296
|
+
setTab(uiScheme);
|
|
297
|
+
}
|
|
298
|
+
setTerminalView(viewFor(next.draft, uiScheme));
|
|
299
|
+
setFormKey((k) => k + 1);
|
|
300
|
+
};
|
|
301
|
+
const select = (id, kindForNew) => guarded(() => load(start(id, kindForNew)));
|
|
302
|
+
const seenThemeId = useRef(themeId);
|
|
303
|
+
useEffect(() => {
|
|
304
|
+
// On the way out the app clears the id; that is not a new selection.
|
|
305
|
+
if (!open || closing.current)
|
|
306
|
+
return;
|
|
307
|
+
if (themeId === seenThemeId.current)
|
|
308
|
+
return;
|
|
309
|
+
seenThemeId.current = themeId;
|
|
310
|
+
if (themeId !== undefined && themeId === draft.id)
|
|
311
|
+
return;
|
|
312
|
+
select(themeId);
|
|
313
|
+
});
|
|
314
|
+
const stored = customIds.has(draft.id);
|
|
315
|
+
const deletedElsewhere = state.saved !== null && !stored;
|
|
316
|
+
const label = draft.label.trim();
|
|
317
|
+
const clash = label === ""
|
|
318
|
+
? undefined
|
|
319
|
+
: [...everyCustom.filter((c) => c.id !== draft.id), ...composeList].find((t) => t.label.trim().toLowerCase() === label.toLowerCase());
|
|
320
|
+
const nameError = label === ""
|
|
321
|
+
? "A theme needs a name."
|
|
322
|
+
: clash
|
|
323
|
+
? `The theme "${clash.label}" already has this name.`
|
|
324
|
+
: null;
|
|
325
|
+
const sideError = draft.light === undefined && draft.dark === undefined
|
|
326
|
+
? "A theme needs a light or a dark side."
|
|
327
|
+
: null;
|
|
328
|
+
const save = () => {
|
|
329
|
+
if (nameError !== null || sideError !== null)
|
|
330
|
+
return;
|
|
331
|
+
let theme = { ...draft, label };
|
|
332
|
+
// A new theme's id was free when it was made; another tab may have
|
|
333
|
+
// taken it since.
|
|
334
|
+
if (state.saved === null && stored) {
|
|
335
|
+
theme = {
|
|
336
|
+
...theme,
|
|
337
|
+
id: newThemeId([
|
|
338
|
+
...everyCustom.map((c) => c.id),
|
|
339
|
+
...composeList.map((t) => t.id),
|
|
340
|
+
]),
|
|
341
|
+
};
|
|
342
|
+
}
|
|
343
|
+
const saved = theme;
|
|
344
|
+
const next = everyCustom.some((c) => c.id === saved.id)
|
|
345
|
+
? everyCustom.map((c) => (c.id === saved.id ? saved : c))
|
|
346
|
+
: [...everyCustom, saved];
|
|
347
|
+
try {
|
|
348
|
+
ui.setCustomThemes(next);
|
|
349
|
+
}
|
|
350
|
+
catch (error) {
|
|
351
|
+
setSaveError(`The theme could not be saved: ${error instanceof Error ? error.message : String(error)}`);
|
|
352
|
+
return;
|
|
353
|
+
}
|
|
354
|
+
dispatch({ type: "markSaved", saved });
|
|
355
|
+
// The saved theme stays on screen: reverting to the old pick right
|
|
356
|
+
// after the page showed the new one would look like the save failed.
|
|
357
|
+
if (isCustomTerminalTheme(saved)) {
|
|
358
|
+
const mode = pref.terminal?.mode ?? pref.ui?.mode ?? "system";
|
|
359
|
+
setPref({ ...pref, terminal: { theme: saved.id, mode } });
|
|
360
|
+
}
|
|
361
|
+
else {
|
|
362
|
+
setPref({
|
|
363
|
+
...pref,
|
|
364
|
+
ui: { theme: saved.id, mode: pref.ui?.mode ?? uiScheme },
|
|
365
|
+
});
|
|
366
|
+
}
|
|
367
|
+
finish();
|
|
368
|
+
};
|
|
369
|
+
// The order the list shows: UI themes, then terminal themes.
|
|
370
|
+
const listed = useMemo(() => [
|
|
371
|
+
...customs.filter((c) => !isCustomTerminalTheme(c)),
|
|
372
|
+
...customs.filter(isCustomTerminalTheme),
|
|
373
|
+
], [customs]);
|
|
374
|
+
// After Delete the editor stays open on a neighbour of the deleted theme,
|
|
375
|
+
// or a new one of its kind; loaded once the store's list and pick reflect
|
|
376
|
+
// the delete.
|
|
377
|
+
const [afterDelete, setAfterDelete] = useState(null);
|
|
378
|
+
useLayoutEffect(() => {
|
|
379
|
+
if (afterDelete === null)
|
|
380
|
+
return;
|
|
381
|
+
setAfterDelete(null);
|
|
382
|
+
load(start(afterDelete.id, afterDelete.kind));
|
|
383
|
+
});
|
|
384
|
+
const remove = () => {
|
|
385
|
+
setConfirmingDelete(false);
|
|
386
|
+
const at = listed.findIndex((c) => c.id === draft.id);
|
|
387
|
+
const next = listed[at + 1] ?? listed[at - 1];
|
|
388
|
+
try {
|
|
389
|
+
ui.setCustomThemes(everyCustom.filter((c) => c.id !== draft.id));
|
|
390
|
+
}
|
|
391
|
+
catch (error) {
|
|
392
|
+
setSaveError(`The theme could not be deleted: ${error instanceof Error ? error.message : String(error)}`);
|
|
393
|
+
return;
|
|
394
|
+
}
|
|
395
|
+
setAfterDelete({ id: next?.id, kind: kindOf(draft) });
|
|
396
|
+
};
|
|
397
|
+
const copy = () => guarded(() => load({
|
|
398
|
+
draft: duplicate(state.saved ?? draft, {
|
|
399
|
+
baseThemes,
|
|
400
|
+
customs: everyCustom,
|
|
401
|
+
labels,
|
|
402
|
+
}),
|
|
403
|
+
saved: null,
|
|
404
|
+
}));
|
|
405
|
+
const importThemes = (picked) => guarded(() => {
|
|
406
|
+
const taken = [
|
|
407
|
+
...everyCustom.map((c) => c.id),
|
|
408
|
+
...composeList.map((t) => t.id),
|
|
409
|
+
];
|
|
410
|
+
const added = picked.map((theme) => {
|
|
411
|
+
const id = newThemeId(taken);
|
|
412
|
+
taken.push(id);
|
|
413
|
+
return { ...theme, id };
|
|
414
|
+
});
|
|
415
|
+
try {
|
|
416
|
+
ui.setCustomThemes([...everyCustom, ...added]);
|
|
417
|
+
}
|
|
418
|
+
catch (error) {
|
|
419
|
+
setImportError(`The themes could not be imported: ${error instanceof Error ? error.message : String(error)}`);
|
|
420
|
+
return;
|
|
421
|
+
}
|
|
422
|
+
const first = added.find((c) => offered(kindOf(c)));
|
|
423
|
+
if (first)
|
|
424
|
+
load({ draft: first, saved: first });
|
|
425
|
+
});
|
|
426
|
+
const exported = useMemo(() => {
|
|
427
|
+
if (panel.kind !== "export")
|
|
428
|
+
return null;
|
|
429
|
+
if (panel.scope === "all") {
|
|
430
|
+
return {
|
|
431
|
+
title: "Export all themes",
|
|
432
|
+
text: serializeThemeFile(everyCustom, composeList),
|
|
433
|
+
fileName: ALL_THEMES_FILE_NAME,
|
|
434
|
+
};
|
|
435
|
+
}
|
|
436
|
+
const label = displayLabel(draft.label);
|
|
437
|
+
return {
|
|
438
|
+
title: `Export ${label}`,
|
|
439
|
+
text: serializeThemeFile([{ ...draft, label }], composeList),
|
|
440
|
+
fileName: themeFileName(label),
|
|
441
|
+
};
|
|
442
|
+
}, [panel, everyCustom, composeList, draft]);
|
|
443
|
+
// The button that switched panels is gone with the old panel; without
|
|
444
|
+
// this, focus would fall back to the sheet itself.
|
|
445
|
+
const body = useRef(null);
|
|
446
|
+
const shownPanel = useRef(panel);
|
|
447
|
+
useEffect(() => {
|
|
448
|
+
if (shownPanel.current === panel)
|
|
449
|
+
return;
|
|
450
|
+
shownPanel.current = panel;
|
|
451
|
+
body.current?.querySelector("input, textarea")?.focus();
|
|
452
|
+
}, [panel]);
|
|
453
|
+
const changeTab = (next) => {
|
|
454
|
+
setTab(next);
|
|
455
|
+
if (next !== "terminal")
|
|
456
|
+
setUiScheme(next);
|
|
457
|
+
};
|
|
458
|
+
return (_jsxs(_Fragment, { children: [_jsxs(SheetHeader, { className: "gap-3 border-b", children: [_jsxs("div", { className: "flex items-center gap-2", children: [_jsx(SheetTitle, { className: "flex-1", children: "Custom themes" }), _jsx(Button, { type: "button", variant: "ghost", size: "icon-sm", "aria-label": "Close", onClick: close, children: _jsx(XIcon, {}) })] }), _jsx(SheetDescription, { className: "sr-only", children: "Edit a custom theme. The page previews every change until you save or close." }), _jsx(ThemeList, { customs: customs, kinds: ["ui", "terminal"].filter(offered), draft: draft, dirty: dirty, themes: composeList, onSelect: (id) => select(id), onNew: (k) => select(undefined, k), onImport: () => {
|
|
459
|
+
setImportError(null);
|
|
460
|
+
setPanel({ kind: "import" });
|
|
461
|
+
}, exportAllDisabled: everyCustom.length === 0, onExportAll: () => setPanel({ kind: "export", scope: "all" }) })] }), _jsx("div", { ref: body, className: "min-h-0 flex-1 overflow-y-auto p-4", children: panel.kind === "form" ? (_jsxs(_Fragment, { children: [deletedElsewhere && (_jsx("p", { role: "status", className: "mb-3 rounded-lg border border-amber-500/40 bg-amber-500/10 p-2.5 text-xs", children: "This theme was deleted elsewhere. Save adds it again." })), terminalDraft ? (_jsx(TerminalThemeForm, { draft: terminalDraft, dispatch: dispatch, composed: composedTerminal, lastShown: (scheme) => lastShownTerminal.current[scheme], palettes: palettes, composeList: composeList, nameError: nameError, tab: tab === "terminal" ? uiScheme : tab, onTabChange: changeTab, onPreview: queuePreview }, formKey)) : (_jsx(ThemeForm, { draft: uiDraft, dispatch: dispatch, composed: composedUi, lastShown: (scheme) => lastShown.current[scheme], baseThemes: baseThemes, palettes: palettes, composeList: composeList, committedTheme: pref.ui?.theme, nameError: nameError, tab: tab, onTabChange: changeTab, terminalView: terminalView, onTerminalViewChange: setTerminalView, shared: shared, onPreview: queuePreview }, formKey))] })) : panel.kind === "import" ? (_jsx(ImportPanel, { themes: composeList, labels: labels, slots: slots, onImport: importThemes, error: importError, onBack: () => setPanel(FORM) })) : (exported && (_jsx(ExportPanel, { ...exported, onBack: () => setPanel(FORM) }))) }), panel.kind === "form" && (_jsxs(SheetFooter, { className: "gap-2 border-t", children: [[sideError, saveError].map((message) => message && (_jsx("p", { role: "alert", className: "text-xs text-[color-mix(in_oklab,var(--destructive),var(--foreground)_65%)]", children: message }, message))), _jsxs("div", { className: "flex flex-wrap items-center gap-1.5", children: [_jsx(Button, { type: "button", variant: "destructive", size: "sm", disabled: !stored, onClick: () => setConfirmingDelete(true), children: "Delete" }), _jsx(Button, { type: "button", variant: "ghost", size: "sm", onClick: copy, children: "Duplicate" }), _jsx(Button, { type: "button", variant: "ghost", size: "sm", onClick: () => setPanel({ kind: "export", scope: "theme" }), children: "Export" }), _jsx("div", { className: "flex-1" }), _jsx(Button, { type: "button", variant: "outline", size: "sm", onClick: close, children: "Cancel" }), _jsx(Button, { type: "button", size: "sm", disabled: nameError !== null || sideError !== null, onClick: save, children: "Save" })] })] })), _jsx(UnsavedChangesDialog, { open: prompt !== null, onProceed: () => {
|
|
462
|
+
setPrompt(null);
|
|
463
|
+
prompt?.proceed();
|
|
464
|
+
}, onStay: () => setPrompt(null) }), _jsx(ConfirmDialog, { open: confirmingDelete, onOpenChange: setConfirmingDelete, title: `Delete ${displayLabel(state.saved?.label ?? draft.label)}?`, description: "It is removed from the custom themes. A pick that shows it falls back to another theme.", confirmLabel: "Delete", destructive: true, onConfirm: remove })] }));
|
|
465
|
+
}
|
|
466
|
+
const NO_THEMES = [];
|
|
467
|
+
//# sourceMappingURL=theme-editor.js.map
|