@ndlib/component-library 0.0.28 → 0.0.30
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/components/composites/DropdownLinks/DropdownLinks.stories.d.ts +7 -0
- package/dist/components/composites/DropdownLinks/DropdownLinks.stories.js +21 -0
- package/dist/components/composites/DropdownLinks/DropdownLinks.test.d.ts +1 -0
- package/dist/components/composites/DropdownLinks/DropdownLinks.test.js +27 -0
- package/dist/components/composites/DropdownLinks/index.d.ts +15 -0
- package/dist/components/composites/DropdownLinks/index.js +73 -0
- package/dist/components/elements/Fields/option.d.ts +1 -0
- package/dist/components/elements/Link/index.d.ts +1 -0
- package/dist/components/elements/Link/index.js +3 -3
- package/dist/components/elements/ListBox/index.js +2 -2
- package/dist/components/providers/componentConfig.d.ts +2 -0
- package/dist/components/providers/componentConfig.js +3 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.js +1 -0
- package/dist/theme/colors.js +1 -1
- package/package.json +1 -1
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import type { Meta, StoryObj } from '@storybook/react';
|
|
2
|
+
import { DropdownLinks } from '.';
|
|
3
|
+
declare const meta: Meta<typeof DropdownLinks>;
|
|
4
|
+
export default meta;
|
|
5
|
+
type Story = StoryObj<typeof DropdownLinks>;
|
|
6
|
+
export declare const Default: Story;
|
|
7
|
+
export declare const OpenInNewTab: Story;
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
|
+
import ArrowDropDownIcon from '@mui/icons-material/ArrowDropDown';
|
|
3
|
+
import { DropdownLinks } from '.';
|
|
4
|
+
import { Button } from '../../elements/Button';
|
|
5
|
+
import { Box } from '../../elements/layout/Box';
|
|
6
|
+
const meta = {
|
|
7
|
+
title: 'Composites/DropdownLinks',
|
|
8
|
+
component: DropdownLinks,
|
|
9
|
+
tags: ['autodocs'],
|
|
10
|
+
};
|
|
11
|
+
export default meta;
|
|
12
|
+
const options = [
|
|
13
|
+
{ value: 'https://google.com', label: 'Google' },
|
|
14
|
+
{ value: 'https://facebook.com', label: 'Facebook' },
|
|
15
|
+
];
|
|
16
|
+
export const Default = {
|
|
17
|
+
render: () => (_jsx(Box, Object.assign({ sx: { width: '200px', height: '200px' } }, { children: _jsx(DropdownLinks, Object.assign({ options: options }, { children: ({ anchorProps }) => (_jsx(Button, Object.assign({}, anchorProps, { rightIcon: ArrowDropDownIcon }, { children: "Links" }))) })) }))),
|
|
18
|
+
};
|
|
19
|
+
export const OpenInNewTab = {
|
|
20
|
+
render: () => (_jsx(Box, Object.assign({ sx: { width: '200px', height: '200px' } }, { children: _jsx(DropdownLinks, Object.assign({ options: options, openNewTab: true }, { children: ({ anchorProps }) => _jsx(Button, Object.assign({}, anchorProps, { children: "Links" })) })) }))),
|
|
21
|
+
};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
|
+
import { render } from '../../../utils/test';
|
|
3
|
+
import { DropdownLinks } from '.';
|
|
4
|
+
import { Button } from '../../elements/Button';
|
|
5
|
+
import { fireEvent } from '@testing-library/react';
|
|
6
|
+
const options = [
|
|
7
|
+
{ value: 'https://google.com', label: 'Google' },
|
|
8
|
+
{ value: 'https://facebook.com', label: 'Facebook' },
|
|
9
|
+
];
|
|
10
|
+
const DropdownExample = (_jsx(DropdownLinks, Object.assign({ options: options }, { children: ({ anchorProps }) => _jsx(Button, Object.assign({}, anchorProps, { children: "Links" })) })));
|
|
11
|
+
describe('DropdownLinks', () => {
|
|
12
|
+
it('toggle links when button is clicked', () => {
|
|
13
|
+
const { getByRole, getAllByRole } = render(DropdownExample);
|
|
14
|
+
expect(() => getByRole('link')).toThrow();
|
|
15
|
+
expect(() => getByRole('navigation')).toThrow();
|
|
16
|
+
const button = getByRole('button');
|
|
17
|
+
expect(button).not.toHaveAttribute('aria-expanded', 'true');
|
|
18
|
+
fireEvent.click(button);
|
|
19
|
+
expect(button).toHaveAttribute('aria-expanded', 'true');
|
|
20
|
+
expect(getAllByRole('link')).toHaveLength(2);
|
|
21
|
+
expect(getByRole('navigation')).toBeDefined();
|
|
22
|
+
fireEvent.click(button);
|
|
23
|
+
expect(button).not.toHaveAttribute('aria-expanded', 'true');
|
|
24
|
+
expect(() => getByRole('link')).toThrow();
|
|
25
|
+
expect(() => getByRole('navigation')).toThrow();
|
|
26
|
+
});
|
|
27
|
+
});
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import { BasicOption, RenderOption } from '../../elements/Fields/option';
|
|
3
|
+
import { StyledElementProps } from '../../../theme';
|
|
4
|
+
type AnchorProps = Omit<React.HTMLAttributes<HTMLButtonElement>, 'color'>;
|
|
5
|
+
type LinkOption = BasicOption<string>;
|
|
6
|
+
type DropdownChildrenFn = (params: {
|
|
7
|
+
anchorProps: AnchorProps;
|
|
8
|
+
}) => React.ReactNode;
|
|
9
|
+
type DropdownLinksProps = StyledElementProps<HTMLDivElement, {
|
|
10
|
+
options: BasicOption<string>[];
|
|
11
|
+
renderOption?: RenderOption<string, LinkOption>;
|
|
12
|
+
openNewTab?: boolean;
|
|
13
|
+
}, DropdownChildrenFn>;
|
|
14
|
+
export declare const DropdownLinks: React.FC<DropdownLinksProps>;
|
|
15
|
+
export {};
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
var __rest = (this && this.__rest) || function (s, e) {
|
|
2
|
+
var t = {};
|
|
3
|
+
for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p) && e.indexOf(p) < 0)
|
|
4
|
+
t[p] = s[p];
|
|
5
|
+
if (s != null && typeof Object.getOwnPropertySymbols === "function")
|
|
6
|
+
for (var i = 0, p = Object.getOwnPropertySymbols(s); i < p.length; i++) {
|
|
7
|
+
if (e.indexOf(p[i]) < 0 && Object.prototype.propertyIsEnumerable.call(s, p[i]))
|
|
8
|
+
t[p[i]] = s[p[i]];
|
|
9
|
+
}
|
|
10
|
+
return t;
|
|
11
|
+
};
|
|
12
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
13
|
+
import { useCallback, useMemo, useState } from 'react';
|
|
14
|
+
import { autoPlacement, offset, size, useFloating } from '@floating-ui/react';
|
|
15
|
+
import { ListBox } from '../../elements/ListBox';
|
|
16
|
+
import { useUniqueHtmlId } from '../../../utils/hooks/useUniqueHtmlId';
|
|
17
|
+
import { Link } from '../../elements/Link';
|
|
18
|
+
import { Box } from '../../elements/layout/Box';
|
|
19
|
+
import { useComponentConfig } from '../../providers/componentConfig';
|
|
20
|
+
export const DropdownLinks = (_a) => {
|
|
21
|
+
var { children, options: optionsParam, renderOption: renderOptionParam, openNewTab, role = 'navigation' } = _a, rest = __rest(_a, ["children", "options", "renderOption", "openNewTab", "role"]);
|
|
22
|
+
const [isOpen, setIsOpen] = useState(false);
|
|
23
|
+
const [dropdownMinWidth, setDropdownMinWidth] = useState('0px');
|
|
24
|
+
const listboxId = useUniqueHtmlId('dropdown-links');
|
|
25
|
+
const anchorId = useUniqueHtmlId('dropdown-links-anchor');
|
|
26
|
+
const { navigate } = useComponentConfig().link;
|
|
27
|
+
const options = optionsParam.map((option) => (Object.assign(Object.assign({}, option), { onClick: () => {
|
|
28
|
+
if (option.onClick) {
|
|
29
|
+
option.onClick();
|
|
30
|
+
}
|
|
31
|
+
else if (openNewTab) {
|
|
32
|
+
window.open(option.value, '_blank');
|
|
33
|
+
}
|
|
34
|
+
else if (option.value) {
|
|
35
|
+
navigate(option.value);
|
|
36
|
+
}
|
|
37
|
+
setIsOpen(false);
|
|
38
|
+
} })));
|
|
39
|
+
const renderOption = renderOptionParam
|
|
40
|
+
? renderOptionParam
|
|
41
|
+
: (params) => (_jsx(Link, Object.assign({ to: params.option.value, target: openNewTab ? '_blank' : undefined, onClick: (e) => {
|
|
42
|
+
e.preventDefault();
|
|
43
|
+
} }, { children: params.option.label })));
|
|
44
|
+
const { refs, floatingStyles } = useFloating({
|
|
45
|
+
placement: 'bottom-start',
|
|
46
|
+
middleware: [
|
|
47
|
+
offset(2),
|
|
48
|
+
size({
|
|
49
|
+
apply({ rects }) {
|
|
50
|
+
setDropdownMinWidth(`${rects.reference.width}px`);
|
|
51
|
+
},
|
|
52
|
+
}),
|
|
53
|
+
autoPlacement({
|
|
54
|
+
allowedPlacements: ['top-start', 'bottom-start'],
|
|
55
|
+
}),
|
|
56
|
+
],
|
|
57
|
+
});
|
|
58
|
+
const onClick = useCallback(() => {
|
|
59
|
+
setIsOpen(!isOpen);
|
|
60
|
+
}, [isOpen]);
|
|
61
|
+
const dropdownAnchorProps = useMemo(() => ({
|
|
62
|
+
id: anchorId,
|
|
63
|
+
'aria-haspopup': true,
|
|
64
|
+
'aria-expanded': isOpen,
|
|
65
|
+
'aria-owns': listboxId,
|
|
66
|
+
ref: refs.setReference,
|
|
67
|
+
onClick,
|
|
68
|
+
}), [refs, onClick, anchorId, isOpen, listboxId]);
|
|
69
|
+
return (_jsxs(Box, Object.assign({}, rest, { children: [children &&
|
|
70
|
+
children({
|
|
71
|
+
anchorProps: dropdownAnchorProps,
|
|
72
|
+
}), isOpen && (_jsx(ListBox, { role: role, id: listboxId, options: options, ref: refs.setFloating, renderOption: renderOption, style: Object.assign(Object.assign({ minWidth: dropdownMinWidth }, floatingStyles), { zIndex: 1 }) }))] })));
|
|
73
|
+
};
|
|
@@ -25,7 +25,7 @@ const SIZE_TYPOGRAPHY_MAP = {
|
|
|
25
25
|
[LINK_SIZE.LG]: TYPOGRAPHY_TYPE.CONTROL_LARGE,
|
|
26
26
|
};
|
|
27
27
|
export const Link = (_a) => {
|
|
28
|
-
var { size, to, sx } = _a, rest = __rest(_a, ["size", "to", "sx"]);
|
|
28
|
+
var { size, to, sx, target } = _a, rest = __rest(_a, ["size", "to", "sx", "target"]);
|
|
29
29
|
const { link } = useComponentConfig();
|
|
30
30
|
const typography = SIZE_TYPOGRAPHY_MAP[size || LINK_SIZE.MD];
|
|
31
31
|
const InternalLink = link.internalLinkComponent;
|
|
@@ -33,9 +33,9 @@ export const Link = (_a) => {
|
|
|
33
33
|
const isExternal = link.externalMatcher.test(to);
|
|
34
34
|
const styles = Object.assign(Object.assign({ color: COLOR.ND_BLUE_LIGHT, textDecoration: 'none' }, getTypographyStyles(typography)), sx);
|
|
35
35
|
if (isExternal) {
|
|
36
|
-
return _jsx(ExternalLink, Object.assign({ to: to }, rest, { sx: styles }));
|
|
36
|
+
return _jsx(ExternalLink, Object.assign({ to: to }, rest, { sx: styles, target: target }));
|
|
37
37
|
}
|
|
38
38
|
else {
|
|
39
|
-
return _jsx(InternalLink, Object.assign({ to: to }, rest, { sx: styles }));
|
|
39
|
+
return _jsx(InternalLink, Object.assign({ to: to }, rest, { sx: styles, target: target }));
|
|
40
40
|
}
|
|
41
41
|
};
|
|
@@ -21,7 +21,7 @@ function ListBoxInner(_a, ref) {
|
|
|
21
21
|
const typography = getTypographyStyles(TYPOGRAPHY_TYPE.PARAGRAPH_MEDIUM);
|
|
22
22
|
return (_jsx("div", Object.assign({ role: "listbox", ref: ref, sx: Object.assign(Object.assign({ border: 'solid 1px', borderRadius: '4px', borderColor: COLOR.LIGHT_GRAY, backgroundColor: COLOR.BACKGROUND, boxShadow: '0px 0px 8px 2px #dddddd' }, typography), sx), onKeyDown: (e) => {
|
|
23
23
|
console.log(e);
|
|
24
|
-
} }, rest, { children: options.map((option) => (_jsx("div", Object.assign({ id: getOptionId(option.value), "aria-selected": selected === option.value, role: "option", onMouseDown: () => selectOption && selectOption(option.value), sx: {
|
|
24
|
+
} }, rest, { children: options.map((option) => (_jsx("div", Object.assign({ id: getOptionId(option.value), "aria-selected": selected === option.value, role: "option", onClick: option.onClick, onMouseDown: () => selectOption && selectOption(option.value), sx: {
|
|
25
25
|
px: 4,
|
|
26
26
|
py: 3,
|
|
27
27
|
backgroundColor: focused === option.value ? COLOR.FAINT_GRAY : COLOR.BACKGROUND,
|
|
@@ -45,6 +45,6 @@ function ListBoxInner(_a, ref) {
|
|
|
45
45
|
option,
|
|
46
46
|
selected: selected === option.value,
|
|
47
47
|
focused: focused === option.value,
|
|
48
|
-
}) })))) })));
|
|
48
|
+
}) }), option.value))) })));
|
|
49
49
|
}
|
|
50
50
|
export const ListBox = React.forwardRef(ListBoxInner);
|
|
@@ -4,6 +4,7 @@ import { StyledElementProps } from '../../theme';
|
|
|
4
4
|
export type ComponentConfig = {
|
|
5
5
|
link: {
|
|
6
6
|
externalMatcher: RegExp;
|
|
7
|
+
navigate: (url: string) => void;
|
|
7
8
|
internalLinkComponent: React.FC<LinkComponentProps>;
|
|
8
9
|
externalLinkComponent: React.FC<LinkComponentProps>;
|
|
9
10
|
};
|
|
@@ -11,6 +12,7 @@ export type ComponentConfig = {
|
|
|
11
12
|
export type ComponentConfigParam = DeepPartial<ComponentConfig>;
|
|
12
13
|
export type LinkComponentProps = StyledElementProps<HTMLAnchorElement, {
|
|
13
14
|
to: string;
|
|
15
|
+
target?: string;
|
|
14
16
|
}>;
|
|
15
17
|
export declare const DefaultLink: React.FC<LinkComponentProps>;
|
|
16
18
|
export declare const ComponentConfigContext: import("react").Context<ComponentConfig>;
|
|
@@ -19,6 +19,9 @@ export const DefaultLink = (_a) => {
|
|
|
19
19
|
const defaultComponentConfig = {
|
|
20
20
|
link: {
|
|
21
21
|
externalMatcher: DEFAULT_EXTERNAL_LINK_MATCHER,
|
|
22
|
+
navigate: (url) => {
|
|
23
|
+
window.location.href = url;
|
|
24
|
+
},
|
|
22
25
|
internalLinkComponent: DefaultLink,
|
|
23
26
|
externalLinkComponent: DefaultLink,
|
|
24
27
|
},
|
package/dist/index.d.ts
CHANGED
|
@@ -30,6 +30,7 @@ export { Pill, PILL_SIZE, PILL_TYPE } from './components/elements/Pill';
|
|
|
30
30
|
export { Card, CARD_SIZE, CARD_LAYOUT } from './components/composites/Card';
|
|
31
31
|
export { NavMenu } from './components/composites/NavMenu';
|
|
32
32
|
export { EmptyState } from './components/composites/EmptyState';
|
|
33
|
+
export { DropdownLinks } from './components/composites/DropdownLinks';
|
|
33
34
|
export { UiProvider } from './components/providers/ui';
|
|
34
35
|
export { MenuProvider, useMenu } from './components/providers/menu';
|
|
35
36
|
export { useAlerts, AlertsProvider, ALERT_DOMAIN, } from './components/providers/alerts';
|
package/dist/index.js
CHANGED
|
@@ -30,6 +30,7 @@ export { Pill, PILL_SIZE, PILL_TYPE } from './components/elements/Pill';
|
|
|
30
30
|
export { Card, CARD_SIZE, CARD_LAYOUT } from './components/composites/Card';
|
|
31
31
|
export { NavMenu } from './components/composites/NavMenu';
|
|
32
32
|
export { EmptyState } from './components/composites/EmptyState';
|
|
33
|
+
export { DropdownLinks } from './components/composites/DropdownLinks';
|
|
33
34
|
export { UiProvider } from './components/providers/ui';
|
|
34
35
|
export { MenuProvider, useMenu } from './components/providers/menu';
|
|
35
36
|
export { useAlerts, AlertsProvider, ALERT_DOMAIN, } from './components/providers/alerts';
|
package/dist/theme/colors.js
CHANGED
|
@@ -47,7 +47,7 @@ export const colors = {
|
|
|
47
47
|
[COLOR.SECONDARY]: '#f8e999',
|
|
48
48
|
[COLOR.SECONDARY_HIGHLIGHT]: '#ddcc70',
|
|
49
49
|
[COLOR.TEXT_ON_SECONDARY]: '#242111',
|
|
50
|
-
[COLOR.LIGHT_GRAY]: '#
|
|
50
|
+
[COLOR.LIGHT_GRAY]: '#767676',
|
|
51
51
|
[COLOR.FAINT_GRAY]: '#eeeeee',
|
|
52
52
|
[COLOR.ND_BLUE]: '#0c2340',
|
|
53
53
|
[COLOR.ND_BLUE_LIGHT]: '#143865',
|