@mackin.com/styleguide 6.0.2 → 7.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (3) hide show
  1. package/index.d.ts +15 -27
  2. package/index.js +363 -343
  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 = React.ClassAttributes<HTMLFormElement> & React.FormHTMLAttributes<HTMLFormElement>;
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, "inline" | "key" | "css" | "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"> & 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
 
@@ -939,6 +925,8 @@ interface SliderProps<T extends SliderValue> {
939
925
  onUpdate?: (value: T) => void;
940
926
  /** Used with showValue. Will be called to render the display string. */
941
927
  renderValue?: (value: number) => string;
928
+ /** Used with renderValue for the custom element width. Defaults to theme.controls.height * 2. */
929
+ renderValueWidth?: string;
942
930
  }
943
931
  declare const Slider: <T extends SliderValue>(p: SliderProps<T>) => jsx.JSX.Element;
944
932
 
@@ -972,4 +960,4 @@ declare const Backdrop: (p: {
972
960
 
973
961
  declare const useMediaQuery: (query: string) => boolean;
974
962
 
975
- 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();
@@ -1835,6 +1644,7 @@ const FormColumnRow = (props) => {
1835
1644
  };
1836
1645
 
1837
1646
  /** @jsx jsx */
1647
+ //TB: have a sticky variant
1838
1648
  const Header = (props) => {
1839
1649
  const theme = useThemeSafely();
1840
1650
  const bodyStyles = css.css `
@@ -1951,6 +1761,38 @@ const Image = (props) => {
1951
1761
  }, alt: props.alt, style: props.style, className: mergeClassNames('image', props.className), src: props.src }));
1952
1762
  };
1953
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
+
1954
1796
  /** @jsx jsx */
1955
1797
  const Popover = (p) => {
1956
1798
  var _a, _b;
@@ -2066,19 +1908,19 @@ const formatLocalValue = (value, type) => {
2066
1908
  }
2067
1909
  return newValue;
2068
1910
  };
2069
- const Input = (props) => {
1911
+ const Input = React__namespace.forwardRef((props, ref) => {
2070
1912
  var _a, _b, _c;
2071
1913
  const [localValue, setLocalValue] = React__namespace.useState(formatLocalValue(props.value, props.type));
2072
1914
  const debounceMs = (_a = props.debounceMs) !== null && _a !== void 0 ? _a : DEFAULT_DEBOUNCE_MS;
2073
1915
  const autoComplete = (_b = props.autoComplete) !== null && _b !== void 0 ? _b : 'off';
2074
1916
  const vars = React__namespace.useRef({
2075
- wrappedOnChange: (props.onChange && debounceMs) ? lodash.debounce((value) => {
1917
+ wrappedOnChange: (props.onChange && debounceMs) ? lodash.debounce((value, name) => {
2076
1918
  var _a;
2077
- (_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);
2078
1920
  }, debounceMs) : undefined,
2079
1921
  focused: false
2080
1922
  });
2081
- 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;
2082
1924
  const trySyncLocalValue = () => {
2083
1925
  if (vars.current.focused) {
2084
1926
  return;
@@ -2091,53 +1933,49 @@ const Input = (props) => {
2091
1933
  };
2092
1934
  React__namespace.useEffect(() => {
2093
1935
  trySyncLocalValue();
2094
- });
1936
+ }, [props.value]);
2095
1937
  const theme = useThemeSafely();
2096
- const inputStyles = react.css `
2097
- font-family: ${theme.fonts.family};
2098
- font-size: ${theme.fonts.size};
2099
- width: 100%;
2100
- border: ${theme.controls.border};
2101
- color: ${theme.colors.font};
2102
- padding-left: ${theme.controls.padding};
2103
- padding-right: ${theme.controls.padding};
2104
- height: ${theme.controls.height};
2105
- transition: ${theme.controls.transition};
2106
- &:focus {
2107
- outline: none;
2108
- box-shadow: ${theme.controls.focusOutlineShadow};
2109
- }
2110
- &:disabled {
2111
- background-color: ${theme.colors.disabled};
2112
- cursor: not-allowed;
2113
- }
2114
- ${props.required && !localValue && `
2115
- border-color: ${theme.colors.required};
2116
- &:focus {
2117
- box-shadow: ${theme.controls.focusOutlineRequiredShadow};
2118
- }
2119
- `}
2120
- ${props.round && props.type !== 'textarea' && `
2121
- border-radius: ${theme.controls.roundRadius};
2122
- padding-left: calc(${theme.controls.padding} * 2);
2123
- padding-right: calc(${theme.controls.padding} * 2);
2124
- `}
2125
- ${props.rounded && `
2126
- border-radius: ${theme.controls.roundedRadius};
2127
- `}
2128
- ${props.readOnly && `
2129
- background-color: transparent;
2130
- cursor: default;
2131
- border: none;
2132
- &:focus {
2133
- outline: none;
2134
- box-shadow: none;
2135
- }
2136
- `}
2137
- ${props.rightControl && props.type !== 'textarea' && `
2138
- padding-right: ${theme.controls.height};
2139
- `}
2140
- `;
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
+ });
2141
1979
  let inputElement;
2142
1980
  const onFocus = (e) => {
2143
1981
  var _a;
@@ -2150,78 +1988,84 @@ const Input = (props) => {
2150
1988
  trySyncLocalValue();
2151
1989
  (_a = props.onBlur) === null || _a === void 0 ? void 0 : _a.call(props, e);
2152
1990
  };
1991
+ let localOnChange;
2153
1992
  if (props.type === 'number') {
2154
- inputElement = react.jsx("input", { style: props.style, autoComplete: autoComplete, tabIndex: props.readOnly ? -1 : undefined, readOnly: props.readOnly,
2155
- // set fixed default to defeat pasting stupid numbers
2156
- 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 => {
2157
- const value = e.target.value;
2158
- if (NUMBER_REGEX.test(value)) {
2159
- let numValue = parseFloat(value);
2160
- if (props.min && numValue < props.min) {
2161
- numValue = props.min;
2162
- }
2163
- if (props.max && numValue > props.max) {
2164
- numValue = props.max;
2165
- }
2166
- setLocalValue(numValue);
2167
- 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;
2168
1999
  }
2169
- else if (!value) {
2170
- setLocalValue(value);
2171
- onChange === null || onChange === void 0 ? void 0 : onChange(undefined);
2000
+ if (props.max && numValue > props.max) {
2001
+ numValue = props.max;
2172
2002
  }
2173
- } });
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
+ };
2174
2011
  }
2175
2012
  else if (props.type === 'date') {
2176
- 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 => {
2177
- const value = e.target.value;
2178
- setLocalValue(value);
2179
- if (onChange) {
2180
- const dateParts = DATE_REGEX.exec(value);
2181
- if (!dateParts) {
2182
- onChange(undefined);
2183
- }
2184
- else {
2185
- const year = parseInt(dateParts[3], 10);
2186
- const month = parseInt(dateParts[1], 10);
2187
- const day = parseInt(dateParts[2], 10);
2188
- if (dateFns.isExists(year, month, day)) {
2189
- let ms = new Date(year, month - 1, day).valueOf();
2190
- if (props.min && ms < props.min) {
2191
- ms = props.min;
2192
- }
2193
- if (props.max && ms > props.max) {
2194
- ms = props.max;
2195
- }
2196
- 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;
2197
2029
  }
2198
- else {
2199
- onChange(undefined);
2030
+ if (props.max && ms > props.max) {
2031
+ ms = props.max;
2200
2032
  }
2033
+ outerOnChange(ms, props.name);
2034
+ }
2035
+ else {
2036
+ outerOnChange(undefined, props.name);
2201
2037
  }
2202
2038
  }
2203
- } });
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 });
2204
2056
  }
2205
2057
  else if (props.type === 'textarea') {
2206
- 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 `
2207
2059
  ${inputStyles}
2208
2060
  max-width: 100%;
2209
2061
  min-height: ${theme.controls.height};
2210
2062
  padding-top: 0.75rem;
2211
2063
  height:auto;
2212
- `, className: props.inputClassName, placeholder: props.placeholder, value: localValue, onFocus: onFocus, onBlur: onBlur, onChange: e => {
2213
- const value = e.target.value;
2214
- setLocalValue(value);
2215
- onChange === null || onChange === void 0 ? void 0 : onChange(value);
2216
- } });
2064
+ `, className: props.inputClassName, placeholder: props.placeholder, value: localValue, onFocus: onFocus, onBlur: onBlur, onChange: localOnChange });
2217
2065
  }
