tide-design-system 2.5.2 → 2.5.3
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/style.css +1 -1
- package/dist/tide-design-system.cjs +2 -2
- package/dist/tide-design-system.esm.d.ts +2 -0
- package/dist/tide-design-system.esm.js +1370 -1332
- package/dist/utilities/validation.ts +1 -1
- package/package.json +1 -1
- package/src/components/TideCarousel.vue +104 -40
- package/src/components/TideLink.vue +6 -0
- package/src/components/TideMenuItem.vue +1 -1
- package/src/stories/TideCarousel.stories.ts +47 -25
- package/src/types/Formatted.ts +1 -1
- package/src/utilities/validation.ts +1 -1
- package/tests/utilities-format.spec.ts +40 -0
|
@@ -62,7 +62,7 @@ export const getFieldValidationResult = ({
|
|
|
62
62
|
|
|
63
63
|
// custom validator prop errors from have second highest precedence
|
|
64
64
|
if (validators) {
|
|
65
|
-
const validation = validateProperty(value.value, validators);
|
|
65
|
+
const validation = validateProperty(value.value ?? '', validators);
|
|
66
66
|
|
|
67
67
|
if (!validation.valid) {
|
|
68
68
|
return validation;
|
package/package.json
CHANGED
|
@@ -34,6 +34,7 @@
|
|
|
34
34
|
|
|
35
35
|
const emit = defineEmits<Emits>();
|
|
36
36
|
|
|
37
|
+
const carouselRef = ref<HTMLDivElement | null>(null);
|
|
37
38
|
const containerRef = ref<HTMLDivElement | null>(null);
|
|
38
39
|
const contentWidth = ref<number>(0);
|
|
39
40
|
const currentPageIndex = ref<number>(0);
|
|
@@ -46,26 +47,25 @@
|
|
|
46
47
|
const slides = ref<HTMLElement[]>([]);
|
|
47
48
|
const slidesInView = ref<number[]>([]);
|
|
48
49
|
const slotObserver = ref<MutationObserver | null>(null);
|
|
50
|
+
const touchStart = ref<Touch | undefined>(undefined);
|
|
51
|
+
const slidesInViewCount = ref<number>(1);
|
|
49
52
|
|
|
50
53
|
const currentPage = computed(() => currentPageIndex.value + 1);
|
|
51
54
|
const dotContainerWidth = computed(() => dotCountVisible.value * dotWidth + (dotCountVisible.value - 1) * dotGap);
|
|
52
55
|
const dotCountVisible = computed(() => (props.maxDots > totalPages.value ? totalPages.value : props.maxDots));
|
|
53
|
-
const lastPageIndex = computed(() => totalPages.value - 1);
|
|
54
56
|
const totalPages = computed(() => {
|
|
55
57
|
if (!slides.value.length) return 0;
|
|
56
58
|
if (!props.isScrollByPage) return slides.value.length;
|
|
57
59
|
|
|
58
|
-
const
|
|
59
|
-
const
|
|
60
|
-
const quotient = Math.round(contentNoGap / frameWidth.value);
|
|
61
|
-
const remainder = contentNoGap % frameWidth.value;
|
|
60
|
+
const quotient = Math.floor(slides.value.length / slidesInViewCount.value);
|
|
61
|
+
const remainder = slides.value.length % slidesInViewCount.value;
|
|
62
62
|
|
|
63
63
|
return remainder ? quotient + 1 : quotient;
|
|
64
64
|
});
|
|
65
65
|
|
|
66
|
-
const cardGap: number = 16;
|
|
67
66
|
const dotGap: number = 8;
|
|
68
67
|
const dotWidth: number = 8;
|
|
68
|
+
let scrollTimeout: number | undefined;
|
|
69
69
|
|
|
70
70
|
const getDotClass = (dotIndex: number) => {
|
|
71
71
|
let className = '';
|
|
@@ -76,17 +76,64 @@
|
|
|
76
76
|
return className;
|
|
77
77
|
};
|
|
78
78
|
|
|
79
|
+
const getIsElementWithinContainer = (element: HTMLElement, container: HTMLElement) => {
|
|
80
|
+
const containerRect = container.getBoundingClientRect();
|
|
81
|
+
const containerRight = containerRect.left + containerRect.width;
|
|
82
|
+
const elementRect = element.getBoundingClientRect();
|
|
83
|
+
const elementRight = elementRect.left + elementRect.width;
|
|
84
|
+
|
|
85
|
+
return elementRight <= containerRight && elementRect.left >= containerRect.left;
|
|
86
|
+
};
|
|
87
|
+
|
|
88
|
+
const handleTouchEnd = (event: TouchEvent) => {
|
|
89
|
+
if (!touchStart.value) return;
|
|
90
|
+
|
|
91
|
+
const touchEnd = event.changedTouches[0];
|
|
92
|
+
const deltaX = touchStart.value.clientX - touchEnd.clientX;
|
|
93
|
+
const deltaY = touchEnd.clientY - touchStart.value.clientY;
|
|
94
|
+
|
|
95
|
+
if (Math.abs(deltaX) > Math.abs(deltaY)) scrollByDelta(deltaX);
|
|
96
|
+
};
|
|
97
|
+
|
|
98
|
+
const handleTouchStart = (event: TouchEvent) => {
|
|
99
|
+
touchStart.value = event.touches[0];
|
|
100
|
+
};
|
|
101
|
+
|
|
102
|
+
const handleWheel = (event: WheelEvent) => {
|
|
103
|
+
const isShiftKeyDown = event.shiftKey;
|
|
104
|
+
const isWheel = Math.abs(event.deltaY) >= 80;
|
|
105
|
+
|
|
106
|
+
if (isWheel && !isShiftKeyDown) return;
|
|
107
|
+
if (event.shiftKey) event.preventDefault();
|
|
108
|
+
|
|
109
|
+
clearTimeout(scrollTimeout);
|
|
110
|
+
|
|
111
|
+
scrollTimeout = window.setTimeout(() => {
|
|
112
|
+
if (isWheel) {
|
|
113
|
+
scrollByDelta(event.deltaY);
|
|
114
|
+
} else {
|
|
115
|
+
const offset = slides.value[slidesInView.value[0]]?.offsetLeft || 0;
|
|
116
|
+
|
|
117
|
+
scrollToOffset(offset);
|
|
118
|
+
}
|
|
119
|
+
}, 100);
|
|
120
|
+
};
|
|
121
|
+
|
|
79
122
|
const measureDom = () => {
|
|
80
123
|
if (!containerRef.value) return;
|
|
81
124
|
|
|
82
125
|
contentWidth.value = containerRef.value.scrollWidth;
|
|
83
126
|
frameWidth.value = containerRef.value.clientWidth;
|
|
84
127
|
showButtons.value = contentWidth.value > frameWidth.value;
|
|
128
|
+
|
|
129
|
+
slidesInViewCount.value = slides.value.filter((slide) =>
|
|
130
|
+
getIsElementWithinContainer(slide, containerRef.value as HTMLElement)
|
|
131
|
+
).length;
|
|
85
132
|
};
|
|
86
133
|
|
|
87
134
|
const observeSlides = () => {
|
|
88
135
|
const options = {
|
|
89
|
-
root:
|
|
136
|
+
root: carouselRef?.value,
|
|
90
137
|
rootMargin: '0px 1px 0px 0px',
|
|
91
138
|
threshold: 1,
|
|
92
139
|
};
|
|
@@ -131,12 +178,20 @@
|
|
|
131
178
|
if (containerRef.value) slotObserver.value.observe(containerRef.value, { childList: true });
|
|
132
179
|
};
|
|
133
180
|
|
|
181
|
+
const scrollByDelta = (delta: number) => {
|
|
182
|
+
const isScrollingLeft = delta < 0;
|
|
183
|
+
|
|
184
|
+
if (isScrollingLeft) {
|
|
185
|
+
props.isScrollByPage ? showPreviousPage() : showPreviousSlide();
|
|
186
|
+
} else {
|
|
187
|
+
props.isScrollByPage ? showNextPage() : showNextSlide();
|
|
188
|
+
}
|
|
189
|
+
};
|
|
190
|
+
|
|
134
191
|
const scrollToOffset = (target: number) => {
|
|
135
192
|
if (containerRef.value === null) return;
|
|
136
193
|
|
|
137
|
-
const
|
|
138
|
-
const placement = (target / lastOffset) * lastPageIndex.value;
|
|
139
|
-
const isScrollingLeft = placement <= currentPageIndex.value;
|
|
194
|
+
const isScrollingLeft = target <= containerRef.value.scrollLeft;
|
|
140
195
|
|
|
141
196
|
currentPageIndex.value = isScrollingLeft ? currentPageIndex.value - 1 : currentPageIndex.value + 1;
|
|
142
197
|
|
|
@@ -148,19 +203,21 @@
|
|
|
148
203
|
};
|
|
149
204
|
|
|
150
205
|
const showNextPage = () => {
|
|
151
|
-
if (slidesInView.value.length === 0) return;
|
|
206
|
+
if (slidesInView.value.length === 0 || isLastSlide.value) return;
|
|
152
207
|
|
|
153
208
|
const nextSlide: number = slidesInView.value[slidesInView.value.length - 1] + 1;
|
|
209
|
+
const offset = slides.value[nextSlide]?.offsetLeft;
|
|
154
210
|
|
|
155
|
-
scrollToOffset(
|
|
211
|
+
scrollToOffset(offset);
|
|
156
212
|
};
|
|
157
213
|
|
|
158
214
|
const showPreviousPage = () => {
|
|
159
|
-
if (slidesInView.value.length === 0) return;
|
|
215
|
+
if (slidesInView.value.length === 0 || isFirstSlide.value) return;
|
|
160
216
|
|
|
161
217
|
const previousSlide: number = slidesInView.value[0] - slidesInView.value.length;
|
|
218
|
+
const offset = slides.value[previousSlide]?.offsetLeft || 0;
|
|
162
219
|
|
|
163
|
-
scrollToOffset(
|
|
220
|
+
scrollToOffset(offset);
|
|
164
221
|
};
|
|
165
222
|
|
|
166
223
|
const showNextSlide = () => {
|
|
@@ -284,51 +341,58 @@
|
|
|
284
341
|
CSS.SNAP.ON,
|
|
285
342
|
]"
|
|
286
343
|
ref="containerRef"
|
|
344
|
+
@touchend="handleTouchEnd"
|
|
345
|
+
@touchstart.passive="handleTouchStart"
|
|
346
|
+
@wheel="handleWheel"
|
|
287
347
|
>
|
|
288
348
|
<slot />
|
|
289
349
|
</ul>
|
|
290
350
|
|
|
291
351
|
<div
|
|
292
352
|
:class="[
|
|
293
|
-
'tide-carousel-dots-container',
|
|
294
353
|
CSS.POSITION.ABSOLUTE,
|
|
295
354
|
CSS.POSITIONING.BOTTOM,
|
|
296
355
|
CSS.DISPLAY.FLEX,
|
|
297
356
|
CSS.AXIS1.CENTER,
|
|
298
357
|
CSS.MARGIN.BOTTOM.HALF,
|
|
299
358
|
CSS.WIDTH.FULL,
|
|
359
|
+
CSS.POINTER_EVENTS.OFF,
|
|
300
360
|
]"
|
|
301
|
-
|
|
302
|
-
width: `${dotContainerWidth}px`,
|
|
303
|
-
}"
|
|
304
|
-
v-if="isScrollByPage && hasDots && totalPages > 1"
|
|
361
|
+
v-if="hasDots && totalPages > 1"
|
|
305
362
|
>
|
|
306
363
|
<div
|
|
307
|
-
:class="[
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
CSS.AXIS2.CENTER,
|
|
312
|
-
CSS.GAP.HALF,
|
|
313
|
-
CSS.OVERFLOW.X.SCROLL,
|
|
314
|
-
CSS.POINTER_EVENTS.OFF,
|
|
315
|
-
CSS.SCROLLBAR.OFF,
|
|
316
|
-
]"
|
|
317
|
-
ref="dotsRef"
|
|
364
|
+
:class="['tide-carousel-dots-container']"
|
|
365
|
+
:style="{
|
|
366
|
+
width: `${dotContainerWidth}px`,
|
|
367
|
+
}"
|
|
318
368
|
>
|
|
319
369
|
<div
|
|
320
370
|
:class="[
|
|
321
|
-
'tide-carousel-
|
|
322
|
-
CSS.FLEX
|
|
323
|
-
CSS.
|
|
324
|
-
CSS.
|
|
325
|
-
CSS.
|
|
326
|
-
CSS.
|
|
327
|
-
|
|
371
|
+
'tide-carousel-dots',
|
|
372
|
+
CSS.DISPLAY.FLEX,
|
|
373
|
+
CSS.AXIS1.START,
|
|
374
|
+
CSS.AXIS2.CENTER,
|
|
375
|
+
CSS.GAP.HALF,
|
|
376
|
+
CSS.OVERFLOW.X.SCROLL,
|
|
377
|
+
CSS.POINTER_EVENTS.OFF,
|
|
378
|
+
CSS.SCROLLBAR.OFF,
|
|
328
379
|
]"
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
380
|
+
ref="dotsRef"
|
|
381
|
+
>
|
|
382
|
+
<div
|
|
383
|
+
:class="[
|
|
384
|
+
'tide-carousel-dot',
|
|
385
|
+
CSS.FLEX.SHRINK.OFF,
|
|
386
|
+
CSS.HEIGHT.ZERO,
|
|
387
|
+
CSS.WIDTH.ZERO,
|
|
388
|
+
CSS.BORDER.RADIUS.FULL,
|
|
389
|
+
CSS.BG.SURFACE.DEFAULT,
|
|
390
|
+
getDotClass(index),
|
|
391
|
+
]"
|
|
392
|
+
:key="index"
|
|
393
|
+
v-for="(_, index) in totalPages"
|
|
394
|
+
/>
|
|
395
|
+
</div>
|
|
332
396
|
</div>
|
|
333
397
|
</div>
|
|
334
398
|
|
|
@@ -4,16 +4,19 @@
|
|
|
4
4
|
import InternalBaseLink from '@/components/InternalBaseLink.vue';
|
|
5
5
|
import TideIcon from '@/components/TideIcon.vue';
|
|
6
6
|
import { ELEMENT } from '@/types/Element';
|
|
7
|
+
import { SIZE } from '@/types/Size';
|
|
7
8
|
import { CSS } from '@/types/Styles';
|
|
8
9
|
import { TARGET } from '@/types/Target';
|
|
9
10
|
|
|
10
11
|
import type { Element } from '@/types/Element';
|
|
11
12
|
import type { Icon } from '@/types/Icon';
|
|
13
|
+
import type { Size } from '@/types/Size';
|
|
12
14
|
|
|
13
15
|
type Props = {
|
|
14
16
|
element?: Element;
|
|
15
17
|
href?: string;
|
|
16
18
|
iconLeading?: Icon;
|
|
19
|
+
iconSize?: Size;
|
|
17
20
|
iconTrailing?: Icon;
|
|
18
21
|
isNewTab?: boolean;
|
|
19
22
|
label: string;
|
|
@@ -24,6 +27,7 @@
|
|
|
24
27
|
element: ELEMENT.LINK,
|
|
25
28
|
href: undefined,
|
|
26
29
|
iconLeading: undefined,
|
|
30
|
+
iconSize: SIZE.SMALL,
|
|
27
31
|
iconTrailing: undefined,
|
|
28
32
|
isNewTab: false,
|
|
29
33
|
label: undefined,
|
|
@@ -49,6 +53,7 @@
|
|
|
49
53
|
<TideIcon
|
|
50
54
|
:class="[CSS.DISPLAY.INLINE_BLOCK, CSS.ALIGN.Y.MIDDLE, CSS.MARGIN.RIGHT.QUARTER]"
|
|
51
55
|
:icon="props.iconLeading"
|
|
56
|
+
:size="iconSize"
|
|
52
57
|
v-if="props.iconLeading"
|
|
53
58
|
/>
|
|
54
59
|
|
|
@@ -59,6 +64,7 @@
|
|
|
59
64
|
<TideIcon
|
|
60
65
|
:class="[CSS.DISPLAY.INLINE_BLOCK, CSS.ALIGN.Y.MIDDLE, CSS.MARGIN.LEFT.QUARTER]"
|
|
61
66
|
:icon="props.iconTrailing"
|
|
67
|
+
:size="iconSize"
|
|
62
68
|
v-if="props.iconTrailing"
|
|
63
69
|
/>
|
|
64
70
|
</component>
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { action } from '@storybook/addon-actions';
|
|
2
|
+
import { computed } from 'vue';
|
|
2
3
|
|
|
3
4
|
import TideCard from '@/components/TideCard.vue';
|
|
4
5
|
import TideCarousel from '@/components/TideCarousel.vue';
|
|
@@ -8,6 +9,8 @@ import type { StoryContext } from '@storybook/vue3';
|
|
|
8
9
|
|
|
9
10
|
type Args = InstanceType<typeof TideCarousel>['$props'] & {
|
|
10
11
|
handleSlidesAddedToView: string;
|
|
12
|
+
isFullWidthCards: boolean | undefined;
|
|
13
|
+
maxDots: string | undefined;
|
|
11
14
|
};
|
|
12
15
|
|
|
13
16
|
const formatSnippet = (code: string, context: StoryContext) => {
|
|
@@ -19,7 +22,6 @@ const formatSnippet = (code: string, context: StoryContext) => {
|
|
|
19
22
|
if (args.isFloating !== undefined) argsWithValues.push(`:is-floating="${args.isFloating}"`);
|
|
20
23
|
if (args.isHideawayButtons !== undefined) argsWithValues.push(`:is-hideaway-buttons="${args.isHideawayButtons}"`);
|
|
21
24
|
if (args.isScrollByPage !== undefined) argsWithValues.push(`:is-scroll-by-page="false"`);
|
|
22
|
-
if (args.isTouchscreen !== undefined) argsWithValues.push(`:is-touchscreen="${args.isTouchscreen}"`);
|
|
23
25
|
if (args.maxDots !== '') argsWithValues.push(`max-dots="${args.maxDots}"`);
|
|
24
26
|
if (args.subtitle !== '') argsWithValues.push(`subtitle="${args.subtitle}"`);
|
|
25
27
|
if (args.title !== '') argsWithValues.push(`title="${args.title}"`);
|
|
@@ -31,7 +33,7 @@ const formatSnippet = (code: string, context: StoryContext) => {
|
|
|
31
33
|
// prettier-ignore
|
|
32
34
|
`<TideCarousel ${argsWithValues.join(' ')}>` + lineBreak +
|
|
33
35
|
slotContentMisc + tab +
|
|
34
|
-
`<li class="tide-shrink-none" v-for="(_child, index) in new Array(12)">` + lineBreak + tab + tab +
|
|
36
|
+
`<li class="tide-shrink-none${args.isFullWidthCards ? ' tide-width-full' : ''}" v-for="(_child, index) in new Array(12)">` + lineBreak + tab + tab +
|
|
35
37
|
args.default + lineBreak + tab +
|
|
36
38
|
`</li>` + lineBreak +
|
|
37
39
|
`</TideCarousel>`
|
|
@@ -66,19 +68,31 @@ const render = (args: Args) => ({
|
|
|
66
68
|
}
|
|
67
69
|
},
|
|
68
70
|
},
|
|
69
|
-
setup: () =>
|
|
71
|
+
setup: () => {
|
|
72
|
+
const conditionalFullWidth = computed(() => (args.isFullWidthCards ? ' tide-width-full' : ''));
|
|
73
|
+
const argsFormatted = computed(() => ({
|
|
74
|
+
...args,
|
|
75
|
+
maxDots: args.maxDots === '' ? undefined : args.maxDots,
|
|
76
|
+
}));
|
|
77
|
+
|
|
78
|
+
return {
|
|
79
|
+
argsFormatted,
|
|
80
|
+
conditionalFullWidth,
|
|
81
|
+
};
|
|
82
|
+
},
|
|
70
83
|
// prettier-ignore
|
|
71
84
|
template:
|
|
72
85
|
`<TideCarousel
|
|
73
86
|
@slides-added-to-view="handleSlidesAddedToView"
|
|
74
|
-
v-bind="
|
|
87
|
+
v-bind="argsFormatted"
|
|
88
|
+
:key="argsFormatted.isFullWidthCards"
|
|
75
89
|
>
|
|
76
|
-
<template #misc>{{
|
|
90
|
+
<template #misc>{{ argsFormatted.misc }}</template>
|
|
77
91
|
<li
|
|
78
|
-
class="tide-shrink-none tide-border-1 sb-border-blue tide-padding-1 sb-bg-blue-light"
|
|
92
|
+
:class="['tide-shrink-none tide-border-1 sb-border-blue tide-padding-1 sb-bg-blue-light', conditionalFullWidth]"
|
|
79
93
|
v-for="(_child, index) in new Array(12)"
|
|
80
94
|
>
|
|
81
|
-
{{
|
|
95
|
+
{{ argsFormatted.default.replace('#', index) }}
|
|
82
96
|
</li>
|
|
83
97
|
</TideCarousel>`,
|
|
84
98
|
});
|
|
@@ -106,7 +120,7 @@ export default {
|
|
|
106
120
|
},
|
|
107
121
|
hasDots: {
|
|
108
122
|
...argTypeBooleanUnrequired,
|
|
109
|
-
description: 'Determines whether to display the indicator dots overlay',
|
|
123
|
+
description: 'Determines whether to display the indicator dots overlay<br />(Only valid with full width cards)',
|
|
110
124
|
table: {
|
|
111
125
|
defaultValue: { summary: 'False' },
|
|
112
126
|
},
|
|
@@ -118,6 +132,16 @@ export default {
|
|
|
118
132
|
defaultValue: { summary: 'False' },
|
|
119
133
|
},
|
|
120
134
|
},
|
|
135
|
+
isFullWidthCards: {
|
|
136
|
+
...argTypeBooleanUnrequired,
|
|
137
|
+
description: 'Preview 1 card per page implementation',
|
|
138
|
+
table: {
|
|
139
|
+
category: 'Demo',
|
|
140
|
+
defaultValue: { summary: 'None' },
|
|
141
|
+
disable: true,
|
|
142
|
+
type: { summary: 'boolean' },
|
|
143
|
+
},
|
|
144
|
+
},
|
|
121
145
|
isHeadline1: {
|
|
122
146
|
...argTypeBooleanUnrequired,
|
|
123
147
|
description: 'Determines font role used for title display',
|
|
@@ -133,23 +157,11 @@ export default {
|
|
|
133
157
|
defaultValue: { summary: 'True' },
|
|
134
158
|
},
|
|
135
159
|
},
|
|
136
|
-
isScrollByPage:
|
|
137
|
-
...argTypeBooleanUnrequired,
|
|
138
|
-
description: 'Determines pagination method',
|
|
139
|
-
table: {
|
|
140
|
-
defaultValue: { summary: 'None' },
|
|
141
|
-
},
|
|
142
|
-
},
|
|
143
|
-
isTouchscreen: {
|
|
144
|
-
...argTypeBooleanUnrequired,
|
|
145
|
-
description: 'Determines button and/or swipe control scheme',
|
|
146
|
-
table: {
|
|
147
|
-
defaultValue: { summary: 'None' },
|
|
148
|
-
},
|
|
149
|
-
},
|
|
160
|
+
isScrollByPage: disabledArgType,
|
|
150
161
|
maxDots: {
|
|
151
162
|
control: 'text',
|
|
152
163
|
description: 'Determines the max number of indicator dots to display at a given time',
|
|
164
|
+
if: { arg: 'hasDots', eq: true },
|
|
153
165
|
table: {
|
|
154
166
|
defaultValue: { summary: 'None' },
|
|
155
167
|
type: { summary: 'number' },
|
|
@@ -186,9 +198,8 @@ export default {
|
|
|
186
198
|
handleSlidesAddedToView: 'doSomething',
|
|
187
199
|
hasDots: undefined,
|
|
188
200
|
isFloating: undefined,
|
|
201
|
+
isFullWidthCards: undefined,
|
|
189
202
|
isHideawayButtons: undefined,
|
|
190
|
-
isScrollByPage: undefined,
|
|
191
|
-
isTouchscreen: undefined,
|
|
192
203
|
maxDots: '',
|
|
193
204
|
misc: '',
|
|
194
205
|
subtitle: '',
|
|
@@ -201,4 +212,15 @@ export default {
|
|
|
201
212
|
title: 'Components/TideCarousel',
|
|
202
213
|
};
|
|
203
214
|
|
|
204
|
-
export const Demo = {
|
|
215
|
+
export const Demo = {
|
|
216
|
+
name: 'Default',
|
|
217
|
+
};
|
|
218
|
+
|
|
219
|
+
export const FullWidth = {
|
|
220
|
+
args: {
|
|
221
|
+
default: 'Full-width card #',
|
|
222
|
+
isFullWidthCards: true,
|
|
223
|
+
title: 'Demo',
|
|
224
|
+
},
|
|
225
|
+
name: 'Full-width cards ',
|
|
226
|
+
};
|
package/src/types/Formatted.ts
CHANGED
|
@@ -15,7 +15,7 @@ export const FORMAT_REGEX = {
|
|
|
15
15
|
alphaNumberOrEmpty: /^$|^[a-z0-9]+$/i,
|
|
16
16
|
alphaSpace: /^[a-zA-Z ]+$/g,
|
|
17
17
|
email:
|
|
18
|
-
/^(([^<>()[\]\\.,;:\s@"]+(\.[^<>()[\]\\.,;:\s@"]+)*)|(".+"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\])|(([a-zA-
|
|
18
|
+
/^(([^<>()[\]\\.,;:\s@"]+(\.[^<>()[\]\\.,;:\s@"]+)*)|(".+"))@(?!(?:[a-zA-Z0-9-]+\.)*(?<tld>[a-zA-Z]{2,})\.\k<tld>$)((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\])|(([a-zA-Z0-9-]+\.)+[a-zA-Z]{2,}))$/,
|
|
19
19
|
numberFormatted: /^$|(?=.)^\$?(([1-9][0-9]{0,2}(,[0-9]{3})*)|0)?(\.[0-9]{1,2})?$/,
|
|
20
20
|
phone: /^(?:\d{3}-\d{3}-\d{4}|\d{1}-\d{3}-\d{3}-\d{4})?$/,
|
|
21
21
|
price: /(?=.)^\$?(([1-9][0-9]{0,2}(,[0-9]{3})*)|0)?(\.[0-9]{1,2})?$/,
|
|
@@ -62,7 +62,7 @@ export const getFieldValidationResult = ({
|
|
|
62
62
|
|
|
63
63
|
// custom validator prop errors from have second highest precedence
|
|
64
64
|
if (validators) {
|
|
65
|
-
const validation = validateProperty(value.value, validators);
|
|
65
|
+
const validation = validateProperty(value.value ?? '', validators);
|
|
66
66
|
|
|
67
67
|
if (!validation.valid) {
|
|
68
68
|
return validation;
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { describe, expect, it } from 'vitest';
|
|
2
2
|
|
|
3
|
+
import { FORMAT_REGEX } from '../src/types/Formatted';
|
|
3
4
|
import {
|
|
4
5
|
formatCamelCase,
|
|
5
6
|
formatKebabCase,
|
|
@@ -428,3 +429,42 @@ describe('@/src/utilities/format.ts', () => {
|
|
|
428
429
|
});
|
|
429
430
|
});
|
|
430
431
|
});
|
|
432
|
+
|
|
433
|
+
describe('@/src/types/Formatted.ts', () => {
|
|
434
|
+
describe('FORMAT_REGEX.email', () => {
|
|
435
|
+
it('accepts a standard email address.', () => {
|
|
436
|
+
const input = 'john.doe@gmail.com';
|
|
437
|
+
const output = true;
|
|
438
|
+
|
|
439
|
+
expect(FORMAT_REGEX.email.test(input)).toEqual(output);
|
|
440
|
+
});
|
|
441
|
+
|
|
442
|
+
it('accepts an email address with subdomains.', () => {
|
|
443
|
+
const input = 'john.doe@mail.sub.example.co.uk';
|
|
444
|
+
const output = true;
|
|
445
|
+
|
|
446
|
+
expect(FORMAT_REGEX.email.test(input)).toEqual(output);
|
|
447
|
+
});
|
|
448
|
+
|
|
449
|
+
it('rejects an email address that ends with a repeated TLD label (e.g. .com.com).', () => {
|
|
450
|
+
const input = 'john@gmail.com.com';
|
|
451
|
+
const output = false;
|
|
452
|
+
|
|
453
|
+
expect(FORMAT_REGEX.email.test(input)).toEqual(output);
|
|
454
|
+
});
|
|
455
|
+
|
|
456
|
+
it('rejects an email address that ends with a repeated TLD label (e.g. .net.net).', () => {
|
|
457
|
+
const input = 'john@company.net.net';
|
|
458
|
+
const output = false;
|
|
459
|
+
|
|
460
|
+
expect(FORMAT_REGEX.email.test(input)).toEqual(output);
|
|
461
|
+
});
|
|
462
|
+
|
|
463
|
+
it('rejects an email address missing a valid top-level domain.', () => {
|
|
464
|
+
const input = 'john@gmail';
|
|
465
|
+
const output = false;
|
|
466
|
+
|
|
467
|
+
expect(FORMAT_REGEX.email.test(input)).toEqual(output);
|
|
468
|
+
});
|
|
469
|
+
});
|
|
470
|
+
});
|