feat(root): link docs updated, link code improved, fast-context removed

This commit is contained in:
Junior Garcia 2023-01-08 22:33:21 -03:00
parent 241b4bd1ab
commit 53b14819fd
13 changed files with 204 additions and 152 deletions

View File

@ -0,0 +1,43 @@
const App = `import { Link, Spacer, Badge } from "@nextui-org/react";
const CustomLink = () => {
return (
<svg
className="custom-link-icon"
width="1em"
height="1em"
fill="none"
viewBox="0 0 24 24"
shapeRendering="geometricPrecision"
stroke="currentColor"
stroke-width="2"
stroke-linecap="round"
stroke-linejoin="round"
>
<path d="M10 13a5 5 0 0 0 7.54.54l3-3a5 5 0 0 0-7.07-7.07l-1.72 1.71" />
<path d="M14 11a5 5 0 0 0-7.54-.54l-3 3a5 5 0 0 0 7.07 7.07l1.71-1.71" />
</svg>
)
}
export default function App() {
return (
<>
<Link href="#" isExternal color="success">
"First solve the problem. Then, write the code." - Jon Johnson.
</Link>
<Spacer />
<Link href="#" isExternal externalIcon={<CustomLink />} color="secondary">
"First solve the problem. Then, write the code." - Jon Johnson.
</Link>
</>
);
}`;
const react = {
"/App.js": App,
};
export default {
...react,
};

View File

@ -1,23 +0,0 @@
const App = `import { Link, Spacer } from "@nextui-org/react";
export default function App() {
return (
<>
<Link href="#" isExternal>
"First solve the problem. Then, write the code." - Jon Johnson.
</Link>
<Spacer />
<Link href="#" isExternal color="success">
"First solve the problem. Then, write the code." - Jon Johnson.
</Link>
</>
);
}`;
const react = {
"/App.js": App,
};
export default {
...react,
};

View File

