feat(components): badge component added, plop template improved"

This commit is contained in:
Junior Garcia 2022-10-02 11:17:59 -03:00
parent e865e64be2
commit 68113816fe
28 changed files with 1179 additions and 22 deletions

View File

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

View File

@ -0,0 +1,24 @@
# @nextui-org/badge
A Quick description of the component
> This is an internal utility, not intended for public usage.
## Installation
```sh
yarn add @nextui-org/badge
# or
npm i @nextui-org/badge
```
## 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,66 @@
import * as React from "react";
import {render} from "@testing-library/react";
import {Badge} from "../src";
describe("Badge", () => {
it("should render correctly", () => {
const wrapper = render(<Badge>New</Badge>);
expect(() => wrapper.unmount()).not.toThrow();
});
it("ref should be forwarded", () => {
const ref = React.createRef<HTMLSpanElement>();
render(<Badge ref={ref} />);
expect(ref.current).not.toBeNull();
});
it("should render children and content", () => {
const wrapper = render(
<Badge content={<span data-testid="badge-content" />}>
<span data-testid="badge-children">new</span>
</Badge>,
);
expect(wrapper.getByTestId("badge-content")).toBeTruthy();
expect(wrapper.getByTestId("badge-children")).toBeTruthy();
});
it("should not render children if variant is dot", () => {
const wrapper = render(
<Badge variant="dot">
<span data-testid="badge-children">new</span>
</Badge>,
);
expect(wrapper.queryAllByTestId("badge-children")).toHaveLength(0);
});
it("should not render children if variant is points", () => {
const wrapper = render(
<Badge variant="points">
<span data-testid="badge-children">new</span>
</Badge>,
);
expect(wrapper.queryAllByTestId("badge-children")).toHaveLength(0);
});
it("should have 3 points if variant is points", () => {
const wrapper = render(<Badge variant="points" />);
expect(wrapper.getAllByTestId("badge-point")).toHaveLength(3);
});
it("should be invisible if invisible is true", () => {
const wrapper = render(
<Badge isInvisible content={<span data-testid="badge-content" />} data-testid="badge-root">
<span data-testid="badge-children">new</span>
</Badge>,
);
expect(wrapper.getByTestId("badge-root")).toHaveClass("nextui-badge--is-invisible");
});
});

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,48 @@
{
"name": "@nextui-org/badge",
"version": "1.0.0-beta.11",
"description": "Badges are used as a small numerical value or status descriptor for UI elements.",
"keywords": [
"badge"
],
"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/badge"
},
"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": {
"clean-package": "2.1.1",
"react": "^17.0.2"
}
}

View File

@ -0,0 +1,118 @@
import {keyframes} from "@nextui-org/system";
export const pointAnimation = keyframes({
"0%": {
opacity: 1,
},
"50%": {
opacity: "0.4",
transform: "scale(0.5)",
},
"100%": {
opacity: 1,
},
});
export const appearanceInTopRight = keyframes({
"0%": {
opacity: 0,
transform: "scale(0.2) translate(50%, -50%)",
},
"60%": {
opacity: 0.75,
transform: "scale(1.2) translate(50%, -50%)",
},
"100%": {
opacity: 1,
transform: "scale(1) translate(50%, -50%)",
},
});
export const appearanceOutTopRight = keyframes({
"0%": {
opacity: 1,
transform: "scale(1) translate(50%, -50%)",
},
"100%": {
opacity: 0,
transform: "scale(0.2) translate(50%, -50%)",
},
});
export const appearanceInTopLeft = keyframes({
"0%": {
opacity: 0,
transform: "scale(0.2) translate(-50%, -50%)",
},
"60%": {
opacity: 0.75,
transform: "scale(1.2) translate(-50%, -50%)",
},
"100%": {
opacity: 1,
transform: "scale(1) translate(-50%, -50%)",
},
});
export const appearanceOutTopLeft = keyframes({
"0%": {
opacity: 1,
transform: "scale(1) translate(-50%, -50%)",
},
"100%": {
opacity: 0,
transform: "scale(0.2) translate(-50%, -50%)",
},
});
export const appearanceInBottomRight = keyframes({
"0%": {
opacity: 0,
transform: "scale(0.2) translate(50%, 50%)",
},
"60%": {
opacity: 0.75,
transform: "scale(1.2) translate(50%, 50%)",
},
"100%": {
opacity: 1,
transform: "scale(1) translate(50%, 50%)",
},
});
export const appearanceOutBottomRight = keyframes({
"0%": {
opacity: 1,
transform: "scale(1) translate(50%, 50%)",
},
"100%": {
opacity: 0,
transform: "scale(0.2) translate(50%, 50%)",
},
});
export const appearanceInBottomLeft = keyframes({
"0%": {
opacity: 0,
transform: "scale(0.2) translate(-50%, 50%)",
},
"60%": {
opacity: 0.75,
transform: "scale(1.2) translate(-50%, 50%)",
},
"100%": {
opacity: 1,
transform: "scale(1) translate(-50%, 50%)",
},
});
export const appearanceOutBottomLeft = keyframes({
"0%": {
opacity: 1,
transform: "scale(1) translate(-50%, 50%)",
},
"100%": {
opacity: 0,
transform: "scale(0.2) translate(-50%, 50%)",
},
});

