@ulu/frontend-vue 0.1.0-beta.9 → 0.1.1-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 (99) hide show
  1. package/dist/{breakpoints-BbkGNxxt.js → breakpoints-DM-CBTtb.js} +1 -1
  2. package/dist/frontend-vue.css +1 -1
  3. package/dist/frontend-vue.js +79 -68
  4. package/dist/index-BNRZ3Apw.js +7541 -0
  5. package/lib/components/collapsible/UluAccordion.vue +71 -53
  6. package/lib/components/collapsible/UluAccordionGroup.vue +54 -0
  7. package/lib/components/collapsible/UluCollapsible.vue +144 -0
  8. package/lib/components/collapsible/UluDropdown.vue +29 -29
  9. package/lib/components/collapsible/UluOverflowPopover.vue +1 -1
  10. package/lib/components/elements/UluBadge.vue +51 -28
  11. package/lib/components/elements/UluBadgeStack.vue +8 -13
  12. package/lib/components/elements/UluButtonVerbose.vue +119 -0
  13. package/lib/components/elements/UluCard.vue +1 -1
  14. package/lib/components/elements/UluDefinitionList.vue +14 -17
  15. package/lib/components/elements/UluExternalLink.vue +21 -27
  16. package/lib/components/elements/UluIcon.vue +11 -1
  17. package/lib/components/elements/UluList.vue +53 -55
  18. package/lib/components/elements/UluSpokeSpinner.vue +12 -18
  19. package/lib/components/elements/UluTag.vue +35 -35
  20. package/lib/components/forms/UluFileDisplay.vue +49 -31
  21. package/lib/components/forms/UluFormFile.vue +37 -24
  22. package/lib/components/forms/UluFormMessage.vue +13 -10
  23. package/lib/components/forms/UluFormSelect.vue +28 -16
  24. package/lib/components/forms/UluFormText.vue +24 -15
  25. package/lib/components/forms/UluSearchForm.vue +11 -10
  26. package/lib/components/forms/UluSelectableMenu.vue +99 -0
  27. package/lib/components/index.js +4 -3
  28. package/lib/components/layout/UluTitleRail.vue +18 -0
  29. package/lib/components/layout/UluWhenBreakpoint.vue +9 -0
  30. package/lib/components/navigation/UluBreadcrumb.vue +9 -2
  31. package/lib/components/navigation/UluMenu.vue +8 -3
  32. package/lib/components/navigation/UluMenuStack.vue +3 -1
  33. package/lib/components/navigation/UluPager.vue +102 -0
  34. package/lib/components/systems/facets/ExampleFacetsWithPagination.vue +119 -0
  35. package/lib/components/systems/facets/UluFacetsFilterLists.vue +91 -0
  36. package/lib/components/systems/facets/UluFacetsFilterPopovers.vue +125 -0
  37. package/lib/components/systems/facets/UluFacetsFilterSelects.vue +71 -0
  38. package/lib/components/systems/facets/UluFacetsHeaderLayout.vue +24 -0
  39. package/lib/components/systems/facets/UluFacetsList.vue +62 -34
  40. package/lib/components/systems/facets/UluFacetsResults.vue +63 -0
  41. package/lib/components/systems/facets/UluFacetsSearch.vue +27 -50
  42. package/lib/components/systems/facets/UluFacetsSidebarLayout.vue +70 -0
  43. package/lib/components/systems/facets/UluFacetsSort.vue +45 -0
  44. package/lib/components/systems/facets/_facets.scss +2 -3
  45. package/lib/components/systems/facets/_mock-data.js +40 -0
  46. package/lib/components/systems/facets/useFacets.js +268 -0
  47. package/lib/components/systems/index.js +13 -2
  48. package/lib/components/systems/scroll-anchors/UluScrollAnchors.vue +2 -1
  49. package/lib/components/systems/skeleton/UluShowSkeleton.vue +9 -8
  50. package/lib/components/systems/skeleton/UluSkeletonContent.vue +39 -43
  51. package/lib/components/systems/skeleton/UluSkeletonMedia.vue +4 -6
  52. package/lib/components/systems/skeleton/UluSkeletonText.vue +27 -0
  53. package/lib/components/systems/slider/UluImageSlideShow.vue +1 -1
  54. package/lib/components/systems/slider/UluSlideShow.vue +8 -3
  55. package/lib/components/systems/table-sticky/UluTableSticky.vue +7 -7
  56. package/lib/components/systems/table-sticky/UluTableStickyTable.vue +3 -3
  57. package/lib/components/visualizations/UluAnimateNumber.vue +7 -1
  58. package/lib/components/visualizations/UluProgressBar.vue +99 -68
  59. package/lib/components/visualizations/UluProgressCircle.vue +146 -0
  60. package/lib/components/visualizations/progress-bar-examples.html +175 -0
  61. package/lib/composables/index.js +3 -1
  62. package/lib/composables/useDocumentTitle.js +61 -0
  63. package/lib/composables/usePagination.js +122 -0
  64. package/lib/index.js +1 -0
  65. package/lib/plugins/core/index.js +6 -1
  66. package/lib/plugins/popovers/UluPopover.vue +8 -3
  67. package/lib/plugins/toast/UluToast.vue +1 -1
  68. package/lib/plugins/toast/UluToastDisplay.vue +19 -2
  69. package/lib/utils/dom.js +12 -0
  70. package/lib/utils/index.js +2 -0
  71. package/lib/utils/{vue-router.js → router.js} +114 -30
  72. package/package.json +17 -11
  73. package/types/components/systems/facets/_mock-data.d.ts +18 -0
  74. package/types/components/systems/facets/_mock-data.d.ts.map +1 -0
  75. package/types/components/systems/facets/useFacets.d.ts +39 -0
  76. package/types/components/systems/facets/useFacets.d.ts.map +1 -0
  77. package/types/components/systems/index.d.ts +1 -1
  78. package/types/composables/index.d.ts +2 -0
  79. package/types/composables/useDocumentTitle.d.ts +22 -0
  80. package/types/composables/useDocumentTitle.d.ts.map +1 -0
  81. package/types/composables/usePageTitle.d.ts +19 -0
  82. package/types/composables/usePageTitle.d.ts.map +1 -0
  83. package/types/composables/usePagination.d.ts +25 -0
  84. package/types/composables/usePagination.d.ts.map +1 -0
  85. package/types/index.d.ts +1 -0
  86. package/types/plugins/core/index.d.ts.map +1 -1
  87. package/types/utils/dom.d.ts +1 -0
  88. package/types/utils/dom.d.ts.map +1 -1
  89. package/types/utils/index.d.ts +3 -0
  90. package/types/utils/index.d.ts.map +1 -0
  91. package/types/utils/router.d.ts +144 -0
  92. package/types/utils/router.d.ts.map +1 -0
  93. package/dist/index-D3Uc6T5M.js +0 -6469
  94. package/lib/components/collapsible/UluCollapsibleRegion.vue +0 -278
  95. package/lib/components/forms/UluCheckboxMenu.vue +0 -36
  96. package/lib/components/systems/facets/UluFacets.vue +0 -380
  97. package/lib/components/systems/skeleton/UluSkeletonTextInline.vue +0 -9
  98. package/lib/components/visualizations/UluProgressDonut.vue +0 -97
  99. package/lib/utils/placeholder.js +0 -6
