feat(components): loading component added, button stories improved

This commit is contained in:
Junior Garcia 2022-10-09 18:25:08 -03:00
parent 47580d1be9
commit 0f07cd0125
18 changed files with 905 additions and 1 deletions

View File

@ -50,6 +50,7 @@
"devDependencies": {
"@nextui-org/spacer": "workspace:*",
"@nextui-org/grid": "workspace:*",
"@nextui-org/loading": "workspace:*",
"@nextui-org/icons-utils": "workspace:*",
"@react-types/shared": "^3.14.1",
"@react-types/button": "^3.6.2",

View File

@ -1,6 +1,8 @@
import React from "react";
import {Meta} from "@storybook/react";
import {Spacer} from "@nextui-org/spacer";
import {Grid} from "@nextui-org/grid";
import {Loading} from "@nextui-org/loading";
import {Lock, Notification, User, Camera, Activity} from "@nextui-org/icons-utils";
import {Button} from "../src";
@ -45,6 +47,36 @@ export const Sizes = () => (
</div>
);
export const Loadings = () => (
<Grid.Container gap={2}>
<Grid>
<Button auto disabled color="primary" css={{px: "$13"}}>
<Loading color="currentColor" size="sm" />
</Button>
</Grid>
<Grid>
<Button auto disabled color="secondary" css={{px: "$13"}}>
<Loading color="currentColor" size="sm" type="spinner" />
</Button>
</Grid>
<Grid>
<Button auto disabled color="success" css={{px: "$13"}}>
<Loading color="currentColor" size="sm" type="points" />
</Button>
</Grid>
<Grid>
<Button auto disabled color="warning" css={{px: "$13"}}>
<Loading color="currentColor" size="sm" type="points-opacity" />
</Button>
</Grid>
<Grid>
<Button auto disabled color="error" css={{px: "$13"}}>
<Loading color="currentColor" size="sm" type="spinner" />
</Button>
</Grid>
</Grid.Container>
);
export const Colors = () => (
<>
<Button color="primary">Primary</Button>

View File

@ -42,7 +42,7 @@ export interface UseImageProps extends Omit<HTMLNextUIProps<"img">, "height" | "
*/
objectFit?: CSS["objectFit"];
/**
* Override default Image xontainer styles
* Override default Image container styles
*/
containerCss?: CSS;

View File

@ -0,0 +1,24 @@
# @nextui-org/loading
A Quick description of the component
> This is an internal utility, not intended for public usage.
## Installation
```sh
yarn add @nextui-org/loading
# or
npm i @nextui-org/loading
```
## 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,49 @@
import * as React from "react";
import {render} from "@testing-library/react";
import {Loading} from "../src";
describe("Loading", () => {
it("should render correctly", () => {
const wrapper = render(<Loading />);
expect(() => wrapper.unmount()).not.toThrow();
});
it("ref should be forwarded", () => {
const ref = React.createRef<HTMLDivElement>();
render(<Loading ref={ref} />);
expect(ref.current).not.toBeNull();
});
it("should render with default aria-label", () => {
const {getByLabelText} = render(<Loading />);
expect(getByLabelText("Loading")).toBeInTheDocument();
});
it("should render with default aria-label for spinner", () => {
const {getByLabelText} = render(<Loading type="spinner" />);
expect(getByLabelText("Loading")).toBeInTheDocument();
});
it("should work with text in spinner type", () => {
const {getByText} = render(<Loading type="spinner">Loading</Loading>);
expect(getByText("Loading")).toBeInTheDocument();
});
it("should replace the default aria-label when a children is passed", () => {
const {getByLabelText} = render(<Loading>Custom label</Loading>);
expect(getByLabelText("Custom label")).toBeInTheDocument();
});
it("should replace the default aria-label if aria-label is passed", () => {
const {getByLabelText} = render(<Loading aria-label="Custom label" />);
expect(getByLabelText("Custom label")).toBeInTheDocument();
});
});

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,50 @@
{
"name": "@nextui-org/loading",
"version": "1.0.0-beta.11",
"description": "Loaders express an unspecified wait time or display the length of a process.",
"keywords": [
"loading"
],
"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/loading"
},
"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"
},
"dependencies": {
"@nextui-org/system": "workspace:*",
"@nextui-org/shared-utils": "workspace:*",
"@nextui-org/dom-utils": "workspace:*"
},
"devDependencies": {
"@nextui-org/spacer": "workspace:*",
"@nextui-org/grid": "workspace:*",
"clean-package": "2.1.1",
"react": "^17.0.2"
}
}

View File

