@rural-environmental-registry/map_component 1.2.0 → 2.0.1

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.
@@ -3,10 +3,16 @@
3
3
  :index="data.key"
4
4
  class="child-menu"
5
5
  >
6
- <template #title>
6
+ <div class="child-menu-content">
7
7
  <div class="child-menu-row">
8
8
  <div class="child-layer-title">
9
9
  <span
10
+ v-if="legendIconName"
11
+ class="child-layer-legend child-layer-legend--icon"
12
+ :style="{ color: legendColor }"
13
+ ><FontAwesomeIcon :iconName="legendIconName" /></span>
14
+ <span
15
+ v-else-if="data.style"
10
16
  :style="{
11
17
  borderColor: data.style.fillColor,
12
18
  backgroundColor: data.style.color
@@ -14,64 +20,238 @@
14
20
  class="child-layer-legend"
15
21
  />
16
22
 
17
- <span>{{ data.name }}</span>
18
- </div>
19
- <span class="switch-component">
20
- <ElSwitch
21
- v-model="active"
22
- @click.stop
23
+ <ElTooltip
24
+ :content="data.tooltip"
25
+ :disabled="!data.tooltip"
26
+ placement="top"
27
+ popper-class="child-layer-name-tooltip"
23
28
  >
24
- <template #active-action>
25
- <FontAwesomeIcon iconName="check" />
26
- </template>
27
- <template #inactive-action>
28
- <FontAwesomeIcon iconName="xmark" />
29
- </template>
30
- </ElSwitch>
31
- <span class="child-layer-status">
32
- {{ active ? data.toggle.active : data.toggle.inactive }}
29
+ <span
30
+ class="child-layer-name"
31
+ :aria-label="data.tooltip || data.name"
32
+ >{{ data.name }}</span>
33
+ </ElTooltip>
34
+
35
+ <span
36
+ v-if="data.required"
37
+ class="child-layer-required"
38
+ aria-hidden="true"
39
+ >*</span>
40
+
41
+ <span
42
+ v-if="activeInfoIcons.length"
43
+ class="child-info-icons"
44
+ >
45
+ <ElTooltip
46
+ v-for="(infoIcon, index) in activeInfoIcons"
47
+ :key="infoIcon.key ?? `${infoIcon.icon}-${index}`"
48
+ :content="infoIcon.tooltip"
49
+ :disabled="!infoIcon.tooltip"
50
+ placement="top"
51
+ >
52
+ <span
53
+ class="child-info-icon"
54
+ :style="infoIconStyle(infoIcon)"
55
+ :aria-label="infoIcon.tooltip"
56
+ :aria-hidden="infoIcon.tooltip ? undefined : true"
57
+ >
58
+ <span
59
+ v-if="infoIcon.style?.backgroundColor"
60
+ class="child-info-icon-inner"
61
+ :style="{ backgroundColor: infoIcon.style.backgroundColor }"
62
+ aria-hidden="true"
63
+ />
64
+ <FontAwesomeIcon :iconName="infoIcon.icon" />
65
+ </span>
66
+ </ElTooltip>
33
67
  </span>
68
+ </div>
69
+ <span
70
+ v-if="showVisibilityControl"
71
+ class="visibility-control"
72
+ >
73
+ <button
74
+ v-if="visibilityMode === 'eye' && showEyeControl"
75
+ type="button"
76
+ class="visibility-eye-button"
77
+ :aria-label="active ? hideLabel : showLabel"
78
+ :title="active ? hideLabel : showLabel"
79
+ @click.stop="active = !active"
80
+ >
81
+ <FontAwesomeIcon :iconName="active ? 'eye' : 'eye-slash'" />
82
+ </button>
83
+ <div
84
+ v-if="!hideControls && visibleActions.length"
85
+ class="child-menu-actions"
86
+ >
87
+ <ElTooltip
88
+ v-for="action in visibleActions"
89
+ :key="action.key"
90
+ :content="action.tooltip || action.name"
91
+ :disabled="!(action.tooltip || action.name)"
92
+ placement="top"
93
+ >
94
+ <button
95
+ type="button"
96
+ class="child-action-button"
97
+ :disabled="action.disabled"
98
+ :aria-label="action.tooltip || action.name"
99
+ @click.stop="onActionClick(action)"
100
+ >
101
+ <FontAwesomeIcon
102
+ v-if="action.icon"
103
+ :iconName="action.icon"
104
+ />
105
+ </button>
106
+ </ElTooltip>
107
+ </div>
108
+ <template v-else-if="!hideControls && visibilityMode !== 'eye'">
109
+ <ElSwitch
110
+ v-model="active"
111
+ @click.stop
112
+ >
113
+ <template #active-action>
114
+ <FontAwesomeIcon iconName="check" />
115
+ </template>
116
+ <template #inactive-action>
117
+ <FontAwesomeIcon iconName="xmark" />
118
+ </template>
119
+ </ElSwitch>
120
+ <span
121
+ v-if="data.toggle"
122
+ class="child-layer-status"
123
+ >
124
+ {{ active ? data.toggle.active : data.toggle.inactive }}
125
+ </span>
126
+ </template>
34
127
  </span>
