@pequity/squirrel 8.6.0 → 10.0.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 +1 -1
- package/dist/cjs/chunks/p-btn.js +14 -7
- package/dist/cjs/chunks/p-icon.js +1 -2058
- package/dist/cjs/number.js +3 -2
- package/dist/es/chunks/p-btn.js +14 -7
- package/dist/es/chunks/p-icon.js +1 -2058
- package/dist/es/number.js +3 -2
- package/dist/squirrel/components/p-btn/p-btn.vue.d.ts +51 -21
- package/package.json +21 -20
- package/squirrel/components/p-action-bar/p-action-bar.spec.ts +2 -2
- package/squirrel/components/p-btn/p-btn.spec.js +60 -10
- package/squirrel/components/p-btn/p-btn.stories.js +79 -16
- package/squirrel/components/p-btn/p-btn.vue +15 -7
- package/squirrel/utils/number.spec.js +13 -3
- package/squirrel/utils/number.ts +5 -2
package/dist/es/number.js
CHANGED
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
const toNumberOrNull = (val) => {
|
|
2
|
-
if (val === "" || val === null
|
|
2
|
+
if (val === "" || val === null) {
|
|
3
3
|
return null;
|
|
4
4
|
}
|
|
5
|
-
|
|
5
|
+
const num = Number(val);
|
|
6
|
+
return isFinite(num) ? num : null;
|
|
6
7
|
};
|
|
7
8
|
export {
|
|
8
9
|
toNumberOrNull
|
|
@@ -41,6 +41,16 @@ declare const btn: import("tailwind-variants").TVReturnType<{
|
|
|
41
41
|
readonly button: "bg-p-green-40 text-white hover:bg-p-green-50";
|
|
42
42
|
readonly loader: "text-white";
|
|
43
43
|
};
|
|
44
|
+
readonly black: {
|
|
45
|
+
readonly button: "bg-night text-white hover:bg-p-gray-80";
|
|
46
|
+
readonly loader: "text-white";
|
|
47
|
+
};
|
|
48
|
+
readonly 'black-outline-gradient': {
|
|
49
|
+
readonly button: "bg-gradient-to-r from-[#17c0e8] via-[#8e8bda] to-[#f65ece] hover:bg-gradient-to-l p-px group";
|
|
50
|
+
readonly content: "bg-night rounded-[calc(theme(borderRadius.DEFAULT)-1px)] text-white group-hover:bg-p-gray-80 transition-colors duration-300";
|
|
51
|
+
readonly icon: "-mx-px";
|
|
52
|
+
readonly loader: "text-white";
|
|
53
|
+
};
|
|
44
54
|
readonly 'primary-link': {
|
|
45
55
|
readonly button: "bg-transparent text-primary underline hover:text-accent";
|
|
46
56
|
readonly loader: "text-p-blue-60";
|
|
@@ -56,23 +66,23 @@ declare const btn: import("tailwind-variants").TVReturnType<{
|
|
|
56
66
|
};
|
|
57
67
|
readonly size: {
|
|
58
68
|
readonly sm: {
|
|
59
|
-
readonly button: "
|
|
60
|
-
readonly content: "gap-1";
|
|
69
|
+
readonly button: "text-sm leading-5 h-8";
|
|
70
|
+
readonly content: "px-3 has-[.slot-wrapper:empty]:px-1.5 py-1.5 gap-1";
|
|
61
71
|
readonly icon: "text-base p-0.5";
|
|
62
72
|
};
|
|
63
73
|
readonly md: {
|
|
64
|
-
readonly button: "
|
|
65
|
-
readonly content: "gap-2";
|
|
74
|
+
readonly button: "text-base h-10";
|
|
75
|
+
readonly content: "px-6 has-[.slot-wrapper:empty]:px-2.5 has-[.slot-wrapper:empty]:py-2.5 py-2 gap-2";
|
|
66
76
|
readonly icon: "text-xl";
|
|
67
77
|
};
|
|
68
78
|
readonly lg: {
|
|
69
|
-
readonly button: "
|
|
70
|
-
readonly content: "gap-2.5";
|
|
79
|
+
readonly button: "text-lg leading-6 h-12";
|
|
80
|
+
readonly content: "px-6 has-[.slot-wrapper:empty]:px-3 py-3 gap-2.5";
|
|
71
81
|
readonly icon: "text-2xl";
|
|
72
82
|
};
|
|
73
83
|
};
|
|
74
84
|
}, {
|
|
75
|
-
readonly button: "relative inline-
|
|
85
|
+
readonly button: "relative inline-flex whitespace-nowrap rounded font-medium outline-none disabled:pointer-events-none disabled:cursor-default disabled:opacity-50 aria-disabled:pointer-events-none aria-disabled:cursor-default aria-disabled:opacity-50";
|
|
76
86
|
readonly content: "flex items-center justify-center has-[.slot-wrapper:empty]:gap-0";
|
|
77
87
|
readonly loader: "absolute bottom-0 left-0 right-0 top-0 flex items-center justify-center font-medium";
|
|
78
88
|
readonly icon: "shrink-0";
|
|
@@ -110,6 +120,16 @@ declare const btn: import("tailwind-variants").TVReturnType<{
|
|
|
110
120
|
readonly button: "bg-p-green-40 text-white hover:bg-p-green-50";
|
|
111
121
|
readonly loader: "text-white";
|
|
112
122
|
};
|
|
123
|
+
readonly black: {
|
|
124
|
+
readonly button: "bg-night text-white hover:bg-p-gray-80";
|
|
125
|
+
readonly loader: "text-white";
|
|
126
|
+
};
|
|
127
|
+
readonly 'black-outline-gradient': {
|
|
128
|
+
readonly button: "bg-gradient-to-r from-[#17c0e8] via-[#8e8bda] to-[#f65ece] hover:bg-gradient-to-l p-px group";
|
|
129
|
+
readonly content: "bg-night rounded-[calc(theme(borderRadius.DEFAULT)-1px)] text-white group-hover:bg-p-gray-80 transition-colors duration-300";
|
|
130
|
+
readonly icon: "-mx-px";
|
|
131
|
+
readonly loader: "text-white";
|
|
132
|
+
};
|
|
113
133
|
readonly 'primary-link': {
|
|
114
134
|
readonly button: "bg-transparent text-primary underline hover:text-accent";
|
|
115
135
|
readonly loader: "text-p-blue-60";
|
|
@@ -125,23 +145,23 @@ declare const btn: import("tailwind-variants").TVReturnType<{
|
|
|
125
145
|
};
|
|
126
146
|
readonly size: {
|
|
127
147
|
readonly sm: {
|
|
128
|
-
readonly button: "
|
|
129
|
-
readonly content: "gap-1";
|
|
148
|
+
readonly button: "text-sm leading-5 h-8";
|
|
149
|
+
readonly content: "px-3 has-[.slot-wrapper:empty]:px-1.5 py-1.5 gap-1";
|
|
130
150
|
readonly icon: "text-base p-0.5";
|
|
131
151
|
};
|
|
132
152
|
readonly md: {
|
|
133
|
-
readonly button: "
|
|
134
|
-
readonly content: "gap-2";
|
|
153
|
+
readonly button: "text-base h-10";
|
|
154
|
+
readonly content: "px-6 has-[.slot-wrapper:empty]:px-2.5 has-[.slot-wrapper:empty]:py-2.5 py-2 gap-2";
|
|
135
155
|
readonly icon: "text-xl";
|
|
136
156
|
};
|
|
137
157
|
readonly lg: {
|
|
138
|
-
readonly button: "
|
|
139
|
-
readonly content: "gap-2.5";
|
|
158
|
+
readonly button: "text-lg leading-6 h-12";
|
|
159
|
+
readonly content: "px-6 has-[.slot-wrapper:empty]:px-3 py-3 gap-2.5";
|
|
140
160
|
readonly icon: "text-2xl";
|
|
141
161
|
};
|
|
142
162
|
};
|
|
143
163
|
}, {
|
|
144
|
-
readonly button: "relative inline-
|
|
164
|
+
readonly button: "relative inline-flex whitespace-nowrap rounded font-medium outline-none disabled:pointer-events-none disabled:cursor-default disabled:opacity-50 aria-disabled:pointer-events-none aria-disabled:cursor-default aria-disabled:opacity-50";
|
|
145
165
|
readonly content: "flex items-center justify-center has-[.slot-wrapper:empty]:gap-0";
|
|
146
166
|
readonly loader: "absolute bottom-0 left-0 right-0 top-0 flex items-center justify-center font-medium";
|
|
147
167
|
readonly icon: "shrink-0";
|
|
@@ -179,6 +199,16 @@ declare const btn: import("tailwind-variants").TVReturnType<{
|
|
|
179
199
|
readonly button: "bg-p-green-40 text-white hover:bg-p-green-50";
|
|
180
200
|
readonly loader: "text-white";
|
|
181
201
|
};
|
|
202
|
+
readonly black: {
|
|
203
|
+
readonly button: "bg-night text-white hover:bg-p-gray-80";
|
|
204
|
+
readonly loader: "text-white";
|
|
205
|
+
};
|
|
206
|
+
readonly 'black-outline-gradient': {
|
|
207
|
+
readonly button: "bg-gradient-to-r from-[#17c0e8] via-[#8e8bda] to-[#f65ece] hover:bg-gradient-to-l p-px group";
|
|
208
|
+
readonly content: "bg-night rounded-[calc(theme(borderRadius.DEFAULT)-1px)] text-white group-hover:bg-p-gray-80 transition-colors duration-300";
|
|
209
|
+
readonly icon: "-mx-px";
|
|
210
|
+
readonly loader: "text-white";
|
|
211
|
+
};
|
|
182
212
|
readonly 'primary-link': {
|
|
183
213
|
readonly button: "bg-transparent text-primary underline hover:text-accent";
|
|
184
214
|
readonly loader: "text-p-blue-60";
|
|
@@ -194,23 +224,23 @@ declare const btn: import("tailwind-variants").TVReturnType<{
|
|
|
194
224
|
};
|
|
195
225
|
readonly size: {
|
|
196
226
|
readonly sm: {
|
|
197
|
-
readonly button: "
|
|
198
|
-
readonly content: "gap-1";
|
|
227
|
+
readonly button: "text-sm leading-5 h-8";
|
|
228
|
+
readonly content: "px-3 has-[.slot-wrapper:empty]:px-1.5 py-1.5 gap-1";
|
|
199
229
|
readonly icon: "text-base p-0.5";
|
|
200
230
|
};
|
|
201
231
|
readonly md: {
|
|
202
|
-
readonly button: "
|
|
203
|
-
readonly content: "gap-2";
|
|
232
|
+
readonly button: "text-base h-10";
|
|
233
|
+
readonly content: "px-6 has-[.slot-wrapper:empty]:px-2.5 has-[.slot-wrapper:empty]:py-2.5 py-2 gap-2";
|
|
204
234
|
readonly icon: "text-xl";
|
|
205
235
|
};
|
|
206
236
|
readonly lg: {
|
|
207
|
-
readonly button: "
|
|
208
|
-
readonly content: "gap-2.5";
|
|
237
|
+
readonly button: "text-lg leading-6 h-12";
|
|
238
|
+
readonly content: "px-6 has-[.slot-wrapper:empty]:px-3 py-3 gap-2.5";
|
|
209
239
|
readonly icon: "text-2xl";
|
|
210
240
|
};
|
|
211
241
|
};
|
|
212
242
|
}, {
|
|
213
|
-
readonly button: "relative inline-
|
|
243
|
+
readonly button: "relative inline-flex whitespace-nowrap rounded font-medium outline-none disabled:pointer-events-none disabled:cursor-default disabled:opacity-50 aria-disabled:pointer-events-none aria-disabled:cursor-default aria-disabled:opacity-50";
|
|
214
244
|
readonly content: "flex items-center justify-center has-[.slot-wrapper:empty]:gap-0";
|
|
215
245
|
readonly loader: "absolute bottom-0 left-0 right-0 top-0 flex items-center justify-center font-medium";
|
|
216
246
|
readonly icon: "shrink-0";
|
package/package.json
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@pequity/squirrel",
|
|
3
3
|
"description": "Squirrel component library",
|
|
4
|
-
"version": "
|
|
5
|
-
"packageManager": "pnpm@10.
|
|
4
|
+
"version": "10.0.0",
|
|
5
|
+
"packageManager": "pnpm@10.17.1",
|
|
6
6
|
"type": "module",
|
|
7
7
|
"scripts": {
|
|
8
8
|
"preinstall": "npx only-allow pnpm",
|
|
@@ -41,6 +41,7 @@
|
|
|
41
41
|
"@tanstack/vue-virtual": "^3.8.3",
|
|
42
42
|
"@vuepic/vue-datepicker": "^11.0.1",
|
|
43
43
|
"floating-vue": "^5.2.2",
|
|
44
|
+
"iconify-icon": "^3.0.0",
|
|
44
45
|
"lodash-es": "^4.17.21",
|
|
45
46
|
"vue": "^3.4.33",
|
|
46
47
|
"vue-currency-input": "^3.1.0",
|
|
@@ -50,19 +51,19 @@
|
|
|
50
51
|
"devDependencies": {
|
|
51
52
|
"@commitlint/cli": "^19.8.1",
|
|
52
53
|
"@commitlint/config-conventional": "^19.8.1",
|
|
53
|
-
"@pequity/eslint-config": "^2.0.
|
|
54
|
+
"@pequity/eslint-config": "^2.0.4",
|
|
54
55
|
"@playwright/test": "^1.55.0",
|
|
55
56
|
"@semantic-release/changelog": "^6.0.3",
|
|
56
57
|
"@semantic-release/git": "^10.0.1",
|
|
57
|
-
"@storybook/addon-a11y": "^9.1.
|
|
58
|
-
"@storybook/addon-docs": "^9.1.
|
|
59
|
-
"@storybook/addon-links": "^9.1.
|
|
60
|
-
"@storybook/addon-vitest": "^9.1.
|
|
61
|
-
"@storybook/vue3-vite": "^9.1.
|
|
58
|
+
"@storybook/addon-a11y": "^9.1.7",
|
|
59
|
+
"@storybook/addon-docs": "^9.1.7",
|
|
60
|
+
"@storybook/addon-links": "^9.1.7",
|
|
61
|
+
"@storybook/addon-vitest": "^9.1.7",
|
|
62
|
+
"@storybook/vue3-vite": "^9.1.7",
|
|
62
63
|
"@tanstack/vue-virtual": "3.13.12",
|
|
63
64
|
"@types/jsdom": "^21.1.7",
|
|
64
65
|
"@types/lodash-es": "^4.17.12",
|
|
65
|
-
"@types/node": "^24.
|
|
66
|
+
"@types/node": "^24.5.2",
|
|
66
67
|
"@vitejs/plugin-vue": "^6.0.1",
|
|
67
68
|
"@vitest/browser": "3.2.4",
|
|
68
69
|
"@vitest/coverage-v8": "^3.2.4",
|
|
@@ -70,14 +71,14 @@
|
|
|
70
71
|
"@vue/test-utils": "^2.4.6",
|
|
71
72
|
"@vuepic/vue-datepicker": "11.0.2",
|
|
72
73
|
"autoprefixer": "^10.4.21",
|
|
73
|
-
"eslint": "^9.
|
|
74
|
-
"eslint-plugin-storybook": "^9.1.
|
|
74
|
+
"eslint": "^9.36.0",
|
|
75
|
+
"eslint-plugin-storybook": "^9.1.7",
|
|
75
76
|
"floating-vue": "5.2.2",
|
|
76
77
|
"glob": "^11.0.3",
|
|
77
78
|
"husky": "^9.1.7",
|
|
78
|
-
"iconify-icon": "^3.0.
|
|
79
|
-
"jsdom": "^
|
|
80
|
-
"lint-staged": "^16.
|
|
79
|
+
"iconify-icon": "^3.0.1",
|
|
80
|
+
"jsdom": "^27.0.0",
|
|
81
|
+
"lint-staged": "^16.2.0",
|
|
81
82
|
"lodash-es": "4.17.21",
|
|
82
83
|
"make-coverage-badge": "^1.2.0",
|
|
83
84
|
"playwright": "^1.55.0",
|
|
@@ -86,22 +87,22 @@
|
|
|
86
87
|
"prettier-plugin-tailwindcss": "^0.6.14",
|
|
87
88
|
"resolve-tspaths": "^0.8.23",
|
|
88
89
|
"rimraf": "^6.0.1",
|
|
89
|
-
"sass": "^1.
|
|
90
|
-
"semantic-release": "^24.2.
|
|
91
|
-
"storybook": "^9.1.
|
|
90
|
+
"sass": "^1.93.1",
|
|
91
|
+
"semantic-release": "^24.2.9",
|
|
92
|
+
"storybook": "^9.1.7",
|
|
92
93
|
"svgo": "^4.0.0",
|
|
93
94
|
"tailwindcss": "^3.4.17",
|
|
94
95
|
"typescript": "5.9.2",
|
|
95
|
-
"vite": "^7.1.
|
|
96
|
+
"vite": "^7.1.7",
|
|
96
97
|
"vitest": "^3.2.4",
|
|
97
98
|
"vue": "3.5.21",
|
|
98
99
|
"vue-currency-input": "3.2.1",
|
|
99
100
|
"vue-router": "4.5.1",
|
|
100
101
|
"vue-toastification": "2.0.0-rc.5",
|
|
101
|
-
"vue-tsc": "3.0.
|
|
102
|
+
"vue-tsc": "3.0.8"
|
|
102
103
|
},
|
|
103
104
|
"dependencies": {
|
|
104
105
|
"tailwind-merge": "^3.3.1",
|
|
105
|
-
"tailwind-variants": "^3.1.
|
|
106
|
+
"tailwind-variants": "^3.1.1"
|
|
106
107
|
}
|
|
107
108
|
}
|
|
@@ -110,8 +110,8 @@ describe('PActionBar.vue', () => {
|
|
|
110
110
|
expect(dismissIcon.exists()).toBe(true);
|
|
111
111
|
expect(actionBarDiv.find('.text-xs').text()).toBe('Clear All');
|
|
112
112
|
|
|
113
|
-
const actionBtn =
|
|
114
|
-
expect(actionBtn
|
|
113
|
+
const actionBtn = buttons.find((button: VueWrapper) => button.text().includes('Say Hi'));
|
|
114
|
+
expect(actionBtn).toBeDefined();
|
|
115
115
|
expect(actionBtn.text()).toContain('Say Hi');
|
|
116
116
|
});
|
|
117
117
|
|
|
@@ -18,7 +18,7 @@ const ELEMENTS_MAP = {
|
|
|
18
18
|
|
|
19
19
|
const DEFAULT_CLASSES_ARRAY = [
|
|
20
20
|
'relative',
|
|
21
|
-
'inline-
|
|
21
|
+
'inline-flex',
|
|
22
22
|
'whitespace-nowrap',
|
|
23
23
|
'outline-none',
|
|
24
24
|
'disabled:opacity-50',
|
|
@@ -72,6 +72,19 @@ describe('PBtn.vue', () => {
|
|
|
72
72
|
['text-p-red-50', 'bg-surface', 'ring-1', 'ring-inset', 'ring-p-red-20', 'hover:bg-p-red-10/30'],
|
|
73
73
|
],
|
|
74
74
|
['success', ['text-white', 'bg-p-green-40', 'hover:bg-p-green-50']],
|
|
75
|
+
['black', ['text-white', 'bg-night', 'hover:bg-p-gray-80']],
|
|
76
|
+
[
|
|
77
|
+
'black-outline-gradient',
|
|
78
|
+
[
|
|
79
|
+
'bg-gradient-to-r',
|
|
80
|
+
'from-[#17c0e8]',
|
|
81
|
+
'via-[#8e8bda]',
|
|
82
|
+
'to-[#f65ece]',
|
|
83
|
+
'hover:bg-gradient-to-l',
|
|
84
|
+
'p-px',
|
|
85
|
+
'group',
|
|
86
|
+
],
|
|
87
|
+
],
|
|
75
88
|
['primary-link', ['text-primary', 'bg-transparent', 'hover:text-accent', 'underline']],
|
|
76
89
|
['secondary-ghost', ['text-on-surface', 'hover:bg-p-gray-20']],
|
|
77
90
|
])(`renders a ${el} of type %s`, async (type, classes) => {
|
|
@@ -96,9 +109,46 @@ describe('PBtn.vue', () => {
|
|
|
96
109
|
});
|
|
97
110
|
|
|
98
111
|
it.each([
|
|
99
|
-
[
|
|
100
|
-
|
|
101
|
-
|
|
112
|
+
[
|
|
113
|
+
'sm',
|
|
114
|
+
[
|
|
115
|
+
'flex',
|
|
116
|
+
'items-center',
|
|
117
|
+
'justify-center',
|
|
118
|
+
'has-[.slot-wrapper:empty]:gap-0',
|
|
119
|
+
'px-3',
|
|
120
|
+
'has-[.slot-wrapper:empty]:px-1.5',
|
|
121
|
+
'py-1.5',
|
|
122
|
+
'gap-1',
|
|
123
|
+
],
|
|
124
|
+
],
|
|
125
|
+
[
|
|
126
|
+
'md',
|
|
127
|
+
[
|
|
128
|
+
'flex',
|
|
129
|
+
'items-center',
|
|
130
|
+
'justify-center',
|
|
131
|
+
'has-[.slot-wrapper:empty]:gap-0',
|
|
132
|
+
'px-6',
|
|
133
|
+
'has-[.slot-wrapper:empty]:px-2.5',
|
|
134
|
+
'has-[.slot-wrapper:empty]:py-2.5',
|
|
135
|
+
'py-2',
|
|
136
|
+
'gap-2',
|
|
137
|
+
],
|
|
138
|
+
],
|
|
139
|
+
[
|
|
140
|
+
'lg',
|
|
141
|
+
[
|
|
142
|
+
'flex',
|
|
143
|
+
'items-center',
|
|
144
|
+
'justify-center',
|
|
145
|
+
'has-[.slot-wrapper:empty]:gap-0',
|
|
146
|
+
'px-6',
|
|
147
|
+
'has-[.slot-wrapper:empty]:px-3',
|
|
148
|
+
'py-3',
|
|
149
|
+
'gap-2.5',
|
|
150
|
+
],
|
|
151
|
+
],
|
|
102
152
|
])('renders slots for content', async (size, classes) => {
|
|
103
153
|
const wrapper = createWrapperFor(PBtn, {
|
|
104
154
|
props: { size },
|
|
@@ -178,9 +228,9 @@ describe('PBtn.vue', () => {
|
|
|
178
228
|
});
|
|
179
229
|
|
|
180
230
|
it.each([
|
|
181
|
-
['sm', ['
|
|
182
|
-
['md', ['
|
|
183
|
-
['lg', ['
|
|
231
|
+
['sm', ['rounded', 'font-medium', 'text-sm', 'leading-5', 'h-8']],
|
|
232
|
+
['md', ['rounded', 'font-medium', 'text-base', 'h-10']],
|
|
233
|
+
['lg', ['rounded', 'font-medium', 'text-lg', 'leading-6', 'h-12']],
|
|
184
234
|
])('renders a button of size %s', async (size, classes) => {
|
|
185
235
|
const wrapper = createWrapperFor(PBtn, { props: { size }, slots: { default: `button` } });
|
|
186
236
|
|
|
@@ -294,17 +344,17 @@ describe('PBtn.vue', () => {
|
|
|
294
344
|
['sm', ['shrink-0', 'text-base', 'p-0.5'], ['has-[.slot-wrapper:empty]:px-1.5']],
|
|
295
345
|
['md', ['shrink-0', 'text-xl'], ['has-[.slot-wrapper:empty]:px-2.5']],
|
|
296
346
|
['lg', ['shrink-0', 'text-2xl'], ['has-[.slot-wrapper:empty]:px-3']],
|
|
297
|
-
])('renders a button with an icon without text of size %s', async (size, classes,
|
|
347
|
+
])('renders a button with an icon without text of size %s', async (size, classes, contentClasses) => {
|
|
298
348
|
const wrapper = createWrapperFor(PBtn, {
|
|
299
349
|
props: { size, icon: 'edit' },
|
|
300
350
|
global: { stubs: { PIcon: true } },
|
|
301
351
|
});
|
|
302
352
|
|
|
303
|
-
const
|
|
353
|
+
const content = wrapper.find('div');
|
|
304
354
|
const icon = wrapper.findComponent({ name: 'PIcon' });
|
|
305
355
|
|
|
306
356
|
expect(classes.every((c) => icon.classes().includes(c))).toBe(true);
|
|
307
|
-
expect(
|
|
357
|
+
expect(contentClasses.every((c) => content.classes().includes(c))).toBe(true);
|
|
308
358
|
});
|
|
309
359
|
});
|
|
310
360
|
});
|
|
@@ -2,6 +2,24 @@ import PBtn from '@squirrel/components/p-btn/p-btn.vue';
|
|
|
2
2
|
import { action } from 'storybook/actions';
|
|
3
3
|
import { expect, within } from 'storybook/test';
|
|
4
4
|
|
|
5
|
+
const BUTTON_TYPES = [
|
|
6
|
+
'primary',
|
|
7
|
+
'secondary',
|
|
8
|
+
'primary-outline',
|
|
9
|
+
'secondary-outline',
|
|
10
|
+
'secondary-outline-blue',
|
|
11
|
+
'error',
|
|
12
|
+
'error-outline',
|
|
13
|
+
'success',
|
|
14
|
+
'black',
|
|
15
|
+
'black-outline-gradient',
|
|
16
|
+
'primary-link',
|
|
17
|
+
'secondary-ghost',
|
|
18
|
+
'secondary-ghost-dark',
|
|
19
|
+
];
|
|
20
|
+
|
|
21
|
+
const BUTTON_SIZES = ['sm', 'md', 'lg'];
|
|
22
|
+
|
|
5
23
|
// You can also import an `md` file and use it as a story's description
|
|
6
24
|
// import PBtnReadme from './PBtn.readme.md';
|
|
7
25
|
|
|
@@ -14,18 +32,7 @@ export default {
|
|
|
14
32
|
argTypes: {
|
|
15
33
|
type: {
|
|
16
34
|
control: { type: 'select' },
|
|
17
|
-
options:
|
|
18
|
-
'primary',
|
|
19
|
-
'secondary',
|
|
20
|
-
'primary-outline',
|
|
21
|
-
'secondary-outline',
|
|
22
|
-
'secondary-outline-blue',
|
|
23
|
-
'error',
|
|
24
|
-
'error-outline',
|
|
25
|
-
'success',
|
|
26
|
-
'primary-link',
|
|
27
|
-
'secondary-ghost',
|
|
28
|
-
],
|
|
35
|
+
options: BUTTON_TYPES,
|
|
29
36
|
},
|
|
30
37
|
size: {
|
|
31
38
|
control: { type: 'select' },
|
|
@@ -65,7 +72,7 @@ Primary.play = async ({ canvasElement }) => {
|
|
|
65
72
|
|
|
66
73
|
await expect(primaryButton.innerText).toBe('Primary button');
|
|
67
74
|
await expect(primaryButton).toHaveClass(
|
|
68
|
-
'relative inline-
|
|
75
|
+
'relative inline-flex whitespace-nowrap rounded font-medium outline-none disabled:pointer-events-none disabled:cursor-default disabled:opacity-50 aria-disabled:pointer-events-none aria-disabled:cursor-default aria-disabled:opacity-50 bg-primary text-surface hover:bg-accent active:bg-p-blue-80 text-base h-10'
|
|
69
76
|
);
|
|
70
77
|
await expect(primaryButton).toHaveStyle('background-color: rgb(0, 0, 120)');
|
|
71
78
|
};
|
|
@@ -107,7 +114,7 @@ export const Icon = {
|
|
|
107
114
|
setup() {
|
|
108
115
|
return { args };
|
|
109
116
|
},
|
|
110
|
-
template:
|
|
117
|
+
template: `<PBtn v-bind="args" icon="archive">Icon Button</PBtn>`,
|
|
111
118
|
}),
|
|
112
119
|
args: {
|
|
113
120
|
size: 'md',
|
|
@@ -121,7 +128,7 @@ export const IconRight = {
|
|
|
121
128
|
setup() {
|
|
122
129
|
return { args };
|
|
123
130
|
},
|
|
124
|
-
template:
|
|
131
|
+
template: `<PBtn v-bind="args" icon-right="archive">Icon Button</PBtn>`,
|
|
125
132
|
}),
|
|
126
133
|
args: {
|
|
127
134
|
size: 'md',
|
|
@@ -135,7 +142,7 @@ export const IconWithoutContent = {
|
|
|
135
142
|
setup() {
|
|
136
143
|
return { args };
|
|
137
144
|
},
|
|
138
|
-
template:
|
|
145
|
+
template: `<PBtn v-bind="args" icon-right="archive"></PBtn>`,
|
|
139
146
|
}),
|
|
140
147
|
args: {
|
|
141
148
|
size: 'md',
|
|
@@ -205,3 +212,59 @@ export const Ghost = {
|
|
|
205
212
|
default: 'Ghost button',
|
|
206
213
|
},
|
|
207
214
|
};
|
|
215
|
+
|
|
216
|
+
export const Black = {
|
|
217
|
+
args: {
|
|
218
|
+
...Primary.args,
|
|
219
|
+
type: 'black',
|
|
220
|
+
default: 'Black button',
|
|
221
|
+
},
|
|
222
|
+
};
|
|
223
|
+
|
|
224
|
+
export const BlackGradientOutline = {
|
|
225
|
+
args: {
|
|
226
|
+
...Primary.args,
|
|
227
|
+
type: 'black-outline-gradient',
|
|
228
|
+
default: 'Black gradient outline',
|
|
229
|
+
},
|
|
230
|
+
};
|
|
231
|
+
|
|
232
|
+
export const IconButtonSizes = {
|
|
233
|
+
render: (args) => ({
|
|
234
|
+
components: { PBtn },
|
|
235
|
+
setup() {
|
|
236
|
+
return { args, BUTTON_TYPES, BUTTON_SIZES };
|
|
237
|
+
},
|
|
238
|
+
template: `
|
|
239
|
+
<div class="flex gap-4 items-center p-5 bg-white flex-wrap">
|
|
240
|
+
<template v-for="type in BUTTON_TYPES">
|
|
241
|
+
<PBtn v-for="size in BUTTON_SIZES" :key="type + '-' + size" :size="size" :type="type" icon="edit" :data-testid="type + '-' + size" />
|
|
242
|
+
</template>
|
|
243
|
+
</div>
|
|
244
|
+
`,
|
|
245
|
+
}),
|
|
246
|
+
play: async ({ canvasElement }) => {
|
|
247
|
+
const canvas = within(canvasElement);
|
|
248
|
+
|
|
249
|
+
// Wait a bit for styles to be fully applied
|
|
250
|
+
await new Promise((resolve) => setTimeout(resolve, 250));
|
|
251
|
+
|
|
252
|
+
const sizeConfigs = [
|
|
253
|
+
{ size: 'sm', expectedPixels: 32 },
|
|
254
|
+
{ size: 'md', expectedPixels: 40 },
|
|
255
|
+
{ size: 'lg', expectedPixels: 48 },
|
|
256
|
+
];
|
|
257
|
+
|
|
258
|
+
// Test icon button dimensions for all button types and sizes
|
|
259
|
+
for (const { size, expectedPixels } of sizeConfigs) {
|
|
260
|
+
for (const type of BUTTON_TYPES) {
|
|
261
|
+
const button = await canvas.getByTestId(`${type}-${size}`);
|
|
262
|
+
const rect = button.getBoundingClientRect();
|
|
263
|
+
|
|
264
|
+
// Test expected exact pixel dimensions
|
|
265
|
+
await expect(rect.height).toBe(expectedPixels);
|
|
266
|
+
await expect(rect.width).toBe(expectedPixels);
|
|
267
|
+
}
|
|
268
|
+
}
|
|
269
|
+
},
|
|
270
|
+
};
|
|
@@ -32,7 +32,7 @@ type Icon = InstanceType<typeof PIcon>['$props']['icon'];
|
|
|
32
32
|
const btnClasses = {
|
|
33
33
|
slots: {
|
|
34
34
|
button:
|
|
35
|
-
'relative inline-
|
|
35
|
+
'relative inline-flex whitespace-nowrap rounded font-medium outline-none disabled:pointer-events-none disabled:cursor-default disabled:opacity-50 aria-disabled:pointer-events-none aria-disabled:cursor-default aria-disabled:opacity-50',
|
|
36
36
|
content: 'flex items-center justify-center has-[.slot-wrapper:empty]:gap-0',
|
|
37
37
|
loader: 'absolute bottom-0 left-0 right-0 top-0 flex items-center justify-center font-medium',
|
|
38
38
|
icon: 'shrink-0',
|
|
@@ -67,24 +67,32 @@ const btnClasses = {
|
|
|
67
67
|
loader: 'text-p-red-50',
|
|
68
68
|
},
|
|
69
69
|
success: { button: 'bg-p-green-40 text-white hover:bg-p-green-50', loader: 'text-white' },
|
|
70
|
+
black: { button: 'bg-night text-white hover:bg-p-gray-80', loader: 'text-white' },
|
|
71
|
+
'black-outline-gradient': {
|
|
72
|
+
button: 'bg-gradient-to-r from-[#17c0e8] via-[#8e8bda] to-[#f65ece] hover:bg-gradient-to-l p-px group',
|
|
73
|
+
content:
|
|
74
|
+
'bg-night rounded-[calc(theme(borderRadius.DEFAULT)-1px)] text-white group-hover:bg-p-gray-80 transition-colors duration-300',
|
|
75
|
+
icon: '-mx-px',
|
|
76
|
+
loader: 'text-white',
|
|
77
|
+
},
|
|
70
78
|
'primary-link': { button: 'bg-transparent text-primary underline hover:text-accent', loader: 'text-p-blue-60' },
|
|
71
79
|
'secondary-ghost': { button: 'text-on-surface hover:bg-p-gray-20', loader: 'text-p-purple-60' },
|
|
72
80
|
'secondary-ghost-dark': { button: 'text-white hover:bg-p-purple-50', loader: 'text-p-blue-15' },
|
|
73
81
|
},
|
|
74
82
|
size: {
|
|
75
83
|
sm: {
|
|
76
|
-
button: '
|
|
77
|
-
content: 'gap-1',
|
|
84
|
+
button: 'text-sm leading-5 h-8',
|
|
85
|
+
content: 'px-3 has-[.slot-wrapper:empty]:px-1.5 py-1.5 gap-1',
|
|
78
86
|
icon: 'text-base p-0.5',
|
|
79
87
|
},
|
|
80
88
|
md: {
|
|
81
|
-
button: '
|
|
82
|
-
content: 'gap-2',
|
|
89
|
+
button: 'text-base h-10',
|
|
90
|
+
content: 'px-6 has-[.slot-wrapper:empty]:px-2.5 has-[.slot-wrapper:empty]:py-2.5 py-2 gap-2',
|
|
83
91
|
icon: 'text-xl',
|
|
84
92
|
},
|
|
85
93
|
lg: {
|
|
86
|
-
button: '
|
|
87
|
-
content: 'gap-2.5',
|
|
94
|
+
button: 'text-lg leading-6 h-12',
|
|
95
|
+
content: 'px-6 has-[.slot-wrapper:empty]:px-3 py-3 gap-2.5',
|
|
88
96
|
icon: 'text-2xl',
|
|
89
97
|
},
|
|
90
98
|
},
|
|
@@ -1,11 +1,21 @@
|
|
|
1
1
|
import { toNumberOrNull } from '@squirrel/utils/number';
|
|
2
2
|
|
|
3
3
|
describe('toNumberOrNull', () => {
|
|
4
|
-
it.each(['', null, 'abc', NaN, {}])('returns null if input is %s', (val) => {
|
|
4
|
+
it.each(['', null, 'abc', NaN, {}, Infinity, -Infinity])('returns null if input is %s', (val) => {
|
|
5
5
|
expect(toNumberOrNull(val)).toBeNull();
|
|
6
6
|
});
|
|
7
7
|
|
|
8
|
-
it('
|
|
9
|
-
|
|
8
|
+
it.each([123, '123', 0, '0', -42, '-42', 3.14, '3.14'])(
|
|
9
|
+
'returns a number if input is a valid finite number: %s',
|
|
10
|
+
(val) => {
|
|
11
|
+
expect(toNumberOrNull(val)).toBe(Number(val));
|
|
12
|
+
}
|
|
13
|
+
);
|
|
14
|
+
|
|
15
|
+
it('handles edge cases for infinite values', () => {
|
|
16
|
+
expect(toNumberOrNull('Infinity')).toBeNull();
|
|
17
|
+
expect(toNumberOrNull('-Infinity')).toBeNull();
|
|
18
|
+
expect(toNumberOrNull(Number.POSITIVE_INFINITY)).toBeNull();
|
|
19
|
+
expect(toNumberOrNull(Number.NEGATIVE_INFINITY)).toBeNull();
|
|
10
20
|
});
|
|
11
21
|
});
|
package/squirrel/utils/number.ts
CHANGED
|
@@ -7,8 +7,11 @@
|
|
|
7
7
|
* @returns a number or null
|
|
8
8
|
*/
|
|
9
9
|
export const toNumberOrNull = (val: unknown) => {
|
|
10
|
-
if (val === '' || val === null
|
|
10
|
+
if (val === '' || val === null) {
|
|
11
11
|
return null;
|
|
12
12
|
}
|
|
13
|
-
|
|
13
|
+
|
|
14
|
+
const num = Number(val);
|
|
15
|
+
|
|
16
|
+
return isFinite(num) ? num : null;
|
|
14
17
|
};
|