@pequity/squirrel 8.3.5 → 8.4.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cjs/chunks/p-dropdown-select.js +1 -4
- package/dist/cjs/chunks/p-tabs-pills.js +44 -0
- package/dist/cjs/index.js +2 -0
- package/dist/cjs/p-tabs-pills.js +3 -0
- package/dist/es/chunks/p-dropdown-select.js +1 -4
- package/dist/es/chunks/p-tabs-pills.js +45 -0
- package/dist/es/index.js +4 -2
- package/dist/es/p-tabs-pills.js +4 -0
- package/dist/squirrel/components/index.d.ts +2 -1
- package/dist/squirrel/components/p-tabs-pills/p-tabs-pills.vue.d.ts +21 -0
- package/package.json +26 -32
- package/squirrel/components/index.ts +2 -0
- package/squirrel/components/p-action-bar/p-action-bar.stories.js +2 -2
- package/squirrel/components/p-alert/p-alert.stories.js +1 -1
- package/squirrel/components/p-btn/p-btn.stories.js +2 -2
- package/squirrel/components/p-checkbox/p-checkbox.stories.js +1 -1
- package/squirrel/components/p-close-btn/p-close-btn.stories.js +2 -2
- package/squirrel/components/p-dropdown-select/p-dropdown-select.spec.js +12 -25
- package/squirrel/components/p-dropdown-select/p-dropdown-select.vue +1 -5
- package/squirrel/components/p-file-upload/p-file-upload.spec.js +1 -1
- package/squirrel/components/p-input-number/p-input-number.spec.js +1 -1
- package/squirrel/components/p-ring-loader/p-ring-loader.spec.js +1 -1
- package/squirrel/components/p-select-btn/p-select-btn.stories.js +1 -1
- package/squirrel/components/p-select-pill/p-select-pill.stories.js +1 -1
- package/squirrel/components/p-table-header-cell/p-table-header-cell.stories.js +1 -1
- package/squirrel/components/p-tabs/p-tabs.stories.js +1 -1
- package/squirrel/components/p-tabs-pills/p-tabs-pills.spec.js +117 -0
- package/squirrel/components/p-tabs-pills/p-tabs-pills.stories.js +139 -0
- package/squirrel/components/p-tabs-pills/p-tabs-pills.vue +60 -0
- package/squirrel/components/p-toggle/p-toggle.spec.js +1 -1
- package/squirrel/composables/useInputClasses.spec.js +1 -1
|
@@ -260,9 +260,6 @@ const _sfc_main = /* @__PURE__ */ vue.defineComponent({
|
|
|
260
260
|
return rest;
|
|
261
261
|
});
|
|
262
262
|
const style = vue.computed(() => $attrs.style);
|
|
263
|
-
const selectableItemsCount = vue.computed(
|
|
264
|
-
() => internalItems.value.filter((item) => !isDisabled(item) || isSelected(item[props.itemValue])).length
|
|
265
|
-
);
|
|
266
263
|
vue.watch(
|
|
267
264
|
dropdownShow,
|
|
268
265
|
(nV) => {
|
|
@@ -502,7 +499,7 @@ const _sfc_main = /* @__PURE__ */ vue.defineComponent({
|
|
|
502
499
|
], 8, _hoisted_5)
|
|
503
500
|
], 2);
|
|
504
501
|
}), 128))
|
|
505
|
-
])) : (vue.openBlock(), vue.createElementBlock("div", _hoisted_6, vue.toDisplayString(__props.multiple ? vue.unref(selectedItems).length ===
|
|
502
|
+
])) : (vue.openBlock(), vue.createElementBlock("div", _hoisted_6, vue.toDisplayString(__props.multiple ? vue.unref(selectedItems).length === vue.unref(computedItems).length ? "All options selected" : `${vue.unref(selectedItems).length} option${vue.unref(selectedItems).length > 1 ? "s" : ""} selected` : vue.unref(selectedItems)[0][__props.itemText]), 1))
|
|
506
503
|
]),
|
|
507
504
|
__props.clearable && vue.unref(selectedItems).length ? (vue.openBlock(), vue.createElementBlock("button", {
|
|
508
505
|
key: 2,
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
const vue = require("vue");
|
|
3
|
+
const _hoisted_1 = {
|
|
4
|
+
class: "flex h-6 w-fit flex-row space-x-1 overflow-x-auto rounded bg-p-gray-10 p-1 text-sm font-medium text-p-gray-50",
|
|
5
|
+
"aria-label": "Tabs Pills",
|
|
6
|
+
role: "tablist",
|
|
7
|
+
"aria-orientation": "horizontal"
|
|
8
|
+
};
|
|
9
|
+
const _hoisted_2 = ["disabled", "aria-selected", "data-tab", "aria-controls", "onClick"];
|
|
10
|
+
const _sfc_main = /* @__PURE__ */ vue.defineComponent({
|
|
11
|
+
...{
|
|
12
|
+
name: "PTabsPills"
|
|
13
|
+
},
|
|
14
|
+
__name: "p-tabs-pills",
|
|
15
|
+
props: {
|
|
16
|
+
modelValue: { type: [String, Number, Boolean], default: "" },
|
|
17
|
+
items: { default: () => [] },
|
|
18
|
+
itemText: { default: "text" },
|
|
19
|
+
itemValue: { default: "value" }
|
|
20
|
+
},
|
|
21
|
+
emits: ["update:modelValue"],
|
|
22
|
+
setup(__props) {
|
|
23
|
+
return (_ctx, _cache) => {
|
|
24
|
+
return vue.openBlock(), vue.createElementBlock("nav", _hoisted_1, [
|
|
25
|
+
(vue.openBlock(true), vue.createElementBlock(vue.Fragment, null, vue.renderList(_ctx.items, (tab) => {
|
|
26
|
+
return vue.openBlock(), vue.createElementBlock("button", {
|
|
27
|
+
key: String(tab[_ctx.itemValue]),
|
|
28
|
+
type: "button",
|
|
29
|
+
class: vue.normalizeClass(["rounded px-2 pb-px leading-none transition-all duration-300", [
|
|
30
|
+
tab.disabled ? "text-p-gray-30" : tab[_ctx.itemValue] === _ctx.modelValue ? "bg-night text-surface hover:bg-night hover:text-surface" : "hover:text-p-gray-70"
|
|
31
|
+
]]),
|
|
32
|
+
disabled: !!tab.disabled,
|
|
33
|
+
role: "tab",
|
|
34
|
+
"aria-selected": tab[_ctx.itemValue] === _ctx.modelValue,
|
|
35
|
+
"data-tab": `#${tab[_ctx.itemValue]}`,
|
|
36
|
+
"aria-controls": String(tab[_ctx.itemValue]),
|
|
37
|
+
onClick: ($event) => _ctx.$emit("update:modelValue", tab[_ctx.itemValue])
|
|
38
|
+
}, vue.toDisplayString(tab[_ctx.itemText]), 11, _hoisted_2);
|
|
39
|
+
}), 128))
|
|
40
|
+
]);
|
|
41
|
+
};
|
|
42
|
+
}
|
|
43
|
+
});
|
|
44
|
+
exports._sfc_main = _sfc_main;
|
package/dist/cjs/index.js
CHANGED
|
@@ -52,6 +52,7 @@ const pTableFilterIcon = require("./p-table-filter-icon.js");
|
|
|
52
52
|
const pTableLoader_vue_vue_type_script_setup_true_lang = require("./chunks/p-table-loader.js");
|
|
53
53
|
const pTableSort = require("./p-table-sort.js");
|
|
54
54
|
const pTabs_vue_vue_type_script_setup_true_lang = require("./chunks/p-tabs.js");
|
|
55
|
+
const pTabsPills_vue_vue_type_script_setup_true_lang = require("./chunks/p-tabs-pills.js");
|
|
55
56
|
const pTextarea_vue_vue_type_script_setup_true_lang = require("./chunks/p-textarea.js");
|
|
56
57
|
const pToggle = require("./p-toggle.js");
|
|
57
58
|
const config = require("./config.js");
|
|
@@ -1086,6 +1087,7 @@ exports.PFilterIcon = pTableFilterIcon;
|
|
|
1086
1087
|
exports.PTableLoader = pTableLoader_vue_vue_type_script_setup_true_lang._sfc_main;
|
|
1087
1088
|
exports.SORTING_TYPES = pTableSort.SORTING_TYPES;
|
|
1088
1089
|
exports.PTabs = pTabs_vue_vue_type_script_setup_true_lang._sfc_main;
|
|
1090
|
+
exports.PTabsPills = pTabsPills_vue_vue_type_script_setup_true_lang._sfc_main;
|
|
1089
1091
|
exports.PTextarea = pTextarea_vue_vue_type_script_setup_true_lang._sfc_main;
|
|
1090
1092
|
exports.PToggle = pToggle;
|
|
1091
1093
|
exports.squirrelTailwindConfig = config.squirrelTailwindConfig;
|
|
@@ -259,9 +259,6 @@ const _sfc_main = /* @__PURE__ */ defineComponent({
|
|
|
259
259
|
return rest;
|
|
260
260
|
});
|
|
261
261
|
const style = computed(() => $attrs.style);
|
|
262
|
-
const selectableItemsCount = computed(
|
|
263
|
-
() => internalItems.value.filter((item) => !isDisabled(item) || isSelected(item[props.itemValue])).length
|
|
264
|
-
);
|
|
265
262
|
watch(
|
|
266
263
|
dropdownShow,
|
|
267
264
|
(nV) => {
|
|
@@ -501,7 +498,7 @@ const _sfc_main = /* @__PURE__ */ defineComponent({
|
|
|
501
498
|
], 8, _hoisted_5)
|
|
502
499
|
], 2);
|
|
503
500
|
}), 128))
|
|
504
|
-
])) : (openBlock(), createElementBlock("div", _hoisted_6, toDisplayString(__props.multiple ? unref(selectedItems).length ===
|
|
501
|
+
])) : (openBlock(), createElementBlock("div", _hoisted_6, toDisplayString(__props.multiple ? unref(selectedItems).length === unref(computedItems).length ? "All options selected" : `${unref(selectedItems).length} option${unref(selectedItems).length > 1 ? "s" : ""} selected` : unref(selectedItems)[0][__props.itemText]), 1))
|
|
505
502
|
]),
|
|
506
503
|
__props.clearable && unref(selectedItems).length ? (openBlock(), createElementBlock("button", {
|
|
507
504
|
key: 2,
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import { defineComponent, createElementBlock, openBlock, Fragment, renderList, normalizeClass, toDisplayString } from "vue";
|
|
2
|
+
const _hoisted_1 = {
|
|
3
|
+
class: "flex h-6 w-fit flex-row space-x-1 overflow-x-auto rounded bg-p-gray-10 p-1 text-sm font-medium text-p-gray-50",
|
|
4
|
+
"aria-label": "Tabs Pills",
|
|
5
|
+
role: "tablist",
|
|
6
|
+
"aria-orientation": "horizontal"
|
|
7
|
+
};
|
|
8
|
+
const _hoisted_2 = ["disabled", "aria-selected", "data-tab", "aria-controls", "onClick"];
|
|
9
|
+
const _sfc_main = /* @__PURE__ */ defineComponent({
|
|
10
|
+
...{
|
|
11
|
+
name: "PTabsPills"
|
|
12
|
+
},
|
|
13
|
+
__name: "p-tabs-pills",
|
|
14
|
+
props: {
|
|
15
|
+
modelValue: { type: [String, Number, Boolean], default: "" },
|
|
16
|
+
items: { default: () => [] },
|
|
17
|
+
itemText: { default: "text" },
|
|
18
|
+
itemValue: { default: "value" }
|
|
19
|
+
},
|
|
20
|
+
emits: ["update:modelValue"],
|
|
21
|
+
setup(__props) {
|
|
22
|
+
return (_ctx, _cache) => {
|
|
23
|
+
return openBlock(), createElementBlock("nav", _hoisted_1, [
|
|
24
|
+
(openBlock(true), createElementBlock(Fragment, null, renderList(_ctx.items, (tab) => {
|
|
25
|
+
return openBlock(), createElementBlock("button", {
|
|
26
|
+
key: String(tab[_ctx.itemValue]),
|
|
27
|
+
type: "button",
|
|
28
|
+
class: normalizeClass(["rounded px-2 pb-px leading-none transition-all duration-300", [
|
|
29
|
+
tab.disabled ? "text-p-gray-30" : tab[_ctx.itemValue] === _ctx.modelValue ? "bg-night text-surface hover:bg-night hover:text-surface" : "hover:text-p-gray-70"
|
|
30
|
+
]]),
|
|
31
|
+
disabled: !!tab.disabled,
|
|
32
|
+
role: "tab",
|
|
33
|
+
"aria-selected": tab[_ctx.itemValue] === _ctx.modelValue,
|
|
34
|
+
"data-tab": `#${tab[_ctx.itemValue]}`,
|
|
35
|
+
"aria-controls": String(tab[_ctx.itemValue]),
|
|
36
|
+
onClick: ($event) => _ctx.$emit("update:modelValue", tab[_ctx.itemValue])
|
|
37
|
+
}, toDisplayString(tab[_ctx.itemText]), 11, _hoisted_2);
|
|
38
|
+
}), 128))
|
|
39
|
+
]);
|
|
40
|
+
};
|
|
41
|
+
}
|
|
42
|
+
});
|
|
43
|
+
export {
|
|
44
|
+
_sfc_main as _
|
|
45
|
+
};
|
package/dist/es/index.js
CHANGED
|
@@ -52,7 +52,8 @@ import { default as default11 } from "./p-table-filter-icon.js";
|
|
|
52
52
|
import { _ as _20 } from "./chunks/p-table-loader.js";
|
|
53
53
|
import { SORTING_TYPES } from "./p-table-sort.js";
|
|
54
54
|
import { _ as _21 } from "./chunks/p-tabs.js";
|
|
55
|
-
import { _ as _22 } from "./chunks/p-
|
|
55
|
+
import { _ as _22 } from "./chunks/p-tabs-pills.js";
|
|
56
|
+
import { _ as _23 } from "./chunks/p-textarea.js";
|
|
56
57
|
import { default as default12 } from "./p-toggle.js";
|
|
57
58
|
import { squirrelTailwindConfig } from "./config.js";
|
|
58
59
|
import { S } from "./chunks/p-btn.types.js";
|
|
@@ -1079,7 +1080,8 @@ export {
|
|
|
1079
1080
|
_sfc_main as PTableSort,
|
|
1080
1081
|
PTableTd,
|
|
1081
1082
|
_21 as PTabs,
|
|
1082
|
-
_22 as
|
|
1083
|
+
_22 as PTabsPills,
|
|
1084
|
+
_23 as PTextarea,
|
|
1083
1085
|
default12 as PToggle,
|
|
1084
1086
|
P_ICON_ALIASES,
|
|
1085
1087
|
S as SIZES,
|
|
@@ -50,6 +50,7 @@ import { SORTING_TYPES, type SortingType, type SortingTypeWithoutNoSorting } fro
|
|
|
50
50
|
import PTableSort from './p-table-sort/p-table-sort.vue';
|
|
51
51
|
import PTableTd from './p-table-td/p-table-td.vue';
|
|
52
52
|
import PTabs from './p-tabs/p-tabs.vue';
|
|
53
|
+
import PTabsPills from './p-tabs-pills/p-tabs-pills.vue';
|
|
53
54
|
import PTextarea from './p-textarea/p-textarea.vue';
|
|
54
55
|
import PToggle from './p-toggle/p-toggle.vue';
|
|
55
|
-
export { colsInjectionKey, FileUploadFile, HeaderCellAttrs, isColsResizableInjectionKey, isFirstColFixedInjectionKey, isLastColFixedInjectionKey, MIN_WIDTH_COL_RESIZE, P_ICON_ALIASES, PActionBar, PActionBarAction, PAlert, PAvatar, PBtn, PCard, PCheckbox, PChips, PCloseBtn, PDatePicker, PDrawer, PDropdown, PDropdownSelect, PFileUpload, PFilterIcon, PIcon, PIconAlias, PInfoIcon, PInlineDatePicker, PInput, PInputNumber, PInputPercent, PInputSearch, PLink, PLoading, PModal, PPagination, PPaginationInfo, PProgressBar, PRingLoader, PSelect, PSelectBtn, PSelectList, PSelectPill, PSkeletonLoader, PSteps, PTable, PTableHeaderCell, PTableLoader, PTableSort, PTableTd, PTabs, PTextarea, PToggle, Size, SORTING_TYPES, SortingType, SortingTypeWithoutNoSorting, StepItem, TableCol, ThAttrs, usePLoading, usePModal, usePTableColResize, usePTableRowVirtualizer, useSelectList, };
|
|
56
|
+
export { colsInjectionKey, FileUploadFile, HeaderCellAttrs, isColsResizableInjectionKey, isFirstColFixedInjectionKey, isLastColFixedInjectionKey, MIN_WIDTH_COL_RESIZE, P_ICON_ALIASES, PActionBar, PActionBarAction, PAlert, PAvatar, PBtn, PCard, PCheckbox, PChips, PCloseBtn, PDatePicker, PDrawer, PDropdown, PDropdownSelect, PFileUpload, PFilterIcon, PIcon, PIconAlias, PInfoIcon, PInlineDatePicker, PInput, PInputNumber, PInputPercent, PInputSearch, PLink, PLoading, PModal, PPagination, PPaginationInfo, PProgressBar, PRingLoader, PSelect, PSelectBtn, PSelectList, PSelectPill, PSkeletonLoader, PSteps, PTable, PTableHeaderCell, PTableLoader, PTableSort, PTableTd, PTabs, PTabsPills, PTextarea, PToggle, Size, SORTING_TYPES, SortingType, SortingTypeWithoutNoSorting, StepItem, TableCol, ThAttrs, usePLoading, usePModal, usePTableColResize, usePTableRowVirtualizer, useSelectList, };
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
type Value = string | number | boolean;
|
|
2
|
+
type Option = {
|
|
3
|
+
[key: string]: Value;
|
|
4
|
+
};
|
|
5
|
+
type Props = {
|
|
6
|
+
modelValue?: Value;
|
|
7
|
+
items?: readonly Option[];
|
|
8
|
+
itemText?: string;
|
|
9
|
+
itemValue?: string;
|
|
10
|
+
};
|
|
11
|
+
declare const _default: import("vue").DefineComponent<Props, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {
|
|
12
|
+
"update:modelValue": (val: Value) => any;
|
|
13
|
+
}, string, import("vue").PublicProps, Readonly<Props> & Readonly<{
|
|
14
|
+
"onUpdate:modelValue"?: ((val: Value) => any) | undefined;
|
|
15
|
+
}>, {
|
|
16
|
+
modelValue: Value;
|
|
17
|
+
items: readonly Option[];
|
|
18
|
+
itemText: string;
|
|
19
|
+
itemValue: string;
|
|
20
|
+
}, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>;
|
|
21
|
+
export default _default;
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@pequity/squirrel",
|
|
3
3
|
"description": "Squirrel component library",
|
|
4
|
-
"version": "8.
|
|
4
|
+
"version": "8.4.1",
|
|
5
5
|
"packageManager": "pnpm@10.6.4",
|
|
6
6
|
"type": "module",
|
|
7
7
|
"scripts": {
|
|
@@ -49,56 +49,50 @@
|
|
|
49
49
|
"devDependencies": {
|
|
50
50
|
"@commitlint/cli": "^19.8.1",
|
|
51
51
|
"@commitlint/config-conventional": "^19.8.1",
|
|
52
|
-
"@pequity/eslint-config": "^2.0.
|
|
53
|
-
"@playwright/test": "^1.
|
|
52
|
+
"@pequity/eslint-config": "^2.0.2",
|
|
53
|
+
"@playwright/test": "^1.53.1",
|
|
54
54
|
"@semantic-release/changelog": "^6.0.3",
|
|
55
55
|
"@semantic-release/git": "^10.0.1",
|
|
56
|
-
"@storybook/addon-a11y": "^
|
|
57
|
-
"@storybook/addon-
|
|
58
|
-
"@storybook/addon-
|
|
59
|
-
"@storybook/
|
|
60
|
-
"@storybook/
|
|
61
|
-
"@storybook/
|
|
62
|
-
"@
|
|
63
|
-
"@storybook/test": "^8.6.14",
|
|
64
|
-
"@storybook/test-runner": "^0.22.0",
|
|
65
|
-
"@storybook/theming": "^8.6.14",
|
|
66
|
-
"@storybook/vue3": "^8.6.14",
|
|
67
|
-
"@storybook/vue3-vite": "^8.6.14",
|
|
68
|
-
"@tanstack/vue-virtual": "3.13.9",
|
|
56
|
+
"@storybook/addon-a11y": "^9.0.14",
|
|
57
|
+
"@storybook/addon-docs": "^9.0.14",
|
|
58
|
+
"@storybook/addon-links": "^9.0.14",
|
|
59
|
+
"@storybook/test-runner": "^0.23.0",
|
|
60
|
+
"@storybook/vue3": "^9.0.14",
|
|
61
|
+
"@storybook/vue3-vite": "^9.0.14",
|
|
62
|
+
"@tanstack/vue-virtual": "3.13.12",
|
|
69
63
|
"@types/jsdom": "^21.1.7",
|
|
70
64
|
"@types/lodash-es": "^4.17.12",
|
|
71
|
-
"@types/node": "^
|
|
65
|
+
"@types/node": "^24.0.7",
|
|
72
66
|
"@vitejs/plugin-vue": "^5.2.4",
|
|
73
|
-
"@vitest/coverage-v8": "^3.
|
|
74
|
-
"@vue/compiler-sfc": "3.5.
|
|
67
|
+
"@vitest/coverage-v8": "^3.2.4",
|
|
68
|
+
"@vue/compiler-sfc": "3.5.17",
|
|
75
69
|
"@vue/test-utils": "^2.4.6",
|
|
76
70
|
"@vuepic/vue-datepicker": "11.0.2",
|
|
77
71
|
"autoprefixer": "^10.4.21",
|
|
78
|
-
"eslint": "^9.
|
|
79
|
-
"eslint-plugin-storybook": "^0.
|
|
72
|
+
"eslint": "^9.30.0",
|
|
73
|
+
"eslint-plugin-storybook": "^9.0.14",
|
|
80
74
|
"floating-vue": "5.2.2",
|
|
81
|
-
"glob": "^11.0.
|
|
75
|
+
"glob": "^11.0.3",
|
|
82
76
|
"husky": "^9.1.7",
|
|
83
77
|
"iconify-icon": "^3.0.0",
|
|
84
78
|
"jsdom": "^26.1.0",
|
|
85
|
-
"lint-staged": "^16.
|
|
79
|
+
"lint-staged": "^16.1.2",
|
|
86
80
|
"lodash-es": "4.17.21",
|
|
87
81
|
"make-coverage-badge": "^1.2.0",
|
|
88
|
-
"postcss": "^8.5.
|
|
89
|
-
"prettier": "^3.
|
|
90
|
-
"prettier-plugin-tailwindcss": "^0.6.
|
|
82
|
+
"postcss": "^8.5.6",
|
|
83
|
+
"prettier": "^3.6.2",
|
|
84
|
+
"prettier-plugin-tailwindcss": "^0.6.13",
|
|
91
85
|
"resolve-tspaths": "^0.8.23",
|
|
92
86
|
"rimraf": "^6.0.1",
|
|
93
|
-
"sass": "^1.89.
|
|
94
|
-
"semantic-release": "^24.2.
|
|
95
|
-
"storybook": "^
|
|
96
|
-
"svgo": "^
|
|
87
|
+
"sass": "^1.89.2",
|
|
88
|
+
"semantic-release": "^24.2.6",
|
|
89
|
+
"storybook": "^9.0.14",
|
|
90
|
+
"svgo": "^4.0.0",
|
|
97
91
|
"tailwindcss": "^3.4.17",
|
|
98
92
|
"typescript": "5.8.3",
|
|
99
93
|
"vite": "^6.3.5",
|
|
100
|
-
"vitest": "^3.
|
|
101
|
-
"vue": "3.5.
|
|
94
|
+
"vitest": "^3.2.4",
|
|
95
|
+
"vue": "3.5.17",
|
|
102
96
|
"vue-currency-input": "3.2.1",
|
|
103
97
|
"vue-router": "4.5.1",
|
|
104
98
|
"vue-toastification": "2.0.0-rc.5",
|
|
@@ -63,6 +63,7 @@ import {
|
|
|
63
63
|
import PTableSort from '@squirrel/components/p-table-sort/p-table-sort.vue';
|
|
64
64
|
import PTableTd from '@squirrel/components/p-table-td/p-table-td.vue';
|
|
65
65
|
import PTabs from '@squirrel/components/p-tabs/p-tabs.vue';
|
|
66
|
+
import PTabsPills from '@squirrel/components/p-tabs-pills/p-tabs-pills.vue';
|
|
66
67
|
import PTextarea from '@squirrel/components/p-textarea/p-textarea.vue';
|
|
67
68
|
import PToggle from '@squirrel/components/p-toggle/p-toggle.vue';
|
|
68
69
|
|
|
@@ -117,6 +118,7 @@ export {
|
|
|
117
118
|
PTableSort,
|
|
118
119
|
PTableTd,
|
|
119
120
|
PTabs,
|
|
121
|
+
PTabsPills,
|
|
120
122
|
PTextarea,
|
|
121
123
|
PToggle,
|
|
122
124
|
Size,
|
|
@@ -2,8 +2,8 @@ import PaginateLeftIcon from '@squirrel/assets/pagination-left-icon.svg?inline';
|
|
|
2
2
|
import PaginateRightIcon from '@squirrel/assets/pagination-right-icon.svg?inline';
|
|
3
3
|
import PActionBar from '@squirrel/components/p-action-bar/p-action-bar.vue';
|
|
4
4
|
import PBtn from '@squirrel/components/p-btn/p-btn.vue';
|
|
5
|
-
import { action } from '
|
|
6
|
-
import { expect, fn, userEvent, waitFor, within } from '
|
|
5
|
+
import { action } from 'storybook/actions';
|
|
6
|
+
import { expect, fn, userEvent, waitFor, within } from 'storybook/test';
|
|
7
7
|
|
|
8
8
|
const actionBarActions = [
|
|
9
9
|
{
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import PBtn from '@squirrel/components/p-btn/p-btn.vue';
|
|
2
|
-
import { action } from '
|
|
3
|
-
import { expect, within } from '
|
|
2
|
+
import { action } from 'storybook/actions';
|
|
3
|
+
import { expect, within } from 'storybook/test';
|
|
4
4
|
|
|
5
5
|
// You can also import an `md` file and use it as a story's description
|
|
6
6
|
// import PBtnReadme from './PBtn.readme.md';
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import PCloseBtn from '@squirrel/components/p-close-btn/p-close-btn.vue';
|
|
2
|
-
import { action } from '
|
|
3
|
-
import { expect, within } from '
|
|
2
|
+
import { action } from 'storybook/actions';
|
|
3
|
+
import { expect, within } from 'storybook/test';
|
|
4
4
|
|
|
5
5
|
export default {
|
|
6
6
|
title: 'Components/PCloseBtn',
|
|
@@ -198,58 +198,45 @@ describe('PDropdownSelect.vue', () => {
|
|
|
198
198
|
cleanup(wrapper);
|
|
199
199
|
});
|
|
200
200
|
|
|
201
|
-
it('shows "All options selected" when all
|
|
201
|
+
it('shows "All options selected" when all options are selected', async () => {
|
|
202
202
|
useVirtualizer.mockImplementation(() => createMockedVirtualizer(10));
|
|
203
203
|
|
|
204
204
|
const items = cloneDeep(filterListItems).slice(0, 10);
|
|
205
205
|
items[0].disabled = true;
|
|
206
206
|
items[1].disabled = true;
|
|
207
|
-
|
|
208
|
-
const wrapper = createWrapper({ selected: [1], items }, { multiple: true });
|
|
207
|
+
const wrapper = createWrapper({ selected: filterListItems.map((item) => item.value), items }, { multiple: true });
|
|
209
208
|
|
|
210
209
|
await wrapper.find('button').trigger('click');
|
|
211
|
-
await wrapper.findByText('Select all').trigger('click');
|
|
212
|
-
|
|
213
|
-
const selectedItemsOptions = wrapper.findAll('[p-select-list-option-item]');
|
|
214
|
-
const selectedItems = wrapper.findAll('[p-select-list-option-item].selected');
|
|
215
210
|
|
|
216
|
-
// Should have all non-disabled items (8) plus the pre-selected disabled item (1)
|
|
217
|
-
expect(selectedItems.length).toBe(selectedItemsOptions.length - 1);
|
|
218
|
-
expect(wrapper.vm.$data.selected.length).toBe(items.length - 1);
|
|
219
211
|
expect(wrapper.find('button').text()).toBe('All options selected');
|
|
220
212
|
|
|
221
213
|
cleanup(wrapper);
|
|
222
214
|
});
|
|
223
215
|
|
|
224
|
-
it('
|
|
216
|
+
it('does not select disabled options when "Select All" is clicked', async () => {
|
|
225
217
|
useVirtualizer.mockImplementation(() => createMockedVirtualizer(10));
|
|
226
218
|
|
|
227
219
|
const items = cloneDeep(filterListItems).slice(0, 10);
|
|
228
220
|
items[0].disabled = true;
|
|
229
|
-
|
|
221
|
+
items[1].disabled = true;
|
|
222
|
+
const wrapper = createWrapper({ selected: [], items }, { multiple: true });
|
|
230
223
|
|
|
231
|
-
|
|
224
|
+
await wrapper.find('button').trigger('click');
|
|
225
|
+
await wrapper.findByText('Select all').trigger('click');
|
|
226
|
+
|
|
227
|
+
expect(wrapper.find('button').text()).toBe('8 options selected');
|
|
232
228
|
|
|
233
229
|
cleanup(wrapper);
|
|
234
230
|
});
|
|
235
231
|
|
|
236
|
-
it('
|
|
232
|
+
it('shows number of selected options when not all non-disabled options are selected', async () => {
|
|
237
233
|
useVirtualizer.mockImplementation(() => createMockedVirtualizer(10));
|
|
238
234
|
|
|
239
235
|
const items = cloneDeep(filterListItems).slice(0, 10);
|
|
240
236
|
items[0].disabled = true;
|
|
241
|
-
|
|
242
|
-
const wrapper = createWrapper({ selected: [], items }, { multiple: true });
|
|
243
|
-
|
|
244
|
-
await wrapper.find('button').trigger('click');
|
|
245
|
-
|
|
246
|
-
await wrapper.findByText('Select all').trigger('click');
|
|
247
|
-
|
|
248
|
-
const selectedItemsOptions = wrapper.findAll('[p-select-list-option-item]');
|
|
249
|
-
const selectedItems = wrapper.findAll('[p-select-list-option-item].selected');
|
|
237
|
+
const wrapper = createWrapper({ selected: [2, 3], items }, { multiple: true });
|
|
250
238
|
|
|
251
|
-
expect(
|
|
252
|
-
expect(wrapper.vm.$data.selected.length).toBe(items.length - 2);
|
|
239
|
+
expect(wrapper.find('button').text()).toBe('2 options selected');
|
|
253
240
|
|
|
254
241
|
cleanup(wrapper);
|
|
255
242
|
});
|
|
@@ -59,7 +59,7 @@
|
|
|
59
59
|
<div v-else class="truncate text-left">
|
|
60
60
|
{{
|
|
61
61
|
multiple
|
|
62
|
-
? selectedItems.length ===
|
|
62
|
+
? selectedItems.length === computedItems.length
|
|
63
63
|
? 'All options selected'
|
|
64
64
|
: `${selectedItems.length} option${selectedItems.length > 1 ? 's' : ''} selected`
|
|
65
65
|
: selectedItems[0][itemText]
|
|
@@ -467,10 +467,6 @@ const attrs = computed(() => {
|
|
|
467
467
|
|
|
468
468
|
const style = computed(() => $attrs.style as StyleValue);
|
|
469
469
|
|
|
470
|
-
const selectableItemsCount = computed(
|
|
471
|
-
() => internalItems.value.filter((item) => !isDisabled(item) || isSelected(item[props.itemValue])).length
|
|
472
|
-
);
|
|
473
|
-
|
|
474
470
|
// Watch
|
|
475
471
|
// Sorts internalItems putting the selected ones first
|
|
476
472
|
watch(
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import PFileUpload from '@squirrel/components/p-file-upload/p-file-upload.vue';
|
|
2
|
-
import { expect } from '@storybook/test';
|
|
3
2
|
import { createWrapperFor } from '@tests/vitest.helpers';
|
|
3
|
+
import { expect } from 'storybook/test';
|
|
4
4
|
import { vi } from 'vitest';
|
|
5
5
|
|
|
6
6
|
// Mocking the toastification library
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import PInputNumber from '@squirrel/components/p-input-number/p-input-number.vue';
|
|
2
|
-
import { expect } from '@storybook/test';
|
|
3
2
|
import { createWrapperFor } from '@tests/vitest.helpers';
|
|
3
|
+
import { expect } from 'storybook/test';
|
|
4
4
|
|
|
5
5
|
const baseClasses = () => [
|
|
6
6
|
'text-night',
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import PRingLoader from '@squirrel/components/p-ring-loader/p-ring-loader.vue';
|
|
2
|
-
import { expect } from '@storybook/test';
|
|
3
2
|
import { createWrapperFor } from '@tests/vitest.helpers';
|
|
3
|
+
import { expect } from 'storybook/test';
|
|
4
4
|
|
|
5
5
|
describe('PRingLoader.vue', () => {
|
|
6
6
|
it('renders correctly with custom props', async () => {
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { fieldArgTypes } from '@root/stories/common/field';
|
|
2
2
|
import PSelectBtn from '@squirrel/components/p-select-btn/p-select-btn.vue';
|
|
3
|
-
import { expect, userEvent, within } from '
|
|
3
|
+
import { expect, userEvent, within } from 'storybook/test';
|
|
4
4
|
import { ref } from 'vue';
|
|
5
5
|
|
|
6
6
|
const selectItems = [
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { fieldArgTypes } from '@root/stories/common/field';
|
|
2
2
|
import { getCSSTransitionDuration, sleep } from '@root/stories/common/helpers';
|
|
3
3
|
import PSelectPill from '@squirrel/components/p-select-pill/p-select-pill.vue';
|
|
4
|
-
import { expect, userEvent, within } from '
|
|
4
|
+
import { expect, userEvent, within } from 'storybook/test';
|
|
5
5
|
import { ref } from 'vue';
|
|
6
6
|
|
|
7
7
|
const ACTIVE_CLASS = 'text-p-purple-60';
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import PaginateRightIcon from '@squirrel/assets/pagination-right-icon.svg?inline';
|
|
2
2
|
import PTabs from '@squirrel/components/p-tabs/p-tabs.vue';
|
|
3
|
-
import { action } from '
|
|
3
|
+
import { action } from 'storybook/actions';
|
|
4
4
|
|
|
5
5
|
export default {
|
|
6
6
|
title: 'Components/PTabs',
|
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
import PTabsPills from '@squirrel/components/p-tabs-pills/p-tabs-pills.vue';
|
|
2
|
+
import { createWrapperFor } from '@tests/vitest.helpers';
|
|
3
|
+
|
|
4
|
+
const ACTIVE_CLASSES = ['bg-night', 'text-surface', 'hover:bg-night', 'hover:text-surface'];
|
|
5
|
+
|
|
6
|
+
const createTabItems = () => [
|
|
7
|
+
{ value: 'tab1', text: 'Home' },
|
|
8
|
+
{ value: 'tab2', text: 'Profile' },
|
|
9
|
+
{ value: 'tab3', text: 'Messages' },
|
|
10
|
+
{ value: 'tab4', text: 'About' },
|
|
11
|
+
{ value: 'tab5', text: 'Settings' },
|
|
12
|
+
];
|
|
13
|
+
|
|
14
|
+
const createWrapper = (props = {}) => {
|
|
15
|
+
return createWrapperFor(PTabsPills, {
|
|
16
|
+
props: {
|
|
17
|
+
items: createTabItems(),
|
|
18
|
+
...props,
|
|
19
|
+
},
|
|
20
|
+
});
|
|
21
|
+
};
|
|
22
|
+
|
|
23
|
+
describe('PTabsPills.vue', () => {
|
|
24
|
+
it('renders correctly', async () => {
|
|
25
|
+
const wrapper = createWrapper();
|
|
26
|
+
const buttons = await wrapper.findAll('button');
|
|
27
|
+
|
|
28
|
+
buttons.forEach((button) => {
|
|
29
|
+
ACTIVE_CLASSES.forEach((className) => {
|
|
30
|
+
expect(button.classes()).not.toContain(className);
|
|
31
|
+
});
|
|
32
|
+
});
|
|
33
|
+
expect(buttons.length).toBe(5);
|
|
34
|
+
expect(buttons[1].text()).toBe(createTabItems()[1].text);
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
it(`updates the value bound with v-model`, async () => {
|
|
38
|
+
const wrapper = createWrapperFor({
|
|
39
|
+
data() {
|
|
40
|
+
return {
|
|
41
|
+
selected: 'tab1',
|
|
42
|
+
items: createTabItems(),
|
|
43
|
+
};
|
|
44
|
+
},
|
|
45
|
+
template: '<PTabsPills :items="items" v-model="selected" />',
|
|
46
|
+
components: { PTabsPills },
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
await wrapper.setData({ selected: 'tab4' });
|
|
50
|
+
|
|
51
|
+
const selected = wrapper.findAll('button')[3];
|
|
52
|
+
|
|
53
|
+
ACTIVE_CLASSES.forEach((className) => {
|
|
54
|
+
expect(selected.classes()).toContain(className);
|
|
55
|
+
});
|
|
56
|
+
expect(selected.attributes('aria-selected')).toBe('true');
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
it(`updates the value bound with v-model when an option is clicked`, async () => {
|
|
60
|
+
const wrapper = createWrapperFor({
|
|
61
|
+
data() {
|
|
62
|
+
return {
|
|
63
|
+
selected: 'tab1',
|
|
64
|
+
items: createTabItems(),
|
|
65
|
+
};
|
|
66
|
+
},
|
|
67
|
+
template: '<PTabsPills :items="items" v-model="selected" />',
|
|
68
|
+
components: { PTabsPills },
|
|
69
|
+
});
|
|
70
|
+
|
|
71
|
+
await wrapper.findAll('button')[3].trigger('click');
|
|
72
|
+
|
|
73
|
+
const selected = wrapper.findAll('button')[3];
|
|
74
|
+
|
|
75
|
+
ACTIVE_CLASSES.forEach((className) => {
|
|
76
|
+
expect(selected.classes()).toContain(className);
|
|
77
|
+
});
|
|
78
|
+
expect(selected.attributes('aria-selected')).toBe('true');
|
|
79
|
+
});
|
|
80
|
+
|
|
81
|
+
it('works with custom itemValue and itemText', async () => {
|
|
82
|
+
const tabItems = createTabItems();
|
|
83
|
+
|
|
84
|
+
const wrapper = createWrapper({
|
|
85
|
+
items: tabItems.map((item) => ({ customValue: item.value, customText: item.text })),
|
|
86
|
+
itemValue: 'customValue',
|
|
87
|
+
itemText: 'customText',
|
|
88
|
+
});
|
|
89
|
+
|
|
90
|
+
const buttons = await wrapper.findAll('button');
|
|
91
|
+
|
|
92
|
+
expect(buttons.length).toBe(5);
|
|
93
|
+
|
|
94
|
+
buttons.forEach((button, i) => {
|
|
95
|
+
expect(button.text()).toBe(tabItems[i].text);
|
|
96
|
+
});
|
|
97
|
+
});
|
|
98
|
+
|
|
99
|
+
it(`disables an option based on the disabled attribute`, async () => {
|
|
100
|
+
const wrapper = createWrapper({
|
|
101
|
+
items: createTabItems().map((item, i) => ({ ...item, disabled: i === 2 })),
|
|
102
|
+
modelValue: 'tab1',
|
|
103
|
+
});
|
|
104
|
+
|
|
105
|
+
const buttons = await wrapper.findAll('button');
|
|
106
|
+
|
|
107
|
+
buttons.forEach((button, i) => {
|
|
108
|
+
if (i === 2) {
|
|
109
|
+
expect(button.element.disabled).toBe(true);
|
|
110
|
+
expect(button.classes()).toContain('text-p-gray-30');
|
|
111
|
+
} else {
|
|
112
|
+
expect(button.element.disabled).toBe(false);
|
|
113
|
+
expect(button.classes()).not.toContain('text-p-gray-30');
|
|
114
|
+
}
|
|
115
|
+
});
|
|
116
|
+
});
|
|
117
|
+
});
|
|
@@ -0,0 +1,139 @@
|
|
|
1
|
+
import PTabsPills from '@squirrel/components/p-tabs-pills/p-tabs-pills.vue';
|
|
2
|
+
import { expect, userEvent, within } from 'storybook/test';
|
|
3
|
+
import { ref } from 'vue';
|
|
4
|
+
|
|
5
|
+
const ACTIVE_CLASSES = ['bg-night', 'text-surface', 'hover:bg-night', 'hover:text-surface'];
|
|
6
|
+
|
|
7
|
+
const DISABLED_CLASSES = ['text-p-gray-30'];
|
|
8
|
+
|
|
9
|
+
export default {
|
|
10
|
+
title: 'Components/PTabsPills',
|
|
11
|
+
component: PTabsPills,
|
|
12
|
+
tags: ['autodocs'],
|
|
13
|
+
render: (args) => ({
|
|
14
|
+
components: { PTabsPills },
|
|
15
|
+
setup() {
|
|
16
|
+
const selected = ref('tab1');
|
|
17
|
+
|
|
18
|
+
return { args, selected };
|
|
19
|
+
},
|
|
20
|
+
template: `
|
|
21
|
+
<PTabsPills v-model="selected" v-bind="args" />
|
|
22
|
+
<div class="mt-2">Selected: {{ selected }}</div>
|
|
23
|
+
`,
|
|
24
|
+
}),
|
|
25
|
+
parameters: {
|
|
26
|
+
docs: {
|
|
27
|
+
description: {
|
|
28
|
+
component: `PTabsPills can be used as a tab or button group that allows the user to select an option from a list of options.`,
|
|
29
|
+
},
|
|
30
|
+
},
|
|
31
|
+
},
|
|
32
|
+
};
|
|
33
|
+
|
|
34
|
+
export const Default = {
|
|
35
|
+
args: {
|
|
36
|
+
items: [
|
|
37
|
+
{ value: 'tab1', text: 'Home' },
|
|
38
|
+
{ value: 'tab2', text: 'Profile' },
|
|
39
|
+
{ value: 'tab3', text: 'Messages' },
|
|
40
|
+
],
|
|
41
|
+
},
|
|
42
|
+
play: async ({ canvasElement }) => {
|
|
43
|
+
const canvas = within(canvasElement);
|
|
44
|
+
|
|
45
|
+
await userEvent.click(canvas.getByText(/Profile/i));
|
|
46
|
+
|
|
47
|
+
const button = canvas.getByText(/Profile/i).closest('button');
|
|
48
|
+
ACTIVE_CLASSES.forEach((className) => {
|
|
49
|
+
expect(button.classList).toContain(className);
|
|
50
|
+
});
|
|
51
|
+
},
|
|
52
|
+
};
|
|
53
|
+
|
|
54
|
+
export const CustomValueAndText = {
|
|
55
|
+
args: {
|
|
56
|
+
items: [
|
|
57
|
+
{ myValue: 'tab1', myText: 'Home' },
|
|
58
|
+
{ myValue: 'tab2', myText: 'Profile' },
|
|
59
|
+
{ myValue: 'tab3', myText: 'Messages' },
|
|
60
|
+
],
|
|
61
|
+
itemValue: 'myValue',
|
|
62
|
+
itemText: 'myText',
|
|
63
|
+
},
|
|
64
|
+
parameters: {
|
|
65
|
+
docs: {
|
|
66
|
+
description: {
|
|
67
|
+
story: 'You can customize the property names used for value and text using `itemValue` and `itemText` props.',
|
|
68
|
+
},
|
|
69
|
+
},
|
|
70
|
+
},
|
|
71
|
+
play: async ({ canvasElement }) => {
|
|
72
|
+
const canvas = within(canvasElement);
|
|
73
|
+
|
|
74
|
+
await userEvent.click(canvas.getByText(/Profile/i));
|
|
75
|
+
|
|
76
|
+
const button = canvas.getByText(/Profile/i).closest('button');
|
|
77
|
+
ACTIVE_CLASSES.forEach((className) => {
|
|
78
|
+
expect(button.classList).toContain(className);
|
|
79
|
+
});
|
|
80
|
+
},
|
|
81
|
+
};
|
|
82
|
+
|
|
83
|
+
export const DisabledItems = {
|
|
84
|
+
args: {
|
|
85
|
+
items: [
|
|
86
|
+
{ myValue: 'tab1', myText: 'Home', disabled: false },
|
|
87
|
+
{ myValue: 'tab2', myText: 'Profile', disabled: true },
|
|
88
|
+
{ myValue: 'tab3', myText: 'About', disabled: true },
|
|
89
|
+
{ myValue: 'tab4', myText: 'Messages', disabled: false },
|
|
90
|
+
],
|
|
91
|
+
itemValue: 'myValue',
|
|
92
|
+
itemText: 'myText',
|
|
93
|
+
},
|
|
94
|
+
parameters: {
|
|
95
|
+
docs: {
|
|
96
|
+
description: {
|
|
97
|
+
story: 'You can disable specific tabs by adding a `disabled` property to the items.',
|
|
98
|
+
},
|
|
99
|
+
},
|
|
100
|
+
},
|
|
101
|
+
play: async ({ canvasElement }) => {
|
|
102
|
+
const canvas = within(canvasElement);
|
|
103
|
+
|
|
104
|
+
// Check that disabled buttons have disabled classes
|
|
105
|
+
const disabledButtons = [
|
|
106
|
+
canvas.getByText(/Profile/i).closest('button'),
|
|
107
|
+
canvas.getByText(/About/i).closest('button'),
|
|
108
|
+
];
|
|
109
|
+
disabledButtons.forEach((button) => {
|
|
110
|
+
DISABLED_CLASSES.forEach((className) => {
|
|
111
|
+
expect(button.classList).toContain(className);
|
|
112
|
+
});
|
|
113
|
+
ACTIVE_CLASSES.forEach((className) => {
|
|
114
|
+
expect(button.classList).not.toContain(className);
|
|
115
|
+
});
|
|
116
|
+
});
|
|
117
|
+
|
|
118
|
+
// Try to click a disabled button
|
|
119
|
+
await userEvent.click(canvas.getByText(/Profile/i));
|
|
120
|
+
|
|
121
|
+
// Check that disabled button still has disabled classes and not active classes
|
|
122
|
+
const disabledButton = canvas.getByText(/Profile/i).closest('button');
|
|
123
|
+
DISABLED_CLASSES.forEach((className) => {
|
|
124
|
+
expect(disabledButton.classList).toContain(className);
|
|
125
|
+
});
|
|
126
|
+
ACTIVE_CLASSES.forEach((className) => {
|
|
127
|
+
expect(disabledButton.classList).not.toContain(className);
|
|
128
|
+
});
|
|
129
|
+
|
|
130
|
+
// Check that Home button still has active classes
|
|
131
|
+
const homeButton = canvas.getByText(/Home/i).closest('button');
|
|
132
|
+
ACTIVE_CLASSES.forEach((className) => {
|
|
133
|
+
expect(homeButton.classList).toContain(className);
|
|
134
|
+
});
|
|
135
|
+
DISABLED_CLASSES.forEach((className) => {
|
|
136
|
+
expect(homeButton.classList).not.toContain(className);
|
|
137
|
+
});
|
|
138
|
+
},
|
|
139
|
+
};
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<nav
|
|
3
|
+
class="flex h-6 w-fit flex-row space-x-1 overflow-x-auto rounded bg-p-gray-10 p-1 text-sm font-medium text-p-gray-50"
|
|
4
|
+
aria-label="Tabs Pills"
|
|
5
|
+
role="tablist"
|
|
6
|
+
aria-orientation="horizontal"
|
|
7
|
+
>
|
|
8
|
+
<button
|
|
9
|
+
v-for="tab in items"
|
|
10
|
+
:key="String(tab[itemValue])"
|
|
11
|
+
type="button"
|
|
12
|
+
class="rounded px-2 pb-px leading-none transition-all duration-300"
|
|
13
|
+
:class="[
|
|
14
|
+
tab.disabled
|
|
15
|
+
? 'text-p-gray-30'
|
|
16
|
+
: tab[itemValue] === modelValue
|
|
17
|
+
? 'bg-night text-surface hover:bg-night hover:text-surface'
|
|
18
|
+
: 'hover:text-p-gray-70',
|
|
19
|
+
]"
|
|
20
|
+
:disabled="!!tab.disabled"
|
|
21
|
+
role="tab"
|
|
22
|
+
:aria-selected="tab[itemValue] === modelValue"
|
|
23
|
+
:data-tab="`#${tab[itemValue]}`"
|
|
24
|
+
:aria-controls="String(tab[itemValue])"
|
|
25
|
+
@click="$emit('update:modelValue', tab[itemValue])"
|
|
26
|
+
>
|
|
27
|
+
{{ tab[itemText] }}
|
|
28
|
+
</button>
|
|
29
|
+
</nav>
|
|
30
|
+
</template>
|
|
31
|
+
|
|
32
|
+
<script setup lang="ts">
|
|
33
|
+
defineOptions({
|
|
34
|
+
name: 'PTabsPills',
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
type Value = string | number | boolean;
|
|
38
|
+
|
|
39
|
+
type Option = {
|
|
40
|
+
[key: string]: Value;
|
|
41
|
+
};
|
|
42
|
+
|
|
43
|
+
type Props = {
|
|
44
|
+
modelValue?: Value;
|
|
45
|
+
items?: readonly Option[];
|
|
46
|
+
itemText?: string;
|
|
47
|
+
itemValue?: string;
|
|
48
|
+
};
|
|
49
|
+
|
|
50
|
+
withDefaults(defineProps<Props>(), {
|
|
51
|
+
modelValue: '',
|
|
52
|
+
items: () => [],
|
|
53
|
+
itemValue: 'value',
|
|
54
|
+
itemText: 'text',
|
|
55
|
+
});
|
|
56
|
+
|
|
57
|
+
defineEmits<{
|
|
58
|
+
'update:modelValue': [val: Value];
|
|
59
|
+
}>();
|
|
60
|
+
</script>
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import PToggle from '@squirrel/components/p-toggle/p-toggle.vue';
|
|
2
|
-
import { expect } from '@storybook/test';
|
|
3
2
|
import { createWrapperFor } from '@tests/vitest.helpers';
|
|
3
|
+
import { expect } from 'storybook/test';
|
|
4
4
|
|
|
5
5
|
describe('PToggle.vue', () => {
|
|
6
6
|
it('renders correctly', async () => {
|