<SYSTEM>This is the llms.txt documentation for the "tables" directory of the Origin UI - Svelte project.</SYSTEM> # "tables" directory > A collection of production-ready, accessible UI components built with Svelte 5 and Tailwind CSS. These components are designed to be drop-in solutions for rapidly building modern web applications. This documentation covers 20 components, each following best practices for accessibility, performance, and type safety. ## Components ## table-01 > A type-safe, accessible table-01 component for building modern UIs. This component is part of the tables collection. ### Core Information - **Component ID**: `table-01` - **Location**: `/src/lib/components/tables/table-01.svelte` - **Type**: UI Component ### Usage ```svelte Full component implementation: ```svelte <script lang="ts"> import { Table, TableBody, TableCell, TableFooter, TableHead, TableHeader, TableRow } from '$lib/components/ui/table'; const items = [ { balance: '$1,250.00', email: 'alex.t@company.com', id: '1', location: 'San Francisco, US', name: 'Alex Thompson', status: 'Active' }, { balance: '$600.00', email: 'sarah.c@company.com', id: '2', location: 'Singapore', name: 'Sarah Chen', status: 'Active' }, { balance: '$650.00', email: 'j.wilson@company.com', id: '3', location: 'London, UK', name: 'James Wilson', status: 'Inactive' }, { balance: '$0.00', email: 'm.garcia@company.com', id: '4', location: 'Madrid, Spain', name: 'Maria Garcia', status: 'Active' }, { balance: '-$1,000.00', email: 'd.kim@company.com', id: '5', location: 'Seoul, KR', name: 'David Kim', status: 'Active' } ]; </script> <div> <Table> <TableHeader> <TableRow class="hover:bg-transparent"> <TableHead>Name</TableHead> <TableHead>Email</TableHead> <TableHead>Location</TableHead> <TableHead>Status</TableHead> <TableHead class="text-right">Balance</TableHead> </TableRow> </TableHeader> <TableBody> {#each items as item (item.id)} <TableRow> <TableCell class="font-medium">{item.name}</TableCell> <TableCell>{item.email}</TableCell> <TableCell>{item.location}</TableCell> <TableCell>{item.status}</TableCell> <TableCell class="text-right">{item.balance}</TableCell> </TableRow> {/each} </TableBody> <TableFooter class="bg-transparent"> <TableRow class="hover:bg-transparent"> <TableCell colspan={4}>Total</TableCell> <TableCell class="text-right">$2,500.00</TableCell> </TableRow> </TableFooter> </Table> <p class="mt-4 text-center text-sm text-muted-foreground">Basic table</p> </div> ``` ### Links - [View Source](https://github.com/max-got/originui-svelte/tree/main/src/lib/components/tables/table-01.svelte) ## table-02 > A type-safe, accessible table-02 component for building modern UIs. This component is part of the tables collection. ### Core Information - **Component ID**: `table-02` - **Location**: `/src/lib/components/tables/table-02.svelte` - **Type**: UI Component ### Usage ```svelte Full component implementation: ```svelte <script lang="ts"> import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from '$lib/components/ui/table'; const items = [ { balance: '$1,250.00', email: 'alex.t@company.com', id: '1', image: 'https://res.cloudinary.com/dlzlfasou/image/upload/v1736358071/avatar-40-02_upqrxi.jpg', location: 'San Francisco, US', name: 'Alex Thompson', status: 'Active', username: '@alexthompson' }, { balance: '$600.00', email: 'sarah.c@company.com', id: '2', image: 'https://res.cloudinary.com/dlzlfasou/image/upload/v1736358073/avatar-40-01_ij9v7j.jpg', location: 'Singapore', name: 'Sarah Chen', status: 'Active', username: '@sarahchen' }, { balance: '$0.00', email: 'm.garcia@company.com', id: '4', image: 'https://res.cloudinary.com/dlzlfasou/image/upload/v1736358072/avatar-40-03_dkeufx.jpg', location: 'Madrid, Spain', name: 'Maria Garcia', status: 'Active', username: '@mariagarcia' }, { balance: '-$1,000.00', email: 'd.kim@company.com', id: '5', image: 'https://res.cloudinary.com/dlzlfasou/image/upload/v1736358070/avatar-40-05_cmz0mg.jpg', location: 'Seoul, KR', name: 'David Kim', status: 'Active', username: '@davidkim' } ]; </script> <div> <Table> <TableHeader> <TableRow class="hover:bg-transparent"> <TableHead>Name</TableHead> <TableHead>Email</TableHead> <TableHead>Location</TableHead> <TableHead>Status</TableHead> <TableHead class="text-right">Balance</TableHead> </TableRow> </TableHeader> <TableBody> {#each items as item (item.id)} <TableRow> <TableCell> <div class="flex items-center gap-3"> <img class="rounded-full" src={item.image} width={40} height={40} alt={item.name} /> <div> <div class="font-medium">{item.name}</div> <span class="mt-0.5 text-xs text-muted-foreground"> {item.username} </span> </div> </div> </TableCell> <TableCell>{item.email}</TableCell> <TableCell>{item.location}</TableCell> <TableCell>{item.status}</TableCell> <TableCell class="text-right">{item.balance}</TableCell> </TableRow> {/each} </TableBody> </Table> <p class="mt-4 text-center text-sm text-muted-foreground">Table with images</p> </div> ``` ### Links - [View Source](https://github.com/max-got/originui-svelte/tree/main/src/lib/components/tables/table-02.svelte) ## table-03 > A type-safe, accessible table-03 component for building modern UIs. This component is part of the tables collection. ### Core Information - **Component ID**: `table-03` - **Location**: `/src/lib/components/tables/table-03.svelte` - **Type**: UI Component ### Usage ```svelte Full component implementation: ```svelte <script lang="ts"> import { Table, TableBody, TableCell, TableFooter, TableHead, TableHeader, TableRow } from '$lib/components/ui/table'; const items = [ { balance: '$1,250.00', email: 'alex.t@company.com', id: '1', location: 'San Francisco, US', name: 'Alex Thompson', status: 'Active' }, { balance: '$600.00', email: 'sarah.c@company.com', id: '2', location: 'Singapore', name: 'Sarah Chen', status: 'Active' }, { balance: '$650.00', email: 'j.wilson@company.com', id: '3', location: 'London, UK', name: 'James Wilson', status: 'Inactive' }, { balance: '$0.00', email: 'm.garcia@company.com', id: '4', location: 'Madrid, Spain', name: 'Maria Garcia', status: 'Active' }, { balance: '-$1,000.00', email: 'd.kim@company.com', id: '5', location: 'Seoul, KR', name: 'David Kim', status: 'Active' } ]; </script> <div> <Table> <TableHeader> <TableRow class="hover:bg-transparent"> <TableHead>Name</TableHead> <TableHead>Email</TableHead> <TableHead>Location</TableHead> <TableHead>Status</TableHead> <TableHead class="text-right">Balance</TableHead> </TableRow> </TableHeader> <tbody aria-hidden="true" class="table-row h-2"></tbody> <TableBody class="[&_td:first-child]:rounded-l-lg [&_td:last-child]:rounded-r-lg"> {#each items as item (item.id)} <TableRow class="border-none"> <TableCell class="py-2.5 font-medium">{item.name}</TableCell> <TableCell class="py-2.5">{item.email}</TableCell> <TableCell class="py-2.5">{item.location}</TableCell> <TableCell class="py-2.5">{item.status}</TableCell> <TableCell class="py-2.5 text-right"> {item.balance} </TableCell> </TableRow> {/each} </TableBody> <tbody aria-hidden="true" class="table-row h-2"></tbody> <TableFooter class="bg-transparent"> <TableRow class="hover:bg-transparent"> <TableCell colspan={4}>Total</TableCell> <TableCell class="text-right">$2,500.00</TableCell> </TableRow> </TableFooter> </Table> <p class="mt-4 text-center text-sm text-muted-foreground">Table without horizontal dividers</p> </div> ``` ### Links - [View Source](https://github.com/max-got/originui-svelte/tree/main/src/lib/components/tables/table-03.svelte) ## table-04 > A type-safe, accessible table-04 component for building modern UIs. This component is part of the tables collection. ### Core Information - **Component ID**: `table-04` - **Location**: `/src/lib/components/tables/table-04.svelte` - **Type**: UI Component ### Usage ```svelte Full component implementation: ```svelte <script lang="ts"> import { Table, TableBody, TableCell, TableFooter, TableHead, TableHeader, TableRow } from '$lib/components/ui/table'; const items = [ { balance: '$1,250.00', email: 'alex.t@company.com', id: '1', location: 'San Francisco, US', name: 'Alex Thompson', status: 'Active' }, { balance: '$600.00', email: 'sarah.c@company.com', id: '2', location: 'Singapore', name: 'Sarah Chen', status: 'Active' }, { balance: '$650.00', email: 'j.wilson@company.com', id: '3', location: 'London, UK', name: 'James Wilson', status: 'Inactive' }, { balance: '$0.00', email: 'm.garcia@company.com', id: '4', location: 'Madrid, Spain', name: 'Maria Garcia', status: 'Active' }, { balance: '-$1,000.00', email: 'd.kim@company.com', id: '5', location: 'Seoul, KR', name: 'David Kim', status: 'Active' } ]; </script> <div> <Table> <TableHeader class="bg-transparent"> <TableRow class="hover:bg-transparent"> <TableHead>Name</TableHead> <TableHead>Email</TableHead> <TableHead>Location</TableHead> <TableHead>Status</TableHead> <TableHead class="text-right">Balance</TableHead> </TableRow> </TableHeader> <tbody aria-hidden="true" class="table-row h-2"></tbody> <TableBody class="[&_td:first-child]:rounded-l-lg [&_td:last-child]:rounded-r-lg"> {#each items as item (item.id)} <TableRow class="border-none odd:bg-muted/50 hover:bg-transparent odd:hover:bg-muted/50"> <TableCell class="py-2.5 font-medium">{item.name}</TableCell> <TableCell class="py-2.5">{item.email}</TableCell> <TableCell class="py-2.5">{item.location}</TableCell> <TableCell class="py-2.5">{item.status}</TableCell> <TableCell class="py-2.5 text-right"> {item.balance} </TableCell> </TableRow> {/each} </TableBody> <tbody aria-hidden="true" class="table-row h-2"></tbody> <TableFooter class="bg-transparent"> <TableRow class="hover:bg-transparent"> <TableCell colspan={4}>Total</TableCell> <TableCell class="text-right">$2,500.00</TableCell> </TableRow> </TableFooter> </Table> <p class="mt-4 text-center text-sm text-muted-foreground">Striped table</p> </div> ``` ### Links - [View Source](https://github.com/max-got/originui-svelte/tree/main/src/lib/components/tables/table-04.svelte) ## table-05 > A type-safe, accessible table-05 component for building modern UIs. This component is part of the tables collection. ### Core Information - **Component ID**: `table-05` - **Location**: `/src/lib/components/tables/table-05.svelte` - **Type**: UI Component ### Usage ```svelte Full component implementation: ```svelte <script lang="ts"> import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from '$lib/components/ui/table'; const items = [ { balance: '$1,250.00', email: 'alex.t@company.com', id: '1', location: 'San Francisco, US', name: 'Alex Thompson', status: 'Active' }, { balance: '$600.00', email: 'sarah.c@company.com', id: '2', location: 'Singapore', name: 'Sarah Chen', status: 'Active' }, { balance: '$650.00', email: 'j.wilson@company.com', id: '3', location: 'London, UK', name: 'James Wilson', status: 'Inactive' }, { balance: '$0.00', email: 'm.garcia@company.com', id: '4', location: 'Madrid, Spain', name: 'Maria Garcia', status: 'Active' }, { balance: '-$1,000.00', email: 'd.kim@company.com', id: '5', location: 'Seoul, KR', name: 'David Kim', status: 'Active' } ]; </script> <div> <Table> <TableHeader class="bg-transparent"> <TableRow class="*:border-border hover:bg-transparent [&>:not(:last-child)]:border-r"> <TableHead>Name</TableHead> <TableHead>Email</TableHead> <TableHead>Location</TableHead> <TableHead>Status</TableHead> <TableHead class="text-right">Balance</TableHead> </TableRow> </TableHeader> <TableBody class="[&_td:first-child]:rounded-l-lg [&_td:last-child]:rounded-r-lg"> {#each items as item (item.id)} <TableRow class="*:border-border hover:bg-transparent [&>:not(:last-child)]:border-r"> <TableCell class="font-medium">{item.name}</TableCell> <TableCell>{item.email}</TableCell> <TableCell>{item.location}</TableCell> <TableCell>{item.status}</TableCell> <TableCell class="text-right">{item.balance}</TableCell> </TableRow> {/each} </TableBody> </Table> <p class="mt-4 text-center text-sm text-muted-foreground">Table with vertical lines</p> </div> ``` ### Links - [View Source](https://github.com/max-got/originui-svelte/tree/main/src/lib/components/tables/table-05.svelte) ## table-06 > A type-safe, accessible table-06 component for building modern UIs. This component is part of the tables collection. ### Core Information - **Component ID**: `table-06` - **Location**: `/src/lib/components/tables/table-06.svelte` - **Type**: UI Component ### Usage ```svelte Full component implementation: ```svelte <script lang="ts"> import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from '$lib/components/ui/table'; const programmingLanguages = [ { developer: 'Brendan Eich', extension: '.js', id: '1', latestVersion: 'ES2021', name: 'JavaScript', paradigm: 'Multi-paradigm', popularity: 'High', releaseYear: '1995', typing: 'Dynamic' }, { developer: 'Guido van Rossum', extension: '.py', id: '2', latestVersion: '3.10', name: 'Python', paradigm: 'Multi-paradigm', popularity: 'High', releaseYear: '1991', typing: 'Dynamic' }, { developer: 'James Gosling', extension: '.java', id: '3', latestVersion: '17', name: 'Java', paradigm: 'Object-oriented', popularity: 'High', releaseYear: '1995', typing: 'Static' }, { developer: 'Bjarne Stroustrup', extension: '.cpp', id: '4', latestVersion: 'C++20', name: 'C++', paradigm: 'Multi-paradigm', popularity: 'High', releaseYear: '1985', typing: 'Static' }, { developer: 'Yukihiro Matsumoto', extension: '.rb', id: '5', latestVersion: '3.0', name: 'Ruby', paradigm: 'Multi-paradigm', popularity: 'Low', releaseYear: '1995', typing: 'Dynamic' } ]; </script> <div> <div class="overflow-hidden rounded-md border bg-background"> <Table> <TableHeader> <TableRow class="bg-muted/50"> <TableHead class="h-9 py-2">Name</TableHead> <TableHead class="h-9 py-2">Release Year</TableHead> <TableHead class="h-9 py-2">Developer</TableHead> <TableHead class="h-9 py-2">Typing</TableHead> <TableHead class="h-9 py-2">Paradigm</TableHead> <TableHead class="h-9 py-2">Extension</TableHead> <TableHead class="h-9 py-2">Latest Version</TableHead> <TableHead class="h-9 py-2">Popularity</TableHead> </TableRow> </TableHeader> <TableBody> {#each programmingLanguages as language (language.id)} <TableRow> <TableCell class="py-2 font-medium"> {language.name} </TableCell> <TableCell class="py-2">{language.releaseYear}</TableCell> <TableCell class="py-2">{language.developer}</TableCell> <TableCell class="py-2">{language.typing}</TableCell> <TableCell class="py-2">{language.paradigm}</TableCell> <TableCell class="py-2">{language.extension}</TableCell> <TableCell class="py-2">{language.latestVersion}</TableCell> <TableCell class="py-2">{language.popularity}</TableCell> </TableRow> {/each} </TableBody> </Table> </div> <p class="mt-4 text-center text-sm text-muted-foreground">Dense table</p> </div> ``` ### Links - [View Source](https://github.com/max-got/originui-svelte/tree/main/src/lib/components/tables/table-06.svelte) ## table-07 > A type-safe, accessible table-07 component for building modern UIs. This component is part of the tables collection. ### Core Information - **Component ID**: `table-07` - **Location**: `/src/lib/components/tables/table-07.svelte` - **Type**: UI Component ### Usage ```svelte Full component implementation: ```svelte <script lang="ts"> import Checkbox from '$lib/components/ui/checkbox.svelte'; import { Table, TableBody, TableCell, TableFooter, TableHead, TableHeader, TableRow } from '$lib/components/ui/table'; const items = [ { balance: '$1,250.00', email: 'alex.t@company.com', id: '1', location: 'San Francisco, US', name: 'Alex Thompson', status: 'Active' }, { balance: '$600.00', email: 'sarah.c@company.com', id: '2', location: 'Singapore', name: 'Sarah Chen', status: 'Active' }, { balance: '$650.00', email: 'j.wilson@company.com', id: '3', location: 'London, UK', name: 'James Wilson', status: 'Inactive' }, { balance: '$0.00', email: 'm.garcia@company.com', id: '4', location: 'Madrid, Spain', name: 'Maria Garcia', status: 'Active' }, { balance: '-$1,000.00', email: 'd.kim@company.com', id: '5', location: 'Seoul, KR', name: 'David Kim', status: 'Active' } ]; const id = $props.id(); </script> <div> <Table> <TableHeader> <TableRow class="hover:bg-transparent"> <TableHead> <Checkbox {id} /> </TableHead> <TableHead>Name</TableHead> <TableHead>Email</TableHead> <TableHead>Location</TableHead> <TableHead>Status</TableHead> <TableHead class="text-right">Balance</TableHead> </TableRow> </TableHeader> <TableBody> {#each items as item (item.id)} <TableRow class="has-data-[state=checked]:bg-muted/50"> <TableCell> <Checkbox id="table-checkbox-{item.id}" /> </TableCell> <TableCell class="font-medium">{item.name}</TableCell> <TableCell>{item.email}</TableCell> <TableCell>{item.location}</TableCell> <TableCell>{item.status}</TableCell> <TableCell class="text-right">{item.balance}</TableCell> </TableRow> {/each} </TableBody> <TableFooter class="bg-transparent"> <TableRow class="hover:bg-transparent"> <TableCell colspan={5}>Total</TableCell> <TableCell class="text-right">$2,500.00</TableCell> </TableRow> </TableFooter> </Table> <p class="mt-4 text-center text-sm text-muted-foreground">Table with row selection</p> </div> ``` ### Links - [View Source](https://github.com/max-got/originui-svelte/tree/main/src/lib/components/tables/table-07.svelte) ## table-08 > A type-safe, accessible table-08 component for building modern UIs. This component is part of the tables collection. ### Core Information - **Component ID**: `table-08` - **Location**: `/src/lib/components/tables/table-08.svelte` - **Type**: UI Component ### Usage ```svelte Full component implementation: ```svelte <script lang="ts"> import Checkbox from '$lib/components/ui/checkbox.svelte'; import { Table, TableBody, TableCell, TableFooter, TableHead, TableHeader, TableRow } from '$lib/components/ui/table'; const items = [ { balance: '$1,250.00', email: 'alex.t@company.com', id: '1', location: 'San Francisco, US', name: 'Alex Thompson', status: 'Active' }, { balance: '$600.00', email: 'sarah.c@company.com', id: '2', location: 'Singapore', name: 'Sarah Chen', status: 'Active' }, { balance: '$650.00', email: 'j.wilson@company.com', id: '3', location: 'London, UK', name: 'James Wilson', status: 'Inactive' }, { balance: '$0.00', email: 'm.garcia@company.com', id: '4', location: 'Madrid, Spain', name: 'Maria Garcia', status: 'Active' }, { balance: '-$1,000.00', email: 'd.kim@company.com', id: '5', location: 'Seoul, KR', name: 'David Kim', status: 'Active' } ]; const id = $props.id(); </script> <div> <div class="overflow-hidden rounded-md border bg-background"> <Table> <TableHeader> <TableRow class="hover:bg-transparent"> <TableHead class="h-11"> <Checkbox {id} /> </TableHead> <TableHead class="h-11">Name</TableHead> <TableHead class="h-11">Email</TableHead> <TableHead class="h-11">Location</TableHead> <TableHead class="h-11">Status</TableHead> <TableHead class="h-11 text-right">Balance</TableHead> </TableRow> </TableHeader> <TableBody> {#each items as item (item.id)} <TableRow> <TableCell> <Checkbox id="table-checkbox-{item.id}" /> </TableCell> <TableCell class="font-medium">{item.name}</TableCell> <TableCell>{item.email}</TableCell> <TableCell>{item.location}</TableCell> <TableCell>{item.status}</TableCell> <TableCell class="text-right">{item.balance}</TableCell> </TableRow> {/each} </TableBody> <TableFooter class="bg-transparent"> <TableRow class="hover:bg-transparent"> <TableCell colspan={5}>Total</TableCell> <TableCell class="text-right">$2,500.00</TableCell> </TableRow> </TableFooter> </Table> </div> <p class="mt-4 text-center text-sm text-muted-foreground">Card Table</p> </div> ``` ### Links - [View Source](https://github.com/max-got/originui-svelte/tree/main/src/lib/components/tables/table-08.svelte) ## table-09 > A type-safe, accessible table-09 component for building modern UIs. This component is part of the tables collection. ### Core Information - **Component ID**: `table-09` - **Location**: `/src/lib/components/tables/table-09.svelte` - **Type**: UI Component ### Usage ```svelte Full component implementation: ```svelte <script lang="ts"> import { Table, TableBody, TableCell, TableRow } from '$lib/components/ui/table'; </script> <div class="mx-auto max-w-lg"> <div class="overflow-hidden rounded-md border bg-background"> <Table> <TableBody> <TableRow class="*:border-border hover:bg-transparent [&>:not(:last-child)]:border-r"> <TableCell class="bg-muted/50 py-2 font-medium">Name</TableCell> <TableCell class="py-2">David Kim</TableCell> </TableRow> <TableRow class="*:border-border hover:bg-transparent [&>:not(:last-child)]:border-r"> <TableCell class="bg-muted/50 py-2 font-medium">Email</TableCell> <TableCell class="py-2">d.kim@company.com</TableCell> </TableRow> <TableRow class="*:border-border hover:bg-transparent [&>:not(:last-child)]:border-r"> <TableCell class="bg-muted/50 py-2 font-medium">Location</TableCell> <TableCell class="py-2">Seoul, KR</TableCell> </TableRow> <TableRow class="*:border-border hover:bg-transparent [&>:not(:last-child)]:border-r"> <TableCell class="bg-muted/50 py-2 font-medium">Status</TableCell> <TableCell class="py-2">Active</TableCell> </TableRow> <TableRow class="*:border-border hover:bg-transparent [&>:not(:last-child)]:border-r"> <TableCell class="bg-muted/50 py-2 font-medium">Balance</TableCell> <TableCell class="py-2">$1,000.00</TableCell> </TableRow> </TableBody> </Table> </div> <p class="mt-4 text-center text-sm text-muted-foreground">Vertical table</p> </div> ``` ### Links - [View Source](https://github.com/max-got/originui-svelte/tree/main/src/lib/components/tables/table-09.svelte) ## table-10 > A type-safe, accessible table-10 component for building modern UIs. This component is part of the tables collection. ### Core Information - **Component ID**: `table-10` - **Location**: `/src/lib/components/tables/table-10.svelte` - **Type**: UI Component ### Usage ```svelte Full component implementation: ```svelte <script lang="ts"> import { Table, TableBody, TableCell, TableFooter, TableHead, TableHeader, TableRow } from '$lib/components/ui/table'; const items = [ { balance: '$1,250.00', email: 'alex.t@company.com', id: '1', location: 'San Francisco, US', name: 'Alex Thompson', status: 'Active' }, { balance: '$600.00', email: 'sarah.c@company.com', id: '2', location: 'Singapore', name: 'Sarah Chen', status: 'Active' }, { balance: '$650.00', email: 'j.wilson@company.com', id: '3', location: 'London, UK', name: 'James Wilson', status: 'Inactive' }, { balance: '$0.00', email: 'm.garcia@company.com', id: '4', location: 'Madrid, Spain', name: 'Maria Garcia', status: 'Active' }, { balance: '-$1,000.00', email: 'd.kim@company.com', id: '5', location: 'Seoul, KR', name: 'David Kim', status: 'Active' }, { balance: '$1,500.00', email: 'john.brown@company.com', id: '6', location: 'New York, US', name: 'John Brown', status: 'Active' }, { balance: '$200.00', email: 'jane.doe@company.com', id: '7', location: 'Paris, FR', name: 'Jane Doe', status: 'Inactive' }, { balance: '$1,000.00', email: 'peter.smith@company.com', id: '8', location: 'Berlin, DE', name: 'Peter Smith', status: 'Active' }, { balance: '$500.00', email: 'olivia.lee@company.com', id: '9', location: 'Tokyo, JP', name: 'Olivia Lee', status: 'Active' }, { balance: '$300.00', email: 'liam.chen@company.com', id: '10', location: 'Shanghai, CN', name: 'Liam Chen', status: 'Inactive' }, { balance: '$800.00', email: 'ethan.kim@company.com', id: '11', location: 'Busan, KR', name: 'Ethan Kim', status: 'Active' }, { balance: '$1,200.00', email: 'ava.brown@company.com', id: '12', location: 'London, UK', name: 'Ava Brown', status: 'Active' }, { balance: '$400.00', email: 'lily.lee@company.com', id: '13', location: 'Seoul, KR', name: 'Lily Lee', status: 'Active' }, { balance: '$600.00', email: 'noah.smith@company.com', id: '14', location: 'New York, US', name: 'Noah Smith', status: 'Inactive' }, { balance: '$1,800.00', email: 'eve.chen@company.com', id: '15', location: 'Taipei, TW', name: 'Eve Chen', status: 'Active' } ]; </script> <div> <div class="[&>div]:max-h-96"> <Table class="border-separate border-spacing-0 [&_td]:border-border [&_tfoot_td]:border-t [&_th]:border-b [&_th]:border-border [&_tr:not(:last-child)_td]:border-b [&_tr]:border-none" > <TableHeader class="backdrop-blur-xs sticky top-0 z-10 bg-background/90"> <TableRow class="hover:bg-transparent"> <TableHead>Name</TableHead> <TableHead>Email</TableHead> <TableHead>Location</TableHead> <TableHead>Status</TableHead> <TableHead class="text-right">Balance</TableHead> </TableRow> </TableHeader> <TableBody> {#each items as item (item.id)} <TableRow> <TableCell class="font-medium">{item.name}</TableCell> <TableCell>{item.email}</TableCell> <TableCell>{item.location}</TableCell> <TableCell>{item.status}</TableCell> <TableCell class="text-right">{item.balance}</TableCell> </TableRow> {/each} </TableBody> <TableFooter class="bg-transparent"> <TableRow class="hover:bg-transparent"> <TableCell colspan={4}>Total</TableCell> <TableCell class="text-right">$2,500.00</TableCell> </TableRow> </TableFooter> </Table> </div> <p class="mt-8 text-center text-sm text-muted-foreground">Table with sticky header</p> </div> ``` ### Links - [View Source](https://github.com/max-got/originui-svelte/tree/main/src/lib/components/tables/table-10.svelte) ## table-11 > A type-safe, accessible table-11 component for building modern UIs. This component is part of the tables collection. ### Core Information - **Component ID**: `table-11` - **Location**: `/src/lib/components/tables/table-11.svelte` - **Type**: UI Component ### Usage ```svelte ### Dependencies Required packages and components: - [`lucide-svelte`](https://github.com/lucide-icons/lucide) Full component implementation: ```svelte <script lang="ts"> import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from '$lib/components/ui/table'; import Check from 'lucide-svelte/icons/check'; import Monitor from 'lucide-svelte/icons/monitor'; import Smartphone from 'lucide-svelte/icons/smartphone'; import X from 'lucide-svelte/icons/x'; const items = [ { desktop: [ { name: 'Chrome', supported: true, version: '115' }, { name: 'Edge', supported: true, version: '115' }, { name: 'Firefox', supported: false, version: '111' }, { name: 'Opera', supported: true, version: '101' }, { name: 'Safari', supported: false, version: 'No' } ], feature: 'scroll-timeline', mobile: [ { name: 'Chrome Android', supported: true, version: '115' }, { name: 'Firefox Android', supported: false, version: 'No' }, { name: 'Opera Android', supported: true, version: '77' }, { name: 'Safari iOS', supported: false, version: 'No' }, { name: 'Samsung Internet', supported: true, version: '23' } ] }, { desktop: [ { name: 'Chrome', supported: true, version: '115' }, { name: 'Edge', supported: true, version: '115' }, { name: 'Firefox', supported: false, version: '114' }, { name: 'Opera', supported: true, version: '101' }, { name: 'Safari', supported: false, version: 'No' } ], feature: 'view-timeline', mobile: [ { name: 'Chrome Android', supported: true, version: '115' }, { name: 'Firefox Android', supported: false, version: 'No' }, { name: 'Opera Android', supported: true, version: '77' }, { name: 'Safari iOS', supported: false, version: 'No' }, { name: 'Samsung Internet', supported: true, version: '23' } ] }, { desktop: [ { name: 'Chrome', supported: true, version: '127' }, { name: 'Edge', supported: true, version: '127' }, { name: 'Firefox', supported: false, version: '3' }, { name: 'Opera', supported: true, version: '113' }, { name: 'Safari', supported: true, version: '16.4' } ], feature: 'font-size-adjust', mobile: [ { name: 'Chrome Android', supported: true, version: '127' }, { name: 'Firefox Android', supported: true, version: '4' }, { name: 'Opera Android', supported: true, version: '84' }, { name: 'Safari iOS', supported: true, version: '16.4' }, { name: 'Samsung Internet', supported: false, version: 'No' } ] } ]; </script> <Table> <TableHeader> <TableRow class="border-y-0 *:border-border hover:bg-transparent [&>:not(:last-child)]:border-r" > <TableCell></TableCell> <TableHead class="border-b text-center" colspan={5}> <Monitor class="inline-flex" size={16} aria-hidden="true" /> <span class="sr-only">Desktop browsers</span> </TableHead> <TableHead class="border-b text-center" colspan={5}> <Smartphone class="inline-flex" size={16} aria-hidden="true" /> <span class="sr-only">Mobile browsers</span> </TableHead> </TableRow> </TableHeader> <TableHeader> <TableRow class="*:border-border hover:bg-transparent [&>:not(:last-child)]:border-r"> <TableCell></TableCell> {#each items[0].desktop as browser (browser.name)} <TableHead class="h-auto py-3 align-bottom text-foreground"> <span class="relative left-[calc(50%-.5rem)] block rotate-180 whitespace-nowrap leading-4 [text-orientation:sideways] [writing-mode:vertical-rl]" > {browser.name} </span> </TableHead> {/each} {#each items[0].mobile as browser (browser.name)} <TableHead class="h-auto py-3 align-bottom text-foreground"> <span class="relative left-[calc(50%-.5rem)] block rotate-180 whitespace-nowrap leading-4 [text-orientation:sideways] [writing-mode:vertical-rl]" > {browser.name} </span> </TableHead> {/each} </TableRow> </TableHeader> <TableBody> {#each items as item (item.feature)} <TableRow class="*:border-border [&>:not(:last-child)]:border-r"> <TableHead class="font-medium text-foreground"> {item.feature} </TableHead> {#each [...item.desktop, ...item.mobile] as browser, index (`${browser.name}-${index}`)} <TableCell class="space-y-1 text-center"> {#if browser.supported} <Check class="inline-flex stroke-emerald-600" size={16} aria-hidden="true" /> {:else} <X class="inline-flex stroke-red-600" size={16} aria-hidden="true" /> {/if} <span class="sr-only"> {browser.supported ? 'Supported' : 'Not supported'} </span> <div class="text-xs font-medium text-muted-foreground"> {browser.version} </div> </TableCell> {/each} </TableRow> {/each} </TableBody> </Table> ``` ### Links - [View Source](https://github.com/max-got/originui-svelte/tree/main/src/lib/components/tables/table-11.svelte) ## table-12 > A type-safe, accessible table-12 component for building modern UIs. This component is part of the tables collection. ### Core Information - **Component ID**: `table-12` - **Location**: `/src/lib/components/tables/table-12.svelte` - **Type**: UI Component ### Usage ```svelte Full component implementation: ```svelte <script lang="ts"> import type { User } from '$data/api/data/users.handlers'; import Badge from '$lib/components/ui/badge.svelte'; import Checkbox from '$lib/components/ui/checkbox.svelte'; import { type ColumnDef, getCoreRowModel, type RowSelectionState } from '@tanstack/table-core'; import { fetchUsers } from '$data/api/data/users'; import { createSvelteTable, FlexRender, renderComponent, renderSnippet } from '$lib/components/ui/data-table'; import { Table, TableBody, TableCell, TableFooter, TableHead, TableHeader, TableRow } from '$lib/components/ui/table'; import { cn } from '$lib/utils'; import { createRawSnippet } from 'svelte'; const columns: ColumnDef<User>[] = [ { cell: ({ row }) => renderComponent(Checkbox, { 'aria-label': 'Select row', checked: row.getIsSelected(), onCheckedChange: (value) => row.toggleSelected(!!value) }), header: ({ table }) => renderComponent(Checkbox, { 'aria-label': 'Select all', checked: table.getIsAllPageRowsSelected(), indeterminate: table.getIsSomePageRowsSelected() && !table.getIsAllPageRowsSelected(), onCheckedChange: (value) => table.toggleAllPageRowsSelected(!!value) }), id: 'select' }, { accessorKey: 'name', cell: ({ row }) => { const nameSnippet = createRawSnippet<[string]>((getName) => { const name = getName(); return { render: () => `<div class="font-medium">${name}</div>` }; }); return renderSnippet(nameSnippet, row.getValue('name')); }, header: 'Name' }, { accessorKey: 'email', header: 'Email' }, { accessorKey: 'location', cell: ({ row }) => { const locationSnippet = createRawSnippet<[{ flag: string; location: string }]>((args) => { const { flag, location } = args(); return { render: () => ` <div> <span class="text-lg leading-none">${flag}</span> ${location} </div>` }; }); return renderSnippet(locationSnippet, { flag: row.original.flag, location: row.getValue('location') as string }); }, header: 'Location' }, { accessorKey: 'status', cell: ({ row }) => renderComponent(Badge, { children: createRawSnippet(() => { const status = row.getValue('status') as string; return { render: () => status }; }), class: cn( row.getValue('status') === 'Inactive' && 'bg-muted-foreground/60 text-primary-foreground' ) }), header: 'Status' }, { accessorKey: 'balance', cell: ({ row }) => { return renderSnippet( createRawSnippet((getBalance) => { const balance = getBalance() as string; const formatted = new Intl.NumberFormat('en-US', { currency: 'USD', style: 'currency' }).format(parseFloat(balance)); return { render: () => `<div class="text-right">${formatted}</div>` }; }), row.getValue('balance') ); }, header: () => { const nameSnippet = createRawSnippet(() => { return { render: () => `<div class="text-right">Balance</div>` }; }); return renderSnippet(nameSnippet, {}); } } ]; let rowSelection = $state<RowSelectionState>({}); let data = $state<User[]>([]); $effect(() => { fetchUsers() .then((response) => { data = response.slice(0, 5); }) .catch((err) => { console.error(err); }); }); const table = createSvelteTable<User>({ columns, get data() { return data; }, getCoreRowModel: getCoreRowModel(), onRowSelectionChange: (updater) => { if (typeof updater === 'function') { rowSelection = updater(rowSelection); } else { rowSelection = updater; } }, state: { get rowSelection() { return rowSelection; } } }); </script> <div> <Table> <TableHeader> {#each table.getHeaderGroups() as headerGroup (headerGroup.id)} <TableRow class="hover:bg-transparent"> {#each headerGroup.headers as header (header.id)} <TableHead> {#if !header.isPlaceholder} <FlexRender content={header.column.columnDef.header} context={header.getContext()} /> {/if} </TableHead> {/each} </TableRow> {/each} </TableHeader> <TableBody> {#each table.getRowModel().rows as row (row.id)} <TableRow data-state={row.getIsSelected() && 'selected'}> {#each row.getVisibleCells() as cell (cell.id)} <TableCell> <FlexRender content={cell.column.columnDef.cell} context={cell.getContext()} /> </TableCell> {/each} </TableRow> {:else} <TableRow> <TableCell colspan={columns.length} class="h-24 text-center">No results.</TableCell> </TableRow> {/each} </TableBody> <TableFooter class="bg-transparent"> <TableRow class="hover:bg-transparent"> <TableCell colspan={5}>Total</TableCell> <TableCell class="text-right"> {new Intl.NumberFormat('en-US', { currency: 'USD', style: 'currency' }).format(data.reduce((total, item) => total + item.balance, 0))} </TableCell> </TableRow> </TableFooter> </Table> <p class="mt-4 text-center text-sm text-muted-foreground"> Basic data table made with <a class="underline hover:text-foreground" href="https://tanstack.com/table" target="_blank" rel="noopener noreferrer" > TanStack Table </a> </p> </div> ``` ### Links - [View Source](https://github.com/max-got/originui-svelte/tree/main/src/lib/components/tables/table-12.svelte) ## table-13 > A type-safe, accessible table-13 component for building modern UIs. This component is part of the tables collection. ### Core Information - **Component ID**: `table-13.todo` - **Location**: `/src/lib/components/tables/table-13.todo.svelte` - **Type**: UI Component ### Usage ```svelte Full component implementation: ```svelte ``` ### Links - [View Source](https://github.com/max-got/originui-svelte/tree/main/src/lib/components/tables/table-13.todo.svelte) ## table-14 > A type-safe, accessible table-14 component for building modern UIs. This component is part of the tables collection. ### Core Information - **Component ID**: `table-14` - **Location**: `/src/lib/components/tables/table-14.svelte` - **Type**: UI Component ### Usage ```svelte ### Dependencies Required packages and components: - [`lucide-svelte`](https://github.com/lucide-icons/lucide) Full component implementation: ```svelte <script lang="ts"> import type { User } from '$data/api/data/users.handlers'; import { type ColumnDef, type ColumnSizingState, getCoreRowModel, getSortedRowModel, type SortingState } from '@tanstack/table-core'; import { fetchUsers } from '$data/api/data/users'; import { createSvelteTable, FlexRender, renderSnippet } from '$lib/components/ui/data-table'; import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from '$lib/components/ui/table'; import { cn } from '$lib/utils'; import ChevronDown from 'lucide-svelte/icons/chevron-down'; import ChevronUp from 'lucide-svelte/icons/chevron-up'; import { createRawSnippet } from 'svelte'; const columns: ColumnDef<User>[] = [ { accessorKey: 'name', cell: ({ row }) => { const nameSnippet = createRawSnippet<[string]>((getName) => { const name = getName(); return { render: () => `<div class="truncate font-medium">${name}</div>` }; }); return renderSnippet(nameSnippet, row.getValue('name')); }, header: 'Name', sortDescFirst: false, sortUndefined: 'last' }, { accessorKey: 'email', header: 'Email' }, { accessorKey: 'location', cell: ({ row }) => { const locationSnippet = createRawSnippet<[{ flag: string; location: string }]>((args) => { const { flag, location } = args(); return { render: () => ` <div> <span class="text-lg leading-none">${flag}</span> ${location} </div>` }; }); return renderSnippet(locationSnippet, { flag: row.original.flag, location: row.getValue('location') as string }); }, header: 'Location' }, { accessorKey: 'status', header: 'Status' }, { accessorKey: 'balance', cell: ({ row }) => { const amount = parseFloat(row.getValue('balance')); const formatted = new Intl.NumberFormat('en-US', { currency: 'USD', style: 'currency' }).format(amount); return formatted; }, header: 'Balance' }, { accessorKey: 'department', header: 'Department' }, { accessorKey: 'role', header: 'Role' }, { accessorKey: 'joinDate', header: 'Join Date' }, { accessorKey: 'lastActive', header: 'Last Active' }, { accessorKey: 'performance', header: 'Performance' } ]; let sorting = $state<SortingState>([ { desc: false, id: 'name' } ]); let columnSizing = $state<ColumnSizingState>({}); let data = $state<User[]>([]); $effect(() => { fetchUsers() .then((response) => { data = response.slice(0, 5); }) .catch((err) => { console.error(err); }); }); const table = createSvelteTable<User>({ columnResizeMode: 'onChange', columns, get data() { return data; }, getCoreRowModel: getCoreRowModel(), getSortedRowModel: getSortedRowModel(), onColumnSizingChange: (updater) => { if (typeof updater === 'function') { columnSizing = updater(columnSizing); } else { columnSizing = updater; } }, onSortingChange: (updater) => { if (typeof updater === 'function') { sorting = updater(sorting); } else { sorting = updater; } }, state: { get columnSizing() { return columnSizing; }, get sorting() { return sorting; } } }); </script> <div> <Table class="table-fixed" style="width: {table.getCenterTotalSize()}px"> <TableHeader> {#each table.getHeaderGroups() as headerGroup (headerGroup.id)} <TableRow class="bg-muted/50"> {#each headerGroup.headers as header (header.id)} <TableHead class="relative h-10 select-none border-t [&:last-child>.cursor-col-resize]:opacity-0" aria-sort={header.column.getIsSorted() === 'asc' ? 'ascending' : header.column.getIsSorted() === 'desc' ? 'descending' : 'none'} colspan={header.colSpan} style="width: {header.getSize()}px" > {#if !header.isPlaceholder} <div class={cn( header.column.getCanSort() && 'flex h-full cursor-pointer select-none items-center justify-between gap-2' )} onclick={header.column.getToggleSortingHandler()} onkeydown={(e) => { // Enhanced keyboard handling for sorting if (header.column.getCanSort() && (e.key === 'Enter' || e.key === ' ')) { e.preventDefault(); header.column.getToggleSortingHandler()?.(e); } }} tabindex={header.column.getCanSort() ? 0 : undefined} > <span class="truncate"> <FlexRender content={header.column.columnDef.header} context={header.getContext()} /> </span> {#if header.column.getIsSorted() === 'asc'} <ChevronUp class="shrink-0 opacity-60" size={16} aria-hidden="true" /> {:else if header.column.getIsSorted() === 'desc'} <ChevronDown class="shrink-0 opacity-60" size={16} aria-hidden="true" /> {/if} </div> {/if} {#if header.column.getCanResize()} <div class="user-select-none absolute -right-2 top-0 z-10 flex h-full w-4 cursor-col-resize touch-none justify-center before:absolute before:inset-y-0 before:w-px before:translate-x-px before:bg-border" ondblclick={() => header.column.resetSize()} onmousedown={header.getResizeHandler()} ontouchstart={header.getResizeHandler()} /> {/if} </TableHead> {/each} </TableRow> {/each} </TableHeader> <TableBody> {#each table.getRowModel().rows as row (row.id)} <TableRow data-state={row.getIsSelected() && 'selected'}> {#each row.getVisibleCells() as cell (cell.id)} <TableCell class="truncate"> <FlexRender content={cell.column.columnDef.cell} context={cell.getContext()} /> </TableCell> {/each} </TableRow> {:else} <TableRow> <TableCell colspan={columns.length} class="h-24 text-center">No results.</TableCell> </TableRow> {/each} </TableBody> </Table> <p class="mt-4 text-center text-sm text-muted-foreground"> Resizable and sortable columns made with <a class="underline hover:text-foreground" href="https://tanstack.com/table" target="_blank" rel="noopener noreferrer" > TanStack Table </a> </p> </div> ``` ### Links - [View Source](https://github.com/max-got/originui-svelte/tree/main/src/lib/components/tables/table-14.svelte) ## table-15 > A type-safe, accessible table-15 component for building modern UIs. This component is part of the tables collection. ### Core Information - **Component ID**: `table-15` - **Location**: `/src/lib/components/tables/table-15.svelte` - **Type**: UI Component ### Usage ```svelte ### Dependencies Required packages and components: - [`lucide-svelte`](https://github.com/lucide-icons/lucide) Full component implementation: ```svelte <script lang="ts"> import type { User } from '$data/api/data/users.handlers'; import type { HTMLAttributes } from 'svelte/elements'; import Button from '$lib/components/ui/button.svelte'; import { type Column, type ColumnDef, type ColumnPinningState, type ColumnSizingState, getCoreRowModel, getSortedRowModel, type SortingState } from '@tanstack/table-core'; import { fetchUsers } from '$data/api/data/users'; import { createSvelteTable, FlexRender, renderSnippet } from '$lib/components/ui/data-table'; import { DropdownMenu, DropdownMenuContent, DropdownMenuItem, DropdownMenuTrigger } from '$lib/components/ui/dropdowns'; import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from '$lib/components/ui/table'; import ArrowLeftToLineIcon from 'lucide-svelte/icons/arrow-left-to-line'; import ArrowRightToLineIcon from 'lucide-svelte/icons/arrow-right-to-line'; import EllipsisIcon from 'lucide-svelte/icons/ellipsis'; import PinOffIcon from 'lucide-svelte/icons/pin-off'; import { createRawSnippet } from 'svelte'; let sorting = $state<SortingState>([ { desc: false, id: 'name' } ]); // Helper function to compute pinning styles for columns function getPinningStyles(column: Column<User>): HTMLAttributes<HTMLTableCellElement>['style'] { const isPinned = column.getIsPinned(); const properties = { left: isPinned === 'left' ? `${column.getStart('left')}px` : undefined, position: isPinned ? 'sticky' : 'relative', right: isPinned === 'right' ? `${column.getAfter('right')}px` : undefined, width: column.getSize() + 'px', 'z-index': isPinned ? 1 : 0 }; return Object.entries(properties) .filter(([, value]) => value !== undefined) .map(([key, value]) => `${key}: ${value}`) .join(';'); } const columns: ColumnDef<User>[] = [ { accessorKey: 'name', cell: ({ row }) => { const nameSnippet = createRawSnippet<[string]>((getName) => { const name = getName(); return { render: () => `<div class="truncate font-medium">${name}</div>` }; }); return renderSnippet(nameSnippet, row.getValue('name')); }, header: 'Name' }, { accessorKey: 'email', header: 'Email' }, { accessorKey: 'location', cell: ({ row }) => { const locationSnippet = createRawSnippet<[{ flag: string; location: string }]>((args) => { const { flag, location } = args(); return { render: () => ` <div> <span class="text-lg leading-none">${flag}</span> ${location} </div>` }; }); return renderSnippet(locationSnippet, { flag: row.original.flag, location: row.getValue('location') as string }); }, header: 'Location' }, { accessorKey: 'status', header: 'Status' }, { accessorKey: 'balance', cell: ({ row }) => { const amount = parseFloat(row.getValue('balance')); const formatted = new Intl.NumberFormat('en-US', { currency: 'USD', style: 'currency' }).format(amount); return formatted; }, header: 'Balance' }, { accessorKey: 'department', header: 'Department' }, { accessorKey: 'role', header: 'Role' }, { accessorKey: 'joinDate', header: 'Join Date' }, { accessorKey: 'lastActive', header: 'Last Active' }, { accessorKey: 'performance', header: 'Performance' } ]; let columnSizing = $state<ColumnSizingState>({}); let columnPinning = $state<ColumnPinningState>({}); let data = $state<User[]>([]); $effect(() => { fetchUsers() .then((response) => { data = response.slice(0, 5); }) .catch((err) => { console.error(err); }); }); const table = createSvelteTable<User>({ columnResizeMode: 'onChange', columns, get data() { return data; }, enableSortingRemoval: false, getCoreRowModel: getCoreRowModel(), getSortedRowModel: getSortedRowModel(), onColumnPinningChange: (updater) => { if (typeof updater === 'function') { columnPinning = updater(columnPinning); } else { columnPinning = updater; } }, onColumnSizingChange: (updater) => { if (typeof updater === 'function') { columnSizing = updater(columnSizing); } else { columnSizing = updater; } }, onSortingChange: (updater) => { if (typeof updater === 'function') { sorting = updater(sorting); } else { sorting = updater; } }, state: { get columnPinning() { return columnPinning; }, get columnSizing() { return columnSizing; }, get sorting() { return sorting; } } }); </script> <div> <Table class="table-fixed border-separate border-spacing-0 [&_td]:border-border [&_tfoot_td]:border-t [&_th]:border-b [&_th]:border-border [&_tr:not(:last-child)_td]:border-b [&_tr]:border-none" style="width: {table.getCenterTotalSize()}px" > <TableHeader> {#each table.getHeaderGroups() as headerGroup (headerGroup.id)} <TableRow class="bg-muted/50"> {#each headerGroup.headers as header (header.id)} {@const isPinned = header.column.getIsPinned()} {@const isLastLeftPinned = isPinned === 'left' && header.column.getIsLastColumn('left')} {@const isFirstRightPinned = isPinned === 'right' && header.column.getIsFirstColumn('right')} <TableHead class="data-pinned:bg-muted/90 data-pinned:backdrop-blur-xs relative h-10 truncate border-t [&:not([data-pinned]):has(+[data-pinned])_div.cursor-col-resize:last-child]:opacity-0 [&[data-last-col=left]_div.cursor-col-resize:last-child]:opacity-0 [&[data-pinned=left][data-last-col=left]]:border-r [&[data-pinned=right]:last-child_div.cursor-col-resize:last-child]:opacity-0 [&[data-pinned=right][data-last-col=right]]:border-l [&[data-pinned][data-last-col]]:border-border" aria-sort={header.column.getIsSorted() === 'asc' ? 'ascending' : header.column.getIsSorted() === 'desc' ? 'descending' : 'none'} colspan={header.colSpan} data-pinned={isPinned || undefined} data-last-col={isLastLeftPinned ? 'left' : isFirstRightPinned ? 'right' : undefined} style={getPinningStyles(header.column)} > <div class="flex items-center justify-between gap-2"> {#if !header.isPlaceholder} <span class="truncate"> <FlexRender content={header.column.columnDef.header} context={header.getContext()} /> </span> {/if} <!-- Pin/Unpin column controls with enhanced accessibility --> {#if !header.isPlaceholder && header.column.getCanPin() && header.column.getIsPinned()} <Button size="icon" variant="ghost" class="-mr-1 size-7 shadow-none" onclick={() => header.column.pin(false)} aria-label="Unpin {header.column.columnDef.header} column" title="Unpin {header.column.columnDef.header} column" > <PinOffIcon class="opacity-60" size={16} aria-hidden="true" /> </Button> {:else} <DropdownMenu> <DropdownMenuTrigger> {#snippet child({ props })} <Button size="icon" variant="ghost" class="-mr-1 size-7 shadow-none" aria-label="Pin options for {header.column.columnDef.header} column" title="Pin options for {header.column.columnDef.header} column" {...props} > <EllipsisIcon class="opacity-60" size={16} aria-hidden="true" /> </Button> {/snippet} </DropdownMenuTrigger> <DropdownMenuContent align="end"> <DropdownMenuItem onclick={() => header.column.pin('left')}> <ArrowLeftToLineIcon size={16} class="opacity-60" aria-hidden="true" /> Stick to left </DropdownMenuItem> <DropdownMenuItem onclick={() => header.column.pin('right')}> <ArrowRightToLineIcon size={16} class="opacity-60" aria-hidden="true" /> Stick to right </DropdownMenuItem> </DropdownMenuContent> </DropdownMenu> {/if} {#if header.column.getCanResize()} <div class="user-select-none absolute -right-2 top-0 z-10 flex h-full w-4 cursor-col-resize touch-none justify-center before:absolute before:inset-y-0 before:w-px before:-translate-x-px before:bg-border" ondblclick={() => header.column.resetSize()} onmousedown={header.getResizeHandler()} ontouchstart={header.getResizeHandler()} /> {/if} </div> </TableHead> {/each} </TableRow> {/each} </TableHeader> <TableBody> {#each table.getRowModel().rows as row (row.id)} <TableRow data-state={row.getIsSelected() && 'selected'}> {#each row.getVisibleCells() as cell (cell.id)} {@const isPinned = cell.column.getIsPinned()} {@const isLastLeftPinned = isPinned === 'left' && cell.column.getIsLastColumn('left')} {@const isFirstRightPinned = isPinned === 'right' && cell.column.getIsFirstColumn('right')} <TableCell class="data-pinned:bg-background/90 data-pinned:backdrop-blur-xs truncate [&[data-pinned=left][data-last-col=left]]:border-r [&[data-pinned=right][data-last-col=right]]:border-l [&[data-pinned][data-last-col]]:border-border" style={getPinningStyles(cell.column)} data-pinned={isPinned || undefined} data-last-col={isLastLeftPinned ? 'left' : isFirstRightPinned ? 'right' : undefined} > <FlexRender content={cell.column.columnDef.cell} context={cell.getContext()} /> </TableCell> {/each} </TableRow> {:else} <TableRow> <TableCell colspan={columns.length} class="h-24 text-center">No results.</TableCell> </TableRow> {/each} </TableBody> </Table> <p class="mt-4 text-center text-sm text-muted-foreground"> Pinnable columns made with <a class="underline hover:text-foreground" href="https://tanstack.com/table" target="_blank" rel="noopener noreferrer" > TanStack Table </a> </p> </div> ``` ### Links - [View Source](https://github.com/max-got/originui-svelte/tree/main/src/lib/components/tables/table-15.svelte) ## table-16 > A type-safe, accessible table-16 component for building modern UIs. This component is part of the tables collection. ### Core Information - **Component ID**: `table-16` - **Location**: `/src/lib/components/tables/table-16.svelte` - **Type**: UI Component ### Usage ```svelte ### Dependencies Required packages and components: - [`@dnd-kit-svelte/core`](https://github.com/HanielU/dnd-kit-svelte) - [`@dnd-kit-svelte/modifiers`](https://github.com/HanielU/dnd-kit-svelte) - [`@dnd-kit-svelte/sortable`](https://github.com/HanielU/dnd-kit-svelte) - [`@dnd-kit-svelte/utilities`](https://github.com/HanielU/dnd-kit-svelte) - [`lucide-svelte`](https://github.com/lucide-icons/lucide) Full component implementation: ```svelte <script lang="ts"> import type { User } from '$data/api/data/users.handlers'; import Button from '$lib/components/ui/button.svelte'; import { closestCenter, DndContext, type DragEndEvent, KeyboardSensor, MouseSensor, TouchSensor, useSensor, useSensors } from '@dnd-kit-svelte/core'; import { restrictToHorizontalAxis } from '@dnd-kit-svelte/modifiers'; import { arrayMove, horizontalListSortingStrategy, SortableContext, useSortable } from '@dnd-kit-svelte/sortable'; import { CSS } from '@dnd-kit-svelte/utilities'; import { type Cell, type ColumnDef, getCoreRowModel, getSortedRowModel, type Header, type SortingState } from '@tanstack/table-core'; import { fetchUsers } from '$data/api/data/users'; import { createSvelteTable, FlexRender, renderSnippet } from '$lib/components/ui/data-table'; import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from '$lib/components/ui/table'; import ChevronDown from 'lucide-svelte/icons/chevron-down'; import ChevronUp from 'lucide-svelte/icons/chevron-up'; import GripVertical from 'lucide-svelte/icons/grip-vertical'; import { createRawSnippet } from 'svelte'; const columns: ColumnDef<User>[] = [ { accessorKey: 'name', cell: ({ row }) => { const nameSnippet = createRawSnippet<[string]>((getName) => { const name = getName(); return { render: () => `<div class="truncate font-medium">${name}</div>` }; }); return renderSnippet(nameSnippet, row.getValue('name')); }, header: 'Name', id: 'name', sortDescFirst: false, sortUndefined: 'last' }, { accessorKey: 'email', header: 'Email', id: 'email' }, { accessorKey: 'location', cell: ({ row }) => { const locationSnippet = createRawSnippet<[{ flag: string; location: string }]>((args) => { const { flag, location } = args(); return { render: () => ` <div class="truncate"> <span class="text-lg leading-none">${flag}</span> ${location} </div>` }; }); return renderSnippet(locationSnippet, { flag: row.original.flag, location: row.getValue('location') as string }); }, header: 'Location', id: 'location' }, { accessorKey: 'status', header: 'Status', id: 'status' }, { accessorKey: 'balance', cell: ({ row }) => { const amount = parseFloat(row.getValue('balance')); const formatted = new Intl.NumberFormat('en-US', { currency: 'USD', style: 'currency' }).format(amount); return formatted; }, header: 'Balance', id: 'balance' } ]; let data = $state<User[]>([]); let sorting = $state<SortingState>([ { desc: false, id: 'name' } ]); let columnOrder = $state<string[]>(columns.map((column) => column.id ?? '')); $effect(() => { fetchUsers() .then((response) => { data = response.slice(0, 5); }) .catch((err) => { console.error(err); }); }); const table = createSvelteTable<User>({ columnResizeMode: 'onChange', columns, get data() { return data; }, enableSortingRemoval: false, getCoreRowModel: getCoreRowModel(), getSortedRowModel: getSortedRowModel(), onColumnOrderChange: (updater) => { if (typeof updater === 'function') { columnOrder = updater(columnOrder); } else { columnOrder = updater; } }, onSortingChange: (updater) => { if (typeof updater === 'function') { sorting = updater(sorting); } else { sorting = updater; } }, state: { get columnOrder() { return columnOrder; }, get sorting() { return sorting; } } }); // reorder columns after drag & drop function handleDragEnd(event: DragEndEvent) { const { active, over } = event; if (active && over && active.id !== over.id) { // this is just a splice util columnOrder = arrayMove( columnOrder, columnOrder.indexOf(active.id as string), // oldIndex columnOrder.indexOf(over.id as string) // newIndex ); } } const sensors = useSensors( useSensor(MouseSensor, {}), useSensor(TouchSensor, {}), useSensor(KeyboardSensor, {}) ); let id = $props.id(); </script> <DndContext {id} collisionDetection={closestCenter} modifiers={[restrictToHorizontalAxis]} onDragEnd={handleDragEnd} {sensors} > <Table> <TableHeader> {#each table.getHeaderGroups() as headerGroup (headerGroup.id)} <TableRow class="bg-muted/50"> <SortableContext items={columnOrder} strategy={horizontalListSortingStrategy}> {#each headerGroup.headers as header (header.id)} {@render DraggableTableHeader(header)} {/each} </SortableContext> </TableRow> {/each} </TableHeader> <TableBody> {#each table.getRowModel().rows as row (row.id)} <TableRow data-state={row.getIsSelected() && 'selected'}> {#each row.getVisibleCells() as cell (cell.id)} <SortableContext items={columnOrder} strategy={horizontalListSortingStrategy}> {@render DragAlongCell(cell)} </SortableContext> {/each} </TableRow> {:else} <TableRow> <TableCell colspan={columns.length} class="h-24 text-center">No results.</TableCell> </TableRow> {/each} </TableBody> </Table> <p class="mt-4 text-center text-sm text-muted-foreground"> Draggable columns made with <a class="underline hover:text-foreground" href="https://tanstack.com/table" target="_blank" rel="noopener noreferrer" > TanStack Table </a> and <a href="https://dnd-kit-svelte.vercel.app/" target="_blank" rel="noopener noreferrer"> dnd kit </a> </p> </DndContext> {#snippet DraggableTableHeader(header: Header<User, unknown>)} {@const { attributes, isDragging, listeners, setNodeRef, transform, transition } = useSortable({ id: header.column.id })} {@const style = `opacity: ${isDragging.current ? 0.8 : 1}; position: relative; transform: ${CSS.Translate.toString(transform.current)}; transition: ${transition.current ?? 'initial'}; whiteSpace: nowrap; width: ${header.column.getSize()}px; z-index: ${isDragging.current ? 1 : 0}`} <TableHead ref={setNodeRef as unknown as HTMLElement} class="relative h-10 border-t before:absolute before:inset-y-0 before:start-0 before:w-px before:bg-border first:before:bg-transparent" {style} aria-sort={header.column.getIsSorted() === 'asc' ? 'ascending' : header.column.getIsSorted() === 'desc' ? 'descending' : 'none'} > <div class="flex items-center justify-start gap-0.5"> <Button size="icon" variant="ghost" class="-ml-2 size-7 shadow-none" {...attributes.current} {...listeners.current} aria-label="Drag to reorder" > <GripVertical class="opacity-60" size={16} aria-hidden="true" /> </Button> <span class="grow truncate"> {#if !header.isPlaceholder} <FlexRender content={header.column.columnDef.header} context={header.getContext()} /> {/if} </span> <Button size="icon" variant="ghost" class="group -mr-1 size-7 shadow-none" onclick={header.column.getToggleSortingHandler()} onkeydown={(e: KeyboardEvent) => { // Enhanced keyboard handling for sorting if (header.column.getCanSort() && (e.key === 'Enter' || e.key === ' ')) { e.preventDefault(); header.column.getToggleSortingHandler()?.(e); } }} > {#if header.column.getIsSorted() === 'asc'} <ChevronUp class="shrink-0 opacity-60" size={16} aria-hidden="true" /> {:else if header.column.getIsSorted() === 'desc'} <ChevronDown class="shrink-0 opacity-60" size={16} aria-hidden="true" /> {:else if header.column.getIsSorted() === false} <ChevronUp class="shrink-0 opacity-0 group-hover:opacity-60" size={16} aria-hidden="true" /> {/if} </Button> </div> </TableHead> {/snippet} {#snippet DragAlongCell(cell: Cell<User, unknown>)} {@const { isDragging, setNodeRef, transform, transition } = useSortable({ id: cell.column.id })} {@const style = `opacity: ${isDragging.current ? 0.8 : 1}; position: relative; transform: ${CSS.Translate.toString(transform.current)}; transition: ${transition.current ?? 'initial'}; width: ${cell.column.getSize()}px; z-index: ${isDragging.current ? 1 : 0}`} <TableCell class="truncate" ref={setNodeRef as unknown as HTMLElement} {style}> <FlexRender content={cell.column.columnDef.cell} context={cell.getContext()} /> </TableCell> {/snippet} ``` ### Links - [View Source](https://github.com/max-got/originui-svelte/tree/main/src/lib/components/tables/table-16.svelte) ## table-17 > A type-safe, accessible table-17 component for building modern UIs. This component is part of the tables collection. ### Core Information - **Component ID**: `table-17` - **Location**: `/src/lib/components/tables/table-17.svelte` - **Type**: UI Component ### Usage ```svelte ### Dependencies Required packages and components: - [`lucide-svelte`](https://github.com/lucide-icons/lucide) Full component implementation: ```svelte <script lang="ts"> import type { User } from '$data/api/data/users.handlers'; import Badge from '$lib/components/ui/badge.svelte'; import Button from '$lib/components/ui/button.svelte'; import Checkbox from '$lib/components/ui/checkbox.svelte'; import { type ColumnDef, type ExpandedState, getCoreRowModel, getExpandedRowModel, type RowSelectionState } from '@tanstack/table-core'; import { fetchUsers } from '$data/api/data/users'; import { createSvelteTable, FlexRender, renderComponent, renderSnippet } from '$lib/components/ui/data-table'; import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from '$lib/components/ui/table'; import { cn } from '$lib/utils'; import ChevronDown from 'lucide-svelte/icons/chevron-down'; import ChevronUp from 'lucide-svelte/icons/chevron-up'; import Info from 'lucide-svelte/icons/info'; import { createRawSnippet, mount, unmount } from 'svelte'; const columns: ColumnDef<User>[] = [ { cell: ({ row }) => { if (!row.getCanExpand()) return; return renderComponent(Button, { 'aria-expanded': row.getIsExpanded(), 'aria-label': row.getIsExpanded() ? `Collapse details for ${row.original.name}` : `Expand details for ${row.original.name}`, children: createRawSnippet(() => { return { render: () => '<!---->', setup: (target) => { const icon = mount(row.getIsExpanded() ? ChevronUp : ChevronDown, { props: { 'aria-hidden': true, class: 'opacity-60', size: 16 }, target: target.parentElement as Element }); return () => unmount(icon); } }; }), class: 'size-7 shadow-none text-muted-foreground', onclick: row.getToggleExpandedHandler(), size: 'icon', variant: 'ghost' }); }, header: () => null, id: 'expander' }, { cell: ({ row }) => renderComponent(Checkbox, { 'aria-label': 'Select row', checked: row.getIsSelected(), onCheckedChange: (value) => row.toggleSelected(!!value) }), header: ({ table }) => renderComponent(Checkbox, { 'aria-label': 'Select all', checked: table.getIsAllPageRowsSelected(), indeterminate: table.getIsSomePageRowsSelected() && !table.getIsAllPageRowsSelected(), onCheckedChange: (value) => table.toggleAllPageRowsSelected(!!value) }), id: 'select' }, { accessorKey: 'name', cell: ({ row }) => { const nameSnippet = createRawSnippet<[string]>((getName) => { const name = getName(); return { render: () => `<div class="font-medium">${name}</div>` }; }); return renderSnippet(nameSnippet, row.getValue('name')); }, header: 'Name' }, { accessorKey: 'email', header: 'Email' }, { accessorKey: 'location', cell: ({ row }) => { const locationSnippet = createRawSnippet<[{ flag: string; location: string }]>((args) => { const { flag, location } = args(); return { render: () => ` <div> <span class="text-lg leading-none">${flag}</span> ${location} </div>` }; }); return renderSnippet(locationSnippet, { flag: row.original.flag, location: row.getValue('location') as string }); }, header: 'Location' }, { accessorKey: 'status', cell: ({ row }) => renderComponent(Badge, { children: createRawSnippet(() => { const status = row.getValue('status') as string; return { render: () => status }; }), class: cn( row.getValue('status') === 'Inactive' && 'bg-muted-foreground/60 text-primary-foreground' ) }), header: 'Status' }, { accessorKey: 'balance', cell: ({ row }) => { return renderSnippet( createRawSnippet((getBalance) => { const balance = getBalance() as string; const formatted = new Intl.NumberFormat('en-US', { currency: 'USD', style: 'currency' }).format(parseFloat(balance)); return { render: () => `<div class="text-right">${formatted}</div>` }; }), row.getValue('balance') ); }, header: () => { const nameSnippet = createRawSnippet(() => { return { render: () => `<div class="text-right">Balance</div>` }; }); return renderSnippet(nameSnippet, {}); } } ]; let rowSelection = $state<RowSelectionState>({}); let expanded = $state<ExpandedState>({}); let data = $state<User[]>([]); $effect(() => { fetchUsers() .then((response) => { data = response.slice(0, 5); }) .catch((err) => { console.error(err); }); }); const table = createSvelteTable<User>({ columns, get data() { return data; }, getCoreRowModel: getCoreRowModel(), getExpandedRowModel: getExpandedRowModel(), getRowCanExpand: (row) => Boolean(row.original.note), onExpandedChange: (updater) => { if (typeof updater === 'function') { expanded = updater(expanded); } else { expanded = updater; } }, onRowSelectionChange: (updater) => { if (typeof updater === 'function') { rowSelection = updater(rowSelection); } else { rowSelection = updater; } }, state: { get expanded() { return expanded; }, get rowSelection() { return rowSelection; } } }); </script> <div> <Table> <TableHeader> {#each table.getHeaderGroups() as headerGroup (headerGroup.id)} <TableRow class="hover:bg-transparent"> {#each headerGroup.headers as header (header.id)} <TableHead> {#if !header.isPlaceholder} <FlexRender content={header.column.columnDef.header} context={header.getContext()} /> {/if} </TableHead> {/each} </TableRow> {/each} </TableHeader> <TableBody> {#each table.getRowModel().rows as row (row.id)} <TableRow data-state={row.getIsSelected() && 'selected'}> {#each row.getVisibleCells() as cell (cell.id)} <TableCell class="whitespace-nowrap [&:has([aria-expanded])]:w-px [&:has([aria-expanded])]:py-0 [&:has([aria-expanded])]:pr-0" > <FlexRender content={cell.column.columnDef.cell} context={cell.getContext()} /> </TableCell> {/each} </TableRow> {#if row.getIsExpanded()} <TableRow> <TableCell colspan={row.getVisibleCells().length}> <div class="flex items-start py-2 text-primary/80"> <span class="me-3 mt-0.5 flex w-7 shrink-0 justify-center" aria-hidden="true"> <Info class="opacity-60" size={16} /> </span> <p class="text-sm">{row.original.note}</p> </div> </TableCell> </TableRow> {/if} {:else} <TableRow> <TableCell colspan={columns.length} class="h-24 text-center">No results.</TableCell> </TableRow> {/each} </TableBody> </Table> <p class="mt-4 text-center text-sm text-muted-foreground"> Expanding sub-row made with <a class="underline hover:text-foreground" href="https://tanstack.com/table" target="_blank" rel="noopener noreferrer" > TanStack Table </a> </p> </div> ``` ### Links - [View Source](https://github.com/max-got/originui-svelte/tree/main/src/lib/components/tables/table-17.svelte) ## table-18 > A type-safe, accessible table-18 component for building modern UIs. This component is part of the tables collection. ### Core Information - **Component ID**: `table-18` - **Location**: `/src/lib/components/tables/table-18.svelte` - **Type**: UI Component ### Usage ```svelte ### Dependencies Required packages and components: - [`lucide-svelte`](https://github.com/lucide-icons/lucide) Full component implementation: ```svelte <script lang="ts"> import type { User } from '$data/api/data/users.handlers'; import Badge from '$lib/components/ui/badge.svelte'; import Button from '$lib/components/ui/button.svelte'; import Checkbox from '$lib/components/ui/checkbox.svelte'; import Label from '$lib/components/ui/label.svelte'; import { type ColumnDef, getCoreRowModel, getPaginationRowModel, getSortedRowModel, type PaginationState, type RowSelectionState, type SortingState } from '@tanstack/table-core'; import { fetchUsers } from '$data/api/data/users'; import { createSvelteTable, FlexRender, renderComponent, renderSnippet } from '$lib/components/ui/data-table'; import { Pagination, PaginationContent, PaginationItem } from '$lib/components/ui/pagination'; import { Select, SelectContent, SelectItem, SelectTrigger } from '$lib/components/ui/select'; import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from '$lib/components/ui/table'; import { cn } from '$lib/utils'; import ChevronDown from 'lucide-svelte/icons/chevron-down'; import ChevronFirst from 'lucide-svelte/icons/chevron-first'; import ChevronLast from 'lucide-svelte/icons/chevron-last'; import ChevronLeft from 'lucide-svelte/icons/chevron-left'; import ChevronRight from 'lucide-svelte/icons/chevron-right'; import ChevronUp from 'lucide-svelte/icons/chevron-up'; import { createRawSnippet } from 'svelte'; const columns: ColumnDef<User>[] = [ { cell: ({ row }) => renderComponent(Checkbox, { 'aria-label': 'Select row', checked: row.getIsSelected(), onCheckedChange: (value) => row.toggleSelected(!!value) }), enableSorting: false, header: ({ table }) => renderComponent(Checkbox, { 'aria-label': 'Select all', checked: table.getIsAllPageRowsSelected(), indeterminate: table.getIsSomePageRowsSelected() && !table.getIsAllPageRowsSelected(), onCheckedChange: (value) => table.toggleAllPageRowsSelected(!!value) }), id: 'select', size: 28 }, { accessorKey: 'name', cell: ({ row }) => { const nameSnippet = createRawSnippet<[string]>((getName) => { const name = getName(); return { render: () => `<div class="font-medium">${name}</div>` }; }); return renderSnippet(nameSnippet, row.getValue('name')); }, header: 'Name', size: 180 }, { accessorKey: 'email', header: 'Email', size: 200 }, { accessorKey: 'location', cell: ({ row }) => { const locationSnippet = createRawSnippet<[{ flag: string; location: string }]>((args) => { const { flag, location } = args(); return { render: () => ` <div> <span class="text-lg leading-none">${flag}</span> ${location} </div>` }; }); return renderSnippet(locationSnippet, { flag: row.original.flag, location: row.getValue('location') as string }); }, header: 'Location', size: 180 }, { accessorKey: 'status', cell: ({ row }) => renderComponent(Badge, { children: createRawSnippet(() => { const status = row.getValue('status') as string; return { render: () => status }; }), class: cn( row.getValue('status') === 'Inactive' && 'bg-muted-foreground/60 text-primary-foreground' ) }), header: 'Status', size: 120 }, { accessorKey: 'balance', cell: ({ row }) => { const amount = parseFloat(row.getValue('balance')); const formatted = new Intl.NumberFormat('en-US', { currency: 'USD', style: 'currency' }).format(amount); return formatted; }, header: 'Balance', size: 120 } ]; const id = $props.id(); let pagination = $state<PaginationState>({ pageIndex: 0, pageSize: 5 }); let sorting = $state<SortingState>([ { desc: false, id: 'name' } ]); let rowSelection = $state<RowSelectionState>({}); let data = $state<User[]>([]); $effect(() => { fetchUsers() .then((response) => { data = [...response]; }) .catch((err) => { console.error(err); }); }); const table = createSvelteTable<User>({ columns, get data() { return data; }, enableSortingRemoval: false, getCoreRowModel: getCoreRowModel(), getPaginationRowModel: getPaginationRowModel(), getSortedRowModel: getSortedRowModel(), onPaginationChange: (updater) => { if (typeof updater === 'function') { pagination = updater(pagination); } else { pagination = updater; } }, onRowSelectionChange: (updater) => { if (typeof updater === 'function') { rowSelection = updater(rowSelection); } else { rowSelection = updater; } }, onSortingChange: (updater) => { if (typeof updater === 'function') { sorting = updater(sorting); } else { sorting = updater; } }, state: { get pagination() { return pagination; }, get rowSelection() { return rowSelection; }, get sorting() { return sorting; } } }); </script> <div class="space-y-4"> <div class="overflow-hidden rounded-md border bg-background"> <Table class="table-fixed"> <TableHeader> {#each table.getHeaderGroups() as headerGroup (headerGroup.id)} <TableRow class="hover:bg-transparent"> {#each headerGroup.headers as header (header.id)} <TableHead style="width: {header.getSize()}px" class="h-11"> {#if !header.isPlaceholder && header.column.getCanSort()} <div class={cn( header.column.getCanSort() && 'flex h-full cursor-pointer select-none items-center justify-between gap-2' )} onclick={header.column.getToggleSortingHandler()} onkeydown={(e) => { // Enhanced keyboard handling for sorting if (header.column.getCanSort() && (e.key === 'Enter' || e.key === ' ')) { e.preventDefault(); header.column.getToggleSortingHandler()?.(e); } }} tabIndex={header.column.getCanSort() ? 0 : undefined} > <FlexRender content={header.column.columnDef.header} context={header.getContext()} /> {#if header.column.getIsSorted() === 'asc'} <ChevronUp class="shrink-0 opacity-60" size={16} aria-hidden="true" /> {:else if header.column.getIsSorted() === 'desc'} <ChevronDown class="shrink-0 opacity-60" size={16} aria-hidden="true" /> {/if} </div> {:else if !header.isPlaceholder && !header.column.getCanSort()} <FlexRender content={header.column.columnDef.header} context={header.getContext()} /> {/if} </TableHead> {/each} </TableRow> {/each} </TableHeader> <TableBody> {#each table.getRowModel().rows as row (row.id)} <TableRow data-state={row.getIsSelected() && 'selected'}> {#each row.getVisibleCells() as cell (cell.id)} <TableCell> <FlexRender content={cell.column.columnDef.cell} context={cell.getContext()} /> </TableCell> {/each} </TableRow> {:else} <TableRow> <TableCell colspan={columns.length} class="h-24 text-center">No results.</TableCell> </TableRow> {/each} </TableBody> </Table> </div> <!-- Pagination --> <div class="flex items-center justify-between gap-8"> <!-- Results per page --> <div class="flex items-center gap-3"> <Label for={id} class="max-sm:sr-only">Rows per page</Label> <Select type="single" value={table.getState().pagination.pageSize.toString()} onValueChange={(value) => { table.setPageSize(Number(value)); }} > <SelectTrigger placeholder="Select number of results" {id} class="w-fit whitespace-nowrap"> {table.getState().pagination.pageSize.toString()} </SelectTrigger> <SelectContent class="[&_*[role=option]>span]:end-2 [&_*[role=option]>span]:start-auto [&_*[role=option]]:pe-8 [&_*[role=option]]:ps-2" > {#each [5, 10, 25, 50] as pageSize (pageSize)} <SelectItem value={pageSize.toString()}> {pageSize} </SelectItem> {/each} </SelectContent> </Select> </div> <!-- Page number information --> <div class="flex grow justify-end whitespace-nowrap text-sm text-muted-foreground"> <p class="whitespace-nowrap text-sm text-muted-foreground" aria-live="polite"> <span class="text-foreground"> {table.getState().pagination.pageIndex * table.getState().pagination.pageSize + 1} - {Math.min( Math.max( table.getState().pagination.pageIndex * table.getState().pagination.pageSize + table.getState().pagination.pageSize, 0 ), table.getRowCount() )} </span> of <span class="text-foreground"> {table.getRowCount().toString()} </span> </p> </div> <!-- Pagination buttons --> <div> <Pagination> <PaginationContent> <!-- First page button --> <PaginationItem> <Button size="icon" variant="outline" class="disabled:pointer-events-none disabled:opacity-50" onclick={() => table.firstPage()} disabled={!table.getCanPreviousPage()} aria-label="Go to first page" > <ChevronFirst size={16} aria-hidden="true" /> </Button> </PaginationItem> <!-- Previous page button --> <PaginationItem> <Button size="icon" variant="outline" class="disabled:pointer-events-none disabled:opacity-50" onclick={() => table.previousPage()} disabled={!table.getCanPreviousPage()} aria-label="Go to previous page" > <ChevronLeft size={16} aria-hidden="true" /> </Button> </PaginationItem> <!-- Next page button --> <PaginationItem> <Button size="icon" variant="outline" class="disabled:pointer-events-none disabled:opacity-50" onclick={() => table.nextPage()} disabled={!table.getCanNextPage()} aria-label="Go to next page" > <ChevronRight size={16} aria-hidden="true" /> </Button> </PaginationItem> <!-- Last page button --> <PaginationItem> <Button size="icon" variant="outline" class="disabled:pointer-events-none disabled:opacity-50" onclick={() => table.lastPage()} disabled={!table.getCanNextPage()} aria-label="Go to last page" > <ChevronLast size={16} aria-hidden="true" /> </Button> </PaginationItem> </PaginationContent> </Pagination> </div> </div> <p class="mt-4 text-center text-sm text-muted-foreground"> Paginated table made with <a class="underline hover:text-foreground" href="https://tanstack.com/table" target="_blank" rel="noopener noreferrer" > TanStack Table </a> </p> </div> ``` ### Links - [View Source](https://github.com/max-got/originui-svelte/tree/main/src/lib/components/tables/table-18.svelte) ## table-19 > A type-safe, accessible table-19 component for building modern UIs. This component is part of the tables collection. ### Core Information - **Component ID**: `table-19` - **Location**: `/src/lib/components/tables/table-19.svelte` - **Type**: UI Component ### Usage ```svelte ### Dependencies Required packages and components: - [`lucide-svelte`](https://github.com/lucide-icons/lucide) Full component implementation: ```svelte <script lang="ts"> import type { User } from '$data/api/data/users.handlers'; import Badge from '$lib/components/ui/badge.svelte'; import Button from '$lib/components/ui/button.svelte'; import Checkbox from '$lib/components/ui/checkbox.svelte'; import { usePagination } from '$lib/hooks/use-pagination.svelte'; import { type ColumnDef, getCoreRowModel, getPaginationRowModel, getSortedRowModel, type PaginationState, type RowSelectionState, type SortingState } from '@tanstack/table-core'; import { fetchUsers } from '$data/api/data/users'; import { createSvelteTable, FlexRender, renderComponent, renderSnippet } from '$lib/components/ui/data-table'; import { Pagination, PaginationContent, PaginationEllipsis, PaginationItem } from '$lib/components/ui/pagination'; import { Select, SelectContent, SelectItem, SelectTrigger // SelectValue } from '$lib/components/ui/select'; import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from '$lib/components/ui/table'; import { cn } from '$lib/utils'; import ChevronDown from 'lucide-svelte/icons/chevron-down'; import ChevronLeft from 'lucide-svelte/icons/chevron-left'; import ChevronRight from 'lucide-svelte/icons/chevron-right'; import ChevronUp from 'lucide-svelte/icons/chevron-up'; import { createRawSnippet } from 'svelte'; const columns: ColumnDef<User>[] = [ { cell: ({ row }) => renderComponent(Checkbox, { 'aria-label': 'Select row', checked: row.getIsSelected(), onCheckedChange: (value) => row.toggleSelected(!!value) }), enableSorting: false, header: ({ table }) => renderComponent(Checkbox, { 'aria-label': 'Select all rows', checked: table.getIsAllPageRowsSelected(), indeterminate: table.getIsSomePageRowsSelected() && !table.getIsAllPageRowsSelected(), onCheckedChange: (value) => table.toggleAllPageRowsSelected(!!value) }), id: 'select', size: 28 }, { accessorKey: 'name', cell: ({ row }) => { const nameSnippet = createRawSnippet<[string]>((getName) => { const name = getName(); return { render: () => `<div class="font-medium">${name}</div>` }; }); return renderSnippet(nameSnippet, row.getValue('name')); }, header: 'Name', size: 180 }, { accessorKey: 'email', header: 'Email', size: 200 }, { accessorKey: 'location', cell: ({ row }) => { const locationSnippet = createRawSnippet<[{ flag: string; location: string }]>((args) => { const { flag, location } = args(); return { render: () => ` <div> <span class="text-lg leading-none">${flag}</span> ${location} </div>` }; }); return renderSnippet(locationSnippet, { flag: row.original.flag, location: row.getValue('location') as string }); }, header: 'Location', size: 180 }, { accessorKey: 'status', cell: ({ row }) => renderComponent(Badge, { children: createRawSnippet(() => { const status = row.getValue('status') as string; return { render: () => status }; }), class: cn( row.getValue('status') === 'Inactive' && 'bg-muted-foreground/60 text-primary-foreground' ) }), header: 'Status', size: 120 }, { accessorKey: 'balance', cell: ({ row }) => { const amount = parseFloat(row.getValue('balance')); const formatted = new Intl.NumberFormat('en-US', { currency: 'USD', style: 'currency' }).format(amount); return formatted; }, header: 'Balance', size: 120 } ]; let pageSize = $state(5); let pagination = $state<PaginationState>({ pageIndex: 0, pageSize: pageSize }); let sorting = $state<SortingState>([ { desc: false, id: 'name' } ]); let rowSelection = $state<RowSelectionState>({}); let data = $state<User[]>([]); $effect(() => { fetchUsers() .then((response) => { data = [...response]; }) .catch((err) => { console.error(err); }); }); const table = createSvelteTable<User>({ columns, get data() { return data; }, enableSortingRemoval: false, getCoreRowModel: getCoreRowModel(), getPaginationRowModel: getPaginationRowModel(), getSortedRowModel: getSortedRowModel(), onPaginationChange: (updater) => { if (typeof updater === 'function') { pagination = updater(pagination); } else { pagination = updater; } }, onRowSelectionChange: (updater) => { if (typeof updater === 'function') { rowSelection = updater(rowSelection); } else { rowSelection = updater; } }, onSortingChange: (updater) => { if (typeof updater === 'function') { sorting = updater(sorting); } else { sorting = updater; } }, state: { get pagination() { return pagination; }, get rowSelection() { return rowSelection; }, get sorting() { return sorting; } } }); const paginated = $derived( usePagination({ currentPage: table.getState().pagination.pageIndex + 1, paginationItemsToDisplay: 5, totalPages: table.getPageCount() }) ); </script> <div class="space-y-4"> <div class="overflow-hidden rounded-md border bg-background"> <Table class="table-fixed"> <TableHeader> {#each table.getHeaderGroups() as headerGroup (headerGroup.id)} <TableRow class="hover:bg-transparent"> {#each headerGroup.headers as header (header.id)} <TableHead style="width: {header.getSize()}px" class="h-11"> {#if !header.isPlaceholder && header.column.getCanSort()} <div class={cn( header.column.getCanSort() && 'flex h-full cursor-pointer select-none items-center justify-between gap-2' )} onclick={header.column.getToggleSortingHandler()} onkeydown={(e) => { // Enhanced keyboard handling for sorting if (header.column.getCanSort() && (e.key === 'Enter' || e.key === ' ')) { e.preventDefault(); header.column.getToggleSortingHandler()?.(e); } }} tabIndex={header.column.getCanSort() ? 0 : undefined} > <FlexRender content={header.column.columnDef.header} context={header.getContext()} /> {#if header.column.getIsSorted() === 'asc'} <ChevronUp class="shrink-0 opacity-60" size={16} aria-hidden="true" /> {:else if header.column.getIsSorted() === 'desc'} <ChevronDown class="shrink-0 opacity-60" size={16} aria-hidden="true" /> {/if} </div> {:else if !header.isPlaceholder && !header.column.getCanSort()} <FlexRender content={header.column.columnDef.header} context={header.getContext()} /> {/if} </TableHead> {/each} </TableRow> {/each} </TableHeader> <TableBody> {#each table.getRowModel().rows as row (row.id)} <TableRow data-state={row.getIsSelected() && 'selected'}> {#each row.getVisibleCells() as cell (cell.id)} <TableCell> <FlexRender content={cell.column.columnDef.cell} context={cell.getContext()} /> </TableCell> {/each} </TableRow> {:else} <TableRow> <TableCell colspan={columns.length} class="h-24 text-center">No results.</TableCell> </TableRow> {/each} </TableBody> </Table> </div> <!-- Pagination --> <div class="flex items-center justify-between gap-3 max-sm:flex-col"> <!-- Page number information --> <p class="flex-1 whitespace-nowrap text-sm text-muted-foreground" aria-live="polite"> Page <span class="text-foreground"> {table.getState().pagination.pageIndex + 1} </span> of <span class="text-foreground">{table.getPageCount()}</span> </p> <!-- Pagination buttons --> <div class="grow"> <Pagination> <PaginationContent> <!-- Previous page button --> <PaginationItem> <Button size="icon" variant="outline" class="disabled:pointer-events-none disabled:opacity-50" onclick={() => table.previousPage()} disabled={!table.getCanPreviousPage()} aria-label="Go to previous page" > <ChevronLeft size={16} aria-hidden="true" /> </Button> </PaginationItem> <!-- Left ellipsis (...) --> {#if paginated.showLeftEllipsis} <PaginationItem> <PaginationEllipsis /> </PaginationItem> {/if} <!-- Page number buttons --> {#each paginated.pages as page (page)} {@const isActive = page === table.getState().pagination.pageIndex + 1} <PaginationItem> <Button size="icon" variant={`${isActive ? 'outline' : 'ghost'}`} onclick={() => table.setPageIndex(page - 1)} aria-current={isActive ? 'page' : undefined} > {page} </Button> </PaginationItem> {:else} <p>empty</p> {/each} <!-- Right ellipsis (...) --> {#if paginated.showRightEllipsis} <PaginationItem> <PaginationEllipsis /> </PaginationItem> {/if} <!-- Next page button --> <PaginationItem> <Button size="icon" variant="outline" class="disabled:pointer-events-none disabled:opacity-50" onclick={() => table.nextPage()} disabled={!table.getCanNextPage()} aria-label="Go to next page" > <ChevronRight size={16} aria-hidden="true" /> </Button> </PaginationItem> </PaginationContent> </Pagination> </div> <!-- Results per page --> <div class="flex flex-1 justify-end"> <Select type="single" value={table.getState().pagination.pageSize.toString()} onValueChange={(value) => { table.setPageSize(Number(value)); }} > <SelectTrigger id="results-per-page" class="w-fit whitespace-nowrap" placeholder="Select number of results" aria-label="Results per page" > {table.getState().pagination.pageSize.toString()} / page </SelectTrigger> <SelectContent> {#each [5, 10, 25, 50] as pageSize (pageSize)} <SelectItem value={pageSize.toString()}> {pageSize} / page </SelectItem> {/each} </SelectContent> </Select> </div> </div> <p class="mt-4 text-center text-sm text-muted-foreground"> Numeric pagination made with <a class="underline hover:text-foreground" href="https://tanstack.com/table" target="_blank" rel="noopener noreferrer" > TanStack Table </a> </p> </div> ``` ### Links - [View Source](https://github.com/max-got/originui-svelte/tree/main/src/lib/components/tables/table-19.svelte) ## table-20 > A type-safe, accessible table-20 component for building modern UIs. This component is part of the tables collection. ### Core Information - **Component ID**: `table-20` - **Location**: `/src/lib/components/tables/table-20.svelte` - **Type**: UI Component ### Usage ```svelte ### Dependencies Required packages and components: - [`lucide-svelte`](https://github.com/lucide-icons/lucide) Full component implementation: ```svelte <script lang="ts"> import type { User } from '$data/api/data/users.handlers'; import Badge from '$lib/components/ui/badge.svelte'; import Button from '$lib/components/ui/button.svelte'; import Checkbox from '$lib/components/ui/checkbox.svelte'; import Input from '$lib/components/ui/input.svelte'; import Label from '$lib/components/ui/label.svelte'; import { type ColumnDef, type ColumnFiltersState, type FilterFn, getCoreRowModel, getFacetedUniqueValues, getFilteredRowModel, getPaginationRowModel, getSortedRowModel, type PaginationState, type RowSelectionState, type SortingState, type VisibilityState } from '@tanstack/table-core'; import { AlertDialogAction, AlertDialogCancel, AlertDialogContent, AlertDialogDescription, AlertDialogFooter, AlertDialogHeader, AlertDialogRoot, AlertDialogTitle, AlertDialogTrigger } from '$lib/components/ui/alert-dialog'; import { createSvelteTable, FlexRender, renderComponent, renderSnippet } from '$lib/components/ui/data-table'; import { DropdownMenu, DropdownMenuCheckboxItem, DropdownMenuContent, DropdownMenuGroup, DropdownMenuItem, DropdownMenuLabel, DropdownMenuSeparator, DropdownMenuShortcut, DropdownMenuSub, DropdownMenuSubContent, DropdownMenuSubTrigger, DropdownMenuTrigger } from '$lib/components/ui/dropdowns'; import { Pagination, PaginationContent, PaginationItem } from '$lib/components/ui/pagination'; import { Popover, PopoverContent, PopoverTrigger } from '$lib/components/ui/popover'; import { Select, SelectContent, SelectItem, SelectTrigger } from '$lib/components/ui/select'; import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from '$lib/components/ui/table'; import { cn } from '$lib/utils'; import ChevronDown from 'lucide-svelte/icons/chevron-down'; import ChevronFirst from 'lucide-svelte/icons/chevron-first'; import ChevronLast from 'lucide-svelte/icons/chevron-last'; import ChevronLeft from 'lucide-svelte/icons/chevron-left'; import ChevronRight from 'lucide-svelte/icons/chevron-right'; import ChevronUp from 'lucide-svelte/icons/chevron-up'; import CircleAlert from 'lucide-svelte/icons/circle-alert'; import CircleX from 'lucide-svelte/icons/circle-x'; import Columns3 from 'lucide-svelte/icons/columns-3'; import Ellipsis from 'lucide-svelte/icons/ellipsis'; import Filter from 'lucide-svelte/icons/filter'; import ListFilter from 'lucide-svelte/icons/list-filter'; import Plus from 'lucide-svelte/icons/plus'; import Trash from 'lucide-svelte/icons/trash'; import { createRawSnippet } from 'svelte'; // Custom filter function for multi-column searching const multiColumnFilterFn: FilterFn<User> = (row, _, filterValue) => { const searchableRowContent = `${row.original.name} ${row.original.email}`.toLowerCase(); const searchTerm = (filterValue ?? '').toLowerCase(); return searchableRowContent.includes(searchTerm); }; const statusFilterFn: FilterFn<User> = (row, columnId, filterValue: string[]) => { if (!filterValue?.length) return true; const status = row.getValue(columnId) as string; return filterValue.includes(status); }; const columns: ColumnDef<User>[] = [ { cell: ({ row }) => renderComponent(Checkbox, { 'aria-label': 'Select row', checked: row.getIsSelected(), onCheckedChange: (value) => row.toggleSelected(!!value) }), enableHiding: false, enableSorting: false, header: ({ table }) => renderComponent(Checkbox, { 'aria-label': 'Select all', checked: table.getIsAllPageRowsSelected(), indeterminate: table.getIsSomePageRowsSelected() && !table.getIsAllPageRowsSelected(), onCheckedChange: (value) => table.toggleAllPageRowsSelected(!!value) }), id: 'select', size: 28 }, { accessorKey: 'name', cell: ({ row }) => { const nameSnippet = createRawSnippet<[string]>((getName) => { const name = getName(); return { render: () => `<div class="font-medium">${name}</div>` }; }); return renderSnippet(nameSnippet, row.getValue('name')); }, enableHiding: false, filterFn: multiColumnFilterFn, header: 'Name', size: 180 }, { accessorKey: 'email', header: 'Email', size: 220 }, { accessorKey: 'location', cell: ({ row }) => { const locationSnippet = createRawSnippet<[{ flag: string; location: string }]>((args) => { const { flag, location } = args(); return { render: () => ` <div> <span class="text-lg leading-none">${flag}</span> ${location} </div>` }; }); return renderSnippet(locationSnippet, { flag: row.original.flag, location: row.getValue('location') as string }); }, header: 'Location', size: 180 }, { accessorKey: 'status', cell: ({ row }) => renderComponent(Badge, { children: createRawSnippet(() => { const status = row.getValue('status') as string; return { render: () => status }; }), class: cn( row.getValue('status') === 'Inactive' && 'bg-muted-foreground/60 text-primary-foreground' ) }), filterFn: statusFilterFn, header: 'Status', size: 100 }, { accessorKey: 'performance', header: 'Performance' }, { accessorKey: 'balance', cell: ({ row }) => { const amount = parseFloat(row.getValue('balance')); const formatted = new Intl.NumberFormat('en-US', { currency: 'USD', style: 'currency' }).format(amount); return formatted; }, header: 'Balance', size: 120 }, { cell: ({ row }) => renderSnippet(RowActions, { row }), enableHiding: false, header: () => renderSnippet( createRawSnippet(() => { return { render: () => `<span class="sr-only">Actions</span>` }; }), {} ), id: 'actions', size: 60 } ]; let columnFilters = $state<ColumnFiltersState>([]); let columnVisibility = $state<VisibilityState>({}); let pagination = $state<PaginationState>({ pageIndex: 0, pageSize: 10 }); let rowSelection = $state<RowSelectionState>({}); let sorting = $state<SortingState>([ { desc: false, id: 'name' } ]); let data = $state<User[]>([]); $effect(() => { fetch('https://res.cloudinary.com/dlzlfasou/raw/upload/users-01_fertyx.json') .then((res) => res.json()) .then((response) => { data = response; }) .catch((err) => { console.error(err); }); }); function handleDeleteRows() { const selectedRows = table.getSelectedRowModel().rows; data = data.filter((item) => !selectedRows.some((row) => row.original.id === item.id)); table.resetRowSelection(); } const table = createSvelteTable<User>({ columns, get data() { return data; }, enableSortingRemoval: false, getCoreRowModel: getCoreRowModel(), getFacetedUniqueValues: getFacetedUniqueValues(), getFilteredRowModel: getFilteredRowModel(), getPaginationRowModel: getPaginationRowModel(), getSortedRowModel: getSortedRowModel(), onColumnFiltersChange: (updater) => { if (typeof updater === 'function') { columnFilters = updater(columnFilters); } else { columnFilters = updater; } }, onColumnVisibilityChange: (updater) => { if (typeof updater === 'function') { columnVisibility = updater(columnVisibility); } else { columnVisibility = updater; } }, onPaginationChange: (updater) => { if (typeof updater === 'function') { pagination = updater(pagination); } else { pagination = updater; } }, onRowSelectionChange: (updater) => { if (typeof updater === 'function') { rowSelection = updater(rowSelection); } else { rowSelection = updater; } }, onSortingChange: (updater) => { if (typeof updater === 'function') { sorting = updater(sorting); } else { sorting = updater; } }, state: { get columnFilters() { return columnFilters; }, get columnVisibility() { return columnVisibility; }, get pagination() { return pagination; }, get rowSelection() { return rowSelection; }, get sorting() { return sorting; } } }); const uniqueStatusValues = $derived.by(() => { const statusColumn = table.getColumn('status'); if (!statusColumn) return []; return Array.from(statusColumn.getFacetedUniqueValues().keys()).sort(); }); const statusCounts = $derived.by(() => { const statusColumn = table.getColumn('status'); if (!statusColumn) return new Map(); return statusColumn.getFacetedUniqueValues(); }); const selectedStatuses = $derived.by(() => { const filterValue = table.getColumn('status')?.getFilterValue() as string[]; return filterValue ?? []; }); function handleStatusChange(checked: boolean, value: string) { const filterValue = table.getColumn('status')?.getFilterValue() as string[]; const newFilterValue = filterValue ? [...filterValue] : []; if (checked) { newFilterValue.push(value); } else { const index = newFilterValue.indexOf(value); if (index > -1) { newFilterValue.splice(index, 1); } } table.getColumn('status')?.setFilterValue(newFilterValue.length ? newFilterValue : undefined); } </script> <div class="space-y-4"> <!-- Filters --> <div class="flex flex-wrap items-center justify-between gap-3"> <div class="flex items-center gap-3"> <!-- Filter by name or email --> <div class="relative"> <Input class={cn( 'peer min-w-60 ps-9', Boolean(table.getColumn('name')?.getFilterValue()) && 'pe-9' )} value={(table.getColumn('name')?.getFilterValue() ?? '') as string} oninput={(e) => table.getColumn('name')?.setFilterValue(e.currentTarget.value)} placeholder="Filter by name or email..." type="text" aria-label="Filter by name or email" /> <div class="pointer-events-none absolute inset-y-0 start-0 flex items-center justify-center ps-3 text-muted-foreground/80 peer-disabled:opacity-50" > <ListFilter size={16} aria-hidden="true" /> </div> {#if Boolean(table.getColumn('name')?.getFilterValue())} <button class="absolute inset-y-0 end-0 flex h-full w-9 items-center justify-center rounded-e-md text-muted-foreground/80 outline-none transition-[color,box-shadow] hover:text-foreground focus:z-10 focus-visible:border-ring focus-visible:ring-[3px] focus-visible:ring-ring/50 disabled:pointer-events-none disabled:cursor-not-allowed disabled:opacity-50" aria-label="Clear filter" onclick={() => { table.getColumn('name')?.setFilterValue(''); }} > <CircleX size={16} aria-hidden="true" /> </button> {/if} </div> <!-- Filter by status --> <Popover> <PopoverTrigger> {#snippet child({ props })} <Button variant="outline" {...props}> <Filter class="-ms-1 opacity-60" size={16} aria-hidden="true" /> Status {#if selectedStatuses.length > 0} <span class="-me-1 inline-flex h-5 max-h-full items-center rounded border bg-background px-1 font-[inherit] text-[0.625rem] font-medium text-muted-foreground/70" > {selectedStatuses.length} </span> {/if} </Button> {/snippet} </PopoverTrigger> <PopoverContent class="w-auto min-w-36 p-3" align="start"> <div class="space-y-3"> <div class="text-xs font-medium text-muted-foreground">Filters</div> <div class="space-y-3"> {#each uniqueStatusValues as value (value)} <div class="flex items-center gap-2"> <Checkbox checked={selectedStatuses.includes(value)} onCheckedChange={(checked: boolean) => handleStatusChange(checked, value)} /> <Label class="flex grow justify-between gap-2 font-normal"> {value} <span class="ms-2 text-xs text-muted-foreground"> {statusCounts.get(value)} </span> </Label> </div> {/each} </div> </div> </PopoverContent> </Popover> <!-- Toggle columns visibility --> <DropdownMenu> <DropdownMenuTrigger> {#snippet child({ props })} <Button variant="outline" {...props}> <Columns3 class="-ms-1 opacity-60" size={16} aria-hidden="true" /> View </Button> {/snippet} </DropdownMenuTrigger> <DropdownMenuContent align="end"> <DropdownMenuLabel>Toggle columns</DropdownMenuLabel> {#each table .getAllColumns() .filter((column) => column.getCanHide()) as column (column.id)} <DropdownMenuCheckboxItem class="capitalize" checked={column.getIsVisible()} onCheckedChange={(value) => column.toggleVisibility(!!value)} > {column.id} </DropdownMenuCheckboxItem> {/each} </DropdownMenuContent> </DropdownMenu> </div> <div class="flex items-center gap-3"> <!-- Delete button --> {#if table.getSelectedRowModel().rows.length > 0} <AlertDialogRoot> <AlertDialogTrigger> {#snippet child({ props })} <Button class="ml-auto" variant="outline" {...props}> <Trash class="-ms-1 opacity-60" size={16} aria-hidden="true" /> Delete <span class="-me-1 inline-flex h-5 max-h-full items-center rounded border bg-background px-1 font-[inherit] text-[0.625rem] font-medium text-muted-foreground/70" > {table.getSelectedRowModel().rows.length} </span> </Button> {/snippet} </AlertDialogTrigger> <AlertDialogContent> <div class="flex flex-col gap-2 max-sm:items-center sm:flex-row sm:gap-4"> <div class="flex size-9 shrink-0 items-center justify-center rounded-full border" aria-hidden="true" > <CircleAlert class="opacity-80" size={16} /> </div> <AlertDialogHeader> <AlertDialogTitle>Are you absolutely sure?</AlertDialogTitle> <AlertDialogDescription> This action cannot be undone. This will permanently delete {table.getSelectedRowModel().rows.length} selected {table.getSelectedRowModel().rows.length === 1 ? 'row' : 'rows'}. </AlertDialogDescription> </AlertDialogHeader> </div> <AlertDialogFooter> <AlertDialogCancel>Cancel</AlertDialogCancel> <AlertDialogAction onclick={handleDeleteRows}>Delete</AlertDialogAction> </AlertDialogFooter> </AlertDialogContent> </AlertDialogRoot> {/if} <!-- Add user button --> <Button class="ml-auto" variant="outline"> <Plus class="-ms-1 opacity-60" size={16} aria-hidden="true" /> Add user </Button> </div> </div> <!-- Table --> <div class="overflow-hidden rounded-md border bg-background"> <Table class="table-fixed"> <TableHeader> {#each table.getHeaderGroups() as headerGroup (headerGroup.id)} <TableRow class="hover:bg-transparent"> {#each headerGroup.headers as header (header.id)} <TableHead style="width: {header.getSize()}px" class="h-11"> {#if !header.isPlaceholder && header.column.getCanSort()} <div class={cn( header.column.getCanSort() && 'flex h-full cursor-pointer select-none items-center justify-between gap-2' )} onclick={header.column.getToggleSortingHandler()} onkeydown={(e) => { if (header.column.getCanSort() && (e.key === 'Enter' || e.key === ' ')) { e.preventDefault(); header.column.getToggleSortingHandler()?.(e); } }} tabindex={header.column.getCanSort() ? 0 : undefined} > <FlexRender content={header.column.columnDef.header} context={header.getContext()} /> {#if header.column.getIsSorted() === 'asc'} <ChevronUp class="shrink-0 opacity-60" size={16} aria-hidden="true" /> {:else if header.column.getIsSorted() === 'desc'} <ChevronDown class="shrink-0 opacity-60" size={16} aria-hidden="true" /> {/if} </div> {:else if !header.isPlaceholder && !header.column.getCanSort()} <FlexRender content={header.column.columnDef.header} context={header.getContext()} /> {/if} </TableHead> {/each} </TableRow> {/each} </TableHeader> <TableBody> {#if table.getRowModel().rows?.length} {#each table.getRowModel().rows as row (row.id)} <TableRow data-state={row.getIsSelected() && 'selected'}> {#each row.getVisibleCells() as cell (cell.id)} <TableCell class="last:py-0"> <FlexRender content={cell.column.columnDef.cell} context={cell.getContext()} /> </TableCell> {/each} </TableRow> {:else} <TableRow> <TableCell colspan={columns.length} class="h-24 text-center">No results.</TableCell> </TableRow> {/each} {/if} </TableBody> </Table> </div> <!-- Pagination --> <div class="flex items-center justify-between gap-8"> <!-- Results per page --> <div class="flex items-center gap-3"> <Label class="max-sm:sr-only">Rows per page</Label> <Select type="single" value={table.getState().pagination.pageSize.toString()} onValueChange={(value) => { table.setPageSize(Number(value)); }} > <SelectTrigger class="w-fit whitespace-nowrap"> {table.getState().pagination.pageSize.toString() ?? 'Select number of results'} </SelectTrigger> <SelectContent class="[&_*[role=option]>span]:end-2 [&_*[role=option]>span]:start-auto [&_*[role=option]]:pe-8 [&_*[role=option]]:ps-2" > {#each [5, 10, 25, 50] as pageSize (pageSize)} <SelectItem value={pageSize.toString()}> {pageSize} </SelectItem> {/each} </SelectContent> </Select> </div> <!-- Page number information --> <div class="flex grow justify-end whitespace-nowrap text-sm text-muted-foreground"> <p class="whitespace-nowrap text-sm text-muted-foreground" aria-live="polite"> <span class="text-foreground"> {table.getState().pagination.pageIndex * table.getState().pagination.pageSize + 1}-{Math.min( Math.max( table.getState().pagination.pageIndex * table.getState().pagination.pageSize + table.getState().pagination.pageSize, 0 ), table.getRowCount() )} </span> of <span class="text-foreground"> {table.getRowCount().toString()} </span> </p> </div> <!-- Pagination buttons --> <div> <Pagination> <PaginationContent> <!-- First page button --> <PaginationItem> <Button size="icon" variant="outline" class="disabled:pointer-events-none disabled:opacity-50" onclick={() => table.firstPage()} disabled={!table.getCanPreviousPage()} aria-label="Go to first page" > <ChevronFirst size={16} aria-hidden="true" /> </Button> </PaginationItem> <!-- Previous page button --> <PaginationItem> <Button size="icon" variant="outline" class="disabled:pointer-events-none disabled:opacity-50" onclick={() => table.previousPage()} disabled={!table.getCanPreviousPage()} aria-label="Go to previous page" > <ChevronLeft size={16} aria-hidden="true" /> </Button> </PaginationItem> <!-- Next page button --> <PaginationItem> <Button size="icon" variant="outline" class="disabled:pointer-events-none disabled:opacity-50" onclick={() => table.nextPage()} disabled={!table.getCanNextPage()} aria-label="Go to next page" > <ChevronRight size={16} aria-hidden="true" /> </Button> </PaginationItem> <!-- Last page button --> <PaginationItem> <Button size="icon" variant="outline" class="disabled:pointer-events-none disabled:opacity-50" onclick={() => table.lastPage()} disabled={!table.getCanNextPage()} aria-label="Go to last page" > <ChevronLast size={16} aria-hidden="true" /> </Button> </PaginationItem> </PaginationContent> </Pagination> </div> </div> <p class="mt-4 text-center text-sm text-muted-foreground"> Example of a more complex table made with <a class="underline hover:text-foreground" href="https://tanstack.com/table" target="_blank" rel="noopener noreferrer" > TanStack Table </a> </p> </div> {#snippet RowActions()} <DropdownMenu> <DropdownMenuTrigger> {#snippet child({ props })} <div class="flex justify-end"> <Button size="icon" variant="ghost" class="shadow-none" aria-label="Edit item" {...props}> <Ellipsis size={16} aria-hidden="true" /> </Button> </div> {/snippet} </DropdownMenuTrigger> <DropdownMenuContent align="end"> <DropdownMenuGroup> <DropdownMenuItem> <span>Edit</span> <DropdownMenuShortcut>⌘E</DropdownMenuShortcut> </DropdownMenuItem> <DropdownMenuItem> <span>Duplicate</span> <DropdownMenuShortcut>⌘D</DropdownMenuShortcut> </DropdownMenuItem> </DropdownMenuGroup> <DropdownMenuSeparator /> <DropdownMenuGroup> <DropdownMenuItem> <span>Archive</span> <DropdownMenuShortcut>⌘A</DropdownMenuShortcut> </DropdownMenuItem> <DropdownMenuSub> <DropdownMenuSubTrigger>More</DropdownMenuSubTrigger> <DropdownMenuSubContent> <DropdownMenuItem>Move to project</DropdownMenuItem> <DropdownMenuItem>Move to folder</DropdownMenuItem> <DropdownMenuSeparator /> <DropdownMenuItem>Advanced options</DropdownMenuItem> </DropdownMenuSubContent> </DropdownMenuSub> </DropdownMenuGroup> <DropdownMenuSeparator /> <DropdownMenuGroup> <DropdownMenuItem> <span>Share</span> <DropdownMenuShortcut>⌘S</DropdownMenuShortcut> </DropdownMenuItem> <DropdownMenuItem> <span>Add to favorites</span> <DropdownMenuShortcut>⌘F</DropdownMenuShortcut> </DropdownMenuItem> </DropdownMenuGroup> <DropdownMenuSeparator /> <DropdownMenuItem class="text-destructive focus:text-destructive"> <span>Delete</span> <DropdownMenuShortcut>⌘⌫</DropdownMenuShortcut> </DropdownMenuItem> </DropdownMenuContent> </DropdownMenu> {/snippet} ``` ### Links - [View Source](https://github.com/max-got/originui-svelte/tree/main/src/lib/components/tables/table-20.svelte)