@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,32 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<span><slot :currentValue="currentValue">{{ currentValue }}</slot></span>
|
|
3
|
+
</template>
|
|
4
|
+
|
|
5
|
+
<script>
|
|
6
|
+
import gsap from "gsap";
|
|
7
|
+
export default {
|
|
8
|
+
name: 'AnimateNumber',
|
|
9
|
+
props: {
|
|
10
|
+
/**
|
|
11
|
+
* Number to animate as it changes
|
|
12
|
+
*/
|
|
13
|
+
value: Number
|
|
14
|
+
},
|
|
15
|
+
watch: {
|
|
16
|
+
value() {
|
|
17
|
+
gsap.to(this, {
|
|
18
|
+
tweenValue: this.value,
|
|
19
|
+
onUpdate: () => {
|
|
20
|
+
this.currentValue = Math.ceil(this.tweenValue);
|
|
21
|
+
},
|
|
22
|
+
});
|
|
23
|
+
},
|
|
24
|
+
},
|
|
25
|
+
data() {
|
|
26
|
+
return {
|
|
27
|
+
currentValue: this.value,
|
|
28
|
+
tweenValue: this.value,
|
|
29
|
+
};
|
|
30
|
+
},
|
|
31
|
+
}
|
|
32
|
+
</script>
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<div
|
|
3
|
+
class="progress-bar"
|
|
4
|
+
:class="{
|
|
5
|
+
'progress-bar--small' : small,
|
|
6
|
+
'progress-bar--icon-left' : iconOnLeft,
|
|
7
|
+
'type-small' : small
|
|
8
|
+
}"
|
|
9
|
+
>
|
|
10
|
+
<div class="progress-bar__header">
|
|
11
|
+
<strong
|
|
12
|
+
class="progress-bar__label"
|
|
13
|
+
:class="{
|
|
14
|
+
'type-normal' : small,
|
|
15
|
+
'hidden-visually' : labelHidden,
|
|
16
|
+
}"
|
|
17
|
+
>
|
|
18
|
+
{{ label }}
|
|
19
|
+
</strong>
|
|
20
|
+
<div v-if="status" class="progress-bar__icon">
|
|
21
|
+
<StatusIcon :type="status.type" />
|
|
22
|
+
<span class="hidden-visually">{{ status.message }}</span>
|
|
23
|
+
</div>
|
|
24
|
+
</div>
|
|
25
|
+
<div class="progress-bar__track">
|
|
26
|
+
<div
|
|
27
|
+
class="progress-bar__bar"
|
|
28
|
+
:style="`width: ${ percentage }%`"
|
|
29
|
+
></div>
|
|
30
|
+
<div
|
|
31
|
+
v-if="deficit"
|
|
32
|
+
class="progress-bar__bar--deficit"
|
|
33
|
+
:style="`width: ${ defPercentage }%`"
|
|
34
|
+
></div>
|
|
35
|
+
</div>
|
|
36
|
+
<div class="progress-bar__values">
|
|
37
|
+
<div class="progress-bar__value progress-bar__value--amount">
|
|
38
|
+
<strong class="hidden-visually">Amount:</strong>
|
|
39
|
+
{{ amount }}
|
|
40
|
+
</div>
|
|
41
|
+
<div
|
|
42
|
+
v-if="deficit > 0"
|
|
43
|
+
class="progress-bar__value progress-bar__value--deficit color-status is-danger"
|
|
44
|
+
>
|
|
45
|
+
<strong class="hidden-visually">Deficit: </strong>
|
|
46
|
+
-{{ deficit }}
|
|
47
|
+
</div>
|
|
48
|
+
<div class="progress-bar__value progress-bar__value--total">
|
|
49
|
+
<strong class="hidden-visually">Total:</strong>
|
|
50
|
+
{{ total }}
|
|
51
|
+
</div>
|
|
52
|
+
</div>
|
|
53
|
+
</div>
|
|
54
|
+
</template>
|
|
55
|
+
|
|
56
|
+
<script>
|
|
57
|
+
export default {
|
|
58
|
+
name: 'ProgressBar',
|
|
59
|
+
props: {
|
|
60
|
+
small: Boolean,
|
|
61
|
+
label: {
|
|
62
|
+
type: String,
|
|
63
|
+
default: "Progress"
|
|
64
|
+
},
|
|
65
|
+
labelHidden: Boolean,
|
|
66
|
+
total: Number,
|
|
67
|
+
deficit: Number,
|
|
68
|
+
amount: Number,
|
|
69
|
+
iconOnLeft: Boolean
|
|
70
|
+
},
|
|
71
|
+
computed: {
|
|
72
|
+
percentage() {
|
|
73
|
+
const { amount, total } = this;
|
|
74
|
+
return amount / total * 100;
|
|
75
|
+
},
|
|
76
|
+
defPercentage() {
|
|
77
|
+
const { deficit, total } = this;
|
|
78
|
+
return deficit ? deficit / total * 100 : 0;
|
|
79
|
+
},
|
|
80
|
+
isComplete() {
|
|
81
|
+
return this.amount >= this.total;
|
|
82
|
+
},
|
|
83
|
+
status() {
|
|
84
|
+
return this.isComplete ? {
|
|
85
|
+
type: "success",
|
|
86
|
+
message: "Item Completed"
|
|
87
|
+
} : this.deficit ? {
|
|
88
|
+
type: "danger",
|
|
89
|
+
message: "Item Has Deficit"
|
|
90
|
+
} : null;
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
</script>
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<div
|
|
3
|
+
class="progress-donut"
|
|
4
|
+
:class="{
|
|
5
|
+
'progress-donut--small' : small,
|
|
6
|
+
'progress-donut--small-below' : smallBelow,
|
|
7
|
+
'progress-donut--status-low' : !neutral && percentage < 30,
|
|
8
|
+
'progress-donut--status-incomplete' : !neutral && percentage >= 30 && percentage < 100,
|
|
9
|
+
'progress-donut--status-complete' : !neutral && percentage >= 100,
|
|
10
|
+
}"
|
|
11
|
+
>
|
|
12
|
+
<strong class="hidden-visually">Course Progress</strong>
|
|
13
|
+
<div class="progress-donut__chart">
|
|
14
|
+
<!-- Added the 1% extra to 100% becasuse sometimes it renders with a tiny gap -->
|
|
15
|
+
<svg class="progress-donut__chart-svg" viewBox="0 0 32 32">
|
|
16
|
+
<circle
|
|
17
|
+
class="progress-donut__chart-pie"
|
|
18
|
+
ref="pie"
|
|
19
|
+
r="16"
|
|
20
|
+
cx="16"
|
|
21
|
+
cy="16"
|
|
22
|
+
:style="{ strokeDasharray: endDasharray }"
|
|
23
|
+
/>
|
|
24
|
+
<circle
|
|
25
|
+
class="progress-donut__chart-mask"
|
|
26
|
+
:r="small ? 7 : 11"
|
|
27
|
+
cx="16"
|
|
28
|
+
cy="16"
|
|
29
|
+
/>
|
|
30
|
+
</svg>
|
|
31
|
+
<strong v-if="!small" class="progress-donut__chart-value">
|
|
32
|
+
{{ percentage }}%
|
|
33
|
+
</strong>
|
|
34
|
+
</div>
|
|
35
|
+
<!-- The valus is shown to the side or below when small mode -->
|
|
36
|
+
<strong v-if="small" class="progress-donut__value type-small-x">
|
|
37
|
+
{{ percentage }}%
|
|
38
|
+
</strong>
|
|
39
|
+
</div>
|
|
40
|
+
</template>
|
|
41
|
+
|
|
42
|
+
<script>
|
|
43
|
+
let counter = 0;
|
|
44
|
+
export default {
|
|
45
|
+
name: 'ProgressDonut',
|
|
46
|
+
props: {
|
|
47
|
+
percentage: {
|
|
48
|
+
type: Number,
|
|
49
|
+
default: 0
|
|
50
|
+
},
|
|
51
|
+
small: Boolean,
|
|
52
|
+
smallBelow: Boolean,
|
|
53
|
+
neutral: Boolean,
|
|
54
|
+
duration: {
|
|
55
|
+
type: Number,
|
|
56
|
+
default: 500
|
|
57
|
+
},
|
|
58
|
+
easing: {
|
|
59
|
+
type: String,
|
|
60
|
+
default: "ease-in"
|
|
61
|
+
}
|
|
62
|
+
},
|
|
63
|
+
data() {
|
|
64
|
+
return {
|
|
65
|
+
uid: `progress-donut-${ ++counter }`
|
|
66
|
+
}
|
|
67
|
+
},
|
|
68
|
+
watch: {
|
|
69
|
+
// Need to reanimate if value changes
|
|
70
|
+
percentage(newVal, oldVal) {
|
|
71
|
+
if (newVal !== oldVal) {
|
|
72
|
+
this.animate(this.normalize(oldVal));
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
},
|
|
76
|
+
computed: {
|
|
77
|
+
endDasharray() {
|
|
78
|
+
return `${ this.normalize(this.percentage) } 100`;
|
|
79
|
+
}
|
|
80
|
+
},
|
|
81
|
+
methods: {
|
|
82
|
+
normalize(percentage) {
|
|
83
|
+
return percentage === 100 ? 101 : percentage;
|
|
84
|
+
},
|
|
85
|
+
animate(from = 0) {
|
|
86
|
+
const { pie } = this.$refs;
|
|
87
|
+
if (!pie.animate) return; // No Animation API
|
|
88
|
+
const { duration, easing, endDasharray } = this;
|
|
89
|
+
const keyframes = { strokeDasharray: [`${ from } 100`, endDasharray] };
|
|
90
|
+
pie.animate(keyframes, { duration, easing, fill: "forwards" });
|
|
91
|
+
}
|
|
92
|
+
},
|
|
93
|
+
mounted() {
|
|
94
|
+
this.animate();
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
</script>
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @module composables/index.js
|
|
3
|
+
* Responsible for exporting all composables
|
|
4
|
+
* - Used in bundle exports
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
export { useIcon } from './useIcon.js';
|
|
8
|
+
export { useModifiers } from './useModifiers.js';
|
|
9
|
+
export { useWindowResize } from './useWindowResize.js';
|
|
10
|
+
export { useBreakpointManager } from './useBreakpointManager.js';
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
import { ref, markRaw } from "vue";
|
|
2
|
+
import { isBrowser } from "@ulu/utils/browser/dom.js";
|
|
3
|
+
|
|
4
|
+
const defaults = {
|
|
5
|
+
/**
|
|
6
|
+
* Set an initial value (value in mounted, SSR)
|
|
7
|
+
*/
|
|
8
|
+
initialValue: null,
|
|
9
|
+
/**
|
|
10
|
+
* Function called after init (passed manager)
|
|
11
|
+
*/
|
|
12
|
+
onReady: null,
|
|
13
|
+
/**
|
|
14
|
+
* Options sent to CssBreakpoints library
|
|
15
|
+
*/
|
|
16
|
+
plugin: {
|
|
17
|
+
customProperty: "--breakpoint"
|
|
18
|
+
}
|
|
19
|
+
};
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Use the CssBreakpoints module in Vue
|
|
23
|
+
* - Normally use only once, unless you have different sets of breakpoints
|
|
24
|
+
* @param {Object} options Configuration options overrides
|
|
25
|
+
* @return {Object} { manager, active, direction } (all are null in SSR environment until init)
|
|
26
|
+
*/
|
|
27
|
+
export function useBreakpointManager(options) {
|
|
28
|
+
const config = Object.assign({}, defaults, options);
|
|
29
|
+
|
|
30
|
+
const breakpointManager = ref(null);
|
|
31
|
+
const breakpointActive = ref(config.initialValue);
|
|
32
|
+
const breakpointDirection = ref(null);
|
|
33
|
+
|
|
34
|
+
const init = async () => {
|
|
35
|
+
if (!isBrowser()) return;
|
|
36
|
+
|
|
37
|
+
// Wait for the DOM to be ready before initializing the breakpoint manager
|
|
38
|
+
await new Promise(resolve => {
|
|
39
|
+
if (document.readyState === 'loading') {
|
|
40
|
+
document.addEventListener('DOMContentLoaded', () => resolve());
|
|
41
|
+
} else {
|
|
42
|
+
resolve();
|
|
43
|
+
}
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
const mod = await import("@ulu/frontend/js/ui/breakpoints.js");
|
|
47
|
+
const { BreakpointManager } = mod;
|
|
48
|
+
const manager = markRaw(new BreakpointManager(config.plugin));
|
|
49
|
+
|
|
50
|
+
breakpointManager.value = markRaw(manager);
|
|
51
|
+
|
|
52
|
+
const setValues = () => {
|
|
53
|
+
breakpointActive.value = manager.active;
|
|
54
|
+
breakpointDirection.value = manager.resizeDirection;
|
|
55
|
+
};
|
|
56
|
+
|
|
57
|
+
setValues();
|
|
58
|
+
if (config.onReady) {
|
|
59
|
+
config.onReady(manager);
|
|
60
|
+
}
|
|
61
|
+
manager.onChange(setValues);
|
|
62
|
+
};
|
|
63
|
+
|
|
64
|
+
// Initialize automatically
|
|
65
|
+
init();
|
|
66
|
+
|
|
67
|
+
return { breakpointManager, breakpointActive, breakpointDirection };
|
|
68
|
+
}
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Utility composable for handling and formatting icon props for UluIcon.
|
|
3
|
+
* @returns {Object} An object with utility functions { getIconProps, getClassesFromDefinition }
|
|
4
|
+
*/
|
|
5
|
+
export function useIcon() {
|
|
6
|
+
const getIconProps = (definition) => {
|
|
7
|
+
if (!definition) return null;
|
|
8
|
+
if (typeof definition === 'object' && !Array.isArray(definition)) {
|
|
9
|
+
// If it's already an object (and not an array), assume it's directly props
|
|
10
|
+
return definition;
|
|
11
|
+
}
|
|
12
|
+
// Otherwise, treat it as the 'icon' prop
|
|
13
|
+
return { icon: definition };
|
|
14
|
+
};
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* Converts an icon definition (string, array, or object) into a string of FontAwesome classes.
|
|
18
|
+
* @param {String|Array|Object} definition - The icon definition.
|
|
19
|
+
* @returns {String|null} A string of FontAwesome classes, or null if unable to parse.
|
|
20
|
+
*/
|
|
21
|
+
const getClassesFromDefinition = (definition) => {
|
|
22
|
+
if (!definition) {
|
|
23
|
+
return null;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
// If definition is already a string (e.g., "fas fa-home")
|
|
27
|
+
if (typeof definition === "string") {
|
|
28
|
+
return definition;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
// If definition is an array (e.g., ["fas", "home"])
|
|
32
|
+
if (Array.isArray(definition)) {
|
|
33
|
+
// FontAwesome's component expects ['fas', 'home'], but for classes,
|
|
34
|
+
// we need to join them. Assuming the first element is the style prefix.
|
|
35
|
+
if (definition.length >= 2) {
|
|
36
|
+
// Common case: ['fas', 'home'] -> 'fas fa-home'
|
|
37
|
+
return `${definition[0]} fa-${definition[1]}`;
|
|
38
|
+
}
|
|
39
|
+
// Handle edge cases or just return the first if it's the only one
|
|
40
|
+
// e.g., ['fas'] might result in just 'fas' which isn't a full icon, but handles the edge
|
|
41
|
+
return definition.join(' ');
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
// If definition is an object (e.g., { icon: ['fas', 'home'] } or { icon: 'fas fa-home' })
|
|
45
|
+
if (typeof definition === 'object' && definition.icon) {
|
|
46
|
+
if (typeof definition.icon === 'string') {
|
|
47
|
+
return definition.icon;
|
|
48
|
+
}
|
|
49
|
+
if (Array.isArray(definition.icon)) {
|
|
50
|
+
if (definition.icon.length >= 2) {
|
|
51
|
+
return `${definition.icon[0]} fa-${definition.icon[1]}`;
|
|
52
|
+
}
|
|
53
|
+
return definition.icon.join(' ');
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
console.warn("useIcon: Unable to parse definition for static FontAwesome classes:", definition);
|
|
58
|
+
return null;
|
|
59
|
+
};
|
|
60
|
+
|
|
61
|
+
return { getIconProps, getClassesFromDefinition };
|
|
62
|
+
}
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @module composables/useModifiers
|
|
3
|
+
* Handles user modifiers prop and internal modifiers for a given component
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import { computed, toRefs, toValue } from "vue";
|
|
7
|
+
import { normalizeClasses } from "@ulu/utils/templating.js";
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* A composable to manage and resolve BEM style modifiers for a component,
|
|
11
|
+
* combining user-passed modifiers with internally derived conditional modifiers.
|
|
12
|
+
*
|
|
13
|
+
* @param {object} options - The options for the composable.
|
|
14
|
+
* @param {object} options.props - The component's props object. (Must contain a 'modifiers' prop if user-passed modifiers are expected)
|
|
15
|
+
* @param {string | import('vue').Ref<string>} options.baseClass - The base CSS class name for the component (e.g., 'modal').
|
|
16
|
+
* Can be a string or a ref to a string.
|
|
17
|
+
* @param {string | string[] | Object.<string, any> | import('vue').ComputedRef<string | string[] | Object.<string, any>>} [options.internal={}] -
|
|
18
|
+
* A flexible input for component's internal modifiers. Can be a string, array of strings/objects, or an object mapping modifier names to conditions.
|
|
19
|
+
* @returns {object} An object containing the computed property `resolvedModifiers`
|
|
20
|
+
*
|
|
21
|
+
* @example
|
|
22
|
+
* // In MyComponent.vue:
|
|
23
|
+
* <template>
|
|
24
|
+
* <div :class="[resolvedModifiers, 'other-class']"></div>
|
|
25
|
+
* </template>
|
|
26
|
+
*
|
|
27
|
+
* <script>
|
|
28
|
+
* import { computed, ref } from 'vue';
|
|
29
|
+
* import { useModifiers } from './composables/useModifiers.js'; // Adjust path
|
|
30
|
+
*
|
|
31
|
+
* export default {
|
|
32
|
+
* props: {
|
|
33
|
+
* variant: String, // e.g., 'primary', 'secondary'
|
|
34
|
+
* isActive: Boolean,
|
|
35
|
+
* modifiers: [String, Array, Object] // User-passed modifiers
|
|
36
|
+
* },
|
|
37
|
+
* setup(props) {
|
|
38
|
+
* const isHovered = ref(false);
|
|
39
|
+
*
|
|
40
|
+
* // Define component-internal modifiers based on props or local state
|
|
41
|
+
* const internalModifiers = computed(() => ({
|
|
42
|
+
* [props.variant]: !!props.variant, // Add 'primary' or 'secondary' if prop exists
|
|
43
|
+
* 'active': props.isActive, // Add 'active' if isActive prop is true
|
|
44
|
+
* 'hovered': isHovered.value, // Add 'hovered' if local state is true
|
|
45
|
+
* 'default': !props.variant && !props.isActive // Add 'default' if no variant/active
|
|
46
|
+
* }));
|
|
47
|
+
*
|
|
48
|
+
* // Use the composable to get the combined modifier classes
|
|
49
|
+
* const { resolvedModifiers } = useModifiers({
|
|
50
|
+
* props: props, // Pass component props for 'modifiers' prop
|
|
51
|
+
* baseClass: 'button', // The BEM block name
|
|
52
|
+
* internal: internalModifiers // The computed internal modifiers
|
|
53
|
+
* });
|
|
54
|
+
*
|
|
55
|
+
* return { resolvedModifiers, isHovered };
|
|
56
|
+
* }
|
|
57
|
+
* };
|
|
58
|
+
* </script>
|
|
59
|
+
*
|
|
60
|
+
* // Resulting class examples for 'my-component':
|
|
61
|
+
* // <MyComponent /> => class="my-component my-component--default"
|
|
62
|
+
* // <MyComponent variant="primary" /> => class="my-component my-component--primary"
|
|
63
|
+
* // <MyComponent isActive /> => class="my-component my-component--active"
|
|
64
|
+
* // <MyComponent modifiers="condensed" /> => class="my-component my-component--default my-component--condensed"
|
|
65
|
+
* // <MyComponent variant="secondary" :isActive="true" modifiers="round" />
|
|
66
|
+
* // => class="my-component my-component--secondary my-component--active my-component--round"
|
|
67
|
+
*/
|
|
68
|
+
export function useModifiers({ props, baseClass, internal = {} }) {
|
|
69
|
+
// Use toRefs to destructure props to maintain reactivity for `modifiers`
|
|
70
|
+
// Ensure props has a 'modifiers' property, or handle its absence
|
|
71
|
+
const { modifiers } = toRefs(props);
|
|
72
|
+
|
|
73
|
+
const resolvedModifiers = computed(() => {
|
|
74
|
+
const resolvedBase = toValue(baseClass);
|
|
75
|
+
|
|
76
|
+
// Normalize both userModifiers and internal using the helper
|
|
77
|
+
const userModifiers = normalizeClasses(toValue(modifiers));
|
|
78
|
+
const internalModifiers = normalizeClasses(toValue(internal));
|
|
79
|
+
|
|
80
|
+
if (!resolvedBase) {
|
|
81
|
+
console.warn("useModifiers: Missing 'baseClass' argument, modifiers will not be applied.");
|
|
82
|
+
return '';
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
// Combine all active modifiers into one Set
|
|
86
|
+
const all = new Set([...internalModifiers, ...userModifiers]);
|
|
87
|
+
|
|
88
|
+
// Join all collected modifiers
|
|
89
|
+
return Array.from(all).map(modifier => `${ resolvedBase }--${ modifier }`);
|
|
90
|
+
});
|
|
91
|
+
|
|
92
|
+
return { resolvedModifiers };
|
|
93
|
+
}
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Reusable window resize
|
|
3
|
+
* - Future could have request animation frame throttled onResize
|
|
4
|
+
* - Just start and end for now, until thats needed
|
|
5
|
+
*/
|
|
6
|
+
import { ref } from "vue";
|
|
7
|
+
import { debounce } from "@ulu/utils/performance.js";
|
|
8
|
+
const resizing = ref(false);
|
|
9
|
+
const callbacks = {
|
|
10
|
+
start: [],
|
|
11
|
+
end: []
|
|
12
|
+
};
|
|
13
|
+
// Method is to add a resize handler just for the first call (start)
|
|
14
|
+
// which removes itself, the end (debounced) will be called after resizing
|
|
15
|
+
// stops at which point it will add the start handler to repeat the process
|
|
16
|
+
// Debounced end handler never needs to be removed.
|
|
17
|
+
function onStart() {
|
|
18
|
+
window.removeEventListener("resize", onStart);
|
|
19
|
+
resizing.value = true;
|
|
20
|
+
callbacks.start.forEach(cb => cb());
|
|
21
|
+
}
|
|
22
|
+
function onEnd() {
|
|
23
|
+
resizing.value = false;
|
|
24
|
+
callbacks.end.forEach(cb => cb());
|
|
25
|
+
window.addEventListener("resize", onStart);
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
// Only allow in browser contexts
|
|
29
|
+
if (!import.meta.env.SSR) {
|
|
30
|
+
window.addEventListener("resize", onStart);
|
|
31
|
+
window.addEventListener("resize", debounce(onEnd, 300));
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
*
|
|
36
|
+
* @param {Array} array Internal callback array to put callback in
|
|
37
|
+
* @param {*} callback Users callback
|
|
38
|
+
* @returns {Function} Remove Function
|
|
39
|
+
*/
|
|
40
|
+
function register(array, callback) {
|
|
41
|
+
array.push(callback);
|
|
42
|
+
return () => {
|
|
43
|
+
const index = array.findIndex(cb => cb === callback);
|
|
44
|
+
if (index > -1) {
|
|
45
|
+
array.splice(index);
|
|
46
|
+
}
|
|
47
|
+
};
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* Composable function
|
|
52
|
+
* @return {Object} Contains reactive 'resizing' and two methods for calling callbacks (onResizeStart, onResizeEnd)
|
|
53
|
+
*/
|
|
54
|
+
export function useWindowResize() {
|
|
55
|
+
return {
|
|
56
|
+
resizing,
|
|
57
|
+
onResizeStart(callback) {
|
|
58
|
+
return register(callbacks.start, callback);
|
|
59
|
+
},
|
|
60
|
+
onResizeEnd(callback) {
|
|
61
|
+
return register(callbacks.end, callback);
|
|
62
|
+
}
|
|
63
|
+
};
|
|
64
|
+
}
|
package/lib/index.js
ADDED
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import { ref, computed } from "vue";
|
|
2
|
+
import { useBreakpointManager } from "../../composables/useBreakpointManager.js";
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Plugin Options
|
|
6
|
+
*/
|
|
7
|
+
const defaults = {
|
|
8
|
+
/**
|
|
9
|
+
* Breakpoint for mobile down/max
|
|
10
|
+
*/
|
|
11
|
+
breakpointMobile: "small",
|
|
12
|
+
/**
|
|
13
|
+
* Options to pass to useBreakpointManager()
|
|
14
|
+
*/
|
|
15
|
+
managerOptions: {}
|
|
16
|
+
};
|
|
17
|
+
|
|
18
|
+
export default function install(app, userOptions) {
|
|
19
|
+
const isMobile = ref(false);
|
|
20
|
+
const options = Object.assign({}, defaults, userOptions);
|
|
21
|
+
const { breakpointMobile } = options;
|
|
22
|
+
const { onReady: userOnReady } = options.managerOptions;
|
|
23
|
+
|
|
24
|
+
// Setup breakpoint manager options but insert our own onReady (create flag for mobile)
|
|
25
|
+
const pluginManagerOptions = {
|
|
26
|
+
onReady(manager) {
|
|
27
|
+
manager.at(breakpointMobile).max({
|
|
28
|
+
on() { isMobile.value = true; },
|
|
29
|
+
off() { isMobile.value = false; }
|
|
30
|
+
});
|
|
31
|
+
if (userOnReady) userOnReady(manager);
|
|
32
|
+
}
|
|
33
|
+
};
|
|
34
|
+
|
|
35
|
+
const managerOptions = Object.assign({}, options.managerOptions, pluginManagerOptions);
|
|
36
|
+
|
|
37
|
+
const {
|
|
38
|
+
breakpointManager,
|
|
39
|
+
breakpointActive,
|
|
40
|
+
breakpointDirection
|
|
41
|
+
} = useBreakpointManager(managerOptions);
|
|
42
|
+
|
|
43
|
+
app.provide("uluBreakpointActive", computed(() => breakpointActive.value));
|
|
44
|
+
app.provide("uluBreakpointDirection", computed(() => breakpointDirection.value));
|
|
45
|
+
app.provide("uluBreakpointManager", computed(() => breakpointManager.value));
|
|
46
|
+
app.provide("uluIsMobile", computed(() => isMobile.value));
|
|
47
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @module plugins/index.js
|
|
3
|
+
* Responsible for exporting all plugins
|
|
4
|
+
* - Used in bundle exports
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
export { default as popoversPlugin } from './popovers/index.js';
|
|
9
|
+
export { default as modalsPlugin } from './modals/index.js';
|
|
10
|
+
export { default as toastPlugin } from './toast/index.js';
|
|
11
|
+
export { default as breakpointsPlugin } from './breakpoints/index.js';
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<component
|
|
3
|
+
v-if="currentModal"
|
|
4
|
+
:is="currentModal.component"
|
|
5
|
+
v-bind="currentProps"
|
|
6
|
+
v-model="open"
|
|
7
|
+
@vue:mounted="modalMounted"
|
|
8
|
+
@vue:unmounted="modalUnmounted"
|
|
9
|
+
/>
|
|
10
|
+
</template>
|
|
11
|
+
|
|
12
|
+
<script>
|
|
13
|
+
export default {
|
|
14
|
+
name: "UluModalsDisplay",
|
|
15
|
+
emits: [
|
|
16
|
+
"modal-unmount",
|
|
17
|
+
"modal-mount"
|
|
18
|
+
],
|
|
19
|
+
data() {
|
|
20
|
+
return {
|
|
21
|
+
open: false
|
|
22
|
+
};
|
|
23
|
+
},
|
|
24
|
+
computed: {
|
|
25
|
+
currentModal() {
|
|
26
|
+
return this.$uluModalsState.data?.active;
|
|
27
|
+
},
|
|
28
|
+
currentProps() {
|
|
29
|
+
return this.$uluModalsState.data?.activeProps;
|
|
30
|
+
}
|
|
31
|
+
},
|
|
32
|
+
watch: {
|
|
33
|
+
// Watch for changes in the global state (e.g., when $uluModals.open() is called)
|
|
34
|
+
currentModal(newValue) {
|
|
35
|
+
if (newValue) {
|
|
36
|
+
this.open = true;
|
|
37
|
+
} else {
|
|
38
|
+
this.open = false;
|
|
39
|
+
}
|
|
40
|
+
},
|
|
41
|
+
// Watch for changes in the local state (e.g., when the modal emits 'update:modelValue')
|
|
42
|
+
open(newValue) {
|
|
43
|
+
if (!newValue && this.currentModal) {
|
|
44
|
+
this.$uluModals.close();
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
},
|
|
48
|
+
methods: {
|
|
49
|
+
modalMounted() {
|
|
50
|
+
this.$emit("modal-mount", { modal: this.currentModal });
|
|
51
|
+
},
|
|
52
|
+
modalUnmounted() {
|
|
53
|
+
this.$nextTick(() => {
|
|
54
|
+
this.$emit("modal-unmount");
|
|
55
|
+
});
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
};
|
|
59
|
+
</script>
|