compote-ui 0.32.0 → 0.33.1
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/dist/components/checkbox/checkbox.svelte +34 -9
- package/dist/components/checkbox/checkbox.types.d.ts +5 -2
- package/dist/components/data-table/core/cells.d.ts +78 -0
- package/dist/components/data-table/core/cells.js +66 -0
- package/dist/components/data-table/core/create-table.svelte.d.ts +24 -0
- package/dist/components/data-table/core/create-table.svelte.js +82 -0
- package/dist/components/data-table/core/index.d.ts +3 -0
- package/dist/components/data-table/core/index.js +2 -0
- package/dist/components/data-table/data-table-column-visibility.svelte +79 -0
- package/dist/components/data-table/data-table-column-visibility.svelte.d.ts +29 -0
- package/dist/components/data-table/data-table-filters.svelte +285 -0
- package/dist/components/data-table/data-table-filters.svelte.d.ts +29 -0
- package/dist/components/data-table/data-table-title.svelte +16 -0
- package/dist/components/data-table/data-table-title.svelte.d.ts +10 -0
- package/dist/components/data-table/data-table-toolbar.svelte +16 -0
- package/dist/components/data-table/data-table-toolbar.svelte.d.ts +10 -0
- package/dist/components/data-table/data-table.svelte +342 -0
- package/dist/components/data-table/data-table.svelte.d.ts +32 -0
- package/dist/components/data-table/index.d.ts +7 -0
- package/dist/components/data-table/index.js +7 -0
- package/dist/components/number-input/number-input.svelte +3 -0
- package/dist/components/popover/index.d.ts +6 -0
- package/dist/components/popover/index.js +6 -0
- package/dist/components/popover/popover-close-trigger.svelte +25 -0
- package/dist/components/popover/popover-close-trigger.svelte.d.ts +8 -0
- package/dist/components/popover/popover-content.svelte +71 -0
- package/dist/components/popover/popover-content.svelte.d.ts +10 -0
- package/dist/components/popover/popover-description.svelte +15 -0
- package/dist/components/popover/popover-description.svelte.d.ts +8 -0
- package/dist/components/popover/popover-root.svelte +24 -0
- package/dist/components/popover/popover-root.svelte.d.ts +11 -0
- package/dist/components/popover/popover-title.svelte +15 -0
- package/dist/components/popover/popover-title.svelte.d.ts +8 -0
- package/dist/components/popover/popover-trigger.svelte +17 -0
- package/dist/components/popover/popover-trigger.svelte.d.ts +10 -0
- package/dist/components/scroll-area/scroll-area-content.svelte +1 -1
- package/dist/icons/PhArrowSquareOut.svelte +18 -0
- package/dist/icons/PhArrowSquareOut.svelte.d.ts +5 -0
- package/dist/icons/index.d.ts +1 -0
- package/dist/icons/index.js +1 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.js +1 -0
- package/package.json +12 -1
|
@@ -0,0 +1,285 @@
|
|
|
1
|
+
<script lang="ts" generics="TData extends RowData, TSelected = object">
|
|
2
|
+
import * as Field from '../field';
|
|
3
|
+
import * as Popover from '../popover';
|
|
4
|
+
import * as ScrollArea from '../scroll-area';
|
|
5
|
+
import * as Fieldset from '../fieldset';
|
|
6
|
+
import Checkbox from '../checkbox/checkbox.svelte';
|
|
7
|
+
import Button from '../button/button.svelte';
|
|
8
|
+
import NumberInput from '../number-input/number-input.svelte';
|
|
9
|
+
import Select from '../select/select.svelte';
|
|
10
|
+
import type { Column, RowData } from '@tanstack/svelte-table';
|
|
11
|
+
import {
|
|
12
|
+
getDataTableFilterConfig,
|
|
13
|
+
type DataTable,
|
|
14
|
+
type DataTableFeatures,
|
|
15
|
+
type DataTableFilterConfig,
|
|
16
|
+
type DataTableFilterOption,
|
|
17
|
+
type DataTableSelectedState
|
|
18
|
+
} from './core';
|
|
19
|
+
|
|
20
|
+
type Props = {
|
|
21
|
+
table: DataTable<TData, TSelected>;
|
|
22
|
+
triggerLabel?: string;
|
|
23
|
+
};
|
|
24
|
+
|
|
25
|
+
type FilterableColumn = Column<DataTableFeatures, TData, unknown>;
|
|
26
|
+
|
|
27
|
+
let { table, triggerLabel = 'Filters' }: Props = $props();
|
|
28
|
+
|
|
29
|
+
const columnFilters = $derived((table.state as unknown as DataTableSelectedState).columnFilters);
|
|
30
|
+
const activeFilterCount = $derived(columnFilters.length);
|
|
31
|
+
const filterableColumns = $derived(
|
|
32
|
+
table
|
|
33
|
+
.getAllLeafColumns()
|
|
34
|
+
.filter((column) => column.getCanFilter() && getDataTableFilterConfig(column.columnDef))
|
|
35
|
+
);
|
|
36
|
+
|
|
37
|
+
function getColumnLabel(column: FilterableColumn) {
|
|
38
|
+
return typeof column.columnDef.header === 'string' ? column.columnDef.header : column.id;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
function getFilterConfig(column: FilterableColumn, filtersState: unknown) {
|
|
42
|
+
void filtersState;
|
|
43
|
+
return getDataTableFilterConfig(column.columnDef);
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
function getFilterValue(column: FilterableColumn, filtersState: unknown) {
|
|
47
|
+
void filtersState;
|
|
48
|
+
return column.getFilterValue();
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
function setTextFilter(column: FilterableColumn, value: string) {
|
|
52
|
+
column.setFilterValue(value || undefined);
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
function setBooleanFilter(column: FilterableColumn, value: string) {
|
|
56
|
+
if (value === 'true') {
|
|
57
|
+
column.setFilterValue(true);
|
|
58
|
+
return;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
if (value === 'false') {
|
|
62
|
+
column.setFilterValue(false);
|
|
63
|
+
return;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
column.setFilterValue(undefined);
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
function setNumberRangeFilter(column: FilterableColumn, index: 0 | 1, value: number | null) {
|
|
70
|
+
const currentValue = column.getFilterValue();
|
|
71
|
+
const current = Array.isArray(currentValue) ? currentValue : [];
|
|
72
|
+
const next = [current[0], current[1]] as [number | undefined, number | undefined];
|
|
73
|
+
next[index] = value ?? undefined;
|
|
74
|
+
|
|
75
|
+
column.setFilterValue(next[0] === undefined && next[1] === undefined ? undefined : next);
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
function getNumberRangeValue(
|
|
79
|
+
column: FilterableColumn,
|
|
80
|
+
filtersState: unknown
|
|
81
|
+
): [number | undefined, number | undefined] {
|
|
82
|
+
const value = getFilterValue(column, filtersState);
|
|
83
|
+
return Array.isArray(value) ? [value[0], value[1]] : [undefined, undefined];
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
function getFacetOptions(
|
|
87
|
+
column: FilterableColumn,
|
|
88
|
+
config: DataTableFilterConfig,
|
|
89
|
+
filtersState: unknown
|
|
90
|
+
): DataTableFilterOption[] {
|
|
91
|
+
void filtersState;
|
|
92
|
+
|
|
93
|
+
if (config.type !== 'facet') return [];
|
|
94
|
+
if (config.options) return config.options;
|
|
95
|
+
|
|
96
|
+
return Array.from(column.getFacetedUniqueValues().entries())
|
|
97
|
+
.sort(([a], [b]) => String(a).localeCompare(String(b)))
|
|
98
|
+
.slice(0, config.maxOptions ?? 20)
|
|
99
|
+
.map(([value, count]) => ({
|
|
100
|
+
value,
|
|
101
|
+
label: `${String(value)} (${count})`
|
|
102
|
+
}));
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
function getFacetFilterValue(column: FilterableColumn, filtersState: unknown) {
|
|
106
|
+
const value = getFilterValue(column, filtersState);
|
|
107
|
+
return Array.isArray(value) ? value : [];
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
function getFacetOptionChecked(
|
|
111
|
+
column: FilterableColumn,
|
|
112
|
+
option: DataTableFilterOption,
|
|
113
|
+
filtersState: unknown
|
|
114
|
+
) {
|
|
115
|
+
return getFacetFilterValue(column, filtersState).some((value) =>
|
|
116
|
+
Object.is(value, option.value)
|
|
117
|
+
);
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
function toggleFacetFilterValue(
|
|
121
|
+
column: FilterableColumn,
|
|
122
|
+
option: DataTableFilterOption,
|
|
123
|
+
checked: boolean
|
|
124
|
+
) {
|
|
125
|
+
const currentValue = column.getFilterValue();
|
|
126
|
+
const current = Array.isArray(currentValue) ? currentValue : [];
|
|
127
|
+
const next = checked
|
|
128
|
+
? [...current, option.value]
|
|
129
|
+
: current.filter((value) => !Object.is(value, option.value));
|
|
130
|
+
|
|
131
|
+
column.setFilterValue(next.length > 0 ? next : undefined);
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
function getBooleanFilterValue(column: FilterableColumn, filtersState: unknown) {
|
|
135
|
+
const value = getFilterValue(column, filtersState);
|
|
136
|
+
if (value === true) return 'true';
|
|
137
|
+
if (value === false) return 'false';
|
|
138
|
+
return null;
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
function getBooleanLabel(config: DataTableFilterConfig, value: boolean) {
|
|
142
|
+
if (config.type !== 'boolean') return value ? 'True' : 'False';
|
|
143
|
+
return value ? (config.trueLabel ?? 'True') : (config.falseLabel ?? 'False');
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
function getBooleanFilterItems(config: DataTableFilterConfig) {
|
|
147
|
+
return [
|
|
148
|
+
{ value: 'true', label: getBooleanLabel(config, true) },
|
|
149
|
+
{ value: 'false', label: getBooleanLabel(config, false) }
|
|
150
|
+
];
|
|
151
|
+
}
|
|
152
|
+
</script>
|
|
153
|
+
|
|
154
|
+
<Popover.Root positioning={{ placement: 'bottom-end' }}>
|
|
155
|
+
<Popover.Trigger
|
|
156
|
+
class="flex h-9 cursor-pointer items-center gap-2 rounded-md border border-surface-3 bg-surface-1 px-3 text-sm font-medium text-ink shadow-sm outline-none hover:bg-surface-2 focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-ring"
|
|
157
|
+
>
|
|
158
|
+
<span>{triggerLabel}</span>
|
|
159
|
+
{#if activeFilterCount > 0}
|
|
160
|
+
<span class="rounded-full bg-surface-3 px-1.5 text-xs text-ink-dim">
|
|
161
|
+
{activeFilterCount}
|
|
162
|
+
</span>
|
|
163
|
+
{/if}
|
|
164
|
+
</Popover.Trigger>
|
|
165
|
+
|
|
166
|
+
<Popover.Content class="w-80 p-0" showArrow={false}>
|
|
167
|
+
<Popover.Title class="flex items-center justify-between gap-3 border-b px-4 py-2">
|
|
168
|
+
<div class="text-sm font-medium text-ink">Filters</div>
|
|
169
|
+
<Button
|
|
170
|
+
variant="ghost"
|
|
171
|
+
disabled={activeFilterCount === 0}
|
|
172
|
+
onclick={() => table.resetColumnFilters(true)}
|
|
173
|
+
>
|
|
174
|
+
Clear
|
|
175
|
+
</Button>
|
|
176
|
+
</Popover.Title>
|
|
177
|
+
|
|
178
|
+
{#if filterableColumns.length === 0}
|
|
179
|
+
<div class="py-2 text-sm text-ink-dim">No filters available</div>
|
|
180
|
+
{:else}
|
|
181
|
+
<ScrollArea.Root class="h-96">
|
|
182
|
+
<ScrollArea.Viewport>
|
|
183
|
+
<ScrollArea.Content>
|
|
184
|
+
<div class="flex flex-col gap-4">
|
|
185
|
+
{#each filterableColumns as column (column.id)}
|
|
186
|
+
{@const config = getFilterConfig(column, columnFilters)}
|
|
187
|
+
{#if config}
|
|
188
|
+
<div class="space-y-2">
|
|
189
|
+
{#if config.type === 'text'}
|
|
190
|
+
<Field.Root>
|
|
191
|
+
<Field.Label>{getColumnLabel(column)}</Field.Label>
|
|
192
|
+
<Field.Input
|
|
193
|
+
type="search"
|
|
194
|
+
placeholder={config.placeholder ?? `Search ${getColumnLabel(column)}`}
|
|
195
|
+
value={String(getFilterValue(column, columnFilters) ?? '')}
|
|
196
|
+
oninput={(event) =>
|
|
197
|
+
setTextFilter(column, (event.currentTarget as HTMLInputElement).value)}
|
|
198
|
+
/>
|
|
199
|
+
</Field.Root>
|
|
200
|
+
{:else if config.type === 'number-range'}
|
|
201
|
+
{@const rangeValue = getNumberRangeValue(column, columnFilters)}
|
|
202
|
+
<div class="text-sm font-medium text-ink">{getColumnLabel(column)}</div>
|
|
203
|
+
<div class="grid grid-cols-2 gap-2">
|
|
204
|
+
<NumberInput
|
|
205
|
+
label="Min"
|
|
206
|
+
min={config.min}
|
|
207
|
+
max={config.max}
|
|
208
|
+
step={config.step}
|
|
209
|
+
formatOptions={config.formatOptions ?? { minimumFractionDigits: 0 }}
|
|
210
|
+
value={rangeValue[0] ?? null}
|
|
211
|
+
onValueChange={({ valueAsNumber }) =>
|
|
212
|
+
setNumberRangeFilter(
|
|
213
|
+
column,
|
|
214
|
+
0,
|
|
215
|
+
Number.isNaN(valueAsNumber) ? null : valueAsNumber
|
|
216
|
+
)}
|
|
217
|
+
/>
|
|
218
|
+
<NumberInput
|
|
219
|
+
label="Max"
|
|
220
|
+
min={config.min}
|
|
221
|
+
max={config.max}
|
|
222
|
+
step={config.step}
|
|
223
|
+
formatOptions={config.formatOptions ?? { minimumFractionDigits: 0 }}
|
|
224
|
+
value={rangeValue[1] ?? null}
|
|
225
|
+
onValueChange={({ valueAsNumber }) =>
|
|
226
|
+
setNumberRangeFilter(
|
|
227
|
+
column,
|
|
228
|
+
1,
|
|
229
|
+
Number.isNaN(valueAsNumber) ? null : valueAsNumber
|
|
230
|
+
)}
|
|
231
|
+
/>
|
|
232
|
+
</div>
|
|
233
|
+
{:else if config.type === 'boolean'}
|
|
234
|
+
<Select
|
|
235
|
+
label={getColumnLabel(column)}
|
|
236
|
+
items={getBooleanFilterItems(config)}
|
|
237
|
+
placeholder="All"
|
|
238
|
+
value={getBooleanFilterValue(column, columnFilters)}
|
|
239
|
+
onValueChange={({ value }) =>
|
|
240
|
+
setBooleanFilter(
|
|
241
|
+
column,
|
|
242
|
+
Array.isArray(value) && value.length > 0 ? value[0] : ''
|
|
243
|
+
)}
|
|
244
|
+
/>
|
|
245
|
+
{:else if config.type === 'facet'}
|
|
246
|
+
<Fieldset.Root class="max-h-44">
|
|
247
|
+
<Fieldset.Legend>{getColumnLabel(column)}</Fieldset.Legend>
|
|
248
|
+
<ScrollArea.Root class="hrounded-md border border-surface-3">
|
|
249
|
+
<ScrollArea.Viewport>
|
|
250
|
+
<ScrollArea.Content class="p-1 pe-3">
|
|
251
|
+
<div class="flex flex-col">
|
|
252
|
+
{#each getFacetOptions(column, config, columnFilters) as option (String(option.value))}
|
|
253
|
+
<Checkbox
|
|
254
|
+
size="sm"
|
|
255
|
+
label={option.label}
|
|
256
|
+
class="min-h-8 rounded-sm px-2 hover:bg-surface-2"
|
|
257
|
+
checked={getFacetOptionChecked(column, option, columnFilters)}
|
|
258
|
+
onCheckedChange={({ checked }) =>
|
|
259
|
+
toggleFacetFilterValue(column, option, checked === true)}
|
|
260
|
+
/>
|
|
261
|
+
{/each}
|
|
262
|
+
</div>
|
|
263
|
+
</ScrollArea.Content>
|
|
264
|
+
</ScrollArea.Viewport>
|
|
265
|
+
<ScrollArea.Scrollbar orientation="vertical">
|
|
266
|
+
<ScrollArea.Thumb />
|
|
267
|
+
</ScrollArea.Scrollbar>
|
|
268
|
+
<ScrollArea.Corner />
|
|
269
|
+
</ScrollArea.Root>
|
|
270
|
+
</Fieldset.Root>
|
|
271
|
+
{/if}
|
|
272
|
+
</div>
|
|
273
|
+
{/if}
|
|
274
|
+
{/each}
|
|
275
|
+
</div>
|
|
276
|
+
</ScrollArea.Content>
|
|
277
|
+
</ScrollArea.Viewport>
|
|
278
|
+
<ScrollArea.Scrollbar orientation="vertical">
|
|
279
|
+
<ScrollArea.Thumb />
|
|
280
|
+
</ScrollArea.Scrollbar>
|
|
281
|
+
<ScrollArea.Corner />
|
|
282
|
+
</ScrollArea.Root>
|
|
283
|
+
{/if}
|
|
284
|
+
</Popover.Content>
|
|
285
|
+
</Popover.Root>
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import type { RowData } from '@tanstack/svelte-table';
|
|
2
|
+
import { type DataTable } from './core';
|
|
3
|
+
declare function $$render<TData extends RowData, TSelected = object>(): {
|
|
4
|
+
props: {
|
|
5
|
+
table: DataTable<TData, TSelected>;
|
|
6
|
+
triggerLabel?: string;
|
|
7
|
+
};
|
|
8
|
+
exports: {};
|
|
9
|
+
bindings: "";
|
|
10
|
+
slots: {};
|
|
11
|
+
events: {};
|
|
12
|
+
};
|
|
13
|
+
declare class __sveltets_Render<TData extends RowData, TSelected = object> {
|
|
14
|
+
props(): ReturnType<typeof $$render<TData, TSelected>>['props'];
|
|
15
|
+
events(): ReturnType<typeof $$render<TData, TSelected>>['events'];
|
|
16
|
+
slots(): ReturnType<typeof $$render<TData, TSelected>>['slots'];
|
|
17
|
+
bindings(): "";
|
|
18
|
+
exports(): {};
|
|
19
|
+
}
|
|
20
|
+
interface $$IsomorphicComponent {
|
|
21
|
+
new <TData extends RowData, TSelected = object>(options: import('svelte').ComponentConstructorOptions<ReturnType<__sveltets_Render<TData, TSelected>['props']>>): import('svelte').SvelteComponent<ReturnType<__sveltets_Render<TData, TSelected>['props']>, ReturnType<__sveltets_Render<TData, TSelected>['events']>, ReturnType<__sveltets_Render<TData, TSelected>['slots']>> & {
|
|
22
|
+
$$bindings?: ReturnType<__sveltets_Render<TData, TSelected>['bindings']>;
|
|
23
|
+
} & ReturnType<__sveltets_Render<TData, TSelected>['exports']>;
|
|
24
|
+
<TData extends RowData, TSelected = object>(internal: unknown, props: ReturnType<__sveltets_Render<TData, TSelected>['props']> & {}): ReturnType<__sveltets_Render<TData, TSelected>['exports']>;
|
|
25
|
+
z_$$bindings?: ReturnType<__sveltets_Render<any, any>['bindings']>;
|
|
26
|
+
}
|
|
27
|
+
declare const DataTableFilters: $$IsomorphicComponent;
|
|
28
|
+
type DataTableFilters<TData extends RowData, TSelected = object> = InstanceType<typeof DataTableFilters<TData, TSelected>>;
|
|
29
|
+
export default DataTableFilters;
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
import { cn, type ClassValue } from 'tailwind-variants';
|
|
3
|
+
import type { HTMLAttributes } from 'svelte/elements';
|
|
4
|
+
import type { Snippet } from 'svelte';
|
|
5
|
+
|
|
6
|
+
type Props = Omit<HTMLAttributes<HTMLHeadingElement>, 'class'> & {
|
|
7
|
+
class?: ClassValue;
|
|
8
|
+
children?: Snippet;
|
|
9
|
+
};
|
|
10
|
+
|
|
11
|
+
let { class: className, children, ...rest }: Props = $props();
|
|
12
|
+
</script>
|
|
13
|
+
|
|
14
|
+
<h2 class={cn('text-lg font-semibold text-ink', className)} {...rest}>
|
|
15
|
+
{@render children?.()}
|
|
16
|
+
</h2>
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { type ClassValue } from 'tailwind-variants';
|
|
2
|
+
import type { HTMLAttributes } from 'svelte/elements';
|
|
3
|
+
import type { Snippet } from 'svelte';
|
|
4
|
+
type Props = Omit<HTMLAttributes<HTMLHeadingElement>, 'class'> & {
|
|
5
|
+
class?: ClassValue;
|
|
6
|
+
children?: Snippet;
|
|
7
|
+
};
|
|
8
|
+
declare const DataTableTitle: import("svelte").Component<Props, {}, "">;
|
|
9
|
+
type DataTableTitle = ReturnType<typeof DataTableTitle>;
|
|
10
|
+
export default DataTableTitle;
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
import { cn, type ClassValue } from 'tailwind-variants';
|
|
3
|
+
import type { HTMLAttributes } from 'svelte/elements';
|
|
4
|
+
import type { Snippet } from 'svelte';
|
|
5
|
+
|
|
6
|
+
type Props = Omit<HTMLAttributes<HTMLDivElement>, 'class'> & {
|
|
7
|
+
class?: ClassValue;
|
|
8
|
+
children?: Snippet;
|
|
9
|
+
};
|
|
10
|
+
|
|
11
|
+
let { class: className, children, ...rest }: Props = $props();
|
|
12
|
+
</script>
|
|
13
|
+
|
|
14
|
+
<div class={cn('mb-4 flex flex-wrap items-center justify-between gap-3', className)} {...rest}>
|
|
15
|
+
{@render children?.()}
|
|
16
|
+
</div>
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { type ClassValue } from 'tailwind-variants';
|
|
2
|
+
import type { HTMLAttributes } from 'svelte/elements';
|
|
3
|
+
import type { Snippet } from 'svelte';
|
|
4
|
+
type Props = Omit<HTMLAttributes<HTMLDivElement>, 'class'> & {
|
|
5
|
+
class?: ClassValue;
|
|
6
|
+
children?: Snippet;
|
|
7
|
+
};
|
|
8
|
+
declare const DataTableToolbar: import("svelte").Component<Props, {}, "">;
|
|
9
|
+
type DataTableToolbar = ReturnType<typeof DataTableToolbar>;
|
|
10
|
+
export default DataTableToolbar;
|