Skip to content

Commit d662c08

Browse files
authored
Merge pull request #2360 from flow-php/website/light-theme
feature: website light / dark / system theme
2 parents a9beea7 + 45857cd commit d662c08

32 files changed

Lines changed: 984 additions & 338 deletions

web/landing/assets/codemirror/themes/theme-flow.js

Lines changed: 144 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -145,3 +145,147 @@ export const flowThemeExtension = [
145145
flowTheme,
146146
syntaxHighlighting(flowHighlightStyle)
147147
]
148+
149+
/* Light variant — stock prism-inspired palette on near-white background. */
150+
export const flowLightTheme = EditorView.theme({
151+
"&": {
152+
backgroundColor: "#ffffff",
153+
color: "#1f2937",
154+
height: "100%"
155+
},
156+
".cm-content": {
157+
fontSize: "16px",
158+
lineHeight: "1.4",
159+
caretColor: "#0f172a"
160+
},
161+
".cm-cursor, .cm-dropCursor": {
162+
borderLeftColor: "#0f172a"
163+
},
164+
".cm-selectionBackground, ::selection": {
165+
backgroundColor: "rgba(99, 102, 241, 0.15)"
166+
},
167+
"&.cm-focused .cm-selectionBackground": {
168+
backgroundColor: "rgba(99, 102, 241, 0.18)"
169+
},
170+
".cm-activeLine": {
171+
backgroundColor: "rgba(15, 23, 42, 0.04)"
172+
},
173+
".cm-gutters": {
174+
backgroundColor: "#ffffff",
175+
color: "#94a3b8",
176+
border: "none"
177+
},
178+
".cm-activeLineGutter": {
179+
backgroundColor: "rgba(15, 23, 42, 0.04)"
180+
},
181+
".cm-foldPlaceholder": {
182+
backgroundColor: "#dd4a68",
183+
border: "none",
184+
color: "#ffffff"
185+
},
186+
".cm-searchMatch": {
187+
backgroundColor: "rgba(7, 119, 170, 0.25)"
188+
},
189+
".cm-searchMatch.cm-searchMatch-selected": {
190+
backgroundColor: "rgba(7, 119, 170, 0.45)"
191+
},
192+
".cm-error": {
193+
backgroundColor: "rgba(221, 74, 104, 0.12)",
194+
borderLeft: "3px solid #dd4a68"
195+
},
196+
".cm-tooltip.cm-tooltip-autocomplete": {
197+
backgroundColor: "#ffffff",
198+
border: "1px solid #cbd5e1",
199+
borderRadius: "8px",
200+
boxShadow: "0 8px 32px rgba(15, 23, 42, 0.12)",
201+
fontFamily: "'Cabin Variable', system-ui",
202+
fontSize: "16px",
203+
padding: "6px"
204+
},
205+
".cm-tooltip-autocomplete ul": {
206+
fontFamily: "inherit",
207+
maxHeight: "400px"
208+
},
209+
".cm-tooltip-autocomplete ul li": {
210+
padding: "8px 14px",
211+
borderRadius: "5px",
212+
margin: "2px 0",
213+
color: "#1f2937",
214+
cursor: "pointer"
215+
},
216+
".cm-tooltip-autocomplete ul li[aria-selected]": {
217+
background: "linear-gradient(90deg, #07a 0%, #0991c2 100%)",
218+
color: "#ffffff",
219+
fontWeight: "600"
220+
},
221+
".cm-completionLabel": {
222+
color: "inherit"
223+
},
224+
".cm-completionDetail": {
225+
color: "#64748b",
226+
fontSize: "12px",
227+
fontStyle: "italic",
228+
marginLeft: "16px"
229+
},
230+
".cm-tooltip-autocomplete ul li[aria-selected] .cm-completionDetail": {
231+
color: "#e0f2fe",
232+
fontWeight: "500"
233+
},
234+
".cm-completionIcon": {
235+
fontSize: "14px",
236+
width: "1em",
237+
lineHeight: "1",
238+
marginRight: "8px",
239+
textAlign: "center",
240+
paddingRight: "4px"
241+
},
242+
".cm-completionIcon-function": { color: "#dd4a68" },
243+
".cm-completionIcon-class": { color: "#07a" },
244+
".cm-completionIcon-keyword": { color: "#07a" },
245+
".cm-completionIcon-variable": { color: "#1f2937" },
246+
".cm-completionIcon-constant": { color: "#905" },
247+
".cm-completionIcon-type": { color: "#07a" },
248+
".cm-completionIcon-namespace": { color: "#dd4a68" },
249+
".cm-tooltip.cm-completionInfo": {
250+
backgroundColor: "#ffffff",
251+
border: "1px solid #cbd5e1",
252+
borderRadius: "8px",
253+
boxShadow: "0 8px 32px rgba(15, 23, 42, 0.12)",
254+
color: "#1f2937",
255+
fontFamily: "'Cabin Variable', system-ui",
256+
fontSize: "14px",
257+
maxWidth: "600px",
258+
padding: "16px"
259+
},
260+
".cm-completionInfo code": {
261+
backgroundColor: "#f1f5f9",
262+
padding: "2px 6px",
263+
borderRadius: "4px",
264+
fontFamily: "'Fira Code', 'JetBrains Mono', 'Consolas', monospace",
265+
fontSize: "13px"
266+
}
267+
}, { dark: false })
268+
269+
export const flowLightHighlightStyle = HighlightStyle.define([
270+
{ tag: t.keyword, color: "#07a" },
271+
{ tag: [t.name, t.deleted, t.character, t.propertyName, t.macroName], color: "#1f2937" },
272+
{ tag: [t.function(t.variableName), t.labelName], color: "#dd4a68" },
273+
{ tag: [t.color, t.constant(t.name), t.standard(t.name)], color: "#905" },
274+
{ tag: [t.definition(t.name), t.separator], color: "#1f2937" },
275+
{ tag: [t.typeName, t.className, t.number, t.changed, t.annotation, t.modifier, t.self, t.namespace], color: "#905" },
276+
{ tag: [t.operator, t.operatorKeyword, t.url, t.escape, t.regexp, t.link, t.special(t.string)], color: "#9a6e3a" },
277+
{ tag: [t.meta, t.comment], color: "#708090" },
278+
{ tag: t.strong, fontWeight: "bold" },
279+
{ tag: t.emphasis, fontStyle: "italic" },
280+
{ tag: t.strikethrough, textDecoration: "line-through" },
281+
{ tag: t.link, color: "#07a", textDecoration: "underline" },
282+
{ tag: t.heading, fontWeight: "bold", color: "#07a" },
283+
{ tag: [t.atom, t.bool, t.special(t.variableName)], color: "#905" },
284+
{ tag: [t.processingInstruction, t.string, t.inserted], color: "#690" },
285+
{ tag: t.invalid, color: "#ffffff", backgroundColor: "#dd4a68" }
286+
])
287+
288+
export const flowLightThemeExtension = [
289+
flowLightTheme,
290+
syntaxHighlighting(flowLightHighlightStyle)
291+
]

