chore(svelte): Update fuzzy finder designs (#63354)

Closes srch-458

This implements the new fuzzy finder design, specifically:

- Backdrop and dropshadow
- Border radius
- Tab header (affects all tab headers)
- Options

Note 1: Some aspects of the options UI (such as how paths are rendered
and highlighted), and the "footer" depend on how the highlighted parts
are computed. This will change when the local matching and ranking logic
is removed and will be updated when that happens.

Note 2: The symbol icon coloring was broken by #63288 and will be fixed
in a separate PR.

## Test plan

Manual testing
This commit is contained in:
Felix Kling 2024-06-20 10:28:29 +02:00 committed by GitHub
parent 10f015b18f
commit 3b7919501a
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
5 changed files with 101 additions and 32 deletions

View File

@ -52,12 +52,11 @@ A component to display the keyboard shortcuts for the application.
line-height: 1;
background-color: var(--secondary-4);
color: var(--text-muted);
color: var(--text-body);
// When inside a selected container, show the selected variant
:global([aria-selected='true']) & {
color: white;
background-color: var(--primary);
color: var(--primary);
}
}
</style>

View File

@ -77,7 +77,7 @@
align-items: center;
min-height: 2rem;
padding: 0.25rem 0.75rem;
color: var(--text-body);
color: var(--text-muted);
display: inline-flex;
flex-flow: row nowrap;
justify-content: center;
@ -95,6 +95,8 @@
}
&:hover {
--icon-color: currentColor;
color: var(--text-title);
background-color: var(--secondary-2);
}
@ -102,9 +104,7 @@
&[aria-selected='true'] {
--icon-color: currentColor;
font-weight: 500;
color: var(--text-title);
background-color: var(--secondary-2);
color: var(--primary);
&::after {
border-color: var(--primary);
@ -119,10 +119,16 @@
&::before {
content: attr(data-tab-title);
display: block;
font-weight: 500;
height: 0;
visibility: hidden;
}
}
&[aria-selected='true'] span,
span::before {
// Hidden rendering of the bold tab title to prevent
// shifting when the tab is selected.
font-weight: 500;
}
}
</style>

View File

@ -18,6 +18,7 @@ export {
isLinuxPlatform,
getPlatform,
} from '@sourcegraph/common/src/util/browserDetection'
export { dirname, basename } from '@sourcegraph/common/src/util/path'
let highlightingLoaded = false

View File