2218
2066
  else {
2219
- // text & password
2220
- 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 => {
2221
- const value = e.target.value;
2222
- setLocalValue(value);
2223
- onChange === null || onChange === void 0 ? void 0 : onChange(value);
2224
- } });
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 });
2225
2069
  }
2226
2070
  const inputWrapperStyles = react.css `
2227
2071
  width:100%;
@@ -2243,7 +2087,7 @@ const Input = (props) => {
2243
2087
  return (react.jsx("div", { css: inputWrapperStyles, className: mergeClassNames('input', props.className) },
2244
2088
  inputElement,
2245
2089
  props.rightControl && props.type !== 'textarea' && react.jsx("div", { css: rightControlStyles }, props.rightControl)));
2246
- };
2090
+ });
2247
2091
 
2248
2092
  /** @jsx jsx */
2249
2093
  const List = (props) => {
@@ -3059,65 +2903,217 @@ const GlobalStyles = (p) => {
3059
2903
  }) }));
3060
2904
  };
3061
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
+
3062
2956
  /** @jsx jsx */
3063
2957
  const DEFAULT_FAILURE_MESSAGE = 'Upload failed.';
2958
+ const hoverClass = css.css({
2959
+ backgroundColor: 'rgba(0,0,0,0.25) !important'
2960
+ });
3064
2961
  const FileUploader = (p) => {
3065
- var _a;
2962
+ var _a, _b, _c, _d, _e, _f;
3066
2963
  const [message, setMessage] = React.useState(undefined);
3067
2964
  const [canUpload, setCanUpload] = React.useState(false);
3068
2965
  const [uploading, setUploading] = React.useState(false);
3069
2966
  const [files, setFiles] = React.useState(undefined);
3070
2967
  const [fullFailureMessage, setFullFailureMessage] = React.useState(undefined);
3071
- const clearFilePickerHandler = React.useRef(() => {
3072
- // noop
3073
- });
3074
- const { buttonText, successMessage, failureMessage, buttonWidth, buttonVariant, onChange } = p, filePickerProps = __rest(p, ["buttonText", "successMessage", "failureMessage", "buttonWidth", "buttonVariant", "onChange"]);
3075
- const onClear = () => {
3076
- var _a;
3077
- (_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));
3078
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
+ });
3079
2999
  };
3080
- 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 => {
3081
3026
  if (!files) {
3082
3027
  return;
3083
3028
  }
3084
3029
  setUploading(true);
3085
- p.onUpload(files).then(() => {
3030
+ p.onUpload(files.raw).then(() => {
3086
3031
  setMessage('success');
3087
- if (p.clearOnUpload) {
3088
- clearFilePickerHandler.current();
3089
- }
3032
+ setAllFiles(undefined);
3090
3033
  }).catch(err => {
3034
+ var _a;
3091
3035
  setMessage('failure');
3092
- 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}`);
3093
3037
  }).finally(() => {
3094
3038
  setUploading(false);
3095
3039
  setCanUpload(false);
3096
3040
  });