@ -0,0 +1,5 @@
// export types
export type {LoadingProps} from "./loading";
// export component
export {default as Loading} from "./loading";

View File

@ -0,0 +1,34 @@
import {keyframes} from "@nextui-org/system";
export const blink = keyframes({
"0%": {
opacity: "0.2",
},
"20%": {
opacity: 1,
},
"100%": {
opacity: "0.2",
},
});
export const rotate = keyframes({
"0%": {
transform: "rotate(0deg)",
},
"100%": {
transform: "rotate(360deg)",
},
});
export const points = keyframes({
"0%": {
transform: "translate(0px, 0px)",
},
"50%": {
transform: "translate(0, calc(-$$loadingSize * 1.4))",
},
"100%": {
transform: "translate(0px, 0px)",
},
});

View File

@ -0,0 +1,263 @@
import {styled} from "@nextui-org/system";
import {rotate, points, blink} from "./loading.animations";
export const StyledLoadingContainer = styled("div", {
d: "flex",
fd: "column",
jc: "center",
ai: "center",
position: "relative",
});
export const StyledLoading = styled("span", {
position: "absolute",
top: 0,
left: 0,
right: 0,
bottom: 0,
size: "100%",
dflex: "center",
bgColor: "transparent",
us: "none",
variants: {
size: {
xs: {
$$loadingSize: "$space$8",
$$loadingBorder: "$space$1",
},
sm: {
$$loadingSize: "$space$10",
$$loadingBorder: "$space$1",
},
md: {
$$loadingSize: "$space$12",
$$loadingBorder: "calc($space$1 * 1.5)",
},
lg: {
$$loadingSize: "$space$15",
$$loadingBorder: "$space$2",
},
xl: {
$$loadingSize: "$space$18",
$$loadingBorder: "$space$3",
},
},
type: {
default: {
d: "flex",
br: "$rounded",
position: "relative",
size: "$$loadingSize",
i: {
top: "0px",
size: "100%",
position: "absolute",
br: "inherit",
},
"._1": {
border: "$$loadingBorder solid $$loadingColor",
borderTop: "$$loadingBorder solid transparent",
borderLeft: "$$loadingBorder solid transparent",
borderRight: "$$loadingBorder solid transparent",
animation: `${rotate} 0.8s ease infinite`,
},
"._2": {
border: "$$loadingBorder dotted $$loadingColor",
borderTop: "$$loadingBorder solid transparent",
borderLeft: "$$loadingBorder solid transparent",
borderRight: "$$loadingBorder solid transparent",
animation: `${rotate} 0.8s linear infinite`,
opacity: 0.5,
},
"._3": {
display: "none",
},
},
points: {
d: "flex",
position: "relative",
transform: "translate(0, calc($$loadingSize * 0.6))",
i: {
size: "$$loadingSize",
margin: "0 3px",
bg: "$$loadingColor",
},
"._1": {
br: "$rounded",
animation: `${points} 0.75s ease infinite`,
},
"._2": {
br: "$rounded",
animation: `${points} 0.75s ease infinite 0.25s`,
},
"._3": {
br: "$rounded",
animation: `${points} 0.75s ease infinite 0.5s`,
},
},
"points-opacity": {
d: "flex",
position: "relative",
i: {
display: "inline-block",
size: "$$loadingSize",
br: "$rounded",
bg: "$$loadingColor",
margin: "0 1px",
animation: `${blink} 1.4s infinite both`,
},
"._2": {
animationDelay: "0.2s",
},
"._3": {
animationDelay: "0.4s",
},
},
spinner: {},
gradient: {
display: "flex",
position: "relative",
size: "$$loadingSize",
"._1": {
position: "absolute",
size: "100%",
border: "0px",
animation: `${rotate} 1s linear infinite`,
top: "0px",
br: "$rounded",
bg: "linear-gradient(0deg, $background 33%,$$loadingColor 100%)",
},
"._2": {
top: "2px",
position: "absolute",
size: "calc(100% - 4px)",
border: "0px",
bg: "$background",
br: "$rounded",
},
"._3": {
display: "none",
},
},
},
},
compoundVariants: [
// points-opacity & xs size
{
size: "xs",
type: "points-opacity",
css: {
$$loadingSize: "$space$1",
},
},
// points-opacity & sm size
{
size: "sm",
type: "points-opacity",
css: {
$$loadingSize: "$space$2",
},
},
// points-opacity & md size
{
size: "md",
type: "points-opacity",
css: {
$$loadingSize: "$space$3",
},
},
// points-opacity & lg size
{
size: "lg",
type: "points-opacity",
css: {
$$loadingSize: "$space$4",
},
},
// points-opacity & xl size
{
size: "xl",
type: "points-opacity",
css: {
$$loadingSize: "$space$5",
},
},
// points & xs size
{
size: "xs",
type: "points",
css: {
$$loadingSize: "$space$1",
},
},
// points & sm size
{
size: "sm",
type: "points",
css: {
$$loadingSize: "$space$2",
},
},
// points & md size
{
size: "md",
type: "points",
css: {
$$loadingSize: "$space$3",
},
},
// points & lg size
{
size: "lg",
type: "points",
css: {
$$loadingSize: "$space$4",
},
},
// points & xl size
{
size: "xl",
type: "points",
css: {
$$loadingSize: "$space$5",
},
},
],
defaultVariants: {
type: "default",
},
});
export const StyledLoadingLabel = styled("label", {
mt: "$1",
color: "$$labelColor",
fontSize: "$$loadingSize",
"*": {
margin: 0,
},
variants: {
size: {
xs: {
fontSize: "$space$5",
marginTop: "$2",
},
sm: {
fontSize: "$space$6",
marginTop: "$3",
},
md: {
fontSize: "$base",
marginTop: "$4",
},
lg: {
fontSize: "$space$10",
marginTop: "$4",
},
xl: {
fontSize: "$space$11",
marginTop: "$5",
},
},
},
});

