@janovix/blocks 1.0.0-rc.2 → 1.0.0-rc.4

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
@@ -1,11 +1,17 @@
1
1
  import { useTheme } from 'next-themes';
2
- import { CheckIcon, CircleIcon, ChevronRightIcon, Monitor, Sun, Moon, Languages } from 'lucide-react';
2
+ import { CheckIcon, CircleIcon, ChevronRightIcon, Monitor, Sun, Moon, Languages, Upload, Move, ZoomOut, ZoomIn, RotateCcw, RotateCw, Grid3X3, RefreshCw, X, XIcon, User, Pencil, Check, Loader2, Bell, CheckCheck, XCircle, AlertTriangle, CheckCircle, Info, Trash2 } from 'lucide-react';
3
3
  import { AnimatePresence, motion } from 'motion/react';
4
4
  import * as React32 from 'react';
5
- import React32__default, { useLayoutEffect, useState, useCallback, useEffect } from 'react';
5
+ import React32__default, { useLayoutEffect, useState, useCallback, useEffect, useRef } from 'react';
6
6
  import { jsx, jsxs, Fragment } from 'react/jsx-runtime';
7
7
  import * as ReactDOM from 'react-dom';
8
8
  import ReactDOM__default from 'react-dom';
9
+ import * as SliderPrimitive from '@radix-ui/react-slider';
10
+ import * as TogglePrimitive from '@radix-ui/react-toggle';
11
+ import * as DialogPrimitive from '@radix-ui/react-dialog';
12
+ import { Drawer as Drawer$1 } from 'vaul';
13
+ import * as PopoverPrimitive from '@radix-ui/react-popover';
14
+ import * as ScrollAreaPrimitive from '@radix-ui/react-scroll-area';
9
15
 
