Skip to content

Fix checkbox state on table #270

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 7 commits into
base: next
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
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
1 change: 1 addition & 0 deletions v2/pink-sb/src/lib/selector/Checkbox.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@
{...$root}
{disabled}
use:root
on:click
class:active={$isIndeterminate || $isChecked}
class:s={size === 's'}
>
Expand Down
41 changes: 41 additions & 0 deletions v2/pink-sb/src/lib/table/Row.svelte
Original file line number Diff line number Diff line change
@@ -1,8 +1,49 @@
<script lang="ts">
import { getContext, onMount } from 'svelte';
import Cell from '$lib/table/header/Cell.svelte';
import Checkbox from '$lib/selector/Checkbox.svelte';
import { TABLE_CONTEXT, type TableContext } from './Table.svelte';

export let type: 'row' | 'header' = 'row';
export let id: string | undefined = undefined;

const tableCtx = getContext<TableContext>(TABLE_CONTEXT);
let allSelected = tableCtx.allSelected;
let someSelected = tableCtx.someSelected;

function checkboxClick(e: MouseEvent) {
e.stopPropagation();
type === 'row' && id ? tableCtx.toggleRow(id) : tableCtx.toggleAll();
}

$: isChecked = () => {
return type === 'header'
? $allSelected
? true
: $someSelected
? 'indeterminate'
: false
: id
? tableCtx.isSelected(id)
: false;
};

onMount(async () => {
if (tableCtx.selection && type === 'row' && !id) {
console.error(
"Selection mode in `Table` requires each `Row` to have a unique 'id' to avoid inconsistent states."
);
}
});
</script>

<div role={type === 'row' ? 'row' : 'rowheader'}>
{#if tableCtx.selection}
<Cell width="20px">
<Checkbox size="s" on:click={checkboxClick} checked={isChecked()} />
</Cell>
{/if}

<slot />
</div>

Expand Down
63 changes: 62 additions & 1 deletion v2/pink-sb/src/lib/table/Table.svelte
Original file line number Diff line number Diff line change
@@ -1,5 +1,66 @@
<script>
<script context="module" lang="ts">
import { type Readable } from 'svelte/store';

export const TABLE_CONTEXT = Symbol('table');

export interface TableContext {
selection: boolean;
selectedIds: Readable<string[]>;
allSelected: Readable<boolean>;
someSelected: Readable<boolean>;
isSelected: (id: string) => boolean;
toggleRow: (id: string) => void;
toggleAll: () => void;
}
</script>

<script lang="ts">
import Row from './Row.svelte';
import { setContext } from 'svelte';
import { writable, derived } from 'svelte/store';

export let selection = false;
export let selectedIds: string[] = [];
export let selectableIds: string[] = [];

const selectedIdsStore = writable(selectedIds);

const allSelected = derived(
selectedIdsStore,
($ids) => selectableIds.length > 0 && selectableIds.every((id) => $ids.includes(id))
);

const someSelected = derived(
[selectedIdsStore, allSelected],
([$ids, $all]) => !$all && selectableIds.some((id) => $ids.includes(id))
);

function isSelected(id: string): boolean {
return selectedIds.includes(id);
}

function toggleRow(id: string) {
selectedIds = isSelected(id)
? selectedIds.filter((sel) => sel !== id)
: [...selectedIds, id];

selectedIdsStore.set(selectedIds);
}

function toggleAll() {
selectedIds = $allSelected ? [] : [...selectableIds];
selectedIdsStore.set(selectedIds);
}

setContext<TableContext>(TABLE_CONTEXT, {
selection,
selectedIds: selectedIdsStore,
allSelected,
someSelected,
isSelected,
toggleRow,
toggleAll
});
</script>

<div class="root">
Expand Down
73 changes: 39 additions & 34 deletions v2/pink-sb/src/stories/Table.stories.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,14 @@
import { Selector, Button, Badge, Tag, Icon, Status, Typography } from '$lib/index.js';
import { Story } from '@storybook/addon-svelte-csf';
import { IconDuplicate } from '@appwrite.io/pink-icons-svelte';

$: selectedIds = [];
let tableItems = [
{ id: 1, name: 'Arman Nik', role: 'Product Engineer', location: 'Italy' },
{ id: 2, name: 'Darshan Pandya', role: 'Product Engineer', location: 'India' },
{ id: 3, name: 'Ernst Mulders', role: 'Product Engineer', location: 'Netherlands' },
{ id: 4, name: 'Torsten Dittmann', role: 'Product Architect', location: 'Germany' }
];
</script>

<Story name="Default">
Expand Down Expand Up @@ -147,40 +155,37 @@
</Story>

<Story name="Checkboxes">
<Table.Root>
<svelte:fragment slot="header">
<Table.Cell width="20px">
<Selector.Checkbox size="s" />
</Table.Cell>
<Table.Cell>Lorem</Table.Cell>
<Table.Cell>Ipsum</Table.Cell>
<Table.Cell>Dolor</Table.Cell>
</svelte:fragment>
<Table.Row>
<Table.Cell>
<Selector.Checkbox size="s" />
</Table.Cell>
<Table.Cell>Lorem</Table.Cell>
<Table.Cell>Ipsum</Table.Cell>
<Table.Cell>Dolor</Table.Cell>
</Table.Row>
<Table.Row>
<Table.Cell>
<Selector.Checkbox size="s" />
</Table.Cell>
<Table.Cell>Lorem</Table.Cell>
<Table.Cell>Ipsum</Table.Cell>
<Table.Cell>Dolor</Table.Cell>
</Table.Row>
<Table.Row>
<Table.Cell>
<Selector.Checkbox size="s" />
</Table.Cell>
<Table.Cell>Lorem</Table.Cell>
<Table.Cell>Ipsum</Table.Cell>
<Table.Cell>Dolor</Table.Cell>
</Table.Row>
</Table.Root>
<Layout.Stack direction="column" gap="l">
{@const selectableIds = tableItems.map((item) => item.id.toString())}
<Table.Root selection {selectableIds} bind:selectedIds>
<svelte:fragment slot="header">
<Table.Cell>Name</Table.Cell>
<Table.Cell>Role</Table.Cell>
<Table.Cell>Location</Table.Cell>
</svelte:fragment>

{#each tableItems as tableItem}
<!-- passing `id` is impt. here -->
<Table.Row id={tableItem.id.toString()}>
<Table.Cell>{tableItem.name}</Table.Cell>
<Table.Cell>{tableItem.role}</Table.Cell>
<Table.Cell>{tableItem.location}</Table.Cell>
</Table.Row>
{/each}
</Table.Root>

<Typography.Caption variant="400">Selected IDs: {selectedIds.join(', ')}</Typography.Caption
>

<Typography.Caption variant="400"
>Selected Persons: {selectedIds
.map(
(selectedId) =>
tableItems.find((tableItem) => tableItem.id.toString() === selectedId)?.name
)
.join(', ')}</Typography.Caption
>
</Layout.Stack>
</Story>

<Story name="Overflow">
Expand Down
Loading