@mackin.com/styleguide 6.0.1 → 7.0.0

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 -26
  2. package/index.js +365 -352
  3. package/package.json +1 -1
package/index.d.ts CHANGED
@@ -262,29 +262,11 @@ 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
265
  interface IProps extends React.DetailedHTMLProps<React.FormHTMLAttributes<HTMLFormElement>, HTMLFormElement> {
284
266
  inline?: boolean;
285
267
  }
286
268
  /** 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;
269
+ 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<unknown>>;
288
270
  declare const FormFlexRow: (props: {
289
271
  children: React.ReactNode;
290
272
  className?: string;
@@ -374,9 +356,10 @@ declare const InfoTip: (props: InfoTipProps) => jsx.JSX.Element;
374
356
  /** @jsx jsx */
375
357
 
376
358
  declare type InputValue = string | number | undefined;
377
- declare type InputType = 'text' | 'number' | 'textarea' | 'date' | 'password';
359
+ declare type InputType = 'text' | 'number' | 'textarea' | 'date' | 'password' | 'url' | 'email';
378
360
  interface InputProps {
379
361
  type: InputType;
362
+ name?: string;
380
363
  style?: React.CSSProperties;
381
364
  value?: InputValue;
382
365
  rounded?: boolean;
@@ -392,7 +375,7 @@ interface InputProps {
392
375
  /** Defaults to 'off'. */
393
376
  autoComplete?: string;
394
377
  /** Called with debounce when the input changes. */
395
- onChange?: (value: InputValue) => void;
378
+ onChange?: (value: InputValue, name?: string) => void;
396
379
  onFocus?: (event: React.FocusEvent<HTMLInputElement | HTMLTextAreaElement>) => void;
397
380
  onBlur?: (event: React.FocusEvent<HTMLInputElement | HTMLTextAreaElement>) => void;
398
381
  /** Defaults to 100. Ignored for type=number and type=date. */
@@ -407,8 +390,9 @@ interface InputProps {
407
390
  max?: number;
408
391
  /** Only used for type=textarea. Defaults to 10. */
409
392
  rows?: number;
393
+ pattern?: string;
410
394
  }
411
- declare const Input: (props: InputProps) => jsx.JSX.Element;
395
+ declare const Input: React.ForwardRefExoticComponent<InputProps & React.RefAttributes<any>>;
412
396
 
413
397
  /** @jsx jsx */
414
398
 
@@ -816,7 +800,7 @@ declare const TabLocker: (props: {
816
800
 
817
801
  /** @jsx jsx */
818
802
 
819
- interface FileUploaderProps extends FilePickerProps {
803
+ interface FileUploaderProps {
820
804
  onUpload: (files: FileList) => Promise<void>;
821
805
  /** Defaults to 'Upload'. */
822
806
  buttonText?: string;
@@ -828,8 +812,9 @@ interface FileUploaderProps extends FilePickerProps {
828
812
  buttonWidth?: string;
829
813
  /** Defaults to 'primary'. */
830
814
  buttonVariant?: ButtonVariant;
831
- /** Will clear all files on upload success. */
832
- clearOnUpload?: boolean;
815
+ maxBytes?: number;
816
+ multiple?: boolean;
817
+ accept?: string;
833
818
  }
834
819
  declare const FileUploader: (p: FileUploaderProps) => jsx.JSX.Element;
835
820
 
@@ -939,6 +924,8 @@ interface SliderProps<T extends SliderValue> {
939
924
  onUpdate?: (value: T) => void;
940
925
  /** Used with showValue. Will be called to render the display string. */
941
926
  renderValue?: (value: number) => string;
927
+ /** Used with renderValue for the custom element width. Defaults to theme.controls.height * 2. */
928
+ renderValueWidth?: string;
942
929
  }
943
930
  declare const Slider: <T extends SliderValue>(p: SliderProps<T>) => jsx.JSX.Element;
944
931
 
@@ -972,4 +959,4 @@ declare const Backdrop: (p: {
972
959
 
973
960
  declare const useMediaQuery: (query: string) => boolean;
974
961
 
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 };
962
+ 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,18 +1908,19 @@ const formatLocalValue = (value, type) => {
2066
1908
  }
2067
1909
  return newValue;
2068
1910
  };
2069
- const Input = (props) => {
2070
- var _a, _b;
1911
+ const Input = React__namespace.forwardRef((props, ref) => {
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);
2078
- }, debounceMs) : props.onChange,
1919
+ (_a = props.onChange) === null || _a === void 0 ? void 0 : _a.call(props, value, name);
1920
+ }, debounceMs) : undefined,
2079
1921
  focused: false
2080
1922
  });
1923
+ const outerOnChange = (_c = vars.current.wrappedOnChange) !== null && _c !== void 0 ? _c : props.onChange;
2081
1924
  const trySyncLocalValue = () => {
2082
1925
  if (vars.current.focused) {
2083
1926
  return;
@@ -2090,53 +1933,49 @@ const Input = (props) => {
2090
1933
  };
2091
1934
  React__namespace.useEffect(() => {
2092
1935
  trySyncLocalValue();
2093
- });
1936
+ }, [props.value]);
2094
1937
  const theme = useThemeSafely();
2095
- const inputStyles = react.css `
2096
- font-family: ${theme.fonts.family};
2097
- font-size: ${theme.fonts.size};
2098
- width: 100%;
2099
- border: ${theme.controls.border};
2100
- color: ${theme.colors.font};
2101
- padding-left: ${theme.controls.padding};
2102
- padding-right: ${theme.controls.padding};
2103
- height: ${theme.controls.height};
2104
- transition: ${theme.controls.transition};
2105
- &:focus {
2106
- outline: none;
2107
- box-shadow: ${theme.controls.focusOutlineShadow};
2108
- }
2109
- &:disabled {
2110
- background-color: ${theme.colors.disabled};
2111
- cursor: not-allowed;
2112
- }
2113
- ${props.required && !localValue && `
2114
- border-color: ${theme.colors.required};
2115
- &:focus {
2116
- box-shadow: ${theme.controls.focusOutlineRequiredShadow};
2117
- }
2118
- `}
2119
- ${props.round && props.type !== 'textarea' && `
2120
- border-radius: ${theme.controls.roundRadius};
2121
- padding-left: calc(${theme.controls.padding} * 2);
2122
- padding-right: calc(${theme.controls.padding} * 2);
2123
- `}
2124
- ${props.rounded && `
2125
- border-radius: ${theme.controls.roundedRadius};
2126
- `}
2127
- ${props.readOnly && `
2128
- background-color: transparent;
2129
- cursor: default;
2130
- border: none;
2131
- &:focus {
2132
- outline: none;
2133
- box-shadow: none;
2134
- }
2135
- `}
2136
- ${props.rightControl && props.type !== 'textarea' && `
2137
- padding-right: ${theme.controls.height};
2138
- `}
2139
- `;
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
+ });
2140
1979
  let inputElement;
