tide-design-system 2.0.22 → 2.0.24

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 (304) hide show
  1. package/.eslintrc.cjs +90 -0
  2. package/.storybook/main.ts +60 -0
  3. package/.storybook/preview.ts +252 -0
  4. package/dist/IconAccountBalance-0a430d19.cjs +2 -0
  5. package/dist/IconAdd-91de604b.cjs +2 -0
  6. package/dist/IconAiDescription-674cca6d.cjs +2 -0
  7. package/dist/IconAlignSpace-7ee811f8.cjs +2 -0
  8. package/dist/IconApplePay-92395240.cjs +2 -0
  9. package/dist/IconArrowBack-3d47d166.cjs +2 -0
  10. package/dist/IconArrowForward-613e128c.cjs +2 -0
  11. package/dist/IconArrowRight-9f39cdbb.cjs +2 -0
  12. package/dist/IconAssignment-915702cc.cjs +2 -0
  13. package/dist/IconAwardStar-447cf4fc.cjs +2 -0
  14. package/dist/IconBookmark-3f6454d9.cjs +2 -0
  15. package/dist/IconCalendarMonth-bb5bbb9b.cjs +2 -0
  16. package/dist/IconCall-08f3119f.cjs +2 -0
  17. package/dist/IconCheck-910929df.cjs +2 -0
  18. package/dist/IconChevronLeft-6626777e.cjs +2 -0
  19. package/dist/IconChevronRight-91bc7ad1.cjs +2 -0
  20. package/dist/IconClear-489c5a46.cjs +2 -0
  21. package/dist/IconClose-538a540f.cjs +2 -0
  22. package/dist/IconCycle-7b7c7325.cjs +2 -0
  23. package/dist/IconDelete-5cc224a1.cjs +2 -0
  24. package/dist/IconDiamond-4e736f5f.cjs +2 -0
  25. package/dist/IconDraft-26850369.cjs +2 -0
  26. package/dist/IconEdit-b4d5f175.cjs +2 -0
  27. package/dist/IconError-4ad48be1.cjs +2 -0
  28. package/dist/IconExpandContent-03a28bfb.cjs +2 -0
  29. package/dist/IconExpandLess-59a80211.cjs +2 -0
  30. package/dist/IconExpandMore-edf55cdd.cjs +2 -0
  31. package/dist/IconFacebook-9c43095a.cjs +2 -0
  32. package/dist/IconFavorite-43091cef.cjs +2 -0
  33. package/dist/IconFavoriteFilled-71277849.cjs +2 -0
  34. package/dist/IconFormatBold-4faa5f99.cjs +2 -0
  35. package/dist/IconFormatItalic-fee490fc.cjs +2 -0
  36. package/dist/IconFormatListBulleted-d0a77d0e.cjs +2 -0
  37. package/dist/IconForum-22217aa0.cjs +2 -0
  38. package/dist/IconGavel-aacae653.cjs +2 -0
  39. package/dist/IconGoogle-77ec192e.cjs +2 -0
  40. package/dist/IconGooglePay-049e93ec.cjs +2 -0
  41. package/dist/IconGrid-1693599f.cjs +2 -0
  42. package/dist/IconHeight-d8ee424f.cjs +2 -0
  43. package/dist/IconHelp-43137a66.cjs +2 -0
  44. package/dist/IconInfo-2d14575c.cjs +2 -0
  45. package/dist/IconInsertText-14c80b9a.cjs +2 -0
  46. package/dist/IconInstagram-43bb02ea.cjs +2 -0
  47. package/dist/IconIosShare-526a016a.cjs +2 -0
  48. package/dist/IconLayout-e663120e.cjs +2 -0
  49. package/dist/IconLinkedIn-088a3792.cjs +2 -0
  50. package/dist/IconLocalShipping-9bca5a95.cjs +2 -0
  51. package/dist/IconLock-1c996287.cjs +2 -0
  52. package/dist/IconMail-f2b8fee4.cjs +2 -0
  53. package/dist/IconMenu-c7c2b143.cjs +2 -0
  54. package/dist/IconMoreHoriz-2c82da62.cjs +2 -0
  55. package/dist/IconNotifications-01f7658b.cjs +2 -0
  56. package/dist/IconOpenInNew-9032f1db.cjs +2 -0
  57. package/dist/IconPalette-269c7286.cjs +2 -0
  58. package/dist/IconPaypal-427e8667.cjs +2 -0
  59. package/dist/IconPerson-ee87961e.cjs +2 -0
  60. package/dist/IconPhotoCamera-56cb060d.cjs +2 -0
  61. package/dist/IconPinterest-87034fbf.cjs +2 -0
  62. package/dist/IconPlayArrow-80f601bf.cjs +2 -0
  63. package/dist/IconRemove-73b05132.cjs +2 -0
  64. package/dist/IconRoundedCorners-53cb2cfc.cjs +2 -0
  65. package/dist/IconRuler-5feb5a83.cjs +2 -0
  66. package/dist/IconSearch-45b2c0e5.cjs +2 -0
  67. package/dist/IconSeating-bc19b98f.cjs +2 -0
  68. package/dist/IconSell-bcede586.cjs +2 -0
  69. package/dist/IconShare-ff50a973.cjs +2 -0
  70. package/dist/IconShoppingCart-eee95744.cjs +2 -0
  71. package/dist/IconSms-0575f458.cjs +2 -0
  72. package/dist/IconStar-e2a88836.cjs +2 -0
  73. package/dist/IconSwapVert-29258e96.cjs +2 -0
  74. package/dist/IconThreeDRotation-7be89ff8.cjs +2 -0
  75. package/dist/IconTrophy-4d05eb75.cjs +2 -0
  76. package/dist/IconTune-1276696d.cjs +2 -0
  77. package/dist/IconTwitter-55b8ba63.cjs +2 -0
  78. package/dist/IconVideocam-f5b07935.cjs +2 -0
  79. package/dist/IconViewInAr-a583aff8.cjs +2 -0
  80. package/dist/IconVisibility-4984bcac.cjs +2 -0
  81. package/dist/IconVolumeOff-35d350ee.cjs +2 -0
  82. package/dist/IconVolumeOn-386ad18b.cjs +2 -0
  83. package/dist/IconWarning-54f3a9ac.cjs +2 -0
  84. package/dist/IconWeight-c6ddd367.cjs +2 -0
  85. package/dist/IconWidth-a556befc.cjs +2 -0
  86. package/dist/IconWidth-c81dbc7a.js +16 -0
  87. package/dist/IconYoutube-ed951c21.cjs +2 -0
  88. package/dist/css/grid-layout.css +6 -2
  89. package/dist/tide-design-system.cjs +2 -0
  90. package/dist/tide-design-system.esm.d.ts +1925 -0
  91. package/dist/tide-design-system.esm.js +2168 -0
  92. package/index.ts +122 -0
  93. package/package.json +8 -12
  94. package/src/assets/css/animation.css +14 -0
  95. package/src/assets/css/dynamic-buttons.css +79 -0
  96. package/src/assets/css/dynamic-utilities.css +39 -0
  97. package/src/assets/css/grid-layout.css +71 -0
  98. package/src/assets/css/main.css +5 -0
  99. package/src/assets/css/realm/aero.css +42 -0
  100. package/src/assets/css/realm/atv.css +43 -0
  101. package/src/assets/css/realm/boatmart.css +42 -0
  102. package/src/assets/css/realm/cycle.css +42 -0
  103. package/src/assets/css/realm/equip.css +42 -0
  104. package/src/assets/css/realm/pwc.css +42 -0
  105. package/src/assets/css/realm/rv.css +50 -0
  106. package/src/assets/css/realm/snow.css +42 -0
  107. package/src/assets/css/realm/truck.css +42 -0
  108. package/src/assets/css/reset.css +79 -0
  109. package/src/assets/css/storybook.css +9 -0
  110. package/src/assets/css/utilities.css +1602 -0
  111. package/src/assets/css/variables.css +118 -0
  112. package/src/assets/svg/icons/IconAccountBalance.svg +5 -0
  113. package/src/assets/svg/icons/IconAdd.svg +3 -0
  114. package/src/assets/svg/icons/IconAiDescription.svg +7 -0
  115. package/src/assets/svg/icons/IconAlignSpace.svg +3 -0
  116. package/src/assets/svg/icons/IconApplePay.svg +5 -0
  117. package/src/assets/svg/icons/IconArrowBack.svg +3 -0
  118. package/src/assets/svg/icons/IconArrowForward.svg +3 -0
  119. package/src/assets/svg/icons/IconArrowRight.svg +3 -0
  120. package/src/assets/svg/icons/IconAssignment.svg +5 -0
  121. package/src/assets/svg/icons/IconAwardStar.svg +5 -0
  122. package/src/assets/svg/icons/IconBookmark.svg +5 -0
  123. package/src/assets/svg/icons/IconCalendarMonth.svg +5 -0
  124. package/src/assets/svg/icons/IconCall.svg +5 -0
  125. package/src/assets/svg/icons/IconCheck.svg +3 -0
  126. package/src/assets/svg/icons/IconChevronLeft.svg +3 -0
  127. package/src/assets/svg/icons/IconChevronRight.svg +3 -0
  128. package/src/assets/svg/icons/IconClear.svg +5 -0
  129. package/src/assets/svg/icons/IconClose.svg +3 -0
  130. package/src/assets/svg/icons/IconCycle.svg +5 -0
  131. package/src/assets/svg/icons/IconDelete.svg +5 -0
  132. package/src/assets/svg/icons/IconDiamond.svg +5 -0
  133. package/src/assets/svg/icons/IconDraft.svg +3 -0
  134. package/src/assets/svg/icons/IconEdit.svg +5 -0
  135. package/src/assets/svg/icons/IconError.svg +5 -0
  136. package/src/assets/svg/icons/IconExpandContent.svg +3 -0
  137. package/src/assets/svg/icons/IconExpandLess.svg +3 -0
  138. package/src/assets/svg/icons/IconExpandMore.svg +3 -0
  139. package/src/assets/svg/icons/IconFacebook.svg +5 -0
  140. package/src/assets/svg/icons/IconFavorite.svg +5 -0
  141. package/src/assets/svg/icons/IconFavoriteFilled.svg +5 -0
  142. package/src/assets/svg/icons/IconFormatBold.svg +5 -0
  143. package/src/assets/svg/icons/IconFormatItalic.svg +3 -0
  144. package/src/assets/svg/icons/IconFormatListBulleted.svg +5 -0
  145. package/src/assets/svg/icons/IconForum.svg +5 -0
  146. package/src/assets/svg/icons/IconGavel.svg +5 -0
  147. package/src/assets/svg/icons/IconGoogle.svg +18 -0
  148. package/src/assets/svg/icons/IconGooglePay.svg +5 -0
  149. package/src/assets/svg/icons/IconGrid.svg +3 -0
  150. package/src/assets/svg/icons/IconHeight.svg +3 -0
  151. package/src/assets/svg/icons/IconHelp.svg +5 -0
  152. package/src/assets/svg/icons/IconInfo.svg +5 -0
  153. package/src/assets/svg/icons/IconInsertText.svg +5 -0
  154. package/src/assets/svg/icons/IconInstagram.svg +5 -0
  155. package/src/assets/svg/icons/IconIosShare.svg +5 -0
  156. package/src/assets/svg/icons/IconLayout.svg +5 -0
  157. package/src/assets/svg/icons/IconLinkedIn.svg +5 -0
  158. package/src/assets/svg/icons/IconLocalShipping.svg +5 -0
  159. package/src/assets/svg/icons/IconLock.svg +5 -0
  160. package/src/assets/svg/icons/IconMail.svg +5 -0
  161. package/src/assets/svg/icons/IconMenu.svg +3 -0
  162. package/src/assets/svg/icons/IconMoreHoriz.svg +5 -0
  163. package/src/assets/svg/icons/IconNotifications.svg +5 -0
  164. package/src/assets/svg/icons/IconOpenInNew.svg +5 -0
  165. package/src/assets/svg/icons/IconPalette.svg +5 -0
  166. package/src/assets/svg/icons/IconPaypal.svg +5 -0
  167. package/src/assets/svg/icons/IconPerson.svg +5 -0
  168. package/src/assets/svg/icons/IconPhotoCamera.svg +5 -0
  169. package/src/assets/svg/icons/IconPinterest.svg +5 -0
  170. package/src/assets/svg/icons/IconPlayArrow.svg +3 -0
  171. package/src/assets/svg/icons/IconRemove.svg +3 -0
  172. package/src/assets/svg/icons/IconRoundedCorners.svg +5 -0
  173. package/src/assets/svg/icons/IconRuler.svg +3 -0
  174. package/src/assets/svg/icons/IconSearch.svg +5 -0
  175. package/src/assets/svg/icons/IconSeating.svg +3 -0
  176. package/src/assets/svg/icons/IconSell.svg +5 -0
  177. package/src/assets/svg/icons/IconShare.svg +5 -0
  178. package/src/assets/svg/icons/IconShoppingCart.svg +5 -0
  179. package/src/assets/svg/icons/IconSms.svg +5 -0
  180. package/src/assets/svg/icons/IconStar.svg +5 -0
  181. package/src/assets/svg/icons/IconSwapVert.svg +5 -0
  182. package/src/assets/svg/icons/IconThreeDRotation.svg +18 -0
  183. package/src/assets/svg/icons/IconTrophy.svg +5 -0
  184. package/src/assets/svg/icons/IconTune.svg +5 -0
  185. package/src/assets/svg/icons/IconTwitter.svg +5 -0
  186. package/src/assets/svg/icons/IconVideocam.svg +5 -0
  187. package/src/assets/svg/icons/IconViewInAr.svg +5 -0
  188. package/src/assets/svg/icons/IconVisibility.svg +5 -0
  189. package/src/assets/svg/icons/IconVolumeOff.svg +5 -0
  190. package/src/assets/svg/icons/IconVolumeOn.svg +5 -0
  191. package/src/assets/svg/icons/IconWarning.svg +5 -0
  192. package/src/assets/svg/icons/IconWeight.svg +3 -0
  193. package/src/assets/svg/icons/IconWidth.svg +3 -0
  194. package/src/assets/svg/icons/IconYoutube.svg +5 -0
  195. package/src/components/TideAccordionItem.vue +105 -0
  196. package/src/components/TideAlert.vue +124 -0
  197. package/src/components/TideBackgroundImage.vue +44 -0
  198. package/src/components/TideBadge.vue +30 -0
  199. package/src/components/TideBadgePremium.vue +31 -0
  200. package/src/components/TideBadgeTrustedPartner.vue +38 -0
  201. package/src/components/TideBreadCrumbs.vue +53 -0
  202. package/src/components/TideButton.vue +67 -0
  203. package/src/components/TideButtonIcon.vue +52 -0
  204. package/src/components/TideButtonPagination.vue +71 -0
  205. package/src/components/TideCard.vue +19 -0
  206. package/src/components/TideCarousel.vue +188 -0
  207. package/src/components/TideChipAction.vue +48 -0
  208. package/src/components/TideChipFilter.vue +55 -0
  209. package/src/components/TideChipInput.vue +44 -0
  210. package/src/components/TideColumns.vue +53 -0
  211. package/src/components/TideDivider.vue +24 -0
  212. package/src/components/TideIcon.vue +58 -0
  213. package/src/components/TideImage.vue +44 -0
  214. package/src/components/TideIndicator.vue +57 -0
  215. package/src/components/TideInputCheckbox.vue +145 -0
  216. package/src/components/TideInputRadio.vue +87 -0
  217. package/src/components/TideInputSelect.vue +204 -0
  218. package/src/components/TideInputText.vue +292 -0
  219. package/src/components/TideInputTextarea.vue +196 -0
  220. package/src/components/TideLink.vue +66 -0
  221. package/src/components/TideModal.vue +187 -0
  222. package/src/components/TidePagination.vue +67 -0
  223. package/src/components/TideSeoLinks.vue +58 -0
  224. package/src/components/TideTabs.vue +89 -0
  225. package/src/components/TideToggle.vue +95 -0
  226. package/src/docs/development.md +51 -0
  227. package/src/docs/integration.md +79 -0
  228. package/src/docs/storybook.md +39 -0
  229. package/src/stories/DemoCssUtilities.stories.ts +154 -0
  230. package/src/stories/DemoCssUtilitiesByTextInput.stories.ts +61 -0
  231. package/src/stories/FoundationsBorder.stories.ts +230 -0
  232. package/src/stories/FoundationsGap.stories.ts +180 -0
  233. package/src/stories/FoundationsMargin.stories.ts +155 -0
  234. package/src/stories/FoundationsPadding.stories.ts +108 -0
  235. package/src/stories/FoundationsShadow.stories.ts +84 -0
  236. package/src/stories/FoundationsTypography.stories.ts +172 -0
  237. package/src/stories/Template.stories.ts +71 -0
  238. package/src/stories/TideAccordionItem.stories.ts +68 -0
  239. package/src/stories/TideAlert.stories.ts +58 -0
  240. package/src/stories/TideBackgroundImage.stories.ts +53 -0
  241. package/src/stories/TideBadge.stories.ts +31 -0
  242. package/src/stories/TideBadgePremium.stories.ts +31 -0
  243. package/src/stories/TideBadgeTrustedPartner.stories.ts +33 -0
  244. package/src/stories/TideBreadCrumbs.stories.ts +43 -0
  245. package/src/stories/TideButton.stories.ts +118 -0
  246. package/src/stories/TideButtonIcon.stories.ts +103 -0
  247. package/src/stories/TideButtonPagination.stories.ts +99 -0
  248. package/src/stories/TideCard.stories.ts +38 -0
  249. package/src/stories/TideCarousel.stories.ts +130 -0
  250. package/src/stories/TideChipAction.stories.ts +46 -0
  251. package/src/stories/TideChipFilter.stories.ts +59 -0
  252. package/src/stories/TideChipInput.stories.ts +42 -0
  253. package/src/stories/TideColumns.stories.ts +90 -0
  254. package/src/stories/TideDivider.stories.ts +46 -0
  255. package/src/stories/TideIcon.stories.ts +29 -0
  256. package/src/stories/TideIndicator.stories.ts +31 -0
  257. package/src/stories/TideInputCheckbox.stories.ts +86 -0
  258. package/src/stories/TideInputRadio.stories.ts +71 -0
  259. package/src/stories/TideInputSelect.stories.ts +145 -0
  260. package/src/stories/TideInputText.stories.ts +181 -0
  261. package/src/stories/TideInputTextarea.stories.ts +116 -0
  262. package/src/stories/TideLink.stories.ts +137 -0
  263. package/src/stories/TideModal.stories.ts +122 -0
  264. package/src/stories/TidePagination.stories.ts +83 -0
  265. package/src/stories/TideSeoLinks.stories.ts +67 -0
  266. package/src/stories/TideTabs.stories.ts +100 -0
  267. package/src/stories/TideToggle.stories.ts +69 -0
  268. package/src/stories/Welcome.mdx +17 -0
  269. package/src/utilities/format.ts +184 -0
  270. package/src/utilities/storybook.ts +295 -0
  271. package/src/utilities/validation.ts +197 -0
  272. package/tests/utilities-format.spec.ts +430 -0
  273. package/tsconfig.app.json +14 -0
  274. package/tsconfig.config.json +12 -0
  275. package/tsconfig.json +22 -0
  276. package/tsconfig.vitest.json +10 -0
  277. package/vite.config.ts +43 -0
  278. package/dist/IconWidth-023f69f2.js +0 -16
  279. package/dist/tide-design-system.js +0 -2168
  280. /package/{dist → src}/types/Alert.ts +0 -0
  281. /package/{dist → src}/types/Badge.ts +0 -0
  282. /package/{dist → src}/types/BreadCrumb.ts +0 -0
  283. /package/{dist → src}/types/Detail.ts +0 -0
  284. /package/{dist → src}/types/Element.ts +0 -0
  285. /package/{dist → src}/types/FacetRange.ts +0 -0
  286. /package/{dist → src}/types/Field.ts +0 -0
  287. /package/{dist → src}/types/Form.ts +0 -0
  288. /package/{dist → src}/types/Formatted.ts +0 -0
  289. /package/{dist → src}/types/Icon.ts +0 -0
  290. /package/{dist → src}/types/Link.ts +0 -0
  291. /package/{dist → src}/types/ListingMedia.ts +0 -0
  292. /package/{dist → src}/types/Orientation.ts +0 -0
  293. /package/{dist → src}/types/Priority.ts +0 -0
  294. /package/{dist → src}/types/Raw.ts +0 -0
  295. /package/{dist → src}/types/Realm.ts +0 -0
  296. /package/{dist → src}/types/RealmConfig.ts +0 -0
  297. /package/{dist → src}/types/Select.ts +0 -0
  298. /package/{dist → src}/types/Size.ts +0 -0
  299. /package/{dist → src}/types/Storybook.ts +0 -0
  300. /package/{dist → src}/types/Styles.ts +0 -0
  301. /package/{dist → src}/types/Tab.ts +0 -0
  302. /package/{dist → src}/types/Target.ts +0 -0
  303. /package/{dist → src}/types/TextInput.ts +0 -0
  304. /package/{dist → src}/types/Validation.ts +0 -0
