@kaizen/components 1.72.0 → 1.73.1
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/bin/codemod.sh +2 -0
- package/codemods/README.md +46 -0
- package/codemods/upgradeV1Buttons/index.ts +19 -0
- package/codemods/upgradeV1Buttons/transformV1ButtonAttributes.spec.ts +202 -0
- package/codemods/upgradeV1Buttons/transformV1ButtonAttributes.ts +146 -0
- package/codemods/upgradeV1Buttons/upgradeV1Buttons.spec.ts +658 -0
- package/codemods/upgradeV1Buttons/upgradeV1Buttons.ts +93 -0
- package/codemods/utils/createJsxElementWithChildren.spec.ts +119 -0
- package/codemods/utils/createJsxElementWithChildren.ts +55 -0
- package/codemods/utils/createProp.spec.ts +75 -19
- package/codemods/utils/createProp.ts +8 -1
- package/codemods/utils/getKaioTagName.ts +13 -5
- package/codemods/utils/index.ts +1 -0
- package/dist/cjs/Link/Link.cjs +45 -0
- package/dist/cjs/Link/Link.module.css.cjs +20 -0
- package/dist/cjs/Link/subcomponents/LinkContent.cjs +34 -0
- package/dist/cjs/index.cjs +2 -0
- package/dist/cjs/utilitiesV3.cjs +2 -0
- package/dist/esm/Link/Link.mjs +40 -0
- package/dist/esm/Link/Link.module.css.mjs +18 -0
- package/dist/esm/Link/subcomponents/LinkContent.mjs +26 -0
- package/dist/esm/index.mjs +1 -0
- package/dist/esm/utilitiesV3.mjs +1 -0
- package/dist/styles.css +120 -0
- package/dist/types/Link/Link.d.ts +39 -0
- package/dist/types/Link/index.d.ts +1 -0
- package/dist/types/Link/subcomponents/LinkContent.d.ts +8 -0
- package/dist/types/index.d.ts +1 -0
- package/package.json +3 -3
- package/src/Link/Link.module.css +119 -0
- package/src/Link/Link.tsx +90 -0
- package/src/Link/_docs/Link--api-specification.mdx +133 -0
- package/src/Link/_docs/Link--api-usage-guidelines.mdx +107 -0
- package/src/Link/_docs/Link.doc.stories.tsx +238 -0
- package/src/Link/_docs/Link.stickersheet.stories.tsx +191 -0
- package/src/Link/index.ts +1 -0
- package/src/Link/subcomponents/LinkContent.tsx +31 -0
- package/src/LinkButton/_docs/LinkButton--api-specification.mdx +1 -57
- package/src/__next__/Button/_docs/Button--migration-guide.mdx +81 -0
- package/src/index.ts +1 -0
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import { mergeClassNames } from '../../utils/mergeClassNames.mjs';
|
|
3
|
+
import styles from '../Link.module.css.mjs';
|
|
4
|
+
var LinkIcon = function (_a) {
|
|
5
|
+
var icon = _a.icon;
|
|
6
|
+
return /*#__PURE__*/React.createElement("span", {
|
|
7
|
+
className: styles.icon
|
|
8
|
+
}, icon);
|
|
9
|
+
};
|
|
10
|
+
var LinkContent = function (_a) {
|
|
11
|
+
var children = _a.children,
|
|
12
|
+
icon = _a.icon,
|
|
13
|
+
iconPosition = _a.iconPosition,
|
|
14
|
+
isUnderlined = _a.isUnderlined;
|
|
15
|
+
var iconPositionStyling = iconPosition === 'start' ? styles.iconStart : styles.iconEnd;
|
|
16
|
+
return /*#__PURE__*/React.createElement("span", {
|
|
17
|
+
className: mergeClassNames(styles.linkContent, isUnderlined && styles.isUnderlined)
|
|
18
|
+
}, icon && iconPosition === 'start' && /*#__PURE__*/React.createElement(LinkIcon, {
|
|
19
|
+
icon: icon
|
|
20
|
+
}), /*#__PURE__*/React.createElement("span", {
|
|
21
|
+
className: mergeClassNames(icon && iconPositionStyling)
|
|
22
|
+
}, children), icon && iconPosition === 'end' && /*#__PURE__*/React.createElement(LinkIcon, {
|
|
23
|
+
icon: icon
|
|
24
|
+
}));
|
|
25
|
+
};
|
|
26
|
+
export { LinkContent };
|
package/dist/esm/index.mjs
CHANGED
|
@@ -316,6 +316,7 @@ export { InputSearch } from './Input/InputSearch/InputSearch.mjs';
|
|
|
316
316
|
export { KaizenProvider } from './KaizenProvider/KaizenProvider.mjs';
|
|
317
317
|
export { Label } from './Label/Label.mjs';
|
|
318
318
|
export { LabelledMessage } from './LabelledMessage/LabelledMessage.mjs';
|
|
319
|
+
export { Link } from './Link/Link.mjs';
|
|
319
320
|
export { LikertScaleLegacy } from './LikertScaleLegacy/LikertScaleLegacy.mjs';
|
|
320
321
|
export { LinkButton } from './LinkButton/LinkButton.mjs';
|
|
321
322
|
export { LoadingGraphic } from './Loading/LoadingGraphic/LoadingGraphic.mjs';
|
package/dist/esm/utilitiesV3.mjs
CHANGED
|
@@ -316,6 +316,7 @@ export { InputSearch } from './Input/InputSearch/InputSearch.mjs';
|
|
|
316
316
|
export { KaizenProvider } from './KaizenProvider/KaizenProvider.mjs';
|
|
317
317
|
export { Label } from './Label/Label.mjs';
|
|
318
318
|
export { LabelledMessage } from './LabelledMessage/LabelledMessage.mjs';
|
|
319
|
+
export { Link } from './Link/Link.mjs';
|
|
319
320
|
export { LikertScaleLegacy } from './LikertScaleLegacy/LikertScaleLegacy.mjs';
|
|
320
321
|
export { LinkButton } from './LinkButton/LinkButton.mjs';
|
|
321
322
|
export { LoadingGraphic } from './Loading/LoadingGraphic/LoadingGraphic.mjs';
|
package/dist/styles.css
CHANGED
|
@@ -5849,6 +5849,126 @@ input[type=range].InputRange-module_ratingScaleRange__gI-rs::-ms-thumb:not(:disa
|
|
|
5849
5849
|
left: var(--spacing-md, 1.5rem);
|
|
5850
5850
|
}
|
|
5851
5851
|
}
|
|
5852
|
+
.Link-module_link__8oxip {
|
|
5853
|
+
color: var(--link-text-color, var(--color-blue-500));
|
|
5854
|
+
font-family: var(--typography-paragraph-body-font-family);
|
|
5855
|
+
font-size: var(--link-font-size, inherit);
|
|
5856
|
+
line-height: var(--link-line-height, inherit);
|
|
5857
|
+
font-weight: var(--typography-paragraph-body-font-weight);
|
|
5858
|
+
text-decoration: none;
|
|
5859
|
+
position: relative;
|
|
5860
|
+
white-space: nowrap;
|
|
5861
|
+
outline: 0;
|
|
5862
|
+
}
|
|
5863
|
+
|
|
5864
|
+
.Link-module_link__8oxip[data-focus-visible]::after {
|
|
5865
|
+
content: '';
|
|
5866
|
+
position: absolute;
|
|
5867
|
+
background: transparent;
|
|
5868
|
+
border-color: var(--color-blue-500);
|
|
5869
|
+
border-radius: 0;
|
|
5870
|
+
border-width: var(--border-focus-ring-border-width);
|
|
5871
|
+
border-style: var(--border-focus-ring-border-style);
|
|
5872
|
+
inset: calc(-1 * (var(--border-focus-ring-border-width) * 2) - 1px);
|
|
5873
|
+
}
|
|
5874
|
+
|
|
5875
|
+
.Link-module_isUnderlined__qvbtS {
|
|
5876
|
+
border-bottom: var(--spacing-1) solid var(--link-text-color, var(--color-blue-500));
|
|
5877
|
+
}
|
|
5878
|
+
|
|
5879
|
+
.Link-module_icon__CXh2J > * {
|
|
5880
|
+
vertical-align: text-bottom;
|
|
5881
|
+
font-size: var(--icon-font-size, 1em);
|
|
5882
|
+
}
|
|
5883
|
+
|
|
5884
|
+
.Link-module_iconStart__ZysGR {
|
|
5885
|
+
margin-inline-start: var(--text-icon-gap, 0.5em);
|
|
5886
|
+
}
|
|
5887
|
+
|
|
5888
|
+
.Link-module_iconEnd__-DTiD {
|
|
5889
|
+
margin-inline-end: var(--text-icon-gap, 0.5em);
|
|
5890
|
+
}
|
|
5891
|
+
|
|
5892
|
+
.Link-module_primary__bmhX0[data-hovered] {
|
|
5893
|
+
--link-text-color: var(--color-blue-600);
|
|
5894
|
+
}
|
|
5895
|
+
|
|
5896
|
+
.Link-module_primary__bmhX0[data-pressed] {
|
|
5897
|
+
--link-text-color: var(--color-blue-700);
|
|
5898
|
+
}
|
|
5899
|
+
|
|
5900
|
+
.Link-module_secondary__pyYIV {
|
|
5901
|
+
--link-text-color: var(--color-purple-800);
|
|
5902
|
+
}
|
|
5903
|
+
|
|
5904
|
+
.Link-module_secondary__pyYIV[data-hovered] {
|
|
5905
|
+
--link-text-color: var(--color-gray-600);
|
|
5906
|
+
}
|
|
5907
|
+
|
|
5908
|
+
.Link-module_secondary__pyYIV[data-pressed] {
|
|
5909
|
+
--link-text-color: var(--color-black);
|
|
5910
|
+
}
|
|
5911
|
+
|
|
5912
|
+
[class*='extra-small'] .Link-module_link__8oxip.Link-module_isInline__sznAm,
|
|
5913
|
+
.Link-module_extra-small__nScM0 {
|
|
5914
|
+
--link-font-size: var(--typography-paragraph-extra-small-font-size);
|
|
5915
|
+
--link-line-height: var(--typography-paragraph-extra-small-line-height);
|
|
5916
|
+
--icon-font-size: 0.875rem;
|
|
5917
|
+
--text-icon-gap: var(--spacing-2);
|
|
5918
|
+
}
|
|
5919
|
+
|
|
5920
|
+
[class*='body'] .Link-module_link__8oxip.Link-module_isInline__sznAm,
|
|
5921
|
+
.Link-module_body__H4Jgj {
|
|
5922
|
+
--link-font-size: var(--typography-paragraph-body-font-size);
|
|
5923
|
+
--link-line-height: var(--typography-paragraph-body-line-height);
|
|
5924
|
+
--icon-font-size: 1.25rem;
|
|
5925
|
+
--text-icon-gap: var(--spacing-6);
|
|
5926
|
+
}
|
|
5927
|
+
|
|
5928
|
+
[class*='intro-lede'] .Link-module_link__8oxip.Link-module_isInline__sznAm,
|
|
5929
|
+
.Link-module_intro-lede__38Y6J {
|
|
5930
|
+
--link-font-size: var(--typography-paragraph-intro-lede-font-size);
|
|
5931
|
+
--link-line-height: var(--typography-paragraph-intro-lede-line-height);
|
|
5932
|
+
--icon-font-size: 1.5rem;
|
|
5933
|
+
--text-icon-gap: var(--spacing-8);
|
|
5934
|
+
}
|
|
5935
|
+
|
|
5936
|
+
[class*='small']:not([class*='extra-small']) .Link-module_link__8oxip.Link-module_isInline__sznAm,
|
|
5937
|
+
.Link-module_small__4kJcR {
|
|
5938
|
+
--link-font-size: var(--typography-paragraph-small-font-size);
|
|
5939
|
+
--link-line-height: var(--typography-paragraph-small-line-height);
|
|
5940
|
+
--icon-font-size: 1rem;
|
|
5941
|
+
--text-icon-gap: var(--spacing-4);
|
|
5942
|
+
}
|
|
5943
|
+
|
|
5944
|
+
.Link-module_white__qQr6q {
|
|
5945
|
+
--link-text-color: var(--color-white);
|
|
5946
|
+
}
|
|
5947
|
+
|
|
5948
|
+
.Link-module_white__qQr6q[data-focus-visible]::after {
|
|
5949
|
+
border-color: var(--color-blue-300);
|
|
5950
|
+
}
|
|
5951
|
+
|
|
5952
|
+
.Link-module_white__qQr6q.Link-module_isDisabled__gvLNv {
|
|
5953
|
+
--link-text-color: rgba(var(--color-white-rgb), 0.2);
|
|
5954
|
+
}
|
|
5955
|
+
|
|
5956
|
+
.Link-module_reversed__IqX8B {
|
|
5957
|
+
--link-text-color: var(--color-white);
|
|
5958
|
+
}
|
|
5959
|
+
|
|
5960
|
+
.Link-module_reversed__IqX8B[data-focus-visible]::after {
|
|
5961
|
+
border-color: var(--color-blue-300);
|
|
5962
|
+
}
|
|
5963
|
+
|
|
5964
|
+
.Link-module_link__8oxip.Link-module_isDisabled__gvLNv {
|
|
5965
|
+
--link-text-color: var(--color-gray-400);
|
|
5966
|
+
}
|
|
5967
|
+
|
|
5968
|
+
.Link-module_reversed__IqX8B.Link-module_isDisabled__gvLNv {
|
|
5969
|
+
--link-text-color: rgba(var(--color-white-rgb), 0.2);
|
|
5970
|
+
}
|
|
5971
|
+
|
|
5852
5972
|
/** THIS IS AN AUTOGENERATED FILE **/
|
|
5853
5973
|
/** THIS IS AN AUTOGENERATED FILE **/
|
|
5854
5974
|
/** THIS IS AN AUTOGENERATED FILE **/
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import { type LinkProps as RACLinkProps } from 'react-aria-components';
|
|
3
|
+
import { type TextProps } from "../Text";
|
|
4
|
+
export type LinkProps = BaseLinkProps & ((UnderlinedLink | NonUnderlinedLink) & (InlineLink | NonInlineLink));
|
|
5
|
+
type BaseLinkProps = {
|
|
6
|
+
/** Controls the visual style of a link. @default 'primary' */
|
|
7
|
+
variant?: 'primary' | 'secondary' | 'white';
|
|
8
|
+
/** Controls the position of a link. @default 'end' */
|
|
9
|
+
iconPosition?: 'start' | 'end';
|
|
10
|
+
} & Omit<RACLinkProps, 'children'> & {
|
|
11
|
+
/** Used as the label for the Link. */
|
|
12
|
+
children: RACLinkProps['children'];
|
|
13
|
+
};
|
|
14
|
+
type UnderlinedLink = {
|
|
15
|
+
/** Toggles the underline of the icon and children @default true */
|
|
16
|
+
isUnderlined?: true;
|
|
17
|
+
/** The icon to be displayed, optional when link is underlined */
|
|
18
|
+
icon?: JSX.Element;
|
|
19
|
+
};
|
|
20
|
+
type NonUnderlinedLink = {
|
|
21
|
+
/** Toggles the underline of the icon and children */
|
|
22
|
+
isUnderlined?: false;
|
|
23
|
+
/** The icon to be displayed, required when link is not underlined */
|
|
24
|
+
icon: JSX.Element;
|
|
25
|
+
};
|
|
26
|
+
type InlineLink = {
|
|
27
|
+
/** isInline assumes the Link is wrapped in a [Text](https://cultureamp.design/?path=/docs/components-text--docs) component */
|
|
28
|
+
isInline: true;
|
|
29
|
+
/** The size of the link, not passed when isInline */
|
|
30
|
+
size?: never;
|
|
31
|
+
};
|
|
32
|
+
type NonInlineLink = {
|
|
33
|
+
/** isInline assumes the Link is wrapped in a [Text](https://cultureamp.design/?path=/docs/components-text--docs) component @default false */
|
|
34
|
+
isInline?: false;
|
|
35
|
+
/** The size of the link. Sizes correlate to body text sizes. @default 'body' */
|
|
36
|
+
size?: TextProps['variant'];
|
|
37
|
+
};
|
|
38
|
+
export declare const Link: React.ForwardRefExoticComponent<LinkProps & React.RefAttributes<HTMLAnchorElement>>;
|
|
39
|
+
export {};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from './Link';
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import { type ReactNode } from 'react';
|
|
2
|
+
export type LinkContentProps = {
|
|
3
|
+
children: ReactNode;
|
|
4
|
+
icon?: JSX.Element;
|
|
5
|
+
iconPosition?: 'start' | 'end';
|
|
6
|
+
isUnderlined: boolean;
|
|
7
|
+
};
|
|
8
|
+
export declare const LinkContent: ({ children, icon, iconPosition, isUnderlined, }: LinkContentProps) => JSX.Element;
|
package/dist/types/index.d.ts
CHANGED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@kaizen/components",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.73.1",
|
|
4
4
|
"description": "Kaizen component library",
|
|
5
5
|
"author": "Geoffrey Chong <geoff.chong@cultureamp.com>",
|
|
6
6
|
"homepage": "https://cultureamp.design",
|
|
@@ -185,8 +185,8 @@
|
|
|
185
185
|
"svgo": "^3.3.2",
|
|
186
186
|
"tslib": "^2.8.1",
|
|
187
187
|
"tsx": "^4.19.2",
|
|
188
|
-
"@kaizen/
|
|
189
|
-
"@kaizen/
|
|
188
|
+
"@kaizen/design-tokens": "10.8.7",
|
|
189
|
+
"@kaizen/package-bundler": "2.0.4"
|
|
190
190
|
},
|
|
191
191
|
"devDependenciesComments": {
|
|
192
192
|
"sass": "Prevent deprecation warnings introduced in 1.80 as we plan to move away from sass",
|
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
.link {
|
|
2
|
+
color: var(--link-text-color, var(--color-blue-500));
|
|
3
|
+
font-family: var(--typography-paragraph-body-font-family);
|
|
4
|
+
font-size: var(--link-font-size, inherit);
|
|
5
|
+
line-height: var(--link-line-height, inherit);
|
|
6
|
+
font-weight: var(--typography-paragraph-body-font-weight);
|
|
7
|
+
text-decoration: none;
|
|
8
|
+
position: relative;
|
|
9
|
+
white-space: nowrap;
|
|
10
|
+
outline: 0;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
.link[data-focus-visible]::after {
|
|
14
|
+
content: '';
|
|
15
|
+
position: absolute;
|
|
16
|
+
background: transparent;
|
|
17
|
+
border-color: var(--color-blue-500);
|
|
18
|
+
border-radius: 0;
|
|
19
|
+
border-width: var(--border-focus-ring-border-width);
|
|
20
|
+
border-style: var(--border-focus-ring-border-style);
|
|
21
|
+
inset: calc(-1 * (var(--border-focus-ring-border-width) * 2) - 1px);
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
.isUnderlined {
|
|
25
|
+
border-bottom: var(--spacing-1) solid var(--link-text-color, var(--color-blue-500));
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
.icon > * {
|
|
29
|
+
vertical-align: text-bottom;
|
|
30
|
+
font-size: var(--icon-font-size, 1em);
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
.iconStart {
|
|
34
|
+
margin-inline-start: var(--text-icon-gap, 0.5em);
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
.iconEnd {
|
|
38
|
+
margin-inline-end: var(--text-icon-gap, 0.5em);
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
.primary[data-hovered] {
|
|
42
|
+
--link-text-color: var(--color-blue-600);
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
.primary[data-pressed] {
|
|
46
|
+
--link-text-color: var(--color-blue-700);
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
.secondary {
|
|
50
|
+
--link-text-color: var(--color-purple-800);
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
.secondary[data-hovered] {
|
|
54
|
+
--link-text-color: var(--color-gray-600);
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
.secondary[data-pressed] {
|
|
58
|
+
--link-text-color: var(--color-black);
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
[class*='extra-small'] .link.isInline,
|
|
62
|
+
.extra-small {
|
|
63
|
+
--link-font-size: var(--typography-paragraph-extra-small-font-size);
|
|
64
|
+
--link-line-height: var(--typography-paragraph-extra-small-line-height);
|
|
65
|
+
--icon-font-size: 0.875rem;
|
|
66
|
+
--text-icon-gap: var(--spacing-2);
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
[class*='body'] .link.isInline,
|
|
70
|
+
.body {
|
|
71
|
+
--link-font-size: var(--typography-paragraph-body-font-size);
|
|
72
|
+
--link-line-height: var(--typography-paragraph-body-line-height);
|
|
73
|
+
--icon-font-size: 1.25rem;
|
|
74
|
+
--text-icon-gap: var(--spacing-6);
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
[class*='intro-lede'] .link.isInline,
|
|
78
|
+
.intro-lede {
|
|
79
|
+
--link-font-size: var(--typography-paragraph-intro-lede-font-size);
|
|
80
|
+
--link-line-height: var(--typography-paragraph-intro-lede-line-height);
|
|
81
|
+
--icon-font-size: 1.5rem;
|
|
82
|
+
--text-icon-gap: var(--spacing-8);
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
[class*='small']:not([class*='extra-small']) .link.isInline,
|
|
86
|
+
.small {
|
|
87
|
+
--link-font-size: var(--typography-paragraph-small-font-size);
|
|
88
|
+
--link-line-height: var(--typography-paragraph-small-line-height);
|
|
89
|
+
--icon-font-size: 1rem;
|
|
90
|
+
--text-icon-gap: var(--spacing-4);
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
.white {
|
|
94
|
+
--link-text-color: var(--color-white);
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
.white[data-focus-visible]::after {
|
|
98
|
+
border-color: var(--color-blue-300);
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
.white.isDisabled {
|
|
102
|
+
--link-text-color: rgba(var(--color-white-rgb), 0.2);
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
.reversed {
|
|
106
|
+
--link-text-color: var(--color-white);
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
.reversed[data-focus-visible]::after {
|
|
110
|
+
border-color: var(--color-blue-300);
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
.link.isDisabled {
|
|
114
|
+
--link-text-color: var(--color-gray-400);
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
.reversed.isDisabled {
|
|
118
|
+
--link-text-color: rgba(var(--color-white-rgb), 0.2);
|
|
119
|
+
}
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
import React, { forwardRef } from 'react'
|
|
2
|
+
import { Link as RACLink, type LinkProps as RACLinkProps } from 'react-aria-components'
|
|
3
|
+
import { type TextProps } from '~components/Text'
|
|
4
|
+
import { mergeClassNames } from '~components/utils/mergeClassNames'
|
|
5
|
+
import { LinkContent } from './subcomponents/LinkContent'
|
|
6
|
+
import styles from './Link.module.css'
|
|
7
|
+
|
|
8
|
+
export type LinkProps = BaseLinkProps &
|
|
9
|
+
((UnderlinedLink | NonUnderlinedLink) & (InlineLink | NonInlineLink))
|
|
10
|
+
|
|
11
|
+
type BaseLinkProps = {
|
|
12
|
+
/** Controls the visual style of a link. @default 'primary' */
|
|
13
|
+
variant?: 'primary' | 'secondary' | 'white'
|
|
14
|
+
/** Controls the position of a link. @default 'end' */
|
|
15
|
+
iconPosition?: 'start' | 'end'
|
|
16
|
+
} & Omit<RACLinkProps, 'children'> & {
|
|
17
|
+
/** Used as the label for the Link. */
|
|
18
|
+
children: RACLinkProps['children']
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
type UnderlinedLink = {
|
|
22
|
+
/** Toggles the underline of the icon and children @default true */
|
|
23
|
+
isUnderlined?: true
|
|
24
|
+
/** The icon to be displayed, optional when link is underlined */
|
|
25
|
+
icon?: JSX.Element
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
type NonUnderlinedLink = {
|
|
29
|
+
/** Toggles the underline of the icon and children */
|
|
30
|
+
isUnderlined?: false
|
|
31
|
+
/** The icon to be displayed, required when link is not underlined */
|
|
32
|
+
icon: JSX.Element
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
type InlineLink = {
|
|
36
|
+
/** isInline assumes the Link is wrapped in a [Text](https://cultureamp.design/?path=/docs/components-text--docs) component */
|
|
37
|
+
isInline: true
|
|
38
|
+
/** The size of the link, not passed when isInline */
|
|
39
|
+
size?: never
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
type NonInlineLink = {
|
|
43
|
+
/** isInline assumes the Link is wrapped in a [Text](https://cultureamp.design/?path=/docs/components-text--docs) component @default false */
|
|
44
|
+
isInline?: false
|
|
45
|
+
/** The size of the link. Sizes correlate to body text sizes. @default 'body' */
|
|
46
|
+
size?: TextProps['variant']
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
export const Link = forwardRef(
|
|
50
|
+
(
|
|
51
|
+
{
|
|
52
|
+
children,
|
|
53
|
+
variant = 'primary',
|
|
54
|
+
size = 'body',
|
|
55
|
+
icon,
|
|
56
|
+
iconPosition = 'end',
|
|
57
|
+
isInline = false,
|
|
58
|
+
isDisabled,
|
|
59
|
+
className,
|
|
60
|
+
isUnderlined = true,
|
|
61
|
+
...otherProps
|
|
62
|
+
}: LinkProps,
|
|
63
|
+
ref: React.ForwardedRef<HTMLAnchorElement>,
|
|
64
|
+
) => {
|
|
65
|
+
const childIsFunction = typeof children === 'function'
|
|
66
|
+
|
|
67
|
+
return (
|
|
68
|
+
<RACLink
|
|
69
|
+
ref={ref}
|
|
70
|
+
className={mergeClassNames(
|
|
71
|
+
styles.link,
|
|
72
|
+
isDisabled && styles.isDisabled,
|
|
73
|
+
isInline ? styles.isInline : styles[size],
|
|
74
|
+
styles[variant],
|
|
75
|
+
className,
|
|
76
|
+
)}
|
|
77
|
+
isDisabled={isDisabled}
|
|
78
|
+
{...otherProps}
|
|
79
|
+
>
|
|
80
|
+
{(racStateProps) => (
|
|
81
|
+
<LinkContent icon={icon} iconPosition={iconPosition} isUnderlined={isUnderlined}>
|
|
82
|
+
{childIsFunction ? children(racStateProps) : children}
|
|
83
|
+
</LinkContent>
|
|
84
|
+
)}
|
|
85
|
+
</RACLink>
|
|
86
|
+
)
|
|
87
|
+
},
|
|
88
|
+
)
|
|
89
|
+
|
|
90
|
+
Link.displayName = 'Link'
|
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
import { Canvas, Meta, Controls, ArgTypes, DocsStory } from '@storybook/blocks'
|
|
2
|
+
import { ResourceLinks, KAIOInstallation, LinkTo } from '~storybook/components'
|
|
3
|
+
import * as exampleStories from './Link.doc.stories'
|
|
4
|
+
|
|
5
|
+
<Meta title="Components/Link/API Specification" />
|
|
6
|
+
|
|
7
|
+
# Link API Specification
|
|
8
|
+
|
|
9
|
+
Update Jan 29, 2025
|
|
10
|
+
|
|
11
|
+
<ResourceLinks
|
|
12
|
+
sourceCode="blah blah update when available on main"
|
|
13
|
+
figma="https://www.figma.com/design/FWIOtGjpv9z0by95j1SgP0/Link?node-id=273-4107"
|
|
14
|
+
designGuidelines="/?path=/docs/components-link-usage-guidelines--docs"
|
|
15
|
+
/>
|
|
16
|
+
|
|
17
|
+
<KAIOInstallation exportNames={'Link'} />
|
|
18
|
+
|
|
19
|
+
## Overview
|
|
20
|
+
|
|
21
|
+
`Link` allows users to navigate to another page or resource. It is a wrapper around the native `a` element, with additional functionality and styling.
|
|
22
|
+
|
|
23
|
+
The following example and table showcases the essential props that enable the core functionality of `Link`. For the remaining suite of API options refer to [this section](#additional-api-options).
|
|
24
|
+
|
|
25
|
+
<Canvas of={exampleStories.Playground} />
|
|
26
|
+
|
|
27
|
+
<Controls
|
|
28
|
+
of={exampleStories.Playground}
|
|
29
|
+
include={[
|
|
30
|
+
'className',
|
|
31
|
+
'children',
|
|
32
|
+
'href',
|
|
33
|
+
'variant',
|
|
34
|
+
'size',
|
|
35
|
+
'icon',
|
|
36
|
+
'iconPosition',
|
|
37
|
+
'underlined',
|
|
38
|
+
'isInline',
|
|
39
|
+
'isUnderlined',
|
|
40
|
+
'isReversed',
|
|
41
|
+
'isDisabled',
|
|
42
|
+
]}
|
|
43
|
+
/>
|
|
44
|
+
|
|
45
|
+
## API
|
|
46
|
+
|
|
47
|
+
This is built on top of [React Aria's Link component](https://react-spectrum.adobe.com/react-aria/Link.html)
|
|
48
|
+
|
|
49
|
+
## Navigation and native anchor attributes
|
|
50
|
+
|
|
51
|
+
Out of the box, `Link` supports the majority of native `anchor` attributes and shares the same basic behaviour, i.e. `href` will trigger new page loads.
|
|
52
|
+
|
|
53
|
+
While client side routing is possible, the `Link` is agnostic to the routing technology chosen. Refer to our general set up guide to get started with [client side routing](#client-side-routing).
|
|
54
|
+
|
|
55
|
+
#### Opening new tabs and accessibility considerations
|
|
56
|
+
|
|
57
|
+
The general recommendation is to limit the number of links that open a new tab or window on a single page. While there are valid scenarios that can help avoid loss of data and or progress, as with links in forms, opening new tabs can be disorienting for users - especially for those who have difficulty perceiving visual content.
|
|
58
|
+
|
|
59
|
+
In order to provide advance warning to all users, it is recommended that links using `target="_blank"` be accompanied by a visual indicator and audible warning. As shown in the following example, additional context can be provided via a visually hidden element within the `children` of the component.
|
|
60
|
+
|
|
61
|
+
You may also consider using the `rel="noopener noreferrer"` attribute to prevent the new tab from accessing the `window.opener` object, which can be a security risk. Read more about this consideration on the [Lighthouse docs](https://developer.chrome.com/docs/lighthouse/best-practices/external-anchors-use-rel-noopener).
|
|
62
|
+
|
|
63
|
+
<Canvas of={exampleStories.LinkOpensInNewTab} />
|
|
64
|
+
|
|
65
|
+
For more context on this recommendation, we recommend taking a look at the [W3C page on the G200 success criteria](https://www.w3.org/TR/WCAG20-TECHS/G200.html).
|
|
66
|
+
|
|
67
|
+
### Variants
|
|
68
|
+
|
|
69
|
+
`Link` supports `primary`, `secondary` and `white` variants. If the `variant` prop is not specified, the default variant is `primary`.
|
|
70
|
+
|
|
71
|
+
<Canvas of={exampleStories.LinkVariants} />
|
|
72
|
+
|
|
73
|
+
<DocsStory of={exampleStories.LinkVariantWhite} expanded={false} />
|
|
74
|
+
|
|
75
|
+
### Sizes
|
|
76
|
+
|
|
77
|
+
Link supports the following sizes: `extra-small`, `small`, `body` and `intro-lede`. If the `size` prop is not specified, the default size is `body`.
|
|
78
|
+
|
|
79
|
+
<Canvas of={exampleStories.LinkSizes} />
|
|
80
|
+
|
|
81
|
+
### Links within Text
|
|
82
|
+
|
|
83
|
+
The `isInline` prop can be toggled to have the `Link` inherit the sizing from the parent text element. This is useful when the `Link` is nested within a paragraph or other text element.
|
|
84
|
+
|
|
85
|
+
### Link content and children
|
|
86
|
+
|
|
87
|
+
Labels and any `Link` content can be passed to the component via `children`. Where possible, ensure `Link` does not break over multiple lines. [See the section](/docs/components-link-usage-guidelines--docs) for standalone links for guidance. For icons as content, refer to the [next section](#icons-and-positioning).
|
|
88
|
+
|
|
89
|
+
```tsx
|
|
90
|
+
<Link href="#link">Label</Link>
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
While in most cases, `children` will be a `ReactNode`, `Link` also accepts a render function with React Aria's `LinkRenderProps`. This allows for more advanced styling and rendering options by hooking into React Aria's internal Link state. You can read more about this [here](https://react-spectrum.adobe.com/react-aria/Link.html#styling).
|
|
94
|
+
|
|
95
|
+
### Icons and positioning
|
|
96
|
+
|
|
97
|
+
[This is the intended icon](docs/components-icon-icon-future-api-specification--docs) component to be used with `Link`. The `icon` property abstracts the need to handle positioning and sizing of icons within the `Link` component. The `iconPosition` prop allows for the icon to be positioned to the left or right of the `children`.
|
|
98
|
+
|
|
99
|
+
### Icons and Underlines
|
|
100
|
+
|
|
101
|
+
The underline on the `Link` can be toggled using the `isUnderlined` prop and is `true` by default. If the `Link` is not underlined, the `icon` prop must be passed. The `icon` prop is optional if the link is underlined.
|
|
102
|
+
|
|
103
|
+
<Canvas of={exampleStories.LinkWithIconStart} />
|
|
104
|
+
|
|
105
|
+
Setting the `iconPosition` props will ensure content is flipped in `RTL` layouts. Note that icons will need the [shouldMirrorInRTL](/docs/illustrations-icon-icon-future-api-specification--docs#mirror-in-rtl) prop set to `true` when mirroring is required.
|
|
106
|
+
|
|
107
|
+
<Canvas of={exampleStories.LinkWithIconEnd} />
|
|
108
|
+
|
|
109
|
+
## Client side routing
|
|
110
|
+
|
|
111
|
+
Please refer to the [client side routing](/docs/guides-client-side-routing--docs) for more information on how to set up client side routing with the `Link` component.
|
|
112
|
+
|
|
113
|
+
## Additional API options
|
|
114
|
+
|
|
115
|
+
The following table is a collection of additional React Aria and native HTML props that are exposed from the [React Aria Link API](https://react-spectrum.adobe.com/react-aria/Link.html). These are not required for the implementation of `LinkButton` but can be used to extend its functionality. Refer back to the [overview section](#overview) for the core props that enable most use cases.
|
|
116
|
+
|
|
117
|
+
<ArgTypes
|
|
118
|
+
of={exampleStories.Playground}
|
|
119
|
+
exclude={[
|
|
120
|
+
'className',
|
|
121
|
+
'children',
|
|
122
|
+
'href',
|
|
123
|
+
'variant',
|
|
124
|
+
'size',
|
|
125
|
+
'icon',
|
|
126
|
+
'iconPosition',
|
|
127
|
+
'underlined',
|
|
128
|
+
'isInline',
|
|
129
|
+
'isUnderlined',
|
|
130
|
+
'isReversed',
|
|
131
|
+
'isDisabled',
|
|
132
|
+
]}
|
|
133
|
+
/>
|