@spacesync/client 0.1.6 → 0.1.9

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.
@@ -1,4 +1,4 @@
1
- import { ResponsiveDialog, ResponsiveDialogContent, ResponsiveDialogTitle } from './chunk-H3CNZCYU.js';
1
+ import { ResponsiveDialog, ResponsiveDialogContent, ResponsiveDialogTitle } from './chunk-F7MU5T46.js';
2
2
  import './chunk-RV5PMZYZ.js';
3
3
  import './chunk-L65F4EMK.js';
4
4
  import './chunk-PBNBLCC2.js';
@@ -4,11 +4,12 @@ interface FloorSwitcherFloor {
4
4
  id: string;
5
5
  name: string;
6
6
  }
7
- declare function FloorSwitcher({ floors, activeFloorId, onFloorChange, onAddFloor, }: {
7
+ declare function FloorSwitcher({ floors, activeFloorId, onFloorChange, onAddFloor, onDeleteFloor, }: {
8
8
  floors: FloorSwitcherFloor[];
9
9
  activeFloorId: string;
10
10
  onFloorChange: (floorId: string) => void;
11
11
  onAddFloor?: (name: string) => void;
12
+ onDeleteFloor?: (floorId: string) => void;
12
13
  }): React.JSX.Element;
13
14
 
14
15
  export { FloorSwitcher, type FloorSwitcherFloor };
@@ -1,4 +1,4 @@
1
- import { DropdownMenu, DropdownMenuTrigger, DropdownMenuContent, DropdownMenuItem, DropdownMenuSeparator } from './chunk-H3CNZCYU.js';
1
+ import { DropdownMenu, DropdownMenuTrigger, DropdownMenuContent, DropdownMenuItem, DropdownMenuSeparator } from './chunk-F7MU5T46.js';
2
2
  import { Label } from './chunk-RV5PMZYZ.js';
3
3
  import './chunk-L65F4EMK.js';
4
4
  import './chunk-PBNBLCC2.js';
@@ -10,7 +10,7 @@ import { Button } from './chunk-VHINHHZQ.js';
10
10
  import { shellTopBarMutedIconClass, shellTopBarSecondaryButtonClass } from './chunk-FCY5FYJB.js';
11
11
  import './chunk-OWNUMPUC.js';
12
12
  import { cn } from './chunk-DMPSQ22Q.js';
13
- import { Layers, ChevronDown, Check, Plus } from 'lucide-react';
13
+ import { Layers, ChevronDown, Check, Plus, Trash2 } from 'lucide-react';
14
14
  import { useState, useEffect } from 'react';
15
15
  import { jsxs, Fragment, jsx } from 'react/jsx-runtime';
16
16
 
@@ -18,11 +18,13 @@ function FloorSwitcher({
18
18
  floors,
19
19
  activeFloorId,
20
20
  onFloorChange,
21
- onAddFloor
21
+ onAddFloor,
22
+ onDeleteFloor
22
23
  }) {
23
24
  const activeFloor = floors.find((f) => f.id === activeFloorId) ?? floors[0];
24
25
  const [addOpen, setAddOpen] = useState(false);
25
26
  const [name, setName] = useState("");
27
+ const canDeleteFloor = Boolean(onDeleteFloor) && floors.length > 1;
26
28
  useEffect(() => {
27
29
  if (addOpen) {
28
30
  setName(`Floor ${floors.length + 1}`);
@@ -63,12 +65,23 @@ function FloorSwitcher({
63
65
  },
64
66
  floor.id
65
67
  )),
66
- onAddFloor ? /* @__PURE__ */ jsxs(Fragment, { children: [
68
+ onAddFloor || canDeleteFloor ? /* @__PURE__ */ jsxs(Fragment, { children: [
67
69
  /* @__PURE__ */ jsx(DropdownMenuSeparator, {}),
68
- /* @__PURE__ */ jsxs(DropdownMenuItem, { onClick: () => setAddOpen(true), children: [
70
+ onAddFloor ? /* @__PURE__ */ jsxs(DropdownMenuItem, { onClick: () => setAddOpen(true), children: [
69
71
  /* @__PURE__ */ jsx(Plus, { size: 14, className: "mr-1.5" }),
70
72
  "Add floor\u2026"
71
- ] })
73
+ ] }) : null,
74
+ canDeleteFloor ? /* @__PURE__ */ jsxs(
75
+ DropdownMenuItem,
76
+ {
77
+ variant: "destructive",
78
+ onClick: () => onDeleteFloor?.(activeFloorId),
79
+ children: [
80
+ /* @__PURE__ */ jsx(Trash2, { size: 14, className: "mr-1.5" }),
81
+ "Delete floor\u2026"
82
+ ]
83
+ }
84
+ ) : null
72
85
  ] }) : null
73
86
  ] })
74
87
  ] }),
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/live-space/floor-switcher.tsx"],"names":[],"mappings":";;;;;;;;;;;;;;;;AA0BO,SAAS,aAAA,CAAc;AAAA,EAC5B,MAAA;AAAA,EACA,aAAA;AAAA,EACA,aAAA;AAAA,EACA;AACF,CAAA,EAKG;AACD,EAAA,MAAM,WAAA,GAAc,MAAA,CAAO,IAAA,CAAK,CAAC,CAAA,KAAM,EAAE,EAAA,KAAO,aAAa,CAAA,IAAK,MAAA,CAAO,CAAC,CAAA;AAC1E,EAAA,MAAM,CAAC,OAAA,EAAS,UAAU,CAAA,GAAI,SAAS,KAAK,CAAA;AAC5C,EAAA,MAAM,CAAC,IAAA,EAAM,OAAO,CAAA,GAAI,SAAS,EAAE,CAAA;AAEnC,EAAA,SAAA,CAAU,MAAM;AACd,IAAA,IAAI,OAAA,EAAS;AACX,MAAA,OAAA,CAAQ,CAAA,MAAA,EAAS,MAAA,CAAO,MAAA,GAAS,CAAC,CAAA,CAAE,CAAA;AAAA,IACtC;AAAA,EACF,CAAA,EAAG,CAAC,OAAA,EAAS,MAAA,CAAO,MAAM,CAAC,CAAA;AAE3B,EAAA,MAAM,YAAY,MAAM;AACtB,IAAA,UAAA,GAAa,KAAK,IAAA,EAAK,IAAK,SAAS,MAAA,CAAO,MAAA,GAAS,CAAC,CAAA,CAAE,CAAA;AACxD,IAAA,UAAA,CAAW,KAAK,CAAA;AAAA,EAClB,CAAA;AAEA,EAAA,uBACE,IAAA,CAAA,QAAA,EAAA,EACE,QAAA,EAAA;AAAA,oBAAA,IAAA,CAAC,YAAA,EAAA,EACC,QAAA,EAAA;AAAA,sBAAA,GAAA,CAAC,mBAAA,EAAA,EAAoB,SAAO,IAAA,EAC1B,QAAA,kBAAA,IAAA;AAAA,QAAC,QAAA;AAAA,QAAA;AAAA,UACC,IAAA,EAAK,QAAA;AAAA,UACL,SAAA,EAAW,EAAA;AAAA,YACT,+BAAA;AAAA,YACA;AAAA,WACF;AAAA,UACA,YAAA,EAAW,cAAA;AAAA,UAEX,QAAA,EAAA;AAAA,4BAAA,GAAA,CAAC,MAAA,EAAA,EAAO,MAAM,EAAA,EAAI,SAAA,EAAW,GAAG,UAAA,EAAY,yBAAyB,CAAA,EAAG,aAAA,EAAW,IAAA,EAAC,CAAA;AAAA,gCACnF,MAAA,EAAA,EAAK,SAAA,EAAU,UAAA,EAAY,QAAA,EAAA,WAAA,EAAa,QAAQ,OAAA,EAAQ,CAAA;AAAA,4BACzD,GAAA,CAAC,WAAA,EAAA,EAAY,IAAA,EAAM,EAAA,EAAI,SAAA,EAAW,GAAG,UAAA,EAAY,yBAAyB,CAAA,EAAG,aAAA,EAAW,IAAA,EAAC;AAAA;AAAA;AAAA,OAC3F,EACF,CAAA;AAAA,sBACA,IAAA,CAAC,mBAAA,EAAA,EAAoB,KAAA,EAAM,OAAA,EAAQ,WAAU,MAAA,EAC1C,QAAA,EAAA;AAAA,QAAA,MAAA,CAAO,GAAA,CAAI,CAAC,KAAA,qBACX,IAAA;AAAA,UAAC,gBAAA;AAAA,UAAA;AAAA,YAEC,OAAA,EAAS,MAAM,aAAA,CAAc,KAAA,CAAM,EAAE,CAAA;AAAA,YACrC,SAAA,EAAU,yCAAA;AAAA,YAEV,QAAA,EAAA;AAAA,8BAAA,GAAA,CAAC,MAAA,EAAA,EAAK,SAAA,EAAU,UAAA,EAAY,QAAA,EAAA,KAAA,CAAM,IAAA,EAAK,CAAA;AAAA,cACtC,MAAM,EAAA,KAAO,aAAA,mBACZ,GAAA,CAAC,KAAA,EAAA,EAAM,MAAM,EAAA,EAAI,SAAA,EAAU,wBAAA,EAAyB,aAAA,EAAW,MAAC,CAAA,mBAEhE,GAAA,CAAC,UAAK,SAAA,EAAU,mBAAA,EAAoB,eAAW,IAAA,EAAC;AAAA;AAAA,WAAA;AAAA,UAR7C,KAAA,CAAM;AAAA,SAWd,CAAA;AAAA,QACA,6BACC,IAAA,CAAA,QAAA,EAAA,EACE,QAAA,EAAA;AAAA,0BAAA,GAAA,CAAC,qBAAA,EAAA,EAAsB,CAAA;AAAA,+BACtB,gBAAA,EAAA,EAAiB,OAAA,EAAS,MAAM,UAAA,CAAW,IAAI,CAAA,EAC9C,QAAA,EAAA;AAAA,4BAAA,GAAA,CAAC,IAAA,EAAA,EAAK,IAAA,EAAM,EAAA,EAAI,SAAA,EAAU,QAAA,EAAS,CAAA;AAAA,YAAE;AAAA,WAAA,EAEvC;AAAA,SAAA,EACF,CAAA,GACE;AAAA,OAAA,EACN;AAAA,KAAA,EACF,CAAA;AAAA,IAEC,UAAA,mBACC,GAAA,CAAC,MAAA,EAAA,EAAO,IAAA,EAAM,OAAA,EAAS,cAAc,UAAA,EACnC,QAAA,kBAAA,IAAA,CAAC,aAAA,EAAA,EAAc,SAAA,EAAU,2BAAA,EACvB,QAAA,EAAA;AAAA,sBAAA,IAAA,CAAC,YAAA,EAAA,EACC,QAAA,EAAA;AAAA,wBAAA,GAAA,CAAC,eAAY,QAAA,EAAA,WAAA,EAAS,CAAA;AAAA,wBACtB,GAAA,CAAC,qBAAkB,QAAA,EAAA,0DAAA,EAEnB;AAAA,OAAA,EACF,CAAA;AAAA,sBACA,IAAA,CAAC,KAAA,EAAA,EAAI,SAAA,EAAU,MAAA,EACb,QAAA,EAAA;AAAA,wBAAA,GAAA,CAAC,KAAA,EAAA,EAAM,OAAA,EAAQ,gBAAA,EAAiB,QAAA,EAAA,MAAA,EAAI,CAAA;AAAA,wBACpC,GAAA;AAAA,UAAC,KAAA;AAAA,UAAA;AAAA,YACC,EAAA,EAAG,gBAAA;AAAA,YACH,KAAA,EAAO,IAAA;AAAA,YACP,UAAU,CAAC,CAAA,KAAM,OAAA,CAAQ,CAAA,CAAE,OAAO,KAAK,CAAA;AAAA,YACvC,WAAA,EAAY,cAAA;AAAA,YACZ,SAAA,EAAU,UAAA;AAAA,YACV,SAAA,EAAS,IAAA;AAAA,YACT,SAAA,EAAW,CAAC,CAAA,KAAM;AAChB,cAAA,IAAI,CAAA,CAAE,GAAA,KAAQ,OAAA,EAAS,SAAA,EAAU;AAAA,YACnC;AAAA;AAAA;AACF,OAAA,EACF,CAAA;AAAA,2BACC,YAAA,EAAA,EACC,QAAA,EAAA;AAAA,wBAAA,GAAA,CAAC,MAAA,EAAA,EAAO,IAAA,EAAK,QAAA,EAAS,OAAA,EAAQ,SAAA,EAAU,SAAS,MAAM,UAAA,CAAW,KAAK,CAAA,EAAG,QAAA,EAAA,QAAA,EAE1E,CAAA;AAAA,4BACC,MAAA,EAAA,EAAO,IAAA,EAAK,QAAA,EAAS,OAAA,EAAS,WAAW,QAAA,EAAA,WAAA,EAE1C;AAAA,OAAA,EACF;AAAA,KAAA,EACF,GACF,CAAA,GACE;AAAA,GAAA,EACN,CAAA;AAEJ","file":"floor-switcher.js","sourcesContent":["import { Check, ChevronDown, Layers, Plus } from \"lucide-react\";\nimport { shellTopBarMutedIconClass, shellTopBarSecondaryButtonClass } from \"../shell/shell-top-bar-chrome\";\nimport {\n Button,\n cn,\n Dialog,\n DialogContent,\n DialogDescription,\n DialogFooter,\n DialogHeader,\n DialogTitle,\n DropdownMenu,\n DropdownMenuContent,\n DropdownMenuItem,\n DropdownMenuSeparator,\n DropdownMenuTrigger,\n Input,\n Label,\n} from \"../ui\";\nimport { useEffect, useState } from \"react\";\n\nexport interface FloorSwitcherFloor {\n id: string;\n name: string;\n}\n\nexport function FloorSwitcher({\n floors,\n activeFloorId,\n onFloorChange,\n onAddFloor,\n}: {\n floors: FloorSwitcherFloor[];\n activeFloorId: string;\n onFloorChange: (floorId: string) => void;\n onAddFloor?: (name: string) => void;\n}) {\n const activeFloor = floors.find((f) => f.id === activeFloorId) ?? floors[0];\n const [addOpen, setAddOpen] = useState(false);\n const [name, setName] = useState(\"\");\n\n useEffect(() => {\n if (addOpen) {\n setName(`Floor ${floors.length + 1}`);\n }\n }, [addOpen, floors.length]);\n\n const handleAdd = () => {\n onAddFloor?.(name.trim() || `Floor ${floors.length + 1}`);\n setAddOpen(false);\n };\n\n return (\n <>\n <DropdownMenu>\n <DropdownMenuTrigger asChild>\n <button\n type=\"button\"\n className={cn(\n shellTopBarSecondaryButtonClass,\n \"min-w-0 max-w-[10rem] outline-none focus-visible:ring-2 focus-visible:ring-blue-500/30\",\n )}\n aria-label=\"Switch floor\"\n >\n <Layers size={12} className={cn(\"shrink-0\", shellTopBarMutedIconClass)} aria-hidden />\n <span className=\"truncate\">{activeFloor?.name ?? \"Floor\"}</span>\n <ChevronDown size={12} className={cn(\"shrink-0\", shellTopBarMutedIconClass)} aria-hidden />\n </button>\n </DropdownMenuTrigger>\n <DropdownMenuContent align=\"start\" className=\"w-48\">\n {floors.map((floor) => (\n <DropdownMenuItem\n key={floor.id}\n onClick={() => onFloorChange(floor.id)}\n className=\"flex items-center justify-between gap-2\"\n >\n <span className=\"truncate\">{floor.name}</span>\n {floor.id === activeFloorId ? (\n <Check size={14} className=\"shrink-0 text-blue-600\" aria-hidden />\n ) : (\n <span className=\"size-3.5 shrink-0\" aria-hidden />\n )}\n </DropdownMenuItem>\n ))}\n {onAddFloor ? (\n <>\n <DropdownMenuSeparator />\n <DropdownMenuItem onClick={() => setAddOpen(true)}>\n <Plus size={14} className=\"mr-1.5\" />\n Add floor…\n </DropdownMenuItem>\n </>\n ) : null}\n </DropdownMenuContent>\n </DropdownMenu>\n\n {onAddFloor ? (\n <Dialog open={addOpen} onOpenChange={setAddOpen}>\n <DialogContent className=\"sm:max-w-md border-border\">\n <DialogHeader>\n <DialogTitle>Add floor</DialogTitle>\n <DialogDescription>\n Create a new floor with its own resources and 2D layout.\n </DialogDescription>\n </DialogHeader>\n <div className=\"py-1\">\n <Label htmlFor=\"new-floor-name\">Name</Label>\n <Input\n id=\"new-floor-name\"\n value={name}\n onChange={(e) => setName(e.target.value)}\n placeholder=\"e.g. Floor 2\"\n className=\"mt-2 h-9\"\n autoFocus\n onKeyDown={(e) => {\n if (e.key === \"Enter\") handleAdd();\n }}\n />\n </div>\n <DialogFooter>\n <Button type=\"button\" variant=\"outline\" onClick={() => setAddOpen(false)}>\n Cancel\n </Button>\n <Button type=\"button\" onClick={handleAdd}>\n Add floor\n </Button>\n </DialogFooter>\n </DialogContent>\n </Dialog>\n ) : null}\n </>\n );\n}\n"]}
1
+ {"version":3,"sources":["../src/live-space/floor-switcher.tsx"],"names":[],"mappings":"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","file":"floor-switcher.js","sourcesContent":["import { Check, ChevronDown, Layers, Plus, Trash2 } from \"lucide-react\";\nimport { shellTopBarMutedIconClass, shellTopBarSecondaryButtonClass } from \"../shell/shell-top-bar-chrome\";\nimport {\n Button,\n cn,\n Dialog,\n DialogContent,\n DialogDescription,\n DialogFooter,\n DialogHeader,\n DialogTitle,\n DropdownMenu,\n DropdownMenuContent,\n DropdownMenuItem,\n DropdownMenuSeparator,\n DropdownMenuTrigger,\n Input,\n Label,\n} from \"../ui\";\nimport { useEffect, useState } from \"react\";\n\nexport interface FloorSwitcherFloor {\n id: string;\n name: string;\n}\n\nexport function FloorSwitcher({\n floors,\n activeFloorId,\n onFloorChange,\n onAddFloor,\n onDeleteFloor,\n}: {\n floors: FloorSwitcherFloor[];\n activeFloorId: string;\n onFloorChange: (floorId: string) => void;\n onAddFloor?: (name: string) => void;\n onDeleteFloor?: (floorId: string) => void;\n}) {\n const activeFloor = floors.find((f) => f.id === activeFloorId) ?? floors[0];\n const [addOpen, setAddOpen] = useState(false);\n const [name, setName] = useState(\"\");\n const canDeleteFloor = Boolean(onDeleteFloor) && floors.length > 1;\n\n useEffect(() => {\n if (addOpen) {\n setName(`Floor ${floors.length + 1}`);\n }\n }, [addOpen, floors.length]);\n\n const handleAdd = () => {\n onAddFloor?.(name.trim() || `Floor ${floors.length + 1}`);\n setAddOpen(false);\n };\n\n return (\n <>\n <DropdownMenu>\n <DropdownMenuTrigger asChild>\n <button\n type=\"button\"\n className={cn(\n shellTopBarSecondaryButtonClass,\n \"min-w-0 max-w-[10rem] outline-none focus-visible:ring-2 focus-visible:ring-blue-500/30\",\n )}\n aria-label=\"Switch floor\"\n >\n <Layers size={12} className={cn(\"shrink-0\", shellTopBarMutedIconClass)} aria-hidden />\n <span className=\"truncate\">{activeFloor?.name ?? \"Floor\"}</span>\n <ChevronDown size={12} className={cn(\"shrink-0\", shellTopBarMutedIconClass)} aria-hidden />\n </button>\n </DropdownMenuTrigger>\n <DropdownMenuContent align=\"start\" className=\"w-48\">\n {floors.map((floor) => (\n <DropdownMenuItem\n key={floor.id}\n onClick={() => onFloorChange(floor.id)}\n className=\"flex items-center justify-between gap-2\"\n >\n <span className=\"truncate\">{floor.name}</span>\n {floor.id === activeFloorId ? (\n <Check size={14} className=\"shrink-0 text-blue-600\" aria-hidden />\n ) : (\n <span className=\"size-3.5 shrink-0\" aria-hidden />\n )}\n </DropdownMenuItem>\n ))}\n {onAddFloor || canDeleteFloor ? (\n <>\n <DropdownMenuSeparator />\n {onAddFloor ? (\n <DropdownMenuItem onClick={() => setAddOpen(true)}>\n <Plus size={14} className=\"mr-1.5\" />\n Add floor…\n </DropdownMenuItem>\n ) : null}\n {canDeleteFloor ? (\n <DropdownMenuItem\n variant=\"destructive\"\n onClick={() => onDeleteFloor?.(activeFloorId)}\n >\n <Trash2 size={14} className=\"mr-1.5\" />\n Delete floor…\n </DropdownMenuItem>\n ) : null}\n </>\n ) : null}\n </DropdownMenuContent>\n </DropdownMenu>\n\n {onAddFloor ? (\n <Dialog open={addOpen} onOpenChange={setAddOpen}>\n <DialogContent className=\"sm:max-w-md border-border\">\n <DialogHeader>\n <DialogTitle>Add floor</DialogTitle>\n <DialogDescription>\n Create a new floor with its own resources and 2D layout.\n </DialogDescription>\n </DialogHeader>\n <div className=\"py-1\">\n <Label htmlFor=\"new-floor-name\">Name</Label>\n <Input\n id=\"new-floor-name\"\n value={name}\n onChange={(e) => setName(e.target.value)}\n placeholder=\"e.g. Floor 2\"\n className=\"mt-2 h-9\"\n autoFocus\n onKeyDown={(e) => {\n if (e.key === \"Enter\") handleAdd();\n }}\n />\n </div>\n <DialogFooter>\n <Button type=\"button\" variant=\"outline\" onClick={() => setAddOpen(false)}>\n Cancel\n </Button>\n <Button type=\"button\" onClick={handleAdd}>\n Add floor\n </Button>\n </DialogFooter>\n </DialogContent>\n </Dialog>\n ) : null}\n </>\n );\n}\n"]}
@@ -1,6 +1,6 @@
1
1
  import * as React from 'react';