2141
1980
  const onFocus = (e) => {
2142
1981
  var _a;
@@ -2149,86 +1988,84 @@ const Input = (props) => {
2149
1988
  trySyncLocalValue();
2150
1989
  (_a = props.onBlur) === null || _a === void 0 ? void 0 : _a.call(props, e);
2151
1990
  };
1991
+ let localOnChange;
2152
1992
  if (props.type === 'number') {
2153
- inputElement = react.jsx("input", { style: props.style, autoComplete: autoComplete, tabIndex: props.readOnly ? -1 : undefined, readOnly: props.readOnly,
2154
- // set fixed default to defeat pasting stupid numbers
2155
- 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 => {
2156
- const value = e.target.value;
2157
- if (NUMBER_REGEX.test(value)) {
2158
- let numValue = parseFloat(value);
2159
- if (props.min && numValue < props.min) {
2160
- numValue = props.min;
2161
- }
2162
- if (props.max && numValue > props.max) {
2163
- numValue = props.max;
2164
- }
2165
- setLocalValue(numValue);
2166
- if (vars.current.wrappedOnChange) {
2167
- vars.current.wrappedOnChange(numValue);
2168
- }
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
- if (vars.current.wrappedOnChange) {
2173
- vars.current.wrappedOnChange(undefined);
2174
- }
2000
+ if (props.max && numValue > props.max) {
2001
+ numValue = props.max;
2175
2002
  }
2176
- } });
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
+ };
2177
2011
  }
2178
2012
  else if (props.type === 'date') {
2179
- 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 => {
2180
- const value = e.target.value;
2181
- setLocalValue(value);
2182
- if (vars.current.wrappedOnChange) {
2183
- const dateParts = DATE_REGEX.exec(value);
2184
- if (!dateParts) {
2185
- vars.current.wrappedOnChange(undefined);
2186
- }
2187
- else {
2188
- const year = parseInt(dateParts[3], 10);
2189
- const month = parseInt(dateParts[1], 10);
2190
- const day = parseInt(dateParts[2], 10);
2191
- if (dateFns.isExists(year, month, day)) {
2192
- let ms = new Date(year, month - 1, day).valueOf();
2193
- if (props.min && ms < props.min) {
2194
- ms = props.min;
2195
- }
2196
- if (props.max && ms > props.max) {
2197
- ms = props.max;
2198
- }
2199
- vars.current.wrappedOnChange(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;
2200
2029
  }
2201
- else {
2202
- vars.current.wrappedOnChange(undefined);
2030
+ if (props.max && ms > props.max) {
2031
+ ms = props.max;
2203
2032
  }
2033
+ outerOnChange(ms, props.name);
2034
+ }
2035
+ else {
2036
+ outerOnChange(undefined, props.name);
2204
2037
  }
2205
2038
  }
2206
- } });
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 });
2207
2056
  }
