@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.
@@ -7,21 +7,39 @@
7
7
  <template #title>
8
8
  <div class="parent-menu-row">
9
9
  <div class="parent-layer-title">{{ groupData.name }}</div>
10
- <span class="switch-component">
11
- <ElSwitch
12
- v-model="allLayersActive"
13
- @click.stop
10
+ <span
11
+ v-if="showVisibilityControl"
12
+ class="visibility-control"
13
+ >
14
+ <button
15
+ v-if="visibilityMode === 'eye'"
16
+ type="button"
17
+ class="visibility-eye-button"
18
+ :aria-label="allLayersActive ? hideLabel : showLabel"
19
+ :title="allLayersActive ? hideLabel : showLabel"
20
+ @click.stop="allLayersActive = !allLayersActive"
14
21
  >
15
- <template #active-action>
16
- <FontAwesomeIcon iconName="check" />
17
- </template>
18
- <template #inactive-action>
19
- <FontAwesomeIcon iconName="xmark" />
20
- </template>
21
- </ElSwitch>
22
- <span class="parent-layer-status">
23
- {{ allLayersActive ? groupData.toggle.active : groupData.toggle.inactive }}
24
- </span>
22
+ <FontAwesomeIcon :iconName="allLayersActive ? 'eye' : 'eye-slash'" />
23
+ </button>
24
+ <template v-else>
25
+ <ElSwitch
26
+ v-model="allLayersActive"
27
+ @click.stop
28
+ >
29
+ <template #active-action>
30
+ <FontAwesomeIcon iconName="check" />
31
+ </template>
32
+ <template #inactive-action>
33
+ <FontAwesomeIcon iconName="xmark" />
34
+ </template>
35
+ </ElSwitch>
36
+ <span
37
+ v-if="groupData.toggle"
38
+ class="parent-layer-status"
39
+ >
40
+ {{ allLayersActive ? groupData.toggle.active : groupData.toggle.inactive }}
41
+ </span>
42
+ </template>
25
43
  <ElDivider
26
44
  class="divider-bar"
27
45
  direction="vertical"
@@ -33,43 +51,153 @@
33
51
  v-for="(child, idx) in childrenLayers"
34
52
  :key="child.key"
35
53
  >
54
+ <ElMenuItem
55
+ v-if="isMenuSeparator(child)"
56
+ :index="child.key"
57
+ class="parent-menu-subgroup-header"
58
+ disabled
59
+ >
60
+ <div class="parent-menu-subgroup-title">{{ child.name }}</div>
61
+ </ElMenuItem>
36
62
  <ChildMenu
37
- v-if="child.name"
63
+ v-else-if="child.name"
64
+ :actionDefaults="actionDefaults"
38
65
  :data="child"
66
+ :groupKey="groupData.key"
39
67
  :persist="props.persist"
68
+ :sectionKey="sectionKey"
69
+ :visibilityMode="visibilityMode"
40
70
  @onChildLayerToggle="onChildChange($event, idx)"
71
+ @onLayerAction="emit('onLayerAction', $event)"
41
72
  />
42
73
  </template>
74
+
75
+ <ElMenuItem
76
+ v-if="visibleGroupActions.length"
77
+ :index="`${groupData.key}__group-actions`"
78
+ class="parent-menu-group-actions-item"
79
+ >
80
+ <div class="parent-menu-group-actions">
81
+ <button
82
+ v-for="action in visibleGroupActions"
83
+ :key="action.key"
84
+ type="button"
85
+ class="parent-group-action-button"
86
+ :style="actionButtonStyle(action)"
87
+ :disabled="action.disabled"
88
+ @click.stop="onGroupActionClick(action)"
89
+ >
90
+ <FontAwesomeIcon
91
+ v-if="action.icon"
92
+ :iconName="action.icon"
93
+ />
94
+ <span>{{ action.name }}</span>
95
+ </button>
96
+ </div>
97
+ </ElMenuItem>
43
98
  </ElSubMenu>
44
99
  <ElDivider class="parent-menu-divider-row" />
45
100
  </template>
46
101
 
47
102
  <script lang="ts" setup>
48
- import { ElDivider, ElSubMenu, ElSwitch } from 'element-plus'
49
- import { computed, ref } from 'vue'
103
+ import { ElDivider, ElMenuItem, ElSubMenu, ElSwitch } from 'element-plus'
104
+ import { computed, ref, watch } from 'vue'
50
105
  import FontAwesomeIcon from '../fa-icon/FontAwesomeIcon.vue'
51
106
  import ChildMenu from './ChildMenu.vue'
