@fastnd/components 1.0.31 → 1.0.32
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/QuickAccessCard/QuickAccessCard.d.ts +12 -0
- package/dist/components/ScoreBar/ScoreBar.d.ts +8 -0
- package/dist/components/index.d.ts +2 -0
- package/dist/examples/dashboard/StatusDonutChart/StatusDonutChart.tsx +41 -56
- package/dist/examples/data-visualization/CardCarouselPanel/CardCarouselPanel.tsx +171 -0
- package/dist/examples/data-visualization/CellRenderers/CellRenderers.tsx +175 -0
- package/dist/examples/data-visualization/ColumnConfigPopover/ColumnConfigPopover.tsx +124 -0
- package/dist/examples/data-visualization/DataCardView/DataCardView.tsx +223 -0
- package/dist/examples/data-visualization/DataExplorerPagination/DataExplorerPagination.tsx +143 -0
- package/dist/examples/data-visualization/DataExplorerToolbar/DataExplorerToolbar.tsx +88 -0
- package/dist/examples/data-visualization/DataListView/DataListView.tsx +246 -0
- package/dist/examples/data-visualization/DataTableView/DataTableView.tsx +449 -0
- package/dist/examples/data-visualization/DataVisualizationPage/DataVisualizationPage.tsx +140 -0
- package/dist/examples/data-visualization/FilterChipGroup/FilterChipGroup.tsx +125 -0
- package/dist/examples/data-visualization/MoreFiltersPopover/MoreFiltersPopover.tsx +132 -0
- package/dist/examples/data-visualization/constants.ts +587 -0
- package/dist/examples/data-visualization/hooks/use-column-config.ts +76 -0
- package/dist/examples/data-visualization/hooks/use-data-explorer-state.ts +313 -0
- package/dist/examples/data-visualization/index.ts +1 -0
- package/dist/examples/data-visualization/types.ts +99 -0
- package/dist/examples/quickaccess/QuickAccess/QuickAccess.tsx +97 -0
- package/dist/examples/quickaccess/index.ts +2 -0
- package/dist/examples/quickaccess/types.ts +11 -0
- package/dist/fastnd-components.js +5708 -5590
- package/package.json +1 -1
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
import React, { useState } from 'react'
|
|
2
|
+
import { ChevronDown, X } from 'lucide-react'
|
|
3
|
+
import { Button } from '@/components/ui/button'
|
|
4
|
+
import { Popover, PopoverContent, PopoverTrigger } from '@/components/ui/popover'
|
|
5
|
+
import { Checkbox } from '@/components/ui/checkbox'
|
|
6
|
+
import { ScrollArea } from '@/components/ui/scroll-area'
|
|
7
|
+
import { Input } from '@/components/ui/input'
|
|
8
|
+
import { cn } from '@/lib/utils'
|
|
9
|
+
import type { ColumnDef, FilterState } from '../types'
|
|
10
|
+
|
|
11
|
+
export interface FilterChipGroupProps {
|
|
12
|
+
columns: Record<string, ColumnDef>
|
|
13
|
+
filters: FilterState
|
|
14
|
+
onToggleFilter: (column: string, value: string) => void
|
|
15
|
+
onClearFilter: (column: string) => void
|
|
16
|
+
getFilterOptions: (colKey: string) => string[]
|
|
17
|
+
className?: string
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
interface FilterChipProps {
|
|
21
|
+
colKey: string
|
|
22
|
+
col: ColumnDef
|
|
23
|
+
selected: string[]
|
|
24
|
+
onToggleFilter: (column: string, value: string) => void
|
|
25
|
+
onClearFilter: (column: string) => void
|
|
26
|
+
getFilterOptions: (colKey: string) => string[]
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
function FilterChip({
|
|
30
|
+
colKey,
|
|
31
|
+
col,
|
|
32
|
+
selected,
|
|
33
|
+
onToggleFilter,
|
|
34
|
+
onClearFilter,
|
|
35
|
+
getFilterOptions,
|
|
36
|
+
}: FilterChipProps) {
|
|
37
|
+
const [search, setSearch] = useState('')
|
|
38
|
+
const isActive = selected.length > 0
|
|
39
|
+
const options = getFilterOptions(colKey)
|
|
40
|
+
const filteredOptions = search
|
|
41
|
+
? options.filter((opt) => opt.toLowerCase().includes(search.toLowerCase()))
|
|
42
|
+
: options
|
|
43
|
+
|
|
44
|
+
return (
|
|
45
|
+
<Popover>
|
|
46
|
+
<PopoverTrigger asChild>
|
|
47
|
+
<Button
|
|
48
|
+
variant="outline"
|
|
49
|
+
size="sm"
|
|
50
|
+
className={cn(isActive && 'border-primary text-primary bg-primary/5')}
|
|
51
|
+
>
|
|
52
|
+
{col.label}
|
|
53
|
+
{isActive && ` (${selected.length})`}
|
|
54
|
+
{isActive ? (
|
|
55
|
+
<X
|
|
56
|
+
className="ml-1 size-3.5"
|
|
57
|
+
aria-label="Filter entfernen"
|
|
58
|
+
onClick={(e) => {
|
|
59
|
+
e.stopPropagation()
|
|
60
|
+
onClearFilter(colKey)
|
|
61
|
+
}}
|
|
62
|
+
/>
|
|
63
|
+
) : (
|
|
64
|
+
<ChevronDown className="ml-1 size-3.5" />
|
|
65
|
+
)}
|
|
66
|
+
</Button>
|
|
67
|
+
</PopoverTrigger>
|
|
68
|
+
<PopoverContent className="w-60 p-0" align="start">
|
|
69
|
+
<div className="p-2 border-b border-border">
|
|
70
|
+
<Input
|
|
71
|
+
placeholder="Suchen..."
|
|
72
|
+
value={search}
|
|
73
|
+
onChange={(e) => setSearch(e.target.value)}
|
|
74
|
+
className="h-8"
|
|
75
|
+
/>
|
|
76
|
+
</div>
|
|
77
|
+
<ScrollArea className="max-h-[300px]">
|
|
78
|
+
<div className="p-1" role="listbox" aria-label={`${col.label} Filter`}>
|
|
79
|
+
{filteredOptions.map((opt) => (
|
|
80
|
+
<div
|
|
81
|
+
key={opt}
|
|
82
|
+
role="option"
|
|
83
|
+
aria-selected={selected.includes(opt)}
|
|
84
|
+
className="flex items-center gap-2 px-2 py-1.5 rounded-md hover:bg-accent cursor-pointer"
|
|
85
|
+
onClick={() => onToggleFilter(colKey, opt)}
|
|
86
|
+
>
|
|
87
|
+
<Checkbox checked={selected.includes(opt)} tabIndex={-1} />
|
|
88
|
+
<span className="text-sm">{opt}</span>
|
|
89
|
+
</div>
|
|
90
|
+
))}
|
|
91
|
+
</div>
|
|
92
|
+
</ScrollArea>
|
|
93
|
+
</PopoverContent>
|
|
94
|
+
</Popover>
|
|
95
|
+
)
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
export function FilterChipGroup({
|
|
99
|
+
columns,
|
|
100
|
+
filters,
|
|
101
|
+
onToggleFilter,
|
|
102
|
+
onClearFilter,
|
|
103
|
+
getFilterOptions,
|
|
104
|
+
className,
|
|
105
|
+
}: FilterChipGroupProps) {
|
|
106
|
+
const primaryFilterEntries = Object.entries(columns).filter(
|
|
107
|
+
([, col]) => col.filterable && col.primaryFilter,
|
|
108
|
+
)
|
|
109
|
+
|
|
110
|
+
return (
|
|
111
|
+
<div className={cn('flex flex-wrap items-center gap-2', className)}>
|
|
112
|
+
{primaryFilterEntries.map(([colKey, col]) => (
|
|
113
|
+
<FilterChip
|
|
114
|
+
key={colKey}
|
|
115
|
+
colKey={colKey}
|
|
116
|
+
col={col}
|
|
117
|
+
selected={filters[colKey] ?? []}
|
|
118
|
+
onToggleFilter={onToggleFilter}
|
|
119
|
+
onClearFilter={onClearFilter}
|
|
120
|
+
getFilterOptions={getFilterOptions}
|
|
121
|
+
/>
|
|
122
|
+
))}
|
|
123
|
+
</div>
|
|
124
|
+
)
|
|
125
|
+
}
|
|
@@ -0,0 +1,132 @@
|
|
|
1
|
+
import React, { useState } from 'react'
|
|
2
|
+
import { Filter, ChevronDown } from 'lucide-react'
|
|
3
|
+
import { Button } from '@/components/ui/button'
|
|
4
|
+
import { Badge } from '@/components/ui/badge'
|
|
5
|
+
import { Popover, PopoverContent, PopoverTrigger } from '@/components/ui/popover'
|
|
6
|
+
import { Collapsible, CollapsibleContent, CollapsibleTrigger } from '@/components/ui/collapsible'
|
|
7
|
+
import { Checkbox } from '@/components/ui/checkbox'
|
|
8
|
+
import { ScrollArea } from '@/components/ui/scroll-area'
|
|
9
|
+
import { Input } from '@/components/ui/input'
|
|
10
|
+
import { cn } from '@/lib/utils'
|
|
11
|
+
import type { ColumnDef, FilterState } from '../types'
|
|
12
|
+
|
|
13
|
+
export interface MoreFiltersPopoverProps {
|
|
14
|
+
columns: Record<string, ColumnDef>
|
|
15
|
+
filters: FilterState
|
|
16
|
+
onToggleFilter: (column: string, value: string) => void
|
|
17
|
+
onClearSecondaryFilters: () => void
|
|
18
|
+
getFilterOptions: (colKey: string) => string[]
|
|
19
|
+
className?: string
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
interface SectionProps {
|
|
23
|
+
colKey: string
|
|
24
|
+
col: ColumnDef
|
|
25
|
+
selected: string[]
|
|
26
|
+
onToggleFilter: (column: string, value: string) => void
|
|
27
|
+
getFilterOptions: (colKey: string) => string[]
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
function FilterSection({ colKey, col, selected, onToggleFilter, getFilterOptions }: SectionProps) {
|
|
31
|
+
const [search, setSearch] = useState('')
|
|
32
|
+
const options = getFilterOptions(colKey)
|
|
33
|
+
const filteredOptions = search
|
|
34
|
+
? options.filter((opt) => opt.toLowerCase().includes(search.toLowerCase()))
|
|
35
|
+
: options
|
|
36
|
+
const count = selected.length
|
|
37
|
+
|
|
38
|
+
return (
|
|
39
|
+
<Collapsible>
|
|
40
|
+
<CollapsibleTrigger className="flex items-center justify-between w-full px-3 py-2 hover:bg-accent text-left">
|
|
41
|
+
<span className="text-sm">{col.label}</span>
|
|
42
|
+
<div className="flex items-center gap-1.5">
|
|
43
|
+
{count > 0 && <Badge variant="secondary">{count}</Badge>}
|
|
44
|
+
<ChevronDown className="size-4 transition-transform data-[state=open]:rotate-180" />
|
|
45
|
+
</div>
|
|
46
|
+
</CollapsibleTrigger>
|
|
47
|
+
<CollapsibleContent>
|
|
48
|
+
<div className="px-2 pb-1.5 pt-0">
|
|
49
|
+
<div className="px-1 pb-1.5">
|
|
50
|
+
<Input
|
|
51
|
+
placeholder="Suchen..."
|
|
52
|
+
value={search}
|
|
53
|
+
onChange={(e) => setSearch(e.target.value)}
|
|
54
|
+
className="h-8"
|
|
55
|
+
/>
|
|
56
|
+
</div>
|
|
57
|
+
<div role="listbox" aria-label={`${col.label} Filter`}>
|
|
58
|
+
{filteredOptions.map((opt) => (
|
|
59
|
+
<div
|
|
60
|
+
key={opt}
|
|
61
|
+
role="option"
|
|
62
|
+
aria-selected={selected.includes(opt)}
|
|
63
|
+
className="flex items-center gap-2 px-2 py-1.5 rounded-md hover:bg-accent cursor-pointer"
|
|
64
|
+
onClick={() => onToggleFilter(colKey, opt)}
|
|
65
|
+
>
|
|
66
|
+
<Checkbox checked={selected.includes(opt)} tabIndex={-1} />
|
|
67
|
+
<span className="text-sm">{opt}</span>
|
|
68
|
+
</div>
|
|
69
|
+
))}
|
|
70
|
+
</div>
|
|
71
|
+
</div>
|
|
72
|
+
</CollapsibleContent>
|
|
73
|
+
</Collapsible>
|
|
74
|
+
)
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
export function MoreFiltersPopover({
|
|
78
|
+
columns,
|
|
79
|
+
filters,
|
|
80
|
+
onToggleFilter,
|
|
81
|
+
onClearSecondaryFilters,
|
|
82
|
+
getFilterOptions,
|
|
83
|
+
className,
|
|
84
|
+
}: MoreFiltersPopoverProps) {
|
|
85
|
+
const secondaryCols = Object.entries(columns).filter(
|
|
86
|
+
([, col]) => col.filterable && !col.primaryFilter,
|
|
87
|
+
)
|
|
88
|
+
const secondaryCount = secondaryCols.reduce((acc, [k]) => acc + (filters[k]?.length || 0), 0)
|
|
89
|
+
const isActive = secondaryCount > 0
|
|
90
|
+
|
|
91
|
+
return (
|
|
92
|
+
<Popover>
|
|
93
|
+
<PopoverTrigger asChild>
|
|
94
|
+
<Button
|
|
95
|
+
variant={isActive ? 'default' : 'outline'}
|
|
96
|
+
size="sm"
|
|
97
|
+
className={cn(className)}
|
|
98
|
+
>
|
|
99
|
+
<Filter className="size-4" />
|
|
100
|
+
Weitere Filter
|
|
101
|
+
{isActive && (
|
|
102
|
+
<Badge variant="secondary" className="ml-1.5">
|
|
103
|
+
{secondaryCount}
|
|
104
|
+
</Badge>
|
|
105
|
+
)}
|
|
106
|
+
</Button>
|
|
107
|
+
</PopoverTrigger>
|
|
108
|
+
<PopoverContent className="w-80 p-0" align="start">
|
|
109
|
+
<div className="flex items-center justify-between p-3 border-b border-border">
|
|
110
|
+
<span className="text-sm font-semibold">Weitere Filter</span>
|
|
111
|
+
{isActive && (
|
|
112
|
+
<Button variant="ghost" size="sm" onClick={onClearSecondaryFilters}>
|
|
113
|
+
Alle zurücksetzen
|
|
114
|
+
</Button>
|
|
115
|
+
)}
|
|
116
|
+
</div>
|
|
117
|
+
<ScrollArea className="max-h-[400px]">
|
|
118
|
+
{secondaryCols.map(([colKey, col]) => (
|
|
119
|
+
<FilterSection
|
|
120
|
+
key={colKey}
|
|
121
|
+
colKey={colKey}
|
|
122
|
+
col={col}
|
|
123
|
+
selected={filters[colKey] ?? []}
|
|
124
|
+
onToggleFilter={onToggleFilter}
|
|
125
|
+
getFilterOptions={getFilterOptions}
|
|
126
|
+
/>
|
|
127
|
+
))}
|
|
128
|
+
</ScrollArea>
|
|
129
|
+
</PopoverContent>
|
|
130
|
+
</Popover>
|
|
131
|
+
)
|
|
132
|
+
}
|