@tooee/themes 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/README.md +5 -0
- package/dist/ThemePicker.d.ts +14 -0
- package/dist/ThemePicker.d.ts.map +1 -0
- package/dist/ThemePicker.js +96 -0
- package/dist/ThemePicker.js.map +1 -0
- package/dist/index.d.ts +5 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +3 -0
- package/dist/index.js.map +1 -0
- package/dist/theme.d.ts +111 -0
- package/dist/theme.d.ts.map +1 -0
- package/dist/theme.js +560 -0
- package/dist/theme.js.map +1 -0
- package/package.json +43 -0
- package/src/ThemePicker.tsx +163 -0
- package/src/index.ts +21 -0
- package/src/theme.tsx +727 -0
- package/src/themes/aura.json +69 -0
- package/src/themes/ayu.json +80 -0
- package/src/themes/catppuccin-frappe.json +233 -0
- package/src/themes/catppuccin-macchiato.json +233 -0
- package/src/themes/catppuccin.json +112 -0
- package/src/themes/cobalt2.json +228 -0
- package/src/themes/cursor.json +249 -0
- package/src/themes/dracula.json +219 -0
- package/src/themes/everforest.json +241 -0
- package/src/themes/flexoki.json +237 -0
- package/src/themes/github-light.json +56 -0
- package/src/themes/github.json +233 -0
- package/src/themes/gruvbox.json +95 -0
- package/src/themes/kanagawa.json +77 -0
- package/src/themes/lucent-orng.json +227 -0
- package/src/themes/material.json +235 -0
- package/src/themes/matrix.json +77 -0
- package/src/themes/mercury.json +252 -0
- package/src/themes/monokai.json +221 -0
- package/src/themes/nightowl.json +221 -0
- package/src/themes/nord.json +223 -0
- package/src/themes/one-dark.json +84 -0
- package/src/themes/opencode-light.json +62 -0
- package/src/themes/opencode.json +245 -0
- package/src/themes/orng.json +245 -0
- package/src/themes/osaka-jade.json +93 -0
- package/src/themes/palenight.json +222 -0
- package/src/themes/rosepine.json +234 -0
- package/src/themes/solarized.json +223 -0
- package/src/themes/synthwave84.json +226 -0
- package/src/themes/tokyonight.json +243 -0
- package/src/themes/vercel.json +245 -0
- package/src/themes/vesper.json +218 -0
- package/src/themes/zenburn.json +223 -0
|
@@ -0,0 +1,163 @@
|
|
|
1
|
+
import { useState, useMemo, useCallback } from "react"
|
|
2
|
+
import { useKeyboard } from "@opentui/react"
|
|
3
|
+
import { useTheme } from "./theme.js"
|
|
4
|
+
|
|
5
|
+
export interface ThemePickerEntry {
|
|
6
|
+
id: string
|
|
7
|
+
title: string
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
interface ThemePickerProps {
|
|
11
|
+
entries: ThemePickerEntry[]
|
|
12
|
+
currentTheme: string
|
|
13
|
+
onSelect: (name: string) => void
|
|
14
|
+
onClose: () => void
|
|
15
|
+
onNavigate: (name: string) => void
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
function fuzzyMatch(query: string, text: string): number | null {
|
|
19
|
+
const lowerQuery = query.toLowerCase()
|
|
20
|
+
const lowerText = text.toLowerCase()
|
|
21
|
+
|
|
22
|
+
let qi = 0
|
|
23
|
+
let score = 0
|
|
24
|
+
let lastMatchIndex = -2
|
|
25
|
+
|
|
26
|
+
for (let ti = 0; ti < lowerText.length && qi < lowerQuery.length; ti++) {
|
|
27
|
+
if (lowerText[ti] === lowerQuery[qi]) {
|
|
28
|
+
if (ti === 0) score += 3
|
|
29
|
+
else if (" -./".includes(lowerText[ti - 1]!)) score += 2
|
|
30
|
+
else if (ti === lastMatchIndex + 1) score += 1
|
|
31
|
+
|
|
32
|
+
lastMatchIndex = ti
|
|
33
|
+
qi++
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
return qi === lowerQuery.length ? score : null
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
export function ThemePicker({
|
|
41
|
+
entries,
|
|
42
|
+
currentTheme,
|
|
43
|
+
onSelect,
|
|
44
|
+
onClose,
|
|
45
|
+
onNavigate,
|
|
46
|
+
}: ThemePickerProps) {
|
|
47
|
+
const { theme } = useTheme()
|
|
48
|
+
const [filter, setFilter] = useState("")
|
|
49
|
+
const [activeIndex, setActiveIndex] = useState(() => {
|
|
50
|
+
const idx = entries.findIndex((e) => e.id === currentTheme)
|
|
51
|
+
return idx >= 0 ? idx : 0
|
|
52
|
+
})
|
|
53
|
+
|
|
54
|
+
const filtered = useMemo(() => {
|
|
55
|
+
if (!filter) return entries
|
|
56
|
+
const results: { entry: ThemePickerEntry; score: number }[] = []
|
|
57
|
+
for (const entry of entries) {
|
|
58
|
+
const score = fuzzyMatch(filter, entry.title)
|
|
59
|
+
if (score !== null) results.push({ entry, score })
|
|
60
|
+
}
|
|
61
|
+
results.sort((a, b) => b.score - a.score)
|
|
62
|
+
return results.map((r) => r.entry)
|
|
63
|
+
}, [entries, filter])
|
|
64
|
+
|
|
65
|
+
const handleSelect = useCallback(() => {
|
|
66
|
+
const item = filtered[activeIndex]
|
|
67
|
+
if (item) {
|
|
68
|
+
onSelect(item.id)
|
|
69
|
+
}
|
|
70
|
+
}, [filtered, activeIndex, onSelect])
|
|
71
|
+
|
|
72
|
+
const navigateTo = useCallback(
|
|
73
|
+
(index: number) => {
|
|
74
|
+
setActiveIndex(index)
|
|
75
|
+
const item = filtered[index]
|
|
76
|
+
if (item) {
|
|
77
|
+
onNavigate(item.id)
|
|
78
|
+
}
|
|
79
|
+
},
|
|
80
|
+
[filtered, onNavigate],
|
|
81
|
+
)
|
|
82
|
+
|
|
83
|
+
useKeyboard((key) => {
|
|
84
|
+
if (key.name === "escape") {
|
|
85
|
+
key.preventDefault()
|
|
86
|
+
onClose()
|
|
87
|
+
} else if (key.name === "return") {
|
|
88
|
+
key.preventDefault()
|
|
89
|
+
handleSelect()
|
|
90
|
+
} else if (key.name === "up") {
|
|
91
|
+
key.preventDefault()
|
|
92
|
+
navigateTo(Math.max(0, activeIndex - 1))
|
|
93
|
+
} else if (key.name === "down") {
|
|
94
|
+
key.preventDefault()
|
|
95
|
+
navigateTo(Math.min(filtered.length - 1, activeIndex + 1))
|
|
96
|
+
}
|
|
97
|
+
})
|
|
98
|
+
|
|
99
|
+
return (
|
|
100
|
+
<box
|
|
101
|
+
position="absolute"
|
|
102
|
+
left="20%"
|
|
103
|
+
right="20%"
|
|
104
|
+
top={2}
|
|
105
|
+
maxHeight="60%"
|
|
106
|
+
flexDirection="column"
|
|
107
|
+
backgroundColor={theme.backgroundPanel}
|
|
108
|
+
border
|
|
109
|
+
borderColor={theme.border}
|
|
110
|
+
>
|
|
111
|
+
{/* Filter row */}
|
|
112
|
+
<box flexDirection="row" paddingLeft={1} paddingRight={1} height={1}>
|
|
113
|
+
<text content="🎨 " fg={theme.accent} />
|
|
114
|
+
<input
|
|
115
|
+
focused
|
|
116
|
+
placeholder="Filter themes..."
|
|
117
|
+
onInput={(value: string) => {
|
|
118
|
+
setFilter(value)
|
|
119
|
+
setActiveIndex(0)
|
|
120
|
+
// Preview first match
|
|
121
|
+
if (!value) {
|
|
122
|
+
if (entries.length > 0) onNavigate(entries[0]!.id)
|
|
123
|
+
} else {
|
|
124
|
+
const results: { entry: ThemePickerEntry; score: number }[] = []
|
|
125
|
+
for (const entry of entries) {
|
|
126
|
+
const score = fuzzyMatch(value, entry.title)
|
|
127
|
+
if (score !== null) results.push({ entry, score })
|
|
128
|
+
}
|
|
129
|
+
results.sort((a, b) => b.score - a.score)
|
|
130
|
+
if (results.length > 0) onNavigate(results[0]!.entry.id)
|
|
131
|
+
}
|
|
132
|
+
}}
|
|
133
|
+
backgroundColor="transparent"
|
|
134
|
+
focusedBackgroundColor="transparent"
|
|
135
|
+
textColor={theme.text}
|
|
136
|
+
placeholderColor={theme.textMuted}
|
|
137
|
+
cursorColor={theme.accent}
|
|
138
|
+
style={{ flexGrow: 1 }}
|
|
139
|
+
/>
|
|
140
|
+
<text content={` ${filtered.length}`} fg={theme.textMuted} />
|
|
141
|
+
</box>
|
|
142
|
+
|
|
143
|
+
{/* Separator */}
|
|
144
|
+
<box height={1} width="100%" backgroundColor={theme.border} />
|
|
145
|
+
|
|
146
|
+
{/* Theme list */}
|
|
147
|
+
<scrollbox focused={false} style={{ flexGrow: 1 }}>
|
|
148
|
+
{filtered.map((entry, i) => (
|
|
149
|
+
<box
|
|
150
|
+
key={entry.id}
|
|
151
|
+
flexDirection="row"
|
|
152
|
+
paddingLeft={1}
|
|
153
|
+
paddingRight={1}
|
|
154
|
+
height={1}
|
|
155
|
+
backgroundColor={i === activeIndex ? theme.backgroundElement : undefined}
|
|
156
|
+
>
|
|
157
|
+
<text content={entry.title} fg={theme.text} style={{ flexGrow: 1 }} />
|
|
158
|
+
</box>
|
|
159
|
+
))}
|
|
160
|
+
</scrollbox>
|
|
161
|
+
</box>
|
|
162
|
+
)
|
|
163
|
+
}
|
package/src/index.ts
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
export {
|
|
2
|
+
ThemeProvider,
|
|
3
|
+
ThemeSwitcherProvider,
|
|
4
|
+
useTheme,
|
|
5
|
+
useThemeSwitcher,
|
|
6
|
+
defaultTheme,
|
|
7
|
+
resolveTheme,
|
|
8
|
+
buildSyntaxStyle,
|
|
9
|
+
loadThemes,
|
|
10
|
+
getThemeNames,
|
|
11
|
+
} from "./theme.js"
|
|
12
|
+
export type {
|
|
13
|
+
Theme,
|
|
14
|
+
ThemeJSON,
|
|
15
|
+
ResolvedTheme,
|
|
16
|
+
ThemeProviderProps,
|
|
17
|
+
ThemeSwitcherProviderProps,
|
|
18
|
+
} from "./theme.js"
|
|
19
|
+
|
|
20
|
+
export { ThemePicker } from "./ThemePicker.jsx"
|
|
21
|
+
export type { ThemePickerEntry } from "./ThemePicker.jsx"
|