@utahdts/utah-design-system 1.15.5 → 1.16.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.
Files changed (33) hide show
  1. package/css/1-settings/_spacing.scss +16 -0
  2. package/css/6-components/_components-index.scss +1 -0
  3. package/css/6-components/base-components/forms/_combo-box-input.scss +5 -0
  4. package/css/6-components/base-components/forms/_time-input.scss +28 -0
  5. package/css/6-components/base-components/navigation/_menu-item.scss +35 -0
  6. package/css/6-components/base-components/navigation/_side-panel-navigation.scss +6 -1
  7. package/css/6-components/base-components/navigation/_vertical-menu.scss +45 -1
  8. package/dist/style.css +297 -9
  9. package/dist/utah-design-system.es.js +3846 -3441
  10. package/dist/utah-design-system.umd.js +3845 -3440
  11. package/index.js +2 -0
  12. package/package.json +12 -12
  13. package/react/components/forms/ComboBox/ComboBox.jsx +9 -5
  14. package/react/components/forms/ComboBox/ComboBoxOption.jsx +10 -0
  15. package/react/components/forms/ComboBox/context/ComboBoxContextProvider.jsx +10 -0
  16. package/react/components/forms/ComboBox/internal/CombBoxListBox.jsx +6 -4
  17. package/react/components/forms/ComboBox/internal/ComboBoxTextInput.jsx +11 -1
  18. package/react/components/forms/FormContext/useFormContextInputValue.js +4 -1
  19. package/react/components/forms/MultiSelect/MultiSelect.jsx +6 -0
  20. package/react/components/forms/MultiSelect/MultiSelectComboBox.jsx +6 -0
  21. package/react/components/forms/PlainText.jsx +7 -2
  22. package/react/components/forms/Select.jsx +3 -3
  23. package/react/components/forms/TextArea.jsx +3 -3
  24. package/react/components/forms/TextInput.jsx +3 -3
  25. package/react/components/forms/TimeInput.jsx +166 -0
  26. package/react/components/navigation/HorizontalMenu.jsx +2 -2
  27. package/react/components/navigation/VerticalMenu.jsx +45 -8
  28. package/react/components/navigation/items/MenuItemFlyout.jsx +119 -0
  29. package/react/components/navigation/{MenuItem.jsx → items/MenuItemInline.jsx} +24 -8
  30. package/react/components/navigation/items/MenuItemPlain.jsx +63 -0
  31. package/react/enums/menuTypes.js +7 -0
  32. package/react/hooks/useDebounceFunc.js +1 -1
  33. package/react/hooks/useGlobalKeyEvent.js +1 -1
@@ -1,34 +1,71 @@
1
+ import { childrenMenuTypes } from '@utahdts/utah-design-system-header';
1
2
  import { joinClassNames } from '../../util/joinClassNames';
2
- import { MenuItem } from './MenuItem';
3
+ import { MenuItemInline } from './items/MenuItemInline';
4
+ import { MenuItemPlain } from './items/MenuItemPlain';
5
+ import { MenuItemFlyout } from './items/MenuItemFlyout';
6
+ import { menuTypes } from '../../enums/menuTypes';
3
7
 
4
8
  /** @typedef {import('@utahdts/utah-design-system').WebsiteMainMenu} WebsiteMainMenu */
5
9
  /** @typedef {import('@utahdts/utah-design-system').WebsiteMainMenuItem} WebsiteMainMenuItem */
6
10
 
7
11
  /**
8
12
  * @param {object} props
13
+ * @param {string} [props.className]
9
14
  * @param {WebsiteMainMenu | WebsiteMainMenuItem} [props.currentMenuItem]
10
15
  * @param {WebsiteMainMenu[]} props.menus
11
16
  * @returns {import('react').JSX.Element}
12
17
  */
