@pequity/squirrel 8.0.1 → 8.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (42) hide show
  1. package/dist/cjs/chunks/p-action-bar.js +6 -9
  2. package/dist/cjs/chunks/p-steps.js +52 -0
  3. package/dist/cjs/index.js +2 -0
  4. package/dist/cjs/p-steps.js +3 -0
  5. package/dist/cjs/usePLoading.js +7 -1
  6. package/dist/es/chunks/p-action-bar.js +6 -9
  7. package/dist/es/chunks/p-steps.js +53 -0
  8. package/dist/es/index.js +8 -6
  9. package/dist/es/p-steps.js +4 -0
  10. package/dist/es/usePLoading.js +7 -1
  11. package/dist/squirrel/components/index.d.ts +2 -1
  12. package/dist/squirrel/components/p-alert/p-alert.vue.d.ts +3 -2
  13. package/dist/squirrel/components/p-btn/p-btn.vue.d.ts +1 -1
  14. package/dist/squirrel/components/p-card/p-card.vue.d.ts +2 -19
  15. package/dist/squirrel/components/p-checkbox/p-checkbox.vue.d.ts +2 -37
  16. package/dist/squirrel/components/p-drawer/p-drawer.vue.d.ts +3 -2
  17. package/dist/squirrel/components/p-info-icon/p-info-icon.vue.d.ts +2 -9
  18. package/dist/squirrel/components/p-inline-date-picker/p-inline-date-picker.vue.d.ts +3 -33
  19. package/dist/squirrel/components/p-input/p-input.vue.d.ts +6 -4
  20. package/dist/squirrel/components/p-input-number/p-input-number.vue.d.ts +3 -107
  21. package/dist/squirrel/components/p-link/p-link.vue.d.ts +3 -11
  22. package/dist/squirrel/components/p-loading/usePLoading.d.ts +1 -0
  23. package/dist/squirrel/components/p-modal/p-modal.vue.d.ts +2 -227
  24. package/dist/squirrel/components/p-pagination-info/p-pagination-info.vue.d.ts +2 -74
  25. package/dist/squirrel/components/p-select/p-select.vue.d.ts +3 -110
  26. package/dist/squirrel/components/p-select-btn/p-select-btn.vue.d.ts +2 -29
  27. package/dist/squirrel/components/p-steps/p-steps.vue.d.ts +17 -0
  28. package/dist/squirrel/components/p-table/p-table.vue.d.ts +10 -5
  29. package/dist/squirrel/components/p-table-header-cell/p-table-header-cell.vue.d.ts +1 -1
  30. package/dist/squirrel/components/p-table-td/p-table-td.vue.d.ts +2 -19
  31. package/dist/squirrel/components/p-textarea/p-textarea.vue.d.ts +3 -72
  32. package/dist/squirrel/components/p-toggle/p-toggle.vue.d.ts +2 -2
  33. package/package.json +14 -14
  34. package/squirrel/components/index.ts +2 -0
  35. package/squirrel/components/p-action-bar/p-action-bar.spec.ts +17 -11
  36. package/squirrel/components/p-action-bar/p-action-bar.vue +6 -6
  37. package/squirrel/components/p-loading/p-loading.spec.js +35 -2
  38. package/squirrel/components/p-loading/usePLoading.ts +10 -1
  39. package/squirrel/components/p-steps/__snapshots__/p-steps.spec.js.snap +16 -0
  40. package/squirrel/components/p-steps/p-steps.spec.js +126 -0
  41. package/squirrel/components/p-steps/p-steps.stories.js +31 -0
  42. package/squirrel/components/p-steps/p-steps.vue +47 -0
@@ -25,25 +25,30 @@ declare const _default: <T extends Record<string, unknown>>(__VLS_props: NonNull
25
25
  [x: `prepend-header-cell-${string}`]: ((props: {
26
26
  col: TableCol;
27
27
  }) => any) | undefined;
28
+ } & {
28
29
  [x: `subheader-cell-${string}`]: ((props: {}) => any) | undefined;
30
+ } & {
29
31
  [x: `cell-${string}`]: ((props: {
30
32
  col: TableCol;
31
33
  rowData: T;
32
34
  rowIndex: number;
33
35
  cellData: unknown;
34
36
  }) => any) | undefined;
35
- default?: ((props: {}) => any) | undefined;
36
- row?: ((props: {
37
+ } & {
38
+ default?: (props: {}) => any;
39
+ } & {
40
+ row?: (props: {
37
41
  rowData: T;
38
42
  rowIndex: number;
39
- }) => any) | undefined;
40
- cell?: ((props: {
43
+ }) => any;
44
+ } & {
45
+ cell?: (props: {
41
46
  col: TableCol;
42
47
  colIndex: number;
43
48
  rowData: T;
44
49
  rowIndex: number;
45
50
  cellData: unknown;
46
- }) => any) | undefined;
51
+ }) => any;
47
52
  };
