@altimateai/ui-components 0.0.49 → 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 +2 -2
- package/dist/Stack.js +317 -316
- package/dist/TagsInput.js +2615 -2469
- package/dist/assets/icons/index.js +1 -1
- 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 -9
- package/dist/shadcn/index.js +3 -3
- package/dist/storybook/Combobox.stories.tsx +738 -86
- package/package.json +1 -1
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
import { Meta, StoryFn } from "@storybook/react";
|
|
2
2
|
import { Combobox, Button } from "../shadcn";
|
|
3
|
+
import type { ComboboxOption } from "./Combobox";
|
|
3
4
|
import { DatabaseIcon } from "@ac-assets/icons";
|
|
4
|
-
import { useState } from "react";
|
|
5
|
+
import { useCallback, useState } from "react";
|
|
5
6
|
|
|
6
7
|
export default {
|
|
7
8
|
title: "Shadcn/Components/Combobox",
|
|
@@ -164,7 +165,7 @@ const ComboboxColumnsExample = () => {
|
|
|
164
165
|
placeholder="Columns"
|
|
165
166
|
multiSelect={true}
|
|
166
167
|
showApplyButton={true}
|
|
167
|
-
buttonProps={{ className: "al-w-
|
|
168
|
+
buttonProps={{ className: "al-w-auto" }}
|
|
168
169
|
/>
|
|
169
170
|
);
|
|
170
171
|
};
|
|
@@ -436,81 +437,6 @@ export const ComboboxInfiniteScrollExample: StoryFn = () => {
|
|
|
436
437
|
);
|
|
437
438
|
};
|
|
438
439
|
|
|
439
|
-
export const ComboboxSearchExample: StoryFn = () => {
|
|
440
|
-
const [options] = useState([
|
|
441
|
-
{ value: "react", label: "React" },
|
|
442
|
-
{ value: "vue", label: "Vue" },
|
|
443
|
-
{ value: "angular", label: "Angular" },
|
|
444
|
-
{ value: "svelte", label: "Svelte" },
|
|
445
|
-
{ value: "nextjs", label: "Next.js" },
|
|
446
|
-
]);
|
|
447
|
-
|
|
448
|
-
const [selectedValue, setSelectedValue] = useState("");
|
|
449
|
-
const [searchValue, setSearchValue] = useState("");
|
|
450
|
-
const [searchHistory, setSearchHistory] = useState<string[]>([]);
|
|
451
|
-
|
|
452
|
-
const handleSearch = (searchTerm: string) => {
|
|
453
|
-
setSearchValue(searchTerm);
|
|
454
|
-
if (searchTerm.trim() && !searchHistory.includes(searchTerm.trim())) {
|
|
455
|
-
setSearchHistory(prev => [searchTerm.trim(), ...prev.slice(0, 4)]); // Keep last 5 searches
|
|
456
|
-
}
|
|
457
|
-
};
|
|
458
|
-
|
|
459
|
-
const clearHistory = () => {
|
|
460
|
-
setSearchHistory([]);
|
|
461
|
-
setSearchValue("");
|
|
462
|
-
};
|
|
463
|
-
|
|
464
|
-
return (
|
|
465
|
-
<div className="al-flex al-flex-col al-gap-6 al-justify-start al-items-start">
|
|
466
|
-
<div className="al-flex al-gap-4 al-items-center">
|
|
467
|
-
<h3 className="al-text-lg al-font-medium">Search Callback Example</h3>
|
|
468
|
-
<Button onClick={clearHistory} size="sm" variant="outline">
|
|
469
|
-
Clear History
|
|
470
|
-
</Button>
|
|
471
|
-
</div>
|
|
472
|
-
|
|
473
|
-
<div className="al-flex al-flex-col al-gap-4">
|
|
474
|
-
<div className="al-flex al-flex-col al-gap-2">
|
|
475
|
-
<label className="al-text-sm al-font-medium">Combobox with Search Callback</label>
|
|
476
|
-
<p className="al-text-xs al-text-muted-foreground">
|
|
477
|
-
Type in the search box to see the search values being emitted
|
|
478
|
-
</p>
|
|
479
|
-
<Combobox
|
|
480
|
-
options={options}
|
|
481
|
-
value={selectedValue}
|
|
482
|
-
onChange={value => setSelectedValue(value as string)}
|
|
483
|
-
placeholder="Search frameworks..."
|
|
484
|
-
searchPlaceholder="Type to search..."
|
|
485
|
-
onSearch={handleSearch}
|
|
486
|
-
buttonProps={{ className: "al-w-[250px]" }}
|
|
487
|
-
/>
|
|
488
|
-
</div>
|
|
489
|
-
|
|
490
|
-
<div className="al-flex al-flex-col al-gap-2">
|
|
491
|
-
<label className="al-text-sm al-font-medium">Current Search Value:</label>
|
|
492
|
-
<code className="al-text-sm al-bg-muted al-p-2 al-rounded">
|
|
493
|
-
{searchValue || "No search performed yet"}
|
|
494
|
-
</code>
|
|
495
|
-
</div>
|
|
496
|
-
|
|
497
|
-
{searchHistory.length > 0 && (
|
|
498
|
-
<div className="al-flex al-flex-col al-gap-2">
|
|
499
|
-
<label className="al-text-sm al-font-medium">Search History:</label>
|
|
500
|
-
<ul className="al-text-sm al-bg-muted al-p-2 al-rounded al-space-y-1">
|
|
501
|
-
{searchHistory.map((search, index) => (
|
|
502
|
-
<li key={index} className="al-font-mono">
|
|
503
|
-
{index + 1}. "{search}"
|
|
504
|
-
</li>
|
|
505
|
-
))}
|
|
506
|
-
</ul>
|
|
507
|
-
</div>
|
|
508
|
-
)}
|
|
509
|
-
</div>
|
|
510
|
-
</div>
|
|
511
|
-
);
|
|
512
|
-
};
|
|
513
|
-
|
|
514
440
|
export const ComboboxCallbacksExample: StoryFn = () => {
|
|
515
441
|
const options = [
|
|
516
442
|
{ value: "react", label: "React" },
|
|
@@ -592,7 +518,7 @@ export const ComboboxCustomNodesExample: StoryFn = () => {
|
|
|
592
518
|
{
|
|
593
519
|
value: "react",
|
|
594
520
|
label: "React",
|
|
595
|
-
|
|
521
|
+
labelNode: (
|
|
596
522
|
<div className="al-flex al-items-center al-justify-between al-w-full">
|
|
597
523
|
<div className="al-flex al-items-center al-gap-2">
|
|
598
524
|
<div className="al-w-4 al-h-4 al-bg-blue-500 al-rounded"></div>
|
|
@@ -605,7 +531,7 @@ export const ComboboxCustomNodesExample: StoryFn = () => {
|
|
|
605
531
|
{
|
|
606
532
|
value: "vue",
|
|
607
533
|
label: "Vue",
|
|
608
|
-
|
|
534
|
+
labelNode: (
|
|
609
535
|
<div className="al-flex al-items-center al-justify-between al-w-full">
|
|
610
536
|
<div className="al-flex al-items-center al-gap-2">
|
|
611
537
|
<div className="al-w-4 al-h-4 al-bg-green-500 al-rounded"></div>
|
|
@@ -618,7 +544,7 @@ export const ComboboxCustomNodesExample: StoryFn = () => {
|
|
|
618
544
|
{
|
|
619
545
|
value: "angular",
|
|
620
546
|
label: "Angular",
|
|
621
|
-
|
|
547
|
+
labelNode: (
|
|
622
548
|
<div className="al-flex al-items-center al-justify-between al-w-full">
|
|
623
549
|
<div className="al-flex al-items-center al-gap-2">
|
|
624
550
|
<div className="al-w-4 al-h-4 al-bg-red-500 al-rounded"></div>
|
|
@@ -631,7 +557,7 @@ export const ComboboxCustomNodesExample: StoryFn = () => {
|
|
|
631
557
|
{
|
|
632
558
|
value: "svelte",
|
|
633
559
|
label: "Svelte",
|
|
634
|
-
|
|
560
|
+
labelNode: (
|
|
635
561
|
<div className="al-flex al-items-center al-justify-between al-w-full">
|
|
636
562
|
<div className="al-flex al-items-center al-gap-2">
|
|
637
563
|
<div className="al-w-4 al-h-4 al-bg-orange-500 al-rounded"></div>
|
|
@@ -648,7 +574,7 @@ export const ComboboxCustomNodesExample: StoryFn = () => {
|
|
|
648
574
|
{
|
|
649
575
|
value: "typescript",
|
|
650
576
|
label: "TypeScript",
|
|
651
|
-
|
|
577
|
+
labelNode: (
|
|
652
578
|
<div className="al-flex al-items-center al-justify-between al-w-full">
|
|
653
579
|
<div className="al-flex al-items-center al-gap-2">
|
|
654
580
|
<div className="al-w-4 al-h-4 al-bg-blue-600 al-rounded"></div>
|
|
@@ -672,7 +598,7 @@ export const ComboboxCustomNodesExample: StoryFn = () => {
|
|
|
672
598
|
{
|
|
673
599
|
value: "javascript",
|
|
674
600
|
label: "JavaScript",
|
|
675
|
-
|
|
601
|
+
labelNode: (
|
|
676
602
|
<div className="al-flex al-items-center al-justify-between al-w-full">
|
|
677
603
|
<div className="al-flex al-items-center al-gap-2">
|
|
678
604
|
<div className="al-w-4 al-h-4 al-bg-yellow-500 al-rounded"></div>
|
|
@@ -696,7 +622,7 @@ export const ComboboxCustomNodesExample: StoryFn = () => {
|
|
|
696
622
|
{
|
|
697
623
|
value: "python",
|
|
698
624
|
label: "Python",
|
|
699
|
-
|
|
625
|
+
labelNode: (
|
|
700
626
|
<div className="al-flex al-items-center al-justify-between al-w-full">
|
|
701
627
|
<div className="al-flex al-items-center al-gap-2">
|
|
702
628
|
<div className="al-w-4 al-h-4 al-bg-green-600 al-rounded"></div>
|
|
@@ -727,7 +653,7 @@ export const ComboboxCustomNodesExample: StoryFn = () => {
|
|
|
727
653
|
{
|
|
728
654
|
value: "custom1",
|
|
729
655
|
label: "Custom Option 1",
|
|
730
|
-
|
|
656
|
+
labelNode: (
|
|
731
657
|
<div className="al-flex al-items-center al-gap-2">
|
|
732
658
|
<div className="al-w-2 al-h-2 al-bg-purple-500 al-rounded-full"></div>
|
|
733
659
|
<span className="al-italic">Custom with node</span>
|
|
@@ -738,7 +664,7 @@ export const ComboboxCustomNodesExample: StoryFn = () => {
|
|
|
738
664
|
{
|
|
739
665
|
value: "custom2",
|
|
740
666
|
label: "Custom Option 2",
|
|
741
|
-
|
|
667
|
+
labelNode: (
|
|
742
668
|
<div className="al-flex al-items-center al-gap-2">
|
|
743
669
|
<div className="al-w-2 al-h-2 al-bg-pink-500 al-rounded-full"></div>
|
|
744
670
|
<span className="al-font-bold">Another custom node</span>
|
|
@@ -900,3 +826,729 @@ export const ComboboxPopoverCustomizationExample: StoryFn = () => {
|
|
|
900
826
|
</div>
|
|
901
827
|
);
|
|
902
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
|
+
};
|