@terminal-blueprint/react 0.1.0 → 0.1.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -156,6 +156,7 @@ function CardRoot({
156
156
  brackets = false,
157
157
  pulse = false,
158
158
  hoverable = false,
159
+ href,
159
160
  className,
160
161
  children,
161
162
  ref,
@@ -171,10 +172,12 @@ function CardRoot({
171
172
  brackets && "tb-card--bracketed",
172
173
  pulse && "tb-card--pulse",
173
174
  hoverable && "tb-card--hoverable",
175
+ href && "tb-card--link",
174
176
  className
175
177
  ),
176
178
  ...props,
177
179
  children: [
180
+ href && /* @__PURE__ */ jsx4("a", { className: "tb-card__link", href, "aria-hidden": "true", tabIndex: -1 }),
178
181
  brackets && /* @__PURE__ */ jsxs4(Fragment, { children: [
179
182
  /* @__PURE__ */ jsx4("span", { className: "tb-card__bracket tb-card__bracket--tl" }),
180
183
  /* @__PURE__ */ jsx4("span", { className: "tb-card__bracket tb-card__bracket--tr" }),
@@ -598,6 +601,7 @@ function ModalRoot({
598
601
  closeOnBackdrop = true,
599
602
  hideCloseButton = false,
600
603
  portalTarget,
604
+ variant,
601
605
  children,
602
606
  className,
603
607
  ref
@@ -683,7 +687,7 @@ function ModalRoot({
683
687
  "aria-modal": "true",
684
688
  "aria-labelledby": titleId,
685
689
  "aria-describedby": descId,
686
- className: cn("tb-modal", className),
690
+ className: cn("tb-modal", variant && `tb-modal--${variant}`, className),
687
691
  tabIndex: -1,
688
692
  onKeyDown: handleKeyDown,
689
693
  children: [
@@ -1342,22 +1346,81 @@ function Progress({
1342
1346
  );
1343
1347
  }
1344
1348
 
1345
- // src/components/Battery.tsx
1349
+ // src/components/Slider.tsx
1350
+ import { useId as useId4 } from "react";
1346
1351
  import { jsx as jsx22, jsxs as jsxs18 } from "react/jsx-runtime";
1352
+ function Slider({
1353
+ value,
1354
+ onChange,
1355
+ min = 0,
1356
+ max = 100,
1357
+ step = 1,
1358
+ variant = "default",
1359
+ label,
1360
+ showValue = false,
1361
+ disabled = false,
1362
+ className,
1363
+ ref
1364
+ }) {
1365
+ const id = useId4();
1366
+ const percent = (value - min) / (max - min) * 100;
1367
+ return /* @__PURE__ */ jsxs18(
1368
+ "div",
1369
+ {
1370
+ className: cn(
1371
+ "tb-slider",
1372
+ variant !== "default" && `tb-slider--${variant}`,
1373
+ disabled && "tb-slider--disabled",
1374
+ className
1375
+ ),
1376
+ children: [
1377
+ (label || showValue) && /* @__PURE__ */ jsxs18("div", { className: "tb-slider__header", children: [
1378
+ label && /* @__PURE__ */ jsx22("label", { className: "tb-slider__label", htmlFor: id, children: label }),
1379
+ showValue && /* @__PURE__ */ jsx22("span", { className: "tb-slider__value", children: value })
1380
+ ] }),
1381
+ /* @__PURE__ */ jsx22(
1382
+ "input",
1383
+ {
1384
+ ref,
1385
+ id,
1386
+ type: "range",
1387
+ className: "tb-slider__input",
1388
+ style: { "--slider-percent": `${percent}%` },
1389
+ value,
1390
+ min,
1391
+ max,
1392
+ step,
1393
+ disabled,
1394
+ onChange: (e) => onChange(Number(e.target.value)),
1395
+ "aria-label": label,
1396
+ "aria-valuenow": value,
1397
+ "aria-valuemin": min,
1398
+ "aria-valuemax": max
1399
+ }
1400
+ )
1401
+ ]
1402
+ }
1403
+ );
1404
+ }
1405
+
1406
+ // src/components/Battery.tsx
1407
+ import { jsx as jsx23, jsxs as jsxs19 } from "react/jsx-runtime";
1347
1408
  function Battery({
1348
1409
  value,
1349
1410
  total = 10,
1350
1411
  variant = "default",
1351
1412
  animated = false,
1352
1413
  label,
1414
+ showValue = false,
1353
1415
  className
1354
1416
  }) {
1355
1417
  const clampedValue = Math.max(0, Math.min(total, value));
1356
- return /* @__PURE__ */ jsxs18(
1418
+ const percent = Math.round(clampedValue / total * 100);
1419
+ return /* @__PURE__ */ jsxs19(
1357
1420
  "div",
1358
1421
  {
1359
1422
  className: cn(
1360
- "tb-battery",
1423
+ "tb-battery tb-battery--inline",
1361
1424
  variant !== "default" && `tb-battery--${variant}`,
1362
1425
  animated && "tb-battery--animated",
1363
1426
  className
@@ -1368,19 +1431,20 @@ function Battery({
1368
1431
  "aria-valuemax": total,
1369
1432
  "aria-label": label,
1370
1433
  children: [
1371
- label && /* @__PURE__ */ jsx22("span", { className: "tb-battery__label", children: label }),
1372
- /* @__PURE__ */ jsxs18("div", { className: "tb-battery__body", children: [
1373
- /* @__PURE__ */ jsx22("div", { className: "tb-battery__cap" }),
1374
- /* @__PURE__ */ jsx22("div", { className: "tb-battery__segments", children: Array.from({ length: total }, (_, i) => /* @__PURE__ */ jsx22(
1375
- "div",
1376
- {
1377
- className: cn(
1378
- "tb-battery__segment",
1379
- i < clampedValue && "tb-battery__segment--filled"
1380
- )
1381
- },
1382
- i
1383
- )) })
1434
+ label && /* @__PURE__ */ jsx23("span", { className: "tb-battery__label", children: label }),
1435
+ /* @__PURE__ */ jsx23("div", { className: "tb-battery__track", children: Array.from({ length: total }, (_, i) => /* @__PURE__ */ jsx23(
1436
+ "div",
1437
+ {
1438
+ className: cn(
1439
+ "tb-battery__segment",
1440
+ i < clampedValue && "tb-battery__segment--filled"
1441
+ )
1442
+ },
1443
+ i
1444
+ )) }),
1445
+ showValue && /* @__PURE__ */ jsxs19("span", { className: "tb-battery__value", children: [
1446
+ percent,
1447
+ "%"
1384
1448
  ] })
1385
1449
  ]
1386
1450
  }
@@ -1388,7 +1452,7 @@ function Battery({
1388
1452
  }
1389
1453
 
1390
1454
  // src/components/BatteryInline.tsx
1391
- import { jsx as jsx23, jsxs as jsxs19 } from "react/jsx-runtime";
1455
+ import { jsx as jsx24, jsxs as jsxs20 } from "react/jsx-runtime";
1392
1456
  function BatteryInline({
1393
1457
  value,
1394
1458
  total = 10,
@@ -1400,11 +1464,12 @@ function BatteryInline({
1400
1464
  }) {
1401
1465
  const clampedValue = Math.max(0, Math.min(total, value));
1402
1466
  const percent = Math.round(clampedValue / total * 100);
1403
- return /* @__PURE__ */ jsxs19(
1467
+ return /* @__PURE__ */ jsxs20(
1404
1468
  "div",
1405
1469
  {
1406
1470
  className: cn(
1407
1471
  "tb-battery tb-battery--inline",
1472
+ variant !== "default" && `tb-battery--${variant}`,
1408
1473
  animated && "tb-battery--animated",
1409
1474
  className
1410
1475
  ),
@@ -1414,20 +1479,18 @@ function BatteryInline({
1414
1479
  "aria-valuemax": total,
1415
1480
  "aria-label": label,
1416
1481
  children: [
1417
- label && /* @__PURE__ */ jsx23("span", { className: "tb-battery__label", children: label }),
1418
- /* @__PURE__ */ jsx23("div", { className: "tb-battery__track", children: Array.from({ length: total }, (_, i) => /* @__PURE__ */ jsx23(
1482
+ label && /* @__PURE__ */ jsx24("span", { className: "tb-battery__label", children: label }),
1483
+ /* @__PURE__ */ jsx24("div", { className: "tb-battery__track", children: Array.from({ length: total }, (_, i) => /* @__PURE__ */ jsx24(
1419
1484
  "div",
1420
1485
  {
1421
1486
  className: cn(
1422
1487
  "tb-battery__segment",
1423
- i < clampedValue && "tb-battery__segment--filled",
1424
- i < clampedValue && variant !== "default" && `tb-battery__segment--${variant}`
1488
+ i < clampedValue && "tb-battery__segment--filled"
1425
1489
  )
1426
1490
  },
1427
1491
  i
1428
1492
  )) }),
1429
- /* @__PURE__ */ jsx23("div", { className: "tb-battery__cap" }),
1430
- showValue && /* @__PURE__ */ jsxs19("span", { className: "tb-battery__value", children: [
1493
+ showValue && /* @__PURE__ */ jsxs20("span", { className: "tb-battery__value", children: [
1431
1494
  percent,
1432
1495
  "%"
1433
1496
  ] })
@@ -1437,7 +1500,7 @@ function BatteryInline({
1437
1500
  }
1438
1501
 
1439
1502
  // src/components/Table.tsx
1440
- import { jsx as jsx24, jsxs as jsxs20 } from "react/jsx-runtime";
1503
+ import { jsx as jsx25, jsxs as jsxs21 } from "react/jsx-runtime";
1441
1504
  function getCellValue(row, accessor) {
1442
1505
  if (typeof accessor === "function") return accessor(row);
1443
1506
  return row[accessor];
@@ -1448,9 +1511,9 @@ function Table({
1448
1511
  onRowClick,
1449
1512
  className
1450
1513
  }) {
1451
- return /* @__PURE__ */ jsx24("div", { className: cn("tb-table__wrapper", className), children: /* @__PURE__ */ jsxs20("table", { className: "tb-table", children: [
1452
- /* @__PURE__ */ jsx24("thead", { className: "tb-table__head", children: /* @__PURE__ */ jsx24("tr", { className: "tb-table__row", children: columns.map((col) => /* @__PURE__ */ jsx24("th", { className: "tb-table__th", children: col.header }, col.id)) }) }),
1453
- /* @__PURE__ */ jsx24("tbody", { className: "tb-table__body", children: data.map((row, rowIndex) => /* @__PURE__ */ jsx24(
1514
+ return /* @__PURE__ */ jsx25("div", { className: cn("tb-table__wrapper", className), children: /* @__PURE__ */ jsxs21("table", { className: "tb-table", children: [
1515
+ /* @__PURE__ */ jsx25("thead", { className: "tb-table__head", children: /* @__PURE__ */ jsx25("tr", { className: "tb-table__row", children: columns.map((col) => /* @__PURE__ */ jsx25("th", { className: "tb-table__th", children: col.header }, col.id)) }) }),
1516
+ /* @__PURE__ */ jsx25("tbody", { className: "tb-table__body", children: data.map((row, rowIndex) => /* @__PURE__ */ jsx25(
1454
1517
  "tr",
1455
1518
  {
1456
1519
  className: cn(
@@ -1460,7 +1523,7 @@ function Table({
1460
1523
  onClick: onRowClick ? () => onRowClick(row) : void 0,
1461
1524
  children: columns.map((col) => {
1462
1525
  const value = getCellValue(row, col.accessor);
1463
- return /* @__PURE__ */ jsx24("td", { className: "tb-table__td", children: col.cell ? col.cell(value, row) : value }, col.id);
1526
+ return /* @__PURE__ */ jsx25("td", { className: "tb-table__td", children: col.cell ? col.cell(value, row) : value }, col.id);
1464
1527
  })
1465
1528
  },
1466
1529
  rowIndex
@@ -1469,7 +1532,7 @@ function Table({
1469
1532
  }
1470
1533
 
1471
1534
  // src/components/Skeleton.tsx
1472
- import { jsx as jsx25 } from "react/jsx-runtime";
1535
+ import { jsx as jsx26 } from "react/jsx-runtime";
1473
1536
  function Skeleton({
1474
1537
  variant = "text",
1475
1538
  width,
@@ -1482,7 +1545,7 @@ function Skeleton({
1482
1545
  height: typeof height === "number" ? `${height}px` : height
1483
1546
  };
1484
1547
  if (variant === "text" && lines > 1) {
1485
- return /* @__PURE__ */ jsx25("div", { className: cn("tb-skeleton__group", className), children: Array.from({ length: lines }, (_, i) => /* @__PURE__ */ jsx25(
1548
+ return /* @__PURE__ */ jsx26("div", { className: cn("tb-skeleton__group", className), children: Array.from({ length: lines }, (_, i) => /* @__PURE__ */ jsx26(
1486
1549
  "div",
1487
1550
  {
1488
1551
  className: cn(
@@ -1494,7 +1557,7 @@ function Skeleton({
1494
1557
  i
1495
1558
  )) });
1496
1559
  }
1497
- return /* @__PURE__ */ jsx25(
1560
+ return /* @__PURE__ */ jsx26(
1498
1561
  "div",
1499
1562
  {
1500
1563
  className: cn(
@@ -1508,13 +1571,13 @@ function Skeleton({
1508
1571
  }
1509
1572
 
1510
1573
  // src/components/Spinner.tsx
1511
- import { Fragment as Fragment3, jsx as jsx26, jsxs as jsxs21 } from "react/jsx-runtime";
1574
+ import { Fragment as Fragment3, jsx as jsx27, jsxs as jsxs22 } from "react/jsx-runtime";
1512
1575
  function Spinner({
1513
1576
  variant = "multi-square",
1514
1577
  size = "md",
1515
1578
  className
1516
1579
  }) {
1517
- return /* @__PURE__ */ jsx26(
1580
+ return /* @__PURE__ */ jsx27(
1518
1581
  "span",
1519
1582
  {
1520
1583
  className: cn(
@@ -1525,19 +1588,19 @@ function Spinner({
1525
1588
  ),
1526
1589
  role: "status",
1527
1590
  "aria-label": "Loading",
1528
- children: variant === "multi-square" && /* @__PURE__ */ jsxs21(Fragment3, { children: [
1529
- /* @__PURE__ */ jsx26("span", { className: "tb-spinner__square" }),
1530
- /* @__PURE__ */ jsx26("span", { className: "tb-spinner__square" }),
1531
- /* @__PURE__ */ jsx26("span", { className: "tb-spinner__square" }),
1532
- /* @__PURE__ */ jsx26("span", { className: "tb-spinner__square" }),
1533
- /* @__PURE__ */ jsx26("span", { className: "tb-spinner__square" })
1591
+ children: variant === "multi-square" && /* @__PURE__ */ jsxs22(Fragment3, { children: [
1592
+ /* @__PURE__ */ jsx27("span", { className: "tb-spinner__square" }),
1593
+ /* @__PURE__ */ jsx27("span", { className: "tb-spinner__square" }),
1594
+ /* @__PURE__ */ jsx27("span", { className: "tb-spinner__square" }),
1595
+ /* @__PURE__ */ jsx27("span", { className: "tb-spinner__square" }),
1596
+ /* @__PURE__ */ jsx27("span", { className: "tb-spinner__square" })
1534
1597
  ] })
1535
1598
  }
1536
1599
  );
1537
1600
  }
1538
1601
 
1539
1602
  // src/components/Pagination.tsx
1540
- import { jsx as jsx27, jsxs as jsxs22 } from "react/jsx-runtime";
1603
+ import { jsx as jsx28, jsxs as jsxs23 } from "react/jsx-runtime";
1541
1604
  function getPageRange(page, totalPages, siblingCount) {
1542
1605
  const totalSlots = siblingCount * 2 + 5;
1543
1606
  if (totalPages <= totalSlots) {
@@ -1570,8 +1633,8 @@ function Pagination({
1570
1633
  className
1571
1634
  }) {
1572
1635
  const pages = getPageRange(page, totalPages, siblingCount);
1573
- return /* @__PURE__ */ jsxs22("nav", { className: cn("tb-pagination", className), "aria-label": "Pagination", children: [
1574
- /* @__PURE__ */ jsx27(
1636
+ return /* @__PURE__ */ jsxs23("nav", { className: cn("tb-pagination", className), "aria-label": "Pagination", children: [
1637
+ /* @__PURE__ */ jsx28(
1575
1638
  "button",
1576
1639
  {
1577
1640
  className: "tb-pagination__btn tb-pagination__btn--prev",
@@ -1582,7 +1645,7 @@ function Pagination({
1582
1645
  }
1583
1646
  ),
1584
1647
  pages.map(
1585
- (p, i) => p === "ellipsis" ? /* @__PURE__ */ jsx27("span", { className: "tb-pagination__ellipsis", children: "..." }, `ellipsis-${i}`) : /* @__PURE__ */ jsx27(
1648
+ (p, i) => p === "ellipsis" ? /* @__PURE__ */ jsx28("span", { className: "tb-pagination__ellipsis", children: "..." }, `ellipsis-${i}`) : /* @__PURE__ */ jsx28(
1586
1649
  "button",
1587
1650
  {
1588
1651
  className: cn(
@@ -1596,7 +1659,7 @@ function Pagination({
1596
1659
  p
1597
1660
  )
1598
1661
  ),
1599
- /* @__PURE__ */ jsx27(
1662
+ /* @__PURE__ */ jsx28(
1600
1663
  "button",
1601
1664
  {
1602
1665
  className: "tb-pagination__btn tb-pagination__btn--next",
@@ -1610,7 +1673,7 @@ function Pagination({
1610
1673
  }
1611
1674
 
1612
1675
  // src/components/Breadcrumbs.tsx
1613
- import { jsx as jsx28, jsxs as jsxs23 } from "react/jsx-runtime";
1676
+ import { jsx as jsx29, jsxs as jsxs24 } from "react/jsx-runtime";
1614
1677
  function Breadcrumbs({
1615
1678
  items,
1616
1679
  separator = "/",
@@ -1618,10 +1681,10 @@ function Breadcrumbs({
1618
1681
  className
1619
1682
  }) {
1620
1683
  const LinkComponent = linkComponent || "a";
1621
- return /* @__PURE__ */ jsx28("nav", { className: cn("tb-breadcrumbs", className), "aria-label": "Breadcrumb", children: /* @__PURE__ */ jsx28("ol", { className: "tb-breadcrumbs__list", children: items.map((item, i) => {
1684
+ return /* @__PURE__ */ jsx29("nav", { className: cn("tb-breadcrumbs", className), "aria-label": "Breadcrumb", children: /* @__PURE__ */ jsx29("ol", { className: "tb-breadcrumbs__list", children: items.map((item, i) => {
1622
1685
  const isLast = i === items.length - 1;
1623
- return /* @__PURE__ */ jsxs23("li", { className: "tb-breadcrumbs__item", children: [
1624
- item.href && !isLast ? /* @__PURE__ */ jsx28(LinkComponent, { href: item.href, className: "tb-breadcrumbs__link", children: item.label }) : /* @__PURE__ */ jsx28(
1686
+ return /* @__PURE__ */ jsxs24("li", { className: "tb-breadcrumbs__item", children: [
1687
+ item.href && !isLast ? /* @__PURE__ */ jsx29(LinkComponent, { href: item.href, className: "tb-breadcrumbs__link", children: item.label }) : /* @__PURE__ */ jsx29(
1625
1688
  "span",
1626
1689
  {
1627
1690
  className: "tb-breadcrumbs__current",
@@ -1629,14 +1692,14 @@ function Breadcrumbs({
1629
1692
  children: item.label
1630
1693
  }
1631
1694
  ),
1632
- !isLast && /* @__PURE__ */ jsx28("span", { className: "tb-breadcrumbs__separator", "aria-hidden": "true", children: separator })
1695
+ !isLast && /* @__PURE__ */ jsx29("span", { className: "tb-breadcrumbs__separator", "aria-hidden": "true", children: separator })
1633
1696
  ] }, i);
1634
1697
  }) }) });
1635
1698
  }
1636
1699
 
1637
1700
  // src/components/Toolbar.tsx
1638
1701
  import { useState as useState8, useRef as useRef5, useEffect as useEffect6 } from "react";
1639
- import { jsx as jsx29, jsxs as jsxs24 } from "react/jsx-runtime";
1702
+ import { jsx as jsx30, jsxs as jsxs25 } from "react/jsx-runtime";
1640
1703
  function ToolbarSelect({
1641
1704
  options,
1642
1705
  value: controlledValue,
@@ -1665,13 +1728,13 @@ function ToolbarSelect({
1665
1728
  onChange?.(opt.value);
1666
1729
  setOpen(false);
1667
1730
  }
1668
- return /* @__PURE__ */ jsxs24(
1731
+ return /* @__PURE__ */ jsxs25(
1669
1732
  "div",
1670
1733
  {
1671
1734
  ref,
1672
1735
  className: cn("tb-toolbar__select", open && "tb-toolbar__select--open", className),
1673
1736
  children: [
1674
- /* @__PURE__ */ jsxs24(
1737
+ /* @__PURE__ */ jsxs25(
1675
1738
  "button",
1676
1739
  {
1677
1740
  type: "button",
@@ -1682,20 +1745,19 @@ function ToolbarSelect({
1682
1745
  setOpen(!open);
1683
1746
  },
1684
1747
  children: [
1685
- /* @__PURE__ */ jsx29("span", { children: activeOption?.label ?? "" }),
1686
- /* @__PURE__ */ jsx29("span", { className: "tb-toolbar__chevron" })
1748
+ /* @__PURE__ */ jsx30("span", { children: activeOption?.label ?? "" }),
1749
+ /* @__PURE__ */ jsx30("span", { className: "tb-toolbar__chevron" })
1687
1750
  ]
1688
1751
  }
1689
1752
  ),
1690
- /* @__PURE__ */ jsx29("div", { className: cn("tb-toolbar__dropdown", dropDown && "tb-toolbar__dropdown--down"), children: options.map((opt) => /* @__PURE__ */ jsxs24(
1753
+ /* @__PURE__ */ jsx30("div", { className: cn("tb-toolbar__dropdown", dropDown && "tb-toolbar__dropdown--down"), children: options.map((opt) => /* @__PURE__ */ jsxs25(
1691
1754
  "div",
1692
1755
  {
1693
1756
  className: cn("tb-toolbar__option", opt.value === value && "tb-toolbar__option--active"),
1694
- style: { fontFamily: opt.value },
1695
1757
  onClick: () => select(opt),
1696
1758
  children: [
1697
1759
  opt.label,
1698
- opt.meta && /* @__PURE__ */ jsx29("span", { className: "tb-toolbar__option-meta", children: opt.meta })
1760
+ opt.meta && /* @__PURE__ */ jsx30("span", { className: "tb-toolbar__option-meta", children: opt.meta })
1699
1761
  ]
1700
1762
  },
1701
1763
  opt.value
@@ -1705,13 +1767,13 @@ function ToolbarSelect({
1705
1767
  );
1706
1768
  }
1707
1769
  function ToolbarLabel({ children, className, ...props }) {
1708
- return /* @__PURE__ */ jsx29("span", { className: cn("tb-toolbar__label", className), ...props, children });
1770
+ return /* @__PURE__ */ jsx30("span", { className: cn("tb-toolbar__label", className), ...props, children });
1709
1771
  }
1710
1772
  function ToolbarDivider({ className, ...props }) {
1711
- return /* @__PURE__ */ jsx29("div", { className: cn("tb-toolbar__divider", className), ...props });
1773
+ return /* @__PURE__ */ jsx30("div", { className: cn("tb-toolbar__divider", className), ...props });
1712
1774
  }
1713
1775
  function ToolbarRoot({ position, fixed = false, children, className, ref, ...props }) {
1714
- return /* @__PURE__ */ jsx29(
1776
+ return /* @__PURE__ */ jsx30(
1715
1777
  "div",
1716
1778
  {
1717
1779
  ref,
@@ -1732,13 +1794,207 @@ var Toolbar = Object.assign(ToolbarRoot, {
1732
1794
  Divider: ToolbarDivider
1733
1795
  });
1734
1796
 
1797
+ // src/components/FileUpload.tsx
1798
+ import {
1799
+ useState as useState9,
1800
+ useCallback as useCallback4,
1801
+ useRef as useRef6,
1802
+ useId as useId5
1803
+ } from "react";
1804
+ import { Fragment as Fragment4, jsx as jsx31, jsxs as jsxs26 } from "react/jsx-runtime";
1805
+ function formatSize(bytes) {
1806
+ if (bytes < 1024) return `${bytes} B`;
1807
+ if (bytes < 1024 * 1024) return `${(bytes / 1024).toFixed(1)} KB`;
1808
+ return `${(bytes / (1024 * 1024)).toFixed(1)} MB`;
1809
+ }
1810
+ function isImage(file) {
1811
+ return file.type.startsWith("image/");
1812
+ }
1813
+ function FileUpload({
1814
+ onChange,
1815
+ value,
1816
+ accept,
1817
+ multiple = false,
1818
+ maxSize,
1819
+ compact = false,
1820
+ preview = false,
1821
+ label,
1822
+ placeholder,
1823
+ hint,
1824
+ error: errorProp,
1825
+ icon,
1826
+ disabled = false,
1827
+ className,
1828
+ ref
1829
+ }) {
1830
+ const autoId = useId5();
1831
+ const inputRef = useRef6(null);
1832
+ const [dragging, setDragging] = useState9(false);
1833
+ const [files, setFiles] = useState9([]);
1834
+ const [internalError, setInternalError] = useState9();
1835
+ const error = errorProp ?? internalError;
1836
+ const displayFiles = value ? value.map((f) => ({ file: f, preview: preview && isImage(f) ? URL.createObjectURL(f) : void 0 })) : files;
1837
+ const processFiles = useCallback4(
1838
+ (incoming) => {
1839
+ const arr = Array.from(incoming);
1840
+ setInternalError(void 0);
1841
+ if (maxSize) {
1842
+ const tooBig = arr.find((f) => f.size > maxSize);
1843
+ if (tooBig) {
1844
+ setInternalError(`${tooBig.name} exceeds ${formatSize(maxSize)}`);
1845
+ return;
1846
+ }
1847
+ }
1848
+ const newFiles = arr.map((f) => ({
1849
+ file: f,
1850
+ preview: preview && isImage(f) ? URL.createObjectURL(f) : void 0
1851
+ }));
1852
+ if (multiple) {
1853
+ const merged = [...files, ...newFiles];
1854
+ setFiles(merged);
1855
+ onChange?.(merged.map((f) => f.file));
1856
+ } else {
1857
+ files.forEach((f) => f.preview && URL.revokeObjectURL(f.preview));
1858
+ setFiles(newFiles.slice(0, 1));
1859
+ onChange?.(newFiles.slice(0, 1).map((f) => f.file));
1860
+ }
1861
+ },
1862
+ [files, maxSize, multiple, onChange, preview]
1863
+ );
1864
+ const handleChange = useCallback4(
1865
+ (e) => {
1866
+ if (e.target.files?.length) {
1867
+ processFiles(e.target.files);
1868
+ }
1869
+ e.target.value = "";
1870
+ },
1871
+ [processFiles]
1872
+ );
1873
+ const handleDragOver = useCallback4(
1874
+ (e) => {
1875
+ e.preventDefault();
1876
+ if (!disabled) setDragging(true);
1877
+ },
1878
+ [disabled]
1879
+ );
1880
+ const handleDragLeave = useCallback4((e) => {
1881
+ e.preventDefault();
1882
+ setDragging(false);
1883
+ }, []);
1884
+ const handleDrop = useCallback4(
1885
+ (e) => {
1886
+ e.preventDefault();
1887
+ setDragging(false);
1888
+ if (!disabled && e.dataTransfer.files.length) {
1889
+ processFiles(e.dataTransfer.files);
1890
+ }
1891
+ },
1892
+ [disabled, processFiles]
1893
+ );
1894
+ const removeFile = useCallback4(
1895
+ (index) => {
1896
+ const removed = files[index];
1897
+ if (removed?.preview) URL.revokeObjectURL(removed.preview);
1898
+ const next = files.filter((_, i) => i !== index);
1899
+ setFiles(next);
1900
+ onChange?.(next.map((f) => f.file));
1901
+ },
1902
+ [files, onChange]
1903
+ );
1904
+ const setRef = useCallback4(
1905
+ (el) => {
1906
+ inputRef.current = el;
1907
+ if (typeof ref === "function") ref(el);
1908
+ else if (ref) ref.current = el;
1909
+ },
1910
+ [ref]
1911
+ );
1912
+ const defaultPlaceholder = compact ? "Choose file" : "Drop files here or click to browse";
1913
+ return /* @__PURE__ */ jsxs26(
1914
+ "div",
1915
+ {
1916
+ className: cn(
1917
+ "tb-file-upload",
1918
+ compact && "tb-file-upload--compact",
1919
+ className
1920
+ ),
1921
+ children: [
1922
+ label && /* @__PURE__ */ jsx31("label", { htmlFor: autoId, className: "tb-label", children: label }),
1923
+ /* @__PURE__ */ jsxs26(
1924
+ "div",
1925
+ {
1926
+ className: cn(
1927
+ "tb-file-upload__zone",
1928
+ dragging && "tb-file-upload__zone--drag",
1929
+ error && "tb-file-upload__zone--error",
1930
+ disabled && "tb-file-upload__zone--disabled"
1931
+ ),
1932
+ onDragOver: handleDragOver,
1933
+ onDragLeave: handleDragLeave,
1934
+ onDrop: handleDrop,
1935
+ children: [
1936
+ /* @__PURE__ */ jsx31(
1937
+ "input",
1938
+ {
1939
+ ref: setRef,
1940
+ id: autoId,
1941
+ type: "file",
1942
+ className: "tb-file-upload__input",
1943
+ accept,
1944
+ multiple,
1945
+ disabled,
1946
+ onChange: handleChange,
1947
+ "aria-describedby": error ? `${autoId}-error` : void 0
1948
+ }
1949
+ ),
1950
+ compact ? /* @__PURE__ */ jsxs26("span", { className: "tb-btn tb-btn--sm", children: [
1951
+ icon && /* @__PURE__ */ jsx31("span", { className: "tb-btn__icon", children: icon }),
1952
+ placeholder ?? defaultPlaceholder
1953
+ ] }) : /* @__PURE__ */ jsxs26(Fragment4, { children: [
1954
+ /* @__PURE__ */ jsx31("span", { className: "tb-file-upload__icon", "aria-hidden": "true", children: icon ?? /* @__PURE__ */ jsx31("span", { className: "material-symbols-outlined", children: "upload_file" }) }),
1955
+ /* @__PURE__ */ jsx31("span", { className: "tb-file-upload__text", children: placeholder ?? defaultPlaceholder }),
1956
+ hint && /* @__PURE__ */ jsx31("span", { className: "tb-file-upload__hint-text", children: hint })
1957
+ ] })
1958
+ ]
1959
+ }
1960
+ ),
1961
+ error && /* @__PURE__ */ jsx31("span", { id: `${autoId}-error`, className: "tb-file-upload__error", role: "alert", children: error }),
1962
+ displayFiles.length > 0 && /* @__PURE__ */ jsx31("div", { className: "tb-file-upload__files", children: displayFiles.map((f, i) => /* @__PURE__ */ jsxs26("div", { className: "tb-file-upload__file", children: [
1963
+ f.preview ? /* @__PURE__ */ jsx31(
1964
+ "img",
1965
+ {
1966
+ src: f.preview,
1967
+ alt: f.file.name,
1968
+ className: "tb-file-upload__preview"
1969
+ }
1970
+ ) : /* @__PURE__ */ jsx31("span", { className: "tb-file-upload__file-icon", "aria-hidden": "true", children: /* @__PURE__ */ jsx31("span", { className: "material-symbols-outlined", children: isImage(f.file) ? "image" : "description" }) }),
1971
+ /* @__PURE__ */ jsxs26("div", { className: "tb-file-upload__file-info", children: [
1972
+ /* @__PURE__ */ jsx31("div", { className: "tb-file-upload__file-name", children: f.file.name }),
1973
+ /* @__PURE__ */ jsx31("div", { className: "tb-file-upload__file-size", children: formatSize(f.file.size) })
1974
+ ] }),
1975
+ !value && /* @__PURE__ */ jsx31(
1976
+ "button",
1977
+ {
1978
+ type: "button",
1979
+ className: "tb-file-upload__file-remove",
1980
+ onClick: () => removeFile(i),
1981
+ "aria-label": `Remove ${f.file.name}`,
1982
+ children: /* @__PURE__ */ jsx31("span", { className: "material-symbols-outlined", children: "close" })
1983
+ }
1984
+ )
1985
+ ] }, `${f.file.name}-${i}`)) })
1986
+ ]
1987
+ }
1988
+ );
1989
+ }
1990
+
1735
1991
  // src/components/Sidenav/Sidenav.tsx
1736
1992
  import {
1737
1993
  createContext as createContext5,
1738
1994
  useContext as useContext5,
1739
- useState as useState9
1995
+ useState as useState10
1740
1996
  } from "react";
1741
- import { jsx as jsx30, jsxs as jsxs25 } from "react/jsx-runtime";
1997
+ import { jsx as jsx32, jsxs as jsxs27 } from "react/jsx-runtime";
1742
1998
  var SidenavContext = createContext5({
1743
1999
  iconPosition: "left",
1744
2000
  borderSide: "left"
@@ -1751,7 +2007,7 @@ function SidenavRoot({
1751
2007
  ref,
1752
2008
  ...props
1753
2009
  }) {
1754
- return /* @__PURE__ */ jsx30(SidenavContext.Provider, { value: { iconPosition, borderSide }, children: /* @__PURE__ */ jsx30(
2010
+ return /* @__PURE__ */ jsx32(SidenavContext.Provider, { value: { iconPosition, borderSide }, children: /* @__PURE__ */ jsx32(
1755
2011
  "nav",
1756
2012
  {
1757
2013
  ref,
@@ -1775,7 +2031,7 @@ function Item({
1775
2031
  ...props
1776
2032
  }) {
1777
2033
  const Component = as || "a";
1778
- return /* @__PURE__ */ jsx30(
2034
+ return /* @__PURE__ */ jsx32(
1779
2035
  Component,
1780
2036
  {
1781
2037
  ref,
@@ -1790,7 +2046,7 @@ function Item({
1790
2046
  );
1791
2047
  }
1792
2048
  function Icon({ className, ref, ...props }) {
1793
- return /* @__PURE__ */ jsx30(
2049
+ return /* @__PURE__ */ jsx32(
1794
2050
  "span",
1795
2051
  {
1796
2052
  ref,
@@ -1810,7 +2066,7 @@ function Group({
1810
2066
  ref,
1811
2067
  ...props
1812
2068
  }) {
1813
- const [internalOpen, setInternalOpen] = useState9(defaultOpen);
2069
+ const [internalOpen, setInternalOpen] = useState10(defaultOpen);
1814
2070
  const isControlled = controlledOpen !== void 0;
1815
2071
  const isOpen = isControlled ? controlledOpen : internalOpen;
1816
2072
  function toggle() {
@@ -1818,7 +2074,7 @@ function Group({
1818
2074
  if (!isControlled) setInternalOpen(next);
1819
2075
  onOpenChange?.(next);
1820
2076
  }
1821
- return /* @__PURE__ */ jsxs25(
2077
+ return /* @__PURE__ */ jsxs27(
1822
2078
  "div",
1823
2079
  {
1824
2080
  ref,
@@ -1829,7 +2085,7 @@ function Group({
1829
2085
  ),
1830
2086
  ...props,
1831
2087
  children: [
1832
- /* @__PURE__ */ jsxs25(
2088
+ /* @__PURE__ */ jsxs27(
1833
2089
  "button",
1834
2090
  {
1835
2091
  type: "button",
@@ -1837,13 +2093,13 @@ function Group({
1837
2093
  onClick: toggle,
1838
2094
  "aria-expanded": isOpen,
1839
2095
  children: [
1840
- icon && /* @__PURE__ */ jsx30("span", { className: "tb-sidenav__icon", children: icon }),
2096
+ icon && /* @__PURE__ */ jsx32("span", { className: "tb-sidenav__icon", children: icon }),
1841
2097
  label,
1842
- /* @__PURE__ */ jsx30("span", { className: "tb-sidenav__group-chevron material-symbols-outlined", "aria-hidden": "true", children: "expand_more" })
2098
+ /* @__PURE__ */ jsx32("span", { className: "tb-sidenav__group-chevron material-symbols-outlined", "aria-hidden": "true", children: "expand_more" })
1843
2099
  ]
1844
2100
  }
1845
2101
  ),
1846
- /* @__PURE__ */ jsx30("div", { className: "tb-sidenav__group-content", children })
2102
+ /* @__PURE__ */ jsx32("div", { className: "tb-sidenav__group-content", children })
1847
2103
  ]
1848
2104
  }
1849
2105
  );
@@ -1855,27 +2111,27 @@ var Sidenav = Object.assign(SidenavRoot, {
1855
2111
  });
1856
2112
 
1857
2113
  // src/components/CodeBlock.tsx
1858
- import { useState as useState10, useRef as useRef6 } from "react";
1859
- import { jsx as jsx31, jsxs as jsxs26 } from "react/jsx-runtime";
2114
+ import { useState as useState11, useRef as useRef7 } from "react";
2115
+ import { jsx as jsx33, jsxs as jsxs28 } from "react/jsx-runtime";
1860
2116
  function CodeBlock({ children, copyText, className, ref, ...props }) {
1861
- const [copied, setCopied] = useState10(false);
1862
- const contentRef = useRef6(null);
2117
+ const [copied, setCopied] = useState11(false);
2118
+ const contentRef = useRef7(null);
1863
2119
  async function handleCopy() {
1864
2120
  const text = copyText ?? contentRef.current?.textContent ?? "";
1865
2121
  await navigator.clipboard.writeText(text);
1866
2122
  setCopied(true);
1867
2123
  setTimeout(() => setCopied(false), 2e3);
1868
2124
  }
1869
- return /* @__PURE__ */ jsxs26("div", { ref, className: cn("tb-code-block", className), ...props, children: [
1870
- /* @__PURE__ */ jsx31("div", { ref: contentRef, children }),
1871
- /* @__PURE__ */ jsx31(
2125
+ return /* @__PURE__ */ jsxs28("div", { ref, className: cn("tb-code-block", className), ...props, children: [
2126
+ /* @__PURE__ */ jsx33("div", { ref: contentRef, children }),
2127
+ /* @__PURE__ */ jsx33(
1872
2128
  "button",
1873
2129
  {
1874
2130
  type: "button",
1875
2131
  className: cn("tb-code-block__copy", copied && "tb-code-block__copy--copied"),
1876
2132
  onClick: handleCopy,
1877
2133
  "aria-label": copied ? "Copied" : "Copy to clipboard",
1878
- children: /* @__PURE__ */ jsx31("span", { className: "material-symbols-outlined", style: { fontSize: 14 }, children: copied ? "check" : "content_copy" })
2134
+ children: /* @__PURE__ */ jsx33("span", { className: "material-symbols-outlined", style: { fontSize: 14 }, children: copied ? "check" : "content_copy" })
1879
2135
  }
1880
2136
  )
1881
2137
  ] });
@@ -1885,11 +2141,11 @@ function CodeBlock({ children, copyText, className, ref, ...props }) {
1885
2141
  import {
1886
2142
  createContext as createContext6,
1887
2143
  useContext as useContext6,
1888
- useState as useState11,
2144
+ useState as useState12,
1889
2145
  useEffect as useEffect7,
1890
- useCallback as useCallback4
2146
+ useCallback as useCallback5
1891
2147
  } from "react";
1892
- import { jsx as jsx32 } from "react/jsx-runtime";
2148
+ import { jsx as jsx34 } from "react/jsx-runtime";
1893
2149
  var ThemeContext = createContext6(null);
1894
2150
  function useTheme() {
1895
2151
  const ctx = useContext6(ThemeContext);
@@ -1907,25 +2163,25 @@ function ThemeProvider({
1907
2163
  }) {
1908
2164
  const isThemeControlled = controlledTheme !== void 0;
1909
2165
  const isContrastControlled = controlledContrast !== void 0;
1910
- const [internalTheme, setInternalTheme] = useState11(defaultTheme);
1911
- const [internalContrast, setInternalContrast] = useState11(defaultContrast);
2166
+ const [internalTheme, setInternalTheme] = useState12(defaultTheme);
2167
+ const [internalContrast, setInternalContrast] = useState12(defaultContrast);
1912
2168
  const theme = isThemeControlled ? controlledTheme : internalTheme;
1913
2169
  const contrast = isContrastControlled ? controlledContrast : internalContrast;
1914
- const setTheme = useCallback4(
2170
+ const setTheme = useCallback5(
1915
2171
  (next) => {
1916
2172
  if (!isThemeControlled) setInternalTheme(next);
1917
2173
  onThemeChange?.(next);
1918
2174
  },
1919
2175
  [isThemeControlled, onThemeChange]
1920
2176
  );
1921
- const setContrast = useCallback4(
2177
+ const setContrast = useCallback5(
1922
2178
  (next) => {
1923
2179
  if (!isContrastControlled) setInternalContrast(next);
1924
2180
  onContrastChange?.(next);
1925
2181
  },
1926
2182
  [isContrastControlled, onContrastChange]
1927
2183
  );
1928
- const toggleTheme = useCallback4(
2184
+ const toggleTheme = useCallback5(
1929
2185
  () => setTheme(theme === "dark" ? "light" : "dark"),
1930
2186
  [theme, setTheme]
1931
2187
  );
@@ -1938,23 +2194,23 @@ function ThemeProvider({
1938
2194
  root.removeAttribute("data-contrast");
1939
2195
  }
1940
2196
  }, [theme, contrast]);
1941
- return /* @__PURE__ */ jsx32(ThemeContext.Provider, { value: { theme, setTheme, contrast, setContrast, toggleTheme }, children });
2197
+ return /* @__PURE__ */ jsx34(ThemeContext.Provider, { value: { theme, setTheme, contrast, setContrast, toggleTheme }, children });
1942
2198
  }
1943
2199
 
1944
2200
  // src/hooks/useDisclosure.ts
1945
- import { useState as useState12, useCallback as useCallback5 } from "react";
2201
+ import { useState as useState13, useCallback as useCallback6 } from "react";
1946
2202
  function useDisclosure(options = {}) {
1947
2203
  const { defaultOpen = false, onOpen: onOpenCallback, onClose: onCloseCallback } = options;
1948
- const [isOpen, setIsOpen] = useState12(defaultOpen);
1949
- const onOpen = useCallback5(() => {
2204
+ const [isOpen, setIsOpen] = useState13(defaultOpen);
2205
+ const onOpen = useCallback6(() => {
1950
2206
  setIsOpen(true);
1951
2207
  onOpenCallback?.();
1952
2208
  }, [onOpenCallback]);
1953
- const onClose = useCallback5(() => {
2209
+ const onClose = useCallback6(() => {
1954
2210
  setIsOpen(false);
1955
2211
  onCloseCallback?.();
1956
2212
  }, [onCloseCallback]);
1957
- const onToggle = useCallback5(() => {
2213
+ const onToggle = useCallback6(() => {
1958
2214
  if (isOpen) {
1959
2215
  onClose();
1960
2216
  } else {
@@ -1965,7 +2221,7 @@ function useDisclosure(options = {}) {
1965
2221
  }
1966
2222
 
1967
2223
  // src/hooks/useFocusTrap.ts
1968
- import { useEffect as useEffect8, useRef as useRef7 } from "react";
2224
+ import { useEffect as useEffect8, useRef as useRef8 } from "react";
1969
2225
  var FOCUSABLE_SELECTOR = [
1970
2226
  "a[href]",
1971
2227
  "button:not([disabled])",
@@ -1975,7 +2231,7 @@ var FOCUSABLE_SELECTOR = [
1975
2231
  '[tabindex]:not([tabindex="-1"])'
1976
2232
  ].join(", ");
1977
2233
  function useFocusTrap(ref, active) {
1978
- const previouslyFocusedRef = useRef7(null);
2234
+ const previouslyFocusedRef = useRef8(null);
1979
2235
  useEffect8(() => {
1980
2236
  if (!active || !ref.current) return;
1981
2237
  previouslyFocusedRef.current = document.activeElement;
@@ -2013,13 +2269,13 @@ function useFocusTrap(ref, active) {
2013
2269
  }
2014
2270
 
2015
2271
  // src/hooks/useKeyboardNav.ts
2016
- import { useState as useState13, useCallback as useCallback6 } from "react";
2272
+ import { useState as useState14, useCallback as useCallback7 } from "react";
2017
2273
  function useKeyboardNav(items, options = {}) {
2018
2274
  const { orientation = "vertical", loop = true, onSelect } = options;
2019
- const [activeIndex, setActiveIndex] = useState13(0);
2275
+ const [activeIndex, setActiveIndex] = useState14(0);
2020
2276
  const prevKey = orientation === "vertical" ? "ArrowUp" : "ArrowLeft";
2021
2277
  const nextKey = orientation === "vertical" ? "ArrowDown" : "ArrowRight";
2022
- const onKeyDown = useCallback6(
2278
+ const onKeyDown = useCallback7(
2023
2279
  (e) => {
2024
2280
  const list = items.current;
2025
2281
  if (!list || list.length === 0) return;
@@ -2068,9 +2324,9 @@ function useKeyboardNav(items, options = {}) {
2068
2324
  }
2069
2325
 
2070
2326
  // src/hooks/useReducedMotion.ts
2071
- import { useState as useState14, useEffect as useEffect9 } from "react";
2327
+ import { useState as useState15, useEffect as useEffect9 } from "react";
2072
2328
  function useReducedMotion() {
2073
- const [reduced, setReduced] = useState14(false);
2329
+ const [reduced, setReduced] = useState15(false);
2074
2330
  useEffect9(() => {
2075
2331
  const mq = window.matchMedia("(prefers-reduced-motion: reduce)");
2076
2332
  setReduced(mq.matches);
@@ -2082,9 +2338,9 @@ function useReducedMotion() {
2082
2338
  }
2083
2339
 
2084
2340
  // src/hooks/useMergedRef.ts
2085
- import { useCallback as useCallback7 } from "react";
2341
+ import { useCallback as useCallback8 } from "react";
2086
2342
  function useMergedRef(...refs) {
2087
- return useCallback7((node) => {
2343
+ return useCallback8((node) => {
2088
2344
  refs.forEach((ref) => {
2089
2345
  if (typeof ref === "function") ref(node);
2090
2346
  else if (ref) ref.current = node;
@@ -2101,6 +2357,7 @@ export {
2101
2357
  Checkbox,
2102
2358
  CodeBlock,
2103
2359
  CornerBrackets,
2360
+ FileUpload,
2104
2361
  Input,
2105
2362
  Modal,
2106
2363
  NavDropdown,
@@ -2115,6 +2372,7 @@ export {
2115
2372
  SelectTrigger,
2116
2373
  Sidenav,
2117
2374
  Skeleton,
2375
+ Slider,
2118
2376
  Spinner,
2119
2377
  Table,
2120
2378
  Tabs,