@particle-academy/react-fancy 1.5.1 → 1.7.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 CHANGED
@@ -1323,13 +1323,6 @@ var avatarSize = {
1323
1323
  lg: "w-6 h-6",
1324
1324
  xl: "w-7 h-7"
1325
1325
  };
1326
- var alertIconSize = {
1327
- xs: "w-2 h-2",
1328
- sm: "w-2.5 h-2.5",
1329
- md: "w-3 h-3",
1330
- lg: "w-4 h-4",
1331
- xl: "w-4 h-4"
1332
- };
1333
1326
  var badgeSize = {
1334
1327
  xs: "text-[10px] px-1 min-w-[14px] h-3.5",
1335
1328
  sm: "text-[10px] px-1.5 min-w-[16px] h-4",
@@ -1441,7 +1434,7 @@ var Action = react.forwardRef(
1441
1434
  return /* @__PURE__ */ jsxRuntime.jsx(
1442
1435
  "span",
1443
1436
  {
1444
- className: cn("flex-shrink-0", iconColorCls),
1437
+ className: cn("inline-flex items-center flex-shrink-0", iconColorCls),
1445
1438
  children: /* @__PURE__ */ jsxRuntime.jsx(Icon, { name: iconSlug, size: iconSizeMap[size] })
1446
1439
  },
1447
1440
  `icon-${trailing ? "t" : "l"}`
@@ -1495,17 +1488,8 @@ var Action = react.forwardRef(
1495
1488
  className: "relative inline-flex flex-shrink-0",
1496
1489
  "data-action-alert": true,
1497
1490
  children: [
1498
- /* @__PURE__ */ jsxRuntime.jsx("span", { className: cn(alertIconSize[size], "text-red-500 dark:text-red-400 animate-pulse"), children: alertIconEl }),
1499
- /* @__PURE__ */ jsxRuntime.jsx(
1500
- "span",
1501
- {
1502
- className: cn(
1503
- alertIconSize[size],
1504
- "absolute inset-0 text-red-400 dark:text-red-300 animate-ping opacity-75"
1505
- ),
1506
- children: alertIconEl
1507
- }
1508
- )
1491
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-red-500 dark:text-red-400 animate-pulse", children: alertIconEl }),
1492
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "absolute inset-0 flex items-center justify-center text-red-400 dark:text-red-300 animate-ping opacity-75", children: alertIconEl })
1509
1493
  ]
1510
1494
  },
1511
1495
  "alert-icon"
@@ -1645,7 +1629,7 @@ function dirtyRingClasses(dirty) {
1645
1629
  function errorClasses(error) {
1646
1630
  return error ? "border-red-500 focus:ring-red-500" : "";
1647
1631
  }
1648
- var inputBaseClasses = "border border-zinc-300 bg-white text-zinc-900 placeholder:text-zinc-400 focus:outline-none focus:ring-2 focus:ring-blue-500/40 focus:border-blue-500 disabled:opacity-50 disabled:cursor-not-allowed dark:border-zinc-600 dark:bg-zinc-800 dark:text-zinc-100 dark:placeholder:text-zinc-500";
1632
+ var inputBaseClasses = "border border-zinc-300 bg-white text-zinc-900 placeholder:text-zinc-400 transition-[border-color,box-shadow] duration-150 focus:outline-none focus:ring-2 focus:ring-blue-500/40 focus:border-blue-500 disabled:opacity-50 disabled:cursor-not-allowed dark:border-zinc-700 dark:bg-[#1e1e24] dark:text-zinc-100 dark:placeholder:text-zinc-500 dark:focus:border-blue-400 dark:focus:ring-blue-400/20";
1649
1633
  function resolveOption(option) {
1650
1634
  if (typeof option === "string") {
1651
1635
  return { value: option, label: option };
@@ -1662,13 +1646,13 @@ function Field({
1662
1646
  children,
1663
1647
  className
1664
1648
  }) {
1665
- return /* @__PURE__ */ jsxRuntime.jsxs("div", { "data-react-fancy-field": "", className: cn("flex flex-col gap-1.5", className), children: [
1649
+ return /* @__PURE__ */ jsxRuntime.jsxs("div", { "data-react-fancy-field": "", className: cn("flex flex-col gap-2", className), children: [
1666
1650
  label && /* @__PURE__ */ jsxRuntime.jsxs(
1667
1651
  "label",
1668
1652
  {
1669
1653
  htmlFor,
1670
1654
  className: cn(
1671
- "font-medium text-zinc-700 dark:text-zinc-300",
1655
+ "font-medium text-zinc-700 dark:text-zinc-100",
1672
1656
  labelSizeClasses[size]
1673
1657
  ),
1674
1658
  children: [
@@ -1696,7 +1680,7 @@ var insidePaddingRight = {
1696
1680
  lg: "pr-10",
1697
1681
  xl: "pr-11"
1698
1682
  };
1699
- var affixOutsideClasses = "inline-flex items-center border border-zinc-300 bg-zinc-50 px-3 text-sm text-zinc-500 dark:border-zinc-600 dark:bg-zinc-800 dark:text-zinc-400";
1683
+ var affixOutsideClasses = "inline-flex items-center border border-zinc-300 bg-zinc-50 px-3 text-sm text-zinc-500 dark:border-zinc-700 dark:bg-zinc-800 dark:text-zinc-400";
1700
1684
  function InputWrapper({
1701
1685
  children,
1702
1686
  prefix,
@@ -1940,10 +1924,157 @@ var Textarea = react.forwardRef(
1940
1924
  }
1941
1925
  );
1942
1926
  Textarea.displayName = "Textarea";
1927
+ function Portal({ children, container }) {
1928
+ if (typeof document === "undefined") return null;
1929
+ const target = container ?? document.body;
1930
+ return reactDom.createPortal(
1931
+ /* @__PURE__ */ jsxRuntime.jsx(PortalDarkWrapper, { children }),
1932
+ target
1933
+ );
1934
+ }
1935
+ function PortalDarkWrapper({ children }) {
1936
+ const ref = react.useRef(null);
1937
+ react.useEffect(() => {
1938
+ const root = document.documentElement;
1939
+ const wrapper = ref.current;
1940
+ if (!wrapper) return;
1941
+ const sync = () => {
1942
+ const isDark = root.classList.contains("dark") || root.getAttribute("data-theme") === "dark";
1943
+ wrapper.classList.toggle("dark", isDark);
1944
+ };
1945
+ sync();
1946
+ const observer = new MutationObserver(sync);
1947
+ observer.observe(root, {
1948
+ attributes: true,
1949
+ attributeFilter: ["class", "data-theme"]
1950
+ });
1951
+ return () => observer.disconnect();
1952
+ }, []);
1953
+ return /* @__PURE__ */ jsxRuntime.jsx("div", { ref, "data-react-fancy-portal": "", style: { display: "contents" }, children });
1954
+ }
1955
+ function getPosition(anchor, floating, placement, offset) {
1956
+ let x = 0;
1957
+ let y = 0;
1958
+ const base = placement.split("-")[0];
1959
+ const align = placement.split("-")[1];
1960
+ switch (base) {
1961
+ case "top":
1962
+ x = anchor.left + anchor.width / 2 - floating.width / 2;
1963
+ y = anchor.top - floating.height - offset;
1964
+ break;
1965
+ case "bottom":
1966
+ x = anchor.left + anchor.width / 2 - floating.width / 2;
1967
+ y = anchor.bottom + offset;
1968
+ break;
1969
+ case "left":
1970
+ x = anchor.left - floating.width - offset;
1971
+ y = anchor.top + anchor.height / 2 - floating.height / 2;
1972
+ break;
1973
+ case "right":
1974
+ x = anchor.right + offset;
1975
+ y = anchor.top + anchor.height / 2 - floating.height / 2;
1976
+ break;
1977
+ }
1978
+ if (base === "top" || base === "bottom") {
1979
+ if (align === "start") x = anchor.left;
1980
+ else if (align === "end") x = anchor.right - floating.width;
1981
+ }
1982
+ if (base === "left" || base === "right") {
1983
+ if (align === "start") y = anchor.top;
1984
+ else if (align === "end") y = anchor.bottom - floating.height;
1985
+ }
1986
+ let finalPlacement = placement;
1987
+ const vw = window.innerWidth;
1988
+ const vh = window.innerHeight;
1989
+ if (base === "bottom" && y + floating.height > vh) {
1990
+ y = anchor.top - floating.height - offset;
1991
+ finalPlacement = placement.replace("bottom", "top");
1992
+ } else if (base === "top" && y < 0) {
1993
+ y = anchor.bottom + offset;
1994
+ finalPlacement = placement.replace("top", "bottom");
1995
+ }
1996
+ x = Math.max(4, Math.min(x, vw - floating.width - 4));
1997
+ y = Math.max(4, Math.min(y, vh - floating.height - 4));
1998
+ return { x, y, placement: finalPlacement };
1999
+ }
2000
+ function useFloatingPosition(anchorRef, floatingRef, options = {}) {
2001
+ const { placement = "bottom", offset = 8, enabled = true } = options;
2002
+ const [position, setPosition] = react.useState({
2003
+ x: -9999,
2004
+ y: -9999,
2005
+ placement
2006
+ });
2007
+ const update = react.useCallback(() => {
2008
+ const anchor = anchorRef.current;
2009
+ const floating = floatingRef.current;
2010
+ if (!anchor || !floating) return;
2011
+ const anchorRect = anchor.getBoundingClientRect();
2012
+ const floatingRect = floating.getBoundingClientRect();
2013
+ setPosition(getPosition(anchorRect, floatingRect, placement, offset));
2014
+ }, [anchorRef, floatingRef, placement, offset]);
2015
+ react.useLayoutEffect(() => {
2016
+ if (!enabled) return;
2017
+ update();
2018
+ const raf = requestAnimationFrame(() => {
2019
+ update();
2020
+ });
2021
+ return () => cancelAnimationFrame(raf);
2022
+ }, [update, enabled]);
2023
+ react.useEffect(() => {
2024
+ if (!enabled) return;
2025
+ window.addEventListener("scroll", update, true);
2026
+ window.addEventListener("resize", update);
2027
+ return () => {
2028
+ window.removeEventListener("scroll", update, true);
2029
+ window.removeEventListener("resize", update);
2030
+ };
2031
+ }, [update, enabled]);
2032
+ return position;
2033
+ }
2034
+ function useOutsideClick(ref, handler, enabled = true, ignoreRef) {
2035
+ react.useEffect(() => {
2036
+ if (!enabled) return;
2037
+ const listener = (event) => {
2038
+ const el = ref.current;
2039
+ if (!el || el.contains(event.target)) return;
2040
+ if (ignoreRef?.current?.contains(event.target)) return;
2041
+ handler(event);
2042
+ };
2043
+ document.addEventListener("mousedown", listener);
2044
+ document.addEventListener("touchstart", listener);
2045
+ return () => {
2046
+ document.removeEventListener("mousedown", listener);
2047
+ document.removeEventListener("touchstart", listener);
2048
+ };
2049
+ }, [ref, handler, enabled, ignoreRef]);
2050
+ }
2051
+ function useEscapeKey(handler, enabled = true) {
2052
+ react.useEffect(() => {
2053
+ if (!enabled) return;
2054
+ const listener = (event) => {
2055
+ if (event.key === "Escape") {
2056
+ handler();
2057
+ }
2058
+ };
2059
+ document.addEventListener("keydown", listener);
2060
+ return () => document.removeEventListener("keydown", listener);
2061
+ }, [handler, enabled]);
2062
+ }
1943
2063
  function isOptionGroup(item) {
1944
2064
  return typeof item === "object" && "options" in item;
1945
2065
  }
1946
- function renderOption(option, index) {
2066
+ function flattenOptions(list) {
2067
+ const flat = [];
2068
+ for (const item of list) {
2069
+ if (isOptionGroup(item)) {
2070
+ flat.push(...item.options);
2071
+ } else {
2072
+ flat.push(item);
2073
+ }
2074
+ }
2075
+ return flat;
2076
+ }
2077
+ function renderNativeOption(option, index) {
1947
2078
  const resolved = resolveOption(option);
1948
2079
  return /* @__PURE__ */ jsxRuntime.jsx(
1949
2080
  "option",
@@ -1955,7 +2086,7 @@ function renderOption(option, index) {
1955
2086
  `${resolved.value}-${index}`
1956
2087
  );
1957
2088
  }
1958
- var Select = react.forwardRef(
2089
+ var NativeSelect = react.forwardRef(
1959
2090
  ({
1960
2091
  size = "md",
1961
2092
  dirty,
@@ -2013,8 +2144,8 @@ var Select = react.forwardRef(
2013
2144
  placeholder && /* @__PURE__ */ jsxRuntime.jsx("option", { value: "", disabled: true, children: placeholder }),
2014
2145
  list.map(
2015
2146
  (item, index) => isOptionGroup(item) ? /* @__PURE__ */ jsxRuntime.jsx("optgroup", { label: item.label, children: item.options.map(
2016
- (opt, optIndex) => renderOption(opt, optIndex)
2017
- ) }, `group-${index}`) : renderOption(item, index)
2147
+ (opt, optIndex) => renderNativeOption(opt, optIndex)
2148
+ ) }, `group-${index}`) : renderNativeOption(item, index)
2018
2149
  )
2019
2150
  ]
2020
2151
  }
@@ -2038,6 +2169,270 @@ var Select = react.forwardRef(
2038
2169
  return select;
2039
2170
  }
2040
2171
  );
2172
+ NativeSelect.displayName = "NativeSelect";
2173
+ var ListboxSelect = react.forwardRef(
2174
+ ({
2175
+ size = "md",
2176
+ dirty,
2177
+ error,
2178
+ label,
2179
+ description,
2180
+ required,
2181
+ disabled,
2182
+ className,
2183
+ id,
2184
+ list,
2185
+ placeholder = "Select...",
2186
+ multiple = false,
2187
+ values: controlledValues,
2188
+ defaultValues,
2189
+ onValueChange,
2190
+ onValuesChange,
2191
+ searchable = false,
2192
+ selectedSuffix = "selected",
2193
+ indicator = "check",
2194
+ value: controlledSingleValue,
2195
+ defaultValue: defaultSingleValue
2196
+ }, _ref) => {
2197
+ const autoId = react.useId();
2198
+ const selectId = id ?? autoId;
2199
+ const [open, setOpen] = react.useState(false);
2200
+ const [search2, setSearch] = react.useState("");
2201
+ const [activeIndex, setActiveIndex] = react.useState(-1);
2202
+ const [singleValue, setSingleValue] = react.useState(
2203
+ controlledSingleValue ?? defaultSingleValue ?? ""
2204
+ );
2205
+ const currentSingle = controlledSingleValue ?? singleValue;
2206
+ const [multiValues, setMultiValues] = react.useState(
2207
+ controlledValues ?? defaultValues ?? []
2208
+ );
2209
+ const currentMulti = controlledValues ?? multiValues;
2210
+ react.useEffect(() => {
2211
+ if (controlledValues) setMultiValues(controlledValues);
2212
+ }, [controlledValues]);
2213
+ react.useEffect(() => {
2214
+ if (controlledSingleValue !== void 0) setSingleValue(controlledSingleValue);
2215
+ }, [controlledSingleValue]);
2216
+ const anchorRef = react.useRef(null);
2217
+ const listRef = react.useRef(null);
2218
+ const wrapperRef = react.useRef(null);
2219
+ const searchRef = react.useRef(null);
2220
+ const position = useFloatingPosition(anchorRef, listRef, {
2221
+ placement: "bottom-start",
2222
+ offset: 4,
2223
+ enabled: open
2224
+ });
2225
+ const close = react.useCallback(() => {
2226
+ setOpen(false);
2227
+ setSearch("");
2228
+ setActiveIndex(-1);
2229
+ }, []);
2230
+ useOutsideClick(wrapperRef, close, open);
2231
+ useEscapeKey(close, open);
2232
+ react.useEffect(() => {
2233
+ if (open && searchable) {
2234
+ requestAnimationFrame(() => searchRef.current?.focus());
2235
+ }
2236
+ }, [open, searchable]);
2237
+ const allOptions = flattenOptions(list);
2238
+ const resolvedOptions = allOptions.map(resolveOption);
2239
+ const filtered = search2 ? resolvedOptions.filter(
2240
+ (o) => o.label.toLowerCase().includes(search2.toLowerCase())
2241
+ ) : resolvedOptions;
2242
+ const isSelected = (value) => {
2243
+ if (multiple) return currentMulti.includes(value);
2244
+ return currentSingle === value;
2245
+ };
2246
+ const toggleOption = react.useCallback(
2247
+ (value) => {
2248
+ if (multiple) {
2249
+ const next = currentMulti.includes(value) ? currentMulti.filter((v) => v !== value) : [...currentMulti, value];
2250
+ setMultiValues(next);
2251
+ onValuesChange?.(next);
2252
+ } else {
2253
+ setSingleValue(value);
2254
+ onValueChange?.(value);
2255
+ close();
2256
+ }
2257
+ },
2258
+ [multiple, currentMulti, onValuesChange, onValueChange, close]
2259
+ );
2260
+ const getDisplayText = () => {
2261
+ if (multiple) {
2262
+ if (currentMulti.length === 0) return placeholder;
2263
+ if (currentMulti.length === 1) {
2264
+ const opt2 = resolvedOptions.find((o) => o.value === currentMulti[0]);
2265
+ return opt2?.label ?? currentMulti[0];
2266
+ }
2267
+ return `${currentMulti.length} ${selectedSuffix}`;
2268
+ }
2269
+ if (!currentSingle) return placeholder;
2270
+ const opt = resolvedOptions.find((o) => o.value === currentSingle);
2271
+ return opt?.label ?? currentSingle;
2272
+ };
2273
+ const hasValue = multiple ? currentMulti.length > 0 : !!currentSingle;
2274
+ const handleKeyDown = (e) => {
2275
+ if (!open) {
2276
+ if (e.key === "ArrowDown" || e.key === "Enter" || e.key === " ") {
2277
+ e.preventDefault();
2278
+ setOpen(true);
2279
+ }
2280
+ return;
2281
+ }
2282
+ if (e.key === "ArrowDown") {
2283
+ e.preventDefault();
2284
+ setActiveIndex((i) => Math.min(i + 1, filtered.length - 1));
2285
+ } else if (e.key === "ArrowUp") {
2286
+ e.preventDefault();
2287
+ setActiveIndex((i) => Math.max(i - 1, 0));
2288
+ } else if (e.key === "Enter" && activeIndex >= 0) {
2289
+ e.preventDefault();
2290
+ const item = filtered[activeIndex];
2291
+ if (item && !item.disabled) toggleOption(item.value);
2292
+ }
2293
+ };
2294
+ const trigger = /* @__PURE__ */ jsxRuntime.jsxs(
2295
+ "button",
2296
+ {
2297
+ ref: anchorRef,
2298
+ type: "button",
2299
+ id: selectId,
2300
+ disabled,
2301
+ onClick: () => setOpen((o) => !o),
2302
+ onKeyDown: handleKeyDown,
2303
+ role: "combobox",
2304
+ "aria-expanded": open,
2305
+ "aria-haspopup": "listbox",
2306
+ "data-react-fancy-select": "",
2307
+ "data-variant": "listbox",
2308
+ className: cn(
2309
+ inputBaseClasses,
2310
+ inputSizeClasses[size],
2311
+ dirtyClasses(dirty),
2312
+ errorClasses(error),
2313
+ "flex w-full cursor-pointer items-center justify-between gap-2 text-left",
2314
+ !hasValue && "text-zinc-400 dark:text-zinc-500",
2315
+ className
2316
+ ),
2317
+ children: [
2318
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "truncate", children: getDisplayText() }),
2319
+ /* @__PURE__ */ jsxRuntime.jsx(
2320
+ "svg",
2321
+ {
2322
+ className: cn("h-4 w-4 shrink-0 text-zinc-400 transition-transform", open && "rotate-180"),
2323
+ viewBox: "0 0 20 20",
2324
+ fill: "currentColor",
2325
+ children: /* @__PURE__ */ jsxRuntime.jsx(
2326
+ "path",
2327
+ {
2328
+ fillRule: "evenodd",
2329
+ d: "M5.23 7.21a.75.75 0 011.06.02L10 11.168l3.71-3.938a.75.75 0 111.08 1.04l-4.25 4.5a.75.75 0 01-1.08 0l-4.25-4.5a.75.75 0 01.02-1.06z",
2330
+ clipRule: "evenodd"
2331
+ }
2332
+ )
2333
+ }
2334
+ )
2335
+ ]
2336
+ }
2337
+ );
2338
+ const dropdown = open && /* @__PURE__ */ jsxRuntime.jsx(Portal, { children: /* @__PURE__ */ jsxRuntime.jsxs(
2339
+ "div",
2340
+ {
2341
+ ref: listRef,
2342
+ role: "listbox",
2343
+ "aria-multiselectable": multiple || void 0,
2344
+ className: "fixed z-50 max-h-60 min-w-[8rem] overflow-y-auto rounded-xl border border-zinc-200 bg-white p-1 shadow-lg dark:border-zinc-700 dark:bg-zinc-900 dark:shadow-zinc-950/50 fancy-scale-in",
2345
+ style: {
2346
+ left: position.x,
2347
+ top: position.y,
2348
+ width: anchorRef.current?.offsetWidth
2349
+ },
2350
+ children: [
2351
+ searchable && /* @__PURE__ */ jsxRuntime.jsx("div", { className: "px-2 pb-1", children: /* @__PURE__ */ jsxRuntime.jsx(
2352
+ "input",
2353
+ {
2354
+ ref: searchRef,
2355
+ type: "text",
2356
+ value: search2,
2357
+ onChange: (e) => {
2358
+ setSearch(e.target.value);
2359
+ setActiveIndex(-1);
2360
+ },
2361
+ onKeyDown: handleKeyDown,
2362
+ placeholder: "Search...",
2363
+ className: "w-full rounded-md border-0 bg-zinc-100 px-2.5 py-1.5 text-sm text-zinc-900 placeholder:text-zinc-400 outline-none dark:bg-zinc-800 dark:text-zinc-100 dark:placeholder:text-zinc-500"
2364
+ }
2365
+ ) }),
2366
+ filtered.length === 0 ? /* @__PURE__ */ jsxRuntime.jsx("div", { className: "px-3 py-2 text-sm text-zinc-400", children: "No results found" }) : filtered.map((option, i) => {
2367
+ const selected = isSelected(option.value);
2368
+ return /* @__PURE__ */ jsxRuntime.jsxs(
2369
+ "button",
2370
+ {
2371
+ type: "button",
2372
+ role: "option",
2373
+ "aria-selected": selected,
2374
+ disabled: option.disabled,
2375
+ onClick: () => toggleOption(option.value),
2376
+ className: cn(
2377
+ "flex w-full items-center gap-2 rounded-lg px-3 py-2 text-left text-sm transition-colors",
2378
+ i === activeIndex ? "bg-zinc-100 dark:bg-zinc-800" : "hover:bg-zinc-50 dark:hover:bg-zinc-800/50",
2379
+ selected ? "text-zinc-900 dark:text-zinc-100" : "text-zinc-700 dark:text-zinc-300",
2380
+ option.disabled && "cursor-not-allowed opacity-50"
2381
+ ),
2382
+ children: [
2383
+ indicator === "checkbox" ? /* @__PURE__ */ jsxRuntime.jsx(
2384
+ "span",
2385
+ {
2386
+ className: cn(
2387
+ "flex h-4 w-4 shrink-0 items-center justify-center rounded border transition-colors",
2388
+ selected ? "border-blue-500 bg-blue-500 text-white dark:border-blue-400 dark:bg-blue-400" : "border-zinc-300 dark:border-zinc-600"
2389
+ ),
2390
+ children: selected && /* @__PURE__ */ jsxRuntime.jsx("svg", { className: "h-3 w-3", viewBox: "0 0 12 12", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", children: /* @__PURE__ */ jsxRuntime.jsx("polyline", { points: "2.5 6 5 8.5 9.5 3.5" }) })
2391
+ }
2392
+ ) : /* @__PURE__ */ jsxRuntime.jsx("span", { className: "flex h-4 w-4 shrink-0 items-center justify-center", children: selected && /* @__PURE__ */ jsxRuntime.jsx("svg", { className: "h-4 w-4 text-blue-500 dark:text-blue-400", viewBox: "0 0 20 20", fill: "currentColor", children: /* @__PURE__ */ jsxRuntime.jsx("path", { fillRule: "evenodd", d: "M16.704 4.153a.75.75 0 01.143 1.052l-8 10.5a.75.75 0 01-1.127.075l-4.5-4.5a.75.75 0 011.06-1.06l3.894 3.893 7.48-9.817a.75.75 0 011.05-.143z", clipRule: "evenodd" }) }) }),
2393
+ /* @__PURE__ */ jsxRuntime.jsxs("span", { className: "min-w-0 flex-1", children: [
2394
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "block truncate", children: option.label }),
2395
+ option.description && /* @__PURE__ */ jsxRuntime.jsx("span", { className: "block truncate text-xs text-zinc-400 dark:text-zinc-500", children: option.description })
2396
+ ] })
2397
+ ]
2398
+ },
2399
+ option.value
2400
+ );
2401
+ })
2402
+ ]
2403
+ }
2404
+ ) });
2405
+ const content = /* @__PURE__ */ jsxRuntime.jsxs("div", { ref: wrapperRef, className: "relative", children: [
2406
+ trigger,
2407
+ dropdown
2408
+ ] });
2409
+ if (label || error || description) {
2410
+ return /* @__PURE__ */ jsxRuntime.jsx(
2411
+ Field,
2412
+ {
2413
+ label,
2414
+ description,
2415
+ error,
2416
+ required,
2417
+ htmlFor: selectId,
2418
+ size,
2419
+ children: content
2420
+ }
2421
+ );
2422
+ }
2423
+ return content;
2424
+ }
2425
+ );
2426
+ ListboxSelect.displayName = "ListboxSelect";
2427
+ var Select = react.forwardRef(
2428
+ (props, ref) => {
2429
+ const variant = props.variant ?? (props.multiple ? "listbox" : "native");
2430
+ if (variant === "listbox") {
2431
+ return /* @__PURE__ */ jsxRuntime.jsx(ListboxSelect, { ...props, ref });
2432
+ }
2433
+ return /* @__PURE__ */ jsxRuntime.jsx(NativeSelect, { ...props, ref });
2434
+ }
2435
+ );
2041
2436
  Select.displayName = "Select";
2042
2437
  function useControllableState(controlledValue, defaultValue, onChange) {
2043
2438
  const [uncontrolledValue, setUncontrolledValue] = react.useState(defaultValue);
@@ -2116,7 +2511,7 @@ var Checkbox = react.forwardRef(
2116
2511
  onChange: (e) => setChecked(e.target.checked),
2117
2512
  className: cn(
2118
2513
  sizeClasses6,
2119
- "cursor-pointer rounded border border-zinc-300 bg-white text-blue-600 focus:ring-2 focus:ring-blue-500/40 focus:ring-offset-0 disabled:cursor-not-allowed disabled:opacity-50 dark:border-zinc-600 dark:bg-zinc-800",
2514
+ "cursor-pointer rounded border border-zinc-300 bg-white text-blue-600 transition-[border-color,box-shadow] duration-150 focus:ring-2 focus:ring-blue-500/40 focus:ring-offset-0 disabled:cursor-not-allowed disabled:opacity-50 dark:border-zinc-700 dark:bg-[#1e1e24]",
2120
2515
  dirtyRingClasses(dirty),
2121
2516
  error && "border-red-500"
2122
2517
  )
@@ -2128,7 +2523,7 @@ var Checkbox = react.forwardRef(
2128
2523
  {
2129
2524
  htmlFor: checkboxId,
2130
2525
  className: cn(
2131
- "cursor-pointer text-sm text-zinc-700 dark:text-zinc-300",
2526
+ "cursor-pointer text-sm text-zinc-700 dark:text-zinc-100",
2132
2527
  disabled && "cursor-not-allowed opacity-50"
2133
2528
  ),
2134
2529
  children: [
@@ -2203,7 +2598,7 @@ function CheckboxGroup({
2203
2598
  onChange: () => handleToggle(resolved.value),
2204
2599
  className: cn(
2205
2600
  sizeClasses6,
2206
- "cursor-pointer rounded border border-zinc-300 bg-white text-blue-600 focus:ring-2 focus:ring-blue-500/40 focus:ring-offset-0 disabled:cursor-not-allowed disabled:opacity-50 dark:border-zinc-600 dark:bg-zinc-800",
2601
+ "cursor-pointer rounded border border-zinc-300 bg-white text-blue-600 transition-[border-color,box-shadow] duration-150 focus:ring-2 focus:ring-blue-500/40 focus:ring-offset-0 disabled:cursor-not-allowed disabled:opacity-50 dark:border-zinc-700 dark:bg-[#1e1e24]",
2207
2602
  dirtyRingClasses(dirty),
2208
2603
  error && "border-red-500"
2209
2604
  )
@@ -2215,7 +2610,7 @@ function CheckboxGroup({
2215
2610
  {
2216
2611
  htmlFor: optionId,
2217
2612
  className: cn(
2218
- "cursor-pointer text-sm text-zinc-700 dark:text-zinc-300",
2613
+ "cursor-pointer text-sm text-zinc-700 dark:text-zinc-100",
2219
2614
  (disabled || resolved.disabled) && "cursor-not-allowed opacity-50"
2220
2615
  ),
2221
2616
  children: resolved.label
@@ -2300,7 +2695,7 @@ function RadioGroup({
2300
2695
  onChange: () => setValue(resolved.value),
2301
2696
  className: cn(
2302
2697
  sizeClasses6,
2303
- "cursor-pointer border border-zinc-300 bg-white text-blue-600 focus:ring-2 focus:ring-blue-500/40 focus:ring-offset-0 disabled:cursor-not-allowed disabled:opacity-50 dark:border-zinc-600 dark:bg-zinc-800",
2698
+ "cursor-pointer border border-zinc-300 bg-white text-blue-600 transition-[border-color,box-shadow] duration-150 focus:ring-2 focus:ring-blue-500/40 focus:ring-offset-0 disabled:cursor-not-allowed disabled:opacity-50 dark:border-zinc-700 dark:bg-[#1e1e24]",
2304
2699
  dirtyRingClasses(dirty),
2305
2700
  error && "border-red-500"
2306
2701
  )
@@ -2312,7 +2707,7 @@ function RadioGroup({
2312
2707
  {
2313
2708
  htmlFor: optionId,
2314
2709
  className: cn(
2315
- "cursor-pointer text-sm text-zinc-700 dark:text-zinc-300",
2710
+ "cursor-pointer text-sm text-zinc-700 dark:text-zinc-100",
2316
2711
  (disabled || resolved.disabled) && "cursor-not-allowed opacity-50"
2317
2712
  ),
2318
2713
  children: resolved.label
@@ -2419,7 +2814,7 @@ var Switch = react.forwardRef(
2419
2814
  className: cn(
2420
2815
  "relative inline-flex shrink-0 cursor-pointer rounded-full border-2 border-transparent transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-blue-500/40 focus-visible:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50",
2421
2816
  trackSizes,
2422
- checked ? trackColorMap[color] : "bg-zinc-200 dark:bg-zinc-700",
2817
+ checked ? trackColorMap[color] : "bg-zinc-200 dark:bg-zinc-600",
2423
2818
  dirtyRingClasses(dirty),
2424
2819
  error && "ring-2 ring-red-500/50"
2425
2820
  ),
@@ -2442,7 +2837,7 @@ var Switch = react.forwardRef(
2442
2837
  {
2443
2838
  htmlFor: switchId,
2444
2839
  className: cn(
2445
- "cursor-pointer text-sm text-zinc-700 dark:text-zinc-300",
2840
+ "cursor-pointer text-sm text-zinc-700 dark:text-zinc-100",
2446
2841
  disabled && "cursor-not-allowed opacity-50"
2447
2842
  ),
2448
2843
  children: [
@@ -2744,7 +3139,7 @@ function MultiSwitch({
2744
3139
  role: "radiogroup",
2745
3140
  id,
2746
3141
  className: cn(
2747
- "relative inline-flex rounded-lg border border-zinc-300 bg-zinc-100 dark:border-zinc-600 dark:bg-zinc-800",
3142
+ "relative inline-flex rounded-lg border border-zinc-300 bg-zinc-100 dark:border-zinc-700 dark:bg-zinc-800",
2748
3143
  dirty && "ring-2 ring-amber-400/50",
2749
3144
  error && "ring-2 ring-red-500/50",
2750
3145
  disabled && "opacity-50 cursor-not-allowed",
@@ -3438,7 +3833,7 @@ function EmojiSelect({
3438
3833
  "button",
3439
3834
  {
3440
3835
  type: "button",
3441
- className: "flex items-center gap-2 rounded-lg border border-zinc-300 px-3 py-2 text-sm dark:border-zinc-600",
3836
+ className: "flex items-center gap-2 rounded-lg border border-zinc-300 px-3 py-2 text-sm transition-[border-color,box-shadow] duration-150 dark:border-zinc-700 dark:bg-[#1e1e24]",
3442
3837
  onClick: () => setOpen(!open),
3443
3838
  children: selected ? /* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-xl", children: selected }) : /* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-zinc-400", children: "Pick emoji" })
3444
3839
  }
@@ -3451,7 +3846,7 @@ function EmojiSelect({
3451
3846
  value: query,
3452
3847
  onChange: (e) => setQuery(e.target.value),
3453
3848
  placeholder,
3454
- className: "mb-2 w-full rounded-md border border-zinc-200 px-2 py-1 text-sm dark:border-zinc-600 dark:bg-zinc-700",
3849
+ className: "mb-2 w-full rounded-md border border-zinc-200 px-2 py-1 text-sm transition-[border-color,box-shadow] duration-150 dark:border-zinc-700 dark:bg-[#1e1e24] dark:text-zinc-100",
3455
3850
  autoFocus: true
3456
3851
  }
3457
3852
  ),
@@ -3706,34 +4101,6 @@ var Table = Object.assign(TableRoot, {
3706
4101
  Tray: TableTray,
3707
4102
  RowTray: TableRowTray
3708
4103
  });
3709
- function Portal({ children, container }) {
3710
- if (typeof document === "undefined") return null;
3711
- const target = container ?? document.body;
3712
- return reactDom.createPortal(
3713
- /* @__PURE__ */ jsxRuntime.jsx(PortalDarkWrapper, { children }),
3714
- target
3715
- );
3716
- }
3717
- function PortalDarkWrapper({ children }) {
3718
- const ref = react.useRef(null);
3719
- react.useEffect(() => {
3720
- const root = document.documentElement;
3721
- const wrapper = ref.current;
3722
- if (!wrapper) return;
3723
- const sync = () => {
3724
- const isDark = root.classList.contains("dark") || root.getAttribute("data-theme") === "dark";
3725
- wrapper.classList.toggle("dark", isDark);
3726
- };
3727
- sync();
3728
- const observer = new MutationObserver(sync);
3729
- observer.observe(root, {
3730
- attributes: true,
3731
- attributeFilter: ["class", "data-theme"]
3732
- });
3733
- return () => observer.disconnect();
3734
- }, []);
3735
- return /* @__PURE__ */ jsxRuntime.jsx("div", { ref, "data-react-fancy-portal": "", style: { display: "contents" }, children });
3736
- }
3737
4104
  var sizeClasses3 = {
3738
4105
  xs: "text-xs",
3739
4106
  sm: "text-sm",
@@ -4511,42 +4878,66 @@ var Callout = react.forwardRef(
4511
4878
  );
4512
4879
  Callout.displayName = "Callout";
4513
4880
  var TimelineContext = react.createContext({
4514
- orientation: "vertical",
4515
- index: 0
4881
+ variant: "stacked",
4882
+ index: 0,
4883
+ total: 0,
4884
+ animated: true
4516
4885
  });
4517
4886
  function useTimeline() {
4518
4887
  return react.useContext(TimelineContext);
4519
4888
  }
4520
4889
  var dotColorClasses2 = {
4521
- blue: "bg-blue-500 dark:bg-blue-400",
4522
- green: "bg-green-500 dark:bg-green-400",
4523
- amber: "bg-amber-500 dark:bg-amber-400",
4524
- red: "bg-red-500 dark:bg-red-400",
4525
- zinc: "bg-zinc-400 dark:bg-zinc-500"
4526
- };
4527
- var iconColorClasses2 = {
4528
- blue: "text-blue-500 dark:text-blue-400",
4529
- green: "text-green-500 dark:text-green-400",
4530
- amber: "text-amber-500 dark:text-amber-400",
4531
- red: "text-red-500 dark:text-red-400",
4532
- zinc: "text-zinc-500 dark:text-zinc-400"
4890
+ red: "bg-red-500",
4891
+ orange: "bg-orange-500",
4892
+ amber: "bg-amber-500",
4893
+ yellow: "bg-yellow-500",
4894
+ lime: "bg-lime-500",
4895
+ green: "bg-green-500",
4896
+ emerald: "bg-emerald-500",
4897
+ teal: "bg-teal-500",
4898
+ cyan: "bg-cyan-500",
4899
+ sky: "bg-sky-500",
4900
+ blue: "bg-blue-500",
4901
+ indigo: "bg-indigo-500",
4902
+ violet: "bg-violet-500",
4903
+ purple: "bg-purple-500",
4904
+ fuchsia: "bg-fuchsia-500",
4905
+ pink: "bg-pink-500",
4906
+ rose: "bg-rose-500",
4907
+ zinc: "bg-zinc-300 dark:bg-zinc-600"
4533
4908
  };
4534
4909
  var ringColorClasses = {
4535
- blue: "ring-blue-500/30 dark:ring-blue-400/30",
4536
- green: "ring-green-500/30 dark:ring-green-400/30",
4537
- amber: "ring-amber-500/30 dark:ring-amber-400/30",
4538
- red: "ring-red-500/30 dark:ring-red-400/30",
4910
+ red: "ring-red-500/30",
4911
+ orange: "ring-orange-500/30",
4912
+ amber: "ring-amber-500/30",
4913
+ yellow: "ring-yellow-500/30",
4914
+ lime: "ring-lime-500/30",
4915
+ green: "ring-green-500/30",
4916
+ emerald: "ring-emerald-500/30",
4917
+ teal: "ring-teal-500/30",
4918
+ cyan: "ring-cyan-500/30",
4919
+ sky: "ring-sky-500/30",
4920
+ blue: "ring-blue-500/30",
4921
+ indigo: "ring-indigo-500/30",
4922
+ violet: "ring-violet-500/30",
4923
+ purple: "ring-purple-500/30",
4924
+ fuchsia: "ring-fuchsia-500/30",
4925
+ pink: "ring-pink-500/30",
4926
+ rose: "ring-rose-500/30",
4539
4927
  zinc: "ring-zinc-400/30 dark:ring-zinc-500/30"
4540
4928
  };
4541
- function Dot({ icon, color, active }) {
4929
+ function Dot({ icon, emoji, color, active }) {
4542
4930
  const c = color ?? "zinc";
4931
+ if (emoji) {
4932
+ return /* @__PURE__ */ jsxRuntime.jsx("span", { className: "flex h-8 w-8 shrink-0 items-center justify-center rounded-full border border-zinc-200 bg-zinc-100 dark:border-zinc-700 dark:bg-zinc-800", children: /* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-sm", children: emoji }) });
4933
+ }
4543
4934
  if (icon) {
4544
4935
  return /* @__PURE__ */ jsxRuntime.jsx(
4545
4936
  "span",
4546
4937
  {
4547
4938
  className: cn(
4548
- "flex h-6 w-6 items-center justify-center rounded-full bg-white dark:bg-zinc-900",
4549
- iconColorClasses2[c],
4939
+ "flex h-8 w-8 shrink-0 items-center justify-center rounded-full text-white",
4940
+ dotColorClasses2[c],
4550
4941
  active && "ring-4",
4551
4942
  active && ringColorClasses[c]
4552
4943
  ),
@@ -4558,7 +4949,7 @@ function Dot({ icon, color, active }) {
4558
4949
  "span",
4559
4950
  {
4560
4951
  className: cn(
4561
- "h-3 w-3 rounded-full",
4952
+ "h-3 w-3 shrink-0 rounded-full",
4562
4953
  dotColorClasses2[c],
4563
4954
  active && "ring-4",
4564
4955
  active && ringColorClasses[c]
@@ -4566,32 +4957,83 @@ function Dot({ icon, color, active }) {
4566
4957
  }
4567
4958
  );
4568
4959
  }
4960
+ function useIntersectionReveal(animated) {
4961
+ const ref = react.useRef(null);
4962
+ const [visible, setVisible] = react.useState(!animated);
4963
+ react.useEffect(() => {
4964
+ if (!animated || !ref.current) return;
4965
+ const el = ref.current;
4966
+ const observer = new IntersectionObserver(
4967
+ ([entry]) => {
4968
+ if (entry.isIntersecting) {
4969
+ setVisible(true);
4970
+ observer.disconnect();
4971
+ }
4972
+ },
4973
+ { threshold: 0.2 }
4974
+ );
4975
+ observer.observe(el);
4976
+ return () => observer.disconnect();
4977
+ }, [animated]);
4978
+ return { ref, visible };
4979
+ }
4569
4980
  var TimelineItem = react.forwardRef(
4570
- ({
4571
- children,
4572
- icon,
4573
- color = "zinc",
4574
- active = false,
4575
- className
4576
- }, ref) => {
4577
- const { orientation, index } = useTimeline();
4578
- if (orientation === "horizontal") {
4579
- const isTop = index % 2 === 0;
4981
+ ({ children, icon, emoji, date, color = "zinc", active = false, className }, _ref) => {
4982
+ const { variant, index, total, animated } = useTimeline();
4983
+ const { ref, visible } = useIntersectionReveal(animated);
4984
+ const isLast = index === total - 1;
4985
+ const isLargeDot = !!icon || !!emoji;
4986
+ const isEven = index % 2 === 0;
4987
+ if (variant === "horizontal") {
4580
4988
  return /* @__PURE__ */ jsxRuntime.jsxs(
4581
4989
  "div",
4582
4990
  {
4583
4991
  ref,
4584
4992
  "data-react-fancy-timeline-item": "",
4585
- className: cn("relative flex flex-col items-center", className),
4586
- style: { minWidth: 120 },
4993
+ className: cn(
4994
+ "flex flex-col items-center",
4995
+ !isLast && "min-w-40",
4996
+ animated && "transition duration-500 ease-out",
4997
+ animated && (visible ? "translate-y-0 opacity-100" : "translate-y-4 opacity-0"),
4998
+ className
4999
+ ),
4587
5000
  children: [
4588
- /* @__PURE__ */ jsxRuntime.jsx("div", { className: cn("flex min-h-[4rem] items-end pb-2", !isTop && "invisible"), children: isTop && /* @__PURE__ */ jsxRuntime.jsx("div", { className: "min-w-0 text-center", children }) }),
4589
- /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "relative flex w-full items-center justify-center", children: [
4590
- /* @__PURE__ */ jsxRuntime.jsx("div", { className: "h-px flex-1 bg-zinc-200 dark:bg-zinc-700" }),
4591
- /* @__PURE__ */ jsxRuntime.jsx("div", { className: "relative z-10 flex shrink-0 items-center justify-center", children: /* @__PURE__ */ jsxRuntime.jsx(Dot, { icon, color, active }) }),
4592
- /* @__PURE__ */ jsxRuntime.jsx("div", { className: "h-px flex-1 bg-zinc-200 dark:bg-zinc-700" })
5001
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex h-8 w-full items-center", children: [
5002
+ index > 0 ? /* @__PURE__ */ jsxRuntime.jsx("div", { className: "h-px flex-1 bg-zinc-200 dark:bg-zinc-700" }) : /* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex-1" }),
5003
+ /* @__PURE__ */ jsxRuntime.jsx(Dot, { icon, emoji, color, active }),
5004
+ !isLast ? /* @__PURE__ */ jsxRuntime.jsx("div", { className: "h-px flex-1 bg-zinc-200 dark:bg-zinc-700" }) : /* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex-1" })
4593
5005
  ] }),
4594
- /* @__PURE__ */ jsxRuntime.jsx("div", { className: cn("flex min-h-[4rem] items-start pt-2", isTop && "invisible"), children: !isTop && /* @__PURE__ */ jsxRuntime.jsx("div", { className: "min-w-0 text-center", children }) })
5006
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "mt-3 max-w-40 px-2 text-center", children: [
5007
+ date && /* @__PURE__ */ jsxRuntime.jsx("time", { className: "text-xs font-medium uppercase tracking-wide text-zinc-500 dark:text-zinc-400", children: date }),
5008
+ children
5009
+ ] })
5010
+ ]
5011
+ }
5012
+ );
5013
+ }
5014
+ if (variant === "alternating") {
5015
+ return /* @__PURE__ */ jsxRuntime.jsxs(
5016
+ "div",
5017
+ {
5018
+ ref,
5019
+ "data-react-fancy-timeline-item": "",
5020
+ className: cn(
5021
+ "relative flex gap-x-4 md:grid md:grid-cols-[1fr_1.5rem_1fr] md:gap-x-6",
5022
+ animated && "transition duration-500 ease-out",
5023
+ animated && (visible ? "translate-y-0 opacity-100" : "translate-y-4 opacity-0"),
5024
+ className
5025
+ ),
5026
+ children: [
5027
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "relative z-10 flex w-8 shrink-0 justify-center md:col-start-2 md:row-start-1 md:w-auto md:justify-center", children: !isLargeDot ? /* @__PURE__ */ jsxRuntime.jsx("div", { className: "mt-1.5", children: /* @__PURE__ */ jsxRuntime.jsx(Dot, { icon, emoji, color, active }) }) : /* @__PURE__ */ jsxRuntime.jsx(Dot, { icon, emoji, color, active }) }),
5028
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: cn(
5029
+ "min-w-0 flex-1",
5030
+ !isLast && "pb-8",
5031
+ isLargeDot && "pt-1",
5032
+ isEven ? "md:col-start-1 md:row-start-1 md:text-right" : "md:col-start-3"
5033
+ ), children: [
5034
+ date && /* @__PURE__ */ jsxRuntime.jsx("time", { className: "text-xs font-medium uppercase tracking-wide text-zinc-500 dark:text-zinc-400", children: date }),
5035
+ children
5036
+ ] })
4595
5037
  ]
4596
5038
  }
4597
5039
  );
@@ -4601,11 +5043,18 @@ var TimelineItem = react.forwardRef(
4601
5043
  {
4602
5044
  ref,
4603
5045
  "data-react-fancy-timeline-item": "",
4604
- className: cn("relative flex gap-4 pb-8 last:pb-0", className),
5046
+ className: cn(
5047
+ "relative flex gap-x-4",
5048
+ animated && "transition duration-500 ease-out",
5049
+ animated && (visible ? "translate-y-0 opacity-100" : "translate-y-4 opacity-0"),
5050
+ className
5051
+ ),
4605
5052
  children: [
4606
- /* @__PURE__ */ jsxRuntime.jsx("div", { className: "absolute left-[11px] top-6 bottom-0 w-px bg-zinc-200 last:hidden dark:bg-zinc-700" }),
4607
- /* @__PURE__ */ jsxRuntime.jsx("div", { className: "relative z-10 flex shrink-0 mt-1.5", children: /* @__PURE__ */ jsxRuntime.jsx(Dot, { icon, color, active }) }),
4608
- /* @__PURE__ */ jsxRuntime.jsx("div", { className: "min-w-0 flex-1 pt-0.5", children })
5053
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "relative z-10 flex w-8 shrink-0 justify-center", children: !isLargeDot ? /* @__PURE__ */ jsxRuntime.jsx("div", { className: "mt-1.5", children: /* @__PURE__ */ jsxRuntime.jsx(Dot, { icon, emoji, color, active }) }) : /* @__PURE__ */ jsxRuntime.jsx(Dot, { icon, emoji, color, active }) }),
5054
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: cn("min-w-0 flex-1", !isLast && "pb-8", isLargeDot && "pt-1"), children: [
5055
+ date && /* @__PURE__ */ jsxRuntime.jsx("time", { className: "text-xs font-medium uppercase tracking-wide text-zinc-500 dark:text-zinc-400", children: date }),
5056
+ children
5057
+ ] })
4609
5058
  ]
4610
5059
  }
4611
5060
  );
@@ -4613,8 +5062,8 @@ var TimelineItem = react.forwardRef(
4613
5062
  );
4614
5063
  TimelineItem.displayName = "TimelineItem";
4615
5064
  var TimelineBlock = react.forwardRef(
4616
- ({ heading, children, icon, color = "zinc", active = false, className }, ref) => {
4617
- return /* @__PURE__ */ jsxRuntime.jsx(TimelineItem, { icon, color, active, children: /* @__PURE__ */ jsxRuntime.jsxs(
5065
+ ({ heading, children, icon, emoji, color = "zinc", active = false, className }, ref) => {
5066
+ return /* @__PURE__ */ jsxRuntime.jsx(TimelineItem, { icon, emoji, color, active, children: /* @__PURE__ */ jsxRuntime.jsxs(
4618
5067
  "div",
4619
5068
  {
4620
5069
  ref,
@@ -4624,117 +5073,75 @@ var TimelineBlock = react.forwardRef(
4624
5073
  active && "ring-2 ring-blue-500/20 dark:ring-blue-400/20",
4625
5074
  className
4626
5075
  ),
4627
- children: [
4628
- heading && /* @__PURE__ */ jsxRuntime.jsx("div", { className: "mb-2 text-sm font-semibold text-zinc-900 dark:text-zinc-100", children: heading }),
4629
- /* @__PURE__ */ jsxRuntime.jsx("div", { className: "text-sm text-zinc-600 dark:text-zinc-400", children })
4630
- ]
4631
- }
4632
- ) });
4633
- }
4634
- );
4635
- TimelineBlock.displayName = "TimelineBlock";
4636
- var TimelineRoot = react.forwardRef(
4637
- ({ children, orientation = "vertical", className }, ref) => {
4638
- const items = react.Children.toArray(children);
4639
- return /* @__PURE__ */ jsxRuntime.jsx(
4640
- "div",
4641
- {
4642
- ref,
4643
- "data-react-fancy-timeline": "",
4644
- "data-orientation": orientation,
4645
- className: cn(
4646
- orientation === "vertical" ? "flex flex-col" : "flex flex-row items-center overflow-x-auto",
4647
- className
4648
- ),
4649
- children: items.map((child, i) => /* @__PURE__ */ jsxRuntime.jsx(TimelineContext.Provider, { value: { orientation, index: i }, children: child }, i))
4650
- }
4651
- );
4652
- }
4653
- );
4654
- TimelineRoot.displayName = "Timeline";
4655
- var Timeline = Object.assign(TimelineRoot, {
4656
- Item: TimelineItem,
4657
- Block: TimelineBlock
4658
- });
4659
- function getPosition(anchor, floating, placement, offset) {
4660
- let x = 0;
4661
- let y = 0;
4662
- const base = placement.split("-")[0];
4663
- const align = placement.split("-")[1];
4664
- switch (base) {
4665
- case "top":
4666
- x = anchor.left + anchor.width / 2 - floating.width / 2;
4667
- y = anchor.top - floating.height - offset;
4668
- break;
4669
- case "bottom":
4670
- x = anchor.left + anchor.width / 2 - floating.width / 2;
4671
- y = anchor.bottom + offset;
4672
- break;
4673
- case "left":
4674
- x = anchor.left - floating.width - offset;
4675
- y = anchor.top + anchor.height / 2 - floating.height / 2;
4676
- break;
4677
- case "right":
4678
- x = anchor.right + offset;
4679
- y = anchor.top + anchor.height / 2 - floating.height / 2;
4680
- break;
4681
- }
4682
- if (base === "top" || base === "bottom") {
4683
- if (align === "start") x = anchor.left;
4684
- else if (align === "end") x = anchor.right - floating.width;
4685
- }
4686
- if (base === "left" || base === "right") {
4687
- if (align === "start") y = anchor.top;
4688
- else if (align === "end") y = anchor.bottom - floating.height;
4689
- }
4690
- let finalPlacement = placement;
4691
- const vw = window.innerWidth;
4692
- const vh = window.innerHeight;
4693
- if (base === "bottom" && y + floating.height > vh) {
4694
- y = anchor.top - floating.height - offset;
4695
- finalPlacement = placement.replace("bottom", "top");
4696
- } else if (base === "top" && y < 0) {
4697
- y = anchor.bottom + offset;
4698
- finalPlacement = placement.replace("top", "bottom");
5076
+ children: [
5077
+ heading && /* @__PURE__ */ jsxRuntime.jsx("div", { className: "mb-2 text-sm font-semibold text-zinc-900 dark:text-zinc-100", children: heading }),
5078
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "text-sm text-zinc-600 dark:text-zinc-400", children })
5079
+ ]
5080
+ }
5081
+ ) });
4699
5082
  }
4700
- x = Math.max(4, Math.min(x, vw - floating.width - 4));
4701
- y = Math.max(4, Math.min(y, vh - floating.height - 4));
4702
- return { x, y, placement: finalPlacement };
4703
- }
4704
- function useFloatingPosition(anchorRef, floatingRef, options = {}) {
4705
- const { placement = "bottom", offset = 8, enabled = true } = options;
4706
- const [position, setPosition] = react.useState({
4707
- x: -9999,
4708
- y: -9999,
4709
- placement
4710
- });
4711
- const update = react.useCallback(() => {
4712
- const anchor = anchorRef.current;
4713
- const floating = floatingRef.current;
4714
- if (!anchor || !floating) return;
4715
- const anchorRect = anchor.getBoundingClientRect();
4716
- const floatingRect = floating.getBoundingClientRect();
4717
- setPosition(getPosition(anchorRect, floatingRect, placement, offset));
4718
- }, [anchorRef, floatingRef, placement, offset]);
4719
- react.useLayoutEffect(() => {
4720
- if (!enabled) return;
4721
- update();
4722
- const raf = requestAnimationFrame(() => {
4723
- update();
4724
- });
4725
- return () => cancelAnimationFrame(raf);
4726
- }, [update, enabled]);
4727
- react.useEffect(() => {
4728
- if (!enabled) return;
4729
- window.addEventListener("scroll", update, true);
4730
- window.addEventListener("resize", update);
4731
- return () => {
4732
- window.removeEventListener("scroll", update, true);
4733
- window.removeEventListener("resize", update);
4734
- };
4735
- }, [update, enabled]);
4736
- return position;
4737
- }
5083
+ );
5084
+ TimelineBlock.displayName = "TimelineBlock";
5085
+ var TimelineRoot = react.forwardRef(
5086
+ ({
5087
+ children,
5088
+ variant: variantProp,
5089
+ orientation,
5090
+ events,
5091
+ heading,
5092
+ description,
5093
+ animated = true,
5094
+ className
5095
+ }, ref) => {
5096
+ const scrollRef = react.useRef(null);
5097
+ let variant = variantProp ?? "stacked";
5098
+ if (!variantProp && orientation) {
5099
+ variant = orientation === "horizontal" ? "horizontal" : "stacked";
5100
+ }
5101
+ const isHorizontal = variant === "horizontal";
5102
+ const isAlternating = variant === "alternating";
5103
+ const items = events ? events.map((e, i) => /* @__PURE__ */ jsxRuntime.jsxs(TimelineItem, { date: e.date, emoji: e.emoji, icon: e.icon, color: e.color, children: [
5104
+ e.title && /* @__PURE__ */ jsxRuntime.jsx("h3", { className: "font-semibold text-zinc-900 dark:text-white", children: e.title }),
5105
+ e.description && /* @__PURE__ */ jsxRuntime.jsx("div", { className: "mt-1 text-sm leading-relaxed text-zinc-600 dark:text-zinc-400", children: e.description })
5106
+ ] }, i)) : react.Children.toArray(children);
5107
+ const handleWheel = react.useCallback((e) => {
5108
+ if (!scrollRef.current) return;
5109
+ e.preventDefault();
5110
+ scrollRef.current.scrollLeft += e.deltaY;
5111
+ }, []);
5112
+ const content = items.map((child, i) => /* @__PURE__ */ jsxRuntime.jsx(TimelineContext.Provider, { value: { variant, index: i, total: items.length, animated }, children: child }, i));
5113
+ return /* @__PURE__ */ jsxRuntime.jsxs("div", { ref, "data-react-fancy-timeline": "", "data-variant": variant, className, children: [
5114
+ (heading || description) && /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "mb-8", children: [
5115
+ heading && /* @__PURE__ */ jsxRuntime.jsx("h2", { className: "text-xl font-semibold text-zinc-900 dark:text-white", children: heading }),
5116
+ description && /* @__PURE__ */ jsxRuntime.jsx("p", { className: "mt-1 text-sm text-zinc-500 dark:text-zinc-400", children: description })
5117
+ ] }),
5118
+ isHorizontal ? /* @__PURE__ */ jsxRuntime.jsx(
5119
+ "div",
5120
+ {
5121
+ ref: scrollRef,
5122
+ className: "overflow-x-auto pb-4 -mb-4",
5123
+ style: { scrollbarWidth: "thin", scrollbarColor: "rgb(161 161 170) transparent" },
5124
+ onWheel: handleWheel,
5125
+ children: /* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex min-w-max items-start", children: content })
5126
+ }
5127
+ ) : (
5128
+ /* Vertical variants: continuous background line behind all events */
5129
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "relative", children: [
5130
+ items.length > 1 && /* @__PURE__ */ jsxRuntime.jsx("div", { className: cn(
5131
+ "absolute top-0 bottom-0 w-px bg-zinc-200 dark:bg-zinc-700",
5132
+ isAlternating ? "left-4 md:left-1/2 md:-translate-x-px" : "left-4"
5133
+ ) }),
5134
+ content
5135
+ ] })
5136
+ )
5137
+ ] });
5138
+ }
5139
+ );
5140
+ TimelineRoot.displayName = "Timeline";
5141
+ var Timeline = Object.assign(TimelineRoot, {
5142
+ Item: TimelineItem,
5143
+ Block: TimelineBlock
5144
+ });
4738
5145
  var Tooltip = react.forwardRef(
4739
5146
  function Tooltip2({ children, content, placement = "top", delay = 200, offset = 8, className }, _ref) {
4740
5147
  const [open, setOpen] = react.useState(false);
@@ -4769,7 +5176,7 @@ var Tooltip = react.forwardRef(
4769
5176
  "data-react-fancy-tooltip": "",
4770
5177
  role: "tooltip",
4771
5178
  className: cn(
4772
- "fancy-fade-in pointer-events-none fixed z-50 max-w-xs rounded-lg bg-zinc-900 px-3 py-1.5 text-sm text-white shadow-lg dark:bg-zinc-100 dark:text-zinc-900",
5179
+ "fancy-fade-in pointer-events-none fixed z-50 max-w-xs rounded-lg bg-zinc-900 px-3 py-1.5 text-sm text-white shadow-lg dark:bg-zinc-700 dark:text-zinc-100",
4773
5180
  className
4774
5181
  ),
4775
5182
  style: { left: position.x, top: position.y },
@@ -4779,7 +5186,7 @@ var Tooltip = react.forwardRef(
4779
5186
  "div",
4780
5187
  {
4781
5188
  className: cn(
4782
- "absolute h-2 w-2 rotate-45 bg-zinc-900 dark:bg-zinc-100",
5189
+ "absolute h-2 w-2 rotate-45 bg-zinc-900 dark:bg-zinc-700",
4783
5190
  position.placement.startsWith("top") && "bottom-[-4px] left-1/2 -translate-x-1/2",
4784
5191
  position.placement.startsWith("bottom") && "top-[-4px] left-1/2 -translate-x-1/2",
4785
5192
  position.placement.startsWith("left") && "right-[-4px] top-1/2 -translate-y-1/2",
@@ -4802,78 +5209,26 @@ function usePopover() {
4802
5209
  }
4803
5210
  return ctx;
4804
5211
  }
4805
- function PopoverTrigger({ children }) {
4806
- const { setOpen, open, anchorRef } = usePopover();
4807
- return react.cloneElement(children, {
4808
- ref: anchorRef,
4809
- onClick: () => setOpen(!open),
4810
- "aria-expanded": open,
4811
- "aria-haspopup": true
4812
- });
4813
- }
4814
- PopoverTrigger.displayName = "PopoverTrigger";
4815
- function useOutsideClick(ref, handler, enabled = true, ignoreRef) {
4816
- react.useEffect(() => {
4817
- if (!enabled) return;
4818
- const listener = (event) => {
4819
- const el = ref.current;
4820
- if (!el || el.contains(event.target)) return;
4821
- if (ignoreRef?.current?.contains(event.target)) return;
4822
- handler(event);
4823
- };
4824
- document.addEventListener("mousedown", listener);
4825
- document.addEventListener("touchstart", listener);
4826
- return () => {
4827
- document.removeEventListener("mousedown", listener);
4828
- document.removeEventListener("touchstart", listener);
4829
- };
4830
- }, [ref, handler, enabled, ignoreRef]);
4831
- }
4832
- function useEscapeKey(handler, enabled = true) {
4833
- react.useEffect(() => {
4834
- if (!enabled) return;
4835
- const listener = (event) => {
4836
- if (event.key === "Escape") {
4837
- handler();
4838
- }
4839
- };
4840
- document.addEventListener("keydown", listener);
4841
- return () => document.removeEventListener("keydown", listener);
4842
- }, [handler, enabled]);
4843
- }
4844
- function useAnimation({
4845
- open,
4846
- enterClass,
4847
- exitClass
4848
- }) {
4849
- const [mounted, setMounted] = react.useState(open);
4850
- const [animClass, setAnimClass] = react.useState(open ? enterClass : "");
4851
- const ref = react.useRef(null);
4852
- react.useEffect(() => {
4853
- if (open) {
4854
- setMounted(true);
4855
- setAnimClass(enterClass);
4856
- } else if (mounted) {
4857
- setAnimClass(exitClass);
4858
- }
4859
- }, [open, enterClass, exitClass, mounted]);
4860
- const handleAnimationEnd = react.useCallback(() => {
4861
- if (!open) {
4862
- setMounted(false);
4863
- setAnimClass("");
5212
+ function PopoverTrigger({ children, className }) {
5213
+ const { setOpen, open, anchorRef, hover, onHoverEnter, onHoverLeave } = usePopover();
5214
+ return /* @__PURE__ */ jsxRuntime.jsx(
5215
+ "span",
5216
+ {
5217
+ ref: anchorRef,
5218
+ "data-react-fancy-popover-trigger": "",
5219
+ className: cn("inline-flex", className),
5220
+ onClick: hover ? void 0 : () => setOpen(!open),
5221
+ onMouseEnter: hover ? onHoverEnter : void 0,
5222
+ onMouseLeave: hover ? onHoverLeave : void 0,
5223
+ "aria-expanded": open,
5224
+ "aria-haspopup": true,
5225
+ children
4864
5226
  }
4865
- }, [open]);
4866
- react.useEffect(() => {
4867
- const el = ref.current;
4868
- if (!el) return;
4869
- el.addEventListener("animationend", handleAnimationEnd);
4870
- return () => el.removeEventListener("animationend", handleAnimationEnd);
4871
- }, [handleAnimationEnd, mounted]);
4872
- return { mounted, className: animClass, ref };
5227
+ );
4873
5228
  }
5229
+ PopoverTrigger.displayName = "PopoverTrigger";
4874
5230
  function PopoverContent({ children, className }) {
4875
- const { open, setOpen, anchorRef, placement, offset } = usePopover();
4876
- const floatingRef = react.useRef(null);
5231
+ const { open, setOpen, anchorRef, floatingRef, placement, offset, hover, onHoverEnter, onHoverLeave } = usePopover();
4877
5232
  const outsideRef = react.useRef(null);
4878
5233
  const position = useFloatingPosition(anchorRef, floatingRef, {
4879
5234
  placement,
@@ -4881,29 +5236,26 @@ function PopoverContent({ children, className }) {
4881
5236
  enabled: open
4882
5237
  });
4883
5238
  const close = react.useCallback(() => setOpen(false), [setOpen]);
4884
- useOutsideClick(outsideRef, close, open, anchorRef);
5239
+ useOutsideClick(outsideRef, close, open && !hover, anchorRef);
4885
5240
  useEscapeKey(close, open);
4886
- const { mounted, className: animClass, ref: animRef } = useAnimation({
4887
- open,
4888
- enterClass: "fancy-scale-in",
4889
- exitClass: "fancy-fade-out"
4890
- });
4891
- if (!mounted) return null;
5241
+ const positioned = position.x !== -9999 && position.y !== -9999;
5242
+ if (!open) return null;
4892
5243
  return /* @__PURE__ */ jsxRuntime.jsx(Portal, { children: /* @__PURE__ */ jsxRuntime.jsx(
4893
5244
  "div",
4894
5245
  {
4895
5246
  ref: (node) => {
4896
5247
  outsideRef.current = node;
4897
5248
  floatingRef.current = node;
4898
- animRef.current = node;
4899
5249
  },
4900
5250
  "data-react-fancy-popover": "",
4901
5251
  className: cn(
4902
5252
  "fixed z-50 rounded-xl border border-zinc-200 bg-white p-4 text-zinc-700 shadow-lg dark:border-zinc-700 dark:bg-zinc-900 dark:text-zinc-200 dark:shadow-zinc-950/50",
4903
- animClass,
5253
+ positioned ? "fancy-scale-in" : "invisible",
4904
5254
  className
4905
5255
  ),
4906
5256
  style: { left: position.x, top: position.y },
5257
+ onMouseEnter: hover ? onHoverEnter : void 0,
5258
+ onMouseLeave: hover ? onHoverLeave : void 0,
4907
5259
  children
4908
5260
  }
4909
5261
  ) });
@@ -4915,17 +5267,37 @@ function PopoverRoot({
4915
5267
  defaultOpen = false,
4916
5268
  onOpenChange,
4917
5269
  placement = "bottom",
4918
- offset = 8
5270
+ offset = 8,
5271
+ hover = false,
5272
+ hoverDelay = 200,
5273
+ hoverCloseDelay = 300
4919
5274
  }) {
4920
- const [open, setOpen] = useControllableState(
4921
- controlledOpen,
4922
- defaultOpen,
4923
- onOpenChange
4924
- );
5275
+ const [internalOpen, setInternalOpen] = react.useState(defaultOpen);
5276
+ const isControlled = controlledOpen !== void 0;
5277
+ const open = isControlled ? controlledOpen : internalOpen;
4925
5278
  const anchorRef = react.useRef(null);
5279
+ const floatingRef = react.useRef(null);
5280
+ const hoverTimeoutRef = react.useRef(void 0);
5281
+ const setOpen = react.useCallback(
5282
+ (next) => {
5283
+ if (!isControlled) setInternalOpen(next);
5284
+ onOpenChange?.(next);
5285
+ },
5286
+ [isControlled, onOpenChange]
5287
+ );
5288
+ const onHoverEnter = react.useCallback(() => {
5289
+ if (!hover) return;
5290
+ clearTimeout(hoverTimeoutRef.current);
5291
+ hoverTimeoutRef.current = setTimeout(() => setOpen(true), hoverDelay);
5292
+ }, [hover, hoverDelay, setOpen]);
5293
+ const onHoverLeave = react.useCallback(() => {
5294
+ if (!hover) return;
5295
+ clearTimeout(hoverTimeoutRef.current);
5296
+ hoverTimeoutRef.current = setTimeout(() => setOpen(false), hoverCloseDelay);
5297
+ }, [hover, hoverCloseDelay, setOpen]);
4926
5298
  const ctx = react.useMemo(
4927
- () => ({ open, setOpen, anchorRef, placement, offset }),
4928
- [open, setOpen, anchorRef, placement, offset]
5299
+ () => ({ open, setOpen, anchorRef, floatingRef, placement, offset, hover, onHoverEnter, onHoverLeave }),
5300
+ [open, setOpen, placement, offset, hover, onHoverEnter, onHoverLeave]
4929
5301
  );
4930
5302
  return /* @__PURE__ */ jsxRuntime.jsx(PopoverContext.Provider, { value: ctx, children });
4931
5303
  }
@@ -4953,6 +5325,36 @@ function DropdownTrigger({ children }) {
4953
5325
  });
4954
5326
  }
4955
5327
  DropdownTrigger.displayName = "DropdownTrigger";
5328
+ function useAnimation({
5329
+ open,
5330
+ enterClass,
5331
+ exitClass
5332
+ }) {
5333
+ const [mounted, setMounted] = react.useState(open);
5334
+ const [animClass, setAnimClass] = react.useState(open ? enterClass : "");
5335
+ const ref = react.useRef(null);
5336
+ react.useEffect(() => {
5337
+ if (open) {
5338
+ setMounted(true);
5339
+ setAnimClass(enterClass);
5340
+ } else if (mounted) {
5341
+ setAnimClass(exitClass);
5342
+ }
5343
+ }, [open, enterClass, exitClass, mounted]);
5344
+ const handleAnimationEnd = react.useCallback(() => {
5345
+ if (!open) {
5346
+ setMounted(false);
5347
+ setAnimClass("");
5348
+ }
5349
+ }, [open]);
5350
+ react.useEffect(() => {
5351
+ const el = ref.current;
5352
+ if (!el) return;
5353
+ el.addEventListener("animationend", handleAnimationEnd);
5354
+ return () => el.removeEventListener("animationend", handleAnimationEnd);
5355
+ }, [handleAnimationEnd, mounted]);
5356
+ return { mounted, className: animClass, ref };
5357
+ }
4956
5358
  function DropdownItems({ children, className }) {
4957
5359
  const { open, setOpen, anchorRef, activeIndex, setActiveIndex, placement, offset } = useDropdown();
4958
5360
  const floatingRef = react.useRef(null);
@@ -6271,7 +6673,7 @@ var Autocomplete = react.forwardRef(
6271
6673
  role: "combobox",
6272
6674
  "aria-expanded": open,
6273
6675
  "aria-autocomplete": "list",
6274
- className: "w-full rounded-lg border border-zinc-200 bg-white px-3 py-2 text-sm text-zinc-900 placeholder:text-zinc-400 outline-none transition-colors focus:border-zinc-400 dark:border-zinc-700 dark:bg-zinc-900 dark:text-zinc-100 dark:placeholder:text-zinc-500 dark:focus:border-zinc-500"
6676
+ className: "w-full rounded-lg border border-zinc-200 bg-white px-3 py-2 text-sm text-zinc-900 placeholder:text-zinc-400 outline-none transition-[border-color,box-shadow] duration-150 focus:border-blue-500 focus:ring-2 focus:ring-blue-500/40 dark:border-zinc-700 dark:bg-[#1e1e24] dark:text-zinc-100 dark:placeholder:text-zinc-500 dark:focus:border-blue-400 dark:focus:ring-blue-400/20"
6275
6677
  }
6276
6678
  ),
6277
6679
  open && /* @__PURE__ */ jsxRuntime.jsx(Portal, { children: /* @__PURE__ */ jsxRuntime.jsx(
@@ -6365,7 +6767,7 @@ var Pillbox = react.forwardRef(
6365
6767
  "data-react-fancy-pillbox": "",
6366
6768
  ref,
6367
6769
  className: cn(
6368
- "flex flex-wrap items-center gap-1.5 rounded-lg border border-zinc-200 bg-white px-3 py-2 transition-colors focus-within:border-zinc-400 dark:border-zinc-700 dark:bg-zinc-900 dark:focus-within:border-zinc-500",
6770
+ "flex flex-wrap items-center gap-1.5 rounded-lg border border-zinc-200 bg-white px-3 py-2 transition-[border-color,box-shadow] duration-150 focus-within:border-blue-500 focus-within:ring-2 focus-within:ring-blue-500/40 dark:border-zinc-700 dark:bg-[#1e1e24] dark:text-zinc-100 dark:focus-within:border-blue-400 dark:focus-within:ring-blue-400/20",
6369
6771
  disabled && "cursor-not-allowed opacity-50",
6370
6772
  className
6371
6773
  ),
@@ -6496,7 +6898,7 @@ var OtpInput = react.forwardRef(
6496
6898
  onFocus: (e) => e.target.select(),
6497
6899
  disabled,
6498
6900
  autoFocus: autoFocus && i === 0,
6499
- className: "h-12 w-10 rounded-lg border border-zinc-200 bg-white text-center text-lg font-medium outline-none transition-colors focus:border-zinc-400 dark:border-zinc-700 dark:bg-zinc-900 dark:focus:border-zinc-500",
6901
+ className: "h-12 w-10 rounded-lg border border-zinc-200 bg-white text-center text-lg font-medium text-zinc-900 outline-none transition-[border-color,box-shadow] duration-150 focus:border-blue-500 focus:ring-2 focus:ring-blue-500/40 dark:border-zinc-700 dark:bg-[#1e1e24] dark:text-zinc-100 dark:focus:border-blue-400 dark:focus:ring-blue-400/20",
6500
6902
  "aria-label": `Digit ${i + 1}`
6501
6903
  },
6502
6904
  i
@@ -6545,7 +6947,7 @@ function FileUploadDropzone({
6545
6947
  onClick: () => !disabled && inputRef.current?.click(),
6546
6948
  className: cn(
6547
6949
  "flex cursor-pointer flex-col items-center justify-center rounded-xl border-2 border-dashed p-8 text-center transition-colors",
6548
- dragOver ? "border-blue-400 bg-blue-50 dark:border-blue-500 dark:bg-blue-950" : "border-zinc-300 hover:border-zinc-400 dark:border-zinc-600 dark:hover:border-zinc-500",
6950
+ dragOver ? "border-blue-400 bg-blue-50 dark:border-blue-500 dark:bg-blue-950" : "border-zinc-300 hover:border-zinc-400 dark:border-zinc-700 dark:hover:border-zinc-500",
6549
6951
  disabled && "cursor-not-allowed opacity-50",
6550
6952
  className
6551
6953
  ),
@@ -7905,6 +8307,7 @@ function usePanZoom({
7905
8307
  if (!container) return;
7906
8308
  function handleWheel(e) {
7907
8309
  if (!zoomableRef.current) return;
8310
+ if (!e.ctrlKey && !e.metaKey) return;
7908
8311
  e.preventDefault();
7909
8312
  const rect = container.getBoundingClientRect();
7910
8313
  const mouseX = e.clientX - rect.left;
@@ -9097,9 +9500,11 @@ function useCanvas() {
9097
9500
  if (!ctx) throw new Error("useCanvas must be used within a Canvas component");
9098
9501
  return ctx;
9099
9502
  }
9100
- function CanvasNode({ children, id, x, y, className, style }) {
9101
- const { registerNode, unregisterNode } = useCanvas();
9503
+ function CanvasNode({ children, id, x, y, draggable, onPositionChange, className, style }) {
9504
+ const { registerNode, unregisterNode, viewport } = useCanvas();
9102
9505
  const nodeRef = react.useRef(null);
9506
+ const isDragging = react.useRef(false);
9507
+ const dragStart = react.useRef({ mouseX: 0, mouseY: 0, nodeX: 0, nodeY: 0 });
9103
9508
  react.useEffect(() => {
9104
9509
  const el = nodeRef.current;
9105
9510
  if (!el) return;
@@ -9114,14 +9519,39 @@ function CanvasNode({ children, id, x, y, className, style }) {
9114
9519
  unregisterNode(id);
9115
9520
  };
9116
9521
  }, [id, x, y, registerNode, unregisterNode]);
9522
+ const handlePointerDown = react.useCallback(
9523
+ (e) => {
9524
+ if (!draggable || e.button !== 0) return;
9525
+ e.stopPropagation();
9526
+ isDragging.current = true;
9527
+ dragStart.current = { mouseX: e.clientX, mouseY: e.clientY, nodeX: x, nodeY: y };
9528
+ e.target.setPointerCapture(e.pointerId);
9529
+ },
9530
+ [draggable, x, y]
9531
+ );
9532
+ const handlePointerMove = react.useCallback(
9533
+ (e) => {
9534
+ if (!isDragging.current) return;
9535
+ const dx = (e.clientX - dragStart.current.mouseX) / viewport.zoom;
9536
+ const dy = (e.clientY - dragStart.current.mouseY) / viewport.zoom;
9537
+ onPositionChange?.(dragStart.current.nodeX + dx, dragStart.current.nodeY + dy);
9538
+ },
9539
+ [viewport.zoom, onPositionChange]
9540
+ );
9541
+ const handlePointerUp = react.useCallback(() => {
9542
+ isDragging.current = false;
9543
+ }, []);
9117
9544
  return /* @__PURE__ */ jsxRuntime.jsx(
9118
9545
  "div",
9119
9546
  {
9120
9547
  ref: nodeRef,
9121
9548
  "data-react-fancy-canvas-node": "",
9122
9549
  "data-node-id": id,
9123
- className: cn("absolute", className),
9550
+ className: cn("absolute", draggable && "cursor-grab active:cursor-grabbing", className),
9124
9551
  style: { left: x, top: y, ...style },
9552
+ onPointerDown: handlePointerDown,
9553
+ onPointerMove: handlePointerMove,
9554
+ onPointerUp: handlePointerUp,
9125
9555
  children
9126
9556
  }
9127
9557
  );
@@ -9158,10 +9588,18 @@ function getAnchorPoint(rect, anchor, otherRect) {
9158
9588
  }
9159
9589
  }
9160
9590
  function bezierPath(from, to) {
9161
- const dx = Math.abs(to.x - from.x) * 0.5;
9162
- const cp1x = from.x + (to.x > from.x ? dx : -dx);
9163
- const cp2x = to.x + (to.x > from.x ? -dx : dx);
9164
- return `M${from.x},${from.y} C${cp1x},${from.y} ${cp2x},${to.y} ${to.x},${to.y}`;
9591
+ const dx = Math.abs(to.x - from.x);
9592
+ const dy = Math.abs(to.y - from.y);
9593
+ if (dx > dy) {
9594
+ const offset2 = dx * 0.5;
9595
+ const cp1x = from.x + (to.x > from.x ? offset2 : -offset2);
9596
+ const cp2x = to.x + (to.x > from.x ? -offset2 : offset2);
9597
+ return `M${from.x},${from.y} C${cp1x},${from.y} ${cp2x},${to.y} ${to.x},${to.y}`;
9598
+ }
9599
+ const offset = Math.max(dy * 0.5, 30);
9600
+ const cp1y = from.y + (to.y > from.y ? offset : -offset);
9601
+ const cp2y = to.y + (to.y > from.y ? -offset : offset);
9602
+ return `M${from.x},${from.y} C${from.x},${cp1y} ${to.x},${cp2y} ${to.x},${to.y}`;
9165
9603
  }
9166
9604
  function stepPath(from, to) {
9167
9605
  const midX = (from.x + to.x) / 2;
@@ -9346,6 +9784,7 @@ function CanvasRoot({
9346
9784
  pannable = true,
9347
9785
  zoomable = true,
9348
9786
  showGrid = false,
9787
+ fitOnMount = false,
9349
9788
  className,
9350
9789
  style
9351
9790
  }) {
@@ -9365,15 +9804,41 @@ function CanvasRoot({
9365
9804
  () => ({ viewport, setViewport, registerNode, unregisterNode, nodeRects, registryVersion, containerRef }),
9366
9805
  [viewport, setViewport, registerNode, unregisterNode, nodeRects, registryVersion]
9367
9806
  );
9807
+ const hasFitted = react.useRef(false);
9808
+ react.useEffect(() => {
9809
+ if (!fitOnMount || hasFitted.current || nodeRects.size === 0) return;
9810
+ const container = containerRef.current;
9811
+ if (!container || container.clientWidth === 0) return;
9812
+ hasFitted.current = true;
9813
+ requestAnimationFrame(() => {
9814
+ let minX = Infinity, minY = Infinity, maxX = -Infinity, maxY = -Infinity;
9815
+ nodeRects.forEach((r) => {
9816
+ minX = Math.min(minX, r.x);
9817
+ minY = Math.min(minY, r.y);
9818
+ maxX = Math.max(maxX, r.x + r.width);
9819
+ maxY = Math.max(maxY, r.y + r.height);
9820
+ });
9821
+ const padding = 40;
9822
+ const contentW = maxX - minX + padding * 2;
9823
+ const contentH = maxY - minY + padding * 2;
9824
+ const cw = container.clientWidth;
9825
+ const ch = container.clientHeight;
9826
+ const zoom = Math.min(cw / contentW, ch / contentH, 1.5);
9827
+ const panX = (cw - contentW * zoom) / 2 - minX * zoom + padding * zoom;
9828
+ const panY = (ch - contentH * zoom) / 2 - minY * zoom + padding * zoom;
9829
+ setViewport({ panX, panY, zoom });
9830
+ });
9831
+ }, [fitOnMount, nodeRects, registryVersion, setViewport]);
9368
9832
  const edges = [];
9369
9833
  const others = [];
9370
9834
  const overlays = [];
9371
9835
  react.Children.forEach(children, (child) => {
9372
9836
  const el = child;
9373
9837
  if (!el || !el.type) return;
9374
- if (el.type === CanvasEdge) {
9838
+ const elType = el.type;
9839
+ if (elType === CanvasEdge || elType?._isCanvasEdge) {
9375
9840
  edges.push(el);
9376
- } else if (el.type === CanvasMinimap || el.type === CanvasControls) {
9841
+ } else if (elType === CanvasMinimap || elType === CanvasControls) {
9377
9842
  overlays.push(el);
9378
9843
  } else {
9379
9844
  others.push(el);
@@ -9420,15 +9885,16 @@ function CanvasRoot({
9420
9885
  },
9421
9886
  children: [
9422
9887
  /* @__PURE__ */ jsxRuntime.jsxs("defs", { children: [
9423
- /* @__PURE__ */ jsxRuntime.jsx("marker", { id: "canvas-arrow", viewBox: "0 0 10 10", refX: "10", refY: "5", markerWidth: "8", markerHeight: "8", orient: "auto-start-reverse", children: /* @__PURE__ */ jsxRuntime.jsx("path", { d: "M0,0 L10,5 L0,10 Z", fill: "currentColor", className: "text-zinc-400 dark:text-zinc-500" }) }),
9424
- /* @__PURE__ */ jsxRuntime.jsx("marker", { id: "canvas-circle", viewBox: "0 0 10 10", refX: "5", refY: "5", markerWidth: "8", markerHeight: "8", orient: "auto-start-reverse", children: /* @__PURE__ */ jsxRuntime.jsx("circle", { cx: "5", cy: "5", r: "3.5", fill: "currentColor", className: "text-zinc-400 dark:text-zinc-500" }) }),
9425
- /* @__PURE__ */ jsxRuntime.jsx("marker", { id: "canvas-square", viewBox: "0 0 10 10", refX: "5", refY: "5", markerWidth: "8", markerHeight: "8", orient: "auto-start-reverse", children: /* @__PURE__ */ jsxRuntime.jsx("rect", { x: "1.5", y: "1.5", width: "7", height: "7", fill: "currentColor", className: "text-zinc-400 dark:text-zinc-500" }) }),
9426
- /* @__PURE__ */ jsxRuntime.jsxs("marker", { id: "canvas-crow-foot", viewBox: "0 0 12 12", refX: "12", refY: "6", markerWidth: "10", markerHeight: "10", orient: "auto-start-reverse", children: [
9427
- /* @__PURE__ */ jsxRuntime.jsx("line", { x1: "0", y1: "0", x2: "12", y2: "6", stroke: "currentColor", strokeWidth: "1.5", className: "text-zinc-400 dark:text-zinc-500" }),
9428
- /* @__PURE__ */ jsxRuntime.jsx("line", { x1: "0", y1: "12", x2: "12", y2: "6", stroke: "currentColor", strokeWidth: "1.5", className: "text-zinc-400 dark:text-zinc-500" }),
9429
- /* @__PURE__ */ jsxRuntime.jsx("line", { x1: "0", y1: "6", x2: "12", y2: "6", stroke: "currentColor", strokeWidth: "1.5", className: "text-zinc-400 dark:text-zinc-500" })
9430
- ] }),
9431
- /* @__PURE__ */ jsxRuntime.jsx("marker", { id: "canvas-diamond", viewBox: "0 0 12 12", refX: "6", refY: "6", markerWidth: "10", markerHeight: "10", orient: "auto-start-reverse", children: /* @__PURE__ */ jsxRuntime.jsx("polygon", { points: "6,0 12,6 6,12 0,6", fill: "none", stroke: "currentColor", strokeWidth: "1.5", className: "text-zinc-400 dark:text-zinc-500" }) })
9888
+ /* @__PURE__ */ jsxRuntime.jsx("marker", { id: "canvas-arrow", viewBox: "0 0 10 10", refX: "10", refY: "5", markerWidth: "8", markerHeight: "8", orient: "auto", children: /* @__PURE__ */ jsxRuntime.jsx("path", { d: "M0,0 L10,5 L0,10 Z", fill: "#71717a" }) }),
9889
+ /* @__PURE__ */ jsxRuntime.jsx("marker", { id: "canvas-circle", viewBox: "0 0 10 10", refX: "5", refY: "5", markerWidth: "8", markerHeight: "8", orient: "auto", children: /* @__PURE__ */ jsxRuntime.jsx("circle", { cx: "5", cy: "5", r: "3.5", fill: "#71717a" }) }),
9890
+ /* @__PURE__ */ jsxRuntime.jsx("marker", { id: "canvas-diamond", viewBox: "0 0 12 12", refX: "6", refY: "6", markerWidth: "10", markerHeight: "10", orient: "auto", children: /* @__PURE__ */ jsxRuntime.jsx("polygon", { points: "6,0 12,6 6,12 0,6", fill: "none", stroke: "#71717a", strokeWidth: "1.5" }) }),
9891
+ /* @__PURE__ */ jsxRuntime.jsx("marker", { id: "canvas-one", viewBox: "0 0 2 16", refX: "1", refY: "8", markerWidth: "2", markerHeight: "14", orient: "auto", children: /* @__PURE__ */ jsxRuntime.jsx("line", { x1: "1", y1: "0", x2: "1", y2: "16", stroke: "#71717a", strokeWidth: "2" }) }),
9892
+ /* @__PURE__ */ jsxRuntime.jsxs("marker", { id: "canvas-crow-foot", viewBox: "0 0 16 16", refX: "16", refY: "8", markerWidth: "14", markerHeight: "14", orient: "auto", children: [
9893
+ /* @__PURE__ */ jsxRuntime.jsx("line", { x1: "16", y1: "8", x2: "0", y2: "0", stroke: "#71717a", strokeWidth: "2", strokeLinecap: "round" }),
9894
+ /* @__PURE__ */ jsxRuntime.jsx("line", { x1: "16", y1: "8", x2: "0", y2: "8", stroke: "#71717a", strokeWidth: "2", strokeLinecap: "round" }),
9895
+ /* @__PURE__ */ jsxRuntime.jsx("line", { x1: "16", y1: "8", x2: "0", y2: "16", stroke: "#71717a", strokeWidth: "2", strokeLinecap: "round" }),
9896
+ /* @__PURE__ */ jsxRuntime.jsx("line", { x1: "16", y1: "0", x2: "16", y2: "16", stroke: "#71717a", strokeWidth: "2", strokeLinecap: "round" })
9897
+ ] })
9432
9898
  ] }),
9433
9899
  edges
9434
9900
  ]
@@ -9486,13 +9952,16 @@ function DiagramField({
9486
9952
  DiagramField.displayName = "DiagramField";
9487
9953
  function DiagramEntity({
9488
9954
  children,
9489
- id,
9955
+ id: idProp,
9490
9956
  name,
9491
9957
  x = 0,
9492
9958
  y = 0,
9493
9959
  color = "bg-blue-600 dark:bg-blue-500",
9960
+ draggable,
9961
+ onPositionChange,
9494
9962
  className
9495
9963
  }) {
9964
+ const id = idProp ?? name;
9496
9965
  const fields = [];
9497
9966
  const other = [];
9498
9967
  react.Children.forEach(children, (child) => {
@@ -9504,7 +9973,7 @@ function DiagramEntity({
9504
9973
  other.push(el);
9505
9974
  }
9506
9975
  });
9507
- return /* @__PURE__ */ jsxRuntime.jsx(Canvas.Node, { id, x, y, children: /* @__PURE__ */ jsxRuntime.jsxs(
9976
+ return /* @__PURE__ */ jsxRuntime.jsx(Canvas.Node, { id, x, y, draggable, onPositionChange, children: /* @__PURE__ */ jsxRuntime.jsxs(
9508
9977
  "div",
9509
9978
  {
9510
9979
  "data-react-fancy-diagram-entity": "",
@@ -9531,37 +10000,193 @@ function DiagramEntity({
9531
10000
  ) });
9532
10001
  }
9533
10002
  DiagramEntity.displayName = "DiagramEntity";
9534
- function getMarkers(type) {
9535
- switch (type) {
9536
- case "one-to-one":
9537
- return { markerStart: "canvas-arrow", markerEnd: "canvas-arrow" };
9538
- case "one-to-many":
9539
- return { markerStart: "canvas-arrow", markerEnd: "canvas-crow-foot" };
9540
- case "many-to-many":
9541
- return { markerStart: "canvas-crow-foot", markerEnd: "canvas-crow-foot" };
10003
+ var HEADER_HEIGHT = 36;
10004
+ var FIELD_HEIGHT = 28;
10005
+ var SYMBOL_SIZE = 12;
10006
+ function oneSymbol(pt, direction) {
10007
+ const s = SYMBOL_SIZE * 0.6;
10008
+ switch (direction) {
10009
+ case "left":
10010
+ case "right":
10011
+ return `M${pt.x},${pt.y - s} L${pt.x},${pt.y + s}`;
10012
+ case "up":
10013
+ case "down":
10014
+ return `M${pt.x - s},${pt.y} L${pt.x + s},${pt.y}`;
9542
10015
  }
9543
10016
  }
10017
+ function crowFootSymbol(pt, direction) {
10018
+ const s = SYMBOL_SIZE;
10019
+ const spread = s * 0.8;
10020
+ let tip;
10021
+ switch (direction) {
10022
+ case "right":
10023
+ tip = { x: pt.x - s, y: pt.y };
10024
+ return [
10025
+ `M${pt.x},${pt.y - spread} L${tip.x},${tip.y}`,
10026
+ `M${pt.x},${pt.y} L${tip.x},${tip.y}`,
10027
+ `M${pt.x},${pt.y + spread} L${tip.x},${tip.y}`,
10028
+ // bar at entity edge
10029
+ `M${pt.x},${pt.y - spread} L${pt.x},${pt.y + spread}`
10030
+ ].join(" ");
10031
+ case "left":
10032
+ tip = { x: pt.x + s, y: pt.y };
10033
+ return [
10034
+ `M${pt.x},${pt.y - spread} L${tip.x},${tip.y}`,
10035
+ `M${pt.x},${pt.y} L${tip.x},${tip.y}`,
10036
+ `M${pt.x},${pt.y + spread} L${tip.x},${tip.y}`,
10037
+ `M${pt.x},${pt.y - spread} L${pt.x},${pt.y + spread}`
10038
+ ].join(" ");
10039
+ case "down":
10040
+ tip = { x: pt.x, y: pt.y - s };
10041
+ return [
10042
+ `M${pt.x - spread},${pt.y} L${tip.x},${tip.y}`,
10043
+ `M${pt.x},${pt.y} L${tip.x},${tip.y}`,
10044
+ `M${pt.x + spread},${pt.y} L${tip.x},${tip.y}`,
10045
+ `M${pt.x - spread},${pt.y} L${pt.x + spread},${pt.y}`
10046
+ ].join(" ");
10047
+ case "up":
10048
+ tip = { x: pt.x, y: pt.y + s };
10049
+ return [
10050
+ `M${pt.x - spread},${pt.y} L${tip.x},${tip.y}`,
10051
+ `M${pt.x},${pt.y} L${tip.x},${tip.y}`,
10052
+ `M${pt.x + spread},${pt.y} L${tip.x},${tip.y}`,
10053
+ `M${pt.x - spread},${pt.y} L${pt.x + spread},${pt.y}`
10054
+ ].join(" ");
10055
+ }
10056
+ }
10057
+ function getSymbolPath(type, end, pt, direction) {
10058
+ const side = end === "start" ? type.split("-to-")[0] : type.split("-to-")[1];
10059
+ if (side === "one") return oneSymbol(pt, direction);
10060
+ if (side === "many") return crowFootSymbol(pt, direction);
10061
+ return null;
10062
+ }
9544
10063
  function DiagramRelation({
9545
10064
  from,
9546
10065
  to,
10066
+ fromField: fromFieldProp,
10067
+ toField: toFieldProp,
9547
10068
  type,
9548
- label,
9549
- className
10069
+ label
9550
10070
  }) {
9551
- const markers = getMarkers(type);
9552
- return /* @__PURE__ */ jsxRuntime.jsx(
9553
- Canvas.Edge,
9554
- {
9555
- from,
9556
- to,
9557
- curve: "bezier",
9558
- markerStart: markers.markerStart,
9559
- markerEnd: markers.markerEnd,
9560
- label,
9561
- className: cn("text-zinc-300 dark:text-zinc-600", className)
10071
+ const { nodeRects, registryVersion } = useCanvas();
10072
+ const { schema } = useDiagram();
10073
+ const result = react.useMemo(() => {
10074
+ const fromRect = nodeRects.get(from);
10075
+ const toRect = nodeRects.get(to);
10076
+ if (!fromRect || !toRect) return null;
10077
+ const fromEntity = schema.entities.find((e) => (e.id ?? e.name) === from);
10078
+ const toEntity = schema.entities.find((e) => (e.id ?? e.name) === to);
10079
+ let fromFieldIdx = -1;
10080
+ let toFieldIdx = -1;
10081
+ if (fromFieldProp && fromEntity?.fields) {
10082
+ fromFieldIdx = fromEntity.fields.findIndex((f) => f.name === fromFieldProp);
10083
+ } else if (fromEntity?.fields) {
10084
+ fromFieldIdx = fromEntity.fields.findIndex((f) => f.primary);
9562
10085
  }
9563
- );
10086
+ if (toFieldProp && toEntity?.fields) {
10087
+ toFieldIdx = toEntity.fields.findIndex((f) => f.name === toFieldProp);
10088
+ } else if (toEntity?.fields) {
10089
+ const fromName = (fromEntity?.name ?? from).toLowerCase();
10090
+ toFieldIdx = toEntity.fields.findIndex(
10091
+ (f) => f.foreign && (f.name === `${fromName}_id` || f.name === `${fromName}Id`)
10092
+ );
10093
+ if (toFieldIdx === -1) {
10094
+ toFieldIdx = toEntity.fields.findIndex((f) => f.foreign);
10095
+ }
10096
+ }
10097
+ const fromFieldY = fromFieldIdx >= 0 ? HEADER_HEIGHT + fromFieldIdx * FIELD_HEIGHT + FIELD_HEIGHT / 2 : fromRect.height / 2;
10098
+ const toFieldY = toFieldIdx >= 0 ? HEADER_HEIGHT + toFieldIdx * FIELD_HEIGHT + FIELD_HEIGHT / 2 : toRect.height / 2;
10099
+ const fromCx = fromRect.x + fromRect.width / 2;
10100
+ const toCx = toRect.x + toRect.width / 2;
10101
+ const fromCy = fromRect.y + fromRect.height / 2;
10102
+ const toCy = toRect.y + toRect.height / 2;
10103
+ const dx = Math.abs(fromCx - toCx);
10104
+ const dy = Math.abs(fromCy - toCy);
10105
+ let fromPt, toPt;
10106
+ let fromDir;
10107
+ let toDir;
10108
+ if (dx > dy * 0.5) {
10109
+ if (fromCx < toCx) {
10110
+ fromPt = { x: fromRect.x + fromRect.width, y: fromRect.y + fromFieldY };
10111
+ toPt = { x: toRect.x, y: toRect.y + toFieldY };
10112
+ fromDir = "right";
10113
+ toDir = "left";
10114
+ } else {
10115
+ fromPt = { x: fromRect.x, y: fromRect.y + fromFieldY };
10116
+ toPt = { x: toRect.x + toRect.width, y: toRect.y + toFieldY };
10117
+ fromDir = "left";
10118
+ toDir = "right";
10119
+ }
10120
+ } else {
10121
+ if (fromCy < toCy) {
10122
+ fromPt = { x: fromRect.x + fromRect.width / 2, y: fromRect.y + fromRect.height };
10123
+ toPt = { x: toRect.x + toRect.width / 2, y: toRect.y };
10124
+ fromDir = "down";
10125
+ toDir = "up";
10126
+ } else {
10127
+ fromPt = { x: fromRect.x + fromRect.width / 2, y: fromRect.y };
10128
+ toPt = { x: toRect.x + toRect.width / 2, y: toRect.y + toRect.height };
10129
+ fromDir = "up";
10130
+ toDir = "down";
10131
+ }
10132
+ }
10133
+ const offsetFrom = { ...fromPt };
10134
+ const offsetTo = { ...toPt };
10135
+ switch (fromDir) {
10136
+ case "right":
10137
+ offsetFrom.x += SYMBOL_SIZE;
10138
+ break;
10139
+ case "left":
10140
+ offsetFrom.x -= SYMBOL_SIZE;
10141
+ break;
10142
+ case "down":
10143
+ offsetFrom.y += SYMBOL_SIZE;
10144
+ break;
10145
+ case "up":
10146
+ offsetFrom.y -= SYMBOL_SIZE;
10147
+ break;
10148
+ }
10149
+ switch (toDir) {
10150
+ case "right":
10151
+ offsetTo.x += SYMBOL_SIZE;
10152
+ break;
10153
+ case "left":
10154
+ offsetTo.x -= SYMBOL_SIZE;
10155
+ break;
10156
+ case "down":
10157
+ offsetTo.y += SYMBOL_SIZE;
10158
+ break;
10159
+ case "up":
10160
+ offsetTo.y -= SYMBOL_SIZE;
10161
+ break;
10162
+ }
10163
+ const adx = Math.abs(offsetTo.x - offsetFrom.x);
10164
+ const ady = Math.abs(offsetTo.y - offsetFrom.y);
10165
+ let linePath;
10166
+ if (adx > ady) {
10167
+ const off = adx * 0.4;
10168
+ const cp1x = offsetFrom.x + (offsetTo.x > offsetFrom.x ? off : -off);
10169
+ const cp2x = offsetTo.x + (offsetTo.x > offsetFrom.x ? -off : off);
10170
+ linePath = `M${offsetFrom.x},${offsetFrom.y} C${cp1x},${offsetFrom.y} ${cp2x},${offsetTo.y} ${offsetTo.x},${offsetTo.y}`;
10171
+ } else {
10172
+ const off = Math.max(ady * 0.4, 20);
10173
+ const cp1y = offsetFrom.y + (offsetTo.y > offsetFrom.y ? off : -off);
10174
+ const cp2y = offsetTo.y + (offsetTo.y > offsetFrom.y ? -off : off);
10175
+ linePath = `M${offsetFrom.x},${offsetFrom.y} C${offsetFrom.x},${cp1y} ${offsetTo.x},${cp2y} ${offsetTo.x},${offsetTo.y}`;
10176
+ }
10177
+ const startSymbol = getSymbolPath(type, "start", fromPt, fromDir);
10178
+ const endSymbol = getSymbolPath(type, "end", toPt, toDir);
10179
+ return { linePath, startSymbol, endSymbol, midX: (offsetFrom.x + offsetTo.x) / 2, midY: (offsetFrom.y + offsetTo.y) / 2 };
10180
+ }, [from, to, fromFieldProp, toFieldProp, type, schema, nodeRects, registryVersion]);
10181
+ if (!result) return null;
10182
+ return /* @__PURE__ */ jsxRuntime.jsxs("g", { "data-react-fancy-diagram-relation": "", children: [
10183
+ /* @__PURE__ */ jsxRuntime.jsx("path", { d: result.linePath, fill: "none", stroke: "#71717a", strokeWidth: 2 }),
10184
+ result.startSymbol && /* @__PURE__ */ jsxRuntime.jsx("path", { d: result.startSymbol, fill: "none", stroke: "#71717a", strokeWidth: 2 }),
10185
+ result.endSymbol && /* @__PURE__ */ jsxRuntime.jsx("path", { d: result.endSymbol, fill: "none", stroke: "#71717a", strokeWidth: 2 }),
10186
+ label && /* @__PURE__ */ jsxRuntime.jsx("foreignObject", { x: result.midX - 40, y: result.midY - 12, width: 80, height: 24, children: /* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex items-center justify-center text-xs text-zinc-500", children: label }) })
10187
+ ] });
9564
10188
  }
10189
+ DiagramRelation._isCanvasEdge = true;
9565
10190
  DiagramRelation.displayName = "DiagramRelation";
9566
10191
  var FORMAT_LABELS = {
9567
10192
  erd: "ERD",
@@ -9672,12 +10297,12 @@ DiagramToolbar.displayName = "DiagramToolbar";
9672
10297
 
9673
10298
  // src/components/Diagram/diagram.layout.ts
9674
10299
  var ENTITY_WIDTH = 220;
9675
- var HEADER_HEIGHT = 40;
9676
- var FIELD_HEIGHT = 28;
10300
+ var HEADER_HEIGHT2 = 40;
10301
+ var FIELD_HEIGHT2 = 28;
9677
10302
  var HORIZONTAL_GAP = 80;
9678
10303
  var VERTICAL_GAP = 60;
9679
10304
  function getEntityHeight(fieldCount) {
9680
- return HEADER_HEIGHT + Math.max(fieldCount, 1) * FIELD_HEIGHT;
10305
+ return HEADER_HEIGHT2 + Math.max(fieldCount, 1) * FIELD_HEIGHT2;
9681
10306
  }
9682
10307
  function computeDiagramLayout(schema) {
9683
10308
  const positions = /* @__PURE__ */ new Map();
@@ -9780,58 +10405,103 @@ function DiagramRoot({
9780
10405
  }) {
9781
10406
  const downloadableRef = react.useRef(downloadable);
9782
10407
  const importableRef = react.useRef(importable);
9783
- const resolvedSchema = react.useMemo(() => {
10408
+ const normalizedSchema = react.useMemo(() => {
9784
10409
  if (!schema) return { entities: [], relations: [] };
9785
- const layout = computeDiagramLayout(schema);
9786
- const entities = schema.entities.map((entity) => {
9787
- if (entity.x !== void 0 && entity.y !== void 0) return entity;
9788
- const pos = layout.get(entity.id);
9789
- return pos ? { ...entity, x: pos.x, y: pos.y } : entity;
9790
- });
9791
- return { entities, relations: schema.relations };
10410
+ const entities = schema.entities.map((e) => ({
10411
+ ...e,
10412
+ id: e.id ?? e.name
10413
+ }));
10414
+ const relations = schema.relations.map((r, i) => ({
10415
+ ...r,
10416
+ id: r.id ?? `rel-${i}`
10417
+ }));
10418
+ return { entities, relations };
9792
10419
  }, [schema]);
10420
+ const initialPositions = react.useMemo(() => {
10421
+ if (normalizedSchema.entities.length === 0) return /* @__PURE__ */ new Map();
10422
+ const layout = computeDiagramLayout(normalizedSchema);
10423
+ const positions = /* @__PURE__ */ new Map();
10424
+ for (const entity of normalizedSchema.entities) {
10425
+ if (entity.x !== void 0 && entity.y !== void 0) {
10426
+ positions.set(entity.id, { x: entity.x, y: entity.y });
10427
+ } else {
10428
+ const pos = layout.get(entity.id);
10429
+ positions.set(entity.id, pos ?? { x: 0, y: 0 });
10430
+ }
10431
+ }
10432
+ return positions;
10433
+ }, [normalizedSchema]);
10434
+ const computedDefaultViewport = react.useMemo(() => {
10435
+ if (defaultViewport) return defaultViewport;
10436
+ if (initialPositions.size === 0) return { panX: 0, panY: 0, zoom: 1 };
10437
+ let minX = Infinity, minY = Infinity, maxX = -Infinity, maxY = -Infinity;
10438
+ initialPositions.forEach((pos) => {
10439
+ minX = Math.min(minX, pos.x);
10440
+ minY = Math.min(minY, pos.y);
10441
+ maxX = Math.max(maxX, pos.x + 220);
10442
+ maxY = Math.max(maxY, pos.y + 200);
10443
+ });
10444
+ const padding = 40;
10445
+ const panX = -minX + padding;
10446
+ const panY = -minY + padding;
10447
+ return { panX, panY, zoom: 1 };
10448
+ }, [defaultViewport, initialPositions]);
10449
+ const [entityPositions, setEntityPositions] = react.useState(initialPositions);
10450
+ const handleEntityMove = react.useCallback((entityId, x, y) => {
10451
+ setEntityPositions((prev) => {
10452
+ const next = new Map(prev);
10453
+ next.set(entityId, { x, y });
10454
+ return next;
10455
+ });
10456
+ }, []);
9793
10457
  const ctx = react.useMemo(
9794
10458
  () => ({
9795
10459
  diagramType: type,
9796
- schema: resolvedSchema,
10460
+ schema: normalizedSchema,
9797
10461
  downloadableRef,
9798
10462
  importableRef,
9799
10463
  exportFormats,
9800
10464
  onImport
9801
10465
  }),
9802
- [type, resolvedSchema, exportFormats, onImport]
10466
+ [type, normalizedSchema, exportFormats, onImport]
9803
10467
  );
9804
10468
  return /* @__PURE__ */ jsxRuntime.jsx(DiagramContext.Provider, { value: ctx, children: /* @__PURE__ */ jsxRuntime.jsx("div", { "data-react-fancy-diagram": "", className: "relative h-full w-full", children: /* @__PURE__ */ jsxRuntime.jsxs(
9805
10469
  Canvas,
9806
10470
  {
9807
10471
  viewport,
9808
- defaultViewport,
10472
+ defaultViewport: computedDefaultViewport,
9809
10473
  onViewportChange,
9810
10474
  showGrid: true,
10475
+ fitOnMount: true,
9811
10476
  className: cn("h-full w-full", className),
9812
10477
  children: [
9813
- resolvedSchema.entities.map((entity) => /* @__PURE__ */ jsxRuntime.jsx(
9814
- DiagramEntity,
9815
- {
9816
- id: entity.id,
9817
- name: entity.name,
9818
- x: entity.x ?? 0,
9819
- y: entity.y ?? 0,
9820
- children: entity.fields?.map((field) => /* @__PURE__ */ jsxRuntime.jsx(
9821
- DiagramField,
9822
- {
9823
- name: field.name,
9824
- type: field.type,
9825
- primary: field.primary,
9826
- foreign: field.foreign,
9827
- nullable: field.nullable
9828
- },
9829
- field.name
9830
- ))
9831
- },
9832
- entity.id
9833
- )),
9834
- resolvedSchema.relations.map((rel) => /* @__PURE__ */ jsxRuntime.jsx(
10478
+ normalizedSchema.entities.map((entity) => {
10479
+ const pos = entityPositions.get(entity.id) ?? { x: 0, y: 0 };
10480
+ return /* @__PURE__ */ jsxRuntime.jsx(
10481
+ DiagramEntity,
10482
+ {
10483
+ id: entity.id,
10484
+ name: entity.name,
10485
+ x: pos.x,
10486
+ y: pos.y,
10487
+ draggable: true,
10488
+ onPositionChange: (nx, ny) => handleEntityMove(entity.id, nx, ny),
10489
+ children: entity.fields?.map((field) => /* @__PURE__ */ jsxRuntime.jsx(
10490
+ DiagramField,
10491
+ {
10492
+ name: field.name,
10493
+ type: field.type,
10494
+ primary: field.primary,
10495
+ foreign: field.foreign,
10496
+ nullable: field.nullable
10497
+ },
10498
+ field.name
10499
+ ))
10500
+ },
10501
+ entity.id
10502
+ );
10503
+ }),
10504
+ normalizedSchema.relations.map((rel) => /* @__PURE__ */ jsxRuntime.jsx(
9835
10505
  DiagramRelation,
9836
10506
  {
9837
10507
  from: rel.from,