View File

@ -0,0 +1,72 @@
import {forwardRef} from "@nextui-org/system";
import {useDOMRef} from "@nextui-org/dom-utils";
import {clsx, __DEV__} from "@nextui-org/shared-utils";
import {StyledLoadingContainer, StyledLoading, StyledLoadingLabel} from "./loading.styles";
import {UseLoadingProps, useLoading} from "./use-loading";
import Spinner from "./variants/spinner";
export interface LoadingProps extends UseLoadingProps {}
const Loading = forwardRef<LoadingProps, "div">((props, ref) => {
const {
css,
containerCss,
gradientCSS,
children,
size,
type,
ariaLabel,
loadingColor,
labelColor,
className,
...otherProps
} = useLoading(props);
const domRef = useDOMRef(ref);
return (
<StyledLoadingContainer
ref={domRef}
className={clsx("nextui-loading-container", className)}
css={{
$$loadingColor: loadingColor,
$$labelColor: labelColor,
...containerCss,
}}
{...otherProps}
>
{type === "spinner" ? (
<Spinner aria-label={ariaLabel} css={css} size={size} />
) : (
<StyledLoading
aria-label={ariaLabel}
className={clsx("nextui-loading", `nextui-loading-${type}`)}
css={{
...css,
...gradientCSS,
}}
size={size}
type={type}
>
<i className="_1" />
<i className="_2" />
<i className="_3" />
</StyledLoading>
)}
{children && (
<StyledLoadingLabel className="nextui-loading-label" size={size}>
{children}
</StyledLoadingLabel>
)}
</StyledLoadingContainer>
);
});
if (__DEV__) {
Loading.displayName = "NextUI.Loading";
}
Loading.toString = () => ".nextui-loading";
export default Loading;

View File

@ -0,0 +1,71 @@
import {useMemo} from "react";
import {getCSSColor, NormalSizes} from "@nextui-org/shared-utils";
import {CSS, HTMLNextUIProps} from "@nextui-org/system";
export interface UseLoadingProps extends HTMLNextUIProps<"div"> {
/**
* Loading size.
* @default "md"
*/
size?: NormalSizes;
/**
* Loading color.
* @default "primary"
*/
color?: CSS["color"];
/**
* Loading label color.
* @default "default"
*/
textColor?: CSS["color"];
/**
* Loading type.
* @default "default"
*/
//TODO: rename to "variant"
type?: "default" | "points" | "points-opacity" | "gradient" | "spinner";
/**
* Sets a background for the "gradient" loading variant.
*/
gradientBackground?: CSS["color"];
/**
* Override default container styles
*/
containerCss?: CSS;
}
export function useLoading(props: UseLoadingProps) {
const {
children,
size = "md",
color = "primary",
type = "default",
textColor = "default",
gradientBackground,
...otherProps
} = props;
const gradientCSS = useMemo(() => {
return type === "gradient" ? {"._2": {bg: gradientBackground}} : {};
}, [type, gradientBackground]);
const ariaLabel = useMemo(() => {
if (children && typeof children === "string") {
return children;
}
return !otherProps["aria-label"] ? "Loading" : "";
}, [children, otherProps["aria-label"]]);
const loadingColor = useMemo(() => {
return getCSSColor(color as string);
}, [color]);
const labelColor = useMemo(() => {
return getCSSColor(textColor as string);
}, [textColor]);
return {children, size, type, loadingColor, labelColor, ariaLabel, gradientCSS, ...otherProps};
}
export type UseLoadingReturn = ReturnType<typeof useLoading>;

