mgv-backoffice 1.36.0 → 1.36.2

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.
Files changed (60) hide show
  1. package/README.md +1794 -1719
  2. package/dist/components/BaseBadge.vue.d.ts +1 -11
  3. package/dist/components/BaseDropdown.vue.d.ts +1 -1
  4. package/dist/ui-lib.js +942 -925
  5. package/dist/ui-lib.umd.cjs +1 -1
  6. package/dist/utils/specForm.d.ts +1 -1
  7. package/package.json +54 -54
  8. package/src/components/BaseAlert.vue +104 -104
  9. package/src/components/BaseBadge.vue +12 -2
  10. package/src/components/BaseBarDistribution.test.ts +51 -51
  11. package/src/components/BaseBarDistribution.vue +73 -73
  12. package/src/components/BaseChipButton.vue +42 -42
  13. package/src/components/BaseCodeBlock.test.ts +70 -70
  14. package/src/components/BaseCodeBlock.vue +74 -74
  15. package/src/components/BaseCollapsibleSection.vue +113 -113
  16. package/src/components/BaseConfirmModal.vue +114 -114
  17. package/src/components/BaseCredentialsForm.test.ts +108 -108
  18. package/src/components/BaseCredentialsForm.vue +241 -241
  19. package/src/components/BaseDropdown.test.ts +87 -87
  20. package/src/components/BaseDropdown.vue +172 -172
  21. package/src/components/BaseEntityPickerModal.vue +330 -330
  22. package/src/components/BaseFileDropzone.vue +94 -94
  23. package/src/components/BaseFilterChip.test.ts +44 -44
  24. package/src/components/BaseFilterChip.vue +67 -67
  25. package/src/components/BaseModalShell.vue +209 -209
  26. package/src/components/BasePageHeader.vue +120 -120
  27. package/src/components/BasePillPickerModal.test.ts +95 -95
  28. package/src/components/BaseRemoveButton.vue +22 -22
  29. package/src/components/BaseSpecFields.test.ts +39 -0
  30. package/src/components/BaseSpecFields.vue +27 -3
  31. package/src/components/BaseStatBreakdown.test.ts +56 -56
  32. package/src/components/BaseStatBreakdown.vue +49 -49
  33. package/src/components/BaseStatusPill.vue +54 -54
  34. package/src/components/BaseTextInputModal.vue +140 -140
  35. package/src/components/BaseToolbarButton.test.ts +48 -48
  36. package/src/components/BaseToolbarButton.vue +94 -94
  37. package/src/composables/useFieldClasses.test.ts +55 -55
  38. package/src/composables/useFieldClasses.ts +56 -56
  39. package/src/composables/usePolling.test.ts +112 -112
  40. package/src/composables/usePolling.ts +102 -102
  41. package/src/composables/useQueryParamSync.test.ts +56 -56
  42. package/src/composables/useQueryParamSync.ts +42 -42
  43. package/src/composables/useThemeClasses.test.ts +34 -34
  44. package/src/composables/useThemeClasses.ts +137 -137
  45. package/src/index.ts +138 -138
  46. package/src/style.css +32 -32
  47. package/src/types/distribution.ts +7 -7
  48. package/src/types/pillPicker.ts +14 -14
  49. package/src/types/specField.ts +30 -30
  50. package/src/types/statBreakdown.ts +6 -6
  51. package/src/utils/format.test.ts +264 -264
  52. package/src/utils/format.ts +216 -216
  53. package/src/utils/httpColors.test.ts +100 -100
  54. package/src/utils/httpColors.ts +99 -99
  55. package/src/utils/kvRows.test.ts +47 -47
  56. package/src/utils/kvRows.ts +34 -34
  57. package/src/utils/specForm.test.ts +64 -53
  58. package/src/utils/specForm.ts +49 -48
  59. package/src/utils/validate.test.ts +82 -82
  60. package/src/utils/validate.ts +76 -76
