|
3 | 3 | - SPDX-License-Identifier: AGPL-3.0-or-later |
4 | 4 | --> |
5 | 5 | <template> |
6 | | - <div class="actions__item" :class="{ colored: colored }" :style="{ backgroundColor: colored ? operation.color : 'transparent' }"> |
| 6 | + <div |
| 7 | + ref="operationElement" |
| 8 | + class="actions__item" |
| 9 | + :class="{ colored: colored }"> |
7 | 10 | <div class="icon" :class="operation.iconClass" :style="{ backgroundImage: operation.iconClass ? '' : `url(${operation.icon})` }" /> |
8 | 11 | <div class="actions__item__description"> |
9 | 12 | <h3>{{ operation.name }}</h3> |
|
18 | 21 | </div> |
19 | 22 | </template> |
20 | 23 |
|
21 | | -<script> |
| 24 | +<script setup lang="ts"> |
| 25 | +/* eslint vue/multi-word-component-names: "warn" */ |
| 26 | +
|
| 27 | +import { t } from '@nextcloud/l10n' |
| 28 | +import Color from 'color' |
| 29 | +import { computed, nextTick, ref, watch } from 'vue' |
22 | 30 | import NcButton from '@nextcloud/vue/components/NcButton' |
23 | 31 |
|
24 | | -export default { |
25 | | - /* eslint vue/multi-word-component-names: "warn" */ |
26 | | - name: 'Operation', |
27 | | - components: { |
28 | | - NcButton, |
29 | | - }, |
30 | | -
|
31 | | - props: { |
32 | | - operation: { |
33 | | - type: Object, |
34 | | - required: true, |
35 | | - }, |
36 | | -
|
37 | | - colored: { |
38 | | - type: Boolean, |
39 | | - default: true, |
40 | | - }, |
41 | | - }, |
42 | | -} |
| 32 | +const props = defineProps<{ |
| 33 | + operation: Record<string, string> |
| 34 | + colored?: boolean |
| 35 | +}>() |
| 36 | +
|
| 37 | +const operationElement = ref<HTMLDivElement>() |
| 38 | +const color = ref('var(--color-main-text)') |
| 39 | +const backgroundColor = computed(() => props.colored ? (props.operation.color || 'var(--color-primary-element)') : 'transparent') |
| 40 | +
|
| 41 | +watch(backgroundColor, async () => { |
| 42 | + if (backgroundColor.value === 'transparent') { |
| 43 | + color.value = 'var(--color-main-text)' |
| 44 | + return |
| 45 | + } else if (backgroundColor.value === 'var(--color-primary-element)') { |
| 46 | + color.value = 'var(--color-primary-element-text)' |
| 47 | + return |
| 48 | + } |
| 49 | +
|
| 50 | + let bgColor = backgroundColor.value |
| 51 | + if (!bgColor.startsWith('#')) { |
| 52 | + await nextTick() |
| 53 | + bgColor = window.getComputedStyle(operationElement.value!).backgroundColor |
| 54 | + } |
| 55 | + try { |
| 56 | + const contrast = Color(bgColor).contrast(Color('#ffffff')) |
| 57 | + color.value = contrast > 4.5 ? '#ffffff' : '#000000' |
| 58 | + } catch { |
| 59 | + color.value = 'var(--color-main-text)' |
| 60 | + } |
| 61 | +}, { immediate: true }) |
| 62 | +
|
| 63 | +/** |
| 64 | + * Filter to apply to the icon to make it accessible on the given background color. |
| 65 | + */ |
| 66 | +const iconFilter = computed(() => { |
| 67 | + if (color.value === '#000000') { |
| 68 | + return 'invert(100%)' |
| 69 | + } |
| 70 | + return 'none' |
| 71 | +}) |
43 | 72 | </script> |
44 | 73 |
|
45 | 74 | <style scoped lang="scss"> |
46 | 75 | @use "./../styles/operation.scss" as *; |
| 76 | +
|
| 77 | +.actions__item { |
| 78 | + color: v-bind('color'); |
| 79 | + background-color: v-bind('backgroundColor'); |
| 80 | +
|
| 81 | + h3 { |
| 82 | + color: v-bind('color'); |
| 83 | + } |
| 84 | +
|
| 85 | + .icon { |
| 86 | + filter: v-bind('iconFilter'); |
| 87 | + } |
| 88 | +} |
47 | 89 | </style> |
0 commit comments