@djangocfg/layouts 1.2.48 โ†’ 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.48",
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.48",
67
- "@djangocfg/og-image": "^1.2.48",
68
- "@djangocfg/ui": "^1.2.48",
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.48",
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-20T17:19:40.883Z
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.48"
24
+ "version": "1.2.50"
25
25
  },
26
26
  {
27
27
  "name": "@djangocfg/api",
28
- "version": "1.2.48"
28
+ "version": "1.2.50"
29
29
  },
30
30
  {
31
31
  "name": "@djangocfg/layouts",
32
- "version": "1.2.48"
32
+ "version": "1.2.50"
33
33
  },
34
34
  {
35
35
  "name": "@djangocfg/markdown",
36
- "version": "1.2.48"
36
+ "version": "1.2.50"
37
37
  },
38
38
  {
39
39
  "name": "@djangocfg/og-image",
40
- "version": "1.2.48"
40
+ "version": "1.2.50"
41
41
  },
42
42
  {
43
43
  "name": "@djangocfg/eslint-config",
44
- "version": "1.2.48"
44
+ "version": "1.2.50"
45
45
  },
46
46
  {
47
47
  "name": "@djangocfg/typescript-config",
48
- "version": "1.2.48"
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,6 +22,8 @@ import {
22
22
  Slider,
23
23
  Combobox,
24
24
  MultiSelect,
25
+ MultiSelectPro,
26
+ MultiSelectProAsync,
25
27
  InputOTP,
26
28
  InputOTPGroup,
27
29
  InputOTPSlot,
@@ -34,6 +36,7 @@ import {
34
36
  FormLabel,
35
37
  FormMessage,
36
38
  Field,
39
+ useDebounce,
37
40
  } from '@djangocfg/ui';
38
41
  import { JsonSchemaForm } from '@djangocfg/ui/tools';
39
42
  import type { ComponentConfig } from './types';
@@ -391,6 +394,353 @@ export const FORM_COMPONENTS: ComponentConfig[] = [
391
394
  />
392
395
  ),
393
396
  },
397
+ {
398
+ name: 'MultiSelectPro',
399
+ category: 'forms',
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();`,
492
+ preview: (
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>
570
+
571
+ <div className="p-4 border rounded-md bg-muted/50">
572
+ <p className="text-sm font-medium mb-2">Features:</p>
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>
586
+ </ul>
587
+ </div>
588
+ </div>
589
+ ),
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
+ },
394
744
  {
395
745
  name: 'InputOTP',
396
746
  category: 'forms',
@@ -92,20 +92,57 @@ const countdown = useCountdown(targetDate);
92
92
  {
93
93
  name: 'useDebounce',
94
94
  category: 'hooks',
95
- description: 'Debounce value changes',
95
+ description: 'Debounce value changes to reduce API calls and improve performance. Perfect for search inputs and form fields.',
96
96
  importPath: "import { useDebounce } from '@djangocfg/ui';",
97
- example: `const [search, setSearch] = useState('');
98
- const debouncedSearch = useDebounce(search, 500);
97
+ example: `// Basic search debouncing
98
+ const [search, setSearch] = useState('');
99
+ const debouncedSearch = useDebounce(search, 300); // Default 300ms
99
100
 
100
- // debouncedSearch updates 500ms after last change`,
101
+ useEffect(() => {
102
+ if (debouncedSearch) {
103
+ // API call only fires 300ms after user stops typing
104
+ fetchResults(debouncedSearch);
105
+ }
106
+ }, [debouncedSearch]);
107
+
108
+ // With MultiSelectPro (has built-in search)
109
+ const [searchValue, setSearchValue] = useState('');
110
+ const debouncedSearchValue = useDebounce(searchValue, 500);
111
+
112
+ useEffect(() => {
113
+ // Fetch options from API with debounced search
114
+ if (debouncedSearchValue) {
115
+ fetchOptions(debouncedSearchValue);
116
+ }
117
+ }, [debouncedSearchValue]);
118
+
119
+ <MultiSelectPro
120
+ options={filteredOptions}
121
+ onValueChange={setSelected}
122
+ placeholder="Search and select..."
123
+ searchable={true}
124
+ // ... other props
125
+ />`,
101
126
  preview: (
102
127
  <div className="p-4 border rounded-md">
103
128
  <code className="text-sm">
104
- useDebounce(value, delay)
129
+ const debouncedValue = useDebounce(value, delay);
105
130
  </code>
106
- <p className="mt-2 text-sm text-muted-foreground">
107
- Debounces value changes with specified delay
108
- </p>
131
+ <div className="mt-3 space-y-2 text-sm text-muted-foreground">
132
+ <p><strong>Parameters:</strong></p>
133
+ <ul className="list-disc list-inside space-y-1 ml-2">
134
+ <li><code>value</code>: Any value to debounce</li>
135
+ <li><code>delay</code>: Delay in milliseconds (default: 300ms)</li>
136
+ </ul>
137
+ <p className="mt-2"><strong>Returns:</strong> Debounced value</p>
138
+ <p className="mt-2"><strong>Use Cases:</strong></p>
139
+ <ul className="list-disc list-inside space-y-1 ml-2">
140
+ <li>Search inputs (reduce API calls)</li>
141
+ <li>Form validation</li>
142
+ <li>Auto-save functionality</li>
143
+ <li>Scroll/resize event handlers</li>
144
+ </ul>
145
+ </div>
109
146
  </div>
110
147
  ),
111
148
  },