@antify/ui 2.1.0 → 2.2.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 (28) hide show
  1. package/README.md +13 -0
  2. package/dist/components/AntDropdown.vue +1 -1
  3. package/dist/components/AntModal.vue +1 -1
  4. package/dist/components/AntToaster.vue +1 -1
  5. package/dist/components/buttons/AntButton.vue +0 -1
  6. package/dist/components/inputs/AntCheckbox.vue +1 -0
  7. package/dist/components/inputs/AntCheckboxGroup.vue +1 -0
  8. package/dist/components/inputs/AntNumberInput.vue +40 -12
  9. package/dist/components/inputs/AntRangeSlider.vue +1 -1
  10. package/dist/components/inputs/AntSelect.vue +2 -1
  11. package/dist/components/inputs/AntSwitch.vue +1 -0
  12. package/dist/components/inputs/AntTextarea.vue +1 -1
  13. package/dist/components/inputs/Elements/AntBaseInput.vue +2 -1
  14. package/dist/components/inputs/__stories/AntNumberInput.stories.js +21 -1
  15. package/dist/components/inputs/__stories/AntNumberInput.stories.mjs +15 -1
  16. package/dist/components/inputs/__stories/AntSelect.stories.js +22 -1
  17. package/dist/components/inputs/__stories/AntSelect.stories.mjs +22 -1
  18. package/dist/components/table/AntCollapsibleTableRowContent.vue +25 -0
  19. package/dist/components/table/AntTable.vue +125 -47
  20. package/dist/components/table/AntTd.vue +0 -4
  21. package/dist/components/table/__stories/AntTable.stories.d.ts +3 -0
  22. package/dist/components/table/__stories/AntTable.stories.js +238 -16
  23. package/dist/components/table/__stories/AntTable.stories.mjs +195 -15
  24. package/dist/components/table/__types/AntCollapsibleTable.types.d.ts +4 -0
  25. package/dist/components/table/__types/AntCollapsibleTable.types.js +11 -0
  26. package/dist/components/table/__types/AntCollapsibleTable.types.mjs +5 -0
  27. package/dist/tailwind.config.d.ts +2 -1
  28. package/package.json +5 -3
package/README.md CHANGED
@@ -8,6 +8,19 @@ The antify ui is a vue component library for applications based on tailwindcss.
8
8
  pnpm i && pnpm dev
