@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.
@@ -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,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
+ };
@@ -3,6 +3,7 @@ export type Key = string | number;
3
3
  export interface BasicOption<Value extends Key> {
4
4
  value: Value;
5
5
  label?: string;
6
+ onClick?: () => void;
6
7
  }
7
8
  export type RenderOption<Value extends Key, Option extends BasicOption<Value>> = (params: {
8
9
  option: Option;
@@ -3,6 +3,7 @@ import React from 'react';
3
3
  import { StyledElementProps } from '../../../theme';
4
4
  type LinkProps = StyledElementProps<HTMLSpanElement, {
5
5
  to: string;
6
+ target?: string;
6
7
  size?: LINK_SIZE;
7
8
  }>;
8
9
  export declare enum LINK_SIZE {
@@ -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';
@@ -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]: '#888888',
50
+ [COLOR.LIGHT_GRAY]: '#767676',
51
51
  [COLOR.FAINT_GRAY]: '#eeeeee',
52
52
  [COLOR.ND_BLUE]: '#0c2340',
53
53
  [COLOR.ND_BLUE_LIGHT]: '#143865',
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ndlib/component-library",
3
- "version": "0.0.28",
3
+ "version": "0.0.30",
4
4
  "type": "module",
5
5
  "sideEffects": false,
6
6
  "files": [