@arronqzy/react-view 1.0.38 → 1.0.40

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 React from "react";
1
+ import React, { useState } from "react";
2
2
  import {
3
3
  Checkbox,
4
4
  Input,
@@ -16,9 +16,12 @@ import {
16
16
  IconChevron,
17
17
  IconClose,
18
18
  IconEdit,
19
+ IconEyeOff,
20
+ IconLayers,
19
21
  IconLock,
20
22
  IconMerge,
21
23
  IconPlus,
24
+ IconStar,
22
25
  IconTrash,
23
26
  } from "../icons";
24
27
  import type { PanelLayer } from "../types";
@@ -54,6 +57,9 @@ export type PanelLayerDockProps = {
54
57
  mergeSelectedLayers: (name?: string) => void;
55
58
  };
56
59
 
60
+ const iconBtn =
61
+ "inline-flex h-6 w-6 shrink-0 items-center justify-center rounded-md border border-border/60 bg-background/80 text-muted-foreground transition-colors hover:bg-accent hover:text-accent-foreground disabled:cursor-not-allowed disabled:opacity-40";
62
+
57
63
  export function PanelLayerDock({
58
64
  t,
59
65
  isLayerPanelExpanded,
@@ -84,329 +90,404 @@ export function PanelLayerDock({
84
90
  setMergeLayerName,
85
91
  mergeSelectedLayers,
86
92
  }: PanelLayerDockProps) {
93
+ const [dockVisible, setDockVisible] = useState(true);
94
+
95
+ if (!dockVisible) {
96
+ return (
97
+ <div className="flex justify-center border-t border-border/50 bg-muted/5 px-2 py-0.5">
98
+ <TooltipProvider delayDuration={120}>
99
+ <Tooltip>
100
+ <TooltipTrigger asChild>
101
+ <button
102
+ type="button"
103
+ onClick={() => setDockVisible(true)}
104
+ aria-label={t("panel.layers.title")}
105
+ className="inline-flex h-6 items-center gap-1 rounded-md border border-border/60 bg-background/90 px-2 text-muted-foreground hover:bg-accent hover:text-accent-foreground"
106
+ >
107
+ <IconLayers />
108
+ <span data-typo="caption" className="hidden text-[11px] font-medium sm:inline">
109
+ {t("panel.layers.title")}
110
+ </span>
111
+ </button>
112
+ </TooltipTrigger>
113
+ <TooltipContent className="z-[10000]">{t("panel.layers.title")}</TooltipContent>
114
+ </Tooltip>
115
+ </TooltipProvider>
116
+ </div>
117
+ );
118
+ }
119
+
87
120
  return (
88
- <div className="border-t border-border bg-background/95 px-2 py-1.5">
89
- <TooltipProvider delayDuration={120}>
90
- <Tabs value={activeLayerId} onValueChange={setActiveLayer}>
91
- <div className="mb-1 flex items-center gap-2">
92
- <div className="text-[11px] font-semibold text-muted-foreground">{t("panel.layers.title")}</div>
93
- <Tooltip>
94
- <TooltipTrigger asChild>
95
- <button
96
- type="button"
97
- onClick={() => setIsLayerPanelExpanded((v) => !v)}
98
- aria-label={isLayerPanelExpanded ? t("panel.layers.collapseInputAria") : t("panel.layers.expandInputAria")}
99
- className="rounded border border-border p-1 hover:bg-accent"
100
- >
101
- <IconChevron expanded={isLayerPanelExpanded} />
102
- </button>
103
- </TooltipTrigger>
104
- <TooltipContent className="z-[10000]">
105
- {isLayerPanelExpanded ? t("panel.layers.collapseInput") : t("panel.layers.expandInput")}
106
- </TooltipContent>
107
- </Tooltip>
108
- <Tooltip>
109
- <TooltipTrigger asChild>
110
- <button
111
- type="button"
112
- onClick={addLayer}
113
- aria-label={t("panel.layers.add")}
114
- className="rounded border border-border p-1 hover:bg-accent"
115
- >
116
- <IconPlus />
117
- </button>
118
- </TooltipTrigger>
119
- <TooltipContent className="z-[10000]">{t("panel.layers.add")}</TooltipContent>
120
- </Tooltip>
121
- <Tooltip>
122
- <TooltipTrigger asChild>
123
- <button
124
- type="button"
125
- onClick={() => {
126
- if (!canMergeLayers) return;
127
- setIsMergingLayers(true);
128
- }}
129
- aria-label={t("panel.layers.merge")}
130
- disabled={!canMergeLayers}
131
- className="rounded border border-border p-1 hover:bg-accent disabled:cursor-not-allowed disabled:opacity-40"
132
- >
133
- <IconMerge />
134
- </button>
135
- </TooltipTrigger>
136
- <TooltipContent className="z-[10000]">
137
- {canMergeLayers ? t("panel.layers.merge") : t("panel.layers.mergeNeedTwo")}
138
- </TooltipContent>
139
- </Tooltip>
140
- </div>
121
+ <div className="border-t border-border/50 bg-muted/5 px-2.5 py-1.5">
122
+ <TooltipProvider delayDuration={120}>
123
+ <Tabs value={activeLayerId} onValueChange={setActiveLayer}>
124
+ <div className="flex min-w-0 items-center gap-1">
125
+ <Tooltip>
126
+ <TooltipTrigger asChild>
127
+ <span className="inline-flex h-6 w-6 shrink-0 items-center justify-center text-muted-foreground">
128
+ <IconLayers />
129
+ </span>
130
+ </TooltipTrigger>
131
+ <TooltipContent className="z-[10000]">{t("panel.layers.title")}</TooltipContent>
132
+ </Tooltip>
133
+
134
+ <Tooltip>
135
+ <TooltipTrigger asChild>
136
+ <button
137
+ type="button"
138
+ onClick={() => setIsLayerPanelExpanded((v) => !v)}
139
+ aria-label={
140
+ isLayerPanelExpanded
141
+ ? t("panel.layers.collapseInputAria")
142
+ : t("panel.layers.expandInputAria")
143
+ }
144
+ className={iconBtn}
145
+ >
146
+ <IconChevron expanded={isLayerPanelExpanded} />
147
+ </button>
148
+ </TooltipTrigger>
149
+ <TooltipContent className="z-[10000]">
150
+ {isLayerPanelExpanded
151
+ ? t("panel.layers.collapseInput")
152
+ : t("panel.layers.expandInput")}
153
+ </TooltipContent>
154
+ </Tooltip>
155
+
156
+ <Tooltip>
157
+ <TooltipTrigger asChild>
158
+ <button
159
+ type="button"
160
+ onClick={addLayer}
161
+ aria-label={t("panel.layers.add")}
162
+ className={iconBtn}
163
+ >
164
+ <IconPlus />
165
+ </button>
166
+ </TooltipTrigger>
167
+ <TooltipContent className="z-[10000]">{t("panel.layers.add")}</TooltipContent>
168
+ </Tooltip>
169
+
170
+ <Tooltip>
171
+ <TooltipTrigger asChild>
172
+ <button
173
+ type="button"
174
+ onClick={() => {
175
+ if (!canMergeLayers) return;
176
+ setIsMergingLayers(true);
177
+ setIsLayerPanelExpanded(true);
178
+ }}
179
+ aria-label={t("panel.layers.merge")}
180
+ disabled={!canMergeLayers}
181
+ className={iconBtn}
182
+ >
183
+ <IconMerge />
184
+ </button>
185
+ </TooltipTrigger>
186
+ <TooltipContent className="z-[10000]">
187
+ {canMergeLayers ? t("panel.layers.merge") : t("panel.layers.mergeNeedTwo")}
188
+ </TooltipContent>
189
+ </Tooltip>
190
+
191
+ <TabsList
192
+ className={`ml-0.5 h-6 min-w-0 flex-1 justify-start gap-0.5 overflow-x-auto rounded-md bg-muted/30 p-0.5 ${themedScrollbarClass}`}
193
+ >
194
+ {layers.map((layer) => (
195
+ <TabsTrigger
196
+ key={layer.id}
197
+ value={layer.id}
198
+ className="flex h-5 max-w-[120px] min-w-[64px] items-center gap-1 rounded px-1.5 py-0 text-[11px] font-medium data-[state=active]:font-semibold"
199
+ >
200
+ <span className="truncate">{layer.name}</span>
201
+ {layer.isPrimary ? (
202
+ <span className="text-emerald-500" title={t("panel.layers.primary")}>
203
+ <IconStar filled />
204
+ </span>
205
+ ) : null}
206
+ {layer.locked ? <IconLock locked /> : null}
207
+ </TabsTrigger>
208
+ ))}
209
+ </TabsList>
210
+
211
+ {activeLayer ? (
212
+ <div className="flex shrink-0 items-center gap-0.5 border-l border-border/50 pl-1">
213
+ {editingLayerId === activeLayer.id ? (
214
+ <Input
215
+ value={editingLayerName}
216
+ onChange={(e) => setEditingLayerName(e.target.value)}
217
+ onKeyDown={(e) => {
218
+ if (e.key === "Enter") {
219
+ e.preventDefault();
220
+ renameLayer(editingLayerId, editingLayerName);
221
+ setEditingLayerId(null);
222
+ setEditingLayerName("");
223
+ return;
224
+ }
225
+ if (e.key === "Escape") {
226
+ e.preventDefault();
227
+ setEditingLayerId(null);
228
+ setEditingLayerName("");
229
+ }
230
+ }}
231
+ className="h-6 w-24 min-w-0 text-[11px]"
232
+ placeholder={t("panel.layers.namePlaceholder")}
233
+ autoFocus
234
+ />
235
+ ) : null}
141
236
 
142
- <TabsList
143
- className={`h-auto w-full justify-start gap-1 overflow-x-auto bg-muted/40 p-1 ${themedScrollbarClass}`}
237
+ <Tooltip>
238
+ <TooltipTrigger asChild>
239
+ <button
240
+ type="button"
241
+ onClick={() => setPrimaryLayer(activeLayer.id)}
242
+ disabled={activeLayer.isPrimary}
243
+ aria-label={
244
+ activeLayer.isPrimary
245
+ ? t("panel.layers.isPrimary")
246
+ : t("panel.layers.setPrimary")
247
+ }
248
+ className={[
249
+ iconBtn,
250
+ activeLayer.isPrimary ? "text-emerald-500" : "",
251
+ ].join(" ")}
144
252
  >
145
- {layers.map((layer) => (
146
- <TabsTrigger
147
- key={layer.id}
148
- value={layer.id}
149
- className="flex min-w-[120px] items-center gap-1 px-2 py-1 text-xs"
150
- >
151
- <span className="truncate">{layer.name}</span>
152
- {layer.isMapping ? (
153
- <span className="rounded border border-primary/40 bg-primary/10 px-1 text-[10px] text-primary">
154
- {t("panel.layers.mapping")}
155
- </span>
156
- ) : null}
157
- {layer.isPrimary ? (
158
- <span className="rounded border border-emerald-500/40 bg-emerald-500/10 px-1 text-[10px] text-emerald-600">
159
- {t("panel.layers.primary")}
160
- </span>
161
- ) : null}
162
- {layer.locked ? <IconLock locked /> : null}
163
- </TabsTrigger>
164
- ))}
165
- </TabsList>
253
+ <IconStar filled={activeLayer.isPrimary} />
254
+ </button>
255
+ </TooltipTrigger>
256
+ <TooltipContent className="z-[10000]">
257
+ {activeLayer.isPrimary
258
+ ? t("panel.layers.isPrimaryHint")
259
+ : t("panel.layers.setPrimaryHint")}
260
+ </TooltipContent>
261
+ </Tooltip>
166
262
 
167
- <TabsContent value={activeLayerId} className="mt-2 space-y-2">
168
- {activeLayer ? (
169
- <div className="flex items-center gap-2 rounded border border-border bg-card px-2 py-1.5 text-xs">
170
- {editingLayerId === activeLayer.id ? (
171
- <Input
172
- value={editingLayerName}
173
- onChange={(e) => setEditingLayerName(e.target.value)}
174
- onKeyDown={(e) => {
175
- if (e.key === "Enter") {
176
- e.preventDefault();
177
- renameLayer(editingLayerId, editingLayerName);
178
- setEditingLayerId(null);
179
- setEditingLayerName("");
180
- return;
181
- }
182
- if (e.key === "Escape") {
183
- e.preventDefault();
184
- setEditingLayerId(null);
185
- setEditingLayerName("");
186
- }
187
- }}
188
- className="h-7 min-w-0 flex-1"
189
- placeholder={t("panel.layers.namePlaceholder")}
190
- autoFocus
191
- />
192
- ) : (
193
- <span className="truncate font-medium">{activeLayer.name}</span>
194
- )}
195
- {activeLayer.isMapping ? (
196
- <span className="rounded border border-primary/40 bg-primary/10 px-1 text-[10px] text-primary">
197
- {t("panel.layers.mappingLayer")}
198
- </span>
199
- ) : null}
200
- {activeLayer.isPrimary ? (
201
- <span className="rounded border border-emerald-500/40 bg-emerald-500/10 px-1 text-[10px] text-emerald-600">
202
- {t("panel.layers.primaryLayer")}
203
- </span>
204
- ) : null}
205
- <div className="flex-1" />
206
- <Tooltip>
207
- <TooltipTrigger asChild>
208
- <button
209
- type="button"
210
- onClick={() => setPrimaryLayer(activeLayer.id)}
211
- disabled={activeLayer.isPrimary}
212
- aria-label={activeLayer.isPrimary ? t("panel.layers.isPrimary") : t("panel.layers.setPrimary")}
213
- className={[
214
- "rounded border px-2 py-1 text-[11px] disabled:cursor-not-allowed disabled:opacity-60",
215
- activeLayer.isPrimary
216
- ? "border-emerald-500/40 bg-emerald-500/10 text-emerald-600"
217
- : "border-border hover:bg-accent",
218
- ].join(" ")}
219
- >
220
- {activeLayer.isPrimary ? t("panel.layers.isPrimary") : t("panel.layers.setPrimary")}
221
- </button>
222
- </TooltipTrigger>
223
- <TooltipContent className="z-[10000]">
224
- {activeLayer.isPrimary ? t("panel.layers.isPrimaryHint") : t("panel.layers.setPrimaryHint")}
225
- </TooltipContent>
226
- </Tooltip>
227
- <Tooltip>
228
- <TooltipTrigger asChild>
229
- <button
230
- type="button"
231
- onClick={() => toggleLayerLock(activeLayer.id)}
232
- disabled={!activeLayer.editable}
233
- aria-label={activeLayer.locked ? t("panel.layers.unlock") : t("panel.layers.lock")}
234
- className="rounded border border-border p-1 disabled:opacity-40"
235
- >
236
- <IconLock locked={activeLayer.locked} />
237
- </button>
238
- </TooltipTrigger>
239
- <TooltipContent className="z-[10000]">
240
- {activeLayer.locked ? t("panel.layers.unlock") : t("panel.layers.lock")}
241
- </TooltipContent>
242
- </Tooltip>
243
- <Tooltip>
244
- <TooltipTrigger asChild>
245
- {editingLayerId === activeLayer.id ? (
246
- <button
247
- type="button"
248
- onClick={() => {
249
- renameLayer(editingLayerId, editingLayerName);
250
- setEditingLayerId(null);
251
- setEditingLayerName("");
252
- }}
253
- aria-label={t("panel.layers.saveName")}
254
- className="rounded border border-border p-1 hover:bg-accent"
255
- >
256
- <IconCheck />
257
- </button>
258
- ) : (
259
- <button
260
- type="button"
261
- onClick={() => {
262
- setEditingLayerId(activeLayer.id);
263
- setEditingLayerName(activeLayer.name);
264
- }}
265
- disabled={!activeLayer.editable}
266
- aria-label={t("panel.layers.rename")}
267
- className="rounded border border-border p-1 disabled:opacity-40"
268
- >
269
- <IconEdit />
270
- </button>
271
- )}
272
- </TooltipTrigger>
273
- <TooltipContent className="z-[10000]">
274
- {editingLayerId === activeLayer.id ? t("panel.layers.saveName") : t("panel.layers.rename")}
275
- </TooltipContent>
276
- </Tooltip>
277
- {editingLayerId === activeLayer.id ? (
278
- <Tooltip>
279
- <TooltipTrigger asChild>
280
- <button
281
- type="button"
282
- onClick={() => {
283
- setEditingLayerId(null);
284
- setEditingLayerName("");
285
- }}
286
- aria-label={t("panel.layers.cancelEditName")}
287
- className="rounded border border-border p-1 hover:bg-accent"
288
- >
289
- <IconClose />
290
- </button>
291
- </TooltipTrigger>
292
- <TooltipContent className="z-[10000]">{t("panel.layers.cancelEdit")}</TooltipContent>
293
- </Tooltip>
294
- ) : null}
295
- <Tooltip>
296
- <TooltipTrigger asChild>
297
- <button
298
- type="button"
299
- onClick={() => {
300
- const blockReason = getLayerDeleteBlockReason(activeLayer.id);
301
- if (blockReason) {
302
- showActionHint(blockReason);
303
- return;
304
- }
305
- setConfirmDeleteLayerId(activeLayer.id);
306
- const firstTarget = layers.find((l) => l.id !== activeLayer.id);
307
- setDeleteTargetLayerId(firstTarget?.id ?? "");
308
- setDeleteMode(activeLayer.isMapping ? "remove" : "move");
309
- }}
310
- disabled={!activeLayer.editable || activeLayer.locked}
311
- aria-label={
312
- !activeLayer.editable
313
- ? t("panel.layers.cannotDeleteDefault")
314
- : activeLayer.locked
315
- ? t("panel.layers.cannotDeleteLocked")
316
- : t("panel.layers.delete")
317
- }
318
- className="rounded border border-border p-1 disabled:opacity-40"
319
- >
320
- <IconTrash />
321
- </button>
322
- </TooltipTrigger>
323
- <TooltipContent className="z-[10000]">
324
- {!activeLayer.editable
325
- ? t("panel.layers.cannotDeleteDefault")
326
- : activeLayer.locked
327
- ? t("panel.layers.cannotDeleteLocked")
328
- : t("panel.layers.delete")}
329
- </TooltipContent>
330
- </Tooltip>
331
- </div>
332
- ) : null}
263
+ <Tooltip>
264
+ <TooltipTrigger asChild>
265
+ <button
266
+ type="button"
267
+ onClick={() => toggleLayerLock(activeLayer.id)}
268
+ disabled={!activeLayer.editable}
269
+ aria-label={
270
+ activeLayer.locked ? t("panel.layers.unlock") : t("panel.layers.lock")
271
+ }
272
+ className={iconBtn}
273
+ >
274
+ <IconLock locked={activeLayer.locked} />
275
+ </button>
276
+ </TooltipTrigger>
277
+ <TooltipContent className="z-[10000]">
278
+ {activeLayer.locked ? t("panel.layers.unlock") : t("panel.layers.lock")}
279
+ </TooltipContent>
280
+ </Tooltip>
333
281
 
334
- {isLayerPanelExpanded ? (
335
- <div className="rounded border border-border bg-card px-2 py-1.5 text-xs">
336
- <div className="mb-1 text-muted-foreground">{t("panel.layers.selectMergeLayers")}</div>
337
- <div className="flex flex-wrap gap-2">
338
- {layers.map((layer) => (
339
- <label key={layer.id} className="flex items-center gap-1.5">
340
- <Checkbox
341
- checked={Boolean(layer.mergeSelected)}
342
- disabled={layer.isMapping}
343
- className="h-4 w-4 border-2 border-foreground/80 bg-background ring-1 ring-foreground/40 data-[state=checked]:border-primary data-[state=checked]:ring-primary/40"
344
- onCheckedChange={() => toggleLayerMergeSelected(layer.id)}
345
- />
346
- <span
347
- className="max-w-[140px] truncate"
348
- title={layer.isMapping ? t("panel.layers.mappingCannotMerge") : layer.name}
349
- >
350
- {layer.name}
351
- {layer.isMapping ? t("panel.layers.cannotMergeSuffix") : ""}
352
- </span>
353
- </label>
354
- ))}
355
- </div>
356
- </div>
357
- ) : null}
358
- </TabsContent>
359
- </Tabs>
282
+ <Tooltip>
283
+ <TooltipTrigger asChild>
284
+ {editingLayerId === activeLayer.id ? (
285
+ <button
286
+ type="button"
287
+ onClick={() => {
288
+ renameLayer(editingLayerId, editingLayerName);
289
+ setEditingLayerId(null);
290
+ setEditingLayerName("");
291
+ }}
292
+ aria-label={t("panel.layers.saveName")}
293
+ className={iconBtn}
294
+ >
295
+ <IconCheck />
296
+ </button>
297
+ ) : (
298
+ <button
299
+ type="button"
300
+ onClick={() => {
301
+ setEditingLayerId(activeLayer.id);
302
+ setEditingLayerName(activeLayer.name);
303
+ }}
304
+ disabled={!activeLayer.editable}
305
+ aria-label={t("panel.layers.rename")}
306
+ className={iconBtn}
307
+ >
308
+ <IconEdit />
309
+ </button>
310
+ )}
311
+ </TooltipTrigger>
312
+ <TooltipContent className="z-[10000]">
313
+ {editingLayerId === activeLayer.id
314
+ ? t("panel.layers.saveName")
315
+ : t("panel.layers.rename")}
316
+ </TooltipContent>
317
+ </Tooltip>
360
318
 
361
- {isLayerPanelExpanded && isMergingLayers ? (
362
- <div className="mt-2 flex items-center gap-2 rounded border border-border bg-card px-2 py-1.5 text-xs">
363
- <span className="text-muted-foreground">{t("panel.layers.mergedName")}</span>
364
- <Input
365
- value={mergeLayerName}
366
- onChange={(e) => setMergeLayerName(e.target.value)}
367
- className="h-7 min-w-0 flex-1"
368
- placeholder={t("panel.layers.mergedNamePlaceholder")}
369
- autoFocus
370
- />
371
- <Tooltip>
372
- <TooltipTrigger asChild>
373
- <button
374
- type="button"
375
- onClick={() => {
376
- if (!canMergeLayers) return;
377
- mergeSelectedLayers(mergeLayerName || undefined);
378
- setIsMergingLayers(false);
379
- setMergeLayerName("");
380
- }}
381
- aria-label={t("panel.layers.confirmMergeAria")}
382
- disabled={!canMergeLayers}
383
- className="rounded border border-border p-1 hover:bg-accent"
384
- >
385
- <IconCheck />
386
- </button>
387
- </TooltipTrigger>
388
- <TooltipContent className="z-[10000]">{t("panel.layers.confirmMerge")}</TooltipContent>
389
- </Tooltip>
390
- <Tooltip>
391
- <TooltipTrigger asChild>
392
- <button
393
- type="button"
394
- onClick={() => {
395
- setIsMergingLayers(false);
396
- setMergeLayerName("");
397
- }}
398
- aria-label={t("panel.layers.cancelMergeAria")}
399
- className="rounded border border-border p-1 hover:bg-accent"
400
- >
401
- <IconClose />
402
- </button>
403
- </TooltipTrigger>
404
- <TooltipContent className="z-[10000]">{t("panel.layers.cancelMerge")}</TooltipContent>
405
- </Tooltip>
406
- </div>
319
+ {editingLayerId === activeLayer.id ? (
320
+ <Tooltip>
321
+ <TooltipTrigger asChild>
322
+ <button
323
+ type="button"
324
+ onClick={() => {
325
+ setEditingLayerId(null);
326
+ setEditingLayerName("");
327
+ }}
328
+ aria-label={t("panel.layers.cancelEditName")}
329
+ className={iconBtn}
330
+ >
331
+ <IconClose />
332
+ </button>
333
+ </TooltipTrigger>
334
+ <TooltipContent className="z-[10000]">
335
+ {t("panel.layers.cancelEdit")}
336
+ </TooltipContent>
337
+ </Tooltip>
407
338
  ) : null}
408
- </TooltipProvider>
339
+
340
+ <Tooltip>
341
+ <TooltipTrigger asChild>
342
+ <button
343
+ type="button"
344
+ onClick={() => {
345
+ const blockReason = getLayerDeleteBlockReason(activeLayer.id);
346
+ if (blockReason) {
347
+ showActionHint(blockReason);
348
+ return;
349
+ }
350
+ setConfirmDeleteLayerId(activeLayer.id);
351
+ const firstTarget = layers.find((l) => l.id !== activeLayer.id);
352
+ setDeleteTargetLayerId(firstTarget?.id ?? "");
353
+ setDeleteMode(activeLayer.isMapping ? "remove" : "move");
354
+ }}
355
+ disabled={!activeLayer.editable || activeLayer.locked}
356
+ aria-label={
357
+ !activeLayer.editable
358
+ ? t("panel.layers.cannotDeleteDefault")
359
+ : activeLayer.locked
360
+ ? t("panel.layers.cannotDeleteLocked")
361
+ : t("panel.layers.delete")
362
+ }
363
+ className={iconBtn}
364
+ >
365
+ <IconTrash />
366
+ </button>
367
+ </TooltipTrigger>
368
+ <TooltipContent className="z-[10000]">
369
+ {!activeLayer.editable
370
+ ? t("panel.layers.cannotDeleteDefault")
371
+ : activeLayer.locked
372
+ ? t("panel.layers.cannotDeleteLocked")
373
+ : t("panel.layers.delete")}
374
+ </TooltipContent>
375
+ </Tooltip>
376
+ </div>
377
+ ) : null}
378
+
379
+ <Tooltip>
380
+ <TooltipTrigger asChild>
381
+ <button
382
+ type="button"
383
+ onClick={() => setDockVisible(false)}
384
+ aria-label={t("panel.layers.collapseInputAria")}
385
+ className={iconBtn}
386
+ >
387
+ <IconEyeOff />
388
+ </button>
389
+ </TooltipTrigger>
390
+ <TooltipContent className="z-[10000]">
391
+ {t("panel.layers.collapseInput")}
392
+ </TooltipContent>
393
+ </Tooltip>
394
+ </div>
395
+
396
+ {isLayerPanelExpanded ? (
397
+ <TabsContent value={activeLayerId} className="mt-1 space-y-1">
398
+ <div className="rounded-md border border-border/50 bg-card/80 px-2 py-1.5">
399
+ <div
400
+ data-typo="caption"
401
+ className="mb-1 text-[11px] font-medium text-muted-foreground"
402
+ >
403
+ {t("panel.layers.selectMergeLayers")}
404
+ </div>
405
+ <div className="flex flex-wrap gap-x-3 gap-y-1">
406
+ {layers.map((layer) => (
407
+ <label
408
+ key={layer.id}
409
+ className="flex min-w-0 items-center gap-1.5 text-[11px]"
410
+ >
411
+ <Checkbox
412
+ checked={Boolean(layer.mergeSelected)}
413
+ disabled={layer.isMapping}
414
+ className="h-3.5 w-3.5 border border-foreground/70 bg-background data-[state=checked]:border-primary data-[state=checked]:bg-primary"
415
+ onCheckedChange={() => toggleLayerMergeSelected(layer.id)}
416
+ />
417
+ <span
418
+ className="max-w-[120px] truncate font-medium text-foreground/90"
419
+ title={
420
+ layer.isMapping ? t("panel.layers.mappingCannotMerge") : layer.name
421
+ }
422
+ >
423
+ {layer.name}
424
+ {layer.isMapping ? t("panel.layers.cannotMergeSuffix") : ""}
425
+ </span>
426
+ </label>
427
+ ))}
428
+ </div>
409
429
  </div>
410
430
 
431
+ {isMergingLayers ? (
432
+ <div className="flex items-center gap-1.5 rounded-md border border-border/50 bg-card/80 px-2 py-1">
433
+ <span
434
+ data-typo="caption"
435
+ className="shrink-0 text-[11px] text-muted-foreground"
436
+ >
437
+ {t("panel.layers.mergedName")}
438
+ </span>
439
+ <Input
440
+ value={mergeLayerName}
441
+ onChange={(e) => setMergeLayerName(e.target.value)}
442
+ className="h-6 min-w-0 flex-1 text-[11px]"
443
+ placeholder={t("panel.layers.mergedNamePlaceholder")}
444
+ autoFocus
445
+ />
446
+ <Tooltip>
447
+ <TooltipTrigger asChild>
448
+ <button
449
+ type="button"
450
+ onClick={() => {
451
+ if (!canMergeLayers) return;
452
+ mergeSelectedLayers(mergeLayerName || undefined);
453
+ setIsMergingLayers(false);
454
+ setMergeLayerName("");
455
+ }}
456
+ aria-label={t("panel.layers.confirmMergeAria")}
457
+ disabled={!canMergeLayers}
458
+ className={iconBtn}
459
+ >
460
+ <IconCheck />
461
+ </button>
462
+ </TooltipTrigger>
463
+ <TooltipContent className="z-[10000]">
464
+ {t("panel.layers.confirmMerge")}
465
+ </TooltipContent>
466
+ </Tooltip>
467
+ <Tooltip>
468
+ <TooltipTrigger asChild>
469
+ <button
470
+ type="button"
471
+ onClick={() => {
472
+ setIsMergingLayers(false);
473
+ setMergeLayerName("");
474
+ }}
475
+ aria-label={t("panel.layers.cancelMergeAria")}
476
+ className={iconBtn}
477
+ >
478
+ <IconClose />
479
+ </button>
480
+ </TooltipTrigger>
481
+ <TooltipContent className="z-[10000]">
482
+ {t("panel.layers.cancelMerge")}
483
+ </TooltipContent>
484
+ </Tooltip>
485
+ </div>
486
+ ) : null}
487
+ </TabsContent>
488
+ ) : null}
489
+ </Tabs>
490
+ </TooltipProvider>
491
+ </div>
411
492
  );
412
493
  }