Skip to content

Commit bdfc42b

Browse files
committed
feat(Electron template): improve scroll
1 parent 1bb0c0d commit bdfc42b

File tree

7 files changed

+243
-181
lines changed

7 files changed

+243
-181
lines changed

templates/electron-typescript-react/electron/index.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,8 @@ function createWindow() {
3030
win = new BrowserWindow({
3131
icon: path.join(process.env.VITE_PUBLIC, "electron-vite.svg"),
3232
webPreferences: {
33-
preload: path.join(__dirname, "preload.mjs")
33+
preload: path.join(__dirname, "preload.mjs"),
34+
scrollBounce: true
3435
},
3536
width: 1000,
3637
height: 700
Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
import React, {useLayoutEffect, useRef} from "react";
2+
import classNames from "classnames";
3+
4+
export function FixedDivWithSpacer({className, ...props}: FixedDivWithSpacerProps) {
5+
const spacerRef = useRef<HTMLDivElement>(null);
6+
7+
useLayoutEffect(() => {
8+
if (spacerRef.current == null)
9+
return;
10+
11+
const spacerTag = spacerRef.current;
12+
const mainTag = spacerTag.previousElementSibling as HTMLDivElement | null;
13+
14+
if (mainTag == null)
15+
return;
16+
17+
const resizeObserver = new ResizeObserver(() => {
18+
spacerTag.style.width = `${mainTag.offsetWidth}px`;
19+
spacerTag.style.height = `${mainTag.offsetHeight}px`;
20+
});
21+
resizeObserver.observe(mainTag, {
22+
box: "content-box"
23+
});
24+
25+
return () => {
26+
resizeObserver.disconnect();
27+
};
28+
}, [spacerRef]);
29+
30+
return <>
31+
<div className={classNames(className, "main")} {...props} />
32+
<div ref={spacerRef} className={classNames(className, "spacer")} />
33+
</>;
34+
}
35+
36+
type DivProps = React.DetailedHTMLProps<React.HTMLAttributes<HTMLDivElement>, HTMLDivElement>;
37+
type FixedDivWithSpacerProps = DivProps;
Lines changed: 71 additions & 63 deletions
Original file line numberDiff line numberDiff line change
@@ -1,90 +1,98 @@
11
.appHeader {
22
display: flex;
33
flex-direction: row;
4-
z-index: 10;
5-
position: sticky;
64
top: 16px;
75
pointer-events: none;
86

9-
> .panel {
10-
pointer-events: all;
11-
display: flex;
12-
flex-direction: row;
13-
align-self: start;
14-
background-color: var(--panel-background-color);
15-
border-radius: 12px;
16-
backdrop-filter: blur(8px);
17-
box-shadow: var(--panel-box-shadow);
18-
overflow: clip;
19-
isolation: isolate;
20-
color: var(--panel-text-color);
7+
&.spacer {
8+
position: sticky;
9+
}
10+
11+
&.main {
12+
width: calc(100% - 16px * 2);
13+
position: fixed;
2114
z-index: 10;
2215

23-
> button {
24-
flex-shrink: 0;
16+
> .panel {
17+
pointer-events: all;
2518
display: flex;
26-
flex-direction: column;
27-
align-items: center;
28-
justify-content: center;
29-
padding: 8px 12px;
30-
margin: 8px;
31-
background-color: var(--panel-button-background-color);
19+
flex-direction: row;
20+
align-self: start;
21+
background-color: var(--panel-background-color);
22+
border-radius: 12px;
23+
backdrop-filter: blur(8px);
24+
box-shadow: var(--panel-box-shadow);
25+
overflow: clip;
26+
isolation: isolate;
3227
color: var(--panel-text-color);
33-
fill: var(--panel-text-color);
28+
z-index: 10;
3429

35-
+ button {
36-
margin-inline-start: 0px;
37-
}
30+
> button {
31+
flex-shrink: 0;
32+
display: flex;
33+
flex-direction: column;
34+
align-items: center;
35+
justify-content: center;
36+
padding: 8px 12px;
37+
margin: 8px;
38+
background-color: var(--panel-button-background-color);
39+
color: var(--panel-text-color);
40+
fill: var(--panel-text-color);
3841

39-
&:hover,
40-
&:focus,
41-
&:focus-visible {
42-
border-color: var(--panel-button-hover-border-color);
43-
}
42+
+ button {
43+
margin-inline-start: 0px;
44+
}
45+
46+
&:hover,
47+
&:focus,
48+
&:focus-visible {
49+
border-color: var(--panel-button-hover-border-color);
50+
}
4451

45-
> .icon {
46-
width: 20px;
47-
height: 20px;
52+
> .icon {
53+
width: 20px;
54+
height: 20px;
55+
}
4856
}
4957
}
50-
}
5158

52-
> .model {
53-
position: relative;
59+
> .model {
60+
position: relative;
5461

55-
> .progress {
56-
position: absolute;
57-
inset-inline-start: 0;
58-
top: 0;
59-
bottom: 0;
60-
background-color: var(--panel-progress-color);
61-
width: calc(var(--progress) * 100%);
62-
pointer-events: none;
63-
z-index: -1;
62+
> .progress {
63+
position: absolute;
64+
inset-inline-start: 0;
65+
top: 0;
66+
bottom: 0;
67+
background-color: var(--panel-progress-color);
68+
width: calc(var(--progress) * 100%);
69+
pointer-events: none;
70+
z-index: -1;
6471

65-
--progress: 0;
72+
--progress: 0;
6673

67-
&.hide {
68-
opacity: 0;
74+
&.hide {
75+
opacity: 0;
6976

70-
transition: opacity 0.3s var(--transition-easing);
77+
transition: opacity 0.3s var(--transition-easing);
78+
}
7179
}
72-
}
7380

74-
> .modelName,
75-
> .noModel {
76-
flex: 1;
77-
text-align: start;
78-
align-self: center;
79-
flex-basis: 400px;
80-
padding: 12px 24px;
81-
word-break: break-word;
81+
> .modelName,
82+
> .noModel {
83+
flex: 1;
84+
text-align: start;
85+
align-self: center;
86+
flex-basis: 400px;
87+
padding: 12px 24px;
88+
word-break: break-word;
8289

83-
margin-inline-end: 48px;
90+
margin-inline-end: 48px;
91+
}
8492
}
85-
}
8693

87-
> .spacer {
88-
flex-grow: 1;
94+
> .spacer {
95+
flex-grow: 1;
96+
}
8997
}
9098
}

templates/electron-typescript-react/src/App/components/Header/Header.tsx

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,13 +2,16 @@ import {CSSProperties} from "react";
22
import classNames from "classnames";
33
import {LoadFileIconSVG} from "../../../icons/LoadFileIconSVG.tsx";
44
import {DeleteIconSVG} from "../../../icons/DeleteIconSVG.tsx";
5-
import {UpdateBadge} from "./components/UpdateBadge.js";
5+
import {FixedDivWithSpacer} from "../FixedDivWithSpacer/FixedDivWithSpacer.tsx";
6+
import {UpdateBadge} from "./components/UpdateBadge.tsx";
67

78
import "./Header.css";
89

910

1011
export function Header({appVersion, canShowCurrentVersion, modelName, onLoadClick, loadPercentage, onResetChatClick}: HeaderProps) {
11-
return <div className="appHeader">
12+
// we use a FixedDivWithSpacer to push down the content while keeping the header fixed.
13+
// this allows the content to have macOS's scroll bounce while keeping the header fixed at the top.
14+
return <FixedDivWithSpacer className="appHeader">
1215
<div className="panel model">
1316
<div
1417
className={classNames("progress", loadPercentage === 1 && "hide")}
@@ -42,7 +45,7 @@ export function Header({appVersion, canShowCurrentVersion, modelName, onLoadClic
4245
appVersion={appVersion}
4346
canShowCurrentVersion={canShowCurrentVersion}
4447
/>
45-
</div>;
48+
</FixedDivWithSpacer>;
4649
}
4750

4851
type HeaderProps = {

0 commit comments

Comments
 (0)