52
- import { GroupLayerData, LayerData } from '../../types'
107
+ import {
108
+ GroupActionConfig,
109
+ GroupLayerData,
110
+ LayerActionDefaults,
111
+ LayerActionPayload,
112
+ LayerActionStyle,
113
+ LayerData
114
+ } from '../../types'
115
+ import {
116
+ isMenuSeparator,
117
+ resolveActiveGroupActions
118
+ } from '../../utils/layersConfigNormalizer'
53
119
  import { resolveLayerActiveState, setHistory } from '../../utils/menuHistory.ts'
54
120
 
121
+ const DEFAULT_ACTION_STYLE: Required<LayerActionStyle> = {
122
+ backgroundColor: 'var(--mapa-base-green)',
123
+ color: 'var(--mapa-base-white)',
124
+ borderColor: 'var(--mapa-base-white)'
125
+ }
126
+
127
+ const resolveActionStyle = (style?: LayerActionStyle): Required<LayerActionStyle> => {
128
+ const color = style?.color ?? DEFAULT_ACTION_STYLE.color
129
+ return {
130
+ backgroundColor: style?.backgroundColor ?? DEFAULT_ACTION_STYLE.backgroundColor,
131
+ color,
132
+ borderColor: style?.borderColor ?? color
133
+ }
134
+ }
135
+
136
+ const actionButtonStyle = (action: GroupActionConfig): Required<LayerActionStyle> =>
137
+ resolveActionStyle(action.style)
138
+
55
139
  type ParentMenuProps = {
56
140
  groupData: GroupLayerData
57
141
  persist: boolean
142
+ sectionKey?: string
143
+ actionDefaults?: LayerActionDefaults
144
+ visibilityMode?: 'switch' | 'eye'
58
145
  }
59
146
 
60
- const props = defineProps<ParentMenuProps>()
147
+ const props = withDefaults(defineProps<ParentMenuProps>(), {
148
+ visibilityMode: 'switch'
149
+ })
61
150
 
62
151
  const emit = defineEmits<{
63
152
  onChildLayerToggle: [LayerData]
64
153
  onGroupLayerToggle: [GroupLayerData]
154
+ onLayerAction: [LayerActionPayload]
65
155
  }>()
66
156
 
67
157
  const childrenLayers = ref<LayerData[]>(
68
158
  (props.groupData.layers ?? []).map((layer) => resolveLayerActiveState(layer, props.persist))
69
159
  )
70
160
 