@@ -0,0 +1,197 @@
1
+ import type { Ref } from 'vue';
2
+
3
+ import type { RangeData } from '@/types/FacetRange';
4
+ import type { SelectOption } from '@/types/Select';
5
+ import type { StringInput } from '@/types/Form';
6
+ import type { ValidationError, ValidationLength, ValidationResult, Validator } from '@/types/Validation';
7
+
8
+ import { priceToNumber } from '@/utilities/format';
9
+
10
+ export const errorMessageDefault = 'Please enter a valid value.';
11
+
12
+ export const noError = {
13
+ message: '',
14
+ valid: true,
15
+ } as Readonly<ValidationResult>;
16
+
17
+ export const checkFormat = (format: RegExp) => {
18
+ return (value: string): ValidationResult => {
19
+ let result = noError;
20
+
21
+ if (!value.trim().match(format)) {
22
+ result = {
23
+ message: errorMessageDefault,
24
+ valid: false,
25
+ };
26
+ }
27
+
28
+ return result;
29
+ };
30
+ };
31
+
32
+ export const getErrorMessage = (errorFromProps: ValidationError, errorFromRef: ValidationError) => {
33
+ // error in props takes precedence over validation error
34
+ if (typeof errorFromProps === 'string' && errorFromProps.length > 0) return errorFromProps;
35
+
36
+ return typeof errorFromRef === 'string' && errorFromRef.length > 0 ? errorFromRef : errorMessageDefault;
37
+ };
38
+
39
+ export const getFieldHasError = (errorFromProps: ValidationError, errorFromRef: ValidationError) =>
40
+ errorFromProps !== false || errorFromRef !== false;
41
+
42
+ export const getFieldLengthIsValid = ({ maxlength, minlength, value }: ValidationLength) => {
43
+ const tooShort = maxlength && value.length > maxlength;
44
+ const tooLong = minlength && value.length < minlength;
45
+
46
+ return !tooShort && !tooLong;
47
+ };
48
+
49
+ export const getMaxRangeIsValid = ({ min }: Pick<RangeData, 'min'>, type?: 'price') => {
50
+ return (value: string): ValidationResult => {
51
+ let newMax: number | null = type === 'price' ? priceToNumber(value) : Number(value);
52
+ newMax = !isNaN(newMax) ? newMax : null;
53
+ if (min && newMax) {
54
+ if (newMax >= min) {
55
+ return {
56
+ message: '',
57
+ valid: true,
58
+ };
59
+ } else {
60
+ return {
61
+ message: `Must be greater than min`,
62
+ valid: false,
63
+ };
64
+ }
65
+ } else {
66
+ return noError;
67
+ }
68
+ };
69
+ };
70
+
71
+ export const getMinRangeIsValid = ({ max }: Pick<RangeData, 'max'>, type?: 'price') => {
72
+ return (value: string): ValidationResult => {
73
+ let newMin: number | null = type === 'price' ? priceToNumber(value) : Number(value);
74
+ newMin = !isNaN(newMin) ? newMin : null;
75
+ if (max && newMin) {
76
+ if (newMin <= max) {
77
+ return {
78
+ message: '',
79
+ valid: true,
80
+ };
81
+ } else {
82
+ return {
83
+ message: `Must be less than max`,
84
+ valid: false,
85
+ };
86
+ }
87
+ } else {
88
+ return noError;
89
+ }
90
+ };
91
+ };
92
+
93
+ export const getSelectOptionsFromStrings = (strings: string[]) =>
94
+ strings.map(
95
+ (option) =>
96
+ ({
97
+ label: option,
98
+ value: option,
99
+ } as SelectOption)
100
+ );
101
+
102
+ export const handleFieldValidation = ({
103
+ error,
104
+ errorFromProps,
105
+ maxlength,
106
+ minlength,
107
+ validators,
108
+ value,
109
+ }: {
110
+ error: Ref<ValidationError>;
111
+ errorFromProps: ValidationError;
112
+ maxlength?: number;
113
+ minlength?: number;
114
+ validators?: Validator[];
115
+ value: Ref<string>;
116
+ }) => {
117
+ // error in props takes precedence over validation error
118
+
119
+ error.value = errorFromProps ? errorFromProps : false;
120
+
121
+ if (!error.value && validators) {
122
+ const validation = validateProperty(value.value, validators);
123
+
124
+ if (!validation.valid) {
125
+ error.value = validation.message;
126
+ }
127
+ }
128
+
129
+ if (!error.value && (maxlength || minlength)) {
130
+ const lengthvalidation = validateLength({
131
+ maxlength,
132
+ minlength,
133
+ value: value.value,
134
+ });
135
+
136
+ if (!lengthvalidation.valid) {
137
+ error.value = lengthvalidation.message;
138
+ }
139
+ }
140
+ };
141
+
142
+ export function validateFieldsFromRefs(fields: { [key: string]: Ref<StringInput | null> }) {
143
+ let valid = true;
144
+
145
+ for (const key in fields) {
146
+ if (fields[key].value?.required) {
147
+ const value = fields[key].value?.value;
148
+ valid = valid && !!value && value.trim() !== '';
149
+ }
150
+
151
+ const error = fields[key].value?.error;
152
+ valid = valid && !error;
153
+ }
154
+
155
+ return valid;
156
+ }
157
+
158
+ export const validateLength = ({ maxlength, minlength, value }: ValidationLength): ValidationResult => {
159
+ const response = {
160
+ message: '',
161
+ valid: true,
162
+ };
163
+
164
+ response.valid = getFieldLengthIsValid({
165
+ maxlength,
166
+ minlength,
167
+ value,
168
+ });
169
+
170
+ if (response.valid) return response;
171
+
172
+ response.message = errorMessageDefault;
173
+
174
+ if (maxlength && minlength) {
175
+ response.message = `Please enter a value between ${minlength} and ${maxlength} characters in length.`;
176
+ } else if (maxlength) {
177
+ response.message = `Please enter a value no more than ${maxlength} characters in length.`;
178
+ } else if (minlength) {
179
+ response.message = `Please enter a value no less than ${minlength} characters in length.`;
180
+ }
181
+
182
+ return response;
183
+ };
184
+
185
+ export function validateProperty(value: string, validators: ((value: string) => ValidationResult)[]): ValidationResult {
186
+ for (const validator of validators) {
187
+ const validation = validator(value);
188
+ if (!validation.valid) {
189
+ return validation;
190
+ }
191
+ }
192
+
193
+ return {
194
+ message: '',
195
+ valid: true,
196
+ };
197
+ }
@@ -0,0 +1,430 @@
1
+ import { describe, expect, it } from 'vitest';
2
+
3
+ import {
4
+ formatCamelCase,
5
+ formatKebabCase,
6
+ formatNumber,
7
+ formatPascalCase,
8
+ formatPhone,
9
+ formatPrice,
10
+ formatSentenceCase,
11
+ formatSnakeCase,
12
+ formatTitleCase,
13
+ } from '../src/utilities/format';
14
+
15
+ describe('@/src/utilities/format.ts', () => {
16
+ const camelCase = 'theQuickBrownFoxJumpsOverTheLazyDog';
17
+ const kebabCase = 'the-quick-brown-fox-jumps-over-the-lazy-dog';
18
+ const lowerCase = 'the quick brown fox jumps over the lazy dog';
19
+ const pascalCase = 'TheQuickBrownFoxJumpsOverTheLazyDog';
20
+ const sentenceCase = 'The quick brown fox jumps over the lazy dog';
21
+ const snakeCase = 'the_quick_brown_fox_jumps_over_the_lazy_dog';
22
+ const titleCase = 'The Quick Brown Fox Jumps Over The Lazy Dog';
23
+ const upperCase = 'THE QUICK BROWN FOX JUMPS OVER THE LAZY DOG';
24
+
25
+ describe('formatCamelCase', () => {
26
+ it('converts camelCase to camelCase.', () => {
27
+ expect(formatCamelCase(camelCase)).toEqual(camelCase);
28
+ });
29
+
30
+ it('converts kebab-case to camelCase.', () => {
31
+ expect(formatCamelCase(kebabCase)).toEqual(camelCase);
32
+ });
33
+
34
+ it('converts PascalCase to camelCase.', () => {
35
+ expect(formatCamelCase(pascalCase)).toEqual(camelCase);
36
+ });
37
+
38
+ it('converts lower case to camelCase.', () => {
39
+ expect(formatCamelCase(lowerCase)).toEqual(camelCase);
40
+ });
41
+
42
+ it('converts "Sentence case" to camelCase.', () => {
43
+ expect(formatCamelCase(sentenceCase)).toEqual(camelCase);
44
+ });
45
+
46
+ it('converts snake_case to camelCase.', () => {
47
+ expect(formatCamelCase(snakeCase)).toEqual(camelCase);
48
+ });
49
+
50
+ it('converts "Title Case" to camelCase.', () => {
51
+ expect(formatCamelCase(titleCase)).toEqual(camelCase);
52
+ });
53
+
54
+ it('converts UPPER CASE to camelCase.', () => {
55
+ expect(formatCamelCase(upperCase)).toEqual(camelCase);
56
+ });
57
+
58
+ it('ignores an empty string.', () => {
59
+ const input = '';
60
+ const output = '';
61
+
62
+ expect(formatCamelCase(input)).toEqual(output);
63
+ });
64
+ });
65
+
66
+ describe('formatKebabCase', () => {
67
+ it('converts camelCase to kebab-case.', () => {
68
+ expect(formatKebabCase(camelCase)).toEqual(kebabCase);
69
+ });
70
+
71
+ it('converts kebab-case to kebab-case.', () => {
72
+ expect(formatKebabCase(kebabCase)).toEqual(kebabCase);
73
+ });
74
+
75
+ it('converts PascalCase to kebab-case.', () => {
76
+ expect(formatKebabCase(pascalCase)).toEqual(kebabCase);
77
+ });
78
+
79
+ it('converts lower case to kebab-case.', () => {
80
+ expect(formatKebabCase(lowerCase)).toEqual(kebabCase);
81
+ });
82
+
83
+ it('converts "Sentence case" to kebab-case.', () => {
84
+ expect(formatKebabCase(sentenceCase)).toEqual(kebabCase);
85
+ });
86
+
87
+ it('converts snake_case to kebab-case.', () => {
88
+ expect(formatKebabCase(snakeCase)).toEqual(kebabCase);
89
+ });
90
+
91
+ it('converts "Title Case" to kebab-case.', () => {
92
+ expect(formatKebabCase(titleCase)).toEqual(kebabCase);
93
+ });
94
+
95
+ it('converts UPPER CASE to kebab-case.', () => {
96
+ expect(formatKebabCase(upperCase)).toEqual(kebabCase);
97
+ });
98
+
99
+ it('ignores an empty string.', () => {
100
+ const input = '';
101
+ const output = '';
102
+
103
+ expect(formatKebabCase(input)).toEqual(output);
104
+ });
105
+ });
106
+
107
+ describe('formatNumber', () => {
108
+ it('formats a 1 digit number.', () => {
109
+ const input = 1;
110
+ const output = '1';
111
+
112
+ expect(formatNumber(input)).toEqual(output);
113
+ });
114
+
115
+ it('formats a 2 digit number.', () => {
116
+ const input = 22;
117
+ const output = '22';
118
+
119
+ expect(formatNumber(input)).toEqual(output);
120
+ });
121
+
122
+ it('formats a 3 digit number.', () => {
123
+ const input = 333;
124
+ const output = '333';
125
+
126
+ expect(formatNumber(input)).toEqual(output);
127
+ });
128
+
129
+ it('formats a 4 digit number.', () => {
130
+ const input = 4444;
131
+ const output = '4,444';
132
+
133
+ expect(formatNumber(input)).toEqual(output);
134
+ });
135
+
136
+ it('formats a 5 digit number.', () => {
137
+ const input = 55555;
138
+ const output = '55,555';
139
+
140
+ expect(formatNumber(input)).toEqual(output);
141
+ });
142
+
143
+ it('formats a 6 digit number.', () => {
144
+ const input = 666666;
145
+ const output = '666,666';
146
+
147
+ expect(formatNumber(input)).toEqual(output);
148
+ });
149
+
150
+ it('formats a 7 digit number.', () => {
151
+ const input = 7777777;
152
+ const output = '7,777,777';
153
+
154
+ expect(formatNumber(input)).toEqual(output);
155
+ });
156
+
157
+ it('ignores an empty string.', () => {
158
+ const input = '';
159
+ const output = '';
160
+
161
+ expect(formatNumber(input)).toEqual(output);
162
+ });
163
+ });
164
+
165
+ describe('formatPascalCase', () => {
166
+ it('converts camelCase to PascalCase.', () => {
167
+ expect(formatPascalCase(camelCase)).toEqual(pascalCase);
168
+ });
169
+
170
+ it('converts kebab-case to PascalCase.', () => {
171
+ expect(formatPascalCase(kebabCase)).toEqual(pascalCase);
172
+ });
173
+
174
+ it('converts PascalCase to PascalCase.', () => {
175
+ expect(formatPascalCase(pascalCase)).toEqual(pascalCase);
176
+ });
177
+
178
+ it('converts lower case to PascalCase.', () => {
179
+ expect(formatPascalCase(lowerCase)).toEqual(pascalCase);
180
+ });
181
+
182
+ it('converts "Sentence case" to PascalCase.', () => {
183
+ expect(formatPascalCase(sentenceCase)).toEqual(pascalCase);
184
+ });
185
+
186
+ it('converts snake_case to PascalCase.', () => {
187
+ expect(formatPascalCase(snakeCase)).toEqual(pascalCase);
188
+ });
189
+
190
+ it('converts "Title Case" to PascalCase.', () => {
191
+ expect(formatPascalCase(titleCase)).toEqual(pascalCase);
192
+ });
193
+
194
+ it('converts UPPER CASE to PascalCase.', () => {
195
+ expect(formatPascalCase(upperCase)).toEqual(pascalCase);
196
+ });
197
+
198
+ it('ignores an empty string.', () => {
199
+ const input = '';
200
+ const output = '';
201
+
202
+ expect(formatPascalCase(input)).toEqual(output);
203
+ });
204
+ });
205
+
206
+ describe('formatPhone', () => {
207
+ it('formats a 7 digit price.', () => {
208
+ const input = 1234567;
209
+ const output = '123-4567';
210
+
211
+ expect(formatPhone(input)).toEqual(output);
212
+ });
213
+
214
+ it('formats a 10 digit phone number.', () => {
215
+ const input = 1234567890;
216
+ const output = '123-456-7890';
217
+
218
+ expect(formatPhone(input)).toEqual(output);
219
+ });
220
+
221
+ it('formats a 11 digit phone number.', () => {
222
+ const input = 12345678900;
223
+ const output = '1-234-567-8900';
224
+
225
+ expect(formatPhone(input)).toEqual(output);
226
+ });
227
+
228
+ it('ignores a phone number of less than 7 digits.', () => {
229
+ const input = 123456;
230
+ const output = '123456';
231
+
232
+ expect(formatPhone(input)).toEqual(output);
233
+ });
234
+
235
+ it('ignores a phone number of more than 11 digits.', () => {
236
+ const input = 123456789000;
237
+ const output = '123456789000';
238
+
239
+ expect(formatPhone(input)).toEqual(output);
240
+ });
241
+
242
+ it('ignores an empty string.', () => {
243
+ const input = '';
244
+ const output = '';
245
+
246
+ expect(formatPhone(input)).toEqual(output);
247
+ });
248
+ });
249
+
250
+ describe('formatPrice', () => {
251
+ it('formats a 1 digit price.', () => {
252
+ const input = 1;
253
+ const output = '$1';
254
+
255
+ expect(formatPrice(input)).toEqual(output);
256
+ });
257
+
258
+ it('formats a 2 digit price.', () => {
259
+ const input = 12;
260
+ const output = '$12';
261
+
262
+ expect(formatPrice(input)).toEqual(output);
263
+ });
264
+
265
+ it('formats a 3 digit price.', () => {
266
+ const input = 123;
267
+ const output = '$123';
268
+
269
+ expect(formatPrice(input)).toEqual(output);
270
+ });
271
+
272
+ it('formats a 4 digit price.', () => {
273
+ const input = 1234;
274
+ const output = '$1,234';
275
+
276
+ expect(formatPrice(input)).toEqual(output);
277
+ });
278
+
279
+ it('formats a 5 digit price.', () => {
280
+ const input = 12345;
281
+ const output = '$12,345';
282
+
283
+ expect(formatPrice(input)).toEqual(output);
284
+ });
285
+
286
+ it('formats a 6 digit price.', () => {
287
+ const input = 123456;
288
+ const output = '$123,456';
289
+
290
+ expect(formatPrice(input)).toEqual(output);
291
+ });
292
+
293
+ it('formats a 7 digit price.', () => {
294
+ const input = 1234567;
295
+ const output = '$1,234,567';
296
+
297
+ expect(formatPrice(input)).toEqual(output);
298
+ });
299
+
300
+ it('ignores an empty string.', () => {
301
+ const input = '';
302
+ const output = '$--';
303
+
304
+ expect(formatPrice(input)).toEqual(output);
305
+ });
306
+ });
307
+
308
+ describe('formatSentenceCase', () => {
309
+ it('converts camelCase to "Sentence case".', () => {
310
+ expect(formatSentenceCase(camelCase)).toEqual(sentenceCase);
311
+ });
312
+
313
+ it('converts kebab-case to "Sentence case".', () => {
314
+ expect(formatSentenceCase(kebabCase)).toEqual(sentenceCase);
315
+ });
316
+
317
+ it('converts PascalCase to "Sentence case".', () => {
318
+ expect(formatSentenceCase(pascalCase)).toEqual(sentenceCase);
319
+ });
320
+
321
+ it('converts lower case to "Sentence case".', () => {
322
+ expect(formatSentenceCase(lowerCase)).toEqual(sentenceCase);
323
+ });
324
+
325
+ it('converts "Sentence case" to "Sentence case".', () => {
326
+ expect(formatSentenceCase(sentenceCase)).toEqual(sentenceCase);
327
+ });
328
+
329
+ it('converts snake_case to "Sentence case".', () => {
330
+ expect(formatSentenceCase(snakeCase)).toEqual(sentenceCase);
331
+ });
332
+
333
+ it('converts "Title Case" to "Sentence case".', () => {
334
+ expect(formatSentenceCase(titleCase)).toEqual(sentenceCase);
335
+ });
336
+
337
+ it('converts UPPER CASE to "Sentence case".', () => {
338
+ expect(formatSentenceCase(upperCase)).toEqual(sentenceCase);
339
+ });
340
+
341
+ it('ignores an empty string.', () => {
342
+ const input = '';
343
+ const output = '';
344
+
345
+ expect(formatSentenceCase(input)).toEqual(output);
346
+ });
347
+ });
348
+
349
+ describe('formatSnakeCase', () => {
350
+ it('converts camelCase to snake_case.', () => {
351
+ expect(formatSnakeCase(camelCase)).toEqual(snakeCase);
352
+ });
353
+
354
+ it('converts kebab-case to snake_case.', () => {
355
+ expect(formatSnakeCase(kebabCase)).toEqual(snakeCase);
356
+ });
357
+
358
+ it('converts PascalCase to snake_case.', () => {
359
+ expect(formatSnakeCase(pascalCase)).toEqual(snakeCase);
360
+ });
361
+
362
+ it('converts lower case to snake_case.', () => {
363
+ expect(formatSnakeCase(lowerCase)).toEqual(snakeCase);
364
+ });
365
+
366
+ it('converts "Sentence case" to snake_case.', () => {
367
+ expect(formatSnakeCase(sentenceCase)).toEqual(snakeCase);
368
+ });
369
+
370
+ it('converts snake_case to snake_case.', () => {
371
+ expect(formatSnakeCase(snakeCase)).toEqual(snakeCase);
372
+ });
373
+
374
+ it('converts "Title Case" to snake_case.', () => {
375
+ expect(formatSnakeCase(titleCase)).toEqual(snakeCase);
376
+ });
377
+
378
+ it('converts UPPER CASE to snake_case.', () => {
379
+ expect(formatSnakeCase(upperCase)).toEqual(snakeCase);
380
+ });
381
+
382
+ it('ignores an empty string.', () => {
383
+ const input = '';
384
+ const output = '';
385
+
386
+ expect(formatSnakeCase(input)).toEqual(output);
387
+ });
388
+ });
389
+
390
+ describe('formatTitleCase', () => {
391
+ it('converts camelCase to "Title Case".', () => {
392
+ expect(formatTitleCase(camelCase)).toEqual(titleCase);
393
+ });
394
+
395
+ it('converts kebab-case to "Title Case".', () => {
396
+ expect(formatTitleCase(kebabCase)).toEqual(titleCase);
397
+ });
398
+
399
+ it('converts PascalCase to "Title Case".', () => {
400
+ expect(formatTitleCase(pascalCase)).toEqual(titleCase);
401
+ });
402
+
403
+ it('converts lower case to "Title Case".', () => {
404
+ expect(formatTitleCase(lowerCase)).toEqual(titleCase);
405
+ });
406
+
407
+ it('converts "Sentence case" to "Title Case".', () => {
408
+ expect(formatTitleCase(sentenceCase)).toEqual(titleCase);
409
+ });
410
+
411
+ it('converts snake_case to "Title Case".', () => {
412
+ expect(formatTitleCase(snakeCase)).toEqual(titleCase);
413
+ });
414
+
415
+ it('converts "Title Case" to "Title Case".', () => {
416
+ expect(formatTitleCase(titleCase)).toEqual(titleCase);
417
+ });
418
+
419
+ it('converts UPPER CASE to "Title Case".', () => {
420
+ expect(formatTitleCase(upperCase)).toEqual(titleCase);
421
+ });
422
+
423
+ it('ignores an empty string.', () => {
424
+ const input = '';
425
+ const output = '';
426
+
427
+ expect(formatTitleCase(input)).toEqual(output);
428
+ });
429
+ });
430
+ });
@@ -0,0 +1,14 @@
1
+ {
2
+ "declaration": true,
3
+ "extends": "@vue/tsconfig/tsconfig.dom.json",
4
+ "include": ["src/**/*"],
5
+ "exclude": ["src/**/__tests__/*"],
6
+ "compilerOptions": {
7
+ "baseUrl": ".",
8
+ "composite": true,
9
+ "module": "esnext",
10
+ "paths": {
11
+ "@/*": ["./src/*"]
12
+ }
13
+ }
14
+ }
@@ -0,0 +1,12 @@
1
+ {
2
+ "declaration": true,
3
+ "extends": [
4
+ "@tsconfig/node18/tsconfig.json",
5
+ "@vue/tsconfig/tsconfig.json"
6
+ ],
7
+ "include": ["vite.config.*", "vitest.config.*", "cypress.config.*", "playwright.config.*"],
8
+ "compilerOptions": {
9
+ "composite": true,
10
+ "types": ["node"]
11
+ }
12
+ }
package/tsconfig.json ADDED
@@ -0,0 +1,22 @@
1
+ {
2
+ "extends": "@vue/tsconfig/tsconfig.dom.json",
3
+ "include": ["env.d.ts", "src/**/*", "src/**/*.vue", "*.js", "*.ts"],
4
+ "exclude": ["dist", ".storybook"],
5
+ "compilerOptions": {
6
+ "declaration": true,
7
+ "allowJs": true,
8
+ "baseUrl": ".",
9
+ "lib": ["dom", "es2022"],
10
+ "module": "esnext",
11
+ "paths": {
12
+ "@/*": ["./src/*"]
13
+ },
14
+ "target": "esnext",
15
+ },
16
+
17
+ "references": [
18
+ {
19
+ "path": "./tsconfig.config.json"
20
+ }
21
+ ]
22
+ }
@@ -0,0 +1,10 @@
1
+ {
2
+ "extends": "./tsconfig.app.json",
3
+ "exclude": [],
4
+ "compilerOptions": {
5
+ "composite": true,
6
+ "lib": [],
7
+ "module": "esnext",
8
+ "types": ["node", "jsdom"]
9
+ }
10
+ }