@pequity/squirrel 8.3.4 → 8.4.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 (32) hide show
  1. package/dist/cjs/chunks/p-tabs-pills.js +44 -0
  2. package/dist/cjs/index.js +2 -0
  3. package/dist/cjs/p-checkbox.js +10 -3
  4. package/dist/cjs/p-tabs-pills.js +3 -0
  5. package/dist/es/chunks/p-tabs-pills.js +45 -0
  6. package/dist/es/index.js +4 -2
  7. package/dist/es/p-checkbox.js +10 -3
  8. package/dist/es/p-tabs-pills.js +4 -0
  9. package/dist/squirrel/components/index.d.ts +2 -1
  10. package/dist/squirrel/components/p-tabs-pills/p-tabs-pills.vue.d.ts +21 -0
  11. package/dist/squirrel.css +1 -1
  12. package/package.json +23 -29
  13. package/squirrel/components/index.ts +2 -0
  14. package/squirrel/components/p-action-bar/p-action-bar.stories.js +2 -2
  15. package/squirrel/components/p-alert/p-alert.stories.js +1 -1
  16. package/squirrel/components/p-btn/p-btn.stories.js +2 -2
  17. package/squirrel/components/p-checkbox/p-checkbox.spec.js +16 -1
  18. package/squirrel/components/p-checkbox/p-checkbox.stories.js +1 -1
  19. package/squirrel/components/p-checkbox/p-checkbox.vue +14 -2
  20. package/squirrel/components/p-close-btn/p-close-btn.stories.js +2 -2
  21. package/squirrel/components/p-file-upload/p-file-upload.spec.js +1 -1
  22. package/squirrel/components/p-input-number/p-input-number.spec.js +1 -1
  23. package/squirrel/components/p-ring-loader/p-ring-loader.spec.js +1 -1
  24. package/squirrel/components/p-select-btn/p-select-btn.stories.js +1 -1
  25. package/squirrel/components/p-select-pill/p-select-pill.stories.js +1 -1
  26. package/squirrel/components/p-table-header-cell/p-table-header-cell.stories.js +1 -1
  27. package/squirrel/components/p-tabs/p-tabs.stories.js +1 -1
  28. package/squirrel/components/p-tabs-pills/p-tabs-pills.spec.js +117 -0
  29. package/squirrel/components/p-tabs-pills/p-tabs-pills.stories.js +139 -0
  30. package/squirrel/components/p-tabs-pills/p-tabs-pills.vue +60 -0
  31. package/squirrel/components/p-toggle/p-toggle.spec.js +1 -1
  32. package/squirrel/composables/useInputClasses.spec.js +1 -1
