diff --git a/web/package-lock.json b/web/package-lock.json index 2eb5628d4..a78c46342 100644 --- a/web/package-lock.json +++ b/web/package-lock.json @@ -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", diff --git a/web/package.json b/web/package.json index 3ba0e4d1e..117e04a3a 100644 --- a/web/package.json +++ b/web/package.json @@ -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", diff --git a/web/src/components/ui/avatar/Avatar.stories.tsx b/web/src/components/ui/avatar/Avatar.stories.tsx new file mode 100644 index 000000000..44c6f63fb --- /dev/null +++ b/web/src/components/ui/avatar/Avatar.stories.tsx @@ -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; + +export default meta; +type Story = StoryObj; + +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: {} +}; diff --git a/web/src/components/ui/avatar/Avatar.tsx b/web/src/components/ui/avatar/Avatar.tsx new file mode 100644 index 000000000..71e0106ad --- /dev/null +++ b/web/src/components/ui/avatar/Avatar.tsx @@ -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 = React.memo( + ({ image, name, className, useToolTip }) => { + const hasName = !!name; + const nameLetters = hasName ? createNameLetters(name, image) : ''; + + return ( + + + {image && } + + {nameLetters || } + + + + ); + } +); +Avatar.displayName = 'Avatar'; + +const BusterAvatarFallback: React.FC = () => { + return ( +
+ +
+ ); +}; + +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 ''; +}; diff --git a/web/src/components/ui/avatar/AvatarBase.tsx b/web/src/components/ui/avatar/AvatarBase.tsx new file mode 100644 index 000000000..f6bf55b0d --- /dev/null +++ b/web/src/components/ui/avatar/AvatarBase.tsx @@ -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, + React.ComponentPropsWithoutRef +>(({ className, ...props }, ref) => ( + +)); +Avatar.displayName = AvatarPrimitive.Root.displayName; + +const AvatarImage = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, ...props }, ref) => ( + +)); +AvatarImage.displayName = AvatarPrimitive.Image.displayName; + +const AvatarFallback = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, ...props }, ref) => ( + +)); +AvatarFallback.displayName = AvatarPrimitive.Fallback.displayName; + +export { Avatar, AvatarImage, AvatarFallback }; diff --git a/web/src/components/ui/card/CardBase.stories.tsx b/web/src/components/ui/card/CardBase.stories.tsx index 3bc3528c5..9ef51bf1b 100644 --- a/web/src/components/ui/card/CardBase.stories.tsx +++ b/web/src/components/ui/card/CardBase.stories.tsx @@ -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' diff --git a/web/src/components/ui/card/InfoCard.stories.tsx b/web/src/components/ui/card/InfoCard.stories.tsx index 17515e1f5..7f183946e 100644 --- a/web/src/components/ui/card/InfoCard.stories.tsx +++ b/web/src/components/ui/card/InfoCard.stories.tsx @@ -4,7 +4,7 @@ import { BellOutlined } from '@ant-design/icons'; import { faker } from '@faker-js/faker'; const meta: Meta = { - title: 'Base/InfoCard', + title: 'Base/Cards/InfoCard', component: InfoCard, tags: ['autodocs'], args: { diff --git a/web/src/components/ui/card/ItemContainer.tsx b/web/src/components/ui/card/ItemContainer.tsx deleted file mode 100644 index 3a68bc47b..000000000 --- a/web/src/components/ui/card/ItemContainer.tsx +++ /dev/null @@ -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 ( -
-
{title}
-
{children}
-
- ); -}; diff --git a/web/src/components/ui/tooltip/Tooltip.tsx b/web/src/components/ui/tooltip/Tooltip.tsx index 018c9a44d..8c2cd175e 100644 --- a/web/src/components/ui/tooltip/Tooltip.tsx +++ b/web/src/components/ui/tooltip/Tooltip.tsx @@ -19,6 +19,8 @@ export interface TooltipProps export const Tooltip = React.memo( ({ children, title, shortcut, delayDuration, skipDelayDuration, align, side, open }) => { + if (!title && !shortcut?.length) return children; + return ( diff --git a/web/src/controllers/TermIndividualController/TermIndividualContent.tsx b/web/src/controllers/TermIndividualController/TermIndividualContent.tsx index 8f30f23bd..926ea492e 100644 --- a/web/src/controllers/TermIndividualController/TermIndividualContent.tsx +++ b/web/src/controllers/TermIndividualController/TermIndividualContent.tsx @@ -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 ); }; + +const ItemContainer: React.FC<{ + title: string | React.ReactNode; + children: React.ReactNode; + className?: string; +}> = ({ title, children, className }) => { + return ( + + {title} + {children} + + ); +};