mirror of
https://github.com/nextui-org/nextui.git
synced 2025-12-08 19:26:11 +00:00
feat: user created, avatar improvements
This commit is contained in:
parent
015526b016
commit
a6d8b976c8
@ -40,7 +40,6 @@
|
||||
"@nextui-org/system": "workspace:*",
|
||||
"@nextui-org/theme": "workspace:*",
|
||||
"@nextui-org/shared-utils": "workspace:*",
|
||||
"@nextui-org/shared-css": "workspace:*",
|
||||
"@nextui-org/dom-utils": "workspace:*",
|
||||
"@nextui-org/use-image": "workspace:*",
|
||||
"@react-aria/focus": "^3.9.0",
|
||||
|
||||
@ -30,6 +30,14 @@ export interface UseAvatarGroupProps extends HTMLNextUIProps<"div"> {
|
||||
* Whether the avatars are bordered
|
||||
*/
|
||||
isBordered?: AvatarProps["isBordered"];
|
||||
/**
|
||||
* Whether the avatars are disabled
|
||||
*/
|
||||
isDisabled?: AvatarProps["isDisabled"];
|
||||
/**
|
||||
* Whether the avatars should be displayed in a grid
|
||||
*/
|
||||
isGrid?: boolean;
|
||||
/**
|
||||
* The maximum number of visible avatars
|
||||
* @default 5
|
||||
@ -49,7 +57,9 @@ export type ContextType = {
|
||||
size?: AvatarProps["size"];
|
||||
color?: AvatarProps["color"];
|
||||
radius?: AvatarProps["radius"];
|
||||
isGrid?: boolean;
|
||||
isBordered?: AvatarProps["isBordered"];
|
||||
isDisabled?: AvatarProps["isDisabled"];
|
||||
};
|
||||
|
||||
export function useAvatarGroup(props: UseAvatarGroupProps) {
|
||||
@ -61,8 +71,10 @@ export function useAvatarGroup(props: UseAvatarGroupProps) {
|
||||
size,
|
||||
color,
|
||||
radius,
|
||||
isBordered,
|
||||
children,
|
||||
isBordered,
|
||||
isDisabled,
|
||||
isGrid,
|
||||
className,
|
||||
...otherProps
|
||||
} = props;
|
||||
@ -75,10 +87,12 @@ export function useAvatarGroup(props: UseAvatarGroupProps) {
|
||||
size,
|
||||
color,
|
||||
radius,
|
||||
isGrid,
|
||||
isBordered,
|
||||
isDisabled,
|
||||
};
|
||||
|
||||
const styles = avatarGroup({className});
|
||||
const styles = avatarGroup({className, isGrid});
|
||||
|
||||
const validChildren = getValidChildren(children);
|
||||
const childrenWithinMax = max ? validChildren.slice(0, max) : validChildren;
|
||||
@ -91,7 +105,7 @@ export function useAvatarGroup(props: UseAvatarGroupProps) {
|
||||
|
||||
const childProps = {
|
||||
className: clsx(
|
||||
isFirstAvatar ? "ml-0" : "-ml-2",
|
||||
isFirstAvatar ? "ml-0" : !isGrid ? "-ml-2" : "",
|
||||
isLastAvatar && remainingCount < 1 ? "hover:-translate-x-0" : "",
|
||||
),
|
||||
};
|
||||
|
||||
@ -11,7 +11,8 @@ import {useImage} from "@nextui-org/use-image";
|
||||
|
||||
import {useAvatarGroupContext} from "./avatar-group-context";
|
||||
|
||||
export interface UseAvatarProps extends HTMLNextUIProps<"span", AvatarVariantProps> {
|
||||
export interface UseAvatarProps
|
||||
extends Omit<HTMLNextUIProps<"span", AvatarVariantProps>, "children" | "isFocusVisible"> {
|
||||
/**
|
||||
* Ref to the DOM node.
|
||||
*/
|
||||
@ -98,6 +99,8 @@ export function useAvatar(props: UseAvatarProps) {
|
||||
radius = groupContext?.radius ?? "full",
|
||||
size = groupContext?.size ?? "md",
|
||||
isBordered = groupContext?.isBordered ?? false,
|
||||
isDisabled = groupContext?.isDisabled ?? false,
|
||||
isInGridGroup = groupContext?.isGrid ?? false,
|
||||
isFocusable = false,
|
||||
getInitials = safeText,
|
||||
ignoreFallback = false,
|
||||
@ -133,7 +136,16 @@ export function useAvatar(props: UseAvatarProps) {
|
||||
|
||||
const {isFocusVisible, focusProps} = useFocusRing();
|
||||
|
||||
const slots = avatar({color, radius, size, isBordered, isFocusVisible, isInGroup});
|
||||
const slots = avatar({
|
||||
color,
|
||||
radius,
|
||||
size,
|
||||
isBordered,
|
||||
isFocusVisible,
|
||||
isDisabled,
|
||||
isInGroup,
|
||||
isInGridGroup,
|
||||
});
|
||||
|
||||
const imgStyles = clsx(
|
||||
"transition-opacity !duration-500 opacity-0 data-[loaded=true]:opacity-100",
|
||||
@ -154,7 +166,7 @@ export function useAvatar(props: UseAvatarProps) {
|
||||
}),
|
||||
...mergeProps(otherProps, canBeFocused ? focusProps : {}),
|
||||
}),
|
||||
[canBeFocused, slots, baseStyles, buttonStyles],
|
||||
[canBeFocused, slots, baseStyles, buttonStyles, focusProps, otherProps],
|
||||
);
|
||||
|
||||
return {
|
||||
@ -172,7 +184,6 @@ export function useAvatar(props: UseAvatarProps) {
|
||||
imgStyles,
|
||||
getAvatarProps,
|
||||
getInitials,
|
||||
...otherProps,
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@ -39,6 +39,8 @@ const pics = [
|
||||
"https://i.pravatar.cc/300?u=a042581f4e29026707d",
|
||||
"https://i.pravatar.cc/300?u=a042581f4e29026709d",
|
||||
"https://i.pravatar.cc/300?u=a042581f4f29026709d",
|
||||
"https://i.pravatar.cc/300?u=a042581f4e29026710d",
|
||||
"https://i.pravatar.cc/300?u=a042581f4e29026711d",
|
||||
];
|
||||
|
||||
const Template: ComponentStory<typeof AvatarGroup> = (args: AvatarGroupProps) => (
|
||||
@ -48,6 +50,10 @@ const Template: ComponentStory<typeof AvatarGroup> = (args: AvatarGroupProps) =>
|
||||
<Avatar src={pics[2]} />
|
||||
<Avatar src={pics[3]} />
|
||||
<Avatar src={pics[4]} />
|
||||
<Avatar src={pics[5]} />
|
||||
<Avatar src={pics[2]} />
|
||||
<Avatar src={pics[3]} />
|
||||
<Avatar src={pics[6]} />
|
||||
</AvatarGroup>
|
||||
);
|
||||
|
||||
@ -57,6 +63,21 @@ Default.args = {
|
||||
isBordered: true,
|
||||
};
|
||||
|
||||
export const Grid = Template.bind({});
|
||||
Grid.args = {
|
||||
color: "primary",
|
||||
isBordered: true,
|
||||
max: 7,
|
||||
isGrid: true,
|
||||
};
|
||||
|
||||
export const isDisabled = Template.bind({});
|
||||
isDisabled.args = {
|
||||
color: "warning",
|
||||
isBordered: true,
|
||||
isDisabled: true,
|
||||
};
|
||||
|
||||
export const WithMaxCount = Template.bind({});
|
||||
WithMaxCount.args = {
|
||||
color: "primary",
|
||||
|
||||
@ -37,8 +37,20 @@ export default {
|
||||
const Template: ComponentStory<typeof Avatar> = (args: AvatarProps) => <Avatar {...args} />;
|
||||
|
||||
export const Default = Template.bind({});
|
||||
Default.args = {
|
||||
name: "Junior",
|
||||
Default.args = {};
|
||||
|
||||
export const WithText = Template.bind({});
|
||||
WithText.args = {
|
||||
name: "JW",
|
||||
color: "error",
|
||||
};
|
||||
|
||||
export const isDisabled = Template.bind({});
|
||||
isDisabled.args = {
|
||||
src: "https://i.pravatar.cc/300?u=a042581f4e29026709d",
|
||||
color: "secondary",
|
||||
isBordered: true,
|
||||
isDisabled: true,
|
||||
};
|
||||
|
||||
export const WithImage = Template.bind({});
|
||||
@ -68,11 +80,27 @@ export const Custom = Template.bind({});
|
||||
Custom.args = {
|
||||
icon: <Activity fill="currentColor" size={20} />,
|
||||
radius: "xl",
|
||||
classes: {
|
||||
styles: {
|
||||
base: "shadow-lg bg-cyan-200 dark:bg-cyan-800",
|
||||
},
|
||||
};
|
||||
|
||||
export const CustomSize = Template.bind({});
|
||||
CustomSize.args = {
|
||||
styles: {
|
||||
base: "w-32 h-32 text-md",
|
||||
},
|
||||
};
|
||||
|
||||
export const CustomSizeImg = Template.bind({});
|
||||
CustomSizeImg.args = {
|
||||
src: "https://i.pravatar.cc/300?u=a042581f4e29026705d",
|
||||
name: "Junior",
|
||||
styles: {
|
||||
base: "w-32 h-32 text-md",
|
||||
},
|
||||
};
|
||||
|
||||
export const DefaultIcon = Template.bind({});
|
||||
DefaultIcon.args = {
|
||||
styles: {
|
||||
|
||||
@ -38,10 +38,8 @@
|
||||
},
|
||||
"dependencies": {
|
||||
"@nextui-org/dom-utils": "workspace:*",
|
||||
"@nextui-org/shared-css": "workspace:*",
|
||||
"@nextui-org/shared-utils": "workspace:*",
|
||||
"@nextui-org/system": "workspace:*",
|
||||
"@nextui-org/use-is-mounted": "workspace:*",
|
||||
"@nextui-org/theme": "workspace:*",
|
||||
"@react-aria/link": "^3.3.4",
|
||||
"@react-aria/utils": "^3.14.0",
|
||||
|
||||
@ -2,6 +2,7 @@ module.exports = {
|
||||
stories: [
|
||||
"../../link/stories/*.stories.@(js|jsx|ts|tsx)",
|
||||
"../../avatar/stories/*.stories.@(js|jsx|ts|tsx)",
|
||||
"../../user/stories/*.stories.@(js|jsx|ts|tsx)",
|
||||
],
|
||||
staticDirs: ["../public"],
|
||||
addons: [
|
||||
|
||||
@ -39,15 +39,15 @@
|
||||
"dependencies": {
|
||||
"@nextui-org/system": "workspace:*",
|
||||
"@nextui-org/avatar": "workspace:*",
|
||||
"@nextui-org/link": "workspace:*",
|
||||
"@nextui-org/theme": "workspace:*",
|
||||
"@nextui-org/shared-utils": "workspace:*",
|
||||
"@nextui-org/shared-css": "workspace:*",
|
||||
"@nextui-org/dom-utils": "workspace:*",
|
||||
"@react-aria/focus": "^3.9.0",
|
||||
"@react-aria/utils": "^3.14.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"clean-package": "2.1.1",
|
||||
"@nextui-org/link": "workspace:*",
|
||||
"react": "^17.0.2"
|
||||
},
|
||||
"tsup": {
|
||||
|
||||
@ -1,10 +1,19 @@
|
||||
import type {UserVariantProps, SlotsToClasses, UserSlots} from "@nextui-org/theme";
|
||||
import type {AvatarProps} from "@nextui-org/avatar";
|
||||
|
||||
import {ReactNode, useMemo} from "react";
|
||||
import {ReactNode, useMemo, useCallback} from "react";
|
||||
import {useFocusRing} from "@react-aria/focus";
|
||||
import {HTMLNextUIProps} from "@nextui-org/system";
|
||||
|
||||
export interface UseUserProps extends HTMLNextUIProps<"div", AvatarProps> {
|
||||
import {user} from "@nextui-org/theme";
|
||||
import {ReactRef, clsx} from "@nextui-org/shared-utils";
|
||||
import {useDOMRef} from "@nextui-org/dom-utils";
|
||||
import {mergeProps} from "@react-aria/utils";
|
||||
export interface UseUserProps
|
||||
extends Omit<HTMLNextUIProps<"div", UserVariantProps>, "children" | "isFocusVisible"> {
|
||||
/**
|
||||
* Ref to the DOM node.
|
||||
*/
|
||||
ref?: ReactRef<HTMLDivElement | null>;
|
||||
/**
|
||||
* The user name.
|
||||
*/
|
||||
@ -13,41 +22,104 @@ export interface UseUserProps extends HTMLNextUIProps<"div", AvatarProps> {
|
||||
* The user information, like email, phone, etc.
|
||||
*/
|
||||
description?: ReactNode | string;
|
||||
/**
|
||||
* Whether the user can be focused.
|
||||
* @default false
|
||||
*/
|
||||
isFocusable?: boolean;
|
||||
/**
|
||||
* The user avatar props
|
||||
* @see https://nextui.org/docs/components/avatar
|
||||
*/
|
||||
avatarProps?: AvatarProps;
|
||||
/**
|
||||
* Classname or List of classes to change the styles of the avatar.
|
||||
* if `className` is passed, it will be added to the base slot.
|
||||
*
|
||||
* @example
|
||||
* ```ts
|
||||
* <User styles={{
|
||||
* base:"base-classes",
|
||||
* wrapper: "wrapper-classes",
|
||||
* name: "name-classes",
|
||||
* description: "description-classes",
|
||||
* }} />
|
||||
* ```
|
||||
*/
|
||||
styles?: SlotsToClasses<UserSlots>;
|
||||
}
|
||||
|
||||
export function useUser(props: UseUserProps) {
|
||||
const {as, className, css, name, description, ...otherProps} = props;
|
||||
|
||||
const {isFocusVisible, focusProps} = 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 {
|
||||
const {
|
||||
as,
|
||||
userCss,
|
||||
className,
|
||||
ref,
|
||||
css,
|
||||
name,
|
||||
description,
|
||||
isFocusVisible,
|
||||
className,
|
||||
styles,
|
||||
isFocusable = false,
|
||||
avatarProps = {
|
||||
isFocusable: false,
|
||||
name: typeof name === "string" ? name : undefined,
|
||||
},
|
||||
...otherProps
|
||||
} = props;
|
||||
|
||||
const domRef = useDOMRef(ref);
|
||||
|
||||
const {isFocusVisible, focusProps} = useFocusRing();
|
||||
|
||||
const Component = as || "div";
|
||||
|
||||
const canBeFocused = useMemo(() => {
|
||||
return isFocusable || as === "button";
|
||||
}, [isFocusable, as]);
|
||||
|
||||
const slots = user({isFocusVisible});
|
||||
|
||||
const baseStyles = clsx(styles?.base, className);
|
||||
|
||||
const buttonStyles = useMemo(() => {
|
||||
if (as !== "button") return "";
|
||||
|
||||
// reset button styles
|
||||
return [
|
||||
"p-0",
|
||||
"m-0",
|
||||
"bg-none",
|
||||
"radius-none",
|
||||
"appearance-none",
|
||||
"outline-none",
|
||||
"border-none",
|
||||
"cursor-pointer",
|
||||
];
|
||||
}, [as]);
|
||||
|
||||
const getUserProps = useCallback(
|
||||
() => ({
|
||||
tabIndex: canBeFocused ? 0 : -1,
|
||||
className: slots.base({
|
||||
class: clsx(baseStyles, buttonStyles),
|
||||
}),
|
||||
...mergeProps(otherProps, canBeFocused ? focusProps : {}),
|
||||
}),
|
||||
[canBeFocused, slots, baseStyles, focusProps, otherProps],
|
||||
);
|
||||
|
||||
return {
|
||||
Component,
|
||||
domRef,
|
||||
className,
|
||||
css,
|
||||
slots,
|
||||
name,
|
||||
description,
|
||||
styles,
|
||||
baseStyles,
|
||||
focusProps,
|
||||
...otherProps,
|
||||
avatarProps,
|
||||
getUserProps,
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@ -1,38 +0,0 @@
|
||||
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";
|
||||
@ -1,82 +1,30 @@
|
||||
import {mergeProps} from "@react-aria/utils";
|
||||
import {forwardRef} from "@nextui-org/system";
|
||||
import {useDOMRef} from "@nextui-org/dom-utils";
|
||||
import {clsx, __DEV__} from "@nextui-org/shared-utils";
|
||||
import {__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);
|
||||
const User = forwardRef<UserProps, "div">((props, ref) => {
|
||||
const {Component, domRef, name, slots, description, avatarProps, getUserProps} = useUser({
|
||||
ref,
|
||||
...props,
|
||||
});
|
||||
|
||||
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>
|
||||
<Component ref={domRef} {...getUserProps()}>
|
||||
<Avatar {...avatarProps} />
|
||||
<div className={slots.wrapper()}>
|
||||
<span className={slots.name()}>{name}</span>
|
||||
<span className={slots.description()}>{description}</span>
|
||||
</div>
|
||||
</Component>
|
||||
);
|
||||
});
|
||||
|
||||
User.Link = UserLink;
|
||||
|
||||
if (__DEV__) {
|
||||
User.displayName = "NextUI.User";
|
||||
}
|
||||
|
||||
User.toString = () => ".nextui-user";
|
||||
|
||||
export default User;
|
||||
|
||||
@ -1,27 +1,66 @@
|
||||
import React from "react";
|
||||
import {Meta} from "@storybook/react";
|
||||
import {ComponentStory, ComponentMeta} from "@storybook/react";
|
||||
import {Link} from "@nextui-org/link";
|
||||
|
||||
import {User} from "../src";
|
||||
import {User, UserProps} from "../src";
|
||||
|
||||
export default {
|
||||
title: "Display/User",
|
||||
component: User,
|
||||
} as Meta;
|
||||
} as ComponentMeta<typeof User>;
|
||||
|
||||
const url = "https://avatars.githubusercontent.com/u/30373425?v=4";
|
||||
|
||||
export const Default = () => <User squared name="Junior García" src={url} />;
|
||||
const Template: ComponentStory<typeof User> = (args: UserProps) => <User {...args} />;
|
||||
|
||||
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>
|
||||
);
|
||||
export const Default = Template.bind({});
|
||||
Default.args = {
|
||||
name: "Junior Garcia",
|
||||
avatarProps: {
|
||||
src: url,
|
||||
},
|
||||
};
|
||||
|
||||
export const isFocusable = Template.bind({});
|
||||
isFocusable.args = {
|
||||
name: "Junior Garcia",
|
||||
isFocusable: true,
|
||||
avatarProps: {
|
||||
src: url,
|
||||
},
|
||||
};
|
||||
|
||||
export const WithDefaultAvatar = Template.bind({});
|
||||
WithDefaultAvatar.args = {
|
||||
name: "Junior Garcia",
|
||||
avatarProps: {
|
||||
name: "Junior Garcia",
|
||||
getInitials: (name) =>
|
||||
name
|
||||
.split(" ")
|
||||
.map((n) => n[0])
|
||||
.join(""),
|
||||
},
|
||||
};
|
||||
|
||||
export const WithDescription = Template.bind({});
|
||||
WithDescription.args = {
|
||||
name: "Junior Garcia",
|
||||
description: "Software Engineer",
|
||||
avatarProps: {
|
||||
src: url,
|
||||
},
|
||||
};
|
||||
|
||||
export const WithLinkDescription = Template.bind({});
|
||||
WithLinkDescription.args = {
|
||||
name: "Junior Garcia",
|
||||
description: (
|
||||
<Link href="https://twitter.com/jrgarciadev" size="xs">
|
||||
@jrgarciadev
|
||||
</Link>
|
||||
),
|
||||
avatarProps: {
|
||||
src: url,
|
||||
},
|
||||
};
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
import {tv} from "tailwind-variants";
|
||||
import {tv, VariantProps} from "tailwind-variants";
|
||||
|
||||
/**
|
||||
* AvatarGroup wrapper tv component
|
||||
@ -12,6 +12,13 @@ import {tv} from "tailwind-variants";
|
||||
*/
|
||||
const avatarGroup = tv({
|
||||
base: "flex items-center justify-center h-auto w-max-content",
|
||||
variants: {
|
||||
isGrid: {
|
||||
true: "inline-grid grid-cols-4 gap-3",
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
export type AvatarGroupVariantProps = VariantProps<typeof avatarGroup>;
|
||||
|
||||
export {avatarGroup};
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
import {tv, type VariantProps} from "tailwind-variants";
|
||||
|
||||
import {translateCenterClasses} from "../../utils";
|
||||
import {translateCenterClasses, ringClasses} from "../../utils";
|
||||
|
||||
/**
|
||||
* Avatar wrapper tv component
|
||||
@ -106,9 +106,14 @@ const avatar = tv({
|
||||
base: "ring-2 ring-offset-2 ring-offset-background dark:ring-offset-background-dark",
|
||||
},
|
||||
},
|
||||
isDisabled: {
|
||||
true: {
|
||||
base: "opacity-50",
|
||||
},
|
||||
},
|
||||
isFocusVisible: {
|
||||
true: {
|
||||
base: "outline-none ring-2 !ring-primary ring-offset-2 ring-offset-background dark:ring-offset-background-dark",
|
||||
base: [...ringClasses],
|
||||
},
|
||||
},
|
||||
isInGroup: {
|
||||
@ -116,6 +121,11 @@ const avatar = tv({
|
||||
base: "-ml-2 hover:-translate-x-3 transition-transform",
|
||||
},
|
||||
},
|
||||
isInGridGroup: {
|
||||
true: {
|
||||
base: "m-0 hover:translate-x-0",
|
||||
},
|
||||
},
|
||||
},
|
||||
defaultVariants: {
|
||||
size: "md",
|
||||
|
||||
@ -1,3 +1,4 @@
|
||||
export * from "./link";
|
||||
export * from "./avatar";
|
||||
export * from "./avatar-group";
|
||||
export * from "./user";
|
||||
|
||||
38
packages/core/theme/src/components/user/index.ts
Normal file
38
packages/core/theme/src/components/user/index.ts
Normal file
@ -0,0 +1,38 @@
|
||||
import {tv, VariantProps} from "tailwind-variants";
|
||||
|
||||
import {ringClasses} from "../../utils";
|
||||
|
||||
/**
|
||||
* User wrapper tv component
|
||||
*
|
||||
* const {base, wrapper, name, description} = user({...})
|
||||
*
|
||||
* @example
|
||||
* <div className={base())}>
|
||||
* // avatar element @see avatar
|
||||
* <div className={wrapper())}>
|
||||
* <span className={name())}>user name</span>
|
||||
* <span className={description())}>user description</span>
|
||||
* </div>
|
||||
* </div>
|
||||
*/
|
||||
const user = tv({
|
||||
slots: {
|
||||
base: "inline-flex items-center justify-center gap-2",
|
||||
wrapper: "inline-flex flex-col items-start",
|
||||
name: "text-sm text-foreground dark:text-foreground-dark",
|
||||
description: "text-xs text-neutral-500",
|
||||
},
|
||||
variants: {
|
||||
isFocusVisible: {
|
||||
true: {
|
||||
base: [...ringClasses, "rounded-sm"],
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
export type UserVariantProps = VariantProps<typeof user>;
|
||||
export type UserSlots = keyof ReturnType<typeof user>;
|
||||
|
||||
export {user};
|
||||
@ -11,6 +11,15 @@ export const focusVisibleClasses = [
|
||||
"dark:focus-visible:ring-offset-background-dark",
|
||||
];
|
||||
|
||||
export const ringClasses = [
|
||||
"outline-none",
|
||||
"ring-2",
|
||||
"!ring-primary",
|
||||
"ring-offset-2",
|
||||
"ring-offset-background",
|
||||
"dark:ring-offset-background-dark",
|
||||
];
|
||||
|
||||
/**
|
||||
* This classes centers the element by using absolute positioning.
|
||||
*/
|
||||
|
||||
12
pnpm-lock.yaml
generated
12
pnpm-lock.yaml
generated
@ -334,7 +334,6 @@ importers:
|
||||
packages/components/avatar:
|
||||
specifiers:
|
||||
'@nextui-org/dom-utils': workspace:*
|
||||
'@nextui-org/shared-css': workspace:*
|
||||
'@nextui-org/shared-icons': workspace:*
|
||||
'@nextui-org/shared-utils': workspace:*
|
||||
'@nextui-org/system': workspace:*
|
||||
@ -347,7 +346,6 @@ importers:
|
||||
react: ^17.0.2
|
||||
dependencies:
|
||||
'@nextui-org/dom-utils': link:../../utilities/dom-utils
|
||||
'@nextui-org/shared-css': link:../../utilities/shared-css
|
||||
'@nextui-org/shared-utils': link:../../utilities/shared-utils
|
||||
'@nextui-org/system': link:../../core/system
|
||||
'@nextui-org/theme': link:../../core/theme
|
||||
@ -569,11 +567,9 @@ importers:
|
||||
packages/components/link:
|
||||
specifiers:
|
||||
'@nextui-org/dom-utils': workspace:*
|
||||
'@nextui-org/shared-css': workspace:*
|
||||
'@nextui-org/shared-utils': workspace:*
|
||||
'@nextui-org/system': workspace:*
|
||||
'@nextui-org/theme': workspace:*
|
||||
'@nextui-org/use-is-mounted': workspace:*
|
||||
'@react-aria/focus': ^3.9.0
|
||||
'@react-aria/link': ^3.3.4
|
||||
'@react-aria/utils': ^3.14.0
|
||||
@ -582,11 +578,9 @@ importers:
|
||||
react: ^17.0.2
|
||||
dependencies:
|
||||
'@nextui-org/dom-utils': link:../../utilities/dom-utils
|
||||
'@nextui-org/shared-css': link:../../utilities/shared-css
|
||||
'@nextui-org/shared-utils': link:../../utilities/shared-utils
|
||||
'@nextui-org/system': link:../../core/system
|
||||
'@nextui-org/theme': link:../../core/theme
|
||||
'@nextui-org/use-is-mounted': link:../../hooks/use-is-mounted
|
||||
'@react-aria/focus': 3.10.1_react@17.0.2
|
||||
'@react-aria/link': 3.3.6_react@17.0.2
|
||||
'@react-aria/utils': 3.14.2_react@17.0.2
|
||||
@ -741,9 +735,9 @@ importers:
|
||||
'@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:*
|
||||
'@nextui-org/theme': workspace:*
|
||||
'@react-aria/focus': ^3.9.0
|
||||
'@react-aria/utils': ^3.14.0
|
||||
clean-package: 2.1.1
|
||||
@ -751,13 +745,13 @@ importers:
|
||||
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
|
||||
'@nextui-org/theme': link:../../core/theme
|
||||
'@react-aria/focus': 3.10.1_react@17.0.2
|
||||
'@react-aria/utils': 3.14.2_react@17.0.2
|
||||
devDependencies:
|
||||
'@nextui-org/link': link:../link
|
||||
clean-package: 2.1.1
|
||||
react: 17.0.2
|
||||
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user