@hashrytech/quick-components-kit 0.16.7 → 0.17.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +12 -0
- package/dist/actions/disable-scroll.js +7 -2
- package/dist/components/button/Button.svelte +2 -2
- package/dist/components/checkbox/Checkbox.svelte +19 -17
- package/dist/components/checkbox/Checkbox.svelte.d.ts +3 -1
- package/dist/components/drawer/Drawer.svelte +1 -1
- package/dist/components/drawer/index.d.ts +13 -0
- package/dist/components/hamburger-menu/HamburgerMenu.svelte +4 -4
- package/dist/components/link-button/LinkButton.svelte +1 -1
- package/dist/components/overlay/Overlay.svelte +3 -2
- package/dist/components/radio/Radio.svelte +5 -3
- package/dist/components/tab-navigation/TabNavigation.svelte +1 -1
- package/dist/components/table/Table.svelte +226 -0
- package/dist/components/table/Table.svelte.d.ts +143 -0
- package/dist/components/table/TableTd.svelte +19 -0
- package/dist/components/table/TableTd.svelte.d.ts +9 -0
- package/dist/components/table/TableTh.svelte +19 -0
- package/dist/components/table/TableTh.svelte.d.ts +9 -0
- package/dist/components/table/index.d.ts +3 -0
- package/dist/components/table/index.js +3 -0
- package/dist/components/text-input/TextInput.svelte +3 -3
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,17 @@
|
|
|
1
1
|
# @hashrytech/quick-components-kit
|
|
2
2
|
|
|
3
|
+
## 0.17.0
|
|
4
|
+
|
|
5
|
+
### Minor Changes
|
|
6
|
+
|
|
7
|
+
- refactor: Updating theme variables for each component and adding table component
|
|
8
|
+
|
|
9
|
+
## 0.16.8
|
|
10
|
+
|
|
11
|
+
### Patch Changes
|
|
12
|
+
|
|
13
|
+
- fix: Fix for scroll jump after disable-scroll ends
|
|
14
|
+
|
|
3
15
|
## 0.16.7
|
|
4
16
|
|
|
5
17
|
### Patch Changes
|
|
@@ -23,12 +23,17 @@ export function disableScroll(node, enabled = true) {
|
|
|
23
23
|
document.body.style.width = '100%';
|
|
24
24
|
}
|
|
25
25
|
function removeLock() {
|
|
26
|
+
const originalScrollBehavior = document.documentElement.style.scrollBehavior;
|
|
26
27
|
document.body.style.position = originalBodyPosition;
|
|
27
28
|
document.body.style.width = originalBodyWidth;
|
|
28
29
|
document.body.style.top = originalTop;
|
|
29
30
|
document.documentElement.style.overflowY = originalOverflow;
|
|
30
|
-
|
|
31
|
-
window.scrollTo(
|
|
31
|
+
document.documentElement.style.scrollBehavior = 'auto';
|
|
32
|
+
window.scrollTo(0, scrollTop);
|
|
33
|
+
// Restore scroll behavior without causing a jump
|
|
34
|
+
requestAnimationFrame(() => {
|
|
35
|
+
document.documentElement.style.scrollBehavior = originalScrollBehavior;
|
|
36
|
+
});
|
|
32
37
|
}
|
|
33
38
|
if (node && enabled)
|
|
34
39
|
applyLock();
|
|
@@ -20,8 +20,8 @@
|
|
|
20
20
|
|
|
21
21
|
</script>
|
|
22
22
|
|
|
23
|
-
<button {disabled} class={twMerge("flex flex-row items-center gap-2 px-4 py-2 bg-button
|
|
24
|
-
"disabled:bg-button
|
|
23
|
+
<button {disabled} class={twMerge("flex flex-row items-center gap-2 px-4 py-2 bg-primary-button hover:bg-primary-button-hover rounded-primary w-fit cursor-pointer text-white focus:outline-focus-primary",
|
|
24
|
+
"disabled:bg-primary-button/60 disabled:cursor-default", props.class)}
|
|
25
25
|
{onclick}>
|
|
26
26
|
{#if icon}<span class="w-10">{@render icon()}</span>{/if}
|
|
27
27
|
{#if loadingIcon}<span class="w-10">{@render loadingIcon()}</span>{/if}
|
|
@@ -4,26 +4,28 @@
|
|
|
4
4
|
import {twMerge} from 'tailwind-merge';
|
|
5
5
|
|
|
6
6
|
export type CheckBoxProps = {
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
7
|
+
id: string;
|
|
8
|
+
name?: string;
|
|
9
|
+
label?: string;
|
|
10
|
+
disabled?: boolean;
|
|
11
|
+
checked?: boolean;
|
|
12
|
+
value?: any;
|
|
13
|
+
group?: any[];
|
|
14
|
+
labelPosition?: "left" | "right";
|
|
15
|
+
size?: "sm" | "md" | "lg";
|
|
16
|
+
children?: Snippet;
|
|
17
|
+
icon?: Snippet;
|
|
18
|
+
onclick?: (event: Event) => void;
|
|
19
|
+
onchange?: (event: Event) => void;
|
|
20
|
+
labelClass?: ClassNameValue;
|
|
21
|
+
class?: ClassNameValue;
|
|
20
22
|
};
|
|
21
23
|
|
|
22
24
|
</script>
|
|
23
25
|
|
|
24
26
|
<script lang="ts">
|
|
25
27
|
|
|
26
|
-
let {id, name, label="", labelPosition="right", checked=$bindable(
|
|
28
|
+
let {id, name, label="", labelPosition="right", checked=$bindable(false), value, group=$bindable(), size="md", disabled=$bindable(false), onclick, onchange, labelClass, ...props}: CheckBoxProps = $props();
|
|
27
29
|
|
|
28
30
|
const sizeMap = {
|
|
29
31
|
sm: 'w-4 h-4',
|
|
@@ -33,9 +35,9 @@
|
|
|
33
35
|
|
|
34
36
|
</script>
|
|
35
37
|
|
|
36
|
-
<label for={id} class="
|
|
38
|
+
<label for={id} class="flex items-center gap-2 cursor-pointer select-none">
|
|
37
39
|
{#if labelPosition === "left"}<span class={twMerge("text-sm", labelClass)}>{label}</span>{/if}
|
|
38
|
-
<input {id} name={name ? name : id} type="checkbox" bind:checked
|
|
39
|
-
sizeMap[size], props.class)} />
|
|
40
|
+
<input {id} name={name ? name : id} type="checkbox" bind:checked {group} {disabled} {value} class={twMerge("rounded focus:ring-2 ring-primary-focus text-primary-input-accent accent-primary-input-accent cursor-pointer",
|
|
41
|
+
checked === true ? "border-primary-input-accent" : "border-primary-input-border", sizeMap[size], props.class)} {onclick} {onchange} />
|
|
40
42
|
{#if labelPosition === "right"}<span class={twMerge("text-sm", labelClass)}>{label}</span>{/if}
|
|
41
43
|
</label>
|
|
@@ -6,12 +6,14 @@ export type CheckBoxProps = {
|
|
|
6
6
|
label?: string;
|
|
7
7
|
disabled?: boolean;
|
|
8
8
|
checked?: boolean;
|
|
9
|
+
value?: any;
|
|
9
10
|
group?: any[];
|
|
10
11
|
labelPosition?: "left" | "right";
|
|
11
12
|
size?: "sm" | "md" | "lg";
|
|
12
13
|
children?: Snippet;
|
|
13
14
|
icon?: Snippet;
|
|
14
|
-
onclick?: (event:
|
|
15
|
+
onclick?: (event: Event) => void;
|
|
16
|
+
onchange?: (event: Event) => void;
|
|
15
17
|
labelClass?: ClassNameValue;
|
|
16
18
|
class?: ClassNameValue;
|
|
17
19
|
};
|
|
@@ -103,7 +103,7 @@ The `<Overlay>` component is used to darken the background and optionally block
|
|
|
103
103
|
</script>
|
|
104
104
|
|
|
105
105
|
{#if open}
|
|
106
|
-
<div class="">
|
|
106
|
+
<div class="fixed inset-0">
|
|
107
107
|
<Overlay {transitionDuration} {disableBodyScroll} class={overlayClasses} onclick={() => open = false} />
|
|
108
108
|
<div role="dialog" aria-modal="true" aria-label={ariaLabel} tabindex="{open ? 0 : -1}" aria-hidden="{!open}"
|
|
109
109
|
class={twMerge("fixed flex flex-col items-center gap-2 bg-white outline-0 focus:outline-0 active:outline-focus-primary focus:outline-focus-primary overflow-y-auto z-50", postionClasses[position], props.class)}
|
|
@@ -1 +1,14 @@
|
|
|
1
|
+
import type { Component } from 'svelte';
|
|
1
2
|
export { default as Drawer } from './Drawer.svelte';
|
|
3
|
+
export type DrawerComponent = Component<{
|
|
4
|
+
open?: boolean;
|
|
5
|
+
escapeKeyClose?: boolean;
|
|
6
|
+
disableBodyScroll?: boolean;
|
|
7
|
+
ariaLabel?: string;
|
|
8
|
+
transitionDuration?: number;
|
|
9
|
+
transitionDistance?: number;
|
|
10
|
+
position?: 'left' | 'right' | 'top' | 'bottom';
|
|
11
|
+
overlayClasses?: string;
|
|
12
|
+
class?: string;
|
|
13
|
+
children?: unknown;
|
|
14
|
+
}>;
|
|
@@ -20,15 +20,15 @@
|
|
|
20
20
|
|
|
21
21
|
</script>
|
|
22
22
|
|
|
23
|
-
<button aria-label={ariaLabel} class={twMerge("px-1 py-0.5 rounded focus:outline-none focus:ring-2 focus:ring-focus
|
|
23
|
+
<button aria-label={ariaLabel} class={twMerge("px-1 py-0.5 rounded focus:outline-none focus:ring-2 focus:ring-primary-focus cursor-pointer w-fit", props.class)} {onclick}>
|
|
24
24
|
<div class={twMerge("flex flex-col items-center justify-center size-7 transition-all gap-2", linesParentClasses)}>
|
|
25
25
|
<!-- Top bar -->
|
|
26
|
-
<span class={twMerge("h-0.5 w-7 bg-button
|
|
26
|
+
<span class={twMerge("h-0.5 w-7 bg-primary-button transition-transform duration-300 origin-center", open && useCloseBtn ? "rotate-45 translate-y-2.5" : "", linesClasses)}></span>
|
|
27
27
|
|
|
28
28
|
<!-- Middle bar -->
|
|
29
|
-
<span class={twMerge("h-0.5 w-7 bg-button
|
|
29
|
+
<span class={twMerge("h-0.5 w-7 bg-primary-button transition-opacity duration-300", open && useCloseBtn ? "opacity-0" : "opacity-100", linesClasses)}></span>
|
|
30
30
|
|
|
31
31
|
<!-- Bottom bar -->
|
|
32
|
-
<span class={twMerge("h-0.5 w-7 bg-button
|
|
32
|
+
<span class={twMerge("h-0.5 w-7 bg-primary-button transition-transform duration-300 origin-center", open && useCloseBtn ? "-rotate-45 -translate-y-2.5" : "", linesClasses)}></span>
|
|
33
33
|
</div>
|
|
34
34
|
</button>
|
|
@@ -21,7 +21,7 @@
|
|
|
21
21
|
</script>
|
|
22
22
|
|
|
23
23
|
<a {href} data-sveltekit-reload={reload} data-sveltekit-preload-data={preload ? 'hover' : false}
|
|
24
|
-
class={twMerge("flex flex-row items-center gap-2 px-4 py-2 bg-button
|
|
24
|
+
class={twMerge("flex flex-row items-center gap-2 px-4 py-2 bg-primary-button hover:bg-primary-button-hover rounded-primary w-fit cursor-pointer text-white focus:outline-focus-primary", props.class)}>
|
|
25
25
|
{#if icon}<span class="w-10">{@render icon()}</span>{/if}
|
|
26
26
|
{@render children?.()}
|
|
27
27
|
</a>
|
|
@@ -4,6 +4,7 @@
|
|
|
4
4
|
import { fade } from 'svelte/transition';
|
|
5
5
|
import {twMerge} from 'tailwind-merge';
|
|
6
6
|
import { disableScroll } from '../../actions/disable-scroll.js';
|
|
7
|
+
import { config } from '../../configs/config.js';
|
|
7
8
|
|
|
8
9
|
export type OverlayProps = {
|
|
9
10
|
disableBodyScroll?: boolean;
|
|
@@ -17,10 +18,10 @@
|
|
|
17
18
|
</script>
|
|
18
19
|
|
|
19
20
|
<script lang="ts">
|
|
20
|
-
let { disableBodyScroll=true, transitionDuration=
|
|
21
|
+
let { disableBodyScroll=true, transitionDuration=config.transitionDuration, ariaLabel="Overlay", onclick, children, ...props }: OverlayProps = $props();
|
|
21
22
|
</script>
|
|
22
23
|
|
|
23
|
-
<div transition:fade={{duration: transitionDuration}} class={twMerge("fixed top-0 left-0 right-0 bottom-0 bg-overlay
|
|
24
|
+
<div transition:fade={{duration: transitionDuration}} class={twMerge("fixed top-0 left-0 right-0 bottom-0 bg-primary-overlay z-40", props.class)} role="presentation" {onclick} use:disableScroll={disableBodyScroll}>
|
|
24
25
|
{@render children?.()}
|
|
25
26
|
</div>
|
|
26
27
|
|
|
@@ -33,9 +33,11 @@
|
|
|
33
33
|
|
|
34
34
|
</script>
|
|
35
35
|
|
|
36
|
-
<label for={id} class="
|
|
36
|
+
<label for={id} class="flex items-center gap-2 cursor-pointer select-none">
|
|
37
37
|
{#if labelPosition === "left"}<span class={twMerge("text-sm", labelClass)}>{label}</span>{/if}
|
|
38
|
-
<input {id} name={name ? name : id} type="radio"
|
|
39
|
-
|
|
38
|
+
<input {id} name={name ? name : id} type="radio" {value} bind:group {disabled}
|
|
39
|
+
class={twMerge("focus:ring-2 ring-primary-focus text-primary-input-accent accent-primary-input-accent cursor-pointer",
|
|
40
|
+
group === value ? "border-primary-input-accent" : "border-primary-input-border",
|
|
41
|
+
sizeMap[size], props.class)} {onclick} />
|
|
40
42
|
{#if labelPosition === "right"}<span class={twMerge("text-sm", labelClass)}>{label}</span>{/if}
|
|
41
43
|
</label>
|
|
@@ -24,7 +24,7 @@
|
|
|
24
24
|
|
|
25
25
|
<div class={twMerge("flex flex-row flex-wrap gap-4 justify-start xxxs:justify-center items-start py-2 px-4 mt-8 font-semibold", divClasses)}>
|
|
26
26
|
{#each links as link}
|
|
27
|
-
<a class={twMerge("px-4 py-1 hover:border-
|
|
27
|
+
<a class={twMerge("px-4 py-1 hover:border-primary-input-accent/40 hover:border-b-2 text-neutral-800 bg-transparent hover:bg-transparent rounded-none", link.active ? "border-primary-input-accent border-b-2": "", props.class)}
|
|
28
28
|
data-sveltekit-reload={reload} data-sveltekit-preload-data={preload ? 'hover' : false}
|
|
29
29
|
href={link.href}>
|
|
30
30
|
{link.text}
|
|
@@ -0,0 +1,226 @@
|
|
|
1
|
+
<!--
|
|
2
|
+
@component Table
|
|
3
|
+
|
|
4
|
+
A fully responsive, themeable table component for Svelte 5 built with Tailwind CSS.
|
|
5
|
+
Supports both desktop and mobile rendering, multi-select checkboxes, and custom component rendering via `Snippet`.
|
|
6
|
+
|
|
7
|
+
## Generic
|
|
8
|
+
- `<Table<T>>` — You must pass a `rows` array of type `T[]` and a `getKey(row: T) => string` function.
|
|
9
|
+
|
|
10
|
+
## Props
|
|
11
|
+
|
|
12
|
+
- `rows: T[]`
|
|
13
|
+
Required. Array of data rows to render.
|
|
14
|
+
|
|
15
|
+
- `getKey: (row: T) => string`
|
|
16
|
+
Required. Function to return a unique key for each row.
|
|
17
|
+
|
|
18
|
+
- `showMultiSelect?: boolean = false`
|
|
19
|
+
If true, displays a checkbox for selecting rows.
|
|
20
|
+
|
|
21
|
+
- `selected?: string[]`
|
|
22
|
+
A list of selected row keys (bindable).
|
|
23
|
+
|
|
24
|
+
- `headings?: Snippet`
|
|
25
|
+
Custom markup or Svelte block for rendering the `<thead>` row.
|
|
26
|
+
|
|
27
|
+
- `tableRow?: Snippet<[T]>`
|
|
28
|
+
Custom markup or Svelte block for rendering each row in desktop view.
|
|
29
|
+
|
|
30
|
+
- `tableRowMobile?: Snippet<[T]>`
|
|
31
|
+
Optional. Enables mobile view by rendering one `<td>` with stacked content.
|
|
32
|
+
|
|
33
|
+
- `class?: ClassNameValue`
|
|
34
|
+
Tailwind classes to apply to the `<table>` element.
|
|
35
|
+
|
|
36
|
+
- `outerDivClass?: ClassNameValue`
|
|
37
|
+
Tailwind classes for the outer scroll wrapper `<div>`.
|
|
38
|
+
|
|
39
|
+
- `headingsRowClass?: ClassNameValue`
|
|
40
|
+
Tailwind classes for the table heading `<tr>`.
|
|
41
|
+
|
|
42
|
+
- `tableRowClass?: ClassNameValue`
|
|
43
|
+
Tailwind classes for each row in desktop view.
|
|
44
|
+
|
|
45
|
+
- `tableRowMobileClass?: ClassNameValue`
|
|
46
|
+
Tailwind classes for each mobile row.
|
|
47
|
+
|
|
48
|
+
- `tableMobileTdClass?: ClassNameValue`
|
|
49
|
+
Tailwind classes for the mobile row's single `<td>` cell.
|
|
50
|
+
|
|
51
|
+
## Features
|
|
52
|
+
|
|
53
|
+
- Multi-select checkboxes with "select all" support
|
|
54
|
+
- Dual rendering paths for desktop and mobile (controlled by `tableRowMobile`)
|
|
55
|
+
- Sticky selection column (`left-0`)
|
|
56
|
+
- Class merging via `tailwind-merge` to avoid duplicate styles
|
|
57
|
+
- Fully customizable using Svelte `Snippet` syntax
|
|
58
|
+
|
|
59
|
+
## Example Usage
|
|
60
|
+
|
|
61
|
+
```svelte
|
|
62
|
+
<Table rows={[ { id: 1, name: "John Doe", age: 30 }, { id: 2, name: "Jane Smith", age: 25 }, { id: 3, name: "Alice Johnson", age: 28 }]} getKey={(u) => u.id.toString()} showMultiSelect={true}>
|
|
63
|
+
{#snippet headings()}
|
|
64
|
+
<TableTd>ID</TableTd>
|
|
65
|
+
<TableTd>Name</TableTd>
|
|
66
|
+
<TableTd>Age</TableTd>
|
|
67
|
+
{/snippet}
|
|
68
|
+
|
|
69
|
+
{#snippet tableRow(row)}
|
|
70
|
+
<TableTd>{row.id}</TableTd>
|
|
71
|
+
<TableTd>{row.name}</TableTd>
|
|
72
|
+
<TableTd>{row.age}</TableTd>
|
|
73
|
+
{/snippet}
|
|
74
|
+
|
|
75
|
+
{#snippet tableRowMobile(row)}
|
|
76
|
+
<div class="flex flex-col gap-2">
|
|
77
|
+
<p class="font-semibold">ID: {row.id}</p>
|
|
78
|
+
<p class="font-semibold">Name: {row.name}</p>
|
|
79
|
+
<p class="font-semibold">Age: {row.age}</p>
|
|
80
|
+
</div>
|
|
81
|
+
{/snippet}
|
|
82
|
+
</Table>
|
|
83
|
+
```
|
|
84
|
+
-->
|
|
85
|
+
|
|
86
|
+
<script lang="ts" module>
|
|
87
|
+
import type { Snippet } from 'svelte';
|
|
88
|
+
import type { ClassNameValue } from 'tailwind-merge';
|
|
89
|
+
|
|
90
|
+
/**
|
|
91
|
+
* Props for the generic Table component
|
|
92
|
+
* @template T - Type of each row object
|
|
93
|
+
*/
|
|
94
|
+
export type TableProps<T> = {
|
|
95
|
+
/** Enable multi-select checkboxes */
|
|
96
|
+
showMultiSelect?: boolean;
|
|
97
|
+
|
|
98
|
+
/** Currently selected row keys */
|
|
99
|
+
selected?: string[];
|
|
100
|
+
|
|
101
|
+
/** Data to render as rows */
|
|
102
|
+
rows: T[];
|
|
103
|
+
|
|
104
|
+
/** Function to extract a unique key for each row */
|
|
105
|
+
getKey: (obj: T) => string;
|
|
106
|
+
|
|
107
|
+
/** Snippet for rendering table headings */
|
|
108
|
+
headings?: Snippet;
|
|
109
|
+
|
|
110
|
+
/** Snippet for rendering a desktop table row */
|
|
111
|
+
tableRow?: Snippet<[T]>;
|
|
112
|
+
|
|
113
|
+
/** Snippet for rendering a mobile table row */
|
|
114
|
+
tableRowMobile?: Snippet<[T]>;
|
|
115
|
+
|
|
116
|
+
/** Snippet for the multi-select checkbox area which should be a TH */
|
|
117
|
+
multiSelectTh?: Snippet;
|
|
118
|
+
|
|
119
|
+
/** Snippet for the multi-select checkbox area which should be a TD */
|
|
120
|
+
multiSelectTd?: Snippet<[T]>;
|
|
121
|
+
|
|
122
|
+
/** Class for the <table> element */
|
|
123
|
+
class?: ClassNameValue;
|
|
124
|
+
|
|
125
|
+
/** Class for the outer <div> wrapper */
|
|
126
|
+
outerDivClass?: ClassNameValue;
|
|
127
|
+
|
|
128
|
+
/** Class for the table headings row */
|
|
129
|
+
headingsRowClass?: ClassNameValue;
|
|
130
|
+
|
|
131
|
+
/** Class for the multi-select checkbox <th> */
|
|
132
|
+
multiSelectThClass?: ClassNameValue;
|
|
133
|
+
|
|
134
|
+
/** Class for each row (desktop) */
|
|
135
|
+
tableRowClass?: ClassNameValue;
|
|
136
|
+
|
|
137
|
+
/** Class for the multi-select checkbox <td> */
|
|
138
|
+
multiSelectTdClass?: ClassNameValue;
|
|
139
|
+
|
|
140
|
+
/** Class for each row (mobile) */
|
|
141
|
+
tableRowMobileClass?: ClassNameValue;
|
|
142
|
+
|
|
143
|
+
/** Class for the <td> cell in mobile view */
|
|
144
|
+
tableMobileTdClass?: ClassNameValue;
|
|
145
|
+
};
|
|
146
|
+
</script>
|
|
147
|
+
|
|
148
|
+
<script lang="ts" generics="T">
|
|
149
|
+
import { twMerge } from "tailwind-merge";
|
|
150
|
+
import {Checkbox} from '../checkbox/index.js';
|
|
151
|
+
|
|
152
|
+
let { showMultiSelect=$bindable(false), selected=$bindable([]), rows=$bindable([]), getKey, headings, tableRow, tableRowMobile, multiSelectTh, multiSelectTd,
|
|
153
|
+
tableMobileTdClass, outerDivClass, headingsRowClass, multiSelectThClass, tableRowClass, multiSelectTdClass, tableRowMobileClass, ...props }: TableProps<T> = $props();
|
|
154
|
+
|
|
155
|
+
function addSelected(rowkey: string){
|
|
156
|
+
if(selected.includes(rowkey)){
|
|
157
|
+
selected = selected.filter((item) => item !== rowkey);
|
|
158
|
+
}else{
|
|
159
|
+
selected.push(rowkey);
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
function handleSelectAll(event: any){
|
|
164
|
+
if (event.target.checked) {
|
|
165
|
+
for(let i = 0; i < rows.length; i++){
|
|
166
|
+
selected.push(getKey(rows[i]));
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
else{
|
|
170
|
+
selected = [];
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
</script>
|
|
175
|
+
|
|
176
|
+
|
|
177
|
+
<div class={twMerge("rounded-lg border-primary-table-border w-full overflow-x-auto", tableRowMobile ? "border-0 md:border bg-transparent" : "border bg-white", outerDivClass)}>
|
|
178
|
+
<table class={twMerge("table-fixed w-full text-sm", tableRowMobile ? "border-separate border-spacing-y-2 md:border-collapse md:border-spacing-y-0": "", props.class)} cellpadding="10px">
|
|
179
|
+
<thead class={twMerge("bg-primary-table-heading", tableRowMobile ? "hidden md:table-header-group" : "")}>
|
|
180
|
+
<tr class={twMerge("text-xs text-primary-table-heading-text whitespace-nowrap bg-inherit", headingsRowClass)}>
|
|
181
|
+
{#if showMultiSelect}
|
|
182
|
+
{#if multiSelectTh}
|
|
183
|
+
{@render multiSelectTh()}
|
|
184
|
+
{:else}
|
|
185
|
+
<th class={twMerge("w-12 px-4 py-2 text-center bg-inherit", multiSelectThClass)}>
|
|
186
|
+
<Checkbox id="header-multiselect-checkbox" onchange={handleSelectAll} checked={ rows ? selected.length === rows.length: false} />
|
|
187
|
+
</th>
|
|
188
|
+
{/if}
|
|
189
|
+
{/if}
|
|
190
|
+
{@render headings?.()}
|
|
191
|
+
</tr>
|
|
192
|
+
</thead>
|
|
193
|
+
|
|
194
|
+
<tbody>
|
|
195
|
+
{#each rows as row, index (getKey ? getKey(row) : index)}
|
|
196
|
+
{@const rowKey = getKey(row)}
|
|
197
|
+
{@const isSelected = selected.includes(rowKey)}
|
|
198
|
+
|
|
199
|
+
{#if tableRow}
|
|
200
|
+
<tr class={twMerge("group border-t border-primary-table-border text-primary-table-row-text", tableRowMobile ? "hidden md:table-row" : "",
|
|
201
|
+
tableRowClass, isSelected ? "bg-primary-table-row-selected" : "hover:bg-primary-table-row-hover bg-white")}>
|
|
202
|
+
{#if showMultiSelect}
|
|
203
|
+
{#if multiSelectTd}
|
|
204
|
+
{@render multiSelectTd(row)}
|
|
205
|
+
{:else}
|
|
206
|
+
<td class={twMerge("text-center px-4 py-2 bg-inherit", multiSelectTdClass )}>
|
|
207
|
+
<Checkbox id={"checkbox-" + rowKey} value={rowKey} checked={isSelected} onchange={()=>{addSelected(rowKey)}} />
|
|
208
|
+
</td>
|
|
209
|
+
{/if}
|
|
210
|
+
{/if}
|
|
211
|
+
{@render tableRow?.(row)}
|
|
212
|
+
</tr>
|
|
213
|
+
{/if}
|
|
214
|
+
|
|
215
|
+
{#if tableRowMobile}
|
|
216
|
+
<tr class={twMerge("md:hidden text-primary-table-row-text", isSelected ? "bg-primary-table-row-selected" : "bg-white", tableRowMobileClass)}>
|
|
217
|
+
<td class={twMerge("border border-primary-table-border border-t-4 rounded-primary px-2 pb-2 relative bg-inherit", tableMobileTdClass)}>
|
|
218
|
+
{#if showMultiSelect}<button onclick={()=>{ addSelected(rowKey)}} aria-label="select row" class="absolute inset-0 cursor-pointer"></button>{/if}
|
|
219
|
+
{@render tableRowMobile?.(row)}
|
|
220
|
+
</td>
|
|
221
|
+
</tr>
|
|
222
|
+
{/if}
|
|
223
|
+
{/each}
|
|
224
|
+
</tbody>
|
|
225
|
+
</table>
|
|
226
|
+
</div>
|
|
@@ -0,0 +1,143 @@
|
|
|
1
|
+
import type { Snippet } from 'svelte';
|
|
2
|
+
import type { ClassNameValue } from 'tailwind-merge';
|
|
3
|
+
/**
|
|
4
|
+
* Props for the generic Table component
|
|
5
|
+
* @template T - Type of each row object
|
|
6
|
+
*/
|
|
7
|
+
export type TableProps<T> = {
|
|
8
|
+
/** Enable multi-select checkboxes */
|
|
9
|
+
showMultiSelect?: boolean;
|
|
10
|
+
/** Currently selected row keys */
|
|
11
|
+
selected?: string[];
|
|
12
|
+
/** Data to render as rows */
|
|
13
|
+
rows: T[];
|
|
14
|
+
/** Function to extract a unique key for each row */
|
|
15
|
+
getKey: (obj: T) => string;
|
|
16
|
+
/** Snippet for rendering table headings */
|
|
17
|
+
headings?: Snippet;
|
|
18
|
+
/** Snippet for rendering a desktop table row */
|
|
19
|
+
tableRow?: Snippet<[T]>;
|
|
20
|
+
/** Snippet for rendering a mobile table row */
|
|
21
|
+
tableRowMobile?: Snippet<[T]>;
|
|
22
|
+
/** Snippet for the multi-select checkbox area which should be a TH */
|
|
23
|
+
multiSelectTh?: Snippet;
|
|
24
|
+
/** Snippet for the multi-select checkbox area which should be a TD */
|
|
25
|
+
multiSelectTd?: Snippet<[T]>;
|
|
26
|
+
/** Class for the <table> element */
|
|
27
|
+
class?: ClassNameValue;
|
|
28
|
+
/** Class for the outer <div> wrapper */
|
|
29
|
+
outerDivClass?: ClassNameValue;
|
|
30
|
+
/** Class for the table headings row */
|
|
31
|
+
headingsRowClass?: ClassNameValue;
|
|
32
|
+
/** Class for the multi-select checkbox <th> */
|
|
33
|
+
multiSelectThClass?: ClassNameValue;
|
|
34
|
+
/** Class for each row (desktop) */
|
|
35
|
+
tableRowClass?: ClassNameValue;
|
|
36
|
+
/** Class for the multi-select checkbox <td> */
|
|
37
|
+
multiSelectTdClass?: ClassNameValue;
|
|
38
|
+
/** Class for each row (mobile) */
|
|
39
|
+
tableRowMobileClass?: ClassNameValue;
|
|
40
|
+
/** Class for the <td> cell in mobile view */
|
|
41
|
+
tableMobileTdClass?: ClassNameValue;
|
|
42
|
+
};
|
|
43
|
+
declare class __sveltets_Render<T> {
|
|
44
|
+
props(): TableProps<T>;
|
|
45
|
+
events(): {};
|
|
46
|
+
slots(): {};
|
|
47
|
+
bindings(): "showMultiSelect" | "selected" | "rows";
|
|
48
|
+
exports(): {};
|
|
49
|
+
}
|
|
50
|
+
interface $$IsomorphicComponent {
|
|
51
|
+
new <T>(options: import('svelte').ComponentConstructorOptions<ReturnType<__sveltets_Render<T>['props']>>): import('svelte').SvelteComponent<ReturnType<__sveltets_Render<T>['props']>, ReturnType<__sveltets_Render<T>['events']>, ReturnType<__sveltets_Render<T>['slots']>> & {
|
|
52
|
+
$$bindings?: ReturnType<__sveltets_Render<T>['bindings']>;
|
|
53
|
+
} & ReturnType<__sveltets_Render<T>['exports']>;
|
|
54
|
+
<T>(internal: unknown, props: ReturnType<__sveltets_Render<T>['props']> & {}): ReturnType<__sveltets_Render<T>['exports']>;
|
|
55
|
+
z_$$bindings?: ReturnType<__sveltets_Render<any>['bindings']>;
|
|
56
|
+
}
|
|
57
|
+
/**
|
|
58
|
+
* Table
|
|
59
|
+
*
|
|
60
|
+
* A fully responsive, themeable table component for Svelte 5 built with Tailwind CSS.
|
|
61
|
+
* Supports both desktop and mobile rendering, multi-select checkboxes, and custom component rendering via `Snippet`.
|
|
62
|
+
*
|
|
63
|
+
* ## Generic
|
|
64
|
+
* - `<Table<T>>` — You must pass a `rows` array of type `T[]` and a `getKey(row: T) => string` function.
|
|
65
|
+
*
|
|
66
|
+
* ## Props
|
|
67
|
+
*
|
|
68
|
+
* - `rows: T[]`
|
|
69
|
+
* Required. Array of data rows to render.
|
|
70
|
+
*
|
|
71
|
+
* - `getKey: (row: T) => string`
|
|
72
|
+
* Required. Function to return a unique key for each row.
|
|
73
|
+
*
|
|
74
|
+
* - `showMultiSelect?: boolean = false`
|
|
75
|
+
* If true, displays a checkbox for selecting rows.
|
|
76
|
+
*
|
|
77
|
+
* - `selected?: string[]`
|
|
78
|
+
* A list of selected row keys (bindable).
|
|
79
|
+
*
|
|
80
|
+
* - `headings?: Snippet`
|
|
81
|
+
* Custom markup or Svelte block for rendering the `<thead>` row.
|
|
82
|
+
*
|
|
83
|
+
* - `tableRow?: Snippet<[T]>`
|
|
84
|
+
* Custom markup or Svelte block for rendering each row in desktop view.
|
|
85
|
+
*
|
|
86
|
+
* - `tableRowMobile?: Snippet<[T]>`
|
|
87
|
+
* Optional. Enables mobile view by rendering one `<td>` with stacked content.
|
|
88
|
+
*
|
|
89
|
+
* - `class?: ClassNameValue`
|
|
90
|
+
* Tailwind classes to apply to the `<table>` element.
|
|
91
|
+
*
|
|
92
|
+
* - `outerDivClass?: ClassNameValue`
|
|
93
|
+
* Tailwind classes for the outer scroll wrapper `<div>`.
|
|
94
|
+
*
|
|
95
|
+
* - `headingsRowClass?: ClassNameValue`
|
|
96
|
+
* Tailwind classes for the table heading `<tr>`.
|
|
97
|
+
*
|
|
98
|
+
* - `tableRowClass?: ClassNameValue`
|
|
99
|
+
* Tailwind classes for each row in desktop view.
|
|
100
|
+
*
|
|
101
|
+
* - `tableRowMobileClass?: ClassNameValue`
|
|
102
|
+
* Tailwind classes for each mobile row.
|
|
103
|
+
*
|
|
104
|
+
* - `tableMobileTdClass?: ClassNameValue`
|
|
105
|
+
* Tailwind classes for the mobile row's single `<td>` cell.
|
|
106
|
+
*
|
|
107
|
+
* ## Features
|
|
108
|
+
*
|
|
109
|
+
* - Multi-select checkboxes with "select all" support
|
|
110
|
+
* - Dual rendering paths for desktop and mobile (controlled by `tableRowMobile`)
|
|
111
|
+
* - Sticky selection column (`left-0`)
|
|
112
|
+
* - Class merging via `tailwind-merge` to avoid duplicate styles
|
|
113
|
+
* - Fully customizable using Svelte `Snippet` syntax
|
|
114
|
+
*
|
|
115
|
+
* ## Example Usage
|
|
116
|
+
*
|
|
117
|
+
* ```svelte
|
|
118
|
+
* <Table rows={[ { id: 1, name: "John Doe", age: 30 }, { id: 2, name: "Jane Smith", age: 25 }, { id: 3, name: "Alice Johnson", age: 28 }]} getKey={(u) => u.id.toString()} showMultiSelect={true}>
|
|
119
|
+
* {#snippet headings()}
|
|
120
|
+
* <TableTd>ID</TableTd>
|
|
121
|
+
* <TableTd>Name</TableTd>
|
|
122
|
+
* <TableTd>Age</TableTd>
|
|
123
|
+
* {/snippet}
|
|
124
|
+
*
|
|
125
|
+
* {#snippet tableRow(row)}
|
|
126
|
+
* <TableTd>{row.id}</TableTd>
|
|
127
|
+
* <TableTd>{row.name}</TableTd>
|
|
128
|
+
* <TableTd>{row.age}</TableTd>
|
|
129
|
+
* {/snippet}
|
|
130
|
+
*
|
|
131
|
+
* {#snippet tableRowMobile(row)}
|
|
132
|
+
* <div class="flex flex-col gap-2">
|
|
133
|
+
* <p class="font-semibold">ID: {row.id}</p>
|
|
134
|
+
* <p class="font-semibold">Name: {row.name}</p>
|
|
135
|
+
* <p class="font-semibold">Age: {row.age}</p>
|
|
136
|
+
* </div>
|
|
137
|
+
* {/snippet}
|
|
138
|
+
* </Table>
|
|
139
|
+
* ```
|
|
140
|
+
*/
|
|
141
|
+
declare const Table: $$IsomorphicComponent;
|
|
142
|
+
type Table<T> = InstanceType<typeof Table<T>>;
|
|
143
|
+
export default Table;
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
<script lang="ts" module>
|
|
2
|
+
import type { Snippet } from 'svelte';
|
|
3
|
+
import type { ClassNameValue } from 'tailwind-merge';
|
|
4
|
+
|
|
5
|
+
export type TableColProps = {
|
|
6
|
+
children: Snippet
|
|
7
|
+
class?: ClassNameValue;
|
|
8
|
+
};
|
|
9
|
+
|
|
10
|
+
</script>
|
|
11
|
+
|
|
12
|
+
<script lang="ts">
|
|
13
|
+
import { twMerge } from "tailwind-merge";
|
|
14
|
+
|
|
15
|
+
let { children, ...props }: TableColProps = $props();
|
|
16
|
+
|
|
17
|
+
</script>
|
|
18
|
+
|
|
19
|
+
<td class={twMerge("text-left align-middle p-2.5 bg-inherit", props.class)}>{@render children?.()}</td>
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import type { Snippet } from 'svelte';
|
|
2
|
+
import type { ClassNameValue } from 'tailwind-merge';
|
|
3
|
+
export type TableColProps = {
|
|
4
|
+
children: Snippet;
|
|
5
|
+
class?: ClassNameValue;
|
|
6
|
+
};
|
|
7
|
+
declare const TableTd: import("svelte").Component<TableColProps, {}, "">;
|
|
8
|
+
type TableTd = ReturnType<typeof TableTd>;
|
|
9
|
+
export default TableTd;
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
<script lang="ts" module>
|
|
2
|
+
import type { Snippet } from 'svelte';
|
|
3
|
+
import type { ClassNameValue } from 'tailwind-merge';
|
|
4
|
+
|
|
5
|
+
export type TableThProps = {
|
|
6
|
+
children: Snippet
|
|
7
|
+
class?: ClassNameValue;
|
|
8
|
+
};
|
|
9
|
+
|
|
10
|
+
</script>
|
|
11
|
+
|
|
12
|
+
<script lang="ts">
|
|
13
|
+
import { twMerge } from "tailwind-merge";
|
|
14
|
+
|
|
15
|
+
let { children, ...props }: TableThProps = $props();
|
|
16
|
+
|
|
17
|
+
</script>
|
|
18
|
+
|
|
19
|
+
<th scope="col" class={twMerge("p-3 text-left uppercase font-semibold", props.class)}>{@render children?.()}</th>
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import type { Snippet } from 'svelte';
|
|
2
|
+
import type { ClassNameValue } from 'tailwind-merge';
|
|
3
|
+
export type TableThProps = {
|
|
4
|
+
children: Snippet;
|
|
5
|
+
class?: ClassNameValue;
|
|
6
|
+
};
|
|
7
|
+
declare const TableTh: import("svelte").Component<TableThProps, {}, "">;
|
|
8
|
+
type TableTh = ReturnType<typeof TableTh>;
|
|
9
|
+
export default TableTh;
|
|
@@ -69,12 +69,12 @@
|
|
|
69
69
|
</script>
|
|
70
70
|
|
|
71
71
|
<div class="flex flex-col gap-1 w-full">
|
|
72
|
-
{#if label}{@render label()}{:else}{#if labelText}<label for={id} class="text-sm font-medium text-
|
|
72
|
+
{#if label}{@render label()}{:else}{#if labelText}<label for={id} class="text-sm font-medium text-primary-label-text ml-1">{labelText}</label>{/if}{/if}
|
|
73
73
|
<div class="relative">
|
|
74
74
|
{#if icon}<div class="absolute inset-y-0 left-0 flex items-center justify-center rounded-l-primary m-0.5 w-10">{@render icon()}</div>{/if}
|
|
75
75
|
<input {disabled} {required} {type} {id} name={name ? name: id} {placeholder} {onchange} {onmouseup} bind:value
|
|
76
|
-
class={twMerge("rounded-primary border-border
|
|
77
|
-
error ? "bg-red-50 border-red-300
|
|
76
|
+
class={twMerge("rounded-primary border-primary-input-border focus:border-primary-focus focus:ring-primary-focus placeholder:opacity-50 disabled:bg-neutral-300/30 disabled:border-neutral-300/30",
|
|
77
|
+
error ? "bg-red-50 border-red-300" : "",
|
|
78
78
|
icon ? "pl-10" : "",
|
|
79
79
|
sizeStyle[size], props.class)}
|
|
80
80
|
/>
|