Skip to content

Commit 9985efa

Browse files
authored
Panel UI (#1032)
* better panel UI * rename slots * convert to svelte 5 * tweaks * lint * use modern spring
1 parent 45926cc commit 9985efa

File tree

3 files changed

+104
-49
lines changed

3 files changed

+104
-49
lines changed

packages/repl/src/lib/Output/Output.svelte

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -112,14 +112,14 @@
112112
{#if embedded}
113113
<Editor workspace={js_workspace} />
114114
{:else}
115-
<PaneWithPanel pos="50%" max="-4.2rem" panel="Compiler options">
116-
<div slot="main">
115+
<PaneWithPanel min="-18rem" pos="-18rem" panel="Compiler options">
116+
{#snippet main()}
117117
<Editor workspace={js_workspace} />
118-
</div>
118+
{/snippet}
119119

120-
<div slot="panel-body">
120+
{#snippet body()}
121121
<CompilerOptions {workspace} />
122-
</div>
122+
{/snippet}
123123
</PaneWithPanel>
124124
{/if}
125125
</div>

packages/repl/src/lib/Output/PaneWithPanel.svelte

Lines changed: 94 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -1,63 +1,117 @@
11
<script lang="ts">
2-
import { spring } from 'svelte/motion';
2+
import { Spring } from 'svelte/motion';
33
import { SplitPane, type Length } from '@rich_harris/svelte-split-pane';
44
55
const UNIT_REGEX = /(\d+)(?:(px|rem|%|em))/i;
66
7-
export let panel: string;
7+
interface Props {
8+
panel: string;
9+
pos?: Length;
10+
min?: Length;
11+
max?: Length;
12+
main?: import('svelte').Snippet;
13+
header?: import('svelte').Snippet;
14+
body?: import('svelte').Snippet;
15+
}
816
9-
export let pos: Length = '90%';
17+
let {
18+
panel,
19+
pos = $bindable('90%'),
20+
min = '4.2rem',
21+
max = '-4.2rem',
22+
main,
23+
header,
24+
body
25+
}: Props = $props();
1026
11-
$: previous_pos = Math.min(+pos.replace(UNIT_REGEX, '$1'), 70);
27+
let previous_pos = Math.min(normalize(pos), 70);
1228
13-
export let max: Length = '90%';
29+
let container: HTMLElement;
1430
1531
// we can't bind to the spring itself, but we
1632
// can still use the spring to drive `pos`
17-
const driver = spring(+pos.replace(UNIT_REGEX, '$1'), {
33+
const driver = new Spring(normalize(pos), {
1834
stiffness: 0.2,
1935
damping: 0.5
2036
});
2137
22-
// @ts-ignore
23-
$: pos = $driver + '%';
38+
$effect(() => {
39+
pos = driver.current + '%';
40+
});
2441
2542
const toggle = () => {
26-
const numeric_pos = +pos.replace(UNIT_REGEX, '$1');
43+
const pc = normalize(pos);
44+
const px = pc * 0.01 * container.clientHeight;
2745
28-
driver.set(numeric_pos, { hard: true });
46+
const open = container.clientHeight - px > 42;
2947
30-
if (numeric_pos > 80) {
31-
driver.set(previous_pos);
32-
} else {
33-
previous_pos = numeric_pos;
48+
driver.set(pc, { hard: true });
49+
50+
if (open) {
51+
previous_pos = pc;
3452
driver.set(100);
53+
} else {
54+
driver.set(previous_pos);
3555
}
3656
};
57+
58+
function normalize(pos: string) {
59+
let normalized = +pos.replace(UNIT_REGEX, '$1');
60+
61+
if (normalized < 0) {
62+
normalized += 100;
63+
}
64+
65+
return normalized;
66+
}
3767
</script>
3868

39-
<SplitPane {max} min="10%" type="vertical" bind:pos>
40-
{#snippet a()}
41-
<section>
42-
<slot name="main" />
43-
</section>
44-
{/snippet}
45-
46-
{#snippet b()}
47-
<section>
48-
<div class="panel-header">
49-
<button class="panel-heading" on:click={toggle}>{panel}</button>
50-
<slot name="panel-header" />
51-
</div>
52-
53-
<div class="panel-body">
54-
<slot name="panel-body" />
55-
</div>
56-
</section>
57-
{/snippet}
58-
</SplitPane>
69+
<div class="container" bind:this={container}>
70+
<SplitPane {min} {max} type="vertical" bind:pos>
71+
{#snippet a()}
72+
<section>
73+
{@render main?.()}
74+
</section>
75+
{/snippet}
76+
77+
{#snippet b()}
78+
<section>
79+
<div class="panel-header">
80+
<button class="panel-heading raised" onclick={toggle}>
81+
<svg
82+
width="1.8rem"
83+
height="1.8rem"
84+
viewBox="0 0 24 24"
85+
fill="none"
86+
stroke="currentColor"
87+
stroke-width="2"
88+
stroke-linecap="round"
89+
stroke-linejoin="round"
90+
>
91+
<path d="m7 15 5 5 5-5" />
92+
<path d="m7 9 5-5 5 5" />
93+
</svg>
94+
95+
{panel}
96+
</button>
97+
98+
{@render header?.()}
99+
</div>
100+
101+
<div class="panel-body">
102+
{@render body?.()}
103+
</div>
104+
</section>
105+
{/snippet}
106+
</SplitPane>
107+
</div>
59108

60109
<style>
110+
.container {
111+
width: 100%;
112+
height: 100%;
113+
}
114+
61115
.panel-header {
62116
height: var(--sk-pane-controls-height);
63117
display: flex;
@@ -74,8 +128,13 @@
74128
.panel-heading {
75129
font: var(--sk-font-ui-small);
76130
text-transform: uppercase;
77-
flex: 1;
131+
height: 3.2rem;
132+
padding: 0 0.8rem;
133+
/* flex: 1; */
78134
text-align: left;
135+
display: flex;
136+
align-items: center;
137+
gap: 0.4rem;
79138
}
80139
81140
section {

packages/repl/src/lib/Output/Viewer.svelte

Lines changed: 5 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -295,23 +295,19 @@
295295

296296
<div class="iframe-container">
297297
{#if !onLog}
298-
<PaneWithPanel pos="100%" max="-4.2rem" panel="Console">
299-
<div slot="main">
300-
{@render main()}
301-
</div>
302-
303-
<div slot="panel-header">
298+
<PaneWithPanel pos="100%" panel="Console" {main}>
299+
{#snippet header()}
304300
<button class="raised" disabled={logs.length === 0} on:click|stopPropagation={clear_logs}>
305301
{#if logs.length > 0}
306302
({logs.length})
307303
{/if}
308304
Clear
309305
</button>
310-
</div>
306+
{/snippet}
311307

312-
<section slot="panel-body">
308+
{#snippet body()}
313309
<Console {logs} />
314-
</section>
310+
{/snippet}
315311
</PaneWithPanel>
316312
{:else}
317313
{@render main()}

0 commit comments

Comments
 (0)