akeneo-design-system 0.1.226 → 0.1.227

Sign up to get free protection for your applications and to get access to all the features.
Files changed (46) hide show
  1. package/example/src/__snapshots__/App.test.js.snap +1 -1
  2. package/jest-puppeteer.config.js +1 -0
  3. package/lib/components/Avatar/Avatar.d.ts +12 -0
  4. package/lib/components/Avatar/Avatar.js +89 -0
  5. package/lib/components/Avatar/Avatar.js.map +1 -0
  6. package/lib/components/Avatar/Avatars.d.ts +8 -0
  7. package/lib/components/Avatar/Avatars.js +71 -0
  8. package/lib/components/Avatar/Avatars.js.map +1 -0
  9. package/lib/components/Dropdown/Dropdown.d.ts +1 -1
  10. package/lib/components/Input/BooleanInput/BooleanInput.d.ts +1 -1
  11. package/lib/components/Input/BooleanInput/BooleanInput.js +1 -1
  12. package/lib/components/Input/BooleanInput/BooleanInput.js.map +1 -1
  13. package/lib/components/Input/MultiSelectInput/MultiSelectInput.d.ts +8 -2
  14. package/lib/components/Input/MultiSelectInput/MultiSelectInput.js +11 -9
  15. package/lib/components/Input/MultiSelectInput/MultiSelectInput.js.map +1 -1
  16. package/lib/components/Input/SelectInput/SelectInput.d.ts +8 -2
  17. package/lib/components/Input/SelectInput/SelectInput.js +13 -11
  18. package/lib/components/Input/SelectInput/SelectInput.js.map +1 -1
  19. package/lib/components/Input/TableInput/TableInput.d.ts +1 -1
  20. package/lib/components/Input/TableInput/TableInputRow/TableInputRow.js +1 -1
  21. package/lib/components/Input/TableInput/TableInputRow/TableInputRow.js.map +1 -1
  22. package/lib/components/Input/TagInput/TagInput.js +5 -2
  23. package/lib/components/Input/TagInput/TagInput.js.map +1 -1
  24. package/lib/components/ProgressIndicator/ProgressIndicator.d.ts +1 -1
  25. package/lib/components/index.d.ts +2 -0
  26. package/lib/components/index.js +2 -0
  27. package/lib/components/index.js.map +1 -1
  28. package/package.json +1 -1
  29. package/src/__image_snapshots__/all-visual-tsx-visual-tests-renders-components-avatar-avatar-list-correctly-1-snap.png +0 -0
  30. package/src/__image_snapshots__/all-visual-tsx-visual-tests-renders-components-avatar-avatar-size-correctly-1-snap.png +0 -0
  31. package/src/__image_snapshots__/all-visual-tsx-visual-tests-renders-components-avatar-background-colors-correctly-1-snap.png +0 -0
  32. package/src/__image_snapshots__/all-visual-tsx-visual-tests-renders-components-avatar-standard-correctly-1-snap.png +0 -0
  33. package/src/__image_snapshots__/all-visual-tsx-visual-tests-renders-components-avatar-with-image-correctly-1-snap.png +0 -0
  34. package/src/components/Avatar/Avatar.stories.mdx +147 -0
  35. package/src/components/Avatar/Avatar.tsx +102 -0
  36. package/src/components/Avatar/Avatar.unit.tsx +79 -0
  37. package/src/components/Avatar/Avatars.tsx +50 -0
  38. package/src/components/Avatar/Avatars.unit.tsx +41 -0
  39. package/src/components/Input/BooleanInput/BooleanInput.tsx +2 -2
  40. package/src/components/Input/MultiSelectInput/MultiSelectInput.tsx +26 -16
  41. package/src/components/Input/MultiSelectInput/MultiSelectInput.unit.tsx +45 -0
  42. package/src/components/Input/SelectInput/SelectInput.tsx +28 -18
  43. package/src/components/Input/SelectInput/SelectInput.unit.tsx +52 -0
  44. package/src/components/Input/TableInput/TableInputRow/TableInputRow.tsx +2 -7
  45. package/src/components/Input/TagInput/TagInput.tsx +3 -3
  46. package/src/components/index.ts +2 -0