web/landing/assets/controllers/code_editor_controller.js

Lines changed: 24 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,12 @@
11
import { Controller } from "@hotwired/stimulus"
22
import { EditorView, basicSetup } from "codemirror"
3-
import { EditorState, StateField, StateEffect } from "@codemirror/state"
3+
import { EditorState, StateField, StateEffect, Compartment } from "@codemirror/state"
44
import { Decoration } from "@codemirror/view"
55
import { keymap } from "@codemirror/view"
66
import { php } from "@codemirror/lang-php"
77
import { autocompletion, snippetKeymap, acceptCompletion } from "@codemirror/autocomplete"
88
import { indentWithTab } from "@codemirror/commands"
9-
import { flowThemeExtension } from "../codemirror/themes/theme-flow.js"
9+
import { flowThemeExtension, flowLightThemeExtension } from "../codemirror/themes/theme-flow.js"
1010
import { flowCompletions } from "../codemirror/completions/flow.js"
1111
import { dslCompletions } from "../codemirror/completions/dsl.js"
1212
import { dataframeCompletions } from "../codemirror/completions/dataframe.js"
@@ -19,6 +19,8 @@ export default class extends Controller {
1919
#textarea
2020
#errorEffect
2121
#editorReady = false
22+
#themeCompartment = new Compartment()
23+
#onThemeChanged = null
2224

2325
#log(...args) {
2426
if (this.#debug) {
@@ -61,7 +63,7 @@ export default class extends Controller {
6163
extensions: [
6264
basicSetup,
6365
php(),
64-
flowThemeExtension,
66+
this.#themeCompartment.of(this.#currentThemeExtension()),
6567
errorField,
6668
keymap.of(snippetKeymap),
6769
keymap.of([indentWithTab]),
@@ -92,6 +94,22 @@ export default class extends Controller {
9294
this.#debug = this.application.debug
9395
this.#log('Connecting Code editor controller')
9496
this.#initializeEditor()
97+
98+
this.#onThemeChanged = this.#handleThemeChange.bind(this)
99+
document.addEventListener('theme:changed', this.#onThemeChanged)
100+
}
101+
102+
#currentThemeExtension() {
103+
return document.documentElement.getAttribute('data-theme') === 'dark'
104+
? flowThemeExtension
105+
: flowLightThemeExtension
106+
}
107+
108+
#handleThemeChange() {
109+
if (!this.#editor) return
110+
this.#editor.dispatch({
111+
effects: this.#themeCompartment.reconfigure(this.#currentThemeExtension())
112+
})
95113
}
96114

97115
isReady() {
@@ -116,6 +134,9 @@ export default class extends Controller {
116134
}
117135

118136
disconnect() {
137+
if (this.#onThemeChanged) {
138+
document.removeEventListener('theme:changed', this.#onThemeChanged)
139+
}
119140
if (this.#editor) {
120141
this.#editor.destroy()
121142
this.#editor = null

web/landing/assets/controllers/mermaid_controller.js

Lines changed: 41 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -4,24 +4,57 @@ import Panzoom from '@panzoom/panzoom';
44
export default class extends Controller {
55
static targets = ['svg', 'zoomIn', 'zoomOut'];
66

7+
#originalSource = null;
8+
#onThemeChanged = null;
9+
#panzoom = null;
10+
711
connect() {
12+
this.#originalSource = this.svgTarget.textContent;
13+
this.#render();
14+
15+
this.#onThemeChanged = this.#handleThemeChange.bind(this);
16+
document.addEventListener('theme:changed', this.#onThemeChanged);
17+
}
18+
19+
disconnect() {
20+
if (this.#onThemeChanged) {
21+
document.removeEventListener('theme:changed', this.#onThemeChanged);
22+
}
23+
}
24+
25+
#handleThemeChange(event) {
26+
const resolved = event.detail?.resolved || 'light';
27+
mermaid.initialize({
28+
startOnLoad: false,
29+
theme: resolved === 'dark' ? 'dark' : 'default',
30+
securityLevel: 'loose',
31+
flowchart: { useMaxWidth: true, htmlLabels: true },
32+
});
33+
34+
this.svgTarget.removeAttribute('data-processed');
35+
this.svgTarget.innerHTML = '';
36+
this.svgTarget.textContent = this.#originalSource;
37+
this.#render();
38+
}
39+
40+
#render() {
841
mermaid.run({
942
nodes: [this.svgTarget],
10-
postRenderCallback: (id) => {
11-
let panzoom = Panzoom(this.svgTarget, {});
12-
panzoom.pan(0, 0)
13-
this.element.addEventListener('wheel', panzoom.zoomWithWheel)
43+
postRenderCallback: () => {
44+
this.#panzoom = Panzoom(this.svgTarget, {});
45+
this.#panzoom.pan(0, 0);
46+
this.element.addEventListener('wheel', this.#panzoom.zoomWithWheel);
1447

1548
this.zoomInTarget.addEventListener('click', (event) => {
1649
event.preventDefault();
17-
panzoom.zoomIn();
50+
this.#panzoom.zoomIn();
1851
});
1952

2053
this.zoomOutTarget.addEventListener('click', (event) => {
2154
event.preventDefault();
22-
panzoom.zoomOut();
55+
this.#panzoom.zoomOut();
2356
});
24-
}
57+
},
2558
});
2659
}
27-
}
60+
}

0 commit comments

Comments
 (0)