@@ -1,172 +1,172 @@
1
- <template>
2
- <!--
3
- Button-style single-select dropdown ("Select Social User ⌄"). Unlike
4
- BaseSelect (a themed native <select>), this renders a trigger button plus
5
- a floating menu, so the closed control shows a placeholder and a chevron
6
- that rotates while open — matching the app's filter dropdowns.
7
-
8
- Options are passed as data (not <option> slots) so the menu rows can carry
9
- tooltips and disabled state. Selecting a row emits its `value` and closes
10
- the menu; Escape and an outside click also close it.
11
-
12
- Same `dark:`-variant note as BaseSelect: the slate skin ships static
13
- light/dark classes so consumers get both themes without extra config.
14
- -->
15
- <div
16
- ref="rootEl"
17
- :class="block ? 'relative w-full' : 'relative inline-block'"
18
- >
19
- <button
20
- type="button"
21
- :class="[triggerClass, block ? 'w-full' : '']"
22
- :disabled="disabled"
23
- :aria-label="ariaLabel || undefined"
24
- aria-haspopup="listbox"
25
- :aria-expanded="open"
26
- @click="toggle"
27
- >
28
- <!-- min-w-0 + truncate so long labels ellipsize inside narrow
29
- (fixed-column) triggers instead of wrapping or widening them. -->
30
- <span class="min-w-0 truncate" :class="selectedOption ? '' : 'text-slate-400 dark:text-slate-500'">
31
- {{ selectedOption ? selectedOption.label : placeholder }}
32
- </span>
33
- <ChevronDownIcon
34
- class="shrink-0 transition-transform"
35
- :class="[chevronClass, open ? 'rotate-180' : '']"
36
- aria-hidden="true"
37
- />
38
- </button>
39
-
40
- <ul
41
- v-if="open"
42
- role="listbox"
43
- :aria-label="ariaLabel || placeholder"
44
- class="absolute z-20 mt-1 w-full min-w-max max-w-xs max-h-60 overflow-auto rounded-lg border border-slate-300 dark:border-slate-600 bg-white dark:bg-slate-900 py-1 shadow-lg focus:outline-none"
45
- >
46
- <li
47
- v-for="opt in options"
48
- :key="String(opt.value)"
49
- role="option"
50
- :aria-selected="opt.value === modelValue"
51
- :title="opt.title"
52
- >
53
- <button
54
- type="button"
55
- :disabled="opt.disabled"
56
- :class="rowClass(opt)"
57
- @click="select(opt)"
58
- >
59
- {{ opt.label }}
60
- </button>
61
- </li>
62
- </ul>
63
- </div>
64
- </template>
65
-
66
- <script setup lang="ts">
67
- import { computed, onBeforeUnmount, onMounted, ref } from 'vue'
68
- import { ChevronDownIcon } from '@heroicons/vue/24/outline'
69
- import { useEscapeKey } from '../composables/useEscapeKey'
70
- import type { DropdownOption } from '../types/dropdown'
71
-
72
- // NOT generic on purpose: vite-plugin-dts flattens SFC generics out of the
73
- // emitted declarations, so consumers would see the erased constraint anyway.
74
- // The update:modelValue payload is `any` so `v-model` on a union-typed ref
75
- // type-checks; the actual payload is always one of the options' values.
76
- interface Props {
77
- options: readonly DropdownOption[]
78
- modelValue?: string | number | null
79
- /** Trigger text shown when nothing is selected. */
80
- placeholder?: string
81
- /** 'md' = px-4 py-2.5 (default, the app filter height); 'sm' = px-3 py-2. */
82
- size?: 'sm' | 'md'
83
- /** Full-width (w-full). Set false for an inline, content-width dropdown. */
84
- block?: boolean
85
- disabled?: boolean
86
- /** Accessible name for the trigger/listbox when there is no visible label. */
87
- ariaLabel?: string
88
- /**
89
- * Replaces the trigger's default slate skin (border/background/text/padding)
90
- * with the given classes — layout, focus ring and disabled classes are kept.
91
- * Lets a consumer render the trigger as e.g. a coloured status pill while
92
- * keeping the shared menu behaviour. `size` is ignored when this is set.
93
- */
94
- triggerClass?: string
95
- /** Chevron sizing/colour classes; shrink for compact triggers. */
96
- chevronClass?: string
97
- }
98
-
99
- const props = withDefaults(defineProps<Props>(), {
100
- modelValue: null,
101
- placeholder: 'Select',
102
- size: 'md',
103
- block: true,
104
- disabled: false,
105
- ariaLabel: '',
106
- triggerClass: '',
107
- chevronClass: 'w-5 h-5 text-slate-500 dark:text-slate-400',
108
- })
109
-
110
- const emit = defineEmits<{
111
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
112
- (e: 'update:modelValue', value: any): void
113
- }>()
114
-
115
- const open = ref(false)
116
- const rootEl = ref<HTMLElement | null>(null)
117
-
118
- const selectedOption = computed(() =>
119
- props.options.find((o) => o.value === props.modelValue) ?? null,
120
- )
121
-
122
- const triggerClass = computed(() => {
123
- const layout =
124
- 'flex items-center justify-between gap-2 text-left transition-colors ' +
125
- 'focus:outline-none focus-visible:ring-2 focus-visible:ring-emerald-500 ' +
126
- 'cursor-pointer disabled:opacity-50 disabled:cursor-not-allowed '
127
- if (props.triggerClass) return layout + props.triggerClass
128
- const padding = props.size === 'sm' ? 'px-3 py-2' : 'px-4 py-2.5'
129
- return (
130
- layout +
131
- 'rounded-lg border text-sm ' +
132
- 'border-slate-300 dark:border-slate-600 bg-white dark:bg-slate-900 text-slate-700 dark:text-slate-100 ' +
133
- 'hover:bg-slate-50 dark:hover:bg-slate-800 ' +
134
- padding
135
- )
136
- })
137
-
138
- function rowClass(opt: DropdownOption): string {
139
- const selected = opt.value === props.modelValue
140
- const base =
141
- 'block w-full px-4 py-2 text-left text-sm transition-colors cursor-pointer focus:outline-none focus-visible:ring-2 focus-visible:ring-inset focus-visible:ring-emerald-500 disabled:opacity-50 disabled:cursor-not-allowed'
142
- const fill = selected
143
- ? 'bg-emerald-500 text-white'
144
- : 'text-slate-700 dark:text-slate-200 hover:bg-slate-100 dark:hover:bg-slate-800'
145
- return `${base} ${fill}`
146
- }
147
-
148
- function toggle() {
149
- if (props.disabled) return
150
- open.value = !open.value
151
- }
152
-
153
- function select(opt: DropdownOption) {
154
- if (opt.disabled) return
155
- emit('update:modelValue', opt.value)
156
- open.value = false
157
- }
158
-
159
- useEscapeKey(() => {
160
- open.value = false
161
- })
162
-
163
- function onDocumentClick(e: MouseEvent) {
164
- if (!open.value) return
165
- if (rootEl.value && !rootEl.value.contains(e.target as Node)) {
166
- open.value = false
167
- }
168
- }
169
-
170
- onMounted(() => document.addEventListener('click', onDocumentClick))
171
- onBeforeUnmount(() => document.removeEventListener('click', onDocumentClick))
172
- </script>
1
+ <template>
2
+ <!--
3
+ Button-style single-select dropdown ("Select Social User ⌄"). Unlike
4
+ BaseSelect (a themed native <select>), this renders a trigger button plus
5
+ a floating menu, so the closed control shows a placeholder and a chevron
6
+ that rotates while open — matching the app's filter dropdowns.
7
+
8
+ Options are passed as data (not <option> slots) so the menu rows can carry
9
+ tooltips and disabled state. Selecting a row emits its `value` and closes
10
+ the menu; Escape and an outside click also close it.
11
+
12
+ Same `dark:`-variant note as BaseSelect: the slate skin ships static
13
+ light/dark classes so consumers get both themes without extra config.
14
+ -->
15
+ <div
16
+ ref="rootEl"
17
+ :class="block ? 'relative w-full' : 'relative inline-block'"
18
+ >
19
+ <button
20
+ type="button"
21
+ :class="[triggerClass, block ? 'w-full' : '']"
22
+ :disabled="disabled"
23
+ :aria-label="ariaLabel || undefined"
24
+ aria-haspopup="listbox"
25
+ :aria-expanded="open"
26
+ @click="toggle"
27
+ >
28
+ <!-- min-w-0 + truncate so long labels ellipsize inside narrow
29
+ (fixed-column) triggers instead of wrapping or widening them. -->
30
+ <span class="min-w-0 truncate" :class="selectedOption ? '' : 'text-slate-400 dark:text-slate-500'">
31
+ {{ selectedOption ? selectedOption.label : placeholder }}
32
+ </span>
33
+ <ChevronDownIcon
34
+ class="shrink-0 transition-transform"
35
+ :class="[chevronClass, open ? 'rotate-180' : '']"
36
+ aria-hidden="true"
37
+ />
38
+ </button>
39
+
40
+ <ul
41
+ v-if="open"
42
+ role="listbox"
43
+ :aria-label="ariaLabel || placeholder"
44
+ class="absolute z-20 mt-1 w-full min-w-max max-w-xs max-h-60 overflow-auto rounded-lg border border-slate-300 dark:border-slate-600 bg-white dark:bg-slate-900 py-1 shadow-lg focus:outline-none"
45
+ >
46
+ <li
47
+ v-for="opt in options"
48
+ :key="String(opt.value)"
49
+ role="option"
50
+ :aria-selected="opt.value === modelValue"
51
+ :title="opt.title"
52
+ >
53
+ <button
54
+ type="button"
55
+ :disabled="opt.disabled"
56
+ :class="rowClass(opt)"
57
+ @click="select(opt)"
58
+ >
59
+ {{ opt.label }}
60
+ </button>
61
+ </li>
62
+ </ul>
63
+ </div>
64
+ </template>
65
+
66
+ <script setup lang="ts">
67
+ import { computed, onBeforeUnmount, onMounted, ref } from 'vue'
68
+ import { ChevronDownIcon } from '@heroicons/vue/24/outline'
69
+ import { useEscapeKey } from '../composables/useEscapeKey'
70
+ import type { DropdownOption } from '../types/dropdown'
71
+
72
+ // NOT generic on purpose: vite-plugin-dts flattens SFC generics out of the
73
+ // emitted declarations, so consumers would see the erased constraint anyway.
74
+ // The update:modelValue payload is `any` so `v-model` on a union-typed ref
75
+ // type-checks; the actual payload is always one of the options' values.
76
+ interface Props {
77
+ options: readonly DropdownOption[]
78
+ modelValue?: string | number | null
79
+ /** Trigger text shown when nothing is selected. */
80
+ placeholder?: string
81
+ /** 'md' = px-4 py-2.5 (default, the app filter height); 'sm' = px-3 py-2. */
82
+ size?: 'sm' | 'md'
83
+ /** Full-width (w-full). Set false for an inline, content-width dropdown. */
84
+ block?: boolean
85
+ disabled?: boolean
86
+ /** Accessible name for the trigger/listbox when there is no visible label. */
87
+ ariaLabel?: string
88
+ /**
89
+ * Replaces the trigger's default slate skin (border/background/text/padding)
90
+ * with the given classes — layout, focus ring and disabled classes are kept.
91
+ * Lets a consumer render the trigger as e.g. a coloured status pill while
92
+ * keeping the shared menu behaviour. `size` is ignored when this is set.
93
+ */
94
+ triggerClass?: string
95
+ /** Chevron sizing/colour classes; shrink for compact triggers. */
96
+ chevronClass?: string
97
+ }
98
+
99
+ const props = withDefaults(defineProps<Props>(), {
100
+ modelValue: null,
101
+ placeholder: 'Select',
102
+ size: 'md',
103
+ block: true,
104
+ disabled: false,
105
+ ariaLabel: '',
106
+ triggerClass: '',
107
+ chevronClass: 'w-5 h-5 text-slate-500 dark:text-slate-400',
108
+ })
109
+
110
+ const emit = defineEmits<{
111
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
112
+ (e: 'update:modelValue', value: any): void
113
+ }>()
114
+
115
+ const open = ref(false)
116
+ const rootEl = ref<HTMLElement | null>(null)
117
+
118
+ const selectedOption = computed(() =>
119
+ props.options.find((o) => o.value === props.modelValue) ?? null,
120
+ )
121
+
122
+ const triggerClass = computed(() => {
123
+ const layout =
124
+ 'flex items-center justify-between gap-2 text-left transition-colors ' +
125
+ 'focus:outline-none focus-visible:ring-2 focus-visible:ring-emerald-500 ' +
126
+ 'cursor-pointer disabled:opacity-50 disabled:cursor-not-allowed '
127
+ if (props.triggerClass) return layout + props.triggerClass
128
+ const padding = props.size === 'sm' ? 'px-3 py-2' : 'px-4 py-2.5'
129
+ return (
130
+ layout +
131
+ 'rounded-lg border text-sm ' +
132
+ 'border-slate-300 dark:border-slate-600 bg-white dark:bg-slate-900 text-slate-700 dark:text-slate-100 ' +
133
+ 'hover:bg-slate-50 dark:hover:bg-slate-800 ' +
134
+ padding
135
+ )
136
+ })
137
+
138
+ function rowClass(opt: DropdownOption): string {
139
+ const selected = opt.value === props.modelValue
140
+ const base =
141
+ 'block w-full px-4 py-2 text-left text-sm transition-colors cursor-pointer focus:outline-none focus-visible:ring-2 focus-visible:ring-inset focus-visible:ring-emerald-500 disabled:opacity-50 disabled:cursor-not-allowed'
142
+ const fill = selected
143
+ ? 'bg-emerald-500 text-white'
144
+ : 'text-slate-700 dark:text-slate-200 hover:bg-slate-100 dark:hover:bg-slate-800'
145
+ return `${base} ${fill}`
146
+ }
147
+
148
+ function toggle() {
149
+ if (props.disabled) return
150
+ open.value = !open.value
151
+ }
152
+
153
+ function select(opt: DropdownOption) {
154
+ if (opt.disabled) return
155
+ emit('update:modelValue', opt.value)
156
+ open.value = false
157
+ }
158
+
159
+ useEscapeKey(() => {
160
+ open.value = false
161
+ })
162
+
163
+ function onDocumentClick(e: MouseEvent) {
164
+ if (!open.value) return
165
+ if (rootEl.value && !rootEl.value.contains(e.target as Node)) {
166
+ open.value = false
167
+ }
168
+ }
169
+
170
+ onMounted(() => document.addEventListener('click', onDocumentClick))
171
+ onBeforeUnmount(() => document.removeEventListener('click', onDocumentClick))
172
+ </script>