@@ -1,3 +1,5 @@
1
+ export * from './Avatar/Avatar';
2
+ export * from './Avatar/Avatars';
1
3
  export * from './Badge/Badge';
2
4
  export * from './Block/Block';
3
5
  export * from './BlockButton/BlockButton';
@@ -10,6 +10,8 @@ var __exportStar = (this && this.__exportStar) || function(m, exports) {
10
10
  for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
11
11
  };
12
12
  Object.defineProperty(exports, "__esModule", { value: true });
13
+ __exportStar(require("./Avatar/Avatar"), exports);
14
+ __exportStar(require("./Avatar/Avatars"), exports);
13
15
  __exportStar(require("./Badge/Badge"), exports);
14
16
  __exportStar(require("./Block/Block"), exports);
15
17
  __exportStar(require("./BlockButton/BlockButton"), exports);
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/components/index.ts"],"names":[],"mappings":";;;;;;;;;;;;AAAA,gDAA8B;AAC9B,gDAA8B;AAC9B,4DAA0C;AAC1C,0DAAwC;AACxC,kDAAgC;AAChC,8CAA4B;AAC5B,sDAAoC;AACpC,sDAAoC;AACpC,sDAAoC;AACpC,gDAA8B;AAC9B,kDAAgC;AAChC,0DAAwC;AACxC,sDAAoC;AACpC,gDAA8B;AAC9B,4DAA0C;AAC1C,0CAAwB;AACxB,wDAAsC;AACtC,8CAA4B;AAC5B,8CAA4B;AAC5B,kDAAgC;AAChC,0DAAwC;AACxC,gDAA8B;AAC9B,+CAA6B;AAC7B,0DAAwC;AACxC,8CAA4B;AAC5B,4DAA0C;AAC1C,oDAAkC;AAClC,4DAA0C;AAC1C,wEAAsD;AACtD,kDAAgC;AAChC,8DAA4C;AAC5C,kEAAgD;AAChD,kDAAgC;AAChC,gDAA8B;AAC9B,8CAA4B;AAC5B,gDAA8B;AAC9B,oDAAkC;AAClC,8CAA4B;AAC5B,oDAAkC"}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/components/index.ts"],"names":[],"mappings":";;;;;;;;;;;;AAAA,kDAAgC;AAChC,mDAAiC;AACjC,gDAA8B;AAC9B,gDAA8B;AAC9B,4DAA0C;AAC1C,0DAAwC;AACxC,kDAAgC;AAChC,8CAA4B;AAC5B,sDAAoC;AACpC,sDAAoC;AACpC,sDAAoC;AACpC,gDAA8B;AAC9B,kDAAgC;AAChC,0DAAwC;AACxC,sDAAoC;AACpC,gDAA8B;AAC9B,4DAA0C;AAC1C,0CAAwB;AACxB,wDAAsC;AACtC,8CAA4B;AAC5B,8CAA4B;AAC5B,kDAAgC;AAChC,0DAAwC;AACxC,gDAA8B;AAC9B,+CAA6B;AAC7B,0DAAwC;AACxC,8CAA4B;AAC5B,4DAA0C;AAC1C,oDAAkC;AAClC,4DAA0C;AAC1C,wEAAsD;AACtD,kDAAgC;AAChC,8DAA4C;AAC5C,kEAAgD;AAChD,kDAAgC;AAChC,gDAA8B;AAC9B,8CAA4B;AAC5B,gDAA8B;AAC9B,oDAAkC;AAClC,8CAA4B;AAC5B,oDAAkC"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "akeneo-design-system",
3
- "version": "0.1.226",
3
+ "version": "0.1.227",
4
4
  "description": "Akeneo design system",
5
5
  "main": "lib/index.js",