@@ -1,46 +1,33 @@
1
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
- }"
2
+ <div :class="componentClasses">
3
+ <div v-if="label || $slots.icon" class="progress-bar__header">
4
+ <strong
5
+ v-if="label"
6
+ class="progress-bar__label"
7
+ :class="{ 'hidden-visually': labelHidden }"
17
8
  >
18
9
  {{ label }}
19
10
  </strong>
20
- <div v-if="status" class="progress-bar__icon">
21
- <StatusIcon :type="status.type" />
22
- <span class="hidden-visually">{{ status.message }}</span>
11
+ <div v-if="$slots.icon" class="progress-bar__icon">
12
+ <slot name="icon" />
23
13
  </div>
24
14
  </div>
25
15
  <div class="progress-bar__track">
26
- <div
27
- class="progress-bar__bar"
28
- :style="`width: ${ percentage }%`"
29
- ></div>
30
- <div
31
- v-if="deficit"
16
+ <div class="progress-bar__bar" :style="{ width: barWidth }"></div>
17
+ <div
18
+ v-if="deficit > 0"
32
19
  class="progress-bar__bar--deficit"
33
- :style="`width: ${ defPercentage }%`"
20
+ :style="{ width: deficitBarWidth }"
34
21
  ></div>
35
22
  </div>
