fix(web): unify workspace avatar usage (#39160)
This commit is contained in:
@@ -0,0 +1,43 @@
|
||||
import type { Meta, StoryObj } from '@storybook/nextjs-vite'
|
||||
import { WorkspaceAvatar } from '.'
|
||||
|
||||
const meta = {
|
||||
title: 'Base/General/WorkspaceAvatar',
|
||||
component: WorkspaceAvatar,
|
||||
parameters: {
|
||||
docs: {
|
||||
description: {
|
||||
component: 'Square workspace avatar with the shared workspace fallback-initial treatment.',
|
||||
},
|
||||
},
|
||||
},
|
||||
tags: ['autodocs'],
|
||||
args: {
|
||||
name: 'Solar Studio',
|
||||
size: 'sm',
|
||||
},
|
||||
} satisfies Meta<typeof WorkspaceAvatar>
|
||||
|
||||
export default meta
|
||||
type Story = StoryObj<typeof meta>
|
||||
|
||||
export const Default: Story = {}
|
||||
|
||||
export const Sizes: Story = {
|
||||
render: (args) => (
|
||||
<div className="flex items-end gap-4">
|
||||
{(['xs', 'sm', 'lg', '2xl'] as const).map((size) => (
|
||||
<div key={size} className="flex flex-col items-center gap-2">
|
||||
<WorkspaceAvatar {...args} size={size} />
|
||||
<span className="text-xs text-text-tertiary uppercase">{size}</span>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
),
|
||||
}
|
||||
|
||||
export const MissingName: Story = {
|
||||
args: {
|
||||
name: undefined,
|
||||
},
|
||||
}
|
||||
@@ -0,0 +1,47 @@
|
||||
'use client'
|
||||
|
||||
import type { AvatarSize } from '@langgenius/dify-ui/avatar'
|
||||
import { AvatarFallback, AvatarRoot } from '@langgenius/dify-ui/avatar'
|
||||
import { cn } from '@langgenius/dify-ui/cn'
|
||||
|
||||
export type WorkspaceAvatarSize = Extract<AvatarSize, 'xs' | 'sm' | 'lg' | '2xl'>
|
||||
|
||||
const workspaceAvatarClasses: Record<WorkspaceAvatarSize, { root: string; text: string }> = {
|
||||
xs: { root: 'rounded-md', text: 'text-[10px] leading-3' },
|
||||
sm: { root: 'rounded-md', text: 'text-[13px] leading-4' },
|
||||
lg: { root: 'rounded-lg', text: 'text-base leading-5' },
|
||||
'2xl': { root: 'rounded-xl', text: 'text-xl leading-7' },
|
||||
}
|
||||
|
||||
export function WorkspaceAvatar({
|
||||
name,
|
||||
size = 'sm',
|
||||
className,
|
||||
}: {
|
||||
name?: string
|
||||
size?: WorkspaceAvatarSize
|
||||
className?: string
|
||||
}) {
|
||||
return (
|
||||
<AvatarRoot
|
||||
size={size}
|
||||
className={cn('shadow-xs', workspaceAvatarClasses[size].root, className)}
|
||||
>
|
||||
<AvatarFallback
|
||||
size={size}
|
||||
className={cn(
|
||||
'border border-divider-regular bg-components-icon-bg-orange-dark-solid bg-linear-to-br from-components-avatar-bg-mask-stop-0 to-components-avatar-bg-mask-stop-100 p-1',
|
||||
)}
|
||||
>
|
||||
<span
|
||||
className={cn(
|
||||
'bg-linear-to-r from-components-avatar-shape-fill-stop-0 to-components-avatar-shape-fill-stop-100 bg-clip-text font-semibold text-shadow-shadow-1 text-transparent uppercase opacity-90',
|
||||
workspaceAvatarClasses[size].text,
|
||||
)}
|
||||
>
|
||||
{name?.[0]?.toLocaleUpperCase() || '?'}
|
||||
</span>
|
||||
</AvatarFallback>
|
||||
</AvatarRoot>
|
||||
)
|
||||
}
|
||||
@@ -8,6 +8,7 @@ import {
|
||||
} from '@langgenius/dify-ui/select'
|
||||
import { memo } from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import { WorkspaceAvatar } from '@/app/components/base/workspace-avatar'
|
||||
import { Plan } from '@/app/components/billing/type'
|
||||
import { PlanBadge } from '@/app/components/header/plan-badge'
|
||||
|
||||
@@ -32,11 +33,7 @@ const WorkplaceSelectorItem = memo(({ workspace }: WorkplaceSelectorItemProps) =
|
||||
|
||||
return (
|
||||
<SelectItem value={workspace.id} className="gap-2 py-1 pr-2 pl-3">
|
||||
<div className="flex h-6 w-6 shrink-0 items-center justify-center rounded-md bg-components-icon-bg-blue-solid text-[13px]">
|
||||
<span className="h-6 bg-linear-to-r from-components-avatar-shape-fill-stop-0 to-components-avatar-shape-fill-stop-100 bg-clip-text align-middle leading-6 font-semibold text-shadow-shadow-1 uppercase opacity-90">
|
||||
{workspaceName[0]?.toLocaleUpperCase()}
|
||||
</span>
|
||||
</div>
|
||||
<WorkspaceAvatar name={workspaceName} size="sm" />
|
||||
<SelectItemText className="system-md-regular">{workspaceName}</SelectItemText>
|
||||
<PlanBadge plan={workspacePlan} />
|
||||
</SelectItem>
|
||||
|
||||
@@ -8,6 +8,7 @@ import { useSuspenseQuery } from '@tanstack/react-query'
|
||||
import { useAtomValue } from 'jotai'
|
||||
import { useCallback, useState } from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import { WorkspaceAvatar } from '@/app/components/base/workspace-avatar'
|
||||
import { NUM_INFINITE } from '@/app/components/billing/config'
|
||||
import { Plan } from '@/app/components/billing/type'
|
||||
import UpgradeBtn from '@/app/components/billing/upgrade-btn'
|
||||
@@ -96,11 +97,7 @@ const MembersPage = () => {
|
||||
<>
|
||||
<div className="flex flex-col">
|
||||
<div className="mb-6 flex items-center gap-3 rounded-xl border-t-[0.5px] border-l-[0.5px] border-divider-subtle bg-linear-to-r from-background-gradient-bg-fill-chat-bg-2 to-background-gradient-bg-fill-chat-bg-1 py-2 pr-5 pl-2">
|
||||
<div className="flex h-12 w-12 items-center justify-center rounded-xl bg-components-icon-bg-blue-solid text-[20px]">
|
||||
<span className="bg-linear-to-r from-components-avatar-shape-fill-stop-0 to-components-avatar-shape-fill-stop-100 bg-clip-text font-semibold text-shadow-shadow-1 uppercase opacity-90">
|
||||
{currentWorkspace?.name[0]?.toLocaleUpperCase()}
|
||||
</span>
|
||||
</div>
|
||||
<WorkspaceAvatar name={currentWorkspace.name} size="2xl" />
|
||||
<div className="grow">
|
||||
<div className="flex items-center gap-1 system-md-semibold text-text-secondary">
|
||||
<span>{currentWorkspace?.name}</span>
|
||||
|
||||
@@ -8,6 +8,7 @@ import { useMutation, useQuery } from '@tanstack/react-query'
|
||||
import { useAtomValue } from 'jotai'
|
||||
import { useState } from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import { WorkspaceAvatar } from '@/app/components/base/workspace-avatar'
|
||||
import { Plan } from '@/app/components/billing/type'
|
||||
import { ACCOUNT_SETTING_TAB } from '@/app/components/header/account-setting/constants'
|
||||
import LicenseNav from '@/app/components/header/license-env'
|
||||
@@ -21,7 +22,7 @@ import { consoleQuery } from '@/service/client'
|
||||
import { hasPermission } from '@/utils/permission'
|
||||
import { basePath } from '@/utils/var'
|
||||
import { formatCredits, getRemainingCredits } from '../utils'
|
||||
import { WorkspaceIcon, WorkspaceMenuItemContent } from './workspace-menu-content'
|
||||
import { WorkspaceMenuItemContent } from './workspace-menu-content'
|
||||
import WorkspacePlanBadge from './workspace-plan-badge'
|
||||
import { WorkspaceSwitcher } from './workspace-switcher'
|
||||
|
||||
@@ -116,7 +117,7 @@ function WorkspaceCardTrigger({
|
||||
open && 'bg-linear-to-b from-background-section-burn to-background-section',
|
||||
)}
|
||||
>
|
||||
<WorkspaceIcon name={name} className="h-6 w-6 rounded-lg" />
|
||||
<WorkspaceAvatar name={name} size="sm" />
|
||||
<div className="min-w-0 grow">
|
||||
<div className="flex min-w-0 items-center gap-1 pr-0.5">
|
||||
<span
|
||||
@@ -189,7 +190,7 @@ function WorkspaceMenuHeader({
|
||||
</PopoverTitle>
|
||||
{status}
|
||||
</div>
|
||||
<WorkspaceIcon name={name} className="h-9 w-9 shrink-0" />
|
||||
<WorkspaceAvatar name={name} size="lg" />
|
||||
</div>
|
||||
<button
|
||||
type="button"
|
||||
|
||||
@@ -1,19 +1,4 @@
|
||||
import type { ReactNode } from 'react'
|
||||
import { cn } from '@langgenius/dify-ui/cn'
|
||||
import { getWorkspaceInitial } from '../utils'
|
||||
|
||||
export function WorkspaceIcon({ name, className }: { name?: string; className?: string }) {
|
||||
return (
|
||||
<div
|
||||
className={cn(
|
||||
'flex h-8 w-8 shrink-0 items-center justify-center rounded-lg bg-components-icon-bg-orange-dark-solid text-white shadow-xs',
|
||||
className,
|
||||
)}
|
||||
>
|
||||
<span className="system-md-semibold">{getWorkspaceInitial(name)}</span>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export function WorkspaceMenuItemContent({
|
||||
icon,
|
||||
|
||||
@@ -13,7 +13,8 @@ import {
|
||||
import { useMemo, useState } from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import { SearchInput } from '@/app/components/base/search-input'
|
||||
import { WorkspaceIcon, WorkspaceMenuItemContent } from './workspace-menu-content'
|
||||
import { WorkspaceAvatar } from '@/app/components/base/workspace-avatar'
|
||||
import { WorkspaceMenuItemContent } from './workspace-menu-content'
|
||||
|
||||
const workspaceSwitchActionButtonClassName =
|
||||
'flex shrink-0 items-center justify-center rounded-md p-0.5 text-text-tertiary outline-hidden hover:bg-state-base-hover hover:text-text-secondary focus-visible:ring-2 focus-visible:ring-state-accent-solid'
|
||||
@@ -194,7 +195,7 @@ export function WorkspaceSwitcher({ workspaces, onSwitchWorkspace }: WorkspaceSw
|
||||
}}
|
||||
>
|
||||
<WorkspaceMenuItemContent
|
||||
icon={<WorkspaceIcon name={workspaceName} className="h-5 w-5 rounded-md" />}
|
||||
icon={<WorkspaceAvatar name={workspaceName} size="xs" />}
|
||||
label={workspaceName}
|
||||
trailing={
|
||||
workspace.current ? (
|
||||
|
||||
@@ -1,5 +1,3 @@
|
||||
export const getWorkspaceInitial = (name?: string) => name?.[0]?.toLocaleUpperCase() || '?'
|
||||
|
||||
export const getRemainingCredits = (total: number, used: number) => Math.max(total - used, 0)
|
||||
|
||||
export const formatCredits = (value: number) => new Intl.NumberFormat().format(value)
|
||||
|
||||
Reference in New Issue
Block a user