improve dropdowns

This commit is contained in:
Brent Schroeter 2025-10-25 05:32:22 +00:00
parent a8dd49f730
commit fb6b0f0ed8
10 changed files with 148 additions and 114 deletions

View file

@ -6,7 +6,7 @@
<div class="page-grid__toolbar">
<div class="page-grid__toolbar-utilities">
<a href="settings">
<button class="button--secondary" style="margin-left: 0.5rem;" type="button">
<button class="button--secondary" type="button">
Portal Settings
</button>
</a>
@ -15,6 +15,20 @@
initial-value="{{ filter | json }}"
></filter-menu>
</div>
<div class="page-grid__toolbar-user">
<basic-dropdown alignment="right">
<span slot="button-contents" aria-label="Account menu" title="Account menu">
<i aria-hidden="true" class="ti ti-user"></i>
</span>
<menu class="basic-dropdown__menu" slot="popover">
<li>
<a href="{{ settings.root_path }}/auth/logout" role="button">
Log out
</a>
</li>
</menu>
</basic-dropdown>
</div>
</div>
<div class="page-grid__sidebar">
<div style="padding: 1rem;">

View file

@ -8,21 +8,26 @@
{% endif %}
</h1>
<basic-dropdown button-class="button--secondary button--small" button-aria-label="Workspace Menu">
<span slot="button-contents">...</span>
<span slot="button-contents"><i class="ti ti-dots" aria-hidden="true"></i></span>
<menu slot="popover" class="basic-dropdown__menu">
<li>
<button
popovertarget="sql-conn-dialog"
popovertargetaction="toggle"
type="button"
<a
href="{{ navigator.get_root_path() }}/w/{{ workspace.id.simple() }}/service-credentials"
role="button"
>
PostgreSQL connection
</button>
PostgreSQL Credentials
</a>
</li>
<li>
<a
href="{{ navigator.get_root_path() }}/"
role="button"
>
All Workspaces
</a>
</li>
</menu>
</basic-dropdown>
<dialog class="dialog" id="sql-conn-dialog" popover="auto">
</dialog>
</div>
<section class="workspace-nav__section">
<div class="workspace-nav__heading">

View file

@ -9,7 +9,6 @@
&:popover-open {
@include globals.popover;
left: anchor(left);
padding: 0;
position: absolute;
position-anchor: --anchor-button;
@ -29,9 +28,11 @@
background: #0001;
}
& > button {
& > button, & > [role="button"] {
@include globals.reset-button;
@include globals.reset-anchor;
display: block;
padding: 8px 16px;
}
}

View file

@ -126,6 +126,11 @@ $hover-lightness-scale-factor: -5%;
outline: none;
}
@mixin reset-anchor {
color: inherit;
text-decoration: none;
}
@mixin rounded-sm {
border-radius: $border-radius-rounded-sm;
}

View file

@ -94,8 +94,19 @@ button, input[type="submit"] {
&__toolbar-utilities {
align-items: center;
display: flex;
gap: 12px;
grid-area: utilities;
justify-content: flex-start;
padding: 0 12px;
}
&__toolbar-user {
align-items: center;
display: flex;
gap: 12px;
grid-area: user;
justify-content: flex-end;
padding: 0 12px;
}
&__sidebar {
@ -112,12 +123,6 @@ button, input[type="submit"] {
}
}
.toolbar__utilities {
align-items: center;
display: flex;
justify-content: flex-start;
}
.toolbar-item {
flex: 0;
}

View file

@ -325,14 +325,6 @@ $table-border-color: #ccc;
}
}
.toolbar-item {
padding: 0.5rem;
&__button {
@include globals.button-secondary;
}
}
.toolbar-popover {
&:popover-open {
@include globals.rounded;

View file

@ -3,6 +3,7 @@
props: {
button_aria_label: { attribute: "button-aria-label" },
button_class: { attribute: "button-class" },
alignment: { attribute: "alignment" },
},
tag: "basic-dropdown",
}}
@ -10,13 +11,42 @@
<script lang="ts">
type Props = {
alignment?: string;
button_aria_label?: string;
button_class?: string;
on_toggle?(ev: ToggleEvent): void;
};
let { button_aria_label, button_class = "button--secondary" }: Props =
$props();
let {
alignment,
button_aria_label,
button_class = "button--secondary",
on_toggle,
}: Props = $props();
let popover_element: HTMLElement | undefined = $state();
// Hacky workaround because as of September 2025 implicit anchor association
// is still pretty broken, at least in Firefox.
let anchor_name = $state(`--anchor-${Math.floor(Math.random() * 1000000)}`);
let popover_left = $derived(
alignment?.toLocaleLowerCase("en-US") === "right"
? "unset"
: "anchor(left)",
);
let popover_right = $derived(
alignment?.toLocaleLowerCase("en-US") === "right"
? "anchor(right)"
: "unset",
);
$effect(() => {
if (on_toggle) {
popover_element?.addEventListener("toggle", on_toggle);
return () => {
popover_element?.removeEventListener("toggle", on_toggle);
};
}
});
</script>
<button
@ -24,8 +54,9 @@
class={["basic-dropdown__button", button_class]}
id="dropdown-button"
onclick={() => {
popover_element.showPopover();
popover_element?.showPopover();
}}
style:anchor-name={anchor_name}
type="button"
>
<slot name="button-contents"></slot>
@ -34,6 +65,9 @@
aria-labelledby="dropdown-button"
bind:this={popover_element}
class="basic-dropdown__popover"
style:left={popover_left}
style:position-anchor={anchor_name}
style:right={popover_right}
popover="auto"
>
<slot name="popover"></slot>