9
9
  ```
10
10
 
11
+ ## Component Hierarchy
12
+ Certain components, such as modals and toasts, need to be displayed on top of other elements to remain visible at all times. As a result, they require a higher z-index. The following hierarchy defines the rendering order for components with this property.
13
+
14
+ | Component | z-index |
15
+ |-------------|---------|
16
+ | AntDropdown | 50 |
17
+ | AntModal | 80 |
18
+ | AntPopover | 90 |
19
+ | AntToaster | 100 |
20
+
21
+
22
+
23
+
11
24
  ## Useful links:
12
25
 
13
26
  - Chromatic docu: https://www.chromatic.com/docs/
@@ -34,7 +34,7 @@ const {floatingStyles} = useFloating(reference, floating, {
34
34
  });
35
35
 
36
36
  const _dropdownClasses = computed(() => ({
37
- 'min-w-[10rem] z-[90]': true,
37
+ 'min-w-[10rem] z-[50]': true,
38
38
  ...classesToObjectSyntax(props.dropdownClasses)
39
39
  }));
40
40
 
@@ -47,7 +47,7 @@ function closeModal() {
47
47
  <Transition name="fade">
48
48
  <div
49
49
  v-if="openBackground"
50
- class="absolute inset-0 flex items-center justify-center z-50 cursor-pointer overflow-hidden"
50
+ class="absolute inset-0 flex items-center justify-center z-[80] cursor-pointer overflow-hidden"
51
51
  :class="{'bg-black/50 backdrop-blur-sm': !fullscreen}"
52
52
  @click.self="closeModal"
53
53
  >
@@ -23,7 +23,7 @@ const classes = computed(() => ({
23
23
 
24
24
  <template>
25
25
  <div
26
- class="absolute flex flex-col space-y-2.5 p-2.5"
26
+ class="absolute flex flex-col space-y-2.5 p-2.5 z-[100]"
27
27
  :class="classes"
28
28
  data-e2e="toaster"
29
29
  >
@@ -108,7 +108,6 @@ const classes = computed(() => {
108
108
 
109
109
  return {
110
110
  'transition-all inline-flex items-center justify-center relative font-medium': true,
111
- 'focus:z-10': true,
112
111
  'active:shadow-[inset_0_4px_4px_rgba(0,0,0,0.25)]': !hasInputState.value,
113
112
  'p-1 text-xs gap-1': props.size === Size.xs2,
114
113
  'p-1.5 text-xs gap-1.5': props.size === Size.xs,
@@ -140,6 +140,7 @@ onMounted(() => {
140
140
 
141
141
  <template>
142
142
  <AntField
143
+ data-e2e="checkbox"
143
144
  :label="label"
144
145
  :description="description"
145
146
  :skeleton="skeleton"
@@ -86,6 +86,7 @@ onMounted(() => {
86
86
 
87
87
  <template>
88
88
  <AntField
89
+ data-e2e="checkbox-group"
89
90
  :label="label"
90
91
  :description="description"
91
92
  :skeleton="skeleton"
@@ -10,11 +10,17 @@ import {handleEnumValidation} from '../../handler';
10
10
  import {useVModel} from '@vueuse/core';
11
11
  import {Grouped} from '../../enums/Grouped.enum';
12
12
  import {BaseInputType} from './Elements/__types';
13
+ import Big from 'big.js';
14
+ Big.RM = Big.roundHalfEven;
13
15
 
14
16
  defineOptions({inheritAttrs: false});
15
17
 
18
+ /**
19
+ * We use a string as the modelValue to ensure that numbers are correctly padded with a trailing 0 instead of cut off (e.g. 0.10 would be converted to 0.1).
20
+ * Additionally, the initial value (if none is given) gets set to "0" with the same amount of decimals as used in the steps.
21
+ */
16
22
  const props = withDefaults(defineProps<{
17
- modelValue: number | null;
23
+ modelValue: string | null;
18
24
  label?: string;
19
25
  placeholder?: string;
20
26
  description?: string;
@@ -42,6 +48,7 @@ const props = withDefaults(defineProps<{
42
48
  });
43
49
  const emit = defineEmits(['update:modelValue', 'validate']);
44
50
  const _modelValue = useVModel(props, 'modelValue', emit);
51
+
45
52
  const isPrevButtonDisabled = computed(() => {
46
53
  if (props.disabled) {
47
54
  return true;
@@ -51,7 +58,7 @@ const isPrevButtonDisabled = computed(() => {
51
58
  return false;
52
59
  }
53
60
 
54
- return props.min !== undefined ? _modelValue.value <= props.min : false;
61
+ return props.min !== undefined ? Number(_modelValue.value) <= props.min : false;
55
62
  });
56
63
  const isNextButtonDisabled = computed(() => {
57
64
  if (props.disabled) {
@@ -62,7 +69,7 @@ const isNextButtonDisabled = computed(() => {
62
69
  return false;
63
70
  }
64
71
 
65
- return props.max !== undefined ? _modelValue.value >= props.max : false;
72
+ return props.max !== undefined ? Number(_modelValue.value) >= props.max : false;
66
73
  });
67
74
 
68
75
  onMounted(() => {
@@ -70,23 +77,44 @@ onMounted(() => {
70
77
  handleEnumValidation(props.state, InputState, 'state');
71
78
  });
72
79
 
80
+ /**
81
+ * Returns the amount of decimal places of the given value.
82
+ * @param value Number to get decimal places from.
83
+ */
84
+ function getDecimalPlaces(value: number | string) {
85
+ const strValue = String(value);
86
+ const decimalIndex = strValue.indexOf('.');
87
+
88
+ if (decimalIndex === -1) return 0;
89
+
90
+ return strValue.length - decimalIndex - 1;
91
+ }
92
+
73
93
  function subtract() {
94
+ const modelDecimalPlaces = getDecimalPlaces(_modelValue.value || 0);
95
+ const stepDecimalPlaces = getDecimalPlaces(props.steps);
96
+ const decimalPlaces = Math.max(modelDecimalPlaces, stepDecimalPlaces);
97
+
74
98
  if (_modelValue.value === null) {
75
- _modelValue.value = props.max || 0;
76
- } else if (props.max !== undefined && _modelValue.value - props.steps > props.max) {
77
- _modelValue.value = props.max;
99
+ _modelValue.value = String(props.max || new Big(0).toFixed(decimalPlaces));
100
+ } else if (props.max !== undefined && Number(_modelValue.value) - props.steps > props.max) {
101
+ _modelValue.value = String(props.max);
78
102
  } else {
79
- _modelValue.value -= props.steps;
103
+ _modelValue.value = new Big(_modelValue.value).sub(props.steps).toFixed(decimalPlaces);
80
104
  }
81
105
  }
82
106
 
83
107
  function add() {
108
+ const modelDecimalPlaces = getDecimalPlaces(_modelValue.value || 0);
109
+ const stepDecimalPlaces = getDecimalPlaces(props.steps);
110
+ const decimalPlaces = Math.max(modelDecimalPlaces, stepDecimalPlaces);
111
+
84
112
  if (_modelValue.value === null) {
85
- return _modelValue.value = props.min || 0;
86
- } else if (props.min !== undefined && _modelValue.value + props.steps < props.min) {
87
- return _modelValue.value = props.min;
113
+ return _modelValue.value = String(props.min || new Big(0).toFixed(decimalPlaces));
114
+ } else if (props.min !== undefined && Number(_modelValue.value) + props.steps < props.min) {
115
+ return _modelValue.value = String(props.min);
88
116
  } else {
89
- _modelValue.value += props.steps;
117
+ _modelValue.value = new Big(_modelValue.value).add(props.steps).toFixed(decimalPlaces);
90
118
  }
91
119
  }
92
120
 
@@ -103,7 +131,7 @@ function onButtonBlur() {
103
131
  :description="description"
104
132
  :state="state"
105
133
  :limiter-max-value="limiter && max !== undefined ? max : undefined"
106
- :limiter-value="limiter && _modelValue !== undefined && _modelValue !== null ? _modelValue : undefined"
134
+ :limiter-value="limiter && _modelValue !== undefined && _modelValue !== null ? Number(_modelValue) : undefined"
107
135
  :messages="messages"
108
136
  >
109
137
  <div
@@ -38,7 +38,7 @@ const inputClasses = computed(() => {
38
38
  };
39
39
 
40
40
  return {
41
- 'ant-range-slider transition-colors relative border-none w-full focus:z-10 h-2 bg-base-300 rounded-md outline-none': true,
41
+ 'ant-range-slider transition-colors relative border-none w-full h-2 bg-base-300 rounded-md outline-none': true,
42
42
  'disabled:opacity-50 disabled:cursor-not-allowed': props.disabled,
43
43
  'invisible': props.skeleton,
44
44
  [variants[props.state]]: true
@@ -79,7 +79,7 @@ const inputClasses = computed(() => {
79
79
  };
80
80
 
81
81
  return {
82
- 'flex items-center transition-colors border-none outline relative focus:z-10': true,
82
+ 'flex items-center transition-colors border-none outline relative': true,
83
83
  'outline-offset-[-1px] outline-1 focus:outline-offset-[-1px] focus:outline-1': true,
84
84
  [variants[props.state]]: true,
85
85
  // Skeleton
@@ -315,6 +315,7 @@ function onClickRemoveButton() {
315
315
  :grouped="[Grouped.left, Grouped.center].some(item => item === grouped) ? Grouped.center : Grouped.right"
316
316
  :size="size"
317
317
  :skeleton="skeleton"
318
+ :disabled="disabled"
318
319
  @click="onClickRemoveButton"
319
320
  />
320
321
  </div>
@@ -130,6 +130,7 @@ function onBlur(e: FocusEvent) {
130
130
 
131
131
  <template>
132
132
  <AntField
133
+ data-e2e="switch"
133
134
  :label="label"
134
135
  :size="size"
135
136
  :description="description"
@@ -70,7 +70,7 @@ const inputClasses = computed(() => {
70
70
  };
71
71
 
72
72
  return {
73
- 'block transition-colors relative border-none outline w-full focus:z-10 h-full text-black': true,
73
+ 'block transition-colors relative border-none outline w-full h-full text-black': true,
74
74
  'outline-offset-[-1px] outline-1 focus:outline-offset-[-1px] focus:outline-1': true,
75
75
  'disabled:opacity-50 disabled:cursor-not-allowed': props.disabled,
76
76
  [variants[props.state]]: true,
@@ -68,7 +68,7 @@ const inputClasses = computed(() => {
68
68
  };
69
69
 
70
70
  return {
71
- 'block transition-colors relative border-none outline w-full focus:z-10 text-black font-regular': true,
71
+ 'block transition-colors relative border-none outline w-full text-black font-regular': true,
72
72
  'outline-offset-[-1px] outline-1 focus:outline-offset-[-1px] focus:outline-1': true,
73
73
  'disabled:opacity-50 disabled:cursor-not-allowed': props.disabled,
74
74
  'text-right': props.type === BaseInputType.number,
@@ -228,6 +228,7 @@ function onClickClearIcon() {
228
228
  :disabled="disabled || skeleton"
229
229
  :readonly="readonly"
230
230
  :tabindex="hasInputState ? -1 : 0"
231
+ title=""
231
232
  v-bind="$attrs"
232
233
  @blur="onBlur"
233
234
  >
@@ -81,7 +81,27 @@ const Docs = exports.Docs = {
81
81
  }
82
82
  };
83
83
  const WithIndicators = exports.WithIndicators = {
84
- render: Docs.render,
84
+ render: args => ({
85
+ components: {
86
+ AntNumberInput: _AntNumberInput.default,
87
+ AntFormGroup: _AntFormGroup.default,
88
+ AntFormGroupLabel: _AntFormGroupLabel.default
89
+ },
90
+ setup() {
91
+ return {
92
+ args
93
+ };
94
+ },
95
+ template: `
96
+ <AntFormGroup>
97
+ <AntFormGroup direction="column">
98
+ <AntNumberInput v-bind="args" v-model="args.modelValue" label="Label"
99
+ description="Lorem ipsum dolor sit amet"/>
100
+ <AntNumberInput v-bind="args" v-model="args.modelValue" label="Label"
101
+ description="Lorem ipsum dolor sit amet" :steps="0.0001"/>
102
+ </AntFormGroup>
103
+ </AntFormGroup>`
104
+ }),
85
105
  args: {
86
106
  ...Docs.args,
87
107
  indicators: true
@@ -48,7 +48,21 @@ export const Docs = {
48
48
  }
49
49
  };
50
50
  export const WithIndicators = {
51
- render: Docs.render,
51
+ render: (args) => ({
52
+ components: { AntNumberInput, AntFormGroup, AntFormGroupLabel },
53
+ setup() {
54
+ return { args };
55
+ },
56
+ template: `
57
+ <AntFormGroup>
58
+ <AntFormGroup direction="column">
59
+ <AntNumberInput v-bind="args" v-model="args.modelValue" label="Label"
60
+ description="Lorem ipsum dolor sit amet"/>
61
+ <AntNumberInput v-bind="args" v-model="args.modelValue" label="Label"
62
+ description="Lorem ipsum dolor sit amet" :steps="0.0001"/>
63
+ </AntFormGroup>
64
+ </AntFormGroup>`
65
+ }),
52
66
  args: {
53
67
  ...Docs.args,
54
68
  indicators: true
@@ -143,9 +143,23 @@ const skeleton = exports.skeleton = {
143
143
  }
144
144
  };
145
145
  const disabled = exports.disabled = {
146
- render: Docs.render,
146
+ render: (args, ctx) => ({
147
+ // @ts-ignore
148
+ components: Docs.render(args, ctx).components,
149
+ // @ts-ignore
150
+ setup: Docs.render(args, ctx).setup,
151
+ template: `
152
+ <div ref="scrollContainer" class="overflow-y-auto h-[100vh] p-2.5 dashed">
153
+ <div class="flex flex-col gap-4 justify-center h-[200vh]">
154
+ <AntSelect v-bind="args" v-model="modelValue"/>
155
+ <AntSelect v-bind="args" v-model="modelValue" nullable/>
156
+ </div>
157
+ </div>
158
+ `
159
+ }),
147
160
  args: {
148
161
  ...Docs.args,
162
+ modelValue: "1",
149
163
  disabled: true
150
164
  }
151
165
  };
@@ -242,6 +256,13 @@ const summary = exports.summary = {
242
256
  <AntSelect v-bind="args" value="1" size="sm" state="warning" disabled/>
243
257
  <AntSelect v-bind="args" value="1" size="sm" state="danger" disabled/>
244
258
  </div>
259
+ <div class="flex gap-2.5">
260
+ <AntSelect v-bind="args" :value="1" v-model="modelValue" size="sm" state="base" disabled nullable/>
261
+ <AntSelect v-bind="args" :value="1" v-model="modelValue" size="sm" state="info" disabled nullable/>
262
+ <AntSelect v-bind="args" :value="1" v-model="modelValue" size="sm" state="success" disabled nullable/>
263
+ <AntSelect v-bind="args" :value="1" v-model="modelValue" size="sm" state="warning" disabled nullable/>
264
+ <AntSelect v-bind="args" :value="1" v-model="modelValue" size="sm" state="danger" disabled nullable/>
265
+ </div>
245
266
  <div class="flex gap-2.5">
246
267
  <AntSelect v-bind="args" :value="null" grouped="none"/>
247
268
  <AntSelect v-bind="args" :value="null" grouped="right"/>
@@ -114,9 +114,23 @@ export const skeleton = {
114
114
  }
115
115
  };
116
116
  export const disabled = {
117
- render: Docs.render,
117
+ render: (args, ctx) => ({
118
+ // @ts-ignore
119
+ components: Docs.render(args, ctx).components,
120
+ // @ts-ignore
121
+ setup: Docs.render(args, ctx).setup,
122
+ template: `
123
+ <div ref="scrollContainer" class="overflow-y-auto h-[100vh] p-2.5 dashed">
124
+ <div class="flex flex-col gap-4 justify-center h-[200vh]">
125
+ <AntSelect v-bind="args" v-model="modelValue"/>
126
+ <AntSelect v-bind="args" v-model="modelValue" nullable/>
127
+ </div>
128
+ </div>
129
+ `
130
+ }),
118
131
  args: {
119
132
  ...Docs.args,
133
+ modelValue: "1",
120
134
  disabled: true
121
135
  }
122
136
  };
@@ -206,6 +220,13 @@ export const summary = {
206
220
  <AntSelect v-bind="args" value="1" size="sm" state="warning" disabled/>
207
221
  <AntSelect v-bind="args" value="1" size="sm" state="danger" disabled/>
208
222
  </div>
223
+ <div class="flex gap-2.5">
224
+ <AntSelect v-bind="args" :value="1" v-model="modelValue" size="sm" state="base" disabled nullable/>
225
+ <AntSelect v-bind="args" :value="1" v-model="modelValue" size="sm" state="info" disabled nullable/>
226
+ <AntSelect v-bind="args" :value="1" v-model="modelValue" size="sm" state="success" disabled nullable/>
227
+ <AntSelect v-bind="args" :value="1" v-model="modelValue" size="sm" state="warning" disabled nullable/>
228
+ <AntSelect v-bind="args" :value="1" v-model="modelValue" size="sm" state="danger" disabled nullable/>
229
+ </div>
209
230
  <div class="flex gap-2.5">
210
231
  <AntSelect v-bind="args" :value="null" grouped="none"/>
211
232
  <AntSelect v-bind="args" :value="null" grouped="right"/>
@@ -0,0 +1,25 @@
1
+ <script setup lang="ts">
2
+ import {computed} from "vue";
3
+
4
+ const props = withDefaults(defineProps<{
5
+ isOpen: boolean;
6
+ skeleton?: boolean;
7
+ }>(), {
8
+ skeleton: false,
9
+ });
10
+
11
+ const rowContentClasses = computed(() => ({
12
+ "grid transition-all duration-300 delay-0 ease-in-out": true,
13
+ "grid-rows-[0fr]": !props.isOpen,
14
+ "grid-rows-[1fr]": props.isOpen,
15
+ }));
16
+
17
+ </script>
18
+
19
+ <template>
20
+ <div :class="rowContentClasses">
21
+ <div class="overflow-hidden">
22
+ <slot/>
23
+ </div>
24
+ </div>
25
+ </template>
@@ -1,12 +1,18 @@
1
1
  <script lang="ts" setup>
2
2
  import {AntTableSize, AntTableSortDirection, type TableHeader} from './__types/TableHeader.types';
3
- import {computed, ref, type Ref, watch} from 'vue';
3
+ import {computed, onMounted, ref, type Ref, watch} from 'vue';
4
4
  import {useVModel} from '@vueuse/core';
5
- import {State} from '../../enums';
5
+ import {Size, State} from '../../enums';
6
6
  import AntTh from './AntTh.vue';
7
7
  import AntTd from './AntTd.vue';
8
8
  import AntSpinner from '../AntSpinner.vue';
9
9
  import AntSkeleton from '../AntSkeleton.vue';
10
+ import {hasSlotContent} from "../../utils";
11
+ import AntCollapsibleTableRowContent from "./AntCollapsibleTableRowContent.vue";
12
+ import {CollapseStrategy} from "../../types";
13
+ import {faAngleDown, faAngleUp} from "@fortawesome/free-solid-svg-icons";
14
+ import AntButton from "../buttons/AntButton.vue";
15
+ import AntIcon from "../AntIcon.vue";
10
16
 
11
17
  defineOptions({inheritAttrs: false});
12
18
 
@@ -30,6 +36,8 @@ const props = withDefaults(
30
36
  size?: AntTableSize;
31
37
  headerColor?: string;
32
38
  emptyStateText?: string;
39
+ collapseStrategy?: CollapseStrategy;
40
+ rowsCollapsed?: boolean;
33
41
  }>(), {
34
42
  rowKey: 'id',
35
43
  loading: false,
@@ -38,8 +46,12 @@ const props = withDefaults(
38
46
  size: AntTableSize.md,
39
47
  headerColor: 'bg-base-200',
40
48
  emptyStateText: 'We couldn\'t find any entry',
49
+ collapseStrategy: CollapseStrategy.single,
50
+ rowsCollapsed: true,
41
51
  });
52
+ const slots = defineSlots();
42
53
 
54
+ const openItems = ref<number[]>([]);
43
55
  const selected: Ref<Record<string, unknown> | undefined> = useVModel(props, 'selectedRow', emits);
44
56
  const _loading: Ref<boolean> = useVModel(props, 'loading', emits);
45
57
  const _showLightVersion = ref(props.showLightVersion);
@@ -53,6 +65,8 @@ const _headers = computed(() => {
53
65
  return props.headers;
54
66
  });
55
67
 
68
+ const maxColSpan = computed(() => _headers.value.length + (hasSlotContent(slots['rowFirstCell']) ? 1 : 0) + (hasSlotContent(slots['rowLastCell']) ? 1 : 0))
69
+
56
70
  watch(() => props.showLightVersion, (val) => {
57
71
  setTimeout(() => _showLightVersion.value = val, val ? 200 : 400);
58
72
  });
@@ -87,6 +101,42 @@ function rowClick(elem: Record<string, unknown>): void {
87
101
 
88
102
  emits('rowClick', elem);
89
103
  }
104
+
105
+ function toggleRowContent(index: number) {
106
+ const isOpen = openItems.value.includes(index);
107
+
108
+ if (isOpen) {
109
+ openItems.value = props.collapseStrategy === CollapseStrategy.single
110
+ ? []
111
+ : openItems.value.filter(item => item !== index);
112
+ } else {
113
+ openItems.value = props.collapseStrategy === CollapseStrategy.single
114
+ ? [index]
115
+ : [...openItems.value, index];
116
+ }
117
+ }
118
+
119
+ function openRowsByDefault() {
120
+ if (!props.rowsCollapsed && props.data.length > 0) {
121
+ openItems.value = props.data.map((_, index) => index);
122
+ }
123
+ }
124
+
125
+ watch(() => props.data, (currVal, prevVal) => {
126
+ if (currVal.length > prevVal.length) {
127
+ // Add newest element to the list of open items so it is open by default
128
+ // Necessary when table content is changed dynamically
129
+ if (props.collapseStrategy === CollapseStrategy.single) {
130
+ openItems.value = [currVal.length - 1];
131
+ } else {
132
+ openItems.value = [...openItems.value, currVal.length - 1];
133
+ }
134
+ }
135
+ })
136
+
137
+ onMounted(() => {
138
+ openRowsByDefault();
139
+ });
90
140
  </script>
91
141
 
92
142
  <template>
@@ -121,67 +171,95 @@ function rowClick(elem: Record<string, unknown>): void {
121
171
  </AntTh>
122
172
  </template>
123
173
 
174
+ <template v-if="!!$slots.afterRowContent">
175
+ <td/>
176
+ </template>
177
+
124
178
  <slot name="headerLastCell"></slot>
125
179
  </tr>
126
180
  </thead>
127
181
 
128
182
  <tbody class="bg-white relative">
129
183
  <!-- TODO:: Add some kind of virtual list for very large tree data (or required pagination??) -->
130
- <tr
131
- v-for="(elem, rowIndex) in data"
132
- :id="elem[rowKey] as string"
133
- :key="`table-row-${elem[rowKey]}-${rowIndex}`"
134
- :class="{
184
+ <template
185
+ v-for="(elem, rowIndex) in data" :key="`table-row-${elem[rowKey]}-${rowIndex}`"
186
+ >
187
+ <tr
188
+ data-e2e="table-row"
189
+ :id="elem[rowKey] as string"
190
+ :class="{
135
191
  'bg-primary-200 text-primary-200-font transition-colors': elem === selected,
136
192
  'bg-white text-for-white-bg-font': elem !== selected && rowIndex % 2 === 0,
137
193
  'bg-base-100 text-base-100-font': elem !== selected && rowIndex % 2 !== 0,
138
194
  'cursor-pointer': selectableRows,
139
195
  'hover:bg-base-200': selectableRows && elem !== selected,
140
196
  }"
141
- >
142
- <slot
143
- name="rowFirstCell"
144
- v-bind="{ elem }"
145
- />
197
+ >
198
+ <slot
199
+ name="rowFirstCell"
200
+ v-bind="{ elem }"
201
+ />
146
202
 
147
- <template v-for="(header, colIndex) in _headers">
148
- <AntTd
149
- v-if="!_showLightVersion || (_showLightVersion && header.lightVersion)"
150
- :key="`table-cell-${header.identifier}-${colIndex}`"
151
- :header="header"
152
- :element="elem"
153
- :align="header.align"
154
- :size="size"
155
- @click="rowClick(elem)"
156
- >
157
- <template #beforeCellContent="props">
158
- <slot
159
- name="beforeCellContent"
160
- v-bind="{...props, colIndex, rowIndex}"
161
- />
162
- </template>
203
+ <template v-for="(header, colIndex) in _headers">
204
+ <AntTd
205
+ v-if="!_showLightVersion || (_showLightVersion && header.lightVersion)"
206
+ :key="`table-cell-${header.identifier}-${rowIndex}-${colIndex}`"
207
+ :data-e2e="`table-cell-${header.identifier}`"
208
+ :header="header"
209
+ :element="elem"
210
+ :align="header.align"
211
+ :size="size"
212
+ @click="rowClick(elem)"
213
+ >
214
+ <template #beforeCellContent="props">
215
+ <slot
216
+ name="beforeCellContent"
217
+ v-bind="{...props, colIndex, rowIndex}"
218
+ />
219
+ </template>
163
220
 
164
- <template #cellContent="props">
165
- <slot
166
- name="cellContent"
167
- v-bind="{...props, colIndex, rowIndex}"
168
- />
169
- </template>
221
+ <template #cellContent="props">
222
+ <slot
223
+ name="cellContent"
224
+ v-bind="{...props, colIndex, rowIndex}"
225
+ />
226
+ </template>
170
227
 
171
- <template #afterCellContent="props">
172
- <slot
173
- name="afterCellContent"
174
- v-bind="{...props, colIndex, rowIndex}"
175
- />
176
- </template>
177
- </AntTd>
178
- </template>
228
+ <template #afterCellContent="props">
229
+ <slot
230
+ name="afterCellContent"
231
+ v-bind="{...props, colIndex, rowIndex}"
232
+ />
233
+ </template>
234
+ </AntTd>
235
+ </template>
179
236
 
180
- <slot
181
- name="rowLastCell"
182
- v-bind="{ elem }"
183
- />
184
- </tr>
237
+ <template v-if="!!$slots.afterRowContent">
238
+ <td class="whitespace-nowrap text-sm font-medium relative px-2 py-0 h-9 text-right"
239
+ @click="rowClick(elem)">
240
+ <AntButton @click="toggleRowContent(rowIndex)" :size="Size.xs2">
241
+ <AntIcon :icon="openItems.includes(rowIndex) ? faAngleUp : faAngleDown"/>
242
+ </AntButton>
243
+ </td>
244
+ </template>
245
+
246
+ <slot
247
+ name="rowLastCell"
248
+ v-bind="{ elem }"
249
+ />
250
+ </tr>
251
+
252
+ <template v-if="!!$slots.afterRowContent">
253
+ <tr data-e2e="table-after-row-content">
254
+ <td :colspan="maxColSpan + 1" class="p-0">
255
+ <AntCollapsibleTableRowContent :is-open="openItems.includes(rowIndex)"
256
+ >
257
+ <slot name="afterRowContent" v-bind="{ element: elem, rowIndex }"/>
258
+ </AntCollapsibleTableRowContent>
259
+ </td>
260
+ </tr>
261
+ </template>
262
+ </template>
185
263
 
186
264
  <tr v-if="data.length <= 0 && !_loading">
187
265
  <td
@@ -74,7 +74,3 @@ const cellClasses = computed(() => ({
74
74
  />
75
75
  </td>
76
76
  </template>
77
-
78
- <style scoped>
79
-
80
- </style>
@@ -7,3 +7,6 @@ export declare const Docs: Story;
7
7
  export declare const Empty: Story;
8
8
  export declare const Skeleton: Story;
9
9
  export declare const Loading: Story;
10
+ export declare const Collapsible: Story;
11
+ export declare const MultipleCollapseStrategy: Story;
12
+ export declare const DefaultCollapseOpen: Story;