Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
15 commits
Select commit Hold shift + click to select a range
0c7b467
fix: match by key when $id is absent to prevent all variables being o…
HarshMN2345 Jun 29, 2026
806df25
revert: remove portal from wizard env variables popover — breaks onclick
HarshMN2345 Jun 29, 2026
0a21d85
fix: allow action menu to overflow table cell so popover is not clipped
HarshMN2345 Jun 29, 2026
56515de
fix: override table cell overflow to unclip action menu popover
HarshMN2345 Jun 29, 2026
9f2ef42
fix: use portal for action menu popover, set state before toggle to e…
HarshMN2345 Jun 29, 2026
3bdc82f
fix: replace pink Popover with custom Svelte 5 floating action menu t…
HarshMN2345 Jun 29, 2026
de34d5e
fix: add background, border and shadow to floating action menu
HarshMN2345 Jun 29, 2026
e1e5ce4
fix: portal menu div to document.body to prevent table cell layout br…
HarshMN2345 Jun 29, 2026
7160ccb
fix: reduce min column widths on small viewport to prevent table over…
HarshMN2345 Jun 29, 2026
d86ad4e
fix: use small min widths on mobile so table fits inside wizard conta…
HarshMN2345 Jun 29, 2026
7193390
fix: show icon-only create variable button on mobile to save horizont…
HarshMN2345 Jun 29, 2026
c58a082
fix: add strategy fixed, return autoUpdate cleanup from effect, use o…
HarshMN2345 Jun 29, 2026
d73328c
format
HarshMN2345 Jun 29, 2026
84a7c73
revert: undo greptile fixes (strategy fixed, effect cleanup, onclick)
HarshMN2345 Jun 29, 2026
2aa0eb7
fix: remove stopPropagation so outside-click handler can close other …
HarshMN2345 Jun 29, 2026
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
97 changes: 25 additions & 72 deletions src/lib/components/variables/environmentVariables.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -2,27 +2,17 @@
import { Empty, Paginator } from '$lib/components';
import { Button } from '$lib/elements/forms';
import {
ActionMenu,
Accordion,
Badge,
InteractiveText,
Icon,
Layout,
Popover,
Skeleton,
Table,
Tooltip,
Button as PinkButton
Tooltip
} from '@appwrite.io/pink-svelte';
import {
IconDotsHorizontal,
IconCode,
IconUpload,
IconPlus,
IconTrash,
IconEyeOff,
IconPencil
} from '@appwrite.io/pink-icons-svelte';
import { IconCode, IconUpload, IconPlus } from '@appwrite.io/pink-icons-svelte';
import VariableActionMenu from './variableActionMenu.svelte';
import type { Models } from '@appwrite.io/console';
import VariableEditorModal from './variableEditorModal.svelte';
import SecretVariableModal from './secretVariableModal.svelte';
Expand Down Expand Up @@ -66,14 +56,14 @@
const tableColumns = $derived(
$isSmallViewport
? [
{ id: 'key', width: { min: 420 } },
{ id: 'value', width: { min: 240 } },
{ id: 'key', width: { min: 120, max: 300 } },
{ id: 'value', width: { min: 100, max: 200 } },
{ id: 'actions', width: 40 }
]
: [
{ id: 'key', width: { min: 300 } },
{ id: 'value', width: { min: 280 } },
{ id: 'actions', width: 40 }
{ id: 'key', width: { min: 280, max: 420 } },
{ id: 'value', width: { min: 200, max: 400 } },
{ id: 'actions', width: 50 }
]
);
</script>
Expand Down Expand Up @@ -111,13 +101,15 @@
<Button
secondary
size="s"
icon={$isSmallViewport}
on:click={() => {
showCreate = true;
trackEvent(Click.VariablesCreateClick, {
source: createSource
});
}}>
<Icon slot="start" icon={IconPlus} /> Create variable
<Icon slot="start" icon={IconPlus} />
{#if !$isSmallViewport}Create variable{/if}
</Button>
{/if}
</Layout.Stack>
Expand Down Expand Up @@ -179,59 +171,20 @@
</Table.Cell>
<Table.Cell column="actions" {root}>
<div style="margin-inline-start: auto">
<Popover
padding="none"
placement="bottom-end"
let:toggle>
<PinkButton.Button
icon
variant="text"
size="s"
aria-label="More options"
onclick={(e) => {
e.preventDefault();
toggle(e);
}}>
<Icon icon={IconDotsHorizontal} size="s" />
</PinkButton.Button>

<svelte:fragment slot="tooltip" let:toggle>
<ActionMenu.Root>
{#if !variable?.secret}
<ActionMenu.Item.Button
leadingIcon={IconPencil}
onclick={(e) => {
toggle(e);
currentVariable = variable;
showUpdate = true;
}}>
Update
</ActionMenu.Item.Button>
{/if}
{#if !variable?.secret}
<ActionMenu.Item.Button
leadingIcon={IconEyeOff}
onclick={(e) => {
toggle(e);
currentVariable = variable;
showSecretModal = true;
}}>
Secret
</ActionMenu.Item.Button>
{/if}
<ActionMenu.Item.Button
status="danger"
leadingIcon={IconTrash}
onclick={(e) => {
toggle(e);
currentVariable = variable;
showDelete = true;
}}>
Delete
</ActionMenu.Item.Button>
</ActionMenu.Root>
</svelte:fragment>
</Popover>
<VariableActionMenu
{variable}
onUpdate={() => {
currentVariable = variable;
showUpdate = true;
}}
onSecret={() => {
currentVariable = variable;
showSecretModal = true;
}}
onDelete={() => {
currentVariable = variable;
showDelete = true;
}} />
</div>
</Table.Cell>
</Table.Row.Base>
Expand Down
8 changes: 4 additions & 4 deletions src/lib/components/variables/updateVariableModal.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -23,10 +23,10 @@
function handleVariable() {
if (selectedVar) {
variables = variables.map((variable) => {
if (variable.$id === selectedVar.$id) {
return pair;
}
return variable;
const match = selectedVar.$id
? variable.$id === selectedVar.$id
: variable.key === selectedVar.key;
return match ? pair : variable;
});
} else {
variables = [...variables, pair];
Expand Down
131 changes: 131 additions & 0 deletions src/lib/components/variables/variableActionMenu.svelte
Original file line number Diff line number Diff line change
@@ -0,0 +1,131 @@
<script lang="ts">
import { computePosition, flip, offset, shift, autoUpdate } from '@floating-ui/dom';
import { Icon, ActionMenu } from '@appwrite.io/pink-svelte';
import {
IconDotsHorizontal,
IconPencil,
IconEyeOff,
IconTrash
} from '@appwrite.io/pink-icons-svelte';
import { Button as PinkButton } from '@appwrite.io/pink-svelte';
import type { Models } from '@appwrite.io/console';

let {
variable,
onUpdate,
onSecret,
onDelete
}: {
variable: Partial<Models.Variable>;
onUpdate: () => void;
onSecret: () => void;
onDelete: () => void;
} = $props();

let open = $state(false);
let triggerEl = $state<HTMLElement | null>(null);
let menuEl = $state<HTMLElement | null>(null);
let cleanup: (() => void) | null = null;

function hide() {
open = false;
}

function toggle() {
open = !open;
}

function portalToBody(node: HTMLElement) {
document.body.appendChild(node);
return {
destroy() {
node.parentNode?.removeChild(node);
}
};
}

$effect(() => {
if (open && triggerEl && menuEl) {
cleanup = autoUpdate(triggerEl, menuEl, () => {
computePosition(triggerEl, menuEl, {
placement: 'bottom-end',
middleware: [offset(2), flip(), shift()]
}).then(({ x, y }) => {
if (menuEl) {
Object.assign(menuEl.style, { left: `${x}px`, top: `${y}px` });
}
});
});
} else {
cleanup?.();
cleanup = null;
}
});

function handleWindowClick(e: MouseEvent) {
if (!open) return;
const target = e.target as Node;
if (triggerEl?.contains(target) || menuEl?.contains(target)) return;
hide();
}

function handleKeydown(e: KeyboardEvent) {
if (e.key === 'Escape') hide();
}
</script>

<svelte:window onclick={handleWindowClick} onkeydown={handleKeydown} />

<span bind:this={triggerEl}>
<PinkButton.Button
icon
variant="text"
size="s"
aria-label="More options"
onclick={(e) => {
e.preventDefault();
toggle();
}}>
Comment thread
HarshMN2345 marked this conversation as resolved.
<Icon icon={IconDotsHorizontal} size="s" />
</PinkButton.Button>
</span>

{#if open}
<div
use:portalToBody
bind:this={menuEl}
style="position: fixed; z-index: 9001; background: var(--bgcolor-neutral-primary); border: var(--border-width-s) solid var(--border-neutral); border-radius: var(--border-radius-m); box-shadow: 0 1px 3px 0 rgba(0,0,0,0.03), 0 4px 4px 0 rgba(0,0,0,0.04); overflow: hidden;"
role="menu">
<ActionMenu.Root>
{#if !variable?.secret}
<ActionMenu.Item.Button
leadingIcon={IconPencil}
on:click={() => {
hide();
onUpdate();
}}>
Update
</ActionMenu.Item.Button>
{/if}
{#if !variable?.secret}
<ActionMenu.Item.Button
leadingIcon={IconEyeOff}
on:click={() => {
hide();
onSecret();
}}>
Secret
</ActionMenu.Item.Button>
{/if}
<ActionMenu.Item.Button
status="danger"
leadingIcon={IconTrash}
on:click={() => {
hide();
onDelete();
}}>
Delete
</ActionMenu.Item.Button>
</ActionMenu.Root>
</div>
{/if}