@ssa-ui-kit/core 1.0.5 → 1.0.7

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.
Files changed (27) hide show
  1. package/dist/components/Typeahead/Typeahead.context.d.ts +4 -2
  2. package/dist/components/Typeahead/Typeahead.d.ts +1 -1
  3. package/dist/components/Typeahead/styles.d.ts +10 -9
  4. package/dist/components/Typeahead/types.d.ts +4 -2
  5. package/dist/components/Typeahead/useTypeahead.d.ts +2 -1
  6. package/dist/index.js +1 -1
  7. package/dist/index.js.map +1 -1
  8. package/package.json +1 -1
  9. package/src/components/CollapsibleNavBar/CollapsibleNavBarBase.ts +3 -3
  10. package/src/components/NavBar/NavBar.stories.tsx +7 -2
  11. package/src/components/NavBar/NavBarWrapper.ts +1 -1
  12. package/src/components/NotificationMenu/NotificationMenu.spec.tsx +1 -1
  13. package/src/components/NotificationMenu/stories/StoryComponent.tsx +1 -1
  14. package/src/components/Radio/RadioBase.tsx +1 -1
  15. package/src/components/RadioGroup/RadioGroup.stories.tsx +1 -1
  16. package/src/components/Typeahead/Typeahead.context.ts +2 -0
  17. package/src/components/Typeahead/Typeahead.spec.tsx +6 -3
  18. package/src/components/Typeahead/Typeahead.stories.tsx +1 -1
  19. package/src/components/Typeahead/Typeahead.tsx +9 -4
  20. package/src/components/Typeahead/components/MultipleTrigger.tsx +2 -2
  21. package/src/components/Typeahead/components/SingleTrigger.tsx +4 -2
  22. package/src/components/Typeahead/components/TypeaheadItem.ts +1 -1
  23. package/src/components/Typeahead/styles.ts +4 -2
  24. package/src/components/Typeahead/types.ts +4 -2
  25. package/src/components/Typeahead/useTypeahead.tsx +21 -4
  26. package/src/components/UserProfile/UserProfile.stories.tsx +4 -2
  27. package/tsbuildcache +1 -1
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ssa-ui-kit/core",
3
- "version": "1.0.5",
3
+ "version": "1.0.7",
4
4
  "main": "dist/index.js",
5
5
  "types": "dist/index.d.ts",
6
6
  "private": false,
@@ -75,7 +75,7 @@ const CollapsibleNavBarBase = styled(NavBarBase)`
75
75
 
76
76
  & ~ div:nth-of-type(2) {
77
77
  display: block;
78
- border-radius: 12px 12px 0 0;
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: unset;
105
- width: 240px;
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, Source } from '@storybook/addon-docs';
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
- <Source code={`<NavBar />`} />
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(-200vh);
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} {
@@ -58,7 +58,7 @@ describe('NotificationMenu', () => {
58
58
  await user.click(getByTestId('trigger-button'));
59
59
 
60
60
  getByRole('link', {
61
- name: new RegExp('View all notification', 'i'),
61
+ name: new RegExp('View all notifications', 'i'),
62
62
  });
63
63
  });
64
64
  });
@@ -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 notification" />
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
  },
@@ -492,15 +492,18 @@ describe('Typeahead', () => {
492
492
 
493
493
  it('Error should be displayed', () => {
494
494
  const selectedIDs = selectedItems.map((item) => item.id);
495
- const { getByTestId } = setup({
495
+
496
+ const additionalProps = {
496
497
  initialSelectedItems: selectedIDs,
497
498
  isMultiple: true,
498
499
  label: 'Label',
499
- errors: {
500
+ error: {
501
+ type: 'required',
500
502
  message: 'Error message',
501
503
  },
502
- });
504
+ };
503
505
 
506
+ const { getByTestId } = setup(additionalProps);
504
507
  expect(getByTestId('helper-text')).toBeInTheDocument();
505
508
  });
506
509
  });
@@ -189,7 +189,7 @@ export const WithError: StoryObj = (args: TypeaheadProps) => {
189
189
  validationSchema={{
190
190
  required: 'Required',
191
191
  }}
192
- errors={mockError}
192
+ error={mockError}
193
193
  renderOption={({ label, input }) => highlightInputMatch(label, input)}>
194
194
  {items.map(({ label, value, id }) => (
195
195
  <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
- errors,
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
- errors,
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
- {(errors?.message || helperText) && (
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
- {errors ? errors?.message : helperText}
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}
@@ -84,7 +84,7 @@ export const MultipleTrigger = () => {
84
84
  disabled={context.isDisabled}
85
85
  className={[
86
86
  'typeahead-input',
87
- S.TypeaheadInput,
87
+ S.TypeaheadInput(theme),
88
88
  S.TypeaheadInputPlaceholder,
89
89
  ].join(' ')}
90
90
  {...typeaheadInputAdditionalProps}
@@ -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}
@@ -40,7 +42,7 @@ export const SingleTrigger = () => {
40
42
  value={context.firstSuggestion}
41
43
  className={[
42
44
  'typeahead-input',
43
- S.TypeaheadInput,
45
+ S.TypeaheadInput(theme),
44
46
  S.TypeaheadInputPlaceholder,
45
47
  ].join(' ')}
46
48
  {...typeaheadInputAdditionalProps}
@@ -1,5 +1,5 @@
1
- import Icon from '@components/Icon';
2
1
  import styled from '@emotion/styled';
2
+ import Icon from '@components/Icon';
3
3
 
4
4
  export const TypeaheadItemImage = styled.img`
