@antify/ui-module 1.3.0 → 1.4.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 (36) hide show
  1. package/dist/module.json +1 -1
  2. package/dist/runtime/components/AntTooltip.vue +69 -63
  3. package/dist/runtime/components/__stories/AntTooltip.stories.d.ts +5 -0
  4. package/dist/runtime/components/__stories/AntTooltip.stories.mjs +33 -11
  5. package/dist/runtime/components/buttons/AntActionButton.vue +35 -13
  6. package/dist/runtime/components/buttons/AntButton.vue +3 -3
  7. package/dist/runtime/components/buttons/AntCreateButton.vue +20 -8
  8. package/dist/runtime/components/buttons/AntDeleteButton.vue +20 -8
  9. package/dist/runtime/components/buttons/AntSaveAndNewButton.vue +21 -8
  10. package/dist/runtime/components/buttons/AntSaveButton.vue +20 -8
  11. package/dist/runtime/components/buttons/__stories/AntActionButton.stories.d.ts +1 -0
  12. package/dist/runtime/components/buttons/__stories/AntActionButton.stories.mjs +22 -0
  13. package/dist/runtime/components/buttons/__stories/AntCreateButton.stories.d.ts +1 -0
  14. package/dist/runtime/components/buttons/__stories/AntCreateButton.stories.mjs +9 -0
  15. package/dist/runtime/components/buttons/__stories/AntDeleteButton.stories.d.ts +1 -0
  16. package/dist/runtime/components/buttons/__stories/AntDeleteButton.stories.mjs +9 -0
  17. package/dist/runtime/components/buttons/__stories/AntSaveAndNewButton.stories.d.ts +1 -0
  18. package/dist/runtime/components/buttons/__stories/AntSaveAndNewButton.stories.mjs +9 -0
  19. package/dist/runtime/components/buttons/__stories/AntSaveButton.stories.d.ts +1 -0
  20. package/dist/runtime/components/buttons/__stories/AntSaveButton.stories.mjs +9 -0
  21. package/dist/runtime/components/crud/AntCrudDetailActions.vue +10 -3
  22. package/dist/runtime/components/crud/AntCrudDetailNav.vue +21 -15
  23. package/dist/runtime/components/crud/AntCrudTableFilter.vue +35 -33
  24. package/dist/runtime/utils.d.ts +6 -0
  25. package/dist/runtime/utils.mjs +21 -0
  26. package/package.json +1 -1
  27. package/src/runtime/components/AntTooltip.vue +69 -63
  28. package/src/runtime/components/buttons/AntActionButton.vue +35 -13
  29. package/src/runtime/components/buttons/AntButton.vue +3 -3
  30. package/src/runtime/components/buttons/AntCreateButton.vue +20 -8
  31. package/src/runtime/components/buttons/AntDeleteButton.vue +20 -8
  32. package/src/runtime/components/buttons/AntSaveAndNewButton.vue +21 -8
  33. package/src/runtime/components/buttons/AntSaveButton.vue +20 -8
  34. package/src/runtime/components/crud/AntCrudDetailActions.vue +10 -3
  35. package/src/runtime/components/crud/AntCrudDetailNav.vue +21 -15
  36. package/src/runtime/components/crud/AntCrudTableFilter.vue +35 -33
@@ -8,3 +8,4 @@ export declare const Disabled: Story;
8
8
  export declare const Grouped: Story;
9
9
  export declare const Skeleton: Story;
10
10
  export declare const Expanded: Story;
11
+ export declare const InvalidPermission: Story;
@@ -1,6 +1,7 @@
1
1
  import AntSaveButton from "../AntSaveButton.vue";
2
2
  import { Size } from "../../../enums/Size.enum.mjs";
3
3
  import { Grouped as _Grouped } from "../../../enums/Grouped.enum.mjs";
4
+ import { Position } from "../../../enums/index.mjs";
4
5
  const meta = {
5
6
  title: "Components/Buttons/Save Button",
6
7
  component: AntSaveButton,
@@ -55,3 +56,11 @@ export const Expanded = {
55
56
  expanded: true
56
57
  }
57
58
  };