161
+ watch(
162
+ () => props.groupData.layers,
163
+ (layers) => {
164
+ childrenLayers.value = (layers ?? []).map((layer) =>
165
+ resolveLayerActiveState(layer, props.persist)
166
+ )
167
+ },
168
+ { deep: true }
169
+ )
170
+
171
+ const visibleGroupActions = computed(() => resolveActiveGroupActions(props.groupData))
172
+
173
+ const showGroupEye = computed(() => props.groupData.visibility?.show !== false)
174
+
175
+ const showVisibilityControl = computed(() => {
176
+ if (props.visibilityMode === 'eye') return showGroupEye.value
177
+ return true
178
+ })
179
+
180
+ const showLabel = computed(
181
+ () =>
182
+ props.groupData.visibility?.labelShow ??
183
+ props.groupData.toggle?.active ??
184
+ 'Exibir'
185
+ )
186
+ const hideLabel = computed(
187
+ () =>
188
+ props.groupData.visibility?.labelHide ??
189
+ props.groupData.toggle?.inactive ??
190
+ 'Ocultar'
191
+ )
192
+
193
+ const toggleableLayers = computed(() =>
194
+ childrenLayers.value.filter((layer: LayerData) => !isMenuSeparator(layer))
195
+ )
196
+
71
197
  const toggleVisibleAllLayers = (): void => {
72
- if (props.persist) childrenLayers.value.forEach((layer: LayerData) => setHistory(layer))
198
+ if (props.persist) {
199
+ toggleableLayers.value.forEach((layer: LayerData) => setHistory(layer))
200
+ }
73
201
 
74
202
  emit('onGroupLayerToggle', {
75
203
  ...props.groupData,
@@ -78,13 +206,11 @@
78
206
  }
79
207
 
80
208
  const allLayersActive = computed<boolean>({
81
- get: () => childrenLayers.value.some((layer: LayerData) => layer.active),
209
+ get: () => toggleableLayers.value.some((layer: LayerData) => layer.active),
82
210
  set: (active: boolean) => {
83
- childrenLayers.value = props.groupData.layers.map((layer: LayerData) => {
84
- return {
85
- ...layer,
86
- active
87
- }
211
+ childrenLayers.value = (props.groupData.layers ?? []).map((layer: LayerData) => {
212
+ if (isMenuSeparator(layer)) return resolveLayerActiveState(layer, props.persist)
213
+ return { ...layer, active }
88
214
  })
89
215
 
90
216
  toggleVisibleAllLayers()
@@ -95,6 +221,18 @@
95
221
  childrenLayers.value[idx] = layer
96
222
  emit('onChildLayerToggle', layer)
97
223
  }
224
+
225
+ const onGroupActionClick = (action: GroupActionConfig): void => {
226
+ if (action.disabled) return
227
+
228
+ emit('onLayerAction', {
229
+ actionKey: action.key,
230
+ actionType: action.type,
231
+ sectionKey: props.sectionKey,
232
+ groupKey: props.groupData.key,
233
+ source: 'group-menu'
234
+ })
235
+ }
98
236
  </script>
99
237
 
100
238
  <style>
@@ -106,13 +244,24 @@
106
244
  font-size: var(--mapa-fs-12);
107
245
  }
108
246
 
109
- .parent-menu .switch-component {
247
+ .parent-menu .visibility-control {
110
248
  display: flex;
111
249
  justify-content: center;
112
250
  align-items: center;
113
251
  gap: var(--mapa-size-base-5);
114
252
  }
115
253
 
254
+ .parent-menu .visibility-eye-button {
255
+ display: inline-flex;
256
+ align-items: center;
257
+ justify-content: center;
258
+ border: none;
259
+ background: transparent;
260
+ color: var(--mapa-base-green);
261
+ cursor: pointer;
262
+ padding: var(--mapa-size-base-2);
263
+ }
264
+
116
265
  .parent-menu .parent-layer-title {
117
266
  word-wrap: break-word;
118
267
  white-space: normal;
@@ -126,7 +275,92 @@
126
275
  border-width: var(--mapa-size-base-2);
127
276
  }
128
277
 
278
+ .parent-menu-group-actions {
279
+ display: flex;
280
+ flex-direction: column;
281
+ align-items: stretch;
282
+ gap: var(--mapa-size-base-5);
283
+ width: 100%;
284
+ box-sizing: border-box;
285
+ padding: var(--mapa-size-base-10) 0;
286
+ }
287
+
288
+ .parent-menu .parent-menu-group-actions-item {
289
+ height: auto !important;
290
+ line-height: normal !important;
291
+ white-space: normal !important;
292
+ padding-left: var(--mapa-size-base-20) !important;
293
+ padding-right: var(--mapa-size-base-20) !important;
294
+ cursor: default !important;
295
+ }
296
+
297
+ .parent-menu .parent-menu-group-actions-item:hover {
298
+ background-color: transparent !important;
299
+ }
300
+
301
+ .parent-group-action-button {
302
+ display: inline-flex;
303
+ flex: 0 0 auto;
304
+ align-items: center;
305
+ justify-content: center;
306
+ gap: var(--mapa-size-base-5);
307
+ width: 100%;
308
+ height: var(--mapa-size-base-40);
309
+ padding: 0 var(--mapa-size-base-10);
310
+ border: 1px solid;
311
+ border-radius: 100em;
312
+ font-size: var(--mapa-fs-15);
313
+ font-weight: 600;
314
+ white-space: nowrap;
315
+ overflow: hidden;
316
+ text-overflow: ellipsis;
317
+ position: relative;
318
+ text-align: center;
319
+ vertical-align: middle;
320
+ cursor: pointer;
321
+ }
322
+
323
+ .parent-group-action-button:not(:disabled):hover {
324
+ background-image: linear-gradient(rgba(0, 0, 0, 0.16), rgba(0, 0, 0, 0.16));
325
+ }
326
+
327
+ .parent-group-action-button:not(:disabled):active {
328
+ background-image: linear-gradient(rgba(0, 0, 0, 0.32), rgba(0, 0, 0, 0.32));
329
+ }
330
+
331
+ .parent-group-action-button:disabled {
332
+ opacity: 0.5;
333
+ cursor: not-allowed;
334
+ }
335
+
129
336
  .parent-menu-divider-row {
130
337
  margin: 0 !important;
131
338
  }
339
+
340
+ /* Título de subgrupo: separador visual, sem accordion */
341
+ .parent-menu .parent-menu-subgroup-header {
342
+ height: auto !important;
343
+ line-height: normal !important;
344
+ white-space: normal !important;
345
+ padding: var(--mapa-size-base-10) var(--mapa-size-base-20) var(--mapa-size-base-5) !important;
346
+ cursor: default !important;
347
+ opacity: 1 !important;
348
+ color: inherit !important;
349
+ }
350
+
351
+ .parent-menu .parent-menu-subgroup-header:hover,
352
+ .parent-menu .parent-menu-subgroup-header.is-disabled {
353
+ background-color: transparent !important;
354
+ cursor: default !important;
355
+ opacity: 1 !important;
356
+ }
357
+
358
+ .parent-menu .parent-menu-subgroup-title {
359
+ font-size: var(--mapa-fs-12);
360
+ font-weight: 700;
361
+ line-height: 150%;
362
+ color: var(--mapa-base-black, #000000);
363
+ word-wrap: break-word;
364
+ white-space: normal;
365
+ }
132
366
  </style>