5
5
  width: 24px;
@@ -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: 300px;
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
- errors?: FieldError;
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
- | 'errors'
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
- errors,
26
+ error,
26
27
  success,
27
28
  placeholder,
28
29
  register,
@@ -41,12 +42,13 @@ export const useTypeahead = ({
41
42
  const [items, setItems] = useState<Array<React.ReactElement> | undefined>();
42
43
  const [inputValue, setInputValue] = useState<string>('');
43
44
  const [status, setStatus] = useState<'basic' | 'success' | 'error'>('basic');
45
+ const [firstSuggestion, setFirstSuggestion] = useState('');
44
46
 
45
47
  const inputRef = useRef<HTMLInputElement>(null);
46
48
  const typeaheadId = useId();
47
49
  const triggerRef: React.MutableRefObject<HTMLDivElement | null> =
48
50
  useRef<HTMLDivElement>(null);
49
- const [firstSuggestion, setFirstSuggestion] = useState('');
51
+ const useFormResult = useForm();
50
52
 
51
53
  useEffect(() => {
52
54
  if (!register) {
@@ -71,9 +73,21 @@ export const useTypeahead = ({
71
73
  }, [isDisabled]);
72
74
 
73
75
  useEffect(() => {
74
- const status = success ? 'success' : errors ? 'error' : 'basic';
76
+ const status = success
77
+ ? 'success'
78
+ : useFormResult.formState.errors[name]
79
+ ? 'error'
80
+ : 'basic';
75
81
  setStatus(status);
76
- }, [errors, success]);
82
+ }, [useFormResult.formState.errors[name], success]);
83
+
84
+ useEffect(() => {
85
+ if (error) {
86
+ useFormResult.setError(name, error);
87
+ } else {
88
+ useFormResult.resetField(name);
89
+ }
90
+ }, [error]);
77
91
 
78
92
  useEffect(() => {
79
93
  const keyedOptions: Record<
@@ -201,6 +215,8 @@ export const useTypeahead = ({
201
215
  setIsOpen(false);
202
216
  setFirstSuggestion('');
203
217
  inputRef.current?.focus();
218
+ setStatus('basic');
219
+ useFormResult.clearErrors();
204
220
  onChange && onChange(changingValue, isNewSelected);
205
221
  };
206
222
 
@@ -305,6 +321,7 @@ export const useTypeahead = ({
305
321
  status,
306
322
  placeholder,
307
323
  options: items,
324
+ useFormResult,
308
325
  register,
309
326
  setValue,
310
327
  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={<Avatar size={42} image="https://via.placeholder.com/42x42" />}
17
+ trigger={<Icon size={42} name="user" color={theme.colors.grey} />}
16
18
  onClick={() => alert('Clicked!')}
17
19
  />
18
20
  );