@oatfi/ui 0.3.0 → 0.5.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.
package/dist/index.cjs.js CHANGED
@@ -2,7 +2,7 @@
2
2
 
3
3
  var styles = require('@mui/material/styles');
4
4
  var jsxRuntime = require('react/jsx-runtime');
5
- var react = require('react');
5
+ var React = require('react');
6
6
  var styled = require('styled-components');
7
7
  var material = require('@mui/material');
8
8
  var CalendarMonthIcon = require('@mui/icons-material/CalendarMonth');
@@ -12,6 +12,17 @@ var IconButton = require('@mui/material/IconButton');
12
12
  var Menu = require('@mui/material/Menu');
13
13
  var MenuItem = require('@mui/material/MenuItem');
14
14
  var MoreVertIcon = require('@mui/icons-material/MoreVert');
15
+ var DescriptionIcon = require('@mui/icons-material/Description');
16
+ var CircularProgress = require('@mui/material/CircularProgress');
17
+ var ExpandMoreIcon = require('@mui/icons-material/ExpandMore');
18
+ var LinkIcon = require('@mui/icons-material/Link');
19
+ var RefreshIcon = require('@mui/icons-material/Refresh');
20
+ var Box = require('@mui/material/Box');
21
+ var Button$1 = require('@mui/material/Button');
22
+ var CheckRoundedIcon = require('@mui/icons-material/CheckRounded');
23
+ var Stack = require('@mui/material/Stack');
24
+ var LockIcon = require('@mui/icons-material/Lock');
25
+ var DeleteOutlineIcon = require('@mui/icons-material/DeleteOutline');
15
26
 
