feat(components): user component added

This commit is contained in:
Junior Garcia 2022-10-03 16:49:15 -03:00
parent 7dd99944cf
commit 655de55c53
15 changed files with 468 additions and 32 deletions

View File

@ -1,14 +1,15 @@
---
title: 'User'
description: 'Flexible User Profile Component'
title: "User"
description: "Flexible User Profile Component"
url: https://nextui.org/docs/components/user
---
# User
Flexible User Profile Component.
```jsx
import { User } from '@nextui-org/react';
import { User } from "@nextui-org/react";
```
<CarbonAd />
@ -47,7 +48,7 @@ import { User } from '@nextui-org/react';
<Playground
title="Pointer"
desc="Display pointer cursor on hover."
desc="Display pointer cursor on hover."
files={UserContent.pointer}
/>
@ -77,21 +78,22 @@ import { User } from '@nextui-org/react';
#### User Props
| Attribute | Type | Accepted values | Description | Default |
| ---------------- | ------------------------------ | ---------------------------------------- | -------------------------------------- | --------- |
| **color** | `NormalColors` `string` | [NormalColors](#normal-colors) | Change User Profile border color | `default` |
| **src** | `string` | - | Image source (local or remote) | - |
| **name** | `string` | - | Display Text Next to Image | - |
| **text** | `string` | - | Display Text when image is missing | - |
| **alt** | `string` | - | Display alt text when image is missing | - |
| **size** | `NormalSizes` `number` | [NormalSizes](#normal-sizes) | User Profile size | `medium` |
| **bordered** | `boolean` | `true/false` | Bordered User Profile | `false` |
| **zoomed** | `boolean` | `true/false` | Zoomed User Profile | `false` |
| **pointer** | `boolean` | `true/false` | Display pointer cursor on hover | `false` |
| **squared** | `boolean` | `true/false` | Squared User Profile | `false` |
| **css** | `Stitches.CSS` | - | Override Default CSS style | - |
| **as** | `keyof JSX.IntrinsicElements` | - | Changes which tag component outputs | `span` |
| ... | `ImgHTMLAttributes` | `'alt', 'crossOrigin', 'className', ...` | Native props | - |
| Attribute | Type | Description | Default |
| --------------- | --------------------------------------------------- | ----------------------------------------------------------------------------- | --------- |
| **color** | [NormalColors](#normal-colors) `string` | Change User Profile border color | `default` |
| **src** | `string` | Image source (local or remote) | - |
| **name** | `string` | Display Text Next to Image | - |
| **text** | `string` | Display Text when image is missing | - |
| **alt** | `string` | Display alt text when image is missing | - |
| **size** | [NormalSizes](#normal-sizes) `number` | User Profile size | `medium` |
| **bordered** | `boolean` | Bordered User Profile | `false` |
| **zoomed** | `boolean` | Zoomed User Profile | `false` |
| **pointer** | `boolean` | Display pointer cursor on hover | `false` |
| **squared** | `boolean` | Squared User Profile | `false` |
| **avatarProps** | [AvatarProps](/docs/components/avatar#avatar-props) | Since user component uses the `Avatar` you can use any of the `Avatar` props. | - |
| **css** | `Stitches.CSS` | Override Default CSS style | - |
| **as** | `keyof JSX.IntrinsicElements` | Changes which tag component outputs | `div` |
<Spacer y={2} />
### User types
@ -102,16 +104,16 @@ import { User } from '@nextui-org/react';
```ts
type NormalColors =
| 'default'
| 'primary'
| 'secondary'
| 'success'
| 'warning'
| 'error';
| "default"
| "primary"
| "secondary"
| "success"
| "warning"
| "error";
```
#### Normal Sizes
```ts
type NormalSizes = 'xs' | 'sm' | 'md' | 'lg' | 'xl';
```
type NormalSizes = "xs" | "sm" | "md" | "lg" | "xl";
```

View File

@ -78,9 +78,6 @@ export interface UseBadgeProps extends HTMLNextUIProps<"span"> {
disableOutline?: boolean;
}
// disableOutline: false,
// isSquared: false,
export function useBadge(props: UseBadgeProps) {
const {
children,

View File

@ -0,0 +1,24 @@
# @nextui-org/user
A Quick description of the component
> This is an internal utility, not intended for public usage.
## Installation
```sh
yarn add @nextui-org/user
# or
npm i @nextui-org/user
```
## 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,59 @@
import * as React from "react";
import {render} from "@testing-library/react";
import {User} from "../src";
describe("User", () => {
it("should render correctly", () => {
const wrapper = render(<User name="Test" />);
expect(() => wrapper.unmount()).not.toThrow();
});
it("ref should be forwarded", () => {
const ref = React.createRef<HTMLDivElement>();
render(<User ref={ref} name="Test" />);
expect(ref.current).not.toBeNull();
});
it("should support image and text", () => {
const wrapper = render(
<div>
<User name="Text" text="User" />
<User name="User test" src="https://avatars.githubusercontent.com/u/30373425?v=4" />
</div>,
);
expect(() => wrapper.unmount()).not.toThrow();
});
it("should render description correctly", () => {
const wrapper = render(
<div>
<User description="This is a description" name="User" />
<User name="User">This is a description</User>
</div>,
);
expect(() => wrapper.unmount()).not.toThrow();
});
it("should render link on user.link", () => {
const {container} = render(
<User name="User">
<User.Link href="https://nextui.org">NextUI</User.Link>
</User>,
);
expect(container.querySelector("a")).not.toBeNull();
});
it("should pass alt attribute", () => {
const {container} = render(
<User alt="User" name="User" src="https://avatars.githubusercontent.com/u/30373425?v=4" />,
);
expect(container.querySelector("img")?.getAttribute("alt")).toBe("User");
});
});

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,53 @@
{
"name": "@nextui-org/user",
"version": "1.0.0-beta.11",
"description": "Flexible User Profile Component.",
"keywords": [
"user"
],
"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/user"
},
"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",
"react-aria": ">=3.18.0"
},
"dependencies": {
"@nextui-org/system": "workspace:*",
"@nextui-org/avatar": "workspace:*",
"@nextui-org/link": "workspace:*",
"@nextui-org/shared-utils": "workspace:*",
"@nextui-org/shared-css": "workspace:*",
"@nextui-org/dom-utils": "workspace:*"
},
"devDependencies": {
"clean-package": "2.1.1",
"react": "^17.0.2",
"react-aria": "3.18.0"
}
}

View File

@ -0,0 +1,5 @@
// export types
export type {UserProps} from "./user";
// export component
export {default as User} from "./user";

View File

@ -0,0 +1,55 @@
import type {AvatarProps} from "@nextui-org/avatar";
import {ReactNode, useMemo} from "react";
import {useFocusRing} from "react-aria";
import {HTMLNextUIProps} from "@nextui-org/system";
import {IFocusRingAria} from "@nextui-org/dom-utils";
export interface UseUserProps extends HTMLNextUIProps<"div", AvatarProps> {
/**
* The user name.
*/
name: ReactNode | string;
/**
* The user information, like email, phone, etc.
*/
description?: ReactNode | string;
}
export function useUser(props: UseUserProps) {
const {as, className, css, name, description, ...otherProps} = props;
const {isFocusVisible, focusProps}: IFocusRingAria<UseUserProps> = useFocusRing();
const userCss = useMemo(() => {
if (as === "button") {
return {
// reset button styles
p: 0,
m: 0,
borderRadius: "$xs",
background: "none",
appearance: "none",
outline: "none",
border: "none",
cursor: "pointer",
};
}
return {};
}, [as]);
return {
as,
userCss,
className,
css,
name,
description,
isFocusVisible,
focusProps,
...otherProps,
};
}
export type UseUserReturn = ReturnType<typeof useUser>;

View File

@ -0,0 +1,38 @@
import {forwardRef} from "@nextui-org/system";
import {useDOMRef} from "@nextui-org/dom-utils";
import {clsx, __DEV__} from "@nextui-org/shared-utils";
import {Link, LinkProps} from "@nextui-org/link";
export interface UserLinkProps extends LinkProps {}
export const UserLink = forwardRef<UserLinkProps, "a">((props, ref) => {
const {
rel = "noopener",
color = "primary",
target = "_blank",
className,
children,
...otherProps
} = props;
const domRef = useDOMRef(ref);
return (
<Link
ref={domRef}
className={clsx("nextui-user-link", className)}
color={color}
rel={rel}
target={target}
{...otherProps}
>
{children}
</Link>
);
});
if (__DEV__) {
UserLink.displayName = "NextUI.UserLink";
}
UserLink.toString = () => ".nextui-user-link";

View File

@ -0,0 +1,48 @@
import {styled} from "@nextui-org/system";
import {cssFocusVisible} from "@nextui-org/shared-css";
export const StyledUser = styled(
"div",
{
d: "inline-flex",
p: "0 $sm",
jc: "center",
ai: "center",
w: "max-content",
maxWidth: "100%",
transition: "transform 250ms ease 0ms, box-shadow 0.25s ease 0s",
"@motion": {
transition: "none",
},
},
cssFocusVisible,
);
export const StyledUserInfo = styled("div", {
ml: "$sm",
d: "inline-flex",
fd: "column",
alignItems: "flex-start",
whiteSpace: "nowrap",
});
export const StyledUserName = styled("span", {
fontSize: "$sm",
color: "$text",
lh: "$sm",
fontWeight: "$medium",
maxW: "$60",
to: "ellipsis", // text overflow
ov: "hidden", // overflow
});
export const StyledUserDesc = styled("span", {
fontSize: "$xs",
color: "$accents7",
"*:first-child": {
mt: 0,
},
"*:last-child": {
mb: 0,
},
});

View File

@ -0,0 +1,82 @@
import {mergeProps} from "react-aria";
import {forwardRef} from "@nextui-org/system";
import {useDOMRef} from "@nextui-org/dom-utils";
import {clsx, __DEV__} from "@nextui-org/shared-utils";
import {Avatar} from "@nextui-org/avatar";
import {UserLink} from "./user-link";
import {StyledUser, StyledUserInfo, StyledUserName, StyledUserDesc} from "./user.styles";
import {UseUserProps, useUser} from "./use-user";
export interface UserProps extends UseUserProps {}
type CompundUser = {
Link: typeof UserLink;
};
const User = forwardRef<UserProps, "div", CompundUser>((props, ref) => {
const {
// user props
css,
name,
userCss,
isFocusVisible,
className,
focusProps,
description,
children,
// avatar props, TODO: this should come from a "avatarProps" prop.
alt,
src,
squared,
size,
zoomed,
bordered,
color,
pointer,
text,
...otherProps
} = useUser(props);
const domRef = useDOMRef(ref);
return (
<StyledUser
ref={domRef}
className={clsx("nextui-user", className)}
css={{
...userCss,
...css,
}}
{...mergeProps(focusProps, otherProps)}
isFocusVisible={isFocusVisible}
>
<Avatar
alt={alt}
bordered={bordered}
className="nextui-user-avatar"
color={color}
pointer={pointer}
size={size}
squared={squared}
src={src}
text={text}
zoomed={zoomed}
/>
<StyledUserInfo className="nextui-user-info">
<StyledUserName className="nextui-user-name">{name}</StyledUserName>
<StyledUserDesc className="nextui-user-desc">{description || children}</StyledUserDesc>
</StyledUserInfo>
</StyledUser>
);
});
User.Link = UserLink;
if (__DEV__) {
User.displayName = "NextUI.User";
}
User.toString = () => ".nextui-user";
export default User;

View File

@ -0,0 +1,27 @@
import React from "react";
import {Meta} from "@storybook/react";
import {User} from "../src";
export default {
title: "Display/User",
component: User,
} as Meta;
const url = "https://avatars.githubusercontent.com/u/30373425?v=4";
export const Default = () => <User squared name="Junior García" src={url} />;
export const Description = () => (
<User squared name="Junior García" src={url}>
Software Developer
</User>
);
export const Link = () => {
return (
<User squared name="Junior García" src={url}>
<User.Link href="https://twitter.com/jrgarciadev">@jrgarciadev</User.Link>
</User>
);
};

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,
});

25
pnpm-lock.yaml generated
View File

@ -475,9 +475,31 @@ importers:
clean-package: 2.1.1
react: 17.0.2
packages/components/user:
specifiers:
'@nextui-org/avatar': workspace:*
'@nextui-org/dom-utils': workspace:*
'@nextui-org/link': workspace:*
'@nextui-org/shared-css': workspace:*
'@nextui-org/shared-utils': workspace:*
'@nextui-org/system': workspace:*
clean-package: 2.1.1
react: ^17.0.2
react-aria: 3.18.0
dependencies:
'@nextui-org/avatar': link:../avatar
'@nextui-org/dom-utils': link:../../utilities/dom-utils
'@nextui-org/link': link:../link
'@nextui-org/shared-css': link:../../utilities/shared-css
'@nextui-org/shared-utils': link:../../utilities/shared-utils
'@nextui-org/system': link:../../core/system
devDependencies:
clean-package: 2.1.1
react: 17.0.2
react-aria: 3.18.0_react@17.0.2
packages/core/react:
specifiers:
'@babel/runtime': ^7.6.2
'@nextui-org/avatar': workspace:*
'@nextui-org/badge': workspace:*
'@nextui-org/col': workspace:*
@ -494,7 +516,6 @@ importers:
react-aria: 3.18.0
react-stately: 3.16.0
dependencies:
'@babel/runtime': 7.19.0
'@nextui-org/avatar': link:../../components/avatar
'@nextui-org/badge': link:../../components/badge
'@nextui-org/col': link:../../components/col