View File

@ -0,0 +1,529 @@
import {styled} from "@nextui-org/system";
import {
pointAnimation,
appearanceInTopRight,
appearanceOutTopRight,
appearanceInTopLeft,
appearanceOutTopLeft,
appearanceInBottomRight,
appearanceOutBottomRight,
appearanceInBottomLeft,
appearanceOutBottomLeft,
} from "./badge.animations";
export const StyledBadgeRoot = styled("span", {
d: "inline-flex",
flexShrink: 0,
verticalAlign: "middle",
position: "relative",
overflow: "visible",
});
export const StyledBadge = styled("span", {
$$badgePlacementHOffset: "0%",
$$badgePlacementVOffset: "0%",
lineHeight: 1,
display: "flex",
flexFlow: "row wrap",
placeContent: "center",
alignItems: "center",
boxSizing: "border-box",
whiteSpace: "nowrap",
bg: "$$badgeBackgroundColor",
color: "$$badgeTextColor",
fontWeight: "$bold",
fontSize: "$$badgeFontSize",
p: "$$badgeVPadding $$badgeHPadding",
"@motion": {
animation: "none",
transition: "none",
"&.nextui-badge--is-invisible": {
animation: "none",
transition: "none",
},
},
variants: {
size: {
xs: {
$$badgeVPadding: "$space$2",
$$badgeHPadding: "$space$2",
$$badgeFontSize: "0.65rem",
},
sm: {
$$badgeVPadding: "$space$2",
$$badgeHPadding: "$space$3",
$$badgeFontSize: "0.73rem",
},
md: {
$$badgeVPadding: "$space$3",
$$badgeHPadding: "$space$4",
$$badgeFontSize: "$fontSizes$xs",
},
lg: {
$$badgeVPadding: "$space$4",
$$badgeHPadding: "$space$5",
$$badgeFontSize: "$fontSizes$base",
},
xl: {
$$badgeVPadding: "$space$5",
$$badgeHPadding: "$space$6",
$$badgeFontSize: "$fontSizes$xl",
},
},
shape: {
circle: {},
rectangle: {},
},
asChild: {
true: {
zIndex: "$2",
position: "absolute",
lineHeight: 1.5,
},
},
isOneChar: {
true: {},
},
color: {
default: {
$$badgeBackgroundColor: "$colors$neutral",
$$badgeTextColor: "$colors$neutralSolidContrast",
$$badgeShadowColor: "$colors$neutralShadow",
},
primary: {
$$badgeBackgroundColor: "$colors$primary",
$$badgeTextColor: "$colors$primarySolidContrast",
$$badgeShadowColor: "$colors$primaryShadow",
},
secondary: {
$$badgeBackgroundColor: "$colors$secondary",
$$badgeTextColor: "$colors$secondarySolidContrast",
$$badgeShadowColor: "$colors$secondaryShadow",
},
success: {
$$badgeBackgroundColor: "$colors$success",
$$badgeTextColor: "$colors$successSolidContrast",
$$badgeShadowColor: "$colors$successShadow",
},
warning: {
$$badgeBackgroundColor: "$colors$warning",
$$badgeTextColor: "$colors$warningSolidContrast",
$$badgeShadowColor: "$colors$warningShadow",
},
error: {
$$badgeBackgroundColor: "$colors$error",
$$badgeTextColor: "$colors$errorSolidContrast",
$$badgeShadowColor: "$colors$errorShadow",
},
},
enableShadow: {
true: {
boxShadow: "0 2px 10px 0 $$badgeShadowColor",
},
},
isSquared: {
true: {
borderRadius: "calc($$badgeFontSize * 0.45)",
},
false: {
borderRadius: "$pill",
},
},
placement: {
"top-right": {
animation: `${appearanceInTopRight} 0.25s ease-out`,
"&.nextui-badge--is-invisible": {
opacity: 0,
animation: `${appearanceOutTopRight} 0.2s ease-in`,
},
},
"top-left": {
animation: `${appearanceInTopLeft} 0.25s ease-out`,
"&.nextui-badge--is-invisible": {
opacity: 0,
animation: `${appearanceOutTopLeft} 0.2s ease-in`,
},
},
"bottom-right": {
animation: `${appearanceInBottomRight} 0.25s ease-out`,
"&.nextui-badge--is-invisible": {
opacity: 0,
animation: `${appearanceOutBottomRight} 0.2s ease-in`,
},
},
"bottom-left": {
animation: `${appearanceInBottomLeft} 0.25s ease-out`,
"&.nextui-badge--is-invisible": {
opacity: 0,
animation: `${appearanceOutBottomLeft} 0.2s ease-in`,
},
},
},
variant: {
default: {},
flat: {},
dot: {
p: 0,
dflex: "center",
minSize: "$$badgeFontSize",
boxSizing: "border-box",
},
points: {
p: "calc($$badgeFontSize * 0.4)",
boxSizing: "border-box",
$$badgePlacementHOffset: "calc($$badgeFontSize * 0.8)",
},
bordered: {
$$badgeBackgroundColor: "$colors$background",
bg: "$background",
bw: "$$badgeBorderWeight",
borderStyle: "solid",
borderColor: "$$badgeTextColor",
},
},
borderWeight: {
light: {
$$badgeBorderWeight: "$borderWeights$light",
},
normal: {
$$badgeBorderWeight: "$borderWeights$normal",
},
bold: {
$$badgeBorderWeight: "$borderWeights$bold",
},
extrabold: {
$$badgeBorderWeight: "$borderWeights$extrabold",
},
black: {
$$badgeBorderWeight: "$borderWeights$black",
},
},
disableOutline: {
true: {
border: "2px solid transparent",
},
false: {
border: "2px solid $colors$background",
},
},
disableAnimation: {
true: {
animation: "none",
transition: "none",
"&.nextui-badge--is-invisible": {
animation: "none",
transition: "none",
},
},
},
},
compoundVariants: [
/***
* @variant flat
* @color {default, primary, secondary, success, warning, error}
*/
// variant=flat && color=default
{
variant: "flat",
color: "default",
css: {
$$badgeBackgroundColor: "$colors$neutralLight",
$$badgeTextColor: "$colors$neutralLightContrast",
},
},
// variant=flat && color=primary
{
variant: "flat",
color: "primary",
css: {
$$badgeBackgroundColor: "$colors$primaryLight",
$$badgeTextColor: "$colors$primaryLightContrast",
},
},
// variant=flat && color=secondary
{
variant: "flat",
color: "secondary",
css: {
$$badgeBackgroundColor: "$colors$secondaryLight",
$$badgeTextColor: "$colors$secondaryLightContrast",
},
},
// variant=flat && color=success
{
variant: "flat",
color: "success",
css: {
$$badgeBackgroundColor: "$colors$successLight",
$$badgeTextColor: "$colors$successLightContrast",
},
},
// variant=flat && color=warning
{
variant: "flat",
color: "warning",
css: {
$$badgeBackgroundColor: "$colors$warningLight",
$$badgeTextColor: "$colors$warningLightContrast",
},
},
// variant=flat && color=error
{
variant: "flat",
color: "error",
css: {
$$badgeBackgroundColor: "$colors$errorLight",
$$badgeTextColor: "$colors$errorLightContrast",
},
},
/***
* @variant bordered
* @color {default, primary, secondary, success, warning, error}
*/
// variant=bordered && color=default
{
variant: "bordered",
color: "default",
css: {
$$badgeTextColor: "$colors$neutral",
},
},
// variant=bordered && color=primary
{
variant: "bordered",
color: "primary",
css: {
$$badgeTextColor: "$colors$primary",
},
},
// variant=bordered && color=secondary
{
variant: "bordered",
color: "secondary",
css: {
$$badgeTextColor: "$colors$secondary",
},
},
// variant=bordered && color=success
{
variant: "bordered",
color: "success",
css: {
$$badgeTextColor: "$colors$success",
},
},
// variant=bordered && color=warning
{
variant: "bordered",
color: "warning",
css: {
$$badgeTextColor: "$colors$warning",
},
},
// variant=bordered && color=error
{
variant: "bordered",
color: "error",
css: {
$$badgeTextColor: "$colors$error",
},
},
/***
* @asChild true
* @shape {rectangle, circle}
* @placement {top-right, top-left, bottom-right, bottom-left}
*/
// placement=top-right && shape=rectangle
{
asChild: true,
shape: "rectangle",
placement: "top-right",
css: {
top: "calc(5% + $$badgePlacementVOffset)",
right: "calc(5% + $$badgePlacementHOffset)",
transform: "scale(1) translate(50%, -50%)",
transformOrigin: "100% 0%",
},
},
// placement=top-left && shape=rectangle
{
asChild: true,
shape: "rectangle",
placement: "top-left",
css: {
top: "calc(5% + $$badgePlacementVOffset)",
left: "calc(5% + $$badgePlacementHOffset)",
transform: "scale(1) translate(-50%, -50%)",
transformOrigin: "0% 0%",
},
},
// placement=bottom-right && shape=rectangle
{
asChild: true,
shape: "rectangle",
placement: "bottom-right",
css: {
bottom: "calc(5% + $$badgePlacementVOffset)",
right: "calc(5% + $$badgePlacementHOffset)",
transform: "scale(1) translate(50%, 50%)",
transformOrigin: "100% 100%",
},
},
// placement=bottom-left && shape=rectangle
{
asChild: true,
shape: "rectangle",
placement: "bottom-left",
css: {
bottom: "calc(5% + $$badgePlacementVOffset)",
left: "calc(5% + $$badgePlacementHOffset)",
transform: "scale(1) translate(-50%, 50%)",
transformOrigin: "0% 100%",
},
},
// placement=top-right && shape=circle
{
asChild: true,
shape: "circle",
placement: "top-right",
css: {
top: "calc(15% + $$badgePlacementVOffset)",
right: "calc(15% + $$badgePlacementHOffset)",
transform: "scale(1) translate(50%, -50%)",
transformOrigin: "100% 0%",
},
},
// placement=top-left && shape=circle
{
asChild: true,
shape: "circle",
placement: "top-left",
css: {
top: "calc(15% + $$badgePlacementVOffset)",
left: "calc(15% + $$badgePlacementHOffset)",
transform: "scale(1) translate(-50%, -50%)",
transformOrigin: "0% 0%",
},
},
// placement=bottom-right && shape=circle
{
asChild: true,
shape: "circle",
placement: "bottom-right",
css: {
bottom: "calc(15% + $$badgePlacementVOffset)",
right: "calc(15% + $$badgePlacementHOffset)",
transform: "scale(1) translate(50%, 50%)",
transformOrigin: "100% 100%",
},
},
// placement=bottom-left && shape=circle
{
asChild: true,
shape: "circle",
placement: "bottom-left",
css: {
bottom: "calc(15% + $$badgePlacementVOffset)",
left: "calc(15% + $$badgePlacementHOffset)",
transform: "scale(1) translate(-50%, 50%)",
transformOrigin: "0% 100%",
},
},
/**
* @variant dot
* @shape rectangle
*/
{
variant: "dot",
shape: "rectangle",
css: {
$$badgePlacementHOffset: "calc($$badgeFontSize * 0.2)",
$$badgePlacementVOffset: "calc($$badgeFontSize * 0.1)",
},
},
/**
* @isOneChar {true, false}
* @asChild {true, false}
*/
{
isOneChar: true,
asChild: true,
css: {
p: 0,
size: "calc($$badgeFontSize + $$badgeHPadding)",
},
},
{
isOneChar: false,
asChild: true,
css: {
$$badgeVPadding: "0px",
$$badgeHPadding: "$space$2",
},
},
/**
* @isOneChar false
* @asChild true
* @size {md, lg, xl}
*/
// size = md
{
isOneChar: false,
asChild: true,
size: "md",
css: {
$$badgeHPadding: "$space$3",
},
},
// size = lg
{
isOneChar: false,
asChild: true,
size: "lg",
css: {
$$badgeHPadding: "$space$4",
},
},
// size = xl
{
isOneChar: false,
asChild: true,
size: "xl",
css: {
$$badgeHPadding: "$space$5",
},
},
/**
* @disableOutline true
* @variant bordered
*/
{
disableOutline: true,
variant: "bordered",
css: {
borderColor: "$$badgeTextColor",
},
},
],
});
export const StyledBadgePoints = styled("div", {
position: "relative",
dflex: "center",
"& .nextui-badge-point": {
size: "calc($$badgeFontSize * 0.3)",
background: "$$badgeTextColor",
margin: "0 2px",
borderRadius: "$pill",
"&:nth-child(1)": {
animation: `${pointAnimation} 1.2s ease infinite`,
},
"&:nth-child(2)": {
animation: `${pointAnimation} 1.2s ease infinite 0.4s`,
},
"&:nth-child(3)": {
animation: `${pointAnimation} 1.2s ease infinite 0.8s`,
},
},
});

