uikit-react-public 0.40.2 → 0.40.5
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/BreadcrumbsNew/BaseBreadcrumbsItem.d.ts +1 -2
- package/dist/components/BreadcrumbsNew/BreadcrumbsLink.d.ts +3 -1
- package/dist/components/Footer/Footer.d.ts +10 -5
- package/dist/components/Footer/Footer.stories.d.ts +1 -1
- package/dist/components/Footer/FooterNavLink.d.ts +1 -1
- package/dist/components/Footer/SocialLink.d.ts +1 -1
- package/dist/components/FooterNew/FooterNavLink.d.ts +1 -1
- package/dist/components/FooterNew/SocialLink.d.ts +1 -1
- package/dist/components/Table/Table.context.d.ts +2 -0
- package/dist/components/Table/Table.d.ts +1 -1
- package/dist/components/Table/Table.stories.d.ts +1 -1
- package/dist/components/Table/Table.types.d.ts +2 -0
- package/dist/index.js +6218 -6142
- package/lib/components/BreadcrumbsNew/BaseBreadcrumbsItem.tsx +0 -9
- package/lib/components/BreadcrumbsNew/BreadcrumbsLink.tsx +23 -6
- package/lib/components/BreadcrumbsNew/__tests__/Breadcrumbs.test.tsx +3 -0
- package/lib/components/Footer/Footer.tsx +83 -6
- package/lib/components/Footer/FooterColumn.tsx +2 -3
- package/lib/components/Footer/FooterNavLink.tsx +3 -1
- package/lib/components/Footer/SocialLink.tsx +4 -1
- package/lib/components/Footer/__tests__/Footer.test.tsx +42 -9
- package/lib/components/FooterNew/BackToTop.tsx +1 -1
- package/lib/components/FooterNew/Footer.tsx +1 -1
- package/lib/components/FooterNew/FooterColumn.tsx +1 -1
- package/lib/components/FooterNew/FooterLinks.tsx +1 -1
- package/lib/components/FooterNew/FooterNavLink.tsx +3 -1
- package/lib/components/FooterNew/LegalAndCopyright.tsx +1 -1
- package/lib/components/FooterNew/LogoAddressAndSocial.tsx +2 -1
- package/lib/components/FooterNew/SocialLink.tsx +4 -1
- package/lib/components/Table/Table.context.ts +10 -1
- package/lib/components/Table/Table.tsx +8 -3
- package/lib/components/Table/Table.types.ts +2 -0
- package/lib/components/Table/__tests__/Table.test.tsx +90 -0
- package/lib/components/Table/subcomponents/Body.tsx +7 -1
- package/lib/components/Table/subcomponents/Cell/Cell.tsx +22 -18
- package/lib/components/Table/subcomponents/Cell/CellContent.tsx +11 -5
- package/lib/components/Table/subcomponents/Head.tsx +7 -1
- package/lib/components/Table/subcomponents/Row.tsx +10 -6
- package/package.json +1 -1
|
@@ -8,7 +8,6 @@ export const NAME = 'ucl-uikit-breadcrumbs__item';
|
|
|
8
8
|
|
|
9
9
|
export interface BaseBreadcrumbsItemProps extends LiHTMLAttributes<HTMLLIElement> {
|
|
10
10
|
separator?: boolean;
|
|
11
|
-
back?: boolean;
|
|
12
11
|
size?: BreadcrumbsSize;
|
|
13
12
|
testId?: string;
|
|
14
13
|
children?: ReactNode;
|
|
@@ -16,7 +15,6 @@ export interface BaseBreadcrumbsItemProps extends LiHTMLAttributes<HTMLLIElement
|
|
|
16
15
|
|
|
17
16
|
const BaseBreadcrumbsItem = ({
|
|
18
17
|
separator = false,
|
|
19
|
-
back = false,
|
|
20
18
|
size = 'medium',
|
|
21
19
|
testId = NAME,
|
|
22
20
|
className,
|
|
@@ -47,13 +45,6 @@ const BaseBreadcrumbsItem = ({
|
|
|
47
45
|
data-testid={testId}
|
|
48
46
|
{...props}
|
|
49
47
|
>
|
|
50
|
-
{back && (
|
|
51
|
-
<Icon.ArrowLeft
|
|
52
|
-
aria-hidden
|
|
53
|
-
className={iconStyle}
|
|
54
|
-
size={size === 'small' ? 16 : 20}
|
|
55
|
-
/>
|
|
56
|
-
)}
|
|
57
48
|
{children}
|
|
58
49
|
{separator && (
|
|
59
50
|
<Icon.ChevronRight
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { Children, isValidElement, ReactNode } from 'react';
|
|
2
2
|
import { css, cx } from '@emotion/css';
|
|
3
|
+
import Icon from '../Icon';
|
|
3
4
|
import StandaloneLink, { StandaloneLinkProps } from '../StandaloneLink';
|
|
4
5
|
import { useTheme } from '../../theme';
|
|
5
6
|
import BaseBreadcrumbsItem, {
|
|
@@ -10,8 +11,10 @@ export const NAME = 'ucl-uikit-breadcrumbs__link';
|
|
|
10
11
|
|
|
11
12
|
type BreadcrumbsLinkOwnProps = Pick<
|
|
12
13
|
BaseBreadcrumbsItemProps,
|
|
13
|
-
'separator' | '
|
|
14
|
-
|
|
14
|
+
'separator' | 'size'
|
|
15
|
+
> & {
|
|
16
|
+
back?: boolean;
|
|
17
|
+
};
|
|
15
18
|
|
|
16
19
|
export type BreadcrumbsLinkProps = BreadcrumbsLinkOwnProps &
|
|
17
20
|
Omit<StandaloneLinkProps, 'size' | 'variant'>;
|
|
@@ -45,9 +48,15 @@ const BreadcrumbsLink = ({
|
|
|
45
48
|
: theme.typography.body.mdSemibold.fontWeight;
|
|
46
49
|
|
|
47
50
|
const linkStyle = css`
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
+
${
|
|
52
|
+
back
|
|
53
|
+
? ''
|
|
54
|
+
: `
|
|
55
|
+
flex-direction: column;
|
|
56
|
+
align-items: flex-start;
|
|
57
|
+
gap: 0;
|
|
58
|
+
`
|
|
59
|
+
}
|
|
51
60
|
|
|
52
61
|
&::after {
|
|
53
62
|
content: attr(data-breadcrumb-label);
|
|
@@ -62,11 +71,19 @@ const BreadcrumbsLink = ({
|
|
|
62
71
|
return (
|
|
63
72
|
<BaseBreadcrumbsItem
|
|
64
73
|
separator={separator}
|
|
65
|
-
back={back}
|
|
66
74
|
size={size}
|
|
67
75
|
>
|
|
68
76
|
<StandaloneLink
|
|
69
77
|
className={cx(NAME, linkStyle, className)}
|
|
78
|
+
icon={
|
|
79
|
+
back ? (
|
|
80
|
+
<Icon.ArrowLeft
|
|
81
|
+
aria-hidden
|
|
82
|
+
width={size === 'small' ? 16 : 20}
|
|
83
|
+
height={size === 'small' ? 16 : 20}
|
|
84
|
+
/>
|
|
85
|
+
) : undefined
|
|
86
|
+
}
|
|
70
87
|
variant='secondary'
|
|
71
88
|
noVisited
|
|
72
89
|
size={size === 'small' ? 'small' : 'default'}
|
|
@@ -30,6 +30,9 @@ describe('BreadcrumbsNew', () => {
|
|
|
30
30
|
expect(
|
|
31
31
|
within(lists[1]).getByRole('link', { hidden: true })
|
|
32
32
|
).toHaveAttribute('href', '/parent');
|
|
33
|
+
expect(
|
|
34
|
+
within(lists[1]).getByRole('link', { hidden: true }).querySelector('svg')
|
|
35
|
+
).toBeTruthy();
|
|
33
36
|
const parentLink = within(lists[0]).getByRole('link', {
|
|
34
37
|
name: 'Parent',
|
|
35
38
|
hidden: true,
|
|
@@ -1,23 +1,31 @@
|
|
|
1
1
|
import { memo, HTMLAttributes } from 'react';
|
|
2
2
|
import { css, cx } from '@emotion/css';
|
|
3
|
-
import {
|
|
3
|
+
import { themes, useTheme } from '../../theme';
|
|
4
|
+
import Divider from '../Divider';
|
|
5
|
+
import { UclLogo } from '../UclLogo';
|
|
4
6
|
import FooterColumn from './FooterColumn';
|
|
7
|
+
import type { FooterColumnProps } from './FooterColumn';
|
|
5
8
|
import SocialLink from './SocialLink';
|
|
6
9
|
import FooterNavLink from './FooterNavLink';
|
|
10
|
+
import type { FooterNavLinkProps } from './FooterNavLink';
|
|
11
|
+
import FooterNew from '../FooterNew';
|
|
7
12
|
|
|
8
13
|
export const NAME = 'ucl-uikit-footer';
|
|
9
14
|
|
|
10
15
|
export interface FooterProps extends HTMLAttributes<HTMLElement> {
|
|
11
16
|
testId?: string;
|
|
17
|
+
addressFormat?: 'compact' | 'full';
|
|
18
|
+
showSocialLinks?: boolean;
|
|
12
19
|
disclaimer?: string;
|
|
13
20
|
freedomOfInformation?: string;
|
|
14
21
|
accessibility?: string;
|
|
15
22
|
cookies?: string;
|
|
16
23
|
privacy?: string;
|
|
17
24
|
slaveryStatement?: string;
|
|
25
|
+
login?: string;
|
|
18
26
|
}
|
|
19
27
|
|
|
20
|
-
const
|
|
28
|
+
const LegacyFooter = ({
|
|
21
29
|
testId = NAME,
|
|
22
30
|
className,
|
|
23
31
|
disclaimer = 'https://www.ucl.ac.uk/legal-services/disclaimer',
|
|
@@ -257,17 +265,86 @@ const Footer = ({
|
|
|
257
265
|
);
|
|
258
266
|
};
|
|
259
267
|
|
|
268
|
+
const Footer = ({
|
|
269
|
+
testId = NAME,
|
|
270
|
+
className,
|
|
271
|
+
addressFormat,
|
|
272
|
+
showSocialLinks,
|
|
273
|
+
disclaimer,
|
|
274
|
+
freedomOfInformation,
|
|
275
|
+
accessibility,
|
|
276
|
+
cookies,
|
|
277
|
+
privacy,
|
|
278
|
+
slaveryStatement,
|
|
279
|
+
login,
|
|
280
|
+
children,
|
|
281
|
+
...props
|
|
282
|
+
}: FooterProps) => {
|
|
283
|
+
const [theme] = useTheme();
|
|
284
|
+
|
|
285
|
+
if (theme === themes.default) {
|
|
286
|
+
return (
|
|
287
|
+
<LegacyFooter
|
|
288
|
+
testId={testId}
|
|
289
|
+
className={className}
|
|
290
|
+
disclaimer={disclaimer}
|
|
291
|
+
freedomOfInformation={freedomOfInformation}
|
|
292
|
+
accessibility={accessibility}
|
|
293
|
+
cookies={cookies}
|
|
294
|
+
privacy={privacy}
|
|
295
|
+
slaveryStatement={slaveryStatement}
|
|
296
|
+
{...props}
|
|
297
|
+
>
|
|
298
|
+
{children}
|
|
299
|
+
</LegacyFooter>
|
|
300
|
+
);
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
return (
|
|
304
|
+
<FooterNew
|
|
305
|
+
testId={testId}
|
|
306
|
+
className={className}
|
|
307
|
+
addressFormat={addressFormat}
|
|
308
|
+
showSocialLinks={showSocialLinks}
|
|
309
|
+
disclaimer={disclaimer}
|
|
310
|
+
freedomOfInformation={freedomOfInformation}
|
|
311
|
+
accessibility={accessibility}
|
|
312
|
+
cookies={cookies}
|
|
313
|
+
privacy={privacy}
|
|
314
|
+
slaveryStatement={slaveryStatement}
|
|
315
|
+
login={login}
|
|
316
|
+
{...props}
|
|
317
|
+
>
|
|
318
|
+
{children}
|
|
319
|
+
</FooterNew>
|
|
320
|
+
);
|
|
321
|
+
};
|
|
322
|
+
|
|
323
|
+
const FooterColumnBridge = (props: FooterColumnProps) => {
|
|
324
|
+
const [theme] = useTheme();
|
|
325
|
+
const Column = theme === themes.default ? FooterColumn : FooterNew.Column;
|
|
326
|
+
|
|
327
|
+
return <Column {...props} />;
|
|
328
|
+
};
|
|
329
|
+
|
|
330
|
+
const FooterNavLinkBridge = (props: FooterNavLinkProps) => {
|
|
331
|
+
const [theme] = useTheme();
|
|
332
|
+
const NavLink = theme === themes.default ? FooterNavLink : FooterNew.NavLink;
|
|
333
|
+
|
|
334
|
+
return <NavLink {...props} />;
|
|
335
|
+
};
|
|
336
|
+
|
|
260
337
|
const MemoFooter = memo(Footer);
|
|
261
338
|
|
|
262
339
|
export interface IFooterSubComponents {
|
|
263
|
-
Column: typeof
|
|
264
|
-
NavLink: typeof
|
|
340
|
+
Column: typeof FooterColumnBridge;
|
|
341
|
+
NavLink: typeof FooterNavLinkBridge;
|
|
265
342
|
}
|
|
266
343
|
|
|
267
344
|
const FooterWithSubComponents = MemoFooter as typeof MemoFooter &
|
|
268
345
|
IFooterSubComponents;
|
|
269
346
|
|
|
270
|
-
FooterWithSubComponents.Column =
|
|
271
|
-
FooterWithSubComponents.NavLink =
|
|
347
|
+
FooterWithSubComponents.Column = FooterColumnBridge;
|
|
348
|
+
FooterWithSubComponents.NavLink = FooterNavLinkBridge;
|
|
272
349
|
|
|
273
350
|
export default FooterWithSubComponents;
|
|
@@ -1,11 +1,10 @@
|
|
|
1
1
|
import { memo, HTMLAttributes } from 'react';
|
|
2
2
|
import { css, cx } from '@emotion/css';
|
|
3
|
-
import { useTheme } from '
|
|
3
|
+
import { useTheme } from '../../theme';
|
|
4
4
|
|
|
5
5
|
export const NAME = 'ucl-uikit-footer__column';
|
|
6
6
|
|
|
7
|
-
export interface FooterColumnProps
|
|
8
|
-
extends HTMLAttributes<HTMLDivElement> {
|
|
7
|
+
export interface FooterColumnProps extends HTMLAttributes<HTMLDivElement> {
|
|
9
8
|
heading: string;
|
|
10
9
|
testId?: string;
|
|
11
10
|
}
|
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
import { memo } from 'react';
|
|
2
2
|
import { css, cx } from '@emotion/css';
|
|
3
|
-
import {
|
|
3
|
+
import { useTheme } from '../../theme';
|
|
4
|
+
import Link from '../Link';
|
|
5
|
+
import type { LinkProps } from '../Link';
|
|
4
6
|
|
|
5
7
|
export const NAME = 'ucl-uikit-footer__nav-link';
|
|
6
8
|
|
|
@@ -1,6 +1,9 @@
|
|
|
1
1
|
import { memo } from 'react';
|
|
2
2
|
import { css, cx } from '@emotion/css';
|
|
3
|
-
import {
|
|
3
|
+
import { useTheme } from '../../theme';
|
|
4
|
+
import Icon from '../Icon';
|
|
5
|
+
import Link from '../Link';
|
|
6
|
+
import type { LinkProps } from '../Link';
|
|
4
7
|
|
|
5
8
|
export const NAME = 'ucl-uikit-footer__social-link';
|
|
6
9
|
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { describe, expect, test } from 'vitest';
|
|
2
|
-
import { render } from '@testing-library/react';
|
|
2
|
+
import { render, screen } from '@testing-library/react';
|
|
3
3
|
import Footer from '../Footer';
|
|
4
4
|
import { ThemeContextProvider } from '../../../theme/useTheme';
|
|
5
5
|
|
|
@@ -12,9 +12,7 @@ describe('Footer', () => {
|
|
|
12
12
|
<Footer />
|
|
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: footer links provided', () => {
|
|
@@ -30,9 +28,7 @@ describe('Footer', () => {
|
|
|
30
28
|
/>
|
|
31
29
|
</ThemeContextProvider>
|
|
32
30
|
);
|
|
33
|
-
expect(
|
|
34
|
-
renderResult.container.firstChild
|
|
35
|
-
).toMatchSnapshot();
|
|
31
|
+
expect(renderResult.container.firstChild).toMatchSnapshot();
|
|
36
32
|
});
|
|
37
33
|
|
|
38
34
|
test('snapshot: nav links', () => {
|
|
@@ -50,8 +46,45 @@ describe('Footer', () => {
|
|
|
50
46
|
</Footer>
|
|
51
47
|
</ThemeContextProvider>
|
|
52
48
|
);
|
|
49
|
+
expect(renderResult.container.firstChild).toMatchSnapshot();
|
|
50
|
+
});
|
|
51
|
+
|
|
52
|
+
test('uses the legacy footer for the default theme', () => {
|
|
53
|
+
render(
|
|
54
|
+
<ThemeContextProvider>
|
|
55
|
+
<Footer />
|
|
56
|
+
</ThemeContextProvider>
|
|
57
|
+
);
|
|
58
|
+
|
|
59
|
+
expect(
|
|
60
|
+
screen.queryByRole('button', { name: /back to top/i })
|
|
61
|
+
).not.toBeInTheDocument();
|
|
62
|
+
});
|
|
63
|
+
|
|
64
|
+
test('uses the new footer for light and dark themes', () => {
|
|
65
|
+
render(
|
|
66
|
+
<ThemeContextProvider theme='light'>
|
|
67
|
+
<Footer
|
|
68
|
+
login='https://www.ucl.ac.uk/login'
|
|
69
|
+
showSocialLinks={false}
|
|
70
|
+
>
|
|
71
|
+
<Footer.Column heading='Category 1'>
|
|
72
|
+
<Footer.NavLink href='#'>Item 1</Footer.NavLink>
|
|
73
|
+
</Footer.Column>
|
|
74
|
+
</Footer>
|
|
75
|
+
</ThemeContextProvider>
|
|
76
|
+
);
|
|
77
|
+
|
|
53
78
|
expect(
|
|
54
|
-
|
|
55
|
-
).
|
|
79
|
+
screen.getByRole('button', { name: /back to top/i })
|
|
80
|
+
).toBeInTheDocument();
|
|
81
|
+
expect(screen.getByRole('link', { name: 'Login' })).toHaveAttribute(
|
|
82
|
+
'href',
|
|
83
|
+
'https://www.ucl.ac.uk/login'
|
|
84
|
+
);
|
|
85
|
+
expect(screen.getByText('Category 1')).toBeInTheDocument();
|
|
86
|
+
expect(screen.getByRole('link', { name: 'Item 1' })).toHaveClass(
|
|
87
|
+
'ucl-uikit-footer__nav-link'
|
|
88
|
+
);
|
|
56
89
|
});
|
|
57
90
|
});
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { memo, HTMLAttributes } from 'react';
|
|
2
2
|
import { css, cx } from '@emotion/css';
|
|
3
|
-
import { useTheme } from '
|
|
3
|
+
import { useTheme } from '../../theme';
|
|
4
4
|
import FooterColumn from './FooterColumn';
|
|
5
5
|
import FooterNavLink from './FooterNavLink';
|
|
6
6
|
import LogoAddressAndSocial from './LogoAddressAndSocial';
|
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
import { memo } from 'react';
|
|
2
2
|
import { css, cx } from '@emotion/css';
|
|
3
|
-
import {
|
|
3
|
+
import { useTheme } from '../../theme';
|
|
4
|
+
import Link from '../Link';
|
|
5
|
+
import type { LinkProps } from '../Link';
|
|
4
6
|
|
|
5
7
|
export const NAME = 'ucl-uikit-footer__nav-link';
|
|
6
8
|
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { HTMLAttributes } from 'react';
|
|
2
2
|
import { css, cx } from '@emotion/css';
|
|
3
|
-
import {
|
|
3
|
+
import { useTheme } from '../../theme';
|
|
4
|
+
import UclLogoNew from '../UclLogoNew/UclLogo';
|
|
4
5
|
import SocialLink from './SocialLink';
|
|
5
6
|
|
|
6
7
|
export const NAME = 'ucl-uikit-footer__logo-address-social';
|
|
@@ -1,6 +1,9 @@
|
|
|
1
1
|
import { memo } from 'react';
|
|
2
2
|
import { css, cx } from '@emotion/css';
|
|
3
|
-
import {
|
|
3
|
+
import { useTheme } from '../../theme';
|
|
4
|
+
import Icon from '../Icon';
|
|
5
|
+
import Link from '../Link';
|
|
6
|
+
import type { LinkProps } from '../Link';
|
|
4
7
|
|
|
5
8
|
export const NAME = 'ucl-uikit-footer__social-link';
|
|
6
9
|
|
|
@@ -3,10 +3,19 @@ import type { TableColumnMetadata } from './Table.types';
|
|
|
3
3
|
|
|
4
4
|
export type TableContextValue = {
|
|
5
5
|
columns: TableColumnMetadata[];
|
|
6
|
+
disableMobileView: boolean;
|
|
6
7
|
};
|
|
7
8
|
|
|
8
|
-
const TableContext = createContext<TableContextValue>({
|
|
9
|
+
const TableContext = createContext<TableContextValue>({
|
|
10
|
+
columns: [],
|
|
11
|
+
disableMobileView: false,
|
|
12
|
+
});
|
|
9
13
|
|
|
10
14
|
export const TableContextProvider = TableContext.Provider;
|
|
11
15
|
|
|
12
16
|
export const useTableContext = () => use(TableContext);
|
|
17
|
+
|
|
18
|
+
export const getMobileBreakpoint = (
|
|
19
|
+
tabletBreakpoint: number,
|
|
20
|
+
disableMobileView: boolean
|
|
21
|
+
) => (disableMobileView ? -1 : tabletBreakpoint - 1);
|
|
@@ -2,7 +2,7 @@ import { Children, isValidElement } from 'react';
|
|
|
2
2
|
import { css, cx } from '@emotion/css';
|
|
3
3
|
import { Head, HeadCell, Body, Row, Cell } from './subcomponents';
|
|
4
4
|
import { useTheme } from '../../theme';
|
|
5
|
-
import { TableContextProvider } from './Table.context';
|
|
5
|
+
import { TableContextProvider, getMobileBreakpoint } from './Table.context';
|
|
6
6
|
import type {
|
|
7
7
|
ColumnVariant,
|
|
8
8
|
NormalizedTableMobileRole,
|
|
@@ -78,10 +78,15 @@ const Table = ({
|
|
|
78
78
|
children,
|
|
79
79
|
ref,
|
|
80
80
|
ariaLabel,
|
|
81
|
+
disableMobileView = false,
|
|
81
82
|
...props
|
|
82
83
|
}: TableProps) => {
|
|
83
84
|
const [theme] = useTheme();
|
|
84
85
|
const columns = getColumnsFromChildren(children);
|
|
86
|
+
const mobileBreakpoint = getMobileBreakpoint(
|
|
87
|
+
theme.breakpoints.tablet,
|
|
88
|
+
disableMobileView
|
|
89
|
+
);
|
|
85
90
|
|
|
86
91
|
const baseStyle = css`
|
|
87
92
|
width: 100%;
|
|
@@ -92,7 +97,7 @@ const Table = ({
|
|
|
92
97
|
line-height: ${theme.typography.body.sm.lineHeight}%;
|
|
93
98
|
border-collapse: collapse;
|
|
94
99
|
|
|
95
|
-
@media screen and (max-width: ${
|
|
100
|
+
@media screen and (max-width: ${mobileBreakpoint}px) {
|
|
96
101
|
display: block;
|
|
97
102
|
border-collapse: separate;
|
|
98
103
|
}
|
|
@@ -101,7 +106,7 @@ const Table = ({
|
|
|
101
106
|
const style = cx(baseStyle, className, NAME);
|
|
102
107
|
|
|
103
108
|
return (
|
|
104
|
-
<TableContextProvider value={{ columns }}>
|
|
109
|
+
<TableContextProvider value={{ columns, disableMobileView }}>
|
|
105
110
|
<table
|
|
106
111
|
className={style}
|
|
107
112
|
data-testid={testId}
|
|
@@ -33,4 +33,6 @@ export interface TableProps extends React.HTMLAttributes<HTMLTableElement> {
|
|
|
33
33
|
className?: string;
|
|
34
34
|
ref?: React.Ref<HTMLTableElement>;
|
|
35
35
|
ariaLabel?: string;
|
|
36
|
+
/** Disable the responsive switch to a mobile card layout below the tablet breakpoint, keeping a real table at all widths. */
|
|
37
|
+
disableMobileView?: boolean;
|
|
36
38
|
}
|
|
@@ -3,6 +3,10 @@ import { describe, expect, test } from 'vitest';
|
|
|
3
3
|
import { fireEvent, render, screen, within } from '@testing-library/react';
|
|
4
4
|
import Table from '../Table';
|
|
5
5
|
import { ThemeContextProvider } from '../../../theme/useTheme';
|
|
6
|
+
import defaultTheme from '../../../theme/original/defaultTheme';
|
|
7
|
+
|
|
8
|
+
const getGeneratedClass = (element: HTMLElement) =>
|
|
9
|
+
[...element.classList].find((className) => className.startsWith('css-'));
|
|
6
10
|
|
|
7
11
|
describe('Table', () => {
|
|
8
12
|
// Snapshot tests
|
|
@@ -231,4 +235,90 @@ describe('Table', () => {
|
|
|
231
235
|
|
|
232
236
|
expect(row).toHaveAttribute('data-mobile-expanded', 'true');
|
|
233
237
|
});
|
|
238
|
+
|
|
239
|
+
test('uses the real mobile breakpoint for the card layout by default', () => {
|
|
240
|
+
render(
|
|
241
|
+
<ThemeContextProvider>
|
|
242
|
+
<Table>
|
|
243
|
+
<Table.Body>
|
|
244
|
+
<Table.Row>
|
|
245
|
+
<Table.Cell>Test Cell</Table.Cell>
|
|
246
|
+
</Table.Row>
|
|
247
|
+
</Table.Body>
|
|
248
|
+
</Table>
|
|
249
|
+
</ThemeContextProvider>
|
|
250
|
+
);
|
|
251
|
+
|
|
252
|
+
const table = screen.getByTestId('ucl-uikit-table');
|
|
253
|
+
const tableClass = getGeneratedClass(table);
|
|
254
|
+
const mobileMaxWidth = defaultTheme.breakpoints.tablet - 1;
|
|
255
|
+
|
|
256
|
+
expect(tableClass).toBeDefined();
|
|
257
|
+
expect(document.head.textContent).toContain(
|
|
258
|
+
`@media screen and (max-width: ${mobileMaxWidth}px){.${tableClass}{display:block;border-collapse:separate;}}`
|
|
259
|
+
);
|
|
260
|
+
});
|
|
261
|
+
|
|
262
|
+
test('disableMobileView removes the mobile-card layout media query from the table', () => {
|
|
263
|
+
render(
|
|
264
|
+
<ThemeContextProvider>
|
|
265
|
+
<Table disableMobileView>
|
|
266
|
+
<Table.Body>
|
|
267
|
+
<Table.Row>
|
|
268
|
+
<Table.Cell>Test Cell</Table.Cell>
|
|
269
|
+
</Table.Row>
|
|
270
|
+
</Table.Body>
|
|
271
|
+
</Table>
|
|
272
|
+
</ThemeContextProvider>
|
|
273
|
+
);
|
|
274
|
+
|
|
275
|
+
const table = screen.getByTestId('ucl-uikit-table');
|
|
276
|
+
const tableClass = getGeneratedClass(table);
|
|
277
|
+
const mobileMaxWidth = defaultTheme.breakpoints.tablet - 1;
|
|
278
|
+
|
|
279
|
+
expect(tableClass).toBeDefined();
|
|
280
|
+
expect(document.head.textContent).toContain(
|
|
281
|
+
`@media screen and (max-width: -1px){.${tableClass}{display:block;border-collapse:separate;}}`
|
|
282
|
+
);
|
|
283
|
+
expect(document.head.textContent).not.toContain(
|
|
284
|
+
`@media screen and (max-width: ${mobileMaxWidth}px){.${tableClass}{display:block;border-collapse:separate;}}`
|
|
285
|
+
);
|
|
286
|
+
});
|
|
287
|
+
|
|
288
|
+
test('disableMobileView also disables the row card layout and details disclosure media queries', () => {
|
|
289
|
+
render(
|
|
290
|
+
<ThemeContextProvider>
|
|
291
|
+
<Table disableMobileView>
|
|
292
|
+
<Table.Head>
|
|
293
|
+
<Table.HeadCell mobileRole='primary'>Name</Table.HeadCell>
|
|
294
|
+
<Table.HeadCell>Email</Table.HeadCell>
|
|
295
|
+
</Table.Head>
|
|
296
|
+
<Table.Body>
|
|
297
|
+
<Table.Row>
|
|
298
|
+
<Table.Cell>Ada Lovelace</Table.Cell>
|
|
299
|
+
<Table.Cell>ada@example.com</Table.Cell>
|
|
300
|
+
</Table.Row>
|
|
301
|
+
</Table.Body>
|
|
302
|
+
</Table>
|
|
303
|
+
</ThemeContextProvider>
|
|
304
|
+
);
|
|
305
|
+
|
|
306
|
+
const row = screen.getByTestId('ucl-uikit-table__row');
|
|
307
|
+
const rowClass = getGeneratedClass(row);
|
|
308
|
+
const detailsCell = row.querySelector('[data-mobile-role="details"]');
|
|
309
|
+
const detailsClass =
|
|
310
|
+
detailsCell && getGeneratedClass(detailsCell as HTMLElement);
|
|
311
|
+
|
|
312
|
+
expect(rowClass).toBeDefined();
|
|
313
|
+
expect(document.head.textContent).toContain(
|
|
314
|
+
`@media screen and (max-width: -1px){.${rowClass}{display:grid;`
|
|
315
|
+
);
|
|
316
|
+
|
|
317
|
+
// The details disclosure cell is hidden via a min-width query one above
|
|
318
|
+
// the (disabled) mobile breakpoint, so it forces display:none at every width.
|
|
319
|
+
expect(detailsClass).toBeDefined();
|
|
320
|
+
expect(document.head.textContent).toContain(
|
|
321
|
+
`@media screen and (min-width: 0px){.${detailsClass}{display:none;}}`
|
|
322
|
+
);
|
|
323
|
+
});
|
|
234
324
|
});
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { css, cx } from '@emotion/css';
|
|
2
2
|
import { useTheme } from '../../../theme';
|
|
3
|
+
import { getMobileBreakpoint, useTableContext } from '../Table.context';
|
|
3
4
|
|
|
4
5
|
export interface TableBodyProps extends React.HTMLAttributes<HTMLTableSectionElement> {}
|
|
5
6
|
|
|
@@ -7,9 +8,14 @@ const NAME = 'ucl-uikit-table__body';
|
|
|
7
8
|
|
|
8
9
|
const Body = ({ className, children, ...props }: TableBodyProps) => {
|
|
9
10
|
const [theme] = useTheme();
|
|
11
|
+
const { disableMobileView } = useTableContext();
|
|
12
|
+
const mobileBreakpoint = getMobileBreakpoint(
|
|
13
|
+
theme.breakpoints.tablet,
|
|
14
|
+
disableMobileView
|
|
15
|
+
);
|
|
10
16
|
|
|
11
17
|
const baseStyle = css`
|
|
12
|
-
@media screen and (max-width: ${
|
|
18
|
+
@media screen and (max-width: ${mobileBreakpoint}px) {
|
|
13
19
|
display: block;
|
|
14
20
|
width: 100%;
|
|
15
21
|
}
|