48
53
  emit: {
49
54
  (e: "scroll", val: Event): void;
@@ -106,7 +106,7 @@ declare const _default: import("vue").DefineComponent<import("vue").ExtractPropT
106
106
  text: string | null;
107
107
  }, {}, string, {}, import("vue").GlobalComponents, import("vue").GlobalDirectives, string, import("vue").ComponentProvideOptions> & import("vue").VNodeProps & import("vue").AllowedComponentProps & import("vue").ComponentCustomProps & (new () => {
108
108
  $slots: {
109
- default?: ((props: {}) => any) | undefined;
109
+ default?: (props: {}) => any;
110
110
  };
111
111
  });
112
112
  }, {}, string, import("vue").ComponentProvideOptions, true, {}, any>;
@@ -1,29 +1,12 @@
1
- import { type Ref } from 'vue';
2
1
  type Props = {
3
2
  colIndex: number;
4
3
  isEditable?: boolean;
5
4
  isSelected?: boolean;
6
5
  };
7
- declare const isLastColFixed: Ref<boolean, boolean>;
8
- declare const isColsResizable: Ref<boolean, boolean>;
9
- declare const isLastCol: import("vue").ComputedRef<boolean>;
10
- declare const tdClass: import("vue").ComputedRef<string[]>;
11
- declare const innerDivClass: import("vue").ComputedRef<string[]>;
12
- declare const __VLS_ctx: InstanceType<__VLS_PickNotAny<typeof __VLS_self, new () => {}>>;
13
6
  declare var __VLS_1: {};