35
128
  </div>
36
- </template>
129
+ </div>
37
130
  </ElMenuItem>
38
131
  <ElDivider class="child-menu-divider-row" />
39
132
  </template>
40
133
 
41
134
  <script lang="ts" setup>
42
- import { ElDivider, ElMenuItem, ElSwitch } from 'element-plus'
135
+ import { ElDivider, ElMenuItem, ElSwitch, ElTooltip } from 'element-plus'
43
136
  import { computed } from 'vue'
44
137
  import FontAwesomeIcon from '../fa-icon/FontAwesomeIcon.vue'
45
- import { LayerData } from '../../types'
138
+ import {
139
+ LayerActionConfig,
140
+ LayerActionDefaults,
141
+ LayerActionPayload,
142
+ LayerData,
143
+ LayerInfoIconConfig
144
+ } from '../../types'
145
+ import {
146
+ resolveActiveLayerInfoIcons,
147
+ resolveLayerActions
148
+ } from '../../utils/layersConfigNormalizer'
46
149
  import { setHistory } from '../../utils/menuHistory.ts'
47
150
 
48
151
  type ChildMenuProps = {
49
152
  data: LayerData
50
153
  persist: boolean
154
+ sectionKey?: string
155
+ groupKey?: string
156
+ actionDefaults?: LayerActionDefaults
157
+ visibilityMode?: 'switch' | 'eye'
158
+ hideControls?: boolean
159
+ }
160
+
161
+ const props = withDefaults(defineProps<ChildMenuProps>(), {
162
+ visibilityMode: 'switch',
163
+ hideControls: false
164
+ })
165
+
166
+ const emit = defineEmits<{
167
+ onChildLayerToggle: [LayerData]
168
+ onLayerAction: [LayerActionPayload]
169
+ }>()
170
+
171
+ const visibleActions = computed(() =>
172
+ resolveLayerActions(props.data, props.actionDefaults).filter((action) => action.icon)
173
+ )
174
+ const activeInfoIcons = computed(() => resolveActiveLayerInfoIcons(props.data))
175
+
176
+ const infoIconStyle = (infoIcon: LayerInfoIconConfig) => {
177
+ if (!infoIcon.style?.color) return undefined
178
+ return { color: infoIcon.style.color }
51
179
  }
52
180
 
53
- const props = defineProps<ChildMenuProps>()
181
+ const legendIconName = computed(() => {
182
+ const icon = props.data.style?.icon?.trim()
183
+ return icon || undefined
184
+ })
185
+
186
+ const legendColor = computed(
187
+ () => props.data.style?.color || props.data.style?.fillColor
188
+ )
189
+
190
+ const showVisibilityControl = computed(() => {
191
+ const hasEye = props.visibilityMode === 'eye' && showEyeControl.value
192
+ const hasActions = !props.hideControls && visibleActions.value.length > 0
193
+ if (props.hideControls) return hasEye
194
+ if (props.visibilityMode === 'eye') return hasEye || hasActions
195
+ return true
196
+ })
54
197
 
55
- const emit = defineEmits<{ onChildLayerToggle: [LayerData] }>()
198
+ const showEyeControl = computed(() => props.data.visibility?.show !== false)
199
+
200
+ const showLabel = computed(
201
+ () =>
202
+ props.data.visibility?.labelShow ??
203
+ props.data.toggle?.active ??
204
+ 'Exibir'
205
+ )
206
+ const hideLabel = computed(
207
+ () =>
208
+ props.data.visibility?.labelHide ??
209
+ props.data.toggle?.inactive ??
210
+ 'Ocultar'
211
+ )
56
212
 
