mgv-backoffice 1.31.0 → 1.32.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.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "mgv-backoffice",
3
- "version": "1.31.0",
3
+ "version": "1.32.0",
4
4
  "description": "Shared Vue 3 UI component library",
5
5
  "type": "module",
6
6
  "main": "./dist/ui-lib.umd.cjs",
@@ -1,26 +1,14 @@
1
1
  <template>
2
2
  <BaseModalShell
3
3
  :title="title"
4
+ :icon="ExclamationTriangleIcon"
5
+ :icon-bg-class="variant === 'danger'
6
+ ? isDark ? 'bg-red-900/30' : 'bg-red-50'
7
+ : isDark ? 'bg-amber-900/30' : 'bg-amber-50'"
8
+ :icon-class="variant === 'danger' ? 'text-red-600' : 'text-amber-600'"
4
9
  :manual-close="submitting"
5
10
  @cancel="handleCancel"
6
11
  >
7
- <template #icon>
8
- <div
9
- class="flex-shrink-0 flex h-12 w-12 items-center justify-center rounded-full"
10
- :class="[
11
- variant === 'danger'
12
- ? isDark ? 'bg-red-900/30' : 'bg-red-50'
13
- : isDark ? 'bg-amber-900/30' : 'bg-amber-50'
14
- ]"
15
- >
16
- <ExclamationTriangleIcon
17
- class="h-6 w-6"
18
- :class="variant === 'danger' ? 'text-red-600' : 'text-amber-600'"
19
- aria-hidden="true"
20
- />
21
- </div>
22
- </template>
23
-
24
12
  <!--
25
13
  Default renders the plain `message` string. Callers that need rich
26
14
  text (bold names, inline code, etc.) can override the `message` slot
@@ -1,18 +1,16 @@
1
1
  <template>
2
2
  <BaseModalShell
3
3
  :title="title"
4
+ :icon="FolderIcon"
5
+ :icon-bg-class="palette.iconBg"
6
+ :icon-class="palette.iconText"
4
7
  :manual-close="submitting"
5
8
  @cancel="handleCancel"
6
9
  >
7
- <template #icon>
8
- <slot name="icon">
9
- <div
10
- class="flex-shrink-0 flex h-12 w-12 items-center justify-center rounded-full"
11
- :class="palette.iconBg"
12
- >
13
- <FolderIcon class="h-6 w-6" :class="palette.iconText" aria-hidden="true" />
14
- </div>
15
- </slot>
10
+ <!-- Forward a caller-supplied icon override; otherwise the shell
11
+ renders the default folder icon in the palette-tinted chip. -->
12
+ <template v-if="$slots.icon" #icon>
13
+ <slot name="icon" />
16
14
  </template>
17
15
 
18
16
  <p v-if="message" class="text-sm" :class="isDark ? 'text-gray-300' : 'text-gray-600'">