2208
2057
  else if (props.type === 'textarea') {
2209
- 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 `
2210
2059
  ${inputStyles}
2211
2060
  max-width: 100%;
2212
2061
  min-height: ${theme.controls.height};
2213
2062
  padding-top: 0.75rem;
2214
2063
  height:auto;
2215
- `, className: props.inputClassName, placeholder: props.placeholder, value: localValue, onFocus: onFocus, onBlur: onBlur, onChange: e => {
2216
- const value = e.target.value;
2217
- setLocalValue(value);
2218
- if (vars.current.wrappedOnChange) {
2219
- vars.current.wrappedOnChange(value);
2220
- }
2221
- } });
2064
+ `, className: props.inputClassName, placeholder: props.placeholder, value: localValue, onFocus: onFocus, onBlur: onBlur, onChange: localOnChange });
2222
2065
  }
2223
2066
  else {
2224
- // text & password
2225
- 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 => {
2226
- const value = e.target.value;
2227
- setLocalValue(value);
2228
- if (vars.current.wrappedOnChange) {
2229
- vars.current.wrappedOnChange(value);
2230
- }
2231
- } });
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 });
2232
2069
  }
2233
2070
  const inputWrapperStyles = react.css `
2234
2071
  width:100%;
@@ -2250,7 +2087,7 @@ const Input = (props) => {
2250
2087
  return (react.jsx("div", { css: inputWrapperStyles, className: mergeClassNames('input', props.className) },
2251
2088
  inputElement,
2252
2089
  props.rightControl && props.type !== 'textarea' && react.jsx("div", { css: rightControlStyles }, props.rightControl)));
2253
- };
2090
+ });
2254
2091
 
2255
2092
  /** @jsx jsx */
2256
2093
  const List = (props) => {
@@ -3066,65 +2903,217 @@ const GlobalStyles = (p) => {
3066
2903
  }) }));
3067
2904
  };
3068
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
+
3069
2956
  /** @jsx jsx */
3070
2957
  const DEFAULT_FAILURE_MESSAGE = 'Upload failed.';
2958
+ const hoverClass = css.css({
2959
+ backgroundColor: 'rgba(0,0,0,0.25) !important'
2960
+ });
3071
2961
  const FileUploader = (p) => {
3072
- var _a;
2962
+ var _a, _b, _c, _d, _e, _f;
3073
2963
  const [message, setMessage] = React.useState(undefined);
3074
2964
  const [canUpload, setCanUpload] = React.useState(false);
3075
2965
  const [uploading, setUploading] = React.useState(false);
3076
2966
  const [files, setFiles] = React.useState(undefined);
3077
2967
  const [fullFailureMessage, setFullFailureMessage] = React.useState(undefined);
3078
- const clearFilePickerHandler = React.useRef(() => {
3079
- // noop
3080
- });
3081
- const { buttonText, successMessage, failureMessage, buttonWidth, buttonVariant, onChange } = p, filePickerProps = __rest(p, ["buttonText", "successMessage", "failureMessage", "buttonWidth", "buttonVariant", "onChange"]);
3082
- const onClear = () => {
3083
- var _a;
3084
- (_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));
3085
2990
  setMessage(undefined);
2991
+ setFullFailureMessage(undefined);
3086
2992
  };