2
2
 
3
- declare function ImagePickerField({ label, value, onFileSelected, onRemove, description, previewAlt, disabled, }: {
3
+ declare function ImagePickerField({ label, value, onFileSelected, onRemove, description, previewAlt, disabled, busy, }: {
4
4
  label?: string;
5
5
  /** Preview URL supplied by the parent (`blob:`, signed HTTPS, or empty). */
6
6
  value?: string;
@@ -9,6 +9,7 @@ declare function ImagePickerField({ label, value, onFileSelected, onRemove, desc
9
9
  description?: string;
10
10
  previewAlt?: string;
11
11
  disabled?: boolean;
12
+ busy?: boolean;
12
13
  }): React.JSX.Element;
13
14
 
14
15
  export { ImagePickerField };
@@ -1,9 +1,52 @@
1
1
  import { Button } from './chunk-VHINHHZQ.js';
2
- import './chunk-DMPSQ22Q.js';
3
- import { useRef } from 'react';
4
- import { ImagePlus, Trash2, Upload } from 'lucide-react';
2
+ import { cn } from './chunk-DMPSQ22Q.js';
3
+ import { useRef, useState, useEffect } from 'react';
4
+ import { ImagePlus, Trash2, Loader2, Upload } from 'lucide-react';
5
5
  import { jsxs, jsx } from 'react/jsx-runtime';
6
6
 
7
+ function ImageBusyOverlay({ label = "Uploading\u2026" }) {
8
+ return /* @__PURE__ */ jsx(
9
+ "div",
10
+ {
11
+ className: "absolute inset-0 z-10 flex items-center justify-center rounded-[inherit] bg-background/75 backdrop-blur-[2px] animate-in fade-in duration-200",
12
+ role: "status",
13
+ "aria-live": "polite",
14
+ children: /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2 rounded-full border border-border bg-card px-3 py-1.5 text-sm text-foreground shadow-sm", children: [
15
+ /* @__PURE__ */ jsx(Loader2, { size: 14, className: "animate-spin text-blue-600" }),
16
+ label
17
+ ] })
18
+ }
19
+ );
20
+ }
21
+ function PreviewImage({ src, alt }) {
22
+ const [ready, setReady] = useState(false);
23
+ useEffect(() => {
24
+ setReady(false);
25
+ }, [src]);
26
+ return /* @__PURE__ */ jsxs("div", { className: "relative aspect-[4/3] w-full overflow-hidden bg-muted", children: [
27
+ !ready ? /* @__PURE__ */ jsx(
28
+ "span",
29
+ {
30
+ className: "absolute inset-0 z-[1] flex items-center justify-center animate-in fade-in duration-200",
31
+ role: "status",
32
+ "aria-label": "Loading image",
33
+ children: /* @__PURE__ */ jsx(Loader2, { size: 18, className: "animate-spin text-muted-foreground" })
34
+ }
35
+ ) : null,
36
+ /* @__PURE__ */ jsx(
37
+ "img",
38
+ {
39
+ src,
40
+ alt,
41
+ onLoad: () => setReady(true),
42
+ className: cn(
43
+ "aspect-[4/3] w-full object-cover transition-opacity duration-300 ease-out",
44
+ ready ? "opacity-100" : "opacity-0"
45
+ )
46
+ }
47
+ )
48
+ ] });
49
+ }
7
50
  function ImagePickerField({
8
51
  label = "Image",
9
52
  value,
@@ -11,18 +54,21 @@ function ImagePickerField({
11
54
  onRemove,
12
55
  description,
13
56
  previewAlt = "Selected image",
14
- disabled = false
57
+ disabled = false,
58
+ busy = false
15
59
  }) {
16
60
  const inputRef = useRef(null);
61
+ const locked = disabled || busy;
17
62
  const handleFileChange = (files) => {
18
63
  const file = files?.[0];
19
- if (!file || disabled) return;
64
+ if (!file || locked) return;
20
65
  onFileSelected(file);
21
66
  };
22
- return /* @__PURE__ */ jsxs("div", { className: "flex flex-col gap-1.5", children: [
23
- /* @__PURE__ */ jsx("label", { className: "text-xs font-medium text-muted-foreground", children: label }),
24
- value ? /* @__PURE__ */ jsxs("div", { className: "max-w-sm overflow-hidden rounded-xl border border-border bg-card", children: [
25
- /* @__PURE__ */ jsx("img", { src: value, alt: previewAlt, className: "aspect-[4/3] w-full object-cover" }),
67
+ return /* @__PURE__ */ jsxs("div", { className: "flex flex-col gap-1.5", "aria-busy": busy, children: [
68
+ label ? /* @__PURE__ */ jsx("label", { className: "text-xs font-medium text-muted-foreground", children: label }) : null,
69
+ value ? /* @__PURE__ */ jsxs("div", { className: "relative max-w-sm overflow-hidden rounded-xl border border-border bg-card", children: [
70
+ /* @__PURE__ */ jsx(PreviewImage, { src: value, alt: previewAlt }),
71
+ busy ? /* @__PURE__ */ jsx(ImageBusyOverlay, {}) : null,
26
72
  /* @__PURE__ */ jsxs("div", { className: "flex items-center justify-between gap-2 border-t border-border px-3 py-2", children: [
27
73
  /* @__PURE__ */ jsxs(
28
74
  Button,
@@ -30,7 +76,7 @@ function ImagePickerField({
30
76
  type: "button",
31
77
  variant: "link",
32
78
  size: "sm",
33
- disabled,
79
+ disabled: locked,
34
80
  onClick: () => inputRef.current?.click(),
35
81
  className: "h-auto gap-1.5 p-0 text-xs font-medium text-blue-600 dark:text-blue-400",
36
82
  children: [
@@ -45,7 +91,7 @@ function ImagePickerField({
45
91
  type: "button",
46
92
  variant: "link",
47
93
  size: "sm",
48
- disabled,
94
+ disabled: locked,
49
95
  onClick: onRemove,
50
96
  className: "h-auto gap-1.5 p-0 text-xs font-medium text-red-600",
51
97
  children: [
@@ -55,28 +101,31 @@ function ImagePickerField({
55
101
  }
56
102
  ) : null
57
103
  ] })
58
- ] }) : /* @__PURE__ */ jsxs(
59
- Button,
60
- {
61
- type: "button",
62
- variant: "outline",
63
- disabled,
64
- onClick: () => inputRef.current?.click(),
65
- className: "flex aspect-[4/3] h-auto max-w-sm w-full flex-col items-center justify-center gap-2 rounded-xl border-dashed bg-muted/50 px-4 py-5 text-center shadow-none hover:border-blue-300 hover:bg-blue-50/30 dark:hover:bg-blue-950/25",
66
- children: [
67
- /* @__PURE__ */ jsx("span", { className: "flex h-9 w-9 items-center justify-center rounded-xl border border-border bg-card", children: /* @__PURE__ */ jsx(Upload, { size: 15, className: "text-muted-foreground" }) }),
68
- /* @__PURE__ */ jsx("span", { className: "text-xs font-medium text-foreground", children: "Choose image" }),
69
- /* @__PURE__ */ jsx("span", { className: "text-[11px] font-normal text-muted-foreground whitespace-normal", children: description ?? "JPG, PNG, WebP or GIF." })
70
- ]
71
- }
72
- ),
104
+ ] }) : /* @__PURE__ */ jsxs("div", { className: "relative max-w-sm overflow-hidden rounded-xl", children: [
105
+ /* @__PURE__ */ jsxs(
106
+ Button,
107
+ {
108
+ type: "button",
109
+ variant: "outline",
110
+ disabled: locked,
111
+ onClick: () => inputRef.current?.click(),
112
+ className: "flex aspect-[4/3] h-auto w-full flex-col items-center justify-center gap-2 rounded-xl border-dashed bg-muted/50 px-4 py-5 text-center shadow-none hover:border-blue-300 hover:bg-blue-50/30 dark:hover:bg-blue-950/25",
113
+ children: [
114
+ /* @__PURE__ */ jsx("span", { className: "flex h-9 w-9 items-center justify-center rounded-xl border border-border bg-card", children: busy ? /* @__PURE__ */ jsx(Loader2, { size: 15, className: "animate-spin text-blue-600" }) : /* @__PURE__ */ jsx(Upload, { size: 15, className: "text-muted-foreground" }) }),
115
+ /* @__PURE__ */ jsx("span", { className: "text-xs font-medium text-foreground", children: busy ? "Uploading\u2026" : "Choose image" }),
116
+ /* @__PURE__ */ jsx("span", { className: "text-[11px] font-normal text-muted-foreground whitespace-normal", children: description ?? "JPG, PNG, WebP or GIF." })
117
+ ]
118
+ }
119
+ ),
120
+ busy ? /* @__PURE__ */ jsx(ImageBusyOverlay, {}) : null
121
+ ] }),
73
122
  /* @__PURE__ */ jsx(
74
123
  "input",
75
124
  {
76
125
  ref: inputRef,
77
126
  type: "file",
78
- accept: "image/*",
79
- disabled,
127
+ accept: "image/jpeg,image/png,image/webp,image/gif,.jpg,.jpeg,.png,.webp,.gif",
128
+ disabled: locked,
80
129
  className: "hidden",
81
130
  onChange: (event) => {
82
131
  handleFileChange(event.target.files);
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/files/image-picker-field.tsx"],"names":[],"mappings":"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","file":"image-picker-field.js","sourcesContent":["import { useRef } from \"react\";\nimport { ImagePlus, Trash2, Upload } from \"lucide-react\";\nimport { Button } from \"../ui/button\";\n\nexport function ImagePickerField({\n label = \"Image\",\n value,\n onFileSelected,\n onRemove,\n description,\n previewAlt = \"Selected image\",\n disabled = false,\n}: {\n label?: string;\n /** Preview URL supplied by the parent (`blob:`, signed HTTPS, or empty). */\n value?: string;\n onFileSelected: (file: File) => void;\n onRemove?: () => void;\n description?: string;\n previewAlt?: string;\n disabled?: boolean;\n}) {\n const inputRef = useRef<HTMLInputElement>(null);\n\n const handleFileChange = (files: FileList | null) => {\n const file = files?.[0];\n if (!file || disabled) return;\n onFileSelected(file);\n };\n\n return (\n <div className=\"flex flex-col gap-1.5\">\n <label className=\"text-xs font-medium text-muted-foreground\">{label}</label>\n {value ? (\n <div className=\"max-w-sm overflow-hidden rounded-xl border border-border bg-card\">\n <img src={value} alt={previewAlt} className=\"aspect-[4/3] w-full object-cover\" />\n <div className=\"flex items-center justify-between gap-2 border-t border-border px-3 py-2\">\n <Button\n type=\"button\"\n variant=\"link\"\n size=\"sm\"\n disabled={disabled}\n onClick={() => inputRef.current?.click()}\n className=\"h-auto gap-1.5 p-0 text-xs font-medium text-blue-600 dark:text-blue-400\"\n >\n <ImagePlus size={13} />\n Change image\n </Button>\n {onRemove ? (\n <Button\n type=\"button\"\n variant=\"link\"\n size=\"sm\"\n disabled={disabled}\n onClick={onRemove}\n className=\"h-auto gap-1.5 p-0 text-xs font-medium text-red-600\"\n >\n <Trash2 size={13} />\n Remove\n </Button>\n ) : null}\n </div>\n </div>\n ) : (\n <Button\n type=\"button\"\n variant=\"outline\"\n disabled={disabled}\n onClick={() => inputRef.current?.click()}\n className=\"flex aspect-[4/3] h-auto max-w-sm w-full flex-col items-center justify-center gap-2 rounded-xl border-dashed bg-muted/50 px-4 py-5 text-center shadow-none hover:border-blue-300 hover:bg-blue-50/30 dark:hover:bg-blue-950/25\"\n >\n <span className=\"flex h-9 w-9 items-center justify-center rounded-xl border border-border bg-card\">\n <Upload size={15} className=\"text-muted-foreground\" />\n </span>\n <span className=\"text-xs font-medium text-foreground\">Choose image</span>\n <span className=\"text-[11px] font-normal text-muted-foreground whitespace-normal\">\n {description ?? \"JPG, PNG, WebP or GIF.\"}\n </span>\n </Button>\n )}\n <input\n ref={inputRef}\n type=\"file\"\n accept=\"image/*\"\n disabled={disabled}\n className=\"hidden\"\n onChange={(event) => {\n handleFileChange(event.target.files);\n event.target.value = \"\";\n }}\n />\n </div>\n );\n}\n"]}
1
+ {"version":3,"sources":["../src/files/image-picker-field.tsx"],"names":[],"mappings":"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","file":"image-picker-field.js","sourcesContent":["import { useEffect, useRef, useState } from \"react\";\nimport { ImagePlus, Loader2, Trash2, Upload } from \"lucide-react\";\nimport { Button } from \"../ui/button\";\nimport { cn } from \"../ui/utils\";\n\nfunction ImageBusyOverlay({ label = \"Uploading…\" }: { label?: string }) {\n return (\n <div\n className=\"absolute inset-0 z-10 flex items-center justify-center rounded-[inherit] bg-background/75 backdrop-blur-[2px] animate-in fade-in duration-200\"\n role=\"status\"\n aria-live=\"polite\"\n >\n <div className=\"flex items-center gap-2 rounded-full border border-border bg-card px-3 py-1.5 text-sm text-foreground shadow-sm\">\n <Loader2 size={14} className=\"animate-spin text-blue-600\" />\n {label}\n </div>\n </div>\n );\n}\n\nfunction PreviewImage({ src, alt }: { src: string; alt: string }) {\n const [ready, setReady] = useState(false);\n\n useEffect(() => {\n setReady(false);\n }, [src]);\n\n return (\n <div className=\"relative aspect-[4/3] w-full overflow-hidden bg-muted\">\n {!ready ? (\n <span\n className=\"absolute inset-0 z-[1] flex items-center justify-center animate-in fade-in duration-200\"\n role=\"status\"\n aria-label=\"Loading image\"\n >\n <Loader2 size={18} className=\"animate-spin text-muted-foreground\" />\n </span>\n ) : null}\n <img\n src={src}\n alt={alt}\n onLoad={() => setReady(true)}\n className={cn(\n \"aspect-[4/3] w-full object-cover transition-opacity duration-300 ease-out\",\n ready ? \"opacity-100\" : \"opacity-0\",\n )}\n />\n </div>\n );\n}\n\nexport function ImagePickerField({\n label = \"Image\",\n value,\n onFileSelected,\n onRemove,\n description,\n previewAlt = \"Selected image\",\n disabled = false,\n busy = false,\n}: {\n label?: string;\n /** Preview URL supplied by the parent (`blob:`, signed HTTPS, or empty). */\n value?: string;\n onFileSelected: (file: File) => void;\n onRemove?: () => void;\n description?: string;\n previewAlt?: string;\n disabled?: boolean;\n busy?: boolean;\n}) {\n const inputRef = useRef<HTMLInputElement>(null);\n const locked = disabled || busy;\n\n const handleFileChange = (files: FileList | null) => {\n const file = files?.[0];\n if (!file || locked) return;\n onFileSelected(file);\n };\n\n return (\n <div className=\"flex flex-col gap-1.5\" aria-busy={busy}>\n {label ? (\n <label className=\"text-xs font-medium text-muted-foreground\">{label}</label>\n ) : null}\n {value ? (\n <div className=\"relative max-w-sm overflow-hidden rounded-xl border border-border bg-card\">\n <PreviewImage src={value} alt={previewAlt} />\n {busy ? <ImageBusyOverlay /> : null}\n <div className=\"flex items-center justify-between gap-2 border-t border-border px-3 py-2\">\n <Button\n type=\"button\"\n variant=\"link\"\n size=\"sm\"\n disabled={locked}\n onClick={() => inputRef.current?.click()}\n className=\"h-auto gap-1.5 p-0 text-xs font-medium text-blue-600 dark:text-blue-400\"\n >\n <ImagePlus size={13} />\n Change image\n </Button>\n {onRemove ? (\n <Button\n type=\"button\"\n variant=\"link\"\n size=\"sm\"\n disabled={locked}\n onClick={onRemove}\n className=\"h-auto gap-1.5 p-0 text-xs font-medium text-red-600\"\n >\n <Trash2 size={13} />\n Remove\n </Button>\n ) : null}\n </div>\n </div>\n ) : (\n <div className=\"relative max-w-sm overflow-hidden rounded-xl\">\n <Button\n type=\"button\"\n variant=\"outline\"\n disabled={locked}\n onClick={() => inputRef.current?.click()}\n className=\"flex aspect-[4/3] h-auto w-full flex-col items-center justify-center gap-2 rounded-xl border-dashed bg-muted/50 px-4 py-5 text-center shadow-none hover:border-blue-300 hover:bg-blue-50/30 dark:hover:bg-blue-950/25\"\n >\n <span className=\"flex h-9 w-9 items-center justify-center rounded-xl border border-border bg-card\">\n {busy ? (\n <Loader2 size={15} className=\"animate-spin text-blue-600\" />\n ) : (\n <Upload size={15} className=\"text-muted-foreground\" />\n )}\n </span>\n <span className=\"text-xs font-medium text-foreground\">\n {busy ? \"Uploading…\" : \"Choose image\"}\n </span>\n <span className=\"text-[11px] font-normal text-muted-foreground whitespace-normal\">\n {description ?? \"JPG, PNG, WebP or GIF.\"}\n </span>\n </Button>\n {busy ? <ImageBusyOverlay /> : null}\n </div>\n )}\n <input\n ref={inputRef}\n type=\"file\"\n accept=\"image/jpeg,image/png,image/webp,image/gif,.jpg,.jpeg,.png,.webp,.gif\"\n disabled={locked}\n className=\"hidden\"\n onChange={(event) => {\n handleFileChange(event.target.files);\n event.target.value = \"\";\n }}\n />\n </div>\n );\n}\n"]}
@@ -0,0 +1,65 @@
1
+ import * as React from 'react';
2
+ import { ReactNode } from 'react';
3
+
4
+ type LayoutCanvasResourceType = "desks" | "rooms" | "other";
5
+ interface LayoutCanvasRect {
6
+ x: number;
7
+ y: number;
8
+ w: number;
9
+ h: number;
10
+ }
11
+ interface LayoutCanvasFloorLayout {
12
+ placedItems: Record<string, LayoutCanvasRect>;
13
+ bgImage: string | null;
14
+ canvasWidth?: number;
15
+ canvasHeight?: number;
16
+ }
17
+ interface LayoutCanvasPlanHint {
18
+ id: string;
19
+ name: string;
20
+ color?: string;
21
+ }
22
+
23
+ interface LayoutCanvasViewRow {
24
+ resource: {
25
+ id: string;
26
+ name: string;
27
+ pricingPlanId: string;
28
+ resourceType: LayoutCanvasResourceType;
29
+ totalSlots?: number;
30
+ };
31
+ merged: {
32
+ occupied: boolean;
33
+ manual?: boolean;
34
+ expectedFreeLabel?: string;
35
+ };
36
+ plan?: LayoutCanvasPlanHint;
37
+ }
38
+ interface LayoutCanvasResourceQuickBook {
39
+ /** When omitted, popover shows that booking is unavailable for this member. */
40
+ onBook?: (resourceId: string) => void;
41
+ /** Optional href for the book action (middle-click / fallback). */
42
+ getBookingHref?: (resourceId: string) => string;
43
+ }
44
+ interface LayoutCanvasViewProps {
45
+ layout: LayoutCanvasFloorLayout;
46
+ rows: LayoutCanvasViewRow[];
47
+ onResourceSelect?: (id: string) => void;
48
+ /** Hover/tap popover with a quick book action (member live space). */
49
+ resourceQuickBook?: LayoutCanvasResourceQuickBook;
50
+ /** When set with `onSearchQueryChange`, shows a search bar and dims non-matching resources. */
51
+ searchQuery?: string;
52
+ onSearchQueryChange?: (value: string) => void;
53
+ searchPlaceholder?: string;
54
+ /** Occupancy green/red dots. Off for booking pickers. */
55
+ showStatusDot?: boolean;
56
+ selectedIds?: string[];
57
+ /** Rendered on the left of the zoom toolbar (e.g. floor select). */
58
+ toolbarStart?: ReactNode;
59
+ className?: string;
60
+ viewportClassName?: string;
61
+ }
62
+ declare function matchesCanvasResourceSearch(resourceName: string, planName: string | undefined, query: string): boolean;
63
+ declare function LayoutCanvasView({ layout, rows, onResourceSelect, resourceQuickBook, searchQuery, onSearchQueryChange, searchPlaceholder, showStatusDot, selectedIds, toolbarStart, className, viewportClassName, }: LayoutCanvasViewProps): React.JSX.Element;
64
+
65
+ export { type LayoutCanvasFloorLayout as L, type LayoutCanvasViewRow as a, type LayoutCanvasResourceQuickBook as b, LayoutCanvasView as c, type LayoutCanvasViewProps as d, matchesCanvasResourceSearch as m };
@@ -1,57 +1,2 @@
1
- import * as React from 'react';
2
-
3
- type LayoutCanvasResourceType = "desks" | "rooms" | "other";
4
- interface LayoutCanvasRect {
5
- x: number;
6
- y: number;
7
- w: number;
8
- h: number;
9
- }
10
- interface LayoutCanvasFloorLayout {
11
- placedItems: Record<string, LayoutCanvasRect>;
12
- bgImage: string | null;
13
- canvasWidth?: number;
14
- canvasHeight?: number;
15
- }
16
- interface LayoutCanvasPlanHint {
17
- id: string;
18
- name: string;
19
- color?: string;
20
- }
21
-
22
- interface LayoutCanvasViewRow {
23
- resource: {
24
- id: string;
25
- name: string;
26
- pricingPlanId: string;
27
- resourceType: LayoutCanvasResourceType;
28
- totalSlots?: number;
29
- };
30
- merged: {
31
- occupied: boolean;
32
- manual?: boolean;
33
- expectedFreeLabel?: string;
34
- };
35
- plan?: LayoutCanvasPlanHint;
36
- }
37
- interface LayoutCanvasResourceQuickBook {
38
- /** When omitted, popover shows that booking is unavailable for this member. */
39
- onBook?: (resourceId: string) => void;
40
- /** Optional href for the book action (middle-click / fallback). */
41
- getBookingHref?: (resourceId: string) => string;
42
- }
43
- interface LayoutCanvasViewProps {
44
- layout: LayoutCanvasFloorLayout;
45
- rows: LayoutCanvasViewRow[];
46
- onResourceSelect?: (id: string) => void;
47
- /** Hover/tap popover with a quick book action (member live space). */
48
- resourceQuickBook?: LayoutCanvasResourceQuickBook;
49
- /** When set with `onSearchQueryChange`, shows a search bar and dims non-matching resources. */
50
- searchQuery?: string;
51
- onSearchQueryChange?: (value: string) => void;
52
- searchPlaceholder?: string;
53
- }
54
- declare function matchesCanvasResourceSearch(resourceName: string, planName: string | undefined, query: string): boolean;
55
- declare function LayoutCanvasView({ layout, rows, onResourceSelect, resourceQuickBook, searchQuery, onSearchQueryChange, searchPlaceholder, }: LayoutCanvasViewProps): React.JSX.Element;
56
-
57
- export { type LayoutCanvasResourceQuickBook, LayoutCanvasView, type LayoutCanvasViewProps, type LayoutCanvasViewRow, matchesCanvasResourceSearch };
1
+ import 'react';
2
+ export { b as LayoutCanvasResourceQuickBook, c as LayoutCanvasView, d as LayoutCanvasViewProps, a as LayoutCanvasViewRow, m as matchesCanvasResourceSearch } from './layout-canvas-view-DOjyUNJH.js';