@xqmsg/ui-core 0.9.3 → 0.11.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/components/button/index.d.ts +3 -7
- package/dist/components/input/Input.stories.d.ts +4 -0
- package/dist/components/input/InputTypes.d.ts +5 -3
- package/dist/components/input/StackedInput/StackedInput.d.ts +0 -3
- package/dist/components/input/StackedMultiSelect/index.d.ts +1 -1
- package/dist/components/input/StackedSelect/StackedSelect.d.ts +7 -3
- package/dist/components/input/components/dropdown/index.d.ts +10 -0
- package/dist/components/input/components/label/index.d.ts +9 -0
- package/dist/components/input/components/token/Token.stories.d.ts +5 -0
- package/dist/components/input/components/token/index.d.ts +7 -0
- package/dist/components/input/index.d.ts +1 -3
- package/dist/theme/components/button.d.ts +68 -207
- package/dist/theme/components/form-error.d.ts +3 -3
- package/dist/theme/components/form-label.d.ts +4 -6
- package/dist/theme/components/form.d.ts +3 -3
- package/dist/theme/components/input.d.ts +32 -161
- package/dist/theme/components/select.d.ts +27 -153
- package/dist/theme/components/textarea.d.ts +10 -117
- package/dist/theme/foundations/colors.d.ts +68 -16
- package/dist/ui-core.cjs.development.js +594 -860
- package/dist/ui-core.cjs.development.js.map +1 -1
- package/dist/ui-core.cjs.production.min.js +1 -1
- package/dist/ui-core.cjs.production.min.js.map +1 -1
- package/dist/ui-core.esm.js +598 -864
- package/dist/ui-core.esm.js.map +1 -1
- package/package.json +1 -2
- package/src/components/banner/index.tsx +7 -1
- package/src/components/button/Button.stories.tsx +19 -7
- package/src/components/button/index.tsx +7 -19
- package/src/components/button/spinner/index.tsx +2 -7
- package/src/components/input/Input.stories.tsx +60 -58
- package/src/components/input/InputTypes.ts +7 -1
- package/src/components/input/StackedInput/StackedInput.tsx +3 -15
- package/src/components/input/StackedMultiSelect/components/MultiValue/index.tsx +2 -2
- package/src/components/input/StackedMultiSelect/index.tsx +88 -92
- package/src/components/input/StackedPilledInput/index.tsx +139 -56
- package/src/components/input/StackedSelect/StackedSelect.tsx +63 -20
- package/src/components/input/StackedSelect/assets/svg/subtract.svg +3 -0
- package/src/components/input/components/dropdown/index.tsx +80 -0
- package/src/components/input/components/label/index.tsx +24 -0
- package/src/components/input/components/token/Token.stories.tsx +22 -0
- package/src/components/input/components/token/assets/svg/close.svg +3 -0
- package/src/components/input/components/token/index.tsx +37 -0
- package/src/components/input/index.tsx +7 -20
- package/src/components/loading/index.tsx +1 -1
- package/src/components/table/Table.stories.tsx +9 -1
- package/src/components/table/index.tsx +1 -1
- package/src/components/table/loading/index.tsx +2 -2
- package/src/components/tabs/index.tsx +1 -1
- package/src/components/text/index.tsx +1 -1
- package/src/theme/components/alert.ts +4 -4
- package/src/theme/components/button.ts +45 -186
- package/src/theme/components/form-error.ts +11 -14
- package/src/theme/components/form-label.ts +8 -8
- package/src/theme/components/form.ts +10 -13
- package/src/theme/components/input.ts +17 -191
- package/src/theme/components/link.ts +2 -1
- package/src/theme/components/select.ts +5 -10
- package/src/theme/components/tabs.ts +3 -3
- package/src/theme/components/textarea.ts +2 -38
- package/src/theme/customXQChakraTheme.ts +0 -2
- package/src/theme/foundations/colors.ts +31 -10
- package/src/theme/foundations/shadows.ts +3 -3
- package/dist/components/input/components/InputTag/index.d.ts +0 -7
- package/dist/theme/components/menu.d.ts +0 -48
- package/src/components/input/components/InputTag/index.tsx +0 -24
- package/src/theme/components/menu.ts +0 -70
package/package.json
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
{
|
|
2
|
-
"version": "0.
|
|
2
|
+
"version": "0.11.0",
|
|
3
3
|
"license": "MIT",
|
|
4
4
|
"main": "dist/index.js",
|
|
5
5
|
"typings": "dist/index.d.ts",
|
|
@@ -98,7 +98,6 @@
|
|
|
98
98
|
"@hookform/resolvers": "^2.9.7",
|
|
99
99
|
"axios": "^0.27.2",
|
|
100
100
|
"framer-motion": "6.3.0",
|
|
101
|
-
"ip-regex": "^5.0.0",
|
|
102
101
|
"react": "^18.0.0",
|
|
103
102
|
"react-dom": "^18.0.0",
|
|
104
103
|
"react-hook-form": "^7.34.0",
|
|
@@ -11,6 +11,7 @@ import ErrorIcon from './assets/svg/error.svg';
|
|
|
11
11
|
import PositiveIcon from './assets/svg/positive.svg';
|
|
12
12
|
import NeutralIcon from './assets/svg/neutral.svg';
|
|
13
13
|
import WarningIcon from './assets/svg/warning.svg';
|
|
14
|
+
import colors from 'src/theme/foundations/colors';
|
|
14
15
|
|
|
15
16
|
export type BannerVariant = 'positive' | 'warning' | 'error' | 'neutral';
|
|
16
17
|
|
|
@@ -52,7 +53,12 @@ export const Banner: React.FC<BannerProps> = ({
|
|
|
52
53
|
{message}
|
|
53
54
|
{onClick && buttonText && (
|
|
54
55
|
<Flex pt="8px" justifyContent="flex-end">
|
|
55
|
-
<Button
|
|
56
|
+
<Button
|
|
57
|
+
size="sm"
|
|
58
|
+
bg="white"
|
|
59
|
+
color={colors.fill.action}
|
|
60
|
+
onClick={onClick}
|
|
61
|
+
>
|
|
56
62
|
{buttonText}
|
|
57
63
|
</Button>
|
|
58
64
|
</Flex>
|
|
@@ -1,11 +1,17 @@
|
|
|
1
1
|
import React from 'react';
|
|
2
2
|
import { Meta, Story } from '@storybook/react';
|
|
3
3
|
import { Button, ButtonProps } from '.';
|
|
4
|
+
import { Box, Flex } from '@chakra-ui/react';
|
|
4
5
|
|
|
5
6
|
const meta: Meta<ButtonProps> = {
|
|
6
7
|
title: 'Button example',
|
|
7
8
|
component: Button,
|
|
8
9
|
argTypes: {
|
|
10
|
+
width: {
|
|
11
|
+
control: null,
|
|
12
|
+
description:
|
|
13
|
+
'`variable` adopts 100% of the width of the parent component, whereas `fixed` fits the label content to the button',
|
|
14
|
+
},
|
|
9
15
|
text: {
|
|
10
16
|
control: {
|
|
11
17
|
type: 'text',
|
|
@@ -24,23 +30,29 @@ const meta: Meta<ButtonProps> = {
|
|
|
24
30
|
control: 'boolean',
|
|
25
31
|
description: 'Disabled state of the button',
|
|
26
32
|
},
|
|
27
|
-
size: {
|
|
28
|
-
control: 'select',
|
|
29
|
-
options: ['xs', 'sm', 'md', 'lg'],
|
|
30
|
-
description: 'The chakra based size prop for the button',
|
|
31
|
-
},
|
|
32
33
|
},
|
|
33
34
|
parameters: {
|
|
34
35
|
controls: { expanded: true },
|
|
35
36
|
},
|
|
36
37
|
};
|
|
37
38
|
export default meta;
|
|
38
|
-
const Template: Story<ButtonProps> = args =>
|
|
39
|
+
const Template: Story<ButtonProps> = args => (
|
|
40
|
+
<Flex flexDir="column" height="200px" justifyContent="space-between">
|
|
41
|
+
<Button {...args} text="Solid Fixed" variant="solid" width="fixed" />
|
|
42
|
+
<Button {...args} text="Outline Fixed" variant="outline" width="fixed" />
|
|
43
|
+
<Button {...args} text="Solid Variable" variant="solid" width="variable" />
|
|
44
|
+
<Button
|
|
45
|
+
{...args}
|
|
46
|
+
text="Outline Variable"
|
|
47
|
+
variant="outline"
|
|
48
|
+
width="variable"
|
|
49
|
+
/>
|
|
50
|
+
</Flex>
|
|
51
|
+
);
|
|
39
52
|
|
|
40
53
|
export const Default = Template.bind({});
|
|
41
54
|
Default.args = {
|
|
42
55
|
onClick: () => alert('This is a button click!'),
|
|
43
|
-
text: 'Button',
|
|
44
56
|
type: 'button',
|
|
45
57
|
disabled: false,
|
|
46
58
|
};
|
|
@@ -1,52 +1,40 @@
|
|
|
1
1
|
import React from 'react';
|
|
2
|
-
import {
|
|
3
|
-
Button as ChakraButton,
|
|
4
|
-
ButtonProps as ChakraButtonProps,
|
|
5
|
-
} from '@chakra-ui/react';
|
|
2
|
+
import { Button as ChakraButton } from '@chakra-ui/react';
|
|
6
3
|
import buttonTheme from '../../theme/components/button';
|
|
7
4
|
|
|
8
5
|
export interface ButtonProps {
|
|
9
6
|
text: string;
|
|
10
7
|
onClick?: () => void;
|
|
8
|
+
width: 'variable' | 'fixed';
|
|
11
9
|
variant?: keyof typeof buttonTheme.variants;
|
|
12
|
-
|
|
13
|
-
type: 'button' | 'submit' | 'reset' | undefined;
|
|
10
|
+
type?: 'button' | 'submit' | 'reset';
|
|
14
11
|
ariaLabel: string;
|
|
15
|
-
size?: 'sm' | 'md' | 'lg' | 'xs';
|
|
16
12
|
disabled?: boolean;
|
|
17
13
|
className?: string;
|
|
18
|
-
style?: React.CSSProperties;
|
|
19
|
-
width?: string | number;
|
|
20
14
|
}
|
|
21
15
|
|
|
22
16
|
/**
|
|
23
17
|
* A functional React component utilized to render the `Button` component
|
|
24
18
|
*/
|
|
25
|
-
export const Button: React.FC<ButtonProps
|
|
19
|
+
export const Button: React.FC<ButtonProps> = ({
|
|
26
20
|
onClick,
|
|
27
21
|
text,
|
|
28
|
-
type,
|
|
22
|
+
type = 'button',
|
|
29
23
|
ariaLabel,
|
|
30
24
|
variant = 'solid',
|
|
31
|
-
colorScheme = 'primary',
|
|
32
|
-
style,
|
|
33
|
-
size = 'md',
|
|
34
25
|
disabled,
|
|
35
26
|
className,
|
|
36
|
-
|
|
27
|
+
width,
|
|
37
28
|
}) => {
|
|
38
29
|
return (
|
|
39
30
|
<ChakraButton
|
|
40
31
|
onClick={onClick}
|
|
41
32
|
type={type}
|
|
42
33
|
variant={variant}
|
|
43
|
-
colorScheme={colorScheme}
|
|
44
|
-
size={size}
|
|
45
34
|
disabled={disabled}
|
|
46
35
|
aria-label={ariaLabel}
|
|
47
|
-
style={style}
|
|
48
36
|
className={className}
|
|
49
|
-
{
|
|
37
|
+
width={width === 'variable' ? '100%' : 'fit-content'}
|
|
50
38
|
>
|
|
51
39
|
{text}
|
|
52
40
|
</ChakraButton>
|
|
@@ -15,25 +15,20 @@ export const SpinnerButton: React.FC<SpinnerButtonProps> = ({
|
|
|
15
15
|
onClick,
|
|
16
16
|
type,
|
|
17
17
|
ariaLabel,
|
|
18
|
-
style,
|
|
19
18
|
variant = 'solid',
|
|
20
|
-
|
|
21
|
-
size = 'md',
|
|
19
|
+
|
|
22
20
|
disabled,
|
|
23
21
|
className,
|
|
24
22
|
}) => {
|
|
25
23
|
return (
|
|
26
24
|
<Button
|
|
27
|
-
spinner={<Spinner size={
|
|
25
|
+
spinner={<Spinner size={'md'} />}
|
|
28
26
|
isLoading={isLoading}
|
|
29
27
|
onClick={onClick}
|
|
30
28
|
type={type}
|
|
31
29
|
variant={variant}
|
|
32
|
-
colorScheme={colorScheme}
|
|
33
|
-
size={size}
|
|
34
30
|
disabled={disabled}
|
|
35
31
|
aria-label={ariaLabel}
|
|
36
|
-
style={style}
|
|
37
32
|
className={className}
|
|
38
33
|
>
|
|
39
34
|
{text}
|
|
@@ -5,8 +5,7 @@ import { Input, InputProps } from '.';
|
|
|
5
5
|
import { useFormHandler } from '../form/hooks/useFormHandler';
|
|
6
6
|
import * as Yup from 'yup';
|
|
7
7
|
import { Form } from '../form';
|
|
8
|
-
import
|
|
9
|
-
import { useEffect, useMemo } from '@storybook/addons';
|
|
8
|
+
import { useMemo } from '@storybook/addons';
|
|
10
9
|
|
|
11
10
|
const meta: Meta<InputProps<StoryFormSchema>> = {
|
|
12
11
|
title: 'Input example',
|
|
@@ -19,17 +18,6 @@ const meta: Meta<InputProps<StoryFormSchema>> = {
|
|
|
19
18
|
description: 'The label for the input',
|
|
20
19
|
},
|
|
21
20
|
inputType: {
|
|
22
|
-
control: 'select',
|
|
23
|
-
options: [
|
|
24
|
-
'text',
|
|
25
|
-
'textarea',
|
|
26
|
-
'radio',
|
|
27
|
-
'select',
|
|
28
|
-
'checkbox',
|
|
29
|
-
'multi-select',
|
|
30
|
-
'pilled-text',
|
|
31
|
-
'switch',
|
|
32
|
-
],
|
|
33
21
|
description: 'The type of the input',
|
|
34
22
|
},
|
|
35
23
|
options: {
|
|
@@ -70,47 +58,28 @@ const meta: Meta<InputProps<StoryFormSchema>> = {
|
|
|
70
58
|
|
|
71
59
|
interface StoryFormSchema {
|
|
72
60
|
prop?: string;
|
|
61
|
+
prop2?: string;
|
|
62
|
+
prop3?: string;
|
|
63
|
+
prop4?: string;
|
|
64
|
+
prop5?: string;
|
|
73
65
|
}
|
|
74
66
|
|
|
75
67
|
const onStubbedSubmit = () => null;
|
|
76
68
|
|
|
77
69
|
const storyFormDefaultValues: StoryFormSchema = {
|
|
78
70
|
prop: '',
|
|
71
|
+
prop2: '',
|
|
72
|
+
prop3: '',
|
|
73
|
+
prop4: '',
|
|
74
|
+
prop5: '',
|
|
79
75
|
};
|
|
80
76
|
|
|
81
77
|
const storyFormSchema: Yup.SchemaOf<StoryFormSchema> = Yup.object().shape({
|
|
82
|
-
prop: Yup.string()
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
const ipStringsToArray = value.split(',');
|
|
88
|
-
const isIPValidArray = ipStringsToArray.map(ip => {
|
|
89
|
-
return ipRegex({ exact: true }).test(ip as string);
|
|
90
|
-
});
|
|
91
|
-
|
|
92
|
-
if (isIPValidArray && isIPValidArray.includes(false)) {
|
|
93
|
-
const malformedIPList = isIPValidArray
|
|
94
|
-
// eslint-disable-next-line
|
|
95
|
-
.map((testedIP, i) => {
|
|
96
|
-
if (testedIP === false) {
|
|
97
|
-
const malformedIP = ipStringsToArray[i];
|
|
98
|
-
|
|
99
|
-
return malformedIP.trim();
|
|
100
|
-
}
|
|
101
|
-
})
|
|
102
|
-
.filter(invalidIP => invalidIP !== undefined);
|
|
103
|
-
|
|
104
|
-
const errorMessage = `Malformed IPs: ${malformedIPList.join(', ')}`;
|
|
105
|
-
return testContext.createError({
|
|
106
|
-
message:
|
|
107
|
-
errorMessage.length <= 45 ? errorMessage : `${errorMessage}...`,
|
|
108
|
-
});
|
|
109
|
-
}
|
|
110
|
-
}
|
|
111
|
-
return true;
|
|
112
|
-
}
|
|
113
|
-
),
|
|
78
|
+
prop: Yup.string(),
|
|
79
|
+
prop2: Yup.string(),
|
|
80
|
+
prop3: Yup.string(),
|
|
81
|
+
prop4: Yup.string(),
|
|
82
|
+
prop5: Yup.string(),
|
|
114
83
|
});
|
|
115
84
|
|
|
116
85
|
export default meta;
|
|
@@ -125,23 +94,37 @@ const Template: Story<InputProps<StoryFormSchema>> = args => {
|
|
|
125
94
|
|
|
126
95
|
const { form } = formHandler;
|
|
127
96
|
|
|
128
|
-
const valueErrorMessage = useMemo(() => {
|
|
129
|
-
if (form.formState.errors) return form.formState.errors.prop?.message;
|
|
130
|
-
}, [form.formState.errors]);
|
|
131
|
-
|
|
132
|
-
console.log(form.watch('prop'));
|
|
133
|
-
|
|
134
|
-
useEffect(() => {
|
|
135
|
-
form.resetField('prop');
|
|
136
|
-
}, [args.inputType]);
|
|
137
|
-
|
|
138
97
|
return (
|
|
139
98
|
<Form formHandler={formHandler}>
|
|
140
99
|
<Input
|
|
141
100
|
{...args}
|
|
101
|
+
inputType="multi-select"
|
|
102
|
+
setValue={form.setValue}
|
|
103
|
+
name="prop5"
|
|
104
|
+
/>
|
|
105
|
+
<Input
|
|
106
|
+
{...args}
|
|
107
|
+
inputType="select"
|
|
108
|
+
setValue={form.setValue}
|
|
109
|
+
name="prop4"
|
|
110
|
+
/>
|
|
111
|
+
<Input
|
|
112
|
+
{...args}
|
|
113
|
+
inputType="text"
|
|
114
|
+
name="prop3"
|
|
115
|
+
onChange={e => form.setValue('prop3', e.target.value)}
|
|
116
|
+
/>
|
|
117
|
+
<Input
|
|
118
|
+
{...args}
|
|
119
|
+
inputType="textarea"
|
|
120
|
+
name="prop2"
|
|
121
|
+
onChange={e => form.setValue('prop2', e.target.value)}
|
|
122
|
+
/>
|
|
123
|
+
<Input
|
|
124
|
+
{...args}
|
|
125
|
+
name="prop"
|
|
126
|
+
inputType="pilled-text"
|
|
142
127
|
setValue={form.setValue}
|
|
143
|
-
errorText={valueErrorMessage}
|
|
144
|
-
isInvalid={!!valueErrorMessage}
|
|
145
128
|
/>
|
|
146
129
|
</Form>
|
|
147
130
|
);
|
|
@@ -151,19 +134,38 @@ export const Default = Template.bind({});
|
|
|
151
134
|
Default.args = {
|
|
152
135
|
label: 'Input Label',
|
|
153
136
|
inputType: 'text',
|
|
154
|
-
name: 'prop',
|
|
155
137
|
options: [
|
|
138
|
+
{ value: 'section_header', label: 'Section 1', sortValue: 0 },
|
|
156
139
|
{
|
|
157
140
|
value: 'value1',
|
|
158
141
|
label: 'Value 1',
|
|
142
|
+
sortValue: 1,
|
|
159
143
|
},
|
|
160
144
|
{
|
|
161
145
|
value: 'value2',
|
|
162
146
|
label: 'Value 2',
|
|
147
|
+
sortValue: 2,
|
|
163
148
|
},
|
|
164
149
|
{
|
|
165
150
|
value: 'value3',
|
|
166
151
|
label: 'Value 3',
|
|
152
|
+
sortValue: 3,
|
|
153
|
+
},
|
|
154
|
+
{
|
|
155
|
+
value: 'value4',
|
|
156
|
+
label: 'Value 4',
|
|
157
|
+
sortValue: 4,
|
|
158
|
+
},
|
|
159
|
+
{ value: 'section_header', label: 'Section 2', sortValue: 5 },
|
|
160
|
+
{
|
|
161
|
+
value: 'value5',
|
|
162
|
+
label: 'Value 5',
|
|
163
|
+
sortValue: 6,
|
|
164
|
+
},
|
|
165
|
+
{
|
|
166
|
+
value: 'value6',
|
|
167
|
+
label: 'Value 6',
|
|
168
|
+
sortValue: 7,
|
|
167
169
|
},
|
|
168
170
|
],
|
|
169
171
|
isRequired: true,
|
|
@@ -18,7 +18,13 @@ export type InputType =
|
|
|
18
18
|
| 'checkbox'
|
|
19
19
|
| 'switch';
|
|
20
20
|
|
|
21
|
-
export type
|
|
21
|
+
export type FieldOption = {
|
|
22
|
+
label: string;
|
|
23
|
+
value: string | 'section_header';
|
|
24
|
+
sortValue: number;
|
|
25
|
+
};
|
|
26
|
+
|
|
27
|
+
export type FieldOptions = FieldOption[];
|
|
22
28
|
|
|
23
29
|
export interface ValidationProps {
|
|
24
30
|
isRequired: boolean;
|
|
@@ -1,29 +1,17 @@
|
|
|
1
1
|
import React from 'react';
|
|
2
|
-
import { Input
|
|
2
|
+
import { Input } from '@chakra-ui/react';
|
|
3
3
|
import { InputFieldProps } from '../InputTypes';
|
|
4
4
|
|
|
5
5
|
export interface StackedInputProps extends InputFieldProps {
|
|
6
|
-
label?: string;
|
|
7
6
|
isRequired?: boolean;
|
|
8
|
-
leftElement?: React.ReactNode;
|
|
9
|
-
rightElement?: React.ReactNode;
|
|
10
7
|
}
|
|
11
8
|
|
|
12
9
|
/**
|
|
13
10
|
* A functional React component utilized to render the `StackedInput` component.
|
|
14
11
|
*/
|
|
15
12
|
const StackedInput = React.forwardRef<HTMLInputElement, StackedInputProps>(
|
|
16
|
-
(
|
|
17
|
-
{ type
|
|
18
|
-
_ref
|
|
19
|
-
) => {
|
|
20
|
-
return (
|
|
21
|
-
<InputGroup>
|
|
22
|
-
{leftElement && leftElement}
|
|
23
|
-
<Input ref={_ref} type={type} isRequired={isRequired} {...props} />
|
|
24
|
-
{rightElement && rightElement}
|
|
25
|
-
</InputGroup>
|
|
26
|
-
);
|
|
13
|
+
({ type = 'text', isRequired, ...props }, _ref) => {
|
|
14
|
+
return <Input ref={_ref} type={type} isRequired={isRequired} {...props} />;
|
|
27
15
|
}
|
|
28
16
|
);
|
|
29
17
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { Box } from '@chakra-ui/react';
|
|
2
2
|
import React, { PropsWithChildren } from 'react';
|
|
3
|
-
import InputTag from '../../../components/
|
|
3
|
+
import InputTag from '../../../components/token';
|
|
4
4
|
|
|
5
5
|
interface MultiValueProps extends PropsWithChildren {
|
|
6
6
|
clearValue: () => void;
|
|
@@ -13,7 +13,7 @@ interface MultiValueProps extends PropsWithChildren {
|
|
|
13
13
|
const MultiValue: React.FC<MultiValueProps> = ({ children, clearValue }) => {
|
|
14
14
|
return (
|
|
15
15
|
<Box marginRight="5px">
|
|
16
|
-
<InputTag
|
|
16
|
+
<InputTag label={children} onDelete={clearValue} />
|
|
17
17
|
</Box>
|
|
18
18
|
);
|
|
19
19
|
};
|
|
@@ -1,15 +1,20 @@
|
|
|
1
|
-
import React, { useEffect, useState } from 'react';
|
|
2
|
-
import { Flex } from '@chakra-ui/react';
|
|
3
|
-
import {
|
|
4
|
-
|
|
1
|
+
import React, { useEffect, useRef, useState } from 'react';
|
|
2
|
+
import { Box, Flex, Text, Image, useOutsideClick } from '@chakra-ui/react';
|
|
3
|
+
import {
|
|
4
|
+
FieldOption,
|
|
5
|
+
FieldOptions,
|
|
6
|
+
ReactSelectFieldProps,
|
|
7
|
+
} from '../InputTypes';
|
|
5
8
|
import colors from '../../../theme/foundations/colors';
|
|
6
|
-
import MultiValue from './components/MultiValue';
|
|
7
9
|
import {
|
|
8
10
|
Control,
|
|
9
11
|
FieldValues,
|
|
10
12
|
UseFormSetValue,
|
|
11
13
|
useWatch,
|
|
12
14
|
} from 'react-hook-form';
|
|
15
|
+
import SubtractIcon from '../StackedSelect/assets/svg/subtract.svg';
|
|
16
|
+
import { Dropdown } from '../components/dropdown';
|
|
17
|
+
import Token from '../components/token';
|
|
13
18
|
|
|
14
19
|
export interface StackedMultiSelectProps extends ReactSelectFieldProps {
|
|
15
20
|
options: FieldOptions;
|
|
@@ -21,22 +26,26 @@ export interface StackedMultiSelectProps extends ReactSelectFieldProps {
|
|
|
21
26
|
* A functional React component utilized to render the `StackedMultiSelect` component.
|
|
22
27
|
*/
|
|
23
28
|
const StackedMultiSelect = React.forwardRef<
|
|
24
|
-
|
|
29
|
+
HTMLInputElement,
|
|
25
30
|
StackedMultiSelectProps
|
|
26
|
-
>(({ options, setValue, control, name }, _ref) => {
|
|
31
|
+
>(({ options, setValue, control, name, placeholder, disabled }, _ref) => {
|
|
27
32
|
const watchedValue = useWatch({ control, name: name as string });
|
|
33
|
+
const dropdownRef = useRef(null);
|
|
34
|
+
|
|
35
|
+
const [localValues, setLocalValues] = useState<FieldOptions>([]);
|
|
36
|
+
const [localOptions, setLocalOptions] = useState<FieldOptions>(options);
|
|
37
|
+
const [isFocussed, setIsFocussed] = useState(false);
|
|
28
38
|
|
|
29
|
-
|
|
30
|
-
const [localValue, setLocalValue] = useState([]);
|
|
39
|
+
useOutsideClick({ ref: dropdownRef, handler: () => setIsFocussed(false) });
|
|
31
40
|
|
|
32
41
|
// gets latest watched form value (common delimited) from RHF state and creates a list
|
|
33
42
|
useEffect(() => {
|
|
34
43
|
if (watchedValue !== undefined && !watchedValue.length) {
|
|
35
|
-
|
|
44
|
+
setLocalValues([]);
|
|
36
45
|
}
|
|
37
46
|
|
|
38
47
|
if (watchedValue !== undefined && watchedValue?.length) {
|
|
39
|
-
|
|
48
|
+
setLocalValues(
|
|
40
49
|
watchedValue
|
|
41
50
|
.split(',')
|
|
42
51
|
.filter(Boolean)
|
|
@@ -47,98 +56,85 @@ const StackedMultiSelect = React.forwardRef<
|
|
|
47
56
|
}
|
|
48
57
|
}, [options, watchedValue]);
|
|
49
58
|
|
|
50
|
-
const
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
clearValue={() => {
|
|
55
|
-
const arrayValue = watchedValue
|
|
56
|
-
.split(',')
|
|
57
|
-
.filter((_: string, index: number) => index !== props.index);
|
|
58
|
-
|
|
59
|
-
setLocalValue(
|
|
60
|
-
arrayValue.map((value: string) =>
|
|
61
|
-
options.find(option => option.value === value)
|
|
62
|
-
)
|
|
63
|
-
);
|
|
64
|
-
|
|
65
|
-
setValue(name as string, arrayValue.join(','), {
|
|
66
|
-
shouldValidate: true,
|
|
67
|
-
shouldDirty: true,
|
|
68
|
-
});
|
|
69
|
-
}}
|
|
70
|
-
>
|
|
71
|
-
{props.children}
|
|
72
|
-
</MultiValue>
|
|
73
|
-
),
|
|
74
|
-
};
|
|
59
|
+
const handleChange = (option: FieldOption) => {
|
|
60
|
+
const newValue = [...localValues, option]
|
|
61
|
+
.map(({ value }) => value)
|
|
62
|
+
.join(',');
|
|
75
63
|
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
}
|
|
64
|
+
setValue(name as string, newValue, {
|
|
65
|
+
shouldValidate: true,
|
|
66
|
+
shouldDirty: true,
|
|
67
|
+
});
|
|
68
|
+
|
|
69
|
+
setLocalOptions(prevLocalOptions =>
|
|
70
|
+
prevLocalOptions.filter(prevLocalOption => prevLocalOption !== option)
|
|
84
71
|
);
|
|
72
|
+
|
|
73
|
+
setLocalValues(prevLocalValues => [...prevLocalValues, option]);
|
|
85
74
|
};
|
|
86
75
|
|
|
87
|
-
const
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
76
|
+
const handleDelete = (option: FieldOption) => {
|
|
77
|
+
const newValue = localValues
|
|
78
|
+
.filter(localValue => localValue !== option)
|
|
79
|
+
.map(({ value }) => value)
|
|
80
|
+
.join(',');
|
|
91
81
|
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
82
|
+
setValue(name as string, newValue, {
|
|
83
|
+
shouldValidate: true,
|
|
84
|
+
shouldDirty: true,
|
|
85
|
+
});
|
|
95
86
|
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
<Flex alignItems="center" justifyContent="space-between">
|
|
99
|
-
<span>{data.label}</span>
|
|
100
|
-
</Flex>
|
|
87
|
+
setLocalOptions(prevLocalOptions =>
|
|
88
|
+
[...prevLocalOptions, option].sort((a, b) => a.sortValue - b.sortValue)
|
|
101
89
|
);
|
|
102
|
-
};
|
|
103
90
|
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
fontSize: '16px',
|
|
108
|
-
color: '#202020',
|
|
109
|
-
backgroundColor: '#FFFFFF',
|
|
110
|
-
border: '1px solid',
|
|
111
|
-
borderColor: colors.gray[200],
|
|
112
|
-
minHeight: '48px',
|
|
113
|
-
display: 'flex',
|
|
114
|
-
padding: '2px 6px',
|
|
115
|
-
}),
|
|
116
|
-
menu: () => ({
|
|
117
|
-
boxShadow: '0 5px 5px 0 rgba(16, 27, 79, 0.15)',
|
|
118
|
-
borderRadius: '6px',
|
|
119
|
-
backgroundColor: 'white',
|
|
120
|
-
}),
|
|
121
|
-
indicatorsContainer: () => ({ display: 'none' }),
|
|
122
|
-
placeholder: () => ({ color: '#CBCDCF', fontSize: '16px' }),
|
|
91
|
+
setLocalValues(prevLocalValues =>
|
|
92
|
+
prevLocalValues.filter(prevLocalValue => prevLocalValue !== option)
|
|
93
|
+
);
|
|
123
94
|
};
|
|
124
95
|
|
|
125
96
|
return (
|
|
126
|
-
<
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
97
|
+
<Box ref={dropdownRef} position="relative">
|
|
98
|
+
<Flex
|
|
99
|
+
fontSize="13px"
|
|
100
|
+
border={isFocussed ? '2px solid' : '1px solid'}
|
|
101
|
+
borderColor={isFocussed ? colors.border.focus : colors.border.default}
|
|
102
|
+
py="5px"
|
|
103
|
+
pl="8px"
|
|
104
|
+
borderRadius="4px"
|
|
105
|
+
alignItems="center"
|
|
106
|
+
justifyContent="space-between"
|
|
107
|
+
onClick={() => !disabled && setIsFocussed(true)}
|
|
108
|
+
bg={disabled ? colors.fill.light.quaternary : '#ffffff'}
|
|
109
|
+
cursor={disabled ? 'not-allowed' : 'pointer'}
|
|
110
|
+
>
|
|
111
|
+
<Flex height="28px" alignItems="center">
|
|
112
|
+
{localValues.length ? (
|
|
113
|
+
localValues.map(option => (
|
|
114
|
+
<Box mr="4px">
|
|
115
|
+
<Token
|
|
116
|
+
label={option.label}
|
|
117
|
+
onDelete={() => handleDelete(option)}
|
|
118
|
+
/>
|
|
119
|
+
</Box>
|
|
120
|
+
))
|
|
121
|
+
) : (
|
|
122
|
+
<Text color={colors.label.secondary.light} fontSize="13px">
|
|
123
|
+
{placeholder}
|
|
124
|
+
</Text>
|
|
125
|
+
)}
|
|
126
|
+
</Flex>
|
|
127
|
+
<Flex width="39px" justifyContent="center" alignItems="center">
|
|
128
|
+
<Image src={SubtractIcon} alt="subtract" boxSize="16px" />
|
|
129
|
+
</Flex>
|
|
130
|
+
</Flex>
|
|
131
|
+
{isFocussed && (
|
|
132
|
+
<Dropdown
|
|
133
|
+
onSelectItem={option => handleChange(option)}
|
|
134
|
+
options={localOptions}
|
|
135
|
+
/>
|
|
136
|
+
)}
|
|
137
|
+
</Box>
|
|
142
138
|
);
|
|
143
139
|
});
|
|
144
140
|
|