@altimateai/ui-components 0.0.48 → 0.0.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/dist/CoachForm.js +9985 -10068
- package/dist/Stack.js +317 -316
- package/dist/TagsInput.js +2615 -2469
- package/dist/assets/icons/index.js +1 -1
- package/dist/chatbotV2/index.d.ts +6 -6
- package/dist/chatbotV2/index.js +5 -5
- package/dist/index.d.ts +2 -2
- package/dist/index.js +1 -1
- package/dist/index2.js +6 -6
- package/dist/lineage/index.js +2 -2
- package/dist/main.js +146 -146
- package/dist/redux-toolkit.modern.js +1 -1
- package/dist/shadcn/index.d.ts +16 -8
- package/dist/shadcn/index.js +3 -3
- package/dist/storybook/Combobox.stories.tsx +983 -79
- package/dist/{types-CqeMsC8t.d.ts → types-oWZJEjV7.d.ts} +1 -5
- package/package.json +1 -1
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import { Meta, StoryFn } from "@storybook/react";
|
|
2
|
-
import { Combobox } from "../shadcn";
|
|
3
|
-
import {
|
|
2
|
+
import { Combobox, Button } from "../shadcn";
|
|
3
|
+
import type { ComboboxOption } from "./Combobox";
|
|
4
4
|
import { DatabaseIcon } from "@ac-assets/icons";
|
|
5
|
-
import { useState } from "react";
|
|
5
|
+
import { useCallback, useState } from "react";
|
|
6
6
|
|
|
7
7
|
export default {
|
|
8
8
|
title: "Shadcn/Components/Combobox",
|
|
@@ -165,7 +165,7 @@ const ComboboxColumnsExample = () => {
|
|
|
165
165
|
placeholder="Columns"
|
|
166
166
|
multiSelect={true}
|
|
167
167
|
showApplyButton={true}
|
|
168
|
-
buttonProps={{ className: "al-w-
|
|
168
|
+
buttonProps={{ className: "al-w-auto" }}
|
|
169
169
|
/>
|
|
170
170
|
);
|
|
171
171
|
};
|
|
@@ -437,81 +437,6 @@ export const ComboboxInfiniteScrollExample: StoryFn = () => {
|
|
|
437
437
|
);
|
|
438
438
|
};
|
|
439
439
|
|
|
440
|
-
export const ComboboxSearchExample: StoryFn = () => {
|
|
441
|
-
const [options] = useState([
|
|
442
|
-
{ value: "react", label: "React" },
|
|
443
|
-
{ value: "vue", label: "Vue" },
|
|
444
|
-
{ value: "angular", label: "Angular" },
|
|
445
|
-
{ value: "svelte", label: "Svelte" },
|
|
446
|
-
{ value: "nextjs", label: "Next.js" },
|
|
447
|
-
]);
|
|
448
|
-
|
|
449
|
-
const [selectedValue, setSelectedValue] = useState("");
|
|
450
|
-
const [searchValue, setSearchValue] = useState("");
|
|
451
|
-
const [searchHistory, setSearchHistory] = useState<string[]>([]);
|
|
452
|
-
|
|
453
|
-
const handleSearch = (searchTerm: string) => {
|
|
454
|
-
setSearchValue(searchTerm);
|
|
455
|
-
if (searchTerm.trim() && !searchHistory.includes(searchTerm.trim())) {
|
|
456
|
-
setSearchHistory(prev => [searchTerm.trim(), ...prev.slice(0, 4)]); // Keep last 5 searches
|
|
457
|
-
}
|
|
458
|
-
};
|
|
459
|
-
|
|
460
|
-
const clearHistory = () => {
|
|
461
|
-
setSearchHistory([]);
|
|
462
|
-
setSearchValue("");
|
|
463
|
-
};
|
|
464
|
-
|
|
465
|
-
return (
|
|
466
|
-
<div className="al-flex al-flex-col al-gap-6 al-justify-start al-items-start">
|
|
467
|
-
<div className="al-flex al-gap-4 al-items-center">
|
|
468
|
-
<h3 className="al-text-lg al-font-medium">Search Callback Example</h3>
|
|
469
|
-
<Button onClick={clearHistory} size="sm" variant="outline">
|
|
470
|
-
Clear History
|
|
471
|
-
</Button>
|
|
472
|
-
</div>
|
|
473
|
-
|
|
474
|
-
<div className="al-flex al-flex-col al-gap-4">
|
|
475
|
-
<div className="al-flex al-flex-col al-gap-2">
|
|
476
|
-
<label className="al-text-sm al-font-medium">Combobox with Search Callback</label>
|
|
477
|
-
<p className="al-text-xs al-text-muted-foreground">
|
|
478
|
-
Type in the search box to see the search values being emitted
|
|
479
|
-
</p>
|
|
480
|
-
<Combobox
|
|
481
|
-
options={options}
|
|
482
|
-
value={selectedValue}
|
|
483
|
-
onChange={value => setSelectedValue(value as string)}
|
|
484
|
-
placeholder="Search frameworks..."
|
|
485
|
-
searchPlaceholder="Type to search..."
|
|
486
|
-
onSearch={handleSearch}
|
|
487
|
-
buttonProps={{ className: "al-w-[250px]" }}
|
|
488
|
-
/>
|
|
489
|
-
</div>
|
|
490
|
-
|
|
491
|
-
<div className="al-flex al-flex-col al-gap-2">
|
|
492
|
-
<label className="al-text-sm al-font-medium">Current Search Value:</label>
|
|
493
|
-
<code className="al-text-sm al-bg-muted al-p-2 al-rounded">
|
|
494
|
-
{searchValue || "No search performed yet"}
|
|
495
|
-
</code>
|
|
496
|
-
</div>
|
|
497
|
-
|
|
498
|
-
{searchHistory.length > 0 && (
|
|
499
|
-
<div className="al-flex al-flex-col al-gap-2">
|
|
500
|
-
<label className="al-text-sm al-font-medium">Search History:</label>
|
|
501
|
-
<ul className="al-text-sm al-bg-muted al-p-2 al-rounded al-space-y-1">
|
|
502
|
-
{searchHistory.map((search, index) => (
|
|
503
|
-
<li key={index} className="al-font-mono">
|
|
504
|
-
{index + 1}. "{search}"
|
|
505
|
-
</li>
|
|
506
|
-
))}
|
|
507
|
-
</ul>
|
|
508
|
-
</div>
|
|
509
|
-
)}
|
|
510
|
-
</div>
|
|
511
|
-
</div>
|
|
512
|
-
);
|
|
513
|
-
};
|
|
514
|
-
|
|
515
440
|
export const ComboboxCallbacksExample: StoryFn = () => {
|
|
516
441
|
const options = [
|
|
517
442
|
{ value: "react", label: "React" },
|
|
@@ -584,6 +509,259 @@ export const ComboboxCallbacksExample: StoryFn = () => {
|
|
|
584
509
|
);
|
|
585
510
|
};
|
|
586
511
|
|
|
512
|
+
export const ComboboxCustomNodesExample: StoryFn = () => {
|
|
513
|
+
const [singleValue, setSingleValue] = useState("");
|
|
514
|
+
const [multiValue, setMultiValue] = useState<string[]>([]);
|
|
515
|
+
|
|
516
|
+
// Options with custom node rendering
|
|
517
|
+
const optionsWithNodes = [
|
|
518
|
+
{
|
|
519
|
+
value: "react",
|
|
520
|
+
label: "React",
|
|
521
|
+
labelNode: (
|
|
522
|
+
<div className="al-flex al-items-center al-justify-between al-w-full">
|
|
523
|
+
<div className="al-flex al-items-center al-gap-2">
|
|
524
|
+
<div className="al-w-4 al-h-4 al-bg-blue-500 al-rounded"></div>
|
|
525
|
+
<span>React</span>
|
|
526
|
+
</div>
|
|
527
|
+
<span className="al-text-xs al-text-muted-foreground">Meta</span>
|
|
528
|
+
</div>
|
|
529
|
+
),
|
|
530
|
+
},
|
|
531
|
+
{
|
|
532
|
+
value: "vue",
|
|
533
|
+
label: "Vue",
|
|
534
|
+
labelNode: (
|
|
535
|
+
<div className="al-flex al-items-center al-justify-between al-w-full">
|
|
536
|
+
<div className="al-flex al-items-center al-gap-2">
|
|
537
|
+
<div className="al-w-4 al-h-4 al-bg-green-500 al-rounded"></div>
|
|
538
|
+
<span>Vue.js</span>
|
|
539
|
+
</div>
|
|
540
|
+
<span className="al-text-xs al-text-muted-foreground">Evan You</span>
|
|
541
|
+
</div>
|
|
542
|
+
),
|
|
543
|
+
},
|
|
544
|
+
{
|
|
545
|
+
value: "angular",
|
|
546
|
+
label: "Angular",
|
|
547
|
+
labelNode: (
|
|
548
|
+
<div className="al-flex al-items-center al-justify-between al-w-full">
|
|
549
|
+
<div className="al-flex al-items-center al-gap-2">
|
|
550
|
+
<div className="al-w-4 al-h-4 al-bg-red-500 al-rounded"></div>
|
|
551
|
+
<span>Angular</span>
|
|
552
|
+
</div>
|
|
553
|
+
<span className="al-text-xs al-text-muted-foreground">Google</span>
|
|
554
|
+
</div>
|
|
555
|
+
),
|
|
556
|
+
},
|
|
557
|
+
{
|
|
558
|
+
value: "svelte",
|
|
559
|
+
label: "Svelte",
|
|
560
|
+
labelNode: (
|
|
561
|
+
<div className="al-flex al-items-center al-justify-between al-w-full">
|
|
562
|
+
<div className="al-flex al-items-center al-gap-2">
|
|
563
|
+
<div className="al-w-4 al-h-4 al-bg-orange-500 al-rounded"></div>
|
|
564
|
+
<span>Svelte</span>
|
|
565
|
+
</div>
|
|
566
|
+
<span className="al-text-xs al-text-muted-foreground">Rich Harris</span>
|
|
567
|
+
</div>
|
|
568
|
+
),
|
|
569
|
+
},
|
|
570
|
+
];
|
|
571
|
+
|
|
572
|
+
// Options with interactive elements
|
|
573
|
+
const optionsWithButtons = [
|
|
574
|
+
{
|
|
575
|
+
value: "typescript",
|
|
576
|
+
label: "TypeScript",
|
|
577
|
+
labelNode: (
|
|
578
|
+
<div className="al-flex al-items-center al-justify-between al-w-full">
|
|
579
|
+
<div className="al-flex al-items-center al-gap-2">
|
|
580
|
+
<div className="al-w-4 al-h-4 al-bg-blue-600 al-rounded"></div>
|
|
581
|
+
<div>
|
|
582
|
+
<div className="al-font-medium">TypeScript</div>
|
|
583
|
+
<div className="al-text-xs al-text-muted-foreground">Strongly typed JavaScript</div>
|
|
584
|
+
</div>
|
|
585
|
+
</div>
|
|
586
|
+
<button
|
|
587
|
+
onClick={e => {
|
|
588
|
+
e.stopPropagation();
|
|
589
|
+
alert("TypeScript info clicked!");
|
|
590
|
+
}}
|
|
591
|
+
className="al-text-xs al-px-2 al-py-1 al-bg-blue-100 al-text-blue-700 al-rounded hover:al-bg-blue-200"
|
|
592
|
+
>
|
|
593
|
+
Info
|
|
594
|
+
</button>
|
|
595
|
+
</div>
|
|
596
|
+
),
|
|
597
|
+
},
|
|
598
|
+
{
|
|
599
|
+
value: "javascript",
|
|
600
|
+
label: "JavaScript",
|
|
601
|
+
labelNode: (
|
|
602
|
+
<div className="al-flex al-items-center al-justify-between al-w-full">
|
|
603
|
+
<div className="al-flex al-items-center al-gap-2">
|
|
604
|
+
<div className="al-w-4 al-h-4 al-bg-yellow-500 al-rounded"></div>
|
|
605
|
+
<div>
|
|
606
|
+
<div className="al-font-medium">JavaScript</div>
|
|
607
|
+
<div className="al-text-xs al-text-muted-foreground">Dynamic scripting language</div>
|
|
608
|
+
</div>
|
|
609
|
+
</div>
|
|
610
|
+
<button
|
|
611
|
+
onClick={e => {
|
|
612
|
+
e.stopPropagation();
|
|
613
|
+
alert("JavaScript info clicked!");
|
|
614
|
+
}}
|
|
615
|
+
className="al-text-xs al-px-2 al-py-1 al-bg-yellow-100 al-text-yellow-700 al-rounded hover:al-bg-yellow-200"
|
|
616
|
+
>
|
|
617
|
+
Info
|
|
618
|
+
</button>
|
|
619
|
+
</div>
|
|
620
|
+
),
|
|
621
|
+
},
|
|
622
|
+
{
|
|
623
|
+
value: "python",
|
|
624
|
+
label: "Python",
|
|
625
|
+
labelNode: (
|
|
626
|
+
<div className="al-flex al-items-center al-justify-between al-w-full">
|
|
627
|
+
<div className="al-flex al-items-center al-gap-2">
|
|
628
|
+
<div className="al-w-4 al-h-4 al-bg-green-600 al-rounded"></div>
|
|
629
|
+
<div>
|
|
630
|
+
<div className="al-font-medium">Python</div>
|
|
631
|
+
<div className="al-text-xs al-text-muted-foreground">
|
|
632
|
+
High-level programming language
|
|
633
|
+
</div>
|
|
634
|
+
</div>
|
|
635
|
+
</div>
|
|
636
|
+
<button
|
|
637
|
+
onClick={e => {
|
|
638
|
+
e.stopPropagation();
|
|
639
|
+
alert("Python info clicked!");
|
|
640
|
+
}}
|
|
641
|
+
className="al-text-xs al-px-2 al-py-1 al-bg-green-100 al-text-green-700 al-rounded hover:al-bg-green-200"
|
|
642
|
+
>
|
|
643
|
+
Info
|
|
644
|
+
</button>
|
|
645
|
+
</div>
|
|
646
|
+
),
|
|
647
|
+
},
|
|
648
|
+
];
|
|
649
|
+
|
|
650
|
+
// Mixed options - some with nodes, some without
|
|
651
|
+
const mixedOptions = [
|
|
652
|
+
{ value: "standard1", label: "Standard Option 1" },
|
|
653
|
+
{
|
|
654
|
+
value: "custom1",
|
|
655
|
+
label: "Custom Option 1",
|
|
656
|
+
labelNode: (
|
|
657
|
+
<div className="al-flex al-items-center al-gap-2">
|
|
658
|
+
<div className="al-w-2 al-h-2 al-bg-purple-500 al-rounded-full"></div>
|
|
659
|
+
<span className="al-italic">Custom with node</span>
|
|
660
|
+
</div>
|
|
661
|
+
),
|
|
662
|
+
},
|
|
663
|
+
{ value: "standard2", label: "Standard Option 2" },
|
|
664
|
+
{
|
|
665
|
+
value: "custom2",
|
|
666
|
+
label: "Custom Option 2",
|
|
667
|
+
labelNode: (
|
|
668
|
+
<div className="al-flex al-items-center al-gap-2">
|
|
669
|
+
<div className="al-w-2 al-h-2 al-bg-pink-500 al-rounded-full"></div>
|
|
670
|
+
<span className="al-font-bold">Another custom node</span>
|
|
671
|
+
</div>
|
|
672
|
+
),
|
|
673
|
+
},
|
|
674
|
+
];
|
|
675
|
+
|
|
676
|
+
return (
|
|
677
|
+
<div className="al-flex al-flex-col al-gap-8 al-justify-start al-items-start">
|
|
678
|
+
<div className="al-flex al-flex-col al-gap-2">
|
|
679
|
+
<h3 className="al-text-lg al-font-medium">Custom Node Rendering</h3>
|
|
680
|
+
<p className="al-text-sm al-text-muted-foreground">
|
|
681
|
+
Use the optional <code>node</code> property to provide custom ReactNode rendering for
|
|
682
|
+
dropdown options while keeping <code>label</code> as a string for text operations.
|
|
683
|
+
</p>
|
|
684
|
+
</div>
|
|
685
|
+
|
|
686
|
+
<div className="al-flex al-flex-col al-gap-6">
|
|
687
|
+
<div className="al-flex al-flex-col al-gap-4">
|
|
688
|
+
<h4 className="al-text-md al-font-medium">Framework Selector with Icons & Authors</h4>
|
|
689
|
+
<p className="al-text-sm al-text-muted-foreground">
|
|
690
|
+
Custom nodes with colored indicators and author information
|
|
691
|
+
</p>
|
|
692
|
+
|
|
693
|
+
<div className="al-flex al-flex-col al-gap-2">
|
|
694
|
+
<label className="al-text-sm al-font-medium">Single Select</label>
|
|
695
|
+
<Combobox
|
|
696
|
+
options={optionsWithNodes}
|
|
697
|
+
value={singleValue}
|
|
698
|
+
onChange={value => setSingleValue(value as string)}
|
|
699
|
+
placeholder="Select a framework..."
|
|
700
|
+
buttonProps={{ className: "al-w-[300px]" }}
|
|
701
|
+
/>
|
|
702
|
+
<div className="al-text-sm">Selected: {singleValue || "None"}</div>
|
|
703
|
+
</div>
|
|
704
|
+
|
|
705
|
+
<div className="al-flex al-flex-col al-gap-2">
|
|
706
|
+
<label className="al-text-sm al-font-medium">Multi Select</label>
|
|
707
|
+
<Combobox
|
|
708
|
+
options={optionsWithNodes}
|
|
709
|
+
value={multiValue}
|
|
710
|
+
onChange={value => setMultiValue(value as string[])}
|
|
711
|
+
placeholder="Select frameworks..."
|
|
712
|
+
multiSelect={true}
|
|
713
|
+
buttonProps={{ className: "al-w-[300px]" }}
|
|
714
|
+
/>
|
|
715
|
+
<div className="al-text-sm">
|
|
716
|
+
Selected: {multiValue.length > 0 ? multiValue.join(", ") : "None"}
|
|
717
|
+
</div>
|
|
718
|
+
</div>
|
|
719
|
+
</div>
|
|
720
|
+
|
|
721
|
+
<div className="al-flex al-flex-col al-gap-4">
|
|
722
|
+
<h4 className="al-text-md al-font-medium">Interactive Elements in Options</h4>
|
|
723
|
+
<p className="al-text-sm al-text-muted-foreground">
|
|
724
|
+
Options with interactive buttons that can be clicked without selecting the option
|
|
725
|
+
</p>
|
|
726
|
+
|
|
727
|
+
<div className="al-flex al-flex-col al-gap-2">
|
|
728
|
+
<label className="al-text-sm al-font-medium">
|
|
729
|
+
Programming Languages with Info Buttons
|
|
730
|
+
</label>
|
|
731
|
+
<Combobox
|
|
732
|
+
options={optionsWithButtons}
|
|
733
|
+
value={singleValue}
|
|
734
|
+
onChange={value => setSingleValue(value as string)}
|
|
735
|
+
placeholder="Select a language..."
|
|
736
|
+
buttonProps={{ className: "al-w-[350px]" }}
|
|
737
|
+
popoverContentProps={{ className: "al-w-[350px]" }}
|
|
738
|
+
/>
|
|
739
|
+
</div>
|
|
740
|
+
</div>
|
|
741
|
+
|
|
742
|
+
<div className="al-flex al-flex-col al-gap-4">
|
|
743
|
+
<h4 className="al-text-md al-font-medium">Mixed Standard and Custom Options</h4>
|
|
744
|
+
<p className="al-text-sm al-text-muted-foreground">
|
|
745
|
+
Demonstrating that you can mix standard string labels with custom node rendering
|
|
746
|
+
</p>
|
|
747
|
+
|
|
748
|
+
<div className="al-flex al-flex-col al-gap-2">
|
|
749
|
+
<label className="al-text-sm al-font-medium">Mixed Options</label>
|
|
750
|
+
<Combobox
|
|
751
|
+
options={mixedOptions}
|
|
752
|
+
value={singleValue}
|
|
753
|
+
onChange={value => setSingleValue(value as string)}
|
|
754
|
+
placeholder="Select an option..."
|
|
755
|
+
buttonProps={{ className: "al-w-[250px]" }}
|
|
756
|
+
/>
|
|
757
|
+
<div className="al-text-sm">Selected: {singleValue || "None"}</div>
|
|
758
|
+
</div>
|
|
759
|
+
</div>
|
|
760
|
+
</div>
|
|
761
|
+
</div>
|
|
762
|
+
);
|
|
763
|
+
};
|
|
764
|
+
|
|
587
765
|
export const ComboboxPopoverCustomizationExample: StoryFn = () => {
|
|
588
766
|
const options = [
|
|
589
767
|
{ value: "react", label: "React" },
|
|
@@ -648,3 +826,729 @@ export const ComboboxPopoverCustomizationExample: StoryFn = () => {
|
|
|
648
826
|
</div>
|
|
649
827
|
);
|
|
650
828
|
};
|
|
829
|
+
|
|
830
|
+
export const ComboboxValidationExample: StoryFn = () => {
|
|
831
|
+
const options = [
|
|
832
|
+
{ value: "databases", label: "Databases" },
|
|
833
|
+
{ value: "api", label: "API" },
|
|
834
|
+
{ value: "frontend", label: "Frontend" },
|
|
835
|
+
{ value: "backend", label: "Backend" },
|
|
836
|
+
{ value: "devops", label: "DevOps" },
|
|
837
|
+
{ value: "mobile", label: "Mobile" },
|
|
838
|
+
];
|
|
839
|
+
|
|
840
|
+
const [selectedValue, setSelectedValue] = useState<string[]>(["databases"]);
|
|
841
|
+
const [selectedValueWithoutValidation, setSelectedValueWithoutValidation] = useState<string[]>(
|
|
842
|
+
[]
|
|
843
|
+
);
|
|
844
|
+
|
|
845
|
+
return (
|
|
846
|
+
<div className="al-flex al-flex-col al-gap-6 al-justify-start al-items-start">
|
|
847
|
+
<div className="al-flex al-flex-col al-gap-2">
|
|
848
|
+
<h3 className="al-text-lg al-font-medium">Validation Examples</h3>
|
|
849
|
+
<p className="al-text-sm al-text-muted-foreground">
|
|
850
|
+
Demonstrating the disableAllDeselect prop for preventing empty selections
|
|
851
|
+
</p>
|
|
852
|
+
</div>
|
|
853
|
+
|
|
854
|
+
<div className="al-flex al-flex-col al-gap-6">
|
|
855
|
+
<div className="al-flex al-flex-col al-gap-2">
|
|
856
|
+
<label className="al-text-sm al-font-medium">
|
|
857
|
+
With Validation (disableAllDeselect=true)
|
|
858
|
+
</label>
|
|
859
|
+
<p className="al-text-xs al-text-muted-foreground">
|
|
860
|
+
Try to deselect all items and click Apply - you'll see a warning message
|
|
861
|
+
</p>
|
|
862
|
+
<Combobox
|
|
863
|
+
options={options}
|
|
864
|
+
value={selectedValue}
|
|
865
|
+
onChange={value => setSelectedValue(value as string[])}
|
|
866
|
+
placeholder="Select at least one skill..."
|
|
867
|
+
multiSelect={true}
|
|
868
|
+
showApplyButton={true}
|
|
869
|
+
showClearButton={true}
|
|
870
|
+
disableAllDeselect={true}
|
|
871
|
+
buttonProps={{ className: "al-w-[300px]" }}
|
|
872
|
+
/>
|
|
873
|
+
<div className="al-text-sm">
|
|
874
|
+
Selected: {selectedValue.length > 0 ? selectedValue.join(", ") : "None"}
|
|
875
|
+
</div>
|
|
876
|
+
</div>
|
|
877
|
+
|
|
878
|
+
<div className="al-flex al-flex-col al-gap-2">
|
|
879
|
+
<label className="al-text-sm al-font-medium">
|
|
880
|
+
Without Validation (disableAllDeselect=false)
|
|
881
|
+
</label>
|
|
882
|
+
<p className="al-text-xs al-text-muted-foreground">
|
|
883
|
+
You can deselect all items and apply with an empty selection
|
|
884
|
+
</p>
|
|
885
|
+
<Combobox
|
|
886
|
+
options={options}
|
|
887
|
+
value={selectedValueWithoutValidation}
|
|
888
|
+
onChange={value => setSelectedValueWithoutValidation(value as string[])}
|
|
889
|
+
placeholder="Select skills (optional)..."
|
|
890
|
+
multiSelect={true}
|
|
891
|
+
showApplyButton={true}
|
|
892
|
+
showClearButton={true}
|
|
893
|
+
disableAllDeselect={false}
|
|
894
|
+
buttonProps={{ className: "al-w-[300px]" }}
|
|
895
|
+
/>
|
|
896
|
+
<div className="al-text-sm">
|
|
897
|
+
Selected:{" "}
|
|
898
|
+
{selectedValueWithoutValidation.length > 0
|
|
899
|
+
? selectedValueWithoutValidation.join(", ")
|
|
900
|
+
: "None"}
|
|
901
|
+
</div>
|
|
902
|
+
</div>
|
|
903
|
+
</div>
|
|
904
|
+
</div>
|
|
905
|
+
);
|
|
906
|
+
};
|
|
907
|
+
|
|
908
|
+
export const ComboboxDebouncedSearchExample: StoryFn = () => {
|
|
909
|
+
const allOptions = [
|
|
910
|
+
{ value: "javascript", label: "JavaScript" },
|
|
911
|
+
{ value: "typescript", label: "TypeScript" },
|
|
912
|
+
{ value: "python", label: "Python" },
|
|
913
|
+
{ value: "java", label: "Java" },
|
|
914
|
+
{ value: "csharp", label: "C#" },
|
|
915
|
+
{ value: "cpp", label: "C++" },
|
|
916
|
+
{ value: "go", label: "Go" },
|
|
917
|
+
{ value: "rust", label: "Rust" },
|
|
918
|
+
{ value: "php", label: "PHP" },
|
|
919
|
+
{ value: "ruby", label: "Ruby" },
|
|
920
|
+
{ value: "swift", label: "Swift" },
|
|
921
|
+
{ value: "kotlin", label: "Kotlin" },
|
|
922
|
+
];
|
|
923
|
+
|
|
924
|
+
const [selectedValue, setSelectedValue] = useState("");
|
|
925
|
+
const [filteredOptions, setFilteredOptions] = useState(allOptions);
|
|
926
|
+
const [searchHistory, setSearchHistory] = useState<string[]>([]);
|
|
927
|
+
|
|
928
|
+
const clearHistory = () => {
|
|
929
|
+
setSearchHistory([]);
|
|
930
|
+
setFilteredOptions(allOptions);
|
|
931
|
+
};
|
|
932
|
+
|
|
933
|
+
return (
|
|
934
|
+
<div className="al-flex al-flex-col al-gap-6 al-justify-start al-items-start">
|
|
935
|
+
<div className="al-flex al-gap-4 al-items-center">
|
|
936
|
+
<h3 className="al-text-lg al-font-medium">Debounced Search Example</h3>
|
|
937
|
+
<Button onClick={clearHistory} size="sm" variant="outline">
|
|
938
|
+
Clear History
|
|
939
|
+
</Button>
|
|
940
|
+
</div>
|
|
941
|
+
|
|
942
|
+
<div className="al-flex al-flex-col al-gap-4">
|
|
943
|
+
<div className="al-flex al-flex-col al-gap-2">
|
|
944
|
+
<label className="al-text-sm al-font-medium">Search with Default Debounce (300ms)</label>
|
|
945
|
+
<p className="al-text-xs al-text-muted-foreground">
|
|
946
|
+
Type quickly to see how the search is debounced. The search function will only be called
|
|
947
|
+
300ms after you stop typing.
|
|
948
|
+
</p>
|
|
949
|
+
<Combobox
|
|
950
|
+
options={filteredOptions}
|
|
951
|
+
value={selectedValue}
|
|
952
|
+
onChange={value => setSelectedValue(value as string)}
|
|
953
|
+
placeholder="Search programming languages..."
|
|
954
|
+
searchPlaceholder="Type to search languages..."
|
|
955
|
+
buttonProps={{ className: "al-w-[300px]" }}
|
|
956
|
+
/>
|
|
957
|
+
<div className="al-text-sm">
|
|
958
|
+
Showing {filteredOptions.length} of {allOptions.length} languages
|
|
959
|
+
</div>
|
|
960
|
+
</div>
|
|
961
|
+
|
|
962
|
+
{searchHistory.length > 0 && (
|
|
963
|
+
<div className="al-flex al-flex-col al-gap-2">
|
|
964
|
+
<label className="al-text-sm al-font-medium">Search History (Last 10 calls):</label>
|
|
965
|
+
<ul className="al-text-xs al-bg-muted al-p-2 al-rounded al-space-y-1 al-max-h-40 al-overflow-y-auto">
|
|
966
|
+
{searchHistory.map((entry, index) => (
|
|
967
|
+
<li key={index} className="al-font-mono">
|
|
968
|
+
{entry}
|
|
969
|
+
</li>
|
|
970
|
+
))}
|
|
971
|
+
</ul>
|
|
972
|
+
</div>
|
|
973
|
+
)}
|
|
974
|
+
</div>
|
|
975
|
+
</div>
|
|
976
|
+
);
|
|
977
|
+
};
|
|
978
|
+
|
|
979
|
+
export const ComboboxFilterDropdownCompatibilityExample: StoryFn = () => {
|
|
980
|
+
const tagOptions = [
|
|
981
|
+
{ value: "urgent", label: "Urgent" },
|
|
982
|
+
{ value: "bug", label: "Bug" },
|
|
983
|
+
{ value: "feature", label: "Feature" },
|
|
984
|
+
{ value: "enhancement", label: "Enhancement" },
|
|
985
|
+
{ value: "documentation", label: "Documentation" },
|
|
986
|
+
{ value: "question", label: "Question" },
|
|
987
|
+
{ value: "duplicate", label: "Duplicate" },
|
|
988
|
+
{ value: "wontfix", label: "Won't Fix" },
|
|
989
|
+
];
|
|
990
|
+
|
|
991
|
+
const [selectedTags, setSelectedTags] = useState<string[]>(["urgent", "bug"]);
|
|
992
|
+
const [requiredTags, setRequiredTags] = useState<string[]>(["urgent"]);
|
|
993
|
+
|
|
994
|
+
return (
|
|
995
|
+
<div className="al-flex al-flex-col al-gap-6 al-justify-start al-items-start">
|
|
996
|
+
<div className="al-flex al-flex-col al-gap-2">
|
|
997
|
+
<h3 className="al-text-lg al-font-medium">FilterDropdown Compatibility</h3>
|
|
998
|
+
<p className="al-text-sm al-text-muted-foreground">
|
|
999
|
+
Demonstrating all FilterDropdown features: debounced search, validation, apply button, and
|
|
1000
|
+
clear functionality
|
|
1001
|
+
</p>
|
|
1002
|
+
</div>
|
|
1003
|
+
|
|
1004
|
+
<div className="al-flex al-flex-col al-gap-6">
|
|
1005
|
+
<div className="al-flex al-flex-col al-gap-2">
|
|
1006
|
+
<label className="al-text-sm al-font-medium">Full Feature Set</label>
|
|
1007
|
+
<p className="al-text-xs al-text-muted-foreground">
|
|
1008
|
+
Multi-select with apply button, clear button, debounced search, and validation
|
|
1009
|
+
</p>
|
|
1010
|
+
<Combobox
|
|
1011
|
+
options={tagOptions}
|
|
1012
|
+
value={selectedTags}
|
|
1013
|
+
onChange={value => setSelectedTags(value as string[])}
|
|
1014
|
+
placeholder="Select tags..."
|
|
1015
|
+
searchPlaceholder="Search tags..."
|
|
1016
|
+
multiSelect={true}
|
|
1017
|
+
showApplyButton={true}
|
|
1018
|
+
showClearButton={true}
|
|
1019
|
+
disableAllDeselect={false}
|
|
1020
|
+
buttonProps={{ className: "al-w-[300px]" }}
|
|
1021
|
+
/>
|
|
1022
|
+
<div className="al-text-sm">
|
|
1023
|
+
Selected tags: {selectedTags.length > 0 ? selectedTags.join(", ") : "None"}
|
|
1024
|
+
</div>
|
|
1025
|
+
</div>
|
|
1026
|
+
|
|
1027
|
+
<div className="al-flex al-flex-col al-gap-2">
|
|
1028
|
+
<label className="al-text-sm al-font-medium">
|
|
1029
|
+
Required Tags (disableAllDeselect=true)
|
|
1030
|
+
</label>
|
|
1031
|
+
<p className="al-text-xs al-text-muted-foreground">
|
|
1032
|
+
At least one tag must always be selected. Try to deselect all and apply.
|
|
1033
|
+
</p>
|
|
1034
|
+
<Combobox
|
|
1035
|
+
options={tagOptions}
|
|
1036
|
+
value={requiredTags}
|
|
1037
|
+
onChange={value => setRequiredTags(value as string[])}
|
|
1038
|
+
placeholder="Select required tags..."
|
|
1039
|
+
searchPlaceholder="Find tags..."
|
|
1040
|
+
multiSelect={true}
|
|
1041
|
+
showApplyButton={true}
|
|
1042
|
+
showClearButton={true}
|
|
1043
|
+
disableAllDeselect={true}
|
|
1044
|
+
buttonProps={{ className: "al-w-[300px]" }}
|
|
1045
|
+
/>
|
|
1046
|
+
<div className="al-text-sm">
|
|
1047
|
+
Required tags: {requiredTags.length > 0 ? requiredTags.join(", ") : "None"}
|
|
1048
|
+
</div>
|
|
1049
|
+
</div>
|
|
1050
|
+
</div>
|
|
1051
|
+
</div>
|
|
1052
|
+
);
|
|
1053
|
+
};
|
|
1054
|
+
|
|
1055
|
+
export const ComboboxHierarchicalExample: StoryFn = () => {
|
|
1056
|
+
const [selectedDepartment, setSelectedDepartment] = useState("");
|
|
1057
|
+
const [selectedMultiDepartment, setSelectedMultiDepartment] = useState<string[]>([]);
|
|
1058
|
+
const [hierarchicalSelection, setHierarchicalSelection] = useState<{
|
|
1059
|
+
value: string;
|
|
1060
|
+
label: string;
|
|
1061
|
+
parent?: { value: string; label: string };
|
|
1062
|
+
} | null>(null);
|
|
1063
|
+
|
|
1064
|
+
// Helper function to handle hierarchical selection
|
|
1065
|
+
const handleHierarchicalSelect = (value: string, parentValue: string, parentLabel: string) => {
|
|
1066
|
+
// Update single select state
|
|
1067
|
+
setSelectedDepartment(value);
|
|
1068
|
+
setHierarchicalSelection({
|
|
1069
|
+
value,
|
|
1070
|
+
label:
|
|
1071
|
+
departmentOptions.find(opt => opt.nestedLabels?.[value])?.nestedLabels?.[value] || value,
|
|
1072
|
+
parent: { value: parentValue, label: parentLabel },
|
|
1073
|
+
});
|
|
1074
|
+
|
|
1075
|
+
// Also update multi-select state (toggle the value) since no apply button
|
|
1076
|
+
setSelectedMultiDepartment(prev => {
|
|
1077
|
+
if (prev.includes(value)) {
|
|
1078
|
+
return prev.filter(v => v !== value);
|
|
1079
|
+
} else {
|
|
1080
|
+
return [...prev, value];
|
|
1081
|
+
}
|
|
1082
|
+
});
|
|
1083
|
+
};
|
|
1084
|
+
|
|
1085
|
+
// Helper function to get the selected value for a nested combobox
|
|
1086
|
+
const getNestedValue = (parentValue: string): string => {
|
|
1087
|
+
const parentOption = departmentOptions.find(opt => opt.value === parentValue);
|
|
1088
|
+
if (parentOption?.nestedLabels && selectedDepartment in parentOption.nestedLabels) {
|
|
1089
|
+
return selectedDepartment;
|
|
1090
|
+
}
|
|
1091
|
+
return "";
|
|
1092
|
+
};
|
|
1093
|
+
|
|
1094
|
+
// Hierarchical options with subcomponents for single select
|
|
1095
|
+
const departmentOptions: ComboboxOption[] = [
|
|
1096
|
+
{
|
|
1097
|
+
value: "engineering",
|
|
1098
|
+
label: "Engineering",
|
|
1099
|
+
nestedLabels: {
|
|
1100
|
+
frontend: "Frontend",
|
|
1101
|
+
backend: "Backend",
|
|
1102
|
+
devops: "DevOps",
|
|
1103
|
+
mobile: "Mobile",
|
|
1104
|
+
},
|
|
1105
|
+
children: (close: () => void) => (
|
|
1106
|
+
<div className="al-flex al-flex-col al-gap-2">
|
|
1107
|
+
<h4 className="al-font-medium al-text-sm">Engineering Teams</h4>
|
|
1108
|
+
<div className="al-grid al-grid-cols-2 al-gap-2">
|
|
1109
|
+
<Button
|
|
1110
|
+
size="sm"
|
|
1111
|
+
variant="outline"
|
|
1112
|
+
onClick={() => {
|
|
1113
|
+
handleHierarchicalSelect("frontend", "engineering", "Engineering");
|
|
1114
|
+
close();
|
|
1115
|
+
}}
|
|
1116
|
+
>
|
|
1117
|
+
Frontend
|
|
1118
|
+
</Button>
|
|
1119
|
+
<Button
|
|
1120
|
+
size="sm"
|
|
1121
|
+
variant="outline"
|
|
1122
|
+
onClick={() => {
|
|
1123
|
+
handleHierarchicalSelect("backend", "engineering", "Engineering");
|
|
1124
|
+
close();
|
|
1125
|
+
}}
|
|
1126
|
+
>
|
|
1127
|
+
Backend
|
|
1128
|
+
</Button>
|
|
1129
|
+
<Button
|
|
1130
|
+
size="sm"
|
|
1131
|
+
variant="outline"
|
|
1132
|
+
onClick={() => {
|
|
1133
|
+
handleHierarchicalSelect("devops", "engineering", "Engineering");
|
|
1134
|
+
close();
|
|
1135
|
+
}}
|
|
1136
|
+
>
|
|
1137
|
+
DevOps
|
|
1138
|
+
</Button>
|
|
1139
|
+
<Button
|
|
1140
|
+
size="sm"
|
|
1141
|
+
variant="outline"
|
|
1142
|
+
onClick={() => {
|
|
1143
|
+
handleHierarchicalSelect("mobile", "engineering", "Engineering");
|
|
1144
|
+
close();
|
|
1145
|
+
}}
|
|
1146
|
+
>
|
|
1147
|
+
Mobile
|
|
1148
|
+
</Button>
|
|
1149
|
+
</div>
|
|
1150
|
+
<div className="al-text-xs al-text-muted-foreground al-mt-2">
|
|
1151
|
+
Click a team to select and close
|
|
1152
|
+
</div>
|
|
1153
|
+
</div>
|
|
1154
|
+
),
|
|
1155
|
+
},
|
|
1156
|
+
{
|
|
1157
|
+
value: "design",
|
|
1158
|
+
label: "Design",
|
|
1159
|
+
nestedLabels: {
|
|
1160
|
+
"ux-design": "UX Designer",
|
|
1161
|
+
"ui-design": "UI Designer",
|
|
1162
|
+
"product-design": "Product Designer",
|
|
1163
|
+
},
|
|
1164
|
+
children: (close: () => void) => (
|
|
1165
|
+
<div className="al-flex al-flex-col al-gap-2">
|
|
1166
|
+
<h4 className="al-font-medium al-text-sm">Design Roles</h4>
|
|
1167
|
+
<div className="al-space-y-1">
|
|
1168
|
+
<Button
|
|
1169
|
+
size="sm"
|
|
1170
|
+
variant="ghost"
|
|
1171
|
+
className="al-w-full al-justify-start"
|
|
1172
|
+
onClick={() => {
|
|
1173
|
+
handleHierarchicalSelect("ux-design", "design", "Design");
|
|
1174
|
+
close();
|
|
1175
|
+
}}
|
|
1176
|
+
>
|
|
1177
|
+
UX Designer
|
|
1178
|
+
</Button>
|
|
1179
|
+
<Button
|
|
1180
|
+
size="sm"
|
|
1181
|
+
variant="ghost"
|
|
1182
|
+
className="al-w-full al-justify-start"
|
|
1183
|
+
onClick={() => {
|
|
1184
|
+
handleHierarchicalSelect("ui-design", "design", "Design");
|
|
1185
|
+
close();
|
|
1186
|
+
}}
|
|
1187
|
+
>
|
|
1188
|
+
UI Designer
|
|
1189
|
+
</Button>
|
|
1190
|
+
<Button
|
|
1191
|
+
size="sm"
|
|
1192
|
+
variant="ghost"
|
|
1193
|
+
className="al-w-full al-justify-start"
|
|
1194
|
+
onClick={() => {
|
|
1195
|
+
handleHierarchicalSelect("product-design", "design", "Design");
|
|
1196
|
+
close();
|
|
1197
|
+
}}
|
|
1198
|
+
>
|
|
1199
|
+
Product Designer
|
|
1200
|
+
</Button>
|
|
1201
|
+
</div>
|
|
1202
|
+
</div>
|
|
1203
|
+
),
|
|
1204
|
+
},
|
|
1205
|
+
{
|
|
1206
|
+
value: "marketing",
|
|
1207
|
+
label: "Marketing",
|
|
1208
|
+
nestedLabels: {
|
|
1209
|
+
content: "Content Marketing",
|
|
1210
|
+
social: "Social Media",
|
|
1211
|
+
email: "Email Marketing",
|
|
1212
|
+
seo: "SEO/SEM",
|
|
1213
|
+
},
|
|
1214
|
+
children: (close: () => void) => (
|
|
1215
|
+
<div className="al-flex al-flex-col al-gap-2">
|
|
1216
|
+
<h4 className="al-font-medium al-text-sm">Marketing Channels</h4>
|
|
1217
|
+
<Combobox
|
|
1218
|
+
options={[
|
|
1219
|
+
{ value: "content", label: "Content Marketing" },
|
|
1220
|
+
{ value: "social", label: "Social Media" },
|
|
1221
|
+
{ value: "email", label: "Email Marketing" },
|
|
1222
|
+
{ value: "seo", label: "SEO/SEM" },
|
|
1223
|
+
]}
|
|
1224
|
+
value={getNestedValue("marketing")}
|
|
1225
|
+
onChange={value => {
|
|
1226
|
+
handleHierarchicalSelect(value as string, "marketing", "Marketing");
|
|
1227
|
+
close();
|
|
1228
|
+
}}
|
|
1229
|
+
placeholder="Select marketing area..."
|
|
1230
|
+
buttonProps={{ className: "al-w-full" }}
|
|
1231
|
+
/>
|
|
1232
|
+
<div className="al-text-xs al-text-muted-foreground">
|
|
1233
|
+
Nested combobox for marketing specializations
|
|
1234
|
+
</div>
|
|
1235
|
+
</div>
|
|
1236
|
+
),
|
|
1237
|
+
},
|
|
1238
|
+
{
|
|
1239
|
+
value: "sales",
|
|
1240
|
+
label: "Sales",
|
|
1241
|
+
// No subComponent - regular option
|
|
1242
|
+
},
|
|
1243
|
+
{
|
|
1244
|
+
value: "hr",
|
|
1245
|
+
label: "Human Resources",
|
|
1246
|
+
// No subComponent - regular option
|
|
1247
|
+
},
|
|
1248
|
+
];
|
|
1249
|
+
|
|
1250
|
+
return (
|
|
1251
|
+
<div className="al-flex al-flex-col al-gap-6 al-justify-start al-items-start">
|
|
1252
|
+
<div className="al-flex al-flex-col al-gap-2">
|
|
1253
|
+
<h3 className="al-text-lg al-font-medium">Hierarchical Options</h3>
|
|
1254
|
+
<p className="al-text-sm al-text-muted-foreground">
|
|
1255
|
+
NEW FEATURE: Options with subComponent show a chevron and display nested content on hover.
|
|
1256
|
+
Mix hierarchical and regular options freely.
|
|
1257
|
+
</p>
|
|
1258
|
+
</div>
|
|
1259
|
+
|
|
1260
|
+
<div className="al-flex al-flex-col al-gap-6">
|
|
1261
|
+
<div className="al-flex al-flex-col al-gap-2">
|
|
1262
|
+
<label className="al-text-sm al-font-medium">Department Selection (Single)</label>
|
|
1263
|
+
<p className="al-text-xs al-text-muted-foreground">
|
|
1264
|
+
Hover over Engineering, Design, or Marketing to see subcomponents. Sales and HR are
|
|
1265
|
+
regular options.
|
|
1266
|
+
</p>
|
|
1267
|
+
<Combobox
|
|
1268
|
+
options={departmentOptions}
|
|
1269
|
+
value={selectedDepartment}
|
|
1270
|
+
onChange={value => setSelectedDepartment(value as string)}
|
|
1271
|
+
placeholder="Select department..."
|
|
1272
|
+
searchPlaceholder="Search departments..."
|
|
1273
|
+
hoverDelayMs={200}
|
|
1274
|
+
buttonProps={{ className: "al-w-[300px]" }}
|
|
1275
|
+
/>
|
|
1276
|
+
<div className="al-text-sm">Selected: {selectedDepartment || "None"}</div>
|
|
1277
|
+
{hierarchicalSelection && (
|
|
1278
|
+
<div className="al-text-sm al-bg-blue-50 al-p-2 al-rounded">
|
|
1279
|
+
<strong>Hierarchical Selection:</strong>
|
|
1280
|
+
<br />
|
|
1281
|
+
Value: {hierarchicalSelection.value}
|
|
1282
|
+
<br />
|
|
1283
|
+
Label: {hierarchicalSelection.label}
|
|
1284
|
+
<br />
|
|
1285
|
+
{hierarchicalSelection.parent && (
|
|
1286
|
+
<>
|
|
1287
|
+
Parent: {hierarchicalSelection.parent.label} ({hierarchicalSelection.parent.value}
|
|
1288
|
+
)
|
|
1289
|
+
</>
|
|
1290
|
+
)}
|
|
1291
|
+
</div>
|
|
1292
|
+
)}
|
|
1293
|
+
</div>
|
|
1294
|
+
|
|
1295
|
+
<div className="al-flex al-flex-col al-gap-2">
|
|
1296
|
+
<label className="al-text-sm al-font-medium">Department Selection (Multi-Select)</label>
|
|
1297
|
+
<p className="al-text-xs al-text-muted-foreground">
|
|
1298
|
+
Multi-select mode also supports hierarchical options. Regular options (Sales/HR) can be
|
|
1299
|
+
selected from dropdown, hierarchical options (Engineering/Design/Marketing) show nested
|
|
1300
|
+
selections on hover.
|
|
1301
|
+
</p>
|
|
1302
|
+
<Combobox
|
|
1303
|
+
options={departmentOptions}
|
|
1304
|
+
value={selectedMultiDepartment}
|
|
1305
|
+
onChange={value => setSelectedMultiDepartment(value as string[])}
|
|
1306
|
+
placeholder="Select departments..."
|
|
1307
|
+
searchPlaceholder="Search departments..."
|
|
1308
|
+
multiSelect={true}
|
|
1309
|
+
showApplyButton={false}
|
|
1310
|
+
hoverDelayMs={300}
|
|
1311
|
+
buttonProps={{ className: "al-w-[300px]" }}
|
|
1312
|
+
/>
|
|
1313
|
+
<div className="al-text-sm">
|
|
1314
|
+
Selected:{" "}
|
|
1315
|
+
{selectedMultiDepartment.length > 0 ? selectedMultiDepartment.join(", ") : "None"}
|
|
1316
|
+
</div>
|
|
1317
|
+
</div>
|
|
1318
|
+
</div>
|
|
1319
|
+
</div>
|
|
1320
|
+
);
|
|
1321
|
+
};
|
|
1322
|
+
|
|
1323
|
+
export const ComboboxEnhancedAsyncExample: StoryFn = () => {
|
|
1324
|
+
const [selectedValue, setSelectedValue] = useState("");
|
|
1325
|
+
const [multiSelectedValue, setMultiSelectedValue] = useState<string[]>([]);
|
|
1326
|
+
const [displayedOptions, setDisplayedOptions] = useState<Array<{ value: string; label: string }>>(
|
|
1327
|
+
[]
|
|
1328
|
+
);
|
|
1329
|
+
const [hasMore, setHasMore] = useState(true);
|
|
1330
|
+
const [currentPage, setCurrentPage] = useState(1);
|
|
1331
|
+
const [currentSearch, setCurrentSearch] = useState("");
|
|
1332
|
+
|
|
1333
|
+
// Simulate API data
|
|
1334
|
+
const generateApiData = (search: string = "", page: number = 1) => {
|
|
1335
|
+
const allItems = Array.from({ length: 100 }, (_, i) => ({
|
|
1336
|
+
value: `item-${i + 1}`,
|
|
1337
|
+
label: `${search ? "Filtered " : ""}API Item ${i + 1}`,
|
|
1338
|
+
}));
|
|
1339
|
+
|
|
1340
|
+
const filteredItems = search
|
|
1341
|
+
? allItems.filter(item => item.label.toLowerCase().includes(search.toLowerCase()))
|
|
1342
|
+
: allItems;
|
|
1343
|
+
|
|
1344
|
+
const startIndex = (page - 1) * 20;
|
|
1345
|
+
const endIndex = startIndex + 20;
|
|
1346
|
+
const pageItems = filteredItems.slice(startIndex, endIndex);
|
|
1347
|
+
|
|
1348
|
+
return {
|
|
1349
|
+
items: pageItems,
|
|
1350
|
+
hasMore: endIndex < filteredItems.length,
|
|
1351
|
+
total: filteredItems.length,
|
|
1352
|
+
page,
|
|
1353
|
+
};
|
|
1354
|
+
};
|
|
1355
|
+
|
|
1356
|
+
const handleLoadMore = useCallback(async (searchValue?: string, page?: number) => {
|
|
1357
|
+
// Simulate API delay
|
|
1358
|
+
await new Promise(resolve => setTimeout(resolve, 800));
|
|
1359
|
+
|
|
1360
|
+
const isNewSearch = searchValue !== currentSearch;
|
|
1361
|
+
const targetPage = page || (isNewSearch ? 1 : currentPage + 1);
|
|
1362
|
+
|
|
1363
|
+
const result = generateApiData(searchValue || "", targetPage);
|
|
1364
|
+
|
|
1365
|
+
if (isNewSearch || targetPage === 1) {
|
|
1366
|
+
setDisplayedOptions(result.items);
|
|
1367
|
+
} else {
|
|
1368
|
+
setDisplayedOptions(prev => [...prev, ...result.items]);
|
|
1369
|
+
}
|
|
1370
|
+
|
|
1371
|
+
setHasMore(result.hasMore);
|
|
1372
|
+
setCurrentPage(targetPage);
|
|
1373
|
+
setCurrentSearch(searchValue || "");
|
|
1374
|
+
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
1375
|
+
}, []);
|
|
1376
|
+
|
|
1377
|
+
const handlePageReset = () => {
|
|
1378
|
+
setDisplayedOptions([]);
|
|
1379
|
+
setCurrentPage(1);
|
|
1380
|
+
setHasMore(true);
|
|
1381
|
+
};
|
|
1382
|
+
|
|
1383
|
+
const resetDemo = () => {
|
|
1384
|
+
setSelectedValue("");
|
|
1385
|
+
setMultiSelectedValue([]);
|
|
1386
|
+
setDisplayedOptions([]);
|
|
1387
|
+
setCurrentPage(1);
|
|
1388
|
+
setCurrentSearch("");
|
|
1389
|
+
setHasMore(true);
|
|
1390
|
+
};
|
|
1391
|
+
|
|
1392
|
+
return (
|
|
1393
|
+
<div className="al-flex al-flex-col al-gap-6 al-justify-start al-items-start">
|
|
1394
|
+
<div className="al-flex al-gap-4 al-items-center">
|
|
1395
|
+
<h3 className="al-text-lg al-font-medium">Enhanced Async Loading</h3>
|
|
1396
|
+
<Button onClick={resetDemo} size="sm" variant="outline">
|
|
1397
|
+
Reset Demo
|
|
1398
|
+
</Button>
|
|
1399
|
+
</div>
|
|
1400
|
+
|
|
1401
|
+
<div className="al-flex al-flex-col al-gap-2">
|
|
1402
|
+
<p className="al-text-sm al-text-muted-foreground">
|
|
1403
|
+
NEW FEATURES: Enhanced onLoadMore with search and page parameters, automatic page
|
|
1404
|
+
management, debounced search with page reset, and improved loading states.
|
|
1405
|
+
</p>
|
|
1406
|
+
<div className="al-text-xs al-bg-blue-50 al-p-2 al-rounded">
|
|
1407
|
+
<strong>Try this:</strong> Open dropdown → scroll to load more → search → see how
|
|
1408
|
+
pagination resets → search again
|
|
1409
|
+
</div>
|
|
1410
|
+
</div>
|
|
1411
|
+
|
|
1412
|
+
<div className="al-flex al-flex-col al-gap-6">
|
|
1413
|
+
<div className="al-flex al-flex-col al-gap-2">
|
|
1414
|
+
<label className="al-text-sm al-font-medium">Single Select with Enhanced Async</label>
|
|
1415
|
+
<p className="al-text-xs al-text-muted-foreground">
|
|
1416
|
+
Page: {currentPage} | Showing: {displayedOptions.length} items | Search: "
|
|
1417
|
+
{currentSearch}" | Has more: {hasMore ? "Yes" : "No"}
|
|
1418
|
+
</p>
|
|
1419
|
+
<Combobox
|
|
1420
|
+
options={displayedOptions}
|
|
1421
|
+
value={selectedValue}
|
|
1422
|
+
onChange={value => setSelectedValue(value as string)}
|
|
1423
|
+
placeholder="Select item..."
|
|
1424
|
+
searchPlaceholder="Search items (debounced)..."
|
|
1425
|
+
onLoadMore={handleLoadMore}
|
|
1426
|
+
hasMore={hasMore}
|
|
1427
|
+
onPageReset={handlePageReset}
|
|
1428
|
+
buttonProps={{ className: "al-w-[300px]" }}
|
|
1429
|
+
/>
|
|
1430
|
+
<div className="al-text-sm">Selected: {selectedValue || "None"}</div>
|
|
1431
|
+
</div>
|
|
1432
|
+
|
|
1433
|
+
<div className="al-flex al-flex-col al-gap-2">
|
|
1434
|
+
<label className="al-text-sm al-font-medium">Multi-Select with Enhanced Async</label>
|
|
1435
|
+
<p className="al-text-xs al-text-muted-foreground">
|
|
1436
|
+
Multi-select mode with apply button and async loading
|
|
1437
|
+
</p>
|
|
1438
|
+
<Combobox
|
|
1439
|
+
options={displayedOptions}
|
|
1440
|
+
value={multiSelectedValue}
|
|
1441
|
+
onChange={value => setMultiSelectedValue(value as string[])}
|
|
1442
|
+
placeholder="Select multiple items..."
|
|
1443
|
+
searchPlaceholder="Search and select..."
|
|
1444
|
+
multiSelect={true}
|
|
1445
|
+
showApplyButton={true}
|
|
1446
|
+
onLoadMore={handleLoadMore}
|
|
1447
|
+
hasMore={hasMore}
|
|
1448
|
+
onPageReset={handlePageReset}
|
|
1449
|
+
buttonProps={{ className: "al-w-[300px]" }}
|
|
1450
|
+
/>
|
|
1451
|
+
<div className="al-text-sm">
|
|
1452
|
+
Selected: {multiSelectedValue.length} items{" "}
|
|
1453
|
+
{multiSelectedValue.length > 0 &&
|
|
1454
|
+
`(${multiSelectedValue.slice(0, 3).join(", ")}${multiSelectedValue.length > 3 ? "..." : ""})`}
|
|
1455
|
+
</div>
|
|
1456
|
+
</div>
|
|
1457
|
+
</div>
|
|
1458
|
+
</div>
|
|
1459
|
+
);
|
|
1460
|
+
};
|
|
1461
|
+
|
|
1462
|
+
export const ComboboxSheetIntegrationExample: StoryFn = () => {
|
|
1463
|
+
const [isSheetOpen, setIsSheetOpen] = useState(false);
|
|
1464
|
+
const [selectedValue, setSelectedValue] = useState("");
|
|
1465
|
+
const [multiSelectedValue, setSelectedMultiValue] = useState<string[]>([]);
|
|
1466
|
+
|
|
1467
|
+
const options = [
|
|
1468
|
+
{ value: "react", label: "React" },
|
|
1469
|
+
{ value: "vue", label: "Vue" },
|
|
1470
|
+
{ value: "angular", label: "Angular" },
|
|
1471
|
+
{ value: "svelte", label: "Svelte" },
|
|
1472
|
+
{ value: "nextjs", label: "Next.js" },
|
|
1473
|
+
{ value: "remix", label: "Remix" },
|
|
1474
|
+
{ value: "gatsby", label: "Gatsby" },
|
|
1475
|
+
{ value: "ember", label: "Ember.js" },
|
|
1476
|
+
{ value: "solid", label: "Solid.js" },
|
|
1477
|
+
{ value: "qwik", label: "Qwik" },
|
|
1478
|
+
{ value: "alpine", label: "Alpine.js" },
|
|
1479
|
+
{ value: "lit", label: "Lit" },
|
|
1480
|
+
];
|
|
1481
|
+
|
|
1482
|
+
return (
|
|
1483
|
+
<div className="al-flex al-flex-col al-gap-6 al-justify-start al-items-start">
|
|
1484
|
+
<div className="al-flex al-flex-col al-gap-2">
|
|
1485
|
+
<h3 className="al-text-lg al-font-medium">Sheet Integration</h3>
|
|
1486
|
+
<p className="al-text-sm al-text-muted-foreground">
|
|
1487
|
+
BUG FIX: Fixed scrolling issues when Combobox is used inside Sheet components. Added
|
|
1488
|
+
onWheel stopPropagation to prevent scroll conflicts.
|
|
1489
|
+
</p>
|
|
1490
|
+
</div>
|
|
1491
|
+
|
|
1492
|
+
<Button onClick={() => setIsSheetOpen(true)}>Open Sheet with Combobox</Button>
|
|
1493
|
+
|
|
1494
|
+
{/* Note: This is a simulated sheet for the story. In real usage, you'd use the actual Sheet component */}
|
|
1495
|
+
{isSheetOpen && (
|
|
1496
|
+
<div className="al-fixed al-inset-0 al-bg-black/50 al-z-50 al-flex al-items-center al-justify-center">
|
|
1497
|
+
<div className="al-bg-white al-rounded-lg al-p-6 al-w-[500px] al-max-h-[80vh] al-overflow-y-auto">
|
|
1498
|
+
<div className="al-flex al-justify-between al-items-center al-mb-4">
|
|
1499
|
+
<h4 className="al-text-lg al-font-medium">Sheet with Combobox</h4>
|
|
1500
|
+
<Button variant="ghost" size="sm" onClick={() => setIsSheetOpen(false)}>
|
|
1501
|
+
✕
|
|
1502
|
+
</Button>
|
|
1503
|
+
</div>
|
|
1504
|
+
|
|
1505
|
+
<div className="al-space-y-4">
|
|
1506
|
+
<div className="al-text-sm al-text-muted-foreground">
|
|
1507
|
+
Try scrolling in the combobox dropdown - it should work properly now!
|
|
1508
|
+
</div>
|
|
1509
|
+
|
|
1510
|
+
<div className="al-flex al-flex-col al-gap-2">
|
|
1511
|
+
<label className="al-text-sm al-font-medium">Single Select in Sheet</label>
|
|
1512
|
+
<Combobox
|
|
1513
|
+
options={options}
|
|
1514
|
+
value={selectedValue}
|
|
1515
|
+
onChange={value => setSelectedValue(value as string)}
|
|
1516
|
+
placeholder="Select framework..."
|
|
1517
|
+
searchPlaceholder="Search frameworks..."
|
|
1518
|
+
buttonProps={{ className: "al-w-full" }}
|
|
1519
|
+
/>
|
|
1520
|
+
<div className="al-text-xs">Selected: {selectedValue || "None"}</div>
|
|
1521
|
+
</div>
|
|
1522
|
+
|
|
1523
|
+
<div className="al-flex al-flex-col al-gap-2">
|
|
1524
|
+
<label className="al-text-sm al-font-medium">Multi-Select in Sheet</label>
|
|
1525
|
+
<Combobox
|
|
1526
|
+
options={options}
|
|
1527
|
+
value={multiSelectedValue}
|
|
1528
|
+
onChange={value => setSelectedMultiValue(value as string[])}
|
|
1529
|
+
placeholder="Select frameworks..."
|
|
1530
|
+
searchPlaceholder="Search frameworks..."
|
|
1531
|
+
multiSelect={true}
|
|
1532
|
+
showApplyButton={true}
|
|
1533
|
+
buttonProps={{ className: "al-w-full" }}
|
|
1534
|
+
/>
|
|
1535
|
+
<div className="al-text-xs">
|
|
1536
|
+
Selected: {multiSelectedValue.length > 0 ? multiSelectedValue.join(", ") : "None"}
|
|
1537
|
+
</div>
|
|
1538
|
+
</div>
|
|
1539
|
+
|
|
1540
|
+
<div className="al-text-xs al-text-muted-foreground al-bg-green-50 al-p-2 al-rounded">
|
|
1541
|
+
✅ Mouse wheel scrolling now works properly in both comboboxes!
|
|
1542
|
+
</div>
|
|
1543
|
+
</div>
|
|
1544
|
+
</div>
|
|
1545
|
+
</div>
|
|
1546
|
+
)}
|
|
1547
|
+
|
|
1548
|
+
<div className="al-text-sm">
|
|
1549
|
+
Selected in sheet: {selectedValue || "None"} | Multi:{" "}
|
|
1550
|
+
{multiSelectedValue.join(", ") || "None"}
|
|
1551
|
+
</div>
|
|
1552
|
+
</div>
|
|
1553
|
+
);
|
|
1554
|
+
};
|