Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .Jules/palette.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,3 +2,8 @@

**Learning:** Async buttons that handle errors often fail to reset their error state on subsequent attempts. This leads to a confusing UX where a successful retry still displays the error icon, making the user believe the action failed again.
**Action:** Always ensure that error flags (e.g., `hasError`) are reset at the _start_ of the async operation, not just set in the `catch` block.

## 2024-10-24 - In-Place Loading Indicators

**Learning:** Replacing static icons (like search) with loading spinners within the same container (e.g., input field) prevents layout shifts and provides immediate, contextual feedback.
**Action:** Use conditional rendering to swap icons for spinners in `trailingIcon` slots or button contents during async operations.
1 change: 1 addition & 0 deletions .jules/palette.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
## 2024-10-24 - Accessible Icon Props and Loading Button State

**Learning:** Svelte wrapper components (like `Icon.svelte`) must spread `$$restProps` to allow passing accessibility attributes (e.g., `aria-label`) from parent components. Without this, icons remain inaccessible to screen readers. Also, persistent "Success" states on buttons can be confusing; auto-resetting them after a timeout improves clarity.
**Action:** Always include `{...$$restProps}` in wrapper components and implement auto-reset logic for temporary success states in interactive elements.
29 changes: 24 additions & 5 deletions src/routes/DomainSearch.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -22,8 +22,17 @@
$: invalid = domainName !== '' && !validator.validate(domainName, { raiseError: false });
$: nameSearchedLabel = nameSearched ? `${nameSearched}.${$metaNamesSdk.config.tld}` : null;

function debounce() {
$: debounce(domainName);

function debounce(name: string) {
clearTimeout(debounceTimer);

if (name === '' || invalid) {
domain = undefined;
isLoading = false;
return;
}

debounceTimer = setTimeout(async () => await search(), 400);
}

Expand Down Expand Up @@ -60,17 +69,27 @@
class="domain-input"
variant="outlined"
bind:value={domainName}
on:keyup={() => debounce()}
bind:invalid
label="Domain name"
withTrailingIcon
autofocus
>
<svelte:fragment slot="trailingIcon">
<div class="submit">
<IconButton aria-label="search">
<Icon icon="search" />
</IconButton>
{#if isLoading}
<div
class="loading-icon"
role="status"
aria-label="Searching"
style="display: flex; align-items: center; justify-content: center;"
>
<CircularProgress style="height: 24px; width: 24px;" indeterminate />
</div>
{:else}
<IconButton aria-label="search">
<Icon icon="search" />
</IconButton>
{/if}
</div>
</svelte:fragment>
<svelte:fragment slot="helper">
Expand Down
2 changes: 1 addition & 1 deletion vite.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ export default defineConfig({
}),
sveltekit(),
nodePolyfills({
include: ['buffer', 'crypto', 'stream']
include: ['buffer', 'crypto', 'stream', 'util']
}),
tsconfigPaths()
],
Expand Down