36
- <div class="progress-bar__values">
23
+ <div v-if="!loader && !indeterminate" class="progress-bar__values">
37
24
  <div class="progress-bar__value progress-bar__value--amount">
38
25
  <strong class="hidden-visually">Amount:</strong>
39
26
  {{ amount }}
40
27
  </div>
41
- <div
42
- v-if="deficit > 0"
43
- class="progress-bar__value progress-bar__value--deficit color-status is-danger"
28
+ <div
29
+ v-if="deficit > 0"
30
+ class="progress-bar__value progress-bar__value--deficit"
44
31
  >
45
32
  <strong class="hidden-visually">Deficit: </strong>
46
33
  -{{ deficit }}
@@ -53,42 +40,86 @@
53
40
  </div>
54
41
  </template>
55
42
 
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>
43
+ <script setup>
44
+ import { computed } from "vue";
45
+
46
+ /**
47
+ * A linear progress bar to display progress, with support for various styles and a deficit indicator.
48
+ * @slot icon - A slot for placing an icon in the header, typically to indicate status.
49
+ */
50
+ const props = defineProps({
51
+ /**
52
+ * The label to display above the progress bar.
53
+ */
54
+ label: String,
55
+ /**
56
+ * Hides the label visually, but keeps it for screen readers.
57
+ */
58
+ labelHidden: Boolean,
59
+ /**
60
+ * The current amount of progress.
61
+ */
62
+ amount: {
63
+ type: Number,
64
+ default: 0,
65
+ },
66
+ /**
67
+ * The total amount that represents 100% progress.
68
+ */
69
+ total: {
70
+ type: Number,
71
+ default: 100,
72
+ },
73
+ /**
74
+ * The amount of deficit to display on the bar.
75
+ */
76
+ deficit: {
77
+ type: Number,
78
+ default: 0,
79
+ },
80
+ /**
81
+ * Renders a smaller version of the progress bar.
82
+ */
83
+ small: Boolean,
84
+ /**
85
+ * Applies the 'positive' style (e.g., green).
86
+ */
87
+ positive: Boolean,
88
+ /**
89
+ * Applies the 'negative' style (e.g., red).
90
+ */
91
+ negative: Boolean,
92
+ /**
93
+ * Applies styles for use as a thin loader.
94
+ */
95
+ loader: Boolean,
96
+ /**
97
+ * Applies an indeterminate animation for unknown progress.
98
+ */
99
+ indeterminate: Boolean,
100
+ });
101
+
102
+ const getCssPercentage = (amount, total) => {
103
+ const percent = total === 0 ? 0 : (amount / total) * 100;
104
+ return `${ percent }%`;
105
+ };
106
+
107
+ const barWidth = computed(() => {
108
+ if (props.indeterminate) return null; // No value for width
109
+ return getCssPercentage(props.amount, props.total);
110
+ });
111
+
112
+ const deficitBarWidth = computed(() => getCssPercentage(props.deficit, props.total));
113
+
114
+ const componentClasses = computed(() => {
115
+ return {
116
+ 'progress-bar': true,
117
+ 'progress-bar--small': props.small,
118
+ 'progress-bar--positive': props.positive,
119
+ 'progress-bar--negative': props.negative,
120
+ 'progress-bar--loader': props.loader,
121
+ 'progress-bar--indeterminate': props.indeterminate,
122
+ 'type-small': props.small, // From original component, seems to control font size
123
+ };
124
+ });
125
+ </script>
@@ -0,0 +1,146 @@
1
+ <template>
2
+ <div :class="componentClasses">
3
+ <strong class="hidden-visually">{{ label }}</strong>
4
+ <div class="progress-circle__chart">
5
+ <svg class="progress-circle__chart-svg" viewBox="0 0 32 32">
6
+ <circle
7
+ class="progress-circle__chart-track"
8
+ r="16"
9
+ cx="16"
10
+ cy="16"
11
+ />
12
+ <circle
13
+ class="progress-circle__chart-pie"
14
+ ref="pie"
15
+ r="16"
16
+ cx="16"
17
+ cy="16"
18
+ :style="{ strokeDasharray: endDasharray }"
19
+ />
20
+ <circle
21
+ class="progress-circle__chart-mask"
22
+ cx="16"
23
+ cy="16"
24
+ />
25
+ </svg>
26
+ <strong v-if="!showValueOutside" class="progress-circle__chart-value">
27
+ {{ percentage }}%
28
+ </strong>
29
+ </div>
30
+ <strong v-if="showValueOutside" class="progress-circle__value type-small-x">
31
+ {{ percentage }}%
32
+ </strong>
33
+ </div>
34
+ </template>
35
+
36
+ <script>
37
+ /**
38
+ * A circular progress indicator component.
39
+ */
40
+ export default {
41
+ name: 'UluProgressCircle',
42
+ props: {
43
+ /**
44
+ * The label for accessibility (visually hidden).
45
+ */
46
+ label: {
47
+ type: String,
48
+ default: "Progress"
49
+ },
50
+ /**
51
+ * The progress percentage (0-100).
52
+ */
53
+ percentage: {
54
+ type: Number,
55
+ default: 0
56
+ },
57
+ /**
58
+ * Renders a smaller version of the component.
59
+ */
60
+ small: Boolean,
61
+ /**
62
+ * Displays the percentage value outside (to the side) of the circle.
63
+ */
64
+ outside: Boolean,
65
+ /**
66
+ * Displays the percentage value below the circle.
67
+ */
68
+ outsideBelow: Boolean,
69
+ /**
70
+ * Sets the status color of the progress circle (e.g., 'low', 'incomplete', 'complete').
71
+ */
72
+ status: {
73
+ type: String,
74
+ default: ''
75
+ },
76
+ /**
77
+ * Renders the component as a solid pie chart instead of a donut.
78
+ */
79
+ pieStyle: Boolean,
80
+ /**
81
+ * Removes the center mask, filling the entire circle.
82
+ */
83
+ noMask: Boolean,
84
+ /**
85
+ * The duration of the animation in milliseconds.
86
+ */
87
+ duration: {
88
+ type: Number,
89
+ default: 1000 // Matches SCSS animation-duration
90
+ },
91
+ /**
92
+ * The easing function for the animation.
93
+ */
94
+ easing: {
95
+ type: String,
96
+ default: "ease-in" // Matches SCSS animation-timing
97
+ }
98
+ },
99
+ watch: {
100
+ // Need to reanimate if value changes
101
+ percentage(newVal, oldVal) {
102
+ if (newVal !== oldVal) {
103
+ this.animate(this.normalize(oldVal));
104
+ }
105
+ }
106
+ },
107
+ computed: {
108
+ endDasharray() {
109
+ return `${ this.normalize(this.percentage) } 100`;
110
+ },
111
+ showValueOutside() {
112
+ return this.outside || this.outsideBelow || this.small;
113
+ },
114
+ componentClasses() {
115
+ const classes = {
116
+ 'progress-circle': true,
117
+ 'progress-circle--small': this.small,
118
+ 'progress-circle--pie': this.pieStyle,
119
+ 'progress-circle--outside': this.showValueOutside,
120
+ 'progress-circle--outside-below': this.outsideBelow,
121
+ 'progress-circle--no-mask': this.noMask,
122
+ };
123
+ if (this.status) {
124
+ classes[`progress-circle--${this.status}`] = true;
125
+ }
126
+ return classes;
127
+ }
128
+ },
129
+ methods: {
130
+ normalize(percentage) {
131
+ // Added the 1% extra to 100% because sometimes it renders with a tiny gap
132
+ return percentage === 100 ? 101 : percentage;
133
+ },
134
+ animate(from = 0) {
135
+ const { pie } = this.$refs;
136
+ if (!pie || !pie.animate) return; // No Animation API or element not ready
137
+ const { duration, easing, endDasharray } = this;
138
+ const keyframes = { strokeDasharray: [`${ from } 100`, endDasharray] };
139
+ pie.animate(keyframes, { duration, easing, fill: "forwards" });
140
+ }
141
+ },
142
+ mounted() {
143
+ this.animate();
144
+ }
145
+ }
146
+ </script>
@@ -0,0 +1,175 @@
1
+
2
+ <h2 class="h2">Basic Example</h2>
3
+ <p>The default progress bar has no modifiers.</p>
4
+ <div class="progress-bar">
5
+ <div class="progress-bar__header"><strong class="progress-bar__label">Label that is long test</strong>
6
+ </div>
7
+ <div class="progress-bar__track">
8
+ <div class="progress-bar__bar" style="width: 40%;"></div>
9
+ </div>
10
+ <div class="progress-bar__values">
11
+ <div class="progress-bar__value progress-bar__value--amount"><strong class="hidden-visually">Amount:</strong>
12
+ 200</div>
13
+ <div class="progress-bar__value progress-bar__value--total"><strong class="hidden-visually">Total:</strong> 500
14
+ </div>
15
+ </div>
16
+ </div>
17
+
18
+ <div class="rule rule--light"></div>
19
+
20
+ <h2 class="h2">Indeterminate Modifier</h2>
21
+ <p>Uses the <code>.progress-bar--indeterminate</code> modifier for an animated loading state when the progress is unknown.</p>
22
+ <div class="progress-bar progress-bar--loader progress-bar--indeterminate">
23
+ <div class="progress-bar__track">
24
+ <div class="progress-bar__bar"></div>
25
+ </div>
26
+ </div>
27
+
28
+ <div class="rule rule--light"></div>
29
+
30
+ <h2 class="h2">Loader Style</h2>
31
+ <p>Uses the <code>.progress-bar--loader</code> modifier for a thin loading bar.</p>
32
+ <div class="progress-bar progress-bar--loader">
33
+ <div class="progress-bar__track">
34
+ <div class="progress-bar__bar" style="width: 65%;"></div>
35
+ </div>
36
+ </div>
37
+
38
+
39
+ <div class="rule rule--light"></div>
40
+
41
+ <h2 class="h2">Completed Example</h2>
42
+ <p>An example of a completed progress bar with an icon.</p>
43
+ <div class="progress-bar">
44
+ <div class="progress-bar__header"><strong class="progress-bar__label">Progress</strong>
45
+ <div class="progress-bar__icon">
46
+ <span class="fas fa-check" aria-hidden="true"></span>
47
+ <span class="hidden-visually">Item Completed</span>
48
+ </div>
49
+ </div>
50
+ <div class="progress-bar__track">
51
+ <div class="progress-bar__bar" style="width: 100%;"></div>
52
+ </div>
53
+ <div class="progress-bar__values">
54
+ <div class="progress-bar__value progress-bar__value--amount"><strong class="hidden-visually">Amount:</strong>
55
+ 500</div>
56
+ <div class="progress-bar__value progress-bar__value--total"><strong class="hidden-visually">Total:</strong> 500
57
+ </div>
58
+ </div>
59
+ </div>
60
+
61
+ <div class="rule rule--light"></div>
62
+
63
+ <h2 class="h2">Deficit Example</h2>
64
+ <p>An example of a progress bar with a deficit.</p>
65
+ <div class="progress-bar">
66
+ <div class="progress-bar__header"><strong class="progress-bar__label">Progress</strong>
67
+ <div class="progress-bar__icon"><span class="fas fa-triangle-exclamation" aria-hidden="true"></span><span class="hidden-visually">Item Has Deficit</span></div>
68
+ </div>
69
+ <div class="progress-bar__track">
70
+ <div class="progress-bar__bar" style="width: 60%;"></div>
71
+ <div class="progress-bar__bar progress-bar__bar--deficit" style="width: 20%;"></div>
72
+ </div>
73
+ <div class="progress-bar__values">
74
+ <div class="progress-bar__value progress-bar__value--amount"><strong class="hidden-visually">Amount:</strong>
75
+ 300</div>
76
+ <div class="progress-bar__value progress-bar__value--deficit color-status is-danger"><strong
77
+ class="hidden-visually">Deficit: </strong> -100</div>
78
+ <div class="progress-bar__value progress-bar__value--total"><strong class="hidden-visually">Total:</strong> 500
79
+ </div>
80
+ </div>
81
+ </div>
82
+
83
+ <div class="rule rule--light"></div>
84
+
85
+ <h2 class="h2">Positive Style</h2>
86
+ <p>Uses the <code>.progress-bar--positive</code> modifier.</p>
87
+ <div class="progress-bar progress-bar--positive">
88
+ <div class="progress-bar__header"><strong class="progress-bar__label">Positive</strong></div>
89
+ <div class="progress-bar__track">
90
+ <div class="progress-bar__bar" style="width: 75%;"></div>
91
+ </div>
92
+ </div>
93
+
94
+ <div class="rule rule--light"></div>
95
+
96
+ <h2 class="h2">Negative Style</h2>
97
+ <p>Uses the <code>.progress-bar--negative</code> modifier.</p>
98
+ <div class="progress-bar progress-bar--negative">
99
+ <div class="progress-bar__header"><strong class="progress-bar__label">Negative</strong></div>
100
+ <div class="progress-bar__track">
101
+ <div class="progress-bar__bar" style="width: 30%;"></div>
102
+ </div>
103
+ </div>
104
+
105
+ <div class="rule rule--light"></div>
106
+
107
+ <h2 class="h2">Small Modifier</h2>
108
+ <p>Uses the <code>.progress-bar--small</code> modifier. Font sizing is controlled by the parent element.</p>
109
+ <div class="progress-bar progress-bar--small progress-bar--positive type-small">
110
+ <div class="progress-bar__header"><strong class="progress-bar__label type-normal">Progress</strong>
111
+ <div class="progress-bar__icon">
112
+ <span class="fas fa-check" aria-hidden="true"></span>
113
+ <span class="hidden-visually">Item Completed</span>
114
+ </div>
115
+ </div>
116
+ <div class="progress-bar__track">
117
+ <div class="progress-bar__bar" style="width: 100%;"></div>
118
+ </div>
119
+ <div class="progress-bar__values">
120
+ <div class="progress-bar__value progress-bar__value--amount"><strong class="hidden-visually">Amount:</strong>
121
+ 500</div>
122
+ <div class="progress-bar__value progress-bar__value--total"><strong class="hidden-visually">Total:</strong> 500
123
+ </div>
124
+ </div>
125
+ </div>
126
+
127
+ <div class="progress-bar progress-bar--small progress-bar--deficit type-small">
128
+ <div class="progress-bar__header"><strong class="progress-bar__label type-normal">Progress</strong>
129
+ <div class="progress-bar__icon"><span class="fas fa-triangle-exclamation" aria-hidden="true"></span><span class="hidden-visually">Item Has Deficit</span></div>
130
+ </div>
131
+ <div class="progress-bar__track">
132
+ <div class="progress-bar__bar" style="width: 60%;"></div>
133
+ <div class="progress-bar__bar progress-bar__bar--deficit" style="width: 20%;"></div>
134
+ </div>
135
+ <div class="progress-bar__values">
136
+ <div class="progress-bar__value progress-bar__value--amount"><strong class="hidden-visually">Amount:</strong>
137
+ 300</div>
138
+ <div class="progress-bar__value progress-bar__value--deficit color-status is-danger"><strong
139
+ class="hidden-visually">Deficit: </strong> -100</div>
140
+ <div class="progress-bar__value progress-bar__value--total"><strong class="hidden-visually">Total:</strong> 500
141
+ </div>
142
+ </div>
143
+ </div>
144
+
145
+ <div class="rule rule--light"></div>
146
+
147
+ <h2 class="h2">Icon left with rail</h2>
148
+ <p>Combining with rail component for icon on left</p>
149
+
150
+ <div class="progress-bar progress-bar--positive">
151
+ <div class="rail">
152
+ <div class="rail__item">
153
+ <div class="progress-bar__icon type-large">
154
+ <span class="fas fa-check" aria-hidden="true"></span>
155
+ </div>
156
+ </div>
157
+ <div class="rail__item flex-grow">
158
+ <div class="progress-bar__header">
159
+ <strong class="progress-bar__label">Progress</strong>
160
+ </div>
161
+ <div class="progress-bar__track">
162
+ <div class="progress-bar__bar" style="width: 100%;"></div>
163
+ </div>
164
+ <div class="progress-bar__values">
165
+ <div class="progress-bar__value progress-bar__value--amount"><strong class="hidden-visually">Amount:</strong>
166
+ 300</div>
167
+ <div class="progress-bar__value progress-bar__value--deficit color-status is-danger"><strong
168
+ class="hidden-visually">Deficit: </strong> -100</div>
169
+ <div class="progress-bar__value progress-bar__value--total"><strong class="hidden-visually">Total:</strong> 500
170
+ </div>
171
+ </div>
172
+ </div>
173
+ </div>
174
+
175
+
@@ -8,4 +8,6 @@ export { useIcon } from './useIcon.js';
8
8
  export { useModifiers } from './useModifiers.js';
