aboutsummaryrefslogtreecommitdiffstats
path: root/subprojects/frontend/src/editor/EditorAreaDecorations.tsx
diff options
context:
space:
mode:
authorLibravatar Kristóf Marussy <kristof@marussy.com>2022-10-31 19:15:21 -0400
committerLibravatar Kristóf Marussy <kristof@marussy.com>2022-11-05 19:41:17 +0100
commit216dea1a36d1c05108ac5cfcec84a7808ecf1d6e (patch)
tree338e11bc8f90f270899f40f8217a70a040375d7c /subprojects/frontend/src/editor/EditorAreaDecorations.tsx
parentrefactor(frontend): editor theme improvements (diff)
downloadrefinery-216dea1a36d1c05108ac5cfcec84a7808ecf1d6e.tar.gz
refinery-216dea1a36d1c05108ac5cfcec84a7808ecf1d6e.tar.zst
refinery-216dea1a36d1c05108ac5cfcec84a7808ecf1d6e.zip
feat(frontend): overlay scrollbars for editor
Diffstat (limited to 'subprojects/frontend/src/editor/EditorAreaDecorations.tsx')
-rw-r--r--subprojects/frontend/src/editor/EditorAreaDecorations.tsx148
1 files changed, 0 insertions, 148 deletions
diff --git a/subprojects/frontend/src/editor/EditorAreaDecorations.tsx b/subprojects/frontend/src/editor/EditorAreaDecorations.tsx
deleted file mode 100644
index e00cc290..00000000
--- a/subprojects/frontend/src/editor/EditorAreaDecorations.tsx
+++ /dev/null
@@ -1,148 +0,0 @@
1import { type CSSObject, type Theme, styled } from '@mui/material/styles';
2import React, { memo, useEffect, useState } from 'react';
3
4const SHADOW_SIZE = 10;
5
6const EditorAreaDecoration = styled('div')({
7 position: 'absolute',
8 pointerEvents: 'none',
9});
10
11function shadowTheme(
12 origin: string,
13 scaleX: boolean,
14 scaleY: boolean,
15): CSSObject {
16 function radialGradient(opacity: number, scale: string): string {
17 return `radial-gradient(
18 farthest-side at ${origin},
19 rgba(0, 0, 0, ${opacity}),
20 rgba(0, 0, 0, 0)
21 )
22 ${origin} /
23 ${scaleX ? scale : '100%'}
24 ${scaleY ? scale : '100%'}
25 no-repeat`;
26 }
27
28 return {
29 background: `
30 ${radialGradient(0.2, '40%')},
31 ${radialGradient(0.14, '50%')},
32 ${radialGradient(0.12, '100%')}
33 `,
34 };
35}
36
37function animateSize(
38 theme: Theme,
39 direction: 'height' | 'width',
40 opacity: number,
41): CSSObject {
42 return {
43 [direction]: opacity * SHADOW_SIZE,
44 transition: theme.transitions.create(direction, {
45 duration: theme.transitions.duration.shortest,
46 }),
47 };
48}
49
50const TopDecoration = memo(
51 styled(EditorAreaDecoration, {
52 shouldForwardProp: (prop) => prop !== 'visible' && prop !== 'opacity',
53 })<{
54 visible: boolean;
55 opacity: number;
56 }>(({ theme, visible, opacity }) => ({
57 display: visible ? 'block' : 'none',
58 top: 0,
59 left: 0,
60 right: 0,
61 ...shadowTheme('50% 0', false, true),
62 ...animateSize(theme, 'height', opacity),
63 })),
64);
65
66const GutterDecoration = memo(
67 styled(EditorAreaDecoration, {
68 shouldForwardProp: (prop) =>
69 prop !== 'top' &&
70 prop !== 'bottom' &&
71 prop !== 'guttersWidth' &&
72 prop !== 'opacity',
73 })<{
74 top: number;
75 bottom: number;
76 guttersWidth: number;
77 opacity: number;
78 }>(({ theme, top, bottom, guttersWidth, opacity }) => ({
79 top,
80 left: guttersWidth,
81 bottom,
82 ...shadowTheme('0 50%', true, false),
83 ...animateSize(theme, 'width', opacity),
84 })),
85);
86
87function convertToOpacity(scroll: number): number {
88 return Math.max(0, Math.min(1, scroll / SHADOW_SIZE));
89}
90
91export default function EditorAreaDecorations({
92 parent,
93 scroller,
94}: {
95 parent: HTMLElement | undefined;
96 scroller: HTMLElement | undefined;
97}): JSX.Element {
98 const [top, setTop] = useState(0);
99 const [bottom, setBottom] = useState(0);
100 const [guttersWidth, setGuttersWidth] = useState(0);
101 const [topOpacity, setTopOpacity] = useState(0);
102 const [gutterOpacity, setGutterOpacity] = useState(0);
103
104 useEffect(() => {
105 if (parent === undefined || scroller === undefined) {
106 return () => {};
107 }
108 const gutters = scroller.querySelector('.cm-gutters');
109
110 const updateBounds = () => {
111 const parentRect = parent.getBoundingClientRect();
112 const rect = scroller.getBoundingClientRect();
113 setTop(rect.top - parentRect.top);
114 setBottom(parentRect.bottom - rect.bottom);
115 setGuttersWidth(gutters?.clientWidth ?? 0);
116 };
117 updateBounds();
118 const resizeObserver = new ResizeObserver(updateBounds);
119 resizeObserver.observe(scroller);
120 if (gutters !== null) {
121 resizeObserver.observe(gutters);
122 }
123
124 const updateScroll = () => {
125 setTopOpacity(convertToOpacity(scroller.scrollTop));
126 setGutterOpacity(convertToOpacity(scroller.scrollLeft));
127 };
128 updateScroll();
129 scroller.addEventListener('scroll', updateScroll);
130
131 return () => {
132 resizeObserver.disconnect();
133 scroller.removeEventListener('scroll', updateScroll);
134 };
135 }, [parent, scroller, setTop]);
136
137 return (
138 <>
139 <GutterDecoration
140 top={top}
141 bottom={bottom}
142 guttersWidth={guttersWidth}
143 opacity={gutterOpacity}
144 />
145 <TopDecoration visible={top === 0} opacity={topOpacity} />
146 </>
147 );
148}