Skip to content

Commit 4683fef

Browse files
first attempt to add a context menu for canvas
Signed-off-by: NikitaSkrynnik <[email protected]>
1 parent 364cc67 commit 4683fef

File tree

3 files changed

+242
-4
lines changed

3 files changed

+242
-4
lines changed

src/components/Browser.tsx

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,8 @@ import { AppState, TabConnection } from "../state/state";
33
import "./Browser.css";
44
import { domCodeToKeyCode } from "../keyboard/keycodes";
55
import { Cursor } from "cef-client";
6+
import BrowserContextMenu from "./BrowserContextMenu";
7+
68

79
function Browser(props: { app: AppState }) {
810
let canvasContainer!: HTMLDivElement;
@@ -59,10 +61,12 @@ function Browser(props: { app: AppState }) {
5961

6062
return (
6163
<>
62-
<div class="canvas-container" ref={canvasContainer}>
63-
<div class="fps-counter">{fps()} FPS</div>
64-
<canvas tabIndex="0" class="canvas" ref={canvas}></canvas>
65-
</div>
64+
<BrowserContextMenu app={props.app}>
65+
<div class="canvas-container" ref={canvasContainer}>
66+
<div class="fps-counter">{fps()} FPS</div>
67+
<canvas tabIndex="0" class="canvas" ref={canvas}></canvas>
68+
</div>
69+
</BrowserContextMenu>
6670
</>
6771
);
6872
}
Lines changed: 182 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,182 @@
1+
// Variables
2+
$border-radius: 8px;
3+
$border-radius-small: 5px;
4+
$padding-sm: 4px;
5+
$padding-md: 8px;
6+
$padding-lg: 12px;
7+
$gap-sm: 8px;
8+
$font-size-base: 14px;
9+
$min-width: 160px;
10+
$z-index-context-menu: 1000;
11+
$transition-fast: 0.1s ease;
12+
$transition-medium: 0.2s ease-out;
13+
14+
// Colors - Light theme
15+
$color-bg-primary: #ffffff;
16+
$color-bg-hover: #f6f8fa;
17+
$color-bg-focus: #0969da;
18+
$color-border: #e1e5e9;
19+
$color-text-primary: #1f2328;
20+
$color-text-secondary: #656d76;
21+
$color-text-disabled: #8c959f;
22+
$color-text-inverted: #ffffff;
23+
$color-shadow: rgba(0, 0, 0, 0.1);
24+
25+
// Colors - Dark theme
26+
$color-bg-primary-dark: #24292f;
27+
$color-bg-hover-dark: #30363d;
28+
$color-border-dark: #30363d;
29+
$color-text-primary-dark: #f0f6fc;
30+
$color-text-secondary-dark: #8b949e;
31+
$color-text-disabled-dark: #7d8590;
32+
33+
// Font stack
34+
$font-family-system: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
35+
36+
// Animation keyframes
37+
@keyframes contextMenuShow {
38+
from {
39+
opacity: 0;
40+
transform: scale(0.95) translateY(-2px);
41+
}
42+
43+
to {
44+
opacity: 1;
45+
transform: scale(1) translateY(0);
46+
}
47+
}
48+
49+
@keyframes contextMenuHide {
50+
from {
51+
opacity: 1;
52+
transform: scale(1) translateY(0);
53+
}
54+
55+
to {
56+
opacity: 0;
57+
transform: scale(0.95) translateY(-2px);
58+
}
59+
}
60+
61+
// Base styles
62+
.content {
63+
background-color: $color-bg-primary;
64+
border: 1px solid $color-border;
65+
border-radius: $border-radius;
66+
box-shadow: 0 4px 12px $color-shadow;
67+
padding: $padding-sm 0;
68+
min-width: $min-width;
69+
z-index: $z-index-context-menu;
70+
font-size: $font-size-base;
71+
font-family: $font-family-system;
72+
73+
&[data-state="open"] {
74+
animation: contextMenuShow $transition-medium;
75+
}
76+
77+
&[data-state="closed"] {
78+
animation: contextMenuHide ease-in $transition-medium;
79+
}
80+
}
81+
82+
.trigger {
83+
width: 100%;
84+
height: 100%;
85+
}
86+
87+
.arrow {
88+
fill: $color-bg-primary;
89+
stroke: $color-border;
90+
stroke-width: 1px;
91+
}
92+
93+
.item {
94+
display: flex;
95+
align-items: center;
96+
gap: $gap-sm;
97+
padding: $padding-md $padding-lg;
98+
cursor: pointer;
99+
background: none;
100+
border: none;
101+
width: 100%;
102+
text-align: left;
103+
color: $color-text-primary;
104+
transition: background-color $transition-fast;
105+
106+
&:hover {
107+
background-color: $color-bg-hover;
108+
}
109+
110+
&:focus {
111+
background-color: $color-bg-focus;
112+
color: $color-text-inverted;
113+
outline: none;
114+
115+
.icon {
116+
color: $color-text-inverted;
117+
}
118+
}
119+
120+
&[data-disabled] {
121+
color: $color-text-disabled;
122+
cursor: not-allowed;
123+
124+
&:hover {
125+
background-color: transparent;
126+
}
127+
}
128+
}
129+
130+
.icon {
131+
color: $color-text-secondary;
132+
flex-shrink: 0;
133+
}
134+
135+
.label {
136+
flex: 1;
137+
font-weight: 400;
138+
}
139+
140+
.separator {
141+
height: 1px;
142+
background-color: $color-border;
143+
margin: $padding-sm 0;
144+
}
145+
146+
// Dark theme support
147+
@media (prefers-color-scheme: dark) {
148+
.content {
149+
background-color: $color-bg-primary-dark;
150+
border-color: $color-border-dark;
151+
color: $color-text-primary-dark;
152+
}
153+
154+
.arrow {
155+
fill: $color-bg-primary-dark;
156+
stroke: $color-border-dark;
157+
}
158+
159+
.item {
160+
color: $color-text-primary-dark;
161+
162+
&:hover {
163+
background-color: $color-bg-hover-dark;
164+
}
165+
166+
&:focus {
167+
background-color: $color-bg-focus;
168+
}
169+
170+
&[data-disabled] {
171+
color: $color-text-disabled-dark;
172+
}
173+
}
174+
175+
.icon {
176+
color: $color-text-secondary-dark;
177+
}
178+
179+
.separator {
180+
background-color: $color-border-dark;
181+
}
182+
}
Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
import { ContextMenu } from "@kobalte/core/context-menu";
2+
import { ArrowLeft, ArrowRight, RotateCcw } from "lucide-solid";
3+
import styles from "./BrowserContextMenu.module.scss";
4+
import { AppState } from "../state/state";
5+
import { createMemo, JSX } from "solid-js";
6+
7+
function BrowserContextMenu(props: { app: AppState, children: JSX.Element }) {
8+
let activeTabMemo = createMemo(() => props.app.getActiveTab());
9+
10+
return (
11+
<ContextMenu>
12+
<ContextMenu.Trigger class={styles.trigger}>
13+
{props.children}
14+
</ContextMenu.Trigger>
15+
<ContextMenu.Portal>
16+
<ContextMenu.Content class={styles.content}>
17+
<ContextMenu.Arrow class={styles.arrow} />
18+
19+
<ContextMenu.Item
20+
class={styles.item}
21+
disabled={!activeTabMemo()?.canGoBack}
22+
onSelect={activeTabMemo()?.goBack}
23+
>
24+
<ArrowLeft size={16} class={styles.icon} />
25+
<span class={styles.label}>Back</span>
26+
</ContextMenu.Item>
27+
28+
<ContextMenu.Item
29+
class={styles.item}
30+
disabled={!activeTabMemo()?.canGoForward}
31+
onSelect={activeTabMemo()?.goForward}
32+
>
33+
<ArrowRight size={16} class={styles.icon} />
34+
<span class={styles.label}>Forward</span>
35+
</ContextMenu.Item>
36+
37+
<ContextMenu.Separator class={styles.separator} />
38+
39+
<ContextMenu.Item
40+
class={styles.item}
41+
onSelect={activeTabMemo()?.reload}
42+
>
43+
<RotateCcw size={16} class={styles.icon} />
44+
<span class={styles.label}>Reload</span>
45+
</ContextMenu.Item>
46+
</ContextMenu.Content>
47+
</ContextMenu.Portal>
48+
</ContextMenu>
49+
);
50+
};
51+
52+
export default BrowserContextMenu;

0 commit comments

Comments
 (0)