feat(components): card & related components added

This commit is contained in:
Junior Garcia 2022-10-07 21:55:51 -03:00
parent 3e6673269e
commit 7dcf0d0318
85 changed files with 2571 additions and 26 deletions

2
next-env.d.ts vendored
View File

@ -1,2 +0,0 @@
/// <reference types="next" />
/// <reference types="next/types/global" />

View File

@ -0,0 +1,24 @@
# @nextui-org/card
A Quick description of the component
> This is an internal utility, not intended for public usage.
## Installation
```sh
yarn add @nextui-org/card
# or
npm i @nextui-org/card
```
## 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,50 @@
import * as React from "react";
import {render} from "@testing-library/react";
import userEvent from "@testing-library/user-event";
import {Card} from "../src";
describe("Card", () => {
it("should render correctly", () => {
const wrapper = render(<Card />);
expect(() => wrapper.unmount()).not.toThrow();
});
it("ref should be forwarded", () => {
const ref = React.createRef<HTMLDivElement>();
render(<Card ref={ref} />);
expect(ref.current).not.toBeNull();
});
it("should support hoverable", () => {
const wrapper = render(<Card isHoverable />);
expect(() => wrapper.unmount()).not.toThrow();
});
it("should be clicked when is pressable", () => {
const onPress = jest.fn();
const {container} = render(<Card isPressable onPress={onPress} />);
userEvent.click(container.firstChild as HTMLElement);
expect(onPress).toBeCalledTimes(1);
});
it("should render correctly when nested", () => {
const wrapper = render(
<Card>
<Card />
</Card>,
);
expect(() => wrapper.unmount()).not.toThrow();
});
it("should have a button role when is pressable", () => {
const {container} = render(<Card isPressable />);
expect(container.firstChild).toHaveAttribute("role", "button");
});
});

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,62 @@
{
"name": "@nextui-org/card",
"version": "1.0.0-beta.11",
"description": "Card is a container for text, photos, and actions in the context of a single subject.",
"keywords": [
"card"
],
"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/card"
},
"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/shared-css": "workspace:*",
"@nextui-org/dom-utils": "workspace:*",
"@nextui-org/drip": "workspace:*",
"@nextui-org/divider": "workspace:*",
"@nextui-org/image": "workspace:*",
"@react-aria/focus": "^3.9.0",
"@react-aria/utils": "^3.14.0",
"@react-aria/interactions": "^3.12.0"
},
"devDependencies": {
"@react-types/shared": "^3.14.1",
"@nextui-org/text": "workspace:*",
"@nextui-org/grid": "workspace:*",
"@nextui-org/col": "workspace:*",
"@nextui-org/row": "workspace:*",
"@nextui-org/code": "workspace:*",
"@nextui-org/link": "workspace:*",
"clean-package": "2.1.1",
"react": "^17.0.2"
}
}

View File

@ -0,0 +1,177 @@
import {styled} from "@nextui-org/system";
import {cssFocusVisible, cssNoBlurriness} from "@nextui-org/shared-css";
export const StyledCardBody = styled("div", {
d: "flex",
w: "100%",
h: "auto",
flex: "1 1 auto",
fd: "column",
jc: "inherit",
ai: "inherit",
ac: "inherit",
py: "$lg",
px: "$sm",
oy: "auto",
position: "relative",
ta: "left",
});
export const StyledCard = styled(
"div",
{
$$cardColor: "$colors$backgroundContrast",
$$cardTextColor: "$colors$text",
m: 0,
p: 0,
br: "$lg",
bg: "$$cardColor",
color: "$$cardTextColor",
position: "relative",
display: "flex",
overflow: "hidden",
fd: "column",
width: "100%",
height: "auto",
boxSizing: "border-box",
"@motion": {
transition: "none",
},
".nextui-image": {
width: "100%",
},
".nextui-drip": {
zIndex: "$1",
".nextui-drip-filler": {
opacity: 0.25,
fill: "$accents6",
},
},
variants: {
variant: {
flat: {
bg: "$accents0",
},
shadow: {
dropShadow: "$lg",
"@safari": {
boxShadow: "$lg",
dropShadow: "none",
},
},
bordered: {
borderStyle: "solid",
borderColor: "$border",
},
},
borderWeight: {
light: {
bw: "$light",
},
normal: {
bw: "$normal",
},
bold: {
bw: "$bold",
},
extrabold: {
bw: "$extrabold",
},
black: {
bw: "$black",
},
},
disableAnimation: {
true: {
transition: "none",
},
false: {
transition: "$card",
},
},
isPressable: {
true: {
cursor: "pointer",
us: "none",
WebkitTapHighlightColor: "transparent",
},
},
isPressed: {
true: {},
},
isHovered: {
true: {
dropShadow: "$lg",
"@safari": {
boxShadow: "$lg",
dropShadow: "none",
},
},
},
},
compoundVariants: [
// isPreseed && !disableAnimation
{
isPressed: true,
disableAnimation: false,
css: {
transform: "scale(0.97)",
},
},
// isHovered && !disableAnimation
{
isHovered: true,
disableAnimation: false,
css: {
transform: "translateY(-2px)",
},
},
// isHovered && variant === 'shadow'
{
isHovered: true,
variant: "shadow",
css: {
dropShadow: "$xl",
"@safari": {
boxShadow: "$xl",
dropShadow: "none",
},
},
},
],
},
cssNoBlurriness,
cssFocusVisible,
);
export const StyledCardHeader = styled("div", {
w: "100%",
display: "flex",
flexShrink: 0,
zIndex: "$1",
jc: "flex-start",
ai: "center",
overflow: "hidden",
color: "inherit",
p: "$sm",
});
export const StyledCardFooter = styled("div", {
w: "100%",
h: "auto",
p: "$sm",
d: "flex",
ai: "center",
overflow: "hidden",
color: "inherit",
bblr: "$lg",
bbrr: "$lg",
variants: {
isBlurred: {
true: {
bf: "saturate(180%) blur(10px)",
bg: "$$cardColor",
},
},
},
});

View File

@ -0,0 +1,75 @@
import {forwardRef} from "@nextui-org/system";
import {clsx, __DEV__} from "@nextui-org/shared-utils";
import {Divider} from "@nextui-org/divider";
import {Image} from "@nextui-org/image";
import {Drip} from "@nextui-org/drip";
import {
StyledCard,
StyledCardHeader as CardHeader,
StyledCardFooter as CardFooter,
StyledCardBody as CardBody,
} from "./card.styles";
import {UseCardProps, useCard} from "./use-card";
export interface CardProps extends Omit<UseCardProps, "ref"> {}
type CompoundCard = {
Header: typeof CardHeader;
Body: typeof CardBody;
Footer: typeof CardFooter;
Image: typeof Image;
Divider: typeof Divider;
};
const Card = forwardRef<CardProps, "div", CompoundCard>((props, ref) => {
const {
cardRef,
children,
className,
variant,
isFocusVisible,
isPressable,
isPressed,
disableAnimation,
disableRipple,
borderWeight,
isHovered,
dripBindings,
getCardProps,
} = useCard({ref, ...props});
return (
<StyledCard
ref={cardRef}
borderWeight={borderWeight}
className={clsx("nextui-card", className)}
disableAnimation={disableAnimation}
isFocusVisible={isFocusVisible}
isHovered={isHovered}
isPressable={isPressable}
isPressed={isPressed}
role={isPressable ? "button" : "section"}
tabIndex={isPressable ? 0 : -1}
variant={variant}
{...getCardProps()}
>
{isPressable && !disableAnimation && !disableRipple && <Drip {...dripBindings} />}
{children}
</StyledCard>
);
});
Card.Header = CardHeader;
Card.Body = CardBody;
Card.Footer = CardFooter;
Card.Image = Image;
Card.Divider = Divider;
if (__DEV__) {
Card.displayName = "NextUI.Card";
}
Card.toString = () => ".nextui-card";
export default Card;

