@agility/plenum-ui 2.0.0-rc9 → 2.0.1
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/README.md +104 -31
- package/build.js +30 -25
- package/dist/index.d.ts +103 -79
- package/dist/index.js +1 -6295
- package/dist/index.js.map +4 -4
- package/dist/{lib/tailwind.css → tailwind.css} +3754 -8120
- package/dist/types/stories/atoms/buttons/Button/Alternative/Alternative.stories.d.ts +1 -0
- package/dist/types/stories/atoms/buttons/Button/Button.d.ts +3 -7
- package/dist/types/stories/atoms/buttons/Button/Danger/Danger.stories.d.ts +1 -0
- package/dist/types/stories/atoms/buttons/Button/Primary/Primary.stories.d.ts +1 -0
- package/dist/types/stories/atoms/buttons/Button/Secondary/Secondary.stories.d.ts +1 -0
- package/dist/types/stories/atoms/buttons/Capsule/Capsule.d.ts +1 -1
- package/dist/types/stories/atoms/icons/DynamicIcon.d.ts +2 -2
- package/dist/types/stories/atoms/icons/TablerIcon.d.ts +1 -1
- package/dist/types/stories/index.d.ts +4 -4
- package/dist/types/stories/molecules/inputs/InputField/InputField.d.ts +7 -7
- package/dist/types/stories/molecules/inputs/TextInput/TextInput.d.ts +1 -1
- package/dist/types/stories/molecules/inputs/checkbox/Checkbox.d.ts +2 -0
- package/dist/types/stories/molecules/inputs/select/Select.d.ts +4 -2
- package/dist/types/stories/molecules/inputs/textArea/TextArea.d.ts +5 -1
- package/dist/types/stories/molecules/inputs/toggleSwitch/ToggleSwitch.d.ts +10 -7
- package/dist/types/stories/organisms/AnimatedLabelInput/AnimatedLabelInput.d.ts +5 -5
- package/dist/types/stories/organisms/AnimatedLabelInput/index.d.ts +1 -1
- package/dist/types/stories/organisms/AnimatedLabelTextArea/AnimatedLabelTextArea.d.ts +12 -0
- package/dist/types/stories/organisms/AnimatedLabelTextArea/index.d.ts +3 -0
- package/dist/types/stories/organisms/ButtonDropdown/ButtonDropdown.d.ts +1 -0
- package/dist/types/stories/organisms/DropdownComponent/DropdownComponent.d.ts +24 -13
- package/dist/types/stories/organisms/DropdownComponent/index.d.ts +2 -2
- package/dist/types/stories/organisms/EmptySectionPlaceholder/index.d.ts +1 -1
- package/dist/types/stories/organisms/index.d.ts +4 -3
- package/local.sh +100 -0
- package/package.json +35 -18
- package/rollup.config.mjs +42 -0
- package/stories/atoms/badges/Badge.tsx +1 -1
- package/stories/atoms/buttons/Button/Alternative/Alternative.stories.ts +10 -0
- package/stories/atoms/buttons/Button/Button.tsx +111 -25
- package/stories/atoms/buttons/Button/Danger/Danger.stories.ts +14 -2
- package/stories/atoms/buttons/Button/Primary/Primary.stories.ts +14 -2
- package/stories/atoms/buttons/Button/Secondary/Secondary.stories.ts +13 -1
- package/stories/atoms/buttons/Button/defaultArgs.ts +1 -1
- package/stories/atoms/buttons/Capsule/Capsule.tsx +2 -1
- package/stories/atoms/icons/DynamicIcon.stories.ts +1 -1
- package/stories/atoms/icons/DynamicIcon.tsx +7 -7
- package/stories/atoms/icons/IconWithShadow.stories.ts +3 -3
- package/stories/atoms/icons/TablerIcon.tsx +1 -1
- package/stories/atoms/loaders/Loader.tsx +12 -6
- package/stories/atoms/loaders/NProgress/RadialProgress.tsx +0 -2
- package/stories/index.ts +8 -4
- package/stories/molecules/inputs/InputCounter/InputCounter.tsx +2 -2
- package/stories/molecules/inputs/InputField/InputField.tsx +31 -29
- package/stories/molecules/inputs/InputLabel/InputLabel.tsx +6 -6
- package/stories/molecules/inputs/TextInput/TextInput.stories.tsx +31 -1
- package/stories/molecules/inputs/TextInput/TextInput.tsx +15 -7
- package/stories/molecules/inputs/checkbox/Checkbox.stories.ts +1 -1
- package/stories/molecules/inputs/checkbox/Checkbox.tsx +7 -4
- package/stories/molecules/inputs/combobox/ComboBox.tsx +126 -135
- package/stories/molecules/inputs/radio/Radio.stories.ts +2 -2
- package/stories/molecules/inputs/select/Select.stories.tsx +53 -0
- package/stories/molecules/inputs/select/Select.tsx +11 -3
- package/stories/molecules/inputs/textArea/{TextArea.stories.ts → TextArea.stories.tsx} +25 -2
- package/stories/molecules/inputs/textArea/TextArea.tsx +58 -28
- package/stories/molecules/inputs/toggleSwitch/ToggleSwitch.stories.tsx +15 -16
- package/stories/molecules/inputs/toggleSwitch/ToggleSwitch.tsx +63 -57
- package/stories/molecules/tabs/index.tsx +2 -3
- package/stories/organisms/AnimatedLabelInput/AnimatedLabelInput.stories.tsx +32 -2
- package/stories/organisms/AnimatedLabelInput/AnimatedLabelInput.tsx +66 -37
- package/stories/organisms/AnimatedLabelInput/index.tsx +1 -1
- package/stories/organisms/AnimatedLabelTextArea/AnimatedLabelTextArea.stories.tsx +26 -0
- package/stories/organisms/AnimatedLabelTextArea/AnimatedLabelTextArea.tsx +61 -0
- package/stories/organisms/AnimatedLabelTextArea/index.tsx +3 -0
- package/stories/organisms/ButtonDropdown/ButtonDropdown.stories.tsx +59 -59
- package/stories/organisms/ButtonDropdown/ButtonDropdown.tsx +42 -30
- package/stories/organisms/DropdownComponent/Dropdown.stories.tsx +26 -2
- package/stories/organisms/DropdownComponent/DropdownComponent.tsx +232 -180
- package/stories/organisms/DropdownComponent/dropdownItems.ts +30 -9
- package/stories/organisms/DropdownComponent/index.ts +2 -2
- package/stories/organisms/EmptySectionPlaceholder/EmptySectionPlaceholder.stories.tsx +3 -3
- package/stories/organisms/EmptySectionPlaceholder/index.tsx +2 -1
- package/stories/organisms/FormInputWithAddons/FormInputWithAddons.stories.tsx +1 -1
- package/stories/organisms/FormInputWithAddons/FormInputWithAddons.tsx +7 -2
- package/stories/organisms/index.ts +12 -3
- package/tailwind.config.js +139 -38
- package/tsconfig.lib.json +13 -6
- package/watch.js +49 -0
- package/.yarnrc.yml +0 -1
- package/dist/types/stories/layouts/index.d.ts +0 -0
- package/dist/types/stories/molecules/inputs/select/Select.stories.d.ts +0 -6
- package/dist/types/stories/molecules/inputs/textArea/TextArea.stories.d.ts +0 -6
- package/stories/layouts/CardLayout/CardLayout.stories.tsx +0 -18
- package/stories/layouts/CardLayout/CardLayout.tsx +0 -22
- package/stories/layouts/CardLayout/index.tsx +0 -3
- package/stories/layouts/ModalLayout/ModalLayout.stories.tsx +0 -18
- package/stories/layouts/ModalLayout/ModalLayout.tsx +0 -22
- package/stories/layouts/ModalLayout/index.tsx +0 -3
- package/stories/layouts/index.ts +0 -0
- package/stories/molecules/inputs/select/Select.stories.ts +0 -23
- package/stories/organisms/DropdownComponent/Dropdown.test.tsx +0 -0
|
@@ -8,6 +8,7 @@ import Dropdown, { IDropdownProps, defaultClassNames } from "../DropdownComponen
|
|
|
8
8
|
export interface IButtonDropdownProps {
|
|
9
9
|
button: IButtonProps
|
|
10
10
|
dropDown: IDropdownProps
|
|
11
|
+
hideDivider?: boolean
|
|
11
12
|
placement?: IDropdownProps["placement"]
|
|
12
13
|
offsetOptions?: IDropdownProps["offsetOptions"]
|
|
13
14
|
}
|
|
@@ -15,22 +16,7 @@ export interface IButtonDropdownProps {
|
|
|
15
16
|
/**
|
|
16
17
|
* Primary UI component for user interaction
|
|
17
18
|
*/
|
|
18
|
-
const ButtonDropdown: FC<IButtonDropdownProps> = ({ button, dropDown, placement, offsetOptions }) => {
|
|
19
|
-
const dropDownClasses: IDropdownProps["classNames"] = {
|
|
20
|
-
...defaultClassNames,
|
|
21
|
-
groupClassname: cn(
|
|
22
|
-
"flex items-center justify-center rounded-l-none border !border-l-0 rounded-r px-2 transition-all hover:!border-l-0",
|
|
23
|
-
button.actionType === "primary"
|
|
24
|
-
? "border-purple-600 bg-purple-600 !text-white hover:border-purple-700 hover:bg-purple-700 active:!border-purple-800 active:!bg-purple-800 fill-white"
|
|
25
|
-
: "",
|
|
26
|
-
button.actionType === "secondary"
|
|
27
|
-
? "border-purple-50 bg-purple-50 !text-purple-700 hover:border-purple-100 hover:bg-purple-100 active:!border-purple-300 active:!bg-purple-300 fill-purple-700"
|
|
28
|
-
: "",
|
|
29
|
-
button.actionType === "alternative"
|
|
30
|
-
? "border-gray-300 bg-white !text-gray-700 fill-gray-700 hover:border-gray-300 hover:bg-gray-50 active:bg-gray-100"
|
|
31
|
-
: ""
|
|
32
|
-
)
|
|
33
|
-
}
|
|
19
|
+
const ButtonDropdown: FC<IButtonDropdownProps> = ({ button, dropDown, hideDivider = false, placement = "bottom-end", offsetOptions }) => {
|
|
34
20
|
return (
|
|
35
21
|
<div className="flex items-stretch focus-within:ring-purple-600 focus-within:ring-2 focus-within:ring-offset-white focus-within:ring-offset-2 rounded-[3px]">
|
|
36
22
|
<Button
|
|
@@ -38,40 +24,66 @@ const ButtonDropdown: FC<IButtonDropdownProps> = ({ button, dropDown, placement,
|
|
|
38
24
|
...button,
|
|
39
25
|
className: cn(
|
|
40
26
|
button.className,
|
|
41
|
-
"!rounded-r-none !border-r-0 hover:!border-r-0 !focus:ring-transparent !focus-visible:ring-transparent !focus-within:ring-transparent !focus:ring-0 !focus-within:ring-0 !focus-visible:ring-0 !focus:ring-offset-0 !focus-visible:ring-offset-0 !focus-within:ring-offset-0 !ring-0 outline-none focus:outline-none focus-visible:outline-none focus-within:outline-none !ring-offset-0"
|
|
27
|
+
"!rounded-r-none !border-r-0 hover:!border-r-0 !focus:ring-transparent !focus-visible:ring-transparent !focus-within:ring-transparent !focus:ring-0 !focus-within:ring-0 !focus-visible:ring-0 !focus:ring-offset-0 !focus-visible:ring-offset-0 !focus-within:ring-offset-0 !ring-0 outline-none focus:outline-none focus-visible:outline-none focus-within:outline-none !ring-offset-0",
|
|
28
|
+
"border-r-transparent"
|
|
42
29
|
)
|
|
43
30
|
}}
|
|
44
31
|
/>
|
|
45
|
-
<div
|
|
32
|
+
{!hideDivider && <div
|
|
46
33
|
className={cn(
|
|
47
34
|
"w-[1px] rt",
|
|
48
|
-
button.actionType === "primary"
|
|
49
|
-
|
|
35
|
+
button.actionType === "primary"
|
|
36
|
+
? "bg-violet-700 text-violet-100 hover:border-violet-700 hover:bg-violet-700 disabled:bg-violet-400 disabled:focus-visible:ring-0"
|
|
37
|
+
: "",
|
|
38
|
+
button.actionType === "secondary" ? "bg-purple-200 " : "",
|
|
50
39
|
button.actionType === "alternative" ? "bg-gray-300" : ""
|
|
51
40
|
)}
|
|
52
|
-
></div>
|
|
41
|
+
></div>}
|
|
53
42
|
<Dropdown
|
|
54
43
|
{...{
|
|
55
|
-
...(dropDown as IDropdownProps),
|
|
56
44
|
CustomDropdownTrigger: (
|
|
57
45
|
<DynamicIcon
|
|
58
46
|
{...{
|
|
59
|
-
icon: "
|
|
47
|
+
icon: "IconChevronDown",
|
|
60
48
|
className: cn("h-5 w-5", {
|
|
61
|
-
"text-
|
|
62
|
-
"text-purple-700": button.actionType === "secondary",
|
|
49
|
+
"text-violet-100": button.actionType === "primary",
|
|
50
|
+
"text-purple-700 ": button.actionType === "secondary",
|
|
63
51
|
"text-gray-700": button.actionType === "alternative"
|
|
64
|
-
}
|
|
52
|
+
},
|
|
53
|
+
dropDown.iconClassname
|
|
54
|
+
),
|
|
65
55
|
}}
|
|
66
56
|
/>
|
|
67
57
|
),
|
|
68
|
-
|
|
58
|
+
buttonClassname: cn(
|
|
59
|
+
"flex items-center justify-center rounded-l-none border !border-l-0 rounded-r px-2 transition-all hover:!border-l-0",
|
|
60
|
+
button.actionType === "primary"
|
|
61
|
+
? cn(
|
|
62
|
+
"border-violet-700 bg-violet-800 !text-white hover:border-violet-700 hover:bg-violet-700 active:!border-violet-800 active:bg-violet-800 fill-white",
|
|
63
|
+
"disabled:bg-violet-400 disabled:text-white disabled:hover:none disabled:active:bg-violet-400 disabled:border-violet-400"
|
|
64
|
+
)
|
|
65
|
+
: "",
|
|
66
|
+
button.actionType === "secondary"
|
|
67
|
+
? cn(
|
|
68
|
+
"border-purple-400 bg-purple-50 text-purple-700 hover:bg-purple-100 active:bg-purple-300 fill-purple-700",
|
|
69
|
+
"disabled:bg-purple-50 disabled:text-grey-50 disabled:hover:none disabled:active:bg-purple-50 "
|
|
70
|
+
)
|
|
71
|
+
: "",
|
|
72
|
+
button.actionType === "alternative"
|
|
73
|
+
? cn(
|
|
74
|
+
"border-gray-300 bg-white text-gray-700 fill-gray-700 hover:border-gray-300 hover:bg-gray-50 active:bg-gray-100",
|
|
75
|
+
"disabled:bg-gray-100 disabled:text-gray-500 disabled:hover:none disabled:active:bg-gray-100 disabled:border-gray-300"
|
|
76
|
+
)
|
|
77
|
+
: "",
|
|
78
|
+
dropDown.buttonClassname
|
|
79
|
+
),
|
|
69
80
|
offsetOptions: offsetOptions ?? {
|
|
70
81
|
crossAxis: 0,
|
|
71
|
-
mainAxis:
|
|
72
|
-
alignmentAxis:
|
|
82
|
+
mainAxis: -4, //up/down
|
|
83
|
+
alignmentAxis: 0 //left/right
|
|
73
84
|
},
|
|
74
|
-
placement
|
|
85
|
+
placement,
|
|
86
|
+
...(dropDown as IDropdownProps),
|
|
75
87
|
}}
|
|
76
88
|
/>
|
|
77
89
|
<div className="hidden !bg-purple-100 !text-purple-600 transition-all hover:bg-purple-200 focus:bg-purple-300" />
|
|
@@ -13,12 +13,12 @@ const meta: Meta<typeof Dropdown> = {
|
|
|
13
13
|
type Story = StoryObj<typeof Dropdown>
|
|
14
14
|
|
|
15
15
|
const IconElement = () => (
|
|
16
|
-
<DynamicIcon className="h-5 w-5 text-gray-400 hover:text-gray-600" icon={"
|
|
16
|
+
<DynamicIcon className="h-5 w-5 text-gray-400 hover:text-gray-600" icon={"IconDotsVertical"} />
|
|
17
17
|
)
|
|
18
18
|
const defaultArgs: Story["args"] = {
|
|
19
19
|
items: [...dropdownDataBase],
|
|
20
20
|
label: "Dropdown",
|
|
21
|
-
|
|
21
|
+
|
|
22
22
|
placement: "bottom-end"
|
|
23
23
|
}
|
|
24
24
|
export const WithLabel: Story = {
|
|
@@ -39,6 +39,30 @@ export const WithLabelAndIcons: Story = {
|
|
|
39
39
|
items: [...dropdownDataWithIcons]
|
|
40
40
|
}
|
|
41
41
|
}
|
|
42
|
+
export const WithCustomItemLabelsAndIcons: Story = {
|
|
43
|
+
args: {
|
|
44
|
+
...defaultArgs,
|
|
45
|
+
items: [
|
|
46
|
+
...dropdownDataWithIcons,
|
|
47
|
+
[
|
|
48
|
+
{
|
|
49
|
+
icon: { icon: "Icon123" },
|
|
50
|
+
label: (
|
|
51
|
+
<div className="flex items-baseline gap-x-2">
|
|
52
|
+
sdfsdf{" "}
|
|
53
|
+
<div className="flex space-x-1">
|
|
54
|
+
<div className="h-1 w-1 animate-[bounce_1s_infinite] rounded-full bg-gray-500"></div>
|
|
55
|
+
<div className="h-1 w-1 animate-[bounce_1s_infinite_0.2s] rounded-full bg-gray-500"></div>
|
|
56
|
+
<div className="h-1 w-1 animate-[bounce_1s_infinite_0.4s] rounded-full bg-gray-500"></div>
|
|
57
|
+
</div>
|
|
58
|
+
</div>
|
|
59
|
+
),
|
|
60
|
+
key: "sdfsdf"
|
|
61
|
+
}
|
|
62
|
+
]
|
|
63
|
+
]
|
|
64
|
+
}
|
|
65
|
+
}
|
|
42
66
|
export const WithIcons: Story = {
|
|
43
67
|
args: {
|
|
44
68
|
...defaultArgs,
|
|
@@ -1,5 +1,4 @@
|
|
|
1
|
-
import React, {
|
|
2
|
-
import { Transition } from "@headlessui/react"
|
|
1
|
+
import React, { HTMLAttributes, useEffect, useMemo, useRef, useState } from "react"
|
|
3
2
|
import { default as cn } from "classnames"
|
|
4
3
|
import {
|
|
5
4
|
useFloating,
|
|
@@ -13,50 +12,60 @@ import {
|
|
|
13
12
|
autoPlacement,
|
|
14
13
|
shift,
|
|
15
14
|
FloatingPortal,
|
|
15
|
+
FloatingList,
|
|
16
16
|
useTransitionStyles,
|
|
17
|
-
Placement
|
|
17
|
+
Placement,
|
|
18
|
+
useListNavigation
|
|
18
19
|
} from "@floating-ui/react"
|
|
19
20
|
|
|
20
21
|
import { ClassNameWithAutocomplete } from "utils/types"
|
|
21
22
|
import { DynamicIcon, IDynamicIconProps, UnifiedIconName } from "@/stories/atoms/icons"
|
|
23
|
+
import { list } from "postcss"
|
|
22
24
|
|
|
23
|
-
export interface IItemProp
|
|
24
|
-
|
|
25
|
+
export interface IItemProp {
|
|
26
|
+
//Don't think this needs to extend HtmlButton... extends HTMLAttributes<HTMLButtonElement> {
|
|
27
|
+
icon?: IDynamicIconProps
|
|
25
28
|
iconPosition?: "trailing" | "leading"
|
|
26
|
-
label: string
|
|
29
|
+
label: string | JSX.Element
|
|
27
30
|
onClick?(): void
|
|
28
31
|
isEmphasized?: boolean
|
|
29
32
|
key: React.Key
|
|
33
|
+
iconObj?: JSX.Element
|
|
30
34
|
}
|
|
31
|
-
|
|
32
|
-
groupClassname?: ClassNameWithAutocomplete
|
|
33
|
-
itemsClassname?: ClassNameWithAutocomplete
|
|
34
|
-
itemClassname?: ClassNameWithAutocomplete
|
|
35
|
-
activeItemClassname?: ClassNameWithAutocomplete
|
|
36
|
-
buttonClassname?: ClassNameWithAutocomplete
|
|
37
|
-
}
|
|
35
|
+
|
|
38
36
|
export interface IDropdownProps extends HTMLAttributes<HTMLDivElement> {
|
|
39
37
|
items: IItemProp[][]
|
|
40
38
|
label: string
|
|
41
39
|
CustomDropdownTrigger?: React.ReactNode
|
|
42
40
|
id: string
|
|
43
|
-
|
|
41
|
+
groupClassname?: ClassNameWithAutocomplete
|
|
42
|
+
itemsClassname?: ClassNameWithAutocomplete
|
|
43
|
+
itemClassname?: ClassNameWithAutocomplete
|
|
44
|
+
activeItemClassname?: ClassNameWithAutocomplete
|
|
45
|
+
buttonClassname?: ClassNameWithAutocomplete
|
|
46
|
+
iconClassname?: ClassNameWithAutocomplete
|
|
47
|
+
iconSpacingClassname?: ClassNameWithAutocomplete
|
|
44
48
|
placement?: Placement
|
|
45
49
|
offsetOptions?: Partial<{
|
|
46
50
|
mainAxis: number
|
|
47
51
|
crossAxis: number
|
|
48
52
|
alignmentAxis: number | null
|
|
49
53
|
}>
|
|
54
|
+
disabled?: boolean
|
|
55
|
+
onFocus?: () => void
|
|
56
|
+
onBlur?: () => void
|
|
50
57
|
}
|
|
51
|
-
export const defaultClassNames
|
|
58
|
+
export const defaultClassNames = {
|
|
52
59
|
groupClassname: "flex inline-block text-left",
|
|
53
60
|
itemsClassname:
|
|
54
|
-
"mt-2 origin-bottom-right rounded bg-white shadow-lg z-
|
|
61
|
+
"mt-2 origin-bottom-right rounded bg-white shadow-lg z-[99999] divide-y divide-gray-100 border border-gray-300 ",
|
|
55
62
|
itemClassname:
|
|
56
63
|
"group flex font-muli cursor-pointer items-center px-4 py-2 text-sm transition-all hover:bg-gray-100 hover:text-gray-900 justify-between gap-4 ",
|
|
57
64
|
activeItemClassname: "block px-4 py-2 text-sm text-gray-700 bg-gray-100 hover:bg-gray-200 hover:text-gray-900",
|
|
58
65
|
buttonClassname:
|
|
59
|
-
"py-[2px]
|
|
66
|
+
"py-[2px] flex items-center rounded outline-purple-500 transition-all text-gray-400 hover:text-gray-600 ",
|
|
67
|
+
iconClassname: "ml-1 h-5 w-6",
|
|
68
|
+
iconSpacingClassname: "flex items-center gap-x-4"
|
|
60
69
|
}
|
|
61
70
|
|
|
62
71
|
/** Comment */
|
|
@@ -64,22 +73,44 @@ const Dropdown: React.FC<IDropdownProps> = ({
|
|
|
64
73
|
items,
|
|
65
74
|
id,
|
|
66
75
|
label,
|
|
67
|
-
|
|
76
|
+
groupClassname,
|
|
77
|
+
itemsClassname,
|
|
78
|
+
itemClassname,
|
|
79
|
+
activeItemClassname,
|
|
80
|
+
buttonClassname,
|
|
81
|
+
iconClassname,
|
|
82
|
+
iconSpacingClassname,
|
|
68
83
|
CustomDropdownTrigger,
|
|
69
84
|
placement = "bottom-start",
|
|
70
85
|
offsetOptions,
|
|
86
|
+
disabled,
|
|
87
|
+
onFocus,
|
|
88
|
+
onBlur,
|
|
71
89
|
...props
|
|
72
90
|
}: IDropdownProps): JSX.Element | null => {
|
|
73
91
|
const [isOpen, setIsOpen] = useState(false)
|
|
74
92
|
const [activeItem, setActiveItem] = useState<React.Key | null>(null)
|
|
93
|
+
const [activeIndex, setActiveIndex] = useState<number | null>(null)
|
|
94
|
+
|
|
95
|
+
const listRef = useRef<(HTMLButtonElement | null)[]>([])
|
|
75
96
|
|
|
76
97
|
// Floating UI logic
|
|
77
98
|
const { refs, floatingStyles, context } = useFloating({
|
|
78
99
|
open: isOpen,
|
|
79
|
-
onOpenChange:
|
|
100
|
+
onOpenChange: (bool: boolean) => {
|
|
101
|
+
listRef.current = []
|
|
102
|
+
setActiveIndex(null)
|
|
103
|
+
setIsOpen(bool)
|
|
104
|
+
},
|
|
80
105
|
placement,
|
|
81
106
|
middleware: [
|
|
82
|
-
offset(
|
|
107
|
+
offset(
|
|
108
|
+
offsetOptions ?? {
|
|
109
|
+
crossAxis: 0,
|
|
110
|
+
mainAxis: -4, //up/down
|
|
111
|
+
alignmentAxis: 0 //left/right
|
|
112
|
+
}
|
|
113
|
+
),
|
|
83
114
|
autoPlacement({
|
|
84
115
|
allowedPlacements: [placement, "bottom-start", "bottom-end", "bottom"]
|
|
85
116
|
}),
|
|
@@ -90,26 +121,169 @@ const Dropdown: React.FC<IDropdownProps> = ({
|
|
|
90
121
|
const click = useClick(context)
|
|
91
122
|
const dismiss = useDismiss(context)
|
|
92
123
|
const role = useRole(context)
|
|
93
|
-
const
|
|
124
|
+
const listNavigation = useListNavigation(context, {
|
|
125
|
+
listRef,
|
|
126
|
+
activeIndex,
|
|
127
|
+
onNavigate: (index: number | null) => {
|
|
128
|
+
if (index !== null && listRef.current[index]) {
|
|
129
|
+
setActiveIndex(index)
|
|
130
|
+
listRef.current[index]?.focus()
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
})
|
|
134
|
+
|
|
135
|
+
const { getReferenceProps, getFloatingProps, getItemProps } = useInteractions([
|
|
136
|
+
click,
|
|
137
|
+
dismiss,
|
|
138
|
+
role,
|
|
139
|
+
listNavigation
|
|
140
|
+
])
|
|
141
|
+
|
|
142
|
+
useEffect(() => {
|
|
143
|
+
if (isOpen) {
|
|
144
|
+
onFocus && onFocus()
|
|
145
|
+
} else {
|
|
146
|
+
onBlur && onBlur()
|
|
147
|
+
}
|
|
148
|
+
}, [isOpen, onBlur, onFocus])
|
|
149
|
+
|
|
150
|
+
const ItemComponents = useMemo(
|
|
151
|
+
() =>
|
|
152
|
+
items.map((itemStack, stackIndex) => {
|
|
153
|
+
return itemStack.map((item, itemIndex) => {
|
|
154
|
+
const { key, label, icon, iconObj, iconPosition, isEmphasized, onClick, ...rest } = item
|
|
155
|
+
const active = activeItem && activeItem === key
|
|
156
|
+
const itemClass = cn(
|
|
157
|
+
defaultClassNames.itemClassname,
|
|
158
|
+
itemClassname,
|
|
159
|
+
"group flex cursor-pointer items-center px-4 py-2 text-sm transition-all",
|
|
160
|
+
{
|
|
161
|
+
"text-red-500": isEmphasized
|
|
162
|
+
},
|
|
163
|
+
{
|
|
164
|
+
"text-gray-900": !isEmphasized
|
|
165
|
+
},
|
|
166
|
+
{
|
|
167
|
+
"bg-gray-100 text-gray-900": active
|
|
168
|
+
},
|
|
169
|
+
active ? cn(defaultClassNames.activeItemClassname, activeItemClassname) : "",
|
|
170
|
+
{
|
|
171
|
+
"bg-gray-100 text-red-500 hover:text-red-500": active && isEmphasized
|
|
172
|
+
}
|
|
173
|
+
)
|
|
174
|
+
return (
|
|
175
|
+
<button
|
|
176
|
+
{...{
|
|
177
|
+
key: key,
|
|
178
|
+
id: key.toString(),
|
|
179
|
+
className: cn(itemClass, "w-full"),
|
|
180
|
+
...rest,
|
|
181
|
+
...getItemProps(),
|
|
182
|
+
onClick: () => {
|
|
183
|
+
onClick && onClick()
|
|
184
|
+
setTimeout(() => {
|
|
185
|
+
//hide the dropdown after click
|
|
186
|
+
setIsOpen(false)
|
|
187
|
+
}, 150)
|
|
188
|
+
}
|
|
189
|
+
}}
|
|
190
|
+
ref={(node) => {
|
|
191
|
+
//If the list ref already contains a node with the same id do nothing, otherwise add it
|
|
192
|
+
if (listRef.current.some((item) => item?.id === key)) {
|
|
193
|
+
return
|
|
194
|
+
}
|
|
195
|
+
listRef.current.push(node)
|
|
196
|
+
}}
|
|
197
|
+
key={key}
|
|
198
|
+
>
|
|
199
|
+
<div className={cn(defaultClassNames.iconSpacingClassname, iconSpacingClassname)}>
|
|
200
|
+
{iconObj && (iconPosition === "leading" || iconPosition === undefined) && (
|
|
201
|
+
<>{iconObj}</>
|
|
202
|
+
)}
|
|
203
|
+
{icon &&
|
|
204
|
+
(iconPosition === "leading" || iconPosition === undefined) &&
|
|
205
|
+
(typeof icon === "string" ? (
|
|
206
|
+
<DynamicIcon
|
|
207
|
+
{...{
|
|
208
|
+
icon: icon,
|
|
209
|
+
className: cn(
|
|
210
|
+
{
|
|
211
|
+
"text-red-500": isEmphasized
|
|
212
|
+
},
|
|
213
|
+
"opacity-60 group"
|
|
214
|
+
)
|
|
215
|
+
}}
|
|
216
|
+
/>
|
|
217
|
+
) : (
|
|
218
|
+
<DynamicIcon
|
|
219
|
+
{...{
|
|
220
|
+
...icon,
|
|
221
|
+
className: cn(
|
|
222
|
+
icon.className,
|
|
223
|
+
{
|
|
224
|
+
"text-red-500": isEmphasized
|
|
225
|
+
},
|
|
226
|
+
"opacity-60 group"
|
|
227
|
+
)
|
|
228
|
+
}}
|
|
229
|
+
/>
|
|
230
|
+
))}
|
|
231
|
+
<div className="break-all line-clamp-1">{label}</div>
|
|
232
|
+
{iconObj && iconPosition === "trailing" && <>{iconObj}</>}
|
|
233
|
+
{icon &&
|
|
234
|
+
iconPosition === "trailing" &&
|
|
235
|
+
(typeof icon === "string" ? (
|
|
236
|
+
<DynamicIcon
|
|
237
|
+
{...{
|
|
238
|
+
icon: icon,
|
|
239
|
+
className: cn(
|
|
240
|
+
{
|
|
241
|
+
"text-red-500": isEmphasized
|
|
242
|
+
},
|
|
243
|
+
"opacity-60 group"
|
|
244
|
+
)
|
|
245
|
+
}}
|
|
246
|
+
/>
|
|
247
|
+
) : (
|
|
248
|
+
<DynamicIcon
|
|
249
|
+
{...{
|
|
250
|
+
...icon,
|
|
251
|
+
className: cn(
|
|
252
|
+
icon.className,
|
|
253
|
+
{
|
|
254
|
+
"text-red-500": isEmphasized
|
|
255
|
+
},
|
|
256
|
+
"opacity-60 group"
|
|
257
|
+
)
|
|
258
|
+
}}
|
|
259
|
+
/>
|
|
260
|
+
))}
|
|
261
|
+
</div>
|
|
262
|
+
</button>
|
|
263
|
+
)
|
|
264
|
+
})
|
|
265
|
+
}),
|
|
266
|
+
[activeItem, activeItemClassname, getItemProps, iconSpacingClassname, itemClassname, items]
|
|
267
|
+
)
|
|
268
|
+
|
|
94
269
|
const { isMounted, styles: transitionStyles } = useTransitionStyles(context, {
|
|
95
270
|
duration: {
|
|
96
271
|
open: 200,
|
|
97
272
|
close: 200
|
|
98
273
|
},
|
|
99
274
|
initial: {
|
|
100
|
-
opacity: 0
|
|
275
|
+
opacity: 0,
|
|
276
|
+
scale: 95
|
|
101
277
|
},
|
|
102
278
|
open: {
|
|
103
|
-
opacity: 1
|
|
279
|
+
opacity: 1,
|
|
280
|
+
scale: 100
|
|
104
281
|
}
|
|
105
282
|
})
|
|
106
|
-
|
|
107
|
-
const { groupClassname, buttonClassname, itemsClassname, itemClassname, activeItemClassname } = classNames
|
|
108
|
-
|
|
109
283
|
return (
|
|
110
284
|
<div
|
|
111
285
|
{...{
|
|
112
|
-
className: groupClassname,
|
|
286
|
+
className: cn(defaultClassNames.groupClassname, groupClassname),
|
|
113
287
|
role: "combobox",
|
|
114
288
|
"aria-owns": `${id}-list`,
|
|
115
289
|
"aria-expanded": isOpen,
|
|
@@ -120,10 +294,12 @@ const Dropdown: React.FC<IDropdownProps> = ({
|
|
|
120
294
|
<button
|
|
121
295
|
{...{
|
|
122
296
|
ref: refs.setReference,
|
|
123
|
-
className: buttonClassname,
|
|
297
|
+
className: cn(defaultClassNames.buttonClassname, buttonClassname),
|
|
124
298
|
onClick: () => {
|
|
125
299
|
setIsOpen(!isOpen)
|
|
126
300
|
},
|
|
301
|
+
type: "button",
|
|
302
|
+
disabled: disabled,
|
|
127
303
|
...getReferenceProps()
|
|
128
304
|
}}
|
|
129
305
|
>
|
|
@@ -132,166 +308,42 @@ const Dropdown: React.FC<IDropdownProps> = ({
|
|
|
132
308
|
) : (
|
|
133
309
|
<>
|
|
134
310
|
<span className="pl-1">{label}</span>
|
|
135
|
-
<DynamicIcon
|
|
311
|
+
<DynamicIcon
|
|
312
|
+
icon="IconChevronDown"
|
|
313
|
+
className={cn(defaultClassNames.iconClassname, iconClassname)}
|
|
314
|
+
/>
|
|
136
315
|
</>
|
|
137
316
|
)}
|
|
138
317
|
</button>
|
|
139
318
|
|
|
140
319
|
{isMounted && items.length > 0 && isOpen && (
|
|
141
|
-
<
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
leaveTo="transform opacity-0 scale-95"
|
|
152
|
-
>
|
|
153
|
-
<ul
|
|
154
|
-
{...{
|
|
155
|
-
...getFloatingProps(),
|
|
156
|
-
className: itemsClassname,
|
|
157
|
-
ref: refs.setFloating,
|
|
158
|
-
// style: floatingStyles,
|
|
159
|
-
"aria-labelledby": label,
|
|
160
|
-
id: `${id}-list`,
|
|
161
|
-
role: "listbox",
|
|
162
|
-
style: {
|
|
163
|
-
position: context.strategy,
|
|
164
|
-
top: Math.round(context.y ?? 0),
|
|
165
|
-
left: Math.round(context.x ?? 0),
|
|
166
|
-
width: "max-content",
|
|
167
|
-
maxWidth: "min(calc(100vw - 10px), 25rem)",
|
|
168
|
-
...floatingStyles
|
|
169
|
-
}
|
|
170
|
-
}}
|
|
171
|
-
className={itemsClassname}
|
|
320
|
+
<FloatingList
|
|
321
|
+
{...{
|
|
322
|
+
elementsRef: listRef
|
|
323
|
+
}}
|
|
324
|
+
>
|
|
325
|
+
<FloatingPortal>
|
|
326
|
+
<FloatingFocusManager context={context} modal={true}>
|
|
327
|
+
<div
|
|
328
|
+
{...getFloatingProps()}
|
|
329
|
+
className={cn(defaultClassNames.itemsClassname, itemsClassname)}
|
|
172
330
|
ref={refs.setFloating}
|
|
173
331
|
aria-labelledby={label}
|
|
174
|
-
{
|
|
332
|
+
style={{
|
|
333
|
+
position: context.strategy,
|
|
334
|
+
top: Math.round(context.y ?? 0),
|
|
335
|
+
left: Math.round(context.x ?? 0),
|
|
336
|
+
width: "max-content",
|
|
337
|
+
maxWidth: "min(calc(100vw - 10px), 25rem)",
|
|
338
|
+
...floatingStyles,
|
|
339
|
+
...transitionStyles
|
|
340
|
+
}}
|
|
175
341
|
>
|
|
176
|
-
{
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
onClick,
|
|
182
|
-
label,
|
|
183
|
-
key,
|
|
184
|
-
isEmphasized,
|
|
185
|
-
icon,
|
|
186
|
-
iconPosition,
|
|
187
|
-
...rest
|
|
188
|
-
}) => {
|
|
189
|
-
const active = activeItem && activeItem === key
|
|
190
|
-
const itemClass = cn(
|
|
191
|
-
itemClassname,
|
|
192
|
-
"group flex cursor-pointer items-center px-4 py-2 text-sm transition-all",
|
|
193
|
-
{
|
|
194
|
-
"text-red-500": isEmphasized
|
|
195
|
-
},
|
|
196
|
-
{
|
|
197
|
-
"text-gray-900": !isEmphasized
|
|
198
|
-
},
|
|
199
|
-
{
|
|
200
|
-
"bg-gray-100 text-gray-900": active
|
|
201
|
-
},
|
|
202
|
-
active ? activeItemClassname : "",
|
|
203
|
-
{
|
|
204
|
-
"bg-gray-100 text-red-500 hover:text-red-500":
|
|
205
|
-
active && isEmphasized
|
|
206
|
-
},
|
|
207
|
-
itemClassname
|
|
208
|
-
)
|
|
209
|
-
|
|
210
|
-
return (
|
|
211
|
-
<li key={key}>
|
|
212
|
-
<button
|
|
213
|
-
{...{
|
|
214
|
-
onClick: () => {
|
|
215
|
-
setActiveItem(key)
|
|
216
|
-
onClick && onClick()
|
|
217
|
-
},
|
|
218
|
-
key,
|
|
219
|
-
className: cn(itemClass, "w-full"),
|
|
220
|
-
...rest
|
|
221
|
-
}}
|
|
222
|
-
>
|
|
223
|
-
<div className="flex items-center gap-x-4">
|
|
224
|
-
{icon &&
|
|
225
|
-
(iconPosition === "leading" ||
|
|
226
|
-
iconPosition === undefined) &&
|
|
227
|
-
(typeof icon === "string" ? (
|
|
228
|
-
<DynamicIcon
|
|
229
|
-
{...{
|
|
230
|
-
icon: icon,
|
|
231
|
-
className: cn(
|
|
232
|
-
{
|
|
233
|
-
"text-red-500": isEmphasized
|
|
234
|
-
},
|
|
235
|
-
"opacity-60 group"
|
|
236
|
-
)
|
|
237
|
-
}}
|
|
238
|
-
/>
|
|
239
|
-
) : (
|
|
240
|
-
<DynamicIcon
|
|
241
|
-
{...{
|
|
242
|
-
...icon,
|
|
243
|
-
className: cn(
|
|
244
|
-
icon.className,
|
|
245
|
-
{
|
|
246
|
-
"text-red-500": isEmphasized
|
|
247
|
-
},
|
|
248
|
-
"opacity-60 group"
|
|
249
|
-
)
|
|
250
|
-
}}
|
|
251
|
-
/>
|
|
252
|
-
))}
|
|
253
|
-
<div className="whitespace-nowrap">{label}</div>
|
|
254
|
-
{icon &&
|
|
255
|
-
iconPosition === "trailing" &&
|
|
256
|
-
(typeof icon === "string" ? (
|
|
257
|
-
<DynamicIcon
|
|
258
|
-
{...{
|
|
259
|
-
icon: icon,
|
|
260
|
-
className: cn(
|
|
261
|
-
{
|
|
262
|
-
"text-red-500": isEmphasized
|
|
263
|
-
},
|
|
264
|
-
"opacity-60 group"
|
|
265
|
-
)
|
|
266
|
-
}}
|
|
267
|
-
/>
|
|
268
|
-
) : (
|
|
269
|
-
<DynamicIcon
|
|
270
|
-
{...{
|
|
271
|
-
...icon,
|
|
272
|
-
className: cn(
|
|
273
|
-
icon.className,
|
|
274
|
-
{
|
|
275
|
-
"text-red-500": isEmphasized
|
|
276
|
-
},
|
|
277
|
-
"opacity-60 group"
|
|
278
|
-
)
|
|
279
|
-
}}
|
|
280
|
-
/>
|
|
281
|
-
))}
|
|
282
|
-
</div>
|
|
283
|
-
</button>
|
|
284
|
-
</li>
|
|
285
|
-
)
|
|
286
|
-
}
|
|
287
|
-
)}
|
|
288
|
-
</React.Fragment>
|
|
289
|
-
)
|
|
290
|
-
})}
|
|
291
|
-
</ul>
|
|
292
|
-
</Transition>
|
|
293
|
-
</FloatingFocusManager>
|
|
294
|
-
</FloatingPortal>
|
|
342
|
+
{ItemComponents}
|
|
343
|
+
</div>
|
|
344
|
+
</FloatingFocusManager>
|
|
345
|
+
</FloatingPortal>
|
|
346
|
+
</FloatingList>
|
|
295
347
|
)}
|
|
296
348
|
</div>
|
|
297
349
|
)
|