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.
@@ -1,5 +1,6 @@
1
+ import { Children, isValidElement, ReactNode } from 'react';
1
2
  import { css, cx } from '@emotion/css';
2
- import Link, { LinkProps } from '../Link';
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<LinkProps, 'size'>;
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 = cx(
29
- NAME,
30
- css`
31
- color: ${theme.colour.link.primary};
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
- &:hover {
39
- text-decoration: underline;
40
- }
41
-
42
- &:active {
43
- color: ${theme.colour.link.primaryHover};
44
- }
45
- `,
46
- className
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
- <Link
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
- </Link>
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
- {icon &&
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-zj0pf"
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-zj0pf"
14
+ class="ucl-uikit-base-link ucl-uikit-standalone-link css-1x772n"
15
15
  data-testid="test123"
16
16
  >
17
17
  testidlink
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.29.1",
5
+ "version": "0.29.2",
6
6
  "type": "module",
7
7
  "main": "dist/index.js",
8
8
  "types": "dist/index.d.ts",