@ssa-ui-kit/core 1.0.5 → 1.0.8
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/Typeahead/Typeahead.context.d.ts +4 -2
- package/dist/components/Typeahead/Typeahead.d.ts +1 -1
- package/dist/components/Typeahead/styles.d.ts +10 -9
- package/dist/components/Typeahead/types.d.ts +4 -2
- package/dist/components/Typeahead/useTypeahead.d.ts +2 -1
- package/dist/index.js +1 -1
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
- package/src/components/CollapsibleNavBar/CollapsibleNavBarBase.ts +3 -3
- package/src/components/NavBar/NavBar.stories.tsx +7 -2
- package/src/components/NavBar/NavBarWrapper.ts +1 -1
- package/src/components/NotificationMenu/NotificationMenu.spec.tsx +1 -1
- package/src/components/NotificationMenu/stories/StoryComponent.tsx +1 -1
- package/src/components/Radio/RadioBase.tsx +1 -1
- package/src/components/RadioGroup/RadioGroup.stories.tsx +1 -1
- package/src/components/Typeahead/Typeahead.context.ts +2 -0
- package/src/components/Typeahead/Typeahead.spec.tsx +27 -10
- package/src/components/Typeahead/Typeahead.stories.tsx +38 -4
- package/src/components/Typeahead/Typeahead.tsx +9 -4
- package/src/components/Typeahead/components/MultipleTrigger.tsx +6 -5
- package/src/components/Typeahead/components/SingleTrigger.tsx +8 -5
- package/src/components/Typeahead/components/TypeaheadItem.ts +1 -1
- package/src/components/Typeahead/styles.ts +4 -2
- package/src/components/Typeahead/types.ts +4 -2
- package/src/components/Typeahead/useTypeahead.tsx +36 -13
- package/src/components/UserProfile/UserProfile.stories.tsx +4 -2
- package/tsbuildcache +1 -1
package/package.json
CHANGED
|
@@ -75,7 +75,7 @@ const CollapsibleNavBarBase = styled(NavBarBase)`
|
|
|
75
75
|
|
|
76
76
|
& ~ div:nth-of-type(2) {
|
|
77
77
|
display: block;
|
|
78
|
-
border-radius:
|
|
78
|
+
border-radius: 0;
|
|
79
79
|
height: calc(100vh - 60px);
|
|
80
80
|
|
|
81
81
|
${({ theme }) => theme.mediaQueries.xlg} {
|
|
@@ -101,8 +101,8 @@ const CollapsibleNavBarBase = styled(NavBarBase)`
|
|
|
101
101
|
|
|
102
102
|
${({ theme }) => theme.mediaQueries.lg} {
|
|
103
103
|
&.opened {
|
|
104
|
-
min-width:
|
|
105
|
-
width:
|
|
104
|
+
min-width: 240px;
|
|
105
|
+
width: 291px;
|
|
106
106
|
|
|
107
107
|
& > div:nth-of-type(2) {
|
|
108
108
|
width: 240px;
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { Fragment } from 'react';
|
|
2
2
|
import { Routes, Route, MemoryRouter } from 'react-router-dom';
|
|
3
3
|
import { Meta } from '@storybook/react';
|
|
4
|
-
import { Title, Description,
|
|
4
|
+
import { Title, Description, Subtitle, Primary } from '@storybook/addon-docs';
|
|
5
5
|
|
|
6
6
|
import { NavBar } from './NavBar';
|
|
7
7
|
import { DecoratorFunction } from '@storybook/types';
|
|
@@ -42,12 +42,17 @@ export default {
|
|
|
42
42
|
viewport: {
|
|
43
43
|
defaultViewport: 'mobile2',
|
|
44
44
|
},
|
|
45
|
+
source: {
|
|
46
|
+
type: 'code',
|
|
47
|
+
},
|
|
45
48
|
docs: {
|
|
49
|
+
inlineStories: false,
|
|
46
50
|
page: () => (
|
|
47
51
|
<Fragment>
|
|
48
52
|
<Title />
|
|
53
|
+
<Subtitle />
|
|
49
54
|
<Description />
|
|
50
|
-
<
|
|
55
|
+
<Primary />
|
|
51
56
|
</Fragment>
|
|
52
57
|
),
|
|
53
58
|
},
|
|
@@ -15,7 +15,7 @@ const NavBarWrapper = styled.div`
|
|
|
15
15
|
${({ theme }) => theme.colors.greyDarker} 100%
|
|
16
16
|
);
|
|
17
17
|
|
|
18
|
-
transform: translateY(-
|
|
18
|
+
transform: translateY(-300vh);
|
|
19
19
|
transition: height 475ms ease, transform 450ms ease, border-radius 450ms ease;
|
|
20
20
|
|
|
21
21
|
${({ theme }) => theme.mediaQueries.md} {
|
|
@@ -71,7 +71,7 @@ export const StoryComponent = () => {
|
|
|
71
71
|
}
|
|
72
72
|
rightButton={
|
|
73
73
|
<Link to={'/'} css={{ gridColumn: 2 }}>
|
|
74
|
-
<Button variant="info" text="View all
|
|
74
|
+
<Button variant="info" text="View all notifications" />
|
|
75
75
|
</Link>
|
|
76
76
|
}
|
|
77
77
|
isLoading={isLoading}>
|
|
@@ -6,6 +6,7 @@ export const RadioBase = styled(Label)`
|
|
|
6
6
|
flex-grow: 0;
|
|
7
7
|
align-items: center;
|
|
8
8
|
cursor: pointer;
|
|
9
|
+
gap: 5px;
|
|
9
10
|
|
|
10
11
|
&:has(input:disabled) {
|
|
11
12
|
cursor: default;
|
|
@@ -29,7 +30,6 @@ export const RadioBase = styled(Label)`
|
|
|
29
30
|
}
|
|
30
31
|
|
|
31
32
|
span {
|
|
32
|
-
margin-left: 10px;
|
|
33
33
|
font-size: 14px;
|
|
34
34
|
font-weight: 100;
|
|
35
35
|
}
|
|
@@ -47,7 +47,7 @@ export const HorizontalRadioGroupStories: StoryObj<typeof RadioGroup> = (
|
|
|
47
47
|
) => (
|
|
48
48
|
<Fragment>
|
|
49
49
|
<Typography variant="h4">Horizontal Radio Group</Typography>
|
|
50
|
-
<RadioGroup {...args} css={{ marginTop: '10px' }}>
|
|
50
|
+
<RadioGroup {...args} css={{ marginTop: '10px', gap: 10, display: 'flex' }}>
|
|
51
51
|
<Radio id="radio1" value="apple" text="Apple" />
|
|
52
52
|
<Radio id="radio2" value="orange" text="Orange" />
|
|
53
53
|
<Radio id="radio3" value="banana" text="Banana" isDisabled={true} />
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import * as React from 'react';
|
|
2
|
+
import { FieldValues, UseFormReturn } from 'react-hook-form';
|
|
2
3
|
import { UseTypeaheadResult } from './useTypeahead';
|
|
3
4
|
|
|
4
5
|
export const TypeaheadContext = React.createContext<UseTypeaheadResult>({
|
|
@@ -23,6 +24,7 @@ export const TypeaheadContext = React.createContext<UseTypeaheadResult>({
|
|
|
23
24
|
isDisabled: false,
|
|
24
25
|
options: [],
|
|
25
26
|
placeholder: '',
|
|
27
|
+
useFormResult: {} as UseFormReturn<FieldValues>,
|
|
26
28
|
setValue: () => {
|
|
27
29
|
/* no-op */
|
|
28
30
|
},
|
|
@@ -60,7 +60,9 @@ describe('Typeahead', () => {
|
|
|
60
60
|
it('Renders without a selected item', async () => {
|
|
61
61
|
const { user, mockOnChange, getByRole, queryByRole, getByTestId } = setup();
|
|
62
62
|
|
|
63
|
-
expect(mockOnChange).toBeCalledWith('typeahead-dropdown', undefined
|
|
63
|
+
expect(mockOnChange).toBeCalledWith('typeahead-dropdown', undefined, {
|
|
64
|
+
shouldDirty: false,
|
|
65
|
+
});
|
|
64
66
|
|
|
65
67
|
const mainElement = getByTestId('typeahead');
|
|
66
68
|
|
|
@@ -104,7 +106,9 @@ describe('Typeahead', () => {
|
|
|
104
106
|
label: 'Label',
|
|
105
107
|
});
|
|
106
108
|
|
|
107
|
-
expect(mockOnChange).toBeCalledWith('typeahead-dropdown', selectedIDs
|
|
109
|
+
expect(mockOnChange).toBeCalledWith('typeahead-dropdown', selectedIDs, {
|
|
110
|
+
shouldDirty: false,
|
|
111
|
+
});
|
|
108
112
|
|
|
109
113
|
let mainElement = getByTestId('typeahead');
|
|
110
114
|
|
|
@@ -164,7 +168,9 @@ describe('Typeahead', () => {
|
|
|
164
168
|
/>,
|
|
165
169
|
);
|
|
166
170
|
|
|
167
|
-
expect(mockOnChange).toBeCalledWith('typeahead-dropdown', []
|
|
171
|
+
expect(mockOnChange).toBeCalledWith('typeahead-dropdown', [], {
|
|
172
|
+
shouldDirty: false,
|
|
173
|
+
});
|
|
168
174
|
|
|
169
175
|
let mainElement = getByTestId('typeahead');
|
|
170
176
|
|
|
@@ -203,7 +209,9 @@ describe('Typeahead', () => {
|
|
|
203
209
|
label: 'Label',
|
|
204
210
|
});
|
|
205
211
|
|
|
206
|
-
expect(mockOnChange).toBeCalledWith('typeahead-dropdown', selectedIDs
|
|
212
|
+
expect(mockOnChange).toBeCalledWith('typeahead-dropdown', selectedIDs, {
|
|
213
|
+
shouldDirty: false,
|
|
214
|
+
});
|
|
207
215
|
|
|
208
216
|
const mainElement = getByTestId('typeahead');
|
|
209
217
|
|
|
@@ -243,7 +251,9 @@ describe('Typeahead', () => {
|
|
|
243
251
|
label: 'Label',
|
|
244
252
|
});
|
|
245
253
|
|
|
246
|
-
expect(mockOnChange).lastCalledWith('typeahead-dropdown', selectedIDs[0]
|
|
254
|
+
expect(mockOnChange).lastCalledWith('typeahead-dropdown', selectedIDs[0], {
|
|
255
|
+
shouldDirty: true,
|
|
256
|
+
});
|
|
247
257
|
|
|
248
258
|
let inputEl = screen.queryByTestId('typeahead-input');
|
|
249
259
|
expect(inputEl).toHaveValue('First');
|
|
@@ -345,7 +355,9 @@ describe('Typeahead', () => {
|
|
|
345
355
|
label: 'Label',
|
|
346
356
|
});
|
|
347
357
|
|
|
348
|
-
expect(mockOnChange).toBeCalledWith('typeahead-dropdown', selectedIDs
|
|
358
|
+
expect(mockOnChange).toBeCalledWith('typeahead-dropdown', selectedIDs, {
|
|
359
|
+
shouldDirty: false,
|
|
360
|
+
});
|
|
349
361
|
|
|
350
362
|
let mainElement = getByTestId('typeahead');
|
|
351
363
|
|
|
@@ -448,7 +460,9 @@ describe('Typeahead', () => {
|
|
|
448
460
|
label: 'Label',
|
|
449
461
|
});
|
|
450
462
|
|
|
451
|
-
expect(mockOnChange).toBeCalledWith('typeahead-dropdown', selectedIDs
|
|
463
|
+
expect(mockOnChange).toBeCalledWith('typeahead-dropdown', selectedIDs, {
|
|
464
|
+
shouldDirty: false,
|
|
465
|
+
});
|
|
452
466
|
|
|
453
467
|
let mainElement = getByTestId('typeahead');
|
|
454
468
|
let toggleElement = within(mainElement).getByRole('combobox');
|
|
@@ -492,15 +506,18 @@ describe('Typeahead', () => {
|
|
|
492
506
|
|
|
493
507
|
it('Error should be displayed', () => {
|
|
494
508
|
const selectedIDs = selectedItems.map((item) => item.id);
|
|
495
|
-
|
|
509
|
+
|
|
510
|
+
const additionalProps = {
|
|
496
511
|
initialSelectedItems: selectedIDs,
|
|
497
512
|
isMultiple: true,
|
|
498
513
|
label: 'Label',
|
|
499
|
-
|
|
514
|
+
error: {
|
|
515
|
+
type: 'required',
|
|
500
516
|
message: 'Error message',
|
|
501
517
|
},
|
|
502
|
-
}
|
|
518
|
+
};
|
|
503
519
|
|
|
520
|
+
const { getByTestId } = setup(additionalProps);
|
|
504
521
|
expect(getByTestId('helper-text')).toBeInTheDocument();
|
|
505
522
|
});
|
|
506
523
|
});
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import React, { useState } from 'react';
|
|
1
|
+
import React, { useEffect, useState } from 'react';
|
|
2
2
|
import {
|
|
3
3
|
FieldError,
|
|
4
4
|
FieldValues,
|
|
@@ -86,8 +86,38 @@ Basic.args = { isDisabled: false };
|
|
|
86
86
|
|
|
87
87
|
export const Multiple: StoryObj = (args: TypeaheadProps) => {
|
|
88
88
|
const useFormResult = useForm<FieldValues>();
|
|
89
|
-
const {
|
|
89
|
+
const {
|
|
90
|
+
handleSubmit,
|
|
91
|
+
register,
|
|
92
|
+
setValue,
|
|
93
|
+
setError,
|
|
94
|
+
clearErrors,
|
|
95
|
+
watch,
|
|
96
|
+
formState: { errors, isDirty },
|
|
97
|
+
} = useFormResult;
|
|
98
|
+
const fieldName = 'typeahead-dropdown';
|
|
99
|
+
const error = errors[fieldName]
|
|
100
|
+
? {
|
|
101
|
+
type: errors[fieldName].type,
|
|
102
|
+
message: errors[fieldName].message,
|
|
103
|
+
}
|
|
104
|
+
: undefined;
|
|
105
|
+
|
|
90
106
|
const onSubmit: SubmitHandler<FieldValues> = (data) => console.log(data);
|
|
107
|
+
const fieldWatch = watch(fieldName);
|
|
108
|
+
useEffect(() => {
|
|
109
|
+
if (isDirty) {
|
|
110
|
+
if (Array.isArray(fieldWatch) && !fieldWatch.length) {
|
|
111
|
+
setError(fieldName, {
|
|
112
|
+
message: 'Required field',
|
|
113
|
+
type: 'required',
|
|
114
|
+
});
|
|
115
|
+
} else {
|
|
116
|
+
clearErrors(fieldName);
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
}, [fieldWatch, isDirty]);
|
|
120
|
+
|
|
91
121
|
return (
|
|
92
122
|
<form onSubmit={handleSubmit(onSubmit)}>
|
|
93
123
|
<Typeahead
|
|
@@ -98,7 +128,11 @@ export const Multiple: StoryObj = (args: TypeaheadProps) => {
|
|
|
98
128
|
helperText="Helper Text"
|
|
99
129
|
register={register}
|
|
100
130
|
setValue={setValue}
|
|
101
|
-
|
|
131
|
+
validationSchema={{
|
|
132
|
+
required: 'Required',
|
|
133
|
+
}}
|
|
134
|
+
name={fieldName}
|
|
135
|
+
error={error as FieldError}
|
|
102
136
|
renderOption={({ label, input }) => highlightInputMatch(label, input)}>
|
|
103
137
|
{items.map(({ label, value, id }) => (
|
|
104
138
|
<TypeaheadOption key={id} value={id} label={label || value}>
|
|
@@ -189,7 +223,7 @@ export const WithError: StoryObj = (args: TypeaheadProps) => {
|
|
|
189
223
|
validationSchema={{
|
|
190
224
|
required: 'Required',
|
|
191
225
|
}}
|
|
192
|
-
|
|
226
|
+
error={mockError}
|
|
193
227
|
renderOption={({ label, input }) => highlightInputMatch(label, input)}>
|
|
194
228
|
{items.map(({ label, value, id }) => (
|
|
195
229
|
<TypeaheadOption key={id} value={id} label={label || value}>
|
|
@@ -32,7 +32,7 @@ export const Typeahead = ({
|
|
|
32
32
|
className,
|
|
33
33
|
startIcon,
|
|
34
34
|
endIcon,
|
|
35
|
-
|
|
35
|
+
error,
|
|
36
36
|
success,
|
|
37
37
|
helperText,
|
|
38
38
|
validationSchema,
|
|
@@ -40,6 +40,8 @@ export const Typeahead = ({
|
|
|
40
40
|
startIconClassName,
|
|
41
41
|
endIconClassName,
|
|
42
42
|
optionsClassName,
|
|
43
|
+
wrapperClassName,
|
|
44
|
+
width = 300,
|
|
43
45
|
setValue,
|
|
44
46
|
register,
|
|
45
47
|
onChange,
|
|
@@ -58,7 +60,7 @@ export const Typeahead = ({
|
|
|
58
60
|
endIcon,
|
|
59
61
|
startIconClassName,
|
|
60
62
|
endIconClassName,
|
|
61
|
-
|
|
63
|
+
error,
|
|
62
64
|
success,
|
|
63
65
|
validationSchema,
|
|
64
66
|
placeholder,
|
|
@@ -74,7 +76,9 @@ export const Typeahead = ({
|
|
|
74
76
|
css={{
|
|
75
77
|
flexDirection: 'column',
|
|
76
78
|
alignItems: 'flex-start',
|
|
79
|
+
width,
|
|
77
80
|
}}
|
|
81
|
+
className={wrapperClassName}
|
|
78
82
|
data-testid="typeahead">
|
|
79
83
|
{label && (
|
|
80
84
|
<Label
|
|
@@ -94,6 +98,7 @@ export const Typeahead = ({
|
|
|
94
98
|
css={{
|
|
95
99
|
width: hookResult.triggerRef.current?.clientWidth,
|
|
96
100
|
boxShadow: `-4px 4px 14px 0px ${theme.colors.greyDarker14}`,
|
|
101
|
+
zIndex: 100,
|
|
97
102
|
}}
|
|
98
103
|
isFocusManagerDisabled>
|
|
99
104
|
<PopoverDescription css={{ width: '100%' }}>
|
|
@@ -105,13 +110,13 @@ export const Typeahead = ({
|
|
|
105
110
|
</PopoverDescription>
|
|
106
111
|
</PopoverContent>
|
|
107
112
|
</Popover>
|
|
108
|
-
{(
|
|
113
|
+
{(hookResult.status === 'error' || helperText) && (
|
|
109
114
|
<FormHelperText
|
|
110
115
|
role="status"
|
|
111
116
|
status={hookResult.status}
|
|
112
117
|
disabled={isDisabled}
|
|
113
118
|
data-testid="helper-text">
|
|
114
|
-
{
|
|
119
|
+
{error ? error?.message : helperText}
|
|
115
120
|
</FormHelperText>
|
|
116
121
|
)}
|
|
117
122
|
</Wrapper>
|
|
@@ -69,7 +69,7 @@ export const MultipleTrigger = () => {
|
|
|
69
69
|
onChange: context.handleInputChange,
|
|
70
70
|
value: context.inputValue,
|
|
71
71
|
autoComplete: 'off',
|
|
72
|
-
className: ['typeahead-input', S.TypeaheadInput].join(' '),
|
|
72
|
+
className: ['typeahead-input', S.TypeaheadInput(theme)].join(' '),
|
|
73
73
|
}}
|
|
74
74
|
wrapperClassName={S.TypeaheadInputWrapper}
|
|
75
75
|
ref={context.inputRef}
|
|
@@ -78,20 +78,20 @@ export const MultipleTrigger = () => {
|
|
|
78
78
|
<input
|
|
79
79
|
type="text"
|
|
80
80
|
data-testid="typeahead-input"
|
|
81
|
-
aria-hidden
|
|
81
|
+
aria-hidden={context.isOpen}
|
|
82
82
|
readOnly
|
|
83
83
|
value={context.firstSuggestion}
|
|
84
|
+
tabIndex={-1}
|
|
84
85
|
disabled={context.isDisabled}
|
|
85
86
|
className={[
|
|
86
87
|
'typeahead-input',
|
|
87
|
-
S.TypeaheadInput,
|
|
88
|
+
S.TypeaheadInput(theme),
|
|
88
89
|
S.TypeaheadInputPlaceholder,
|
|
89
90
|
].join(' ')}
|
|
90
91
|
{...typeaheadInputAdditionalProps}
|
|
91
92
|
/>
|
|
92
93
|
<input
|
|
93
94
|
type="hidden"
|
|
94
|
-
aria-hidden
|
|
95
95
|
readOnly
|
|
96
96
|
value={context.selectedItems as string[]}
|
|
97
97
|
{...context.register?.(context.name, context.validationSchema)}
|
|
@@ -103,7 +103,8 @@ export const MultipleTrigger = () => {
|
|
|
103
103
|
data-testid="remove-all-button"
|
|
104
104
|
endIcon={<Icon name="cross" size={8} tooltip="Remove all" />}
|
|
105
105
|
css={{
|
|
106
|
-
padding: '0
|
|
106
|
+
padding: '0 10px',
|
|
107
|
+
marginRight: 4,
|
|
107
108
|
position: 'absolute',
|
|
108
109
|
right: 0,
|
|
109
110
|
zIndex: 10,
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { InputHTMLAttributes } from 'react';
|
|
2
|
+
import { useTheme } from '@emotion/react';
|
|
2
3
|
import Input from '@components/Input';
|
|
3
4
|
import Button from '@components/Button';
|
|
4
5
|
import Icon from '@components/Icon';
|
|
@@ -7,6 +8,7 @@ import * as S from '../styles';
|
|
|
7
8
|
|
|
8
9
|
export const SingleTrigger = () => {
|
|
9
10
|
const context = useTypeaheadContext();
|
|
11
|
+
const theme = useTheme();
|
|
10
12
|
const typeaheadInputAdditionalProps: InputHTMLAttributes<HTMLInputElement> =
|
|
11
13
|
{};
|
|
12
14
|
if (!context.selectedItems.length && !!context.placeholder) {
|
|
@@ -26,7 +28,7 @@ export const SingleTrigger = () => {
|
|
|
26
28
|
onChange: context.handleInputChange,
|
|
27
29
|
value: context.inputValue,
|
|
28
30
|
autoComplete: 'off',
|
|
29
|
-
className: ['typeahead-input', S.TypeaheadInput].join(' '),
|
|
31
|
+
className: ['typeahead-input', S.TypeaheadInput(theme)].join(' '),
|
|
30
32
|
}}
|
|
31
33
|
wrapperClassName={S.TypeaheadInputWrapper}
|
|
32
34
|
ref={context.inputRef}
|
|
@@ -35,19 +37,19 @@ export const SingleTrigger = () => {
|
|
|
35
37
|
<input
|
|
36
38
|
type="text"
|
|
37
39
|
data-testid="typeahead-input"
|
|
38
|
-
aria-hidden
|
|
40
|
+
aria-hidden={context.isOpen}
|
|
39
41
|
readOnly
|
|
40
42
|
value={context.firstSuggestion}
|
|
43
|
+
tabIndex={-1}
|
|
41
44
|
className={[
|
|
42
45
|
'typeahead-input',
|
|
43
|
-
S.TypeaheadInput,
|
|
46
|
+
S.TypeaheadInput(theme),
|
|
44
47
|
S.TypeaheadInputPlaceholder,
|
|
45
48
|
].join(' ')}
|
|
46
49
|
{...typeaheadInputAdditionalProps}
|
|
47
50
|
/>
|
|
48
51
|
<input
|
|
49
52
|
type="hidden"
|
|
50
|
-
aria-hidden
|
|
51
53
|
readOnly
|
|
52
54
|
value={(context.selectedItems[0] || '') as string | undefined}
|
|
53
55
|
{...context.register?.(context.name, context.validationSchema)}
|
|
@@ -58,7 +60,8 @@ export const SingleTrigger = () => {
|
|
|
58
60
|
data-testid="remove-all-button"
|
|
59
61
|
endIcon={<Icon name="cross" size={8} tooltip="Remove" />}
|
|
60
62
|
css={{
|
|
61
|
-
padding: '0
|
|
63
|
+
padding: '0 10px',
|
|
64
|
+
marginRight: 4,
|
|
62
65
|
position: 'absolute',
|
|
63
66
|
right: -28,
|
|
64
67
|
zIndex: 10,
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { Theme } from '@emotion/react';
|
|
1
2
|
import { css } from '@emotion/css';
|
|
2
3
|
import styled from '@emotion/styled';
|
|
3
4
|
import Wrapper from '@components/Wrapper';
|
|
@@ -35,8 +36,9 @@ export const TypeaheadOption = styled.li<TypeaheadItemProps>`
|
|
|
35
36
|
}
|
|
36
37
|
`;
|
|
37
38
|
|
|
38
|
-
export const TypeaheadInput = css`
|
|
39
|
+
export const TypeaheadInput = (theme: Theme) => css`
|
|
39
40
|
&.typeahead-input {
|
|
41
|
+
color: ${theme.colors.greyDarker};
|
|
40
42
|
border: none;
|
|
41
43
|
border-radius: 0;
|
|
42
44
|
height: 32px;
|
|
@@ -148,7 +150,7 @@ export const TypeaheadTrigger = styled(PopoverTrigger)<{
|
|
|
148
150
|
background: #fff;
|
|
149
151
|
gap: 8px;
|
|
150
152
|
padding: 5px 28px 5px 8px;
|
|
151
|
-
width:
|
|
153
|
+
width: 100%;
|
|
152
154
|
flex-wrap: wrap;
|
|
153
155
|
border-color: ${({ isOpen, theme, status }) =>
|
|
154
156
|
isOpen &&
|
|
@@ -17,6 +17,8 @@ export interface TypeaheadProps {
|
|
|
17
17
|
children?: React.ReactNode;
|
|
18
18
|
className?: string;
|
|
19
19
|
optionsClassName?: string;
|
|
20
|
+
wrapperClassName?: string;
|
|
21
|
+
width?: string | number;
|
|
20
22
|
isOpen?: boolean;
|
|
21
23
|
startIcon?: React.ReactNode;
|
|
22
24
|
endIcon?: React.ReactNode;
|
|
@@ -25,7 +27,7 @@ export interface TypeaheadProps {
|
|
|
25
27
|
name?: string;
|
|
26
28
|
label?: string;
|
|
27
29
|
helperText?: string;
|
|
28
|
-
|
|
30
|
+
error?: FieldError;
|
|
29
31
|
success?: boolean;
|
|
30
32
|
validationSchema?: Record<string, unknown>;
|
|
31
33
|
placeholder?: string | null;
|
|
@@ -57,7 +59,7 @@ export type UseTypeaheadProps = Pick<
|
|
|
57
59
|
| 'register'
|
|
58
60
|
| 'setValue'
|
|
59
61
|
| 'validationSchema'
|
|
60
|
-
| '
|
|
62
|
+
| 'error'
|
|
61
63
|
| 'success'
|
|
62
64
|
| 'placeholder'
|
|
63
65
|
>;
|
|
@@ -6,6 +6,7 @@ import React, {
|
|
|
6
6
|
useRef,
|
|
7
7
|
useState,
|
|
8
8
|
} from 'react';
|
|
9
|
+
import { useForm } from 'react-hook-form';
|
|
9
10
|
import { propOr } from '@ssa-ui-kit/utils';
|
|
10
11
|
import { TypeaheadOptionProps, UseTypeaheadProps } from './types';
|
|
11
12
|
|
|
@@ -22,7 +23,7 @@ export const useTypeahead = ({
|
|
|
22
23
|
startIconClassName,
|
|
23
24
|
endIconClassName,
|
|
24
25
|
validationSchema,
|
|
25
|
-
|
|
26
|
+
error,
|
|
26
27
|
success,
|
|
27
28
|
placeholder,
|
|
28
29
|
register,
|
|
@@ -38,15 +39,17 @@ export const useTypeahead = ({
|
|
|
38
39
|
const [optionsWithKey, setOptionsWithKey] = useState<
|
|
39
40
|
Record<number | string, Record<string, string | number>>
|
|
40
41
|
>({});
|
|
42
|
+
const [isFirstRender, setFirstRender] = useState<boolean>(true);
|
|
41
43
|
const [items, setItems] = useState<Array<React.ReactElement> | undefined>();
|
|
42
44
|
const [inputValue, setInputValue] = useState<string>('');
|
|
43
45
|
const [status, setStatus] = useState<'basic' | 'success' | 'error'>('basic');
|
|
46
|
+
const [firstSuggestion, setFirstSuggestion] = useState('');
|
|
44
47
|
|
|
45
48
|
const inputRef = useRef<HTMLInputElement>(null);
|
|
46
49
|
const typeaheadId = useId();
|
|
47
50
|
const triggerRef: React.MutableRefObject<HTMLDivElement | null> =
|
|
48
51
|
useRef<HTMLDivElement>(null);
|
|
49
|
-
const
|
|
52
|
+
const useFormResult = useForm();
|
|
50
53
|
|
|
51
54
|
useEffect(() => {
|
|
52
55
|
if (!register) {
|
|
@@ -56,11 +59,15 @@ export const useTypeahead = ({
|
|
|
56
59
|
|
|
57
60
|
useEffect(() => {
|
|
58
61
|
if (isMultiple) {
|
|
59
|
-
setValue?.(name, selected
|
|
62
|
+
setValue?.(name, selected, {
|
|
63
|
+
shouldDirty: !isFirstRender,
|
|
64
|
+
});
|
|
60
65
|
setInputValue('');
|
|
61
66
|
setFirstSuggestion('');
|
|
62
67
|
} else {
|
|
63
|
-
setValue?.(name, selected.length ? selected[0] : undefined
|
|
68
|
+
setValue?.(name, selected.length ? selected[0] : undefined, {
|
|
69
|
+
shouldDirty: !isFirstRender,
|
|
70
|
+
});
|
|
64
71
|
}
|
|
65
72
|
}, [selected]);
|
|
66
73
|
|
|
@@ -71,9 +78,22 @@ export const useTypeahead = ({
|
|
|
71
78
|
}, [isDisabled]);
|
|
72
79
|
|
|
73
80
|
useEffect(() => {
|
|
74
|
-
const status = success
|
|
81
|
+
const status = success
|
|
82
|
+
? 'success'
|
|
83
|
+
: useFormResult.formState.errors[name]
|
|
84
|
+
? 'error'
|
|
85
|
+
: 'basic';
|
|
75
86
|
setStatus(status);
|
|
76
|
-
}, [errors, success]);
|
|
87
|
+
}, [useFormResult.formState.errors[name], success]);
|
|
88
|
+
|
|
89
|
+
useEffect(() => {
|
|
90
|
+
if (error) {
|
|
91
|
+
useFormResult.setError(name, error);
|
|
92
|
+
} else {
|
|
93
|
+
setStatus('basic');
|
|
94
|
+
useFormResult.resetField(name);
|
|
95
|
+
}
|
|
96
|
+
}, [error]);
|
|
77
97
|
|
|
78
98
|
useEffect(() => {
|
|
79
99
|
const keyedOptions: Record<
|
|
@@ -94,6 +114,7 @@ export const useTypeahead = ({
|
|
|
94
114
|
});
|
|
95
115
|
setOptionsWithKey(keyedOptions);
|
|
96
116
|
setItems(childItems);
|
|
117
|
+
setFirstRender(false);
|
|
97
118
|
}, [initialSelectedItems, children]);
|
|
98
119
|
|
|
99
120
|
useEffect(() => {
|
|
@@ -201,6 +222,9 @@ export const useTypeahead = ({
|
|
|
201
222
|
setIsOpen(false);
|
|
202
223
|
setFirstSuggestion('');
|
|
203
224
|
inputRef.current?.focus();
|
|
225
|
+
setStatus('basic');
|
|
226
|
+
useFormResult.clearErrors(name);
|
|
227
|
+
useFormResult.trigger(name);
|
|
204
228
|
onChange && onChange(changingValue, isNewSelected);
|
|
205
229
|
};
|
|
206
230
|
|
|
@@ -214,6 +238,7 @@ export const useTypeahead = ({
|
|
|
214
238
|
setInputValue('');
|
|
215
239
|
setIsOpen(false);
|
|
216
240
|
setFirstSuggestion('');
|
|
241
|
+
useFormResult.trigger(name);
|
|
217
242
|
inputRef.current?.focus();
|
|
218
243
|
};
|
|
219
244
|
|
|
@@ -231,13 +256,12 @@ export const useTypeahead = ({
|
|
|
231
256
|
const handleInputKeyDown: React.KeyboardEventHandler<HTMLInputElement> = (
|
|
232
257
|
event,
|
|
233
258
|
) => {
|
|
234
|
-
if (['
|
|
259
|
+
if (['Space'].includes(event.code) && !firstSuggestion) {
|
|
235
260
|
setIsOpen(true);
|
|
236
261
|
inputRef.current?.focus();
|
|
237
262
|
event.stopPropagation();
|
|
238
263
|
event.preventDefault();
|
|
239
|
-
}
|
|
240
|
-
if (['Tab', 'Enter'].includes(event.code) && firstSuggestion) {
|
|
264
|
+
} else if (['Tab', 'Enter'].includes(event.code) && firstSuggestion) {
|
|
241
265
|
const foundItem = Object.values(optionsWithKey).find(
|
|
242
266
|
(item) =>
|
|
243
267
|
`${item.label}`.toLowerCase() === firstSuggestion.toLowerCase(),
|
|
@@ -248,8 +272,7 @@ export const useTypeahead = ({
|
|
|
248
272
|
}
|
|
249
273
|
event.preventDefault();
|
|
250
274
|
return false;
|
|
251
|
-
}
|
|
252
|
-
if (
|
|
275
|
+
} else if (
|
|
253
276
|
isMultiple &&
|
|
254
277
|
event.code === 'Backspace' &&
|
|
255
278
|
selected.length > 0 &&
|
|
@@ -258,8 +281,7 @@ export const useTypeahead = ({
|
|
|
258
281
|
handleChange(selected[selected.length - 1]);
|
|
259
282
|
event.preventDefault();
|
|
260
283
|
return false;
|
|
261
|
-
}
|
|
262
|
-
if (!isOpen) {
|
|
284
|
+
} else if (!isOpen) {
|
|
263
285
|
setIsOpen(true);
|
|
264
286
|
}
|
|
265
287
|
};
|
|
@@ -305,6 +327,7 @@ export const useTypeahead = ({
|
|
|
305
327
|
status,
|
|
306
328
|
placeholder,
|
|
307
329
|
options: items,
|
|
330
|
+
useFormResult,
|
|
308
331
|
register,
|
|
309
332
|
setValue,
|
|
310
333
|
handleChange,
|
|
@@ -1,6 +1,7 @@
|
|
|
1
|
+
import { useTheme } from '@emotion/react';
|
|
1
2
|
import type { Meta, StoryObj } from '@storybook/react';
|
|
2
|
-
import Avatar from '@components/Avatar';
|
|
3
3
|
import { UserProfile } from './UserProfile';
|
|
4
|
+
import Icon from '@components/Icon';
|
|
4
5
|
|
|
5
6
|
export default {
|
|
6
7
|
title: 'Widgets/UserProfile',
|
|
@@ -8,11 +9,12 @@ export default {
|
|
|
8
9
|
} as Meta<typeof UserProfile>;
|
|
9
10
|
|
|
10
11
|
export const Default: StoryObj<typeof UserProfile> = () => {
|
|
12
|
+
const theme = useTheme();
|
|
11
13
|
return (
|
|
12
14
|
<UserProfile
|
|
13
15
|
name="Josh Li"
|
|
14
16
|
email="Josh@gmail.com"
|
|
15
|
-
trigger={<
|
|
17
|
+
trigger={<Icon size={42} name="user" color={theme.colors.grey} />}
|
|
16
18
|
onClick={() => alert('Clicked!')}
|
|
17
19
|
/>
|
|
18
20
|
);
|