9
9
  export { useWindowResize } from './useWindowResize.js';
10
10
  export { useRequiredInject } from './useRequiredInject.js';
11
- export { useBreakpointManager } from './useBreakpointManager.js';
11
+ export { useBreakpointManager } from './useBreakpointManager.js';
12
+ export { usePagination } from './usePagination.js';
13
+ export { useDocumentTitle } from './useDocumentTitle.js';
@@ -0,0 +1,61 @@
1
+ import { reactive, watchEffect, onUnmounted, unref, computed } from "vue";
2
+ import { useHead as defaultUseHead } from "@unhead/vue";
3
+ import { useRoute as defaultUseRoute } from "vue-router";
4
+ import { getRouteTitle } from "../utils/router.js";
5
+
6
+ // A reactive map to store component-defined titles.
7
+ const componentTitles = reactive({});
8
+
9
+ /**
10
+ * A composable to manage the document title.
11
+ *
12
+ * When called with a `title` option, it sets a dynamic title for the current page.
13
+ * This is for use within specific components.
14
+ *
15
+ * When called without a `title` option (typically in App.vue), it manages the
16
+ * document title for the whole app, using titles from components or route meta.
17
+ *
18
+ * @param {object} options
19
+ * @param {import('vue').Ref<string> | string} [options.title] - The dynamic title to set for the current page.
20
+ * @param {string} [options.titleTemplate='%s'] - The template for the document title, e.g., '%s | My Site'.
21
+ * @param {Function} [options.useRoute=defaultUseRoute] - Injectable `useRoute` for testing.
22
+ * @param {Function} [options.useHead=defaultUseHead] - Injectable `useHead` for testing.
23
+ */
24
+ export function useDocumentTitle(options = {}) {
25
+ const {
26
+ title,
27
+ titleTemplate = "%s",
28
+ useRoute = defaultUseRoute,
29
+ useHead = defaultUseHead,
30
+ } = options;
31
+
32
+ const route = useRoute();
33
+ const path = route.path;
34
+
35
+ // --- Setter Mode ---
36
+ // If a title is provided, we're in "setter" mode, used within a component.
37
+ if (title !== undefined) {
38
+ watchEffect(() => {
39
+ componentTitles[path] = unref(title);
40
+ });
41
+
42
+ onUnmounted(() => {
43
+ delete componentTitles[path];
44
+ });
45
+ return; // End execution for setter mode.
46
+ }
47
+
48
+ // --- Manager Mode ---
49
+ // If no title is provided, we're in "manager" mode, used in App.vue.
50
+ const documentTitle = computed(() => {
51
+ const titleFromComponent = componentTitles[route.path];
52
+ const titleFromMeta = getRouteTitle(route, route);
53
+ const resolvedTitle = titleFromComponent || titleFromMeta;
54
+
55
+ return resolvedTitle ? titleTemplate.replace("%s", resolvedTitle) : "App";
56
+ });
57
+
58
+ useHead({
59
+ title: documentTitle,
60
+ });
61
+ }