View File

@ -0,0 +1,5 @@
// export types
export type {CardProps} from "./card";
// export component
export {default as Card} from "./card";

View File

@ -0,0 +1,145 @@
import type {PressEvents, PressEvent, FocusableProps} from "@react-types/shared";
import {MouseEvent, useCallback} from "react";
import {mergeProps} from "@react-aria/utils";
import {useFocusRing} from "@react-aria/focus";
import {usePress, useHover} from "@react-aria/interactions";
import {HTMLNextUIProps} from "@nextui-org/system";
import {NormalWeights, ReactRef} from "@nextui-org/shared-utils";
import {useDOMRef, IFocusRingAria} from "@nextui-org/dom-utils";
import {useDrip} from "@nextui-org/drip";
export interface UseCardProps extends HTMLNextUIProps<"div", PressEvents & FocusableProps> {
/**
* The card ref.
*/
ref: ReactRef<HTMLDivElement | null>;
/**
* The card variant style.
* @default "shadow"
*/
variant?: "shadow" | "flat" | "bordered";
/**
* The border weight of the `bordered` variant card.
* @default "normal"
*/
borderWeight?: NormalWeights;
/**
* Whether the card should allow users to interact with the card.
* @default false
*/
isPressable?: boolean;
/**
* Whether the card can be hovered by the user.
* @default false
*/
isHoverable?: boolean;
/**
* Whether the card should show a ripple animation on press
* @default false
*/
disableRipple?: boolean;
// @deprecated - use `onPress` instead
onClick?: React.MouseEventHandler<HTMLDivElement>;
/**
* Whether the card is animated.
* @default false
*/
disableAnimation?: boolean;
/**
* Whether the card should allow text selection on press. (only for pressable cards)
* @default true
*/
allowTextSelectionOnPress?: boolean;
}
export function useCard(props: UseCardProps) {
const {
ref,
children,
disableAnimation = false,
disableRipple = false,
variant = "shadow",
isHoverable = false,
borderWeight = "light",
isPressable = false,
onClick: deprecatedOnClick,
onPress,
autoFocus,
className,
allowTextSelectionOnPress = true,
...otherProps
} = props;
const cardRef = useDOMRef<HTMLDivElement>(ref);
const {onClick: onDripClickHandler, ...dripBindings} = useDrip(false, cardRef);
const handleDrip = (e: MouseEvent<HTMLDivElement> | PressEvent | Event) => {
if (!disableAnimation && !disableRipple && cardRef.current) {
onDripClickHandler(e);
}
};
const handlePress = (e: PressEvent) => {
if (e.pointerType === "keyboard" || e.pointerType === "virtual") {
handleDrip(e);
} else if (typeof window !== "undefined" && window.event) {
handleDrip(window.event);
}
if (deprecatedOnClick) {
deprecatedOnClick(e as any);
console.warn("onClick is deprecated, please use onPress");
}
onPress?.(e);
};
const {isPressed, pressProps} = usePress({
isDisabled: !isPressable,
onPress: handlePress,
allowTextSelectionOnPress,
...otherProps,
});
const {hoverProps, isHovered} = useHover({
isDisabled: !isHoverable,
...otherProps,
});
const {isFocusVisible, focusProps}: IFocusRingAria<Pick<UseCardProps, "css">> = useFocusRing({
autoFocus,
});
const getCardProps = useCallback(
(props = {}) => {
return {
...mergeProps(
isPressable ? {...pressProps, ...focusProps} : {},
isHoverable ? hoverProps : {},
otherProps,
props,
),
};
},
[isPressable, isHoverable, pressProps, focusProps, hoverProps, otherProps],
);
return {
cardRef,
variant,
children,
borderWeight,
isPressable,
isHovered,
isPressed,
disableAnimation,
disableRipple,
dripBindings,
onDripClickHandler,
isFocusVisible,
className,
getCardProps,
};
}
export type UseCardReturn = ReturnType<typeof useCard>;

Binary file not shown.

After

Width:  |  Height:  |  Size: 13 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 14 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 13 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 13 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 668 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 29 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 13 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 54 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 451 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 283 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 24 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 170 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 37 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 20 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.6 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 22 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 14 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 147 KiB

View File

