Skip to content

Commit 0b313da

Browse files
committed
refactor: overhaul button styles and UI polish
- Add modular button utilities with CSS variables for consistent theming - Update icon colors to text-em-med where applicable - Add focus rings and inset shadows to form inputs - Replace dialog header background with border - Fix DiffStats empty state display - Rename DirectoryInput 'loading' prop to 'picking' for clarity - Add cancel handler for legacy directory picker
1 parent a42b3cc commit 0b313da

16 files changed

+200
-111
lines changed

web/src/app.css

Lines changed: 79 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -76,49 +76,105 @@
7676
}
7777
}
7878

79-
@utility btn-ghost-hover {
80-
@apply bg-neutral-300/80 dark:bg-neutral-700/80;
79+
@utility btn-link-hover {
80+
@apply text-blue-500;
8181
}
82-
@utility btn-ghost-active {
83-
@apply bg-neutral-400/80 dark:bg-neutral-600/80;
82+
@utility btn-link-active {
83+
@apply text-blue-600;
8484
}
85-
@utility btn-ghost {
85+
@utility btn-link {
8686
&:hover {
87-
@apply btn-ghost-hover;
87+
@apply btn-link-hover;
8888
}
8989
&:active {
90-
@apply btn-ghost-active;
90+
@apply btn-link-active;
9191
}
92+
@apply cursor-pointer text-em-med transition-colors duration-100 ease-in-out;
93+
}
94+
95+
@utility btn-base {
96+
--btn-color: var(--color-primary);
97+
--btn-focus: var(--color-primary);
98+
--tw-shadow-color: color-mix(in oklab, var(--btn-color) 50%, transparent);
99+
100+
--btn-hover: color-mix(in oklch, var(--btn-color), black 5%);
101+
--btn-active: color-mix(in oklch, var(--btn-color), black 10%);
102+
--btn-border: color-mix(in oklch, var(--btn-color), black 12%);
103+
@variant dark {
104+
--btn-hover: color-mix(in oklch, var(--btn-color), white 5%);
105+
--btn-active: color-mix(in oklch, var(--btn-color), white 10%);
106+
--btn-border: color-mix(in oklch, var(--btn-color), white 12%);
107+
}
108+
109+
border-color: var(--btn-border);
110+
92111
@apply transition-colors duration-100 ease-in-out;
112+
@apply ring-(--btn-focus) focus:outline-none focus-visible:ring-2 has-focus-visible:ring-2;
93113
}
94-
@utility btn-ghost-visible {
95-
@apply btn-ghost-hover;
114+
115+
@utility btn-fill {
116+
@apply border btn-base shadow-xs;
117+
118+
background-color: var(--btn-color);
119+
120+
&:hover {
121+
background-color: var(--btn-hover);
122+
}
96123
&:active {
97-
@apply btn-ghost-active;
124+
background-color: var(--btn-active);
125+
@apply shadow-none;
98126
}
99127
}
100128

101-
@utility btn-primary-hover {
102-
@apply bg-blue-600;
129+
@utility btn-fill-danger {
130+
@apply btn-fill text-white;
131+
--btn-color: var(--color-red-500);
103132
}
104-
@utility btn-primary {
105-
@apply bg-primary text-white;
106-
&:hover {
107-
@apply btn-primary-hover;
133+
134+
@utility btn-fill-primary {
135+
@apply btn-fill text-white;
136+
--btn-color: var(--color-primary);
137+
--btn-focus: var(--color-neutral-800);
138+
@variant dark {
139+
--btn-focus: var(--color-neutral-100);
108140
}
109-
&:active {
110-
@apply bg-blue-700;
141+
}
142+
143+
@utility btn-fill-neutral {
144+
@apply btn-fill;
145+
--btn-color: var(--color-neutral-100);
146+
@variant dark {
147+
--btn-color: var(--color-neutral-800);
111148
}
112-
@apply transition-colors duration-100 ease-in-out;
113149
}
114150

115-
@utility btn-danger {
116-
@apply bg-red-500 text-white;
151+
@utility btn-ghost {
152+
@apply btn-base;
153+
154+
--btn-color: var(--color-neutral-100);
155+
@variant dark {
156+
--btn-color: var(--color-neutral-800);
157+
}
158+
117159
&:hover {
118-
@apply bg-red-600;
160+
background-color: var(--btn-hover);
119161
}
120162
&:active {
121-
@apply bg-red-700;
163+
background-color: var(--btn-active);
164+
}
165+
}
166+
@utility btn-ghost-hover {
167+
@apply btn-ghost;
168+
background-color: var(--btn-hover);
169+
}
170+
@utility btn-ghost-active {
171+
@apply btn-ghost;
172+
background-color: var(--btn-active);
173+
}
174+
@utility btn-ghost-visible {
175+
@apply btn-ghost-hover;
176+
&:active {
177+
@apply btn-ghost-active;
122178
}
123179
}
124180

web/src/lib/components/InfoPopup.svelte

Lines changed: 2 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -5,11 +5,8 @@
55
</script>
66

77
<Popover.Root>
8-
<Popover.Trigger
9-
title="Information"
10-
class="flex size-6 items-center justify-center self-center rounded-md btn-ghost p-0.5 data-[state=open]:btn-ghost-visible"
11-
>
12-
<span aria-hidden="true" class="iconify size-4 bg-primary octicon--info-16"></span>
8+
<Popover.Trigger title="Information" class="flex size-6 btn-link items-center justify-center self-center p-0.5 data-[state=open]:btn-link-hover">
9+
<span aria-hidden="true" class="iconify size-4 octicon--info-16"></span>
1310
</Popover.Trigger>
1411
<Popover.Portal>
1512
<Popover.Content class="z-50 mx-2 max-w-64 rounded-sm border bg-neutral px-2 py-1 text-sm">

web/src/lib/components/SidebarToggle.svelte

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@
1313
mergeProps(
1414
{
1515
title: viewer.layoutState.sidebarCollapsed ? "Expand sidebar" : "Collapse sidebar",
16-
class: "flex size-6 items-center justify-center rounded-md btn-ghost text-primary",
16+
class: "flex size-6 items-center justify-center btn-ghost rounded-sm",
1717
},
1818
restProps,
1919
),
@@ -22,7 +22,7 @@
2222

2323
<Button.Root type="button" data-side={globalOptions.sidebarLocation} onclick={() => viewer.layoutState.toggleSidebar()} data-sidebar-toggle {...mergedProps}>
2424
<span
25-
class="iconify size-4 shrink-0 octicon--sidebar-collapse-16 data-[collapsed=false]:octicon--sidebar-expand-16 data-[side=right]:scale-x-[-1]"
25+
class="iconify size-4 shrink-0 text-em-med octicon--sidebar-collapse-16 data-[collapsed=false]:octicon--sidebar-expand-16 data-[side=right]:scale-x-[-1]"
2626
aria-hidden="true"
2727
data-collapsed={viewer.layoutState.sidebarCollapsed}
2828
data-side={globalOptions.sidebarLocation}

web/src/lib/components/diff/DiffStats.svelte

Lines changed: 9 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -73,11 +73,15 @@
7373
<Tooltip.Root>
7474
<Tooltip.Trigger>
7575
<div class="relative h-3 w-12 bg-neutral-2">
76-
<div
77-
class="absolute top-0 left-0 h-3 border border-green-700 bg-green-600 dark:border-green-600 dark:bg-green-400"
78-
style={getAddBarStyle()}
79-
></div>
80-
<div class="absolute top-0 h-3 border border-red-700 bg-red-600 dark:border-red-600 dark:bg-red-400" style={getRemoveBarStyle()}></div>
76+
{#if add === 0 && remove === 0}
77+
<div class="absolute top-0 left-0 h-3 w-full border"></div>
78+
{:else}
79+
<div
80+
class="absolute top-0 left-0 h-3 border border-green-700 bg-green-600 dark:border-green-600 dark:bg-green-400"
81+
style={getAddBarStyle()}
82+
></div>
83+
<div class="absolute top-0 h-3 border border-red-700 bg-red-600 dark:border-red-600 dark:bg-red-400" style={getRemoveBarStyle()}></div>
84+
{/if}
8185
</div>
8286
</Tooltip.Trigger>
8387
<Tooltip.Portal>

web/src/lib/components/files/DirectoryInput.svelte

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -3,22 +3,22 @@
33
import { type DirectoryEntry, type DirectoryInputProps, DirectoryInputState } from "$lib/components/files/index.svelte";
44
import { box } from "svelte-toolbelt";
55
6-
let { children, directory = $bindable<DirectoryEntry | undefined>(), loading = $bindable(false), ...restProps }: DirectoryInputProps = $props();
6+
let { children, directory = $bindable<DirectoryEntry | undefined>(), picking = $bindable(false), ...restProps }: DirectoryInputProps = $props();
77
88
const instance = new DirectoryInputState({
99
directory: box.with(
1010
() => directory,
1111
(v) => (directory = v),
1212
),
13-
loading: box.with(
14-
() => loading,
15-
(v) => (loading = v),
13+
picking: box.with(
14+
() => picking,
15+
(v) => (picking = v),
1616
),
1717
});
1818
1919
const mergedProps = $derived(mergeProps(instance.props, restProps));
2020
</script>
2121

2222
<Button.Root type="button" {...mergedProps}>
23-
{@render children?.({ directory, loading })}
23+
{@render children?.({ directory, picking })}
2424
</Button.Root>

web/src/lib/components/files/DirectorySelect.svelte

Lines changed: 8 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -11,17 +11,16 @@
1111
let { placeholder = "Select Directory", directory = $bindable<DirectoryEntry | undefined>(undefined) }: Props = $props();
1212
</script>
1313

14-
<DirectoryInput class="flex max-w-full items-center gap-2 rounded-md border btn-ghost px-2 py-1" bind:directory>
15-
{#snippet children({ directory, loading })}
16-
<span class="iconify size-4 shrink-0 text-em-disabled octicon--file-directory-16"></span>
17-
{#if !loading && directory}
14+
<DirectoryInput class="flex max-w-full items-center gap-2 rounded-md btn-fill-neutral px-2 py-1" bind:directory>
15+
{#snippet children({ directory, picking })}
16+
<span class="iconify size-4 shrink-0 text-em-med octicon--file-directory-16"></span>
17+
{#if picking}
18+
<span>Picking {placeholder}...</span><Spinner size={4} />
19+
{:else if directory}
1820
<span class="truncate">{directory.fileName}</span>
1921
{:else}
20-
<span class="font-light">{placeholder}</span>
22+
<span>Pick {placeholder}</span>
2123
{/if}
22-
{#if loading}
23-
<Spinner size={4} />
24-
{/if}
25-
<span class="iconify size-4 shrink-0 text-em-disabled octicon--triangle-down-16"></span>
24+
<span class="iconify size-4 shrink-0 text-em-med octicon--triangle-down-16"></span>
2625
{/snippet}
2726
</DirectoryInput>

web/src/lib/components/files/FileTypeSelect.svelte

Lines changed: 11 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -52,31 +52,35 @@
5252
<!-- TODO Label gets active & hover styles, but doesn't open selector -->
5353
<Label.Root id={fileTypeLabelId} for={fileTypeId} class="text-sm">File Type</Label.Root>
5454
<Select.Root type="single" bind:value scrollAlignment="center">
55-
<Select.Trigger id={fileTypeId} aria-labelledby={fileTypeLabelId} class="flex items-center gap-1 rounded-sm border btn-ghost px-2 text-sm">
55+
<Select.Trigger
56+
id={fileTypeId}
57+
aria-labelledby={fileTypeLabelId}
58+
class="flex items-center gap-1 rounded-sm btn-fill-neutral px-2 text-sm data-[state=open]:bg-(--btn-hover) data-[state=open]:active:bg-(--btn-active)"
59+
>
5660
{#if value === "auto"}
5761
Infer Type
5862
{:else}
5963
{languagesMap.get(value)?.name ?? "Unknown Type (Error?)"}
6064
{/if}
61-
<span aria-hidden="true" class="iconify size-4 shrink-0 text-base text-em-disabled octicon--triangle-down-16"></span>
65+
<span aria-hidden="true" class="iconify size-4 shrink-0 text-base text-em-med octicon--triangle-down-16"></span>
6266
</Select.Trigger>
6367
<Select.Portal>
64-
<Select.Content class="z-100 mt-0.5 flex max-h-64 flex-col rounded-sm border bg-neutral p-1.5 shadow-md">
68+
<Select.Content class="z-100 mt-0.5 flex max-h-64 flex-col gap-1 rounded-md border bg-neutral p-1 shadow-md">
6569
{#if allowAuto}
66-
<Select.Group class="mb-1">
70+
<Select.Group>
6771
<Select.Item
6872
value="auto"
69-
class="cursor-default rounded-sm px-2 py-1 text-sm data-highlighted:bg-neutral-3 data-selected:bg-primary data-selected:text-white"
73+
class="cursor-default rounded-sm border px-2 py-1 text-sm data-highlighted:bg-neutral-3 data-selected:bg-primary data-selected:text-white"
7074
>
7175
Infer Type
7276
</Select.Item>
7377
</Select.Group>
7478
{/if}
75-
<Select.Group class="flex grow flex-col gap-1 overflow-y-auto">
79+
<Select.Group class="flex grow flex-col overflow-y-auto rounded-sm border">
7680
{#each languages as lang (lang.id)}
7781
<Select.Item
7882
value={lang.id}
79-
class="group flex cursor-default flex-col rounded-sm px-2 py-1 text-sm data-highlighted:bg-neutral-3 data-selected:bg-primary data-selected:text-white"
83+
class="group flex cursor-default flex-col px-2 py-1 text-sm data-highlighted:bg-neutral-3 data-selected:bg-primary data-selected:text-white"
8084
>
8185
{lang.name}
8286
<span class="text-sm font-light text-em-med group-data-selected:text-white/80">{lang.aliases.join(", ")}</span>

web/src/lib/components/files/MultimodalFileInput.svelte

Lines changed: 20 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -77,7 +77,10 @@
7777
</script>
7878

7979
{#snippet radioItem(name: string)}
80-
<RadioGroup.Item value={name.toLowerCase()} class="rounded-sm px-2 text-sm data-[state=checked]:btn-primary data-[state=unchecked]:btn-ghost">
80+
<RadioGroup.Item
81+
value={name.toLowerCase()}
82+
class="rounded-sm px-2 text-sm ring-primary hover:bg-neutral/65 focus:outline-none focus-visible:ring-2 data-[state=checked]:bg-neutral data-[state=checked]:shadow-sm"
83+
>
8184
{name}
8285
</RadioGroup.Item>
8386
{/snippet}
@@ -91,7 +94,7 @@
9194
ondragleavecapture={handleDragLeave}
9295
>
9396
<div class="mb-1 flex w-full flex-wrap items-center gap-1">
94-
<RadioGroup.Root class="me-2 flex overflow-hidden rounded-sm border" bind:value={instance.mode}>
97+
<RadioGroup.Root class="me-2 flex overflow-hidden rounded-md bg-neutral-3 p-0.5" bind:value={instance.mode}>
9598
{@render radioItem("File")}
9699
{@render radioItem("Text")}
97100
{@render radioItem("URL")}
@@ -114,11 +117,24 @@
114117
{/snippet}
115118

116119
{#snippet urlInput()}
117-
<input title="{label} URL" bind:value={instance.url} placeholder="Enter file URL" type="url" {required} class="w-full rounded-md border px-2 py-1" />
120+
<input
121+
title="{label} URL"
122+
bind:value={instance.url}
123+
placeholder="Enter file URL"
124+
type="url"
125+
{required}
126+
class="w-full rounded-md border bg-neutral px-2 py-1 inset-shadow-xs ring-primary focus:outline-none focus-visible:ring-2"
127+
/>
118128
{/snippet}
119129

120130
{#snippet textInput()}
121-
<textarea title="{label} Text" bind:value={instance.text} placeholder="Enter text here" {required} class="w-full rounded-md border px-2 py-1"></textarea>
131+
<textarea
132+
title="{label} Text"
133+
bind:value={instance.text}
134+
placeholder="Enter text here"
135+
{required}
136+
class="w-full rounded-md border bg-neutral px-2 py-1 inset-shadow-xs ring-primary focus:outline-none focus-visible:ring-2"
137+
></textarea>
122138
{/snippet}
123139

124140
<style>

web/src/lib/components/files/SingleFileInput.svelte

Lines changed: 12 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -29,13 +29,20 @@
2929
const inputId = `${uid}-input`;
3030
</script>
3131

32-
<label id={labelId} for={inputId} class="relative flex w-fit max-w-full items-center gap-2 rounded-md border btn-ghost px-2 py-1 has-focus-visible:outline-2">
33-
<span class="iconify size-4 shrink-0 text-em-disabled octicon--file-16"></span>
32+
<label id={labelId} for={inputId} class="relative flex w-fit max-w-full items-center gap-2 rounded-md btn-fill-neutral px-2 py-1">
33+
<span class="iconify size-4 shrink-0 text-em-med octicon--file-16"></span>
3434
{#if file}
3535
<span class="truncate">{file.name}</span>
3636
{:else}
37-
<span class="font-light">{label}</span>
37+
<span>Pick {label}</span>
3838
{/if}
39-
<span class="iconify size-4 shrink-0 text-em-disabled octicon--triangle-down-16"></span>
40-
<input id={inputId} aria-labelledby={labelId} type="file" {required} bind:files={getFiles, setFiles} class="absolute top-0 left-0 size-full opacity-0" />
39+
<span class="iconify size-4 shrink-0 text-em-med octicon--triangle-down-16"></span>
40+
<input
41+
id={inputId}
42+
aria-labelledby={labelId}
43+
type="file"
44+
{required}
45+
bind:files={getFiles, setFiles}
46+
class="absolute top-0 left-0 size-full opacity-0 focus:outline-none"
47+
/>
4148
</label>

0 commit comments

Comments
 (0)