Skip to content

Commit 1c44652

Browse files
committed
Add support for custom link component
1 parent aff299e commit 1c44652

File tree

12 files changed

+1835
-1409
lines changed

12 files changed

+1835
-1409
lines changed

.eslintignore

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
# Don't ever lint node_modules
2+
node_modules
3+
# Don't lint build output
4+
dist
5+
# Don't lint nyc coverage output
6+
coverage
7+
.eslintrc.js

.eslintrc.js

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,15 @@
11
module.exports = {
22
env: {
33
browser: true,
4+
commonjs: true,
45
es6: true,
6+
jest: true,
7+
node: true,
58
},
69
extends: [
10+
'eslint:recommended',
711
'plugin:@typescript-eslint/recommended',
12+
'plugin:@typescript-eslint/recommended-requiring-type-checking',
813
'plugin:react/recommended',
914
'plugin:react-hooks/recommended',
1015
'prettier',
@@ -16,17 +21,17 @@ module.exports = {
1621
Atomics: 'readonly',
1722
SharedArrayBuffer: 'readonly',
1823
},
24+
root: true,
1925
parser: '@typescript-eslint/parser',
2026
parserOptions: {
27+
tsconfigRootDir: __dirname,
28+
project: ['./tsconfig.json'],
2129
ecmaFeatures: {
2230
jsx: true,
2331
},
24-
ecmaVersion: 2018,
32+
ecmaVersion: 2020,
2533
sourceType: 'module',
2634
},
27-
rules: {
28-
'@typescript-eslint/explicit-function-return-type': 'off',
29-
},
3035
settings: {
3136
react: {
3237
version: 'detect',

README.md

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,3 +47,28 @@ const SomeComponent = () => {
4747
)
4848
}
4949
```
50+
51+
### Custom link component
52+
53+
You can set custom link component that is rendered instead of default anchor element.
54+
55+
```jsx static
56+
import React from 'react'
57+
import { Anchorme, LinkComponentProps } from 'react-anchorme'
58+
59+
const SomeComponent = () => {
60+
const CustomLink = (props: LinkComponentProps) => {
61+
return (
62+
<i>
63+
<a {...props} />
64+
</i>
65+
)
66+
}
67+
68+
return (
69+
<Anchorme linkComponent={CustomLink} target="_blank" rel="noreferrer noopener">
70+
Lorem ipsum http://example.loc dolor sit amet
71+
</Anchorme>
72+
)
73+
}
74+
```

package.json

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -34,31 +34,31 @@
3434
},
3535
"devDependencies": {
3636
"@testing-library/jest-dom": "^5.5.0",
37-
"@testing-library/react": "^10.0.3",
38-
"@types/jest": "^25.2.1",
37+
"@testing-library/react": "^11.0.2",
38+
"@types/jest": "^26.0.13",
3939
"@types/react": "^16.9.34",
4040
"@types/react-dom": "^16.9.7",
41-
"@typescript-eslint/eslint-plugin": "^2.30.0",
42-
"@typescript-eslint/parser": "^2.30.0",
41+
"@typescript-eslint/eslint-plugin": "^4.0.1",
42+
"@typescript-eslint/parser": "^4.0.1",
4343
"eslint": "^7.0.0",
4444
"eslint-config-prettier": "^6.11.0",
4545
"eslint-plugin-import": "^2.20.2",
4646
"eslint-plugin-prettier": "^3.1.3",
4747
"eslint-plugin-react": "^7.19.0",
4848
"eslint-plugin-react-hooks": "^4.0.0",
4949
"husky": "^4.2.5",
50-
"jest": "^25.5.1",
50+
"jest": "^26.4.2",
5151
"lint-staged": "^10.2.0",
5252
"np": "^6.2.3",
5353
"prettier": "^2.0.5",
5454
"react": "^16.13.1",
5555
"react-dom": "^16.13.1",
5656
"rollup": "^2.7.5",
5757
"rollup-plugin-sizes": "^1.0.2",
58-
"rollup-plugin-terser": "^5.3.0",
58+
"rollup-plugin-terser": "^7.0.2",
5959
"rollup-plugin-typescript2": "^0.27.0",
60-
"ts-jest": "^25.4.0",
61-
"typescript": "^3.8.3"
60+
"ts-jest": "^26.3.0",
61+
"typescript": "^4.0.2"
6262
},
6363
"husky": {
6464
"hooks": {

src/Anchorme.test.tsx

Lines changed: 87 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ import { render } from '@testing-library/react'
33
import '@testing-library/jest-dom/extend-expect'
44

55
import Anchorme from './Anchorme'
6+
import { LinkComponentProps } from './types'
67

78
describe('Anchorme', () => {
89
const URL = 'http://www.example.loc'
@@ -62,4 +63,90 @@ describe('Anchorme', () => {
6263
expect(linkEl?.rel).toBe('noreferrer noopener')
6364
expect(getByText(URL)).toBeInTheDocument()
6465
})
66+
67+
it('should render with custom link component', () => {
68+
const CustomLink = (props: LinkComponentProps) => {
69+
return (
70+
<i>
71+
<a {...props} />
72+
</i>
73+
)
74+
}
75+
76+
const { container, getByText } = render(
77+
<Anchorme
78+
linkComponent={CustomLink}
79+
target="_blank"
80+
rel="noreferrer noopener"
81+
>
82+
{URL}
83+
</Anchorme>,
84+
)
85+
86+
const linkEl = container.querySelector('a')
87+
expect(linkEl).not.toBeNull()
88+
expect(linkEl?.href).toBe(`${URL}/`)
89+
expect(linkEl?.target).toBe('_blank')
90+
expect(linkEl?.rel).toBe('noreferrer noopener')
91+
expect(getByText(URL)).toBeInTheDocument()
92+
})
93+
94+
it('should render with custom component', () => {
95+
const customCallback = jest.fn<void, [string]>()
96+
97+
const CustomLink = ({ href, children }: LinkComponentProps) => {
98+
return (
99+
<strong>
100+
<span onClick={() => customCallback(href)}>{children}</span>
101+
</strong>
102+
)
103+
}
104+
105+
const { container, getByText } = render(
106+
<Anchorme linkComponent={CustomLink}>{URL}</Anchorme>,
107+
)
108+
109+
const linkEl = container.querySelector('span')
110+
expect(linkEl).not.toBeNull()
111+
expect(getByText(URL)).toBeInTheDocument()
112+
113+
linkEl?.click()
114+
expect(customCallback).toHaveBeenCalledTimes(1)
115+
expect(customCallback).toHaveBeenCalledWith(URL)
116+
})
117+
118+
it('should render with custom inline component', () => {
119+
const customCallback = jest.fn<void, [string]>()
120+
121+
const { container, getByText } = render(
122+
<Anchorme
123+
linkComponent={({ href, children }) => (
124+
<a
125+
href={href}
126+
onClick={(event) => {
127+
event.preventDefault()
128+
customCallback('link click')
129+
}}
130+
target="_blank"
131+
rel="noreferrer noopener"
132+
>
133+
{children}
134+
</a>
135+
)}
136+
>
137+
{URL}
138+
</Anchorme>,
139+
)
140+
141+
const linkEl = container.querySelector('a')
142+
expect(linkEl).not.toBeNull()
143+
expect(linkEl?.href).toBe(`${URL}/`)
144+
expect(linkEl?.target).toBe('_blank')
145+
expect(linkEl?.rel).toBe('noreferrer noopener')
146+
expect(getByText(URL)).toBeInTheDocument()
147+
148+
linkEl?.click()
149+
expect(customCallback).toHaveBeenCalledTimes(1)
150+
expect(customCallback).toHaveBeenCalledWith('link click')
151+
})
65152
})

src/Anchorme.tsx

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,12 @@
11
import React, { useCallback, useMemo } from 'react'
22
import anchorme from 'anchorme'
33

4-
import { AnchorProps } from './types'
4+
import { AnchorProps, LinkComponent } from './types'
55
import { Link } from './Link'
66

77
type Props = {
88
children: string
9+
linkComponent?: LinkComponent
910
} & AnchorProps
1011

1112
const Anchorme = ({ children, ...rest }: Props) => {

src/Link.tsx

Lines changed: 8 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,19 @@
11
import React from 'react'
22

3-
import { AnchorProps } from './types'
3+
import { LinkComponent, LinkComponentProps } from './types'
44
import { getProtocol } from './utils'
55

66
type Props = {
7-
href: string
8-
} & AnchorProps
7+
linkComponent?: LinkComponent
8+
} & LinkComponentProps
99

10-
export const Link = ({ href, ...rest }: Props) => {
10+
export const Link = ({ href, linkComponent, ...rest }: Props): JSX.Element => {
11+
const Component = linkComponent ?? 'a'
1112
const protocol = getProtocol(href)
13+
1214
return (
13-
<a {...rest} href={`${protocol}${href}`}>
15+
<Component {...rest} href={`${protocol}${href}`}>
1416
{href}
15-
</a>
17+
</Component>
1618
)
1719
}

src/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1 +1,2 @@
11
export { default as Anchorme } from './Anchorme'
2+
export { LinkComponent, LinkComponentProps } from './types'

src/types.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,9 @@
11
import React from 'react'
22

33
export type AnchorProps = Omit<React.HTMLProps<HTMLAnchorElement>, 'href'>
4+
5+
export type LinkComponentProps = {
6+
href: string
7+
} & AnchorProps
8+
9+
export type LinkComponent = React.ElementType<LinkComponentProps>

src/utils.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ const protocolRegex = /^((file:\/\/\/)|(https?:|ftps?:)\/\/|(mailto:))/i
44

55
const hasProtocol = (input: string) => protocolRegex.test(input)
66

7-
export const getProtocol = (input: string) => {
7+
export const getProtocol = (input: string): string => {
88
if (hasProtocol(input)) return ''
99

1010
return anchorme.validate.email(input) ? 'mailto:' : 'http://'

0 commit comments

Comments
 (0)