@@ -0,0 +1,94 @@
1
+ <template>
2
+ <!--
3
+ Dashed "click to select a file" upload zone — extracted from WireMate's
4
+ ImportPostmanModal file picker. Also accepts drag-and-drop.
5
+
6
+ The hidden input is reset after every selection so choosing the same
7
+ file twice still fires `files` — callers don't need their own reset.
8
+ -->
9
+ <label
10
+ class="flex flex-col items-center justify-center gap-2 rounded-lg border-2 border-dashed px-6 py-10 text-center transition-colors"
11
+ :class="[
12
+ disabled ? 'opacity-50 cursor-not-allowed' : 'cursor-pointer',
13
+ dragging
14
+ ? (isDark ? 'border-emerald-500 text-emerald-400' : 'border-emerald-500 text-emerald-600')
15
+ : (isDark
16
+ ? 'border-gray-700 hover:border-emerald-500 text-gray-400 hover:text-emerald-400'
17
+ : 'border-gray-300 hover:border-emerald-500 text-gray-500 hover:text-emerald-600'),
18
+ ]"
19
+ @dragover.prevent="onDragOver"
20
+ @dragleave.prevent="dragging = false"
21
+ @drop.prevent="onDrop"
22
+ >
23
+ <slot name="icon">
24
+ <svg class="w-10 h-10" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="1.5" aria-hidden="true">
25
+ <path
26
+ stroke-linecap="round"
27
+ stroke-linejoin="round"
28
+ d="M19.5 14.25v-2.625a3.375 3.375 0 0 0-3.375-3.375h-1.5A1.125 1.125 0 0 1 13.5 7.125v-1.5a3.375 3.375 0 0 0-3.375-3.375H8.25m6.75 12-3-3m0 0-3 3m3-3v6m-1.5-15H5.625c-.621 0-1.125.504-1.125 1.125v17.25c0 .621.504 1.125 1.125 1.125h12.75c.621 0 1.125-.504 1.125-1.125V11.25a9 9 0 0 0-9-9Z"
29
+ />
30
+ </svg>
31
+ </slot>
32
+ <p class="text-sm font-medium">{{ label }}</p>
33
+ <p v-if="hint" class="text-xs" :class="isDark ? 'text-gray-500' : 'text-gray-400'">
34
+ {{ hint }}
35
+ </p>
36
+ <input
37
+ type="file"
38
+ class="hidden"
39
+ :accept="accept"
40
+ :multiple="multiple"
41
+ :disabled="disabled"
42
+ @change="onChange"
43
+ />
44
+ </label>
45
+ </template>
46
+
47
+ <script lang="ts" setup>
48
+ import { ref } from 'vue'
49
+ import { useTheme } from '../composables/useTheme'
50
+
51
+ interface Props {
52
+ /** Main line, e.g. "Click to select a Postman collection (.json)". */
53
+ label: string
54
+ /** Optional dimmed helper line below the label. */
55
+ hint?: string
56
+ /** Forwarded to the hidden input's `accept`. Dropped files are NOT filtered by it. */
57
+ accept?: string
58
+ multiple?: boolean
59
+ disabled?: boolean
60
+ }
61
+
62
+ const props = withDefaults(defineProps<Props>(), {
63
+ hint: '',
64
+ accept: '',
65
+ multiple: false,
66
+ disabled: false,
67
+ })
68
+
69
+ const emit = defineEmits<{ files: [files: File[]] }>()
70
+
71
+ const { isDark } = useTheme()
72
+
73
+ const dragging = ref(false)
74
+
75
+ function onChange(e: Event) {
76
+ const input = e.target as HTMLInputElement
77
+ const files = Array.from(input.files ?? [])
78
+ // Reset so re-selecting the same file re-triggers change.
79
+ input.value = ''
80
+ if (files.length) emit('files', files)
81
+ }
82
+
83
+ function onDragOver() {
84
+ if (!props.disabled) dragging.value = true
85
+ }
86
+
87
+ function onDrop(e: DragEvent) {
88
+ dragging.value = false
89
+ if (props.disabled) return
90
+ const dropped = Array.from(e.dataTransfer?.files ?? [])
91
+ if (!dropped.length) return
92
+ emit('files', props.multiple ? dropped : dropped.slice(0, 1))
93
+ }
94
+ </script>
@@ -4,8 +4,14 @@
4
4
  backdrop, dark-mode card, escape key, aria-modal). Concrete modals
5
5
  should compose this rather than re-implementing the chrome.
6
6
 
7
+ Every modal shows an icon left of the title: the REQUIRED `icon`
8
+ prop (a component, e.g. a heroicon) renders inside a tinted
9
+ circular chip. Tint via `icon-bg-class` / `icon-class`; the `icon`
10
+ slot remains as a full-markup override for callers that need
11
+ something richer than the standard chip.
12
+
7
13
  Slots:
8
- • icon — small circular icon shown next to the title
14
+ • icon — override the standard icon chip next to the title
9
15
  • default — body content
10
16
  • footer — action buttons (cancel / confirm / etc.)
11
17
 
@@ -37,7 +43,14 @@
37
43
  >
38
44
  <div class="flex items-start justify-between gap-4 p-6 border-b shrink-0" :class="t.border">
39
45
  <div class="flex items-start gap-4 min-w-0">
40
- <slot name="icon" />
46
+ <slot name="icon">
47
+ <div
48
+ class="flex-shrink-0 flex h-12 w-12 items-center justify-center rounded-full"
49
+ :class="iconChipBg"
50
+ >
51
+ <component :is="icon" class="h-6 w-6" :class="iconClass" aria-hidden="true" />
52
+ </div>
53
+ </slot>
41
54
  <div class="flex-1 min-w-0">
42
55
  <h3 :id="titleId" class="text-lg font-medium" :class="t.primaryText">
43
56
  {{ title }}
@@ -69,7 +82,14 @@
69
82
  <div class="p-6">
