Skip to content

Commit 970d89d

Browse files
authored
Add right sidebar option and adjust padding (#11)
1 parent 03cd51a commit 970d89d

File tree

5 files changed

+94
-40
lines changed

5 files changed

+94
-40
lines changed

web/src/lib/components/settings-popover/SettingsPopover.svelte

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,10 +3,11 @@
33
</script>
44

55
<script lang="ts">
6-
import GlobalThemeRadio from "./GlobalThemeRadio.svelte";
76
import SettingsPopoverGroup from "./SettingsPopoverGroup.svelte";
87
import type { RestProps } from "$lib/types";
98
import { mergeProps, Popover, type WithChildren } from "bits-ui";
9+
import SimpleRadioGroup from "$lib/components/settings-popover/SimpleRadioGroup.svelte";
10+
import { getGlobalTheme, setGlobalTheme } from "$lib/theme.svelte";
1011
1112
let { children, ...restProps }: WithChildren<RestProps> = $props();
1213
@@ -25,7 +26,7 @@
2526
{#snippet globalThemeSetting()}
2627
<SettingsPopoverGroup title="Theme">
2728
<div class="px-2 py-1">
28-
<GlobalThemeRadio aria-label="Select theme" />
29+
<SimpleRadioGroup values={["light", "dark", "auto"]} bind:value={getGlobalTheme, setGlobalTheme} aria-label="Select theme" />
2930
</div>
3031
</SettingsPopoverGroup>
3132
{/snippet}

web/src/lib/components/settings-popover/GlobalThemeRadio.svelte renamed to web/src/lib/components/settings-popover/SimpleRadioGroup.svelte

Lines changed: 19 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,25 @@
11
<script lang="ts">
2-
import { getGlobalTheme, setGlobalTheme } from "$lib/theme.svelte";
3-
import { Label, RadioGroup, useId } from "bits-ui";
2+
import { Label, RadioGroup } from "bits-ui";
43
import { capitalizeFirstLetter } from "$lib/util";
4+
import type { RestProps } from "$lib/types";
55
6-
let { ...props } = $props();
6+
interface Props extends RestProps {
7+
value: string;
8+
values: string[];
9+
}
10+
11+
let { value = $bindable(), values, ...restProps }: Props = $props();
12+
13+
const uid = $props.id();
714
</script>
815

9-
{#snippet themeItem(theme: string)}
10-
{@const labelId = useId()}
11-
{@const itemId = useId()}
16+
{#snippet item(value: string)}
17+
{@const labelId = `${uid}-${value}-label`}
18+
{@const itemId = `${uid}-${value}-item`}
1219
<Label.Root id={labelId} for={itemId} class="flex cursor-pointer flex-row items-center gap-1 text-sm">
1320
<RadioGroup.Item
1421
class="flex size-4 cursor-pointer items-center justify-center rounded-full border btn-ghost"
15-
value={theme}
22+
{value}
1623
id={itemId}
1724
aria-labelledby={labelId}
1825
>
@@ -22,12 +29,12 @@
2229
{/if}
2330
{/snippet}
2431
</RadioGroup.Item>
25-
{capitalizeFirstLetter(theme)}
32+
{capitalizeFirstLetter(value)}
2633
</Label.Root>
2734
{/snippet}
2835

29-
<RadioGroup.Root class="flex flex-row items-center gap-2" bind:value={getGlobalTheme, setGlobalTheme} {...props}>
30-
{@render themeItem("light")}
31-
{@render themeItem("dark")}
32-
{@render themeItem("auto")}
36+
<RadioGroup.Root class="flex flex-row items-center gap-2" bind:value {...restProps}>
37+
{#each values as value (value)}
38+
{@render item(value)}
39+
{/each}
3340
</RadioGroup.Root>

web/src/lib/diff-viewer-multi-file.svelte.ts

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,8 @@ import { VList } from "virtua/svelte";
2626
import { Context, Debounced } from "runed";
2727
import { MediaQuery } from "svelte/reactivity";
2828

29+
export type SidebarLocation = "left" | "right";
30+
2931
export class GlobalOptions {
3032
private static readonly localStorageKey = "diff-viewer-global-options";
3133
private static readonly context = new Context<GlobalOptions>(GlobalOptions.localStorageKey);
@@ -54,6 +56,8 @@ export class GlobalOptions {
5456
wordDiffs = $state(true);
5557
lineWrap = $state(true);
5658
omitPatchHeaderOnlyHunks = $state(true);
59+
// TODO: send to server (use cookie?) to that the initial position is correct
60+
sidebarLocation: SidebarLocation = $state("left");
5761

5862
private constructor() {
5963
$effect(() => {
@@ -90,6 +94,7 @@ export class GlobalOptions {
9094
omitPatchHeaderOnlyHunks: this.omitPatchHeaderOnlyHunks,
9195
wordDiff: this.wordDiffs,
9296
lineWrap: this.lineWrap,
97+
sidebarLocation: this.sidebarLocation,
9398
};
9499
if (this.syntaxHighlightingThemeLight !== DEFAULT_THEME_LIGHT) {
95100
cereal.syntaxHighlightingThemeLight = this.syntaxHighlightingThemeLight;
@@ -124,6 +129,9 @@ export class GlobalOptions {
124129
if (jsonObject.lineWrap !== undefined) {
125130
this.lineWrap = jsonObject.lineWrap;
126131
}
132+
if (jsonObject.sidebarLocation !== undefined) {
133+
this.sidebarLocation = jsonObject.sidebarLocation;
134+
}
127135
}
128136
}
129137

web/src/routes/+page.svelte

Lines changed: 29 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@
2020
import SettingsPopoverGroup from "$lib/components/settings-popover/SettingsPopoverGroup.svelte";
2121
import LabeledCheckbox from "$lib/components/LabeledCheckbox.svelte";
2222
import ShikiThemeSelector from "$lib/components/settings-popover/ShikiThemeSelector.svelte";
23+
import SimpleRadioGroup from "$lib/components/settings-popover/SimpleRadioGroup.svelte";
2324
import DiffSearch from "./DiffSearch.svelte";
2425
import FileHeader from "./FileHeader.svelte";
2526
import DiffTitle from "./DiffTitle.svelte";
@@ -28,16 +29,21 @@
2829
import ActionsPopover from "./ActionsPopover.svelte";
2930
import LoadDiffDialog from "./LoadDiffDialog.svelte";
3031
import InfoPopup from "./InfoPopup.svelte";
31-
import { Button } from "bits-ui";
32+
import { Button, Label } from "bits-ui";
3233
import { onClickOutside } from "runed";
34+
import SidebarToggle from "./SidebarToggle.svelte";
3335
3436
const globalOptions = GlobalOptions.init();
3537
const viewer = MultiFileDiffViewerState.init();
3638
let sidebarElement: HTMLDivElement | undefined = $state();
3739
3840
onClickOutside(
3941
() => sidebarElement,
40-
() => {
42+
(e) => {
43+
if (e.target instanceof HTMLElement && e.target.closest("[data-sidebar-toggle]")) {
44+
// Ignore toggle button clicks
45+
return;
46+
}
4147
if (!staticSidebar.current) {
4248
viewer.sidebarCollapsed = true;
4349
}
@@ -104,21 +110,6 @@
104110
/>
105111
</svelte:head>
106112

107-
{#snippet sidebarToggle()}
108-
<button
109-
title={viewer.sidebarCollapsed ? "Expand sidebar" : "Collapse sidebar"}
110-
type="button"
111-
class="flex size-6 items-center justify-center rounded-md btn-ghost text-primary"
112-
onclick={() => (viewer.sidebarCollapsed = !viewer.sidebarCollapsed)}
113-
>
114-
{#if viewer.sidebarCollapsed}
115-
<span class="iconify size-4 shrink-0 octicon--sidebar-collapse-16" aria-hidden="true"></span>
116-
{:else}
117-
<span class="iconify size-4 shrink-0 octicon--sidebar-expand-16" aria-hidden="true"></span>
118-
{/if}
119-
</button>
120-
{/snippet}
121-
122113
{#snippet settingsPopover()}
123114
<SettingsPopover class="self-center">
124115
{@render globalThemeSetting()}
@@ -131,14 +122,28 @@
131122
<LabeledCheckbox labelText="Concise nested diffs" bind:checked={globalOptions.omitPatchHeaderOnlyHunks} />
132123
<LabeledCheckbox labelText="Word diffs" bind:checked={globalOptions.wordDiffs} />
133124
<LabeledCheckbox labelText="Line wrapping" bind:checked={globalOptions.lineWrap} />
125+
<div class="flex justify-between px-2 py-1">
126+
<Label.Root id="sidebarLocationLabel" for="sidebarLocation">Sidebar location</Label.Root>
127+
<SimpleRadioGroup
128+
id="sidebarLocation"
129+
aria-labelledby="sidebarLocationLabel"
130+
values={["left", "right"]}
131+
bind:value={globalOptions.sidebarLocation}
132+
/>
133+
</div>
134134
</SettingsPopoverGroup>
135135
</SettingsPopover>
136136
{/snippet}
137137

138138
<div class="relative flex min-h-screen flex-row justify-center">
139139
<div
140140
bind:this={sidebarElement}
141-
class="absolute top-0 left-0 z-10 flex h-full w-full flex-col border-e bg-neutral data-[collapsed=true]:hidden md:w-[350px] md:shadow-md lg:static lg:h-auto lg:shadow-none"
141+
class="absolute top-0 z-10 flex h-full w-full flex-col bg-neutral
142+
data-[collapsed=true]:hidden
143+
data-[side=left]:left-0 data-[side=left]:border-e data-[side=right]:right-0 data-[side=right]:order-10 data-[side=right]:border-s
144+
md:w-[350px] md:shadow-md lg:static
145+
lg:h-auto lg:shadow-none"
146+
data-side={globalOptions.sidebarLocation}
142147
data-collapsed={viewer.sidebarCollapsed}
143148
>
144149
<div class="m-2 flex flex-row items-center gap-2">
@@ -159,9 +164,7 @@
159164
></button>
160165
{/if}
161166
</div>
162-
<div class="flex items-center lg:hidden">
163-
{@render sidebarToggle()}
164-
</div>
167+
<SidebarToggle class="lg:hidden" />
165168
</div>
166169
{#if viewer.filteredFileDetails.length !== viewer.fileDetails.length}
167170
<div class="ms-2 mb-2 text-sm text-gray-600">
@@ -233,8 +236,8 @@
233236
</div>
234237
</div>
235238
</div>
236-
<div class="flex grow flex-col p-3">
237-
<div class="mb-2 flex flex-wrap items-center gap-2">
239+
<div class="flex grow flex-col">
240+
<div class="flex flex-wrap items-center gap-2 px-3 py-2">
238241
{#if viewer.diffMetadata !== null}
239242
<DiffTitle meta={viewer.diffMetadata} />
240243
{/if}
@@ -276,16 +279,16 @@
276279
</InfoPopup>
277280
</div>
278281
</div>
279-
<div class="mb-1 flex flex-row items-center gap-2">
280-
{@render sidebarToggle()}
282+
<div class="flex flex-row items-center gap-2 px-3 pb-2">
283+
<SidebarToggle class="data-[side=right]:order-10" />
281284
{#await viewer.stats}
282285
<DiffStats />
283286
{:then stats}
284287
<DiffStats add={stats.addedLines} remove={stats.removedLines} />
285288
{/await}
286289
<DiffSearch />
287290
</div>
288-
<div class="flex flex-1 flex-col border">
291+
<div class="flex flex-1 flex-col border-t">
289292
<VList data={viewer.fileDetails} style="height: 100%;" getKey={(_, i) => i} bind:this={viewer.vlist} overscan={3}>
290293
{#snippet children(value, index)}
291294
{@const lines = viewer.diffText[index] !== undefined ? viewer.diffText[index] : null}
Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
<script lang="ts">
2+
import { GlobalOptions, MultiFileDiffViewerState } from "$lib/diff-viewer-multi-file.svelte";
3+
import { Button, mergeProps } from "bits-ui";
4+
import { type RestProps } from "$lib/types";
5+
6+
let { ...restProps }: RestProps = $props();
7+
8+
const viewer = MultiFileDiffViewerState.get();
9+
const globalOptions = GlobalOptions.get();
10+
11+
let mergedProps = $derived(
12+
mergeProps(
13+
{
14+
class: "flex size-6 items-center justify-center rounded-md btn-ghost text-primary",
15+
},
16+
restProps,
17+
),
18+
);
19+
</script>
20+
21+
<Button.Root
22+
title={viewer.sidebarCollapsed ? "Expand sidebar" : "Collapse sidebar"}
23+
type="button"
24+
data-side={globalOptions.sidebarLocation}
25+
onclick={() => (viewer.sidebarCollapsed = !viewer.sidebarCollapsed)}
26+
data-sidebar-toggle
27+
{...mergedProps}
28+
>
29+
<span
30+
class="iconify size-4 shrink-0 octicon--sidebar-collapse-16 data-[collapsed=false]:octicon--sidebar-expand-16 data-[side=right]:scale-x-[-1]"
31+
aria-hidden="true"
32+
data-collapsed={viewer.sidebarCollapsed}
33+
data-side={globalOptions.sidebarLocation}
34+
></span>
35+
</Button.Root>

0 commit comments

Comments
 (0)