make avatar

This commit is contained in:
Nate Kelley 2025-02-26 14:06:23 -07:00
parent bf377b5c80
commit 715132a7bc
No known key found for this signature in database
GPG Key ID: FD90372AB8D98B4F
10 changed files with 205 additions and 37 deletions

27
web/package-lock.json generated
View File

@ -17,6 +17,7 @@
"@manufac/echarts-simple-transform": "^2.0.11",
"@million/lint": "^1.0.14",
"@monaco-editor/react": "^4.7.0",
"@radix-ui/react-avatar": "^1.1.3",
"@radix-ui/react-checkbox": "^1.1.4",
"@radix-ui/react-dropdown-menu": "^2.1.6",
"@radix-ui/react-popover": "^1.1.6",
@ -5626,6 +5627,32 @@
}
}
},
"node_modules/@radix-ui/react-avatar": {
"version": "1.1.3",
"resolved": "https://registry.npmjs.org/@radix-ui/react-avatar/-/react-avatar-1.1.3.tgz",
"integrity": "sha512-Paen00T4P8L8gd9bNsRMw7Cbaz85oxiv+hzomsRZgFm2byltPFDtfcoqlWJ8GyZlIBWgLssJlzLCnKU0G0302g==",
"license": "MIT",
"dependencies": {
"@radix-ui/react-context": "1.1.1",
"@radix-ui/react-primitive": "2.0.2",
"@radix-ui/react-use-callback-ref": "1.1.0",
"@radix-ui/react-use-layout-effect": "1.1.0"
},
"peerDependencies": {
"@types/react": "*",
"@types/react-dom": "*",
"react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc",
"react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
},
"peerDependenciesMeta": {
"@types/react": {
"optional": true
},
"@types/react-dom": {
"optional": true
}
}
},
"node_modules/@radix-ui/react-checkbox": {
"version": "1.1.4",
"resolved": "https://registry.npmjs.org/@radix-ui/react-checkbox/-/react-checkbox-1.1.4.tgz",

View File

@ -26,6 +26,7 @@
"@manufac/echarts-simple-transform": "^2.0.11",
"@million/lint": "^1.0.14",
"@monaco-editor/react": "^4.7.0",
"@radix-ui/react-avatar": "^1.1.3",
"@radix-ui/react-checkbox": "^1.1.4",
"@radix-ui/react-dropdown-menu": "^2.1.6",
"@radix-ui/react-popover": "^1.1.6",

View File

@ -0,0 +1,51 @@
import type { Meta, StoryObj } from '@storybook/react';
import { Avatar } from './Avatar';
const meta = {
title: 'Base/Avatar',
component: Avatar,
parameters: {
layout: 'centered'
},
tags: ['autodocs']
} satisfies Meta<typeof Avatar>;
export default meta;
type Story = StoryObj<typeof Avatar>;
export const Default: Story = {
args: {
name: 'John Doe'
}
};
export const WithImage: Story = {
args: {
name: 'John Doe',
image: 'https://github.com/shadcn.png'
}
};
export const WithTooltip: Story = {
args: {
name: 'John Doe',
useToolTip: true
}
};
export const CustomClassName: Story = {
args: {
name: 'John Doe',
className: 'h-12 w-12'
}
};
export const SingleLetter: Story = {
args: {
name: 'John'
}
};
export const NoName: Story = {
args: {}
};

View File

@ -0,0 +1,54 @@
import React from 'react';
import { Avatar as AvatarBase, AvatarFallback, AvatarImage } from './AvatarBase';
import { getFirstTwoCapitalizedLetters } from '@/lib/text';
import { Tooltip } from '../tooltip/Tooltip';
import { BusterLogo } from '@/assets/svg/BusterLogo';
import { cn } from '@/lib/utils';
export interface BusterUserAvatarProps {
image?: string;
name?: string | null;
className?: string;
useToolTip?: boolean;
}
export const Avatar: React.FC<BusterUserAvatarProps> = React.memo(
({ image, name, className, useToolTip }) => {
const hasName = !!name;
const nameLetters = hasName ? createNameLetters(name, image) : '';
return (
<Tooltip title={useToolTip ? nameLetters : ''}>
<AvatarBase className={className}>
{image && <AvatarImage src={image} />}
<AvatarFallback className={cn(!hasName && 'border bg-white')}>
{nameLetters || <BusterAvatarFallback />}
</AvatarFallback>
</AvatarBase>
</Tooltip>
);
}
);
Avatar.displayName = 'Avatar';
const BusterAvatarFallback: React.FC = () => {
return (
<div className="flex h-full w-full items-center justify-center text-black dark:text-white">
<BusterLogo className="h-full w-full" />
</div>
);
};
const createNameLetters = (name?: string | null, image?: string | null | React.ReactNode) => {
if (name && !image) {
const firstTwoLetters = getFirstTwoCapitalizedLetters(name);
if (firstTwoLetters.length == 2) return firstTwoLetters;
//Get First Name Initial
const _name = name.split(' ') as [string, string];
const returnName = `${_name[0][0]}`.toUpperCase().replace('@', '');
return returnName;
}
return '';
};

View File

@ -0,0 +1,47 @@
'use client';
import * as React from 'react';
import * as AvatarPrimitive from '@radix-ui/react-avatar';
import { cn } from '@/lib/utils';
const Avatar = React.forwardRef<
React.ElementRef<typeof AvatarPrimitive.Root>,
React.ComponentPropsWithoutRef<typeof AvatarPrimitive.Root>
>(({ className, ...props }, ref) => (
<AvatarPrimitive.Root
ref={ref}
className={cn('relative flex h-7 w-7 shrink-0 overflow-hidden rounded-full', className)}
{...props}
/>
));
Avatar.displayName = AvatarPrimitive.Root.displayName;
const AvatarImage = React.forwardRef<
React.ElementRef<typeof AvatarPrimitive.Image>,
React.ComponentPropsWithoutRef<typeof AvatarPrimitive.Image>
>(({ className, ...props }, ref) => (
<AvatarPrimitive.Image
ref={ref}
className={cn('aspect-square h-full w-full', className)}
{...props}
/>
));
AvatarImage.displayName = AvatarPrimitive.Image.displayName;
const AvatarFallback = React.forwardRef<
React.ElementRef<typeof AvatarPrimitive.Fallback>,
React.ComponentPropsWithoutRef<typeof AvatarPrimitive.Fallback>
>(({ className, ...props }, ref) => (
<AvatarPrimitive.Fallback
ref={ref}
className={cn(
'bg-gray-light/70 text-background flex h-full w-full items-center justify-center rounded-full text-base',
className
)}
{...props}
/>
));
AvatarFallback.displayName = AvatarPrimitive.Fallback.displayName;
export { Avatar, AvatarImage, AvatarFallback };

View File

@ -2,7 +2,7 @@ import type { Meta, StoryObj } from '@storybook/react';
import { Card, CardHeader, CardFooter, CardTitle, CardDescription, CardContent } from './CardBase';
const meta = {
title: 'Base/Card',
title: 'Base/Cards/Card',
component: Card,
parameters: {
layout: 'centered'

View File

@ -4,7 +4,7 @@ import { BellOutlined } from '@ant-design/icons';
import { faker } from '@faker-js/faker';
const meta: Meta<typeof InfoCard> = {
title: 'Base/InfoCard',
title: 'Base/Cards/InfoCard',
component: InfoCard,
tags: ['autodocs'],
args: {

View File

@ -1,34 +0,0 @@
import { createStyles } from 'antd-style';
import React from 'react';
const useStyles = createStyles(({ token, css }) => ({
container: css`
border-radius: ${token.borderRadius}px;
border: 0.5px solid ${token.colorBorder};
`,
header: css`
color: ${token.colorText};
background: ${token.controlItemBgActive};
border-bottom: 0.5px solid ${token.colorBorder};
height: 36px;
`,
body: css`
background: ${token.colorBgBase};
`
}));
export const ItemContainer: React.FC<{
children: React.ReactNode;
title: string | React.ReactNode;
bodyClass?: string;
className?: string;
}> = ({ className = '', bodyClass = '', children, title }) => {
const { styles, cx } = useStyles();
return (
<div className={cx(styles.container, className)}>
<div className={cx(styles.header, 'flex items-center py-1 pr-3 pl-4')}>{title}</div>
<div className={cx(styles.body, bodyClass || 'px-4 py-5')}>{children}</div>
</div>
);
};

View File

@ -19,6 +19,8 @@ export interface TooltipProps
export const Tooltip = React.memo<TooltipProps>(
({ children, title, shortcut, delayDuration, skipDelayDuration, align, side, open }) => {
if (!title && !shortcut?.length) return children;
return (
<TooltipProvider delayDuration={delayDuration} skipDelayDuration={skipDelayDuration}>
<TooltipBase open={open}>

View File

@ -11,10 +11,17 @@ import { useAntToken } from '@/styles/useAntToken';
import { AppCodeEditor } from '@/components/ui/inputs/AppCodeEditor';
import clamp from 'lodash/clamp';
import { MenuProps } from 'antd/lib';
import { ItemContainer } from '@/components/ui/card/ItemContainer';
import { Text } from '@/components/ui';
import { BusterRoutes } from '@/routes';
import { useAppLayoutContextSelector } from '@/context/BusterAppLayout';
import {
Card,
CardHeader,
CardFooter,
CardTitle,
CardDescription,
CardContent
} from '@/components/ui/card/CardBase';
export const TermIndividualContent: React.FC<{
termId: string;
@ -209,3 +216,16 @@ const MoreDropdown: React.FC<{ termId: string; setEditingTermName: (value: boole
</Dropdown>
);
};
const ItemContainer: React.FC<{
title: string | React.ReactNode;
children: React.ReactNode;
className?: string;
}> = ({ title, children, className }) => {
return (
<Card className={className}>
<CardHeader variant={'gray'}>{title}</CardHeader>
<CardContent>{children}</CardContent>
</Card>
);
};