View file

@ -96,7 +96,7 @@ incompatible with the current presentation configuration.-->
tag: (typeof all_presentation_tags)[number],
): Presentation {
if (tag === "Dropdown") {
return { t: "Dropdown", c: { allow_custom: true } };
return { t: "Dropdown", c: { allow_custom: true, options: [] } };
}
if (tag === "Text") {
return { t: "Text", c: { input_mode: { t: "SingleLine", c: {} } } };

View file

@ -1,4 +1,5 @@
<script lang="ts">
import BasicDropdown from "./basic-dropdown.webc.svelte";
import { type FieldInfo } from "./field.svelte";
import FieldDetails from "./field-details.svelte";
@ -12,35 +13,12 @@
const original_label_value = field.field.table_label;
let type_indicator_element = $state<HTMLButtonElement | undefined>();
let popover_element = $state<HTMLDivElement | undefined>();
let name_value = $state(field.field.name);
let label_value = $state(field.field.table_label ?? "");
// Hacky workaround because as of September 2025 implicit anchor association
// is still pretty broken, at least in Firefox.
let anchor_name = $state(`--anchor-${Math.floor(Math.random() * 1000000)}`);
$effect(() => {
field.field.table_label = label_value === "" ? undefined : label_value;
});
$effect(() => {
popover_element?.addEventListener("toggle", handle_popover_toggle);
return () => {
popover_element?.removeEventListener("toggle", handle_popover_toggle);
};
});
function handle_type_indicator_element_click() {
popover_element?.togglePopover();
}
function handle_popover_toggle(ev: ToggleEvent) {
if (ev.newState === "closed") {
type_indicator_element?.focus();
field.field.table_label = original_label_value;
label_value = original_label_value ?? "";
}
}
</script>
<div
@ -53,38 +31,40 @@
{field.field.table_label ?? field.field.name}
</div>
<div class="field-header__menu-container">
<button
bind:this={type_indicator_element}
class="field-header__type-indicator"
onclick={handle_type_indicator_element_click}
style:anchor-name={anchor_name}
<BasicDropdown
alignment="right"
button_class="field-header__type-indicator"
on_toggle={(ev) => {
if (ev.newState === "closed") {
type_indicator_element?.focus();
field.field.table_label = original_label_value;
label_value = original_label_value ?? "";
}
}}
>
{#if field.field.presentation.t === "Dropdown"}
<i class="ti ti-pointer"></i>
{:else if field.field.presentation.t === "Text"}
<i class="ti ti-file-text"></i>
{:else if field.field.presentation.t === "Timestamp"}
<i class="ti ti-calendar"></i>
{:else if field.field.presentation.t === "Uuid"}
<i class="ti ti-id"></i>
{/if}
</button>
<div
bind:this={popover_element}
class="field-header__popover"
popover="auto"
style:position-anchor={anchor_name}
>
<form method="post" action="update-field">
<FieldDetails
bind:name_value
bind:label_value
name_input_disabled
presentation={field.field.presentation}
/>
<input type="hidden" name="field_id" value={field.field.id} />
<button class="button--primary" type="submit">Save</button>
</form>
</div>
<span slot="button-contents">
{#if field.field.presentation.t === "Dropdown"}
<i class="ti ti-pointer"></i>
{:else if field.field.presentation.t === "Text"}
<i class="ti ti-file-text"></i>
{:else if field.field.presentation.t === "Timestamp"}
<i class="ti ti-calendar"></i>
{:else if field.field.presentation.t === "Uuid"}
<i class="ti ti-id"></i>
{/if}
</span>
<div slot="popover" style:padding="16px">
<form method="post" action="update-field">
<FieldDetails
bind:name_value
bind:label_value
name_input_disabled
presentation={field.field.presentation}
/>
<input type="hidden" name="field_id" value={field.field.id} />
<button class="button--primary" type="submit">Save</button>
</form>
</div>
</BasicDropdown>
</div>
</div>

View file

@ -32,34 +32,32 @@
}
</script>
<div class="toolbar__utilities">
<div class="toolbar-item">
<button
class="toolbar-item__button"
onclick={handle_toolbar_button_click}
type="button"
>
Filter
</button>
<div bind:this={popover_element} class="toolbar-popover" popover="auto">
<form action="set-filter" method="post">
<ExpressionEditor bind:value={expr} {identifier_hints} />
<div class="toolbar-popover__form-actions">
<input
name="filter_expression"
type="hidden"
value={JSON.stringify(expr)}
/>
<button
class="button--secondary"
onclick={handle_clear_button_click}
type="button"
>
Clear
</button>
<button class="button--primary" type="submit">Apply</button>
</div>
</form>
</div>
<div class="toolbar-item">
<button
class="button--secondary"
onclick={handle_toolbar_button_click}
type="button"
>
Filter
</button>
<div bind:this={popover_element} class="toolbar-popover" popover="auto">
<form action="set-filter" method="post">
<ExpressionEditor bind:value={expr} {identifier_hints} />
<div class="toolbar-popover__form-actions">
<input
name="filter_expression"
type="hidden"
value={JSON.stringify(expr)}
/>
<button
class="button--secondary"
onclick={handle_clear_button_click}
type="button"
>
Clear
</button>
<button class="button--primary" type="submit">Apply</button>
</div>
</form>
</div>
</div>