@ulu/frontend-vue 0.1.0-beta.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.
- package/LICENSE +21 -0
- package/README.md +9 -0
- package/dist/breakpoints-ClT9bfZm.js +211 -0
- package/dist/frontend-vue.css +1 -0
- package/dist/frontend-vue.js +82 -0
- package/dist/frontend-vue.umd.cjs +561 -0
- package/dist/index-P5Rwl_Dl.js +7263 -0
- package/dist/index.es-HlG3u0J5.js +3134 -0
- package/lib/_index.scss +14 -0
- package/lib/components/_index.scss +6 -0
- package/lib/components/collapsible/UluAccordion.vue +82 -0
- package/lib/components/collapsible/UluCollapsibleRegion.vue +278 -0
- package/lib/components/collapsible/UluDropdown.vue +42 -0
- package/lib/components/collapsible/UluModal.vue +384 -0
- package/lib/components/collapsible/UluOverflowPopover.vue +52 -0
- package/lib/components/collapsible/UluTab.vue +9 -0
- package/lib/components/collapsible/UluTabGroup.vue +31 -0
- package/lib/components/collapsible/UluTabList.vue +9 -0
- package/lib/components/collapsible/UluTabPanel.vue +9 -0
- package/lib/components/collapsible/UluTabPanels.vue +9 -0
- package/lib/components/elements/UluAlert.vue +81 -0
- package/lib/components/elements/UluBadge.vue +58 -0
- package/lib/components/elements/UluBadgeStack.vue +27 -0
- package/lib/components/elements/UluButton.vue +161 -0
- package/lib/components/elements/UluCallout.vue +30 -0
- package/lib/components/elements/UluCard.vue +241 -0
- package/lib/components/elements/UluDefinitionList.vue +40 -0
- package/lib/components/elements/UluExternalLink.vue +47 -0
- package/lib/components/elements/UluIcon.vue +108 -0
- package/lib/components/elements/UluList.vue +87 -0
- package/lib/components/elements/UluMain.vue +5 -0
- package/lib/components/elements/UluSpokeSpinner.vue +25 -0
- package/lib/components/elements/UluTag.vue +53 -0
- package/lib/components/forms/UluCheckboxMenu.vue +36 -0
- package/lib/components/forms/UluFileDisplay.vue +39 -0
- package/lib/components/forms/UluFormDropzone.vue +62 -0
- package/lib/components/forms/UluFormFile.vue +47 -0
- package/lib/components/forms/UluFormMessage.vue +20 -0
- package/lib/components/forms/UluFormSelect.vue +37 -0
- package/lib/components/forms/UluFormText.vue +32 -0
- package/lib/components/forms/UluSearchForm.vue +31 -0
- package/lib/components/index.js +54 -0
- package/lib/components/layout/UluAdaptiveLayout.vue +11 -0
- package/lib/components/layout/UluDataGrid.vue +41 -0
- package/lib/components/layout/UluTitleRail.vue +56 -0
- package/lib/components/layout/UluWhenBreakpoint.vue +86 -0
- package/lib/components/navigation/UluBreadcrumb.vue +72 -0
- package/lib/components/navigation/UluMenu.vue +105 -0
- package/lib/components/navigation/UluMenuStack.vue +49 -0
- package/lib/components/navigation/UluNavStrip.vue +48 -0
- package/lib/components/navigation/UluSkipLink.vue +5 -0
- package/lib/components/systems/facets/UluFacets.vue +380 -0
- package/lib/components/systems/facets/UluFacetsList.vue +39 -0
- package/lib/components/systems/facets/UluFacetsSearch.vue +67 -0
- package/lib/components/systems/facets/_facets.scss +64 -0
- package/lib/components/systems/index.js +17 -0
- package/lib/components/systems/scroll-anchors/UluScrollAnchors.vue +152 -0
- package/lib/components/systems/scroll-anchors/UluScrollAnchorsNav.vue +37 -0
- package/lib/components/systems/scroll-anchors/UluScrollAnchorsNavAnimated.vue +124 -0
- package/lib/components/systems/scroll-anchors/UluScrollAnchorsSection.vue +63 -0
- package/lib/components/systems/scroll-anchors/symbols.js +6 -0
- package/lib/components/systems/skeleton/UluShowSkeleton.vue +13 -0
- package/lib/components/systems/skeleton/UluSkeletonContent.vue +60 -0
- package/lib/components/systems/skeleton/UluSkeletonMedia.vue +11 -0
- package/lib/components/systems/skeleton/UluSkeletonTextInline.vue +9 -0
- package/lib/components/systems/slider/UluImageSlideShow.vue +75 -0
- package/lib/components/systems/slider/UluSlideShow.vue +331 -0
- package/lib/components/systems/slider/UluSlideShowSlide.vue +25 -0
- package/lib/components/systems/table-sticky/UluTableSticky.vue +793 -0
- package/lib/components/systems/table-sticky/UluTableStickyRows.vue +73 -0
- package/lib/components/systems/table-sticky/UluTableStickyTable.vue +237 -0
- package/lib/components/systems/table-sticky/_table-sticky.scss +185 -0
- package/lib/components/utils/UluCondText.vue +28 -0
- package/lib/components/utils/UluEmpty.vue +3 -0
- package/lib/components/utils/UluEmptyView.vue +3 -0
- package/lib/components/utils/UluPlaceholderImage.vue +53 -0
- package/lib/components/utils/UluPlaceholderText.vue +25 -0
- package/lib/components/utils/UluRouteAnnouncer.vue +83 -0
- package/lib/components/visualizations/UluAnimateNumber.vue +32 -0
- package/lib/components/visualizations/UluProgressBar.vue +94 -0
- package/lib/components/visualizations/UluProgressDonut.vue +97 -0
- package/lib/composables/index.js +10 -0
- package/lib/composables/useBreakpointManager.js +68 -0
- package/lib/composables/useIcon.js +62 -0
- package/lib/composables/useModifiers.js +93 -0
- package/lib/composables/useWindowResize.js +64 -0
- package/lib/index.js +10 -0
- package/lib/plugins/_index.scss +7 -0
- package/lib/plugins/breakpoints/index.js +47 -0
- package/lib/plugins/index.js +11 -0
- package/lib/plugins/modals/UluModalsDisplay.vue +59 -0
- package/lib/plugins/modals/api.js +76 -0
- package/lib/plugins/modals/index.js +60 -0
- package/lib/plugins/modals/useModals.js +9 -0
- package/lib/plugins/popovers/UluPopover.vue +189 -0
- package/lib/plugins/popovers/UluTooltipDisplay.vue +15 -0
- package/lib/plugins/popovers/UluTooltipPopover.vue +83 -0
- package/lib/plugins/popovers/defaults.js +108 -0
- package/lib/plugins/popovers/directive.js +95 -0
- package/lib/plugins/popovers/index.js +18 -0
- package/lib/plugins/popovers/manager.js +54 -0
- package/lib/plugins/popovers/useFollow.js +80 -0
- package/lib/plugins/popovers/utils.js +5 -0
- package/lib/plugins/toast/UluToast.vue +87 -0
- package/lib/plugins/toast/UluToastDisplay.vue +35 -0
- package/lib/plugins/toast/_toast.scss +198 -0
- package/lib/plugins/toast/defaults.js +30 -0
- package/lib/plugins/toast/index.js +17 -0
- package/lib/plugins/toast/store.js +71 -0
- package/lib/plugins/toast/useToast.js +18 -0
- package/lib/settings.js +119 -0
- package/lib/utils/dom.js +14 -0
- package/lib/utils/placeholder.js +6 -0
- package/lib/utils/vue-router.js +219 -0
- package/package.json +75 -0
|
@@ -0,0 +1,384 @@
|
|
|
1
|
+
|
|
2
|
+
<template>
|
|
3
|
+
<Teleport
|
|
4
|
+
:to="teleport === false ? null : teleport"
|
|
5
|
+
:disabled="teleport === false"
|
|
6
|
+
>
|
|
7
|
+
<dialog
|
|
8
|
+
class="modal"
|
|
9
|
+
:class="[resolvedModifiers, classes.container]"
|
|
10
|
+
:aria-labelledby="resolvedLabelledby"
|
|
11
|
+
:aria-describedby="describedby"
|
|
12
|
+
ref="container"
|
|
13
|
+
:style="{ width: containerWidth }"
|
|
14
|
+
@cancel.prevent="close"
|
|
15
|
+
@close="handleDialogCloseEvent"
|
|
16
|
+
@click="handleClick"
|
|
17
|
+
@toggle="handleToggle"
|
|
18
|
+
>
|
|
19
|
+
<header
|
|
20
|
+
v-if="hasHeader"
|
|
21
|
+
class="modal__header"
|
|
22
|
+
:class="classes.header"
|
|
23
|
+
>
|
|
24
|
+
<h2 class="modal__title" :class="classes.title" :id="titleId">
|
|
25
|
+
<slot name="title" :close="close">
|
|
26
|
+
<UluIcon
|
|
27
|
+
v-if="titleIcon"
|
|
28
|
+
class="modal__title-icon"
|
|
29
|
+
:definition="titleIcon"
|
|
30
|
+
/>
|
|
31
|
+
<span class="modal__title-text">{{ title }}</span>
|
|
32
|
+
</slot>
|
|
33
|
+
</h2>
|
|
34
|
+
<button class="modal__close" aria-label="Close modal" @click="close" autofocus>
|
|
35
|
+
<slot name="closeIcon">
|
|
36
|
+
<UluIcon
|
|
37
|
+
class="modal__close-icon"
|
|
38
|
+
type="close"
|
|
39
|
+
:definition="closeIcon"
|
|
40
|
+
/>
|
|
41
|
+
</slot>
|
|
42
|
+
</button>
|
|
43
|
+
</header>
|
|
44
|
+
<div
|
|
45
|
+
class="modal__body"
|
|
46
|
+
:class="classes.body"
|
|
47
|
+
>
|
|
48
|
+
<slot :close="close"/>
|
|
49
|
+
</div>
|
|
50
|
+
<div
|
|
51
|
+
v-if="$slots.footer"
|
|
52
|
+
class="site-modal__footer"
|
|
53
|
+
:class="classes.footer"
|
|
54
|
+
>
|
|
55
|
+
<slot name="footer" :close="close"/>
|
|
56
|
+
</div>
|
|
57
|
+
<button v-if="resizerEnabled" class="modal__resizer" ref="resizer" type="button">
|
|
58
|
+
<slot name="resizerIcon">
|
|
59
|
+
<UluIcon class="modal__resizer-icon" :type="resizerIconType" :definition="resizerIcon" />
|
|
60
|
+
</slot>
|
|
61
|
+
</button>
|
|
62
|
+
</dialog>
|
|
63
|
+
</Teleport>
|
|
64
|
+
</template>
|
|
65
|
+
|
|
66
|
+
<script>
|
|
67
|
+
import { useSlots, computed } from "vue";
|
|
68
|
+
import UluIcon from "../elements/UluIcon.vue";
|
|
69
|
+
import { useModifiers } from "../../composables/useModifiers.js";
|
|
70
|
+
import { wasClickOutside, preventScroll as setupPreventScroll } from "@ulu/utils/browser/dom.js";
|
|
71
|
+
import { Resizer } from "@ulu/frontend/js/ui/resizer.js";
|
|
72
|
+
|
|
73
|
+
let modalCount = 0;
|
|
74
|
+
|
|
75
|
+
export default {
|
|
76
|
+
name: "UluModal",
|
|
77
|
+
components: {
|
|
78
|
+
UluIcon
|
|
79
|
+
},
|
|
80
|
+
emits: ['update:modelValue', 'close', 'open'],
|
|
81
|
+
props: {
|
|
82
|
+
/**
|
|
83
|
+
* Controls the visibility of the modal (for v-model).
|
|
84
|
+
*/
|
|
85
|
+
modelValue: Boolean,
|
|
86
|
+
/**
|
|
87
|
+
* Target for Vue's Teleport. Defaults to 'body'.
|
|
88
|
+
* Set to `false` to disable teleporting (modal renders inline).
|
|
89
|
+
* Set to `null` or `undefined` for `body` fallback with disabled as false.
|
|
90
|
+
*/
|
|
91
|
+
teleport: {
|
|
92
|
+
type: [String, Boolean, Object], // Allow string for target selector, or false to disable, or object (Dome node)
|
|
93
|
+
default: 'body'
|
|
94
|
+
},
|
|
95
|
+
/**
|
|
96
|
+
* When open and not non-modal, the body is prevented from scrolling (defaults to true).
|
|
97
|
+
*/
|
|
98
|
+
preventScroll: {
|
|
99
|
+
type: Boolean,
|
|
100
|
+
default: true
|
|
101
|
+
},
|
|
102
|
+
/**
|
|
103
|
+
* Compensate for layout shift when preventing scroll. Which adds padding equal to scrollbars
|
|
104
|
+
* width while dialog is open
|
|
105
|
+
*/
|
|
106
|
+
preventScrollShift: {
|
|
107
|
+
type: Boolean,
|
|
108
|
+
default: true
|
|
109
|
+
},
|
|
110
|
+
/**
|
|
111
|
+
* Use non-modal interface for dialog
|
|
112
|
+
*/
|
|
113
|
+
nonModal: Boolean,
|
|
114
|
+
/**
|
|
115
|
+
* Close modal on click outside
|
|
116
|
+
*/
|
|
117
|
+
clickOutsideCloses: {
|
|
118
|
+
type: Boolean,
|
|
119
|
+
default: true
|
|
120
|
+
},
|
|
121
|
+
/**
|
|
122
|
+
* Enable resizer
|
|
123
|
+
*/
|
|
124
|
+
allowResize: Boolean,
|
|
125
|
+
/**
|
|
126
|
+
* Position (any position that modal.scss supports)
|
|
127
|
+
*/
|
|
128
|
+
position: {
|
|
129
|
+
type: String,
|
|
130
|
+
default: "center"
|
|
131
|
+
},
|
|
132
|
+
/**
|
|
133
|
+
* If `true`, the modal body will fill the available space.
|
|
134
|
+
*/
|
|
135
|
+
bodyFills: Boolean,
|
|
136
|
+
/**
|
|
137
|
+
* If `true`, no backdrop will be displayed behind the modal
|
|
138
|
+
*/
|
|
139
|
+
noBackdrop: Boolean,
|
|
140
|
+
/**
|
|
141
|
+
* If `true`, the modal will not have a minimum height
|
|
142
|
+
*/
|
|
143
|
+
noMinHeight: Boolean,
|
|
144
|
+
/**
|
|
145
|
+
* Set aria-labelledby by element id (to add accessible label)
|
|
146
|
+
* - Use this if you are not using the default modal title (custom titles)
|
|
147
|
+
*/
|
|
148
|
+
labelledby: String,
|
|
149
|
+
/**
|
|
150
|
+
* Set aria-describedby by element id (to add accessible description)
|
|
151
|
+
* - This is usually content you passed into the modal body (paragraph/etc)
|
|
152
|
+
*/
|
|
153
|
+
describedby: String,
|
|
154
|
+
/**
|
|
155
|
+
* Text for modal title in header (can use title slot as well for complex markup), if not passed the header will be omitted
|
|
156
|
+
*/
|
|
157
|
+
title: String,
|
|
158
|
+
/**
|
|
159
|
+
* Optional icon for before title (uses UluIcon interface)
|
|
160
|
+
*/
|
|
161
|
+
titleIcon: String,
|
|
162
|
+
/**
|
|
163
|
+
* Default icon for resizer
|
|
164
|
+
*/
|
|
165
|
+
resizerIcon: String,
|
|
166
|
+
/**
|
|
167
|
+
* Default icon for close button (uses UluIcon interface)
|
|
168
|
+
*/
|
|
169
|
+
closeIcon: String,
|
|
170
|
+
/**
|
|
171
|
+
* Classes for elements ({ container, header, title, body, footer })
|
|
172
|
+
* - Any valid class binding value per element
|
|
173
|
+
*/
|
|
174
|
+
classes: {
|
|
175
|
+
type: Object,
|
|
176
|
+
default: () => ({})
|
|
177
|
+
},
|
|
178
|
+
/**
|
|
179
|
+
* Modifiers (to add any modifier classes based on base class [ie. 'tertiary'])
|
|
180
|
+
*/
|
|
181
|
+
modifiers: [String, Array]
|
|
182
|
+
},
|
|
183
|
+
data() {
|
|
184
|
+
++modalCount;
|
|
185
|
+
return {
|
|
186
|
+
containerWidth: null,
|
|
187
|
+
titleId: `ulu-modal-${ modalCount }-title`,
|
|
188
|
+
bodyOverflowValue: null,
|
|
189
|
+
bodyPaddingRightValue: null,
|
|
190
|
+
isResizing: false,
|
|
191
|
+
}
|
|
192
|
+
},
|
|
193
|
+
setup(props) {
|
|
194
|
+
const slots = useSlots(); // Access slots via useSlots() helper
|
|
195
|
+
|
|
196
|
+
// Note: These two computed need to be defined in setup since their used in internalModifiers
|
|
197
|
+
const hasHeader = computed(() => props.title || slots.title);
|
|
198
|
+
/**
|
|
199
|
+
* Flag for if resizer script should be enabled
|
|
200
|
+
* - Resizer only available for left and right
|
|
201
|
+
*/
|
|
202
|
+
const resizerEnabled = computed(() => {
|
|
203
|
+
const { allowResize, position } = props;
|
|
204
|
+
if (!allowResize || !position) return;
|
|
205
|
+
|
|
206
|
+
const resizablePositions = ["left", "right", "center"];
|
|
207
|
+
if (resizablePositions.includes(position)) {
|
|
208
|
+
return true;
|
|
209
|
+
} else {
|
|
210
|
+
console.warn(`Passed invalid position for resize (${ position }), use ${ resizablePositions.join(", ") }`);
|
|
211
|
+
return false;
|
|
212
|
+
}
|
|
213
|
+
});
|
|
214
|
+
|
|
215
|
+
const resizerIconType = computed(() => {
|
|
216
|
+
return props.position === 'center' ? 'resizeBoth' : 'resizeHorizontal';
|
|
217
|
+
});
|
|
218
|
+
|
|
219
|
+
// Define the internal modifiers object as a computed property (so it can react to changes)
|
|
220
|
+
const internalModifiers = computed(() => ({
|
|
221
|
+
[props.position]: props.position,
|
|
222
|
+
"resize": props.allowResize,
|
|
223
|
+
"no-resize": !props.allowResize,
|
|
224
|
+
"no-header": !hasHeader.value,
|
|
225
|
+
"body-fills": props.bodyFills,
|
|
226
|
+
"no-backdrop": props.noBackdrop,
|
|
227
|
+
"no-min-height": props.noMinHeight,
|
|
228
|
+
"non-modal": props.nonModal,
|
|
229
|
+
"resizer-active": resizerEnabled.value,
|
|
230
|
+
}));
|
|
231
|
+
|
|
232
|
+
const { resolvedModifiers } = useModifiers({
|
|
233
|
+
props: props,
|
|
234
|
+
baseClass: "modal",
|
|
235
|
+
internal: internalModifiers
|
|
236
|
+
});
|
|
237
|
+
|
|
238
|
+
return {
|
|
239
|
+
resolvedModifiers,
|
|
240
|
+
hasHeader,
|
|
241
|
+
resizerEnabled,
|
|
242
|
+
resizerIconType
|
|
243
|
+
};
|
|
244
|
+
},
|
|
245
|
+
computed: {
|
|
246
|
+
resolvedLabelledby() {
|
|
247
|
+
const { labelledby, titleId } = this;
|
|
248
|
+
return labelledby ? labelledby : titleId;
|
|
249
|
+
}
|
|
250
|
+
},
|
|
251
|
+
watch: {
|
|
252
|
+
modelValue: {
|
|
253
|
+
// So that it runs on mount (if modelValue is initially true)
|
|
254
|
+
immediate: true,
|
|
255
|
+
handler(newValue) {
|
|
256
|
+
// Use nextTick to ensure the dialog element is in the DOM before calling showModal
|
|
257
|
+
this.$nextTick(() => {
|
|
258
|
+
const { container } = this.$refs;
|
|
259
|
+
if (newValue) {
|
|
260
|
+
container[this.nonModal ? "show" : "showModal"]();
|
|
261
|
+
this.$emit("open");
|
|
262
|
+
} else {
|
|
263
|
+
container.close();
|
|
264
|
+
}
|
|
265
|
+
});
|
|
266
|
+
}
|
|
267
|
+
},
|
|
268
|
+
resizerEnabled: {
|
|
269
|
+
immediate: false, // Don't run on initial mount, as setupResizer is called in mounted
|
|
270
|
+
handler(newValue) {
|
|
271
|
+
if (newValue) {
|
|
272
|
+
this.$nextTick(() => {
|
|
273
|
+
this.setupResizer();
|
|
274
|
+
});
|
|
275
|
+
} else {
|
|
276
|
+
this.destroyResizer();
|
|
277
|
+
}
|
|
278
|
+
}
|
|
279
|
+
},
|
|
280
|
+
position(newValue, oldValue) {
|
|
281
|
+
if (newValue !== oldValue) {
|
|
282
|
+
this.destroyResizer();
|
|
283
|
+
this.$nextTick(() => {
|
|
284
|
+
this.setupResizer();
|
|
285
|
+
});
|
|
286
|
+
}
|
|
287
|
+
}
|
|
288
|
+
},
|
|
289
|
+
methods: {
|
|
290
|
+
close() {
|
|
291
|
+
// Emit 'update:modelValue' to inform parent to set modelValue to false
|
|
292
|
+
// This will trigger the watch handler above to call this.$refs.container.close()
|
|
293
|
+
this.$emit("update:modelValue", false);
|
|
294
|
+
this.$emit("close"); // Also emit a generic 'close' event for convenience
|
|
295
|
+
},
|
|
296
|
+
handleDialogCloseEvent() {
|
|
297
|
+
// Ensure modelValue is false, primarily for scenarios where dialog is closed
|
|
298
|
+
// by user agent (e.g., form submission within dialog) or direct native API call
|
|
299
|
+
// that bypasses `this.close()` or Escape key.
|
|
300
|
+
if (this.modelValue) { // Only emit if it was open based on modelValue
|
|
301
|
+
this.$emit("update:modelValue", false);
|
|
302
|
+
this.$emit("close");
|
|
303
|
+
}
|
|
304
|
+
},
|
|
305
|
+
handleClick(event) {
|
|
306
|
+
if (this.clickOutsideCloses && !this.isResizing) {
|
|
307
|
+
const { target } = event;
|
|
308
|
+
const { container } = this.$refs;
|
|
309
|
+
if (target === container && wasClickOutside(container, event)) {
|
|
310
|
+
this.close();
|
|
311
|
+
}
|
|
312
|
+
}
|
|
313
|
+
},
|
|
314
|
+
setupPreventScroll() {
|
|
315
|
+
const { body } = document;
|
|
316
|
+
this.bodyOverflowValue = body.style.overflow;
|
|
317
|
+
this.bodyPaddingRightValue = body.style.paddingRight;
|
|
318
|
+
},
|
|
319
|
+
destroyPreventScroll() {
|
|
320
|
+
if (this.restoreScroll) {
|
|
321
|
+
this.restoreScroll();
|
|
322
|
+
}
|
|
323
|
+
},
|
|
324
|
+
handleToggle(event) {
|
|
325
|
+
if (!this.nonModal && this.preventScroll) {
|
|
326
|
+
const { preventScrollShift: preventShift } = this;
|
|
327
|
+
const isOpen = event.newState === "open";
|
|
328
|
+
if (isOpen) {
|
|
329
|
+
this.restoreScroll = setupPreventScroll({ preventShift });
|
|
330
|
+
} else {
|
|
331
|
+
this.destroyPreventScroll();
|
|
332
|
+
}
|
|
333
|
+
}
|
|
334
|
+
},
|
|
335
|
+
setupResizer() {
|
|
336
|
+
const { position, resizerEnabled } = this;
|
|
337
|
+
if (resizerEnabled) {
|
|
338
|
+
const { container, resizer } = this.$refs;
|
|
339
|
+
const options = position === "center" ?
|
|
340
|
+
{ fromX: "right", fromY: "bottom", multiplier: 2 } :
|
|
341
|
+
{ fromX: position === "right" ? "left" : "right" };
|
|
342
|
+
this.resizerInstance = new Resizer(container, resizer, options);
|
|
343
|
+
this.handleResizerStart = () => {
|
|
344
|
+
this.isResizing = true;
|
|
345
|
+
};
|
|
346
|
+
this.handleResizerEnd = () => {
|
|
347
|
+
// After click has ended (next in event loop)
|
|
348
|
+
setTimeout(() => { this.isResizing = false; }, 0)
|
|
349
|
+
};
|
|
350
|
+
container.addEventListener("ulu:resizer:start", this.handleResizerStart);
|
|
351
|
+
container.addEventListener("ulu:resizer:end", this.handleResizerEnd);
|
|
352
|
+
}
|
|
353
|
+
},
|
|
354
|
+
destroyResizer() {
|
|
355
|
+
const { container } = this.$refs;
|
|
356
|
+
if (this.resizerInstance) {
|
|
357
|
+
this.resizerInstance.destroy();
|
|
358
|
+
this.resizerInstance = null;
|
|
359
|
+
}
|
|
360
|
+
if (this.handleResizerStart) {
|
|
361
|
+
container.removeEventListener("ulu:resizer:start", this.handleResizerStart);
|
|
362
|
+
}
|
|
363
|
+
if (this.handleResizerEnd) {
|
|
364
|
+
container.removeEventListener("ulu:resizer:end", this.handleResizerEnd);
|
|
365
|
+
}
|
|
366
|
+
}
|
|
367
|
+
},
|
|
368
|
+
mounted() {
|
|
369
|
+
++modalCount;
|
|
370
|
+
if (this.preventScroll) {
|
|
371
|
+
this.setupPreventScroll();
|
|
372
|
+
}
|
|
373
|
+
this.setupResizer();
|
|
374
|
+
},
|
|
375
|
+
beforeUnmount() {
|
|
376
|
+
const { container } = this.$refs;
|
|
377
|
+
if (container && container.open) {
|
|
378
|
+
container.close();
|
|
379
|
+
}
|
|
380
|
+
this.destroyPreventScroll();
|
|
381
|
+
this.destroyResizer();
|
|
382
|
+
}
|
|
383
|
+
};
|
|
384
|
+
</script>
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<div class="layout-flex-baseline">
|
|
3
|
+
<div class="type-truncate" ref="text">
|
|
4
|
+
<slot/>
|
|
5
|
+
</div>
|
|
6
|
+
<UluPopover
|
|
7
|
+
v-if="isOverflown && !resizing"
|
|
8
|
+
triggerAlt="Show Full Text"
|
|
9
|
+
size="large"
|
|
10
|
+
>
|
|
11
|
+
<template #trigger>
|
|
12
|
+
<UluIcon type="ellipsis" :definition="triggerIcon"/>
|
|
13
|
+
</template>
|
|
14
|
+
<template #content>
|
|
15
|
+
<div class="type-word-break">
|
|
16
|
+
<slot/>
|
|
17
|
+
</div>
|
|
18
|
+
</template>
|
|
19
|
+
</UluPopover>
|
|
20
|
+
</div>
|
|
21
|
+
</template>
|
|
22
|
+
|
|
23
|
+
<script setup>
|
|
24
|
+
import { ref, nextTick, onMounted, onUnmounted } from "vue";
|
|
25
|
+
import { useWindowResize } from "../../composables";
|
|
26
|
+
|
|
27
|
+
import UluPopover from "../../plugins/popovers/UluPopover.vue";
|
|
28
|
+
import UluIcon from "../elements/UluIcon.vue";
|
|
29
|
+
|
|
30
|
+
defineProps({
|
|
31
|
+
/**
|
|
32
|
+
* Default icon for overflow popover trigger
|
|
33
|
+
*/
|
|
34
|
+
triggerIcon: String
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
const { resizing, onResizeEnd } = useWindowResize();
|
|
38
|
+
const text = ref(null);
|
|
39
|
+
const isOverflown = ref(false);
|
|
40
|
+
/**
|
|
41
|
+
* Function checks if text element is overflown
|
|
42
|
+
*/
|
|
43
|
+
const update = () => {
|
|
44
|
+
nextTick(() => {
|
|
45
|
+
const element = text.value;
|
|
46
|
+
isOverflown.value = element.offsetWidth < element.scrollWidth;
|
|
47
|
+
});
|
|
48
|
+
};
|
|
49
|
+
const removeResize = onResizeEnd(update);
|
|
50
|
+
onMounted(update);
|
|
51
|
+
onUnmounted(removeResize);
|
|
52
|
+
</script>
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<TabGroup v-slot="slotProps" :defaultIndex="defaultIndex" :vertical="vertical">
|
|
3
|
+
<div
|
|
4
|
+
class="tabs"
|
|
5
|
+
:class="{
|
|
6
|
+
'tabs--vertical' : vertical
|
|
7
|
+
}"
|
|
8
|
+
>
|
|
9
|
+
<slot v-bind="slotProps"/>
|
|
10
|
+
</div>
|
|
11
|
+
</TabGroup>
|
|
12
|
+
</template>
|
|
13
|
+
|
|
14
|
+
<script setup>
|
|
15
|
+
import { TabGroup } from "@headlessui/vue";
|
|
16
|
+
|
|
17
|
+
defineOptions({
|
|
18
|
+
inheritAttrs: false
|
|
19
|
+
});
|
|
20
|
+
|
|
21
|
+
defineProps({
|
|
22
|
+
/**
|
|
23
|
+
* Active tab index by default
|
|
24
|
+
*/
|
|
25
|
+
defaultIndex: Number,
|
|
26
|
+
/**
|
|
27
|
+
* Whether or not to use vertical layout
|
|
28
|
+
*/
|
|
29
|
+
vertical: Boolean
|
|
30
|
+
});
|
|
31
|
+
</script>
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<div class="callout" :class="resolvedModifiers">
|
|
3
|
+
<div class="layout-flex">
|
|
4
|
+
<UluIcon
|
|
5
|
+
class="type-large margin-right-small"
|
|
6
|
+
:class="`color-${ type }`"
|
|
7
|
+
:type="type"
|
|
8
|
+
:definition="icon"
|
|
9
|
+
/>
|
|
10
|
+
<div class="type-small">
|
|
11
|
+
<div>
|
|
12
|
+
<slot name="title"><strong>{{ title }}</strong></slot>
|
|
13
|
+
</div>
|
|
14
|
+
<div>
|
|
15
|
+
<slot name="description">{{ description }}</slot>
|
|
16
|
+
</div>
|
|
17
|
+
</div>
|
|
18
|
+
<div v-if="$slots.action" class="margin-left-auto align-self-center">
|
|
19
|
+
<slot name="action"/>
|
|
20
|
+
</div>
|
|
21
|
+
</div>
|
|
22
|
+
</div>
|
|
23
|
+
</template>
|
|
24
|
+
|
|
25
|
+
<script>
|
|
26
|
+
import { computed } from "vue";
|
|
27
|
+
import UluButton from "./UluButton.vue";
|
|
28
|
+
import UluIcon from "./UluIcon.vue";
|
|
29
|
+
import { useModifiers } from "../../composables/useModifiers.js";
|
|
30
|
+
/**
|
|
31
|
+
* Callout with alert layout
|
|
32
|
+
*/
|
|
33
|
+
export default {
|
|
34
|
+
name: "UluAlert",
|
|
35
|
+
components: {
|
|
36
|
+
UluButton,
|
|
37
|
+
UluIcon
|
|
38
|
+
},
|
|
39
|
+
props: {
|
|
40
|
+
/**
|
|
41
|
+
* Alert Title
|
|
42
|
+
*/
|
|
43
|
+
title: String,
|
|
44
|
+
/**
|
|
45
|
+
* Alert description
|
|
46
|
+
*/
|
|
47
|
+
description: String,
|
|
48
|
+
/**
|
|
49
|
+
* Pass specific icon definition, else it will resolve based on common types
|
|
50
|
+
*/
|
|
51
|
+
icon: String,
|
|
52
|
+
/**
|
|
53
|
+
* Error, warning, info, success etc (must have these callout modifiers setup and this is used for type color [ie. color-error])
|
|
54
|
+
*/
|
|
55
|
+
type: {
|
|
56
|
+
type: String,
|
|
57
|
+
default: "danger"
|
|
58
|
+
},
|
|
59
|
+
/**
|
|
60
|
+
* Compact callout style
|
|
61
|
+
*/
|
|
62
|
+
compact: Boolean,
|
|
63
|
+
/**
|
|
64
|
+
* Modifiers (to add any modifier classes based on base class [ie. 'tertiary'])
|
|
65
|
+
*/
|
|
66
|
+
modifiers: [String, Array]
|
|
67
|
+
},
|
|
68
|
+
setup(props) {
|
|
69
|
+
const { resolvedModifiers } = useModifiers({
|
|
70
|
+
props,
|
|
71
|
+
baseClass: "callout",
|
|
72
|
+
internal: computed(() => ({
|
|
73
|
+
[props.type] : props.type,
|
|
74
|
+
"compact": props.compact,
|
|
75
|
+
}))
|
|
76
|
+
});
|
|
77
|
+
|
|
78
|
+
return { resolvedModifiers };
|
|
79
|
+
}
|
|
80
|
+
};
|
|
81
|
+
</script>
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<component
|
|
3
|
+
class="badge"
|
|
4
|
+
:class="[
|
|
5
|
+
size ? `badge--${ size }` : null,
|
|
6
|
+
type ? `badge--${ type }` : null,
|
|
7
|
+
{ 'badge--clickable' : isInteractive }
|
|
8
|
+
]"
|
|
9
|
+
:is="element"
|
|
10
|
+
:to="to"
|
|
11
|
+
:href="href"
|
|
12
|
+
@click="click"
|
|
13
|
+
>
|
|
14
|
+
<span class="badge__inner" :class="{ 'skeleton__background-color' : skeleton }">
|
|
15
|
+
<!-- If just text user should use prop -->
|
|
16
|
+
<span v-if="text" :aria-hidden="alt ? 'true' : null">
|
|
17
|
+
{{ text }}
|
|
18
|
+
</span>
|
|
19
|
+
|
|
20
|
+
<!-- Else display as is, slot should contain an HTML element / component
|
|
21
|
+
cannot be just a text node, if text node use text prop or wrap text in html element -->
|
|
22
|
+
<slot v-else />
|
|
23
|
+
<!-- When using text (abbreviations, etc) -->
|
|
24
|
+
<span v-if="alt" class="hidden-visually">{{ alt }}</span>
|
|
25
|
+
</span>
|
|
26
|
+
</component>
|
|
27
|
+
</template>
|
|
28
|
+
|
|
29
|
+
<script>
|
|
30
|
+
import { RouterLink } from "vue-router";
|
|
31
|
+
export default {
|
|
32
|
+
name: "UluBadge",
|
|
33
|
+
props: {
|
|
34
|
+
skeleton: Boolean,
|
|
35
|
+
size: String,
|
|
36
|
+
text: String,
|
|
37
|
+
alt: String,
|
|
38
|
+
type: String,
|
|
39
|
+
click: Function,
|
|
40
|
+
to: [Object, String],
|
|
41
|
+
href: String,
|
|
42
|
+
},
|
|
43
|
+
computed: {
|
|
44
|
+
isInteractive() {
|
|
45
|
+
return Boolean(this.to || this.click);
|
|
46
|
+
},
|
|
47
|
+
element() {
|
|
48
|
+
const { click, to, href } = this;
|
|
49
|
+
/* eslint-disable */
|
|
50
|
+
return click ? 'button' :
|
|
51
|
+
to ? RouterLink :
|
|
52
|
+
href ? 'a' :
|
|
53
|
+
'span';
|
|
54
|
+
/* eslint-enable */
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
};
|
|
58
|
+
</script>
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<ul class="badge-stack">
|
|
3
|
+
<li
|
|
4
|
+
class="badge-stack__item"
|
|
5
|
+
v-for="(item, index) in items"
|
|
6
|
+
:key="index"
|
|
7
|
+
>
|
|
8
|
+
<UluBadge v-bind="item" />
|
|
9
|
+
</li>
|
|
10
|
+
</ul>
|
|
11
|
+
</template>
|
|
12
|
+
|
|
13
|
+
<script>
|
|
14
|
+
import UluBadge from "./UluBadge.vue";
|
|
15
|
+
export default {
|
|
16
|
+
name: 'UluBadgeStack',
|
|
17
|
+
components: {
|
|
18
|
+
UluBadge
|
|
19
|
+
},
|
|
20
|
+
props: {
|
|
21
|
+
/**
|
|
22
|
+
* Array of props for each badge
|
|
23
|
+
*/
|
|
24
|
+
items: Array
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
</script>
|