@ -2,7 +2,7 @@ import default_link from "./default";
import color from "./color";
import variation from "./variation";
import block from "./block";
import icon from "./icon";
import external from "./external";
import nextLink from "./nextLink";
export default {
@ -10,6 +10,6 @@ export default {
color,
variation,
block,
icon,
external,
nextLink,
};

View File

@ -42,8 +42,8 @@ import { Link } from "@nextui-org/react";
<Playground
title="External Link"
desc="Show an icon in the `Link` with the `isExternal` prop."
files={linkContent.icon}
desc="Open link in new tab with the `isExternal` prop, you can also define custom `externalIcon` by yourself."
files={linkContent.external}
/>
<Playground
@ -70,6 +70,7 @@ import { Link } from "@nextui-org/react";
| **color** | `LinkColors` `boolean` `string` | [LinkColors](#link-colors) | Change link color | `false` |
| **href** | `string` | - | Link url | - |
| **isExternal** `updated` | `boolean` | `true/false` | Show link icon | `false` |
| **externalIcon** `updated` | `React.ReactNode` | - | Suffix link icon when `isExternal=true` | `<LinkIcon />` |
| **underline** | `boolean` | `true/false` | Display underline | `false` |
| **block** | `boolean` | `true/false` | Display as a separate block | `false` |
| **ref** | <Code>Ref<HTMLAnchorElement &#124; null></Code> | - | forwardRef | - |

View File

@ -2,4 +2,5 @@
export type {LinkProps} from "./link";
// export component
export * from "./link-icon";
export {default as Link} from "./link";

View File

@ -1,14 +1,6 @@
import {NextUI} from "@nextui-org/system";
export const LinkIcon = () => (
<NextUI.Svg
className="nextui-link-icon"
css={{
ml: "$1",
as: "center",
display: "flex",
color: "currentColor",
}}
<svg
className="flex ml-1 text-current self-center"
fill="none"
height="1em"
shapeRendering="geometricPrecision"
@ -22,7 +14,7 @@ export const LinkIcon = () => (
<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" />
</NextUI.Svg>
</svg>
);
LinkIcon.toString = () => ".nextui-link-icon";

View File

@ -1,9 +1,7 @@
import {useLink as useAriaLink} from "@react-aria/link";
import {mergeProps} from "@react-aria/utils";
import {forwardRef} from "@nextui-org/system";
import {useDOMRef} from "@nextui-org/dom-utils";
import {__DEV__} from "@nextui-org/shared-utils";
import {link, cx} from "@nextui-org/theme";
import {__DEV__} from "@nextui-org/shared-utils";
import {UseLinkProps, useLink} from "./use-link";
import {LinkIcon} from "./link-icon";
@ -12,24 +10,23 @@ export interface LinkProps extends UseLinkProps {}
const Link = forwardRef<LinkProps, "a">((props, ref) => {
const {
children,
as,
color,
size,
domRef,
children,
isUnderline,
isBlock,
disableAnimation,
externalIcon = <LinkIcon />,
isExternal,
focusProps,
linkProps,
className,
...otherProps
} = useLink(props);
} = useLink({...props, ref});
const domRef = useDOMRef(ref);
const Component = as || "a";
const {linkProps} = useAriaLink({...otherProps, elementType: `${as}`}, domRef);
return (
<Component
ref={domRef}
@ -43,11 +40,11 @@ const Link = forwardRef<LinkProps, "a">((props, ref) => {
}),
className,
)}
{...mergeProps(linkProps, focusProps, otherProps)}
{...mergeProps(linkProps, otherProps)}
>
<>
{children}
{isExternal && <LinkIcon />}
{isExternal && externalIcon}
</>
</Component>
);

View File

@ -1,20 +1,43 @@
import type {AriaLinkProps} from "@react-types/link";
import type {StyledLinkProps} from "@nextui-org/theme";
import {useFocusRing} from "@react-aria/focus";
import {useLink as useAriaLink} from "@react-aria/link";
import {HTMLNextUIProps} from "@nextui-org/system";
import {useDOMRef} from "@nextui-org/dom-utils";
import {ReactRef} from "@nextui-org/shared-utils";
export interface Props extends HTMLNextUIProps<"a">, StyledLinkProps {
/**
* Ref to the DOM node.
*/
ref?: ReactRef<HTMLAnchorElement | null>;
/**
* Whether the link is external.
* @default false
*/
isExternal?: boolean;
/**
* The icon to display when the link is external.
* @default <LinkIcon />
*/
externalIcon?: React.ReactNode;
}
export type UseLinkProps = Props & AriaLinkProps;
export function useLink(props: UseLinkProps) {
const {isExternal = false, autoFocus, ...otherProps} = props;
const {ref, as = "a", isExternal = false, ...otherProps} = props;
const {isFocusVisible, focusProps} = useFocusRing({autoFocus});
const domRef = useDOMRef(ref);
return {focusProps, isExternal, isFocusVisible, ...otherProps};
const {linkProps} = useAriaLink({...otherProps, elementType: `${as}`}, domRef);
if (isExternal) {
otherProps.rel = otherProps.rel ?? "noopener";
otherProps.target = otherProps.target ?? "_blank";
}
return {as, domRef, linkProps, isExternal, ...otherProps};
}
export type UseLinkReturn = ReturnType<typeof useLink>;

View File

@ -117,20 +117,47 @@ export const isUnderline = () => (
</Link>
);
export const isExternal = () => (
<Grid.Container gap={1}>
<Grid xs={12}>
<Link isExternal href="#">
{text}
</Link>
</Grid>
<Grid xs={12}>
<Link isExternal color="secondary" href="#">
{text}
</Link>
</Grid>
</Grid.Container>
);
export const isExternal = () => {
const CustomLink = () => {
return (
<svg
className="custom-link-icon ml-1"
fill="none"
height="1em"
shapeRendering="geometricPrecision"
stroke="currentColor"
strokeLinecap="round"
strokeLinejoin="round"
strokeWidth="2"
viewBox="0 0 24 24"
width="1em"
>
<path d="M10 13a5 5 0 0 0 7.54.54l3-3a5 5 0 0 0-7.07-7.07l-1.72 1.71" />
<path d="M14 11a5 5 0 0 0-7.54-.54l-3 3a5 5 0 0 0 7.07 7.07l1.71-1.71" />
</svg>
);
};
return (
<Grid.Container gap={1}>
<Grid xs={12}>
<Link isExternal href="#">
{text}
</Link>
</Grid>
<Grid xs={12}>
<Link isExternal color="secondary" href="#">
{text}
</Link>
</Grid>
<Grid xs={12}>
<Link isExternal color="success" externalIcon={<CustomLink />} href="#">
{text}
</Link>
</Grid>
</Grid.Container>
);
};
export const isBlock = () => (
<Grid.Container gap={1}>

View File

@ -34,14 +34,12 @@
"postpack": "clean-package restore"
},
"dependencies": {
"use-sync-external-store": "^1.2.0",
"deepmerge": "4.2.2"
},
"peerDependencies": {
"react": ">=16.8.0"
},
"devDependencies": {
"@types/use-sync-external-store": "^0.0.3",
"clean-package": "2.1.1",
"react": "^17.0.2"
}

View File

@ -1,72 +0,0 @@
import React, {useRef, createContext, useContext, useCallback} from "react";
// TODO: remove this and use the one from "react:18" once we migrate to it
import {useSyncExternalStore} from "use-sync-external-store";
/**
* Creates a fast named context, provider, and store hook.
* This is a faster alternative to React's built-in context API, it saves the data in a ref and
* updates it when the context value changes. It also allows to subscribe to the context specific value.
*/
export function createFastContext<Store>(initialState: Store, name = "StoreFastContext") {
function useStoreData(): {
get: () => Store;
set: (value: Partial<Store>) => void;
subscribe: (callback: () => void) => () => void;
} {
const store = useRef(initialState);
const get = useCallback(() => store.current, []);
const subscribers = useRef(new Set<() => void>());
const set = useCallback((value: Partial<Store>) => {
store.current = {...store.current, ...value};
subscribers.current.forEach((callback) => callback());
}, []);
const subscribe = useCallback((callback: () => void) => {
subscribers.current.add(callback);
return () => subscribers.current.delete(callback);
}, []);
return {
get,
set,
subscribe,
};
}
type UseStoreDataReturnType = ReturnType<typeof useStoreData>;
const StoreContext = createContext<UseStoreDataReturnType | null>(null);
StoreContext.displayName = name;
function Provider({children}: {children: React.ReactNode}) {
return <StoreContext.Provider value={useStoreData()}>{children}</StoreContext.Provider>;
}
function useStore<T>(selector: (store: Store) => T): [T, (value: Partial<Store>) => void] {
const store = useContext(StoreContext);
if (!store) {
const error = new Error(
"Store not found. Seems you forgot to wrap component within the Provider",
);
error.name = "FastContextError";
Error.captureStackTrace?.(error, useContext);
throw error;
}
const state = useSyncExternalStore(store.subscribe, () => selector(store.get()));
return [state, store.set];
}
return {
Provider,
useStore,
};
}

View File

@ -12,4 +12,3 @@ export * from "./functions";
export * from "./context";
export * from "./numbers";
export * from "./console";
export * from "./fast-context";

86
pnpm-lock.yaml generated
View File

@ -1092,16 +1092,12 @@ importers:
packages/utilities/shared-utils:
specifiers:
'@types/use-sync-external-store': ^0.0.3
clean-package: 2.1.1
deepmerge: 4.2.2
react: ^17.0.2
use-sync-external-store: ^1.2.0
dependencies:
deepmerge: 4.2.2
use-sync-external-store: 1.2.0_react@17.0.2
devDependencies:
'@types/use-sync-external-store': 0.0.3
clean-package: 2.1.1
react: 17.0.2
@ -9246,6 +9242,17 @@ packages:
telejson: 7.0.4
dev: true
/@storybook/channel-postmessage/7.0.0-beta.21:
resolution: {integrity: sha512-nLDCaI/EN9oz/EDevgU77WI3mXHGw1OTvmqjEVZgDRAMAsjThatgA5TVWG66hb6KtFb5u42peCzXTL6MbB90gA==}
dependencies:
'@storybook/channels': 7.0.0-beta.21
'@storybook/client-logger': 7.0.0-beta.21
'@storybook/core-events': 7.0.0-beta.21
'@storybook/global': 5.0.0
qs: 6.11.0
telejson: 7.0.4
dev: true
/@storybook/channel-postmessage/7.0.0-beta.8:
resolution: {integrity: sha512-UnTampw/jKAFn+p7C7UvCJ5gkyc+0jdZ+vamgop63zDak8qhYq2ZbvhmC4sL7vubbHHe0BEILoUERBWhnMspVA==}
dependencies:
@ -9286,6 +9293,10 @@ packages:
resolution: {integrity: sha512-D2XsdINQIJZT3W067sh5e17Tgt96qIrLLGiykFXJvXEVSb2jy5uFTPJR5LNI0DCF6o653i3FoYonvryGkr6bFw==}
dev: true
/@storybook/channels/7.0.0-beta.21:
resolution: {integrity: sha512-vnDT94sGdg/sbeg/D571fJapWX1w8e+wonNGwBvXMLSv+8rhfAL1c2cHRevKgYnGs8yTdb2CtCdEQlzO2lKAKg==}
dev: true
/@storybook/channels/7.0.0-beta.8:
resolution: {integrity: sha512-yr5907Q9wivjSxIZDOxl/ft8xeZjbir5EuLkNnCcN8XHb6p5zNWVky5zm2+5c3o79nU4tk/z7YOxbJI6vMmmQw==}
dev: true
@ -9370,6 +9381,12 @@ packages:
'@storybook/global': 5.0.0
dev: true
/@storybook/client-logger/7.0.0-beta.21:
resolution: {integrity: sha512-rIZw80Lku4SnmGNQivRVWktuSiStQdplhyzlEmMtL4xVgvl0y/r2QptUkVdv1tsi714QfEvcGHAJI8MX74MFbg==}
dependencies:
'@storybook/global': 5.0.0
dev: true
/@storybook/client-logger/7.0.0-beta.8:
resolution: {integrity: sha512-aZzSPbh66sEX4BQS7aEFoW8bdHrWQJksFOcSNdwCsNk34rG9zSQrQJfKT/YJl1iKgfhX/k4d/fu7JfjlOrDgxQ==}
dependencies:
@ -9507,6 +9524,10 @@ packages:
resolution: {integrity: sha512-Ag/M1UvxtR5iewwrpFJ8/1Ba5MPigdFp6LPQdlCUFJ1ALRfFx434ij8UYYk9AbyFuaHchHiJ3zN0tbFwVr8xWw==}
dev: true
/@storybook/core-events/7.0.0-beta.21:
resolution: {integrity: sha512-O961GDo2pz/qRimp9FoEEk4bWlL4QuTeQqd0bYnZ/5nBMWDjoQrVkWgkXsE7p5AoM7V7iLHR1G1f2RQcfGzdXw==}
dev: true
/@storybook/core-events/7.0.0-beta.8:
resolution: {integrity: sha512-+Jphuq3Spexn+zcPDEsfABnCf7uj+Q5MmnvXlDRp4mO3mlhu2rkFlHMelCCpLo9oGA3dIiwfeyn7LjQKI95h1Q==}
dev: true
@ -9643,6 +9664,19 @@ packages:
- supports-color
dev: true
/@storybook/instrumenter/7.0.0-beta.21:
resolution: {integrity: sha512-3hNcQ3etVd2+h6NdT6LB4T0H7beVkKPD75T7BRGehfzUyM6L1+QJ3mBQe+Xw5KA6Gs6c21BgSGhZ9Ht3z7TzRw==}
dependencies:
'@storybook/channels': 7.0.0-beta.21
'@storybook/client-logger': 7.0.0-beta.21
'@storybook/core-events': 7.0.0-beta.21
'@storybook/global': 5.0.0
'@storybook/preview-api': 7.0.0-beta.21
core-js: 3.26.0
transitivePeerDependencies:
- supports-color
dev: true
/@storybook/manager-api/7.0.0-beta.20_sfoxds7t5ydpegc3knd667wn6m:
resolution: {integrity: sha512-LBL/Rv7Ww1bphmPbUP3oP4tOOAPSV0blmxiLrM/FJuezTjOPQTjhoHI46TPLRjxP64C4X2acUnrWlHJACftCzQ==}
peerDependencies:
@ -9785,6 +9819,29 @@ packages:
- supports-color
dev: true
/@storybook/preview-api/7.0.0-beta.21:
resolution: {integrity: sha512-mDYRN62STmng2QoVOCr8QiP/It+PgTVfNW4ZuRddtG6uV5oT3D0yirGF7nmbuubplRHNqJG0z3sXMemxdHe4Uw==}
dependencies:
'@storybook/channel-postmessage': 7.0.0-beta.21
'@storybook/channels': 7.0.0-beta.21
'@storybook/client-logger': 7.0.0-beta.21
'@storybook/core-events': 7.0.0-beta.21
'@storybook/csf': 0.0.2-next.8
'@storybook/global': 5.0.0
'@storybook/types': 7.0.0-beta.21
'@types/qs': 6.9.7
dequal: 2.0.3
lodash: 4.17.21
memoizerific: 1.11.3
qs: 6.11.0
slash: 3.0.0
synchronous-promise: 2.0.16
ts-dedent: 2.2.0
util-deprecate: 1.0.2
transitivePeerDependencies:
- supports-color
dev: true
/@storybook/preview-api/7.0.0-beta.8:
resolution: {integrity: sha512-8tkvGNLO8Z7jY+ORrqxi222di2ALqmnSOWgAGapXpte+KWQe9enSPD+3kk4NTurpMmzc/52Lvly1D0s22CKLgA==}
dependencies:
@ -10058,8 +10115,8 @@ packages:
/@storybook/testing-library/0.0.14-next.1:
resolution: {integrity: sha512-1CAl40IKIhcPaCC4pYCG0b9IiYNymktfV/jTrX7ctquRY3akaN7f4A1SippVHosksft0M+rQTFE0ccfWW581fw==}
dependencies:
'@storybook/client-logger': 7.0.0-beta.20
'@storybook/instrumenter': 7.0.0-beta.20
'@storybook/client-logger': 7.0.0-beta.21
'@storybook/instrumenter': 7.0.0-beta.21
'@testing-library/dom': 8.19.0
'@testing-library/user-event': 13.5.0_aaq3sbffpfe3jnxzm2zngsddei
ts-dedent: 2.2.0
@ -10122,6 +10179,19 @@ packages:
- supports-color
dev: true
/@storybook/types/7.0.0-beta.21:
resolution: {integrity: sha512-FoQwVfuHVBAOrAkSgMq08InLe0u2+LGIGvUODH9Qvt+PF+Y7FGZRL/i7SplBIDjwlts+zY8NMNCFU9+0PPaKsg==}
dependencies:
'@babel/core': 7.20.12
'@storybook/channels': 7.0.0-beta.21
'@types/babel__core': 7.1.20
'@types/express': 4.17.15
express: 4.18.2
file-system-cache: 2.0.1
transitivePeerDependencies:
- supports-color
dev: true
/@storybook/types/7.0.0-beta.8:
resolution: {integrity: sha512-u5g7T7a5Z9yDknBGceh1ZBi3f/KQAoRJosCtLVXLZ0eVDFCyToFunWsR91O+GhbB8dzyjmhJl4Fs0abhYQNueQ==}
dependencies:
@ -10890,10 +10960,6 @@ packages:
/@types/unist/2.0.6:
resolution: {integrity: sha512-PBjIUxZHOuj0R15/xuwJYjFi+KZdNFrehocChv4g5hu6aFroHue8m0lBP0POdK2nKzbw0cgV1mws8+V/JAcEkQ==}
/@types/use-sync-external-store/0.0.3:
resolution: {integrity: sha512-EwmlvuaxPNej9+T4v5AuBPJa2x2UOJVdjCtDHgcDqitUeOtjnJKJ+apYjVcAoBEMjKW1VVFGZLUb5+qqa09XFA==}
dev: true
/@types/uuid/8.3.4:
resolution: {integrity: sha512-c/I8ZRb51j+pYGAu5CrFMRxqZ2ke4y2grEBO5AUjgSkSk+qT2Ea+OdWElz/OiMf5MNpn2b17kuVBwZLQJXzihw==}
dev: true