@pequity/squirrel 8.3.1 → 8.3.2

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.
@@ -1,21 +1,26 @@
1
1
  <template>
2
2
  <div class="flex items-center gap-2">
3
- <template v-for="(step, idx) in steps" :key="step">
4
- <div class="text-nowrap rounded-full border px-4 py-1 text-sm font-semibold" :class="stepClasses(step, idx)">
5
- {{ stepTitle(step) }}
3
+ <template v-for="(step, i) in steps" :key="step.value">
4
+ <div
5
+ class="text-nowrap rounded-full border px-4 py-1 text-sm font-semibold"
6
+ :class="[stepClasses(step, i), { 'cursor-pointer': clickable && !step.disabled }]"
7
+ @click="emit('click:step', step, i)"
8
+ >
9
+ {{ step.text || startCase(String(step.value)) }}
6
10
  </div>
7
- <div v-if="idx < steps.length - 1" class="flex items-center">
11
+ <div v-if="i < steps.length - 1" class="flex items-center">
8
12
  <PIcon
9
13
  icon="material-symbols:arrow-right-alt-rounded"
10
- :class="[currentStepIndex <= idx ? 'text-p-gray-30' : 'text-p-blue-50']"
14
+ :class="[activeStepIndex <= i ? 'text-p-gray-30' : 'text-p-blue-50']"
11
15
  />
12
16
  </div>
13
17
  </template>
14
18
  </div>
15
19
  </template>
16
20
 
17
- <script setup lang="ts" generic="T extends readonly string[]">
21
+ <script setup lang="ts">
18
22
  import PIcon from '@squirrel/components/p-icon/p-icon.vue';
23
+ import type { StepItem } from '@squirrel/components/p-steps/p-steps.types';
19
24
  import { startCase } from 'lodash-es';
20
25
  import { computed } from 'vue';
21
26
 
@@ -24,28 +29,32 @@ defineOptions({
24
29
  });
25
30
 
26
31
  type Props = {
27
- steps: T;
28
- currentStep: T[number];
29
- stepTitleMap?: Partial<Record<T[number], string>>;
32
+ activeStep?: StepItem['value'];
33
+ steps?: readonly StepItem[];
34
+ clickable?: boolean;
30
35
  };
31
36
 
32
- const props = defineProps<Props>();
37
+ const props = withDefaults(defineProps<Props>(), {
38
+ activeStep: null,
39
+ steps: () => [],
40
+ clickable: false,
41
+ });
33
42
 
34
- const currentStepIndex = computed(() => props.steps.findIndex((s) => s === props.currentStep));
43
+ const emit = defineEmits<{
44
+ 'click:step': [step: StepItem, index: number];
45
+ }>();
35
46
 