3097
3041
  } },
3098
- react.jsx(FilePicker, Object.assign({}, filePickerProps, { onChange: filesFromPicker => {
3099
- setFiles(filesFromPicker);
3100
- setCanUpload(!!(filesFromPicker === null || filesFromPicker === void 0 ? void 0 : filesFromPicker.length));
3101
- setMessage(undefined);
3102
- setFullFailureMessage(undefined);
3103
- if (onChange) {
3104
- 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);
3105
3058
  }
3106
- return false;
3107
- }, __passClearFilesHandle: func => clearFilePickerHandler.current = func })),
3108
- react.jsx("span", null,
3109
- 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')),
3110
- message === 'success' && (react.jsx(UploadInfoPanel, { variant: "positive", message: successMessage !== null && successMessage !== void 0 ? successMessage : 'Upload successful.', onClear: onClear })),
3111
- 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) }))));
3112
3093
  };
3113
3094
  const UploadInfoPanel = (p) => {
3114
- useThemeSafely();
3115
- return (react.jsx(InfoPanel, { variant: p.variant },
3095
+ return (react.jsx(InfoPanel, { variant: p.variant, css: { zIndex: 1 } },
3116
3096
  p.message,
3117
- react.jsx(Button, { block: true, className: css.css({
3097
+ react.jsx(Button, { block: true, css: {
3118
3098
  color: 'inherit',
3119
3099
  marginTop: '1rem'
3120
- }), 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
+ }
3121
3117
  };
3122
3118
 
3123
3119
  const CopyButton = (props) => {
@@ -3173,8 +3169,9 @@ const BoundStaticPager = (p) => {
3173
3169
  const Slider = (p) => {
3174
3170
  const theme = useThemeSafely();
3175
3171
  const currentValue = React.useRef(p.value);
3172
+ const sliderContainer = React.useRef(null);
3176
3173
  const height = p.showValue ? `calc(${theme.controls.height} + 1.5rem)` : theme.controls.height;
3177
- return (react.jsx("div", { css: {
3174
+ return (react.jsx("div", { ref: sliderContainer, css: {
3178
3175
  width: '100%',
3179
3176
  height,
3180
3177
  } },
@@ -3217,22 +3214,46 @@ const Slider = (p) => {
3217
3214
  '&:hover': {
3218
3215
  filter: theme.controls.hoverBrightness
3219
3216
  }
3220
- } }, props), p.showValue && react.jsx(HandleText, { value: currentValue.current, index: state.index, renderValue: p.renderValue })));
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 }))));
3221
3218
  } })));
3222
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
+ };
3223
3229
  const HandleText = (p) => {
3224
3230
  var _a, _b, _c;
3225
3231
  const theme = useThemeSafely();
3226
- const value = Array.isArray(p.value) ? p.value[(_a = p.index) !== null && _a !== void 0 ? _a : 0] : p.value;
3227
- const displayValue = (_c = (_b = p.renderValue) === null || _b === void 0 ? void 0 : _b.call(p, value)) !== null && _c !== void 0 ? _c : value;
3228
- return (react.jsx(Text, { css: {
3229
- //width: theme.controls.height,
3230
- width: `calc(${theme.controls.height} * 2)`,
3231
- left: `calc(${theme.controls.height} / 2 * -1)`,
3232
- bottom: '-1.5rem',
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
+ });
3249
+ return (react.jsx(Text, { ellipsis: true, css: {
3250
+ width: renderValueWidth,
3251
+ left: renderValueLeft,
3252
+ top: flipText ? undefined : theme.controls.height,
3253
+ bottom: flipText ? theme.controls.height : undefined,
3233
3254
  position: 'absolute',
3234
- overflow: 'hidden'
3235
- }, tag: "div", align: "center" }, displayValue));
3255
+ overflow: 'hidden',
3256
+ }, className: "slider-handle", tag: "div", align: "center" }, displayValue));
3236
3257
  };
3237
3258
 
3238
3259
  /** @jsx jsx */
@@ -3364,7 +3385,6 @@ exports.CopyButton = CopyButton;
3364
3385
  exports.DatePicker = DatePicker;
3365
3386
  exports.Divider = Divider;
3366
3387
  exports.ErrorModal = ErrorModal;
3367
- exports.FilePicker = FilePicker;
3368
3388
  exports.FileUploader = FileUploader;
3369
3389
  exports.Form = Form;
3370
3390
  exports.FormColumnRow = FormColumnRow;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mackin.com/styleguide",
3
- "version": "6.0.2",
3
+ "version": "7.0.1",
4
4
  "description": "",
5
5
  "main": "./index.js",
6
6
  "types": "./index.d.ts",