@ -0,0 +1,668 @@
import React from "react";
import {styled} from "@nextui-org/system";
import {Meta} from "@storybook/react";
import {Text} from "@nextui-org/text";
import {Grid} from "@nextui-org/grid";
import {Link} from "@nextui-org/link";
import {Col} from "@nextui-org/col";
import {Row} from "@nextui-org/row";
import {Code} from "@nextui-org/code";
import {Card} from "../src";
export default {
title: "General/Card",
component: Card,
} as Meta;
export const Default = () => (
<Card css={{w: "400px"}}>
<Card.Body css={{py: "$lg"}}>
<Text>A basic card</Text>
</Card.Body>
</Card>
);
export const Hoverable = () => (
<Card isHoverable css={{w: "400px"}} variant="bordered">
<Card.Body css={{py: "$lg"}}>
<Text>A basic card</Text>
</Card.Body>
</Card>
);
export const Variants = () => (
<Grid.Container gap={2}>
<Grid xs={4}>
<Card>
<Card.Body>
<Text>Default card. (shadow)</Text>
</Card.Body>
</Card>
</Grid>
<Grid xs={4}>
<Card variant="flat">
<Card.Body>
<Text>Flat card.</Text>
</Card.Body>
</Card>
</Grid>
<Grid xs={4}>
<Card variant="bordered">
<Card.Body>
<Text>Bordered card.</Text>
</Card.Body>
</Card>
</Grid>
</Grid.Container>
);
export const WithFooter = () => (
<Grid.Container gap={2}>
<Grid xs={4}>
<Card css={{p: "$6"}}>
<Card.Header>
<img
alt="nextui logo"
height="34px"
src="https://avatars.githubusercontent.com/u/86160567?s=200&v=4"
width="34px"
/>
<Grid.Container css={{pl: "$6"}}>
<Grid xs={12}>
<Text h4 css={{lineHeight: "$xs"}}>
Next UI
</Text>
</Grid>
<Grid xs={12}>
<Text css={{color: "$accents8"}}>nextui.org</Text>
</Grid>
</Grid.Container>
</Card.Header>
<Card.Body css={{py: "$2"}}>
<Text>Make beautiful websites regardless of your design experience.</Text>
</Card.Body>
<Card.Footer>
<Link
isExternal
color="primary"
href="https://github.com/nextui-org/nextui"
target="_blank"
>
Visit source code on GitHub.
</Link>
</Card.Footer>
</Card>
</Grid>
</Grid.Container>
);
export const AbsImageWithHeader = () => {
return (
<Grid.Container gap={1} justify="center">
<Grid>
<Card css={{w: "330px"}}>
<Card.Header css={{position: "absolute", top: 5, zIndex: 1}}>
<Col>
<Text color="#ffffffAA" size={12} transform="uppercase" weight="bold">
What to watch
</Text>
<Text h3 color="white">
Stream the Apple event
</Text>
</Col>
</Card.Header>
<Card.Image
alt="Apple event background"
autoResize={false}
height={440}
src={require("./assets/apple-event.jpeg")}
style={{objectFit: "cover"}}
width="100%"
/>
</Card>
</Grid>
</Grid.Container>
);
};
export const AbsImgWithHeaderFooter = () => {
return (
<Grid.Container gap={2} justify="center">
<Grid>
<Card css={{w: "330px", bg: "$gray50"}}>
<Card.Header css={{position: "absolute", top: 5, zIndex: 1}}>
<Col>
<Text color="#9E9E9E" size={12} transform="uppercase" weight="bold">
New
</Text>
<Text h2 color="black">
HomePod mini
</Text>
<Text size={14} style={{paddingRight: "10px"}}>
Room-filling sound, Intelligent assistant. Smart home control. Works seamlessly with
iPhone. Check it out
</Text>
</Col>
</Card.Header>
<Card.Image
alt="Apple homedpods background"
autoResize={false}
height={440}
src={require("./assets/homepod.jpeg")}
style={{objectFit: "cover", paddingTop: "100px"}}
width="100%"
/>
<Card.Footer css={{m: 0}}>
<Row>
<Col>
<Text size={12}>Available soon.</Text>
<Text size={12}>Get notified.</Text>
</Col>
<Col>
<Row justify="flex-end">
<Text size={12} transform="uppercase" weight="bold">
Notify Me
</Text>
</Row>
</Col>
</Row>
</Card.Footer>
</Card>
</Grid>
<Grid>
<Card css={{w: "630px"}}>
<Card.Header css={{position: "absolute", top: 5, zIndex: 1}}>
<Col>
<Text color="#9E9E9E" size={12} transform="uppercase" weight="bold">
Your day your way
</Text>
<Text h3 color="white">
Your checklist for better sleep
</Text>
</Col>
</Card.Header>
<Card.Image
alt="Apple homedpods background"
autoResize={false}
height={440}
src={require("./assets/relaxing.jpeg")}
style={{objectFit: "cover"}}
width="100%"
/>
<Card.Footer
isBlurred
css={{
position: "absolute",
bgBlur: "#0f111466",
borderTop: "$borderWeights$light solid $gray500",
bottom: 0,
zIndex: 1,
}}
>
<Row>
<Col>
<Row>
<Col span={3}>
<Card.Image
alt="Breathing app icon"
autoResize={false}
height={40}
src={require("./assets/breathing-app-icon.jpeg")}
style={{background: "black"}}
width={40}
/>
</Col>
<Col>
<Text color="#d1d1d1" size={12}>
Breathing App
</Text>
<Text color="#d1d1d1" size={12}>
Get a good night&apos;s sleep.
</Text>
</Col>
</Row>
</Col>
<Col>
<Row justify="flex-end">
<Text size={12} transform="uppercase" weight="bold">
Get App
</Text>
</Row>
</Col>
</Row>
</Card.Footer>
</Card>
</Grid>
</Grid.Container>
);
};
export const CoverImage = () => (
<Grid.Container gap={2} justify="center">
<Grid sm={4} xs={12}>
<Card>
<Card.Header css={{position: "absolute", zIndex: 1, top: 5}}>
<Col>
<Text color="#ffffffAA" size={12} transform="uppercase" weight="bold">
What to watch
</Text>
<Text h4 color="white">
Stream the Acme event
</Text>
</Col>
</Card.Header>
<Card.Image
alt="Card image background"
height={340}
objectFit="cover"
src="https://nextui.org/images/card-example-4.jpeg"
width="100%"
/>
</Card>
</Grid>
<Grid sm={4} xs={12}>
<Card css={{w: "100%"}}>
<Card.Header css={{position: "absolute", zIndex: 1, top: 5}}>
<Col>
<Text color="#ffffffAA" size={12} transform="uppercase" weight="bold">
Plant a tree
</Text>
<Text h4 color="white">
Contribute to the planet
</Text>
</Col>
</Card.Header>
<Card.Image
alt="Card image background"
height={340}
objectFit="cover"
src="https://nextui.org/images/card-example-3.jpeg"
width="100%"
/>
</Card>
</Grid>
<Grid sm={4} xs={12}>
<Card css={{bg: "$black", w: "100%"}}>
<Card.Header css={{position: "absolute", zIndex: 1, top: 5}}>
<Col>
<Text color="#ffffffAA" size={12} transform="uppercase" weight="bold">
Supercharged
</Text>
<Text h4 color="white">
Creates beauty like a beast
</Text>
</Col>
</Card.Header>
<Card.Image
alt="Card image background"
height={340}
objectFit="cover"
src="https://nextui.org/images/card-example-2.jpeg"
width="100%"
/>
</Card>
</Grid>
<Grid sm={5} xs={12}>
<Card css={{w: "100%", h: "400px"}}>
<Card.Header css={{position: "absolute", zIndex: 1, top: 5}}>
<Col>
<Text color="#ffffffAA" size={12} transform="uppercase" weight="bold">
New
</Text>
<Text h3 color="black">
Acme camera
</Text>
</Col>
</Card.Header>
<Card.Body css={{p: 0}}>
<Card.Image
alt="Card example background"
height="100%"
objectFit="cover"
src="https://nextui.org/images/card-example-6.jpeg"
width="100%"
/>
</Card.Body>
<Card.Footer
isBlurred
css={{
position: "absolute",
bgBlur: "#ffffff66",
borderTop: "$borderWeights$light solid rgba(255, 255, 255, 0.2)",
bottom: 0,
zIndex: 1,
}}
>
<Row>
<Col>
<Text color="#000" size={12}>
Available soon.
</Text>
<Text color="#000" size={12}>
Get notified.
</Text>
</Col>
<Col>
<Row justify="flex-end">
<Text css={{color: "inherit"}} size={12} transform="uppercase" weight="bold">
Notify Me
</Text>
</Row>
</Col>
</Row>
</Card.Footer>
</Card>
</Grid>
<Grid sm={7} xs={12}>
<Card css={{w: "100%", h: "400px"}}>
<Card.Header css={{position: "absolute", zIndex: 1, top: 5}}>
<Col>
<Text color="#9E9E9E" size={12} transform="uppercase" weight="bold">
Your day your way
</Text>
<Text h3 color="white">
Your checklist for better sleep
</Text>
</Col>
</Card.Header>
<Card.Body css={{p: 0}}>
<Card.Image
alt="Relaxing app background"
height="100%"
objectFit="cover"
src="https://nextui.org/images/card-example-5.jpeg"
width="100%"
/>
</Card.Body>
<Card.Footer
isBlurred
css={{
position: "absolute",
bgBlur: "#0f111466",
borderTop: "$borderWeights$light solid $gray700",
bottom: 0,
zIndex: 1,
}}
>
<Row>
<Col>
<Row>
<Col span={3}>
<Card.Image
alt="Breathing app icon"
css={{bg: "black", br: "50%"}}
height={40}
src="https://nextui.org/images/breathing-app-icon.jpeg"
width={40}
/>
</Col>
<Col>
<Text color="#d1d1d1" size={12}>
Breathing App
</Text>
<Text color="#d1d1d1" size={12}>
Get a good night&apos;s sleep.
</Text>
</Col>
</Row>
</Col>
<Col>
<Row justify="flex-end">
<Text css={{color: "inherit"}} size={12} transform="uppercase" weight="bold">
Get App
</Text>
</Row>
</Col>
</Row>
</Card.Footer>
</Card>
</Grid>
</Grid.Container>
);
export const PrimaryAction = () => {
const list = [
{
title: "Orange",
img: "/images/fruit-1.jpeg",
price: "$5.50",
},
{
title: "Tangerine",
img: "/images/fruit-2.jpeg",
price: "$3.00",
},
{
title: "Raspberry",
img: "/images/fruit-3.jpeg",
price: "$10.00",
},
{
title: "Lemon",
img: "/images/fruit-4.jpeg",
price: "$5.30",
},
{
title: "Advocato",
img: "/images/fruit-5.jpeg",
price: "$15.70",
},
{
title: "Lemon 2",
img: "/images/fruit-6.jpeg",
price: "$8.00",
},
{
title: "Banana",
img: "/images/fruit-7.jpeg",
price: "$7.50",
},
{
title: "Watermelon",
img: "/images/fruit-8.jpeg",
price: "$12.20",
},
];
return (
<Grid.Container gap={2} justify="flex-start">
{list.map((item, index) => (
<Grid key={index} sm={3} xs={6}>
<Card isPressable onPress={() => console.log("item pressed", item)}>
<Card.Body css={{p: 0}}>
<Card.Image
alt={item.title}
height={140}
objectFit="cover"
src={"https://nextui.org" + item.img}
width="100%"
/>
</Card.Body>
<Card.Footer css={{justifyItems: "flex-start"}}>
<Row justify="space-between" wrap="wrap">
<Text b>{item.title}</Text>
<Text css={{color: "$accents7", fontWeight: "$semibold"}}>{item.price}</Text>
</Row>
</Card.Footer>
</Card>
</Grid>
))}
</Grid.Container>
);
};
export const CenterImgWithHeader = () => {
const list = [
{
title: "Mac",
img: require("./assets/mac.png"),
},
{
title: "iPhone",
img: require("./assets/iphone.png"),
},
{
title: "iPad",
img: require("./assets/ipad.png"),
},
{
title: "Apple Watch",
img: require("./assets/apple-watch.png"),
},
{
title: "AirPods",
img: require("./assets/airpods.png"),
},
{
title: "AirTag",
img: require("./assets/airtag.png"),
},
{
title: "Apple TV",
img: require("./assets/appletv.png"),
},
{
title: "HomePod mini",
img: require("./assets/homepod-mini.png"),
},
{
title: "Accessories",
img: require("./assets/accessories.png"),
},
];
return (
<Grid.Container gap={2} justify="center">
{list.map((item, index) => (
<Grid key={index}>
<Card isHoverable isPressable css={{w: "200px", h: "220px"}}>
<Card.Header css={{p: 0}}>
<Text h5 style={{paddingLeft: "24px", paddingTop: "10px"}}>
{item.title}
</Text>
</Card.Header>
<Card.Body css={{h: "100%", jc: "center"}}>
<Card.Image alt={item.title} autoResize={false} src={item.img} width={180} />
</Card.Body>
</Card>
</Grid>
))}
</Grid.Container>
);
};
export const WithDivider = () => (
<Card css={{w: "400px"}}>
<Card.Header>
<Text b>Description</Text>
</Card.Header>
<Card.Divider />
<Card.Body>
<Text>The Object constructor creates an object wrapper for the given value.</Text>
</Card.Body>
<Card.Divider />
<Card.Footer>
<Text>
When called in a non-constructor context, Object behaves identically to{" "}
<Code>new Object()</Code>.
</Text>
</Card.Footer>
</Card>
);
export const Shadows = () => {
const Box = styled("div", {
size: "120px",
dflex: "center",
bg: "$backgroundContrast",
br: "$md",
});
const shadows = ["$xs", "$sm", "$md", "$lg", "$xl"];
return (
<Grid.Container gap={3} justify="center">
<Grid justify="center" xs={12}>
<Text b>Drop shadows</Text>
</Grid>
{shadows.map((shadow, index) => (
<Grid key={`${shadow}_${index}`} sm={2} xs={6}>
<Box css={{dropShadow: shadow}}>
<Text>Shadow: {shadow}</Text>
</Box>
</Grid>
))}
<Grid justify="center" xs={12}>
<Text b>Box shadows</Text>
</Grid>
{shadows.map((shadow, index) => (
<Grid key={`${shadow}_${index}`} sm={2} xs={6}>
<Box css={{boxShadow: shadow}}>
<Text>Shadow: {shadow}</Text>
</Box>
</Grid>
))}
</Grid.Container>
);
};
//TODO: Input & Button still missing
// export const withForm = () => {
// return (
// <Card css={{mw: "400px"}}>
// <Card.Header css={{justifyContent: "center"}}>
// <Text size={18}>
// Welcome to&nbsp;
// <Text b size={18}>
// NextUI
// </Text>
// </Text>
// </Card.Header>
// <Card.Body css={{px: "$10", pt: "$1", ov: "visible"}}>
// <Input
// bordered
// clearable
// fullWidth
// color="primary"
// contentLeft={<Mail fill="currentColor" />}
// placeholder="Email"
// size="lg"
// />
// <Spacer y={0.5} />
// <Input
// bordered
// clearable
// fullWidth
// color="primary"
// contentLeft={<Password />}
// placeholder="Password"
// size="lg"
// />
// <Spacer y={0.5} />
// <Row align="center" justify="space-between">
// <Checkbox>
// <Text css={{color: "$accents8"}} size={14}>
// Remember me
// </Text>
// </Checkbox>
// <Link css={{color: "$link", fontSize: "$sm"}} href="#">
// Forgot password?
// </Link>
// </Row>
// </Card.Body>
// <Card.Footer css={{pt: 0}}>
// <Grid.Container gap={1} justify="flex-end">
// <Grid>
// <Button auto flat>
// Sign Up
// </Button>
// </Grid>
// <Grid>
// <Button auto>Login</Button>
// </Grid>
// </Grid.Container>
// </Card.Footer>
// </Card>
// );
// };

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

