feat(link): focus visible added, theme made more robust

This commit is contained in:
Junior Garcia 2023-01-08 15:56:35 -03:00
parent 95084d6271
commit 7081b41cbb
8 changed files with 209 additions and 138 deletions

View File

@ -1,41 +0,0 @@
import {styled} from "@nextui-org/system";
import {cssFocusVisible} from "@nextui-org/shared-css";
export const StyledLink = styled(
"a",
{
display: "inline-flex",
alignItems: "center",
lineHeight: "inherit",
textDecoration: "none",
width: "$fit",
backgroundColor: "transparent",
backgroundImage: "inherit",
backgroundClip: "inherit",
WebkitTextFillColor: "inherit",
color: "$$linkColor",
outline: "none",
maxW: "$max",
"&:hover": {
opacity: 0.8,
},
"@motion": {
transition: "none",
},
variants: {
underline: {
true: {
"&:hover, &:active, &:focus": {
textDecoration: "underline",
},
},
},
animated: {
true: {
transition: "opacity 0.2s ease 0s, background 0.2s ease 0s",
},
},
},
},
cssFocusVisible,
);

View File

@ -1,7 +1,8 @@
import React from "react";
import {Grid} from "@nextui-org/grid";
import {cva, linkVariants, type VariantProps, ExtendVariantProps} from "@nextui-org/theme";
import {Link} from "../src";
import {Link, LinkProps} from "../src";
export default {
title: "Navigation/Link",
@ -12,6 +13,34 @@ const text = `"First solve the problem. Then, write the code." - Jon Johnson.`;
export const Default = () => <Link href="#">{text}</Link>;
const customLink = cva(null, {
variants: {
color: {
...linkVariants.color,
teal: "text-teal-600",
},
link: {
true: "before:content-['👉'] before:mr-1",
},
},
});
type MyLinkProps = ExtendVariantProps<LinkProps, VariantProps<typeof customLink>>;
const MyLink = (props: MyLinkProps) => {
const {link, color, ...otherProps} = props;
return <Link {...otherProps} isExternal className={customLink({color, link})} />;
};
export const CustomVariant = () => {
return (
<MyLink link color="teal" href="#">
Visit out new Store
</MyLink>
);
};
export const Sizes = () => (
<Grid.Container gap={1}>
<Grid xs={12}>
@ -40,7 +69,7 @@ export const Sizes = () => (
</Link>
</Grid>
<Grid xs={12}>
<Link className="text-3xl text-pink-500" href="#">
<Link className="text-2xl text-pink-500" href="#">
{text}
</Link>
</Grid>

View File

@ -2,6 +2,8 @@ const plugin = require("tailwindcss/plugin");
const colors = require("./colors.js");
const DEFAULT_TRANSITION_DURATION = "200ms";
module.exports = plugin(
function ({addUtilities}) {
addUtilities({
@ -23,7 +25,7 @@ module.exports = plugin(
".transition-background": {
"transition-property": "background",
"transition-timing-function": "ease",
"transition-duration": "250ms",
"transition-duration": DEFAULT_TRANSITION_DURATION,
},
/**
* Tailwind utilities
@ -31,36 +33,36 @@ module.exports = plugin(
".transition-all": {
"transition-property": "all",
"transition-timing-function": "ease",
"transition-duration": "250ms",
"transition-duration": DEFAULT_TRANSITION_DURATION,
},
".transition": {
"transition-property":
"color, background-color, border-color, text-decoration-color, fill, stroke, opacity, box-shadow, transform, filter, backdrop-filter",
"transition-timing-function": "ease",
"transition-duration": "250ms",
"transition-duration": DEFAULT_TRANSITION_DURATION,
},
".transition-colors": {
"transition-property":
"color, background-color, border-color, text-decoration-color, fill, stroke",
"transition-timing-function": "ease",
"transition-duration": "250ms",
"transition-duration": DEFAULT_TRANSITION_DURATION,
},
".transition-opacity": {
"transition-property": "opacity",
"transition-timing-function": "ease",
"transition-duration": "250ms",
"transition-duration": DEFAULT_TRANSITION_DURATION,
},
".transition-shadow": {
"transition-property": "box-shadow",
"transition-timing-function": "ease",
"transition-duration": "250ms",
"transition-duration": DEFAULT_TRANSITION_DURATION,
},
".transition-transform": {
"transition-property": "transform",
"transition-timing-function": "ease",
"transition-duration": "250ms",
"transition-duration": DEFAULT_TRANSITION_DURATION,
},
});
},
@ -72,6 +74,16 @@ module.exports = plugin(
transparent: "transparent",
white: "#ffffff",
black: "#000000",
background: {
light: "#ffffff",
DEFAULT: "#ffffff",
dark: "#000000",
},
foreground: {
light: "#11181C",
DEFAULT: "#11181C",
dark: "#ECEDEE",
},
neutral: {
light: "#889096",
DEFAULT: "#889096",
@ -102,11 +114,6 @@ module.exports = plugin(
DEFAULT: colors.red[500],
dark: colors.red[500],
},
text: {
light: "#11181C",
DEFAULT: "#11181C",
dark: "#ECEDEE",
},
red: {
...colors.red,
DEFAULT: colors.red[500],

View File

@ -1,83 +1,92 @@
import {styled, type VariantProps} from "../../utils";
import {cva, withFocusVisible, type VariantProps} from "../../utils";
export const link = styled(
[
"inline-flex",
"relative",
"items-center",
"leading-inherit",
"text-current",
"w-fit",
"outline-0",
"bg-transparent",
"bg-img-inherit",
"bg-clip-inherit",
"text-fill-inherit",
"transition-opacity",
],
{
variants: {
size: {
xs: "text-xs",
sm: "text-sm",
md: "text-base",
lg: "text-lg",
xl: "text-xl",
},
color: {
primary: "text-primary",
secondary: "text-secondary dark:text-secondary-dark",
success: "text-success",
warning: "text-warning",
error: "text-error",
},
isUnderline: {
true: "hover:underline active:underline focus:underline",
false: "no-underline",
},
isBlock: {
true: "px-2 py-1 hover:after:opacity-100 after:content-[' '] after:inset-0 after:opacity-0 after:w-full after:h-full after:rounded-xl after:transition-background after:absolute",
false: "hover:opacity-80",
},
disableAnimation: {
true: "after:transition-none",
},
},
compoundVariants: [
{
isBlock: true,
color: "primary",
class: "hover:after:bg-primary/25",
},
{
isBlock: true,
color: "secondary",
class: "hover:after:bg-secondary/25 dark:hover:after:bg-secondary-dark/25",
},
{
isBlock: true,
color: "success",
class: "hover:after:bg-success/25",
},
{
isBlock: true,
color: "warning",
class: "hover:after:bg-warning/25",
},
{
isBlock: true,
color: "error",
class: "hover:after:bg-error/25",
},
],
defaultVariants: {
color: "primary",
size: "md",
isBlock: false,
isUnderline: false,
disableAnimation: false,
},
const linkBase = withFocusVisible([
"inline-flex",
"relative",
"items-center",
"leading-inherit",
"text-current",
"w-fit",
"rounded-lg",
"bg-transparent",
"bg-img-inherit",
"bg-clip-inherit",
"text-fill-inherit",
]);
const linkVariants = {
size: {
xs: "text-xs",
sm: "text-sm",
md: "text-base",
lg: "text-lg",
xl: "text-xl",
},
);
color: {
primary: "text-primary",
secondary: "text-secondary dark:text-secondary-dark",
success: "text-success",
warning: "text-warning",
error: "text-error",
},
isUnderline: {
true: "hover:underline active:underline focus:underline",
false: "no-underline",
},
isBlock: {
true: "px-2 py-1 hover:after:opacity-100 after:content-[' '] after:inset-0 after:opacity-0 after:w-full after:h-full after:rounded-xl after:transition-background after:absolute",
false: "hover:opacity-80",
},
disableAnimation: {
true: "after:transition-none transition-none",
},
};
const linkCompoundVariants = [
{
isBlock: true,
color: "primary",
class: "hover:after:bg-primary/25",
},
{
isBlock: true,
color: "secondary",
class: "hover:after:bg-secondary/25 dark:hover:after:bg-secondary-dark/25",
},
{
isBlock: true,
color: "success",
class: "hover:after:bg-success/25",
},
{
isBlock: true,
color: "warning",
class: "hover:after:bg-warning/25",
},
{
isBlock: true,
color: "error",
class: "hover:after:bg-error/25",
},
];
const linkDefaultVariants = {
color: "primary",
size: "md",
isBlock: false,
isUnderline: false,
disableAnimation: false,
};
const linkStyles = {
variants: linkVariants,
compoundVariants: linkCompoundVariants,
defaultVariants: linkDefaultVariants,
};
// @ts-ignore
const link = cva(linkBase, linkStyles);
export {linkBase, linkVariants, linkCompoundVariants, linkDefaultVariants, linkStyles, link};
export type StyledLinkProps = VariantProps<typeof link>;

View File

@ -1,5 +1,2 @@
import {cva} from "class-variance-authority";
export {cx} from "class-variance-authority";
export {cva, cx} from "class-variance-authority";
export type {VariantProps} from "class-variance-authority";
export {cva, cva as styled};

View File

@ -1 +1,3 @@
export * from "./cva";
export * from "./styles";
export * from "./types";

View File

@ -0,0 +1,41 @@
/**
* focus styles when the element is focused by keyboard.
*/
export const focusVisibleClasses = [
"focus:outline-0",
"focus-visible:ring-2",
"focus-visible:ring-primary",
"focus-visible:ring-offset-2",
"focus-visible:transition-shadow",
"focus-visible:ring-offset-background",
"dark:focus-visible:ring-offset-background-dark",
];
/**
* This function takes an array of classes and adds another array of classes to it.
* @param classes Array<string>
* @param newClasses Array<string>
* @returns Array<string>
*/
export const withClasses = (classes: Array<string>, newClasses: Array<string>) => {
// If there are existing classes, but no new classes, return the existing classes
if (!classes) {
if (!newClasses) {
return [];
}
return newClasses;
}
// If there are new classes, but no existing classes, return the new classes
if (!newClasses) {
return classes;
}
// If there are both new classes and existing classes, return a combination of the two
return classes.concat(newClasses);
};
export const withFocusVisible = (classes: Array<string>) => {
return withClasses(classes, focusVisibleClasses);
};

View File

@ -0,0 +1,27 @@
import type {VariantProps} from "class-variance-authority";
/**
* This is a utility type that allows you to extend the props of a component and add variant props.
* @example
*
* import {cva, VariantProps, ExtendVariantProps} from "@nextui-org/theme";
*
* type ComponentProps = {
* foo: string;
* bar: string;
* }
*
* const myComponent = cva(["text-blue-500", "font-bold"], {
* variants: {
* isFoo: {
* true: "text-red-500",
* false: "text-green-500"
* }
* }
* })
*
* type MyVariantProps = VariantProps<typeof myComponent>;
*
* type MyComponentProps = ExtendVariantProps<ComponentProps, MyVariantProps>;
*/
export type ExtendVariantProps<P, T extends VariantProps<any>> = Omit<P, keyof T> & T;