@object-ui/plugin-list 0.5.1 → 2.0.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/.turbo/turbo-build.log +8 -8
- package/CHANGELOG.md +14 -0
- package/dist/index.js +23276 -21531
- package/dist/index.umd.cjs +28 -28
- package/dist/plugin-list.css +1 -1
- package/dist/src/ListView.d.ts +4 -0
- package/dist/src/ListView.d.ts.map +1 -1
- package/package.json +6 -6
- package/src/ListView.tsx +215 -115
- package/src/__tests__/ListView.test.tsx +16 -7
- package/src/__tests__/ListViewPersistence.test.tsx +3 -3
- package/src/index.tsx +22 -0
package/src/ListView.tsx
CHANGED
|
@@ -7,12 +7,12 @@
|
|
|
7
7
|
*/
|
|
8
8
|
|
|
9
9
|
import * as React from 'react';
|
|
10
|
-
import { cn, Button, Input, Popover, PopoverContent, PopoverTrigger, FilterBuilder, SortBuilder } from '@object-ui/components';
|
|
10
|
+
import { cn, Button, Input, Popover, PopoverContent, PopoverTrigger, FilterBuilder, SortBuilder, NavigationOverlay } from '@object-ui/components';
|
|
11
11
|
import type { SortItem } from '@object-ui/components';
|
|
12
|
-
import { Search, SlidersHorizontal, ArrowUpDown, X } from 'lucide-react';
|
|
12
|
+
import { Search, SlidersHorizontal, ArrowUpDown, X, EyeOff, Group, Paintbrush, Ruler } from 'lucide-react';
|
|
13
13
|
import type { FilterGroup } from '@object-ui/components';
|
|
14
14
|
import { ViewSwitcher, ViewType } from './ViewSwitcher';
|
|
15
|
-
import { SchemaRenderer } from '@object-ui/react';
|
|
15
|
+
import { SchemaRenderer, useNavigationOverlay } from '@object-ui/react';
|
|
16
16
|
import type { ListViewSchema } from '@object-ui/types';
|
|
17
17
|
|
|
18
18
|
export interface ListViewProps {
|
|
@@ -22,6 +22,10 @@ export interface ListViewProps {
|
|
|
22
22
|
onFilterChange?: (filters: any) => void;
|
|
23
23
|
onSortChange?: (sort: any) => void;
|
|
24
24
|
onSearchChange?: (search: string) => void;
|
|
25
|
+
/** Callback when a row/item is clicked (overrides NavigationConfig) */
|
|
26
|
+
onRowClick?: (record: Record<string, unknown>) => void;
|
|
27
|
+
/** Show view type switcher (Grid/Kanban/etc). Default: false (view type is fixed) */
|
|
28
|
+
showViewSwitcher?: boolean;
|
|
25
29
|
[key: string]: any;
|
|
26
30
|
}
|
|
27
31
|
|
|
@@ -65,6 +69,8 @@ export const ListView: React.FC<ListViewProps> = ({
|
|
|
65
69
|
onFilterChange,
|
|
66
70
|
onSortChange,
|
|
67
71
|
onSearchChange,
|
|
72
|
+
onRowClick,
|
|
73
|
+
showViewSwitcher = false,
|
|
68
74
|
...props
|
|
69
75
|
}) => {
|
|
70
76
|
// Kernel level default: Ensure viewType is always defined (default to 'grid')
|
|
@@ -272,6 +278,14 @@ export const ListView: React.FC<ListViewProps> = ({
|
|
|
272
278
|
onSearchChange?.(value);
|
|
273
279
|
}, [onSearchChange]);
|
|
274
280
|
|
|
281
|
+
// --- NavigationConfig support ---
|
|
282
|
+
const navigation = useNavigationOverlay({
|
|
283
|
+
navigation: schema.navigation,
|
|
284
|
+
objectName: schema.objectName,
|
|
285
|
+
onNavigate: schema.onNavigate,
|
|
286
|
+
onRowClick,
|
|
287
|
+
});
|
|
288
|
+
|
|
275
289
|
// Generate the appropriate view component schema
|
|
276
290
|
const viewComponentSchema = React.useMemo(() => {
|
|
277
291
|
const baseProps = {
|
|
@@ -282,6 +296,8 @@ export const ListView: React.FC<ListViewProps> = ({
|
|
|
282
296
|
className: "h-full w-full",
|
|
283
297
|
// Disable internal controls that clash with ListView toolbar
|
|
284
298
|
showSearch: false,
|
|
299
|
+
// Pass navigation click handler to child views
|
|
300
|
+
onRowClick: navigation.handleClick,
|
|
285
301
|
};
|
|
286
302
|
|
|
287
303
|
switch (currentView) {
|
|
@@ -374,124 +390,181 @@ export const ListView: React.FC<ListViewProps> = ({
|
|
|
374
390
|
}));
|
|
375
391
|
}, [objectDef, schema.fields]);
|
|
376
392
|
|
|
393
|
+
const [searchExpanded, setSearchExpanded] = React.useState(false);
|
|
394
|
+
|
|
377
395
|
return (
|
|
378
396
|
<div className={cn('flex flex-col h-full bg-background', className)}>
|
|
379
|
-
{/* Airtable-style Toolbar */}
|
|
380
|
-
|
|
381
|
-
<div className="
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
onViewChange={handleViewChange}
|
|
388
|
-
/>
|
|
389
|
-
</div>
|
|
390
|
-
|
|
391
|
-
{/* Action Tools */}
|
|
392
|
-
<div className="flex items-center gap-1">
|
|
393
|
-
<Popover open={showFilters} onOpenChange={setShowFilters}>
|
|
394
|
-
<PopoverTrigger asChild>
|
|
395
|
-
<Button
|
|
396
|
-
variant={hasFilters ? "secondary" : "ghost"}
|
|
397
|
-
size="sm"
|
|
398
|
-
className={cn(
|
|
399
|
-
"h-8 px-2 lg:px-3 text-muted-foreground hover:text-primary",
|
|
400
|
-
hasFilters && "text-primary bg-secondary/50"
|
|
401
|
-
)}
|
|
402
|
-
>
|
|
403
|
-
<SlidersHorizontal className="h-4 w-4 mr-2" />
|
|
404
|
-
<span className="hidden lg:inline">Filter</span>
|
|
405
|
-
{hasFilters && (
|
|
406
|
-
<span className="ml-1.5 flex h-4 w-4 items-center justify-center rounded-full bg-primary/10 text-[10px] font-medium text-primary">
|
|
407
|
-
{currentFilters.conditions?.length || 0}
|
|
408
|
-
</span>
|
|
409
|
-
)}
|
|
410
|
-
</Button>
|
|
411
|
-
</PopoverTrigger>
|
|
412
|
-
<PopoverContent align="start" className="w-[600px] p-4">
|
|
413
|
-
<div className="space-y-4">
|
|
414
|
-
<div className="flex items-center justify-between border-b pb-2">
|
|
415
|
-
<h4 className="font-medium text-sm">Filter Records</h4>
|
|
416
|
-
</div>
|
|
417
|
-
<FilterBuilder
|
|
418
|
-
fields={filterFields}
|
|
419
|
-
value={currentFilters}
|
|
420
|
-
onChange={(newFilters) => {
|
|
421
|
-
console.log('Filter Changed:', newFilters);
|
|
422
|
-
setCurrentFilters(newFilters);
|
|
423
|
-
// Convert FilterBuilder format to OData $filter string if needed
|
|
424
|
-
// For now we just update state and notify listener
|
|
425
|
-
// In a real app, this would likely build an OData string
|
|
426
|
-
if (onFilterChange) onFilterChange(newFilters);
|
|
427
|
-
}}
|
|
428
|
-
/>
|
|
429
|
-
</div>
|
|
430
|
-
</PopoverContent>
|
|
431
|
-
</Popover>
|
|
432
|
-
|
|
433
|
-
<Popover open={showSort} onOpenChange={setShowSort}>
|
|
434
|
-
<PopoverTrigger asChild>
|
|
435
|
-
<Button
|
|
436
|
-
variant={currentSort.length > 0 ? "secondary" : "ghost"}
|
|
437
|
-
size="sm"
|
|
438
|
-
className={cn(
|
|
439
|
-
"h-8 px-2 lg:px-3 text-muted-foreground hover:text-primary",
|
|
440
|
-
currentSort.length > 0 && "text-primary bg-secondary/50"
|
|
441
|
-
)}
|
|
442
|
-
>
|
|
443
|
-
<ArrowUpDown className="h-4 w-4 mr-2" />
|
|
444
|
-
<span className="hidden lg:inline">Sort</span>
|
|
445
|
-
{currentSort.length > 0 && (
|
|
446
|
-
<span className="ml-1.5 flex h-4 w-4 items-center justify-center rounded-full bg-primary/10 text-[10px] font-medium text-primary">
|
|
447
|
-
{currentSort.length}
|
|
448
|
-
</span>
|
|
449
|
-
)}
|
|
450
|
-
</Button>
|
|
451
|
-
</PopoverTrigger>
|
|
452
|
-
<PopoverContent align="start" className="w-[600px] p-4">
|
|
453
|
-
<div className="space-y-4">
|
|
454
|
-
<div className="flex items-center justify-between border-b pb-2">
|
|
455
|
-
<h4 className="font-medium text-sm">Sort Records</h4>
|
|
456
|
-
</div>
|
|
457
|
-
<SortBuilder
|
|
458
|
-
fields={filterFields}
|
|
459
|
-
value={currentSort}
|
|
460
|
-
onChange={(newSort) => {
|
|
461
|
-
console.log('Sort Changed:', newSort);
|
|
462
|
-
setCurrentSort(newSort);
|
|
463
|
-
if (onSortChange) onSortChange(newSort);
|
|
464
|
-
}}
|
|
465
|
-
/>
|
|
466
|
-
</div>
|
|
467
|
-
</PopoverContent>
|
|
468
|
-
</Popover>
|
|
469
|
-
|
|
470
|
-
{/* Future: Group, Color, Height */}
|
|
471
|
-
</div>
|
|
397
|
+
{/* Airtable-style Toolbar — Row 1: View tabs */}
|
|
398
|
+
{showViewSwitcher && (
|
|
399
|
+
<div className="border-b px-4 py-1 flex items-center bg-background">
|
|
400
|
+
<ViewSwitcher
|
|
401
|
+
currentView={currentView}
|
|
402
|
+
availableViews={availableViews}
|
|
403
|
+
onViewChange={handleViewChange}
|
|
404
|
+
/>
|
|
472
405
|
</div>
|
|
406
|
+
)}
|
|
407
|
+
|
|
408
|
+
{/* Airtable-style Toolbar — Row 2: Tool buttons */}
|
|
409
|
+
<div className="border-b px-4 py-1 flex items-center justify-between gap-2 bg-background">
|
|
410
|
+
<div className="flex items-center gap-0.5 overflow-hidden">
|
|
411
|
+
{/* Hide Fields */}
|
|
412
|
+
<Button
|
|
413
|
+
variant="ghost"
|
|
414
|
+
size="sm"
|
|
415
|
+
className="h-7 px-2 text-muted-foreground hover:text-primary text-xs"
|
|
416
|
+
disabled
|
|
417
|
+
>
|
|
418
|
+
<EyeOff className="h-3.5 w-3.5 mr-1.5" />
|
|
419
|
+
<span className="hidden sm:inline">Hide fields</span>
|
|
420
|
+
</Button>
|
|
473
421
|
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
422
|
+
{/* Filter */}
|
|
423
|
+
<Popover open={showFilters} onOpenChange={setShowFilters}>
|
|
424
|
+
<PopoverTrigger asChild>
|
|
425
|
+
<Button
|
|
426
|
+
variant="ghost"
|
|
427
|
+
size="sm"
|
|
428
|
+
className={cn(
|
|
429
|
+
"h-7 px-2 text-muted-foreground hover:text-primary text-xs",
|
|
430
|
+
hasFilters && "text-primary"
|
|
431
|
+
)}
|
|
432
|
+
>
|
|
433
|
+
<SlidersHorizontal className="h-3.5 w-3.5 mr-1.5" />
|
|
434
|
+
<span className="hidden sm:inline">Filter</span>
|
|
435
|
+
{hasFilters && (
|
|
436
|
+
<span className="ml-1 flex h-4 min-w-[16px] items-center justify-center rounded-full bg-primary/10 text-[10px] font-medium text-primary">
|
|
437
|
+
{currentFilters.conditions?.length || 0}
|
|
438
|
+
</span>
|
|
439
|
+
)}
|
|
440
|
+
</Button>
|
|
441
|
+
</PopoverTrigger>
|
|
442
|
+
<PopoverContent align="start" className="w-[600px] p-4">
|
|
443
|
+
<div className="space-y-4">
|
|
444
|
+
<div className="flex items-center justify-between border-b pb-2">
|
|
445
|
+
<h4 className="font-medium text-sm">Filter Records</h4>
|
|
446
|
+
</div>
|
|
447
|
+
<FilterBuilder
|
|
448
|
+
fields={filterFields}
|
|
449
|
+
value={currentFilters}
|
|
450
|
+
onChange={(newFilters) => {
|
|
451
|
+
setCurrentFilters(newFilters);
|
|
452
|
+
if (onFilterChange) onFilterChange(newFilters);
|
|
453
|
+
}}
|
|
483
454
|
/>
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
455
|
+
</div>
|
|
456
|
+
</PopoverContent>
|
|
457
|
+
</Popover>
|
|
458
|
+
|
|
459
|
+
{/* Group */}
|
|
460
|
+
<Button
|
|
461
|
+
variant="ghost"
|
|
462
|
+
size="sm"
|
|
463
|
+
className="h-7 px-2 text-muted-foreground hover:text-primary text-xs"
|
|
464
|
+
disabled
|
|
465
|
+
>
|
|
466
|
+
<Group className="h-3.5 w-3.5 mr-1.5" />
|
|
467
|
+
<span className="hidden sm:inline">Group</span>
|
|
468
|
+
</Button>
|
|
469
|
+
|
|
470
|
+
{/* Sort */}
|
|
471
|
+
<Popover open={showSort} onOpenChange={setShowSort}>
|
|
472
|
+
<PopoverTrigger asChild>
|
|
473
|
+
<Button
|
|
474
|
+
variant="ghost"
|
|
475
|
+
size="sm"
|
|
476
|
+
className={cn(
|
|
477
|
+
"h-7 px-2 text-muted-foreground hover:text-primary text-xs",
|
|
478
|
+
currentSort.length > 0 && "text-primary"
|
|
493
479
|
)}
|
|
494
|
-
|
|
480
|
+
>
|
|
481
|
+
<ArrowUpDown className="h-3.5 w-3.5 mr-1.5" />
|
|
482
|
+
<span className="hidden sm:inline">Sort</span>
|
|
483
|
+
{currentSort.length > 0 && (
|
|
484
|
+
<span className="ml-1 flex h-4 min-w-[16px] items-center justify-center rounded-full bg-primary/10 text-[10px] font-medium text-primary">
|
|
485
|
+
{currentSort.length}
|
|
486
|
+
</span>
|
|
487
|
+
)}
|
|
488
|
+
</Button>
|
|
489
|
+
</PopoverTrigger>
|
|
490
|
+
<PopoverContent align="start" className="w-[600px] p-4">
|
|
491
|
+
<div className="space-y-4">
|
|
492
|
+
<div className="flex items-center justify-between border-b pb-2">
|
|
493
|
+
<h4 className="font-medium text-sm">Sort Records</h4>
|
|
494
|
+
</div>
|
|
495
|
+
<SortBuilder
|
|
496
|
+
fields={filterFields}
|
|
497
|
+
value={currentSort}
|
|
498
|
+
onChange={(newSort) => {
|
|
499
|
+
setCurrentSort(newSort);
|
|
500
|
+
if (onSortChange) onSortChange(newSort);
|
|
501
|
+
}}
|
|
502
|
+
/>
|
|
503
|
+
</div>
|
|
504
|
+
</PopoverContent>
|
|
505
|
+
</Popover>
|
|
506
|
+
|
|
507
|
+
{/* Color */}
|
|
508
|
+
<Button
|
|
509
|
+
variant="ghost"
|
|
510
|
+
size="sm"
|
|
511
|
+
className="h-7 px-2 text-muted-foreground hover:text-primary text-xs"
|
|
512
|
+
disabled
|
|
513
|
+
>
|
|
514
|
+
<Paintbrush className="h-3.5 w-3.5 mr-1.5" />
|
|
515
|
+
<span className="hidden sm:inline">Color</span>
|
|
516
|
+
</Button>
|
|
517
|
+
|
|
518
|
+
{/* Row Height */}
|
|
519
|
+
<Button
|
|
520
|
+
variant="ghost"
|
|
521
|
+
size="sm"
|
|
522
|
+
className="h-7 px-2 text-muted-foreground hover:text-primary text-xs hidden lg:flex"
|
|
523
|
+
disabled
|
|
524
|
+
>
|
|
525
|
+
<Ruler className="h-3.5 w-3.5 mr-1.5" />
|
|
526
|
+
<span className="hidden sm:inline">Row height</span>
|
|
527
|
+
</Button>
|
|
528
|
+
</div>
|
|
529
|
+
|
|
530
|
+
{/* Right: Search */}
|
|
531
|
+
<div className="flex items-center gap-1">
|
|
532
|
+
{searchExpanded ? (
|
|
533
|
+
<div className="relative w-48 lg:w-64">
|
|
534
|
+
<Search className="absolute left-2 top-1/2 -translate-y-1/2 h-3.5 w-3.5 text-muted-foreground" />
|
|
535
|
+
<Input
|
|
536
|
+
placeholder="Find..."
|
|
537
|
+
value={searchTerm}
|
|
538
|
+
onChange={(e) => handleSearchChange(e.target.value)}
|
|
539
|
+
className="pl-7 h-7 text-xs bg-muted/50 border-transparent hover:bg-muted focus:bg-background focus:border-input transition-colors"
|
|
540
|
+
autoFocus
|
|
541
|
+
onBlur={() => {
|
|
542
|
+
if (!searchTerm) setSearchExpanded(false);
|
|
543
|
+
}}
|
|
544
|
+
/>
|
|
545
|
+
<Button
|
|
546
|
+
variant="ghost"
|
|
547
|
+
size="sm"
|
|
548
|
+
className="absolute right-0.5 top-1/2 -translate-y-1/2 h-5 w-5 p-0 hover:bg-muted-foreground/20"
|
|
549
|
+
onClick={() => {
|
|
550
|
+
handleSearchChange('');
|
|
551
|
+
setSearchExpanded(false);
|
|
552
|
+
}}
|
|
553
|
+
>
|
|
554
|
+
<X className="h-3 w-3" />
|
|
555
|
+
</Button>
|
|
556
|
+
</div>
|
|
557
|
+
) : (
|
|
558
|
+
<Button
|
|
559
|
+
variant="ghost"
|
|
560
|
+
size="sm"
|
|
561
|
+
className="h-7 px-2 text-muted-foreground hover:text-primary text-xs"
|
|
562
|
+
onClick={() => setSearchExpanded(true)}
|
|
563
|
+
>
|
|
564
|
+
<Search className="h-3.5 w-3.5 mr-1.5" />
|
|
565
|
+
<span className="hidden sm:inline">Search</span>
|
|
566
|
+
</Button>
|
|
567
|
+
)}
|
|
495
568
|
</div>
|
|
496
569
|
</div>
|
|
497
570
|
|
|
@@ -507,6 +580,33 @@ export const ListView: React.FC<ListViewProps> = ({
|
|
|
507
580
|
loading={loading}
|
|
508
581
|
/>
|
|
509
582
|
</div>
|
|
583
|
+
|
|
584
|
+
{/* Navigation Overlay (drawer/modal/popover) */}
|
|
585
|
+
{navigation.isOverlay && (
|
|
586
|
+
<NavigationOverlay
|
|
587
|
+
{...navigation}
|
|
588
|
+
title={
|
|
589
|
+
schema.label
|
|
590
|
+
? `${schema.label} Detail`
|
|
591
|
+
: schema.objectName
|
|
592
|
+
? `${schema.objectName.charAt(0).toUpperCase() + schema.objectName.slice(1)} Detail`
|
|
593
|
+
: 'Record Detail'
|
|
594
|
+
}
|
|
595
|
+
>
|
|
596
|
+
{(record) => (
|
|
597
|
+
<div className="space-y-3">
|
|
598
|
+
{Object.entries(record).map(([key, value]) => (
|
|
599
|
+
<div key={key} className="flex flex-col">
|
|
600
|
+
<span className="text-xs font-medium text-muted-foreground uppercase tracking-wide">
|
|
601
|
+
{key.replace(/_/g, ' ')}
|
|
602
|
+
</span>
|
|
603
|
+
<span className="text-sm">{String(value ?? '—')}</span>
|
|
604
|
+
</div>
|
|
605
|
+
))}
|
|
606
|
+
</div>
|
|
607
|
+
)}
|
|
608
|
+
</NavigationOverlay>
|
|
609
|
+
)}
|
|
510
610
|
</div>
|
|
511
611
|
);
|
|
512
612
|
};
|
|
@@ -66,7 +66,7 @@ describe('ListView', () => {
|
|
|
66
66
|
expect(container).toBeTruthy();
|
|
67
67
|
});
|
|
68
68
|
|
|
69
|
-
it('should render search
|
|
69
|
+
it('should render search button', () => {
|
|
70
70
|
const schema: ListViewSchema = {
|
|
71
71
|
type: 'list-view',
|
|
72
72
|
objectName: 'contacts',
|
|
@@ -75,11 +75,11 @@ describe('ListView', () => {
|
|
|
75
75
|
};
|
|
76
76
|
|
|
77
77
|
renderWithProvider(<ListView schema={schema} />);
|
|
78
|
-
const
|
|
79
|
-
expect(
|
|
78
|
+
const searchButton = screen.getByRole('button', { name: /search/i });
|
|
79
|
+
expect(searchButton).toBeInTheDocument();
|
|
80
80
|
});
|
|
81
81
|
|
|
82
|
-
it('should call onSearchChange when search input changes', () => {
|
|
82
|
+
it('should expand search and call onSearchChange when search input changes', () => {
|
|
83
83
|
const onSearchChange = vi.fn();
|
|
84
84
|
const schema: ListViewSchema = {
|
|
85
85
|
type: 'list-view',
|
|
@@ -89,8 +89,12 @@ describe('ListView', () => {
|
|
|
89
89
|
};
|
|
90
90
|
|
|
91
91
|
renderWithProvider(<ListView schema={schema} onSearchChange={onSearchChange} />);
|
|
92
|
-
const searchInput = screen.getByPlaceholderText(/find/i);
|
|
93
92
|
|
|
93
|
+
// Click search button to expand
|
|
94
|
+
const searchButton = screen.getByRole('button', { name: /search/i });
|
|
95
|
+
fireEvent.click(searchButton);
|
|
96
|
+
|
|
97
|
+
const searchInput = screen.getByPlaceholderText(/find/i);
|
|
94
98
|
fireEvent.change(searchInput, { target: { value: 'test' } });
|
|
95
99
|
expect(onSearchChange).toHaveBeenCalledWith('test');
|
|
96
100
|
});
|
|
@@ -108,7 +112,7 @@ describe('ListView', () => {
|
|
|
108
112
|
},
|
|
109
113
|
};
|
|
110
114
|
|
|
111
|
-
renderWithProvider(<ListView schema={schema} />);
|
|
115
|
+
renderWithProvider(<ListView schema={schema} showViewSwitcher={true} />);
|
|
112
116
|
|
|
113
117
|
// Find kanban view button and click it
|
|
114
118
|
// ViewSwitcher uses buttons with aria-label
|
|
@@ -196,13 +200,18 @@ describe('ListView', () => {
|
|
|
196
200
|
};
|
|
197
201
|
|
|
198
202
|
renderWithProvider(<ListView schema={schema} />);
|
|
203
|
+
|
|
204
|
+
// Click search button to expand search input
|
|
205
|
+
const searchButton = screen.getByRole('button', { name: /search/i });
|
|
206
|
+
fireEvent.click(searchButton);
|
|
207
|
+
|
|
199
208
|
const searchInput = screen.getByPlaceholderText(/find/i) as HTMLInputElement;
|
|
200
209
|
|
|
201
210
|
// Type in search
|
|
202
211
|
fireEvent.change(searchInput, { target: { value: 'test' } });
|
|
203
212
|
expect(searchInput.value).toBe('test');
|
|
204
213
|
|
|
205
|
-
// Find and click clear button
|
|
214
|
+
// Find and click clear button (the X button inside the expanded search)
|
|
206
215
|
const buttons = screen.getAllByRole('button');
|
|
207
216
|
const clearButton = buttons.find(btn =>
|
|
208
217
|
btn.querySelector('svg') !== null && searchInput.value !== ''
|
|
@@ -56,7 +56,7 @@ describe('ListView Persistence', () => {
|
|
|
56
56
|
},
|
|
57
57
|
};
|
|
58
58
|
|
|
59
|
-
renderWithProvider(<ListView schema={schema} />);
|
|
59
|
+
renderWithProvider(<ListView schema={schema} showViewSwitcher={true} />);
|
|
60
60
|
|
|
61
61
|
// Simulate changing to kanban view
|
|
62
62
|
const kanbanButton = screen.getByLabelText('Kanban');
|
|
@@ -89,7 +89,7 @@ describe('ListView Persistence', () => {
|
|
|
89
89
|
},
|
|
90
90
|
};
|
|
91
91
|
|
|
92
|
-
renderWithProvider(<ListView schema={viewB_Schema} />);
|
|
92
|
+
renderWithProvider(<ListView schema={viewB_Schema} showViewSwitcher={true} />);
|
|
93
93
|
|
|
94
94
|
// Should use the schema default 'kanban' (since no storage exists for THIS view id)
|
|
95
95
|
// It should NOT use 'grid' from the global/default view.
|
|
@@ -117,7 +117,7 @@ describe('ListView Persistence', () => {
|
|
|
117
117
|
},
|
|
118
118
|
};
|
|
119
119
|
|
|
120
|
-
renderWithProvider(<ListView schema={schema} />);
|
|
120
|
+
renderWithProvider(<ListView schema={schema} showViewSwitcher={true} />);
|
|
121
121
|
|
|
122
122
|
// Should respect schema ('grid') because storage persistence is currently disabled
|
|
123
123
|
const kanbanButton = screen.getByLabelText('Kanban');
|
package/src/index.tsx
CHANGED
|
@@ -45,3 +45,25 @@ ComponentRegistry.register('list-view', ListView, {
|
|
|
45
45
|
options: {},
|
|
46
46
|
}
|
|
47
47
|
});
|
|
48
|
+
|
|
49
|
+
// Alias for generic view
|
|
50
|
+
ComponentRegistry.register('list', ListView, {
|
|
51
|
+
namespace: 'view',
|
|
52
|
+
category: 'view',
|
|
53
|
+
label: 'List',
|
|
54
|
+
icon: 'LayoutList',
|
|
55
|
+
inputs: [
|
|
56
|
+
{ name: 'objectName', type: 'string', label: 'Object Name', required: true },
|
|
57
|
+
{ name: 'viewType', type: 'enum', label: 'Default View', enum: [
|
|
58
|
+
{ label: 'Grid', value: 'grid' },
|
|
59
|
+
{ label: 'List', value: 'list' },
|
|
60
|
+
{ label: 'Kanban', value: 'kanban' },
|
|
61
|
+
{ label: 'Calendar', value: 'calendar' },
|
|
62
|
+
{ label: 'Chart', value: 'chart' }
|
|
63
|
+
], defaultValue: 'grid' },
|
|
64
|
+
{ name: 'fields', type: 'array', label: 'Fields' },
|
|
65
|
+
{ name: 'filters', type: 'array', label: 'Filters' },
|
|
66
|
+
{ name: 'sort', type: 'array', label: 'Sort' },
|
|
67
|
+
{ name: 'options', type: 'object', label: 'View Options' },
|
|
68
|
+
]
|
|
69
|
+
});
|