@mackin.com/styleguide 11.0.1 → 11.0.3

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 +32 -19
  2. package/index.esm.js +134 -50
  3. package/index.js +134 -50
  4. package/package.json +1 -1
package/index.d.ts CHANGED
@@ -151,6 +151,7 @@ interface CheckboxProps extends Omit<React.DetailedHTMLProps<React.InputHTMLAttr
151
151
  declare const Checkbox: (props: CheckboxProps) => React.JSX.Element;
152
152
 
153
153
  interface ConfirmModalProps {
154
+ id: string;
154
155
  show: boolean;
155
156
  text: string;
156
157
  header: string;
@@ -160,7 +161,6 @@ interface ConfirmModalProps {
160
161
  cancelText?: string;
161
162
  className?: string;
162
163
  variant?: 'omg';
163
- id?: string;
164
164
  __debug?: boolean;
165
165
  }
166
166
  declare const ConfirmModal: (props: ConfirmModalProps) => React.JSX.Element;
@@ -176,9 +176,9 @@ declare const Divider: (p: React.DetailedHTMLProps<React.HTMLAttributes<HTMLHREl
176
176
  declare const ErrorModal: (props: {
177
177
  message: string;
178
178
  show: boolean;
179
- id?: string;
179
+ id: string;
180
180
  __debug?: boolean;
181
- close: () => void;
181
+ onClose: () => void;
182
182
  }) => React.JSX.Element;
183
183
 
184
184
  interface FileUploaderProps {
@@ -293,18 +293,18 @@ interface InfoTipProps {
293
293
  bgColor?: string;
294
294
  /** Defaults to nav font color. */
295
295
  fontColor?: string;
296
- /** For variant=modal only. */
297
- modalHeader?: string;
298
- /** For variant=modal only. */
299
- modalId?: string;
300
- /** For variant=modal only. */
301
- __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
+ };
302
302
  /** Whether to move the popover on collision with the parent's bounds. Default is false. For variant=info only. */
303
303
  reposition?: boolean;
304
304
  /** Order of positions as the Popover colides with the window edge. The default order is ['right', 'top', 'left', 'bottom']. For variant=info only. */
305
305
  positions?: ("bottom" | "left" | "right" | "top")[] | undefined;
306
306
  }
307
- declare const InfoTip: (props: InfoTipProps) => React.JSX.Element;
307
+ declare const InfoTip: (props: InfoTipProps) => React.JSX.Element | null;
308
308
 
309
309
  interface BaseInputProps extends React.DetailedHTMLProps<React.InputHTMLAttributes<HTMLInputElement>, HTMLInputElement> {
310
310
  rightControl?: JSX.Element;
@@ -403,30 +403,34 @@ interface ListItemProps extends React.DetailedHTMLProps<React.LiHTMLAttributes<H
403
403
  declare const ListItem: (props: ListItemProps) => React.JSX.Element;
404
404
 
405
405
  interface ModalProps {
406
+ /** Required for various ARIA support choices. */
407
+ id: string;
406
408
  show: boolean;
407
409
  children: React__default.ReactNode;
410
+ heading: string;
408
411
  /** Defaults to theme.breakpoints.tablet. */
409
412
  maxWidth?: string;
410
413
  minWidth?: string;
411
- /** Will not apply a background color to the modal body. */
412
- noBackground?: boolean;
413
- closeButton?: boolean;
414
- heading?: string;
415
414
  /** Use to override default heading styling. */
416
415
  headerClassName?: string;
417
- /** 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. */
418
417
  focusSelector?: string;
419
418
  scrollable?: boolean;
420
- id?: string;
421
419
  /** Applied to the modal body. */
422
420
  className?: string;
423
421
  __debug?: boolean;
424
- onClick?: () => void;
422
+ /** Only used for the `WaitingIndicator`. This will break accessiblity so do not use. */
423
+ __noHeader?: boolean;
424
+ onClose: () => void;
425
425
  }
426
426
  declare const Modal: (p: ModalProps) => React__default.ReactPortal | null;
427
427
 
428
428
  declare const Nav: (props: {
429
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;
430
434
  toggle: (show: boolean) => void;
431
435
  id?: string;
432
436
  children?: React.ReactNode;
@@ -882,8 +886,12 @@ interface TabHeaderTabProps {
882
886
  title?: string;
883
887
  }
884
888
  interface TabHeaderProps {
889
+ /** Required for generating element IDs for ARIA. */
890
+ id: string;
885
891
  tabs: TabHeaderTabProps[];
886
- id?: string;
892
+ ariaLabel: string;
893
+ /** The ID of the content panel with `role=tabpanel` this header is associated with. */
894
+ ariaControlsId: string;
887
895
  /** Defaults to 10rem. */
888
896
  maxTabWidth?: string;
889
897
  /** Defaults to 'tab'. */
@@ -952,6 +960,7 @@ declare const TabLocker: (props: {
952
960
  disabled?: boolean;
953
961
  children?: React.ReactNode;
954
962
  style?: React.CSSProperties;
963
+ className?: string;
955
964
  }) => React.JSX.Element;
956
965
 
957
966
  type SupportedTags = 'p' | 'h1' | 'h2' | 'h3' | 'h4' | 'span' | 'div';
@@ -977,11 +986,13 @@ interface TextProps {
977
986
  /** Will remove all margin/padding from specified tag */
978
987
  noPad?: boolean;
979
988
  leftPad?: string;
989
+ id?: string;
980
990
  }
981
991
  /** Wraps common needs for displaying text. Use for all text-containing elements to save on duplicated styling. */
982
992
  declare const Text: (props: TextProps) => React.DetailedReactHTMLElement<{
983
993
  style: React.CSSProperties;
984
994
  className: string;
995
+ id: string | undefined;
985
996
  }, HTMLElement>;
986
997
 
987
998
  type BaseProps = Omit<React.DetailedHTMLProps<React.TextareaHTMLAttributes<HTMLTextAreaElement>, HTMLTextAreaElement>, 'value'> & InputOnFocusProps;
@@ -1103,13 +1114,15 @@ interface ThemeRendererProps extends MackinTheme {
1103
1114
  declare const ThemeRenderer: (p: ThemeRendererProps) => React.JSX.Element;
1104
1115
 
1105
1116
  interface TabContainerProps {
1117
+ /** Required for generating element IDs for ARIA. */
1118
+ id: string;
1119
+ ariaLabel: string;
1106
1120
  tabs: {
1107
1121
  name: string | JSX.Element;
1108
1122
  /** The HTML title of the tab button. Defaults to 'name' prop. */
1109
1123
  title?: string;
1110
1124
  getContent: () => JSX.Element;
1111
1125
  }[];
1112
- id?: string;
1113
1126
  /** Defaults to 10rem. */
1114
1127
  maxTabWidth?: string;
1115
1128
  /** Defaults to 'tab'. */
package/index.esm.js CHANGED
@@ -288,7 +288,8 @@ 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
 
@@ -949,7 +950,7 @@ const ListItem = (props) => {
949
950
 
950
951
  const TabLocker = (props) => {
951
952
  const tabLocker = React.useRef(null);
952
- 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 => {
953
954
  var _a, _b;
954
955
  if (props.disabled) {
955
956
  return;
@@ -1579,7 +1580,7 @@ const Checkbox = (props) => {
1579
1580
  `}
1580
1581
  `;
1581
1582
  const iconStyles = css `
1582
- ${!!label && `
1583
+ ${!!label && !hideLabel && `
1583
1584
  margin-right: 0.5rem;
1584
1585
  `}
1585
1586
  ${props.disabled && `
@@ -1662,7 +1663,7 @@ const Modal = (p) => {
1662
1663
  const backdrop = useBackdropContext();
1663
1664
  const mouseDownElement = useRef(undefined);
1664
1665
  const theme = useThemeSafely();
1665
- const hasHeader = p.closeButton || p.heading;
1666
+ const hasHeader = !p.__noHeader;
1666
1667
  const contentRef = React__default.useRef(null);
1667
1668
  const log = useLogger((_a = p.id) !== null && _a !== void 0 ? _a : 'Modal', (_b = p.__debug) !== null && _b !== void 0 ? _b : false);
1668
1669
  const showing = useRef(p.show);
@@ -1701,10 +1702,22 @@ const Modal = (p) => {
1701
1702
  }
1702
1703
  };
1703
1704
  const zIndex = theme.zIndexes.modal;
1705
+ const { getText, language } = useLocalization();
1706
+ const closeText = React__default.useMemo(() => {
1707
+ return getText('Close');
1708
+ }, [language]);
1704
1709
  useEffect(() => {
1705
1710
  log('mounted');
1711
+ const escapeRemover = createBodyEscapeHandler(() => {
1712
+ if (showing.current) {
1713
+ p.onClose();
1714
+ }
1715
+ });
1716
+ log('body escape handler created');
1706
1717
  return () => {
1707
1718
  var _a;
1719
+ escapeRemover();
1720
+ log('body escape handler removed');
1708
1721
  if (showing.current) {
1709
1722
  log(`un-mount in progress and this modal is showing. decrement the backdrop and try to remove singleton body styles.`);
1710
1723
  backdrop.setShow(false, { key: (_a = p.id) !== null && _a !== void 0 ? _a : 'Modal', zIndex });
@@ -1751,9 +1764,9 @@ const Modal = (p) => {
1751
1764
  zIndex,
1752
1765
  cursor: 'default',
1753
1766
  margin: '1rem',
1754
- backgroundColor: p.noBackground ? undefined : theme.colors.modalBg,
1755
- border: p.noBackground ? undefined : theme.controls.border,
1756
- 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,
1757
1770
  maxWidth: (_c = p.maxWidth) !== null && _c !== void 0 ? _c : theme.breakpoints.tablet,
1758
1771
  minWidth: (_d = p.minWidth) !== null && _d !== void 0 ? _d : (hasHeader ? '250px' : undefined),
1759
1772
  opacity: p.show ? 1 : 0,
@@ -1780,26 +1793,30 @@ const Modal = (p) => {
1780
1793
  display: 'flex',
1781
1794
  justifyContent: 'center',
1782
1795
  alignItems: 'center',
1783
- cursor: p.onClick ? 'pointer' : 'default'
1796
+ cursor: 'pointer'
1784
1797
  }, p.scrollable && {
1785
1798
  overflowY: 'auto',
1786
1799
  overflowX: 'hidden',
1787
1800
  alignItems: 'flex-start'
1788
1801
  }]);
1802
+ const ariaLabelId = `${p.id}_label`;
1789
1803
  if (p.show) {
1790
1804
  const backdropContainer = document.getElementById(backdrop.portalId);
1791
1805
  if (backdropContainer) {
1792
- 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 => {
1793
1807
  e.stopPropagation();
1794
1808
  if (!mouseDownElement.current) {
1795
- if (p.onClick) {
1796
- log('backdropContainer onClick');
1797
- p.onClick();
1798
- }
1809
+ log('backdropContainer onClick');
1810
+ p.onClose();
1799
1811
  }
1800
1812
  mouseDownElement.current = undefined;
1813
+ }, onKeyDown: e => {
1814
+ if (e.code === 'Escape') {
1815
+ e.stopPropagation();
1816
+ p.onClose();
1817
+ }
1801
1818
  }, className: cx('modalContainer', modalContainerStyles) },
1802
- 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 => {
1803
1820
  mouseDownElement.current = e.target;
1804
1821
  e.stopPropagation();
1805
1822
  }, onMouseUp: () => {
@@ -1813,21 +1830,35 @@ const Modal = (p) => {
1813
1830
  }, className: cx('modalBody', modalBodyStyles, p.className) },
1814
1831
  React__default.createElement(TabLocker, null,
1815
1832
  hasHeader && (React__default.createElement("header", { className: cx('modalHeader', modalHeaderStyles) },
1816
- p.heading ? React__default.createElement(Text, { className: css({
1833
+ React__default.createElement(Text, { id: ariaLabelId, className: css({
1817
1834
  margin: 0,
1818
1835
  flexGrow: 1
1819
- }), tag: "h1", bold: true }, p.heading) : React__default.createElement("span", null),
1820
- 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({
1821
1838
  color: theme.colors.headerFont,
1822
1839
  marginLeft: '1rem',
1823
1840
  backgroundColor: 'transparent'
1824
- })), "aria-label": "Close", variant: "icon", onClick: p.onClick },
1825
- 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" })))),
1826
1846
  p.children)))), backdropContainer);
1827
1847
  }
1828
1848
  }
1829
1849
  return null;
1830
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
+ }
1831
1862
 
1832
1863
  const ConfirmModal = (props) => {
1833
1864
  const theme = useThemeSafely();
@@ -1845,7 +1876,7 @@ const ConfirmModal = (props) => {
1845
1876
  const cancelText = props.cancelText || getText('Cancel');
1846
1877
  return { confirmText, cancelText };
1847
1878
  }, [language, props.confirmText, props.cancelText]);
1848
- 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 },
1849
1880
  React.createElement("div", { className: css({ padding: '1rem' }) },
1850
1881
  React.createElement(Text, { align: "center" }, props.text),
1851
1882
  React.createElement("div", { className: css({ textAlign: 'center' }) },
@@ -1909,7 +1940,7 @@ const ErrorModal = (props) => {
1909
1940
  color: ${theme.colors.omgFont};
1910
1941
  }
1911
1942
  `;
1912
- 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 },
1913
1944
  React.createElement("div", { className: css({ padding: '1rem' }) },
1914
1945
  React.createElement(Text, { align: "center" }, message))));
1915
1946
  };
@@ -2381,6 +2412,10 @@ const InfoTip = (props) => {
2381
2412
  var _a, _b, _c;
2382
2413
  const [showTip, setShowTip] = React.useState(false);
2383
2414
  const theme = useThemeSafely();
2415
+ if (props.variant === 'modal' && !props.modalProps) {
2416
+ console.warn(`InfoTip with variant=modal requires modalProps.`);
2417
+ return null;
2418
+ }
2384
2419
  const bgColor = (_a = props.bgColor) !== null && _a !== void 0 ? _a : theme.colors.nav;
2385
2420
  const fontColor = (_b = props.fontColor) !== null && _b !== void 0 ? _b : theme.colors.navFont;
2386
2421
  const onClick = () => {
@@ -2437,12 +2472,12 @@ const InfoTip = (props) => {
2437
2472
  display:inline-block;
2438
2473
  `;
2439
2474
  const button = React.createElement(Button, { className: buttonStyles, disabled: props.disabled, variant: 'circle', tabIndex: props.tabIndex, onClick: onClick, onMouseEnter: onMouseOver, onMouseLeave: onMouseOut }, "i");
2440
- if (props.variant === 'modal') {
2475
+ if (props.variant === 'modal' && props.modalProps) {
2441
2476
  return (React.createElement(React.Fragment, null,
2442
2477
  button,
2443
- 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({
2444
2479
  whiteSpace: 'normal'
2445
- }), closeButton: true },
2480
+ }) },
2446
2481
  React.createElement("div", { className: css({ padding: '1rem' }) }, props.content))));
2447
2482
  }
2448
2483
  else {
@@ -2862,6 +2897,10 @@ const Nav = (props) => {
2862
2897
  if (props.show) {
2863
2898
  if (!nav.current.classList.contains(classNavShowing)) {
2864
2899
  nav.current.classList.add(classNavShowing);
2900
+ setTimeout(() => {
2901
+ var _a;
2902
+ (_a = document.querySelector(props.focusSelector)) === null || _a === void 0 ? void 0 : _a.focus();
2903
+ }, slideMs + 1);
2865
2904
  }
2866
2905
  }
2867
2906
  else {
@@ -2877,7 +2916,12 @@ const Nav = (props) => {
2877
2916
  }
2878
2917
  }
2879
2918
  });
2880
- return (React.createElement("nav", { ref: nav, className: cx('nav', navStyles, props.className) }, props.children));
2919
+ return (React.createElement("nav", { role: "dialog", "aria-modal": "true", "aria-label": props.ariaLabel, ref: nav, className: cx('nav', navStyles, props.className), onKeyDown: e => {
2920
+ if (e.code === 'Escape') {
2921
+ props.toggle(false);
2922
+ }
2923
+ } },
2924
+ React.createElement(TabLocker, { className: css({ height: '100%' }) }, props.children)));
2881
2925
  };
2882
2926
 
2883
2927
  const LinkContent = (props) => {
@@ -4048,8 +4092,37 @@ const TabHeader = (p) => {
4048
4092
  borderBottom: theme.controls.border,
4049
4093
  paddingBottom: '1rem'
4050
4094
  });
4095
+ function onTabSelect(index, tabId, focusAfter) {
4096
+ const onChange = () => {
4097
+ var _a;
4098
+ setTabIndex(index);
4099
+ (_a = p.onTabChanged) === null || _a === void 0 ? void 0 : _a.call(p, index);
4100
+ if (focusAfter) {
4101
+ setTimeout(() => {
4102
+ var _a;
4103
+ (_a = document.getElementById(tabId)) === null || _a === void 0 ? void 0 : _a.focus();
4104
+ }, 0);
4105
+ }
4106
+ };
4107
+ if (p.onBeforeTabChanged) {
4108
+ setTabsChanging(true);
4109
+ p.onBeforeTabChanged(index)
4110
+ .then(() => {
4111
+ onChange();
4112
+ })
4113
+ .catch(() => {
4114
+ /* do nothing */
4115
+ })
4116
+ .finally(() => {
4117
+ setTabsChanging(false);
4118
+ });
4119
+ }
4120
+ else {
4121
+ onChange();
4122
+ }
4123
+ }
4051
4124
  return (React.createElement("div", { className: "tabHeader" },
4052
- React.createElement("ul", { className: cx(menuStyles, p.containerClassName) }, p.tabs.map((tab, index) => {
4125
+ React.createElement("ul", { role: 'tablist', "aria-label": p.ariaLabel, className: cx(menuStyles, p.containerClassName) }, p.tabs.map((tab, index) => {
4053
4126
  var _a, _b;
4054
4127
  const active = index === tabIndex;
4055
4128
  let tabStyles;
@@ -4093,28 +4166,31 @@ const TabHeader = (p) => {
4093
4166
  else {
4094
4167
  buttonContent = tab.name;
4095
4168
  }
4169
+ const tabId = getTabHeaderTabId(p.id, index);
4096
4170
  return (React.createElement("li", { key: index, className: cx(tabStyles, p.tabClassName) },
4097
- React.createElement(Button, { "aria-role": "tab", "aria-selected": active, disabled: tabsChanging, className: buttonStyles, variant: buttonVariant, title: title, readOnly: active, onClick: () => {
4098
- const onChange = () => {
4099
- var _a;
4100
- setTabIndex(index);
4101
- (_a = p.onTabChanged) === null || _a === void 0 ? void 0 : _a.call(p, index);
4102
- };
4103
- if (p.onBeforeTabChanged) {
4104
- setTabsChanging(true);
4105
- p.onBeforeTabChanged(index)
4106
- .then(() => {
4107
- onChange();
4108
- })
4109
- .catch(() => {
4110
- /* do nothing */
4111
- })
4112
- .finally(() => {
4113
- setTabsChanging(false);
4114
- });
4171
+ React.createElement(Button, { id: tabId, tabIndex: active ? 0 : -1, role: "tab", "aria-controls": p.ariaControlsId, "aria-selected": active, disabled: tabsChanging, className: buttonStyles, variant: buttonVariant, title: title, readOnly: active, onClick: () => {
4172
+ onTabSelect(index, tabId, false);
4173
+ }, onKeyDown: e => {
4174
+ e.stopPropagation();
4175
+ let newIndex = index;
4176
+ if (e.code === 'ArrowLeft') {
4177
+ if (index === 0) {
4178
+ newIndex = p.tabs.length - 1;
4179
+ }
4180
+ else {
4181
+ newIndex = index - 1;
4182
+ }
4115
4183
  }
4116
- else {
4117
- onChange();
4184
+ else if (e.code === 'ArrowRight') {
4185
+ if (index === p.tabs.length - 1) {
4186
+ newIndex = 0;
4187
+ }
4188
+ else {
4189
+ newIndex = index + 1;
4190
+ }
4191
+ }
4192
+ if (newIndex !== index) {
4193
+ onTabSelect(newIndex, getTabHeaderTabId(p.id, newIndex), true);
4118
4194
  }
4119
4195
  } }, buttonContent)));
4120
4196
  })),
@@ -4126,6 +4202,9 @@ const TabHeader = (p) => {
4126
4202
  position: 'relative'
4127
4203
  }), p.tabHeaderDividerClassName) }))));
4128
4204
  };
4205
+ function getTabHeaderTabId(tabHeaderId, tabIndex) {
4206
+ return `${tabHeaderId}_tab_${tabIndex}`;
4207
+ }
4129
4208
 
4130
4209
  const Table = (props) => {
4131
4210
  const theme = useThemeSafely();
@@ -4430,7 +4509,7 @@ const TogglePasswordInput = React.forwardRef((props, ref) => {
4430
4509
  });
4431
4510
 
4432
4511
  const WaitingIndicator = (p) => {
4433
- var _a, _b;
4512
+ var _a, _b, _c;
4434
4513
  const [show, setShow] = useState(p.show);
4435
4514
  const hideTimer = useRef(0);
4436
4515
  const lastShowStatus = useRef(false);
@@ -4482,7 +4561,10 @@ const WaitingIndicator = (p) => {
4482
4561
  }
4483
4562
  }
4484
4563
  }, [p.show]);
4485
- return (React__default.createElement(Modal, { id: p.id, __debug: p.__debug, className: "waitingIndicator", show: show, noBackground: true },
4564
+ const id = (_c = p.id) !== null && _c !== void 0 ? _c : 'WaitingIndicator';
4565
+ return (React__default.createElement(Modal, { id: id, __debug: p.__debug, __noHeader: true, heading: '', onClose: () => {
4566
+ /* noop */
4567
+ }, className: "waitingIndicator", show: show },
4486
4568
  React__default.createElement("div", { className: css({
4487
4569
  color: 'white',
4488
4570
  fontSize: '3rem',
@@ -4892,15 +4974,17 @@ const TabContainer = (p) => {
4892
4974
  var _a;
4893
4975
  const [tabIndex, setTabIndex] = React.useState((_a = p.startingIndex) !== null && _a !== void 0 ? _a : 0);
4894
4976
  const theme = useThemeSafely();
4977
+ const tabPanelId = `${p.id}_tabpanel`;
4978
+ const tabHeaderId = `${p.id}_TabHeader`;
4895
4979
  return (React.createElement("div", { className: css({
4896
4980
  label: 'TabContainer'
4897
4981
  }) },
4898
- React.createElement(TabHeader, { tabs: p.tabs, maxTabWidth: p.maxTabWidth, variant: p.variant, containerClassName: p.tabHeaderClassName, tabClassName: p.tabClassName, tabHeaderDividerClassName: p.tabHeaderDividerClassName, startingIndex: tabIndex, onBeforeTabChanged: p.onBeforeTabChanged, onTabChanged: newIndex => {
4982
+ React.createElement(TabHeader, { id: tabHeaderId, ariaControlsId: tabPanelId, ariaLabel: p.ariaLabel, tabs: p.tabs, maxTabWidth: p.maxTabWidth, variant: p.variant, containerClassName: p.tabHeaderClassName, tabClassName: p.tabClassName, tabHeaderDividerClassName: p.tabHeaderDividerClassName, startingIndex: tabIndex, onBeforeTabChanged: p.onBeforeTabChanged, onTabChanged: (newIndex) => {
4899
4983
  var _a;
4900
4984
  setTabIndex(newIndex);
4901
4985
  (_a = p.onTabChanged) === null || _a === void 0 ? void 0 : _a.call(p, newIndex);
4902
4986
  } }),
4903
- React.createElement("div", { className: cx(css({
4987
+ React.createElement("div", { role: 'tabpanel', id: tabPanelId, "aria-labelledby": getTabHeaderTabId(tabHeaderId, tabIndex), className: cx(css({
4904
4988
  label: 'TabContainerContent',
4905
4989
  padding: '1rem',
4906
4990
  borderLeft: theme.controls.border,
package/index.js CHANGED
@@ -306,7 +306,8 @@ 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
 
@@ -967,7 +968,7 @@ const ListItem = (props) => {
967
968
 
968
969
  const TabLocker = (props) => {
969
970
  const tabLocker = React__namespace.useRef(null);
970
- 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 => {
971
972
  var _a, _b;
972
973
  if (props.disabled) {
973
974
  return;
@@ -1597,7 +1598,7 @@ const Checkbox = (props) => {
1597
1598
  `}
1598
1599
  `;
1599
1600
  const iconStyles = css.css `
1600
- ${!!label && `
1601
+ ${!!label && !hideLabel && `
1601
1602
  margin-right: 0.5rem;
1602
1603
  `}
1603
1604
  ${props.disabled && `
@@ -1680,7 +1681,7 @@ const Modal = (p) => {
1680
1681
  const backdrop = useBackdropContext();
1681
1682
  const mouseDownElement = React.useRef(undefined);
1682
1683
  const theme = useThemeSafely();
1683
- const hasHeader = p.closeButton || p.heading;
1684
+ const hasHeader = !p.__noHeader;
1684
1685
  const contentRef = React.useRef(null);
1685
1686
  const log = useLogger((_a = p.id) !== null && _a !== void 0 ? _a : 'Modal', (_b = p.__debug) !== null && _b !== void 0 ? _b : false);
1686
1687
  const showing = React.useRef(p.show);
@@ -1719,10 +1720,22 @@ const Modal = (p) => {
1719
1720
  }
1720
1721
  };
1721
1722
  const zIndex = theme.zIndexes.modal;
1723
+ const { getText, language } = useLocalization();
1724
+ const closeText = React.useMemo(() => {
1725
+ return getText('Close');
1726
+ }, [language]);
1722
1727
  React.useEffect(() => {
1723
1728
  log('mounted');
1729
+ const escapeRemover = createBodyEscapeHandler(() => {
1730
+ if (showing.current) {
1731
+ p.onClose();
1732
+ }
1733
+ });
1734
+ log('body escape handler created');
1724
1735
  return () => {
1725
1736
  var _a;
1737
+ escapeRemover();
1738
+ log('body escape handler removed');
1726
1739
  if (showing.current) {
1727
1740
  log(`un-mount in progress and this modal is showing. decrement the backdrop and try to remove singleton body styles.`);
1728
1741
  backdrop.setShow(false, { key: (_a = p.id) !== null && _a !== void 0 ? _a : 'Modal', zIndex });
@@ -1769,9 +1782,9 @@ const Modal = (p) => {
1769
1782
  zIndex,
1770
1783
  cursor: 'default',
1771
1784
  margin: '1rem',
1772
- backgroundColor: p.noBackground ? undefined : theme.colors.modalBg,
1773
- border: p.noBackground ? undefined : theme.controls.border,
1774
- 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,
1775
1788
  maxWidth: (_c = p.maxWidth) !== null && _c !== void 0 ? _c : theme.breakpoints.tablet,
1776
1789
  minWidth: (_d = p.minWidth) !== null && _d !== void 0 ? _d : (hasHeader ? '250px' : undefined),
1777
1790
  opacity: p.show ? 1 : 0,
@@ -1798,26 +1811,30 @@ const Modal = (p) => {
1798
1811
  display: 'flex',
1799
1812
  justifyContent: 'center',
1800
1813
  alignItems: 'center',
1801
- cursor: p.onClick ? 'pointer' : 'default'
1814
+ cursor: 'pointer'
1802
1815
  }, p.scrollable && {
1803
1816
  overflowY: 'auto',
1804
1817
  overflowX: 'hidden',
1805
1818
  alignItems: 'flex-start'
1806
1819
  }]);
1820
+ const ariaLabelId = `${p.id}_label`;
1807
1821
  if (p.show) {
1808
1822
  const backdropContainer = document.getElementById(backdrop.portalId);
1809
1823
  if (backdropContainer) {
1810
- 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 => {
1811
1825
  e.stopPropagation();
1812
1826
  if (!mouseDownElement.current) {
1813
- if (p.onClick) {
1814
- log('backdropContainer onClick');
1815
- p.onClick();
1816
- }
1827
+ log('backdropContainer onClick');
1828
+ p.onClose();
1817
1829
  }
1818
1830
  mouseDownElement.current = undefined;
1831
+ }, onKeyDown: e => {
1832
+ if (e.code === 'Escape') {
1833
+ e.stopPropagation();
1834
+ p.onClose();
1835
+ }
1819
1836
  }, className: css.cx('modalContainer', modalContainerStyles) },
1820
- 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 => {
1821
1838
  mouseDownElement.current = e.target;
1822
1839
  e.stopPropagation();
1823
1840
  }, onMouseUp: () => {
@@ -1831,21 +1848,35 @@ const Modal = (p) => {
1831
1848
  }, className: css.cx('modalBody', modalBodyStyles, p.className) },
1832
1849
  React.createElement(TabLocker, null,
1833
1850
  hasHeader && (React.createElement("header", { className: css.cx('modalHeader', modalHeaderStyles) },
1834
- p.heading ? React.createElement(Text, { className: css.css({
1851
+ React.createElement(Text, { id: ariaLabelId, className: css.css({
1835
1852
  margin: 0,
1836
1853
  flexGrow: 1
1837
- }), tag: "h1", bold: true }, p.heading) : React.createElement("span", null),
1838
- 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({
1839
1856
  color: theme.colors.headerFont,
1840
1857
  marginLeft: '1rem',
1841
1858
  backgroundColor: 'transparent'
1842
- })), "aria-label": "Close", variant: "icon", onClick: p.onClick },
1843
- 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" })))),
1844
1864
  p.children)))), backdropContainer);
1845
1865
  }
1846
1866
  }
1847
1867
  return null;
1848
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
+ }
1849
1880
 
1850
1881
  const ConfirmModal = (props) => {
1851
1882
  const theme = useThemeSafely();
@@ -1863,7 +1894,7 @@ const ConfirmModal = (props) => {
1863
1894
  const cancelText = props.cancelText || getText('Cancel');
1864
1895
  return { confirmText, cancelText };
1865
1896
  }, [language, props.confirmText, props.cancelText]);
1866
- 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 },
1867
1898
  React__namespace.createElement("div", { className: css.css({ padding: '1rem' }) },
1868
1899
  React__namespace.createElement(Text, { align: "center" }, props.text),
1869
1900
  React__namespace.createElement("div", { className: css.css({ textAlign: 'center' }) },
@@ -1927,7 +1958,7 @@ const ErrorModal = (props) => {
1927
1958
  color: ${theme.colors.omgFont};
1928
1959
  }
1929
1960
  `;
1930
- 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 },
1931
1962
  React__namespace.createElement("div", { className: css.css({ padding: '1rem' }) },
1932
1963
  React__namespace.createElement(Text, { align: "center" }, message))));
1933
1964
  };
@@ -2399,6 +2430,10 @@ const InfoTip = (props) => {
2399
2430
  var _a, _b, _c;
2400
2431
  const [showTip, setShowTip] = React__namespace.useState(false);
2401
2432
  const theme = useThemeSafely();
2433
+ if (props.variant === 'modal' && !props.modalProps) {
2434
+ console.warn(`InfoTip with variant=modal requires modalProps.`);
2435
+ return null;
2436
+ }
2402
2437
  const bgColor = (_a = props.bgColor) !== null && _a !== void 0 ? _a : theme.colors.nav;
2403
2438
  const fontColor = (_b = props.fontColor) !== null && _b !== void 0 ? _b : theme.colors.navFont;
2404
2439
  const onClick = () => {
@@ -2455,12 +2490,12 @@ const InfoTip = (props) => {
2455
2490
  display:inline-block;
2456
2491
  `;
2457
2492
  const button = React__namespace.createElement(Button, { className: buttonStyles, disabled: props.disabled, variant: 'circle', tabIndex: props.tabIndex, onClick: onClick, onMouseEnter: onMouseOver, onMouseLeave: onMouseOut }, "i");
2458
- if (props.variant === 'modal') {
2493
+ if (props.variant === 'modal' && props.modalProps) {
2459
2494
  return (React__namespace.createElement(React__namespace.Fragment, null,
2460
2495
  button,
2461
- 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({
2462
2497
  whiteSpace: 'normal'
2463
- }), closeButton: true },
2498
+ }) },
2464
2499
  React__namespace.createElement("div", { className: css.css({ padding: '1rem' }) }, props.content))));
2465
2500
  }
2466
2501
  else {
@@ -2880,6 +2915,10 @@ const Nav = (props) => {
2880
2915
  if (props.show) {
2881
2916
  if (!nav.current.classList.contains(classNavShowing)) {
2882
2917
  nav.current.classList.add(classNavShowing);
2918
+ setTimeout(() => {
2919
+ var _a;
2920
+ (_a = document.querySelector(props.focusSelector)) === null || _a === void 0 ? void 0 : _a.focus();
2921
+ }, slideMs + 1);
2883
2922
  }
2884
2923
  }
2885
2924
  else {
@@ -2895,7 +2934,12 @@ const Nav = (props) => {
2895
2934
  }
2896
2935
  }
2897
2936
  });
2898
- return (React__namespace.createElement("nav", { ref: nav, className: css.cx('nav', navStyles, props.className) }, props.children));
2937
+ 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 => {
2938
+ if (e.code === 'Escape') {
2939
+ props.toggle(false);
2940
+ }
2941
+ } },
2942
+ React__namespace.createElement(TabLocker, { className: css.css({ height: '100%' }) }, props.children)));
2899
2943
  };
2900
2944
 
2901
2945
  const LinkContent = (props) => {
@@ -4066,8 +4110,37 @@ const TabHeader = (p) => {
4066
4110
  borderBottom: theme.controls.border,
4067
4111
  paddingBottom: '1rem'
4068
4112
  });
4113
+ function onTabSelect(index, tabId, focusAfter) {
4114
+ const onChange = () => {
4115
+ var _a;
4116
+ setTabIndex(index);
4117
+ (_a = p.onTabChanged) === null || _a === void 0 ? void 0 : _a.call(p, index);
4118
+ if (focusAfter) {
4119
+ setTimeout(() => {
4120
+ var _a;
4121
+ (_a = document.getElementById(tabId)) === null || _a === void 0 ? void 0 : _a.focus();
4122
+ }, 0);
4123
+ }
4124
+ };
4125
+ if (p.onBeforeTabChanged) {
4126
+ setTabsChanging(true);
4127
+ p.onBeforeTabChanged(index)
4128
+ .then(() => {
4129
+ onChange();
4130
+ })
4131
+ .catch(() => {
4132
+ /* do nothing */
4133
+ })
4134
+ .finally(() => {
4135
+ setTabsChanging(false);
4136
+ });
4137
+ }
4138
+ else {
4139
+ onChange();
4140
+ }
4141
+ }
4069
4142
  return (React__namespace.createElement("div", { className: "tabHeader" },
4070
- React__namespace.createElement("ul", { className: css.cx(menuStyles, p.containerClassName) }, p.tabs.map((tab, index) => {
4143
+ React__namespace.createElement("ul", { role: 'tablist', "aria-label": p.ariaLabel, className: css.cx(menuStyles, p.containerClassName) }, p.tabs.map((tab, index) => {
4071
4144
  var _a, _b;
4072
4145
  const active = index === tabIndex;
4073
4146
  let tabStyles;
@@ -4111,28 +4184,31 @@ const TabHeader = (p) => {
4111
4184
  else {
4112
4185
  buttonContent = tab.name;
4113
4186
  }
4187
+ const tabId = getTabHeaderTabId(p.id, index);
4114
4188
  return (React__namespace.createElement("li", { key: index, className: css.cx(tabStyles, p.tabClassName) },
4115
- React__namespace.createElement(Button, { "aria-role": "tab", "aria-selected": active, disabled: tabsChanging, className: buttonStyles, variant: buttonVariant, title: title, readOnly: active, onClick: () => {
4116
- const onChange = () => {
4117
- var _a;
4118
- setTabIndex(index);
4119
- (_a = p.onTabChanged) === null || _a === void 0 ? void 0 : _a.call(p, index);
4120
- };
4121
- if (p.onBeforeTabChanged) {
4122
- setTabsChanging(true);
4123
- p.onBeforeTabChanged(index)
4124
- .then(() => {
4125
- onChange();
4126
- })
4127
- .catch(() => {
4128
- /* do nothing */
4129
- })
4130
- .finally(() => {
4131
- setTabsChanging(false);
4132
- });
4189
+ React__namespace.createElement(Button, { id: tabId, tabIndex: active ? 0 : -1, role: "tab", "aria-controls": p.ariaControlsId, "aria-selected": active, disabled: tabsChanging, className: buttonStyles, variant: buttonVariant, title: title, readOnly: active, onClick: () => {
4190
+ onTabSelect(index, tabId, false);
4191
+ }, onKeyDown: e => {
4192
+ e.stopPropagation();
4193
+ let newIndex = index;
4194
+ if (e.code === 'ArrowLeft') {
4195
+ if (index === 0) {
4196
+ newIndex = p.tabs.length - 1;
4197
+ }
4198
+ else {
4199
+ newIndex = index - 1;
4200
+ }
4133
4201
  }
4134
- else {
4135
- onChange();
4202
+ else if (e.code === 'ArrowRight') {
4203
+ if (index === p.tabs.length - 1) {
4204
+ newIndex = 0;
4205
+ }
4206
+ else {
4207
+ newIndex = index + 1;
4208
+ }
4209
+ }
4210
+ if (newIndex !== index) {
4211
+ onTabSelect(newIndex, getTabHeaderTabId(p.id, newIndex), true);
4136
4212
  }
4137
4213
  } }, buttonContent)));
4138
4214
  })),
@@ -4144,6 +4220,9 @@ const TabHeader = (p) => {
4144
4220
  position: 'relative'
4145
4221
  }), p.tabHeaderDividerClassName) }))));
4146
4222
  };
4223
+ function getTabHeaderTabId(tabHeaderId, tabIndex) {
4224
+ return `${tabHeaderId}_tab_${tabIndex}`;
4225
+ }
4147
4226
 
4148
4227
  const Table = (props) => {
4149
4228
  const theme = useThemeSafely();
@@ -4448,7 +4527,7 @@ const TogglePasswordInput = React__namespace.forwardRef((props, ref) => {
4448
4527
  });
4449
4528
 
4450
4529
  const WaitingIndicator = (p) => {
4451
- var _a, _b;
4530
+ var _a, _b, _c;
4452
4531
  const [show, setShow] = React.useState(p.show);
4453
4532
  const hideTimer = React.useRef(0);
4454
4533
  const lastShowStatus = React.useRef(false);
@@ -4500,7 +4579,10 @@ const WaitingIndicator = (p) => {
4500
4579
  }
4501
4580
  }
4502
4581
  }, [p.show]);
4503
- return (React.createElement(Modal, { id: p.id, __debug: p.__debug, className: "waitingIndicator", show: show, noBackground: true },
4582
+ const id = (_c = p.id) !== null && _c !== void 0 ? _c : 'WaitingIndicator';
4583
+ return (React.createElement(Modal, { id: id, __debug: p.__debug, __noHeader: true, heading: '', onClose: () => {
4584
+ /* noop */
4585
+ }, className: "waitingIndicator", show: show },
4504
4586
  React.createElement("div", { className: css.css({
4505
4587
  color: 'white',
4506
4588
  fontSize: '3rem',
@@ -4910,15 +4992,17 @@ const TabContainer = (p) => {
4910
4992
  var _a;
4911
4993
  const [tabIndex, setTabIndex] = React__namespace.useState((_a = p.startingIndex) !== null && _a !== void 0 ? _a : 0);
4912
4994
  const theme = useThemeSafely();
4995
+ const tabPanelId = `${p.id}_tabpanel`;
4996
+ const tabHeaderId = `${p.id}_TabHeader`;
4913
4997
  return (React__namespace.createElement("div", { className: css.css({
4914
4998
  label: 'TabContainer'
4915
4999
  }) },
4916
- React__namespace.createElement(TabHeader, { tabs: p.tabs, maxTabWidth: p.maxTabWidth, variant: p.variant, containerClassName: p.tabHeaderClassName, tabClassName: p.tabClassName, tabHeaderDividerClassName: p.tabHeaderDividerClassName, startingIndex: tabIndex, onBeforeTabChanged: p.onBeforeTabChanged, onTabChanged: newIndex => {
5000
+ React__namespace.createElement(TabHeader, { id: tabHeaderId, ariaControlsId: tabPanelId, ariaLabel: p.ariaLabel, tabs: p.tabs, maxTabWidth: p.maxTabWidth, variant: p.variant, containerClassName: p.tabHeaderClassName, tabClassName: p.tabClassName, tabHeaderDividerClassName: p.tabHeaderDividerClassName, startingIndex: tabIndex, onBeforeTabChanged: p.onBeforeTabChanged, onTabChanged: (newIndex) => {
4917
5001
  var _a;
4918
5002
  setTabIndex(newIndex);
4919
5003
  (_a = p.onTabChanged) === null || _a === void 0 ? void 0 : _a.call(p, newIndex);
4920
5004
  } }),
4921
- React__namespace.createElement("div", { className: css.cx(css.css({
5005
+ React__namespace.createElement("div", { role: 'tabpanel', id: tabPanelId, "aria-labelledby": getTabHeaderTabId(tabHeaderId, tabIndex), className: css.cx(css.css({
4922
5006
  label: 'TabContainerContent',
4923
5007
  padding: '1rem',
4924
5008
  borderLeft: theme.controls.border,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mackin.com/styleguide",
3
- "version": "11.0.1",
3
+ "version": "11.0.3",
4
4
  "description": "",
5
5
  "main": "./index.js",
6
6
  "module": "./index.esm.js",