@rural-environmental-registry/map_component 1.2.0 → 2.0.0

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