View File

@ -0,0 +1,129 @@
import {styled, keyframes, forwardRef} from "@nextui-org/system";
import {useDOMRef} from "@nextui-org/dom-utils";
import {clsx, __DEV__} from "@nextui-org/shared-utils";
import {UseLoadingProps} from "../use-loading";
export interface SpinnerProps
extends Omit<UseLoadingProps, "type" | "gradientBackground" | "containerCss"> {}
export const spinnerAnimation = keyframes({
"0%": {
opacity: 1,
},
"100%": {
opacity: 0.15,
},
});
export const StyledSpinner = styled("div", {
d: "flex",
fd: "column",
jc: "center",
ai: "center",
position: "relative",
variants: {
size: {
xs: {
size: "$6",
},
sm: {
size: "$8",
},
md: {
size: "$9",
},
lg: {
size: "$11",
},
xl: {
size: "$12",
},
},
},
});
export const StyledSpinnerSpan = styled("span", {
bg: "$$loadingColor",
position: "absolute",
width: "34%",
height: "8%",
br: "$lg",
animation: `${spinnerAnimation} 1.2s linear 0s infinite normal none running`,
"&:nth-child(1)": {
animationDelay: "-1.2s",
transform: "rotate(0deg) translate(146%)",
},
"&:nth-child(2)": {
animationDelay: "-1.1s",
transform: "rotate(30deg) translate(146%)",
},
"&:nth-child(3)": {
animationDelay: "-1s",
transform: "rotate(60deg) translate(146%)",
},
"&:nth-child(4)": {
animationDelay: "-0.9s",
transform: "rotate(90deg) translate(146%)",
},
"&:nth-child(5)": {
animationDelay: "-0.8s",
transform: "rotate(120deg) translate(146%)",
},
"&:nth-child(6)": {
animationDelay: "-0.7s",
transform: "rotate(150deg) translate(146%)",
},
"&:nth-child(7)": {
animationDelay: "-0.6s",
transform: "rotate(180deg) translate(146%)",
},
"&:nth-child(8)": {
animationDelay: "-0.5s",
transform: "rotate(210deg) translate(146%)",
},
"&:nth-child(9)": {
animationDelay: "-0.4s",
transform: "rotate(240deg) translate(146%)",
},
"&:nth-child(10)": {
animationDelay: "-0.3s",
transform: "rotate(270deg) translate(146%)",
},
"&:nth-child(11)": {
animationDelay: "-0.2s",
transform: "rotate(300deg) translate(146%)",
},
"&:nth-child(12)": {
animationDelay: "-0.1s",
transform: "rotate(330deg) translate(146%)",
},
});
const Spinner = forwardRef<SpinnerProps, "div">((props, ref) => {
const {children, size, className, ...otherProps} = props;
const domRef = useDOMRef(ref);
return (
<StyledSpinner
ref={domRef}
className={clsx("nextui-spinner", className)}
size={size}
{...otherProps}
>
{[...new Array(12)].map((_, index) => (
<StyledSpinnerSpan key={`nextui-spinner-${index}`} />
))}
{children}
</StyledSpinner>
);
});
if (__DEV__) {
Spinner.displayName = "NextUI.Spinner";
}
Spinner.toString = () => ".nextui-spinner";
export default Spinner;

View File