@ -0,0 +1,24 @@
# @nextui-org/drip
A Quick description of the component
> This is an internal utility, not intended for public usage.
## Installation
```sh
yarn add @nextui-org/drip
# or
npm i @nextui-org/drip
```
## 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 { Drip } from "../src";
describe("Drip", () => {
it("should render correctly", () => {
const wrapper = render(<Drip />);
expect(() => wrapper.unmount()).not.toThrow();
});
it("ref should be forwarded", () => {
const ref = React.createRef<HTMLDivElement>();
render(<Drip 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,48 @@
{
"name": "@nextui-org/drip",
"version": "1.0.0-beta.11",
"description": "A ripple effect for ensuring that the user fells the system is reacting instantaneously",
"keywords": [
"drip"
],
"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/drip"
},
"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,18 @@
import {keyframes} from "@nextui-org/system";
export const expand = keyframes({
"0%": {
opacity: 0,
transform: "scale(0.25)",
},
"30%": {
opacity: 1,
},
"80%": {
opacity: 0.5,
},
"100%": {
transform: "scale(28)",
opacity: 0,
},
});

View File

@ -0,0 +1,18 @@
import {styled} from "@nextui-org/system";
import {expand} from "./drip.animations";
export const StyledDrip = styled("div", {
position: "absolute",
left: 0,
right: 0,
top: 0,
bottom: 0,
"& svg": {
position: "absolute",
animation: `350ms linear ${expand}`,
animationFillMode: "forwards",
width: "$md",
height: "$md",
},
});

View File

@ -0,0 +1,57 @@
import {useEffect} from "react";
import {NextUI, forwardRef, HTMLNextUIProps} from "@nextui-org/system";
import {useDOMRef} from "@nextui-org/dom-utils";
import {clsx, __DEV__} from "@nextui-org/shared-utils";
import {StyledDrip} from "./drip.styles";
export interface DripProps extends HTMLNextUIProps<"div"> {
isVisible?: boolean;
x: number;
y: number;
color?: string;
onCompleted: () => void;
}
const Drip = forwardRef<DripProps, "div">((props, ref) => {
const {isVisible, x, y, color, onCompleted, className, ...otherProps} = props;
const domRef = useDOMRef(ref);
const top = Number.isNaN(+y) ? 0 : y - 10;
const left = Number.isNaN(+x) ? 0 : x - 10;
useEffect(() => {
let drip = domRef.current;
if (!drip) return;
drip.addEventListener("animationend", onCompleted);
return () => {
if (!drip) return;
drip.removeEventListener("animationend", onCompleted);
};
});
if (!isVisible) return null;
return (
<StyledDrip ref={domRef} className={clsx("nextui-drip", className)} {...otherProps}>
<NextUI.Svg css={{top, left}} height="20" viewBox="0 0 20 20" width="20">
<g fill="none" fillRule="evenodd" stroke="none" strokeWidth="1">
<g className="nextui-drip-filler" fill={color}>
<rect height="100%" rx="10" width="100%" />
</g>
</g>
</NextUI.Svg>
</StyledDrip>
);
});
if (__DEV__) {
Drip.displayName = "NextUI.Drip";
}
Drip.toString = () => ".nextui-drip";
export default Drip;

View File

@ -0,0 +1,8 @@
// export hook
export {useDrip} from "./use-drip";
// export types
export type {DripProps} from "./drip";
// export component
export {default as Drip} from "./drip";

View File

@ -0,0 +1,37 @@
import {PressEvent} from "@react-types/shared";
import {MouseEvent, RefObject, useState} from "react";
export function useDrip(initialValue = false, ref: RefObject<HTMLElement>) {
const [isVisible, setIsVisible] = useState(initialValue);
const [dripX, setDripX] = useState(0);
const [dripY, setDripY] = useState(0);
const onCompleted = () => {
setIsVisible(false);
setDripX(0);
setDripY(0);
};
const onClick = (event: MouseEvent<HTMLElement> | PressEvent | Event) => {
if (!ref?.current) return;
const rect = ref.current.getBoundingClientRect?.();
if (!rect) return;
setIsVisible(true);
if (typeof event === "object" && "clientX" in event) {
setDripX(event.clientX - rect.left);
setDripY(event.clientY - rect.top);
}
};
return {
isVisible,
x: dripX,
y: dripY,
onClick,
onCompleted,
};
}
export type UseDripReturn = ReturnType<typeof useDrip>;

View File

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

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

@ -0,0 +1,24 @@
# @nextui-org/image
A Quick description of the component
> This is an internal utility, not intended for public usage.
## Installation
```sh
yarn add @nextui-org/image
# or
npm i @nextui-org/image
```
## 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,51 @@
import * as React from "react";
import {act, render, waitFor} from "@testing-library/react";
import {Image} from "../src";
const url =
"data:image/png;base64, iVBORw0KGgoAAAANSUhEUgAAAAUA" +
"AAAFCAYAAACNbyblAAAAHElEQVQI12P4//8/w38GIAXDIBKE0DHxgljNBAAO" +
"9TXL0Y4OHwAAAABJRU5ErkJggg==";
describe("Image", () => {
it("should render correctly", () => {
const wrapper = render(<Image src={url} />);
expect(() => wrapper.unmount()).not.toThrow();
});
it("ref should be forwarded", () => {
const ref = React.createRef<HTMLImageElement>();
render(<Image ref={ref} src={url} />);
expect(ref.current).not.toBeNull();
});
it("should work correctly with skeleton", async () => {
const {container} = render(<Image showSkeleton src={url} />);
expect(container.querySelector(".nextui-image-skeleton")).not.toBeNull();
});
it("should remove skeleton when timeout", async () => {
Object.defineProperty((global as any).Image.prototype, "complete", {
get() {
return true;
},
});
const {container} = render(<Image maxDelay={100} src={url} />);
const img = container.querySelector("img");
// simulate img load
act(() => {
img?.dispatchEvent(new Event("load"));
});
await waitFor(() => {
expect(container.querySelector(".nextui-image-skeleton")).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,52 @@
{
"name": "@nextui-org/image",
"version": "1.0.0-beta.11",
"description": "The Image component is used to display images with support for fallback.",
"keywords": [
"image"
],
"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/image"
},
"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:*",
"@nextui-org/use-ref-state": "workspace:*",
"@nextui-org/use-real-shape": "workspace:*",
"@nextui-org/use-resize": "workspace:*"
},
"devDependencies": {
"@nextui-org/grid": "workspace:*",
"clean-package": "2.1.1",
"react": "^17.0.2"
}
}

View File

@ -0,0 +1,39 @@
import {forwardRef, HTMLNextUIProps} from "@nextui-org/system";
import {useDOMRef} from "@nextui-org/dom-utils";
import {clsx, __DEV__} from "@nextui-org/shared-utils";
import {StyledImageSkeleton} from "./image.styles";
export interface ImageSkeletonProps extends HTMLNextUIProps<"div"> {
/**
* The skeleton opacity - between 0 and 1.
* @default 0.5
*/
opacity?: number;
}
const ImageSkeleton = forwardRef<ImageSkeletonProps, "div">((props, ref) => {
const {opacity, css, className, ...otherProps} = props;
const domRef = useDOMRef(ref);
return (
<StyledImageSkeleton
ref={domRef}
className={clsx("nextui-image-skeleton", className)}
css={{
opacity,
...css,
}}
{...otherProps}
/>
);
});
if (__DEV__) {
ImageSkeleton.displayName = "NextUI.ImageSkeleton";
}
ImageSkeleton.toString = () => ".nextui-image-skeleton";
export default ImageSkeleton;

View File

@ -0,0 +1,10 @@
import {keyframes} from "@nextui-org/system";
export const loading = keyframes({
"0%": {
backgroundPosition: "200% 0",
},
to: {
backgroundPosition: "-200% 0",
},
});

View File

@ -0,0 +1,45 @@
import {styled} from "@nextui-org/system";
import {loading} from "./image.animations";
export const StyledImage = styled("img", {
size: "100%",
display: "block",
});
export const StyledImageContainer = styled("div", {
opacity: 0,
margin: "0 auto",
position: "relative",
overflow: "hidden",
maxWidth: "100%",
transition: "transform 250ms ease 0ms, opacity 200ms ease-in 0ms",
"@motion": {
transition: "none",
},
variants: {
isLoaded: {
true: {
opacity: 1,
},
false: {
opacity: 0,
},
},
},
});
export const StyledImageSkeleton = styled("div", {
position: "absolute",
top: 0,
left: 0,
right: 0,
bottom: 0,
size: "100%",
borderRadius: "inherit",
backgroundImage:
"linear-gradient(270deg, $colors$accents1, $colors$accents2, $colors$accents2, $colors$accents1)",
backgroundSize: "400% 100%",
animation: `${loading} 5s ease-in-out infinite`,
transition: "opacity 300ms ease-out",
});

View File

@ -0,0 +1,69 @@
import {forwardRef} from "@nextui-org/system";
import {clsx, __DEV__} from "@nextui-org/shared-utils";
import ImageSkeleton from "./image-skeleton";
import {StyledImage, StyledImageContainer} from "./image.styles";
import {UseImageProps, useImage} from "./use-image";
export interface ImageProps extends Omit<UseImageProps, "ref"> {}
const Image = forwardRef<ImageProps, "img">((props, ref) => {
const {
w,
css,
src,
width,
height,
imageRef,
objectFit,
isLoading,
showSkeleton,
zoomHeight,
state,
className,
containerCss,
onLoad,
...otherProps
} = useImage({
ref,
...props,
});
return (
<StyledImageContainer
className={clsx("nextui-image-container", className)}
css={{
width: w,
height: zoomHeight,
...containerCss,
}}
data-state={state}
isLoaded={!isLoading || showSkeleton}
>
{showSkeleton && <ImageSkeleton opacity={1} />}
<StyledImage
ref={imageRef}
alt={props.alt || ""}
className="nextui-image"
css={{
objectFit,
...css,
}}
data-state={state}
height={height}
src={src}
width={width}
onLoad={onLoad}
{...otherProps}
/>
</StyledImageContainer>
);
});
if (__DEV__) {
Image.displayName = "NextUI.Image";
}
Image.toString = () => ".nextui-image";
export default Image;

View File

@ -0,0 +1,5 @@
// export types
export type {ImageProps} from "./image";
// export component
export {default as Image} from "./image";

View File

@ -0,0 +1,153 @@
import {useState, useEffect, useMemo} from "react";
import {HTMLNextUIProps, CSS} from "@nextui-org/system";
import {useDOMRef} from "@nextui-org/dom-utils";
import {useRefState} from "@nextui-org/use-ref-state";
import {useRealShape} from "@nextui-org/use-real-shape";
import {useResize} from "@nextui-org/use-resize";
export interface UseImageProps extends Omit<HTMLNextUIProps<"img">, "height" | "width"> {
/**
* The image ref.
*/
ref?: React.Ref<HTMLImageElement>;
/**
* The image source (local or remote)
*/
src: string;
/**
* Resize Image to fits screen width
* @default false
*/
autoResize?: boolean;
/**
* Shows loading Skeleton while image is loading
* @default true
*/
showSkeleton?: boolean;
/**
* Specifies Image width
*/
width?: number | string;
/**
* Specifies Image height
*/
height?: number | string;
/**
* Specifies how long Image Skeleton Renders Animation
* @default 3000
*/
maxDelay?: number;
/**
* Property tells the content to fill the container
*/
objectFit?: CSS["objectFit"];
/**
* Override default Image xontainer styles
*/
containerCss?: CSS;
/**
* Function to be called when the image is loaded
*/
onLoad?: () => void;
}
export function useImage(props: UseImageProps) {
const {
ref,
width,
height,
showSkeleton: showSkeletonProp = true,
maxDelay = 3000,
autoResize = false,
objectFit = "scale-down",
onLoad: onLoadProp,
...otherProps
} = props;
const imageRef = useDOMRef(ref);
const [isLoading, setIsLoading] = useState(true);
const [showSkeleton, setShowSkeleton] = useState(showSkeletonProp);
const {w, h} = useMemo(() => {
return {
w: width ? (typeof width === "number" ? `${width}px` : width) : "auto",
h: height ? (typeof height === "number" ? `${height}px` : height) : "auto",
};
}, [width, height]);
const [zoomHeight, setZoomHeight, zoomHeightRef] = useRefState<string>(h);
const [shape, updateShape] = useRealShape(imageRef);
const showAnimation = showSkeletonProp && !!width && !!height;
const onLoad = () => {
setIsLoading(false);
onLoadProp?.();
};
useEffect(() => {
if (!imageRef.current) return;
if (imageRef.current.complete) {
setIsLoading(false);
setShowSkeleton(false);
}
});
useEffect(() => {
const timer = setTimeout(() => {
if (showAnimation) {
setShowSkeleton(false);
}
clearTimeout(timer);
}, maxDelay);
return () => clearTimeout(timer);
}, [isLoading]);
/**
* On mobile devices, the render witdth may be less than CSS width value.
* If the image is scaled, set the height manually.
* This is to ensure the aspect ratio of the image.
*
* If the image is auto width, ignore all.
*/
useEffect(() => {
if (!autoResize) return;
const notLoaded = shape.width === 0;
const isAutoZoom = zoomHeightRef.current === "auto";
if (notLoaded || !width || !height) return;
if (shape.width < width) {
!isAutoZoom && setZoomHeight("auto");
} else {
isAutoZoom && setZoomHeight(h);
}
}, [shape, width]);
useResize(() => {
if (!autoResize) return;
updateShape();
});
const state = useMemo(() => {
return isLoading ? "loading" : "loaded";
}, [isLoading]);
return {
w,
state,
width,
height,
imageRef,
objectFit,
zoomHeight,
isLoading,
showSkeleton,
onLoad,
...otherProps,
};
}
export type UseImageReturn = ReturnType<typeof useImage>;

View File

@ -0,0 +1,68 @@
import React from "react";
import {Meta} from "@storybook/react";
import {Grid} from "@nextui-org/grid";
import {Image} from "../src";
export default {
title: "Display/Image",
component: Image,
} as Meta;
export const Default = () => (
<Image
alt="Default Image"
height={180}
objectFit="cover"
src="https://github.com/nextui-org/nextui/blob/next/apps/docs/public/nextui-banner.jpeg?raw=true"
width={320}
/>
);
export const Sizes = () => (
<Grid.Container gap={2}>
<Grid>
<Image alt="Default Image" height={50} src="http://placehold.jp/50x50.png" width={50} />
</Grid>
<Grid>
<Image alt="Default Image" height={100} src="http://placehold.jp/100x100.png" width={100} />
</Grid>
<Grid>
<Image alt="Default Image" height={150} src="http://placehold.jp/150x150.png" width={150} />
</Grid>
</Grid.Container>
);
export const Skeleton = () => (
<Image
showSkeleton
alt="Default Image"
height={180}
maxDelay={5000}
src="http://www.deelay.me/5000/https://github.com/nextui-org/nextui/blob/next/apps/docs/public/nextui-banner.jpeg?raw=true"
width={320}
/>
);
export const ObjectFit = () => (
<Grid.Container gap={2}>
<Grid>
<Image
alt="Default Image"
height={180}
objectFit="contain"
src="https://github.com/nextui-org/nextui/blob/next/apps/docs/public/nextui-banner.jpeg?raw=true"
width={320}
/>
</Grid>
<Grid>
<Image
alt="Default Image"
height={180}
objectFit="cover"
src="https://github.com/nextui-org/nextui/blob/next/apps/docs/public/nextui-banner.jpeg?raw=true"
width={320}
/>
</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

@ -0,0 +1,24 @@
# @nextui-org/use-real-shape
A Quick description of the component
> This is an internal utility, not intended for public usage.
## Installation
```sh
yarn add @nextui-org/use-real-shape
# or
npm i @nextui-org/use-real-shape
```
## 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,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,46 @@
{
"name": "@nextui-org/use-real-shape",
"version": "1.0.0-beta.11",
"description": "Hook that returns the real dimensions of an element",
"keywords": [
"use-real-shape"
],
"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/hooks/use-real-shape"
},
"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"
},
"dependencies": {
"@nextui-org/dom-utils": "workspace:*"
},
"peerDependencies": {
"react": ">=16.8.0"
},
"devDependencies": {
"clean-package": "2.1.1",
"react": "^17.0.2"
}
}

View File

@ -0,0 +1,24 @@
import {RefObject, useState, useEffect} from "react";
import {ShapeType, getRealShape} from "@nextui-org/dom-utils";
export type ShapeResult = [ShapeType, () => void];
export function useRealShape<T extends HTMLElement>(ref: RefObject<T | null>) {
const [shape, setState] = useState<ShapeType>({
width: 0,
height: 0,
});
const updateShape = () => {
if (!ref?.current) return;
const {width, height} = getRealShape(ref.current);
setState({width, height});
};
useEffect(() => updateShape(), [ref.current]);
return [shape, updateShape] as ShapeResult;
}
export type UseRealShapeReturn = ReturnType<typeof useRealShape>;

View File

@ -0,0 +1,4 @@
{
"extends": "../../../tsconfig.json",
"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

@ -0,0 +1,24 @@
# @nextui-org/use-ref-state
A Quick description of the component
> This is an internal utility, not intended for public usage.
## Installation
```sh
yarn add @nextui-org/use-ref-state
# or
npm i @nextui-org/use-ref-state
```
## 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,61 @@
import * as React from "react";
import {renderHook, act} from "@testing-library/react-hooks";
import {render} from "@testing-library/react";
import {useRefState} from "../src";
describe("useRefState", () => {
it("should work correctly", () => {
const {result} = renderHook(() => useRefState(""));
expect(result.current[0]).toEqual("");
act(() => result.current[1]("test"));
expect(result.current[0]).toEqual("test");
expect(result.current[2].current).toEqual("test");
});
it("functional initial mode should be supported", () => {
const {result} = renderHook(() => useRefState(() => "test"));
expect(result.current[0]).toEqual("test");
expect(result.current[2].current).toEqual("test");
});
it("functional update mode should be supported", () => {
const {result} = renderHook(() => useRefState(""));
expect(result.current[0]).toEqual("");
act(() => result.current[1]((value) => value + "test"));
expect(result.current[0]).toEqual("test");
expect(result.current[2].current).toEqual("test");
});
it("only the ref should track latest value", () => {
const Mock: React.FC<unknown> = () => {
const [state, setState, stateRef] = useRefState("");
React.useEffect(() => {
return () => {
setTimeout(() => {
expect(state).not.toEqual("test2");
expect(stateRef.current).toEqual("test2");
}, 0);
};
}, []);
React.useEffect(() => {
setState("test");
setState("test2");
}, []);
return <span />;
};
const wrapper = render(<Mock />);
expect(() => wrapper.unmount()).not.toThrow();
});
});

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,43 @@
{
"name": "@nextui-org/use-ref-state",
"version": "1.0.0-beta.11",
"description": "Hook for saving the state in a ref value",
"keywords": [
"use-ref-state"
],
"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/hooks/use-ref-state"
},
"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"
},
"devDependencies": {
"clean-package": "2.1.1",
"react": "^17.0.2"
}
}

View File

@ -0,0 +1,28 @@
import {Dispatch, MutableRefObject, SetStateAction, useEffect, useRef, useState} from "react";
export type CurrentStateType<S> = [S, Dispatch<SetStateAction<S>>, MutableRefObject<S>];
export interface UseUseRefStateProps {}
export function useRefState<S>(initialState: S | (() => S)) {
const [state, setState] = useState<S>(() => {
return typeof initialState === "function" ? (initialState as () => S)() : initialState;
});
const ref = useRef<S>(initialState as S);
useEffect(() => {
ref.current = state;
}, [state]);
const setValue = (val: SetStateAction<S>) => {
const result = typeof val === "function" ? (val as (prevState: S) => S)(ref.current) : val;
ref.current = result;
setState(result);
};
return [state, setValue, ref] as CurrentStateType<S>;
}
export type UseRefStateReturn = ReturnType<typeof useRefState>;

View File

@ -0,0 +1,4 @@
{
"extends": "../../../tsconfig.json",
"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

@ -0,0 +1,24 @@
# @nextui-org/use-resize
A Quick description of the component
> This is an internal utility, not intended for public usage.
## Installation
```sh
yarn add @nextui-org/use-resize
# or
npm i @nextui-org/use-resize
```
## 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,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,43 @@
{
"name": "@nextui-org/use-resize",
"version": "1.0.0-beta.11",
"description": "Hook that adds an event listener to the resize window event",
"keywords": [
"use-resize"
],
"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/hooks/use-resize"
},
"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"
},
"devDependencies": {
"clean-package": "2.1.1",
"react": "^17.0.2"
}
}

View File

@ -0,0 +1,14 @@
import {useEffect} from "react";
export function useResize(callback: Function, immediatelyInvoke: boolean = true) {
useEffect(() => {
const fn = () => callback();
if (immediatelyInvoke) {
fn();
}
window.addEventListener("resize", fn);
return () => window.removeEventListener("resize", fn);
}, []);
}

View File

@ -0,0 +1,4 @@
{
"extends": "../../../tsconfig.json",
"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

@ -0,0 +1,29 @@
export type ShapeType = {
width: number;
height: number;
};
export const getCSSStyleVal = (str: string, parentNum: number) => {
if (!str) return 0;
const strVal = str.includes("px")
? +str.split("px")[0]
: str.includes("%")
? +str.split("%")[0] * parentNum * 0.01
: str;
return Number.isNaN(+strVal) ? 0 : +strVal;
};
export const getRealShape = (el: HTMLElement | null): ShapeType => {
const defaultShape: ShapeType = {width: 0, height: 0};
if (!el || typeof window === "undefined") return defaultShape;
const rect = el.getBoundingClientRect();
const {width, height} = window.getComputedStyle(el);
return {
width: getCSSStyleVal(`${width}`, rect.width),
height: getCSSStyleVal(`${height}`, rect.height),
};
};

View File

@ -1,2 +1,3 @@
export * from "./dom";
export * from "./aria";
export * from "./dimensions";

View File

@ -2,7 +2,7 @@ import * as React from "react";
import {isFunction} from "./assertion";
export type ReactRef<T> = React.Ref<T> | React.RefObject<T> | React.MutableRefObject<T>;
export type ReactRef<T> = React.RefObject<T> | React.MutableRefObject<T> | React.Ref<T>;
/**
* Assigns a value to a ref function or object

View File

@ -1,4 +1,4 @@
import {renderHook} from "@testing-library/react";
import {renderHook} from "@testing-library/react-hooks";
import { {{camelCase hookName}} } from "../src";

View File

@ -7,5 +7,5 @@ export function {{camelCase hookName}}(props: Use{{capitalize hookName}}Props =
return {...otherProps};
}
export type Use{{capitalize hookName}}Return = ReturnType<typeof {{camelCase hookName}}>;
export type {{capitalize hookName}}Return = ReturnType<typeof {{camelCase hookName}}>;

108
pnpm-lock.yaml generated
View File

@ -342,6 +342,49 @@ importers:
clean-package: 2.1.1
react: 17.0.2
packages/components/card:
specifiers:
'@nextui-org/code': workspace:*
'@nextui-org/col': workspace:*
'@nextui-org/divider': workspace:*
'@nextui-org/dom-utils': workspace:*
'@nextui-org/drip': workspace:*
'@nextui-org/grid': workspace:*
'@nextui-org/image': workspace:*
'@nextui-org/link': workspace:*
'@nextui-org/row': workspace:*
'@nextui-org/shared-css': workspace:*
'@nextui-org/shared-utils': workspace:*
'@nextui-org/system': workspace:*
'@nextui-org/text': workspace:*
'@react-aria/focus': ^3.9.0
'@react-aria/interactions': ^3.12.0
'@react-aria/utils': ^3.14.0
'@react-types/shared': ^3.14.1
clean-package: 2.1.1
react: ^17.0.2
dependencies:
'@nextui-org/divider': link:../divider
'@nextui-org/dom-utils': link:../../utilities/dom-utils
'@nextui-org/drip': link:../drip
'@nextui-org/image': link:../image
'@nextui-org/shared-css': link:../../utilities/shared-css
'@nextui-org/shared-utils': link:../../utilities/shared-utils
'@nextui-org/system': link:../../core/system
'@react-aria/focus': 3.9.0_react@17.0.2
'@react-aria/interactions': 3.12.0_react@17.0.2
'@react-aria/utils': 3.14.0_react@17.0.2
devDependencies:
'@nextui-org/code': link:../code
'@nextui-org/col': link:../col
'@nextui-org/grid': link:../grid
'@nextui-org/link': link:../link
'@nextui-org/row': link:../row
'@nextui-org/text': link:../text
'@react-types/shared': 3.15.0_react@17.0.2
clean-package: 2.1.1
react: 17.0.2
packages/components/checkbox:
specifiers:
'@nextui-org/dom-utils': workspace:*
@ -438,6 +481,21 @@ importers:
clean-package: 2.1.1
react: 17.0.2
packages/components/drip:
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/grid:
specifiers:
'@nextui-org/dom-utils': workspace:*
@ -453,6 +511,29 @@ importers:
clean-package: 2.1.1
react: 17.0.2
packages/components/image:
specifiers:
'@nextui-org/dom-utils': workspace:*
'@nextui-org/grid': workspace:*
'@nextui-org/shared-utils': workspace:*
'@nextui-org/system': workspace:*
'@nextui-org/use-real-shape': workspace:*
'@nextui-org/use-ref-state': workspace:*
'@nextui-org/use-resize': 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
'@nextui-org/use-real-shape': link:../../hooks/use-real-shape
'@nextui-org/use-ref-state': link:../../hooks/use-ref-state
'@nextui-org/use-resize': link:../../hooks/use-resize
devDependencies:
'@nextui-org/grid': link:../grid
clean-package: 2.1.1
react: 17.0.2
packages/components/link:
specifiers:
'@nextui-org/dom-utils': workspace:*
@ -633,6 +714,33 @@ importers:
clean-package: 2.1.1
react: 17.0.2
packages/hooks/use-real-shape:
specifiers:
'@nextui-org/dom-utils': workspace:*
clean-package: 2.1.1
react: ^17.0.2
dependencies:
'@nextui-org/dom-utils': link:../../utilities/dom-utils
devDependencies:
clean-package: 2.1.1
react: 17.0.2
packages/hooks/use-ref-state:
specifiers:
clean-package: 2.1.1
react: ^17.0.2
devDependencies:
clean-package: 2.1.1
react: 17.0.2
packages/hooks/use-resize:
specifiers:
clean-package: 2.1.1
react: ^17.0.2
devDependencies:
clean-package: 2.1.1
react: 17.0.2
packages/hooks/use-ssr:
specifiers:
clean-package: 2.1.1

View File

@ -15,7 +15,7 @@
"esModuleInterop": true,
"resolveJsonModule": true,
"allowSyntheticDefaultImports": true,
"downlevelIteration": true
"downlevelIteration": true,
},
"include": ["packages", "react-shim.js"],
"exclude": ["**/node_modules"]

5
typings/mdx.d.ts vendored
View File

@ -1,5 +0,0 @@
declare module '*.mdx' {
let MDXComponent: (props: any) => JSX.Element
export default MDXComponent
}

7
typings/meta.d.ts vendored
View File

@ -1,7 +0,0 @@
import React from 'react'
declare module 'react' {
interface MetaHTMLAttributes<T> extends React.MetaHTMLAttributes<T> {
itemprop?: string
}
}

8
typings/styled.d.ts vendored
View File

@ -1,8 +0,0 @@
declare global {
declare module "react" {
interface StyleHTMLAttributes<T> extends React.HTMLAttributes<T> {
jsx?: boolean;
global?: boolean;
}
}
}