6
6
  "scripts": {
@@ -0,0 +1,147 @@
1
+ import {Meta, Story, ArgsTable, Canvas} from '@storybook/addon-docs';
2
+ import {Avatar} from './Avatar.tsx';
3
+ import {Avatars} from './Avatars.tsx';
4
+
5
+ <Meta
6
+ title="Components/Avatar"
7
+ component={Avatar}
8
+ argTypes={{
9
+ firstName: {control: {type: 'text'}},
10
+ lastName: {control: {type: 'text'}},
11
+ username: {control: {type: 'text'}},
12
+ avatarUrl: {control: {type: 'text'}},
13
+ size: {control: {type: 'select', options: ['default', 'big']}},
14
+ }}
15
+ args={{
16
+ firstName: 'John',
17
+ lastName: 'Doe',
18
+ username: 'admin',
19
+ avatarUrl: undefined,
20
+ size: 'default',
21
+ }}
22
+ />
23
+
24
+ # Avatar
25
+
26
+ ## Usage
27
+
28
+ This component is used to display users avatars. If no avatar is available, first letters of the first and last name are
29
+ displayed with a dedicated color.
30
+
31
+ ## Playground
32
+
33
+ <Canvas>
34
+ <Story name="Standard">
35
+ {args => {
36
+ return <Avatar firstName={'John'} lastName={'Doe'} username={'admin'} {...args} />;
37
+ }}
38
+ </Story>
39
+ </Canvas>
40
+
41
+ <ArgsTable story="Standard" />
42
+
43
+ ## Variation on background colors
44
+
45
+ The background color is based from the username.
46
+
47
+ <Canvas>
48
+ <Story name="Background colors">
49
+ {args => {
50
+ return (
51
+ <>
52
+ <Avatar {...args} firstName={'Albert'} lastName={'Doe'} username={'a'} />
53
+ <Avatar {...args} firstName={'Bertrand'} lastName={'Doe'} username={'b'} />
54
+ <Avatar {...args} firstName={'Chris'} lastName={'Doe'} username={'c'} />
55
+ <Avatar {...args} firstName={'Danny'} lastName={'Doe'} username={'d'} />
56
+ <Avatar {...args} firstName={'Elon'} lastName={'Doe'} username={'e'} />
57
+ <Avatar {...args} firstName={'Fred'} lastName={'Doe'} username={'f'} />
58
+ <Avatar {...args} firstName={'Gus'} lastName={'Doe'} username={'g'} />
59
+ <Avatar {...args} firstName={'Helen'} lastName={'Doe'} username={'h'} />
60
+ <Avatar {...args} firstName={'Isabel'} lastName={'Doe'} username={'i'} />
61
+ <Avatar {...args} firstName={'John'} lastName={'Doe'} username={'j'} />
62
+ <Avatar {...args} firstName={'Kurt'} lastName={'Doe'} username={'k'} />
63
+ <Avatar {...args} firstName={'Leonard'} lastName={'Doe'} username={'l'} />
64
+ </>
65
+ );
66
+ }}
67
+ </Story>
68
+ </Canvas>
69
+
70
+ ## Variation with image
71
+
72
+ <Canvas>
73
+ <Story name="With image">
74
+ {args => {
75
+ return (
76
+ <>
77
+ <Avatar {...args} avatarUrl={'https://picsum.photos/seed/akeneo/32/32'} />
78
+ </>
79
+ );
80
+ }}
81
+ </Story>
82
+ </Canvas>
83
+
84
+ ## Variation on List
85
+
86
+ You can use a dedicated component to display avatar list. After a defined maximum, other avatars are not displayed.
87
+
88
+ <Canvas>
89
+ <Story name="Avatar list">
90
+ {args => {
91
+ return (
92
+ <>
93
+ <Avatars
94
+ max={5}
95
+ title="Helen Doe&#10;Isabel Doe&#10;John Doe&#10;Kurt Doe&#10;Leonard Doe"
96
+ >
97
+ <Avatar
98
+ {...args}
99
+ firstName={'Albert'}
100
+ lastName={'Doe'}
101
+ username={'a'}
102
+ avatarUrl={'https://picsum.photos/seed/akeneo/32/32'}
103
+ />
104
+ <Avatar {...args} firstName={'Bertrand'} lastName={'Doe'} username={'b'} />
105
+ <Avatar
106
+ {...args}
107
+ firstName={'Chris'}
108
+ lastName={'Doe'}
109
+ username={'c'}
110
+ avatarUrl={'https://picsum.photos/seed/bkeneo/32/32'}
111
+ />
112
+ <Avatar {...args} firstName={'Danny'} lastName={'Doe'} username={'d'} />
113
+ <Avatar
114
+ {...args}
115
+ firstName={'Elon'}
116
+ lastName={'Doe'}
117
+ username={'e'}
118
+ avatarUrl={'https://picsum.photos/seed/ckeneo/32/32'}
119
+ />
120
+ <Avatar {...args} firstName={'Fred'} lastName={'Doe'} username={'f'} />
121
+ <Avatar {...args} firstName={'Gus'} lastName={'Doe'} username={'g'} />
122
+ <Avatar {...args} firstName={'Helen'} lastName={'Doe'} username={'h'} />
123
+ <Avatar {...args} firstName={'Isabel'} lastName={'Doe'} username={'i'} />
124
+ <Avatar {...args} firstName={'John'} lastName={'Doe'} username={'j'} />
125
+ <Avatar {...args} firstName={'Kurt'} lastName={'Doe'} username={'k'} />
126
+ <Avatar {...args} firstName={'Leonard'} lastName={'Doe'} username={'l'} />
127
+ </Avatars>
128
+ </>
129
+ );
130
+ }}
131
+ </Story>
132
+ </Canvas>
133
+
134
+ ## Variation on Size
135
+
136
+ <Canvas>
137
+ <Story name="Avatar size">
138
+ {args => {
139
+ return (
140
+ <>
141
+ <Avatar {...args} size={'default'} />
142
+ <Avatar {...args} size={'big'} />
143
+ </>
144
+ );
145
+ }}
146
+ </Story>
147
+ </Canvas>
@@ -0,0 +1,102 @@
1
+ import React, {useMemo} from 'react';
2
+ import styled, {css} from 'styled-components';
3
+ import {useTheme} from '../../hooks';
4
+ import {Override} from '../../shared';
5
+ import {AkeneoThemedProps, getColor} from '../../theme';
6
+
7
+ const AvatarContainer = styled.span<AvatarProps & AkeneoThemedProps>`
8
+ ${({size}) =>
9
+ size === 'default'
10
+ ? css`
11
+ height: 32px;
12
+ width: 32px;
13
+ line-height: 32px;
14
+ font-size: 15px;
15
+ border-radius: 32px;
16
+ `
17
+ : css`
18
+ height: 140px;
19
+ width: 140px;
20
+ line-height: 140px;
21
+ font-size: 66px;
22
+ border-radius: 140px;
23
+ `}
24
+ display: inline-block;
25
+ color: ${getColor('white')};
26
+ text-align: center;
27
+ background-position: center;
28
+ background-repeat: no-repeat;
29
+ background-size: cover;
30
+ text-transform: uppercase;
31
+ cursor: ${({onClick}) => (onClick ? 'pointer' : 'default')};
32
+ `;
33
+
34
+ type AvatarProps = Override<
35
+ React.HTMLAttributes<HTMLSpanElement>,
36
+ {
37
+ /**
38
+ * Username to use as fallback if the avatar is not provided and the Firstname and Lastname are empty.
39
+ */
40
+ username: string;
41
+
42
+ /**
43
+ * Firstname to use as fallback with the Lastname if the avatar is not provided.
44
+ */
45
+ firstName: string;
46
+
47
+ /**
48
+ * Lastname to use as fallback with the Firstname if the avatar is not provided.
49
+ */
50
+ lastName: string;
51
+
52
+ /**
53
+ * Url of the avatar image.
54
+ */
55
+ avatarUrl?: string;
56
+
57
+ /**
58
+ * Size of the avatar.
59
+ */
60
+ size?: 'default' | 'big';
61
+ }
62
+ >;
63
+
64
+ const Avatar = ({username, firstName, lastName, avatarUrl, size = 'default', ...rest}: AvatarProps) => {
65
+ const theme = useTheme();
66
+
67
+ const fallback = (
68
+ firstName.trim().charAt(0) + lastName.trim().charAt(0) || username.substring(0, 2)
69
+ ).toLocaleUpperCase();
70
+ const title = `${firstName} ${lastName}`.trim() || username;
71
+
72
+ const backgroundColor = useMemo(() => {
73
+ const colorId = username.split('').reduce<number>((s, l) => s + l.charCodeAt(0), 0);
74
+ const colors = [
75
+ theme.colorAlternative.green120,
76
+ theme.colorAlternative.darkCyan120,
77
+ theme.colorAlternative.forestGreen120,
78
+ theme.colorAlternative.oliveGreen120,
79
+ theme.colorAlternative.blue120,
80
+ theme.colorAlternative.darkBlue120,
81
+ theme.colorAlternative.hotPink120,
82
+ theme.colorAlternative.red120,
83
+ theme.colorAlternative.coralRed120,
84
+ theme.colorAlternative.yellow120,
85
+ theme.colorAlternative.orange120,
86
+ theme.colorAlternative.chocolate120,
87
+ ];
88
+
89
+ return colors[colorId % colors.length];
90
+ }, [theme, username]);
91
+
92
+ const style = avatarUrl ? {backgroundImage: `url(${avatarUrl})`} : {backgroundColor};
93
+
94
+ return (
95
+ <AvatarContainer size={size} {...rest} style={style} title={title}>
96
+ {avatarUrl ? '' : fallback}
97
+ </AvatarContainer>
98
+ );
99
+ };
100
+
101
+ export {Avatar};
102
+ export type {AvatarProps};
@@ -0,0 +1,79 @@
1
+ import React from 'react';
2
+ import {render, screen} from '../../storybook/test-util';
3
+ import {Avatar} from './Avatar';
4
+
5
+ test('renders', () => {
6
+ render(<Avatar username="john" firstName="John" lastName="Doe" />);
7
+
8
+ const avatar = screen.getByTitle('John Doe');
9
+ expect(avatar).toBeInTheDocument();
10
+ });
11
+
12
+ test('avatar image', () => {
13
+ render(<Avatar username="john" firstName="John" lastName="Doe" avatarUrl="path/to/image" />);
14
+
15
+ const avatar = screen.getByTitle('John Doe');
16
+ expect(avatar).toHaveStyle('background-image: url(path/to/image)');
17
+ });
18
+
19
+ test('deterministic fallback color', () => {
20
+ render(<Avatar username="john" firstName="John" lastName="Doe" />);
21
+
22
+ const avatar = screen.getByTitle('John Doe');
23
+ expect(avatar).toHaveStyle('background-color: rgb(68, 31, 0)');
24
+ });
25
+
26
+ test('fallback to firstname + lastname', () => {
27
+ render(<Avatar username="" firstName="John" lastName="Doe" />);
28
+
29
+ const avatar = screen.getByTitle('John Doe');
30
+ expect(avatar).toHaveTextContent('JD');
31
+ });
32
+
33
+ test('fallback to firstname only', () => {
34
+ render(<Avatar username="" firstName="John" lastName="" />);
35
+
36
+ const avatar = screen.getByTitle('John');
37
+ expect(avatar).toHaveTextContent('J');
38
+ });
39
+
40
+ test('fallback to lastname only', () => {
41
+ render(<Avatar username="" firstName="" lastName="Doe" />);
42
+
43
+ const avatar = screen.getByTitle('Doe');
44
+ expect(avatar).toHaveTextContent('D');
45
+ });
46
+
47
+ test('fallback to username', () => {
48
+ render(<Avatar username="john" firstName="" lastName="" />);
49
+
50
+ const avatar = screen.getByTitle('john');
51
+ expect(avatar).toHaveTextContent('JO');
52
+ });
53
+
54
+ test('initial are converted to uppercase', () => {
55
+ render(<Avatar username="" firstName="john" lastName="doe" />);
56
+
57
+ const avatar = screen.getByTitle('john doe');
58
+ expect(avatar).toHaveTextContent('JD');
59
+ });
60
+
61
+ test('size default', () => {
62
+ render(<Avatar username="john" firstName="John" lastName="Doe" />);
63
+
64
+ const avatar = screen.getByTitle('John Doe');
65
+ expect(avatar).toHaveStyle('width: 32px');
66
+ });
67
+
68
+ test('size big', () => {
69
+ render(<Avatar username="john" firstName="John" lastName="Doe" size="big" />);
70
+
71
+ const avatar = screen.getByTitle('John Doe');
72
+ expect(avatar).toHaveStyle('width: 140px');
73
+ });
74
+
75
+ test('supports ...rest props', () => {
76
+ render(<Avatar username="john" firstName="John" lastName="Doe" data-testid="my_value" />);
77
+
78
+ expect(screen.getByTestId('my_value')).toBeInTheDocument();
79
+ });
@@ -0,0 +1,50 @@
1
+ import React, {Children} from 'react';
2
+ import styled from 'styled-components';
3
+ import {Override} from '../../shared';
4
+ import {AkeneoThemedProps, getColor} from '../../theme';
5
+
6
+ const AvatarListContainer = styled.div<AvatarsProps & AkeneoThemedProps>`
7
+ display: flex;
8
+ flex-direction: row-reverse;
9
+ justify-content: flex-end;
10
+ & > * {
11
+ margin-right: -4px;
12
+ position: relative;
13
+ }
14
+ `;
15
+
16
+ const RemainingAvatar = styled.span`
17
+ height: 32px;
18
+ width: 32px;
19
+ display: inline-block;
20
+ border: 1px solid ${getColor('grey', 10)};
21
+ line-height: 32px;
22
+ text-align: center;
23
+ font-size: 15px;
24
+ border-radius: 32px;
25
+ background-color: ${getColor('white')};
26
+ `;
27
+
28
+ type AvatarsProps = Override<
29
+ React.HTMLAttributes<HTMLDivElement>,
30
+ {
31
+ max: number;
32
+ }
33
+ >;
34
+
35
+ const Avatars = ({max, children, ...rest}: AvatarsProps) => {
36
+ const childrenArray = Children.toArray(children);
37
+ const displayedChildren = childrenArray.slice(0, max);
38
+ const remainingChildrenCount = childrenArray.length - max;
39
+ const reverseChildren = displayedChildren.reverse();
40
+
41
+ return (
42
+ <AvatarListContainer {...rest}>
43
+ {remainingChildrenCount > 0 && <RemainingAvatar>+{remainingChildrenCount}</RemainingAvatar>}
44
+ {reverseChildren}
45
+ </AvatarListContainer>
46
+ );
47
+ };
48
+
49
+ export {Avatars};
50
+ export type {AvatarsProps};
@@ -0,0 +1,41 @@
1
+ import React from 'react';
2
+ import {render, screen} from '../../storybook/test-util';
3
+ import {Avatar} from './Avatar';
4
+ import {Avatars} from './Avatars';
5
+
6
+ test('renders multiple avatars', () => {
7
+ render(
8
+ <Avatars max={2}>
9
+ <Avatar username="john" firstName="John" lastName="Doe" />
10
+ <Avatar username="leonard" firstName="Leonard" lastName="Doe" />
11
+ </Avatars>
12
+ );
13
+
14
+ expect(screen.getByTitle('John Doe')).toBeInTheDocument();
15
+ expect(screen.getByTitle('Leonard Doe')).toBeInTheDocument();
16
+ });
17
+
18
+ test('renders a maximum number of avatars', () => {
19
+ render(
20
+ <Avatars max={1}>
21
+ <Avatar username="john" firstName="John" lastName="Doe" />
22
+ <Avatar username="leonard" firstName="Leonard" lastName="Doe" />
23
+ </Avatars>
24
+ );
25
+
26
+ expect(screen.getByTitle('John Doe')).toBeInTheDocument();
27
+ expect(screen.queryByTitle('Leonard Doe')).not.toBeInTheDocument();
28
+ expect(screen.getByText('+1')).toBeInTheDocument();
29
+ });
30
+
31
+ test('renders no avatar', () => {
32
+ render(<Avatars max={1}></Avatars>);
33
+
34
+ expect(screen.queryByTitle('+1')).not.toBeInTheDocument();
35
+ });
36
+
37
+ test('supports ...rest props', () => {
38
+ render(<Avatars max={1} data-testid="my_value" />);
39
+
40
+ expect(screen.getByTestId('my_value')).toBeInTheDocument();
41
+ });
@@ -149,7 +149,7 @@ type BooleanInputProps = Override<
149
149
  clearLabel?: string;
150
150
  }