70
83
  <!-- Title row -->
71
84
  <div class="flex items-start gap-4">
72
- <slot name="icon" />
85
+ <slot name="icon">
86
+ <div
87
+ class="flex-shrink-0 flex h-12 w-12 items-center justify-center rounded-full"
88
+ :class="iconChipBg"
89
+ >
90
+ <component :is="icon" class="h-6 w-6" :class="iconClass" aria-hidden="true" />
91
+ </div>
92
+ </slot>
73
93
  <div class="flex-1">
74
94
  <h3
75
95
  :id="titleId"
@@ -98,12 +118,26 @@
98
118
 
99
119
  <script setup lang="ts">
100
120
  import { computed } from 'vue'
121
+ import type { Component } from 'vue'
101
122
  import { useTheme } from '../composables/useTheme'
102
123
  import { useThemeClasses } from '../composables/useThemeClasses'
103
124
  import { useEscapeKey } from '../composables/useEscapeKey'
104
125
 
105
126
  interface Props {
106
127
  title: string
128
+ /**
129
+ * Icon component (e.g. a heroicon) rendered in a tinted circular
130
+ * chip left of the title. REQUIRED — every modal must name itself
131
+ * with an icon. The `icon` slot can override the whole chip.
132
+ */
133
+ icon: Component
134
+ /**
135
+ * Background classes of the icon chip. Defaults to the emerald
136
+ * tint (dark-mode aware).
137
+ */
138
+ iconBgClass?: string
139
+ /** Classes applied to the icon itself. Defaults to emerald. */
140
+ iconClass?: string
107
141
  /**
108
142
  * Max-width of the dialog card. Defaults to `max-w-md`. Pass any
109
143
  * Tailwind max-w utility, or empty string to opt out.
@@ -135,6 +169,8 @@ const props = withDefaults(defineProps<Props>(), {
135
169
  manualClose: false,
136
170
  scrollable: false,
137
171
  subtitle: '',
172
+ iconBgClass: '',
173
+ iconClass: 'text-emerald-600',
138
174
  })
139
175
 
140
176
  const emit = defineEmits<{
@@ -156,6 +192,12 @@ const shellClass = computed(() =>
156
192
  : 'bg-white border border-gray-200',
157
193
  )
158
194
 
195
+ // The icon chip's background: an explicit `icon-bg-class` wins, else the
196
+ // standard emerald tint in the current theme.
197
+ const iconChipBg = computed(() =>
198
+ props.iconBgClass || (isDark.value ? 'bg-emerald-900/30' : 'bg-emerald-50'),
199
+ )
200
+
159
201
  function onBackdrop() {
160
202
  emit('backdrop')
161
203
  if (!props.manualClose) emit('cancel')
@@ -12,18 +12,14 @@
12
12
  -->
13
13
  <BaseModalShell
14
14
  :title="title"
15
+ :icon="DocumentDuplicateIcon"
15
16
  manual-close
16
17
  @backdrop="handleBackdropClick"
17
18
  >
18
- <template #icon>
19
- <slot name="icon">
20
- <div
21
- class="flex-shrink-0 flex h-12 w-12 items-center justify-center rounded-full"
22
- :class="isDark ? 'bg-emerald-900/30' : 'bg-emerald-50'"
23
- >
24
- <DocumentDuplicateIcon class="h-6 w-6 text-emerald-600" aria-hidden="true" />
25
- </div>
26
- </slot>
19
+ <!-- Forward a caller-supplied icon override; otherwise the shell
20
+ renders the default document icon in the standard emerald chip. -->
21
+ <template v-if="$slots.icon" #icon>
22
+ <slot name="icon" />
27
23
  </template>
28
24
 
29
25
  <p class="text-sm" :class="isDark ? 'text-gray-300' : 'text-gray-600'">
package/src/index.ts CHANGED
@@ -36,6 +36,7 @@ export { default as BaseNotificationPanel } from './components/BaseNotificationP
36
36
  export { default as BaseChipButton } from './components/BaseChipButton.vue'
37
37
  export { default as BaseRemoveButton } from './components/BaseRemoveButton.vue'
38
38
  export { default as BaseStatusPill } from './components/BaseStatusPill.vue'
39
+ export { default as BaseFileDropzone } from './components/BaseFileDropzone.vue'
39
40
 
40
41
  // Components — extracted from TradeAutomation
41
42
  export { default as BaseInput } from './components/BaseInput.vue'