@webdevarif/dashui 0.5.0 → 0.6.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.mjs CHANGED
@@ -1752,436 +1752,150 @@ function StorageBar({
1752
1752
  ] });
1753
1753
  }
1754
1754
 
1755
- // src/components/media/media-card.tsx
1755
+ // src/components/media/upload-zone.tsx
1756
+ import { useState as useState4, useRef as useRef2 } from "react";
1757
+ import { Cloud } from "lucide-react";
1756
1758
  import { jsx as jsx32, jsxs as jsxs18 } from "react/jsx-runtime";
1757
- function stripExtension(name) {
1758
- return name.replace(/\.[^/.]+$/, "");
1759
- }
1760
- function getFileEmoji(mimeType) {
1761
- if (mimeType.startsWith("video/")) return "\u{1F3AC}";
1762
- if (mimeType.startsWith("audio/")) return "\u{1F3B5}";
1763
- if (mimeType === "application/pdf") return "\u{1F4C4}";
1764
- return "\u{1F4CE}";
1765
- }
1766
- function MediaCard({ file, selected = false, onClick, className }) {
1767
- const isImage = file.mimeType.startsWith("image/");
1768
- const displayName = stripExtension(file.name);
1759
+ var CloudUploadIcon = Cloud;
1760
+ function UploadZone({
1761
+ onFiles,
1762
+ accept = "*",
1763
+ multiple = true,
1764
+ disabled = false
1765
+ }) {
1766
+ const [dragActive, setDragActive] = useState4(false);
1767
+ const inputRef = useRef2(null);
1768
+ const handleDrag = (e) => {
1769
+ e.preventDefault();
1770
+ e.stopPropagation();
1771
+ if (!disabled) {
1772
+ setDragActive(e.type === "dragenter" || e.type === "dragover");
1773
+ }
1774
+ };
1775
+ const handleDrop = (e) => {
1776
+ e.preventDefault();
1777
+ e.stopPropagation();
1778
+ setDragActive(false);
1779
+ if (!disabled && e.dataTransfer.files?.length) {
1780
+ onFiles(Array.from(e.dataTransfer.files));
1781
+ }
1782
+ };
1783
+ const handleChange = (e) => {
1784
+ if (e.target.files?.length) {
1785
+ onFiles(Array.from(e.target.files));
1786
+ }
1787
+ };
1769
1788
  return /* @__PURE__ */ jsxs18(
1770
1789
  "div",
1771
1790
  {
1772
- role: "button",
1773
- tabIndex: 0,
1774
- onClick,
1775
- onKeyDown: (e) => {
1776
- if (e.key === "Enter" || e.key === " ") onClick?.();
1777
- },
1778
- className: cn(
1779
- "group relative aspect-square cursor-pointer rounded-xl overflow-hidden border-2 transition-all duration-150",
1780
- selected ? "border-[color:var(--primary)] shadow-[0_0_0_2px_rgba(40,127,113,0.2)]" : "border-transparent hover:border-[color:var(--primary)]/40",
1781
- className
1782
- ),
1791
+ className: `relative rounded-lg border-2 border-dashed p-8 text-center transition-colors ${dragActive ? "border-blue-500 bg-blue-50/50" : "border-gray-300 bg-gray-50/50 hover:border-gray-400"} ${disabled ? "opacity-50 cursor-not-allowed" : "cursor-pointer"}`,
1792
+ onDragEnter: handleDrag,
1793
+ onDragLeave: handleDrag,
1794
+ onDragOver: handleDrag,
1795
+ onDrop: handleDrop,
1796
+ onClick: () => !disabled && inputRef.current?.click(),
1783
1797
  children: [
1784
- isImage ? /* @__PURE__ */ jsx32(
1785
- "img",
1798
+ /* @__PURE__ */ jsx32(
1799
+ "input",
1786
1800
  {
1787
- src: file.url,
1788
- alt: file.name,
1789
- className: "h-full w-full object-cover transition-transform duration-150 group-hover:scale-[1.03]",
1790
- loading: "lazy"
1801
+ ref: inputRef,
1802
+ type: "file",
1803
+ accept,
1804
+ multiple,
1805
+ onChange: handleChange,
1806
+ disabled,
1807
+ className: "hidden"
1791
1808
  }
1792
- ) : /* @__PURE__ */ jsx32("div", { className: "flex h-full w-full items-center justify-center bg-muted text-4xl", children: getFileEmoji(file.mimeType) }),
1793
- /* @__PURE__ */ jsx32("div", { className: "absolute inset-0 flex items-end bg-gradient-to-t from-black/60 via-transparent to-transparent opacity-0 transition-opacity duration-150 group-hover:opacity-100", children: /* @__PURE__ */ jsx32("p", { className: "truncate px-2 pb-2 text-[11px] font-medium text-white", children: displayName }) }),
1794
- selected && /* @__PURE__ */ jsx32("div", { className: "absolute right-1.5 top-1.5 flex h-5 w-5 items-center justify-center rounded-full bg-primary shadow", children: /* @__PURE__ */ jsx32(
1795
- "svg",
1796
- {
1797
- className: "h-3 w-3 text-white",
1798
- xmlns: "http://www.w3.org/2000/svg",
1799
- viewBox: "0 0 24 24",
1800
- fill: "none",
1801
- stroke: "currentColor",
1802
- strokeWidth: "3",
1803
- strokeLinecap: "round",
1804
- strokeLinejoin: "round",
1805
- children: /* @__PURE__ */ jsx32("polyline", { points: "20 6 9 17 4 12" })
1806
- }
1807
- ) })
1809
+ ),
1810
+ /* @__PURE__ */ jsxs18("div", { className: "flex flex-col items-center gap-2", children: [
1811
+ /* @__PURE__ */ jsx32(CloudUploadIcon, { className: "w-8 h-8 text-gray-400" }),
1812
+ /* @__PURE__ */ jsxs18("div", { children: [
1813
+ /* @__PURE__ */ jsx32("p", { className: "text-sm font-medium text-gray-700", children: dragActive ? "Drop files here" : "Drag & drop files, or click to browse" }),
1814
+ /* @__PURE__ */ jsx32("p", { className: "text-xs text-gray-500 mt-1", children: "Max file size: 100MB" })
1815
+ ] })
1816
+ ] })
1808
1817
  ]
1809
1818
  }
1810
1819
  );
1811
1820
  }
1812
1821
 
1813
- // src/components/media/media-grid.tsx
1822
+ // src/components/media/upload-progress-panel.tsx
1823
+ import { CheckCircle, X as X2, Image, File, Video } from "lucide-react";
1814
1824
  import { jsx as jsx33, jsxs as jsxs19 } from "react/jsx-runtime";
1815
- var columnsClass = {
1816
- 4: "grid-cols-2 sm:grid-cols-3 md:grid-cols-4",
1817
- 5: "grid-cols-2 sm:grid-cols-3 md:grid-cols-4 lg:grid-cols-5",
1818
- 6: "grid-cols-2 sm:grid-cols-3 md:grid-cols-4 lg:grid-cols-6"
1819
- };
1820
- function MediaGrid({
1821
- files,
1822
- selected,
1823
- onSelect,
1824
- loading = false,
1825
- columns = 5,
1826
- emptyMessage = "No files yet.",
1827
- onUploadClick,
1828
- className
1825
+ var CheckmarkCircle01Icon = CheckCircle;
1826
+ var Cancel01Icon = X2;
1827
+ function UploadProgressPanel({
1828
+ items,
1829
+ onRetry,
1830
+ onCancel,
1831
+ onCancelAll
1829
1832
  }) {
1830
- if (loading) {
1831
- return /* @__PURE__ */ jsx33("div", { className: cn("grid gap-2", columnsClass[columns] ?? columnsClass[5], className), children: Array.from({ length: columns * 2 }).map((_, i) => /* @__PURE__ */ jsx33(
1833
+ if (items.length === 0) return null;
1834
+ const activeCount = items.filter((it) => it.status === "uploading").length;
1835
+ const doneCount = items.filter((it) => it.status === "done").length;
1836
+ const failedCount = items.filter((it) => it.status === "failed").length;
1837
+ const avgProgress = items.length > 0 ? Math.round(items.reduce((s, it) => s + it.progress, 0) / items.length) : 0;
1838
+ return /* @__PURE__ */ jsxs19("div", { className: "rounded-lg border border-gray-200 bg-white p-4 shadow-sm", children: [
1839
+ /* @__PURE__ */ jsxs19("div", { className: "flex items-center justify-between mb-4", children: [
1840
+ /* @__PURE__ */ jsx33("h3", { className: "text-sm font-semibold text-gray-900", children: activeCount > 0 ? `Uploading ${activeCount} file${activeCount > 1 ? "s" : ""}` : "Upload complete" }),
1841
+ onCancelAll && items.some((it) => it.status === "uploading" || it.status === "pending") && /* @__PURE__ */ jsx33(
1842
+ "button",
1843
+ {
1844
+ onClick: onCancelAll,
1845
+ className: "text-xs text-red-600 hover:text-red-700 font-medium",
1846
+ children: "Cancel all"
1847
+ }
1848
+ )
1849
+ ] }),
1850
+ /* @__PURE__ */ jsx33("div", { className: "w-full h-2 bg-gray-200 rounded-full overflow-hidden mb-4", children: /* @__PURE__ */ jsx33(
1832
1851
  "div",
1833
1852
  {
1834
- className: "aspect-square animate-pulse rounded-xl bg-muted"
1835
- },
1836
- i
1837
- )) });
1838
- }
1839
- if (files.length === 0) {
1840
- return /* @__PURE__ */ jsxs19("div", { className: cn("flex flex-col items-center justify-center gap-3 py-16 text-center", className), children: [
1841
- /* @__PURE__ */ jsx33("div", { className: "flex h-14 w-14 items-center justify-center rounded-full bg-muted", children: /* @__PURE__ */ jsxs19(
1842
- "svg",
1853
+ className: "h-full bg-blue-500 transition-all duration-300",
1854
+ style: { width: `${avgProgress}%` }
1855
+ }
1856
+ ) }),
1857
+ /* @__PURE__ */ jsxs19("p", { className: "text-xs text-gray-600 mb-4", children: [
1858
+ doneCount,
1859
+ " done",
1860
+ failedCount > 0 && ` \xB7 ${failedCount} failed`,
1861
+ activeCount > 0 && ` \xB7 ${activeCount} uploading`
1862
+ ] }),
1863
+ /* @__PURE__ */ jsx33("div", { className: "space-y-2 max-h-48 overflow-y-auto", children: items.map((item) => /* @__PURE__ */ jsxs19("div", { className: "flex items-center gap-3 p-2 rounded-md bg-gray-50 hover:bg-gray-100 transition-colors", children: [
1864
+ /* @__PURE__ */ jsxs19("div", { className: "shrink-0", children: [
1865
+ item.status === "done" && /* @__PURE__ */ jsx33(CheckmarkCircle01Icon, { className: "w-4 h-4 text-green-600" }),
1866
+ item.status === "failed" && /* @__PURE__ */ jsx33(Cancel01Icon, { className: "w-4 h-4 text-red-600" }),
1867
+ (item.status === "uploading" || item.status === "pending") && /* @__PURE__ */ jsx33("div", { className: "w-4 h-4 rounded-full border-2 border-blue-300 border-t-blue-600 animate-spin" })
1868
+ ] }),
1869
+ /* @__PURE__ */ jsxs19("div", { className: "flex-1 min-w-0", children: [
1870
+ /* @__PURE__ */ jsx33("p", { className: "text-xs font-medium text-gray-900 truncate", children: item.name }),
1871
+ item.error && /* @__PURE__ */ jsx33("p", { className: "text-xs text-red-600", children: item.error }),
1872
+ item.status === "uploading" && /* @__PURE__ */ jsxs19("p", { className: "text-xs text-gray-500", children: [
1873
+ item.progress,
1874
+ "%"
1875
+ ] })
1876
+ ] }),
1877
+ item.status === "failed" && onRetry && /* @__PURE__ */ jsx33(
1878
+ "button",
1843
1879
  {
1844
- className: "h-7 w-7 text-muted-foreground",
1845
- xmlns: "http://www.w3.org/2000/svg",
1846
- viewBox: "0 0 24 24",
1847
- fill: "none",
1848
- stroke: "currentColor",
1849
- strokeWidth: "1.5",
1850
- strokeLinecap: "round",
1851
- strokeLinejoin: "round",
1852
- children: [
1853
- /* @__PURE__ */ jsx33("rect", { width: "18", height: "18", x: "3", y: "3", rx: "2", ry: "2" }),
1854
- /* @__PURE__ */ jsx33("circle", { cx: "9", cy: "9", r: "2" }),
1855
- /* @__PURE__ */ jsx33("path", { d: "m21 15-3.086-3.086a2 2 0 0 0-2.828 0L6 21" })
1856
- ]
1880
+ onClick: () => onRetry(item.id),
1881
+ className: "text-xs text-blue-600 hover:text-blue-700 font-medium shrink-0",
1882
+ children: "Retry"
1857
1883
  }
1858
- ) }),
1859
- /* @__PURE__ */ jsx33("p", { className: "text-sm text-muted-foreground", children: emptyMessage }),
1860
- onUploadClick && /* @__PURE__ */ jsx33(
1884
+ ),
1885
+ (item.status === "uploading" || item.status === "pending") && onCancel && /* @__PURE__ */ jsx33(
1861
1886
  "button",
1862
1887
  {
1863
- onClick: onUploadClick,
1864
- className: "rounded-md bg-primary px-4 py-1.5 text-sm font-medium text-primary-foreground transition-opacity hover:opacity-90",
1865
- children: "Upload files"
1888
+ onClick: () => onCancel(item.id),
1889
+ className: "text-xs text-gray-600 hover:text-gray-700 shrink-0",
1890
+ children: /* @__PURE__ */ jsx33(Cancel01Icon, { className: "w-4 h-4" })
1866
1891
  }
1867
1892
  )
1868
- ] });
1869
- }
1870
- return /* @__PURE__ */ jsx33("div", { className: cn("grid gap-2", columnsClass[columns] ?? columnsClass[5], className), children: files.map((file) => /* @__PURE__ */ jsx33(
1871
- MediaCard,
1872
- {
1873
- file,
1874
- selected: selected?.has(file.id),
1875
- onClick: () => onSelect?.(file)
1876
- },
1877
- file.id
1878
- )) });
1879
- }
1880
-
1881
- // src/components/media/media-picker-dialog.tsx
1882
- import * as React20 from "react";
1883
- import { Fragment as Fragment4, jsx as jsx34, jsxs as jsxs20 } from "react/jsx-runtime";
1884
- var ALL_TYPE_OPTIONS = [
1885
- { value: "all", label: "All" },
1886
- { value: "images", label: "Images" },
1887
- { value: "videos", label: "Videos" },
1888
- { value: "documents", label: "Docs" }
1889
- ];
1890
- function MediaPickerDialog({
1891
- open,
1892
- onOpenChange,
1893
- title = "Media Library",
1894
- files,
1895
- folders = [],
1896
- loading = false,
1897
- total,
1898
- typeFilter = "all",
1899
- onTypeChange,
1900
- search = "",
1901
- onSearch,
1902
- activeFolderId,
1903
- onFolderChange,
1904
- onUpload,
1905
- onLoadMore,
1906
- hasMore = false,
1907
- multiple = false,
1908
- initialSelected = [],
1909
- accept = "all",
1910
- onSelect
1911
- }) {
1912
- const [localSelected, setLocalSelected] = React20.useState(new Set(initialSelected));
1913
- const [isDragging, setIsDragging] = React20.useState(false);
1914
- const fileInputRef = React20.useRef(null);
1915
- const dragCounterRef = React20.useRef(0);
1916
- React20.useEffect(() => {
1917
- if (!open) {
1918
- setLocalSelected(/* @__PURE__ */ new Set());
1919
- setIsDragging(false);
1920
- dragCounterRef.current = 0;
1921
- }
1922
- }, [open]);
1923
- const prevInitialRef = React20.useRef("");
1924
- React20.useEffect(() => {
1925
- if (!open) return;
1926
- const key = initialSelected.join(",");
1927
- if (key === prevInitialRef.current) return;
1928
- prevInitialRef.current = key;
1929
- if (initialSelected.length > 0) {
1930
- setLocalSelected(new Set(initialSelected));
1931
- }
1932
- }, [open, initialSelected]);
1933
- const handleDragEnter = (e) => {
1934
- e.preventDefault();
1935
- e.stopPropagation();
1936
- if (e.dataTransfer.types.includes("Files")) {
1937
- dragCounterRef.current++;
1938
- setIsDragging(true);
1939
- }
1940
- };
1941
- const handleDragLeave = (e) => {
1942
- e.preventDefault();
1943
- e.stopPropagation();
1944
- dragCounterRef.current--;
1945
- if (dragCounterRef.current <= 0) {
1946
- dragCounterRef.current = 0;
1947
- setIsDragging(false);
1948
- }
1949
- };
1950
- const handleDragOver = (e) => {
1951
- e.preventDefault();
1952
- e.stopPropagation();
1953
- };
1954
- const handleDrop = (e) => {
1955
- e.preventDefault();
1956
- e.stopPropagation();
1957
- dragCounterRef.current = 0;
1958
- setIsDragging(false);
1959
- if (!onUpload || !e.dataTransfer.files.length) return;
1960
- const accepted = accept === "images" ? Array.from(e.dataTransfer.files).filter((f) => f.type.startsWith("image/")) : Array.from(e.dataTransfer.files);
1961
- if (!accepted.length) return;
1962
- const dt = new DataTransfer();
1963
- accepted.forEach((f) => dt.items.add(f));
1964
- onUpload(dt.files);
1965
- };
1966
- const typeOptions = ALL_TYPE_OPTIONS.filter(
1967
- (o) => accept === "images" ? o.value === "all" || o.value === "images" : true
1968
- );
1969
- const handleFileSelect = (file) => {
1970
- setLocalSelected((prev) => {
1971
- if (multiple) {
1972
- const next = new Set(prev);
1973
- next.has(file.id) ? next.delete(file.id) : next.add(file.id);
1974
- return next;
1975
- }
1976
- return prev.has(file.id) ? /* @__PURE__ */ new Set() : /* @__PURE__ */ new Set([file.id]);
1977
- });
1978
- };
1979
- const handleConfirm = () => {
1980
- const selected = files.filter((f) => localSelected.has(f.id));
1981
- onSelect(selected);
1982
- onOpenChange(false);
1983
- };
1984
- const handleUploadChange = (e) => {
1985
- if (e.target.files && onUpload) {
1986
- onUpload(e.target.files);
1987
- }
1988
- };
1989
- const firstSelected = files.find((f) => localSelected.has(f.id));
1990
- return /* @__PURE__ */ jsx34(Dialog, { open, onOpenChange, children: /* @__PURE__ */ jsxs20(
1991
- DialogContent,
1992
- {
1993
- "aria-describedby": void 0,
1994
- className: "flex h-[85vh] max-h-[700px] max-w-4xl flex-col gap-0 p-0",
1995
- children: [
1996
- /* @__PURE__ */ jsxs20(DialogHeader, { className: "flex-row items-center gap-3 border-b px-5 py-3 pr-12 space-y-0 shrink-0", children: [
1997
- onUpload && /* @__PURE__ */ jsxs20(Fragment4, { children: [
1998
- /* @__PURE__ */ jsx34(
1999
- "input",
2000
- {
2001
- ref: fileInputRef,
2002
- type: "file",
2003
- className: "hidden",
2004
- multiple: true,
2005
- accept: accept === "images" ? "image/*" : void 0,
2006
- onChange: handleUploadChange
2007
- }
2008
- ),
2009
- /* @__PURE__ */ jsxs20(
2010
- "button",
2011
- {
2012
- onClick: () => fileInputRef.current?.click(),
2013
- className: "flex shrink-0 items-center gap-1.5 rounded-md bg-primary px-3 py-1.5 text-xs font-medium text-primary-foreground transition-opacity hover:opacity-90",
2014
- children: [
2015
- /* @__PURE__ */ jsxs20("svg", { className: "h-3.5 w-3.5", xmlns: "http://www.w3.org/2000/svg", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", children: [
2016
- /* @__PURE__ */ jsx34("path", { d: "M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4" }),
2017
- /* @__PURE__ */ jsx34("polyline", { points: "17 8 12 3 7 8" }),
2018
- /* @__PURE__ */ jsx34("line", { x1: "12", y1: "3", x2: "12", y2: "15" })
2019
- ] }),
2020
- "Upload"
2021
- ]
2022
- }
2023
- )
2024
- ] }),
2025
- /* @__PURE__ */ jsx34(DialogTitle, { className: "text-sm font-semibold flex-1", children: title }),
2026
- /* @__PURE__ */ jsxs20("span", { className: "text-xs text-muted-foreground shrink-0", children: [
2027
- total,
2028
- " files"
2029
- ] })
2030
- ] }),
2031
- /* @__PURE__ */ jsxs20("div", { className: "flex flex-1 overflow-hidden min-h-0", children: [
2032
- folders.length > 0 && /* @__PURE__ */ jsxs20("aside", { className: "flex w-44 shrink-0 flex-col gap-0.5 overflow-y-auto border-r p-2", children: [
2033
- /* @__PURE__ */ jsxs20(
2034
- "button",
2035
- {
2036
- onClick: () => onFolderChange?.(""),
2037
- className: cn(
2038
- "flex w-full items-center gap-2 rounded-md px-2.5 py-1.5 text-sm transition-colors",
2039
- !activeFolderId ? "bg-primary/10 font-medium text-primary" : "text-muted-foreground hover:bg-accent"
2040
- ),
2041
- children: [
2042
- /* @__PURE__ */ jsx34("svg", { className: "h-3.5 w-3.5 shrink-0", xmlns: "http://www.w3.org/2000/svg", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", children: /* @__PURE__ */ jsx34("path", { d: "M22 19a2 2 0 0 1-2 2H4a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h5l2 3h9a2 2 0 0 1 2 2z" }) }),
2043
- "All files"
2044
- ]
2045
- }
2046
- ),
2047
- folders.map((folder) => /* @__PURE__ */ jsxs20(
2048
- "button",
2049
- {
2050
- onClick: () => onFolderChange?.(folder.id),
2051
- className: cn(
2052
- "flex w-full items-center gap-2 rounded-md px-2.5 py-1.5 text-sm transition-colors",
2053
- activeFolderId === folder.id ? "bg-primary/10 font-medium text-primary" : "text-muted-foreground hover:bg-accent"
2054
- ),
2055
- children: [
2056
- /* @__PURE__ */ jsx34("span", { className: "text-base leading-none", children: folder.icon ?? "\u{1F4C1}" }),
2057
- /* @__PURE__ */ jsx34("span", { className: "truncate", children: folder.name })
2058
- ]
2059
- },
2060
- folder.id
2061
- ))
2062
- ] }),
2063
- /* @__PURE__ */ jsxs20("div", { className: "flex flex-1 flex-col overflow-hidden min-w-0", children: [
2064
- /* @__PURE__ */ jsxs20("div", { className: "flex items-center gap-2 border-b px-4 py-2 shrink-0", children: [
2065
- /* @__PURE__ */ jsx34("div", { className: "flex items-center gap-0.5 rounded-lg border border-input bg-muted/50 p-0.5", children: typeOptions.map((opt) => /* @__PURE__ */ jsx34(
2066
- "button",
2067
- {
2068
- onClick: () => onTypeChange?.(opt.value),
2069
- className: cn(
2070
- "rounded-md px-2.5 py-1 text-xs font-medium transition-colors",
2071
- typeFilter === opt.value ? "bg-background text-foreground shadow-sm" : "text-muted-foreground hover:text-foreground"
2072
- ),
2073
- children: opt.label
2074
- },
2075
- opt.value
2076
- )) }),
2077
- /* @__PURE__ */ jsxs20("div", { className: "relative flex-1 max-w-xs ml-auto", children: [
2078
- /* @__PURE__ */ jsxs20("svg", { className: "pointer-events-none absolute left-2.5 top-1/2 h-3.5 w-3.5 -translate-y-1/2 text-muted-foreground", xmlns: "http://www.w3.org/2000/svg", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", children: [
2079
- /* @__PURE__ */ jsx34("circle", { cx: "11", cy: "11", r: "8" }),
2080
- /* @__PURE__ */ jsx34("path", { d: "m21 21-4.35-4.35" })
2081
- ] }),
2082
- /* @__PURE__ */ jsx34(
2083
- "input",
2084
- {
2085
- type: "search",
2086
- value: search,
2087
- onChange: (e) => onSearch?.(e.target.value),
2088
- placeholder: "Search\u2026",
2089
- className: "h-8 w-full rounded-lg border border-input bg-background pl-8 pr-3 text-xs outline-none focus:border-primary focus:ring-1 focus:ring-primary/30"
2090
- }
2091
- )
2092
- ] })
2093
- ] }),
2094
- /* @__PURE__ */ jsxs20(
2095
- "div",
2096
- {
2097
- className: "relative flex-1 overflow-y-auto p-4",
2098
- onDragEnter: handleDragEnter,
2099
- onDragLeave: handleDragLeave,
2100
- onDragOver: handleDragOver,
2101
- onDrop: handleDrop,
2102
- children: [
2103
- isDragging && onUpload && /* @__PURE__ */ jsxs20("div", { className: "pointer-events-none absolute inset-0 z-10 flex flex-col items-center justify-center gap-3 rounded-lg border-2 border-dashed border-primary bg-primary/5 backdrop-blur-[1px]", children: [
2104
- /* @__PURE__ */ jsxs20("svg", { className: "h-10 w-10 text-primary opacity-70", xmlns: "http://www.w3.org/2000/svg", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "1.5", strokeLinecap: "round", strokeLinejoin: "round", children: [
2105
- /* @__PURE__ */ jsx34("path", { d: "M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4" }),
2106
- /* @__PURE__ */ jsx34("polyline", { points: "17 8 12 3 7 8" }),
2107
- /* @__PURE__ */ jsx34("line", { x1: "12", y1: "3", x2: "12", y2: "15" })
2108
- ] }),
2109
- /* @__PURE__ */ jsxs20("p", { className: "text-sm font-medium text-primary", children: [
2110
- "Drop ",
2111
- accept === "images" ? "images" : "files",
2112
- " to upload"
2113
- ] })
2114
- ] }),
2115
- /* @__PURE__ */ jsx34(
2116
- MediaGrid,
2117
- {
2118
- files,
2119
- selected: localSelected,
2120
- onSelect: handleFileSelect,
2121
- loading,
2122
- columns: 5,
2123
- onUploadClick: onUpload ? () => fileInputRef.current?.click() : void 0
2124
- }
2125
- ),
2126
- hasMore && /* @__PURE__ */ jsx34("div", { className: "mt-4 flex justify-center", children: /* @__PURE__ */ jsx34(
2127
- "button",
2128
- {
2129
- onClick: onLoadMore,
2130
- className: "rounded-lg border px-4 py-2 text-xs text-muted-foreground transition-colors hover:bg-accent",
2131
- children: "Load more"
2132
- }
2133
- ) })
2134
- ]
2135
- }
2136
- )
2137
- ] })
2138
- ] }),
2139
- localSelected.size > 0 && /* @__PURE__ */ jsxs20("div", { className: "flex items-center justify-between gap-4 border-t px-5 py-3 shrink-0 bg-muted/20", children: [
2140
- /* @__PURE__ */ jsxs20("span", { className: "text-sm text-muted-foreground", children: [
2141
- localSelected.size,
2142
- " file",
2143
- localSelected.size !== 1 ? "s" : "",
2144
- " selected"
2145
- ] }),
2146
- firstSelected && firstSelected.mimeType.startsWith("image/") && /* @__PURE__ */ jsx34(
2147
- "img",
2148
- {
2149
- src: firstSelected.url,
2150
- alt: firstSelected.name,
2151
- className: "h-9 w-9 rounded-lg border border-border object-cover shadow-sm"
2152
- }
2153
- ),
2154
- /* @__PURE__ */ jsxs20("div", { className: "flex items-center gap-2", children: [
2155
- /* @__PURE__ */ jsx34(
2156
- "button",
2157
- {
2158
- onClick: () => setLocalSelected(/* @__PURE__ */ new Set()),
2159
- className: "rounded-lg px-3 py-1.5 text-sm text-muted-foreground transition-colors hover:bg-accent",
2160
- children: "Cancel"
2161
- }
2162
- ),
2163
- /* @__PURE__ */ jsxs20(
2164
- "button",
2165
- {
2166
- onClick: handleConfirm,
2167
- className: "rounded-lg bg-primary px-4 py-1.5 text-sm font-medium text-primary-foreground transition-opacity hover:opacity-90",
2168
- children: [
2169
- "Use ",
2170
- localSelected.size,
2171
- " file",
2172
- localSelected.size !== 1 ? "s" : ""
2173
- ]
2174
- }
2175
- )
2176
- ] })
2177
- ] })
2178
- ]
2179
- }
2180
- ) });
1893
+ ] }, item.id)) })
1894
+ ] });
2181
1895
  }
2182
1896
 
2183
1897
  // src/components/media/image-picker-field.tsx
2184
- import { jsx as jsx35, jsxs as jsxs21 } from "react/jsx-runtime";
1898
+ import { jsx as jsx34, jsxs as jsxs20 } from "react/jsx-runtime";
2185
1899
  var sizeMap = {
2186
1900
  sm: { box: "h-12 w-12", icon: "h-4 w-4", text: "text-[9px]" },
2187
1901
  md: { box: "h-20 w-20", icon: "h-5 w-5", text: "text-[10px]" },
@@ -2197,8 +1911,8 @@ function ImagePickerField({
2197
1911
  className
2198
1912
  }) {
2199
1913
  const sz = sizeMap[size];
2200
- return /* @__PURE__ */ jsxs21("div", { className: cn("relative inline-flex shrink-0", className), children: [
2201
- /* @__PURE__ */ jsx35(
1914
+ return /* @__PURE__ */ jsxs20("div", { className: cn("relative inline-flex shrink-0", className), children: [
1915
+ /* @__PURE__ */ jsx34(
2202
1916
  "button",
2203
1917
  {
2204
1918
  type: "button",
@@ -2208,15 +1922,15 @@ function ImagePickerField({
2208
1922
  sz.box,
2209
1923
  value ? "border-border overflow-hidden" : "border-border hover:border-[color:var(--primary)] hover:bg-primary/5"
2210
1924
  ),
2211
- children: value ? /* @__PURE__ */ jsx35(
1925
+ children: value ? /* @__PURE__ */ jsx34(
2212
1926
  "img",
2213
1927
  {
2214
1928
  src: value,
2215
1929
  alt: filename ?? "Selected image",
2216
1930
  className: "h-full w-full rounded-md object-cover"
2217
1931
  }
2218
- ) : /* @__PURE__ */ jsxs21("div", { className: "flex flex-col items-center gap-1", children: [
2219
- /* @__PURE__ */ jsxs21(
1932
+ ) : /* @__PURE__ */ jsxs20("div", { className: "flex flex-col items-center gap-1", children: [
1933
+ /* @__PURE__ */ jsxs20(
2220
1934
  "svg",
2221
1935
  {
2222
1936
  className: cn(sz.icon, "text-muted-foreground transition-colors group-hover:text-primary"),
@@ -2228,17 +1942,17 @@ function ImagePickerField({
2228
1942
  strokeLinecap: "round",
2229
1943
  strokeLinejoin: "round",
2230
1944
  children: [
2231
- /* @__PURE__ */ jsx35("rect", { width: "18", height: "18", x: "3", y: "3", rx: "2", ry: "2" }),
2232
- /* @__PURE__ */ jsx35("circle", { cx: "9", cy: "9", r: "2" }),
2233
- /* @__PURE__ */ jsx35("path", { d: "m21 15-3.086-3.086a2 2 0 0 0-2.828 0L6 21" })
1945
+ /* @__PURE__ */ jsx34("rect", { width: "18", height: "18", x: "3", y: "3", rx: "2", ry: "2" }),
1946
+ /* @__PURE__ */ jsx34("circle", { cx: "9", cy: "9", r: "2" }),
1947
+ /* @__PURE__ */ jsx34("path", { d: "m21 15-3.086-3.086a2 2 0 0 0-2.828 0L6 21" })
2234
1948
  ]
2235
1949
  }
2236
1950
  ),
2237
- size !== "sm" && /* @__PURE__ */ jsx35("span", { className: cn(sz.text, "text-muted-foreground text-center leading-tight transition-colors group-hover:text-primary"), children: emptyLabel })
1951
+ size !== "sm" && /* @__PURE__ */ jsx34("span", { className: cn(sz.text, "text-muted-foreground text-center leading-tight transition-colors group-hover:text-primary"), children: emptyLabel })
2238
1952
  ] })
2239
1953
  }
2240
1954
  ),
2241
- value && onRemove && /* @__PURE__ */ jsx35(
1955
+ value && onRemove && /* @__PURE__ */ jsx34(
2242
1956
  "button",
2243
1957
  {
2244
1958
  type: "button",
@@ -2248,12 +1962,70 @@ function ImagePickerField({
2248
1962
  },
2249
1963
  className: "absolute -right-1.5 -top-1.5 flex h-5 w-5 items-center justify-center rounded-full bg-destructive text-destructive-foreground shadow-sm transition-opacity hover:opacity-90",
2250
1964
  "aria-label": "Remove image",
2251
- children: /* @__PURE__ */ jsx35("svg", { className: "h-2.5 w-2.5", xmlns: "http://www.w3.org/2000/svg", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "3", strokeLinecap: "round", strokeLinejoin: "round", children: /* @__PURE__ */ jsx35("path", { d: "M18 6 6 18M6 6l12 12" }) })
1965
+ children: /* @__PURE__ */ jsx34("svg", { className: "h-2.5 w-2.5", xmlns: "http://www.w3.org/2000/svg", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "3", strokeLinecap: "round", strokeLinejoin: "round", children: /* @__PURE__ */ jsx34("path", { d: "M18 6 6 18M6 6l12 12" }) })
2252
1966
  }
2253
1967
  )
2254
1968
  ] });
2255
1969
  }
2256
1970
 
1971
+ // src/components/media/media-card.tsx
1972
+ import { jsx as jsx35, jsxs as jsxs21 } from "react/jsx-runtime";
1973
+ function stripExtension(name) {
1974
+ return name.replace(/\.[^/.]+$/, "");
1975
+ }
1976
+ function getFileEmoji(mimeType) {
1977
+ if (mimeType.startsWith("video/")) return "\u{1F3AC}";
1978
+ if (mimeType.startsWith("audio/")) return "\u{1F3B5}";
1979
+ if (mimeType === "application/pdf") return "\u{1F4C4}";
1980
+ return "\u{1F4CE}";
1981
+ }
1982
+ function MediaCard({ file, selected = false, onClick, className }) {
1983
+ const isImage = file.mimeType.startsWith("image/");
1984
+ const displayName = stripExtension(file.name);
1985
+ return /* @__PURE__ */ jsxs21(
1986
+ "div",
1987
+ {
1988
+ role: "button",
1989
+ tabIndex: 0,
1990
+ onClick,
1991
+ onKeyDown: (e) => {
1992
+ if (e.key === "Enter" || e.key === " ") onClick?.();
1993
+ },
1994
+ className: cn(
1995
+ "group relative aspect-square cursor-pointer rounded-xl overflow-hidden border-2 transition-all duration-150",
1996
+ selected ? "border-[color:var(--primary)] shadow-[0_0_0_2px_rgba(40,127,113,0.2)]" : "border-transparent hover:border-[color:var(--primary)]/40",
1997
+ className
1998
+ ),
1999
+ children: [
2000
+ isImage ? /* @__PURE__ */ jsx35(
2001
+ "img",
2002
+ {
2003
+ src: file.url,
2004
+ alt: file.name,
2005
+ className: "h-full w-full object-cover transition-transform duration-150 group-hover:scale-[1.03]",
2006
+ loading: "lazy"
2007
+ }
2008
+ ) : /* @__PURE__ */ jsx35("div", { className: "flex h-full w-full items-center justify-center bg-muted text-4xl", children: getFileEmoji(file.mimeType) }),
2009
+ /* @__PURE__ */ jsx35("div", { className: "absolute inset-0 flex items-end bg-gradient-to-t from-black/60 via-transparent to-transparent opacity-0 transition-opacity duration-150 group-hover:opacity-100", children: /* @__PURE__ */ jsx35("p", { className: "truncate px-2 pb-2 text-[11px] font-medium text-white", children: displayName }) }),
2010
+ selected && /* @__PURE__ */ jsx35("div", { className: "absolute right-1.5 top-1.5 flex h-5 w-5 items-center justify-center rounded-full bg-primary shadow", children: /* @__PURE__ */ jsx35(
2011
+ "svg",
2012
+ {
2013
+ className: "h-3 w-3 text-white",
2014
+ xmlns: "http://www.w3.org/2000/svg",
2015
+ viewBox: "0 0 24 24",
2016
+ fill: "none",
2017
+ stroke: "currentColor",
2018
+ strokeWidth: "3",
2019
+ strokeLinecap: "round",
2020
+ strokeLinejoin: "round",
2021
+ children: /* @__PURE__ */ jsx35("polyline", { points: "20 6 9 17 4 12" })
2022
+ }
2023
+ ) })
2024
+ ]
2025
+ }
2026
+ );
2027
+ }
2028
+
2257
2029
  // src/components/form/form-field.tsx
2258
2030
  import { jsx as jsx36, jsxs as jsxs22 } from "react/jsx-runtime";
2259
2031
  function FormField({
@@ -2321,7 +2093,7 @@ function FormSection({
2321
2093
  }
2322
2094
 
2323
2095
  // src/components/form/local-input.tsx
2324
- import { useState as useState5, useRef as useRef3, useEffect as useEffect4 } from "react";
2096
+ import { useState as useState5, useRef as useRef3, useEffect as useEffect3 } from "react";
2325
2097
  import { jsx as jsx39 } from "react/jsx-runtime";
2326
2098
  function LocalInput({
2327
2099
  value,
@@ -2333,7 +2105,7 @@ function LocalInput({
2333
2105
  const ref = useRef3(null);
2334
2106
  const onChangeRef = useRef3(onChange);
2335
2107
  onChangeRef.current = onChange;
2336
- useEffect4(() => {
2108
+ useEffect3(() => {
2337
2109
  if (document.activeElement !== ref.current) {
2338
2110
  setLocal(String(value ?? ""));
2339
2111
  }
@@ -2539,7 +2311,7 @@ function ResponsiveSizeField({
2539
2311
  }
2540
2312
 
2541
2313
  // src/components/feedback/alert.tsx
2542
- import { AlertCircle, CheckCircle2, Info, AlertTriangle, X as X2 } from "lucide-react";
2314
+ import { AlertCircle, CheckCircle2, Info, AlertTriangle, X as X3 } from "lucide-react";
2543
2315
  import { jsx as jsx43, jsxs as jsxs28 } from "react/jsx-runtime";
2544
2316
  var variantConfig = {
2545
2317
  info: {
@@ -2589,7 +2361,7 @@ function Alert({
2589
2361
  {
2590
2362
  onClick: onDismiss,
2591
2363
  className: "absolute right-3 top-3 rounded-md p-1 opacity-70 hover:opacity-100",
2592
- children: /* @__PURE__ */ jsx43(X2, { className: "h-4 w-4" })
2364
+ children: /* @__PURE__ */ jsx43(X3, { className: "h-4 w-4" })
2593
2365
  }
2594
2366
  )
2595
2367
  ]
@@ -2718,7 +2490,7 @@ function PostStatusBadge({
2718
2490
  }
2719
2491
 
2720
2492
  // src/components/content/post-list-table.tsx
2721
- import * as React21 from "react";
2493
+ import * as React20 from "react";
2722
2494
 
2723
2495
  // src/components/Skeleton.tsx
2724
2496
  import { jsx as jsx47 } from "react/jsx-runtime";
@@ -2808,10 +2580,10 @@ function getInitials(name) {
2808
2580
  return name.split(" ").slice(0, 2).map((w) => w[0]?.toUpperCase() ?? "").join("");
2809
2581
  }
2810
2582
  function RowActions({ post, onEdit, onDelete, onDuplicate, onStatusChange }) {
2811
- const [open, setOpen] = React21.useState(false);
2812
- const [statusOpen, setStatusOpen] = React21.useState(false);
2813
- const ref = React21.useRef(null);
2814
- React21.useEffect(() => {
2583
+ const [open, setOpen] = React20.useState(false);
2584
+ const [statusOpen, setStatusOpen] = React20.useState(false);
2585
+ const ref = React20.useRef(null);
2586
+ React20.useEffect(() => {
2815
2587
  if (!open) return;
2816
2588
  function handleClick(e) {
2817
2589
  if (ref.current && !ref.current.contains(e.target)) {
@@ -3323,7 +3095,7 @@ function SlugInput({
3323
3095
  }
3324
3096
 
3325
3097
  // src/components/content/post-sidebar-section.tsx
3326
- import * as React22 from "react";
3098
+ import * as React21 from "react";
3327
3099
  import { jsx as jsx52, jsxs as jsxs35 } from "react/jsx-runtime";
3328
3100
  function IconChevronDown() {
3329
3101
  return /* @__PURE__ */ jsx52(
@@ -3347,7 +3119,7 @@ function PostSidebarSection({
3347
3119
  defaultOpen = true,
3348
3120
  className
3349
3121
  }) {
3350
- const [open, setOpen] = React22.useState(defaultOpen);
3122
+ const [open, setOpen] = React21.useState(defaultOpen);
3351
3123
  return /* @__PURE__ */ jsxs35("div", { className: cn("border-b border-border", className), children: [
3352
3124
  /* @__PURE__ */ jsxs35(
3353
3125
  "button",
@@ -3383,7 +3155,7 @@ function PostSidebarSection({
3383
3155
  }
3384
3156
 
3385
3157
  // src/components/ui/hsl-color-input.tsx
3386
- import { useState as useState11, useRef as useRef6, useEffect as useEffect7, useCallback as useCallback2 } from "react";
3158
+ import { useState as useState11, useRef as useRef6, useEffect as useEffect6, useCallback as useCallback2 } from "react";
3387
3159
  import Color2 from "color";
3388
3160
  import * as Popover2 from "@radix-ui/react-popover";
3389
3161
 
@@ -3394,7 +3166,7 @@ import {
3394
3166
  memo,
3395
3167
  useCallback,
3396
3168
  useContext,
3397
- useEffect as useEffect6,
3169
+ useEffect as useEffect5,
3398
3170
  useMemo as useMemo2,
3399
3171
  useRef as useRef5,
3400
3172
  useState as useState10
@@ -3427,7 +3199,7 @@ var ColorPicker = ({
3427
3199
  const [alpha, setAlphaState] = useState10(initial.alpha() * 100);
3428
3200
  const [mode, setMode] = useState10("HEX");
3429
3201
  const notifyRef = useRef5(onChange);
3430
- useEffect6(() => {
3202
+ useEffect5(() => {
3431
3203
  notifyRef.current = onChange;
3432
3204
  }, [onChange]);
3433
3205
  const notify = useCallback((h, s, l, a) => {
@@ -3475,7 +3247,7 @@ var ColorPickerSelection = memo(({ className, ...props }) => {
3475
3247
  setSaturation(x * 100);
3476
3248
  setLightness((x < 0.01 ? 100 : 50 + 50 * (1 - x)) * (1 - y));
3477
3249
  }, [isDragging, setSaturation, setLightness]);
3478
- useEffect6(() => {
3250
+ useEffect5(() => {
3479
3251
  if (!isDragging) return;
3480
3252
  const up = () => setIsDragging(false);
3481
3253
  window.addEventListener("pointermove", handleMove);
@@ -3599,7 +3371,7 @@ var ColorPickerFormat = ({ className, ...props }) => {
3599
3371
  }
3600
3372
  return "";
3601
3373
  })();
3602
- useEffect6(() => {
3374
+ useEffect5(() => {
3603
3375
  if (!focused) setLocalVal(computedVal);
3604
3376
  }, [computedVal, focused]);
3605
3377
  const tryApply = (raw) => {
@@ -3697,7 +3469,7 @@ function hexToHsl(hex) {
3697
3469
  }
3698
3470
  function ColorReader({ onHexChange }) {
3699
3471
  const { hue, saturation, lightness, alpha } = useColorPicker();
3700
- useEffect7(() => {
3472
+ useEffect6(() => {
3701
3473
  try {
3702
3474
  const hex = Color2.hsl(hue, saturation, lightness).alpha(alpha / 100).hex();
3703
3475
  onHexChange(hex);
@@ -3712,10 +3484,10 @@ function HslColorInput({ value, onChange, className, inputClassName, disabled })
3712
3484
  const cssColor = value ? `hsl(${value})` : "transparent";
3713
3485
  const pendingHexRef = useRef6(hexValue);
3714
3486
  const onChangeRef = useRef6(onChange);
3715
- useEffect7(() => {
3487
+ useEffect6(() => {
3716
3488
  onChangeRef.current = onChange;
3717
3489
  }, [onChange]);
3718
- useEffect7(() => {
3490
+ useEffect6(() => {
3719
3491
  if (open) pendingHexRef.current = hexValue;
3720
3492
  }, [open, hexValue]);
3721
3493
  const handleHexChange = useCallback2((hex) => {
@@ -4012,7 +3784,7 @@ function AuthField({ label, error, hint, rightLabel, id, ...props }) {
4012
3784
  }
4013
3785
 
4014
3786
  // src/components/auth/AuthButton.tsx
4015
- import { Fragment as Fragment5, jsx as jsx60, jsxs as jsxs42 } from "react/jsx-runtime";
3787
+ import { Fragment as Fragment4, jsx as jsx60, jsxs as jsxs42 } from "react/jsx-runtime";
4016
3788
  function AuthButton({
4017
3789
  loading,
4018
3790
  variant = "primary",
@@ -4067,7 +3839,7 @@ function AuthButton({
4067
3839
  e.currentTarget.style.filter = "none";
4068
3840
  },
4069
3841
  ...props,
4070
- children: loading ? /* @__PURE__ */ jsxs42(Fragment5, { children: [
3842
+ children: loading ? /* @__PURE__ */ jsxs42(Fragment4, { children: [
4071
3843
  /* @__PURE__ */ jsx60(
4072
3844
  "span",
4073
3845
  {
@@ -4197,8 +3969,6 @@ export {
4197
3969
  LoadingSpinner,
4198
3970
  LocalInput,
4199
3971
  MediaCard,
4200
- MediaGrid,
4201
- MediaPickerDialog,
4202
3972
  NotificationBell,
4203
3973
  Page,
4204
3974
  PageSection,
@@ -4244,6 +4014,8 @@ export {
4244
4014
  TooltipProvider,
4245
4015
  TooltipTrigger,
4246
4016
  TopBar,
4017
+ UploadProgressPanel,
4018
+ UploadZone,
4247
4019
  badgeVariants,
4248
4020
  buttonVariants,
4249
4021
  cn,