@ -13,6 +13,7 @@
import { isMacPlatform } from '@sourcegraph/common'
import { dirname } from '$lib/common'
import { nextSibling, onClickOutside, previousSibling } from '$lib/dom'
import { getGraphQLClient } from '$lib/graphql'
import Icon from '$lib/Icon.svelte'
@ -20,6 +21,7 @@
import CodeHostIcon from '$lib/search/CodeHostIcon.svelte'
import EmphasizedLabel from '$lib/search/EmphasizedLabel.svelte'
import SymbolKindIcon from '$lib/search/SymbolKindIcon.svelte'
import { displayRepoName } from '$lib/shared'
import TabsHeader, { type Tab } from '$lib/TabsHeader.svelte'
import { Input } from '$lib/wildcard'
import Button from '$lib/wildcard/Button.svelte'
@ -181,6 +183,8 @@
</script>
<dialog bind:this={dialog} on:close>
<!-- We cannot use the `use:onClickOutside` directive on the dialog element itself because the element will take
up the entire viewport and the event will never be triggered. -->
<div class="content" use:onClickOutside on:click-outside={() => dialog?.close()}>
<header>
<TabsHeader
@ -193,9 +197,11 @@
input?.focus()
}}
/>
<Button variant="icon" on:click={() => dialog?.close()} size="sm">
<Icon icon={ILucideX} aria-label="Close" inline />
</Button>
<span class="close">
<Button variant="icon" on:click={() => dialog?.close()} size="sm">
<Icon icon={ILucideX} aria-label="Close" />
</Button>
</span>
</header>
<main>
<div class="input">
@ -223,41 +229,51 @@
<ul role="listbox" bind:this={listbox} aria-label="Search results">
{#if $source.value}
{#each $source.value as item, index (item.item)}
{@const repo = item.item.repository.name}
{@const displayRepo = displayRepoName(repo)}
<li role="option" aria-selected={selectedOption === index} data-index={index}>
{#if item.item.type === 'repo'}
{@const matchOffset = repo.length - displayRepo.length}
<a href="/{item.item.repository.name}" on:click={handleClick}>
<CodeHostIcon repository={item.item.repository.name} />
<span
<span class="icon"><CodeHostIcon repository={item.item.repository.name} /></span>
<span class="label"
><EmphasizedLabel
label={item.item.repository.name}
label={displayRepo}
matches={item.positions}
offset={matchOffset}
/></span
>
<span class="info">{repo}</span>
</a>
{:else if item.item.type == 'symbol'}
<a href={item.item.symbol.location.url} on:click={handleClick}>
<SymbolKindIcon symbolKind={item.item.symbol.kind} />
<span
<span class="icon"><SymbolKindIcon symbolKind={item.item.symbol.kind} /></span>
<span class="label"
><EmphasizedLabel
label={item.item.symbol.name}
matches={item.positions}
/></span
>
<small>-</small>
<FileIcon file={item.item.file} inline />
<small
>{#if !useScope}{item.item.repository.name}/{/if}{item.item.file.path}</small
<span class="info mono"
>{#if !useScope}{displayRepo} &middot; {/if}{item.item.file.path}</span
>
</a>
{:else if item.item.type == 'file'}
{@const fileName = item.item.file.name}
{@const folderName = dirname(item.item.file.path)}
<a href={item.item.file.url} on:click={handleClick}>
<FileIcon file={item.item.file} inline />
<span
>{#if !useScope}{item.item.repository.name}/{/if}<EmphasizedLabel
label={item.item.file.path}
<span class="icon"><FileIcon file={item.item.file} inline /></span>
<span class="label"
><EmphasizedLabel
label={fileName}
matches={item.positions}
offset={folderName.length + 1}
/></span
>
<span class="info mono">
{#if !useScope}{displayRepo} &middot; {/if}
<EmphasizedLabel label={folderName} matches={item.positions} />
</span>
</a>
{/if}
</li>
@ -274,14 +290,17 @@
dialog {
width: 80vw;
height: 80vh;
border: none;
border-radius: 0.75rem;
padding: 0;
overflow: hidden;
border: 1px solid var(--border-color);
border-radius: var(--border-radius);
background-color: var(--body-bg);
background-color: var(--color-bg-1);
box-shadow: var(--fuzzy-finder-shadow);
&::backdrop {
background-color: var(--modal-bg);
background: var(--fuzzy-finder-backdrop);
}
}
@ -316,14 +335,18 @@
[role='option'] {
a {
display: flex;
align-items: center;
padding: 0.25rem 1rem;
display: grid;
grid-template-columns: [icon] auto [label] 1fr;
grid-template-rows: auto;
grid-template-areas: 'icon label' '. info';
column-gap: 0.5rem;
cursor: pointer;
color: var(--body-color);
gap: 0.25rem;
padding: 0.25rem 0.75rem;
text-decoration: none;
color: var(--body-color);
font-size: var(--font-size-small);
}
small {
@ -334,6 +357,28 @@
a:hover {
background-color: var(--color-bg-2);
}
.icon {
grid-area: icon;
// Centers the icon vertically
display: flex;
align-items: center;
}
.label {
grid-area: label;
}
.info {
grid-area: info;
color: var(--text-muted);
font-size: var(--font-size-extra-small);
&.mono {
font-family: var(--code-font-family);
}
}
}
.empty {
@ -358,5 +403,16 @@
bottom: 0;
left: 0;
}
.close {
position: fixed;
right: 2rem;
background-color: var(--color-bg-1);
border-radius: 50%;
&:hover {
background-color: var(--color-bg-2);
}
}
}
</style>

View File

@ -16,6 +16,7 @@ body {
--code-font-family: var(--monospace-font-family);
--font-size-base: 0.9375rem;
--font-size-small: 0.875rem;
--font-size-extra-small: 0.6875rem;
--font-size-tiny: 0.8125rem;
--code-font-size: 13px;
--border-radius: 4px;
@ -43,6 +44,7 @@ label {
--code-bg: var(--white);
--modal-bg: rgba(219, 226, 240, 0.5);
--code-selection-bg: rgba(231, 238, 250, 0.8);
--fuzzy-finder-backdrop: rgba(255, 255, 255, 0.96);
// Shadows
--sidebar-shadow: 0 79px 22px 0 rgba(30, 4, 47, 0), 0 51px 20px 0 rgba(30, 4, 47, 0.01),
@ -57,6 +59,8 @@ label {
0 -18px 11px 0 rgba(1, 6, 12, 0.02), 0 -8px 8px 0 rgba(1, 6, 12, 0.03), 0 -2px 4px 0 rgba(1, 6, 12, 0.04);
--popover-shadow: 0 174px 49px 0 rgba(0, 0, 0, 0), 0 112px 45px 0 rgba(0, 0, 0, 0.01),
0 63px 38px 0 rgba(0, 0, 0, 0.02), 0 28px 28px 0 rgba(0, 0, 0, 0.03), 0 7px 15px 0 rgba(0, 0, 0, 0.04);
--fuzzy-finder-shadow: 0 186px 52px 0 rgba(0, 0, 0, 0), 0 119px 48px 0 rgba(0, 0, 0, 0.01),
0 67px 40px 0 rgba(0, 0, 0, 0.02), 0 30px 30px 0 rgba(0, 0, 0, 0.03), 0 7px 16px 0 rgba(0, 0, 0, 0.04);
}
// Theme Dark
@ -71,6 +75,7 @@ label {
--code-selection-bg: var(--color-bg-2);
--code-bg: var(--gray-12);
--modal-bg: rgba(52, 58, 77, 0.5);
--fuzzy-finder-backdrop: rgba(20, 23, 31, 0.96);
// Shadows
--sidebar-shadow: 0 240px 80px 0 rgba(12, 1, 19, 0.01), 0 160px 80px 0 rgba(12, 1, 19, 0.02),
@ -85,4 +90,6 @@ label {
0 -16px 8px 0 rgba(0, 0, 0, 0.08), 0 -8px 4px 0 rgba(0, 0, 0, 0.16), 0 -2px 4px 0 rgba(0, 0, 0, 0.32);
--popover-shadow: 0 174px 49px 0 rgba(0, 0, 0, 0.01), 0 112px 45px 0 rgba(0, 0, 0, 0.05),
0 63px 38px 0 rgba(0, 0, 0, 0.16), 0 28px 28px 0 rgba(0, 0, 0, 0.27), 0 7px 15px 0 rgba(0, 0, 0, 0.31);
--fuzzy-finder-shadow: 0 186px 52px 0 rgba(0, 0, 0, 0.01), 0 119px 48px 0 rgba(0, 0, 0, 0.03),
0 67px 40px 0 rgba(0, 0, 0, 0.06), 0 30px 30px 0 rgba(0, 0, 0, 0.12), 0 7px 16px 0 rgba(0, 0, 0, 0.24);
}