@fastnd/components 1.0.30 → 1.0.31

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.
Files changed (34) hide show
  1. package/dist/fastnd-components.js +1 -1
  2. package/package.json +1 -1
  3. package/dist/examples/data-explorer/CardCarouselPanel/CardCarouselPanel.tsx +0 -197
  4. package/dist/examples/data-explorer/CardView/CardView.tsx +0 -168
  5. package/dist/examples/data-explorer/ColumnConfigPopover/ColumnConfigPopover.tsx +0 -157
  6. package/dist/examples/data-explorer/DataExplorerEmpty/DataExplorerEmpty.tsx +0 -56
  7. package/dist/examples/data-explorer/DataExplorerPage/DataExplorerPage.tsx +0 -101
  8. package/dist/examples/data-explorer/DataExplorerPagination/DataExplorerPagination.tsx +0 -129
  9. package/dist/examples/data-explorer/DataExplorerToolbar/DataExplorerToolbar.tsx +0 -143
  10. package/dist/examples/data-explorer/DomainSwitcher/DomainSwitcher.tsx +0 -36
  11. package/dist/examples/data-explorer/ExpansionRows/ExpansionRows.tsx +0 -180
  12. package/dist/examples/data-explorer/FilterChip/FilterChip.tsx +0 -85
  13. package/dist/examples/data-explorer/FilterPopoverContent/FilterPopoverContent.tsx +0 -73
  14. package/dist/examples/data-explorer/ListView/ListView.tsx +0 -305
  15. package/dist/examples/data-explorer/MoreFiltersPopover/MoreFiltersPopover.tsx +0 -113
  16. package/dist/examples/data-explorer/TableView/TableView.tsx +0 -193
  17. package/dist/examples/data-explorer/cells/CellRenderer.tsx +0 -147
  18. package/dist/examples/data-explorer/cells/CurrencyCell.tsx +0 -31
  19. package/dist/examples/data-explorer/cells/DoubleTextCell.tsx +0 -27
  20. package/dist/examples/data-explorer/cells/ExpandButton.tsx +0 -67
  21. package/dist/examples/data-explorer/cells/InventoryBadgeCell.tsx +0 -52
  22. package/dist/examples/data-explorer/cells/LinkCell.tsx +0 -42
  23. package/dist/examples/data-explorer/cells/ScoreBar.tsx +0 -50
  24. package/dist/examples/data-explorer/cells/StatusBadgeCell.tsx +0 -39
  25. package/dist/examples/data-explorer/cells/TextCell.tsx +0 -35
  26. package/dist/examples/data-explorer/cells/index.ts +0 -26
  27. package/dist/examples/data-explorer/domains/applications.ts +0 -225
  28. package/dist/examples/data-explorer/domains/customers.ts +0 -267
  29. package/dist/examples/data-explorer/domains/index.ts +0 -26
  30. package/dist/examples/data-explorer/domains/products.ts +0 -1116
  31. package/dist/examples/data-explorer/domains/projects.ts +0 -205
  32. package/dist/examples/data-explorer/hooks/use-data-explorer-state.ts +0 -371
  33. package/dist/examples/data-explorer/index.ts +0 -3
  34. package/dist/examples/data-explorer/types.ts +0 -239