View File

@ -0,0 +1,76 @@
import {useMemo} from "react";
import {forwardRef} from "@nextui-org/system";
import {useDOMRef} from "@nextui-org/dom-utils";
import {clsx, __DEV__} from "@nextui-org/shared-utils";
import {StyledBadgeRoot, StyledBadge, StyledBadgePoints} from "./badge.styles";
import {UseBadgeProps, useBadge} from "./use-badge";
export interface BadgeProps extends UseBadgeProps {}
const Badge = forwardRef<BadgeProps, "span">((props, ref) => {
const {
children,
content,
badgeCss,
css,
variant,
asChild,
isOneChar,
isInvisible,
disableAnimation,
disableOutline,
className,
...otherProps
} = useBadge(props);
const domRef = useDOMRef(ref);
const badgeChildren = useMemo(() => {
if (variant === "dot") {
return null;
}
if (variant === "points") {
return (
<StyledBadgePoints className="nextui-badge-points">
<span className="nextui-badge-point" data-testid="badge-point" />
<span className="nextui-badge-point" data-testid="badge-point" />
<span className="nextui-badge-point" data-testid="badge-point" />
</StyledBadgePoints>
);
}
return asChild ? content : children;
}, [variant, isOneChar, asChild, content, children]);
return (
<StyledBadgeRoot ref={domRef}>
{asChild && children}
<StyledBadge
asChild={asChild}
className={clsx(
"nextui-badge",
{
"nextui-badge--is-invisible": isInvisible,
},
className,
)}
css={{...badgeCss, ...css}}
disableAnimation={disableAnimation || !asChild}
disableOutline={variant === "bordered" || disableOutline}
{...otherProps}
>
{badgeChildren}
</StyledBadge>
</StyledBadgeRoot>
);
});
if (__DEV__) {
Badge.displayName = "NextUI.Badge";
}
Badge.toString = () => ".nextui-badge";
export default Badge;