13
- export function VerticalMenu({ currentMenuItem, menus }) {
18
+ export function VerticalMenu({ className, currentMenuItem, menus }) {
14
19
  return (
15
20
  <>
16
21
  {
17
22
  menus.map((menu) => {
18
23
  const TitleTagName = menu.titleTagName || 'h2';
19
24
  return (
20
- <nav className="menu-side-panel" key={`side-panel-navigation-menu__${menu.id}`} aria-labelledby={menu.id}>
25
+ <nav className={className} key={`vertical-menu__${menu.id}`} aria-labelledby={menu.id}>
21
26
  {/* @ts-ignore */}
22
27
  <TitleTagName
23
28
  id={menu.id}
24
- className={joinClassNames(menu.titleTagClassName, 'menu-side-panel__header')}
29
+ className={joinClassNames(menu.titleTagClassName, 'vertical-menu__header')}
25
30
  >
26
31
  {menu.header}
27
32
  </TitleTagName>
28
- <ul key={`side-panel-navigation-menu__${menu.id}`} role="menu">
29
- {menu.menuItems.map((menuItem) => (
30
- <MenuItem currentMenuItem={currentMenuItem} menuItem={menuItem} key={`menu-side-panel__menu-item__${menuItem.link}-${menuItem.title}}`} />
31
- ))}
33
+ <ul key={`vertical-menu__list__${menu.id}`} role="menu" className="vertical-menu">
34
+ {menu.menuItems.map((menuItem) => {
35
+ let result;
36
+ switch (menuItem.childrenMenuType) {
37
+ case childrenMenuTypes.INLINE:
38
+ result = (
39
+ <MenuItemInline
40
+ menuType={menuTypes.VERTICAL}
41
+ currentMenuItem={currentMenuItem}
42
+ menuItem={menuItem}
43
+ key={`vertical-menu__menu-item__${menuItem.link}-${menuItem.title}}`}
44
+ />
45
+ );
46
+ break;
47
+ case childrenMenuTypes.FLYOUT:
48
+ result = (
49
+ <MenuItemFlyout
50
+ menuType={menuTypes.VERTICAL}
51
+ currentMenuItem={currentMenuItem}
52
+ menuItem={menuItem}
53
+ key={`vertical-menu__menu-item__${menuItem.link}-${menuItem.title}}`}
54
+ />
55
+ );
56
+ break;
57
+ default:
58
+ result = (
59
+ <MenuItemPlain
60
+ menuType={menuTypes.VERTICAL}
61
+ menuItem={menuItem}
62
+ key={`vertical-menu__menu-item__${menuItem.link}-${menuItem.title}}`}
63
+ currentMenuItem={currentMenuItem}
64
+ />
65
+ );
66
+ }
67
+ return result;
68
+ })}
32
69
  </ul>
33
70
  </nav>
34
71
  );
@@ -0,0 +1,119 @@
1
+ import { NavLink } from 'react-router-dom';
2
+ import { useImmer } from 'use-immer';
3
+ import { useRef } from 'react';
4
+ import { usePopper } from 'react-popper';
5
+ import { joinClassNames } from '../../../util/joinClassNames';
6
+ import { IconButton } from '../../buttons/IconButton';
7
+ import { ICON_BUTTON_APPEARANCE } from '../../../enums/buttonEnums';
8
+ import { Icons } from '../../icons/Icons';
9
+ import { menuTypes } from '../../../enums/menuTypes';
10
+ import { useClickOutside } from '../../../hooks/useClickOutside';
11
+
12
+ /** @typedef {import('@utahdts/utah-design-system').WebsiteMainMenu} WebsiteMainMenu */
13
+ /** @typedef {import('@utahdts/utah-design-system').WebsiteMainMenuItem} WebsiteMainMenuItem */
14
+ /** @typedef {import('@utahdts/utah-design-system').MenuTypes} MenuTypes */
15
+
16
+ /**
17
+ * @param {object} props
18
+ * @param {WebsiteMainMenu | WebsiteMainMenuItem} [props.currentMenuItem]
19
+ * @param {WebsiteMainMenuItem} props.menuItem
20
+ * @param {MenuTypes} [props.menuType]
21
+ * @returns {import('react').JSX.Element}
22
+ */
23
+ export function MenuItemFlyout({
24
+ currentMenuItem,
25
+ menuItem,
26
+ menuType,
27
+ }) {
28
+ const [isChildrenOpen, setIsChildrenOpen] = useImmer(false);
29
+ const referenceElement = useRef(/** @type {HTMLLIElement | null} */(null));
30
+ const popperRef = useRef(/** @type {HTMLDivElement | null} */(null));
31
+ const { styles, attributes } = usePopper(referenceElement.current, popperRef.current, { placement: 'right-start' });
32
+
33
+ const navLinkRef = useRef(/** @type {HTMLAnchorElement | null} */(null));
34
+ useClickOutside(popperRef, () => setIsChildrenOpen(false), !isChildrenOpen);
35
+
36
+ return (
37
+ <li className={menuType === menuTypes.VERTICAL ? 'vertical-menu__item' : 'menu-item'} ref={referenceElement}>
38
+ <span className={menuType === menuTypes.VERTICAL ? 'vertical-menu__title' : 'menu-item__title'}>
39
+ {
40
+ (!menuItem?.link || menuItem?.link?.includes('::'))
41
+ ? (
42
+ <button
43
+ aria-expanded={isChildrenOpen ? 'true' : 'false'}
44
+ aria-controls={`menu-item-${menuItem.id}-${menuItem.link}-popup`}
45
+ aria-haspopup="dialog"
46
+ className="menu-item__button-title"
47
+ id={`menu-item-${menuItem.id}-${menuItem.link}`}
48
+ onClick={() => setIsChildrenOpen((previouslyOpen) => !previouslyOpen)}
49
+ type="button"
50
+ title={menuItem.children ? 'Expand sub-menu' : ''}
51
+ >
52
+ <span className={menuType === menuTypes.VERTICAL ? 'vertical-menu__link-text' : 'menu__link-text'}>{menuItem.title}</span>
53
+ {menuItem.children ? <span className="utds-icon-before-chevron-right vertical-menu__chevron is-closed" aria-hidden="true" /> : null}
54
+ </button>
55
+ )
56
+ : (
57
+ <NavLink
58
+ className={(navData) => joinClassNames(
59
+ menuType === menuTypes.VERTICAL ? 'vertical-menu__link-title' : 'menu-item__link-title',
60
+ (currentMenuItem?.parentLinks?.includes(menuItem.link ?? '') || navData.isActive)
61
+ && (currentMenuItem?.children?.length ? 'menu-item--selected_parent' : 'menu-item--selected')
62
+ )}
63
+ end
64
+ to={menuItem.link}
65
+ ref={navLinkRef}
66
+ >
67
+ {menuItem.title}
68
+ </NavLink>
69
+ )
70
+ }
71
+ {
72
+ (menuItem.children && menuItem?.link)
73
+ ? (
74
+ <IconButton
75
+ appearance={ICON_BUTTON_APPEARANCE.BORDERLESS}
76
+ aria-labelledby={`menu-item-${menuItem.id}-${menuItem.link}`}
77
+ aria-expanded={isChildrenOpen ? 'true' : 'false'}
78
+ className="menu-item__chevron"
79
+ onClick={() => setIsChildrenOpen((previouslyOpen) => !previouslyOpen)}
80
+ icon={<Icons.IconChevron />}
81
+ title="Expand sub-menu"
82
+ />
83
+ )
84
+ : null
85
+ }
86
+ <span className="menu-chiclet" />
87
+ </span>
88
+ {
89
+ menuItem.children
90
+ ? (
91
+ <div
92
+ aria-labelledby={`menu-item-${menuItem.id}-${menuItem.link}`}
93
+ className={joinClassNames(
94
+ 'popup__wrapper',
95
+ isChildrenOpen ? 'popup__wrapper--visible' : 'popup__wrapper--hidden'
96
+ )}
97
+ id={`menu-item-${menuItem.id}-${menuItem.link}-popup`}
98
+ ref={popperRef}
99
+ style={styles.popper}
100
+ {...attributes.popper}
101
+ >
102
+ <div className="popup__content flyout-menu">
103
+ <ul role="menu" className={menuType === menuTypes.VERTICAL ? 'vertical-menu' : ''}>
104
+ {menuItem.children?.map((menuItemChild) => (
105
+ <MenuItemFlyout
106
+ key={`menu-item__child__${menuItemChild.link}-${menuItemChild.title}}`}
107
+ menuItem={menuItemChild}
108
+ menuType={menuType}
109
+ />
110
+ ))}
111
+ </ul>
112
+ </div>
113
+ </div>
114
+ )
115
+ : null
116
+ }
117
+ </li>
118
+ );
119
+ }
@@ -1,21 +1,28 @@
1
1
  import { useEffect, useLayoutEffect, useRef } from 'react';
2
2
  import { NavLink, useLocation } from 'react-router-dom';
3
3
  import { useImmer } from 'use-immer';
4
- import { ICON_BUTTON_APPEARANCE } from '../../enums/buttonEnums';
5
- import { joinClassNames } from '../../util/joinClassNames';
6
- import { IconButton } from '../buttons/IconButton';
7
- import { Icons } from '../icons/Icons';
4
+ import { ICON_BUTTON_APPEARANCE } from '../../../enums/buttonEnums';
5
+ import { joinClassNames } from '../../../util/joinClassNames';
6
+ import { IconButton } from '../../buttons/IconButton';
7
+ import { Icons } from '../../icons/Icons';
8
+ import { menuTypes } from '../../../enums/menuTypes';
8
9
 
9
10
  /** @typedef {import('@utahdts/utah-design-system').WebsiteMainMenu} WebsiteMainMenu */
10
11
  /** @typedef {import('@utahdts/utah-design-system').WebsiteMainMenuItem} WebsiteMainMenuItem */
12
+ /** @typedef {import('@utahdts/utah-design-system').MenuTypes} MenuTypes */
11
13
 
12
14
  /**
13
15
  * @param {object} props
14
16
  * @param {WebsiteMainMenu | WebsiteMainMenuItem} [props.currentMenuItem]
15
17
  * @param {WebsiteMainMenuItem} props.menuItem
18
+ * @param {MenuTypes} [props.menuType]
16
19
  * @returns {import('react').JSX.Element}
17
20
  */
18
- export function MenuItem({ currentMenuItem, menuItem }) {
21
+ export function MenuItemInline({
22
+ currentMenuItem,
23
+ menuItem,
24
+ menuType = menuTypes.VERTICAL,
25
+ }) {
19
26
  const { pathname } = useLocation();
20
27
  // check if any of this menuItem's children are the currently open page/menuItem and if so, then keep this menuItem's children list open
21
28
  const [isChildrenOpen, setIsChildrenOpen] = useImmer(() => (
@@ -49,7 +56,7 @@ export function MenuItem({ currentMenuItem, menuItem }) {
49
56
  }
50
57
 
51
58
  return (
52
- <li className="menu-item">
59
+ <li className={menuType === menuTypes.VERTICAL ? 'vertical-menu__item' : 'menu-item'}>
53
60
  <span className="menu-item__title">
54
61
  {/* === menu item title === */}
55
62
  {
@@ -68,6 +75,7 @@ export function MenuItem({ currentMenuItem, menuItem }) {
68
75
  : (
69
76
  <NavLink
70
77
  className={(navData) => joinClassNames(
78
+ 'menu-item__link-title',
71
79
  (currentMenuItem?.parentLinks?.includes(menuItem.link ?? '') || navData.isActive)
72
80
  && (currentMenuItem?.children?.length ? 'menu-item--selected_parent' : 'menu-item--selected')
73
81
  )}
@@ -106,11 +114,19 @@ export function MenuItem({ currentMenuItem, menuItem }) {
106
114
  {
107
115
  menuItem.children
108
116
  ? (
109
- <ul className={joinClassNames('menu-item__sub-menu', isChildrenOpen ? 'menu-item__sub-menu--open' : '')} role="menu">
117
+ <ul
118
+ role="menu"
119
+ className={joinClassNames(
120
+ 'menu-item__sub-menu',
121
+ menuType === menuTypes.VERTICAL ? 'vertical-menu' : '',
122
+ isChildrenOpen ? 'menu-item__sub-menu--open' : ''
123
+ )}
124
+ >
110
125
  {menuItem.children?.map((menuItemChild) => (
111
- <MenuItem
126
+ <MenuItemInline
112
127
  key={`menu-item__child__${menuItemChild.link}-${menuItemChild.title}}`}
113
128
  menuItem={menuItemChild}
129
+ menuType={menuType}
114
130
  />
115
131
  ))}
116
132
  </ul>
@@ -0,0 +1,63 @@
1
+ import { NavLink } from 'react-router-dom';
2
+ import { useRef } from 'react';
3
+ import { joinClassNames } from '../../../util/joinClassNames';
4
+ import { menuTypes } from '../../../enums/menuTypes';
5
+
6
+ /** @typedef {import('@utahdts/utah-design-system').WebsiteMainMenu} WebsiteMainMenu */
7
+ /** @typedef {import('@utahdts/utah-design-system').WebsiteMainMenuItem} WebsiteMainMenuItem */
8
+ /** @typedef {import('@utahdts/utah-design-system').MenuTypes} MenuTypes */
9
+
10
+ /**
11
+ * @param {object} props
12
+ * @param {WebsiteMainMenu | WebsiteMainMenuItem} [props.currentMenuItem]
13
+ * @param {WebsiteMainMenuItem} props.menuItem
14
+ * @param {MenuTypes} [props.menuType]
15
+ * @returns {import('react').JSX.Element}
16
+ */
17
+ export function MenuItemPlain({
18
+ currentMenuItem,
19
+ menuItem,
20
+ menuType,
21
+ }) {
22
+ const navLinkRef = useRef(/** @type {HTMLAnchorElement | null} */(null));
23
+ return (
24
+ <li className={menuType === menuTypes.VERTICAL ? 'vertical-menu__item' : 'menu-item'}>
25
+ <span className={menuType === menuTypes.VERTICAL ? 'vertical-menu__title' : 'menu-item__title'}>
26
+ {
27
+ (!menuItem?.link || menuItem?.link?.includes('::'))
28
+ ? <span className={menuType === menuTypes.VERTICAL ? 'vertical-menu__title__plain' : 'menu-item__title__plain'}>{menuItem.title}</span>
29
+ : (
30
+ <NavLink
31
+ className={(navData) => joinClassNames(
32
+ menuType === menuTypes.VERTICAL ? 'vertical-menu__link-title' : 'menu-item__link-title',
33
+ (currentMenuItem?.parentLinks?.includes(menuItem.link ?? '') || navData.isActive)
34
+ && (currentMenuItem?.children?.length ? 'menu-item--selected_parent' : 'menu-item--selected')
35
+ )}
36
+ end
37
+ to={menuItem.link}
38
+ ref={navLinkRef}
39
+ >
40
+ {menuItem.title}
41
+ </NavLink>
42
+ )
43
+ }
44
+ <span className="menu-chiclet" />
45
+ </span>
46
+ {
47
+ menuItem.children?.length
48
+ ? (
49
+ <ul role="menu" className={menuType === menuTypes.VERTICAL ? 'vertical-menu' : ''}>
50
+ {menuItem.children.map((menuItemChild) => (
51
+ <MenuItemPlain
52
+ key={`menu-item__child__${menuItemChild.link}-${menuItemChild.title}}`}
53
+ menuItem={menuItemChild}
54
+ menuType={menuType}
55
+ />
56
+ ))}
57
+ </ul>
58
+ )
59
+ : null
60
+ }
61
+ </li>
62
+ );
63
+ }
@@ -0,0 +1,7 @@
1
+ /** @typedef {import('@utahdts/utah-design-system').MenuTypes} MenuTypes */
2
+
3
+ /** @enum {MenuTypes} */
4
+ export const menuTypes = {
5
+ VERTICAL: /** @type {MenuTypes} */ ('vertical'),
6
+ HORIZONTAL: /** @type {MenuTypes} */ ('horizontal'),
7
+ };
@@ -30,7 +30,7 @@ export function useDebounceFunc(func, delay = 1000) {
30
30
  clearTimeout(timeoutRef.current);
31
31
  timeoutRef.current = window.setTimeout(
32
32
  () => {
33
- func(lastVarArgsRef.current);
33
+ func(...(lastVarArgsRef.current || []));
34
34
  lastVarArgsRef.current = null;
35
35
  lastInvocationRef.current = NaN;
36
36
  resolve(varArgs);
@@ -3,7 +3,7 @@ import { useEffect, useRef, useState } from 'react';
3
3
  /**
4
4
  * @template KeyboardEventHandlerT
5
5
  * @param {object} params
6
- * @param {string} params.whichKeyCode
6
+ * @param {string} params.whichKeyCode https://www.w3.org/TR/uievents-key/#named-key-attribute-values
7
7
  * @param {import('react').KeyboardEventHandler<KeyboardEventHandlerT>} [params.onKeyDown]
8
8
  * @param {import('react').KeyboardEventHandler<KeyboardEventHandlerT>} [params.onKeyUp]
9
9
  * @returns {boolean}