mirror of
https://github.com/coollabsio/coolify.git
synced 2026-11-06 13:10:37 -04:00
fix(ui): use our own dropdown for the canvas toolbar filters
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5.5
parent
7e612422b6
commit
ec5d11790e
@@ -0,0 +1,76 @@
|
||||
<script>
|
||||
// Our listbox (same look as <x-forms.listbox>) instead of the native <select>.
|
||||
// The panel is absolute, not fixed: the zoom controls bar has a backdrop filter,
|
||||
// which makes it the containing block of fixed children.
|
||||
|
||||
let { options, value, label, class: className = '', onchange } = $props();
|
||||
|
||||
let open = $state(false);
|
||||
let above = $state(false);
|
||||
let active = $state(0);
|
||||
let root;
|
||||
let trigger;
|
||||
let list = $state();
|
||||
|
||||
const current = $derived(options.find(o => o.value === value) ?? options[0]);
|
||||
|
||||
function toggle() {
|
||||
if (open) return close();
|
||||
const r = trigger.getBoundingClientRect();
|
||||
above = innerHeight - r.bottom < 280 && r.top > innerHeight - r.bottom;
|
||||
active = Math.max(0, options.findIndex(o => o.value === value));
|
||||
open = true;
|
||||
}
|
||||
function close(focus = false) {
|
||||
open = false;
|
||||
if (focus) trigger.focus();
|
||||
}
|
||||
function pick(option) {
|
||||
close(true);
|
||||
if (option.value !== value) onchange(option.value);
|
||||
}
|
||||
function keys(e) {
|
||||
if (e.key === 'Escape' && open) {
|
||||
e.preventDefault();
|
||||
close(true);
|
||||
} else if (e.key === 'ArrowDown' || e.key === 'ArrowUp') {
|
||||
e.preventDefault();
|
||||
if (!open) return toggle();
|
||||
active = (active + (e.key === 'ArrowDown' ? 1 : -1) + options.length) % options.length;
|
||||
} else if (e.key === 'Enter' && open) {
|
||||
e.preventDefault();
|
||||
pick(options[active]);
|
||||
}
|
||||
}
|
||||
|
||||
$effect(() => {
|
||||
if (open) list?.children[active]?.scrollIntoView({ block: 'nearest' });
|
||||
});
|
||||
</script>
|
||||
|
||||
<svelte:window onpointerdown={e => open && !root.contains(e.target) && close()} />
|
||||
|
||||
<div class="relative {className}" bind:this={root} onkeydown={keys} role="presentation">
|
||||
<button type="button" class="listbox-trigger h-8! text-[12px]!" bind:this={trigger} onclick={toggle}
|
||||
aria-haspopup="listbox" aria-expanded={open} aria-label={label} title={label}>
|
||||
<span class="listbox-trigger-label">{current?.label}</span>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="2" stroke="currentColor" class="size-3.5 shrink-0 opacity-60">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" d="m8 9 4-4 4 4m0 6-4 4-4-4" />
|
||||
</svg>
|
||||
</button>
|
||||
{#if open}
|
||||
<div class="listbox-panel" style={above ? 'top: auto; bottom: calc(100% + 0.25rem)' : ''} role="listbox" aria-label={label} bind:this={list}>
|
||||
{#each options as option, i (option.value)}
|
||||
<button type="button" class="listbox-option text-[13px]!" style:background={i === active ? 'var(--coollabs-fill)' : null} role="option" aria-selected={option.value === value}
|
||||
onpointerenter={() => (active = i)} onclick={() => pick(option)}>
|
||||
<span class="truncate">{option.label}</span>
|
||||
{#if option.value === value}
|
||||
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="2.5" stroke="currentColor" class="ml-auto size-3.5 shrink-0">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" d="m4.5 12.75 6 6 9-13.5" />
|
||||
</svg>
|
||||
{/if}
|
||||
</button>
|
||||
{/each}
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
@@ -1,11 +1,13 @@
|
||||
<script>
|
||||
import BetaBadge from '../BetaBadge.svelte';
|
||||
import Select from './Select.svelte';
|
||||
// Filters and view toggles. The root docks it (use:dock) into the zoom controls bar
|
||||
// (CanvasControls, bottom left) on desktop, or in a rounded box above the canvas on small screens.
|
||||
|
||||
let { viewOnly, viewOnlyTitle, filter, projects, environments, views, dock, onfilter } = $props();
|
||||
|
||||
const select = 'select h-8! w-44 py-0! text-[12px]! max-lg:w-auto max-lg:min-w-0 max-lg:flex-1 max-lg:basis-32';
|
||||
const select = 'w-44 max-lg:w-auto max-lg:min-w-0 max-lg:flex-1 max-lg:basis-32';
|
||||
const options = (all, list) => [{ value: '', label: all }, ...list.map(o => ({ value: o.uuid, label: o.name }))];
|
||||
const value = (options, wanted) => (options.some(o => o.uuid === wanted) ? wanted : '');
|
||||
|
||||
/** Table / grid / canvas switcher from the page (Blade), when the page has one. */
|
||||
@@ -24,15 +26,13 @@
|
||||
<!-- Small screens: the actions wrap onto more rows instead of covering the title. -->
|
||||
<div class="ml-auto flex shrink-0 items-center gap-2 max-lg:min-w-0 max-lg:shrink max-lg:flex-wrap" use:adopt={views}>
|
||||
<!-- Connect and server views: show the resources of one project and / or environment only. -->
|
||||
<select class={select} class:hidden={projects.length < 2} aria-label="Filter by project" title="Filter by project"
|
||||
value={value(projects, filter.project)} onchange={e => onfilter({ project: e.currentTarget.value, environment: '' })}>
|
||||
<option value="">All projects</option>
|
||||
{#each projects as o (o.uuid)}<option value={o.uuid}>{o.name}</option>{/each}
|
||||
</select>
|
||||
<select class={select} class:hidden={environments.length < 2} aria-label="Filter by environment" title="Filter by environment"
|
||||
value={value(environments, filter.environment)} onchange={e => onfilter({ ...filter, environment: e.currentTarget.value })}>
|
||||
<option value="">All environments</option>
|
||||
{#each environments as o (o.uuid)}<option value={o.uuid}>{o.name}</option>{/each}
|
||||
</select>
|
||||
{#if projects.length > 1}
|
||||
<Select class={select} label="Filter by project" value={value(projects, filter.project)}
|
||||
options={options('All projects', projects)} onchange={project => onfilter({ project, environment: '' })} />
|
||||
{/if}
|
||||
{#if environments.length > 1}
|
||||
<Select class={select} label="Filter by environment" value={value(environments, filter.environment)}
|
||||
options={options('All environments', environments)} onchange={environment => onfilter({ ...filter, environment })} />
|
||||
{/if}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
Reference in New Issue
Block a user