uikit-react-public 0.26.0 → 0.26.3

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.
@@ -23,7 +23,13 @@ const HeaderItem = ({
23
23
  min-height: 40px;
24
24
 
25
25
  &:not(:first-of-type) {
26
- margin-left: ${theme.margin.m8};
26
+ margin-left: ${theme.margin.m16};
27
+ }
28
+
29
+ @media (min-width: ${theme.breakpoints.desktop}px) {
30
+ &:not(:first-of-type) {
31
+ margin-left: 0;
32
+ }
27
33
  }
28
34
  `;
29
35
 
@@ -39,4 +39,34 @@ describe('Header', () => {
39
39
  expect(link).toBeInTheDocument();
40
40
  expect(link.getAttribute('href')).toBe('/');
41
41
  });
42
+
43
+ test('spaces subsequent header items on smaller screens and resets spacing on desktop', () => {
44
+ render(
45
+ <ThemeContextProvider>
46
+ <Header title='LIDS'>
47
+ <Header.MenuContainer>
48
+ <Header.Item testId='first-header-item'>First</Header.Item>
49
+ <Header.Item testId='second-header-item'>Second</Header.Item>
50
+ </Header.MenuContainer>
51
+ </Header>
52
+ </ThemeContextProvider>
53
+ );
54
+
55
+ const firstItem = screen.getByTestId('first-header-item');
56
+ const secondItem = screen.getByTestId('second-header-item');
57
+ const generatedClass = [...secondItem.classList].find((className) =>
58
+ className.startsWith('css-')
59
+ );
60
+ const styles = document.head.textContent;
61
+
62
+ expect(firstItem).toHaveClass('ucl-uikit-header__item');
63
+ expect(secondItem).toHaveClass('ucl-uikit-header__item');
64
+ expect(generatedClass).toBeDefined();
65
+ expect(styles).toContain(
66
+ `.${generatedClass}:not(:first-of-type){margin-left:16px;}`
67
+ );
68
+ expect(styles).toContain(
69
+ `@media (min-width: 1056px){.${generatedClass}:not(:first-of-type){margin-left:0;}}`
70
+ );
71
+ });
42
72
  });
@@ -14,6 +14,7 @@ import MenuAccordionChevron from './MenuAccordionChevron';
14
14
  import MenuAccordionItem, { MenuAccordionItemProps } from './MenuAccordionItem';
15
15
 
16
16
  export const NAME = 'ucl-uikit-menu__accordion';
17
+ const FOCUS_RING_GUTTER_PX = 6;
17
18
 
18
19
  export interface MenuAccordionProps extends Omit<
19
20
  MenuBaseItemProps,
@@ -60,8 +61,35 @@ const MenuAccordion = ({
60
61
  `;
61
62
 
62
63
  const bodyStyle = css`
64
+ display: grid;
65
+ grid-template-rows: 0fr;
63
66
  width: 100%;
67
+ margin-top: 0;
68
+ overflow: hidden;
69
+ opacity: 0;
70
+ pointer-events: none;
71
+ transition:
72
+ grid-template-rows 200ms ease-in-out,
73
+ margin-top 200ms ease-in-out,
74
+ opacity 200ms ease-in-out;
75
+ `;
76
+
77
+ const bodyExpandedStyle = css`
78
+ grid-template-rows: 1fr;
64
79
  margin-top: ${theme.margin.m8};
80
+ opacity: 1;
81
+ pointer-events: auto;
82
+ transition:
83
+ grid-template-rows 200ms ease-in-out,
84
+ margin-top 200ms ease-in-out,
85
+ opacity 200ms ease-in-out;
86
+ `;
87
+
88
+ const bodyInnerStyle = css`
89
+ min-height: 0;
90
+ padding-top: ${FOCUS_RING_GUTTER_PX}px;
91
+ padding-bottom: ${FOCUS_RING_GUTTER_PX}px;
92
+ overflow: hidden;
65
93
  `;
66
94
 
67
95
  const handleClick = (event: MouseEvent<HTMLDivElement>) => {
@@ -74,7 +102,7 @@ const MenuAccordion = ({
74
102
  <MenuBaseItem
75
103
  className={style}
76
104
  iconPosition='left'
77
- underlineOnHover={false}
105
+ hoverMode='background'
78
106
  onClick={handleClick}
79
107
  endAdornment={<MenuAccordionChevron expanded={expanded} />}
80
108
  aria-expanded={expanded}
@@ -84,7 +112,13 @@ const MenuAccordion = ({
84
112
  <MenuItemText className={textStyle}>{label}</MenuItemText>
85
113
  </span>
86
114
  </MenuBaseItem>
87
- {expanded && <div className={bodyStyle}>{children}</div>}
115
+ <div
116
+ className={cx(bodyStyle, expanded && bodyExpandedStyle)}
117
+ aria-hidden={!expanded}
118
+ inert={!expanded}
119
+ >
120
+ <div className={bodyInnerStyle}>{children}</div>
121
+ </div>
88
122
  </>
89
123
  );
90
124
  };
@@ -1,3 +1,4 @@
1
+ import { css, cx } from '@emotion/css';
1
2
  import Icon from '../../Icon';
2
3
 
3
4
  export interface MenuAccordionChevronProps {
@@ -8,23 +9,24 @@ export interface MenuAccordionChevronProps {
8
9
  const MenuAccordionChevron = ({
9
10
  expanded,
10
11
  className,
11
- }: MenuAccordionChevronProps) =>
12
- expanded ? (
13
- <Icon.ChevronUp
14
- className={className}
15
- size={24}
16
- aria-hidden='true'
17
- focusable='false'
18
- testId='ucl-uikit-menu__accordion__icon--chevron-up'
19
- />
20
- ) : (
12
+ }: MenuAccordionChevronProps) => {
13
+ const baseStyle = css`
14
+ transition: transform 150ms ease-in-out;
15
+ `;
16
+
17
+ const expandedStyle = css`
18
+ transform: rotate(180deg);
19
+ `;
20
+
21
+ return (
21
22
  <Icon.ChevronDown
22
- className={className}
23
+ className={cx(baseStyle, expanded && expandedStyle, className)}
23
24
  size={24}
24
25
  aria-hidden='true'
25
26
  focusable='false'
26
27
  testId='ucl-uikit-menu__accordion__icon--chevron-down'
27
28
  />
28
29
  );
30
+ };
29
31
 
30
32
  export default MenuAccordionChevron;
@@ -5,6 +5,7 @@ import MenuItemText from '../MenuItemText';
5
5
  import { useTheme } from '../../../theme';
6
6
 
7
7
  export const NAME = 'ucl-uikit-menu__accordion-item';
8
+ const FOCUS_RING_GUTTER_PX = 6;
8
9
 
9
10
  export interface MenuAccordionItemProps extends HTMLAttributes<HTMLDivElement> {
10
11
  asChild?: boolean;
@@ -21,11 +22,12 @@ const MenuAccordionItem = ({
21
22
  const [theme] = useTheme();
22
23
 
23
24
  const baseStyle = css`
24
- margin: 0;
25
- padding-left: ${theme.padding.p40};
25
+ width: calc(100% - ${FOCUS_RING_GUTTER_PX * 2}px);
26
+ margin: 0 ${FOCUS_RING_GUTTER_PX}px;
27
+ padding-left: calc(${theme.padding.p40} - ${FOCUS_RING_GUTTER_PX}px);
26
28
 
27
29
  @media screen and (min-width: ${theme.breakpoints.tablet}px) {
28
- padding: 0 ${theme.padding.p56};
30
+ padding: 0 calc(${theme.padding.p56} - ${FOCUS_RING_GUTTER_PX}px);
29
31
  }
30
32
  `;
31
33
 
@@ -6,7 +6,7 @@ export interface MenuBaseItemProps extends HTMLAttributes<HTMLDivElement> {
6
6
  icon?: ReactNode;
7
7
  iconPosition?: 'left' | 'right';
8
8
  endAdornment?: ReactNode;
9
- underlineOnHover?: boolean;
9
+ hoverMode?: 'background' | 'underline';
10
10
  asChild?: boolean;
11
11
  }
12
12
 
@@ -20,7 +20,7 @@ const MenuBaseItem = ({
20
20
  onKeyDown,
21
21
  role,
22
22
  tabIndex,
23
- underlineOnHover = true,
23
+ hoverMode = 'background',
24
24
  asChild = false,
25
25
  ...props
26
26
  }: MenuBaseItemProps) => {
@@ -63,15 +63,26 @@ const MenuBaseItem = ({
63
63
  &:focus-within {
64
64
  box-shadow: ${theme.boxShadow.focus};
65
65
  }
66
+ `;
66
67
 
67
- ${underlineOnHover &&
68
- `
69
- &:hover .ucl-uikit-menu__item-text {
70
- text-decoration: underline;
71
- }
72
- `}
68
+ const backgroundOnHoverStyle = css`
69
+ &:hover {
70
+ background-color: ${theme.colour.fill.brandSubtleHover};
71
+ }
73
72
  `;
74
- const style = cx(baseStyle, className);
73
+
74
+ const underlineStyleOnHoverStyle = css`
75
+ &:hover .ucl-uikit-menu__item-text {
76
+ text-decoration: underline;
77
+ }
78
+ `;
79
+
80
+ const style = cx(
81
+ baseStyle,
82
+ hoverMode === 'underline' && underlineStyleOnHoverStyle,
83
+ hoverMode === 'background' && backgroundOnHoverStyle,
84
+ className
85
+ );
75
86
 
76
87
  const endAdornmentStyle = css`
77
88
  margin-left: auto;
@@ -49,6 +49,7 @@ const SecondaryMenuItem = ({
49
49
  <MenuBaseItem
50
50
  className={style}
51
51
  asChild={asChild}
52
+ hoverMode='underline'
52
53
  {...props}
53
54
  >
54
55
  <span className={contentStyle}>
@@ -86,7 +86,7 @@ describe('Menu', () => {
86
86
  ).toBeInTheDocument();
87
87
  });
88
88
 
89
- test('accordion toggles to expanded state with up chevron', () => {
89
+ test('accordion toggles to expanded state with rotated down chevron', () => {
90
90
  wrap(
91
91
  <Menu>
92
92
  <Menu.Accordion
@@ -98,14 +98,51 @@ describe('Menu', () => {
98
98
  </Menu>
99
99
  );
100
100
 
101
- expect(screen.queryByText('Library')).not.toBeInTheDocument();
101
+ const accordionContent = screen
102
+ .getByText('Library')
103
+ .closest('[aria-hidden]');
104
+ expect(accordionContent).toHaveAttribute('aria-hidden', 'true');
102
105
  fireEvent.click(screen.getByText('Services'));
106
+ expect(accordionContent).toHaveAttribute('aria-hidden', 'false');
103
107
  expect(
104
- screen.getByTestId('ucl-uikit-menu__accordion__icon--chevron-up')
108
+ screen.getByTestId('ucl-uikit-menu__accordion__icon--chevron-down')
105
109
  ).toBeInTheDocument();
110
+ expect(
111
+ screen.queryByTestId('ucl-uikit-menu__accordion__icon--chevron-up')
112
+ ).not.toBeInTheDocument();
106
113
  expect(screen.getByText('Library')).toBeInTheDocument();
107
114
  });
108
115
 
116
+ test('accordion items allow space for focus borders', () => {
117
+ wrap(
118
+ <Menu>
119
+ <Menu.Accordion
120
+ label='Services'
121
+ defaultExpanded
122
+ >
123
+ <Menu.Accordion.Item>Library</Menu.Accordion.Item>
124
+ </Menu.Accordion>
125
+ </Menu>
126
+ );
127
+
128
+ const accordionItem = screen
129
+ .getByText('Library')
130
+ .closest('.ucl-uikit-menu__accordion-item');
131
+ const accordionContentInner = accordionItem?.parentElement;
132
+
133
+ expect(accordionItem).toHaveStyle({
134
+ marginTop: '0px',
135
+ marginLeft: '6px',
136
+ marginRight: '6px',
137
+ marginBottom: '0px',
138
+ width: 'calc(100% - 12px)',
139
+ });
140
+ expect(accordionContentInner).toHaveStyle({
141
+ paddingTop: '6px',
142
+ paddingBottom: '6px',
143
+ });
144
+ });
145
+
109
146
  test('accordion item supports asChild link content', () => {
110
147
  wrap(
111
148
  <Menu>
@@ -220,10 +257,10 @@ describe('Menu', () => {
220
257
  expect(item?.firstChild).toHaveAttribute('data-testid', 'left-icon');
221
258
  });
222
259
 
223
- test('can disable hover underline', () => {
260
+ test('primary items use background hover instead of underline hover', () => {
224
261
  wrap(
225
262
  <Menu>
226
- <Menu.PrimaryItem underlineOnHover={false}>Home</Menu.PrimaryItem>
263
+ <Menu.PrimaryItem>Home</Menu.PrimaryItem>
227
264
  </Menu>
228
265
  );
229
266
 
package/package.json CHANGED
@@ -2,7 +2,7 @@
2
2
  "name": "uikit-react-public",
3
3
  "private": false,
4
4
  "license": "UNLICENSED",
5
- "version": "0.26.0",
5
+ "version": "0.26.3",
6
6
  "type": "module",
7
7
  "main": "dist/index.js",
8
8
  "types": "dist/index.d.ts",