36
- const stepClasses = (step: T[number], stepIndex: number) => {
37
- if (step === props.currentStep) {
38
- return 'border border-p-blue-50 bg-p-blue-50 text-surface';
39
- }
47
+ const activeStepIndex = computed(() => props.steps.findIndex((s) => s.value === props.activeStep));
40
48
 
41
- if (currentStepIndex.value < stepIndex) {
42
- return 'border border-p-gray-30 text-p-gray-30';
49
+ const stepClasses = (step: StepItem, stepIndex: number) => {
50
+ if (step.value === props.activeStep) {
51
+ return 'border-p-blue-50 bg-p-blue-50 text-surface';
43
52
  }
44
53
 
45
- return 'border border-p-blue-50 text-p-blue-50';
46
- };
54
+ if (activeStepIndex.value < stepIndex) {
55
+ return 'border-p-gray-30 text-p-gray-30';
56
+ }
47
57
 
48
- const stepTitle = (step: T[number]) => {
49
- return props.stepTitleMap?.[step] || startCase(step);
58
+ return 'border-p-blue-50 text-p-blue-50';
50
59
  };
51
60
  </script>
@@ -1,55 +0,0 @@
1
- "use strict";
2
- const vue = require("vue");
3
- const pIcon_vue_vue_type_script_setup_true_lang = require("./p-icon.js");
4
- const lodashEs = require("lodash-es");
5
- const _hoisted_1 = { class: "flex items-center gap-2" };
6
- const _hoisted_2 = {
7
- key: 0,
8
- class: "flex items-center"
9
- };
10
- const _sfc_main = /* @__PURE__ */ vue.defineComponent({
11
- ...{
12
- name: "PSteps"
13
- },
14
- __name: "p-steps",
15
- props: {
16
- steps: {},
17
- currentStep: {},
18
- stepTitleMap: {}
19
- },
20
- setup(__props) {
21
- const props = __props;
22
- const currentStepIndex = vue.computed(() => props.steps.findIndex((s) => s === props.currentStep));
23
- const stepClasses = (step, stepIndex) => {
24
- if (step === props.currentStep) {
25
- return "border border-p-blue-50 bg-p-blue-50 text-surface";
26
- }
27
- if (currentStepIndex.value < stepIndex) {
28
- return "border border-p-gray-30 text-p-gray-30";
29
- }
30
- return "border border-p-blue-50 text-p-blue-50";
31
- };
32
- const stepTitle = (step) => {
33
- var _a;
34
- return ((_a = props.stepTitleMap) == null ? void 0 : _a[step]) || lodashEs.startCase(step);
35
- };
36
- return (_ctx, _cache) => {
37
- return vue.openBlock(), vue.createElementBlock("div", _hoisted_1, [
38
- (vue.openBlock(true), vue.createElementBlock(vue.Fragment, null, vue.renderList(_ctx.steps, (step, idx) => {
39
- return vue.openBlock(), vue.createElementBlock(vue.Fragment, { key: step }, [
40
- vue.createElementVNode("div", {
41
- class: vue.normalizeClass(["text-nowrap rounded-full border px-4 py-1 text-sm font-semibold", stepClasses(step, idx)])
42
- }, vue.toDisplayString(stepTitle(step)), 3),
43
- idx < _ctx.steps.length - 1 ? (vue.openBlock(), vue.createElementBlock("div", _hoisted_2, [
44
- vue.createVNode(pIcon_vue_vue_type_script_setup_true_lang._sfc_main, {
45
- icon: "material-symbols:arrow-right-alt-rounded",
46
- class: vue.normalizeClass([currentStepIndex.value <= idx ? "text-p-gray-30" : "text-p-blue-50"])
47
- }, null, 8, ["class"])
48
- ])) : vue.createCommentVNode("", true)
49
- ], 64);
50
- }), 128))
51
- ]);
52
- };
53
- }
54
- });
55
- exports._sfc_main = _sfc_main;
@@ -1,56 +0,0 @@
1
- import { defineComponent, computed, createElementBlock, openBlock, Fragment, renderList, createElementVNode, createCommentVNode, normalizeClass, toDisplayString, createVNode } from "vue";
2
- import { _ as _sfc_main$1 } from "./p-icon.js";
3
- import { startCase } from "lodash-es";
4
- const _hoisted_1 = { class: "flex items-center gap-2" };
5
- const _hoisted_2 = {
6
- key: 0,
7
- class: "flex items-center"
8
- };
9
- const _sfc_main = /* @__PURE__ */ defineComponent({
10
- ...{
11
- name: "PSteps"
12
- },
13
- __name: "p-steps",
14
- props: {
15
- steps: {},
16
- currentStep: {},
17
- stepTitleMap: {}
18
- },
19
- setup(__props) {
20
- const props = __props;
21
- const currentStepIndex = computed(() => props.steps.findIndex((s) => s === props.currentStep));
22
- const stepClasses = (step, stepIndex) => {
23
- if (step === props.currentStep) {
24
- return "border border-p-blue-50 bg-p-blue-50 text-surface";
25
- }
26
- if (currentStepIndex.value < stepIndex) {
27
- return "border border-p-gray-30 text-p-gray-30";
28
- }
29
- return "border border-p-blue-50 text-p-blue-50";
30
- };
31
- const stepTitle = (step) => {
32
- var _a;
33
- return ((_a = props.stepTitleMap) == null ? void 0 : _a[step]) || startCase(step);
34
- };
35
- return (_ctx, _cache) => {
36
- return openBlock(), createElementBlock("div", _hoisted_1, [
37
- (openBlock(true), createElementBlock(Fragment, null, renderList(_ctx.steps, (step, idx) => {
38
- return openBlock(), createElementBlock(Fragment, { key: step }, [
39
- createElementVNode("div", {
40
- class: normalizeClass(["text-nowrap rounded-full border px-4 py-1 text-sm font-semibold", stepClasses(step, idx)])
41
- }, toDisplayString(stepTitle(step)), 3),
42
- idx < _ctx.steps.length - 1 ? (openBlock(), createElementBlock("div", _hoisted_2, [
43
- createVNode(_sfc_main$1, {
44
- icon: "material-symbols:arrow-right-alt-rounded",
45
- class: normalizeClass([currentStepIndex.value <= idx ? "text-p-gray-30" : "text-p-blue-50"])
46
- }, null, 8, ["class"])
47
- ])) : createCommentVNode("", true)
48
- ], 64);
49
- }), 128))
50
- ]);
51
- };
52
- }
53
- });
54
- export {
55
- _sfc_main as _
56
- };
@@ -1,126 +0,0 @@
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
- });