59
+ export const InvalidPermission = {
60
+ render: Docs.render,
61
+ args: {
62
+ ...Docs.args,
63
+ canSave: false,
64
+ invalidPermissionTooltipPosition: Position.right
65
+ }
66
+ };
@@ -8,14 +8,19 @@ defineEmits(['back', 'save', 'save-and-new']);
8
8
  withDefaults(defineProps<{
9
9
  disabled?: boolean
10
10
  skeleton?: boolean
11
+ canSave?: boolean
11
12
  }>(), {
12
13
  disabled: false,
13
14
  skeleton: false,
15
+ canSave: true
14
16
  });
15
17
  </script>
16
18
 
17
19
  <template>
18
- <div class="flex justify-between p-2.5 gap-2.5 bg-neutral-50" data-e2e="crud-detail-actions">
20
+ <div
21
+ class="flex justify-between p-2.5 gap-2.5 bg-neutral-50"
22
+ data-e2e="crud-detail-actions"
23
+ >
19
24
  <div class="flex gap-2.5">
20
25
  <slot name="buttons-left">
21
26
  <AntButton
@@ -31,21 +36,23 @@ withDefaults(defineProps<{
31
36
  </div>
32
37
 
33
38
  <div class="flex gap-2.5">
34
- <slot name="before-buttons-right"/>
39
+ <slot name="before-buttons-right" />
35
40
  <slot name="buttons-right">
36
41
  <AntSaveAndNewButton
37
42
  :skeleton="skeleton"
38
43
  :disabled="disabled"
44
+ :can-save="canSave"
39
45
  @click="$emit('save-and-new')"
40
46
  />
41
47
 
42
48
  <AntSaveButton
43
49
  :skeleton="skeleton"
44
50
  :disabled="disabled"
51
+ :can-save="canSave"
45
52
  @click="$emit('save')"
46
53
  />
47
54
  </slot>
48
- <slot name="after-buttons-right"/>
55
+ <slot name="after-buttons-right" />
49
56
  </div>
50
57
  </div>
51
58
  </template>
@@ -5,40 +5,46 @@ import AntTabs from '../tabs/AntTabs.vue';
5
5
  import AntDeleteButton from '../buttons/AntDeleteButton.vue';
6
6
  import AntDeleteDialog from '../dialogs/AntDeleteDialog.vue';
7
7
  import {ref} from 'vue';
8
+ import {Position} from '../../enums';
8
9
 
9
10
  defineEmits(['delete']);
10
11
  withDefaults(defineProps<{
11
- tabItems?: TabItem[]
12
- disabled?: boolean
13
- getEntityName: () => string
12
+ tabItems?: TabItem[]
13
+ deleteButtonDisabled?: boolean
14
+ getEntityName: () => string
15
+ canDelete?: boolean
14
16
  }>(), {
15
- disabled: false,
17
+ deleteButtonDisabled: false,
18
+ canDelete: true
16
19
  });
17
20
 
18
21
  const dialogOpen = ref(false);
19
22
  </script>
20
23
 
21
24
  <template>
22
- <div class="flex justify-between items-stretch gap-2.5 bg-neutral-50" data-e2e="crud-detail-nav">
25
+ <div
26
+ class="flex justify-between items-stretch gap-2.5 bg-neutral-50"
27
+ data-e2e="crud-detail-nav"
28
+ >
23
29
  <slot name="tabs">
24
30
  <AntTabs
25
- :tabItems="tabItems"
31
+ :tab-items="tabItems"
26
32
  />
27
33
  </slot>
28
34
 
29
35
  <div class="flex gap-2.5 pr-2.5 py-2.5">
30
36
  <slot name="buttons">
31
- <slot name="before-delete-button"/>
37
+ <slot name="before-delete-button" />
32
38
 
33
- <slot name="delete-button">
34
- <AntDeleteButton
35
- :disabled="disabled"
36
- filled
37
- @click="() => dialogOpen = true"
38
- />
39
- </slot>
39
+ <AntDeleteButton
40
+ :disabled="deleteButtonDisabled || !canDelete"
41
+ filled
42
+ :can-delete="canDelete"
43
+ :invalid-permission-tooltip-position="Position.left"
44
+ @click="() => dialogOpen = true"
45
+ />
40
46
 
41
- <slot name="after-delete-button"/>
47
+ <slot name="after-delete-button" />
42
48
  </slot>
43
49
  </div>
44
50
 
@@ -1,10 +1,8 @@
1
1
  <script lang="ts" setup>
2
2
  // TODO:: This component works only with vue-router. Make it work in storybook.
3
- // TODO:: add skeleton
4
3
  import AntSearch from '../form/AntSearch.vue';
5
4
  import AntCreateButton from '../buttons/AntCreateButton.vue';
6
5
  import AntDropdown from '../AntDropdown.vue';
7
- import AntIcon from '../AntIcon.vue';
8
6
  import {computed, ref, watch} from 'vue';
9
7
  import AntButton from '../buttons/AntButton.vue';
10
8
  import {faFilter, faMultiply} from '@fortawesome/free-solid-svg-icons';
@@ -12,17 +10,19 @@ import {ColorType, Grouped, Position} from '../../enums';
12
10
  import {useRoute, useRouter} from 'vue-router';
13
11
 
14
12
  const props = withDefaults(defineProps<{
15
- fullWidth?: boolean,
16
- showFilter?: boolean,
17
- searchQuery?: string,
18
- hasFilter?: boolean,
19
- skeleton?: boolean,
13
+ fullWidth?: boolean,
14
+ showFilter?: boolean,
15
+ searchQuery?: string,
16
+ hasFilter?: boolean,
17
+ canCreate?: boolean,
18
+ skeleton?: boolean,
20
19
  }>(), {
21
- fullWidth: true,
22
- showFilter: true,
23
- searchQuery: 'search',
24
- hasFilter: false,
25
- skeleton: false,
20
+ fullWidth: true,
21
+ showFilter: true,
22
+ searchQuery: 'search',
23
+ hasFilter: false,
24
+ canCreate: true,
25
+ skeleton: false,
26
26
  });
27
27
  const emit = defineEmits(['search', 'create', 'removeFilter']);
28
28
  const router = useRouter();
@@ -31,32 +31,32 @@ const route = useRoute();
31
31
  const showDropdown = ref(false);
32
32
  const _fullWidth = ref(props.fullWidth)
33
33
  const search = computed({
34
- get: () => route.query[props.searchQuery] as string || '',
35
- set: (value) => {
36
- const query = {
37
- ...route.query,
38
- [props.searchQuery]: value
39
- }
34
+ get: () => route.query[props.searchQuery] as string || '',
35
+ set: (value) => {
36
+ const query = {
37
+ ...route.query,
38
+ [props.searchQuery]: value
39
+ }
40
40
 
41
- if (!value) {
42
- delete query[props.searchQuery];
43
- }
41
+ if (!value) {
42
+ delete query[props.searchQuery];
43
+ }
44
44
 
45
- (async () => {
46
- await router.replace({
47
- ...route,
48
- query
49
- })
45
+ (async () => {
46
+ await router.replace({
47
+ ...route,
48
+ query
49
+ })
50
50
 
51
- emit('search', value)
52
- })()
53
- }
51
+ emit('search', value)
52
+ })()
53
+ }
54
54
  })
55
55
 
56
56
  watch(() => props.fullWidth, (val) => {
57
- setTimeout(() => {
58
- _fullWidth.value = val
59
- }, val ? 250 : 300)
57
+ setTimeout(() => {
58
+ _fullWidth.value = val
59
+ }, val ? 250 : 300)
60
60
  })
61
61
  </script>
62
62
 
@@ -102,7 +102,7 @@ watch(() => props.fullWidth, (val) => {
102
102
  </div>
103
103
 
104
104
  <template #content>
105
- <slot name="dropdownContent"/>
105
+ <slot name="dropdownContent" />
106
106
  </template>
107
107
  </AntDropdown>
108
108
  </div>
@@ -112,6 +112,8 @@ watch(() => props.fullWidth, (val) => {
112
112
  <AntCreateButton
113
113
  filled
114
114
  :skeleton="skeleton"
115
+ :can-create="canCreate"
116
+ :invalid-permission-tooltip-position="Position.left"
115
117
  @click="() => emit('create')"
116
118
  />
117
119
  </slot>
@@ -1,3 +1,4 @@
1
+ import type { Slot } from 'vue';
1
2
  /**
2
3
  * Convert html class syntax given as undefined, string ("text-base bg-primary-500") or
3
4
  * object syntax ({"text-base bg-primary-500": true}) always to object syntax ({"text-base bg-primary-500": true}).
@@ -12,3 +13,8 @@ export declare function classesToObjectSyntax(classes: string | undefined | Reco
12
13
  * @param className
13
14
  */
14
15
  export declare function enumToPlainText(value: object, className: string): string;
16
+ /**
17
+ * Detect if the given vue template slot exists and has content.
18
+ * To get a slot in your component, call $slots['slotName'] or useSlots()['slotName'].
19
+ */
20
+ export declare function hasSlotContent(slot: Slot<any> | undefined): boolean;
@@ -1,3 +1,4 @@
1
+ import { Fragment } from "vue";
1
2
  export function classesToObjectSyntax(classes) {
2
3
  if (typeof classes === "object") {
3
4
  return classes;
@@ -18,3 +19,23 @@ export function enumToPlainText(value, className) {
18
19
  });
19
20
  return text + "}";
20
21
  }
22
+ export function hasSlotContent(slot) {
23
+ if (!slot) {
24
+ return false;
25
+ }
26
+ const isVnodeEmpty = (vnodes) => {
27
+ return vnodes.every((node) => {
28
+ if (node.type === Comment) {
29
+ return true;
30
+ }
31
+ if (node.type === Text && typeof node.children === "string" && !node.children.trim()) {
32
+ return true;
33
+ }
34
+ if (node.type === Fragment && isVnodeEmpty(node.children)) {
35
+ return true;
36
+ }
37
+ return false;
38
+ });
39
+ };
40
+ return !isVnodeEmpty(slot());
41
+ }
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@antify/ui-module",
3
3
  "private": false,
4
- "version": "1.3.0",
4
+ "version": "1.4.0",
5
5
  "license": "MIT",
6
6
  "type": "module",
7
7
  "exports": {
@@ -3,78 +3,80 @@ import {computed, onMounted, ref} from 'vue';
3
3
  import {handleEnumValidation} from '../handler';
4
4
  import {InputColorType, Position} from '../enums';
5
5
  import {classesToObjectSyntax} from '../utils';
6
+ import {hasSlotContent} from '../utils';
6
7
 
7
8
  const props = withDefaults(defineProps<{
8
- content?: string,
9
- position?: Position,
10
- tooltipClasses?: string | Record<string, boolean>,
11
- colorType?: InputColorType
9
+ position?: Position
10
+ tooltipClasses?: string | Record<string, boolean>
11
+ colorType?: InputColorType
12
+ expanded?: boolean
12
13
  }>(), {
13
- position: Position.left,
14
- tooltipClasses: '',
15
- colorType: InputColorType.base
14
+ position: Position.left,
15
+ tooltipClasses: '',
16
+ colorType: InputColorType.base,
17
+ expanded: false
16
18
  });
17
19
  const visible = ref(false);
18
20
  const _tooltipClasses = computed(() => ({
19
- 'absolute w-max inline-flex': true,
20
- // Position
21
- 'bottom-full pb-3.5': props.position === Position.top,
22
- 'top-full pt-3.5': props.position === Position.bottom,
23
- 'right-full pr-3.5': props.position === Position.left,
24
- 'left-full pl-3.5': props.position === Position.right,
25
- ...classesToObjectSyntax(props.tooltipClasses)
21
+ 'absolute w-max inline-flex': true,
22
+ // Position
23
+ 'bottom-full pb-3.5': props.position === Position.top,
24
+ 'top-full pt-3.5': props.position === Position.bottom,
25
+ 'right-full pr-3.5': props.position === Position.left,
26
+ 'left-full pl-3.5': props.position === Position.right,
27
+ ...classesToObjectSyntax(props.tooltipClasses)
26
28
  }));
27
29
  const classes = computed(() => ({
28
- 'z-10 absolute flex': true,
29
- 'top-0 left-0 right-0 -m-[2px] justify-center': props.position === Position.bottom,
30
- 'bottom-0 left-0 right-0 -m-[2px] justify-center': props.position === Position.top,
31
- 'top-0 left-0 bottom-0 -ml-[2.2px] items-center': props.position === Position.right,
32
- 'top-0 right-0 bottom-0 -mr-[2.2px] items-center': props.position === Position.left,
30
+ 'z-10 absolute flex': true,
31
+ 'top-0 left-0 right-0 -m-[2px] justify-center': props.position === Position.bottom,
32
+ 'bottom-0 left-0 right-0 -m-[2px] justify-center': props.position === Position.top,
33
+ 'top-0 left-0 bottom-0 -ml-[2.2px] items-center': props.position === Position.right,
34
+ 'top-0 right-0 bottom-0 -mr-[2.2px] items-center': props.position === Position.left,
33
35
  }));
34
36
  const itemContainerClasses = computed(() => ({
35
- 'relative flex items-center': true,
36
- 'justify-center': props.position === Position.bottom,
37
- 'justify-center rotate-180': props.position === Position.top,
38
- 'justify-center -rotate-90': props.position === Position.right,
39
- 'justify-center rotate-90': props.position === Position.left,
37
+ 'relative flex items-center': true,
38
+ 'justify-center': props.position === Position.bottom,
39
+ 'justify-center rotate-180': props.position === Position.top,
40
+ 'justify-center -rotate-90': props.position === Position.right,
41
+ 'justify-center rotate-90': props.position === Position.left,
40
42
  }));
41
43
  const contentClasses = computed(() => {
42
- const variants: Record<InputColorType, string> = {
43
- [InputColorType.base]: 'text-neutral-50-font bg-neutral-50 border-neutral-300',
44
- [InputColorType.danger]: 'text-danger-500-font bg-danger-500 border-danger-500',
45
- [InputColorType.info]: 'text-info-500-font bg-info-500 border-info-500',
46
- [InputColorType.success]: 'text-success-500-font bg-success-500 border-success-500',
47
- [InputColorType.warning]: 'text-warning-500-font bg-warning-500 border-warning-500',
48
- };
44
+ const variants: Record<InputColorType, string> = {
45
+ [InputColorType.base]: 'text-neutral-50-font bg-neutral-50 border-neutral-300',
46
+ [InputColorType.danger]: 'text-danger-500-font bg-danger-500 border-danger-500',
47
+ [InputColorType.info]: 'text-info-500-font bg-info-500 border-info-500',
48
+ [InputColorType.success]: 'text-success-500-font bg-success-500 border-success-500',
49
+ [InputColorType.warning]: 'text-warning-500-font bg-warning-500 border-warning-500',
50
+ };
49
51
 
50
- return {[variants[props.colorType]]: true};
52
+ return {[variants[props.colorType]]: true};
51
53
  });
52
54
  const svgPathClasses = computed(() => {
53
- const variants: Record<InputColorType, string> = {
54
- [InputColorType.base]: 'fill-neutral-50 stroke-neutral-50',
55
- [InputColorType.danger]: 'fill-danger-500 stroke-danger-500',
56
- [InputColorType.info]: 'fill-info-500 stroke-info-500',
57
- [InputColorType.success]: 'fill-success-500 stroke-success-500',
58
- [InputColorType.warning]: 'fill-warning-500 stroke-warning-500',
59
- };
55
+ const variants: Record<InputColorType, string> = {
56
+ [InputColorType.base]: 'fill-neutral-50 stroke-neutral-50',
57
+ [InputColorType.danger]: 'fill-danger-500 stroke-danger-500',
58
+ [InputColorType.info]: 'fill-info-500 stroke-info-500',
59
+ [InputColorType.success]: 'fill-success-500 stroke-success-500',
60
+ [InputColorType.warning]: 'fill-warning-500 stroke-warning-500',
61
+ };
60
62
 
61
- return {[variants[props.colorType]]: true};
63
+ return {[variants[props.colorType]]: true};
62
64
  });
63
65
  const arrowSvgPathClasses = computed(() => {
64
- const variants: Record<InputColorType, string> = {
65
- [InputColorType.base]: 'stroke-neutral-300',
66
- [InputColorType.danger]: 'stroke-danger-500',
67
- [InputColorType.info]: 'stroke-info-500',
68
- [InputColorType.success]: 'stroke-success-500',
69
- [InputColorType.warning]: 'stroke-warning-500',
70
- };
66
+ const variants: Record<InputColorType, string> = {
67
+ [InputColorType.base]: 'stroke-neutral-300',
68
+ [InputColorType.danger]: 'stroke-danger-500',
69
+ [InputColorType.info]: 'stroke-info-500',
70
+ [InputColorType.success]: 'stroke-success-500',
71
+ [InputColorType.warning]: 'stroke-warning-500',
72
+ };
71
73
 
72
- return {[variants[props.colorType]]: true};
74
+ return {[variants[props.colorType]]: true};
73
75
  });
74
76
 
75
77
  onMounted(() => {
76
- handleEnumValidation(props.position, Position, 'Position')
77
- handleEnumValidation(props.colorType, InputColorType, 'colorType')
78
+ handleEnumValidation(props.position, Position, 'Position')
79
+ handleEnumValidation(props.colorType, InputColorType, 'colorType')
78
80
  });
79
81
 
80
82
  /**
@@ -84,33 +86,37 @@ onMounted(() => {
84
86
  const delayVisible = ref(visible.value);
85
87
 
86
88
  function onMouseOver() {
87
- delayVisible.value = true;
89
+ delayVisible.value = true;
88
90
 
89
- setTimeout(() => {
90
- if (delayVisible.value) {
91
- visible.value = true
92
- }
93
- }, 300)
91
+ setTimeout(() => {
92
+ if (delayVisible.value) {
93
+ visible.value = true
94
+ }
95
+ }, 300)
94
96
  }
95
97
 
96
98
  function onMouseLeave() {
97
- delayVisible.value = false
98
- visible.value = false
99
+ delayVisible.value = false
100
+ visible.value = false
99
101
  }
100
102
  </script>
101
103
 
102
104
  <template>
103
105
  <div
104
- class="relative inline-flex justify-center items-center"
106
+ class="relative justify-center items-center"
107
+ :class="{'flex w-full': props.expanded, 'inline-flex': !props.expanded}"
105
108
  data-e2e="tooltip"
106
109
  @mouseover="onMouseOver"
107
110
  @mouseleave="onMouseLeave"
108
111
  >
109
- <slot/>
112
+ <slot />
110
113
 
111
- <div v-if="visible" :class="_tooltipClasses">
114
+ <div
115
+ v-if="visible && hasSlotContent($slots.content)"
116
+ :class="_tooltipClasses"
117
+ >
112
118
  <div
113
- class="shadow-lg text-sm relative inline-flex flex-col relative"
119
+ class="shadow-lg text-sm relative"
114
120
  >
115
121
  <div
116
122
  :class="classes"
@@ -157,7 +163,7 @@ function onMouseLeave() {
157
163
  class="p-2.5 rounded-md border"
158
164
  :class="contentClasses"
159
165
  >
160
- <slot name="content"/>
166
+ <slot name="content" />
161
167
  </div>
162
168
  </div>
163
169
  </div>
@@ -5,8 +5,10 @@
5
5
  */
6
6
  import {Grouped} from '../../enums/Grouped.enum';
7
7
  import {Size} from '../../enums/Size.enum';
8
- import {ColorType} from '../../enums/ColorType.enum';
8
+ import {ColorType, InputColorType} from '../../enums/ColorType.enum';
9
+ import {Position} from '../../enums/Position.enum';
9
10
  import AntButton from './AntButton.vue';
11
+ import AntTooltip from '../AntTooltip.vue';
10
12
 
11
13
  defineEmits(['click', 'blur']);
12
14
  withDefaults(
@@ -17,25 +19,45 @@ withDefaults(
17
19
  colorType?: ColorType;
18
20
  skeleton?: boolean;
19
21
  expanded?: boolean;
22
+ filled?: boolean;
23
+ hasPermission?: boolean;
24
+ invalidPermissionTooltipPosition?: Position;
20
25
  }>(), {
21
26
  colorType: ColorType.primary,
27
+ hasPermission: true,
28
+ filled: true
22
29
  }
23
30
  )
24
31
  </script>
25
32
 
26
33
  <template>
27
- <AntButton
28
- :size="size"
29
- :disabled="disabled"
30
- :grouped="grouped"
31
- :skeleton="skeleton"
34
+ <AntTooltip
32
35
  :expanded="expanded"
33
- :color-type="colorType"
34
- filled
35
- data-e2e="action-button"
36
- @click="$emit('click')"
37
- @blur="$emit('blur')"
36
+ :position="invalidPermissionTooltipPosition"
37
+ :color-type="InputColorType.info"
38
38
  >
39
- <slot />
40
- </AntButton>
39
+ <slot name="button">
40
+ <AntButton
41
+ :size="size"
42
+ :disabled="disabled || !hasPermission"
43
+ :grouped="grouped"
44
+ :skeleton="skeleton"
45
+ :expanded="expanded"
46
+ :color-type="colorType"
47
+ :filled="filled"
48
+ data-e2e="action-button"
49
+ @click="$emit('click')"
50
+ @blur="$emit('blur')"
51
+ >
52
+ <slot />
53
+ </AntButton>
54
+ </slot>
55
+
56
+ <template
57
+ v-if="!hasPermission && !skeleton"
58
+ #content
59
+ >
60
+ <slot name="invalidPermissionTooltipContent" />
61
+ </template>
62
+ </AntTooltip>
41
63
  </template>
@@ -102,7 +102,7 @@ const classes = computed(() => {
102
102
  return {
103
103
  'transition-all inline-flex items-center justify-center relative font-medium': true,
104
104
  'focus:z-10': true,
105
- 'active:shadow-[inset_0_4px_4px_rgba(0,0,0,0.25)]': !props.readonly,
105
+ 'active:shadow-[inset_0_4px_4px_rgba(0,0,0,0.25)]': !hasAction.value,
106
106
  'py-1.5 px-2.5 text-xs gap-1': props.size === Size.sm,
107
107
  'py-2.5 px-3.5 text-sm gap-2.5': props.size === Size.md,
108
108
  'disabled:opacity-50 disabled:cursor-not-allowed': true,
@@ -115,9 +115,9 @@ const classes = computed(() => {
115
115
  ...groupedClassList.value,
116
116
  [variants[props.colorType]]: true,
117
117
  [notFilledVariants[props.colorType]]: !props.filled,
118
- [notFilledHoverVariants[props.colorType]]: !props.filled && !props.readonly,
118
+ [notFilledHoverVariants[props.colorType]]: !props.filled && !hasAction.value,
119
119
  [filledVariants[props.colorType]]: props.filled,
120
- [filledHoverVariants[props.colorType]]: props.filled && !props.readonly,
120
+ [filledHoverVariants[props.colorType]]: props.filled && !hasAction.value,
121
121
  };
122
122
  });
123
123
  const iconColor = computed(() => {
@@ -2,15 +2,20 @@
2
2
  import {Grouped} from '../../enums/Grouped.enum';
3
3
  import {Size} from '../../enums/Size.enum';
4
4
  import AntActionButton from './AntActionButton.vue';
5
+ import {Position} from '../../enums';
5
6
 
6
7
  defineEmits(['click', 'blur']);
7
- defineProps<{
8
- size?: Size;
9
- disabled?: boolean;
10
- grouped?: Grouped;
11
- skeleton?: boolean;
12
- expanded?: boolean;
13
- }>()
8
+ withDefaults(defineProps<{
9
+ size?: Size;
10
+ disabled?: boolean;
11
+ grouped?: Grouped;
12
+ skeleton?: boolean;
13
+ expanded?: boolean;
14
+ canCreate?: boolean;
15
+ invalidPermissionTooltipPosition?: Position;
16
+ }>(), {
17
+ canCreate: true
18
+ });
14
19
  </script>
15
20
 
16
21
  <template>
@@ -20,10 +25,17 @@ defineProps<{
20
25
  :grouped="grouped"
21
26
  :skeleton="skeleton"
22
27
  :expanded="expanded"
28
+ :has-permission="canCreate"
29
+ :invalid-permission-tooltip-position="invalidPermissionTooltipPosition"
23
30
  data-e2e="create-button"
24
31
  @click="$emit('click')"
25
32
  @blur="$emit('blur')"
26
33
  >
27
- Create
34
+ <template #default>Create</template>
35
+
36
+ <template #invalidPermissionTooltipContent>
37
+ You have no permission to create new entries.<br>
38
+ Please contact your administrator.
39
+ </template>
28
40
  </AntActionButton>
29
41
  </template>