@pequity/squirrel 8.4.5 → 8.5.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.
- package/README.md +29 -0
- package/dist/cjs/chunks/index.js +530 -179
- package/dist/cjs/chunks/p-alert.js +11 -16
- package/dist/cjs/chunks/p-btn.js +1 -1
- package/dist/cjs/chunks/p-input-percent.js +2 -2
- package/dist/cjs/index.js +33 -27
- package/dist/cjs/inputClasses.js +3 -3
- package/dist/cjs/p-icon.js +2 -1
- package/dist/cjs/p-loading.js +2 -2
- package/dist/cjs/p-modal.js +45 -43
- package/dist/cjs/p-table-header-cell.js +3 -2
- package/dist/cjs/p-table.js +2 -0
- package/dist/cjs/usePTableHeaderWrap.js +38 -0
- package/dist/es/chunks/index.js +530 -179
- package/dist/es/chunks/p-alert.js +11 -16
- package/dist/es/chunks/p-btn.js +2 -2
- package/dist/es/chunks/p-input-percent.js +2 -2
- package/dist/es/index.js +39 -33
- package/dist/es/inputClasses.js +4 -4
- package/dist/es/p-icon.js +2 -1
- package/dist/es/p-loading.js +2 -2
- package/dist/es/p-modal.js +45 -43
- package/dist/es/p-table-header-cell.js +3 -2
- package/dist/es/p-table.js +2 -0
- package/dist/es/usePTableHeaderWrap.js +38 -0
- package/dist/squirrel/components/p-action-bar/p-action-bar.vue.d.ts +1 -1
- package/dist/squirrel/components/p-alert/p-alert.vue.d.ts +2 -2
- package/dist/squirrel/components/p-avatar/p-avatar.vue.d.ts +1 -1
- package/dist/squirrel/components/p-btn/p-btn.vue.d.ts +3 -3
- package/dist/squirrel/components/p-card/p-card.vue.d.ts +1 -1
- package/dist/squirrel/components/p-checkbox/p-checkbox.vue.d.ts +1 -1
- package/dist/squirrel/components/p-close-btn/p-close-btn.vue.d.ts +1 -1
- package/dist/squirrel/components/p-date-picker/p-date-picker.vue.d.ts +1 -1
- package/dist/squirrel/components/p-drawer/p-drawer.vue.d.ts +12 -12
- package/dist/squirrel/components/p-dropdown-select/p-dropdown-select.vue.d.ts +1 -1
- package/dist/squirrel/components/p-file-upload/p-file-upload.vue.d.ts +1 -1
- package/dist/squirrel/components/p-icon/p-icon.types.d.ts +1 -0
- package/dist/squirrel/components/p-icon/p-icon.vue.d.ts +1 -1
- package/dist/squirrel/components/p-info-icon/p-info-icon.vue.d.ts +1 -1
- package/dist/squirrel/components/p-inline-date-picker/p-inline-date-picker.vue.d.ts +1 -1
- package/dist/squirrel/components/p-input/p-input.vue.d.ts +1 -1
- package/dist/squirrel/components/p-input-percent/p-input-percent.vue.d.ts +1 -1
- package/dist/squirrel/components/p-input-search/p-input-search.vue.d.ts +1 -1
- package/dist/squirrel/components/p-link/p-link.vue.d.ts +1 -1
- package/dist/squirrel/components/p-loading/p-loading.vue.d.ts +1 -1
- package/dist/squirrel/components/p-modal/p-modal.vue.d.ts +5 -1
- package/dist/squirrel/components/p-pagination/p-pagination.vue.d.ts +1 -1
- package/dist/squirrel/components/p-pagination-info/p-pagination-info.vue.d.ts +1 -1
- package/dist/squirrel/components/p-progress-bar/p-progress-bar.vue.d.ts +1 -1
- package/dist/squirrel/components/p-ring-loader/p-ring-loader.vue.d.ts +1 -1
- package/dist/squirrel/components/p-select/p-select.vue.d.ts +1 -1
- package/dist/squirrel/components/p-select-btn/p-select-btn.vue.d.ts +1 -1
- package/dist/squirrel/components/p-select-list/p-select-list.vue.d.ts +1 -1
- package/dist/squirrel/components/p-steps/p-steps.vue.d.ts +1 -1
- package/dist/squirrel/components/p-table/p-table.types.d.ts +1 -0
- package/dist/squirrel/components/p-table/p-table.vue.d.ts +1 -1
- package/dist/squirrel/components/p-table/usePTableHeaderWrap.d.ts +4 -0
- package/dist/squirrel/components/p-table-header-cell/p-table-filter-icon.vue.d.ts +1 -1
- package/dist/squirrel/components/p-table-header-cell/p-table-header-cell.vue.d.ts +3 -3
- package/dist/squirrel/components/p-table-loader/p-table-loader.vue.d.ts +1 -1
- package/dist/squirrel/components/p-table-sort/p-table-sort.vue.d.ts +1 -1
- package/dist/squirrel/components/p-table-td/p-table-td.vue.d.ts +1 -1
- package/dist/squirrel/components/p-tabs/p-tabs.vue.d.ts +1 -1
- package/dist/squirrel/components/p-tabs-pills/p-tabs-pills.vue.d.ts +1 -1
- package/dist/squirrel/components/p-textarea/p-textarea.vue.d.ts +1 -1
- package/dist/squirrel/components/p-toggle/p-toggle.vue.d.ts +1 -1
- package/dist/squirrel.css +22 -22
- package/package.json +23 -21
- package/squirrel/components/p-alert/p-alert.spec.js +4 -4
- package/squirrel/components/p-alert/p-alert.stories.js +19 -13
- package/squirrel/components/p-alert/p-alert.vue +9 -11
- package/squirrel/components/p-icon/p-icon.types.ts +1 -0
- package/squirrel/components/p-modal/p-modal-basic.spec.js +29 -3
- package/squirrel/components/p-modal/p-modal.vue +44 -33
- package/squirrel/components/p-table/p-table.spec.js +51 -15
- package/squirrel/components/p-table/p-table.types.ts +2 -0
- package/squirrel/components/p-table/p-table.vue +7 -4
- package/squirrel/components/p-table/usePTableHeaderWrap.spec.js +118 -0
- package/squirrel/components/p-table/usePTableHeaderWrap.ts +45 -0
- package/squirrel/components/p-table-header-cell/p-table-header-cell.spec.js +5 -1
- package/squirrel/components/p-table-header-cell/p-table-header-cell.vue +2 -1
package/package.json
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@pequity/squirrel",
|
|
3
3
|
"description": "Squirrel component library",
|
|
4
|
-
"version": "8.
|
|
5
|
-
"packageManager": "pnpm@10.
|
|
4
|
+
"version": "8.5.0",
|
|
5
|
+
"packageManager": "pnpm@10.15.1",
|
|
6
6
|
"type": "module",
|
|
7
7
|
"scripts": {
|
|
8
8
|
"preinstall": "npx only-allow pnpm",
|
|
@@ -13,6 +13,7 @@
|
|
|
13
13
|
"lint": "eslint \"**/*.{vue,ts,js}\"",
|
|
14
14
|
"lint-fix": "eslint --fix \"**/*.{vue,ts,js}\"",
|
|
15
15
|
"test:unit": "vitest --project=unit",
|
|
16
|
+
"test:unit-nowatch": "vitest run --project=unit",
|
|
16
17
|
"test:unit-coverage": "vitest run --project=unit --coverage && make-coverage-badge",
|
|
17
18
|
"typecheck": "vue-tsc --noEmit",
|
|
18
19
|
"storybook": "storybook dev -p 6006",
|
|
@@ -49,57 +50,58 @@
|
|
|
49
50
|
"devDependencies": {
|
|
50
51
|
"@commitlint/cli": "^19.8.1",
|
|
51
52
|
"@commitlint/config-conventional": "^19.8.1",
|
|
52
|
-
"@pequity/eslint-config": "^2.0.
|
|
53
|
-
"@playwright/test": "^1.
|
|
53
|
+
"@pequity/eslint-config": "^2.0.3",
|
|
54
|
+
"@playwright/test": "^1.55.0",
|
|
54
55
|
"@semantic-release/changelog": "^6.0.3",
|
|
55
56
|
"@semantic-release/git": "^10.0.1",
|
|
56
|
-
"@storybook/addon-a11y": "^9.
|
|
57
|
-
"@storybook/addon-docs": "^9.
|
|
58
|
-
"@storybook/addon-links": "^9.
|
|
59
|
-
"@storybook/addon-vitest": "^9.
|
|
60
|
-
"@storybook/vue3-vite": "^9.
|
|
57
|
+
"@storybook/addon-a11y": "^9.1.4",
|
|
58
|
+
"@storybook/addon-docs": "^9.1.4",
|
|
59
|
+
"@storybook/addon-links": "^9.1.4",
|
|
60
|
+
"@storybook/addon-vitest": "^9.1.4",
|
|
61
|
+
"@storybook/vue3-vite": "^9.1.4",
|
|
61
62
|
"@tanstack/vue-virtual": "3.13.12",
|
|
62
63
|
"@types/jsdom": "^21.1.7",
|
|
63
64
|
"@types/lodash-es": "^4.17.12",
|
|
64
|
-
"@types/node": "^24.
|
|
65
|
+
"@types/node": "^24.3.0",
|
|
65
66
|
"@vitejs/plugin-vue": "^6.0.1",
|
|
66
67
|
"@vitest/browser": "3.2.4",
|
|
67
68
|
"@vitest/coverage-v8": "^3.2.4",
|
|
68
|
-
"@vue/compiler-sfc": "3.5.
|
|
69
|
+
"@vue/compiler-sfc": "3.5.21",
|
|
69
70
|
"@vue/test-utils": "^2.4.6",
|
|
70
71
|
"@vuepic/vue-datepicker": "11.0.2",
|
|
71
72
|
"autoprefixer": "^10.4.21",
|
|
72
|
-
"eslint": "^9.
|
|
73
|
-
"eslint-plugin-storybook": "^9.
|
|
73
|
+
"eslint": "^9.34.0",
|
|
74
|
+
"eslint-plugin-storybook": "^9.1.4",
|
|
74
75
|
"floating-vue": "5.2.2",
|
|
75
76
|
"glob": "^11.0.3",
|
|
76
77
|
"husky": "^9.1.7",
|
|
77
78
|
"iconify-icon": "^3.0.0",
|
|
78
79
|
"jsdom": "^26.1.0",
|
|
79
|
-
"lint-staged": "^16.1.
|
|
80
|
+
"lint-staged": "^16.1.6",
|
|
80
81
|
"lodash-es": "4.17.21",
|
|
81
82
|
"make-coverage-badge": "^1.2.0",
|
|
82
|
-
"playwright": "^1.
|
|
83
|
+
"playwright": "^1.55.0",
|
|
83
84
|
"postcss": "^8.5.6",
|
|
84
85
|
"prettier": "^3.6.2",
|
|
85
86
|
"prettier-plugin-tailwindcss": "^0.6.14",
|
|
86
87
|
"resolve-tspaths": "^0.8.23",
|
|
87
88
|
"rimraf": "^6.0.1",
|
|
88
|
-
"sass": "^1.
|
|
89
|
+
"sass": "^1.92.0",
|
|
89
90
|
"semantic-release": "^24.2.7",
|
|
90
|
-
"storybook": "^9.
|
|
91
|
+
"storybook": "^9.1.4",
|
|
91
92
|
"svgo": "^4.0.0",
|
|
92
93
|
"tailwindcss": "^3.4.17",
|
|
93
94
|
"typescript": "5.8.3",
|
|
94
|
-
"vite": "^7.
|
|
95
|
+
"vite": "^7.1.4",
|
|
95
96
|
"vitest": "^3.2.4",
|
|
96
|
-
"vue": "3.5.
|
|
97
|
+
"vue": "3.5.21",
|
|
97
98
|
"vue-currency-input": "3.2.1",
|
|
98
99
|
"vue-router": "4.5.1",
|
|
99
100
|
"vue-toastification": "2.0.0-rc.5",
|
|
100
|
-
"vue-tsc": "3.0.
|
|
101
|
+
"vue-tsc": "3.0.6"
|
|
101
102
|
},
|
|
102
103
|
"dependencies": {
|
|
103
|
-
"tailwind-
|
|
104
|
+
"tailwind-merge": "^3.3.1",
|
|
105
|
+
"tailwind-variants": "^3.1.0"
|
|
104
106
|
}
|
|
105
107
|
}
|
|
@@ -3,10 +3,10 @@ import { createWrapperFor } from '@tests/vitest.helpers';
|
|
|
3
3
|
|
|
4
4
|
describe('PAlert.vue', () => {
|
|
5
5
|
it.each([
|
|
6
|
-
['info', ['bg-info', 'text-on-info'], '
|
|
7
|
-
['warning', ['bg-warning', 'text-on-warning'], '
|
|
8
|
-
['error', ['bg-error', 'text-on-error'], '
|
|
9
|
-
['success', ['bg-success', 'text-on-success'], '
|
|
6
|
+
['info', ['bg-info', 'text-on-info'], 'material-symbols:info-outline'],
|
|
7
|
+
['warning', ['bg-warning', 'text-on-warning'], 'warning'],
|
|
8
|
+
['error', ['bg-error', 'text-on-error'], 'cancel-circle'],
|
|
9
|
+
['success', ['bg-success', 'text-on-success'], 'ok-circle'],
|
|
10
10
|
])('renders an alert of type %s', async (type, classes, iconName) => {
|
|
11
11
|
const wrapper = createWrapperFor(PAlert, { props: { type }, global: { stubs: { PIcon: true } } });
|
|
12
12
|
|
|
@@ -5,6 +5,13 @@ export default {
|
|
|
5
5
|
title: 'Components/PAlert',
|
|
6
6
|
component: PAlert,
|
|
7
7
|
tags: ['autodocs'],
|
|
8
|
+
render: (args) => ({
|
|
9
|
+
components: { PAlert },
|
|
10
|
+
setup() {
|
|
11
|
+
return { args };
|
|
12
|
+
},
|
|
13
|
+
template: `<PAlert v-bind="args">${args.default}</PAlert>`,
|
|
14
|
+
}),
|
|
8
15
|
argTypes: {
|
|
9
16
|
type: {
|
|
10
17
|
control: { type: 'select' },
|
|
@@ -22,16 +29,10 @@ export default {
|
|
|
22
29
|
};
|
|
23
30
|
|
|
24
31
|
export const Info = {
|
|
25
|
-
render: (args) => ({
|
|
26
|
-
components: { PAlert },
|
|
27
|
-
setup() {
|
|
28
|
-
return { args };
|
|
29
|
-
},
|
|
30
|
-
template: `<PAlert v-bind="args">${args.default}</PAlert>`,
|
|
31
|
-
}),
|
|
32
32
|
args: {
|
|
33
33
|
type: 'info',
|
|
34
|
-
default:
|
|
34
|
+
default:
|
|
35
|
+
'Your account has been updated with the latest security features.<br />Two-factor authentication is now enabled for enhanced protection.',
|
|
35
36
|
},
|
|
36
37
|
};
|
|
37
38
|
|
|
@@ -39,28 +40,33 @@ Info.play = async ({ canvasElement }) => {
|
|
|
39
40
|
const canvas = within(canvasElement);
|
|
40
41
|
const alertDiv = await canvas.getByRole('alert');
|
|
41
42
|
|
|
42
|
-
await expect(alertDiv.innerText).toBe(
|
|
43
|
-
|
|
43
|
+
await expect(alertDiv.innerText).toBe(
|
|
44
|
+
'Your account has been updated with the latest security features.\nTwo-factor authentication is now enabled for enhanced protection.'
|
|
45
|
+
);
|
|
46
|
+
await expect(alertDiv).toHaveClass('p-4 rounded-lg text-xs font-semibold bg-info text-on-info');
|
|
44
47
|
await expect(alertDiv).toHaveStyle('background-color: #e4edfa');
|
|
45
48
|
};
|
|
46
49
|
|
|
47
50
|
export const Success = {
|
|
48
51
|
args: {
|
|
49
|
-
...Info.args,
|
|
50
52
|
type: 'success',
|
|
53
|
+
default:
|
|
54
|
+
'Your profile has been successfully updated and changes are now live.<br />Other team members can now see your updated information.',
|
|
51
55
|
},
|
|
52
56
|
};
|
|
53
57
|
|
|
54
58
|
export const Warning = {
|
|
55
59
|
args: {
|
|
56
|
-
...Info.args,
|
|
57
60
|
type: 'warning',
|
|
61
|
+
default:
|
|
62
|
+
'You have unsaved changes that will be lost if you navigate away from this page.<br />Make sure to save your work before continuing.',
|
|
58
63
|
},
|
|
59
64
|
};
|
|
60
65
|
|
|
61
66
|
export const Error = {
|
|
62
67
|
args: {
|
|
63
|
-
...Info.args,
|
|
64
68
|
type: 'error',
|
|
69
|
+
default:
|
|
70
|
+
'Failed to save changes. Please check your internet connection and try again.<br />If the problem persists, contact support at help@pequity.com.',
|
|
65
71
|
},
|
|
66
72
|
};
|
|
@@ -1,11 +1,9 @@
|
|
|
1
1
|
<template>
|
|
2
|
-
<div :class="['rounded p-4 text-xs font-semibold', ALERT_TYPES[props.type].classes]" role="alert">
|
|
3
|
-
<div class="flex">
|
|
4
|
-
<
|
|
5
|
-
<
|
|
6
|
-
|
|
7
|
-
</slot>
|
|
8
|
-
</div>
|
|
2
|
+
<div :class="['rounded-lg p-4 text-xs font-semibold', ALERT_TYPES[props.type].classes]" role="alert">
|
|
3
|
+
<div class="flex items-center gap-4">
|
|
4
|
+
<slot name="icon">
|
|
5
|
+
<PIcon :icon="ALERT_TYPES[props.type].icon" width="20" />
|
|
6
|
+
</slot>
|
|
9
7
|
<div>
|
|
10
8
|
<slot></slot>
|
|
11
9
|
</div>
|
|
@@ -18,10 +16,10 @@ import PIcon from '@squirrel/components/p-icon/p-icon.vue';
|
|
|
18
16
|
import { type PropType } from 'vue';
|
|
19
17
|
|
|
20
18
|
const ALERT_TYPES = {
|
|
21
|
-
info: { classes: `bg-info text-on-info`, icon: '
|
|
22
|
-
warning: { classes: `bg-warning text-on-warning`, icon: '
|
|
23
|
-
error: { classes: `bg-error text-on-error`, icon: '
|
|
24
|
-
success: { classes: `bg-success text-on-success`, icon: '
|
|
19
|
+
info: { classes: `bg-info text-on-info`, icon: 'material-symbols:info-outline' },
|
|
20
|
+
warning: { classes: `bg-warning text-on-warning`, icon: 'warning' },
|
|
21
|
+
error: { classes: `bg-error text-on-error`, icon: 'cancel-circle' },
|
|
22
|
+
success: { classes: `bg-success text-on-success`, icon: 'ok-circle' },
|
|
25
23
|
};
|
|
26
24
|
</script>
|
|
27
25
|
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import PModal from '@squirrel/components/p-modal/p-modal.vue';
|
|
2
|
-
import { waitRAF } from '@tests/vitest.helpers';
|
|
2
|
+
import { waitNT, waitRAF } from '@tests/vitest.helpers';
|
|
3
3
|
import { mount } from '@vue/test-utils';
|
|
4
4
|
|
|
5
5
|
const createWrapperContainer = (componentArgs) => {
|
|
@@ -85,18 +85,44 @@ describe('Modal basic functionality', () => {
|
|
|
85
85
|
await wrapper.setData({ showModal: true });
|
|
86
86
|
|
|
87
87
|
expect(wrapper.find('[data-pm-id]').classes()).toEqual(
|
|
88
|
-
'pm relative flex flex-col rounded-2xl
|
|
88
|
+
'pm relative flex flex-col rounded-2xl cursor-default bg-surface shadow-xl pb-6'.split(' ')
|
|
89
89
|
);
|
|
90
90
|
|
|
91
91
|
wrapper.unmount();
|
|
92
92
|
});
|
|
93
93
|
|
|
94
|
+
it('sets the correct base modal class when modal-wrapper slot is used', async () => {
|
|
95
|
+
const wrapper = mount(PModal, {
|
|
96
|
+
attachTo: document.body,
|
|
97
|
+
global: {
|
|
98
|
+
stubs: {
|
|
99
|
+
transition: true,
|
|
100
|
+
teleport: true,
|
|
101
|
+
},
|
|
102
|
+
},
|
|
103
|
+
props: {
|
|
104
|
+
modelValue: true,
|
|
105
|
+
},
|
|
106
|
+
slots: {
|
|
107
|
+
'modal-wrapper': '<div>Modal content goes here...</div>',
|
|
108
|
+
},
|
|
109
|
+
});
|
|
110
|
+
|
|
111
|
+
await waitNT(wrapper.vm);
|
|
112
|
+
|
|
113
|
+
const modalContent = wrapper.find('[data-pm-id]');
|
|
114
|
+
|
|
115
|
+
expect(modalContent.classes()).not.toContain('pb-6');
|
|
116
|
+
|
|
117
|
+
wrapper.unmount();
|
|
118
|
+
});
|
|
119
|
+
|
|
94
120
|
it('passes the modalBaseClass prop to the modal', async () => {
|
|
95
121
|
const wrapper = createWrapperContainer({ modalBaseClass: 'custom-class' });
|
|
96
122
|
|
|
97
123
|
await wrapper.setData({ showModal: true });
|
|
98
124
|
|
|
99
|
-
expect(wrapper.find('[data-pm-id]').classes()).toEqual(['custom-class']);
|
|
125
|
+
expect(wrapper.find('[data-pm-id]').classes()).toEqual(['custom-class', 'pb-6']);
|
|
100
126
|
|
|
101
127
|
wrapper.unmount();
|
|
102
128
|
});
|
|
@@ -29,40 +29,47 @@
|
|
|
29
29
|
@click="overlayClick($event)"
|
|
30
30
|
@keydown="keydown($event)"
|
|
31
31
|
>
|
|
32
|
-
<div
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
32
|
+
<div
|
|
33
|
+
ref="pm"
|
|
34
|
+
:data-pm-id="id"
|
|
35
|
+
:class="[modalBaseClass, modalClass, { 'pb-6': !$slots['modal-wrapper'] }]"
|
|
36
|
+
:style="modalStyle"
|
|
37
|
+
>
|
|
38
|
+
<slot name="modal-wrapper">
|
|
39
|
+
<slot name="title-wrapper">
|
|
40
|
+
<div class="flex pb-4 pl-8 pr-4 pt-4">
|
|
41
|
+
<h3 v-if="title" :id="`${id}-title`" class="mr-auto pt-4 text-xl font-semibold">
|
|
42
|
+
{{ title }}
|
|
43
|
+
</h3>
|
|
44
|
+
<div class="ml-auto">
|
|
45
|
+
<PCloseBtn
|
|
46
|
+
:disabled="disabled"
|
|
47
|
+
:class="{ invisible: !enableClose }"
|
|
48
|
+
:aria-label="closeLabel"
|
|
49
|
+
@click.prevent="close"
|
|
50
|
+
/>
|
|
51
|
+
</div>
|
|
45
52
|
</div>
|
|
53
|
+
</slot>
|
|
54
|
+
<div v-if="errorMsg" class="mb-4 px-8">
|
|
55
|
+
<PAlert type="error">{{ errorMsg }}</PAlert>
|
|
46
56
|
</div>
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
>
|
|
59
|
-
<
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
<div v-if="$slots.footer" class="px-8 pt-6">
|
|
64
|
-
<slot name="footer"></slot>
|
|
65
|
-
</div>
|
|
57
|
+
<slot name="content-wrapper">
|
|
58
|
+
<div
|
|
59
|
+
:id="`${id}-content`"
|
|
60
|
+
:class="[
|
|
61
|
+
'relative grow overflow-y-auto overflow-x-hidden px-8',
|
|
62
|
+
{ 'pointer-events-none opacity-50': disabled },
|
|
63
|
+
]"
|
|
64
|
+
>
|
|
65
|
+
<slot></slot>
|
|
66
|
+
</div>
|
|
67
|
+
</slot>
|
|
68
|
+
<slot name="footer-wrapper">
|
|
69
|
+
<div v-if="$slots.footer" class="px-8 pt-6">
|
|
70
|
+
<slot name="footer"></slot>
|
|
71
|
+
</div>
|
|
72
|
+
</slot>
|
|
66
73
|
</slot>
|
|
67
74
|
</div>
|
|
68
75
|
</div>
|
|
@@ -106,6 +113,10 @@ defineSlots<{
|
|
|
106
113
|
* Default content slot for the modal body.
|
|
107
114
|
*/
|
|
108
115
|
default?: () => unknown;
|
|
116
|
+
/**
|
|
117
|
+
* Custom modal wrapper content.
|
|
118
|
+
*/
|
|
119
|
+
'modal-wrapper'?: () => unknown;
|
|
109
120
|
/**
|
|
110
121
|
* Custom title wrapper content.
|
|
111
122
|
*/
|
|
@@ -209,7 +220,7 @@ const props = defineProps({
|
|
|
209
220
|
*/
|
|
210
221
|
modalBaseClass: {
|
|
211
222
|
type: [String, Object, Array] as PropType<StyleValue>,
|
|
212
|
-
default: 'pm relative flex flex-col rounded-2xl
|
|
223
|
+
default: 'pm relative flex flex-col rounded-2xl cursor-default bg-surface shadow-xl',
|
|
213
224
|
},
|
|
214
225
|
/**
|
|
215
226
|
* Additional CSS classes for the modal content.
|
|
@@ -354,6 +354,57 @@ describe('PTable.vue', () => {
|
|
|
354
354
|
expect(wrapper.findAll('tbody div')[2].text()).toBe('true');
|
|
355
355
|
});
|
|
356
356
|
|
|
357
|
+
it('renders correctly with column resizing enabled', async () => {
|
|
358
|
+
const cols = cloneDeep(columns);
|
|
359
|
+
const wrapper = createWrapperFor(PTable, {
|
|
360
|
+
props: { cols, colsResizable: true },
|
|
361
|
+
global: {
|
|
362
|
+
stubs: {
|
|
363
|
+
PTableHeaderCell: { template: `<div class="header-cell-stub">{{ text }}</div>`, props: { text: '' } },
|
|
364
|
+
},
|
|
365
|
+
},
|
|
366
|
+
});
|
|
367
|
+
|
|
368
|
+
// Should have resize handles for middle columns (not first, not last when isLastColFixed)
|
|
369
|
+
const resizeHandles = wrapper.findAll('[data-resize-handle]');
|
|
370
|
+
expect(resizeHandles.length).toBe(cols.length - 1); // All columns except first
|
|
371
|
+
|
|
372
|
+
// Should have extra th for column resizing when not isLastColFixed
|
|
373
|
+
const extraTh = wrapper.find('thead th:last-child');
|
|
374
|
+
expect(extraTh.classes()).toContain('min-w-[80px]');
|
|
375
|
+
expect(extraTh.classes()).toContain('bg-gradient-to-r');
|
|
376
|
+
});
|
|
377
|
+
|
|
378
|
+
it('renders correctly with subheader', async () => {
|
|
379
|
+
const cols = cloneDeep(columns);
|
|
380
|
+
const wrapper = createWrapperFor(PTable, {
|
|
381
|
+
props: { cols, subheader: true, isFirstColFixed: true, isLastColFixed: true },
|
|
382
|
+
slots: {
|
|
383
|
+
'subheader-cell-first-column': `<div class="subheader-content">Subheader 1</div>`,
|
|
384
|
+
'subheader-cell-third-column': `<div class="subheader-content">Subheader 3</div>`,
|
|
385
|
+
},
|
|
386
|
+
global: {
|
|
387
|
+
stubs: {
|
|
388
|
+
PTableHeaderCell: { template: `<div class="header-cell-stub">{{ text }}</div>`, props: { text: '' } },
|
|
389
|
+
},
|
|
390
|
+
},
|
|
391
|
+
});
|
|
392
|
+
|
|
393
|
+
// Check subheader divs exist
|
|
394
|
+
const subheaderDivs = wrapper.findAll('.subheader-content');
|
|
395
|
+
expect(subheaderDivs.length).toBe(2);
|
|
396
|
+
expect(subheaderDivs[0].text()).toBe('Subheader 1');
|
|
397
|
+
expect(subheaderDivs[1].text()).toBe('Subheader 3');
|
|
398
|
+
|
|
399
|
+
// Check subheader classes include th-shadow for fixed columns
|
|
400
|
+
cols.forEach((col, i) => {
|
|
401
|
+
const subheaderDiv = wrapper.find(`th[data-col-id="${col.id}"] > div:last-child`);
|
|
402
|
+
if (i === 0 || i === cols.length - 1) {
|
|
403
|
+
expect(subheaderDiv.classes()).toContain('th-shadow');
|
|
404
|
+
}
|
|
405
|
+
});
|
|
406
|
+
});
|
|
407
|
+
|
|
357
408
|
it('shows additional rows when the virtualizer padding options are set', async () => {
|
|
358
409
|
const cols = cloneDeep(columns);
|
|
359
410
|
const wrapper = createWrapperFor(PTable, {
|
|
@@ -374,19 +425,4 @@ describe('PTable.vue', () => {
|
|
|
374
425
|
expect(wrapper.find('table tbody tr:last-child > td').attributes().style).toEqual('height: 100px;');
|
|
375
426
|
expect(wrapper.find('table tbody tr:last-child').classes()).toEqual([]);
|
|
376
427
|
});
|
|
377
|
-
|
|
378
|
-
it('sets th refs correctly', async () => {
|
|
379
|
-
const cols = cloneDeep(columns);
|
|
380
|
-
const wrapper = createWrapperFor(PTable, { props: { cols } });
|
|
381
|
-
|
|
382
|
-
const thsRefs = wrapper.vm.ths;
|
|
383
|
-
|
|
384
|
-
expect(thsRefs.length).toBe(3);
|
|
385
|
-
thsRefs.forEach((thRef, i) => {
|
|
386
|
-
const thClasses = [...thRef.classList];
|
|
387
|
-
|
|
388
|
-
expect(thClasses).toContain(cols[i].thAttrs.class);
|
|
389
|
-
expect(thRef instanceof HTMLTableCellElement).toBe(true);
|
|
390
|
-
});
|
|
391
|
-
});
|
|
392
428
|
});
|
|
@@ -21,7 +21,7 @@
|
|
|
21
21
|
]"
|
|
22
22
|
v-on="colsResizable ? { mousemove: colResize } : {}"
|
|
23
23
|
>
|
|
24
|
-
<thead>
|
|
24
|
+
<thead ref="theadRef">
|
|
25
25
|
<tr>
|
|
26
26
|
<th
|
|
27
27
|
v-for="(col, i) in props.cols"
|
|
@@ -33,7 +33,7 @@
|
|
|
33
33
|
class="bg-surface"
|
|
34
34
|
>
|
|
35
35
|
<div :class="thDivClasses(i)" :style="bgColorStyle(col)">
|
|
36
|
-
<div class="flex">
|
|
36
|
+
<div :class="['flex', { 'h-10': hasWrap }]">
|
|
37
37
|
<slot :name="`prepend-header-cell-${kebabCase(col.name)}`" :col="col" />
|
|
38
38
|
<PTableHeaderCell
|
|
39
39
|
:text="col.title"
|
|
@@ -48,7 +48,7 @@
|
|
|
48
48
|
</div>
|
|
49
49
|
<div
|
|
50
50
|
v-if="colsResizable && i !== 0 && !(i === cols.length - 1 && isLastColFixed)"
|
|
51
|
-
class="absolute
|
|
51
|
+
class="absolute right-0 top-1/2 z-110 h-5 w-2 -translate-y-1/2 cursor-col-resize after:absolute after:bottom-0 after:z-110 after:block after:h-full after:w-2 after:cursor-col-resize after:border-r-2 after:border-dashed after:border-p-gray-30"
|
|
52
52
|
:class="i === cols.length - 1 ? 'after:right-0.5' : 'after:right-0'"
|
|
53
53
|
data-resize-handle
|
|
54
54
|
@mousedown="colResizeStart($event, i)"
|
|
@@ -108,10 +108,11 @@ import {
|
|
|
108
108
|
type TableCol,
|
|
109
109
|
} from '@squirrel/components/p-table/p-table.types';
|
|
110
110
|
import { usePTableColResize } from '@squirrel/components/p-table/usePTableColResize';
|
|
111
|
+
import { usePTableHeaderWrap } from '@squirrel/components/p-table/usePTableHeaderWrap';
|
|
111
112
|
import PTableHeaderCell from '@squirrel/components/p-table-header-cell/p-table-header-cell.vue';
|
|
112
113
|
import PTableTd from '@squirrel/components/p-table-td/p-table-td.vue';
|
|
113
114
|
import { kebabCase } from 'lodash-es';
|
|
114
|
-
import { computed, onBeforeUnmount, onMounted, provide, ref, watch } from 'vue';
|
|
115
|
+
import { computed, onBeforeUnmount, onMounted, provide, ref, useTemplateRef, watch } from 'vue';
|
|
115
116
|
|
|
116
117
|
type Props = {
|
|
117
118
|
/**
|
|
@@ -209,6 +210,7 @@ provide(
|
|
|
209
210
|
|
|
210
211
|
// Data
|
|
211
212
|
const scrollWrapper = ref<HTMLElement | null>(null);
|
|
213
|
+
const theadRef = useTemplateRef('theadRef');
|
|
212
214
|
const ths = ref<HTMLElement[]>([]);
|
|
213
215
|
const {
|
|
214
216
|
isColResizing,
|
|
@@ -222,6 +224,7 @@ const {
|
|
|
222
224
|
enabled: computed(() => props.colsResizable),
|
|
223
225
|
ths,
|
|
224
226
|
});
|
|
227
|
+
const { hasWrap } = usePTableHeaderWrap(theadRef);
|
|
225
228
|
const tbodyElement = ref<HTMLElement | null>(null);
|
|
226
229
|
|
|
227
230
|
// Methods
|
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
import { HEADER_CELL_ONE_LINE_HEIGHT } from '@squirrel/components/p-table/p-table.types';
|
|
2
|
+
import { usePTableHeaderWrap } from '@squirrel/components/p-table/usePTableHeaderWrap';
|
|
3
|
+
import { waitNT } from '@tests/vitest.helpers';
|
|
4
|
+
import { mount } from '@vue/test-utils';
|
|
5
|
+
import { defineComponent, useTemplateRef } from 'vue';
|
|
6
|
+
|
|
7
|
+
// Mock ResizeObserver to capture the composable's callback
|
|
8
|
+
let composableResizeCallback;
|
|
9
|
+
const mockResizeObserver = vi.fn((callback) => {
|
|
10
|
+
composableResizeCallback = callback;
|
|
11
|
+
return {
|
|
12
|
+
observe: vi.fn(),
|
|
13
|
+
unobserve: vi.fn(),
|
|
14
|
+
disconnect: vi.fn(),
|
|
15
|
+
};
|
|
16
|
+
});
|
|
17
|
+
|
|
18
|
+
const createWrapper = (refName = 'theadRef') => {
|
|
19
|
+
const TestComponent = defineComponent({
|
|
20
|
+
setup() {
|
|
21
|
+
const theadRef = useTemplateRef('theadRef');
|
|
22
|
+
const { hasWrap } = usePTableHeaderWrap(theadRef);
|
|
23
|
+
|
|
24
|
+
return {
|
|
25
|
+
theadRef,
|
|
26
|
+
hasWrap,
|
|
27
|
+
};
|
|
28
|
+
},
|
|
29
|
+
template: `
|
|
30
|
+
<table>
|
|
31
|
+
<thead ref="${refName}" :data-has-wrap="hasWrap">
|
|
32
|
+
<tr>
|
|
33
|
+
<th>
|
|
34
|
+
<div data-p-table-header-text class="div-to-resize">Short</div>
|
|
35
|
+
</th>
|
|
36
|
+
<th>
|
|
37
|
+
<span data-p-table-header-text>Very Long Header That Could Potentially Wrap To Multiple Lines</span>
|
|
38
|
+
</th>
|
|
39
|
+
</tr>
|
|
40
|
+
</thead>
|
|
41
|
+
</table>
|
|
42
|
+
`,
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
return mount(TestComponent, {
|
|
46
|
+
attachTo: document.body,
|
|
47
|
+
});
|
|
48
|
+
};
|
|
49
|
+
|
|
50
|
+
describe('usePTableHeaderWrap', () => {
|
|
51
|
+
const originalOffsetHeight = Object.getOwnPropertyDescriptor(HTMLElement.prototype, 'offsetHeight');
|
|
52
|
+
const originalGlobalResizeObserver = globalThis.ResizeObserver;
|
|
53
|
+
|
|
54
|
+
beforeAll(() => {
|
|
55
|
+
Object.defineProperty(HTMLElement.prototype, 'offsetHeight', {
|
|
56
|
+
configurable: true,
|
|
57
|
+
value: HEADER_CELL_ONE_LINE_HEIGHT,
|
|
58
|
+
});
|
|
59
|
+
globalThis.ResizeObserver = mockResizeObserver;
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
afterAll(() => {
|
|
63
|
+
Object.defineProperty(HTMLElement.prototype, 'offsetHeight', originalOffsetHeight);
|
|
64
|
+
globalThis.ResizeObserver = originalGlobalResizeObserver;
|
|
65
|
+
});
|
|
66
|
+
|
|
67
|
+
beforeEach(() => {
|
|
68
|
+
mockResizeObserver.mockClear();
|
|
69
|
+
composableResizeCallback = null;
|
|
70
|
+
});
|
|
71
|
+
|
|
72
|
+
it('should have hasWrap false when all divs are single line height', async () => {
|
|
73
|
+
const wrapper = createWrapper();
|
|
74
|
+
|
|
75
|
+
// Trigger the composable's ResizeObserver callback
|
|
76
|
+
if (composableResizeCallback) {
|
|
77
|
+
composableResizeCallback();
|
|
78
|
+
await waitNT(wrapper.vm);
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
expect(wrapper.find('thead').attributes()['data-has-wrap']).toBe('false');
|
|
82
|
+
|
|
83
|
+
wrapper.unmount();
|
|
84
|
+
});
|
|
85
|
+
|
|
86
|
+
it('should have hasWrap true when one div is double line height', async () => {
|
|
87
|
+
// Override offsetHeight for the "div-to-resize" element
|
|
88
|
+
Object.defineProperty(HTMLElement.prototype, 'offsetHeight', {
|
|
89
|
+
configurable: true,
|
|
90
|
+
get() {
|
|
91
|
+
if (this.classList.contains('div-to-resize')) {
|
|
92
|
+
return HEADER_CELL_ONE_LINE_HEIGHT * 2;
|
|
93
|
+
}
|
|
94
|
+
return HEADER_CELL_ONE_LINE_HEIGHT;
|
|
95
|
+
},
|
|
96
|
+
});
|
|
97
|
+
|
|
98
|
+
const wrapper = createWrapper();
|
|
99
|
+
|
|
100
|
+
// Trigger the composable's ResizeObserver callback
|
|
101
|
+
if (composableResizeCallback) {
|
|
102
|
+
composableResizeCallback();
|
|
103
|
+
await waitNT(wrapper.vm);
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
expect(wrapper.find('thead').attributes()['data-has-wrap']).toBe('true');
|
|
107
|
+
|
|
108
|
+
wrapper.unmount();
|
|
109
|
+
});
|
|
110
|
+
|
|
111
|
+
it('should handle null theadRef gracefully', () => {
|
|
112
|
+
const wrapper = createWrapper('nullRef');
|
|
113
|
+
|
|
114
|
+
expect(wrapper.find('thead').attributes()['data-has-wrap']).toBe('false');
|
|
115
|
+
|
|
116
|
+
wrapper.unmount();
|
|
117
|
+
});
|
|
118
|
+
});
|