mirror of
https://github.com/nextui-org/nextui.git
synced 2025-12-08 19:26:11 +00:00
feat(components): user component added
This commit is contained in:
parent
7dd99944cf
commit
655de55c53
@ -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";
|
||||
```
|
||||
|
||||
@ -78,9 +78,6 @@ export interface UseBadgeProps extends HTMLNextUIProps<"span"> {
|
||||
disableOutline?: boolean;
|
||||
}
|
||||
|
||||
// disableOutline: false,
|
||||
// isSquared: false,
|
||||
|
||||
export function useBadge(props: UseBadgeProps) {
|
||||
const {
|
||||
children,
|
||||
|
||||
24
packages/components/user/README.md
Normal file
24
packages/components/user/README.md
Normal 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).
|
||||
59
packages/components/user/__tests__/user.test.tsx
Normal file
59
packages/components/user/__tests__/user.test.tsx
Normal 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");
|
||||
});
|
||||
});
|
||||
3
packages/components/user/clean-package.config.json
Normal file
3
packages/components/user/clean-package.config.json
Normal 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" } } }
|
||||
53
packages/components/user/package.json
Normal file
53
packages/components/user/package.json
Normal 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"
|
||||
}
|
||||
}
|
||||
5
packages/components/user/src/index.ts
Normal file
5
packages/components/user/src/index.ts
Normal file
@ -0,0 +1,5 @@
|
||||
// export types
|
||||
export type {UserProps} from "./user";
|
||||
|
||||
// export component
|
||||
export {default as User} from "./user";
|
||||
55
packages/components/user/src/use-user.ts
Normal file
55
packages/components/user/src/use-user.ts
Normal 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>;
|
||||
38
packages/components/user/src/user-link.tsx
Normal file
38
packages/components/user/src/user-link.tsx
Normal 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";
|
||||
48
packages/components/user/src/user.styles.ts
Normal file
48
packages/components/user/src/user.styles.ts
Normal 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,
|
||||
},
|
||||
});
|
||||
82
packages/components/user/src/user.tsx
Normal file
82
packages/components/user/src/user.tsx
Normal 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;
|
||||
27
packages/components/user/stories/user.stories.tsx
Normal file
27
packages/components/user/stories/user.stories.tsx
Normal 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>
|
||||
);
|
||||
};
|
||||
9
packages/components/user/tsconfig.json
Normal file
9
packages/components/user/tsconfig.json
Normal file
@ -0,0 +1,9 @@
|
||||
{
|
||||
"extends": "../../../tsconfig.json",
|
||||
"compilerOptions": {
|
||||
"paths": {
|
||||
"@stitches/react": ["../../../node_modules/@stitches/react"]
|
||||
}
|
||||
},
|
||||
"include": ["src", "index.ts"]
|
||||
}
|
||||
13
packages/components/user/tsup.config.ts
Normal file
13
packages/components/user/tsup.config.ts
Normal 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
25
pnpm-lock.yaml
generated
@ -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
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user