@ -0,0 +1,99 @@
import React from "react";
import {Meta} from "@storybook/react";
import {Spacer} from "@nextui-org/spacer";
import {Grid} from "@nextui-org/grid";
import {Loading} from "../src";
export default {
title: "Feedback/Loading",
component: Loading,
} as Meta;
export const Default = () => <Loading />;
export const Text = () => (
<>
<Loading size="xs">Loading</Loading>
<Spacer x={1} />
<Loading size="sm">Loading</Loading>
<Spacer x={1} />
<Loading size="md">Loading</Loading>
<Spacer x={1} />
<Loading size="lg">Loading</Loading>
<Spacer x={1} />
<Loading size="xl">Loading</Loading>
<Spacer x={1} />
</>
);
export const Colors = () => (
<>
<Loading color="primary">Primary</Loading>
<Spacer x={1} />
<Loading color="secondary">Secondary</Loading>
<Spacer x={1} />
<Loading color="success">Success</Loading>
<Spacer x={1} />
<Loading color="warning">Warning</Loading>
<Spacer x={1} />
<Loading color="error">Error</Loading>
<Spacer x={1} />
</>
);
export const TextColors = () => (
<>
<Loading textColor="primary">Primary</Loading>
<Spacer x={1} />
<Loading textColor="secondary">Secondary</Loading>
<Spacer x={1} />
<Loading textColor="success">Success</Loading>
<Spacer x={1} />
<Loading textColor="warning">Warning</Loading>
<Spacer x={1} />
<Loading textColor="error">Error</Loading>
<Spacer x={1} />
</>
);
export const Sizes = () => (
<>
<Loading size="xs">mini</Loading>
<Spacer y={2} />
<Loading size="sm">small</Loading>
<Spacer y={2} />
<Loading size="md">medium</Loading>
<Spacer y={2} />
<Loading size="lg">large</Loading>
<Spacer y={2} />
<Loading size="xl">xlarge</Loading>
<Spacer y={2} />
</>
);
export const Types = () => (
<Grid.Container gap={4} justify="center">
<Grid>
<Loading type="default">default</Loading>
</Grid>
<Grid>
<Loading type="spinner">spinner</Loading>
</Grid>
<Grid>
<Loading style={{marginLeft: "-0.5rem"}} type="points">
points
</Loading>
</Grid>
<Grid>
<Loading style={{marginLeft: "-0.5rem"}} type="points-opacity">
points-opacity
</Loading>
</Grid>
<Grid>
<Loading style={{marginLeft: "-0.5rem"}} type="gradient">
gradient
</Loading>
</Grid>
</Grid.Container>
);

View File

@ -0,0 +1,9 @@
{
"extends": "../../../tsconfig.json",
"compilerOptions": {
"paths": {
"@stitches/react": ["../../../node_modules/@stitches/react"]
}
},
"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: true,
format: ["cjs", "esm"],
outExtension(ctx) {
return {js: `.${ctx.format}.js`};
},
inject: process.env.JSX ? [findUpSync("react-shim.js")!] : undefined,
});

View File

@ -187,3 +187,32 @@ export const invertHex = (hexProp: string, smooth = true) => {
// pad each with zeros and return
return "#" + padZero(r.toString(16)) + padZero(g.toString(16)) + padZero(b.toString(16));
};
/**
* Function that returns the color token name, if it exists, otherwise returns the color itself
* @example getCSSColor("primary") // returns "$primary", getCSSColor("#fff") // returns "#fff"
* @param color - string
* @param defaultColor - string
* @returns string
*/
export function getCSSColor(color: string, defaultColor: string = "$colors$text") {
if (!color) {
return defaultColor;
}
if (isNormalColor(color)) {
switch (color) {
case "default":
return defaultColor;
default:
return `$colors$${color}`;
}
}
if (color?.startsWith("$") && color?.includes("$colors")) {
// $colors$<color> e.g. $colors$primary
return `$colors$${color}`;
}
// #color || rgb || rgba
return color;
}

21
pnpm-lock.yaml generated
View File

@ -348,6 +348,7 @@ importers:
'@nextui-org/drip': workspace:*
'@nextui-org/grid': workspace:*
'@nextui-org/icons-utils': workspace:*
'@nextui-org/loading': workspace:*
'@nextui-org/shared-css': workspace:*
'@nextui-org/shared-utils': workspace:*
'@nextui-org/spacer': workspace:*
@ -373,6 +374,7 @@ importers:
devDependencies:
'@nextui-org/grid': link:../grid
'@nextui-org/icons-utils': link:../../utilities/icons-utils
'@nextui-org/loading': link:../loading
'@nextui-org/spacer': link:../spacer
'@react-types/button': 3.6.2_react@17.0.2
'@react-types/shared': 3.15.0_react@17.0.2
@ -602,6 +604,25 @@ importers:
clean-package: 2.1.1
react: 17.0.2
packages/components/loading:
specifiers:
'@nextui-org/dom-utils': workspace:*
'@nextui-org/grid': workspace:*
'@nextui-org/shared-utils': workspace:*
'@nextui-org/spacer': workspace:*
'@nextui-org/system': workspace:*
clean-package: 2.1.1
react: ^17.0.2
dependencies:
'@nextui-org/dom-utils': link:../../utilities/dom-utils
'@nextui-org/shared-utils': link:../../utilities/shared-utils
'@nextui-org/system': link:../../core/system
devDependencies:
'@nextui-org/grid': link:../grid
'@nextui-org/spacer': link:../spacer
clean-package: 2.1.1
react: 17.0.2
packages/components/row:
specifiers:
'@nextui-org/dom-utils': workspace:*