@webitel/ui-sdk 26.8.124 → 26.8.126
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.
- package/dist/{clients-DB8pfOu3.js → clients-DV2KSEHt.js} +363 -350
- package/dist/{components-BQdHCAFb.js → components-BAjXjwgz.js} +468 -376
- package/dist/{convertDuration-BePVKvK7.js → convertDuration-B5bq1uxb.js} +1 -1
- package/dist/{enums-DxMEZrBG.js → enums-Xag5PrU-.js} +31 -27
- package/dist/ui-sdk.css +1 -1
- package/dist/ui-sdk.js +3 -3
- package/dist/ui-sdk.umd.cjs +660 -660
- package/dist/{useValidation-D5TO0uhR.js → useValidation-D3Tu7osU.js} +1 -1
- package/dist/{utils-CPN7-J-Q.js → utils-B4S9Yz-y.js} +1 -1
- package/dist/{wt-action-bar-B-9xnBTL.js → wt-action-bar-BAkn5Om9.js} +2 -2
- package/dist/{wt-badge-BVmgv9--.js → wt-badge-DoYHG05A.js} +1 -1
- package/dist/{wt-button-bcce_YuV.js → wt-button-BfNbgYHy.js} +4 -4
- package/dist/{wt-chat-emoji-TrDrwRSR.js → wt-chat-emoji-DUrA-v1_.js} +4 -4
- package/dist/{wt-datepicker-B5KHFajA.js → wt-datepicker-D5qEc5xY.js} +6 -6
- package/dist/{wt-display-chip-items-BgEg9DP1.js → wt-display-chip-items-CZvSZVP_.js} +2 -2
- package/dist/{wt-dual-panel-DOyhwf4I.js → wt-dual-panel-ruU3Urks.js} +1 -1
- package/dist/{wt-expansion-card-C5E3FWdz.js → wt-expansion-card-DLGj2407.js} +1 -1
- package/dist/{wt-expansion-panel-CUnL0cUQ.js → wt-expansion-panel-DfhgRSs4.js} +1 -1
- package/dist/{wt-icon-BBMBXy7F.js → wt-icon-CCEttp-t.js} +1 -1
- package/dist/{wt-icon-action-BklvRyKt.js → wt-icon-action-B6KmDhfn.js} +1 -1
- package/dist/{wt-icon-btn-BC7iDIVr.js → wt-icon-btn-B7BR2BJW.js} +1 -1
- package/dist/{wt-notifications-bar-26cq-yoJ.js → wt-notifications-bar-B20L-XMV.js} +2 -2
- package/dist/{wt-player-Cqv4wQTe.js → wt-player-68WtJY5v.js} +3 -3
- package/dist/{wt-screen-recordings-action-NPGKFuQh.js → wt-screen-recordings-action-Yx0duVey.js} +1 -1
- package/dist/{wt-search-bar-BRgvzuKB.js → wt-search-bar-CxMHtUb-.js} +1 -1
- package/dist/{wt-send-message-popup-BEVTTQIC.js → wt-send-message-popup-DphWte4I.js} +3 -3
- package/dist/{wt-status-select-DfWLVRWJ.js → wt-status-select-3qvqcdaM.js} +2 -2
- package/dist/{wt-table-1lchEUvV.js → wt-table-BDJXh1rJ.js} +3 -3
- package/dist/{wt-timepicker-gUeA9XtI.js → wt-timepicker-BtjKfsPX.js} +2 -2
- package/dist/{wt-toast-ehTHwE5R.js → wt-toast-BScwyGca.js} +2 -2
- package/dist/{wt-tree-table-CQX1WVzP.js → wt-tree-table-B4N6HMgm.js} +1 -1
- package/dist/{wt-type-extension-value-input-C5TLLXps.js → wt-type-extension-value-input-BjmJrhU5.js} +122 -122
- package/dist/{wt-vidstack-player-B726cUC6.js → wt-vidstack-player-Zns27bwa.js} +5 -5
- package/package.json +1 -1
- package/src/components/index.js +9 -0
- package/src/components/wt-content-wrapper/__tests__/WtContentWrapper.spec.js +34 -0
- package/src/components/wt-content-wrapper/_variables.css +10 -0
- package/src/components/wt-content-wrapper/wt-content-wrapper.vue +46 -0
- package/src/components/wt-layout/__tests__/WtLayout.spec.js +153 -0
- package/src/components/wt-layout/_variables.css +15 -0
- package/src/components/wt-layout/wt-layout.vue +217 -0
- package/src/components/wt-page/__tests__/WtPage.spec.js +35 -0
- package/src/components/wt-page/_variables.css +15 -0
- package/src/components/wt-page/wt-page.vue +52 -0
- package/src/components/wt-textarea/__tests__/WtTextarea.spec.js +19 -0
- package/src/components/wt-textarea/wt-textarea.vue +3 -0
- package/src/css/components-variables.css +3 -0
- package/types/.tsbuildinfo +1 -1
- package/types/components/index.d.ts +7 -1
- package/types/components/wt-content-wrapper/__tests__/WtContentWrapper.spec.d.ts +1 -0
- package/types/components/wt-content-wrapper/wt-content-wrapper.vue.d.ts +18 -0
- package/types/components/wt-layout/__tests__/WtLayout.spec.d.ts +1 -0
- package/types/components/wt-layout/wt-layout.vue.d.ts +36 -0
- package/types/components/wt-page/__tests__/WtPage.spec.d.ts +1 -0
- package/types/components/wt-page/wt-page.vue.d.ts +13 -0
- package/types/components/wt-textarea/wt-textarea.vue.d.ts +2 -0
|
@@ -0,0 +1,153 @@
|
|
|
1
|
+
import { mount, shallowMount } from '@vue/test-utils';
|
|
2
|
+
|
|
3
|
+
import WtLayout from '../wt-layout.vue';
|
|
4
|
+
|
|
5
|
+
const mockRect = (el, width) => {
|
|
6
|
+
el.getBoundingClientRect = () => ({
|
|
7
|
+
width,
|
|
8
|
+
});
|
|
9
|
+
el.style.minWidth = '320px';
|
|
10
|
+
};
|
|
11
|
+
|
|
12
|
+
const mountPair = (props) => {
|
|
13
|
+
const Host = {
|
|
14
|
+
components: {
|
|
15
|
+
WtLayout,
|
|
16
|
+
},
|
|
17
|
+
template: `
|
|
18
|
+
<div>
|
|
19
|
+
<wt-layout v-bind="layoutProps" @resize="onResize" />
|
|
20
|
+
<wt-layout class="neighbor" />
|
|
21
|
+
</div>
|
|
22
|
+
`,
|
|
23
|
+
data: () => ({
|
|
24
|
+
layoutProps: props,
|
|
25
|
+
resized: [],
|
|
26
|
+
}),
|
|
27
|
+
methods: {
|
|
28
|
+
onResize(width) {
|
|
29
|
+
this.resized.push(width);
|
|
30
|
+
},
|
|
31
|
+
},
|
|
32
|
+
};
|
|
33
|
+
const wrapper = mount(Host, {
|
|
34
|
+
attachTo: document.body,
|
|
35
|
+
});
|
|
36
|
+
const [layout, neighbor] = wrapper.findAll('.wt-layout');
|
|
37
|
+
mockRect(layout.element, 400);
|
|
38
|
+
mockRect(neighbor.element, 600);
|
|
39
|
+
return {
|
|
40
|
+
wrapper,
|
|
41
|
+
layout,
|
|
42
|
+
};
|
|
43
|
+
};
|
|
44
|
+
|
|
45
|
+
describe('WtLayout', () => {
|
|
46
|
+
it('renders a component', () => {
|
|
47
|
+
const wrapper = shallowMount(WtLayout);
|
|
48
|
+
expect(wrapper.classes('wt-layout')).toBe(true);
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
it('renders content via default slot', () => {
|
|
52
|
+
const content = 'Layout content';
|
|
53
|
+
const wrapper = shallowMount(WtLayout, {
|
|
54
|
+
slots: {
|
|
55
|
+
default: content,
|
|
56
|
+
},
|
|
57
|
+
});
|
|
58
|
+
expect(wrapper.text()).toBe(content);
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
it('is fluid without defaultWidth', () => {
|
|
62
|
+
const wrapper = shallowMount(WtLayout);
|
|
63
|
+
expect(wrapper.classes('wt-layout--fixed')).toBe(false);
|
|
64
|
+
expect(wrapper.attributes('style')).toBeUndefined();
|
|
65
|
+
});
|
|
66
|
+
|
|
67
|
+
it('starts at defaultWidth', () => {
|
|
68
|
+
const wrapper = shallowMount(WtLayout, {
|
|
69
|
+
props: {
|
|
70
|
+
defaultWidth: 320,
|
|
71
|
+
},
|
|
72
|
+
});
|
|
73
|
+
expect(wrapper.classes('wt-layout--fixed')).toBe(true);
|
|
74
|
+
expect(wrapper.element.style.flexBasis).toBe('320px');
|
|
75
|
+
});
|
|
76
|
+
|
|
77
|
+
it('renders resizer only when resizable', async () => {
|
|
78
|
+
const wrapper = shallowMount(WtLayout);
|
|
79
|
+
expect(wrapper.find('.wt-layout__resizer').exists()).toBe(false);
|
|
80
|
+
await wrapper.setProps({
|
|
81
|
+
resizable: true,
|
|
82
|
+
});
|
|
83
|
+
expect(wrapper.find('.wt-layout__resizer').exists()).toBe(true);
|
|
84
|
+
});
|
|
85
|
+
|
|
86
|
+
it('resizes by dragging, bounded by the neighbor min width', async () => {
|
|
87
|
+
const { wrapper, layout } = mountPair({
|
|
88
|
+
resizable: true,
|
|
89
|
+
});
|
|
90
|
+
const resizer = layout.find('.wt-layout__resizer');
|
|
91
|
+
|
|
92
|
+
await resizer.trigger('pointerdown', {
|
|
93
|
+
button: 0,
|
|
94
|
+
clientX: 400,
|
|
95
|
+
});
|
|
96
|
+
window.dispatchEvent(
|
|
97
|
+
new PointerEvent('pointermove', {
|
|
98
|
+
clientX: 500,
|
|
99
|
+
}),
|
|
100
|
+
);
|
|
101
|
+
await wrapper.vm.$nextTick();
|
|
102
|
+
expect(layout.element.style.flexBasis).toBe('500px');
|
|
103
|
+
|
|
104
|
+
// neighbor is 600px wide with 320px min => max 400 + 280 = 680
|
|
105
|
+
window.dispatchEvent(
|
|
106
|
+
new PointerEvent('pointermove', {
|
|
107
|
+
clientX: 2000,
|
|
108
|
+
}),
|
|
109
|
+
);
|
|
110
|
+
window.dispatchEvent(new PointerEvent('pointerup'));
|
|
111
|
+
await wrapper.vm.$nextTick();
|
|
112
|
+
expect(layout.element.style.flexBasis).toBe('680px');
|
|
113
|
+
expect(wrapper.vm.resized).toEqual([
|
|
114
|
+
680,
|
|
115
|
+
]);
|
|
116
|
+
wrapper.unmount();
|
|
117
|
+
});
|
|
118
|
+
|
|
119
|
+
it('does not shrink below its min width', async () => {
|
|
120
|
+
const { wrapper, layout } = mountPair({
|
|
121
|
+
resizable: true,
|
|
122
|
+
});
|
|
123
|
+
const resizer = layout.find('.wt-layout__resizer');
|
|
124
|
+
|
|
125
|
+
await resizer.trigger('pointerdown', {
|
|
126
|
+
button: 0,
|
|
127
|
+
clientX: 400,
|
|
128
|
+
});
|
|
129
|
+
window.dispatchEvent(
|
|
130
|
+
new PointerEvent('pointermove', {
|
|
131
|
+
clientX: 0,
|
|
132
|
+
}),
|
|
133
|
+
);
|
|
134
|
+
window.dispatchEvent(new PointerEvent('pointerup'));
|
|
135
|
+
await wrapper.vm.$nextTick();
|
|
136
|
+
expect(layout.element.style.flexBasis).toBe('320px');
|
|
137
|
+
wrapper.unmount();
|
|
138
|
+
});
|
|
139
|
+
|
|
140
|
+
it('resizes with arrow keys', async () => {
|
|
141
|
+
const { wrapper, layout } = mountPair({
|
|
142
|
+
resizable: true,
|
|
143
|
+
});
|
|
144
|
+
await layout.find('.wt-layout__resizer').trigger('keydown', {
|
|
145
|
+
key: 'ArrowRight',
|
|
146
|
+
});
|
|
147
|
+
expect(layout.element.style.flexBasis).toBe('416px');
|
|
148
|
+
expect(wrapper.vm.resized).toEqual([
|
|
149
|
+
416,
|
|
150
|
+
]);
|
|
151
|
+
wrapper.unmount();
|
|
152
|
+
});
|
|
153
|
+
});
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
:root {
|
|
2
|
+
--wt-layout-background-color: var(--p-surface-0);
|
|
3
|
+
--wt-layout-border-radius: var(--p-border-radius-lg);
|
|
4
|
+
--wt-layout-padding: 0;
|
|
5
|
+
--wt-layout-gap: 0;
|
|
6
|
+
--wt-layout-min-width: 320px;
|
|
7
|
+
|
|
8
|
+
--wt-layout-resizer-width: var(--spacing-xs);
|
|
9
|
+
--wt-layout-resizer-line-width: 2px;
|
|
10
|
+
--wt-layout-resizer-active-color: var(--p-primary-color);
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
:root.theme--dark {
|
|
14
|
+
--wt-layout-background-color: var(--p-surface-800);
|
|
15
|
+
}
|
|
@@ -0,0 +1,217 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<section
|
|
3
|
+
ref="root"
|
|
4
|
+
:class="{
|
|
5
|
+
'wt-layout--fixed': width !== undefined,
|
|
6
|
+
'wt-layout--resizing': resizing,
|
|
7
|
+
}"
|
|
8
|
+
:style="width !== undefined ? { flexBasis: `${width}px` } : undefined"
|
|
9
|
+
class="wt-layout"
|
|
10
|
+
>
|
|
11
|
+
<slot />
|
|
12
|
+
<div
|
|
13
|
+
v-if="resizable"
|
|
14
|
+
:aria-valuenow="width"
|
|
15
|
+
:class="`wt-layout__resizer--${resizeEdge}`"
|
|
16
|
+
aria-orientation="vertical"
|
|
17
|
+
class="wt-layout__resizer"
|
|
18
|
+
role="separator"
|
|
19
|
+
tabindex="0"
|
|
20
|
+
@keydown="handleResizerKeydown"
|
|
21
|
+
@pointerdown="startResize"
|
|
22
|
+
/>
|
|
23
|
+
</section>
|
|
24
|
+
</template>
|
|
25
|
+
|
|
26
|
+
<script setup lang="ts">
|
|
27
|
+
import { onBeforeUnmount, ref, useTemplateRef, watch } from 'vue';
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* Structural container inside `wt-page` body. Fills available width
|
|
31
|
+
* (min 320px) unless `defaultWidth` is set; can be resized by dragging
|
|
32
|
+
* its edge, the adjacent layout compensates. To hide a layout, the parent
|
|
33
|
+
* (i.e. its sibling's owner) removes it with `v-if` / `v-show` — the rest
|
|
34
|
+
* redistribute the space automatically.
|
|
35
|
+
*/
|
|
36
|
+
interface Props {
|
|
37
|
+
/** Initial width in px. Without it, the layout is fluid. */
|
|
38
|
+
defaultWidth?: number;
|
|
39
|
+
/** Allows the user to change the width by dragging the edge. */
|
|
40
|
+
resizable?: boolean;
|
|
41
|
+
/** Which edge carries the resize handle. The neighbor on that side compensates. */
|
|
42
|
+
resizeEdge?: 'start' | 'end';
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
const props = withDefaults(defineProps<Props>(), {
|
|
46
|
+
defaultWidth: undefined,
|
|
47
|
+
resizable: false,
|
|
48
|
+
resizeEdge: 'end',
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
const emit = defineEmits<{
|
|
52
|
+
/** Emitted once a resize is finished, with the new width in px. */
|
|
53
|
+
resize: [
|
|
54
|
+
width: number,
|
|
55
|
+
];
|
|
56
|
+
}>();
|
|
57
|
+
|
|
58
|
+
const KEYBOARD_STEP = 16;
|
|
59
|
+
|
|
60
|
+
const root = useTemplateRef<HTMLElement>('root');
|
|
61
|
+
const width = ref<number | undefined>(props.defaultWidth);
|
|
62
|
+
const resizing = ref(false);
|
|
63
|
+
|
|
64
|
+
watch(
|
|
65
|
+
() => props.defaultWidth,
|
|
66
|
+
(value) => {
|
|
67
|
+
width.value = value;
|
|
68
|
+
},
|
|
69
|
+
);
|
|
70
|
+
|
|
71
|
+
const getWidthBounds = () => {
|
|
72
|
+
const el = root.value as HTMLElement;
|
|
73
|
+
const current = el.getBoundingClientRect().width;
|
|
74
|
+
const min = Number.parseFloat(getComputedStyle(el).minWidth) || 0;
|
|
75
|
+
const neighbor =
|
|
76
|
+
props.resizeEdge === 'end'
|
|
77
|
+
? el.nextElementSibling
|
|
78
|
+
: el.previousElementSibling;
|
|
79
|
+
if (!(neighbor instanceof HTMLElement)) {
|
|
80
|
+
return {
|
|
81
|
+
current,
|
|
82
|
+
min,
|
|
83
|
+
max: Number.POSITIVE_INFINITY,
|
|
84
|
+
};
|
|
85
|
+
}
|
|
86
|
+
const neighborMin =
|
|
87
|
+
Number.parseFloat(getComputedStyle(neighbor).minWidth) || 0;
|
|
88
|
+
const max = current + neighbor.getBoundingClientRect().width - neighborMin;
|
|
89
|
+
return {
|
|
90
|
+
current,
|
|
91
|
+
min,
|
|
92
|
+
max: Math.max(min, max),
|
|
93
|
+
};
|
|
94
|
+
};
|
|
95
|
+
|
|
96
|
+
const clamp = (value: number, min: number, max: number) =>
|
|
97
|
+
Math.min(Math.max(value, min), max);
|
|
98
|
+
|
|
99
|
+
let stopResize: (() => void) | null = null;
|
|
100
|
+
|
|
101
|
+
const startResize = (event: PointerEvent) => {
|
|
102
|
+
if (!root.value || event.button !== 0) return;
|
|
103
|
+
event.preventDefault();
|
|
104
|
+
|
|
105
|
+
const { current, min, max } = getWidthBounds();
|
|
106
|
+
const startX = event.clientX;
|
|
107
|
+
const direction = props.resizeEdge === 'end' ? 1 : -1;
|
|
108
|
+
width.value = current;
|
|
109
|
+
resizing.value = true;
|
|
110
|
+
|
|
111
|
+
const onMove = (moveEvent: PointerEvent) => {
|
|
112
|
+
const delta = (moveEvent.clientX - startX) * direction;
|
|
113
|
+
width.value = clamp(current + delta, min, max);
|
|
114
|
+
};
|
|
115
|
+
|
|
116
|
+
const onUp = () => {
|
|
117
|
+
stopResize?.();
|
|
118
|
+
emit('resize', width.value as number);
|
|
119
|
+
};
|
|
120
|
+
|
|
121
|
+
stopResize = () => {
|
|
122
|
+
window.removeEventListener('pointermove', onMove);
|
|
123
|
+
window.removeEventListener('pointerup', onUp);
|
|
124
|
+
window.removeEventListener('pointercancel', onUp);
|
|
125
|
+
resizing.value = false;
|
|
126
|
+
stopResize = null;
|
|
127
|
+
};
|
|
128
|
+
|
|
129
|
+
window.addEventListener('pointermove', onMove);
|
|
130
|
+
window.addEventListener('pointerup', onUp);
|
|
131
|
+
window.addEventListener('pointercancel', onUp);
|
|
132
|
+
};
|
|
133
|
+
|
|
134
|
+
const handleResizerKeydown = (event: KeyboardEvent) => {
|
|
135
|
+
if (!root.value) return;
|
|
136
|
+
const keyDirection = {
|
|
137
|
+
ArrowLeft: -1,
|
|
138
|
+
ArrowRight: 1,
|
|
139
|
+
}[event.key];
|
|
140
|
+
if (!keyDirection) return;
|
|
141
|
+
event.preventDefault();
|
|
142
|
+
|
|
143
|
+
const { current, min, max } = getWidthBounds();
|
|
144
|
+
const direction = props.resizeEdge === 'end' ? 1 : -1;
|
|
145
|
+
width.value = clamp(
|
|
146
|
+
current + keyDirection * direction * KEYBOARD_STEP,
|
|
147
|
+
min,
|
|
148
|
+
max,
|
|
149
|
+
);
|
|
150
|
+
emit('resize', width.value);
|
|
151
|
+
};
|
|
152
|
+
|
|
153
|
+
onBeforeUnmount(() => stopResize?.());
|
|
154
|
+
</script>
|
|
155
|
+
|
|
156
|
+
<style scoped>
|
|
157
|
+
.wt-layout {
|
|
158
|
+
display: flex;
|
|
159
|
+
position: relative;
|
|
160
|
+
flex: 1 1 0;
|
|
161
|
+
flex-direction: column;
|
|
162
|
+
gap: var(--wt-layout-gap);
|
|
163
|
+
box-sizing: border-box;
|
|
164
|
+
border-radius: var(--wt-layout-border-radius);
|
|
165
|
+
background: var(--wt-layout-background-color);
|
|
166
|
+
padding: var(--wt-layout-padding);
|
|
167
|
+
min-width: var(--wt-layout-min-width);
|
|
168
|
+
min-height: 0;
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
.wt-layout--fixed {
|
|
172
|
+
flex-grow: 0;
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
.wt-layout--resizing {
|
|
176
|
+
user-select: none;
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
.wt-layout__resizer {
|
|
180
|
+
position: absolute;
|
|
181
|
+
top: 0;
|
|
182
|
+
bottom: 0;
|
|
183
|
+
z-index: 1;
|
|
184
|
+
cursor: col-resize;
|
|
185
|
+
width: var(--wt-layout-resizer-width);
|
|
186
|
+
touch-action: none;
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
.wt-layout__resizer--end {
|
|
190
|
+
right: calc(var(--wt-layout-resizer-width) * -1);
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
.wt-layout__resizer--start {
|
|
194
|
+
left: calc(var(--wt-layout-resizer-width) * -1);
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
.wt-layout__resizer::after {
|
|
198
|
+
position: absolute;
|
|
199
|
+
top: 0;
|
|
200
|
+
bottom: 0;
|
|
201
|
+
left: calc(50% - var(--wt-layout-resizer-line-width) / 2);
|
|
202
|
+
transition: var(--transition);
|
|
203
|
+
background: transparent;
|
|
204
|
+
width: var(--wt-layout-resizer-line-width);
|
|
205
|
+
content: '';
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
.wt-layout__resizer:hover::after,
|
|
209
|
+
.wt-layout__resizer:focus-visible::after,
|
|
210
|
+
.wt-layout--resizing .wt-layout__resizer::after {
|
|
211
|
+
background: var(--wt-layout-resizer-active-color);
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
.wt-layout__resizer:focus-visible {
|
|
215
|
+
outline: none;
|
|
216
|
+
}
|
|
217
|
+
</style>
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import { shallowMount } from '@vue/test-utils';
|
|
2
|
+
|
|
3
|
+
import WtPage from '../wt-page.vue';
|
|
4
|
+
|
|
5
|
+
describe('WtPage', () => {
|
|
6
|
+
it('renders a component', () => {
|
|
7
|
+
const wrapper = shallowMount(WtPage);
|
|
8
|
+
expect(wrapper.classes('wt-page')).toBe(true);
|
|
9
|
+
});
|
|
10
|
+
|
|
11
|
+
it('renders layouts into the body via default slot', () => {
|
|
12
|
+
const content = 'Page layouts';
|
|
13
|
+
const wrapper = shallowMount(WtPage, {
|
|
14
|
+
slots: {
|
|
15
|
+
default: content,
|
|
16
|
+
},
|
|
17
|
+
});
|
|
18
|
+
expect(wrapper.find('.wt-page__body').text()).toBe(content);
|
|
19
|
+
});
|
|
20
|
+
|
|
21
|
+
it('renders header via header slot', () => {
|
|
22
|
+
const content = 'Page header';
|
|
23
|
+
const wrapper = shallowMount(WtPage, {
|
|
24
|
+
slots: {
|
|
25
|
+
header: content,
|
|
26
|
+
},
|
|
27
|
+
});
|
|
28
|
+
expect(wrapper.find('.wt-page__header').text()).toBe(content);
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
it('omits header section without header slot', () => {
|
|
32
|
+
const wrapper = shallowMount(WtPage);
|
|
33
|
+
expect(wrapper.find('.wt-page__header').exists()).toBe(false);
|
|
34
|
+
});
|
|
35
|
+
});
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
:root {
|
|
2
|
+
--wt-page-background-color: var(--p-surface-50);
|
|
3
|
+
--wt-page-padding-x: 0;
|
|
4
|
+
--wt-page-padding-y: 0;
|
|
5
|
+
--wt-page-gap: 0;
|
|
6
|
+
|
|
7
|
+
--wt-page-body-background-color: transparent;
|
|
8
|
+
--wt-page-body-padding-x: var(--spacing-sm);
|
|
9
|
+
--wt-page-body-padding-y: var(--spacing-sm);
|
|
10
|
+
--wt-page-body-gap: var(--spacing-sm);
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
:root.theme--dark {
|
|
14
|
+
--wt-page-background-color: var(--p-surface-850);
|
|
15
|
+
}
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<div class="wt-page">
|
|
3
|
+
<div v-if="$slots.header" class="wt-page__header">
|
|
4
|
+
<slot name="header" />
|
|
5
|
+
</div>
|
|
6
|
+
<div class="wt-page__body">
|
|
7
|
+
<!-- @slot Layouts of the page -->
|
|
8
|
+
<slot />
|
|
9
|
+
</div>
|
|
10
|
+
</div>
|
|
11
|
+
</template>
|
|
12
|
+
|
|
13
|
+
<script setup lang="ts">
|
|
14
|
+
/**
|
|
15
|
+
* Top structural level of the interface: header + body, where the body
|
|
16
|
+
* distributes its width between one or more `wt-layout`s.
|
|
17
|
+
*/
|
|
18
|
+
defineSlots<{
|
|
19
|
+
header?: () => unknown;
|
|
20
|
+
default?: () => unknown;
|
|
21
|
+
}>();
|
|
22
|
+
</script>
|
|
23
|
+
|
|
24
|
+
<style scoped>
|
|
25
|
+
.wt-page {
|
|
26
|
+
display: flex;
|
|
27
|
+
flex-direction: column;
|
|
28
|
+
gap: var(--wt-page-gap);
|
|
29
|
+
box-sizing: border-box;
|
|
30
|
+
background: var(--wt-page-background-color);
|
|
31
|
+
padding: var(--wt-page-padding-y) var(--wt-page-padding-x);
|
|
32
|
+
width: 100%;
|
|
33
|
+
height: 100%;
|
|
34
|
+
min-height: 0;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
.wt-page__header {
|
|
38
|
+
flex: 0 0 auto;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
.wt-page__body {
|
|
42
|
+
display: flex;
|
|
43
|
+
position: relative;
|
|
44
|
+
flex: 1 1 auto;
|
|
45
|
+
gap: var(--wt-page-body-gap);
|
|
46
|
+
box-sizing: border-box;
|
|
47
|
+
background: var(--wt-page-body-background-color);
|
|
48
|
+
padding: var(--wt-page-body-padding-y) var(--wt-page-body-padding-x);
|
|
49
|
+
min-width: 0;
|
|
50
|
+
min-height: 0;
|
|
51
|
+
}
|
|
52
|
+
</style>
|
|
@@ -101,4 +101,23 @@ describe('WtTextarea', () => {
|
|
|
101
101
|
});
|
|
102
102
|
expect(wrapper.findComponent(WtLabel).exists()).toBe(false);
|
|
103
103
|
});
|
|
104
|
+
|
|
105
|
+
it('marks textarea invalid and shows the first error from regleValidation', () => {
|
|
106
|
+
const wrapper = mount(WtTextarea, {
|
|
107
|
+
stubs: {
|
|
108
|
+
WtLabel,
|
|
109
|
+
},
|
|
110
|
+
props: {
|
|
111
|
+
label: 'Result',
|
|
112
|
+
regleValidation: {
|
|
113
|
+
$error: true,
|
|
114
|
+
$errors: [
|
|
115
|
+
'Field is required',
|
|
116
|
+
],
|
|
117
|
+
},
|
|
118
|
+
},
|
|
119
|
+
});
|
|
120
|
+
expect(wrapper.find('.wt-label').classes()).toContain('wt-label--invalid');
|
|
121
|
+
expect(wrapper.text()).toContain('Field is required');
|
|
122
|
+
});
|
|
104
123
|
});
|
|
@@ -51,6 +51,7 @@
|
|
|
51
51
|
</template>
|
|
52
52
|
|
|
53
53
|
<script setup lang="ts">
|
|
54
|
+
import type { SuperCompatibleRegleFieldStatus } from '@regle/core';
|
|
54
55
|
import type { TextareaProps } from 'primevue/textarea';
|
|
55
56
|
import { computed, onMounted, ref, toRef, useSlots, useTemplateRef } from 'vue';
|
|
56
57
|
import { ComponentSize, MessageVariant } from '../../enums';
|
|
@@ -136,6 +137,7 @@ interface Props extends /* @vue-ignore */ TextareaProps {
|
|
|
136
137
|
* Validation rules
|
|
137
138
|
*/
|
|
138
139
|
v?: VuelidateFieldLike;
|
|
140
|
+
regleValidation?: SuperCompatibleRegleFieldStatus;
|
|
139
141
|
/**
|
|
140
142
|
* Custom validators array
|
|
141
143
|
* @type {Array<{name: string, text: string}>}
|
|
@@ -186,6 +188,7 @@ const { isValidation, invalid, validationText, validationTextColor } =
|
|
|
186
188
|
useValidation({
|
|
187
189
|
v: toRef(props, 'v'),
|
|
188
190
|
customValidators: toRef(props, 'customValidators'),
|
|
191
|
+
regleValidation: toRef(props, 'regleValidation'),
|
|
189
192
|
});
|
|
190
193
|
|
|
191
194
|
const handleKeypress = (event: KeyboardEvent) => {
|
|
@@ -12,6 +12,7 @@
|
|
|
12
12
|
@import "../components/wt-chip/_variables.css";
|
|
13
13
|
@import "../components/wt-context-menu/_variables.css";
|
|
14
14
|
@import "../components/wt-datepicker/_variables.css";
|
|
15
|
+
@import "../components/wt-content-wrapper/_variables.css";
|
|
15
16
|
@import "../components/wt-divider/_variables.css";
|
|
16
17
|
@import "../components/wt-dual-panel/_variables.css";
|
|
17
18
|
@import "../components/wt-dummy/_variables.css";
|
|
@@ -25,11 +26,13 @@
|
|
|
25
26
|
@import "../components/wt-input/_variables.css";
|
|
26
27
|
@import "../components/wt-item-link/_variables.css";
|
|
27
28
|
@import "../components/wt-label/_variables.css";
|
|
29
|
+
@import "../components/wt-layout/_variables.css";
|
|
28
30
|
@import "../components/wt-load-bar/_variables.css";
|
|
29
31
|
@import "../components/wt-loader/_variables.css";
|
|
30
32
|
@import "../components/wt-logo/_variables.css";
|
|
31
33
|
@import "../components/wt-navigation-bar/_variables.css";
|
|
32
34
|
@import "../components/wt-notifications-bar/_variables.css";
|
|
35
|
+
@import "../components/wt-page/_variables.css";
|
|
33
36
|
@import "../components/wt-page-wrapper/_variables.css";
|
|
34
37
|
@import "../components/wt-pagination/_variables.css";
|
|
35
38
|
@import "../components/wt-player/_variables.css";
|