10
16
  function composeEventHandlers(originalEventHandler, ourEventHandler, { checkForDefaultPrevented = true } = {}) {
11
17
  return function handleEvent(event) {
@@ -9507,7 +9513,1530 @@ function LanguageSwitcher({
9507
9513
  }
9508
9514
  );
9509
9515
  }
9516
+ function Slider({
9517
+ className,
9518
+ defaultValue,
9519
+ value,
9520
+ min: min2 = 0,
9521
+ max: max2 = 100,
9522
+ ...props
9523
+ }) {
9524
+ const _values = React32.useMemo(
9525
+ () => Array.isArray(value) ? value : Array.isArray(defaultValue) ? defaultValue : [min2, max2],
9526
+ [value, defaultValue, min2, max2]
9527
+ );
9528
+ return /* @__PURE__ */ jsxs(
9529
+ SliderPrimitive.Root,
9530
+ {
9531
+ "data-slot": "slider",
9532
+ defaultValue,
9533
+ value,
9534
+ min: min2,
9535
+ max: max2,
9536
+ className: cn(
9537
+ "relative flex w-full touch-none items-center select-none data-[disabled]:opacity-50 data-[orientation=vertical]:h-full data-[orientation=vertical]:min-h-44 data-[orientation=vertical]:w-auto data-[orientation=vertical]:flex-col",
9538
+ className
9539
+ ),
9540
+ ...props,
9541
+ children: [
9542
+ /* @__PURE__ */ jsx(
9543
+ SliderPrimitive.Track,
9544
+ {
9545
+ "data-slot": "slider-track",
9546
+ className: cn(
9547
+ "bg-muted relative grow overflow-hidden rounded-full data-[orientation=horizontal]:h-1.5 data-[orientation=horizontal]:w-full data-[orientation=vertical]:h-full data-[orientation=vertical]:w-1.5"
9548
+ ),
9549
+ children: /* @__PURE__ */ jsx(
9550
+ SliderPrimitive.Range,
9551
+ {
9552
+ "data-slot": "slider-range",
9553
+ className: cn(
9554
+ "bg-primary absolute data-[orientation=horizontal]:h-full data-[orientation=vertical]:w-full"
9555
+ )
9556
+ }
9557
+ )
9558
+ }
9559
+ ),
9560
+ Array.from({ length: _values.length }, (_, index2) => /* @__PURE__ */ jsx(
9561
+ SliderPrimitive.Thumb,
9562
+ {
9563
+ "data-slot": "slider-thumb",
9564
+ className: "border-primary ring-ring/50 block size-4 shrink-0 rounded-full border bg-white shadow-sm transition-[color,box-shadow] hover:ring-4 focus-visible:ring-4 focus-visible:outline-hidden disabled:pointer-events-none disabled:opacity-50"
9565
+ },
9566
+ index2
9567
+ ))
9568
+ ]
9569
+ }
9570
+ );
9571
+ }
9572
+ var toggleVariants = cva(
9573
+ "inline-flex items-center justify-center gap-2 rounded-md text-sm font-medium hover:bg-muted hover:text-muted-foreground disabled:pointer-events-none disabled:opacity-50 data-[state=on]:bg-accent data-[state=on]:text-accent-foreground [&_svg]:pointer-events-none [&_svg:not([class*='size-'])]:size-4 [&_svg]:shrink-0 focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:ring-[3px] outline-none transition-[color,box-shadow] aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive whitespace-nowrap",
9574
+ {
9575
+ variants: {
9576
+ variant: {
9577
+ default: "bg-transparent",
9578
+ outline: "border border-input bg-transparent shadow-xs hover:bg-accent hover:text-accent-foreground"
9579
+ },
9580
+ size: {
9581
+ default: "h-9 px-2 min-w-9",
9582
+ sm: "h-8 px-1.5 min-w-8",
9583
+ lg: "h-10 px-2.5 min-w-10"
9584
+ }
9585
+ },
9586
+ defaultVariants: {
9587
+ variant: "default",
9588
+ size: "default"
9589
+ }
9590
+ }
9591
+ );
9592
+ function Toggle({
9593
+ className,
9594
+ variant,
9595
+ size: size4,
9596
+ ...props
9597
+ }) {
9598
+ return /* @__PURE__ */ jsx(
9599
+ TogglePrimitive.Root,
9600
+ {
9601
+ "data-slot": "toggle",
9602
+ className: cn(toggleVariants({ variant, size: size4, className })),
9603
+ ...props
9604
+ }
9605
+ );
9606
+ }
9607
+ function AvatarEditor({
9608
+ value,
9609
+ onChange,
9610
+ size: size4 = 280,
9611
+ showGrid: initialShowGrid = false,
9612
+ outputSize = 256,
9613
+ outputFormat = "png",
9614
+ outputQuality = 0.92,
9615
+ className,
9616
+ controlSize = "default",
9617
+ defaultImage,
9618
+ initials: _initials
9619
+ }) {
9620
+ const canvasRef = useRef(null);
9621
+ const fileInputRef = useRef(null);
9622
+ const containerRef = useRef(null);
9623
+ const imageSource = value ?? defaultImage;
9624
+ const [image, setImage] = useState(null);
9625
+ const [imageLoaded, setImageLoaded] = useState(false);
9626
+ const [zoom, setZoom] = useState(1);
9627
+ const [rotation, setRotation] = useState(0);
9628
+ const [position, setPosition] = useState({ x: 0, y: 0 });
9629
+ const [isDragging, setIsDragging] = useState(false);
9630
+ const [dragStart, setDragStart] = useState({ x: 0, y: 0 });
9631
+ const [showGrid, setShowGrid] = useState(initialShowGrid);
9632
+ useEffect(() => {
9633
+ if (imageSource && !imageSource.startsWith("data:")) {
9634
+ const img = new Image();
9635
+ img.crossOrigin = "anonymous";
9636
+ img.onload = () => {
9637
+ setImage(img);
9638
+ setImageLoaded(true);
9639
+ setZoom(1);
9640
+ setRotation(0);
9641
+ setPosition({ x: 0, y: 0 });
9642
+ };
9643
+ img.src = imageSource;
9644
+ }
9645
+ }, [imageSource]);
9646
+ const generateOutput = useCallback(() => {
9647
+ if (!image) return null;
9648
+ const outputCanvas = document.createElement("canvas");
9649
+ outputCanvas.width = outputSize;
9650
+ outputCanvas.height = outputSize;
9651
+ const ctx = outputCanvas.getContext("2d");
9652
+ if (!ctx) return null;
9653
+ ctx.imageSmoothingEnabled = true;
9654
+ ctx.imageSmoothingQuality = "high";
9655
+ if (outputFormat === "jpeg") {
9656
+ ctx.fillStyle = "#ffffff";
9657
+ ctx.fillRect(0, 0, outputSize, outputSize);
9658
+ }
9659
+ ctx.save();
9660
+ ctx.translate(outputSize / 2, outputSize / 2);
9661
+ ctx.rotate(rotation * Math.PI / 180);
9662
+ const scale = zoom;
9663
+ const imgAspect = image.width / image.height;
9664
+ let drawWidth, drawHeight;
9665
+ if (imgAspect > 1) {
9666
+ drawHeight = outputSize * scale;
9667
+ drawWidth = drawHeight * imgAspect;
9668
+ } else {
9669
+ drawWidth = outputSize * scale;
9670
+ drawHeight = drawWidth / imgAspect;
9671
+ }
9672
+ const positionScale = outputSize / size4;
9673
+ const scaledPosX = position.x * positionScale;
9674
+ const scaledPosY = position.y * positionScale;
9675
+ ctx.drawImage(
9676
+ image,
9677
+ -drawWidth / 2 + scaledPosX,
9678
+ -drawHeight / 2 + scaledPosY,
9679
+ drawWidth,
9680
+ drawHeight
9681
+ );
9682
+ ctx.restore();
9683
+ const mimeType = `image/${outputFormat}`;
9684
+ return outputCanvas.toDataURL(mimeType, outputQuality);
9685
+ }, [
9686
+ image,
9687
+ outputSize,
9688
+ outputFormat,
9689
+ outputQuality,
9690
+ zoom,
9691
+ rotation,
9692
+ position,
9693
+ size4
9694
+ ]);
9695
+ useEffect(() => {
9696
+ if (!canvasRef.current || !image || !imageLoaded) return;
9697
+ const canvas = canvasRef.current;
9698
+ const ctx = canvas.getContext("2d");
9699
+ if (!ctx) return;
9700
+ const displaySize = size4;
9701
+ canvas.width = displaySize;
9702
+ canvas.height = displaySize;
9703
+ ctx.clearRect(0, 0, displaySize, displaySize);
9704
+ ctx.fillStyle = "#1a1a2e";
9705
+ ctx.fillRect(0, 0, displaySize, displaySize);
9706
+ ctx.save();
9707
+ ctx.translate(displaySize / 2, displaySize / 2);
9708
+ ctx.rotate(rotation * Math.PI / 180);
9709
+ const scale = zoom;
9710
+ const imgAspect = image.width / image.height;
9711
+ let drawWidth, drawHeight;
9712
+ if (imgAspect > 1) {
9713
+ drawHeight = displaySize * scale;
9714
+ drawWidth = drawHeight * imgAspect;
9715
+ } else {
9716
+ drawWidth = displaySize * scale;
9717
+ drawHeight = drawWidth / imgAspect;
9718
+ }
9719
+ ctx.drawImage(
9720
+ image,
9721
+ -drawWidth / 2 + position.x,
9722
+ -drawHeight / 2 + position.y,
9723
+ drawWidth,
9724
+ drawHeight
9725
+ );
9726
+ ctx.restore();
9727
+ ctx.globalCompositeOperation = "destination-in";
9728
+ ctx.beginPath();
9729
+ ctx.arc(
9730
+ displaySize / 2,
9731
+ displaySize / 2,
9732
+ displaySize / 2 - 4,
9733
+ 0,
9734
+ Math.PI * 2
9735
+ );
9736
+ ctx.fill();
9737
+ ctx.globalCompositeOperation = "source-over";
9738
+ ctx.strokeStyle = "hsl(var(--primary))";
9739
+ ctx.lineWidth = 3;
9740
+ ctx.beginPath();
9741
+ ctx.arc(
9742
+ displaySize / 2,
9743
+ displaySize / 2,
9744
+ displaySize / 2 - 2,
9745
+ 0,
9746
+ Math.PI * 2
9747
+ );
9748
+ ctx.stroke();
9749
+ const dataUrl = generateOutput();
9750
+ if (dataUrl) {
9751
+ onChange?.(dataUrl);
9752
+ }
9753
+ }, [
9754
+ image,
9755
+ imageLoaded,
9756
+ zoom,
9757
+ rotation,
9758
+ position,
9759
+ size4,
9760
+ generateOutput,
9761
+ onChange
9762
+ ]);
9763
+ const resetTransforms = useCallback(() => {
9764
+ setZoom(1);
9765
+ setRotation(0);
9766
+ setPosition({ x: 0, y: 0 });
9767
+ }, []);
9768
+ const handleFileSelect = useCallback(
9769
+ (e) => {
9770
+ const file = e.target.files?.[0];
9771
+ if (!file) return;
9772
+ const reader = new FileReader();
9773
+ reader.onload = (event) => {
9774
+ const img = new Image();
9775
+ img.crossOrigin = "anonymous";
9776
+ img.onload = () => {
9777
+ setImage(img);
9778
+ setImageLoaded(true);
9779
+ resetTransforms();
9780
+ };
9781
+ img.src = event.target?.result;
9782
+ };
9783
+ reader.readAsDataURL(file);
9784
+ },
9785
+ [resetTransforms]
9786
+ );
9787
+ const handleMouseDown = useCallback(
9788
+ (e) => {
9789
+ if (!imageLoaded) return;
9790
+ setIsDragging(true);
9791
+ setDragStart({ x: e.clientX - position.x, y: e.clientY - position.y });
9792
+ },
9793
+ [imageLoaded, position]
9794
+ );
9795
+ const handleMouseMove = useCallback(
9796
+ (e) => {
9797
+ if (!isDragging) return;
9798
+ setPosition({
9799
+ x: e.clientX - dragStart.x,
9800
+ y: e.clientY - dragStart.y
9801
+ });
9802
+ },
9803
+ [isDragging, dragStart]
9804
+ );
9805
+ const handleMouseUp = useCallback(() => {
9806
+ setIsDragging(false);
9807
+ }, []);
9808
+ const handleTouchStart = useCallback(
9809
+ (e) => {
9810
+ if (!imageLoaded) return;
9811
+ const touch = e.touches[0];
9812
+ setIsDragging(true);
9813
+ setDragStart({
9814
+ x: touch.clientX - position.x,
9815
+ y: touch.clientY - position.y
9816
+ });
9817
+ },
9818
+ [imageLoaded, position]
9819
+ );
9820
+ const handleTouchMove = useCallback(
9821
+ (e) => {
9822
+ if (!isDragging) return;
9823
+ const touch = e.touches[0];
9824
+ setPosition({
9825
+ x: touch.clientX - dragStart.x,
9826
+ y: touch.clientY - dragStart.y
9827
+ });
9828
+ },
9829
+ [isDragging, dragStart]
9830
+ );
9831
+ const handleDiscard = useCallback(() => {
9832
+ setImage(null);
9833
+ setImageLoaded(false);
9834
+ resetTransforms();
9835
+ if (fileInputRef.current) {
9836
+ fileInputRef.current.value = "";
9837
+ }
9838
+ onChange?.(null);
9839
+ }, [resetTransforms, onChange]);
9840
+ const handleZoomIn = () => setZoom((z) => Math.min(z + 0.1, 3));
9841
+ const handleZoomOut = () => setZoom((z) => Math.max(z - 0.1, 0.5));
9842
+ const handleRotateLeft = () => setRotation((r2) => r2 - 15);
9843
+ const handleRotateRight = () => setRotation((r2) => r2 + 15);
9844
+ const isLarge = controlSize === "large";
9845
+ const buttonSize = isLarge ? "h-12 w-12" : "h-8 w-8";
9846
+ const iconSize = isLarge ? "w-6 h-6" : "w-4 h-4";
9847
+ const smallIconSize = isLarge ? "w-5 h-5" : "w-3 h-3";
9848
+ const textSize = isLarge ? "text-sm" : "text-xs";
9849
+ const uploadIconSize = isLarge ? "w-12 h-12" : "w-8 h-8";
9850
+ const uploadContainerSize = isLarge ? "w-24 h-24" : "w-16 h-16";
9851
+ const gapSize = isLarge ? "gap-6" : "gap-4";
9852
+ const controlGap = isLarge ? "gap-3" : "gap-2";
9853
+ const sliderHeight = isLarge ? "[&_[role=slider]]:h-6 [&_[role=slider]]:w-6" : "";
9854
+ return /* @__PURE__ */ jsxs(
9855
+ "div",
9856
+ {
9857
+ className: cn("flex flex-col", gapSize, className),
9858
+ style: { width: size4 },
9859
+ children: [
9860
+ /* @__PURE__ */ jsxs(
9861
+ "div",
9862
+ {
9863
+ ref: containerRef,
9864
+ className: "relative bg-muted rounded-2xl overflow-hidden",
9865
+ style: { width: size4, height: size4 },
9866
+ children: [
9867
+ !imageLoaded && /* @__PURE__ */ jsxs(
9868
+ "button",
9869
+ {
9870
+ onClick: () => fileInputRef.current?.click(),
9871
+ className: cn(
9872
+ "absolute inset-0 z-10 flex flex-col items-center justify-center text-muted-foreground hover:text-foreground hover:bg-muted/80 transition-colors cursor-pointer",
9873
+ isLarge ? "gap-4" : "gap-3"
9874
+ ),
9875
+ "aria-label": "Upload image",
9876
+ type: "button",
9877
+ children: [
9878
+ /* @__PURE__ */ jsx(
9879
+ "div",
9880
+ {
9881
+ className: cn(
9882
+ "rounded-full bg-primary/10 flex items-center justify-center",
9883
+ uploadContainerSize
9884
+ ),
9885
+ children: /* @__PURE__ */ jsx(Upload, { className: cn("text-primary", uploadIconSize) })
9886
+ }
9887
+ ),
9888
+ /* @__PURE__ */ jsx(
9889
+ "span",
9890
+ {
9891
+ className: cn("font-medium", isLarge ? "text-base" : "text-sm"),
9892
+ children: "Click to upload"
9893
+ }
9894
+ ),
9895
+ /* @__PURE__ */ jsx("span", { className: cn("text-muted-foreground", textSize), children: "PNG, JPG up to 10MB" })
9896
+ ]
9897
+ }
9898
+ ),
9899
+ /* @__PURE__ */ jsx(
9900
+ "canvas",
9901
+ {
9902
+ ref: canvasRef,
9903
+ className: cn(
9904
+ "absolute inset-0",
9905
+ imageLoaded ? "cursor-move" : "pointer-events-none opacity-0"
9906
+ ),
9907
+ style: { width: size4, height: size4 },
9908
+ onMouseDown: handleMouseDown,
9909
+ onMouseMove: handleMouseMove,
9910
+ onMouseUp: handleMouseUp,
9911
+ onMouseLeave: handleMouseUp,
9912
+ onTouchStart: handleTouchStart,
9913
+ onTouchMove: handleTouchMove,
9914
+ onTouchEnd: handleMouseUp
9915
+ }
9916
+ ),
9917
+ showGrid && imageLoaded && /* @__PURE__ */ jsx(
9918
+ "div",
9919
+ {
9920
+ className: "absolute inset-0 pointer-events-none",
9921
+ style: { width: size4, height: size4 },
9922
+ children: /* @__PURE__ */ jsxs("svg", { width: size4, height: size4, className: "opacity-30", children: [
9923
+ /* @__PURE__ */ jsx(
9924
+ "line",
9925
+ {
9926
+ x1: size4 / 3,
9927
+ y1: 0,
9928
+ x2: size4 / 3,
9929
+ y2: size4,
9930
+ stroke: "white",
9931
+ strokeWidth: "1"
9932
+ }
9933
+ ),
9934
+ /* @__PURE__ */ jsx(
9935
+ "line",
9936
+ {
9937
+ x1: size4 * 2 / 3,
9938
+ y1: 0,
9939
+ x2: size4 * 2 / 3,
9940
+ y2: size4,
9941
+ stroke: "white",
9942
+ strokeWidth: "1"
9943
+ }
9944
+ ),
9945
+ /* @__PURE__ */ jsx(
9946
+ "line",
9947
+ {
9948
+ x1: 0,
9949
+ y1: size4 / 3,
9950
+ x2: size4,
9951
+ y2: size4 / 3,
9952
+ stroke: "white",
9953
+ strokeWidth: "1"
9954
+ }
9955
+ ),
9956
+ /* @__PURE__ */ jsx(
9957
+ "line",
9958
+ {
9959
+ x1: 0,
9960
+ y1: size4 * 2 / 3,
9961
+ x2: size4,
9962
+ y2: size4 * 2 / 3,
9963
+ stroke: "white",
9964
+ strokeWidth: "1"
9965
+ }
9966
+ ),
9967
+ /* @__PURE__ */ jsx(
9968
+ "line",
9969
+ {
9970
+ x1: size4 / 2 - 10,
9971
+ y1: size4 / 2,
9972
+ x2: size4 / 2 + 10,
9973
+ y2: size4 / 2,
9974
+ stroke: "white",
9975
+ strokeWidth: "1"
9976
+ }
9977
+ ),
9978
+ /* @__PURE__ */ jsx(
9979
+ "line",
9980
+ {
9981
+ x1: size4 / 2,
9982
+ y1: size4 / 2 - 10,
9983
+ x2: size4 / 2,
9984
+ y2: size4 / 2 + 10,
9985
+ stroke: "white",
9986
+ strokeWidth: "1"
9987
+ }
9988
+ )
9989
+ ] })
9990
+ }
9991
+ ),
9992
+ imageLoaded && isDragging && /* @__PURE__ */ jsxs(
9993
+ "div",
9994
+ {
9995
+ className: cn(
9996
+ "absolute top-2 left-1/2 -translate-x-1/2 bg-black/60 rounded text-white flex items-center",
9997
+ isLarge ? "px-3 py-2 text-sm gap-2" : "px-2 py-1 text-xs gap-1"
9998
+ ),
9999
+ children: [
10000
+ /* @__PURE__ */ jsx(Move, { className: smallIconSize }),
10001
+ "Dragging"
10002
+ ]
10003
+ }
10004
+ )
10005
+ ]
10006
+ }
10007
+ ),
10008
+ imageLoaded && /* @__PURE__ */ jsxs("div", { className: cn("space-y-4", isLarge && "space-y-5"), children: [
10009
+ /* @__PURE__ */ jsxs("div", { className: cn("flex items-center", controlGap), children: [
10010
+ /* @__PURE__ */ jsx(
10011
+ Button,
10012
+ {
10013
+ variant: "ghost",
10014
+ size: "icon",
10015
+ className: cn(buttonSize, "shrink-0"),
10016
+ onClick: handleZoomOut,
10017
+ "aria-label": "Zoom out",
10018
+ type: "button",
10019
+ children: /* @__PURE__ */ jsx(ZoomOut, { className: iconSize })
10020
+ }
10021
+ ),
10022
+ /* @__PURE__ */ jsx(
10023
+ Slider,
10024
+ {
10025
+ value: [zoom],
10026
+ onValueChange: ([v]) => setZoom(v),
10027
+ min: 0.5,
10028
+ max: 3,
10029
+ step: 0.01,
10030
+ className: cn("flex-1", sliderHeight),
10031
+ "aria-label": "Zoom level"
10032
+ }
10033
+ ),
10034
+ /* @__PURE__ */ jsx(
10035
+ Button,
10036
+ {
10037
+ variant: "ghost",
10038
+ size: "icon",
10039
+ className: cn(buttonSize, "shrink-0"),
10040
+ onClick: handleZoomIn,
10041
+ "aria-label": "Zoom in",
10042
+ type: "button",
10043
+ children: /* @__PURE__ */ jsx(ZoomIn, { className: iconSize })
10044
+ }
10045
+ )
10046
+ ] }),
10047
+ /* @__PURE__ */ jsxs("div", { className: cn("flex items-center justify-between", controlGap), children: [
10048
+ /* @__PURE__ */ jsxs(
10049
+ "div",
10050
+ {
10051
+ className: cn("flex items-center", isLarge ? "gap-2" : "gap-1"),
10052
+ children: [
10053
+ /* @__PURE__ */ jsx(
10054
+ Button,
10055
+ {
10056
+ variant: "ghost",
10057
+ size: "icon",
10058
+ className: buttonSize,
10059
+ onClick: handleRotateLeft,
10060
+ "aria-label": "Rotate left 15 degrees",
10061
+ type: "button",
10062
+ children: /* @__PURE__ */ jsx(RotateCcw, { className: iconSize })
10063
+ }
10064
+ ),
10065
+ /* @__PURE__ */ jsxs(
10066
+ "span",
10067
+ {
10068
+ className: cn(
10069
+ "text-muted-foreground text-center tabular-nums",
10070
+ textSize,
10071
+ isLarge ? "w-16" : "w-12"
10072
+ ),
10073
+ children: [
10074
+ rotation,
10075
+ "\xB0"
10076
+ ]
10077
+ }
10078
+ ),
10079
+ /* @__PURE__ */ jsx(
10080
+ Button,
10081
+ {
10082
+ variant: "ghost",
10083
+ size: "icon",
10084
+ className: buttonSize,
10085
+ onClick: handleRotateRight,
10086
+ "aria-label": "Rotate right 15 degrees",
10087
+ type: "button",
10088
+ children: /* @__PURE__ */ jsx(RotateCw, { className: iconSize })
10089
+ }
10090
+ )
10091
+ ]
10092
+ }
10093
+ ),
10094
+ /* @__PURE__ */ jsxs(
10095
+ "div",
10096
+ {
10097
+ className: cn("flex items-center", isLarge ? "gap-2" : "gap-1"),
10098
+ children: [
10099
+ /* @__PURE__ */ jsx(
10100
+ Toggle,
10101
+ {
10102
+ pressed: showGrid,
10103
+ onPressedChange: setShowGrid,
10104
+ size: "sm",
10105
+ className: cn(buttonSize, "p-0"),
10106
+ "aria-label": "Toggle grid overlay",
10107
+ children: /* @__PURE__ */ jsx(Grid3X3, { className: iconSize })
10108
+ }
10109
+ ),
10110
+ /* @__PURE__ */ jsx(
10111
+ Button,
10112
+ {
10113
+ variant: "ghost",
10114
+ size: "icon",
10115
+ className: buttonSize,
10116
+ onClick: resetTransforms,
10117
+ "aria-label": "Reset all transforms",
10118
+ type: "button",
10119
+ children: /* @__PURE__ */ jsx(RefreshCw, { className: iconSize })
10120
+ }
10121
+ ),
10122
+ /* @__PURE__ */ jsx(
10123
+ Button,
10124
+ {
10125
+ variant: "ghost",
10126
+ size: "icon",
10127
+ className: buttonSize,
10128
+ onClick: () => fileInputRef.current?.click(),
10129
+ "aria-label": "Upload new image",
10130
+ type: "button",
10131
+ children: /* @__PURE__ */ jsx(Upload, { className: iconSize })
10132
+ }
10133
+ ),
10134
+ /* @__PURE__ */ jsx(
10135
+ Button,
10136
+ {
10137
+ variant: "ghost",
10138
+ size: "icon",
10139
+ className: cn(
10140
+ buttonSize,
10141
+ "text-destructive hover:text-destructive hover:bg-destructive/10"
10142
+ ),
10143
+ onClick: handleDiscard,
10144
+ "aria-label": "Discard image",
10145
+ type: "button",
10146
+ children: /* @__PURE__ */ jsx(X, { className: iconSize })
10147
+ }
10148
+ )
10149
+ ]
10150
+ }
10151
+ )
10152
+ ] })
10153
+ ] }),
10154
+ /* @__PURE__ */ jsx(
10155
+ "input",
10156
+ {
10157
+ ref: fileInputRef,
10158
+ type: "file",
10159
+ accept: "image/*",
10160
+ onChange: handleFileSelect,
10161
+ className: "hidden",
10162
+ "aria-hidden": "true"
10163
+ }
10164
+ )
10165
+ ]
10166
+ }
10167
+ );
10168
+ }
10169
+ function Dialog({
10170
+ ...props
10171
+ }) {
10172
+ return /* @__PURE__ */ jsx(DialogPrimitive.Root, { "data-slot": "dialog", ...props });
10173
+ }
10174
+ function DialogTrigger({
10175
+ ...props
10176
+ }) {
10177
+ return /* @__PURE__ */ jsx(DialogPrimitive.Trigger, { "data-slot": "dialog-trigger", ...props });
10178
+ }
10179
+ function DialogPortal({
10180
+ ...props
10181
+ }) {
10182
+ return /* @__PURE__ */ jsx(DialogPrimitive.Portal, { "data-slot": "dialog-portal", ...props });
10183
+ }
10184
+ function DialogClose({
10185
+ ...props
10186
+ }) {
10187
+ return /* @__PURE__ */ jsx(DialogPrimitive.Close, { "data-slot": "dialog-close", ...props });
10188
+ }
10189
+ function DialogOverlay({
10190
+ className,
10191
+ ...props
10192
+ }) {
10193
+ return /* @__PURE__ */ jsx(
10194
+ DialogPrimitive.Overlay,
10195
+ {
10196
+ "data-slot": "dialog-overlay",
10197
+ className: cn(
10198
+ "data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 fixed inset-0 z-50 bg-black/50",
10199
+ className
10200
+ ),
10201
+ ...props
10202
+ }
10203
+ );
10204
+ }
10205
+ function DialogContent({
10206
+ className,
10207
+ children,
10208
+ showCloseButton = true,
10209
+ ...props
10210
+ }) {
10211
+ return /* @__PURE__ */ jsxs(DialogPortal, { "data-slot": "dialog-portal", children: [
10212
+ /* @__PURE__ */ jsx(DialogOverlay, {}),
10213
+ /* @__PURE__ */ jsxs(
10214
+ DialogPrimitive.Content,
10215
+ {
10216
+ "data-slot": "dialog-content",
10217
+ className: cn(
10218
+ "bg-background data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 fixed top-[50%] left-[50%] z-50 grid w-full max-w-[calc(100%-2rem)] translate-x-[-50%] translate-y-[-50%] gap-4 rounded-lg border p-6 shadow-lg duration-200 outline-none sm:max-w-lg",
10219
+ className
10220
+ ),
10221
+ ...props,
10222
+ children: [
10223
+ children,
10224
+ showCloseButton && /* @__PURE__ */ jsxs(
10225
+ DialogPrimitive.Close,
10226
+ {
10227
+ "data-slot": "dialog-close",
10228
+ className: "ring-offset-background focus:ring-ring data-[state=open]:bg-accent data-[state=open]:text-muted-foreground absolute top-4 right-4 rounded-xs opacity-70 transition-opacity hover:opacity-100 focus:ring-2 focus:ring-offset-2 focus:outline-hidden disabled:pointer-events-none [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4",
10229
+ children: [
10230
+ /* @__PURE__ */ jsx(XIcon, {}),
10231
+ /* @__PURE__ */ jsx("span", { className: "sr-only", children: "Close" })
10232
+ ]
10233
+ }
10234
+ )
10235
+ ]
10236
+ }
10237
+ )
10238
+ ] });
10239
+ }
10240
+ function DialogHeader({ className, ...props }) {
10241
+ return /* @__PURE__ */ jsx(
10242
+ "div",
10243
+ {
10244
+ "data-slot": "dialog-header",
10245
+ className: cn("flex flex-col gap-2 text-center sm:text-left", className),
10246
+ ...props
10247
+ }
10248
+ );
10249
+ }
10250
+ function DialogFooter({ className, ...props }) {
10251
+ return /* @__PURE__ */ jsx(
10252
+ "div",
10253
+ {
10254
+ "data-slot": "dialog-footer",
10255
+ className: cn(
10256
+ "flex flex-col-reverse gap-2 sm:flex-row sm:justify-end",
10257
+ className
10258
+ ),
10259
+ ...props
10260
+ }
10261
+ );
10262
+ }
10263
+ function DialogTitle({
10264
+ className,
10265
+ ...props
10266
+ }) {
10267
+ return /* @__PURE__ */ jsx(
10268
+ DialogPrimitive.Title,
10269
+ {
10270
+ "data-slot": "dialog-title",
10271
+ className: cn("text-lg leading-none font-semibold", className),
10272
+ ...props
10273
+ }
10274
+ );
10275
+ }
10276
+ function DialogDescription({
10277
+ className,
10278
+ ...props
10279
+ }) {
10280
+ return /* @__PURE__ */ jsx(
10281
+ DialogPrimitive.Description,
10282
+ {
10283
+ "data-slot": "dialog-description",
10284
+ className: cn("text-muted-foreground text-sm", className),
10285
+ ...props
10286
+ }
10287
+ );
10288
+ }
10289
+ function Drawer({
10290
+ ...props
10291
+ }) {
10292
+ return /* @__PURE__ */ jsx(Drawer$1.Root, { "data-slot": "drawer", ...props });
10293
+ }
10294
+ function DrawerTrigger({
10295
+ ...props
10296
+ }) {
10297
+ return /* @__PURE__ */ jsx(Drawer$1.Trigger, { "data-slot": "drawer-trigger", ...props });
10298
+ }
10299
+ function DrawerPortal({
10300
+ ...props
10301
+ }) {
10302
+ return /* @__PURE__ */ jsx(Drawer$1.Portal, { "data-slot": "drawer-portal", ...props });
10303
+ }
10304
+ function DrawerClose({
10305
+ ...props
10306
+ }) {
10307
+ return /* @__PURE__ */ jsx(Drawer$1.Close, { "data-slot": "drawer-close", ...props });
10308
+ }
10309
+ function DrawerOverlay({
10310
+ className,
10311
+ ...props
10312
+ }) {
10313
+ return /* @__PURE__ */ jsx(
10314
+ Drawer$1.Overlay,
10315
+ {
10316
+ "data-slot": "drawer-overlay",
10317
+ className: cn(
10318
+ "data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 fixed inset-0 z-50 bg-black/50",
10319
+ className
10320
+ ),
10321
+ ...props
10322
+ }
10323
+ );
10324
+ }
10325
+ function DrawerContent({
10326
+ className,
10327
+ children,
10328
+ ...props
10329
+ }) {
10330
+ return /* @__PURE__ */ jsxs(DrawerPortal, { "data-slot": "drawer-portal", children: [
10331
+ /* @__PURE__ */ jsx(DrawerOverlay, {}),
10332
+ /* @__PURE__ */ jsxs(
10333
+ Drawer$1.Content,
10334
+ {
10335
+ "data-slot": "drawer-content",
10336
+ className: cn(
10337
+ "group/drawer-content bg-background fixed z-50 flex h-auto flex-col",
10338
+ "data-[vaul-drawer-direction=top]:inset-x-0 data-[vaul-drawer-direction=top]:top-0 data-[vaul-drawer-direction=top]:mb-24 data-[vaul-drawer-direction=top]:max-h-[80vh] data-[vaul-drawer-direction=top]:rounded-b-lg data-[vaul-drawer-direction=top]:border-b",
10339
+ "data-[vaul-drawer-direction=bottom]:inset-x-0 data-[vaul-drawer-direction=bottom]:bottom-0 data-[vaul-drawer-direction=bottom]:mt-24 data-[vaul-drawer-direction=bottom]:max-h-[80vh] data-[vaul-drawer-direction=bottom]:rounded-t-lg data-[vaul-drawer-direction=bottom]:border-t",
10340
+ "data-[vaul-drawer-direction=right]:inset-y-0 data-[vaul-drawer-direction=right]:right-0 data-[vaul-drawer-direction=right]:w-3/4 data-[vaul-drawer-direction=right]:border-l data-[vaul-drawer-direction=right]:sm:max-w-sm",
10341
+ "data-[vaul-drawer-direction=left]:inset-y-0 data-[vaul-drawer-direction=left]:left-0 data-[vaul-drawer-direction=left]:w-3/4 data-[vaul-drawer-direction=left]:border-r data-[vaul-drawer-direction=left]:sm:max-w-sm",
10342
+ className
10343
+ ),
10344
+ ...props,
10345
+ children: [
10346
+ /* @__PURE__ */ jsx("div", { className: "bg-muted mx-auto mt-4 hidden h-2 w-[100px] shrink-0 rounded-full group-data-[vaul-drawer-direction=bottom]/drawer-content:block" }),
10347
+ children
10348
+ ]
10349
+ }
10350
+ )
10351
+ ] });
10352
+ }
10353
+ function DrawerHeader({ className, ...props }) {
10354
+ return /* @__PURE__ */ jsx(
10355
+ "div",
10356
+ {
10357
+ "data-slot": "drawer-header",
10358
+ className: cn(
10359
+ "flex flex-col gap-0.5 p-4 group-data-[vaul-drawer-direction=bottom]/drawer-content:text-center group-data-[vaul-drawer-direction=top]/drawer-content:text-center md:gap-1.5 md:text-left",
10360
+ className
10361
+ ),
10362
+ ...props
10363
+ }
10364
+ );
10365
+ }
10366
+ function DrawerFooter({ className, ...props }) {
10367
+ return /* @__PURE__ */ jsx(
10368
+ "div",
10369
+ {
10370
+ "data-slot": "drawer-footer",
10371
+ className: cn("mt-auto flex flex-col gap-2 p-4", className),
10372
+ ...props
10373
+ }
10374
+ );
10375
+ }
10376
+ function DrawerTitle({
10377
+ className,
10378
+ ...props
10379
+ }) {
10380
+ return /* @__PURE__ */ jsx(
10381
+ Drawer$1.Title,
10382
+ {
10383
+ "data-slot": "drawer-title",
10384
+ className: cn("text-foreground font-semibold", className),
10385
+ ...props
10386
+ }
10387
+ );
10388
+ }
10389
+ function DrawerDescription({
10390
+ className,
10391
+ ...props
10392
+ }) {
10393
+ return /* @__PURE__ */ jsx(
10394
+ Drawer$1.Description,
10395
+ {
10396
+ "data-slot": "drawer-description",
10397
+ className: cn("text-muted-foreground text-sm", className),
10398
+ ...props
10399
+ }
10400
+ );
10401
+ }
10402
+ function useMediaQuery(query) {
10403
+ const [matches, setMatches] = useState(false);
10404
+ useEffect(() => {
10405
+ const media = window.matchMedia(query);
10406
+ if (media.matches !== matches) {
10407
+ setMatches(media.matches);
10408
+ }
10409
+ const listener = () => setMatches(media.matches);
10410
+ media.addEventListener("change", listener);
10411
+ return () => media.removeEventListener("change", listener);
10412
+ }, [matches, query]);
10413
+ return matches;
10414
+ }
10415
+ function AvatarEditorDialog({
10416
+ value,
10417
+ onChange,
10418
+ onSave,
10419
+ displaySize = 120,
10420
+ editorSize = 280,
10421
+ outputSize = 256,
10422
+ placeholder: _placeholder = "Add Photo",
10423
+ editLabel = "Edit avatar",
10424
+ dialogTitle = "Edit Avatar",
10425
+ acceptText = "Accept",
10426
+ cancelText = "Cancel",
10427
+ successMessage = "Avatar saved successfully!",
10428
+ errorMessage = "Failed to save avatar. Please try again.",
10429
+ className
10430
+ }) {
10431
+ const [isOpen, setIsOpen] = useState(false);
10432
+ const [editedValue, setEditedValue] = useState(value ?? null);
10433
+ const [isSaving, setIsSaving] = useState(false);
10434
+ const [feedback, setFeedback] = useState(null);
10435
+ const isMobile = useMediaQuery("(max-width: 640px)");
10436
+ const handleOpenChange = useCallback(
10437
+ (open) => {
10438
+ if (open) {
10439
+ setEditedValue(value ?? null);
10440
+ setFeedback(null);
10441
+ }
10442
+ setIsOpen(open);
10443
+ },
10444
+ [value]
10445
+ );
10446
+ const handleAccept = useCallback(async () => {
10447
+ if (!editedValue) return;
10448
+ setIsSaving(true);
10449
+ setFeedback(null);
10450
+ try {
10451
+ if (onSave) {
10452
+ const result = await onSave(editedValue);
10453
+ if (result) {
10454
+ setFeedback({ type: "success", message: successMessage });
10455
+ setTimeout(() => {
10456
+ setIsOpen(false);
10457
+ setFeedback(null);
10458
+ }, 1500);
10459
+ } else {
10460
+ setFeedback({ type: "error", message: errorMessage });
10461
+ }
10462
+ } else {
10463
+ onChange?.(editedValue);
10464
+ setFeedback({ type: "success", message: successMessage });
10465
+ setTimeout(() => {
10466
+ setIsOpen(false);
10467
+ setFeedback(null);
10468
+ }, 1500);
10469
+ }
10470
+ } catch {
10471
+ setFeedback({ type: "error", message: errorMessage });
10472
+ } finally {
10473
+ setIsSaving(false);
10474
+ }
10475
+ }, [editedValue, onChange, onSave, successMessage, errorMessage]);
10476
+ const AvatarDisplay = /* @__PURE__ */ jsxs(
10477
+ "div",
10478
+ {
10479
+ className: cn("relative group", className),
10480
+ style: { width: displaySize, height: displaySize },
10481
+ children: [
10482
+ /* @__PURE__ */ jsx(
10483
+ "div",
10484
+ {
10485
+ className: "w-full h-full rounded-full overflow-hidden bg-muted border-2 border-border flex items-center justify-center",
10486
+ style: { width: displaySize, height: displaySize },
10487
+ children: value ? /* @__PURE__ */ jsx(
10488
+ "img",
10489
+ {
10490
+ src: value || "/placeholder.svg",
10491
+ alt: "Avatar",
10492
+ className: "w-full h-full object-cover"
10493
+ }
10494
+ ) : /* @__PURE__ */ jsx("div", { className: "flex items-center justify-center text-muted-foreground", children: /* @__PURE__ */ jsx(User, { className: "w-10 h-10" }) })
10495
+ }
10496
+ ),
10497
+ /* @__PURE__ */ jsx(
10498
+ "button",
10499
+ {
10500
+ onClick: () => handleOpenChange(true),
10501
+ className: "absolute bottom-0 right-0 w-8 h-8 rounded-full bg-primary text-primary-foreground flex items-center justify-center shadow-lg hover:bg-primary/90 transition-colors focus:outline-none focus:ring-2 focus:ring-primary focus:ring-offset-2",
10502
+ "aria-label": editLabel,
10503
+ type: "button",
10504
+ children: /* @__PURE__ */ jsx(Pencil, { className: "w-4 h-4" })
10505
+ }
10506
+ )
10507
+ ]
10508
+ }
10509
+ );
10510
+ const mobileEditorSize = isMobile ? Math.min(editorSize + 40, window.innerWidth - 48) : editorSize;
10511
+ const EditorContent = /* @__PURE__ */ jsxs("div", { className: "flex flex-col items-center gap-4", children: [
10512
+ /* @__PURE__ */ jsx(
10513
+ AvatarEditor,
10514
+ {
10515
+ value: editedValue,
10516
+ onChange: setEditedValue,
10517
+ size: mobileEditorSize,
10518
+ outputSize,
10519
+ controlSize: isMobile ? "large" : "default"
10520
+ }
10521
+ ),
10522
+ /* @__PURE__ */ jsx(AnimatePresence, { children: feedback && /* @__PURE__ */ jsxs(
10523
+ motion.div,
10524
+ {
10525
+ initial: { opacity: 0, y: -10 },
10526
+ animate: { opacity: 1, y: 0 },
10527
+ exit: { opacity: 0, y: -10 },
10528
+ className: cn(
10529
+ "flex items-center gap-2 rounded-lg font-medium",
10530
+ isMobile ? "px-5 py-3 text-base" : "px-4 py-2 text-sm",
10531
+ feedback.type === "success" ? "bg-green-100 text-green-800 dark:bg-green-900/30 dark:text-green-400" : "bg-red-100 text-red-800 dark:bg-red-900/30 dark:text-red-400"
10532
+ ),
10533
+ children: [
10534
+ feedback.type === "success" ? /* @__PURE__ */ jsx(Check, { className: isMobile ? "w-5 h-5" : "w-4 h-4" }) : /* @__PURE__ */ jsx(X, { className: isMobile ? "w-5 h-5" : "w-4 h-4" }),
10535
+ feedback.message
10536
+ ]
10537
+ }
10538
+ ) })
10539
+ ] });
10540
+ const FooterButtons = /* @__PURE__ */ jsxs(Fragment, { children: [
10541
+ isMobile ? /* @__PURE__ */ jsx(DrawerClose, { asChild: true, children: /* @__PURE__ */ jsx(
10542
+ Button,
10543
+ {
10544
+ variant: "outline",
10545
+ disabled: isSaving,
10546
+ size: "lg",
10547
+ className: "flex-1 bg-transparent",
10548
+ children: cancelText
10549
+ }
10550
+ ) }) : /* @__PURE__ */ jsx(DialogClose, { asChild: true, children: /* @__PURE__ */ jsx(Button, { variant: "outline", disabled: isSaving, children: cancelText }) }),
10551
+ /* @__PURE__ */ jsx(
10552
+ Button,
10553
+ {
10554
+ onClick: handleAccept,
10555
+ disabled: !editedValue || isSaving || feedback?.type === "success",
10556
+ size: isMobile ? "lg" : "default",
10557
+ className: isMobile ? "flex-1" : "",
10558
+ children: isSaving ? /* @__PURE__ */ jsxs(Fragment, { children: [
10559
+ /* @__PURE__ */ jsx(
10560
+ Loader2,
10561
+ {
10562
+ className: cn(
10563
+ "mr-2 animate-spin",
10564
+ isMobile ? "w-5 h-5" : "w-4 h-4"
10565
+ )
10566
+ }
10567
+ ),
10568
+ "Saving..."
10569
+ ] }) : feedback?.type === "success" ? /* @__PURE__ */ jsxs(Fragment, { children: [
10570
+ /* @__PURE__ */ jsx(Check, { className: cn("mr-2", isMobile ? "w-5 h-5" : "w-4 h-4") }),
10571
+ "Saved!"
10572
+ ] }) : acceptText
10573
+ }
10574
+ )
10575
+ ] });
10576
+ if (isMobile) {
10577
+ return /* @__PURE__ */ jsxs(Fragment, { children: [
10578
+ AvatarDisplay,
10579
+ /* @__PURE__ */ jsx(Drawer, { open: isOpen, onOpenChange: handleOpenChange, children: /* @__PURE__ */ jsxs(DrawerContent, { className: "max-h-[96vh]", children: [
10580
+ /* @__PURE__ */ jsx(DrawerHeader, { className: "text-center", children: /* @__PURE__ */ jsx(DrawerTitle, { className: "text-xl", children: dialogTitle }) }),
10581
+ /* @__PURE__ */ jsx("div", { className: "px-6 pb-6 overflow-y-auto", children: EditorContent }),
10582
+ /* @__PURE__ */ jsx(DrawerFooter, { className: "flex-row gap-3 px-6 pb-8", children: FooterButtons })
10583
+ ] }) })
10584
+ ] });
10585
+ }
10586
+ return /* @__PURE__ */ jsxs(Fragment, { children: [
10587
+ AvatarDisplay,
10588
+ /* @__PURE__ */ jsx(Dialog, { open: isOpen, onOpenChange: handleOpenChange, children: /* @__PURE__ */ jsxs(DialogContent, { className: "sm:max-w-md", children: [
10589
+ /* @__PURE__ */ jsx(DialogHeader, { children: /* @__PURE__ */ jsx(DialogTitle, { children: dialogTitle }) }),
10590
+ /* @__PURE__ */ jsx("div", { className: "flex justify-center py-4", children: EditorContent }),
10591
+ /* @__PURE__ */ jsx(DialogFooter, { className: "gap-2 sm:gap-0", children: FooterButtons })
10592
+ ] }) })
10593
+ ] });
10594
+ }
10595
+ function Popover({
10596
+ ...props
10597
+ }) {
10598
+ return /* @__PURE__ */ jsx(PopoverPrimitive.Root, { "data-slot": "popover", ...props });
10599
+ }
10600
+ function PopoverTrigger({
10601
+ ...props
10602
+ }) {
10603
+ return /* @__PURE__ */ jsx(PopoverPrimitive.Trigger, { "data-slot": "popover-trigger", ...props });
10604
+ }
10605
+ function PopoverContent({
10606
+ className,
10607
+ align = "center",
10608
+ sideOffset = 4,
10609
+ ...props
10610
+ }) {
10611
+ return /* @__PURE__ */ jsx(PopoverPrimitive.Portal, { children: /* @__PURE__ */ jsx(
10612
+ PopoverPrimitive.Content,
10613
+ {
10614
+ "data-slot": "popover-content",
10615
+ align,
10616
+ sideOffset,
10617
+ className: cn(
10618
+ "bg-popover text-popover-foreground data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 z-50 w-72 origin-(--radix-popover-content-transform-origin) rounded-md border p-4 shadow-md outline-hidden",
10619
+ className
10620
+ ),
10621
+ ...props
10622
+ }
10623
+ ) });
10624
+ }
10625
+ function PopoverAnchor({
10626
+ ...props
10627
+ }) {
10628
+ return /* @__PURE__ */ jsx(PopoverPrimitive.Anchor, { "data-slot": "popover-anchor", ...props });
10629
+ }
10630
+ function ScrollArea({
10631
+ className,
10632
+ children,
10633
+ ...props
10634
+ }) {
10635
+ return /* @__PURE__ */ jsxs(
10636
+ ScrollAreaPrimitive.Root,
10637
+ {
10638
+ "data-slot": "scroll-area",
10639
+ className: cn("relative overflow-hidden", className),
10640
+ ...props,
10641
+ children: [
10642
+ /* @__PURE__ */ jsx(
10643
+ ScrollAreaPrimitive.Viewport,
10644
+ {
10645
+ "data-slot": "scroll-area-viewport",
10646
+ className: "h-full w-full rounded-[inherit]",
10647
+ children
10648
+ }
10649
+ ),
10650
+ /* @__PURE__ */ jsx(ScrollBar, {}),
10651
+ /* @__PURE__ */ jsx(ScrollAreaPrimitive.Corner, {})
10652
+ ]
10653
+ }
10654
+ );
10655
+ }
10656
+ function ScrollBar({
10657
+ className,
10658
+ orientation = "vertical",
10659
+ ...props
10660
+ }) {
10661
+ return /* @__PURE__ */ jsx(
10662
+ ScrollAreaPrimitive.ScrollAreaScrollbar,
10663
+ {
10664
+ "data-slot": "scroll-bar",
10665
+ orientation,
10666
+ className: cn(
10667
+ "flex touch-none select-none transition-colors",
10668
+ orientation === "vertical" && "h-full w-2.5 border-l border-l-transparent p-[1px]",
10669
+ orientation === "horizontal" && "h-2.5 flex-col border-t border-t-transparent p-[1px]",
10670
+ className
10671
+ ),
10672
+ ...props,
10673
+ children: /* @__PURE__ */ jsx(
10674
+ ScrollAreaPrimitive.ScrollAreaThumb,
10675
+ {
10676
+ "data-slot": "scroll-thumb",
10677
+ className: "relative flex-1 rounded-full bg-border"
10678
+ }
10679
+ )
10680
+ }
10681
+ );
10682
+ }
10683
+ var sizeConfig = {
10684
+ sm: {
10685
+ button: "h-8 w-8",
10686
+ icon: "w-4 h-4",
10687
+ badge: "min-w-[16px] h-4 text-[10px] -top-1 -right-1",
10688
+ dot: "w-2.5 h-2.5 -top-0.5 -right-0.5"
10689
+ },
10690
+ md: {
10691
+ button: "h-9 w-9",
10692
+ icon: "w-5 h-5",
10693
+ badge: "min-w-[18px] h-[18px] text-[11px] -top-1 -right-1",
10694
+ dot: "w-3 h-3 -top-0.5 -right-0.5"
10695
+ },
10696
+ lg: {
10697
+ button: "h-10 w-10",
10698
+ icon: "w-6 h-6",
10699
+ badge: "min-w-[20px] h-5 text-xs -top-1.5 -right-1.5",
10700
+ dot: "w-3.5 h-3.5 -top-0.5 -right-0.5"
10701
+ }
10702
+ };
10703
+ var typeConfig = {
10704
+ info: {
10705
+ icon: Info,
10706
+ color: "text-blue-500",
10707
+ bg: "bg-blue-500/10"
10708
+ },
10709
+ success: {
10710
+ icon: CheckCircle,
10711
+ color: "text-green-500",
10712
+ bg: "bg-green-500/10"
10713
+ },
10714
+ warning: {
10715
+ icon: AlertTriangle,
10716
+ color: "text-amber-500",
10717
+ bg: "bg-amber-500/10"
10718
+ },
10719
+ error: {
10720
+ icon: XCircle,
10721
+ color: "text-red-500",
10722
+ bg: "bg-red-500/10"
10723
+ }
10724
+ };
10725
+ var dotColorConfig = {
10726
+ red: "bg-red-500",
10727
+ blue: "bg-blue-500",
10728
+ green: "bg-green-500",
10729
+ amber: "bg-amber-500",
10730
+ purple: "bg-purple-500",
10731
+ primary: "bg-primary"
10732
+ };
10733
+ var soundConfig = {
10734
+ chime: {
10735
+ frequencies: [880, 1100],
10736
+ durations: [0.1, 0.2],
10737
+ gain: 0.3
10738
+ },
10739
+ bell: {
10740
+ frequencies: [523, 659, 784],
10741
+ durations: [0.15, 0.15, 0.2],
10742
+ gain: 0.25
10743
+ },
10744
+ pop: {
10745
+ frequencies: [400, 600],
10746
+ durations: [0.05, 0.08],
10747
+ gain: 0.4
10748
+ },
10749
+ ding: {
10750
+ frequencies: [1200],
10751
+ durations: [0.15],
10752
+ gain: 0.2
10753
+ }
10754
+ };
10755
+ var pulseVariants = {
10756
+ ring: {
10757
+ animate: { scale: [1, 1.8], opacity: [0.6, 0] },
10758
+ transition: {
10759
+ duration: 1.2,
10760
+ repeat: Number.POSITIVE_INFINITY,
10761
+ ease: "easeOut"
10762
+ }
10763
+ },
10764
+ glow: {
10765
+ animate: { scale: [1, 1.3, 1], opacity: [0.8, 0.4, 0.8] },
10766
+ transition: {
10767
+ duration: 1.5,
10768
+ repeat: Number.POSITIVE_INFINITY,
10769
+ ease: "easeInOut"
10770
+ }
10771
+ },
10772
+ bounce: {
10773
+ animate: { scale: [1, 1.2, 1], y: [0, -2, 0] },
10774
+ transition: {
10775
+ duration: 0.6,
10776
+ repeat: Number.POSITIVE_INFINITY,
10777
+ ease: "easeInOut"
10778
+ }
10779
+ }
10780
+ };
10781
+ function formatTimeAgo(date) {
10782
+ const now = /* @__PURE__ */ new Date();
10783
+ const diffMs = now.getTime() - date.getTime();
10784
+ const diffSecs = Math.floor(diffMs / 1e3);
10785
+ const diffMins = Math.floor(diffSecs / 60);
10786
+ const diffHours = Math.floor(diffMins / 60);
10787
+ const diffDays = Math.floor(diffHours / 24);
10788
+ if (diffSecs < 60) return "just now";
10789
+ if (diffMins < 60) return `${diffMins}m ago`;
10790
+ if (diffHours < 24) return `${diffHours}h ago`;
10791
+ if (diffDays < 7) return `${diffDays}d ago`;
10792
+ return date.toLocaleDateString();
10793
+ }
10794
+ function playNotificationSound(soundType = "chime", soundUrl) {
10795
+ if (typeof window === "undefined" || soundType === "none") return;
10796
+ if (soundUrl) {
10797
+ const audio = new Audio(soundUrl);
10798
+ audio.volume = 0.5;
10799
+ audio.play().catch(() => {
10800
+ });
10801
+ return;
10802
+ }
10803
+ const config = soundConfig[soundType];
10804
+ if (!config) return;
10805
+ try {
10806
+ const audioContext = new (window.AudioContext || window.webkitAudioContext)();
10807
+ let currentTime = audioContext.currentTime;
10808
+ config.frequencies.forEach((freq, i) => {
10809
+ const oscillator = audioContext.createOscillator();
10810
+ const gainNode = audioContext.createGain();
10811
+ oscillator.connect(gainNode);
10812
+ gainNode.connect(audioContext.destination);
10813
+ oscillator.frequency.setValueAtTime(freq, currentTime);
10814
+ gainNode.gain.setValueAtTime(config.gain, currentTime);
10815
+ gainNode.gain.exponentialRampToValueAtTime(
10816
+ 0.01,
10817
+ currentTime + config.durations[i]
10818
+ );
10819
+ oscillator.start(currentTime);
10820
+ oscillator.stop(currentTime + config.durations[i]);
10821
+ currentTime += config.durations[i] * 0.7;
10822
+ });
10823
+ } catch {
10824
+ }
10825
+ }
10826
+ function NotificationsWidget({
10827
+ notifications,
10828
+ onMarkAsRead,
10829
+ onMarkAllAsRead,
10830
+ onDismiss,
10831
+ onClearAll,
10832
+ onNotificationClick,
10833
+ size: size4 = "md",
10834
+ maxVisible = 5,
10835
+ playSound: _playSound = true,
10836
+ soundUrl,
10837
+ className,
10838
+ emptyMessage = "No notifications",
10839
+ title = "Notifications",
10840
+ dotColor = "red",
10841
+ showPulse = true,
10842
+ soundType = "chime",
10843
+ pulseStyle = "ring",
10844
+ soundCooldown = 2e3
10845
+ }) {
10846
+ const [isOpen, setIsOpen] = React32.useState(false);
10847
+ const [prevCount, setPrevCount] = React32.useState(0);
10848
+ const lastSoundPlayedRef = React32.useRef(0);
10849
+ const styles = sizeConfig[size4];
10850
+ const dotBgColor = dotColorConfig[dotColor];
10851
+ const unreadCount = notifications.filter((n) => !n.read).length;
10852
+ const visibleNotifications = notifications.slice(0, maxVisible);
10853
+ const hasMore = notifications.length > maxVisible;
10854
+ React32.useEffect(() => {
10855
+ if (soundType !== "none" && unreadCount > prevCount && prevCount > 0) {
10856
+ const now = Date.now();
10857
+ if (now - lastSoundPlayedRef.current >= soundCooldown) {
10858
+ playNotificationSound(soundType, soundUrl);
10859
+ lastSoundPlayedRef.current = now;
10860
+ }
10861
+ }
10862
+ setPrevCount(unreadCount);
10863
+ }, [unreadCount, prevCount, soundType, soundUrl, soundCooldown]);
10864
+ React32.useEffect(() => {
10865
+ if (isOpen && onMarkAsRead) {
10866
+ visibleNotifications.forEach((notification) => {
10867
+ if (!notification.read) {
10868
+ onMarkAsRead(notification.id);
10869
+ }
10870
+ });
10871
+ }
10872
+ }, [isOpen, onMarkAsRead, visibleNotifications]);
10873
+ const handleNotificationClick = (notification) => {
10874
+ if (notification.href) {
10875
+ onNotificationClick?.(notification);
10876
+ }
10877
+ };
10878
+ return /* @__PURE__ */ jsxs(Popover, { open: isOpen, onOpenChange: setIsOpen, children: [
10879
+ /* @__PURE__ */ jsx(PopoverTrigger, { asChild: true, children: /* @__PURE__ */ jsxs(
10880
+ motion.button,
10881
+ {
10882
+ className: cn(
10883
+ "relative inline-flex items-center justify-center rounded-lg",
10884
+ "bg-muted/50 border border-border/50 hover:bg-muted/70 transition-colors",
10885
+ styles.button,
10886
+ className
10887
+ ),
10888
+ whileTap: { scale: 0.95 },
10889
+ "aria-label": `Notifications${unreadCount > 0 ? ` (${unreadCount} unread)` : ""}`,
10890
+ children: [
10891
+ /* @__PURE__ */ jsx(Bell, { className: cn(styles.icon, "text-muted-foreground") }),
10892
+ /* @__PURE__ */ jsx(AnimatePresence, { children: unreadCount > 0 && /* @__PURE__ */ jsx(
10893
+ motion.div,
10894
+ {
10895
+ initial: { scale: 0, opacity: 0 },
10896
+ animate: { scale: 1, opacity: 1 },
10897
+ exit: { scale: 0, opacity: 0 },
10898
+ className: cn(
10899
+ "absolute flex items-center justify-center rounded-full",
10900
+ dotBgColor,
10901
+ "text-white font-medium",
10902
+ unreadCount > 9 ? styles.badge : styles.dot,
10903
+ unreadCount > 9 && "px-1"
10904
+ ),
10905
+ children: unreadCount > 9 ? /* @__PURE__ */ jsx("span", { children: unreadCount > 99 ? "99+" : unreadCount }) : null
10906
+ }
10907
+ ) }),
10908
+ /* @__PURE__ */ jsx(AnimatePresence, { children: unreadCount > 0 && showPulse && pulseStyle !== "none" && /* @__PURE__ */ jsx(
10909
+ motion.div,
10910
+ {
10911
+ initial: { scale: 1, opacity: 0.5 },
10912
+ animate: pulseVariants[pulseStyle].animate,
10913
+ transition: pulseVariants[pulseStyle].transition,
10914
+ className: cn("absolute rounded-full", dotBgColor, styles.dot)
10915
+ }
10916
+ ) })
10917
+ ]
10918
+ }
10919
+ ) }),
10920
+ /* @__PURE__ */ jsxs(
10921
+ PopoverContent,
10922
+ {
10923
+ side: "bottom",
10924
+ align: "end",
10925
+ className: "w-80 p-0 overflow-hidden",
10926
+ children: [
10927
+ /* @__PURE__ */ jsxs("div", { className: "flex items-center justify-between px-4 py-3 border-b border-border", children: [
10928
+ /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2", children: [
10929
+ /* @__PURE__ */ jsx("h4", { className: "font-semibold text-sm", children: title }),
10930
+ unreadCount > 0 && /* @__PURE__ */ jsxs("span", { className: "px-2 py-0.5 text-xs font-medium rounded-full bg-primary/10 text-primary", children: [
10931
+ unreadCount,
10932
+ " new"
10933
+ ] })
10934
+ ] }),
10935
+ /* @__PURE__ */ jsx("div", { className: "flex items-center gap-1", children: unreadCount > 0 && onMarkAllAsRead && /* @__PURE__ */ jsxs(
10936
+ Button,
10937
+ {
10938
+ variant: "ghost",
10939
+ size: "sm",
10940
+ className: "h-7 px-2 text-xs",
10941
+ onClick: () => onMarkAllAsRead(),
10942
+ children: [
10943
+ /* @__PURE__ */ jsx(CheckCheck, { className: "w-3.5 h-3.5 mr-1" }),
10944
+ "Mark all read"
10945
+ ]
10946
+ }
10947
+ ) })
10948
+ ] }),
10949
+ notifications.length === 0 ? /* @__PURE__ */ jsxs("div", { className: "flex flex-col items-center justify-center py-8 px-4 text-center", children: [
10950
+ /* @__PURE__ */ jsx("div", { className: "w-12 h-12 rounded-full bg-muted flex items-center justify-center mb-3", children: /* @__PURE__ */ jsx(Bell, { className: "w-6 h-6 text-muted-foreground" }) }),
10951
+ /* @__PURE__ */ jsx("p", { className: "text-sm text-muted-foreground", children: emptyMessage })
10952
+ ] }) : /* @__PURE__ */ jsxs(Fragment, { children: [
10953
+ /* @__PURE__ */ jsx(ScrollArea, { className: "h-[320px]", children: /* @__PURE__ */ jsx("div", { className: "divide-y divide-border", children: visibleNotifications.map((notification) => {
10954
+ const config = typeConfig[notification.type || "info"];
10955
+ const Icon = config.icon;
10956
+ const hasLink = !!notification.href;
10957
+ return /* @__PURE__ */ jsxs(
10958
+ motion.div,
10959
+ {
10960
+ initial: { opacity: 0, y: -10 },
10961
+ animate: { opacity: 1, y: 0 },
10962
+ className: cn(
10963
+ "relative flex gap-3 px-4 py-3 transition-colors group",
10964
+ hasLink && "cursor-pointer hover:bg-muted/50",
10965
+ !hasLink && "cursor-default",
10966
+ !notification.read && "bg-primary/5"
10967
+ ),
10968
+ onClick: () => handleNotificationClick(notification),
10969
+ children: [
10970
+ /* @__PURE__ */ jsx(
10971
+ "div",
10972
+ {
10973
+ className: cn(
10974
+ "flex-shrink-0 w-8 h-8 rounded-full flex items-center justify-center",
10975
+ config.bg
10976
+ ),
10977
+ children: /* @__PURE__ */ jsx(Icon, { className: cn("w-4 h-4", config.color) })
10978
+ }
10979
+ ),
10980
+ /* @__PURE__ */ jsxs("div", { className: "flex-1 min-w-0", children: [
10981
+ /* @__PURE__ */ jsx("div", { className: "flex items-start justify-between gap-2", children: /* @__PURE__ */ jsx(
10982
+ "p",
10983
+ {
10984
+ className: cn(
10985
+ "text-sm line-clamp-1",
10986
+ !notification.read ? "font-semibold" : "font-medium",
10987
+ hasLink && "group-hover:underline"
10988
+ ),
10989
+ children: notification.title
10990
+ }
10991
+ ) }),
10992
+ notification.message && /* @__PURE__ */ jsx("p", { className: "text-xs text-muted-foreground line-clamp-2 mt-0.5", children: notification.message }),
10993
+ /* @__PURE__ */ jsx("p", { className: "text-[10px] text-muted-foreground/70 mt-1", children: formatTimeAgo(notification.timestamp) })
10994
+ ] }),
10995
+ /* @__PURE__ */ jsx("div", { className: "flex-shrink-0 flex items-start gap-1 opacity-0 group-hover:opacity-100 transition-opacity", children: onDismiss && /* @__PURE__ */ jsx(
10996
+ Button,
10997
+ {
10998
+ variant: "ghost",
10999
+ size: "icon",
11000
+ className: "h-6 w-6 text-muted-foreground hover:text-destructive",
11001
+ onClick: (e) => {
11002
+ e.stopPropagation();
11003
+ onDismiss(notification.id);
11004
+ },
11005
+ children: /* @__PURE__ */ jsx(X, { className: "w-3 h-3" })
11006
+ }
11007
+ ) })
11008
+ ]
11009
+ },
11010
+ notification.id
11011
+ );
11012
+ }) }) }),
11013
+ (hasMore || onClearAll) && /* @__PURE__ */ jsxs("div", { className: "flex items-center justify-between px-4 py-2 border-t border-border bg-muted/30", children: [
11014
+ hasMore && /* @__PURE__ */ jsxs("span", { className: "text-xs text-muted-foreground", children: [
11015
+ "+",
11016
+ notifications.length - maxVisible,
11017
+ " more"
11018
+ ] }),
11019
+ onClearAll && notifications.length > 0 && /* @__PURE__ */ jsxs(
11020
+ Button,
11021
+ {
11022
+ variant: "ghost",
11023
+ size: "sm",
11024
+ className: "h-7 px-2 text-xs text-muted-foreground hover:text-destructive",
11025
+ onClick: () => onClearAll(),
11026
+ children: [
11027
+ /* @__PURE__ */ jsx(Trash2, { className: "w-3 h-3 mr-1" }),
11028
+ "Clear all"
11029
+ ]
11030
+ }
11031
+ )
11032
+ ] })
11033
+ ] })
11034
+ ]
11035
+ }
11036
+ )
11037
+ ] });
11038
+ }
9510
11039
 