16
27
  const defaultTheme = {
17
28
  colors: {
@@ -219,8 +230,8 @@ const GlobalStyle = styled.createGlobalStyle`
219
230
  }
220
231
  `;
221
232
  function ThemeProvider({ branding, children }) {
222
- const theme = react.useMemo(() => mergeBranding(branding), [branding]);
223
- const muiTheme = react.useMemo(() => buildMuiTheme(theme), [theme]);
233
+ const theme = React.useMemo(() => mergeBranding(branding), [branding]);
234
+ const muiTheme = React.useMemo(() => buildMuiTheme(theme), [theme]);
224
235
  return /* @__PURE__ */ jsxRuntime.jsxs(styled.ThemeProvider, { theme, children: [
225
236
  /* @__PURE__ */ jsxRuntime.jsx(GlobalStyle, {}),
226
237
  /* @__PURE__ */ jsxRuntime.jsx(styles.ThemeProvider, { theme: muiTheme, children })
@@ -431,7 +442,7 @@ const Container = styled.div`
431
442
  max-width: 1000px;
432
443
  margin: 0 auto;
433
444
  `;
434
- const Content = styled.div`
445
+ const Content$1 = styled.div`
435
446
  margin-top: 1.5rem;
436
447
  margin-bottom: 1.5rem;
437
448
  `;
@@ -814,7 +825,7 @@ function CurrencyInput({
814
825
  name,
815
826
  testId
816
827
  }) {
817
- const reactId = react.useId();
828
+ const reactId = React.useId();
818
829
  const inputId = `${reactId}-input`;
819
830
  const errorId = `${reactId}-error`;
820
831
  const handleChange = (e) => {
@@ -859,7 +870,7 @@ function DateInput({
859
870
  min,
860
871
  max
861
872
  }) {
862
- const inputRef = react.useRef(null);
873
+ const inputRef = React.useRef(null);
863
874
  const handleIconClick = () => {
864
875
  const input = inputRef.current;
865
876
  if (!input) return;
@@ -919,7 +930,7 @@ function DropDown({
919
930
  name,
920
931
  testId
921
932
  }) {
922
- const reactId = react.useId();
933
+ const reactId = React.useId();
923
934
  const labelId = `${reactId}-label`;
924
935
  const selectId = `${reactId}-select`;
925
936
  const selectDisplayProps = {
@@ -1025,6 +1036,14 @@ function SearchDropdown({
1025
1036
  );
1026
1037
  }
1027
1038
 
1039
+ function formatFileSize(bytes) {
1040
+ if (bytes === 0) return "0 Bytes";
1041
+ const k = 1024;
1042
+ const sizes = ["Bytes", "KB", "MB", "GB"];
1043
+ const i = Math.floor(Math.log(bytes) / Math.log(k));
1044
+ return `${parseFloat((bytes / Math.pow(k, i)).toFixed(2))} ${sizes[i]}`;
1045
+ }
1046
+
1028
1047
  const FileInputContainer = styled.div`
1029
1048
  display: flex;
1030
1049
  flex-direction: column;
@@ -1109,13 +1128,6 @@ const FileRequirements = styled.div`
1109
1128
  border: 1px solid ${({ theme }) => theme.border.color};
1110
1129
  border-radius: ${({ theme }) => theme.border.radius};
1111
1130
  `;
1112
- const formatFileSize = (bytes) => {
1113
- if (bytes === 0) return "0 Bytes";
1114
- const k = 1024;
1115
- const sizes = ["Bytes", "KB", "MB", "GB"];
1116
- const i = Math.floor(Math.log(bytes) / Math.log(k));
1117
- return parseFloat((bytes / Math.pow(k, i)).toFixed(2)) + " " + sizes[i];
1118
- };
1119
1131
  function FileInput({
1120
1132
  label,
1121
1133
  error,
@@ -1130,9 +1142,9 @@ function FileInput({
1130
1142
  name,
1131
1143
  testId
1132
1144
  }) {
1133
- const fileInputRef = react.useRef(null);
1134
- const [dragOver, setDragOver] = react.useState(false);
1135
- const reactId = react.useId();
1145
+ const fileInputRef = React.useRef(null);
1146
+ const [dragOver, setDragOver] = React.useState(false);
1147
+ const reactId = React.useId();
1136
1148
  const inputId = `${reactId}-input`;
1137
1149
  const errorId = `${reactId}-error`;
1138
1150
  const requirementsId = `${reactId}-requirements`;
@@ -1437,8 +1449,8 @@ function Tabs({
1437
1449
  onTabChange,
1438
1450
  testId
1439
1451
  }) {
1440
- const [activeTab, setActiveTab] = react.useState(defaultActiveTab);
1441
- const baseId = react.useId();
1452
+ const [activeTab, setActiveTab] = React.useState(defaultActiveTab);
1453
+ const baseId = React.useId();
1442
1454
  const handleTabClick = (index) => {
1443
1455
  setActiveTab(index);
1444
1456
  onTabChange?.(index);
@@ -1483,15 +1495,55 @@ function Loader({ className, size, label = "Loading", testId }) {
1483
1495
  );
1484
1496
  }
1485
1497
 
1498
+ const Root$1 = styled.footer`
1499
+ margin-top: 2rem;
1500
+ padding-top: 0.75rem;
1501
+ border-top: ${({ theme }) => `${theme.border.width} solid ${theme.colors.border}`};
1502
+ text-align: center;
1503
+ font-family: ${({ theme }) => theme.typography.fontFamily};
1504
+ font-size: 0.8125rem;
1505
+ line-height: 1.4;
1506
+ color: ${({ theme }) => theme.colors.secondary};
1507
+ `;
1508
+ const Title = styled.p`
1509
+ margin: 0 0 0.25rem;
1510
+ font-weight: 600;
1511
+ color: ${({ theme }) => theme.colors.primary};
1512
+ `;
1513
+ const Anchor = styled.a`
1514
+ color: ${({ theme }) => theme.colors.accent};
1515
+ text-decoration: none;
1516
+
1517
+ &:hover,
1518
+ &:focus-visible {
1519
+ text-decoration: underline;
1520
+ }
1521
+ `;
1522
+ function Footnote({ title, text, links = [], className, testId }) {
1523
+ return /* @__PURE__ */ jsxRuntime.jsxs(Root$1, { className, "data-testid": testId, children: [
1524
+ title && /* @__PURE__ */ jsxRuntime.jsx(Title, { children: title }),
1525
+ /* @__PURE__ */ jsxRuntime.jsxs("p", { style: { margin: 0 }, children: [
1526
+ text && /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
1527
+ text,
1528
+ " "
1529
+ ] }),
1530
+ links.map((link, index) => /* @__PURE__ */ jsxRuntime.jsxs(React.Fragment, { children: [
1531
+ index > 0 && /* @__PURE__ */ jsxRuntime.jsx(jsxRuntime.Fragment, { children: " \xB7 " }),
1532
+ /* @__PURE__ */ jsxRuntime.jsx(Anchor, { href: link.href, target: "_blank", rel: "noopener noreferrer", children: link.label })
1533
+ ] }, link.href))
1534
+ ] })
1535
+ ] });
1536
+ }
1537
+
1486
1538
  function DotsMenu({
1487
1539
  options,
1488
1540
  iconSize = "medium",
1489
1541
  ariaLabel = "more",
1490
1542
  testId
1491
1543
  }) {
1492
- const [anchorEl, setAnchorEl] = react.useState(null);
1544
+ const [anchorEl, setAnchorEl] = React.useState(null);
1493
1545
  const open = Boolean(anchorEl);
1494
- const baseId = react.useId();
1546
+ const baseId = React.useId();
1495
1547
  const buttonId = `${baseId}-button`;
1496
1548
  const menuId = `${baseId}-menu`;
1497
1549
  const handleClick = (event) => {
@@ -1536,6 +1588,768 @@ function DotsMenu({
1536
1588
  ] });
1537
1589
  }
1538
1590
 
1591
+ const AsteriskMark = styled.sup`
1592
+ color: ${({ theme }) => theme.colors.accent};
1593
+ font-weight: 600;
1594
+ margin-left: 1px;
1595
+ font-size: 0.95em;
1596
+ `;
1597
+ const AsteriskNote = styled.p`
1598
+ font-size: 12px;
1599
+ color: ${({ theme }) => theme.colors.secondary};
1600
+ margin: 14px 4px 0;
1601
+ line-height: 1.5;
1602
+ font-style: italic;
1603
+
1604
+ .asterisk {
1605
+ font-style: normal;
1606
+ color: ${({ theme }) => theme.colors.accent};
1607
+ font-weight: 600;
1608
+ margin-right: 2px;
1609
+ }
1610
+ `;
1611
+
1612
+ const background = (theme, variant) => {
1613
+ if (variant === "filled") return theme.colors.accent;
1614
+ if (variant === "success" || variant === "neutral") return theme.colors.background;
1615
+ return `${theme.colors.accent}14`;
1616
+ };
1617
+ const foreground = (theme, variant) => {
1618
+ if (variant === "filled") return theme.colors.background;
1619
+ if (variant === "success") return theme.colors.success;
1620
+ if (variant === "neutral") return theme.colors.secondary;
1621
+ return theme.colors.accent;
1622
+ };
1623
+ const outline = (theme, variant) => {
1624
+ if (variant === "success") return `1px solid ${theme.colors.success}`;
1625
+ if (variant === "neutral") return `1px solid ${theme.colors.border}`;
1626
+ if (variant === "filled") return `1px solid ${theme.colors.accent}`;
1627
+ return "none";
1628
+ };
1629
+ const Badge = styled.span`
1630
+ font-size: 10px;
1631
+ padding: 2px 8px;
1632
+ border-radius: 4px;
1633
+ font-weight: 600;
1634
+ letter-spacing: 0.04em;
1635
+ text-transform: uppercase;
1636
+ white-space: nowrap;
1637
+ background: ${({ theme, $variant }) => background(theme, $variant)};
1638
+ color: ${({ theme, $variant }) => foreground(theme, $variant)};
1639
+ border: ${({ theme, $variant }) => outline(theme, $variant)};
1640
+ `;
1641
+
1642
+ const Zone = styled.div`
1643
+ display: flex;
1644
+ flex-direction: column;
1645
+ align-items: center;
1646
+ justify-content: center;
1647
+ gap: 4px;
1648
+ width: 100%;
1649
+ min-height: 100px;
1650
+ border: 1px dashed ${({ theme }) => theme.colors.border};
1651
+ border-radius: ${({ theme }) => theme.border.radius};
1652
+ padding: 16px;
1653
+ text-align: center;
1654
+ background: ${({ theme }) => theme.colors.surface};
1655
+ cursor: pointer;
1656
+ transition:
1657
+ border-color 0.15s ease,
1658
+ background 0.15s ease;
1659
+
1660
+ ${({ $dragging, theme }) => $dragging && `
1661
+ border-color: ${theme.colors.accent};
1662
+ background: ${theme.colors.accent}14;
1663
+ `}
1664
+
1665
+ &:hover {
1666
+ border-color: ${({ theme }) => theme.colors.accent};
1667
+ background: ${({ theme }) => `${theme.colors.accent}14`};
1668
+ }
1669
+
1670
+ &:focus-visible {
1671
+ outline: none;
1672
+ border-color: ${({ theme }) => theme.colors.accent};
1673
+ box-shadow: 0 0 0 3px ${({ theme }) => `${theme.colors.accent}26`};
1674
+ }
1675
+ `;
1676
+ const ZoneIcon = styled(CloudUploadIcon)`
1677
+ color: ${({ theme }) => theme.colors.secondary};
1678
+ `;
1679
+ const ZoneText = styled.p`
1680
+ font-size: 13px;
1681
+ margin: 0;
1682
+ color: ${({ theme }) => theme.colors.primary};
1683
+
1684
+ span {
1685
+ color: ${({ theme }) => theme.colors.accent};
1686
+ }
1687
+ `;
1688
+ const ZoneHint = styled.p`
1689
+ font-size: 11px;
1690
+ color: ${({ theme }) => theme.colors.secondary};
1691
+ margin: 0;
1692
+ `;
1693
+ const HiddenInput = styled.input`
1694
+ display: none;
1695
+ `;
1696
+ const FileList = styled.div`
1697
+ margin-top: 10px;
1698
+ display: flex;
1699
+ flex-direction: column;
1700
+ gap: 6px;
1701
+ `;
1702
+ const FileItem = styled.div`
1703
+ display: flex;
1704
+ align-items: center;
1705
+ gap: 10px;
1706
+ padding: 8px 12px;
1707
+ background: ${({ theme }) => theme.colors.background};
1708
+ border: 1px solid ${({ theme }) => theme.colors.border};
1709
+ border-radius: 4px;
1710
+ font-size: 13px;
1711
+
1712
+ .file-icon {
1713
+ color: ${({ theme }) => theme.colors.secondary};
1714
+ font-size: 18px;
1715
+ flex-shrink: 0;
1716
+ }
1717
+
1718
+ .file-name {
1719
+ flex: 1;
1720
+ min-width: 0;
1721
+ overflow: hidden;
1722
+ text-overflow: ellipsis;
1723
+ white-space: nowrap;
1724
+ color: ${({ theme }) => theme.colors.primary};
1725
+ }
1726
+
1727
+ .file-size {
1728
+ color: ${({ theme }) => theme.colors.secondary};
1729
+ font-size: 12px;
1730
+ flex-shrink: 0;
1731
+ }
1732
+ `;
1733
+ const RemoveButton = styled.button`
1734
+ display: inline-flex;
1735
+ align-items: center;
1736
+ justify-content: center;
1737
+ width: 24px;
1738
+ height: 24px;
1739
+ padding: 0;
1740
+ border: none;
1741
+ background: transparent;
1742
+ color: ${({ theme }) => theme.colors.secondary};
1743
+ border-radius: 4px;
1744
+ cursor: pointer;
1745
+ flex-shrink: 0;
1746
+ transition:
1747
+ background 0.15s ease,
1748
+ color 0.15s ease;
1749
+
1750
+ &:hover {
1751
+ background: ${({ theme }) => theme.colors.surface};
1752
+ color: ${({ theme }) => theme.colors.error};
1753
+ }
1754
+
1755
+ &:disabled {
1756
+ cursor: not-allowed;
1757
+ opacity: 0.6;
1758
+ }
1759
+
1760
+ svg {
1761
+ font-size: 16px;
1762
+ }
1763
+ `;
1764
+ function UploadZone({
1765
+ accept,
1766
+ hint,
1767
+ files = [],
1768
+ onAdd,
1769
+ onRemove,
1770
+ removingIds,
1771
+ disabled = false,
1772
+ inputId,
1773
+ testId
1774
+ }) {
1775
+ const inputRef = React.useRef(null);
1776
+ const [dragging, setDragging] = React.useState(false);
1777
+ const openPicker = () => {
1778
+ if (!disabled) inputRef.current?.click();
1779
+ };
1780
+ const handleKeyDown = (event) => {
1781
+ if (event.key === "Enter" || event.key === " ") {
1782
+ event.preventDefault();
1783
+ openPicker();
1784
+ }
1785
+ };
1786
+ const handleChange = (event) => {
1787
+ if (event.target.files?.length) onAdd(event.target.files);
1788
+ event.target.value = "";
1789
+ };
1790
+ const handleDrop = (event) => {
1791
+ event.preventDefault();
1792
+ setDragging(false);
1793
+ if (disabled) return;
1794
+ if (event.dataTransfer.files?.length) onAdd(event.dataTransfer.files);
1795
+ };
1796
+ return /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
1797
+ /* @__PURE__ */ jsxRuntime.jsxs(
1798
+ Zone,
1799
+ {
1800
+ role: "button",
1801
+ tabIndex: disabled ? -1 : 0,
1802
+ "aria-disabled": disabled,
1803
+ $dragging: dragging,
1804
+ onClick: openPicker,
1805
+ onKeyDown: handleKeyDown,
1806
+ onDragOver: (event) => {
1807
+ event.preventDefault();
1808
+ if (!disabled) setDragging(true);
1809
+ },
1810
+ onDragLeave: () => setDragging(false),
1811
+ onDrop: handleDrop,
1812
+ children: [
1813
+ /* @__PURE__ */ jsxRuntime.jsx(
1814
+ HiddenInput,
1815
+ {
1816
+ id: inputId,
1817
+ ref: inputRef,
1818
+ type: "file",
1819
+ multiple: true,
1820
+ accept,
1821
+ disabled,
1822
+ onChange: handleChange,
1823
+ "data-testid": testId
1824
+ }
1825
+ ),
1826
+ /* @__PURE__ */ jsxRuntime.jsx(ZoneIcon, { fontSize: "medium", "aria-hidden": "true" }),
1827
+ /* @__PURE__ */ jsxRuntime.jsxs(ZoneText, { children: [
1828
+ "Drop files or ",
1829
+ /* @__PURE__ */ jsxRuntime.jsx("span", { children: "browse" })
1830
+ ] }),
1831
+ /* @__PURE__ */ jsxRuntime.jsx(ZoneHint, { children: hint })
1832
+ ]
1833
+ }
1834
+ ),
1835
+ files.length > 0 && /* @__PURE__ */ jsxRuntime.jsx(FileList, { children: files.map((file) => {
1836
+ const isRemoving = removingIds?.has(file.id);
1837
+ return /* @__PURE__ */ jsxRuntime.jsxs(FileItem, { children: [
1838
+ /* @__PURE__ */ jsxRuntime.jsx(DescriptionIcon, { className: "file-icon", "aria-hidden": "true" }),
1839
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "file-name", children: file.fileName }),
1840
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "file-size", children: formatFileSize(file.fileSize) }),
1841
+ isRemoving ? /* @__PURE__ */ jsxRuntime.jsx(CircularProgress, { size: 16, color: "secondary" }) : /* @__PURE__ */ jsxRuntime.jsx(
1842
+ RemoveButton,
1843
+ {
1844
+ type: "button",
1845
+ "aria-label": `Remove ${file.fileName}`,
1846
+ disabled,
1847
+ onClick: () => onRemove(file.id),
1848
+ children: /* @__PURE__ */ jsxRuntime.jsx(CloseIcon, { "aria-hidden": "true" })
1849
+ }
1850
+ )
1851
+ ] }, file.id);
1852
+ }) })
1853
+ ] });
1854
+ }
1855
+
1856
+ const Wrapper = styled.div`
1857
+ background: ${({ theme }) => theme.colors.background};
1858
+ border: 1px solid ${({ theme, $open }) => $open ? theme.colors.secondary : theme.colors.border};
1859
+ border-radius: ${({ theme }) => theme.border.radius};
1860
+ overflow: hidden;
1861
+ transition: border-color 0.15s ease;
1862
+
1863
+ ${({ $open }) => $open && "grid-column: 1 / -1;"}
1864
+ `;
1865
+ const Summary = styled.button`
1866
+ width: 100%;
1867
+ text-align: left;
1868
+ cursor: pointer;
1869
+ padding: 12px 16px;
1870
+ display: flex;
1871
+ align-items: center;
1872
+ gap: 12px;
1873
+ background: transparent;
1874
+ border: none;
1875
+ transition: background 0.15s ease;
1876
+
1877
+ &:hover {
1878
+ background: ${({ theme }) => theme.colors.surface};
1879
+ }
1880
+
1881
+ &:focus-visible {
1882
+ outline: none;
1883
+ box-shadow: inset 0 0 0 2px ${({ theme }) => `${theme.colors.accent}40`};
1884
+ }
1885
+ `;
1886
+ const SummaryText = styled.div`
1887
+ flex: 1;
1888
+ min-width: 0;
1889
+ `;
1890
+ const Chevron = styled(ExpandMoreIcon)`
1891
+ color: ${({ theme }) => theme.colors.secondary};
1892
+ transition: transform 0.2s ease;
1893
+ transform: rotate(${({ $open }) => $open ? "180deg" : "0deg"});
1894
+ flex-shrink: 0;
1895
+ `;
1896
+ const Content = styled.div`
1897
+ padding: 12px 16px 14px;
1898
+ `;
1899
+ const Note = styled(AsteriskNote)`
1900
+ margin: 4px 0 0;
1901
+ `;
1902
+ function CollapsibleSource({
1903
+ title,
1904
+ desc,
1905
+ note,
1906
+ open = false,
1907
+ onToggle,
1908
+ children,
1909
+ testId
1910
+ }) {
1911
+ const contentId = React.useId();
1912
+ return /* @__PURE__ */ jsxRuntime.jsxs(Wrapper, { $open: open, children: [
1913
+ /* @__PURE__ */ jsxRuntime.jsxs(
1914
+ Summary,
1915
+ {
1916
+ type: "button",
1917
+ "aria-expanded": open,
1918
+ "aria-controls": contentId,
1919
+ onClick: onToggle,
1920
+ "data-testid": testId,
1921
+ children: [
1922
+ /* @__PURE__ */ jsxRuntime.jsxs(SummaryText, { children: [
1923
+ /* @__PURE__ */ jsxRuntime.jsxs(FormSubtitle, { style: { margin: 0 }, children: [
1924
+ title,
1925
+ note ? /* @__PURE__ */ jsxRuntime.jsx(AsteriskMark, { "aria-hidden": "true", children: "*" }) : null
1926
+ ] }),
1927
+ desc ? /* @__PURE__ */ jsxRuntime.jsx(Body100, { color: "secondary", style: { margin: "2px 0 0" }, children: desc }) : null,
1928
+ note ? /* @__PURE__ */ jsxRuntime.jsxs(Note, { children: [
1929
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "asterisk", "aria-hidden": "true", children: "*" }),
1930
+ note
1931
+ ] }) : null
1932
+ ] }),
1933
+ /* @__PURE__ */ jsxRuntime.jsx(Chevron, { $open: open, "aria-hidden": "true" })
1934
+ ]
1935
+ }
1936
+ ),
1937
+ open && /* @__PURE__ */ jsxRuntime.jsx(Content, { id: contentId, children })
1938
+ ] });
1939
+ }
1940
+
1941
+ const maskOf$1 = (plaidData) => {
1942
+ const account = plaidData?.accounts?.[0];
1943
+ return account?.mask || account?.accountMask || null;
1944
+ };
1945
+ function PlaidTile({ plaidData, onConnect, isConnecting = false, testId }) {
1946
+ const { palette, shape } = styles.useTheme();
1947
+ const connected = Boolean(plaidData);
1948
+ const institutionName = plaidData?.institution?.name || "Your bank";
1949
+ const mask = maskOf$1(plaidData);
1950
+ const accentColor = connected ? palette.success.dark : palette.primary.main;
1951
+ return /* @__PURE__ */ jsxRuntime.jsxs(
1952
+ Box,
1953
+ {
1954
+ component: "section",
1955
+ "aria-labelledby": "plaid-tile-title",
1956
+ sx: {
1957
+ display: "flex",
1958
+ alignItems: "center",
1959
+ justifyContent: "space-between",
1960
+ gap: "14px",
1961
+ flexWrap: "wrap",
1962
+ padding: "16px 18px",
1963
+ marginBottom: "14px",
1964
+ borderRadius: `${shape.borderRadius}px`,
1965
+ border: `1px solid ${accentColor}`,
1966
+ backgroundColor: connected ? styles.alpha(palette.success.main, 0.08) : palette.background.default,
1967
+ transition: "border-color 0.15s ease, background 0.15s ease"
1968
+ },
1969
+ children: [
1970
+ /* @__PURE__ */ jsxRuntime.jsxs(Box, { sx: { flex: 1, minWidth: 220 }, children: [
1971
+ /* @__PURE__ */ jsxRuntime.jsxs(Box, { sx: { display: "flex", alignItems: "center", gap: "10px", marginBottom: "2px" }, children: [
1972
+ /* @__PURE__ */ jsxRuntime.jsx(FormSubtitle, { id: "plaid-tile-title", style: { margin: 0, fontWeight: 600 }, children: connected ? "Connected via Plaid" : "Connect via Plaid" }),
1973
+ /* @__PURE__ */ jsxRuntime.jsx(Badge, { $variant: connected ? "success" : "info", children: connected ? "Verified" : "Recommended" })
1974
+ ] }),
1975
+ connected ? /* @__PURE__ */ jsxRuntime.jsxs(Body100, { style: { margin: 0, color: palette.success.dark, fontWeight: 500 }, children: [
1976
+ institutionName,
1977
+ mask ? ` \xB7\xB7\xB7\xB7${mask}` : ""
1978
+ ] }) : /* @__PURE__ */ jsxRuntime.jsx(Body100, { color: "secondary", style: { margin: 0 }, children: "Securely link your bank in seconds. Best signal for fastest decisions." })
1979
+ ] }),
1980
+ connected ? /* @__PURE__ */ jsxRuntime.jsx(
1981
+ Button$1,
1982
+ {
1983
+ size: "small",
1984
+ variant: "outlined",
1985
+ color: "inherit",
1986
+ type: "button",
1987
+ onClick: onConnect,
1988
+ disabled: isConnecting,
1989
+ "data-testid": testId,
1990
+ startIcon: /* @__PURE__ */ jsxRuntime.jsx(RefreshIcon, { fontSize: "small" }),
1991
+ sx: { flexShrink: 0, textTransform: "none" },
1992
+ children: isConnecting ? "Connecting\u2026" : "Reconnect"
1993
+ }
1994
+ ) : /* @__PURE__ */ jsxRuntime.jsx(
1995
+ Button$1,
1996
+ {
1997
+ size: "small",
1998
+ variant: "contained",
1999
+ color: "primary",
2000
+ type: "button",
2001
+ onClick: onConnect,
2002
+ disabled: isConnecting,
2003
+ "data-testid": testId,
2004
+ startIcon: /* @__PURE__ */ jsxRuntime.jsx(LinkIcon, { fontSize: "small" }),
2005
+ sx: { flexShrink: 0, textTransform: "none" },
2006
+ children: isConnecting ? "Connecting\u2026" : "Connect bank account"
2007
+ }
2008
+ )
2009
+ ]
2010
+ }
2011
+ );
2012
+ }
2013
+
2014
+ const Root = styled.div`
2015
+ background-color: ${({ theme }) => theme.colors.surface};
2016
+ border: 1px solid ${({ theme }) => theme.colors.border};
2017
+ border-radius: ${({ theme }) => theme.border.radius};
2018
+ padding: 1.5rem;
2019
+ overflow-y: auto;
2020
+ max-height: 400px;
2021
+ margin-bottom: 1rem;
2022
+ font-size: 14px;
2023
+ line-height: 1.6;
2024
+ `;
2025
+ function TermsDocument({ html, className, testId }) {
2026
+ return /* @__PURE__ */ jsxRuntime.jsx(
2027
+ Root,
2028
+ {
2029
+ className,
2030
+ "data-testid": testId,
2031
+ dangerouslySetInnerHTML: { __html: html }
2032
+ }
2033
+ );
2034
+ }
2035
+ const HelperText = styled.span`
2036
+ display: block;
2037
+ font-size: 12.5px;
2038
+ color: ${({ theme }) => theme.colors.secondary};
2039
+ text-align: right;
2040
+ margin-top: 0.5rem;
2041
+ `;
2042
+
2043
+ const DECLINED = /* @__PURE__ */ new Set([
2044
+ "CLOSED_NO_DECISION",
2045
+ "DECLINED",
2046
+ "DENIED",
2047
+ "ERROR",
2048
+ "N/A",
2049
+ "REJECTED"
2050
+ ]);
2051
+ function UnderwritingStatus({
2052
+ status,
2053
+ creditLimit,
2054
+ children,
2055
+ testId
2056
+ }) {
2057
+ if (status === "PROCESSING") {
2058
+ return /* @__PURE__ */ jsxRuntime.jsxs("div", { "data-testid": testId, children: [
2059
+ /* @__PURE__ */ jsxRuntime.jsx(H3, { children: "Your application is being reviewed" }),
2060
+ /* @__PURE__ */ jsxRuntime.jsxs(Stack, { width: "100%", alignItems: "center", gap: "16px", children: [
2061
+ /* @__PURE__ */ jsxRuntime.jsx(Loader, {}),
2062
+ /* @__PURE__ */ jsxRuntime.jsx(Body200, { children: "We are underwriting your business. This process can take a minute or two." })
2063
+ ] })
2064
+ ] });
2065
+ }
2066
+ if (status === "APPROVED") {
2067
+ return /* @__PURE__ */ jsxRuntime.jsxs("div", { "data-testid": testId, children: [
2068
+ /* @__PURE__ */ jsxRuntime.jsx(H3, { children: "Congrats!" }),
2069
+ /* @__PURE__ */ jsxRuntime.jsxs(Body200, { children: [
2070
+ "You have been approved.",
2071
+ creditLimit ? /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
2072
+ " Your credit limit is: ",
2073
+ creditLimit
2074
+ ] }) : null
2075
+ ] }),
2076
+ /* @__PURE__ */ jsxRuntime.jsx(Box, { sx: { display: "flex", justifyContent: "center", alignItems: "center" }, children: /* @__PURE__ */ jsxRuntime.jsx(CheckRoundedIcon, { color: "success", sx: { fontSize: "5rem" } }) }),
2077
+ children
2078
+ ] });
2079
+ }
2080
+ if (status && DECLINED.has(status)) {
2081
+ return /* @__PURE__ */ jsxRuntime.jsxs("div", { "data-testid": testId, children: [
2082
+ /* @__PURE__ */ jsxRuntime.jsx(H3, { children: "Sorry, we cannot approve your application" }),
2083
+ /* @__PURE__ */ jsxRuntime.jsx(Body200, { children: "We are sorry to inform you that we cannot approve your application. Please try again later." })
2084
+ ] });
2085
+ }
2086
+ return /* @__PURE__ */ jsxRuntime.jsxs("div", { "data-testid": testId, children: [
2087
+ /* @__PURE__ */ jsxRuntime.jsx(H3, { children: "Your application is being reviewed" }),
2088
+ /* @__PURE__ */ jsxRuntime.jsx(Body200, { children: "We are reviewing your application and will reach out within 2 business days." })
2089
+ ] });
2090
+ }
2091
+
2092
+ const MethodCard = styled.div`
2093
+ display: flex;
2094
+ gap: 12px;
2095
+ align-items: flex-start;
2096
+ padding: 14px 16px;
2097
+ margin-bottom: 10px;
2098
+ border-radius: ${({ theme }) => theme.border.radius};
2099
+ border: 1px solid
2100
+ ${({ theme, $default }) => $default ? theme.colors.accent : theme.colors.border};
2101
+ background: ${({ theme, $default, $readonly }) => $default ? `${theme.colors.accent}14` : $readonly ? theme.colors.surface : theme.colors.background};
2102
+ transition:
2103
+ border-color 0.15s ease,
2104
+ background 0.15s ease;
2105
+ `;
2106
+ const MethodBody = styled.div`
2107
+ flex: 1;
2108
+ min-width: 0;
2109
+ `;
2110
+ const MethodHeader = styled.div`
2111
+ display: flex;
2112
+ align-items: center;
2113
+ justify-content: space-between;
2114
+ gap: 8px;
2115
+ margin-bottom: ${({ $tight }) => $tight ? "6px" : "12px"};
2116
+ `;
2117
+ const MethodTitle = styled.span`
2118
+ display: flex;
2119
+ align-items: center;
2120
+ gap: 8px;
2121
+ flex-wrap: wrap;
2122
+ font-size: 14px;
2123
+ font-weight: 600;
2124
+ color: ${({ theme }) => theme.colors.primary};
2125
+ `;
2126
+ const FieldsGrid = styled.div`
2127
+ display: grid;
2128
+ grid-template-columns: 1fr 1fr;
2129
+ gap: 10px;
2130
+
2131
+ @media (max-width: 540px) {
2132
+ grid-template-columns: 1fr;
2133
+ }
2134
+ `;
2135
+ const Banner = styled.div`
2136
+ display: flex;
2137
+ gap: 10px;
2138
+ align-items: flex-start;
2139
+ padding: 10px 14px;
2140
+ background: ${({ theme }) => `${theme.colors.accent}14`};
2141
+ border-left: 3px solid ${({ theme }) => theme.colors.accent};
2142
+ border-radius: ${({ theme }) => theme.border.radius};
2143
+ margin-bottom: 1.25rem;
2144
+
2145
+ svg {
2146
+ color: ${({ theme }) => theme.colors.accent};
2147
+ flex-shrink: 0;
2148
+ margin-top: 1px;
2149
+ }
2150
+
2151
+ p {
2152
+ font-size: 13px;
2153
+ color: ${({ theme }) => theme.colors.accent};
2154
+ margin: 0;
2155
+ line-height: 1.5;
2156
+ }
2157
+ `;
2158
+ const FooterActions = styled.div`
2159
+ display: flex;
2160
+ justify-content: space-between;
2161
+ align-items: center;
2162
+ gap: 12px;
2163
+ margin-top: 1.25rem;
2164
+ flex-wrap: wrap;
2165
+
2166
+ button {
2167
+ margin-top: 0;
2168
+ }
2169
+ `;
2170
+ const FooterRight = styled.div`
2171
+ display: flex;
2172
+ align-items: center;
2173
+ gap: 14px;
2174
+ `;
2175
+
2176
+ const maskOf = (plaidData) => {
2177
+ const account = plaidData?.accounts?.[0];
2178
+ return account?.mask || account?.accountMask || null;
2179
+ };
2180
+ function PlaidMethodCard({
2181
+ plaidData,
2182
+ isDefault = false,
2183
+ onSelectDefault,
2184
+ name = "default-method",
2185
+ testId
2186
+ }) {
2187
+ const institutionName = plaidData?.institution?.name || "Your bank";
2188
+ const mask = maskOf(plaidData);
2189
+ return /* @__PURE__ */ jsxRuntime.jsxs(MethodCard, { as: "label", htmlFor: "default-plaid", $default: isDefault, $readonly: true, children: [
2190
+ /* @__PURE__ */ jsxRuntime.jsx(
2191
+ RadioInput,
2192
+ {
2193
+ type: "radio",
2194
+ id: "default-plaid",
2195
+ name,
2196
+ checked: isDefault,
2197
+ onChange: onSelectDefault,
2198
+ "data-testid": testId
2199
+ }
2200
+ ),
2201
+ /* @__PURE__ */ jsxRuntime.jsxs(MethodBody, { children: [
2202
+ /* @__PURE__ */ jsxRuntime.jsxs(MethodHeader, { $tight: true, children: [
2203
+ /* @__PURE__ */ jsxRuntime.jsxs(MethodTitle, { children: [
2204
+ institutionName,
2205
+ /* @__PURE__ */ jsxRuntime.jsx(Badge, { $variant: "neutral", children: "Via Plaid" }),
2206
+ isDefault && /* @__PURE__ */ jsxRuntime.jsx(Badge, { $variant: "filled", children: "Default" })
2207
+ ] }),
2208
+ /* @__PURE__ */ jsxRuntime.jsx(LockIcon, { fontSize: "small", titleAccess: "Managed via Plaid" })
2209
+ ] }),
2210
+ /* @__PURE__ */ jsxRuntime.jsx(Body200, { color: "secondary", style: { margin: 0 }, children: mask ? `Account \xB7\xB7\xB7\xB7${mask}` : "Connected account" })
2211
+ ] })
2212
+ ] });
2213
+ }
2214
+
2215
+ function ManualMethodCard({
2216
+ index,
2217
+ value,
2218
+ isDefault = false,
2219
+ trashDisabled = false,
2220
+ disabled = false,
2221
+ accountError,
2222
+ routingError,
2223
+ onSelectDefault,
2224
+ onChangeField,
2225
+ onBlurField,
2226
+ onRemove,
2227
+ name = "default-method"
2228
+ }) {
2229
+ const radioId = `default-${value.id}`;
2230
+ return /* @__PURE__ */ jsxRuntime.jsxs(MethodCard, { $default: isDefault, children: [
2231
+ /* @__PURE__ */ jsxRuntime.jsx(
2232
+ RadioInput,
2233
+ {
2234
+ type: "radio",
2235
+ id: radioId,
2236
+ name,
2237
+ checked: isDefault,
2238
+ onChange: onSelectDefault,
2239
+ disabled
2240
+ }
2241
+ ),
2242
+ /* @__PURE__ */ jsxRuntime.jsxs(MethodBody, { children: [
2243
+ /* @__PURE__ */ jsxRuntime.jsxs(MethodHeader, { children: [
2244
+ /* @__PURE__ */ jsxRuntime.jsxs(MethodTitle, { children: [
2245
+ /* @__PURE__ */ jsxRuntime.jsxs(Body200, { as: "label", htmlFor: radioId, style: { margin: 0, cursor: "pointer" }, children: [
2246
+ "Account ",
2247
+ index + 1
2248
+ ] }),
2249
+ isDefault && /* @__PURE__ */ jsxRuntime.jsx(Badge, { $variant: "filled", children: "Default" })
2250
+ ] }),
2251
+ /* @__PURE__ */ jsxRuntime.jsx(
2252
+ IconButton,
2253
+ {
2254
+ size: "small",
2255
+ type: "button",
2256
+ onClick: onRemove,
2257
+ disabled: disabled || trashDisabled,
2258
+ "aria-label": "Remove this account",
2259
+ title: trashDisabled ? "At least one account is required" : "Remove this account",
2260
+ children: /* @__PURE__ */ jsxRuntime.jsx(DeleteOutlineIcon, { fontSize: "small" })
2261
+ }
2262
+ )
2263
+ ] }),
2264
+ /* @__PURE__ */ jsxRuntime.jsxs(FieldsGrid, { children: [
2265
+ /* @__PURE__ */ jsxRuntime.jsx("div", { onBlur: () => onBlurField("account"), children: /* @__PURE__ */ jsxRuntime.jsx(
2266
+ TextInput,
2267
+ {
2268
+ label: "Account number",
2269
+ placeholder: "Account number",
2270
+ value: value.account,
2271
+ onChange: (next) => onChangeField("account", next),
2272
+ error: accountError,
2273
+ disabled,
2274
+ onlyNumbers: true
2275
+ }
2276
+ ) }),
2277
+ /* @__PURE__ */ jsxRuntime.jsx("div", { onBlur: () => onBlurField("routing"), children: /* @__PURE__ */ jsxRuntime.jsx(
2278
+ TextInput,
2279
+ {
2280
+ label: "Routing number",
2281
+ placeholder: "Routing number",
2282
+ value: value.routing,
2283
+ onChange: (next) => onChangeField("routing", next),
2284
+ error: routingError,
2285
+ disabled,
2286
+ onlyNumbers: true
2287
+ }
2288
+ ) })
2289
+ ] })
2290
+ ] })
2291
+ ] });
2292
+ }
2293
+
2294
+ const ConsentRow = styled.label`
2295
+ display: flex;
2296
+ align-items: center;
2297
+ padding: 12px 14px;
2298
+ background: ${({ theme }) => theme.colors.surface};
2299
+ border-radius: ${({ theme }) => theme.border.radius};
2300
+ margin: 1.25rem 0;
2301
+ cursor: pointer;
2302
+ gap: 10px;
2303
+ `;
2304
+ function AchVerificationConsent({
2305
+ consentGiven = false,
2306
+ onChange,
2307
+ disabled = false,
2308
+ actionLabel = "Continue",
2309
+ testId
2310
+ }) {
2311
+ return /* @__PURE__ */ jsxRuntime.jsxs(ConsentRow, { children: [
2312
+ /* @__PURE__ */ jsxRuntime.jsx(
2313
+ CheckboxInput,
2314
+ {
2315
+ type: "checkbox",
2316
+ checked: consentGiven,
2317
+ onChange: (event) => onChange(event.target.checked),
2318
+ disabled,
2319
+ "data-testid": testId
2320
+ }
2321
+ ),
2322
+ /* @__PURE__ */ jsxRuntime.jsxs(Body100, { style: { margin: 0 }, children: [
2323
+ "By clicking ",
2324
+ actionLabel,
2325
+ ", I agree to the",
2326
+ " ",
2327
+ /* @__PURE__ */ jsxRuntime.jsx(
2328
+ "a",
2329
+ {
2330
+ href: "https://oatfi.com/payment-method-verification-consent",
2331
+ target: "_blank",
2332
+ rel: "noopener noreferrer",
2333
+ children: "payment method verification"
2334
+ }
2335
+ ),
2336
+ " ",
2337
+ "and acknowledge the",
2338
+ " ",
2339
+ /* @__PURE__ */ jsxRuntime.jsx(
2340
+ "a",
2341
+ {
2342
+ href: "https://oatfi.com/usa-patriot-act-disclosure-page",
2343
+ target: "_blank",
2344
+ rel: "noopener noreferrer",
2345
+ children: "USA PATRIOT Act disclosure"
2346
+ }
2347
+ ),
2348
+ "."
2349
+ ] })
2350
+ ] });
2351
+ }
2352
+
1539
2353
  const PreviewCard = styled.div`
1540
2354
  display: flex;
1541
2355
  gap: 2rem;
@@ -1562,9 +2376,9 @@ const SideColumn = styled.div`
1562
2376
  min-width: 0;
