uikit-react-public 0.29.1 → 0.29.2
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/BreadcrumbsLink.d.ts +2 -2
- package/dist/index.js +3198 -3186
- package/lib/components/BreadcrumbsNew/BreadcrumbsLink.tsx +39 -24
- package/lib/components/BreadcrumbsNew/__tests__/Breadcrumbs.test.tsx +6 -0
- package/lib/components/StandaloneLink/StandaloneLink.tsx +40 -7
- package/lib/components/StandaloneLink/__tests__/StandaloneLink.test.tsx +44 -0
- package/lib/components/StandaloneLink/__tests__/__snapshots__/StandaloneLink.test.tsx.snap +2 -2
- package/package.json +1 -1
|
@@ -1,5 +1,6 @@
|
|
|
1
|
+
import { Children, isValidElement, ReactNode } from 'react';
|
|
1
2
|
import { css, cx } from '@emotion/css';
|
|
2
|
-
import
|
|
3
|
+
import StandaloneLink, { StandaloneLinkProps } from '../StandaloneLink';
|
|
3
4
|
import { useTheme } from '../../theme';
|
|
4
5
|
import BaseBreadcrumbsItem, {
|
|
5
6
|
BaseBreadcrumbsItemProps,
|
|
@@ -13,7 +14,20 @@ type BreadcrumbsLinkOwnProps = Pick<
|
|
|
13
14
|
>;
|
|
14
15
|
|
|
15
16
|
export type BreadcrumbsLinkProps = BreadcrumbsLinkOwnProps &
|
|
16
|
-
Omit<
|
|
17
|
+
Omit<StandaloneLinkProps, 'size' | 'variant'>;
|
|
18
|
+
|
|
19
|
+
const getTextContent = (node: ReactNode): string =>
|
|
20
|
+
Children.toArray(node)
|
|
21
|
+
.map((child) => {
|
|
22
|
+
if (typeof child === 'string' || typeof child === 'number') {
|
|
23
|
+
return String(child);
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
return isValidElement<{ children?: ReactNode }>(child)
|
|
27
|
+
? getTextContent(child.props.children)
|
|
28
|
+
: '';
|
|
29
|
+
})
|
|
30
|
+
.join('');
|
|
17
31
|
|
|
18
32
|
const BreadcrumbsLink = ({
|
|
19
33
|
separator,
|
|
@@ -24,27 +38,26 @@ const BreadcrumbsLink = ({
|
|
|
24
38
|
...props
|
|
25
39
|
}: BreadcrumbsLinkProps) => {
|
|
26
40
|
const [theme] = useTheme();
|
|
41
|
+
const label = getTextContent(children);
|
|
42
|
+
const semiboldWeight =
|
|
43
|
+
size === 'small'
|
|
44
|
+
? theme.typography.body.smSemibold.fontWeight
|
|
45
|
+
: theme.typography.body.mdSemibold.fontWeight;
|
|
27
46
|
|
|
28
|
-
const linkStyle =
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
text-decoration: none;
|
|
33
|
-
|
|
34
|
-
&:visited {
|
|
35
|
-
color: ${theme.colour.link.primary};
|
|
36
|
-
}
|
|
47
|
+
const linkStyle = css`
|
|
48
|
+
flex-direction: column;
|
|
49
|
+
align-items: flex-start;
|
|
50
|
+
gap: 0;
|
|
37
51
|
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
);
|
|
52
|
+
&::after {
|
|
53
|
+
content: attr(data-breadcrumb-label);
|
|
54
|
+
height: 0;
|
|
55
|
+
overflow: hidden;
|
|
56
|
+
visibility: hidden;
|
|
57
|
+
font-weight: ${semiboldWeight};
|
|
58
|
+
pointer-events: none;
|
|
59
|
+
}
|
|
60
|
+
`;
|
|
48
61
|
|
|
49
62
|
return (
|
|
50
63
|
<BaseBreadcrumbsItem
|
|
@@ -52,14 +65,16 @@ const BreadcrumbsLink = ({
|
|
|
52
65
|
back={back}
|
|
53
66
|
size={size}
|
|
54
67
|
>
|
|
55
|
-
<
|
|
56
|
-
className={linkStyle}
|
|
68
|
+
<StandaloneLink
|
|
69
|
+
className={cx(NAME, linkStyle, className)}
|
|
70
|
+
variant='secondary'
|
|
57
71
|
noVisited
|
|
58
72
|
size={size === 'small' ? 'small' : 'default'}
|
|
73
|
+
data-breadcrumb-label={label}
|
|
59
74
|
{...props}
|
|
60
75
|
>
|
|
61
76
|
{children}
|
|
62
|
-
</
|
|
77
|
+
</StandaloneLink>
|
|
63
78
|
</BaseBreadcrumbsItem>
|
|
64
79
|
);
|
|
65
80
|
};
|
|
@@ -30,6 +30,12 @@ describe('BreadcrumbsNew', () => {
|
|
|
30
30
|
expect(
|
|
31
31
|
within(lists[1]).getByRole('link', { hidden: true })
|
|
32
32
|
).toHaveAttribute('href', '/parent');
|
|
33
|
+
const parentLink = within(lists[0]).getByRole('link', {
|
|
34
|
+
name: 'Parent',
|
|
35
|
+
hidden: true,
|
|
36
|
+
});
|
|
37
|
+
expect(parentLink).toHaveClass('ucl-uikit-standalone-link');
|
|
38
|
+
expect(parentLink).toHaveAttribute('data-breadcrumb-label', 'Parent');
|
|
33
39
|
expect(screen.getByText('Current page')).toHaveAttribute(
|
|
34
40
|
'aria-current',
|
|
35
41
|
'page'
|
|
@@ -1,8 +1,10 @@
|
|
|
1
1
|
import React, {
|
|
2
|
+
Children,
|
|
2
3
|
cloneElement,
|
|
3
4
|
ComponentPropsWithoutRef,
|
|
4
5
|
ElementType,
|
|
5
6
|
forwardRef,
|
|
7
|
+
isValidElement,
|
|
6
8
|
ReactElement,
|
|
7
9
|
} from 'react';
|
|
8
10
|
import { css, cx } from '@emotion/css';
|
|
@@ -41,6 +43,8 @@ const StandaloneLink = forwardRef(function StandaloneLinkInner(
|
|
|
41
43
|
testId = NAME,
|
|
42
44
|
className,
|
|
43
45
|
children,
|
|
46
|
+
asChild = false,
|
|
47
|
+
size = 'default',
|
|
44
48
|
...props
|
|
45
49
|
}: StandaloneLinkProps<ElementType>,
|
|
46
50
|
ref: React.Ref<Element>
|
|
@@ -52,11 +56,22 @@ const StandaloneLink = forwardRef(function StandaloneLinkInner(
|
|
|
52
56
|
}[variant];
|
|
53
57
|
|
|
54
58
|
const [theme] = useTheme();
|
|
59
|
+
const {
|
|
60
|
+
typography: {
|
|
61
|
+
body: { md, sm },
|
|
62
|
+
},
|
|
63
|
+
} = theme;
|
|
64
|
+
const typography = size === 'small' ? sm : md;
|
|
55
65
|
|
|
56
66
|
const baseStyle = css`
|
|
57
67
|
display: inline-flex;
|
|
58
68
|
align-items: center;
|
|
59
69
|
gap: 8px;
|
|
70
|
+
font-family: ${typography.fontFamily};
|
|
71
|
+
font-feature-settings: ${typography.fontSettings};
|
|
72
|
+
font-size: ${typography.fontSize}px;
|
|
73
|
+
font-weight: ${typography.fontWeight};
|
|
74
|
+
line-height: ${typography.lineHeight}%;
|
|
60
75
|
text-decoration: none;
|
|
61
76
|
`;
|
|
62
77
|
|
|
@@ -178,21 +193,39 @@ const StandaloneLink = forwardRef(function StandaloneLinkInner(
|
|
|
178
193
|
className
|
|
179
194
|
);
|
|
180
195
|
|
|
196
|
+
const renderContent = (content: React.ReactNode) => (
|
|
197
|
+
<>
|
|
198
|
+
{icon &&
|
|
199
|
+
iconPosition === 'left' &&
|
|
200
|
+
cloneElement(icon, { size: iconSize })}
|
|
201
|
+
{content}
|
|
202
|
+
{icon &&
|
|
203
|
+
iconPosition === 'right' &&
|
|
204
|
+
cloneElement(icon, { size: iconSize })}
|
|
205
|
+
</>
|
|
206
|
+
);
|
|
207
|
+
|
|
208
|
+
const content = asChild
|
|
209
|
+
? (() => {
|
|
210
|
+
const child = Children.only(children);
|
|
211
|
+
|
|
212
|
+
return isValidElement<{ children?: React.ReactNode }>(child)
|
|
213
|
+
? cloneElement(child, {}, renderContent(child.props.children))
|
|
214
|
+
: child;
|
|
215
|
+
})()
|
|
216
|
+
: renderContent(children);
|
|
217
|
+
|
|
181
218
|
return (
|
|
182
219
|
<BaseLink
|
|
183
220
|
ref={ref}
|
|
184
221
|
className={style}
|
|
185
222
|
testId={testId}
|
|
186
223
|
disabled={disabled}
|
|
224
|
+
asChild={asChild}
|
|
225
|
+
size={size}
|
|
187
226
|
{...props}
|
|
188
227
|
>
|
|
189
|
-
{
|
|
190
|
-
iconPosition === 'left' &&
|
|
191
|
-
cloneElement(icon, { size: iconSize })}
|
|
192
|
-
{children}
|
|
193
|
-
{icon &&
|
|
194
|
-
iconPosition === 'right' &&
|
|
195
|
-
cloneElement(icon, { size: iconSize })}
|
|
228
|
+
{content}
|
|
196
229
|
</BaseLink>
|
|
197
230
|
);
|
|
198
231
|
}) as unknown as StandaloneLinkComponent;
|
|
@@ -2,6 +2,7 @@ import { describe, expect, test } from 'vitest';
|
|
|
2
2
|
import { render, screen } from '@testing-library/react';
|
|
3
3
|
import StandaloneLink from '../StandaloneLink';
|
|
4
4
|
import { ThemeContextProvider } from '../../../theme/useTheme';
|
|
5
|
+
import Text from '../../Text';
|
|
5
6
|
|
|
6
7
|
describe('Link', () => {
|
|
7
8
|
// Snapshot tests
|
|
@@ -54,4 +55,47 @@ describe('Link', () => {
|
|
|
54
55
|
expect(link.textContent).toBe('linktext');
|
|
55
56
|
expect(link.href).toBe('http://localhost:3000/testlink');
|
|
56
57
|
});
|
|
58
|
+
|
|
59
|
+
test('renders a custom link child with asChild', () => {
|
|
60
|
+
render(
|
|
61
|
+
<ThemeContextProvider>
|
|
62
|
+
<StandaloneLink asChild>
|
|
63
|
+
<a href='/testlink'>linktext</a>
|
|
64
|
+
</StandaloneLink>
|
|
65
|
+
</ThemeContextProvider>
|
|
66
|
+
);
|
|
67
|
+
|
|
68
|
+
const link = screen.getByRole('link');
|
|
69
|
+
|
|
70
|
+
expect(link).toHaveAttribute('href', '/testlink');
|
|
71
|
+
expect(link).toHaveClass('ucl-uikit-standalone-link');
|
|
72
|
+
expect(link).toHaveTextContent('linktext');
|
|
73
|
+
});
|
|
74
|
+
|
|
75
|
+
test.each([
|
|
76
|
+
{ size: 'default' as const, textLevel: 'md' as const },
|
|
77
|
+
{ size: 'small' as const, textLevel: 'sm' as const },
|
|
78
|
+
])('uses the same typography as $textLevel Text', ({ size, textLevel }) => {
|
|
79
|
+
render(
|
|
80
|
+
<ThemeContextProvider>
|
|
81
|
+
<StandaloneLink
|
|
82
|
+
variant='secondary'
|
|
83
|
+
size={size}
|
|
84
|
+
href='/testlink'
|
|
85
|
+
>
|
|
86
|
+
linktext
|
|
87
|
+
</StandaloneLink>
|
|
88
|
+
<Text level={textLevel}>text</Text>
|
|
89
|
+
</ThemeContextProvider>
|
|
90
|
+
);
|
|
91
|
+
|
|
92
|
+
const linkStyle = getComputedStyle(screen.getByRole('link'));
|
|
93
|
+
const textStyle = getComputedStyle(screen.getByText('text'));
|
|
94
|
+
|
|
95
|
+
expect(linkStyle.fontFamily).toBe(textStyle.fontFamily);
|
|
96
|
+
expect(linkStyle.fontFeatureSettings).toBe(textStyle.fontFeatureSettings);
|
|
97
|
+
expect(linkStyle.fontSize).toBe(textStyle.fontSize);
|
|
98
|
+
expect(linkStyle.fontWeight).toBe(textStyle.fontWeight);
|
|
99
|
+
expect(linkStyle.lineHeight).toBe(textStyle.lineHeight);
|
|
100
|
+
});
|
|
57
101
|
});
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
exports[`Link > snapshot: no props 1`] = `
|
|
4
4
|
<a
|
|
5
|
-
class="ucl-uikit-base-link ucl-uikit-standalone-link css-
|
|
5
|
+
class="ucl-uikit-base-link ucl-uikit-standalone-link css-1x772n"
|
|
6
6
|
data-testid="ucl-uikit-standalone-link"
|
|
7
7
|
>
|
|
8
8
|
test
|
|
@@ -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-uikit-base-link ucl-uikit-standalone-link css-
|
|
14
|
+
class="ucl-uikit-base-link ucl-uikit-standalone-link css-1x772n"
|
|
15
15
|
data-testid="test123"
|
|
16
16
|
>
|
|
17
17
|
testidlink
|