@@ -1,305 +0,0 @@
1
- import * as React from 'react'
2
- import {
3
- Table,
4
- TableBody,
5
- TableCell,
6
- TableHead,
7
- TableHeader,
8
- TableRow,
9
- } from '@/components/ui/table'
10
- import { FavoriteButton } from '@/components/FavoriteButton/FavoriteButton'
11
- import { cn } from '@/lib/utils'
12
- import type { ColumnDef, ListLayout } from '../types'
13
- import { CellRenderer } from '../cells'
14
- import { ScoreBar } from '../cells'
15
-
16
- interface ListViewProps {
17
- data: Record<string, unknown>[]
18
- columns: Record<string, ColumnDef>
19
- layout: ListLayout
20
- expandedRows: Set<string>
21
- favorites: Set<string>
22
- onToggleExpand: (rowId: string) => void
23
- onToggleFavorite: (rowId: string) => void
24
- className?: string
25
- }
26
-
27
- // ---------------------------------------------------------------------------
28
- // Expansion table rendered inline below a list item when a field is expanded
29
- // ---------------------------------------------------------------------------
30
-
31
- interface ExpansionTableProps {
32
- row: Record<string, unknown>
33
- columnKey: string
34
- column: ColumnDef
35
- }
36
-
37
- const ExpansionTable = ({ row, columnKey, column }: ExpansionTableProps) => {
38
- if (!column.expandColumns || column.expandColumns.length === 0) return null
39
-
40
- const items = row[columnKey]
41
- if (!Array.isArray(items) || items.length === 0) return null
42
-
43
- const title = column.expandTitleFn
44
- ? column.expandTitleFn(row)
45
- : column.expandLabel ?? columnKey
46
-
47
- return (
48
- <div data-slot="expansion-table" className="border rounded-lg overflow-hidden bg-muted/30">
49
- <div className="px-3 py-2 border-b bg-muted/50">
50
- <span className="text-xs font-medium text-foreground">{title}</span>
51
- <span className="ml-2 text-xs text-muted-foreground">({items.length})</span>
52
- </div>
53
- <Table>
54
- <TableHeader>
55
- <TableRow>
56
- {column.expandColumns.map((ec) => (
57
- <TableHead key={ec.key} className="text-xs h-8 px-3">
58
- {ec.label}
59
- </TableHead>
60
- ))}
61
- </TableRow>
62
- </TableHeader>
63
- <TableBody>
64
- {(items as Record<string, unknown>[]).map((item, idx) => (
65
- <TableRow key={idx}>
66
- {column.expandColumns!.map((ec) => {
67
- const value = item[ec.key]
68
-
69
- if (ec.type === 'score-bar') {
70
- const numValue = typeof value === 'number' ? value : 0
71
- return (
72
- <TableCell key={ec.key} className="px-3 py-1.5">
73
- <ScoreBar value={numValue} />
74
- </TableCell>
75
- )
76
- }
77
-
78
- let display: React.ReactNode
79
-
80
- if (ec.bold) {
81
- display = (
82
- <span className="font-medium text-xs text-foreground">
83
- {value != null ? String(value) : '—'}
84
- </span>
85
- )
86
- } else if (ec.muted) {
87
- display = (
88
- <span className="text-xs text-muted-foreground">
89
- {value != null ? String(value) : '—'}
90
- </span>
91
- )
92
- } else {
93
- display = (
94
- <span className="text-xs">
95
- {typeof value === 'boolean'
96
- ? value
97
- ? 'Ja'
98
- : 'Nein'
99
- : value != null
100
- ? String(value)
101
- : '—'}
102
- </span>
103
- )
104
- }
105
-
106
- return (
107
- <TableCell key={ec.key} className="px-3 py-1.5">
108
- {display}
109
- </TableCell>
110
- )
111
- })}
112
- </TableRow>
113
- ))}
114
- </TableBody>
115
- </Table>
116
- </div>
117
- )
118
- }
119
-
120
- ExpansionTable.displayName = 'ExpansionTable'
121
-
122
- // ---------------------------------------------------------------------------
123
- // Single list item — CSS Grid row, no Card
124
- // ---------------------------------------------------------------------------
125
-
126
- interface ListItemProps {
127
- row: Record<string, unknown>
128
- columns: Record<string, ColumnDef>
129
- layout: ListLayout
130
- expandedRows: Set<string>
131
- isFavorite: boolean
132
- isLast: boolean
133
- onToggleExpand: (rowId: string) => void
134
- onToggleFavorite: (rowId: string) => void
135
- }
136
-
137
- const ListItem = ({
138
- row,
139
- columns,
140
- layout,
141
- expandedRows,
142
- isFavorite,
143
- isLast,
144
- onToggleExpand,
145
- onToggleFavorite,
146
- }: ListItemProps) => {
147
- const rowId = row['id'] as string
148
- const expandFields = layout.expandFields ?? (layout.expandField ? [layout.expandField] : [])
149
-
150
- const activeExpandField = expandFields.find((f) => expandedRows.has(`${rowId}::${f}`))
151
-
152
- const titleCol = columns[layout.titleField]
153
- const subtitle =
154
- titleCol?.type === 'double-text' && titleCol.secondary
155
- ? (row[titleCol.secondary] as string | null | undefined)
156
- : undefined
157
-
158
- const borderClass = isLast && !activeExpandField ? '' : 'border-b border-border'
159
-
160
- return (
161
- <>
162
- {/* Main grid row */}
163
- <div
164
- data-slot="list-item"
165
- className={cn(
166
- 'grid grid-cols-[36px_1fr_auto_auto] gap-x-3 items-center py-3 px-4 hover:bg-accent transition-colors',
167
- borderClass,
168
- )}
169
- >
170
- {/* Col 1 (row-span-2): Favorite */}
171
- <div className="row-span-2 self-center">
172
- <FavoriteButton
173
- pressed={isFavorite}
174
- itemName={String(row[layout.titleField] ?? rowId)}
175
- onPressedChange={() => onToggleFavorite(rowId)}
176
- />
177
- </div>
178
-
179
- {/* Col 2 Row 1: Title */}
180
- <div className="font-semibold text-sm max-w-[280px] truncate">
181
- {row[layout.titleField] != null ? String(row[layout.titleField]) : '—'}
182
- </div>
183
-
184
- {/* Col 3 (row-span-2): Badges */}
185
- <div className="row-span-2 flex gap-1.5 items-center self-center">
186
- {layout.badgeFields?.map((field) => {
187
- const col = columns[field]
188
- if (!col) return null
189
- return (
190
- <CellRenderer
191
- key={field}
192
- column={col}
193
- columnKey={field}
194
- row={row}
195
- />
196
- )
197
- })}
198
- </div>
199
-
200
- {/* Col 4 (row-span-2): Expand actions */}
201
- <div className="row-span-2 flex gap-1 items-center self-center">
202
- {expandFields.map((field) => {
203
- const col = columns[field]
204
- if (!col) return null
205
- const expansionKey = `${rowId}::${field}`
206
- return (
207
- <CellRenderer
208
- key={field}
209
- column={col}
210
- columnKey={field}
211
- row={row}
212
- expanded={expandedRows.has(expansionKey)}
213
- onToggleExpand={() => onToggleExpand(expansionKey)}
214
- />
215
- )
216
- })}
217
- </div>
218
-
219
- {/* Col 2 Row 2: Subtitle + meta chips */}
220
- <div className="flex gap-1.5 items-center mt-0.5 min-w-0">
221
- {subtitle && (
222
- <span className="text-xs text-muted-foreground max-w-[320px] truncate">
223
- {subtitle}
224
- </span>
225
- )}
226
- {layout.metaFields?.map((field) => {
227
- const value = row[field]
228
- if (value == null || value === '') return null
229
- return (
230
- <span
231
- key={field}
232
- className="bg-secondary rounded-sm px-2 py-0.5 text-xs text-muted-foreground shrink-0"
233
- >
234
- {String(value)}
235
- </span>
236
- )
237
- })}
238
- </div>
239
- </div>
240
-
241
- {/* Expansion content */}
242
- {activeExpandField && columns[activeExpandField] && (
243
- <div
244
- data-slot="expansion-row"
245
- className={cn(
246
- 'py-3 pl-[52px] pr-4',
247
- isLast ? '' : 'border-b border-border',
248
- )}
249
- >
250
- <ExpansionTable
251
- row={row}
252
- columnKey={activeExpandField}
253
- column={columns[activeExpandField]}
254
- />
255
- </div>
256
- )}
257
- </>
258
- )
259
- }
260
-
261
- ListItem.displayName = 'ListItem'
262
-
263
- // ---------------------------------------------------------------------------
264
- // ListView — single bordered container, items separated by border-bottom
265
- // ---------------------------------------------------------------------------
266
-
267
- const ListView = ({
268
- data,
269
- columns,
270
- layout,
271
- expandedRows,
272
- favorites,
273
- onToggleExpand,
274
- onToggleFavorite,
275
- className,
276
- }: ListViewProps) => {
277
- return (
278
- <div
279
- data-slot="list-view"
280
- className={cn('border border-border rounded-lg mx-6 overflow-hidden', className)}
281
- >
282
- {data.map((row, index) => {
283
- const rowId = row['id'] as string
284
- return (
285
- <ListItem
286
- key={rowId}
287
- row={row}
288
- columns={columns}
289
- layout={layout}
290
- expandedRows={expandedRows}
291
- isFavorite={favorites.has(rowId)}
292
- isLast={index === data.length - 1}
293
- onToggleExpand={onToggleExpand}
294
- onToggleFavorite={onToggleFavorite}
295
- />
296
- )
297
- })}
298
- </div>
299
- )
300
- }
301
-
302
- ListView.displayName = 'ListView'
303
-
304
- export { ListView }
305
- export type { ListViewProps }
@@ -1,113 +0,0 @@
1
- import { ChevronDown, ListFilter } from 'lucide-react'
2
- import { Badge } from '@/components/ui/badge'
3
- import { Button } from '@/components/ui/button'
4
- import { Collapsible, CollapsibleContent, CollapsibleTrigger } from '@/components/ui/collapsible'
5
- import { Popover, PopoverContent, PopoverTrigger } from '@/components/ui/popover'
6
- import { FilterPopoverContent } from '../FilterPopoverContent/FilterPopoverContent'
7
- import type { ColumnDef } from '../types'
8
- import { cn } from '@/lib/utils'
9
-
10
- export interface MoreFiltersPopoverProps {
11
- columns: [string, ColumnDef][]
12
- filters: Record<string, string[]>
13
- getFilterOptions: (columnKey: string) => string[]
14
- onToggleOption: (columnKey: string, value: string) => void
15
- onResetSecondary: () => void
16
- totalSecondaryCount: number
17
- className?: string
18
- }
19
-
20
- export function MoreFiltersPopover({
21
- columns,
22
- filters,
23
- getFilterOptions,
24
- onToggleOption,
25
- onResetSecondary,
26
- totalSecondaryCount,
27
- className,
28
- }: MoreFiltersPopoverProps) {
29
- if (columns.length === 0) return null
30
-
31
- return (
32
- <Popover>
33
- <PopoverTrigger asChild>
34
- <Button
35
- data-slot="more-filters-popover"
36
- variant="outline"
37
- size="sm"
38
- aria-haspopup="dialog"
39
- aria-label="Weitere Filter"
40
- className={cn(
41
- 'h-8 gap-1.5 text-sm',
42
- totalSecondaryCount > 0 && 'border-primary/50 bg-primary/5 text-primary hover:bg-primary/10',
43
- className,
44
- )}
45
- >
46
- <ListFilter className="size-3.5" />
47
- <span>Weitere Filter</span>
48
- {totalSecondaryCount > 0 && (
49
- <Badge variant="default" className="h-4.5 min-w-4 px-1.5 py-0 text-[10px]">
50
- {totalSecondaryCount}
51
- </Badge>
52
- )}
53
- </Button>
54
- </PopoverTrigger>
55
-
56
- <PopoverContent
57
- align="start"
58
- sideOffset={4}
59
- role="dialog"
60
- aria-label="Weitere Filter"
61
- className="w-80 p-0"
62
- >
63
- <div className="flex items-center justify-between border-b px-3 py-2">
64
- <span className="text-sm font-medium">Weitere Filter</span>
65
- {totalSecondaryCount > 0 && (
66
- <Button
67
- variant="ghost"
68
- size="sm"
69
- onClick={onResetSecondary}
70
- className="h-7 px-2 text-xs text-muted-foreground hover:text-destructive"
71
- >
72
- Alle zurücksetzen
73
- </Button>
74
- )}
75
- </div>
76
-
77
- <div className="max-h-[420px] overflow-y-auto">
78
- {columns.map(([colKey, col]) => {
79
- const selected = filters[colKey] ?? []
80
- const options = getFilterOptions(colKey)
81
- return (
82
- <Collapsible key={colKey} defaultOpen>
83
- <CollapsibleTrigger className="group flex w-full items-center justify-between px-3 py-2 text-left text-sm font-medium hover:bg-accent">
84
- <span className="flex items-center gap-2">
85
- {col.label}
86
- {selected.length > 0 && (
87
- <Badge variant="default" className="h-4 min-w-4 px-1.5 py-0 text-[10px]">
88
- {selected.length}
89
- </Badge>
90
- )}
91
- </span>
92
- <ChevronDown className="size-3.5 text-muted-foreground transition-transform group-data-[state=open]:rotate-180" />
93
- </CollapsibleTrigger>
94
- <CollapsibleContent>
95
- <div className="px-3 pb-3 pt-1">
96
- <FilterPopoverContent
97
- options={options}
98
- selectedValues={selected}
99
- onToggleOption={(value) => onToggleOption(colKey, value)}
100
- labelMap={col.labelMap}
101
- />
102
- </div>
103
- </CollapsibleContent>
104
- </Collapsible>
105
- )
106
- })}
107
- </div>
108
- </PopoverContent>
109
- </Popover>
110
- )
111
- }
112
-
113
- MoreFiltersPopover.displayName = 'MoreFiltersPopover'
@@ -1,193 +0,0 @@
1
- import * as React from 'react'
2
- import { ChevronUp, ChevronDown, ChevronsUpDown } from 'lucide-react'
3
- import { cn } from '@/lib/utils'
4
- import {
5
- Table,
6
- TableBody,
7
- TableHead,
8
- TableHeader,
9
- TableRow,
10
- TableCell,
11
- } from '@/components/ui/table'
12
- import { CellRenderer } from '../cells'
13
- import { ExpansionRows } from '../ExpansionRows/ExpansionRows'
14
- import type { ColumnDef, SortDirection } from '../types'
15
-
16
- interface TableViewProps {
17
- data: Record<string, unknown>[]
18
- columns: Record<string, ColumnDef>
19
- visibleColumns: string[]
20
- sortColumn: string | null
21
- sortDirection: SortDirection
22
- expandedRows: Set<string>
23
- favorites: Set<string>
24
- onToggleSort: (column: string) => void
25
- onToggleExpand: (rowId: string) => void
26
- onToggleFavorite: (rowId: string) => void
27
- className?: string
28
- }
29
-
30
- const SortIcon = ({
31
- columnKey,
32
- sortColumn,
33
- sortDirection,
34
- }: {
35
- columnKey: string
36
- sortColumn: string | null
37
- sortDirection: SortDirection
38
- }) => {
39
- if (sortColumn !== columnKey) {
40
- return <ChevronsUpDown className="size-3.5 text-muted-foreground/60" aria-hidden />
41
- }
42
- return sortDirection === 'asc' ? (
43
- <ChevronUp className="size-3.5" aria-hidden />
44
- ) : (
45
- <ChevronDown className="size-3.5" aria-hidden />
46
- )
47
- }
48
-
49
- const TableView = ({
50
- data,
51
- columns,
52
- visibleColumns,
53
- sortColumn,
54
- sortDirection,
55
- expandedRows,
56
- favorites,
57
- onToggleSort,
58
- onToggleExpand,
59
- onToggleFavorite,
60
- className,
61
- }: TableViewProps) => {
62
- return (
63
- <div
64
- data-slot="table-view"
65
- className={cn('border border-border rounded-lg mx-6 overflow-hidden', className)}
66
- >
67
- <div
68
- role="region"
69
- aria-label="Datentabelle"
70
- tabIndex={0}
71
- className="overflow-auto focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring/50"
72
- >
73
- <Table>
74
- <TableHeader className="bg-accent">
75
- <TableRow className="hover:bg-transparent">
76
- {visibleColumns.map((columnKey) => {
77
- const column = columns[columnKey]
78
- if (!column) return null
79
-
80
- const isSorted = sortColumn === columnKey
81
- const ariaSortValue: React.AriaAttributes['aria-sort'] = isSorted
82
- ? sortDirection === 'asc'
83
- ? 'ascending'
84
- : 'descending'
85
- : undefined
86
-
87
- return (
88
- <TableHead
89
- key={columnKey}
90
- scope="col"
91
- aria-sort={ariaSortValue}
92
- onClick={column.sortable ? () => onToggleSort(columnKey) : undefined}
93
- className={cn(
94
- 'text-xs font-semibold uppercase tracking-wide text-muted-foreground px-3 py-2.5 h-auto',
95
- column.sortable && 'cursor-pointer select-none',
96
- column.hideMobile && 'hidden sm:table-cell',
97
- column.hideTablet && 'hidden lg:table-cell',
98
- )}
99
- >
100
- <span className="inline-flex items-center gap-1">
101
- {column.label}
102
- {column.sortable && (
103
- <SortIcon
104
- columnKey={columnKey}
105
- sortColumn={sortColumn}
106
- sortDirection={sortDirection}
107
- />
108
- )}
109
- </span>
110
- </TableHead>
111
- )
112
- })}
113
- </TableRow>
114
- </TableHeader>
115
-
116
- <TableBody>
117
- {data.map((row) => {
118
- const rowId = row['id'] as string
119
-
120
- // Collect which expand columns are currently expanded for this row
121
- const expandedColumnKeys = visibleColumns.filter((columnKey) => {
122
- const column = columns[columnKey]
123
- return column?.type === 'expand' && expandedRows.has(`${rowId}::${columnKey}`)
124
- })
125
-
126
- const isExpanded = expandedColumnKeys.length > 0
127
-
128
- return (
129
- <React.Fragment key={rowId}>
130
- <TableRow className={cn(isExpanded && 'bg-primary/[0.03]')}>
131
- {visibleColumns.map((columnKey) => {
132
- const column = columns[columnKey]
133
- if (!column) return null
134
-
135
- const expandKey = `${rowId}::${columnKey}`
136
- const isExpandedCell = expandedRows.has(expandKey)
137
-
138
- return (
139
- <TableCell
140
- key={columnKey}
141
- className={cn(
142
- 'px-3 py-2.5 text-[13px]',
143
- column.hideMobile && 'hidden sm:table-cell',
144
- column.hideTablet && 'hidden lg:table-cell',
145
- )}
146
- >
147
- <CellRenderer
148
- column={column}
149
- columnKey={columnKey}
150
- row={row}
151
- expanded={isExpandedCell}
152
- onToggleExpand={
153
- column.type === 'expand'
154
- ? () => onToggleExpand(expandKey)
155
- : undefined
156
- }
157
- onToggleFavorite={
158
- column.type === 'favorite'
159
- ? () => onToggleFavorite(rowId)
160
- : undefined
161
- }
162
- isFavorite={favorites.has(rowId)}
163
- />
164
- </TableCell>
165
- )
166
- })}
167
- </TableRow>
168
-
169
- {expandedColumnKeys.map((columnKey) => (
170
- <ExpansionRows
171
- key={`${rowId}::${columnKey}::expansion`}
172
- row={row}
173
- columnKey={columnKey}
174
- column={columns[columnKey]}
175
- visibleColumns={visibleColumns}
176
- columns={columns}
177
- colSpan={visibleColumns.length}
178
- />
179
- ))}
180
- </React.Fragment>
181
- )
182
- })}
183
- </TableBody>
184
- </Table>
185
- </div>
186
- </div>
187
- )
188
- }
189
-
190
- TableView.displayName = 'TableView'
191
-
192
- export { TableView }
193
- export type { TableViewProps }