14
- type __VLS_Slots = __VLS_PrettifyGlobal<__VLS_OmitStringIndex<typeof __VLS_ctx.$slots> & {
7
+ type __VLS_Slots = {} & {
15
8
  default?: (props: typeof __VLS_1) => any;
16
- }>;
17
- declare const __VLS_self: import("vue").DefineComponent<Props, {
18
- isLastColFixed: typeof isLastColFixed;
19
- isColsResizable: typeof isColsResizable;
20
- isLastCol: typeof isLastCol;
21
- tdClass: typeof tdClass;
22
- innerDivClass: typeof innerDivClass;
23
- }, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {}, string, import("vue").PublicProps, Readonly<Props> & Readonly<{}>, {
24
- isSelected: boolean;
25
- isEditable: boolean;
26
- }, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>;
9
+ };
27
10
  declare const __VLS_component: import("vue").DefineComponent<Props, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {}, string, import("vue").PublicProps, Readonly<Props> & Readonly<{}>, {
28
11
  isSelected: boolean;
29
12
  isEditable: boolean;
@@ -1,81 +1,12 @@
1
1
  import { type Size } from '../p-btn/p-btn.types';
2
- import { type PropType, type StyleValue } from 'vue';
3
- declare const labelClasses: import("vue").ComputedRef<string>, textareaClasses: import("vue").ComputedRef<string>, errorMsgClasses: import("vue").ComputedRef<string>;
4
- declare const attrs: import("vue").ComputedRef<{
5
- [x: string]: unknown;
6
- }>;
7
- declare const style: import("vue").ComputedRef<StyleValue>;
8
- declare const updateValue: (e: Event) => void;
9
- declare const __VLS_ctx: InstanceType<__VLS_PickNotAny<typeof __VLS_self, new () => {}>>;
2
+ import { type PropType } from 'vue';
10
3
  declare var __VLS_1: {
11
4
  label: string;
12
5
  labelClasses: string;
13
6
  };
14
- type __VLS_Slots = __VLS_PrettifyGlobal<__VLS_OmitStringIndex<typeof __VLS_ctx.$slots> & {
7
+ type __VLS_Slots = {} & {
15
8
  label?: (props: typeof __VLS_1) => any;
16
- }>;
17
- declare const __VLS_self: import("vue").DefineComponent<import("vue").ExtractPropTypes<{
18
- modelValue: {
19
- type: PropType<string | number | null>;
20
- default: string;
21
- };
22
- label: {
23
- type: StringConstructor;
24
- default: string;
25
- };
26
- errorMsg: {
27
- type: StringConstructor;
28
- default: string;
29
- };
30
- required: {
31
- type: BooleanConstructor;
32
- default: boolean;
33
- };
34
- size: {
35
- type: PropType<Size>;
36
- default: string;
37
- validator(value: Size): boolean;
38
- };
39
- }>, {
40
- labelClasses: typeof labelClasses;
41
- textareaClasses: typeof textareaClasses;
42
- errorMsgClasses: typeof errorMsgClasses;
43
- attrs: typeof attrs;
44
- style: typeof style;
45
- updateValue: typeof updateValue;
46
- }, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {
47
- "update:modelValue": (...args: any[]) => void;
48
- }, string, import("vue").PublicProps, Readonly<import("vue").ExtractPropTypes<{
49
- modelValue: {
50
- type: PropType<string | number | null>;
51
- default: string;
52
- };
53
- label: {
54
- type: StringConstructor;
55
- default: string;
56
- };
57
- errorMsg: {
58
- type: StringConstructor;
59
- default: string;
60
- };
61
- required: {
62
- type: BooleanConstructor;
63
- default: boolean;
64
- };
65
- size: {
66
- type: PropType<Size>;
67
- default: string;
68
- validator(value: Size): boolean;
69
- };
70
- }>> & Readonly<{
71
- "onUpdate:modelValue"?: ((...args: any[]) => any) | undefined;
72
- }>, {
73
- size: "sm" | "md" | "lg";
74
- label: string;
75
- required: boolean;
76
- modelValue: string | number | null;
77
- errorMsg: string;
78
- }, {}, {}, {}, string, import("vue").ComponentProvideOptions, true, {}, any>;
9
+ };
79
10
  declare const __VLS_component: import("vue").DefineComponent<import("vue").ExtractPropTypes<{
80
11
  modelValue: {
81
12
  type: PropType<string | number | null>;
@@ -55,10 +55,10 @@ declare const _default: __VLS_WithSlots<import("vue").DefineComponent<import("vu
55
55
  modelValue: boolean;
56
56
  errorMsg: string;
57
57
  }, {}, {}, {}, string, import("vue").ComponentProvideOptions, true, {}, any>, {
58
- label?: ((props: {
58
+ label?: (props: {
59
59
  label: string;
60
60
  labelClasses: string;
61
- }) => any) | undefined;
61
+ }) => any;
62
62
  }>;
63
63
  export default _default;
64
64
  type __VLS_WithSlots<T, S> = T & {
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@pequity/squirrel",
3
3
  "description": "Squirrel component library",
4
- "version": "8.0.1",
4
+ "version": "8.1.0",
5
5
  "packageManager": "pnpm@10.6.4",
6
6
  "type": "module",
7
7
  "scripts": {
@@ -18,7 +18,7 @@
18
18
  "storybook": "storybook dev -p 6006",
19
19
  "build-storybook": "storybook build",
20
20
  "test-storybook": "test-storybook",
21
- "quality": "pnpm run lint && pnpm run typecheck && pnpm run test:unit && pnpm run build",
21
+ "quality": "pnpm run lint && pnpm run typecheck && vitest run && pnpm run build",
22
22
  "prepare": "husky"
23
23
  },
24
24
  "files": [
@@ -50,7 +50,7 @@
50
50
  "@commitlint/cli": "^19.8.0",
51
51
  "@commitlint/config-conventional": "^19.8.0",
52
52
  "@pequity/eslint-config": "^2.0.0",
53
- "@playwright/test": "^1.51.1",
53
+ "@playwright/test": "^1.52.0",
54
54
  "@semantic-release/changelog": "^6.0.3",
55
55
  "@semantic-release/git": "^10.0.1",
56
56
  "@storybook/addon-a11y": "^8.6.12",
@@ -68,21 +68,21 @@
68
68
  "@tanstack/vue-virtual": "3.13.6",
69
69
  "@types/jsdom": "^21.1.7",
70
70
  "@types/lodash-es": "^4.17.12",
71
- "@types/node": "^22.14.0",
71
+ "@types/node": "^22.15.2",
72
72
  "@vitejs/plugin-vue": "^5.2.3",
73
- "@vitest/coverage-v8": "^3.1.1",
73
+ "@vitest/coverage-v8": "^3.1.2",
74
74
  "@vue/compiler-sfc": "3.5.13",
75
75
  "@vue/test-utils": "^2.4.6",
76
76
  "@vuepic/vue-datepicker": "11.0.2",
77
77
  "autoprefixer": "^10.4.21",
78
- "eslint": "^9.24.0",
78
+ "eslint": "^9.25.1",
79
79
  "eslint-plugin-storybook": "^0.12.0",
80
80
  "floating-vue": "5.2.2",
81
- "glob": "^11.0.1",
81
+ "glob": "^11.0.2",
82
82
  "husky": "^9.1.7",
83
83
  "iconify-icon": "^2.3.0",
84
- "jsdom": "^26.0.0",
85
- "lint-staged": "^15.5.0",
84
+ "jsdom": "^26.1.0",
85
+ "lint-staged": "^15.5.1",
86
86
  "lodash-es": "4.17.21",
87
87
  "make-coverage-badge": "^1.2.0",
88
88
  "postcss": "^8.5.3",
@@ -90,19 +90,19 @@
90
90
  "prettier-plugin-tailwindcss": "^0.6.11",
91
91
  "resolve-tspaths": "^0.8.23",
92
92
  "rimraf": "^6.0.1",
93
- "sass": "^1.86.3",
93
+ "sass": "^1.87.0",
94
94
  "semantic-release": "^24.2.3",
95
95
  "storybook": "^8.6.12",
96
96
  "svgo": "^3.3.2",
97
97
  "tailwindcss": "^3.4.17",
98
98
  "typescript": "5.8.3",
99
- "vite": "^6.2.5",
100
- "vitest": "^3.1.1",
99
+ "vite": "^6.3.3",
100
+ "vitest": "^3.1.2",
101
101
  "vue": "3.5.13",
102
102
  "vue-currency-input": "3.2.1",
103
- "vue-router": "4.5.0",
103
+ "vue-router": "4.5.1",
104
104
  "vue-toastification": "2.0.0-rc.5",
105
- "vue-tsc": "2.2.8"
105
+ "vue-tsc": "2.2.10"
106
106
  },
107
107
  "dependencies": {
108
108
  "tailwind-variants": "^1.0.0"
@@ -37,6 +37,7 @@ import PSelectList from '@squirrel/components/p-select-list/p-select-list.vue';
37
37
  import { useSelectList } from '@squirrel/components/p-select-list/useSelectList';
38
38
  import PSelectPill from '@squirrel/components/p-select-pill/p-select-pill.vue';
39
39
  import PSkeletonLoader from '@squirrel/components/p-skeleton-loader/p-skeleton-loader.vue';
40
+ import PSteps from '@squirrel/components/p-steps/p-steps.vue';
40
41
  import {
41
42
  colsInjectionKey,
42
43
  type HeaderCellAttrs,
@@ -108,6 +109,7 @@ export {
108
109
  PSelectList,
109
110
  PSelectPill,
110
111
  PSkeletonLoader,
112
+ PSteps,
111
113
  PTable,
112
114
  PTableHeaderCell,
113
115
  PTableLoader,
@@ -1,5 +1,6 @@
1
1
  import PActionBar from '@squirrel/components/p-action-bar/p-action-bar.vue';
2
2
  import { createWrapperFor } from '@tests/vitest.helpers';
3
+ import { type VueWrapper } from '@vue/test-utils';
3
4
  import { defineComponent } from 'vue';
4
5
 
5
6
  const createPDropdownStub = () => {
@@ -77,7 +78,7 @@ describe('PActionBar.vue', () => {
77
78
  },
78
79
  });
79
80
 
80
- const mainDiv = await wrapper.find('.teleport-stub > .fixed.bottom-6');
81
+ const mainDiv = wrapper.find('.teleport-stub > .fixed.bottom-6');
81
82
  expect(mainDiv.exists()).toBe(true);
82
83
 
83
84
  expect(mainDiv.classes()).toEqual(['fixed', 'bottom-6', 'left-1/2', 'z-[100]', '-translate-x-2/4']);
@@ -97,14 +98,19 @@ describe('PActionBar.vue', () => {
97
98
  'text-white',
98
99
  ]);
99
100
 
100
- const paragraph = await wrapper.find('p');
101
+ const buttons = wrapper.findAllComponents({ name: 'PBtn' });
102
+ buttons.forEach((button: VueWrapper) => {
103
+ expect(button.find('.slot-wrapper > div').classes()).toEqual(['flex', 'items-center', 'gap-2', 'px-1']);
104
+ });
105
+
106
+ const paragraph = wrapper.find('p');
101
107
  expect(paragraph.text()).toContain('Hello World');
102
108
 
103
- const dismissIcon = await wrapper.find('i.x-white-icon');
109
+ const dismissIcon = wrapper.find('i.x-white-icon');
104
110
  expect(dismissIcon.exists()).toBe(true);
105
111
  expect(actionBarDiv.find('.text-xs').text()).toBe('Clear All');
106
112
 
107
- const actionBtn = await wrapper.find('button.inline-block');
113
+ const actionBtn = wrapper.find('button.inline-block');
108
114
  expect(actionBtn.exists()).toBe(true);
109
115
  expect(actionBtn.text()).toContain('Say Hi');
110
116
  });
@@ -125,9 +131,9 @@ describe('PActionBar.vue', () => {
125
131
  },
126
132
  });
127
133
 
128
- const mainDiv = await wrapper.find('.teleport-stub > .fixed.bottom-6');
134
+ const mainDiv = wrapper.find('.teleport-stub > .fixed.bottom-6');
129
135
 
130
- await mainDiv.find('.p-dropdown-stub > button').trigger('click');
136
+ mainDiv.find('.p-dropdown-stub > button').trigger('click');
131
137
 
132
138
  await wrapper.vm.$nextTick();
133
139
 
@@ -155,7 +161,7 @@ describe('PActionBar.vue', () => {
155
161
  },
156
162
  });
157
163
 
158
- const actionBar = await wrapper.find('.teleport-stub > div');
164
+ const actionBar = wrapper.find('.teleport-stub > div');
159
165
 
160
166
  expect(actionBar.attributes('role')).toBe('alertdialog');
161
167
  expect(actionBar.attributes('data-testid')).toBe('TestId');
@@ -168,7 +174,7 @@ describe('PActionBar.vue', () => {
168
174
  props: { show: true, label: 'Hello World', actions: [{ label: 'Say Hi', name: 'greet' }] },
169
175
  });
170
176
 
171
- const button = await wrapper.find('button.inline-flex');
177
+ const button = wrapper.find('button.inline-flex');
172
178
 
173
179
  await button.trigger('click');
174
180
 
@@ -187,15 +193,15 @@ describe('PActionBar.vue', () => {
187
193
  },
188
194
  });
189
195
 
190
- const button = await wrapper.findByText('Say Hi', 'button');
196
+ const button = wrapper.findByText('Say Hi', 'button');
191
197
  await button.trigger('click');
192
198
 
193
199
  expect(wrapper.emitted()['click:action'][0]).toEqual(['greet']);
194
200
 
195
- const actionMenu = await wrapper.findByText('Action menu', 'button');
201
+ const actionMenu = wrapper.findByText('Action menu', 'button');
196
202
  await actionMenu.trigger('click');
197
203
 
198
- const subAction = await wrapper.findByText('Say Bye', 'button');
204
+ const subAction = wrapper.findByText('Say Bye', 'button');
199
205
  await subAction.trigger('click');
200
206
 
201
207
  expect(wrapper.emitted()['click:action'][1]).toEqual(['bye']);
@@ -19,17 +19,17 @@
19
19
  type="secondary-ghost-dark"
20
20
  @click="$emit('click:action', actionOrMenu.name)"
21
21
  >
22
- <div class="flex items-center gap-2 px-1 py-0.5">
22
+ <div class="flex items-center gap-2 px-1">
23
23
  <Component :is="actionOrMenu.icon" v-if="isComponent(actionOrMenu.icon)" class="h-4 w-4" />
24
- <PIcon v-if="isString(actionOrMenu.icon)" :icon="actionOrMenu.icon" width="16px" height="16px" />
24
+ <PIcon v-if="isString(actionOrMenu.icon)" :icon="actionOrMenu.icon" width="16" />
25
25
  <div>{{ actionOrMenu.label }}</div>
26
26
  </div>
27
27
  </PBtn>
28
28
  <PDropdown v-else placement="top" strategy="fixed">
29
29
  <PBtn size="sm" type="secondary-ghost-dark">
30
- <div class="flex items-center gap-2 px-1 py-0.5">
30
+ <div class="flex items-center gap-2 px-1">
31
31
  <Component :is="actionOrMenu.icon" v-if="isComponent(actionOrMenu.icon)" class="h-4 w-4" />
32
- <PIcon v-if="isString(actionOrMenu.icon)" :icon="actionOrMenu.icon" width="16px" height="16px" />
32
+ <PIcon v-if="isString(actionOrMenu.icon)" :icon="actionOrMenu.icon" width="16" />
33
33
  <div>{{ actionOrMenu.label }}</div>
34
34
  </div>
35
35
  </PBtn>
@@ -42,9 +42,9 @@
42
42
  type="secondary-ghost-dark"
43
43
  @click="($emit('click:action', subaction.name), hide())"
44
44
  >
45
- <div class="flex items-center gap-2 px-1 py-0.5">
45
+ <div class="flex items-center gap-2 px-1">
46
46
  <Component :is="subaction.icon" v-if="isComponent(subaction.icon)" class="h-4 w-4" />
47
- <PIcon v-if="isString(subaction.icon)" :icon="subaction.icon" width="16px" height="16px" />
47
+ <PIcon v-if="isString(subaction.icon)" :icon="subaction.icon" width="16" />
48
48
  <div>{{ subaction.label }}</div>
49
49
  </div>
50
50
  </PBtn>
@@ -24,7 +24,7 @@ const createWrapper = (options) => {
24
24
  <button class="request-1-sec-component" @click="fireRequestComponent(1000)"></button>
25
25
  `,
26
26
  setup() {
27
- const { loadingShow, loadingHide, show } = usePLoading(options);
27
+ const { loadingShow, loadingHide, show, isLoading } = usePLoading(options);
28
28
 
29
29
  const fireRequest = async (time) => {
30
30
  const id = `default-${time}`;
@@ -47,7 +47,7 @@ const createWrapper = (options) => {
47
47
  loadingHide(id);
48
48
  };
49
49
 
50
- return { loadingShow, loadingHide, fireRequest, fireRequestText, fireRequestComponent, show };
50
+ return { loadingShow, loadingHide, fireRequest, fireRequestText, fireRequestComponent, show, isLoading };
51
51
  },
52
52
  },
53
53
  {
@@ -90,6 +90,39 @@ describe('PLoading.vue', () => {
90
90
  expect(appWrapper.find('[aria-busy]').exists()).toBe(false);
91
91
  });
92
92
 
93
+ it('correctly reports loading state for a single loader', async () => {
94
+ const wrapper = createWrapper({ delay: 0 });
95
+
96
+ await wrapper.find('.request-1-sec').trigger('click');
97
+
98
+ vi.advanceTimersByTime(500);
99
+ await waitNT(appWrapper.vm);
100
+ expect(wrapper.vm.isLoading('default-1000')).toBe(true);
101
+
102
+ vi.advanceTimersByTime(600);
103
+ await waitNT(appWrapper.vm);
104
+ expect(wrapper.vm.isLoading('default-1000')).toBe(false);
105
+ });
106
+
107
+ it('correctly reports loading state for multiple loaders', async () => {
108
+ const wrapper = createWrapper({ delay: 0 });
109
+
110
+ await wrapper.find('.request-1-sec').trigger('click');
111
+ await wrapper.find('.request-3-secs').trigger('click');
112
+
113
+ vi.advanceTimersByTime(500);
114
+ await waitNT(appWrapper.vm);
115
+ expect(wrapper.vm.isLoading()).toBe(true);
116
+
117
+ vi.advanceTimersByTime(1000);
118
+ await waitNT(appWrapper.vm);
119
+ expect(wrapper.vm.isLoading()).toBe(true);
120
+
121
+ vi.advanceTimersByTime(2000);
122
+ await waitNT(appWrapper.vm);
123
+ expect(wrapper.vm.isLoading()).toBe(false);
124
+ });
125
+
93
126
  it(`doesn't show the loading indicator when loadingHide gets called before the delay time has passed`, async () => {
94
127
  const wrapper = createWrapper({ delay: 1000 });
95
128
 
@@ -22,6 +22,7 @@ type UsePLoading = {
22
22
  props: Ref;
23
23
  loadingShow: (loadingItem: LoadingItemOption) => void;
24
24
  loadingHide: (id?: LoadingItemOption['id']) => void;
25
+ isLoading: (id?: LoadingItemOption['id']) => boolean;
25
26
  };
26
27
 
27
28
  const LOADING_TEXT = 'Loading...';
@@ -86,6 +87,14 @@ export const usePLoading = (options?: Options): UsePLoading => {
86
87
  }
87
88
  };
88
89
 
90
+ const isLoading = (id?: LoadingItemOption['id']) => {
91
+ if (id) {
92
+ return loadingItems.value.some((item) => item.id === id);
93
+ }
94
+
95
+ return loadingItems.value.length > 0;
96
+ };
97
+
89
98
  if (scope) {
90
99
  onScopeDispose(() => {
91
100
  if (loadingItems.value.some((item) => item.clearOnDispose === false)) {
@@ -102,5 +111,5 @@ export const usePLoading = (options?: Options): UsePLoading => {
102
111
  });
103
112
  }
104
113
 
105
- return { show, content, props, loadingShow, loadingHide };
114
+ return { show, content, props, loadingShow, loadingHide, isLoading };
106
115
  };
@@ -0,0 +1,16 @@
1
+ // Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html
2
+
3
+ exports[`PSteps.vue > renders correctly 1`] = `
4
+ "<div class="flex items-center gap-2">
5
+ <div class="text-nowrap rounded-full border px-4 py-1 text-sm font-semibold border border-p-blue-50 text-p-blue-50">First</div>
6
+ <div class="flex items-center">
7
+ <iconify-icon icon="material-symbols:arrow-right-alt-rounded" class="text-p-blue-50"></iconify-icon>
8
+ </div>
9
+ <div class="text-nowrap rounded-full border px-4 py-1 text-sm font-semibold border border-p-blue-50 bg-p-blue-50 text-surface">Second</div>
10
+ <div class="flex items-center">
11
+ <iconify-icon icon="material-symbols:arrow-right-alt-rounded" class="text-p-gray-30"></iconify-icon>
12
+ </div>
13
+ <div class="text-nowrap rounded-full border px-4 py-1 text-sm font-semibold border border-p-gray-30 text-p-gray-30">Third</div>
14
+ <!--v-if-->
15
+ </div>"
16
+ `;
@@ -0,0 +1,126 @@
1
+ import PSteps from '@squirrel/components/p-steps/p-steps.vue';
2
+ import { createWrapperFor } from '@tests/vitest.helpers';
3
+
4
+ describe('PSteps.vue', () => {
5
+ it('renders correctly', () => {
6
+ const wrapper = createWrapperFor(PSteps, {
7
+ props: {
8
+ steps: ['first', 'second', 'third'],
9
+ currentStep: 'second',
10
+ stepTitleMap: {},
11
+ },
12
+ });
13
+ expect(wrapper.html()).toMatchSnapshot();
14
+ });
15
+
16
+ it('renders the correct number of steps', () => {
17
+ const steps = ['first', 'second', 'third'];
18
+ const wrapper = createWrapperFor(PSteps, {
19
+ props: {
20
+ steps,
21
+ currentStep: 'second',
22
+ stepTitleMap: {},
23
+ },
24
+ });
25
+
26
+ const stepElements = wrapper.findAll('.rounded-full.border');
27
+ expect(stepElements.length).toBe(steps.length);
28
+ });
29
+
30
+ it('applies correct classes for current step', () => {
31
+ const wrapper = createWrapperFor(PSteps, {
32
+ props: {
33
+ steps: ['first', 'second', 'third'],
34
+ currentStep: 'second',
35
+ stepTitleMap: {},
36
+ },
37
+ });
38
+
39
+ const stepElements = wrapper.findAll('.rounded-full.border');
40
+
41
+ // First step should be completed (blue text)
42
+ expect(stepElements[0].classes()).toContain('text-nowrap');
43
+ expect(stepElements[0].classes()).toContain('text-p-blue-50');
44
+ expect(stepElements[0].classes()).toContain('border-p-blue-50');
45
+
46
+ // Second step should be current (blue background)
47
+ expect(stepElements[1].classes()).toContain('text-nowrap');
48
+ expect(stepElements[1].classes()).toContain('bg-p-blue-50');
49
+ expect(stepElements[1].classes()).toContain('text-surface');
50
+ expect(stepElements[1].classes()).toContain('border-p-blue-50');
51
+
52
+ // Third step should be upcoming (gray)
53
+ expect(stepElements[2].classes()).toContain('text-nowrap');
54
+ expect(stepElements[2].classes()).toContain('text-p-gray-30');
55
+ expect(stepElements[2].classes()).toContain('border-p-gray-30');
56
+ });
57
+
58
+ it('displays step titles from stepTitleMap when provided', () => {
59
+ const wrapper = createWrapperFor(PSteps, {
60
+ props: {
61
+ steps: ['step1', 'step2', 'step3'],
62
+ currentStep: 'step2',
63
+ stepTitleMap: {
64
+ step1: 'Custom Step 1',
65
+ step2: 'Custom Step 2',
66
+ step3: 'Custom Step 3',
67
+ },
68
+ },
69
+ });
70
+
71
+ const stepElements = wrapper.findAll('.rounded-full.border');
72
+ expect(stepElements[0].text()).toBe('Custom Step 1');
73
+ expect(stepElements[1].text()).toBe('Custom Step 2');
74
+ expect(stepElements[2].text()).toBe('Custom Step 3');
75
+ });
76
+
77
+ it('uses startCase for step titles when stepTitleMap entry is not provided', () => {
78
+ const wrapper = createWrapperFor(PSteps, {
79
+ props: {
80
+ steps: ['firstStep', 'secondStep', 'thirdStep'],
81
+ currentStep: 'secondStep',
82
+ stepTitleMap: {
83
+ secondStep: 'Custom Second',
84
+ },
85
+ },
86
+ });
87
+
88
+ const stepElements = wrapper.findAll('.rounded-full.border');
89
+ expect(stepElements[0].text()).toBe('First Step'); // startCase applied
90
+ expect(stepElements[1].text()).toBe('Custom Second'); // from map
91
+ expect(stepElements[2].text()).toBe('Third Step'); // startCase applied
92
+ });
93
+
94
+ it('renders the correct number of arrows between steps', () => {
95
+ const wrapper = createWrapperFor(PSteps, {
96
+ props: {
97
+ steps: ['first', 'second', 'third', 'fourth'],
98
+ currentStep: 'second',
99
+ stepTitleMap: {},
100
+ },
101
+ });
102
+
103
+ // There should be 3 arrows for 4 steps
104
+ const arrowElements = wrapper.findAll('[icon="material-symbols:arrow-right-alt-rounded"]');
105
+ expect(arrowElements.length).toBe(3);
106
+ });
107
+
108
+ it('applies the correct classes to arrows based on current step', () => {
109
+ const wrapper = createWrapperFor(PSteps, {
110
+ props: {
111
+ steps: ['first', 'second', 'third', 'fourth'],
112
+ currentStep: 'second',
113
+ stepTitleMap: {},
114
+ },
115
+ });
116
+
117
+ const arrowElements = wrapper.findAll('[icon="material-symbols:arrow-right-alt-rounded"]');
118
+
119
+ // Arrow between first and second step should be colored
120
+ expect(arrowElements[0].classes()).toContain('text-p-blue-50');
121
+
122
+ // Arrow after current step should be gray
123
+ expect(arrowElements[1].classes()).toContain('text-p-gray-30');
124
+ expect(arrowElements[2].classes()).toContain('text-p-gray-30');
125
+ });
126
+ });
@@ -0,0 +1,31 @@
1
+ import PSteps from '@squirrel/components/p-steps/p-steps.vue';
2
+
3
+ export default {
4
+ title: 'Components/PSteps',
5
+ component: PSteps,
6
+ tags: ['autodocs'],
7
+ parameters: {
8
+ docs: {
9
+ description: {
10
+ component: 'Steps Component to be used in wizards',
11
+ },
12
+ },
13
+ },
14
+ };
15
+
16
+ export const Default = {
17
+ render: (args) => ({
18
+ components: { PSteps },
19
+ setup() {
20
+ return { args };
21
+ },
22
+ template: `<PSteps v-bind="args" />`,
23
+ }),
24
+ args: {
25
+ steps: ['stepOne', 'stepTwo', 'stepThree', 'stepFour'],
26
+ currentStep: 'stepTwo',
27
+ stepTitleMap: {
28
+ stepTwo: 'Criteria',
29
+ },
30
+ },
31
+ };