@mackin.com/styleguide 6.1.0 → 7.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 (3) hide show
  1. package/index.d.ts +13 -27
  2. package/index.js +359 -341
  3. package/package.json +1 -1
package/index.d.ts CHANGED
@@ -262,29 +262,12 @@ declare const ErrorModal: (props: {
262
262
 
263
263
  /** @jsx jsx */
264
264
 
265
- interface FilePickerProps {
266
- maxBytes?: number;
267
- multiple?: boolean;
268
- accept?: string;
269
- /** Defaults to 'Choose File(s)'. */
270
- buttonText?: string;
271
- /** If the handler returns true, the native file input will be cleared. */
272
- onChange?: (files: FileList | undefined) => boolean | undefined;
273
- /**
274
- * Used the facilitate communication between the FilePicker and FileUploader.
275
- * Temp solution until we merge both together. Don't use! */
276
- __passClearFilesHandle?: (func: () => void) => void;
277
- }
278
- /** Basic implementation here for later abstraction. */
279
- declare const FilePicker: (props: FilePickerProps) => jsx.JSX.Element;
280
-
281
- /** @jsx jsx */
282
-
283
- interface IProps extends React.DetailedHTMLProps<React.FormHTMLAttributes<HTMLFormElement>, HTMLFormElement> {
265
+ declare type FormProps = Omit<React.ClassAttributes<HTMLFormElement> & React.FormHTMLAttributes<HTMLFormElement>, 'css'>;
266
+ interface IProps extends FormProps {
284
267
  inline?: boolean;
285
268
  }
286
269
  /** Use this instead of <form> directly. If we need to fight Chrome's autofill, we can do so at a global level. */
287
- declare const Form: (props: IProps) => jsx.JSX.Element;
270
+ declare const Form: React.ForwardRefExoticComponent<Pick<IProps, "key" | "acceptCharset" | "action" | "autoComplete" | "encType" | "method" | "name" | "noValidate" | "target" | "defaultChecked" | "defaultValue" | "suppressContentEditableWarning" | "suppressHydrationWarning" | "accessKey" | "className" | "contentEditable" | "contextMenu" | "dir" | "draggable" | "hidden" | "id" | "lang" | "placeholder" | "slot" | "spellCheck" | "style" | "tabIndex" | "title" | "translate" | "radioGroup" | "role" | "about" | "datatype" | "inlist" | "prefix" | "property" | "resource" | "typeof" | "vocab" | "autoCapitalize" | "autoCorrect" | "autoSave" | "color" | "itemProp" | "itemScope" | "itemType" | "itemID" | "itemRef" | "results" | "security" | "unselectable" | "inputMode" | "is" | "aria-activedescendant" | "aria-atomic" | "aria-autocomplete" | "aria-busy" | "aria-checked" | "aria-colcount" | "aria-colindex" | "aria-colspan" | "aria-controls" | "aria-current" | "aria-describedby" | "aria-details" | "aria-disabled" | "aria-dropeffect" | "aria-errormessage" | "aria-expanded" | "aria-flowto" | "aria-grabbed" | "aria-haspopup" | "aria-hidden" | "aria-invalid" | "aria-keyshortcuts" | "aria-label" | "aria-labelledby" | "aria-level" | "aria-live" | "aria-modal" | "aria-multiline" | "aria-multiselectable" | "aria-orientation" | "aria-owns" | "aria-placeholder" | "aria-posinset" | "aria-pressed" | "aria-readonly" | "aria-relevant" | "aria-required" | "aria-roledescription" | "aria-rowcount" | "aria-rowindex" | "aria-rowspan" | "aria-selected" | "aria-setsize" | "aria-sort" | "aria-valuemax" | "aria-valuemin" | "aria-valuenow" | "aria-valuetext" | "children" | "dangerouslySetInnerHTML" | "onCopy" | "onCopyCapture" | "onCut" | "onCutCapture" | "onPaste" | "onPasteCapture" | "onCompositionEnd" | "onCompositionEndCapture" | "onCompositionStart" | "onCompositionStartCapture" | "onCompositionUpdate" | "onCompositionUpdateCapture" | "onFocus" | "onFocusCapture" | "onBlur" | "onBlurCapture" | "onChange" | "onChangeCapture" | "onBeforeInput" | "onBeforeInputCapture" | "onInput" | "onInputCapture" | "onReset" | "onResetCapture" | "onSubmit" | "onSubmitCapture" | "onInvalid" | "onInvalidCapture" | "onLoad" | "onLoadCapture" | "onError" | "onErrorCapture" | "onKeyDown" | "onKeyDownCapture" | "onKeyPress" | "onKeyPressCapture" | "onKeyUp" | "onKeyUpCapture" | "onAbort" | "onAbortCapture" | "onCanPlay" | "onCanPlayCapture" | "onCanPlayThrough" | "onCanPlayThroughCapture" | "onDurationChange" | "onDurationChangeCapture" | "onEmptied" | "onEmptiedCapture" | "onEncrypted" | "onEncryptedCapture" | "onEnded" | "onEndedCapture" | "onLoadedData" | "onLoadedDataCapture" | "onLoadedMetadata" | "onLoadedMetadataCapture" | "onLoadStart" | "onLoadStartCapture" | "onPause" | "onPauseCapture" | "onPlay" | "onPlayCapture" | "onPlaying" | "onPlayingCapture" | "onProgress" | "onProgressCapture" | "onRateChange" | "onRateChangeCapture" | "onSeeked" | "onSeekedCapture" | "onSeeking" | "onSeekingCapture" | "onStalled" | "onStalledCapture" | "onSuspend" | "onSuspendCapture" | "onTimeUpdate" | "onTimeUpdateCapture" | "onVolumeChange" | "onVolumeChangeCapture" | "onWaiting" | "onWaitingCapture" | "onAuxClick" | "onAuxClickCapture" | "onClick" | "onClickCapture" | "onContextMenu" | "onContextMenuCapture" | "onDoubleClick" | "onDoubleClickCapture" | "onDrag" | "onDragCapture" | "onDragEnd" | "onDragEndCapture" | "onDragEnter" | "onDragEnterCapture" | "onDragExit" | "onDragExitCapture" | "onDragLeave" | "onDragLeaveCapture" | "onDragOver" | "onDragOverCapture" | "onDragStart" | "onDragStartCapture" | "onDrop" | "onDropCapture" | "onMouseDown" | "onMouseDownCapture" | "onMouseEnter" | "onMouseLeave" | "onMouseMove" | "onMouseMoveCapture" | "onMouseOut" | "onMouseOutCapture" | "onMouseOver" | "onMouseOverCapture" | "onMouseUp" | "onMouseUpCapture" | "onSelect" | "onSelectCapture" | "onTouchCancel" | "onTouchCancelCapture" | "onTouchEnd" | "onTouchEndCapture" | "onTouchMove" | "onTouchMoveCapture" | "onTouchStart" | "onTouchStartCapture" | "onPointerDown" | "onPointerDownCapture" | "onPointerMove" | "onPointerMoveCapture" | "onPointerUp" | "onPointerUpCapture" | "onPointerCancel" | "onPointerCancelCapture" | "onPointerEnter" | "onPointerEnterCapture" | "onPointerLeave" | "onPointerLeaveCapture" | "onPointerOver" | "onPointerOverCapture" | "onPointerOut" | "onPointerOutCapture" | "onGotPointerCapture" | "onGotPointerCaptureCapture" | "onLostPointerCapture" | "onLostPointerCaptureCapture" | "onScroll" | "onScrollCapture" | "onWheel" | "onWheelCapture" | "onAnimationStart" | "onAnimationStartCapture" | "onAnimationEnd" | "onAnimationEndCapture" | "onAnimationIteration" | "onAnimationIterationCapture" | "onTransitionEnd" | "onTransitionEndCapture" | "inline"> & React.RefAttributes<any>>;
288
271
  declare const FormFlexRow: (props: {
289
272
  children: React.ReactNode;
290
273
  className?: string;
@@ -374,9 +357,10 @@ declare const InfoTip: (props: InfoTipProps) => jsx.JSX.Element;
374
357
  /** @jsx jsx */
375
358
 
376
359
  declare type InputValue = string | number | undefined;
377
- declare type InputType = 'text' | 'number' | 'textarea' | 'date' | 'password';
360
+ declare type InputType = 'text' | 'number' | 'textarea' | 'date' | 'password' | 'url' | 'email';
378
361
  interface InputProps {
379
362
  type: InputType;
363
+ name?: string;
380
364
  style?: React.CSSProperties;
381
365
  value?: InputValue;
382
366
  rounded?: boolean;
@@ -392,7 +376,7 @@ interface InputProps {
392
376
  /** Defaults to 'off'. */
393
377
  autoComplete?: string;
394
378
  /** Called with debounce when the input changes. */
395
- onChange?: (value: InputValue) => void;
379
+ onChange?: (value: InputValue, name?: string) => void;
396
380
  onFocus?: (event: React.FocusEvent<HTMLInputElement | HTMLTextAreaElement>) => void;
397
381
  onBlur?: (event: React.FocusEvent<HTMLInputElement | HTMLTextAreaElement>) => void;
398
382
  /** Defaults to 100. Ignored for type=number and type=date. */
@@ -407,8 +391,9 @@ interface InputProps {
407
391
  max?: number;
408
392
  /** Only used for type=textarea. Defaults to 10. */
409
393
  rows?: number;
394
+ pattern?: string;
410
395
  }
411
- declare const Input: (props: InputProps) => jsx.JSX.Element;
396
+ declare const Input: React.ForwardRefExoticComponent<InputProps & React.RefAttributes<any>>;
412
397
 
413
398
  /** @jsx jsx */
414
399
 
@@ -816,7 +801,7 @@ declare const TabLocker: (props: {
816
801
 
817
802
  /** @jsx jsx */
818
803
 
819
- interface FileUploaderProps extends FilePickerProps {
804
+ interface FileUploaderProps {
820
805
  onUpload: (files: FileList) => Promise<void>;
821
806
  /** Defaults to 'Upload'. */
822
807
  buttonText?: string;
@@ -828,8 +813,9 @@ interface FileUploaderProps extends FilePickerProps {
828
813
  buttonWidth?: string;
829
814
  /** Defaults to 'primary'. */
830
815
  buttonVariant?: ButtonVariant;
831
- /** Will clear all files on upload success. */
832
- clearOnUpload?: boolean;
816
+ maxBytes?: number;
817
+ multiple?: boolean;
818
+ accept?: string;
833
819
  }
834
820
  declare const FileUploader: (p: FileUploaderProps) => jsx.JSX.Element;
835
821
 
@@ -974,4 +960,4 @@ declare const Backdrop: (p: {
974
960
 
975
961
  declare const useMediaQuery: (query: string) => boolean;
976
962
 
977
- export { Alignment, Backdrop$1 as Backdrop, Backdrop as Backdrop2, BoundMemoryPager, BoundStaticPager, Button, ButtonProps, Calendar, CalendarProps, Checkbox, CheckboxProps, ConfirmModal, ConfirmModalProps, CopyButton, DatePicker, DatePickerProps, Divider, ErrorModal, FilePicker, FilePickerProps, FileUploader, Form, FormColumnRow, FormFlexRow, GlobalStyles, Header, Highlight, ICONS, Icon, Image, InfoPanel, InfoTip, InfoTipProps, Input, InputProps, ItemPager, Label, LabelProps, List, ListItem, MackinTheme, Modal, Nav, OmniLink, OmniLinkProps, PagedResult, Pager, PagerProps, Picker, PickerProps, Popover, ProgressBar, ProgressBarProps, SearchBox, SearchBoxProps, Slider, TabHeader, TabHeaderProps, TabLocker, Table, Td, TdCurrency, TdNumber, Text, TextProps, Th, ThSort, ToggleButton, ToggleButtonGroup, ToggleButtonGroupProps, ToggleButtonProps, TogglePasswordInput, Tr, WaitingIndicator, calcDynamicThemeProps, defaultTheme, getCurrencyDisplay, mergeClassNames, useMediaQuery, useThemeSafely };
963
+ export { Alignment, Backdrop$1 as Backdrop, Backdrop as Backdrop2, BoundMemoryPager, BoundStaticPager, Button, ButtonProps, Calendar, CalendarProps, Checkbox, CheckboxProps, ConfirmModal, ConfirmModalProps, CopyButton, DatePicker, DatePickerProps, Divider, ErrorModal, FileUploader, Form, FormColumnRow, FormFlexRow, GlobalStyles, Header, Highlight, ICONS, Icon, Image, InfoPanel, InfoTip, InfoTipProps, Input, InputProps, ItemPager, Label, LabelProps, List, ListItem, MackinTheme, Modal, Nav, OmniLink, OmniLinkProps, PagedResult, Pager, PagerProps, Picker, PickerProps, Popover, ProgressBar, ProgressBarProps, SearchBox, SearchBoxProps, Slider, TabHeader, TabHeaderProps, TabLocker, Table, Td, TdCurrency, TdNumber, Text, TextProps, Th, ThSort, ToggleButton, ToggleButtonGroup, ToggleButtonGroupProps, ToggleButtonProps, TogglePasswordInput, Tr, WaitingIndicator, calcDynamicThemeProps, defaultTheme, getCurrencyDisplay, mergeClassNames, useMediaQuery, useThemeSafely };
package/index.js CHANGED
@@ -1591,7 +1591,7 @@ const Divider = (props) => {
1591
1591
 
1592
1592
  /** @jsx jsx */
1593
1593
  const ErrorModal = (props) => {
1594
- const { message } = props; __rest(props, ["message"]);
1594
+ const { message } = props;
1595
1595
  const theme = useThemeSafely();
1596
1596
  const modalStyles = react.css `
1597
1597
  .modalHeader {
@@ -1607,203 +1607,12 @@ const ErrorModal = (props) => {
1607
1607
  react.jsx(Text, { align: "center" }, message))));
1608
1608
  };
1609
1609
 
1610
- /** @jsx jsx */
1611
- const InfoPanel = (props) => {
1612
- const theme = useThemeSafely();
1613
- const styles = react.css `
1614
- border:${theme.colors.border};
1615
- padding:1rem;
1616
- color: rgba(0, 0, 0, 0.7);
1617
- margin: 0 !important;
1618
- ${props.variant === 'info' && `
1619
- background-color:${theme.colors.info};
1620
- color:${theme.colors.infoFont};
1621
- `}
1622
- ${props.variant === 'warning' && `
1623
- background-color:${theme.colors.warning};
1624
- color:${theme.colors.warningFont};
1625
- `}
1626
- ${props.variant === 'error' && `
1627
- background-color:${theme.colors.omg};
1628
- color:${theme.colors.omgFont};
1629
- `}
1630
- ${props.variant === 'negative' && `
1631
- background-color:${theme.colors.negative};
1632
- color:${theme.colors.negativeFont};
1633
- `}
1634
- ${props.variant === 'positive' && `
1635
- background-color:${theme.colors.positive};
1636
- color:${theme.colors.positiveFont};
1637
- `}
1638
- `;
1639
- return (react.jsx(Text, { style: props.style, align: "center", css: styles, className: mergeClassNames('infoPanel', props.className) }, props.children));
1640
- };
1641
-
1642
- class FileListPlus {
1643
- constructor(_raw, _args = {}) {
1644
- this._raw = _raw;
1645
- this._args = _args;
1646
- this._files = Array.from(this._raw).map(f => {
1647
- return { name: f.name, size: f.size, type: f.type };
1648
- });
1649
- if (this._args.accept) {
1650
- const acceptTypes = this._args.accept.split(',');
1651
- this._invalidFiles = this._files.filter(f => {
1652
- if (acceptTypes.includes(f.type)) {
1653
- return false;
1654
- }
1655
- if (acceptTypes.some(t => f.name.endsWith(t))) {
1656
- return false;
1657
- }
1658
- return true;
1659
- });
1660
- }
1661
- else {
1662
- this._invalidFiles = [];
1663
- }
1664
- }
1665
- get raw() {
1666
- return this._raw;
1667
- }
1668
- get length() {
1669
- return this._files.length;
1670
- }
1671
- get files() {
1672
- return this._files;
1673
- }
1674
- get invalidFiles() {
1675
- return this._invalidFiles;
1676
- }
1677
- get totalBytes() {
1678
- return lodash.sumBy(this.files, f => f.size);
1679
- }
1680
- get overMaxBytes() {
1681
- var _a;
1682
- return this.totalBytes >= ((_a = this._args.maxBytes) !== null && _a !== void 0 ? _a : Infinity);
1683
- }
1684
- get overFileLimit() {
1685
- return this.length > (this._args.multiple ? Infinity : 1);
1686
- }
1687
- get hasErrors() {
1688
- return this.overMaxBytes || this.overFileLimit || !!this.invalidFiles.length;
1689
- }
1690
- }
1691
-
1692
- /** @jsx jsx */
1693
- /** Basic implementation here for later abstraction. */
1694
- const FilePicker = (props) => {
1695
- var _a, _b, _c, _d;
1696
- const input = React__namespace.useRef(null);
1697
- const [fileList, setFileList] = React__namespace.useState(undefined);
1698
- const totalFileSize = (_a = fileList === null || fileList === void 0 ? void 0 : fileList.totalBytes) !== null && _a !== void 0 ? _a : 0;
1699
- const theme = useThemeSafely();
1700
- let filesDisplay = '';
1701
- if (!(fileList === null || fileList === void 0 ? void 0 : fileList.length)) {
1702
- filesDisplay = `No file${props.multiple ? 's' : ''} chosen.`;
1703
- }
1704
- else {
1705
- filesDisplay = `${fileList.length.toLocaleString()} file${fileList.length > 1 ? 's' : ''} selected (${getSizeString(totalFileSize)}): ${fileList.files.map(f => f.name).join(', ')}`;
1706
- }
1707
- const width = '10rem';
1708
- const nativeInputStyles = react.css `
1709
- position: absolute;
1710
- top: 0;
1711
- left: 0;
1712
- bottom: 0;
1713
- right: 0;
1714
- border: 1px solid black;
1715
- cursor: pointer;
1716
- width:${width};
1717
- opacity: 0;
1718
- `;
1719
- const buttonText = (_b = props.buttonText) !== null && _b !== void 0 ? _b : `Choose File${props.multiple ? 's' : ''}`;
1720
- const clearFiles = () => {
1721
- if (input.current) {
1722
- input.current.value = '';
1723
- }
1724
- setFileList(undefined);
1725
- };
1726
- (_c = props.__passClearFilesHandle) === null || _c === void 0 ? void 0 : _c.call(props, clearFiles);
1727
- return (react.jsx("span", { css: { display: 'inline-block' }, className: "fileUploader" },
1728
- react.jsx("div", { css: {
1729
- position: 'relative',
1730
- width
1731
- } },
1732
- react.jsx(Button, { block: true, variant: "secondary", type: "button" }, buttonText),
1733
- react.jsx("input", { ref: input, css: nativeInputStyles, type: "file", multiple: props.multiple, accept: props.accept, onChange: e => {
1734
- var _a;
1735
- try {
1736
- if (!e.target.files) {
1737
- setFileList(undefined);
1738
- (_a = props.onChange) === null || _a === void 0 ? void 0 : _a.call(props, undefined);
1739
- return;
1740
- }
1741
- const fileListPlus = new FileListPlus(e.target.files, {
1742
- accept: props.accept,
1743
- multiple: props.multiple,
1744
- maxBytes: props.maxBytes
1745
- });
1746
- if (props.onChange) {
1747
- if (fileListPlus.hasErrors) {
1748
- props.onChange(undefined);
1749
- setFileList(fileListPlus);
1750
- }
1751
- else {
1752
- const removeFiles = props.onChange(fileListPlus.raw);
1753
- if (removeFiles) {
1754
- setFileList(undefined);
1755
- }
1756
- else {
1757
- setFileList(fileListPlus);
1758
- }
1759
- }
1760
- }
1761
- else {
1762
- setFileList(fileListPlus);
1763
- }
1764
- }
1765
- catch (err) {
1766
- // tslint:disable-next-line
1767
- console.error(err);
1768
- setFileList(undefined);
1769
- }
1770
- } })),
1771
- react.jsx(Text, null,
1772
- filesDisplay,
1773
- !!(fileList === null || fileList === void 0 ? void 0 : fileList.length) && (react.jsx(Button, { onClick: () => {
1774
- var _a;
1775
- clearFiles();
1776
- (_a = props.onChange) === null || _a === void 0 ? void 0 : _a.call(props, undefined);
1777
- }, css: { marginLeft: '1rem', color: theme.colors.negative }, rightIcon: react.jsx(Icon, { id: "clear" }), variant: "inlineLink" }, "Clear"))),
1778
- !!(fileList === null || fileList === void 0 ? void 0 : fileList.invalidFiles.length) && react.jsx(InfoPanel, { variant: "error" },
1779
- "Invalid files: ",
1780
- fileList.invalidFiles.map(f => f.name).join(', '),
1781
- "."),
1782
- (fileList === null || fileList === void 0 ? void 0 : fileList.overMaxBytes) && react.jsx(InfoPanel, { variant: "error" },
1783
- "Max file size exceeded (",
1784
- getSizeString((_d = props.maxBytes) !== null && _d !== void 0 ? _d : 0),
1785
- ").")));
1786
- };
1787
- const bytesInMb = 1048576;
1788
- const bytesInKb = 1024;
1789
- const getSizeString = (size) => {
1790
- if (size < bytesInKb) {
1791
- return size.toLocaleString() + ' B';
1792
- }
1793
- else if (size < bytesInMb) {
1794
- return (size / bytesInKb).toLocaleString(undefined, { minimumFractionDigits: 1, maximumFractionDigits: 1 }) + ' KB';
1795
- }
1796
- else {
1797
- return (size / bytesInMb).toLocaleString(undefined, { minimumFractionDigits: 1, maximumFractionDigits: 1 }) + ' MB';
1798
- }
1799
- };
1800
-
1801
1610
  /** @jsx jsx */
1802
1611
  /** Use this instead of <form> directly. If we need to fight Chrome's autofill, we can do so at a global level. */
1803
- const Form = (props) => {
1612
+ const Form = React__namespace.forwardRef((props, ref) => {
1804
1613
  const { inline, children, onSubmit } = props, rest = __rest(props, ["inline", "children", "onSubmit"]);
1805
1614
  const theme = useThemeSafely();
1806
- return (react.jsx("form", Object.assign({ className: "form", css: {
1615
+ return (react.jsx("form", Object.assign({ ref: ref, className: "form", css: {
1807
1616
  display: 'flex',
1808
1617
  flexDirection: props.inline ? 'row' : 'column',
1809
1618
  alignItems: props.inline ? 'flex-end' : 'normal',
@@ -1815,7 +1624,7 @@ const Form = (props) => {
1815
1624
  onSubmit(e);
1816
1625
  }
1817
1626
  } }), children));
1818
- };
1627
+ });
1819
1628
  const FormFlexRow = (props) => {
1820
1629
  var _a;
1821
1630
  const theme = useThemeSafely();
@@ -1952,6 +1761,38 @@ const Image = (props) => {
1952
1761
  }, alt: props.alt, style: props.style, className: mergeClassNames('image', props.className), src: props.src }));
1953
1762
  };
1954
1763
 
1764
+ /** @jsx jsx */
1765
+ const InfoPanel = (props) => {
1766
+ const theme = useThemeSafely();
1767
+ const styles = react.css `
1768
+ border:${theme.colors.border};
1769
+ padding:1rem;
1770
+ color: rgba(0, 0, 0, 0.7);
1771
+ margin: 0 !important;
1772
+ ${props.variant === 'info' && `
1773
+ background-color:${theme.colors.info};
1774
+ color:${theme.colors.infoFont};
1775
+ `}
1776
+ ${props.variant === 'warning' && `
1777
+ background-color:${theme.colors.warning};
1778
+ color:${theme.colors.warningFont};
1779
+ `}
1780
+ ${props.variant === 'error' && `
1781
+ background-color:${theme.colors.omg};
1782
+ color:${theme.colors.omgFont};
1783
+ `}
1784
+ ${props.variant === 'negative' && `
1785
+ background-color:${theme.colors.negative};
1786
+ color:${theme.colors.negativeFont};
1787
+ `}
1788
+ ${props.variant === 'positive' && `
1789
+ background-color:${theme.colors.positive};
1790
+ color:${theme.colors.positiveFont};
1791
+ `}
1792
+ `;
1793
+ return (react.jsx(Text, { style: props.style, align: "center", css: styles, className: mergeClassNames('infoPanel', props.className) }, props.children));
1794
+ };
1795
+
1955
1796
  /** @jsx jsx */
1956
1797
  const Popover = (p) => {
1957
1798
  var _a, _b;
@@ -2067,19 +1908,19 @@ const formatLocalValue = (value, type) => {
2067
1908
  }
2068
1909
  return newValue;
2069
1910
  };
2070
- const Input = (props) => {
1911
+ const Input = React__namespace.forwardRef((props, ref) => {
2071
1912
  var _a, _b, _c;
2072
1913
  const [localValue, setLocalValue] = React__namespace.useState(formatLocalValue(props.value, props.type));
2073
1914
  const debounceMs = (_a = props.debounceMs) !== null && _a !== void 0 ? _a : DEFAULT_DEBOUNCE_MS;
2074
1915
  const autoComplete = (_b = props.autoComplete) !== null && _b !== void 0 ? _b : 'off';
2075
1916
  const vars = React__namespace.useRef({
2076
- wrappedOnChange: (props.onChange && debounceMs) ? lodash.debounce((value) => {
1917
+ wrappedOnChange: (props.onChange && debounceMs) ? lodash.debounce((value, name) => {
2077
1918
  var _a;
2078
- (_a = props.onChange) === null || _a === void 0 ? void 0 : _a.call(props, value);
1919
+ (_a = props.onChange) === null || _a === void 0 ? void 0 : _a.call(props, value, name);
2079
1920
  }, debounceMs) : undefined,
2080
1921
  focused: false
2081
1922
  });
2082
- const onChange = (_c = vars.current.wrappedOnChange) !== null && _c !== void 0 ? _c : props.onChange;
1923
+ const outerOnChange = (_c = vars.current.wrappedOnChange) !== null && _c !== void 0 ? _c : props.onChange;
2083
1924
  const trySyncLocalValue = () => {
2084
1925
  if (vars.current.focused) {
2085
1926
  return;
@@ -2092,53 +1933,49 @@ const Input = (props) => {
2092
1933
  };
2093
1934
  React__namespace.useEffect(() => {
2094
1935
  trySyncLocalValue();
2095
- });
1936
+ }, [props.value]);
2096
1937
  const theme = useThemeSafely();
2097
- const inputStyles = react.css `
2098
- font-family: ${theme.fonts.family};
2099
- font-size: ${theme.fonts.size};
2100
- width: 100%;
2101
- border: ${theme.controls.border};
2102
- color: ${theme.colors.font};
2103
- padding-left: ${theme.controls.padding};
2104
- padding-right: ${theme.controls.padding};
2105
- height: ${theme.controls.height};
2106
- transition: ${theme.controls.transition};
2107
- &:focus {
2108
- outline: none;
2109
- box-shadow: ${theme.controls.focusOutlineShadow};
2110
- }
2111
- &:disabled {
2112
- background-color: ${theme.colors.disabled};
2113
- cursor: not-allowed;
2114
- }
2115
- ${props.required && !localValue && `
2116
- border-color: ${theme.colors.required};
2117
- &:focus {
2118
- box-shadow: ${theme.controls.focusOutlineRequiredShadow};
2119
- }
2120
- `}
2121
- ${props.round && props.type !== 'textarea' && `
2122
- border-radius: ${theme.controls.roundRadius};
2123
- padding-left: calc(${theme.controls.padding} * 2);
2124
- padding-right: calc(${theme.controls.padding} * 2);
2125
- `}
2126
- ${props.rounded && `
2127
- border-radius: ${theme.controls.roundedRadius};
2128
- `}
2129
- ${props.readOnly && `
2130
- background-color: transparent;
2131
- cursor: default;
2132
- border: none;
2133
- &:focus {
2134
- outline: none;
2135
- box-shadow: none;
2136
- }
2137
- `}
2138
- ${props.rightControl && props.type !== 'textarea' && `
2139
- padding-right: ${theme.controls.height};
2140
- `}
2141
- `;
1938
+ const inputStyles = react.css({
1939
+ fontFamily: theme.fonts.family,
1940
+ fontSize: theme.fonts.size,
1941
+ width: '100%',
1942
+ border: theme.controls.border,
1943
+ color: theme.colors.font,
1944
+ paddingLeft: theme.controls.padding,
1945
+ paddingRight: theme.controls.padding,
1946
+ height: theme.controls.height,
1947
+ transition: theme.controls.transition,
1948
+ ':focus': {
1949
+ outline: 'none',
1950
+ boxShadow: theme.controls.focusOutlineShadow
1951
+ },
1952
+ ':disabled': {
1953
+ backgroundColor: theme.colors.disabled,
1954
+ cursor: 'not-allowed'
1955
+ },
1956
+ ':invalid': {
1957
+ borderColor: theme.colors.required,
1958
+ ':focus': {
1959
+ boxShadow: theme.controls.focusOutlineRequiredShadow
1960
+ }
1961
+ },
1962
+ }, props.round && props.type !== 'textarea' && {
1963
+ borderRadius: theme.controls.roundRadius,
1964
+ paddingLeft: `calc(${theme.controls.padding} * 2)`,
1965
+ paddingRight: `calc(${theme.controls.padding} * 2)`
1966
+ }, props.rounded && {
1967
+ borderRadius: theme.controls.roundedRadius
1968
+ }, props.readOnly && {
1969
+ backgroundColor: 'transparent',
1970
+ cursor: 'default',
1971
+ border: 'none',
1972
+ ':focus': {
1973
+ outline: 'none',
1974
+ boxShadow: 'none'
1975
+ }
1976
+ }, props.rightControl && props.type !== 'textarea' && {
1977
+ paddingRight: theme.controls.height
1978
+ });
2142
1979
  let inputElement;
2143
1980
  const onFocus = (e) => {
2144
1981
  var _a;
@@ -2151,78 +1988,84 @@ const Input = (props) => {
2151
1988
  trySyncLocalValue();
2152
1989
  (_a = props.onBlur) === null || _a === void 0 ? void 0 : _a.call(props, e);
2153
1990
  };
1991
+ let localOnChange;
2154
1992
  if (props.type === 'number') {
2155
- inputElement = react.jsx("input", { style: props.style, autoComplete: autoComplete, tabIndex: props.readOnly ? -1 : undefined, readOnly: props.readOnly,
2156
- // set fixed default to defeat pasting stupid numbers
2157
- maxLength: 50, min: props.min, max: props.max, required: props.required, disabled: props.disabled, id: props.id, css: inputStyles, className: props.inputClassName, placeholder: props.placeholder, type: "number", value: localValue, onFocus: onFocus, onBlur: onBlur, onChange: e => {
2158
- const value = e.target.value;
2159
- if (NUMBER_REGEX.test(value)) {
2160
- let numValue = parseFloat(value);
2161
- if (props.min && numValue < props.min) {
2162
- numValue = props.min;
2163
- }
2164
- if (props.max && numValue > props.max) {
2165
- numValue = props.max;
2166
- }
2167
- setLocalValue(numValue);
2168
- onChange === null || onChange === void 0 ? void 0 : onChange(numValue);
1993
+ localOnChange = e => {
1994
+ const value = e.target.value;
1995
+ if (NUMBER_REGEX.test(value)) {
1996
+ let numValue = parseFloat(value);
1997
+ if (props.min && numValue < props.min) {
1998
+ numValue = props.min;
2169
1999
  }
2170
- else if (!value) {
2171
- setLocalValue(value);
2172
- onChange === null || onChange === void 0 ? void 0 : onChange(undefined);
2000
+ if (props.max && numValue > props.max) {
2001
+ numValue = props.max;
2173
2002
  }
2174
- } });
2003
+ setLocalValue(numValue);
2004
+ outerOnChange === null || outerOnChange === void 0 ? void 0 : outerOnChange(numValue, props.name);
2005
+ }
2006
+ else if (!value) {
2007
+ setLocalValue(value);
2008
+ outerOnChange === null || outerOnChange === void 0 ? void 0 : outerOnChange(undefined, props.name);
2009
+ }
2010
+ };
2175
2011
  }
2176
2012
  else if (props.type === 'date') {
2177
- inputElement = react.jsx("input", { style: props.style, autoComplete: autoComplete, tabIndex: props.readOnly ? -1 : undefined, readOnly: props.readOnly, maxLength: 10, required: props.required, disabled: props.disabled, id: props.id, css: inputStyles, className: props.inputClassName, placeholder: props.placeholder, type: "text", value: localValue, onFocus: onFocus, onBlur: onBlur, onChange: e => {
2178
- const value = e.target.value;
2179
- setLocalValue(value);
2180
- if (onChange) {
2181
- const dateParts = DATE_REGEX.exec(value);
2182
- if (!dateParts) {
2183
- onChange(undefined);
2184
- }
2185
- else {
2186
- const year = parseInt(dateParts[3], 10);
2187
- const month = parseInt(dateParts[1], 10);
2188
- const day = parseInt(dateParts[2], 10);
2189
- if (dateFns.isExists(year, month, day)) {
2190
- let ms = new Date(year, month - 1, day).valueOf();
2191
- if (props.min && ms < props.min) {
2192
- ms = props.min;
2193
- }
2194
- if (props.max && ms > props.max) {
2195
- ms = props.max;
2196
- }
2197
- onChange(ms);
2013
+ localOnChange = e => {
2014
+ const value = e.target.value;
2015
+ setLocalValue(value);
2016
+ if (outerOnChange) {
2017
+ const dateParts = DATE_REGEX.exec(value);
2018
+ if (!dateParts) {
2019
+ outerOnChange(undefined, props.name);
2020
+ }
2021
+ else {
2022
+ const year = parseInt(dateParts[3], 10);
2023
+ const month = parseInt(dateParts[1], 10);
2024
+ const day = parseInt(dateParts[2], 10);
2025
+ if (dateFns.isExists(year, month, day)) {
2026
+ let ms = new Date(year, month - 1, day).valueOf();
2027
+ if (props.min && ms < props.min) {
2028
+ ms = props.min;
2198
2029
  }
2199
- else {
2200
- onChange(undefined);
2030
+ if (props.max && ms > props.max) {
2031
+ ms = props.max;
2201
2032
  }
2033
+ outerOnChange(ms, props.name);
2034
+ }
2035
+ else {
2036
+ outerOnChange(undefined, props.name);
2202
2037
  }
2203
2038
  }
2204
- } });
2039
+ }
2040
+ };
2041
+ }
2042
+ else {
2043
+ localOnChange = e => {
2044
+ const value = e.target.value;
2045
+ setLocalValue(value);
2046
+ outerOnChange === null || outerOnChange === void 0 ? void 0 : outerOnChange(value, props.name);
2047
+ };
2048
+ }
2049
+ if (props.type === 'number') {
2050
+ inputElement = react.jsx("input", { ref: ref, name: props.name, pattern: props.pattern, style: props.style, autoComplete: autoComplete, tabIndex: props.readOnly ? -1 : undefined, readOnly: props.readOnly,
2051
+ // set fixed default to defeat pasting stupid numbers
2052
+ maxLength: 50, min: props.min, max: props.max, required: props.required, disabled: props.disabled, id: props.id, css: inputStyles, className: props.inputClassName, placeholder: props.placeholder, type: "number", value: localValue, onFocus: onFocus, onBlur: onBlur, onChange: localOnChange });
2053
+ }
2054
+ else if (props.type === 'date') {
2055
+ inputElement = react.jsx("input", { ref: ref, name: props.name, pattern: props.pattern, style: props.style, autoComplete: autoComplete, tabIndex: props.readOnly ? -1 : undefined, readOnly: props.readOnly, maxLength: 10, required: props.required, disabled: props.disabled, id: props.id, css: inputStyles, className: props.inputClassName, placeholder: props.placeholder, type: "text", value: localValue, onFocus: onFocus, onBlur: onBlur, onChange: localOnChange });
2205
2056
  }
2206
2057
  else if (props.type === 'textarea') {
2207
- inputElement = react.jsx("textarea", { style: props.style, rows: props.rows || 10, autoComplete: autoComplete, tabIndex: props.readOnly ? -1 : undefined, readOnly: props.readOnly, maxLength: props.maxLength || DEFAULT_MAX_LENGTH, required: props.required, disabled: props.disabled, id: props.id, css: react.css `
2058
+ inputElement = react.jsx("textarea", { ref: ref, name: props.name, style: props.style, rows: props.rows || 10, autoComplete: autoComplete, tabIndex: props.readOnly ? -1 : undefined, readOnly: props.readOnly, maxLength: props.maxLength || DEFAULT_MAX_LENGTH, required: props.required, disabled: props.disabled, id: props.id, css: react.css `
2208
2059
  ${inputStyles}
2209
2060
  max-width: 100%;
2210
2061
  min-height: ${theme.controls.height};
2211
2062
  padding-top: 0.75rem;
2212
2063
  height:auto;
2213
- `, className: props.inputClassName, placeholder: props.placeholder, value: localValue, onFocus: onFocus, onBlur: onBlur, onChange: e => {
2214
- const value = e.target.value;
2215
- setLocalValue(value);
2216
- onChange === null || onChange === void 0 ? void 0 : onChange(value);
2217
- } });
2064
+ `, className: props.inputClassName, placeholder: props.placeholder, value: localValue, onFocus: onFocus, onBlur: onBlur, onChange: localOnChange });
2218
2065
  }
2219
2066
  else {
2220
- // text & password
2221
- inputElement = react.jsx("input", { style: props.style, autoComplete: autoComplete, tabIndex: props.readOnly ? -1 : undefined, readOnly: props.readOnly, maxLength: props.maxLength || DEFAULT_MAX_LENGTH, required: props.required, disabled: props.disabled, id: props.id, css: inputStyles, className: props.inputClassName, placeholder: props.placeholder, type: props.type, value: localValue, onFocus: onFocus, onBlur: onBlur, onChange: e => {
2222
- const value = e.target.value;
2223
- setLocalValue(value);
2224
- onChange === null || onChange === void 0 ? void 0 : onChange(value);
2225
- } });
2067
+ // text, password, email, and url
2068
+ inputElement = react.jsx("input", { ref: ref, name: props.name, pattern: props.pattern, style: props.style, autoComplete: autoComplete, tabIndex: props.readOnly ? -1 : undefined, readOnly: props.readOnly, maxLength: props.maxLength || DEFAULT_MAX_LENGTH, required: props.required, disabled: props.disabled, id: props.id, css: inputStyles, className: props.inputClassName, placeholder: props.placeholder, type: props.type, value: localValue, onFocus: onFocus, onBlur: onBlur, onChange: localOnChange });
2226
2069
  }
2227
2070
  const inputWrapperStyles = react.css `
2228
2071
  width:100%;
@@ -2244,7 +2087,7 @@ const Input = (props) => {
2244
2087
  return (react.jsx("div", { css: inputWrapperStyles, className: mergeClassNames('input', props.className) },
2245
2088
  inputElement,
2246
2089
  props.rightControl && props.type !== 'textarea' && react.jsx("div", { css: rightControlStyles }, props.rightControl)));
2247
- };
2090
+ });
2248
2091
 
2249
2092
  /** @jsx jsx */
2250
2093
  const List = (props) => {
@@ -3060,65 +2903,217 @@ const GlobalStyles = (p) => {
3060
2903
  }) }));
3061
2904
  };
3062
2905
 
2906
+ class FileListPlus {
2907
+ constructor(_raw, _args = {}) {
2908
+ this._raw = _raw;
2909
+ this._args = _args;
2910
+ this._files = Array.from(this._raw).map(f => {
2911
+ return { name: f.name, size: f.size, type: f.type };
2912
+ });
2913
+ if (this._args.accept) {
2914
+ const acceptTypes = this._args.accept.split(',');
2915
+ this._invalidFiles = this._files.filter(f => {
2916
+ if (acceptTypes.includes(f.type)) {
2917
+ return false;
2918
+ }
2919
+ if (acceptTypes.some(t => f.name.endsWith(t))) {
2920
+ return false;
2921
+ }
2922
+ return true;
2923
+ });
2924
+ }
2925
+ else {
2926
+ this._invalidFiles = [];
2927
+ }
2928
+ }
2929
+ get raw() {
2930
+ return this._raw;
2931
+ }
2932
+ get length() {
2933
+ return this._files.length;
2934
+ }
2935
+ get files() {
2936
+ return this._files;
2937
+ }
2938
+ get invalidFiles() {
2939
+ return this._invalidFiles;
2940
+ }
2941
+ get totalBytes() {
2942
+ return lodash.sumBy(this.files, f => f.size);
2943
+ }
2944
+ get overMaxBytes() {
2945
+ var _a;
2946
+ return this.totalBytes >= ((_a = this._args.maxBytes) !== null && _a !== void 0 ? _a : Infinity);
2947
+ }
2948
+ get overFileLimit() {
2949
+ return this.length > (this._args.multiple ? Infinity : 1);
2950
+ }
2951
+ get hasErrors() {
2952
+ return this.overMaxBytes || this.overFileLimit || !!this.invalidFiles.length;
2953
+ }
2954
+ }
2955
+
3063
2956
  /** @jsx jsx */
3064
2957
  const DEFAULT_FAILURE_MESSAGE = 'Upload failed.';
2958
+ const hoverClass = css.css({
2959
+ backgroundColor: 'rgba(0,0,0,0.25) !important'
2960
+ });
3065
2961
  const FileUploader = (p) => {
3066
- var _a;
2962
+ var _a, _b, _c, _d, _e, _f;
3067
2963
  const [message, setMessage] = React.useState(undefined);
3068
2964
  const [canUpload, setCanUpload] = React.useState(false);
3069
2965
  const [uploading, setUploading] = React.useState(false);
3070
2966
  const [files, setFiles] = React.useState(undefined);
3071
2967
  const [fullFailureMessage, setFullFailureMessage] = React.useState(undefined);
3072
- const clearFilePickerHandler = React.useRef(() => {
3073
- // noop
3074
- });
3075
- const { buttonText, successMessage, failureMessage, buttonWidth, buttonVariant, onChange } = p, filePickerProps = __rest(p, ["buttonText", "successMessage", "failureMessage", "buttonWidth", "buttonVariant", "onChange"]);
3076
- const onClear = () => {
3077
- var _a;
3078
- (_a = clearFilePickerHandler.current) === null || _a === void 0 ? void 0 : _a.call(clearFilePickerHandler);
2968
+ const form = React.useRef(null);
2969
+ const input = React.useRef(null);
2970
+ const totalFileSize = (_a = files === null || files === void 0 ? void 0 : files.totalBytes) !== null && _a !== void 0 ? _a : 0;
2971
+ const theme = useThemeSafely();
2972
+ let filesDisplay = '';
2973
+ if (!message) {
2974
+ if (!(files === null || files === void 0 ? void 0 : files.length)) {
2975
+ filesDisplay = `Click or drag and drop files.`;
2976
+ }
2977
+ else {
2978
+ filesDisplay = `${files.length.toLocaleString()} file${files.length > 1 ? 's' : ''} selected (${getSizeString(totalFileSize)}): ${files.files.map(f => f.name).join(', ')}`;
2979
+ }
2980
+ }
2981
+ const setAllFiles = (inputFiles) => {
2982
+ if (input.current && inputFiles === undefined) {
2983
+ input.current.value = '';
2984
+ }
2985
+ setFiles(inputFiles);
2986
+ };
2987
+ const onFilesChange = (rawFiles) => {
2988
+ setAllFiles(rawFiles ? createFileList(rawFiles) : undefined);
2989
+ setCanUpload(!!(rawFiles === null || rawFiles === void 0 ? void 0 : rawFiles.length));
3079
2990
  setMessage(undefined);
2991
+ setFullFailureMessage(undefined);
2992
+ };
2993
+ const createFileList = (rawFiles) => {
2994
+ return new FileListPlus(rawFiles, {
2995
+ accept: p.accept,
2996
+ multiple: p.multiple,
2997
+ maxBytes: p.maxBytes
2998
+ });
3080
2999
  };
3081
- return (react.jsx(Form, { onSubmit: () => {
3000
+ return (react.jsx(Form, { ref: form, css: {
3001
+ border: theme.controls.border,
3002
+ borderStyle: 'dashed',
3003
+ alignItems: 'center',
3004
+ justifyContent: 'center',
3005
+ position: 'relative',
3006
+ padding: '1rem',
3007
+ overflow: 'hidden',
3008
+ backgroundColor: theme.colors.lightBg,
3009
+ }, onDragOver: e => {
3010
+ var _a, _b;
3011
+ e.preventDefault();
3012
+ // we're using onDragOver instead of onDragEnter because *Enter and *Leave apparently can fire multiple times even though you're over the target.
3013
+ // *Over is continuous.
3014
+ if (!((_a = form.current) === null || _a === void 0 ? void 0 : _a.classList.contains(hoverClass))) {
3015
+ (_b = form.current) === null || _b === void 0 ? void 0 : _b.classList.add(hoverClass);
3016
+ }
3017
+ }, onDragLeave: e => {
3018
+ var _a;
3019
+ (_a = form.current) === null || _a === void 0 ? void 0 : _a.classList.remove(hoverClass);
3020
+ }, onDrop: e => {
3021
+ var _a;
3022
+ e.preventDefault();
3023
+ (_a = form.current) === null || _a === void 0 ? void 0 : _a.classList.remove(hoverClass);
3024
+ onFilesChange(e.dataTransfer.files);
3025
+ }, onSubmit: e => {
3082
3026
  if (!files) {
3083
3027
  return;
3084
3028
  }
3085
3029
  setUploading(true);
3086
- p.onUpload(files).then(() => {
3030
+ p.onUpload(files.raw).then(() => {
3087
3031
  setMessage('success');
3088
- if (p.clearOnUpload) {
3089
- clearFilePickerHandler.current();
3090
- }
3032
+ setAllFiles(undefined);
3091
3033
  }).catch(err => {
3034
+ var _a;
3092
3035
  setMessage('failure');
3093
- setFullFailureMessage(`${failureMessage !== null && failureMessage !== void 0 ? failureMessage : DEFAULT_FAILURE_MESSAGE} Error: ${err instanceof Error ? err.message : err}`);
3036
+ setFullFailureMessage(`${(_a = p.failureMessage) !== null && _a !== void 0 ? _a : DEFAULT_FAILURE_MESSAGE} Error: ${err instanceof Error ? err.message : err}`);
3094
3037
  }).finally(() => {
3095
3038
  setUploading(false);
3096
3039
  setCanUpload(false);
3097
3040
  });
3098
3041
  } },
3099
- react.jsx(FilePicker, Object.assign({}, filePickerProps, { onChange: filesFromPicker => {
3100
- setFiles(filesFromPicker);
3101
- setCanUpload(!!(filesFromPicker === null || filesFromPicker === void 0 ? void 0 : filesFromPicker.length));
3102
- setMessage(undefined);
3103
- setFullFailureMessage(undefined);
3104
- if (onChange) {
3105
- return onChange(filesFromPicker);
3042
+ react.jsx("input", { ref: input, css: {
3043
+ position: 'absolute',
3044
+ top: -50,
3045
+ left: 0,
3046
+ bottom: 0,
3047
+ right: 0,
3048
+ width: '100%',
3049
+ cursor: 'pointer',
3050
+ opacity: 0
3051
+ }, type: "file", multiple: p.multiple, accept: p.accept, onChange: e => {
3052
+ try {
3053
+ if (!e.target.files) {
3054
+ onFilesChange(undefined);
3055
+ return;
3056
+ }
3057
+ onFilesChange(e.target.files);
3106
3058
  }
3107
- return false;
3108
- }, __passClearFilesHandle: func => clearFilePickerHandler.current = func })),
3109
- react.jsx("span", null,
3110
- react.jsx(Button, { css: { width: (_a = p.buttonWidth) !== null && _a !== void 0 ? _a : '10rem' }, disabled: !canUpload, waiting: uploading, type: "submit", variant: buttonVariant !== null && buttonVariant !== void 0 ? buttonVariant : 'primary', rightIcon: react.jsx(Icon, { id: "save" }) }, buttonText !== null && buttonText !== void 0 ? buttonText : 'Upload')),
3111
- message === 'success' && (react.jsx(UploadInfoPanel, { variant: "positive", message: successMessage !== null && successMessage !== void 0 ? successMessage : 'Upload successful.', onClear: onClear })),
3112
- message === 'failure' && (react.jsx(UploadInfoPanel, { variant: "error", message: fullFailureMessage, onClear: onClear }))));
3059
+ catch (err) {
3060
+ // tslint:disable-next-line
3061
+ console.error(err);
3062
+ onFilesChange(undefined);
3063
+ }
3064
+ } }),
3065
+ !message && (react.jsx("span", { css: {
3066
+ display: 'flex',
3067
+ flexDirection: 'column',
3068
+ gap: '1rem',
3069
+ alignItems: 'center',
3070
+ zIndex: !!(files === null || files === void 0 ? void 0 : files.length) ? 1 : undefined
3071
+ } },
3072
+ !(files === null || files === void 0 ? void 0 : files.length) && react.jsx(Icon, { style: { fontSize: '2rem' }, id: "upload" }),
3073
+ react.jsx(Text, { align: "center", noPad: true, spacedOut: true },
3074
+ filesDisplay,
3075
+ !!(files === null || files === void 0 ? void 0 : files.length) && (react.jsx(Button, { onClick: e => {
3076
+ e.stopPropagation();
3077
+ onFilesChange(undefined);
3078
+ }, css: { marginLeft: '1rem', color: theme.colors.negative }, rightIcon: react.jsx(Icon, { id: "clear" }), variant: "inlineLink" }, "Clear"))),
3079
+ !!(files === null || files === void 0 ? void 0 : files.invalidFiles.length) && (react.jsx(InfoPanel, { variant: "error" },
3080
+ "Invalid files: ",
3081
+ files.invalidFiles.map(f => f.name).join(', '),
3082
+ ".")),
3083
+ (files === null || files === void 0 ? void 0 : files.overMaxBytes) && (react.jsx(InfoPanel, { variant: "error" },
3084
+ "Max file size exceeded (",
3085
+ getSizeString((_b = p.maxBytes) !== null && _b !== void 0 ? _b : 0),
3086
+ ").")))),
3087
+ canUpload && !(files === null || files === void 0 ? void 0 : files.hasErrors) && (react.jsx(Button, { css: {
3088
+ width: (_c = p.buttonWidth) !== null && _c !== void 0 ? _c : '10rem',
3089
+ zIndex: 1,
3090
+ }, waiting: uploading, type: "submit", variant: (_d = p.buttonVariant) !== null && _d !== void 0 ? _d : 'primary' }, (_e = p.buttonText) !== null && _e !== void 0 ? _e : 'Upload')),
3091
+ message === 'success' && (react.jsx(UploadInfoPanel, { variant: "positive", message: (_f = p.successMessage) !== null && _f !== void 0 ? _f : 'Upload successful.', onClear: () => setMessage(undefined) })),
3092
+ message === 'failure' && (react.jsx(UploadInfoPanel, { variant: "error", message: fullFailureMessage, onClear: () => setMessage(undefined) }))));
3113
3093
  };
3114
3094
  const UploadInfoPanel = (p) => {
3115
- useThemeSafely();
3116
- return (react.jsx(InfoPanel, { variant: p.variant },
3095
+ return (react.jsx(InfoPanel, { variant: p.variant, css: { zIndex: 1 } },
3117
3096
  p.message,
3118
- react.jsx(Button, { block: true, className: css.css({
3097
+ react.jsx(Button, { block: true, css: {
3119
3098
  color: 'inherit',
3120
3099
  marginTop: '1rem'
3121
- }), onClick: p.onClear, rightIcon: react.jsx(Icon, { id: "clear" }), variant: "inlineLink" }, "Clear")));
3100
+ }, onClick: e => {
3101
+ e.stopPropagation();
3102
+ p.onClear();
3103
+ }, rightIcon: react.jsx(Icon, { id: "clear" }), variant: "inlineLink" }, "Clear")));
3104
+ };
3105
+ const bytesInMb = 1048576;
3106
+ const bytesInKb = 1024;
3107
+ const getSizeString = (size) => {
3108
+ if (size < bytesInKb) {
3109
+ return size.toLocaleString() + ' B';
3110
+ }
3111
+ else if (size < bytesInMb) {
3112
+ return (size / bytesInKb).toLocaleString(undefined, { minimumFractionDigits: 1, maximumFractionDigits: 1 }) + ' KB';
3113
+ }
3114
+ else {
3115
+ return (size / bytesInMb).toLocaleString(undefined, { minimumFractionDigits: 1, maximumFractionDigits: 1 }) + ' MB';
3116
+ }
3122
3117
  };
3123
3118
 
3124
3119
  const CopyButton = (props) => {
@@ -3174,8 +3169,9 @@ const BoundStaticPager = (p) => {
3174
3169
  const Slider = (p) => {
3175
3170
  const theme = useThemeSafely();
3176
3171
  const currentValue = React.useRef(p.value);
3172
+ const sliderContainer = React.useRef(null);
3177
3173
  const height = p.showValue ? `calc(${theme.controls.height} + 1.5rem)` : theme.controls.height;
3178
- return (react.jsx("div", { css: {
3174
+ return (react.jsx("div", { ref: sliderContainer, css: {
3179
3175
  width: '100%',
3180
3176
  height,
3181
3177
  } },
@@ -3218,23 +3214,46 @@ const Slider = (p) => {
3218
3214
  '&:hover': {
3219
3215
  filter: theme.controls.hoverBrightness
3220
3216
  }
3221
- } }, props), p.showValue && react.jsx(HandleText, { value: currentValue.current, index: state.index, renderValue: p.renderValue, renderValueWidth: p.renderValueWidth })));
3217
+ } }, props), p.showValue && (react.jsx(HandleText, { index: state.index, parentElement: sliderContainer.current, value: Array.isArray(currentValue.current) ? currentValue.current[state.index] : currentValue.current, renderValue: p.renderValue, renderValueWidth: p.renderValueWidth }))));
3222
3218
  } })));
3223
3219
  };
3220
+ const rectsCollideX = (r1, r2) => {
3221
+ if (r1.left >= r2.left && r1.left <= r2.right) {
3222
+ return true;
3223
+ }
3224
+ if (r1.right >= r2.left && r1.right <= r2.right) {
3225
+ return true;
3226
+ }
3227
+ return false;
3228
+ };
3224
3229
  const HandleText = (p) => {
3225
- var _a, _b, _c, _d;
3230
+ var _a, _b, _c;
3226
3231
  const theme = useThemeSafely();
3227
- const value = Array.isArray(p.value) ? p.value[(_a = p.index) !== null && _a !== void 0 ? _a : 0] : p.value;
3228
- const displayValue = (_c = (_b = p.renderValue) === null || _b === void 0 ? void 0 : _b.call(p, value)) !== null && _c !== void 0 ? _c : value;
3229
- const renderValueWidth = (_d = p.renderValueWidth) !== null && _d !== void 0 ? _d : theme.controls.height;
3230
- const renderValueLeft = `calc(${renderValueWidth} * 0.5 * -1 + (${theme.controls.height} * 0.5))`;
3232
+ const displayValue = (_b = (_a = p.renderValue) === null || _a === void 0 ? void 0 : _a.call(p, p.value)) !== null && _b !== void 0 ? _b : p.value;
3233
+ const renderValueWidth = (_c = p.renderValueWidth) !== null && _c !== void 0 ? _c : theme.controls.height;
3234
+ const renderValueLeft = React.useMemo(() => {
3235
+ return `calc(${renderValueWidth} * 0.5 * -1 + (${theme.controls.height} * 0.5))`;
3236
+ }, [p.renderValueWidth]);
3237
+ const [flipText, setFlipText] = React.useState(false);
3238
+ React.useEffect(() => {
3239
+ // this needs to fire on every render due to the other handle also moving.
3240
+ var _a, _b;
3241
+ if (p.index === 1) {
3242
+ // only do this for the max/right-most handle
3243
+ const [r1, r2] = Array.from((_b = (_a = p.parentElement) === null || _a === void 0 ? void 0 : _a.querySelectorAll('.slider-handle').values()) !== null && _b !== void 0 ? _b : []).map(e => e.getBoundingClientRect());
3244
+ if (r1 && r2) {
3245
+ setFlipText(rectsCollideX(r1, r2));
3246
+ }
3247
+ }
3248
+ });
3231
3249
  return (react.jsx(Text, { ellipsis: true, css: {
3232
3250
  width: renderValueWidth,
3233
3251
  left: renderValueLeft,
3234
- bottom: '-1.5rem',
3252
+ top: flipText ? undefined : theme.controls.height,
3253
+ bottom: flipText ? theme.controls.height : undefined,
3235
3254
  position: 'absolute',
3236
3255
  overflow: 'hidden',
3237
- }, tag: "div", align: "center" }, displayValue));
3256
+ }, className: "slider-handle", tag: "div", align: "center" }, displayValue));
3238
3257
  };
3239
3258
 
3240
3259
  /** @jsx jsx */
@@ -3366,7 +3385,6 @@ exports.CopyButton = CopyButton;
3366
3385
  exports.DatePicker = DatePicker;
3367
3386
  exports.Divider = Divider;
3368
3387
  exports.ErrorModal = ErrorModal;
3369
- exports.FilePicker = FilePicker;
3370
3388
  exports.FileUploader = FileUploader;
3371
3389
  exports.Form = Form;
3372
3390
  exports.FormColumnRow = FormColumnRow;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mackin.com/styleguide",
3
- "version": "6.1.0",
3
+ "version": "7.0.2",
4
4
  "description": "",
5
5
  "main": "./index.js",
6
6
  "types": "./index.d.ts",