View File

@ -0,0 +1,5 @@
// export types
export type {BadgeProps} from "./badge";
// export component
export {default as Badge} from "./badge";

View File

@ -0,0 +1,160 @@
import type {HTMLNextUIProps} from "@nextui-org/system";
import type {
SimpleColors,
SimplePlacement,
NormalSizes,
NormalWeights,
} from "@nextui-org/shared-utils";
import {useMemo} from "react";
export interface UseBadgeProps extends HTMLNextUIProps<"span"> {
/**
* The content of the badge. The badge will be rendered relative to its children.
*/
content?: string | number | React.ReactNode;
/**
* The badge variation.
* @default "default"
*/
variant?: "default" | "flat" | "dot" | "points" | "bordered";
/**
* The badge color.
* @default "default"
*/
color?: SimpleColors;
/**
* The badge size.
* @default "md"
*/
size?: NormalSizes;
/**
* The placement of the badge content.
* @default "top-right"
*/
placement?: SimplePlacement;
/**
* The border weight for bordered badge variation.
* @default "normal"
*/
borderWeight?: NormalWeights;
/**
* The vertical offset of the badge content.
*/
verticalOffset?: string | number;
/**
* The horizontal offset of the badge content.
*/
horizontalOffset?: string | number;
/**
* The wrapped shape the badge should overlap.
* @default "rectangle"
*/
shape?: "circle" | "rectangle";
/**
* Whether the badge is invisible.
* @default false
*/
isInvisible?: boolean;
/**
* Whether the badge corners should be squared.
* @default false
*/
isSquared?: boolean;
/**
* Whether the badge shadow should be enabled.
* @default false
*/
enableShadow?: boolean;
/**
* Whether the badge content animation should be disabled.
* @default false
*/
disableAnimation?: boolean;
/**
* Whether the badge content animation should be disabled.
* @default false
*/
disableOutline?: boolean;
}
// disableOutline: false,
// isSquared: false,
export function useBadge(props: UseBadgeProps) {
const {
children,
content,
size = "md",
color = "default",
variant = "default",
borderWeight = "normal",
placement = "top-right",
shape = "rectangle",
enableShadow = false,
verticalOffset,
horizontalOffset,
isInvisible = false,
disableOutline = false,
disableAnimation = false,
...otherProps
} = props;
const asChild = content !== undefined && !!children;
const isOneChar = useMemo(() => {
if (asChild && content && variant !== "points" && variant !== "dot") {
return String(content)?.length === 1;
}
if (children && typeof children === "string") {
return children.length === 1;
}
return false;
}, [asChild, children, variant, content]);
const badgeCss = useMemo(() => {
const isHOffsetNumber = typeof horizontalOffset === "number";
const isVOffsetNumber = typeof verticalOffset === "number";
if (verticalOffset && horizontalOffset) {
return {
$$badgePlacementHOffset: isHOffsetNumber ? `${horizontalOffset}px` : horizontalOffset,
$$badgePlacementVOffset: isVOffsetNumber ? `${verticalOffset}px` : verticalOffset,
};
}
if (verticalOffset) {
return {
$$badgePlacementVOffset: isVOffsetNumber ? `${verticalOffset}px` : verticalOffset,
};
}
if (horizontalOffset) {
return {
$$badgePlacementHOffset: isHOffsetNumber ? `${horizontalOffset}px` : horizontalOffset,
};
}
return {};
}, [verticalOffset, horizontalOffset]);
return {
children,
content,
variant,
shape,
size,
color,
borderWeight,
asChild,
isOneChar,
badgeCss,
placement,
isInvisible,
enableShadow,
disableAnimation,
disableOutline,
...otherProps,
};
}
export type UseBadgeReturn = ReturnType<typeof useBadge>;

