Skip to content

Commit d389fe7

Browse files
committed
Upgrade to valibot for props validation
1 parent 6aebcf9 commit d389fe7

9 files changed

Lines changed: 169 additions & 90 deletions

File tree

packages/component/src/SendBox/AttachmentBar/AttachmentBarItem.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,6 @@ import { type SendBoxAttachment } from 'botframework-webchat-core';
33
import classNames from 'classnames';
44
import React, { memo, useCallback, useEffect, useMemo, useRef } from 'react';
55
import { useRefFrom } from 'use-ref-from';
6-
76
import {
87
custom,
98
function_,
@@ -17,6 +16,7 @@ import {
1716
union,
1817
type InferInput
1918
} from 'valibot';
19+
2020
import { useFocus, useStyleSet } from '../../hooks';
2121
import testIds from '../../testIds';
2222
import DeleteButton from './ItemDeleteButton';
Lines changed: 16 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,20 @@
1+
import { validateProps } from 'botframework-webchat-api/internal';
12
import React, { memo } from 'react';
3+
import { type InferInput, object, optional, picklist, pipe, readonly } from 'valibot';
24

3-
type DeleteIconProps = Readonly<{
4-
size?: 'large' | 'small' | undefined;
5-
}>;
5+
const deleteIconPropsSchema = pipe(
6+
object({
7+
size: optional(picklist(['large', 'small']))
8+
}),
9+
readonly()
10+
);
611

7-
const DeleteIcon = ({ size }: DeleteIconProps) =>
8-
size === 'large' ? (
12+
type DeleteIconProps = InferInput<typeof deleteIconPropsSchema>;
13+
14+
function DeleteIcon(props: DeleteIconProps) {
15+
const { size } = validateProps(deleteIconPropsSchema, props);
16+
17+
return size === 'large' ? (
918
<svg fill="none" height="20" viewBox="0 0 20 20" width="20" xmlns="http://www.w3.org/2000/svg">
1019
<path
1120
d="M4.60507 5.12706L4.66061 5.06059C4.82723 4.89396 5.08588 4.87545 5.27295 5.00505L5.33943 5.06059L9.80002 9.52128L14.2606 5.06059C14.4481 4.87314 14.752 4.87314 14.9394 5.06059C15.1269 5.24804 15.1269 5.55196 14.9394 5.73941L10.4787 10.2L14.9394 14.6606C15.1061 14.8272 15.1246 15.0859 14.995 15.2729L14.9394 15.3394C14.7728 15.506 14.5142 15.5245 14.3271 15.395L14.2606 15.3394L9.80002 10.8787L5.33943 15.3394C5.15198 15.5269 4.84806 15.5269 4.66061 15.3394C4.47316 15.152 4.47316 14.848 4.66061 14.6606L9.1213 10.2L4.66061 5.73941C4.49398 5.57279 4.47547 5.31414 4.60507 5.12706L4.66061 5.06059L4.60507 5.12706Z"
@@ -20,7 +29,7 @@ const DeleteIcon = ({ size }: DeleteIconProps) =>
2029
/>
2130
</svg>
2231
);
32+
}
2333

2434
export default memo(DeleteIcon);
25-
26-
export { type DeleteIconProps };
35+
export { deleteIconPropsSchema, type DeleteIconProps };

packages/component/src/SendBox/AttachmentBar/ItemDeleteButton.tsx

Lines changed: 18 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,28 @@
11
import { hooks } from 'botframework-webchat-api';
2+
import { validateProps } from 'botframework-webchat-api/internal';
23
import React, { KeyboardEventHandler, useCallback } from 'react';
4+
import { function_, object, optional, picklist, pipe, readonly, string, type InferInput } from 'valibot';
5+
36
import { useFocus } from '../../hooks';
47
import testIds from '../../testIds';
58
import DeleteIcon from './DeleteIcon';
69

710
const { useLocalizer } = hooks;
811

9-
type AttachmentDeleteButton = Readonly<{
10-
attachmentName: string;
11-
onClick?: (() => void) | undefined;
12-
size?: 'large' | 'small' | undefined;
13-
}>;
12+
const attachmentDeleteButtonPropsSchema = pipe(
13+
object({
14+
attachmentName: string(),
15+
onClick: optional(function_()),
16+
size: optional(picklist(['large', 'small']))
17+
}),
18+
readonly()
19+
);
20+
21+
type AttachmentDeleteButtonProps = InferInput<typeof attachmentDeleteButtonPropsSchema>;
22+
23+
function AttachmentDeleteButton(props: AttachmentDeleteButtonProps) {
24+
const { attachmentName, onClick, size } = validateProps(attachmentDeleteButtonPropsSchema, props);
1425

15-
const AttachmentDeleteButton = ({ attachmentName, onClick, size }: AttachmentDeleteButton) => {
1626
const focus = useFocus();
1727
const localize = useLocalizer();
1828

@@ -40,9 +50,7 @@ const AttachmentDeleteButton = ({ attachmentName, onClick, size }: AttachmentDel
4050
<DeleteIcon size={size} />
4151
</button>
4252
);
43-
};
44-
45-
AttachmentDeleteButton.displayName = 'SendBoxAttachmentItemDeleteButton';
53+
}
4654

4755
export default AttachmentDeleteButton;
48-
export { type AttachmentDeleteButton };
56+
export { attachmentDeleteButtonPropsSchema, type AttachmentDeleteButtonProps };

packages/component/src/SendBox/AttachmentBar/ItemPreview.tsx

Lines changed: 19 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,27 @@
1+
import { validateProps } from 'botframework-webchat-api/internal';
12
import { type SendBoxAttachment } from 'botframework-webchat-core';
23
import React, { memo } from 'react';
4+
import { object, picklist, pipe, readonly, string, type InferInput } from 'valibot';
35

46
import FilePreview from './Preview/FilePreview';
57
import ImagePreview from './Preview/ImagePreview';
8+
import { sendBoxAttachmentSchema } from './Preview/sendBoxAttachment';
69

7-
type AttachmentBarItemPreviewProps = Readonly<{
8-
attachment: SendBoxAttachment;
9-
attachmentName: string;
10-
mode: 'list item' | 'thumbnail';
11-
}>;
10+
const sendBoxAttachmentBarItemPreviewPropsSchema = pipe(
11+
object({
12+
attachment: sendBoxAttachmentSchema,
13+
attachmentName: string(),
14+
mode: picklist(['list item', 'thumbnail'])
15+
}),
16+
readonly()
17+
);
18+
19+
type SendBoxAttachmentBarItemPreviewProps = InferInput<typeof sendBoxAttachmentBarItemPreviewPropsSchema>;
1220

1321
// TODO: Turn this into middleware.
14-
const AttachmentBarItemPreview = ({ attachment, attachmentName, mode }: AttachmentBarItemPreviewProps) => {
22+
function SendBoxAttachmentBarItemPreview(props: SendBoxAttachmentBarItemPreviewProps) {
23+
const { attachment, attachmentName, mode } = validateProps(sendBoxAttachmentBarItemPreviewPropsSchema, props);
24+
1525
let element: React.ReactNode;
1626

1727
if (attachment.thumbnailURL) {
@@ -33,9 +43,7 @@ const AttachmentBarItemPreview = ({ attachment, attachmentName, mode }: Attachme
3343
}
3444

3545
return <div className="webchat__send-box-attachment-bar-item__preview">{element}</div>;
36-
};
37-
38-
AttachmentBarItemPreview.displayName = 'SendBoxAttachmentBarItemPreview';
46+
}
3947

40-
export default memo(AttachmentBarItemPreview);
41-
export { type AttachmentBarItemPreviewProps };
48+
export default memo(SendBoxAttachmentBarItemPreview);
49+
export { sendBoxAttachmentBarItemPreviewPropsSchema, type SendBoxAttachmentBarItemPreviewProps };

packages/component/src/SendBox/AttachmentBar/Preview/FileIcon.tsx

Lines changed: 16 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,20 @@
1+
import { validateProps } from 'botframework-webchat-api/internal';
12
import React, { memo } from 'react';
3+
import { type InferInput, object, optional, picklist, pipe, readonly } from 'valibot';
24

3-
type FileIconProps = Readonly<{
4-
size?: 'large' | 'small' | undefined;
5-
}>;
5+
const fileIconPropsSchema = pipe(
6+
object({
7+
size: optional(picklist(['large', 'small']))
8+
}),
9+
readonly()
10+
);
611

7-
const FileIcon = ({ size }: FileIconProps) =>
8-
size === 'large' ? (
12+
type FileIconProps = InferInput<typeof fileIconPropsSchema>;
13+
14+
function FileIcon(props: FileIconProps) {
15+
const { size } = validateProps(fileIconPropsSchema, props);
16+
17+
return size === 'large' ? (
918
<svg fill="none" height="36" viewBox="0 0 36 36" width="36" xmlns="http://www.w3.org/2000/svg">
1019
<path
1120
d="M7.386 33H28.6C28.816 33 29 32.83 29 32.628V10H23.4C22.628 10 22 9.372 22 8.6V3H7.4C7.184 3 7 3.17 7 3.372V32.642C7 32.839 7.173 33 7.386 33Z"
@@ -39,7 +48,7 @@ const FileIcon = ({ size }: FileIconProps) =>
3948
/>
4049
</svg>
4150
);
51+
}
4252

4353
export default memo(FileIcon);
44-
45-
export { type FileIconProps };
54+
export { fileIconPropsSchema, type FileIconProps };

packages/component/src/SendBox/AttachmentBar/Preview/FilePreview.tsx

Lines changed: 28 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,32 @@
1-
import { type SendBoxAttachment } from 'botframework-webchat-core';
1+
import { validateProps } from 'botframework-webchat-api/internal';
22
import classNames from 'classnames';
33
import React, { memo } from 'react';
4+
import { object, picklist, pipe, readonly, string, type InferInput } from 'valibot';
5+
46
import { useStyleSet } from '../../../hooks';
57
import FileIcon from './FileIcon';
68
import ImageIcon from './ImageIcon';
9+
import { sendBoxAttachmentSchema } from './sendBoxAttachment';
10+
11+
const sendBoxAttachmentBarItemFileAttachmentPreviewPropsSchema = pipe(
12+
object({
13+
attachment: sendBoxAttachmentSchema,
14+
attachmentName: string(),
15+
mode: picklist(['list item', 'thumbnail'])
16+
}),
17+
readonly()
18+
);
719

8-
type FileAttachmentPreviewProps = Readonly<{
9-
attachment: SendBoxAttachment;
10-
attachmentName: string;
11-
mode: 'list item' | 'thumbnail';
12-
}>;
20+
type SendBoxAttachmentBarItemFileAttachmentPreviewProps = InferInput<
21+
typeof sendBoxAttachmentBarItemFileAttachmentPreviewPropsSchema
22+
>;
23+
24+
function SendBoxAttachmentBarItemFileAttachmentPreview(props: SendBoxAttachmentBarItemFileAttachmentPreviewProps) {
25+
const { attachment, mode, attachmentName } = validateProps(
26+
sendBoxAttachmentBarItemFileAttachmentPreviewPropsSchema,
27+
props
28+
);
1329

14-
const FileAttachmentPreview = ({ attachment, mode, attachmentName }: FileAttachmentPreviewProps) => {
1530
const [{ sendBoxAttachmentBarItemFilePreview: sendBoxAttachmentBarItemFilePreviewClassName }] = useStyleSet();
1631

1732
return mode === 'list item' ? (
@@ -36,9 +51,10 @@ const FileAttachmentPreview = ({ attachment, mode, attachmentName }: FileAttachm
3651
<FileIcon size="large" />
3752
</div>
3853
);
39-
};
40-
41-
FileAttachmentPreview.displayName = 'SendBoxAttachmentBarItemFileAttachmentPreview';
54+
}
4255

43-
export default memo(FileAttachmentPreview);
44-
export { type FileAttachmentPreviewProps };
56+
export default memo(SendBoxAttachmentBarItemFileAttachmentPreview);
57+
export {
58+
sendBoxAttachmentBarItemFileAttachmentPreviewPropsSchema,
59+
type SendBoxAttachmentBarItemFileAttachmentPreviewProps
60+
};
Lines changed: 30 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -1,32 +1,34 @@
11
import React, { memo } from 'react';
22

3-
const ImagePreviewIcon = () => (
4-
<svg fill="none" height="16" viewBox="0 0 16 16" width="16" xmlns="http://www.w3.org/2000/svg">
5-
<path
6-
d="M2.5 14H13.5C13.775 14 14 13.775 14 13.5V2.5C14 2.225 13.775 2 13.5 2H2.5C2.225 2 2 2.225 2 2.5V13.5C2 13.775 2.225 14 2.5 14Z"
7-
fill="#FCFCFC"
8-
/>
9-
<path
10-
clipRule="evenodd"
11-
d="M9.27768 8.63L8.66968 9.476L7.38468 7.792C7.19551 7.54408 6.90152 7.39861 6.58968 7.39861C6.27784 7.39861 5.98385 7.54408 5.79468 7.792L4.18968 9.893C3.95877 10.1953 3.91939 10.6024 4.08807 10.9434C4.25675 11.2844 4.60427 11.5001 4.98468 11.5H11.0137C11.3887 11.4997 11.7321 11.2896 11.903 10.9557C12.074 10.6219 12.0437 10.2205 11.8247 9.916L10.9007 8.63C10.7128 8.36861 10.4106 8.21365 10.0887 8.21365C9.76678 8.21365 9.46456 8.36861 9.27668 8.63H9.27768Z"
12-
fill="#FCFCFC"
13-
fillRule="evenodd"
14-
stroke="#A6CCC3"
15-
/>
16-
<path
17-
clipRule="evenodd"
18-
d="M10.5 6.5C11.0523 6.5 11.5 6.05228 11.5 5.5C11.5 4.94772 11.0523 4.5 10.5 4.5C9.94772 4.5 9.5 4.94772 9.5 5.5C9.5 6.05228 9.94772 6.5 10.5 6.5Z"
19-
fillRule="evenodd"
20-
stroke="#FF9810"
21-
/>
22-
<path
23-
clipRule="evenodd"
24-
d="M2.5 15H13.5C14.327 15 15 14.327 15 13.5V2.5C15 1.673 14.327 1 13.5 1H2.5C1.673 1 1 1.673 1 2.5V13.5C1 14.327 1.673 15 2.5 15ZM2 2.5C2 2.22386 2.22386 2 2.5 2H13.5C13.7761 2 14 2.22386 14 2.5V13.5C14 13.7761 13.7761 14 13.5 14H2.5C2.22386 14 2 13.7761 2 13.5V2.5Z"
25-
fill="#616161"
26-
fillRule="evenodd"
27-
opacity="0.67"
28-
/>
29-
</svg>
30-
);
3+
function ImagePreviewIcon() {
4+
return (
5+
<svg fill="none" height="16" viewBox="0 0 16 16" width="16" xmlns="http://www.w3.org/2000/svg">
6+
<path
7+
d="M2.5 14H13.5C13.775 14 14 13.775 14 13.5V2.5C14 2.225 13.775 2 13.5 2H2.5C2.225 2 2 2.225 2 2.5V13.5C2 13.775 2.225 14 2.5 14Z"
8+
fill="#FCFCFC"
9+
/>
10+
<path
11+
clipRule="evenodd"
12+
d="M9.27768 8.63L8.66968 9.476L7.38468 7.792C7.19551 7.54408 6.90152 7.39861 6.58968 7.39861C6.27784 7.39861 5.98385 7.54408 5.79468 7.792L4.18968 9.893C3.95877 10.1953 3.91939 10.6024 4.08807 10.9434C4.25675 11.2844 4.60427 11.5001 4.98468 11.5H11.0137C11.3887 11.4997 11.7321 11.2896 11.903 10.9557C12.074 10.6219 12.0437 10.2205 11.8247 9.916L10.9007 8.63C10.7128 8.36861 10.4106 8.21365 10.0887 8.21365C9.76678 8.21365 9.46456 8.36861 9.27668 8.63H9.27768Z"
13+
fill="#FCFCFC"
14+
fillRule="evenodd"
15+
stroke="#A6CCC3"
16+
/>
17+
<path
18+
clipRule="evenodd"
19+
d="M10.5 6.5C11.0523 6.5 11.5 6.05228 11.5 5.5C11.5 4.94772 11.0523 4.5 10.5 4.5C9.94772 4.5 9.5 4.94772 9.5 5.5C9.5 6.05228 9.94772 6.5 10.5 6.5Z"
20+
fillRule="evenodd"
21+
stroke="#FF9810"
22+
/>
23+
<path
24+
clipRule="evenodd"
25+
d="M2.5 15H13.5C14.327 15 15 14.327 15 13.5V2.5C15 1.673 14.327 1 13.5 1H2.5C1.673 1 1 1.673 1 2.5V13.5C1 14.327 1.673 15 2.5 15ZM2 2.5C2 2.22386 2.22386 2 2.5 2H13.5C13.7761 2 14 2.22386 14 2.5V13.5C14 13.7761 13.7761 14 13.5 14H2.5C2.22386 14 2 13.7761 2 13.5V2.5Z"
26+
fill="#616161"
27+
fillRule="evenodd"
28+
opacity="0.67"
29+
/>
30+
</svg>
31+
);
32+
}
3133

3234
export default memo(ImagePreviewIcon);
Lines changed: 28 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,31 @@
1-
import { type SendBoxAttachment } from 'botframework-webchat-core';
1+
import { validateProps } from 'botframework-webchat-api/internal';
22
import classNames from 'classnames';
33
import React, { memo } from 'react';
4+
import { object, picklist, pipe, readonly, string, type InferInput } from 'valibot';
5+
46
import { useStyleSet } from '../../../hooks';
57
import FilePreview from './FilePreview';
8+
import { sendBoxAttachmentSchema } from './sendBoxAttachment';
9+
10+
const sendBoxAttachmentBarItemImageAttachmentPreviewPropsSchema = pipe(
11+
object({
12+
attachment: sendBoxAttachmentSchema,
13+
attachmentName: string(),
14+
mode: picklist(['list item', 'thumbnail'])
15+
}),
16+
readonly()
17+
);
618

7-
type ImageAttachmentPreviewProps = Readonly<{
8-
attachment: SendBoxAttachment & {
9-
thumbnailURL: URL;
10-
};
11-
attachmentName: string;
12-
mode: 'list item' | 'thumbnail';
13-
}>;
19+
type SendBoxAttachmentBarItemImageAttachmentPreviewProps = InferInput<
20+
typeof sendBoxAttachmentBarItemImageAttachmentPreviewPropsSchema
21+
>;
22+
23+
function SendBoxAttachmentBarItemImageAttachmentPreview(props: SendBoxAttachmentBarItemImageAttachmentPreviewProps) {
24+
const { attachment, mode, attachmentName } = validateProps(
25+
sendBoxAttachmentBarItemImageAttachmentPreviewPropsSchema,
26+
props
27+
);
1428

15-
const ImageAttachmentPreview = ({ attachment, mode, attachmentName }: ImageAttachmentPreviewProps) => {
1629
const [{ sendBoxAttachmentBarItemImagePreview: sendBoxAttachmentBarItemImagePreviewClassName }] = useStyleSet();
1730

1831
return mode === 'list item' ? (
@@ -27,9 +40,10 @@ const ImageAttachmentPreview = ({ attachment, mode, attachmentName }: ImageAttac
2740
src={attachment.thumbnailURL.href}
2841
/>
2942
);
30-
};
31-
32-
ImageAttachmentPreview.displayName = 'SendBoxAttachmentBarItemImageAttachmentPreview';
43+
}
3344

34-
export default memo(ImageAttachmentPreview);
35-
export { type ImageAttachmentPreviewProps };
45+
export default memo(SendBoxAttachmentBarItemImageAttachmentPreview);
46+
export {
47+
sendBoxAttachmentBarItemImageAttachmentPreviewPropsSchema,
48+
type SendBoxAttachmentBarItemImageAttachmentPreviewProps
49+
};
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
import { type InferInput, instance, object, optional, pipe, readonly, union } from 'valibot';
2+
3+
const sendBoxAttachmentSchema = pipe(
4+
object({
5+
blob: union([instance(Blob), instance(File)]),
6+
thumbnailURL: optional(instance(URL))
7+
}),
8+
readonly()
9+
);
10+
11+
type SendBoxAttachment = InferInput<typeof sendBoxAttachmentSchema>;
12+
13+
export { sendBoxAttachmentSchema, type SendBoxAttachment };

0 commit comments

Comments
 (0)