gd-ui-library 1.0.21 → 1.0.22

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -67,6 +67,8 @@ __export(index_exports, {
67
67
  GanttChart: () => GanttChart,
68
68
  Grid: () => Grid,
69
69
  Hero: () => Hero,
70
+ IconSelect: () => IconSelect,
71
+ ImageCrop: () => ImageCrop,
70
72
  InfiniteScroll: () => InfiniteScroll,
71
73
  Input: () => Input,
72
74
  InputGroup: () => InputGroup,
@@ -5338,6 +5340,556 @@ var GanttChart = (0, import_react70.forwardRef)(
5338
5340
  );
5339
5341
  GanttChart.displayName = "GanttChart";
5340
5342
 
5343
+ // src/components/IconSelect/IconSelect.tsx
5344
+ var import_react71 = require("react");
5345
+ var Lucide = __toESM(require("lucide-react"));
5346
+ var import_lucide_react33 = require("lucide-react");
5347
+ var import_jsx_runtime70 = require("react/jsx-runtime");
5348
+ var BATCH_SIZE = 50;
5349
+ var EXCLUDED = /* @__PURE__ */ new Set(["createLucideIcon", "LucideIcon", "default"]);
5350
+ var IconSelect = (0, import_react71.forwardRef)(
5351
+ ({
5352
+ value,
5353
+ onChange,
5354
+ placeholder = "Select an icon...",
5355
+ label,
5356
+ error,
5357
+ description,
5358
+ disabled = false,
5359
+ className
5360
+ }, ref) => {
5361
+ const [open, setOpen] = (0, import_react71.useState)(false);
5362
+ const [search, setSearch] = (0, import_react71.useState)("");
5363
+ const [visibleCount, setVisibleCount] = (0, import_react71.useState)(BATCH_SIZE);
5364
+ const containerRef = (0, import_react71.useRef)(null);
5365
+ const listRef = (0, import_react71.useRef)(null);
5366
+ const searchRef = (0, import_react71.useRef)(null);
5367
+ const iconList = (0, import_react71.useMemo)(() => {
5368
+ return Object.entries(Lucide).filter(([name, item]) => {
5369
+ if (EXCLUDED.has(name) || name.endsWith("Icon")) return false;
5370
+ return typeof item === "function" || typeof item === "object" && item !== null;
5371
+ }).map(([name]) => name).sort((a, b) => a.localeCompare(b));
5372
+ }, []);
5373
+ const filtered = (0, import_react71.useMemo)(() => {
5374
+ if (!search.trim()) return iconList;
5375
+ const q = search.toLowerCase();
5376
+ return iconList.filter((n) => n.toLowerCase().includes(q));
5377
+ }, [search, iconList]);
5378
+ const visible = filtered.slice(0, visibleCount);
5379
+ const handleListScroll = () => {
5380
+ const el = listRef.current;
5381
+ if (!el) return;
5382
+ if (el.scrollTop + el.clientHeight >= el.scrollHeight - 40) {
5383
+ setVisibleCount((prev) => Math.min(prev + BATCH_SIZE, filtered.length));
5384
+ }
5385
+ };
5386
+ (0, import_react71.useEffect)(() => {
5387
+ const handler = (e) => {
5388
+ if (containerRef.current && !containerRef.current.contains(e.target)) {
5389
+ setOpen(false);
5390
+ }
5391
+ };
5392
+ if (open) {
5393
+ document.addEventListener("mousedown", handler);
5394
+ setTimeout(() => searchRef.current?.focus(), 50);
5395
+ }
5396
+ return () => document.removeEventListener("mousedown", handler);
5397
+ }, [open]);
5398
+ (0, import_react71.useEffect)(() => {
5399
+ setVisibleCount(BATCH_SIZE);
5400
+ }, [search]);
5401
+ const SelectedIcon = value ? Lucide[value] : null;
5402
+ const handleSelect = (name) => {
5403
+ onChange?.(name);
5404
+ setOpen(false);
5405
+ setSearch("");
5406
+ };
5407
+ const handleClear = (e) => {
5408
+ e.stopPropagation();
5409
+ onChange?.("");
5410
+ };
5411
+ return /* @__PURE__ */ (0, import_jsx_runtime70.jsxs)("div", { ref, className: cn("w-full space-y-1.5", className), children: [
5412
+ label && /* @__PURE__ */ (0, import_jsx_runtime70.jsx)("label", { className: cn("text-sm font-medium block", disabled ? "text-gray-400" : "text-gray-700"), children: label }),
5413
+ description && !error && /* @__PURE__ */ (0, import_jsx_runtime70.jsx)("p", { className: "text-xs text-gray-500", children: description }),
5414
+ /* @__PURE__ */ (0, import_jsx_runtime70.jsxs)("div", { ref: containerRef, className: "relative", children: [
5415
+ /* @__PURE__ */ (0, import_jsx_runtime70.jsxs)(
5416
+ "button",
5417
+ {
5418
+ type: "button",
5419
+ disabled,
5420
+ onClick: () => !disabled && setOpen((o) => !o),
5421
+ className: cn(
5422
+ "w-full flex items-center justify-between gap-2",
5423
+ "min-h-[40px] rounded-lg border border-gray-300 bg-white px-3.5 py-2 text-sm",
5424
+ "transition-all duration-150 outline-none shadow-sm cursor-pointer",
5425
+ "hover:border-gray-400",
5426
+ open && !error && "border-blue-500 ring-2 ring-blue-100",
5427
+ error && "border-red-500 ring-1 ring-red-500",
5428
+ disabled && "bg-gray-100 text-gray-400 cursor-not-allowed opacity-60"
5429
+ ),
5430
+ children: [
5431
+ /* @__PURE__ */ (0, import_jsx_runtime70.jsx)("div", { className: "flex items-center gap-2 min-w-0 flex-1", children: value && SelectedIcon ? /* @__PURE__ */ (0, import_jsx_runtime70.jsxs)(import_jsx_runtime70.Fragment, { children: [
5432
+ /* @__PURE__ */ (0, import_jsx_runtime70.jsx)(SelectedIcon, { className: "w-4 h-4 flex-shrink-0 text-gray-600" }),
5433
+ /* @__PURE__ */ (0, import_jsx_runtime70.jsx)("span", { className: "truncate text-gray-700", children: value })
5434
+ ] }) : /* @__PURE__ */ (0, import_jsx_runtime70.jsx)("span", { className: "text-gray-400 truncate", children: placeholder }) }),
5435
+ /* @__PURE__ */ (0, import_jsx_runtime70.jsxs)("div", { className: "flex items-center gap-1 flex-shrink-0", children: [
5436
+ value && !disabled && /* @__PURE__ */ (0, import_jsx_runtime70.jsx)(
5437
+ "span",
5438
+ {
5439
+ role: "button",
5440
+ onClick: handleClear,
5441
+ className: "p-0.5 rounded hover:bg-gray-100 text-gray-400 hover:text-gray-600 cursor-pointer",
5442
+ children: /* @__PURE__ */ (0, import_jsx_runtime70.jsx)(import_lucide_react33.X, { className: "w-3 h-3" })
5443
+ }
5444
+ ),
5445
+ /* @__PURE__ */ (0, import_jsx_runtime70.jsx)(import_lucide_react33.ChevronDown, { className: cn("w-4 h-4 text-gray-400 transition-transform", open && "rotate-180") })
5446
+ ] })
5447
+ ]
5448
+ }
5449
+ ),
5450
+ open && /* @__PURE__ */ (0, import_jsx_runtime70.jsxs)("div", { className: "absolute z-50 w-full mt-1 bg-white border border-gray-200 rounded-lg shadow-xl overflow-hidden", children: [
5451
+ /* @__PURE__ */ (0, import_jsx_runtime70.jsxs)("div", { className: "sticky top-0 bg-white border-b border-gray-100 p-2", children: [
5452
+ /* @__PURE__ */ (0, import_jsx_runtime70.jsxs)("div", { className: "relative", children: [
5453
+ /* @__PURE__ */ (0, import_jsx_runtime70.jsx)(import_lucide_react33.Search, { className: "absolute left-2.5 top-1/2 -translate-y-1/2 w-4 h-4 text-gray-400" }),
5454
+ /* @__PURE__ */ (0, import_jsx_runtime70.jsx)(
5455
+ "input",
5456
+ {
5457
+ ref: searchRef,
5458
+ type: "text",
5459
+ value: search,
5460
+ onChange: (e) => setSearch(e.target.value),
5461
+ placeholder: "Search icons...",
5462
+ className: "w-full pl-8 pr-3 py-1.5 text-sm border border-gray-200 rounded-md bg-gray-50 focus:outline-none focus:ring-1 focus:ring-blue-500 focus:border-blue-500"
5463
+ }
5464
+ )
5465
+ ] }),
5466
+ /* @__PURE__ */ (0, import_jsx_runtime70.jsxs)("p", { className: "text-xs text-gray-400 mt-1 pl-0.5", children: [
5467
+ filtered.length,
5468
+ " of ",
5469
+ iconList.length,
5470
+ " icons"
5471
+ ] })
5472
+ ] }),
5473
+ /* @__PURE__ */ (0, import_jsx_runtime70.jsxs)(
5474
+ "div",
5475
+ {
5476
+ ref: listRef,
5477
+ onScroll: handleListScroll,
5478
+ className: "overflow-y-auto max-h-60",
5479
+ children: [
5480
+ visible.length === 0 ? /* @__PURE__ */ (0, import_jsx_runtime70.jsxs)("div", { className: "px-4 py-6 text-center text-sm text-gray-500", children: [
5481
+ "No icons found for \u201C",
5482
+ search,
5483
+ "\u201D"
5484
+ ] }) : /* @__PURE__ */ (0, import_jsx_runtime70.jsx)("div", { className: "grid grid-cols-1 py-1", children: visible.map((name) => {
5485
+ const Icon = Lucide[name];
5486
+ const isSelected = name === value;
5487
+ return /* @__PURE__ */ (0, import_jsx_runtime70.jsxs)(
5488
+ "button",
5489
+ {
5490
+ type: "button",
5491
+ onClick: () => handleSelect(name),
5492
+ className: cn(
5493
+ "flex items-center gap-2.5 px-3 py-2 text-sm text-left w-full transition-colors",
5494
+ isSelected ? "bg-blue-50 text-blue-700" : "text-gray-700 hover:bg-gray-50"
5495
+ ),
5496
+ children: [
5497
+ Icon && /* @__PURE__ */ (0, import_jsx_runtime70.jsx)(Icon, { className: "w-4 h-4 flex-shrink-0" }),
5498
+ /* @__PURE__ */ (0, import_jsx_runtime70.jsx)("span", { className: "truncate", children: name }),
5499
+ isSelected && /* @__PURE__ */ (0, import_jsx_runtime70.jsx)("span", { className: "ml-auto text-blue-500", children: /* @__PURE__ */ (0, import_jsx_runtime70.jsx)(Lucide.Check, { className: "w-3.5 h-3.5" }) })
5500
+ ]
5501
+ },
5502
+ name
5503
+ );
5504
+ }) }),
5505
+ visibleCount < filtered.length && /* @__PURE__ */ (0, import_jsx_runtime70.jsx)("div", { className: "py-2 text-center text-xs text-gray-400", children: "Scroll for more\u2026" })
5506
+ ]
5507
+ }
5508
+ )
5509
+ ] })
5510
+ ] }),
5511
+ error && /* @__PURE__ */ (0, import_jsx_runtime70.jsxs)("p", { className: "text-xs text-red-500 flex items-center gap-1", children: [
5512
+ /* @__PURE__ */ (0, import_jsx_runtime70.jsx)(Lucide.AlertCircle, { className: "w-3.5 h-3.5" }),
5513
+ error
5514
+ ] })
5515
+ ] });
5516
+ }
5517
+ );
5518
+ IconSelect.displayName = "IconSelect";
5519
+
5520
+ // src/components/ImageCrop/ImageCrop.tsx
5521
+ var import_react72 = require("react");
5522
+ var import_lucide_react34 = require("lucide-react");
5523
+ var import_jsx_runtime71 = require("react/jsx-runtime");
5524
+ var clamp = (v, min, max) => Math.max(min, Math.min(max, v));
5525
+ var ImageCrop = (0, import_react72.forwardRef)(
5526
+ ({
5527
+ src,
5528
+ aspectRatio = null,
5529
+ onCrop,
5530
+ onCancel,
5531
+ className,
5532
+ outputWidth,
5533
+ outputHeight,
5534
+ showPreview = true
5535
+ }, ref) => {
5536
+ const canvasRef = (0, import_react72.useRef)(null);
5537
+ const previewRef = (0, import_react72.useRef)(null);
5538
+ const containerRef = (0, import_react72.useRef)(null);
5539
+ const imgRef = (0, import_react72.useRef)(null);
5540
+ const [cropBox, setCropBox] = (0, import_react72.useState)({ x: 0, y: 0, w: 0, h: 0 });
5541
+ const [zoom, setZoom] = (0, import_react72.useState)(1);
5542
+ const [imgOffset, setImgOffset] = (0, import_react72.useState)({ x: 0, y: 0 });
5543
+ const [imgSize, setImgSize] = (0, import_react72.useState)({ w: 0, h: 0 });
5544
+ const [loaded, setLoaded] = (0, import_react72.useState)(false);
5545
+ const dragging = (0, import_react72.useRef)(null);
5546
+ const dragStart = (0, import_react72.useRef)({ mx: 0, my: 0, bx: 0, by: 0, bw: 0, bh: 0, ox: 0, oy: 0 });
5547
+ (0, import_react72.useEffect)(() => {
5548
+ setLoaded(false);
5549
+ const img = new Image();
5550
+ img.crossOrigin = "anonymous";
5551
+ img.onload = () => {
5552
+ imgRef.current = img;
5553
+ setLoaded(true);
5554
+ };
5555
+ img.src = src;
5556
+ }, [src]);
5557
+ const draw = (0, import_react72.useCallback)(() => {
5558
+ const canvas = canvasRef.current;
5559
+ const img = imgRef.current;
5560
+ if (!canvas || !img) return;
5561
+ const ctx = canvas.getContext("2d");
5562
+ if (!ctx) return;
5563
+ ctx.clearRect(0, 0, canvas.width, canvas.height);
5564
+ ctx.drawImage(img, imgOffset.x, imgOffset.y, imgSize.w, imgSize.h);
5565
+ ctx.save();
5566
+ ctx.fillStyle = "rgba(0,0,0,0.5)";
5567
+ ctx.fillRect(0, 0, canvas.width, canvas.height);
5568
+ ctx.globalCompositeOperation = "destination-out";
5569
+ ctx.fillStyle = "rgba(0,0,0,1)";
5570
+ ctx.fillRect(cropBox.x, cropBox.y, cropBox.w, cropBox.h);
5571
+ ctx.restore();
5572
+ ctx.save();
5573
+ ctx.beginPath();
5574
+ ctx.rect(cropBox.x, cropBox.y, cropBox.w, cropBox.h);
5575
+ ctx.clip();
5576
+ ctx.drawImage(img, imgOffset.x, imgOffset.y, imgSize.w, imgSize.h);
5577
+ ctx.restore();
5578
+ ctx.strokeStyle = "rgba(255,255,255,0.9)";
5579
+ ctx.lineWidth = 1.5;
5580
+ ctx.strokeRect(cropBox.x, cropBox.y, cropBox.w, cropBox.h);
5581
+ ctx.strokeStyle = "rgba(255,255,255,0.25)";
5582
+ ctx.lineWidth = 0.5;
5583
+ for (let i = 1; i < 3; i++) {
5584
+ const tx = cropBox.x + cropBox.w / 3 * i;
5585
+ const ty = cropBox.y + cropBox.h / 3 * i;
5586
+ ctx.beginPath();
5587
+ ctx.moveTo(tx, cropBox.y);
5588
+ ctx.lineTo(tx, cropBox.y + cropBox.h);
5589
+ ctx.stroke();
5590
+ ctx.beginPath();
5591
+ ctx.moveTo(cropBox.x, ty);
5592
+ ctx.lineTo(cropBox.x + cropBox.w, ty);
5593
+ ctx.stroke();
5594
+ }
5595
+ const hs = 8;
5596
+ ctx.fillStyle = "white";
5597
+ ctx.strokeStyle = "#3B82F6";
5598
+ ctx.lineWidth = 1.5;
5599
+ [
5600
+ [cropBox.x, cropBox.y],
5601
+ [cropBox.x + cropBox.w, cropBox.y],
5602
+ [cropBox.x, cropBox.y + cropBox.h],
5603
+ [cropBox.x + cropBox.w, cropBox.y + cropBox.h]
5604
+ ].forEach(([hx, hy]) => {
5605
+ ctx.fillRect(hx - hs / 2, hy - hs / 2, hs, hs);
5606
+ ctx.strokeRect(hx - hs / 2, hy - hs / 2, hs, hs);
5607
+ });
5608
+ drawPreview();
5609
+ }, [cropBox, imgOffset, imgSize]);
5610
+ const drawPreview = (0, import_react72.useCallback)(() => {
5611
+ const preview = previewRef.current;
5612
+ const img = imgRef.current;
5613
+ if (!preview || !img || !showPreview) return;
5614
+ const ctx = preview.getContext("2d");
5615
+ if (!ctx) return;
5616
+ const canvas = canvasRef.current;
5617
+ if (!canvas) return;
5618
+ const scaleX = img.naturalWidth / imgSize.w;
5619
+ const scaleY = img.naturalHeight / imgSize.h;
5620
+ const srcX = (cropBox.x - imgOffset.x) * scaleX;
5621
+ const srcY = (cropBox.y - imgOffset.y) * scaleY;
5622
+ const srcW = cropBox.w * scaleX;
5623
+ const srcH = cropBox.h * scaleY;
5624
+ ctx.clearRect(0, 0, preview.width, preview.height);
5625
+ if (srcW > 0 && srcH > 0) {
5626
+ ctx.drawImage(img, srcX, srcY, srcW, srcH, 0, 0, preview.width, preview.height);
5627
+ }
5628
+ }, [cropBox, imgOffset, imgSize, showPreview]);
5629
+ (0, import_react72.useEffect)(() => {
5630
+ if (!loaded || !imgRef.current) return;
5631
+ const img = imgRef.current;
5632
+ const container = containerRef.current;
5633
+ if (!container) return;
5634
+ const canvas = canvasRef.current;
5635
+ if (!canvas) return;
5636
+ const cw = container.clientWidth;
5637
+ const ch = Math.round(cw * 0.65);
5638
+ canvas.width = cw;
5639
+ canvas.height = ch;
5640
+ const scale = Math.min(cw / img.naturalWidth, ch / img.naturalHeight) * zoom;
5641
+ const iw = img.naturalWidth * scale;
5642
+ const ih = img.naturalHeight * scale;
5643
+ const ox = (cw - iw) / 2;
5644
+ const oy = (ch - ih) / 2;
5645
+ setImgSize({ w: iw, h: ih });
5646
+ setImgOffset({ x: ox, y: oy });
5647
+ const pad = 0.1;
5648
+ let bw = iw * (1 - 2 * pad);
5649
+ let bh = aspectRatio ? bw / aspectRatio : ih * (1 - 2 * pad);
5650
+ if (aspectRatio && bh > ih * (1 - 2 * pad)) {
5651
+ bh = ih * (1 - 2 * pad);
5652
+ bw = bh * aspectRatio;
5653
+ }
5654
+ const bx = ox + (iw - bw) / 2;
5655
+ const by = oy + (ih - bh) / 2;
5656
+ setCropBox({ x: bx, y: by, w: bw, h: bh });
5657
+ }, [loaded, zoom]);
5658
+ (0, import_react72.useEffect)(() => {
5659
+ draw();
5660
+ }, [draw]);
5661
+ const getHandle = (mx, my) => {
5662
+ const hs = 12;
5663
+ const { x, y, w, h } = cropBox;
5664
+ if (Math.abs(mx - x) < hs && Math.abs(my - y) < hs) return "nw";
5665
+ if (Math.abs(mx - (x + w)) < hs && Math.abs(my - y) < hs) return "ne";
5666
+ if (Math.abs(mx - x) < hs && Math.abs(my - (y + h)) < hs) return "sw";
5667
+ if (Math.abs(mx - (x + w)) < hs && Math.abs(my - (y + h)) < hs) return "se";
5668
+ if (mx > x && mx < x + w && my > y && my < y + h) return "move";
5669
+ return null;
5670
+ };
5671
+ const canvasCoords = (e) => {
5672
+ const rect = canvasRef.current.getBoundingClientRect();
5673
+ return { mx: e.clientX - rect.left, my: e.clientY - rect.top };
5674
+ };
5675
+ const onMouseDown = (e) => {
5676
+ const { mx, my } = canvasCoords(e);
5677
+ const h = getHandle(mx, my);
5678
+ if (h) {
5679
+ dragging.current = h;
5680
+ dragStart.current = {
5681
+ mx,
5682
+ my,
5683
+ bx: cropBox.x,
5684
+ by: cropBox.y,
5685
+ bw: cropBox.w,
5686
+ bh: cropBox.h,
5687
+ ox: imgOffset.x,
5688
+ oy: imgOffset.y
5689
+ };
5690
+ }
5691
+ };
5692
+ const onMouseMove = (e) => {
5693
+ if (!dragging.current) return;
5694
+ const { mx, my } = canvasCoords(e);
5695
+ const dx = mx - dragStart.current.mx;
5696
+ const dy = my - dragStart.current.my;
5697
+ const { bx, by, bw, bh, ox, oy } = dragStart.current;
5698
+ const canvas = canvasRef.current;
5699
+ if (!canvas) return;
5700
+ const minSize = 20;
5701
+ setCropBox((prev) => {
5702
+ let { x, y, w, h } = { x: bx, y: by, w: bw, h: bh };
5703
+ const applyAspect = (nw, nh) => {
5704
+ if (!aspectRatio) return { nw, nh };
5705
+ return { nw, nh: nw / aspectRatio };
5706
+ };
5707
+ if (dragging.current === "move") {
5708
+ x = clamp(bx + dx, imgOffset.x, imgOffset.x + imgSize.w - w);
5709
+ y = clamp(by + dy, imgOffset.y, imgOffset.y + imgSize.h - h);
5710
+ } else if (dragging.current === "se") {
5711
+ let nw = Math.max(minSize, bw + dx);
5712
+ let nh = Math.max(minSize, bh + dy);
5713
+ ({ nw, nh } = applyAspect(nw, nh));
5714
+ w = Math.min(nw, imgOffset.x + imgSize.w - x);
5715
+ h = Math.min(nh, imgOffset.y + imgSize.h - y);
5716
+ } else if (dragging.current === "sw") {
5717
+ let nw = Math.max(minSize, bw - dx);
5718
+ let nh = Math.max(minSize, bh + dy);
5719
+ ({ nw, nh } = applyAspect(nw, nh));
5720
+ x = Math.max(imgOffset.x, bx + bw - nw);
5721
+ w = bx + bw - x;
5722
+ h = Math.min(nh, imgOffset.y + imgSize.h - y);
5723
+ } else if (dragging.current === "ne") {
5724
+ let nw = Math.max(minSize, bw + dx);
5725
+ let nh = Math.max(minSize, bh - dy);
5726
+ ({ nw, nh } = applyAspect(nw, nh));
5727
+ y = Math.max(imgOffset.y, by + bh - nh);
5728
+ w = Math.min(nw, imgOffset.x + imgSize.w - x);
5729
+ h = by + bh - y;
5730
+ } else if (dragging.current === "nw") {
5731
+ let nw = Math.max(minSize, bw - dx);
5732
+ let nh = Math.max(minSize, bh - dy);
5733
+ ({ nw, nh } = applyAspect(nw, nh));
5734
+ x = Math.max(imgOffset.x, bx + bw - nw);
5735
+ y = Math.max(imgOffset.y, by + bh - nh);
5736
+ w = bx + bw - x;
5737
+ h = by + bh - y;
5738
+ }
5739
+ return { x, y, w: Math.max(minSize, w), h: Math.max(minSize, h) };
5740
+ });
5741
+ };
5742
+ const onMouseUp = () => {
5743
+ dragging.current = null;
5744
+ };
5745
+ const [cursor, setCursor] = (0, import_react72.useState)("default");
5746
+ const onMouseMoveCursor = (e) => {
5747
+ const { mx, my } = canvasCoords(e);
5748
+ const h = getHandle(mx, my);
5749
+ const map = {
5750
+ nw: "nw-resize",
5751
+ ne: "ne-resize",
5752
+ sw: "sw-resize",
5753
+ se: "se-resize",
5754
+ move: "move",
5755
+ img: "grab"
5756
+ };
5757
+ setCursor(h ? map[h] : "default");
5758
+ onMouseMove(e);
5759
+ };
5760
+ const doCrop = (0, import_react72.useCallback)(() => {
5761
+ const img = imgRef.current;
5762
+ if (!img || !onCrop) return;
5763
+ const scaleX = img.naturalWidth / imgSize.w;
5764
+ const scaleY = img.naturalHeight / imgSize.h;
5765
+ const srcX = (cropBox.x - imgOffset.x) * scaleX;
5766
+ const srcY = (cropBox.y - imgOffset.y) * scaleY;
5767
+ const srcW = cropBox.w * scaleX;
5768
+ const srcH = cropBox.h * scaleY;
5769
+ const out = document.createElement("canvas");
5770
+ out.width = outputWidth ?? Math.round(srcW);
5771
+ out.height = outputHeight ?? Math.round(srcH);
5772
+ const ctx = out.getContext("2d");
5773
+ ctx.drawImage(img, srcX, srcY, srcW, srcH, 0, 0, out.width, out.height);
5774
+ out.toBlob((blob) => {
5775
+ if (blob) {
5776
+ const cropArea = {
5777
+ x: srcX / img.naturalWidth,
5778
+ y: srcY / img.naturalHeight,
5779
+ width: srcW / img.naturalWidth,
5780
+ height: srcH / img.naturalHeight
5781
+ };
5782
+ onCrop(blob, cropArea);
5783
+ }
5784
+ }, "image/png");
5785
+ }, [cropBox, imgOffset, imgSize, outputWidth, outputHeight, onCrop]);
5786
+ const doReset = (0, import_react72.useCallback)(() => {
5787
+ setZoom(1);
5788
+ setLoaded(false);
5789
+ setTimeout(() => setLoaded(true), 10);
5790
+ }, []);
5791
+ (0, import_react72.useImperativeHandle)(ref, () => ({ crop: doCrop, reset: doReset }));
5792
+ const handleZoom = (delta) => {
5793
+ setZoom((z) => clamp(z + delta, 0.3, 3));
5794
+ };
5795
+ return /* @__PURE__ */ (0, import_jsx_runtime71.jsxs)("div", { className: cn("flex flex-col gap-3 w-full", className), children: [
5796
+ /* @__PURE__ */ (0, import_jsx_runtime71.jsxs)("div", { ref: containerRef, className: "relative w-full rounded-xl overflow-hidden bg-gray-900 shadow-inner", children: [
5797
+ /* @__PURE__ */ (0, import_jsx_runtime71.jsx)(
5798
+ "canvas",
5799
+ {
5800
+ ref: canvasRef,
5801
+ className: "w-full block",
5802
+ style: { cursor },
5803
+ onMouseDown,
5804
+ onMouseMove: onMouseMoveCursor,
5805
+ onMouseUp,
5806
+ onMouseLeave: onMouseUp
5807
+ }
5808
+ ),
5809
+ !loaded && /* @__PURE__ */ (0, import_jsx_runtime71.jsx)("div", { className: "absolute inset-0 flex items-center justify-center", children: /* @__PURE__ */ (0, import_jsx_runtime71.jsx)("div", { className: "w-8 h-8 border-4 border-blue-500 border-t-transparent rounded-full animate-spin" }) })
5810
+ ] }),
5811
+ /* @__PURE__ */ (0, import_jsx_runtime71.jsxs)("div", { className: "flex items-center gap-3 flex-wrap", children: [
5812
+ /* @__PURE__ */ (0, import_jsx_runtime71.jsxs)("div", { className: "flex items-center gap-1.5 bg-gray-100 rounded-lg p-1", children: [
5813
+ /* @__PURE__ */ (0, import_jsx_runtime71.jsx)(
5814
+ "button",
5815
+ {
5816
+ type: "button",
5817
+ onClick: () => handleZoom(-0.1),
5818
+ className: "w-7 h-7 flex items-center justify-center rounded-md hover:bg-white transition-colors text-gray-600",
5819
+ title: "Zoom out",
5820
+ children: /* @__PURE__ */ (0, import_jsx_runtime71.jsx)(import_lucide_react34.ZoomOut, { className: "w-4 h-4" })
5821
+ }
5822
+ ),
5823
+ /* @__PURE__ */ (0, import_jsx_runtime71.jsxs)("span", { className: "text-xs font-medium text-gray-600 w-10 text-center select-none", children: [
5824
+ Math.round(zoom * 100),
5825
+ "%"
5826
+ ] }),
5827
+ /* @__PURE__ */ (0, import_jsx_runtime71.jsx)(
5828
+ "button",
5829
+ {
5830
+ type: "button",
5831
+ onClick: () => handleZoom(0.1),
5832
+ className: "w-7 h-7 flex items-center justify-center rounded-md hover:bg-white transition-colors text-gray-600",
5833
+ title: "Zoom in",
5834
+ children: /* @__PURE__ */ (0, import_jsx_runtime71.jsx)(import_lucide_react34.ZoomIn, { className: "w-4 h-4" })
5835
+ }
5836
+ )
5837
+ ] }),
5838
+ /* @__PURE__ */ (0, import_jsx_runtime71.jsxs)(
5839
+ "button",
5840
+ {
5841
+ type: "button",
5842
+ onClick: doReset,
5843
+ className: "flex items-center gap-1.5 px-3 py-1.5 text-sm rounded-lg border border-gray-300 bg-white hover:bg-gray-50 text-gray-600 transition-colors",
5844
+ children: [
5845
+ /* @__PURE__ */ (0, import_jsx_runtime71.jsx)(import_lucide_react34.RotateCcw, { className: "w-3.5 h-3.5" }),
5846
+ "Reset"
5847
+ ]
5848
+ }
5849
+ ),
5850
+ /* @__PURE__ */ (0, import_jsx_runtime71.jsx)("div", { className: "flex-1" }),
5851
+ onCancel && /* @__PURE__ */ (0, import_jsx_runtime71.jsxs)(
5852
+ "button",
5853
+ {
5854
+ type: "button",
5855
+ onClick: onCancel,
5856
+ className: "flex items-center gap-1.5 px-4 py-1.5 text-sm rounded-lg border border-gray-300 bg-white hover:bg-gray-50 text-gray-600 transition-colors",
5857
+ children: [
5858
+ /* @__PURE__ */ (0, import_jsx_runtime71.jsx)(import_lucide_react34.X, { className: "w-4 h-4" }),
5859
+ "Cancel"
5860
+ ]
5861
+ }
5862
+ ),
5863
+ onCrop && /* @__PURE__ */ (0, import_jsx_runtime71.jsxs)(
5864
+ "button",
5865
+ {
5866
+ type: "button",
5867
+ onClick: doCrop,
5868
+ className: "flex items-center gap-1.5 px-4 py-1.5 text-sm rounded-lg bg-blue-600 hover:bg-blue-700 text-white font-medium transition-colors shadow-sm",
5869
+ children: [
5870
+ /* @__PURE__ */ (0, import_jsx_runtime71.jsx)(import_lucide_react34.Check, { className: "w-4 h-4" }),
5871
+ "Crop"
5872
+ ]
5873
+ }
5874
+ )
5875
+ ] }),
5876
+ showPreview && /* @__PURE__ */ (0, import_jsx_runtime71.jsxs)("div", { className: "flex items-start gap-3", children: [
5877
+ /* @__PURE__ */ (0, import_jsx_runtime71.jsx)("span", { className: "text-xs text-gray-500 font-medium mt-1", children: "Preview" }),
5878
+ /* @__PURE__ */ (0, import_jsx_runtime71.jsx)("div", { className: "rounded-lg overflow-hidden border border-gray-200 shadow-sm bg-gray-50", children: /* @__PURE__ */ (0, import_jsx_runtime71.jsx)(
5879
+ "canvas",
5880
+ {
5881
+ ref: previewRef,
5882
+ width: 120,
5883
+ height: aspectRatio ? Math.round(120 / aspectRatio) : 80,
5884
+ className: "block"
5885
+ }
5886
+ ) })
5887
+ ] })
5888
+ ] });
5889
+ }
5890
+ );
5891
+ ImageCrop.displayName = "ImageCrop";
5892
+
5341
5893
  // src/validators/email.ts
5342
5894
  var validateEmail = (value) => {
5343
5895
  if (!value) return "Email is required";
@@ -5501,6 +6053,8 @@ var validateUrl = (value) => {
5501
6053
  GanttChart,
5502
6054
  Grid,
5503
6055
  Hero,
6056
+ IconSelect,
6057
+ ImageCrop,
5504
6058
  InfiniteScroll,
5505
6059
  Input,
5506
6060
  InputGroup,