151
151
  ) & {
152
- readOnly: boolean;
152
+ readOnly?: boolean;
153
153
  yesLabel: string;
154
154
  noLabel: string;
155
155
  invalid?: boolean;
@@ -165,7 +165,7 @@ const BooleanInput = React.forwardRef<HTMLDivElement, BooleanInputProps>(
165
165
  (
166
166
  {
167
167
  value,
168
- readOnly,
168
+ readOnly = false,
169
169
  onChange,
170
170
  clearable = false,
171
171
  yesLabel,
@@ -150,17 +150,24 @@ type MultiMultiSelectInputProps = Override<
150
150
  * Callback called when the user hit enter on the field.
151
151
  */
152
152
  onSubmit?: () => void;
153
-
154
- /**
155
- * Handler called when the next page is almost reached.
156
- */
157
- onNextPage?: () => void;
158
-
159
- /**
160
- * Handler called when the search value changed
161
- */
162
- onSearchChange?: (searchValue: string) => void;
163
- }
153
+ } & (
154
+ | {
155
+ /**
156
+ * Handler called when the next page is almost reached.
157
+ */
158
+ onNextPage?: () => void;
159
+ /**
160
+ * Handler called when the search value changed
161
+ */
162
+ onSearchChange?: (searchValue: string) => void;
163
+ disableInternalSearch?: false;
164
+ }
165
+ | {
166
+ onNextPage: () => void;
167
+ onSearchChange: (searchValue: string) => void;
168
+ disableInternalSearch: true;
169
+ }
170
+ )
164
171
  >;
165
172
 
166
173
  /**
@@ -183,6 +190,7 @@ const MultiSelectInput = ({
183
190
  verticalPosition,
184
191
  onNextPage,
185
192
  onSearchChange,
193
+ disableInternalSearch = false,
186
194
  'aria-labelledby': ariaLabelledby,
187
195
  ...rest
188
196
  }: MultiMultiSelectInputProps) => {
@@ -211,12 +219,14 @@ const MultiSelectInput = ({
211
219
  return indexedChips;
212
220
  }, {});
213
221
 
214
- const filteredChildren = validChildren.filter(({props}) => {
215
- const childValue = props.value;
216
- const optionValue = childValue + props.children;
222
+ const filteredChildren = disableInternalSearch
223
+ ? validChildren
224
+ : validChildren.filter(({props}) => {
225
+ const childValue = props.value;
226
+ const optionValue = childValue + props.children;
217
227
 
218
- return !value.includes(childValue) && optionValue.toLowerCase().includes(searchValue.toLowerCase());
219
- });
228
+ return !value.includes(childValue) && optionValue.toLowerCase().includes(searchValue.toLowerCase());
229
+ });
220
230
 
221
231
  const handleEnter = () => {
222
232
  if (filteredChildren.length > 0 && dropdownIsOpen) {
@@ -77,6 +77,51 @@ test('it handles search', () => {
77
77
  expect(onChange).toHaveBeenCalledTimes(2);
78
78
  });
79
79
 
80
+ test('it handles external search', () => {
81
+ const onChange = jest.fn();
82
+ const onNextPage = jest.fn();
83
+ const onSearchChange = jest.fn();
84
+
85
+ const observe = jest.fn();
86
+ const unobserve = jest.fn();
87
+ window.IntersectionObserver = jest.fn(() => ({
88
+ observe,
89
+ unobserve,
90
+ })) as unknown as jest.Mock<IntersectionObserver>;
91
+
92
+ render(
93
+ <MultiSelectInput
94
+ openLabel="Open"
95
+ value={[]}
96
+ onChange={onChange}
97
+ placeholder="Placeholder"
98
+ emptyResultLabel="Empty result"
99
+ onNextPage={onNextPage}
100
+ onSearchChange={onSearchChange}
101
+ disableInternalSearch={true}
102
+ removeLabel="Remove"
103
+ >
104
+ <MultiSelectInput.Option value="en_US">English</MultiSelectInput.Option>
105
+ <MultiSelectInput.Option value="fr_FR">French</MultiSelectInput.Option>
106
+ <MultiSelectInput.Option value="de_DE">German</MultiSelectInput.Option>
107
+ <MultiSelectInput.Option value="es_ES">Spanish</MultiSelectInput.Option>
108
+ </MultiSelectInput>
109
+ );
110
+
111
+ const input = screen.getByRole('textbox');
112
+ fireEvent.click(input);
113
+ fireEvent.change(input, {target: {value: 'Fr'}});
114
+
115
+ const germanOption = screen.queryByText('German');
116
+ expect(germanOption).toBeInTheDocument();
117
+ const usOption = screen.queryByText('English');
118
+ expect(usOption).toBeInTheDocument();
119
+ const spanishOption = screen.queryByText('Spanish');
120
+ expect(spanishOption).toBeInTheDocument();
121
+ const frenchOption = screen.getByText('French');
122
+ expect(frenchOption).toBeInTheDocument();
123
+ });
124
+
80
125
  test('it handles empty cases', () => {
81
126
  const onChange = jest.fn();
82
127
  render(
@@ -178,17 +178,24 @@ type SelectInputProps = Override<
178
178
  * Force the vertical position of the overlay.
179
179
  */
180
180
  verticalPosition?: VerticalPosition;
181
-
182
- /**
183
- * Handler called when the next page is almost reached.
184
- */
185
- onNextPage?: () => void;
186
-
187
- /**
188
- * Handler called when the search value changed
189
- */
190
- onSearchChange?: (searchValue: string) => void;
191
- }
181
+ } & (
182
+ | {
183
+ /**
184
+ * Handler called when the next page is almost reached.
185
+ */
186
+ onNextPage?: () => void;
187
+ /**
188
+ * Handler called when the search value changed
189
+ */
190
+ onSearchChange?: (searchValue: string) => void;
191
+ disableInternalSearch?: false;
192
+ }
193
+ | {
194
+ onNextPage: () => void;
195
+ onSearchChange: (searchValue: string) => void;
196
+ disableInternalSearch: true;
197
+ }
198
+ )
192
199
  >;
