@indico-data/design-system 3.9.0 → 3.11.0
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/lib/components/floatUI/FloatUI.d.ts +1 -1
- package/lib/components/floatUI/FloatUI.stories.d.ts +1 -0
- package/lib/components/floatUI/types.d.ts +2 -0
- package/lib/components/forms/select/Select.d.ts +3 -1
- package/lib/components/forms/select/types.d.ts +2 -1
- package/lib/index.d.ts +8 -4
- package/lib/index.esm.js +330 -11
- package/lib/index.esm.js.map +1 -1
- package/lib/index.js +330 -11
- package/lib/index.js.map +1 -1
- package/package.json +1 -1
- package/src/components/floatUI/FloatUI.mdx +8 -1
- package/src/components/floatUI/FloatUI.stories.tsx +44 -0
- package/src/components/floatUI/FloatUI.tsx +6 -2
- package/src/components/floatUI/__tests__/FloatUI.test.tsx +32 -0
- package/src/components/floatUI/types.ts +2 -0
- package/src/components/forms/select/Select.tsx +30 -22
- package/src/components/forms/select/types.ts +3 -1
package/package.json
CHANGED
|
@@ -17,9 +17,16 @@ The FloatUI component is used to display content relative to another element. It
|
|
|
17
17
|
|
|
18
18
|
FloatUI can be used in two modes:
|
|
19
19
|
|
|
20
|
-
- **Uncontrolled Mode:** FloatUI manages its own state, toggling visibility when the trigger is clicked.
|
|
20
|
+
- **Uncontrolled Mode:** FloatUI manages its own state, toggling visibility when the trigger is clicked (or hovered if `hover` prop is true).
|
|
21
21
|
- **Controlled Mode:** The parent component controls visibility by passing `isOpen` and `setIsOpen` props
|
|
22
22
|
|
|
23
|
+
## Interaction Types
|
|
24
|
+
|
|
25
|
+
FloatUI supports two interaction types:
|
|
26
|
+
|
|
27
|
+
- **Click (default):** Opens when the trigger is clicked
|
|
28
|
+
- **Hover:** Opens when the trigger is hovered. Set the `hover` prop to `true` to enable hover interactions.
|
|
29
|
+
|
|
23
30
|
### Example: Controlled `FloatUI`
|
|
24
31
|
|
|
25
32
|
```tsx
|
|
@@ -73,6 +73,18 @@ const meta: Meta<typeof FloatUI> = {
|
|
|
73
73
|
},
|
|
74
74
|
},
|
|
75
75
|
},
|
|
76
|
+
hover: {
|
|
77
|
+
control: 'boolean',
|
|
78
|
+
table: {
|
|
79
|
+
category: 'Props',
|
|
80
|
+
type: {
|
|
81
|
+
summary: 'boolean',
|
|
82
|
+
},
|
|
83
|
+
defaultValue: {
|
|
84
|
+
summary: 'false',
|
|
85
|
+
},
|
|
86
|
+
},
|
|
87
|
+
},
|
|
76
88
|
},
|
|
77
89
|
decorators: [
|
|
78
90
|
(Story: React.ComponentType) => (
|
|
@@ -170,3 +182,35 @@ export const Controlled: Story = {
|
|
|
170
182
|
);
|
|
171
183
|
},
|
|
172
184
|
};
|
|
185
|
+
|
|
186
|
+
export const Hover: Story = {
|
|
187
|
+
args: {
|
|
188
|
+
ariaLabel: 'Hover FloatUI',
|
|
189
|
+
hover: true,
|
|
190
|
+
},
|
|
191
|
+
render: (args) => (
|
|
192
|
+
<FloatUI {...args} ariaLabel="Hover FloatUI" hover>
|
|
193
|
+
<Button iconLeft="info" ariaLabel="Hover me" variant="action">
|
|
194
|
+
Hover me
|
|
195
|
+
</Button>
|
|
196
|
+
<Menu>
|
|
197
|
+
<Button
|
|
198
|
+
data-testid="hover-item-1"
|
|
199
|
+
ariaLabel="Item 1"
|
|
200
|
+
iconLeft="retrain"
|
|
201
|
+
onClick={() => console.log('Item 1')}
|
|
202
|
+
>
|
|
203
|
+
Item 1
|
|
204
|
+
</Button>
|
|
205
|
+
<Button
|
|
206
|
+
data-testid="hover-item-2"
|
|
207
|
+
ariaLabel="Item 2"
|
|
208
|
+
iconLeft="edit"
|
|
209
|
+
onClick={() => console.log('Item 2')}
|
|
210
|
+
>
|
|
211
|
+
Item 2
|
|
212
|
+
</Button>
|
|
213
|
+
</Menu>
|
|
214
|
+
</FloatUI>
|
|
215
|
+
),
|
|
216
|
+
};
|
|
@@ -2,6 +2,7 @@ import React, { useRef, isValidElement, useState } from 'react';
|
|
|
2
2
|
import {
|
|
3
3
|
FloatingPortal,
|
|
4
4
|
useClick,
|
|
5
|
+
useHover,
|
|
5
6
|
useFloating,
|
|
6
7
|
useInteractions,
|
|
7
8
|
UseFloatingOptions,
|
|
@@ -27,6 +28,7 @@ export function FloatUI({
|
|
|
27
28
|
portalOptions = {},
|
|
28
29
|
floatingOptions = defaultOptions,
|
|
29
30
|
className,
|
|
31
|
+
hover = false,
|
|
30
32
|
}: FloatUIProps) {
|
|
31
33
|
const [internalIsOpen, setInternalIsOpen] = useState(false);
|
|
32
34
|
|
|
@@ -60,10 +62,12 @@ export function FloatUI({
|
|
|
60
62
|
},
|
|
61
63
|
});
|
|
62
64
|
|
|
63
|
-
|
|
65
|
+
// Can't call hooks conditionally so this enabled option is needed.
|
|
66
|
+
const click = useClick(context, { enabled: !hover });
|
|
67
|
+
const hoverHook = useHover(context, { enabled: hover });
|
|
64
68
|
const dismiss = useDismiss(context);
|
|
65
69
|
|
|
66
|
-
const { getReferenceProps, getFloatingProps } = useInteractions([click, dismiss]);
|
|
70
|
+
const { getReferenceProps, getFloatingProps } = useInteractions([click, hoverHook, dismiss]);
|
|
67
71
|
|
|
68
72
|
const tooltipContent = (
|
|
69
73
|
<div
|
|
@@ -2,6 +2,7 @@ import { render, screen, fireEvent } from '@testing-library/react';
|
|
|
2
2
|
import { FloatUI } from '../FloatUI';
|
|
3
3
|
import { Menu } from '../../menu';
|
|
4
4
|
import { Button } from '../../button';
|
|
5
|
+
import userEvent from '@testing-library/user-event';
|
|
5
6
|
|
|
6
7
|
describe('FloatUI Component', () => {
|
|
7
8
|
it('does not display FloatUI content initially when rendered in uncontrolled mode', () => {
|
|
@@ -108,4 +109,35 @@ describe('FloatUI Component', () => {
|
|
|
108
109
|
fireEvent.click(screen.getByText('Toggle'));
|
|
109
110
|
expect(setIsOpen).toHaveBeenCalledWith(true, expect.any(Object), 'click');
|
|
110
111
|
});
|
|
112
|
+
|
|
113
|
+
it('displays the FloatUI content when the trigger is hovered in hover mode', async () => {
|
|
114
|
+
const user = userEvent.setup();
|
|
115
|
+
render(
|
|
116
|
+
<FloatUI ariaLabel="Example FloatUI" hover>
|
|
117
|
+
<Button ariaLabel="Hover me">Hover me</Button>
|
|
118
|
+
<div>FloatUI Content</div>
|
|
119
|
+
</FloatUI>,
|
|
120
|
+
);
|
|
121
|
+
|
|
122
|
+
expect(screen.queryByText('FloatUI Content')).not.toBeInTheDocument();
|
|
123
|
+
|
|
124
|
+
await user.hover(screen.getByText('Hover me'));
|
|
125
|
+
expect(screen.getByText('FloatUI Content')).toBeInTheDocument();
|
|
126
|
+
});
|
|
127
|
+
|
|
128
|
+
it('hides the FloatUI content when mouse leaves in hover mode', async () => {
|
|
129
|
+
const user = userEvent.setup();
|
|
130
|
+
render(
|
|
131
|
+
<FloatUI ariaLabel="Example FloatUI" hover>
|
|
132
|
+
<Button ariaLabel="Hover me">Hover me</Button>
|
|
133
|
+
<div>FloatUI Content</div>
|
|
134
|
+
</FloatUI>,
|
|
135
|
+
);
|
|
136
|
+
|
|
137
|
+
await user.hover(screen.getByText('Hover me'));
|
|
138
|
+
expect(screen.getByText('FloatUI Content')).toBeInTheDocument();
|
|
139
|
+
|
|
140
|
+
await user.unhover(screen.getByText('Hover me'));
|
|
141
|
+
expect(screen.queryByText('FloatUI Content')).not.toBeInTheDocument();
|
|
142
|
+
});
|
|
111
143
|
});
|
|
@@ -19,4 +19,6 @@ export type FloatUIProps = {
|
|
|
19
19
|
};
|
|
20
20
|
/** Function to toggle the visibility of the FloatUI (for controlled mode). */
|
|
21
21
|
setIsOpen?: React.Dispatch<React.SetStateAction<boolean>>;
|
|
22
|
+
/** If true, opens on hover instead of click. Defaults to false. */
|
|
23
|
+
hover?: boolean;
|
|
22
24
|
};
|
|
@@ -34,30 +34,38 @@ const OptionComponent = <OptionType extends SelectOption>({
|
|
|
34
34
|
);
|
|
35
35
|
};
|
|
36
36
|
|
|
37
|
-
const Select =
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
37
|
+
const Select = React.forwardRef(
|
|
38
|
+
<OptionType extends SelectOption>(
|
|
39
|
+
{
|
|
40
|
+
classNamePrefix = 'select',
|
|
41
|
+
className,
|
|
42
|
+
components: customComponents,
|
|
43
|
+
label,
|
|
44
|
+
hasHiddenLabel,
|
|
45
|
+
name,
|
|
46
|
+
...props
|
|
47
|
+
}: SelectProps<OptionType>,
|
|
48
|
+
ref: React.Ref<any>,
|
|
49
|
+
) => {
|
|
50
|
+
const defaultComponents = {
|
|
51
|
+
Option: OptionComponent as React.ComponentType<OptionProps<OptionType>>,
|
|
52
|
+
};
|
|
49
53
|
|
|
50
|
-
|
|
54
|
+
const mergedComponents = { ...defaultComponents, ...customComponents };
|
|
51
55
|
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
56
|
+
return (
|
|
57
|
+
<ReactSelect
|
|
58
|
+
ref={ref}
|
|
59
|
+
classNamePrefix={classNamePrefix}
|
|
60
|
+
className={classNames('select-wrapper', className)}
|
|
61
|
+
components={mergedComponents}
|
|
62
|
+
{...props}
|
|
63
|
+
/>
|
|
64
|
+
);
|
|
65
|
+
},
|
|
66
|
+
) as <OptionType extends SelectOption>(
|
|
67
|
+
props: SelectProps<OptionType> & { ref?: React.Ref<any> },
|
|
68
|
+
) => React.ReactElement;
|
|
61
69
|
|
|
62
70
|
const LabeledSelect = withLabel(Select);
|
|
63
71
|
|