feat(components): link component in progress, plop fixed

This commit is contained in:
Junior Garcia 2022-10-02 18:33:23 -03:00
parent b6faafd665
commit 3285cee9ba
21 changed files with 420 additions and 26 deletions

View File

@ -15,7 +15,7 @@
"pnpm": ">=1.12.x"
},
"scripts": {
"clean:pn-css-type": "rimraf node_modules/.pnpm/csstype*",
"clean:pn-types": "rimraf node_modules/.pnpm/csstype*",
"dev:docs": "turbo run dev --scope=@nextui-org/docs --no-deps",
"start:docs": "turbo run start --scope=@nextui-org/docs --no-deps",
"build:docs": "turbo run build --scope=@nextui-org/docs",
@ -43,11 +43,13 @@
"release": "changeset publish",
"version:dev": "changeset version --snapshot dev",
"release:dev": "changeset publish --tag dev",
"postinstall": "pnpm clean:pn-css-type"
"postinstall": "pnpm clean:pn-types"
},
"dependencies": {
"@babel/cli": "^7.14.5",
"@babel/core": "^7.16.7",
"@react-types/link": "^3.3.3",
"@react-types/shared": "^3.14.1",
"@babel/plugin-proposal-object-rest-spread": "^7.15.6",
"@babel/plugin-transform-runtime": "^7.14.5",
"@babel/preset-env": "^7.14.5",

View File

@ -0,0 +1,24 @@
# @nextui-org/link
A Quick description of the component
> This is an internal utility, not intended for public usage.
## Installation
```sh
yarn add @nextui-org/link
# or
npm i @nextui-org/link
```
## Contribution
Yes please! See the
[contributing guidelines](https://github.com/nextui-org/nextui/blob/master/CONTRIBUTING.md)
for details.
## Licence
This project is licensed under the terms of the
[MIT license](https://github.com/nextui-org/nextui/blob/master/LICENSE).

View File

@ -0,0 +1,19 @@
import * as React from "react";
import {render} from "@testing-library/react";
import {Link} from "../src";
describe("Link", () => {
it("should render correctly", () => {
const wrapper = render(<Link />);
expect(() => wrapper.unmount()).not.toThrow();
});
it("ref should be forwarded", () => {
const ref = React.createRef<HTMLDivElement>();
render(<Link ref={ref} />);
expect(ref.current).not.toBeNull();
});
});

View File

@ -0,0 +1,3 @@
{ "replace": { "main": "dist/index.cjs.js", "module": "dist/index.esm.js",
"types": "dist/index.d.ts", "exports": { ".": { "import": "./dist/index.esm.js",
"require": "./dist/index.cjs.js" }, "./package.json": "./package.json" } } }

View File

@ -0,0 +1,53 @@
{
"name": "@nextui-org/link",
"version": "1.0.0-beta.11",
"description": "Links allow users to click their way from page to page. This component is styled to resemble a hyperlink and semantically renders an &lt;a&gt;",
"keywords": [
"link"
],
"author": "Junior Garcia <jrgarciadev@gmail.com>",
"homepage": "https://nextui.org",
"license": "MIT",
"main": "src/index.ts",
"sideEffects": false,
"files": [
"dist"
],
"publishConfig": {
"access": "public"
},
"repository": {
"type": "git",
"url": "git+https://github.com/nextui-org/nextui.git",
"directory": "packages/components/link"
},
"bugs": {
"url": "https://github.com/nextui-org/nextui/issues"
},
"scripts": {
"build": "tsup src/index.ts --format=esm,cjs --dts",
"dev": "yarn build:fast -- --watch",
"clean": "rimraf dist .turbo",
"typecheck": "tsc --noEmit",
"build:fast": "tsup src/index.ts --format=esm,cjs",
"prepack": "clean-package",
"postpack": "clean-package restore"
},
"peerDependencies": {
"react": ">=16.8.0",
"react-aria": ">=3.18.0"
},
"dependencies": {
"@nextui-org/dom-utils": "workspace:*",
"@nextui-org/shared-css": "workspace:*",
"@nextui-org/shared-utils": "workspace:*",
"@nextui-org/system": "workspace:*"
},
"devDependencies": {
"@react-types/link": "^3.3.3",
"@nextui-org/react": "workspace:*",
"clean-package": "2.1.1",
"react": "^17.0.2",
"react-aria": "3.18.0"
}
}

View File

@ -0,0 +1,5 @@
// export types
export type { LinkProps } from "./link";
// export component
export { default as Link } from './link';

View File

@ -0,0 +1,24 @@
import {StyledLinkIcon} from "./link.styles";
export const LinkIcon: React.FC<{}> = () => {
return (
<StyledLinkIcon
className="nextui-link-icon"
fill="none"
height="1em"
shapeRendering="geometricPrecision"
stroke="currentColor"
strokeLinecap="round"
strokeLinejoin="round"
strokeWidth="1.5"
viewBox="0 0 24 24"
width="1em"
>
<path d="M18 13v6a2 2 0 01-2 2H5a2 2 0 01-2-2V8a2 2 0 012-2h6" />
<path d="M15 3h6v6" />
<path d="M10 14L21 3" />
</StyledLinkIcon>
);
};
LinkIcon.toString = () => ".nextui-link-icon";

View File

@ -0,0 +1,11 @@
import {styled} from "@nextui-org/system";
import {cssFocusVisible} from "@nextui-org/shared-css";
export const StyledLink = styled("a", {}, cssFocusVisible);
export const StyledLinkIcon = styled("svg", {
ml: "$1",
as: "center",
display: "flex",
color: "currentColor",
});

View File

@ -0,0 +1,53 @@
import {HTMLAttributes} from "react";
import {useLink as useAriaLink} from "react-aria";
import {forwardRef} from "@nextui-org/system";
import {useDOMRef} from "@nextui-org/dom-utils";
import {clsx, __DEV__} from "@nextui-org/shared-utils";
import {mergeProps} from "react-aria";
import {StyledLink} from "./link.styles";
import {UseLinkProps, useLink} from "./use-link";
import {LinkIcon} from "./link-icon";
export interface LinkProps extends UseLinkProps {}
interface ILinkAria {
/** Props for the link element. */
linkProps: Omit<HTMLAttributes<HTMLElement>, keyof UseLinkProps>;
/** Whether the link is currently pressed. */
isPressed: boolean;
}
const Link = forwardRef<LinkProps, "div">((props, ref) => {
const {children, as, css, linkCss, isExternal, focusProps, className, ...otherProps} =
useLink(props);
const domRef = useDOMRef(ref);
const {linkProps}: ILinkAria = useAriaLink({...otherProps, elementType: `${as}`}, domRef);
return (
<StyledLink
ref={domRef}
className={clsx("nextui-link", className)}
css={{
...linkCss,
...css,
}}
{...mergeProps(linkProps, focusProps, otherProps)}
>
<>
{children}
{isExternal && <LinkIcon />}
</>
</StyledLink>
);
});
if (__DEV__) {
Link.displayName = "NextUI.Link";
}
Link.toString = () => ".nextui-link";
export default Link;

View File

@ -0,0 +1,63 @@
import type {AriaLinkProps} from "@react-types/link";
import {useMemo} from "react";
import {useFocusRing} from "react-aria";
import {HTMLNextUIProps, CSS, getTokenValue} from "@nextui-org/system";
import {IFocusRingAria} from "@nextui-org/dom-utils";
import {isNormalColor} from "@nextui-org//shared-utils";
export interface UseLinkProps extends HTMLNextUIProps<"a", AriaLinkProps> {
/**
* The link's color.
* @default "$colors$link"
*/
color?: CSS["color"];
/**
* Whether the link should have a underline. text-decoration: underline.
* @default false
*/
underline?: boolean;
/**
* Whether the link should be displayed as a separate block.
*/
block?: boolean;
/**
* Whether the link opacity && box-shadow should be animated.
* @default false
*/
animated?: boolean;
/**
* Whether the link should show an icon.
* @default false
*/
isExternal?: boolean;
}
export function useLink(props: UseLinkProps) {
const {isExternal = false, color = "$link", block = false, autoFocus, ...otherProps} = props;
const {isFocusVisible, focusProps}: IFocusRingAria<UseLinkProps> = useFocusRing({autoFocus});
const linkCss = useMemo(() => {
let backgroundColor = isNormalColor(color as string)
? `${color}Light`
: getTokenValue("colors", color as string, 0.2);
if (block) {
return {
color,
padding: "$2 $4",
borderRadius: "$base",
"&:hover": {
backgroundColor,
},
};
}
return {color};
}, [color, block]);
return {linkCss, focusProps, isExternal, isFocusVisible, ...otherProps};
}
export type UseLinkReturn = ReturnType<typeof useLink>;

View File

@ -0,0 +1,90 @@
import React from "react";
import {Meta} from "@storybook/react";
import {Spacer, Grid, Text} from "@nextui-org/react";
import {Link} from "../src";
export default {
title: "Navigation/Link",
component: Link,
} as Meta;
const text = `"First solve the problem. Then, write the code." - Jon Johnson.`;
export const Default = () => <Link href="#">{text}</Link>;
export const Underline = () => (
<Link underline color="primary">
{text}
</Link>
);
export const Variants = () => (
<>
<Text>
<Link href="#">{text}</Link>
</Text>
<Text>
<Link color="secondary" href="#">
{text}
</Link>
</Text>
<Text>
<Link color="success" href="#">
{text}
</Link>
</Text>
<Text>
<Link color="error" href="#">
{text}
</Link>
</Text>
</>
);
export const isExternal = () => (
<>
<Link isExternal href="#">
{text}
</Link>
<Spacer y={0.5} />
<Link isExternal color="primary" href="#">
{text}
</Link>
</>
);
export const Block = () => (
<Grid.Container gap={1}>
<Grid xs={12}>
<Link block color="primary" href="#">
{text}
</Link>
</Grid>
<Grid xs={12}>
<Link block color="text" href="#">
{text}
</Link>
</Grid>
<Grid xs={12}>
<Link block color="secondary" href="#">
{text}
</Link>
</Grid>
<Grid xs={12}>
<Link block color="success" href="#">
{text}
</Link>
</Grid>
<Grid xs={12}>
<Link block color="warning" href="#">
{text}
</Link>
</Grid>
<Grid xs={12}>
<Link block color="error" href="#">
{text}
</Link>
</Grid>
</Grid.Container>
);

View File

@ -0,0 +1,10 @@
{
"extends": "../../../tsconfig.json",
"compilerOptions": {
"paths": {
"@stitches/react": ["../../../node_modules/@stitches/react"],
"@react-types/link": ["../../../node_modules/@react-types/link"],
}
},
"include": ["src", "index.ts"]
}

View File

@ -0,0 +1,13 @@
import {defineConfig} from "tsup";
import {findUpSync} from "find-up";
export default defineConfig({
clean: true,
minify: false,
treeshake: false,
format: ["cjs", "esm"],
outExtension(ctx) {
return {js: `.${ctx.format}.js`};
},
inject: process.env.JSX ? [findUpSync("react-shim.js")!] : undefined,
});

View File

@ -46,7 +46,9 @@
"@nextui-org/row": "workspace:*",
"@nextui-org/spacer": "workspace:*",
"@nextui-org/container": "workspace:*",
"@nextui-org/badge": "workspace:*"
"@nextui-org/badge": "workspace:*",
"@nextui-org/grid": "workspace:*",
"@nextui-org/text": "workspace:*"
},
"peerDependencies": {
"react": ">=16.8.0",

View File

@ -5,3 +5,5 @@ export * from "@nextui-org/container";
export * from "@nextui-org/spacer";
export * from "@nextui-org/avatar";
export * from "@nextui-org/badge";
export * from "@nextui-org/grid";
export * from "@nextui-org/text";

View File

@ -5,9 +5,12 @@ import defaultTheme from "./light-theme";
import {ThemeType, TokenValue, TokenKeyName} from "./types";
export const getTokenValue = (token: TokenKeyName, tokenName: string, alpha = 1) => {
if (typeof document === "undefined" || !token) return "";
if (typeof document === "undefined" || !token || !tokenName) return "";
const name = tokenName.includes("$") ? tokenName.replace("$", "") : tokenName;
let docStyle = getComputedStyle(document.documentElement);
const tokenKey = `--${commonTheme.prefix}-${token}-${tokenName}`;
const tokenKey = `--${commonTheme.prefix}-${token}-${name}`;
const tokenValue = docStyle.getPropertyValue(tokenKey);
if (tokenValue && tokenValue.includes("var")) {

View File

@ -34,7 +34,7 @@
"postpack": "clean-package restore"
},
"dependencies": {
"@react-types/shared": "3.14.0"
"@react-types/shared": "^3.14.1"
},
"peerDependencies": {
"react": ">=16.8.0",

View File

@ -57,7 +57,9 @@ export const hexToRGBA = (hex: string, alpha: number = 1): string => {
};
export const isNormalColor = (color: string): boolean => {
let found = normalColors.find((el) => el === color);
let c = color.includes("$") ? color.replace("$", "") : color;
let found = normalColors.find((el) => el === c);
return found !== undefined && found !== null;
};

View File

@ -13,7 +13,7 @@ describe("{{capitalize componentName}}", () => {
it("ref should be forwarded", () => {
const ref = React.createRef<HTMLDivElement>();
render(<{capitalize componentName}} ref={ref} />);
render(<{{capitalize componentName}} ref={ref} />);
expect(ref.current).not.toBeNull();
});
});

View File

@ -1,4 +1,3 @@
import * as React from "react";
import {HTMLNextUIProps} from "@nextui-org/system";
export interface Use{{capitalize componentName}}Props extends HTMLNextUIProps<"div"> {}

50
pnpm-lock.yaml generated
View File

@ -20,6 +20,8 @@ importers:
'@changesets/types': 5.1.0
'@docusaurus/utils': 2.0.0-beta.3
'@react-bootstrap/babel-preset': ^2.1.0
'@react-types/link': ^3.3.3
'@react-types/shared': ^3.14.1
'@storybook/addon-a11y': ^6.5.3
'@storybook/addon-actions': ^6.5.3
'@storybook/addon-essentials': ^6.5.3
@ -106,6 +108,8 @@ importers:
'@changesets/types': 5.1.0
'@docusaurus/utils': 2.0.0-beta.3_cxgt5hwiwgacztvwocsya4465q
'@react-bootstrap/babel-preset': 2.1.0
'@react-types/link': 3.3.3_react@17.0.2
'@react-types/shared': 3.14.1_react@17.0.2
'@storybook/addon-a11y': 6.5.12_sfoxds7t5ydpegc3knd667wn6m
'@storybook/addon-actions': 6.5.12_sfoxds7t5ydpegc3knd667wn6m
'@storybook/addon-essentials': 6.5.12_ekvsmtdrng24vr6exv7osytnem
@ -397,6 +401,29 @@ importers:
clean-package: 2.1.1
react: 17.0.2
packages/components/link:
specifiers:
'@nextui-org/dom-utils': workspace:*
'@nextui-org/react': workspace:*
'@nextui-org/shared-css': workspace:*
'@nextui-org/shared-utils': workspace:*
'@nextui-org/system': workspace:*
'@react-types/link': ^3.3.3
clean-package: 2.1.1
react: ^17.0.2
react-aria: 3.18.0
dependencies:
'@nextui-org/dom-utils': link:../../utilities/dom-utils
'@nextui-org/shared-css': link:../../utilities/shared-css
'@nextui-org/shared-utils': link:../../utilities/shared-utils
'@nextui-org/system': link:../../core/system
devDependencies:
'@nextui-org/react': link:../../core/react
'@react-types/link': 3.3.3_react@17.0.2
clean-package: 2.1.1
react: 17.0.2
react-aria: 3.18.0_react@17.0.2
packages/components/row:
specifiers:
'@nextui-org/dom-utils': workspace:*
@ -451,9 +478,11 @@ importers:
'@nextui-org/badge': workspace:*
'@nextui-org/col': workspace:*
'@nextui-org/container': workspace:*
'@nextui-org/grid': workspace:*
'@nextui-org/row': workspace:*
'@nextui-org/spacer': workspace:*
'@nextui-org/system': workspace:*
'@nextui-org/text': workspace:*
'@stitches/react': 1.2.8
clean-package: 2.1.1
react: 17.0.2
@ -465,9 +494,11 @@ importers:
'@nextui-org/badge': link:../../components/badge
'@nextui-org/col': link:../../components/col
'@nextui-org/container': link:../../components/container
'@nextui-org/grid': link:../../components/grid
'@nextui-org/row': link:../../components/row
'@nextui-org/spacer': link:../../components/spacer
'@nextui-org/system': link:../system
'@nextui-org/text': link:../../components/text
devDependencies:
'@stitches/react': 1.2.8_react@17.0.2
clean-package: 2.1.1
@ -502,12 +533,12 @@ importers:
packages/utilities/dom-utils:
specifiers:
'@react-types/shared': 3.14.0
'@react-types/shared': ^3.14.1
clean-package: 2.1.1
react: ^17.0.2
react-aria: 3.18.0
dependencies:
'@react-types/shared': 3.14.0_react@17.0.2
'@react-types/shared': 3.14.1_react@17.0.2
devDependencies:
clean-package: 2.1.1
react: 17.0.2
@ -4591,7 +4622,6 @@ packages:
'@react-aria/utils': 3.13.3_react@17.0.2
'@react-types/shared': 3.14.1_react@17.0.2
react: 17.0.2
dev: true
/@react-aria/label/3.4.1_react@17.0.2:
resolution: {integrity: sha512-sdXkCrMh3JfV8dw/S+ENOuATG39sFFyCcokhhRgshIlbqkjWU0Wa2RQ2fxr1hmDepai/5LNOPwWTTOqI+SfMMw==}
@ -4854,7 +4884,6 @@ packages:
dependencies:
'@babel/runtime': 7.19.0
react: 17.0.2
dev: true
/@react-aria/switch/3.2.3_react@17.0.2:
resolution: {integrity: sha512-+VMntitxI4j+ry51csibUJ5vKXVS6UcrB0anU7T2dZ/C19yXPQHOkRz7f+tEFaYrmAVnEaVT1TeGfEXEAt/NOg==}
@ -4965,7 +4994,6 @@ packages:
'@react-types/shared': 3.14.1_react@17.0.2
clsx: 1.2.1
react: 17.0.2
dev: true
/@react-aria/visually-hidden/3.4.1_react@17.0.2:
resolution: {integrity: sha512-dx7OSdnQvvR8awpxwiHHBdk0N3UGyGEBI17vogmO09685J+MRW8UuJuXRNl4Eg5FnWoFHxWRnmHmXin7fdGU+w==}
@ -5304,7 +5332,6 @@ packages:
dependencies:
'@babel/runtime': 7.19.0
react: 17.0.2
dev: true
/@react-stately/virtualizer/3.3.0_react@17.0.2:
resolution: {integrity: sha512-63HqPKIL1nhqJoE6UubZ+lb5AMU0ss/pgX38wuoDWHnU41hPG0cMXx0AkwNAHzkIepp4b931k3SfHOx5FiB4CA==}
@ -5411,7 +5438,6 @@ packages:
'@react-aria/interactions': 3.11.0_react@17.0.2
'@react-types/shared': 3.14.1_react@17.0.2
react: 17.0.2
dev: true
/@react-types/listbox/3.3.3_react@17.0.2:
resolution: {integrity: sha512-EhwUl1j2xv48LB8oLcw9uhDGbNY+K1sTrHVdkQpXJwtgV5Mp6WN1yWL3naR+GK23aWL1kD/yxkQU7Lg5a2EXBw==}
@ -5497,21 +5523,12 @@ packages:
react: 17.0.2
dev: true
/@react-types/shared/3.14.0_react@17.0.2:
resolution: {integrity: sha512-K069Bh/P0qV3zUG8kqabeO8beAUlFdyVPvpcNVPjRl+0Q9NDS9mfdQbmUa0LqdVo5e6jRPdos77Ylflkrz8wcw==}
peerDependencies:
react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0
dependencies:
react: 17.0.2
dev: false
/@react-types/shared/3.14.1_react@17.0.2:
resolution: {integrity: sha512-yPPgVRWWanXqbdxFTgJmVwx0JlcnEK3dqkKDIbVk6mxAHvEESI9+oDnHvO8IMHqF+GbrTCzVtAs0zwhYI/uHJA==}
peerDependencies:
react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0
dependencies:
react: 17.0.2
dev: true
/@react-types/slider/3.2.1_react@17.0.2:
resolution: {integrity: sha512-adqWZLE2IEzqBGnGHKYQwJ2IY4xlwFcPt3KWCsfp1c1WyG/d7xxQus8rL4eWLqoiMgguTxbYm9F2TF77itw8JA==}
@ -9785,7 +9802,6 @@ packages:
/clsx/1.2.1:
resolution: {integrity: sha512-EcR6r5a8bj6pu3ycsa/E/cKVGuTgZJZdsyUYHOksG/UHIiKfjxzRxYJpyVBwYaQeOvghal9fcc4PidlgzugAQg==}
engines: {node: '>=6'}
dev: true
/co/4.6.0:
resolution: {integrity: sha512-QVb0dM5HvG+uaxitm8wONl7jltx8dqhfU33DcqtOZcLSVIKSDDLDi7+0LbAKiyI8hD9u42m2YxXSkMGWThaecQ==}