@tapizlabs/ui 0.1.4 → 0.1.6

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.d.ts CHANGED
@@ -408,9 +408,10 @@ interface PageHeaderProps {
408
408
  subtitle?: string;
409
409
  action?: ReactNode;
410
410
  icon?: ReactNode;
411
+ banner?: ReactNode;
411
412
  className?: string;
412
413
  }
413
- declare function PageHeader({ title, subtitle, action, icon, className }: PageHeaderProps): react.JSX.Element;
414
+ declare function PageHeader({ title, subtitle, action, icon, banner, className }: PageHeaderProps): react.JSX.Element;
414
415
 
415
416
  interface SearchInputProps extends Omit<InputHTMLAttributes<HTMLInputElement>, "value" | "onChange"> {
416
417
  value: string;
package/dist/index.js CHANGED
@@ -992,7 +992,7 @@ function Toast({ message, ok, durationMs = 5e3 }) {
992
992
  const borderColor = ok ? "var(--color-good)" : "var(--color-warn)";
993
993
  const accentColor = ok ? "var(--color-good)" : "var(--color-warn)";
994
994
  return createPortal(
995
- /* @__PURE__ */ jsx20("div", { className: "pointer-events-none fixed bottom-20 left-4 right-4 z-60 flex justify-center sm:bottom-auto sm:left-auto sm:right-5 sm:top-5 sm:justify-end", children: /* @__PURE__ */ jsxs13(
995
+ /* @__PURE__ */ jsx20("div", { className: "pointer-events-none fixed bottom-20 left-4 right-4 z-9999 flex justify-center min-[600px]:bottom-auto min-[600px]:left-auto min-[600px]:right-5 min-[600px]:top-5 min-[600px]:justify-end", children: /* @__PURE__ */ jsxs13(
996
996
  "div",
997
997
  {
998
998
  className: `${transform} pointer-events-auto relative flex w-full max-w-sm items-center gap-2.5 overflow-hidden px-4 py-3 text-sm font-medium transition-all duration-300 sm:w-auto sm:max-w-xs`,
@@ -1095,33 +1095,82 @@ function DefaultErrorFallback({
1095
1095
  label = "Tapiz UI \xB7 Runtime Error",
1096
1096
  reloadLabel = "Reload page"
1097
1097
  }) {
1098
- return /* @__PURE__ */ jsxs16("div", { className: "fixed inset-0 flex flex-col items-center justify-center overflow-hidden bg-ink-100 px-6", children: [
1099
- /* @__PURE__ */ jsx23(GridBg, {}),
1100
- /* @__PURE__ */ jsx23(Spotlight, { color: "rgba(255,100,100,0.06)" }),
1101
- /* @__PURE__ */ jsxs16("div", { className: "relative z-10 flex w-full max-w-sm animate-fade-in-up flex-col items-center gap-6 text-center", children: [
1102
- /* @__PURE__ */ jsxs16("div", { children: [
1103
- /* @__PURE__ */ jsx23("div", { className: "font-mono text-[clamp(72px,16vw,120px)] font-bold leading-none tracking-[-0.04em] text-border-hi", children: "500" }),
1104
- /* @__PURE__ */ jsx23("div", { className: "mt-1 h-0.5 w-full opacity-35 [background:linear-gradient(90deg,transparent,var(--color-warn),transparent)]" })
1105
- ] }),
1106
- /* @__PURE__ */ jsxs16("div", { className: "flex flex-col gap-1.5", children: [
1107
- /* @__PURE__ */ jsx23("p", { className: "text-base font-semibold text-txt-1", children: title }),
1108
- /* @__PURE__ */ jsx23("p", { className: "font-mono text-[11px] leading-relaxed text-txt-3", children: description })
1109
- ] }),
1110
- /* @__PURE__ */ jsx23("div", { className: "bg-[rgba(255,180,0,0.04)] px-2 py-1 font-mono text-[9px] uppercase tracking-[.2em] text-warn [border:1px_solid_rgba(255,180,0,0.2)]", children: label }),
1111
- /* @__PURE__ */ jsx23("div", { className: "w-full", children: /* @__PURE__ */ jsxs16(
1112
- "button",
1113
- {
1114
- type: "button",
1115
- onClick: () => window.location.reload(),
1116
- className: "btn-primary w-full py-2.5 text-xs",
1117
- children: [
1118
- reloadLabel,
1119
- " \u2192"
1120
- ]
1121
- }
1122
- ) })
1123
- ] })
1124
- ] });
1098
+ return /* @__PURE__ */ jsxs16(
1099
+ "div",
1100
+ {
1101
+ className: "fixed inset-0 flex flex-col items-center justify-center overflow-hidden px-6",
1102
+ style: { background: "var(--color-ink-100)" },
1103
+ children: [
1104
+ /* @__PURE__ */ jsx23(GridBg, {}),
1105
+ /* @__PURE__ */ jsx23(Spotlight, { color: "rgba(255,100,100,0.06)" }),
1106
+ /* @__PURE__ */ jsxs16(
1107
+ "div",
1108
+ {
1109
+ className: "relative z-10 flex w-full max-w-sm flex-col items-center gap-6 text-center",
1110
+ style: { animation: "var(--animate-fade-in-up)" },
1111
+ children: [
1112
+ /* @__PURE__ */ jsxs16("div", { children: [
1113
+ /* @__PURE__ */ jsx23(
1114
+ "div",
1115
+ {
1116
+ className: "font-mono font-bold leading-none",
1117
+ style: { fontSize: "clamp(72px,16vw,120px)", color: "var(--color-border-hi)", letterSpacing: "-0.04em" },
1118
+ children: "500"
1119
+ }
1120
+ ),
1121
+ /* @__PURE__ */ jsx23(
1122
+ "div",
1123
+ {
1124
+ className: "mt-1 h-0.5 w-full",
1125
+ style: { background: "linear-gradient(90deg,transparent,rgba(255,80,80,0.7),transparent)", opacity: 0.5 }
1126
+ }
1127
+ )
1128
+ ] }),
1129
+ /* @__PURE__ */ jsxs16("div", { className: "flex flex-col gap-1.5", children: [
1130
+ /* @__PURE__ */ jsx23("p", { className: "text-base font-semibold", style: { color: "var(--color-txt-1)" }, children: title }),
1131
+ /* @__PURE__ */ jsx23("p", { className: "font-mono text-[11px] leading-relaxed", style: { color: "var(--color-txt-3)" }, children: description })
1132
+ ] }),
1133
+ /* @__PURE__ */ jsx23(
1134
+ "div",
1135
+ {
1136
+ className: "font-mono text-[9px] uppercase px-2 py-1",
1137
+ style: {
1138
+ letterSpacing: ".2em",
1139
+ color: "rgba(255,80,80,0.9)",
1140
+ border: "1px solid rgba(255,80,80,0.25)",
1141
+ background: "rgba(255,80,80,0.05)"
1142
+ },
1143
+ children: label
1144
+ }
1145
+ ),
1146
+ /* @__PURE__ */ jsx23("div", { className: "w-full", children: /* @__PURE__ */ jsxs16(
1147
+ "button",
1148
+ {
1149
+ type: "button",
1150
+ onClick: () => window.location.reload(),
1151
+ style: {
1152
+ width: "100%",
1153
+ padding: "10px",
1154
+ fontSize: "12px",
1155
+ background: "var(--color-primary-300)",
1156
+ color: "var(--color-ink-100)",
1157
+ border: "none",
1158
+ cursor: "pointer",
1159
+ fontFamily: "var(--font-mono)",
1160
+ letterSpacing: "0.05em"
1161
+ },
1162
+ children: [
1163
+ reloadLabel,
1164
+ " \u2192"
1165
+ ]
1166
+ }
1167
+ ) })
1168
+ ]
1169
+ }
1170
+ )
1171
+ ]
1172
+ }
1173
+ );
1125
1174
  }
1126
1175
  function GridBg() {
1127
1176
  return /* @__PURE__ */ jsx23("div", { className: "pointer-events-none absolute inset-0 opacity-50 bg-[linear-gradient(var(--color-border)_1px,transparent_1px),linear-gradient(90deg,var(--color-border)_1px,transparent_1px)] bg-size-[32px_32px]" });
@@ -1312,11 +1361,12 @@ function Tooltip({ text, children, position = "top", align = "center", width = "
1312
1361
  /* @__PURE__ */ jsxs19(
1313
1362
  "span",
1314
1363
  {
1315
- className: `pointer-events-none absolute ${alignClass} z-60 ${width} w-max px-2.5 py-1.5
1364
+ className: `pointer-events-none absolute ${alignClass} ${width} w-max px-2.5 py-1.5
1316
1365
  text-center text-[11px] leading-snug
1317
1366
  opacity-0 group-hover:opacity-100 transition-opacity duration-150
1318
1367
  ${position === "top" ? "bottom-full mb-2" : "top-full mt-2"}`,
1319
1368
  style: {
1369
+ zIndex: 60,
1320
1370
  background: "var(--color-ink-300)",
1321
1371
  border: "1px solid var(--color-border-hi)",
1322
1372
  color: "var(--color-txt-2)",
@@ -1477,7 +1527,7 @@ function InfoBanner(props) {
1477
1527
  const { text, variant = "info", className: className2 = "" } = props;
1478
1528
  const styles = variant === "warn" ? "bg-warn/8 border-warn/25 text-warn" : variant === "lock" ? "bg-warn/8 border-warn/30 text-warn" : "bg-primary-500/10 border-primary-100 text-primary-500";
1479
1529
  const icon = variant === "lock" ? /* @__PURE__ */ jsx32(LockIcon, { size: 14, className: "mt-1 shrink-0" }) : /* @__PURE__ */ jsx32(Info, { size: 14, className: "mt-1 shrink-0" });
1480
- return /* @__PURE__ */ jsxs22("div", { className: `flex items-start gap-2 border px-3 py-2.5 text-[13px] ${styles} ${className2}`, children: [
1530
+ return /* @__PURE__ */ jsxs22("div", { className: `app-info-banner flex items-start gap-2 border px-3 py-2.5 text-[13px] ${styles} ${className2}`, children: [
1481
1531
  icon,
1482
1532
  /* @__PURE__ */ jsx32("p", { style: { fontFamily: "var(--font-mono)" }, children: text })
1483
1533
  ] });
@@ -1486,7 +1536,7 @@ function InfoBanner(props) {
1486
1536
  return /* @__PURE__ */ jsxs22(
1487
1537
  "div",
1488
1538
  {
1489
- className: `px-4 py-3 ${className}`,
1539
+ className: `app-info-banner px-4 py-3 ${className}`,
1490
1540
  style: {
1491
1541
  background: "rgba(94,231,255,0.04)",
1492
1542
  border: "1px solid rgba(94,231,255,0.12)",
@@ -1509,20 +1559,23 @@ function InfoBanner(props) {
1509
1559
 
1510
1560
  // src/components/shared/PageHeader.tsx
1511
1561
  import { jsx as jsx33, jsxs as jsxs23 } from "react/jsx-runtime";
1512
- function PageHeader({ title, subtitle, action, icon, className = "" }) {
1562
+ function PageHeader({ title, subtitle, action, icon, banner, className = "" }) {
1513
1563
  return /* @__PURE__ */ jsxs23(
1514
1564
  "div",
1515
1565
  {
1516
- className: `mb-5 flex flex-col gap-3 border-b border-border pb-4 animate-fade-in-up sm:flex-row sm:items-start sm:justify-between ${className}`.trim(),
1566
+ className: `page-header mb-5 flex flex-col gap-3 border-b border-border pb-4 animate-fade-in-up ${className}`.trim(),
1517
1567
  children: [
1518
- /* @__PURE__ */ jsxs23("div", { children: [
1519
- subtitle ? /* @__PURE__ */ jsx33("div", { className: "kicker mb-1.5 text-primary-300!", children: subtitle }) : null,
1520
- /* @__PURE__ */ jsxs23("div", { className: "flex items-center gap-2.5", children: [
1521
- icon ? /* @__PURE__ */ jsx33("span", { className: "text-primary-300", children: icon }) : null,
1522
- /* @__PURE__ */ jsx33("h2", { className: "font-(--font-display) text-[22px] tracking-[-0.03em] text-txt-1", children: title })
1523
- ] })
1568
+ /* @__PURE__ */ jsxs23("div", { className: "flex flex-col gap-3 sm:flex-row sm:items-start sm:justify-between", children: [
1569
+ /* @__PURE__ */ jsxs23("div", { children: [
1570
+ subtitle ? /* @__PURE__ */ jsx33("div", { className: "kicker mb-1.5", children: subtitle }) : null,
1571
+ /* @__PURE__ */ jsxs23("div", { className: "flex items-center gap-2.5", children: [
1572
+ icon ? /* @__PURE__ */ jsx33("span", { className: "text-primary-300", children: icon }) : null,
1573
+ /* @__PURE__ */ jsx33("h2", { className: "font-(--font-display) text-[22px] tracking-[-0.03em] text-txt-1", children: title })
1574
+ ] })
1575
+ ] }),
1576
+ action ? /* @__PURE__ */ jsx33("div", { children: action }) : null
1524
1577
  ] }),
1525
- action ? /* @__PURE__ */ jsx33("div", { children: action }) : null
1578
+ banner ? banner : null
1526
1579
  ]
1527
1580
  }
1528
1581
  );
@@ -1663,7 +1716,8 @@ function StatusBadge({ label, variant = "default", className = "" }) {
1663
1716
  }
1664
1717
 
1665
1718
  // src/components/shared/ActionMenu.tsx
1666
- import { useEffect as useEffect2, useRef as useRef2, useState as useState5 } from "react";
1719
+ import { useEffect as useEffect2, useLayoutEffect, useRef as useRef2, useState as useState5 } from "react";
1720
+ import { createPortal as createPortal4 } from "react-dom";
1667
1721
  import { Fragment, jsx as jsx38, jsxs as jsxs26 } from "react/jsx-runtime";
1668
1722
  var defaultMenuStyle = {
1669
1723
  background: "var(--color-ink-200)",
@@ -1680,23 +1734,117 @@ function ActionMenu({
1680
1734
  buttonSize = "sm",
1681
1735
  buttonVariant = "secondary",
1682
1736
  buttonClassName = "",
1683
- menuClassName = "absolute bottom-full right-0 z-50 mb-1 w-[min(20rem,calc(100vw-1.5rem))] max-w-[calc(100vw-1.5rem)] overflow-auto sm:min-w-52 sm:w-auto",
1737
+ menuClassName,
1684
1738
  menuStyle,
1685
1739
  fullWidth = false,
1686
1740
  closeLabel
1687
1741
  }) {
1688
1742
  const [open, setOpen] = useState5(false);
1689
- const rootRef = useRef2(null);
1743
+ const [pos, setPos] = useState5(null);
1744
+ const btnRef = useRef2(null);
1745
+ const menuRef = useRef2(null);
1746
+ useLayoutEffect(() => {
1747
+ if (!open || !btnRef.current) return;
1748
+ const updatePosition = () => {
1749
+ const buttonElement = btnRef.current;
1750
+ if (!buttonElement) return;
1751
+ const rect = buttonElement.getBoundingClientRect();
1752
+ const viewportPadding = 8;
1753
+ const menuOffset = 4;
1754
+ const menuW = Math.min(320, window.innerWidth - viewportPadding * 2);
1755
+ const left = Math.max(
1756
+ viewportPadding,
1757
+ Math.min(rect.right - menuW, window.innerWidth - menuW - viewportPadding)
1758
+ );
1759
+ const estimatedMenuHeight = Math.min(320, items.length * 44 + 16);
1760
+ const spaceAbove = Math.max(0, rect.top - viewportPadding - menuOffset);
1761
+ const spaceBelow = Math.max(0, window.innerHeight - rect.bottom - viewportPadding - menuOffset);
1762
+ const preferBelow = spaceBelow >= estimatedMenuHeight || spaceBelow >= spaceAbove;
1763
+ if (preferBelow) {
1764
+ setPos({
1765
+ top: Math.min(rect.bottom + menuOffset, window.innerHeight - viewportPadding),
1766
+ left,
1767
+ width: menuW,
1768
+ maxHeight: Math.max(120, spaceBelow)
1769
+ });
1770
+ return;
1771
+ }
1772
+ setPos({
1773
+ bottom: Math.max(window.innerHeight - rect.top + menuOffset, viewportPadding),
1774
+ left,
1775
+ width: menuW,
1776
+ maxHeight: Math.max(120, spaceAbove)
1777
+ });
1778
+ };
1779
+ updatePosition();
1780
+ window.addEventListener("resize", updatePosition);
1781
+ document.addEventListener("scroll", updatePosition, true);
1782
+ return () => {
1783
+ window.removeEventListener("resize", updatePosition);
1784
+ document.removeEventListener("scroll", updatePosition, true);
1785
+ };
1786
+ }, [items.length, open]);
1690
1787
  useEffect2(() => {
1691
- function handleOutside(event) {
1692
- if (rootRef.current && !rootRef.current.contains(event.target)) {
1693
- setOpen(false);
1788
+ if (!open) return;
1789
+ function handle(e) {
1790
+ if (e instanceof KeyboardEvent) {
1791
+ if (e.key === "Escape") setOpen(false);
1792
+ return;
1694
1793
  }
1794
+ const target = e.target;
1795
+ if (btnRef.current?.contains(target) || menuRef.current?.contains(target)) return;
1796
+ setOpen(false);
1695
1797
  }
1696
- document.addEventListener("mousedown", handleOutside);
1697
- return () => document.removeEventListener("mousedown", handleOutside);
1698
- }, []);
1699
- return /* @__PURE__ */ jsxs26("div", { ref: rootRef, className: "relative", children: [
1798
+ document.addEventListener("mousedown", handle);
1799
+ document.addEventListener("keydown", handle);
1800
+ return () => {
1801
+ document.removeEventListener("mousedown", handle);
1802
+ document.removeEventListener("keydown", handle);
1803
+ };
1804
+ }, [open]);
1805
+ const menuNode = open && pos ? /* @__PURE__ */ jsxs26(Fragment, { children: [
1806
+ /* @__PURE__ */ jsx38("div", { className: "fixed inset-0 z-9998", onClick: () => setOpen(false) }),
1807
+ /* @__PURE__ */ jsx38(
1808
+ "div",
1809
+ {
1810
+ ref: menuRef,
1811
+ className: menuClassName ?? "overflow-auto",
1812
+ style: {
1813
+ position: "fixed",
1814
+ top: pos.top !== void 0 ? pos.top : void 0,
1815
+ bottom: pos.bottom !== void 0 ? pos.bottom : void 0,
1816
+ left: pos.left,
1817
+ width: pos.width,
1818
+ maxWidth: "calc(100vw - 16px)",
1819
+ zIndex: 9999,
1820
+ ...defaultMenuStyle,
1821
+ maxHeight: pos.maxHeight ?? defaultMenuStyle.maxHeight,
1822
+ ...menuStyle
1823
+ },
1824
+ children: items.map((item, index) => /* @__PURE__ */ jsxs26("div", { children: [
1825
+ index > 0 && item.danger ? /* @__PURE__ */ jsx38("div", { style: { borderTop: "1px solid var(--color-border)" } }) : null,
1826
+ /* @__PURE__ */ jsxs26(
1827
+ "button",
1828
+ {
1829
+ type: "button",
1830
+ className: itemBaseClass,
1831
+ style: { color: item.danger ? "var(--color-warn)" : "var(--color-txt-2)" },
1832
+ disabled: item.disabled || item.loading,
1833
+ onClick: () => {
1834
+ setOpen(false);
1835
+ item.onSelect();
1836
+ },
1837
+ children: [
1838
+ item.loading ? /* @__PURE__ */ jsx38(Spinner, { color: "text-[var(--color-txt-3)]" }) : /* @__PURE__ */ jsx38("span", { className: item.danger ? "shrink-0 text-warn" : "shrink-0 text-primary-300", children: item.icon }),
1839
+ /* @__PURE__ */ jsx38("span", { children: item.label })
1840
+ ]
1841
+ }
1842
+ )
1843
+ ] }, item.key))
1844
+ }
1845
+ )
1846
+ ] }) : null;
1847
+ return /* @__PURE__ */ jsxs26("div", { ref: btnRef, className: fullWidth ? "relative w-full" : "relative inline-block max-w-full", children: [
1700
1848
  /* @__PURE__ */ jsx38(
1701
1849
  Button,
1702
1850
  {
@@ -1710,37 +1858,7 @@ function ActionMenu({
1710
1858
  children: label
1711
1859
  }
1712
1860
  ),
1713
- open ? /* @__PURE__ */ jsxs26(Fragment, { children: [
1714
- /* @__PURE__ */ jsx38(
1715
- "button",
1716
- {
1717
- type: "button",
1718
- className: "fixed inset-0 z-40 bg-black/10 backdrop-blur-[3px]",
1719
- "aria-label": closeLabel ?? label,
1720
- onClick: () => setOpen(false)
1721
- }
1722
- ),
1723
- /* @__PURE__ */ jsx38("div", { className: menuClassName, style: { ...defaultMenuStyle, ...menuStyle }, children: items.map((item, index) => /* @__PURE__ */ jsxs26("div", { children: [
1724
- index > 0 && item.danger ? /* @__PURE__ */ jsx38("div", { style: { borderTop: "1px solid var(--color-border)" } }) : null,
1725
- /* @__PURE__ */ jsxs26(
1726
- "button",
1727
- {
1728
- type: "button",
1729
- className: itemBaseClass,
1730
- style: { color: item.danger ? "var(--color-warn)" : "var(--color-txt-2)" },
1731
- disabled: item.disabled || item.loading,
1732
- onClick: () => {
1733
- setOpen(false);
1734
- item.onSelect();
1735
- },
1736
- children: [
1737
- item.loading ? /* @__PURE__ */ jsx38(Spinner, { color: "text-[var(--color-txt-3)]" }) : /* @__PURE__ */ jsx38("span", { className: item.danger ? "shrink-0 text-warn" : "shrink-0 text-primary-300", children: item.icon }),
1738
- /* @__PURE__ */ jsx38("span", { children: item.label })
1739
- ]
1740
- }
1741
- )
1742
- ] }, item.key)) })
1743
- ] }) : null
1861
+ typeof document !== "undefined" ? createPortal4(menuNode, document.body) : null
1744
1862
  ] });
1745
1863
  }
1746
1864