;
-export const Text = () => Obfuscate this text;
+export const Text: Story = {
+ args: {
+ children: 'This text is obfuscated',
+ },
+};
+
+export const Email: Story = {
+ args: {
+ email: true,
+ children: 'hello@example.com',
+ },
+};
-export const Email = () => hello@example.com;
+export const Tel: Story = {
+ args: {
+ tel: true,
+ children: '+01 012 12345678',
+ },
+};
-export const Tel = () => +01 012 12345678;
+export const Sms: Story = {
+ args: {
+ sms: true,
+ children: '+01 012 12345678',
+ },
+};
-export const Sms = () => +01 012 12345678;
+export const Href: Story = {
+ args: {
+ href: 'https://example.com',
+ target: '_blank',
+ rel: 'noopener noreferrer',
+ },
+};
-export const Href = () => ;
+export const CustomChildren: Story = {
+ render: () => (
+
+ email
+ telephone
+ sms
+
+ href
+
+
+ ),
+};
-export const CustomChildren = () => (
-
- Email
- Telephone
- SMS
-
- Href
-
-
-);
+const Anchor = forwardRef>(function Anchor(
+ { style, ...rest },
+ ref,
+) {
+ // eslint-disable-next-line jsx-a11y/anchor-has-content
+ return ;
+});
-export const CustomComponent = () => (
-
- as `span` component
-
- as custom `Anchor` component
-
-
-);
+export const CustomElements: Story = {
+ render: () => (
+
+ as `span` component
+
+ as custom `Anchor` component
+
+
+ ),
+};
diff --git a/src/Obfuscate.tsx b/src/Obfuscate.tsx
index 2d0936b..b8e1bf5 100644
--- a/src/Obfuscate.tsx
+++ b/src/Obfuscate.tsx
@@ -1,18 +1,24 @@
-/* eslint-env browser */
-
-import * as React from 'react';
-import styled from 'styled-components';
+import {
+ AllHTMLAttributes,
+ CSSProperties,
+ ElementType,
+ FocusEventHandler,
+ MouseEventHandler,
+ createElement,
+ forwardRef,
+ useCallback,
+ useMemo,
+ useState,
+} from 'react';
import { createUri, isString, noop, reverse } from './util';
-const Component = styled('a')``;
-
-export interface ObfuscateProps extends React.AnchorHTMLAttributes {
+export interface ObfuscateBaseProps {
/**
- * Styled components's `as` polymorphic prop.
+ * Polymorphic `as` component. Use this to change the Obfuscate element.
*
- * View the [`styled-components` docs](https://styled-components.com/docs/api#as-polymorphic-prop) for more information.
+ * @default 'a'
*/
- as?: keyof JSX.IntrinsicElements | React.ComponentType;
+ as?: ElementType;
/**
* When set as a `boolean`, the component will treat the `children` as an email address.
* Alternatively when set as a `string` the prop will be used to create the href and use the `children` for display.
@@ -33,50 +39,62 @@ export interface ObfuscateProps extends React.AnchorHTMLAttributes, keyof ObfuscateBaseProps> {}
+
/**
* A React component to obfuscate your contact links and text on your website.
*
- * ```
+ * ```tsx
* // use the component's children to create the link and display
* hello@example.com
*
* // or specify the email address for the link with custom children
- * Email me!
+ * Send email
* ```
*
* @see https://github.com/South-Paw/react-obfuscate-ts
*/
-const Obfuscate: React.FC = ({
- email,
- tel,
- sms,
- href,
- children,
- 'aria-label': ariaLabel = 'focus to reveal obfuscated content',
- obfuscateText = 'obfuscated',
- style,
- onClick = noop,
- onFocus = noop,
- onMouseOver = noop,
- onContextMenu = noop,
- ...other
-}) => {
- const [humanInteraction, setHumanInteraction] = React.useState(false);
+export const Obfuscate = forwardRef(function Obfuscate(
+ {
+ as = 'a',
+ email,
+ tel,
+ sms,
+ href,
+ children,
+ 'aria-label': ariaLabel = 'focus to reveal obfuscated content',
+ obfuscateText = 'obfuscated',
+ style,
+ onClick = noop,
+ onFocus = noop,
+ onMouseOver = noop,
+ onContextMenu = noop,
+ ...rest
+ },
+ ref,
+) {
+ const [humanInteraction, setHumanInteraction] = useState(false);
- const componentStyle = React.useMemo(
+ const componentStyle = useMemo(
() => ({
...style,
unicodeBidi: 'bidi-override',
@@ -85,7 +103,7 @@ const Obfuscate: React.FC = ({
[style, humanInteraction],
);
- const content = React.useMemo(() => {
+ const content = useMemo(() => {
if (children) return children;
if (isString(email)) return email;
if (isString(tel)) return tel;
@@ -94,10 +112,10 @@ const Obfuscate: React.FC = ({
return '';
}, [children, email, tel, sms, href]);
- const uri = React.useMemo(() => createUri({ email, tel, sms, href, children }), [email, tel, sms, href, children]);
- const hrefUri = React.useMemo(() => (humanInteraction ? uri : obfuscateText), [humanInteraction, uri, obfuscateText]);
+ const uri = useMemo(() => createUri({ email, tel, sms, href, children }), [email, tel, sms, href, children]);
+ const hrefUri = useMemo(() => (humanInteraction ? uri : obfuscateText), [humanInteraction, uri, obfuscateText]);
- const handleOnClick = React.useCallback<(e: React.MouseEvent) => void>(
+ const handleOnClick = useCallback>(
(e) => {
onClick(e);
@@ -108,7 +126,7 @@ const Obfuscate: React.FC = ({
[humanInteraction, uri, onClick],
);
- const handleOnFocus = React.useCallback<(e: React.FocusEvent) => void>(
+ const handleOnFocus = useCallback>(
(e) => {
onFocus(e);
setHumanInteraction(true);
@@ -116,7 +134,7 @@ const Obfuscate: React.FC = ({
[onFocus],
);
- const handleOnMouseOver = React.useCallback<(e: React.MouseEvent) => void>(
+ const handleOnMouseOver = useCallback>(
(e) => {
onMouseOver(e);
setHumanInteraction(true);
@@ -124,7 +142,7 @@ const Obfuscate: React.FC = ({
[onMouseOver],
);
- const handleOnContextMenu = React.useCallback<(e: React.MouseEvent) => void>(
+ const handleOnContextMenu = useCallback>(
(e) => {
onContextMenu(e);
setHumanInteraction(true);
@@ -132,22 +150,20 @@ const Obfuscate: React.FC = ({
[onContextMenu],
);
- return (
-
- {humanInteraction ? content : reverse(content)}
-
- );
-};
-
-Obfuscate.displayName = 'Obfuscate';
+ return createElement(
+ as,
+ {
+ style: componentStyle,
+ href: uri ? hrefUri : undefined,
+ 'aria-label': humanInteraction ? undefined : ariaLabel,
+ onClick: handleOnClick,
+ onFocus: handleOnFocus,
+ onMouseOver: handleOnMouseOver,
+ onContextMenu: handleOnContextMenu,
+ ...rest,
-export { Obfuscate };
+ ref,
+ },
+ humanInteraction ? content : reverse(content),
+ );
+});
diff --git a/src/__test__/Obfuscate.test.tsx b/src/__test__/Obfuscate.test.tsx
index a29c478..3421434 100644
--- a/src/__test__/Obfuscate.test.tsx
+++ b/src/__test__/Obfuscate.test.tsx
@@ -1,6 +1,5 @@
import '@testing-library/jest-dom/extend-expect';
import { fireEvent, render } from '@testing-library/react';
-import * as React from 'react';
import { Obfuscate } from '../Obfuscate';
describe('Obfuscate', () => {
diff --git a/tsconfig.json b/tsconfig.json
index 816d092..3a14be9 100644
--- a/tsconfig.json
+++ b/tsconfig.json
@@ -1,19 +1,18 @@
{
- "include": ["src", "types"],
+ "include": ["src", "tsup.config.ts"],
+ "exclude": ["node_modules"],
"compilerOptions": {
- "module": "esnext",
- "lib": ["dom", "esnext"],
- "importHelpers": true,
- "declaration": true,
- "sourceMap": true,
- "rootDir": "./src",
- "strict": true,
+ "allowJs": true,
+ "esModuleInterop": true,
+ "forceConsistentCasingInFileNames": true,
+ "isolatedModules": true,
+ "jsx": "react-jsx",
+ "moduleResolution": "node",
+ "noFallthroughCasesInSwitch": true,
+ "noImplicitReturns": true,
"noUnusedLocals": true,
"noUnusedParameters": true,
- "noImplicitReturns": true,
- "noFallthroughCasesInSwitch": true,
- "moduleResolution": "node",
- "jsx": "react",
- "esModuleInterop": true
+ "skipLibCheck": true,
+ "strict": true
}
}
diff --git a/tsup.config.ts b/tsup.config.ts
new file mode 100644
index 0000000..46b099a
--- /dev/null
+++ b/tsup.config.ts
@@ -0,0 +1,12 @@
+import { defineConfig } from 'tsup';
+
+export default defineConfig({
+ clean: true,
+ dts: true,
+ entry: ['src/index.ts'],
+ format: ['cjs', 'esm'],
+ metafile: true,
+ outExtension: (ctx) => ({ js: `.${ctx.format}.js` }),
+ sourcemap: true,
+ splitting: false,
+});