@djangocfg/layouts 1.2.49 โ†’ 1.2.50

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@djangocfg/layouts",
3
- "version": "1.2.49",
3
+ "version": "1.2.50",
4
4
  "description": "Layout system and components for Unrealon applications",
5
5
  "author": {
6
6
  "name": "DjangoCFG",
@@ -63,9 +63,9 @@
63
63
  "check": "tsc --noEmit"
64
64
  },
65
65
  "peerDependencies": {
66
- "@djangocfg/api": "^1.2.49",
67
- "@djangocfg/og-image": "^1.2.49",
68
- "@djangocfg/ui": "^1.2.49",
66
+ "@djangocfg/api": "^1.2.50",
67
+ "@djangocfg/og-image": "^1.2.50",
68
+ "@djangocfg/ui": "^1.2.50",
69
69
  "@hookform/resolvers": "^5.2.0",
70
70
  "consola": "^3.4.2",
71
71
  "lucide-react": "^0.468.0",
@@ -86,7 +86,7 @@
86
86
  "vidstack": "0.6.15"
87
87
  },
88
88
  "devDependencies": {
89
- "@djangocfg/typescript-config": "^1.2.49",
89
+ "@djangocfg/typescript-config": "^1.2.50",
90
90
  "@types/node": "^24.7.2",
91
91
  "@types/react": "19.2.2",
92
92
  "@types/react-dom": "19.2.1",
@@ -16,36 +16,36 @@ export interface PackageInfo {
16
16
  /**
17
17
  * Package versions registry
18
18
  * Auto-synced from package.json files
19
- * Last updated: 2025-11-21T05:40:14.485Z
19
+ * Last updated: 2025-11-21T06:17:17.842Z
20
20
  */
21
21
  const PACKAGE_VERSIONS: PackageInfo[] = [
22
22
  {
23
23
  "name": "@djangocfg/ui",
24
- "version": "1.2.49"
24
+ "version": "1.2.50"
25
25
  },
26
26
  {
27
27
  "name": "@djangocfg/api",
28
- "version": "1.2.49"
28
+ "version": "1.2.50"
29
29
  },
30
30
  {
31
31
  "name": "@djangocfg/layouts",
32
- "version": "1.2.49"
32
+ "version": "1.2.50"
33
33
  },
34
34
  {
35
35
  "name": "@djangocfg/markdown",
36
- "version": "1.2.49"
36
+ "version": "1.2.50"
37
37
  },
38
38
  {
39
39
  "name": "@djangocfg/og-image",
40
- "version": "1.2.49"
40
+ "version": "1.2.50"
41
41
  },
42
42
  {
43
43
  "name": "@djangocfg/eslint-config",
44
- "version": "1.2.49"
44
+ "version": "1.2.50"
45
45
  },
46
46
  {
47
47
  "name": "@djangocfg/typescript-config",
48
- "version": "1.2.49"
48
+ "version": "1.2.50"
49
49
  }
50
50
  ];
51
51
 
@@ -2,7 +2,7 @@
2
2
  * Form Components Configuration
3
3
  */
4
4
 
5
- import React from 'react';
5
+ import React, { useState, useEffect, useMemo } from 'react';
6
6
  import {
7
7
  Button,
8
8
  ButtonLink,
@@ -22,7 +22,8 @@ import {
22
22
  Slider,
23
23
  Combobox,
24
24
  MultiSelect,
25
- SearchableMultiSelect,
25
+ MultiSelectPro,
26
+ MultiSelectProAsync,
26
27
  InputOTP,
27
28
  InputOTPGroup,
28
29
  InputOTPSlot,
@@ -35,6 +36,7 @@ import {
35
36
  FormLabel,
36
37
  FormMessage,
37
38
  Field,
39
+ useDebounce,
38
40
  } from '@djangocfg/ui';
39
41
  import { JsonSchemaForm } from '@djangocfg/ui/tools';
40
42
  import type { ComponentConfig } from './types';
@@ -393,73 +395,352 @@ export const FORM_COMPONENTS: ComponentConfig[] = [
393
395
  ),
394
396
  },
395
397
  {
396
- name: 'SearchableMultiSelect',
398
+ name: 'MultiSelectPro',
397
399
  category: 'forms',
398
- description: 'Multi-select with integrated search and debounced filtering. Wraps MultiSelect with search functionality.',
399
- importPath: "import { SearchableMultiSelect } from '@djangocfg/ui';",
400
- example: `const [searchValue, setSearchValue] = useState('');
401
- const [selectedValues, setSelectedValues] = useState<string[]>([]);
402
-
403
- // Filter options based on search
404
- const filteredOptions = useMemo(() => {
405
- if (!searchValue) return allOptions;
406
- return allOptions.filter(opt =>
407
- opt.label.toLowerCase().includes(searchValue.toLowerCase())
408
- );
409
- }, [searchValue, allOptions]);
410
-
411
- <SearchableMultiSelect
412
- // Search props
413
- searchValue={searchValue}
414
- onSearchChange={setSearchValue}
415
- searchPlaceholder="Search countries..."
416
- searchDebounceMs={300}
417
-
418
- // MultiSelect props
419
- options={filteredOptions}
420
- value={selectedValues}
421
- onChange={setSelectedValues}
422
- placeholder="Select countries..."
423
- isLoading={isLoading}
424
- emptyText="No countries found."
425
- maxDisplay={3}
426
- />`,
400
+ description: 'Advanced multi-select with animations, custom styling, grouped options, and comprehensive accessibility. Supports variants, icons, gradients, responsive design, and imperative control via ref.',
401
+ importPath: "import { MultiSelectPro } from '@djangocfg/ui';",
402
+ example: `import { MultiSelectPro } from '@djangocfg/ui';
403
+ import type { MultiSelectProOption } from '@djangocfg/ui';
404
+ import { useState } from 'react';
405
+
406
+ // Basic usage
407
+ const [selected, setSelected] = useState<string[]>([]);
408
+
409
+ <MultiSelectPro
410
+ options={[
411
+ { value: "react", label: "React" },
412
+ { value: "vue", label: "Vue.js" },
413
+ { value: "angular", label: "Angular" },
414
+ ]}
415
+ onValueChange={setSelected}
416
+ defaultValue={selected}
417
+ placeholder="Select frameworks..."
418
+ />
419
+
420
+ // With custom styling and icons
421
+ const styledOptions = [
422
+ {
423
+ value: "react",
424
+ label: "React",
425
+ style: {
426
+ badgeColor: "#61DAFB",
427
+ iconColor: "#282C34",
428
+ },
429
+ },
430
+ {
431
+ value: "vue",
432
+ label: "Vue.js",
433
+ style: {
434
+ gradient: "linear-gradient(135deg, #4FC08D 0%, #42B883 100%)",
435
+ },
436
+ },
437
+ ];
438
+
439
+ <MultiSelectPro
440
+ options={styledOptions}
441
+ onValueChange={setSelected}
442
+ variant="secondary"
443
+ animationConfig={{
444
+ badgeAnimation: "bounce",
445
+ popoverAnimation: "scale",
446
+ duration: 0.3,
447
+ }}
448
+ maxCount={3}
449
+ closeOnSelect={false}
450
+ />
451
+
452
+ // With grouped options
453
+ const groupedOptions = [
454
+ {
455
+ heading: "Frontend Frameworks",
456
+ options: [
457
+ { value: "react", label: "React" },
458
+ { value: "vue", label: "Vue.js" },
459
+ { value: "angular", label: "Angular", disabled: true },
460
+ ],
461
+ },
462
+ {
463
+ heading: "Backend Technologies",
464
+ options: [
465
+ { value: "node", label: "Node.js" },
466
+ { value: "python", label: "Python" },
467
+ ],
468
+ },
469
+ ];
470
+
471
+ <MultiSelectPro
472
+ options={groupedOptions}
473
+ onValueChange={setSelected}
474
+ placeholder="Select technologies..."
475
+ searchable={true}
476
+ responsive={true}
477
+ minWidth="200px"
478
+ maxWidth="500px"
479
+ />
480
+
481
+ // With imperative control via ref
482
+ import { useRef } from 'react';
483
+ import type { MultiSelectProRef } from '@djangocfg/ui';
484
+
485
+ const ref = useRef<MultiSelectProRef>(null);
486
+
487
+ // Later in code:
488
+ ref.current?.clear();
489
+ ref.current?.reset();
490
+ ref.current?.setSelectedValues(['react', 'vue']);
491
+ const values = ref.current?.getSelectedValues();`,
427
492
  preview: (
428
- <div className="space-y-4">
429
- <SearchableMultiSelect
430
- searchValue=""
431
- onSearchChange={() => {}}
432
- searchPlaceholder="Search frameworks..."
433
- options={[
434
- { value: "react", label: "React" },
435
- { value: "vue", label: "Vue" },
436
- { value: "angular", label: "Angular" },
437
- { value: "svelte", label: "Svelte" },
438
- { value: "next", label: "Next.js" },
439
- { value: "nuxt", label: "Nuxt" },
440
- { value: "gatsby", label: "Gatsby" },
441
- ]}
442
- value={[]}
443
- onChange={() => {}}
444
- placeholder="Select frameworks..."
445
- emptyText="No framework found."
446
- maxDisplay={2}
447
- className="w-[300px]"
448
- />
493
+ <div className="space-y-6">
494
+ <div className="space-y-2">
495
+ <p className="text-sm font-medium">Basic with animations:</p>
496
+ <MultiSelectPro
497
+ options={[
498
+ { value: "react", label: "React" },
499
+ { value: "vue", label: "Vue" },
500
+ { value: "angular", label: "Angular" },
501
+ { value: "svelte", label: "Svelte" },
502
+ ]}
503
+ defaultValue={[]}
504
+ onValueChange={() => {}}
505
+ placeholder="Select frameworks..."
506
+ animationConfig={{
507
+ badgeAnimation: "bounce",
508
+ popoverAnimation: "scale",
509
+ duration: 0.3,
510
+ }}
511
+ maxCount={2}
512
+ className="w-[350px]"
513
+ />
514
+ </div>
515
+
516
+ <div className="space-y-2">
517
+ <p className="text-sm font-medium">With variants and styles:</p>
518
+ <div className="grid grid-cols-2 gap-2">
519
+ <MultiSelectPro
520
+ options={[
521
+ { value: "1", label: "Option 1" },
522
+ { value: "2", label: "Option 2" },
523
+ ]}
524
+ defaultValue={["1"]}
525
+ onValueChange={() => {}}
526
+ variant="secondary"
527
+ placeholder="Secondary"
528
+ maxCount={1}
529
+ />
530
+ <MultiSelectPro
531
+ options={[
532
+ { value: "1", label: "Option 1" },
533
+ { value: "2", label: "Option 2" },
534
+ ]}
535
+ defaultValue={["2"]}
536
+ onValueChange={() => {}}
537
+ variant="destructive"
538
+ placeholder="Destructive"
539
+ maxCount={1}
540
+ />
541
+ </div>
542
+ </div>
543
+
544
+ <div className="space-y-2">
545
+ <p className="text-sm font-medium">Grouped options:</p>
546
+ <MultiSelectPro
547
+ options={[
548
+ {
549
+ heading: "Frontend",
550
+ options: [
551
+ { value: "react", label: "React" },
552
+ { value: "vue", label: "Vue" },
553
+ ],
554
+ },
555
+ {
556
+ heading: "Backend",
557
+ options: [
558
+ { value: "node", label: "Node.js" },
559
+ { value: "python", label: "Python" },
560
+ ],
561
+ },
562
+ ]}
563
+ defaultValue={[]}
564
+ onValueChange={() => {}}
565
+ placeholder="Select from groups..."
566
+ className="w-[350px]"
567
+ maxCount={2}
568
+ />
569
+ </div>
449
570
 
450
571
  <div className="p-4 border rounded-md bg-muted/50">
451
572
  <p className="text-sm font-medium mb-2">Features:</p>
452
- <ul className="space-y-1 text-sm text-muted-foreground">
453
- <li>โ€ข Integrated search input above MultiSelect</li>
454
- <li>โ€ข Debounced search (default 300ms)</li>
455
- <li>โ€ข Loading state support</li>
456
- <li>โ€ข All MultiSelect features included</li>
457
- <li>โ€ข Combine with API calls for dynamic filtering</li>
573
+ <ul className="grid grid-cols-2 gap-x-4 gap-y-1 text-sm text-muted-foreground">
574
+ <li>โœจ Multiple variants (default, secondary, destructive, inverted)</li>
575
+ <li>๐ŸŒˆ Custom badge colors & gradients</li>
576
+ <li>๐Ÿ“ Grouped options with separators</li>
577
+ <li>๐Ÿšซ Disabled options support</li>
578
+ <li>๐ŸŽจ Badge animations (bounce, pulse, wiggle, fade, slide)</li>
579
+ <li>๐Ÿ” Built-in search & filter</li>
580
+ <li>๐Ÿ“ฑ Responsive design (mobile/tablet/desktop)</li>
581
+ <li>๐Ÿ“ Width constraints (min/max)</li>
582
+ <li>โ™ฟ Full accessibility (ARIA, keyboard nav)</li>
583
+ <li>๐Ÿ”ง Imperative control via ref</li>
584
+ <li>๐Ÿ”„ Duplicate handling</li>
585
+ <li>๐ŸŽ›๏ธ Auto-close, single-line, auto-size modes</li>
458
586
  </ul>
459
587
  </div>
460
588
  </div>
461
589
  ),
462
590
  },
591
+ {
592
+ name: 'MultiSelectProAsync',
593
+ category: 'forms',
594
+ description: 'Async multi-select with external API search, debouncing, and loading states. Perfect for large datasets and server-side filtering.',
595
+ importPath: "import { MultiSelectProAsync, useDebounce } from '@djangocfg/ui';",
596
+ example: `import { MultiSelectProAsync, useDebounce } from '@djangocfg/ui';
597
+ import { useState, useEffect } from 'react';
598
+
599
+ // Mock API function (replace with your actual API)
600
+ const searchAPI = async (query: string) => {
601
+ const response = await fetch(\`/api/search?q=\${query}\`);
602
+ return response.json();
603
+ };
604
+
605
+ function AsyncExample() {
606
+ const [searchValue, setSearchValue] = useState('');
607
+ const [options, setOptions] = useState([]);
608
+ const [isLoading, setIsLoading] = useState(false);
609
+ const [selected, setSelected] = useState<string[]>([]);
610
+
611
+ // Debounce search to reduce API calls
612
+ const debouncedSearch = useDebounce(searchValue, 300);
613
+
614
+ // Fetch options when debounced search changes
615
+ useEffect(() => {
616
+ if (!debouncedSearch) {
617
+ setOptions([]);
618
+ return;
619
+ }
620
+
621
+ const fetchOptions = async () => {
622
+ setIsLoading(true);
623
+ try {
624
+ const results = await searchAPI(debouncedSearch);
625
+ setOptions(results);
626
+ } catch (error) {
627
+ console.error('Search failed:', error);
628
+ } finally {
629
+ setIsLoading(false);
630
+ }
631
+ };
632
+
633
+ fetchOptions();
634
+ }, [debouncedSearch]);
635
+
636
+ return (
637
+ <MultiSelectProAsync
638
+ // Search control (managed by parent)
639
+ searchValue={searchValue}
640
+ onSearchChange={setSearchValue}
641
+ isLoading={isLoading}
642
+
643
+ // Options from API
644
+ options={options}
645
+
646
+ // Selection
647
+ onValueChange={setSelected}
648
+ defaultValue={selected}
649
+
650
+ // UI
651
+ placeholder="Search and select..."
652
+ searchPlaceholder="Type to search..."
653
+ emptyText="No results found"
654
+ loadingText="Searching..."
655
+
656
+ // Features
657
+ variant="default"
658
+ maxCount={3}
659
+ closeOnSelect={false}
660
+ />
661
+ );
662
+ }`,
663
+ preview: (() => {
664
+ // Demo component with mock data
665
+ const DemoAsync = () => {
666
+ const [searchValue, setSearchValue] = useState('');
667
+ const [selected, setSelected] = useState<string[]>([]);
668
+ const debouncedSearch = useDebounce(searchValue, 300);
669
+ const [isLoading, setIsLoading] = useState(false);
670
+
671
+ // Mock database of countries
672
+ const allCountries = useMemo(() => [
673
+ { value: 'us', label: 'United States' },
674
+ { value: 'uk', label: 'United Kingdom' },
675
+ { value: 'ca', label: 'Canada' },
676
+ { value: 'au', label: 'Australia' },
677
+ { value: 'de', label: 'Germany' },
678
+ { value: 'fr', label: 'France' },
679
+ { value: 'it', label: 'Italy' },
680
+ { value: 'es', label: 'Spain' },
681
+ { value: 'jp', label: 'Japan' },
682
+ { value: 'cn', label: 'China' },
683
+ { value: 'in', label: 'India' },
684
+ { value: 'br', label: 'Brazil' },
685
+ { value: 'mx', label: 'Mexico' },
686
+ { value: 'ru', label: 'Russia' },
687
+ { value: 'za', label: 'South Africa' },
688
+ ], []);
689
+
690
+ // Filtered options based on debounced search
691
+ const filteredOptions = useMemo(() => {
692
+ if (!debouncedSearch) return [];
693
+ return allCountries.filter(country =>
694
+ country.label.toLowerCase().includes(debouncedSearch.toLowerCase())
695
+ );
696
+ }, [debouncedSearch, allCountries]);
697
+
698
+ // Simulate API loading
699
+ useEffect(() => {
700
+ if (debouncedSearch) {
701
+ setIsLoading(true);
702
+ const timer = setTimeout(() => setIsLoading(false), 300);
703
+ return () => clearTimeout(timer);
704
+ }
705
+ }, [debouncedSearch]);
706
+
707
+ return (
708
+ <div className="space-y-4">
709
+ <MultiSelectProAsync
710
+ searchValue={searchValue}
711
+ onSearchChange={setSearchValue}
712
+ isLoading={isLoading}
713
+ options={filteredOptions}
714
+ onValueChange={setSelected}
715
+ defaultValue={selected}
716
+ placeholder="Search countries..."
717
+ searchPlaceholder="Type to search countries..."
718
+ emptyText="No countries found"
719
+ loadingText="Searching..."
720
+ maxCount={2}
721
+ className="w-[350px]"
722
+ />
723
+
724
+ <div className="p-4 border rounded-md bg-muted/50">
725
+ <p className="text-sm font-medium mb-2">Features:</p>
726
+ <ul className="space-y-1 text-sm text-muted-foreground">
727
+ <li>๐Ÿ” <strong>Async search</strong> - Controlled search value for API integration</li>
728
+ <li>โฑ๏ธ <strong>Debouncing</strong> - Use useDebounce hook to reduce API calls</li>
729
+ <li>๐Ÿ”„ <strong>Loading states</strong> - Shows spinner during data fetching</li>
730
+ <li>๐Ÿงน <strong>Auto-clear</strong> - Clears search when popover closes</li>
731
+ <li>โœจ <strong>All MultiSelectPro features</strong> - Animations, variants, groups, etc.</li>
732
+ </ul>
733
+ <div className="mt-3 p-2 bg-muted rounded text-xs">
734
+ <strong>Try it:</strong> Type "united" or "india" to see async filtering
735
+ </div>
736
+ </div>
737
+ </div>
738
+ );
739
+ };
740
+
741
+ return <DemoAsync />;
742
+ })(),
743
+ },
463
744
  {
464
745
  name: 'InputOTP',
465
746
  category: 'forms',
@@ -105,7 +105,7 @@ useEffect(() => {
105
105
  }
106
106
  }, [debouncedSearch]);
107
107
 
108
- // With SearchableMultiSelect
108
+ // With MultiSelectPro (has built-in search)
109
109
  const [searchValue, setSearchValue] = useState('');
110
110
  const debouncedSearchValue = useDebounce(searchValue, 500);
111
111
 
@@ -116,10 +116,11 @@ useEffect(() => {
116
116
  }
117
117
  }, [debouncedSearchValue]);
118
118
 
119
- <SearchableMultiSelect
120
- searchValue={searchValue}
121
- onSearchChange={setSearchValue}
119
+ <MultiSelectPro
122
120
  options={filteredOptions}
121
+ onValueChange={setSelected}
122
+ placeholder="Search and select..."
123
+ searchable={true}
123
124
  // ... other props
124
125
  />`,
125
126
  preview: (