@lumx/react 3.0.6-alpha.1 → 3.0.6
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/_internal/types.d.ts +18 -1
- package/index.d.ts +33 -6
- package/index.js +550 -482
- package/index.js.map +1 -1
- package/package.json +4 -4
- package/src/components/autocomplete/Autocomplete.tsx +4 -1
- package/src/components/button/Button.stories.tsx +27 -1
- package/src/components/button/Button.tsx +3 -3
- package/src/components/dropdown/Dropdown.tsx +4 -1
- package/src/components/popover/Popover.tsx +18 -3
- package/src/components/popover-dialog/PopoverDialog.stories.tsx +75 -0
- package/src/components/popover-dialog/PopoverDialog.test.tsx +65 -0
- package/src/components/popover-dialog/PopoverDialog.tsx +65 -0
- package/src/components/popover-dialog/index.tsx +1 -0
- package/src/index.ts +1 -0
- package/src/stories/generated/PopoverDialog/Demos.stories.tsx +6 -0
- package/src/utils/type.ts +20 -0
package/package.json
CHANGED
|
@@ -7,8 +7,8 @@
|
|
|
7
7
|
},
|
|
8
8
|
"dependencies": {
|
|
9
9
|
"@juggle/resize-observer": "^3.2.0",
|
|
10
|
-
"@lumx/core": "^3.0.6
|
|
11
|
-
"@lumx/icons": "^3.0.6
|
|
10
|
+
"@lumx/core": "^3.0.6",
|
|
11
|
+
"@lumx/icons": "^3.0.6",
|
|
12
12
|
"@popperjs/core": "^2.5.4",
|
|
13
13
|
"body-scroll-lock": "^3.1.5",
|
|
14
14
|
"classnames": "^2.2.6",
|
|
@@ -114,6 +114,6 @@
|
|
|
114
114
|
"build:storybook": "cd storybook && ./build"
|
|
115
115
|
},
|
|
116
116
|
"sideEffects": false,
|
|
117
|
-
"version": "3.0.6
|
|
118
|
-
"gitHead": "
|
|
117
|
+
"version": "3.0.6",
|
|
118
|
+
"gitHead": "1fed89f798ebc21e67fff1b6aee01e4aa86431a6"
|
|
119
119
|
}
|
|
@@ -41,7 +41,10 @@ export interface AutocompleteProps extends GenericProps, HasTheme {
|
|
|
41
41
|
*/
|
|
42
42
|
placement?: Placement;
|
|
43
43
|
/**
|
|
44
|
-
*
|
|
44
|
+
* Manage dropdown width:
|
|
45
|
+
* - `maxWidth`: dropdown not bigger than anchor
|
|
46
|
+
* - `minWidth` or `true`: dropdown not smaller than anchor
|
|
47
|
+
* - `width`: dropdown equal to the anchor.
|
|
45
48
|
* @see {@link DropdownProps#fitToAnchorWidth}
|
|
46
49
|
*/
|
|
47
50
|
fitToAnchorWidth?: DropdownProps['fitToAnchorWidth'];
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import React, { Fragment } from 'react';
|
|
2
2
|
import { mdiSend } from '@lumx/icons';
|
|
3
|
-
import { Button, ColorPalette, IconButton } from '@lumx/react';
|
|
3
|
+
import { Button, ColorPalette, IconButton, Text } from '@lumx/react';
|
|
4
4
|
import { squareImageKnob } from '@lumx/react/stories/knobs/image';
|
|
5
5
|
import { buttonSize } from '@lumx/react/stories/knobs/buttonKnob';
|
|
6
6
|
import { emphasis } from '@lumx/react/stories/knobs/emphasisKnob';
|
|
@@ -30,6 +30,32 @@ export const SimpleButton = ({ theme }: any) => {
|
|
|
30
30
|
);
|
|
31
31
|
};
|
|
32
32
|
|
|
33
|
+
export const SimpleButtonWithTruncatedText = ({ theme }: any) => {
|
|
34
|
+
const buttonText =
|
|
35
|
+
'Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Potenti nullam ac tortor vitae. Lorem ipsum dolor sit amet. Diam sollicitudin tempor id eu nisl nunc mi ipsum. Elementum facilisis leo vel fringilla est ullamcorper eget nulla. Mollis aliquam ut porttitor leo a diam sollicitudin tempor. Ultrices tincidunt arcu non sodales neque sodales.';
|
|
36
|
+
return (
|
|
37
|
+
<Button
|
|
38
|
+
aria-pressed={boolean('isSelected', Boolean(DEFAULT_PROPS.isSelected))}
|
|
39
|
+
emphasis={emphasis('Emphasis', DEFAULT_PROPS.emphasis)}
|
|
40
|
+
theme={theme}
|
|
41
|
+
rightIcon={select('Right icon', { none: undefined, mdiSend }, undefined)}
|
|
42
|
+
leftIcon={select('Left icon', { none: undefined, mdiSend }, undefined)}
|
|
43
|
+
size={buttonSize()}
|
|
44
|
+
isSelected={boolean('isSelected', Boolean(DEFAULT_PROPS.isSelected))}
|
|
45
|
+
isDisabled={boolean('isDisabled', Boolean(DEFAULT_PROPS.isDisabled))}
|
|
46
|
+
color={select('color', ColorPalette, DEFAULT_PROPS.color)}
|
|
47
|
+
href={text('Button link', '')}
|
|
48
|
+
hasBackground={boolean('hasBackground', Boolean(DEFAULT_PROPS.hasBackground))}
|
|
49
|
+
fullWidth
|
|
50
|
+
title={buttonText}
|
|
51
|
+
>
|
|
52
|
+
<Text as="span" truncate>
|
|
53
|
+
{text('Button content', buttonText)}
|
|
54
|
+
</Text>
|
|
55
|
+
</Button>
|
|
56
|
+
);
|
|
57
|
+
};
|
|
58
|
+
|
|
33
59
|
export const WithHref = () => <Button href="https://google.com">Button with redirection</Button>;
|
|
34
60
|
|
|
35
61
|
export const Disabled = () => <Button isDisabled>Disabled button</Button>;
|
|
@@ -3,8 +3,8 @@ import React, { forwardRef, ReactNode } from 'react';
|
|
|
3
3
|
import classNames from 'classnames';
|
|
4
4
|
import isEmpty from 'lodash/isEmpty';
|
|
5
5
|
|
|
6
|
-
import { Emphasis, Icon, Size, Theme } from '@lumx/react';
|
|
7
|
-
import { Comp } from '@lumx/react/utils/type';
|
|
6
|
+
import { Emphasis, Icon, Size, Theme, Text } from '@lumx/react';
|
|
7
|
+
import { Comp, isComponent } from '@lumx/react/utils/type';
|
|
8
8
|
import { getBasicClass, getRootClassName } from '@lumx/react/utils/className';
|
|
9
9
|
import { BaseButtonProps, ButtonRoot } from './ButtonRoot';
|
|
10
10
|
|
|
@@ -71,7 +71,7 @@ export const Button: Comp<ButtonProps, HTMLButtonElement | HTMLAnchorElement> =
|
|
|
71
71
|
variant="button"
|
|
72
72
|
>
|
|
73
73
|
{leftIcon && !isEmpty(leftIcon) && <Icon icon={leftIcon} />}
|
|
74
|
-
{children && <span>{children}</span>}
|
|
74
|
+
{children && (isComponent(Text)(children) ? children : <span>{children}</span>)}
|
|
75
75
|
{rightIcon && !isEmpty(rightIcon) && <Icon icon={rightIcon} />}
|
|
76
76
|
</ButtonRoot>
|
|
77
77
|
);
|
|
@@ -34,7 +34,10 @@ export interface DropdownProps extends GenericProps {
|
|
|
34
34
|
*/
|
|
35
35
|
closeOnEscape?: boolean;
|
|
36
36
|
/**
|
|
37
|
-
*
|
|
37
|
+
* Manage dropdown width:
|
|
38
|
+
* - `maxWidth`: dropdown not bigger than anchor
|
|
39
|
+
* - `minWidth` or `true`: dropdown not smaller than anchor
|
|
40
|
+
* - `width`: dropdown equal to the anchor.
|
|
38
41
|
* @see {@link PopoverProps#fitToAnchorWidth}
|
|
39
42
|
*/
|
|
40
43
|
fitToAnchorWidth?: PopoverProps['fitToAnchorWidth'];
|
|
@@ -16,6 +16,7 @@ import { getRootClassName, handleBasicClasses } from '@lumx/react/utils/classNam
|
|
|
16
16
|
import { mergeRefs } from '@lumx/react/utils/mergeRefs';
|
|
17
17
|
import { useFocusWithin } from '@lumx/react/hooks/useFocusWithin';
|
|
18
18
|
import { getFirstAndLastFocusable } from '@lumx/react/utils/focus/getFirstAndLastFocusable';
|
|
19
|
+
import { useFocusTrap } from '@lumx/react/hooks/useFocusTrap';
|
|
19
20
|
|
|
20
21
|
/**
|
|
21
22
|
* Different possible placements for the popover.
|
|
@@ -86,7 +87,12 @@ export interface PopoverProps extends GenericProps {
|
|
|
86
87
|
closeOnEscape?: boolean;
|
|
87
88
|
/** Shadow elevation. */
|
|
88
89
|
elevation?: Elevation;
|
|
89
|
-
/**
|
|
90
|
+
/**
|
|
91
|
+
* Manage popover width:
|
|
92
|
+
* - `maxWidth`: popover not bigger than anchor
|
|
93
|
+
* - `minWidth` or `true`: popover not smaller than anchor
|
|
94
|
+
* - `width`: popover equal to the anchor.
|
|
95
|
+
*/
|
|
90
96
|
fitToAnchorWidth?: AnchorWidthOption | boolean;
|
|
91
97
|
/** Shrink popover if even after flipping there is not enough space. */
|
|
92
98
|
fitWithinViewportHeight?: boolean;
|
|
@@ -110,6 +116,8 @@ export interface PopoverProps extends GenericProps {
|
|
|
110
116
|
zIndex?: number;
|
|
111
117
|
/** On close callback (on click away or Escape pressed). */
|
|
112
118
|
onClose?(): void;
|
|
119
|
+
/** Whether the popover should trap the focus within itself. Default to false. */
|
|
120
|
+
withFocusTrap?: boolean;
|
|
113
121
|
}
|
|
114
122
|
|
|
115
123
|
/**
|
|
@@ -230,6 +238,7 @@ export const Popover: Comp<PopoverProps, HTMLDivElement> = forwardRef((props, re
|
|
|
230
238
|
usePortal,
|
|
231
239
|
zIndex,
|
|
232
240
|
focusAnchorOnClose = true,
|
|
241
|
+
withFocusTrap,
|
|
233
242
|
...forwardedProps
|
|
234
243
|
} = props;
|
|
235
244
|
// eslint-disable-next-line react-hooks/rules-of-hooks
|
|
@@ -238,6 +247,8 @@ export const Popover: Comp<PopoverProps, HTMLDivElement> = forwardRef((props, re
|
|
|
238
247
|
const [arrowElement, setArrowElement] = useState<null | HTMLElement>(null);
|
|
239
248
|
// eslint-disable-next-line react-hooks/rules-of-hooks
|
|
240
249
|
const clickAwayRef = useRef<HTMLDivElement>(null);
|
|
250
|
+
// eslint-disable-next-line react-hooks/rules-of-hooks
|
|
251
|
+
const contentRef = useRef<HTMLDivElement>(null);
|
|
241
252
|
|
|
242
253
|
/**
|
|
243
254
|
* Track whether the focus is currently set in the
|
|
@@ -335,8 +346,12 @@ export const Popover: Comp<PopoverProps, HTMLDivElement> = forwardRef((props, re
|
|
|
335
346
|
|
|
336
347
|
// eslint-disable-next-line react-hooks/rules-of-hooks
|
|
337
348
|
useCallbackOnEscape(handleClose, isOpen && closeOnEscape);
|
|
349
|
+
|
|
350
|
+
/** Only set focus within if the focus trap is disabled as they interfere with one another. */
|
|
351
|
+
// eslint-disable-next-line react-hooks/rules-of-hooks
|
|
352
|
+
useFocus(focusElement?.current, !withFocusTrap && isOpen && (state?.rects?.popper?.y ?? -1) >= 0);
|
|
338
353
|
// eslint-disable-next-line react-hooks/rules-of-hooks
|
|
339
|
-
|
|
354
|
+
useFocusTrap(withFocusTrap && isOpen && contentRef?.current, focusElement?.current);
|
|
340
355
|
|
|
341
356
|
// eslint-disable-next-line react-hooks/rules-of-hooks
|
|
342
357
|
const clickAwayRefs = useRef([clickAwayRef, anchorRef]);
|
|
@@ -345,7 +360,7 @@ export const Popover: Comp<PopoverProps, HTMLDivElement> = forwardRef((props, re
|
|
|
345
360
|
? renderPopover(
|
|
346
361
|
<div
|
|
347
362
|
{...forwardedProps}
|
|
348
|
-
ref={mergeRefs<HTMLDivElement>(setPopperElement, ref, clickAwayRef)}
|
|
363
|
+
ref={mergeRefs<HTMLDivElement>(setPopperElement, ref, clickAwayRef, contentRef)}
|
|
349
364
|
className={classNames(
|
|
350
365
|
className,
|
|
351
366
|
handleBasicClasses({ prefix: CLASSNAME, elevation: Math.min(elevation || 0, 5), position }),
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
import { mdiMenu } from '@lumx/icons/index';
|
|
2
|
+
import { mdiSettings } from '@lumx/icons/v4-to-v5-aliases';
|
|
3
|
+
import React, { useRef, useState } from 'react';
|
|
4
|
+
import { PopoverDialog, PopoverDialogProps } from '.';
|
|
5
|
+
import { Emphasis, Orientation, Size, Typography } from '..';
|
|
6
|
+
import { Button, IconButton } from '../button';
|
|
7
|
+
import { FlexBox } from '../flex-box';
|
|
8
|
+
import { Heading } from '../heading';
|
|
9
|
+
import { List, ListItem } from '../list';
|
|
10
|
+
import { Placement } from '../popover/Popover';
|
|
11
|
+
import { Toolbar } from '../toolbar';
|
|
12
|
+
|
|
13
|
+
const WithButton = (Story: any, context: any) => {
|
|
14
|
+
const anchorRef = useRef(null);
|
|
15
|
+
const [isOpen, setIsOpen] = useState<boolean>(context?.args?.isOpen || false);
|
|
16
|
+
|
|
17
|
+
return (
|
|
18
|
+
<>
|
|
19
|
+
<Button ref={anchorRef} onClick={() => setIsOpen((current) => !current)}>
|
|
20
|
+
Open popover
|
|
21
|
+
</Button>
|
|
22
|
+
<Story anchorRef={anchorRef} isOpen={isOpen} onClose={() => setIsOpen(false)} />
|
|
23
|
+
</>
|
|
24
|
+
);
|
|
25
|
+
};
|
|
26
|
+
|
|
27
|
+
const dialogHeaderId = 'dialog-header';
|
|
28
|
+
|
|
29
|
+
const DemoPopoverContent = () => (
|
|
30
|
+
<FlexBox orientation={Orientation.vertical}>
|
|
31
|
+
<Toolbar
|
|
32
|
+
label={
|
|
33
|
+
<Heading id="dialogHeaderId" typography={Typography.headline}>
|
|
34
|
+
Title
|
|
35
|
+
</Heading>
|
|
36
|
+
}
|
|
37
|
+
after={<IconButton label="Settings" icon={mdiSettings} emphasis={Emphasis.low} />}
|
|
38
|
+
/>
|
|
39
|
+
<List>
|
|
40
|
+
<ListItem size={Size.huge} after={<IconButton label="Menu" icon={mdiMenu} size={Size.s} />}>
|
|
41
|
+
List Item With Actions
|
|
42
|
+
</ListItem>
|
|
43
|
+
<ListItem
|
|
44
|
+
size={Size.huge}
|
|
45
|
+
linkProps={{
|
|
46
|
+
href: 'http://google.com',
|
|
47
|
+
}}
|
|
48
|
+
>
|
|
49
|
+
Clickable list item
|
|
50
|
+
</ListItem>
|
|
51
|
+
</List>
|
|
52
|
+
</FlexBox>
|
|
53
|
+
);
|
|
54
|
+
|
|
55
|
+
export default {
|
|
56
|
+
title: 'LumX components/popover-dialog/PopoverDialog',
|
|
57
|
+
component: PopoverDialog,
|
|
58
|
+
decorators: [WithButton],
|
|
59
|
+
args: {
|
|
60
|
+
children: <DemoPopoverContent />,
|
|
61
|
+
'aria-labelledby': dialogHeaderId,
|
|
62
|
+
placement: Placement.BOTTOM,
|
|
63
|
+
},
|
|
64
|
+
};
|
|
65
|
+
|
|
66
|
+
const Template = (args: PopoverDialogProps, context: any) => {
|
|
67
|
+
const { anchorRef, isOpen, onClose } = context;
|
|
68
|
+
return (
|
|
69
|
+
<PopoverDialog {...args} anchorRef={anchorRef} isOpen={isOpen} onClose={onClose}>
|
|
70
|
+
{args.children}
|
|
71
|
+
</PopoverDialog>
|
|
72
|
+
);
|
|
73
|
+
};
|
|
74
|
+
|
|
75
|
+
export const Default = Template.bind({});
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
import React, { useRef, useState } from 'react';
|
|
2
|
+
import { render, screen, within } from '@testing-library/react';
|
|
3
|
+
import userEvent from '@testing-library/user-event';
|
|
4
|
+
|
|
5
|
+
import { PopoverDialog } from './PopoverDialog';
|
|
6
|
+
|
|
7
|
+
const DialogWithButton = (forwardedProps: any) => {
|
|
8
|
+
const anchorRef = useRef(null);
|
|
9
|
+
const [isOpen, setIsOpen] = useState<boolean>(false);
|
|
10
|
+
|
|
11
|
+
return (
|
|
12
|
+
<>
|
|
13
|
+
<button type="button" ref={anchorRef} onClick={() => setIsOpen((current) => !current)}>
|
|
14
|
+
Open popover
|
|
15
|
+
</button>
|
|
16
|
+
|
|
17
|
+
<PopoverDialog {...forwardedProps} anchorRef={anchorRef} isOpen={isOpen} onClose={() => setIsOpen(false)}>
|
|
18
|
+
<button type="button">Button 1</button>
|
|
19
|
+
<button type="button">Button 2</button>
|
|
20
|
+
</PopoverDialog>
|
|
21
|
+
{/* This should never have focus while popover is opened */}
|
|
22
|
+
<button type="button">External button</button>
|
|
23
|
+
</>
|
|
24
|
+
);
|
|
25
|
+
};
|
|
26
|
+
|
|
27
|
+
describe(`<${PopoverDialog.displayName}>`, () => {
|
|
28
|
+
it('should behave like a dialog', async () => {
|
|
29
|
+
const label = 'Test Label';
|
|
30
|
+
|
|
31
|
+
render(<DialogWithButton label={label} />);
|
|
32
|
+
|
|
33
|
+
/** Open the popover */
|
|
34
|
+
const triggerElement = screen.getByRole('button', { name: 'Open popover' });
|
|
35
|
+
await userEvent.click(triggerElement);
|
|
36
|
+
|
|
37
|
+
const dialog = await screen.findByRole('dialog', { name: label });
|
|
38
|
+
const withinDialog = within(dialog);
|
|
39
|
+
|
|
40
|
+
/** Get buttons within dialog */
|
|
41
|
+
const dialogButtons = withinDialog.getAllByRole('button');
|
|
42
|
+
|
|
43
|
+
// First button should have focus by default on opening
|
|
44
|
+
expect(dialogButtons[0]).toHaveFocus();
|
|
45
|
+
|
|
46
|
+
// Tab to next button
|
|
47
|
+
await userEvent.tab();
|
|
48
|
+
|
|
49
|
+
// Second button should have focus
|
|
50
|
+
expect(dialogButtons[1]).toHaveFocus();
|
|
51
|
+
|
|
52
|
+
// Tab to next button
|
|
53
|
+
await userEvent.tab();
|
|
54
|
+
|
|
55
|
+
// As there is no more button, focus should loop back to first button.
|
|
56
|
+
expect(dialogButtons[0]).toHaveFocus();
|
|
57
|
+
|
|
58
|
+
// Close the popover
|
|
59
|
+
await userEvent.keyboard('{escape}');
|
|
60
|
+
|
|
61
|
+
expect(screen.queryByRole('dialog', { name: label })).not.toBeInTheDocument();
|
|
62
|
+
/** Anchor should retrieve the focus */
|
|
63
|
+
expect(triggerElement).toHaveFocus();
|
|
64
|
+
});
|
|
65
|
+
});
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
import React, { forwardRef } from 'react';
|
|
2
|
+
import classNames from 'classnames';
|
|
3
|
+
|
|
4
|
+
import { Comp, HasAriaLabelOrLabelledBy } from '@lumx/react/utils/type';
|
|
5
|
+
import { getRootClassName, handleBasicClasses } from '@lumx/react/utils/className';
|
|
6
|
+
|
|
7
|
+
import { Popover, PopoverProps } from '../popover/Popover';
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* PopoverDialog props.
|
|
11
|
+
* The PopoverDialog has the same props as the Popover but requires an accessible label.
|
|
12
|
+
*/
|
|
13
|
+
export type PopoverDialogProps = PopoverProps & HasAriaLabelOrLabelledBy;
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Component display name.
|
|
17
|
+
*/
|
|
18
|
+
const COMPONENT_NAME = 'PopoverDialog';
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* Component default class name and class prefix.
|
|
22
|
+
*/
|
|
23
|
+
const CLASSNAME = getRootClassName(COMPONENT_NAME);
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* Component default props.
|
|
27
|
+
*/
|
|
28
|
+
const DEFAULT_PROPS: Partial<PopoverDialogProps> = {};
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* PopoverDialog component.
|
|
32
|
+
* Defines a popover that acts like a dialog
|
|
33
|
+
* * Has a dialog aria role
|
|
34
|
+
* * Sets a focus trap within the popover
|
|
35
|
+
* * Closes on click away and escape.
|
|
36
|
+
*/
|
|
37
|
+
export const PopoverDialog: Comp<PopoverDialogProps, HTMLDivElement> = forwardRef((props, ref) => {
|
|
38
|
+
const { children, isOpen, focusElement, label, className, ...forwardedProps } = props;
|
|
39
|
+
|
|
40
|
+
return (
|
|
41
|
+
<Popover
|
|
42
|
+
{...forwardedProps}
|
|
43
|
+
ref={ref}
|
|
44
|
+
className={classNames(className, handleBasicClasses({ prefix: CLASSNAME }))}
|
|
45
|
+
role="dialog"
|
|
46
|
+
aria-modal="true"
|
|
47
|
+
/**
|
|
48
|
+
* If a label is set, set as aria-label.
|
|
49
|
+
* If it is undefined, the label can be set using the `aria-label` and `aria-labelledby` props
|
|
50
|
+
*/
|
|
51
|
+
aria-label={label}
|
|
52
|
+
isOpen={isOpen}
|
|
53
|
+
focusElement={focusElement}
|
|
54
|
+
closeOnClickAway
|
|
55
|
+
closeOnEscape
|
|
56
|
+
withFocusTrap
|
|
57
|
+
>
|
|
58
|
+
{children}
|
|
59
|
+
</Popover>
|
|
60
|
+
);
|
|
61
|
+
});
|
|
62
|
+
|
|
63
|
+
PopoverDialog.displayName = COMPONENT_NAME;
|
|
64
|
+
PopoverDialog.className = CLASSNAME;
|
|
65
|
+
PopoverDialog.defaultProps = DEFAULT_PROPS;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from './PopoverDialog';
|
package/src/index.ts
CHANGED
|
@@ -35,6 +35,7 @@ export * from './components/message';
|
|
|
35
35
|
export * from './components/mosaic';
|
|
36
36
|
export * from './components/notification';
|
|
37
37
|
export * from './components/popover';
|
|
38
|
+
export * from './components/popover-dialog';
|
|
38
39
|
export * from './components/post-block';
|
|
39
40
|
export * from './components/progress';
|
|
40
41
|
export * from './components/progress-tracker';
|
package/src/utils/type.ts
CHANGED
|
@@ -91,3 +91,23 @@ export const isComponentType = (type: ReactElement['type']) => (node: ReactNode)
|
|
|
91
91
|
* (excluding `NaN` as it can't be distinguished from `number`)
|
|
92
92
|
*/
|
|
93
93
|
export type Falsy = false | undefined | null | 0 | '';
|
|
94
|
+
|
|
95
|
+
/**
|
|
96
|
+
* Require either `aria-label` or `arial-labelledby` prop.
|
|
97
|
+
* If none are set, the order will prioritize `aria-labelledby` over `aria-label` as it
|
|
98
|
+
* needs a visible element.
|
|
99
|
+
*/
|
|
100
|
+
export type HasAriaLabelOrLabelledBy<T = string | undefined> = T extends string
|
|
101
|
+
? {
|
|
102
|
+
/**
|
|
103
|
+
* The id of the element to use as title of the dialog. Can be within or out of the dialog.
|
|
104
|
+
* Although it is not recommended, aria-label can be used instead if no visible element is available.
|
|
105
|
+
*/
|
|
106
|
+
'aria-labelledby': T;
|
|
107
|
+
/** The label of the dialog. */
|
|
108
|
+
'aria-label'?: undefined;
|
|
109
|
+
}
|
|
110
|
+
: {
|
|
111
|
+
'aria-label': string;
|
|
112
|
+
'aria-labelledby'?: undefined;
|
|
113
|
+
};
|