@xen-orchestra/web-core 0.31.1 → 0.33.0

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 (75) hide show
  1. package/lib/assets/css/_colors.pcss +8 -0
  2. package/lib/components/button-group/VtsButtonGroup.vue +5 -1
  3. package/lib/components/menu/MenuList.vue +1 -2
  4. package/lib/components/menu/MenuTrigger.vue +5 -11
  5. package/lib/components/modal/VtsModal.vue +82 -0
  6. package/lib/components/modal/VtsModalButton.vue +36 -0
  7. package/lib/components/modal/VtsModalCancelButton.vue +37 -0
  8. package/lib/components/modal/VtsModalConfirmButton.vue +21 -0
  9. package/lib/components/modal/VtsModalList.vue +34 -0
  10. package/lib/components/object-icon/VtsObjectIcon.vue +3 -8
  11. package/lib/components/status/VtsStatus.vue +66 -0
  12. package/lib/components/task/VtsQuickTaskList.vue +17 -5
  13. package/lib/components/tree/VtsTreeItem.vue +2 -2
  14. package/lib/components/ui/breadcrumb/UiBreadcrumb.vue +79 -0
  15. package/lib/components/ui/button/UiButton.vue +13 -67
  16. package/lib/components/ui/modal/UiModal.vue +164 -0
  17. package/lib/components/ui/quick-task-item/UiQuickTaskItem.vue +2 -2
  18. package/lib/composables/context.composable.ts +3 -5
  19. package/lib/composables/link-component.composable.ts +3 -2
  20. package/lib/composables/pagination.composable.ts +3 -2
  21. package/lib/composables/tree-filter.composable.ts +5 -3
  22. package/lib/icons/fa-icons.ts +13 -1
  23. package/lib/icons/index.ts +17 -0
  24. package/lib/locales/cs.json +60 -2
  25. package/lib/locales/de.json +40 -2
  26. package/lib/locales/en.json +27 -1
  27. package/lib/locales/es.json +51 -5
  28. package/lib/locales/fa.json +10 -10
  29. package/lib/locales/fr.json +28 -2
  30. package/lib/locales/it.json +4 -0
  31. package/lib/locales/nl.json +64 -14
  32. package/lib/locales/pt_BR.json +3 -3
  33. package/lib/locales/ru.json +41 -2
  34. package/lib/locales/sv.json +55 -1
  35. package/lib/locales/uk.json +4 -4
  36. package/lib/packages/collection/use-collection.ts +3 -2
  37. package/lib/packages/form-select/use-form-option-controller.ts +3 -2
  38. package/lib/packages/form-select/use-form-select.ts +8 -7
  39. package/lib/packages/menu/action.ts +4 -3
  40. package/lib/packages/menu/link.ts +5 -4
  41. package/lib/packages/menu/router-link.ts +3 -2
  42. package/lib/packages/menu/toggle-target.ts +3 -2
  43. package/lib/packages/modal/ModalProvider.vue +17 -0
  44. package/lib/packages/modal/README.md +253 -0
  45. package/lib/packages/modal/create-modal-opener.ts +103 -0
  46. package/lib/packages/modal/modal.store.ts +22 -0
  47. package/lib/packages/modal/types.ts +92 -0
  48. package/lib/packages/modal/use-modal.ts +53 -0
  49. package/lib/packages/progress/use-progress.ts +4 -3
  50. package/lib/packages/table/README.md +336 -0
  51. package/lib/packages/table/apply-extensions.ts +26 -0
  52. package/lib/packages/table/define-columns.ts +62 -0
  53. package/lib/packages/table/define-renderer/define-table-cell-renderer.ts +27 -0
  54. package/lib/packages/table/define-renderer/define-table-renderer.ts +47 -0
  55. package/lib/packages/table/define-renderer/define-table-row-renderer.ts +29 -0
  56. package/lib/packages/table/define-renderer/define-table-section-renderer.ts +29 -0
  57. package/lib/packages/table/define-table/define-multi-source-table.ts +39 -0
  58. package/lib/packages/table/define-table/define-table.ts +13 -0
  59. package/lib/packages/table/define-table/define-typed-table.ts +18 -0
  60. package/lib/packages/table/index.ts +11 -0
  61. package/lib/packages/table/transform-sources.ts +13 -0
  62. package/lib/packages/table/types/extensions.ts +16 -0
  63. package/lib/packages/table/types/index.ts +47 -0
  64. package/lib/packages/table/types/table-cell.ts +18 -0
  65. package/lib/packages/table/types/table-row.ts +20 -0
  66. package/lib/packages/table/types/table-section.ts +19 -0
  67. package/lib/packages/table/types/table.ts +28 -0
  68. package/lib/packages/threshold/use-threshold.ts +4 -3
  69. package/lib/types/vue-virtual-scroller.d.ts +101 -0
  70. package/lib/utils/injection-keys.util.ts +3 -0
  71. package/lib/utils/progress.util.ts +2 -1
  72. package/lib/utils/to-computed.util.ts +15 -0
  73. package/package.json +3 -2
  74. package/lib/components/backup-state/VtsBackupState.vue +0 -37
  75. package/lib/components/connection-status/VtsConnectionStatus.vue +0 -36
