@mackin.com/styleguide 11.0.0 → 11.0.2

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 (4) hide show
  1. package/index.d.ts +26 -17
  2. package/index.esm.js +103 -49
  3. package/index.js +103 -49
  4. package/package.json +1 -1
package/index.d.ts CHANGED
@@ -4,6 +4,8 @@ import { IconDefinition } from '@fortawesome/fontawesome-svg-core';
4
4
 
5
5
  type HeaderVariant = 'label' | 'link' | 'primary' | 'secondary' | 'omg' | 'primary2' | 'positive' | 'negative';
6
6
  interface AccordianProps {
7
+ /** Required for ARIA. */
8
+ id: string;
7
9
  header: JSX.Element | string;
8
10
  children: React.ReactNode;
9
11
  variant?: HeaderVariant;
@@ -149,6 +151,7 @@ interface CheckboxProps extends Omit<React.DetailedHTMLProps<React.InputHTMLAttr
149
151
  declare const Checkbox: (props: CheckboxProps) => React.JSX.Element;
150
152
 
151
153
  interface ConfirmModalProps {
154
+ id: string;
152
155
  show: boolean;
153
156
  text: string;
154
157
  header: string;
@@ -158,7 +161,6 @@ interface ConfirmModalProps {
158
161
  cancelText?: string;
159
162
  className?: string;
160
163
  variant?: 'omg';
161
- id?: string;
162
164
  __debug?: boolean;
163
165
  }
164
166
  declare const ConfirmModal: (props: ConfirmModalProps) => React.JSX.Element;
@@ -174,9 +176,9 @@ declare const Divider: (p: React.DetailedHTMLProps<React.HTMLAttributes<HTMLHREl
174
176
  declare const ErrorModal: (props: {
175
177
  message: string;
176
178
  show: boolean;
177
- id?: string;
179
+ id: string;
178
180
  __debug?: boolean;
179
- close: () => void;
181
+ onClose: () => void;
180
182
  }) => React.JSX.Element;
181
183
 
182
184
  interface FileUploaderProps {
@@ -291,18 +293,18 @@ interface InfoTipProps {
291
293
  bgColor?: string;
292
294
  /** Defaults to nav font color. */
293
295
  fontColor?: string;
294
- /** For variant=modal only. */
295
- modalHeader?: string;
296
- /** For variant=modal only. */
297
- modalId?: string;
298
- /** For variant=modal only. */
299
- __modalDebug?: boolean;
296
+ /** For variant=modal only. The InfoTip will not render if `variant=modal` and this is missing. */
297
+ modalProps?: {
298
+ heading: string;
299
+ id: string;
300
+ __debug?: boolean;
301
+ };
300
302
  /** Whether to move the popover on collision with the parent's bounds. Default is false. For variant=info only. */
301
303
  reposition?: boolean;
302
304
  /** Order of positions as the Popover colides with the window edge. The default order is ['right', 'top', 'left', 'bottom']. For variant=info only. */
303
305
  positions?: ("bottom" | "left" | "right" | "top")[] | undefined;
304
306
  }
305
- declare const InfoTip: (props: InfoTipProps) => React.JSX.Element;
307
+ declare const InfoTip: (props: InfoTipProps) => React.JSX.Element | null;
306
308
 
307
309
  interface BaseInputProps extends React.DetailedHTMLProps<React.InputHTMLAttributes<HTMLInputElement>, HTMLInputElement> {
308
310
  rightControl?: JSX.Element;
@@ -401,30 +403,34 @@ interface ListItemProps extends React.DetailedHTMLProps<React.LiHTMLAttributes<H
401
403
  declare const ListItem: (props: ListItemProps) => React.JSX.Element;
402
404
 
403
405
  interface ModalProps {
406
+ /** Required for various ARIA support choices. */
407
+ id: string;
404
408
  show: boolean;
405
409
  children: React__default.ReactNode;
410
+ heading: string;
406
411
  /** Defaults to theme.breakpoints.tablet. */
407
412
  maxWidth?: string;
408
413
  minWidth?: string;
409
- /** Will not apply a background color to the modal body. */
410
- noBackground?: boolean;
411
- closeButton?: boolean;
412
- heading?: string;
413
414
  /** Use to override default heading styling. */
414
415
  headerClassName?: string;
415
- /** Selector of the element to focus on initial show. */
416
+ /** Selector of the element to focus on initial show. Will default to the close button. */
416
417
  focusSelector?: string;
417
418
  scrollable?: boolean;
418
- id?: string;
419
419
  /** Applied to the modal body. */
420
420
  className?: string;
421
421
  __debug?: boolean;
422
- onClick?: () => void;
422
+ /** Only used for the `WaitingIndicator`. This will break accessiblity so do not use. */
423
+ __noHeader?: boolean;
424
+ onClose: () => void;
423
425
  }
424
426
  declare const Modal: (p: ModalProps) => React__default.ReactPortal | null;
425
427
 
426
428
  declare const Nav: (props: {
427
429
  show: boolean;
430
+ /** Required for ARIA. The element to focus when first opened. */
431
+ focusSelector: string;
432
+ /** Required for ARIA. Description of the `Nav` purpose. */
433
+ ariaLabel: string;
428
434
  toggle: (show: boolean) => void;
429
435
  id?: string;
430
436
  children?: React.ReactNode;
@@ -950,6 +956,7 @@ declare const TabLocker: (props: {
950
956
  disabled?: boolean;
951
957
  children?: React.ReactNode;
952
958
  style?: React.CSSProperties;
959
+ className?: string;
953
960
  }) => React.JSX.Element;
954
961
 
955
962
  type SupportedTags = 'p' | 'h1' | 'h2' | 'h3' | 'h4' | 'span' | 'div';
@@ -975,11 +982,13 @@ interface TextProps {
975
982
  /** Will remove all margin/padding from specified tag */
976
983
  noPad?: boolean;
977
984
  leftPad?: string;
985
+ id?: string;
978
986
  }
979
987
  /** Wraps common needs for displaying text. Use for all text-containing elements to save on duplicated styling. */
980
988
  declare const Text: (props: TextProps) => React.DetailedReactHTMLElement<{
981
989
  style: React.CSSProperties;
982
990
  className: string;
991
+ id: string | undefined;
983
992
  }, HTMLElement>;
984
993
 
985
994
  type BaseProps = Omit<React.DetailedHTMLProps<React.TextareaHTMLAttributes<HTMLTextAreaElement>, HTMLTextAreaElement>, 'value'> & InputOnFocusProps;
package/index.esm.js CHANGED
@@ -288,11 +288,11 @@ const Text = (props) => {
288
288
  });
289
289
  return React.createElement(tagChoice, {
290
290
  style: style,
291
- className: cx('text', styles, props.className)
291
+ className: cx('text', styles, props.className),
292
+ id: props.id
292
293
  }, props.children);
293
294
  };
294
295
 
295
- //TB: FUTURE de-dup these styles. create individual styles and compose them manually.
296
296
  const Button = React.forwardRef((props, ref) => {
297
297
  var _a;
298
298
  const { variant, round, rightIcon, leftIcon, iconBlock, small, readOnly, waiting, enforceMinWidth, controlAlign } = props, nativeProps = __rest(props, ["variant", "round", "rightIcon", "leftIcon", "iconBlock", "small", "readOnly", "waiting", "enforceMinWidth", "controlAlign"]);
@@ -551,26 +551,33 @@ const Accordian = (props) => {
551
551
  }
552
552
  setOpen((_a = props.open) !== null && _a !== void 0 ? _a : false);
553
553
  }, [props.open]);
554
- return (React.createElement("div", { className: "accordian", "aria-expanded": open },
555
- React.createElement(Button, { readOnly: props.disabled, variant: props.variant, className: cx(css({
556
- display: 'flex',
557
- alignItems: 'center',
558
- justifyContent: 'space-between',
559
- height: 'auto',
560
- minHeight: theme.controls.height,
561
- width: ((_d = props.block) !== null && _d !== void 0 ? _d : true) ? '100%' : 'auto'
562
- }, props.className)), onClick: e => {
563
- e.stopPropagation();
564
- if (props.onChange) {
565
- props.onChange(!open);
566
- }
567
- else {
568
- setOpen(!open);
569
- }
570
- }, rightIcon: !props.disabled ? React.createElement(Icon, { id: open ? 'collapse' : 'expand' }) : undefined },
571
- React.createElement("span", null, props.header)),
554
+ const expandedPanelId = `${props.id}_expanded`;
555
+ return (React.createElement("div", { id: props.id, className: "accordian" },
556
+ React.createElement("h3", { className: css({
557
+ // required for ARIA
558
+ margin: 0,
559
+ padding: 0,
560
+ fontSize: 'inherit'
561
+ }) },
562
+ React.createElement(Button, { "aria-controls": expandedPanelId, "aria-expanded": open, "aria-disabled": props.disabled, readOnly: props.disabled, variant: props.variant, className: cx(css({
563
+ display: 'flex',
564
+ alignItems: 'center',
565
+ justifyContent: 'space-between',
566
+ height: 'auto',
567
+ minHeight: theme.controls.height,
568
+ width: ((_d = props.block) !== null && _d !== void 0 ? _d : true) ? '100%' : 'auto'
569
+ }, props.className)), onClick: e => {
570
+ e.stopPropagation();
571
+ if (props.onChange) {
572
+ props.onChange(!open);
573
+ }
574
+ else {
575
+ setOpen(!open);
576
+ }
577
+ }, rightIcon: !props.disabled ? React.createElement(Icon, { id: open ? 'collapse' : 'expand' }) : undefined },
578
+ React.createElement("span", null, props.header))),
572
579
  React.createElement("div", { ref: content, className: cx('accordian__body', contentStyles) },
573
- React.createElement("div", { className: expandedContentWrapperStyles }, children))));
580
+ React.createElement("div", { className: expandedContentWrapperStyles, id: expandedPanelId }, children))));
574
581
  };
575
582
  const useAccordianState = (count, openIndex) => {
576
583
  const [panels, setShowPanel] = React.useState(new Array(count).fill(false).map((b, i) => {
@@ -943,7 +950,7 @@ const ListItem = (props) => {
943
950
 
944
951
  const TabLocker = (props) => {
945
952
  const tabLocker = React.useRef(null);
946
- return (React.createElement("div", { className: "tabLocker", style: props.style, ref: tabLocker, onKeyDown: e => {
953
+ return (React.createElement("div", { className: cx('tabLocker', props.className), style: props.style, ref: tabLocker, onKeyDown: e => {
947
954
  var _a, _b;
948
955
  if (props.disabled) {
949
956
  return;
@@ -983,7 +990,7 @@ const defaultOnPickFocusMs = 100;
983
990
  const Autocomplete = (p) => {
984
991
  var _a;
985
992
  // eslint-disable-next-line @typescript-eslint/no-unused-vars
986
- const inputProps = __rest(p, ["value", "className", "inputWrapperClassName", "inputClassName", "listClassName", "listItemClassName", "maxShownValues", "allowScroll", "options", "onPick", "onPickFocusWaitMs"]);
993
+ const inputProps = __rest(p, ["value", "className", "inputWrapperClassName", "inputClassName", "listClassName", "listItemClassName", "maxShownValues", "allowScroll", "options", "onPick", "onPickFocusWaitMs", "ariaLabel"]);
987
994
  const theme = useThemeSafely();
988
995
  const element = React.useRef(null);
989
996
  const input = React.useRef(null);
@@ -1656,7 +1663,7 @@ const Modal = (p) => {
1656
1663
  const backdrop = useBackdropContext();
1657
1664
  const mouseDownElement = useRef(undefined);
1658
1665
  const theme = useThemeSafely();
1659
- const hasHeader = p.closeButton || p.heading;
1666
+ const hasHeader = !p.__noHeader;
1660
1667
  const contentRef = React__default.useRef(null);
1661
1668
  const log = useLogger((_a = p.id) !== null && _a !== void 0 ? _a : 'Modal', (_b = p.__debug) !== null && _b !== void 0 ? _b : false);
1662
1669
  const showing = useRef(p.show);
@@ -1695,10 +1702,22 @@ const Modal = (p) => {
1695
1702
  }
1696
1703
  };
1697
1704
  const zIndex = theme.zIndexes.modal;
1705
+ const { getText, language } = useLocalization();
1706
+ const closeText = React__default.useMemo(() => {
1707
+ return getText('Close');
1708
+ }, [language]);
1698
1709
  useEffect(() => {
1699
1710
  log('mounted');
1711
+ const escapeRemover = createBodyEscapeHandler(() => {
1712
+ if (showing.current) {
1713
+ p.onClose();
1714
+ }
1715
+ });
1716
+ log('body escape handler created');
1700
1717
  return () => {
1701
1718
  var _a;
1719
+ escapeRemover();
1720
+ log('body escape handler removed');
1702
1721
  if (showing.current) {
1703
1722
  log(`un-mount in progress and this modal is showing. decrement the backdrop and try to remove singleton body styles.`);
1704
1723
  backdrop.setShow(false, { key: (_a = p.id) !== null && _a !== void 0 ? _a : 'Modal', zIndex });
@@ -1745,9 +1764,9 @@ const Modal = (p) => {
1745
1764
  zIndex,
1746
1765
  cursor: 'default',
1747
1766
  margin: '1rem',
1748
- backgroundColor: p.noBackground ? undefined : theme.colors.modalBg,
1749
- border: p.noBackground ? undefined : theme.controls.border,
1750
- boxShadow: p.noBackground ? undefined : theme.controls.boxShadow,
1767
+ backgroundColor: !hasHeader ? undefined : theme.colors.modalBg,
1768
+ border: !hasHeader ? undefined : theme.controls.border,
1769
+ boxShadow: !hasHeader ? undefined : theme.controls.boxShadow,
1751
1770
  maxWidth: (_c = p.maxWidth) !== null && _c !== void 0 ? _c : theme.breakpoints.tablet,
1752
1771
  minWidth: (_d = p.minWidth) !== null && _d !== void 0 ? _d : (hasHeader ? '250px' : undefined),
1753
1772
  opacity: p.show ? 1 : 0,
@@ -1774,26 +1793,30 @@ const Modal = (p) => {
1774
1793
  display: 'flex',
1775
1794
  justifyContent: 'center',
1776
1795
  alignItems: 'center',
1777
- cursor: p.onClick ? 'pointer' : 'default'
1796
+ cursor: 'pointer'
1778
1797
  }, p.scrollable && {
1779
1798
  overflowY: 'auto',
1780
1799
  overflowX: 'hidden',
1781
1800
  alignItems: 'flex-start'
1782
1801
  }]);
1802
+ const ariaLabelId = `${p.id}_label`;
1783
1803
  if (p.show) {
1784
1804
  const backdropContainer = document.getElementById(backdrop.portalId);
1785
1805
  if (backdropContainer) {
1786
- return createPortal((React__default.createElement("div", { onClick: e => {
1806
+ return createPortal((React__default.createElement("div", { id: p.id, role: "dialog", "aria-modal": "true", "aria-labelledby": ariaLabelId, onClick: e => {
1787
1807
  e.stopPropagation();
1788
1808
  if (!mouseDownElement.current) {
1789
- if (p.onClick) {
1790
- log('backdropContainer onClick');
1791
- p.onClick();
1792
- }
1809
+ log('backdropContainer onClick');
1810
+ p.onClose();
1793
1811
  }
1794
1812
  mouseDownElement.current = undefined;
1813
+ }, onKeyDown: e => {
1814
+ if (e.code === 'Escape') {
1815
+ e.stopPropagation();
1816
+ p.onClose();
1817
+ }
1795
1818
  }, className: cx('modalContainer', modalContainerStyles) },
1796
- React__default.createElement("div", { id: p.id, ref: contentRef, onClick: e => e.stopPropagation(), onMouseDown: e => {
1819
+ React__default.createElement("div", { ref: contentRef, onClick: e => e.stopPropagation(), onMouseDown: e => {
1797
1820
  mouseDownElement.current = e.target;
1798
1821
  e.stopPropagation();
1799
1822
  }, onMouseUp: () => {
@@ -1807,21 +1830,35 @@ const Modal = (p) => {
1807
1830
  }, className: cx('modalBody', modalBodyStyles, p.className) },
1808
1831
  React__default.createElement(TabLocker, null,
1809
1832
  hasHeader && (React__default.createElement("header", { className: cx('modalHeader', modalHeaderStyles) },
1810
- p.heading ? React__default.createElement(Text, { className: css({
1833
+ React__default.createElement(Text, { id: ariaLabelId, className: css({
1811
1834
  margin: 0,
1812
1835
  flexGrow: 1
1813
- }), tag: "h1", bold: true }, p.heading) : React__default.createElement("span", null),
1814
- p.closeButton && p.onClick ? React__default.createElement(Button, { className: cx('modalCloseButton', css({
1836
+ }), tag: "h1", bold: true }, p.heading),
1837
+ React__default.createElement(Button, { className: cx('modalCloseButton', css({
1815
1838
  color: theme.colors.headerFont,
1816
1839
  marginLeft: '1rem',
1817
1840
  backgroundColor: 'transparent'
1818
- })), "aria-label": "Close", variant: "icon", onClick: p.onClick },
1819
- React__default.createElement(Icon, { id: "close" })) : React__default.createElement("span", null))),
1841
+ })), "aria-label": closeText, variant: "icon", onClick: e => {
1842
+ e.stopPropagation();
1843
+ p.onClose();
1844
+ } },
1845
+ React__default.createElement(Icon, { id: "close" })))),
1820
1846
  p.children)))), backdropContainer);
1821
1847
  }
1822
1848
  }
1823
1849
  return null;
1824
1850
  };
1851
+ function createBodyEscapeHandler(onEscape) {
1852
+ const handler = (e) => {
1853
+ if (e.code === 'Escape') {
1854
+ onEscape();
1855
+ }
1856
+ };
1857
+ document.addEventListener('keydown', handler);
1858
+ return () => {
1859
+ document.removeEventListener('keydown', handler);
1860
+ };
1861
+ }
1825
1862
 
1826
1863
  const ConfirmModal = (props) => {
1827
1864
  const theme = useThemeSafely();
@@ -1839,7 +1876,7 @@ const ConfirmModal = (props) => {
1839
1876
  const cancelText = props.cancelText || getText('Cancel');
1840
1877
  return { confirmText, cancelText };
1841
1878
  }, [language, props.confirmText, props.cancelText]);
1842
- return (React.createElement(Modal, { id: props.id, __debug: props.__debug, className: cx('confirmModal', modalStyle, props.className), heading: props.header, closeButton: true, show: props.show, onClick: props.onCancel },
1879
+ return (React.createElement(Modal, { id: props.id, __debug: props.__debug, className: cx('confirmModal', modalStyle, props.className), heading: props.header, show: props.show, onClose: props.onCancel },
1843
1880
  React.createElement("div", { className: css({ padding: '1rem' }) },
1844
1881
  React.createElement(Text, { align: "center" }, props.text),
1845
1882
  React.createElement("div", { className: css({ textAlign: 'center' }) },
@@ -1903,7 +1940,7 @@ const ErrorModal = (props) => {
1903
1940
  color: ${theme.colors.omgFont};
1904
1941
  }
1905
1942
  `;
1906
- return (React.createElement(Modal, { id: props.id, __debug: props.__debug, className: cx('errorModal', modalStyles), heading: heading, closeButton: true, show: props.show, onClick: props.close },
1943
+ return (React.createElement(Modal, { id: props.id, __debug: props.__debug, className: cx('errorModal', modalStyles), heading: heading, show: props.show, onClose: props.onClose },
1907
1944
  React.createElement("div", { className: css({ padding: '1rem' }) },
1908
1945
  React.createElement(Text, { align: "center" }, message))));
1909
1946
  };
@@ -2375,6 +2412,10 @@ const InfoTip = (props) => {
2375
2412
  var _a, _b, _c;
2376
2413
  const [showTip, setShowTip] = React.useState(false);
2377
2414
  const theme = useThemeSafely();
2415
+ if (props.variant === 'modal' && !props.modalProps) {
2416
+ console.warn(`InfoTip with variant=modal requires modalProps.`);
2417
+ return null;
2418
+ }
2378
2419
  const bgColor = (_a = props.bgColor) !== null && _a !== void 0 ? _a : theme.colors.nav;
2379
2420
  const fontColor = (_b = props.fontColor) !== null && _b !== void 0 ? _b : theme.colors.navFont;
2380
2421
  const onClick = () => {
@@ -2431,12 +2472,12 @@ const InfoTip = (props) => {
2431
2472
  display:inline-block;
2432
2473
  `;
2433
2474
  const button = React.createElement(Button, { className: buttonStyles, disabled: props.disabled, variant: 'circle', tabIndex: props.tabIndex, onClick: onClick, onMouseEnter: onMouseOver, onMouseLeave: onMouseOut }, "i");
2434
- if (props.variant === 'modal') {
2475
+ if (props.variant === 'modal' && props.modalProps) {
2435
2476
  return (React.createElement(React.Fragment, null,
2436
2477
  button,
2437
- React.createElement(Modal, { id: props.modalId, __debug: props.__modalDebug, show: showTip, heading: props.modalHeader, onClick: closeTip, className: css({
2478
+ React.createElement(Modal, { id: props.modalProps.id, __debug: props.modalProps.__debug, show: showTip, heading: props.modalProps.heading, onClose: closeTip, className: css({
2438
2479
  whiteSpace: 'normal'
2439
- }), closeButton: true },
2480
+ }) },
2440
2481
  React.createElement("div", { className: css({ padding: '1rem' }) }, props.content))));
2441
2482
  }
2442
2483
  else {
@@ -2842,6 +2883,7 @@ const Nav = (props) => {
2842
2883
  padding-top:0;
2843
2884
  `;
2844
2885
  React.useEffect(() => {
2886
+ console.log('useEffect');
2845
2887
  if (!backdrop.showing) {
2846
2888
  props.toggle(false);
2847
2889
  }
@@ -2852,10 +2894,16 @@ const Nav = (props) => {
2852
2894
  backdrop.setShow(current, { key: (_a = props.id) !== null && _a !== void 0 ? _a : 'Nav', zIndex });
2853
2895
  }, props.show);
2854
2896
  React.useLayoutEffect(() => {
2897
+ console.log('useLayoutEffect');
2855
2898
  if (nav && nav.current) {
2856
2899
  if (props.show) {
2857
2900
  if (!nav.current.classList.contains(classNavShowing)) {
2858
2901
  nav.current.classList.add(classNavShowing);
2902
+ setTimeout(() => {
2903
+ var _a;
2904
+ (_a = document.querySelector(props.focusSelector)) === null || _a === void 0 ? void 0 : _a.focus();
2905
+ console.log(props.focusSelector, document.querySelector(props.focusSelector), 'focused');
2906
+ }, slideMs + 1);
2859
2907
  }
2860
2908
  }
2861
2909
  else {
@@ -2871,7 +2919,12 @@ const Nav = (props) => {
2871
2919
  }
2872
2920
  }
2873
2921
  });
2874
- return (React.createElement("nav", { ref: nav, className: cx('nav', navStyles, props.className) }, props.children));
2922
+ return (React.createElement("nav", { role: "dialog", "aria-modal": "true", "aria-label": props.ariaLabel, ref: nav, className: cx('nav', navStyles, props.className), onKeyDown: e => {
2923
+ if (e.code === 'Escape') {
2924
+ props.toggle(false);
2925
+ }
2926
+ } },
2927
+ React.createElement(TabLocker, { className: css({ height: '100%' }) }, props.children)));
2875
2928
  };
2876
2929
 
2877
2930
  const LinkContent = (props) => {
@@ -2892,7 +2945,6 @@ const LinkContent = (props) => {
2892
2945
  }) }, props.rightIcon)));
2893
2946
  };
2894
2947
 
2895
- //TB: FUTURE de-dup these styles. create individual styles and compose them manually.
2896
2948
  const generateLinkStyles = (props, theme) => {
2897
2949
  const disabled = props.disabled || props.waiting;
2898
2950
  let color = props.colorOverride;
@@ -4261,7 +4313,6 @@ const TextArea = React.forwardRef((props, ref) => {
4261
4313
  }
4262
4314
  else {
4263
4315
  if (reportValueOnError) {
4264
- //TB: temp, add a custom list of validators that will be run for all inputs if a pattern cannot be decided.
4265
4316
  onValueChange(localValue);
4266
4317
  }
4267
4318
  else {
@@ -4426,7 +4477,7 @@ const TogglePasswordInput = React.forwardRef((props, ref) => {
4426
4477
  });
4427
4478
 
4428
4479
  const WaitingIndicator = (p) => {
4429
- var _a, _b;
4480
+ var _a, _b, _c;
4430
4481
  const [show, setShow] = useState(p.show);
4431
4482
  const hideTimer = useRef(0);
4432
4483
  const lastShowStatus = useRef(false);
@@ -4478,7 +4529,10 @@ const WaitingIndicator = (p) => {
4478
4529
  }
4479
4530
  }
4480
4531
  }, [p.show]);
4481
- return (React__default.createElement(Modal, { id: p.id, __debug: p.__debug, className: "waitingIndicator", show: show, noBackground: true },
4532
+ const id = (_c = p.id) !== null && _c !== void 0 ? _c : 'WaitingIndicator';
4533
+ return (React__default.createElement(Modal, { id: id, __debug: p.__debug, __noHeader: true, heading: '', onClose: () => {
4534
+ /* noop */
4535
+ }, className: "waitingIndicator", show: show },
4482
4536
  React__default.createElement("div", { className: css({
4483
4537
  color: 'white',
4484
4538
  fontSize: '3rem',
package/index.js CHANGED
@@ -306,11 +306,11 @@ const Text = (props) => {
306
306
  });
307
307
  return React__namespace.createElement(tagChoice, {
308
308
  style: style,
309
- className: css.cx('text', styles, props.className)
309
+ className: css.cx('text', styles, props.className),
310
+ id: props.id
310
311
  }, props.children);
311
312
  };
312
313
 
313
- //TB: FUTURE de-dup these styles. create individual styles and compose them manually.
314
314
  const Button = React__namespace.forwardRef((props, ref) => {
315
315
  var _a;
316
316
  const { variant, round, rightIcon, leftIcon, iconBlock, small, readOnly, waiting, enforceMinWidth, controlAlign } = props, nativeProps = __rest(props, ["variant", "round", "rightIcon", "leftIcon", "iconBlock", "small", "readOnly", "waiting", "enforceMinWidth", "controlAlign"]);
@@ -569,26 +569,33 @@ const Accordian = (props) => {
569
569
  }
570
570
  setOpen((_a = props.open) !== null && _a !== void 0 ? _a : false);
571
571
  }, [props.open]);
572
- return (React__namespace.createElement("div", { className: "accordian", "aria-expanded": open },
573
- React__namespace.createElement(Button, { readOnly: props.disabled, variant: props.variant, className: css.cx(css.css({
574
- display: 'flex',
575
- alignItems: 'center',
576
- justifyContent: 'space-between',
577
- height: 'auto',
578
- minHeight: theme.controls.height,
579
- width: ((_d = props.block) !== null && _d !== void 0 ? _d : true) ? '100%' : 'auto'
580
- }, props.className)), onClick: e => {
581
- e.stopPropagation();
582
- if (props.onChange) {
583
- props.onChange(!open);
584
- }
585
- else {
586
- setOpen(!open);
587
- }
588
- }, rightIcon: !props.disabled ? React__namespace.createElement(Icon, { id: open ? 'collapse' : 'expand' }) : undefined },
589
- React__namespace.createElement("span", null, props.header)),
572
+ const expandedPanelId = `${props.id}_expanded`;
573
+ return (React__namespace.createElement("div", { id: props.id, className: "accordian" },
574
+ React__namespace.createElement("h3", { className: css.css({
575
+ // required for ARIA
576
+ margin: 0,
577
+ padding: 0,
578
+ fontSize: 'inherit'
579
+ }) },
580
+ React__namespace.createElement(Button, { "aria-controls": expandedPanelId, "aria-expanded": open, "aria-disabled": props.disabled, readOnly: props.disabled, variant: props.variant, className: css.cx(css.css({
581
+ display: 'flex',
582
+ alignItems: 'center',
583
+ justifyContent: 'space-between',
584
+ height: 'auto',
585
+ minHeight: theme.controls.height,
586
+ width: ((_d = props.block) !== null && _d !== void 0 ? _d : true) ? '100%' : 'auto'
587
+ }, props.className)), onClick: e => {
588
+ e.stopPropagation();
589
+ if (props.onChange) {
590
+ props.onChange(!open);
591
+ }
592
+ else {
593
+ setOpen(!open);
594
+ }
595
+ }, rightIcon: !props.disabled ? React__namespace.createElement(Icon, { id: open ? 'collapse' : 'expand' }) : undefined },
596
+ React__namespace.createElement("span", null, props.header))),
590
597
  React__namespace.createElement("div", { ref: content, className: css.cx('accordian__body', contentStyles) },
591
- React__namespace.createElement("div", { className: expandedContentWrapperStyles }, children))));
598
+ React__namespace.createElement("div", { className: expandedContentWrapperStyles, id: expandedPanelId }, children))));
592
599
  };
593
600
  const useAccordianState = (count, openIndex) => {
594
601
  const [panels, setShowPanel] = React__namespace.useState(new Array(count).fill(false).map((b, i) => {
@@ -961,7 +968,7 @@ const ListItem = (props) => {
961
968
 
962
969
  const TabLocker = (props) => {
963
970
  const tabLocker = React__namespace.useRef(null);
964
- return (React__namespace.createElement("div", { className: "tabLocker", style: props.style, ref: tabLocker, onKeyDown: e => {
971
+ return (React__namespace.createElement("div", { className: css.cx('tabLocker', props.className), style: props.style, ref: tabLocker, onKeyDown: e => {
965
972
  var _a, _b;
966
973
  if (props.disabled) {
967
974
  return;
@@ -1001,7 +1008,7 @@ const defaultOnPickFocusMs = 100;
1001
1008
  const Autocomplete = (p) => {
1002
1009
  var _a;
1003
1010
  // eslint-disable-next-line @typescript-eslint/no-unused-vars
1004
- const inputProps = __rest(p, ["value", "className", "inputWrapperClassName", "inputClassName", "listClassName", "listItemClassName", "maxShownValues", "allowScroll", "options", "onPick", "onPickFocusWaitMs"]);
1011
+ const inputProps = __rest(p, ["value", "className", "inputWrapperClassName", "inputClassName", "listClassName", "listItemClassName", "maxShownValues", "allowScroll", "options", "onPick", "onPickFocusWaitMs", "ariaLabel"]);
1005
1012
  const theme = useThemeSafely();
1006
1013
  const element = React__namespace.useRef(null);
1007
1014
  const input = React__namespace.useRef(null);
@@ -1674,7 +1681,7 @@ const Modal = (p) => {
1674
1681
  const backdrop = useBackdropContext();
1675
1682
  const mouseDownElement = React.useRef(undefined);
1676
1683
  const theme = useThemeSafely();
1677
- const hasHeader = p.closeButton || p.heading;
1684
+ const hasHeader = !p.__noHeader;
1678
1685
  const contentRef = React.useRef(null);
1679
1686
  const log = useLogger((_a = p.id) !== null && _a !== void 0 ? _a : 'Modal', (_b = p.__debug) !== null && _b !== void 0 ? _b : false);
1680
1687
  const showing = React.useRef(p.show);
@@ -1713,10 +1720,22 @@ const Modal = (p) => {
1713
1720
  }
1714
1721
  };
1715
1722
  const zIndex = theme.zIndexes.modal;
1723
+ const { getText, language } = useLocalization();
1724
+ const closeText = React.useMemo(() => {
1725
+ return getText('Close');
1726
+ }, [language]);
1716
1727
  React.useEffect(() => {
1717
1728
  log('mounted');
1729
+ const escapeRemover = createBodyEscapeHandler(() => {
1730
+ if (showing.current) {
1731
+ p.onClose();
1732
+ }
1733
+ });
1734
+ log('body escape handler created');
1718
1735
  return () => {
1719
1736
  var _a;
1737
+ escapeRemover();
1738
+ log('body escape handler removed');
1720
1739
  if (showing.current) {
1721
1740
  log(`un-mount in progress and this modal is showing. decrement the backdrop and try to remove singleton body styles.`);
1722
1741
  backdrop.setShow(false, { key: (_a = p.id) !== null && _a !== void 0 ? _a : 'Modal', zIndex });
@@ -1763,9 +1782,9 @@ const Modal = (p) => {
1763
1782
  zIndex,
1764
1783
  cursor: 'default',
1765
1784
  margin: '1rem',
1766
- backgroundColor: p.noBackground ? undefined : theme.colors.modalBg,
1767
- border: p.noBackground ? undefined : theme.controls.border,
1768
- boxShadow: p.noBackground ? undefined : theme.controls.boxShadow,
1785
+ backgroundColor: !hasHeader ? undefined : theme.colors.modalBg,
1786
+ border: !hasHeader ? undefined : theme.controls.border,
1787
+ boxShadow: !hasHeader ? undefined : theme.controls.boxShadow,
1769
1788
  maxWidth: (_c = p.maxWidth) !== null && _c !== void 0 ? _c : theme.breakpoints.tablet,
1770
1789
  minWidth: (_d = p.minWidth) !== null && _d !== void 0 ? _d : (hasHeader ? '250px' : undefined),
1771
1790
  opacity: p.show ? 1 : 0,
@@ -1792,26 +1811,30 @@ const Modal = (p) => {
1792
1811
  display: 'flex',
1793
1812
  justifyContent: 'center',
1794
1813
  alignItems: 'center',
1795
- cursor: p.onClick ? 'pointer' : 'default'
1814
+ cursor: 'pointer'
1796
1815
  }, p.scrollable && {
1797
1816
  overflowY: 'auto',
1798
1817
  overflowX: 'hidden',
1799
1818
  alignItems: 'flex-start'
1800
1819
  }]);
1820
+ const ariaLabelId = `${p.id}_label`;
1801
1821
  if (p.show) {
1802
1822
  const backdropContainer = document.getElementById(backdrop.portalId);
1803
1823
  if (backdropContainer) {
1804
- return reactDom.createPortal((React.createElement("div", { onClick: e => {
1824
+ return reactDom.createPortal((React.createElement("div", { id: p.id, role: "dialog", "aria-modal": "true", "aria-labelledby": ariaLabelId, onClick: e => {
1805
1825
  e.stopPropagation();
1806
1826
  if (!mouseDownElement.current) {
1807
- if (p.onClick) {
1808
- log('backdropContainer onClick');
1809
- p.onClick();
1810
- }
1827
+ log('backdropContainer onClick');
1828
+ p.onClose();
1811
1829
  }
1812
1830
  mouseDownElement.current = undefined;
1831
+ }, onKeyDown: e => {
1832
+ if (e.code === 'Escape') {
1833
+ e.stopPropagation();
1834
+ p.onClose();
1835
+ }
1813
1836
  }, className: css.cx('modalContainer', modalContainerStyles) },
1814
- React.createElement("div", { id: p.id, ref: contentRef, onClick: e => e.stopPropagation(), onMouseDown: e => {
1837
+ React.createElement("div", { ref: contentRef, onClick: e => e.stopPropagation(), onMouseDown: e => {
1815
1838
  mouseDownElement.current = e.target;
1816
1839
  e.stopPropagation();
1817
1840
  }, onMouseUp: () => {
@@ -1825,21 +1848,35 @@ const Modal = (p) => {
1825
1848
  }, className: css.cx('modalBody', modalBodyStyles, p.className) },
1826
1849
  React.createElement(TabLocker, null,
1827
1850
  hasHeader && (React.createElement("header", { className: css.cx('modalHeader', modalHeaderStyles) },
1828
- p.heading ? React.createElement(Text, { className: css.css({
1851
+ React.createElement(Text, { id: ariaLabelId, className: css.css({
1829
1852
  margin: 0,
1830
1853
  flexGrow: 1
1831
- }), tag: "h1", bold: true }, p.heading) : React.createElement("span", null),
1832
- p.closeButton && p.onClick ? React.createElement(Button, { className: css.cx('modalCloseButton', css.css({
1854
+ }), tag: "h1", bold: true }, p.heading),
1855
+ React.createElement(Button, { className: css.cx('modalCloseButton', css.css({
1833
1856
  color: theme.colors.headerFont,
1834
1857
  marginLeft: '1rem',
1835
1858
  backgroundColor: 'transparent'
1836
- })), "aria-label": "Close", variant: "icon", onClick: p.onClick },
1837
- React.createElement(Icon, { id: "close" })) : React.createElement("span", null))),
1859
+ })), "aria-label": closeText, variant: "icon", onClick: e => {
1860
+ e.stopPropagation();
1861
+ p.onClose();
1862
+ } },
1863
+ React.createElement(Icon, { id: "close" })))),
1838
1864
  p.children)))), backdropContainer);
1839
1865
  }
1840
1866
  }
1841
1867
  return null;
1842
1868
  };
1869
+ function createBodyEscapeHandler(onEscape) {
1870
+ const handler = (e) => {
1871
+ if (e.code === 'Escape') {
1872
+ onEscape();
1873
+ }
1874
+ };
1875
+ document.addEventListener('keydown', handler);
1876
+ return () => {
1877
+ document.removeEventListener('keydown', handler);
1878
+ };
1879
+ }
1843
1880
 
1844
1881
  const ConfirmModal = (props) => {
1845
1882
  const theme = useThemeSafely();
@@ -1857,7 +1894,7 @@ const ConfirmModal = (props) => {
1857
1894
  const cancelText = props.cancelText || getText('Cancel');
1858
1895
  return { confirmText, cancelText };
1859
1896
  }, [language, props.confirmText, props.cancelText]);
1860
- return (React__namespace.createElement(Modal, { id: props.id, __debug: props.__debug, className: css.cx('confirmModal', modalStyle, props.className), heading: props.header, closeButton: true, show: props.show, onClick: props.onCancel },
1897
+ return (React__namespace.createElement(Modal, { id: props.id, __debug: props.__debug, className: css.cx('confirmModal', modalStyle, props.className), heading: props.header, show: props.show, onClose: props.onCancel },
1861
1898
  React__namespace.createElement("div", { className: css.css({ padding: '1rem' }) },
1862
1899
  React__namespace.createElement(Text, { align: "center" }, props.text),
1863
1900
  React__namespace.createElement("div", { className: css.css({ textAlign: 'center' }) },
@@ -1921,7 +1958,7 @@ const ErrorModal = (props) => {
1921
1958
  color: ${theme.colors.omgFont};
1922
1959
  }
1923
1960
  `;
1924
- return (React__namespace.createElement(Modal, { id: props.id, __debug: props.__debug, className: css.cx('errorModal', modalStyles), heading: heading, closeButton: true, show: props.show, onClick: props.close },
1961
+ return (React__namespace.createElement(Modal, { id: props.id, __debug: props.__debug, className: css.cx('errorModal', modalStyles), heading: heading, show: props.show, onClose: props.onClose },
1925
1962
  React__namespace.createElement("div", { className: css.css({ padding: '1rem' }) },
1926
1963
  React__namespace.createElement(Text, { align: "center" }, message))));
1927
1964
  };
@@ -2393,6 +2430,10 @@ const InfoTip = (props) => {
2393
2430
  var _a, _b, _c;
2394
2431
  const [showTip, setShowTip] = React__namespace.useState(false);
2395
2432
  const theme = useThemeSafely();
2433
+ if (props.variant === 'modal' && !props.modalProps) {
2434
+ console.warn(`InfoTip with variant=modal requires modalProps.`);
2435
+ return null;
2436
+ }
2396
2437
  const bgColor = (_a = props.bgColor) !== null && _a !== void 0 ? _a : theme.colors.nav;
2397
2438
  const fontColor = (_b = props.fontColor) !== null && _b !== void 0 ? _b : theme.colors.navFont;
2398
2439
  const onClick = () => {
@@ -2449,12 +2490,12 @@ const InfoTip = (props) => {
2449
2490
  display:inline-block;
2450
2491
  `;
2451
2492
  const button = React__namespace.createElement(Button, { className: buttonStyles, disabled: props.disabled, variant: 'circle', tabIndex: props.tabIndex, onClick: onClick, onMouseEnter: onMouseOver, onMouseLeave: onMouseOut }, "i");
2452
- if (props.variant === 'modal') {
2493
+ if (props.variant === 'modal' && props.modalProps) {
2453
2494
  return (React__namespace.createElement(React__namespace.Fragment, null,
2454
2495
  button,
2455
- React__namespace.createElement(Modal, { id: props.modalId, __debug: props.__modalDebug, show: showTip, heading: props.modalHeader, onClick: closeTip, className: css.css({
2496
+ React__namespace.createElement(Modal, { id: props.modalProps.id, __debug: props.modalProps.__debug, show: showTip, heading: props.modalProps.heading, onClose: closeTip, className: css.css({
2456
2497
  whiteSpace: 'normal'
2457
- }), closeButton: true },
2498
+ }) },
2458
2499
  React__namespace.createElement("div", { className: css.css({ padding: '1rem' }) }, props.content))));
2459
2500
  }
2460
2501
  else {
@@ -2860,6 +2901,7 @@ const Nav = (props) => {
2860
2901
  padding-top:0;
2861
2902
  `;
2862
2903
  React__namespace.useEffect(() => {
2904
+ console.log('useEffect');
2863
2905
  if (!backdrop.showing) {
2864
2906
  props.toggle(false);
2865
2907
  }
@@ -2870,10 +2912,16 @@ const Nav = (props) => {
2870
2912
  backdrop.setShow(current, { key: (_a = props.id) !== null && _a !== void 0 ? _a : 'Nav', zIndex });
2871
2913
  }, props.show);
2872
2914
  React__namespace.useLayoutEffect(() => {
2915
+ console.log('useLayoutEffect');
2873
2916
  if (nav && nav.current) {
2874
2917
  if (props.show) {
2875
2918
  if (!nav.current.classList.contains(classNavShowing)) {
2876
2919
  nav.current.classList.add(classNavShowing);
2920
+ setTimeout(() => {
2921
+ var _a;
2922
+ (_a = document.querySelector(props.focusSelector)) === null || _a === void 0 ? void 0 : _a.focus();
2923
+ console.log(props.focusSelector, document.querySelector(props.focusSelector), 'focused');
2924
+ }, slideMs + 1);
2877
2925
  }
2878
2926
  }
2879
2927
  else {
@@ -2889,7 +2937,12 @@ const Nav = (props) => {
2889
2937
  }
2890
2938
  }
2891
2939
  });
2892
- return (React__namespace.createElement("nav", { ref: nav, className: css.cx('nav', navStyles, props.className) }, props.children));
2940
+ return (React__namespace.createElement("nav", { role: "dialog", "aria-modal": "true", "aria-label": props.ariaLabel, ref: nav, className: css.cx('nav', navStyles, props.className), onKeyDown: e => {
2941
+ if (e.code === 'Escape') {
2942
+ props.toggle(false);
2943
+ }
2944
+ } },
2945
+ React__namespace.createElement(TabLocker, { className: css.css({ height: '100%' }) }, props.children)));
2893
2946
  };
2894
2947
 
2895
2948
  const LinkContent = (props) => {
@@ -2910,7 +2963,6 @@ const LinkContent = (props) => {
2910
2963
  }) }, props.rightIcon)));
2911
2964
  };
2912
2965
 
2913
- //TB: FUTURE de-dup these styles. create individual styles and compose them manually.
2914
2966
  const generateLinkStyles = (props, theme) => {
2915
2967
  const disabled = props.disabled || props.waiting;
2916
2968
  let color = props.colorOverride;
@@ -4279,7 +4331,6 @@ const TextArea = React__namespace.forwardRef((props, ref) => {
4279
4331
  }
4280
4332
  else {
4281
4333
  if (reportValueOnError) {
4282
- //TB: temp, add a custom list of validators that will be run for all inputs if a pattern cannot be decided.
4283
4334
  onValueChange(localValue);
4284
4335
  }
4285
4336
  else {
@@ -4444,7 +4495,7 @@ const TogglePasswordInput = React__namespace.forwardRef((props, ref) => {
4444
4495
  });
4445
4496
 
4446
4497
  const WaitingIndicator = (p) => {
4447
- var _a, _b;
4498
+ var _a, _b, _c;
4448
4499
  const [show, setShow] = React.useState(p.show);
4449
4500
  const hideTimer = React.useRef(0);
4450
4501
  const lastShowStatus = React.useRef(false);
@@ -4496,7 +4547,10 @@ const WaitingIndicator = (p) => {
4496
4547
  }
4497
4548
  }
4498
4549
  }, [p.show]);
4499
- return (React.createElement(Modal, { id: p.id, __debug: p.__debug, className: "waitingIndicator", show: show, noBackground: true },
4550
+ const id = (_c = p.id) !== null && _c !== void 0 ? _c : 'WaitingIndicator';
4551
+ return (React.createElement(Modal, { id: id, __debug: p.__debug, __noHeader: true, heading: '', onClose: () => {
4552
+ /* noop */
4553
+ }, className: "waitingIndicator", show: show },
4500
4554
  React.createElement("div", { className: css.css({
4501
4555
  color: 'white',
4502
4556
  fontSize: '3rem',
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mackin.com/styleguide",
3
- "version": "11.0.0",
3
+ "version": "11.0.2",
4
4
  "description": "",
5
5
  "main": "./index.js",
6
6
  "module": "./index.esm.js",