3087
- return (react.jsx(Form, { onSubmit: () => {
2993
+ const createFileList = (rawFiles) => {
2994
+ return new FileListPlus(rawFiles, {
2995
+ accept: p.accept,
2996
+ multiple: p.multiple,
2997
+ maxBytes: p.maxBytes
2998
+ });
2999
+ };
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 => {
3088
3026
  if (!files) {
3089
3027
  return;
3090
3028
  }
3091
3029
  setUploading(true);
3092
- p.onUpload(files).then(() => {
3030
+ p.onUpload(files.raw).then(() => {
3093
3031
  setMessage('success');
3094
- if (p.clearOnUpload) {
3095
- clearFilePickerHandler.current();
3096
- }
3032
+ setAllFiles(undefined);
3097
3033
  }).catch(err => {
3034
+ var _a;
3098
3035
  setMessage('failure');
3099
- 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}`);
3100
3037
  }).finally(() => {
3101
3038
  setUploading(false);
3102
3039
  setCanUpload(false);
3103
3040
  });
3104
3041
  } },
3105
- react.jsx(FilePicker, Object.assign({}, filePickerProps, { onChange: filesFromPicker => {
3106
- setFiles(filesFromPicker);
3107
- setCanUpload(!!(filesFromPicker === null || filesFromPicker === void 0 ? void 0 : filesFromPicker.length));
3108
- setMessage(undefined);
3109
- setFullFailureMessage(undefined);
3110
- if (onChange) {
3111
- 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);
3112
3058
  }
3113
- return false;
3114
- }, __passClearFilesHandle: func => clearFilePickerHandler.current = func })),
3115
- react.jsx("span", null,
3116
- 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')),
3117
- message === 'success' && (react.jsx(UploadInfoPanel, { variant: "positive", message: successMessage !== null && successMessage !== void 0 ? successMessage : 'Upload successful.', onClear: onClear })),
3118
- 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) }))));
3119
3093
  };
3120
3094
  const UploadInfoPanel = (p) => {
3121
- useThemeSafely();
3122
- return (react.jsx(InfoPanel, { variant: p.variant },
3095
+ return (react.jsx(InfoPanel, { variant: p.variant, css: { zIndex: 1 } },
3123
3096
  p.message,
3124
- react.jsx(Button, { block: true, className: css.css({
3097
+ react.jsx(Button, { block: true, css: {
3125
3098
  color: 'inherit',
3126
3099
  marginTop: '1rem'
3127
- }), 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
+ }
3128
3117
  };
3129
3118
 
3130
3119
  const CopyButton = (props) => {
@@ -3180,8 +3169,9 @@ const BoundStaticPager = (p) => {
3180
3169
  const Slider = (p) => {
3181
3170
  const theme = useThemeSafely();
3182
3171
  const currentValue = React.useRef(p.value);
3172
+ const sliderContainer = React.useRef(null);
3183
3173
  const height = p.showValue ? `calc(${theme.controls.height} + 1.5rem)` : theme.controls.height;
3184
- return (react.jsx("div", { css: {
3174
+ return (react.jsx("div", { ref: sliderContainer, css: {
3185
3175
  width: '100%',
3186
3176
  height,
3187
3177
  } },
@@ -3224,22 +3214,46 @@ const Slider = (p) => {
3224
3214
  '&:hover': {
3225
3215
  filter: theme.controls.hoverBrightness
3226
3216
  }
3227
- } }, 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 }))));
3228
3218
  } })));
3229
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
+ };
3230
3229
  const HandleText = (p) => {
3231
3230
  var _a, _b, _c;
3232
3231
  const theme = useThemeSafely();
3233
- const value = Array.isArray(p.value) ? p.value[(_a = p.index) !== null && _a !== void 0 ? _a : 0] : p.value;
3234
- const displayValue = (_c = (_b = p.renderValue) === null || _b === void 0 ? void 0 : _b.call(p, value)) !== null && _c !== void 0 ? _c : value;
3235
- return (react.jsx(Text, { css: {
3236
- //width: theme.controls.height,
3237
- width: `calc(${theme.controls.height} * 2)`,
3238
- left: `calc(${theme.controls.height} / 2 * -1)`,
3239
- 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,
3240
3254
  position: 'absolute',
3241
- overflow: 'hidden'
3242
- }, tag: "div", align: "center" }, displayValue));
3255
+ overflow: 'hidden',
3256
+ }, className: "slider-handle", tag: "div", align: "center" }, displayValue));
3243
3257
  };
3244
3258
 
3245
3259
  /** @jsx jsx */
@@ -3371,7 +3385,6 @@ exports.CopyButton = CopyButton;
3371
3385
  exports.DatePicker = DatePicker;
3372
3386
  exports.Divider = Divider;
3373
3387
  exports.ErrorModal = ErrorModal;
3374
- exports.FilePicker = FilePicker;
3375
3388
  exports.FileUploader = FileUploader;
3376
3389
  exports.Form = Form;
3377
3390
  exports.FormColumnRow = FormColumnRow;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mackin.com/styleguide",
3
- "version": "6.0.1",
3
+ "version": "7.0.0",
4
4
  "description": "",
5
5
  "main": "./index.js",
6
6
  "types": "./index.d.ts",