uikit-react-public 0.11.24 → 0.14.21

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 (184) hide show
  1. package/dist/components/Badge/Badge.d.ts +6 -0
  2. package/dist/components/Badge/Badge.stories.d.ts +15 -0
  3. package/dist/components/Badge/index.d.ts +2 -0
  4. package/dist/components/Button/Button.d.ts +2 -1
  5. package/dist/components/CookieNotice/CookieNotice.d.ts +16 -0
  6. package/dist/components/CookieNotice/index.d.ts +2 -0
  7. package/dist/components/Dialog/BaseDialog.d.ts +7 -2
  8. package/dist/components/FileInput/FileInput.d.ts +8 -0
  9. package/dist/components/FileInput/FileInput.stories.d.ts +16 -0
  10. package/dist/components/FileInput/__tests__/FileInput.test.d.ts +1 -0
  11. package/dist/components/FileInput/index.d.ts +2 -0
  12. package/dist/components/Header/Header.d.ts +4 -1
  13. package/dist/components/Heading/Heading.d.ts +1 -1
  14. package/dist/components/Link/BaseLink.d.ts +10 -0
  15. package/dist/components/Link/Link.d.ts +5 -10
  16. package/dist/components/Link/Link.stories.d.ts +1 -1
  17. package/dist/components/Link/index.d.ts +1 -1
  18. package/dist/components/Menu/MenuContent.d.ts +1 -1
  19. package/dist/components/Menu/MenuItem.d.ts +2 -0
  20. package/dist/components/Menu/MenuSection.d.ts +1 -1
  21. package/dist/components/Search/Search.d.ts +16 -0
  22. package/dist/components/Search/Search.stories.d.ts +34 -0
  23. package/dist/components/Search/__tests__/Search.test.d.ts +1 -0
  24. package/dist/components/Search/index.d.ts +2 -0
  25. package/dist/components/Select/Select.d.ts +1 -1
  26. package/dist/components/Select/Select.stories.d.ts +3 -7
  27. package/dist/components/Select/Select.types.d.ts +19 -14
  28. package/dist/components/Select/subcomponents/CustomOption.d.ts +1 -1
  29. package/dist/components/Select/subcomponents/CustomSelect.d.ts +1 -2
  30. package/dist/components/Select/subcomponents/Panel.d.ts +1 -1
  31. package/dist/components/Select/subcomponents/VisibleField.d.ts +4 -4
  32. package/dist/components/StandaloneLink/StandaloneLink.d.ts +12 -0
  33. package/dist/components/StandaloneLink/StandaloneLink.stories.d.ts +13 -0
  34. package/dist/components/StandaloneLink/__tests__/StandaloneLink.test.d.ts +1 -0
  35. package/dist/components/StandaloneLink/index.d.ts +2 -0
  36. package/dist/components/Table/Table.d.ts +10 -8
  37. package/dist/components/Table/Table.stories.d.ts +21 -0
  38. package/dist/components/Table/Table.types.d.ts +11 -0
  39. package/dist/components/Table/__tests__/Table.test.d.ts +1 -0
  40. package/dist/components/Table/index.d.ts +2 -1
  41. package/dist/components/Table/subcomponents/Body.d.ts +4 -0
  42. package/dist/components/Table/subcomponents/Cell/Cell.d.ts +12 -0
  43. package/dist/components/Table/subcomponents/Cell/Cell.stories.d.ts +313 -0
  44. package/dist/components/Table/subcomponents/Cell/CellContent.d.ts +10 -0
  45. package/dist/components/Table/subcomponents/Cell/__tests__/Cell.test.d.ts +1 -0
  46. package/dist/components/Table/subcomponents/Head.d.ts +4 -0
  47. package/dist/components/Table/subcomponents/HeadCell/HeadCell.d.ts +13 -0
  48. package/dist/components/Table/subcomponents/HeadCell/HeadCell.stories.d.ts +312 -0
  49. package/dist/components/Table/subcomponents/HeadCell/HeadCellContent.d.ts +10 -0
  50. package/dist/components/Table/subcomponents/HeadCell/__tests__/HeadCell.test.d.ts +1 -0
  51. package/dist/components/Table/subcomponents/Row.d.ts +5 -0
  52. package/dist/components/Table/subcomponents/SortIcon.d.ts +7 -0
  53. package/dist/components/Table/subcomponents/index.d.ts +10 -0
  54. package/dist/components/Tabs/Tab.d.ts +1 -1
  55. package/dist/components/Tabs/TabContext.d.ts +1 -0
  56. package/dist/components/Tabs/Tabs.d.ts +3 -1
  57. package/dist/components/Tabs/Tabs.stories.d.ts +3 -0
  58. package/dist/components/Timepicker/Timepicker.d.ts +10 -0
  59. package/dist/components/Timepicker/Timepicker.stories.d.ts +7 -0
  60. package/dist/components/Timepicker/__tests__/Timepicker.test.d.ts +1 -0
  61. package/dist/components/Timepicker/index.d.ts +2 -0
  62. package/dist/components/Timepicker/utils/convertDateToTimeString.d.ts +2 -0
  63. package/dist/components/Timepicker/utils/convertDateToTimeString.test.d.ts +1 -0
  64. package/dist/components/Timepicker/utils/index.d.ts +1 -0
  65. package/dist/components/WeekPicker/WeekPicker.d.ts +3 -0
  66. package/dist/components/WeekPicker/index.d.ts +1 -0
  67. package/dist/components/WeekPicker/subcomponents/CustomDatepicker.d.ts +17 -0
  68. package/dist/components/WeekPicker/subcomponents/DatepickerInput.d.ts +13 -0
  69. package/dist/components/WeekPicker/subcomponents/VisibleField.d.ts +15 -0
  70. package/dist/components/WeekPicker/subcomponents/index.d.ts +3 -0
  71. package/dist/components/index.d.ts +11 -0
  72. package/dist/hooks/index.d.ts +2 -0
  73. package/dist/hooks/useFocusTrap.d.ts +9 -0
  74. package/dist/index.d.ts +1 -0
  75. package/dist/index.js +5703 -4448
  76. package/dist/theme/defaultTheme.d.ts +7 -0
  77. package/dist/theme/useTheme.d.ts +14 -0
  78. package/dist/utils/__tests__/capitalise.test.d.ts +1 -0
  79. package/dist/utils/capitalise.d.ts +2 -0
  80. package/lib/components/Alert/Alert.tsx +7 -1
  81. package/lib/components/Alert/__tests__/__snapshots__/Alert.test.tsx.snap +4 -0
  82. package/lib/components/Badge/Badge.stories.tsx +19 -0
  83. package/lib/components/Badge/Badge.tsx +48 -0
  84. package/lib/components/Badge/index.ts +2 -0
  85. package/lib/components/Breadcrumbs/__tests__/__snapshots__/Breadcrumbs.test.tsx.snap +4 -4
  86. package/lib/components/Button/Button.tsx +5 -2
  87. package/lib/components/Calendar/subcomponents/Grid.tsx +0 -1
  88. package/lib/components/CookieNotice/CookieNotice.tsx +114 -0
  89. package/lib/components/CookieNotice/index.ts +2 -0
  90. package/lib/components/Dialog/BaseDialog.tsx +44 -4
  91. package/lib/components/Field/__tests__/Field.test.tsx +148 -148
  92. package/lib/components/FileInput/FileInput.stories.tsx +70 -0
  93. package/lib/components/FileInput/FileInput.tsx +68 -0
  94. package/lib/components/FileInput/__tests__/FileInput.test.tsx +99 -0
  95. package/lib/components/FileInput/__tests__/__snapshots__/FileInput.test.tsx.snap +91 -0
  96. package/lib/components/FileInput/index.ts +2 -0
  97. package/lib/components/Footer/__tests__/__snapshots__/Footer.test.tsx.snap +25 -25
  98. package/lib/components/Header/Header.tsx +19 -2
  99. package/lib/components/Header/__tests__/__snapshots__/Header.test.tsx.snap +4 -4
  100. package/lib/components/Heading/Documentation.mdx +1 -1
  101. package/lib/components/Heading/Heading.tsx +1 -1
  102. package/lib/components/Heading/__tests__/Heading.test.tsx +7 -19
  103. package/lib/components/Heading/__tests__/__snapshots__/Heading.test.tsx.snap +7 -7
  104. package/lib/components/Label/Label.tsx +0 -2
  105. package/lib/components/Label/__tests__/__snapshots__/Label.test.tsx.snap +7 -7
  106. package/lib/components/Link/BaseLink.tsx +84 -0
  107. package/lib/components/Link/Link.tsx +72 -32
  108. package/lib/components/Link/__tests__/__snapshots__/link.test.tsx.snap +3 -3
  109. package/lib/components/Link/__tests__/link.test.tsx +6 -13
  110. package/lib/components/Link/index.ts +1 -1
  111. package/lib/components/Menu/Menu.context.tsx +3 -1
  112. package/lib/components/Menu/Menu.tsx +2 -2
  113. package/lib/components/Menu/MenuContent.tsx +5 -5
  114. package/lib/components/Menu/MenuItem.tsx +20 -3
  115. package/lib/components/Menu/MenuSection.tsx +4 -3
  116. package/lib/components/Pagination/PaginationControls.tsx +1 -3
  117. package/lib/components/Search/Search.stories.tsx +41 -0
  118. package/lib/components/Search/Search.tsx +167 -0
  119. package/lib/components/Search/__tests__/Search.test.tsx +94 -0
  120. package/lib/components/Search/__tests__/__snapshots__/Search.test.tsx.snap +179 -0
  121. package/lib/components/Search/index.ts +2 -0
  122. package/lib/components/Select/Select.stories.tsx +8 -35
  123. package/lib/components/Select/Select.tsx +2 -2
  124. package/lib/components/Select/Select.types.ts +20 -15
  125. package/lib/components/Select/__tests__/__snapshots__/Select.test.tsx.snap +3 -3
  126. package/lib/components/Select/subcomponents/CustomOption.tsx +22 -9
  127. package/lib/components/Select/subcomponents/CustomSelect.tsx +31 -20
  128. package/lib/components/Select/subcomponents/Panel.tsx +4 -5
  129. package/lib/components/Select/subcomponents/VisibleField.tsx +26 -22
  130. package/lib/components/StandaloneLink/StandaloneLink.stories.tsx +32 -0
  131. package/lib/components/StandaloneLink/StandaloneLink.tsx +183 -0
  132. package/lib/components/StandaloneLink/__tests__/StandaloneLink.test.tsx +57 -0
  133. package/lib/components/StandaloneLink/__tests__/__snapshots__/StandaloneLink.test.tsx.snap +19 -0
  134. package/lib/components/StandaloneLink/index.ts +2 -0
  135. package/lib/components/Table/Table.stories.tsx +337 -0
  136. package/lib/components/Table/Table.tsx +42 -67
  137. package/lib/components/Table/Table.types.ts +14 -0
  138. package/lib/components/Table/__tests__/Table.test.tsx +121 -0
  139. package/lib/components/Table/__tests__/__snapshots__/Table.test.tsx.snap +210 -0
  140. package/lib/components/Table/index.ts +8 -1
  141. package/lib/components/Table/subcomponents/Body.tsx +18 -0
  142. package/lib/components/Table/subcomponents/Cell/Cell.stories.tsx +151 -0
  143. package/lib/components/Table/subcomponents/Cell/Cell.tsx +72 -0
  144. package/lib/components/Table/subcomponents/Cell/CellContent.tsx +91 -0
  145. package/lib/components/Table/subcomponents/Cell/__tests__/Cell.test.tsx +115 -0
  146. package/lib/components/Table/subcomponents/Cell/__tests__/__snapshots__/Cell.test.tsx.snap +107 -0
  147. package/lib/components/Table/subcomponents/Head.tsx +34 -0
  148. package/lib/components/Table/subcomponents/HeadCell/HeadCell.stories.tsx +85 -0
  149. package/lib/components/Table/subcomponents/HeadCell/HeadCell.tsx +99 -0
  150. package/lib/components/Table/subcomponents/HeadCell/HeadCellContent.tsx +61 -0
  151. package/lib/components/Table/subcomponents/HeadCell/__tests__/HeadCell.test.tsx +137 -0
  152. package/lib/components/Table/subcomponents/HeadCell/__tests__/__snapshots__/HeadCell.test.tsx.snap +110 -0
  153. package/lib/components/Table/subcomponents/Row.tsx +49 -0
  154. package/lib/components/Table/subcomponents/SortIcon.tsx +63 -0
  155. package/lib/components/Table/subcomponents/index.ts +14 -0
  156. package/lib/components/Tabs/Tab.tsx +3 -3
  157. package/lib/components/Tabs/TabContext.tsx +1 -0
  158. package/lib/components/Tabs/Tabs.stories.tsx +9 -3
  159. package/lib/components/Tabs/Tabs.tsx +10 -32
  160. package/lib/components/Tabs/__tests__/Tabs.test.tsx +10 -4
  161. package/lib/components/Tabs/__tests__/__snapshots__/Tabs.test.tsx.snap +32 -32
  162. package/lib/components/Timepicker/Timepicker.stories.tsx +43 -0
  163. package/lib/components/Timepicker/Timepicker.tsx +96 -0
  164. package/lib/components/Timepicker/__tests__/Timepicker.test.tsx +55 -0
  165. package/lib/components/Timepicker/__tests__/__snapshots__/Timepicker.test.tsx.snap +19 -0
  166. package/lib/components/Timepicker/index.tsx +2 -0
  167. package/lib/components/Timepicker/utils/convertDateToTimeString.test.ts +54 -0
  168. package/lib/components/Timepicker/utils/convertDateToTimeString.ts +10 -0
  169. package/lib/components/Timepicker/utils/index.ts +1 -0
  170. package/lib/components/WeekPicker/WeekPicker.tsx +26 -0
  171. package/lib/components/WeekPicker/index.ts +1 -0
  172. package/lib/components/WeekPicker/subcomponents/CustomDatepicker.tsx +298 -0
  173. package/lib/components/WeekPicker/subcomponents/DatepickerInput.tsx +111 -0
  174. package/lib/components/WeekPicker/subcomponents/VisibleField.tsx +126 -0
  175. package/lib/components/WeekPicker/subcomponents/index.ts +3 -0
  176. package/lib/components/index.ts +17 -0
  177. package/lib/hooks/index.ts +2 -0
  178. package/lib/hooks/useFocusTrap.ts +123 -0
  179. package/lib/index.ts +1 -0
  180. package/lib/theme/defaultTheme.ts +7 -0
  181. package/lib/utils/__tests__/capitalise.test.ts +40 -0
  182. package/lib/utils/capitalise.ts +4 -0
  183. package/package.json +1 -1
  184. package/lib/components/Field/__tests__/__snapshots__/Field.test.tsx.snap +0 -300