@@ -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;
@@ -32,15 +32,22 @@ const _sfc_main = /* @__PURE__ */ vue.defineComponent({
32
32
  const style = vue.computed(() => {
33
33
  return allAttrs.style;
34
34
  });
35
+ const isDisabled = vue.computed(() => {
36
+ return "disabled" in allAttrs && allAttrs.disabled !== false;
37
+ });
35
38
  return (_ctx, _cache) => {
36
39
  return vue.openBlock(), vue.createElementBlock("label", {
37
- class: vue.normalizeClass(["inline-flex items-center", [{ hidden: _ctx.$attrs.hidden }, _ctx.$attrs.class]]),
40
+ class: vue.normalizeClass(["inline-flex items-center", [
41
+ { hidden: _ctx.$attrs.hidden },
42
+ _ctx.$attrs.class,
43
+ { "cursor-not-allowed opacity-50": isDisabled.value, "cursor-pointer": !isDisabled.value }
44
+ ]]),
38
45
  style: vue.normalizeStyle(style.value)
39
46
  }, [
40
47
  vue.renderSlot(_ctx.$slots, "label-before", {}, void 0, true),
41
48
  vue.createElementVNode("input", vue.mergeProps({
42
49
  type: "checkbox",
43
- class: "h-4 w-4 shrink-0 appearance-none rounded border border-p-gray-30 bg-surface duration-100 ease-in-out checked:border-none checked:bg-primary disabled:opacity-50"
50
+ class: "h-4 w-4 shrink-0 appearance-none rounded border border-p-gray-30 bg-surface duration-100 ease-in-out checked:border-none checked:bg-primary"
44
51
  }, attrs.value, {
45
52
  checked: __props.modelValue,
46
53
  onChange: _cache[0] || (_cache[0] = ($event) => _ctx.$emit("update:modelValue", $event.target.checked))
@@ -52,5 +59,5 @@ const _sfc_main = /* @__PURE__ */ vue.defineComponent({
52
59
  };
53
60
  }
54
61
  });
55
- const pCheckbox = /* @__PURE__ */ _pluginVue_exportHelper._export_sfc(_sfc_main, [["__scopeId", "data-v-81cd6154"]]);
62
+ const pCheckbox = /* @__PURE__ */ _pluginVue_exportHelper._export_sfc(_sfc_main, [["__scopeId", "data-v-b394a2f0"]]);
56
63
  module.exports = pCheckbox;
@@ -0,0 +1,3 @@
1
+ "use strict";
2
+ const pTabsPills_vue_vue_type_script_setup_true_lang = require("./chunks/p-tabs-pills.js");
3
+ module.exports = pTabsPills_vue_vue_type_script_setup_true_lang._sfc_main;
@@ -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-textarea.js";
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 PTextarea,
1083
+ _22 as PTabsPills,
1084
+ _23 as PTextarea,
1083
1085
  default12 as PToggle,
1084
1086
  P_ICON_ALIASES,
1085
1087
  S as SIZES,
@@ -31,15 +31,22 @@ const _sfc_main = /* @__PURE__ */ defineComponent({
31
31
  const style = computed(() => {
32
32
  return allAttrs.style;
33
33
  });
34
+ const isDisabled = computed(() => {
35
+ return "disabled" in allAttrs && allAttrs.disabled !== false;
36
+ });
34
37
  return (_ctx, _cache) => {
35
38
  return openBlock(), createElementBlock("label", {
36
- class: normalizeClass(["inline-flex items-center", [{ hidden: _ctx.$attrs.hidden }, _ctx.$attrs.class]]),
39
+ class: normalizeClass(["inline-flex items-center", [
40
+ { hidden: _ctx.$attrs.hidden },
41
+ _ctx.$attrs.class,
42
+ { "cursor-not-allowed opacity-50": isDisabled.value, "cursor-pointer": !isDisabled.value }
43
+ ]]),
37
44
  style: normalizeStyle(style.value)
38
45
  }, [
39
46
  renderSlot(_ctx.$slots, "label-before", {}, void 0, true),
40
47
  createElementVNode("input", mergeProps({
41
48
  type: "checkbox",
42
- class: "h-4 w-4 shrink-0 appearance-none rounded border border-p-gray-30 bg-surface duration-100 ease-in-out checked:border-none checked:bg-primary disabled:opacity-50"
49
+ class: "h-4 w-4 shrink-0 appearance-none rounded border border-p-gray-30 bg-surface duration-100 ease-in-out checked:border-none checked:bg-primary"
43
50
  }, attrs.value, {
44
51
  checked: __props.modelValue,
45
52
  onChange: _cache[0] || (_cache[0] = ($event) => _ctx.$emit("update:modelValue", $event.target.checked))
@@ -51,7 +58,7 @@ const _sfc_main = /* @__PURE__ */ defineComponent({
51
58
  };
52
59
  }
53
60
  });
54
- const pCheckbox = /* @__PURE__ */ _export_sfc(_sfc_main, [["__scopeId", "data-v-81cd6154"]]);
61
+ const pCheckbox = /* @__PURE__ */ _export_sfc(_sfc_main, [["__scopeId", "data-v-b394a2f0"]]);
55
62
  export {
56
63
  pCheckbox as default
57
64
  };
@@ -0,0 +1,4 @@
1
+ import { _ as _sfc_main } from "./chunks/p-tabs-pills.js";
2
+ export {
3
+ _sfc_main as default
4
+ };
@@ -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/dist/squirrel.css CHANGED
@@ -191,7 +191,7 @@ div[id^=popper_].dropdown .v-popper__inner .dropdown-menu .dropdown-divider {
191
191
  --tw-content: "";
192
192
  content: var(--tw-content);
193
193
  }
194
- input[type='checkbox'][data-v-81cd6154]:checked {
194
+ input[type='checkbox'][data-v-b394a2f0]:checked {
195
195
  background-image: url("data:image/svg+xml;charset=utf-8,%3Csvg viewBox='0 0 16 16' fill='%23fff' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath d='M12.207 4.793a1 1 0 010 1.414l-5 5a1 1 0 01-1.414 0l-2-2a1 1 0 011.414-1.414L6.5 9.086l4.293-4.293a1 1 0 011.414 0z'/%3E%3C/svg%3E");
196
196
  }
197
197
 
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@pequity/squirrel",
3
3
  "description": "Squirrel component library",
4
- "version": "8.3.4",
4
+ "version": "8.4.0",
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.1",
53
- "@playwright/test": "^1.52.0",
52
+ "@pequity/eslint-config": "^2.0.2",
53
+ "@playwright/test": "^1.53.0",
54
54
  "@semantic-release/changelog": "^6.0.3",
55
55
  "@semantic-release/git": "^10.0.1",
56
- "@storybook/addon-a11y": "^8.6.14",
57
- "@storybook/addon-actions": "^8.6.14",
58
- "@storybook/addon-essentials": "^8.6.14",
59
- "@storybook/addon-interactions": "^8.6.14",
60
- "@storybook/addon-links": "^8.6.14",
61
- "@storybook/blocks": "^8.6.14",
62
- "@storybook/manager-api": "^8.6.14",
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.10",
57
+ "@storybook/addon-docs": "^9.0.10",
58
+ "@storybook/addon-links": "^9.0.10",
59
+ "@storybook/test-runner": "^0.23.0",
60
+ "@storybook/vue3": "^9.0.10",
61
+ "@storybook/vue3-vite": "^9.0.10",
62
+ "@tanstack/vue-virtual": "3.13.10",
69
63
  "@types/jsdom": "^21.1.7",
70
64
  "@types/lodash-es": "^4.17.12",
71
- "@types/node": "^22.15.21",
65
+ "@types/node": "^24.0.3",
72
66
  "@vitejs/plugin-vue": "^5.2.4",
73
- "@vitest/coverage-v8": "^3.1.4",
74
- "@vue/compiler-sfc": "3.5.15",
67
+ "@vitest/coverage-v8": "^3.2.3",
68
+ "@vue/compiler-sfc": "3.5.16",
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.27.0",
79
- "eslint-plugin-storybook": "^0.12.0",
72
+ "eslint": "^9.29.0",
73
+ "eslint-plugin-storybook": "^9.0.10",
80
74
  "floating-vue": "5.2.2",
81
- "glob": "^11.0.2",
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.0.0",
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.3",
82
+ "postcss": "^8.5.6",
89
83
  "prettier": "^3.5.3",
90
- "prettier-plugin-tailwindcss": "^0.6.11",
84
+ "prettier-plugin-tailwindcss": "^0.6.12",
91
85
  "resolve-tspaths": "^0.8.23",
92
86
  "rimraf": "^6.0.1",
93
- "sass": "^1.89.0",
87
+ "sass": "^1.89.2",
94
88
  "semantic-release": "^24.2.5",
95
- "storybook": "^8.6.14",
89
+ "storybook": "^9.0.10",
96
90
  "svgo": "^3.3.2",
97
91
  "tailwindcss": "^3.4.17",
98
92
  "typescript": "5.8.3",
99
93
  "vite": "^6.3.5",
100
- "vitest": "^3.1.4",
101
- "vue": "3.5.15",
94
+ "vitest": "^3.2.3",
95
+ "vue": "3.5.16",
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 '@storybook/addon-actions';
6
- import { expect, fn, userEvent, waitFor, within } from '@storybook/test';
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,5 +1,5 @@
1
1
  import PAlert from '@squirrel/components/p-alert/p-alert.vue';
2
- import { expect, within } from '@storybook/test';
2
+ import { expect, within } from 'storybook/test';
3
3
 
4
4
  export default {
5
5
  title: 'Components/PAlert',
@@ -1,6 +1,6 @@
1
1
  import PBtn from '@squirrel/components/p-btn/p-btn.vue';
2
- import { action } from '@storybook/addon-actions';
3
- import { expect, within } from '@storybook/test';
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';
@@ -14,7 +14,6 @@ const checkboxClasses = [
14
14
  'ease-in-out',
15
15
  'checked:border-none',
16
16
  'checked:bg-primary',
17
- 'disabled:opacity-50',
18
17
  ];
19
18
 
20
19
  describe('PCheckbox.vue', () => {
@@ -39,4 +38,20 @@ describe('PCheckbox.vue', () => {
39
38
  expect(input.classes()).toEqual(checkboxClasses);
40
39
  expect(input.attributes().disabled).toBeDefined();
41
40
  });
41
+
42
+ it('applies correct styling to label based on disabled state', async () => {
43
+ const enabledWrapper = createWrapperFor(PCheckbox, { props: { checked: true } });
44
+ const disabledWrapper = createWrapperFor(PCheckbox, { props: { checked: true, disabled: true } });
45
+
46
+ const enabledLabel = await enabledWrapper.find('label');
47
+ const disabledLabel = await disabledWrapper.find('label');
48
+
49
+ expect(enabledLabel.classes()).toContain('cursor-pointer');
50
+ expect(enabledLabel.classes()).not.toContain('cursor-not-allowed');
51
+ expect(enabledLabel.classes()).not.toContain('opacity-50');
52
+
53
+ expect(disabledLabel.classes()).toContain('cursor-not-allowed');
54
+ expect(disabledLabel.classes()).toContain('opacity-50');
55
+ expect(disabledLabel.classes()).not.toContain('cursor-pointer');
56
+ });
42
57
  });
@@ -1,5 +1,5 @@
1
1
  import PCheckbox from '@squirrel/components/p-checkbox/p-checkbox.vue';
2
- import { action } from '@storybook/addon-actions';
2
+ import { action } from 'storybook/actions';
3
3
 
4
4
  export default {
5
5
  title: 'Components/PCheckbox',
@@ -1,9 +1,17 @@
1
1
  <template>
2
- <label class="inline-flex items-center" :class="[{ hidden: $attrs.hidden }, $attrs.class]" :style="style">
2
+ <label
3
+ class="inline-flex items-center"
4
+ :class="[
5
+ { hidden: $attrs.hidden },
6
+ $attrs.class,
7
+ { 'cursor-not-allowed opacity-50': isDisabled, 'cursor-pointer': !isDisabled },
8
+ ]"
9
+ :style="style"
10
+ >
3
11
  <slot name="label-before"></slot>
4
12
  <input
5
13
  type="checkbox"
6
- class="h-4 w-4 shrink-0 appearance-none rounded border border-p-gray-30 bg-surface duration-100 ease-in-out checked:border-none checked:bg-primary disabled:opacity-50"
14
+ class="h-4 w-4 shrink-0 appearance-none rounded border border-p-gray-30 bg-surface duration-100 ease-in-out checked:border-none checked:bg-primary"
7
15
  v-bind="attrs"
8
16
  :checked="modelValue"
9
17
  @change="$emit('update:modelValue', ($event.target as HTMLInputElement).checked)"
@@ -48,6 +56,10 @@ const attrs = computed(() => {
48
56
  const style = computed(() => {
49
57
  return allAttrs.style as StyleValue;
50
58
  });
59
+
60
+ const isDisabled = computed(() => {
61
+ return 'disabled' in allAttrs && allAttrs.disabled !== false;
62
+ });
51
63
  </script>
52
64
 
53
65
  <style lang="css" scoped>
@@ -1,6 +1,6 @@
1
1
  import PCloseBtn from '@squirrel/components/p-close-btn/p-close-btn.vue';
2
- import { action } from '@storybook/addon-actions';
3
- import { expect, within } from '@storybook/test';
2
+ import { action } from 'storybook/actions';
3
+ import { expect, within } from 'storybook/test';
4
4
 
5
5
  export default {
6
6
  title: 'Components/PCloseBtn',
@@ -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 '@storybook/test';
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 '@storybook/test';
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,5 +1,5 @@
1
1
  import PTableHeaderCell from '@squirrel/components/p-table-header-cell/p-table-header-cell.vue';
2
- import { action } from '@storybook/addon-actions';
2
+ import { action } from 'storybook/actions';
3
3
 
4
4
  export default {
5
5
  title: 'Components/PTableHeaderCell',
@@ -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 '@storybook/addon-actions';
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 () => {
@@ -1,5 +1,5 @@
1
1
  import { useInputClasses } from '@squirrel/composables/useInputClasses';
2
- import { expect } from '@storybook/test';
2
+ import { expect } from 'storybook/test';
3
3
  import { reactive, ref } from 'vue';
4
4
 
5
5
  const baseInputClassesMd = () => [