57
213
  const active = computed<boolean>({
58
214
  get: () => props.data.active,
59
- set: (active: boolean) => {
60
- toggleLayerVisible(active)
215
+ set: (value: boolean) => {
216
+ toggleLayerVisible(value)
61
217
  }
62
218
  })
63
219
 
64
- const toggleLayerVisible = (active: boolean): void => {
65
- const layer = { ...props.data, active }
220
+ const toggleLayerVisible = (activeValue: boolean): void => {
221
+ const layer = { ...props.data, active: activeValue }
66
222
  if (props.persist) setHistory(layer)
67
223
  emit('onChildLayerToggle', layer)
68
224
  }
69
225
 
226
+ const onActionClick = (action: LayerActionConfig): void => {
227
+ if (action.disabled) return
228
+
229
+ emit('onLayerAction', {
230
+ actionKey: action.key,
231
+ actionType: action.type,
232
+ sectionKey: props.sectionKey,
233
+ groupKey: props.groupKey,
234
+ layerKey: props.data.key,
235
+ layer: props.data,
236
+ source: 'child-menu'
237
+ })
238
+ }
70
239
  </script>
71
240
 
72
241
  <style>
73
242
  .child-menu {
74
243
  padding-left: var(--mapa-size-base-20) !important;
244
+ height: auto !important;
245
+ line-height: normal !important;
246
+ white-space: normal !important;
247
+ }
248
+
249
+ .child-menu .child-menu-content {
250
+ display: flex;
251
+ flex-direction: column;
252
+ gap: var(--mapa-size-base-5);
253
+ width: 100%;
254
+ padding-block: var(--mapa-size-base-5);
75
255
  }
76
256
 
77
257
  .child-menu .child-menu-row {
@@ -82,11 +262,23 @@
82
262
  font-size: var(--mapa-fs-12);
83
263
  }
84
264
 
85
- .child-menu .switch-component {
265
+ .child-menu .visibility-control {
86
266
  display: flex;
87
267
  justify-content: center;
88
268
  align-items: center;
89
269
  gap: var(--mapa-size-base-5);
270
+ flex-shrink: 0;
271
+ }
272
+
273
+ .child-menu .visibility-eye-button {
274
+ display: inline-flex;
275
+ align-items: center;
276
+ justify-content: center;
277
+ border: none;
278
+ background: transparent;
279
+ color: var(--mapa-base-green);
280
+ cursor: pointer;
281
+ padding: var(--mapa-size-base-2);
90
282
  }
91
283
 
92
284
  .child-menu .child-layer-title {
@@ -99,14 +291,109 @@
99
291
  margin-block: auto;
100
292
  }
101
293
 
294
+ .child-menu .child-layer-name {
295
+ cursor: default;
296
+ }
297
+
102
298
  .child-menu .child-layer-legend {
103
- display: inline-block;
299
+ display: inline-flex;
300
+ align-items: center;
301
+ justify-content: center;
302
+ flex-shrink: 0;
303
+ box-sizing: border-box;
104
304
  width: var(--mapa-size-base-8);
105
305
  height: var(--mapa-size-base-8);
106
306
  min-width: var(--mapa-size-base-8);
307
+ min-height: var(--mapa-size-base-8);
308
+ border-radius: 50%;
309
+ line-height: 0;
310
+ }
311
+
312
+ .child-menu .child-layer-legend--icon {
313
+ background: transparent;
314
+ overflow: hidden;
315
+ font-size: var(--mapa-size-base-8);
316
+ }
317
+
318
+ .child-menu .child-layer-legend--icon svg {
319
+ display: block;
320
+ width: auto;
321
+ height: 100%;
322
+ max-width: 100%;
323
+ margin: 0;
324
+ padding: 0;
325
+ vertical-align: 0;
326
+ overflow: hidden;
327
+ }
328
+
329
+ .child-menu .child-layer-required {
330
+ color: #e52207;
331
+ font-weight: 700;
332
+ line-height: 1;
333
+ }
334
+
335
+ .child-menu .child-info-icons {
336
+ display: inline-flex;
337
+ align-items: center;
338
+ gap: var(--mapa-size-base-5);
339
+ flex-shrink: 0;
340
+ color: var(--mapa-base-green);
341
+ }
342
+
343
+ .child-menu .child-info-icon {
344
+ position: relative;
345
+ display: inline-flex;
346
+ align-items: center;
347
+ justify-content: center;
348
+ }
349
+
350
+ .child-menu .child-info-icon-inner {
351
+ position: absolute;
352
+ left: 50%;
353
+ top: 50%;
354
+ width: 0.28em;
355
+ height: 0.7em;
356
+ transform: translate(-50%, -50%);
357
+ pointer-events: none;
358
+ z-index: 0;
359
+ }
360
+
361
+ .child-menu .child-info-icon svg {
362
+ position: relative;
363
+ z-index: 1;
364
+ }
365
+
366
+ .child-menu .child-menu-actions {
367
+ display: flex;
368
+ flex-wrap: wrap;
369
+ gap: var(--mapa-size-base-5);
370
+ }
371
+
372
+ .child-menu .child-action-button {
373
+ display: inline-flex;
374
+ align-items: center;
375
+ justify-content: center;
376
+ border: none !important;
377
+ background: transparent !important;
378
+ color: var(--mapa-base-green);
379
+ cursor: pointer;
380
+ padding: var(--mapa-size-base-2);
381
+ }
382
+
383
+ .child-menu .child-action-button:disabled {
384
+ opacity: 0.5;
385
+ cursor: not-allowed;
386
+ border: none;
107
387
  }
108
388
 
109
389
  .child-menu-divider-row {
110
390
  margin: 0 !important;
111
391
  }
112
392
  </style>
393
+
394
+ <style>
395
+ .child-layer-name-tooltip {
396
+ max-width: 22rem;
397
+ line-height: 1.4;
398
+ }
399
+ </style>