@open-mercato/ui 0.6.7-develop.6828.1.ab1620a63e → 0.6.7-develop.6834.1.e76f4b8cbc
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/backend/JsonBuilder.js +3 -3
- package/dist/backend/JsonBuilder.js.map +2 -2
- package/dist/backend/RowActions.js +2 -2
- package/dist/backend/RowActions.js.map +2 -2
- package/dist/backend/confirm-dialog/ConfirmDialog.js +1 -1
- package/dist/backend/confirm-dialog/ConfirmDialog.js.map +2 -2
- package/dist/backend/detail/AttachmentDeleteDialog.js +1 -1
- package/dist/backend/detail/AttachmentDeleteDialog.js.map +2 -2
- package/dist/backend/detail/AttachmentsSection.js +1 -1
- package/dist/backend/detail/AttachmentsSection.js.map +2 -2
- package/dist/backend/detail/NotesSection.js +1 -1
- package/dist/backend/detail/NotesSection.js.map +2 -2
- package/dist/backend/detail/TagsSection.js +1 -1
- package/dist/backend/detail/TagsSection.js.map +2 -2
- package/dist/backend/forms/FormHeader.js +1 -2
- package/dist/backend/forms/FormHeader.js.map +2 -2
- package/dist/backend/schedule/ScheduleAgenda.js +3 -3
- package/dist/backend/schedule/ScheduleAgenda.js.map +1 -1
- package/dist/backend/schedule/ScheduleGrid.js +3 -3
- package/dist/backend/schedule/ScheduleGrid.js.map +1 -1
- package/dist/primitives/button.js +9 -4
- package/dist/primitives/button.js.map +2 -2
- package/package.json +3 -3
- package/src/backend/JsonBuilder.tsx +3 -3
- package/src/backend/RowActions.tsx +2 -2
- package/src/backend/confirm-dialog/ConfirmDialog.tsx +1 -1
- package/src/backend/confirm-dialog/__tests__/ConfirmDialog.test.tsx +35 -0
- package/src/backend/detail/AttachmentDeleteDialog.tsx +1 -1
- package/src/backend/detail/AttachmentsSection.tsx +1 -1
- package/src/backend/detail/NotesSection.tsx +1 -1
- package/src/backend/detail/TagsSection.tsx +1 -1
- package/src/backend/forms/FormHeader.tsx +1 -2
- package/src/backend/schedule/ScheduleAgenda.tsx +3 -3
- package/src/backend/schedule/ScheduleGrid.tsx +3 -3
- package/src/primitives/__tests__/button.test.tsx +70 -0
- package/src/primitives/button.tsx +10 -4
|
@@ -148,7 +148,7 @@ function JsonBuilder({
|
|
|
148
148
|
disabled
|
|
149
149
|
}
|
|
150
150
|
),
|
|
151
|
-
parseError && /* @__PURE__ */ jsx("div", { className: "text-xs text-
|
|
151
|
+
parseError && /* @__PURE__ */ jsx("div", { className: "text-xs text-status-error-text", children: "Invalid JSON format" })
|
|
152
152
|
] }) : /* @__PURE__ */ jsx("div", { className: "min-h-[300px] text-sm overflow-x-auto", children: typeof value === "object" && value !== null ? /* @__PURE__ */ jsx(
|
|
153
153
|
JsonNode,
|
|
154
154
|
{
|
|
@@ -159,7 +159,7 @@ function JsonBuilder({
|
|
|
159
159
|
isRoot: true
|
|
160
160
|
}
|
|
161
161
|
) : /* @__PURE__ */ jsx("div", { className: "text-muted-foreground italic p-4 text-center", children: "Value is not an object or array. Switch to Raw to edit." }) }),
|
|
162
|
-
error && /* @__PURE__ */ jsx("div", { className: "text-xs text-
|
|
162
|
+
error && /* @__PURE__ */ jsx("div", { className: "text-xs text-status-error-text", children: error }),
|
|
163
163
|
ConfirmDialogElement
|
|
164
164
|
] });
|
|
165
165
|
}
|
|
@@ -295,7 +295,7 @@ function JsonNode({ data, onChange, onDelete, readOnly, label, isRoot, confirm }
|
|
|
295
295
|
type: "button",
|
|
296
296
|
variant: "ghost",
|
|
297
297
|
size: "xs",
|
|
298
|
-
className: "text-muted-foreground hover:text-
|
|
298
|
+
className: "text-muted-foreground hover:text-destructive opacity-0 group-hover:opacity-100 transition-opacity",
|
|
299
299
|
title: "Remove item",
|
|
300
300
|
onClick: onDelete,
|
|
301
301
|
children: /* @__PURE__ */ jsx(Trash2, { className: "w-3.5 h-3.5" })
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../src/backend/JsonBuilder.tsx"],
|
|
4
|
-
"sourcesContent": ["\"use client\"\n\nimport * as React from 'react'\nimport { Button } from '../primitives/button'\nimport { IconButton } from '../primitives/icon-button'\nimport { Input } from '../primitives/input'\nimport { InlineInput } from '../primitives/inline-input'\nimport {\n Select,\n SelectContent,\n SelectItem,\n SelectTrigger,\n SelectValue,\n} from '../primitives/select'\nimport { Plus, Trash2, ChevronRight, ChevronDown, Code, LayoutList } from 'lucide-react'\nimport { useConfirmDialog } from './confirm-dialog'\nimport type { ConfirmDialogOptions } from './confirm-dialog'\n\nfunction cn(...classes: (string | undefined | null | false)[]) {\n return classes.filter(Boolean).join(' ')\n}\n\nexport type JsonBuilderProps = {\n value: any\n onChange: (value: any) => void\n disabled?: boolean\n error?: string\n}\n\ntype JsonNodeType = 'string' | 'number' | 'boolean' | 'object' | 'array' | 'null'\n\ntype ConfirmFn = (options?: ConfirmDialogOptions) => Promise<boolean>\n\nfunction getJsonType(value: any): JsonNodeType {\n if (value === null) return 'null'\n if (Array.isArray(value)) return 'array'\n return typeof value as JsonNodeType\n}\n\nfunction defaultValueForType(type: JsonNodeType): any {\n switch (type) {\n case 'string': return \"\"\n case 'number': return 0\n case 'boolean': return false\n case 'object': return {}\n case 'array': return []\n case 'null': return null\n }\n}\n\nfunction toRawString(value: any): string {\n if (value === null || value === undefined) return '{}'\n if (typeof value === 'object') return JSON.stringify(value, null, 2)\n return String(value)\n}\n\nexport function JsonBuilder({\n value,\n onChange,\n disabled,\n error\n}: JsonBuilderProps) {\n const [mode, setMode] = React.useState<'raw' | 'builder'>('raw')\n const [rawString, setRawString] = React.useState(() => toRawString(value))\n const [rawDirty, setRawDirty] = React.useState(false)\n const [parseError, setParseError] = React.useState<string | null>(null)\n const { confirm, ConfirmDialogElement } = useConfirmDialog()\n\n React.useEffect(() => {\n if (value === null && !disabled) {\n onChange({})\n }\n }, [value, disabled, onChange])\n\n // Mirror external value changes (e.g. async record load, builder edits) into\n // the raw textarea only while the user has NOT started typing in it. Once the\n // textarea is dirty it becomes the source of truth, so re-deriving it from\n // `value` on every keystroke \u2014 the parent's onChange identity changes each\n // render \u2014 would clobber typing and make Raw JSON uneditable (issue #2817).\n React.useEffect(() => {\n if (rawDirty) return\n if (value !== null && typeof value === 'object') {\n setRawString(JSON.stringify(value, null, 2))\n setParseError(null)\n }\n }, [value, rawDirty])\n\n const handleRawChange = (str: string) => {\n setRawDirty(true)\n setRawString(str)\n try {\n if (str.trim() === '') {\n onChange({})\n setParseError(null)\n } else {\n const parsed = JSON.parse(str)\n onChange(parsed)\n setParseError(null)\n }\n } catch (e) {\n onChange(str)\n setParseError(\"Invalid JSON\")\n }\n }\n\n const switchToRaw = () => {\n // Re-sync the textarea from the current value and let it mirror external\n // changes again until the user edits it.\n setRawDirty(false)\n setRawString(toRawString(value))\n setParseError(null)\n setMode('raw')\n }\n\n const switchToBuilder = () => {\n try {\n if (typeof value === 'string') {\n JSON.parse(value)\n }\n setMode('builder')\n } catch (e) {\n alert(\"Cannot switch to Builder mode: Invalid JSON\")\n }\n }\n\n return (\n <div className=\"space-y-4 border rounded-md p-4 bg-card\">\n <div className=\"flex items-center space-x-2 border-b pb-2 mb-2\">\n <Button\n type=\"button\"\n variant=\"ghost\"\n size=\"sm\"\n className={cn(mode === 'raw' && \"bg-muted text-foreground\")}\n onClick={switchToRaw}\n >\n <Code className=\"w-4 h-4\" />\n Raw JSON\n </Button>\n <Button\n type=\"button\"\n variant=\"ghost\"\n size=\"sm\"\n className={cn(mode === 'builder' && \"bg-muted text-foreground\")}\n onClick={switchToBuilder}\n >\n <LayoutList className=\"w-4 h-4\" />\n Builder\n </Button>\n </div>\n\n {mode === 'raw' ? (\n <div className=\"space-y-2\">\n <textarea\n value={rawString}\n onChange={(e) => handleRawChange(e.target.value)}\n onBlur={() => {\n try {\n const parsed = JSON.parse(rawString)\n setRawString(JSON.stringify(parsed, null, 2))\n } catch { }\n }}\n placeholder='{\"key\": \"value\"}'\n className=\"w-full rounded border px-3 py-2 min-h-[300px] text-sm font-mono focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring\"\n disabled={disabled}\n />\n {parseError && (\n <div className=\"text-xs text-red-600\">Invalid JSON format</div>\n )}\n </div>\n ) : (\n <div className=\"min-h-[300px] text-sm overflow-x-auto\">\n {typeof value === 'object' && value !== null ? (\n <JsonNode\n data={value}\n onChange={onChange}\n readOnly={disabled}\n confirm={confirm}\n isRoot\n />\n ) : (\n <div className=\"text-muted-foreground italic p-4 text-center\">\n Value is not an object or array. Switch to Raw to edit.\n </div>\n )}\n </div>\n )}\n\n {error && <div className=\"text-xs text-red-600\">{error}</div>}\n {ConfirmDialogElement}\n </div>\n )\n}\n\ninterface JsonNodeProps {\n data: any\n onChange: (val: any) => void\n onDelete?: () => void\n readOnly?: boolean\n label?: string\n isRoot?: boolean\n confirm?: ConfirmFn\n}\n\nfunction JsonNode({ data, onChange, onDelete, readOnly, label, isRoot, confirm }: JsonNodeProps) {\n const type = getJsonType(data)\n const isContainer = type === 'object' || type === 'array'\n const [collapsed, setCollapsed] = React.useState(false)\n\n const handleTypeChange = async (newType: JsonNodeType) => {\n if (newType === type) return\n // Switching away from a non-empty container discards its contents. Ask\n // for confirmation first so configured properties aren't lost silently\n // (issue #2817). The Select is controlled by the derived type, so when\n // the user cancels we simply leave the data untouched and the control\n // snaps back to its previous value.\n const itemCount = type === 'object'\n ? Object.keys(data).length\n : type === 'array'\n ? (data as any[]).length\n : 0\n if (itemCount > 0 && confirm) {\n const noun = type === 'object'\n ? `${itemCount} ${itemCount === 1 ? 'property' : 'properties'}`\n : `${itemCount} ${itemCount === 1 ? 'item' : 'items'}`\n const confirmed = await confirm({\n title: 'Change value type?',\n text: `Changing the type from ${type} to ${newType} will discard the ${noun} currently configured. This cannot be undone.`,\n confirmText: 'Discard and change',\n variant: 'destructive',\n })\n if (!confirmed) return\n }\n onChange(defaultValueForType(newType))\n }\n\n const handleAddKey = () => {\n if (type === 'object') {\n const newKey = `newKey_${Object.keys(data).length}`\n onChange({ ...data, [newKey]: \"\" })\n } else if (type === 'array') {\n onChange([...data, \"\"])\n }\n }\n\n const handleChildChange = (key: string | number, newVal: any) => {\n if (type === 'object') {\n onChange({ ...data, [key]: newVal })\n } else if (type === 'array') {\n const arr = [...data]\n arr[Number(key)] = newVal\n onChange(arr)\n }\n }\n\n const handleKeyRename = (oldKey: string, newKey: string) => {\n if (oldKey === newKey) return\n const keys = Object.keys(data)\n const newData: any = {}\n keys.forEach(k => {\n if (k === oldKey) {\n newData[newKey] = data[k]\n } else {\n newData[k] = data[k]\n }\n })\n onChange(newData)\n }\n\n const handleChildDelete = (key: string | number) => {\n if (type === 'object') {\n const newData = { ...data }\n delete newData[key as string]\n onChange(newData)\n } else if (type === 'array') {\n onChange(data.filter((_: any, i: number) => i !== key))\n }\n }\n\n return (\n <div className={cn(\"pl-0\", !isRoot && \"pl-4 border-l border-border ml-1\")}>\n <div className=\"flex items-start gap-2 py-1 group\">\n\n {isContainer && (\n <IconButton type=\"button\" variant=\"ghost\" size=\"xs\" className=\"mt-1 text-muted-foreground hover:text-foreground\" onClick={() => setCollapsed(!collapsed)}>\n {collapsed ? <ChevronRight className=\"w-3 h-3\" /> : <ChevronDown className=\"w-3 h-3\" />}\n </IconButton>\n )}\n {!isContainer && !isRoot && <div className=\"w-3\" />} {/* Spacer */}\n\n {label !== undefined && !isRoot && (\n <div className=\"flex items-center gap-1\">\n <span className=\"text-xs text-muted-foreground font-mono\">\n {label}\n </span>\n <span className=\"text-muted-foreground text-xs\">:</span>\n </div>\n )}\n\n <div className=\"flex-1 flex gap-2 items-center flex-wrap\">\n\n {!readOnly && (\n <Select\n value={type}\n onValueChange={(next) => { void handleTypeChange(next as JsonNodeType) }}\n >\n <SelectTrigger size=\"sm\" className=\"w-auto min-w-[6rem]\">\n <SelectValue />\n </SelectTrigger>\n <SelectContent>\n <SelectItem value=\"string\">String</SelectItem>\n <SelectItem value=\"number\">Number</SelectItem>\n <SelectItem value=\"boolean\">Boolean</SelectItem>\n <SelectItem value=\"object\">Object</SelectItem>\n <SelectItem value=\"array\">Array</SelectItem>\n <SelectItem value=\"null\">Null</SelectItem>\n </SelectContent>\n </Select>\n )}\n\n {type === 'string' && (\n <Input\n size=\"sm\"\n className=\"flex-1 min-w-0 sm:min-w-[120px]\"\n value={data}\n onChange={e => onChange(e.target.value)}\n disabled={readOnly}\n />\n )}\n {type === 'number' && (\n <Input\n type=\"number\"\n size=\"sm\"\n className=\"flex-1 w-full sm:w-[100px]\"\n value={data}\n onChange={e => onChange(parseFloat(e.target.value) || 0)}\n disabled={readOnly}\n />\n )}\n {type === 'boolean' && (\n <Select\n value={String(data)}\n onValueChange={(next) => onChange(next === 'true')}\n disabled={readOnly}\n >\n <SelectTrigger size=\"sm\" className=\"flex-1 w-full sm:w-[100px]\">\n <SelectValue />\n </SelectTrigger>\n <SelectContent>\n <SelectItem value=\"true\">true</SelectItem>\n <SelectItem value=\"false\">false</SelectItem>\n </SelectContent>\n </Select>\n )}\n {type === 'null' && <span className=\"text-xs text-muted-foreground\">null</span>}\n {isContainer && (\n <span className=\"text-xs text-muted-foreground\">\n {type === 'object' ? `{ ${Object.keys(data).length} items }` : `[ ${data.length} items ]`}\n </span>\n )}\n\n {onDelete && !readOnly && (\n <IconButton\n type=\"button\"\n variant=\"ghost\"\n size=\"xs\"\n className=\"text-muted-foreground hover:text-red-500 opacity-0 group-hover:opacity-100 transition-opacity\"\n title=\"Remove item\"\n onClick={onDelete}\n >\n <Trash2 className=\"w-3.5 h-3.5\" />\n </IconButton>\n )}\n </div>\n </div>\n\n {isContainer && !collapsed && (\n <div className=\"flex flex-col gap-1 w-full pl-2\">\n {type === 'object' && Object.entries(data).map(([key, val], idx) => (\n <div key={idx} className=\"flex\">\n <div className=\"pt-2\">\n {/* Key Renamer */}\n <InlineInput\n className=\"w-full sm:w-[100px]\"\n inputClassName=\"text-right text-xs font-mono pr-1\"\n value={key}\n onChange={(e) => handleKeyRename(key, e.target.value)}\n disabled={readOnly}\n />\n </div>\n <div className=\"flex-1\">\n <JsonNode\n data={val}\n onChange={(v) => handleChildChange(key, v)}\n onDelete={() => handleChildDelete(key)}\n readOnly={readOnly}\n confirm={confirm}\n />\n </div>\n </div>\n ))}\n\n {type === 'array' && (data as any[]).map((val, idx) => (\n <JsonNode\n key={idx}\n label={String(idx)}\n data={val}\n onChange={(v) => handleChildChange(idx, v)}\n onDelete={() => handleChildDelete(idx)}\n readOnly={readOnly}\n confirm={confirm}\n />\n ))}\n\n {!readOnly && (\n <div className=\"pl-4 mt-1\">\n <Button\n type=\"button\"\n variant=\"outline\"\n size=\"sm\"\n onClick={handleAddKey}\n className=\"h-6 text-xs\"\n >\n <Plus className=\"w-3 h-3 mr-1\" />\n Add {type === 'object' ? 'Property' : 'Item'}\n </Button>\n </div>\n )}\n </div>\n )}\n </div>\n )\n}\n"],
|
|
5
|
-
"mappings": "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
|
|
4
|
+
"sourcesContent": ["\"use client\"\n\nimport * as React from 'react'\nimport { Button } from '../primitives/button'\nimport { IconButton } from '../primitives/icon-button'\nimport { Input } from '../primitives/input'\nimport { InlineInput } from '../primitives/inline-input'\nimport {\n Select,\n SelectContent,\n SelectItem,\n SelectTrigger,\n SelectValue,\n} from '../primitives/select'\nimport { Plus, Trash2, ChevronRight, ChevronDown, Code, LayoutList } from 'lucide-react'\nimport { useConfirmDialog } from './confirm-dialog'\nimport type { ConfirmDialogOptions } from './confirm-dialog'\n\nfunction cn(...classes: (string | undefined | null | false)[]) {\n return classes.filter(Boolean).join(' ')\n}\n\nexport type JsonBuilderProps = {\n value: any\n onChange: (value: any) => void\n disabled?: boolean\n error?: string\n}\n\ntype JsonNodeType = 'string' | 'number' | 'boolean' | 'object' | 'array' | 'null'\n\ntype ConfirmFn = (options?: ConfirmDialogOptions) => Promise<boolean>\n\nfunction getJsonType(value: any): JsonNodeType {\n if (value === null) return 'null'\n if (Array.isArray(value)) return 'array'\n return typeof value as JsonNodeType\n}\n\nfunction defaultValueForType(type: JsonNodeType): any {\n switch (type) {\n case 'string': return \"\"\n case 'number': return 0\n case 'boolean': return false\n case 'object': return {}\n case 'array': return []\n case 'null': return null\n }\n}\n\nfunction toRawString(value: any): string {\n if (value === null || value === undefined) return '{}'\n if (typeof value === 'object') return JSON.stringify(value, null, 2)\n return String(value)\n}\n\nexport function JsonBuilder({\n value,\n onChange,\n disabled,\n error\n}: JsonBuilderProps) {\n const [mode, setMode] = React.useState<'raw' | 'builder'>('raw')\n const [rawString, setRawString] = React.useState(() => toRawString(value))\n const [rawDirty, setRawDirty] = React.useState(false)\n const [parseError, setParseError] = React.useState<string | null>(null)\n const { confirm, ConfirmDialogElement } = useConfirmDialog()\n\n React.useEffect(() => {\n if (value === null && !disabled) {\n onChange({})\n }\n }, [value, disabled, onChange])\n\n // Mirror external value changes (e.g. async record load, builder edits) into\n // the raw textarea only while the user has NOT started typing in it. Once the\n // textarea is dirty it becomes the source of truth, so re-deriving it from\n // `value` on every keystroke \u2014 the parent's onChange identity changes each\n // render \u2014 would clobber typing and make Raw JSON uneditable (issue #2817).\n React.useEffect(() => {\n if (rawDirty) return\n if (value !== null && typeof value === 'object') {\n setRawString(JSON.stringify(value, null, 2))\n setParseError(null)\n }\n }, [value, rawDirty])\n\n const handleRawChange = (str: string) => {\n setRawDirty(true)\n setRawString(str)\n try {\n if (str.trim() === '') {\n onChange({})\n setParseError(null)\n } else {\n const parsed = JSON.parse(str)\n onChange(parsed)\n setParseError(null)\n }\n } catch (e) {\n onChange(str)\n setParseError(\"Invalid JSON\")\n }\n }\n\n const switchToRaw = () => {\n // Re-sync the textarea from the current value and let it mirror external\n // changes again until the user edits it.\n setRawDirty(false)\n setRawString(toRawString(value))\n setParseError(null)\n setMode('raw')\n }\n\n const switchToBuilder = () => {\n try {\n if (typeof value === 'string') {\n JSON.parse(value)\n }\n setMode('builder')\n } catch (e) {\n alert(\"Cannot switch to Builder mode: Invalid JSON\")\n }\n }\n\n return (\n <div className=\"space-y-4 border rounded-md p-4 bg-card\">\n <div className=\"flex items-center space-x-2 border-b pb-2 mb-2\">\n <Button\n type=\"button\"\n variant=\"ghost\"\n size=\"sm\"\n className={cn(mode === 'raw' && \"bg-muted text-foreground\")}\n onClick={switchToRaw}\n >\n <Code className=\"w-4 h-4\" />\n Raw JSON\n </Button>\n <Button\n type=\"button\"\n variant=\"ghost\"\n size=\"sm\"\n className={cn(mode === 'builder' && \"bg-muted text-foreground\")}\n onClick={switchToBuilder}\n >\n <LayoutList className=\"w-4 h-4\" />\n Builder\n </Button>\n </div>\n\n {mode === 'raw' ? (\n <div className=\"space-y-2\">\n <textarea\n value={rawString}\n onChange={(e) => handleRawChange(e.target.value)}\n onBlur={() => {\n try {\n const parsed = JSON.parse(rawString)\n setRawString(JSON.stringify(parsed, null, 2))\n } catch { }\n }}\n placeholder='{\"key\": \"value\"}'\n className=\"w-full rounded border px-3 py-2 min-h-[300px] text-sm font-mono focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring\"\n disabled={disabled}\n />\n {parseError && (\n <div className=\"text-xs text-status-error-text\">Invalid JSON format</div>\n )}\n </div>\n ) : (\n <div className=\"min-h-[300px] text-sm overflow-x-auto\">\n {typeof value === 'object' && value !== null ? (\n <JsonNode\n data={value}\n onChange={onChange}\n readOnly={disabled}\n confirm={confirm}\n isRoot\n />\n ) : (\n <div className=\"text-muted-foreground italic p-4 text-center\">\n Value is not an object or array. Switch to Raw to edit.\n </div>\n )}\n </div>\n )}\n\n {error && <div className=\"text-xs text-status-error-text\">{error}</div>}\n {ConfirmDialogElement}\n </div>\n )\n}\n\ninterface JsonNodeProps {\n data: any\n onChange: (val: any) => void\n onDelete?: () => void\n readOnly?: boolean\n label?: string\n isRoot?: boolean\n confirm?: ConfirmFn\n}\n\nfunction JsonNode({ data, onChange, onDelete, readOnly, label, isRoot, confirm }: JsonNodeProps) {\n const type = getJsonType(data)\n const isContainer = type === 'object' || type === 'array'\n const [collapsed, setCollapsed] = React.useState(false)\n\n const handleTypeChange = async (newType: JsonNodeType) => {\n if (newType === type) return\n // Switching away from a non-empty container discards its contents. Ask\n // for confirmation first so configured properties aren't lost silently\n // (issue #2817). The Select is controlled by the derived type, so when\n // the user cancels we simply leave the data untouched and the control\n // snaps back to its previous value.\n const itemCount = type === 'object'\n ? Object.keys(data).length\n : type === 'array'\n ? (data as any[]).length\n : 0\n if (itemCount > 0 && confirm) {\n const noun = type === 'object'\n ? `${itemCount} ${itemCount === 1 ? 'property' : 'properties'}`\n : `${itemCount} ${itemCount === 1 ? 'item' : 'items'}`\n const confirmed = await confirm({\n title: 'Change value type?',\n text: `Changing the type from ${type} to ${newType} will discard the ${noun} currently configured. This cannot be undone.`,\n confirmText: 'Discard and change',\n variant: 'destructive',\n })\n if (!confirmed) return\n }\n onChange(defaultValueForType(newType))\n }\n\n const handleAddKey = () => {\n if (type === 'object') {\n const newKey = `newKey_${Object.keys(data).length}`\n onChange({ ...data, [newKey]: \"\" })\n } else if (type === 'array') {\n onChange([...data, \"\"])\n }\n }\n\n const handleChildChange = (key: string | number, newVal: any) => {\n if (type === 'object') {\n onChange({ ...data, [key]: newVal })\n } else if (type === 'array') {\n const arr = [...data]\n arr[Number(key)] = newVal\n onChange(arr)\n }\n }\n\n const handleKeyRename = (oldKey: string, newKey: string) => {\n if (oldKey === newKey) return\n const keys = Object.keys(data)\n const newData: any = {}\n keys.forEach(k => {\n if (k === oldKey) {\n newData[newKey] = data[k]\n } else {\n newData[k] = data[k]\n }\n })\n onChange(newData)\n }\n\n const handleChildDelete = (key: string | number) => {\n if (type === 'object') {\n const newData = { ...data }\n delete newData[key as string]\n onChange(newData)\n } else if (type === 'array') {\n onChange(data.filter((_: any, i: number) => i !== key))\n }\n }\n\n return (\n <div className={cn(\"pl-0\", !isRoot && \"pl-4 border-l border-border ml-1\")}>\n <div className=\"flex items-start gap-2 py-1 group\">\n\n {isContainer && (\n <IconButton type=\"button\" variant=\"ghost\" size=\"xs\" className=\"mt-1 text-muted-foreground hover:text-foreground\" onClick={() => setCollapsed(!collapsed)}>\n {collapsed ? <ChevronRight className=\"w-3 h-3\" /> : <ChevronDown className=\"w-3 h-3\" />}\n </IconButton>\n )}\n {!isContainer && !isRoot && <div className=\"w-3\" />} {/* Spacer */}\n\n {label !== undefined && !isRoot && (\n <div className=\"flex items-center gap-1\">\n <span className=\"text-xs text-muted-foreground font-mono\">\n {label}\n </span>\n <span className=\"text-muted-foreground text-xs\">:</span>\n </div>\n )}\n\n <div className=\"flex-1 flex gap-2 items-center flex-wrap\">\n\n {!readOnly && (\n <Select\n value={type}\n onValueChange={(next) => { void handleTypeChange(next as JsonNodeType) }}\n >\n <SelectTrigger size=\"sm\" className=\"w-auto min-w-[6rem]\">\n <SelectValue />\n </SelectTrigger>\n <SelectContent>\n <SelectItem value=\"string\">String</SelectItem>\n <SelectItem value=\"number\">Number</SelectItem>\n <SelectItem value=\"boolean\">Boolean</SelectItem>\n <SelectItem value=\"object\">Object</SelectItem>\n <SelectItem value=\"array\">Array</SelectItem>\n <SelectItem value=\"null\">Null</SelectItem>\n </SelectContent>\n </Select>\n )}\n\n {type === 'string' && (\n <Input\n size=\"sm\"\n className=\"flex-1 min-w-0 sm:min-w-[120px]\"\n value={data}\n onChange={e => onChange(e.target.value)}\n disabled={readOnly}\n />\n )}\n {type === 'number' && (\n <Input\n type=\"number\"\n size=\"sm\"\n className=\"flex-1 w-full sm:w-[100px]\"\n value={data}\n onChange={e => onChange(parseFloat(e.target.value) || 0)}\n disabled={readOnly}\n />\n )}\n {type === 'boolean' && (\n <Select\n value={String(data)}\n onValueChange={(next) => onChange(next === 'true')}\n disabled={readOnly}\n >\n <SelectTrigger size=\"sm\" className=\"flex-1 w-full sm:w-[100px]\">\n <SelectValue />\n </SelectTrigger>\n <SelectContent>\n <SelectItem value=\"true\">true</SelectItem>\n <SelectItem value=\"false\">false</SelectItem>\n </SelectContent>\n </Select>\n )}\n {type === 'null' && <span className=\"text-xs text-muted-foreground\">null</span>}\n {isContainer && (\n <span className=\"text-xs text-muted-foreground\">\n {type === 'object' ? `{ ${Object.keys(data).length} items }` : `[ ${data.length} items ]`}\n </span>\n )}\n\n {onDelete && !readOnly && (\n <IconButton\n type=\"button\"\n variant=\"ghost\"\n size=\"xs\"\n className=\"text-muted-foreground hover:text-destructive opacity-0 group-hover:opacity-100 transition-opacity\"\n title=\"Remove item\"\n onClick={onDelete}\n >\n <Trash2 className=\"w-3.5 h-3.5\" />\n </IconButton>\n )}\n </div>\n </div>\n\n {isContainer && !collapsed && (\n <div className=\"flex flex-col gap-1 w-full pl-2\">\n {type === 'object' && Object.entries(data).map(([key, val], idx) => (\n <div key={idx} className=\"flex\">\n <div className=\"pt-2\">\n {/* Key Renamer */}\n <InlineInput\n className=\"w-full sm:w-[100px]\"\n inputClassName=\"text-right text-xs font-mono pr-1\"\n value={key}\n onChange={(e) => handleKeyRename(key, e.target.value)}\n disabled={readOnly}\n />\n </div>\n <div className=\"flex-1\">\n <JsonNode\n data={val}\n onChange={(v) => handleChildChange(key, v)}\n onDelete={() => handleChildDelete(key)}\n readOnly={readOnly}\n confirm={confirm}\n />\n </div>\n </div>\n ))}\n\n {type === 'array' && (data as any[]).map((val, idx) => (\n <JsonNode\n key={idx}\n label={String(idx)}\n data={val}\n onChange={(v) => handleChildChange(idx, v)}\n onDelete={() => handleChildDelete(idx)}\n readOnly={readOnly}\n confirm={confirm}\n />\n ))}\n\n {!readOnly && (\n <div className=\"pl-4 mt-1\">\n <Button\n type=\"button\"\n variant=\"outline\"\n size=\"sm\"\n onClick={handleAddKey}\n className=\"h-6 text-xs\"\n >\n <Plus className=\"w-3 h-3 mr-1\" />\n Add {type === 'object' ? 'Property' : 'Item'}\n </Button>\n </div>\n )}\n </div>\n )}\n </div>\n )\n}\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
6
|
"names": []
|
|
7
7
|
}
|
|
@@ -115,7 +115,7 @@ function RowActions({ items = [] }) {
|
|
|
115
115
|
"a",
|
|
116
116
|
{
|
|
117
117
|
href: it.href,
|
|
118
|
-
className: `block w-full text-left px-2 py-1 text-sm rounded hover:bg-accent ${it.destructive ? "text-
|
|
118
|
+
className: `block w-full text-left px-2 py-1 text-sm rounded hover:bg-accent ${it.destructive ? "text-destructive" : ""}`,
|
|
119
119
|
role: "menuitem",
|
|
120
120
|
onClick: (event) => {
|
|
121
121
|
event.stopPropagation();
|
|
@@ -130,7 +130,7 @@ function RowActions({ items = [] }) {
|
|
|
130
130
|
type: "button",
|
|
131
131
|
variant: "ghost",
|
|
132
132
|
size: "sm",
|
|
133
|
-
className: `w-full justify-start rounded-none font-normal ${it.destructive ? "text-
|
|
133
|
+
className: `w-full justify-start rounded-none font-normal ${it.destructive ? "text-destructive" : ""}`,
|
|
134
134
|
role: "menuitem",
|
|
135
135
|
onClick: (event) => {
|
|
136
136
|
event.stopPropagation();
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../src/backend/RowActions.tsx"],
|
|
4
|
-
"sourcesContent": ["\"use client\"\nimport * as React from 'react'\nimport { createPortal } from 'react-dom'\nimport { useT } from '@open-mercato/shared/lib/i18n/context'\nimport { IconButton } from '../primitives/icon-button'\nimport { Button } from '../primitives/button'\n\nexport type RowActionItem = {\n id?: string\n label: string\n onSelect?: () => void\n href?: string\n destructive?: boolean\n}\n\nexport function RowActions({ items = [] }: { items?: RowActionItem[] }) {\n const t = useT()\n const [open, setOpen] = React.useState(false)\n const btnRef = React.useRef<HTMLButtonElement>(null)\n const menuRef = React.useRef<HTMLDivElement>(null)\n const hoverTimeoutRef = React.useRef<NodeJS.Timeout | null>(null)\n const [anchorRect, setAnchorRect] = React.useState<DOMRect | null>(null)\n const [direction, setDirection] = React.useState<'down' | 'up'>('down')\n\n const updatePosition = React.useCallback(() => {\n if (!btnRef.current) return\n const rect = btnRef.current.getBoundingClientRect()\n setAnchorRect(rect)\n // Decide whether to open up or down based on available viewport space\n const spaceBelow = window.innerHeight - rect.bottom\n const spaceAbove = rect.top\n setDirection(spaceBelow < 180 && spaceAbove > spaceBelow ? 'up' : 'down')\n }, [])\n\n React.useEffect(() => {\n if (!open) return\n updatePosition()\n function onDocClick(e: MouseEvent) {\n const t = e.target as Node\n if (menuRef.current && !menuRef.current.contains(t) && btnRef.current && !btnRef.current.contains(t)) {\n setOpen(false)\n }\n }\n function onKey(e: KeyboardEvent) {\n if (e.key === 'Escape') {\n setOpen(false)\n btnRef.current?.focus()\n }\n }\n function onScrollOrResize() {\n updatePosition()\n }\n document.addEventListener('mousedown', onDocClick)\n document.addEventListener('keydown', onKey)\n window.addEventListener('scroll', onScrollOrResize, true)\n window.addEventListener('resize', onScrollOrResize)\n return () => {\n document.removeEventListener('mousedown', onDocClick)\n document.removeEventListener('keydown', onKey)\n window.removeEventListener('scroll', onScrollOrResize, true)\n window.removeEventListener('resize', onScrollOrResize)\n }\n }, [open, updatePosition])\n\n // Cleanup timeout on unmount\n React.useEffect(() => {\n return () => {\n if (hoverTimeoutRef.current) {\n clearTimeout(hoverTimeoutRef.current)\n }\n }\n }, [])\n\n if (items.length === 0) return null\n\n const handlePointerEnter = (event: React.PointerEvent) => {\n if (event.pointerType === 'touch') return\n if (hoverTimeoutRef.current) {\n clearTimeout(hoverTimeoutRef.current)\n }\n setOpen(true)\n }\n\n const handlePointerLeave = (event: React.PointerEvent) => {\n if (event.pointerType === 'touch') return\n hoverTimeoutRef.current = setTimeout(() => {\n setOpen(false)\n }, 150)\n }\n\n return (\n <div\n className=\"relative inline-block text-left\"\n onPointerEnter={handlePointerEnter}\n onPointerLeave={handlePointerLeave}\n >\n <IconButton\n ref={btnRef}\n type=\"button\"\n variant=\"ghost\"\n aria-haspopup=\"menu\"\n aria-expanded={open}\n onClick={(e) => { e.stopPropagation(); setOpen(true); requestAnimationFrame(updatePosition) }}\n >\n <span aria-hidden=\"true\">\u22EF</span>\n <span className=\"sr-only\">{t('ui.rowActions.openActions', 'Open actions')}</span>\n </IconButton>\n {open && anchorRect && createPortal(\n <div\n ref={menuRef}\n role=\"menu\"\n className=\"fixed w-44 max-w-[calc(100vw-1rem)] rounded-md border bg-background p-1 shadow focus-visible:outline-none z-dropdown\"\n style={{\n top: direction === 'down' ? anchorRect.bottom + 8 : anchorRect.top - 8,\n left: Math.min(anchorRect.right, window.innerWidth - 8),\n transform: `translate(-100%, ${direction === 'down' ? '0' : '-100%'})`,\n }}\n onPointerEnter={handlePointerEnter}\n onPointerLeave={handlePointerLeave}\n >\n {items.map((it, idx) => (\n it.href ? (\n <a\n key={idx}\n href={it.href}\n className={`block w-full text-left px-2 py-1 text-sm rounded hover:bg-accent ${it.destructive ? 'text-
|
|
5
|
-
"mappings": "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
|
|
4
|
+
"sourcesContent": ["\"use client\"\nimport * as React from 'react'\nimport { createPortal } from 'react-dom'\nimport { useT } from '@open-mercato/shared/lib/i18n/context'\nimport { IconButton } from '../primitives/icon-button'\nimport { Button } from '../primitives/button'\n\nexport type RowActionItem = {\n id?: string\n label: string\n onSelect?: () => void\n href?: string\n destructive?: boolean\n}\n\nexport function RowActions({ items = [] }: { items?: RowActionItem[] }) {\n const t = useT()\n const [open, setOpen] = React.useState(false)\n const btnRef = React.useRef<HTMLButtonElement>(null)\n const menuRef = React.useRef<HTMLDivElement>(null)\n const hoverTimeoutRef = React.useRef<NodeJS.Timeout | null>(null)\n const [anchorRect, setAnchorRect] = React.useState<DOMRect | null>(null)\n const [direction, setDirection] = React.useState<'down' | 'up'>('down')\n\n const updatePosition = React.useCallback(() => {\n if (!btnRef.current) return\n const rect = btnRef.current.getBoundingClientRect()\n setAnchorRect(rect)\n // Decide whether to open up or down based on available viewport space\n const spaceBelow = window.innerHeight - rect.bottom\n const spaceAbove = rect.top\n setDirection(spaceBelow < 180 && spaceAbove > spaceBelow ? 'up' : 'down')\n }, [])\n\n React.useEffect(() => {\n if (!open) return\n updatePosition()\n function onDocClick(e: MouseEvent) {\n const t = e.target as Node\n if (menuRef.current && !menuRef.current.contains(t) && btnRef.current && !btnRef.current.contains(t)) {\n setOpen(false)\n }\n }\n function onKey(e: KeyboardEvent) {\n if (e.key === 'Escape') {\n setOpen(false)\n btnRef.current?.focus()\n }\n }\n function onScrollOrResize() {\n updatePosition()\n }\n document.addEventListener('mousedown', onDocClick)\n document.addEventListener('keydown', onKey)\n window.addEventListener('scroll', onScrollOrResize, true)\n window.addEventListener('resize', onScrollOrResize)\n return () => {\n document.removeEventListener('mousedown', onDocClick)\n document.removeEventListener('keydown', onKey)\n window.removeEventListener('scroll', onScrollOrResize, true)\n window.removeEventListener('resize', onScrollOrResize)\n }\n }, [open, updatePosition])\n\n // Cleanup timeout on unmount\n React.useEffect(() => {\n return () => {\n if (hoverTimeoutRef.current) {\n clearTimeout(hoverTimeoutRef.current)\n }\n }\n }, [])\n\n if (items.length === 0) return null\n\n const handlePointerEnter = (event: React.PointerEvent) => {\n if (event.pointerType === 'touch') return\n if (hoverTimeoutRef.current) {\n clearTimeout(hoverTimeoutRef.current)\n }\n setOpen(true)\n }\n\n const handlePointerLeave = (event: React.PointerEvent) => {\n if (event.pointerType === 'touch') return\n hoverTimeoutRef.current = setTimeout(() => {\n setOpen(false)\n }, 150)\n }\n\n return (\n <div\n className=\"relative inline-block text-left\"\n onPointerEnter={handlePointerEnter}\n onPointerLeave={handlePointerLeave}\n >\n <IconButton\n ref={btnRef}\n type=\"button\"\n variant=\"ghost\"\n aria-haspopup=\"menu\"\n aria-expanded={open}\n onClick={(e) => { e.stopPropagation(); setOpen(true); requestAnimationFrame(updatePosition) }}\n >\n <span aria-hidden=\"true\">\u22EF</span>\n <span className=\"sr-only\">{t('ui.rowActions.openActions', 'Open actions')}</span>\n </IconButton>\n {open && anchorRect && createPortal(\n <div\n ref={menuRef}\n role=\"menu\"\n className=\"fixed w-44 max-w-[calc(100vw-1rem)] rounded-md border bg-background p-1 shadow focus-visible:outline-none z-dropdown\"\n style={{\n top: direction === 'down' ? anchorRect.bottom + 8 : anchorRect.top - 8,\n left: Math.min(anchorRect.right, window.innerWidth - 8),\n transform: `translate(-100%, ${direction === 'down' ? '0' : '-100%'})`,\n }}\n onPointerEnter={handlePointerEnter}\n onPointerLeave={handlePointerLeave}\n >\n {items.map((it, idx) => (\n it.href ? (\n <a\n key={idx}\n href={it.href}\n className={`block w-full text-left px-2 py-1 text-sm rounded hover:bg-accent ${it.destructive ? 'text-destructive' : ''}`}\n role=\"menuitem\"\n onClick={(event) => {\n event.stopPropagation()\n setOpen(false)\n }}\n >\n {it.label}\n </a>\n ) : (\n <Button\n key={idx}\n type=\"button\"\n variant=\"ghost\"\n size=\"sm\"\n className={`w-full justify-start rounded-none font-normal ${it.destructive ? 'text-destructive' : ''}`}\n role=\"menuitem\"\n onClick={(event) => {\n event.stopPropagation()\n setOpen(false)\n it.onSelect?.()\n }}\n >\n {it.label}\n </Button>\n )\n ))}\n </div>,\n document.body\n )}\n </div>\n )\n}\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
6
|
"names": ["t"]
|
|
7
7
|
}
|
|
@@ -208,7 +208,7 @@ function ConfirmDialog({
|
|
|
208
208
|
Button,
|
|
209
209
|
{
|
|
210
210
|
ref: confirmButtonRef,
|
|
211
|
-
variant: variant === "destructive" ? "destructive" : "default",
|
|
211
|
+
variant: variant === "destructive" ? "destructive-solid" : "default",
|
|
212
212
|
onClick: handleConfirm,
|
|
213
213
|
disabled: loading,
|
|
214
214
|
className: "w-full sm:w-auto",
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../../src/backend/confirm-dialog/ConfirmDialog.tsx"],
|
|
4
|
-
"sourcesContent": ["\"use client\";\nimport * as React from \"react\";\nimport { createPortal } from \"react-dom\";\nimport { useT } from \"@open-mercato/shared/lib/i18n/context\";\nimport { Button } from \"@open-mercato/ui/primitives/button\";\nimport { IconButton } from \"../../primitives/icon-button\";\nimport { cn } from \"@open-mercato/shared/lib/utils\";\nimport { Loader2, X } from \"lucide-react\";\n\nexport type ConfirmDialogProps = {\n /** Whether the dialog is open (controlled mode \u2014 used by useConfirmDialog) */\n open?: boolean;\n /** Callback when open state changes (controlled mode) */\n onOpenChange?: (open: boolean) => void;\n /** Callback when user confirms */\n onConfirm: () => void | Promise<void>;\n /** Callback when user cancels (optional, defaults to closing) */\n onCancel?: () => void;\n /** Dialog title. Defaults to i18n key \"ui.dialogs.confirm.defaultTitle\" (\"Are you sure?\") */\n title?: string;\n /** Dialog body text / description */\n text?: string;\n /** Confirm button label. Defaults to i18n \"ui.dialogs.confirm.confirmText\" (\"Confirm\").\n * Pass `false` to hide the confirm button entirely. */\n confirmText?: string | false;\n /** Cancel button label. Defaults to i18n \"ui.dialogs.confirm.cancelText\" (\"Cancel\").\n * Pass `false` to hide the cancel button entirely. */\n cancelText?: string | false;\n /** Visual variant \u2014 \"destructive\" renders the confirm button in red */\n variant?: \"default\" | \"destructive\";\n /** Whether the confirm button shows a loading spinner.\n * Useful for async onConfirm handlers (e.g., waiting for API response before closing). */\n loading?: boolean;\n /** Trigger element \u2014 when provided, component manages its own open state (declarative mode).\n * Clicking the trigger opens the dialog. */\n trigger?: React.ReactNode;\n};\n\nexport function ConfirmDialog({\n open: controlledOpen,\n onOpenChange,\n onConfirm,\n onCancel,\n title,\n text,\n confirmText,\n cancelText,\n variant = \"default\",\n loading = false,\n trigger,\n}: ConfirmDialogProps) {\n const t = useT();\n const dialogRef = React.useRef<HTMLDialogElement>(null);\n const [internalOpen, setInternalOpen] = React.useState(false);\n const cancelButtonRef = React.useRef<HTMLButtonElement>(null);\n const confirmButtonRef = React.useRef<HTMLButtonElement>(null);\n const [portalTarget, setPortalTarget] = React.useState<HTMLElement | null>(null);\n // Unique IDs so multiple ConfirmDialog instances on the same page don't\n // collide and make `aria-labelledby` resolve to the wrong dialog's title.\n const reactId = React.useId();\n const titleId = `confirm-dialog-title-${reactId}`;\n const descriptionId = `confirm-dialog-description-${reactId}`;\n\n // Determine if we're in controlled mode (open prop provided) or declarative mode (trigger provided)\n const isControlled = controlledOpen !== undefined;\n const open = isControlled ? controlledOpen : internalOpen;\n const setOpen = isControlled\n ? onOpenChange || (() => {})\n : setInternalOpen;\n const handleCancelCallback = React.useCallback(() => {\n if (!isControlled) {\n onCancel?.();\n }\n }, [isControlled, onCancel]);\n\n // Default text values from i18n\n const resolvedTitle =\n title ?? t(\"ui.dialogs.confirm.defaultTitle\", \"Are you sure?\");\n const resolvedConfirmText =\n confirmText === false\n ? false\n : confirmText ?? t(\"ui.dialogs.confirm.confirmText\", \"Confirm\");\n const resolvedCancelText =\n cancelText === false\n ? false\n : cancelText ?? t(\"ui.dialogs.confirm.cancelText\", \"Cancel\");\n const closeAriaLabel = t(\"ui.dialog.close.ariaLabel\", \"Close\");\n const requestClose = React.useCallback(() => {\n setOpen(false);\n handleCancelCallback();\n }, [setOpen, handleCancelCallback]);\n\n React.useLayoutEffect(() => {\n setPortalTarget(document.body);\n }, []);\n\n // Handle dialog open/close with native showModal/close\n React.useLayoutEffect(() => {\n const dialog = dialogRef.current;\n if (!dialog) return;\n\n if (open) {\n if (!dialog.open) {\n dialog.showModal();\n // Focus cancel button (safe default) or confirm if no cancel\n setTimeout(() => {\n if (resolvedCancelText !== false && cancelButtonRef.current) {\n cancelButtonRef.current.focus();\n } else if (confirmButtonRef.current) {\n confirmButtonRef.current.focus();\n }\n }, 0);\n }\n } else {\n if (dialog.open) {\n dialog.close();\n }\n }\n }, [open, resolvedCancelText, portalTarget]);\n\n // Handle native cancel event (Escape key)\n React.useEffect(() => {\n const dialog = dialogRef.current;\n if (!dialog) return;\n\n const handleCancel = (e: Event) => {\n // Prevent close if loading\n if (loading) {\n e.preventDefault();\n return;\n }\n requestClose();\n };\n\n dialog.addEventListener(\"cancel\", handleCancel);\n return () => dialog.removeEventListener(\"cancel\", handleCancel);\n }, [loading, requestClose]);\n\n React.useEffect(() => {\n if (!open) return;\n\n const handleEscape = (event: KeyboardEvent) => {\n if (event.key !== \"Escape\") return;\n if (!(event.target instanceof Node) || !dialogRef.current?.contains(event.target)) return;\n event.preventDefault();\n event.stopPropagation();\n if (!loading) requestClose();\n };\n\n window.addEventListener(\"keydown\", handleEscape, true);\n return () => window.removeEventListener(\"keydown\", handleEscape, true);\n }, [loading, open, requestClose]);\n\n // Handle backdrop click\n const handleBackdropClick = (e: React.MouseEvent<HTMLDialogElement>) => {\n // Only close if clicking directly on the dialog (backdrop), not its children\n if (e.target === dialogRef.current && !loading) {\n requestClose();\n }\n };\n\n // Handle keyboard shortcuts\n React.useEffect(() => {\n if (!open) return;\n\n const handleKeyDown = (e: KeyboardEvent) => {\n // Cmd/Ctrl+Enter confirms\n if ((e.metaKey || e.ctrlKey) && e.key === \"Enter\" && !loading) {\n e.preventDefault();\n handleConfirm();\n }\n };\n\n window.addEventListener(\"keydown\", handleKeyDown);\n return () => window.removeEventListener(\"keydown\", handleKeyDown);\n }, [open, loading]);\n\n const handleConfirm = async () => {\n await onConfirm();\n // Don't auto-close if loading \u2014 let the parent control when to close\n if (!loading) {\n setOpen(false);\n }\n };\n\n const handleCancel = () => {\n requestClose();\n };\n\n const handleTriggerClick = () => {\n if (trigger) {\n setOpen(true);\n }\n };\n\n const dialogElement = (\n <dialog\n ref={dialogRef}\n role=\"alertdialog\"\n aria-labelledby={titleId}\n aria-describedby={text ? descriptionId : undefined}\n onClick={handleBackdropClick}\n className={cn(\n // Reset dialog defaults\n \"m-0 p-0 max-w-none bg-transparent border-none pointer-events-auto\",\n // Backdrop styling\n \"backdrop:bg-black/50 backdrop:backdrop-blur-sm backdrop:transition-opacity\",\n // Mobile: bottom sheet\n \"fixed inset-x-0 bottom-0 top-auto w-full\",\n // Desktop: centered\n \"sm:inset-auto sm:mx-auto sm:my-auto sm:max-w-md sm:top-1/2 sm:left-1/2 sm:-translate-x-1/2 sm:-translate-y-1/2\",\n // Animation with reduced motion support\n \"motion-safe:open:animate-in motion-safe:open:fade-in-0 motion-safe:open:slide-in-from-bottom-4\",\n \"sm:motion-safe:open:slide-in-from-bottom-0 sm:motion-safe:open:zoom-in-95\",\n // Duration\n \"motion-safe:open:duration-300\"\n )}\n >\n <div\n role=\"document\"\n className={cn(\n // Panel container\n \"flex flex-col gap-4 rounded-t-2xl border-t bg-card p-6 text-foreground shadow-lg\",\n \"sm:rounded-xl sm:border\",\n // Relative positioning for close button\n \"relative\"\n )}\n >\n {/* Close button */}\n <IconButton\n type=\"button\"\n variant=\"ghost\"\n size=\"sm\"\n onClick={handleCancel}\n disabled={loading}\n aria-label={closeAriaLabel}\n className=\"absolute right-4 top-4 opacity-70 hover:opacity-100\"\n >\n <X className=\"h-4 w-4\" />\n </IconButton>\n\n {/* Title */}\n <h2\n id={titleId}\n className={cn(\n \"text-sm font-medium leading-snug tracking-tight pr-6\",\n // Mobile: centered, Desktop: left-aligned\n \"text-center sm:text-left\"\n )}\n >\n {resolvedTitle}\n </h2>\n\n {/* Description (optional) */}\n {text && (\n <p\n id={descriptionId}\n className=\"text-sm font-medium leading-snug text-muted-foreground\"\n >\n {text}\n </p>\n )}\n\n {/* Actions */}\n <div className=\"flex flex-col-reverse gap-2 sm:flex-row sm:justify-end\">\n {resolvedCancelText !== false && (\n <Button\n ref={cancelButtonRef}\n variant=\"outline\"\n onClick={handleCancel}\n disabled={loading}\n className=\"w-full sm:w-auto\"\n >\n {resolvedCancelText}\n </Button>\n )}\n {resolvedConfirmText !== false && (\n <Button\n ref={confirmButtonRef}\n variant={variant === \"destructive\" ? \"destructive\" : \"default\"}\n onClick={handleConfirm}\n disabled={loading}\n className=\"w-full sm:w-auto\"\n >\n {loading && <Loader2 className=\"mr-2 h-4 w-4 animate-spin\" />}\n {resolvedConfirmText}\n </Button>\n )}\n </div>\n </div>\n </dialog>\n );\n\n return (\n <>\n {trigger && (\n <div onClick={handleTriggerClick} className=\"inline-block\" role=\"presentation\">\n {trigger}\n </div>\n )}\n\n {portalTarget ? createPortal(dialogElement, portalTarget) : null}\n </>\n );\n}\n"],
|
|
5
|
-
"mappings": "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
|
|
4
|
+
"sourcesContent": ["\"use client\";\nimport * as React from \"react\";\nimport { createPortal } from \"react-dom\";\nimport { useT } from \"@open-mercato/shared/lib/i18n/context\";\nimport { Button } from \"@open-mercato/ui/primitives/button\";\nimport { IconButton } from \"../../primitives/icon-button\";\nimport { cn } from \"@open-mercato/shared/lib/utils\";\nimport { Loader2, X } from \"lucide-react\";\n\nexport type ConfirmDialogProps = {\n /** Whether the dialog is open (controlled mode \u2014 used by useConfirmDialog) */\n open?: boolean;\n /** Callback when open state changes (controlled mode) */\n onOpenChange?: (open: boolean) => void;\n /** Callback when user confirms */\n onConfirm: () => void | Promise<void>;\n /** Callback when user cancels (optional, defaults to closing) */\n onCancel?: () => void;\n /** Dialog title. Defaults to i18n key \"ui.dialogs.confirm.defaultTitle\" (\"Are you sure?\") */\n title?: string;\n /** Dialog body text / description */\n text?: string;\n /** Confirm button label. Defaults to i18n \"ui.dialogs.confirm.confirmText\" (\"Confirm\").\n * Pass `false` to hide the confirm button entirely. */\n confirmText?: string | false;\n /** Cancel button label. Defaults to i18n \"ui.dialogs.confirm.cancelText\" (\"Cancel\").\n * Pass `false` to hide the cancel button entirely. */\n cancelText?: string | false;\n /** Visual variant \u2014 \"destructive\" renders the confirm button in red */\n variant?: \"default\" | \"destructive\";\n /** Whether the confirm button shows a loading spinner.\n * Useful for async onConfirm handlers (e.g., waiting for API response before closing). */\n loading?: boolean;\n /** Trigger element \u2014 when provided, component manages its own open state (declarative mode).\n * Clicking the trigger opens the dialog. */\n trigger?: React.ReactNode;\n};\n\nexport function ConfirmDialog({\n open: controlledOpen,\n onOpenChange,\n onConfirm,\n onCancel,\n title,\n text,\n confirmText,\n cancelText,\n variant = \"default\",\n loading = false,\n trigger,\n}: ConfirmDialogProps) {\n const t = useT();\n const dialogRef = React.useRef<HTMLDialogElement>(null);\n const [internalOpen, setInternalOpen] = React.useState(false);\n const cancelButtonRef = React.useRef<HTMLButtonElement>(null);\n const confirmButtonRef = React.useRef<HTMLButtonElement>(null);\n const [portalTarget, setPortalTarget] = React.useState<HTMLElement | null>(null);\n // Unique IDs so multiple ConfirmDialog instances on the same page don't\n // collide and make `aria-labelledby` resolve to the wrong dialog's title.\n const reactId = React.useId();\n const titleId = `confirm-dialog-title-${reactId}`;\n const descriptionId = `confirm-dialog-description-${reactId}`;\n\n // Determine if we're in controlled mode (open prop provided) or declarative mode (trigger provided)\n const isControlled = controlledOpen !== undefined;\n const open = isControlled ? controlledOpen : internalOpen;\n const setOpen = isControlled\n ? onOpenChange || (() => {})\n : setInternalOpen;\n const handleCancelCallback = React.useCallback(() => {\n if (!isControlled) {\n onCancel?.();\n }\n }, [isControlled, onCancel]);\n\n // Default text values from i18n\n const resolvedTitle =\n title ?? t(\"ui.dialogs.confirm.defaultTitle\", \"Are you sure?\");\n const resolvedConfirmText =\n confirmText === false\n ? false\n : confirmText ?? t(\"ui.dialogs.confirm.confirmText\", \"Confirm\");\n const resolvedCancelText =\n cancelText === false\n ? false\n : cancelText ?? t(\"ui.dialogs.confirm.cancelText\", \"Cancel\");\n const closeAriaLabel = t(\"ui.dialog.close.ariaLabel\", \"Close\");\n const requestClose = React.useCallback(() => {\n setOpen(false);\n handleCancelCallback();\n }, [setOpen, handleCancelCallback]);\n\n React.useLayoutEffect(() => {\n setPortalTarget(document.body);\n }, []);\n\n // Handle dialog open/close with native showModal/close\n React.useLayoutEffect(() => {\n const dialog = dialogRef.current;\n if (!dialog) return;\n\n if (open) {\n if (!dialog.open) {\n dialog.showModal();\n // Focus cancel button (safe default) or confirm if no cancel\n setTimeout(() => {\n if (resolvedCancelText !== false && cancelButtonRef.current) {\n cancelButtonRef.current.focus();\n } else if (confirmButtonRef.current) {\n confirmButtonRef.current.focus();\n }\n }, 0);\n }\n } else {\n if (dialog.open) {\n dialog.close();\n }\n }\n }, [open, resolvedCancelText, portalTarget]);\n\n // Handle native cancel event (Escape key)\n React.useEffect(() => {\n const dialog = dialogRef.current;\n if (!dialog) return;\n\n const handleCancel = (e: Event) => {\n // Prevent close if loading\n if (loading) {\n e.preventDefault();\n return;\n }\n requestClose();\n };\n\n dialog.addEventListener(\"cancel\", handleCancel);\n return () => dialog.removeEventListener(\"cancel\", handleCancel);\n }, [loading, requestClose]);\n\n React.useEffect(() => {\n if (!open) return;\n\n const handleEscape = (event: KeyboardEvent) => {\n if (event.key !== \"Escape\") return;\n if (!(event.target instanceof Node) || !dialogRef.current?.contains(event.target)) return;\n event.preventDefault();\n event.stopPropagation();\n if (!loading) requestClose();\n };\n\n window.addEventListener(\"keydown\", handleEscape, true);\n return () => window.removeEventListener(\"keydown\", handleEscape, true);\n }, [loading, open, requestClose]);\n\n // Handle backdrop click\n const handleBackdropClick = (e: React.MouseEvent<HTMLDialogElement>) => {\n // Only close if clicking directly on the dialog (backdrop), not its children\n if (e.target === dialogRef.current && !loading) {\n requestClose();\n }\n };\n\n // Handle keyboard shortcuts\n React.useEffect(() => {\n if (!open) return;\n\n const handleKeyDown = (e: KeyboardEvent) => {\n // Cmd/Ctrl+Enter confirms\n if ((e.metaKey || e.ctrlKey) && e.key === \"Enter\" && !loading) {\n e.preventDefault();\n handleConfirm();\n }\n };\n\n window.addEventListener(\"keydown\", handleKeyDown);\n return () => window.removeEventListener(\"keydown\", handleKeyDown);\n }, [open, loading]);\n\n const handleConfirm = async () => {\n await onConfirm();\n // Don't auto-close if loading \u2014 let the parent control when to close\n if (!loading) {\n setOpen(false);\n }\n };\n\n const handleCancel = () => {\n requestClose();\n };\n\n const handleTriggerClick = () => {\n if (trigger) {\n setOpen(true);\n }\n };\n\n const dialogElement = (\n <dialog\n ref={dialogRef}\n role=\"alertdialog\"\n aria-labelledby={titleId}\n aria-describedby={text ? descriptionId : undefined}\n onClick={handleBackdropClick}\n className={cn(\n // Reset dialog defaults\n \"m-0 p-0 max-w-none bg-transparent border-none pointer-events-auto\",\n // Backdrop styling\n \"backdrop:bg-black/50 backdrop:backdrop-blur-sm backdrop:transition-opacity\",\n // Mobile: bottom sheet\n \"fixed inset-x-0 bottom-0 top-auto w-full\",\n // Desktop: centered\n \"sm:inset-auto sm:mx-auto sm:my-auto sm:max-w-md sm:top-1/2 sm:left-1/2 sm:-translate-x-1/2 sm:-translate-y-1/2\",\n // Animation with reduced motion support\n \"motion-safe:open:animate-in motion-safe:open:fade-in-0 motion-safe:open:slide-in-from-bottom-4\",\n \"sm:motion-safe:open:slide-in-from-bottom-0 sm:motion-safe:open:zoom-in-95\",\n // Duration\n \"motion-safe:open:duration-300\"\n )}\n >\n <div\n role=\"document\"\n className={cn(\n // Panel container\n \"flex flex-col gap-4 rounded-t-2xl border-t bg-card p-6 text-foreground shadow-lg\",\n \"sm:rounded-xl sm:border\",\n // Relative positioning for close button\n \"relative\"\n )}\n >\n {/* Close button */}\n <IconButton\n type=\"button\"\n variant=\"ghost\"\n size=\"sm\"\n onClick={handleCancel}\n disabled={loading}\n aria-label={closeAriaLabel}\n className=\"absolute right-4 top-4 opacity-70 hover:opacity-100\"\n >\n <X className=\"h-4 w-4\" />\n </IconButton>\n\n {/* Title */}\n <h2\n id={titleId}\n className={cn(\n \"text-sm font-medium leading-snug tracking-tight pr-6\",\n // Mobile: centered, Desktop: left-aligned\n \"text-center sm:text-left\"\n )}\n >\n {resolvedTitle}\n </h2>\n\n {/* Description (optional) */}\n {text && (\n <p\n id={descriptionId}\n className=\"text-sm font-medium leading-snug text-muted-foreground\"\n >\n {text}\n </p>\n )}\n\n {/* Actions */}\n <div className=\"flex flex-col-reverse gap-2 sm:flex-row sm:justify-end\">\n {resolvedCancelText !== false && (\n <Button\n ref={cancelButtonRef}\n variant=\"outline\"\n onClick={handleCancel}\n disabled={loading}\n className=\"w-full sm:w-auto\"\n >\n {resolvedCancelText}\n </Button>\n )}\n {resolvedConfirmText !== false && (\n <Button\n ref={confirmButtonRef}\n variant={variant === \"destructive\" ? \"destructive-solid\" : \"default\"}\n onClick={handleConfirm}\n disabled={loading}\n className=\"w-full sm:w-auto\"\n >\n {loading && <Loader2 className=\"mr-2 h-4 w-4 animate-spin\" />}\n {resolvedConfirmText}\n </Button>\n )}\n </div>\n </div>\n </dialog>\n );\n\n return (\n <>\n {trigger && (\n <div onClick={handleTriggerClick} className=\"inline-block\" role=\"presentation\">\n {trigger}\n </div>\n )}\n\n {portalTarget ? createPortal(dialogElement, portalTarget) : null}\n </>\n );\n}\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
6
|
"names": ["handleCancel"]
|
|
7
7
|
}
|
|
@@ -22,7 +22,7 @@ function AttachmentDeleteDialog({ open, onOpenChange, fileName, onConfirm, isDel
|
|
|
22
22
|
] }),
|
|
23
23
|
/* @__PURE__ */ jsxs(DialogFooter, { children: [
|
|
24
24
|
/* @__PURE__ */ jsx(Button, { type: "button", variant: "outline", onClick: () => onOpenChange(false), disabled: isDeleting, children: t("attachments.library.metadata.cancel", "Cancel") }),
|
|
25
|
-
/* @__PURE__ */ jsx(Button, { type: "button", variant: "destructive", onClick: onConfirm, disabled: isDeleting, children: t("attachments.library.actions.delete", "Delete") })
|
|
25
|
+
/* @__PURE__ */ jsx(Button, { type: "button", variant: "destructive-solid", onClick: onConfirm, disabled: isDeleting, children: t("attachments.library.actions.delete", "Delete") })
|
|
26
26
|
] })
|
|
27
27
|
] }) });
|
|
28
28
|
}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../../src/backend/detail/AttachmentDeleteDialog.tsx"],
|
|
4
|
-
"sourcesContent": ["\"use client\"\n\nimport * as React from 'react'\nimport { Dialog, DialogContent, DialogDescription, DialogFooter, DialogHeader, DialogTitle } from '@open-mercato/ui/primitives/dialog'\nimport { Button } from '@open-mercato/ui/primitives/button'\nimport { useDialogKeyHandler } from '@open-mercato/ui/hooks/useDialogKeyHandler'\nimport { useT } from '@open-mercato/shared/lib/i18n/context'\n\ntype Props = {\n open: boolean\n onOpenChange: (next: boolean) => void\n fileName?: string | null\n onConfirm: () => void\n isDeleting?: boolean\n}\n\nexport function AttachmentDeleteDialog({ open, onOpenChange, fileName, onConfirm, isDeleting }: Props) {\n const t = useT()\n const description = t(\n 'attachments.library.confirm.delete',\n 'Delete attachment \"{{name}}\"? This action cannot be undone.',\n ).replace('{{name}}', fileName || t('attachments.library.metadata.title', 'attachment'))\n\n const handleKeyDown = useDialogKeyHandler({\n onConfirm,\n onCancel: () => onOpenChange(false),\n disabled: isDeleting,\n })\n\n return (\n <Dialog open={open} onOpenChange={onOpenChange}>\n <DialogContent onKeyDown={handleKeyDown}>\n <DialogHeader>\n <DialogTitle>{t('attachments.library.actions.delete', 'Delete')}</DialogTitle>\n <DialogDescription>{description}</DialogDescription>\n </DialogHeader>\n <DialogFooter>\n <Button type=\"button\" variant=\"outline\" onClick={() => onOpenChange(false)} disabled={isDeleting}>\n {t('attachments.library.metadata.cancel', 'Cancel')}\n </Button>\n <Button type=\"button\" variant=\"destructive\" onClick={onConfirm} disabled={isDeleting}>\n {t('attachments.library.actions.delete', 'Delete')}\n </Button>\n </DialogFooter>\n </DialogContent>\n </Dialog>\n )\n}\n"],
|
|
5
|
-
"mappings": ";AAgCQ,SACE,KADF;AA7BR,SAAS,QAAQ,eAAe,mBAAmB,cAAc,cAAc,mBAAmB;AAClG,SAAS,cAAc;AACvB,SAAS,2BAA2B;AACpC,SAAS,YAAY;AAUd,SAAS,uBAAuB,EAAE,MAAM,cAAc,UAAU,WAAW,WAAW,GAAU;AACrG,QAAM,IAAI,KAAK;AACf,QAAM,cAAc;AAAA,IAClB;AAAA,IACA;AAAA,EACF,EAAE,QAAQ,YAAY,YAAY,EAAE,sCAAsC,YAAY,CAAC;AAEvF,QAAM,gBAAgB,oBAAoB;AAAA,IACxC;AAAA,IACA,UAAU,MAAM,aAAa,KAAK;AAAA,IAClC,UAAU;AAAA,EACZ,CAAC;AAED,SACE,oBAAC,UAAO,MAAY,cAClB,+BAAC,iBAAc,WAAW,eACxB;AAAA,yBAAC,gBACC;AAAA,0BAAC,eAAa,YAAE,sCAAsC,QAAQ,GAAE;AAAA,MAChE,oBAAC,qBAAmB,uBAAY;AAAA,OAClC;AAAA,IACA,qBAAC,gBACC;AAAA,0BAAC,UAAO,MAAK,UAAS,SAAQ,WAAU,SAAS,MAAM,aAAa,KAAK,GAAG,UAAU,YACnF,YAAE,uCAAuC,QAAQ,GACpD;AAAA,MACA,oBAAC,UAAO,MAAK,UAAS,SAAQ,
|
|
4
|
+
"sourcesContent": ["\"use client\"\n\nimport * as React from 'react'\nimport { Dialog, DialogContent, DialogDescription, DialogFooter, DialogHeader, DialogTitle } from '@open-mercato/ui/primitives/dialog'\nimport { Button } from '@open-mercato/ui/primitives/button'\nimport { useDialogKeyHandler } from '@open-mercato/ui/hooks/useDialogKeyHandler'\nimport { useT } from '@open-mercato/shared/lib/i18n/context'\n\ntype Props = {\n open: boolean\n onOpenChange: (next: boolean) => void\n fileName?: string | null\n onConfirm: () => void\n isDeleting?: boolean\n}\n\nexport function AttachmentDeleteDialog({ open, onOpenChange, fileName, onConfirm, isDeleting }: Props) {\n const t = useT()\n const description = t(\n 'attachments.library.confirm.delete',\n 'Delete attachment \"{{name}}\"? This action cannot be undone.',\n ).replace('{{name}}', fileName || t('attachments.library.metadata.title', 'attachment'))\n\n const handleKeyDown = useDialogKeyHandler({\n onConfirm,\n onCancel: () => onOpenChange(false),\n disabled: isDeleting,\n })\n\n return (\n <Dialog open={open} onOpenChange={onOpenChange}>\n <DialogContent onKeyDown={handleKeyDown}>\n <DialogHeader>\n <DialogTitle>{t('attachments.library.actions.delete', 'Delete')}</DialogTitle>\n <DialogDescription>{description}</DialogDescription>\n </DialogHeader>\n <DialogFooter>\n <Button type=\"button\" variant=\"outline\" onClick={() => onOpenChange(false)} disabled={isDeleting}>\n {t('attachments.library.metadata.cancel', 'Cancel')}\n </Button>\n <Button type=\"button\" variant=\"destructive-solid\" onClick={onConfirm} disabled={isDeleting}>\n {t('attachments.library.actions.delete', 'Delete')}\n </Button>\n </DialogFooter>\n </DialogContent>\n </Dialog>\n )\n}\n"],
|
|
5
|
+
"mappings": ";AAgCQ,SACE,KADF;AA7BR,SAAS,QAAQ,eAAe,mBAAmB,cAAc,cAAc,mBAAmB;AAClG,SAAS,cAAc;AACvB,SAAS,2BAA2B;AACpC,SAAS,YAAY;AAUd,SAAS,uBAAuB,EAAE,MAAM,cAAc,UAAU,WAAW,WAAW,GAAU;AACrG,QAAM,IAAI,KAAK;AACf,QAAM,cAAc;AAAA,IAClB;AAAA,IACA;AAAA,EACF,EAAE,QAAQ,YAAY,YAAY,EAAE,sCAAsC,YAAY,CAAC;AAEvF,QAAM,gBAAgB,oBAAoB;AAAA,IACxC;AAAA,IACA,UAAU,MAAM,aAAa,KAAK;AAAA,IAClC,UAAU;AAAA,EACZ,CAAC;AAED,SACE,oBAAC,UAAO,MAAY,cAClB,+BAAC,iBAAc,WAAW,eACxB;AAAA,yBAAC,gBACC;AAAA,0BAAC,eAAa,YAAE,sCAAsC,QAAQ,GAAE;AAAA,MAChE,oBAAC,qBAAmB,uBAAY;AAAA,OAClC;AAAA,IACA,qBAAC,gBACC;AAAA,0BAAC,UAAO,MAAK,UAAS,SAAQ,WAAU,SAAS,MAAM,aAAa,KAAK,GAAG,UAAU,YACnF,YAAE,uCAAuC,QAAQ,GACpD;AAAA,MACA,oBAAC,UAAO,MAAK,UAAS,SAAQ,qBAAoB,SAAS,WAAW,UAAU,YAC7E,YAAE,sCAAsC,QAAQ,GACnD;AAAA,OACF;AAAA,KACF,GACF;AAEJ;",
|
|
6
6
|
"names": []
|
|
7
7
|
}
|
|
@@ -214,7 +214,7 @@ function AttachmentsSectionImpl({
|
|
|
214
214
|
]
|
|
215
215
|
}
|
|
216
216
|
),
|
|
217
|
-
error ? /* @__PURE__ */ jsx("p", { className: "text-xs font-medium text-
|
|
217
|
+
error ? /* @__PURE__ */ jsx("p", { className: "text-xs font-medium text-status-error-text", children: error }) : null,
|
|
218
218
|
loading ? /* @__PURE__ */ jsx("div", { className: "text-sm text-muted-foreground", children: t("attachments.library.loading", "Loading attachments\u2026") }) : items.length ? /* @__PURE__ */ jsx("div", { className: cn(
|
|
219
219
|
"grid gap-3",
|
|
220
220
|
compact ? "grid-cols-2 sm:grid-cols-4 lg:grid-cols-5" : "grid-cols-1 sm:grid-cols-2 lg:grid-cols-3"
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../../src/backend/detail/AttachmentsSection.tsx"],
|
|
4
|
-
"sourcesContent": ["\"use client\"\n\nimport * as React from 'react'\nimport { Upload, Trash2, File, FileText, FileSpreadsheet, FileArchive, FileAudio, FileVideo, FileCode } from 'lucide-react'\nimport { Button } from '../../primitives/button'\nimport { apiCall } from '@open-mercato/ui/backend/utils/apiCall'\nimport { useT } from '@open-mercato/shared/lib/i18n/context'\nimport { cn } from '@open-mercato/shared/lib/utils'\nimport { AttachmentVisualPreview, formatAttachmentFileSize } from './AttachmentVisualPreview'\nimport { AttachmentDeleteDialog } from './AttachmentDeleteDialog'\nimport { AttachmentMetadataDialog, type AttachmentItem, type AttachmentMetadataSavePayload } from './AttachmentMetadataDialog'\nimport { ComponentReplacementHandles } from '@open-mercato/shared/modules/widgets/component-registry'\nimport { useRegisteredComponent } from '../injection/useRegisteredComponent'\n\ntype AttachmentsResponse = {\n items?: AttachmentItem[]\n total?: number\n page?: number\n pageSize?: number\n totalPages?: number\n error?: string\n}\n\ntype Props = {\n entityId: string\n recordId: string | null\n title?: string\n description?: string\n className?: string\n showHeader?: boolean\n compact?: boolean\n onChanged?: () => void\n}\n\nfunction AttachmentsSectionImpl({\n entityId,\n recordId,\n title,\n description,\n className,\n showHeader = true,\n compact = false,\n onChanged,\n}: Props) {\n const t = useT()\n const [items, setItems] = React.useState<AttachmentItem[]>([])\n const [page, setPage] = React.useState(1)\n const [totalPages, setTotalPages] = React.useState(1)\n const [loading, setLoading] = React.useState(false)\n const [error, setError] = React.useState<string | null>(null)\n const [isUploading, setIsUploading] = React.useState(false)\n const [isDragOver, setIsDragOver] = React.useState(false)\n const [metadataOpen, setMetadataOpen] = React.useState(false)\n const [selectedItem, setSelectedItem] = React.useState<AttachmentItem | null>(null)\n const [deleteOpen, setDeleteOpen] = React.useState(false)\n const [deleteTarget, setDeleteTarget] = React.useState<AttachmentItem | null>(null)\n const fileInputRef = React.useRef<HTMLInputElement | null>(null)\n\n const load = React.useCallback(async (targetPage = 1, replace = true) => {\n if (!recordId) return\n setLoading(true)\n setError(null)\n try {\n const params = new URLSearchParams({\n entityId,\n recordId,\n page: String(targetPage),\n pageSize: '24',\n })\n const call = await apiCall<AttachmentsResponse>(\n `/api/attachments?${params.toString()}`,\n undefined,\n { fallback: { items: [] } },\n )\n if (!call.ok) {\n const message = call.result?.error || t('attachments.library.errors.load', 'Failed to load attachments.')\n throw new Error(message)\n }\n const payload = call.result ?? { items: [] }\n const nextItems = Array.isArray(payload.items) ? payload.items : []\n setItems((current) => (replace ? nextItems : [...current, ...nextItems]))\n setPage(typeof payload.page === 'number' ? payload.page : targetPage)\n setTotalPages(typeof payload.totalPages === 'number' ? payload.totalPages : 1)\n } catch (err: any) {\n setError(err?.message || t('attachments.library.errors.load', 'Failed to load attachments.'))\n } finally {\n setLoading(false)\n }\n }, [entityId, recordId, t])\n\n React.useEffect(() => {\n if (recordId) {\n void load()\n } else {\n setItems([])\n setPage(1)\n setTotalPages(1)\n setError(null)\n }\n }, [load, recordId])\n\n const acceptFiles = React.useCallback(\n async (files: FileList | null) => {\n if (!files || !files.length || !recordId) return\n setError(null)\n setIsUploading(true)\n try {\n for (const file of Array.from(files)) {\n const fd = new FormData()\n fd.set('entityId', entityId)\n fd.set('recordId', recordId)\n fd.set('file', file)\n const call = await apiCall<{ ok?: boolean; item?: AttachmentItem; error?: string }>(\n '/api/attachments',\n { method: 'POST', body: fd },\n { fallback: null },\n )\n if (!call.ok) {\n const message = call.result?.error || t('attachments.library.upload.failed', 'Upload failed.')\n throw new Error(message)\n }\n }\n await load(1, true)\n onChanged?.()\n } catch (err: any) {\n setError(err?.message || t('attachments.library.upload.failed', 'Upload failed.'))\n } finally {\n setIsUploading(false)\n if (fileInputRef.current) {\n fileInputRef.current.value = ''\n }\n }\n },\n [entityId, load, onChanged, recordId, t],\n )\n\n const handleDrop = React.useCallback(\n (event: React.DragEvent<HTMLDivElement>) => {\n event.preventDefault()\n event.stopPropagation()\n setIsDragOver(false)\n void acceptFiles(event.dataTransfer?.files ?? null)\n },\n [acceptFiles],\n )\n\n const handleDragOver = React.useCallback((event: React.DragEvent<HTMLDivElement>) => {\n event.preventDefault()\n event.stopPropagation()\n setIsDragOver(true)\n }, [])\n\n const handleDragLeave = React.useCallback((event: React.DragEvent<HTMLDivElement>) => {\n event.preventDefault()\n event.stopPropagation()\n setIsDragOver(false)\n }, [])\n\n const openMetadataDialog = React.useCallback((item: AttachmentItem) => {\n setSelectedItem(item)\n setMetadataOpen(true)\n }, [])\n\n const openDeleteDialog = React.useCallback((item: AttachmentItem) => {\n setDeleteTarget(item)\n setDeleteOpen(true)\n }, [])\n\n const handleDelete = React.useCallback(async () => {\n if (!deleteTarget) return\n try {\n const call = await apiCall<{ error?: string }>(\n `/api/attachments?id=${encodeURIComponent(deleteTarget.id)}`,\n { method: 'DELETE' },\n )\n if (!call.ok) {\n const message = call.result?.error || t('attachments.library.errors.delete', 'Failed to delete attachment.')\n throw new Error(message)\n }\n setDeleteOpen(false)\n setDeleteTarget(null)\n await load(1, true)\n onChanged?.()\n } catch (err: any) {\n setError(err?.message || t('attachments.library.errors.delete', 'Failed to delete attachment.'))\n }\n }, [deleteTarget, load, onChanged, t])\n\n const handleMetadataSave = React.useCallback(\n async (id: string, payload: AttachmentMetadataSavePayload) => {\n const body: Record<string, unknown> = {\n tags: payload.tags,\n assignments: payload.assignments,\n }\n if (payload.customFields && Object.keys(payload.customFields).length) {\n body.customFields = payload.customFields\n }\n const call = await apiCall<{ error?: string }>(`/api/attachments/library/${encodeURIComponent(id)}`, {\n method: 'PATCH',\n headers: { 'content-type': 'application/json' },\n body: JSON.stringify(body),\n })\n if (!call.ok) {\n const message = call.result?.error || t('attachments.library.metadata.error', 'Failed to update metadata.')\n throw new Error(message)\n }\n setMetadataOpen(false)\n await load(1, true)\n onChanged?.()\n },\n [load, onChanged, t],\n )\n\n const sectionTitle = title ?? t('attachments.library.title', 'Attachments')\n const sectionDescription =\n description ?? t('attachments.library.description', 'Browse, tag, and manage every file stored in this workspace.')\n\n return (\n <div className={cn('space-y-4', className)}>\n {showHeader ? (\n <div className=\"space-y-1\">\n <div className=\"text-base font-medium\">{sectionTitle}</div>\n <div className=\"text-sm text-muted-foreground\">{sectionDescription}</div>\n </div>\n ) : null}\n\n {!recordId ? (\n <div className=\"rounded-md border border-dashed border-border/70 px-4 py-6 text-sm text-muted-foreground\">\n {t('attachments.library.upload.saveFirst', 'Save the record before uploading files.')}\n </div>\n ) : (\n <div\n className={cn(\n 'flex flex-col items-center justify-center rounded-lg border border-dashed p-6 text-center transition-colors',\n isDragOver ? 'border-primary bg-primary/5' : 'border-muted-foreground/30',\n )}\n onDrop={handleDrop}\n onDragOver={handleDragOver}\n onDragLeave={handleDragLeave}\n role=\"presentation\"\n >\n <Upload className=\"mx-auto h-6 w-6 text-muted-foreground\" />\n <p className=\"mt-2 text-sm text-muted-foreground\">\n {t('attachments.library.upload.dropHint', 'Drag and drop files here or click to upload.')}\n </p>\n <Button type=\"button\" variant=\"outline\" size=\"sm\" className=\"mt-4\" onClick={() => fileInputRef.current?.click()} disabled={isUploading}>\n {isUploading ? t('attachments.library.upload.submitting', 'Uploading\u2026') : t('attachments.library.upload.choose', 'Choose files')}\n </Button>\n <input\n ref={fileInputRef}\n type=\"file\"\n multiple\n className=\"hidden\"\n onChange={(event) => void acceptFiles(event.target.files)}\n />\n </div>\n )}\n\n {error ? <p className=\"text-xs font-medium text-red-600\">{error}</p> : null}\n\n {loading ? (\n <div className=\"text-sm text-muted-foreground\">{t('attachments.library.loading', 'Loading attachments\u2026')}</div>\n ) : items.length ? (\n <div className={cn(\n 'grid gap-3',\n compact ? 'grid-cols-2 sm:grid-cols-4 lg:grid-cols-5' : 'grid-cols-1 sm:grid-cols-2 lg:grid-cols-3',\n )}>\n {items.map((item) => {\n return (\n <div\n key={item.id}\n role=\"group\"\n className=\"group relative flex flex-col overflow-hidden rounded-lg border bg-card text-left transition-shadow hover:shadow-sm focus-within:ring-2 focus-within:ring-ring focus-within:ring-offset-2\"\n >\n <Button\n type=\"button\"\n variant=\"ghost\"\n aria-label={item.fileName}\n onClick={() => openMetadataDialog(item)}\n onKeyDown={(event) => {\n if (event.key === 'Enter' || event.key === ' ') {\n event.preventDefault()\n openMetadataDialog(item)\n }\n }}\n className=\"flex h-auto w-full flex-col items-stretch rounded-lg p-0 text-left hover:bg-transparent focus-visible:outline-none focus-visible:ring-0\"\n >\n <AttachmentVisualPreview\n fileName={item.fileName}\n mimeType={item.mimeType}\n thumbnailUrl={item.thumbnailUrl}\n className={compact ? 'aspect-[2/1] w-full' : 'aspect-[4/3] w-full'}\n />\n <div className={cn('space-y-1 w-full', compact ? 'p-2' : 'p-3')}>\n <div className={cn('truncate font-medium', compact ? 'text-xs' : 'text-sm')} title={item.fileName}>\n {item.fileName}\n </div>\n <div className=\"text-xs text-muted-foreground\">\n {formatAttachmentFileSize(item.fileSize)}\n </div>\n </div>\n </Button>\n <Button\n type=\"button\"\n variant=\"ghost\"\n size=\"icon\"\n className=\"absolute right-2 top-2 z-10 opacity-100 transition-opacity md:opacity-0 md:group-hover:opacity-100\"\n onClick={(event) => {\n event.stopPropagation()\n openDeleteDialog(item)\n }}\n aria-label={t('attachments.library.delete', 'Delete attachment')}\n >\n <Trash2 className=\"h-4 w-4 text-destructive\" />\n </Button>\n </div>\n )\n })}\n </div>\n ) : (\n <div className=\"text-sm text-muted-foreground\">\n {t('attachments.library.table.empty', 'No attachments found.')}\n </div>\n )}\n\n {items.length > 0 && page < totalPages ? (\n <div className=\"flex justify-center\">\n <Button\n type=\"button\"\n variant=\"outline\"\n size=\"sm\"\n onClick={() => { void load(page + 1, false) }}\n disabled={loading}\n >\n {t('attachments.library.loadMore', 'Load more')}\n </Button>\n </div>\n ) : null}\n\n <AttachmentMetadataDialog\n open={metadataOpen}\n onOpenChange={setMetadataOpen}\n item={selectedItem}\n availableTags={[]}\n onSave={handleMetadataSave}\n />\n <AttachmentDeleteDialog\n open={deleteOpen}\n onOpenChange={setDeleteOpen}\n fileName={deleteTarget?.fileName}\n onConfirm={handleDelete}\n isDeleting={false}\n />\n </div>\n )\n}\n\nexport function AttachmentsSection(props: Props) {\n const handle = ComponentReplacementHandles.section('ui.detail', 'AttachmentsSection')\n const Resolved = useRegisteredComponent<Props>(\n handle,\n AttachmentsSectionImpl as React.ComponentType<Props>,\n )\n\n return (\n <div data-component-handle={handle}>\n <Resolved {...props} />\n </div>\n )\n}\n"],
|
|
5
|
-
"mappings": "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
|
|
4
|
+
"sourcesContent": ["\"use client\"\n\nimport * as React from 'react'\nimport { Upload, Trash2, File, FileText, FileSpreadsheet, FileArchive, FileAudio, FileVideo, FileCode } from 'lucide-react'\nimport { Button } from '../../primitives/button'\nimport { apiCall } from '@open-mercato/ui/backend/utils/apiCall'\nimport { useT } from '@open-mercato/shared/lib/i18n/context'\nimport { cn } from '@open-mercato/shared/lib/utils'\nimport { AttachmentVisualPreview, formatAttachmentFileSize } from './AttachmentVisualPreview'\nimport { AttachmentDeleteDialog } from './AttachmentDeleteDialog'\nimport { AttachmentMetadataDialog, type AttachmentItem, type AttachmentMetadataSavePayload } from './AttachmentMetadataDialog'\nimport { ComponentReplacementHandles } from '@open-mercato/shared/modules/widgets/component-registry'\nimport { useRegisteredComponent } from '../injection/useRegisteredComponent'\n\ntype AttachmentsResponse = {\n items?: AttachmentItem[]\n total?: number\n page?: number\n pageSize?: number\n totalPages?: number\n error?: string\n}\n\ntype Props = {\n entityId: string\n recordId: string | null\n title?: string\n description?: string\n className?: string\n showHeader?: boolean\n compact?: boolean\n onChanged?: () => void\n}\n\nfunction AttachmentsSectionImpl({\n entityId,\n recordId,\n title,\n description,\n className,\n showHeader = true,\n compact = false,\n onChanged,\n}: Props) {\n const t = useT()\n const [items, setItems] = React.useState<AttachmentItem[]>([])\n const [page, setPage] = React.useState(1)\n const [totalPages, setTotalPages] = React.useState(1)\n const [loading, setLoading] = React.useState(false)\n const [error, setError] = React.useState<string | null>(null)\n const [isUploading, setIsUploading] = React.useState(false)\n const [isDragOver, setIsDragOver] = React.useState(false)\n const [metadataOpen, setMetadataOpen] = React.useState(false)\n const [selectedItem, setSelectedItem] = React.useState<AttachmentItem | null>(null)\n const [deleteOpen, setDeleteOpen] = React.useState(false)\n const [deleteTarget, setDeleteTarget] = React.useState<AttachmentItem | null>(null)\n const fileInputRef = React.useRef<HTMLInputElement | null>(null)\n\n const load = React.useCallback(async (targetPage = 1, replace = true) => {\n if (!recordId) return\n setLoading(true)\n setError(null)\n try {\n const params = new URLSearchParams({\n entityId,\n recordId,\n page: String(targetPage),\n pageSize: '24',\n })\n const call = await apiCall<AttachmentsResponse>(\n `/api/attachments?${params.toString()}`,\n undefined,\n { fallback: { items: [] } },\n )\n if (!call.ok) {\n const message = call.result?.error || t('attachments.library.errors.load', 'Failed to load attachments.')\n throw new Error(message)\n }\n const payload = call.result ?? { items: [] }\n const nextItems = Array.isArray(payload.items) ? payload.items : []\n setItems((current) => (replace ? nextItems : [...current, ...nextItems]))\n setPage(typeof payload.page === 'number' ? payload.page : targetPage)\n setTotalPages(typeof payload.totalPages === 'number' ? payload.totalPages : 1)\n } catch (err: any) {\n setError(err?.message || t('attachments.library.errors.load', 'Failed to load attachments.'))\n } finally {\n setLoading(false)\n }\n }, [entityId, recordId, t])\n\n React.useEffect(() => {\n if (recordId) {\n void load()\n } else {\n setItems([])\n setPage(1)\n setTotalPages(1)\n setError(null)\n }\n }, [load, recordId])\n\n const acceptFiles = React.useCallback(\n async (files: FileList | null) => {\n if (!files || !files.length || !recordId) return\n setError(null)\n setIsUploading(true)\n try {\n for (const file of Array.from(files)) {\n const fd = new FormData()\n fd.set('entityId', entityId)\n fd.set('recordId', recordId)\n fd.set('file', file)\n const call = await apiCall<{ ok?: boolean; item?: AttachmentItem; error?: string }>(\n '/api/attachments',\n { method: 'POST', body: fd },\n { fallback: null },\n )\n if (!call.ok) {\n const message = call.result?.error || t('attachments.library.upload.failed', 'Upload failed.')\n throw new Error(message)\n }\n }\n await load(1, true)\n onChanged?.()\n } catch (err: any) {\n setError(err?.message || t('attachments.library.upload.failed', 'Upload failed.'))\n } finally {\n setIsUploading(false)\n if (fileInputRef.current) {\n fileInputRef.current.value = ''\n }\n }\n },\n [entityId, load, onChanged, recordId, t],\n )\n\n const handleDrop = React.useCallback(\n (event: React.DragEvent<HTMLDivElement>) => {\n event.preventDefault()\n event.stopPropagation()\n setIsDragOver(false)\n void acceptFiles(event.dataTransfer?.files ?? null)\n },\n [acceptFiles],\n )\n\n const handleDragOver = React.useCallback((event: React.DragEvent<HTMLDivElement>) => {\n event.preventDefault()\n event.stopPropagation()\n setIsDragOver(true)\n }, [])\n\n const handleDragLeave = React.useCallback((event: React.DragEvent<HTMLDivElement>) => {\n event.preventDefault()\n event.stopPropagation()\n setIsDragOver(false)\n }, [])\n\n const openMetadataDialog = React.useCallback((item: AttachmentItem) => {\n setSelectedItem(item)\n setMetadataOpen(true)\n }, [])\n\n const openDeleteDialog = React.useCallback((item: AttachmentItem) => {\n setDeleteTarget(item)\n setDeleteOpen(true)\n }, [])\n\n const handleDelete = React.useCallback(async () => {\n if (!deleteTarget) return\n try {\n const call = await apiCall<{ error?: string }>(\n `/api/attachments?id=${encodeURIComponent(deleteTarget.id)}`,\n { method: 'DELETE' },\n )\n if (!call.ok) {\n const message = call.result?.error || t('attachments.library.errors.delete', 'Failed to delete attachment.')\n throw new Error(message)\n }\n setDeleteOpen(false)\n setDeleteTarget(null)\n await load(1, true)\n onChanged?.()\n } catch (err: any) {\n setError(err?.message || t('attachments.library.errors.delete', 'Failed to delete attachment.'))\n }\n }, [deleteTarget, load, onChanged, t])\n\n const handleMetadataSave = React.useCallback(\n async (id: string, payload: AttachmentMetadataSavePayload) => {\n const body: Record<string, unknown> = {\n tags: payload.tags,\n assignments: payload.assignments,\n }\n if (payload.customFields && Object.keys(payload.customFields).length) {\n body.customFields = payload.customFields\n }\n const call = await apiCall<{ error?: string }>(`/api/attachments/library/${encodeURIComponent(id)}`, {\n method: 'PATCH',\n headers: { 'content-type': 'application/json' },\n body: JSON.stringify(body),\n })\n if (!call.ok) {\n const message = call.result?.error || t('attachments.library.metadata.error', 'Failed to update metadata.')\n throw new Error(message)\n }\n setMetadataOpen(false)\n await load(1, true)\n onChanged?.()\n },\n [load, onChanged, t],\n )\n\n const sectionTitle = title ?? t('attachments.library.title', 'Attachments')\n const sectionDescription =\n description ?? t('attachments.library.description', 'Browse, tag, and manage every file stored in this workspace.')\n\n return (\n <div className={cn('space-y-4', className)}>\n {showHeader ? (\n <div className=\"space-y-1\">\n <div className=\"text-base font-medium\">{sectionTitle}</div>\n <div className=\"text-sm text-muted-foreground\">{sectionDescription}</div>\n </div>\n ) : null}\n\n {!recordId ? (\n <div className=\"rounded-md border border-dashed border-border/70 px-4 py-6 text-sm text-muted-foreground\">\n {t('attachments.library.upload.saveFirst', 'Save the record before uploading files.')}\n </div>\n ) : (\n <div\n className={cn(\n 'flex flex-col items-center justify-center rounded-lg border border-dashed p-6 text-center transition-colors',\n isDragOver ? 'border-primary bg-primary/5' : 'border-muted-foreground/30',\n )}\n onDrop={handleDrop}\n onDragOver={handleDragOver}\n onDragLeave={handleDragLeave}\n role=\"presentation\"\n >\n <Upload className=\"mx-auto h-6 w-6 text-muted-foreground\" />\n <p className=\"mt-2 text-sm text-muted-foreground\">\n {t('attachments.library.upload.dropHint', 'Drag and drop files here or click to upload.')}\n </p>\n <Button type=\"button\" variant=\"outline\" size=\"sm\" className=\"mt-4\" onClick={() => fileInputRef.current?.click()} disabled={isUploading}>\n {isUploading ? t('attachments.library.upload.submitting', 'Uploading\u2026') : t('attachments.library.upload.choose', 'Choose files')}\n </Button>\n <input\n ref={fileInputRef}\n type=\"file\"\n multiple\n className=\"hidden\"\n onChange={(event) => void acceptFiles(event.target.files)}\n />\n </div>\n )}\n\n {error ? <p className=\"text-xs font-medium text-status-error-text\">{error}</p> : null}\n\n {loading ? (\n <div className=\"text-sm text-muted-foreground\">{t('attachments.library.loading', 'Loading attachments\u2026')}</div>\n ) : items.length ? (\n <div className={cn(\n 'grid gap-3',\n compact ? 'grid-cols-2 sm:grid-cols-4 lg:grid-cols-5' : 'grid-cols-1 sm:grid-cols-2 lg:grid-cols-3',\n )}>\n {items.map((item) => {\n return (\n <div\n key={item.id}\n role=\"group\"\n className=\"group relative flex flex-col overflow-hidden rounded-lg border bg-card text-left transition-shadow hover:shadow-sm focus-within:ring-2 focus-within:ring-ring focus-within:ring-offset-2\"\n >\n <Button\n type=\"button\"\n variant=\"ghost\"\n aria-label={item.fileName}\n onClick={() => openMetadataDialog(item)}\n onKeyDown={(event) => {\n if (event.key === 'Enter' || event.key === ' ') {\n event.preventDefault()\n openMetadataDialog(item)\n }\n }}\n className=\"flex h-auto w-full flex-col items-stretch rounded-lg p-0 text-left hover:bg-transparent focus-visible:outline-none focus-visible:ring-0\"\n >\n <AttachmentVisualPreview\n fileName={item.fileName}\n mimeType={item.mimeType}\n thumbnailUrl={item.thumbnailUrl}\n className={compact ? 'aspect-[2/1] w-full' : 'aspect-[4/3] w-full'}\n />\n <div className={cn('space-y-1 w-full', compact ? 'p-2' : 'p-3')}>\n <div className={cn('truncate font-medium', compact ? 'text-xs' : 'text-sm')} title={item.fileName}>\n {item.fileName}\n </div>\n <div className=\"text-xs text-muted-foreground\">\n {formatAttachmentFileSize(item.fileSize)}\n </div>\n </div>\n </Button>\n <Button\n type=\"button\"\n variant=\"ghost\"\n size=\"icon\"\n className=\"absolute right-2 top-2 z-10 opacity-100 transition-opacity md:opacity-0 md:group-hover:opacity-100\"\n onClick={(event) => {\n event.stopPropagation()\n openDeleteDialog(item)\n }}\n aria-label={t('attachments.library.delete', 'Delete attachment')}\n >\n <Trash2 className=\"h-4 w-4 text-destructive\" />\n </Button>\n </div>\n )\n })}\n </div>\n ) : (\n <div className=\"text-sm text-muted-foreground\">\n {t('attachments.library.table.empty', 'No attachments found.')}\n </div>\n )}\n\n {items.length > 0 && page < totalPages ? (\n <div className=\"flex justify-center\">\n <Button\n type=\"button\"\n variant=\"outline\"\n size=\"sm\"\n onClick={() => { void load(page + 1, false) }}\n disabled={loading}\n >\n {t('attachments.library.loadMore', 'Load more')}\n </Button>\n </div>\n ) : null}\n\n <AttachmentMetadataDialog\n open={metadataOpen}\n onOpenChange={setMetadataOpen}\n item={selectedItem}\n availableTags={[]}\n onSave={handleMetadataSave}\n />\n <AttachmentDeleteDialog\n open={deleteOpen}\n onOpenChange={setDeleteOpen}\n fileName={deleteTarget?.fileName}\n onConfirm={handleDelete}\n isDeleting={false}\n />\n </div>\n )\n}\n\nexport function AttachmentsSection(props: Props) {\n const handle = ComponentReplacementHandles.section('ui.detail', 'AttachmentsSection')\n const Resolved = useRegisteredComponent<Props>(\n handle,\n AttachmentsSectionImpl as React.ComponentType<Props>,\n )\n\n return (\n <div data-component-handle={handle}>\n <Resolved {...props} />\n </div>\n )\n}\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
6
|
"names": []
|
|
7
7
|
}
|
|
@@ -1000,7 +1000,7 @@ function NotesSectionImpl({
|
|
|
1000
1000
|
remarkPlugins: markdownPlugins
|
|
1001
1001
|
}
|
|
1002
1002
|
),
|
|
1003
|
-
contentError ? /* @__PURE__ */ jsx("p", { className: "text-xs text-
|
|
1003
|
+
contentError ? /* @__PURE__ */ jsx("p", { className: "text-xs text-status-error-text", children: contentError }) : null,
|
|
1004
1004
|
/* @__PURE__ */ jsxs("div", { className: "flex flex-wrap items-center gap-2", children: [
|
|
1005
1005
|
/* @__PURE__ */ jsx(Button, { type: "button", size: "sm", onClick: handleContentSave, disabled: contentSavingId === note.id, children: contentSavingId === note.id ? /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
1006
1006
|
/* @__PURE__ */ jsx(Loader2, { className: "mr-2 h-4 w-4 animate-spin" }),
|