1563
2377
  `;
1564
2378
  function PreviewContent() {
1565
- const [name, setName] = react.useState("Acme Corporation");
1566
- const [plan, setPlan] = react.useState("growth");
1567
- const [autopay, setAutopay] = react.useState(true);
2379
+ const [name, setName] = React.useState("Acme Corporation");
2380
+ const [plan, setPlan] = React.useState("growth");
2381
+ const [autopay, setAutopay] = React.useState(true);
1568
2382
  return /* @__PURE__ */ jsxRuntime.jsxs(PreviewCard, { "data-testid": "oatfi-ui-preview", children: [
1569
2383
  /* @__PURE__ */ jsxRuntime.jsxs(MainColumn, { children: [
1570
2384
  /* @__PURE__ */ jsxRuntime.jsxs(HeaderRow, { children: [
@@ -1607,7 +2421,12 @@ function Preview({ branding, className }) {
1607
2421
  return /* @__PURE__ */ jsxRuntime.jsx(ThemeProvider, { branding, children: /* @__PURE__ */ jsxRuntime.jsx("div", { className, children: /* @__PURE__ */ jsxRuntime.jsx(PreviewContent, {}) }) });
1608
2422
  }
1609
2423
 
2424
+ exports.AchVerificationConsent = AchVerificationConsent;
2425
+ exports.AsteriskMark = AsteriskMark;
2426
+ exports.AsteriskNote = AsteriskNote;
1610
2427
  exports.AutocompleteInput = AutocompleteInput;
2428
+ exports.Badge = Badge;
2429
+ exports.Banner = Banner;
1611
2430
  exports.BaseInput = BaseInput;
1612
2431
  exports.Body100 = Body100;
1613
2432
  exports.Body200 = Body200;
@@ -1615,8 +2434,9 @@ exports.Body400 = Body400;
1615
2434
  exports.Button = Button;
1616
2435
  exports.ButtonContainer = ButtonContainer;
1617
2436
  exports.CheckboxInput = CheckboxInput;
2437
+ exports.CollapsibleSource = CollapsibleSource;
1618
2438
  exports.Container = Container;
1619
- exports.Content = Content;
2439
+ exports.Content = Content$1;
1620
2440
  exports.CurrencyInput = CurrencyInput;
1621
2441
  exports.DateInput = DateInput;
1622
2442
  exports.DetailElement = DetailElement;
@@ -1628,9 +2448,13 @@ exports.DropdownContainer = DropdownContainer;
1628
2448
  exports.DropdownItem = DropdownItem;
1629
2449
  exports.DropdownList = DropdownList;
1630
2450
  exports.EmailInput = EmailInput;
2451
+ exports.FieldsGrid = FieldsGrid;
1631
2452
  exports.FileInput = FileInput;
1632
2453
  exports.FlexColumnWrapper = FlexColumnWrapper;
1633
2454
  exports.FlexRowWrapper = FlexRowWrapper;
2455
+ exports.FooterActions = FooterActions;
2456
+ exports.FooterRight = FooterRight;
2457
+ exports.Footnote = Footnote;
1634
2458
  exports.Form = Form;
1635
2459
  exports.FormActions = FormActions;
1636
2460
  exports.FormCheckboxLabel = FormCheckboxLabel;
@@ -1641,12 +2465,20 @@ exports.FormTitle = FormTitle;
1641
2465
  exports.H1 = H1;
1642
2466
  exports.H2 = H2;
1643
2467
  exports.H3 = H3;
2468
+ exports.HelperText = HelperText;
1644
2469
  exports.InputError = InputError;
1645
2470
  exports.InputLabel = InputLabel;
1646
2471
  exports.InputWrapper = InputWrapper;
1647
2472
  exports.Loader = Loader;
2473
+ exports.ManualMethodCard = ManualMethodCard;
2474
+ exports.MethodBody = MethodBody;
2475
+ exports.MethodCard = MethodCard;
2476
+ exports.MethodHeader = MethodHeader;
2477
+ exports.MethodTitle = MethodTitle;
1648
2478
  exports.NoResults = NoResults;
1649
2479
  exports.PhoneInput = PhoneInput;
2480
+ exports.PlaidMethodCard = PlaidMethodCard;
2481
+ exports.PlaidTile = PlaidTile;
1650
2482
  exports.Preview = Preview;
1651
2483
  exports.RadioInput = RadioInput;
1652
2484
  exports.SSNInput = SSNInput;
@@ -1656,9 +2488,12 @@ exports.StatusTag = StatusTag;
1656
2488
  exports.Stepper = Stepper;
1657
2489
  exports.Tabs = Tabs;
1658
2490
  exports.TaxIdInput = TaxIdInput;
2491
+ exports.TermsDocument = TermsDocument;
1659
2492
  exports.TermsList = TermsList;
1660
2493
  exports.TextInput = TextInput;
1661
2494
  exports.ThemeProvider = ThemeProvider;
2495
+ exports.UnderwritingStatus = UnderwritingStatus;
2496
+ exports.UploadZone = UploadZone;
1662
2497
  exports.buildMuiTheme = buildMuiTheme;
1663
2498
  exports.defaultTheme = defaultTheme;
1664
2499
  exports.mergeBranding = mergeBranding;