@@ -1,36 +1,23 @@
1
- import {
2
- memo,
3
- forwardRef,
4
- AnchorHTMLAttributes,
5
- ElementType,
6
- ComponentPropsWithRef,
7
- } from 'react';
1
+ import { forwardRef } from 'react';
8
2
  import { css, cx } from '@emotion/css';
9
- import { useTheme } from '../../theme';
3
+ import useTheme from '../../theme/useTheme';
4
+ import BaseLink, { BaseLinkProps } from './BaseLink';
10
5
 
11
- export const NAME = 'ucl-link';
12
-
13
- export interface LinkBaseProps {
14
- noVisited?: boolean;
15
- testId?: string;
16
- component?: ElementType;
6
+ export interface LinkProps extends BaseLinkProps {
7
+ variant?: 'primary' | 'secondary';
17
8
  }
18
9
 
19
- export type LinkProps<C extends ElementType = 'a'> = LinkBaseProps &
20
- (C extends 'a'
21
- ? AnchorHTMLAttributes<HTMLAnchorElement>
22
- : Omit<ComponentPropsWithRef<C>, keyof LinkBaseProps>);
23
-
24
- export type Ref = HTMLAnchorElement;
10
+ const NAME = 'ucl-uikit-link';
25
11
 
26
- const Link = forwardRef<Ref, LinkProps>(
12
+ const Link = forwardRef<HTMLAnchorElement, LinkProps>(
27
13
  (
28
14
  {
29
15
  noVisited = false,
16
+ variant = 'primary',
17
+ disabled = false,
30
18
  testId = NAME,
31
19
  className,
32
20
  children,
33
- component: Component = 'a',
34
21
  ...props
35
22
  },
36
23
  ref
@@ -38,15 +25,24 @@ const Link = forwardRef<Ref, LinkProps>(
38
25
  const [theme] = useTheme();
39
26
 
40
27
  const baseStyle = css`
41
- color: ${theme.color.link.default};
42
- font-family: ${theme.font.family.primary};
28
+ text-decoration-skip-ink: none;
29
+ text-decoration-thickness: 10%;
30
+ text-underline-offset: 25%;
31
+ text-underline-position: from-font;
32
+ `;
43
33
 
44
- &:hover {
34
+ const primaryStyle = css`
35
+ color: ${theme.color.link.default};
36
+ &:active {
45
37
  color: ${theme.color.link.hover};
46
38
  }
39
+ `;
40
+
41
+ const secondaryStyle = css`
42
+ color: ${theme.color.link.secondaryDefault};
47
43
 
48
44
  &:active {
49
- color: ${theme.color.link.visited};
45
+ color: ${theme.color.link.secondaryHover};
50
46
  }
51
47
  `;
52
48
 
@@ -56,31 +52,75 @@ const Link = forwardRef<Ref, LinkProps>(
56
52
  }
57
53
  `;
58
54
 
59
- const noVisitedStyle = css`
55
+ const primaryNoVisitedStyle = css`
60
56
  &:visited {
61
57
  color: ${theme.color.link.default};
62
58
  }
63
59
  `;
64
60
 
61
+ const secondaryNoVisitedStyle = css`
62
+ &:visited {
63
+ color: ${theme.color.link.secondaryDefault};
64
+ }
65
+ `;
66
+
67
+ const primaryHoverStyle = css`
68
+ &:hover {
69
+ color: ${theme.color.link.hover};
70
+ }
71
+ `;
72
+
73
+ const secondaryHoverStyle = css`
74
+ &:hover {
75
+ color: ${theme.color.link.secondaryHover};
76
+ }
77
+ `;
78
+
79
+ const disabledStyle = css`
80
+ cursor: not-allowed;
81
+ color: ${theme.color.link.disabled};
82
+
83
+ &:hover {
84
+ color: ${theme.color.link.disabled};
85
+ }
86
+ `;
87
+
88
+ let variantStyle = undefined;
89
+ if (variant === 'primary') {
90
+ variantStyle = cx(
91
+ primaryStyle,
92
+ noVisited && !disabled && primaryNoVisitedStyle,
93
+ !disabled && primaryHoverStyle
94
+ );
95
+ } else if (variant === 'secondary') {
96
+ variantStyle = cx(
97
+ secondaryStyle,
98
+ noVisited && !disabled && secondaryNoVisitedStyle,
99
+ !disabled && secondaryHoverStyle
100
+ );
101
+ }
102
+
65
103
  const style = cx(
66
104
  NAME,
67
105
  baseStyle,
68
- !noVisited && visitedStyle,
69
- noVisited && noVisitedStyle,
106
+ variantStyle,
107
+ !noVisited && !disabled && visitedStyle,
108
+ disabled && disabledStyle,
70
109
  className
71
110
  );
72
111
 
73
112
  return (
74
- <Component
113
+ <BaseLink
75
114
  ref={ref}
76
115
  className={style}
77
116
  data-testid={testId}
117
+ disabled={disabled}
78
118
  {...props}
79
119
  >
80
120
  {children}
81
- </Component>
121
+ </BaseLink>
82
122
  );
83
123
  }
84
124
  );
85
125
 
86
- export default memo(Link);
126
+ export default Link;
@@ -2,8 +2,8 @@
2
2
 
3
3
  exports[`Link > snapshot: no props 1`] = `
4
4
  <a
5
- class="ucl-link css-f2zime"
6
- data-testid="ucl-link"
5
+ class="ucl-uikit-base-link ucl-uikit-link css-145ds9b"
6
+ data-testid="ucl-uikit-link"
7
7
  >
8
8
  test
9
9
  </a>
@@ -11,7 +11,7 @@ exports[`Link > snapshot: no props 1`] = `
11
11
 
12
12
  exports[`Link > snapshot: testId prop 1`] = `
13
13
  <a
14
- class="ucl-link css-f2zime"
14
+ class="ucl-uikit-base-link ucl-uikit-link css-145ds9b"
15
15
  data-testid="test123"
16
16
  >
17
17
  testidlink
@@ -12,9 +12,7 @@ describe('Link', () => {
12
12
  <Link>test</Link>
13
13
  </ThemeContextProvider>
14
14
  );
15
- expect(
16
- renderResult.container.firstChild
17
- ).toMatchSnapshot();
15
+ expect(renderResult.container.firstChild).toMatchSnapshot();
18
16
  });
19
17
 
20
18
  test('snapshot: testId prop', () => {
@@ -23,9 +21,7 @@ describe('Link', () => {
23
21
  <Link testId='test123'> testidlink </Link>
24
22
  </ThemeContextProvider>
25
23
  );
26
- expect(
27
- renderResult.container.firstChild
28
- ).toMatchSnapshot();
24
+ expect(renderResult.container.firstChild).toMatchSnapshot();
29
25
  });
30
26
 
31
27
  // // Interaction tests
@@ -36,7 +32,8 @@ describe('Link', () => {
36
32
  <Link> testidlink </Link>
37
33
  </ThemeContextProvider>
38
34
  );
39
- const link = screen.getByTestId('ucl-link');
35
+
36
+ const link = screen.getByTestId('ucl-uikit-link');
40
37
  expect(link).toBeDefined();
41
38
  });
42
39
 
@@ -56,12 +53,8 @@ describe('Link', () => {
56
53
  <Link href='/testlink'>linktext</Link>
57
54
  </ThemeContextProvider>
58
55
  );
59
- const link = screen.getByRole(
60
- 'link'
61
- ) as HTMLAnchorElement;
56
+ const link = screen.getByRole('link') as HTMLAnchorElement;
62
57
  expect(link.textContent).toBe('linktext');
63
- expect(link.href).toBe(
64
- 'http://localhost:3000/testlink'
65
- );
58
+ expect(link.href).toBe('http://localhost:3000/testlink');
66
59
  });
67
60
  });
@@ -1,2 +1,2 @@
1
1
  export { default } from './Link';
2
- export type { LinkProps, LinkBaseProps } from './Link';
2
+ export type { LinkProps } from './Link';
@@ -68,7 +68,9 @@ const MenuProvider = ({
68
68
  if (!isOpen || !contentRef.current) return;
69
69
 
70
70
  const focusableElements = Array.from(
71
- contentRef.current.querySelectorAll('[role="menuitem"]')
71
+ contentRef.current.querySelectorAll(
72
+ '[role="menuitem"], [aria-label="Close menu"]'
73
+ )
72
74
  ).filter((el) => {
73
75
  const element = el as HTMLElement;
74
76
  return (
@@ -37,7 +37,7 @@ const Menu = ({
37
37
  const style = cx(NAME, baseStyle, className);
38
38
 
39
39
  return (
40
- <div
40
+ <nav
41
41
  className={style}
42
42
  data-testid={testId}
43
43
  {...props}
@@ -52,7 +52,7 @@ const Menu = ({
52
52
  {children}
53
53
  </MenuContent>
54
54
  </MenuProvider>
55
- </div>
55
+ </nav>
56
56
  );
57
57
  };
58
58
 
@@ -9,7 +9,7 @@ import { useAppMenu } from './Menu.context';
9
9
 
10
10
  export const NAME = 'ucl-uikit-menu__content';
11
11
 
12
- export interface MenuContentProps extends HTMLAttributes<HTMLDivElement> {
12
+ export interface MenuContentProps extends HTMLAttributes<HTMLUListElement> {
13
13
  title?: string;
14
14
  testId?: string;
15
15
  position?: 'left' | 'right';
@@ -91,7 +91,8 @@ const MenuContent: React.FC<MenuContentProps> = ({
91
91
  list-style: none;
92
92
 
93
93
  @media (min-width: ${theme.breakpoints.desktop}px) {
94
- margin: 0 ${padding.p64} 0 ${padding.p72};
94
+ margin: 0 ${padding.p64};
95
+ padding: 0;
95
96
  }
96
97
  `;
97
98
 
@@ -114,14 +115,13 @@ const MenuContent: React.FC<MenuContentProps> = ({
114
115
  <header className={headerStyle}>
115
116
  <h3 className={titleStyle}>{title}</h3>
116
117
  <IconButton
117
- role='menuitem'
118
118
  onClick={toggleMenu}
119
119
  aria-label='Close menu'
120
120
  >
121
121
  <Icon.X size={40} />
122
122
  </IconButton>
123
123
  </header>
124
- <div
124
+ <ul
125
125
  role='menu'
126
126
  aria-labelledby='app-menu-button'
127
127
  className={bodyStyle}
@@ -130,7 +130,7 @@ const MenuContent: React.FC<MenuContentProps> = ({
130
130
  {...props}
131
131
  >
132
132
  {children}
133
- </div>
133
+ </ul>
134
134
  </div>
135
135
  );
136
136
 
@@ -1,12 +1,15 @@
1
1
  import React, { LiHTMLAttributes, memo } from 'react';
2
2
  import { css, cx } from '@emotion/css';
3
3
  import useTheme from '../../theme/useTheme';
4
+ import Icon from '../Icon/Icon';
4
5
 
5
6
  export const NAME = 'ucl-uikit-menu__item';
6
7
 
7
8
  export interface MenuItemProps extends LiHTMLAttributes<HTMLLIElement> {
8
9
  testId?: string;
9
10
  icon?: React.ReactNode;
11
+ secondary?: boolean;
12
+ externalLink?: boolean;
10
13
  }
11
14
 
12
15
  const MenuItem: React.FC<MenuItemProps> = ({
@@ -15,6 +18,8 @@ const MenuItem: React.FC<MenuItemProps> = ({
15
18
  children,
16
19
  className,
17
20
  onClick,
21
+ secondary,
22
+ externalLink,
18
23
  ...props
19
24
  }) => {
20
25
  const isClickable = !!onClick;
@@ -22,17 +27,21 @@ const MenuItem: React.FC<MenuItemProps> = ({
22
27
 
23
28
  const baseStyle = css`
24
29
  height: 40px;
25
- margin-bottom: ${theme.margin.m32};
30
+ margin-bottom: ${theme.margin.m16};
26
31
  display: flex;
27
32
  align-items: center;
28
33
  font-family: ${theme.font.family.primary};
29
34
  font-size: ${theme.font.size.f16};
30
- font-weight: ${theme.font.weight.bold};
35
+ font-weight: ${secondary
36
+ ? `${theme.font.weight.medium}`
37
+ : `${theme.font.weight.bold}`};
31
38
  color: ${theme.color.text.secondary};
32
39
  `;
33
40
 
34
41
  const clickableMenuItemStyle = css`
35
- color: ${theme.color.text.primary};
42
+ color: ${secondary
43
+ ? `${theme.color.text.secondary}`
44
+ : `${theme.color.text.primary}`};
36
45
  cursor: pointer;
37
46
  &:hover {
38
47
  background-color: ${theme.color.interaction.blue5};
@@ -77,6 +86,14 @@ const MenuItem: React.FC<MenuItemProps> = ({
77
86
  {...props}
78
87
  >
79
88
  {content}
89
+ {externalLink && (
90
+ <Icon.ExternalLink
91
+ size={16}
92
+ className={css`
93
+ margin-left: 8px;
94
+ `}
95
+ />
96
+ )}
80
97
  </li>
81
98
  );
82
99
  };
@@ -4,7 +4,7 @@ import { useTheme } from '../../theme';
4
4
 
5
5
  export const NAME = 'ucl-uikit-menu__section';
6
6
 
7
- export interface MenuSectionProps extends HTMLAttributes<HTMLElement> {
7
+ export interface MenuSectionProps extends HTMLAttributes<HTMLLIElement> {
8
8
  testId?: string;
9
9
  }
10
10
 
@@ -34,12 +34,13 @@ const MenuSection: React.FC<MenuSectionProps> = ({
34
34
  `;
35
35
 
36
36
  return (
37
- <section
37
+ <li
38
+ role='presentation'
38
39
  className={style}
39
40
  {...props}
40
41
  >
41
42
  <ul className={listStyle}>{children}</ul>
42
- </section>
43
+ </li>
43
44
  );
44
45
  };
45
46
 
@@ -102,7 +102,7 @@ const PaginationControls = ({
102
102
  return (
103
103
  <nav
104
104
  className={style}
105
- aria-label='Pagination'
105
+ aria-label={`Pagination, ${totalPages} pages total`}
106
106
  data-testid={testId}
107
107
  >
108
108
  <ul className={listStyle}>
@@ -140,8 +140,6 @@ const PaginationControls = ({
140
140
  variant='secondary'
141
141
  aria-label={`Page ${page}, current page`}
142
142
  aria-current='page'
143
- aria-setsize={totalPages}
144
- aria-posinset={page}
145
143
  disabled
146
144
  onClick={() => handlePageChange(page)}
147
145
  >
@@ -0,0 +1,41 @@
1
+ import type { Meta, StoryObj } from '@storybook/react';
2
+ import Search from './Search';
3
+
4
+ const meta = {
5
+ title: 'Components/Ready to use/Search',
6
+ component: Search,
7
+ parameters: { layout: 'padded' },
8
+ argTypes: {
9
+ placeholder: { control: { type: 'text' } },
10
+ disabled: { control: { type: 'boolean' } },
11
+ testId: { control: { type: 'text' } },
12
+ onSearch: { action: 'searched' },
13
+ },
14
+ tags: ['autodocs'],
15
+ } satisfies Meta<typeof Search>;
16
+
17
+ export default meta;
18
+ type Story = StoryObj<typeof meta>;
19
+
20
+ export const Default: Story = {
21
+ args: {
22
+ placeholder: 'Search...',
23
+ },
24
+ render: (args) => <Search {...args} />,
25
+ };
26
+
27
+ export const Disabled: Story = {
28
+ args: {
29
+ placeholder: 'Search (disabled)',
30
+ disabled: true,
31
+ },
32
+ render: (args) => <Search {...args} />,
33
+ };
34
+
35
+ export const WithPlaceholder: Story = {
36
+ name: 'With custom placeholder',
37
+ args: {
38
+ placeholder: 'Type to filter results',
39
+ },
40
+ render: (args) => <Search {...args} />,
41
+ };
@@ -0,0 +1,167 @@
1
+ import { HTMLAttributes, useState } from 'react';
2
+ import { css, cx } from '@emotion/css';
3
+ import { Input, Icon, IconButton, InputProps, IconButtonProps } from '../..';
4
+ import useTheme from '../../theme/useTheme';
5
+ import marginsStyle, { MarginProps } from '../common/marginsStyle';
6
+
7
+ export interface SearchBaseProps extends HTMLAttributes<HTMLDivElement> {
8
+ onSearch?: (searchTerms: string) => void;
9
+ placeholder?: string;
10
+ disabled?: boolean;
11
+ inputProps?: InputProps;
12
+ clearButtonProps?: IconButtonProps;
13
+ submitButtonProps?: IconButtonProps;
14
+ testId?: string;
15
+ }
16
+
17
+ export type SearchProps = SearchBaseProps & MarginProps;
18
+
19
+ export const NAME = 'ucl-uikit-search';
20
+
21
+ const Search = ({
22
+ placeholder = '',
23
+ testId = NAME,
24
+ onSearch,
25
+ disabled = false,
26
+ m,
27
+ mv,
28
+ mh,
29
+ mt,
30
+ mb,
31
+ ml,
32
+ mr,
33
+ noMargins,
34
+ inputProps,
35
+ clearButtonProps,
36
+ submitButtonProps,
37
+ className,
38
+ ...props
39
+ }: SearchProps) => {
40
+ const [searchTerms, setSearchTerms] = useState<string>('');
41
+ const [theme] = useTheme();
42
+
43
+ const handleSearchTermsChange = (e: React.ChangeEvent<HTMLInputElement>) => {
44
+ setSearchTerms(e.target.value);
45
+ };
46
+
47
+ const handleClearButtonClick = () => {
48
+ setSearchTerms('');
49
+ };
50
+
51
+ const handleSearchButtonClick = () => {
52
+ if (onSearch) {
53
+ onSearch(searchTerms);
54
+ }
55
+ };
56
+
57
+ const baseStyle = css`
58
+ position: relative;
59
+ height: 48px;
60
+ `;
61
+
62
+ const style = cx(
63
+ NAME,
64
+ baseStyle,
65
+ marginsStyle(
66
+ {
67
+ m,
68
+ mv,
69
+ mh,
70
+ mt,
71
+ mb,
72
+ ml,
73
+ mr,
74
+ noMargins,
75
+ },
76
+ theme
77
+ ),
78
+ className
79
+ );
80
+
81
+ const inputStyle = css`
82
+ width: 100%;
83
+ box-sizing: border-box;
84
+ padding-right: 96px;
85
+ `;
86
+
87
+ const buttonsStyle = css`
88
+ position: absolute;
89
+ right: 1px;
90
+ top: 1px;
91
+ bottom: 1px;
92
+ display: flex;
93
+ justify-content: flex-end;
94
+ align-items: center;
95
+ `;
96
+
97
+ const cancelButtonBaseStyle = css`
98
+ margin-left: ${theme.margin.m12};
99
+ margin-right: ${theme.margin.m12};
100
+ `;
101
+
102
+ const cancelIconButtonInactiveStyle = css`
103
+ color: ${theme.color.neutral.grey20};
104
+ `;
105
+
106
+ const cancelButtonStyle = cx(
107
+ cancelButtonBaseStyle,
108
+ searchTerms.length === 0 && cancelIconButtonInactiveStyle
109
+ );
110
+
111
+ const dividerStyle = css`
112
+ width: 1px;
113
+ height: 38px;
114
+ background-color: ${theme.color.neutral.grey20};
115
+ `;
116
+
117
+ const submitButtonStyle = css`
118
+ width: 48px;
119
+ height: 100%;
120
+ `;
121
+
122
+ return (
123
+ <div
124
+ className={style}
125
+ {...props}
126
+ >
127
+ <Input
128
+ inputClassName={inputStyle}
129
+ placeholder={placeholder}
130
+ type='text'
131
+ onChange={handleSearchTermsChange}
132
+ value={searchTerms}
133
+ disabled={disabled}
134
+ data-testid={testId}
135
+ {...inputProps}
136
+ />
137
+
138
+ <div className={buttonsStyle}>
139
+ <IconButton
140
+ disabled={disabled}
141
+ className={cancelButtonStyle}
142
+ onClick={handleClearButtonClick}
143
+ aria-label='Clear search'
144
+ data-testid={`${testId}-clear-search-btn`}
145
+ {...clearButtonProps}
146
+ >
147
+ <Icon.X />
148
+ </IconButton>
149
+
150
+ <div className={dividerStyle} />
151
+
152
+ <IconButton
153
+ className={submitButtonStyle}
154
+ disabled={disabled}
155
+ onClick={handleSearchButtonClick}
156
+ aria-label='Search'
157
+ data-testid={`${testId}-search-btn`}
158
+ {...submitButtonProps}
159
+ >
160
+ <Icon.Search />
161
+ </IconButton>
162
+ </div>
163
+ </div>
164
+ );
165
+ };
166
+
167
+ export default Search;