@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.
Files changed (115) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +9 -0
  3. package/dist/breakpoints-ClT9bfZm.js +211 -0
  4. package/dist/frontend-vue.css +1 -0
  5. package/dist/frontend-vue.js +82 -0
  6. package/dist/frontend-vue.umd.cjs +561 -0
  7. package/dist/index-P5Rwl_Dl.js +7263 -0
  8. package/dist/index.es-HlG3u0J5.js +3134 -0
  9. package/lib/_index.scss +14 -0
  10. package/lib/components/_index.scss +6 -0
  11. package/lib/components/collapsible/UluAccordion.vue +82 -0
  12. package/lib/components/collapsible/UluCollapsibleRegion.vue +278 -0
  13. package/lib/components/collapsible/UluDropdown.vue +42 -0
  14. package/lib/components/collapsible/UluModal.vue +384 -0
  15. package/lib/components/collapsible/UluOverflowPopover.vue +52 -0
  16. package/lib/components/collapsible/UluTab.vue +9 -0
  17. package/lib/components/collapsible/UluTabGroup.vue +31 -0
  18. package/lib/components/collapsible/UluTabList.vue +9 -0
  19. package/lib/components/collapsible/UluTabPanel.vue +9 -0
  20. package/lib/components/collapsible/UluTabPanels.vue +9 -0
  21. package/lib/components/elements/UluAlert.vue +81 -0
  22. package/lib/components/elements/UluBadge.vue +58 -0
  23. package/lib/components/elements/UluBadgeStack.vue +27 -0
  24. package/lib/components/elements/UluButton.vue +161 -0
  25. package/lib/components/elements/UluCallout.vue +30 -0
  26. package/lib/components/elements/UluCard.vue +241 -0
  27. package/lib/components/elements/UluDefinitionList.vue +40 -0
  28. package/lib/components/elements/UluExternalLink.vue +47 -0
  29. package/lib/components/elements/UluIcon.vue +108 -0
  30. package/lib/components/elements/UluList.vue +87 -0
  31. package/lib/components/elements/UluMain.vue +5 -0
  32. package/lib/components/elements/UluSpokeSpinner.vue +25 -0
  33. package/lib/components/elements/UluTag.vue +53 -0
  34. package/lib/components/forms/UluCheckboxMenu.vue +36 -0
  35. package/lib/components/forms/UluFileDisplay.vue +39 -0
  36. package/lib/components/forms/UluFormDropzone.vue +62 -0
  37. package/lib/components/forms/UluFormFile.vue +47 -0
  38. package/lib/components/forms/UluFormMessage.vue +20 -0
  39. package/lib/components/forms/UluFormSelect.vue +37 -0
  40. package/lib/components/forms/UluFormText.vue +32 -0
  41. package/lib/components/forms/UluSearchForm.vue +31 -0
  42. package/lib/components/index.js +54 -0
  43. package/lib/components/layout/UluAdaptiveLayout.vue +11 -0
  44. package/lib/components/layout/UluDataGrid.vue +41 -0
  45. package/lib/components/layout/UluTitleRail.vue +56 -0
  46. package/lib/components/layout/UluWhenBreakpoint.vue +86 -0
  47. package/lib/components/navigation/UluBreadcrumb.vue +72 -0
  48. package/lib/components/navigation/UluMenu.vue +105 -0
  49. package/lib/components/navigation/UluMenuStack.vue +49 -0
  50. package/lib/components/navigation/UluNavStrip.vue +48 -0
  51. package/lib/components/navigation/UluSkipLink.vue +5 -0
  52. package/lib/components/systems/facets/UluFacets.vue +380 -0
  53. package/lib/components/systems/facets/UluFacetsList.vue +39 -0
  54. package/lib/components/systems/facets/UluFacetsSearch.vue +67 -0
  55. package/lib/components/systems/facets/_facets.scss +64 -0
  56. package/lib/components/systems/index.js +17 -0
  57. package/lib/components/systems/scroll-anchors/UluScrollAnchors.vue +152 -0
  58. package/lib/components/systems/scroll-anchors/UluScrollAnchorsNav.vue +37 -0
  59. package/lib/components/systems/scroll-anchors/UluScrollAnchorsNavAnimated.vue +124 -0
  60. package/lib/components/systems/scroll-anchors/UluScrollAnchorsSection.vue +63 -0
  61. package/lib/components/systems/scroll-anchors/symbols.js +6 -0
  62. package/lib/components/systems/skeleton/UluShowSkeleton.vue +13 -0
  63. package/lib/components/systems/skeleton/UluSkeletonContent.vue +60 -0
  64. package/lib/components/systems/skeleton/UluSkeletonMedia.vue +11 -0
  65. package/lib/components/systems/skeleton/UluSkeletonTextInline.vue +9 -0
  66. package/lib/components/systems/slider/UluImageSlideShow.vue +75 -0
  67. package/lib/components/systems/slider/UluSlideShow.vue +331 -0
  68. package/lib/components/systems/slider/UluSlideShowSlide.vue +25 -0
  69. package/lib/components/systems/table-sticky/UluTableSticky.vue +793 -0
  70. package/lib/components/systems/table-sticky/UluTableStickyRows.vue +73 -0
  71. package/lib/components/systems/table-sticky/UluTableStickyTable.vue +237 -0
  72. package/lib/components/systems/table-sticky/_table-sticky.scss +185 -0
  73. package/lib/components/utils/UluCondText.vue +28 -0
  74. package/lib/components/utils/UluEmpty.vue +3 -0
  75. package/lib/components/utils/UluEmptyView.vue +3 -0
  76. package/lib/components/utils/UluPlaceholderImage.vue +53 -0
  77. package/lib/components/utils/UluPlaceholderText.vue +25 -0
  78. package/lib/components/utils/UluRouteAnnouncer.vue +83 -0
  79. package/lib/components/visualizations/UluAnimateNumber.vue +32 -0
  80. package/lib/components/visualizations/UluProgressBar.vue +94 -0
  81. package/lib/components/visualizations/UluProgressDonut.vue +97 -0
  82. package/lib/composables/index.js +10 -0
  83. package/lib/composables/useBreakpointManager.js +68 -0
  84. package/lib/composables/useIcon.js +62 -0
  85. package/lib/composables/useModifiers.js +93 -0
  86. package/lib/composables/useWindowResize.js +64 -0
  87. package/lib/index.js +10 -0
  88. package/lib/plugins/_index.scss +7 -0
  89. package/lib/plugins/breakpoints/index.js +47 -0
  90. package/lib/plugins/index.js +11 -0
  91. package/lib/plugins/modals/UluModalsDisplay.vue +59 -0
  92. package/lib/plugins/modals/api.js +76 -0
  93. package/lib/plugins/modals/index.js +60 -0
  94. package/lib/plugins/modals/useModals.js +9 -0
  95. package/lib/plugins/popovers/UluPopover.vue +189 -0
  96. package/lib/plugins/popovers/UluTooltipDisplay.vue +15 -0
  97. package/lib/plugins/popovers/UluTooltipPopover.vue +83 -0
  98. package/lib/plugins/popovers/defaults.js +108 -0
  99. package/lib/plugins/popovers/directive.js +95 -0
  100. package/lib/plugins/popovers/index.js +18 -0
  101. package/lib/plugins/popovers/manager.js +54 -0
  102. package/lib/plugins/popovers/useFollow.js +80 -0
  103. package/lib/plugins/popovers/utils.js +5 -0
  104. package/lib/plugins/toast/UluToast.vue +87 -0
  105. package/lib/plugins/toast/UluToastDisplay.vue +35 -0
  106. package/lib/plugins/toast/_toast.scss +198 -0
  107. package/lib/plugins/toast/defaults.js +30 -0
  108. package/lib/plugins/toast/index.js +17 -0
  109. package/lib/plugins/toast/store.js +71 -0
  110. package/lib/plugins/toast/useToast.js +18 -0
  111. package/lib/settings.js +119 -0
  112. package/lib/utils/dom.js +14 -0
  113. package/lib/utils/placeholder.js +6 -0
  114. package/lib/utils/vue-router.js +219 -0
  115. 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,9 @@
1
+ <template>
2
+ <Tab v-slot="slotProps">
3
+ <slot v-bind="slotProps"/>
4
+ </Tab>
5
+ </template>
6
+
7
+ <script setup>
8
+ import { Tab } from "@headlessui/vue";
9
+ </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,9 @@
1
+ <template>
2
+ <TabList class="tabs__tablist">
3
+ <slot/>
4
+ </TabList>
5
+ </template>
6
+
7
+ <script setup>
8
+ import { TabList } from "@headlessui/vue";
9
+ </script>
@@ -0,0 +1,9 @@
1
+ <template>
2
+ <TabPanel v-slot="slotProps">
3
+ <slot v-bind="slotProps"/>
4
+ </TabPanel>
5
+ </template>
6
+
7
+ <script setup>
8
+ import { TabPanel } from "@headlessui/vue";
9
+ </script>
@@ -0,0 +1,9 @@
1
+ <template>
2
+ <TabPanels v-slot="slotProps">
3
+ <slot v-bind="slotProps"/>
4
+ </TabPanels>
5
+ </template>
6
+
7
+ <script setup>
8
+ import { TabPanels } from "@headlessui/vue";
9
+ </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>