9511
- export { Button, DropdownMenu2 as DropdownMenu, DropdownMenuCheckboxItem2 as DropdownMenuCheckboxItem, DropdownMenuContent2 as DropdownMenuContent, DropdownMenuGroup2 as DropdownMenuGroup, DropdownMenuItem2 as DropdownMenuItem, DropdownMenuLabel2 as DropdownMenuLabel, DropdownMenuPortal2 as DropdownMenuPortal, DropdownMenuRadioGroup2 as DropdownMenuRadioGroup, DropdownMenuRadioItem2 as DropdownMenuRadioItem, DropdownMenuSeparator2 as DropdownMenuSeparator, DropdownMenuShortcut, DropdownMenuSub2 as DropdownMenuSub, DropdownMenuSubContent2 as DropdownMenuSubContent, DropdownMenuSubTrigger2 as DropdownMenuSubTrigger, DropdownMenuTrigger2 as DropdownMenuTrigger, LanguageSwitcher, ThemeSwitcher, Tooltip2 as Tooltip, TooltipContent2 as TooltipContent, TooltipProvider2 as TooltipProvider, TooltipTrigger2 as TooltipTrigger, buttonVariants, cn };
11040
+ export { AvatarEditor, AvatarEditorDialog, Button, Dialog, DialogClose, DialogContent, DialogDescription, DialogFooter, DialogHeader, DialogOverlay, DialogPortal, DialogTitle, DialogTrigger, Drawer, DrawerClose, DrawerContent, DrawerDescription, DrawerFooter, DrawerHeader, DrawerOverlay, DrawerPortal, DrawerTitle, DrawerTrigger, DropdownMenu2 as DropdownMenu, DropdownMenuCheckboxItem2 as DropdownMenuCheckboxItem, DropdownMenuContent2 as DropdownMenuContent, DropdownMenuGroup2 as DropdownMenuGroup, DropdownMenuItem2 as DropdownMenuItem, DropdownMenuLabel2 as DropdownMenuLabel, DropdownMenuPortal2 as DropdownMenuPortal, DropdownMenuRadioGroup2 as DropdownMenuRadioGroup, DropdownMenuRadioItem2 as DropdownMenuRadioItem, DropdownMenuSeparator2 as DropdownMenuSeparator, DropdownMenuShortcut, DropdownMenuSub2 as DropdownMenuSub, DropdownMenuSubContent2 as DropdownMenuSubContent, DropdownMenuSubTrigger2 as DropdownMenuSubTrigger, DropdownMenuTrigger2 as DropdownMenuTrigger, LanguageSwitcher, NotificationsWidget, Popover, PopoverAnchor, PopoverContent, PopoverTrigger, ScrollArea, ScrollBar, Slider, ThemeSwitcher, Toggle, Tooltip2 as Tooltip, TooltipContent2 as TooltipContent, TooltipProvider2 as TooltipProvider, TooltipTrigger2 as TooltipTrigger, buttonVariants, cn, toggleVariants };
9512
11041
  //# sourceMappingURL=index.js.map
9513
11042
  //# sourceMappingURL=index.js.map