@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,73 @@
1
+ <template>
2
+ <tr
3
+ v-for="(row, rowIndex) in rows"
4
+ :key="`br-${ rowIndex }`"
5
+ :id="optionalAttr(isActual && row.id)"
6
+ :class="resolveClasses(classes.row, { row: row.data, rowIndex, isActual, foot })"
7
+ :style="{
8
+ height: row.height
9
+ }"
10
+ >
11
+ <component
12
+ v-for="(column, index) in rowColumns"
13
+ :is="column.rowHeader ? 'th' : 'td'"
14
+ :id="optionalAttr(isActual && column.rowHeader && column.getRowHeaderId(rowIndex))"
15
+ :scope="optionalAttr(isActual && column.rowHeader && 'row')"
16
+ :key="`bc-${ index }`"
17
+ :headers="optionalAttr(isActual && getCellHeaders(column, rowIndex))"
18
+ :class="resolveClasses(column.class, { column, index, isActual, row, rowIndex, foot })"
19
+ :style="{
20
+ width: columnWidth
21
+ }"
22
+ >
23
+ <template v-if="$slots[column.slot]">
24
+ <slot
25
+ :name="column.slot"
26
+ :row="row.data"
27
+ :column="column"
28
+ :rowIndex="rowIndex"
29
+ :index="index"
30
+ :foot="foot"
31
+ :isActual="isActual"
32
+ />
33
+ </template>
34
+ <!--
35
+ <component
36
+ v-else-if="column.component"
37
+ :is="column.component"
38
+ :row="row.data"
39
+ :column="column"
40
+ :rowIndex="rowIndex"
41
+ :index="index"
42
+ /> -->
43
+ <div
44
+ v-else-if="column.html"
45
+ v-html="value({ row, column, rowIndex, isActual, foot })"
46
+ ></div>
47
+ <template v-else>
48
+ {{ value({ row, column, rowIndex, isActual, foot }) }}
49
+ </template>
50
+ </component>
51
+ </tr>
52
+ </template>
53
+
54
+ <script>
55
+ export default {
56
+ name: "UluTableStickyRows",
57
+ props: {
58
+ rows: Array,
59
+ rowColumns: Array,
60
+ columnWidth: String,
61
+ optionalAttr: Function,
62
+ resolveClasses: Function,
63
+ getCellHeaders: Function,
64
+ value: Function,
65
+ isActual: Boolean,
66
+ classes: Object,
67
+ foot: {
68
+ type: Boolean,
69
+ default: false
70
+ }
71
+ }
72
+ };
73
+ </script>
@@ -0,0 +1,237 @@
1
+ <template>
2
+ <table
3
+ :class="resolveClasses(classes.table, { isActual })"
4
+ :aria-hidden="isActual ? 'false' : 'true'"
5
+ >
6
+ <caption v-if="caption" class="table-sticky__caption">
7
+ {{ caption }}
8
+ </caption>
9
+ <thead>
10
+ <tr
11
+ v-for="(row, rowIndex) in headerRows"
12
+ :key="`hr-${ rowIndex }`"
13
+ :id="optionalAttr(isActual && row.id)"
14
+ :class="resolveClasses(classes.rowHeader, { row, rowIndex, isActual })"
15
+ :style="{
16
+ height: row.height
17
+ }"
18
+ >
19
+ <th
20
+ v-for="(column, index) in row.columns"
21
+ :key="`hc-${ index }`"
22
+ :id="optionalAttr(isActual && column.id)"
23
+ :rowspan="column.rowspan"
24
+ :colspan="column.colspan"
25
+ :data-child-columns="column.columns && column.columns.length"
26
+ :class="[
27
+ {
28
+ 'sort-active' : column.sortApplied,
29
+ 'sort-ascending' : column.sortApplied && column.sortAscending,
30
+ 'sort-descending' : column.sortApplied && !column.sortAscending,
31
+ },
32
+ resolveClasses(column.classHeader, { column, index, isActual })
33
+ ]"
34
+ :style="{
35
+ width: column.width
36
+ }"
37
+ :aria-sort="column.sort ? column.sortAscending ? 'ascending' : 'descending' : null"
38
+ :scope="optionalAttr(isActual && (column.colspan > 1 ? 'colgroup' : 'col'))"
39
+ :headers="optionalAttr(isActual && getHeaderHeaders(column, rowIndex))"
40
+ :ref="(el) => addHeaderRef(column, el)"
41
+ >
42
+ <!--
43
+ If sortable print button but div on fake headers (click only)
44
+ Focus will go back to real header (focus and blur will update styling)
45
+ -->
46
+ <component
47
+ v-if="column.sortable"
48
+ :is="isActual ? 'button' : 'div'"
49
+ class="table-sticky__sort-button"
50
+ :class="{
51
+ 'table-sticky__sort-button--focused' : column.sortFocused,
52
+ }"
53
+ @click="$emit('columnSorted', column)"
54
+ @focus="handleSortFocus(column, true)"
55
+ @blur="handleSortFocus(column, false)"
56
+ :aria-pressed="column.sortApplied ? 'true' : 'false'"
57
+ >
58
+ <template v-if="$slots[column.slotHeader]">
59
+ <slot
60
+ :name="column.slotHeader"
61
+ :isActual="isActual"
62
+ :column="column"
63
+ :index="index"
64
+ />
65
+ </template>
66
+ <div v-else-if="column.htmlTitle" v-html="getColumnTitle({ column, index, isActual })"></div>
67
+ <template v-else>
68
+ {{ getColumnTitle({ column, index, isActual }) }}
69
+ </template>
70
+ <span class="table-sticky__sort-icon" aria-hidden="true">
71
+ <span class="table-sticky__sort-icon-inner">
72
+ <slot name="sortIcon">▼</slot>
73
+ </span>
74
+ </span>
75
+ </component>
76
+ <template v-else>
77
+ <template v-if="$slots[column.slotHeader]">
78
+ <slot
79
+ :name="column.slotHeader"
80
+ :isActual="isActual"
81
+ :column="column"
82
+ :index="index"
83
+ />
84
+ </template>
85
+ <div v-else-if="column.htmlTitle" v-html="getColumnTitle({ column, index, isActual })"></div>
86
+ <template v-else>
87
+ {{ getColumnTitle({ column, index, isActual }) }}
88
+ </template>
89
+ </template>
90
+ </th>
91
+ </tr>
92
+ </thead>
93
+ <template v-if="rows">
94
+ <tbody>
95
+ <UluTableStickyRows
96
+ :rows="rows"
97
+ :rowColumns="rowColumns"
98
+ :optionalAttr="optionalAttr"
99
+ :resolveClasses="resolveClasses"
100
+ :getCellHeaders="getCellHeaders"
101
+ :isActual="isActual"
102
+ :columnWidth="columnWidth"
103
+ :classes="classes"
104
+ :value="value"
105
+ >
106
+ <template v-for="(_, name) in $slots" v-slot:[name]="slotData">
107
+ <slot :name="name" v-bind="slotData" />
108
+ </template>
109
+ </UluTableStickyRows>
110
+ </tbody>
111
+ </template>
112
+ <template v-if="footerRows">
113
+ <tfoot>
114
+ <UluTableStickyRows
115
+ :rows="footerRows"
116
+ :rowColumns="rowColumns"
117
+ :optionalAttr="optionalAttr"
118
+ :resolveClasses="resolveClasses"
119
+ :getCellHeaders="getCellHeaders"
120
+ :isActual="isActual"
121
+ :columnWidth="columnWidth"
122
+ :classes="classes"
123
+ :value="value"
124
+ foot
125
+ >
126
+ <template v-for="(_, name) in $slots" v-slot:[name]="slotData">
127
+ <slot :name="name" v-bind="slotData" />
128
+ </template>
129
+ </UluTableStickyRows>
130
+ </tfoot>
131
+ </template>
132
+ </table>
133
+ </template>
134
+
135
+ <script>
136
+ import UluTableStickyRows from "./UluTableStickyRows.vue";
137
+ export default {
138
+ name: "UluTableStickyTable",
139
+ components: {
140
+ UluTableStickyRows,
141
+ },
142
+ props: {
143
+ resolveClasses: Function,
144
+ classes: {
145
+ type: Object,
146
+ default: () => ({})
147
+ },
148
+ caption: String,
149
+ idPrefix: String,
150
+ headerRows: {
151
+ type: Array,
152
+ required: true
153
+ },
154
+ rows: Array,
155
+ footerRows: Array,
156
+ rowColumns: Array,
157
+ /**
158
+ * Is the actual table not a clone for sticky headers
159
+ */
160
+ isActual: {
161
+ type: Boolean
162
+ },
163
+ columnWidth: {
164
+ type: String
165
+ },
166
+ /**
167
+ * Optional user overridden value getter (for rows)
168
+ * @param {Object} row The current row
169
+ * @param {Object} column The current column in the row
170
+ */
171
+ getRowValue: {
172
+ type: Function,
173
+ default: ({ row, column }) => row[column.key]
174
+ },
175
+ /**
176
+ * Optional user overridden value getter (for rows)
177
+ * @param {Object} row The current row
178
+ * @param {Object} column The current column in the row
179
+ */
180
+ getColumnTitle: {
181
+ type: Function,
182
+ default: ({ column }) => column.title
183
+ },
184
+ },
185
+ data() {
186
+ return {
187
+ headerRefs: {}
188
+ };
189
+ },
190
+ methods: {
191
+ handleSortFocus(column, isFocused) {
192
+ if (this.isActual) {
193
+ column.sortFocused = isFocused;
194
+ }
195
+ },
196
+ addHeaderRef(column, el) {
197
+ const { headerRefs, isActual } = this;
198
+ if (!isActual || !el) return;
199
+ const { id } = column;
200
+ const old = headerRefs[id];
201
+ if (old) {
202
+ this.$emit("actualHeaderRemoved", old);
203
+ }
204
+ this.$emit("actualHeaderAdded", el);
205
+ headerRefs[id] = el;
206
+ },
207
+ /**
208
+ * False is no longer not printed
209
+ */
210
+ optionalAttr(val) {
211
+ return val ? val : null;
212
+ },
213
+ value({ row, column, rowIndex }) {
214
+ const value = column.value;
215
+ // Column has value function, pass to user
216
+ if (value) {
217
+ return value({ row: row.data, column, rowIndex });
218
+ } else {
219
+ // added .value to work with data. Should refactor depending on what we need
220
+ return this.getRowValue({ row: row.data, column, rowIndex });
221
+ }
222
+ },
223
+ getCellHeaders(column, rowIndex) {
224
+ const headers = column.headers.join(" ");
225
+ const rowHeaders = column.getRowHeaders(rowIndex);
226
+ const s = rowHeaders.length ? " " : "";
227
+ return `${ headers }${ s }${ rowHeaders }`;
228
+ },
229
+ getHeaderHeaders(column) {
230
+ const headersArray = column.headers.filter(id => id !== column.id);
231
+ if (headersArray.length) {
232
+ return headersArray.join(" ");
233
+ }
234
+ }
235
+ }
236
+ };
237
+ </script>
@@ -0,0 +1,185 @@
1
+ ////
2
+ /// @group table-sticky
3
+ /// For use with table-sticky plugin (vue) or other framework implementations, not output by default must be enabled.
4
+ ////
5
+
6
+ @use "sass:map";
7
+ @use "sass:meta";
8
+
9
+ @use "@ulu/frontend/scss/selector";
10
+ @use "@ulu/frontend/scss/utils";
11
+ @use "@ulu/frontend/scss/color";
12
+ @use "@ulu/frontend/scss/element";
13
+
14
+
15
+ // Used for function fallback
16
+ $-fallbacks: (
17
+ "box-shadow" : (
18
+ "function" : meta.get-function("get", false, "element"),
19
+ "property" : "box-shadow"
20
+ ),
21
+ );
22
+
23
+ /// Module Settings
24
+ /// @type Map
25
+ /// @prop {CssValue} box-shadow [true] Box shadow for controls, defaults to element box-shadow
26
+
27
+ $config: (
28
+ "box-shadow": true,
29
+ "ui-color-disabled": #6490af,
30
+ ) !default;
31
+
32
+ /// Change modules $config
33
+ /// @param {Map} $changes Map of changes
34
+ /// @example scss
35
+ /// @include ulu.component-table-sticky-set(( "property" : value ));
36
+
37
+ @mixin set($changes) {
38
+ $config: map.merge($config, $changes) !global;
39
+ }
40
+
41
+ /// Get a config option
42
+ /// @param {Map} $name Name of property
43
+ /// @example scss
44
+ /// @include ulu.component-table-sticky-get("property");
45
+
46
+ @function get($name) {
47
+ $value: utils.require-map-get($config, $name, "table-sticky [config]");
48
+ @return utils.function-fallback($name, $value, $-fallbacks);
49
+ }
50
+ /// Prints component styles
51
+ /// @demo table-sticky
52
+ /// @example scss
53
+ /// @include ulu.component-table-sticky-styles();
54
+
55
+ @mixin styles {
56
+ $prefix: selector.class("table-sticky");
57
+
58
+ #{ $prefix } {
59
+ position: relative; // For controls
60
+ width: 100%;
61
+ * {
62
+ box-sizing: border-box;
63
+ }
64
+ }
65
+ #{ $prefix }__display {
66
+ overflow-x: auto;
67
+ overflow-y: hidden;
68
+ // scroll-behavior: smooth;
69
+ }
70
+ #{ $prefix }__table {
71
+ // border-collapse: collapse;
72
+ margin: 0;
73
+ padding: 0;
74
+ width: 100%;
75
+ }
76
+ #{ $prefix }__sticky-wrap {
77
+ position: sticky;
78
+ top: -1px;
79
+ left: 0;
80
+ width: 100%;
81
+ z-index: 2;
82
+ margin-bottom: 200px; // Distance from bottom of table to disable
83
+ margin-top: -200px; // Distance from bottom of table to disable
84
+ }
85
+ #{ $prefix }__sticky-wrap--first-column-header {
86
+ z-index: 4;
87
+ }
88
+ #{ $prefix }__sticky-wrap--controls {
89
+ margin-top: -52vh;
90
+ margin-bottom: 52vh;
91
+ }
92
+ #{ $prefix }__header-wrap {
93
+ position: absolute;
94
+ top: 0;
95
+ left: 0;
96
+ right: 0;
97
+ overflow: hidden;
98
+ }
99
+ #{ $prefix }__table--first-column-header,
100
+ #{ $prefix }__table--first-column {
101
+ position: absolute;
102
+ top: 0;
103
+ left: 0;
104
+ width: auto !important;
105
+ }
106
+ #{ $prefix }__table--first-column {
107
+ z-index: 3;
108
+ }
109
+ #{ $prefix }__table--header {
110
+ will-change: transform;
111
+ }
112
+ #{ $prefix }__hidden-visually {
113
+ position: absolute;
114
+ left: -10000px;
115
+ top: auto;
116
+ width: 1px;
117
+ height: 1px;
118
+ overflow: hidden;
119
+ }
120
+ // NOTE: The table caption needs to be positioned normally
121
+ // as display table-cell. Making absolute for hidden-visually
122
+ // is causing chrome to add an anonymous cell to the table resulting in a 1px
123
+ // cell under the header. Which is messing up alignments. The only solution
124
+ // without removing the <caption> (not good for WCAG) is to position it at the
125
+ // of the table so it doesn't affect the header alignments. Then cropping it to
126
+ // a pixel and using negative margin to remove it's pixel from the flow beneath
127
+ #{ $prefix }__caption {
128
+ margin-bottom: -1px;
129
+ height: 1px;
130
+ padding: 0 !important;
131
+ margin: 0 !important;
132
+ overflow: hidden;
133
+ display: none;
134
+ }
135
+ // Allow initiating scrolling by dragging first column
136
+ #{ $prefix }--can-scroll-right:not(#{ $prefix }--can-scroll-left) {
137
+ #{ $prefix }__table--first-column-header,
138
+ #{ $prefix }__table--first-column {
139
+ // pointer-events: none;
140
+ visibility: hidden;
141
+ // opacity: 0 !important;
142
+ }
143
+ }
144
+ #{ $prefix }__controls {
145
+ position: absolute;
146
+ top: 50vh;
147
+ right: 20px;
148
+ }
149
+ #{ $prefix }__controls-inner {
150
+ display: flex;
151
+ }
152
+ #{ $prefix }__control {
153
+ box-shadow: get("box-shadow");
154
+ }
155
+ #{ $prefix }__sort-button {
156
+ display: flex;
157
+ align-items: center;
158
+ font-weight: inherit;
159
+ font-style: inherit;
160
+ font-size: inherit;
161
+ }
162
+ #{ $prefix }__sort-button--focused {
163
+ outline: 2px auto Highlight;
164
+ outline: 2px auto -webkit-focus-ring-color;
165
+ }
166
+ #{ $prefix }__sort-icon {
167
+ margin-left: auto;
168
+ padding-left: 1em;
169
+ display: block;
170
+ opacity: 0.5;
171
+ .sort-active & {
172
+ opacity: 1;
173
+ }
174
+ .sort-ascending & {
175
+ #{ $prefix }__sort-icon-inner {
176
+ transform: rotate(180deg);
177
+ }
178
+ }
179
+ }
180
+ #{ $prefix }__sort-icon-inner {
181
+ display: block;
182
+ }
183
+ }
184
+
185
+
@@ -0,0 +1,28 @@
1
+ <template>
2
+ <!-- Intentional Coercion to null (undefined and null, else print) -->
3
+ <component v-if="text != null" :is="element">
4
+ {{ text }}
5
+ </component>
6
+ </template>
7
+
8
+ <script>
9
+ /**
10
+ * Print out text if set (has value)
11
+ */
12
+ export default {
13
+ name: "UluCondText",
14
+ props: {
15
+ /**
16
+ * Text to print in element
17
+ */
18
+ text: [String, Number, Array, Object],
19
+ /**
20
+ * Element type to render (ie. h1, h2, p, etc)
21
+ */
22
+ element: {
23
+ type: String,
24
+ default: "p"
25
+ }
26
+ }
27
+ }
28
+ </script>
@@ -0,0 +1,3 @@
1
+ <template>
2
+ <span style="display: none"></span>
3
+ </template>
@@ -0,0 +1,3 @@
1
+ <template>
2
+ <router-view></router-view>
3
+ </template>
@@ -0,0 +1,53 @@
1
+ <template>
2
+ <img :src="src" :alt="alt">
3
+ </template>
4
+ <script>
5
+ import { randomInt } from "@ulu/utils/random.js";
6
+ export default {
7
+ name: "UluPlaceholderImage",
8
+ props: {
9
+ imageId: String,
10
+ /**
11
+ * Width of the image
12
+ */
13
+ width: {
14
+ type: [String, Number],
15
+ default: "300"
16
+ },
17
+ /**
18
+ * Height of the image
19
+ */
20
+ height: {
21
+ type: [String, Number],
22
+ default: "400"
23
+ },
24
+ /**
25
+ * Alt text for placeholder image
26
+ */
27
+ alt: String,
28
+ /**
29
+ * Random size
30
+ */
31
+ random: Boolean
32
+ },
33
+ computed: {
34
+ src() {
35
+ const { imageId } = this;
36
+ const { width, height } = this.size;
37
+ const id = imageId ? `id/${ imageId }/` : "";
38
+ return `https://picsum.photos/${ id }${ width }/${ height }`;
39
+ },
40
+ size() {
41
+ const { random, width, height } = this;
42
+ if (random) {
43
+ return {
44
+ width: randomInt(500, 1000),
45
+ height: randomInt(500, 1000),
46
+ };
47
+ } else {
48
+ return { width, height };
49
+ }
50
+ }
51
+ }
52
+ };
53
+ </script>
@@ -0,0 +1,25 @@
1
+ <template>
2
+ <component
3
+ v-for="index in parseInt(amount)"
4
+ :key="index"
5
+ :is="element"
6
+ >
7
+ Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed semper erat tincidunt tellus vestibulum dictum. Fusce vel augue commodo, egestas diam sed, accumsan leo. Maecenas congue nec nisl et ullamcorper. Maecenas tincidunt, tortor et viverra eleifend, enim leo vestibulum ipsum, quis placerat mi nisi nec ex. Vivamus a justo volutpat, scelerisque elit eget, lacinia ex. Phasellus dapibus sollicitudin tortor, vitae suscipit nunc condimentum ut. Cras suscipit feugiat nibh nec consectetur. Phasellus vitae quam blandit, cursus metus ut, placerat mi. Sed tempor lacus non est interdum imperdiet nec quis metus. Praesent vel eleifend diam. Donec tincidunt eget purus sed posuere.
8
+ </component>
9
+ </template>
10
+
11
+ <script>
12
+ export default {
13
+ name: "PlaceholderText",
14
+ props: {
15
+ amount: {
16
+ type: Number,
17
+ default: 1
18
+ },
19
+ element: {
20
+ type: String,
21
+ default: "p"
22
+ }
23
+ },
24
+ };
25
+ </script>
@@ -0,0 +1,83 @@
1
+ <!--
2
+ Route Announcer:
3
+ - Used to provide accessible title after route (page) change
4
+ - Will ignore any routes that have hashes
5
+ - Props
6
+ - exclude {Array} You can exclude specific routes () by exact path or path with wildcard at end, alternatively use the 'confirm' prop for complete control
7
+ - validator {Function} Pass a function to determine if current route should be announced
8
+ - getTitle {Function} Provide method for extracting title from route return string
9
+
10
+ Should include <UluRouteAnnouncer /> as first component in app
11
+ -->
12
+ <template>
13
+ <p
14
+ v-if="title"
15
+ tabindex="-1"
16
+ class="hidden-visually"
17
+ ref="el"
18
+ >
19
+ {{ title }}
20
+ </p>
21
+ </template>
22
+
23
+ <script>
24
+ export default {
25
+ name: "RouteAnnouncer",
26
+ props: {
27
+ /**
28
+ * Allow user to bypass this functionality
29
+ * - Function should return true if the page should be annouced
30
+ * - Function is passed (to, from, $route) => {}
31
+ * - to/from are path strings
32
+ */
33
+ validator: {
34
+ type: Function,
35
+ default: () => true
36
+ },
37
+ /**
38
+ * Array of paths to exclude
39
+ * - Can be exact path "/about"
40
+ * - Or can be path with wildcard "/about/*" (match all paths under about)
41
+ */
42
+ exclude: {
43
+ type: Array,
44
+ default: () => []
45
+ },
46
+ /**
47
+ * Function to retrieve routes title
48
+ */
49
+ getTitle: {
50
+ type: Function,
51
+ default: (route) => route.meta?.title
52
+ }
53
+ },
54
+ watch: {
55
+ "$route.path"(to, from) {
56
+ if (this.$route.hash) {
57
+ return;
58
+ }
59
+ const isValid = this.validator(to, from, this.$route);
60
+ const isExcluded = this.exclude.some(ex => {
61
+ // Allow wildcard at end to exclude entire sections, etc
62
+ if (ex.endsWith("*")) {
63
+ return to.startsWith(ex.slice(0, ex.length - 1));
64
+ } else {
65
+ return to === ex;
66
+ }
67
+ });
68
+ if (isValid && !isExcluded) {
69
+ this.$refs.el.focus();
70
+ }
71
+ }
72
+ },
73
+ computed: {
74
+ title() {
75
+ const title = this.getTitle(this.$route);
76
+ if (!title) {
77
+ console.warn("RouteAnnouncer: No page title!");
78
+ }
79
+ return title;
80
+ }
81
+ }
82
+ };
83
+ </script>