View File

@ -0,0 +1,11 @@
import React from "react";
import {Meta} from "@storybook/react";
import {Badge} from "../src";
export default {
title: "Display/Badge",
component: Badge,
} as Meta;
export const Default = () => <Badge />;

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: false,
format: ["cjs", "esm"],
outExtension(ctx) {
return {js: `.${ctx.format}.js`};
},
inject: process.env.JSX ? [findUpSync("react-shim.js")!] : undefined,
});

View File

@ -4,7 +4,7 @@ import {render} from "@testing-library/react";
import {Col} from "../src";
describe("Col", () => {
test("should render correctly", () => {
it("should render correctly", () => {
const wrapper = render(<Col />);
expect(() => wrapper.unmount()).not.toThrow();

View File

@ -1,6 +1,6 @@
import {HTMLNextUIProps, forwardRef} from "@nextui-org/system";
import {useDOMRef} from "@nextui-org/dom-utils";
import {__DEV__} from "@nextui-org/shared-utils";
import {clsx, __DEV__} from "@nextui-org/shared-utils";
import {StyledCol} from "./col.styles";
@ -10,18 +10,19 @@ export interface ColProps extends HTMLNextUIProps<"div"> {
}
const Col = forwardRef<ColProps, "div">((props, ref) => {
const {span = 12, offset = 0, css, children} = props;
const {span = 12, offset = 0, css, children, className, ...otherProps} = props;
const domRef = useDOMRef(ref);
return (
<StyledCol
ref={domRef}
className={clsx("nextui-col", className)}
css={{
width: `${(100 / 12) * span}%`,
marginLeft: `${(100 / 12) * offset}%`,
...css,
}}
{...props}
{...otherProps}
>
{children}
</StyledCol>

View File

@ -4,9 +4,16 @@ import {render} from "@testing-library/react";
import {Container} from "../src";
describe("Container", () => {
test("should render correctly", () => {
it("should render correctly", () => {
const wrapper = render(<Container />);
expect(() => wrapper.unmount()).not.toThrow();
});
it("ref should be forwarded", () => {
const ref = React.createRef<HTMLDivElement>();
render(<Container ref={ref} />);
expect(ref.current).not.toBeNull();
});
});

View File

@ -1,6 +1,6 @@
import {forwardRef} from "@nextui-org/system";
import {useDOMRef} from "@nextui-org/dom-utils";
import {__DEV__} from "@nextui-org/shared-utils";
import {clsx, __DEV__} from "@nextui-org/shared-utils";
import {StyledContainer} from "./container.styles";
import {useContainer, UseContainerProps} from "./use-container";
@ -8,13 +8,14 @@ import {useContainer, UseContainerProps} from "./use-container";
export interface ContainerProps extends UseContainerProps {}
const Container = forwardRef<ContainerProps, "div">((props, ref) => {
const {containerCss, css, fluid, responsive, ...otherProps} = useContainer(props);
const {containerCss, css, fluid, responsive, className, ...otherProps} = useContainer(props);
const domRef = useDOMRef(ref);
return (
<StyledContainer
ref={domRef}
className={clsx("nextui-container", className)}
css={{
...containerCss,
...css,

View File

@ -4,12 +4,19 @@ import {render} from "@testing-library/react";
import {Row} from "../src";
describe("Row", () => {
test("should render correctly", () => {
it("should render correctly", () => {
const wrapper = render(<Row />);
expect(() => wrapper.unmount()).not.toThrow();
});
it("ref should be forwarded", () => {
const ref = React.createRef<HTMLDivElement>();
render(<Row ref={ref} />);
expect(ref.current).not.toBeNull();
});
it("should render different components", () => {
let wrapper = render(<Row as="p" data-testid="p-row-test" />);

View File

@ -1,6 +1,6 @@
import {forwardRef} from "@nextui-org/system";
import {useDOMRef} from "@nextui-org/dom-utils";
import {__DEV__} from "@nextui-org/shared-utils";
import {clsx, __DEV__} from "@nextui-org/shared-utils";
import {useRow, UseRowProps} from "./use-row";
import {StyledRow} from "./row.styles";
@ -8,13 +8,14 @@ import {StyledRow} from "./row.styles";
export interface RowProps extends UseRowProps {}
const Row = forwardRef<RowProps, "div">((props, ref) => {
const {rowCss, css, ...otherProps} = useRow(props);
const {rowCss, css, className, ...otherProps} = useRow(props);
const domRef = useDOMRef(ref);
return (
<StyledRow
ref={domRef}
className={clsx("nextui-row", className)}
css={{
...rowCss,
...css,

View File

@ -10,6 +10,13 @@ describe("Spacer", () => {
expect(() => wrapper.unmount()).not.toThrow();
});
it("ref should be forwarded", () => {
const ref = React.createRef<HTMLDivElement>();
render(<Spacer ref={ref} />);
expect(ref.current).not.toBeNull();
});
it("should support x and y props", () => {
const wrapper = render(
<div>

View File

@ -42,7 +42,11 @@
"@babel/runtime": "^7.6.2",
"@nextui-org/system": "workspace:*",
"@nextui-org/avatar": "workspace:*",
"@nextui-org/col": "workspace:*"
"@nextui-org/col": "workspace:*",
"@nextui-org/row": "workspace:*",
"@nextui-org/spacer": "workspace:*",
"@nextui-org/container": "workspace:*",
"@nextui-org/badge": "workspace:*"
},
"peerDependencies": {
"react": ">=16.8.0",

View File

@ -1,3 +1,7 @@
export * from "@nextui-org/system";
export * from "@nextui-org/avatar";
export * from "@nextui-org/col";
export * from "@nextui-org/row";
export * from "@nextui-org/container";
export * from "@nextui-org/spacer";
export * from "@nextui-org/avatar";
export * from "@nextui-org/badge";

View File

@ -105,6 +105,26 @@ const placement = tuple(
"rightEnd",
);
/**
* used for aria placements
*/
const normalPlacement = tuple(
"bottom",
"bottom-left",
"bottom-right",
"top",
"top-left",
"top-right",
"left",
"left-top",
"left-bottom",
"right",
"right-top",
"right-bottom",
);
const simplePlacement = tuple("top-right", "top-left", "bottom-right", "bottom-left");
const position = tuple(
"static",
"relative",
@ -235,6 +255,10 @@ export type NormalLoaders = typeof normalLoaders[number];
export type NormalAlignment = typeof normalAlignments[number];
export type NormalPlacement = typeof normalPlacement[number];
export type SimplePlacement = typeof simplePlacement[number];
export type SnippetTypes = typeof extendedColors[number];
export type CopyTypes = typeof copyTypes[number];

View File

@ -4,7 +4,7 @@ import userEvent from "@testing-library/user-event";
import { {{capitalize componentName}} } from "../src";
describe("{{capitalize componentName}}", () => {
test("should render correctly", () => {
it("should render correctly", () => {
const wrapper = render(<{{capitalize componentName}} />);
expect(() => wrapper.unmount()).not.toThrow();

View File

@ -1,5 +1,3 @@
export * from "./use-{{componentName}}";
// export types
export type { {{capitalize componentName}}Props } from "./{{componentName}}";

View File

@ -1,9 +1,12 @@
import * as React from "react";
import {HTMLNextUIProps, forwardRef} from "@nextui-org/system";
export interface Use{{capitalize componentName}}Props {}
export interface Use{{capitalize componentName}}Props extends HTMLNextUIProps<"div"> {}
export function use{{capitalize componentName}}(props: Use{{capitalize componentName}}Props) {
return {};
const {...otherProps} = props;
return {...otherProps};
}
export type Use{{capitalize componentName}}Return = ReturnType<typeof use{{capitalize componentName}}>;

View File

@ -1,15 +1,24 @@
import {HTMLNextUIProps, forwardRef} from "@nextui-org/system";
import {forwardRef} from "@nextui-org/system";
import {useDOMRef} from "@nextui-org/dom-utils";
import {__DEV__} from "@nextui-org/shared-utils";
import { Styled{{capitalize componentName}} } from "./{{componentName}}.styles";
import { Use{{capitalize componentName}}Props, use{{capitalize componentName}} } from "./use-{{componentName}}";
export interface {{capitalize componentName}}Props extends HTMLNextUIProps<"div"> {}
export interface {{capitalize componentName}}Props extends Use{{capitalize componentName}}Props {}
const {{capitalize componentName}} = forwardRef<{{capitalize componentName}}Props, "div">((props, ref) => {
const {className, ...otherProps} = use{{capitalize componentName}}(props);
const domRef = useDOMRef(ref);
return <Styled{{capitalize componentName}} ref={domRef} />;
return (
<Styled{{capitalize componentName}}
ref={domRef}
className={clsx("nextui-{{componentName}}", className)}
{...otherProps}
/>
);
});
if (__DEV__) {

23
pnpm-lock.yaml generated
View File

@ -321,6 +321,21 @@ importers:
react: 17.0.2
react-aria: 3.18.0_react@17.0.2
packages/components/badge:
specifiers:
'@nextui-org/dom-utils': workspace:*
'@nextui-org/shared-utils': 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:
clean-package: 2.1.1
react: 17.0.2
packages/components/col:
specifiers:
'@nextui-org/dom-utils': workspace:*
@ -384,7 +399,11 @@ importers:
specifiers:
'@babel/runtime': ^7.6.2
'@nextui-org/avatar': workspace:*
'@nextui-org/badge': workspace:*
'@nextui-org/col': workspace:*
'@nextui-org/container': workspace:*
'@nextui-org/row': workspace:*
'@nextui-org/spacer': workspace:*
'@nextui-org/system': workspace:*
'@stitches/react': 1.2.8
clean-package: 2.1.1
@ -394,7 +413,11 @@ importers:
dependencies:
'@babel/runtime': 7.19.0
'@nextui-org/avatar': link:../../components/avatar
'@nextui-org/badge': link:../../components/badge
'@nextui-org/col': link:../../components/col
'@nextui-org/container': link:../../components/container
'@nextui-org/row': link:../../components/row
'@nextui-org/spacer': link:../../components/spacer
'@nextui-org/system': link:../system
devDependencies:
'@stitches/react': 1.2.8_react@17.0.2