@@ -1,4 +1,4 @@
1
- <!-- v6 -->
1
+ <!-- v7 -->
2
2
  <template>
3
3
  <button :class="classNames" :disabled="busy || isDisabled || lockIcon" class="ui-button" type="button">
4
4
  <VtsIcon :busy :name="leftIcon" class="icon" size="current" />
@@ -14,9 +14,9 @@ import type { IconName } from '@core/icons'
14
14
  import { toVariants } from '@core/utils/to-variants.util'
15
15
  import { computed } from 'vue'
16
16
 
17
- type ButtonVariant = 'primary' | 'secondary' | 'tertiary'
18
- type ButtonAccent = 'brand' | 'warning' | 'danger'
19
- type ButtonSize = 'small' | 'medium'
17
+ export type ButtonVariant = 'primary' | 'secondary' | 'tertiary'
18
+ export type ButtonAccent = 'brand' | 'warning' | 'danger'
19
+ export type ButtonSize = 'small' | 'medium'
20
20
 
21
21
  const { accent, variant, size, disabled, busy, lockIcon } = defineProps<{
22
22
  variant: ButtonVariant
@@ -111,17 +111,11 @@ const classNames = computed(() => [
111
111
  color: var(--color-brand-txt-item);
112
112
  }
113
113
 
114
- &:is(:disabled, .disabled) {
114
+ &:is(:disabled, .disabled, .busy) {
115
115
  background-color: var(--color-brand-item-disabled);
116
116
  border-color: var(--color-brand-item-disabled);
117
117
  color: var(--color-neutral-txt-secondary);
118
118
  }
119
-
120
- &.busy {
121
- background-color: var(--color-brand-item-base);
122
- border-color: var(--color-brand-item-base);
123
- color: var(--color-brand-txt-item);
124
- }
125
119
  }
126
120
 
127
121
  &.variant--secondary {
@@ -145,17 +139,11 @@ const classNames = computed(() => [
145
139
  color: var(--color-brand-txt-active);
146
140
  }
147
141
 
148
- &:is(:disabled, .disabled) {
142
+ &:is(:disabled, .disabled, .busy) {
149
143
  background-color: var(--color-neutral-background-disabled);
150
144
  border-color: var(--color-neutral-txt-secondary);
151
145
  color: var(--color-neutral-txt-secondary);
152
146
  }
153
-
154
- &.busy {
155
- background-color: var(--color-neutral-background-primary);
156
- border-color: var(--color-brand-item-base);
157
- color: var(--color-brand-txt-base);
158
- }
159
147
  }
160
148
 
161
149
  &.variant--tertiary {
@@ -179,17 +167,11 @@ const classNames = computed(() => [
179
167
  color: var(--color-brand-txt-active);
180
168
  }
181
169
 
182
- &:is(:disabled, .disabled) {
170
+ &:is(:disabled, .disabled, .busy) {
183
171
  background-color: transparent;
184
172
  border-color: transparent;
185
173
  color: var(--color-neutral-txt-secondary);
186
174
  }
187
-
188
- &.busy {
189
- background-color: var(--color-brand-background-selected);
190
- border-color: var(--color-brand-background-selected);
191
- color: var(--color-brand-txt-base);
192
- }
193
175
  }
194
176
  }
195
177
 
@@ -215,17 +197,11 @@ const classNames = computed(() => [
215
197
  color: var(--color-warning-txt-item);
216
198
  }
217
199
 
218
- &:is(:disabled, .disabled) {
200
+ &:is(:disabled, .disabled, .busy) {
219
201
  background-color: var(--color-warning-item-disabled);
220
202
  border-color: var(--color-warning-item-disabled);
221
203
  color: var(--color-neutral-txt-secondary);
222
204
  }
223
-
224
- &.busy {
225
- background-color: var(--color-warning-item-base);
226
- border-color: var(--color-warning-item-base);
227
- color: var(--color-warning-txt-item);
228
- }
229
205
  }
230
206
 
231
207
  &.variant--secondary {
@@ -249,17 +225,11 @@ const classNames = computed(() => [
249
225
  color: var(--color-warning-txt-active);
250
226
  }
251
227
 
252
- &:is(:disabled, .disabled) {
228
+ &:is(:disabled, .disabled, .busy) {
253
229
  background-color: var(--color-neutral-background-disabled);
254
230
  border-color: var(--color-neutral-txt-secondary);
255
231
  color: var(--color-neutral-txt-secondary);
256
232
  }
257
-
258
- &.busy {
259
- background-color: var(--color-neutral-background-primary);
260
- border-color: var(--color-warning-txt-base);
261
- color: var(--color-warning-txt-base);
262
- }
263
233
  }
264
234
 
265
235
  &.variant--tertiary {
@@ -283,17 +253,11 @@ const classNames = computed(() => [
283
253
  color: var(--color-warning-txt-active);
284
254
  }
285
255
 
286
- &:is(:disabled, .disabled) {
256
+ &:is(:disabled, .disabled, .busy) {
287
257
  background-color: transparent;
288
258
  border-color: transparent;
289
259
  color: var(--color-neutral-txt-secondary);
290
260
  }
291
-
292
- &.busy {
293
- background-color: var(--color-warning-background-selected);
294
- border-color: var(--color-warning-background-selected);
295
- color: var(--color-warning-txt-base);
296
- }
297
261
  }
298
262
  }
299
263
 
@@ -319,17 +283,11 @@ const classNames = computed(() => [
319
283
  color: var(--color-danger-txt-item);
320
284
  }
321
285
 
322
- &:is(:disabled, .disabled) {
286
+ &:is(:disabled, .disabled, .busy) {
323
287
  background-color: var(--color-danger-item-disabled);
324
288
  border-color: var(--color-danger-item-disabled);
325
289
  color: var(--color-neutral-txt-secondary);
326
290
  }
327
-
328
- &.busy {
329
- background-color: var(--color-danger-item-base);
330
- border-color: var(--color-danger-item-base);
331
- color: var(--color-danger-txt-item);
332
- }
333
291
  }
334
292
 
335
293
  &.variant--secondary {
@@ -353,17 +311,11 @@ const classNames = computed(() => [
353
311
  color: var(--color-danger-txt-active);
354
312
  }
355
313
 
356
- &:is(:disabled, .disabled) {
314
+ &:is(:disabled, .disabled, .busy) {
357
315
  background-color: var(--color-neutral-background-disabled);
358
316
  border-color: var(--color-neutral-txt-secondary);
359
317
  color: var(--color-neutral-txt-secondary);
360
318
  }
361
-
362
- &.busy {
363
- background-color: var(--color-neutral-background-primary);
364
- border-color: var(--color-danger-txt-base);
365
- color: var(--color-danger-txt-base);
366
- }
367
319
  }
368
320
 
369
321
  &.variant--tertiary {
@@ -387,17 +339,11 @@ const classNames = computed(() => [
387
339
  color: var(--color-danger-txt-active);
388
340
  }
389
341
 
390
- &:is(:disabled, .disabled) {
342
+ &:is(:disabled, .disabled, .busy) {
391
343
  background-color: transparent;
392
344
  border-color: transparent;
393
345
  color: var(--color-neutral-txt-secondary);
394
346
  }
395
-
396
- &.busy {
397
- background-color: var(--color-danger-background-selected);
398
- border-color: var(--color-danger-background-selected);
399
- color: var(--color-danger-txt-base);
400
- }
401
347
  }
402
348
  }
403
349
 
@@ -0,0 +1,164 @@
1
+ <!-- v2 -->
2
+ <template>
3
+ <form :class="className" class="ui-modal" @click.self="emit('dismiss')">
4
+ <div class="modal">
5
+ <UiButtonIcon
6
+ v-if="onDismiss"
7
+ :accent="closeIconAccent"
8
+ :target-scale="2"
9
+ class="dismiss-button"
10
+ icon="fa:xmark"
11
+ size="small"
12
+ @click="emit('dismiss')"
13
+ />
14
+ <main class="main">
15
+ <VtsIcon v-if="icon" :name="icon" class="icon" size="current" />
16
+ <div v-if="slots.title" class="typo-h4">
17
+ <slot name="title" />
18
+ </div>
19
+ <div class="content">
20
+ <slot name="content" />
21
+ </div>
22
+ </main>
23
+ <VtsButtonGroup v-if="slots.buttons" class="buttons">
24
+ <slot name="buttons" />
25
+ </VtsButtonGroup>
26
+ </div>
27
+ </form>
28
+ </template>
29
+
30
+ <script lang="ts" setup>
31
+ import VtsButtonGroup from '@core/components/button-group/VtsButtonGroup.vue'
32
+ import VtsIcon from '@core/components/icon/VtsIcon.vue'
33
+ import UiButtonIcon from '@core/components/ui/button-icon/UiButtonIcon.vue'
34
+ import type { IconName } from '@core/icons'
35
+ import { useMapper } from '@core/packages/mapper'
36
+ import { toVariants } from '@core/utils/to-variants.util.ts'
37
+ import { computed } from 'vue'
38
+
39
+ export type ModalAccent = 'info' | 'success' | 'warning' | 'danger'
40
+
41
+ const { accent } = defineProps<{
42
+ accent: ModalAccent
43
+ icon?: IconName
44
+ onDismiss?: () => void
45
+ }>()
46
+
47
+ const emit = defineEmits<{
48
+ dismiss: []
49
+ }>()
50
+
51
+ const slots = defineSlots<{
52
+ content(): any
53
+ buttons?(): any
54
+ title?(): any
55
+ }>()
56
+
57
+ const closeIconAccent = useMapper(
58
+ () => accent,
59
+ {
60
+ info: 'brand',
61
+ success: 'brand',
62
+ warning: 'warning',
63
+ danger: 'danger',
64
+ },
65
+ 'info'
66
+ )
67
+
68
+ const className = computed(() => toVariants({ accent }))
69
+ </script>
70
+
71
+ <style lang="postcss" scoped>
72
+ .ui-modal {
73
+ position: fixed;
74
+ inset: 0;
75
+ display: flex;
76
+ justify-content: center;
77
+ align-items: center;
78
+
79
+ .modal {
80
+ display: flex;
81
+ flex-direction: column;
82
+ min-width: min(40rem, calc(100% - 2rem));
83
+ max-width: min(95vw, 120rem);
84
+ max-height: min(90vh, 80rem);
85
+ padding: 3.2rem 2.4rem 2.4rem;
86
+ gap: 2.4rem;
87
+ border-radius: 1rem;
88
+ overflow: auto;
89
+
90
+ &:not(:has(.buttons)) {
91
+ padding-bottom: 3.2rem;
92
+ }
93
+
94
+ .dismiss-button {
95
+ position: absolute;
96
+ top: 2.4rem;
97
+ right: 2.4rem;
98
+ z-index: 1;
99
+ }
100
+
101
+ &:not(:has(.icon)) .dismiss-button {
102
+ top: 1rem;
103
+ right: 1rem;
104
+ }
105
+
106
+ .main {
107
+ display: flex;
108
+ flex-direction: column;
109
+ align-items: center;
110
+ gap: 2.4rem;
111
+ text-align: center;
112
+ overflow: auto;
113
+
114
+ .icon {
115
+ font-size: 4.8rem;
116
+ }
117
+
118
+ .content {
119
+ width: 100%;
120
+ }
121
+ }
122
+ }
123
+
124
+ &.accent--info {
125
+ .modal {
126
+ background-color: var(--color-info-background-selected);
127
+ }
128
+
129
+ .main .icon {
130
+ color: var(--color-info-txt-base);
131
+ }
132
+ }
133
+
134
+ &.accent--success {
135
+ .modal {
136
+ background-color: var(--color-success-background-selected);
137
+ }
138
+
139
+ .main .icon {
140
+ color: var(--color-success-txt-base);
141
+ }
142
+ }
143
+
144
+ &.accent--warning {
145
+ .modal {
146
+ background-color: var(--color-warning-background-selected);
147
+ }
148
+
149
+ .main .icon {
150
+ color: var(--color-warning-txt-base);
151
+ }
152
+ }
153
+
154
+ &.accent--danger {
155
+ .modal {
156
+ background-color: var(--color-danger-background-selected);
157
+ }
158
+
159
+ .main .icon {
160
+ color: var(--color-danger-txt-base);
161
+ }
162
+ }
163
+ }
164
+ </style>
@@ -1,6 +1,6 @@
1
1
  <!-- WIP -->
2
2
  <template>
3
- <li class="ui-quick-task-item">
3
+ <div class="ui-quick-task-item">
4
4
  <div v-if="hasSubTasks" class="toggle" @click="toggleExpand()">
5
5
  <UiButtonIcon accent="brand" :icon="isExpanded ? 'fa:angle-down' : 'fa:angle-right'" size="small" />
6
6
  </div>
@@ -26,7 +26,7 @@
26
26
  </div>
27
27
  <VtsQuickTaskList v-if="hasSubTasks && isExpanded" :tasks="subTasks" sublist />
28
28
  </div>
29
- </li>
29
+ </div>
30
30
  </template>
31
31
 
32
32
  <script lang="ts" setup>
@@ -1,5 +1,6 @@
1
+ import { toComputed } from '@core/utils/to-computed.util'
1
2
  import type { ComputedRef, InjectionKey, MaybeRefOrGetter } from 'vue'
2
- import { computed, inject, provide, toValue } from 'vue'
3
+ import { inject, provide, toValue } from 'vue'
3
4
 
4
5
  export const createContext = <T, Output = ComputedRef<T>>(
5
6
  initialValue: MaybeRefOrGetter<T>,
@@ -27,8 +28,5 @@ export const useContext = <Ctx extends Context, T extends ContextValue<Ctx>>(
27
28
  const updatedValue = () => toValue(newValue) ?? toValue(currentValue) ?? context.initialValue
28
29
  provide(context.id, updatedValue)
29
30
 
30
- return context.builder(
31
- computed(() => toValue(updatedValue)),
32
- computed(() => toValue(currentValue))
33
- )
31
+ return context.builder(toComputed(updatedValue), toComputed(currentValue))
34
32
  }
@@ -1,5 +1,6 @@
1
+ import { toComputed } from '@core/utils/to-computed.util'
1
2
  import type { MaybeRefOrGetter } from 'vue'
2
- import { computed, toValue } from 'vue'
3
+ import { computed } from 'vue'
3
4
  import type { RouteLocationAsPathGeneric, RouteLocationAsRelativeGeneric, RouteLocationAsString } from 'vue-router'
4
5
 
5
6
  export type LinkOptions = {
@@ -11,7 +12,7 @@ export type LinkOptions = {
11
12
  }
12
13
 
13
14
  export function useLinkComponent(defaultComponent: string, options: MaybeRefOrGetter<LinkOptions>) {
14
- const config = computed(() => toValue(options))
15
+ const config = toComputed(options)
15
16
 
16
17
  const isDisabled = computed(() => config.value.disabled || (!config.value.to && !config.value.href))
17
18
 
@@ -1,11 +1,12 @@
1
1
  import type { TablePaginationSize } from '@core/components/ui/table-pagination/UiTablePagination.vue'
2
2
  import { useRouteQuery } from '@core/composables/route-query.composable'
3
3
  import { useUiStore } from '@core/stores/ui.store.ts'
4
+ import { toComputed } from '@core/utils/to-computed.util'
4
5
  import { clamp, useLocalStorage } from '@vueuse/core'
5
- import { computed, type MaybeRefOrGetter, toValue } from 'vue'
6
+ import { computed, type MaybeRefOrGetter } from 'vue'
6
7
 
7
8
  export function usePagination<T>(id: string, _records: MaybeRefOrGetter<T[]>) {
8
- const records = computed(() => toValue(_records))
9
+ const records = toComputed(_records)
9
10
 
10
11
  const showBy = useLocalStorage(`${id}.per-page`, 24)
11
12
 
@@ -5,10 +5,12 @@ import { computed, ref } from 'vue'
5
5
  export function useTreeFilter() {
6
6
  const filter = ref('')
7
7
  const debouncedFilter = refDebounced(filter, 500)
8
- const hasFilter = computed(() => debouncedFilter.value.trim().length > 0)
9
-
8
+ const hasFilter = computed(() => filter.value.trim().length > 0)
9
+ const isSearching = computed(() =>
10
+ filter.value.trim().length === 0 ? false : filter.value !== debouncedFilter.value
11
+ )
10
12
  const predicate = (node: TreeNodeBase) =>
11
13
  hasFilter.value ? node.label.toLocaleLowerCase().includes(debouncedFilter.value.toLocaleLowerCase()) : undefined
12
14
 
13
- return { filter, predicate }
15
+ return { filter, predicate, isSearching }
14
16
  }
@@ -1,13 +1,14 @@
1
1
  import { defineIconPack } from '@core/packages/icon/define-icon-pack.ts'
2
2
  import {
3
3
  faBuilding,
4
+ faCopy,
4
5
  faFile,
5
6
  faFolderClosed,
6
7
  faFolderOpen,
7
8
  faSquareCheck,
8
- faCopy,
9
9
  } from '@fortawesome/free-regular-svg-icons'
10
10
  import {
11
+ faA,
11
12
  faAlignLeft,
12
13
  faAngleDoubleLeft,
13
14
  faAngleDoubleRight,
@@ -24,6 +25,7 @@ import {
24
25
  faBars,
25
26
  faBarsProgress,
26
27
  faBook,
28
+ faCalendar,
27
29
  faCamera,
28
30
  faCaretDown,
29
31
  faCaretUp,
@@ -36,8 +38,10 @@ import {
36
38
  faCircleMinus,
37
39
  faCircleNotch,
38
40
  faCirclePlay,
41
+ faCircleUser,
39
42
  faCircleXmark,
40
43
  faCity,
44
+ faClock,
41
45
  faClose,
42
46
  faCode,
43
47
  faComments,
@@ -73,6 +77,7 @@ import {
73
77
  faLockOpen,
74
78
  faMagnifyingGlass,
75
79
  faMemory,
80
+ faMessage,
76
81
  faMicrochip,
77
82
  faMinus,
78
83
  faMoon,
@@ -106,6 +111,7 @@ import {
106
111
  } from '@fortawesome/free-solid-svg-icons'
107
112
 
108
113
  export const faIcons = defineIconPack({
114
+ a: { icon: faA },
109
115
  'align-left': { icon: faAlignLeft },
110
116
  'angle-double-left': { icon: faAngleDoubleLeft },
111
117
  'angle-double-right': { icon: faAngleDoubleRight },
@@ -124,6 +130,7 @@ export const faIcons = defineIconPack({
124
130
  book: { icon: faBook },
125
131
  building: { icon: faBuilding },
126
132
  camera: { icon: faCamera },
133
+ calendar: { icon: faCalendar },
127
134
  'caret-down': { icon: faCaretDown },
128
135
  'caret-up': { icon: faCaretUp },
129
136
  check: { icon: faCheck },
@@ -135,13 +142,16 @@ export const faIcons = defineIconPack({
135
142
  'circle-minus': { icon: faCircleMinus },
136
143
  'circle-notch': { icon: faCircleNotch },
137
144
  'circle-play': { icon: faCirclePlay },
145
+ 'circle-user': { icon: faCircleUser },
138
146
  'circle-xmark': { icon: faCircleXmark },
139
147
  city: { icon: faCity },
140
148
  close: { icon: faClose },
149
+ clock: { icon: faClock },
141
150
  code: { icon: faCode },
142
151
  comments: { icon: faComments },
143
152
  copy: { icon: faCopy },
144
153
  database: { icon: faDatabase },
154
+ date: { icon: faCalendar },
145
155
  desktop: { icon: faDesktop },
146
156
  display: { icon: faDisplay },
147
157
  'down-left-and-up-right-to-center': { icon: faDownLeftAndUpRightToCenter },
@@ -176,6 +186,7 @@ export const faIcons = defineIconPack({
176
186
  'lock-open': { icon: faLockOpen },
177
187
  'magnifying-glass': { icon: faMagnifyingGlass },
178
188
  memory: { icon: faMemory },
189
+ message: { icon: faMessage },
179
190
  microchip: { icon: faMicrochip },
180
191
  minus: { icon: faMinus },
181
192
  moon: { icon: faMoon },
@@ -199,6 +210,7 @@ export const faIcons = defineIconPack({
199
210
  star: { icon: faStar },
200
211
  stop: { icon: faStop },
201
212
  tags: { icon: faTags },
213
+ time: { icon: faClock },
202
214
  'thumb-tack': { icon: faThumbTack },
203
215
  'thumb-tack-slash': { icon: faThumbTackSlash },
204
216
  trash: { icon: faTrash },
@@ -17,3 +17,20 @@ export type ObjectIconName = Extract<IconName, `object:${string}:${string}`>
17
17
  export function icon<TName extends IconName>(name: TName): TName {
18
18
  return name
19
19
  }
20
+
21
+ export type ObjectStateByType = {
22
+ [TName in IconName as TName extends `object:${infer TType}:${string}`
23
+ ? TType
24
+ : never]: TName extends `object:${string}:${infer TType}` ? TType : never
25
+ }
26
+
27
+ export type ObjectType = keyof ObjectStateByType
28
+
29
+ export type ObjectState<TType extends ObjectType> = ObjectStateByType[TType]
30
+
31
+ export function objectIcon<TType extends ObjectType, TState extends ObjectState<TType>>(
32
+ type: TType,
33
+ state: TState
34
+ ): IconName {
35
+ return `object:${type}:${state}` as IconName
36
+ }