193
200
 
194
201
  /**
@@ -209,6 +216,7 @@ const SelectInput = ({
209
216
  verticalPosition,
210
217
  onNextPage,
211
218
  onSearchChange,
219
+ disableInternalSearch = false,
212
220
  'aria-labelledby': ariaLabelledby,
213
221
  ...rest
214
222
  }: SelectInputProps) => {
@@ -235,14 +243,16 @@ const SelectInput = ({
235
243
  return optionCodes;
236
244
  }, []);
237
245
 
238
- const filteredChildren = validChildren.filter(child => {
239
- const content = typeof child.props.children === 'string' ? child.props.children : '';
240
- const title = child.props.title ?? '';
241
- const value = child.props.value;
242
- const optionValue = value + content + title;
246
+ const filteredChildren = disableInternalSearch
247
+ ? validChildren
248
+ : validChildren.filter(child => {
249
+ const content = typeof child.props.children === 'string' ? child.props.children : '';
250
+ const title = child.props.title ?? '';
251
+ const value = child.props.value;
252
+ const optionValue = value + content + title;
243
253
 
244
- return optionValue.toLowerCase().includes(searchValue.toLowerCase());
245
- });
254
+ return optionValue.toLowerCase().includes(searchValue.toLowerCase());
255
+ });
246
256
 
247
257
  const currentValueElement =
248
258
  validChildren.find(child => {