fix: UI/UX consistency overhaul — fix 8 bugs, standardize design system
Bug fixes:
- Backup refresh no longer re-renders entire page (separate refreshing state)
- SSL cert button no longer flickers when no certs available
- Volume mode selector rewritten to use proper scope system (7 scopes)
- Navigation flicker eliminated when returning from env/volumes pages
- Logout button moved to sidebar footer near theme/locale controls
- Subdomain pattern now shows variable hint tooltip ({project}, {stage}, etc.)
- SSL certificate selector moved to Credentials page with auto-save
- Projects page now has search/filter by name, image, or registry
Consistency improvements:
- New Breadcrumb component replaces 5 inline implementations
- New IconArrowLeft, IconChevronDown components replace inline SVGs
- All inline spinners replaced with IconLoader component
- 10 semantic badge classes with dark mode variants in tokens.css
- Global disabled button cursor-not-allowed rule
- Raw inputs in auth page replaced with FormField components
- Missing aria-labels added to icon-only buttons
- Error panels standardized to use design tokens
This commit is contained in:
@@ -12,6 +12,18 @@
|
||||
let loading = $state(true);
|
||||
let error = $state('');
|
||||
let showAddForm = $state(false);
|
||||
let searchQuery = $state('');
|
||||
|
||||
const filteredProjects = $derived(
|
||||
searchQuery.trim()
|
||||
? projects.filter(p => {
|
||||
const q = searchQuery.toLowerCase();
|
||||
return p.name.toLowerCase().includes(q)
|
||||
|| p.image.toLowerCase().includes(q)
|
||||
|| (p.registry ?? '').toLowerCase().includes(q);
|
||||
})
|
||||
: projects
|
||||
);
|
||||
|
||||
let formName = $state('');
|
||||
let formImage = $state('');
|
||||
@@ -220,6 +232,22 @@
|
||||
icon="projects"
|
||||
/>
|
||||
{:else}
|
||||
<!-- Search filter -->
|
||||
<div class="relative">
|
||||
<IconSearch size={16} class="absolute left-3 top-1/2 -translate-y-1/2 text-[var(--text-tertiary)]" />
|
||||
<input
|
||||
type="text"
|
||||
bind:value={searchQuery}
|
||||
placeholder={$t('projects.searchPlaceholder')}
|
||||
class="w-full rounded-lg border border-[var(--border-primary)] bg-[var(--surface-card)] py-2.5 pl-10 pr-4 text-sm text-[var(--text-primary)] placeholder:text-[var(--text-tertiary)] focus:border-[var(--color-brand-500)] focus:outline-none focus:ring-1 focus:ring-[var(--color-brand-500)]"
|
||||
/>
|
||||
</div>
|
||||
|
||||
{#if filteredProjects.length === 0}
|
||||
<div class="rounded-xl border border-[var(--border-primary)] bg-[var(--surface-card)] p-8 text-center">
|
||||
<p class="text-sm text-[var(--text-tertiary)]">{$t('projects.noMatchingProjects')}</p>
|
||||
</div>
|
||||
{:else}
|
||||
<div class="overflow-x-auto rounded-xl border border-[var(--border-primary)] bg-[var(--surface-card)] shadow-[var(--shadow-sm)]">
|
||||
<table class="min-w-full divide-y divide-[var(--border-primary)]">
|
||||
<thead class="bg-[var(--surface-card-hover)]">
|
||||
@@ -233,7 +261,7 @@
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody class="divide-y divide-[var(--border-secondary)]">
|
||||
{#each projects as project (project.id)}
|
||||
{#each filteredProjects as project (project.id)}
|
||||
<tr class="hover:bg-[var(--surface-card-hover)] transition-colors duration-150">
|
||||
<td class="whitespace-nowrap px-6 py-4">
|
||||
<a href="/projects/{project.id}" class="font-medium text-[var(--text-link)] hover:text-[var(--text-link-hover)] transition-colors">
|
||||
@@ -262,5 +290,6 @@
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
{/if}
|
||||
{/if}
|
||||
</div>
|
||||
|
||||
Reference in New Issue
Block a user