feat: responsiveContainerHeader (WIP), refactor view selector and other stuff

This commit is contained in:
Arman
2025-04-16 17:53:21 +02:00
parent 31a9b0ed30
commit cd61ad4e68
11 changed files with 401 additions and 110 deletions
+1 -1
View File
@@ -25,7 +25,7 @@
"@appwrite.io/pink-icons": "0.25.0",
"@appwrite.io/pink-icons-svelte": "https://pkg.pr.new/appwrite/pink/@appwrite.io/pink-icons-svelte@6b17cb23",
"@appwrite.io/pink-legacy": "^1.0.3",
"@appwrite.io/pink-svelte": "https://pkg.pr.new/appwrite/pink/@appwrite.io/pink-svelte@6b17cb23",
"@appwrite.io/pink-svelte": "https://pkg.pr.new/appwrite/pink/@appwrite.io/pink-svelte@29469d7",
"@popperjs/core": "^2.11.8",
"@sentry/sveltekit": "^8.38.0",
"@stripe/stripe-js": "^3.5.0",
+12 -12
View File
@@ -21,8 +21,8 @@ importers:
specifier: ^1.0.3
version: 1.0.3
'@appwrite.io/pink-svelte':
specifier: https://pkg.pr.new/appwrite/pink/@appwrite.io/pink-svelte@6b17cb23
version: https://pkg.pr.new/appwrite/pink/@appwrite.io/pink-svelte@6b17cb23(react-dom@18.3.1(react@18.3.1))(svelte@5.25.3)
specifier: https://pkg.pr.new/appwrite/pink/@appwrite.io/pink-svelte@29469d7
version: https://pkg.pr.new/appwrite/pink/@appwrite.io/pink-svelte@29469d7(react-dom@18.3.1(react@18.3.1))(svelte@5.25.3)
'@popperjs/core':
specifier: ^2.11.8
version: 2.11.8
@@ -221,14 +221,14 @@ packages:
resolution: {tarball: https://pkg.pr.new/appwrite/appwrite/@appwrite.io/console@e2f082e}
version: 1.2.1
'@appwrite.io/pink-icons-svelte@https://pkg.pr.new/appwrite/pink/@appwrite.io/pink-icons-svelte@6b17cb23':
resolution: {tarball: https://pkg.pr.new/appwrite/pink/@appwrite.io/pink-icons-svelte@6b17cb23}
'@appwrite.io/pink-icons-svelte@https://pkg.pr.new/appwrite/pink/@appwrite.io/pink-icons-svelte@29469d70d89184306f1766f80ec4af4fab5c4939':
resolution: {tarball: https://pkg.pr.new/appwrite/pink/@appwrite.io/pink-icons-svelte@29469d70d89184306f1766f80ec4af4fab5c4939}
version: 1.0.0-next.7
peerDependencies:
svelte: ^4.0.0
'@appwrite.io/pink-icons-svelte@https://pkg.pr.new/appwrite/pink/@appwrite.io/pink-icons-svelte@6b17cb23ba11fbe98d56b96058a79ecc48b23120':
resolution: {tarball: https://pkg.pr.new/appwrite/pink/@appwrite.io/pink-icons-svelte@6b17cb23ba11fbe98d56b96058a79ecc48b23120}
'@appwrite.io/pink-icons-svelte@https://pkg.pr.new/appwrite/pink/@appwrite.io/pink-icons-svelte@6b17cb23':
resolution: {tarball: https://pkg.pr.new/appwrite/pink/@appwrite.io/pink-icons-svelte@6b17cb23}
version: 1.0.0-next.7
peerDependencies:
svelte: ^4.0.0
@@ -242,8 +242,8 @@ packages:
'@appwrite.io/pink-legacy@1.0.3':
resolution: {integrity: sha512-GGde5fmPhs+s6/3aFeMPc/kKADG/gTFkYQSy6oBN8pK0y0XNCLrZZgBv+EBbdhwdtqVEWXa0X85Mv9w7jcIlwQ==}
'@appwrite.io/pink-svelte@https://pkg.pr.new/appwrite/pink/@appwrite.io/pink-svelte@6b17cb23':
resolution: {tarball: https://pkg.pr.new/appwrite/pink/@appwrite.io/pink-svelte@6b17cb23}
'@appwrite.io/pink-svelte@https://pkg.pr.new/appwrite/pink/@appwrite.io/pink-svelte@29469d7':
resolution: {tarball: https://pkg.pr.new/appwrite/pink/@appwrite.io/pink-svelte@29469d7}
version: 1.0.0-next.85
peerDependencies:
react-dom: ^18.0.0
@@ -3536,11 +3536,11 @@ snapshots:
'@appwrite.io/console@https://pkg.pr.new/appwrite/appwrite/@appwrite.io/console@e2f082e': {}
'@appwrite.io/pink-icons-svelte@https://pkg.pr.new/appwrite/pink/@appwrite.io/pink-icons-svelte@6b17cb23(svelte@5.25.3)':
'@appwrite.io/pink-icons-svelte@https://pkg.pr.new/appwrite/pink/@appwrite.io/pink-icons-svelte@29469d70d89184306f1766f80ec4af4fab5c4939(svelte@5.25.3)':
dependencies:
svelte: 5.25.3
'@appwrite.io/pink-icons-svelte@https://pkg.pr.new/appwrite/pink/@appwrite.io/pink-icons-svelte@6b17cb23ba11fbe98d56b96058a79ecc48b23120(svelte@5.25.3)':
'@appwrite.io/pink-icons-svelte@https://pkg.pr.new/appwrite/pink/@appwrite.io/pink-icons-svelte@6b17cb23(svelte@5.25.3)':
dependencies:
svelte: 5.25.3
@@ -3553,9 +3553,9 @@ snapshots:
'@appwrite.io/pink-icons': 1.0.0
the-new-css-reset: 1.11.3
'@appwrite.io/pink-svelte@https://pkg.pr.new/appwrite/pink/@appwrite.io/pink-svelte@6b17cb23(react-dom@18.3.1(react@18.3.1))(svelte@5.25.3)':
'@appwrite.io/pink-svelte@https://pkg.pr.new/appwrite/pink/@appwrite.io/pink-svelte@29469d7(react-dom@18.3.1(react@18.3.1))(svelte@5.25.3)':
dependencies:
'@appwrite.io/pink-icons-svelte': https://pkg.pr.new/appwrite/pink/@appwrite.io/pink-icons-svelte@6b17cb23ba11fbe98d56b96058a79ecc48b23120(svelte@5.25.3)
'@appwrite.io/pink-icons-svelte': https://pkg.pr.new/appwrite/pink/@appwrite.io/pink-icons-svelte@29469d70d89184306f1766f80ec4af4fab5c4939(svelte@5.25.3)
'@floating-ui/dom': 1.6.13
'@melt-ui/pp': 0.3.2(@melt-ui/svelte@0.86.6(svelte@5.25.3))(svelte@5.25.3)
'@melt-ui/svelte': 0.86.6(svelte@5.25.3)
+92
View File
@@ -0,0 +1,92 @@
<script lang="ts">
import { InputCheckbox } from '$lib/elements/forms';
import { page } from '$app/state';
import type { Writable } from 'svelte/store';
import { preferences } from '$lib/stores/preferences';
import { onMount, type Snippet } from 'svelte';
import { View } from '$lib/helpers/load';
import type { Column } from '$lib/helpers/types';
import { ActionMenu, Icon, Layout, Popover, Button, Selector } from '@appwrite.io/pink-svelte';
import { IconViewBoards } from '@appwrite.io/pink-icons-svelte';
let {
columns,
isCustomCollection = false,
allowNoColumns = false,
children
}: {
columns: Writable<Column[]>;
isCustomCollection?: boolean;
allowNoColumns?: boolean;
children: Snippet<[toggle: () => void, selectedColumnsNumber: number]>;
} = $props();
onMount(async () => {
if (isCustomCollection) {
const prefs = preferences.getCustomCollectionColumns(page.params.collection);
columns.set(
$columns.map((column) => {
column.hide = prefs?.includes(column.id) ?? false;
return column;
})
);
} else {
const prefs = preferences.get(page.route);
// Override the shown columns only if a preference was set
if (prefs?.columns) {
columns.set(
$columns.map((column) => {
column.hide = prefs.columns?.includes(column.id) ?? false;
return column;
})
);
}
}
columns.subscribe((ctx) => {
const columns = ctx.filter((n) => n.hide === true).map((n) => n.id);
if (isCustomCollection) {
preferences.setCustomCollectionColumns(columns);
} else {
preferences.setColumns(columns);
}
});
});
let selectedColumnsNumber = $derived(
$columns.reduce((acc, column) => {
if (column.hide === true) return acc;
return ++acc;
}, 0)
);
</script>
{#if $columns?.length}
<Popover let:toggle placement="bottom-end" padding="none">
{@render children(toggle, selectedColumnsNumber)}
<svelte:fragment slot="tooltip">
<ActionMenu.Root>
{#each $columns as column}
{#if !column?.exclude}
<ActionMenu.Item.Button
on:click={() => (column.hide = !column.hide)}
disabled={allowNoColumns
? false
: selectedColumnsNumber <= 1 && column.hide !== true}>
<Layout.Stack direction="row" gap="s">
<Selector.Checkbox
checked={!column.hide}
size="s"
on:click={() => (column.hide = !column.hide)} />
{column.title}
</Layout.Stack>
</ActionMenu.Item.Button>
{/if}
{/each}
</ActionMenu.Root>
</svelte:fragment>
</Popover>
{/if}
+1
View File
@@ -81,3 +81,4 @@ export { default as Sidebar } from './sidebar.svelte';
export { default as BottomSheet } from './bottom-sheet/index';
export { default as Confirm } from './confirm.svelte';
export { default as UsageCard } from './usageCard.svelte';
export { default as ViewToggle } from './viewToggle.svelte';
+7 -95
View File
@@ -16,6 +16,8 @@
} from '@appwrite.io/pink-svelte';
import { IconViewBoards, IconViewGrid, IconViewList } from '@appwrite.io/pink-icons-svelte';
import { goto } from '$app/navigation';
import ViewToggle from './viewToggle.svelte';
import ColumnSelector from './columnSelector.svelte';
export let columns: Writable<Column[]>;
export let view: View;
@@ -23,68 +25,11 @@
export let hideView = false;
export let hideColumns = false;
export let allowNoColumns = false;
onMount(async () => {
if (isCustomCollection) {
const prefs = preferences.getCustomCollectionColumns(page.params.collection);
columns.set(
$columns.map((column) => {
column.hide = prefs?.includes(column.id) ?? false;
return column;
})
);
} else {
const prefs = preferences.get(page.route);
// Override the shown columns only if a preference was set
if (prefs?.columns) {
columns.set(
$columns.map((column) => {
column.hide = prefs.columns?.includes(column.id) ?? false;
return column;
})
);
}
}
columns.subscribe((ctx) => {
const columns = ctx.filter((n) => n.hide === true).map((n) => n.id);
if (isCustomCollection) {
preferences.setCustomCollectionColumns(columns);
} else {
preferences.setColumns(columns);
}
});
});
function getViewLink(view: View): string {
const url = new URL(page.url);
url.searchParams.set('view', view);
return url.toString();
}
function updateViewPreferences(view: View) {
preferences.setView(view);
}
function onViewChange(event: CustomEvent<View>) {
updateViewPreferences(event.detail);
goto(getViewLink(event.detail), {
replaceState: true
});
}
$: selectedColumnsNumber = $columns.reduce((acc, column) => {
if (column.hide === true) return acc;
return ++acc;
}, 0);
</script>
{#if !hideColumns && view === View.Table}
{#if $columns?.length}
<Popover let:toggle placement="bottom-end">
<ColumnSelector {columns} {isCustomCollection} {allowNoColumns}>
{#snippet children(toggle, selectedColumnsNumber)}
<Button.Button
size="s"
variant="secondary"
@@ -92,43 +37,10 @@
on:click={toggle}>
<Icon slot="start" icon={IconViewBoards} />
</Button.Button>
<svelte:fragment slot="tooltip">
<ActionMenu.Root>
<Layout.Stack>
{#each $columns as column}
{#if !column?.exclude}
<InputCheckbox
id={column.id}
label={column.title}
on:change={() => (column.hide = !column.hide)}
checked={!column.hide}
disabled={allowNoColumns
? false
: selectedColumnsNumber <= 1 && column.hide !== true} />
{/if}
{/each}
</Layout.Stack>
</ActionMenu.Root>
</svelte:fragment>
</Popover>
{/if}
{/snippet}
</ColumnSelector>
{/if}
{#if !hideView}
<ToggleButton
--bgcolor-neutral-default="var(--bgcolor-neutral-primary)"
on:change={onViewChange}
active={view}
buttons={[
{
id: 'table',
label: 'table view',
icon: IconViewList
},
{
id: 'grid',
label: 'grid view',
icon: IconViewGrid
}
]} />
<ViewToggle bind:view />
{/if}
+48
View File
@@ -0,0 +1,48 @@
<script lang="ts">
import { goto } from '$app/navigation';
import { page } from '$app/state';
import type { View } from '$lib/helpers/load';
import { preferences } from '$lib/stores/preferences';
import { IconViewGrid, IconViewList } from '@appwrite.io/pink-icons-svelte';
import { ToggleButton } from '@appwrite.io/pink-svelte';
let {
view = $bindable()
}: {
view: View;
} = $props();
function onViewChange(event: CustomEvent<View>) {
updateViewPreferences(event.detail);
goto(getViewLink(event.detail), {
replaceState: true
});
}
function getViewLink(view: View): string {
const url = new URL(page.url);
url.searchParams.set('view', view);
return url.toString();
}
function updateViewPreferences(view: View) {
preferences.setView(view);
}
</script>
<ToggleButton
--bgcolor-neutral-default="var(--bgcolor-neutral-primary)"
on:change={onViewChange}
active={view}
buttons={[
{
id: 'table',
label: 'table view',
icon: IconViewList
},
{
id: 'grid',
label: 'grid view',
icon: IconViewGrid
}
]} />
@@ -0,0 +1,56 @@
<script lang="ts">
import { page } from '$app/state';
import { Confirm, ViewToggle } from '$lib/components';
import ColumnSelector from '$lib/components/columnSelector.svelte';
import type { View } from '$lib/helpers/load';
import type { Column } from '$lib/helpers/types';
import { preferences } from '$lib/stores/preferences';
import { IconViewBoards } from '@appwrite.io/pink-icons-svelte';
import { Button, Icon, Layout, Typography } from '@appwrite.io/pink-svelte';
import { onMount } from 'svelte';
import type { Writable } from 'svelte/store';
let {
show = $bindable(false),
hideView = false,
view = $bindable(),
columns,
hideColumns = false,
isCustomCollection = false
}: {
show?: boolean;
hideView?: boolean;
view?: View;
columns?: Writable<Column[]>;
hideColumns?: boolean;
isCustomCollection?: boolean;
} = $props();
</script>
<Confirm title="Adjustments" bind:open={show} canDelete={false}>
<Layout.Stack>
{#if !hideView}
<Layout.Stack gap="xs">
<Typography.Text>Layout</Typography.Text>
<ViewToggle bind:view />
</Layout.Stack>
{/if}
{#if !hideColumns && $columns?.length}
<Layout.Stack gap="xs">
<Typography.Text>Columns</Typography.Text>
<ColumnSelector {columns} {isCustomCollection}>
{#snippet children(toggle, selectedColumnsNumber)}
<Button.Button
size="s"
variant="secondary"
badge={selectedColumnsNumber.toString()}
on:click={toggle}>
Columns
<Icon slot="start" icon={IconViewBoards} />
</Button.Button>
{/snippet}
</ColumnSelector>
</Layout.Stack>
{/if}
</Layout.Stack>
</Confirm>
+1
View File
@@ -21,3 +21,4 @@ export { default as WizardSecondaryContainer } from './wizardSecondaryContainer.
export { default as WizardSecondaryContent } from './wizardSecondaryContent.svelte';
export { default as WizardSecondaryFooter } from './wizardSecondaryFooter.svelte';
export { default as Wizard } from './wizard.svelte';
export { default as ResponsiveContainerHeader } from './responsiveContainerHeader.svelte';
@@ -0,0 +1,136 @@
<script lang="ts">
import { SearchQuery, ViewSelector } from '$lib/components';
import { ParsedTagList } from '$lib/components/filters';
import QuickFilters from '$lib/components/filters/quickFilters.svelte';
import Button from '$lib/elements/forms/button.svelte';
import { View } from '$lib/helpers/load';
import type { Column } from '$lib/helpers/types';
import { isSmallViewport } from '$lib/stores/viewport';
import { IconAdjustments, IconFilterLine, IconSearch } from '@appwrite.io/pink-icons-svelte';
import { Icon, Layout } from '@appwrite.io/pink-svelte';
import type { Snippet } from 'svelte';
import type { Writable } from 'svelte/store';
import DisplaySettingsModal from './displaySettingsModal.svelte';
let {
columns,
view = View.Table,
hideView = false,
hideColumns = false,
hasSearch = false,
searchPlaceholder = 'Search by ID',
hasFilters = false,
analyticsSource = '',
children
}: {
columns?: Writable<Column[]>;
view?: View;
hideView?: boolean;
hideColumns?: boolean;
hasSearch?: boolean;
searchPlaceholder?: string;
hasFilters?: boolean;
analyticsSource?: string;
children?: Snippet;
} = $props();
let hasDisplaySettings = $derived(!hideView || (!hideColumns && $columns?.length));
let numberOfOptions = $derived(
[hasSearch, hasFilters && $columns?.length, hasDisplaySettings].filter(Boolean).length
);
let showSearch = $state(false);
let showDisplaySettingsModal = $state(false);
let showFilters = $state(false);
</script>
<header>
<Layout.Stack>
{#if $isSmallViewport}
<Layout.Stack gap="xl">
<Layout.Stack direction="row">
<div style={`--button-width: 100%; width: 100%`}>
{@render children()}
</div>
{#if numberOfOptions === 1}
{#if hasSearch}
{@render searchButton(true)}
{:else if hasFilters && $columns?.length}
{@render filtersButton(true)}
{:else if hasDisplaySettings}
{@render settingsButton(true)}
{/if}
{/if}
</Layout.Stack>
{#if numberOfOptions > 1}
<Layout.Stack
direction="row"
gap="s"
style={`--button-width: calc(${100 / numberOfOptions}% - var(--gap-s) / 2)`}>
{#if hasSearch}
{@render searchButton()}
{/if}
{#if hasFilters && $columns?.length}
{@render filtersButton()}
{/if}
{#if hasDisplaySettings}
{@render settingsButton()}
{/if}
</Layout.Stack>
{/if}
{#if showSearch && hasSearch}
<SearchQuery placeholder={searchPlaceholder} />
{/if}
</Layout.Stack>
{:else}
<Layout.Stack direction="row" justifyContent="space-between">
<Layout.Stack direction="row" alignItems="center">
{#if hasSearch}
<SearchQuery placeholder={searchPlaceholder} />
{/if}
{#if hasFilters && $columns?.length}
<QuickFilters {columns} {analyticsSource} />
{/if}
</Layout.Stack>
<Layout.Stack direction="row" alignItems="center" justifyContent="flex-end">
{#if hasDisplaySettings}
<ViewSelector {view} {columns} {hideView} {hideColumns} />
{/if}
{@render children()}
</Layout.Stack>
</Layout.Stack>
{/if}
<ParsedTagList />
</Layout.Stack>
</header>
{#snippet searchButton(icon = false)}
<Button ariaLabel="Search" on:click={() => (showSearch = !showSearch)} secondary {icon}>
<Icon icon={IconSearch} />
</Button>
{/snippet}
{#snippet settingsButton(icon = false)}
<Button
ariaLabel="Display settings"
on:click={() => (showDisplaySettingsModal = !showDisplaySettingsModal)}
secondary
{icon}>
<Icon icon={IconAdjustments} />
</Button>
{/snippet}
{#snippet filtersButton(icon = false)}
<Button ariaLabel="Filters" on:click={() => (showFilters = !showFilters)} secondary {icon}>
<Icon icon={IconFilterLine} />
</Button>
{/snippet}
{#if showDisplaySettingsModal}
<DisplaySettingsModal
bind:show={showDisplaySettingsModal}
{columns}
{hideColumns}
{hideView}
bind:view />
{/if}
@@ -92,7 +92,7 @@
this={option.component}
editing
bind:data={selectedAttribute}
on:close={() => (option = null)} />
onclose={() => (option = null)} />
{/if}
{/if}
@@ -1,7 +1,7 @@
<script lang="ts">
import { PaginationWithLimit, ViewSelector, EmptyFilter, Empty } from '$lib/components';
import { Button } from '$lib/elements/forms';
import { Container } from '$lib/layout';
import { Container, ResponsiveContainerHeader } from '$lib/layout';
import { Adapter, BuildRuntime, Framework, type Models } from '@appwrite.io/console';
import { View } from '$lib/helpers/load';
import { ActionMenu, Icon, Layout, Popover } from '@appwrite.io/pink-svelte';
@@ -76,6 +76,51 @@
<Layout.Stack gap="xxxl">
<!-- TODO: re-enable metrics after backend changes -->
<!-- <DeploymentMetrics deploymentList={data.deploymentList} /> -->
<!-- TODO: re-enable once component is complete -->
<!-- <ResponsiveContainerHeader
view={View.Table}
{columns}
hasFilters
analyticsSource="site_deployments"
hideView>
<Popover padding="none" let:toggle>
<Button size="s" on:click={toggle}>
<Icon size="s" icon={IconPlus} />
Create deployment
</Button>
<svelte:fragment slot="tooltip" let:toggle>
<ActionMenu.Root>
<ActionMenu.Item.Button
badge="Recommended"
on:click={(e) => {
toggle(e);
if (!hasInstallation) {
showConnectRepo = true;
} else {
showCreateDeployment = true;
}
}}>
Git
</ActionMenu.Item.Button>
<ActionMenu.Item.Button
on:click={(e) => {
toggle(e);
showConnectCLI = true;
}}>
CLI
</ActionMenu.Item.Button>
<ActionMenu.Item.Button
on:click={(e) => {
toggle(e);
showConnectManual = true;
}}>
Manual
</ActionMenu.Item.Button>
</ActionMenu.Root>
</svelte:fragment>
</Popover>
</ResponsiveContainerHeader> -->
<Layout.Stack gap="l">
<Layout.Stack>
<Layout.Stack justifyContent="space-between" direction="row">